12 description:
"Detect rough vertices on terrain.",
14 wbModules: {
"WorldEditor" },
16 awesomeFontCode: 0xF1B3)]
17class SCR_TerrainEdgesFinderPlugin : WorldEditorPlugin
19 [
Attribute(defvalue:
"75",
desc:
"Min angle difference between three vertices [deg]",
params:
"0.01 179.99 0.01")]
20 protected float m_fMinAngleDifference;
25 [
Attribute(defvalue:
"33", uiwidget:
UIWidgets.Slider,
desc:
"Trace to find if an entity is hiding the bad vertice (0 = no trace)",
params:
"0 100 0.1")]
26 protected float m_fEntityTraceOffset;
28 [
Attribute(defvalue:
"0",
desc:
"Force reobtaining terrain's elevation - useful after terrain edits")]
29 protected bool m_bForceHeightmapRefresh;
32 [
Attribute(defvalue:
"1",
desc:
"Output coordinates as Workbench links")]
33 protected bool m_bOutputAsWorkbenchLinks;
35 [
Attribute(defvalue:
"1",
desc:
"Draw arrows pointing at result vertices")]
38 protected ref array<ref array<ref array<float>>> m_aHeightmaps;
39 protected string m_sLastLoadedWorld;
43 protected static const int MODE_ABOVE_WATER = 1;
44 protected static const int MODE_UNDER_WATER = 2;
46 protected static const float DEBUG_SHAPE_HEIGHT = 20;
48 protected static const string PLUGIN_NAME =
"Terrain Edges Finder";
51 override protected void Run()
53 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
55 worldEditorAPI.GetWorldPath(worldPath);
63 if (!SCR_WorldEditorToolHelper.HasTerrainMesh())
69 if (Workbench.ScriptDialog(PLUGIN_NAME,
"",
this) == 0)
72 if (m_bForceHeightmapRefresh || !m_aHeightmaps || m_sLastLoadedWorld != worldPath)
74 WBProgressDialog progress =
new WBProgressDialog(
"Gathering terrain heightmap, please wait...", Workbench.GetModule(WorldEditor));
75 progress.SetProgress(0.42);
76 Debug.BeginTimeMeasure();
77 m_aHeightmaps = GetTerrainHeightmaps();
78 Debug.EndTimeMeasure(
"Obtaining heightmap as one array");
85 worldEditorAPI.GetWorldPath(m_sLastLoadedWorld);
89 foreach (array<ref array<float>> cols : m_aHeightmaps)
91 foreach (array<float> yValues : cols)
93 verticesCount += yValues.Count();
97 if (Workbench.ScriptDialog(PLUGIN_NAME,
"You are about to process " + verticesCount +
" vertices; continue?",
new WorkbenchDialog_OKCancel()) == 0)
100 array<vector> resultPositions = {};
101 array<float> resultAngles = {};
102 GetSuspectVertices(resultPositions, resultAngles);
104 int count = resultPositions.Count();
113 if (Workbench.ScriptDialog(PLUGIN_NAME,
"The tool is about to output " + count +
" entries - this may come from an invalid (too small?) angle set.\nContinue (this may take some time)?",
new WorkbenchDialog_OKCancel()) == 0)
119 foreach (
int i,
float angle : resultAngles)
121 if (maxAngle < angle)
128 float step = worldEditorAPI.GetTerrainUnitScale();
132 string worldPathNoFS;
133 if (m_bOutputAsWorkbenchLinks)
134 worldPathNoFS = SCR_AddonTool.StripFileSystem(worldPath);
137 string resultStr =
string.Format(
138 "World: %1\n%2 suspect vertices (angle > %3 degrees):",
145 resultStr +=
"\n" + resultAngles[i].ToString(6, 2) +
"deg at ";
146 if (m_bOutputAsWorkbenchLinks)
147 resultStr +=
string.Format(
"enfusion://WorldEditor/%1;%2,%3,%4;-70.12,315,0", worldPathNoFS,
position[0] + 1.808,
position[1] + 5,
position[2] - 1.808);
167 "Max angle position: %1 degrees at %2",
169 Debug.GetPositionLinkString(resultPositions[maxAngleIndex], 0),
172 for (
int i; i < printCount; ++i)
182 protected array<ref array<ref array<float>>> GetTerrainHeightmaps()
184 const int terrainIndex = 0;
186 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
190 int tilesCountX = worldEditorAPI.GetTerrainTilesX(terrainIndex);
194 int tilesCountY = worldEditorAPI.GetTerrainTilesY(terrainIndex);
196 array<ref array<ref array<float>>> result = {};
197 result.Resize(tilesCountX);
198 array<float> tileTempArray;
199 for (
int tileX; tileX < tilesCountY; ++tileX)
202 result[tileX].Resize(tilesCountY);
203 for (
int tileY; tileY < tilesCountY; ++tileY)
206 worldEditorAPI.GetTerrainSurfaceTile(terrainIndex, tileX, tileY, tileTempArray);
207 result[tileX][tileY] = tileTempArray;
215 protected void GetSuspectVertices(notnull out array<vector> vertices, out notnull array<float>
angles)
217 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
219 BaseWorld world = worldEditorAPI.GetWorld();
222 bool hasOcean = world.IsOcean();
223 if (!hasOcean &&
m_eMode == MODE_UNDER_WATER)
235 oceanLevel = world.GetOceanBaseHeight();
237 const int terrainIndex = 0;
238 int terrainResolutionX = worldEditorAPI.GetTerrainResolutionX(terrainIndex);
239 int terrainResolutionY = worldEditorAPI.GetTerrainResolutionY(terrainIndex);
240 int terrainResolutionXMinus1 = terrainResolutionX - 1;
241 int terrainResolutionYMinus1 = terrainResolutionY - 1;
242 int tileSizeX = terrainResolutionXMinus1 / worldEditorAPI.GetTerrainTilesX(terrainIndex) + 1;
243 int tileSizeZ = terrainResolutionYMinus1 / worldEditorAPI.GetTerrainTilesY(terrainIndex) + 1;
244 int tileSizeXMinus1 = tileSizeX - 1;
245 int tileSizeZMinus1 = tileSizeZ - 1;
246 float step = worldEditorAPI.GetTerrainUnitScale(terrainIndex);
248 int totalPoints = (terrainResolutionXMinus1 - 1) * (terrainResolutionYMinus1 - 1);
251 if (m_fEntityTraceOffset > 0)
260 WBProgressDialog progress =
new WBProgressDialog(
"Comparing angles...", worldEditor);
262 float minAngleDifferenceRad = m_fMinAngleDifference *
Math.DEG2RAD;
264 int progressStep, progressStepLimit = totalPoints * 0.01;
265 Debug.BeginTimeMeasure();
268 array<float> leftTile;
269 array<float> belowTile;
270 foreach (
int tileX, array<ref array<float>> xTiles : m_aHeightmaps)
272 foreach (
int tileY, array<float> tileHeightmap : xTiles)
274 for (
int z; z < tileSizeZMinus1; ++z)
281 belowTile = m_aHeightmaps[tileX][tileY - 1];
284 for (
int x; x < tileSizeXMinus1; ++x)
294 if (progressStep >= progressStepLimit)
296 progress.SetProgress(progressCount / totalPoints);
300 int currIndex = x + z * tileSizeX;
302 float currY = tileHeightmap[currIndex];
304 if (mode == MODE_ABOVE_WATER)
306 if (currY < oceanLevel)
310 if (mode == MODE_UNDER_WATER)
312 if (currY >= oceanLevel)
317 (x + tileX * tileSizeXMinus1) * step,
319 (z + tileY * tileSizeZMinus1) * step
328 leftTile = m_aHeightmaps[tileX - 1][tileY];
329 leftY = leftTile[tileSizeXMinus1 + z * tileSizeX];
333 leftY = tileHeightmap[currIndex - 1];
336 float rightY = tileHeightmap[currIndex + 1];
340 belowY = belowTile[x + tileSizeZMinus1 * tileSizeX];
342 belowY = tileHeightmap[currIndex - tileSizeX];
344 float aboveY = tileHeightmap[currIndex + tileSizeX];
346 if (IsAngleBad(pos, outAngle, leftY, rightY, step, minAngleDifferenceRad, traceParam, world))
348 vertices.Insert(pos);
354 if (IsAngleBad(pos, outAngle, belowY, aboveY, step, minAngleDifferenceRad, traceParam, world))
356 vertices.Insert(pos);
365 Debug.EndTimeMeasure(
"Comparing angles");
385 float minAngleDifferenceRad,
389 float anglePrevToCurr =
Math.Atan2(
position[1] - prevY, step);
390 float angleCurrToNext =
Math.Atan2(nextY -
position[1], step);
393 if (anglePrevToCurr > angleCurrToNext)
394 difference = anglePrevToCurr - angleCurrToNext;
396 difference = angleCurrToNext - anglePrevToCurr;
399 difference = -difference;
401 if (difference < minAngleDifferenceRad)
406 traceParam.Start =
position + { 0, m_fEntityTraceOffset, 0 };
409 if (world.TraceMove(traceParam) != 1)
413 angle = difference *
Math.RAD2DEG;
420 protected int ButtonProcess()
ref array< string > angles
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
ref SCR_DebugShapeManager m_DebugShapeManager
EWeaponGroupFireMode m_eMode
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
static ParamEnumArray FromString(string input)
static void PrintDialog(string message, string caption="", LogLevel level=LogLevel.WARNING)
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
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.