2[WorkbenchToolAttribute(
3 name:
"Normal-Aligned Floaters Finder",
5 description:
"Find entities that are not properly aligned to terrain's normal",
6 shortcut:
"Ctrl+Alt+Shift+PageUp",
7 wbModules: {
"WorldEditor" },
8 awesomeFontCode: 0xE09A)]
9class SCR_NormalAlignedFloatersFinderPlugin : WorldEditorPlugin
15 [
Attribute(defvalue:
"",
desc:
"Which Prefabs should be checked for proper normal alignment (exact Prefab match)",
category:
"Search")]
16 protected ref array<ResourceName> m_aPrefabsToCheck;
18 [
Attribute(defvalue:
"0",
desc:
"Look for misplaced vegetation (trees & bushes) - does not impact searching by above Prefabs",
category:
"Search")]
19 protected bool m_bSearchForVegetation;
22 protected float m_fNegativeToleranceFromTerrain;
25 protected float m_fPositiveToleranceFromTerrain;
27 [
Attribute(defvalue:
"1",
desc:
"Only check for entities that are above water (ocean) level, whether above or below terrain level",
category:
"Search")]
28 protected bool m_bAboveWaterLevelOnly;
30 [
Attribute(defvalue:
"5", uiwidget: UIWidgets.Slider,
desc:
"Maximum angle the entity can have from terrain slope [degrees]",
params:
"0 90 0.01",
precision: 2,
category:
"Search")]
31 protected float m_fMaxAngleFromTerrainNormal;
33 [
Attribute(defvalue: (Math.Sqrt(2) * 0.5).ToString(),
desc:
"Bounding box lower corners are checked - this multiplier makes checked corners go from the centre to the real bounding box corners\n- 0.5 = half X/Z\n- 0.707 = approximate ellipsis \"corner\"\n- 1 = bounding box exact corner positions",
params:
"0.25 1 0.001",
category:
"Search")]
34 protected float m_fBoundingBoxMultiplier;
41 protected bool m_bCurrentLayerOnly;
43 [
Attribute(defvalue:
"0", uiwidget: UIWidgets.Slider,
desc:
"Search in a radius around camera [m]\n0 = search whole terrain",
params:
"0 5000 100",
category:
"Selection")]
44 protected float m_fMaxDistanceFromCamera;
52 [
Attribute(defvalue:
"2000",
desc:
"Performance-related selection limit - 0 for no limit", uiwidget: UIWidgets.Slider,
params:
"0 10000 100",
category:
"Selection")]
53 protected int m_iMaxSelectedEntities;
55 protected static const int MAX_SUS_ENTITIES_LISTED = 10;
56 protected static const float MAX_ENTITY_NORMAL_DIFFERENCE = 90;
59 protected override void Run()
61 if (Workbench.ScriptDialog(
"Normal-Aligned Floaters Finder",
"Please fill Prefabs To Check below - if left empty, this plugin will scan all world entities",
this) == 0)
64 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
65 BaseWorld baseWorld = worldEditorAPI.GetWorld();
67 baseWorld.GetBoundBox(mins, maxs);
69 int worldEntitiesCount = worldEditorAPI.GetEditorEntityCount();
70 if (worldEntitiesCount < 1)
72 Workbench.Dialog(
"Info",
"No world entities were found");
78 if (!m_bCurrentLayerOnly && m_aPrefabsToCheck.IsEmpty())
80 if (!Workbench.ScriptDialog(
"Warning",
"There are no Prefabs to check in the list - continuing will scan all " + worldEntitiesCount +
" world entities!",
new WorkbenchDialog_OKCancel()))
84 Debug.BeginTimeMeasure();
85 array<IEntitySource> susEntitySources = GetSusEntitySources();
86 Debug.EndTimeMeasure(
string.Format(
"Found %1 floaters amongst %2 entities", susEntitySources.Count(), worldEntitiesCount));
88 OutputEntities(susEntitySources);
93 protected void FilterPrefabs()
95 int count = m_aPrefabsToCheck.Count();
96 for (
int i = count - 1; i >= 0; --i)
98 if (!m_aPrefabsToCheck[i] || m_aPrefabsToCheck.Find(m_aPrefabsToCheck[i]) != i)
99 m_aPrefabsToCheck.RemoveOrdered(i);
105 protected array<IEntitySource> GetSusEntitySources()
107 bool scanAllEntities = m_aPrefabsToCheck.IsEmpty();
109 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
110 BaseWorld baseWorld = worldEditorAPI.GetWorld();
111 bool doOceanCheck = m_bAboveWaterLevelOnly;
116 if (baseWorld.IsOcean())
117 oceanLevel = baseWorld.GetOceanBaseHeight();
119 doOceanCheck =
false;
122 TraceParam traceParam =
new TraceParam();
131 array<IEntitySource> result = {};
134 if (m_bCurrentLayerOnly)
135 currentLayerId = worldEditorAPI.GetCurrentEntityLayerId();
137 bool scanInRadius = m_fMaxDistanceFromCamera > 0;
142 cameraPos = SCR_WorldEditorToolHelper.GetWorldEditorCameraPosition();
143 radiusSq = m_fMaxDistanceFromCamera * m_fMaxDistanceFromCamera;
147 IEntitySource entitySource;
148 IEntitySource ancestor;
150 int count = worldEditorAPI.GetEditorEntityCount();
152 for (
int i; i < count; ++i)
154 entitySource = worldEditorAPI.GetEditorEntity(i);
158 if (m_bCurrentLayerOnly && entitySource.GetLayerID() != currentLayerId)
161 ancestor = entitySource.GetAncestor();
166 if (!scanAllEntities && !m_aPrefabsToCheck.Contains(
resourceName))
169 entity = worldEditorAPI.SourceToEntity(entitySource);
173 if (!m_bSearchForVegetation &&
Tree.Cast(entity) != null)
177 if (doOceanCheck && entityPos[1] < oceanLevel)
180 if (scanInRadius && vector.DistanceSq(cameraPos, entityPos) > radiusSq)
186 if (mins == vector.Zero && maxs == vector.Zero)
189 float surfaceY = baseWorld.GetSurfaceY(entityPos[0], entityPos[2]);
192 float diffY = entityPos[1] - surfaceY;
193 if (m_fNegativeToleranceFromTerrain > diffY || diffY > m_fPositiveToleranceFromTerrain)
195 result.Insert(entitySource);
200 traceParam.Start = { entityPos[0], surfaceY + 1, entityPos[2] };
201 traceParam.End = { entityPos[0], surfaceY - 1, entityPos[2] };
203 if (baseWorld.TraceMove(traceParam, null) >= 1)
206 vector terrainNormal = traceParam.TraceNorm;
207 if (terrainNormal == vector.Zero)
210 vector absEntityUpVector = vector.Direction(entityPos, entity.
CoordToParent(vector.Up)).Normalized();
211 if (m_fMaxAngleFromTerrainNormal < Math.Acos(vector.Dot(terrainNormal, absEntityUpVector)) * Math.RAD2DEG)
213 result.Insert(entitySource);
217 mins *= m_fBoundingBoxMultiplier;
218 maxs *= m_fBoundingBoxMultiplier;
225 for (
int j; j < 4; ++j)
227 diffY = corners[j][1] - baseWorld.GetSurfaceY(corners[j][0], corners[j][2]);
229 if (m_fNegativeToleranceFromTerrain > diffY || diffY > m_fPositiveToleranceFromTerrain)
231 result.Insert(entitySource);
242 protected void OutputEntities(notnull array<IEntitySource> entitySources)
244 int entitiesCount = entitySources.Count();
245 if (entitiesCount == 1)
250 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
251 worldEditorAPI.ClearEntitySelection();
253 bool ellipsisPrinted = m_iMaxSelectedEntities < 1;
254 int entitiesToSelect = m_iMaxSelectedEntities;
255 foreach (
int i, IEntitySource entitySource : entitySources)
257 if (!ellipsisPrinted)
259 if (i < MAX_SUS_ENTITIES_LISTED)
261 Print(
"Suspect entity " +
Debug.GetEntityLinkString(worldEditorAPI.SourceToEntity(entitySource)), level:
LogLevel.NORMAL);
266 ellipsisPrinted =
true;
270 worldEditorAPI.AddToEntitySelection(entitySource);
271 if (m_iMaxSelectedEntities > 0 && --entitiesToSelect < 1)
273 Print(
"Maximum selection reached (" + m_iMaxSelectedEntities +
")",
LogLevel.WARNING);
278 worldEditorAPI.UpdateSelectionGui();
ResourceName resourceName
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
enum EVehicleType IEntity
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
proto external vector GetOrigin()
proto external void GetBounds(out vector mins, out vector maxs)
proto external vector CoordToParent(vector coord)
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
proto void PrintFormat(string fmt, void param1=NULL, void param2=NULL, void param3=NULL, void param4=NULL, void param5=NULL, void param6=NULL, void param7=NULL, void param8=NULL, void param9=NULL, LogLevel level=LogLevel.NORMAL)
SCR_FieldOfViewSettings Attribute