DtNavMeshBuilder.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  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.Detour
  22. {
  23. using static DotRecast.Core.RcMath;
  24. public static class DtNavMeshBuilder
  25. {
  26. const int MESH_NULL_IDX = 0xffff;
  27. private static int[][] CalcExtends(BVItem[] items, int nitems, int imin, int imax)
  28. {
  29. int[] bmin = new int[3];
  30. int[] bmax = new int[3];
  31. bmin[0] = items[imin].bmin[0];
  32. bmin[1] = items[imin].bmin[1];
  33. bmin[2] = items[imin].bmin[2];
  34. bmax[0] = items[imin].bmax[0];
  35. bmax[1] = items[imin].bmax[1];
  36. bmax[2] = items[imin].bmax[2];
  37. for (int i = imin + 1; i < imax; ++i)
  38. {
  39. BVItem it = items[i];
  40. if (it.bmin[0] < bmin[0])
  41. bmin[0] = it.bmin[0];
  42. if (it.bmin[1] < bmin[1])
  43. bmin[1] = it.bmin[1];
  44. if (it.bmin[2] < bmin[2])
  45. bmin[2] = it.bmin[2];
  46. if (it.bmax[0] > bmax[0])
  47. bmax[0] = it.bmax[0];
  48. if (it.bmax[1] > bmax[1])
  49. bmax[1] = it.bmax[1];
  50. if (it.bmax[2] > bmax[2])
  51. bmax[2] = it.bmax[2];
  52. }
  53. return new int[][] { bmin, bmax };
  54. }
  55. private static int LongestAxis(int x, int y, int z)
  56. {
  57. int axis = 0;
  58. int maxVal = x;
  59. if (y > maxVal)
  60. {
  61. axis = 1;
  62. maxVal = y;
  63. }
  64. if (z > maxVal)
  65. {
  66. axis = 2;
  67. maxVal = z;
  68. }
  69. return axis;
  70. }
  71. public static int Subdivide(BVItem[] items, int nitems, int imin, int imax, int curNode, DtBVNode[] nodes)
  72. {
  73. int inum = imax - imin;
  74. int icur = curNode;
  75. DtBVNode node = new DtBVNode();
  76. nodes[curNode++] = node;
  77. if (inum == 1)
  78. {
  79. // Leaf
  80. node.bmin[0] = items[imin].bmin[0];
  81. node.bmin[1] = items[imin].bmin[1];
  82. node.bmin[2] = items[imin].bmin[2];
  83. node.bmax[0] = items[imin].bmax[0];
  84. node.bmax[1] = items[imin].bmax[1];
  85. node.bmax[2] = items[imin].bmax[2];
  86. node.i = items[imin].i;
  87. }
  88. else
  89. {
  90. // Split
  91. int[][] minmax = CalcExtends(items, nitems, imin, imax);
  92. node.bmin = minmax[0];
  93. node.bmax = minmax[1];
  94. int axis = LongestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1],
  95. node.bmax[2] - node.bmin[2]);
  96. if (axis == 0)
  97. {
  98. // Sort along x-axis
  99. Array.Sort(items, imin, inum, BVItemXComparer.Shared);
  100. }
  101. else if (axis == 1)
  102. {
  103. // Sort along y-axis
  104. Array.Sort(items, imin, inum, BVItemYComparer.Shared);
  105. }
  106. else
  107. {
  108. // Sort along z-axis
  109. Array.Sort(items, imin, inum, BVItemZComparer.Shared);
  110. }
  111. int isplit = imin + inum / 2;
  112. // Left
  113. curNode = Subdivide(items, nitems, imin, isplit, curNode, nodes);
  114. // Right
  115. curNode = Subdivide(items, nitems, isplit, imax, curNode, nodes);
  116. int iescape = curNode - icur;
  117. // Negative index means escape.
  118. node.i = -iescape;
  119. }
  120. return curNode;
  121. }
  122. private static int CreateBVTree(DtNavMeshCreateParams option, DtBVNode[] nodes)
  123. {
  124. // Build tree
  125. float quantFactor = 1 / option.cs;
  126. BVItem[] items = new BVItem[option.polyCount];
  127. for (int i = 0; i < option.polyCount; i++)
  128. {
  129. BVItem it = new BVItem();
  130. items[i] = it;
  131. it.i = i;
  132. // Calc polygon bounds. Use detail meshes if available.
  133. if (option.detailMeshes != null)
  134. {
  135. int vb = option.detailMeshes[i * 4 + 0];
  136. int ndv = option.detailMeshes[i * 4 + 1];
  137. RcVec3f bmin = new RcVec3f();
  138. RcVec3f bmax = new RcVec3f();
  139. int dv = vb * 3;
  140. bmin.Set(option.detailVerts, dv);
  141. bmax.Set(option.detailVerts, dv);
  142. for (int j = 1; j < ndv; j++)
  143. {
  144. bmin.Min(option.detailVerts, dv + j * 3);
  145. bmax.Max(option.detailVerts, dv + j * 3);
  146. }
  147. // BV-tree uses cs for all dimensions
  148. it.bmin[0] = Clamp((int)((bmin.x - option.bmin.x) * quantFactor), 0, int.MaxValue);
  149. it.bmin[1] = Clamp((int)((bmin.y - option.bmin.y) * quantFactor), 0, int.MaxValue);
  150. it.bmin[2] = Clamp((int)((bmin.z - option.bmin.z) * quantFactor), 0, int.MaxValue);
  151. it.bmax[0] = Clamp((int)((bmax.x - option.bmin.x) * quantFactor), 0, int.MaxValue);
  152. it.bmax[1] = Clamp((int)((bmax.y - option.bmin.y) * quantFactor), 0, int.MaxValue);
  153. it.bmax[2] = Clamp((int)((bmax.z - option.bmin.z) * quantFactor), 0, int.MaxValue);
  154. }
  155. else
  156. {
  157. int p = i * option.nvp * 2;
  158. it.bmin[0] = it.bmax[0] = option.verts[option.polys[p] * 3 + 0];
  159. it.bmin[1] = it.bmax[1] = option.verts[option.polys[p] * 3 + 1];
  160. it.bmin[2] = it.bmax[2] = option.verts[option.polys[p] * 3 + 2];
  161. for (int j = 1; j < option.nvp; ++j)
  162. {
  163. if (option.polys[p + j] == MESH_NULL_IDX)
  164. break;
  165. int x = option.verts[option.polys[p + j] * 3 + 0];
  166. int y = option.verts[option.polys[p + j] * 3 + 1];
  167. int z = option.verts[option.polys[p + j] * 3 + 2];
  168. if (x < it.bmin[0])
  169. it.bmin[0] = x;
  170. if (y < it.bmin[1])
  171. it.bmin[1] = y;
  172. if (z < it.bmin[2])
  173. it.bmin[2] = z;
  174. if (x > it.bmax[0])
  175. it.bmax[0] = x;
  176. if (y > it.bmax[1])
  177. it.bmax[1] = y;
  178. if (z > it.bmax[2])
  179. it.bmax[2] = z;
  180. }
  181. // Remap y
  182. it.bmin[1] = (int)Math.Floor(it.bmin[1] * option.ch * quantFactor);
  183. it.bmax[1] = (int)Math.Ceiling(it.bmax[1] * option.ch * quantFactor);
  184. }
  185. }
  186. return Subdivide(items, option.polyCount, 0, option.polyCount, 0, nodes);
  187. }
  188. const int XP = 1 << 0;
  189. const int ZP = 1 << 1;
  190. const int XM = 1 << 2;
  191. const int ZM = 1 << 3;
  192. public static int ClassifyOffMeshPoint(RcVec3f pt, RcVec3f bmin, RcVec3f bmax)
  193. {
  194. int outcode = 0;
  195. outcode |= (pt.x >= bmax.x) ? XP : 0;
  196. outcode |= (pt.z >= bmax.z) ? ZP : 0;
  197. outcode |= (pt.x < bmin.x) ? XM : 0;
  198. outcode |= (pt.z < bmin.z) ? ZM : 0;
  199. switch (outcode)
  200. {
  201. case XP:
  202. return 0;
  203. case XP | ZP:
  204. return 1;
  205. case ZP:
  206. return 2;
  207. case XM | ZP:
  208. return 3;
  209. case XM:
  210. return 4;
  211. case XM | ZM:
  212. return 5;
  213. case ZM:
  214. return 6;
  215. case XP | ZM:
  216. return 7;
  217. }
  218. return 0xff;
  219. }
  220. /**
  221. * Builds navigation mesh tile data from the provided tile creation data.
  222. *
  223. * @param option
  224. * Tile creation data.
  225. *
  226. * @return created tile data
  227. */
  228. public static DtMeshData CreateNavMeshData(DtNavMeshCreateParams option)
  229. {
  230. if (option.vertCount >= 0xffff)
  231. return null;
  232. if (option.vertCount == 0 || option.verts == null)
  233. return null;
  234. if (option.polyCount == 0 || option.polys == null)
  235. return null;
  236. int nvp = option.nvp;
  237. // Classify off-mesh connection points. We store only the connections
  238. // whose start point is inside the tile.
  239. int[] offMeshConClass = null;
  240. int storedOffMeshConCount = 0;
  241. int offMeshConLinkCount = 0;
  242. if (option.offMeshConCount > 0)
  243. {
  244. offMeshConClass = new int[option.offMeshConCount * 2];
  245. // Find tight heigh bounds, used for culling out off-mesh start
  246. // locations.
  247. float hmin = float.MaxValue;
  248. float hmax = -float.MaxValue;
  249. if (option.detailVerts != null && option.detailVertsCount != 0)
  250. {
  251. for (int i = 0; i < option.detailVertsCount; ++i)
  252. {
  253. float h = option.detailVerts[i * 3 + 1];
  254. hmin = Math.Min(hmin, h);
  255. hmax = Math.Max(hmax, h);
  256. }
  257. }
  258. else
  259. {
  260. for (int i = 0; i < option.vertCount; ++i)
  261. {
  262. int iv = i * 3;
  263. float h = option.bmin.y + option.verts[iv + 1] * option.ch;
  264. hmin = Math.Min(hmin, h);
  265. hmax = Math.Max(hmax, h);
  266. }
  267. }
  268. hmin -= option.walkableClimb;
  269. hmax += option.walkableClimb;
  270. RcVec3f bmin = new RcVec3f();
  271. RcVec3f bmax = new RcVec3f();
  272. bmin = option.bmin;
  273. bmax = option.bmax;
  274. bmin.y = hmin;
  275. bmax.y = hmax;
  276. for (int i = 0; i < option.offMeshConCount; ++i)
  277. {
  278. var p0 = RcVec3f.Of(option.offMeshConVerts, (i * 2 + 0) * 3);
  279. var p1 = RcVec3f.Of(option.offMeshConVerts, (i * 2 + 1) * 3);
  280. offMeshConClass[i * 2 + 0] = ClassifyOffMeshPoint(p0, bmin, bmax);
  281. offMeshConClass[i * 2 + 1] = ClassifyOffMeshPoint(p1, bmin, bmax);
  282. // Zero out off-mesh start positions which are not even
  283. // potentially touching the mesh.
  284. if (offMeshConClass[i * 2 + 0] == 0xff)
  285. {
  286. if (p0.y < bmin.y || p0.y > bmax.y)
  287. offMeshConClass[i * 2 + 0] = 0;
  288. }
  289. // Count how many links should be allocated for off-mesh
  290. // connections.
  291. if (offMeshConClass[i * 2 + 0] == 0xff)
  292. offMeshConLinkCount++;
  293. if (offMeshConClass[i * 2 + 1] == 0xff)
  294. offMeshConLinkCount++;
  295. if (offMeshConClass[i * 2 + 0] == 0xff)
  296. storedOffMeshConCount++;
  297. }
  298. }
  299. // Off-mesh connections are stored as polygons, adjust values.
  300. int totPolyCount = option.polyCount + storedOffMeshConCount;
  301. int totVertCount = option.vertCount + storedOffMeshConCount * 2;
  302. // Find portal edges which are at tile borders.
  303. int edgeCount = 0;
  304. int portalCount = 0;
  305. for (int i = 0; i < option.polyCount; ++i)
  306. {
  307. int p = i * 2 * nvp;
  308. for (int j = 0; j < nvp; ++j)
  309. {
  310. if (option.polys[p + j] == MESH_NULL_IDX)
  311. break;
  312. edgeCount++;
  313. if ((option.polys[p + nvp + j] & 0x8000) != 0)
  314. {
  315. int dir = option.polys[p + nvp + j] & 0xf;
  316. if (dir != 0xf)
  317. portalCount++;
  318. }
  319. }
  320. }
  321. int maxLinkCount = edgeCount + portalCount * 2 + offMeshConLinkCount * 2;
  322. // Find unique detail vertices.
  323. int uniqueDetailVertCount = 0;
  324. int detailTriCount = 0;
  325. if (option.detailMeshes != null)
  326. {
  327. // Has detail mesh, count unique detail vertex count and use input
  328. // detail tri count.
  329. detailTriCount = option.detailTriCount;
  330. for (int i = 0; i < option.polyCount; ++i)
  331. {
  332. int p = i * nvp * 2;
  333. int ndv = option.detailMeshes[i * 4 + 1];
  334. int nv = 0;
  335. for (int j = 0; j < nvp; ++j)
  336. {
  337. if (option.polys[p + j] == MESH_NULL_IDX)
  338. break;
  339. nv++;
  340. }
  341. ndv -= nv;
  342. uniqueDetailVertCount += ndv;
  343. }
  344. }
  345. else
  346. {
  347. // No input detail mesh, build detail mesh from nav polys.
  348. uniqueDetailVertCount = 0; // No extra detail verts.
  349. detailTriCount = 0;
  350. for (int i = 0; i < option.polyCount; ++i)
  351. {
  352. int p = i * nvp * 2;
  353. int nv = 0;
  354. for (int j = 0; j < nvp; ++j)
  355. {
  356. if (option.polys[p + j] == MESH_NULL_IDX)
  357. break;
  358. nv++;
  359. }
  360. detailTriCount += nv - 2;
  361. }
  362. }
  363. int bvTreeSize = option.buildBvTree ? option.polyCount * 2 : 0;
  364. DtMeshHeader header = new DtMeshHeader();
  365. float[] navVerts = new float[3 * totVertCount];
  366. DtPoly[] navPolys = new DtPoly[totPolyCount];
  367. DtPolyDetail[] navDMeshes = new DtPolyDetail[option.polyCount];
  368. float[] navDVerts = new float[3 * uniqueDetailVertCount];
  369. int[] navDTris = new int[4 * detailTriCount];
  370. DtBVNode[] navBvtree = new DtBVNode[bvTreeSize];
  371. DtOffMeshConnection[] offMeshCons = new DtOffMeshConnection[storedOffMeshConCount];
  372. // Store header
  373. header.magic = DtMeshHeader.DT_NAVMESH_MAGIC;
  374. header.version = DtMeshHeader.DT_NAVMESH_VERSION;
  375. header.x = option.tileX;
  376. header.y = option.tileZ;
  377. header.layer = option.tileLayer;
  378. header.userId = option.userId;
  379. header.polyCount = totPolyCount;
  380. header.vertCount = totVertCount;
  381. header.maxLinkCount = maxLinkCount;
  382. header.bmin = option.bmin;
  383. header.bmax = option.bmax;
  384. header.detailMeshCount = option.polyCount;
  385. header.detailVertCount = uniqueDetailVertCount;
  386. header.detailTriCount = detailTriCount;
  387. header.bvQuantFactor = 1.0f / option.cs;
  388. header.offMeshBase = option.polyCount;
  389. header.walkableHeight = option.walkableHeight;
  390. header.walkableRadius = option.walkableRadius;
  391. header.walkableClimb = option.walkableClimb;
  392. header.offMeshConCount = storedOffMeshConCount;
  393. header.bvNodeCount = bvTreeSize;
  394. int offMeshVertsBase = option.vertCount;
  395. int offMeshPolyBase = option.polyCount;
  396. // Store vertices
  397. // Mesh vertices
  398. for (int i = 0; i < option.vertCount; ++i)
  399. {
  400. int iv = i * 3;
  401. int v = i * 3;
  402. navVerts[v] = option.bmin.x + option.verts[iv] * option.cs;
  403. navVerts[v + 1] = option.bmin.y + option.verts[iv + 1] * option.ch;
  404. navVerts[v + 2] = option.bmin.z + option.verts[iv + 2] * option.cs;
  405. }
  406. // Off-mesh link vertices.
  407. int n = 0;
  408. for (int i = 0; i < option.offMeshConCount; ++i)
  409. {
  410. // Only store connections which start from this tile.
  411. if (offMeshConClass[i * 2 + 0] == 0xff)
  412. {
  413. int linkv = i * 2 * 3;
  414. int v = (offMeshVertsBase + n * 2) * 3;
  415. Array.Copy(option.offMeshConVerts, linkv, navVerts, v, 6);
  416. n++;
  417. }
  418. }
  419. // Store polygons
  420. // Mesh polys
  421. int src = 0;
  422. for (int i = 0; i < option.polyCount; ++i)
  423. {
  424. DtPoly p = new DtPoly(i, nvp);
  425. navPolys[i] = p;
  426. p.vertCount = 0;
  427. p.flags = option.polyFlags[i];
  428. p.SetArea(option.polyAreas[i]);
  429. p.SetPolyType(DtPoly.DT_POLYTYPE_GROUND);
  430. for (int j = 0; j < nvp; ++j)
  431. {
  432. if (option.polys[src + j] == MESH_NULL_IDX)
  433. break;
  434. p.verts[j] = option.polys[src + j];
  435. if ((option.polys[src + nvp + j] & 0x8000) != 0)
  436. {
  437. // Border or portal edge.
  438. int dir = option.polys[src + nvp + j] & 0xf;
  439. if (dir == 0xf) // Border
  440. p.neis[j] = 0;
  441. else if (dir == 0) // Portal x-
  442. p.neis[j] = DtNavMesh.DT_EXT_LINK | 4;
  443. else if (dir == 1) // Portal z+
  444. p.neis[j] = DtNavMesh.DT_EXT_LINK | 2;
  445. else if (dir == 2) // Portal x+
  446. p.neis[j] = DtNavMesh.DT_EXT_LINK | 0;
  447. else if (dir == 3) // Portal z-
  448. p.neis[j] = DtNavMesh.DT_EXT_LINK | 6;
  449. }
  450. else
  451. {
  452. // Normal connection
  453. p.neis[j] = option.polys[src + nvp + j] + 1;
  454. }
  455. p.vertCount++;
  456. }
  457. src += nvp * 2;
  458. }
  459. // Off-mesh connection vertices.
  460. n = 0;
  461. for (int i = 0; i < option.offMeshConCount; ++i)
  462. {
  463. // Only store connections which start from this tile.
  464. if (offMeshConClass[i * 2 + 0] == 0xff)
  465. {
  466. DtPoly p = new DtPoly(offMeshPolyBase + n, nvp);
  467. navPolys[offMeshPolyBase + n] = p;
  468. p.vertCount = 2;
  469. p.verts[0] = offMeshVertsBase + n * 2;
  470. p.verts[1] = offMeshVertsBase + n * 2 + 1;
  471. p.flags = option.offMeshConFlags[i];
  472. p.SetArea(option.offMeshConAreas[i]);
  473. p.SetPolyType(DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION);
  474. n++;
  475. }
  476. }
  477. // Store detail meshes and vertices.
  478. // The nav polygon vertices are stored as the first vertices on each
  479. // mesh.
  480. // We compress the mesh data by skipping them and using the navmesh
  481. // coordinates.
  482. if (option.detailMeshes != null)
  483. {
  484. int vbase = 0;
  485. for (int i = 0; i < option.polyCount; ++i)
  486. {
  487. DtPolyDetail dtl = new DtPolyDetail();
  488. navDMeshes[i] = dtl;
  489. int vb = option.detailMeshes[i * 4 + 0];
  490. int ndv = option.detailMeshes[i * 4 + 1];
  491. int nv = navPolys[i].vertCount;
  492. dtl.vertBase = vbase;
  493. dtl.vertCount = (ndv - nv);
  494. dtl.triBase = option.detailMeshes[i * 4 + 2];
  495. dtl.triCount = option.detailMeshes[i * 4 + 3];
  496. // Copy vertices except the first 'nv' verts which are equal to
  497. // nav poly verts.
  498. if (ndv - nv != 0)
  499. {
  500. Array.Copy(option.detailVerts, (vb + nv) * 3, navDVerts, vbase * 3, 3 * (ndv - nv));
  501. vbase += ndv - nv;
  502. }
  503. }
  504. // Store triangles.
  505. Array.Copy(option.detailTris, 0, navDTris, 0, 4 * option.detailTriCount);
  506. }
  507. else
  508. {
  509. // Create dummy detail mesh by triangulating polys.
  510. int tbase = 0;
  511. for (int i = 0; i < option.polyCount; ++i)
  512. {
  513. DtPolyDetail dtl = new DtPolyDetail();
  514. navDMeshes[i] = dtl;
  515. int nv = navPolys[i].vertCount;
  516. dtl.vertBase = 0;
  517. dtl.vertCount = 0;
  518. dtl.triBase = tbase;
  519. dtl.triCount = (nv - 2);
  520. // Triangulate polygon (local indices).
  521. for (int j = 2; j < nv; ++j)
  522. {
  523. int t = tbase * 4;
  524. navDTris[t + 0] = 0;
  525. navDTris[t + 1] = (j - 1);
  526. navDTris[t + 2] = j;
  527. // Bit for each edge that belongs to poly boundary.
  528. navDTris[t + 3] = (1 << 2);
  529. if (j == 2)
  530. navDTris[t + 3] |= (1 << 0);
  531. if (j == nv - 1)
  532. navDTris[t + 3] |= (1 << 4);
  533. tbase++;
  534. }
  535. }
  536. }
  537. // Store and create BVtree.
  538. // TODO: take detail mesh into account! use byte per bbox extent?
  539. if (option.buildBvTree)
  540. {
  541. // Do not set header.bvNodeCount set to make it work look exactly the same as in original Detour
  542. header.bvNodeCount = CreateBVTree(option, navBvtree);
  543. }
  544. // Store Off-Mesh connections.
  545. n = 0;
  546. for (int i = 0; i < option.offMeshConCount; ++i)
  547. {
  548. // Only store connections which start from this tile.
  549. if (offMeshConClass[i * 2 + 0] == 0xff)
  550. {
  551. DtOffMeshConnection con = new DtOffMeshConnection();
  552. offMeshCons[n] = con;
  553. con.poly = (offMeshPolyBase + n);
  554. // Copy connection end-points.
  555. int endPts = i * 2 * 3;
  556. Array.Copy(option.offMeshConVerts, endPts, con.pos, 0, 6);
  557. con.rad = option.offMeshConRad[i];
  558. con.flags = option.offMeshConDir[i] != 0 ? DtNavMesh.DT_OFFMESH_CON_BIDIR : 0;
  559. con.side = offMeshConClass[i * 2 + 1];
  560. if (option.offMeshConUserID != null)
  561. con.userId = option.offMeshConUserID[i];
  562. n++;
  563. }
  564. }
  565. DtMeshData nmd = new DtMeshData();
  566. nmd.header = header;
  567. nmd.verts = navVerts;
  568. nmd.polys = navPolys;
  569. nmd.detailMeshes = navDMeshes;
  570. nmd.detailVerts = navDVerts;
  571. nmd.detailTris = navDTris;
  572. nmd.bvTree = navBvtree;
  573. nmd.offMeshCons = offMeshCons;
  574. return nmd;
  575. }
  576. }
  577. }