RecastRasterization.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /*
  2. Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
  3. recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
  4. DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
  5. This software is provided 'as-is', without any express or implied
  6. warranty. In no event will the authors be held liable for any damages
  7. arising from the use of this software.
  8. Permission is granted to anyone to use this software for any purpose,
  9. including commercial applications, and to alter it and redistribute it
  10. freely, subject to the following restrictions:
  11. 1. The origin of this software must not be misrepresented; you must not
  12. claim that you wrote the original software. If you use this software
  13. in a product, an acknowledgment in the product documentation would be
  14. appreciated but is not required.
  15. 2. Altered source versions must be plainly marked as such, and must not be
  16. misrepresented as being the original software.
  17. 3. This notice may not be removed or altered from any source distribution.
  18. */
  19. using System;
  20. using DotRecast.Core;
  21. using static DotRecast.Core.RcMath;
  22. using static DotRecast.Recast.RcConstants;
  23. namespace DotRecast.Recast
  24. {
  25. public static class RecastRasterization
  26. {
  27. /**
  28. * Check whether two bounding boxes overlap
  29. *
  30. * @param amin
  31. * Min axis extents of bounding box A
  32. * @param amax
  33. * Max axis extents of bounding box A
  34. * @param bmin
  35. * Min axis extents of bounding box B
  36. * @param bmax
  37. * Max axis extents of bounding box B
  38. * @returns true if the two bounding boxes overlap. False otherwise
  39. */
  40. private static bool OverlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax)
  41. {
  42. bool overlap = true;
  43. overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
  44. overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
  45. overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
  46. return overlap;
  47. }
  48. private static bool OverlapBounds(RcVec3f amin, RcVec3f amax, RcVec3f bmin, RcVec3f bmax)
  49. {
  50. bool overlap = true;
  51. overlap = (amin.x > bmax.x || amax.x < bmin.x) ? false : overlap;
  52. overlap = (amin.y > bmax.y || amax.y < bmin.y) ? false : overlap;
  53. overlap = (amin.z > bmax.z || amax.z < bmin.z) ? false : overlap;
  54. return overlap;
  55. }
  56. /// Adds a span to the heightfield. If the new span overlaps existing spans,
  57. /// it will merge the new span with the existing ones.
  58. ///
  59. /// @param[in] heightfield Heightfield to add spans to
  60. /// @param[in] x The new span's column cell x index
  61. /// @param[in] z The new span's column cell z index
  62. /// @param[in] min The new span's minimum cell index
  63. /// @param[in] max The new span's maximum cell index
  64. /// @param[in] areaID The new span's area type ID
  65. /// @param[in] flagMergeThreshold How close two spans maximum extents need to be to merge area type IDs
  66. public static void AddSpan(RcHeightfield heightfield, int x, int y, int spanMin, int spanMax, int areaId, int flagMergeThreshold)
  67. {
  68. int idx = x + y * heightfield.width;
  69. RcSpan s = new RcSpan();
  70. s.smin = spanMin;
  71. s.smax = spanMax;
  72. s.area = areaId;
  73. s.next = null;
  74. // Empty cell, add the first span.
  75. if (heightfield.spans[idx] == null)
  76. {
  77. heightfield.spans[idx] = s;
  78. return;
  79. }
  80. RcSpan prev = null;
  81. RcSpan cur = heightfield.spans[idx];
  82. // Insert and merge spans.
  83. while (cur != null)
  84. {
  85. if (cur.smin > s.smax)
  86. {
  87. // Current span is further than the new span, break.
  88. break;
  89. }
  90. else if (cur.smax < s.smin)
  91. {
  92. // Current span is before the new span advance.
  93. prev = cur;
  94. cur = cur.next;
  95. }
  96. else
  97. {
  98. // Merge spans.
  99. if (cur.smin < s.smin)
  100. s.smin = cur.smin;
  101. if (cur.smax > s.smax)
  102. s.smax = cur.smax;
  103. // Merge flags.
  104. if (Math.Abs(s.smax - cur.smax) <= flagMergeThreshold)
  105. s.area = Math.Max(s.area, cur.area);
  106. // Remove current span.
  107. RcSpan next = cur.next;
  108. if (prev != null)
  109. prev.next = next;
  110. else
  111. heightfield.spans[idx] = next;
  112. cur = next;
  113. }
  114. }
  115. // Insert new span.
  116. if (prev != null)
  117. {
  118. s.next = prev.next;
  119. prev.next = s;
  120. }
  121. else
  122. {
  123. s.next = heightfield.spans[idx];
  124. heightfield.spans[idx] = s;
  125. }
  126. }
  127. /// Divides a convex polygon of max 12 vertices into two convex polygons
  128. /// across a separating axis.
  129. ///
  130. /// @param[in] inVerts The input polygon vertices
  131. /// @param[in] inVertsCount The number of input polygon vertices
  132. /// @param[out] outVerts1 Resulting polygon 1's vertices
  133. /// @param[out] outVerts1Count The number of resulting polygon 1 vertices
  134. /// @param[out] outVerts2 Resulting polygon 2's vertices
  135. /// @param[out] outVerts2Count The number of resulting polygon 2 vertices
  136. /// @param[in] axisOffset THe offset along the specified axis
  137. /// @param[in] axis The separating axis
  138. private static void DividePoly(float[] inVerts, int inVertsOffset, int inVertsCount,
  139. int outVerts1, out int outVerts1Count,
  140. int outVerts2, out int outVerts2Count,
  141. float axisOffset, int axis)
  142. {
  143. float[] d = new float[12];
  144. // How far positive or negative away from the separating axis is each vertex.
  145. for (int inVert = 0; inVert < inVertsCount; ++inVert)
  146. {
  147. d[inVert] = axisOffset - inVerts[inVertsOffset + inVert * 3 + axis];
  148. }
  149. int poly1Vert = 0;
  150. int poly2Vert = 0;
  151. for (int inVertA = 0, inVertB = inVertsCount - 1; inVertA < inVertsCount; inVertB = inVertA, ++inVertA)
  152. {
  153. bool ina = d[inVertB] >= 0;
  154. bool inb = d[inVertA] >= 0;
  155. if (ina != inb)
  156. {
  157. float s = d[inVertB] / (d[inVertB] - d[inVertA]);
  158. inVerts[outVerts1 + poly1Vert * 3 + 0] = inVerts[inVertsOffset + inVertB * 3 + 0] +
  159. (inVerts[inVertsOffset + inVertA * 3 + 0] - inVerts[inVertsOffset + inVertB * 3 + 0]) * s;
  160. inVerts[outVerts1 + poly1Vert * 3 + 1] = inVerts[inVertsOffset + inVertB * 3 + 1] +
  161. (inVerts[inVertsOffset + inVertA * 3 + 1] - inVerts[inVertsOffset + inVertB * 3 + 1]) * s;
  162. inVerts[outVerts1 + poly1Vert * 3 + 2] = inVerts[inVertsOffset + inVertB * 3 + 2] +
  163. (inVerts[inVertsOffset + inVertA * 3 + 2] - inVerts[inVertsOffset + inVertB * 3 + 2]) * s;
  164. RcVec3f.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, outVerts1 + poly1Vert * 3);
  165. poly1Vert++;
  166. poly2Vert++;
  167. // add the i'th point to the right polygon. Do NOT add points that are on the dividing line
  168. // since these were already added above
  169. if (d[inVertA] > 0)
  170. {
  171. RcVec3f.Copy(inVerts, outVerts1 + poly1Vert * 3, inVerts, inVertsOffset + inVertA * 3);
  172. poly1Vert++;
  173. }
  174. else if (d[inVertA] < 0)
  175. {
  176. RcVec3f.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, inVertsOffset + inVertA * 3);
  177. poly2Vert++;
  178. }
  179. }
  180. else // same side
  181. {
  182. // add the i'th point to the right polygon. Addition is done even for points on the dividing line
  183. if (d[inVertA] >= 0)
  184. {
  185. RcVec3f.Copy(inVerts, outVerts1 + poly1Vert * 3, inVerts, inVertsOffset + inVertA * 3);
  186. poly1Vert++;
  187. if (d[inVertA] != 0)
  188. continue;
  189. }
  190. RcVec3f.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, inVertsOffset + inVertA * 3);
  191. poly2Vert++;
  192. }
  193. }
  194. outVerts1Count = poly1Vert;
  195. outVerts2Count = poly2Vert;
  196. }
  197. /// Rasterize a single triangle to the heightfield.
  198. ///
  199. /// This code is extremely hot, so much care should be given to maintaining maximum perf here.
  200. ///
  201. /// @param[in] v0 Triangle vertex 0
  202. /// @param[in] v1 Triangle vertex 1
  203. /// @param[in] v2 Triangle vertex 2
  204. /// @param[in] areaID The area ID to assign to the rasterized spans
  205. /// @param[in] heightfield Heightfield to rasterize into
  206. /// @param[in] heightfieldBBMin The min extents of the heightfield bounding box
  207. /// @param[in] heightfieldBBMax The max extents of the heightfield bounding box
  208. /// @param[in] cellSize The x and z axis size of a voxel in the heightfield
  209. /// @param[in] inverseCellSize 1 / cellSize
  210. /// @param[in] inverseCellHeight 1 / cellHeight
  211. /// @param[in] flagMergeThreshold The threshold in which area flags will be merged
  212. /// @returns true if the operation completes successfully. false if there was an error adding spans to the heightfield.
  213. private static void RasterizeTri(float[] verts, int v0, int v1, int v2, int area, RcHeightfield heightfield,
  214. RcVec3f heightfieldBBMin, RcVec3f heightfieldBBMax,
  215. float cellSize, float inverseCellSize, float inverseCellHeight,
  216. int flagMergeThreshold)
  217. {
  218. RcVec3f tmin = new RcVec3f();
  219. RcVec3f tmax = new RcVec3f();
  220. float by = heightfieldBBMax.y - heightfieldBBMin.y;
  221. // Calculate the bounding box of the triangle.
  222. RcVec3f.Copy(ref tmin, verts, v0 * 3);
  223. RcVec3f.Copy(ref tmax, verts, v0 * 3);
  224. tmin.Min(verts, v1 * 3);
  225. tmin.Min(verts, v2 * 3);
  226. tmax.Max(verts, v1 * 3);
  227. tmax.Max(verts, v2 * 3);
  228. // If the triangle does not touch the bbox of the heightfield, skip the triagle.
  229. if (!OverlapBounds(heightfieldBBMin, heightfieldBBMax, tmin, tmax))
  230. return;
  231. // Calculate the footprint of the triangle on the grid's y-axis
  232. int z0 = (int)((tmin.z - heightfieldBBMin.z) * inverseCellSize);
  233. int z1 = (int)((tmax.z - heightfieldBBMin.z) * inverseCellSize);
  234. int w = heightfield.width;
  235. int h = heightfield.height;
  236. // use -1 rather than 0 to cut the polygon properly at the start of the tile
  237. z0 = Clamp(z0, -1, h - 1);
  238. z1 = Clamp(z1, 0, h - 1);
  239. // Clip the triangle into all grid cells it touches.
  240. float[] buf = new float[7 * 3 * 4];
  241. int @in = 0;
  242. int inRow = 7 * 3;
  243. int p1 = inRow + 7 * 3;
  244. int p2 = p1 + 7 * 3;
  245. RcVec3f.Copy(buf, 0, verts, v0 * 3);
  246. RcVec3f.Copy(buf, 3, verts, v1 * 3);
  247. RcVec3f.Copy(buf, 6, verts, v2 * 3);
  248. int nvRow, nvIn = 3;
  249. for (int z = z0; z <= z1; ++z)
  250. {
  251. // Clip polygon to row. Store the remaining polygon as well
  252. float cellZ = heightfieldBBMin.z + z * cellSize;
  253. DividePoly(buf, @in, nvIn, inRow, out nvRow, p1, out nvIn, cellZ + cellSize, 2);
  254. (@in, p1) = (p1, @in);
  255. if (nvRow < 3)
  256. continue;
  257. if (z < 0)
  258. {
  259. continue;
  260. }
  261. // find the horizontal bounds in the row
  262. float minX = buf[inRow], maxX = buf[inRow];
  263. for (int i = 1; i < nvRow; ++i)
  264. {
  265. float v = buf[inRow + i * 3];
  266. minX = Math.Min(minX, v);
  267. maxX = Math.Max(maxX, v);
  268. }
  269. int x0 = (int)((minX - heightfieldBBMin.x) * inverseCellSize);
  270. int x1 = (int)((maxX - heightfieldBBMin.x) * inverseCellSize);
  271. if (x1 < 0 || x0 >= w)
  272. {
  273. continue;
  274. }
  275. x0 = Clamp(x0, -1, w - 1);
  276. x1 = Clamp(x1, 0, w - 1);
  277. int nv, nv2 = nvRow;
  278. for (int x = x0; x <= x1; ++x)
  279. {
  280. // Clip polygon to column. store the remaining polygon as well
  281. float cx = heightfieldBBMin.x + x * cellSize;
  282. DividePoly(buf, inRow, nv2, p1, out nv, p2, out nv2, cx + cellSize, 0);
  283. (inRow, p2) = (p2, inRow);
  284. if (nv < 3)
  285. continue;
  286. if (x < 0)
  287. {
  288. continue;
  289. }
  290. // Calculate min and max of the span.
  291. float spanMin = buf[p1 + 1];
  292. float spanMax = buf[p1 + 1];
  293. for (int i = 1; i < nv; ++i)
  294. {
  295. spanMin = Math.Min(spanMin, buf[p1 + i * 3 + 1]);
  296. spanMax = Math.Max(spanMax, buf[p1 + i * 3 + 1]);
  297. }
  298. spanMin -= heightfieldBBMin.y;
  299. spanMax -= heightfieldBBMin.y;
  300. // Skip the span if it is outside the heightfield bbox
  301. if (spanMax < 0.0f)
  302. continue;
  303. if (spanMin > by)
  304. continue;
  305. // Clamp the span to the heightfield bbox.
  306. if (spanMin < 0.0f)
  307. spanMin = 0;
  308. if (spanMax > by)
  309. spanMax = by;
  310. // Snap the span to the heightfield height grid.
  311. int spanMinCellIndex = Clamp((int)Math.Floor(spanMin * inverseCellHeight), 0, SPAN_MAX_HEIGHT);
  312. int spanMaxCellIndex = Clamp((int)Math.Ceiling(spanMax * inverseCellHeight), spanMinCellIndex + 1, SPAN_MAX_HEIGHT);
  313. AddSpan(heightfield, x, z, spanMinCellIndex, spanMaxCellIndex, area, flagMergeThreshold);
  314. }
  315. }
  316. }
  317. /**
  318. * Rasterizes a single triangle into the specified heightfield. Calling this for each triangle in a mesh is less
  319. * efficient than calling rasterizeTriangles. No spans will be added if the triangle does not overlap the
  320. * heightfield grid.
  321. *
  322. * @param heightfield
  323. * An initialized heightfield.
  324. * @param verts
  325. * An array with vertex coordinates [(x, y, z) * N]
  326. * @param v0
  327. * Index of triangle vertex 0, will be multiplied by 3 to get vertex coordinates
  328. * @param v1
  329. * Triangle vertex 1 index
  330. * @param v2
  331. * Triangle vertex 2 index
  332. * @param areaId
  333. * The area id of the triangle. [Limit: <= WALKABLE_AREA)
  334. * @param flagMergeThreshold
  335. * The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
  336. * @see Heightfield
  337. */
  338. public static void RasterizeTriangle(RcHeightfield heightfield, float[] verts, int v0, int v1, int v2, int area,
  339. int flagMergeThreshold, RcTelemetry ctx)
  340. {
  341. using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
  342. float inverseCellSize = 1.0f / heightfield.cs;
  343. float inverseCellHeight = 1.0f / heightfield.ch;
  344. RasterizeTri(verts, v0, v1, v2, area, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize,
  345. inverseCellHeight, flagMergeThreshold);
  346. }
  347. /**
  348. * Rasterizes an indexed triangle mesh into the specified heightfield. Spans will only be added for triangles that
  349. * overlap the heightfield grid.
  350. *
  351. * @param heightfield
  352. * An initialized heightfield.
  353. * @param verts
  354. * The vertices. [(x, y, z) * N]
  355. * @param tris
  356. * The triangle indices. [(vertA, vertB, vertC) * nt]
  357. * @param areaIds
  358. * The area id's of the triangles. [Limit: <= WALKABLE_AREA] [Size: numTris]
  359. * @param numTris
  360. * The number of triangles.
  361. * @param flagMergeThreshold
  362. * The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
  363. * @see Heightfield
  364. */
  365. public static void RasterizeTriangles(RcHeightfield heightfield, float[] verts, int[] tris, int[] areaIds, int numTris,
  366. int flagMergeThreshold, RcTelemetry ctx)
  367. {
  368. using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
  369. float inverseCellSize = 1.0f / heightfield.cs;
  370. float inverseCellHeight = 1.0f / heightfield.ch;
  371. for (int triIndex = 0; triIndex < numTris; ++triIndex)
  372. {
  373. int v0 = tris[triIndex * 3 + 0];
  374. int v1 = tris[triIndex * 3 + 1];
  375. int v2 = tris[triIndex * 3 + 2];
  376. RasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
  377. inverseCellSize, inverseCellHeight, flagMergeThreshold);
  378. }
  379. }
  380. /**
  381. * Rasterizes a triangle list into the specified heightfield. Expects each triangle to be specified as three
  382. * sequential vertices of 3 floats. Spans will only be added for triangles that overlap the heightfield grid.
  383. *
  384. * @param heightfield
  385. * An initialized heightfield.
  386. * @param verts
  387. * The vertices. [(x, y, z) * numVerts]
  388. * @param areaIds
  389. * The area id's of the triangles. [Limit: <= WALKABLE_AREA] [Size: numTris]
  390. * @param tris
  391. * The triangle indices. [(vertA, vertB, vertC) * nt]
  392. * @param numTris
  393. * The number of triangles.
  394. * @param flagMergeThreshold
  395. * The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
  396. * @see Heightfield
  397. */
  398. public static void RasterizeTriangles(RcHeightfield heightfield, float[] verts, int[] areaIds, int numTris,
  399. int flagMergeThreshold, RcTelemetry ctx)
  400. {
  401. using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
  402. float inverseCellSize = 1.0f / heightfield.cs;
  403. float inverseCellHeight = 1.0f / heightfield.ch;
  404. for (int triIndex = 0; triIndex < numTris; ++triIndex)
  405. {
  406. int v0 = (triIndex * 3 + 0);
  407. int v1 = (triIndex * 3 + 1);
  408. int v2 = (triIndex * 3 + 2);
  409. RasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
  410. inverseCellSize, inverseCellHeight, flagMergeThreshold);
  411. }
  412. }
  413. }
  414. }