mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-14 01:01:05 +02:00
1332 lines
48 KiB
C++
1332 lines
48 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
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<dtPolyRef> polyRefs;
|
|
idList<recastBoundsBlocker_t> obstacles;
|
|
idList<recastBoundsBlocker_t> 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<float> verts;
|
|
idList<int> 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<const idMapBrush *>( prim ), origin, axis );
|
|
} else if ( prim->GetType() == idMapPrimitive::TYPE_PATCH ) {
|
|
RecastAddPatchGeometry( geom, static_cast<const idMapPatch *>( 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<const idDeclEntityDef *>( 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<idRecastNavMeshLocal *> 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<const idDeclEntityDef *>( 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;
|
|
}
|