1 [
EntityEditorProps(
category:
"GameLib/Scripted/Generator", description:
"ForestGeneratorEntity", dynamicBox:
true, visible:
false)]
12 class ForestGeneratorEntity : SCR_AreaGeneratorBaseEntity
18 [
Attribute(defvalue:
"42",
category:
"Generation",
desc:
"Seed used by the random generator of this forest generator")]
19 protected int m_iSeed;
21 [
Attribute(defvalue:
"1",
category:
"Generation",
desc:
"Allow partial forest regeneration, regenerates the whole forest otherwise")]
22 protected bool m_bAllowPartialRegeneration;
25 protected bool m_bRegenerateEntireForest;
27 #ifdef COUNTENTRIES_BENCHMARK
29 protected bool m_bUseCountEntriesAround;
30 #endif // COUNTENTRIES_BENCHMARK
37 protected bool m_bPrintArea;
39 [
Attribute(defvalue:
"0",
category:
"Debug",
desc:
"Print the count of entities spawned by this forest generator")]
40 protected bool m_bPrintEntitiesCount;
43 protected bool m_bPrintPerformanceDetails;
46 protected bool m_bDrawDebugShapes;
49 protected bool m_bDrawDebugShapesObstacles;
52 protected bool m_bDrawDebugShapesRectangulation;
55 protected bool m_bDrawDebugShapesRegeneration;
57 [
Attribute(defvalue:
"1",
category:
"Debug",
desc:
"Make entities follow terrain level on shape move (keeping their relative Y)")]
58 protected bool m_bEntitiesFollowTerrainOnShapeMove;
64 [
Attribute(defvalue:
"",
category:
"Forest",
desc:
"Forest generator levels to spawn in this forest generator polygon")]
65 protected ref array<ref ForestGeneratorLevel> m_aLevels;
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;
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;
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;
76 protected static const float SCALE_CURVE_MAX_VALUE = 1;
77 protected static const float SCALE_CURVE_MIN_VALUE = 0;
83 protected ref array<vector> m_aShapePoints;
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 = {};
95 protected ref array<ref ForestGeneratorOutline> m_aOutlines = {};
96 protected float m_fMaxOutlinesWidth;
97 protected float m_fArea;
99 protected ref map<IEntitySource, float> m_mEntitySourceATLHeights;
101 protected static ref array<float> s_aPreviousPoints2D;
102 protected static ref SCR_TimeMeasurementHelper s_Benchmark;
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";
111 protected static const float RECTANGULATION_SIZE = 50;
112 protected static const float HECTARE_CONVERSION_FACTOR = 0.0001;
113 protected static const float MIN_POSSIBLE_SCALE_VALUE = 0.001;
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]);
125 point.m_vPos[0] <= a &&
126 point.m_vPos[0] <= b &&
127 point.m_vPos[2] <= c &&
128 point.m_vPos[2] <= d;
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]);
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);
158 (dir1 != dir2 && dir3 != dir4) ||
159 (dir1 == 0 && OnLine(line1, line2.p1)) ||
160 (dir2 == 0 && OnLine(line1, line2.p2)) ||
161 (dir3 == 0 && OnLine(line2, line1.p1)) ||
162 (dir4 == 0 && OnLine(line2, line1.p2));
169 IsIntersect(line, rectangle.m_Line1) ||
170 IsIntersect(line, rectangle.m_Line2) ||
171 IsIntersect(line, rectangle.m_Line3) ||
172 IsIntersect(line, rectangle.m_Line4);
176 protected bool PreprocessTreeArray(notnull array<ref ForestGeneratorTree> trees,
int groupIdx,
SCR_ETreeType type,
int debugGroupIdx)
180 for (
int i = trees.Count() - 1; i >= 0; --i)
183 if (tree.m_fWeight <= 0 || tree.m_Prefab.IsEmpty())
185 trees.RemoveOrdered(i);
189 probaSum += tree.m_fWeight;
190 tree.m_iGroupIndex = groupIdx;
199 tree2.m_fWeight = tree2.m_fWeight / probaSum;
203 return !trees.IsEmpty();
207 protected void PreprocessAllTrees()
209 int debugGroupIdx = 0;
212 level.m_aGroupProbas = {};
214 if (level.m_eType == SCR_EForestGeneratorLevelType.BOTTOM)
218 PreprocessTreeArray(treeGroup.m_aTrees, 0,
SCR_ETreeType.BOTTOM, debugGroupIdx);
226 float groupProbaSum = 0;
230 for (
int i, groupCount = level.m_aTreeGroups.Count(); i < groupCount; i++)
232 treeGroup = level.m_aTreeGroups[i];
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))
239 groupProbaSum += treeGroup.m_fWeight;
245 level.m_aTreeGroups.RemoveOrdered(i);
253 if (groupProbaSum > 0)
254 treeGroup2.m_fWeight = treeGroup2.m_fWeight / groupProbaSum;
256 level.m_aGroupProbas.Insert(treeGroup2.m_fWeight);
265 protected array<ref SCR_ForestGeneratorPoint> GetClockWisePoints(notnull IEntitySource shapeEntitySource)
267 BaseContainerList points = shapeEntitySource.GetObjectArray(
"Points");
271 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
273 array<ref SCR_ForestGeneratorPoint> result = {};
277 BaseContainerList dataArr;
280 for (
int i, pointCount = points.Count(); i < pointCount; i++)
282 point = points.Get(i);
283 point.Get(
"Position", pos);
285 bool smallOutline =
true;
286 bool middleOutline =
true;
287 dataArr = point.GetObjectArray(
"Data");
289 bool hasPointData =
false;
290 for (
int j, dataCount = dataArr.Count(); j < dataCount; ++j)
292 data = dataArr.Get(j);
293 if (
data.GetClassName() == POINTDATA_CLASSNAME)
295 data.Get(
"m_bSmallOutline", smallOutline);
296 data.Get(
"m_bMiddleOutline", middleOutline);
302 if (!hasPointData && worldEditorAPI && !worldEditorAPI.UndoOrRedoIsRestoring())
303 worldEditorAPI.CreateObjectArrayVariableMember(point,
null,
"Data", POINTDATA_CLASSNAME, dataArr.Count());
308 if (curPoint.m_vPos == pos)
310 Print(
"Found two points on the same position: " + pos +
", Skipping", LogLevel.WARNING);
321 genPoint.m_vPos = pos;
322 genPoint.m_bSmallOutline = smallOutline;
323 genPoint.m_bMiddleOutline = middleOutline;
325 result.Insert(genPoint);
329 int count = result.Count();
336 for (
int i; i < count; i++)
338 currentPoint = result[i].m_vPos;
340 nextPoint = result[0].m_vPos;
342 nextPoint = result[i + 1].m_vPos;
344 sum += (nextPoint[0] - currentPoint[0]) * (nextPoint[2] + currentPoint[2]);
349 for (
int i, iterNum = count * 0.5; i < iterNum; i++)
351 genPoint = result[i];
352 result[i] = result[count - 1 - i];
353 result[count - 1 - i] = genPoint;
361 protected void FillOutlineLinesAndPoints(notnull array<ref SCR_ForestGeneratorPoint> points)
369 line.m_fLength = (line.p2.m_vPos - line.p1.m_vPos).Length();
370 point.m_Line1 = line;
373 if (point.m_bSmallOutline)
374 m_aSmallOutlinePoints.Insert(point);
376 if (point.m_bMiddleOutline)
377 m_aMiddleOutlinePoints.Insert(point);
379 if (line.p1.m_bSmallOutline)
380 m_aSmallOutlineLines.Insert(line);
382 if (line.p1.m_bMiddleOutline)
383 m_aMiddleOutlineLines.Insert(line);
388 point.m_Line2 = line;
393 point.m_Line1 = line;
394 line.m_fLength = (line.p2.m_vPos - line.p1.m_vPos).Length();
397 if (point.m_bSmallOutline)
398 m_aSmallOutlinePoints.Insert(point);
400 if (point.m_bMiddleOutline)
401 m_aMiddleOutlinePoints.Insert(point);
403 if (line.p1.m_bSmallOutline)
404 m_aSmallOutlineLines.Insert(line);
406 if (line.p1.m_bMiddleOutline)
407 m_aMiddleOutlineLines.Insert(line);
411 protected void CalculateOutlineAnglesForPoints(notnull array<ref SCR_ForestGeneratorPoint> points)
413 int count = points.Count();
422 for (
int i; i < count; i++)
424 currentPoint = points[i];
427 nextPoint = points[i + 1];
429 nextPoint = points[0];
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();
437 currentPoint.m_fMinAngle = yaw1 - 360;
438 currentPoint.m_fMaxAngle = yaw2;
442 currentPoint.m_fMinAngle = yaw1;
443 currentPoint.m_fMaxAngle = yaw2;
446 currentPoint.m_fAngle = Math.AbsFloat(currentPoint.m_fMaxAngle - currentPoint.m_fMinAngle);
448 previousPoint = currentPoint;
453 protected override bool _WB_OnKeyChanged(BaseContainer src,
string key, BaseContainerList ownerContainers, IEntity parent)
455 bool parentResult = super._WB_OnKeyChanged(src, key, ownerContainers, parent);
457 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
461 src = worldEditorAPI.EntityToSource(
this);
463 if (key ==
"m_bRegenerateEntireForest")
465 src.ClearVariable(
"m_bRegenerateEntireForest");
466 if (m_ParentShapeSource)
467 RegenerateForest(
true);
470 BaseContainerTools.WriteToInstance(
this, src);
477 protected override void OnShapeChangedInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, array<vector> mins, array<vector> maxes)
479 super.OnShapeChangedInternal(shapeEntitySrc, shapeEntity, mins, maxes);
485 protected override void OnShapeInitInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity)
487 super.OnShapeInitInternal(shapeEntitySrc, shapeEntity);
488 RegenerateForest(
true);
492 protected override void BeforeShapeTransformInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, inout vector oldTransform[4])
494 super.BeforeShapeTransformInternal(shapeEntitySrc, shapeEntity, oldTransform);
496 if (!m_bEntitiesFollowTerrainOnShapeMove)
499 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
503 if (worldEditorAPI.UndoOrRedoIsRestoring())
506 vector localPos, worldPos;
508 shapeEntitySrc.Get(
"coords", parentPos);
510 m_mEntitySourceATLHeights =
new map<IEntitySource, float>();
511 IEntitySource childSource;
512 for (
int i =
m_Source.GetNumChildren() - 1; i >= 0; i--)
515 if (!childSource.Get(
"coords", localPos))
518 worldPos = localPos + parentPos;
521 if (!worldEditorAPI.TryGetTerrainSurfaceY(worldPos[0], worldPos[2], yTerrain))
524 m_mEntitySourceATLHeights.Insert(childSource, worldPos[1] - yTerrain);
529 protected override void OnShapeTransformInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, array<vector> mins, array<vector> maxes)
531 super.OnShapeTransformInternal(shapeEntitySrc, shapeEntity, mins, maxes);
533 if (!m_bEntitiesFollowTerrainOnShapeMove)
536 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
540 if (worldEditorAPI.UndoOrRedoIsRestoring())
543 if (m_mEntitySourceATLHeights.IsEmpty())
547 vector localPos, worldPos;
549 foreach (IEntitySource childSource,
float relativeY : m_mEntitySourceATLHeights)
551 if (!childSource.Get(
"coords", localPos))
554 worldPos = localPos + absPos;
557 if (!worldEditorAPI.TryGetTerrainSurfaceY(worldPos[0], worldPos[2], yTerrain))
560 float difference = relativeY - (worldPos[1] - yTerrain);
563 localPos[1] = localPos[1] + difference;
564 worldEditorAPI.SetVariableValue(childSource,
null,
"coords", localPos.ToString(
false));
568 m_mEntitySourceATLHeights =
null;
573 protected override void OnPointChangedInternal(IEntitySource shapeEntitySrc, ShapeEntity shapeEntity, PointChangedSituation situation,
int pointIndex, vector
position)
575 if (s_aPreviousPoints2D)
578 array<vector> points3D = GetPoints(shapeEntitySrc);
579 s_aPreviousPoints2D = {};
580 SCR_Math2D.Get2DPolygon(points3D, s_aPreviousPoints2D);
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;
608 protected void MemoryCleanup()
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();
622 protected void RegenerateForest(
bool forceRegeneration =
false)
624 float tick = System.GetTickCount();
625 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
628 Print(
"WorldEditorAPI is not available", LogLevel.ERROR);
632 if (worldEditorAPI.UndoOrRedoIsRestoring())
636 m_aSmallOutlinePoints.Clear();
637 m_aMiddleOutlinePoints.Clear();
638 m_aSmallOutlineLines.Clear();
639 m_aMiddleOutlineLines.Clear();
646 s_DebugShapeManager.Clear();
648 Debug.BeginTimeMeasure();
649 PreprocessAllTrees();
650 Debug.EndTimeMeasure(
"Provided data preprocess");
654 float outlineWidthToClear;
655 ForestGeneratorOutline outline;
658 if (outlineWidthToClear < level.m_fOutlineScaleCurveDistance)
659 outlineWidthToClear = level.m_fOutlineScaleCurveDistance;
661 outline = ForestGeneratorOutline.Cast(level);
665 if (m_fMaxOutlinesWidth < outline.m_fMaxDistance)
666 m_fMaxOutlinesWidth = outline.m_fMaxDistance;
668 if (m_fMaxOutlinesWidth < outline.m_fMinDistance)
669 m_fMaxOutlinesWidth = outline.m_fMinDistance;
671 m_aOutlines.Insert(outline);
674 if (outlineWidthToClear < m_fMaxOutlinesWidth)
675 outlineWidthToClear = m_fMaxOutlinesWidth;
677 array<ref SCR_ForestGeneratorPoint> generatorPoints = GetClockWisePoints(m_ParentShapeSource);
678 if (!generatorPoints || generatorPoints.IsEmpty())
681 Debug.BeginTimeMeasure();
682 FillOutlineLinesAndPoints(generatorPoints);
683 CalculateOutlineAnglesForPoints(generatorPoints);
684 Debug.EndTimeMeasure(
"Outline point calculations");
686 if (m_bPrintPerformanceDetails)
687 s_Benchmark =
new SCR_TimeMeasurementHelper();
694 m_aShapePoints = GetPoints(m_ParentShapeSource);
695 m_aShapePoints.Insert(m_aShapePoints[0]);
698 array<vector> polygon3D = GetPoints(m_ParentShapeSource);
699 array<float> polygon2D = {};
700 SCR_Math2D.Get2DPolygon(polygon3D, polygon2D);
702 Print(
"ForestGenerator - Populating grid", LogLevel.DEBUG);
703 Debug.BeginTimeMeasure();
704 PopulateGrid(polygon2D, polygon3D);
705 Debug.EndTimeMeasure(
"ForestGenerator - Populating grid done");
716 if (forceRegeneration)
718 s_aPreviousPoints2D = {};
722 if (!s_aPreviousPoints2D)
724 Print(
"No previous points! Fallback on current points (this edit does not do anything)", LogLevel.WARNING);
725 s_aPreviousPoints2D = polygon2D;
731 worldEditorAPI.BeginEditSequence(
m_Source);
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");
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");
743 worldEditorAPI.EndEditSequence(
m_Source);
745 if (m_bPrintPerformanceDetails)
746 s_Benchmark.PrintAllMeasures();
748 s_aPreviousPoints2D =
null;
751 Print(
"Total time: " + System.GetTickCount(tick) +
" ms", LogLevel.NORMAL);
759 if (forceRegeneration || !m_bAllowPartialRegeneration)
770 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
774 for (
int i =
m_Source.GetNumChildren() - 1; i >= 0; --i)
776 IEntitySource src =
m_Source.GetChild(i);
777 entity = worldEditorAPI.SourceToEntity(src);
778 worldPos = entity.GetOrigin();
779 entityPos = CoordToLocal(worldPos);
782 if (!Math2D.IsPointInPolygon(currentPoints2D, entityPos[0], entityPos[2]))
784 worldEditorAPI.DeleteEntity(src);
789 if (outlineChecker.IsPosWithinSetDistance(entityPos))
791 if (m_bDrawDebugShapesRegeneration)
792 s_DebugShapeManager.AddLine(worldPos, worldPos + DEBUG_VERTICAL_LINE, REGENERATION_DELETION_COLOUR);
794 worldEditorAPI.DeleteEntity(src);
805 WorldEditorAPI worldEditorAPI = _WB_GetEditorAPI();
807 BaseWorld world = worldEditorAPI.GetWorld();
812 int topLevelEntitiesCount = 0;
813 int bottomLevelEntitiesCount = 0;
814 int smallOutlineEntitiesCount = 0;
815 int middleOutlineEntitiesCount = 0;
816 int clusterEntitiesCount = 0;
817 int generatedEntitiesCount = 0;
819 vector worldPos, localPos, entityPos;
820 SCR_ForestGeneratorTreeBase baseEntry;
821 FallenTree fallenTree;
822 WideForestGeneratorClusterObject wideObject;
824 IEntitySource treeSrc;
826 BaseContainerList baseContainerList;
827 BaseContainer baseContainer;
828 vector randomVerticalOffset;
831 vector newUp, newRight, newForward, angles;
833 TraceParam traceParam =
new TraceParam();
834 traceParam.Flags = TraceFlags.WORLD;
839 if (m_bDrawDebugShapesObstacles)
841 foreach (SCR_ObstacleDetectorSplineInfo info : s_ObstacleDetector.GetObstacles())
843 if (info.m_fClearance == 0)
845 s_DebugShapeManager.AddAABBRectangleXZ(info.m_vMinWithClearance, info.m_vMaxWithClearance);
849 vector maxWithClearance;
850 foreach (
int index, vector minWithClearance : info.m_aMinsWithClearance)
852 maxWithClearance = info.m_aMaxsWithClearance[
index];
853 s_DebugShapeManager.AddAABBRectangleXZ(minWithClearance, maxWithClearance);
861 bool partialGeneration = !forceRegeneration && m_bAllowPartialRegeneration &&
m_Source.GetNumChildren() > 0;
864 bool useScaleCurve = m_fGlobalOutlineScaleCurveDistance > 0 && !m_aGlobalOutlineScaleCurve.IsEmpty();
865 float scaleCurveDistanceDivisor;
866 array<float> curveKnots;
869 scaleCurveDistanceDivisor = 1 / m_fGlobalOutlineScaleCurveDistance;
871 foreach (vector scalePoint : m_aGlobalOutlineScaleCurve)
873 curveKnots.Insert(scalePoint[0]);
879 for (
int i, count = m_Grid.GetEntryCount(); i < count; ++i)
881 baseEntry = m_Grid.GetEntry(i, worldPos);
882 if (baseEntry.m_Prefab.IsEmpty())
885 worldPos[1] = world.GetSurfaceY(worldPos[0], worldPos[2]);
886 localPos = CoordToLocal(worldPos);
887 localPos[1] = localPos[1] + baseEntry.m_fVerticalOffset;
889 if (partialGeneration)
892 Math2D.IsPointInPolygon(previousPoints2D, localPos[0], localPos[2]) &&
893 !outlineChecker.IsPosWithinSetDistance(localPos)
898 float scale = baseEntry.m_fScale;
899 if (useScaleCurve && scale > 0)
901 float distanceFromShape =
SCR_Math3D.GetDistanceFromSplineXZ(m_aShapePoints, localPos);
902 if (distanceFromShape <= m_fGlobalOutlineScaleCurveDistance)
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;
909 if (scaleFactor > SCALE_CURVE_MAX_VALUE)
910 scaleFactor = SCALE_CURVE_MAX_VALUE;
912 scale *= scaleFactor;
916 if (scale < MIN_POSSIBLE_SCALE_VALUE)
918 Print(
"avoid near-zero scale tree (scale = " + scale +
" < " + MIN_POSSIBLE_SCALE_VALUE +
")", LogLevel.DEBUG);
923 s_Benchmark.BeginMeasure(
"obstacle");
925 bool hasObstacle = s_ObstacleDetector.HasObstacle(worldPos);
927 s_Benchmark.EndMeasure(
"obstacle");
932 if (partialGeneration)
934 if (m_bDrawDebugShapesRegeneration)
935 s_DebugShapeManager.AddLine(worldPos, worldPos + DEBUG_VERTICAL_LINE, REGENERATION_CREATION_COLOUR);
938 treeSrc = worldEditorAPI.CreateEntity(baseEntry.m_Prefab,
"", worldEditorAPI.GetCurrentEntityLayerId(),
m_Source, localPos, vector.Zero);
939 worldEditorAPI.BeginEditSequence(treeSrc);
941 generatedEntitiesCount++;
944 worldEditorAPI.SetVariableValue(treeSrc,
null,
"scale", scale.ToString());
946 randomVerticalOffset = vector.Zero;
947 bool alignToNormal =
false;
948 bool randomYaw =
false;
950 baseContainerList = treeSrc.GetObjectArray(
"editorData");
951 if (baseContainerList && baseContainerList.Count() > 0)
953 baseContainer = baseContainerList.Get(0);
954 baseContainer.Get(
"randomVertOffset", randomVerticalOffset);
955 baseContainer.Get(
"alignToNormal", alignToNormal);
956 baseContainer.Get(
"randomYaw", randomYaw);
959 if (randomVerticalOffset != vector.Zero)
961 localPos[1] = localPos[1] + SafeRandomFloatInclusive(randomVerticalOffset[0], randomVerticalOffset[1]);
962 worldEditorAPI.SetVariableValue(treeSrc,
null,
"coords", localPos.ToString(
false));
965 fallenTree = FallenTree.Cast(baseEntry);
966 wideObject = WideForestGeneratorClusterObject.Cast(baseEntry);
969 alignToNormal = fallenTree.m_bAlignToNormal;
972 alignToNormal = wideObject.m_bAlignToNormal;
978 yaw = -wideObject.m_fYaw;
980 yaw = -fallenTree.m_fYaw;
985 worldEditorAPI.SetVariableValue(treeSrc,
null,
"angleY", yaw.ToString());
989 if (baseEntry.m_fRandomPitchAngle > 0)
990 pitch =
m_RandomGenerator.RandFloatXY(-baseEntry.m_fRandomPitchAngle, baseEntry.m_fRandomPitchAngle);
993 if (baseEntry.m_fRandomRollAngle > 0)
994 roll =
m_RandomGenerator.RandFloatXY(-baseEntry.m_fRandomRollAngle, baseEntry.m_fRandomRollAngle);
997 worldEditorAPI.SetVariableValue(treeSrc,
null,
"angleX", pitch.ToString());
1000 worldEditorAPI.SetVariableValue(treeSrc,
null,
"angleZ", roll.ToString());
1004 traceParam.Start = worldPos + vector.Up;
1005 traceParam.End = worldPos - vector.Up;
1006 world.TraceMove(traceParam,
null);
1007 worldEditorAPI.SourceToEntity(treeSrc).GetTransform(mat);
1009 newUp = traceParam.TraceNorm;
1014 newRight = newUp * mat[2];
1015 newRight.Normalize();
1018 newForward = newRight * newUp;
1019 newForward.Normalize();
1025 mat[2] = newForward;
1027 angles = Math3D.MatrixToAngles(mat);
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());
1034 worldEditorAPI.EndEditSequence(treeSrc);
1036 switch (baseEntry.m_eType)
1039 case SCR_ETreeType.BOTTOM: bottomLevelEntitiesCount++;
break;
1040 case SCR_ETreeType.MIDDLE_OUTLINE: middleOutlineEntitiesCount++;
break;
1041 case SCR_ETreeType.SMALL_OUTLINE: smallOutlineEntitiesCount++;
break;
1045 if (m_bDrawDebugShapes)
1046 s_DebugShapeManager.AddSphere(worldPos, 1 + 5 - (
int)baseEntry.m_eType, GetColorForTree(baseEntry.m_iGroupIndex, baseEntry.m_eType), ShapeFlags.NOOUTLINE);
1053 Print(
"Area of the polygon is: " + m_fArea.ToString(lenDec: 2) +
" square meters", LogLevel.NORMAL);
1055 if (m_bPrintEntitiesCount)
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);
1065 return generatedEntitiesCount;
1069 protected void PopulateGrid(array<float> polygon2D, array<vector> polygon3D)
1072 m_aGridEntries.Clear();
1074 m_fArea = SCR_Math2D.GetPolygonArea(polygon2D);
1077 m_Grid.Resize(bbox.m_vDimensions[0], bbox.m_vDimensions[2]);
1079 Debug.BeginTimeMeasure();
1080 Rectangulate(bbox, polygon2D);
1081 Debug.EndTimeMeasure(
"ForestGenerator - Rectangulation done");
1084 GetWorldTransform(worldMat);
1085 vector bboxMin = bbox.m_vMin;
1086 vector bboxMinWorld = bboxMin.Multiply4(worldMat);
1087 m_Grid.SetPointOffset(bboxMinWorld[0], bboxMinWorld[2]);
1089 Debug.BeginTimeMeasure();
1090 GenerateForestGeneratorTrees(polygon2D, bbox);
1091 Debug.EndTimeMeasure(
"ForestGenerator - Grid tree generation done");
1095 protected void Rectangulate(
SCR_AABB bbox, array<float> polygon2D)
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);
1108 for (
int x; x < targetRectangleCountW; x++)
1110 for (
int y; y < targetRectangleCountL; y++)
1112 bool isInPolygon =
false;
1117 rectangle.m_fWidth = targetRectangleWidth;
1118 rectangle.m_fLength = targetRectangleLength;
1119 rectangle.m_fArea = rectangle.m_fLength * rectangle.m_fWidth;
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;
1126 isInPolygon = Math2D.IsPointInPolygon(polygon2D, p1[0], p1[2]);
1128 rectangle.m_aPoints.Insert(p1);
1130 p1[0] = p1[0] + targetRectangleWidth;
1131 rectangle.m_Line1.p2.m_vPos = p1;
1132 rectangle.m_Line2.p1.m_vPos = p1;
1134 isInPolygon = Math2D.IsPointInPolygon(polygon2D, p1[0], p1[2]);
1136 rectangle.m_aPoints.Insert(p1);
1138 p1[2] = p1[2] + targetRectangleLength;
1139 rectangle.m_Line2.p2.m_vPos = p1;
1140 rectangle.m_Line3.p1.m_vPos = p1;
1142 isInPolygon = Math2D.IsPointInPolygon(polygon2D, p1[0], p1[2]);
1144 rectangle.m_aPoints.Insert(p1);
1146 p1[0] = p1[0] - targetRectangleWidth;
1147 rectangle.m_Line3.p2.m_vPos = p1;
1148 rectangle.m_Line4.p1.m_vPos = p1;
1150 isInPolygon = Math2D.IsPointInPolygon(polygon2D, p1[0], p1[2]);
1152 rectangle.m_aPoints.Insert(p1);
1156 if (!NeedsCheck(line, rectangle) || !IsIntersect(line, rectangle))
1162 if (rectLine == line)
1170 rectangle.m_aLines.Insert(line);
1173 bool areLinesEmpty = rectangle.m_aLines.IsEmpty();
1174 if (areLinesEmpty && !isInPolygon)
1177 m_aRectangles.Insert(rectangle);
1180 m_aNonOutlineRectangles.Insert(rectangle);
1182 m_aOutlineRectangles.Insert(rectangle);
1184 if (m_bDrawDebugShapesRectangulation)
1199 s_DebugShapeManager.AddBBox(ownerOrigin + rectangle.m_Line1.p1.m_vPos, ownerOrigin + rectangle.m_Line3.p1.m_vPos, ARGB(63, red, green, blue));
1208 vector linePoint1 = line.p1.m_vPos;
1209 vector linePoint2 = line.p2.m_vPos;
1212 rectangle.GetBounds(mins, maxs);
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];
1224 (linePoint1x < minsx && linePoint2x < minsx) ||
1225 (linePoint1x > maxsx && linePoint2x > maxsx) ||
1226 (linePoint1z < minsz && linePoint2z < minsz) ||
1227 (linePoint1z > maxsz && linePoint2z > maxsz))
1234 protected void GenerateForestGeneratorTrees(array<float> polygon2D,
SCR_AABB bbox)
1236 if (bbox.m_vDimensions[0] * bbox.m_vDimensions[2] <= 0.01)
1242 if (!cluster.m_bGenerate || cluster.m_fRadius <= 0)
1245 if (cluster.m_Type == SCR_EForestGeneratorClusterType.CIRCLE)
1246 GenerateCircleCluster(ForestGeneratorCircleCluster.Cast(cluster), polygon2D, bbox);
1248 if (cluster.m_Type == SCR_EForestGeneratorClusterType.STRIP)
1249 GenerateStripCluster(ForestGeneratorStripCluster.Cast(cluster), polygon2D, bbox);
1255 if (!level.m_bGenerate)
1258 switch (level.m_eType)
1260 case SCR_EForestGeneratorLevelType.TOP:
1261 GenerateTopTrees(polygon2D, bbox, ForestGeneratorTopLevel.Cast(level));
1264 case SCR_EForestGeneratorLevelType.OUTLINE:
1265 GenerateOutlineTrees(polygon2D, bbox, ForestGeneratorOutline.Cast(level));
1268 case SCR_EForestGeneratorLevelType.BOTTOM:
1269 GenerateBottomTrees(polygon2D, bbox, ForestGeneratorBottomLevel.Cast(level));
1276 protected int FindRectanglesInCircle(vector center,
float radius, out array<SCR_ForestGeneratorRectangle> rectangles)
1279 float deltaX, deltaY;
1280 float radiusSq = radius * radius;
1284 foreach (vector point : rectangle.m_aPoints)
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));
1289 if (deltaX * deltaX + deltaY * deltaY < radiusSq)
1291 rectangles.Insert(rectangle);
1301 protected bool GetPointOutsideOutlines(notnull array<float> polygon2D,
SCR_AABB bbox, out vector clusterCenter,
float additionalDistance = 0)
1303 bool isInOutline =
true;
1304 int maxTriesToFindCenter = 10;
1305 int currentTriesCount = 0;
1307 array<SCR_ForestGeneratorRectangle> rectangles;
1308 int rectanglesCount;
1309 while (isInOutline && currentTriesCount < maxTriesToFindCenter)
1311 clusterCenter =
m_RandomGenerator.GenerateRandomPoint(polygon2D, bbox.m_vMin, bbox.m_vMax);
1314 rectanglesCount = FindRectanglesInCircle(clusterCenter, additionalDistance + m_fMaxOutlinesWidth, rectangles);
1315 isInOutline =
false;
1319 if (IsInOutline(rectangle, clusterCenter, additionalDistance))
1326 currentTriesCount++;
1329 return currentTriesCount < maxTriesToFindCenter;
1333 protected void GenerateCircleCluster(notnull ForestGeneratorCircleCluster cluster, notnull array<float> polygon2D, notnull
SCR_AABB bbox)
1336 GetWorldTransform(worldMat);
1338 float CDENSHA = SafeRandomFloatInclusive(cluster.m_fMinCDENSHA, cluster.m_fMaxCDENSHA);
1340 vector clusterCenter;
1342 SmallForestGeneratorClusterObject newClusterObject;
1344 WideForestGeneratorClusterObject wideObject;
1345 for (
int c, clusterCount = Math.Ceil(m_fArea * HECTARE_CONVERSION_FACTOR * CDENSHA); c < clusterCount; c++)
1347 if (!GetPointOutsideOutlines(polygon2D, bbox, clusterCenter, cluster.m_fRadius))
1350 bool isPolygonCheckUseless =
SCR_Math3D.IsPointWithinSplineDistanceXZ(m_aShapePoints, clusterCenter, cluster.m_fRadius);
1352 foreach (SmallForestGeneratorClusterObject clusterObject : cluster.m_aObjects)
1354 for (
int o, objectCount = SafeRandomInt(clusterObject.m_iMinCount, clusterObject.m_iMaxCount); o < objectCount; o++)
1356 pointLocal = GeneratePointInCircle(clusterObject.m_fMinRadius, clusterObject.m_fMaxRadius, clusterCenter);
1357 if (isPolygonCheckUseless || Math2D.IsPointInPolygon(polygon2D, pointLocal[0], pointLocal[2]))
1360 s_Benchmark.BeginMeasure(
"clone");
1361 newClusterObject = SmallForestGeneratorClusterObject.Cast(clusterObject.Clone());
1363 s_Benchmark.EndMeasure(
"clone");
1364 if (!newClusterObject)
1367 m_aGridEntries.Insert(newClusterObject);
1368 point = pointLocal.Multiply4(worldMat);
1370 SetObjectScale(newClusterObject);
1372 wideObject = WideForestGeneratorClusterObject.Cast(newClusterObject);
1376 wideObject.Rotate();
1379 if (m_Grid.IsColliding(point, newClusterObject))
1383 m_Grid.AddEntry(newClusterObject, point);
1391 protected void GenerateStripCluster(notnull ForestGeneratorStripCluster cluster, notnull array<float> polygon2D, notnull
SCR_AABB bbox)
1394 GetWorldTransform(worldMat);
1398 vector perpendicular;
1402 float CDENSHA = SafeRandomFloatInclusive(cluster.m_fMinCDENSHA, cluster.m_fMaxCDENSHA);
1404 vector clusterCenter;
1408 SmallForestGeneratorClusterObject newClusterObject;
1409 WideForestGeneratorClusterObject wideObject;
1410 for (
int c, clusterCount = Math.Ceil(m_fArea * HECTARE_CONVERSION_FACTOR * CDENSHA); c < clusterCount; c++)
1412 if (!GetPointOutsideOutlines(polygon2D, bbox, clusterCenter, cluster.m_fRadius))
1415 bool isPolygonCheckUseless =
SCR_Math3D.IsPointWithinSplineDistanceXZ(m_aShapePoints, clusterCenter, cluster.m_fRadius);
1417 foreach (SmallForestGeneratorClusterObject clusterObject : cluster.m_aObjects)
1419 for (
int o, objectCount = SafeRandomInt(clusterObject.m_iMinCount, clusterObject.m_iMaxCount); o < objectCount; o++)
1421 float distance = SafeRandomFloatInclusive(clusterObject.m_fMinRadius, clusterObject.m_fMaxRadius);
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;
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;
1436 if (isPolygonCheckUseless || Math2D.IsPointInPolygon(polygon2D, pointLocal[0], pointLocal[2]))
1439 s_Benchmark.BeginMeasure(
"clone");
1440 newClusterObject = SmallForestGeneratorClusterObject.Cast(clusterObject.Clone());
1442 s_Benchmark.EndMeasure(
"clone");
1443 if (!newClusterObject)
1446 m_aGridEntries.Insert(newClusterObject);
1447 point = pointLocal.Multiply4(worldMat);
1449 SetObjectScale(newClusterObject);
1451 wideObject = WideForestGeneratorClusterObject.Cast(newClusterObject);
1455 wideObject.Rotate();
1458 if (m_Grid.IsColliding(point, newClusterObject))
1462 m_Grid.AddEntry(newClusterObject, point);
1470 protected float SafeRandomFloatInclusive(
float min,
float max)
1478 Print(
"A forest generator object has some min value > max value at " +
GetOrigin(), LogLevel.WARNING);
1483 protected int SafeRandomInt(
int min,
int max)
1491 Print(
"A forest generator object has some min value > max value at " +
GetOrigin(), LogLevel.WARNING);
1496 protected vector GeneratePointInCircle(
float innerRadius,
float outerRadius, vector circleCenter)
1499 float rand = SafeRandomFloatInclusive(innerRadius, outerRadius);
1507 float rand = SafeRandomFloatInclusive(innerRadius, outerRadius);
1515 m_RandomGenerator.RandFloat01() * rectangle.m_fWidth + rectangle.m_Line1.p1.m_vPos[0],
1517 m_RandomGenerator.RandFloat01() * rectangle.m_fLength + rectangle.m_Line1.p1.m_vPos[2]
1522 protected bool GetIsAnyTreeValid(notnull array<ref TreeGroupClass> treeGroups)
1526 if (treeGroup.m_fWeight <= 0)
1531 if (tree.m_fWeight > 0 && !tree.m_Prefab.IsEmpty())
1540 protected void GenerateOutlineTrees(array<float> polygon,
SCR_AABB bbox, ForestGeneratorOutline outline)
1542 if (!outline || !outline.m_aTreeGroups || outline.m_aTreeGroups.IsEmpty())
1545 if (!GetIsAnyTreeValid(outline.m_aTreeGroups))
1549 array<ref SCR_ForestGeneratorPoint> currentOutlinePoints;
1550 array<ref SCR_ForestGeneratorLine> currentOutlineLines;
1552 switch (outline.m_eOutlineType)
1554 case SCR_EForestGeneratorOutlineType.SMALL:
1557 currentOutlinePoints = m_aSmallOutlinePoints;
1558 currentOutlineLines = m_aSmallOutlineLines;
1562 case SCR_EForestGeneratorOutlineType.MIDDLE:
1565 currentOutlinePoints = m_aMiddleOutlinePoints;
1566 currentOutlineLines = m_aMiddleOutlineLines;
1571 if (!currentOutlinePoints || !currentOutlineLines)
1574 array<float> groupProbas = {};
1575 array<float> groupCounts = {};
1576 groupCounts.Resize(outline.m_aTreeGroups.Count());
1579 GetWorldTransform(worldMat);
1581 bool useScaleCurve = outline.m_fOutlineScaleCurveDistance > 0 && !outline.m_aOutlineScaleCurve.IsEmpty();
1582 float scaleCurveDistanceDivisor;
1583 array<float> curveKnots;
1587 scaleCurveDistanceDivisor = 1 / outline.m_fOutlineScaleCurveDistance;
1589 foreach (vector scalePoint : outline.m_aOutlineScaleCurve)
1591 curveKnots.Insert(scalePoint[0]);
1597 vector
direction, perpendicular, pointLocal, point;
1598 float probaSumToNormalize, groupProba, probaSum;
1603 direction = line.p2.m_vPos - line.p1.m_vPos;
1605 perpendicular.Normalize();
1606 iterCount = outline.m_fDensity * (CalculateAreaForOutline(line, outline) * HECTARE_CONVERSION_FACTOR);
1607 for (
int treeIdx; treeIdx < iterCount; ++treeIdx)
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]))
1614 point = pointLocal.Multiply4(worldMat);
1617 int groupProbaCount = groupProbas.Copy(outline.m_aGroupProbas);
1618 for (
int i, count = groupCounts.Count(); i < count; i++)
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);
1630 s_Benchmark.EndMeasure(
"gridCountEntriesAround");
1633 probaSumToNormalize = 0;
1634 for (
int i; i < groupProbaCount; i++)
1636 groupProbas[i] = groupProbas[i] * Math.Pow(groupCounts[i], outline.m_fClusterStrength);
1637 probaSumToNormalize += groupProbas[i];
1640 if (probaSumToNormalize > 0)
1642 for (
int i; i < groupProbaCount; i++)
1644 groupProbas[i] = groupProbas[i] / probaSumToNormalize;
1649 groupIdx = groupProbas.Count() - 1;
1651 for (
int i, count = groupProbas.Count(); i < count; ++i)
1653 probaSum += groupProbas[i];
1654 if (groupProba < probaSum)
1661 tree = SelectTreeToSpawn(point, outline.m_aTreeGroups[groupIdx].m_aTrees);
1663 if (!IsEntryValid(tree, pointLocal))
1666 tree.m_eType = treeType;
1669 float distanceFromShape =
SCR_Math3D.GetDistanceFromSplineXZ(m_aShapePoints, pointLocal);
1670 if (distanceFromShape <= outline.m_fOutlineScaleCurveDistance)
1673 float scaleFactor = Math3D.Curve(ECurveType.CatmullRom, (outline.m_fOutlineScaleCurveDistance - distanceFromShape) * scaleCurveDistanceDivisor, outline.m_aOutlineScaleCurve, curveKnots)[1];
1680 tree.m_fScale *= scaleFactor;
1684 m_Grid.AddEntry(tree, point);
1690 iterCount = outline.m_fDensity * CalculateAreaForOutline(currentPoint, outline) * HECTARE_CONVERSION_FACTOR;
1691 for (
int treeIdx; treeIdx < iterCount; treeIdx++)
1693 pointLocal = GeneratePointInCircle(outline.m_fMinDistance, outline.m_fMaxDistance, currentPoint);
1695 bool lineDistance1 = IsPointInProperDistanceFromLine(pointLocal, currentPoint.m_Line1, outline.m_fMinDistance, outline.m_fMaxDistance);
1699 bool lineDistance2 = IsPointInProperDistanceFromLine(pointLocal, currentPoint.m_Line2, outline.m_fMinDistance, outline.m_fMaxDistance);
1703 if (!Math2D.IsPointInPolygon(polygon, pointLocal[0], pointLocal[2]))
1706 point = pointLocal.Multiply4(worldMat);
1709 groupProbas.Copy(outline.m_aGroupProbas);
1710 for (
int i, count = groupCounts.Count(); i < count; i++)
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);
1722 s_Benchmark.EndMeasure(
"gridCountEntriesAround");
1725 probaSumToNormalize = 0;
1726 for (
int i, count = groupProbas.Count(); i < count; i++)
1728 groupProbas[i] = groupProbas[i] * Math.Pow(groupCounts[i], outline.m_fClusterStrength);
1729 probaSumToNormalize += groupProbas[i];
1732 if (probaSumToNormalize != 0)
1734 for (
int i, count = groupProbas.Count(); i < count; i++)
1736 groupProbas[i] = groupProbas[i] / probaSumToNormalize;
1741 groupIdx = groupProbas.Count() - 1;
1743 for (
int i, count = groupProbas.Count(); i < count; i++)
1745 probaSum += groupProbas[i];
1746 if (groupProba < probaSum)
1753 tree = SelectTreeToSpawn(point, outline.m_aTreeGroups[groupIdx].m_aTrees);
1755 if (!IsEntryValid(tree, pointLocal))
1758 tree.m_eType = treeType;
1759 m_Grid.AddEntry(tree, point);
1765 protected bool IsPointInProperDistanceFromLine(vector point,
SCR_ForestGeneratorLine line,
float minDistance,
float maxDistance)
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] });
1777 FallenTree fallenTree = FallenTree.Cast(tree);
1784 distance = Math3D.PointLineSegmentDistance(pointLocal, line.p1.m_vPos, line.p2.m_vPos);
1785 minDistance = fallenTree.GetMinDistanceFromLine();
1795 protected void GenerateBottomTrees(array<float> polygon,
SCR_AABB bbox, ForestGeneratorBottomLevel bottomLevel)
1797 if (!bottomLevel || bottomLevel.m_aTreeGroups.IsEmpty())
1800 if (!GetIsAnyTreeValid(bottomLevel.m_aTreeGroups))
1803 array<float> groupProbas = {};
1804 float totalWeight = 0;
1805 int groupCount = bottomLevel.m_aTreeGroups.Count();
1806 groupProbas.Resize(groupCount);
1809 totalWeight += treeGroup.m_fWeight;
1812 if (totalWeight != 0)
1814 for (
int i; i < groupCount; i++)
1816 groupProbas[i] = (bottomLevel.m_aTreeGroups[i].m_fWeight / totalWeight);
1821 GetWorldTransform(worldMat);
1823 bool useScaleCurve = bottomLevel.m_fOutlineScaleCurveDistance > 0 && !bottomLevel.m_aOutlineScaleCurve.IsEmpty();
1824 float scaleCurveDistanceDivisor;
1825 array<float> curveKnots;
1829 scaleCurveDistanceDivisor = 1 / bottomLevel.m_fOutlineScaleCurveDistance;
1831 foreach (vector scalePoint : bottomLevel.m_aOutlineScaleCurve)
1833 curveKnots.Insert(scalePoint[0]);
1837 int expectedIterCount = m_fArea * HECTARE_CONVERSION_FACTOR * bottomLevel.m_fDensity;
1843 int iterCount = bottomLevel.m_fDensity * rectangle.m_fArea * HECTARE_CONVERSION_FACTOR;
1844 for (
int treeIdx; treeIdx < iterCount; ++treeIdx)
1846 expectedIterCount--;
1848 pointLocal = GenerateRandomPointInRectangle(rectangle);
1850 if (!rectangle.m_aLines.IsEmpty())
1852 if (!Math2D.IsPointInPolygon(polygon, pointLocal[0], pointLocal[2]))
1855 if (IsInOutline(rectangle, pointLocal))
1859 float perlinValue = Math.PerlinNoise01(pointLocal[0], 0, pointLocal[2]);
1860 if (perlinValue > 1 || perlinValue < 0)
1862 Print(
"Perlin value is out of range <0,1>, something went wrong!", LogLevel.ERROR);
1866 float rangeBeginning = 0;
1868 foreach (
int i,
float groupProba : groupProbas)
1870 if (perlinValue > rangeBeginning && perlinValue < (groupProba + rangeBeginning))
1875 rangeBeginning += groupProba;
1878 point = pointLocal.Multiply4(worldMat);
1879 tree = SelectTreeToSpawn(point, bottomLevel.m_aTreeGroups[groupIdx].m_aTrees);
1881 if (!IsEntryValid(tree, pointLocal))
1885 tree.m_iDebugGroupIndex = groupIdx;
1888 float distanceFromShape =
SCR_Math3D.GetDistanceFromSplineXZ(m_aShapePoints, pointLocal);
1889 if (distanceFromShape <= bottomLevel.m_fOutlineScaleCurveDistance)
1892 float scaleFactor = Math3D.Curve(ECurveType.CatmullRom, (bottomLevel.m_fOutlineScaleCurveDistance - distanceFromShape) * scaleCurveDistanceDivisor, bottomLevel.m_aOutlineScaleCurve, curveKnots)[1];
1899 tree.m_fScale *= scaleFactor;
1903 m_Grid.AddEntry(tree, point);
1908 while (expectedIterCount > 0)
1911 GenerateTreeInsideRectangle(m_aRectangles[
index], bottomLevel, polygon, worldMat);
1912 expectedIterCount--;
1917 protected void GenerateTopTrees(array<float> polygon,
SCR_AABB bbox, ForestGeneratorTopLevel topLevel)
1919 if (!topLevel || topLevel.m_aTreeGroups.IsEmpty())
1922 if (!GetIsAnyTreeValid(topLevel.m_aTreeGroups))
1925 array<float> groupProbas = {};
1926 array<float> groupCounts = {};
1927 groupCounts.Resize(topLevel.m_aTreeGroups.Count());
1930 GetWorldTransform(worldMat);
1932 bool useScaleCurve = topLevel.m_fOutlineScaleCurveDistance > 0 && !topLevel.m_aOutlineScaleCurve.IsEmpty();
1933 float scaleCurveDistanceDivisor;
1934 array<float> curveKnots;
1938 scaleCurveDistanceDivisor = 1 / topLevel.m_fOutlineScaleCurveDistance;
1940 foreach (vector scalePoint : topLevel.m_aOutlineScaleCurve)
1942 curveKnots.Insert(scalePoint[0]);
1947 int expectedIterCount = m_fArea * HECTARE_CONVERSION_FACTOR * topLevel.m_fDensity;
1953 float area = rectangle.m_fArea * HECTARE_CONVERSION_FACTOR;
1954 int iterCount = topLevel.m_fDensity * area;
1956 for (
int treeIdx; treeIdx < iterCount; ++treeIdx)
1959 pointLocal = GenerateRandomPointInRectangle(rectangle);
1960 expectedIterCount--;
1962 if (!rectangle.m_aLines.IsEmpty())
1964 if (!Math2D.IsPointInPolygon(polygon, pointLocal[0], pointLocal[2]))
1967 if (IsInOutline(rectangle, pointLocal))
1971 point = pointLocal.Multiply4(worldMat);
1974 groupProbas.Copy(topLevel.m_aGroupProbas);
1975 for (
int i, count = groupCounts.Count(); i < count; i++)
1981 s_Benchmark.BeginMeasure(
"gridCountEntriesAround");
1983 #ifdef COUNTENTRIES_BENCHMARK
1984 if (m_bUseCountEntriesAround)
1985 #endif // COUNTENTRIES_BENCHMARK
1986 m_Grid.CountEntriesAround(point, topLevel.m_fClusterRadius, groupCounts);
1988 s_Benchmark.EndMeasure(
"gridCountEntriesAround");
1991 float probaSumToNormalize = 0;
1992 for (
int i, count = groupProbas.Count(); i < count; i++)
1994 groupProbas[i] = groupProbas[i] * Math.Pow(groupCounts[i], topLevel.m_fClusterStrength);
1995 probaSumToNormalize += groupProbas[i];
1998 if (probaSumToNormalize != 0)
2000 for (
int i, count = groupProbas.Count(); i < count; i++)
2002 groupProbas[i] = groupProbas[i] / probaSumToNormalize;
2007 int groupIdx = groupProbas.Count() - 1;
2009 for (
int i, count = groupProbas.Count(); i < count; ++i)
2011 probaSum += groupProbas[i];
2012 if (groupProba < probaSum)
2019 tree = SelectTreeToSpawn(point, topLevel.m_aTreeGroups[groupIdx].m_aTrees);
2020 if (!IsEntryValid(tree, pointLocal))
2026 float distanceFromShape =
SCR_Math3D.GetDistanceFromSplineXZ(m_aShapePoints, pointLocal);
2027 if (distanceFromShape <= topLevel.m_fOutlineScaleCurveDistance)
2030 float scaleFactor = Math3D.Curve(ECurveType.CatmullRom, (topLevel.m_fOutlineScaleCurveDistance - distanceFromShape) * scaleCurveDistanceDivisor, topLevel.m_aOutlineScaleCurve, curveKnots)[1];
2037 tree.m_fScale *= scaleFactor;
2041 m_Grid.AddEntry(tree, point);
2046 while (expectedIterCount > 0)
2049 GenerateTreeInsideRectangle(m_aRectangles[
index], topLevel, polygon, worldMat);
2050 expectedIterCount--;
2057 array<float> groupProbas = {};
2058 array<float> groupCounts = {};
2059 groupCounts.Resize(level.m_aTreeGroups.Count());
2062 vector pointLocal = GenerateRandomPointInRectangle(rectangle);
2064 if (!rectangle.m_aLines.IsEmpty())
2066 if (!Math2D.IsPointInPolygon(polygon, pointLocal[0], pointLocal[2]))
2069 if (IsInOutline(rectangle, pointLocal))
2073 vector point = pointLocal.Multiply4(worldMat);
2076 groupProbas.Copy(level.m_aGroupProbas);
2077 for (
int i, count = groupCounts.Count(); i < count; i++)
2082 if (level.m_eType == SCR_EForestGeneratorLevelType.TOP)
2084 ForestGeneratorTopLevel topLevel = ForestGeneratorTopLevel.Cast(level);
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);
2094 s_Benchmark.EndMeasure(
"gridCountEntriesAround");
2097 float probaSumToNormalize = 0;
2098 for (
int i, count = groupProbas.Count(); i < count; i++)
2100 groupProbas[i] = groupProbas[i] * Math.Pow(groupCounts[i], topLevel.m_fClusterStrength);
2101 probaSumToNormalize += groupProbas[i];
2104 if (probaSumToNormalize != 0)
2106 for (
int i, count = groupProbas.Count(); i < count; i++)
2108 groupProbas[i] = groupProbas[i] / probaSumToNormalize;
2115 int groupIdx = groupProbas.Count() - 1;
2117 for (
int i, count = groupProbas.Count(); i < count; ++i)
2119 probaSum += groupProbas[i];
2120 if (groupProba < probaSum)
2127 if (!level.m_aTreeGroups.IsIndexValid(groupIdx))
2130 ForestGeneratorTree tree = SelectTreeToSpawn(point, level.m_aTreeGroups[groupIdx].m_aTrees);
2131 if (!IsEntryValid(tree, pointLocal))
2135 m_Grid.AddEntry(tree, point);
2139 protected ForestGeneratorTree SelectTreeToSpawn(vector point, array<ref ForestGeneratorTree> trees)
2142 int treeTypeIdx = trees.Count() - 1;
2146 probaSum += tree.m_fWeight;
2147 if (treeProba < probaSum)
2155 s_Benchmark.BeginMeasure(
"clone");
2158 s_Benchmark.EndMeasure(
"clone");
2163 SetObjectScale(tree);
2164 FallenTree fallenTree = FallenTree.Cast(tree);
2168 fallenTree.Rotate();
2172 if (m_Grid.IsColliding(point, tree))
2189 m_aGridEntries.Insert(tree);
2195 protected void SetObjectScale(SCR_ForestGeneratorTreeBase
object)
2197 object.m_fScale = SafeRandomFloatInclusive(
object.m_fMinScale,
object.
m_fMaxScale);
2198 object.AdjustScale();
2207 foreach (ForestGeneratorOutline outline : m_aOutlines)
2209 if (!outline.m_bGenerate)
2212 if (outline.m_eOutlineType == SCR_EForestGeneratorOutlineType.SMALL && !line.p1.m_bSmallOutline)
2215 if (outline.m_eOutlineType == SCR_EForestGeneratorOutlineType.MIDDLE && !line.p1.m_bMiddleOutline)
2218 distance = Math3D.PointLineSegmentDistance(pointLocal, line.p1.m_vPos, line.p2.m_vPos);
2220 if (
distance > outline.m_fMinDistance - additionalDistance &&
distance < outline.m_fMaxDistance + additionalDistance)
2231 if (!line || !outline)
2234 return line.m_fLength * (outline.m_fMaxDistance - outline.m_fMinDistance);
2240 if (!point || !outline)
2243 float areaBigger = Math.PI * outline.m_fMaxDistance * outline.m_fMaxDistance;
2244 float areaSmaller = Math.PI * outline.m_fMinDistance * outline.m_fMinDistance;
2246 return (point.m_fAngle / 360) * (areaBigger - areaSmaller);
2250 protected override void OnRegenerate()
2252 RegenerateForest(
true);
2259 void ForestGeneratorEntity(IEntitySource src, IEntity parent)
2262 if (!s_DebugShapeManager)