Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_WorldEntitiesStatisticsPlugin.c
Go to the documentation of this file.
1#ifdef WORKBENCH
3 name: "World Entities Statistics",
4 description: "Get Prefabs usage and Entities statistics in the currently opened world",
5 wbModules: { "WorldEditor" },
6// shortcut: "Ctrl+Shift+A",
7 awesomeFontCode: 0xF1FE)] // terrain-looking chart!
8class SCR_WorldEntitiesStatisticsPlugin : WorkbenchPlugin
9{
10 /*
11 Category: General
12 */
13
14 [Attribute(defvalue: "0", desc: "Output result in storage (txt and edds), otherwise Log Console", category: "General")]
15 protected bool m_bOutputToFile;
16
17 /*
18 Category: Text Output
19 */
20
21 [Attribute(defvalue: "0", desc: "Only analyse Entities/Prefabs from the active layer", category: "Text Output")]
22 protected bool m_bActiveLayerOnly;
23
24 [Attribute(defvalue: "0", desc: "Top X values to display\n0 = display everything", params: "0 inf", category: "Text Output")]
25 protected int m_iMaxDisplayedEntries;
26
27 [Attribute(defvalue: "0", desc: "Value under which entity counts are not listed", params: "0 inf", category: "Text Output")]
28 protected int m_iDisplayThreshold;
29
30 /*
31 Category: Image Output
32 */
33
34 [Attribute(defvalue: "1", desc: "Output terrain entities's density map (requires Output To File)", category: "Heatmap")]
35 protected bool m_bOutputHeatmap;
36
37 [Attribute(defvalue: "0", uiwidget: UIWidgets.ComboBox, desc: "Heat Map Type:\n- density = entity count per pixel\n- variety = different model (not entities/Prefabs) count per pixel", enums: SCR_ParamEnumArray.FromString("Entity density,Number of entities per pixel;Model variety,Number of different models per pixel"), category: "Heatmap")]
38 protected int m_iHeatmapType;
39
40 // not working, IDK why yet
41// [Attribute(defvalue: "0", desc: "Generate a density map for each layer (requires Output To File)", category: "Heatmap")]
42 protected bool m_bOutputHeatmapForAllLayers;
43
44 [Attribute(defvalue: "0", uiwidget: UIWidgets.ComboBox, desc: "- Greyscale: from black to white\n- Thermal: from blue to green to red\n- Alpha: from transparent to white", enums: SCR_ParamEnumArray.FromString("Greyscale,From black to white;Thermal,From blue to green to red;Alpha,From transparent to white"), category: "Heatmap")]
45 protected int m_iHeatmapColourMode;
46
47 [Attribute(defvalue: "0", desc: "Invert pixel value (except for max value), e.g black = most dense, white = least dense instead of the opposite", category: "Heatmap")]
48 protected bool m_bHeatmapValueInversion;
49
50 [Attribute(defvalue: "1", uiwidget: UIWidgets.ComboBox, desc: "This setting can prevent a density peak from \"darkening\" the image everywhere else - it also offers to highlight pixels above the 0..2×median range", enums: SCR_ParamEnumArray.FromString("Raw value;2 × average;2 × median"), category: "Heatmap")]
51 protected int m_iHeatmapMaxValueMode;
52
53 [Attribute(defvalue: "1", desc: "Highlight values above max (when Heat Map Max Value Mode is not Raw)\n- Greyscale: red pixels\n- Thermal: white pixels", category: "Heatmap")]
54 protected bool m_bHeatmapHighlightValuesAboveMax;
55
56 [Attribute(defvalue: "512", uiwidget: UIWidgets.ComboBox, desc: "Number of detection squares in terrain height/width", enums: SCR_ParamEnumArray.FromString("16,16×16,;32,32×32,;64,64×64,;128,128×128,;256,256×256,;512,512×512,;1024,1024×1024,;2048,2048×2048,;4096,4096×4096,;1080,1080×1080 (wallpaper),;1440,1440×1440 (wallpaper),;2160,2160×2160 (wallpaper),"), category: "Heatmap")]
57 protected int m_iHeatmapDefinition;
58
59 [Attribute(defvalue: "1", uiwidget: UIWidgets.ComboBox, desc: "Definition multiplier to obtain a bigger image - e.g ×2 = 1 data point takes 2×2 (4) pixels\nMaximum resolution allowed: " + SCR_HeatmapHelper.MAX_RESOLUTION, enums: SCR_ParamEnumArray.FromString(SCR_HeatmapHelper.GetResolutionFactorEnum()), category: "Heatmap")]
60 protected int m_iHeatmapResolutionFactor;
61
62// [Attribute(defvalue: "0", uiwidget: UIWidgets.ComboBox, desc: "Determine density maps's file format", enums: SCR_ParamEnumArray.FromString(".dds;.png") category: "Heatmap")]
63// protected int m_iHeatmapFileFormat;
64
65 /*
66 Debug
67 */
68
69 [Attribute(defvalue: "0", desc: "Visual shapes debug - display e.g land surface vs water surface", category: "Debug")]
70 protected bool m_bDebug;
71
73
74 protected static const string WORLD_DIRECTORY_FORMAT = "WorldStatistics_%1";
75 protected static const string LAYERS_SUBDIR = "Layers";
76 protected static const string OUTPUT_FILE_NAME = "PrefabStatistics.txt";
77 protected static const string LAYER_OUTPUT_FILE_NAME = "%1_%2_PrefabStatistics.txt";
78 protected static const string OUTPUT_MAP_NAME = "Heatmap_%1_%2.dds";
79 protected static const string LAYER_OUTPUT_MAP_NAME = "%1_%2_Heatmap_%3_%4.dds";
80
81 protected static const string NO_MAP_CONTENT_FORMAT = "No %1 found";
82 protected static const int MIN_TERRAIN_DEBUG_RESOLUTION = 50; // min 50m
83 protected static const float SQM_TO_SQKM = 0.000001; // 1km = 1000×1000 m² = 1M m²
84
85 protected static const int TYPE_DENSITY = 0;
86 protected static const int TYPE_VARIETY = 1;
87
88 protected static const int MIN_ENTITIES_RGB = 8;
89
90 //------------------------------------------------------------------------------------------------
91 protected override void Run()
92 {
93 if (!Workbench.ScriptDialog("", "Get world entities statistics (or close the window)", this))
94 return;
95
96 Debug.BeginTimeMeasure();
97 int scannedEntitiesCount = ScanWorld();
98 if (scannedEntitiesCount < 0)
99 Debug.EndTimeMeasure("World entity scan error");
100 else
101 Debug.EndTimeMeasure(string.Format("World scan (%1 entities)", scannedEntitiesCount));
102 }
103
104 //------------------------------------------------------------------------------------------------
106 protected int ScanWorld()
107 {
108 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
109 if (!worldEditorAPI)
110 {
111 Print("World Editor API is not available", LogLevel.ERROR);
112 return -1;
113 }
114
115 string worldPath = SCR_WorldEditorToolHelper.GetWorldPath();
116 if (!worldPath) // .IsEmpty()
117 {
118 Workbench.Dialog("", "Be sure to load a world (or have it saved) before using the World Entities Statistics plugin.");
119 return -1;
120 }
121
122 IEntitySource terrainEntity = SCR_WorldEditorToolHelper.GetTerrainEntitySource();
123 if (!terrainEntity)
124 {
125 Workbench.Dialog("", "Be sure to have a terrain entity (of type GenericTerrainEntity) before using the World Entities Statistics plugin.");
126 return -1;
127 }
128
129 vector terrainOrigin;
130 if (!terrainEntity.Get("coords", terrainOrigin))
131 return -1;
132
133 float terrainUnitScale = worldEditorAPI.GetTerrainUnitScale();
134 float terrainX = terrainUnitScale * worldEditorAPI.GetTerrainResolutionX();
135 float terrainZ = terrainX;
136 vector terrainSize = { terrainX, 0, terrainZ };
137
138 float landRatio = GetLandAboveWaterRatio(worldEditorAPI, terrainOrigin, terrainSize, terrainUnitScale);
139
141 "%1km² (%2%% land, %3%% water)",
142 (terrainX * terrainZ * SQM_TO_SQKM).ToString(lenDec: 2),
143 (landRatio * 100).ToString(lenDec: 2),
144 ((1 - landRatio) * 100).ToString(lenDec: 2));
145
146 IEntitySource entitySource;
147 IEntity entity;
148 IEntitySource ancestor;
149 int noSourceFound;
150 int sourcesWithoutEntity;
151
152 int currentSceneId;
153 int currentLayerId;
154 int subScenesCount = worldEditorAPI.GetNumSubScenes();
157 SCR_WorldEntitiesStatisticsPlugin_Report currentLayerReport;
158 SCR_WorldEntitiesStatisticsPlugin_Report mainReport;
159
160 SCR_WorldEntitiesStatisticsPlugin_Report_World worldInfo = new SCR_WorldEntitiesStatisticsPlugin_Report_World();
161 worldInfo.m_fTerrainSurface = terrainX * terrainZ;
162 worldInfo.m_fTerrainSurfaceASLRatio = landRatio;
163 worldInfo.m_vTerrainMin = terrainOrigin;
164 worldInfo.m_vTerrainMax = terrainSize;
165
166 if (m_bActiveLayerOnly)
167 {
168 currentSceneId = worldEditorAPI.GetCurrentSubScene();
169 currentLayerId = worldEditorAPI.GetCurrentEntityLayerId();
170 }
171 else
172 {
173 mainReport = new SCR_WorldEntitiesStatisticsPlugin_Report();
174 mainReport.m_WorldInfo = worldInfo;
175 }
176
177 Debug.BeginTimeMeasure();
178
179 int editorEntitiesCount = worldEditorAPI.GetEditorEntityCount();
180 for (int i = editorEntitiesCount - 1; i >= 0; --i)
181 {
182 entitySource = worldEditorAPI.GetEditorEntity(i);
183 if (!entitySource) // wat
184 {
185 ++noSourceFound;
186 continue;
187 }
188
189 int entitySceneId = entitySource.GetSubScene();
190 int entityLayerId = entitySource.GetLayerID();
191 if (m_bActiveLayerOnly && (entityLayerId != currentLayerId || entitySceneId != currentSceneId))
192 continue;
193
194 if (!subSceneLayerReports.Find(entitySceneId, layerReports))
195 {
197 subSceneLayerReports.Set(entitySceneId, layerReports);
198 }
199
200 if (!layerReports.Find(entityLayerId, currentLayerReport))
201 {
202 currentLayerReport = new SCR_WorldEntitiesStatisticsPlugin_Report();
203 currentLayerReport.m_WorldInfo = worldInfo;
204
205 currentLayerReport.m_iSubSceneId = entitySceneId;
206 currentLayerReport.m_sSubSceneName = string.Format("SubScene #%1/%2", entitySceneId + 1, subScenesCount);
207 currentLayerReport.m_iLayerId = entityLayerId;
208 currentLayerReport.m_sLayerName = worldEditorAPI.GetSubsceneLayerPath(entitySceneId, entityLayerId);
209 layerReports.Insert(entityLayerId, currentLayerReport);
210 }
211
212 // add to all
213 entity = worldEditorAPI.SourceToEntity(entitySource);
214 vector worldPos;
215 if (entity)
216 {
217 worldPos = entity.GetOrigin();
218 currentLayerReport.m_aAllEntityPositions.Insert(worldPos);
219 if (mainReport)
220 mainReport.m_aAllEntityPositions.Insert(worldPos);
221 }
222 else
223 {
224 ++sourcesWithoutEntity;
225 }
226
227 ancestor = entitySource.GetAncestor();
228 if (ancestor) // a Prefab
229 {
230 ResourceName resourceName = ancestor.GetResourceName();
231 if (resourceName) // !.IsEmpty()
232 {
233 currentLayerReport.m_mPrefabResult.Set(resourceName, currentLayerReport.m_mPrefabResult.Get(resourceName) + 1);
234
235 IEntityComponentSource componentSource = SCR_BaseContainerTools.FindComponentSource(entitySource, "MeshObject");
236 ResourceName model;
237 if (componentSource)
238 {
239 if (componentSource.Get("Object", model) && model) // !.IsEmpty()
240 {
241 currentLayerReport.m_aModels.Insert(resourceName);
242 currentLayerReport.m_aModelPositions.Insert(worldPos);
243 }
244 }
245
246 if (mainReport)
247 {
248 mainReport.m_mPrefabResult.Set(resourceName, mainReport.m_mPrefabResult.Get(resourceName) + 1);
249 if (model) // !.IsEmpty()
250 {
251 mainReport.m_aModels.Insert(resourceName);
252 mainReport.m_aModelPositions.Insert(worldPos);
253 }
254 }
255 }
256 else // a Prefab without a name...
257 {
258 resourceName = entitySource.GetResourceName();
259 if (resourceName) // !.IsEmpty() // a Prefab with Prefab set on entity itself?
260 {
261 currentLayerReport.m_mPrefabNoAncestorResult.Set(resourceName, currentLayerReport.m_mPrefabNoAncestorResult.Get(resourceName) + 1);
262 if (mainReport)
263 mainReport.m_mPrefabNoAncestorResult.Set(resourceName, mainReport.m_mPrefabNoAncestorResult.Get(resourceName) + 1);
264 }
265 else // a Prefab without a Prefab path
266 {
267 string className = ancestor.GetClassName();
268 currentLayerReport.m_mEmptyResult.Set(className, currentLayerReport.m_mEmptyResult.Get(className) + 1);
269 if (mainReport)
270 mainReport.m_mEmptyResult.Set(className, mainReport.m_mEmptyResult.Get(className) + 1);
271 }
272 }
273 }
274 else // a raw entity (spline, GenericEntity, etc)
275 {
276 string className = entitySource.GetClassName();
277 currentLayerReport.m_mGenericResult.Set(className, currentLayerReport.m_mGenericResult.Get(className) + 1);
278 if (mainReport)
279 mainReport.m_mGenericResult.Set(className, mainReport.m_mGenericResult.Get(className) + 1);
280 }
281 }
282
283 Debug.EndTimeMeasure("Sorting " + editorEntitiesCount + " entities");
284
285 if (noSourceFound > 0)
286 PrintFormat("%1 entities do NOT have an Entity Source!", noSourceFound, level: LogLevel.WARNING);
287
288 if (sourcesWithoutEntity > 0)
289 PrintFormat("%1 entity sources do NOT have an Entity!", sourcesWithoutEntity, level: LogLevel.WARNING);
290
291 // ######
292 // output
293 // ######
294
295 string worldName = SCR_WorldEditorToolHelper.GetWorldName();
296 string worldDirectory = string.Format(WORLD_DIRECTORY_FORMAT, worldName);
297 string layersDirectory = FilePath.Concat(worldDirectory, LAYERS_SUBDIR);
298// layersDirectory = worldDirectory;
299
300 string filePath;
301 foreach (int subSceneId, map<int, ref SCR_WorldEntitiesStatisticsPlugin_Report> layersReports : subSceneLayerReports)
302 {
303 foreach (int layerId, SCR_WorldEntitiesStatisticsPlugin_Report layerReport : layersReports)
304 {
305 if (m_bOutputToFile)
306 {
307 filePath = FilePath.Concat(layersDirectory, string.Format(LAYER_OUTPUT_FILE_NAME, layerReport.m_iSubSceneId, layerReport.m_sLayerName));
308 string fileDir = FilePath.StripFileName(filePath);
309 if (!FileIO.MakeDirectory(fileDir))
310 {
311 Print("Cannot create " + fileDir, LogLevel.WARNING);
312 continue;
313 }
314
315// if (m_bOutputHeatmapForAllLayers)
316// {
318// filePath = FilePath.Concat(worldDirectory, string.Format(LAYER_OUTPUT_MAP_NAME, layerReport.m_iSubSceneId, layerReport.m_sLayerName));
319// if (!CreateDensityImage(
320// filePath,
321// m_iHeatmapDefinition,
322// layerReport.m_WorldInfo.m_vTerrainMin,
323// layerReport.m_WorldInfo.m_vTerrainMax,
324// layerReport.m_aAllEntityPositions))
325// {
326// Print("Cannot create image " + filePath, LogLevel.WARNING);
327// }
328// }
329 }
330
331 OutputLines(GetReportLines(layerReport), filePath);
332
333 if (m_bActiveLayerOnly && filePath) // !.IsEmpty()
334 {
335 string absPath;
336 if (Workbench.GetAbsolutePath(filePath, absPath, true))
337 Workbench.RunCmd(string.Format("notepad \"%1\"", absPath));
338 }
339 }
340 }
341
342 if (mainReport)
343 {
344 filePath = FilePath.Concat(worldDirectory, OUTPUT_FILE_NAME);
345 OutputLines(GetReportLines(mainReport), filePath);
346 if (filePath) // !.IsEmpty()
347 {
348 string absPath;
349 if (Workbench.GetAbsolutePath(filePath, absPath, true))
350 {
351 string cmd = string.Format("explorer \"%1\"", FilePath.StripFileName(absPath));
352 cmd.Replace(SCR_StringHelper.SLASH, SCR_StringHelper.ANTISLASH); // explorer does not support slashes, notepad does -_-
353 Workbench.RunCmd(cmd);
354 }
355 }
356
357 if (m_bOutputToFile && m_bOutputHeatmap)
358 {
359 string type;
360 if (m_iHeatmapType == TYPE_DENSITY)
361 type = "density";
362 else
363// if (m_iHeatmapType == TYPE_VARIETY)
364 type = "variety";
365
366 string colourMode;
367 if (m_iHeatmapColourMode == SCR_HeatmapHelper.COLOUR_MODE_GREYSCALE)
368 colourMode = "BW";
369 else
370 if (m_iHeatmapColourMode == SCR_HeatmapHelper.COLOUR_MODE_THERMAL)
371 colourMode = "RGB";
372 else
373// if (m_iHeatmapColourMode == SCR_HeatmapHelper.COLOUR_MODE_ALPHA)
374 colourMode = "Alpha";
375
376 if (m_bHeatmapValueInversion)
377 colourMode += "inv";
378
379 filePath = FilePath.Concat(worldDirectory, string.Format(OUTPUT_MAP_NAME, type, colourMode));
380 if (m_iHeatmapType == TYPE_DENSITY)
381 {
382 if (!CreateDensityImage(
383 filePath,
384 m_iHeatmapDefinition,
385 mainReport.m_WorldInfo.m_vTerrainMin,
386 mainReport.m_WorldInfo.m_vTerrainMax,
387 mainReport.m_aAllEntityPositions))
388 {
389 Print("Cannot create image " + filePath, LogLevel.WARNING);
390 }
391 }
392 else
393// if (m_iHeatmapType == TYPE_VARIETY)
394 {
395 if (!CreateVarietyImage(
396 filePath,
397 m_iHeatmapDefinition,
398 mainReport.m_WorldInfo.m_vTerrainMin,
399 mainReport.m_WorldInfo.m_vTerrainMax,
400 mainReport.m_aModels,
401 mainReport.m_aModelPositions))
402 {
403 Print("Cannot create image " + filePath, LogLevel.WARNING);
404 }
405 }
406 }
407 }
408
409 return editorEntitiesCount;
410 }
411
412 //------------------------------------------------------------------------------------------------
418 protected float GetLandAboveWaterRatio(notnull WorldEditorAPI worldEditorAPI, vector terrainOrigin, vector terrainSize, float terrainUnitScale)
419 {
420 if (m_bDebug)
421 {
423 m_DebugShapeManager.Clear();
424 else
426 }
427 else
428 {
429 m_DebugShapeManager = null;
430 }
431
432 float oceanLevel = worldEditorAPI.GetWorld().GetOceanBaseHeight();
433
434 float stepX;
435 float stepZ;
436 if (m_bDebug && terrainUnitScale < MIN_TERRAIN_DEBUG_RESOLUTION)
437 {
438 stepX = MIN_TERRAIN_DEBUG_RESOLUTION;
439 stepZ = MIN_TERRAIN_DEBUG_RESOLUTION;
440 PrintFormat("Debug is enabled - terrain surface tracing is every %1m instead of %2m, losing precision", MIN_TERRAIN_DEBUG_RESOLUTION, terrainUnitScale, level: LogLevel.WARNING);
441 }
442 else
443 {
444 stepX = terrainUnitScale;
445 stepZ = terrainUnitScale;
446 }
447
448 int aslMeasures;
449 int totalMeasures;
450 for (float x = terrainOrigin[0]; x < terrainSize[0]; x += stepX)
451 {
452 for (float z = terrainOrigin[2]; z < terrainSize[2]; z += stepZ)
453 {
454 float y = worldEditorAPI.GetTerrainSurfaceY(x, z);
455 bool isAboveOrEqualWaterLevel = y >= oceanLevel;
456 if (isAboveOrEqualWaterLevel)
457 ++aslMeasures;
458
459 ++totalMeasures;
460
462 {
463 vector pos2D = { x, 0, z };
464 vector pos3D = { x, y, z };
465
466// if (isAboveOrEqualWaterLevel)
467// m_DebugShapeManager.AddRectangleXZ(pos, 0, stepX, stepZ, Color.GREEN);
468// else
469// m_DebugShapeManager.AddRectangleXZ(pos, 0, stepX, stepZ, Color.BLUE);
470
471 if (isAboveOrEqualWaterLevel)
472 m_DebugShapeManager.AddLine(pos2D, pos3D, Color.GREEN);
473 else
474 m_DebugShapeManager.AddLine(pos2D, pos3D, Color.BLUE);
475 }
476 }
477 }
478
479 if (totalMeasures < 1 || aslMeasures == totalMeasures)
480 return 1; // 100%
481
482 if (aslMeasures < 1)
483 return 0; // 0%
484
485 return aslMeasures / totalMeasures;
486 }
487
488 //------------------------------------------------------------------------------------------------
491 protected array<string> GetReportLines(notnull SCR_WorldEntitiesStatisticsPlugin_Report report)
492 {
493 array<string> lines = {};
494
495 lines.InsertAll(GetFormattedMapLines("Prefab entities", report.m_mPrefabResult, report));
496 lines.InsertAll(GetFormattedMapLines("entities with Prefab path on EntitySource", report.m_mPrefabNoAncestorResult, report));
497 lines.InsertAll(GetFormattedMapLines("entities with empty Prefab path", report.m_mEmptyResult, report));
498 lines.InsertAll(GetFormattedMapLines("generic entities", report.m_mGenericResult, report));
499
500 return lines;
501 }
502
503 //------------------------------------------------------------------------------------------------
507 protected void OutputLines(notnull array<string> lines, string filePath)
508 {
509 if (lines.IsEmpty())
510 return;
511
512 if (m_bOutputToFile && filePath) // write to txt
513 {
514 if ((!FileIO.FileExists(filePath) || FileIO.DeleteFile(filePath)) && SCR_FileIOHelper.WriteFileContent(filePath, lines))
515 {
516 Print("Successfully wrote lines to file", LogLevel.DEBUG);
517 return;
518 }
519
520 Print("Export failed: cannot write to file - printing instead", LogLevel.WARNING);
521 }
522 // print to log console
523 foreach (string line : lines)
524 {
525 Print("" + line, LogLevel.NORMAL);
526 }
527
528 Print("\n", LogLevel.NORMAL); // or Print("" + SCR_StringHelper.LINE_RETURN, LogLevel.NORMAL);
529 }
530
531 //------------------------------------------------------------------------------------------------
535 protected array<string> GetFormattedMapLines(string description, notnull map<ResourceName, int> resultMap, notnull SCR_WorldEntitiesStatisticsPlugin_Report report)
536 {
537 int count = resultMap.Count();
538 if (count < 1)
539 return { string.Format(NO_MAP_CONTENT_FORMAT, description) };
540
541 array<string> resultLines = {};
542
543 array<int> values = {};
544 values.Reserve(count);
545 int entitiesCount;
546
547 // standard entities
548
549 foreach (string key, int value : resultMap)
550 {
551 values.Insert(value);
552 entitiesCount += value;
553 }
554
555 float entityDensity;
556 if (report.m_WorldInfo.m_fTerrainSurface > 0)
557 entityDensity = entitiesCount / (report.m_WorldInfo.m_fTerrainSurface * SQM_TO_SQKM);
558
559 resultLines.Insert(
560 string.Format(
561 "Found and processed %1 %2 (density: %3/km² on land, %4/km² in water, %5/km² total)", // no ':' colon as the following lines can be hidden by filters
562 entitiesCount,
563 description,
564 (entityDensity * report.m_WorldInfo.m_fTerrainSurfaceASLRatio).ToString(lenDec: 2),
565 (entityDensity * (1 - report.m_WorldInfo.m_fTerrainSurfaceASLRatio)).ToString(lenDec: 2),
566 (entityDensity).ToString(lenDec: 2)
567 ));
568
569 values.Sort(true); // DESC
570 if (m_iMaxDisplayedEntries > 0 && values.Count() > m_iMaxDisplayedEntries)
571 values.Resize(m_iMaxDisplayedEntries);
572
573 foreach (int value : values)
574 {
575 if (value < m_iDisplayThreshold)
576 break;
577
578 ResourceName key;
579 foreach (ResourceName mapKey, int mapValue : resultMap)
580 {
581 if (mapValue == value)
582 {
583 key = mapKey;
584 resultMap.Remove(key); // fix duplicates
585 resultLines.Insert(string.Format("%1× %2", value, key));
586 break;
587 }
588 }
589
590 if (!key) // !.IsEmpty()
591 Print("empty key found", LogLevel.WARNING);
592 }
593
594 return resultLines;
595 }
596
597 //------------------------------------------------------------------------------------------------
605 protected bool CreateDensityImage(string imagePath, int resolution, vector terrainMin, vector terrainMax, notnull array<vector> entityPositions)
606 {
607 if (resolution < 1)
608 {
609 PrintFormat("Invalid resolution (%1×%1)", resolution, level: LogLevel.WARNING);
610 return false;
611 }
612
613 if (terrainMin[0] >= terrainMax[0]
614 // || terrainMin[1] >= terrainMax[1]
615 || terrainMin[2] >= terrainMax[2])
616 {
617 PrintFormat("Invalid terrain min/max (%1 & %2)", terrainMin, terrainMax, level: LogLevel.WARNING);
618 return false;
619 }
620
621 float pixelWidth = (terrainMax[0] - terrainMin[0]) / resolution;
622 float pixelHeight = (terrainMax[2] - terrainMin[2]) / resolution;
623
624 if (pixelWidth < 0.1 || pixelHeight < 0.1)
625 {
626 PrintFormat("Invalid pixel size (%1×%2)", pixelWidth, pixelHeight, level: LogLevel.WARNING);
627 return false;
628 }
629
630 array<int> imageData = {}; // temporarily used to store entity count first
631 imageData.Resize(resolution * resolution);
632
633 Debug.BeginTimeMeasure();
634
635 foreach (vector entityPos : entityPositions)
636 {
637 // do not round - pixel 0,0 is for 0..0.99 entities
638
639 entityPos -= terrainMin;
640
641 int x = entityPos[0] / pixelWidth;
642 if (x < 0 || x >= resolution)
643 continue;
644
645 int z = resolution - entityPos[2] / pixelHeight;
646 if (z < 0 || z >= resolution)
647 continue;
648
649 imageData[x + resolution * z] = imageData[x + resolution * z] + 1;
650 }
651
652 Debug.EndTimeMeasure(
653 string.Format(
654 "Processing all %1 entity positions for image creation (%2×%2 = %3 pixels)",
655 entityPositions.Count(),
656 resolution,
657 resolution * resolution));
658
659 return SCR_HeatmapHelper.CreateHeatmapImageFromData(imagePath, imageData, m_iHeatmapColourMode, m_bHeatmapValueInversion, m_iHeatmapMaxValueMode, m_bHeatmapHighlightValuesAboveMax, m_iHeatmapResolutionFactor);
660 }
661
662 //------------------------------------------------------------------------------------------------
671 protected bool CreateVarietyImage(string imagePath, int resolution, vector terrainMin, vector terrainMax, notnull array<ResourceName> models, notnull array<vector> prefabPositions)
672 {
673 if (resolution < 1)
674 {
675 PrintFormat("Invalid resolution (%1×%1)", resolution, level: LogLevel.WARNING);
676 return false;
677 }
678
679 if (terrainMin[0] >= terrainMax[0]
680 // || terrainMin[1] >= terrainMax[1]
681 || terrainMin[2] >= terrainMax[2])
682 {
683 PrintFormat("Invalid terrain min/max (%1 & %2)", terrainMin, terrainMax, level: LogLevel.WARNING);
684 return false;
685 }
686
687 float pixelWidth = (terrainMax[0] - terrainMin[0]) / resolution;
688 float pixelHeight = (terrainMax[2] - terrainMin[2]) / resolution;
689
690 if (pixelWidth < 0.1 || pixelHeight < 0.1)
691 {
692 PrintFormat("Invalid pixel size (%1×%2)", pixelWidth, pixelHeight, level: LogLevel.WARNING);
693 return false;
694 }
695
696 array<int> imageData = {}; // temporarily used to store entity count first
697 array<ref set<ResourceName>> pixelPrefabs = {};
698 imageData.Resize(resolution * resolution);
699 pixelPrefabs.Resize(resolution * resolution);
700
701 Debug.BeginTimeMeasure();
702
703 set<ResourceName> modelsSet;
704 foreach (int i, vector prefabPosition : prefabPositions)
705 {
706 // do not round - pixel 0,0 is for 0..0.99 entities
707
708 prefabPosition -= terrainMin;
709
710 int x = prefabPosition[0] / pixelWidth;
711 if (x < 0 || x >= resolution)
712 continue;
713
714 int z = resolution - prefabPosition[2] / pixelHeight;
715 if (z < 0 || z >= resolution)
716 continue;
717
718 modelsSet = pixelPrefabs[x + resolution * z];
719 if (!modelsSet)
720 {
721 modelsSet = new set<ResourceName>();
722 pixelPrefabs[x + resolution * z] = modelsSet;
723 }
724
725 modelsSet.Insert(models[i]);
726
727 int value = modelsSet.Count();
728 imageData[x + resolution * z] = value;
729 }
730
731 Debug.EndTimeMeasure(
732 string.Format(
733 "Processing all %1 entity positions for image creation (%2×%2 = %3 pixels)",
734 prefabPositions.Count(),
735 resolution,
736 resolution * resolution));
737
738 return SCR_HeatmapHelper.CreateHeatmapImageFromData(imagePath, imageData);
739 }
740
741 //------------------------------------------------------------------------------------------------
742 [ButtonAttribute("Analyse world", true)]
743 protected int ButtonAnalyse()
744 {
745 return 1;
746 }
747
748 //------------------------------------------------------------------------------------------------
749 [ButtonAttribute("Close")]
750 protected int ButtonClose()
751 {
752 return 0;
753 }
754}
755
756class SCR_WorldEntitiesStatisticsPlugin_Report_World
757{
758 float m_fTerrainSurface;
759 float m_fTerrainSurfaceASLRatio;
760 vector m_vTerrainMin;
761 vector m_vTerrainMax;
762}
763
764class SCR_WorldEntitiesStatisticsPlugin_Report
765{
766 ref SCR_WorldEntitiesStatisticsPlugin_Report_World m_WorldInfo;
767
768 int m_iSubSceneId;
769 string m_sSubSceneName;
770
771 int m_iLayerId;
772 string m_sLayerName;
773
774 // density
775 ref array<vector> m_aAllEntityPositions = {};
776
777 // variety
778 ref array<ResourceName> m_aModels = {};
779 ref array<vector> m_aModelPositions = {};
780
781 // statis
782 ref map<ResourceName, int> m_mPrefabResult = new map<ResourceName, int>();
783 ref map<ResourceName, int> m_mPrefabNoAncestorResult = new map<ResourceName, int>();
784 ref map<ResourceName, int> m_mEmptyResult = new map<ResourceName, int>();
785 ref map<ResourceName, int> m_mGenericResult = new map<ResourceName, int>(); // should ideally be map<string, int>
786
787 // TODO?
788// ref map<ResourceName, ref array<ref array<int>>> m_mPrefabsPresenceMap = new map<ResourceName, ref array<ref array<int>>>();
789}
790
791class SCR_WorldEntitiesStatisticsPlugin_Item
792{
793 string m_sClassName;
794 ResourceName m_sResourceName;
795
796 int m_iSubScene;
797 int m_iLayer;
798
799 vector m_vPosition;
800}
801#endif // WORKBENCH
GenerateFlowMaps WorkbenchPlugin WorkbenchPluginAttribute("Regenerate river flow-maps", "Generate and save/overwrite river flow-maps", "", "", {"WorldEditor"}, "", 0xf773)
Definition FlowmapTool.c:59
ResourceName resourceName
Definition SCR_AIGroup.c:66
vector m_vPosition
EDamageType type
override void Run()
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
ref SCR_DebugShapeManager m_DebugShapeManager
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
Definition Color.c:13
Definition Debug.c:13
proto external vector GetOrigin()
static bool WriteFileContent(string filePath, notnull array< string > lines)
static string GetResolutionFactorEnum(array< int > multipliers=null)
static bool CreateHeatmapImageFromData(string imagePath, notnull array< int > imageData, int colourMode=COLOUR_MODE_GREYSCALE, bool invertColour=false, int maxValueMode=MAX_MODE_RAW, bool highlightValuesAboveMax=false, int resolutionFactor=1)
static ParamEnumArray FromString(string input)
Definition Types.c:486
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
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.