/// @par /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig public static bool rcBuildPolyMeshDetail(rcContext ctx, rcPolyMesh mesh, rcCompactHeightfield chf, float sampleDist, float sampleMaxError, rcPolyMeshDetail dmesh) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL); if (mesh.nverts == 0 || mesh.npolys == 0) return true; int nvp = mesh.nvp; float cs = mesh.cs; float ch = mesh.ch; float[] orig = mesh.bmin; int borderSize = mesh.borderSize; List<int> edges = new List<int>(); List<int> tris = new List<int>(); List<int> stack = new List<int>(); List<int> samples = new List<int>(); edges.Capacity = 64; tris.Capacity = 512; stack.Capacity = 512; samples.Capacity = 512; float[] verts = new float[256*3]; rcHeightPatch hp = new rcHeightPatch(); int nPolyVerts = 0; int maxhw = 0, maxhh = 0; //rcScopedDelete<int> bounds = (int*)rcAlloc(sizeof(int)*mesh.npolys*4, RC_ALLOC_TEMP); int[] bounds = new int[mesh.npolys*4]; if (bounds == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' ("+ mesh.npolys*4+")."); return false; } //rcScopedDelete<float> poly = (float*)rcAlloc(sizeof(float)*nvp*3, RC_ALLOC_TEMP); float[] poly = new float[nvp*3]; if (poly == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' ("+nvp*3+")."); return false; } // Find max size for a polygon area. for (int i = 0; i < mesh.npolys; ++i) { //ushort* p = &mesh.polys[i*nvp*2]; int pStart = i*nvp*2; //int& xmin = bounds[i*4+0]; //int& xmax = bounds[i*4+1]; //int& ymin = bounds[i*4+2]; //int& ymax = bounds[i*4+3]; int xmin = i*4+0; int xmax = i*4+1; int ymin = i*4+2; int ymax = i*4+3; bounds[xmin] = chf.width; bounds[xmax] = 0; bounds[ymin] = chf.height; bounds[ymax] = 0; for (int j = 0; j < nvp; ++j) { if(mesh.polys[pStart + j] == RC_MESH_NULL_IDX) break; //t ushort* v = &mesh.verts[p[j]*3]; int vIndex = mesh.polys[pStart + j] * 3; bounds[xmin] = Math.Min(bounds[xmin], (int)mesh.verts[vIndex + 0]); bounds[xmax] = Math.Max(bounds[xmax], (int)mesh.verts[vIndex + 0]); bounds[ymin] = Math.Min(bounds[ymin], (int)mesh.verts[vIndex + 2]); bounds[ymax] = Math.Max(bounds[ymax], (int)mesh.verts[vIndex + 2]); nPolyVerts++; } bounds[xmin] = Math.Max(0,bounds[xmin]-1); bounds[xmax] = Math.Min(chf.width,bounds[xmax]+1); bounds[ymin] = Math.Max(0,bounds[ymin]-1); bounds[ymax] = Math.Min(chf.height,bounds[ymax]+1); if (bounds[xmin] >= bounds[xmax] || bounds[ymin] >= bounds[ymax]) continue; maxhw = Math.Max(maxhw, bounds[xmax]-bounds[xmin]); maxhh = Math.Max(maxhh, bounds[ymax]-bounds[ymin]); } //hp.data = (ushort*)rcAlloc(sizeof(ushort)*maxhw*maxhh, RC_ALLOC_TEMP); hp.data = new ushort[maxhh*maxhw]; if (hp.data == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' ("+maxhw*maxhh+")."); return false; } dmesh.nmeshes = mesh.npolys; dmesh.nverts = 0; dmesh.ntris = 0; //dmesh.meshes = (uint*)rcAlloc(sizeof(uint)*dmesh.nmeshes*4, RC_ALLOC_PERM); dmesh.meshes = new uint[dmesh.nmeshes*4]; if (dmesh.meshes == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' ("+dmesh.nmeshes*4+")."); return false; } int vcap = nPolyVerts+nPolyVerts/2; int tcap = vcap*2; dmesh.nverts = 0; //dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); dmesh.verts = new float[vcap*3]; if (dmesh.verts == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' ("+vcap*3+")."); return false; } dmesh.ntris = 0; //dmesh.tris = (byte*)rcAlloc(sizeof(byte*)*tcap*4, RC_ALLOC_PERM); dmesh.tris = new byte[tcap*4]; if (dmesh.tris == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' ("+tcap*4+")."); return false; } for (int i = 0; i < mesh.npolys; ++i) { //const ushort* p = &mesh.polys[i*nvp*2]; int pIndex = i*nvp*2; // Store polygon vertices for processing. int npoly = 0; for (int j = 0; j < nvp; ++j) { if(mesh.polys[pIndex + j] == RC_MESH_NULL_IDX) break; //const ushort* v = &mesh.verts[p[j]*3]; int vIndex = mesh.polys[pIndex + j] * 3; poly[j*3+0] = mesh.verts[vIndex + 0]*cs; poly[j*3+1] = mesh.verts[vIndex + 1]*ch; poly[j*3+2] = mesh.verts[vIndex + 2]*cs; npoly++; } // Get the height data from the area of the polygon. hp.xmin = bounds[i*4+0]; hp.ymin = bounds[i*4+2]; hp.width = bounds[i*4+1]-bounds[i*4+0]; hp.height = bounds[i*4+3]-bounds[i*4+2]; getHeightData(chf, mesh.polys, pIndex, npoly, mesh.verts, borderSize, hp, stack, mesh.regs[i]); // Build detail mesh. int nverts = 0; if (!buildPolyDetail(ctx, poly, npoly, sampleDist, sampleMaxError, chf, hp, verts, ref nverts, tris, edges, samples)) { return false; } // Move detail verts to world space. for (int j = 0; j < nverts; ++j) { verts[j*3+0] += orig[0]; verts[j*3+1] += orig[1] + chf.ch; // Is this offset necessary? verts[j*3+2] += orig[2]; } // Offset poly too, will be used to flag checking. for (int j = 0; j < npoly; ++j) { poly[j*3+0] += orig[0]; poly[j*3+1] += orig[1]; poly[j*3+2] += orig[2]; } // Store detail submesh. int ntris = tris.Count/4; dmesh.meshes[i*4+0] = (uint)dmesh.nverts; dmesh.meshes[i*4+1] = (uint)nverts; dmesh.meshes[i*4+2] = (uint)dmesh.ntris; dmesh.meshes[i*4+3] = (uint)ntris; // Store vertices, allocate more memory if necessary. if (dmesh.nverts+nverts > vcap) { while (dmesh.nverts+nverts > vcap){ vcap += 256; } //float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); float[] newv = new float[vcap*3]; if (newv == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' ("+vcap*3+")."); return false; } if (dmesh.nverts != 0){ //memcpy(newv, dmesh.verts, sizeof(float)*3*dmesh.nverts); for (int j=0;j<3*dmesh.nverts;++j){ newv[j] = dmesh.verts[j]; } } //rcFree(dmesh.verts); //dmesh.verts = null; dmesh.verts = newv; } for (int j = 0; j < nverts; ++j) { dmesh.verts[dmesh.nverts*3+0] = verts[j*3+0]; dmesh.verts[dmesh.nverts*3+1] = verts[j*3+1]; dmesh.verts[dmesh.nverts*3+2] = verts[j*3+2]; dmesh.nverts++; } // Store triangles, allocate more memory if necessary. if (dmesh.ntris+ntris > tcap) { while (dmesh.ntris+ntris > tcap){ tcap += 256; } //byte* newt = (byte*)rcAlloc(sizeof(byte)*tcap*4, RC_ALLOC_PERM); byte[] newt = new byte[tcap*4]; if (newt == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' ("+tcap*4+")."); return false; } if (dmesh.ntris != 0){ //memcpy(newt, dmesh.tris, sizeof(byte)*4*dmesh.ntris); for (int j = 0;j<4*dmesh.ntris;++j){ newt[j] = dmesh.tris[j]; } } //rcFree(dmesh.tris); dmesh.tris = newt; } for (int j = 0; j < ntris; ++j) { //const int* t = &tris[j*4]; int tIndex = j*4; dmesh.tris[dmesh.ntris*4+0] = (byte)tris[tIndex + 0]; dmesh.tris[dmesh.ntris*4+1] = (byte)tris[tIndex + 1]; dmesh.tris[dmesh.ntris*4+2] = (byte)tris[tIndex + 2]; dmesh.tris[dmesh.ntris*4+3] = getTriFlags(verts, tris[tIndex + 0]*3, verts, tris[tIndex + 1]*3, verts, tris[tIndex + 2]*3, poly, 0, npoly); dmesh.ntris++; } } ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL); return true; }
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); } }
/// @see rcAllocPolyMesh, rcPolyMesh public static bool rcMergePolyMeshes(rcContext ctx, ref rcPolyMesh[] meshes, int nmeshes, rcPolyMesh mesh) { Debug.Assert(ctx != null, "rcContext is null"); if (nmeshes == 0 || meshes == null) return true; ctx.startTimer(rcTimerLabel.RC_TIMER_MERGE_POLYMESH); mesh.nvp = meshes[0].nvp; mesh.cs = meshes[0].cs; mesh.ch = meshes[0].ch; rcVcopy(mesh.bmin, meshes[0].bmin); rcVcopy(mesh.bmax, meshes[0].bmax); int maxVerts = 0; int maxPolys = 0; int maxVertsPerMesh = 0; for (int i = 0; i < nmeshes; ++i) { rcVmin(mesh.bmin, meshes[i].bmin); rcVmax(mesh.bmax, meshes[i].bmax); maxVertsPerMesh = Math.Max(maxVertsPerMesh, meshes[i].nverts); maxVerts += meshes[i].nverts; maxPolys += meshes[i].npolys; } mesh.nverts = 0; //mesh.verts = (ushort*)rcAlloc(sizeof(ushort)*maxVerts*3, RC_ALLOC_PERM); mesh.verts = new ushort[maxVerts * 3]; if (mesh.verts == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.verts' " + maxVerts * 3); return false; } mesh.npolys = 0; //mesh.polys = (ushort*)rcAlloc(sizeof(ushort)*maxPolys*2*mesh.nvp, RC_ALLOC_PERM); mesh.polys = new ushort[maxPolys * 2 * mesh.nvp]; if (mesh.polys == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.polys' " + maxPolys * 2 * mesh.nvp); return false; } //memset(mesh.polys, 0xff, sizeof(ushort)*maxPolys*2*mesh.nvp); for (int i = 0; i < maxPolys * 2 * mesh.nvp; ++i) { mesh.polys[i] = 0xffff; } //mesh.regs = (ushort*)rcAlloc(sizeof(ushort)*maxPolys, RC_ALLOC_PERM); mesh.regs = new ushort[maxPolys]; if (mesh.regs == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.regs' " + maxPolys); return false; } //memset(mesh.regs, 0, sizeof(ushort)*maxPolys); //mesh.areas = (byte*)rcAlloc(sizeof(byte)*maxPolys, RC_ALLOC_PERM); mesh.areas = new byte[maxPolys]; if (mesh.areas == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.areas' " + maxPolys); return false; } //memset(mesh.areas, 0, sizeof(byte)*maxPolys); //mesh.flags = (ushort*)rcAlloc(sizeof(ushort)*maxPolys, RC_ALLOC_PERM); mesh.flags = new ushort[maxPolys]; if (mesh.flags == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.flags' " + maxPolys); return false; } //memset(mesh.flags, 0, sizeof(ushort)*maxPolys); //rcScopedDelete<int> nextVert = (int*)rcAlloc(sizeof(int)*maxVerts, RC_ALLOC_TEMP); int[] nextVert = new int[maxVerts]; if (nextVert == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'nextVert' " + maxVerts); return false; } //memset(nextVert, 0, sizeof(int)*maxVerts); //rcScopedDelete<int> firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); int[] firstVert = new int[VERTEX_BUCKET_COUNT]; if (firstVert == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'firstVert' " + VERTEX_BUCKET_COUNT); return false; } for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) { firstVert[i] = -1; } //rcScopedDelete<ushort> vremap = (ushort*)rcAlloc(sizeof(ushort)*maxVertsPerMesh, RC_ALLOC_PERM); ushort[] vremap = new ushort[maxVertsPerMesh]; if (vremap == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'vremap' " + maxVertsPerMesh); return false; } //memset(vremap, 0, sizeof(ushort)*maxVertsPerMesh); for (int i = 0; i < nmeshes; ++i) { rcPolyMesh pmesh = meshes[i]; ushort ox = (ushort)Math.Floor((pmesh.bmin[0] - mesh.bmin[0]) / mesh.cs + 0.5f); ushort oz = (ushort)Math.Floor((pmesh.bmin[2] - mesh.bmin[2]) / mesh.cs + 0.5f); bool isMinX = (ox == 0); bool isMinZ = (oz == 0); bool isMaxX = ((ushort)Math.Floor((mesh.bmax[0] - pmesh.bmax[0]) / mesh.cs + 0.5f)) == 0; bool isMaxZ = ((ushort)Math.Floor((mesh.bmax[2] - pmesh.bmax[2]) / mesh.cs + 0.5f)) == 0; bool isOnBorder = (isMinX || isMinZ || isMaxX || isMaxZ); for (int j = 0; j < pmesh.nverts; ++j) { //ushort* v = &pmesh.verts[j*3]; int vIndex = j * 3; vremap[j] = addVertex((ushort)(pmesh.verts[vIndex + 0] + ox), pmesh.verts[vIndex + 1], (ushort)(pmesh.verts[vIndex + 2] + oz), mesh.verts, firstVert, nextVert, ref mesh.nverts); } for (int j = 0; j < pmesh.npolys; ++j) { //ushort* tgt = &mesh.polys[mesh.npolys*2*mesh.nvp]; //ushort* src = &pmesh.polys[j*2*mesh.nvp]; int tgtIndex = mesh.npolys * 2 * mesh.nvp; int srcIndex = j * 2 * mesh.nvp; mesh.regs[mesh.npolys] = pmesh.regs[j]; mesh.areas[mesh.npolys] = pmesh.areas[j]; mesh.flags[mesh.npolys] = pmesh.flags[j]; mesh.npolys++; for (int k = 0; k < mesh.nvp; ++k) { if (pmesh.polys[srcIndex + k] == RC_MESH_NULL_IDX) { break; } mesh.polys[tgtIndex + k] = vremap[pmesh.polys[srcIndex + k]]; } if (isOnBorder) { for (int k = mesh.nvp; k < mesh.nvp * 2; ++k) { if ((pmesh.polys[srcIndex + k] & 0x8000) != 0 && (pmesh.polys[srcIndex + k] != 0xffff)) { ushort dir = (ushort)(pmesh.polys[srcIndex + k] & 0xf); switch (dir) { case 0: // Portal x- if (isMinX) mesh.polys[tgtIndex + k] = pmesh.polys[srcIndex + k]; break; case 1: // Portal z+ if (isMaxZ) mesh.polys[tgtIndex + k] = pmesh.polys[srcIndex + k]; break; case 2: // Portal x+ if (isMaxX) mesh.polys[tgtIndex + k] = pmesh.polys[srcIndex + k]; break; case 3: // Portal z- if (isMinZ) mesh.polys[tgtIndex + k] = pmesh.polys[srcIndex + k]; break; } } } } } } // Calculate adjacency. if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, mesh.nvp)) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Adjacency failed."); return false; } if (mesh.nverts > 0xffff) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many vertices " + mesh.nverts + " (max " + 0xffff + "). Data can be corrupted."); } if (mesh.npolys > 0xffff) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many polygons " + mesh.npolys + " (max " + 0xffff + "). Data can be corrupted."); } ctx.stopTimer(rcTimerLabel.RC_TIMER_MERGE_POLYMESH); return true; }
public static bool rcCopyPolyMesh(rcContext ctx, rcPolyMesh src, rcPolyMesh dst) { Debug.Assert(ctx != null, "rcContext is null"); // Destination must be empty. Debug.Assert(dst.verts == null); Debug.Assert(dst.polys == null); Debug.Assert(dst.regs == null); Debug.Assert(dst.areas == null); Debug.Assert(dst.flags == null); dst.nverts = src.nverts; dst.npolys = src.npolys; dst.maxpolys = src.npolys; dst.nvp = src.nvp; rcVcopy(dst.bmin, src.bmin); rcVcopy(dst.bmax, src.bmax); dst.cs = src.cs; dst.ch = src.ch; dst.borderSize = src.borderSize; //dst.verts = (ushort*)rcAlloc(sizeof(ushort)*src.nverts*3, RC_ALLOC_PERM); dst.verts = new ushort[src.nverts * 3]; if (dst.verts == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.verts' (" + src.nverts * 3 + ")."); return false; } //memcpy(dst.verts, src.verts, sizeof(ushort)*src.nverts*3); for (int i = 0; i < src.nverts * 3; ++i) { dst.verts[i] = src.verts[i]; } //dst.polys = (ushort*)rcAlloc(sizeof(ushort)*src.npolys*2*src.nvp, RC_ALLOC_PERM); dst.polys = new ushort[src.npolys * 2 * src.nvp]; if (dst.polys == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.polys' (" + src.npolys * 2 * src.nvp + ")."); return false; } //memcpy(dst.polys, src.polys, sizeof(ushort)*src.npolys*2*src.nvp); for (int i = 0; i < src.npolys * 2 * src.nvp; ++i) { dst.polys[i] = src.polys[i]; } //dst.regs = (ushort*)rcAlloc(sizeof(ushort)*src.npolys, RC_ALLOC_PERM); dst.regs = new ushort[src.npolys]; if (dst.regs == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.regs' (" + src.npolys + ")."); return false; } //memcpy(dst.regs, src.regs, sizeof(ushort)*src.npolys); for (int i = 0; i < src.npolys; ++i) { dst.regs[i] = src.regs[i]; } //dst.areas = (byte*)rcAlloc(sizeof(byte)*src.npolys, RC_ALLOC_PERM); dst.areas = new byte[src.npolys]; if (dst.areas == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.areas' (" + src.npolys + ")."); return false; } //memcpy(dst.areas, src.areas, sizeof(byte)*src.npolys); for (int i = 0; i < src.npolys; ++i) { dst.areas[i] = src.areas[i]; } //dst.flags = (ushort*)rcAlloc(sizeof(ushort)*src.npolys, RC_ALLOC_PERM); dst.flags = new ushort[src.npolys]; if (dst.flags != null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.flags' (" + src.npolys + ")."); return false; } //memcpy(dst.flags, src.flags, sizeof(byte)*src.npolys); for (int i = 0; i < src.npolys; ++i) { dst.flags[i] = src.flags[i]; } return true; }
/// @par /// /// @note If the mesh data is to be used to construct a Detour navigation mesh, then the upper /// limit must be retricted to <= #DT_VERTS_PER_POLYGON. /// /// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig public static bool rcBuildPolyMesh(rcContext ctx, rcContourSet cset, int nvp, rcPolyMesh mesh) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESH); rcVcopy(mesh.bmin, cset.bmin); rcVcopy(mesh.bmax, cset.bmax); mesh.cs = cset.cs; mesh.ch = cset.ch; mesh.borderSize = cset.borderSize; int maxVertices = 0; int maxTris = 0; int maxVertsPerCont = 0; for (int i = 0; i < cset.nconts; ++i) { // Skip null contours. if (cset.conts[i].nverts < 3) continue; maxVertices += cset.conts[i].nverts; maxTris += cset.conts[i].nverts - 2; maxVertsPerCont = Math.Max(maxVertsPerCont, cset.conts[i].nverts); } if (maxVertices >= 0xfffe) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Too many vertices " + maxVertices); return false; } //rcScopedDelete<byte> vflags = (byte*)rcAlloc(sizeof(byte)*maxVertices, RC_ALLOC_TEMP); byte[] vflags = new byte[maxVertices]; if (vflags == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'vflags' " + maxVertices); return false; } //memset(vflags, 0, maxVertices); //mesh.verts = (ushort*)rcAlloc(sizeof(ushort)*maxVertices*3, RC_ALLOC_PERM); mesh.verts = new ushort[maxVertices * 3]; if (mesh.verts == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' " + maxVertices); return false; } //mesh.polys = (ushort*)rcAlloc(sizeof(ushort)*maxTris*nvp*2, RC_ALLOC_PERM); mesh.polys = new ushort[maxTris * nvp * 2]; if (mesh.polys == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.polys' " + maxTris * nvp * 2); return false; } //mesh.regs = (ushort*)rcAlloc(sizeof(ushort)*maxTris, RC_ALLOC_PERM); mesh.regs = new ushort[maxTris]; if (mesh.regs == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.regs' " + maxTris); return false; } //mesh.areas = (byte*)rcAlloc(sizeof(byte)*maxTris, RC_ALLOC_PERM); mesh.areas = new byte[maxTris]; if (mesh.areas == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.areas' " + maxTris); return false; } mesh.nverts = 0; mesh.npolys = 0; mesh.nvp = nvp; mesh.maxpolys = maxTris; //memset(mesh.verts, 0, sizeof(ushort)*maxVertices*3); //memset(mesh.polys, 0xff, sizeof(ushort)*maxTris*nvp*2); for (int i = 0; i < maxTris * nvp * 2; ++i) { mesh.polys[i] = 0xffff; } //memset(mesh.regs, 0, sizeof(ushort)*maxTris); //memset(mesh.areas, 0, sizeof(byte)*maxTris); //rcScopedDelete<int> nextVert = (int*)rcAlloc(sizeof(int)*maxVertices, RC_ALLOC_TEMP); int[] nextVert = new int[maxVertices]; if (nextVert == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'nextVert' " + maxVertices); return false; } //memset(nextVert, 0, sizeof(int)*maxVertices); //rcScopedDelete<int> firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); int[] firstVert = new int[VERTEX_BUCKET_COUNT]; if (firstVert == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'firstVert' " + VERTEX_BUCKET_COUNT); return false; } for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) firstVert[i] = -1; //rcScopedDelete<int> indices = (int*)rcAlloc(sizeof(int)*maxVertsPerCont, RC_ALLOC_TEMP); int[] indices = new int[maxVertsPerCont]; if (indices == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'indices' " + maxVertsPerCont); return false; } //rcScopedDelete<int> tris = (int*)rcAlloc(sizeof(int)*maxVertsPerCont*3, RC_ALLOC_TEMP); int[] tris = new int[maxVertsPerCont * 3]; if (tris == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'tris' " + maxVertsPerCont * 3); return false; } //rcScopedDelete<ushort> polys = (ushort*)rcAlloc(sizeof(ushort)*(maxVertsPerCont+1)*nvp, RC_ALLOC_TEMP); ushort[] polys = new ushort[(maxVertsPerCont + 1) * nvp]; if (polys == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'polys' " + (maxVertsPerCont + 1) * nvp); return false; } int tmpPolyIndex = maxVertsPerCont * nvp; //ushort[] tmpPoly = &polys[maxVertsPerCont*nvp]; for (int i = 0; i < cset.nconts; ++i) { rcContour cont = cset.conts[i]; // Skip null contours. if (cont.nverts < 3) continue; // Triangulate contour for (int j = 0; j < cont.nverts; ++j) indices[j] = j; int ntris = triangulate(cont.nverts, cont.verts, indices, tris); if (ntris <= 0) { // Bad triangulation, should not happen. /* printf("\tconst float bmin[3] = {%ff,%ff,%ff};\n", cset.bmin[0], cset.bmin[1], cset.bmin[2]); printf("\tconst float cs = %ff;\n", cset.cs); printf("\tconst float ch = %ff;\n", cset.ch); printf("\tconst int verts[] = {\n"); for (int k = 0; k < cont.nverts; ++k) { const int* v = &cont.verts[k*4]; printf("\t\t%d,%d,%d,%d,\n", v[0], v[1], v[2], v[3]); } printf("\t};\n\tconst int nverts = sizeof(verts)/(sizeof(int)*4);\n");*/ ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildPolyMesh: Bad triangulation Contour " + i); ntris = -ntris; } // Add and merge vertices. for (int j = 0; j < cont.nverts; ++j) { int vIndex = j * 4; //const int* v = &cont.verts[j*4]; indices[j] = addVertex((ushort)cont.verts[vIndex + 0], (ushort)cont.verts[vIndex + 1], (ushort)cont.verts[vIndex + 2], mesh.verts, firstVert, nextVert, ref mesh.nverts); if ((cont.verts[vIndex + 3] & RC_BORDER_VERTEX) != 0) { // This vertex should be removed. vflags[indices[j]] = 1; } } // Build initial polygons. int npolys = 0; //memset(polys, 0xff, maxVertsPerCont*nvp*sizeof(ushort)); for (int j = 0; j < nvp * maxVertsPerCont; ++j) { polys[j] = 0xffff; } for (int j = 0; j < ntris; ++j) { int tIndex = j * 3; //int* t = &tris[j*3]; if (tris[tIndex + 0] != tris[tIndex + 1] && tris[tIndex + 0] != tris[tIndex + 2] && tris[tIndex + 1] != tris[tIndex + 2]) { polys[npolys * nvp + 0] = (ushort)indices[tris[tIndex + 0]]; polys[npolys * nvp + 1] = (ushort)indices[tris[tIndex + 1]]; polys[npolys * nvp + 2] = (ushort)indices[tris[tIndex + 2]]; npolys++; } } if (npolys == 0) { continue; } // Merge polygons. if (nvp > 3) { for (; ; ) { // Find best polygons to merge. int bestMergeVal = 0; int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; for (int j = 0; j < npolys - 1; ++j) { int pjIndex = j * nvp; //ushort* pj = &polys[j*nvp]; for (int k = j + 1; k < npolys; ++k) { //ushort* pk = &polys[k*nvp]; int pkIndex = k * nvp; int ea = 0, eb = 0; int v = getPolyMergeValue(polys, pjIndex, polys, pkIndex, mesh.verts, ref ea, ref eb, nvp); if (v > bestMergeVal) { bestMergeVal = v; bestPa = j; bestPb = k; bestEa = ea; bestEb = eb; } } } if (bestMergeVal > 0) { // Found best, merge. //ushort* pa = &polys[bestPa*nvp]; //ushort* pb = &polys[bestPb*nvp]; int paIndex = bestPa * nvp; int pbIndex = bestPb * nvp; mergePolys(polys, paIndex, polys, pbIndex, bestEa, bestEb, polys, tmpPolyIndex, nvp); //ushort* lastPoly = &polys[(npolys-1)*nvp]; int lastPolyIndex = (npolys - 1) * nvp; if (pbIndex != lastPolyIndex) { //memcpy(pb, lastPoly, sizeof(ushort)*nvp); for (int j = 0; j < nvp; ++j) { polys[pbIndex + j] = polys[lastPolyIndex + j]; } } npolys--; } else { // Could not merge any polygons, stop. break; } } } // Store polygons. for (int j = 0; j < npolys; ++j) { //ushort* p = &mesh.polys[mesh.npolys*nvp*2]; //ushort* q = &polys[j*nvp]; int pIndex = mesh.npolys * nvp * 2; int qIndex = j * nvp; for (int k = 0; k < nvp; ++k) { mesh.polys[pIndex + k] = polys[qIndex + k]; } mesh.regs[mesh.npolys] = cont.reg; mesh.areas[mesh.npolys] = cont.area; mesh.npolys++; if (mesh.npolys > maxTris) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Too many polygons " + mesh.npolys + " max " + maxTris); return false; } } } // Remove edge vertices. for (int i = 0; i < mesh.nverts; ++i) { if (vflags[i] != 0) { if (!canRemoveVertex(ctx, mesh, (ushort)i)) { continue; } if (!removeVertex(ctx, mesh, (ushort)i, maxTris)) { // Failed to remove vertex ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Failed to remove edge vertex " + i); return false; } // Remove vertex // Note: mesh.nverts is already decremented inside removeVertex()! // Fixup vertex flags for (int j = i; j < mesh.nverts; ++j) vflags[j] = vflags[j + 1]; --i; } } // Calculate adjacency. if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, nvp)) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Adjacency failed."); return false; } // Find portal edges if (mesh.borderSize > 0) { int w = cset.width; int h = cset.height; for (int i = 0; i < mesh.npolys; ++i) { int pIndex = i * 2 * nvp; //ushort* p = &mesh.polys[i*2*nvp]; for (int j = 0; j < nvp; ++j) { if (mesh.polys[pIndex + j] == RC_MESH_NULL_IDX) { break; } // Skip connected edges. if (mesh.polys[pIndex + nvp + j] != RC_MESH_NULL_IDX) { continue; } int nj = j + 1; if (nj >= nvp || mesh.polys[pIndex + nj] == RC_MESH_NULL_IDX) nj = 0; //ushort* va = &mesh.verts[mesh.polys[pIndex + j]*3]; //ushort* vb = &mesh.verts[mesh.polys[pIndex + nj]*3]; int vaIndex = mesh.polys[pIndex + j] * 3; int vbIndex = mesh.polys[pIndex + nj] * 3; if ((int)mesh.verts[vaIndex + 0] == 0 && (int)mesh.verts[vbIndex + 0] == 0) mesh.polys[pIndex + nvp + j] = 0x8000 | 0; else if ((int)mesh.verts[vaIndex + 2] == h && (int)mesh.verts[vbIndex + 2] == h) mesh.polys[pIndex + nvp + j] = 0x8000 | 1; else if ((int)mesh.verts[vaIndex + 0] == w && (int)mesh.verts[vbIndex + 0] == w) mesh.polys[pIndex + nvp + j] = 0x8000 | 2; else if ((int)mesh.verts[vaIndex + 2] == 0 && (int)mesh.verts[vbIndex + 2] == 0) mesh.polys[pIndex + nvp + j] = 0x8000 | 3; } } } // Just allocate the mesh flags array. The user is resposible to fill it. //mesh.flags = (ushort*)rcAlloc(sizeof(ushort)*mesh.npolys, RC_ALLOC_PERM); mesh.flags = new ushort[mesh.npolys]; if (mesh.flags == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.flags' " + mesh.npolys); return false; } //memset(mesh.flags, 0, sizeof(ushort) * mesh.npolys); if (mesh.nverts > 0xffff) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many vertices " + mesh.nverts + "(max " + 0xffff + ") Data can be corrupted."); } if (mesh.npolys > 0xffff) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many polygons " + mesh.npolys + " (max " + 0xffff + "). Data can be corrupted."); } ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESH); return true; }
public static bool canRemoveVertex(rcContext ctx, rcPolyMesh mesh, ushort rem) { int nvp = mesh.nvp; // Count number of polygons to remove. int numRemovedVerts = 0; int numTouchedVerts = 0; int numRemainingEdges = 0; for (int i = 0; i < mesh.npolys; ++i) { //ushort* p = &mesh.polys[i*nvp*2]; int pIndex = i * nvp * 2; int nv = countPolyVerts(mesh.polys, i * nvp * 2, nvp); int numRemoved = 0; int numVerts = 0; for (int j = 0; j < nv; ++j) { if (mesh.polys[pIndex + j] == rem) { numTouchedVerts++; numRemoved++; } numVerts++; } if (numRemoved != 0) { numRemovedVerts += numRemoved; numRemainingEdges += numVerts - (numRemoved + 1); } } // There would be too few edges remaining to create a polygon. // This can happen for example when a tip of a triangle is marked // as deletion, but there are no other polys that share the vertex. // In this case, the vertex should not be removed. if (numRemainingEdges <= 2) return false; // Find edges which share the removed vertex. int maxEdges = numTouchedVerts * 2; int nedges = 0; //rcScopedDelete<int> edges = (int*)rcAlloc(sizeof(int)*maxEdges*3, RC_ALLOC_TEMP); int[] edges = new int[maxEdges * 3]; if (edges == null) { ctx.log(rcLogCategory.RC_LOG_WARNING, "canRemoveVertex: Out of memory 'edges' " + maxEdges * 3); return false; } for (int i = 0; i < mesh.npolys; ++i) { //ushort* p = &mesh.polys[i*nvp*2]; int pIndex = i * nvp * 2; int nv = countPolyVerts(mesh.polys, pIndex, nvp); // Collect edges which touches the removed vertex. for (int j = 0, k = nv - 1; j < nv; k = j++) { if (mesh.polys[pIndex + j] == rem || mesh.polys[pIndex + k] == rem) { // Arrange edge so that a=rem. int a = mesh.polys[pIndex + j], b = mesh.polys[pIndex + k]; if (b == rem) { rcSwap(ref a, ref b); } // Check if the edge exists bool exists = false; for (int m = 0; m < nedges; ++m) { //int* e = &edges[m*3]; int eIndex = m * 3; if (edges[eIndex + 1] == b) { // Exists, increment vertex share count. edges[eIndex + 2]++; exists = true; } } // Add new edge. if (!exists) { //int* e = &edges[nedges*3]; int eIndex = nedges * 3; edges[eIndex + 0] = a; edges[eIndex + 1] = b; edges[eIndex + 2] = 1; nedges++; } } } } // There should be no more than 2 open edges. // This catches the case that two non-adjacent polygons // share the removed vertex. In that case, do not remove the vertex. int numOpenEdges = 0; for (int i = 0; i < nedges; ++i) { if (edges[i * 3 + 2] < 2) numOpenEdges++; } if (numOpenEdges > 2) return false; return true; }
public static bool removeVertex(rcContext ctx, rcPolyMesh mesh, ushort rem, int maxTris) { int nvp = mesh.nvp; // Count number of polygons to remove. int numRemovedVerts = 0; for (int i = 0; i < mesh.npolys; ++i) { //ushort* p = &mesh.polys[i*nvp*2]; int pIndex = i * nvp * 2; int nv = countPolyVerts(mesh.polys, pIndex, nvp); for (int j = 0; j < nv; ++j) { if (mesh.polys[pIndex + j] == rem) numRemovedVerts++; } } int nedges = 0; //rcScopedDelete<int> edges = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp*4, RC_ALLOC_TEMP); int[] edges = new int[numRemovedVerts * nvp * 4]; if (edges == null) { ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'edges' " + numRemovedVerts * nvp * 4); return false; } int nhole = 0; //rcScopedDelete<int> hole = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); int[] hole = new int[numRemovedVerts * nvp]; if (hole == null) { ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'hole' " + numRemovedVerts * nvp); return false; } int nhreg = 0; //rcScopedDelete<int> hreg = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); int[] hreg = new int[numRemovedVerts * nvp]; if (hreg == null) { ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'hreg' " + numRemovedVerts * nvp); return false; } int nharea = 0; //rcScopedDelete<int> harea = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); int[] harea = new int[numRemovedVerts * nvp]; if (harea == null) { ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'harea' " + numRemovedVerts * nvp); return false; } for (int i = 0; i < mesh.npolys; ++i) { //ushort* p = &mesh.polys[i*nvp*2]; int pIndex = i * nvp * 2; int nv = countPolyVerts(mesh.polys, pIndex, nvp); bool hasRem = false; for (int j = 0; j < nv; ++j) if (mesh.polys[pIndex + j] == rem) hasRem = true; if (hasRem) { // Collect edges which does not touch the removed vertex. for (int j = 0, k = nv - 1; j < nv; k = j++) { if (mesh.polys[pIndex + j] != rem && mesh.polys[pIndex + k] != rem) { //int[] e = &edges[nedges*4]; int eIndex = nedges * 4; edges[eIndex + 0] = mesh.polys[pIndex + k]; edges[eIndex + 1] = mesh.polys[pIndex + j]; edges[eIndex + 2] = mesh.regs[i]; edges[eIndex + 3] = mesh.areas[i]; nedges++; } } // Remove the polygon. //ushort* p2 = &mesh.polys[(mesh.npolys-1)*nvp*2]; int p2Index = (mesh.npolys - 1) * nvp * 2; if (mesh.polys[pIndex] != mesh.polys[p2Index]) { //memcpy(p,p2,sizeof(ushort)*nvp); for (int j = 0; j < nvp; ++j) { mesh.polys[pIndex + j] = mesh.polys[p2Index + j]; } } //memset(p+nvp,0xff,sizeof(ushort)*nvp); for (int j = 0; j < nvp; ++j) { mesh.polys[pIndex + nvp + j] = 0xffff; } mesh.regs[i] = mesh.regs[mesh.npolys - 1]; mesh.areas[i] = mesh.areas[mesh.npolys - 1]; mesh.npolys--; --i; } } // Remove vertex. for (int i = (int)rem; i < mesh.nverts; ++i) { mesh.verts[i * 3 + 0] = mesh.verts[(i + 1) * 3 + 0]; mesh.verts[i * 3 + 1] = mesh.verts[(i + 1) * 3 + 1]; mesh.verts[i * 3 + 2] = mesh.verts[(i + 1) * 3 + 2]; } mesh.nverts--; // Adjust indices to match the removed vertex layout. for (int i = 0; i < mesh.npolys; ++i) { //ushort* p = &mesh.polys[i*nvp*2]; int pIndex = i * nvp * 2; int nv = countPolyVerts(mesh.polys, i * nvp * 2, nvp); for (int j = 0; j < nv; ++j) { if (mesh.polys[pIndex + j] > rem) { mesh.polys[pIndex + j]--; } } } for (int i = 0; i < nedges; ++i) { if (edges[i * 4 + 0] > rem) { edges[i * 4 + 0]--; } if (edges[i * 4 + 1] > rem) { edges[i * 4 + 1]--; } } if (nedges == 0) { return true; } // Start with one vertex, keep appending connected // segments to the start and end of the hole. pushBack(edges[0], hole, ref nhole); pushBack(edges[2], hreg, ref nhreg); pushBack(edges[3], harea, ref nharea); while (nedges != 0) { bool match = false; for (int i = 0; i < nedges; ++i) { int ea = edges[i * 4 + 0]; int eb = edges[i * 4 + 1]; int r = edges[i * 4 + 2]; int a = edges[i * 4 + 3]; bool add = false; if (hole[0] == eb) { // The segment matches the beginning of the hole boundary. pushFront(ea, hole, ref nhole); pushFront(r, hreg, ref nhreg); pushFront(a, harea, ref nharea); add = true; } else if (hole[nhole - 1] == ea) { // The segment matches the end of the hole boundary. pushBack(eb, hole, ref nhole); pushBack(r, hreg, ref nhreg); pushBack(a, harea, ref nharea); add = true; } if (add) { // The edge segment was added, remove it. edges[i * 4 + 0] = edges[(nedges - 1) * 4 + 0]; edges[i * 4 + 1] = edges[(nedges - 1) * 4 + 1]; edges[i * 4 + 2] = edges[(nedges - 1) * 4 + 2]; edges[i * 4 + 3] = edges[(nedges - 1) * 4 + 3]; --nedges; match = true; --i; } } if (!match) break; } //rcScopedDelete<int> tris = (int*)rcAlloc(sizeof(int)*nhole*3, RC_ALLOC_TEMP); int[] tris = new int[nhole * 3]; if (tris == null) { ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'tris' " + nhole * 3); return false; } //rcScopedDelete<int> tverts = (int*)rcAlloc(sizeof(int)*nhole*4, RC_ALLOC_TEMP); int[] tverts = new int[nhole * 4]; if (tverts == null) { ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'tverts' " + nhole * 4); return false; } //rcScopedDelete<int> thole = (int*)rcAlloc(sizeof(int)*nhole, RC_ALLOC_TEMP); int[] thole = new int[nhole]; if (tverts == null) { ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'thole' " + nhole); return false; } // Generate temp vertex array for triangulation. for (int i = 0; i < nhole; ++i) { int pi = hole[i]; tverts[i * 4 + 0] = mesh.verts[pi * 3 + 0]; tverts[i * 4 + 1] = mesh.verts[pi * 3 + 1]; tverts[i * 4 + 2] = mesh.verts[pi * 3 + 2]; tverts[i * 4 + 3] = 0; thole[i] = i; } // Triangulate the hole. int ntris = triangulate(nhole, tverts, thole, tris); if (ntris < 0) { ntris = -ntris; ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: triangulate() returned bad results."); } // Merge the hole triangles back to polygons. //rcScopedDelete<ushort> polys = (ushort*)rcAlloc(sizeof(ushort)*(ntris+1)*nvp, RC_ALLOC_TEMP); ushort[] polys = new ushort[(ntris + 1) * nvp]; if (polys == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "removeVertex: Out of memory 'polys' " + (ntris + 1) * nvp); return false; } //rcScopedDelete<ushort> pregs = (ushort*)rcAlloc(sizeof(ushort)*ntris, RC_ALLOC_TEMP); ushort[] pregs = new ushort[ntris]; if (pregs == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "removeVertex: Out of memory 'pregs' " + ntris); return false; } //rcScopedDelete<byte> pareas = (byte*)rcAlloc(sizeof(byte)*ntris, RC_ALLOC_TEMP); byte[] pareas = new byte[ntris]; if (pregs == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "removeVertex: Out of memory 'pareas' " + ntris); return false; } int tmpPolyIndex = ntris * nvp; //ushort* tmpPoly = &polys[ntris*nvp]; // Build initial polygons. int npolys = 0; //memset(polys, 0xff, ntris*nvp*sizeof(ushort)); for (int i = 0; i < ntris * nvp; ++i) { polys[i] = 0xffff; } for (int j = 0; j < ntris; ++j) { //int* t = &tris[j*3]; int tIndex = j * 3; if (tris[tIndex + 0] != tris[tIndex + 1] && tris[tIndex + 0] != tris[tIndex + 2] && tris[tIndex + 1] != tris[tIndex + 2]) { polys[npolys * nvp + 0] = (ushort)hole[tris[tIndex + 0]]; polys[npolys * nvp + 1] = (ushort)hole[tris[tIndex + 1]]; polys[npolys * nvp + 2] = (ushort)hole[tris[tIndex + 2]]; pregs[npolys] = (ushort)hreg[tris[tIndex + 0]]; pareas[npolys] = (byte)harea[tris[tIndex + 0]]; npolys++; } } if (npolys == 0) { return true; } // Merge polygons. if (nvp > 3) { for (; ; ) { // Find best polygons to merge. int bestMergeVal = 0; int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; for (int j = 0; j < npolys - 1; ++j) { int pjIndex = j * nvp; //ushort* pj = &polys[j*nvp]; for (int k = j + 1; k < npolys; ++k) { int pkIndex = k * nvp; //ushort* pk = &polys[k*nvp]; int ea = 0; int eb = 0; int v = getPolyMergeValue(polys, pjIndex, polys, pkIndex, mesh.verts, ref ea, ref eb, nvp); if (v > bestMergeVal) { bestMergeVal = v; bestPa = j; bestPb = k; bestEa = ea; bestEb = eb; } } } if (bestMergeVal > 0) { // Found best, merge. //ushort* pa = &polys[bestPa*nvp]; //ushort* pb = &polys[bestPb*nvp]; int paIndex = bestPa * nvp; int pbIndex = bestPb * nvp; mergePolys(polys, paIndex, polys, pbIndex, bestEa, bestEb, polys, tmpPolyIndex, nvp); //ushort* last = &polys[(npolys-1)*nvp]; int lastIndex = (npolys - 1) * nvp; if (polys[pbIndex] != polys[lastIndex]) { //memcpy(pb, last, sizeof(ushort)*nvp); for (int j = 0; j < nvp; ++j) { polys[pbIndex + j] = polys[lastIndex + j]; } } pregs[bestPb] = pregs[npolys - 1]; pareas[bestPb] = pareas[npolys - 1]; npolys--; } else { // Could not merge any polygons, stop. break; } } } // Store polygons. for (int i = 0; i < npolys; ++i) { if (mesh.npolys >= maxTris) break; //ushort* p = &mesh.polys[mesh.npolys*nvp*2]; int pIndex = mesh.npolys * nvp * 2; for (int j = 0; j < nvp * 2; ++j) { polys[pIndex + j] = 0xffff; } //memset(p,0xff,sizeof(ushort)*nvp*2); for (int j = 0; j < nvp; ++j) { polys[pIndex + j] = polys[i * nvp + j]; } mesh.regs[mesh.npolys] = pregs[i]; mesh.areas[mesh.npolys] = pareas[i]; mesh.npolys++; if (mesh.npolys > maxTris) { ctx.log(rcLogCategory.RC_LOG_ERROR, "removeVertex: Too many polygons " + mesh.npolys + " (max:" + maxTris + ")"); return false; } } return true; }