diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake index fa32b7b3..5272b2b3 100644 --- a/cmake/compiler.cmake +++ b/cmake/compiler.cmake @@ -1,6 +1,7 @@ if( UNIX ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fPIC" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O3") else() add_definitions( -D_WIN32_WINNT=0x601 ) add_definitions( -D_CRT_SECURE_NO_WARNINGS ) diff --git a/deps/datReader/GameData.cpp b/deps/datReader/GameData.cpp index 9d19644a..4b4a71e4 100644 --- a/deps/datReader/GameData.cpp +++ b/deps/datReader/GameData.cpp @@ -57,9 +57,16 @@ GameData::GameData(const std::experimental::filesystem::path& path) try : m_path(path) { int maxExLevel = 0; + + // msvc has retarded stdlib implementation +#ifdef _WIN32 + static constexpr auto sep = "\\"; +#else + static constexpr auto sep = std::experimental::filesystem::path::preferred_separator; +#endif // Determine which expansions are available - while( std::experimental::filesystem::exists( std::experimental::filesystem::path( m_path.string() + "\\ex" + std::to_string( maxExLevel + 1) + "\\ex" + std::to_string( maxExLevel + 1) + ".ver" ) ) ) + while( std::experimental::filesystem::exists( std::experimental::filesystem::path( m_path.string() + sep + "ex" + std::to_string( maxExLevel + 1 ) + sep + "ex" + std::to_string( maxExLevel + 1 ) + ".ver" ) ) ) { maxExLevel++; } @@ -90,7 +97,7 @@ GameData::GameData(const std::experimental::filesystem::path& path) try : // Check for expansion for( int exNum = 1; exNum <= maxExLevel; exNum++ ) { - const std::string path = m_path.string() + "\\" + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, 0, "win32", "index" ); + const std::string path = m_path.string() + sep + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, 0, "win32", "index" ); if( std::experimental::filesystem::exists( std::experimental::filesystem::path( path ) ) ) { @@ -99,7 +106,7 @@ GameData::GameData(const std::experimental::filesystem::path& path) try : for(int chunkTest = 0; chunkTest < 256; chunkTest++ ) { - if( std::experimental::filesystem::exists( m_path.string() + "\\" + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, chunkTest, "win32", "index" ) ) ) + if( std::experimental::filesystem::exists( m_path.string() + sep + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, chunkTest, "win32", "index" ) ) ) { m_exCats[cat_nb].exNumToChunkMap[exNum].chunkToCatMap[chunkTest] = std::unique_ptr(); chunkCount++; diff --git a/src/common/Common.h b/src/common/Common.h index 6dfc5e70..1735d54b 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -615,6 +615,7 @@ namespace Sapphire::Common InvincibilityNone, InvincibilityRefill, InvincibilityStayAlive, + InvincibilityIgnoreDamage, }; enum PlayerStateFlag : uint8_t diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index ef70853c..65de5804 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -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; diff --git a/src/tools/pcb_reader/CMakeLists.txt b/src/tools/pcb_reader/CMakeLists.txt index 96b4af4e..d42aa873 100644 --- a/src/tools/pcb_reader/CMakeLists.txt +++ b/src/tools/pcb_reader/CMakeLists.txt @@ -3,14 +3,17 @@ 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}) if (UNIX) - target_link_libraries( pcb_reader2 common xivdat pthread mysqlclient dl z stdc++fs Recast Detour ) + target_link_libraries( pcb_reader2 common xivdat pthread mysqlclient dl z stdc++fs Recast Detour DetourTileCache ) else() - target_link_libraries( pcb_reader2 common xivdat mysql zlib Recast Detour ) + target_link_libraries( pcb_reader2 common xivdat mysql zlib Recast Detour DetourTileCache ) endif() target_include_directories( pcb_reader2 diff --git a/src/tools/pcb_reader/cache.h b/src/tools/pcb_reader/cache.h index a6cbced7..3821fd07 100644 --- a/src/tools/pcb_reader/cache.h +++ b/src/tools/pcb_reader/cache.h @@ -83,7 +83,7 @@ private: m_lgbCache.clear(); m_sgbCache.clear(); m_pcbCache.clear(); - std::cout << "Purged PCB/SGB/PCB cache \n"; + std::cout << "Purged PCB/SGB/LGB cache \n"; m_totalFiles = 1; } diff --git a/src/tools/pcb_reader/exportmgr.h b/src/tools/pcb_reader/exportmgr.h index 656e15f7..c448b5d2 100644 --- a/src/tools/pcb_reader/exportmgr.h +++ b/src/tools/pcb_reader/exportmgr.h @@ -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() diff --git a/src/tools/pcb_reader/lgb.h b/src/tools/pcb_reader/lgb.h index 21628b68..d59feb2c 100644 --- a/src/tools/pcb_reader/lgb.h +++ b/src/tools/pcb_reader/lgb.h @@ -9,6 +9,10 @@ #include #include +#include +#include +#include + #include "matrix4.h" #include "vec3.h" #include "sgb.h" @@ -238,6 +242,47 @@ public: }; }; +struct LGB_COLLISION_BOX_HEADER : + public LGB_ENTRY_HEADER +{ + uint8_t unk[100]; +}; + +struct LGB_COLLISION_BOX_ENTRY : + public LGB_ENTRY +{ + LGB_COLLISION_BOX_HEADER header; + std::string name; + + LGB_COLLISION_BOX_ENTRY( char* buf, uint32_t offset ) : + LGB_ENTRY( buf, offset ) + { + header = *reinterpret_cast< LGB_COLLISION_BOX_HEADER* >( buf + offset ); + header.type = LgbEntryType::CollisionBox; + name = std::string( buf + offset + header.nameOffset ); + std::stringstream ss; + ss << "\nName: " << name << "Id: " << header.unknown << "\n"; + ss << "Pos: " << header.translation.x << " " << header.translation.y << " " << header.translation.z << "\n"; + ss << "Rot?: " << header.rotation.x << " " << header.rotation.y << " " << header.rotation.z << "\n"; + ss << "Scale?: " << header.scale.x << " " << header.scale.y << " " << header.scale.z << "\n"; + ss << "00 01 02 03 04 05 06 07 | 08 09 0A 0B 0C 0D 0E 0F\n"; + ss << "-------------------------------------------------\n"; + ss << std::hex; + ss << std::setw( 2 ); + ss << std::setfill( '0' ); + + for( auto i = 1; i < sizeof( header.unk ); ++i ) + if( i % 16 == 0 ) + ss << std::setw(2) << (int)header.unk[i - 1] << "\n"; + else if( i % 8 == 0 ) + ss << std::setw(2) << (int)header.unk[i - 1] << " | "; + else + ss << std::setw(2) << (int)header.unk[i - 1] << " "; + ss << "\n"; + std::cout << ss.str(); + } +}; + struct LGB_GROUP_HEADER { uint32_t unknown; @@ -289,7 +334,11 @@ struct LGB_GROUP case LgbEntryType::EventObject: entries.push_back( std::make_shared< LGB_EOBJ_ENTRY >( buf, entryOffset ) ); break; + case LgbEntryType::CollisionBox: + entries.push_back( std::make_shared< LGB_COLLISION_BOX_ENTRY >( buf, entryOffset ) ); + break; default: + //std::cout << "\t\tUnknown SGB entry! Group: " << name << " type: " << ( int )type << " index: " << i << " entryOffset: " << entryOffset << "\n"; break; } } diff --git a/src/tools/pcb_reader/main.cpp b/src/tools/pcb_reader/main.cpp index bb90af0f..0856e7e5 100644 --- a/src/tools/pcb_reader/main.cpp +++ b/src/tools/pcb_reader/main.cpp @@ -34,12 +34,15 @@ bool noObj = false; std::string gamePath( "C:\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\sqpack" ); std::unordered_map< uint16_t, std::string > zoneNameMap; +std::map< std::string, std::string > exportedTeriMap; + uint32_t zoneId; std::set< std::string > zoneDumpList; std::shared_ptr< Cache > pCache; +std::map< uint32_t, uint16_t > eobjSgbPaths; xiv::dat::GameData* data1 = nullptr; xiv::exd::ExdData* eData = nullptr; @@ -61,6 +64,49 @@ void initExd( const std::string& gamePath ) pCache = std::make_shared< Cache >( data1 ); } +void replaceAll( std::string& str, const std::string& from, const std::string& to ) { + if( from.empty() ) + return; + size_t start_pos = 0; + while( ( start_pos = str.find( from, start_pos ) ) != std::string::npos ) { + str.replace( start_pos, from.length(), to ); + start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + } +} + +std::string getEobjSgbPath( uint32_t eobjId ) +{ + static std::map< uint16_t, std::string > exportedSgMap; + + if( !exportedSgMap.empty() ) + return exportedSgMap[ eobjSgbPaths[ eobjId ] ]; + + auto& eobjCat = eData->get_category( "EObj" ); + auto eObjExd = static_cast< xiv::exd::Exd >( eobjCat.get_data_ln( xiv::exd::Language::none ) ); + + auto& exportedSgCat = eData->get_category( "ExportedSG" ); + auto exportedSgExd = static_cast< xiv::exd::Exd >( exportedSgCat.get_data_ln( xiv::exd::Language::none ) ); + + for( auto& row : exportedSgExd.get_rows() ) + { + auto id = row.first; + auto& fields = row.second; + + auto path = std::get< std::string >( fields.at( 0 ) ); + exportedSgMap[id] = path; + } + + uint16_t exportedSgId{0}; + + for( auto& row : eObjExd.get_rows() ) + { + auto id = row.first; + auto& fields = row.second; + + eobjSgbPaths[id] = std::get< uint16_t >( fields.at( 11 ) ); + } + return exportedSgMap[exportedSgId]; +} std::string zoneNameToPath( const std::string& name ) { @@ -116,7 +162,10 @@ int main( int argc, char* argv[] ) { return arg == "--dump-all"; } ) != argVec.end(); bool generateNavmesh = std::remove_if( argVec.begin(), argVec.end(), []( auto arg ) { return arg == "--navmesh"; } ) != argVec.end(); - + bool splitByGroup = std::remove_if( argVec.begin(), argVec.end(), []( auto arg ) + { return arg == "--split-by-group"; }) != argVec.end(); + bool splitByZone = std::remove_if( argVec.begin(), argVec.end(), []( auto arg ) + { return arg == "--split-by-zone"; }) != argVec.end(); int exportFileType = 0; if( !noObj ) exportFileType |= ExportFileType::WavefrontObj; @@ -137,10 +186,11 @@ int main( int argc, char* argv[] ) try { initExd( gamePath ); + getEobjSgbPath( 0 ); } catch( std::exception& e ) { - printf( "Unable to initialise EXD! Usage: pcb_reader \"path/to/FINAL FANTASY XIV - A REALM REBORN/game/sqpack\" [--no-obj, --dump-all, --navmesh]" ); + printf( "Unable to initialise EXD!\n Usage: pcb_reader \"path/to/FINAL FANTASY XIV - A REALM REBORN/game/sqpack\" [--no-obj, --dump-all, --navmesh]\n" ); return -1; } ExportMgr exportMgr; @@ -156,14 +206,19 @@ int main( int argc, char* argv[] ) zoneDumpList.emplace( zoneName ); } - for( const auto& zoneName : zoneDumpList ) + for( auto zoneName : zoneDumpList ) { try { + const auto& zonePath = zoneNameToPath( zoneName ); + if( exportedTeriMap.find( zonePath ) != exportedTeriMap.end() ) + continue; + + zoneName = zonePath.substr( zonePath.find_last_of( '/' ) ); + ExportedZone exportedZone; exportedZone.name = zoneName; - - const auto& zonePath = zoneNameToPath( zoneName ); + exportedTeriMap[ zonePath ] = zoneName; std::string listPcbPath( zonePath + "/collision/list.pcb" ); std::string bgLgbPath( zonePath + "/level/bg.lgb" ); @@ -324,8 +379,8 @@ int main( int argc, char* argv[] ) if( auto pPcbFile = pCache->getPcbFile( fileName ) ) buildModelEntry( pPcbFile, exportedTerrainGroup, fileName, zoneName ); } - exportMgr.exportGroup( zoneName, exportedTerrainGroup, ( ExportFileType )exportFileType ); - + exportedZone.groups.emplace( exportedTerrainGroup.name, exportedTerrainGroup ); + for( const auto& lgb : lgbList ) { for( const auto& group : lgb.groups ) @@ -351,12 +406,51 @@ int main( int argc, char* argv[] ) } return true; }; + auto exportSgbModel = [&]( const std::string& sgbFilePath, LGB_ENTRY* pGimmick, bool isEobj = false ) + { + if( auto pSgbFile = pCache->getSgbFile( sgbFilePath ) ) + { + const auto& sgbFile = *pSgbFile; + for( const auto& group : sgbFile.entries ) + { + for( const auto& pSgbEntry : group.entries ) + { + auto pModel = dynamic_cast< SGB_MODEL_ENTRY* >( pSgbEntry.get() ); + fileName = pModel->collisionFileName; + if( pModel->type == SgbGroupEntryType::Gimmick ) + { + if( auto pSubSgbFile = pCache->getSgbFile( pModel->modelFileName ) ) + { + for( const auto& subGroup : pSubSgbFile->entries ) + { + for( const auto& pSubEntry : subGroup.entries ) + { + auto pSubModel = dynamic_cast< SGB_MODEL_ENTRY* >( pSubEntry.get() ); + std::string subModelFile = pSubModel->modelFileName; + //"bg/ex1/02_dra_d2/alx/common/bgparts/d2a0_a7_btog2.mdl" + //"bg/ex1/02_dra_d2/alx/common/collision/d2a0_a1_twl01.pcb" + replaceAll( subModelFile, "/bgparts/", "/collision/" ); + replaceAll( subModelFile, ".mdl", ".pcb "); + if( pSubModel && pSubModel->type == SgbGroupEntryType::Model ) + pcbTransformModel( subModelFile, &pGimmick->header.scale, &pGimmick->header.rotation, + &pGimmick->header.translation, pSubModel ); + } + } + } + } + pcbTransformModel( fileName, &pGimmick->header.scale, &pGimmick->header.rotation, + &pGimmick->header.translation, pModel ); + + } + } + } + }; switch( pEntry->getType() ) { case LgbEntryType::BgParts: { - auto pBgParts = static_cast(pEntry.get()); + auto pBgParts = static_cast< LGB_BGPARTS_ENTRY* >( pEntry.get() ); fileName = pBgParts->collisionFileName; pcbTransformModel( fileName, &pBgParts->header.scale, &pBgParts->header.rotation, &pBgParts->header.translation ); @@ -366,37 +460,41 @@ int main( int argc, char* argv[] ) // gimmick entry case LgbEntryType::Gimmick: { - auto pGimmick = static_cast( pEntry.get() ); - if( auto pSgbFile = pCache->getSgbFile( pGimmick->gimmickFileName ) ) - { - const auto& sgbFile = *pSgbFile; - for( const auto& group : sgbFile.entries ) - { - for( const auto& pEntry : group.entries ) - { - auto pModel = dynamic_cast< SGB_MODEL_ENTRY* >( pEntry.get() ); - fileName = pModel->collisionFileName; - pcbTransformModel( fileName, &pGimmick->header.scale, &pGimmick->header.rotation, - &pGimmick->header.translation, pModel ); - } - } - } + auto pGimmick = static_cast< LGB_GIMMICK_ENTRY* >( pEntry.get() ); + + exportSgbModel( pGimmick->gimmickFileName, pGimmick ); } + break; case LgbEntryType::EventObject: { + auto pEobj = static_cast< LGB_EOBJ_ENTRY* >( pEntry.get() ); pcbTransformModel( fileName, &pEntry->header.scale, &pEntry->header.rotation, &pEntry->header.translation ); + + auto sgbPath = getEobjSgbPath( pEobj->header.eobjId ); + if ( !sgbPath.empty() ) + { + exportSgbModel( sgbPath, pEobj, true ); + + if( auto pGimmick = pCache->getSgbFile( sgbPath ) ) + { + for( const auto& offset1cFile : pGimmick->offset1cObjects ) + exportSgbModel( offset1cFile, pEobj, true ); + } + } + } break; default: break; } } - exportMgr.exportGroup( zoneName, exportedGroup, ( ExportFileType )exportFileType ); - //exportedZone.groups.emplace( group.name, exportedGroup ); + if( splitByGroup ) + exportMgr.exportGroup( zoneName, exportedGroup, ( ExportFileType )exportFileType ); + exportedZone.groups.emplace( group.name, exportedGroup ); } } - //exportMgr.exportZone( exportedZone, ( ExportFileType )exportFileType ); + exportMgr.exportZone( exportedZone, static_cast< ExportFileType >( exportFileType ) ); printf( "Exported %s in %lu seconds \n", @@ -416,8 +514,6 @@ int main( int argc, char* argv[] ) 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; diff --git a/src/tools/pcb_reader/nav/TiledNavmeshGenerator.cpp b/src/tools/pcb_reader/nav/TiledNavmeshGenerator.cpp new file mode 100644 index 00000000..55f00c19 --- /dev/null +++ b/src/tools/pcb_reader/nav/TiledNavmeshGenerator.cpp @@ -0,0 +1,581 @@ +#include "TiledNavmeshGenerator.h" + +#include +#include + +#include + +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() +{ + if( m_mesh ) + delete m_mesh; + if( m_chunkyMesh ) + delete m_chunkyMesh; + + if( m_ctx ) + 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; +} diff --git a/src/tools/pcb_reader/nav/TiledNavmeshGenerator.h b/src/tools/pcb_reader/nav/TiledNavmeshGenerator.h new file mode 100644 index 00000000..64785c23 --- /dev/null +++ b/src/tools/pcb_reader/nav/TiledNavmeshGenerator.h @@ -0,0 +1,124 @@ +#ifndef SAPPHIRE_TILEDNAVMESHGENERATOR_H +#define SAPPHIRE_TILEDNAVMESHGENERATOR_H + +#include +#include +#include + +#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 diff --git a/src/tools/pcb_reader/nav/ext/ChunkyTriMesh.cpp b/src/tools/pcb_reader/nav/ext/ChunkyTriMesh.cpp new file mode 100644 index 00000000..7b5ef0d6 --- /dev/null +++ b/src/tools/pcb_reader/nav/ext/ChunkyTriMesh.cpp @@ -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 +#include +#include + +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(inum), sizeof( BoundsItem ), compareItemX ); + } + else if( axis == 1 ) + { + // Sort along y-axis + qsort( items + imin, static_cast(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; +} diff --git a/src/tools/pcb_reader/nav/ext/ChunkyTriMesh.h b/src/tools/pcb_reader/nav/ext/ChunkyTriMesh.h new file mode 100644 index 00000000..24eb5890 --- /dev/null +++ b/src/tools/pcb_reader/nav/ext/ChunkyTriMesh.h @@ -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 diff --git a/src/tools/pcb_reader/nav/ext/MeshLoaderObj.cpp b/src/tools/pcb_reader/nav/ext/MeshLoaderObj.cpp new file mode 100644 index 00000000..08c9c7f1 --- /dev/null +++ b/src/tools/pcb_reader/nav/ext/MeshLoaderObj.cpp @@ -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 +#include +#include + +#define _USE_MATH_DEFINES + +#include + +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; +} diff --git a/src/tools/pcb_reader/nav/ext/MeshLoaderObj.h b/src/tools/pcb_reader/nav/ext/MeshLoaderObj.h new file mode 100644 index 00000000..1b3f9c56 --- /dev/null +++ b/src/tools/pcb_reader/nav/ext/MeshLoaderObj.h @@ -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 + +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 diff --git a/src/tools/pcb_reader/navmesh_exporter.h b/src/tools/pcb_reader/navmesh_exporter.h index c3d4d0c6..79608607 100644 --- a/src/tools/pcb_reader/navmesh_exporter.h +++ b/src/tools/pcb_reader/navmesh_exporter.h @@ -9,17 +9,13 @@ #include #include "exporter.h" -/* -#include -#include -#include -#include -#include -#include -#include -#include -#include -*/ +#include "obj_exporter.h" +#include "nav/TiledNavmeshGenerator.h" + +#include + +namespace fs = std::experimental::filesystem; + class NavmeshExporter { public: @@ -27,11 +23,35 @@ public: { auto start = std::chrono::high_resolution_clock::now(); - auto fileName = zone.name + ".obj"; + static std::string currPath = std::experimental::filesystem::current_path().string(); + + auto dir = fs::current_path().string() + "/pcb_export/" + zone.name + "/"; + auto fileName = dir + zone.name; + + std::error_code e; + if( !fs::exists( fileName, e ) ) + ObjExporter::exportZone( zone ); + + TiledNavmeshGenerator gen; + + auto objName = fileName + ".obj"; + if( !gen.init( objName ) ) + { + printf( "[Navmesh] failed to init TiledNavmeshGenerator for file '%s'\n", zone.name.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 end = std::chrono::high_resolution_clock::now(); printf( "[Navmesh] Finished exporting %s in %lu ms\n", - fileName.c_str(), + zone.name.c_str(), std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() ); } @@ -39,344 +59,38 @@ public: { auto start = std::chrono::high_resolution_clock::now(); - auto fileName = zoneName + "_" + group.name + ".obj"; + static std::string currPath = std::experimental::filesystem::current_path().string(); + + + auto dir = fs::current_path().string() + "/pcb_export/" + zoneName + "/"; + auto fileName = dir + zoneName + "_" + group.name; + + std::error_code e; + if( !fs::exists( fileName, e ) ) + ObjExporter::exportGroup( zoneName, group ); + + + TiledNavmeshGenerator gen; + + auto objName = fileName + ".obj"; + if( !gen.init( objName ) ) + { + 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", fileName.c_str() ); + return; + } + + gen.saveNavmesh( fileName ); auto end = std::chrono::high_resolution_clock::now(); - printf( "[Navmesh] Finished exporting %s in %lu ms\n", fileName.c_str(), std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() ); } -private: - /*/ - 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(); - rcCalcBounds( mesh.verts.data(), size / 3, &cfg.bmin[0], &cfg.bmax[0] ); - verts.reserve( verts.size() + size ); - memcpy( &verts[i], mesh.verts.data(), size ); - i += size; - - size = mesh.indices.size(); - indices.reserve( 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; - } - } - - auto chunkyMesh = new rcChunkyTriMesh; - rcCreateChunkyTriMesh( &verts[0], &indices[0], verts.size() / 3, 256, chunkyMesh ); - 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. - const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); - if (!ncid) - return 0; - - auto tileTriCount = 0; - auto triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; - for (int i = 0; i < ncid; ++i) - { - const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]]; - const int* ctris = &chunkyMesh->tris[node.i*3]; - const int nctris = node.n; - - tileTriCount += nctris; - - memset(triareas, 0, nctris*sizeof(unsigned char)); - rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, - &verts[0], verts.size() / 3, ctris, nctris, triareas); - - if (!rcRasterizeTriangles(&ctx, &verts[0], verts.size() / 3, ctris, triareas, nctris, *hf, cfg.walkableClimb)) - return 0; - } - - { - delete [] triareas; - triareas = 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; - } - - unsigned char* navData = 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 diff --git a/src/tools/pcb_reader/obj_exporter.h b/src/tools/pcb_reader/obj_exporter.h index e1a35747..b01ad970 100644 --- a/src/tools/pcb_reader/obj_exporter.h +++ b/src/tools/pcb_reader/obj_exporter.h @@ -14,23 +14,23 @@ class ObjExporter { public: - static void exportZone( const ExportedZone& zone ) + static std::string exportZone( const ExportedZone& zone ) { static std::string currPath = std::experimental::filesystem::current_path().string(); 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; + return ""; } } std::ofstream of( fileName, std::ios::trunc ); @@ -50,27 +50,29 @@ public: } auto end = std::chrono::high_resolution_clock::now(); + printf( "[Obj] Finished exporting %s in %lu ms\n", - fileName.c_str(), - std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() ); + fileName.substr( fileName.find( "pcb_export" ) - 1 ).c_str(), + std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() ); + return fileName; } - static void exportGroup( const std::string& zoneName, const ExportedGroup& group ) + static std::string 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 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; + return ""; } } std::ofstream of( fileName, std::ios::trunc ); @@ -88,8 +90,10 @@ public: auto end = std::chrono::high_resolution_clock::now(); printf( "[Obj] Finished exporting %s in %lu ms\n", - fileName.c_str(), - std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() ); + fileName.substr( fileName.find( "pcb_export" ) - 1 ).c_str(), + std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() ); + + return fileName; } private: static void exportGroup( const ExportedGroup& group, std::ofstream& of, int& indicesOffset, int& modelCount ) diff --git a/src/tools/pcb_reader/sgb.h b/src/tools/pcb_reader/sgb.h index 6e471be5..b3264055 100644 --- a/src/tools/pcb_reader/sgb.h +++ b/src/tools/pcb_reader/sgb.h @@ -36,6 +36,7 @@ enum SgbGroupEntryType : uint32_t { Model = 0x01, + Gimmick = 0x06, }; struct SGB_GROUP_HEADER @@ -64,6 +65,35 @@ struct SGB_GROUP_HEADER uint32_t unknown44; }; +struct SGB_GROUP1C_HEADER +{ + SgbDataType type; + int32_t nameOffset; + uint32_t unknown08; + + int32_t entryCount; + uint32_t unknown14; + int32_t modelFileOffset; + vec3 unknownFloat3; + vec3 unknownFloat3_2; + int32_t stateOffset; + int32_t modelFileOffset2; + uint32_t unknown3; + float unknown4; + int32_t nameOffset2; + vec3 unknownFloat3_3; +}; + +struct SGB_GROUP1C_ENTRY +{ + uint32_t unk; + uint32_t unk2; + int32_t nameOffset; + uint32_t index; + uint32_t unk3; + int32_t modelFileOffset; +}; + struct SGB_GROUP_ENTRY { public: @@ -113,8 +143,9 @@ struct SGB_MODEL_ENTRY : std::string modelFileName; std::string collisionFileName; - SGB_MODEL_ENTRY( char* buf, uint32_t offset ) + SGB_MODEL_ENTRY( char* buf, uint32_t offset, SgbGroupEntryType type ) { + this->type = type; header = *reinterpret_cast< SGB_MODEL_HEADER* >( buf + offset ); name = std::string( buf + offset + header.nameOffset ); modelFileName = std::string( buf + offset + header.modelFileOffset ); @@ -129,23 +160,45 @@ struct SGB_GROUP SGB_FILE* parent; std::vector< std::shared_ptr< SGB_GROUP_ENTRY > > entries; - SGB_GROUP( char* buf, SGB_FILE* file, uint32_t fileSize, uint32_t offset ) + SGB_GROUP( char* buf, SGB_FILE* file, std::set< std::string >* offset1cObjects, uint32_t fileSize, uint32_t offset, bool isOffset1C = false ) { parent = file; + + + if( isOffset1C ) + { + auto header1c = *reinterpret_cast< SGB_GROUP1C_HEADER* >( buf + offset ); + + auto entriesOffset = offset + sizeof( header1c ); + + auto entryCount = header1c.entryCount; + for( auto i = 0; i < entryCount; ++i ) + { + auto entryOffset = entriesOffset + ( i * 24 ); + auto entry = *reinterpret_cast< SGB_GROUP1C_ENTRY* >( buf + entryOffset ); + + std::string entryModelFile( buf + entryOffset + entry.modelFileOffset + 9 ); + if( entryModelFile.find( ".sgb" ) != std::string::npos ) + { + offset1cObjects->emplace( entryModelFile ); + } + } + return; + } + auto entriesOffset = offset + sizeof( header ); + header = *reinterpret_cast< SGB_GROUP_HEADER* >( buf + offset ); name = std::string( buf + offset + header.nameOffset ); - auto entriesOffset = offset + sizeof( header ); - for( auto i = 0; i < header.entryCount; ++i ) { auto entryOffset = entriesOffset + *reinterpret_cast< uint32_t* >( buf + ( entriesOffset + ( i * 4 ) ) ); if( entryOffset > fileSize ) throw std::runtime_error( "SGB_GROUP entry offset was larger than SGB file size!" ); auto type = *reinterpret_cast< uint32_t* >( buf + entryOffset ); - if( type == SgbGroupEntryType::Model ) + if( type == SgbGroupEntryType::Model || type == SgbGroupEntryType::Gimmick ) { - entries.push_back( std::make_shared< SGB_MODEL_ENTRY >( buf, entryOffset ) ); + entries.push_back( std::make_shared< SGB_MODEL_ENTRY >( buf, entryOffset, ( SgbGroupEntryType )type ) ); } else { @@ -190,6 +243,7 @@ struct SGB_FILE { SGB_HEADER header; std::vector< SGB_GROUP > entries; + std::set< std::string > offset1cObjects; SGB_FILE() { @@ -206,9 +260,9 @@ struct SGB_FILE try { - auto group = SGB_GROUP( buf, this, header.fileSize, baseOffset + header.sharedOffset ); + auto group = SGB_GROUP( buf, this, &offset1cObjects, header.fileSize, baseOffset + header.sharedOffset ); entries.push_back( group ); - auto group2 = SGB_GROUP( buf, this, header.fileSize, baseOffset + header.offset1C ); + auto group2 = SGB_GROUP( buf, this, &offset1cObjects, header.fileSize, baseOffset+ header.offset1C, true ); entries.push_back( group2 ); } catch( std::exception& e ) diff --git a/src/tools/pcb_reader/threadpool.h b/src/tools/pcb_reader/threadpool.h index ebf4243d..78657bd3 100644 --- a/src/tools/pcb_reader/threadpool.h +++ b/src/tools/pcb_reader/threadpool.h @@ -10,6 +10,9 @@ #include #include +// credit to +// https://riptutorial.com/cplusplus/example/15806/create-a-simple-thread-pool + class ThreadPool { public: @@ -55,23 +58,20 @@ public: { std::unique_lock lock( m_mutex ); m_pendingJobs.clear(); - for( auto&& worker : m_workers ) - { - m_pendingJobs.emplace( {} ); - } } - m_cv.notify_all(); - m_workers.clear(); + complete(); } bool complete() { - m_cv.notify_all(); { std::unique_lock lock( m_mutex ); - m_runFlag = false; - m_cv.wait( lock, [&]{ return m_pendingJobs.empty(); } ); + for( auto&& worker : m_workers ) + { + m_pendingJobs.push_back( {} ); + } } + m_cv.notify_all(); m_workers.clear(); return true; } @@ -85,11 +85,6 @@ private: std::unique_lock lock( m_mutex ); if( m_pendingJobs.empty() ) { - if( !m_runFlag ) - { - m_cv.notify_all(); - return; - } m_cv.wait( lock, [&](){ return !m_pendingJobs.empty(); } ); } func = std::move( m_pendingJobs.front() ); diff --git a/src/world/Actor/BNpc.cpp b/src/world/Actor/BNpc.cpp index da046a07..53b15876 100644 --- a/src/world/Actor/BNpc.cpp +++ b/src/world/Actor/BNpc.cpp @@ -174,9 +174,9 @@ void Sapphire::Entity::BNpc::step() // 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 ) * 1.7f ); + auto x = ( cosf( angle ) * .5f ); auto y = stepPos.y; - auto z = ( sinf( angle ) * 1.7f ); + auto z = ( sinf( angle ) * .5f ); face( stepPos ); setPos( { getPos().x + x, y, getPos().z + z } ); @@ -229,7 +229,14 @@ bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos ) 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 ); } @@ -363,8 +370,39 @@ void Sapphire::Entity::BNpc::update( int64_t currTime ) 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; @@ -468,7 +506,6 @@ void Sapphire::Entity::BNpc::update( int64_t currTime ) } } } - } void Sapphire::Entity::BNpc::onActionHostile( Sapphire::Entity::CharaPtr pSource ) diff --git a/src/world/Actor/BNpc.h b/src/world/Actor/BNpc.h index bd5e4d9a..cce71450 100644 --- a/src/world/Actor/BNpc.h +++ b/src/world/Actor/BNpc.h @@ -88,7 +88,7 @@ namespace Sapphire::Entity void onDeath() override; uint32_t getTimeOfDeath() const; - void setTimeOfDeath( uint32_t timeOfDeath); + void setTimeOfDeath( uint32_t timeOfDeath ); private: uint32_t m_bNpcBaseId; diff --git a/src/world/Actor/Chara.cpp b/src/world/Actor/Chara.cpp index 066a6355..6e2272cc 100644 --- a/src/world/Actor/Chara.cpp +++ b/src/world/Actor/Chara.cpp @@ -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 */ @@ -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,6 +393,7 @@ 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( getAsChara() ); diff --git a/src/world/Actor/Chara.h b/src/world/Actor/Chara.h index 94de7845..1adf919b 100644 --- a/src/world/Actor/Chara.h +++ b/src/world/Actor/Chara.h @@ -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 ); diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 9af7e25e..db5e56fb 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -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 ) diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index 3c4382da..02e1c282 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -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(); diff --git a/src/world/Network/Handlers/GMCommandHandlers.cpp b/src/world/Network/Handlers/GMCommandHandlers.cpp index 8291d24d..1526789b 100644 --- a/src/world/Network/Handlers/GMCommandHandlers.cpp +++ b/src/world/Network/Handlers/GMCommandHandlers.cpp @@ -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: diff --git a/src/world/Network/PacketWrappers/MoveActorPacket.h b/src/world/Network/PacketWrappers/MoveActorPacket.h index a3e09ddb..f4a4f589 100644 --- a/src/world/Network/PacketWrappers/MoveActorPacket.h +++ b/src/world/Network/PacketWrappers/MoveActorPacket.h @@ -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 ); diff --git a/src/world/Territory/Zone.cpp b/src/world/Territory/Zone.cpp index 77c8c30d..e7007fff 100644 --- a/src/world/Territory/Zone.cpp +++ b/src/world/Territory/Zone.cpp @@ -405,8 +405,29 @@ void Sapphire::Zone::updateBNpcs( int64_t tickCount ) removeActor( pBNpc ); break; } + } - pBNpc->update( tickCount ); + 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 ) + { + if( actor->isBattleNpc() ) + actor->getAsBNpc()->update( tickCount ); + } + } } }