public void Process(ref NavMeshCreateParams param, NavMeshTileBuildContext bc) { // Update poly flags from areas. for (int i = 0; i < param.polyCount; ++i) { if ((int)bc.LMesh.Areas[i] == (int)TileCacheAreas.RC_WALKABLE_AREA) { bc.LMesh.Areas[i] = SamplePolyAreas.SAMPLE_POLYAREA_GROUND; } if (bc.LMesh.Areas[i] == SamplePolyAreas.SAMPLE_POLYAREA_GROUND || bc.LMesh.Areas[i] == SamplePolyAreas.SAMPLE_POLYAREA_GRASS || bc.LMesh.Areas[i] == SamplePolyAreas.SAMPLE_POLYAREA_ROAD) { bc.LMesh.Flags[i] = SamplePolyFlagTypes.SAMPLE_POLYFLAGS_WALK; } else if (bc.LMesh.Areas[i] == SamplePolyAreas.SAMPLE_POLYAREA_WATER) { bc.LMesh.Flags[i] = SamplePolyFlagTypes.SAMPLE_POLYFLAGS_SWIM; } else if (bc.LMesh.Areas[i] == SamplePolyAreas.SAMPLE_POLYAREA_DOOR) { bc.LMesh.Flags[i] = SamplePolyFlagTypes.SAMPLE_POLYFLAGS_WALK | SamplePolyFlagTypes.SAMPLE_POLYFLAGS_DOOR; } } // Pass in off-mesh connections. if (m_geom != null) { param.offMeshCon = m_geom.GetConnections().ToArray(); param.offMeshConCount = m_geom.GetConnectionCount(); } }
public bool BuildNavMeshTile(CompressedTile tile, NavMesh navmesh) { NavMeshTileBuildContext bc = new NavMeshTileBuildContext(); int walkableClimbVx = (int)(m_params.WalkableClimb / m_params.CellHeight); // Decompress tile layer data. if (!DetourTileCache.DecompressTileCacheLayer(tile.Header, tile.Data, 0, out var layer)) { return(false); } bc.Layer = layer; // Rasterize obstacles. for (int i = 0; i < m_params.MaxObstacles; ++i) { var ob = m_obstacles[i]; if (ob.State == ObstacleState.DT_OBSTACLE_EMPTY || ob.State == ObstacleState.DT_OBSTACLE_REMOVING) { continue; } if (DetourTileCache.Contains(ob.Touched, ob.NTouched, tile)) { if (ob.Type == ObstacleType.DT_OBSTACLE_CYLINDER) { DetourTileCache.MarkCylinderArea(bc, tile.Header.BBox.Minimum, m_params.CellSize, m_params.CellHeight, ob.Cylinder.Pos, ob.Cylinder.Radius, ob.Cylinder.Height, 0); } else if (ob.Type == ObstacleType.DT_OBSTACLE_BOX) { DetourTileCache.MarkBoxArea(bc, tile.Header.BBox.Minimum, m_params.CellSize, m_params.CellHeight, ob.Box.BMin, ob.Box.BMax, 0); } else if (ob.Type == ObstacleType.DT_OBSTACLE_ORIENTED_BOX) { DetourTileCache.MarkBoxArea(bc, tile.Header.BBox.Minimum, m_params.CellSize, m_params.CellHeight, ob.OrientedBox.Center, ob.OrientedBox.HalfExtents, ob.OrientedBox.RotAux, 0); } } } // Build navmesh if (!DetourTileCache.BuildTileCacheRegions(bc, walkableClimbVx)) { return(false); } if (!DetourTileCache.BuildTileCacheContours(bc, walkableClimbVx, m_params.MaxSimplificationError)) { return(false); } if (!DetourTileCache.BuildTileCachePolyMesh(bc)) { return(false); } // Early out if the mesh tile is empty. if (bc.LMesh.NPolys == 0) { // Remove existing tile. navmesh.RemoveTile(navmesh.GetTileRefAt(tile.Header.TX, tile.Header.TY, tile.Header.TLayer), null, 0); return(true); } var param = new NavMeshCreateParams { Verts = bc.LMesh.Verts, VertCount = bc.LMesh.NVerts, Polys = bc.LMesh.Polys, PolyAreas = bc.LMesh.Areas, PolyFlags = bc.LMesh.Flags, polyCount = bc.LMesh.NPolys, nvp = Detour.DT_VERTS_PER_POLYGON, walkableHeight = m_params.WalkableHeight, walkableRadius = m_params.WalkableRadius, walkableClimb = m_params.WalkableClimb, tileX = tile.Header.TX, tileY = tile.Header.TY, tileLayer = tile.Header.TLayer, cs = m_params.CellSize, ch = m_params.CellHeight, buildBvTree = false, bmin = tile.Header.BBox.Minimum, bmax = tile.Header.BBox.Maximum, }; if (m_tmproc != null) { m_tmproc.Process(ref param, bc); } if (!Detour.CreateNavMeshData(param, out MeshData navData)) { return(false); } // Remove existing tile. navmesh.RemoveTile(navmesh.GetTileRefAt(tile.Header.TX, tile.Header.TY, tile.Header.TLayer), null, 0); // Add new tile, or leave the location empty. if (navData != null && !navmesh.AddTile(navData, TileFlagTypes.DT_TILE_FREE_DATA, 0, out int result)) { // Let the navmesh own the data. navData = null; return(false); } return(true); }
public static bool CreateNavMeshData(NavMeshCreateParams param, out MeshData outData) { outData = null; if (param.nvp > DT_VERTS_PER_POLYGON) { return(false); } if (param.VertCount >= 0xffff) { return(false); } if (param.VertCount == 0 || param.Verts == null) { return(false); } if (param.polyCount == 0 || param.Polys == null) { return(false); } int nvp = param.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 (param.offMeshConCount > 0) { offMeshConClass = new int[param.offMeshConCount * 2]; // Find tight heigh bounds, used for culling out off-mesh start locations. float hmin = float.MaxValue; float hmax = float.MinValue; if (param.detailVerts != null && param.detailVertsCount > 0) { for (int i = 0; i < param.detailVertsCount; ++i) { var h = param.detailVerts[i].Y; hmin = Math.Min(hmin, h); hmax = Math.Max(hmax, h); } } else { for (int i = 0; i < param.VertCount; ++i) { var iv = param.Verts[i]; float h = param.bmin.Y + iv.Y * param.ch; hmin = Math.Min(hmin, h); hmax = Math.Max(hmax, h); } } hmin -= param.walkableClimb; hmax += param.walkableClimb; Vector3 bmin = param.bmin; Vector3 bmax = param.bmax; bmin.Y = hmin; bmax.Y = hmax; for (int i = 0; i < param.offMeshConCount; ++i) { var p0 = param.offMeshCon[i].Start; var p1 = param.offMeshCon[i].End; offMeshConClass[i + 0] = ClassifyOffMeshPoint(p0, bmin, bmax); offMeshConClass[i + 1] = ClassifyOffMeshPoint(p1, bmin, bmax); // Zero out off-mesh start positions which are not even potentially touching the mesh. if (offMeshConClass[i * 2] == 0xff && (p0.Y <bmin.Y || p0.Y> bmax.Y)) { offMeshConClass[i * 2] = 0; } // Count how many links should be allocated for off-mesh connections. if (offMeshConClass[i * 2] == 0xff) { offMeshConLinkCount++; } if (offMeshConClass[(i * 2) + 1] == 0xff) { offMeshConLinkCount++; } if (offMeshConClass[i * 2] == 0xff) { storedOffMeshConCount++; } } } // Off-mesh connectionss are stored as polygons, adjust values. int totPolyCount = param.polyCount + storedOffMeshConCount; int totVertCount = param.VertCount + storedOffMeshConCount * 2; // Find portal edges which are at tile borders. int edgeCount = 0; int portalCount = 0; for (int i = 0; i < param.polyCount; ++i) { var p = param.Polys[i]; for (int j = 0; j < nvp; ++j) { if (p[j] == MESH_NULL_IDX) { break; } edgeCount++; if ((p[nvp + j] & 0x8000) != 0) { var dir = 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 (param.detailMeshes != null) { // Has detail mesh, count unique detail vertex count and use input detail tri count. detailTriCount = param.detailTriCount; for (int i = 0; i < param.polyCount; ++i) { var p = param.Polys[i]; var ndv = param.detailMeshes[i].Y; int nv = 0; for (int j = 0; j < nvp; ++j) { if (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 < param.polyCount; ++i) { var p = param.Polys[i]; int nv = 0; for (int j = 0; j < nvp; ++j) { if (p[j] == MESH_NULL_IDX) { break; } nv++; } detailTriCount += nv - 2; } } MeshData data = new MeshData { // Store header Header = new MeshHeader { Magic = DT_NAVMESH_MAGIC, Version = DT_NAVMESH_VERSION, X = param.tileX, Y = param.tileY, Layer = param.tileLayer, UserId = param.userId, PolyCount = totPolyCount, VertCount = totVertCount, MaxLinkCount = maxLinkCount, BMin = param.bmin, BMax = param.bmax, DetailMeshCount = param.polyCount, DetailVertCount = uniqueDetailVertCount, DetailTriCount = detailTriCount, BvQuantFactor = 1.0f / param.cs, OffMeshBase = param.polyCount, WalkableHeight = param.walkableHeight, WalkableRadius = param.walkableRadius, WalkableClimb = param.walkableClimb, OffMeshConCount = storedOffMeshConCount, BvNodeCount = param.buildBvTree ? param.polyCount * 2 : 0 } }; int offMeshVertsBase = param.VertCount; int offMeshPolyBase = param.polyCount; // Store vertices // Mesh vertices for (int i = 0; i < param.VertCount; ++i) { var iv = param.Verts[i]; var v = new Vector3 { X = param.bmin.X + iv.X * param.cs, Y = param.bmin.Y + iv.Y * param.ch, Z = param.bmin.Z + iv.Z * param.cs }; data.NavVerts.Add(v); } // Off-mesh link vertices. int n = 0; for (int i = 0; i < param.offMeshConCount; ++i) { // Only store connections which start from this tile. if (offMeshConClass[i * 2] == 0xff) { var linkv = param.offMeshCon[i]; data.NavVerts.Add(linkv.Start); data.NavVerts.Add(linkv.End); n++; } } // Store polygons // Mesh polys int srcIndex = 0; for (int i = 0; i < param.polyCount; ++i) { var src = param.Polys[srcIndex]; Poly p = new Poly { VertCount = 0, Flags = param.PolyFlags[i], Area = param.PolyAreas[i], Type = PolyTypes.DT_POLYTYPE_GROUND, }; for (int j = 0; j < nvp; ++j) { if (src[j] == MESH_NULL_IDX) { break; } p.Verts[j] = src[j]; if ((src[nvp + j] & 0x8000) != 0) { // Border or portal edge. var dir = src[nvp + j] & 0xf; if (dir == 0xf) // Border { p.Neis[j] = 0; } else if (dir == 0) // Portal x- { p.Neis[j] = DT_EXT_LINK | 4; } else if (dir == 1) // Portal z+ { p.Neis[j] = DT_EXT_LINK | 2; } else if (dir == 2) // Portal x+ { p.Neis[j] = DT_EXT_LINK; } else if (dir == 3) // Portal z- { p.Neis[j] = DT_EXT_LINK | 6; } } else { // Normal connection p.Neis[j] = src[nvp + j] + 1; } p.VertCount++; } data.NavPolys.Add(p); srcIndex++; } // Off-mesh connection vertices. n = 0; for (int i = 0; i < param.offMeshConCount; ++i) { // Only store connections which start from this tile. if (offMeshConClass[i * 2] == 0xff) { Poly p = new Poly { Flags = (SamplePolyFlagTypes)param.offMeshCon[i].FlagTypes, Area = (SamplePolyAreas)param.offMeshCon[i].AreaType, Type = PolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION }; p.Verts[0] = (offMeshVertsBase + (n * 2) + 0); p.Verts[1] = (offMeshVertsBase + (n * 2) + 1); p.VertCount = 2; data.NavPolys.Add(p); 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 (param.detailMeshes != null) { for (int i = 0; i < param.polyCount; ++i) { int vb = param.detailMeshes[i][0]; int ndv = param.detailMeshes[i][1]; int nv = data.NavPolys[i].VertCount; PolyDetail dtl = new PolyDetail { VertBase = data.NavDVerts.Count, VertCount = (ndv - nv), TriBase = param.detailMeshes[i][2], TriCount = param.detailMeshes[i][3] }; // Copy vertices except the first 'nv' verts which are equal to nav poly verts. if (ndv - nv != 0) { var verts = param.detailVerts.Skip(vb + nv).Take(ndv - nv); data.NavDVerts.AddRange(verts); } data.NavDMeshes.Add(dtl); } // Store triangles. data.NavDTris.AddRange(param.detailTris); } else { // Create dummy detail mesh by triangulating polys. int tbase = 0; for (int i = 0; i < param.polyCount; ++i) { int nv = data.NavPolys[i].VertCount; PolyDetail dtl = new PolyDetail { VertBase = 0, VertCount = 0, TriBase = tbase, TriCount = (nv - 2) }; // Triangulate polygon (local indices). for (int j = 2; j < nv; ++j) { var t = new Int4 { X = 0, Y = (j - 1), Z = j, // Bit for each edge that belongs to poly boundary. W = (1 << 2) }; if (j == 2) { t.W |= (1 << 0); } if (j == nv - 1) { t.W |= (1 << 4); } tbase++; data.NavDTris.Add(t); } data.NavDMeshes.Add(dtl); } } // Store and create BVtree. if (param.buildBvTree) { CreateBVTree(param, out var nodes); data.NavBvtree.AddRange(nodes); } // Store Off-Mesh connections. n = 0; for (int i = 0; i < param.offMeshConCount; ++i) { // Only store connections which start from this tile. if (offMeshConClass[i * 2] == 0xff) { // Copy connection end-points. var endPts1 = param.offMeshCon[i].Start; var endPts2 = param.offMeshCon[i].End; var con = new OffMeshConnection { poly = offMeshPolyBase + n, rad = param.offMeshCon[i].Radius, flags = param.offMeshCon[i].Direction != 0 ? DT_OFFMESH_CON_BIDIR : 0, side = offMeshConClass[i * 2 + 1], start = endPts1, end = endPts2, userId = param.offMeshCon[i].Id, }; data.OffMeshCons.Add(con); n++; } } outData = data; return(true); }
public static int CreateBVTree(NavMeshCreateParams param, out List <BVNode> nodes) { nodes = new List <BVNode>(); // Build tree float quantFactor = 1 / param.cs; BVItem[] items = new BVItem[param.polyCount]; for (int i = 0; i < param.polyCount; i++) { var it = items[i]; it.I = i; // Calc polygon bounds. Use detail meshes if available. if (param.detailMeshes != null) { int vb = param.detailMeshes[i][0]; int ndv = param.detailMeshes[i][1]; var bmin = param.detailVerts[vb]; var bmax = param.detailVerts[vb]; for (int j = 1; j < ndv; j++) { bmin = Vector3.Min(bmin, param.detailVerts[vb + j]); bmax = Vector3.Max(bmax, param.detailVerts[vb + j]); } // BV-tree uses cs for all dimensions it.BMin = new Int3 { X = MathUtil.Clamp((int)((bmin.X - param.bmin.X) * quantFactor), 0, 0xffff), Y = MathUtil.Clamp((int)((bmin.Y - param.bmin.Y) * quantFactor), 0, 0xffff), Z = MathUtil.Clamp((int)((bmin.Z - param.bmin.Z) * quantFactor), 0, 0xffff) }; it.BMax = new Int3 { X = MathUtil.Clamp((int)((bmax.X - param.bmin.X) * quantFactor), 0, 0xffff), Y = MathUtil.Clamp((int)((bmax.Y - param.bmin.Y) * quantFactor), 0, 0xffff), Z = MathUtil.Clamp((int)((bmax.Z - param.bmin.Z) * quantFactor), 0, 0xffff) }; } else { var p = param.Polys[i]; var itBMin = param.Verts[p[0]]; var itBMax = param.Verts[p[0]]; for (int j = 1; j < param.nvp; ++j) { if (p[j] == MESH_NULL_IDX) { break; } var x = param.Verts[p[j]].X; var y = param.Verts[p[j]].Y; var z = param.Verts[p[j]].Z; if (x < it.BMin.X) { itBMin.X = x; } if (y < it.BMin.Y) { itBMin.Y = y; } if (z < it.BMin.Z) { itBMin.Z = z; } if (x > it.BMax.X) { itBMax.X = x; } if (y > it.BMax.Y) { itBMax.Y = y; } if (z > it.BMax.Z) { itBMax.Z = z; } } // Remap y itBMin.Y = (int)Math.Floor(it.BMin.Y * param.ch / param.cs); itBMax.Y = (int)Math.Ceiling(it.BMax.Y * param.ch / param.cs); it.BMin = itBMin; it.BMax = itBMax; } items[i] = it; } int curNode = 0; Subdivide(items, param.polyCount, 0, param.polyCount, ref curNode, ref nodes); return(curNode); }