Example #1
0
        /**************************************************************************/
        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);
        }
Example #2
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);
        }
Example #3
0
        /**************************************************************************/
        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);
        }