void buildMoveMapTile(uint mapID, uint tileX, uint tileY, MeshData meshData, float[] bmin, float[] bmax, dtNavMesh navMesh) { // console output string tileString = $"[Map {mapID:D4}] [{tileX:D2},{tileY:D2}]: "; Console.WriteLine($"{tileString} Building movemap tiles..."); Tile iv = new Tile(); float[] tVerts = meshData.solidVerts.ToArray(); int tVertCount = meshData.solidVerts.Count / 3; int[] tTris = meshData.solidTris.ToArray(); int tTriCount = meshData.solidTris.Count / 3; float[] lVerts = meshData.liquidVerts.ToArray(); int lVertCount = meshData.liquidVerts.Count / 3; int[] lTris = meshData.liquidTris.ToArray(); int lTriCount = meshData.liquidTris.Count / 3; byte[] lTriFlags = meshData.liquidType.ToArray(); // these are WORLD UNIT based metrics // this are basic unit dimentions // value have to divide GRID_SIZE(533.3333f) ( aka: 0.5333, 0.2666, 0.3333, 0.1333, etc ) float BASE_UNIT_DIM = 0.2666666f;// m_bigBaseUnit ? 0.5333333f : 0.2666666f; // All are in UNIT metrics! int VERTEX_PER_MAP = (int)(SharedConst.GRID_SIZE / BASE_UNIT_DIM + 0.5f); int VERTEX_PER_TILE = 80;// m_bigBaseUnit ? 40 : 80; // must divide VERTEX_PER_MAP int TILES_PER_MAP = VERTEX_PER_MAP / VERTEX_PER_TILE; rcConfig config = new rcConfig(); rcVcopy(config.bmin, bmin); rcVcopy(config.bmax, bmax); config.maxVertsPerPoly = DT_VERTS_PER_POLYGON; config.cs = BASE_UNIT_DIM; config.ch = BASE_UNIT_DIM; config.walkableSlopeAngle = m_maxWalkableAngle; config.tileSize = VERTEX_PER_TILE; config.walkableRadius = 2; // m_bigBaseUnit ? 1 : 2; config.borderSize = config.walkableRadius + 3; config.maxEdgeLen = VERTEX_PER_TILE + 1; // anything bigger than tileSize config.walkableHeight = 6; // m_bigBaseUnit ? 3 : 6; // a value >= 3|6 allows npcs to walk over some fences // a value >= 4|8 allows npcs to walk over all fences config.walkableClimb = 8;// m_bigBaseUnit ? 4 : 8; config.minRegionArea = dtSqr(60); config.mergeRegionArea = dtSqr(50); config.maxSimplificationError = 1.8f; // eliminates most jagged edges (tiny polygons) config.detailSampleDist = config.cs * 64; config.detailSampleMaxError = config.ch * 2; // this sets the dimensions of the heightfield - should maybe happen before border padding rcCalcGridSize(config.bmin, config.bmax, config.cs, out config.width, out config.height); // allocate subregions : tiles Tile[] tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP]; // Initialize per tile config. rcConfig tileCfg = new rcConfig(config); tileCfg.width = config.tileSize + config.borderSize * 2; tileCfg.height = config.tileSize + config.borderSize * 2; // merge per tile poly and detail meshes rcPolyMesh[] pmmerge = new rcPolyMesh[TILES_PER_MAP * TILES_PER_MAP]; rcPolyMeshDetail[] dmmerge = new rcPolyMeshDetail[TILES_PER_MAP * TILES_PER_MAP]; int nmerge = 0; // build all tiles for (int y = 0; y < TILES_PER_MAP; ++y) { for (int x = 0; x < TILES_PER_MAP; ++x) { Tile tile = tiles[x + y * TILES_PER_MAP]; // Calculate the per tile bounding box. tileCfg.bmin[0] = config.bmin[0] + (float)(x * config.tileSize - config.borderSize) * config.cs; tileCfg.bmin[2] = config.bmin[2] + (float)(y * config.tileSize - config.borderSize) * config.cs; tileCfg.bmax[0] = config.bmin[0] + (float)((x + 1) * config.tileSize + config.borderSize) * config.cs; tileCfg.bmax[2] = config.bmin[2] + (float)((y + 1) * config.tileSize + config.borderSize) * config.cs; // build heightfield tile.solid = new rcHeightfield(); if (!rcCreateHeightfield(m_rcContext, tile.solid, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch)) { Console.WriteLine($"{tileString} Failed building heightfield! "); continue; } // mark all walkable tiles, both liquids and solids byte[] triFlags = new byte[tTriCount]; for (var i = 0; i < tTriCount; ++i) { triFlags[i] = (byte)NavArea.Ground; } rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags); rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, tile.solid, config.walkableClimb); rcFilterLowHangingWalkableObstacles(m_rcContext, config.walkableClimb, tile.solid); rcFilterLedgeSpans(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, tile.solid); rcFilterWalkableLowHeightSpans(m_rcContext, tileCfg.walkableHeight, tile.solid); rcRasterizeTriangles(m_rcContext, lVerts, lVertCount, lTris, lTriFlags, lTriCount, tile.solid, config.walkableClimb); // compact heightfield spans tile.chf = new rcCompactHeightfield(); if (!rcBuildCompactHeightfield(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, tile.solid, tile.chf)) { Console.WriteLine($"{tileString} Failed compacting heightfield!"); continue; } // build polymesh intermediates if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, tile.chf)) { Console.WriteLine($"{tileString} Failed eroding area!"); continue; } if (!rcBuildDistanceField(m_rcContext, tile.chf)) { Console.WriteLine($"{tileString} Failed building distance field!"); continue; } if (!rcBuildRegions(m_rcContext, tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea)) { Console.WriteLine($"{tileString} Failed building regions!"); continue; } tile.cset = new rcContourSet(); if (!rcBuildContours(m_rcContext, tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, tile.cset, 1)) { Console.WriteLine($"{tileString} Failed building contours!"); continue; } // build polymesh tile.pmesh = new rcPolyMesh(); if (!rcBuildPolyMesh(m_rcContext, tile.cset, tileCfg.maxVertsPerPoly, tile.pmesh)) { Console.WriteLine($"{tileString} Failed building polymesh!"); continue; } tile.dmesh = new rcPolyMeshDetail(); if (!rcBuildPolyMeshDetail(m_rcContext, tile.pmesh, tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, tile.dmesh)) { Console.WriteLine($"{tileString} Failed building polymesh detail!"); continue; } // free those up // we may want to keep them in the future for debug // but right now, we don't have the code to merge them tile.solid = null; tile.chf = null; tile.cset = null; pmmerge[nmerge] = tile.pmesh; dmmerge[nmerge] = tile.dmesh; nmerge++; } } iv.pmesh = new rcPolyMesh(); rcMergePolyMeshes(m_rcContext, ref pmmerge, nmerge, iv.pmesh); iv.dmesh = new rcPolyMeshDetail(); rcMergePolyMeshDetails(m_rcContext, dmmerge, nmerge, ref iv.dmesh); // set polygons as walkable // TODO: special flags for DYNAMIC polygons, ie surfaces that can be turned on and off for (int i = 0; i < iv.pmesh.npolys; ++i) { byte area = (byte)(iv.pmesh.areas[i] & RC_WALKABLE_AREA); if (area != 0) { if (area >= (byte)NavArea.MagmaSlime) { iv.pmesh.flags[i] = (ushort)(1 << (63 - area)); } else { iv.pmesh.flags[i] = (byte)NavTerrainFlag.Ground; // TODO: these will be dynamic in future } } } // setup mesh parameters dtNavMeshCreateParams createParams = new dtNavMeshCreateParams(); //memset(&createParams, 0, sizeof(createParams)); createParams.verts = iv.pmesh.verts; createParams.vertCount = iv.pmesh.nverts; createParams.polys = iv.pmesh.polys; createParams.polyAreas = iv.pmesh.areas; createParams.polyFlags = iv.pmesh.flags; createParams.polyCount = iv.pmesh.npolys; createParams.nvp = iv.pmesh.nvp; createParams.detailMeshes = iv.dmesh.meshes; createParams.detailVerts = iv.dmesh.verts; createParams.detailVertsCount = iv.dmesh.nverts; createParams.detailTris = iv.dmesh.tris; createParams.detailTriCount = iv.dmesh.ntris; createParams.offMeshConVerts = meshData.offMeshConnections.ToArray(); createParams.offMeshConCount = meshData.offMeshConnections.Count / 6; createParams.offMeshConRad = meshData.offMeshConnectionRads.ToArray(); createParams.offMeshConDir = meshData.offMeshConnectionDirs.ToArray(); createParams.offMeshConAreas = meshData.offMeshConnectionsAreas.ToArray(); createParams.offMeshConFlags = meshData.offMeshConnectionsFlags.ToArray(); createParams.walkableHeight = BASE_UNIT_DIM * config.walkableHeight; // agent height createParams.walkableRadius = BASE_UNIT_DIM * config.walkableRadius; // agent radius createParams.walkableClimb = BASE_UNIT_DIM * config.walkableClimb; // keep less that walkableHeight (aka agent height)! createParams.tileX = (int)((((bmin[0] + bmax[0]) / 2) - navMesh.getParams().orig[0]) / SharedConst.GRID_SIZE); createParams.tileY = (int)((((bmin[2] + bmax[2]) / 2) - navMesh.getParams().orig[2]) / SharedConst.GRID_SIZE); rcVcopy(createParams.bmin, bmin); rcVcopy(createParams.bmax, bmax); createParams.cs = config.cs; createParams.ch = config.ch; createParams.tileLayer = 0; createParams.buildBvTree = true; // will hold final navmesh dtRawTileData navData = null; do { // these values are checked within dtCreateNavMeshData - handle them here // so we have a clear error message if (createParams.nvp > DT_VERTS_PER_POLYGON) { Console.WriteLine($"{tileString} Invalid verts-per-polygon value!"); break; } if (createParams.vertCount >= 0xffff) { Console.WriteLine($"{tileString} Too many vertices!"); break; } if (createParams.vertCount == 0 || createParams.verts == null) { // occurs mostly when adjacent tiles have models // loaded but those models don't span into this tile // message is an annoyance break; } if (createParams.polyCount == 0 || createParams.polys == null || TILES_PER_MAP * TILES_PER_MAP == createParams.polyCount) { // we have flat tiles with no actual geometry - don't build those, its useless // keep in mind that we do output those into debug info // drop tiles with only exact count - some tiles may have geometry while having less tiles Console.WriteLine($"{tileString} No polygons to build on tile! "); break; } if (createParams.detailMeshes == null || createParams.detailVerts == null || createParams.detailTris == null) { Console.WriteLine($"{tileString} No detail mesh to build tile!"); break; } Console.WriteLine($"{tileString} Building navmesh tile..."); if (!dtCreateNavMeshData(createParams, out navData)) { Console.WriteLine($"{tileString} Failed building navmesh tile!"); break; } ulong tileRef = 0; Console.WriteLine($"{tileString} Adding tile to navmesh..."); // DT_TILE_FREE_DATA tells detour to unallocate memory when the tile // is removed via removeTile() if (dtStatusFailed(navMesh.addTile(navData, (int)dtTileFlags.DT_TILE_FREE_DATA, 0, ref tileRef)) || tileRef == 0) { Console.WriteLine($"{tileString} Failed adding tile to navmesh!"); break; } // file output string fileName = $"mmaps/{mapID:D4}{tileY:D2}{tileX:D2}.mmtile"; using (BinaryWriter binaryWriter = new BinaryWriter(File.Open(fileName, FileMode.Create, FileAccess.Write))) { Console.WriteLine($"{tileString} Writing to file..."); var navDataBytes = navData.ToBytes(); // write header MmapTileHeader header = new MmapTileHeader(); header.mmapMagic = SharedConst.MMAP_MAGIC; header.dtVersion = Detour.DT_NAVMESH_VERSION; header.mmapVersion = SharedConst.MMAP_VERSION; header.usesLiquids = m_terrainBuilder.usesLiquids(); header.size = (uint)navDataBytes.Length; binaryWriter.WriteStruct(header); // write data binaryWriter.Write(navDataBytes); } // now that tile is written to disk, we can unload it navMesh.removeTile(tileRef, out dtRawTileData dtRawTileData); }while (false); }
/// @par /// /// This function returns the data for the tile so that, if desired, /// it can be added back to the navigation mesh at a later point. /// /// @see #addTile /// Removes the specified tile from the navigation mesh. /// @param[in] ref The reference of the tile to remove. /// @param[out] data Data associated with deleted tile. /// @param[out] dataSize Size of the data associated with deleted tile. /// @return The status flags for the operation. public dtStatus removeTile(dtTileRef tileRef, out dtRawTileData rawTileData) { rawTileData = null; if (tileRef == 0) return DT_FAILURE | DT_INVALID_PARAM; uint tileIndex = decodePolyIdTile((dtPolyRef)tileRef); uint tileSalt = decodePolyIdSalt((dtPolyRef)tileRef); if ((int)tileIndex >= m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; dtMeshTile tile = m_tiles[tileIndex]; if (tile.salt != tileSalt) return DT_FAILURE | DT_INVALID_PARAM; // Remove tile from hash lookup. int h = computeTileHash(tile.header.x,tile.header.y,m_tileLutMask); dtMeshTile prev = null; dtMeshTile cur = m_posLookup[h]; while (cur != null) { if (cur == tile) { if (prev != null) prev.next = cur.next; else m_posLookup[h] = cur.next; break; } prev = cur; cur = cur.next; } // Remove connections to neighbour tiles. // Create connections with neighbour tiles. const int MAX_NEIS = 32; dtMeshTile[] neis = new dtMeshTile[MAX_NEIS]; int nneis; // Connect with layers in current tile. nneis = getTilesAt(tile.header.x, tile.header.y, neis, MAX_NEIS); for (int j = 0; j < nneis; ++j) { if (neis[j] == tile) continue; unconnectExtLinks(neis[j], tile); } // Connect with neighbour tiles. for (int i = 0; i < 8; ++i) { nneis = getNeighbourTilesAt(tile.header.x, tile.header.y, i, neis, MAX_NEIS); for (int j = 0; j < nneis; ++j) unconnectExtLinks(neis[j], tile); } // Reset tile. if ((tile.flags & (int)dtTileFlags.DT_TILE_FREE_DATA) != 0) { // Owns data //dtFree(tile.data); tile.data = null; //tile.dataSize = 0; //if (data) *data = 0; //if (dataSize) *dataSize = 0; rawTileData = null; } else { //if (data) *data = tile.data; //if (dataSize) *dataSize = tile.dataSize; rawTileData = tile.data; } tile.header = null; tile.flags = 0; tile.linksFreeList = 0; tile.polys = null; tile.verts = null; tile.links = null; tile.detailMeshes = null; tile.detailVerts = null; tile.detailTris = null; tile.bvTree = null; tile.offMeshCons = null; // Update salt, salt should never be zero. #if DT_POLYREF64 tile.salt = (tile.salt+1) & ((1<<DT_SALT_BITS)-1); #else tile.salt = (dtTileRef)( (tile.salt+1) & ((1<<(int)m_saltBits)-1) ); #endif if (tile.salt == 0) tile.salt++; // Add to free list. tile.next = m_nextFree; m_nextFree = tile; return DT_SUCCESS; }
/// @par /// /// The add operation will fail if the data is in the wrong format, the allocated tile /// space is full, or there is a tile already at the specified reference. /// /// The lastRef parameter is used to restore a tile with the same tile /// reference it had previously used. In this case the #dtPolyRef's for the /// tile will be restored to the same values they were before the tile was /// removed. /// /// @see dtCreateNavMeshData, #removeTile /// Adds a tile to the navigation mesh. /// @param[in] data Data for the new tile mesh. (See: #dtCreateNavMeshData) /// @param[in] dataSize Data size of the new tile mesh. /// @param[in] flags Tile flags. (See: #dtTileFlags) /// @param[in] lastRef The desired reference for the tile. (When reloading a tile.) [opt] [Default: 0] /// @param[out] result The tile reference. (If the tile was succesfully added.) [opt] /// @return The status flags for the operation. public dtStatus addTile(dtRawTileData rawTileData, int flags, dtTileRef lastRef, ref dtTileRef result) { //C#: Using an intermediate class dtRawTileData because Cpp uses a binary buffer. // Make sure the data is in right format. dtMeshHeader header = rawTileData.header; if (header.magic != DT_NAVMESH_MAGIC) return DT_FAILURE | DT_WRONG_MAGIC; if (header.version != DT_NAVMESH_VERSION) return DT_FAILURE | DT_WRONG_VERSION; // Make sure the location is free. if (getTileAt(header.x, header.y, header.layer) != null) return DT_FAILURE; // Allocate a tile. dtMeshTile tile = null; if (lastRef == 0) { if (m_nextFree != null) { tile = m_nextFree; m_nextFree = tile.next; tile.next = null; } } else { // Try to relocate the tile to specific index with same salt. int tileIndex = (int)decodePolyIdTile((dtPolyRef)lastRef); if (tileIndex >= m_maxTiles) return DT_FAILURE | DT_OUT_OF_MEMORY; // Try to find the specific tile id from the free list. dtMeshTile target = m_tiles[tileIndex]; dtMeshTile prev = null; tile = m_nextFree; while (tile != null && tile != target) { prev = tile; tile = tile.next; } // Could not find the correct location. if (tile != target) return DT_FAILURE | DT_OUT_OF_MEMORY; // Remove from freelist if (prev == null) m_nextFree = tile.next; else prev.next = tile.next; // Restore salt. tile.salt = decodePolyIdSalt((dtPolyRef)lastRef); } // Make sure we could allocate a tile. if (tile == null) return DT_FAILURE | DT_OUT_OF_MEMORY; // Insert tile into the position lut. int h = computeTileHash(header.x, header.y, m_tileLutMask); tile.next = m_posLookup[h]; m_posLookup[h] = tile; // Patch header pointers. //int vertsCount = 3*header.vertCount; //int polysSize = header.polyCount; //int linksSize = header.maxLinkCount; //int detailMeshesSize = header.detailMeshCount; //int detailVertsSize = 3*header.detailVertCount; //int detailTrisSize = 4*header.detailTriCount; //int bvtreeSize = header.bvNodeCount; //int offMeshLinksSize = header.offMeshConCount; //byte* d = data + headerSize; tile.verts = rawTileData.verts; tile.polys = rawTileData.polys; tile.links = rawTileData.links; tile.detailMeshes = rawTileData.detailMeshes; tile.detailVerts = rawTileData.detailVerts; tile.detailTris = rawTileData.detailTris; tile.bvTree = rawTileData.bvTree; tile.offMeshCons = rawTileData.offMeshCons; // If there are no items in the bvtree, reset the tree pointer. //c#: unnecessary, Cpp is afraid to point to whatever data ends up here //if (bvtreeSize == 0) // tile.bvTree = null; // Build links freelist tile.linksFreeList = 0; tile.links[header.maxLinkCount-1].next = DT_NULL_LINK; for (int i = 0; i < header.maxLinkCount-1; ++i){ tile.links[i].next = (dtTileRef) (i+1); } // Init tile. tile.header = header; tile.data = rawTileData; //tile.dataSize = dataSize; tile.flags = flags; connectIntLinks(tile); baseOffMeshLinks(tile); // Create connections with neighbour tiles. const int MAX_NEIS = 32; dtMeshTile[] neis = new dtMeshTile[MAX_NEIS]; int nneis; // Connect with layers in current tile. nneis = getTilesAt(header.x, header.y, neis, MAX_NEIS); for (int j = 0; j < nneis; ++j) { if (neis[j] != tile) { connectExtLinks(tile, neis[j], -1); connectExtLinks(neis[j], tile, -1); } connectExtOffMeshLinks(tile, neis[j], -1); connectExtOffMeshLinks(neis[j], tile, -1); } // Connect with neighbour tiles. for (int i = 0; i < 8; ++i) { nneis = getNeighbourTilesAt(header.x, header.y, i, neis, MAX_NEIS); for (int j = 0; j < nneis; ++j) { connectExtLinks(tile, neis[j], i); connectExtLinks(neis[j], tile, dtOppositeTile(i)); connectExtOffMeshLinks(tile, neis[j], i); connectExtOffMeshLinks(neis[j], tile, dtOppositeTile(i)); } } result = getTileRef(tile); return DT_SUCCESS; }
/// Initializes the navigation mesh for single tile use. /// @param[in] data Data of the new tile. (See: #dtCreateNavMeshData) /// @param[in] dataSize The data size of the new tile. /// @param[in] flags The tile flags. (See: #dtTileFlags) /// @return The status flags for the operation. /// @see dtCreateNavMeshData public dtStatus init(dtRawTileData rawTile, int flags) { //C#: Using an intermediate class dtRawTileData because Cpp uses a binary buffer. // Make sure the data is in right format. //dtMeshHeader header = (dtMeshHeader*)data; dtMeshHeader header = rawTile.header; if (header.magic != DT_NAVMESH_MAGIC) return DT_FAILURE | DT_WRONG_MAGIC; if (header.version != DT_NAVMESH_VERSION) return DT_FAILURE | DT_WRONG_VERSION; dtNavMeshParams navMeshParams = new dtNavMeshParams(); dtVcopy(navMeshParams.orig, header.bmin); navMeshParams.tileWidth = header.bmax[0] - header.bmin[0]; navMeshParams.tileHeight = header.bmax[2] - header.bmin[2]; navMeshParams.maxTiles = 1; navMeshParams.maxPolys = header.polyCount; dtStatus status = init(navMeshParams); if (dtStatusFailed(status)) return status; //return addTile(data, dataSize, flags, 0, 0); dtTileRef dummyResult = 0; return addTile(rawTile, flags, 0,ref dummyResult); }