mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-28 15:17:46 +00:00
Merge branch 'develop' into develop
This commit is contained in:
commit
db105cbb98
45 changed files with 2530 additions and 520 deletions
|
@ -19,7 +19,6 @@ add_custom_target( copy_runtime_files ALL
|
|||
# Dependencies and compiler settings #
|
||||
######################################
|
||||
include( "cmake/paths.cmake" )
|
||||
#include( "cmake/mysql.cmake" )
|
||||
include( "cmake/compiler.cmake" )
|
||||
include( "cmake/cotire.cmake" )
|
||||
|
||||
|
|
|
@ -12,3 +12,4 @@ endif()
|
|||
|
||||
# Create log folder
|
||||
file( MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin/log )
|
||||
file( MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin/navi )
|
||||
|
|
|
@ -12,6 +12,8 @@ ServerSecret = default
|
|||
DataPath = C:\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\sqpack
|
||||
WorldID = 67
|
||||
DefaultGMRank = 255
|
||||
LogLevel = 1
|
||||
LogFilter = 0
|
||||
|
||||
[Network]
|
||||
; Values definining how Users and other servers will access - these have to be set to your public IP when running a public server
|
||||
|
|
|
@ -15,6 +15,9 @@ DisconnectTimeout = 20
|
|||
; Sent on login - each line must be shorter than 307 characters, split lines with ';'
|
||||
MotD = Welcome to Sapphire!;This is a very good server;You can change these messages by editing General.MotD in config/config.ini
|
||||
|
||||
[Navigation]
|
||||
MeshPath = navi
|
||||
|
||||
[Housing]
|
||||
; Set the default estate name. {0} will be replaced with the plot number
|
||||
DefaultEstateName = Estate ${0}
|
|
@ -713,6 +713,8 @@ int main( int argc, char* argv[] )
|
|||
if( !loadSettings( argc, argv ) )
|
||||
throw std::exception();
|
||||
|
||||
Logger::setLogLevel( m_config.global.general.logLevel );
|
||||
|
||||
server.resource[ "^/ZoneName/([0-9]+)$" ][ "GET" ] = &getZoneName;
|
||||
server.resource[ "^/sapphire-api/lobby/createAccount" ][ "POST" ] = &createAccount;
|
||||
server.resource[ "^/sapphire-api/lobby/login" ][ "POST" ] = &login;
|
||||
|
|
|
@ -615,6 +615,7 @@ namespace Sapphire::Common
|
|||
InvincibilityNone,
|
||||
InvincibilityRefill,
|
||||
InvincibilityStayAlive,
|
||||
InvincibilityIgnoreDamage,
|
||||
};
|
||||
|
||||
enum PlayerStateFlag : uint8_t
|
||||
|
|
|
@ -16,6 +16,8 @@ namespace Sapphire::Common::Config
|
|||
uint16_t worldID;
|
||||
|
||||
uint8_t defaultGMRank;
|
||||
uint8_t logLevel;
|
||||
uint32_t logFilter;
|
||||
} general;
|
||||
|
||||
struct Network
|
||||
|
@ -56,6 +58,11 @@ namespace Sapphire::Common::Config
|
|||
bool hotSwap;
|
||||
} scripts;
|
||||
|
||||
struct Navigation
|
||||
{
|
||||
std::string meshPath;
|
||||
} navigation;
|
||||
|
||||
std::string motd;
|
||||
};
|
||||
|
||||
|
|
|
@ -57,6 +57,8 @@ bool Sapphire::ConfigMgr::loadGlobalConfig( Common::Config::GlobalConfig& config
|
|||
config.general.serverSecret = getValue< std::string >( "General", "ServerSecret", "default" );
|
||||
config.general.worldID = getValue< uint16_t >( "General", "WorldID", 67 );
|
||||
config.general.defaultGMRank = getValue< uint8_t >( "General", "DefaultGMRank", 255 );
|
||||
config.general.logLevel = getValue< uint8_t >( "General", "LogLevel", 1 );
|
||||
config.general.logFilter = getValue< uint32_t >( "General", "LogFilter", 0 );
|
||||
|
||||
// network
|
||||
config.network.zoneHost = getValue< std::string >( "Network", "ZoneHost", "127.0.0.1" );
|
||||
|
|
|
@ -53,6 +53,11 @@ namespace Sapphire
|
|||
spdlog::flush_on( spdlog::level::critical );
|
||||
}
|
||||
|
||||
void Logger::setLogLevel( uint8_t logLevel )
|
||||
{
|
||||
spdlog::set_level( static_cast< spdlog::level::level_enum >( logLevel ) );
|
||||
}
|
||||
|
||||
void Logger::error( const std::string& text )
|
||||
{
|
||||
spdlog::get( "logger" )->error( text );
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace Sapphire
|
|||
public:
|
||||
|
||||
static void init( const std::string& logPath );
|
||||
static void setLogLevel( uint8_t logLevel );
|
||||
|
||||
// todo: this is a minor increase in build time because of fmtlib, but much less than including spdlog directly
|
||||
|
||||
|
|
|
@ -229,6 +229,17 @@ namespace Sapphire::Network::Packets
|
|||
IPCTYPE_UNK_320 = 0x0253, // updated 4.5
|
||||
IPCTYPE_UNK_322 = 0x0255, // updated 4.5
|
||||
|
||||
/// Doman Mahjong //////////////////////////////////////
|
||||
MahjongOpenGui = 0x02BC, // only available in mahjong instance
|
||||
MahjongNextRound = 0x02BD, // initial hands(baipai), # of riichi(wat), winds, honba, score and stuff
|
||||
MahjongPlayerAction = 0x02BE, // tsumo(as in drawing a tile) called chi/pon/kan/riichi
|
||||
MahjongEndRoundTsumo = 0x02BF, // called tsumo
|
||||
MahjongEndRoundRon = 0x2C0, // called ron or double ron (waiting for action must be flagged from discard packet to call)
|
||||
MahjongTileDiscard = 0x02C1, // giri (discarding a tile.) chi(1)/pon(2)/kan(4)/ron(8) flags etc..
|
||||
MahjongPlayersInfo = 0x02C2, // actor id, name, rating and stuff..
|
||||
// 2C3 and 2C4 are currently unknown
|
||||
MahjongEndRoundDraw = 0x02C5, // self explanatory
|
||||
MahjongEndGame = 0x02C6, // finished oorasu(all-last) round; shows a result screen.
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -666,7 +666,7 @@ struct FFXIVIpcActorMove :
|
|||
{
|
||||
/* 0000 */ uint8_t rotation;
|
||||
/* 0001 */ uint8_t unknown_1;
|
||||
/* 0002 */ uint8_t unknown_2;
|
||||
/* 0002 */ uint8_t animationType;
|
||||
/* 0003 */ uint8_t unknown_3;
|
||||
/* 0004 */ uint16_t unknown_4;
|
||||
/* 0006 */ uint16_t posX;
|
||||
|
|
|
@ -63,6 +63,8 @@ namespace Sapphire
|
|||
return;
|
||||
}
|
||||
|
||||
Logger::setLogLevel( m_config.global.general.logLevel );
|
||||
|
||||
auto pFw = std::make_shared< Framework >();
|
||||
Network::HivePtr hive( new Network::Hive() );
|
||||
Network::addServerToHive< Network::GameConnection >( m_ip, m_port, hive, pFw );
|
||||
|
|
|
@ -3,7 +3,10 @@ cmake_policy(SET CMP0015 NEW)
|
|||
project(Tool_pcb_reader2)
|
||||
|
||||
file(GLOB SERVER_PUBLIC_INCLUDE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*")
|
||||
file(GLOB SERVER_SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}*.c*")
|
||||
file(GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
*.c*
|
||||
nav/*.c*
|
||||
nav/ext/*.c*)
|
||||
|
||||
add_executable(pcb_reader2 ${SERVER_PUBLIC_INCLUDE_FILES} ${SERVER_SOURCE_FILES})
|
||||
|
||||
|
|
|
@ -20,14 +20,14 @@ public:
|
|||
|
||||
void exportZone(const ExportedZone& zone, ExportFileType exportFileTypes)
|
||||
{
|
||||
if( exportFileTypes & ExportFileType::WavefrontObj )
|
||||
m_threadpool.queue( [zone, exportFileTypes]()
|
||||
{
|
||||
m_threadpool.queue( [zone](){ ObjExporter::exportZone( zone ); } );
|
||||
}
|
||||
if( exportFileTypes & ExportFileType::Navmesh )
|
||||
{
|
||||
m_threadpool.queue( [zone](){ NavmeshExporter::exportZone( zone ); } );
|
||||
}
|
||||
if( exportFileTypes & ExportFileType::WavefrontObj )
|
||||
ObjExporter::exportZone( zone );
|
||||
|
||||
if( exportFileTypes & ExportFileType::Navmesh )
|
||||
NavmeshExporter::exportZone( zone );
|
||||
} );
|
||||
}
|
||||
|
||||
void exportGroup( const std::string& zoneName, const ExportedGroup& group, ExportFileType exportFileTypes )
|
||||
|
@ -36,10 +36,10 @@ public:
|
|||
{
|
||||
m_threadpool.queue( [zoneName, group](){ ObjExporter::exportGroup( zoneName, group ); } );
|
||||
}
|
||||
if( exportFileTypes & ExportFileType::Navmesh )
|
||||
{
|
||||
m_threadpool.queue( [zoneName, group](){ NavmeshExporter::exportGroup( zoneName, group ); } );
|
||||
}
|
||||
// if( exportFileTypes & ExportFileType::Navmesh )
|
||||
// {
|
||||
// m_threadpool.queue( [zoneName, group](){ NavmeshExporter::exportGroup( zoneName, group ); } );
|
||||
// }
|
||||
}
|
||||
|
||||
void waitForTasks()
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
// garbage to ignore models
|
||||
bool noObj = false;
|
||||
|
||||
std::string gamePath( "C:\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\sqpack" );
|
||||
std::string gamePath( "/mnt/c/Program Files (x86)/Steam/steamapps/common/FINAL FANTASY XIV Online/game/sqpack" );
|
||||
std::unordered_map< uint16_t, std::string > zoneNameMap;
|
||||
std::map< std::string, std::string > exportedTeriMap;
|
||||
|
||||
|
@ -190,7 +190,7 @@ int main( int argc, char* argv[] )
|
|||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
printf( "Unable to initialise EXD! Usage: pcb_reader <teri> \"path/to/FINAL FANTASY XIV - A REALM REBORN/game/sqpack\" [--no-obj, --dump-all, --navmesh]" );
|
||||
printf( "Unable to initialise EXD!\n Usage: pcb_reader <teri> \"path/to/FINAL FANTASY XIV - A REALM REBORN/game/sqpack\" [--no-obj, --dump-all, --navmesh]\n" );
|
||||
return -1;
|
||||
}
|
||||
ExportMgr exportMgr;
|
||||
|
@ -497,13 +497,13 @@ int main( int argc, char* argv[] )
|
|||
exportMgr.exportZone( exportedZone, ExportFileType::Navmesh );
|
||||
|
||||
|
||||
printf( "Exported %s in %u seconds \n",
|
||||
printf( "Exported %s in %lu seconds \n",
|
||||
zoneName.c_str(),
|
||||
std::chrono::duration_cast< std::chrono::seconds >( std::chrono::high_resolution_clock::now() - entryStartTime ) );
|
||||
std::chrono::duration_cast< std::chrono::seconds >( std::chrono::high_resolution_clock::now() - entryStartTime ).count() );
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
printf( ( std::string( e.what() ) + "\n" ).c_str() );
|
||||
printf( "%s", ( std::string( e.what() ) + "\n" ).c_str() );
|
||||
printf( "Unable to extract collision data.\n" );
|
||||
printf( "Usage: pcb_reader2 territory \"path/to/game/sqpack/ffxiv\"\n" );
|
||||
}
|
||||
|
@ -511,14 +511,11 @@ int main( int argc, char* argv[] )
|
|||
exportMgr.waitForTasks();
|
||||
std::cout << "\n\n\n";
|
||||
|
||||
printf( "Finished all tasks in %u seconds\n",
|
||||
printf( "Finished all tasks in %lu seconds\n",
|
||||
std::chrono::duration_cast< std::chrono::seconds >( std::chrono::high_resolution_clock::now() - startTime ).count() );
|
||||
|
||||
getchar();
|
||||
delete eData;
|
||||
delete data1;
|
||||
|
||||
if( eData )
|
||||
delete eData;
|
||||
if( data1 )
|
||||
delete data1;
|
||||
return 0;
|
||||
}
|
||||
|
|
578
src/tools/pcb_reader/nav/TiledNavmeshGenerator.cpp
Normal file
578
src/tools/pcb_reader/nav/TiledNavmeshGenerator.cpp
Normal file
|
@ -0,0 +1,578 @@
|
|||
#include "TiledNavmeshGenerator.h"
|
||||
|
||||
#include <experimental/filesystem>
|
||||
#include <cstring>
|
||||
|
||||
#include <recastnavigation/Detour/Include/DetourNavMeshBuilder.h>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
|
||||
inline unsigned int nextPow2( uint32_t v )
|
||||
{
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline unsigned int ilog2( uint32_t v )
|
||||
{
|
||||
uint32_t r;
|
||||
uint32_t shift;
|
||||
r = (v > 0xffff) << 4; v >>= r;
|
||||
shift = (v > 0xff) << 3; v >>= shift; r |= shift;
|
||||
shift = (v > 0xf) << 2; v >>= shift; r |= shift;
|
||||
shift = (v > 0x3) << 1; v >>= shift; r |= shift;
|
||||
r |= (v >> 1);
|
||||
return r;
|
||||
}
|
||||
|
||||
bool TiledNavmeshGenerator::init( const std::string& path )
|
||||
{
|
||||
if( !fs::exists( path ) )
|
||||
throw std::runtime_error( "what" );
|
||||
|
||||
// ignore logging/bullshit/etc
|
||||
m_ctx = new rcContext( false );
|
||||
|
||||
printf( "[Navmesh] loading obj: %s\n", path.substr( path.find( "pcb_export" ) - 1 ).c_str() );
|
||||
|
||||
m_mesh = new rcMeshLoaderObj;
|
||||
assert( m_mesh );
|
||||
|
||||
if( !m_mesh->load( path ) )
|
||||
{
|
||||
printf( "[Navmesh] Failed to allocate rcMeshLoaderObj\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
rcCalcBounds( m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax );
|
||||
|
||||
m_chunkyMesh = new rcChunkyTriMesh;
|
||||
assert( m_chunkyMesh );
|
||||
|
||||
if( !rcCreateChunkyTriMesh( m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh ) )
|
||||
{
|
||||
printf( "[Navmesh] buildTiledNavigation: Failed to build chunky mesh.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: load some bullshit settings from exd
|
||||
|
||||
int gw = 0, gh = 0;
|
||||
rcCalcGridSize( m_meshBMin, m_meshBMax, m_cellSize, &gw, &gh );
|
||||
|
||||
auto ts = static_cast< uint32_t >( m_tileSize );
|
||||
const uint32_t tw = ( gw + ts - 1 ) / ts;
|
||||
const uint32_t th = ( gh + ts - 1 ) / ts;
|
||||
|
||||
printf( "[Navmesh] - Tiles %d x %d\n", tw, th );
|
||||
|
||||
int tileBits = rcMin( ( int ) ilog2( nextPow2( tw * th ) ), 14 );
|
||||
if( tileBits > 14 )
|
||||
tileBits = 14;
|
||||
int polyBits = 22 - tileBits;
|
||||
m_maxTiles = 1 << tileBits;
|
||||
m_maxPolysPerTile = 1 << polyBits;
|
||||
|
||||
printf( "[Navmesh] - %.1fK verts, %.1fK tris\n", m_mesh->getVertCount() / 1000.0f, m_mesh->getTriCount() / 1000.0f );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TiledNavmeshGenerator::~TiledNavmeshGenerator()
|
||||
{
|
||||
delete m_mesh;
|
||||
delete m_chunkyMesh;
|
||||
|
||||
delete m_ctx;
|
||||
|
||||
dtFreeNavMesh( m_navMesh );
|
||||
dtFreeNavMeshQuery( m_navQuery );
|
||||
}
|
||||
|
||||
void TiledNavmeshGenerator::saveNavmesh( const std::string& name )
|
||||
{
|
||||
assert( m_navMesh );
|
||||
|
||||
// fuck this gay earth
|
||||
auto mesh = const_cast< const dtNavMesh* >( m_navMesh );
|
||||
|
||||
auto dir = fs::current_path().string() + "/pcb_export/" + name + "/";
|
||||
auto fileName = dir + name + ".nav";
|
||||
|
||||
fs::create_directories( dir );
|
||||
|
||||
FILE* fp = fopen( fileName.c_str(), "wb" );
|
||||
if( !fp )
|
||||
return;
|
||||
|
||||
// Store header.
|
||||
NavMeshSetHeader header;
|
||||
header.magic = NAVMESHSET_MAGIC;
|
||||
header.version = NAVMESHSET_VERSION;
|
||||
header.numTiles = 0;
|
||||
for( int i = 0; i < mesh->getMaxTiles(); ++i )
|
||||
{
|
||||
auto tile = mesh->getTile( i );
|
||||
if( !tile || !tile->header || !tile->dataSize )
|
||||
continue;
|
||||
|
||||
header.numTiles++;
|
||||
}
|
||||
|
||||
memcpy( &header.params, mesh->getParams(), sizeof( dtNavMeshParams ) );
|
||||
fwrite( &header, sizeof( NavMeshSetHeader ), 1, fp );
|
||||
|
||||
// Store tiles.
|
||||
for( int i = 0; i < mesh->getMaxTiles(); ++i )
|
||||
{
|
||||
auto tile = mesh->getTile( i );
|
||||
if( !tile || !tile->header || !tile->dataSize )
|
||||
continue;
|
||||
|
||||
NavMeshTileHeader tileHeader;
|
||||
tileHeader.tileRef = mesh->getTileRef( tile );
|
||||
tileHeader.dataSize = tile->dataSize;
|
||||
fwrite( &tileHeader, sizeof( tileHeader ), 1, fp );
|
||||
|
||||
fwrite( tile->data, tile->dataSize, 1, fp );
|
||||
}
|
||||
|
||||
fclose( fp );
|
||||
|
||||
auto pos = fileName.find( "pcb_export" );
|
||||
fileName = fileName.substr( pos - 1 );
|
||||
|
||||
printf( "[Navmesh] Saved navmesh to '%s'\n", fileName.c_str() );
|
||||
}
|
||||
|
||||
bool TiledNavmeshGenerator::buildNavmesh()
|
||||
{
|
||||
assert( m_mesh );
|
||||
|
||||
m_navMesh = dtAllocNavMesh();
|
||||
if( !m_navMesh )
|
||||
{
|
||||
printf( "[Navmesh] buildTiledNavigation: Could not allocate navmesh.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
dtNavMeshParams params{};
|
||||
rcVcopy( params.orig, m_meshBMin );
|
||||
params.tileWidth = m_tileSize * m_cellSize;
|
||||
params.tileHeight = m_tileSize * m_cellSize;
|
||||
params.maxTiles = m_maxTiles;
|
||||
params.maxPolys = m_maxPolysPerTile;
|
||||
|
||||
dtStatus status;
|
||||
|
||||
status = m_navMesh->init( ¶ms );
|
||||
if( dtStatusFailed( status ) )
|
||||
{
|
||||
printf( "[Navmesh] buildTiledNavigation: Could not init navmesh.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
m_navQuery = dtAllocNavMeshQuery();
|
||||
assert( m_navQuery );
|
||||
|
||||
status = m_navQuery->init( m_navMesh, 2048 );
|
||||
if( dtStatusFailed( status ) )
|
||||
{
|
||||
printf( "[Navmesh] buildTiledNavigation: Could not init Detour navmesh query\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: duplicated from above, we can probably cache all this and only do it once
|
||||
int gw = 0, gh = 0;
|
||||
rcCalcGridSize( m_meshBMin, m_meshBMax, m_cellSize, &gw, &gh );
|
||||
auto ts = static_cast< uint32_t >( m_tileSize );
|
||||
const int tw = ( gw + ts - 1 ) / ts;
|
||||
const int th = ( gh + ts - 1 ) / ts;
|
||||
const float tcs = m_tileSize * m_cellSize;
|
||||
|
||||
for( int y = 0; y < th; y++ )
|
||||
{
|
||||
for( int x = 0; x < tw; x++ )
|
||||
{
|
||||
m_lastBuiltTileBmin[ 0 ] = m_meshBMin[ 0 ] + x * tcs;
|
||||
m_lastBuiltTileBmin[ 1 ] = m_meshBMin[ 1 ];
|
||||
m_lastBuiltTileBmin[ 2 ] = m_meshBMin[ 2 ] + y * tcs;
|
||||
|
||||
m_lastBuiltTileBmax[ 0 ] = m_meshBMin[ 0 ] + ( x + 1 ) * tcs;
|
||||
m_lastBuiltTileBmax[ 1 ] = m_meshBMax[ 1 ];
|
||||
m_lastBuiltTileBmax[ 2 ] = m_meshBMin[ 2 ] + ( y + 1 ) * tcs;
|
||||
|
||||
int dataSize = 0;
|
||||
|
||||
unsigned char* data = buildTileMesh( x, y, m_lastBuiltTileBmin, m_lastBuiltTileBmax, dataSize );
|
||||
if( data )
|
||||
{
|
||||
// Remove any previous data (navmesh owns and deletes the data).
|
||||
m_navMesh->removeTile( m_navMesh->getTileRefAt( x, y, 0 ), nullptr, nullptr );
|
||||
|
||||
// Let the navmesh own the data.
|
||||
status = m_navMesh->addTile( data, dataSize, DT_TILE_FREE_DATA, 0, nullptr );
|
||||
|
||||
if( dtStatusFailed( status ) )
|
||||
{
|
||||
dtFree( data );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
unsigned char* TiledNavmeshGenerator::buildTileMesh( const int tx, const int ty, const float* bmin, const float* bmax,
|
||||
int& dataSize )
|
||||
{
|
||||
const float* verts = m_mesh->getVerts();
|
||||
const int nverts = m_mesh->getVertCount();
|
||||
const int ntris = m_mesh->getTriCount();
|
||||
|
||||
// Init build configuration from GUI
|
||||
memset( &m_cfg, 0, sizeof( m_cfg ) );
|
||||
m_cfg.cs = m_cellSize;
|
||||
m_cfg.ch = m_cellHeight;
|
||||
m_cfg.walkableSlopeAngle = m_agentMaxSlope;
|
||||
m_cfg.walkableHeight = static_cast< int >( ceilf( m_agentHeight / m_cfg.ch ) );
|
||||
m_cfg.walkableClimb = static_cast< int >( floorf( m_agentMaxClimb / m_cfg.ch ) );
|
||||
m_cfg.walkableRadius = static_cast< int >( ceilf( m_agentRadius / m_cfg.cs ) );
|
||||
m_cfg.maxEdgeLen = static_cast< int >( m_edgeMaxLen / m_cellSize );
|
||||
m_cfg.maxSimplificationError = m_edgeMaxError;
|
||||
m_cfg.minRegionArea = static_cast< int >( rcSqr( m_regionMinSize ) ); // Note: area = size*size
|
||||
m_cfg.mergeRegionArea = static_cast< int >( rcSqr( m_regionMergeSize ) ); // Note: area = size*size
|
||||
m_cfg.maxVertsPerPoly = static_cast< int >( m_vertsPerPoly );
|
||||
m_cfg.tileSize = static_cast< int >( m_tileSize );
|
||||
m_cfg.borderSize = m_cfg.walkableRadius + 3; // Reserve enough padding.
|
||||
m_cfg.width = m_cfg.tileSize + m_cfg.borderSize * 2;
|
||||
m_cfg.height = m_cfg.tileSize + m_cfg.borderSize * 2;
|
||||
m_cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist;
|
||||
m_cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError;
|
||||
|
||||
// Expand the heighfield bounding box by border size to find the extents of geometry we need to build this tile.
|
||||
//
|
||||
// This is done in order to make sure that the navmesh tiles connect correctly at the borders,
|
||||
// and the obstacles close to the border work correctly with the dilation process.
|
||||
// No polygons (or contours) will be created on the border area.
|
||||
//
|
||||
// IMPORTANT!
|
||||
//
|
||||
// :''''''''':
|
||||
// : +-----+ :
|
||||
// : | | :
|
||||
// : | |<--- tile to build
|
||||
// : | | :
|
||||
// : +-----+ :<-- geometry needed
|
||||
// :.........:
|
||||
//
|
||||
// You should use this bounding box to query your input geometry.
|
||||
//
|
||||
// For example if you build a navmesh for terrain, and want the navmesh tiles to match the terrain tile size
|
||||
// you will need to pass in data from neighbour terrain tiles too! In a simple case, just pass in all the 8 neighbours,
|
||||
// or use the bounding box below to only pass in a sliver of each of the 8 neighbours.
|
||||
rcVcopy( m_cfg.bmin, bmin );
|
||||
rcVcopy( m_cfg.bmax, bmax );
|
||||
m_cfg.bmin[ 0 ] -= m_cfg.borderSize * m_cfg.cs;
|
||||
m_cfg.bmin[ 2 ] -= m_cfg.borderSize * m_cfg.cs;
|
||||
m_cfg.bmax[ 0 ] += m_cfg.borderSize * m_cfg.cs;
|
||||
m_cfg.bmax[ 2 ] += m_cfg.borderSize * m_cfg.cs;
|
||||
|
||||
m_solid = rcAllocHeightfield();
|
||||
if( !m_solid )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'solid'.\n" );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if( !rcCreateHeightfield( m_ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not create solid heightfield.\n" );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allocate array that can hold triangle flags.
|
||||
// If you have multiple meshes you need to process, allocate
|
||||
// and array which can hold the max number of triangles you need to process.
|
||||
m_triareas = new unsigned char[ m_chunkyMesh->maxTrisPerChunk ];
|
||||
if( !m_triareas )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'm_triareas' (%d).\n", m_chunkyMesh->maxTrisPerChunk );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
float tbmin[ 2 ];
|
||||
float tbmax[ 2 ];
|
||||
tbmin[ 0 ] = m_cfg.bmin[ 0 ];
|
||||
tbmin[ 1 ] = m_cfg.bmin[ 2 ];
|
||||
tbmax[ 0 ] = m_cfg.bmax[ 0 ];
|
||||
tbmax[ 1 ] = m_cfg.bmax[ 2 ];
|
||||
|
||||
int cid[512];// TODO: Make grow when returning too many items.
|
||||
const int ncid = rcGetChunksOverlappingRect( m_chunkyMesh, tbmin, tbmax, cid, 512 );
|
||||
|
||||
if( !ncid )
|
||||
return nullptr;
|
||||
|
||||
m_tileTriCount = 0;
|
||||
|
||||
for( int i = 0; i < ncid; ++i )
|
||||
{
|
||||
const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[ cid[ i ] ];
|
||||
const int* ctris = &m_chunkyMesh->tris[ node.i * 3 ];
|
||||
const int nctris = node.n;
|
||||
|
||||
m_tileTriCount += nctris;
|
||||
|
||||
memset( m_triareas, 0, nctris * sizeof( unsigned char ) );
|
||||
rcMarkWalkableTriangles( m_ctx, m_cfg.walkableSlopeAngle, verts, nverts, ctris, nctris, m_triareas );
|
||||
if( !rcRasterizeTriangles( m_ctx, verts, nverts, ctris, m_triareas, nctris, *m_solid, m_cfg.walkableClimb ) )
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
delete[] m_triareas;
|
||||
m_triareas = nullptr;
|
||||
|
||||
// Once all geometry is rasterized, we do initial pass of filtering to
|
||||
// remove unwanted overhangs caused by the conservative rasterization
|
||||
// as well as filter spans where the character cannot possibly stand.
|
||||
rcFilterLowHangingWalkableObstacles( m_ctx, m_cfg.walkableClimb, *m_solid );
|
||||
rcFilterLedgeSpans( m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid );
|
||||
rcFilterWalkableLowHeightSpans( m_ctx, m_cfg.walkableHeight, *m_solid );
|
||||
|
||||
// Compact the heightfield so that it is faster to handle from now on.
|
||||
// This will result more cache coherent data as well as the neighbours
|
||||
// between walkable cells will be calculated.
|
||||
m_chf = rcAllocCompactHeightfield();
|
||||
if( !m_chf )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'chf'." );
|
||||
return nullptr;
|
||||
}
|
||||
if( !rcBuildCompactHeightfield( m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not build compact data." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rcFreeHeightField( m_solid );
|
||||
m_solid = nullptr;
|
||||
|
||||
// Erode the walkable area by agent radius.
|
||||
if( !rcErodeWalkableArea( m_ctx, m_cfg.walkableRadius, *m_chf ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not erode." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// (Optional) Mark areas.
|
||||
// const ConvexVolume* vols = m_mesh->getConvexVolumes();
|
||||
// for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i)
|
||||
// rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf);
|
||||
|
||||
// Partition the heightfield so that we can use simple algorithm later to triangulate the walkable areas.
|
||||
// There are 3 martitioning methods, each with some pros and cons:
|
||||
// 1) Watershed partitioning
|
||||
// - the classic Recast partitioning
|
||||
// - creates the nicest tessellation
|
||||
// - usually slowest
|
||||
// - partitions the heightfield into nice regions without holes or overlaps
|
||||
// - the are some corner cases where this method creates produces holes and overlaps
|
||||
// - holes may appear when a small obstacles is close to large open area (triangulation can handle this)
|
||||
// - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail
|
||||
// * generally the best choice if you precompute the nacmesh, use this if you have large open areas
|
||||
// 2) Monotone partioning
|
||||
// - fastest
|
||||
// - partitions the heightfield into regions without holes and overlaps (guaranteed)
|
||||
// - creates long thin polygons, which sometimes causes paths with detours
|
||||
// * use this if you want fast navmesh generation
|
||||
// 3) Layer partitoining
|
||||
// - quite fast
|
||||
// - partitions the heighfield into non-overlapping regions
|
||||
// - relies on the triangulation code to cope with holes (thus slower than monotone partitioning)
|
||||
// - produces better triangles than monotone partitioning
|
||||
// - does not have the corner cases of watershed partitioning
|
||||
// - can be slow and create a bit ugly tessellation (still better than monotone)
|
||||
// if you have large open areas with small obstacles (not a problem if you use tiles)
|
||||
// * good choice to use for tiled navmesh with medium and small sized tiles
|
||||
|
||||
if( m_partitionType == SAMPLE_PARTITION_WATERSHED )
|
||||
{
|
||||
// Prepare for region partitioning, by calculating distance field along the walkable surface.
|
||||
if( !rcBuildDistanceField( m_ctx, *m_chf ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not build distance field." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
if( !rcBuildRegions( m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not build watershed regions." );
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else if( m_partitionType == SAMPLE_PARTITION_MONOTONE )
|
||||
{
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
// Monotone partitioning does not need distancefield.
|
||||
if( !rcBuildRegionsMonotone( m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not build monotone regions." );
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else // SAMPLE_PARTITION_LAYERS
|
||||
{
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
if( !rcBuildLayerRegions( m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not build layer regions." );
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Create contours.
|
||||
m_cset = rcAllocContourSet();
|
||||
if( !m_cset )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'cset'." );
|
||||
return nullptr;
|
||||
}
|
||||
if( !rcBuildContours( m_ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not create contours." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if( m_cset->nconts == 0 )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Build polygon navmesh from the contours.
|
||||
m_pmesh = rcAllocPolyMesh();
|
||||
if( !m_pmesh )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'pmesh'." );
|
||||
return nullptr;
|
||||
}
|
||||
if( !rcBuildPolyMesh( m_ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not triangulate contours." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Build detail mesh.
|
||||
m_dmesh = rcAllocPolyMeshDetail();
|
||||
if( !m_dmesh )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'dmesh'." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if( !rcBuildPolyMeshDetail( m_ctx, *m_pmesh, *m_chf,
|
||||
m_cfg.detailSampleDist, m_cfg.detailSampleMaxError,
|
||||
*m_dmesh ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could build polymesh detail." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rcFreeCompactHeightfield( m_chf );
|
||||
rcFreeContourSet( m_cset );
|
||||
m_chf = nullptr;
|
||||
m_cset = nullptr;
|
||||
|
||||
unsigned char* navData = 0;
|
||||
int navDataSize = 0;
|
||||
if( m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON )
|
||||
{
|
||||
if( m_pmesh->nverts >= 0xffff )
|
||||
{
|
||||
// The vertex indices are ushorts, and cannot point to more than 0xffff vertices.
|
||||
printf( "[Navmesh] Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Update poly flags from areas.
|
||||
for( int i = 0; i < m_pmesh->npolys; ++i )
|
||||
{
|
||||
if( m_pmesh->areas[ i ] == RC_WALKABLE_AREA )
|
||||
m_pmesh->areas[ i ] = SAMPLE_POLYAREA_GROUND;
|
||||
|
||||
if( m_pmesh->areas[ i ] == SAMPLE_POLYAREA_GROUND ||
|
||||
m_pmesh->areas[ i ] == SAMPLE_POLYAREA_GRASS ||
|
||||
m_pmesh->areas[ i ] == SAMPLE_POLYAREA_ROAD )
|
||||
{
|
||||
m_pmesh->flags[ i ] = SAMPLE_POLYFLAGS_WALK;
|
||||
}
|
||||
else if( m_pmesh->areas[ i ] == SAMPLE_POLYAREA_WATER )
|
||||
{
|
||||
m_pmesh->flags[ i ] = SAMPLE_POLYFLAGS_SWIM;
|
||||
}
|
||||
else if( m_pmesh->areas[ i ] == SAMPLE_POLYAREA_DOOR )
|
||||
{
|
||||
m_pmesh->flags[ i ] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR;
|
||||
}
|
||||
}
|
||||
|
||||
dtNavMeshCreateParams params;
|
||||
memset( ¶ms, 0, sizeof( params ) );
|
||||
params.verts = m_pmesh->verts;
|
||||
params.vertCount = m_pmesh->nverts;
|
||||
params.polys = m_pmesh->polys;
|
||||
params.polyAreas = m_pmesh->areas;
|
||||
params.polyFlags = m_pmesh->flags;
|
||||
params.polyCount = m_pmesh->npolys;
|
||||
params.nvp = m_pmesh->nvp;
|
||||
params.detailMeshes = m_dmesh->meshes;
|
||||
params.detailVerts = m_dmesh->verts;
|
||||
params.detailVertsCount = m_dmesh->nverts;
|
||||
params.detailTris = m_dmesh->tris;
|
||||
params.detailTriCount = m_dmesh->ntris;
|
||||
|
||||
params.offMeshConVerts = nullptr;
|
||||
params.offMeshConRad = nullptr;
|
||||
params.offMeshConDir = nullptr;
|
||||
params.offMeshConAreas = nullptr;
|
||||
params.offMeshConFlags = nullptr;
|
||||
params.offMeshConUserID = nullptr;
|
||||
params.offMeshConCount = 0;
|
||||
|
||||
params.walkableHeight = m_agentHeight;
|
||||
params.walkableRadius = m_agentRadius;
|
||||
params.walkableClimb = m_agentMaxClimb;
|
||||
params.tileX = tx;
|
||||
params.tileY = ty;
|
||||
params.tileLayer = 0;
|
||||
rcVcopy( params.bmin, m_pmesh->bmin );
|
||||
rcVcopy( params.bmax, m_pmesh->bmax );
|
||||
params.cs = m_cfg.cs;
|
||||
params.ch = m_cfg.ch;
|
||||
params.buildBvTree = true;
|
||||
|
||||
if( !dtCreateNavMeshData( ¶ms, &navData, &navDataSize ) )
|
||||
{
|
||||
printf( "[Navmesh] Could not build Detour navmesh." );
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
rcFreePolyMesh( m_pmesh );
|
||||
rcFreePolyMeshDetail( m_dmesh );
|
||||
m_pmesh = nullptr;
|
||||
m_dmesh = nullptr;
|
||||
|
||||
dataSize = navDataSize;
|
||||
return navData;
|
||||
}
|
124
src/tools/pcb_reader/nav/TiledNavmeshGenerator.h
Normal file
124
src/tools/pcb_reader/nav/TiledNavmeshGenerator.h
Normal file
|
@ -0,0 +1,124 @@
|
|||
#ifndef SAPPHIRE_TILEDNAVMESHGENERATOR_H
|
||||
#define SAPPHIRE_TILEDNAVMESHGENERATOR_H
|
||||
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
#include "ext/MeshLoaderObj.h"
|
||||
#include "ext/ChunkyTriMesh.h"
|
||||
|
||||
#include "recastnavigation/Detour/Include/DetourNavMesh.h"
|
||||
#include "recastnavigation/Detour/Include/DetourNavMeshQuery.h"
|
||||
#include "recastnavigation/Recast/Include/Recast.h"
|
||||
|
||||
class TiledNavmeshGenerator
|
||||
{
|
||||
public:
|
||||
enum SamplePartitionType
|
||||
{
|
||||
SAMPLE_PARTITION_WATERSHED,
|
||||
SAMPLE_PARTITION_MONOTONE,
|
||||
SAMPLE_PARTITION_LAYERS,
|
||||
};
|
||||
|
||||
enum SamplePolyAreas
|
||||
{
|
||||
SAMPLE_POLYAREA_GROUND,
|
||||
SAMPLE_POLYAREA_WATER,
|
||||
SAMPLE_POLYAREA_ROAD,
|
||||
SAMPLE_POLYAREA_DOOR,
|
||||
SAMPLE_POLYAREA_GRASS,
|
||||
SAMPLE_POLYAREA_JUMP,
|
||||
};
|
||||
enum SamplePolyFlags
|
||||
{
|
||||
SAMPLE_POLYFLAGS_WALK = 0x01, // Ability to walk (ground, grass, road)
|
||||
SAMPLE_POLYFLAGS_SWIM = 0x02, // Ability to swim (water).
|
||||
SAMPLE_POLYFLAGS_DOOR = 0x04, // Ability to move through doors.
|
||||
SAMPLE_POLYFLAGS_JUMP = 0x08, // Ability to jump.
|
||||
SAMPLE_POLYFLAGS_DISABLED = 0x10, // Disabled polygon
|
||||
SAMPLE_POLYFLAGS_ALL = 0xffff // All abilities.
|
||||
};
|
||||
|
||||
static const int NAVMESHSET_MAGIC = 'M'<<24 | 'S'<<16 | 'E'<<8 | 'T'; //'MSET';
|
||||
static const int NAVMESHSET_VERSION = 1;
|
||||
|
||||
struct NavMeshSetHeader
|
||||
{
|
||||
int magic;
|
||||
int version;
|
||||
int numTiles;
|
||||
dtNavMeshParams params;
|
||||
};
|
||||
|
||||
struct NavMeshTileHeader
|
||||
{
|
||||
dtTileRef tileRef;
|
||||
int dataSize;
|
||||
};
|
||||
|
||||
|
||||
TiledNavmeshGenerator() = default;
|
||||
~TiledNavmeshGenerator();
|
||||
|
||||
bool init( const std::string& path );
|
||||
unsigned char* buildTileMesh( const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize );
|
||||
bool buildNavmesh();
|
||||
void saveNavmesh( const std::string& name );
|
||||
|
||||
private:
|
||||
rcConfig m_cfg;
|
||||
|
||||
rcMeshLoaderObj* m_mesh;
|
||||
rcChunkyTriMesh* m_chunkyMesh;
|
||||
|
||||
rcContext* m_ctx;
|
||||
dtNavMesh* m_navMesh;
|
||||
dtNavMeshQuery* m_navQuery;
|
||||
rcHeightfield* m_solid;
|
||||
rcContourSet* m_cset;
|
||||
rcPolyMesh* m_pmesh;
|
||||
rcPolyMeshDetail* m_dmesh;
|
||||
|
||||
rcCompactHeightfield* m_chf;
|
||||
|
||||
unsigned char* m_triareas;
|
||||
|
||||
int m_maxTiles = 0;
|
||||
int m_maxPolysPerTile = 0;
|
||||
|
||||
int m_tileTriCount = 0;
|
||||
|
||||
int m_partitionType = SamplePartitionType::SAMPLE_PARTITION_WATERSHED;
|
||||
|
||||
float m_meshBMin[ 3 ];
|
||||
float m_meshBMax[ 3 ];
|
||||
|
||||
float m_lastBuiltTileBmin[ 3 ];
|
||||
float m_lastBuiltTileBmax[ 3 ];
|
||||
|
||||
// options
|
||||
float m_tileSize = 160.f;
|
||||
float m_cellSize = 0.2f;
|
||||
float m_cellHeight = 0.2f;
|
||||
|
||||
float m_agentMaxSlope = 56.f;
|
||||
float m_agentHeight = 2.f;
|
||||
float m_agentMaxClimb = 0.6f;
|
||||
float m_agentRadius = 0.5f;
|
||||
|
||||
float m_regionMinSize = 8.f;
|
||||
float m_regionMergeSize = 20.f;
|
||||
|
||||
float m_edgeMaxLen = 12.f;
|
||||
float m_edgeMaxError = 1.4f;
|
||||
float m_vertsPerPoly = 6.f;
|
||||
|
||||
float m_detailSampleDist = 6.f;
|
||||
float m_detailSampleMaxError = 1.f;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //SAPPHIRE_TILEDNAVMESHGENERATOR_H
|
331
src/tools/pcb_reader/nav/ext/ChunkyTriMesh.cpp
Normal file
331
src/tools/pcb_reader/nav/ext/ChunkyTriMesh.cpp
Normal file
|
@ -0,0 +1,331 @@
|
|||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#include "ChunkyTriMesh.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
struct BoundsItem
|
||||
{
|
||||
float bmin[ 2 ];
|
||||
float bmax[ 2 ];
|
||||
int i;
|
||||
};
|
||||
|
||||
static int compareItemX( const void* va, const void* vb )
|
||||
{
|
||||
const BoundsItem* a = ( const BoundsItem* ) va;
|
||||
const BoundsItem* b = ( const BoundsItem* ) vb;
|
||||
if( a->bmin[ 0 ] < b->bmin[ 0 ] )
|
||||
return -1;
|
||||
if( a->bmin[ 0 ] > b->bmin[ 0 ] )
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int compareItemY( const void* va, const void* vb )
|
||||
{
|
||||
const BoundsItem* a = ( const BoundsItem* ) va;
|
||||
const BoundsItem* b = ( const BoundsItem* ) vb;
|
||||
if( a->bmin[ 1 ] < b->bmin[ 1 ] )
|
||||
return -1;
|
||||
if( a->bmin[ 1 ] > b->bmin[ 1 ] )
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void calcExtends( const BoundsItem* items, const int /*nitems*/,
|
||||
const int imin, const int imax,
|
||||
float* bmin, float* bmax )
|
||||
{
|
||||
bmin[ 0 ] = items[ imin ].bmin[ 0 ];
|
||||
bmin[ 1 ] = items[ imin ].bmin[ 1 ];
|
||||
|
||||
bmax[ 0 ] = items[ imin ].bmax[ 0 ];
|
||||
bmax[ 1 ] = items[ imin ].bmax[ 1 ];
|
||||
|
||||
for( int i = imin + 1; i < imax; ++i )
|
||||
{
|
||||
const BoundsItem& it = items[ i ];
|
||||
if( it.bmin[ 0 ] < bmin[ 0 ] )
|
||||
bmin[ 0 ] = it.bmin[ 0 ];
|
||||
if( it.bmin[ 1 ] < bmin[ 1 ] )
|
||||
bmin[ 1 ] = it.bmin[ 1 ];
|
||||
|
||||
if( it.bmax[ 0 ] > bmax[ 0 ] )
|
||||
bmax[ 0 ] = it.bmax[ 0 ];
|
||||
if( it.bmax[ 1 ] > bmax[ 1 ] )
|
||||
bmax[ 1 ] = it.bmax[ 1 ];
|
||||
}
|
||||
}
|
||||
|
||||
inline int longestAxis( float x, float y )
|
||||
{
|
||||
return y > x ? 1 : 0;
|
||||
}
|
||||
|
||||
static void subdivide( BoundsItem* items, int nitems, int imin, int imax, int trisPerChunk,
|
||||
int& curNode, rcChunkyTriMeshNode* nodes, const int maxNodes,
|
||||
int& curTri, int* outTris, const int* inTris )
|
||||
{
|
||||
int inum = imax - imin;
|
||||
int icur = curNode;
|
||||
|
||||
if( curNode > maxNodes )
|
||||
return;
|
||||
|
||||
rcChunkyTriMeshNode& node = nodes[ curNode++ ];
|
||||
|
||||
if( inum <= trisPerChunk )
|
||||
{
|
||||
// Leaf
|
||||
calcExtends( items, nitems, imin, imax, node.bmin, node.bmax );
|
||||
|
||||
// Copy triangles.
|
||||
node.i = curTri;
|
||||
node.n = inum;
|
||||
|
||||
for( int i = imin; i < imax; ++i )
|
||||
{
|
||||
const int* src = &inTris[ items[ i ].i * 3 ];
|
||||
int* dst = &outTris[ curTri * 3 ];
|
||||
curTri++;
|
||||
dst[ 0 ] = src[ 0 ];
|
||||
dst[ 1 ] = src[ 1 ];
|
||||
dst[ 2 ] = src[ 2 ];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split
|
||||
calcExtends( items, nitems, imin, imax, node.bmin, node.bmax );
|
||||
|
||||
int axis = longestAxis( node.bmax[ 0 ] - node.bmin[ 0 ],
|
||||
node.bmax[ 1 ] - node.bmin[ 1 ] );
|
||||
|
||||
if( axis == 0 )
|
||||
{
|
||||
// Sort along x-axis
|
||||
qsort( items + imin, static_cast<size_t>(inum), sizeof( BoundsItem ), compareItemX );
|
||||
}
|
||||
else if( axis == 1 )
|
||||
{
|
||||
// Sort along y-axis
|
||||
qsort( items + imin, static_cast<size_t>(inum), sizeof( BoundsItem ), compareItemY );
|
||||
}
|
||||
|
||||
int isplit = imin + inum / 2;
|
||||
|
||||
// Left
|
||||
subdivide( items, nitems, imin, isplit, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris );
|
||||
// Right
|
||||
subdivide( items, nitems, isplit, imax, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris );
|
||||
|
||||
int iescape = curNode - icur;
|
||||
// Negative index means escape.
|
||||
node.i = -iescape;
|
||||
}
|
||||
}
|
||||
|
||||
bool rcCreateChunkyTriMesh( const float* verts, const int* tris, int ntris,
|
||||
int trisPerChunk, rcChunkyTriMesh* cm )
|
||||
{
|
||||
int nchunks = ( ntris + trisPerChunk - 1 ) / trisPerChunk;
|
||||
|
||||
cm->nodes = new rcChunkyTriMeshNode[nchunks * 4];
|
||||
if( !cm->nodes )
|
||||
return false;
|
||||
|
||||
cm->tris = new int[ntris * 3];
|
||||
if( !cm->tris )
|
||||
return false;
|
||||
|
||||
cm->ntris = ntris;
|
||||
|
||||
// Build tree
|
||||
BoundsItem* items = new BoundsItem[ntris];
|
||||
if( !items )
|
||||
return false;
|
||||
|
||||
for( int i = 0; i < ntris; i++ )
|
||||
{
|
||||
const int* t = &tris[ i * 3 ];
|
||||
BoundsItem& it = items[ i ];
|
||||
it.i = i;
|
||||
// Calc triangle XZ bounds.
|
||||
it.bmin[ 0 ] = it.bmax[ 0 ] = verts[ t[ 0 ] * 3 + 0 ];
|
||||
it.bmin[ 1 ] = it.bmax[ 1 ] = verts[ t[ 0 ] * 3 + 2 ];
|
||||
for( int j = 1; j < 3; ++j )
|
||||
{
|
||||
const float* v = &verts[ t[ j ] * 3 ];
|
||||
if( v[ 0 ] < it.bmin[ 0 ] )
|
||||
it.bmin[ 0 ] = v[ 0 ];
|
||||
if( v[ 2 ] < it.bmin[ 1 ] )
|
||||
it.bmin[ 1 ] = v[ 2 ];
|
||||
|
||||
if( v[ 0 ] > it.bmax[ 0 ] )
|
||||
it.bmax[ 0 ] = v[ 0 ];
|
||||
if( v[ 2 ] > it.bmax[ 1 ] )
|
||||
it.bmax[ 1 ] = v[ 2 ];
|
||||
}
|
||||
}
|
||||
|
||||
int curTri = 0;
|
||||
int curNode = 0;
|
||||
subdivide( items, ntris, 0, ntris, trisPerChunk, curNode, cm->nodes, nchunks * 4, curTri, cm->tris, tris );
|
||||
|
||||
delete[] items;
|
||||
|
||||
cm->nnodes = curNode;
|
||||
|
||||
// Calc max tris per node.
|
||||
cm->maxTrisPerChunk = 0;
|
||||
for( int i = 0; i < cm->nnodes; ++i )
|
||||
{
|
||||
rcChunkyTriMeshNode& node = cm->nodes[ i ];
|
||||
const bool isLeaf = node.i >= 0;
|
||||
if( !isLeaf )
|
||||
continue;
|
||||
if( node.n > cm->maxTrisPerChunk )
|
||||
cm->maxTrisPerChunk = node.n;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
inline bool checkOverlapRect( const float amin[2], const float amax[2],
|
||||
const float bmin[2], const float bmax[2] )
|
||||
{
|
||||
bool overlap = true;
|
||||
overlap = ( amin[ 0 ] > bmax[ 0 ] || amax[ 0 ] < bmin[ 0 ] ) ? false : overlap;
|
||||
overlap = ( amin[ 1 ] > bmax[ 1 ] || amax[ 1 ] < bmin[ 1 ] ) ? false : overlap;
|
||||
return overlap;
|
||||
}
|
||||
|
||||
int rcGetChunksOverlappingRect( const rcChunkyTriMesh* cm,
|
||||
float bmin[2], float bmax[2],
|
||||
int* ids, const int maxIds )
|
||||
{
|
||||
// Traverse tree
|
||||
int i = 0;
|
||||
int n = 0;
|
||||
while( i < cm->nnodes )
|
||||
{
|
||||
const rcChunkyTriMeshNode* node = &cm->nodes[ i ];
|
||||
const bool overlap = checkOverlapRect( bmin, bmax, node->bmin, node->bmax );
|
||||
const bool isLeafNode = node->i >= 0;
|
||||
|
||||
if( isLeafNode && overlap )
|
||||
{
|
||||
if( n < maxIds )
|
||||
{
|
||||
ids[ n ] = i;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
if( overlap || isLeafNode )
|
||||
i++;
|
||||
else
|
||||
{
|
||||
const int escapeIndex = -node->i;
|
||||
i += escapeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
static bool checkOverlapSegment( const float p[2], const float q[2],
|
||||
const float bmin[2], const float bmax[2] )
|
||||
{
|
||||
static const float EPSILON = 1e-6f;
|
||||
|
||||
float tmin = 0;
|
||||
float tmax = 1;
|
||||
float d[2];
|
||||
d[ 0 ] = q[ 0 ] - p[ 0 ];
|
||||
d[ 1 ] = q[ 1 ] - p[ 1 ];
|
||||
|
||||
for( int i = 0; i < 2; i++ )
|
||||
{
|
||||
if( fabsf( d[ i ] ) < EPSILON )
|
||||
{
|
||||
// Ray is parallel to slab. No hit if origin not within slab
|
||||
if( p[ i ] < bmin[ i ] || p[ i ] > bmax[ i ] )
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compute intersection t value of ray with near and far plane of slab
|
||||
float ood = 1.0f / d[ i ];
|
||||
float t1 = ( bmin[ i ] - p[ i ] ) * ood;
|
||||
float t2 = ( bmax[ i ] - p[ i ] ) * ood;
|
||||
if( t1 > t2 )
|
||||
{
|
||||
float tmp = t1;
|
||||
t1 = t2;
|
||||
t2 = tmp;
|
||||
}
|
||||
if( t1 > tmin )
|
||||
tmin = t1;
|
||||
if( t2 < tmax )
|
||||
tmax = t2;
|
||||
if( tmin > tmax )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int rcGetChunksOverlappingSegment( const rcChunkyTriMesh* cm,
|
||||
float p[2], float q[2],
|
||||
int* ids, const int maxIds )
|
||||
{
|
||||
// Traverse tree
|
||||
int i = 0;
|
||||
int n = 0;
|
||||
while( i < cm->nnodes )
|
||||
{
|
||||
const rcChunkyTriMeshNode* node = &cm->nodes[ i ];
|
||||
const bool overlap = checkOverlapSegment( p, q, node->bmin, node->bmax );
|
||||
const bool isLeafNode = node->i >= 0;
|
||||
|
||||
if( isLeafNode && overlap )
|
||||
{
|
||||
if( n < maxIds )
|
||||
{
|
||||
ids[ n ] = i;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
if( overlap || isLeafNode )
|
||||
i++;
|
||||
else
|
||||
{
|
||||
const int escapeIndex = -node->i;
|
||||
i += escapeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
68
src/tools/pcb_reader/nav/ext/ChunkyTriMesh.h
Normal file
68
src/tools/pcb_reader/nav/ext/ChunkyTriMesh.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#ifndef CHUNKYTRIMESH_H
|
||||
#define CHUNKYTRIMESH_H
|
||||
|
||||
struct rcChunkyTriMeshNode
|
||||
{
|
||||
float bmin[ 2 ];
|
||||
float bmax[ 2 ];
|
||||
int i;
|
||||
int n;
|
||||
};
|
||||
|
||||
struct rcChunkyTriMesh
|
||||
{
|
||||
inline rcChunkyTriMesh() :
|
||||
nodes( 0 ), nnodes( 0 ), tris( 0 ), ntris( 0 ), maxTrisPerChunk( 0 )
|
||||
{
|
||||
};
|
||||
|
||||
inline ~rcChunkyTriMesh()
|
||||
{
|
||||
delete[] nodes;
|
||||
delete[] tris;
|
||||
}
|
||||
|
||||
rcChunkyTriMeshNode* nodes;
|
||||
int nnodes;
|
||||
int* tris;
|
||||
int ntris;
|
||||
int maxTrisPerChunk;
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
rcChunkyTriMesh( const rcChunkyTriMesh& );
|
||||
|
||||
rcChunkyTriMesh& operator=( const rcChunkyTriMesh& );
|
||||
};
|
||||
|
||||
/// Creates partitioned triangle mesh (AABB tree),
|
||||
/// where each node contains at max trisPerChunk triangles.
|
||||
bool rcCreateChunkyTriMesh( const float* verts, const int* tris, int ntris,
|
||||
int trisPerChunk, rcChunkyTriMesh* cm );
|
||||
|
||||
/// Returns the chunk indices which overlap the input rectable.
|
||||
int rcGetChunksOverlappingRect( const rcChunkyTriMesh* cm, float bmin[2], float bmax[2], int* ids, const int maxIds );
|
||||
|
||||
/// Returns the chunk indices which overlap the input segment.
|
||||
int rcGetChunksOverlappingSegment( const rcChunkyTriMesh* cm, float p[2], float q[2], int* ids, const int maxIds );
|
||||
|
||||
|
||||
#endif // CHUNKYTRIMESH_H
|
252
src/tools/pcb_reader/nav/ext/MeshLoaderObj.cpp
Normal file
252
src/tools/pcb_reader/nav/ext/MeshLoaderObj.cpp
Normal file
|
@ -0,0 +1,252 @@
|
|||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#include "MeshLoaderObj.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <cstring>
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include <math.h>
|
||||
|
||||
rcMeshLoaderObj::rcMeshLoaderObj() :
|
||||
m_scale( 1.0f ),
|
||||
m_verts( 0 ),
|
||||
m_tris( 0 ),
|
||||
m_normals( 0 ),
|
||||
m_vertCount( 0 ),
|
||||
m_triCount( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
rcMeshLoaderObj::~rcMeshLoaderObj()
|
||||
{
|
||||
delete[] m_verts;
|
||||
delete[] m_normals;
|
||||
delete[] m_tris;
|
||||
}
|
||||
|
||||
void rcMeshLoaderObj::addVertex( float x, float y, float z, int& cap )
|
||||
{
|
||||
if( m_vertCount + 1 > cap )
|
||||
{
|
||||
cap = !cap ? 8 : cap * 2;
|
||||
float* nv = new float[cap * 3];
|
||||
if( m_vertCount )
|
||||
memcpy( nv, m_verts, m_vertCount * 3 * sizeof( float ) );
|
||||
delete[] m_verts;
|
||||
m_verts = nv;
|
||||
}
|
||||
float* dst = &m_verts[ m_vertCount * 3 ];
|
||||
*dst++ = x * m_scale;
|
||||
*dst++ = y * m_scale;
|
||||
*dst++ = z * m_scale;
|
||||
m_vertCount++;
|
||||
}
|
||||
|
||||
void rcMeshLoaderObj::addTriangle( int a, int b, int c, int& cap )
|
||||
{
|
||||
if( m_triCount + 1 > cap )
|
||||
{
|
||||
cap = !cap ? 8 : cap * 2;
|
||||
int* nv = new int[cap * 3];
|
||||
if( m_triCount )
|
||||
memcpy( nv, m_tris, m_triCount * 3 * sizeof( int ) );
|
||||
delete[] m_tris;
|
||||
m_tris = nv;
|
||||
}
|
||||
int* dst = &m_tris[ m_triCount * 3 ];
|
||||
*dst++ = a;
|
||||
*dst++ = b;
|
||||
*dst++ = c;
|
||||
m_triCount++;
|
||||
}
|
||||
|
||||
static char* parseRow( char* buf, char* bufEnd, char* row, int len )
|
||||
{
|
||||
bool start = true;
|
||||
bool done = false;
|
||||
int n = 0;
|
||||
while( !done && buf < bufEnd )
|
||||
{
|
||||
char c = *buf;
|
||||
buf++;
|
||||
// multirow
|
||||
switch( c )
|
||||
{
|
||||
case '\\':
|
||||
break;
|
||||
case '\n':
|
||||
if( start )
|
||||
break;
|
||||
done = true;
|
||||
break;
|
||||
case '\r':
|
||||
break;
|
||||
case '\t':
|
||||
case ' ':
|
||||
if( start )
|
||||
break;
|
||||
// else falls through
|
||||
default:
|
||||
start = false;
|
||||
row[ n++ ] = c;
|
||||
if( n >= len - 1 )
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
row[ n ] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int parseFace( char* row, int* data, int n, int vcnt )
|
||||
{
|
||||
int j = 0;
|
||||
while( *row != '\0' )
|
||||
{
|
||||
// Skip initial white space
|
||||
while( *row != '\0' && ( *row == ' ' || *row == '\t' ) )
|
||||
row++;
|
||||
char* s = row;
|
||||
// Find vertex delimiter and terminated the string there for conversion.
|
||||
while( *row != '\0' && *row != ' ' && *row != '\t' )
|
||||
{
|
||||
if( *row == '/' )
|
||||
*row = '\0';
|
||||
row++;
|
||||
}
|
||||
if( *s == '\0' )
|
||||
continue;
|
||||
int vi = atoi( s );
|
||||
data[ j++ ] = vi < 0 ? vi + vcnt : vi - 1;
|
||||
if( j >= n )
|
||||
return j;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
bool rcMeshLoaderObj::load( const std::string& filename )
|
||||
{
|
||||
char* buf = 0;
|
||||
FILE* fp = fopen( filename.c_str(), "rb" );
|
||||
if( !fp )
|
||||
return false;
|
||||
if( fseek( fp, 0, SEEK_END ) != 0 )
|
||||
{
|
||||
fclose( fp );
|
||||
return false;
|
||||
}
|
||||
long bufSize = ftell( fp );
|
||||
if( bufSize < 0 )
|
||||
{
|
||||
fclose( fp );
|
||||
return false;
|
||||
}
|
||||
if( fseek( fp, 0, SEEK_SET ) != 0 )
|
||||
{
|
||||
fclose( fp );
|
||||
return false;
|
||||
}
|
||||
buf = new char[bufSize];
|
||||
if( !buf )
|
||||
{
|
||||
fclose( fp );
|
||||
return false;
|
||||
}
|
||||
size_t readLen = fread( buf, bufSize, 1, fp );
|
||||
fclose( fp );
|
||||
|
||||
if( readLen != 1 )
|
||||
{
|
||||
delete[] buf;
|
||||
return false;
|
||||
}
|
||||
|
||||
char* src = buf;
|
||||
char* srcEnd = buf + bufSize;
|
||||
char row[512];
|
||||
int face[32];
|
||||
float x, y, z;
|
||||
int nv;
|
||||
int vcap = 0;
|
||||
int tcap = 0;
|
||||
|
||||
while( src < srcEnd )
|
||||
{
|
||||
// Parse one row
|
||||
row[ 0 ] = '\0';
|
||||
src = parseRow( src, srcEnd, row, sizeof( row ) / sizeof( char ) );
|
||||
// Skip comments
|
||||
if( row[ 0 ] == '#' )
|
||||
continue;
|
||||
if( row[ 0 ] == 'v' && row[ 1 ] != 'n' && row[ 1 ] != 't' )
|
||||
{
|
||||
// Vertex pos
|
||||
sscanf( row + 1, "%f %f %f", &x, &y, &z );
|
||||
addVertex( x, y, z, vcap );
|
||||
}
|
||||
if( row[ 0 ] == 'f' )
|
||||
{
|
||||
// Faces
|
||||
nv = parseFace( row + 1, face, 32, m_vertCount );
|
||||
for( int i = 2; i < nv; ++i )
|
||||
{
|
||||
const int a = face[ 0 ];
|
||||
const int b = face[ i - 1 ];
|
||||
const int c = face[ i ];
|
||||
if( a < 0 || a >= m_vertCount || b < 0 || b >= m_vertCount || c < 0 || c >= m_vertCount )
|
||||
continue;
|
||||
addTriangle( a, b, c, tcap );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete[] buf;
|
||||
|
||||
// Calculate normals.
|
||||
m_normals = new float[m_triCount * 3];
|
||||
for( int i = 0; i < m_triCount * 3; i += 3 )
|
||||
{
|
||||
const float* v0 = &m_verts[ m_tris[ i ] * 3 ];
|
||||
const float* v1 = &m_verts[ m_tris[ i + 1 ] * 3 ];
|
||||
const float* v2 = &m_verts[ m_tris[ i + 2 ] * 3 ];
|
||||
float e0[3], e1[3];
|
||||
for( int j = 0; j < 3; ++j )
|
||||
{
|
||||
e0[ j ] = v1[ j ] - v0[ j ];
|
||||
e1[ j ] = v2[ j ] - v0[ j ];
|
||||
}
|
||||
float* n = &m_normals[ i ];
|
||||
n[ 0 ] = e0[ 1 ] * e1[ 2 ] - e0[ 2 ] * e1[ 1 ];
|
||||
n[ 1 ] = e0[ 2 ] * e1[ 0 ] - e0[ 0 ] * e1[ 2 ];
|
||||
n[ 2 ] = e0[ 0 ] * e1[ 1 ] - e0[ 1 ] * e1[ 0 ];
|
||||
float d = sqrtf( n[ 0 ] * n[ 0 ] + n[ 1 ] * n[ 1 ] + n[ 2 ] * n[ 2 ] );
|
||||
if( d > 0 )
|
||||
{
|
||||
d = 1.0f / d;
|
||||
n[ 0 ] *= d;
|
||||
n[ 1 ] *= d;
|
||||
n[ 2 ] *= d;
|
||||
}
|
||||
}
|
||||
|
||||
m_filename = filename;
|
||||
return true;
|
||||
}
|
82
src/tools/pcb_reader/nav/ext/MeshLoaderObj.h
Normal file
82
src/tools/pcb_reader/nav/ext/MeshLoaderObj.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#ifndef MESHLOADER_OBJ
|
||||
#define MESHLOADER_OBJ
|
||||
|
||||
#include <string>
|
||||
|
||||
class rcMeshLoaderObj
|
||||
{
|
||||
public:
|
||||
rcMeshLoaderObj();
|
||||
|
||||
~rcMeshLoaderObj();
|
||||
|
||||
bool load( const std::string& fileName );
|
||||
|
||||
const float* getVerts() const
|
||||
{
|
||||
return m_verts;
|
||||
}
|
||||
|
||||
const float* getNormals() const
|
||||
{
|
||||
return m_normals;
|
||||
}
|
||||
|
||||
const int* getTris() const
|
||||
{
|
||||
return m_tris;
|
||||
}
|
||||
|
||||
int getVertCount() const
|
||||
{
|
||||
return m_vertCount;
|
||||
}
|
||||
|
||||
int getTriCount() const
|
||||
{
|
||||
return m_triCount;
|
||||
}
|
||||
|
||||
const std::string& getFileName() const
|
||||
{
|
||||
return m_filename;
|
||||
}
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
rcMeshLoaderObj( const rcMeshLoaderObj& );
|
||||
|
||||
rcMeshLoaderObj& operator=( const rcMeshLoaderObj& );
|
||||
|
||||
void addVertex( float x, float y, float z, int& cap );
|
||||
|
||||
void addTriangle( int a, int b, int c, int& cap );
|
||||
|
||||
std::string m_filename;
|
||||
float m_scale;
|
||||
float* m_verts;
|
||||
int* m_tris;
|
||||
float* m_normals;
|
||||
int m_vertCount;
|
||||
int m_triCount;
|
||||
};
|
||||
|
||||
#endif // MESHLOADER_OBJ
|
|
@ -9,17 +9,12 @@
|
|||
#include <chrono>
|
||||
|
||||
#include "exporter.h"
|
||||
#include "obj_exporter.h"
|
||||
#include "nav/TiledNavmeshGenerator.h"
|
||||
|
||||
//*
|
||||
#include <recastnavigation/Recast/Include/Recast.h>
|
||||
#include <recastnavigation/Recast/Include/RecastAlloc.h>
|
||||
#include <recastnavigation/Detour/Include/DetourNavMesh.h>
|
||||
#include <recastnavigation/Detour/Include/DetourNavMeshBuilder.h>
|
||||
#include <recastnavigation/DetourTileCache/Include/DetourTileCache.h>
|
||||
#include <recastnavigation/DetourTileCache/Include/DetourTileCacheBuilder.h>
|
||||
#include <experimental/filesystem>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
//*/
|
||||
class NavmeshExporter
|
||||
{
|
||||
public:
|
||||
|
@ -27,368 +22,34 @@ public:
|
|||
{
|
||||
static std::string currPath = std::experimental::filesystem::current_path().string();
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
auto dir = fs::current_path().string() + "/pcb_export/" + zone.name + "/";
|
||||
auto fileName = dir + zone.name + ".obj";
|
||||
|
||||
TiledNavmeshGenerator gen;
|
||||
|
||||
if( !gen.init( fileName ) )
|
||||
{
|
||||
printf( "[Navmesh] failed to init TiledNavmeshGenerator for file '%s'\n", fileName.c_str() );
|
||||
return;
|
||||
}
|
||||
|
||||
if( !gen.buildNavmesh() )
|
||||
{
|
||||
printf( "[Navmesh] Failed to build navmesh for '%s'\n", zone.name.c_str() );
|
||||
return;
|
||||
}
|
||||
|
||||
gen.saveNavmesh( zone.name );
|
||||
|
||||
auto fileName = currPath + "/" + zone.name + "/" + zone.name + ".nav";
|
||||
exportZoneCommandline( zone, deleteObj );
|
||||
//for( auto& group : zone.groups )
|
||||
//buildTileMesh(group.second, 0, 0);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
printf( "[Navmesh] Finished exporting %s in %u ms\n",
|
||||
printf( "[Navmesh] Finished exporting %s in %lu ms\n",
|
||||
fileName.c_str(),
|
||||
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||
}
|
||||
|
||||
static void exportGroup( const std::string& zoneName, const ExportedGroup& group, bool deleteObj = false )
|
||||
static void exportGroup( const std::string& zoneName, const ExportedGroup& group )
|
||||
{
|
||||
static std::string currPath = std::experimental::filesystem::current_path().string();
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto fileName = currPath + "/" + zoneName + "/" + zoneName + "_" + group.name + ".obj";
|
||||
exportGroupCommandline( zoneName, group );
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
printf( "[Navmesh] Finished exporting %s in %u ms\n",
|
||||
fileName.c_str(),
|
||||
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||
}
|
||||
private:
|
||||
static void exportZoneCommandline( const ExportedZone& zone, bool deleteObj = false )
|
||||
{
|
||||
static std::string currPath = "\"\"" + std::experimental::filesystem::current_path().string();
|
||||
|
||||
auto fileName = ObjExporter::exportZone( zone );
|
||||
if( fileName.empty() )
|
||||
{
|
||||
printf( "Unable to export navmesh for %s", zone.name.c_str() );
|
||||
return;
|
||||
}
|
||||
static std::string recastDemoLaunch = std::string( "RecastDemo.exe --type tileMesh --obj ");
|
||||
std::string actualStr( recastDemoLaunch + "\"" + fileName + "\"" );
|
||||
system( actualStr.c_str() );
|
||||
}
|
||||
|
||||
static void exportGroupCommandline( const std::string& zoneName, const ExportedGroup& group, bool deleteObj = false )
|
||||
{
|
||||
static std::string currPath = "\"\"" + std::experimental::filesystem::current_path().string();
|
||||
|
||||
auto fileName = ObjExporter::exportGroup( zoneName, group );
|
||||
if( fileName.empty() )
|
||||
{
|
||||
printf( "Unable to export navmesh for %s", zoneName.c_str() );
|
||||
return;
|
||||
}
|
||||
static std::string recastDemoLaunch = std::string( "RecastDemo.exe --type tileMesh --obj ");
|
||||
std::string actualStr( recastDemoLaunch + "\"" + fileName + "\"" );
|
||||
system( actualStr.c_str() );
|
||||
}
|
||||
/*/
|
||||
static unsigned char* buildTileMesh( const ExportedGroup& group, int tx, int ty )
|
||||
{
|
||||
unsigned char* navData;
|
||||
rcConfig cfg;
|
||||
cfg.ch = 0.2f;
|
||||
cfg.cs = 0.2f;
|
||||
cfg.walkableHeight = 2.f;
|
||||
cfg.walkableRadius = 0.5;
|
||||
cfg.walkableClimb = 0.6;
|
||||
cfg.walkableSlopeAngle = 58.f;
|
||||
cfg.minRegionArea = 8.0f;
|
||||
cfg.mergeRegionArea = 20.f;
|
||||
cfg.maxEdgeLen = 12.f;
|
||||
cfg.maxSimplificationError = 1.4f;
|
||||
cfg.maxVertsPerPoly = 6.f;
|
||||
cfg.detailSampleDist = 6.f;
|
||||
cfg.detailSampleMaxError = 1.f;
|
||||
cfg.tileSize = 160.f;
|
||||
|
||||
cfg.walkableHeight = (int)ceilf( cfg.walkableHeight / cfg.ch );
|
||||
cfg.walkableClimb = (int)floorf( cfg.walkableClimb / cfg.ch );
|
||||
cfg.walkableRadius = (int)ceilf( cfg.walkableRadius / cfg.cs );
|
||||
cfg.maxEdgeLen = (int)( cfg.maxEdgeLen / cfg.cs );
|
||||
cfg.minRegionArea = (int)rcSqr( cfg.minRegionArea ); // Note: area = size*size
|
||||
cfg.mergeRegionArea = (int)rcSqr( cfg.mergeRegionArea ); // Note: area = size*size
|
||||
cfg.borderSize = cfg.walkableRadius + 3; // Reserve enough padding.
|
||||
cfg.width = cfg.tileSize + cfg.borderSize*2;
|
||||
cfg.height = cfg.tileSize + cfg.borderSize*2;
|
||||
cfg.detailSampleDist = cfg.detailSampleDist < 0.9f ? 0 : cfg.cs * cfg.detailSampleDist;
|
||||
cfg.detailSampleMaxError = cfg.ch * cfg.detailSampleMaxError;
|
||||
|
||||
rcContext ctx;
|
||||
auto hf = rcAllocHeightfield();
|
||||
auto chf = rcAllocCompactHeightfield();
|
||||
auto cs = rcAllocContourSet();
|
||||
auto pmesh = rcAllocPolyMesh();
|
||||
auto pdetailmesh = rcAllocPolyMeshDetail();
|
||||
|
||||
std::vector< float > verts;
|
||||
std::vector< int > indices;
|
||||
|
||||
int i = 0;
|
||||
int numIndices = 0;
|
||||
for( const auto& model : group.models )
|
||||
{
|
||||
for( const auto& mesh : model.second.meshes )
|
||||
{
|
||||
auto size = mesh.verts.size();
|
||||
if (!size)
|
||||
continue;
|
||||
rcCalcBounds( mesh.verts.data(), size / 3, &cfg.bmin[0], &cfg.bmax[0] );
|
||||
verts.resize( verts.size() + size );
|
||||
memcpy( &verts[i], mesh.verts.data(), size );
|
||||
i += size;
|
||||
|
||||
size = mesh.indices.size();
|
||||
indices.resize( indices.size() + size );
|
||||
for( auto j = 0; j < mesh.indices.size(); j += 3 )
|
||||
{
|
||||
indices[j] = mesh.indices[j] + numIndices;
|
||||
indices[j + 1] = mesh.indices[j + 1] + numIndices;
|
||||
indices[j + 2] = mesh.indices[j + 2] + numIndices;
|
||||
}
|
||||
numIndices += size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if( !rcCreateHeightfield( &ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch ) )
|
||||
{
|
||||
|
||||
}
|
||||
float tbmin[2], tbmax[2];
|
||||
tbmin[0] = cfg.bmin[0];
|
||||
tbmin[1] = cfg.bmin[2];
|
||||
tbmax[0] = cfg.bmax[0];
|
||||
tbmax[1] = cfg.bmax[2];
|
||||
int cid[512];// TODO: Make grow when returning too many items.
|
||||
|
||||
|
||||
auto tileTriCount = 0;
|
||||
|
||||
// Once all geometry is rasterized, we do initial pass of filtering to
|
||||
// remove unwanted overhangs caused by the conservative rasterization
|
||||
// as well as filter spans where the character cannot possibly stand.
|
||||
|
||||
rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *hf);
|
||||
|
||||
rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf);
|
||||
rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf);
|
||||
|
||||
// Compact the heightfield so that it is faster to handle from now on.
|
||||
// This will result more cache coherent data as well as the neighbours
|
||||
// between walkable cells will be calculated.
|
||||
chf = rcAllocCompactHeightfield();
|
||||
if (!chf)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'.");
|
||||
return 0;
|
||||
}
|
||||
if (!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build compact data.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
rcFreeHeightField(hf);
|
||||
hf = 0;
|
||||
}
|
||||
|
||||
// Erode the walkable area by agent radius.
|
||||
if (!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not erode.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Partition the heightfield so that we can use simple algorithm later to triangulate the walkable areas.
|
||||
// There are 3 martitioning methods, each with some pros and cons:
|
||||
// 1) Watershed partitioning
|
||||
// - the classic Recast partitioning
|
||||
// - creates the nicest tessellation
|
||||
// - usually slowest
|
||||
// - partitions the heightfield into nice regions without holes or overlaps
|
||||
// - the are some corner cases where this method creates produces holes and overlaps
|
||||
// - holes may appear when a small obstacles is close to large open area (triangulation can handle this)
|
||||
// - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail
|
||||
// * generally the best choice if you precompute the nacmesh, use this if you have large open areas
|
||||
// 2) Monotone partioning
|
||||
// - fastest
|
||||
// - partitions the heightfield into regions without holes and overlaps (guaranteed)
|
||||
// - creates long thin polygons, which sometimes causes paths with detours
|
||||
// * use this if you want fast navmesh generation
|
||||
// 3) Layer partitoining
|
||||
// - quite fast
|
||||
// - partitions the heighfield into non-overlapping regions
|
||||
// - relies on the triangulation code to cope with holes (thus slower than monotone partitioning)
|
||||
// - produces better triangles than monotone partitioning
|
||||
// - does not have the corner cases of watershed partitioning
|
||||
// - can be slow and create a bit ugly tessellation (still better than monotone)
|
||||
// if you have large open areas with small obstacles (not a problem if you use tiles)
|
||||
// * good choice to use for tiled navmesh with medium and small sized tiles
|
||||
|
||||
//if (m_partitionType == SAMPLE_PARTITION_WATERSHED)
|
||||
{
|
||||
// Prepare for region partitioning, by calculating distance field along the walkable surface.
|
||||
if (!rcBuildDistanceField(&ctx, *chf))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build distance field.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
if (!rcBuildRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build watershed regions.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
//else if (m_partitionType == SAMPLE_PARTITION_MONOTONE)
|
||||
//{
|
||||
// // Partition the walkable surface into simple regions without holes.
|
||||
// // Monotone partitioning does not need distancefield.
|
||||
// if (!rcBuildRegionsMonotone(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
|
||||
// {
|
||||
// ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build monotone regions.");
|
||||
// return 0;
|
||||
// }
|
||||
//}
|
||||
//else // SAMPLE_PARTITION_LAYERS
|
||||
//{
|
||||
// // Partition the walkable surface into simple regions without holes.
|
||||
// if (!rcBuildLayerRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea))
|
||||
// {
|
||||
// ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build layer regions.");
|
||||
// return 0;
|
||||
// }
|
||||
//}
|
||||
|
||||
// Create contours.
|
||||
cs = rcAllocContourSet();
|
||||
if (!cs)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'.");
|
||||
return 0;
|
||||
}
|
||||
if (!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cs))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not create contours.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cs->nconts == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build polygon navmesh from the contours.
|
||||
pmesh = rcAllocPolyMesh();
|
||||
if (!pmesh)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'.");
|
||||
return 0;
|
||||
}
|
||||
if (!rcBuildPolyMesh(&ctx, *cs, cfg.maxVertsPerPoly, *pmesh))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build detail mesh.
|
||||
pdetailmesh = rcAllocPolyMeshDetail();
|
||||
if (!pdetailmesh)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!rcBuildPolyMeshDetail(&ctx, *pmesh, *chf,
|
||||
cfg.detailSampleDist, cfg.detailSampleMaxError,
|
||||
*pdetailmesh))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
rcFreeCompactHeightfield(chf);
|
||||
chf = 0;
|
||||
rcFreeContourSet(cs);
|
||||
cs = 0;
|
||||
}
|
||||
|
||||
int navDataSize = 0;
|
||||
if (cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON)
|
||||
{
|
||||
if (pmesh->nverts >= 0xffff)
|
||||
{
|
||||
// The vertex indices are ushorts, and cannot point to more than 0xffff vertices.
|
||||
ctx.log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", pmesh->nverts, 0xffff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Update poly flags from areas.
|
||||
for (int i = 0; i < pmesh->npolys; ++i)
|
||||
{
|
||||
//pmesh->flags[i] = sampleAreaToFlags(pmesh->areas[i]);
|
||||
}
|
||||
|
||||
dtNavMeshCreateParams params;
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
params.verts = pmesh->verts;
|
||||
params.vertCount = pmesh->nverts;
|
||||
params.polys = pmesh->polys;
|
||||
params.polyAreas = pmesh->areas;
|
||||
params.polyFlags = pmesh->flags;
|
||||
params.polyCount = pmesh->npolys;
|
||||
params.nvp = pmesh->nvp;
|
||||
params.detailMeshes = pdetailmesh->meshes;
|
||||
params.detailVerts = pdetailmesh->verts;
|
||||
params.detailVertsCount = pdetailmesh->nverts;
|
||||
params.detailTris = pdetailmesh->tris;
|
||||
params.detailTriCount = pdetailmesh->ntris;
|
||||
params.offMeshConVerts = 0;
|
||||
params.offMeshConRad = 0;
|
||||
params.offMeshConDir = 0;
|
||||
params.offMeshConAreas = 0;
|
||||
params.offMeshConFlags = 0;
|
||||
params.offMeshConUserID = 0;
|
||||
params.offMeshConCount = 0;
|
||||
params.walkableHeight = cfg.walkableHeight;
|
||||
params.walkableRadius = cfg.walkableRadius;
|
||||
params.walkableClimb = cfg.walkableClimb;
|
||||
params.tileX = 0;
|
||||
params.tileY = 0;
|
||||
params.tileLayer = 0;
|
||||
rcVcopy(params.bmin, pmesh->bmin);
|
||||
rcVcopy(params.bmax, pmesh->bmax);
|
||||
params.cs = cfg.cs;
|
||||
params.ch = cfg.ch;
|
||||
params.buildBvTree = true;
|
||||
|
||||
if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "Could not build Detour navmesh.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
auto tileMemUsage = navDataSize/1024.0f;
|
||||
|
||||
ctx.stopTimer(RC_TIMER_TOTAL);
|
||||
|
||||
// Show performance stats.
|
||||
//duLogBuildTimes(*&ctx, ctx.getAccumulatedTime(RC_TIMER_TOTAL));
|
||||
ctx.log(RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons", pmesh->nverts, pmesh->npolys);
|
||||
|
||||
auto tileBuildTime = ctx.getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f;
|
||||
|
||||
auto dataSize = navDataSize;
|
||||
return navData;
|
||||
|
||||
}
|
||||
//*/
|
||||
};
|
||||
#endif // !OBJ_EXPORTER_H
|
||||
|
|
|
@ -20,14 +20,14 @@ public:
|
|||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto dir = currPath + "/" + zone.name + "/";
|
||||
auto fileName = dir + "/" + zone.name + ".obj";
|
||||
auto dir = currPath + "/pcb_export/" + zone.name + "/";
|
||||
auto fileName = dir + zone.name + ".obj";
|
||||
|
||||
std::error_code e;
|
||||
|
||||
if( !std::experimental::filesystem::exists( dir, e ) )
|
||||
{
|
||||
if( !std::experimental::filesystem::create_directory( dir, e ) )
|
||||
if( !std::experimental::filesystem::create_directories( dir, e ) )
|
||||
{
|
||||
printf( "Unable to create directory '%s'", ( dir ).c_str() );
|
||||
return "";
|
||||
|
@ -50,9 +50,10 @@ public:
|
|||
}
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
printf( "[Obj] Finished exporting %s in %u ms\n",
|
||||
fileName.c_str(),
|
||||
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||
|
||||
printf( "[Obj] Finished exporting %s in %lu ms\n",
|
||||
fileName.substr( fileName.find( "pcb_export" ) - 1 ).c_str(),
|
||||
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
@ -62,13 +63,13 @@ public:
|
|||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto dir = currPath + "/" + zoneName + "/";
|
||||
auto fileName = dir + "/" + group.name + ".obj";
|
||||
auto dir = currPath + "/pcb_export/" + zoneName + "/groups/";
|
||||
auto fileName = dir + group.name + ".obj";
|
||||
|
||||
std::error_code e;
|
||||
if( !std::experimental::filesystem::exists( dir, e ) )
|
||||
{
|
||||
if( !std::experimental::filesystem::create_directory( dir, e ) )
|
||||
if( !std::experimental::filesystem::create_directories( dir, e ) )
|
||||
{
|
||||
printf( "Unable to create directory '%s'", ( dir ).c_str() );
|
||||
return "";
|
||||
|
@ -88,9 +89,10 @@ public:
|
|||
}
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
printf( "[Obj] Finished exporting %s in %u ms\n",
|
||||
fileName.c_str(),
|
||||
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||
printf( "[Obj] Finished exporting %s in %lu ms\n",
|
||||
fileName.substr( fileName.find( "pcb_export" ) - 1 ).c_str(),
|
||||
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||
|
||||
return fileName;
|
||||
}
|
||||
private:
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <Network/CommonActorControl.h>
|
||||
#include <Network/PacketWrappers/EffectPacket.h>
|
||||
#include <Network/PacketDef/Zone/ClientZoneDef.h>
|
||||
#include <Logging/Logger.h>
|
||||
|
||||
#include "Forwards.h"
|
||||
#include "Action/Action.h"
|
||||
|
@ -19,6 +20,7 @@
|
|||
#include "Network/PacketWrappers/UpdateHpMpTpPacket.h"
|
||||
#include "Network/PacketWrappers/NpcSpawnPacket.h"
|
||||
#include "Network/PacketWrappers/MoveActorPacket.h"
|
||||
#include "Navi/NaviProvider.h"
|
||||
|
||||
#include "StatusEffect/StatusEffect.h"
|
||||
#include "Action/ActionCollision.h"
|
||||
|
@ -31,6 +33,9 @@
|
|||
#include "BNpcTemplate.h"
|
||||
#include "Manager/TerritoryMgr.h"
|
||||
#include "Common.h"
|
||||
#include "Framework.h"
|
||||
#include <Logging/Logger.h>
|
||||
#include <Manager/NaviMgr.h>
|
||||
|
||||
using namespace Sapphire::Common;
|
||||
using namespace Sapphire::Network::Packets;
|
||||
|
@ -67,12 +72,15 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX
|
|||
|
||||
m_spawnPos = m_pos;
|
||||
|
||||
m_timeOfDeath = 0;
|
||||
|
||||
m_maxHp = maxHp;
|
||||
m_maxMp = 200;
|
||||
m_hp = maxHp;
|
||||
m_mp = 200;
|
||||
|
||||
m_state = BNpcState::Idle;
|
||||
m_status = ActorStatus::Idle;
|
||||
|
||||
m_baseStats.max_hp = maxHp;
|
||||
m_baseStats.max_mp = 200;
|
||||
|
@ -128,12 +136,13 @@ uint32_t Sapphire::Entity::BNpc::getBNpcNameId() const
|
|||
|
||||
void Sapphire::Entity::BNpc::spawn( PlayerPtr pTarget )
|
||||
{
|
||||
pTarget->queuePacket( std::make_shared< NpcSpawnPacket >( *getAsBNpc(), *pTarget ) );
|
||||
pTarget->queuePacket( std::make_shared< NpcSpawnPacket >( *this, *pTarget ) );
|
||||
}
|
||||
|
||||
void Sapphire::Entity::BNpc::despawn( PlayerPtr pTarget )
|
||||
{
|
||||
pTarget->freePlayerSpawnId( getId() );
|
||||
pTarget->queuePacket( makeActorControl143( m_id, DespawnZoneScreenMsg, 0x04, getId(), 0x01 ) );
|
||||
}
|
||||
|
||||
Sapphire::Entity::BNpcState Sapphire::Entity::BNpc::getState() const
|
||||
|
@ -146,38 +155,88 @@ void Sapphire::Entity::BNpc::setState( BNpcState state )
|
|||
m_state = state;
|
||||
}
|
||||
|
||||
void Sapphire::Entity::BNpc::step()
|
||||
{
|
||||
if( m_naviLastPath.empty() )
|
||||
// No path to track
|
||||
return;
|
||||
|
||||
auto stepPos = m_naviLastPath[ m_naviPathStep ];
|
||||
|
||||
if( Util::distance( getPos().x, getPos().y, getPos().z, stepPos.x, stepPos.y, stepPos.z ) <= 4 &&
|
||||
m_naviPathStep < m_naviLastPath.size() - 1 )
|
||||
{
|
||||
// Reached step in path
|
||||
m_naviPathStep++;
|
||||
stepPos = m_naviLastPath[ m_naviPathStep ];
|
||||
}
|
||||
|
||||
// This is probably not a good way to do it but works fine for now
|
||||
float angle = Util::calcAngFrom( getPos().x, getPos().z, stepPos.x, stepPos.z ) + PI;
|
||||
|
||||
auto x = ( cosf( angle ) * .5f );
|
||||
auto y = stepPos.y;
|
||||
auto z = ( sinf( angle ) * .5f );
|
||||
|
||||
face( stepPos );
|
||||
setPos( { getPos().x + x, y, getPos().z + z } );
|
||||
|
||||
sendPositionUpdate();
|
||||
}
|
||||
|
||||
bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos )
|
||||
{
|
||||
if( Util::distance( getPos().x, getPos().y, getPos().z, pos.x, pos.y, pos.z ) <= 4 )
|
||||
// reached destination
|
||||
{
|
||||
// Reached destination
|
||||
m_naviLastPath.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
float rot = Util::calcAngFrom( getPos().x, getPos().z, pos.x, pos.z );
|
||||
float newRot = PI - rot + ( PI / 2 );
|
||||
// Check if we have to recalculate
|
||||
if( Util::getTimeMs() - m_naviLastUpdate > 500 )
|
||||
{
|
||||
auto pNaviMgr = m_pFw->get< World::Manager::NaviMgr >();
|
||||
auto pNaviProvider = pNaviMgr->getNaviProvider( m_pCurrentZone->getBgPath() );
|
||||
|
||||
face( pos );
|
||||
float angle = Util::calcAngFrom( getPos().x, getPos().z, pos.x, pos.z ) + PI;
|
||||
if( !pNaviProvider )
|
||||
{
|
||||
Logger::error( "No NaviProvider for zone#{0} - {1}", m_pCurrentZone->getGuId(), m_pCurrentZone->getInternalName() );
|
||||
return false;
|
||||
}
|
||||
|
||||
auto x = ( cosf( angle ) * 1.1f );
|
||||
auto y = ( getPos().y + pos.y ) * 0.5f; // fake value while there is no collision
|
||||
auto z = ( sinf( angle ) * 1.1f );
|
||||
auto path = pNaviProvider->findFollowPath( m_pos, pos );
|
||||
|
||||
Common::FFXIVARR_POSITION3 newPos{ getPos().x + x, y, getPos().z + z };
|
||||
setPos( newPos );
|
||||
if( !path.empty() )
|
||||
{
|
||||
m_naviLastPath = path;
|
||||
m_naviTarget = pos;
|
||||
m_naviPathStep = 0;
|
||||
m_naviLastUpdate = Util::getTimeMs();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::debug( "No path found from x{0} y{1} z{2} to x{3} y{4} z{5} in {6}",
|
||||
getPos().x, getPos().y, getPos().z, pos.x, pos.y, pos.z, m_pCurrentZone->getInternalName() );
|
||||
}
|
||||
}
|
||||
|
||||
Common::FFXIVARR_POSITION3 tmpPos{ getPos().x + x, y, getPos().z + z };
|
||||
|
||||
setPos( tmpPos );
|
||||
setRot( newRot );
|
||||
|
||||
sendPositionUpdate();
|
||||
step();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Sapphire::Entity::BNpc::sendPositionUpdate()
|
||||
{
|
||||
auto movePacket = std::make_shared< MoveActorPacket >( *getAsChara(), 0x3A, 0, 0, 0x5A );
|
||||
uint8_t unk1 = 0x3a;
|
||||
uint8_t animationType = 2;
|
||||
|
||||
if( m_state == BNpcState::Combat )
|
||||
animationType = 0;
|
||||
|
||||
|
||||
auto movePacket = std::make_shared< MoveActorPacket >( *getAsChara(), unk1, animationType, 0, 0x5A );
|
||||
sendToInRangeSet( movePacket );
|
||||
}
|
||||
|
||||
|
@ -278,7 +337,7 @@ void Sapphire::Entity::BNpc::aggro( Sapphire::Entity::CharaPtr pChara )
|
|||
if( pChara->isPlayer() )
|
||||
{
|
||||
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
||||
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 0, 1, 1 ) );
|
||||
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 1, 1, 1 ) );
|
||||
tmpPlayer->onMobAggro( getAsBNpc() );
|
||||
}
|
||||
}
|
||||
|
@ -291,6 +350,7 @@ void Sapphire::Entity::BNpc::deaggro( Sapphire::Entity::CharaPtr pChara )
|
|||
if( pChara->isPlayer() )
|
||||
{
|
||||
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
||||
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 0, 1, 1 ) );
|
||||
tmpPlayer->onMobDeaggro( getAsBNpc() );
|
||||
}
|
||||
}
|
||||
|
@ -299,22 +359,82 @@ void Sapphire::Entity::BNpc::update( int64_t currTime )
|
|||
{
|
||||
const uint8_t minActorDistance = 4;
|
||||
const uint8_t aggroRange = 8;
|
||||
const uint8_t maxDistanceToOrigin = 30;
|
||||
|
||||
if( m_status == ActorStatus::Dead )
|
||||
return;
|
||||
const uint8_t maxDistanceToOrigin = 40;
|
||||
const uint32_t roamTick = 20;
|
||||
|
||||
switch( m_state )
|
||||
{
|
||||
case BNpcState::Dead:
|
||||
case BNpcState::JustDied:
|
||||
return;
|
||||
|
||||
case BNpcState::Retreat:
|
||||
{
|
||||
setInvincibilityType( InvincibilityType::InvincibilityIgnoreDamage );
|
||||
|
||||
// slowly restore hp every tick
|
||||
|
||||
if( std::difftime( currTime, m_lastTickTime ) > 3000 )
|
||||
{
|
||||
m_lastTickTime = currTime;
|
||||
|
||||
if( m_hp < getMaxHp() )
|
||||
{
|
||||
auto addHp = static_cast< uint32_t >( getMaxHp() * 0.1f + 1 );
|
||||
|
||||
if( m_hp + addHp < getMaxHp() )
|
||||
m_hp += addHp;
|
||||
else
|
||||
m_hp = getMaxHp();
|
||||
}
|
||||
|
||||
sendStatusUpdate();
|
||||
}
|
||||
|
||||
if( moveTo( m_spawnPos ) )
|
||||
{
|
||||
setInvincibilityType( InvincibilityType::InvincibilityNone );
|
||||
|
||||
// retail doesn't seem to roam straight after retreating
|
||||
// todo: perhaps requires more investigation?
|
||||
m_lastRoamTargetReached = Util::getTimeSeconds();
|
||||
|
||||
setHp( getMaxHp() );
|
||||
|
||||
m_state = BNpcState::Idle;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BNpcState::Roaming:
|
||||
{
|
||||
if( moveTo( m_roamPos ) )
|
||||
{
|
||||
m_lastRoamTargetReached = Util::getTimeSeconds();
|
||||
m_state = BNpcState::Idle;
|
||||
}
|
||||
|
||||
// checkaggro
|
||||
}
|
||||
break;
|
||||
|
||||
case BNpcState::Idle:
|
||||
{
|
||||
if( Util::getTimeSeconds() - m_lastRoamTargetReached > roamTick )
|
||||
{
|
||||
auto pNaviMgr = m_pFw->get< World::Manager::NaviMgr >();
|
||||
auto pNaviProvider = pNaviMgr->getNaviProvider( m_pCurrentZone->getBgPath() );
|
||||
|
||||
if( !pNaviProvider )
|
||||
{
|
||||
m_lastRoamTargetReached = Util::getTimeSeconds();
|
||||
break;
|
||||
}
|
||||
|
||||
m_roamPos = pNaviProvider->findRandomPositionInCircle( m_spawnPos, 5 );
|
||||
m_state = BNpcState::Roaming;
|
||||
}
|
||||
|
||||
// passive mobs should ignore players unless aggro'd
|
||||
if( m_aggressionMode == 1 )
|
||||
return;
|
||||
|
@ -330,8 +450,6 @@ void Sapphire::Entity::BNpc::update( int64_t currTime )
|
|||
|
||||
if( distance < aggroRange && pClosestChara->isPlayer() )
|
||||
aggro( pClosestChara );
|
||||
//if( distance < aggroRange && getbehavior() == 2 )
|
||||
// aggro( pClosestActor );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,3 +507,31 @@ void Sapphire::Entity::BNpc::update( int64_t currTime )
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sapphire::Entity::BNpc::onActionHostile( Sapphire::Entity::CharaPtr pSource )
|
||||
{
|
||||
if( !hateListGetHighest() )
|
||||
aggro( pSource );
|
||||
|
||||
//if( !getClaimer() )
|
||||
// setOwner( pSource->getAsPlayer() );
|
||||
}
|
||||
|
||||
void Sapphire::Entity::BNpc::onDeath()
|
||||
{
|
||||
setTargetId( INVALID_GAME_OBJECT_ID );
|
||||
m_currentStance = Stance::Passive;
|
||||
m_state = BNpcState::Dead;
|
||||
m_timeOfDeath = Util::getTimeSeconds();
|
||||
hateListClear();
|
||||
}
|
||||
|
||||
uint32_t Sapphire::Entity::BNpc::getTimeOfDeath() const
|
||||
{
|
||||
return m_timeOfDeath;
|
||||
}
|
||||
|
||||
void Sapphire::Entity::BNpc::setTimeOfDeath( uint32_t timeOfDeath )
|
||||
{
|
||||
m_timeOfDeath = timeOfDeath;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace Sapphire::Entity
|
|||
Idle,
|
||||
Combat,
|
||||
Retreat,
|
||||
Roaming,
|
||||
JustDied,
|
||||
Dead,
|
||||
};
|
||||
|
@ -62,6 +63,9 @@ namespace Sapphire::Entity
|
|||
// return true if it reached the position
|
||||
bool moveTo( const Common::FFXIVARR_POSITION3& pos );
|
||||
|
||||
// processes movement
|
||||
void step();
|
||||
|
||||
void sendPositionUpdate();
|
||||
|
||||
BNpcState getState() const;
|
||||
|
@ -79,6 +83,13 @@ namespace Sapphire::Entity
|
|||
|
||||
void update( int64_t currTime ) override;
|
||||
|
||||
void onActionHostile( CharaPtr pSource ) override;
|
||||
|
||||
void onDeath() override;
|
||||
|
||||
uint32_t getTimeOfDeath() const;
|
||||
void setTimeOfDeath( uint32_t timeOfDeath );
|
||||
|
||||
private:
|
||||
uint32_t m_bNpcBaseId;
|
||||
uint32_t m_bNpcNameId;
|
||||
|
@ -92,11 +103,20 @@ namespace Sapphire::Entity
|
|||
uint32_t m_displayFlags;
|
||||
uint8_t m_level;
|
||||
|
||||
uint32_t m_timeOfDeath;
|
||||
uint32_t m_lastRoamTargetReached;
|
||||
|
||||
Common::FFXIVARR_POSITION3 m_spawnPos;
|
||||
Common::FFXIVARR_POSITION3 m_roamPos;
|
||||
|
||||
BNpcState m_state;
|
||||
std::set< std::shared_ptr< HateListEntry > > m_hateList;
|
||||
|
||||
uint64_t m_naviLastUpdate;
|
||||
std::vector< Common::FFXIVARR_POSITION3 > m_naviLastPath;
|
||||
uint8_t m_naviPathStep;
|
||||
Common::FFXIVARR_POSITION3 m_naviTarget;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -151,35 +151,35 @@ uint32_t Sapphire::Entity::Chara::getMaxMp() const
|
|||
void Sapphire::Entity::Chara::resetHp()
|
||||
{
|
||||
m_hp = getMaxHp();
|
||||
sendStatusUpdate( true );
|
||||
sendStatusUpdate();
|
||||
}
|
||||
|
||||
/*! \return reset mp to current max mp */
|
||||
void Sapphire::Entity::Chara::resetMp()
|
||||
{
|
||||
m_mp = getMaxMp();
|
||||
sendStatusUpdate( true );
|
||||
sendStatusUpdate();
|
||||
}
|
||||
|
||||
/*! \param hp amount to set ( caps to maxHp ) */
|
||||
void Sapphire::Entity::Chara::setHp( uint32_t hp )
|
||||
{
|
||||
m_hp = hp < getMaxHp() ? hp : getMaxHp();
|
||||
sendStatusUpdate( true );
|
||||
sendStatusUpdate();
|
||||
}
|
||||
|
||||
/*! \param mp amount to set ( caps to maxMp ) */
|
||||
void Sapphire::Entity::Chara::setMp( uint32_t mp )
|
||||
{
|
||||
m_mp = mp < getMaxMp() ? mp : getMaxMp();
|
||||
sendStatusUpdate( true );
|
||||
sendStatusUpdate();
|
||||
}
|
||||
|
||||
/*! \param gp amount to set*/
|
||||
void Sapphire::Entity::Chara::setGp( uint32_t gp )
|
||||
{
|
||||
m_gp = gp;
|
||||
sendStatusUpdate( true );
|
||||
sendStatusUpdate();
|
||||
}
|
||||
|
||||
/*! \param type invincibility type to set */
|
||||
|
@ -244,7 +244,7 @@ bool Sapphire::Entity::Chara::face( const Common::FFXIVARR_POSITION3& p )
|
|||
|
||||
setRot( newRot );
|
||||
|
||||
return oldRot != newRot ? true : false;
|
||||
return oldRot != newRot;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -325,12 +325,14 @@ void Sapphire::Entity::Chara::takeDamage( uint32_t damage )
|
|||
case InvincibilityStayAlive:
|
||||
setHp( 0 );
|
||||
break;
|
||||
case InvincibilityIgnoreDamage:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_hp -= damage;
|
||||
|
||||
sendStatusUpdate( false );
|
||||
sendStatusUpdate();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -349,7 +351,7 @@ void Sapphire::Entity::Chara::heal( uint32_t amount )
|
|||
else
|
||||
m_hp += amount;
|
||||
|
||||
sendStatusUpdate( false );
|
||||
sendStatusUpdate();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -359,7 +361,7 @@ so players can have their own version and we can abolish the param.
|
|||
|
||||
\param true if the update should also be sent to the actor ( player ) himself
|
||||
*/
|
||||
void Sapphire::Entity::Chara::sendStatusUpdate( bool toSelf )
|
||||
void Sapphire::Entity::Chara::sendStatusUpdate()
|
||||
{
|
||||
FFXIVPacketBasePtr packet = std::make_shared< UpdateHpMpTpPacket >( *this );
|
||||
sendToInRangeSet( packet );
|
||||
|
@ -391,9 +393,10 @@ void Sapphire::Entity::Chara::autoAttack( CharaPtr pTarget )
|
|||
|
||||
uint64_t tick = Util::getTimeMs();
|
||||
|
||||
// todo: this needs to use the auto attack delay for the equipped weapon
|
||||
if( ( tick - m_lastAttack ) > 2500 )
|
||||
{
|
||||
pTarget->onActionHostile( *this );
|
||||
pTarget->onActionHostile( getAsChara() );
|
||||
m_lastAttack = tick;
|
||||
srand( static_cast< uint32_t >( tick ) );
|
||||
|
||||
|
@ -461,7 +464,7 @@ void Sapphire::Entity::Chara::handleScriptSkill( uint32_t type, uint16_t actionI
|
|||
sendToInRangeSet( effectPacket, true );
|
||||
|
||||
if( target.isAlive() )
|
||||
target.onActionHostile( *this );
|
||||
target.onActionHostile( getAsChara() );
|
||||
|
||||
target.takeDamage( static_cast< uint32_t >( param1 ) );
|
||||
|
||||
|
@ -481,7 +484,7 @@ void Sapphire::Entity::Chara::handleScriptSkill( uint32_t type, uint16_t actionI
|
|||
|
||||
|
||||
if( pHitActor->getAsChara()->isAlive() )
|
||||
pHitActor->getAsChara()->onActionHostile( *this );
|
||||
pHitActor->getAsChara()->onActionHostile( getAsChara() );
|
||||
|
||||
pHitActor->getAsChara()->takeDamage( static_cast< uint32_t >( param1 ) );
|
||||
|
||||
|
|
|
@ -219,7 +219,7 @@ namespace Sapphire::Entity
|
|||
|
||||
virtual void onDamageTaken( Chara& pSource ) {};
|
||||
|
||||
virtual void onActionHostile( Chara& source ) {};
|
||||
virtual void onActionHostile( CharaPtr pSource ) {};
|
||||
|
||||
virtual void onActionFriendly( Chara& pSource ) {};
|
||||
|
||||
|
@ -229,7 +229,7 @@ namespace Sapphire::Entity
|
|||
|
||||
virtual uint8_t getLevel() const;
|
||||
|
||||
virtual void sendStatusUpdate( bool toSelf = true );
|
||||
virtual void sendStatusUpdate();
|
||||
|
||||
virtual void takeDamage( uint32_t damage );
|
||||
|
||||
|
|
|
@ -739,7 +739,7 @@ void Sapphire::Entity::Player::gainLevel()
|
|||
|
||||
}
|
||||
|
||||
void Sapphire::Entity::Player::sendStatusUpdate( bool toSelf )
|
||||
void Sapphire::Entity::Player::sendStatusUpdate()
|
||||
{
|
||||
sendToInRangeSet( std::make_shared< UpdateHpMpTpPacket >( *this ), true );
|
||||
}
|
||||
|
@ -810,7 +810,7 @@ void Sapphire::Entity::Player::setClassJob( Common::ClassJob classJob )
|
|||
|
||||
sendToInRangeSet( makeActorControl142( getId(), ClassJobChange, 0x04 ), true );
|
||||
|
||||
sendStatusUpdate( true );
|
||||
sendStatusUpdate();
|
||||
}
|
||||
|
||||
void Sapphire::Entity::Player::setLevel( uint8_t level )
|
||||
|
@ -1515,7 +1515,7 @@ void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
|
|||
|
||||
auto mainWeap = getItemAt( Common::GearSet0, Common::GearSetSlot::MainHand );
|
||||
|
||||
pTarget->onActionHostile( *this );
|
||||
pTarget->onActionHostile( getAsChara() );
|
||||
//uint64_t tick = Util::getTimeMs();
|
||||
//srand(static_cast< uint32_t >(tick));
|
||||
|
||||
|
|
|
@ -707,7 +707,7 @@ namespace Sapphire::Entity
|
|||
void sendStateFlags();
|
||||
|
||||
/*! send status update */
|
||||
void sendStatusUpdate( bool toSelf = true ) override;
|
||||
void sendStatusUpdate() override;
|
||||
|
||||
/*! send the entire inventory sequence */
|
||||
void sendInventory();
|
||||
|
|
|
@ -19,7 +19,8 @@ file( GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
|||
Script/*.c*
|
||||
StatusEffect/*.c*
|
||||
Territory/*.c*
|
||||
Territory/Housing/*.c*)
|
||||
Territory/Housing/*.c*
|
||||
Navi/*.c*)
|
||||
|
||||
add_executable( world ${SERVER_SOURCE_FILES} )
|
||||
|
||||
|
@ -30,10 +31,12 @@ set_target_properties( world
|
|||
|
||||
target_link_libraries( world
|
||||
PUBLIC
|
||||
common )
|
||||
common
|
||||
Detour)
|
||||
target_include_directories( world
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}" )
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
Detour )
|
||||
|
||||
|
||||
if( UNIX )
|
||||
|
|
|
@ -35,6 +35,11 @@ namespace World
|
|||
TYPE_FORWARD( Session );
|
||||
}
|
||||
|
||||
namespace World::Navi
|
||||
{
|
||||
TYPE_FORWARD( NaviProvider );
|
||||
}
|
||||
|
||||
namespace World::Territory::Housing
|
||||
{
|
||||
TYPE_FORWARD( HousingInteriorTerritory );
|
||||
|
|
|
@ -367,6 +367,18 @@ void Sapphire::World::Manager::DebugCommandMgr::set( char* data, Entity::Player&
|
|||
}
|
||||
}
|
||||
}
|
||||
else if( subCommand == "mobaggro" )
|
||||
{
|
||||
auto inRange = player.getInRangeActors();
|
||||
|
||||
for( auto actor : inRange )
|
||||
{
|
||||
if( actor->getId() == player.getTargetId() && actor->getAsChara()->isAlive() )
|
||||
{
|
||||
actor->getAsBNpc()->onActionHostile( player.getAsChara() );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
player.sendUrgent( "{0} is not a valid SET command.", subCommand );
|
||||
|
|
47
src/world/Manager/NaviMgr.cpp
Normal file
47
src/world/Manager/NaviMgr.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#include "NaviMgr.h"
|
||||
#include "Navi/NaviProvider.h"
|
||||
#include <Logging/Logger.h>
|
||||
|
||||
Sapphire::World::Manager::NaviMgr::NaviMgr( FrameworkPtr pFw ) :
|
||||
BaseManager( pFw ),
|
||||
m_pFw( pFw )
|
||||
{
|
||||
}
|
||||
|
||||
bool Sapphire::World::Manager::NaviMgr::setupTerritory( const std::string& bgPath )
|
||||
{
|
||||
std::string bg = getBgName( bgPath );
|
||||
|
||||
// check if a provider exists already
|
||||
if( m_naviProviderTerritoryMap.find( bg ) != m_naviProviderTerritoryMap.end() )
|
||||
return true;
|
||||
|
||||
auto provider = Navi::make_NaviProvider( bg, m_pFw );
|
||||
|
||||
if( provider->init() )
|
||||
{
|
||||
m_naviProviderTerritoryMap.insert( std::make_pair( bg, provider ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Sapphire::World::Navi::NaviProviderPtr Sapphire::World::Manager::NaviMgr::getNaviProvider( const std::string& bgPath )
|
||||
{
|
||||
std::string bg = getBgName( bgPath );
|
||||
|
||||
if( m_naviProviderTerritoryMap.find( bg ) != m_naviProviderTerritoryMap.end() )
|
||||
return m_naviProviderTerritoryMap[ bg ];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string Sapphire::World::Manager::NaviMgr::getBgName( const std::string& bgPath )
|
||||
{
|
||||
auto findPos = bgPath.find_last_of( "/" );
|
||||
if( findPos != std::string::npos )
|
||||
return bgPath.substr( findPos + 1 );
|
||||
|
||||
return "";
|
||||
}
|
32
src/world/Manager/NaviMgr.h
Normal file
32
src/world/Manager/NaviMgr.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef SAPPHIRE_NAVIMGR_H
|
||||
#define SAPPHIRE_NAVIMGR_H
|
||||
|
||||
#include "ForwardsZone.h"
|
||||
#include "BaseManager.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Sapphire::World::Manager
|
||||
{
|
||||
class NaviMgr : public BaseManager
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
NaviMgr( FrameworkPtr pFw );
|
||||
virtual ~NaviMgr() = default;
|
||||
|
||||
bool setupTerritory( const std::string& bgPath );
|
||||
Navi::NaviProviderPtr getNaviProvider( const std::string& bgPath );
|
||||
|
||||
private:
|
||||
FrameworkPtr m_pFw;
|
||||
|
||||
std::string getBgName( const std::string& bgPath );
|
||||
|
||||
std::unordered_map< std::string, Navi::NaviProviderPtr > m_naviProviderTerritoryMap;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SAPPHIRE_NAVIMGR_H
|
|
@ -16,6 +16,7 @@
|
|||
#include "Territory/Land.h"
|
||||
#include "Territory/House.h"
|
||||
#include "Territory/Housing/HousingInteriorTerritory.h"
|
||||
#include "NaviMgr.h"
|
||||
|
||||
Sapphire::World::Manager::TerritoryMgr::TerritoryMgr( Sapphire::FrameworkPtr pFw ) :
|
||||
BaseManager( pFw ),
|
||||
|
@ -163,12 +164,17 @@ bool Sapphire::World::Manager::TerritoryMgr::createDefaultTerritories()
|
|||
|
||||
uint32_t guid = getNextInstanceId();
|
||||
|
||||
Logger::info( "{0}\t{1}\t{2}\t{3:<10}\t{4}\t{5}",
|
||||
auto pNaviMgr = framework()->get< Manager::NaviMgr >();
|
||||
std::string bgPath = territoryInfo->bg;
|
||||
bool hasNaviMesh = pNaviMgr->setupTerritory( bgPath );
|
||||
|
||||
Logger::info( "{0}\t{1}\t{2}\t{3:<10}\t{4}\t{5}\t{6}",
|
||||
territoryTypeId,
|
||||
guid,
|
||||
territoryInfo->territoryIntendedUse,
|
||||
territoryInfo->name,
|
||||
( isPrivateTerritory( territoryTypeId ) ? "PRIVATE" : "PUBLIC" ),
|
||||
hasNaviMesh ? "NAVI" : "",
|
||||
pPlaceName->name );
|
||||
|
||||
auto pZone = make_Zone( territoryTypeId, guid, territoryInfo->name, pPlaceName->name, framework() );
|
||||
|
@ -194,7 +200,7 @@ bool Sapphire::World::Manager::TerritoryMgr::createHousingTerritories()
|
|||
auto territoryTypeId = territory.first;
|
||||
auto territoryInfo = territory.second;
|
||||
uint32_t wardNum;
|
||||
uint32_t wardMaxNum = 1;
|
||||
uint32_t wardMaxNum = 18;
|
||||
|
||||
if( territoryInfo->name.empty() )
|
||||
continue;
|
||||
|
@ -208,7 +214,7 @@ bool Sapphire::World::Manager::TerritoryMgr::createHousingTerritories()
|
|||
{
|
||||
uint32_t guid = getNextInstanceId();
|
||||
|
||||
Logger::info( "{0}\t{1}\t{2}\t{3:<10}\tHOUSING\t{4}#{5}",
|
||||
Logger::info( "{0}\t{1}\t{2}\t{3:<10}\tHOUSING\t\t{4}#{5}",
|
||||
territoryTypeId,
|
||||
guid,
|
||||
territoryInfo->territoryIntendedUse,
|
||||
|
@ -219,7 +225,6 @@ bool Sapphire::World::Manager::TerritoryMgr::createHousingTerritories()
|
|||
auto pHousingZone = make_HousingZone( wardNum, territoryTypeId, guid, territoryInfo->name,
|
||||
pPlaceName->name, framework() );
|
||||
pHousingZone->init();
|
||||
wardMaxNum = 18;
|
||||
|
||||
InstanceIdToZonePtrMap instanceMap;
|
||||
instanceMap[ guid ] = pHousingZone;
|
||||
|
|
523
src/world/Navi/NaviProvider.cpp
Normal file
523
src/world/Navi/NaviProvider.cpp
Normal file
|
@ -0,0 +1,523 @@
|
|||
#include <Common.h>
|
||||
#include <Framework.h>
|
||||
#include <Territory/Zone.h>
|
||||
#include <Logging/Logger.h>
|
||||
#include <ServerMgr.h>
|
||||
|
||||
#include <Manager/RNGMgr.h>
|
||||
|
||||
#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>
|
||||
|
||||
Sapphire::World::Navi::NaviProvider::NaviProvider( const std::string& internalName, FrameworkPtr pFw ) :
|
||||
m_naviMesh( nullptr ),
|
||||
m_naviMeshQuery( nullptr ),
|
||||
m_internalName( internalName ),
|
||||
m_pFw( pFw )
|
||||
{
|
||||
// Set defaults
|
||||
m_polyFindRange[ 0 ] = 10;
|
||||
m_polyFindRange[ 1 ] = 20;
|
||||
m_polyFindRange[ 2 ] = 10;
|
||||
}
|
||||
|
||||
bool Sapphire::World::Navi::NaviProvider::init()
|
||||
{
|
||||
auto& cfg = m_pFw->get< Sapphire::World::ServerMgr >()->getConfig();
|
||||
|
||||
auto meshesFolder = std::experimental::filesystem::path( cfg.navigation.meshPath );
|
||||
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" );
|
||||
|
||||
if( !loadMesh( baseMesh.string() ) )
|
||||
return false;
|
||||
|
||||
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
|
||||
const int32_t maxNeis = 16;
|
||||
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.
|
||||
const int32_t maxLookAhead = 6;
|
||||
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.
|
||||
const int32_t MAX_STEER_POINTS = 3;
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
&randomRef, randomPt);
|
||||
|
||||
if( dtStatusFailed( status ) )
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return { randomPt[ 0 ], randomPt[ 1 ], randomPt[ 2 ] };
|
||||
}
|
||||
|
||||
std::vector< Sapphire::Common::FFXIVARR_POSITION3 >
|
||||
Sapphire::World::Navi::NaviProvider::findFollowPath( const Common::FFXIVARR_POSITION3& startPos,
|
||||
const Common::FFXIVARR_POSITION3& endPos )
|
||||
{
|
||||
if( !m_naviMesh || !m_naviMeshQuery )
|
||||
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
|
||||
if( !startRef || !endRef )
|
||||
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 );
|
||||
|
||||
//Logger::debug( "IterPos: {0} {1} {2}; TargetPos: {3} {4} {5}",
|
||||
// iterPos[ 0 ], iterPos[ 1 ], iterPos[ 2 ],
|
||||
// targetPos[ 0 ], targetPos[ 1 ], targetPos[ 2 ] );
|
||||
|
||||
const float STEP_SIZE = 1.2f;
|
||||
const float SLOP = 0.15f;
|
||||
|
||||
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 )
|
||||
{
|
||||
resultCoords.emplace_back( Common::FFXIVARR_POSITION3{ smoothPath[ i ], smoothPath[ i + 1 ], smoothPath[ i + 2 ] } );
|
||||
}
|
||||
}
|
||||
|
||||
return resultCoords;
|
||||
}
|
||||
|
||||
bool Sapphire::World::Navi::NaviProvider::loadMesh( const std::string& path )
|
||||
{
|
||||
FILE* fp = fopen( path.c_str(), "rb" );
|
||||
if( !fp )
|
||||
{
|
||||
Logger::error( "Couldn't open navimesh file: {0}", path );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read header.
|
||||
NavMeshSetHeader header;
|
||||
|
||||
size_t readLen = fread( &header, sizeof( NavMeshSetHeader ), 1, fp );
|
||||
if( readLen != 1 )
|
||||
{
|
||||
fclose( fp );
|
||||
Logger::error( "Couldn't read NavMeshSetHeader for {0}", path );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( header.magic != NAVMESHSET_MAGIC )
|
||||
{
|
||||
fclose( fp );
|
||||
Logger::error( "'{0}' has an incorrect NavMeshSet header.", path );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( header.version != NAVMESHSET_VERSION )
|
||||
{
|
||||
fclose( fp );
|
||||
Logger::error( "'{0}' has an incorrect NavMeshSet version. Expected '{1}', got '{2}'", path, NAVMESHSET_VERSION, header.version );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !m_naviMesh )
|
||||
{
|
||||
m_naviMesh = dtAllocNavMesh();
|
||||
if( !m_naviMesh )
|
||||
{
|
||||
fclose( fp );
|
||||
Logger::error( "Couldn't allocate dtNavMesh" );
|
||||
return false;
|
||||
}
|
||||
|
||||
dtStatus status = m_naviMesh->init( &header.params );
|
||||
if( dtStatusFailed( status ) )
|
||||
{
|
||||
fclose( fp );
|
||||
Logger::error( "Couldn't initialise dtNavMesh" );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 );
|
||||
Logger::error( "Couldn't read NavMeshTileHeader from '{0}'", path );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !tileHeader.tileRef || !tileHeader.dataSize )
|
||||
break;
|
||||
|
||||
auto data = reinterpret_cast< uint8_t* >( dtAlloc( tileHeader.dataSize, DT_ALLOC_PERM ) );
|
||||
if( !data )
|
||||
break;
|
||||
memset( data, 0, tileHeader.dataSize );
|
||||
readLen = fread( data, tileHeader.dataSize, 1, fp );
|
||||
if( readLen != 1 )
|
||||
{
|
||||
dtFree( data );
|
||||
fclose( fp );
|
||||
|
||||
Logger::error( "Couldn't read tile data from '{0}'", path );
|
||||
return false;
|
||||
}
|
||||
|
||||
m_naviMesh->addTile( data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0 );
|
||||
}
|
||||
|
||||
fclose( fp );
|
||||
|
||||
return true;
|
||||
}
|
71
src/world/Navi/NaviProvider.h
Normal file
71
src/world/Navi/NaviProvider.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
#ifndef _NAVIPROVIDER_H_
|
||||
#define _NAVIPROVIDER_H_
|
||||
|
||||
#include <Common.h>
|
||||
#include "ForwardsZone.h"
|
||||
#include <recastnavigation/Detour/Include/DetourNavMesh.h>
|
||||
#include <recastnavigation/Detour/Include/DetourNavMeshQuery.h>
|
||||
|
||||
namespace Sapphire::World::Navi
|
||||
{
|
||||
const int32_t MAX_POLYS = 256;
|
||||
const int32_t MAX_SMOOTH = 2048;
|
||||
|
||||
const int32_t NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET'
|
||||
const int32_t NAVMESHSET_VERSION = 1;
|
||||
|
||||
class NaviProvider
|
||||
{
|
||||
struct NavMeshSetHeader
|
||||
{
|
||||
int32_t magic;
|
||||
int32_t version;
|
||||
int32_t numTiles;
|
||||
dtNavMeshParams params;
|
||||
};
|
||||
|
||||
struct NavMeshTileHeader
|
||||
{
|
||||
dtTileRef tileRef;
|
||||
int32_t dataSize;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit NaviProvider( const std::string& internalName, FrameworkPtr pFw );
|
||||
|
||||
bool init();
|
||||
bool loadMesh( const std::string& path );
|
||||
void initQuery();
|
||||
|
||||
void toDetourPos( const Common::FFXIVARR_POSITION3& position, float* out );
|
||||
Common::FFXIVARR_POSITION3 toGamePos( float* pos );
|
||||
|
||||
std::vector< Common::FFXIVARR_POSITION3 > findFollowPath( const Common::FFXIVARR_POSITION3& startPos,
|
||||
const Common::FFXIVARR_POSITION3& endPos );
|
||||
Common::FFXIVARR_POSITION3 findRandomPositionInCircle( const Sapphire::Common::FFXIVARR_POSITION3& startPos,
|
||||
float maxRadius );
|
||||
|
||||
bool hasNaviMesh() const;
|
||||
|
||||
protected:
|
||||
std::string m_internalName;
|
||||
|
||||
dtNavMesh* m_naviMesh;
|
||||
dtNavMeshQuery* m_naviMeshQuery;
|
||||
|
||||
float m_polyFindRange[ 3 ];
|
||||
|
||||
private:
|
||||
int32_t fixupCorridor( dtPolyRef* path, int32_t npath, int32_t maxPath, const dtPolyRef* visited, int32_t nvisited );
|
||||
int32_t fixupShortcuts( dtPolyRef* path, int32_t npath, dtNavMeshQuery* navQuery );
|
||||
inline bool inRange( const float* v1, const float* v2, const float r, const float h );
|
||||
bool getSteerTarget( dtNavMeshQuery* navQuery, const float* startPos, const float* endPos, const float minTargetDist,
|
||||
const dtPolyRef* path, const int32_t pathSize, float* steerPos, uint8_t& steerPosFlag,
|
||||
dtPolyRef& steerPosRef, float* outPoints = 0, int32_t* outPointCount = 0 );
|
||||
|
||||
FrameworkPtr m_pFw;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -275,8 +275,13 @@ void Sapphire::Network::GameConnection::gm1Handler( FrameworkPtr pFw,
|
|||
}
|
||||
case GmCommand::Hp:
|
||||
{
|
||||
targetPlayer->setHp( param1 );
|
||||
player.sendNotice( "Hp for {0} was set to {1}", targetPlayer->getName(), param1 );
|
||||
auto chara = targetActor->getAsChara();
|
||||
if( chara )
|
||||
{
|
||||
chara->setHp( param1 );
|
||||
player.sendNotice( "Hp for {0} was set to {1}", chara->getName(), param1 );
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case GmCommand::Mp:
|
||||
|
|
|
@ -19,19 +19,19 @@ namespace Sapphire::Network::Packets::Server
|
|||
public ZoneChannelPacket< FFXIVIpcActorMove >
|
||||
{
|
||||
public:
|
||||
MoveActorPacket( Entity::Chara& actor, uint8_t unk1, uint8_t unk2, uint8_t unk3, uint16_t unk4 ) :
|
||||
MoveActorPacket( Entity::Chara& actor, uint8_t unk1, uint8_t animationType, uint8_t unk3, uint16_t unk4 ) :
|
||||
ZoneChannelPacket< FFXIVIpcActorMove >( actor.getId(), actor.getId() )
|
||||
{
|
||||
initialize( actor, unk1, unk2, unk3, unk4 );
|
||||
initialize( actor, unk1, animationType, unk3, unk4 );
|
||||
};
|
||||
|
||||
private:
|
||||
void initialize( Entity::Chara& actor, uint8_t unk1, uint8_t unk2, uint8_t unk3, uint16_t unk4 )
|
||||
void initialize( Entity::Chara& actor, uint8_t unk1, uint8_t animationType, uint8_t unk3, uint16_t unk4 )
|
||||
{
|
||||
|
||||
m_data.rotation = Util::floatToUInt8Rot( actor.getRot() );
|
||||
m_data.unknown_1 = unk1;
|
||||
m_data.unknown_2 = unk2;
|
||||
m_data.animationType = animationType;
|
||||
m_data.unknown_3 = unk3;
|
||||
m_data.unknown_4 = unk4;
|
||||
m_data.posX = Util::floatToUInt16( actor.getPos().x );
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <Network/PacketDef/Zone/ServerZoneDef.h>
|
||||
#include <Network/GamePacketNew.h>
|
||||
#include <Util/Util.h>
|
||||
#include <Common.h>
|
||||
#include "Actor/Player.h"
|
||||
#include "Actor/BNpc.h"
|
||||
#include "Forwards.h"
|
||||
|
@ -39,17 +40,12 @@ namespace Sapphire::Network::Packets::Server
|
|||
m_data.mPMax = bnpc.getMaxMp();
|
||||
m_data.subtype = 5;
|
||||
|
||||
//m_data.tPMax = 3000;
|
||||
m_data.level = bnpc.getLevel();
|
||||
m_data.pose = bnpc.getPose();
|
||||
|
||||
memcpy( m_data.look, bnpc.getLookArray(), sizeof( m_data.look ) );
|
||||
|
||||
auto models = bnpc.getModelArray();
|
||||
memcpy( m_data.models, bnpc.getModelArray(), sizeof( m_data.models ) );
|
||||
|
||||
memcpy( m_data.look, bnpc.getLookArray(), sizeof( m_data.look ) );
|
||||
|
||||
m_data.pos.x = bnpc.getPos().x;
|
||||
m_data.pos.y = bnpc.getPos().y;
|
||||
m_data.pos.z = bnpc.getPos().z;
|
||||
|
@ -61,10 +57,8 @@ namespace Sapphire::Network::Packets::Server
|
|||
m_data.aggressionMode = bnpc.getAggressionMode();
|
||||
|
||||
m_data.classJob = 0;
|
||||
//m_data.voice = bnpc.getVoiceId();
|
||||
//m_data.currentMount = bnpc.getCurrentMount();
|
||||
|
||||
//m_data.onlineStatus = static_cast< uint8_t >( bnpc.getOnlineStatus() );
|
||||
m_data.targetId = Common::INVALID_GAME_OBJECT_ID;
|
||||
|
||||
//m_data.u23 = 0x04;
|
||||
//m_data.u24 = 256;
|
||||
|
@ -81,25 +75,13 @@ namespace Sapphire::Network::Packets::Server
|
|||
|
||||
if( !target.isActorSpawnIdValid( m_data.spawnIndex ) )
|
||||
return;
|
||||
|
||||
// 0x20 == spawn hidden to be displayed by the spawneffect control
|
||||
//m_data.displayFlags = bnpc.getDisplayFlags();
|
||||
|
||||
/*if( bnpc.getZoningType() != Common::ZoneingType::None )
|
||||
{
|
||||
m_data.displayFlags |= static_cast< uint16_t >( Common::DisplayFlags::Invisible );
|
||||
}*/
|
||||
|
||||
//m_data.currentMount = bnpc.getCurrentMount();
|
||||
//m_data.persistentEmote = bnpc.getPersistentEmote();
|
||||
|
||||
m_data.targetId = static_cast< uint64_t >( bnpc.getTargetId() );
|
||||
//m_data.type = 1;
|
||||
//m_data.unknown_33 = 4;
|
||||
//m_data.unknown_38 = 0x70;
|
||||
//m_data.unknown_60 = 3;
|
||||
//m_data.unknown_61 = 7;
|
||||
|
||||
|
||||
uint64_t currentTimeMs = Sapphire::Util::getTimeMs();
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ bool Sapphire::Scripting::ScriptMgr::init()
|
|||
|
||||
if( !status )
|
||||
{
|
||||
Logger::error( "ScriptMgr: failed to load scripts, the server will not function correctly without scripts loaded." );
|
||||
Logger::error( "ScriptMgr: failed to load modules, the server will not function correctly without scripts loaded." );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "Manager/ItemMgr.h"
|
||||
#include "Manager/MarketMgr.h"
|
||||
#include "Manager/RNGMgr.h"
|
||||
#include "Manager/NaviMgr.h"
|
||||
|
||||
using namespace Sapphire::World::Manager;
|
||||
|
||||
|
@ -95,6 +96,8 @@ bool Sapphire::World::ServerMgr::loadSettings( int32_t argc, char* argv[] )
|
|||
m_config.scripts.path = pConfig->getValue< std::string >( "Scripts", "Path", "./compiledscripts/" );
|
||||
m_config.scripts.cachePath = pConfig->getValue< std::string >( "Scripts", "CachePath", "./cache/" );
|
||||
|
||||
m_config.navigation.meshPath = pConfig->getValue< std::string >( "Navigation", "MeshPath", "navi" );
|
||||
|
||||
m_config.network.disconnectTimeout = pConfig->getValue< uint16_t >( "Network", "DisconnectTimeout", 20 );
|
||||
m_config.network.listenIp = pConfig->getValue< std::string >( "Network", "ListenIp", "0.0.0.0" );
|
||||
m_config.network.listenPort = pConfig->getValue< uint16_t >( "Network", "ListenPort", 54992 );
|
||||
|
@ -126,6 +129,8 @@ void Sapphire::World::ServerMgr::run( int32_t argc, char* argv[] )
|
|||
return;
|
||||
}
|
||||
|
||||
Logger::setLogLevel( m_config.global.general.logLevel );
|
||||
|
||||
Logger::info( "Setting up generated EXD data" );
|
||||
auto pExdData = std::make_shared< Data::ExdDataGenerated >();
|
||||
auto dataPath = m_config.global.general.dataPath;
|
||||
|
@ -166,6 +171,9 @@ void Sapphire::World::ServerMgr::run( int32_t argc, char* argv[] )
|
|||
|
||||
loadBNpcTemplates();
|
||||
|
||||
auto pNaviMgr = std::make_shared< Manager::NaviMgr >( framework() );
|
||||
framework()->set< Manager::NaviMgr >( pNaviMgr );
|
||||
|
||||
Logger::info( "TerritoryMgr: Setting up zones" );
|
||||
auto pTeriMgr = std::make_shared< Manager::TerritoryMgr >( framework() );
|
||||
auto pHousingMgr = std::make_shared< Manager::HousingMgr >( framework() );
|
||||
|
|
|
@ -79,6 +79,7 @@ Sapphire::Zone::Zone( uint16_t territoryTypeId, uint32_t guId,
|
|||
|
||||
m_weatherOverride = Weather::None;
|
||||
m_territoryTypeInfo = pExdData->get< Sapphire::Data::TerritoryType >( territoryTypeId );
|
||||
m_bgPath = m_territoryTypeInfo->bg;
|
||||
|
||||
loadWeatherRates();
|
||||
loadSpawnGroups();
|
||||
|
@ -352,6 +353,11 @@ const std::string& Sapphire::Zone::getInternalName() const
|
|||
return m_internalName;
|
||||
}
|
||||
|
||||
const std::string& Sapphire::Zone::getBgPath() const
|
||||
{
|
||||
return m_bgPath;
|
||||
}
|
||||
|
||||
std::size_t Sapphire::Zone::getPopCount() const
|
||||
{
|
||||
return m_playerMap.size();
|
||||
|
@ -381,49 +387,48 @@ bool Sapphire::Zone::checkWeather()
|
|||
|
||||
void Sapphire::Zone::updateBNpcs( int64_t tickCount )
|
||||
{
|
||||
if( ( tickCount - m_lastMobUpdate ) > 250 )
|
||||
{
|
||||
m_lastMobUpdate = tickCount;
|
||||
uint32_t currTime = Sapphire::Util::getTimeSeconds();
|
||||
if( ( tickCount - m_lastMobUpdate ) <= 250 )
|
||||
return;
|
||||
|
||||
/*for( auto it3 = m_BattleNpcDeadMap.begin(); it3 != m_BattleNpcDeadMap.end(); ++it3 )
|
||||
m_lastMobUpdate = tickCount;
|
||||
uint32_t currTime = Sapphire::Util::getTimeSeconds();
|
||||
|
||||
for( auto entry : m_bNpcMap )
|
||||
{
|
||||
Entity::BNpcPtr pBNpc = entry.second;
|
||||
|
||||
if( !pBNpc )
|
||||
continue;
|
||||
|
||||
if( !pBNpc->isAlive() && currTime - pBNpc->getTimeOfDeath() > 10 )
|
||||
{
|
||||
removeActor( pBNpc );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for( uint32_t x = 0; x < _sizeX; x++ )
|
||||
{
|
||||
for( uint32_t y = 0; y < _sizeY; ++y )
|
||||
{
|
||||
auto cell = getCellPtr( x, y );
|
||||
if( !cell )
|
||||
continue;
|
||||
|
||||
// todo: this is a pretty shit because we will visit the same cells multiple times over
|
||||
// ideally we run a pass every tick and cache active cells during that initial pass over every cell
|
||||
// that way we don't have an expensive lookup for every actor
|
||||
|
||||
if( !isCellActive( x, y ) )
|
||||
continue;
|
||||
|
||||
for( const auto& actor : cell->m_actors )
|
||||
{
|
||||
|
||||
Entity::BattleNpcPtr pBNpc = *it3;
|
||||
|
||||
if( ( currTime - pBNpc->getTimeOfDeath() ) > 60 )
|
||||
{
|
||||
|
||||
pBNpc->resetHp();
|
||||
pBNpc->resetMp();
|
||||
pBNpc->resetPos();
|
||||
pushActor( pBNpc );
|
||||
|
||||
m_BattleNpcDeadMap.erase( it3 );
|
||||
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
for( auto entry : m_bNpcMap )
|
||||
{
|
||||
Entity::BNpcPtr pBNpc = entry.second;
|
||||
|
||||
if( !pBNpc )
|
||||
continue;
|
||||
|
||||
//if( !pBNpc->isAlive() && currTime - pBNpc->getTimeOfDeath() > ( 10 ) )
|
||||
//{
|
||||
// removeActor( pBNpc );
|
||||
// m_BattleNpcDeadMap.insert( pBNpc );
|
||||
// break;
|
||||
//}
|
||||
|
||||
pBNpc->update( tickCount );
|
||||
|
||||
if( actor->isBattleNpc() )
|
||||
actor->getAsBNpc()->update( tickCount );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -782,7 +787,7 @@ bool Sapphire::Zone::loadSpawnGroups()
|
|||
|
||||
m_spawnGroups.emplace_back( id, templateId, level, maxHp );
|
||||
|
||||
Logger::debug( "id: {0}, template: {1}, level: {2}, maxHp: {3}", id, m_spawnGroups.back().getTemplateId(), level, maxHp );
|
||||
Logger::trace( "id: {0}, template: {1}, level: {2}, maxHp: {3}", id, m_spawnGroups.back().getTemplateId(), level, maxHp );
|
||||
}
|
||||
|
||||
res.reset();
|
||||
|
@ -805,7 +810,7 @@ bool Sapphire::Zone::loadSpawnGroups()
|
|||
|
||||
group.getSpawnPointList().emplace_back( std::make_shared< Entity::SpawnPoint >( x, y, z, r, gimmickId ) );
|
||||
|
||||
Logger::debug( "id: {0}, x: {1}, y: {2}, z: {3}, gimmickId: {4}", id, x, y, z, gimmickId );
|
||||
Logger::trace( "id: {0}, x: {1}, y: {2}, z: {3}, gimmickId: {4}", id, x, y, z, gimmickId );
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -844,7 +849,13 @@ void Sapphire::Zone::updateSpawnPoints()
|
|||
|
||||
pushActor( pBNpc );
|
||||
}
|
||||
else if( point->getLinkedBNpc() && !point->getLinkedBNpc()->isAlive() )
|
||||
{
|
||||
point->setTimeOfDeath( Util::getTimeSeconds() );
|
||||
point->setLinkedBNpc( nullptr );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace Sapphire
|
|||
|
||||
std::string m_placeName;
|
||||
std::string m_internalName;
|
||||
std::string m_bgPath;
|
||||
|
||||
std::unordered_map< int32_t, Entity::PlayerPtr > m_playerMap;
|
||||
std::unordered_map< int32_t, Entity::BNpcPtr > m_bNpcMap;
|
||||
|
@ -135,6 +136,8 @@ namespace Sapphire
|
|||
|
||||
const std::string& getInternalName() const;
|
||||
|
||||
const std::string& getBgPath() const;
|
||||
|
||||
std::size_t getPopCount() const;
|
||||
|
||||
void loadWeatherRates();
|
||||
|
|
Loading…
Add table
Reference in a new issue