From ebfc4ec0913ad9a14f18a2f0c0eae95222fb0ffc Mon Sep 17 00:00:00 2001 From: Amos Date: Wed, 24 Jul 2024 11:30:00 +0200 Subject: [PATCH] Recast: implement polygon cell generation algorithm Fully generates polygon cells for the entire navmesh. Previously we never build this which caused ai to clip into, or walk into each other in-game (this system is an MSET 8 feature). --- src/naveditor/Editor.cpp | 14 +- src/naveditor/Editor_TileMesh.cpp | 1 + src/naveditor/InputGeom.cpp | 6 +- src/naveditor/include/Editor.h | 2 + src/naveditor/include/InputGeom.h | 2 + .../Detour/Include/DetourNavMeshBuilder.h | 1 + .../Detour/Source/DetourNavMeshBuilder.cpp | 139 +++++++++++++++++- 7 files changed, 152 insertions(+), 13 deletions(-) diff --git a/src/naveditor/Editor.cpp b/src/naveditor/Editor.cpp index 31d008fe..27fafb8a 100644 --- a/src/naveditor/Editor.cpp +++ b/src/naveditor/Editor.cpp @@ -151,6 +151,7 @@ void Editor::handleMeshChanged(InputGeom* geom) m_edgeMaxLen = buildSettings->edgeMaxLen; m_edgeMaxError = buildSettings->edgeMaxError; m_vertsPerPoly = buildSettings->vertsPerPoly; + m_polyCellRes = buildSettings->polyCellRes; m_detailSampleDist = buildSettings->detailSampleDist; m_detailSampleMaxError = buildSettings->detailSampleMaxError; m_partitionType = buildSettings->partitionType; @@ -170,6 +171,7 @@ void Editor::collectSettings(BuildSettings& settings) settings.edgeMaxLen = m_edgeMaxLen; settings.edgeMaxError = m_edgeMaxError; settings.vertsPerPoly = m_vertsPerPoly; + settings.polyCellRes = m_polyCellRes; settings.detailSampleDist = m_detailSampleDist; settings.detailSampleMaxError = m_detailSampleMaxError; settings.partitionType = m_partitionType; @@ -317,6 +319,7 @@ void Editor::handleCommonSettings() ImGui::SliderInt("Max Edge Length", &m_edgeMaxLen, 0, 50); // todo(amos): increase due to larger scale maps? ImGui::SliderFloat("Max Edge Error", &m_edgeMaxError, 0.1f, 3.0f); ImGui::SliderInt("Verts Per Poly", &m_vertsPerPoly, 3, 6); + ImGui::SliderInt("Poly Cell Resolution", &m_polyCellRes, 1, 16); ImGui::Separator(); ImGui::Text("Detail Mesh"); @@ -524,11 +527,11 @@ void Editor::renderDetourDebugMenu() // NOTE: the climb height should never equal or exceed the agent's height, see https://groups.google.com/g/recastnavigation/c/L5rBamxcOBk/m/5xGLj6YP25kJ // Quote: "you will get into trouble in cases where there is an overhand which is low enough to step over and high enough for the agent to walk under." const hulldef hulls[NAVMESH_COUNT] = { - { g_navMeshNames[NAVMESH_SMALL] , NAI_Hull::Width(HULL_HUMAN) * NAI_Hull::Scale(HULL_HUMAN) , NAI_Hull::Height(HULL_HUMAN) , NAI_Hull::Height(HULL_HUMAN) * NAI_Hull::Scale(HULL_HUMAN) , 32 }, - { g_navMeshNames[NAVMESH_MED_SHORT] , NAI_Hull::Width(HULL_PROWLER) * NAI_Hull::Scale(HULL_PROWLER), NAI_Hull::Height(HULL_PROWLER), NAI_Hull::Height(HULL_PROWLER) * NAI_Hull::Scale(HULL_PROWLER), 32 }, - { g_navMeshNames[NAVMESH_MEDIUM] , NAI_Hull::Width(HULL_MEDIUM) * NAI_Hull::Scale(HULL_MEDIUM) , NAI_Hull::Height(HULL_MEDIUM) , NAI_Hull::Height(HULL_MEDIUM) * NAI_Hull::Scale(HULL_MEDIUM) , 32 }, - { g_navMeshNames[NAVMESH_LARGE] , NAI_Hull::Width(HULL_TITAN) * NAI_Hull::Scale(HULL_TITAN) , NAI_Hull::Height(HULL_TITAN) , NAI_Hull::Height(HULL_TITAN) * NAI_Hull::Scale(HULL_TITAN) , 64 }, - { g_navMeshNames[NAVMESH_EXTRA_LARGE], NAI_Hull::Width(HULL_GOLIATH) * NAI_Hull::Scale(HULL_GOLIATH), NAI_Hull::Height(HULL_GOLIATH), NAI_Hull::Height(HULL_GOLIATH) * NAI_Hull::Scale(HULL_GOLIATH), 64 }, + { g_navMeshNames[NAVMESH_SMALL] , NAI_Hull::Width(HULL_HUMAN) * NAI_Hull::Scale(HULL_HUMAN) , NAI_Hull::Height(HULL_HUMAN) , NAI_Hull::Height(HULL_HUMAN) * NAI_Hull::Scale(HULL_HUMAN) , 32, 8 }, + { g_navMeshNames[NAVMESH_MED_SHORT] , NAI_Hull::Width(HULL_PROWLER) * NAI_Hull::Scale(HULL_PROWLER), NAI_Hull::Height(HULL_PROWLER), NAI_Hull::Height(HULL_PROWLER) * NAI_Hull::Scale(HULL_PROWLER), 32, 4 }, + { g_navMeshNames[NAVMESH_MEDIUM] , NAI_Hull::Width(HULL_MEDIUM) * NAI_Hull::Scale(HULL_MEDIUM) , NAI_Hull::Height(HULL_MEDIUM) , NAI_Hull::Height(HULL_MEDIUM) * NAI_Hull::Scale(HULL_MEDIUM) , 32, 4 }, + { g_navMeshNames[NAVMESH_LARGE] , NAI_Hull::Width(HULL_TITAN) * NAI_Hull::Scale(HULL_TITAN) , NAI_Hull::Height(HULL_TITAN) , NAI_Hull::Height(HULL_TITAN) * NAI_Hull::Scale(HULL_TITAN) , 64, 2 }, + { g_navMeshNames[NAVMESH_EXTRA_LARGE], NAI_Hull::Width(HULL_GOLIATH) * NAI_Hull::Scale(HULL_GOLIATH), NAI_Hull::Height(HULL_GOLIATH), NAI_Hull::Height(HULL_GOLIATH) * NAI_Hull::Scale(HULL_GOLIATH), 64, 2 }, }; void Editor::selectNavMeshType(const NavMeshType_e navMeshType) @@ -540,6 +543,7 @@ void Editor::selectNavMeshType(const NavMeshType_e navMeshType) m_agentHeight = h.height; m_navmeshName = h.name; m_tileSize = h.tileSize; + m_polyCellRes = h.cellResolution; m_selectedNavMeshType = navMeshType; } diff --git a/src/naveditor/Editor_TileMesh.cpp b/src/naveditor/Editor_TileMesh.cpp index 9f8968da..ad67353f 100644 --- a/src/naveditor/Editor_TileMesh.cpp +++ b/src/naveditor/Editor_TileMesh.cpp @@ -1011,6 +1011,7 @@ unsigned char* Editor_TileMesh::buildTileMesh(const int tx, const int ty, const params.polyFlags = m_pmesh->flags; params.polyCount = m_pmesh->npolys; params.nvp = m_pmesh->nvp; + params.cellResolution = m_polyCellRes; params.detailMeshes = m_dmesh->meshes; params.detailVerts = m_dmesh->verts; params.detailVertsCount = m_dmesh->nverts; diff --git a/src/naveditor/InputGeom.cpp b/src/naveditor/InputGeom.cpp index 31a582d7..e4109f6e 100644 --- a/src/naveditor/InputGeom.cpp +++ b/src/naveditor/InputGeom.cpp @@ -320,7 +320,7 @@ bool InputGeom::loadGeomSet(rcContext* ctx, const std::string& filepath) { // Settings m_hasBuildSettings = true; - sscanf(row + 1, "%f %f %f %f %f %f %d %d %d %f %d %f %f %d %f %f %f %f %f %f %d", + sscanf(row + 1, "%f %f %f %f %f %f %d %d %d %f %d %d %f %f %d %f %f %f %f %f %f %d", &m_buildSettings.cellSize, &m_buildSettings.cellHeight, &m_buildSettings.agentHeight, @@ -332,6 +332,7 @@ bool InputGeom::loadGeomSet(rcContext* ctx, const std::string& filepath) &m_buildSettings.edgeMaxLen, &m_buildSettings.edgeMaxError, &m_buildSettings.vertsPerPoly, + &m_buildSettings.polyCellRes, &m_buildSettings.detailSampleDist, &m_buildSettings.detailSampleMaxError, &m_buildSettings.partitionType, @@ -397,7 +398,7 @@ bool InputGeom::saveGeomSet(const BuildSettings* settings) if (settings) { fprintf(fp, - "s %f %f %f %f %f %f %d %d %d %f %d %f %f %d %f %f %f %f %f %f %d\n", + "s %f %f %f %f %f %f %d %d %d %f %d %d %f %f %d %f %f %f %f %f %f %d\n", settings->cellSize, settings->cellHeight, settings->agentHeight, @@ -409,6 +410,7 @@ bool InputGeom::saveGeomSet(const BuildSettings* settings) settings->edgeMaxLen, settings->edgeMaxError, settings->vertsPerPoly, + settings->polyCellRes, settings->detailSampleDist, settings->detailSampleMaxError, settings->partitionType, diff --git a/src/naveditor/include/Editor.h b/src/naveditor/include/Editor.h index 1d664e6e..5dfdb987 100644 --- a/src/naveditor/include/Editor.h +++ b/src/naveditor/include/Editor.h @@ -31,6 +31,7 @@ struct hulldef float height; float climbHeight; int tileSize; + int cellResolution; }; extern const hulldef hulls[5]; @@ -131,6 +132,7 @@ protected: int m_edgeMaxLen; float m_edgeMaxError; int m_vertsPerPoly; + int m_polyCellRes; float m_detailSampleDist; float m_detailSampleMaxError; int m_partitionType; diff --git a/src/naveditor/include/InputGeom.h b/src/naveditor/include/InputGeom.h index 355ab2ae..da4700b3 100644 --- a/src/naveditor/include/InputGeom.h +++ b/src/naveditor/include/InputGeom.h @@ -56,6 +56,8 @@ struct BuildSettings // Edge max error in voxels float edgeMaxError; int vertsPerPoly; + // The polygon cell resolution. + int polyCellRes; // Detail sample distance in voxels float detailSampleDist; // Detail sample max error in voxel heights. diff --git a/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h b/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h index 13009fcc..ad326420 100644 --- a/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h +++ b/src/thirdparty/recast/Detour/Include/DetourNavMeshBuilder.h @@ -38,6 +38,7 @@ struct dtNavMeshCreateParams const unsigned char* polyAreas; ///< The user defined area ids assigned to each polygon. [Size: #polyCount] int polyCount; ///< Number of polygons in the mesh. [Limit: >= 1] int nvp; ///< Maximum number of vertices per polygon. [Limit: >= 3] + int cellResolution; ///< The resolution of the diamond cell grid [Limit: >= 1] /// @} /// @name Height Detail Attributes (Optional) diff --git a/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp b/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp index 35eacad0..b9b47679 100644 --- a/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp +++ b/src/thirdparty/recast/Detour/Source/DetourNavMeshBuilder.cpp @@ -508,6 +508,100 @@ bool dtCreateTraversalTableData(dtNavMesh* nav, const dtDisjointSet& disjoint, c return true; } +static const unsigned short DT_MESH_NULL_IDX = 0xffff; +static int countPolyVerts(const unsigned short* p, const int nvp) // todo(amos): deduplicate +{ + for (int i = 0; i < nvp; ++i) + if (p[i] == DT_MESH_NULL_IDX) + return i; + return nvp; +} + +struct CellItem +{ + float pos[3]; + int polyIndex; +}; + +bool createPolyMeshCells(const dtNavMeshCreateParams* params, rdTempVector& cellItems) +{ + const int nvp = params->nvp; + const int resolution = params->cellResolution; + const float stepX = (params->bmax[0]-params->bmin[0]) / resolution; + const float stepY = (params->bmax[1]-params->bmin[1]) / resolution; + + for (int i = 0; i < params->polyCount; ++i) + { + const unsigned short* p = ¶ms->polys[i*2*nvp]; + const int nv = countPolyVerts(p, nvp); + + if (nv < 3) // Don't generate cells for off-mesh connections. + continue; + + const unsigned int vb = params->detailMeshes[i*4+0]; + const unsigned int ndv = params->detailMeshes[i*4+1]; + const unsigned int tb = params->detailMeshes[i*4+2]; + + float polyVerts[DT_VERTS_PER_POLYGON*3]; + + for (int j = 0; j < nv; ++j) + { + const unsigned short* polyVert = ¶ms->verts[p[j]*3]; + float* flPolyVert = &polyVerts[j*3]; + + flPolyVert[0] = params->bmin[0]+polyVert[0]*params->cs; + flPolyVert[1] = params->bmin[1]+polyVert[1]*params->cs; + flPolyVert[2] = params->bmin[2]+polyVert[2]*params->ch; + } + + for (int j = 0; j <= resolution; j++) + { + for (int k = 0; k <= resolution; k++) + { + const float offsetX = (k % 2 == 0) ? 0.0f : stepX / 2.0f; + + float targetCellPos[3]; + targetCellPos[0] = params->bmin[0]+j*stepX+offsetX; + targetCellPos[1] = params->bmin[1]+k*stepY; + targetCellPos[2] = 0; // todo(amos): might need a proper fallback, but so far this never failed. + + if (!rdPointInPolygon(targetCellPos, polyVerts, nv)) + continue; + + for (int l = 0; l < params->detailTriCount; ++l) + { + const unsigned char* t = ¶ms->detailTris[(tb+l)*4]; + float storage[3][3]; + const float* v[3]; + + for (int m = 0; m < 3; ++m) + { + if (t[m] < nv) + { + for (int n = 0; n < 3; ++n) + { + storage[m][n] = params->bmin[n] + params->verts[p[t[m]]*3+n] * (n == 2 ? params->ch : params->cs); + } + v[m] = storage[m]; + } + else + { + v[m] = ¶ms->detailVerts[(vb+t[m])*3]; + } + } + + if (rdClosestHeightPointTriangle(targetCellPos, v[0],v[1],v[2], targetCellPos[2])) + break; + } + + cellItems.push_back({ targetCellPos[0],targetCellPos[1],targetCellPos[2], i }); + } + } + } + + return true; +} + // TODO: Better error handling. /// @par @@ -663,6 +757,11 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, } } +#if DT_NAVMESH_SET_VERSION >= 8 + rdTempVector cellItems; + createPolyMeshCells(params, cellItems); +#endif + // Calculate data size const int headerSize = rdAlign4(sizeof(dtMeshHeader)); const int vertsSize = rdAlign4(sizeof(float)*3*totVertCount); @@ -673,13 +772,21 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, const int detailTrisSize = rdAlign4(sizeof(unsigned char)*4*detailTriCount); const int bvTreeSize = params->buildBvTree ? rdAlign4(sizeof(dtBVNode)*params->polyCount*2) : 0; const int offMeshConsSize = rdAlign4(sizeof(dtOffMeshConnection)*storedOffMeshConCount); +#if DT_NAVMESH_SET_VERSION >= 8 + const int cellsSize = rdAlign4(sizeof(dtCell)*(int)cellItems.size()); +#endif int polyMapCount = 0; // TODO: this data has to be reversed still from the NavMesh! const int polyMapSize = polyMapCount * totPolyCount; - const int dataSize = headerSize + vertsSize + polysSize + linksSize + - detailMeshesSize + detailVertsSize + detailTrisSize + - bvTreeSize + offMeshConsSize + polyMapSize; + const int dataSize = headerSize + vertsSize + polysSize + polyMapSize + linksSize + + detailMeshesSize + detailVertsSize + detailTrisSize + + bvTreeSize + offMeshConsSize +#if DT_NAVMESH_SET_VERSION >= 8 + + cellsSize; +#else + ; +#endif //printf("%i %i %i %i(%i links) %i %i %i %i %i\n", headerSize, vertsSize, polysSize, linksSize, maxLinkCount, detailMeshesSize, detailVertsSize, detailTrisSize, bvTreeSize, offMeshConsSize); //printf("%i\n", dataSize); @@ -705,6 +812,9 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, 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 rdIgnoreUnused(polyMap); //for(int i=0;ilayer = params->tileLayer; header->userId = params->userId; header->polyCount = totPolyCount; + header->polyMapCount = polyMapCount; header->vertCount = totVertCount; header->maxLinkCount = maxLinkCount; rdVcopy(header->bmin, params->bmin); @@ -727,13 +838,14 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, header->detailTriCount = detailTriCount; header->bvQuantFactor = 1.0f / params->cs; header->offMeshBase = params->polyCount; - header->maxCellCount = -1; +#if DT_NAVMESH_SET_VERSION >= 8 + header->maxCellCount = (int)cellItems.size(); +#endif header->walkableHeight = params->walkableHeight; header->walkableRadius = params->walkableRadius; header->walkableClimb = params->walkableClimb; header->offMeshConCount = storedOffMeshConCount; header->bvNodeCount = params->buildBvTree ? params->polyCount*2 : 0; - header->polyMapCount = polyMapCount; const int offMeshVertsBase = params->vertCount; const int offMeshPolyBase = params->polyCount; @@ -762,13 +874,24 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, n++; } } + +#if DT_NAVMESH_SET_VERSION >= 8 + // Polygon cells. + for (int i = 0; i < (int)cellItems.size(); i++) + { + const CellItem& cellItem = cellItems[i]; + dtCell& cell = navCells[i]; + + rdVcopy(cell.pos, cellItem.pos); + cell.polyIndex = cellItem.polyIndex; + } +#endif // Store polygons // Mesh polys const unsigned short* src = params->polys; for (int i = 0; i < params->polyCount; ++i) { - dtPoly* p = &navPolys[i]; p->vertCount = 0; p->flags = params->polyFlags[i]; @@ -806,6 +929,7 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, src += nvp*2; } + // Off-mesh connection vertices. n = 0; for (int i = 0; i < params->offMeshConCount; ++i) @@ -953,6 +1077,9 @@ bool dtNavMeshHeaderSwapEndian(unsigned char* data, const int /*dataSize*/) rdSwapEndian(&header->bvNodeCount); rdSwapEndian(&header->offMeshConCount); rdSwapEndian(&header->offMeshBase); +#if DT_NAVMESH_SET_VERSION >= 8 + rdSwapEndian(&header->maxCellCount); +#endif rdSwapEndian(&header->walkableHeight); rdSwapEndian(&header->walkableRadius); rdSwapEndian(&header->walkableClimb);