DotRecastNetSim/src/DotRecast.Detour/DtNavMeshBuilder.cs

645 lines
25 KiB
C#

/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
using System;
using DotRecast.Core;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour
{
public static class DtNavMeshBuilder
{
const int MESH_NULL_IDX = 0xffff;
private static int[][] CalcExtends(BVItem[] items, int nitems, int imin, int imax)
{
int[] bmin = new int[3];
int[] bmax = new int[3];
bmin[0] = items[imin].bmin[0];
bmin[1] = items[imin].bmin[1];
bmin[2] = items[imin].bmin[2];
bmax[0] = items[imin].bmax[0];
bmax[1] = items[imin].bmax[1];
bmax[2] = items[imin].bmax[2];
for (int i = imin + 1; i < imax; ++i)
{
BVItem it = items[i];
if (it.bmin[0] < bmin[0])
bmin[0] = it.bmin[0];
if (it.bmin[1] < bmin[1])
bmin[1] = it.bmin[1];
if (it.bmin[2] < bmin[2])
bmin[2] = it.bmin[2];
if (it.bmax[0] > bmax[0])
bmax[0] = it.bmax[0];
if (it.bmax[1] > bmax[1])
bmax[1] = it.bmax[1];
if (it.bmax[2] > bmax[2])
bmax[2] = it.bmax[2];
}
return new int[][] { bmin, bmax };
}
private static int LongestAxis(int x, int y, int z)
{
int axis = 0;
int maxVal = x;
if (y > maxVal)
{
axis = 1;
maxVal = y;
}
if (z > maxVal)
{
axis = 2;
maxVal = z;
}
return axis;
}
public static int Subdivide(BVItem[] items, int nitems, int imin, int imax, int curNode, DtBVNode[] nodes)
{
int inum = imax - imin;
int icur = curNode;
DtBVNode node = new DtBVNode();
nodes[curNode++] = node;
if (inum == 1)
{
// Leaf
node.bmin[0] = items[imin].bmin[0];
node.bmin[1] = items[imin].bmin[1];
node.bmin[2] = items[imin].bmin[2];
node.bmax[0] = items[imin].bmax[0];
node.bmax[1] = items[imin].bmax[1];
node.bmax[2] = items[imin].bmax[2];
node.i = items[imin].i;
}
else
{
// Split
int[][] minmax = CalcExtends(items, nitems, imin, imax);
node.bmin = minmax[0];
node.bmax = minmax[1];
int axis = LongestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1],
node.bmax[2] - node.bmin[2]);
if (axis == 0)
{
// Sort along x-axis
Array.Sort(items, imin, inum, BVItemXComparer.Shared);
}
else if (axis == 1)
{
// Sort along y-axis
Array.Sort(items, imin, inum, BVItemYComparer.Shared);
}
else
{
// Sort along z-axis
Array.Sort(items, imin, inum, BVItemZComparer.Shared);
}
int isplit = imin + inum / 2;
// Left
curNode = Subdivide(items, nitems, imin, isplit, curNode, nodes);
// Right
curNode = Subdivide(items, nitems, isplit, imax, curNode, nodes);
int iescape = curNode - icur;
// Negative index means escape.
node.i = -iescape;
}
return curNode;
}
private static int CreateBVTree(DtNavMeshCreateParams option, DtBVNode[] nodes)
{
// Build tree
float quantFactor = 1 / option.cs;
BVItem[] items = new BVItem[option.polyCount];
for (int i = 0; i < option.polyCount; i++)
{
BVItem it = new BVItem();
items[i] = it;
it.i = i;
// Calc polygon bounds. Use detail meshes if available.
if (option.detailMeshes != null)
{
int vb = option.detailMeshes[i * 4 + 0];
int ndv = option.detailMeshes[i * 4 + 1];
int dv = vb * 3;
var bmin = RcVecUtils.Create(option.detailVerts, dv);
var bmax = RcVecUtils.Create(option.detailVerts, dv);
for (int j = 1; j < ndv; j++)
{
bmin = RcVecUtils.Min(bmin, option.detailVerts, dv + j * 3);
bmax = RcVecUtils.Max(bmax, option.detailVerts, dv + j * 3);
}
// BV-tree uses cs for all dimensions
it.bmin[0] = Math.Clamp((int)((bmin.X - option.bmin.X) * quantFactor), 0, int.MaxValue);
it.bmin[1] = Math.Clamp((int)((bmin.Y - option.bmin.Y) * quantFactor), 0, int.MaxValue);
it.bmin[2] = Math.Clamp((int)((bmin.Z - option.bmin.Z) * quantFactor), 0, int.MaxValue);
it.bmax[0] = Math.Clamp((int)((bmax.X - option.bmin.X) * quantFactor), 0, int.MaxValue);
it.bmax[1] = Math.Clamp((int)((bmax.Y - option.bmin.Y) * quantFactor), 0, int.MaxValue);
it.bmax[2] = Math.Clamp((int)((bmax.Z - option.bmin.Z) * quantFactor), 0, int.MaxValue);
}
else
{
int p = i * option.nvp * 2;
it.bmin[0] = it.bmax[0] = option.verts[option.polys[p] * 3 + 0];
it.bmin[1] = it.bmax[1] = option.verts[option.polys[p] * 3 + 1];
it.bmin[2] = it.bmax[2] = option.verts[option.polys[p] * 3 + 2];
for (int j = 1; j < option.nvp; ++j)
{
if (option.polys[p + j] == MESH_NULL_IDX)
break;
int x = option.verts[option.polys[p + j] * 3 + 0];
int y = option.verts[option.polys[p + j] * 3 + 1];
int z = option.verts[option.polys[p + j] * 3 + 2];
if (x < it.bmin[0])
it.bmin[0] = x;
if (y < it.bmin[1])
it.bmin[1] = y;
if (z < it.bmin[2])
it.bmin[2] = z;
if (x > it.bmax[0])
it.bmax[0] = x;
if (y > it.bmax[1])
it.bmax[1] = y;
if (z > it.bmax[2])
it.bmax[2] = z;
}
// Remap y
it.bmin[1] = (int)MathF.Floor(it.bmin[1] * option.ch * quantFactor);
it.bmax[1] = (int)MathF.Ceiling(it.bmax[1] * option.ch * quantFactor);
}
}
return Subdivide(items, option.polyCount, 0, option.polyCount, 0, nodes);
}
const int XP = 1 << 0;
const int ZP = 1 << 1;
const int XM = 1 << 2;
const int ZM = 1 << 3;
public static int ClassifyOffMeshPoint(RcVec3f pt, RcVec3f bmin, RcVec3f bmax)
{
int outcode = 0;
outcode |= (pt.X >= bmax.X) ? XP : 0;
outcode |= (pt.Z >= bmax.Z) ? ZP : 0;
outcode |= (pt.X < bmin.X) ? XM : 0;
outcode |= (pt.Z < bmin.Z) ? ZM : 0;
switch (outcode)
{
case XP:
return 0;
case XP | ZP:
return 1;
case ZP:
return 2;
case XM | ZP:
return 3;
case XM:
return 4;
case XM | ZM:
return 5;
case ZM:
return 6;
case XP | ZM:
return 7;
}
return 0xff;
}
/**
* Builds navigation mesh tile data from the provided tile creation data.
*
* @param option
* Tile creation data.
*
* @return created tile data
*/
public static DtMeshData CreateNavMeshData(DtNavMeshCreateParams option)
{
if (option.vertCount >= 0xffff)
return null;
if (option.vertCount == 0 || option.verts == null)
return null;
if (option.polyCount == 0 || option.polys == null)
return null;
int nvp = option.nvp;
// Classify off-mesh connection points. We store only the connections
// whose start point is inside the tile.
int[] offMeshConClass = null;
int storedOffMeshConCount = 0;
int offMeshConLinkCount = 0;
if (option.offMeshConCount > 0)
{
offMeshConClass = new int[option.offMeshConCount * 2];
// Find tight heigh bounds, used for culling out off-mesh start
// locations.
float hmin = float.MaxValue;
float hmax = -float.MaxValue;
if (option.detailVerts != null && option.detailVertsCount != 0)
{
for (int i = 0; i < option.detailVertsCount; ++i)
{
float h = option.detailVerts[i * 3 + 1];
hmin = Math.Min(hmin, h);
hmax = Math.Max(hmax, h);
}
}
else
{
for (int i = 0; i < option.vertCount; ++i)
{
int iv = i * 3;
float h = option.bmin.Y + option.verts[iv + 1] * option.ch;
hmin = Math.Min(hmin, h);
hmax = Math.Max(hmax, h);
}
}
hmin -= option.walkableClimb;
hmax += option.walkableClimb;
RcVec3f bmin = new RcVec3f();
RcVec3f bmax = new RcVec3f();
bmin = option.bmin;
bmax = option.bmax;
bmin.Y = hmin;
bmax.Y = hmax;
for (int i = 0; i < option.offMeshConCount; ++i)
{
var p0 = RcVecUtils.Create(option.offMeshConVerts, (i * 2 + 0) * 3);
var p1 = RcVecUtils.Create(option.offMeshConVerts, (i * 2 + 1) * 3);
offMeshConClass[i * 2 + 0] = ClassifyOffMeshPoint(p0, bmin, bmax);
offMeshConClass[i * 2 + 1] = ClassifyOffMeshPoint(p1, bmin, bmax);
// Zero out off-mesh start positions which are not even
// potentially touching the mesh.
if (offMeshConClass[i * 2 + 0] == 0xff)
{
if (p0.Y < bmin.Y || p0.Y > bmax.Y)
offMeshConClass[i * 2 + 0] = 0;
}
// Count how many links should be allocated for off-mesh
// connections.
if (offMeshConClass[i * 2 + 0] == 0xff)
offMeshConLinkCount++;
if (offMeshConClass[i * 2 + 1] == 0xff)
offMeshConLinkCount++;
if (offMeshConClass[i * 2 + 0] == 0xff)
storedOffMeshConCount++;
}
}
// Off-mesh connections are stored as polygons, adjust values.
int totPolyCount = option.polyCount + storedOffMeshConCount;
int totVertCount = option.vertCount + storedOffMeshConCount * 2;
// Find portal edges which are at tile borders.
int edgeCount = 0;
int portalCount = 0;
for (int i = 0; i < option.polyCount; ++i)
{
int p = i * 2 * nvp;
for (int j = 0; j < nvp; ++j)
{
if (option.polys[p + j] == MESH_NULL_IDX)
break;
edgeCount++;
if ((option.polys[p + nvp + j] & 0x8000) != 0)
{
int dir = option.polys[p + nvp + j] & 0xf;
if (dir != 0xf)
portalCount++;
}
}
}
int maxLinkCount = edgeCount + portalCount * 2 + offMeshConLinkCount * 2;
// Find unique detail vertices.
int uniqueDetailVertCount = 0;
int detailTriCount = 0;
if (option.detailMeshes != null)
{
// Has detail mesh, count unique detail vertex count and use input
// detail tri count.
detailTriCount = option.detailTriCount;
for (int i = 0; i < option.polyCount; ++i)
{
int p = i * nvp * 2;
int ndv = option.detailMeshes[i * 4 + 1];
int nv = 0;
for (int j = 0; j < nvp; ++j)
{
if (option.polys[p + j] == MESH_NULL_IDX)
break;
nv++;
}
ndv -= nv;
uniqueDetailVertCount += ndv;
}
}
else
{
// No input detail mesh, build detail mesh from nav polys.
uniqueDetailVertCount = 0; // No extra detail verts.
detailTriCount = 0;
for (int i = 0; i < option.polyCount; ++i)
{
int p = i * nvp * 2;
int nv = 0;
for (int j = 0; j < nvp; ++j)
{
if (option.polys[p + j] == MESH_NULL_IDX)
break;
nv++;
}
detailTriCount += nv - 2;
}
}
int bvTreeSize = option.buildBvTree ? option.polyCount * 2 : 0;
DtMeshHeader header = new DtMeshHeader();
float[] navVerts = new float[3 * totVertCount];
DtPoly[] navPolys = new DtPoly[totPolyCount];
DtPolyDetail[] navDMeshes = new DtPolyDetail[option.polyCount];
float[] navDVerts = new float[3 * uniqueDetailVertCount];
int[] navDTris = new int[4 * detailTriCount];
DtBVNode[] navBvtree = new DtBVNode[bvTreeSize];
DtOffMeshConnection[] offMeshCons = new DtOffMeshConnection[storedOffMeshConCount];
// Store header
header.magic = DtNavMesh.DT_NAVMESH_MAGIC;
header.version = DtNavMesh.DT_NAVMESH_VERSION;
header.x = option.tileX;
header.y = option.tileZ;
header.layer = option.tileLayer;
header.userId = option.userId;
header.polyCount = totPolyCount;
header.vertCount = totVertCount;
header.maxLinkCount = maxLinkCount;
header.bmin = option.bmin;
header.bmax = option.bmax;
header.detailMeshCount = option.polyCount;
header.detailVertCount = uniqueDetailVertCount;
header.detailTriCount = detailTriCount;
header.bvQuantFactor = 1.0f / option.cs;
header.offMeshBase = option.polyCount;
header.walkableHeight = option.walkableHeight;
header.walkableRadius = option.walkableRadius;
header.walkableClimb = option.walkableClimb;
header.offMeshConCount = storedOffMeshConCount;
header.bvNodeCount = bvTreeSize;
int offMeshVertsBase = option.vertCount;
int offMeshPolyBase = option.polyCount;
// Store vertices
// Mesh vertices
for (int i = 0; i < option.vertCount; ++i)
{
int iv = i * 3;
int v = i * 3;
navVerts[v] = option.bmin.X + option.verts[iv] * option.cs;
navVerts[v + 1] = option.bmin.Y + option.verts[iv + 1] * option.ch;
navVerts[v + 2] = option.bmin.Z + option.verts[iv + 2] * option.cs;
}
// Off-mesh link vertices.
int n = 0;
for (int i = 0; i < option.offMeshConCount; ++i)
{
// Only store connections which start from this tile.
if (offMeshConClass[i * 2 + 0] == 0xff)
{
int linkv = i * 2 * 3;
int v = (offMeshVertsBase + n * 2) * 3;
RcArrays.Copy(option.offMeshConVerts, linkv, navVerts, v, 6);
n++;
}
}
// Store polygons
// Mesh polys
int src = 0;
for (int i = 0; i < option.polyCount; ++i)
{
DtPoly p = new DtPoly(i, nvp);
navPolys[i] = p;
p.vertCount = 0;
p.flags = option.polyFlags[i];
p.SetArea(option.polyAreas[i]);
p.SetPolyType(DtPolyTypes.DT_POLYTYPE_GROUND);
for (int j = 0; j < nvp; ++j)
{
if (option.polys[src + j] == MESH_NULL_IDX)
break;
p.verts[j] = option.polys[src + j];
if ((option.polys[src + nvp + j] & 0x8000) != 0)
{
// Border or portal edge.
int dir = option.polys[src + nvp + j] & 0xf;
if (dir == 0xf) // Border
p.neis[j] = 0;
else if (dir == 0) // Portal x-
p.neis[j] = DtNavMesh.DT_EXT_LINK | 4;
else if (dir == 1) // Portal z+
p.neis[j] = DtNavMesh.DT_EXT_LINK | 2;
else if (dir == 2) // Portal x+
p.neis[j] = DtNavMesh.DT_EXT_LINK | 0;
else if (dir == 3) // Portal z-
p.neis[j] = DtNavMesh.DT_EXT_LINK | 6;
}
else
{
// Normal connection
p.neis[j] = option.polys[src + nvp + j] + 1;
}
p.vertCount++;
}
src += nvp * 2;
}
// Off-mesh connection vertices.
n = 0;
for (int i = 0; i < option.offMeshConCount; ++i)
{
// Only store connections which start from this tile.
if (offMeshConClass[i * 2 + 0] == 0xff)
{
DtPoly p = new DtPoly(offMeshPolyBase + n, nvp);
navPolys[offMeshPolyBase + n] = p;
p.vertCount = 2;
p.verts[0] = offMeshVertsBase + n * 2;
p.verts[1] = offMeshVertsBase + n * 2 + 1;
p.flags = option.offMeshConFlags[i];
p.SetArea(option.offMeshConAreas[i]);
p.SetPolyType(DtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION);
n++;
}
}
// Store detail meshes and vertices.
// The nav polygon vertices are stored as the first vertices on each
// mesh.
// We compress the mesh data by skipping them and using the navmesh
// coordinates.
if (option.detailMeshes != null)
{
int vbase = 0;
for (int i = 0; i < option.polyCount; ++i)
{
int vb = option.detailMeshes[i * 4 + 0];
int ndv = option.detailMeshes[i * 4 + 1];
int nv = navPolys[i].vertCount;
int vertBase = vbase;
int vertCount = (ndv - nv);
int triBase = option.detailMeshes[i * 4 + 2];
int triCount = option.detailMeshes[i * 4 + 3];
navDMeshes[i] = new DtPolyDetail(vertBase, triBase, vertCount, triCount);
// Copy vertices except the first 'nv' verts which are equal to
// nav poly verts.
if (ndv - nv != 0)
{
RcArrays.Copy(option.detailVerts, (vb + nv) * 3, navDVerts, vbase * 3, 3 * (ndv - nv));
vbase += ndv - nv;
}
}
// Store triangles.
RcArrays.Copy(option.detailTris, 0, navDTris, 0, 4 * option.detailTriCount);
}
else
{
// Create dummy detail mesh by triangulating polys.
int tbase = 0;
for (int i = 0; i < option.polyCount; ++i)
{
int nv = navPolys[i].vertCount;
int vertBase = 0;
int vertCount = 0;
int triBase = tbase;
int triCount = (nv - 2);
navDMeshes[i] = new DtPolyDetail(vertBase, triBase, vertCount, triCount);
// Triangulate polygon (local indices).
for (int j = 2; j < nv; ++j)
{
int t = tbase * 4;
navDTris[t + 0] = 0;
navDTris[t + 1] = (j - 1);
navDTris[t + 2] = j;
// Bit for each edge that belongs to poly boundary.
navDTris[t + 3] = (1 << 2);
if (j == 2)
navDTris[t + 3] |= (1 << 0);
if (j == nv - 1)
navDTris[t + 3] |= (1 << 4);
tbase++;
}
}
}
// Store and create BVtree.
// TODO: take detail mesh into account! use byte per bbox extent?
if (option.buildBvTree)
{
// Do not set header.bvNodeCount set to make it work look exactly the same as in original Detour
header.bvNodeCount = CreateBVTree(option, navBvtree);
}
// Store Off-Mesh connections.
n = 0;
for (int i = 0; i < option.offMeshConCount; ++i)
{
// Only store connections which start from this tile.
if (offMeshConClass[i * 2 + 0] == 0xff)
{
DtOffMeshConnection con = new DtOffMeshConnection();
offMeshCons[n] = con;
con.poly = (offMeshPolyBase + n);
// Copy connection end-points.
int endPts = i * 2 * 3;
for (int j = 0; j < 2; ++j)
{
con.pos[j] = RcVecUtils.Create(option.offMeshConVerts, endPts + (j * 3));
}
con.rad = option.offMeshConRad[i];
con.flags = option.offMeshConDir[i] != 0 ? DtNavMesh.DT_OFFMESH_CON_BIDIR : 0;
con.side = offMeshConClass[i * 2 + 1];
if (option.offMeshConUserID != null)
con.userId = option.offMeshConUserID[i];
n++;
}
}
DtMeshData nmd = new DtMeshData();
nmd.header = header;
nmd.verts = navVerts;
nmd.polys = navPolys;
nmd.detailMeshes = navDMeshes;
nmd.detailVerts = navDVerts;
nmd.detailTris = navDTris;
nmd.bvTree = navBvtree;
nmd.offMeshCons = offMeshCons;
return nmd;
}
}
}