Recast: properly implement navmesh destructor

Reverse engineered the last types that were mostly unused and unneeded by the navmesh. But to properly implement the destructor it had to go in. Cells and hints now get freed as well and we can now rely on our own destructor for the navmesh data allocated by the game.
This commit is contained in:
Kawe Mazidjatari 2024-10-22 11:56:26 +02:00
parent aaddd53cfc
commit ac6da44a18
5 changed files with 74 additions and 29 deletions

View File

@ -44,9 +44,7 @@ void Detour_FreeNavMeshByType(const NavMeshType_e navMeshType)
{
// Frees tiles, polys, tris, anything dynamically
// allocated for this navmesh, and the navmesh itself.
v_Detour_FreeNavMesh(nav);
free(nav);
delete nav;
g_pNavMesh[navMeshType] = nullptr;
}
}

View File

@ -8,7 +8,6 @@
// RUNTIME: DETOUR
//-------------------------------------------------------------------------
inline void(*v_Detour_LevelInit)(void);
inline void(*v_Detour_FreeNavMesh)(dtNavMesh* mesh);
inline bool(*v_Detour_IsGoalPolyReachable)(dtNavMesh* const nav, const dtPolyRef fromPoly, const dtPolyRef goalPoly, const TraverseAnimType_e animType);
inline dtStatus(*dtNavMesh__Init)(dtNavMesh* thisptr, unsigned char* data, int flags);
inline dtStatus(*dtNavMesh__addTile)(dtNavMesh* thisptr, void* unused, unsigned char* data, int dataSize, int flags, dtTileRef lastRef);
@ -35,7 +34,6 @@ class VRecast : public IDetour
virtual void GetAdr(void) const
{
LogFunAdr("Detour_LevelInit", v_Detour_LevelInit);
LogFunAdr("Detour_FreeNavMesh", v_Detour_FreeNavMesh);
LogFunAdr("Detour_IsGoalPolyReachable", v_Detour_IsGoalPolyReachable);
LogFunAdr("dtNavMesh::Init", dtNavMesh__Init);
LogFunAdr("dtNavMesh::addTile", dtNavMesh__addTile);
@ -46,7 +44,6 @@ class VRecast : public IDetour
virtual void GetFun(void) const
{
g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 55 41 54 41 55 41 56 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 45 33 E4").GetPtr(v_Detour_LevelInit);
g_GameDll.FindPatternSIMD("40 53 48 83 EC 30 48 89 6C 24 ?? 48 8B D9").GetPtr(v_Detour_FreeNavMesh);
g_GameDll.FindPatternSIMD("48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 49 63 F1").GetPtr(v_Detour_IsGoalPolyReachable);
g_GameDll.FindPatternSIMD("4C 89 44 24 ?? 53 41 56 48 81 EC ?? ?? ?? ?? 0F 10 11").GetPtr(dtNavMesh__Init);
g_GameDll.FindPatternSIMD("44 89 4C 24 ?? 41 55").GetPtr(dtNavMesh__addTile);

View File

@ -655,7 +655,7 @@ bool Editor_TileMesh::handleBuild()
params.traverseTableSize = 0;
params.traverseTableCount = 0;
#if DT_NAVMESH_SET_VERSION >= 8
params.magicDataCount = 0;
params.hintCount = 0;
#endif
dtStatus status;

View File

@ -347,6 +347,18 @@ struct dtPolyDetail
unsigned char triCount; ///< The number of triangles in the sub-mesh.
};
/// Defines the vertical triangle of a wall hint.
struct dtTriangleSurface
{
float pos[3]; ///< The surface position of the triangle. [(x, y, z)]
float minDist; ///< The minimum distance between the agent's and triangle position before they are considered close enough.
unsigned short vertAIndex; ///< The index of the first vert connecting A->B.
unsigned short vertBIndex; ///< The index of the second vert connecting B->C.
unsigned short vertCIndex; ///< The index of the third vert connecting C->A.
float surfArea; ///< The surface area of the triangle. (Note: this field was unused by the engine and therefore re-purposed).
unsigned char edgeFlags; ///< The edge flags, use #dtGetDetailTriEdgeFlags to retrieve them.
};
/// Get flags for edge in detail triangle.
/// @param triFlags[in] The flags for the triangle (last component of detail vertices above).
/// @param edgeIndex[in] The index of the first vertex of the edge. For instance, if 0.
@ -397,6 +409,18 @@ struct dtCell
#endif
};
/// Defines a hint in the navmesh.
/// @note This is used to define wall surface triangles to help with special movements such as wall running.
/// @see dtOffMeshConnection
struct dtHint
{
float* verts; ///< The triangle vertices. [Size: (x, y, z) * dtHint::vertCount]
dtTriangleSurface* triangle; ///< The triangles. [Size: dtHint::vertCount]
char unk[24]; // Editor only.
int vertCount; ///< The number of vertices in the hint.
int triCount; ///< The number of triangles in the hint.
};
/// Bounding volume node.
/// @note This structure is rarely if ever used by the end user.
/// @see dtMeshTile
@ -547,6 +571,14 @@ struct dtMeshHeader
float bvQuantFactor;
};
/// Navigation mesh tile memory tracker base class.
/// @ingroup detour
class dtMeshTileMemoryTracker
{
public:
virtual void destroy(const int flags) = 0;
};
/// Defines a navigation mesh tile.
/// @ingroup detour
struct dtMeshTile
@ -588,7 +620,7 @@ public:
int dataSize; ///< Size of the tile data.
int flags; ///< Tile flags. (See: #dtTileFlags)
dtMeshTile* next; ///< The next free tile, or the next tile in the spatial grid.
void* deleteCallback; ///< Custom destruction callback, called after free. (See [r5apex_ds + F437D9] for usage.)
dtMeshTileMemoryTracker* tracker; ///< The tiles memory tracker, called after destruction. (See [r5apex_ds + F437D9] for usage.)
private:
dtMeshTile(const dtMeshTile&);
dtMeshTile& operator=(const dtMeshTile&);
@ -655,18 +687,14 @@ struct dtNavMeshParams
{
float orig[3]; ///< The world space origin of the navigation mesh's tile space. [(x, y, z)]
float tileWidth; ///< The width of each tile. (Along the x-axis.)
float tileHeight; ///< The height of each tile. (Along the z-axis.)
float tileHeight; ///< The height of each tile. (Along the y-axis.)
int maxTiles; ///< The maximum number of tiles the navigation mesh can contain. This and maxPolys are used to calculate how many bits are needed to identify tiles and polygons uniquely.
int maxPolys; ///< The maximum number of polygons each tile can contain. This and maxTiles are used to calculate how many bits are needed to identify tiles and polygons uniquely.
int polyGroupCount; ///< The total number of disjoint polygon groups.
int traverseTableSize; ///< The total size of the static traverse table. This is computed using calcTraverseTableSize(polyGroupcount).
int traverseTableCount; ///< The total number of traverse tables in this navmesh. Each TraverseAnimType uses its own table as their available jump links should match their behavior and abilities.
#if DT_NAVMESH_SET_VERSION >= 7
// NOTE: this seems to be used for some wallrunning code. This allocates a buffer of size 0x30 * magicDataCount,
// then copies in the data 0x30 * magicDataCount at the end of the navmesh file (past the traverse tables).
// See [r5apex_ds + F43600] for buffer allocation and data copy, see note at dtNavMesh::m_someMagicData for usage.
int magicDataCount;
int hintCount; ///< The total number of hints in the navmesh.
#endif
};
@ -770,8 +798,8 @@ public:
/// @return The maximum number of tiles supported by the navigation mesh.
int getMaxTiles() const;
/// The number of tiles added to this mesh by dtNavMesh::addTile
/// @return The number of tiles added to this mesh by dtNavMesh::addTile
/// The number of tiles added to this mesh by #addTile.
/// @return The number of tiles added to this mesh by #addTile.
int getTileCount() const { return m_tileCount; };
/// Gets the tile at the specified index.
@ -843,6 +871,8 @@ public:
/// @param[in] size The size of the traverse table.
void setTraverseTableSize(const int size) { m_params.traverseTableSize = size; }
void freeHints();
/// @}
/// @{
@ -1049,10 +1079,7 @@ private:
dtMeshTile* m_nextFree; ///< Freelist of tiles.
dtMeshTile* m_tiles; ///< List of tiles.
int** m_traverseTables; ///< Array of traverse tables.
///< FIXME: unknown structure pointer, used for some wallrunning code, see [r5apex_ds + F12687] for usage.
///< See note at dtNavMeshParams::magicDataCount for buffer allocation.
void* m_someMagicData;
dtHint* m_hints; ///< List of hints.
int m_unused0;
int m_unused1;

View File

@ -255,7 +255,7 @@ dtNavMesh::dtNavMesh() :
m_nextFree(0),
m_tiles(0),
m_traverseTables(0),
m_someMagicData(0),
m_hints(0),
m_unused0(0),
m_unused1(0)
{
@ -268,15 +268,24 @@ dtNavMesh::dtNavMesh() :
rdVset(m_orig, 0.0f,0.0f,0.0f);
}
dtNavMesh::~dtNavMesh() // TODO: see [r5apex_ds + F43720] to re-implement this correctly
dtNavMesh::~dtNavMesh()
{
for (int i = 0; i < m_maxTiles; ++i)
{
if (m_tiles[i].flags & DT_TILE_FREE_DATA)
dtMeshTile& tile = m_tiles[i];
const int flags = tile.flags;
if (flags & DT_TILE_FREE_DATA)
{
rdFree(m_tiles[i].data);
m_tiles[i].data = 0;
m_tiles[i].dataSize = 0;
if (flags & DT_CELL_FREE_DATA)
rdFree(tile.cells);
rdFree(tile.data);
tile.data = 0;
tile.dataSize = 0;
if (tile.tracker)
tile.tracker->destroy(1);
}
}
@ -284,8 +293,9 @@ dtNavMesh::~dtNavMesh() // TODO: see [r5apex_ds + F43720] to re-implement this c
rdFree(m_tiles);
freeTraverseTables();
freeHints();
}
dtStatus dtNavMesh::init(const dtNavMeshParams* params)
{
memcpy(&m_params, params, sizeof(dtNavMeshParams));
@ -354,7 +364,7 @@ dtStatus dtNavMesh::init(unsigned char* data, const int dataSize, const int tabl
params.traverseTableSize = 0;
params.traverseTableCount = tableCount;
#if DT_NAVMESH_SET_VERSION >= 7
params.magicDataCount = 0;
params.hintCount = 0;
#endif
dtStatus status = init(&params);
@ -1380,7 +1390,7 @@ dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags,
if (!tile)
return DT_FAILURE | DT_OUT_OF_MEMORY;
tile->deleteCallback = nullptr;
tile->tracker = nullptr;
// Insert tile into the position lut.
if (header->userId != DT_FULL_UNLINKED_TILE_USER_ID)
@ -2083,6 +2093,19 @@ void dtNavMesh::setTraverseTable(const int index, int* const table)
m_traverseTables[index] = table;
}
void dtNavMesh::freeHints()
{
for (int i = 0; i < m_params.hintCount; i++)
{
dtHint& hint = m_hints[i];
rdFree(hint.verts);
rdFree(hint.triangle);
}
rdFree(m_hints);
}
dtStatus dtNavMesh::setPolyFlags(dtPolyRef ref, unsigned short flags)
{
if (!ref) return DT_FAILURE;