RecastBuilder.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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 System.Collections.Generic;
  21. using System.Threading;
  22. using System.Threading.Tasks;
  23. using DotRecast.Core;
  24. using DotRecast.Recast.Geom;
  25. namespace DotRecast.Recast
  26. {
  27. public class RecastBuilder
  28. {
  29. private readonly IRecastBuilderProgressListener progressListener;
  30. public RecastBuilder()
  31. {
  32. progressListener = null;
  33. }
  34. public RecastBuilder(IRecastBuilderProgressListener progressListener)
  35. {
  36. this.progressListener = progressListener;
  37. }
  38. public List<RecastBuilderResult> BuildTiles(IInputGeomProvider geom, RcConfig cfg, TaskFactory taskFactory)
  39. {
  40. RcVec3f bmin = geom.GetMeshBoundsMin();
  41. RcVec3f bmax = geom.GetMeshBoundsMax();
  42. RcUtils.CalcTileCount(bmin, bmax, cfg.Cs, cfg.TileSizeX, cfg.TileSizeZ, out var tw, out var th);
  43. List<RecastBuilderResult> results = new List<RecastBuilderResult>();
  44. if (null != taskFactory)
  45. {
  46. BuildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, default);
  47. }
  48. else
  49. {
  50. BuildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
  51. }
  52. return results;
  53. }
  54. public Task BuildTilesAsync(IInputGeomProvider geom, RcConfig cfg, int threads, List<RecastBuilderResult> results, TaskFactory taskFactory, CancellationToken cancellationToken)
  55. {
  56. RcVec3f bmin = geom.GetMeshBoundsMin();
  57. RcVec3f bmax = geom.GetMeshBoundsMax();
  58. RcUtils.CalcTileCount(bmin, bmax, cfg.Cs, cfg.TileSizeX, cfg.TileSizeZ, out var tw, out var th);
  59. Task task;
  60. if (1 < threads)
  61. {
  62. task = BuildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, cancellationToken);
  63. }
  64. else
  65. {
  66. task = BuildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
  67. }
  68. return task;
  69. }
  70. private Task BuildSingleThreadAsync(IInputGeomProvider geom, RcConfig cfg, RcVec3f bmin, RcVec3f bmax,
  71. int tw, int th, List<RecastBuilderResult> results)
  72. {
  73. RcAtomicInteger counter = new RcAtomicInteger(0);
  74. for (int y = 0; y < th; ++y)
  75. {
  76. for (int x = 0; x < tw; ++x)
  77. {
  78. results.Add(BuildTile(geom, cfg, bmin, bmax, x, y, counter, tw * th));
  79. }
  80. }
  81. return Task.CompletedTask;
  82. }
  83. private Task BuildMultiThreadAsync(IInputGeomProvider geom, RcConfig cfg, RcVec3f bmin, RcVec3f bmax,
  84. int tw, int th, List<RecastBuilderResult> results, TaskFactory taskFactory, CancellationToken cancellationToken)
  85. {
  86. RcAtomicInteger counter = new RcAtomicInteger(0);
  87. CountdownEvent latch = new CountdownEvent(tw * th);
  88. List<Task> tasks = new List<Task>();
  89. for (int x = 0; x < tw; ++x)
  90. {
  91. for (int y = 0; y < th; ++y)
  92. {
  93. int tx = x;
  94. int ty = y;
  95. var task = taskFactory.StartNew(() =>
  96. {
  97. if (cancellationToken.IsCancellationRequested)
  98. return;
  99. try
  100. {
  101. RecastBuilderResult tile = BuildTile(geom, cfg, bmin, bmax, tx, ty, counter, tw * th);
  102. lock (results)
  103. {
  104. results.Add(tile);
  105. }
  106. }
  107. catch (Exception e)
  108. {
  109. Console.WriteLine(e);
  110. }
  111. latch.Signal();
  112. }, cancellationToken);
  113. tasks.Add(task);
  114. }
  115. }
  116. try
  117. {
  118. latch.Wait();
  119. }
  120. catch (ThreadInterruptedException)
  121. {
  122. }
  123. return Task.WhenAll(tasks.ToArray());
  124. }
  125. public RecastBuilderResult BuildTile(IInputGeomProvider geom, RcConfig cfg, RcVec3f bmin, RcVec3f bmax, int tx,
  126. int ty, RcAtomicInteger counter, int total)
  127. {
  128. RecastBuilderResult result = Build(geom, new RecastBuilderConfig(cfg, bmin, bmax, tx, ty));
  129. if (progressListener != null)
  130. {
  131. progressListener.OnProgress(counter.IncrementAndGet(), total);
  132. }
  133. return result;
  134. }
  135. public RecastBuilderResult Build(IInputGeomProvider geom, RecastBuilderConfig builderCfg)
  136. {
  137. RcConfig cfg = builderCfg.cfg;
  138. RcTelemetry ctx = new RcTelemetry();
  139. //
  140. // Step 1. Rasterize input polygon soup.
  141. //
  142. RcHeightfield solid = RecastVoxelization.BuildSolidHeightfield(geom, builderCfg, ctx);
  143. return Build(builderCfg.tileX, builderCfg.tileZ, geom, cfg, solid, ctx);
  144. }
  145. public RecastBuilderResult Build(int tileX, int tileZ, IInputGeomProvider geom, RcConfig cfg, RcHeightfield solid, RcTelemetry ctx)
  146. {
  147. FilterHeightfield(solid, cfg, ctx);
  148. RcCompactHeightfield chf = BuildCompactHeightfield(geom, cfg, ctx, solid);
  149. // Partition the heightfield so that we can use simple algorithm later
  150. // to triangulate the walkable areas.
  151. // There are 3 partitioning methods, each with some pros and cons:
  152. // 1) Watershed partitioning
  153. // - the classic Recast partitioning
  154. // - creates the nicest tessellation
  155. // - usually slowest
  156. // - partitions the heightfield into nice regions without holes or
  157. // overlaps
  158. // - the are some corner cases where this method creates produces holes
  159. // and overlaps
  160. // - holes may appear when a small obstacles is close to large open area
  161. // (triangulation can handle this)
  162. // - overlaps may occur if you have narrow spiral corridors (i.e
  163. // stairs), this make triangulation to fail
  164. // * generally the best choice if you precompute the navmesh, use this
  165. // if you have large open areas
  166. // 2) Monotone partioning
  167. // - fastest
  168. // - partitions the heightfield into regions without holes and overlaps
  169. // (guaranteed)
  170. // - creates long thin polygons, which sometimes causes paths with
  171. // detours
  172. // * use this if you want fast navmesh generation
  173. // 3) Layer partitoining
  174. // - quite fast
  175. // - partitions the heighfield into non-overlapping regions
  176. // - relies on the triangulation code to cope with holes (thus slower
  177. // than monotone partitioning)
  178. // - produces better triangles than monotone partitioning
  179. // - does not have the corner cases of watershed partitioning
  180. // - can be slow and create a bit ugly tessellation (still better than
  181. // monotone)
  182. // if you have large open areas with small obstacles (not a problem if
  183. // you use tiles)
  184. // * good choice to use for tiled navmesh with medium and small sized
  185. // tiles
  186. if (cfg.Partition == RcPartitionType.WATERSHED.Value)
  187. {
  188. // Prepare for region partitioning, by calculating distance field
  189. // along the walkable surface.
  190. RecastRegion.BuildDistanceField(ctx, chf);
  191. // Partition the walkable surface into simple regions without holes.
  192. RecastRegion.BuildRegions(ctx, chf, cfg.MinRegionArea, cfg.MergeRegionArea);
  193. }
  194. else if (cfg.Partition == RcPartitionType.MONOTONE.Value)
  195. {
  196. // Partition the walkable surface into simple regions without holes.
  197. // Monotone partitioning does not need distancefield.
  198. RecastRegion.BuildRegionsMonotone(ctx, chf, cfg.MinRegionArea, cfg.MergeRegionArea);
  199. }
  200. else
  201. {
  202. // Partition the walkable surface into simple regions without holes.
  203. RecastRegion.BuildLayerRegions(ctx, chf, cfg.MinRegionArea);
  204. }
  205. //
  206. // Step 5. Trace and simplify region contours.
  207. //
  208. // Create contours.
  209. RcContourSet cset = RecastContour.BuildContours(ctx, chf, cfg.MaxSimplificationError, cfg.MaxEdgeLen,
  210. RcConstants.RC_CONTOUR_TESS_WALL_EDGES);
  211. //
  212. // Step 6. Build polygons mesh from contours.
  213. //
  214. RcPolyMesh pmesh = RecastMesh.BuildPolyMesh(ctx, cset, cfg.MaxVertsPerPoly);
  215. //
  216. // Step 7. Create detail mesh which allows to access approximate height
  217. // on each polygon.
  218. //
  219. RcPolyMeshDetail dmesh = cfg.BuildMeshDetail
  220. ? RecastMeshDetail.BuildPolyMeshDetail(ctx, pmesh, chf, cfg.DetailSampleDist, cfg.DetailSampleMaxError)
  221. : null;
  222. return new RecastBuilderResult(tileX, tileZ, solid, chf, cset, pmesh, dmesh, ctx);
  223. }
  224. /*
  225. * Step 2. Filter walkable surfaces.
  226. */
  227. private void FilterHeightfield(RcHeightfield solid, RcConfig cfg, RcTelemetry ctx)
  228. {
  229. // Once all geometry is rasterized, we do initial pass of filtering to
  230. // remove unwanted overhangs caused by the conservative rasterization
  231. // as well as filter spans where the character cannot possibly stand.
  232. if (cfg.FilterLowHangingObstacles)
  233. {
  234. RecastFilter.FilterLowHangingWalkableObstacles(ctx, cfg.WalkableClimb, solid);
  235. }
  236. if (cfg.FilterLedgeSpans)
  237. {
  238. RecastFilter.FilterLedgeSpans(ctx, cfg.WalkableHeight, cfg.WalkableClimb, solid);
  239. }
  240. if (cfg.FilterWalkableLowHeightSpans)
  241. {
  242. RecastFilter.FilterWalkableLowHeightSpans(ctx, cfg.WalkableHeight, solid);
  243. }
  244. }
  245. /*
  246. * Step 3. Partition walkable surface to simple regions.
  247. */
  248. private RcCompactHeightfield BuildCompactHeightfield(IInputGeomProvider geom, RcConfig cfg, RcTelemetry ctx,
  249. RcHeightfield solid)
  250. {
  251. // Compact the heightfield so that it is faster to handle from now on.
  252. // This will result more cache coherent data as well as the neighbours
  253. // between walkable cells will be calculated.
  254. RcCompactHeightfield chf = RecastCompact.BuildCompactHeightfield(ctx, cfg.WalkableHeight, cfg.WalkableClimb, solid);
  255. // Erode the walkable area by agent radius.
  256. RecastArea.ErodeWalkableArea(ctx, cfg.WalkableRadius, chf);
  257. // (Optional) Mark areas.
  258. if (geom != null)
  259. {
  260. foreach (RcConvexVolume vol in geom.ConvexVolumes())
  261. {
  262. RecastArea.MarkConvexPolyArea(ctx, vol.verts, vol.hmin, vol.hmax, vol.areaMod, chf);
  263. }
  264. }
  265. return chf;
  266. }
  267. public RcHeightfieldLayerSet BuildLayers(IInputGeomProvider geom, RecastBuilderConfig builderCfg)
  268. {
  269. RcTelemetry ctx = new RcTelemetry();
  270. RcHeightfield solid = RecastVoxelization.BuildSolidHeightfield(geom, builderCfg, ctx);
  271. FilterHeightfield(solid, builderCfg.cfg, ctx);
  272. RcCompactHeightfield chf = BuildCompactHeightfield(geom, builderCfg.cfg, ctx, solid);
  273. return RecastLayers.BuildHeightfieldLayers(ctx, chf, builderCfg.cfg.WalkableHeight);
  274. }
  275. }
  276. }