Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
ForestGeneratorEntity.c
Go to the documentation of this file.
1[EntityEditorProps(category: "GameScripted/Generators", description: "Forest Generator", dynamicBox: true, visible: false)]
5
12class ForestGeneratorEntity : SCR_AreaGeneratorBaseEntity
13{
14 /*
15 Generation
16 */
17
18 [Attribute(defvalue: "1", category: "Generation", desc: "Allow partial forest regeneration, regenerates the whole forest otherwise")]
19 protected bool m_bAllowPartialRegeneration;
20
21 [Attribute(defvalue: "0", category: "Generation", desc: "Click to regenerate the entire forest")]
23
24 /*
25 Debug
26 */
27
28 [Attribute(defvalue: "0", category: "Debug", desc: "Print the area of the forest generator polygon")]
29 protected bool m_bPrintArea;
30
31 [Attribute(defvalue: "0", category: "Debug", desc: "Print the count of entities spawned by this forest generator")]
32 protected bool m_bPrintEntitiesCount;
33
34 [Attribute(defvalue: "0", category: "Debug", desc: "Print advanced performance measurements")]
36
37 [Attribute(defvalue: "0", category: "Debug", desc: "Draw general debug shapes")]
38 protected bool m_bDrawDebugShapes;
39
40 [Attribute(defvalue: "0", category: "Debug", desc: "Draw obstacles debug shapes")]
42
43 [Attribute(defvalue: "0", category: "Debug", desc: "Draw rectangulation debug shapes")]
45
46 [Attribute(defvalue: "0", category: "Debug", desc: "Draw partial regeneration debug shapes")]
48
49 [Attribute(defvalue: "1", category: "Debug", desc: "Make entities follow terrain level on shape move (keeping their relative Y)")]
51
52 /*
53 Forest
54 */
55
56 [Attribute(defvalue: "", category: "Forest", desc: "Forest generator levels to spawn in this forest generator polygon")]
57 protected ref array<ref ForestGeneratorLevel> m_aLevels;
58
59 [Attribute(defvalue: "", category: "Forest", desc: "Forest generator clusters to spawn in this forest generator polygon", params: "noDetails")]
60 protected ref array<ref ForestGeneratorCluster> m_aClusters;
61
62 [Attribute(defvalue: "", category: "Forest", desc: "Curve defining general outline scaling; from inside (left, forest core) to outside (right, forest outline)", uiwidget: UIWidgets.CurveDialog, params: string.Format("%1 %2 0 %3", SCALE_CURVE_RANGE, SCALE_CURVE_MAX_VALUE - SCALE_CURVE_MIN_VALUE, SCALE_CURVE_MIN_VALUE))]
64
65 [Attribute(defvalue: "0", category: "Forest", desc: "Distance from shape over which the scaling occurs", uiwidget: UIWidgets.Slider, params: "0 100 0.1")]
67
68 protected static const float SCALE_CURVE_RANGE = 100;
69 protected static const float SCALE_CURVE_MAX_VALUE = 1;
70 protected static const float SCALE_CURVE_MIN_VALUE = 0;
71
72#ifdef WORKBENCH
73
74 protected ref ForestGeneratorGrid m_Grid;
75 protected ref array<vector> m_aShapePoints; // used by Level scale curves
76
77 protected ref array<ref SCR_ForestGeneratorLine> m_aLines = {};
78 protected ref array<ref SCR_ForestGeneratorPoint> m_aMiddleOutlinePoints = {};
79 protected ref array<ref SCR_ForestGeneratorLine> m_aSmallOutlineLines = {};
80 protected ref array<ref SCR_ForestGeneratorPoint> m_aSmallOutlinePoints = {};
81 protected ref array<ref SCR_ForestGeneratorLine> m_aMiddleOutlineLines = {};
82 protected ref array<ref SCR_ForestGeneratorRectangle> m_aRectangles = {};
83 protected ref array<ref SCR_ForestGeneratorRectangle> m_aOutlineRectangles = {};
84 protected ref array<ref SCR_ForestGeneratorRectangle> m_aNonOutlineRectangles = {};
85 protected ref array<ref ForestGeneratorTreeBase> m_aGridEntries = {}; // only used to keep references for the grid, unused otherwise
86
87 protected ref array<ref ForestGeneratorOutline> m_aOutlines = {};
88 protected float m_fMaxOutlinesWidth;
89 protected float m_fArea;
90
91 protected ref map<IEntitySource, float> m_mEntitySourceATLHeights;
92
93 protected static ref array<float> s_aPreviousPoints2D;
94 protected static ref SCR_TimeMeasurementHelper s_Benchmark;
95
96 protected static const int MAX_CLUSTER_ATTEMPT = 10;
97
98 // debug shapes info
99 protected static ref SCR_DebugShapeManager s_DebugShapeManager; // static to only have one debugged forest at a time
100
101 protected static const int REGENERATION_DELETION_COLOUR = Color.RED;
102 protected static const int REGENERATION_CREATION_COLOUR = Color.GREEN;
103 protected static const vector DEBUG_VERTICAL_LINE = "0 30 0";
104
105 protected static const float RECTANGULATION_SIZE = 50; // 50x50m rectangles - TODO: find a smart calculation?
106 protected static const float HECTARE_CONVERSION_FACTOR = 0.0001; // x/10000
107 protected static const float MIN_POSSIBLE_SCALE_VALUE = 0.001; // 1/1000 is a small enough tree scale
108 protected static const string POINTDATA_CLASSNAME = ((typename)ForestGeneratorPointData).ToString();
109
110 // TODO: generate top trees first THEN other layers
111 protected static const int TREE_GROUPS_OFFSET_HACK = 10;
112
113 protected static const int GRID_SIZE = 10; // 10 seems to be the best value here (tried 1, 20, 100)
114
115 //------------------------------------------------------------------------------------------------
119 protected bool OnLine(SCR_ForestGeneratorLine line, SCR_ForestGeneratorPoint point)
120 {
121 float a = Math.Max(line.p1.m_vPos[0], line.p2.m_vPos[0]);
122 float b = Math.Min(line.p1.m_vPos[0], line.p2.m_vPos[0]);
123 float c = Math.Max(line.p1.m_vPos[2], line.p2.m_vPos[2]);
124 float d = Math.Min(line.p1.m_vPos[2], line.p2.m_vPos[2]);
125
126 return
127 point.m_vPos[0] <= a &&
128 point.m_vPos[0] <= b &&
129 point.m_vPos[2] <= c &&
130 point.m_vPos[2] <= d;
131 }
132
133 //------------------------------------------------------------------------------------------------
138 protected int Direction(SCR_ForestGeneratorPoint a, SCR_ForestGeneratorPoint b, SCR_ForestGeneratorPoint c)
139 {
140 int val =
141 (b.m_vPos[2] - a.m_vPos[2]) * (c.m_vPos[0] - b.m_vPos[0]) -
142 (b.m_vPos[0] - a.m_vPos[0]) * (c.m_vPos[2] - b.m_vPos[2]);
143
144 if (val == 0)
145 return 0; // colinear
146
147 if (val < 0)
148 return 2; // counter-clockwise direction
149
150 return 1; // clockwise direction
151 }
152
153 //------------------------------------------------------------------------------------------------
157 protected bool IsIntersect(SCR_ForestGeneratorLine line1, SCR_ForestGeneratorLine line2)
158 {
159 // four Direction for two lines and points of other line
160 int dir1 = Direction(line1.p1, line1.p2, line2.p1);
161 int dir2 = Direction(line1.p1, line1.p2, line2.p2);
162 int dir3 = Direction(line2.p1, line2.p2, line1.p1);
163 int dir4 = Direction(line2.p1, line2.p2, line1.p2);
164
165 return
166 (dir1 != dir2 && dir3 != dir4) || // they are intersecting
167 (dir1 == 0 && OnLine(line1, line2.p1)) || // when p2 of line2 are on the line1
168 (dir2 == 0 && OnLine(line1, line2.p2)) || // when p1 of line2 are on the line1
169 (dir3 == 0 && OnLine(line2, line1.p1)) || // when p2 of line1 are on the line2
170 (dir4 == 0 && OnLine(line2, line1.p2)); // when p1 of line1 are on the line2
171 }
172
173 //------------------------------------------------------------------------------------------------
177 protected bool IsIntersect(SCR_ForestGeneratorLine line, SCR_ForestGeneratorRectangle rectangle)
178 {
179 return
180 IsIntersect(line, rectangle.m_Line1) ||
181 IsIntersect(line, rectangle.m_Line2) ||
182 IsIntersect(line, rectangle.m_Line3) ||
183 IsIntersect(line, rectangle.m_Line4);
184 }
185
186 //------------------------------------------------------------------------------------------------
193 protected bool PreprocessTreeArray(notnull array<ref ForestGeneratorTree> trees, int groupIdx, SCR_ETreeType type, int debugGroupIdx)
194 {
195 ForestGeneratorTree tree;
196 float probaSum = 0;
197 for (int i = trees.Count() - 1; i >= 0; --i)
198 {
199 tree = trees[i];
200 if (tree.m_fWeight <= 0 || tree.m_Prefab.IsEmpty())
201 {
202 trees.RemoveOrdered(i);
203 }
204 else
205 {
206 probaSum += tree.m_fWeight;
207 tree.m_iGroupIndex = groupIdx;
208 tree.m_eType = type;
209 }
210 }
211
212 if (probaSum > 0)
213 {
214 foreach (ForestGeneratorTree tree2 : trees)
215 {
216 tree2.m_fWeight = tree2.m_fWeight / probaSum;
217 }
218 }
219
220 return !trees.IsEmpty();
221 }
222
223 //------------------------------------------------------------------------------------------------
225 protected void PreprocessAllTrees()
226 {
227 int debugGroupIdx;
228 foreach (ForestGeneratorLevel level : m_aLevels)
229 {
230 level.m_aGroupProbas = {};
231
232 if (level.m_eType == SCR_EForestGeneratorLevelType.BOTTOM)
233 {
234 foreach (TreeGroupClass treeGroup : level.m_aTreeGroups)
235 {
236 PreprocessTreeArray(treeGroup.m_aTrees, 0, SCR_ETreeType.BOTTOM, debugGroupIdx);
237 }
238 continue;
239 }
240
241 // TOP or OUTLINE
242
243 int groupIdx;
244 float groupProbaSum;
245
246 TreeGroupClass treeGroup;
247 // cannot use foreach because editing the currently iterated array (yikes!)
248 for (int i, groupCount = level.m_aTreeGroups.Count(); i < groupCount; i++)
249 {
250 treeGroup = level.m_aTreeGroups[i];
251
252 if (treeGroup.m_fWeight > 0 &&
253 treeGroup.m_aTrees &&
254 !treeGroup.m_aTrees.IsEmpty() &&
255 PreprocessTreeArray(treeGroup.m_aTrees, groupIdx, SCR_ETreeType.TOP, debugGroupIdx))
256 {
257 groupProbaSum += treeGroup.m_fWeight;
258 groupIdx++;
259 debugGroupIdx++;
260 }
261 else
262 {
263 level.m_aTreeGroups.RemoveOrdered(i);
264 groupCount--;
265 i--;
266 }
267 }
268
269 foreach (TreeGroupClass treeGroup2 : level.m_aTreeGroups)
270 {
271 if (groupProbaSum > 0)
272 treeGroup2.m_fWeight = treeGroup2.m_fWeight / groupProbaSum;
273
274 level.m_aGroupProbas.Insert(treeGroup2.m_fWeight);
275 }
276 }
277 }
278
279 //------------------------------------------------------------------------------------------------
283 protected array<ref SCR_ForestGeneratorPoint> GetClockWisePoints(notnull IEntitySource shapeEntitySource)
284 {
285 BaseContainerList points = shapeEntitySource.GetObjectArray("Points");
286 if (!points)
287 return null;
288
289 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
290
291 array<ref SCR_ForestGeneratorPoint> result = {};
292
293 BaseContainer point;
294 vector pos;
295 BaseContainerList dataArr;
296 BaseContainer data;
297 SCR_ForestGeneratorPoint genPoint;
298 for (int i, pointCount = points.Count(); i < pointCount; i++)
299 {
300 point = points.Get(i);
301 point.Get("Position", pos);
302
303 bool smallOutline = true;
304 bool middleOutline = true;
305 dataArr = point.GetObjectArray("Data");
306
307 bool hasPointData = false;
308 for (int j, dataCount = dataArr.Count(); j < dataCount; ++j)
309 {
310 data = dataArr.Get(j);
311 if (data.GetClassName() == POINTDATA_CLASSNAME)
312 {
313 data.Get("m_bSmallOutline", smallOutline);
314 data.Get("m_bMiddleOutline", middleOutline);
315 hasPointData = true;
316 break;
317 }
318 }
319
320 if (!hasPointData && worldEditorAPI && !worldEditorAPI.UndoOrRedoIsRestoring())
321 worldEditorAPI.CreateObjectArrayVariableMember(point, null, "Data", POINTDATA_CLASSNAME, dataArr.Count());
322
323 bool skip = false;
324 foreach (SCR_ForestGeneratorPoint curPoint : result)
325 {
326 if (curPoint.m_vPos == pos)
327 {
328 Print("Found two points on the same position: " + pos + ", Skipping", LogLevel.WARNING);
329 skip = true;
330 break;
331 }
332 }
333
334 if (skip)
335 continue;
336
337 genPoint = new SCR_ForestGeneratorPoint();
338 pos[1] = 0;
339 genPoint.m_vPos = pos;
340 genPoint.m_bSmallOutline = smallOutline;
341 genPoint.m_bMiddleOutline = middleOutline;
342
343 result.Insert(genPoint);
344 }
345
346 // clockwise check
347 int count = result.Count();
348 if (count < 3)
349 return result;
350
351 int sum = 0;
352 vector currentPoint;
353 vector nextPoint;
354 for (int i; i < count; i++)
355 {
356 currentPoint = result[i].m_vPos;
357 if (i == count - 1)
358 nextPoint = result[0].m_vPos;
359 else
360 nextPoint = result[i + 1].m_vPos;
361
362 sum += (nextPoint[0] - currentPoint[0]) * (nextPoint[2] + currentPoint[2]);
363 }
364
365 if (sum < 0) // counter-clockwise, inverting points (reason unknown)
366 {
367 for (int i, iterNum = count * 0.5; i < iterNum; i++)
368 {
369 genPoint = result[i];
370 result[i] = result[count - 1 - i];
371 result[count - 1 - i] = genPoint;
372 }
373 }
374
375 return result;
376 }
377
378 //------------------------------------------------------------------------------------------------
381 protected void FillOutlineLinesAndPoints(notnull array<ref SCR_ForestGeneratorPoint> points)
382 {
383 SCR_ForestGeneratorLine line;
384 foreach (int i, SCR_ForestGeneratorPoint point : points)
385 {
386 if (i > 0)
387 {
388 line.p2 = point;
389 line.m_fLength = (line.p2.m_vPos - line.p1.m_vPos).Length();
390 point.m_Line1 = line;
391 m_aLines.Insert(line);
392
393 if (point.m_bSmallOutline)
394 m_aSmallOutlinePoints.Insert(point);
395
396 if (point.m_bMiddleOutline)
397 m_aMiddleOutlinePoints.Insert(point);
398
399 if (line.p1.m_bSmallOutline)
400 m_aSmallOutlineLines.Insert(line);
401
402 if (line.p1.m_bMiddleOutline)
403 m_aMiddleOutlineLines.Insert(line);
404 }
405
406 line = new SCR_ForestGeneratorLine();
407 line.p1 = point;
408 point.m_Line2 = line;
409 }
410
411 SCR_ForestGeneratorPoint point = points[0];
412 line.p2 = point;
413 point.m_Line1 = line;
414 line.m_fLength = (line.p2.m_vPos - line.p1.m_vPos).Length();
415 m_aLines.Insert(line);
416
417 if (point.m_bSmallOutline)
418 m_aSmallOutlinePoints.Insert(point);
419
420 if (point.m_bMiddleOutline)
421 m_aMiddleOutlinePoints.Insert(point);
422
423 if (line.p1.m_bSmallOutline)
424 m_aSmallOutlineLines.Insert(line);
425
426 if (line.p1.m_bMiddleOutline)
427 m_aMiddleOutlineLines.Insert(line);
428 }
429
430 //------------------------------------------------------------------------------------------------
433 protected void CalculateOutlineAnglesForPoints(notnull array<ref SCR_ForestGeneratorPoint> points)
434 {
435 int count = points.Count();
436 if (count < 3)
437 return;
438
439 SCR_ForestGeneratorPoint previousPoint = points[count - 1];
440 SCR_ForestGeneratorPoint currentPoint;
441 SCR_ForestGeneratorPoint nextPoint;
442 vector dir1;
443 vector dir2;
444 for (int i; i < count; i++)
445 {
446 currentPoint = points[i];
447
448 if (i < count - 1)
449 nextPoint = points[i + 1];
450 else
451 nextPoint = points[0];
452
453 dir1 = previousPoint.m_vPos - currentPoint.m_vPos;
454 dir2 = nextPoint.m_vPos - currentPoint.m_vPos;
455 float yaw1 = dir1.ToYaw();
456 float yaw2 = dir2.ToYaw();
457 if (yaw1 > yaw2)
458 {
459 currentPoint.m_fMinAngle = yaw1 - 360;
460 currentPoint.m_fMaxAngle = yaw2;
461 }
462 else
463 {
464 currentPoint.m_fMinAngle = yaw1;
465 currentPoint.m_fMaxAngle = yaw2;
466 }
467
468 currentPoint.m_fAngle = Math.AbsFloat(currentPoint.m_fMaxAngle - currentPoint.m_fMinAngle);
469
470 previousPoint = currentPoint;
471 }
472 }
473
474 //------------------------------------------------------------------------------------------------
475 protected override bool _WB_OnKeyChanged(BaseContainer src, string key, BaseContainerList ownerContainers, IEntity parent)
476 {
477 if (!super._WB_OnKeyChanged(src, key, ownerContainers, parent))
478 return false;
479
480 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
481 if (!worldEditorAPI || worldEditorAPI.UndoOrRedoIsRestoring())
482 return true;
483
484 src = worldEditorAPI.EntityToSource(this); // src is not fresh enough
485
486 if (key == "m_bRegenerateEntireForest")
487 {
488 src.ClearVariable("m_bRegenerateEntireForest"); // don't save it to layers
489
490 if (m_ParentShapeSource)
491 RegenerateForest(true);
492 }
493
494 BaseContainerTools.WriteToInstance(this, src); // required for tree type changes to be considered without having to reload the world
495
496 if (m_ParentShapeSource && key == "m_iSeed")
497 RegenerateForest(true);
498
499 return true;
500 }
501
502 //------------------------------------------------------------------------------------------------
503 // triggers when a point is created/moved/deleted (or when a point data is edited!)
504 protected override void OnShapeChangedInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, array<vector> mins, array<vector> maxes)
505 {
506 super.OnShapeChangedInternal(shapeEntitySrc, shapeEntity, mins, maxes);
507 RegenerateForest();
508 }
509
510 //------------------------------------------------------------------------------------------------
511 // triggers when the generator is inserted in shape
512 protected override void OnShapeInitInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity)
513 {
514 super.OnShapeInitInternal(shapeEntitySrc, shapeEntity);
515 RegenerateForest(true);
516 }
517
518 //------------------------------------------------------------------------------------------------
519 protected override void BeforeShapeTransformInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, inout vector oldTransform[4])
520 {
521 super.BeforeShapeTransformInternal(shapeEntitySrc, shapeEntity, oldTransform);
522
524 return;
525
526 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
527 if (!worldEditorAPI)
528 return;
529
530 if (worldEditorAPI.UndoOrRedoIsRestoring())
531 return;
532
533 vector localPos, worldPos;
534 vector parentPos;
535 shapeEntitySrc.Get("coords", parentPos); // measurement has to be done on EntitySource, not Entity
536
537 m_mEntitySourceATLHeights = new map<IEntitySource, float>();
538 IEntitySource childSource;
539 for (int i = m_Source.GetNumChildren() - 1; i >= 0; i--)
540 {
541 childSource = m_Source.GetChild(i);
542 if (!childSource.Get("coords", localPos))
543 continue;
544
545 worldPos = localPos + parentPos;
546
547 float yTerrain;
548 if (!worldEditorAPI.TryGetTerrainSurfaceY(worldPos[0], worldPos[2], yTerrain))
549 continue;
550
551 m_mEntitySourceATLHeights.Insert(childSource, worldPos[1] - yTerrain);
552 }
553 }
554
555 //------------------------------------------------------------------------------------------------
556 protected override void OnShapeTransformInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, array<vector> mins, array<vector> maxes)
557 {
558 super.OnShapeTransformInternal(shapeEntitySrc, shapeEntity, mins, maxes);
559
561 return;
562
563 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
564 if (!worldEditorAPI)
565 return;
566
567 if (worldEditorAPI.UndoOrRedoIsRestoring())
568 return;
569
570 if (m_mEntitySourceATLHeights.IsEmpty())
571 return;
572
573 vector absPos = GetOrigin(); // assuming the generator stays at relative 0 0 0
574 vector localPos, worldPos;
575
576 foreach (IEntitySource childSource, float relativeY : m_mEntitySourceATLHeights)
577 {
578 if (!childSource.Get("coords", localPos))
579 continue;
580
581 worldPos = localPos + absPos;
582
583 float yTerrain;
584 if (!worldEditorAPI.TryGetTerrainSurfaceY(worldPos[0], worldPos[2], yTerrain))
585 continue;
586
587 float difference = relativeY - (worldPos[1] - yTerrain);
588 if (difference != 0)
589 {
590 localPos[1] = localPos[1] + difference;
591 worldEditorAPI.SetVariableValue(childSource, null, "coords", localPos.ToString(false));
592 }
593 }
594
595 m_mEntitySourceATLHeights = null;
596 }
597
598 //------------------------------------------------------------------------------------------------
599 // hack!!1!one waiting for BeforeShapeChangedInternal introduction
600 protected override void OnPointChangedInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, PointChangedSituation situation, int pointIndex, vector position)
601 {
602 super.OnPointChangedInternal(shapeEntitySrc, shapeEntity, situation, pointIndex, position);
603 if (s_aPreviousPoints2D)
604 return;
605
606 array<vector> points3D = GetAnchorPoints(m_ParentShapeSource);
607 s_aPreviousPoints2D = {};
608 SCR_Math2D.Get2DPolygon(points3D, s_aPreviousPoints2D);
609 }
610
611 //------------------------------------------------------------------------------------------------
615 protected int GetColorForTree(int index, SCR_ETreeType type)
616 {
617 const int colCount = 11;
618 int colIdx = (5 * index + (int)type) % colCount; // 5 because 5 SCR_ETreeType types
619 int color;
620 switch (colIdx)
621 {
622 case 0: color = 0xFF56E3D7; break;
623 case 1: color = 0xFF428AF5; break;
624 case 2: color = 0xFFF57542; break;
625 case 3: color = 0xFF8AE356; break;
626 case 4: color = 0xFF2F636B; break;
627 case 5: color = 0xFF818491; break;
628 case 6: color = 0xFFED9DBB; break;
629 case 7: color = 0xFF0009AB; break;
630 case 8: color = 0xFFAB003C; break;
631 case 9: color = 0xFFA8AB00; break;
632 case 10: color = 0xFFFFFFFF; break;
633 default: color = 0xFFFFFFFF; break;
634 }
635 return color;
636 }
637
638 //------------------------------------------------------------------------------------------------
640 protected void MemoryCleanup()
641 {
642 // all these are apparently PopulateGrid-related
643 m_aLines.Clear();
644 m_aSmallOutlineLines.Clear();
645 m_aMiddleOutlineLines.Clear();
646 m_aRectangles.Clear();
647 m_aOutlineRectangles.Clear();
648 m_aSmallOutlinePoints.Clear();
649 m_aMiddleOutlinePoints.Clear();
650 m_aNonOutlineRectangles.Clear();
651 }
652
653 //------------------------------------------------------------------------------------------------
656 protected void RegenerateForest(bool forceRegeneration = false)
657 {
658 if (!m_bEnableGeneration)
659 {
660 Print("Forest generation is disabled for this shape - tick it back on before saving", LogLevel.NORMAL);
661 return;
662 }
663
664 float tick = System.GetTickCount(); // not Debug.BeginTimeMeasure because returns are on the way
665 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
666 if (!worldEditorAPI)
667 {
668 Print("WorldEditorAPI is not available", LogLevel.ERROR);
669 return;
670 }
671
672 if (worldEditorAPI.UndoOrRedoIsRestoring() || !worldEditorAPI.AreGeneratorEventsEnabled())
673 return;
674
675 SetSeed();
676
677 // clear everything
678 m_aSmallOutlinePoints.Clear();
679 m_aMiddleOutlinePoints.Clear();
680 m_aSmallOutlineLines.Clear();
681 m_aMiddleOutlineLines.Clear();
682 m_aOutlines.Clear();
683
684 m_Grid = new ForestGeneratorGrid(GRID_SIZE);
685
686 s_DebugShapeManager.Clear();
687
688 Debug.BeginTimeMeasure();
689 PreprocessAllTrees();
690 Debug.EndTimeMeasure("Provided data preprocess");
691
692 // load outlines
693 float outlineWidthToClear; // outline or because scaling curves distance to clean
694 ForestGeneratorOutline outline;
695 foreach (ForestGeneratorLevel level : m_aLevels)
696 {
697 if (outlineWidthToClear < level.m_fOutlineScaleCurveDistance)
698 outlineWidthToClear = level.m_fOutlineScaleCurveDistance;
699
700 outline = ForestGeneratorOutline.Cast(level);
701 if (!outline)
702 continue;
703
704 if (m_fMaxOutlinesWidth < outline.m_fMaxDistance)
705 m_fMaxOutlinesWidth = outline.m_fMaxDistance;
706
707 if (m_fMaxOutlinesWidth < outline.m_fMinDistance) // is this check even required?
708 m_fMaxOutlinesWidth = outline.m_fMinDistance;
709
710 m_aOutlines.Insert(outline);
711 }
712
713 if (outlineWidthToClear < m_fMaxOutlinesWidth)
714 outlineWidthToClear = m_fMaxOutlinesWidth;
715
716 array<ref SCR_ForestGeneratorPoint> generatorPoints = GetClockWisePoints(m_ParentShapeSource);
717 if (!generatorPoints || generatorPoints.IsEmpty())
718 return;
719
720 Debug.BeginTimeMeasure();
721 FillOutlineLinesAndPoints(generatorPoints);
722 CalculateOutlineAnglesForPoints(generatorPoints);
723 Debug.EndTimeMeasure("Outline point calculations");
724
726 s_Benchmark = new SCR_TimeMeasurementHelper();
727 else
728 s_Benchmark = null;
729
730 // generate the forest
731
732 // m_aShapePoints = GetTesselatedShapePoints(m_ParentShapeSource); // splines will be for later
733 m_aShapePoints = GetAnchorPoints(m_ParentShapeSource);
734 m_aShapePoints.Insert(m_aShapePoints[0]); // close the shape
735
736 // see SCR_ObstacleDetector.GetPoints2D3D()
737 array<vector> polygon3D = GetAnchorPoints(m_ParentShapeSource);
738 array<float> polygon2D = {};
739 SCR_Math2D.Get2DPolygon(polygon3D, polygon2D);
740
741 Print("ForestGenerator - Populating grid", LogLevel.DEBUG);
742 Debug.BeginTimeMeasure();
743 PopulateGrid(polygon2D, polygon3D);
744 Debug.EndTimeMeasure("ForestGenerator - Populating grid done");
745
746 // create point -1 - old point - point +1 triangles
747 // create point -1 - new point - point +1 triangles
748 // what happens if a point is just deleted - regenerate everything?
749 // - try to find if deleted or moved (count points, other points moved or not, etc)
750 // - if only deleted, calculate triangle from previous point then new segment (-1,+1)'s middle
751 // - if cannot decide whether or not it was deleted or moved, recalculate everything
752
753 SCR_ForestGeneratorOutlinePositionChecker outlinePositionChecker;
754
755 if (forceRegeneration)
756 {
757 s_aPreviousPoints2D = {};
758 }
759 else
760 {
761 if (!s_aPreviousPoints2D) // should have been obtained by OnPointChangedInternal
762 {
763 Print("No previous points! Fallback on current points (this edit does not do anything)", LogLevel.WARNING);
764 s_aPreviousPoints2D = polygon2D;
765 }
766
767 outlinePositionChecker = new SCR_ForestGeneratorOutlinePositionChecker(s_aPreviousPoints2D, polygon2D, outlineWidthToClear);
768 }
769
770 worldEditorAPI.BeginEditSequence(m_Source);
771
772 Print("ForestGenerator - Deleting previous entities", LogLevel.DEBUG);
773 Debug.BeginTimeMeasure();
774 int entitiesCount = DeletePreviousEntities(polygon2D, outlinePositionChecker, forceRegeneration);
775 Debug.EndTimeMeasure("ForestGenerator - Deleting " + entitiesCount + " previous entities done");
776
777 Print("ForestGenerator - Generating entities", LogLevel.DEBUG);
778 Debug.BeginTimeMeasure();
779 entitiesCount = GenerateEntities(s_aPreviousPoints2D, outlinePositionChecker, forceRegeneration);
780 Debug.EndTimeMeasure("ForestGenerator - Generating " + entitiesCount + " entities done");
781
782 worldEditorAPI.EndEditSequence(m_Source);
783
785 s_Benchmark.PrintAllMeasures();
786
787 s_aPreviousPoints2D = null;
788 MemoryCleanup();
789
790 int totalTime = System.GetTickCount(tick);
791 float msPerEntity;
792 float msPerSqMetre;
793 if (entitiesCount > 0)
794 msPerEntity = totalTime / entitiesCount;
795 if (m_fArea > 0)
796 msPerSqMetre = totalTime / m_fArea;
797
799 "Total time: %1 ms (~%2 ms/entity ratio / ~%3 ms/m²)",
800 totalTime,
801 msPerEntity.ToString(-1, 3),
802 msPerSqMetre.ToString(-1, 3),
803 level: LogLevel.NORMAL);
804 }
805
806 //------------------------------------------------------------------------------------------------
812 protected int DeletePreviousEntities(notnull array<float> currentPoints2D, SCR_ForestGeneratorOutlinePositionChecker outlineChecker, bool forceRegeneration = false)
813 {
814 int result;
815 if (forceRegeneration || !m_bAllowPartialRegeneration)
816 {
817 result = m_Source.GetNumChildren();
818 DeleteAllChildren();
819 return result;
820 }
821
822 // outline deletion
823
824 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
825 IEntitySource entitySource;
826 vector worldPos;
827 vector entityPos;
828 for (int i = m_Source.GetNumChildren() - 1; i >= 0; --i)
829 {
830 entitySource = m_Source.GetChild(i);
831 worldPos = worldEditorAPI.SourceToEntity(entitySource).GetOrigin();
832 entityPos = CoordToLocal(worldPos); // relative pos
833
834 // remove everything not in the new shape
835 if (!Math2D.IsPointInPolygon(currentPoints2D, entityPos[0], entityPos[2]))
836 {
837 worldEditorAPI.DeleteEntity(entitySource);
838 continue;
839 }
840
841 // delete everything close to old and new impacted outlines
842 if (outlineChecker.IsPosWithinSetDistance(entityPos))
843 {
845 s_DebugShapeManager.AddLine(worldPos, worldPos + DEBUG_VERTICAL_LINE, REGENERATION_DELETION_COLOUR);
846
847 worldEditorAPI.DeleteEntity(entitySource);
848 result++;
849 }
850 }
851
852 return result;
853 }
854
855 //------------------------------------------------------------------------------------------------
861 protected int GenerateEntities(notnull array<float> previousPoints2D, SCR_ForestGeneratorOutlinePositionChecker outlineChecker, bool forceRegeneration = false)
862 {
863 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
864
865 BaseWorld world = worldEditorAPI.GetWorld();
866 if (!world)
867 return 0;
868
869 // create new trees
870
871 Debug.BeginTimeMeasure();
872 RefreshObstacles();
873 Debug.EndTimeMeasure("Obstacles scan");
874
875 // draw obstacles
877 {
878 foreach (SCR_ObstacleDetectorSplineInfo info : s_ObstacleDetector.GetObstacles())
879 {
880 if (info.m_fClearance == 0) // an area obstacle
881 {
882 s_DebugShapeManager.AddAABBRectangleXZ(info.m_vMinWithClearance, info.m_vMaxWithClearance);
883 }
884 else // a road-like obstacle
885 {
886 vector maxWithClearance;
887 foreach (int index, vector minWithClearance : info.m_aMinsWithClearance)
888 {
889 maxWithClearance = info.m_aMaxsWithClearance[index];
890 s_DebugShapeManager.AddAABBRectangleXZ(minWithClearance, maxWithClearance);
891 }
892 }
893 }
894 }
895
896 bool partialGeneration = !forceRegeneration && m_bAllowPartialRegeneration && m_Source.GetNumChildren() > 0;
897
898 bool useScaleCurve = m_fGlobalOutlineScaleCurveDistance > 0 && !m_aGlobalOutlineScaleCurve.IsEmpty();
899 float scaleCurveDistanceDivisor;
900 array<float> curveKnots;
901 if (useScaleCurve)
902 {
903 // scaleCurveDistanceDivisor = 1 / m_fGlobalOutlineScaleCurveDistance;
904 scaleCurveDistanceDivisor = SCALE_CURVE_RANGE / m_fGlobalOutlineScaleCurveDistance;
905 // scaleCurveDistanceDivisor = 1;
906 curveKnots = {};
907 foreach (vector scalePoint : m_aGlobalOutlineScaleCurve)
908 {
909 curveKnots.Insert(scalePoint[0]);
910 }
911 }
912
913 // init done
914
915 int topLevelEntitiesCount, bottomLevelEntitiesCount;
916 int smallOutlineEntitiesCount, middleOutlineEntitiesCount;
917 int clusterEntitiesCount, otherEntitiesCount;
918 int generatedEntitiesCount;
919 int setVariableValueCalls;
920
921 TraceParam traceParam = new TraceParam();
922 traceParam.Flags = TraceFlags.WORLD;
923
924 map<ResourceName, ref SCR_RandomisationEditorData> randomValuesMap = new map<ResourceName, ref SCR_RandomisationEditorData>();
925 SCR_RandomisationEditorData randomisationData;
926 SCR_ForestGeneratorTreeBase baseEntry;
927 IEntitySource entitySource;
928 vector entityMatrix[4];
929 FallenTree fallenTree;
930 WideForestGeneratorClusterObject wideObject;
931
932 for (int i, count = m_Grid.GetEntryCount(); i < count; ++i)
933 {
934 vector worldPos;
935 baseEntry = m_Grid.GetEntry(i, worldPos);
936 if (!baseEntry.m_Prefab) // .IsEmpty()
937 continue;
938
939 worldPos[1] = world.GetSurfaceY(worldPos[0], worldPos[2]); // snaps to ground here
940 vector localPos = CoordToLocal(worldPos);
941 localPos[1] = localPos[1] + baseEntry.m_fVerticalOffset;
942
943 if (partialGeneration) // TODO: move to Grid population instead?
944 {
945 if (
946 Math2D.IsPointInPolygon(previousPoints2D, localPos[0], localPos[2]) && // if in the old shape
947 !outlineChecker.IsPosWithinSetDistance(localPos) // and not near a new outline
948 )
949 continue;
950 }
951
952 float scale = baseEntry.m_fScale;
953 if (useScaleCurve && scale > 0)
954 {
955 float distanceFromShape = SCR_Math3D.GetDistanceFromSplineXZ(m_aShapePoints, localPos);
956 if (distanceFromShape <= m_fGlobalOutlineScaleCurveDistance)
957 {
958 // (m_fOutlineScaleCurveDistance - distanceFromShape) because right-to-left curve reading
959 float scaleFactor = LegacyCurve.Curve(ECurveType.CatmullRom, (m_fGlobalOutlineScaleCurveDistance - distanceFromShape) * scaleCurveDistanceDivisor, m_aGlobalOutlineScaleCurve, curveKnots)[1];
960
961 if (scaleFactor < SCALE_CURVE_MIN_VALUE)
962 scaleFactor = SCALE_CURVE_MIN_VALUE;
963 else
964 if (scaleFactor > SCALE_CURVE_MAX_VALUE)
965 scaleFactor = SCALE_CURVE_MAX_VALUE;
966
967 scale *= scaleFactor;
968 }
969 }
970
971 if (scale < MIN_POSSIBLE_SCALE_VALUE)
972 {
973 PrintFormat("Avoiding near-zero scale tree (scale = %1 < %2)", scale, MIN_POSSIBLE_SCALE_VALUE, level: LogLevel.DEBUG);
974 continue;
975 }
976
977 if (s_Benchmark)
978 s_Benchmark.BeginMeasure("obstacleDetection");
979
980 // providing a generated entities list would hinder performance here
981 bool hasObstacle = s_ObstacleDetector.HasObstacle(worldPos);
982
983 if (s_Benchmark)
984 s_Benchmark.EndMeasure("obstacleDetection");
985
986 if (hasObstacle)
987 continue;
988
989 if (partialGeneration)
990 {
992 s_DebugShapeManager.AddLine(worldPos, worldPos + DEBUG_VERTICAL_LINE, REGENERATION_CREATION_COLOUR);
993 }
994
995 if (s_Benchmark)
996 s_Benchmark.BeginMeasure("createEntity");
997
998 entitySource = worldEditorAPI.CreateEntity(baseEntry.m_Prefab, string.Empty, m_iSourceLayerID, m_Source, localPos, vector.Zero);
999
1000 if (s_Benchmark)
1001 s_Benchmark.EndMeasure("createEntity");
1002
1003 generatedEntitiesCount++;
1004 switch (baseEntry.m_eType)
1005 {
1006 case SCR_ETreeType.TOP: topLevelEntitiesCount++; break;
1007 case SCR_ETreeType.BOTTOM: bottomLevelEntitiesCount++; break;
1008 case SCR_ETreeType.MIDDLE_OUTLINE: middleOutlineEntitiesCount++; break;
1009 case SCR_ETreeType.SMALL_OUTLINE: smallOutlineEntitiesCount++; break;
1010 case SCR_ETreeType.CLUSTER: clusterEntitiesCount++; break;
1011 default: otherEntitiesCount++; break;
1012 }
1013
1015 s_DebugShapeManager.AddSphere(worldPos, 1 + 5 - (int)baseEntry.m_eType, GetColorForTree(baseEntry.m_iGroupIndex, baseEntry.m_eType), ShapeFlags.NOOUTLINE);
1016 // 1 + 5 because 5x SCR_ETreeType types
1017
1018 worldEditorAPI.SourceToEntity(entitySource).GetTransform(entityMatrix);
1019
1020 if (s_Benchmark)
1021 s_Benchmark.BeginMeasure("editSequenceCalculation");
1022
1023 float randomVerticalOffset;
1024 vector randomAngles; // pitch yaw roll
1025 bool alignToNormal;
1026
1027 if (!randomValuesMap.Find(baseEntry.m_Prefab, randomisationData))
1028 {
1029 randomisationData = SCR_RandomisationEditorData.CreateFromEntitySource(entitySource);
1030 randomValuesMap.Insert(baseEntry.m_Prefab, randomisationData);
1031 }
1032
1033 if (randomisationData)
1034 {
1035 randomVerticalOffset = SafeRandomFloatInclusive(randomisationData.m_vRandomVertOffset[0], randomisationData.m_vRandomVertOffset[1]);
1036
1037 randomAngles[0] = SafeRandomFloatInclusive(-randomisationData.m_fRandomPitchAngle, randomisationData.m_fRandomPitchAngle);
1038 randomAngles[1] = SafeRandomFloatInclusive(0, 360);
1039 randomAngles[2] = SafeRandomFloatInclusive(-randomisationData.m_fRandomRollAngle, randomisationData.m_fRandomRollAngle);
1040
1041 alignToNormal = randomisationData.m_bAlignToNormal;
1042 }
1043 else
1044 {
1045 if (baseEntry.m_fVerticalOffset > 0)
1046 randomVerticalOffset = SafeRandomFloatInclusive(-baseEntry.m_fVerticalOffset, baseEntry.m_fVerticalOffset);
1047
1048 if (baseEntry.m_fRandomPitchAngle > 0)
1049 randomAngles[0] = SafeRandomFloatInclusive(-baseEntry.m_fRandomPitchAngle, baseEntry.m_fRandomPitchAngle);
1050
1051 randomAngles[1] = SafeRandomFloatInclusive(0, 360);
1052
1053 if (baseEntry.m_fRandomRollAngle > 0)
1054 randomAngles[2] = SafeRandomFloatInclusive(-baseEntry.m_fRandomRollAngle, baseEntry.m_fRandomRollAngle);
1055
1056 // alignToNormal = false;
1057 }
1058
1059 fallenTree = FallenTree.Cast(baseEntry);
1060 wideObject = WideForestGeneratorClusterObject.Cast(baseEntry);
1061
1062 if (wideObject)
1063 alignToNormal = wideObject.m_bAlignToNormal;
1064 else
1065 if (fallenTree)
1066 alignToNormal = fallenTree.m_bAlignToNormal;
1067
1068 if (randomAngles[1] != 0) // yaw first
1069 SCR_Math3D.RotateAround(entityMatrix, entityMatrix[3], entityMatrix[1], -Math.DEG2RAD * randomAngles[1], entityMatrix);
1070
1071 if (randomAngles[0] != 0)
1072 SCR_Math3D.RotateAround(entityMatrix, entityMatrix[3], entityMatrix[0], -Math.DEG2RAD * randomAngles[0], entityMatrix);
1073
1074 if (randomAngles[2] != 0)
1075 SCR_Math3D.RotateAround(entityMatrix, entityMatrix[3], entityMatrix[2], -Math.DEG2RAD * randomAngles[2], entityMatrix);
1076
1077 if (alignToNormal)
1078 {
1079 traceParam.Start = worldPos + vector.Up;
1080 traceParam.End = worldPos - vector.Up;
1081 world.TraceMove(traceParam, null);
1082
1083 entityMatrix[1] = traceParam.TraceNorm.Normalized(); // newUp
1084 entityMatrix[0] = (entityMatrix[1] * entityMatrix[2]).Normalized(); // newRight
1085 entityMatrix[2] = (entityMatrix[0] * entityMatrix[1]).Normalized(); // newForward
1086 }
1087
1088 vector angles = Math3D.MatrixToAngles(entityMatrix);
1089
1090 if (s_Benchmark)
1091 s_Benchmark.EndMeasure("editSequenceCalculation");
1092
1093 if (s_Benchmark)
1094 s_Benchmark.BeginMeasure("editSequence");
1095
1096 if (angles != vector.Zero || scale != 1)
1097 {
1098 worldEditorAPI.BeginEditSequence(entitySource);
1099
1100 if (angles != vector.Zero)
1101 worldEditorAPI.SetVariableValue(entitySource, null, "angles", string.Format("%1 %2 %3", angles[1], angles[0], angles[2]));
1102
1103 if (scale != 1)
1104 worldEditorAPI.SetVariableValue(entitySource, null, "scale", scale.ToString());
1105
1106 worldEditorAPI.EndEditSequence(entitySource);
1107 }
1108
1109 if (s_Benchmark)
1110 s_Benchmark.EndMeasure("editSequence");
1111 }
1112
1113 ClearObstacles(); // frees RAM
1114
1115 if (m_bPrintArea)
1116 Print("Area of the polygon is: " + m_fArea.ToString(lenDec: 2) + " square meters", LogLevel.NORMAL);
1117
1119 {
1120 Print("Forest generator generated: " + topLevelEntitiesCount + " entities in top level", LogLevel.NORMAL);
1121 Print("Forest generator generated: " + bottomLevelEntitiesCount + " entities in bottom level", LogLevel.NORMAL);
1122 Print("Forest generator generated: " + middleOutlineEntitiesCount + " entities in middle outline", LogLevel.NORMAL);
1123 Print("Forest generator generated: " + smallOutlineEntitiesCount + " entities in small outline", LogLevel.NORMAL);
1124 Print("Forest generator generated: " + clusterEntitiesCount + " entities in clusters", LogLevel.NORMAL);
1125 if (otherEntitiesCount > 0)
1126 Print("Forest generator generated: " + clusterEntitiesCount + " uncategorised entities", LogLevel.WARNING);
1127
1128 Print("Forest generator generated: " + generatedEntitiesCount + " entities in total (" + setVariableValueCalls + " WE API calls)", LogLevel.NORMAL);
1129 }
1130
1131 return generatedEntitiesCount;
1132 }
1133
1134 //------------------------------------------------------------------------------------------------
1138 protected void PopulateGrid(array<float> polygon2D, array<vector> polygon3D)
1139 {
1140 m_Grid.Clear();
1141 m_aGridEntries.Clear();
1142
1143 m_fArea = SCR_Math2D.GetPolygonArea(polygon2D);
1144
1145 SCR_AABB bbox = new SCR_AABB(polygon3D);
1146 m_Grid.Resize(bbox.m_vDimensions[0], bbox.m_vDimensions[2]);
1147
1148 Debug.BeginTimeMeasure();
1149 Rectangulate(bbox, polygon2D);
1150 Debug.EndTimeMeasure("ForestGenerator - Rectangulation done");
1151
1152 vector worldMat[4];
1153 GetWorldTransform(worldMat);
1154 vector bboxMin = bbox.m_vMin;
1155 vector bboxMinWorld = bboxMin.Multiply4(worldMat);
1156 m_Grid.SetPointOffset(bboxMinWorld[0], bboxMinWorld[2]);
1157
1158 Debug.BeginTimeMeasure();
1159 GenerateForestGeneratorTrees(polygon2D, bbox);
1160 Debug.EndTimeMeasure("ForestGenerator - Grid tree generation done");
1161 }
1162
1163 //------------------------------------------------------------------------------------------------
1167 protected void Rectangulate(notnull SCR_AABB bbox, notnull array<float> polygon2D)
1168 {
1169 vector direction = bbox.m_vMax - bbox.m_vMin;
1170 float targetRectangleWidth = RECTANGULATION_SIZE;
1171 float targetRectangleLength = RECTANGULATION_SIZE;
1172 int targetRectangleCountW = Math.Ceil(direction[0] / targetRectangleWidth);
1173 int targetRectangleCountL = Math.Ceil(direction[2] / targetRectangleLength);
1174
1175 vector ownerOrigin = GetOrigin();
1176
1177 SCR_ForestGeneratorRectangle rectangle;
1178 vector p1;
1179 Shape shape;
1180 for (int x; x < targetRectangleCountW; x++)
1181 {
1182 for (int y; y < targetRectangleCountL; y++)
1183 {
1184 bool isInPolygon = false;
1185
1186 rectangle = new SCR_ForestGeneratorRectangle();
1187 rectangle.m_iX = x;
1188 rectangle.m_iY = y;
1189 rectangle.m_fWidth = targetRectangleWidth;
1190 rectangle.m_fLength = targetRectangleLength;
1191 rectangle.m_fArea = rectangle.m_fLength * rectangle.m_fWidth;
1192 p1 = bbox.m_vMin;
1193 p1[0] = p1[0] + (x * targetRectangleWidth);
1194 p1[2] = p1[2] + (y * targetRectangleLength);
1195 rectangle.m_Line4.p2.m_vPos = p1;
1196 rectangle.m_Line1.p1.m_vPos = p1;
1197 if (!isInPolygon)
1198 isInPolygon = Math2D.IsPointInPolygon(polygon2D, p1[0], p1[2]);
1199
1200 rectangle.m_aPoints.Insert(p1);
1201
1202 p1[0] = p1[0] + targetRectangleWidth;
1203 rectangle.m_Line1.p2.m_vPos = p1;
1204 rectangle.m_Line2.p1.m_vPos = p1;
1205 if (!isInPolygon)
1206 isInPolygon = Math2D.IsPointInPolygon(polygon2D, p1[0], p1[2]);
1207
1208 rectangle.m_aPoints.Insert(p1);
1209
1210 p1[2] = p1[2] + targetRectangleLength;
1211 rectangle.m_Line2.p2.m_vPos = p1;
1212 rectangle.m_Line3.p1.m_vPos = p1;
1213 if (!isInPolygon)
1214 isInPolygon = Math2D.IsPointInPolygon(polygon2D, p1[0], p1[2]);
1215
1216 rectangle.m_aPoints.Insert(p1);
1217
1218 p1[0] = p1[0] - targetRectangleWidth;
1219 rectangle.m_Line3.p2.m_vPos = p1;
1220 rectangle.m_Line4.p1.m_vPos = p1;
1221 if (!isInPolygon)
1222 isInPolygon = Math2D.IsPointInPolygon(polygon2D, p1[0], p1[2]);
1223
1224 rectangle.m_aPoints.Insert(p1);
1225
1226 foreach (SCR_ForestGeneratorLine line : m_aLines)
1227 {
1228 if (!NeedsCheck(line, rectangle) || !IsIntersect(line, rectangle))
1229 continue;
1230
1231 bool found = false;
1232 foreach (SCR_ForestGeneratorLine rectLine : rectangle.m_aLines)
1233 {
1234 if (rectLine == line)
1235 {
1236 found = true;
1237 break;
1238 }
1239 }
1240
1241 if (!found)
1242 rectangle.m_aLines.Insert(line);
1243 }
1244
1245 bool areLinesEmpty = rectangle.m_aLines.IsEmpty();
1246 if (areLinesEmpty && !isInPolygon)
1247 continue;
1248
1249 m_aRectangles.Insert(rectangle);
1250
1251 if (areLinesEmpty)
1252 m_aNonOutlineRectangles.Insert(rectangle);
1253 else
1254 m_aOutlineRectangles.Insert(rectangle);
1255
1257 {
1258 int red;
1259 int green;
1260 int blue;
1261 if (areLinesEmpty)
1262 {
1263 green = Math.Floor(m_RandomGenerator.RandFloatXY(0, 255));
1264 blue = Math.Floor(m_RandomGenerator.RandFloatXY(0, 255));
1265 }
1266 else
1267 {
1268 red = 255;
1269 }
1270
1271 s_DebugShapeManager.AddBBox(ownerOrigin + rectangle.m_Line1.p1.m_vPos, ownerOrigin + rectangle.m_Line3.p1.m_vPos, ARGB(63, red, green, blue));
1272 }
1273 }
1274 }
1275 }
1276
1277 //------------------------------------------------------------------------------------------------
1282 protected bool NeedsCheck(SCR_ForestGeneratorLine line, SCR_ForestGeneratorRectangle rectangle)
1283 {
1284 vector linePoint1 = line.p1.m_vPos;
1285 vector linePoint2 = line.p2.m_vPos;
1286 vector mins;
1287 vector maxs;
1288 rectangle.GetBounds(mins, maxs);
1289
1290 float linePoint1x = linePoint1[0];
1291 float linePoint1z = linePoint1[2];
1292 float linePoint2x = linePoint2[0];
1293 float linePoint2z = linePoint2[2];
1294 float minsx = mins[0];
1295 float minsz = mins[2];
1296 float maxsx = maxs[0];
1297 float maxsz = maxs[2];
1298
1299 if (
1300 (linePoint1x < minsx && linePoint2x < minsx) ||
1301 (linePoint1x > maxsx && linePoint2x > maxsx) ||
1302 (linePoint1z < minsz && linePoint2z < minsz) ||
1303 (linePoint1z > maxsz && linePoint2z > maxsz))
1304 return false;
1305
1306 return true;
1307 }
1308
1309 //------------------------------------------------------------------------------------------------
1313 protected void GenerateForestGeneratorTrees(array<float> polygon2D, SCR_AABB bbox)
1314 {
1315 if (bbox.m_vDimensions[0] * bbox.m_vDimensions[2] <= 0.01) // too small area
1316 return;
1317
1318 // clusters
1319 foreach (ForestGeneratorCluster cluster : m_aClusters)
1320 {
1321 if (!cluster.m_bGenerate || cluster.m_fRadius <= 0)
1322 continue;
1323
1324 if (cluster.m_Type == SCR_EForestGeneratorClusterType.CIRCLE)
1325 GenerateCircleCluster(ForestGeneratorCircleCluster.Cast(cluster), polygon2D, bbox);
1326 else
1327 if (cluster.m_Type == SCR_EForestGeneratorClusterType.STRIP)
1328 GenerateStripCluster(ForestGeneratorStripCluster.Cast(cluster), polygon2D, bbox);
1329 }
1330
1331 // then trees
1332 foreach (ForestGeneratorLevel level : m_aLevels)
1333 {
1334 if (!level.m_bGenerate)
1335 continue;
1336
1337 switch (level.m_eType)
1338 {
1339 case SCR_EForestGeneratorLevelType.TOP:
1340 GenerateTopTrees(polygon2D, bbox, ForestGeneratorTopLevel.Cast(level));
1341 break;
1342
1343 case SCR_EForestGeneratorLevelType.OUTLINE:
1344 GenerateOutlineTrees(polygon2D, bbox, ForestGeneratorOutline.Cast(level));
1345 break;
1346
1347 case SCR_EForestGeneratorLevelType.BOTTOM:
1348 GenerateBottomTrees(polygon2D, bbox, ForestGeneratorBottomLevel.Cast(level));
1349 break;
1350 }
1351 }
1352 }
1353
1354 //------------------------------------------------------------------------------------------------
1360 protected int FindRectanglesInCircle(vector center, float radius, out array<SCR_ForestGeneratorRectangle> rectangles)
1361 {
1362 int count = 0;
1363 float deltaX, deltaY;
1364 float radiusSq = radius * radius;
1365
1366 foreach (SCR_ForestGeneratorRectangle rectangle : m_aRectangles)
1367 {
1368 foreach (vector point : rectangle.m_aPoints)
1369 {
1370 deltaX = center[0] - Math.Max(rectangle.m_Line1.p1.m_vPos[0], Math.Min(center[0], rectangle.m_Line1.p1.m_vPos[0] + rectangle.m_fWidth));
1371 deltaY = center[2] - Math.Max(rectangle.m_Line1.p1.m_vPos[2], Math.Min(center[2], rectangle.m_Line1.p1.m_vPos[2] + rectangle.m_fLength));
1372
1373 if (deltaX * deltaX + deltaY * deltaY < radiusSq)
1374 {
1375 rectangles.Insert(rectangle);
1376 count++;
1377 }
1378 }
1379 }
1380
1381 return count;
1382 }
1383
1384 //------------------------------------------------------------------------------------------------
1392 protected bool GetClusterPoint(notnull array<float> polygon2D, notnull SCR_AABB bbox, out vector clusterCenter, float additionalDistance, bool allowInForest, bool allowInOutline)
1393 {
1394 if (!allowInForest && !allowInOutline)
1395 return false;
1396
1397 if (allowInForest && allowInOutline)
1398 {
1399 clusterCenter = m_RandomGenerator.GenerateRandomPoint(polygon2D, bbox.m_vMin, bbox.m_vMax);
1400 return true;
1401 }
1402
1403 array<bool> inOutlineList;
1404 if (allowInOutline)
1405 inOutlineList = {};
1406
1407 array<SCR_ForestGeneratorRectangle> rectangles = {};
1408 for (int i; i < MAX_CLUSTER_ATTEMPT; ++i)
1409 {
1410 bool stopThisAttempt;
1411
1412 vector possibleLocation = m_RandomGenerator.GenerateRandomPoint(polygon2D, bbox.m_vMin, bbox.m_vMax);
1413 rectangles.Clear();
1414
1415 int rectanglesCount = FindRectanglesInCircle(possibleLocation, additionalDistance + m_fMaxOutlinesWidth, rectangles);
1416 if (allowInOutline)
1417 {
1418 inOutlineList.Clear();
1419 inOutlineList.Reserve(rectanglesCount);
1420 }
1421
1422 foreach (SCR_ForestGeneratorRectangle rectangle : rectangles)
1423 {
1424 bool isInOutline = IsInOutline(rectangle, possibleLocation, additionalDistance);
1425 if (isInOutline && allowInForest) // "forest only" has absolutely NO presence in outline
1426 {
1427 stopThisAttempt = true;
1428 break;
1429 }
1430
1431 if (allowInOutline)
1432 inOutlineList.Insert(isInOutline);
1433 }
1434
1435 if (stopThisAttempt)
1436 continue;
1437
1438 if (allowInForest // attempt was not skipped, all checks were outside outline
1439 || (allowInOutline && inOutlineList.Contains(true))) // only one point in outline is acceptable
1440 {
1441 clusterCenter = possibleLocation; // set the out value once
1442 return true;
1443 }
1444 }
1445
1446 return false;
1447 }
1448
1449 //------------------------------------------------------------------------------------------------
1454 protected void GenerateCircleCluster(notnull ForestGeneratorCircleCluster cluster, notnull array<float> polygon2D, notnull SCR_AABB bbox)
1455 {
1456 vector worldMat[4];
1457 GetWorldTransform(worldMat);
1458
1459 float CDENSHA = SafeRandomFloatInclusive(cluster.m_fMinCDENSHA, cluster.m_fMaxCDENSHA);
1460
1461 vector clusterCenter;
1462 vector pointLocal;
1463 SmallForestGeneratorClusterObject newClusterObject;
1464 vector point;
1465 WideForestGeneratorClusterObject wideObject;
1466 for (int c, clusterCount = Math.Ceil(m_fArea * HECTARE_CONVERSION_FACTOR * CDENSHA); c < clusterCount; c++)
1467 {
1468 bool allowInForest = cluster.m_iPlacementArea != 2;
1469 bool allowInOutline = cluster.m_iPlacementArea != 1;
1470 if (!GetClusterPoint(polygon2D, bbox, clusterCenter, cluster.m_fRadius, allowInForest, allowInOutline))
1471 continue;
1472
1473 bool isPolygonCheckUseless = !SCR_Math3D.IsPointWithinSplineDistanceXZ(m_aShapePoints, clusterCenter, cluster.m_fRadius);
1474
1475 foreach (SmallForestGeneratorClusterObject clusterObject : cluster.m_aObjects)
1476 {
1477 for (int o, objectCount = GetClusterObjectCount(clusterObject); o < objectCount; o++)
1478 {
1479 pointLocal = GeneratePointInCircle(clusterObject.m_fMinRadius, clusterObject.m_fMaxRadius, clusterCenter);
1480 if (isPolygonCheckUseless || Math2D.IsPointInPolygon(polygon2D, pointLocal[0], pointLocal[2]))
1481 {
1482 if (s_Benchmark)
1483 s_Benchmark.BeginMeasure("clone");
1484
1485 newClusterObject = SmallForestGeneratorClusterObject.Cast(clusterObject.Clone());
1486
1487 if (s_Benchmark)
1488 s_Benchmark.EndMeasure("clone");
1489
1490 if (!newClusterObject)
1491 continue;
1492
1493 m_aGridEntries.Insert(newClusterObject);
1494 point = pointLocal.Multiply4(worldMat);
1495
1496 SetObjectScale(newClusterObject);
1497
1498 wideObject = WideForestGeneratorClusterObject.Cast(newClusterObject);
1499 if (wideObject)
1500 {
1501 wideObject.m_fYaw = m_RandomGenerator.RandFloat01() * 360;
1502 wideObject.Rotate();
1503 }
1504
1505 if (m_Grid.IsColliding(point, newClusterObject))
1506 continue;
1507
1508 newClusterObject.m_eType = SCR_ETreeType.CLUSTER;
1509 m_Grid.AddEntry(newClusterObject, point);
1510 }
1511 }
1512 }
1513 }
1514 }
1515
1516 //------------------------------------------------------------------------------------------------
1521 protected void GenerateStripCluster(notnull ForestGeneratorStripCluster cluster, notnull array<float> polygon2D, notnull SCR_AABB bbox)
1522 {
1523 vector worldMat[4];
1524 GetWorldTransform(worldMat);
1525
1526 vector direction = vector.FromYaw(m_RandomGenerator.RandFloatXY(0, 360));
1527 vector perpendicular;
1528 perpendicular[0] = direction[2];
1529 perpendicular[2] = -direction[0];
1530
1531 float CDENSHA = SafeRandomFloatInclusive(cluster.m_fMinCDENSHA, cluster.m_fMaxCDENSHA);
1532
1533 vector clusterCenter;
1534 vector pointLocal;
1535 vector offset;
1536 vector point;
1537 SmallForestGeneratorClusterObject newClusterObject;
1538 WideForestGeneratorClusterObject wideObject;
1539 for (int c, clusterCount = Math.Ceil(m_fArea * HECTARE_CONVERSION_FACTOR * CDENSHA); c < clusterCount; c++)
1540 {
1541 bool allowInForest = cluster.m_iPlacementArea != 2;
1542 bool allowInOutline = cluster.m_iPlacementArea != 1;
1543 if (!GetClusterPoint(polygon2D, bbox, clusterCenter, cluster.m_fRadius, allowInForest, allowInOutline))
1544 continue;
1545
1546 bool isPolygonCheckUseless = !SCR_Math3D.IsPointWithinSplineDistanceXZ(m_aShapePoints, clusterCenter, cluster.m_fRadius);
1547
1548 foreach (SmallForestGeneratorClusterObject clusterObject : cluster.m_aObjects)
1549 {
1550 for (int o, objectCount = GetClusterObjectCount(clusterObject); o < objectCount; o++)
1551 {
1552 float distance = SafeRandomFloatInclusive(clusterObject.m_fMinRadius, clusterObject.m_fMaxRadius);
1553 int rnd = m_RandomGenerator.RandIntInclusive(0, 1);
1554
1555 if (rnd == 0)
1556 distance = -distance;
1557
1558 float y01 = distance / cluster.m_fRadius * cluster.m_fFrequency;
1559 float ySin = Math.Sin(y01 * 360 * Math.DEG2RAD);
1560 float y = ySin * cluster.m_fAmplitude;
1561
1562 offset = vector.Zero;
1563 offset[0] = SafeRandomFloatInclusive(0, cluster.m_fMaxXOffset);
1564 offset[2] = SafeRandomFloatInclusive(0, cluster.m_fMaxYOffset);
1565 pointLocal = (direction * distance) + (y * perpendicular) + clusterCenter + offset;
1566
1567 if (isPolygonCheckUseless || Math2D.IsPointInPolygon(polygon2D, pointLocal[0], pointLocal[2]))
1568 {
1569 if (s_Benchmark)
1570 s_Benchmark.BeginMeasure("clone");
1571
1572 newClusterObject = SmallForestGeneratorClusterObject.Cast(clusterObject.Clone());
1573
1574 if (s_Benchmark)
1575 s_Benchmark.EndMeasure("clone");
1576
1577 if (!newClusterObject)
1578 continue;
1579
1580 m_aGridEntries.Insert(newClusterObject);
1581 point = pointLocal.Multiply4(worldMat);
1582
1583 SetObjectScale(newClusterObject);
1584
1585 wideObject = WideForestGeneratorClusterObject.Cast(newClusterObject);
1586 if (wideObject)
1587 {
1588 wideObject.m_fYaw = m_RandomGenerator.RandFloatXY(0, 360);
1589 wideObject.Rotate();
1590 }
1591
1592 if (m_Grid.IsColliding(point, newClusterObject))
1593 continue;
1594
1595 newClusterObject.m_eType = SCR_ETreeType.CLUSTER;
1596 m_Grid.AddEntry(newClusterObject, point);
1597 }
1598 }
1599 }
1600 }
1601 }
1602
1603 //------------------------------------------------------------------------------------------------
1606 protected int GetClusterObjectCount(notnull SmallForestGeneratorClusterObject clusterObject)
1607 {
1608 int min = clusterObject.m_iMinCount;
1609 int max = clusterObject.m_iMaxCount;
1610 if (min == max)
1611 return min;
1612
1613 if (min > max)
1614 {
1615 int tmp = min;
1616 min = max;
1617 max = tmp;
1618 Print("A forest generator's cluster has Min Count > Max Count at " + GetOrigin(), LogLevel.WARNING);
1619 }
1620
1621 if (clusterObject.m_iRandomMidPercent < 0)
1622 return m_RandomGenerator.RandIntInclusive(min, max);
1623
1624 // Gaussian curve, here we come
1625
1626 float mid = min + (max - min) * clusterObject.m_iRandomMidPercent * 0.01;
1627 return GetGaussianDistributionRandomIntInclusive(min, mid, max);
1628 }
1629
1630 //------------------------------------------------------------------------------------------------
1636 protected int GetGaussianDistributionRandomIntInclusive(int min, float mid, int max)
1637 {
1638 float result = m_RandomGenerator.RandGaussFloat((max - min) / 6.0, mid); // ~99.73% cases covered
1639
1640 if (result < min)
1641 return min;
1642
1643 if (result > max)
1644 return max;
1645
1646 return Math.Round(result);
1647 }
1648
1649 //------------------------------------------------------------------------------------------------
1654 protected float SafeRandomFloatInclusive(float min, float max)
1655 {
1656 if (min == max)
1657 return max;
1658
1659 if (min < max)
1660 return m_RandomGenerator.RandFloatXY(min, max);
1661
1662 Print("A forest generator object has some min value > max value at " + GetOrigin(), LogLevel.WARNING);
1663 return m_RandomGenerator.RandFloatXY(max, min);
1664 }
1665
1666 //------------------------------------------------------------------------------------------------
1672 protected vector GeneratePointInCircle(float innerRadius, float outerRadius, vector circleCenter)
1673 {
1674 vector direction = vector.FromYaw(m_RandomGenerator.RandFloatXY(0, 360));
1675 float rand = SafeRandomFloatInclusive(innerRadius, outerRadius);
1676 return circleCenter + rand * direction;
1677 }
1678
1679 //------------------------------------------------------------------------------------------------
1685 protected vector GeneratePointInCircle(float innerRadius, float outerRadius, SCR_ForestGeneratorPoint point)
1686 {
1687 vector direction = vector.FromYaw(m_RandomGenerator.RandFloatXY(point.m_fMinAngle, point.m_fMaxAngle));
1688 float rand = SafeRandomFloatInclusive(innerRadius, outerRadius);
1689 return point.m_vPos + rand * direction;
1690 }
1691
1692 //------------------------------------------------------------------------------------------------
1696 protected vector GenerateRandomPointInRectangle(notnull SCR_ForestGeneratorRectangle rectangle)
1697 {
1698 return {
1699 m_RandomGenerator.RandFloat01() * rectangle.m_fWidth + rectangle.m_Line1.p1.m_vPos[0],
1700 0,
1701 m_RandomGenerator.RandFloat01() * rectangle.m_fLength + rectangle.m_Line1.p1.m_vPos[2]
1702 };
1703 }
1704
1705 //------------------------------------------------------------------------------------------------
1708 protected bool GetIsAnyTreeValid(notnull array<ref TreeGroupClass> treeGroups)
1709 {
1710 foreach (TreeGroupClass treeGroup : treeGroups)
1711 {
1712 if (treeGroup.m_fWeight <= 0)
1713 continue;
1714
1715 foreach (ForestGeneratorTree tree : treeGroup.m_aTrees)
1716 {
1717 if (tree.m_fWeight > 0 && !tree.m_Prefab.IsEmpty())
1718 return true;
1719 }
1720 }
1721
1722 return false;
1723 }
1724
1725 //------------------------------------------------------------------------------------------------
1730 protected void GenerateOutlineTrees(array<float> polygon, SCR_AABB bbox, ForestGeneratorOutline outline)
1731 {
1732 if (!outline || !outline.m_aTreeGroups || outline.m_aTreeGroups.IsEmpty())
1733 return;
1734
1735 if (!GetIsAnyTreeValid(outline.m_aTreeGroups))
1736 return;
1737
1738 SCR_ETreeType treeType;
1739 array<ref SCR_ForestGeneratorPoint> currentOutlinePoints;
1740 array<ref SCR_ForestGeneratorLine> currentOutlineLines;
1741
1742 switch (outline.m_eOutlineType)
1743 {
1744 case SCR_EForestGeneratorOutlineType.SMALL:
1745 {
1746 treeType = SCR_ETreeType.SMALL_OUTLINE;
1747 currentOutlinePoints = m_aSmallOutlinePoints;
1748 currentOutlineLines = m_aSmallOutlineLines;
1749 break;
1750 }
1751
1752 case SCR_EForestGeneratorOutlineType.MIDDLE:
1753 {
1754 treeType = SCR_ETreeType.MIDDLE_OUTLINE;
1755 currentOutlinePoints = m_aMiddleOutlinePoints;
1756 currentOutlineLines = m_aMiddleOutlineLines;
1757 break;
1758 }
1759 }
1760
1761 if (!currentOutlinePoints || !currentOutlineLines)
1762 return;
1763
1764 array<float> groupProbas = {}; // test
1765 array<float> groupCounts = {};
1766 groupCounts.Resize(outline.m_aTreeGroups.Count());
1767
1768 vector worldMat[4];
1769 GetWorldTransform(worldMat);
1770
1771 bool useScaleCurve = outline.m_fOutlineScaleCurveDistance > 0 && !outline.m_aOutlineScaleCurve.IsEmpty();
1772 float scaleCurveDistanceDivisor;
1773 array<float> curveKnots;
1774
1775 if (useScaleCurve)
1776 {
1777 scaleCurveDistanceDivisor = SCALE_CURVE_RANGE / outline.m_fOutlineScaleCurveDistance;
1778 curveKnots = {};
1779 foreach (vector scalePoint : outline.m_aOutlineScaleCurve)
1780 {
1781 curveKnots.Insert(scalePoint[0]);
1782 }
1783 }
1784
1785 int iterCount = 0;
1786
1787 vector direction, perpendicular, pointLocal, point;
1788 float probaSumToNormalize, groupProba, probaSum;
1789 int groupIdx;
1790 ForestGeneratorTree tree;
1791 foreach (SCR_ForestGeneratorLine line : currentOutlineLines)
1792 {
1793 direction = line.p2.m_vPos - line.p1.m_vPos;
1794 perpendicular = { direction[2], 0, -direction[0] };
1795 perpendicular.Normalize();
1796 iterCount = outline.m_fDensity * (CalculateAreaForOutline(line, outline) * HECTARE_CONVERSION_FACTOR);
1797 for (int treeIdx; treeIdx < iterCount; ++treeIdx)
1798 {
1799 // generate a point -along- the line (at a perpendicular distance)
1800 pointLocal = line.p1.m_vPos + (direction * m_RandomGenerator.RandFloat01()) + (perpendicular * SafeRandomFloatInclusive(outline.m_fMinDistance, outline.m_fMaxDistance));
1801 if (pointLocal == vector.Zero || !Math2D.IsPointInPolygon(polygon, pointLocal[0], pointLocal[2]))
1802 continue;
1803
1804 point = pointLocal.Multiply4(worldMat);
1805
1806 // see which trees are around - count the types
1807 int groupProbaCount = groupProbas.Copy(outline.m_aGroupProbas);
1808 for (int i, count = groupCounts.Count(); i < count; i++)
1809 {
1810 groupCounts[i] = 1;
1811 }
1812
1813 if (s_Benchmark)
1814 s_Benchmark.BeginMeasure("gridCountEntriesAround");
1815
1816 m_Grid.CountEntriesAround(point, outline.m_fClusterRadius, groupCounts);
1817
1818 if (s_Benchmark)
1819 s_Benchmark.EndMeasure("gridCountEntriesAround");
1820
1821 // skew the probability of given groups based on counts
1822 probaSumToNormalize = 0;
1823 for (int i; i < groupProbaCount; i++)
1824 {
1825 groupProbas[i] = groupProbas[i] * Math.Pow(groupCounts[i], outline.m_fClusterStrength);
1826 probaSumToNormalize += groupProbas[i];
1827 }
1828
1829 if (probaSumToNormalize > 0)
1830 {
1831 for (int i; i < groupProbaCount; i++)
1832 {
1833 groupProbas[i] = groupProbas[i] / probaSumToNormalize;
1834 }
1835 }
1836
1837 groupProba = m_RandomGenerator.RandFloat01();
1838 groupIdx = groupProbas.Count() - 1; // last because there is less than in the loop
1839 probaSum = 0;
1840 for (int i, count = groupProbas.Count(); i < count; ++i)
1841 {
1842 probaSum += groupProbas[i];
1843 if (groupProba < probaSum) // less than to avoid accepting 0 probability tree
1844 {
1845 groupIdx = i;
1846 break;
1847 }
1848 }
1849
1850 tree = SelectTreeToSpawn(point, outline.m_aTreeGroups[groupIdx].m_aTrees);
1851
1852 if (!IsEntryValid(tree, pointLocal))
1853 continue;
1854
1855 tree.m_eType = treeType;
1856 if (useScaleCurve)
1857 {
1858 float distanceFromShape = SCR_Math3D.GetDistanceFromSplineXZ(m_aShapePoints, pointLocal);
1859 if (distanceFromShape <= outline.m_fOutlineScaleCurveDistance)
1860 {
1861 // (m_fOutlineScaleCurveDistance - distanceFromShape) because right-to-left curve reading
1862 float scaleFactor = LegacyCurve.Curve(ECurveType.CatmullRom, (outline.m_fOutlineScaleCurveDistance - distanceFromShape) * scaleCurveDistanceDivisor, outline.m_aOutlineScaleCurve, curveKnots)[1];
1863 if (scaleFactor < ForestGeneratorLevel.SCALE_CURVE_MIN_VALUE)
1864 scaleFactor = ForestGeneratorLevel.SCALE_CURVE_MIN_VALUE;
1865 else
1866 if (scaleFactor > ForestGeneratorLevel.SCALE_CURVE_MAX_VALUE)
1867 scaleFactor = ForestGeneratorLevel.SCALE_CURVE_MAX_VALUE;
1868
1869 tree.m_fScale *= scaleFactor;
1870 }
1871 }
1872
1873 m_Grid.AddEntry(tree, point);
1874 }
1875 }
1876
1877 foreach (SCR_ForestGeneratorPoint currentPoint : currentOutlinePoints)
1878 {
1879 iterCount = outline.m_fDensity * CalculateAreaForOutline(currentPoint, outline) * HECTARE_CONVERSION_FACTOR;
1880 for (int treeIdx; treeIdx < iterCount; treeIdx++)
1881 {
1882 pointLocal = GeneratePointInCircle(outline.m_fMinDistance, outline.m_fMaxDistance, currentPoint);
1883
1884 bool lineDistance1 = IsPointInProperDistanceFromLine(pointLocal, currentPoint.m_Line1, outline.m_fMinDistance, outline.m_fMaxDistance);
1885 if (!lineDistance1)
1886 continue;
1887
1888 bool lineDistance2 = IsPointInProperDistanceFromLine(pointLocal, currentPoint.m_Line2, outline.m_fMinDistance, outline.m_fMaxDistance);
1889 if (!lineDistance2)
1890 continue;
1891
1892 if (!Math2D.IsPointInPolygon(polygon, pointLocal[0], pointLocal[2]))
1893 continue;
1894
1895 point = pointLocal.Multiply4(worldMat);
1896
1897 // see which trees are around - count the types
1898 groupProbas.Copy(outline.m_aGroupProbas);
1899 for (int i, count = groupCounts.Count(); i < count; i++)
1900 {
1901 groupCounts[i] = 1;
1902 }
1903
1904 if (s_Benchmark)
1905 s_Benchmark.BeginMeasure("gridCountEntriesAround");
1906
1907 m_Grid.CountEntriesAround(point, outline.m_fClusterRadius, groupCounts);
1908
1909 if (s_Benchmark)
1910 s_Benchmark.EndMeasure("gridCountEntriesAround");
1911
1912 // skew the probability of given groups based on counts
1913 probaSumToNormalize = 0;
1914 for (int i, count = groupProbas.Count(); i < count; i++)
1915 {
1916 groupProbas[i] = groupProbas[i] * Math.Pow(groupCounts[i], outline.m_fClusterStrength);
1917 probaSumToNormalize += groupProbas[i];
1918 }
1919
1920 if (probaSumToNormalize != 0)
1921 {
1922 for (int i, count = groupProbas.Count(); i < count; i++)
1923 {
1924 groupProbas[i] = groupProbas[i] / probaSumToNormalize;
1925 }
1926 }
1927
1928 groupProba = m_RandomGenerator.RandFloat01();
1929 groupIdx = groupProbas.Count() - 1; // last because there is less than in the loop
1930 probaSum = 0;
1931 for (int i, count = groupProbas.Count(); i < count; i++)
1932 {
1933 probaSum += groupProbas[i];
1934 if (groupProba < probaSum) // less than to avoid accepting 0 probability tree
1935 {
1936 groupIdx = i;
1937 break;
1938 }
1939 }
1940
1941 tree = SelectTreeToSpawn(point, outline.m_aTreeGroups[groupIdx].m_aTrees);
1942
1943 if (!IsEntryValid(tree, pointLocal))
1944 continue;
1945
1946 tree.m_eType = treeType;
1947 m_Grid.AddEntry(tree, point);
1948 }
1949 }
1950 }
1951
1952 //------------------------------------------------------------------------------------------------
1958 protected bool IsPointInProperDistanceFromLine(vector point, SCR_ForestGeneratorLine line, float minDistance, float maxDistance)
1959 {
1960 float distance = Math3D.PointLineSegmentDistance({ point[0], 0, point[2] }, { line.p1.m_vPos[0], 0, line.p1.m_vPos[2] }, { line.p2.m_vPos[0], 0, line.p2.m_vPos[2] });
1961 return distance >= minDistance && distance <= maxDistance;
1962 }
1963
1964 //------------------------------------------------------------------------------------------------
1968 protected bool IsEntryValid(ForestGeneratorTree tree, vector pointLocal)
1969 {
1970 if (!tree)
1971 return false;
1972
1973 FallenTree fallenTree = FallenTree.Cast(tree);
1974 if (fallenTree)
1975 {
1976 float distance;
1977 float minDistance;
1978 foreach (SCR_ForestGeneratorLine line : m_aLines)
1979 {
1980 distance = Math3D.PointLineSegmentDistance(pointLocal, line.p1.m_vPos, line.p2.m_vPos);
1981 minDistance = fallenTree.GetMinDistanceFromLine();
1982 if (distance < minDistance)
1983 return false;
1984 }
1985 }
1986
1987 return true;
1988 }
1989
1990 //------------------------------------------------------------------------------------------------
1995 protected void GenerateBottomTrees(array<float> polygon, SCR_AABB bbox, ForestGeneratorBottomLevel bottomLevel)
1996 {
1997 if (!bottomLevel || bottomLevel.m_aTreeGroups.IsEmpty())
1998 return;
1999
2000 if (!GetIsAnyTreeValid(bottomLevel.m_aTreeGroups))
2001 return;
2002
2003 array<float> groupProbas = {};
2004 float totalWeight = 0;
2005 int groupCount = bottomLevel.m_aTreeGroups.Count();
2006 groupProbas.Resize(groupCount);
2007 foreach (TreeGroupClass treeGroup : bottomLevel.m_aTreeGroups)
2008 {
2009 totalWeight += treeGroup.m_fWeight;
2010 }
2011
2012 if (totalWeight != 0)
2013 {
2014 for (int i; i < groupCount; i++)
2015 {
2016 groupProbas[i] = (bottomLevel.m_aTreeGroups[i].m_fWeight / totalWeight);
2017 }
2018 }
2019
2020 vector worldMat[4];
2021 GetWorldTransform(worldMat);
2022
2023 bool useScaleCurve = bottomLevel.m_fOutlineScaleCurveDistance > 0 && !bottomLevel.m_aOutlineScaleCurve.IsEmpty();
2024 float scaleCurveDistanceDivisor;
2025 array<float> curveKnots;
2026
2027 if (useScaleCurve)
2028 {
2029 scaleCurveDistanceDivisor = SCALE_CURVE_RANGE / bottomLevel.m_fOutlineScaleCurveDistance;
2030 curveKnots = {};
2031 foreach (vector scalePoint : bottomLevel.m_aOutlineScaleCurve)
2032 {
2033 curveKnots.Insert(scalePoint[0]);
2034 }
2035 }
2036
2037 int expectedIterCount = m_fArea * HECTARE_CONVERSION_FACTOR * bottomLevel.m_fDensity;
2038 vector pointLocal;
2039 vector point;
2040 ForestGeneratorTree tree;
2041 foreach (SCR_ForestGeneratorRectangle rectangle : m_aRectangles)
2042 {
2043 int iterCount = bottomLevel.m_fDensity * rectangle.m_fArea * HECTARE_CONVERSION_FACTOR;
2044 for (int treeIdx; treeIdx < iterCount; ++treeIdx)
2045 {
2046 expectedIterCount--;
2047 // generate random point inside the shape (polygon at first)
2048 pointLocal = GenerateRandomPointInRectangle(rectangle);
2049
2050 if (!rectangle.m_aLines.IsEmpty())
2051 {
2052 if (!Math2D.IsPointInPolygon(polygon, pointLocal[0], pointLocal[2]))
2053 continue;
2054
2055 if (!IsBeforeOutlineBorder(rectangle, pointLocal, bottomLevel.m_fOutlineOverlap))
2056 continue;
2057 }
2058
2059 float perlinValue = Math.PerlinNoise01(pointLocal[0], 0, pointLocal[2]); // TODO Can we change the size of perlin noise?
2060 if (perlinValue > 1 || perlinValue < 0)
2061 {
2062 Print("Perlin value is out of range <0,1>, something went wrong!", LogLevel.ERROR);
2063 continue;
2064 }
2065
2066 float rangeBeginning = 0;
2067 int groupIdx = 0;
2068 foreach (int i, float groupProba : groupProbas)
2069 {
2070 if (perlinValue > rangeBeginning && perlinValue < (groupProba + rangeBeginning))
2071 {
2072 groupIdx = i;
2073 break;
2074 }
2075 rangeBeginning += groupProba;
2076 }
2077
2078 point = pointLocal.Multiply4(worldMat);
2079 tree = SelectTreeToSpawn(point, bottomLevel.m_aTreeGroups[groupIdx].m_aTrees);
2080
2081 if (!IsEntryValid(tree, pointLocal))
2082 continue;
2083
2084 tree.m_eType = SCR_ETreeType.BOTTOM;
2085 tree.m_iDebugGroupIndex = groupIdx;
2086 if (useScaleCurve)
2087 {
2088 float distanceFromShape = SCR_Math3D.GetDistanceFromSplineXZ(m_aShapePoints, pointLocal);
2089 if (distanceFromShape <= bottomLevel.m_fOutlineScaleCurveDistance)
2090 {
2091 // (m_fOutlineScaleCurveDistance - distanceFromShape) because right-to-left curve reading
2092 float scaleFactor = LegacyCurve.Curve(ECurveType.CatmullRom, (bottomLevel.m_fOutlineScaleCurveDistance - distanceFromShape) * scaleCurveDistanceDivisor, bottomLevel.m_aOutlineScaleCurve, curveKnots)[1];
2093 if (scaleFactor < ForestGeneratorLevel.SCALE_CURVE_MIN_VALUE)
2094 scaleFactor = ForestGeneratorLevel.SCALE_CURVE_MIN_VALUE;
2095 else
2096 if (scaleFactor > ForestGeneratorLevel.SCALE_CURVE_MAX_VALUE)
2097 scaleFactor = ForestGeneratorLevel.SCALE_CURVE_MAX_VALUE;
2098
2099 tree.m_fScale *= scaleFactor;
2100 }
2101 }
2102
2103 m_Grid.AddEntry(tree, point);
2104 }
2105 }
2106
2107 int index;
2108 while (expectedIterCount > 0)
2109 {
2110 index = m_RandomGenerator.RandFloatXY(0, m_aRectangles.Count() - 1);
2111 GenerateTreeInsideRectangle(m_aRectangles[index], bottomLevel, polygon, worldMat);
2112 expectedIterCount--;
2113 }
2114 }
2115
2116 //------------------------------------------------------------------------------------------------
2121 protected void GenerateTopTrees(array<float> polygon, SCR_AABB bbox, ForestGeneratorTopLevel topLevel)
2122 {
2123 if (!topLevel || topLevel.m_aTreeGroups.IsEmpty())
2124 return;
2125
2126 if (!GetIsAnyTreeValid(topLevel.m_aTreeGroups))
2127 return;
2128
2129 array<float> groupProbas = {};
2130 array<float> groupCounts = {};
2131 groupCounts.Resize(topLevel.m_aTreeGroups.Count() + TREE_GROUPS_OFFSET_HACK);
2132
2133 vector worldMat[4];
2134 GetWorldTransform(worldMat);
2135
2136 bool useScaleCurve = topLevel.m_fOutlineScaleCurveDistance > 0 && !topLevel.m_aOutlineScaleCurve.IsEmpty();
2137 float scaleCurveDistanceDivisor;
2138 array<float> curveKnots;
2139
2140 if (useScaleCurve)
2141 {
2142 scaleCurveDistanceDivisor = SCALE_CURVE_RANGE / topLevel.m_fOutlineScaleCurveDistance;
2143 curveKnots = {};
2144 foreach (vector scalePoint : topLevel.m_aOutlineScaleCurve)
2145 {
2146 curveKnots.Insert(scalePoint[0]);
2147 }
2148 }
2149
2150 vector pointLocal;
2151 int expectedIterCount = m_fArea * HECTARE_CONVERSION_FACTOR * topLevel.m_fDensity;
2152 vector point;
2153 ForestGeneratorTree tree;
2154
2155 foreach (SCR_ForestGeneratorRectangle rectangle : m_aRectangles)
2156 {
2157 float area = rectangle.m_fArea * HECTARE_CONVERSION_FACTOR;
2158 int iterCount = topLevel.m_fDensity * area;
2159
2160 for (int treeIdx; treeIdx < iterCount; ++treeIdx)
2161 {
2162 // generate random point inside the shape (polygon at first)
2163 pointLocal = GenerateRandomPointInRectangle(rectangle);
2164 expectedIterCount--;
2165
2166 if (!rectangle.m_aLines.IsEmpty())
2167 {
2168 if (!Math2D.IsPointInPolygon(polygon, pointLocal[0], pointLocal[2]))
2169 continue;
2170
2171 if (!IsBeforeOutlineBorder(rectangle, pointLocal, topLevel.m_fOutlineOverlap))
2172 continue;
2173 }
2174
2175 point = pointLocal.Multiply4(worldMat);
2176
2177 // see which trees are around - count the types
2178 groupProbas.Copy(topLevel.m_aGroupProbas);
2179 for (int i, count = groupCounts.Count(); i < count; i++)
2180 {
2181 groupCounts[i] = 1;
2182 }
2183
2184 if (s_Benchmark)
2185 s_Benchmark.BeginMeasure("gridCountEntriesAround");
2186
2187 // HERE is the "Invalid tree group index: 1, there are only 1 groups" error source
2188 m_Grid.CountEntriesAround(point, topLevel.m_fClusterRadius, groupCounts);
2189
2190 if (s_Benchmark)
2191 s_Benchmark.EndMeasure("gridCountEntriesAround");
2192
2193 // skew the probability of given groups based on counts
2194 float probaSumToNormalize = 0;
2195 for (int i, count = groupProbas.Count(); i < count; i++)
2196 {
2197 groupProbas[i] = groupProbas[i] * Math.Pow(groupCounts[i], topLevel.m_fClusterStrength);
2198 probaSumToNormalize += groupProbas[i];
2199 }
2200
2201 if (probaSumToNormalize != 0)
2202 {
2203 for (int i, count = groupProbas.Count(); i < count; i++)
2204 {
2205 groupProbas[i] = groupProbas[i] / probaSumToNormalize;
2206 }
2207 }
2208
2209 float groupProba = m_RandomGenerator.RandFloat01();
2210 int groupIdx = groupProbas.Count() - 1; // last because there is less than in the loop
2211 float probaSum = 0;
2212 for (int i, count = groupProbas.Count(); i < count; ++i)
2213 {
2214 probaSum += groupProbas[i];
2215 if (groupProba < probaSum) // less than to avoid accepting 0 probability tree
2216 {
2217 groupIdx = i;
2218 break;
2219 }
2220 }
2221
2222 tree = SelectTreeToSpawn(point, topLevel.m_aTreeGroups[groupIdx].m_aTrees);
2223 if (!IsEntryValid(tree, pointLocal))
2224 continue;
2225
2226 tree.m_eType = SCR_ETreeType.TOP;
2227 if (useScaleCurve)
2228 {
2229 float distanceFromShape = SCR_Math3D.GetDistanceFromSplineXZ(m_aShapePoints, pointLocal);
2230 if (distanceFromShape <= topLevel.m_fOutlineScaleCurveDistance)
2231 {
2232 // (m_fOutlineScaleCurveDistance - distanceFromShape) because right-to-left curve reading
2233 float scaleFactor = LegacyCurve.Curve(ECurveType.CatmullRom, (topLevel.m_fOutlineScaleCurveDistance - distanceFromShape) * scaleCurveDistanceDivisor, topLevel.m_aOutlineScaleCurve, curveKnots)[1];
2234 if (scaleFactor < ForestGeneratorLevel.SCALE_CURVE_MIN_VALUE)
2235 scaleFactor = ForestGeneratorLevel.SCALE_CURVE_MIN_VALUE;
2236 else
2237 if (scaleFactor > ForestGeneratorLevel.SCALE_CURVE_MAX_VALUE)
2238 scaleFactor = ForestGeneratorLevel.SCALE_CURVE_MAX_VALUE;
2239
2240 tree.m_fScale *= scaleFactor;
2241 }
2242 }
2243
2244 m_Grid.AddEntry(tree, point);
2245 }
2246 }
2247
2248 int index;
2249 while (expectedIterCount > 0)
2250 {
2251 index = m_RandomGenerator.RandInt(0, m_aRectangles.Count());
2252 GenerateTreeInsideRectangle(m_aRectangles[index], topLevel, polygon, worldMat);
2253 expectedIterCount--;
2254 }
2255 }
2256
2257 //------------------------------------------------------------------------------------------------
2263 protected void GenerateTreeInsideRectangle(notnull SCR_ForestGeneratorRectangle rectangle, notnull SCR_ForestGeneratorTreeLevel level, notnull array<float> polygon, vector worldMat[4])
2264 {
2265 array<float> groupProbas = {};
2266 array<float> groupCounts = {};
2267 groupCounts.Resize(level.m_aTreeGroups.Count());
2268
2269 // generate random point inside the shape (polygon at first)
2270 vector pointLocal = GenerateRandomPointInRectangle(rectangle);
2271
2272 if (!rectangle.m_aLines.IsEmpty())
2273 {
2274 if (!Math2D.IsPointInPolygon(polygon, pointLocal[0], pointLocal[2]))
2275 return;
2276
2277 if (!IsBeforeOutlineBorder(rectangle, pointLocal, level.m_fOutlineOverlap))
2278 return;
2279 }
2280
2281 vector point = pointLocal.Multiply4(worldMat);
2282
2283 // see which trees are around - count the types
2284 groupProbas.Copy(level.m_aGroupProbas);
2285 for (int i, count = groupCounts.Count(); i < count; i++)
2286 {
2287 groupCounts[i] = 1;
2288 }
2289
2290 if (level.m_eType == SCR_EForestGeneratorLevelType.TOP)
2291 {
2292 ForestGeneratorTopLevel topLevel = ForestGeneratorTopLevel.Cast(level);
2293 if (topLevel)
2294 {
2295 if (s_Benchmark)
2296 s_Benchmark.BeginMeasure("gridCountEntriesAround");
2297
2298 m_Grid.CountEntriesAround(point, topLevel.m_fClusterRadius, groupCounts);
2299
2300 if (s_Benchmark)
2301 s_Benchmark.EndMeasure("gridCountEntriesAround");
2302
2303 // skew the probability of given groups based on counts
2304 float probaSumToNormalize = 0;
2305 for (int i, count = groupProbas.Count(); i < count; i++)
2306 {
2307 groupProbas[i] = groupProbas[i] * Math.Pow(groupCounts[i], topLevel.m_fClusterStrength);
2308 probaSumToNormalize += groupProbas[i];
2309 }
2310
2311 if (probaSumToNormalize != 0)
2312 {
2313 for (int i, count = groupProbas.Count(); i < count; i++)
2314 {
2315 groupProbas[i] = groupProbas[i] / probaSumToNormalize;
2316 }
2317 }
2318 }
2319 }
2320
2321 float groupProba = m_RandomGenerator.RandFloat01();
2322 int groupIdx = groupProbas.Count() - 1; // last because there is less than in the loop
2323 float probaSum = 0;
2324 for (int i, count = groupProbas.Count(); i < count; ++i)
2325 {
2326 probaSum += groupProbas[i];
2327 if (groupProba < probaSum) // less than to avoid accepting 0 probability tree
2328 {
2329 groupIdx = i;
2330 break;
2331 }
2332 }
2333
2334 if (!level.m_aTreeGroups.IsIndexValid(groupIdx))
2335 groupIdx = m_RandomGenerator.RandInt(0, level.m_aTreeGroups.Count());
2336
2337 ForestGeneratorTree tree = SelectTreeToSpawn(point, level.m_aTreeGroups[groupIdx].m_aTrees);
2338 if (!IsEntryValid(tree, pointLocal))
2339 return;
2340
2341 tree.m_eType = SCR_ETreeType.TOP;
2342 m_Grid.AddEntry(tree, point);
2343 }
2344
2345 //------------------------------------------------------------------------------------------------
2350 protected ForestGeneratorTree SelectTreeToSpawn(vector point, array<ref ForestGeneratorTree> trees)
2351 {
2352 float treeProba = m_RandomGenerator.RandFloat01();
2353 int treeTypeIdx = trees.Count() - 1; // last because there is less than in the loop
2354 float probaSum;
2355 foreach (int i, ForestGeneratorTree tree : trees)
2356 {
2357 probaSum += tree.m_fWeight;
2358 if (treeProba < probaSum) // less than to avoid accepting 0 probability tree
2359 {
2360 treeTypeIdx = i;
2361 break;
2362 }
2363 }
2364
2365 if (s_Benchmark)
2366 s_Benchmark.BeginMeasure("clone");
2367
2368 ForestGeneratorTree tree = ForestGeneratorTree.Cast(trees[treeTypeIdx].Clone());
2369
2370 if (s_Benchmark)
2371 s_Benchmark.EndMeasure("clone");
2372
2373 if (!tree)
2374 return null;
2375
2376 SetObjectScale(tree);
2377 FallenTree fallenTree = FallenTree.Cast(tree);
2378 if (fallenTree)
2379 {
2380 fallenTree.m_fYaw = m_RandomGenerator.RandFloat01() * 360;
2381 fallenTree.Rotate();
2382 }
2383
2384 // see if it fits in given place, if not this type is not valid here
2385 if (m_Grid.IsColliding(point, tree))
2386 return null;
2387
2388 /*
2389 if (fallenTree)
2390 {
2391 vector p1 = fallenTree.m_CapsuleStart + point;
2392 vector p2 = fallenTree.m_CapsuleEnd + point;
2393 Shape shape = Shape.Create(ShapeType.LINE, ARGB(255, 0, 0, 255), ShapeFlags.NOZBUFFER, p1, p2);
2394 m_aDebugShapes.Insert(shape);
2395 shape = Shape.CreateSphere(ARGB(255, 0, 255, 0), ShapeFlags.NOOUTLINE | ShapeFlags.NOZBUFFER, p1, 0.5);
2396 m_aDebugShapes.Insert(shape);
2397 shape = Shape.CreateSphere(ARGB(255, 255, 0, 0), ShapeFlags.NOOUTLINE | ShapeFlags.NOZBUFFER, p2, 0.5);
2398 m_aDebugShapes.Insert(shape);
2399 }
2400 */
2401
2402 m_aGridEntries.Insert(tree);
2403
2404 return tree;
2405 }
2406
2407 //------------------------------------------------------------------------------------------------
2409 protected void SetObjectScale(notnull SCR_ForestGeneratorTreeBase object)
2410 {
2411 object.m_fScale = SafeRandomFloatInclusive(object.m_fMinScale, object.m_fMaxScale);
2412 object.AdjustScale();
2413 }
2414
2415 //------------------------------------------------------------------------------------------------
2420 protected bool IsInOutline(notnull SCR_ForestGeneratorRectangle rectangle, vector pointLocal, float additionalDistance = 0)
2421 {
2422 foreach (SCR_ForestGeneratorLine line : rectangle.m_aLines)
2423 {
2424 float distance = Math3D.PointLineSegmentDistance(pointLocal, line.p1.m_vPos, line.p2.m_vPos);
2425
2426 foreach (ForestGeneratorOutline outline : m_aOutlines)
2427 {
2428 if (!outline.m_bGenerate)
2429 continue;
2430
2431 if (outline.m_eOutlineType == SCR_EForestGeneratorOutlineType.SMALL && !line.p1.m_bSmallOutline)
2432 continue;
2433
2434 if (outline.m_eOutlineType == SCR_EForestGeneratorOutlineType.MIDDLE && !line.p1.m_bMiddleOutline)
2435 continue;
2436
2437 if (distance > outline.m_fMinDistance - additionalDistance && distance < outline.m_fMaxDistance + additionalDistance)
2438 return true;
2439 }
2440 }
2441
2442 return false;
2443 }
2444
2445 //------------------------------------------------------------------------------------------------
2450 protected bool IsBeforeOutlineBorder(notnull SCR_ForestGeneratorRectangle rectangle, vector pointLocal, float offset = 0)
2451 {
2452 foreach (SCR_ForestGeneratorLine line : rectangle.m_aLines)
2453 {
2454 float distance = Math3D.PointLineSegmentDistance(pointLocal, line.p1.m_vPos, line.p2.m_vPos);
2455
2456 foreach (ForestGeneratorOutline outline : m_aOutlines)
2457 {
2458 if (!outline.m_bGenerate)
2459 continue;
2460
2461 if (outline.m_eOutlineType == SCR_EForestGeneratorOutlineType.SMALL && !line.p1.m_bSmallOutline)
2462 continue;
2463
2464 if (outline.m_eOutlineType == SCR_EForestGeneratorOutlineType.MIDDLE && !line.p1.m_bMiddleOutline)
2465 continue;
2466
2467 if (distance < outline.m_fMaxDistance - offset)
2468 return false;
2469 }
2470 }
2471
2472 return true;
2473 }
2474
2475 //------------------------------------------------------------------------------------------------
2479 protected float CalculateAreaForOutline(SCR_ForestGeneratorLine line, ForestGeneratorOutline outline)
2480 {
2481 if (!line || !outline)
2482 return 0;
2483
2484 return line.m_fLength * (outline.m_fMaxDistance - outline.m_fMinDistance);
2485 }
2486
2487 //------------------------------------------------------------------------------------------------
2492 protected float CalculateAreaForOutline(SCR_ForestGeneratorPoint point, ForestGeneratorOutline outline)
2493 {
2494 if (!point || !outline)
2495 return 0;
2496
2497 float areaBigger = Math.PI * outline.m_fMaxDistance * outline.m_fMaxDistance;
2498 float areaSmaller = Math.PI * outline.m_fMinDistance * outline.m_fMinDistance;
2499
2500 return (point.m_fAngle / 360) * (areaBigger - areaSmaller);
2501 }
2502
2503 //------------------------------------------------------------------------------------------------
2504 protected override void OnRegenerate()
2505 {
2506 super.OnRegenerate();
2507
2508 RegenerateForest(true);
2509 }
2510
2511 //------------------------------------------------------------------------------------------------
2512 protected override void _WB_OnInit(inout vector mat[4], IEntitySource src)
2513 {
2514 super._WB_OnInit(mat, src);
2515 if (!s_DebugShapeManager)
2516 s_DebugShapeManager = new SCR_DebugShapeManager();
2517 }
2518
2519#endif // WORKBENCH
2520}
2521
vector scale
ref array< ref ForestGeneratorCluster > m_aClusters
bool m_bDrawDebugShapesRectangulation
bool m_bPrintArea
bool m_bEntitiesFollowTerrainOnShapeMove
bool m_bDrawDebugShapesRegeneration
bool m_bPrintPerformanceDetails
ref Curve m_aGlobalOutlineScaleCurve
bool m_bDrawDebugShapes
float m_fGlobalOutlineScaleCurveDistance
bool m_bDrawDebugShapesObstacles
bool m_bPrintEntitiesCount
bool m_bRegenerateEntireForest
not saved to layer (see _WB_OnKeyChanged), used as a button
ref array< ref ForestGeneratorLevel > m_aLevels
float m_fMaxScale
ref array< string > angles
@ CLUSTER
vector GetOrigin()
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 void _WB_OnInit(IEntity owner, inout vector mat[4], IEntitySource src)
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
vector position
SCR_DestructionSynchronizationComponentClass ScriptComponentClass int index
Get all prefabs that have the spawner data
ref array< ref MapLine > m_aLines
SCR_PossessingManagerComponentClass int
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
void SCR_TimeMeasurementHelper()
enum EVehicleType IEntity
Definition Math.c:22
proto external vector GetOrigin()
proto external void GetTransform(out vector mat[])
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
ShapeFlags
Definition ShapeFlags.c:13
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 OnPointChangedInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, PointChangedSituation situation, int pointIndex, vector position)
void OnShapeTransformInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, array< vector > mins, array< vector > maxes)
void OnShapeInitInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity)
void BeforeShapeTransformInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, inout vector oldTransform[4])
void OnShapeChangedInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, array< vector > mins, array< vector > maxes)
PointChangedSituation
ECurveType
Definition ECurveType.c:13
class Class Clone()
Return shallow copy of object, or null if it is not allowed (not public constructor).
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.
void Debug()
Definition Types.c:327
class WidgetType BOTTOM
class WidgetType TOP
TraceFlags
Definition TraceFlags.c:13
proto int ARGB(int a, int r, int g, int b)