2[WorkbenchToolAttribute(
3 name:
"Object Selection Brush",
4 description:
"Select objects by 2D or 3D radius - click or click and drag\n" +
5 "Escape to deselect everything\n" +
6 "Space to switch to selection eraser mode\n" +
7 "Shift to temporarily bypass the filter\n" +
8 "Ctrl+Click to forcefully add/remove an entity to the selection",
10 wbModules: {
"WorldEditor" },
11 awesomeFontCode: 0xF111)]
12class SCR_SelectionBrushTool : WorldEditorTool
22 protected bool m_bDetectBySphere;
25 protected bool m_bSnapToObjects;
27 [
Attribute(
desc:
"Find objects using world Trace instead of Entities with MeshObjects - 2-3× the performance cost",
category:
"Brush")]
28 protected bool m_bUseTraceDetection;
34 [
Attribute(defvalue: SCR_ESelectionBrushToolLayer.ALL_LAYERS.ToString(), uiwidget:
UIWidgets.ComboBox, enumType: SCR_ESelectionBrushToolLayer,
category:
"Selection")]
35 SCR_ESelectionBrushToolLayer m_eLayerSelection;
37 [
Attribute(defvalue:
"0",
desc:
"Select the topmost 3D parent, otherwise select the 3D world entity",
category:
"Selection")]
38 protected bool m_bSelectParentOnly;
41 protected int m_iMaxSelectedEntities;
47 [
Attribute(
desc:
"Define a list of objects to select - can take a SCR_ObjectBrushArrayConfig .conf",
category:
"Filter")]
48 protected ref SCR_SelectionBrushConfig m_ObjectsFilter;
52 protected bool m_bIsWorldValid;
53 protected bool m_bIsMouseHeldDown;
54 protected bool m_bIsInRemovalMode;
55 protected bool m_bSmiley;
56 protected bool m_bLimitReachedWarning;
62 protected ref array<ref Shape> m_aBrushShapes;
64 protected vector m_vBrushShapePos;
66 protected static const int BRUSH_SHAPE_INDEX = 3;
67 protected static const int BRUSH_COLOUR_ON_HOLD =
ARGB(64, 128, 128, 128);
68 protected static const int BRUSH_COLOUR_NORMAL =
ARGB(128, 128, 128, 0);
69 protected static const int BRUSH_COLOUR_NORMAL_2D =
ARGB(255, 0, 255, 0);
70 protected static const int BRUSH_COLOUR_GUIDE_2D =
ARGB(64, 0, 255, 0);
71 protected static const int BRUSH_COLOUR_CAN_ADD =
ARGB(128, 128, 64, 0);
72 protected static const int BRUSH_COLOUR_ADDED =
ARGB(192, 128, 0, 0);
73 protected static const int BRUSH_COLOUR_REMOVAL =
ARGB(128, 64, 64, 255);
74 protected static const int BRUSH_COLOUR_REMOVED =
ARGB(192, 0, 0, 128);
75 protected static const int BRUSH_COLOUR_ALTITUDE =
ARGB(255, 255, 0, 0);
76 protected static const int BRUSH_GUIDE_2D_HEIGHT = 1000;
77 protected static const float BRUSH_ALTITUDE_TOLERANCE = 0.5;
79 protected static const float RADIUS_MIN = 1;
80 protected static const float RADIUS_MAX = 100;
81 protected static const float RADIUS_STEP = 1;
83 protected static const int BRUSH_2D_Y_SEARCH = 10000;
85 protected static const string MESHOBJECT_CLASSNAME =
"MeshObject";
94 protected override void OnMousePressEvent(
float x,
float y, WETMouseButtonFlag buttons)
99 if (buttons != WETMouseButtonFlag.LEFT)
102 vector traceStart, traceEnd, traceDir;
104 if (m_bSnapToObjects)
107 m_API.TraceWorldPos(x, y,
flags, traceStart, traceEnd, traceDir);
109 if (traceEnd ==
vector.Zero)
112 m_bIsMouseHeldDown =
true;
115 if (entity && GetModifierKeyState(ModifierKey.CONTROL))
117 if (m_API.IsEntitySelected(entity))
118 m_API.RemoveFromEntitySelection(entity);
120 m_API.AddToEntitySelection(entity);
124 m_bSmiley =
Math.RandomFloat01() > 0.999;
125 CreateBrush(traceEnd);
126 if (m_bIsInRemovalMode)
127 DeselectAroundCursor(traceEnd);
129 SelectAroundCursor(traceEnd);
131 m_vBrushShapePos = traceEnd;
141 protected override void OnMouseMoveEvent(
float x,
float y)
143 if (!m_bIsWorldValid)
146 vector traceStart, traceEnd, traceDir;
148 if (m_bSnapToObjects)
151 m_API.TraceWorldPos(x, y,
flags, traceStart, traceEnd, traceDir);
153 if (traceEnd ==
vector.Zero)
155 m_aBrushShapes = null;
159 CreateBrush(traceEnd);
160 m_vBrushShapePos = traceEnd;
162 if (m_bIsMouseHeldDown)
164 if (m_bIsInRemovalMode)
165 DeselectAroundCursor(traceEnd);
167 SelectAroundCursor(traceEnd);
175 protected override void OnWheelEvent(
int delta)
178 if (GetModifierKeyState(ModifierKey.CONTROL))
180 m_fRadius = AdjustValueUsingScrollWheel(delta,
m_fRadius, RADIUS_MIN, RADIUS_MAX, RADIUS_STEP);
181 UpdatePropertyPanel();
184 CreateBrush(m_vBrushShapePos);
195 protected override void OnMouseReleaseEvent(
float x,
float y, WETMouseButtonFlag buttons)
197 if (!m_bIsMouseHeldDown)
200 if (buttons != WETMouseButtonFlag.LEFT)
203 m_bIsMouseHeldDown =
false;
205 SetBrushColor(BRUSH_COLOUR_ON_HOLD);
207 if (m_bLimitReachedWarning)
208 m_bLimitReachedWarning =
false;
210 Print(
"" + m_API.GetSelectedEntitiesCount() +
"/" + m_iMaxSelectedEntities +
" entities selected",
LogLevel.NORMAL);
212 m_API.UpdateSelectionGui();
220 protected override void OnKeyPressEvent(
KeyCode key,
bool isAutoRepeat)
224 m_API.ClearEntitySelection();
225 m_API.UpdateSelectionGui();
227 else if (key ==
KeyCode.KC_DELETE)
229 DeleteSelectedEntities();
231 else if (key ==
KeyCode.KC_SPACE)
233 m_bIsInRemovalMode =
true;
234 CreateBrush(m_vBrushShapePos);
243 protected override void OnKeyReleaseEvent(
KeyCode key,
bool isAutoRepeat)
247 m_bIsInRemovalMode =
false;
248 CreateBrush(m_vBrushShapePos);
255 m_bIsWorldValid = IsWorldValid();
259 protected override void OnDeActivate()
261 m_aBrushShapes = null;
265 protected override void OnAfterLoadWorld()
267 m_bIsWorldValid = IsWorldValid();
271 protected void SelectAroundCursor(
vector cursorWorldPos)
273 array<IEntity> entities = GetAllEntitiesAroundCursor(cursorWorldPos);
277 bool selectFromAllLayers = m_eLayerSelection == SCR_ESelectionBrushToolLayer.ALL_LAYERS;
278 int currentLayerId = m_API.GetCurrentEntityLayerId();
280 for (
int i = entities.Count() - 1; i >= 0; i--)
282 entity = entities[i];
283 entitySource = m_API.EntityToSource(entity);
289 if (!(selectFromAllLayers || CanSelectEntityFromItsLayer(currentLayerId, entitySource)))
295 if (!remove && !m_bUseTraceDetection && !HasMeshObject(entitySource))
298 if (!remove && m_bSelectParentOnly && entitySource != GetTopMostParentWithMeshObject(entitySource))
305 if (entities.IsEmpty())
307 SetBrushColor(BRUSH_COLOUR_NORMAL);
311 if (m_ObjectsFilter && m_ObjectsFilter.m_aPrefabs && !GetModifierKeyState(ModifierKey.SHIFT))
313 for (
int i = entities.Count() - 1; i >= 0; i--)
315 ResourceName entityPrefab = entities[i].GetPrefabData().GetPrefabName();
317 foreach (
ResourceName prefab : m_ObjectsFilter.m_aPrefabs)
319 if (prefab ==
string.Empty)
322 if (entityPrefab == prefab)
334 int selectedEntitiesCount = m_API.GetSelectedEntitiesCount();
335 foreach (
IEntity entity2 : entities)
341 if (m_iMaxSelectedEntities > 0 && selectedEntitiesCount >= m_iMaxSelectedEntities)
344 if (m_API.IsEntitySelected(src2))
347 m_API.AddToEntitySelection(src2);
348 selectedEntitiesCount++;
351 if (selectedEntitiesCount != m_API.GetSelectedEntitiesCount())
352 SetBrushColor(BRUSH_COLOUR_ADDED);
354 SetBrushColor(BRUSH_COLOUR_CAN_ADD);
356 if (m_iMaxSelectedEntities > 0 && !m_bLimitReachedWarning && selectedEntitiesCount >= m_iMaxSelectedEntities)
358 PrintFormat(
"%1/%2 entities selected", selectedEntitiesCount, m_iMaxSelectedEntities, level:
LogLevel.WARNING);
359 m_bLimitReachedWarning =
true;
364 protected bool CanSelectEntityFromItsLayer(
int currentLayerId, notnull
IEntitySource entitySource)
366 return m_eLayerSelection == SCR_ESelectionBrushToolLayer.ALL_LAYERS ||
367 (currentLayerId == entitySource.GetLayerID()) == (m_eLayerSelection == SCR_ESelectionBrushToolLayer.CURRENT_LAYER);
371 protected bool HasMeshObject(notnull
IEntitySource entitySource)
374 for (
int i, count = entitySource.GetComponentCount(); i < count; i++)
376 componentSource = entitySource.GetComponent(i);
377 if (componentSource.GetClassName() == MESHOBJECT_CLASSNAME)
392 if (HasMeshObject(parent))
393 entitySource = parent;
395 parent = parent.GetParent();
403 protected void DeselectAroundCursor(
vector cursorWorldPos)
405 array<IEntitySource> entities = {};
407 for (
int i = m_API.GetSelectedEntitiesCount() - 1; i >= 0; i--)
410 if (m_bDetectBySphere)
412 if (
vector.Distance(cursorWorldPos, m_API.SourceToEntity(entity).GetOrigin()) <=
m_fRadius)
413 entities.Insert(entity);
417 if (
vector.DistanceXZ(cursorWorldPos, m_API.SourceToEntity(entity).GetOrigin()) <=
m_fRadius)
418 entities.Insert(entity);
424 m_API.RemoveFromEntitySelection(entity);
427 if (entities.IsEmpty())
428 SetBrushColor(BRUSH_COLOUR_REMOVAL);
430 SetBrushColor(BRUSH_COLOUR_REMOVED);
434 protected array<IEntity> GetAllEntitiesAroundCursor(
vector cursorWorldPos)
436 if (m_bUseTraceDetection)
438 if (m_bDetectBySphere)
440 m_TraceSphere.Start = cursorWorldPos;
444 return SCR_WorldEditorToolHelper.TracePositionEntitiesBySphere(m_API.GetWorld(), m_TraceSphere);
448 m_TraceSphere.Start = cursorWorldPos + { 0, BRUSH_2D_Y_SEARCH * 0.5, 0 };
449 m_TraceSphere.End = cursorWorldPos - { 0, BRUSH_2D_Y_SEARCH * 0.5, 0 };
452 return SCR_WorldEditorToolHelper.TraceMoveEntitiesBySphere(m_API.GetWorld(), m_TraceSphere);
457 if (m_bDetectBySphere)
459 return SCR_WorldEditorToolHelper.QueryEntitiesBySphere(m_API.GetWorld(), cursorWorldPos,
m_fRadius);
465 return SCR_WorldEditorToolHelper.QueryEntitiesByAABB(m_API.GetWorld(), minAABB, maxAABB);
478 protected float AdjustValueUsingScrollWheel(
float delta,
float currentValue,
float min,
float max,
float step)
481 float value = currentValue + (delta / 120) * step;
493 protected void DeleteSelectedEntities()
495 int selectedEntitiesCount = m_API.GetSelectedEntitiesCount();
496 bool manageEditAction = SCR_WorldEditorToolHelper.BeginEntityAction();
497 Debug.BeginTimeMeasure();
498 for (
int i = 0; i < selectedEntitiesCount; i++)
502 m_API.DeleteEntity(entity);
504 Debug.EndTimeMeasure(
"Deleted " + selectedEntitiesCount +
" entities");
505 SCR_WorldEditorToolHelper.EndEntityAction(manageEditAction);
509 protected void CreateBrush(
vector worldPos)
511 int colour = BRUSH_COLOUR_ON_HOLD;
512 if (m_bIsMouseHeldDown)
513 colour = BRUSH_COLOUR_NORMAL;
525 m_aBrushShapes.Insert(
Shape.CreateLines(BRUSH_COLOUR_NORMAL_2D,
ShapeFlags.NOZBUFFER, points, 2));
532 m_aBrushShapes.Insert(
Shape.CreateLines(BRUSH_COLOUR_NORMAL_2D,
ShapeFlags.NOZBUFFER, points, 2));
535 if (m_bDetectBySphere)
542 if (m_bSnapToObjects && m_API.TryGetTerrainSurfaceY(worldPos[0], worldPos[2], yPos) && worldPos[1] - yPos > BRUSH_ALTITUDE_TOLERANCE)
545 vector floorPos = { worldPos[0], yPos, worldPos[2] };
546 array<vector> pointsV = {
550 floorPos + { 0, 0, floorRadius },
552 floorPos + { floorRadius, 0, 0 },
554 floorPos + { 0, 0, -floorRadius },
556 floorPos + { -floorRadius, 0, 0 },
559 for (
int i = 0, count = pointsV.Count(); i < count; i += 2)
565 m_aBrushShapes.Insert(
Shape.CreateLines(BRUSH_COLOUR_ALTITUDE,
ShapeFlags.NOZBUFFER, points, 2));
571 if (m_bIsInRemovalMode)
572 SetBrushColor(BRUSH_COLOUR_REMOVAL);
577 for (
int i = 1; i < 4; i++)
579 shape = m_aBrushShapes[i];
580 shape.SetColor(0x00000000);
591 protected void SetBrushColor(
int colour)
598 m_aBrushShapes[BRUSH_SHAPE_INDEX].SetColor(colour);
602 protected bool IsWorldValid()
608 m_API.GetWorldPath(worldPath);
609 return !worldPath.IsEmpty();
614 protected void SCR_SelectionBrushTool()
620 m_bIsWorldValid = IsWorldValid();
625class SCR_SelectionBrushConfig
627 [
Attribute(uiwidget: UIWidgets.ResourcePickerThumbnail,
desc:
"Only the listed Prefabs will be selected",
params:
"et")]
628 ref array<ResourceName> m_aPrefabs;
631enum SCR_ESelectionBrushToolLayer
SCR_EAIThreatSectorFlags flags
Shape CreateCircle(vector pos, vector aroundDir, float radius, int color, int subdivisions, ShapeFlags flags)
Shape CreateCircleArc(vector pos, vector aroundDir, vector forwardDir, float angMin, float angMax, float radius, int color, int subdivisions, ShapeFlags flags)
SCR_AIAnimation_Loitering BaseContainerProps
Commanding menu commanding element class.
override void OnActivate()
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
proto external vector GetOrigin()
Instance of created debug visualizer.
EPhysicsLayerPresets
Enum is filled by C++ by data in project config PhysicsSettings.LayerPresets.
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
proto void PrintFormat(string fmt, void param1=NULL, void param2=NULL, void param3=NULL, void param4=NULL, void param5=NULL, void param6=NULL, void param7=NULL, void param8=NULL, void param9=NULL, LogLevel level=LogLevel.NORMAL)
SCR_FieldOfViewSettings Attribute
@ CURRENT_LAYER
Current layer entity (only one entity can be current at once).
proto int ARGB(int a, int r, int g, int b)