From 4a34481e43be121b5e42ff0b60e39c861a4fda0e Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:25:14 +0200 Subject: [PATCH] Recast: implement tile update algorithm to prune all unlinked polygons The algorithm goes over each polygon in the tile and only keeps polygons and associated data if they don't originate from an unlinked polygon island (DT_UNLINKED_POLY_GROUP). The algorithm also drops all data associated to the polygons such as the detail meshes, cells, links, verts and detail (if not used by other polygons), off-mesh links that are dead (not having the DT_POLYFLAGS_JUMP_LINKED flag), and rebuilds the BVTree. This algorithm reduced the file size of a navmesh generated for the map mp_rr_desertlands_64k_x_64k by over 50% (209mb to 97mb) while keeping it functionally identical. Some additional updates that were required to make this work: * dtCalcTraverseTableSize now return NULL if there are 2 or less polygroups. * unionTraverseLinkedPolyGroups will not run if parameters instruct it to collapse groups, it will also not run if the disjoint set doesn't contain any poly groups. * dtCreateTraverseTableData will not allocate traverse tables if there are 2 or less polygroups, as dtCalcTraverseTableSize will now always return 0 in this case. This is an enforcement of an optimization from the engine itself as there is no point in allocating and doing lookups on a table with only 2 groups (unlinked and linked), and linked will always be reachable and unlinked can't be reached in the first place. --- src/naveditor/NavMeshPruneTool.cpp | 36 +- src/naveditor/include/NavMeshPruneTool.h | 2 + .../recast/Detour/Include/DetourNavMesh.h | 2 +- .../Detour/Include/DetourNavMeshBuilder.h | 13 +- .../recast/Detour/Source/DetourNavMesh.cpp | 9 +- .../Detour/Source/DetourNavMeshBuilder.cpp | 560 +++++++++++++++++- 6 files changed, 582 insertions(+), 40 deletions(-) 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.