6class WallGeneratorEntity : SCR_LineTerrainShaperGeneratorBaseEntity
13 protected bool m_bEnableMiddleObject;
19 protected bool PlaceMiddleAtVertex;
22 protected bool PlaceMiddleAtVertexOnly;
24 [
Attribute(
"0",
UIWidgets.Slider,
"Middle object pre-padding, essentially a gap between previous prefab and this",
params:
"-5 5 0.01",
category:
"Middle Object")]
25 protected float MiddleObjectPrePadding;
28 protected float MiddleObjectOffsetRight;
30 [
Attribute(
"0",
UIWidgets.Slider,
"Middle object post-padding, essentially a gap between this asset and the next one",
params:
"-5 5 0.01",
category:
"Middle Object")]
31 protected float MiddleObjectPostPadding;
34 protected float MiddleObjectOffsetUp;
41 protected bool m_bEnableFirstObject;
46 [
Attribute(
"0",
UIWidgets.Slider,
"First object pre-padding, essentially a gap between previous vertex first object",
params:
"-5 5 0.01",
category:
"First Object")]
47 protected float FirstObjectPrePadding;
49 [
Attribute(
"0",
UIWidgets.Slider,
"First object post-padding, essentially a gap between first object and the following one",
params:
"-5 5 0.01",
category:
"First Object")]
50 protected float FirstObjectPostPadding;
53 protected float FirstObjectOffsetRight;
56 protected float FirstObjectOffsetUp;
63 protected bool m_bEnableLastObject;
68 [
Attribute(
"0",
UIWidgets.Slider,
"Last object pre-padding, essentially a gap between previous vertex first object",
params:
"-5 5 0.01",
category:
"Last Object")]
69 protected float LastObjectPrePadding;
72 protected float LastObjectPostPadding;
75 protected float LastObjectOffsetRight;
78 protected float LastObjectOffsetUp;
85 protected float PrePadding;
88 protected float PostPadding;
91 protected float m_fOvershoot;
97 protected float m_fOffsetUp;
99 [
Attribute(defvalue:
"", uiwidget:
UIWidgets.Object,
"Contains wall groups which group Wall/Weight pairs by length",
category:
"Global")]
100 protected ref array<ref WallLengthGroup> m_aWallGroups;
106 [
Attribute(defvalue:
"1",
desc:
"Allow pre-padding on first wall asset in each line segment",
category:
"Other")]
107 protected bool PrePadFirst;
109 [
Attribute(defvalue:
"0",
desc:
"Precisely places entities to anchors while sacrificing wall assets contact",
category:
"Other")]
110 protected bool ExactPlacement;
112 [
Attribute(defvalue:
"0",
desc:
"Start the wall from the other end of the polyline",
category:
"Other")]
113 protected bool m_bStartFromTheEnd;
115 [
Attribute(defvalue:
"1",
desc:
"Rotate object so that its X axis is facing in the direction of the polyline segment",
category:
"Other")]
116 protected bool UseXAsForward;
119 protected bool Rotate180;
122 protected bool UseForVerySmallObjects;
128 protected bool m_bSnapToTerrain;
131 protected ref array<ResourceName> WallPrefabs;
135 protected static const float MIN_WALL_PIECE_SIZE = 0.001;
136 protected static const float MIN_WALL_SLOT_SIZE = 0.001;
138 protected ref SCR_WallGroupContainer m_WallGroupContainer;
144 protected static const int MAX_LOOPS_SAFETY = 500;
149 if (!m_bEnableGeneration)
151 Print(
"Wall generation is disabled for this shape - tick it back on before saving",
LogLevel.NORMAL);
157 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
158 if (!worldEditorAPI || worldEditorAPI.UndoOrRedoIsRestoring())
161 BaseWorld world = worldEditorAPI.GetWorld();
167 if (!m_ParentShapeSource || !m_ShapeNextPointHelper || !m_ShapeNextPointHelper.IsValid())
170 ShapeEntity shapeEntity = ShapeEntity.Cast(worldEditorAPI.SourceToEntity(m_ParentShapeSource));
174 float rotationOffset;
178 rotationOffset = -90;
183 rotationOffset += 180;
185 if (m_bStartFromTheEnd)
186 rotationOffset += 180;
188 MEASUREMENTS.Clear();
190 array<vector> anchorPoints = m_ShapeNextPointHelper.GetAnchorPoints();
192 typename type = m_ParentShapeSource.GetClassName().ToType();
193 bool isPolyline =
type &&
type.IsInherited(PolylineShapeEntity);
196 bool isStraightLineMode = isPolyline;
200 if (!m_WallGroupContainer)
201 m_WallGroupContainer =
new SCR_WallGroupContainer(m_aWallGroups, UseXAsForward, MiddleObject);
203 if (isStraightLineMode)
204 GenerateInStraightLine(anchorPoints, rotationOffset, forwardAxis);
206 GenerateClosestToShape(anchorPoints, rotationOffset);
212 protected void GenerateInStraightLine(notnull array<vector> anchorPoints,
float rotationOffset,
int forwardAxis)
214 map<int, ref ShapePointDataScriptBase> pointDataMap = GetFirstPointDataMap(WallGeneratorPointData);
215 WallGeneratorPointData pointData;
217 vector from = anchorPoints[0];
218 vector to, dir, prevDir, rightVec;
220 bool generate =
true;
221 bool exhausted, lastPoint, lastSegment, firstPass;
222 ResourceName customMesh;
227 float lastObjectLength;
229 if (m_bEnableLastObject && LastObject)
230 lastObjectLength = MeasureEntity(LastObject, forwardAxis);
232 bool isGeneratorVisible = _WB_GetEditorAPI().IsEntityVisible(m_Source);
234 int countMinus1 = anchorPoints.Count() - 1;
235 foreach (
int i, vector anchorPoint : anchorPoints)
238 lastPoint = i == countMinus1;
239 lastSegment = i == countMinus1 - 1;
248 from = anchorPoint + (dir * PrePadding);
254 dir = vector.Direction(anchorPoints[i - 1], to).Normalized();
258 to = anchorPoints[i + 1];
259 dir = vector.Direction(from, to).Normalized();
264 pointData = WallGeneratorPointData.Cast(pointDataMap.Get(i));
267 customMesh = pointData.MeshAtPoint;
268 generate = pointData.m_bGenerate;
272 customMesh = ResourceName.Empty;
275 remaining = vector.Distance(from, to) + m_fOvershoot;
279 rightVec = dir * vector.Up;
285 ResourceName bestWall = ResourceName.Empty;
287 float prePaddingToUse = PrePadding;
288 float postPaddingToUse = PostPadding;
289 bool allowClipping =
false;
290 bool alignNext =
true;
291 bool prepadNext =
true;
294 vector offsetRight = vector.Zero;
295 bool firstPlaced =
false;
296 bool placeMiddle =
false;
297 bool placeLast =
false;
298 float lengthRequirement = 0;
299 bool lastPlaced =
false;
300 bool lastInSegmentDoNotPlace =
false;
301 bool middleOfSegmentDoNotPlace =
false;
304 if (i == 0 && firstPass && m_bEnableFirstObject && FirstObject)
306 bestWall = FirstObject;
307 bestLen = MeasureEntity(FirstObject, forwardAxis);
308 prePaddingToUse = FirstObjectPrePadding;
309 postPaddingToUse = FirstObjectPostPadding;
311 offsetRight = rightVec * FirstObjectOffsetRight;
312 offsetUp = FirstObjectOffsetUp;
317 bestWall = customMesh;
318 prePaddingToUse = pointData.PrePadding;
319 postPaddingToUse = pointData.PostPadding;
320 allowClipping = pointData.m_bAllowClipping;
321 offsetUp = pointData.m_fOffsetUp;
322 alignNext = pointData.m_bAlignWithNext;
324 bestLen = MeasureEntity(customMesh, forwardAxis);
325 customMesh =
string.Empty;
330 wall = m_WallGroupContainer.GetRandomWall(remaining,
m_RandomGenerator.RandFloat01());
333 bestLen = wall.m_fWallLength;
334 bestWall = wall.m_sWallAsset;
335 prePaddingToUse += wall.m_fPrePadding;
336 postPaddingToUse += wall.m_fPostPadding;
345 if (bestLen + PrePadding <= 0)
348 Print(
"PrePadding is set too low, not using it in this instance",
LogLevel.WARNING);
351 if (!PrePadFirst && firstPass)
354 PlacePrefab(generate, bestWall, from, dir, prevDir, rotationOffset, isGeneratorVisible, bestLen, prePaddingToUse, postPaddingToUse, offsetUp, alignNext, prepadNext,
false, allowClipping, offsetRight);
356 remaining -= (bestLen * !allowClipping) + prePaddingToUse + postPaddingToUse;
358 placeMiddle = !custom && m_bEnableMiddleObject && MiddleObject;
359 placeLast = lastSegment && m_bEnableLastObject && LastObject;
361 lengthRequirement = m_WallGroupContainer.m_fSmallestWall;
362 if (lengthRequirement == 0)
366 lengthRequirement += m_WallGroupContainer.m_fMiddleObjectLength;
369 lengthRequirement += lastObjectLength;
371 if (remaining < lengthRequirement)
377 if (exhausted && placeLast)
379 bestWall = LastObject;
380 bestLen = lastObjectLength;
381 prePaddingToUse = LastObjectPrePadding;
382 postPaddingToUse = 0;
383 offsetRight = rightVec * LastObjectOffsetRight;
384 offsetUp = LastObjectOffsetUp;
386 PlacePrefab(generate, bestWall, from, dir, prevDir, rotationOffset, isGeneratorVisible, bestLen, prePaddingToUse, postPaddingToUse, offsetUp, alignNext, prepadNext,
false, allowClipping, offsetRight);
388 remaining -= (bestLen * !allowClipping) + prePaddingToUse + postPaddingToUse;
394 lastInSegmentDoNotPlace = exhausted && !(PlaceMiddleAtVertex || PlaceMiddleAtVertexOnly);
397 middleOfSegmentDoNotPlace = (!exhausted && PlaceMiddleAtVertexOnly) || (PlaceMiddleAtVertexOnly && (lastPlaced || firstPlaced));
399 if (!lastInSegmentDoNotPlace && !middleOfSegmentDoNotPlace)
401 from += dir * MiddleObjectPrePadding;
402 offsetRight = rightVec * MiddleObjectOffsetRight;
403 PlacePrefab(generate, MiddleObject, from, dir, prevDir, rotationOffset, isGeneratorVisible, m_WallGroupContainer.m_fMiddleObjectLength, MiddleObjectPrePadding, MiddleObjectPostPadding, MiddleObjectOffsetUp,
true, prepadNext,
true,
false, offsetRight);
404 remaining -= m_WallGroupContainer.m_fMiddleObjectLength + MiddleObjectPrePadding + MiddleObjectPostPadding;
414 protected void GenerateClosestToShape(notnull array<vector> anchorPoints,
float rotationOffset)
416 const bool isPolyline =
false;
418 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
419 int anchorPointsCountMinus1 = anchorPoints.Count() - 1;
420 map<int, ref ShapePointDataScriptBase> pointDataMap = GetFirstPointDataMap(WallGeneratorPointData);
421 vector lastPoint = anchorPoints[anchorPointsCountMinus1];
422 vector prevPoint = anchorPoints[0];
424 bool generateSection =
true;
426 SCR_WallPair wallPair;
427 WallGeneratorPointData wallGeneratorPointData;
428 foreach (
int anchorIndex, vector anchorPos : anchorPoints)
430 wallGeneratorPointData = WallGeneratorPointData.Cast(pointDataMap.Get(anchorIndex));
431 if (wallGeneratorPointData)
432 generateSection = wallGeneratorPointData.m_bGenerate;
434 if (!generateSection)
442 if (anchorIndex == anchorPointsCountMinus1)
444 vector lastDirection = m_ShapeNextPointHelper.GetCurrentDirection();
445 vector
angles = lastDirection.VectorToAngles();
446 angles[1] = Math.Repeat(
angles[0] + rotationOffset, 360);
450 vector right = lastDirection * -vector.Up;
452 if (m_bEnableLastObject && LastObject)
455 worldEditorAPI.CreateEntity(
461 + LastObjectPrePadding * lastDirection
462 + LastObjectOffsetRight * right
463 + LastObjectOffsetUp * vector.Up,
468 if (wallGeneratorPointData && wallGeneratorPointData.m_bGenerate && wallGeneratorPointData.MeshAtPoint)
471 worldEditorAPI.CreateEntity(
472 wallGeneratorPointData.MeshAtPoint,
477 + wallGeneratorPointData.PrePadding * lastDirection
478 + wallGeneratorPointData.m_fOffsetUp * vector.Up,
486 int nextAnchorIndex = anchorIndex + 1;
488 bool generatedAnchorPrefab;
489 if (anchorIndex == 0 && m_bEnableFirstObject && FirstObject)
493 worldEditorAPI, s_World, FirstObject,
494 prevPoint, FirstObjectOffsetUp * vector.Up + FirstObjectOffsetRight * vector.Right,
495 FirstObjectPrePadding, FirstObjectPostPadding,
496 rotationOffset, nextAnchorIndex))
497 generatedAnchorPrefab =
true;
500 if (!generatedAnchorPrefab && wallGeneratorPointData && wallGeneratorPointData.MeshAtPoint)
504 m_ShapeNextPointHelper.SetOnAnchor(anchorIndex);
505 prevPoint = anchorPos;
510 worldEditorAPI, s_World, wallGeneratorPointData.MeshAtPoint,
511 prevPoint, wallGeneratorPointData.m_fOffsetUp * vector.Up,
512 wallGeneratorPointData.PrePadding, wallGeneratorPointData.PostPadding,
513 rotationOffset, nextAnchorIndex))
514 generatedAnchorPrefab =
true;
517 if (!generatedAnchorPrefab && m_bEnableMiddleObject && (PlaceMiddleAtVertex || PlaceMiddleAtVertexOnly) && MiddleObject)
521 worldEditorAPI, s_World, MiddleObject,
522 prevPoint, MiddleObjectOffsetUp * vector.Up + MiddleObjectOffsetRight * vector.Right,
523 MiddleObjectPrePadding, MiddleObjectPostPadding,
524 rotationOffset, nextAnchorIndex))
525 generatedAnchorPrefab =
true;
541int maxLoops1 = MAX_LOOPS_SAFETY;
546Print(
"breaking while #1");
549 float length = wallPair.m_fWallLength + PrePadding + PostPadding;
550 if (length < MIN_WALL_SLOT_SIZE)
553 "Invalid wall length/prePadding/postPadding combination (%1/%2/%3 = %4) - using default %5",
554 wallPair.m_fWallLength,
560 length = MIN_WALL_SLOT_SIZE;
565int maxLoops2 = MAX_LOOPS_SAFETY;
566 while (CreateWallEntity(
567 worldEditorAPI, s_World, wallPair.m_sWallAsset,
568 prevPoint, vector.Zero, wallPair.m_fPrePadding + PrePadding, wallPair.m_fPostPadding + PostPadding,
569 rotationOffset, nextAnchorIndex))
573Print(
"breaking while #2");
576 if (m_bEnableMiddleObject && !PlaceMiddleAtVertexOnly && MiddleObject)
580 worldEditorAPI, s_World, MiddleObject,
581 prevPoint, MiddleObjectOffsetUp * vector.Up + MiddleObjectOffsetRight * vector.Right, MiddleObjectPrePadding, MiddleObjectPostPadding,
582 rotationOffset, nextAnchorIndex);
585 wallPair = m_WallGroupContainer.GetRandomWall(length,
m_RandomGenerator.RandFloat01());
591 distance = vector.DistanceXZ(prevPoint, anchorPoints[nextAnchorIndex]);
604 static float MeasureEntity(ResourceName entityName,
int measureAxis)
610 if (MEASUREMENTS.Find(entityName, result))
615 Resource resource = Resource.Load(entityName);
616 if (!resource.IsValid())
618 MEASUREMENTS.Insert(entityName, result);
624 wallEntity =
GetGame().SpawnEntityPrefab(resource, s_World);
626 wallEntity =
GetGame().SpawnEntityPrefab(resource, ((WorldEditor)Workbench.GetModule(WorldEditor)).GetApi().GetWorld());
635 if (measureAxis == 2)
636 result = (maxBB - minBB)[2];
637 else if (measureAxis == 1)
638 result = (maxBB - minBB)[1];
640 result = (maxBB - minBB)[0];
644 Print(
"Wall asset length is zero, does it have a valid mesh? " + entityName,
LogLevel.ERROR);
648 if (result < MIN_WALL_PIECE_SIZE)
650 Print(
"Wall asset is too small, does it have a valid mesh? " + entityName,
LogLevel.ERROR);
651 result = MIN_WALL_PIECE_SIZE;
656 Print(
"Cannot measure entity " + entityName,
LogLevel.WARNING);
659 MEASUREMENTS.Insert(entityName, result);
665 protected IEntitySource CreateWallEntity(
666 WorldEditorAPI worldEditorAPI,
669 inout vector startPos,
674 int anchorLimit = -1)
685 if (!m_ShapeNextPointHelper.GetNextPoint(
size, nextPoint, anchorLimit, xzMode:
true))
688 if (startPos == nextPoint)
691 vector
direction = vector.Direction(startPos, nextPoint).Normalized();
695 if (m_bSnapOffsetShapeToTheGround)
697 vector absPos = CoordToParent(startPos);
698 absPos[1] = worldEditorAPI.GetWorld().GetSurfaceY(absPos[0], absPos[2]) + m_vShapeOffset[1];
699 startPos = CoordToLocal(absPos);
703 + relPosOffset[0] * (
direction * -vector.Up) + relPosOffset[1] * vector.Up;
706 angles = { 0, Math.Repeat(
angles[0] + dirOffset, 360), 0 };
708 startPos += m_vShapeOffset[1] * vector.Up;
710 IEntitySource result = worldEditorAPI.CreateEntity(
resourceName,
string.Empty, m_iSourceLayerID, m_Source, startPos,
angles);
711 startPos = nextPoint;
716 protected IEntitySource PlacePrefab(
722 float rotationAdjustment,
723 bool isGeneratorVisible,
730 bool snapToGround =
false,
731 bool allowClipping =
false,
732 vector offsetRight = vector.Zero)
737 vector prepadDirection;
743 orientation = prevDir;
746 prepadDirection = dir;
748 prepadDirection = prevDir;
750 pos += prepadDirection * prePadding;
752 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
757 Math3D.DirectionAndUpMatrix(orientation, vector.Up, rotMat);
758 vector rot = Math3D.MatrixToAngles(rotMat);
759 rot = { 0, rot[0] + rotationAdjustment, 0 };
761 if (m_bSnapOffsetShapeToTheGround)
763 vector world = CoordToParent(pos);
764 pos[1] = worldEditorAPI.GetTerrainSurfaceY(world[0], world[2]);
765 if (m_ParentShapeSource)
766 pos[1] = pos[1] - worldEditorAPI.SourceToEntity(m_ParentShapeSource).
GetOrigin()[1];
770 ent = worldEditorAPI.CreateEntityExt(name,
string.Empty, m_iSourceLayerID, m_Source, pos + offsetRight, rot,
TraceFlags.ENTS);
772 ent = worldEditorAPI.CreateEntity(name,
string.Empty, m_iSourceLayerID, m_Source, pos + offsetRight, rot);
777 ent.Get(
"coords", entPos);
778 entPos[1] = entPos[1] + offsetUp;
779 if (entPos != vector.Zero)
780 worldEditorAPI.SetVariableValue(ent, null,
"coords",
string.Format(
"%1 %2 %3", entPos[0], entPos[1], entPos[2]));
783 worldEditorAPI.SetEntityVisible(ent, isGeneratorVisible,
false);
789 pos += dir * Math.Max(0.1, length + postPadding);
795 protected override void _WB_OnParentChange(IEntitySource src, IEntitySource prevParentSrc)
797 super._WB_OnParentChange(src, prevParentSrc);
799 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
800 if (!worldEditorAPI || worldEditorAPI.UndoOrRedoIsRestoring())
807 protected override bool _WB_OnKeyChanged(BaseContainer src,
string key, BaseContainerList ownerContainers,
IEntity parent)
809 if (!super._WB_OnKeyChanged(src, key, ownerContainers, parent))
812 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
813 if (!worldEditorAPI || worldEditorAPI.UndoOrRedoIsRestoring())
817 if (key ==
"m_bStartFromTheEnd")
819 ShapeEntity shapeEntity = ShapeEntity.Cast(worldEditorAPI.SourceToEntity(m_ParentShapeSource));
823 array<vector> anchorPoints = {};
824 array<vector> tesselatedPoints = {};
825 if (!SCR_ParallelShapeHelper.GetAnchorsAndTesselatedPointsFromShape(shapeEntity, m_vShapeOffset, m_bYOffsetInShapeSpace, anchorPoints, tesselatedPoints))
827 PrintFormat(
"[WallGeneratorEntity._WB_OnKeyChanged] error getting shape points from shape with %1 points at position %2", shapeEntity.GetPointCount(), shapeEntity.CoordToParent(shapeEntity.GetOrigin()), level:
LogLevel.WARNING);
831 if (!m_bStartFromTheEnd)
833 SCR_ArrayHelperT<vector>.Reverse(anchorPoints);
834 SCR_ArrayHelperT<vector>.Reverse(tesselatedPoints);
837 m_ShapeNextPointHelper = SCR_ShapeNextPointHelper.CreateFromPoints(anchorPoints, tesselatedPoints);
846 protected override void OnShapeChangedInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, array<vector> mins, array<vector> maxes)
848 super.OnShapeChangedInternal(shapeEntitySrc, shapeEntity, mins, maxes);
ArmaReforgerScripted GetGame()
ref array< string > angles
ResourceName resourceName
ref RandomGenerator m_RandomGenerator
enum SCR_ECompassType EntityEditorProps(category:"GameScripted/Gadgets", description:"Compass", color:"0 0 255 255")
Prefab data class for compass component.
override bool _WB_OnKeyChanged(IEntity owner, BaseContainer src, string key, BaseContainerList ownerContainers, IEntity parent)
Any property value has been changed. You can use editor API here and do some additional edit actions ...
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
enum EVehicleType IEntity
proto external vector GetOrigin()
proto external void GetBounds(out vector mins, out vector maxs)
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
void OnShapeChangedInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, array< vector > mins, array< vector > maxes)