/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include "RecastNavigation.h" #include "Recast.h" #include "DetourAlloc.h" #include "DetourCommon.h" #include "DetourNavMesh.h" #include "DetourNavMeshBuilder.h" #include "DetourNavMeshQuery.h" #include "DetourCrowd.h" static const int RECAST_NAVMESH_VERSION = 1; static const int RECAST_NAVMESH_MAGIC = ( 'R' << 24 ) | ( 'N' << 16 ) | ( 'A' << 8 ) | 'V'; static const unsigned short RECAST_POLYFLAGS_WALK = 0x01; static const int MAX_RECAST_PATH_POLYS = 256; static const float RECAST_AREA_BLOCKER_MATCH_EXPAND = 128.0f; static const float RECAST_BUILD_CELL_SIZE = 4.0f; static const float RECAST_BUILD_CELL_HEIGHT = 2.0f; struct recastBoundsBlocker_t { idBounds bounds; int areaContents; bool active; }; class idRecastNavMeshLocal { public: idRecastNavMeshLocal( void ); ~idRecastNavMeshLocal( void ); void Shutdown( void ); bool InitForAAS( const idStr &aasName, const idAASSettings &settings, unsigned int mapFileCRC ); bool Load( const idStr &fileName, unsigned int mapFileCRC ); bool BuildAndSave( const idMapFile *mapFile, const idStr &fileName, unsigned int mapFileCRC, const idAASSettings &settings ); void DebugDraw( idRenderWorld *renderWorld, int drawMode, int lifetime ) const; bool IsLoaded( void ) const { return navMesh != NULL && navQuery != NULL; } const idStr & GetName( void ) const { return name; } int NumPolys( void ) const { return polyRefs.Num(); } const idAASSettings * GetSettings( void ) const { return IsLoaded() ? &settings : NULL; } void Stats( void ) const; int PointAreaNum( const idVec3 &origin ) const; int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const; int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const; void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const; idVec3 AreaCenter( int areaNum ) const; int AreaFlags( int areaNum ) const; int AreaTravelFlags( int areaNum ) const; bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ); recastObstacleHandle_t AddObstacle( const idBounds &bounds ); void RemoveObstacle( const recastObstacleHandle_t handle ); void RemoveAllObstacles( void ); int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const; bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, recastPath_t &route ) const; bool PathToGoal( recastPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; bool PathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; bool PathAreaList( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, int *areas, int &numAreas, int maxAreas ) const; static bool BuildMapFile( const idStr &mapName, const idStr &aasType ); static idStr NavFileNameForAASName( const idStr &aasName ); private: static void ToDetour( const idVec3 &in, float out[3] ); static idVec3 FromDetour( const float in[3] ); bool FindNearestPoly( const idVec3 &origin, const idBounds &searchBounds, dtPolyRef &ref, idVec3 *nearest ) const; int AreaForRef( dtPolyRef ref ) const; dtPolyRef RefForArea( int areaNum ) const; bool FindPathRefs( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, dtPolyRef *pathRefs, int &numPathRefs, const int maxPathRefs ) const; bool LineHitsBlocker( const idVec3 &start, const idVec3 &end ) const; bool PointInsideBlocker( const idVec3 &origin ) const; private: idStr name; unsigned int crc; idAASSettings settings; dtNavMesh * navMesh; dtNavMeshQuery * navQuery; dtCrowd * crowd; mutable idList polyRefs; idList obstacles; idList areaBlockers; }; class idDoomRecastContext : public rcContext { public: idDoomRecastContext( void ) : rcContext( true ) {} protected: virtual void doLog( const rcLogCategory category, const char *msg, const int len ) { idStr text( msg ); if ( len >= 0 && len < text.Length() ) { text.CapLength( len ); } if ( category == RC_LOG_ERROR ) { common->Warning( "Recast: %s", text.c_str() ); } else if ( category == RC_LOG_WARNING ) { common->DWarning( "Recast: %s", text.c_str() ); } else { common->Printf( "Recast: %s\n", text.c_str() ); } } }; struct recastBuildGeometry_t { idList verts; idList tris; idBounds bounds; void Clear( void ) { verts.Clear(); tris.Clear(); bounds.Clear(); } }; static bool RecastMaterialIsSolid( const char *materialName ) { const idMaterial *mat = declManager->FindMaterial( materialName ); if ( !mat ) { return false; } const int contents = mat->GetContentFlags(); return ( contents & ( CONTENTS_SOLID | CONTENTS_AAS_SOLID | CONTENTS_MONSTERCLIP ) ) != 0; } static bool RecastEntityIsBakeGeometry( const idMapEntity *mapEnt, int entityNum ) { const char *classname = mapEnt->epairs.GetString( "classname" ); if ( entityNum == 0 ) { return true; } if ( idStr::Icmp( classname, "func_static" ) == 0 || idStr::Icmp( classname, "func_clipmodel" ) == 0 ) { return true; } return false; } static void RecastAppendVertex( recastBuildGeometry_t &geom, const idVec3 &v ) { geom.verts.Append( v.x ); geom.verts.Append( v.z ); geom.verts.Append( v.y ); geom.bounds.AddPoint( v ); } static void RecastAppendTriangle( recastBuildGeometry_t &geom, const idVec3 &a, const idVec3 &b, const idVec3 &c ) { if ( idWinding::TriangleArea( a, b, c ) < 0.1f ) { return; } const int base = geom.verts.Num() / 3; RecastAppendVertex( geom, a ); RecastAppendVertex( geom, b ); RecastAppendVertex( geom, c ); geom.tris.Append( base + 0 ); geom.tris.Append( base + 1 ); geom.tris.Append( base + 2 ); } static void RecastAddWinding( recastBuildGeometry_t &geom, const idWinding &winding, const idVec3 &origin, const idMat3 &axis ) { const int numPoints = winding.GetNumPoints(); if ( numPoints < 3 ) { return; } const idVec3 v0 = winding[0].ToVec3() * axis + origin; for ( int i = 1; i < numPoints - 1; i++ ) { const idVec3 v1 = winding[i].ToVec3() * axis + origin; const idVec3 v2 = winding[i + 1].ToVec3() * axis + origin; RecastAppendTriangle( geom, v0, v1, v2 ); } } static void RecastAddBrushGeometry( recastBuildGeometry_t &geom, const idMapBrush *brush, const idVec3 &origin, const idMat3 &axis ) { for ( int i = 0; i < brush->GetNumSides(); i++ ) { const idMapBrushSide *side = brush->GetSide( i ); if ( !RecastMaterialIsSolid( side->GetMaterial() ) ) { continue; } idFixedWinding winding( side->GetPlane() ); bool clipped = true; for ( int j = 0; j < brush->GetNumSides(); j++ ) { if ( j == i ) { continue; } if ( !winding.ClipInPlace( -brush->GetSide( j )->GetPlane(), ON_EPSILON, true ) ) { clipped = false; break; } } if ( clipped && !winding.IsTiny() && !winding.IsHuge() ) { RecastAddWinding( geom, winding, origin, axis ); } } } static void RecastAddPatchGeometry( recastBuildGeometry_t &geom, const idMapPatch *patch, const idVec3 &origin, const idMat3 &axis, const idAASSettings &settings ) { if ( !settings.usePatches || !RecastMaterialIsSolid( patch->GetMaterial() ) ) { return; } idSurface_Patch mesh( *patch ); if ( patch->GetExplicitlySubdivided() ) { mesh.SubdivideExplicit( patch->GetHorzSubdivisions(), patch->GetVertSubdivisions(), false, true ); } else { mesh.Subdivide( DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_LENGTH_CD, false ); } const idDrawVert *verts = mesh.GetVertices(); const int *indexes = mesh.GetIndexes(); for ( int i = 0; i + 2 < mesh.GetNumIndexes(); i += 3 ) { RecastAppendTriangle( geom, verts[indexes[i + 0]].xyz * axis + origin, verts[indexes[i + 1]].xyz * axis + origin, verts[indexes[i + 2]].xyz * axis + origin ); } } static void RecastAddMapEntityGeometry( recastBuildGeometry_t &geom, const idMapEntity *mapEnt, const idAASSettings &settings ) { idVec3 origin; idMat3 axis; mapEnt->epairs.GetVector( "origin", "0 0 0", origin ); if ( !mapEnt->epairs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) { const float angle = mapEnt->epairs.GetFloat( "angle" ); if ( angle != 0.0f ) { axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); } else { axis.Identity(); } } for ( int i = 0; i < mapEnt->GetNumPrimitives(); i++ ) { idMapPrimitive *prim = mapEnt->GetPrimitive( i ); if ( prim->GetType() == idMapPrimitive::TYPE_BRUSH ) { RecastAddBrushGeometry( geom, static_cast( prim ), origin, axis ); } else if ( prim->GetType() == idMapPrimitive::TYPE_PATCH ) { RecastAddPatchGeometry( geom, static_cast( prim ), origin, axis, settings ); } } } static bool RecastCollectMapGeometry( recastBuildGeometry_t &geom, const idMapFile *mapFile, const idAASSettings &settings ) { geom.Clear(); int acceptedEntities = 0; int skippedEntities = 0; int skippedDynamicEntities = 0; int brushPrimitives = 0; int patchPrimitives = 0; common->Printf( "Recast: scanning %d map entities for build geometry\n", mapFile->GetNumEntities() ); for ( int i = 0; i < mapFile->GetNumEntities(); i++ ) { const idMapEntity *mapEnt = mapFile->GetEntity( i ); if ( !RecastEntityIsBakeGeometry( mapEnt, i ) ) { const char *classname = mapEnt->epairs.GetString( "classname" ); if ( idStr::Icmp( classname, "func_aas_obstacle" ) == 0 || idStr::Icmp( classname, "func_aas_portal" ) == 0 || idStr::Icmp( classname, "func_door" ) == 0 || idStr::Icmp( classname, "func_mover" ) == 0 ) { skippedDynamicEntities++; } skippedEntities++; continue; } acceptedEntities++; for ( int j = 0; j < mapEnt->GetNumPrimitives(); j++ ) { idMapPrimitive *prim = mapEnt->GetPrimitive( j ); if ( prim->GetType() == idMapPrimitive::TYPE_BRUSH ) { brushPrimitives++; } else if ( prim->GetType() == idMapPrimitive::TYPE_PATCH ) { patchPrimitives++; } } RecastAddMapEntityGeometry( geom, mapEnt, settings ); } common->Printf( "Recast: accepted %d static entities, skipped %d entities (%d dynamic AAS/door/mover), saw %d brushes and %d patches\n", acceptedEntities, skippedEntities, skippedDynamicEntities, brushPrimitives, patchPrimitives ); common->Printf( "Recast: collected %d verts and %d tris, bounds mins (%g %g %g), maxs (%g %g %g)\n", geom.verts.Num() / 3, geom.tris.Num() / 3, geom.bounds[0].x, geom.bounds[0].y, geom.bounds[0].z, geom.bounds[1].x, geom.bounds[1].y, geom.bounds[1].z ); return geom.verts.Num() >= 9 && geom.tris.Num() >= 3; } idRecastNavMeshLocal::idRecastNavMeshLocal( void ) { crc = 0; navMesh = NULL; navQuery = NULL; crowd = NULL; } idRecastNavMeshLocal::~idRecastNavMeshLocal( void ) { Shutdown(); } void idRecastNavMeshLocal::Shutdown( void ) { if ( crowd ) { dtFreeCrowd( crowd ); crowd = NULL; } if ( navQuery ) { dtFreeNavMeshQuery( navQuery ); navQuery = NULL; } if ( navMesh ) { dtFreeNavMesh( navMesh ); navMesh = NULL; } name.Clear(); crc = 0; polyRefs.Clear(); obstacles.Clear(); areaBlockers.Clear(); } bool idRecastNavMeshLocal::InitForAAS( const idStr &aasName, const idAASSettings &aasSettings, unsigned int mapFileCRC ) { settings = aasSettings; crc = mapFileCRC; name = aasName; return true; } idStr idRecastNavMeshLocal::NavFileNameForAASName( const idStr &aasName ) { idStr navName = aasName; navName += ".navmesh"; return navName; } void idRecastNavMeshLocal::DebugDraw( idRenderWorld *renderWorld, int drawMode, int lifetime ) const { if ( !renderWorld || !navMesh || drawMode <= 0 ) { return; } const idVec3 lift( 0.0f, 0.0f, 2.0f ); for ( int tileNum = 0; tileNum < navMesh->getMaxTiles(); tileNum++ ) { const dtMeshTile *tile = navMesh->getTile( tileNum ); if ( !tile || !tile->header ) { continue; } for ( int polyNum = 0; polyNum < tile->header->polyCount; polyNum++ ) { const dtPoly *poly = &tile->polys[polyNum]; if ( poly->getType() != DT_POLYTYPE_GROUND || poly->vertCount < 3 ) { continue; } for ( int edgeNum = 0; edgeNum < poly->vertCount; edgeNum++ ) { const float *start = &tile->verts[poly->verts[edgeNum] * 3]; const float *end = &tile->verts[poly->verts[( edgeNum + 1 ) % poly->vertCount] * 3]; renderWorld->DebugLine( colorCyan, FromDetour( start ) + lift, FromDetour( end ) + lift, lifetime, false ); } } } if ( drawMode < 2 ) { return; } for ( int i = 0; i < obstacles.Num(); i++ ) { if ( obstacles[i].active ) { renderWorld->DebugBounds( colorOrange, obstacles[i].bounds, vec3_origin, lifetime ); } } for ( int i = 0; i < areaBlockers.Num(); i++ ) { if ( areaBlockers[i].active ) { renderWorld->DebugBounds( colorRed, areaBlockers[i].bounds, vec3_origin, lifetime ); } } } bool idRecastNavMeshLocal::Load( const idStr &fileName, unsigned int mapFileCRC ) { Shutdown(); idFile *file = fileSystem->OpenFileRead( fileName ); if ( !file ) { return false; } int magic, version, dataSize; unsigned int fileCRC; file->ReadInt( magic ); file->ReadInt( version ); file->ReadUnsignedInt( fileCRC ); file->ReadInt( dataSize ); if ( magic != RECAST_NAVMESH_MAGIC || version != RECAST_NAVMESH_VERSION || fileCRC != mapFileCRC || dataSize <= 0 ) { fileSystem->CloseFile( file ); return false; } unsigned char *navData = (unsigned char *)dtAlloc( dataSize, DT_ALLOC_PERM ); if ( !navData ) { fileSystem->CloseFile( file ); return false; } if ( file->Read( navData, dataSize ) != dataSize ) { dtFree( navData ); fileSystem->CloseFile( file ); return false; } fileSystem->CloseFile( file ); navMesh = dtAllocNavMesh(); if ( !navMesh ) { dtFree( navData ); return false; } if ( dtStatusFailed( navMesh->init( navData, dataSize, DT_TILE_FREE_DATA ) ) ) { dtFree( navData ); Shutdown(); return false; } navQuery = dtAllocNavMeshQuery(); if ( !navQuery || dtStatusFailed( navQuery->init( navMesh, 4096 ) ) ) { Shutdown(); return false; } crowd = dtAllocCrowd(); if ( crowd ) { const idBounds &bounds = settings.boundingBoxes[0]; const float radius = max( idMath::Fabs( bounds[0].x ), idMath::Fabs( bounds[1].x ) ); crowd->init( 128, radius, navMesh ); } name = fileName; crc = mapFileCRC; return true; } bool idRecastNavMeshLocal::BuildAndSave( const idMapFile *mapFile, const idStr &fileName, unsigned int mapFileCRC, const idAASSettings &aasSettings ) { common->Printf( "Recast: building navmesh for %s -> %s\n", mapFile->GetName(), fileName.c_str() ); common->Printf( "Recast: map geometry CRC %u\n", mapFileCRC ); recastBuildGeometry_t geom; if ( !RecastCollectMapGeometry( geom, mapFile, aasSettings ) ) { common->Warning( "Recast: no solid map geometry found for %s", mapFile->GetName() ); return false; } idDoomRecastContext ctx; rcConfig cfg; memset( &cfg, 0, sizeof( cfg ) ); const idBounds &agentBounds = aasSettings.boundingBoxes[0]; const float agentRadius = max( idMath::Fabs( agentBounds[0].x ), idMath::Fabs( agentBounds[1].x ) ); const float agentHeight = agentBounds[1].z - agentBounds[0].z; common->Printf( "Recast: agent bbox mins (%g %g %g), maxs (%g %g %g)\n", agentBounds[0].x, agentBounds[0].y, agentBounds[0].z, agentBounds[1].x, agentBounds[1].y, agentBounds[1].z ); common->Printf( "Recast: agent radius %g, height %g, max step %g, min floor cos %g\n", agentRadius, agentHeight, aasSettings.maxStepHeight, aasSettings.minFloorCos ); cfg.cs = RECAST_BUILD_CELL_SIZE; cfg.ch = RECAST_BUILD_CELL_HEIGHT; cfg.walkableSlopeAngle = RAD2DEG( idMath::ACos( aasSettings.minFloorCos ) ); cfg.walkableHeight = (int)ceilf( agentHeight / cfg.ch ); cfg.walkableClimb = Max( 1, (int)ceilf( aasSettings.maxStepHeight / cfg.ch ) ); cfg.walkableRadius = (int)ceilf( agentRadius / cfg.cs ); cfg.maxEdgeLen = (int)( 12.0f * 64.0f / cfg.cs ); cfg.maxSimplificationError = 1.3f; cfg.minRegionArea = rcSqr( 4 ); cfg.mergeRegionArea = rcSqr( 12 ); cfg.maxVertsPerPoly = 6; cfg.detailSampleDist = cfg.cs * 6.0f; cfg.detailSampleMaxError = cfg.ch; cfg.bmin[0] = geom.bounds[0].x; cfg.bmin[1] = geom.bounds[0].z; cfg.bmin[2] = geom.bounds[0].y; cfg.bmax[0] = geom.bounds[1].x; cfg.bmax[1] = geom.bounds[1].z; cfg.bmax[2] = geom.bounds[1].y; rcCalcGridSize( cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height ); common->Printf( "Recast: config cellSize %g, cellHeight %g, grid %d x %d\n", cfg.cs, cfg.ch, cfg.width, cfg.height ); common->Printf( "Recast: walkable height %d, climb %d, radius %d, slope %g degrees\n", cfg.walkableHeight, cfg.walkableClimb, cfg.walkableRadius, cfg.walkableSlopeAngle ); common->Printf( "Recast: region min %d, merge %d, max edge len %d, max verts/poly %d\n", cfg.minRegionArea, cfg.mergeRegionArea, cfg.maxEdgeLen, cfg.maxVertsPerPoly ); rcHeightfield *heightfield = rcAllocHeightfield(); rcCompactHeightfield *compactHeightfield = NULL; rcContourSet *contourSet = NULL; rcPolyMesh *polyMesh = NULL; rcPolyMeshDetail *detailMesh = NULL; unsigned char *triAreas = NULL; unsigned char *navData = NULL; int navDataSize = 0; bool ok = false; const float *verts = geom.verts.Ptr(); const int numVerts = geom.verts.Num() / 3; const int *tris = geom.tris.Ptr(); const int numTris = geom.tris.Num() / 3; common->Printf( "Recast: allocating heightfield\n" ); if ( heightfield && rcCreateHeightfield( &ctx, *heightfield, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch ) ) { common->Printf( "Recast: heightfield created\n" ); triAreas = new unsigned char[numTris]; memset( triAreas, 0, numTris * sizeof( unsigned char ) ); common->Printf( "Recast: marking walkable triangles\n" ); rcMarkWalkableTriangles( &ctx, cfg.walkableSlopeAngle, verts, numVerts, tris, numTris, triAreas ); common->Printf( "Recast: rasterizing %d triangles\n", numTris ); if ( rcRasterizeTriangles( &ctx, verts, numVerts, tris, triAreas, numTris, *heightfield, cfg.walkableClimb ) ) { common->Printf( "Recast: filtering low-hanging obstacles, ledges, and low spans\n" ); rcFilterLowHangingWalkableObstacles( &ctx, cfg.walkableClimb, *heightfield ); rcFilterLedgeSpans( &ctx, cfg.walkableHeight, cfg.walkableClimb, *heightfield ); rcFilterWalkableLowHeightSpans( &ctx, cfg.walkableHeight, *heightfield ); common->Printf( "Recast: building compact heightfield\n" ); compactHeightfield = rcAllocCompactHeightfield(); if ( compactHeightfield && rcBuildCompactHeightfield( &ctx, cfg.walkableHeight, cfg.walkableClimb, *heightfield, *compactHeightfield ) && rcErodeWalkableArea( &ctx, cfg.walkableRadius, *compactHeightfield ) && rcBuildDistanceField( &ctx, *compactHeightfield ) && rcBuildRegions( &ctx, *compactHeightfield, 0, cfg.minRegionArea, cfg.mergeRegionArea ) ) { common->Printf( "Recast: compact heightfield built, walkable spans %d\n", compactHeightfield->spanCount ); common->Printf( "Recast: building contours and poly mesh\n" ); contourSet = rcAllocContourSet(); polyMesh = rcAllocPolyMesh(); detailMesh = rcAllocPolyMeshDetail(); if ( contourSet && polyMesh && detailMesh && rcBuildContours( &ctx, *compactHeightfield, cfg.maxSimplificationError, cfg.maxEdgeLen, *contourSet ) && rcBuildPolyMesh( &ctx, *contourSet, cfg.maxVertsPerPoly, *polyMesh ) && rcBuildPolyMeshDetail( &ctx, *polyMesh, *compactHeightfield, cfg.detailSampleDist, cfg.detailSampleMaxError, *detailMesh ) ) { common->Printf( "Recast: contours %d, poly mesh %d verts / %d polys, detail %d verts / %d tris\n", contourSet->nconts, polyMesh->nverts, polyMesh->npolys, detailMesh->nverts, detailMesh->ntris ); for ( int i = 0; i < polyMesh->npolys; i++ ) { if ( polyMesh->areas[i] == RC_WALKABLE_AREA ) { polyMesh->areas[i] = 0; } polyMesh->flags[i] = RECAST_POLYFLAGS_WALK; } dtNavMeshCreateParams params; memset( ¶ms, 0, sizeof( params ) ); params.verts = polyMesh->verts; params.vertCount = polyMesh->nverts; params.polys = polyMesh->polys; params.polyAreas = polyMesh->areas; params.polyFlags = polyMesh->flags; params.polyCount = polyMesh->npolys; params.nvp = polyMesh->nvp; params.detailMeshes = detailMesh->meshes; params.detailVerts = detailMesh->verts; params.detailVertsCount = detailMesh->nverts; params.detailTris = detailMesh->tris; params.detailTriCount = detailMesh->ntris; params.walkableHeight = agentHeight; params.walkableRadius = agentRadius; params.walkableClimb = aasSettings.maxStepHeight; rcVcopy( params.bmin, polyMesh->bmin ); rcVcopy( params.bmax, polyMesh->bmax ); params.cs = cfg.cs; params.ch = cfg.ch; params.buildBvTree = true; common->Printf( "Recast: creating Detour navmesh data\n" ); ok = dtCreateNavMeshData( ¶ms, &navData, &navDataSize ); if ( ok ) { common->Printf( "Recast: Detour navmesh data created, %d bytes\n", navDataSize ); } else { common->Warning( "Recast: dtCreateNavMeshData failed for %s", fileName.c_str() ); } } else { common->Warning( "Recast: failed to build contours/poly mesh/detail mesh for %s", fileName.c_str() ); } } else { common->Warning( "Recast: failed to build compact heightfield/regions for %s", fileName.c_str() ); } } else { common->Warning( "Recast: failed to rasterize triangles for %s", fileName.c_str() ); } } else { common->Warning( "Recast: failed to allocate/create heightfield for %s", fileName.c_str() ); } if ( ok && navData && navDataSize > 0 ) { common->Printf( "Recast: writing navmesh file %s\n", fileName.c_str() ); idFile *out = fileSystem->OpenFileWrite( fileName, "fs_devpath" ); if ( out ) { out->WriteInt( RECAST_NAVMESH_MAGIC ); out->WriteInt( RECAST_NAVMESH_VERSION ); out->WriteUnsignedInt( mapFileCRC ); out->WriteInt( navDataSize ); out->Write( navData, navDataSize ); fileSystem->CloseFile( out ); common->Printf( "Wrote Recast navmesh %s (%d bytes, %d polys)\n", fileName.c_str(), navDataSize, polyMesh ? polyMesh->npolys : 0 ); } else { common->Warning( "Recast: could not open %s for writing", fileName.c_str() ); ok = false; } } if ( navData ) { dtFree( navData ); } delete[] triAreas; if ( detailMesh ) { rcFreePolyMeshDetail( detailMesh ); } if ( polyMesh ) { rcFreePolyMesh( polyMesh ); } if ( contourSet ) { rcFreeContourSet( contourSet ); } if ( compactHeightfield ) { rcFreeCompactHeightfield( compactHeightfield ); } if ( heightfield ) { rcFreeHeightField( heightfield ); } return ok; } bool idRecastNavMeshLocal::BuildMapFile( const idStr &mapName, const idStr &aasType ) { common->Printf( "Recast: requested build map '%s' type '%s'\n", mapName.c_str(), aasType.c_str() ); idStr normalizedMapName = mapName; normalizedMapName.BackSlashesToSlashes(); if ( normalizedMapName.Icmpn( "maps/", 5 ) != 0 ) { normalizedMapName = "maps/" + normalizedMapName; } normalizedMapName.SetFileExtension( ".map" ); common->Printf( "Recast: normalized map path %s\n", normalizedMapName.c_str() ); common->Printf( "Recast: parsing map file\n" ); idMapFile mapFile; if ( !mapFile.Parse( normalizedMapName ) ) { common->Warning( "Recast: could not parse %s", normalizedMapName.c_str() ); return false; } common->Printf( "Recast: parsed %s with %d entities, geometry CRC %u\n", mapFile.GetName(), mapFile.GetNumEntities(), mapFile.GetGeometryCRC() ); common->Printf( "Recast: loading AAS settings entityDef '%s'\n", aasType.c_str() ); const idDeclEntityDef *settingsDecl = static_cast( declManager->FindType( DECL_ENTITYDEF, aasType, false ) ); const idDict *settingsDict = settingsDecl ? &settingsDecl->dict : NULL; if ( !settingsDict ) { common->Warning( "Recast: unknown AAS/nav type '%s'", aasType.c_str() ); return false; } idAASSettings settings; settings.FromDict( aasType, settingsDict ); common->Printf( "Recast: settings loaded: extension %s, %d bbox(es), usePatches %s\n", settings.fileExtension.c_str(), settings.numBoundingBoxes, settings.usePatches ? "true" : "false" ); idStr output = mapFile.GetName(); output.SetFileExtension( aasType ); output = NavFileNameForAASName( output ); common->Printf( "Recast: output navmesh path %s\n", output.c_str() ); idRecastNavMeshLocal builder; return builder.BuildAndSave( &mapFile, output, mapFile.GetGeometryCRC(), settings ); } void idRecastNavMeshLocal::ToDetour( const idVec3 &in, float out[3] ) { out[0] = in.x; out[1] = in.z; out[2] = in.y; } idVec3 idRecastNavMeshLocal::FromDetour( const float in[3] ) { return idVec3( in[0], in[2], in[1] ); } bool idRecastNavMeshLocal::FindNearestPoly( const idVec3 &origin, const idBounds &searchBounds, dtPolyRef &ref, idVec3 *nearest ) const { if ( !IsLoaded() || PointInsideBlocker( origin ) ) { ref = 0; return false; } float center[3], extents[3], nearestPoint[3]; ToDetour( origin, center ); extents[0] = max( idMath::Fabs( searchBounds[0].x ), idMath::Fabs( searchBounds[1].x ) ); extents[1] = max( idMath::Fabs( searchBounds[0].z ), idMath::Fabs( searchBounds[1].z ) ); extents[2] = max( idMath::Fabs( searchBounds[0].y ), idMath::Fabs( searchBounds[1].y ) ); if ( extents[0] <= 0.0f ) { extents[0] = 64.0f; } if ( extents[1] <= 0.0f ) { extents[1] = 96.0f; } if ( extents[2] <= 0.0f ) { extents[2] = 64.0f; } dtQueryFilter filter; filter.setIncludeFlags( RECAST_POLYFLAGS_WALK ); filter.setExcludeFlags( 0 ); if ( dtStatusFailed( navQuery->findNearestPoly( center, extents, &filter, &ref, nearestPoint ) ) || ref == 0 ) { return false; } if ( nearest ) { *nearest = FromDetour( nearestPoint ); } return true; } int idRecastNavMeshLocal::AreaForRef( dtPolyRef ref ) const { if ( ref == 0 ) { return 0; } for ( int i = 0; i < polyRefs.Num(); i++ ) { if ( polyRefs[i] == ref ) { return i + 1; } } return polyRefs.Append( ref ) + 1; } dtPolyRef idRecastNavMeshLocal::RefForArea( int areaNum ) const { if ( areaNum <= 0 || areaNum > polyRefs.Num() ) { return 0; } return polyRefs[areaNum - 1]; } int idRecastNavMeshLocal::PointAreaNum( const idVec3 &origin ) const { dtPolyRef ref; if ( !FindNearestPoly( origin, settings.boundingBoxes[0], ref, NULL ) ) { return 0; } return AreaForRef( ref ); } int idRecastNavMeshLocal::PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const { dtPolyRef ref; if ( !FindNearestPoly( origin, searchBounds, ref, NULL ) ) { return 0; } return AreaForRef( ref ); } int idRecastNavMeshLocal::BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const { const idVec3 center = bounds.GetCenter(); idBounds searchBounds; searchBounds[0] = bounds[0] - center; searchBounds[1] = bounds[1] - center; return PointReachableAreaNum( center, searchBounds, areaFlags ); } void idRecastNavMeshLocal::PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const { const dtPolyRef ref = RefForArea( areaNum ); if ( ref == 0 || !IsLoaded() ) { return; } float pos[3], nearestPoint[3]; ToDetour( origin, pos ); if ( dtStatusSucceed( navQuery->closestPointOnPoly( ref, pos, nearestPoint, NULL ) ) ) { origin = FromDetour( nearestPoint ); } } idVec3 idRecastNavMeshLocal::AreaCenter( int areaNum ) const { const dtPolyRef ref = RefForArea( areaNum ); if ( ref == 0 || !navMesh ) { return vec3_origin; } const dtMeshTile *tile = NULL; const dtPoly *poly = NULL; if ( dtStatusFailed( navMesh->getTileAndPolyByRef( ref, &tile, &poly ) ) || !tile || !poly ) { return vec3_origin; } float center[3] = { 0.0f, 0.0f, 0.0f }; for ( int i = 0; i < poly->vertCount; i++ ) { const float *v = &tile->verts[poly->verts[i] * 3]; center[0] += v[0]; center[1] += v[1]; center[2] += v[2]; } const float inv = 1.0f / poly->vertCount; center[0] *= inv; center[1] *= inv; center[2] *= inv; return FromDetour( center ); } int idRecastNavMeshLocal::AreaFlags( int areaNum ) const { return AREA_REACHABLE_WALK; } int idRecastNavMeshLocal::AreaTravelFlags( int areaNum ) const { return TFL_WALK; } bool idRecastNavMeshLocal::Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const { idVec3 endPos; int endAreaNum; const int startArea = PointAreaNum( start ); const bool valid = PathValid( startArea, start, 0, end, TFL_WALK, endPos, endAreaNum ); trace.fraction = valid ? 1.0f : 0.0f; trace.endpos = endPos; trace.lastAreaNum = endAreaNum; trace.numAreas = endAreaNum > 0 ? 1 : 0; if ( trace.numAreas > 0 && trace.areas && trace.points && trace.maxAreas > 0 ) { trace.areas[0] = endAreaNum; trace.points[0] = endPos; } return !valid; } bool idRecastNavMeshLocal::SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) { if ( disabled ) { for ( int i = 0; i < areaBlockers.Num(); i++ ) { if ( areaBlockers[i].active && ( areaBlockers[i].areaContents & areaContents ) != 0 && areaBlockers[i].bounds.Expand( RECAST_AREA_BLOCKER_MATCH_EXPAND ).IntersectsBounds( bounds ) ) { areaBlockers[i].bounds = bounds; areaBlockers[i].areaContents = areaContents; return true; } } recastBoundsBlocker_t blocker; blocker.bounds = bounds; blocker.areaContents = areaContents; blocker.active = true; for ( int i = 0; i < areaBlockers.Num(); i++ ) { if ( !areaBlockers[i].active ) { areaBlockers[i] = blocker; return true; } } areaBlockers.Append( blocker ); } else { for ( int i = 0; i < areaBlockers.Num(); i++ ) { if ( areaBlockers[i].active && ( areaBlockers[i].areaContents & areaContents ) != 0 && areaBlockers[i].bounds.Expand( RECAST_AREA_BLOCKER_MATCH_EXPAND ).IntersectsBounds( bounds ) ) { areaBlockers[i].active = false; } } } return true; } recastObstacleHandle_t idRecastNavMeshLocal::AddObstacle( const idBounds &bounds ) { recastBoundsBlocker_t blocker; blocker.bounds = bounds; blocker.areaContents = AREACONTENTS_OBSTACLE; blocker.active = true; for ( int i = 0; i < obstacles.Num(); i++ ) { if ( !obstacles[i].active ) { obstacles[i] = blocker; return i; } } return obstacles.Append( blocker ); } void idRecastNavMeshLocal::RemoveObstacle( const recastObstacleHandle_t handle ) { if ( handle >= 0 && handle < obstacles.Num() ) { obstacles[handle].active = false; } } void idRecastNavMeshLocal::RemoveAllObstacles( void ) { obstacles.Clear(); areaBlockers.Clear(); } bool idRecastNavMeshLocal::LineHitsBlocker( const idVec3 &start, const idVec3 &end ) const { for ( int i = 0; i < obstacles.Num(); i++ ) { if ( obstacles[i].active && obstacles[i].bounds.LineIntersection( start, end ) ) { return true; } } for ( int i = 0; i < areaBlockers.Num(); i++ ) { if ( areaBlockers[i].active && areaBlockers[i].bounds.LineIntersection( start, end ) ) { return true; } } return false; } bool idRecastNavMeshLocal::PointInsideBlocker( const idVec3 &origin ) const { for ( int i = 0; i < obstacles.Num(); i++ ) { if ( obstacles[i].active && obstacles[i].bounds.ContainsPoint( origin ) ) { return true; } } for ( int i = 0; i < areaBlockers.Num(); i++ ) { if ( areaBlockers[i].active && areaBlockers[i].bounds.ContainsPoint( origin ) ) { return true; } } return false; } bool idRecastNavMeshLocal::FindPathRefs( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, dtPolyRef *pathRefs, int &numPathRefs, const int maxPathRefs ) const { numPathRefs = 0; if ( !IsLoaded() || PointInsideBlocker( origin ) || PointInsideBlocker( goalOrigin ) ) { return false; } dtPolyRef startRef = RefForArea( areaNum ); dtPolyRef endRef = RefForArea( goalAreaNum ); idVec3 nearest; if ( startRef == 0 ) { FindNearestPoly( origin, settings.boundingBoxes[0], startRef, &nearest ); } if ( endRef == 0 ) { FindNearestPoly( goalOrigin, settings.boundingBoxes[0], endRef, &nearest ); } if ( startRef == 0 || endRef == 0 ) { return false; } float start[3], end[3]; ToDetour( origin, start ); ToDetour( goalOrigin, end ); dtQueryFilter filter; filter.setIncludeFlags( RECAST_POLYFLAGS_WALK ); filter.setExcludeFlags( 0 ); return dtStatusSucceed( navQuery->findPath( startRef, endRef, start, end, &filter, pathRefs, &numPathRefs, maxPathRefs ) ) && numPathRefs > 0; } int idRecastNavMeshLocal::TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const { dtPolyRef pathRefs[MAX_RECAST_PATH_POLYS]; int numPathRefs; const idVec3 goalOrigin = AreaCenter( goalAreaNum ); if ( !FindPathRefs( areaNum, origin, goalAreaNum, goalOrigin, travelFlags, pathRefs, numPathRefs, MAX_RECAST_PATH_POLYS ) ) { return 0; } const float dist = ( goalOrigin - origin ).Length(); return idMath::Ftoi( dist * 100.0f / 300.0f ); } bool idRecastNavMeshLocal::RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, recastPath_t &route ) const { recastPath_t path; const idVec3 goalOrigin = AreaCenter( goalAreaNum ); if ( !PathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, travelFlags ) ) { route.travelTime = 0; route.moveAreaNum = 0; route.moveGoal = origin; route.secondaryGoal = origin; return false; } route = path; route.travelTime = idMath::Ftoi( ( path.moveGoal - origin ).Length() * 100.0f / 300.0f ); return true; } bool idRecastNavMeshLocal::PathToGoal( recastPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { path.moveGoal = origin; path.moveAreaNum = areaNum; path.secondaryGoal = origin; path.travelTime = 0; dtPolyRef pathRefs[MAX_RECAST_PATH_POLYS]; int numPathRefs; if ( !FindPathRefs( areaNum, origin, goalAreaNum, goalOrigin, travelFlags, pathRefs, numPathRefs, MAX_RECAST_PATH_POLYS ) ) { return false; } float start[3], end[3], straightPath[MAX_RECAST_PATH_POLYS * 3]; unsigned char straightFlags[MAX_RECAST_PATH_POLYS]; dtPolyRef straightRefs[MAX_RECAST_PATH_POLYS]; int numStraight = 0; ToDetour( origin, start ); ToDetour( goalOrigin, end ); dtQueryFilter filter; filter.setIncludeFlags( RECAST_POLYFLAGS_WALK ); filter.setExcludeFlags( 0 ); if ( dtStatusFailed( navQuery->findStraightPath( start, end, pathRefs, numPathRefs, straightPath, straightFlags, straightRefs, &numStraight, MAX_RECAST_PATH_POLYS ) ) || numStraight <= 0 ) { return false; } const int steerIndex = numStraight > 1 ? 1 : 0; path.moveGoal = FromDetour( &straightPath[steerIndex * 3] ); path.moveAreaNum = AreaForRef( straightRefs[steerIndex] ? straightRefs[steerIndex] : pathRefs[numPathRefs - 1] ); path.secondaryGoal = goalOrigin; path.travelTime = idMath::Ftoi( ( path.moveGoal - origin ).Length() * 100.0f / 300.0f ); if ( LineHitsBlocker( origin, path.moveGoal ) ) { return false; } return true; } bool idRecastNavMeshLocal::PathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { if ( LineHitsBlocker( origin, goalOrigin ) ) { endPos = origin; endAreaNum = areaNum; return false; } dtPolyRef startRef = RefForArea( areaNum ); idVec3 nearest; if ( startRef == 0 && !FindNearestPoly( origin, settings.boundingBoxes[0], startRef, &nearest ) ) { endPos = origin; endAreaNum = 0; return false; } float start[3], end[3], hitNormal[3]; float t = 0.0f; ToDetour( origin, start ); ToDetour( goalOrigin, end ); dtQueryFilter filter; filter.setIncludeFlags( RECAST_POLYFLAGS_WALK ); filter.setExcludeFlags( 0 ); dtPolyRef visited[MAX_RECAST_PATH_POLYS]; int numVisited = 0; if ( dtStatusFailed( navQuery->raycast( startRef, start, end, &filter, &t, hitNormal, visited, &numVisited, MAX_RECAST_PATH_POLYS ) ) ) { endPos = origin; endAreaNum = areaNum; return false; } if ( t > 1.0f ) { t = 1.0f; } endPos = origin + ( goalOrigin - origin ) * t; endAreaNum = numVisited > 0 ? AreaForRef( visited[numVisited - 1] ) : areaNum; return t >= 1.0f; } bool idRecastNavMeshLocal::PathAreaList( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, int *areas, int &numAreas, int maxAreas ) const { numAreas = 0; dtPolyRef pathRefs[MAX_RECAST_PATH_POLYS]; int numPathRefs; if ( maxAreas <= 0 || !areas || !FindPathRefs( areaNum, origin, goalAreaNum, goalOrigin, travelFlags, pathRefs, numPathRefs, MAX_RECAST_PATH_POLYS ) ) { return false; } for ( int i = 0; i < numPathRefs && numAreas < maxAreas; i++ ) { areas[numAreas++] = AreaForRef( pathRefs[i] ); } return numAreas > 0; } void idRecastNavMeshLocal::Stats( void ) const { int activeObstacles = 0; int activeBlockers = 0; for ( int i = 0; i < obstacles.Num(); i++ ) { if ( obstacles[i].active ) { activeObstacles++; } } for ( int i = 0; i < areaBlockers.Num(); i++ ) { if ( areaBlockers[i].active ) { activeBlockers++; } } common->Printf( "[%s]\n", name.c_str() ); common->Printf( "Recast/Detour navmesh loaded: %d cached poly refs, %d active obstacles, %d active blockers\n", polyRefs.Num(), activeObstacles, activeBlockers ); common->Printf( "DetourCrowd avoidance: %s\n", crowd ? "initialized" : "not available" ); } class idRecastNavigationManagerLocal : public idRecastNavigationManager { public: virtual ~idRecastNavigationManagerLocal( void ); virtual recastNavHandle_t Alloc( void ); virtual void Free( recastNavHandle_t handle ); virtual bool Load( recastNavHandle_t handle, const char *aasName, const char *fileName, unsigned int mapFileCRC ); virtual bool BuildMapFile( const char *mapName, const char *aasType ); virtual void Stats( recastNavHandle_t handle ) const; virtual void DebugDraw( recastNavHandle_t handle, idRenderWorld *renderWorld, int drawMode, int lifetime ) const; virtual bool IsLoaded( recastNavHandle_t handle ) const; virtual const char * GetName( recastNavHandle_t handle ) const; virtual int NumPolys( recastNavHandle_t handle ) const; virtual const idAASSettings *GetSettings( recastNavHandle_t handle ) const; virtual int PointAreaNum( recastNavHandle_t handle, const idVec3 &origin ) const; virtual int PointReachableAreaNum( recastNavHandle_t handle, const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const; virtual int BoundsReachableAreaNum( recastNavHandle_t handle, const idBounds &bounds, const int areaFlags ) const; virtual void PushPointIntoAreaNum( recastNavHandle_t handle, int areaNum, idVec3 &origin ) const; virtual idVec3 AreaCenter( recastNavHandle_t handle, int areaNum ) const; virtual int AreaFlags( recastNavHandle_t handle, int areaNum ) const; virtual int AreaTravelFlags( recastNavHandle_t handle, int areaNum ) const; virtual bool Trace( recastNavHandle_t handle, aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; virtual bool SetAreaState( recastNavHandle_t handle, const idBounds &bounds, const int areaContents, bool disabled ); virtual recastObstacleHandle_t AddObstacle( recastNavHandle_t handle, const idBounds &bounds ); virtual void RemoveObstacle( recastNavHandle_t handle, const recastObstacleHandle_t obstacleHandle ); virtual void RemoveAllObstacles( recastNavHandle_t handle ); virtual int TravelTimeToGoalArea( recastNavHandle_t handle, int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const; virtual bool RouteToGoalArea( recastNavHandle_t handle, int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, recastPath_t &route ) const; virtual bool PathToGoal( recastNavHandle_t handle, recastPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; virtual bool PathValid( recastNavHandle_t handle, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; virtual bool PathAreaList( recastNavHandle_t handle, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, int *areas, int &numAreas, int maxAreas ) const; private: idRecastNavMeshLocal * Nav( recastNavHandle_t handle ) const; idList navMeshes; }; idRecastNavigationManagerLocal RecastNavigationManagerLocal; idRecastNavigationManager *RecastNavigationManager = &RecastNavigationManagerLocal; idRecastNavigationManagerLocal::~idRecastNavigationManagerLocal( void ) { for ( int i = 0; i < navMeshes.Num(); i++ ) { delete navMeshes[i]; } navMeshes.Clear(); } idRecastNavMeshLocal *idRecastNavigationManagerLocal::Nav( recastNavHandle_t handle ) const { if ( handle < 0 || handle >= navMeshes.Num() ) { return NULL; } return navMeshes[handle]; } recastNavHandle_t idRecastNavigationManagerLocal::Alloc( void ) { for ( int i = 0; i < navMeshes.Num(); i++ ) { if ( navMeshes[i] == NULL ) { navMeshes[i] = new idRecastNavMeshLocal; return i; } } return navMeshes.Append( new idRecastNavMeshLocal ); } void idRecastNavigationManagerLocal::Free( recastNavHandle_t handle ) { idRecastNavMeshLocal *nav = Nav( handle ); if ( !nav ) { return; } delete nav; navMeshes[handle] = NULL; } bool idRecastNavigationManagerLocal::Load( recastNavHandle_t handle, const char *aasName, const char *fileName, unsigned int mapFileCRC ) { idRecastNavMeshLocal *nav = Nav( handle ); if ( !nav ) { return false; } idStr aasType; idStr aasNameStr = aasName; aasNameStr.ExtractFileExtension( aasType ); if ( aasType.IsEmpty() ) { return false; } const idDeclEntityDef *settingsDecl = static_cast( declManager->FindType( DECL_ENTITYDEF, aasType, false ) ); const idDict *settingsDict = settingsDecl ? &settingsDecl->dict : NULL; if ( !settingsDict ) { common->Warning( "Recast: unknown AAS/nav type '%s'", aasType.c_str() ); return false; } idAASSettings settings; settings.FromDict( aasType, settingsDict ); nav->InitForAAS( aasNameStr, settings, mapFileCRC ); if ( !nav->Load( fileName, mapFileCRC ) ) { nav->Shutdown(); return false; } return true; } bool idRecastNavigationManagerLocal::BuildMapFile( const char *mapName, const char *aasType ) { return idRecastNavMeshLocal::BuildMapFile( mapName, aasType ); } void idRecastNavigationManagerLocal::Stats( recastNavHandle_t handle ) const { const idRecastNavMeshLocal *nav = Nav( handle ); if ( nav ) { nav->Stats(); } } void idRecastNavigationManagerLocal::DebugDraw( recastNavHandle_t handle, idRenderWorld *renderWorld, int drawMode, int lifetime ) const { const idRecastNavMeshLocal *nav = Nav( handle ); if ( nav ) { nav->DebugDraw( renderWorld, drawMode, lifetime ); } } bool idRecastNavigationManagerLocal::IsLoaded( recastNavHandle_t handle ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav && nav->IsLoaded(); } const char *idRecastNavigationManagerLocal::GetName( recastNavHandle_t handle ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->GetName().c_str() : ""; } int idRecastNavigationManagerLocal::NumPolys( recastNavHandle_t handle ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->NumPolys() : 0; } const idAASSettings *idRecastNavigationManagerLocal::GetSettings( recastNavHandle_t handle ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->GetSettings() : NULL; } int idRecastNavigationManagerLocal::PointAreaNum( recastNavHandle_t handle, const idVec3 &origin ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->PointAreaNum( origin ) : 0; } int idRecastNavigationManagerLocal::PointReachableAreaNum( recastNavHandle_t handle, const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->PointReachableAreaNum( origin, searchBounds, areaFlags ) : 0; } int idRecastNavigationManagerLocal::BoundsReachableAreaNum( recastNavHandle_t handle, const idBounds &bounds, const int areaFlags ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->BoundsReachableAreaNum( bounds, areaFlags ) : 0; } void idRecastNavigationManagerLocal::PushPointIntoAreaNum( recastNavHandle_t handle, int areaNum, idVec3 &origin ) const { const idRecastNavMeshLocal *nav = Nav( handle ); if ( nav ) { nav->PushPointIntoAreaNum( areaNum, origin ); } } idVec3 idRecastNavigationManagerLocal::AreaCenter( recastNavHandle_t handle, int areaNum ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->AreaCenter( areaNum ) : vec3_origin; } int idRecastNavigationManagerLocal::AreaFlags( recastNavHandle_t handle, int areaNum ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->AreaFlags( areaNum ) : 0; } int idRecastNavigationManagerLocal::AreaTravelFlags( recastNavHandle_t handle, int areaNum ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->AreaTravelFlags( areaNum ) : TFL_INVALID; } bool idRecastNavigationManagerLocal::Trace( recastNavHandle_t handle, aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->Trace( trace, start, end ) : false; } bool idRecastNavigationManagerLocal::SetAreaState( recastNavHandle_t handle, const idBounds &bounds, const int areaContents, bool disabled ) { idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->SetAreaState( bounds, areaContents, disabled ) : false; } recastObstacleHandle_t idRecastNavigationManagerLocal::AddObstacle( recastNavHandle_t handle, const idBounds &bounds ) { idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->AddObstacle( bounds ) : -1; } void idRecastNavigationManagerLocal::RemoveObstacle( recastNavHandle_t handle, const recastObstacleHandle_t obstacleHandle ) { idRecastNavMeshLocal *nav = Nav( handle ); if ( nav ) { nav->RemoveObstacle( obstacleHandle ); } } void idRecastNavigationManagerLocal::RemoveAllObstacles( recastNavHandle_t handle ) { idRecastNavMeshLocal *nav = Nav( handle ); if ( nav ) { nav->RemoveAllObstacles(); } } int idRecastNavigationManagerLocal::TravelTimeToGoalArea( recastNavHandle_t handle, int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->TravelTimeToGoalArea( areaNum, origin, goalAreaNum, travelFlags ) : 0; } bool idRecastNavigationManagerLocal::RouteToGoalArea( recastNavHandle_t handle, int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, recastPath_t &route ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->RouteToGoalArea( areaNum, origin, goalAreaNum, travelFlags, route ) : false; } bool idRecastNavigationManagerLocal::PathToGoal( recastNavHandle_t handle, recastPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->PathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, travelFlags ) : false; } bool idRecastNavigationManagerLocal::PathValid( recastNavHandle_t handle, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->PathValid( areaNum, origin, goalAreaNum, goalOrigin, travelFlags, endPos, endAreaNum ) : false; } bool idRecastNavigationManagerLocal::PathAreaList( recastNavHandle_t handle, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, int *areas, int &numAreas, int maxAreas ) const { const idRecastNavMeshLocal *nav = Nav( handle ); return nav ? nav->PathAreaList( areaNum, origin, goalAreaNum, goalOrigin, travelFlags, areas, numAreas, maxAreas ) : false; }