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