/**************************************************************************/ bool loadMap(uint mapID, uint tileX, uint tileY, MeshData meshData, Spot portion) { string mapFileName = $"maps/{mapID:D4}_{tileY:D2}_{tileX:D2}.map"; if (!File.Exists(mapFileName)) { int parentMapId = vmapManager.GetParentMapId(mapID); if (parentMapId != -1) { mapFileName = $"maps/{parentMapId:D4}_{tileY:D2}_{tileX:D2}.map"; } } if (!File.Exists(mapFileName)) { return(false); } using (BinaryReader reader = new BinaryReader(File.Open(mapFileName, FileMode.Open, FileAccess.Read, FileShare.Read))) { map_fileheader fheader = reader.Read <map_fileheader>(); if (fheader.versionMagic != SharedConst.MAP_VERSION_MAGIC) { Console.WriteLine($"{mapFileName} is the wrong version, please extract new .map files"); return(false); } bool haveTerrain = false; bool haveLiquid = false; reader.BaseStream.Seek(fheader.heightMapOffset, SeekOrigin.Begin); map_heightHeader hheader = reader.Read <map_heightHeader>(); if (hheader.fourcc == 1413957709) { haveTerrain = !Convert.ToBoolean(hheader.flags & (uint)MapHeightFlags.NoHeight); haveLiquid = fheader.liquidMapOffset != 0;// && !m_skipLiquid; } // no data in this map file if (!haveTerrain && !haveLiquid) { return(false); } // data used later byte[][][] holes = new byte[16][][]; for (var i = 0; i < 16; ++i) { holes[i] = new byte[16][]; for (var x = 0; x < 16; ++x) { holes[i][x] = new byte[8]; } } ushort[][] liquid_entry = new ushort[16][]; byte[][] liquid_flags = new byte[16][]; for (var i = 0; i < 16; ++i) { liquid_entry[i] = new ushort[16]; liquid_flags[i] = new byte[16]; } List <int> ltriangles = new List <int>(); List <int> ttriangles = new List <int>(); // terrain data if (haveTerrain) { float heightMultiplier; float[] V9 = new float[SharedConst.V9_SIZE_SQ]; float[] V8 = new float[SharedConst.V8_SIZE_SQ]; int expected = SharedConst.V9_SIZE_SQ + SharedConst.V8_SIZE_SQ; if (Convert.ToBoolean(hheader.flags & (uint)MapHeightFlags.AsInt8)) { byte[] v9 = new byte[SharedConst.V9_SIZE_SQ]; byte[] v8 = new byte[SharedConst.V8_SIZE_SQ]; int count = 0; count += reader.Read(v9, 0, SharedConst.V9_SIZE_SQ); count += reader.Read(v8, 0, SharedConst.V8_SIZE_SQ); if (count != expected) { Console.WriteLine($"TerrainBuilder.loadMap: Failed to read some data expected {expected}, read {count}"); } heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 255; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { V9[i] = (float)v9[i] * heightMultiplier + hheader.gridHeight; } for (int i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { V8[i] = (float)v8[i] * heightMultiplier + hheader.gridHeight; } } else if (Convert.ToBoolean(hheader.flags & (uint)MapHeightFlags.AsInt16)) { ushort[] v9 = new ushort[SharedConst.V9_SIZE_SQ]; ushort[] v8 = new ushort[SharedConst.V8_SIZE_SQ]; for (var i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { v9[i] = reader.ReadUInt16(); } for (var i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { v8[i] = reader.ReadUInt16(); } heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 65535; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { V9[i] = (float)v9[i] * heightMultiplier + hheader.gridHeight; } for (int i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { V8[i] = (float)v8[i] * heightMultiplier + hheader.gridHeight; } } else { for (var i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { V9[i] = reader.ReadSingle(); } for (var i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { V8[i] = reader.ReadSingle(); } } // hole data if (fheader.holesSize != 0) { reader.BaseStream.Seek(fheader.holesOffset, SeekOrigin.Begin); int readCount = 0; for (var i = 0; i < 16; ++i) { for (var x = 0; x < 16; ++x) { for (var c = 0; c < 8; ++c) { if (readCount == fheader.holesSize) { break; } holes[i][x][c] = reader.ReadByte(); } } } } int count1 = meshData.solidVerts.Count / 3; float xoffset = ((float)tileX - 32) * SharedConst.GRID_SIZE; float yoffset = ((float)tileY - 32) * SharedConst.GRID_SIZE; float[] coord = new float[3]; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { getHeightCoord(i, Grid.V9, xoffset, yoffset, ref coord, ref V9); meshData.solidVerts.Add(coord[0]); meshData.solidVerts.Add(coord[2]); meshData.solidVerts.Add(coord[1]); } for (int i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { getHeightCoord(i, Grid.V8, xoffset, yoffset, ref coord, ref V8); meshData.solidVerts.Add(coord[0]); meshData.solidVerts.Add(coord[2]); meshData.solidVerts.Add(coord[1]); } int[] indices = { 0, 0, 0 }; int loopStart = 0, loopEnd = 0, loopInc = 0; getLoopVars(portion, ref loopStart, ref loopEnd, ref loopInc); for (int i = loopStart; i < loopEnd; i += loopInc) { for (Spot j = Spot.Top; j <= Spot.Bottom; j += 1) { getHeightTriangle(i, j, indices); ttriangles.Add(indices[2] + count1); ttriangles.Add(indices[1] + count1); ttriangles.Add(indices[0] + count1); } } } // liquid data if (haveLiquid) { reader.BaseStream.Seek(fheader.liquidMapOffset, SeekOrigin.Begin); map_liquidHeader lheader = reader.Read <map_liquidHeader>(); float[] liquid_map = null; if (!Convert.ToBoolean(lheader.flags & 0x0001)) { for (var i = 0; i < 16; ++i) { for (var x = 0; x < 16; ++x) { liquid_entry[i][x] = reader.ReadUInt16(); } } for (var i = 0; i < 16; ++i) { for (var x = 0; x < 16; ++x) { liquid_flags[i][x] = reader.ReadByte(); } } } else { for (var i = 0; i < 16; ++i) { for (var x = 0; x < 16; ++x) { liquid_entry[i][x] = lheader.liquidType; liquid_flags[i][x] = lheader.liquidFlags; } } } if (!Convert.ToBoolean(lheader.flags & 0x0002)) { int toRead = lheader.width * lheader.height; liquid_map = new float[toRead]; for (var i = 0; i < toRead; ++i) { liquid_map[i] = reader.ReadSingle(); } } int count = meshData.liquidVerts.Count / 3; float xoffset = (tileX - 32) * SharedConst.GRID_SIZE; float yoffset = (tileY - 32) * SharedConst.GRID_SIZE; float[] coord = new float[3]; int row, col; // generate coordinates if (!Convert.ToBoolean(lheader.flags & 0x0002)) { int j = 0; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { row = i / SharedConst.V9_SIZE; col = i % SharedConst.V9_SIZE; if (row < lheader.offsetY || row >= lheader.offsetY + lheader.height || col < lheader.offsetX || col >= lheader.offsetX + lheader.width) { // dummy vert using invalid height meshData.liquidVerts.Add((xoffset + col * SharedConst.GRID_PART_SIZE) * -1); meshData.liquidVerts.Add(SharedConst.INVALID_MAP_LIQ_HEIGHT); meshData.liquidVerts.Add((yoffset + row * SharedConst.GRID_PART_SIZE) * -1); continue; } getLiquidCoord(i, j, xoffset, yoffset, ref coord, ref liquid_map); meshData.liquidVerts.Add(coord[0]); meshData.liquidVerts.Add(coord[2]); meshData.liquidVerts.Add(coord[1]); j++; } } else { for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { row = i / SharedConst.V9_SIZE; col = i % SharedConst.V9_SIZE; meshData.liquidVerts.Add((xoffset + col * SharedConst.GRID_PART_SIZE) * -1); meshData.liquidVerts.Add(lheader.liquidLevel); meshData.liquidVerts.Add((yoffset + row * SharedConst.GRID_PART_SIZE) * -1); } } int[] indices = { 0, 0, 0 }; int loopStart = 0, loopEnd = 0, loopInc = 0, triInc = Spot.Bottom - Spot.Top; getLoopVars(portion, ref loopStart, ref loopEnd, ref loopInc); // generate triangles for (int i = loopStart; i < loopEnd; i += loopInc) { for (Spot j = Spot.Top; j <= Spot.Bottom; j += triInc) { getHeightTriangle(i, j, indices, true); ltriangles.Add(indices[2] + count); ltriangles.Add(indices[1] + count); ltriangles.Add(indices[0] + count); } } } // now that we have gathered the data, we can figure out which parts to keep: // liquid above ground, ground above liquid int loopStart1 = 0, loopEnd1 = 0, loopInc1 = 0, tTriCount = 4; bool useTerrain, useLiquid; float[] lverts = meshData.liquidVerts.ToArray(); int[] ltris = ltriangles.ToArray(); int currentLtrisIndex = 0; float[] tverts = meshData.solidVerts.ToArray(); int[] ttris = ttriangles.ToArray(); int currentTtrisIndex = 0; if ((ltriangles.Count + ttriangles.Count) == 0) { return(false); } // make a copy of liquid vertices // used to pad right-bottom frame due to lost vertex data at extraction float[] lverts_copy = null; if (meshData.liquidVerts.Count != 0) { lverts_copy = new float[meshData.liquidVerts.Count]; Array.Copy(lverts, lverts_copy, meshData.liquidVerts.Count); } getLoopVars(portion, ref loopStart1, ref loopEnd1, ref loopInc1); for (int i = loopStart1; i < loopEnd1; i += loopInc1) { for (int j = 0; j < 2; ++j) { // default is true, will change to false if needed useTerrain = true; useLiquid = true; byte liquidType = 0; // if there is no liquid, don't use liquid if (meshData.liquidVerts.Count == 0 || ltriangles.Count == 0) { useLiquid = false; } else { liquidType = getLiquidType(i, liquid_flags); if (Convert.ToBoolean(liquidType & (byte)LiquidTypeMask.DarkWater)) { // players should not be here, so logically neither should creatures useTerrain = false; useLiquid = false; } else if ((liquidType & (byte)(LiquidTypeMask.Water | LiquidTypeMask.Ocean)) != 0) { liquidType = (byte)NavArea.Water; } else if (Convert.ToBoolean(liquidType & (byte)(LiquidTypeMask.Magma | LiquidTypeMask.Slime))) { liquidType = (byte)NavArea.MagmaSlime; } else { useLiquid = false; } } // if there is no terrain, don't use terrain if (ttriangles.Count == 0) { useTerrain = false; } // while extracting ADT data we are losing right-bottom vertices // this code adds fair approximation of lost data if (useLiquid) { float quadHeight = 0; uint validCount = 0; for (uint idx = 0; idx < 3; idx++) { float h = lverts_copy[ltris[currentLtrisIndex + idx] * 3 + 1]; if (h != SharedConst.INVALID_MAP_LIQ_HEIGHT && h < SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX) { quadHeight += h; validCount++; } } // update vertex height data if (validCount > 0 && validCount < 3) { quadHeight /= validCount; for (uint idx = 0; idx < 3; idx++) { float h = lverts[ltris[currentLtrisIndex + idx] * 3 + 1]; if (h == SharedConst.INVALID_MAP_LIQ_HEIGHT || h > SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX) { lverts[ltris[currentLtrisIndex + idx] * 3 + 1] = quadHeight; } } } // no valid vertexes - don't use this poly at all if (validCount == 0) { useLiquid = false; } } // if there is a hole here, don't use the terrain if (useTerrain && fheader.holesSize != 0) { useTerrain = !isHole(i, holes); } // we use only one terrain kind per quad - pick higher one if (useTerrain && useLiquid) { float minLLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX; float maxLLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT; for (uint x = 0; x < 3; x++) { float h = lverts[ltris[currentLtrisIndex + x] * 3 + 1]; if (minLLevel > h) { minLLevel = h; } if (maxLLevel < h) { maxLLevel = h; } } float maxTLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT; float minTLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX; for (uint x = 0; x < 6; x++) { float h = tverts[ttris[currentTtrisIndex + x] * 3 + 1]; if (maxTLevel < h) { maxTLevel = h; } if (minTLevel > h) { minTLevel = h; } } // terrain under the liquid? if (minLLevel > maxTLevel) { useTerrain = false; } //liquid under the terrain? if (minTLevel > maxLLevel) { useLiquid = false; } } // store the result if (useLiquid) { meshData.liquidType.Add((byte)liquidType); for (int k = 0; k < 3; ++k) { meshData.liquidTris.Add(ltris[currentLtrisIndex + k]); } } if (useTerrain) { for (int k = 0; k < 3 * tTriCount / 2; ++k) { meshData.solidTris.Add(ttris[currentTtrisIndex + k]); } } currentLtrisIndex += 3; //ltris = ltris.Skip(3).ToArray(); currentTtrisIndex += 3 * tTriCount / 2; //ttris = ttris.Skip(3 * tTriCount / 2).ToArray(); } } } return(meshData.solidTris.Count != 0 || meshData.liquidTris.Count != 0); }
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 = 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 = 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 = m_bigBaseUnit ? 1 : 2; config.borderSize = config.walkableRadius + 3; config.maxEdgeLen = VERTEX_PER_TILE + 1; // anything bigger than tileSize config.walkableHeight = 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 = 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] = 0x01; } 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) { if (Convert.ToBoolean(iv.pmesh.areas[i] & RC_WALKABLE_AREA)) { iv.pmesh.flags[i] = iv.pmesh.areas[i]; } } // 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 //printf("%sNo vertices to build tile! \n", tileString); 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); }
/**************************************************************************/ public bool loadVMap(uint mapID, uint tileX, uint tileY, MeshData meshData) { bool result = vmapManager.LoadSingleMap(mapID, "vmaps", tileX, tileY); bool retval = false; do { if (!result) { break; } Dictionary <uint, StaticMapTree> instanceTrees; vmapManager.GetInstanceMapTree(out instanceTrees); if (instanceTrees[mapID] == null) { break; } ModelInstance[] models = null; uint count; instanceTrees[mapID].GetModelInstances(out models, out count); if (models == null) { break; } for (uint i = 0; i < count; ++i) { ModelInstance instance = models[i]; if (instance == null) { continue; } // model instances exist in tree even though there are instances of that model in this tile WorldModel worldModel = instance.GetWorldModel(); if (worldModel == null) { continue; } // now we have a model to add to the meshdata retval = true; List <GroupModel> groupModels; worldModel.getGroupModels(out groupModels); // all M2s need to have triangle indices reversed bool isM2 = (instance.flags & ModelFlags.M2) != 0; // transform data float scale = instance.iScale; Matrix3 rotation = Matrix3.fromEulerAnglesXYZ(MathF.PI * instance.iRot.Z / -180.0f, MathF.PI * instance.iRot.X / -180.0f, MathF.PI * instance.iRot.Y / -180.0f); Vector3 position = instance.iPos; position.X -= 32 * SharedConst.GRID_SIZE; position.Y -= 32 * SharedConst.GRID_SIZE; foreach (var it in groupModels) { List <Vector3> tempVertices; List <Vector3> transformedVertices = new List <Vector3>(); List <MeshTriangle> tempTriangles; WmoLiquid liquid = null; it.GetMeshData(out tempVertices, out tempTriangles, out liquid); // first handle collision mesh transform(tempVertices.ToArray(), transformedVertices, scale, rotation, position); int offset = meshData.solidVerts.Count / 3; copyVertices(transformedVertices.ToArray(), meshData.solidVerts); copyIndices(tempTriangles, meshData.solidTris, offset, isM2); // now handle liquid data if (liquid != null && liquid.iFlags != null) { List <Vector3> liqVerts = new List <Vector3>(); List <uint> liqTris = new List <uint>(); uint tilesX, tilesY, vertsX, vertsY; Vector3 corner; liquid.GetPosInfo(out tilesX, out tilesY, out corner); vertsX = tilesX + 1; vertsY = tilesY + 1; byte[] flags = liquid.iFlags; float[] data = liquid.iHeight; NavArea type = NavArea.Empty; // convert liquid type to NavTerrain var liquidTypeRecord = liquidTypeStorage.LookupByKey(liquid.GetLiquidType()); uint liquidFlags = (uint)(liquidTypeRecord != null ? (1 << liquidTypeRecord.SoundBank) : 0); if ((liquidFlags & (uint)(LiquidTypeMask.Water | LiquidTypeMask.Ocean)) != 0) { type = NavArea.Water; } else if ((liquidFlags & (uint)(LiquidTypeMask.Magma | LiquidTypeMask.Slime)) != 0) { type = NavArea.MagmaSlime; } // indexing is weird... // after a lot of trial and error, this is what works: // vertex = y*vertsX+x // tile = x*tilesY+y // flag = y*tilesY+x Vector3 vert; for (uint x = 0; x < vertsX; ++x) { for (uint y = 0; y < vertsY; ++y) { vert = new Vector3(corner.X + x * SharedConst.GRID_PART_SIZE, corner.Y + y * SharedConst.GRID_PART_SIZE, data[y * vertsX + x]); vert = vert * rotation * scale + position; vert.X *= -1.0f; vert.Y *= -1.0f; liqVerts.Add(vert); } } uint idx1, idx2, idx3, idx4; uint square; for (uint x = 0; x < tilesX; ++x) { for (uint y = 0; y < tilesY; ++y) { if ((flags[x + y * tilesX] & 0x0f) != 0x0f) { square = x * tilesY + y; idx1 = square + x; idx2 = square + 1 + x; idx3 = square + tilesY + 1 + 1 + x; idx4 = square + tilesY + 1 + x; // top triangle liqTris.Add(idx3); liqTris.Add(idx2); liqTris.Add(idx1); // bottom triangle liqTris.Add(idx4); liqTris.Add(idx3); liqTris.Add(idx1); } } } int liqOffset = meshData.liquidVerts.Count / 3; for (int x = 0; x < liqVerts.Count; ++x) { meshData.liquidVerts.Add(liqVerts[x].Y); meshData.liquidVerts.Add(liqVerts[x].Z); meshData.liquidVerts.Add(liqVerts[x].X); } for (int x = 0; x < liqTris.Count / 3; ++x) { meshData.liquidTris.Add((int)(liqTris[x * 3 + 1] + liqOffset)); meshData.liquidTris.Add((int)(liqTris[x * 3 + 2] + liqOffset)); meshData.liquidTris.Add((int)(liqTris[x * 3] + liqOffset)); meshData.liquidType.Add((byte)type); } } } } }while (false); vmapManager.UnloadSingleMap(mapID, tileX, tileY); return(retval); }