2[WorkbenchToolAttribute(
4 description:
"Generate randomized compositions using a brush.\n" +
5 "- click and drag to draw\n" +
6 "- space to delete brush-created entities\n" +
7 "- alt+click then click to create a (wide) line of entities (ESC to cancel)",
9 wbModules: {
"WorldEditor" },
10 awesomeFontCode: 0xF1FC)]
11class SCR_ObjectBrushTool : WorldEditorTool
13 protected static const float HECTARE_CONVERSION_FACTOR = 0.0001;
14 protected static const float MAX_SCALE_THRESHOLD = 1000;
16 protected static const float RADIUS_STEP = 1;
17 protected static const float RADIUS_MAX = 100;
18 protected static const float RADIUS_MIN = 0.1;
20 protected static const float STRENGTH_STEP = 1;
21 protected static const float STRENGTH_MAX = 500;
22 protected static const float STRENGTH_MIN = 0;
24 protected static const float STRENGTH_RELATIVE_RADIUS_DISTANCE_TO_CREATE = 1 / 3;
30 [
Attribute(defvalue:
"10", uiwidget:
UIWidgets.Slider,
desc:
"Radius of the brush",
params:
string.Format(
"%1 %2 %3", RADIUS_MIN, RADIUS_MAX, RADIUS_STEP),
category:
"Object Brush")]
33 [
Attribute(defvalue:
"10", uiwidget:
UIWidgets.Slider,
desc:
"Strength of the brush (objects per hectare (100×100m))",
params:
string.Format(
"%1 %2 %3", STRENGTH_MIN, STRENGTH_MAX, STRENGTH_STEP),
category:
"Object Brush")]
34 protected float m_fStrength;
37 protected ref
Curve m_ScaleFallOffCurve;
40 protected bool m_bDensityFallOffEnabled;
43 protected ref
Curve m_DensityFallOffCurve;
45 [
Attribute(defvalue:
"10", uiwidget:
UIWidgets.SpinBox,
desc:
"Sets how many sub areas will be made; higher amount of subareas will result in a more fluid density fall off",
params:
"1 100 1",
category:
"Object Brush")]
46 protected int m_iDensityFallOffSubareaCount;
49 protected bool m_bOverrideBrush;
51 [
Attribute(
desc:
"Define objects to paint - can take a SCR_ObjectBrushArrayConfig .conf",
category:
"Object Brush")]
52 protected ref SCR_ObjectBrushArrayConfig m_ObjectsConfig;
58 [
Attribute(defvalue:
"0",
desc:
"Objects generated by the brush will avoid static objects",
category:
"Obstacles")]
59 protected bool m_bAvoidObjects;
62 protected float m_fAvoidObjectsDetectionRadius;
65 protected float m_fAvoidObjectsDetectionHeight;
67 [
Attribute(defvalue:
"0",
desc:
"Objects generated by the brush will avoid roads",
category:
"Obstacles")]
68 protected bool m_bAvoidRoads;
70 [
Attribute(defvalue:
"0",
desc:
"Objects generated by the brush will avoid rivers",
category:
"Obstacles")]
71 protected bool m_bAvoidRivers;
73 [
Attribute(defvalue:
"0",
desc:
"Objects generated by the brush will avoid power lines",
category:
"Obstacles")]
74 protected bool m_bAvoidPowerLines;
76 [
Attribute(defvalue:
"0",
desc:
"Objects generated by the brush will avoid land",
category:
"Obstacles")]
77 protected bool m_bAvoidLand;
79 [
Attribute(defvalue:
"0",
desc:
"Objects generated by the brush will avoid ocean",
category:
"Obstacles")]
80 protected bool m_bAvoidOcean;
86 [
Attribute(defvalue:
"0",
desc:
"Objects generated by the brush will avoid forests. Depends on Area Detection Radius",
category:
"Obstacles - Area")]
87 protected bool m_bAvoidForests;
89 [
Attribute(defvalue:
"0",
desc:
"Objects generated by the brush will avoid lakes. Depends on Area Detection Radius",
category:
"Obstacles - Area")]
90 protected bool m_bAvoidLakes;
92 [
Attribute(defvalue:
"100", uiwidget:
UIWidgets.Slider,
"Radius to detect areas (forests and lakes) around the brush's starting point for avoidance - performance setting. Zero is world-wide detection",
"0 1000 100",
category:
"Obstacles - Area")]
93 protected int m_iAreaDetectionRadius;
96 protected ref array<ref Shape> m_aDebugShapes = {};
99 protected ref SCR_ObstacleDetector m_ObstacleDetector;
104 protected bool m_bIsMouseHeldDown;
110 protected ref
Shape m_BrushShape;
111 protected ref array<ref Shape> m_aLineShapes = {};
113 protected bool m_bDeleteMode;
114 protected bool m_bLineMode;
115 protected bool m_bAreasDetectedByWorld;
116 protected bool m_bManageEditAction;
118 protected vector m_vFirstLinePoint;
119 protected vector m_vSecondLinePoint;
121 protected vector m_vLastMousePosition;
122 protected vector m_vLastObjectCreationCentrePosition;
124 protected int m_iBrushShapeColor =
ARGB(255, 0, 255, 0);
132 protected void CreateObjects(
float mouseX,
float mouseY,
vector position)
141 if (!m_API.IsDoingEditAction())
143 Print(
"Workbench isn't performing edit action!",
LogLevel.WARNING);
147 if (!m_ObjectsConfig || !m_ObjectsConfig.m_aObjectArray || m_ObjectsConfig.m_aObjectArray.IsEmpty())
150 float oneDividedByFallOffCount = 1 / m_iDensityFallOffSubareaCount;
151 float radiusDividedByFallOffCount =
m_fRadius * oneDividedByFallOffCount;
153 array<ref SCR_ObjectBrushObjectBase> objectsCreationData = {};
155 if (m_bDensityFallOffEnabled)
157 float subareaRadius = radiusDividedByFallOffCount;
158 float previousArea = 0;
159 float curvePoint = 0;
161 int objectGenerationAttemptMaximumAmount =
Math.Ceil(totalArea * m_fStrength);
163 SCR_ObjectBrushObjectBase obj;
164 for (
int i = 0; i < m_iDensityFallOffSubareaCount; i++)
166 float area =
Math.PI * subareaRadius * subareaRadius * HECTARE_CONVERSION_FACTOR;
167 area -= previousArea;
168 previousArea += area;
169 subareaRadius += radiusDividedByFallOffCount;
171 float densityFallOffMultiplier =
LegacyCurve.Curve(
ECurveType.CurveProperty2D,
Math.Clamp(curvePoint, 0, 1), m_DensityFallOffCurve)[1];
173 int objectGenerationAttemptsForSubarea =
Math.Ceil(area * m_fStrength * densityFallOffMultiplier);
174 if (objectGenerationAttemptMaximumAmount < objectGenerationAttemptsForSubarea)
175 objectGenerationAttemptsForSubarea = objectGenerationAttemptMaximumAmount;
177 for (
int x = 0; x < objectGenerationAttemptsForSubarea; x++)
179 obj = GetRandomBrushObjectData();
182 obj.m_iSubareaIndex = i + 1;
183 objectsCreationData.Insert(obj);
187 curvePoint += oneDividedByFallOffCount;
193 int objectGenerationAttempts =
Math.Ceil(area * m_fStrength);
195 SCR_ObjectBrushObjectBase obj;
196 for (
int x = 0; x < objectGenerationAttempts; x++)
198 obj = GetRandomBrushObjectData();
201 obj.m_iSubareaIndex = 1;
202 objectsCreationData.Insert(obj);
207 if (m_bOverrideBrush)
223 m_API.TraceWorldPos(mouseX, mouseY,
TraceFlags.WORLD, traceStart, traceEndManual, traceDir);
227 m_vLastObjectCreationCentrePosition = traceEndManual;
231 m_ObstacleDetector.RefreshRoadObstaclesBySphere(traceEndManual,
m_fRadius);
234 if (m_iAreaDetectionRadius > 0 && (m_bAvoidForests || m_bAvoidLakes))
236 m_ObstacleDetector.RefreshAreaObstaclesBySphere(traceEndManual, m_iAreaDetectionRadius);
237 m_bAreasDetectedByWorld =
false;
242 foreach (SCR_ObjectBrushObjectBase obj : objectsCreationData)
247 if (m_bDensityFallOffEnabled)
249 float minDistanceFromCenter = radiusDividedByFallOffCount * (obj.m_iSubareaIndex - 1);
250 float maxDistanceFromCenter = radiusDividedByFallOffCount * obj.m_iSubareaIndex;
251 point =
m_RandomGenerator.GenerateRandomPointInRadius(minDistanceFromCenter, maxDistanceFromCenter, traceEndManual);
262 if (m_Grid.IsColliding(point, obj))
265 float terrainY = world.GetSurfaceY(point[0], point[2]);
268 if (m_ObstacleDetector.HasObstacle(point))
273 if (!obj.m_bOverrideRandomization)
275 entitySource = m_API.CreateEntityExt(obj.m_Prefab,
"", m_API.GetCurrentEntityLayerId(), null, point,
vector.Zero,
TraceFlags.WORLD);
278 Print(
"Could not create entity from prefab",
LogLevel.WARNING);
282 scale = m_API.SourceToEntity(entitySource).GetScale();
286 entitySource = m_API.CreateEntity(obj.m_Prefab,
"", m_API.GetCurrentEntityLayerId(), null, point,
vector.Zero);
289 Print(
"Could not create entity from prefab",
LogLevel.WARNING);
293 if (obj.m_fMinScale == obj.m_fMaxScale)
295 scale = obj.m_fMinScale;
299 if (obj.m_fMinScale < obj.m_fMaxScale)
306 if (obj.m_bScaleFalloff)
310 Print(
"Please set radius to a value higher than 0",
LogLevel.WARNING);
314 float distanceFromCenter =
vector.DistanceXZ(traceEndManual, point);
315 float distanceFromCenterInPercent = distanceFromCenter / (
m_fRadius * 0.01);
316 float scaleFallOffMultiplier =
LegacyCurve.Curve(
ECurveType.CurveProperty2D,
Math.Clamp(distanceFromCenterInPercent * 0.01, 0, 1), m_ScaleFallOffCurve)[1];
318 scale *= scaleFallOffMultiplier;
320 if (obj.m_fLowestScaleFalloffValue > obj.m_fMaxScale)
321 Print(
"Lowest scale fall off value for " + obj.m_Prefab +
" is higher than max scale value! Check parameters!",
LogLevel.WARNING);
323 if (
scale < obj.m_fLowestScaleFalloffValue)
324 scale = obj.m_fLowestScaleFalloffValue;
326 if (
scale > MAX_SCALE_THRESHOLD)
327 scale = MAX_SCALE_THRESHOLD;
331 if (entitySource.Get(
"Flags",
flags))
339 point[1] = point[1] + obj.m_fPrefabOffsetY;
340 if (obj.m_fMinRandomVerticalOffset != 0 || obj.m_fMaxRandomVerticalOffset != 0)
341 point[1] = point[1] +
m_RandomGenerator.RandFloatXY(obj.m_fMinRandomVerticalOffset, obj.m_fMaxRandomVerticalOffset);
343 m_API.SetVariableValue(entitySource, null,
"coords", point.ToString(
false));
347 if (obj.m_bAlignToNormal)
350 if (obj.m_bOverrideRandomization && obj.m_fRandomYawAngle > 0)
352 if (entitySource.Get(
"angles",
angles))
355 m_API.SetVariableValue(entitySource, null,
"angles",
string.Format(
"%1 %2 %3",
angles[0],
angles[1],
angles[2]));
360 m_API.SourceToEntity(entitySource).GetWorldTransform(mat);
364 else if (obj.m_bOverrideRandomization)
366 if (obj.m_fRandomPitchAngle > 0)
369 if (obj.m_fRandomYawAngle > 0)
372 if (obj.m_fRandomRollAngle > 0)
376 m_Grid.AddEntry(obj, point);
379 m_API.SetVariableValue(entitySource, null,
"scale",
scale.ToString());
382 m_API.SetVariableValue(entitySource, null,
"angles",
string.Format(
"%1 %2 %3",
angles[1],
angles[0],
angles[2]));
384 entityID = m_API.SourceToEntity(entitySource).GetID();
385 m_mCreatedObjects.Insert(obj, entityID);
386 m_mActiveBrushObjects.Insert(obj, entityID);
403 foreach (SCR_ObjectBrushObjectBase obj,
EntityID entityID : m_mCreatedObjects)
405 entity = world.FindEntityByID(entityID);
408 objectsToDelete.Insert(obj, entityID);
417 if (m_bOverrideBrush)
419 if (!m_mActiveBrushObjects.Contains(obj))
420 objectsToDelete.Insert(obj, entityID);
424 objectsToDelete.Insert(obj, entityID);
429 if (objectsToDelete.IsEmpty())
432 bool manageEditAction = SCR_WorldEditorToolHelper.BeginEntityAction();
434 foreach (SCR_ObjectBrushObjectBase obj,
EntityID entID : objectsToDelete)
436 entity = world.FindEntityByID(entID);
438 m_Grid.RemoveEntry(obj);
440 m_API.DeleteEntity(m_API.EntityToSource(entity));
442 m_mCreatedObjects.Remove(obj);
445 SCR_WorldEditorToolHelper.EndEntityAction(manageEditAction);
451 protected void DeleteEntities()
453 if (m_mCreatedObjects.IsEmpty())
456 bool manageEditAction = SCR_WorldEditorToolHelper.BeginEntityAction();
466 foreach (
EntityID entityID : m_mCreatedObjects)
468 entity = world.FindEntityByID(entityID);
470 m_API.DeleteEntity(m_API.EntityToSource(entity));
475 m_mCreatedObjects.Clear();
478 m_aDebugShapes.Clear();
481 SCR_WorldEditorToolHelper.EndEntityAction(manageEditAction);
491 override void OnMousePressEvent(
float x,
float y, WETMouseButtonFlag buttons)
493 if (buttons != WETMouseButtonFlag.LEFT)
497 m_API.GetWorldPath(worldPath);
498 if (worldPath.IsEmpty())
504 if (!m_ObjectsConfig || !m_ObjectsConfig.m_aObjectArray || m_ObjectsConfig.m_aObjectArray.IsEmpty())
506 Print(
"Object Brush's Objects Config is not filled",
LogLevel.WARNING);
510 vector traceStart, traceEnd, traceDir;
511 m_API.TraceWorldPos(x, y,
TraceFlags.WORLD, traceStart, traceEnd, traceDir);
513 m_bIsMouseHeldDown =
true;
519 foreach (SCR_ObjectBrushObjectBase objBase,
EntityID entityID : m_mCreatedObjects)
521 if (world.FindEntityByID(entityID))
524 m_Grid.RemoveEntry(objBase);
525 m_mCreatedObjects.Remove(objBase);
529 CreateAndInitialiseObstacleDetector();
533 if (!m_bAreasDetectedByWorld && (m_bAvoidForests || m_bAvoidLakes) && m_iAreaDetectionRadius < 1)
535 m_ObstacleDetector.RefreshAreaObstaclesByWorld();
536 m_bAreasDetectedByWorld =
true;
540 if (!m_bLineMode && !GetModifierKeyState(ModifierKey.ALT))
542 m_bManageEditAction = SCR_WorldEditorToolHelper.BeginEntityAction();
544 m_mActiveBrushObjects.Clear();
547 DeleteObjects(traceEnd);
549 CreateObjects(x, y, traceEnd);
553 if (m_vFirstLinePoint ==
vector.Zero)
556 m_vFirstLinePoint = traceEnd;
562 bool manageEditAction = SCR_WorldEditorToolHelper.BeginEntityAction();
564 m_mActiveBrushObjects.Clear();
566 m_vSecondLinePoint = traceEnd;
568 float dist =
vector.Distance(m_vFirstLinePoint, m_vSecondLinePoint);
569 vector dir =
vector.Direction(m_vFirstLinePoint, m_vSecondLinePoint);
572 vector point = m_vFirstLinePoint;
576 p[0] = m_vFirstLinePoint;
577 p[1] = m_vSecondLinePoint;
578 m_aDebugShapes.Insert(
Shape.CreateLines(
ARGB(255, 255, 0, 0),
ShapeFlags.NOZBUFFER, p, 2));
584 DeleteObjects(point);
586 CreateObjects(x, y, point);
589 while (dist - halfRadius > 0)
591 point = point + dir * halfRadius;
599 DeleteObjects(point);
601 CreateObjects(x, y, point);
611 DeleteObjects(m_vSecondLinePoint);
613 CreateObjects(x, y, m_vSecondLinePoint);
616 m_vFirstLinePoint =
vector.Zero;
617 m_vSecondLinePoint =
vector.Zero;
619 SCR_WorldEditorToolHelper.EndEntityAction(manageEditAction);
630 override void OnMouseReleaseEvent(
float x,
float y, WETMouseButtonFlag buttons)
632 if (buttons != WETMouseButtonFlag.LEFT)
635 m_bIsMouseHeldDown =
false;
636 m_vLastObjectCreationCentrePosition =
vector.Zero;
638 SCR_WorldEditorToolHelper.EndEntityAction(m_bManageEditAction);
647 override void OnMouseMoveEvent(
float x,
float y)
649 m_aLineShapes.Clear();
651 vector traceStart, traceEnd, traceDir;
652 m_API.TraceWorldPos(x, y,
TraceFlags.WORLD, traceStart, traceEnd, traceDir);
654 m_vLastMousePosition = traceEnd;
657 m_iBrushShapeColor =
ARGB(255, 255, 0, 0);
659 m_iBrushShapeColor =
ARGB(255, 0, 255, 0);
664 if (m_vFirstLinePoint !=
vector.Zero)
668 vector fromTo = (traceEnd - m_vFirstLinePoint).Normalized();
672 points[0] = m_vFirstLinePoint + fromTo;
673 points[1] = traceEnd - fromTo;
677 m_aLineShapes.Insert(
Shape.CreateLines(m_iBrushShapeColor,
ShapeFlags.NOZBUFFER, points, 2));
680 vector offset = { -fromTo[2], 0, fromTo[0] };
681 points[0] = m_vFirstLinePoint + offset;
682 points[1] = traceEnd + offset;
683 m_aLineShapes.Insert(
Shape.CreateLines(m_iBrushShapeColor,
ShapeFlags.NOZBUFFER, points, 2));
686 offset = { fromTo[2], 0, -fromTo[0] };
687 points[0] = m_vFirstLinePoint + offset;
688 points[1] = traceEnd + offset;
689 m_aLineShapes.Insert(
Shape.CreateLines(m_iBrushShapeColor,
ShapeFlags.NOZBUFFER, points, 2));
692 if (!m_bIsMouseHeldDown)
696 DeleteObjects(traceEnd);
697 else if (m_vLastObjectCreationCentrePosition !=
vector.Zero &&
vector.DistanceXZ(m_vLastMousePosition, m_vLastObjectCreationCentrePosition) >= STRENGTH_RELATIVE_RADIUS_DISTANCE_TO_CREATE *
m_fRadius)
698 CreateObjects(x, y, traceEnd);
705 override void OnWheelEvent(
int delta)
708 if (GetModifierKeyState(ModifierKey.CONTROL))
710 m_fRadius = AdjustValueUsingScrollwheel(delta,
m_fRadius, RADIUS_MIN, RADIUS_MAX, RADIUS_STEP);
712 UpdatePropertyPanel();
716 if (GetModifierKeyState(ModifierKey.SHIFT))
718 m_fStrength = AdjustValueUsingScrollwheel(delta, m_fStrength, STRENGTH_MIN, STRENGTH_MAX, STRENGTH_STEP);
719 UpdatePropertyPanel();
728 override void OnKeyPressEvent(
KeyCode key,
bool isAutoRepeat)
732 m_bDeleteMode =
true;
733 m_iBrushShapeColor =
ARGB(255, 255, 0, 0);
736 else if (key ==
KeyCode.KC_ESCAPE)
741 m_vFirstLinePoint =
vector.Zero;
742 m_aLineShapes.Clear();
752 override void OnKeyReleaseEvent(
KeyCode key,
bool isAutoRepeat)
756 m_bDeleteMode =
false;
757 m_iBrushShapeColor =
ARGB(255, 0, 255, 0);
765 override void OnDeActivate()
768 m_bAreasDetectedByWorld =
false;
778 if (!m_API.GetWorld())
781 vector terrainMin, terrainMax;
782 m_API.GetWorld().GetBoundBox(terrainMin, terrainMax);
784 float x = terrainMax[0] - terrainMin[0];
785 float z = terrainMax[2] - terrainMin[2];
794 protected SCR_ObjectBrushObjectBase GetRandomBrushObjectData()
796 if (m_ObjectsConfig.m_aObjectArray.Count() == 1)
798 if (m_ObjectsConfig.m_aObjectArray[0].m_fWeight <= 0)
801 return SCR_ObjectBrushObjectBase.Cast(m_ObjectsConfig.m_aObjectArray[0].Clone());
804 array<float> weights = {};
805 foreach (SCR_ObjectBrushObjectBase obj : m_ObjectsConfig.m_aObjectArray)
807 weights.Insert(obj.m_fWeight);
811 if (!m_ObjectsConfig.m_aObjectArray.IsIndexValid(
index))
814 SCR_ObjectBrushObjectBase result = m_ObjectsConfig.m_aObjectArray[
index];
818 return SCR_ObjectBrushObjectBase.Cast(result.Clone());
829 protected float AdjustValueUsingScrollwheel(
float delta,
float currentValue,
float min,
float max,
float step)
832 float value = currentValue + (delta / 120) * step;
846 protected void CreateAndInitialiseObstacleDetector()
848 if (!m_ObstacleDetector)
850 m_ObstacleDetector =
new SCR_ObstacleDetector();
851 m_bAreasDetectedByWorld =
false;
854 m_ObstacleDetector.SetAvoidObjects(m_bAvoidObjects);
855 m_ObstacleDetector.SetAvoidObjectsDetectionRadius(m_fAvoidObjectsDetectionRadius);
856 m_ObstacleDetector.SetAvoidObjectsDetectionHeight(m_fAvoidObjectsDetectionHeight);
857 m_ObstacleDetector.SetAvoidRoads(m_bAvoidRoads);
858 m_ObstacleDetector.SetAvoidRivers(m_bAvoidRivers);
859 m_ObstacleDetector.SetAvoidPowerLines(m_bAvoidPowerLines);
860 m_ObstacleDetector.SetAvoidForests(m_bAvoidForests);
861 m_ObstacleDetector.SetAvoidLakes(m_bAvoidLakes);
862 m_ObstacleDetector.SetAvoidLand(m_bAvoidLand);
863 m_ObstacleDetector.SetAvoidOcean(m_bAvoidOcean);
SCR_EAIThreatSectorFlags flags
Shape CreateCircle(vector pos, vector aroundDir, float radius, int color, int subdivisions, ShapeFlags flags)
ref array< string > angles
ref RandomGenerator m_RandomGenerator
override void OnActivate()
SCR_DestructionSynchronizationComponentClass ScriptComponentClass int index
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
proto external vector GetOrigin()
static bool OrientToTerrain(out vector transform[4], BaseWorld world=null, bool noUnderwater=false, TraceParam trace=null)
Instance of created debug visualizer.
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
SCR_FieldOfViewSettings Attribute
EntityFlags
Various entity flags.
proto int ARGB(int a, int r, int g, int b)