RecastArea.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  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. namespace DotRecast.Recast
  22. {
  23. using static RcConstants;
  24. public static class RecastArea
  25. {
  26. /// Erodes the walkable area within the heightfield by the specified radius.
  27. ///
  28. /// Basically, any spans that are closer to a boundary or obstruction than the specified radius
  29. /// are marked as un-walkable.
  30. ///
  31. /// This method is usually called immediately after the heightfield has been built.
  32. ///
  33. /// @see rcCompactHeightfield, rcBuildCompactHeightfield, rcConfig::walkableRadius
  34. /// @ingroup recast
  35. ///
  36. /// @param[in,out] context The build context to use during the operation.
  37. /// @param[in] erosionRadius The radius of erosion. [Limits: 0 < value < 255] [Units: vx]
  38. /// @param[in,out] compactHeightfield The populated compact heightfield to erode.
  39. /// @returns True if the operation completed successfully.
  40. public static void ErodeWalkableArea(RcTelemetry context, int erosionRadius, RcCompactHeightfield compactHeightfield)
  41. {
  42. int xSize = compactHeightfield.width;
  43. int zSize = compactHeightfield.height;
  44. int zStride = xSize; // For readability
  45. using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_ERODE_AREA);
  46. int[] distanceToBoundary = new int[compactHeightfield.spanCount];
  47. Array.Fill(distanceToBoundary, 255);
  48. // Mark boundary cells.
  49. for (int z = 0; z < zSize; ++z)
  50. {
  51. for (int x = 0; x < xSize; ++x)
  52. {
  53. RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
  54. for (int spanIndex = cell.index, maxSpanIndex = cell.index + cell.count; spanIndex < maxSpanIndex; ++spanIndex)
  55. {
  56. if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
  57. {
  58. distanceToBoundary[spanIndex] = 0;
  59. }
  60. else
  61. {
  62. RcCompactSpan span = compactHeightfield.spans[spanIndex];
  63. // Check that there is a non-null adjacent span in each of the 4 cardinal directions.
  64. int neighborCount = 0;
  65. for (int direction = 0; direction < 4; ++direction)
  66. {
  67. int neighborConnection = RecastCommon.GetCon(span, direction);
  68. if (neighborConnection == RC_NOT_CONNECTED)
  69. {
  70. break;
  71. }
  72. int neighborX = x + RecastCommon.GetDirOffsetX(direction);
  73. int neighborZ = z + RecastCommon.GetDirOffsetY(direction);
  74. int neighborSpanIndex = compactHeightfield.cells[neighborX + neighborZ * zStride].index + RecastCommon.GetCon(span, direction);
  75. if (compactHeightfield.areas[neighborSpanIndex] == RC_NULL_AREA)
  76. {
  77. break;
  78. }
  79. neighborCount++;
  80. }
  81. // At least one missing neighbour, so this is a boundary cell.
  82. if (neighborCount != 4)
  83. {
  84. distanceToBoundary[spanIndex] = 0;
  85. }
  86. }
  87. }
  88. }
  89. }
  90. int newDistance;
  91. // Pass 1
  92. for (int z = 0; z < zSize; ++z)
  93. {
  94. for (int x = 0; x < xSize; ++x)
  95. {
  96. RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
  97. int maxSpanIndex = cell.index + cell.count;
  98. for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
  99. {
  100. RcCompactSpan span = compactHeightfield.spans[spanIndex];
  101. if (RecastCommon.GetCon(span, 0) != RC_NOT_CONNECTED)
  102. {
  103. // (-1,0)
  104. int aX = x + RecastCommon.GetDirOffsetX(0);
  105. int aY = z + RecastCommon.GetDirOffsetY(0);
  106. int aIndex = compactHeightfield.cells[aX + aY * xSize].index + RecastCommon.GetCon(span, 0);
  107. RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
  108. newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
  109. if (newDistance < distanceToBoundary[spanIndex])
  110. {
  111. distanceToBoundary[spanIndex] = newDistance;
  112. }
  113. // (-1,-1)
  114. if (RecastCommon.GetCon(aSpan, 3) != RC_NOT_CONNECTED)
  115. {
  116. int bX = aX + RecastCommon.GetDirOffsetX(3);
  117. int bY = aY + RecastCommon.GetDirOffsetY(3);
  118. int bIndex = compactHeightfield.cells[bX + bY * xSize].index + RecastCommon.GetCon(aSpan, 3);
  119. newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
  120. if (newDistance < distanceToBoundary[spanIndex])
  121. {
  122. distanceToBoundary[spanIndex] = newDistance;
  123. }
  124. }
  125. }
  126. if (RecastCommon.GetCon(span, 3) != RC_NOT_CONNECTED)
  127. {
  128. // (0,-1)
  129. int aX = x + RecastCommon.GetDirOffsetX(3);
  130. int aY = z + RecastCommon.GetDirOffsetY(3);
  131. int aIndex = compactHeightfield.cells[aX + aY * xSize].index + RecastCommon.GetCon(span, 3);
  132. RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
  133. newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
  134. if (newDistance < distanceToBoundary[spanIndex])
  135. {
  136. distanceToBoundary[spanIndex] = newDistance;
  137. }
  138. // (1,-1)
  139. if (RecastCommon.GetCon(aSpan, 2) != RC_NOT_CONNECTED)
  140. {
  141. int bX = aX + RecastCommon.GetDirOffsetX(2);
  142. int bY = aY + RecastCommon.GetDirOffsetY(2);
  143. int bIndex = compactHeightfield.cells[bX + bY * xSize].index + RecastCommon.GetCon(aSpan, 2);
  144. newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
  145. if (newDistance < distanceToBoundary[spanIndex])
  146. {
  147. distanceToBoundary[spanIndex] = newDistance;
  148. }
  149. }
  150. }
  151. }
  152. }
  153. }
  154. // Pass 2
  155. for (int z = zSize - 1; z >= 0; --z)
  156. {
  157. for (int x = xSize - 1; x >= 0; --x)
  158. {
  159. RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
  160. int maxSpanIndex = cell.index + cell.count;
  161. for (int i = cell.index; i < maxSpanIndex; ++i)
  162. {
  163. RcCompactSpan span = compactHeightfield.spans[i];
  164. if (RecastCommon.GetCon(span, 2) != RC_NOT_CONNECTED)
  165. {
  166. // (1,0)
  167. int aX = x + RecastCommon.GetDirOffsetX(2);
  168. int aY = z + RecastCommon.GetDirOffsetY(2);
  169. int aIndex = compactHeightfield.cells[aX + aY * xSize].index + RecastCommon.GetCon(span, 2);
  170. RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
  171. newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
  172. if (newDistance < distanceToBoundary[i])
  173. {
  174. distanceToBoundary[i] = newDistance;
  175. }
  176. // (1,1)
  177. if (RecastCommon.GetCon(aSpan, 1) != RC_NOT_CONNECTED)
  178. {
  179. int bX = aX + RecastCommon.GetDirOffsetX(1);
  180. int bY = aY + RecastCommon.GetDirOffsetY(1);
  181. int bIndex = compactHeightfield.cells[bX + bY * xSize].index + RecastCommon.GetCon(aSpan, 1);
  182. newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
  183. if (newDistance < distanceToBoundary[i])
  184. {
  185. distanceToBoundary[i] = newDistance;
  186. }
  187. }
  188. }
  189. if (RecastCommon.GetCon(span, 1) != RC_NOT_CONNECTED)
  190. {
  191. // (0,1)
  192. int aX = x + RecastCommon.GetDirOffsetX(1);
  193. int aY = z + RecastCommon.GetDirOffsetY(1);
  194. int aIndex = compactHeightfield.cells[aX + aY * xSize].index + RecastCommon.GetCon(span, 1);
  195. RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
  196. newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
  197. if (newDistance < distanceToBoundary[i])
  198. {
  199. distanceToBoundary[i] = newDistance;
  200. }
  201. // (-1,1)
  202. if (RecastCommon.GetCon(aSpan, 0) != RC_NOT_CONNECTED)
  203. {
  204. int bX = aX + RecastCommon.GetDirOffsetX(0);
  205. int bY = aY + RecastCommon.GetDirOffsetY(0);
  206. int bIndex = compactHeightfield.cells[bX + bY * xSize].index + RecastCommon.GetCon(aSpan, 0);
  207. newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
  208. if (newDistance < distanceToBoundary[i])
  209. {
  210. distanceToBoundary[i] = newDistance;
  211. }
  212. }
  213. }
  214. }
  215. }
  216. }
  217. int minBoundaryDistance = erosionRadius * 2;
  218. for (int spanIndex = 0; spanIndex < compactHeightfield.spanCount; ++spanIndex)
  219. {
  220. if (distanceToBoundary[spanIndex] < minBoundaryDistance)
  221. {
  222. compactHeightfield.areas[spanIndex] = RC_NULL_AREA;
  223. }
  224. }
  225. }
  226. /// Applies a median filter to walkable area types (based on area id), removing noise.
  227. ///
  228. /// This filter is usually applied after applying area id's using functions
  229. /// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea.
  230. ///
  231. /// @see rcCompactHeightfield
  232. /// @ingroup recast
  233. ///
  234. /// @param[in,out] context The build context to use during the operation.
  235. /// @param[in,out] compactHeightfield A populated compact heightfield.
  236. /// @returns True if the operation completed successfully.
  237. public static bool MedianFilterWalkableArea(RcTelemetry context, RcCompactHeightfield compactHeightfield)
  238. {
  239. int xSize = compactHeightfield.width;
  240. int zSize = compactHeightfield.height;
  241. int zStride = xSize; // For readability
  242. using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MEDIAN_AREA);
  243. int[] areas = new int[compactHeightfield.spanCount];
  244. for (int z = 0; z < zSize; ++z)
  245. {
  246. for (int x = 0; x < xSize; ++x)
  247. {
  248. RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
  249. int maxSpanIndex = cell.index + cell.count;
  250. for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
  251. {
  252. RcCompactSpan span = compactHeightfield.spans[spanIndex];
  253. if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
  254. {
  255. areas[spanIndex] = compactHeightfield.areas[spanIndex];
  256. continue;
  257. }
  258. int[] neighborAreas = new int[9];
  259. for (int neighborIndex = 0; neighborIndex < 9; ++neighborIndex)
  260. {
  261. neighborAreas[neighborIndex] = compactHeightfield.areas[spanIndex];
  262. }
  263. for (int dir = 0; dir < 4; ++dir)
  264. {
  265. if (RecastCommon.GetCon(span, dir) == RC_NOT_CONNECTED)
  266. {
  267. continue;
  268. }
  269. int aX = x + RecastCommon.GetDirOffsetX(dir);
  270. int aZ = z + RecastCommon.GetDirOffsetY(dir);
  271. int aIndex = compactHeightfield.cells[aX + aZ * zStride].index + RecastCommon.GetCon(span, dir);
  272. if (compactHeightfield.areas[aIndex] != RC_NULL_AREA)
  273. {
  274. neighborAreas[dir * 2 + 0] = compactHeightfield.areas[aIndex];
  275. }
  276. RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
  277. int dir2 = (dir + 1) & 0x3;
  278. int neighborConnection2 = RecastCommon.GetCon(aSpan, dir2);
  279. if (neighborConnection2 != RC_NOT_CONNECTED)
  280. {
  281. int bX = aX + RecastCommon.GetDirOffsetX(dir2);
  282. int bZ = aZ + RecastCommon.GetDirOffsetY(dir2);
  283. int bIndex = compactHeightfield.cells[bX + bZ * zStride].index + RecastCommon.GetCon(aSpan, dir2);
  284. if (compactHeightfield.areas[bIndex] != RC_NULL_AREA)
  285. {
  286. neighborAreas[dir * 2 + 1] = compactHeightfield.areas[bIndex];
  287. }
  288. }
  289. }
  290. //Array.Sort(neighborAreas);
  291. neighborAreas.InsertSort();
  292. areas[spanIndex] = neighborAreas[4];
  293. }
  294. }
  295. }
  296. compactHeightfield.areas = areas;
  297. return true;
  298. }
  299. /// Applies an area id to all spans within the specified bounding box. (AABB)
  300. ///
  301. /// @see rcCompactHeightfield, rcMedianFilterWalkableArea
  302. /// @ingroup recast
  303. ///
  304. /// @param[in,out] context The build context to use during the operation.
  305. /// @param[in] boxMinBounds The minimum extents of the bounding box. [(x, y, z)] [Units: wu]
  306. /// @param[in] boxMaxBounds The maximum extents of the bounding box. [(x, y, z)] [Units: wu]
  307. /// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
  308. /// @param[in,out] compactHeightfield A populated compact heightfield.
  309. public static void MarkBoxArea(RcTelemetry context, float[] boxMinBounds, float[] boxMaxBounds, RcAreaModification areaId, RcCompactHeightfield compactHeightfield)
  310. {
  311. using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_BOX_AREA);
  312. int xSize = compactHeightfield.width;
  313. int zSize = compactHeightfield.height;
  314. int zStride = xSize; // For readability
  315. // Find the footprint of the box area in grid cell coordinates.
  316. int minX = (int)((boxMinBounds[0] - compactHeightfield.bmin.x) / compactHeightfield.cs);
  317. int minY = (int)((boxMinBounds[1] - compactHeightfield.bmin.y) / compactHeightfield.ch);
  318. int minZ = (int)((boxMinBounds[2] - compactHeightfield.bmin.z) / compactHeightfield.cs);
  319. int maxX = (int)((boxMaxBounds[0] - compactHeightfield.bmin.x) / compactHeightfield.cs);
  320. int maxY = (int)((boxMaxBounds[1] - compactHeightfield.bmin.y) / compactHeightfield.ch);
  321. int maxZ = (int)((boxMaxBounds[2] - compactHeightfield.bmin.z) / compactHeightfield.cs);
  322. if (maxX < 0)
  323. {
  324. return;
  325. }
  326. if (minX >= xSize)
  327. {
  328. return;
  329. }
  330. if (maxZ < 0)
  331. {
  332. return;
  333. }
  334. if (minZ >= zSize)
  335. {
  336. return;
  337. }
  338. if (minX < 0)
  339. {
  340. minX = 0;
  341. }
  342. if (maxX >= xSize)
  343. {
  344. maxX = xSize - 1;
  345. }
  346. if (minZ < 0)
  347. {
  348. minZ = 0;
  349. }
  350. if (maxZ >= zSize)
  351. {
  352. maxZ = zSize - 1;
  353. }
  354. for (int z = minZ; z <= maxZ; ++z)
  355. {
  356. for (int x = minX; x <= maxX; ++x)
  357. {
  358. RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
  359. int maxSpanIndex = cell.index + cell.count;
  360. for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
  361. {
  362. RcCompactSpan span = compactHeightfield.spans[spanIndex];
  363. // Skip if the span is outside the box extents.
  364. if (span.y < minY || span.y > maxY)
  365. {
  366. continue;
  367. }
  368. // Skip if the span has been removed.
  369. if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
  370. {
  371. continue;
  372. }
  373. // Mark the span.
  374. compactHeightfield.areas[spanIndex] = areaId.Apply(compactHeightfield.areas[spanIndex]);
  375. }
  376. }
  377. }
  378. }
  379. /// Applies the area id to the all spans within the specified convex polygon.
  380. ///
  381. /// The value of spacial parameters are in world units.
  382. ///
  383. /// The y-values of the polygon vertices are ignored. So the polygon is effectively
  384. /// projected onto the xz-plane, translated to @p minY, and extruded to @p maxY.
  385. ///
  386. /// @see rcCompactHeightfield, rcMedianFilterWalkableArea
  387. /// @ingroup recast
  388. ///
  389. /// @param[in,out] context The build context to use during the operation.
  390. /// @param[in] verts The vertices of the polygon [For: (x, y, z) * @p numVerts]
  391. /// @param[in] numVerts The number of vertices in the polygon.
  392. /// @param[in] minY The height of the base of the polygon. [Units: wu]
  393. /// @param[in] maxY The height of the top of the polygon. [Units: wu]
  394. /// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
  395. /// @param[in,out] compactHeightfield A populated compact heightfield.
  396. public static void MarkConvexPolyArea(RcTelemetry context, float[] verts,
  397. float minY, float maxY, RcAreaModification areaId,
  398. RcCompactHeightfield compactHeightfield)
  399. {
  400. using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_CONVEXPOLY_AREA);
  401. int xSize = compactHeightfield.width;
  402. int zSize = compactHeightfield.height;
  403. int zStride = xSize; // For readability
  404. // Compute the bounding box of the polygon
  405. RcVec3f bmin = new RcVec3f();
  406. RcVec3f bmax = new RcVec3f();
  407. RcVec3f.Copy(ref bmin, verts, 0);
  408. RcVec3f.Copy(ref bmax, verts, 0);
  409. for (int i = 3; i < verts.Length; i += 3)
  410. {
  411. bmin.Min(verts, i);
  412. bmax.Max(verts, i);
  413. }
  414. bmin.y = minY;
  415. bmax.y = maxY;
  416. // Compute the grid footprint of the polygon
  417. int minx = (int)((bmin.x - compactHeightfield.bmin.x) / compactHeightfield.cs);
  418. int miny = (int)((bmin.y - compactHeightfield.bmin.y) / compactHeightfield.ch);
  419. int minz = (int)((bmin.z - compactHeightfield.bmin.z) / compactHeightfield.cs);
  420. int maxx = (int)((bmax.x - compactHeightfield.bmin.x) / compactHeightfield.cs);
  421. int maxy = (int)((bmax.y - compactHeightfield.bmin.y) / compactHeightfield.ch);
  422. int maxz = (int)((bmax.z - compactHeightfield.bmin.z) / compactHeightfield.cs);
  423. // Early-out if the polygon lies entirely outside the grid.
  424. if (maxx < 0)
  425. {
  426. return;
  427. }
  428. if (minx >= xSize)
  429. {
  430. return;
  431. }
  432. if (maxz < 0)
  433. {
  434. return;
  435. }
  436. if (minz >= zSize)
  437. {
  438. return;
  439. }
  440. // Clamp the polygon footprint to the grid
  441. if (minx < 0)
  442. {
  443. minx = 0;
  444. }
  445. if (maxx >= xSize)
  446. {
  447. maxx = xSize - 1;
  448. }
  449. if (minz < 0)
  450. {
  451. minz = 0;
  452. }
  453. if (maxz >= zSize)
  454. {
  455. maxz = zSize - 1;
  456. }
  457. // TODO: Optimize.
  458. for (int z = minz; z <= maxz; ++z)
  459. {
  460. for (int x = minx; x <= maxx; ++x)
  461. {
  462. RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
  463. int maxSpanIndex = cell.index + cell.count;
  464. for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
  465. {
  466. RcCompactSpan span = compactHeightfield.spans[spanIndex];
  467. // Skip if span is removed.
  468. if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
  469. continue;
  470. // Skip if y extents don't overlap.
  471. if (span.y < miny || span.y > maxy)
  472. {
  473. continue;
  474. }
  475. RcVec3f point = new RcVec3f(
  476. compactHeightfield.bmin.x + (x + 0.5f) * compactHeightfield.cs,
  477. 0,
  478. compactHeightfield.bmin.z + (z + 0.5f) * compactHeightfield.cs
  479. );
  480. if (PolyUtils.PointInPoly(verts, point))
  481. {
  482. compactHeightfield.areas[spanIndex] = areaId.Apply(compactHeightfield.areas[spanIndex]);
  483. }
  484. }
  485. }
  486. }
  487. }
  488. /// Applies the area id to all spans within the specified y-axis-aligned cylinder.
  489. ///
  490. /// @see rcCompactHeightfield, rcMedianFilterWalkableArea
  491. ///
  492. /// @ingroup recast
  493. ///
  494. /// @param[in,out] context The build context to use during the operation.
  495. /// @param[in] position The center of the base of the cylinder. [Form: (x, y, z)] [Units: wu]
  496. /// @param[in] radius The radius of the cylinder. [Units: wu] [Limit: > 0]
  497. /// @param[in] height The height of the cylinder. [Units: wu] [Limit: > 0]
  498. /// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
  499. /// @param[in,out] compactHeightfield A populated compact heightfield.
  500. public static void MarkCylinderArea(RcTelemetry context, float[] position, float radius, float height,
  501. RcAreaModification areaId, RcCompactHeightfield compactHeightfield)
  502. {
  503. using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA);
  504. int xSize = compactHeightfield.width;
  505. int zSize = compactHeightfield.height;
  506. int zStride = xSize; // For readability
  507. // Compute the bounding box of the cylinder
  508. RcVec3f cylinderBBMin = new RcVec3f(
  509. position[0] - radius,
  510. position[1],
  511. position[2] - radius
  512. );
  513. RcVec3f cylinderBBMax = new RcVec3f(
  514. position[0] + radius,
  515. position[1] + height,
  516. position[2] + radius
  517. );
  518. // Compute the grid footprint of the cylinder
  519. int minx = (int)((cylinderBBMin.x - compactHeightfield.bmin.x) / compactHeightfield.cs);
  520. int miny = (int)((cylinderBBMin.y - compactHeightfield.bmin.y) / compactHeightfield.ch);
  521. int minz = (int)((cylinderBBMin.z - compactHeightfield.bmin.z) / compactHeightfield.cs);
  522. int maxx = (int)((cylinderBBMax.x - compactHeightfield.bmin.x) / compactHeightfield.cs);
  523. int maxy = (int)((cylinderBBMax.y - compactHeightfield.bmin.y) / compactHeightfield.ch);
  524. int maxz = (int)((cylinderBBMax.z - compactHeightfield.bmin.z) / compactHeightfield.cs);
  525. // Early-out if the cylinder is completely outside the grid bounds.
  526. if (maxx < 0)
  527. {
  528. return;
  529. }
  530. if (minx >= xSize)
  531. {
  532. return;
  533. }
  534. if (maxz < 0)
  535. {
  536. return;
  537. }
  538. if (minz >= zSize)
  539. {
  540. return;
  541. }
  542. // Clamp the cylinder bounds to the grid.
  543. if (minx < 0)
  544. {
  545. minx = 0;
  546. }
  547. if (maxx >= xSize)
  548. {
  549. maxx = xSize - 1;
  550. }
  551. if (minz < 0)
  552. {
  553. minz = 0;
  554. }
  555. if (maxz >= zSize)
  556. {
  557. maxz = zSize - 1;
  558. }
  559. float radiusSq = radius * radius;
  560. for (int z = minz; z <= maxz; ++z)
  561. {
  562. for (int x = minx; x <= maxx; ++x)
  563. {
  564. RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
  565. int maxSpanIndex = cell.index + cell.count;
  566. float cellX = compactHeightfield.bmin[0] + ((float)x + 0.5f) * compactHeightfield.cs;
  567. float cellZ = compactHeightfield.bmin[2] + ((float)z + 0.5f) * compactHeightfield.cs;
  568. float deltaX = cellX - position[0];
  569. float deltaZ = cellZ - position[2];
  570. // Skip this column if it's too far from the center point of the cylinder.
  571. if (RcMath.Sqr(deltaX) + RcMath.Sqr(deltaZ) >= radiusSq)
  572. {
  573. continue;
  574. }
  575. // Mark all overlapping spans
  576. for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
  577. {
  578. RcCompactSpan span = compactHeightfield.spans[spanIndex];
  579. // Skip if span is removed.
  580. if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
  581. {
  582. continue;
  583. }
  584. // Mark if y extents overlap.
  585. if (span.y >= miny && span.y <= maxy)
  586. {
  587. compactHeightfield.areas[spanIndex] = areaId.Apply(compactHeightfield.areas[spanIndex]);
  588. }
  589. }
  590. }
  591. }
  592. }
  593. }
  594. }