Beispiel #1
0
        void buildTile(uint mapID, uint tileX, uint tileY, dtNavMesh navMesh)
        {
            Console.WriteLine($"{m_totalTilesProcessed * 100 / m_totalTiles}% [Map {mapID:D4}] Building tile [{tileX:D2},{tileY:D2}]]");

            MeshData meshData = new MeshData();

            // get heightmap data
            m_terrainBuilder.loadMap(mapID, tileX, tileY, meshData, _vmapManager);

            // get model data
            m_terrainBuilder.loadVMap(mapID, tileY, tileX, meshData);

            // if there is no data, give up now
            if (meshData.solidVerts.Count == 0 && meshData.liquidVerts.Count == 0)
            {
                return;
            }

            // remove unused vertices
            TerrainBuilder.cleanVertices(meshData.solidVerts, meshData.solidTris);
            TerrainBuilder.cleanVertices(meshData.liquidVerts, meshData.liquidTris);

            // gather all mesh data for final data check, and bounds calculation
            float[] allVerts = new float[meshData.liquidVerts.Count + meshData.solidVerts.Count];
            Array.Copy(meshData.liquidVerts.ToArray(), allVerts, meshData.liquidVerts.Count);
            Array.Copy(meshData.solidVerts.ToArray(), 0, allVerts, meshData.liquidVerts.Count, meshData.solidVerts.Count);

            if (allVerts.Length == 0)
            {
                return;
            }

            // get bounds of current tile
            getTileBounds(tileX, tileY, allVerts, allVerts.Length / 3, out float[] bmin, out float[] bmax);

            m_terrainBuilder.loadOffMeshConnections(mapID, tileX, tileY, meshData, null);

            // build navmesh tile
            buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh);
        }
Beispiel #2
0
        void buildTile(uint mapID, uint tileX, uint tileY, dtNavMesh navMesh)
        {
            MeshData meshData = new MeshData();

            // get heightmap data
            m_terrainBuilder.loadMap(mapID, tileX, tileY, meshData, _vmapManager);

            // get model data
            m_terrainBuilder.loadVMap(mapID, tileY, tileX, meshData);

            // if there is no data, give up now
            if (meshData.solidVerts.Count == 0 && meshData.liquidVerts.Count == 0)
            {
                return;
            }

            // remove unused vertices
            TerrainBuilder.cleanVertices(meshData.solidVerts, meshData.solidTris);
            TerrainBuilder.cleanVertices(meshData.liquidVerts, meshData.liquidTris);

            // gather all mesh data for final data check, and bounds calculation
            List <float> allVerts = new List <float>();

            allVerts.AddRange(meshData.liquidVerts);
            allVerts.AddRange(meshData.solidVerts);

            if (allVerts.Count == 0)
            {
                return;
            }

            // get bounds of current tile
            getTileBounds(tileX, tileY, allVerts.ToArray(), allVerts.Count / 3, out float[] bmin, out float[] bmax);

            m_terrainBuilder.loadOffMeshConnections(mapID, tileX, tileY, meshData, null);

            // build navmesh tile
            buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh);
        }
Beispiel #3
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}]: ";

            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();

            for (var i = 0; i < 3; ++i)
            {
                config.bmin[i] = bmin[i];
                config.bmax[i] = bmax[i];
            }

            config.maxVertsPerPoly = SharedConst.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          = (60 * 60);
            config.mergeRegionArea        = (50 * 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
            int width, height;

            rcCalcGridSize(config.bmin, config.bmax, config.cs, out width, out height);
            config.width  = width;
            config.height = 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))
                    {
                        //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++;
                }
            }

            rcPolyMesh pmesh = new rcPolyMesh();

            rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, pmesh);

            rcPolyMeshDetail dmesh = new rcPolyMeshDetail();

            rcMergePolyMeshDetails(m_rcContext, dmmerge, nmerge, 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 < pmesh.npolys; ++i)
            {
                byte area = (byte)(pmesh.areas[i] & SharedConst.RC_WALKABLE_AREA);
                if (area != 0)
                {
                    if (area >= (byte)NavArea.MagmaSlime)
                    {
                        pmesh.flags[i] = (ushort)(1 << (63 - area));
                    }
                    else
                    {
                        pmesh.flags[i] = (byte)NavTerrainFlag.Ground; // TODO: these will be dynamic in future
                    }
                }
            }

            // setup mesh parameters
            dtNavMeshCreateParams createParams = new dtNavMeshCreateParams();

            createParams.verts            = pmesh.verts;
            createParams.vertCount        = pmesh.nverts;
            createParams.polys            = pmesh.polys;
            createParams.polyAreas        = pmesh.areas;
            createParams.polyFlags        = pmesh.flags;
            createParams.polyCount        = pmesh.npolys;
            createParams.nvp              = pmesh.nvp;
            createParams.detailMeshes     = dmesh.meshes;
            createParams.detailVerts      = dmesh.verts;
            createParams.detailVertsCount = dmesh.nverts;
            createParams.detailTris       = dmesh.tris;
            createParams.detailTriCount   = 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);
            createParams.bmin           = bmin;
            createParams.bmax           = bmax;
            createParams.cs             = config.cs;
            createParams.ch             = config.ch;
            createParams.tileLayer      = 0;
            createParams.buildBvTree    = true;

            // will hold final navmesh
            dtRawTileData dtRawTile;

            do
            {
                // these values are checked within dtCreateNavMeshData - handle them here
                // so we have a clear error message
                if (createParams.nvp > SharedConst.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;
                }

                if (!dtCreateNavMeshData(createParams, out dtRawTile))
                {
                    //Console.WriteLine($"{tileString} Failed building navmesh tile!");
                    break;
                }

                ulong tileRef = 0;
                // DT_TILE_FREE_DATA tells detour to unallocate memory when the tile
                // is removed via removeTile()
                if (dtStatusFailed(navMesh.addTile(dtRawTile, 1, 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)))
                {
                    var navData = dtRawTile.ToBytes();

                    // write header
                    MmapTileHeader header = new MmapTileHeader();
                    header.mmapMagic   = SharedConst.MMAP_MAGIC;
                    header.dtVersion   = SharedConst.DT_NAVMESH_VERSION;
                    header.mmapVersion = SharedConst.MMAP_VERSION;
                    header.usesLiquids = m_terrainBuilder.usesLiquids();
                    header.size        = (uint)navData.Length;
                    binaryWriter.WriteStruct(header);

                    // write data
                    binaryWriter.Write(navData);
                }

                // now that tile is written to disk, we can unload it
                navMesh.removeTile(tileRef, out dtRawTile);
            }while (false);

            if (_debugMaps)
            {
                generateObjFile(mapID, tileX, tileY, meshData);
            }
        }
Beispiel #4
0
        void buildNavMesh(uint mapID, out dtNavMesh navMesh)
        {
            // if map has a parent we use that to generate dtNavMeshParams - worldserver will load all missing tiles from that map
            int navMeshParamsMapId = _vmapManager.GetParentMapId(mapID);

            if (navMeshParamsMapId == -1)
            {
                navMeshParamsMapId = (int)mapID;
            }

            SortedSet <uint> tiles = getTileList((uint)navMeshParamsMapId);

            // old code for non-statically assigned bitmask sizes:
            ///*** calculate number of bits needed to store tiles & polys ***/
            //int tileBits = dtIlog2(dtNextPow2(tiles.size()));
            //if (tileBits < 1) tileBits = 1;                                     // need at least one bit!
            //int polyBits = sizeof(dtPolyRef)*8 - SALT_MIN_BITS - tileBits;

            int polyBits = SharedConst.DT_POLY_BITS;

            int maxTiles        = tiles.Count;
            int maxPolysPerTile = 1 << polyBits;

            /***          calculate bounds of map         ***/

            uint tileXMin = 64, tileYMin = 64, tileXMax = 0, tileYMax = 0;

            foreach (var it in tiles)
            {
                StaticMapTree.UnpackTileID(it, out uint tileX, out uint tileY);

                if (tileX > tileXMax)
                {
                    tileXMax = tileX;
                }
                else if (tileX < tileXMin)
                {
                    tileXMin = tileX;
                }

                if (tileY > tileYMax)
                {
                    tileYMax = tileY;
                }
                else if (tileY < tileYMin)
                {
                    tileYMin = tileY;
                }
            }

            // use Max because '32 - tileX' is negative for values over 32
            float[] bmin;
            float[] bmax;
            getTileBounds(tileXMax, tileYMax, null, 0, out bmin, out bmax);

            /***       now create the navmesh       ***/

            // navmesh creation params
            dtNavMeshParams navMeshParams = new dtNavMeshParams();

            navMeshParams.tileWidth  = SharedConst.GRID_SIZE;
            navMeshParams.tileHeight = SharedConst.GRID_SIZE;
            rcVcopy(navMeshParams.orig, bmin);
            navMeshParams.maxTiles = maxTiles;
            navMeshParams.maxPolys = maxPolysPerTile;

            navMesh = new dtNavMesh();
            if (dtStatusFailed(navMesh.init(navMeshParams)))
            {
                Console.WriteLine($"[Map: {mapID:D4}] Failed creating navmesh!");
                return;
            }

            string fileName = $"mmaps/{mapID:D4}.mmap";

            using (BinaryWriter writer = new BinaryWriter(File.Open(fileName, FileMode.Create, FileAccess.Write)))
            {
                // now that we know navMesh params are valid, we can write them to file
                writer.Write(bmin[0]);
                writer.Write(bmin[1]);
                writer.Write(bmin[2]);
                writer.Write(SharedConst.GRID_SIZE);
                writer.Write(SharedConst.GRID_SIZE);
                writer.Write(maxTiles);
                writer.Write(maxPolysPerTile);
            }
        }
Beispiel #5
0
        /// @par 
        ///
        /// Must be the first function called after construction, before other
        /// functions are used.
        ///
        /// This function can be used multiple times.
        /// Initializes the query object.
        ///  @param[in]		nav			Pointer to the dtNavMesh object to use for all queries.
        ///  @param[in]		maxNodes	Maximum number of search nodes. [Limits: 0 < value <= 65536]
        /// @returns The status flags for the query.
        public dtStatus init(dtNavMesh nav, int maxNodes)
        {
	        m_nav = nav;
	
	        if (m_nodePool == null || m_nodePool.getMaxNodes() < maxNodes)
	        {
		        if (m_nodePool != null)
		        {
			        //m_nodePool.~dtNodePool();
			        //dtFree(m_nodePool);
			        m_nodePool = null;
		        }
		        m_nodePool = new dtNodePool(maxNodes, (int) dtNextPow2((uint)(maxNodes/4)));//(dtAlloc(sizeof(dtNodePool), DT_ALLOC_PERM)) dtNodePool(maxNodes, dtNextPow2(maxNodes/4));
		        if (m_nodePool == null)
			        return DT_FAILURE | DT_OUT_OF_MEMORY;
	        }
	        else
	        {
		        m_nodePool.clear();
	        }
	
	        if (m_tinyNodePool == null)
	        {
		        m_tinyNodePool = new dtNodePool(64, 32);//(dtAlloc(sizeof(dtNodePool), DT_ALLOC_PERM)) dtNodePool(64, 32);
		        if (m_tinyNodePool == null)
			        return DT_FAILURE | DT_OUT_OF_MEMORY;
	        }
	        else
	        {
		        m_tinyNodePool.clear();
	        }
	
	        // TODO: check the open list size too.
	        if (m_openList == null || m_openList.getCapacity() < maxNodes)
	        {
		        if (m_openList != null)
		        {
			        //m_openList.~dtNodeQueue();
			        //dtFree(m_openList);
			        m_openList = null;
		        }
		        m_openList = new dtNodeQueue(maxNodes);//(dtAlloc(sizeof(dtNodeQueue), DT_ALLOC_PERM)) dtNodeQueue(maxNodes);
		        if (m_openList == null)
			        return DT_FAILURE | DT_OUT_OF_MEMORY;
	        }
	        else
	        {
		        m_openList.clear();
	        }
	
	        return DT_SUCCESS;
        }