2019-01-24 12:10:31 +01:00
|
|
|
#include <Common.h>
|
|
|
|
#include <Framework.h>
|
|
|
|
#include <Territory/Zone.h>
|
|
|
|
#include <Logging/Logger.h>
|
2019-01-25 12:23:38 +11:00
|
|
|
#include <ServerMgr.h>
|
2019-01-24 22:24:45 +01:00
|
|
|
|
2019-04-19 00:39:42 +02:00
|
|
|
#include "Actor/Actor.h"
|
|
|
|
#include "Actor/Chara.h"
|
|
|
|
|
2019-01-26 00:06:24 +01:00
|
|
|
#include <Manager/RNGMgr.h>
|
2019-01-24 12:10:31 +01:00
|
|
|
|
|
|
|
#include "NaviProvider.h"
|
|
|
|
|
|
|
|
#include <recastnavigation/Detour/Include/DetourNavMesh.h>
|
|
|
|
#include <recastnavigation/Detour/Include/DetourNavMeshQuery.h>
|
|
|
|
#include <DetourCommon.h>
|
|
|
|
#include <recastnavigation/Recast/Include/Recast.h>
|
|
|
|
#include <experimental/filesystem>
|
|
|
|
|
2019-01-24 22:24:45 +01:00
|
|
|
Sapphire::World::Navi::NaviProvider::NaviProvider( const std::string& internalName, FrameworkPtr pFw ) :
|
2019-01-24 12:10:31 +01:00
|
|
|
m_naviMesh( nullptr ),
|
|
|
|
m_naviMeshQuery( nullptr ),
|
2019-01-24 22:24:45 +01:00
|
|
|
m_internalName( internalName ),
|
|
|
|
m_pFw( pFw )
|
2019-01-24 12:10:31 +01:00
|
|
|
{
|
|
|
|
// Set defaults
|
2019-01-24 22:24:45 +01:00
|
|
|
m_polyFindRange[ 0 ] = 10;
|
|
|
|
m_polyFindRange[ 1 ] = 20;
|
|
|
|
m_polyFindRange[ 2 ] = 10;
|
2019-01-24 12:10:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Sapphire::World::Navi::NaviProvider::init()
|
|
|
|
{
|
2019-01-25 12:23:38 +11:00
|
|
|
auto& cfg = m_pFw->get< Sapphire::World::ServerMgr >()->getConfig();
|
|
|
|
|
|
|
|
auto meshesFolder = std::experimental::filesystem::path( cfg.navigation.meshPath );
|
2019-01-24 12:10:31 +01:00
|
|
|
auto meshFolder = meshesFolder / std::experimental::filesystem::path( m_internalName );
|
|
|
|
|
|
|
|
if( std::experimental::filesystem::exists( meshFolder ) )
|
|
|
|
{
|
|
|
|
auto baseMesh = meshFolder / std::experimental::filesystem::path( m_internalName + ".nav" );
|
|
|
|
|
2019-01-25 22:43:04 +11:00
|
|
|
if( !loadMesh( baseMesh.string() ) )
|
|
|
|
return false;
|
2019-01-24 12:10:31 +01:00
|
|
|
|
2019-04-19 00:39:42 +02:00
|
|
|
m_pCrowd = std::make_unique< dtCrowd >();
|
|
|
|
|
|
|
|
if( !m_pCrowd->init( 1000, 10.f, m_naviMesh ) )
|
|
|
|
return false;
|
|
|
|
|
2019-04-19 02:15:18 +02:00
|
|
|
dtObstacleAvoidanceParams params;
|
|
|
|
// Use mostly default settings, copy from dtCrowd.
|
|
|
|
memcpy(¶ms, m_pCrowd->getObstacleAvoidanceParams(0), sizeof(dtObstacleAvoidanceParams));
|
|
|
|
|
|
|
|
// Low (11)
|
|
|
|
params.velBias = 0.5f;
|
|
|
|
params.adaptiveDivs = 5;
|
|
|
|
params.adaptiveRings = 2;
|
|
|
|
params.adaptiveDepth = 1;
|
|
|
|
m_pCrowd->setObstacleAvoidanceParams(0, ¶ms);
|
|
|
|
|
|
|
|
// Medium (22)
|
|
|
|
params.velBias = 0.5f;
|
|
|
|
params.adaptiveDivs = 5;
|
|
|
|
params.adaptiveRings = 2;
|
|
|
|
params.adaptiveDepth = 2;
|
|
|
|
m_pCrowd->setObstacleAvoidanceParams(1, ¶ms);
|
|
|
|
|
|
|
|
// Good (45)
|
|
|
|
params.velBias = 0.5f;
|
|
|
|
params.adaptiveDivs = 7;
|
|
|
|
params.adaptiveRings = 2;
|
|
|
|
params.adaptiveDepth = 3;
|
|
|
|
m_pCrowd->setObstacleAvoidanceParams(2, ¶ms);
|
|
|
|
|
|
|
|
// High (66)
|
|
|
|
params.velBias = 0.5f;
|
|
|
|
params.adaptiveDivs = 7;
|
|
|
|
params.adaptiveRings = 3;
|
|
|
|
params.adaptiveDepth = 3;
|
|
|
|
|
|
|
|
m_pCrowd->setObstacleAvoidanceParams(3, ¶ms);
|
|
|
|
|
|
|
|
m_vod = dtAllocObstacleAvoidanceDebugData();
|
|
|
|
m_vod->init( 2048 );
|
|
|
|
|
2019-01-24 12:10:31 +01:00
|
|
|
initQuery();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Sapphire::World::Navi::NaviProvider::hasNaviMesh() const
|
|
|
|
{
|
|
|
|
return m_naviMesh != nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::World::Navi::NaviProvider::initQuery()
|
|
|
|
{
|
|
|
|
if( m_naviMeshQuery )
|
|
|
|
dtFreeNavMeshQuery( m_naviMeshQuery );
|
|
|
|
|
|
|
|
m_naviMeshQuery = dtAllocNavMeshQuery();
|
|
|
|
m_naviMeshQuery->init( m_naviMesh, 2048 );
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t Sapphire::World::Navi::NaviProvider::fixupCorridor( dtPolyRef* path, const int32_t npath, const int32_t maxPath,
|
|
|
|
const dtPolyRef* visited, const int32_t nvisited )
|
|
|
|
{
|
|
|
|
int32_t furthestPath = -1;
|
|
|
|
int32_t furthestVisited = -1;
|
|
|
|
|
|
|
|
// Find furthest common polygon.
|
|
|
|
for( int32_t i = npath - 1; i >= 0; --i )
|
|
|
|
{
|
|
|
|
bool found = false;
|
|
|
|
for( int32_t j = nvisited - 1; j >= 0; --j )
|
|
|
|
{
|
|
|
|
if( path[ i ] == visited[ j ] )
|
|
|
|
{
|
|
|
|
furthestPath = i;
|
|
|
|
furthestVisited = j;
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( found )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no intersection found just return current path.
|
|
|
|
if( furthestPath == -1 || furthestVisited == -1 )
|
|
|
|
return npath;
|
|
|
|
|
|
|
|
// Concatenate paths.
|
|
|
|
|
|
|
|
// Adjust beginning of the buffer to include the visited.
|
|
|
|
const int32_t req = nvisited - furthestVisited;
|
|
|
|
const int32_t orig = rcMin( furthestPath + 1, npath );
|
|
|
|
int32_t size = rcMax( 0, npath - orig );
|
|
|
|
if( req + size > maxPath )
|
|
|
|
size = maxPath - req;
|
|
|
|
if( size )
|
|
|
|
memmove( path + req, path + orig, size * sizeof( dtPolyRef ) );
|
|
|
|
|
|
|
|
// Store visited
|
|
|
|
for( int32_t i = 0; i < req; ++i )
|
|
|
|
path[i] = visited[( nvisited - 1 ) - i];
|
|
|
|
|
|
|
|
return req + size;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t Sapphire::World::Navi::NaviProvider::fixupShortcuts( dtPolyRef* path, int32_t npath, dtNavMeshQuery* navQuery )
|
|
|
|
{
|
|
|
|
if( npath < 3 )
|
|
|
|
return npath;
|
|
|
|
|
|
|
|
// Get connected polygons
|
2019-01-24 22:28:53 +11:00
|
|
|
const int32_t maxNeis = 16;
|
2019-01-24 12:10:31 +01:00
|
|
|
dtPolyRef neis[ maxNeis ];
|
|
|
|
int32_t nneis = 0;
|
|
|
|
|
|
|
|
const dtMeshTile* tile = 0;
|
|
|
|
const dtPoly* poly = 0;
|
|
|
|
if( dtStatusFailed( navQuery->getAttachedNavMesh()->getTileAndPolyByRef( path[ 0 ], &tile, &poly ) ) )
|
|
|
|
return npath;
|
|
|
|
|
|
|
|
for( uint32_t k = poly->firstLink; k != DT_NULL_LINK; k = tile->links[ k ].next )
|
|
|
|
{
|
|
|
|
const dtLink* link = &tile->links[ k ];
|
|
|
|
if( link->ref != 0 )
|
|
|
|
{
|
|
|
|
if( nneis < maxNeis )
|
|
|
|
neis[ nneis++ ] = link->ref;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If any of the neighbour polygons is within the next few polygons
|
|
|
|
// in the path, short cut to that polygon directly.
|
2019-01-24 22:28:53 +11:00
|
|
|
const int32_t maxLookAhead = 6;
|
2019-01-24 12:10:31 +01:00
|
|
|
int32_t cut = 0;
|
|
|
|
for( int32_t i = dtMin( maxLookAhead, npath ) - 1; i > 1 && cut == 0; i-- )
|
|
|
|
{
|
|
|
|
for( int32_t j = 0; j < nneis; j++ )
|
|
|
|
{
|
|
|
|
if( path[ i ] == neis[ j ] )
|
|
|
|
{
|
|
|
|
cut = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( cut > 1 )
|
|
|
|
{
|
|
|
|
int32_t offset = cut - 1;
|
|
|
|
npath -= offset;
|
|
|
|
for( int32_t i = 1; i < npath; i++ )
|
|
|
|
path[ i ] = path[ i + offset ];
|
|
|
|
}
|
|
|
|
|
|
|
|
return npath;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Sapphire::World::Navi::NaviProvider::inRange( const float* v1, const float* v2, const float r, const float h )
|
|
|
|
{
|
|
|
|
const float dx = v2[ 0 ] - v1[ 0 ];
|
|
|
|
const float dy = v2[ 1 ] - v1[ 1 ];
|
|
|
|
const float dz = v2[ 2 ] - v1[ 2 ];
|
|
|
|
return ( dx * dx + dz * dz ) < r * r && fabsf( dy ) < h;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Sapphire::World::Navi::NaviProvider::getSteerTarget( dtNavMeshQuery* navQuery, const float* startPos, const float* endPos,
|
|
|
|
const float minTargetDist, const dtPolyRef* path, const int32_t pathSize,
|
|
|
|
float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef,
|
|
|
|
float* outPoints, int32_t* outPointCount )
|
|
|
|
{
|
|
|
|
// Find steer target.
|
2019-01-24 22:28:53 +11:00
|
|
|
const int32_t MAX_STEER_POINTS = 3;
|
2019-01-24 12:10:31 +01:00
|
|
|
float steerPath[ MAX_STEER_POINTS * 3 ];
|
|
|
|
uint8_t steerPathFlags[ MAX_STEER_POINTS ];
|
|
|
|
dtPolyRef steerPathPolys[ MAX_STEER_POINTS ];
|
|
|
|
int32_t nsteerPath = 0;
|
|
|
|
navQuery->findStraightPath( startPos, endPos, path, pathSize,
|
|
|
|
steerPath, steerPathFlags, steerPathPolys, &nsteerPath, MAX_STEER_POINTS );
|
|
|
|
if( !nsteerPath )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if( outPoints && outPointCount )
|
|
|
|
{
|
|
|
|
*outPointCount = nsteerPath;
|
|
|
|
for( int32_t i = 0; i < nsteerPath; ++i )
|
|
|
|
dtVcopy( &outPoints[ i * 3 ], &steerPath[ i * 3 ] );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find vertex far enough to steer to.
|
|
|
|
int32_t ns = 0;
|
|
|
|
while( ns < nsteerPath )
|
|
|
|
{
|
|
|
|
// Stop at Off-Mesh link or when point is further than slop away.
|
|
|
|
if( ( steerPathFlags[ ns ] & DT_STRAIGHTPATH_OFFMESH_CONNECTION ) ||
|
|
|
|
!inRange( &steerPath[ ns * 3 ], startPos, minTargetDist, 1000.0f ) )
|
|
|
|
break;
|
|
|
|
ns++;
|
|
|
|
}
|
|
|
|
// Failed to find good point to steer to.
|
|
|
|
if( ns >= nsteerPath )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
dtVcopy( steerPos, &steerPath[ ns * 3 ] );
|
|
|
|
steerPos[ 1 ] = startPos[ 1 ];
|
|
|
|
steerPosFlag = steerPathFlags[ ns ];
|
|
|
|
steerPosRef = steerPathPolys[ ns ];
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-01-26 00:06:24 +01:00
|
|
|
static float frand()
|
|
|
|
{
|
|
|
|
return ( float ) rand() / (float)RAND_MAX;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Sapphire::Common::FFXIVARR_POSITION3
|
|
|
|
Sapphire::World::Navi::NaviProvider::findRandomPositionInCircle( const Sapphire::Common::FFXIVARR_POSITION3& startPos,
|
|
|
|
float maxRadius )
|
|
|
|
{
|
|
|
|
dtStatus status;
|
|
|
|
|
|
|
|
float spos[ 3 ] = { startPos.x, startPos.y, startPos.z };
|
|
|
|
|
|
|
|
float polyPickExt[ 3 ];
|
|
|
|
polyPickExt[ 0 ] = 30;
|
|
|
|
polyPickExt[ 1 ] = 60;
|
|
|
|
polyPickExt[ 2 ] = 30;
|
|
|
|
|
|
|
|
float randomPt[ 3 ];
|
|
|
|
float snearest[ 3 ];
|
|
|
|
|
|
|
|
dtQueryFilter filter;
|
|
|
|
filter.setIncludeFlags( 0xffff );
|
|
|
|
filter.setExcludeFlags( 0 );
|
|
|
|
|
|
|
|
dtPolyRef startRef;
|
|
|
|
dtPolyRef randomRef;
|
|
|
|
|
|
|
|
status = m_naviMeshQuery->findNearestPoly( spos, polyPickExt, &filter, &startRef, snearest );
|
|
|
|
|
|
|
|
if( dtStatusFailed( status ) )
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !m_naviMesh->isValidPolyRef( startRef ) )
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >();
|
|
|
|
auto rng = pRNGMgr->getRandGenerator< float >( 0.f, 1.f );
|
|
|
|
status = m_naviMeshQuery->findRandomPointAroundCircle( startRef, spos, maxRadius, &filter, frand,
|
2019-01-27 01:12:31 +01:00
|
|
|
&randomRef, randomPt );
|
2019-01-26 00:06:24 +01:00
|
|
|
|
|
|
|
if( dtStatusFailed( status ) )
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
return { randomPt[ 0 ], randomPt[ 1 ], randomPt[ 2 ] };
|
|
|
|
}
|
|
|
|
|
2019-01-24 12:10:31 +01:00
|
|
|
std::vector< Sapphire::Common::FFXIVARR_POSITION3 >
|
|
|
|
Sapphire::World::Navi::NaviProvider::findFollowPath( const Common::FFXIVARR_POSITION3& startPos,
|
|
|
|
const Common::FFXIVARR_POSITION3& endPos )
|
|
|
|
{
|
2019-01-21 02:08:38 +01:00
|
|
|
if( !m_naviMesh || !m_naviMeshQuery )
|
2019-01-24 12:10:31 +01:00
|
|
|
throw std::runtime_error( "No navimesh loaded" );
|
|
|
|
|
|
|
|
auto resultCoords = std::vector< Common::FFXIVARR_POSITION3 >();
|
|
|
|
|
|
|
|
dtPolyRef startRef, endRef = 0;
|
|
|
|
|
|
|
|
float spos[ 3 ] = { startPos.x, startPos.y, startPos.z };
|
|
|
|
float epos[ 3 ] = { endPos.x, endPos.y, endPos.z };
|
|
|
|
|
|
|
|
dtQueryFilter filter;
|
|
|
|
filter.setIncludeFlags( 0xffff );
|
|
|
|
filter.setExcludeFlags( 0 );
|
|
|
|
|
|
|
|
m_naviMeshQuery->findNearestPoly( spos, m_polyFindRange, &filter, &startRef, 0 );
|
|
|
|
m_naviMeshQuery->findNearestPoly( epos, m_polyFindRange, &filter, &endRef, 0 );
|
|
|
|
|
|
|
|
// Couldn't find any close polys to navigate from
|
2019-01-21 02:08:38 +01:00
|
|
|
if( !startRef || !endRef )
|
2019-01-24 12:10:31 +01:00
|
|
|
return resultCoords;
|
|
|
|
|
|
|
|
dtPolyRef polys[ MAX_POLYS ];
|
|
|
|
int32_t numPolys = 0;
|
|
|
|
|
|
|
|
m_naviMeshQuery->findPath( startRef, endRef, spos, epos, &filter, polys, &numPolys, MAX_POLYS );
|
|
|
|
|
|
|
|
// Check if we got polys back for navigation
|
|
|
|
if( numPolys )
|
|
|
|
{
|
|
|
|
// Iterate over the path to find smooth path on the detail mesh surface.
|
|
|
|
memcpy( polys, polys, sizeof( dtPolyRef )*numPolys );
|
|
|
|
int32_t npolys = numPolys;
|
|
|
|
|
|
|
|
float iterPos[3], targetPos[3];
|
|
|
|
m_naviMeshQuery->closestPointOnPoly( startRef, spos, iterPos, 0 );
|
|
|
|
m_naviMeshQuery->closestPointOnPoly( polys[ npolys - 1 ], epos, targetPos, 0 );
|
|
|
|
|
2019-01-26 00:06:24 +01:00
|
|
|
//Logger::debug( "IterPos: {0} {1} {2}; TargetPos: {3} {4} {5}",
|
|
|
|
// iterPos[ 0 ], iterPos[ 1 ], iterPos[ 2 ],
|
|
|
|
// targetPos[ 0 ], targetPos[ 1 ], targetPos[ 2 ] );
|
2019-01-24 12:10:31 +01:00
|
|
|
|
2019-01-24 22:28:53 +11:00
|
|
|
const float STEP_SIZE = 1.2f;
|
|
|
|
const float SLOP = 0.15f;
|
2019-01-24 12:10:31 +01:00
|
|
|
|
|
|
|
int32_t numSmoothPath = 0;
|
|
|
|
float smoothPath[ MAX_SMOOTH * 3 ];
|
|
|
|
|
|
|
|
dtVcopy( &smoothPath[ numSmoothPath * 3 ], iterPos );
|
|
|
|
numSmoothPath++;
|
|
|
|
|
|
|
|
// Move towards target a small advancement at a time until target reached or
|
|
|
|
// when ran out of memory to store the path.
|
|
|
|
while( npolys && numSmoothPath < MAX_SMOOTH )
|
|
|
|
{
|
|
|
|
// Find location to steer towards.
|
|
|
|
float steerPos[ 3 ];
|
|
|
|
uint8_t steerPosFlag;
|
|
|
|
dtPolyRef steerPosRef;
|
|
|
|
|
|
|
|
if( !getSteerTarget( m_naviMeshQuery, iterPos, targetPos, SLOP,
|
|
|
|
polys, npolys, steerPos, steerPosFlag, steerPosRef ) )
|
|
|
|
break;
|
|
|
|
|
|
|
|
bool endOfPath = ( steerPosFlag & DT_STRAIGHTPATH_END ) ? true : false;
|
|
|
|
bool offMeshConnection = ( steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION ) ? true : false;
|
|
|
|
|
|
|
|
// Find movement delta.
|
|
|
|
float delta[ 3 ], len;
|
|
|
|
dtVsub( delta, steerPos, iterPos );
|
|
|
|
len = dtMathSqrtf( dtVdot( delta, delta ) );
|
|
|
|
// If the steer target is end of path or off-mesh link, do not move past the location.
|
|
|
|
if( ( endOfPath || offMeshConnection ) && len < STEP_SIZE )
|
|
|
|
len = 1;
|
|
|
|
else
|
|
|
|
len = STEP_SIZE / len;
|
|
|
|
float moveTgt[ 3 ];
|
|
|
|
dtVmad( moveTgt, iterPos, delta, len );
|
|
|
|
|
|
|
|
// Move
|
|
|
|
float result[ 3 ];
|
|
|
|
dtPolyRef visited[ 16 ];
|
|
|
|
int32_t nvisited = 0;
|
|
|
|
m_naviMeshQuery->moveAlongSurface( polys[ 0 ], iterPos, moveTgt, &filter,
|
|
|
|
result, visited, &nvisited, 16 );
|
|
|
|
|
|
|
|
npolys = fixupCorridor( polys, npolys, MAX_POLYS, visited, nvisited );
|
|
|
|
npolys = fixupShortcuts( polys, npolys, m_naviMeshQuery );
|
|
|
|
|
|
|
|
float h = 0;
|
|
|
|
m_naviMeshQuery->getPolyHeight( polys[0], result, &h );
|
|
|
|
result[ 1 ] = h;
|
|
|
|
dtVcopy( iterPos, result );
|
|
|
|
|
|
|
|
// Handle end of path and off-mesh links when close enough.
|
|
|
|
if( endOfPath && inRange( iterPos, steerPos, SLOP, 1.0f ) )
|
|
|
|
{
|
|
|
|
// Reached end of path.
|
|
|
|
dtVcopy( iterPos, targetPos );
|
|
|
|
if( numSmoothPath < MAX_SMOOTH )
|
|
|
|
{
|
|
|
|
dtVcopy( &smoothPath[ numSmoothPath * 3 ], iterPos );
|
|
|
|
numSmoothPath++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if( offMeshConnection && inRange( iterPos, steerPos, SLOP, 1.0f ) )
|
|
|
|
{
|
|
|
|
// Reached off-mesh connection.
|
|
|
|
float startPos[ 3 ], endPos[ 3 ];
|
|
|
|
|
|
|
|
// Advance the path up to and over the off-mesh connection.
|
|
|
|
dtPolyRef prevRef = 0, polyRef = polys[ 0 ];
|
|
|
|
int32_t npos = 0;
|
|
|
|
while( npos < npolys && polyRef != steerPosRef )
|
|
|
|
{
|
|
|
|
prevRef = polyRef;
|
|
|
|
polyRef = polys[ npos ];
|
|
|
|
npos++;
|
|
|
|
}
|
|
|
|
for( int32_t i = npos; i < npolys; ++i )
|
|
|
|
polys[ i - npos ] = polys[ i ];
|
|
|
|
npolys -= npos;
|
|
|
|
|
|
|
|
// Handle the connection.
|
|
|
|
dtStatus status = m_naviMesh->getOffMeshConnectionPolyEndPoints( prevRef, polyRef, startPos, endPos );
|
|
|
|
if( dtStatusSucceed( status ) )
|
|
|
|
{
|
|
|
|
if( numSmoothPath < MAX_SMOOTH )
|
|
|
|
{
|
|
|
|
dtVcopy( &smoothPath[ numSmoothPath * 3 ], startPos );
|
|
|
|
numSmoothPath++;
|
|
|
|
// Hack to make the dotted path not visible during off-mesh connection.
|
|
|
|
if( numSmoothPath & 1 )
|
|
|
|
{
|
|
|
|
dtVcopy( &smoothPath[ numSmoothPath * 3 ], startPos );
|
|
|
|
numSmoothPath++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Move position at the other side of the off-mesh link.
|
|
|
|
dtVcopy( iterPos, endPos );
|
|
|
|
float eh = 0.0f;
|
|
|
|
m_naviMeshQuery->getPolyHeight( polys[ 0 ], iterPos, &eh );
|
|
|
|
iterPos[ 1 ] = eh;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store results.
|
|
|
|
if( numSmoothPath < MAX_SMOOTH )
|
|
|
|
{
|
|
|
|
dtVcopy( &smoothPath[ numSmoothPath * 3 ], iterPos );
|
|
|
|
numSmoothPath++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for( int32_t i = 0; i < numSmoothPath; i += 3 )
|
|
|
|
{
|
2019-01-26 00:06:24 +01:00
|
|
|
resultCoords.emplace_back( Common::FFXIVARR_POSITION3{ smoothPath[ i ], smoothPath[ i + 1 ], smoothPath[ i + 2 ] } );
|
2019-01-24 12:10:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return resultCoords;
|
2019-01-20 16:10:48 +01:00
|
|
|
}
|
|
|
|
|
2019-01-25 22:43:04 +11:00
|
|
|
bool Sapphire::World::Navi::NaviProvider::loadMesh( const std::string& path )
|
2019-01-20 16:10:48 +01:00
|
|
|
{
|
2019-01-24 12:10:31 +01:00
|
|
|
FILE* fp = fopen( path.c_str(), "rb" );
|
2019-01-20 16:10:48 +01:00
|
|
|
if( !fp )
|
2019-01-25 22:43:04 +11:00
|
|
|
{
|
|
|
|
Logger::error( "Couldn't open navimesh file: {0}", path );
|
|
|
|
return false;
|
|
|
|
}
|
2019-01-24 12:10:31 +01:00
|
|
|
|
|
|
|
// Read header.
|
|
|
|
NavMeshSetHeader header;
|
|
|
|
|
|
|
|
size_t readLen = fread( &header, sizeof( NavMeshSetHeader ), 1, fp );
|
|
|
|
if( readLen != 1 )
|
|
|
|
{
|
|
|
|
fclose( fp );
|
2019-01-25 22:43:04 +11:00
|
|
|
Logger::error( "Couldn't read NavMeshSetHeader for {0}", path );
|
|
|
|
return false;
|
2019-01-24 12:10:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if( header.magic != NAVMESHSET_MAGIC )
|
|
|
|
{
|
|
|
|
fclose( fp );
|
2019-01-25 22:43:04 +11:00
|
|
|
Logger::error( "'{0}' has an incorrect NavMeshSet header.", path );
|
|
|
|
return false;
|
2019-01-24 12:10:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if( header.version != NAVMESHSET_VERSION )
|
|
|
|
{
|
|
|
|
fclose( fp );
|
2019-01-25 22:43:04 +11:00
|
|
|
Logger::error( "'{0}' has an incorrect NavMeshSet version. Expected '{1}', got '{2}'", path, NAVMESHSET_VERSION, header.version );
|
|
|
|
return false;
|
2019-01-24 12:10:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if( !m_naviMesh )
|
|
|
|
{
|
|
|
|
m_naviMesh = dtAllocNavMesh();
|
|
|
|
if( !m_naviMesh )
|
|
|
|
{
|
|
|
|
fclose( fp );
|
2019-01-25 22:43:04 +11:00
|
|
|
Logger::error( "Couldn't allocate dtNavMesh" );
|
|
|
|
return false;
|
2019-01-24 12:10:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
dtStatus status = m_naviMesh->init( &header.params );
|
|
|
|
if( dtStatusFailed( status ) )
|
|
|
|
{
|
|
|
|
fclose( fp );
|
2019-01-25 22:43:04 +11:00
|
|
|
Logger::error( "Couldn't initialise dtNavMesh" );
|
|
|
|
return false;
|
2019-01-24 12:10:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read tiles.
|
|
|
|
for( int32_t i = 0; i < header.numTiles; ++i )
|
|
|
|
{
|
|
|
|
NavMeshTileHeader tileHeader;
|
|
|
|
readLen = fread( &tileHeader, sizeof( tileHeader ), 1, fp );
|
|
|
|
if( readLen != 1 )
|
|
|
|
{
|
|
|
|
fclose( fp );
|
2019-01-25 22:43:04 +11:00
|
|
|
Logger::error( "Couldn't read NavMeshTileHeader from '{0}'", path );
|
|
|
|
return false;
|
2019-01-24 12:10:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if( !tileHeader.tileRef || !tileHeader.dataSize )
|
|
|
|
break;
|
|
|
|
|
2019-01-25 22:43:04 +11:00
|
|
|
auto data = reinterpret_cast< uint8_t* >( dtAlloc( tileHeader.dataSize, DT_ALLOC_PERM ) );
|
2019-01-24 12:10:31 +01:00
|
|
|
if( !data )
|
|
|
|
break;
|
|
|
|
memset( data, 0, tileHeader.dataSize );
|
|
|
|
readLen = fread( data, tileHeader.dataSize, 1, fp );
|
|
|
|
if( readLen != 1 )
|
|
|
|
{
|
|
|
|
dtFree( data );
|
|
|
|
fclose( fp );
|
2019-01-25 22:43:04 +11:00
|
|
|
|
|
|
|
Logger::error( "Couldn't read tile data from '{0}'", path );
|
|
|
|
return false;
|
2019-01-24 12:10:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
m_naviMesh->addTile( data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0 );
|
|
|
|
}
|
|
|
|
|
2019-01-20 16:10:48 +01:00
|
|
|
fclose( fp );
|
2019-01-25 22:43:04 +11:00
|
|
|
|
|
|
|
return true;
|
2019-01-24 12:10:31 +01:00
|
|
|
}
|
2019-04-19 00:39:42 +02:00
|
|
|
|
|
|
|
int32_t Sapphire::World::Navi::NaviProvider::addAgent( Entity::Chara& chara )
|
|
|
|
{
|
|
|
|
dtCrowdAgentParams params;
|
|
|
|
std::memset( ¶ms, 0, sizeof( params ) );
|
2019-04-20 15:13:46 +10:00
|
|
|
params.height = 3.f;
|
|
|
|
params.maxAcceleration = 25.f;
|
|
|
|
params.maxSpeed = std::pow( 2, chara.getScale() * 0.35f ) + 1.f;
|
2019-04-20 15:42:48 +02:00
|
|
|
params.radius = ( chara.getScale() ) * 0.9f;
|
2019-04-20 15:13:46 +10:00
|
|
|
params.collisionQueryRange = params.radius * 12.0f;
|
|
|
|
params.pathOptimizationRange = params.radius * 20.0f;
|
2019-04-19 02:15:18 +02:00
|
|
|
params.updateFlags = 0;
|
|
|
|
//params.updateFlags |= DT_CROWD_OBSTACLE_AVOIDANCE;
|
2019-04-19 00:39:42 +02:00
|
|
|
float position[] = { chara.getPos().x, chara.getPos().y, chara.getPos().z };
|
|
|
|
return m_pCrowd->addAgent( position, ¶ms );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::World::Navi::NaviProvider::updateCrowd( float timeInSeconds )
|
|
|
|
{
|
|
|
|
dtCrowdAgentDebugInfo info;
|
2019-04-19 02:15:18 +02:00
|
|
|
info.idx = -1;
|
|
|
|
info.vod = m_vod;
|
2019-04-19 00:39:42 +02:00
|
|
|
m_pCrowd->update( timeInSeconds, &info );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::World::Navi::NaviProvider::removeAgent( Sapphire::Entity::Chara& chara )
|
|
|
|
{
|
|
|
|
m_pCrowd->removeAgent( chara.getAgentId() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::World::Navi::NaviProvider::calcVel( float* vel, const float* pos, const float* tgt, const float speed )
|
|
|
|
{
|
|
|
|
dtVsub( vel, tgt, pos );
|
|
|
|
vel[ 1 ] = 0.0;
|
|
|
|
dtVnormalize( vel );
|
|
|
|
dtVscale( vel, vel, speed );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::World::Navi::NaviProvider::setMoveTarget( Entity::Chara& chara,
|
|
|
|
const Sapphire::Common::FFXIVARR_POSITION3& endPos )
|
|
|
|
{
|
|
|
|
// Find nearest point on navmesh and set move request to that location.
|
|
|
|
dtNavMeshQuery* navquery = m_naviMeshQuery;
|
|
|
|
|
|
|
|
const dtQueryFilter* filter = m_pCrowd->getFilter( 0 );
|
|
|
|
const float* halfExtents = m_pCrowd->getQueryExtents();
|
|
|
|
|
|
|
|
float vel[ 3 ];
|
2019-04-19 02:15:18 +02:00
|
|
|
float p[ 3 ] = { endPos.x, endPos.y, endPos.z };
|
2019-04-19 00:39:42 +02:00
|
|
|
|
|
|
|
const dtCrowdAgent* ag = m_pCrowd->getAgent( chara.getAgentId() );
|
|
|
|
if( ag && ag->active )
|
|
|
|
{
|
|
|
|
calcVel( vel, ag->npos, p, ag->params.maxSpeed );
|
|
|
|
m_pCrowd->requestMoveVelocity( chara.getAgentId(), vel );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Sapphire::Common::FFXIVARR_POSITION3 Sapphire::World::Navi::NaviProvider::getMovePos( Entity::Chara& chara )
|
|
|
|
{
|
|
|
|
const dtCrowdAgent* ag = m_pCrowd->getAgent( chara.getAgentId() );
|
|
|
|
if( !ag )
|
|
|
|
return { 0.f, 0.f, 0.f };
|
|
|
|
return { ag->npos[ 0 ], ag->npos[ 1 ], ag->npos[ 2 ] };
|
2019-04-19 02:15:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Sapphire::World::Navi::NaviProvider::isAgentActive( Entity::Chara& chara ) const
|
|
|
|
{
|
|
|
|
const dtCrowdAgent* ag = m_pCrowd->getAgent( chara.getAgentId() );
|
|
|
|
return ag && ag->active;
|
|
|
|
|
|
|
|
}
|
2019-04-19 12:15:09 +02:00
|
|
|
|
|
|
|
bool Sapphire::World::Navi::NaviProvider::hasTargetState( Entity::Chara& chara ) const
|
|
|
|
{
|
|
|
|
const dtCrowdAgent* ag = m_pCrowd->getAgent( chara.getAgentId() );
|
|
|
|
return ag->targetState != DT_CROWDAGENT_TARGET_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::World::Navi::NaviProvider::resetMoveTarget( Entity::Chara& chara )
|
|
|
|
{
|
|
|
|
m_pCrowd->resetMoveTarget( chara.getAgentId() );
|
|
|
|
}
|
2019-04-19 14:04:38 +02:00
|
|
|
|
|
|
|
void Sapphire::World::Navi::NaviProvider::updateAgentPosition( Entity::Chara& chara )
|
|
|
|
{
|
|
|
|
removeAgent( chara );
|
|
|
|
auto newIndex = addAgent( chara );
|
|
|
|
chara.setAgentId( newIndex );
|
|
|
|
}
|
2019-04-19 14:19:14 +02:00
|
|
|
|
|
|
|
bool Sapphire::World::Navi::NaviProvider::syncPosToChara( Entity::Chara& chara )
|
|
|
|
{
|
|
|
|
auto pos = getMovePos( chara );
|
|
|
|
if( pos.x == chara.getPos().x && pos.y == chara.getPos().y && pos.z == chara.getPos().z )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
chara.setPos( pos );
|
|
|
|
return true;
|
2019-04-20 15:13:46 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::World::Navi::NaviProvider::addAgentUpdateFlag( Sapphire::Entity::Chara& chara, uint8_t flags )
|
|
|
|
{
|
|
|
|
auto ag = m_pCrowd->getEditableAgent( chara.getAgentId() );
|
|
|
|
|
|
|
|
if( !ag )
|
|
|
|
return;
|
|
|
|
|
|
|
|
ag->params.updateFlags |= flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::World::Navi::NaviProvider::removeAgentUpdateFlag( Sapphire::Entity::Chara& chara, uint8_t flags )
|
|
|
|
{
|
|
|
|
auto ag = m_pCrowd->getEditableAgent( chara.getAgentId() );
|
|
|
|
|
|
|
|
if( !ag )
|
|
|
|
return;
|
|
|
|
|
2019-04-20 22:50:05 +10:00
|
|
|
ag->params.updateFlags &= ~flags;
|
2019-04-19 14:19:14 +02:00
|
|
|
}
|