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).
This commit is contained in:
Amos 2024-07-24 11:30:00 +02:00
parent 0269882bea
commit ebfc4ec091
7 changed files with 152 additions and 13 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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,

View File

@ -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;

View File

@ -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.

View File

@ -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)

View File

@ -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<CellItem>& 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 = &params->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 = &params->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 = &params->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] = &params->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<CellItem> 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<unsigned char>(d, detailTrisSize);
dtBVNode* navBvtree = rdGetThenAdvanceBufferPointer<dtBVNode>(d, bvTreeSize);
dtOffMeshConnection* offMeshCons = rdGetThenAdvanceBufferPointer<dtOffMeshConnection>(d, offMeshConsSize);
#if DT_NAVMESH_SET_VERSION >= 8
dtCell* navCells = rdGetThenAdvanceBufferPointer<dtCell>(d, cellsSize);
#endif
rdIgnoreUnused(polyMap);
//for(int i=0;i<unkPerPoly*totPolyCount;i++)
@ -718,6 +828,7 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData,
header->layer = 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);