Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
WallGeneratorEntity.c
Go to the documentation of this file.
1[EntityEditorProps(category: "GameScripted/Generators", description: "Wall Generator", dynamicBox: true, visible: false)]
5
6class WallGeneratorEntity : SCR_LineTerrainShaperGeneratorBaseEntity
7{
8 /*
9 Middle Object
10 */
11
12 [Attribute(defvalue: "1", desc: "Enable middle object", category: "Middle Object")]
13 protected bool m_bEnableMiddleObject;
14
15 [Attribute("", UIWidgets.ResourcePickerThumbnail, "Middle Object Prefab", "et", category: "Middle Object")]
16 protected ResourceName MiddleObject;
17
18 [Attribute(defvalue: "1", desc: "Place middle prefab at vertex", category: "Middle Object")]
19 protected bool PlaceMiddleAtVertex;
20
21 [Attribute(defvalue: "0", desc: "Place middle prefab at vertex only", category: "Middle Object")]
22 protected bool PlaceMiddleAtVertexOnly;
23
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;
26
27 [Attribute("0", UIWidgets.Slider,"Middle object offset to the side", params: "-5 5 0.01", category: "Middle Object")]
28 protected float MiddleObjectOffsetRight;
29
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;
32
33 [Attribute("0", UIWidgets.Slider, "Middle object offset up/down", params: "-10 10 0.01", category: "Middle Object")]
34 protected float MiddleObjectOffsetUp;
35
36 /*
37 First Object
38 */
39
40 [Attribute(defvalue: "1", desc: "Enable first object", category: "First Object")]
41 protected bool m_bEnableFirstObject;
42
43 [Attribute("", UIWidgets.ResourcePickerThumbnail, "First Object Prefab", "et", category: "First Object")]
44 protected ResourceName FirstObject;
45
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;
48
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;
51
52 [Attribute("0", UIWidgets.Slider, "First object offset to the side", params: "-5 5 0.01", category: "First Object")]
53 protected float FirstObjectOffsetRight;
54
55 [Attribute("0", UIWidgets.Slider, "First object offset up/down", params: "-10 10 0.01", category: "First Object")]
56 protected float FirstObjectOffsetUp;
57
58 /*
59 Last Object
60 */
61
62 [Attribute(defvalue: "1", desc: "Enable last object", category: "Last Object")]
63 protected bool m_bEnableLastObject;
64
65 [Attribute("", UIWidgets.ResourcePickerThumbnail, "Last Object Prefab", "et", category: "Last Object")]
66 protected ResourceName LastObject;
67
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;
70
71 [Attribute(uiwidget: UIWidgets.None)] // obsolete, kept for Prefabs and layers retrocompatibility
72 protected float LastObjectPostPadding;
73
74 [Attribute("0", UIWidgets.Slider,"Last object offset to the side", params: "-5 5 0.01", category: "Last Object")]
75 protected float LastObjectOffsetRight;
76
77 [Attribute("0", UIWidgets.Slider, "Last object offset up/down", params: "-10 10 0.01", category: "Last Object")]
78 protected float LastObjectOffsetUp;
79
80 /*
81 Global
82 */
83
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;
86
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;
89
90 [Attribute("0.5", UIWidgets.Slider, "Allow overshooting the segment line by this amount when placing assets\nOnly applies to polylines", params: "-5 5 0.01", precision: 2, category: "Global")]
91 protected float m_fOvershoot;
92
93 [Attribute(uiwidget: UIWidgets.None)] // obsolete, kept for Prefabs and layers retrocompatibility
94 protected float m_fOffsetRight;
95
96 [Attribute(uiwidget: UIWidgets.None)] // obsolete, kept for Prefabs and layers retrocompatibility
97 protected float m_fOffsetUp;
98
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;
101
102 /*
103 Other
104 */
105
106 [Attribute(defvalue: "1", desc: "Allow pre-padding on first wall asset in each line segment", category: "Other")]
107 protected bool PrePadFirst;
108
109 [Attribute(defvalue: "0", desc: "Precisely places entities to anchors while sacrificing wall assets contact", category: "Other")]
110 protected bool ExactPlacement;
111
112 [Attribute(defvalue: "0", desc: "Start the wall from the other end of the polyline", category: "Other")]
113 protected bool m_bStartFromTheEnd;
114
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;
117
118 [Attribute(defvalue: "0", desc: "Rotate object 180° around the Yaw axis", category: "Other")]
119 protected bool Rotate180;
120
121 [Attribute(uiwidget: UIWidgets.None)] // obsolete, kept for Prefabs and layers retrocompatibility
122 protected bool UseForVerySmallObjects;
123
124 [Attribute(uiwidget: UIWidgets.None)] // obsolete, kept for Prefabs and layers retrocompatibility
125 bool m_bDebug;
126
127 [Attribute(uiwidget: UIWidgets.None)] // obsolete, kept for Prefabs and layers retrocompatibility
128 protected bool m_bSnapToTerrain;
129
130 [Attribute(uiwidget: UIWidgets.None)] // obsolete, kept for Prefabs and layers retrocompatibility
131 protected ref array<ResourceName> WallPrefabs;
132
133#ifdef WORKBENCH
134
135 protected static const float MIN_WALL_PIECE_SIZE = 0.001;
136 protected static const float MIN_WALL_SLOT_SIZE = 0.001;
137
138 protected ref SCR_WallGroupContainer m_WallGroupContainer;
139
140 protected static BaseWorld s_World; // tricky, but used wisely! nulled after usage
141
142 protected static const ref map<ResourceName, float> MEASUREMENTS = new map<ResourceName, float>(); // always the same axis
143
144 protected static const int MAX_LOOPS_SAFETY = 500; // temporary limit per section calculation
145
146 //------------------------------------------------------------------------------------------------
147 protected void Generate()
148 {
149 if (!m_bEnableGeneration)
150 {
151 Print("Wall generation is disabled for this shape - tick it back on before saving", LogLevel.NORMAL);
152 return;
153 }
154
155 SetSeed();
156
157 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
158 if (!worldEditorAPI || worldEditorAPI.UndoOrRedoIsRestoring())
159 return;
160
161 BaseWorld world = worldEditorAPI.GetWorld();
162 if (!world)
163 return;
164
165 DeleteAllChildren();
166
167 if (!m_ParentShapeSource || !m_ShapeNextPointHelper || !m_ShapeNextPointHelper.IsValid())
168 return;
169
170 ShapeEntity shapeEntity = ShapeEntity.Cast(worldEditorAPI.SourceToEntity(m_ParentShapeSource));
171 if (!shapeEntity)
172 return;
173
174 float rotationOffset;
175
176 int forwardAxis;
177 if (UseXAsForward)
178 rotationOffset = -90;
179 else
180 forwardAxis = 2;
181
182 if (Rotate180)
183 rotationOffset += 180;
184
185 if (m_bStartFromTheEnd)
186 rotationOffset += 180;
187
188 MEASUREMENTS.Clear();
189
190 array<vector> anchorPoints = m_ShapeNextPointHelper.GetAnchorPoints();
191
192 typename type = m_ParentShapeSource.GetClassName().ToType();
193 bool isPolyline = type && type.IsInherited(PolylineShapeEntity); // no "exact placement" otherwise (potential unknown shape type)
194
195 // straight line mode variables start
196 bool isStraightLineMode = isPolyline; // in case we make it a checkbox
197
198 s_World = world;
199
200 if (!m_WallGroupContainer) // needs s_World
201 m_WallGroupContainer = new SCR_WallGroupContainer(m_aWallGroups, UseXAsForward, MiddleObject);
202
203 if (isStraightLineMode)
204 GenerateInStraightLine(anchorPoints, rotationOffset, forwardAxis);
205 else
206 GenerateClosestToShape(anchorPoints, rotationOffset);
207
208 s_World = null; // see? I told you - used wisely
209 }
210
211 //------------------------------------------------------------------------------------------------
212 protected void GenerateInStraightLine(notnull array<vector> anchorPoints, float rotationOffset, int forwardAxis)
213 {
214 map<int, ref ShapePointDataScriptBase> pointDataMap = GetFirstPointDataMap(WallGeneratorPointData);
215 WallGeneratorPointData pointData;
216
217 vector from = anchorPoints[0];
218 vector to, dir, prevDir, rightVec;
219
220 bool generate = true;
221 bool exhausted, lastPoint, lastSegment, firstPass;
222 ResourceName customMesh;
223 float remaining;
224
225 SCR_WallPair wall;
226
227 float lastObjectLength;
228
229 if (m_bEnableLastObject && LastObject)
230 lastObjectLength = MeasureEntity(LastObject, forwardAxis);
231
232 bool isGeneratorVisible = _WB_GetEditorAPI().IsEntityVisible(m_Source);
233
234 int countMinus1 = anchorPoints.Count() - 1;
235 foreach (int i, vector anchorPoint : anchorPoints)
236 {
237 exhausted = false;
238 lastPoint = i == countMinus1;
239 lastSegment = i == countMinus1 - 1;
240
241 prevDir = dir;
242
243 if (ExactPlacement)
244 {
245 if (i == 1)
246 from = anchorPoint;
247 else
248 from = anchorPoint + (dir * PrePadding);
249 }
250
251 if (lastPoint)
252 {
253 to = anchorPoint;
254 dir = vector.Direction(anchorPoints[i - 1], to).Normalized();
255 }
256 else
257 {
258 to = anchorPoints[i + 1];
259 dir = vector.Direction(from, to).Normalized();
260 if (i == 0)
261 prevDir = dir;
262 }
263
264 pointData = WallGeneratorPointData.Cast(pointDataMap.Get(i));
265 if (pointData)
266 {
267 customMesh = pointData.MeshAtPoint;
268 generate = pointData.m_bGenerate;
269 }
270 else
271 {
272 customMesh = ResourceName.Empty;
273 }
274
275 remaining = vector.Distance(from, to) + m_fOvershoot;
276
277 firstPass = true; // first segment on the polyline
278
279 rightVec = dir * vector.Up;
280
281 // walking the polyline line segment and populating it with assets
282 // as long as there is enough room for at least the smallest wall available
283 while (!exhausted)
284 {
285 ResourceName bestWall = ResourceName.Empty;
286 float bestLen = 0;
287 float prePaddingToUse = PrePadding;
288 float postPaddingToUse = PostPadding;
289 bool allowClipping = false;
290 bool alignNext = true;
291 bool prepadNext = true;
292 float offsetUp = 0;
293 bool custom = false;
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;
302
303 // first object
304 if (i == 0 && firstPass && m_bEnableFirstObject && FirstObject) // !.IsEmpty()
305 {
306 bestWall = FirstObject;
307 bestLen = MeasureEntity(FirstObject, forwardAxis);
308 prePaddingToUse = FirstObjectPrePadding;
309 postPaddingToUse = FirstObjectPostPadding;
310 firstPlaced = true;
311 offsetRight = rightVec * FirstObjectOffsetRight;
312 offsetUp = FirstObjectOffsetUp;
313 }
314 // custom mesh from the vertex data
315 else if (customMesh) // !.IsEmpty()
316 {
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;
323 prepadNext = false;
324 bestLen = MeasureEntity(customMesh, forwardAxis);
325 customMesh = string.Empty;
326 custom = true;
327 }
328 else if (!lastPoint)
329 {
330 wall = m_WallGroupContainer.GetRandomWall(remaining, m_RandomGenerator.RandFloat01());
331 if (wall)
332 {
333 bestLen = wall.m_fWallLength;
334 bestWall = wall.m_sWallAsset;
335 prePaddingToUse += wall.m_fPrePadding;
336 postPaddingToUse += wall.m_fPostPadding;
337 }
338 }
339 else
340 {
341 // get here when the vertex has wall data with a mesh object and it's a last point on the polyline
342 break;
343 }
344
345 if (bestLen + PrePadding <= 0)
346 {
347 prePaddingToUse = 0;
348 Print("PrePadding is set too low, not using it in this instance", LogLevel.WARNING);
349 }
350
351 if (!PrePadFirst && firstPass) // do not prepad the first asset in the segment
352 prePaddingToUse = 0;
353
354 PlacePrefab(generate, bestWall, from, dir, prevDir, rotationOffset, isGeneratorVisible, bestLen, prePaddingToUse, postPaddingToUse, offsetUp, alignNext, prepadNext, false, allowClipping, offsetRight);
355
356 remaining -= (bestLen * !allowClipping) + prePaddingToUse + postPaddingToUse; // TODO: fix bool multiplier
357
358 placeMiddle = !custom && m_bEnableMiddleObject && MiddleObject; // !.IsEmpty();
359 placeLast = lastSegment && m_bEnableLastObject && LastObject; // !.IsEmpty();
360
361 lengthRequirement = m_WallGroupContainer.m_fSmallestWall; // the minimal space required to place the next wall asset
362 if (lengthRequirement == 0)
363 break;
364
365 if (placeMiddle)
366 lengthRequirement += m_WallGroupContainer.m_fMiddleObjectLength;
367
368 if (placeLast)
369 lengthRequirement += lastObjectLength;
370
371 if (remaining < lengthRequirement)
372 exhausted = true;
373
374 lastPlaced = false;
375
376 // no more room for 'normal' assets + placing last object
377 if (exhausted && placeLast)
378 {
379 bestWall = LastObject;
380 bestLen = lastObjectLength;
381 prePaddingToUse = LastObjectPrePadding;
382 postPaddingToUse = 0;
383 offsetRight = rightVec * LastObjectOffsetRight;
384 offsetUp = LastObjectOffsetUp;
385
386 PlacePrefab(generate, bestWall, from, dir, prevDir, rotationOffset, isGeneratorVisible, bestLen, prePaddingToUse, postPaddingToUse, offsetUp, alignNext, prepadNext, false, allowClipping, offsetRight);
387 lastPlaced = true;
388 remaining -= (bestLen * !allowClipping) + prePaddingToUse + postPaddingToUse;
389 }
390
391 // placing middle object
392 if (placeMiddle)
393 {
394 lastInSegmentDoNotPlace = exhausted && !(PlaceMiddleAtVertex || PlaceMiddleAtVertexOnly);
395
396 // do not place middle object after first/last assets and any other assets which do not close a line segment
397 middleOfSegmentDoNotPlace = (!exhausted && PlaceMiddleAtVertexOnly) || (PlaceMiddleAtVertexOnly && (lastPlaced || firstPlaced));
398
399 if (!lastInSegmentDoNotPlace && !middleOfSegmentDoNotPlace)
400 {
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;
405 }
406 }
407
408 firstPass = false;
409 }
410 }
411 }
412
413 //------------------------------------------------------------------------------------------------
414 protected void GenerateClosestToShape(notnull array<vector> anchorPoints, float rotationOffset)
415 {
416 const bool isPolyline = false; // only used for splines as of now
417
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];
423
424 bool generateSection = true;
425
426 SCR_WallPair wallPair;
427 WallGeneratorPointData wallGeneratorPointData;
428 foreach (int anchorIndex, vector anchorPos : anchorPoints)
429 {
430 wallGeneratorPointData = WallGeneratorPointData.Cast(pointDataMap.Get(anchorIndex));
431 if (wallGeneratorPointData)
432 generateSection = wallGeneratorPointData.m_bGenerate;
433
434 if (!generateSection) // prevent even the mesh on this point, previous generator's behaviour
435 continue;
436
437 //
438 // generate anchor Prefab, whatever it is (FirstObject, MiddleObject, LastObject, PointDataObject etc)
439 //
440
441 // last anchor - generate "manually" (via CreateEntity) and leave
442 if (anchorIndex == anchorPointsCountMinus1)
443 {
444 vector lastDirection = m_ShapeNextPointHelper.GetCurrentDirection();
445 vector angles = lastDirection.VectorToAngles();
446 angles[1] = Math.Repeat(angles[0] + rotationOffset, 360);
447 angles[0] = 0;
448 angles[2] = 0;
449
450 vector right = lastDirection * -vector.Up;
451
452 if (m_bEnableLastObject && LastObject)
453 {
454 if (
455 worldEditorAPI.CreateEntity(
456 LastObject,
457 string.Empty,
458 m_iSourceLayerID,
459 m_Source,
460 prevPoint
461 + LastObjectPrePadding * lastDirection
462 + LastObjectOffsetRight * right
463 + LastObjectOffsetUp * vector.Up,
464 angles))
465 break;
466 }
467
468 if (wallGeneratorPointData && wallGeneratorPointData.m_bGenerate && wallGeneratorPointData.MeshAtPoint)
469 {
470 if (
471 worldEditorAPI.CreateEntity(
472 wallGeneratorPointData.MeshAtPoint,
473 string.Empty,
474 m_iSourceLayerID,
475 m_Source,
476 prevPoint
477 + wallGeneratorPointData.PrePadding * lastDirection
478 + wallGeneratorPointData.m_fOffsetUp * vector.Up,
479 angles))
480 break;
481 }
482
483 break;
484 }
485
486 int nextAnchorIndex = anchorIndex + 1;
487
488 bool generatedAnchorPrefab;
489 if (anchorIndex == 0 && m_bEnableFirstObject && FirstObject)
490 {
491 if (
492 CreateWallEntity(
493 worldEditorAPI, s_World, FirstObject,
494 prevPoint, FirstObjectOffsetUp * vector.Up + FirstObjectOffsetRight * vector.Right,
495 FirstObjectPrePadding, FirstObjectPostPadding,
496 rotationOffset, nextAnchorIndex))
497 generatedAnchorPrefab = true;
498 }
499
500 if (!generatedAnchorPrefab && wallGeneratorPointData && wallGeneratorPointData.MeshAtPoint)
501 {
502 if (ExactPlacement)
503 {
504 m_ShapeNextPointHelper.SetOnAnchor(anchorIndex);
505 prevPoint = anchorPos;
506 }
507
508 if (
509 CreateWallEntity(
510 worldEditorAPI, s_World, wallGeneratorPointData.MeshAtPoint,
511 prevPoint, wallGeneratorPointData.m_fOffsetUp * vector.Up,
512 wallGeneratorPointData.PrePadding, wallGeneratorPointData.PostPadding,
513 rotationOffset, nextAnchorIndex))
514 generatedAnchorPrefab = true;
515 }
516
517 if (!generatedAnchorPrefab && m_bEnableMiddleObject && (PlaceMiddleAtVertex || PlaceMiddleAtVertexOnly) && MiddleObject)
518 {
519 if (
520 CreateWallEntity(
521 worldEditorAPI, s_World, MiddleObject,
522 prevPoint, MiddleObjectOffsetUp * vector.Up + MiddleObjectOffsetRight * vector.Right,
523 MiddleObjectPrePadding, MiddleObjectPostPadding,
524 rotationOffset, nextAnchorIndex))
525 generatedAnchorPrefab = true;
526 }
527
528 //
529 // generate from current point to the next anchor
530 //
531
532// if (ExactPlacement && isPolyline && !generatedAnchorPrefab)
533// {
534// m_ShapeNextPointHelper.SetOnAnchor(anchorIndex);
535// prevPoint = anchorPos;
536// }
537
538 float distance = float.INFINITY;
539 wallPair = m_WallGroupContainer.GetRandomWall(distance, m_RandomGenerator.RandFloat01());
540
541int maxLoops1 = MAX_LOOPS_SAFETY;
542 while (wallPair)
543 {
544if (maxLoops1-- < 1)
545{
546Print("breaking while #1");
547break;
548}
549 float length = wallPair.m_fWallLength + PrePadding + PostPadding;
550 if (length < MIN_WALL_SLOT_SIZE)
551 {
553 "Invalid wall length/prePadding/postPadding combination (%1/%2/%3 = %4) - using default %5",
554 wallPair.m_fWallLength,
555 PrePadding,
556 PostPadding,
557 length,
558 MIN_WALL_SLOT_SIZE,
559 level: LogLevel.WARNING);
560 length = MIN_WALL_SLOT_SIZE;
561 }
562
563 // normal wall
564
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))
570 {
571if (maxLoops2 -- < 1)
572{
573Print("breaking while #2");
574break;
575}
576 if (m_bEnableMiddleObject && !PlaceMiddleAtVertexOnly && MiddleObject)
577 {
578 // insert a middle object after each wall
579 CreateWallEntity(
580 worldEditorAPI, s_World, MiddleObject,
581 prevPoint, MiddleObjectOffsetUp * vector.Up + MiddleObjectOffsetRight * vector.Right, MiddleObjectPrePadding, MiddleObjectPostPadding,
582 rotationOffset, nextAnchorIndex);
583 }
584
585 wallPair = m_WallGroupContainer.GetRandomWall(length, m_RandomGenerator.RandFloat01());
586 if (!wallPair)
587 break; // "while true" exit
588 }
589
590 // distance is too short for the current wallPair, let's retry with a shorter distance
591 distance = vector.DistanceXZ(prevPoint, anchorPoints[nextAnchorIndex]); // TODO: use -shape- distance to next anchor
592 wallPair = m_WallGroupContainer.GetRandomWall(distance - PrePadding - PostPadding, m_RandomGenerator.RandFloat01());
593 if (!wallPair)
594 break; // "while true" safety
595 }
596 }
597 }
598
599 //------------------------------------------------------------------------------------------------
604 static float MeasureEntity(ResourceName entityName, int measureAxis)
605 {
606 if (!entityName)
607 return -1;
608
609 float result;
610 if (MEASUREMENTS.Find(entityName, result))
611 return result;
612
613 result = -1;
614
615 Resource resource = Resource.Load(entityName);
616 if (!resource.IsValid())
617 {
618 MEASUREMENTS.Insert(entityName, result);
619 return result;
620 }
621
622 IEntity wallEntity;
623 if (s_World) // "little" safety, soon™ SCR_WallGroupContainer will not be the one measuring entities
624 wallEntity = GetGame().SpawnEntityPrefab(resource, s_World);
625 else
626 wallEntity = GetGame().SpawnEntityPrefab(resource, ((WorldEditor)Workbench.GetModule(WorldEditor)).GetApi().GetWorld());
627
628 if (wallEntity)
629 {
630 vector minBB;
631 vector maxBB;
632 wallEntity.GetBounds(minBB, maxBB);
633 delete wallEntity;
634
635 if (measureAxis == 2) // Z-axis
636 result = (maxBB - minBB)[2];
637 else if (measureAxis == 1) // Y-axis
638 result = (maxBB - minBB)[1];
639 else // X-axis or incorrect value
640 result = (maxBB - minBB)[0];
641
642 if (result <= 0)
643 {
644 Print("Wall asset length is zero, does it have a valid mesh? " + entityName, LogLevel.ERROR);
645 result = -1;
646 }
647 else
648 if (result < MIN_WALL_PIECE_SIZE)
649 {
650 Print("Wall asset is too small, does it have a valid mesh? " + entityName, LogLevel.ERROR);
651 result = MIN_WALL_PIECE_SIZE;
652 }
653 }
654 else
655 {
656 Print("Cannot measure entity " + entityName, LogLevel.WARNING);
657 }
658
659 MEASUREMENTS.Insert(entityName, result);
660
661 return result;
662 }
663
664 //------------------------------------------------------------------------------------------------
665 protected IEntitySource CreateWallEntity(
666 WorldEditorAPI worldEditorAPI,
667 BaseWorld world,
668 ResourceName resourceName,
669 inout vector startPos, // as there is Y offset, pre- and post-padding
670 vector relPosOffset,
671 float prePadding,
672 float postPadding,
673 float dirOffset,
674 int anchorLimit = -1)
675 {
676 int axis;
677 if (!UseXAsForward)
678 axis = 2;
679
680 float size = MeasureEntity(resourceName, axis) + prePadding + postPadding;
681 if (size <= 0) // -1
682 return null;
683
684 vector nextPoint;
685 if (!m_ShapeNextPointHelper.GetNextPoint(size, nextPoint, anchorLimit, xzMode: true))
686 return null;
687
688 if (startPos == nextPoint)
689 return null;
690
691 vector direction = vector.Direction(startPos, nextPoint).Normalized();
692 if (direction == vector.Zero)
693 return null;
694
695 if (m_bSnapOffsetShapeToTheGround)
696 {
697 vector absPos = CoordToParent(startPos);
698 absPos[1] = worldEditorAPI.GetWorld().GetSurfaceY(absPos[0], absPos[2]) + m_vShapeOffset[1];
699 startPos = CoordToLocal(absPos);
700 }
701
702 startPos += prePadding * direction
703 + relPosOffset[0] * (direction * -vector.Up) + relPosOffset[1] * vector.Up; // 90° 2D rotation + altitude
704
705 vector angles = direction.VectorToAngles();
706 angles = { 0, Math.Repeat(angles[0] + dirOffset, 360), 0 };
707
708 startPos += m_vShapeOffset[1] * vector.Up;
709
710 IEntitySource result = worldEditorAPI.CreateEntity(resourceName, string.Empty, m_iSourceLayerID, m_Source, startPos, angles);
711 startPos = nextPoint; // out
712 return result;
713 }
714
715 //------------------------------------------------------------------------------------------------
716 protected IEntitySource PlacePrefab(
717 bool generate,
718 ResourceName name,
719 out vector pos,
720 vector dir,
721 vector prevDir,
722 float rotationAdjustment,
723 bool isGeneratorVisible,
724 float length,
725 float prePadding,
726 float postPadding,
727 float offsetUp,
728 bool alignNext,
729 bool prepadNext,
730 bool snapToGround = false,
731 bool allowClipping = false,
732 vector offsetRight = vector.Zero)
733 {
734 if (!name) // .IsEmpty()
735 return null;
736
737 vector prepadDirection;
738 vector orientation;
739
740 if (alignNext)
741 orientation = dir;
742 else
743 orientation = prevDir;
744
745 if (prepadNext)
746 prepadDirection = dir;
747 else
748 prepadDirection = prevDir;
749
750 pos += prepadDirection * prePadding;
751 IEntitySource ent;
752 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
753
754 if (generate)
755 {
756 vector rotMat[4];
757 Math3D.DirectionAndUpMatrix(orientation, vector.Up, rotMat);
758 vector rot = Math3D.MatrixToAngles(rotMat);
759 rot = { 0, rot[0] + rotationAdjustment, 0 };
760
761 if (m_bSnapOffsetShapeToTheGround)
762 {
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];
767 }
768
769 if (snapToGround)
770 ent = worldEditorAPI.CreateEntityExt(name, string.Empty, m_iSourceLayerID, m_Source, pos + offsetRight, rot, TraceFlags.ENTS);
771 else
772 ent = worldEditorAPI.CreateEntity(name, string.Empty, m_iSourceLayerID, m_Source, pos + offsetRight, rot);
773
774 if (offsetUp != 0)
775 {
776 vector entPos;
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]));
781 }
782
783 worldEditorAPI.SetEntityVisible(ent, isGeneratorVisible, false);
784 }
785
786 if (allowClipping)
787 length = 0;
788
789 pos += dir * Math.Max(0.1, length + postPadding);
790
791 return ent;
792 }
793
794 //------------------------------------------------------------------------------------------------
795 protected override void _WB_OnParentChange(IEntitySource src, IEntitySource prevParentSrc)
796 {
797 super._WB_OnParentChange(src, prevParentSrc);
798
799 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
800 if (!worldEditorAPI || worldEditorAPI.UndoOrRedoIsRestoring())
801 return;
802
803 Generate();
804 }
805
806 //------------------------------------------------------------------------------------------------
807 protected override bool _WB_OnKeyChanged(BaseContainer src, string key, BaseContainerList ownerContainers, IEntity parent)
808 {
809 if (!super._WB_OnKeyChanged(src, key, ownerContainers, parent))
810 return false;
811
812 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
813 if (!worldEditorAPI || worldEditorAPI.UndoOrRedoIsRestoring())
814 return false;
815
816 // caution: m_ShapeNextPointHelper is overwritten!
817 if (key == "m_bStartFromTheEnd")
818 {
819 ShapeEntity shapeEntity = ShapeEntity.Cast(worldEditorAPI.SourceToEntity(m_ParentShapeSource));
820 if (!shapeEntity)
821 return false;
822
823 array<vector> anchorPoints = {};
824 array<vector> tesselatedPoints = {};
825 if (!SCR_ParallelShapeHelper.GetAnchorsAndTesselatedPointsFromShape(shapeEntity, m_vShapeOffset, m_bYOffsetInShapeSpace, anchorPoints, tesselatedPoints))
826 {
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);
828 return false;
829 }
830
831 if (!m_bStartFromTheEnd) // actually activated - going from false to true (not yet saved to container)
832 {
833 SCR_ArrayHelperT<vector>.Reverse(anchorPoints);
834 SCR_ArrayHelperT<vector>.Reverse(tesselatedPoints);
835 }
836
837 m_ShapeNextPointHelper = SCR_ShapeNextPointHelper.CreateFromPoints(anchorPoints, tesselatedPoints);
838 }
839
840 Generate();
841
842 return true;
843 }
844
845 //------------------------------------------------------------------------------------------------
846 protected override void OnShapeChangedInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, array<vector> mins, array<vector> maxes)
847 {
848 super.OnShapeChangedInternal(shapeEntitySrc, shapeEntity, mins, maxes);
849
850 Generate();
851 }
852
853#endif WORKBENCH
854}
ArmaReforgerScripted GetGame()
Definition game.c:1398
int size
ref array< string > angles
params precision
ResourceName resourceName
Definition SCR_AIGroup.c:66
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 ...
float distance
EDamageType type
vector direction
void Generate()
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)
Definition Types.c:486
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
Definition LogLevel.c:14
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)
TraceFlags
Definition TraceFlags.c:13