1 [
EntityEditorProps(
category:
"GameLib/Scripted/Generator", description:
"WallGeneratorEntity", dynamicBox:
true, visible:
false)]
13 protected bool m_bEnableMiddleObject;
15 [
Attribute(
"", UIWidgets.ResourcePickerThumbnail,
"Middle Object Prefab",
"et",
category:
"Middle Object")]
16 protected ResourceName MiddleObject;
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;
27 [
Attribute(
"0", UIWidgets.Slider,
"Middle object offset to the side",
params:
"-5 5 0.01",
category:
"Middle Object")]
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;
33 [
Attribute(
"0", UIWidgets.Slider,
"Middle object offset up/down",
params:
"-10 10 0.01",
category:
"Middle Object")]
34 protected float MiddleObjectOffsetUp;
41 protected bool m_bEnableFirstObject;
43 [
Attribute(
"", UIWidgets.ResourcePickerThumbnail,
"First Object Prefab",
"et",
category:
"First Object")]
44 protected ResourceName FirstObject;
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;
52 [
Attribute(
"0", UIWidgets.Slider,
"First object offset to the side",
params:
"-5 5 0.01",
category:
"First Object")]
53 protected float FirstObjectOffsetRight;
55 [
Attribute(
"0", UIWidgets.Slider,
"First object offset up/down",
params:
"-10 10 0.01",
category:
"First Object")]
56 protected float FirstObjectOffsetUp;
63 protected bool m_bEnableLastObject;
65 [
Attribute(
"", UIWidgets.ResourcePickerThumbnail,
"Last Object Prefab",
"et",
category:
"Last Object")]
66 protected ResourceName LastObject;
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;
71 [
Attribute(
"0", UIWidgets.Slider,
"Last object post-padding, essentially a gap between first object and the following one",
params:
"-5 5 0.01",
category:
"Last Object")]
72 protected float LastObjectPostPadding;
74 [
Attribute(
"0", UIWidgets.Slider,
"Last object offset to the side",
params:
"-5 5 0.01",
category:
"Last Object")]
75 protected float LastObjectOffsetRight;
77 [
Attribute(
"0", UIWidgets.Slider,
"Last object offset up/down",
params:
"-10 10 0.01",
category:
"Last Object")]
78 protected float LastObjectOffsetUp;
84 [
Attribute(
"0", UIWidgets.Slider,
"Global object pre-padding, essentially a gap between previous prefab and this",
params:
"-2 2 0.01",
category:
"Global")]
85 protected float PrePadding;
87 [
Attribute(
"0", UIWidgets.Slider,
"Allow pre-padding on first wall asset in each line segment",
params:
"-2 2 0.01",
category:
"Global")]
88 protected float PostPadding;
90 [
Attribute(
"0.5", UIWidgets.Slider,
"Allow overshooting the segment line by this amount when placing assets",
params:
"-5 5 0.01",
category:
"Global")]
91 protected float m_fOvershoot;
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:
"Copy the polyline precisely 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;
121 [
Attribute(defvalue:
"0",
desc:
"If you want to generate objects smaller than 10 centimetres",
category:
"Other")]
122 protected bool UseForVerySmallObjects;
127 [
Attribute(defvalue:
"0",
desc:
"Whether or not walls should be snapped to the terrain",
category:
"Other")]
128 protected bool m_bSnapToTerrain;
131 protected ref array<ref ResourceName> WallPrefabs;
135 protected IEntitySource m_ParentSource;
137 protected ref SCR_WallGroupContainer m_WallGroupContainer;
139 protected ref array<ref SCR_WallGeneratorPoint> m_aPoints = {};
140 protected static ref array<ref Shape> s_aDebugShapes = {};
147 static float MeasureEntity(ResourceName entityName,
int measureAxis, WorldEditorAPI api)
149 float result =
float.MAX;
151 if (entityName.IsEmpty())
154 Resource resource = Resource.Load(entityName);
155 if (!resource.IsValid())
164 wallEntity.GetBounds(minBB, maxBB);
167 result = (maxBB - minBB)[measureAxis];
168 if (result < 0.000001)
170 Print(
"Wall asset " + entityName +
" is too small, does it have a valid mesh?", LogLevel.ERROR);
178 override bool _WB_OnKeyChanged(BaseContainer src,
string key, BaseContainerList ownerContainers, IEntity parent)
180 super._WB_OnKeyChanged(src, key, ownerContainers, parent);
185 WorldEditorAPI api = _WB_GetEditorAPI();
186 if (!api || api.UndoOrRedoIsRestoring())
189 IEntitySource thisSrc = api.EntityToSource(
this);
190 IEntitySource parentSrc = thisSrc.GetParent();
192 BaseContainerTools.WriteToInstance(
this, thisSrc);
193 OnShapeChanged(parentSrc, ShapeEntity.Cast(parent), {}, {});
200 protected void OffsetPoints(array<vector> points,
float offset,
bool debugAllowed =
false)
202 array<vector> pointsTemp = {};
203 pointsTemp.Copy(points);
205 int lastIndex = pointsTemp.Count() - 1;
208 if (m_bDebug && debugAllowed)
209 GetWorldTransform(matWrld);
211 vector forwardPrev =
"1 1 1";
212 foreach (
int i, vector pointTemp : pointsTemp)
217 forwardNext = pointsTemp[i + 1] - pointTemp;
219 forwardNext = -forwardPrev;
222 forwardPrev = -forwardNext;
224 forwardNext.Normalize();
225 forwardPrev.Normalize();
227 float dotProductPrevNext = vector.Dot(forwardPrev, forwardNext);
228 bool almostLine = dotProductPrevNext < -0.95;
229 vector diagonal = forwardNext + forwardPrev;
231 vector normalRight = -forwardPrev *
"0 1 0";
232 normalRight.Normalize();
233 float dotProductNormNext = vector.Dot(normalRight, forwardNext);
234 bool isLeft = dotProductNormNext > 0;
236 vector nextModified = dotProductPrevNext * forwardNext;
237 float dist = vector.Distance(nextModified, forwardPrev);
240 diff = offset / dist;
245 diagonal = diagonal * -1;
249 vector vec = forwardNext - forwardPrev;
250 vector right = vec *
"0 1 0";
252 diagonal = right * offset;
255 points.Insert(pointsTemp[i] + diagonal);
256 forwardPrev = -forwardNext;
259 if (m_bDebug && debugAllowed)
261 pointTemp = pointTemp.Multiply4(matWrld);
262 s_aDebugShapes.Insert(Shape.Create(ShapeType.LINE, ARGB(255, 255, 255, 255), ShapeFlags.NOZBUFFER, pointTemp, pointTemp + forwardNext));
263 s_aDebugShapes.Insert(Shape.Create(ShapeType.LINE, ARGB(255, 0, 255, 0), ShapeFlags.NOZBUFFER, pointTemp, pointTemp + forwardPrev));
264 s_aDebugShapes.Insert(Shape.Create(ShapeType.LINE, ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER, pointTemp, pointTemp + diagonal));
270 override void OnShapeInitInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity)
272 super.OnShapeInitInternal(shapeEntitySrc, shapeEntity);
275 WorldEditorAPI api = _WB_GetEditorAPI();
276 if (!api || api.UndoOrRedoIsRestoring())
279 Preprocess(shapeEntitySrc);
280 Generate(shapeEntity);
284 protected void Preprocess(IEntitySource shapeEntitySrc)
287 WorldEditorAPI api = _WB_GetEditorAPI();
289 m_WallGroupContainer =
new SCR_WallGroupContainer(api, m_aWallGroups, UseXAsForward, MiddleObject,
this);
290 BaseContainerList points = shapeEntitySrc.GetObjectArray(
"Points");
291 if (points !=
null && points.Count() >= 2 && m_WallGroupContainer.m_bGenerated != 0)
293 bool isShapeClosed =
false;
294 shapeEntitySrc.Get(
"IsClosed", isShapeClosed);
295 array<vector> pointsVec = {};
296 int pointCount = points.Count();
297 bool addFirstAsLast =
false;
299 if (isShapeClosed && pointCount > 2)
300 addFirstAsLast =
true;
305 for (
int i = 0; i < pointCount; i++)
307 point = points.Get(i);
308 point.Get(
"Position", pos);
309 pointsVec.Insert(pos);
313 pointsVec.Insert(pointsVec[0]);
319 ResourceName customMesh;
320 float prePadding, postPadding, offsetUp;
321 bool generate, clipping, align;
322 BaseContainerList dataArr;
324 int dataCount, lastPointIndex;
327 for (
int i = 0; i < pointCount; i++)
329 point = points.Get(i);
331 customMesh =
string.Empty;
338 dataArr = point.GetObjectArray(
"Data");
339 dataCount = dataArr.Count();
341 for (
int j = 0; j < dataCount; ++j)
343 data = dataArr.Get(j);
344 if (
data.GetClassName() ==
"WallGeneratorPointData")
346 data.Get(
"MeshAtPoint", customMesh);
347 data.Get(
"PrePadding", prePadding);
348 data.Get(
"PostPadding", postPadding);
349 data.Get(
"m_bGenerate", generate);
350 data.Get(
"m_bAllowClipping", clipping);
351 data.Get(
"m_fOffsetUp", offsetUp);
352 data.Get(
"m_bAlignWithNext", align);
358 genPoint.m_vPos = pos;
359 genPoint.m_sCustomMesh = customMesh;
360 genPoint.m_fPrePadding = prePadding;
361 genPoint.m_fPostPadding = postPadding;
362 genPoint.m_bGenerate = generate;
363 genPoint.m_bClip = clipping;
364 genPoint.m_fOffsetUp = offsetUp;
365 genPoint.m_bAlignNext = align;
366 m_aPoints.Insert(genPoint);
371 m_aPoints.Insert(m_aPoints[0]);
372 lastPointIndex = m_aPoints.Count() - 1;
373 m_aPoints[lastPointIndex].m_vPos = pointsVec[lastPointIndex];
379 protected override void OnShapeChangedInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, array<vector> mins, array<vector> maxes)
381 if (!shapeEntitySrc || _WB_GetEditorAPI().UndoOrRedoIsRestoring())
384 Preprocess(shapeEntitySrc);
385 Generate(shapeEntity);
389 protected IEntitySource PlacePrefab(
395 float rotationAdjustment,
396 bool isGeneratorVisible,
403 bool snapToGround =
false,
404 bool allowClipping =
false,
405 vector offsetRight =
"0 0 0")
410 vector prepadDirection;
416 orientation = prevDir;
419 prepadDirection = dir;
421 prepadDirection = prevDir;
423 pos += prepadDirection * prePadding;
425 WorldEditorAPI api = _WB_GetEditorAPI();
426 IEntitySource thisSource = api.EntityToSource(
this);
427 int layerID = api.GetCurrentEntityLayerId();
432 Math3D.DirectionAndUpMatrix(orientation,
"0 1 0", rotMat);
433 vector rot = Math3D.MatrixToAngles(rotMat);
434 rot[1] = rot[0] + rotationAdjustment;
438 if (m_bSnapToTerrain)
440 vector world = CoordToParent(pos);
441 pos[1] = api.GetTerrainSurfaceY(world[0], world[2]);
443 pos[1] = pos[1] - api.SourceToEntity(m_ParentSource).GetOrigin()[1];
449 GetWorldTransform(matWrld);
451 ent = api.CreateEntityExt(name,
"", layerID, thisSource, (pos + offsetRight), rot, TraceFlags.WORLD);
452 api.ParentEntity(thisSource, ent,
true);
456 ent = api.CreateEntity(name,
"", layerID, thisSource, pos + offsetRight, rot);
462 ent.Get(
"coords", entPos);
463 entPos[1] = entPos[1] + offsetUp;
464 string coords = entPos[0].ToString() +
" " + entPos[1].ToString() +
" " + entPos[2].ToString();
465 api.SetVariableValue(ent,
null,
"coords", coords);
468 api.SetEntityVisible(ent, isGeneratorVisible,
false);
474 if (UseForVerySmallObjects)
475 pos += dir * (length + postPadding);
479 pos += dir * Math.Max(0.1, length + postPadding);
485 protected void Generate(ShapeEntity shapeEntity)
487 if (!m_WallGroupContainer || m_WallGroupContainer.IsEmpty())
490 WorldEditorAPI api = _WB_GetEditorAPI();
491 if (api ==
null || m_WallGroupContainer.IsEmpty())
494 IEntitySource entSrc = api.EntityToSource(
this);
495 m_ParentSource = entSrc.GetParent();
496 int childCount = entSrc.GetNumChildren();
498 for (
int i = childCount - 1; i >= 0; --i)
500 api.DeleteEntity(entSrc.GetChild(i));
503 if (m_aPoints.Count() < 2)
510 float lastObjectLength;
512 if (m_bEnableLastObject && LastObject)
513 lastObjectLength = MeasureEntity(LastObject, forwardAxis, api);
515 bool isGeneratorVisible = api.IsEntityVisible(entSrc);
518 array<ref SCR_WallGeneratorPoint> localPoints = {};
521 localPoints.Insert(wgPoint);
524 if (m_bStartFromTheEnd)
525 SCR_ArrayHelperT<ref SCR_WallGeneratorPoint>.Reverse(localPoints);
527 float rotationAdjustment = 0;
530 rotationAdjustment = -90;
533 rotationAdjustment += 180;
535 if (m_bStartFromTheEnd)
536 rotationAdjustment += 180;
538 vector from = localPoints[0].m_vPos;
539 vector to, dir, prevDir, rightVec;
541 bool firstUsed, exhausted, lastPoint, lastSegment, generate, firstPass;
542 ResourceName customMesh;
547 float bestLen, prePaddingToUse, postPaddingToUse, offsetUp, lengthRequirement;
548 bool allowClipping, alignNext, prepadNext, custom;
549 bool firstPlaced, placeMiddle, placeLast, lastPlaced, lastInSegmentDoNotPlace, middleOfSegmentDoNotPlace;
553 for (
int i, count = localPoints.Count(); i < count; i++)
556 lastPoint = i == count - 1;
557 lastSegment = i == count - 2;
564 from = localPoints[i].m_vPos;
566 from = localPoints[i].m_vPos + (dir * PrePadding);
571 to = localPoints[i].m_vPos;
572 dir = (localPoints[i].m_vPos - localPoints[i - 1].m_vPos).Normalized();
576 to = localPoints[i + 1].m_vPos;
577 dir = (to - from).Normalized();
582 customMesh = localPoints[i].m_sCustomMesh;
583 generate = localPoints[i].m_bGenerate;
584 remaining = vector.Distance(from, to) + m_fOvershoot;
588 rightVec = dir *
"0 1 0";
594 bestWall =
string.Empty;
596 prePaddingToUse = PrePadding;
597 postPaddingToUse = PostPadding;
598 allowClipping =
false;
603 offsetRight = vector.Zero;
607 lengthRequirement = 0;
609 lastInSegmentDoNotPlace =
false;
610 middleOfSegmentDoNotPlace =
false;
613 if (i == 0 && firstPass && m_bEnableFirstObject && !FirstObject.IsEmpty())
615 bestWall = FirstObject;
616 bestLen = MeasureEntity(FirstObject, forwardAxis, api);
617 prePaddingToUse = FirstObjectPrePadding;
618 postPaddingToUse = FirstObjectPostPadding;
620 offsetRight = rightVec * FirstObjectOffsetRight;
621 offsetUp = FirstObjectOffsetUp;
624 else if (!customMesh.IsEmpty())
626 bestWall = customMesh;
627 prePaddingToUse = localPoints[i].m_fPrePadding;
628 postPaddingToUse = localPoints[i].m_fPostPadding;
629 allowClipping = localPoints[i].m_bClip;
630 offsetUp = localPoints[i].m_fOffsetUp;
631 alignNext = localPoints[i].m_bAlignNext;
633 bestLen = MeasureEntity(customMesh, forwardAxis, api);
634 customMesh =
string.Empty;
639 wall = m_WallGroupContainer.GetRandomWall(remaining);
642 bestLen = wall.m_fWallLength;
643 bestWall = wall.m_sWallAsset;
644 prePaddingToUse += wall.m_fPrePadding;
645 postPaddingToUse += wall.m_fPostPadding;
654 if (bestLen + PrePadding <= 0)
657 Print(
"PrePadding is set too low, not using it in this instance", LogLevel.WARNING);
660 if (!PrePadFirst && firstPass)
663 PlacePrefab(generate, bestWall, from, dir, prevDir, rotationAdjustment, isGeneratorVisible, bestLen, prePaddingToUse, postPaddingToUse, offsetUp, alignNext, prepadNext,
false, allowClipping, offsetRight);
665 remaining -= (bestLen * !allowClipping) + prePaddingToUse + postPaddingToUse;
667 placeMiddle = !custom && m_bEnableMiddleObject && !MiddleObject.IsEmpty();
668 placeLast = lastSegment && m_bEnableLastObject && !LastObject.IsEmpty();
670 lengthRequirement = m_WallGroupContainer.m_fSmallestWall;
673 lengthRequirement += m_WallGroupContainer.m_fMiddleObjectLength;
676 lengthRequirement += lastObjectLength;
678 if (remaining < lengthRequirement)
684 if (exhausted && placeLast)
686 bestWall = LastObject;
687 bestLen = lastObjectLength;
688 prePaddingToUse = LastObjectPrePadding;
689 postPaddingToUse = LastObjectPostPadding;
690 offsetRight = rightVec * LastObjectOffsetRight;
691 offsetUp = LastObjectOffsetUp;
693 PlacePrefab(generate, bestWall, from, dir, prevDir, rotationAdjustment, isGeneratorVisible, bestLen, prePaddingToUse, postPaddingToUse, offsetUp, alignNext, prepadNext,
false, allowClipping, offsetRight);
695 remaining -= (bestLen * !allowClipping) + prePaddingToUse + postPaddingToUse;
701 lastInSegmentDoNotPlace = exhausted && !(PlaceMiddleAtVertex || PlaceMiddleAtVertexOnly);
704 middleOfSegmentDoNotPlace = (!exhausted && PlaceMiddleAtVertexOnly) || (PlaceMiddleAtVertexOnly && (lastPlaced || firstPlaced));
706 if (!lastInSegmentDoNotPlace && !middleOfSegmentDoNotPlace)
708 from += dir * MiddleObjectPrePadding;
709 offsetRight = rightVec * MiddleObjectOffsetRight;
710 PlacePrefab(generate, MiddleObject, from, dir, prevDir, rotationAdjustment, isGeneratorVisible, m_WallGroupContainer.m_fMiddleObjectLength, MiddleObjectPrePadding, MiddleObjectPostPadding, MiddleObjectOffsetUp,
true, prepadNext,
true,
false, offsetRight);
711 remaining -= m_WallGroupContainer.m_fMiddleObjectLength + MiddleObjectPrePadding + MiddleObjectPostPadding;
720 if (m_bStartFromTheEnd)
721 SCR_ArrayHelperT<ref SCR_WallGeneratorPoint>.Reverse(localPoints);
727 void WallGeneratorEntity(IEntitySource src, IEntity parent)
730 SetEventMask(EntityEvent.INIT);