/// <summary> /// Connect Off-Mesh links between polygons from two different tiles. /// </summary> /// <param name="tile">Current Tile</param> /// <param name="target">Target Tile</param> /// <param name="side">Polygon edge</param> public void ConnectExtOffMeshLinks(ref MeshTile tile, ref MeshTile target, BoundarySide side) { if (tile == null) { return; } //Connect off-mesh links, specifically links which land from target tile to this tile BoundarySide oppositeSide = side.GetOpposite(); //Iterate through all the off-mesh connections of target tile for (int i = 0; i < target.Header.OffMeshConCount; i++) { OffMeshConnection targetCon = target.OffMeshConnections[i]; if (targetCon.Side != oppositeSide) { continue; } Poly targetPoly = target.Polys[targetCon.Poly]; //Skip off-mesh connections which start location could not be connected at all if (!IsLinkAllocated(targetPoly.FirstLink)) { continue; } Vector3 extents = new Vector3(targetCon.Radius, target.Header.WalkableClimb, targetCon.Radius); //Find polygon to connect to Vector3 p = targetCon.Pos1; Vector3 nearestPt = new Vector3(); PolyId reference = FindNearestPolyInTile(tile, p, extents, ref nearestPt); if (reference == PolyId.Null) { continue; } //Further checks if ((nearestPt.X - p.X) * (nearestPt.X - p.X) + (nearestPt.Z - p.Z) * (nearestPt.Z - p.Z) > (targetCon.Radius * targetCon.Radius)) { continue; } //Make sure the location is on the current mesh target.Verts[targetPoly.Verts[1]] = nearestPt; //Link off-mesh connection to target poly int idx = AllocLink(target); if (IsLinkAllocated(idx)) { target.Links[idx].Reference = reference; target.Links[idx].Edge = i; target.Links[idx].Side = oppositeSide; target.Links[idx].BMin = target.Links[idx].BMax = 0; //add to linked list target.Links[idx].Next = target.Polys[i].FirstLink; target.Polys[i].FirstLink = idx; } //link target poly to off-mesh connection if ((targetCon.Flags & OffMeshConnectionFlags.Bidirectional) != 0) { int tidx = AllocLink(tile); if (IsLinkAllocated(tidx)) { int landPolyIdx = reference.DecodePolyIndex(polyBits); PolyId id; id = GetPolyRefBase(target); PolyId.SetPolyIndex(ref id, targetCon.Poly, out id); tile.Links[tidx].Reference = id; tile.Links[tidx].Edge = 0xff; tile.Links[tidx].Side = side; tile.Links[tidx].BMin = tile.Links[tidx].BMax = 0; //add to linked list tile.Links[tidx].Next = tile.Polys[landPolyIdx].FirstLink; tile.Polys[landPolyIdx].FirstLink = tidx; } } } }
public byte[] Build(int i = 0, int j = 0) { float[] bbMin, bbMax; CalculateTileBounds(out bbMin, out bbMax, false, i, j); #if (TIMETHIS) System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch(); stopWatch.Start(); long cur = 0; #endif // add border bbMin[0] -= Config.BorderSize * Config.CellSize; bbMin[2] -= Config.BorderSize * Config.CellSize; bbMax[0] += Config.BorderSize * Config.CellSize; bbMax[2] += Config.BorderSize * Config.CellSize; // get raw geometry - lots of slowness here float[] vertices; int[] triangles; byte[] areas; Geometry.GetRawData(out vertices, out triangles, out areas); #if (TIMETHIS) Console.WriteLine("GetRawData: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif // Following code would check if we have Index out of range error while computing the sum. the result itself is useless. /*float sum = 0; * foreach (int i2 in triangles) * sum += vertices[i2*3+0] + vertices[i2*3+1] + vertices[i2*3+2]; * Console.WriteLine(sum);*/ // now we can find the min/max height for THIS tile float MinHeight, MaxHeight; Geometry.CalculateMinMaxHeight(out MinHeight, out MaxHeight, bbMin, bbMax); //Geometry.Triangles.Clear(); // We should not clear triangles and stuff because we have severals files for one tile. //Geometry.Vertices.Clear(); // It will be reset at the next tile anyway, no memory issues. bbMin[1] = MinHeight; bbMax[1] = MaxHeight; Heightfield hf; int width = Config.TileWidth + (Config.BorderSize * 2); if (!Context.CreateHeightfield(out hf, width, width, bbMin, bbMax, Config.CellSize, Config.CellHeight)) { throw new OutOfMemoryException("CreateHeightfield ran out of memory"); } #if (TIMETHIS) Console.WriteLine("CreateHeightfield: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif if (triangles.Count() > 0) { /*Console.WriteLine("Context.ClearUnwalkableTriangles: verticles: " + vertices.Length + ", triangles: " + triangles.Length + ", areas: " + areas.Length); * Console.WriteLine("Memory allocated GC: " + GC.GetTotalMemory(true));/*/ Context.ClearUnwalkableTriangles(Config.WalkableSlopeAngle, ref vertices, ref triangles, areas); #if (TIMETHIS) Console.WriteLine("ClearUnwalkableTriangles: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif Context.RasterizeTriangles(ref vertices, ref triangles, ref areas, hf, Config.WalkableClimb); #if (TIMETHIS) Console.WriteLine("RasterizeTriangles: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif GC.KeepAlive(vertices); // force C# to keep vertices, triangles and areas alive while it's in unamanaged code. GC.KeepAlive(triangles); GC.KeepAlive(areas); } vertices = null; triangles = null; areas = null; GC.Collect(); // Once all geometry is rasterized, we do initial pass of filtering to // remove unwanted overhangs caused by the conservative rasterization // as well as filter spans where the character cannot possibly stand. Context.FilterLowHangingWalkableObstacles(Config.WalkableClimb, hf); #if (TIMETHIS) Console.WriteLine("FilterLowHangingWalkableObstacles: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif Context.FilterLedgeSpans(Config.WalkableHeight, Config.WalkableClimb, hf); #if (TIMETHIS) Console.WriteLine("FilterLedgeSpans: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif Context.FilterWalkableLowHeightSpans(Config.WalkableHeight, hf); #if (TIMETHIS) Console.WriteLine("FilterWalkableLowHeightSpans: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif // Rasterize once again after the cleanup we did //Context.RasterizeTriangles(ref vertices, ref triangles, ref areas, hf, Config.WalkableClimb); //#if (TIMETHIS) // Console.WriteLine("RasterizeTriangles: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); // cur = stopWatch.ElapsedMilliseconds; //#endif // Compact the heightfield so that it is faster to handle from now on. // This will result in more cache coherent data as well as the neighbours // between walkable cells will be calculated. CompactHeightfield chf; if (!Context.BuildCompactHeightfield(Config.WalkableHeight, Config.WalkableClimb, hf, out chf)) { throw new OutOfMemoryException("BuildCompactHeightfield ran out of memory"); } #if (TIMETHIS) Console.WriteLine("BuildCompactHeightfield: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif hf.Delete(); // Erode the walkable area by agent radius. if (!Context.ErodeWalkableArea(Config.WalkableRadius, chf)) { throw new OutOfMemoryException("ErodeWalkableArea ran out of memory"); } #if (TIMETHIS) Console.WriteLine("ErodeWalkableArea: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif // Prepare for region partitioning, by calculating distance field along the walkable surface. if (!Context.BuildDistanceField(chf)) { throw new OutOfMemoryException("BuildDistanceField ran out of memory"); } #if (TIMETHIS) Console.WriteLine("BuildDistanceField: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif // Partition the walkable surface into simple regions without holes. if (!Context.BuildRegions(chf, Config.BorderSize, Config.MinRegionArea, Config.MergeRegionArea)) { throw new OutOfMemoryException("BuildRegions ran out of memory"); } #if (TIMETHIS) Console.WriteLine("BuildRegions: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif // Create contours. ContourSet cset; if (!Context.BuildContours(chf, Config.MaxSimplificationError, Config.MaxEdgeLength, out cset)) { throw new OutOfMemoryException("BuildContours ran out of memory"); } #if (TIMETHIS) Console.WriteLine("BuildContours: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif // Build polygon navmesh from the contours. PolyMesh pmesh; if (!Context.BuildPolyMesh(cset, Config.MaxVertsPerPoly, out pmesh)) { throw new OutOfMemoryException("BuildPolyMesh ran out of memory"); } #if (TIMETHIS) Console.WriteLine("BuildPolyMesh: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); cur = stopWatch.ElapsedMilliseconds; #endif // Build detail mesh. PolyMeshDetail dmesh = null; //if (!Context.BuildPolyMeshDetail(pmesh, chf, Config.DetailSampleDistance, Config.DetailSampleMaxError,out dmesh)) // throw new OutOfMemoryException("BuildPolyMeshDetail ran out of memory"); //#if (TIMETHIS) // Console.WriteLine("BuildPolyMeshDetail: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); // cur = stopWatch.ElapsedMilliseconds; //#endif chf.Delete(); cset.Delete(); // Set flags according to area types (e.g. Swim for Water) pmesh.MarkAll(); // get original bounds float[] tilebMin, tilebMax; CalculateTileBounds(out tilebMin, out tilebMax, false, i, j); tilebMin[1] = bbMin[1]; tilebMax[1] = bbMax[1]; // build off mesh connections for flightmasters // bMax and bMin are switched here because of the coordinate system transformation var connections = new List <OffMeshConnection>(); if (MapId == 1220) { SlimDX.Vector3 from = new SlimDX.Vector3(1685.681f, 4709.121f, 139.4296f); SlimDX.Vector3 to = new SlimDX.Vector3(1682.194f, 4712.713f, 139.6532f); OffMeshConnection conn = new OffMeshConnection { AreaId = PolyArea.Road, Flags = PolyFlag.Walk, From = from.ToRecast().ToFloatArray(), To = to.ToRecast().ToFloatArray(), Radius = 5, Type = ConnectionType.BiDirectional, UserID = 1, }; connections.Add(conn); from = new SlimDX.Vector3(1661.302f, 4727.02f, 139.4618f); to = new SlimDX.Vector3(1669.568f, 4733.722f, 138.7087f); conn = new OffMeshConnection { AreaId = PolyArea.Road, Flags = PolyFlag.Walk, From = from.ToRecast().ToFloatArray(), To = to.ToRecast().ToFloatArray(), Radius = 5f, Type = ConnectionType.BiDirectional, UserID = 2, }; connections.Add(conn); } /*if (MapId == 0 && X == 31 && Y == 58) * { * //<X>-14250.71</X> * //<Y>329.7578</Y> * //<Z>24.17678</Z> * * //<X>-14254.86</X> * //<Y>336.1039</Y> * //<Z>26.79213</Z> * * SlimDX.Vector3 from = new SlimDX.Vector3(1.0f, 1.0f, 1.0f); * SlimDX.Vector3 to = new SlimDX.Vector3(1.0f, 1.0f, 1.0f); * OffMeshConnection conn = new OffMeshConnection * { * From = from.ToRecast().ToFloatArray(), * To = to.ToRecast().ToFloatArray(), * Radius = 2.5f, * Type = ConnectionType.BiDirectional, * UserID = 88, * }; * connections.Add(conn); * }*/ /* // Disable mesh connexion RIVAL * var taxis = TaxiHelper.GetNodesInBBox(MapId, tilebMax.ToWoW(), tilebMin.ToWoW()); * foreach (var taxi in taxis) * { * Log.Log(LogCategory.Warning, * "Flightmaster \"" + taxi.Name + "\", Id: " + taxi.Id + " Horde: " + taxi.IsHorde + " Alliance: " + * taxi.IsAlliance); * * var data = TaxiHelper.GetTaxiData(taxi); * var from = taxi.Location.ToRecast().ToFloatArray(); * connections.AddRange(data.To.Select(to => new OffMeshConnection * { * AreaId = PolyArea.Road, * Flags = PolyFlag.FlightMaster, * From = from, * To = to.Value.Location.ToRecast().ToFloatArray(), * Radius = Config.WorldWalkableRadius, * Type = ConnectionType.OneWay, * UserID = (uint) to.Key * })); * * foreach (var target in data.To) * Log.Log(LogCategory.Warning, * "\tPath to: \"" + target.Value.Name + "\" Id: " + target.Value.Id + " Path Id: " + * target.Key); * }*/ byte[] tileData; if (!Detour.CreateNavMeshData(out tileData, pmesh, dmesh, X * Constant.Division + i, Y * Constant.Division + j, tilebMin, tilebMax, Config.WorldWalkableHeight, Config.WorldWalkableRadius, Config.WorldWalkableClimb, Config.CellSize, Config.CellHeight, Config.BuildBvTree, connections.ToArray())) { pmesh.Delete(); //dmesh.Delete(); return(null); } #if (TIMETHIS) Console.WriteLine("CreateNavMeshData: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds); #endif pmesh.Delete(); //dmesh.Delete(); GC.Collect(); return(tileData); }
/// <summary> /// Initializes a new instance of the <see cref="NavMeshBuilder" /> class. /// Add all the PolyMesh and PolyMeshDetail attributes to the Navigation Mesh. /// Then, add Off-Mesh connection support. /// </summary> /// <param name="polyMesh">The PolyMesh</param> /// <param name="polyMeshDetail">The PolyMeshDetail</param> /// <param name="offMeshCons">Offmesh connection data</param> /// <param name="settings">The settings used to build.</param> public NavMeshBuilder(PolyMesh polyMesh, PolyMeshDetail polyMeshDetail, OffMeshConnection[] offMeshCons, NavMeshGenerationSettings settings) { if (settings.VertsPerPoly > PathfindingCommon.VERTS_PER_POLYGON) { throw new InvalidOperationException("The number of vertices per polygon is above SharpNav's limit"); } if (polyMesh.VertCount == 0) { throw new InvalidOperationException("The provided PolyMesh has no vertices."); } if (polyMesh.PolyCount == 0) { throw new InvalidOperationException("The provided PolyMesh has not polys."); } int nvp = settings.VertsPerPoly; //classify off-mesh connection points BoundarySide[] offMeshSides = new BoundarySide[offMeshCons.Length * 2]; int storedOffMeshConCount = 0; int offMeshConLinkCount = 0; if (offMeshCons.Length > 0) { //find height bounds float hmin = float.MaxValue; float hmax = -float.MaxValue; if (polyMeshDetail != null) { for (int i = 0; i < polyMeshDetail.VertCount; i++) { float h = polyMeshDetail.Verts[i].Y; hmin = Math.Min(hmin, h); hmax = Math.Max(hmax, h); } } else { for (int i = 0; i < polyMesh.VertCount; i++) { PolyVertex iv = polyMesh.Verts[i]; float h = polyMesh.Bounds.Min.Y + iv.Y * settings.CellHeight; hmin = Math.Min(hmin, h); hmax = Math.Max(hmax, h); } } hmin -= settings.MaxClimb; hmax += settings.MaxClimb; BBox3 bounds = polyMesh.Bounds; bounds.Min.Y = hmin; bounds.Max.Y = hmax; for (int i = 0; i < offMeshCons.Length; i++) { Vector3 p0 = offMeshCons[i].Pos0; Vector3 p1 = offMeshCons[i].Pos1; offMeshSides[i * 2 + 0] = BoundarySideExtensions.FromPoint(p0, bounds); offMeshSides[i * 2 + 1] = BoundarySideExtensions.FromPoint(p1, bounds); //off-mesh start position isn't touching mesh if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { if (p0.Y < bounds.Min.Y || p0.Y > bounds.Max.Y) { offMeshSides[i * 2 + 0] = 0; } } //count number of links to allocate if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { offMeshConLinkCount++; } if (offMeshSides[i * 2 + 1] == BoundarySide.Internal) { offMeshConLinkCount++; } if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { storedOffMeshConCount++; } } } //off-mesh connections stored as polygons, adjust values int totPolyCount = polyMesh.PolyCount + storedOffMeshConCount; int totVertCount = polyMesh.VertCount + storedOffMeshConCount * 2; //find portal edges int edgeCount = 0; int portalCount = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { PolyMesh.Polygon p = polyMesh.Polys[i]; for (int j = 0; j < nvp; j++) { if (p.Vertices[j] == PolyMesh.NullId) { break; } edgeCount++; if (PolyMesh.IsBoundaryEdge(p.NeighborEdges[j])) { int dir = p.NeighborEdges[j] % 16; if (dir != 15) { portalCount++; } } } } int maxLinkCount = edgeCount + portalCount * 2 + offMeshConLinkCount * 2; //find unique detail vertices int uniqueDetailVertCount = 0; int detailTriCount = 0; if (polyMeshDetail != null) { detailTriCount = polyMeshDetail.TrisCount; for (int i = 0; i < polyMesh.PolyCount; i++) { int numDetailVerts = polyMeshDetail.Meshes[i].VertexCount; int numPolyVerts = polyMesh.Polys[i].VertexCount; uniqueDetailVertCount += numDetailVerts - numPolyVerts; } } else { uniqueDetailVertCount = 0; detailTriCount = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { int numPolyVerts = polyMesh.Polys[i].VertexCount; uniqueDetailVertCount += numPolyVerts - 2; } } //allocate data header = new PathfindingCommon.NavMeshInfo(); navVerts = new Vector3[totVertCount]; navPolys = new Poly[totPolyCount]; navDMeshes = new PolyMeshDetail.MeshData[polyMesh.PolyCount]; navDVerts = new Vector3[uniqueDetailVertCount]; navDTris = new PolyMeshDetail.TriangleData[detailTriCount]; offMeshConnections = new OffMeshConnection[storedOffMeshConCount]; //store header //HACK TiledNavMesh should figure out the X/Y/layer instead of the user maybe? header.X = 0; header.Y = 0; header.Layer = 0; header.PolyCount = totPolyCount; header.VertCount = totVertCount; header.MaxLinkCount = maxLinkCount; header.Bounds = polyMesh.Bounds; header.DetailMeshCount = polyMesh.PolyCount; header.DetailVertCount = uniqueDetailVertCount; header.DetailTriCount = detailTriCount; header.OffMeshBase = polyMesh.PolyCount; header.WalkableHeight = settings.AgentHeight; header.WalkableRadius = settings.AgentRadius; header.WalkableClimb = settings.MaxClimb; header.OffMeshConCount = storedOffMeshConCount; header.BvNodeCount = settings.BuildBoundingVolumeTree ? polyMesh.PolyCount * 2 : 0; header.BvQuantFactor = 1f / settings.CellSize; int offMeshVertsBase = polyMesh.VertCount; int offMeshPolyBase = polyMesh.PolyCount; //store vertices for (int i = 0; i < polyMesh.VertCount; i++) { PolyVertex iv = polyMesh.Verts[i]; navVerts[i].X = polyMesh.Bounds.Min.X + iv.X * settings.CellSize; navVerts[i].Y = polyMesh.Bounds.Min.Y + iv.Y * settings.CellHeight; navVerts[i].Z = polyMesh.Bounds.Min.Z + iv.Z * settings.CellSize; } //off-mesh link vertices int n = 0; for (int i = 0; i < offMeshCons.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { navVerts[offMeshVertsBase + (n * 2 + 0)] = offMeshCons[i].Pos0; navVerts[offMeshVertsBase + (n * 2 + 1)] = offMeshCons[i].Pos1; n++; } } //store polygons for (int i = 0; i < polyMesh.PolyCount; i++) { navPolys[i] = new Poly(); navPolys[i].VertCount = 0; navPolys[i].Tag = polyMesh.Polys[i].Tag; navPolys[i].Area = polyMesh.Polys[i].Area; navPolys[i].PolyType = PolygonType.Ground; navPolys[i].Verts = new int[nvp]; navPolys[i].Neis = new int[nvp]; for (int j = 0; j < nvp; j++) { if (polyMesh.Polys[i].Vertices[j] == PolyMesh.NullId) { break; } navPolys[i].Verts[j] = polyMesh.Polys[i].Vertices[j]; if (PolyMesh.IsBoundaryEdge(polyMesh.Polys[i].NeighborEdges[j])) { //border or portal edge int dir = polyMesh.Polys[i].NeighborEdges[j] % 16; if (dir == 0xf) //border { navPolys[i].Neis[j] = 0; } else if (dir == 0) //portal x- { navPolys[i].Neis[j] = Link.External | 4; } else if (dir == 1) //portal z+ { navPolys[i].Neis[j] = Link.External | 2; } else if (dir == 2) //portal x+ { navPolys[i].Neis[j] = Link.External | 0; } else if (dir == 3) //portal z- { navPolys[i].Neis[j] = Link.External | 6; } } else { //normal connection navPolys[i].Neis[j] = polyMesh.Polys[i].NeighborEdges[j] + 1; } navPolys[i].VertCount++; } } //off-mesh connection vertices n = 0; for (int i = 0; i < offMeshCons.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { navPolys[offMeshPolyBase + n] = new Poly(); navPolys[offMeshPolyBase + n].VertCount = 2; navPolys[offMeshPolyBase + n].Verts = new int[nvp]; navPolys[offMeshPolyBase + n].Verts[0] = offMeshVertsBase + (n * 2 + 0); navPolys[offMeshPolyBase + n].Verts[1] = offMeshVertsBase + (n * 2 + 1); navPolys[offMeshPolyBase + n].Tag = offMeshCons[i].Flags; navPolys[offMeshPolyBase + n].Area = polyMesh.Polys[offMeshCons[i].Poly].Area; //HACK is this correct? navPolys[offMeshPolyBase + n].PolyType = PolygonType.OffMeshConnection; n++; } } //store detail meshes and vertices if (polyMeshDetail != null) { int vbase = 0; List <Vector3> storedDetailVerts = new List <Vector3>(); for (int i = 0; i < polyMesh.PolyCount; i++) { int vb = polyMeshDetail.Meshes[i].VertexIndex; int numDetailVerts = polyMeshDetail.Meshes[i].VertexCount; int numPolyVerts = navPolys[i].VertCount; navDMeshes[i].VertexIndex = vbase; navDMeshes[i].VertexCount = numDetailVerts - numPolyVerts; navDMeshes[i].TriangleIndex = polyMeshDetail.Meshes[i].TriangleIndex; navDMeshes[i].TriangleCount = polyMeshDetail.Meshes[i].TriangleCount; //Copy detail vertices //first 'nv' verts are equal to nav poly verts //the rest are detail verts for (int j = 0; j < navDMeshes[i].VertexCount; j++) { storedDetailVerts.Add(polyMeshDetail.Verts[vb + numPolyVerts + j]); } vbase += numDetailVerts - numPolyVerts; } navDVerts = storedDetailVerts.ToArray(); //store triangles for (int j = 0; j < polyMeshDetail.TrisCount; j++) { navDTris[j] = polyMeshDetail.Tris[j]; } } else { //create dummy detail mesh by triangulating polys int tbase = 0; for (int i = 0; i < polyMesh.PolyCount; i++) { int numPolyVerts = navPolys[i].VertCount; navDMeshes[i].VertexIndex = 0; navDMeshes[i].VertexCount = 0; navDMeshes[i].TriangleIndex = tbase; navDMeshes[i].TriangleCount = numPolyVerts - 2; //triangulate polygon for (int j = 2; j < numPolyVerts; j++) { navDTris[tbase].VertexHash0 = 0; navDTris[tbase].VertexHash1 = j - 1; navDTris[tbase].VertexHash2 = j; //bit for each edge that belongs to the poly boundary navDTris[tbase].Flags = 1 << 2; if (j == 2) { navDTris[tbase].Flags |= 1 << 0; } if (j == numPolyVerts - 1) { navDTris[tbase].Flags |= 1 << 4; } tbase++; } } } //store and create BV tree if (settings.BuildBoundingVolumeTree) { //build tree navBvTree = new BVTree(polyMesh.Verts, polyMesh.Polys, nvp, settings.CellSize, settings.CellHeight); } //store off-mesh connections n = 0; for (int i = 0; i < offMeshConnections.Length; i++) { //only store connections which start from this tile if (offMeshSides[i * 2 + 0] == BoundarySide.Internal) { offMeshConnections [n] = new OffMeshConnection(); offMeshConnections[n].Poly = offMeshPolyBase + n; //copy connection end points offMeshConnections[n].Pos0 = offMeshCons[i].Pos0; offMeshConnections[n].Pos1 = offMeshCons[i].Pos1; offMeshConnections[n].Radius = offMeshCons[i].Radius; offMeshConnections[n].Flags = offMeshCons[i].Flags; offMeshConnections[n].Side = offMeshSides[i * 2 + 1]; offMeshConnections[n].Tag = offMeshCons[i].Tag; n++; } } }
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); }