diff --git a/src/naveditor/NavMeshPruneTool.cpp b/src/naveditor/NavMeshPruneTool.cpp index 5cf4c799..951a6cef 100644 --- a/src/naveditor/NavMeshPruneTool.cpp +++ b/src/naveditor/NavMeshPruneTool.cpp @@ -157,11 +157,11 @@ static void floodNavmesh(dtNavMesh* nav, NavmeshFlags* flags, dtPolyRef start, u } } -static void disableUnvisitedPolys(dtNavMesh* nav, NavmeshFlags* flags) +void NavMeshPruneTool::pruneUnvisitedTilesAndPolys(dtNavMesh* nav, NavmeshFlags* flags) { for (int i = 0; i < nav->getMaxTiles(); ++i) { - const dtMeshTile* tile = nav->getTile(i); + dtMeshTile* tile = nav->getTile(i); dtMeshHeader* header = tile->header; if (!header) continue; @@ -190,25 +190,16 @@ static void disableUnvisitedPolys(dtNavMesh* nav, NavmeshFlags* flags) if (numUnlinkedPolys == header->polyCount) { header->userId = DT_FULL_UNLINKED_TILE_USER_ID; - continue; + nav->removeTile(nav->getTileRef(tile), 0, 0); + } + else + { + header->userId = DT_SEMI_UNLINKED_TILE_USER_ID; + dtUpdateNavMeshData(nav, (unsigned int)i); } } } -static void removeUnlinkedTiles(dtNavMesh* nav) -{ - for (int i = nav->getMaxTiles(); i-- > 0;) - { - const dtMeshTile* tile = nav->getTile(i); - const dtMeshHeader* header = tile->header; - - if (!header) continue; - - if (header->userId == DT_FULL_UNLINKED_TILE_USER_ID) - nav->removeTile(nav->getTileRef(tile), 0, 0); - }; -} - NavMeshPruneTool::NavMeshPruneTool() : m_editor(0), m_flags(0), @@ -249,13 +240,6 @@ void NavMeshPruneTool::handleMenu() dtNavMesh* nav = m_editor->getNavMesh(); if (!nav) return; - // todo(amos): once tile rebuilding is done, also remove unlinked polygons! - if (m_ranPruneTool && ImGui::Button("Remove Unlinked Tiles")) - { - removeUnlinkedTiles(nav); - m_ranPruneTool = false; - } - if (!m_flags) return; if (ImGui::Button("Clear Selection")) @@ -265,7 +249,7 @@ void NavMeshPruneTool::handleMenu() if (ImGui::Button("Prune Unselected")) { - disableUnvisitedPolys(nav, m_flags); + pruneUnvisitedTilesAndPolys(nav, m_flags); dtTraverseTableCreateParams params; m_editor->createTraverseTableParams(¶ms); @@ -294,7 +278,7 @@ void NavMeshPruneTool::handleClick(const float* s, const float* p, const int /*v rdVcopy(m_hitPos, p); m_hitPosSet = true; - const float halfExtents[3] = { 2, 2, 4 }; + const float halfExtents[3] = { 64, 64, 128 }; dtQueryFilter filter; dtPolyRef ref = 0; diff --git a/src/naveditor/include/NavMeshPruneTool.h b/src/naveditor/include/NavMeshPruneTool.h index 948abeda..1dc88cbd 100644 --- a/src/naveditor/include/NavMeshPruneTool.h +++ b/src/naveditor/include/NavMeshPruneTool.h @@ -48,6 +48,8 @@ public: virtual void handleRender(); virtual void handleRenderOverlay(double* proj, double* model, int* view); + void pruneUnvisitedTilesAndPolys(dtNavMesh* nav, NavmeshFlags* flags); + private: // Explicitly disabled copy constructor and copy assignment operator. NavMeshPruneTool(const NavMeshPruneTool&); diff --git a/src/thirdparty/recast/Detour/Include/DetourNavMesh.h b/src/thirdparty/recast/Detour/Include/DetourNavMesh.h index c6bfc0f0..b45c73cf 100644 --- a/src/thirdparty/recast/Detour/Include/DetourNavMesh.h +++ b/src/thirdparty/recast/Detour/Include/DetourNavMesh.h @@ -562,7 +562,7 @@ public: unsigned int linksFreeList; ///Index to the next free link. dtMeshHeader* header; ///The tile header. dtPoly* polys; ///The tile polygons. [Size: dtMeshHeader::polyCount] - int* polyMap; ///TODO: needs to be reversed. + unsigned int* polyMap; ///TODO: needs to be reversed. float* verts; ///The tile vertices. [Size: dtMeshHeader::vertCount] dtLink* links; ///The tile links. [Size: dtMeshHeader::maxLinkCount] dtPolyDetail* detailMeshes; ///The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount] diff --git a/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h b/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h index c20bbbe9..649e9f4c 100644 --- a/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h +++ b/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h @@ -25,7 +25,6 @@ /// @ingroup detour struct dtNavMeshCreateParams { - /// @name Polygon Mesh Attributes /// Used to create the base navigation graph. /// See #rcPolyMesh for details related to these attributes. @@ -239,12 +238,20 @@ bool dtCreateTraverseTableData(const dtTraverseTableCreateParams* params); /// @return True if the tile data was successfully created. bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, int* outDataSize); -/// Swaps the endianess of the tile data's header (#dtMeshHeader). +/// Updates navigation mesh tiles by removing all unlinked polygons. +/// @ingroup detour +/// @param[in] nav The navmesh containing the tile. +/// @param[in] tileIndex The index of the tile to update. +/// @return True if the tile data was successfully updated. +class dtNavMesh; +bool dtUpdateNavMeshData(dtNavMesh* nav, const unsigned int tileIndex); + +/// Swaps the endianness of the tile data's header (#dtMeshHeader). /// @param[in,out] data The tile data array. /// @param[in] dataSize The size of the data array. bool dtNavMeshHeaderSwapEndian(unsigned char* data, const int dataSize); -/// Swaps endianess of the tile data. +/// Swaps endianness of the tile data. /// @param[in,out] data The tile data array. /// @param[in] dataSize The size of the data array. bool dtNavMeshDataSwapEndian(unsigned char* data, const int dataSize); diff --git a/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp b/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp index f6f8417a..e68c7a7c 100644 --- a/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp +++ b/src/thirdparty/recast/Detour/Source/DetourNavMesh.cpp @@ -191,6 +191,11 @@ int dtCalcTraverseTableCellIndex(const int numPolyGroups, int dtCalcTraverseTableSize(const int numPolyGroups) { + // If we only have 2 poly groups, we don't need a traverse table as group + // 1 is for unlinked polygons and group 2 for linked polygons. + if (numPolyGroups <= 2) + return 0; + return sizeof(int)*(numPolyGroups*((numPolyGroups+(RD_BITS_PER_BIT_CELL-1))/RD_BITS_PER_BIT_CELL)); } @@ -1414,7 +1419,7 @@ dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags, unsigned char* d = data + headerSize; tile->verts = rdGetThenAdvanceBufferPointer(d, vertsSize); tile->polys = rdGetThenAdvanceBufferPointer(d, polysSize); - tile->polyMap = rdGetThenAdvanceBufferPointer(d, polyMapSize); + tile->polyMap = rdGetThenAdvanceBufferPointer(d, polyMapSize); tile->links = rdGetThenAdvanceBufferPointer(d, linksSize); tile->detailMeshes = rdGetThenAdvanceBufferPointer(d, detailMeshesSize); tile->detailVerts = rdGetThenAdvanceBufferPointer(d, detailVertsSize); @@ -1616,11 +1621,13 @@ int dtNavMesh::getMaxTiles() const dtMeshTile* dtNavMesh::getTile(int i) { + rdAssert(i >= 0 && i < m_maxTiles); return &m_tiles[i]; } const dtMeshTile* dtNavMesh::getTile(int i) const { + rdAssert(i >= 0 && i < m_maxTiles); return &m_tiles[i]; } diff --git a/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp b/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp index 7ec3c262..6963a2dd 100644 --- a/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp +++ b/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp @@ -190,15 +190,15 @@ static bool createBVTree(dtNavMeshCreateParams* params, rdTempVector& no if (params->detailMeshes) { const int vb = (int)params->detailMeshes[i*4+0]; - vertCount = (int)params->detailMeshes[i*4+1]; + vertCount = (int)params->detailMeshes[i*4+1]; targetVert = ¶ms->detailVerts[vb*3]; } else { const int nvp = params->nvp; - const unsigned short* p = ¶ms->polys[i*nvp * 2]; + vertCount = rdCountPolyVerts(p, nvp); for (int j = 0; j < vertCount; ++j) @@ -242,6 +242,74 @@ static bool createBVTree(dtNavMeshCreateParams* params, rdTempVector& no return true; } +static bool rebuildBVTree(dtMeshTile* tile, const unsigned short* oldPolyIndices, const int polyCount, rdTempVector& nodes) +{ + BVItem* items = (BVItem*)rdAlloc(sizeof(BVItem)*polyCount, RD_ALLOC_TEMP); + + if (!items) + return false; + + // note(amos): reserve enough memory here to avoid reallocation during subdivisions. + if (!nodes.reserve(polyCount * 2)) + return false; + + const dtMeshHeader* header = tile->header; + const float quantFactor = header->bvQuantFactor; + + for (int i = 0; i < polyCount; i++) + { + BVItem& it = items[i]; + it.i = i; + + const int oldPolyIndex = oldPolyIndices[i]; + const dtPoly& poly = tile->polys[oldPolyIndex]; + + rdAssert(poly.getType() != DT_POLYTYPE_OFFMESH_CONNECTION); + + //if (poly.getType() == DT_POLYTYPE_OFFMESH_CONNECTION) + // continue; + + const dtPolyDetail& detail = tile->detailMeshes[oldPolyIndex]; + + float bmin[3]; + float bmax[3]; + + rdVset(bmin, FLT_MAX, FLT_MAX, FLT_MAX); + rdVset(bmax, -FLT_MAX, -FLT_MAX, -FLT_MAX); + + for (int j = 0; j < detail.triCount; ++j) + { + const unsigned char* t = &tile->detailTris[(detail.triBase + j) * 4]; + float triVerts[3][3]; + + for (int k = 0; k < 3; ++k) + { + if (t[k] < poly.vertCount) + rdVcopy(triVerts[k], &tile->verts[poly.verts[t[k]] * 3]); + else + rdVcopy(triVerts[k], &tile->detailVerts[(detail.vertBase + t[k] - poly.vertCount) * 3]); + + rdVmin(bmin, triVerts[k]); + rdVmax(bmax, triVerts[k]); + } + } + + // BV-tree uses cs for all dimensions + it.bmin[0] = (unsigned short)rdClamp((int)((header->bmax[0] - bmax[0]) * quantFactor), 0, 0xffff); + it.bmin[1] = (unsigned short)rdClamp((int)((bmin[1] - header->bmin[1]) * quantFactor), 0, 0xffff); + it.bmin[2] = (unsigned short)rdClamp((int)((bmin[2] - header->bmin[2]) * quantFactor), 0, 0xffff); + + it.bmax[0] = (unsigned short)rdClamp((int)((header->bmax[0] - bmin[0]) * quantFactor), 0, 0xffff); + it.bmax[1] = (unsigned short)rdClamp((int)((bmax[1] - header->bmin[1]) * quantFactor), 0, 0xffff); + it.bmax[2] = (unsigned short)rdClamp((int)((bmax[2] - header->bmin[2]) * quantFactor), 0, 0xffff); + } + + subdivide(items, polyCount, 0, polyCount, nodes); + rdFree(items); + + return true; +} + static void setPolyGroupsTraversalReachability(int* const tableData, const int numPolyGroups, const unsigned short polyGroup1, const unsigned short polyGroup2, const bool isReachable) { @@ -382,9 +450,16 @@ bool dtCreateDisjointPolyGroups(const dtTraverseTableCreateParams* params) static void unionTraverseLinkedPolyGroups(const dtTraverseTableCreateParams* params, const int tableIndex) { - dtNavMesh* nav = params->nav; + if (params->collapseGroups) + return; + dtDisjointSet& set = params->sets[tableIndex]; + if (!set.getSetCount()) + return; + + dtNavMesh* nav = params->nav; + // Sixth pass to handle traverse linked poly's. for (int i = 0; i < nav->getMaxTiles(); ++i) { @@ -514,6 +589,9 @@ bool dtCreateTraverseTableData(const dtTraverseTableCreateParams* params) nav->setTraverseTableSize(tableSize); nav->setTraverseTableCount(tableCount); + if (!tableSize) + return true; + for (int i = 0; i < tableCount; i++) { int* const traverseTable = (int*)rdAlloc(sizeof(int)*tableSize, RD_ALLOC_PERM); @@ -703,10 +781,7 @@ static bool createPolyMeshCells(const dtNavMeshCreateParams* params, rdTempVecto cellItems.resize(newCount); CellItem& cell = cellItems[newCount-1]; - cell.pos[0] = targetCellPos[0]; - cell.pos[1] = targetCellPos[1]; - cell.pos[2] = targetCellPos[2]; - + rdVcopy(cell.pos, targetCellPos); cell.polyIndex = i; } } @@ -1120,7 +1195,7 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, for (int i = 0; i < (int)treeItems.size(); i++) { dtBVNode& node = navBvtree[i]; - BVItem& item = treeItems[i]; + const BVItem& item = treeItems[i]; node.bmin[0] = item.bmin[0]; node.bmin[1] = item.bmin[1]; @@ -1161,7 +1236,7 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, } #if DT_NAVMESH_SET_VERSION >= 8 - // Polygon cells. + // Store polygon cells. for (int i = 0; i < (int)cellItems.size(); i++) { const CellItem& cellItem = cellItems[i]; @@ -1230,6 +1305,473 @@ bool dtNavMeshHeaderSwapEndian(unsigned char* data, const int /*dataSize*/) return true; } +/// @par +/// +/// This function will remove all polygons marked #DT_UNLINKED_POLY_GROUP from +/// the tile. Its associated data, such as the detail polygons, links, cells, +/// etc will also be removed. The BVTree is the only data that needs to be +/// rebuilt as we have to re-subdivide the bounding volumes with only the +/// polygons that remain to exist. Off-mesh connections that lack the poly flag +/// #DT_POLYFLAGS_JUMP_LINKED will also be removed. +bool dtUpdateNavMeshData(dtNavMesh* nav, const unsigned int tileIndex) +{ + dtMeshTile* tile = nav->getTile(tileIndex); + const dtMeshHeader* header = tile->header; + + // Remove the tile instead of updating it! + rdAssert(header->userId != DT_FULL_UNLINKED_TILE_USER_ID); + + rdScopedDelete oldPolyIdMap((unsigned short*)rdAlloc(sizeof(unsigned short)*header->polyCount, RD_ALLOC_TEMP)); + rdScopedDelete newPolyIdMap((unsigned short*)rdAlloc(sizeof(unsigned short)*header->polyCount, RD_ALLOC_TEMP)); + + rdScopedDelete oldVertIdMap((unsigned short*)rdAlloc(sizeof(unsigned short)*header->vertCount, RD_ALLOC_TEMP)); + rdScopedDelete newVertIdMap((unsigned short*)rdAlloc(sizeof(unsigned short)*header->vertCount, RD_ALLOC_TEMP)); + + memset(newVertIdMap, 0xff, sizeof(unsigned short)*header->vertCount); + + rdScopedDelete oldOffMeshConnIdMap((int*)rdAlloc(sizeof(int)*header->offMeshConCount, RD_ALLOC_TEMP)); + rdScopedDelete newOffMeshConnIdMap((int*)rdAlloc(sizeof(int)*header->offMeshConCount, RD_ALLOC_TEMP)); + + rdScopedDelete oldLinkIdMap((unsigned int*)rdAlloc(sizeof(unsigned int)*header->maxLinkCount, RD_ALLOC_TEMP)); + rdScopedDelete newLinkIdMap((unsigned int*)rdAlloc(sizeof(unsigned int)*header->maxLinkCount, RD_ALLOC_TEMP)); + + memset(newLinkIdMap, 0xff, sizeof(unsigned int)*header->maxLinkCount); + + int totPolyCount = 0, offMeshConCount = 0, detailTriCount = 0, portalCount = 0, detailVertCount = 0, vertCount = 0, maxLinkCount = 0; + + // Iterate through this tile's polys, indexing them by their new poly ids + for (int i = 0; i < header->polyCount; i++) + { + const dtPoly& poly = tile->polys[i]; + + // Unlinked polygon, drop it. + if (poly.groupId == DT_UNLINKED_POLY_GROUP) + continue; + + const bool isOffMeshConn = poly.getType() == DT_POLYTYPE_OFFMESH_CONNECTION; + + if (isOffMeshConn) + { + // Unlinked off-mesh connection, drop it. + if (!(poly.flags & DT_POLYFLAGS_JUMP_LINKED)) + continue; + + for (int c = 0; c < header->offMeshConCount; c++) + { + const dtOffMeshConnection& conn = tile->offMeshCons[c]; + + if (conn.poly != i) + continue; + + oldOffMeshConnIdMap[c] = offMeshConCount; + newOffMeshConnIdMap[offMeshConCount] = c; + + offMeshConCount++; + break; + } + } + else + { + for (int j = 0; j < poly.vertCount; j++) + { + if (poly.neis[j] == RD_MESH_NULL_IDX) + continue; + + if (poly.neis[j] & DT_EXT_LINK) + portalCount++; + } + } + + oldPolyIdMap[totPolyCount] = (unsigned short)i; + newPolyIdMap[i] = (unsigned short)totPolyCount++; + + // Flag this poly's vertices so we can throw out those that end up being + // eliminated. Skip vertices which have already been identified as this + // will also prevent us from creating duplicates (and will remove them if + // there are any in the input). + for (unsigned int v = 0; v < poly.vertCount; v++) + { + if (newVertIdMap[poly.verts[v]] != 0xffff) + continue; + + oldVertIdMap[vertCount] = poly.verts[v]; + newVertIdMap[poly.verts[v]] = (unsigned short)vertCount++; + } + + // Off-mesh links don't have detail meshes. + if (!isOffMeshConn) + { + detailVertCount += tile->detailMeshes[i].vertCount; + detailTriCount += tile->detailMeshes[i].triCount; + } + + // Flag all links connected to this polygon. + for (unsigned int k = poly.firstLink; k != DT_NULL_LINK; k = tile->links[k].next) + { + const dtLink& link = tile->links[k]; + + // Skip invalid and visited. + if (!link.ref || newLinkIdMap[k] != 0xffffffff) + continue; + + oldLinkIdMap[maxLinkCount] = k; + newLinkIdMap[k] = maxLinkCount++; + } + } + + if (!totPolyCount) + { + // This happens when all polygons in a tile are marked unreachable. The + // tile itself has to be removed entirely. + rdAssert(0); + return false; + } + + // Count without off-mesh link polygons. + const int polyCount = totPolyCount - offMeshConCount; + + rdTempVector treeItems; + if (header->bvNodeCount) + { + if (!rebuildBVTree(tile, oldPolyIdMap, polyCount, treeItems)) + { + rdAssert(0); + return false; + } + } + +#if DT_NAVMESH_SET_VERSION >= 8 + rdTempVector cellItems(header->maxCellCount); + int numCellsKept = 0; + + for (int i = 0; i < header->maxCellCount; i++) + { + const dtCell& cell = tile->cells[i]; + const dtPoly& poly = tile->polys[cell.polyIndex]; + + // Don't copy cells residing on dead polygons. + if (poly.groupId == DT_UNLINKED_POLY_GROUP) + continue; + + CellItem& newCell = cellItems[numCellsKept++]; + + rdVcopy(newCell.pos, cell.pos); + newCell.polyIndex = newPolyIdMap[cell.polyIndex]; + } +#endif + const int polyMapCount = header->polyMapCount; + + const int headerSize = rdAlign4(sizeof(dtMeshHeader)); + const int vertsSize = rdAlign4(sizeof(float)*3*vertCount); + const int polysSize = rdAlign4(sizeof(dtPoly)*totPolyCount); + const int polyMapSize = rdAlign4(sizeof(int)*(polyMapCount*totPolyCount)); + const int linksSize = rdAlign4(sizeof(dtLink)*maxLinkCount); + const int detailMeshesSize = rdAlign4(sizeof(dtPolyDetail)*polyCount); + const int detailVertsSize = rdAlign4(sizeof(float)*3*detailVertCount); + const int detailTrisSize = rdAlign4(sizeof(unsigned char)*4*detailTriCount); + const int bvTreeSize = rdAlign4(sizeof(dtBVNode)*(int)treeItems.size()); + const int offMeshConsSize = rdAlign4(sizeof(dtOffMeshConnection)*offMeshConCount); +#if DT_NAVMESH_SET_VERSION >= 8 + const int cellsSize = rdAlign4(sizeof(dtCell)*numCellsKept); +#endif + + const unsigned int dataSize = headerSize + vertsSize + polysSize + polyMapSize + linksSize + + detailMeshesSize + detailVertsSize + detailTrisSize + + bvTreeSize + offMeshConsSize +#if DT_NAVMESH_SET_VERSION >= 8 + + cellsSize +#endif + ; + + unsigned char* data = new unsigned char[dataSize]; + + if (!data) + return false; + + memset(data, 0, dataSize); + unsigned char* d = data; + + dtMeshHeader* newHeader = rdGetThenAdvanceBufferPointer(d, headerSize); + float* navVerts = rdGetThenAdvanceBufferPointer(d, vertsSize); + dtPoly* navPolys = rdGetThenAdvanceBufferPointer(d, polysSize); + unsigned int* polyMap = rdGetThenAdvanceBufferPointer(d, polyMapSize); + dtLink* links = rdGetThenAdvanceBufferPointer(d, linksSize); + dtPolyDetail* navDMeshes = rdGetThenAdvanceBufferPointer(d, detailMeshesSize); + float* navDVerts = rdGetThenAdvanceBufferPointer(d, detailVertsSize); + unsigned char* navDTris = rdGetThenAdvanceBufferPointer(d, detailTrisSize); + dtBVNode* navBvtree = rdGetThenAdvanceBufferPointer(d, bvTreeSize); + dtOffMeshConnection* offMeshCons = rdGetThenAdvanceBufferPointer(d, offMeshConsSize); +#if DT_NAVMESH_SET_VERSION >= 8 + dtCell* navCells = rdGetThenAdvanceBufferPointer(d, cellsSize); +#endif + + // Store header + newHeader->magic = DT_NAVMESH_MAGIC; + newHeader->version = DT_NAVMESH_VERSION; + newHeader->x = header->x; + newHeader->y = header->y; + newHeader->layer = header->layer; + newHeader->userId = 0; + newHeader->polyCount = totPolyCount; + newHeader->polyMapCount = polyMapCount; + newHeader->vertCount = vertCount; + newHeader->maxLinkCount = maxLinkCount; + newHeader->detailMeshCount = polyCount; + newHeader->detailVertCount = detailVertCount; + newHeader->detailTriCount = detailTriCount; + newHeader->bvNodeCount = (int)treeItems.size(); + newHeader->offMeshConCount = offMeshConCount; + newHeader->offMeshBase = polyCount; +#if DT_NAVMESH_SET_VERSION >= 8 + newHeader->maxCellCount = numCellsKept; +#endif + newHeader->walkableHeight = header->walkableHeight; + newHeader->walkableRadius = header->walkableRadius; + newHeader->walkableClimb = header->walkableClimb; + rdVcopy(newHeader->bmin, header->bmin); + rdVcopy(newHeader->bmax, header->bmax); + newHeader->bvQuantFactor = header->bvQuantFactor; + + // Store vertices. + for (int i = 0; i < vertCount; i++) + rdVcopy(&navVerts[i*3], &tile->verts[oldVertIdMap[i]*3]); + + // Store polygons. + for (int i = 0; i < totPolyCount; i++) + { + const dtPoly& ip = tile->polys[oldPolyIdMap[i]]; + dtPoly& p = navPolys[i]; + + rdAssert(ip.groupId != DT_UNLINKED_POLY_GROUP); + + p.firstLink = newLinkIdMap[ip.firstLink]; + p.flags = ip.flags; + p.vertCount = ip.vertCount; + p.areaAndtype = ip.areaAndtype; + p.groupId = ip.groupId; + p.surfaceArea = ip.surfaceArea; +#if DT_NAVMESH_SET_VERSION >= 7 + p.unk1 = ip.unk1; + p.unk2 = ip.unk2; +#endif + rdVcopy(p.center, ip.center); + + for (int v = 0; v < p.vertCount; v++) + p.verts[v] = newVertIdMap[ip.verts[v]]; + + for (int n = 0; n < RD_VERTS_PER_POLYGON; n++) + { + // if this is a portal, leave these values unchanged + if (ip.neis[n] & DT_EXT_LINK || !ip.neis[n]) + p.neis[n] = ip.neis[n]; + else + p.neis[n] = newPolyIdMap[ip.neis[n]-1]+1; + } + } + + // Store polymap. + for (int i = 0; i < polyMapCount; i++) + { + unsigned int* oldPolyMapBase = &tile->polyMap[i*header->polyCount]; + unsigned int* newPolyMapBase = &polyMap[i*totPolyCount]; + + for (int j = 0; j < totPolyCount; j++) + newPolyMapBase[j] = oldPolyMapBase[oldPolyIdMap[j]]; + } + + // Fix up internal references and store links. + const dtPolyRef polyRefBase = nav->getPolyRefBase(tile); + + for (int i = 0; i < maxLinkCount; i++) + { + const unsigned int oldIdx = oldLinkIdMap[i]; + + const dtLink& oldLink = tile->links[oldIdx]; + dtLink& newLink = links[i]; + + unsigned int salt, it, ip; + nav->decodePolyId(oldLink.ref, salt, it, ip); + + const bool sameTile = it == tileIndex; + + const dtPolyRef newRef = sameTile + ? (polyRefBase | (dtPolyRef)newPolyIdMap[ip]) + : oldLink.ref; + + const bool nullLink = oldLink.next == DT_NULL_LINK; + + const unsigned int newNext = nullLink + ? DT_NULL_LINK + : newLinkIdMap[oldLink.next]; + + const unsigned short newReverseLink = !sameTile + ? oldLink.reverseLink + : oldLink.reverseLink == DT_NULL_TRAVERSE_REVERSE_LINK + ? DT_NULL_TRAVERSE_REVERSE_LINK + : (unsigned short)newLinkIdMap[oldLink.reverseLink]; + + newLink.ref = newRef; + newLink.next = newNext; + newLink.edge = oldLink.edge; + newLink.side = oldLink.side; + newLink.bmin = oldLink.bmin; + newLink.bmax = oldLink.bmax; + newLink.traverseType = oldLink.traverseType; + newLink.traverseDist = oldLink.traverseDist; + newLink.reverseLink = newReverseLink; + } + + // Fix up external reverences from neighboring tiles. + static const int MAX_NEIS = 32; + dtMeshTile* neis[MAX_NEIS]; + + for (int i = 0; i < 8; ++i) + { + const int nneis = nav->getNeighbourTilesAt(header->x, header->y, i, neis, MAX_NEIS); + for (int j = 0; j < nneis; ++j) + { + const dtMeshTile* neiTile = neis[j]; + const dtMeshHeader* neiHdr = neiTile->header; + + for (int k = 0; k < neiHdr->polyCount; k++) + { + const dtPoly& neiPoly = neiTile->polys[k]; + + for (unsigned int l = neiPoly.firstLink; l != DT_NULL_LINK; l = neiTile->links[l].next) + { + dtLink& neiLink = neiTile->links[l]; + + unsigned int salt, it, ip; + nav->decodePolyId(neiLink.ref, salt, it, ip); + + if (it != tileIndex) + continue; + + const dtPolyRef newRef = (polyRefBase | (dtPolyRef)newPolyIdMap[ip]); + neiLink.ref = newRef; + + if (neiLink.reverseLink != DT_NULL_TRAVERSE_REVERSE_LINK) + neiLink.reverseLink = (unsigned short)newLinkIdMap[neiLink.reverseLink]; + } + } + } + } + + // Store detail meshes. + unsigned int vbase = 0; + unsigned int tbase = 0; + for (int i = 0; i < polyCount; i++) + { + const int oldPolyId = oldPolyIdMap[i]; + const dtPoly& oldPoly = tile->polys[oldPolyId]; + + rdAssert(oldPoly.getType() != DT_POLYTYPE_OFFMESH_CONNECTION); + + const dtPolyDetail& oldDetail = tile->detailMeshes[oldPolyId]; + dtPolyDetail& newDetail = navDMeshes[i]; + + const unsigned int vertBase = oldDetail.vertBase; + const unsigned char dVertCount = oldDetail.vertCount; + const unsigned int triBase = oldDetail.triBase; + const unsigned char triCount = oldDetail.triCount; + + newDetail.vertBase = vbase; + newDetail.vertCount = dVertCount; + newDetail.triBase = tbase; + newDetail.triCount = triCount; + + for (unsigned char j = 0; j < triCount; j++) + { + // Copy four bytes (first 3 for vertex indices for the triangle, 4th for flags) + memcpy(&navDTris[tbase++*4], &tile->detailTris[(triBase+j)*4], sizeof(unsigned char)*4); + } + + for (unsigned char j = 0; j < dVertCount; j++) + rdVcopy(&navDVerts[vbase++*3], &tile->detailVerts[(vertBase+j)*3]); + } + + // Store BVTree. + if (bvTreeSize) + { + for (int i = 0; i < (int)treeItems.size(); i++) + { + const BVItem& item = treeItems[i]; + dtBVNode& node = navBvtree[i]; + + node.bmin[0] = item.bmin[0]; + node.bmin[1] = item.bmin[1]; + node.bmin[2] = item.bmin[2]; + node.bmax[0] = item.bmax[0]; + node.bmax[1] = item.bmax[1]; + node.bmax[2] = item.bmax[2]; + node.i = item.i; + } + } + + // Store Off-Mesh connections. + for (int i = 0; i < offMeshConCount; i++) + { + const dtOffMeshConnection& oldConn = tile->offMeshCons[oldOffMeshConnIdMap[i]]; + dtOffMeshConnection& newConn = offMeshCons[i]; + + rdVcopy(&newConn.pos[0], &oldConn.pos[0]); + rdVcopy(&newConn.pos[3], &oldConn.pos[3]); + newConn.rad = oldConn.rad; + newConn.poly = newPolyIdMap[oldConn.poly]; + newConn.side = oldConn.side; + newConn.userId = oldConn.userId; +#if DT_NAVMESH_SET_VERSION >= 7 + newConn.traverseType = oldConn.traverseType; + newConn.hintIndex = oldConn.hintIndex; +#else + newConn.flags = oldConn.flags; + newConn.traverseContext = oldConn.traverseContext; +#endif + rdVcopy(newConn.refPos, oldConn.refPos); + newConn.refYaw = oldConn.refYaw; +#if DT_NAVMESH_SET_VERSION >= 9 + rdVcopy(&newConn.secPos[0], &oldConn.secPos[0]); + rdVcopy(&newConn.secPos[3], &oldConn.secPos[3]); +#endif + } + +#if DT_NAVMESH_SET_VERSION >= 8 + // Store polygon cells. + for (int i = 0; i < numCellsKept; i++) + { + const CellItem& cellItem = cellItems[i]; + dtCell& cell = navCells[i]; + + rdVcopy(cell.pos, cellItem.pos); + cell.polyIndex = cellItem.polyIndex; + cell.setOccupied(); + } +#endif + + // Free old data. + rdFree(tile->data); + + // Store tile. + tile->linksFreeList = DT_NULL_LINK; // All null links are pruned at this point. + tile->header = newHeader; + tile->verts = navVerts; + tile->polys = navPolys; + tile->polyMap = polyMap; + tile->links = links; + tile->detailMeshes = navDMeshes; + tile->detailVerts = navDVerts; + tile->detailTris = navDTris; + tile->bvTree = navBvtree; + tile->offMeshCons = offMeshCons; +#if DT_NAVMESH_SET_VERSION >= 8 + tile->cells = navCells; +#endif + tile->data = data; + tile->dataSize = dataSize; + + return true; +} + /// @par /// /// @warning This function assumes that the header is in the correct endianess already.