Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_NormalAlignedFloatersFinderPlugin.c
Go to the documentation of this file.
1#ifdef WORKBENCH
2[WorkbenchToolAttribute(
3 name: "Normal-Aligned Floaters Finder",
4 category: SCR_PluginCategory.WORLDEDITOR_ENTITY_CHECK,
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
10{
11 /*
12 Category: Search
13 */
14
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;
17
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;
20
21 [Attribute(defvalue: "-0.1", desc: "Maximum distance the entity or its bounding box corners can be BELOW terrain", params: "-inf 0 0.01", precision: 2, category: "Search")] // max 1 as tracing is set to go max 1m
22 protected float m_fNegativeToleranceFromTerrain;
23
24 [Attribute(defvalue: "0.1", desc: "Maximum distance the entity or its bounding box corners can be ABOVE terrain", params: "0 inf 0.01", precision: 2, category: "Search")] // max 1 as tracing is set to go max 1m
25 protected float m_fPositiveToleranceFromTerrain;
26
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;
29
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;
32
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;
35
36 /*
37 Category: Selection
38 */
39
40 [Attribute(defvalue: "1", desc: "Current layer only", category: "Selection")]
41 protected bool m_bCurrentLayerOnly;
42
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;
45
46// [Attribute(defvalue: SCR_ESelectionBrushToolLayer.ALL_LAYERS.ToString(), uiwidget: UIWidgets.ComboBox, enumType: SCR_ESelectionBrushToolLayer, category: "Selection")]
47// protected SCR_ESelectionBrushToolLayer m_eLayerSelection;
48
49// [Attribute(defvalue: "0", desc: "Select the topmost 3D parent, otherwise select the 3D world entity", category: "Selection")]
50// protected bool m_bSelectParentOnly;
51
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;
54
55 protected static const int MAX_SUS_ENTITIES_LISTED = 10;
56 protected static const float MAX_ENTITY_NORMAL_DIFFERENCE = 90;
57
58 //------------------------------------------------------------------------------------------------
59 protected override void Run()
60 {
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)
62 return;
63
64 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
65 BaseWorld baseWorld = worldEditorAPI.GetWorld();
66 vector mins, maxs;
67 baseWorld.GetBoundBox(mins, maxs);
68
69 int worldEntitiesCount = worldEditorAPI.GetEditorEntityCount();
70 if (worldEntitiesCount < 1)
71 {
72 Workbench.Dialog("Info", "No world entities were found");
73 return;
74 }
75
76 FilterPrefabs();
77
78 if (!m_bCurrentLayerOnly && m_aPrefabsToCheck.IsEmpty())
79 {
80 if (!Workbench.ScriptDialog("Warning", "There are no Prefabs to check in the list - continuing will scan all " + worldEntitiesCount + " world entities!", new WorkbenchDialog_OKCancel()))
81 return;
82 }
83
84 Debug.BeginTimeMeasure();
85 array<IEntitySource> susEntitySources = GetSusEntitySources();
86 Debug.EndTimeMeasure(string.Format("Found %1 floaters amongst %2 entities", susEntitySources.Count(), worldEntitiesCount));
87
88 OutputEntities(susEntitySources);
89 }
90
91 //------------------------------------------------------------------------------------------------
93 protected void FilterPrefabs()
94 {
95 int count = m_aPrefabsToCheck.Count();
96 for (int i = count - 1; i >= 0; --i)
97 {
98 if (!m_aPrefabsToCheck[i] || m_aPrefabsToCheck.Find(m_aPrefabsToCheck[i]) != i)
99 m_aPrefabsToCheck.RemoveOrdered(i);
100 }
101 }
102
103 //------------------------------------------------------------------------------------------------
105 protected array<IEntitySource> GetSusEntitySources()
106 {
107 bool scanAllEntities = m_aPrefabsToCheck.IsEmpty();
108
109 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
110 BaseWorld baseWorld = worldEditorAPI.GetWorld();
111 bool doOceanCheck = m_bAboveWaterLevelOnly;
112
113 float oceanLevel;
114 if (doOceanCheck)
115 {
116 if (baseWorld.IsOcean())
117 oceanLevel = baseWorld.GetOceanBaseHeight();
118 else
119 doOceanCheck = false;
120 }
121
122 TraceParam traceParam = new TraceParam();
123 traceParam.Flags = TraceFlags.WORLD;
124
125 // vector cornerFLWorld; // 1998
126 // vector cornerFRWorld; // France
127 // vector cornerBLWorld; // 3 - 0
128 // vector cornerBRWorld; // Brazil
129 vector corners[4];
130
131 array<IEntitySource> result = {};
132
133 int currentLayerId;
134 if (m_bCurrentLayerOnly)
135 currentLayerId = worldEditorAPI.GetCurrentEntityLayerId();
136
137 bool scanInRadius = m_fMaxDistanceFromCamera > 0;
138 float radiusSq;
139 vector cameraPos;
140 if (scanInRadius)
141 {
142 cameraPos = SCR_WorldEditorToolHelper.GetWorldEditorCameraPosition();
143 radiusSq = m_fMaxDistanceFromCamera * m_fMaxDistanceFromCamera;
144 }
145
146 IEntity entity;
147 IEntitySource entitySource;
148 IEntitySource ancestor;
149
150 int count = worldEditorAPI.GetEditorEntityCount();
151
152 for (int i; i < count; ++i)
153 {
154 entitySource = worldEditorAPI.GetEditorEntity(i);
155 if (!entitySource)
156 continue;
157
158 if (m_bCurrentLayerOnly && entitySource.GetLayerID() != currentLayerId)
159 continue;
160
161 ancestor = entitySource.GetAncestor();
162 if (!ancestor)
163 continue;
164
165 ResourceName resourceName = ancestor.GetResourceName();
166 if (!scanAllEntities && !m_aPrefabsToCheck.Contains(resourceName))
167 continue;
168
169 entity = worldEditorAPI.SourceToEntity(entitySource);
170 if (!entity)
171 continue;
172
173 if (!m_bSearchForVegetation && Tree.Cast(entity) != null)
174 continue;
175
176 vector entityPos = entity.GetOrigin();
177 if (doOceanCheck && entityPos[1] < oceanLevel)
178 continue;
179
180 if (scanInRadius && vector.DistanceSq(cameraPos, entityPos) > radiusSq)
181 continue; // outside range
182
183 // no bounding box, continue
184 vector mins, maxs;
185 entity.GetBounds(mins, maxs);
186 if (mins == vector.Zero && maxs == vector.Zero)
187 continue;
188
189 float surfaceY = baseWorld.GetSurfaceY(entityPos[0], entityPos[2]);
190
191 // is close to terrain?
192 float diffY = entityPos[1] - surfaceY;
193 if (m_fNegativeToleranceFromTerrain > diffY || diffY > m_fPositiveToleranceFromTerrain) // Math.Abs costs more than that
194 {
195 result.Insert(entitySource);
196 continue;
197 }
198
199 // is terrain-aligned?
200 traceParam.Start = { entityPos[0], surfaceY + 1, entityPos[2] };
201 traceParam.End = { entityPos[0], surfaceY - 1, entityPos[2] };
202
203 if (baseWorld.TraceMove(traceParam, null) >= 1) // did not hit terrain
204 continue;
205
206 vector terrainNormal = traceParam.TraceNorm;
207 if (terrainNormal == vector.Zero) // something wrong
208 continue;
209
210 vector absEntityUpVector = vector.Direction(entityPos, entity.CoordToParent(vector.Up)).Normalized();
211 if (m_fMaxAngleFromTerrainNormal < Math.Acos(vector.Dot(terrainNormal, absEntityUpVector)) * Math.RAD2DEG)
212 {
213 result.Insert(entitySource); // angled enough
214 continue;
215 }
216
217 mins *= m_fBoundingBoxMultiplier;
218 maxs *= m_fBoundingBoxMultiplier;
219
220 corners[0] = entity.CoordToParent({ mins[0], 0, maxs[2] });
221 corners[1] = entity.CoordToParent({ maxs[0], 0, maxs[2] });
222 corners[2] = entity.CoordToParent({ mins[0], 0, mins[2] });
223 corners[3] = entity.CoordToParent({ maxs[0], 0, mins[2] });
224
225 for (int j; j < 4; ++j)
226 {
227 diffY = corners[j][1] - baseWorld.GetSurfaceY(corners[j][0], corners[j][2]);
228
229 if (m_fNegativeToleranceFromTerrain > diffY || diffY > m_fPositiveToleranceFromTerrain) // Math.Abs costs more than that
230 {
231 result.Insert(entitySource);
232 break;
233 }
234 }
235 }
236
237 return result;
238 }
239
240 //------------------------------------------------------------------------------------------------
242 protected void OutputEntities(notnull array<IEntitySource> entitySources)
243 {
244 int entitiesCount = entitySources.Count();
245 if (entitiesCount == 1)
246 Print("1 suspect entity", level: LogLevel.NORMAL);
247 else
248 PrintFormat("%1 suspect entities", entitiesCount, level: LogLevel.NORMAL);
249
250 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
251 worldEditorAPI.ClearEntitySelection();
252
253 bool ellipsisPrinted = m_iMaxSelectedEntities < 1;
254 int entitiesToSelect = m_iMaxSelectedEntities;
255 foreach (int i, IEntitySource entitySource : entitySources)
256 {
257 if (!ellipsisPrinted)
258 {
259 if (i < MAX_SUS_ENTITIES_LISTED)
260 {
261 Print("Suspect entity " + Debug.GetEntityLinkString(worldEditorAPI.SourceToEntity(entitySource)), level: LogLevel.NORMAL);
262 }
263 else
264 {
265 Print("(...)", LogLevel.NORMAL);
266 ellipsisPrinted = true;
267 }
268 }
269
270 worldEditorAPI.AddToEntitySelection(entitySource);
271 if (m_iMaxSelectedEntities > 0 && --entitiesToSelect < 1)
272 {
273 Print("Maximum selection reached (" + m_iMaxSelectedEntities + ")", LogLevel.WARNING);
274 break;
275 }
276 }
277
278 worldEditorAPI.UpdateSelectionGui();
279 }
280
281 //------------------------------------------------------------------------------------------------
282 [ButtonAttribute("Process", true)]
283 protected int ButtonOK()
284 {
285 return 1;
286 }
287
288 //------------------------------------------------------------------------------------------------
289 [ButtonAttribute("Cancel")]
290 protected int ButtonCancel()
291 {
292 return 0;
293 }
294}
295#endif
params precision
ResourceName resourceName
Definition SCR_AIGroup.c:66
override void Run()
bool ButtonCancel()
bool ButtonOK()
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)
@ Tree
Definition gameLib.c:47
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
Definition LogLevel.c:14
proto void PrintFormat(string fmt, void param1=NULL, void param2=NULL, void param3=NULL, void param4=NULL, void param5=NULL, void param6=NULL, void param7=NULL, void param8=NULL, void param9=NULL, LogLevel level=LogLevel.NORMAL)
SCR_FieldOfViewSettings Attribute
void Debug()
Definition Types.c:327
TraceFlags
Definition TraceFlags.c:13