Check input and report errors via RecastContext

Recast functions have preconditions for arguments they don't validate. This may
produce garbage data which may lead to crash. Check arguments and log when they
are invalid.

Do not throw exceptions when these function calls fail, capture Recast reported
errors via RecastContext inherited from rcContext and log them.
7098-improve-post-process-behavior-with-transparent-objects^2
elsid 2 years ago
parent 0a32b5750b
commit 15e8f0b53c
No known key found for this signature in database
GPG Key ID: 4DE04C198CBA7625

@ -1203,4 +1203,29 @@ namespace
EXPECT_EQ(mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion(), version);
}
TEST_F(DetourNavigatorNavigatorTest, add_agent_with_zero_coordinate_should_not_have_nav_mesh)
{
constexpr std::array<float, 5 * 5> heightfieldData{ {
0, 0, 0, 0, 0, // row 0
0, -25, -25, -25, -25, // row 1
0, -25, -100, -100, -100, // row 2
0, -25, -100, -100, -100, // row 3
0, -25, -100, -100, -100, // row 4
} };
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1);
const AgentBounds agentBounds{ CollisionShapeType::RotatingBox, { 0, 1, 1 } };
mNavigator->addAgent(agentBounds);
auto updateGuard = mNavigator->makeUpdateGuard();
mNavigator->addHeightfield(mCellPosition, cellSize, surface, updateGuard.get());
mNavigator->update(mPlayerPosition, updateGuard.get());
updateGuard.reset();
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
EXPECT_EQ(
findPath(*mNavigator, agentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::StartPolygonNotFound);
}
}

@ -315,6 +315,7 @@ add_component_dir(detournavigator
collisionshapetype
stats
commulativeaabb
recastcontext
)
add_component_dir(loadinglistener

@ -86,7 +86,8 @@ namespace DetourNavigator
std::ostream& operator<<(std::ostream& s, const AgentBounds& v)
{
return s << "AgentBounds {" << v.mShapeType << ", " << v.mHalfExtents << "}";
return s << "AgentBounds {" << v.mShapeType << ", {" << v.mHalfExtents.x() << ", " << v.mHalfExtents.y() << ", "
<< v.mHalfExtents.z() << "}}";
}
namespace

@ -7,6 +7,7 @@
#include "navmeshtilescache.hpp"
#include "offmeshconnection.hpp"
#include "preparednavmeshdata.hpp"
#include "recastcontext.hpp"
#include "recastmesh.hpp"
#include "recastmeshbuilder.hpp"
#include "recastparams.hpp"
@ -14,6 +15,8 @@
#include "settingsutils.hpp"
#include "sharednavmesh.hpp"
#include "components/debug/debuglog.hpp"
#include <DetourNavMesh.h>
#include <DetourNavMeshBuilder.h>
#include <Recast.h>
@ -138,8 +141,8 @@ namespace DetourNavigator
return result;
}
void initHeightfield(rcContext& context, const TilePosition& tilePosition, float minZ, float maxZ,
const RecastSettings& settings, rcHeightfield& solid)
[[nodiscard]] bool initHeightfield(RecastContext& context, const TilePosition& tilePosition, float minZ,
float maxZ, const RecastSettings& settings, rcHeightfield& solid)
{
const int size = settings.mTileSize + settings.mBorderSize * 2;
const int width = size;
@ -150,14 +153,37 @@ namespace DetourNavigator
const osg::Vec3f bmin(shift.x() - halfBoundsSize, minZ, shift.y() - halfBoundsSize);
const osg::Vec3f bmax(shift.x() + halfBoundsSize, maxZ, shift.y() + halfBoundsSize);
const auto result = rcCreateHeightfield(
&context, solid, width, height, bmin.ptr(), bmax.ptr(), settings.mCellSize, settings.mCellHeight);
if (width < 0)
{
Log(Debug::Warning) << context.getPrefix() << "Invalid width to init heightfield: " << width;
return false;
}
if (height < 0)
{
Log(Debug::Warning) << context.getPrefix() << "Invalid height to init heightfield: " << height;
return false;
}
if (settings.mCellHeight <= 0)
{
Log(Debug::Warning) << context.getPrefix()
<< "Invalid cell height to init heightfield: " << settings.mCellHeight;
return false;
}
if (settings.mCellSize <= 0)
{
Log(Debug::Warning) << context.getPrefix()
<< "Invalid cell size to init heightfield: " << settings.mCellSize;
return false;
}
if (!result)
throw NavigatorException("Failed to create heightfield for navmesh");
return rcCreateHeightfield(
&context, solid, width, height, bmin.ptr(), bmax.ptr(), settings.mCellSize, settings.mCellHeight);
}
bool rasterizeTriangles(rcContext& context, const Mesh& mesh, const RecastSettings& settings,
[[nodiscard]] bool rasterizeTriangles(RecastContext& context, const Mesh& mesh, const RecastSettings& settings,
const RecastParams& params, rcHeightfield& solid)
{
std::vector<unsigned char> areas(mesh.getAreaTypes().begin(), mesh.getAreaTypes().end());
@ -178,7 +204,7 @@ namespace DetourNavigator
mesh.getIndices().data(), areas.data(), static_cast<int>(areas.size()), solid, params.mWalkableClimb);
}
bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, AreaType areaType,
[[nodiscard]] bool rasterizeTriangles(RecastContext& context, const Rectangle& rectangle, AreaType areaType,
const RecastParams& params, rcHeightfield& solid)
{
const std::array vertices{
@ -199,9 +225,9 @@ namespace DetourNavigator
indices.data(), areas.data(), static_cast<int>(areas.size()), solid, params.mWalkableClimb);
}
bool rasterizeTriangles(rcContext& context, float agentHalfExtentsZ, const std::vector<CellWater>& water,
const RecastSettings& settings, const RecastParams& params, const TileBounds& realTileBounds,
rcHeightfield& solid)
[[nodiscard]] bool rasterizeTriangles(RecastContext& context, float agentHalfExtentsZ,
const std::vector<CellWater>& water, const RecastSettings& settings, const RecastParams& params,
const TileBounds& realTileBounds, rcHeightfield& solid)
{
for (const CellWater& cellWater : water)
{
@ -219,7 +245,7 @@ namespace DetourNavigator
return true;
}
bool rasterizeTriangles(rcContext& context, const TileBounds& realTileBounds,
[[nodiscard]] bool rasterizeTriangles(RecastContext& context, const TileBounds& realTileBounds,
const std::vector<FlatHeightfield>& heightfields, const RecastSettings& settings,
const RecastParams& params, rcHeightfield& solid)
{
@ -237,7 +263,7 @@ namespace DetourNavigator
return true;
}
bool rasterizeTriangles(rcContext& context, const std::vector<Heightfield>& heightfields,
[[nodiscard]] bool rasterizeTriangles(RecastContext& context, const std::vector<Heightfield>& heightfields,
const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid)
{
for (const Heightfield& heightfield : heightfields)
@ -249,9 +275,9 @@ namespace DetourNavigator
return true;
}
bool rasterizeTriangles(rcContext& context, const TilePosition& tilePosition, float agentHalfExtentsZ,
const RecastMesh& recastMesh, const RecastSettings& settings, const RecastParams& params,
rcHeightfield& solid)
[[nodiscard]] bool rasterizeTriangles(RecastContext& context, const TilePosition& tilePosition,
float agentHalfExtentsZ, const RecastMesh& recastMesh, const RecastSettings& settings,
const RecastParams& params, rcHeightfield& solid)
{
const TileBounds realTileBounds = makeRealTileBoundsWithBorder(settings, tilePosition);
return rasterizeTriangles(context, recastMesh.getMesh(), settings, params, solid)
@ -262,66 +288,119 @@ namespace DetourNavigator
context, realTileBounds, recastMesh.getFlatHeightfields(), settings, params, solid);
}
void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb,
rcHeightfield& solid, rcCompactHeightfield& compact)
[[nodiscard]] bool buildCompactHeightfield(RecastContext& context, const int walkableHeight,
const int walkableClimb, rcHeightfield& solid, rcCompactHeightfield& compact)
{
const auto result = rcBuildCompactHeightfield(&context, walkableHeight, walkableClimb, solid, compact);
if (walkableHeight < 3)
{
Log(Debug::Warning) << context.getPrefix()
<< "Invalid walkableHeight to build compact heightfield: " << walkableHeight;
return false;
}
if (walkableClimb < 0)
{
Log(Debug::Warning) << context.getPrefix()
<< "Invalid walkableClimb to build compact heightfield: " << walkableClimb;
return false;
}
if (!result)
throw NavigatorException("Failed to build compact heightfield for navmesh");
return rcBuildCompactHeightfield(&context, walkableHeight, walkableClimb, solid, compact);
}
void erodeWalkableArea(rcContext& context, int walkableRadius, rcCompactHeightfield& compact)
[[nodiscard]] bool erodeWalkableArea(RecastContext& context, int walkableRadius, rcCompactHeightfield& compact)
{
const auto result = rcErodeWalkableArea(&context, walkableRadius, compact);
if (walkableRadius <= 0 || 255 <= walkableRadius)
{
Log(Debug::Warning) << context.getPrefix()
<< "Invalid walkableRadius to erode walkable area: " << walkableRadius;
return false;
}
if (!result)
throw NavigatorException("Failed to erode walkable area for navmesh");
return rcErodeWalkableArea(&context, walkableRadius, compact);
}
void buildDistanceField(rcContext& context, rcCompactHeightfield& compact)
[[nodiscard]] bool buildDistanceField(RecastContext& context, rcCompactHeightfield& compact)
{
const auto result = rcBuildDistanceField(&context, compact);
if (!result)
throw NavigatorException("Failed to build distance field for navmesh");
return rcBuildDistanceField(&context, compact);
}
void buildRegions(rcContext& context, rcCompactHeightfield& compact, const int borderSize,
[[nodiscard]] bool buildRegions(RecastContext& context, rcCompactHeightfield& compact, const int borderSize,
const int minRegionArea, const int mergeRegionArea)
{
const auto result = rcBuildRegions(&context, compact, borderSize, minRegionArea, mergeRegionArea);
if (borderSize < 0)
{
Log(Debug::Warning) << context.getPrefix() << "Invalid borderSize to build regions: " << borderSize;
return false;
}
if (minRegionArea < 0)
{
Log(Debug::Warning) << context.getPrefix()
<< "Invalid minRegionArea to build regions: " << minRegionArea;
return false;
}
if (!result)
throw NavigatorException("Failed to build distance field for navmesh");
if (mergeRegionArea < 0)
{
Log(Debug::Warning) << context.getPrefix()
<< "Invalid mergeRegionArea to build regions: " << mergeRegionArea;
return false;
}
return rcBuildRegions(&context, compact, borderSize, minRegionArea, mergeRegionArea);
}
void buildContours(rcContext& context, rcCompactHeightfield& compact, const float maxError,
[[nodiscard]] bool buildContours(RecastContext& context, rcCompactHeightfield& compact, const float maxError,
const int maxEdgeLen, rcContourSet& contourSet, const int buildFlags = RC_CONTOUR_TESS_WALL_EDGES)
{
const auto result = rcBuildContours(&context, compact, maxError, maxEdgeLen, contourSet, buildFlags);
if (maxError < 0)
{
Log(Debug::Warning) << context.getPrefix() << "Invalid maxError to build contours: " << maxError;
return false;
}
if (maxEdgeLen < 0)
{
Log(Debug::Warning) << context.getPrefix() << "Invalid maxEdgeLen to build contours: " << maxEdgeLen;
return false;
}
if (!result)
throw NavigatorException("Failed to build contours for navmesh");
return rcBuildContours(&context, compact, maxError, maxEdgeLen, contourSet, buildFlags);
}
void buildPolyMesh(
rcContext& context, rcContourSet& contourSet, const int maxVertsPerPoly, rcPolyMesh& polyMesh)
[[nodiscard]] bool buildPolyMesh(
RecastContext& context, rcContourSet& contourSet, const int maxVertsPerPoly, rcPolyMesh& polyMesh)
{
const auto result = rcBuildPolyMesh(&context, contourSet, maxVertsPerPoly, polyMesh);
if (maxVertsPerPoly < 3)
{
Log(Debug::Warning) << context.getPrefix()
<< "Invalid maxVertsPerPoly to build poly mesh: " << maxVertsPerPoly;
return false;
}
if (!result)
throw NavigatorException("Failed to build poly mesh for navmesh");
return rcBuildPolyMesh(&context, contourSet, maxVertsPerPoly, polyMesh);
}
void buildPolyMeshDetail(rcContext& context, const rcPolyMesh& polyMesh, const rcCompactHeightfield& compact,
const float sampleDist, const float sampleMaxError, rcPolyMeshDetail& polyMeshDetail)
[[nodiscard]] bool buildPolyMeshDetail(RecastContext& context, const rcPolyMesh& polyMesh,
const rcCompactHeightfield& compact, const float sampleDist, const float sampleMaxError,
rcPolyMeshDetail& polyMeshDetail)
{
const auto result
= rcBuildPolyMeshDetail(&context, polyMesh, compact, sampleDist, sampleMaxError, polyMeshDetail);
if (sampleDist < 0)
{
Log(Debug::Warning) << context.getPrefix()
<< "Invalid sampleDist to build poly mesh detail: " << sampleDist;
return false;
}
if (!result)
throw NavigatorException("Failed to build detail poly mesh for navmesh");
if (sampleMaxError < 0)
{
Log(Debug::Warning) << context.getPrefix()
<< "Invalid sampleMaxError to build poly mesh detail: " << sampleMaxError;
return false;
}
return rcBuildPolyMeshDetail(&context, polyMesh, compact, sampleDist, sampleMaxError, polyMeshDetail);
}
void setPolyMeshFlags(rcPolyMesh& polyMesh)
@ -330,25 +409,36 @@ namespace DetourNavigator
polyMesh.flags[i] = getFlag(static_cast<AreaType>(polyMesh.areas[i]));
}
bool fillPolyMesh(rcContext& context, const RecastSettings& settings, const RecastParams& params,
rcHeightfield& solid, rcPolyMesh& polyMesh, rcPolyMeshDetail& polyMeshDetail)
[[nodiscard]] bool fillPolyMesh(RecastContext& context, const RecastSettings& settings,
const RecastParams& params, rcHeightfield& solid, rcPolyMesh& polyMesh, rcPolyMeshDetail& polyMeshDetail)
{
rcCompactHeightfield compact;
buildCompactHeightfield(context, params.mWalkableHeight, params.mWalkableClimb, solid, compact);
if (!buildCompactHeightfield(context, params.mWalkableHeight, params.mWalkableClimb, solid, compact))
return false;
erodeWalkableArea(context, params.mWalkableRadius, compact);
buildDistanceField(context, compact);
buildRegions(context, compact, settings.mBorderSize, settings.mRegionMinArea, settings.mRegionMergeArea);
if (!erodeWalkableArea(context, params.mWalkableRadius, compact))
return false;
if (!buildDistanceField(context, compact))
return false;
if (!buildRegions(
context, compact, settings.mBorderSize, settings.mRegionMinArea, settings.mRegionMergeArea))
return false;
rcContourSet contourSet;
buildContours(context, compact, settings.mMaxSimplificationError, params.mMaxEdgeLen, contourSet);
if (!buildContours(context, compact, settings.mMaxSimplificationError, params.mMaxEdgeLen, contourSet))
return false;
if (contourSet.nconts == 0)
return false;
buildPolyMesh(context, contourSet, settings.mMaxVertsPerPoly, polyMesh);
if (!buildPolyMesh(context, contourSet, settings.mMaxVertsPerPoly, polyMesh))
return false;
buildPolyMeshDetail(context, polyMesh, compact, params.mSampleDist, params.mSampleMaxError, polyMeshDetail);
if (!buildPolyMeshDetail(
context, polyMesh, compact, params.mSampleDist, params.mSampleMaxError, polyMeshDetail))
return false;
setPolyMeshFlags(polyMesh);
@ -410,13 +500,14 @@ namespace DetourNavigator
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh,
const TilePosition& tilePosition, const AgentBounds& agentBounds, const RecastSettings& settings)
{
rcContext context;
RecastContext context(tilePosition, agentBounds);
const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentBounds.mHalfExtents.z(), settings);
rcHeightfield solid;
initHeightfield(context, tilePosition, toNavMeshCoordinates(settings, minZ),
toNavMeshCoordinates(settings, maxZ), settings, solid);
if (!initHeightfield(context, tilePosition, toNavMeshCoordinates(settings, minZ),
toNavMeshCoordinates(settings, maxZ), settings, solid))
return nullptr;
const RecastParams params = makeRecastParams(settings, agentBounds);

@ -0,0 +1,45 @@
#include "recastcontext.hpp"
#include "debug.hpp"
#include "components/debug/debuglog.hpp"
#include <sstream>
namespace DetourNavigator
{
namespace
{
Debug::Level getLogLevel(rcLogCategory category)
{
switch (category)
{
case RC_LOG_PROGRESS:
return Debug::Verbose;
case RC_LOG_WARNING:
return Debug::Warning;
case RC_LOG_ERROR:
return Debug::Error;
}
return Debug::Debug;
}
std::string formatPrefix(const TilePosition& tilePosition, const AgentBounds& agentBounds)
{
std::ostringstream stream;
stream << "Tile position: " << tilePosition.x() << ", " << tilePosition.y()
<< "; agent bounds: " << agentBounds << "; ";
return stream.str();
}
}
RecastContext::RecastContext(const TilePosition& tilePosition, const AgentBounds& agentBounds)
: mPrefix(formatPrefix(tilePosition, agentBounds))
{
}
void RecastContext::doLog(const rcLogCategory category, const char* msg, const int len)
{
if (len > 0)
Log(getLogLevel(category)) << mPrefix << std::string_view(msg, static_cast<std::size_t>(len));
}
}

@ -0,0 +1,28 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTCONTEXT_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTCONTEXT_H
#include "tileposition.hpp"
#include <string>
#include <Recast.h>
namespace DetourNavigator
{
struct AgentBounds;
class RecastContext final : public rcContext
{
public:
explicit RecastContext(const TilePosition& tilePosition, const AgentBounds& agentBounds);
const std::string& getPrefix() const { return mPrefix; }
private:
std::string mPrefix;
void doLog(rcLogCategory category, const char* msg, int len) override;
};
}
#endif
Loading…
Cancel
Save