mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-27 22:57:45 +00:00
Cleaned up pcb_reader as nav_export
This commit is contained in:
parent
7db1d54fcf
commit
b29311a36b
21 changed files with 3513 additions and 0 deletions
|
@ -23,4 +23,5 @@ add_subdirectory( "quest_parser" )
|
|||
add_subdirectory( "discovery_parser" )
|
||||
add_subdirectory( "mob_parse" )
|
||||
add_subdirectory( "pcb_reader" )
|
||||
add_subdirectory( "nav_export" )
|
||||
add_subdirectory( "event_object_parser" )
|
||||
|
|
22
src/tools/nav_export/CMakeLists.txt
Normal file
22
src/tools/nav_export/CMakeLists.txt
Normal file
|
@ -0,0 +1,22 @@
|
|||
cmake_minimum_required(VERSION 2.6)
|
||||
cmake_policy(SET CMP0015 NEW)
|
||||
project(Tool_nav_export)
|
||||
|
||||
file(GLOB SERVER_PUBLIC_INCLUDE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*")
|
||||
file(GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
*.c*
|
||||
nav/*.c*
|
||||
nav/ext/*.c*)
|
||||
|
||||
add_executable( nav_export ${SERVER_PUBLIC_INCLUDE_FILES} ${SERVER_SOURCE_FILES})
|
||||
|
||||
if (UNIX)
|
||||
target_link_libraries( nav_export common xivdat pthread mysqlclient dl z stdc++fs Recast Detour DetourTileCache )
|
||||
else()
|
||||
target_link_libraries( nav_export common xivdat mysql zlib Recast Detour DetourTileCache )
|
||||
endif()
|
||||
|
||||
target_include_directories( nav_export
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../../deps/" )
|
||||
|
26
src/tools/nav_export/README.md
Normal file
26
src/tools/nav_export/README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
collision data exporter for sapphire
|
||||
|
||||
compile with STANDALONE defined to compile without boost and sapphire dependencies
|
||||
|
||||
usage:
|
||||
- regular
|
||||
- compile with root sapphire dir cmakelists
|
||||
- sapphire/src/tools/bin/pcb_reader2 <territory> "<path/to/game/sqpack/ffxiv>"
|
||||
- standalone
|
||||
- compile main.cpp with STANDALONE defined in build arg
|
||||
- download ffxivexplorer <http://ffxivexplorer.fragmenterworks.com/>
|
||||
- ffxivexplorer > path/to/ffxiv's/game/sqpack/ffxiv/0a0000.dat
|
||||
- exd/territorytype.exh > `File > Export` and copy `territorytype.exh.csv` from exproted directory to `pcb_reader.exe` directory
|
||||
- ffxivexplorer > path/to/ffxiv's/game/sqpack/ffxiv/020000.dat
|
||||
- ctrl click the following:
|
||||
- `bg/ffxiv/[REGION]/common/collision`
|
||||
- `bg/ffxiv/[REGION]/[dun|fld|twn|etc..]/common/collision/`
|
||||
- `bg/ffxiv/[REGION]/[dun|fld|twn|etc..]/collision/`
|
||||
- `bg/ffxiv/region/shared/[for_bg|for_hou]/`
|
||||
- `bg/ffxiv/[REGION]/[dun|fld|twn|etc..]/ZONE/level/`
|
||||
- `bg/ffxiv/[REGION]/[dun|fld|twn|etc..]/ZONE/collision/`
|
||||
- `bgcommon/world/sys/shared/for_bg/`
|
||||
and `File > Export Raw` to pcb_reader exe dir (common and shared files are optional but you will be missing a lot of objects if you skip them)
|
||||
- note: at this time ffxivexplorer is still missing some hashes, though any tool which can export the exds should work fine
|
||||
- main "" <territory>
|
||||
|
135
src/tools/nav_export/cache.h
Normal file
135
src/tools/nav_export/cache.h
Normal file
|
@ -0,0 +1,135 @@
|
|||
#ifndef CACHE_H
|
||||
#define CACHE_H
|
||||
|
||||
#include <any>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#include "pcb.h"
|
||||
#include "lgb.h"
|
||||
#include "sgb.h"
|
||||
|
||||
#include <datReader/GameData.h>
|
||||
#include <datReader/File.h>
|
||||
#include <datReader/DatCat.h>
|
||||
|
||||
class Cache : public std::enable_shared_from_this< Cache >
|
||||
{
|
||||
public:
|
||||
Cache( xiv::dat::GameData* pData )
|
||||
{
|
||||
if( !pData )
|
||||
throw std::runtime_error( "Unable to initialise cache without game data" );
|
||||
m_pData = pData;
|
||||
}
|
||||
~Cache(){}
|
||||
|
||||
std::shared_ptr< SGB_FILE > getSgbFile( const std::string& filepath )
|
||||
{
|
||||
std::scoped_lock lock( m_mutex );
|
||||
|
||||
auto it = m_sgbCache.find( filepath );
|
||||
if( it != m_sgbCache.end() )
|
||||
return it->second;
|
||||
|
||||
auto pFile = loadFile< SGB_FILE >( filepath );
|
||||
m_sgbCache[ filepath ] = pFile;
|
||||
return pFile;
|
||||
}
|
||||
|
||||
std::shared_ptr< LGB_FILE > getLgbFile( const std::string& filepath )
|
||||
{
|
||||
std::scoped_lock lock( m_mutex );
|
||||
|
||||
auto it = m_lgbCache.find( filepath );
|
||||
if( it != m_lgbCache.end() )
|
||||
return it->second;
|
||||
|
||||
auto pFile = loadFile< LGB_FILE >( filepath );
|
||||
m_lgbCache[ filepath ] = pFile;
|
||||
return pFile;
|
||||
}
|
||||
|
||||
std::shared_ptr< PCB_FILE > getPcbFile( const std::string& filepath )
|
||||
{
|
||||
std::scoped_lock lock( m_mutex );
|
||||
|
||||
auto it = m_pcbCache.find( filepath );
|
||||
if( it != m_pcbCache.end() )
|
||||
return it->second;
|
||||
|
||||
auto pFile = loadFile< PCB_FILE >( filepath );
|
||||
m_pcbCache[ filepath ] = pFile;
|
||||
return pFile;
|
||||
}
|
||||
|
||||
void purge()
|
||||
{
|
||||
std::scoped_lock lock( m_mutex );
|
||||
_purge();
|
||||
}
|
||||
|
||||
private:
|
||||
void _purge()
|
||||
{
|
||||
m_lgbCache.clear();
|
||||
m_sgbCache.clear();
|
||||
m_pcbCache.clear();
|
||||
//std::cout << "Purged PCB/SGB/LGB cache \n";
|
||||
}
|
||||
template< typename T >
|
||||
std::shared_ptr< T > loadFile( const std::string& filepath )
|
||||
{
|
||||
auto buf = getFileBuffer( filepath );
|
||||
if( !buf.empty() )
|
||||
{
|
||||
try
|
||||
{
|
||||
auto pFile = std::make_shared< T >( &buf[0] );
|
||||
|
||||
m_totalFiles++;
|
||||
if( m_totalFiles % 1000 == 0 )
|
||||
{
|
||||
_purge();
|
||||
m_totalFiles = 1;
|
||||
}
|
||||
|
||||
return pFile;
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
std::string err( filepath + " " + e.what() );
|
||||
std::cout << err << std::endl;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector< char > getFileBuffer( const std::string& filepath )
|
||||
{
|
||||
try
|
||||
{
|
||||
//std::cout << fileName << " \n";
|
||||
auto pFile = m_pData->getFile( filepath );
|
||||
auto& sections = pFile->get_data_sections();
|
||||
auto& section = sections.at( 0 );
|
||||
return section;
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
std::vector< char > empty;
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex m_mutex;
|
||||
xiv::dat::GameData* m_pData;
|
||||
std::map< std::string, std::shared_ptr< LGB_FILE > > m_lgbCache;
|
||||
std::map< std::string, std::shared_ptr< SGB_FILE > > m_sgbCache;
|
||||
std::map< std::string, std::shared_ptr< PCB_FILE > > m_pcbCache;
|
||||
int m_totalFiles{0};
|
||||
};
|
||||
|
||||
#endif
|
55
src/tools/nav_export/exporter.h
Normal file
55
src/tools/nav_export/exporter.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
#ifndef EXPORTER_H
|
||||
#define EXPORTER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "threadpool.h"
|
||||
|
||||
enum ExportFileType : int
|
||||
{
|
||||
WavefrontObj = 0x01,
|
||||
Navmesh = 0x02,
|
||||
};
|
||||
|
||||
enum ExportSplitType
|
||||
{
|
||||
None,
|
||||
SplitByGroup,
|
||||
SingleZone
|
||||
};
|
||||
|
||||
struct ExportedMesh
|
||||
{
|
||||
std::vector< float > verts;
|
||||
std::vector< int > indices;
|
||||
};
|
||||
|
||||
struct ExportedModel
|
||||
{
|
||||
std::string name;
|
||||
std::vector< ExportedMesh > meshes;
|
||||
};
|
||||
|
||||
struct ExportedGroup
|
||||
{
|
||||
std::string name;
|
||||
std::map< std::string, ExportedModel > models;
|
||||
};
|
||||
|
||||
struct ExportedZone
|
||||
{
|
||||
std::string name;
|
||||
std::map< std::string, ExportedGroup > groups;
|
||||
};
|
||||
|
||||
#endif
|
51
src/tools/nav_export/exportmgr.h
Normal file
51
src/tools/nav_export/exportmgr.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
#ifndef EXPORTMGR_H
|
||||
#define EXPORTMGR_H
|
||||
|
||||
#include "exporter.h"
|
||||
#include "navmesh_exporter.h"
|
||||
#include "obj_exporter.h"
|
||||
#include "threadpool.h"
|
||||
|
||||
class ExportMgr
|
||||
{
|
||||
public:
|
||||
ExportMgr( unsigned int maxJobs = 0 )
|
||||
{
|
||||
m_threadpool.addWorkers( maxJobs );
|
||||
}
|
||||
~ExportMgr()
|
||||
{
|
||||
waitForTasks();
|
||||
}
|
||||
|
||||
void restart( bool cancel = false, unsigned int maxJobs = 0 )
|
||||
{
|
||||
if( cancel )
|
||||
m_threadpool.cancel();
|
||||
|
||||
m_threadpool.complete();
|
||||
|
||||
m_threadpool.addWorkers( maxJobs );
|
||||
}
|
||||
|
||||
void exportZone(const ExportedZone& zone, ExportFileType exportFileTypes)
|
||||
{
|
||||
m_threadpool.queue( [zone, exportFileTypes]()
|
||||
{
|
||||
if( exportFileTypes & ExportFileType::WavefrontObj )
|
||||
ObjExporter::exportZone( zone );
|
||||
|
||||
if( exportFileTypes & ExportFileType::Navmesh )
|
||||
NavmeshExporter::exportZone( zone );
|
||||
} );
|
||||
}
|
||||
|
||||
void waitForTasks()
|
||||
{
|
||||
m_threadpool.complete();
|
||||
}
|
||||
private:
|
||||
ThreadPool m_threadpool;
|
||||
};
|
||||
|
||||
#endif
|
389
src/tools/nav_export/lgb.h
Normal file
389
src/tools/nav_export/lgb.h
Normal file
|
@ -0,0 +1,389 @@
|
|||
#ifndef _LGB_H
|
||||
#define _LGB_H
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "matrix4.h"
|
||||
#include "vec3.h"
|
||||
#include "sgb.h"
|
||||
|
||||
// garbage to skip model loading
|
||||
extern bool noObj;
|
||||
|
||||
// all credit to
|
||||
// https://github.com/ufx/SaintCoinach/blob/master/SaintCoinach/Graphics/Lgb/
|
||||
// this is simply their work ported to c++ since we dont c#
|
||||
struct LGB_FILE;
|
||||
struct LGB_FILE_HEADER;
|
||||
struct LGB_GROUP;
|
||||
struct LGB_GROUP_HEADER;
|
||||
|
||||
enum class LgbEntryType :
|
||||
uint32_t
|
||||
{
|
||||
BgParts = 1,
|
||||
Light = 3,
|
||||
Vfx = 4,
|
||||
PositionMarker = 5,
|
||||
Gimmick = 6,
|
||||
SharedGroup6 = 6,// secondary variable is set to 2
|
||||
Sound = 7,
|
||||
EventNpc = 8,
|
||||
BattleNpc = 9,
|
||||
Aetheryte = 12,
|
||||
EnvSpace = 13,
|
||||
Gathering = 14,
|
||||
SharedGroup15 = 15,// secondary variable is set to 13
|
||||
Treasure = 16,
|
||||
Weapon = 39,
|
||||
PopRange = 40,
|
||||
ExitRange = 41,
|
||||
MapRange = 43,
|
||||
NaviMeshRange = 44,
|
||||
EventObject = 45,
|
||||
EnvLocation = 47,
|
||||
EventRange = 49,
|
||||
QuestMarker = 51,
|
||||
CollisionBox = 57,
|
||||
DoorRange = 58,
|
||||
LineVfx = 59,
|
||||
ClientPath = 65,
|
||||
ServerPath = 66,
|
||||
GimmickRange = 67,
|
||||
TargetMarker = 68,
|
||||
ChairMarker = 69,
|
||||
ClickableRange = 70,
|
||||
PrefetchRange = 71,
|
||||
FateRange = 72,
|
||||
SphereCastRange = 75,
|
||||
};
|
||||
|
||||
struct LGB_ENTRY_HEADER
|
||||
{
|
||||
LgbEntryType type;
|
||||
uint32_t unknown;
|
||||
uint32_t nameOffset;
|
||||
vec3 translation;
|
||||
vec3 rotation;
|
||||
vec3 scale;
|
||||
};
|
||||
|
||||
class LGB_ENTRY
|
||||
{
|
||||
public:
|
||||
char* m_buf;
|
||||
uint32_t m_offset;
|
||||
LGB_ENTRY_HEADER header;
|
||||
|
||||
LGB_ENTRY()
|
||||
{
|
||||
m_buf = nullptr;
|
||||
m_offset = 0;
|
||||
memset( &header, 0, sizeof( header ) );
|
||||
};
|
||||
|
||||
LGB_ENTRY( char* buf, uint32_t offset )
|
||||
{
|
||||
m_buf = buf;
|
||||
m_offset = offset;
|
||||
header = *reinterpret_cast< LGB_ENTRY_HEADER* >( buf + offset );
|
||||
};
|
||||
|
||||
const LgbEntryType getType() const
|
||||
{
|
||||
return header.type;
|
||||
};
|
||||
|
||||
virtual ~LGB_ENTRY()
|
||||
{
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
struct LGB_BGPARTS_HEADER :
|
||||
public LGB_ENTRY_HEADER
|
||||
{
|
||||
uint32_t modelFileOffset;
|
||||
uint32_t collisionFileOffset;
|
||||
uint32_t unknown4;
|
||||
uint32_t unknown5;
|
||||
uint32_t unknown6;
|
||||
uint32_t unknown7;
|
||||
uint32_t unknown8;
|
||||
uint32_t unknown9;
|
||||
};
|
||||
|
||||
class LGB_BGPARTS_ENTRY :
|
||||
public LGB_ENTRY
|
||||
{
|
||||
public:
|
||||
LGB_BGPARTS_HEADER header;
|
||||
std::string name;
|
||||
std::string modelFileName;
|
||||
std::string collisionFileName;
|
||||
|
||||
LGB_BGPARTS_ENTRY()
|
||||
{
|
||||
};
|
||||
|
||||
LGB_BGPARTS_ENTRY( char* buf, uint32_t offset ) :
|
||||
LGB_ENTRY( buf, offset )
|
||||
{
|
||||
header = *reinterpret_cast<LGB_BGPARTS_HEADER*>( buf + offset );
|
||||
name = std::string( buf + offset + header.nameOffset );
|
||||
modelFileName = std::string( buf + offset + header.modelFileOffset );
|
||||
collisionFileName = std::string( buf + offset + header.collisionFileOffset );
|
||||
};
|
||||
};
|
||||
|
||||
struct LGB_GIMMICK_HEADER :
|
||||
public LGB_ENTRY_HEADER
|
||||
{
|
||||
uint32_t gimmickFileOffset;
|
||||
char unknownBytes[100];
|
||||
};
|
||||
|
||||
class LGB_GIMMICK_ENTRY :
|
||||
public LGB_ENTRY
|
||||
{
|
||||
public:
|
||||
LGB_GIMMICK_HEADER header;
|
||||
std::string name;
|
||||
std::string gimmickFileName;
|
||||
|
||||
LGB_GIMMICK_ENTRY( char* buf, uint32_t offset ) :
|
||||
LGB_ENTRY( buf, offset )
|
||||
{
|
||||
header = *reinterpret_cast<LGB_GIMMICK_HEADER*>( buf + offset );
|
||||
name = std::string( buf + offset + header.nameOffset );
|
||||
gimmickFileName = std::string( buf + offset + header.gimmickFileOffset );
|
||||
//std::cout << "\t " << gimmickFileName << " unknown: " << header.unknown << "\n";
|
||||
};
|
||||
};
|
||||
|
||||
struct LGB_ENPC_HEADER :
|
||||
public LGB_ENTRY_HEADER
|
||||
{
|
||||
uint32_t enpcId;
|
||||
uint8_t unknown1[0x24];
|
||||
};
|
||||
|
||||
class LGB_ENPC_ENTRY :
|
||||
public LGB_ENTRY
|
||||
{
|
||||
public:
|
||||
LGB_ENPC_HEADER header;
|
||||
std::string name;
|
||||
|
||||
LGB_ENPC_ENTRY( char* buf, uint32_t offset ) :
|
||||
LGB_ENTRY( buf, offset )
|
||||
{
|
||||
header = *reinterpret_cast< LGB_ENPC_HEADER* >( buf + offset );
|
||||
name = std::string( buf + offset + header.nameOffset );
|
||||
//std::cout << "\t ENpc " << header.enpcId << " " << name << "\n";
|
||||
};
|
||||
};
|
||||
|
||||
struct LGB_EOBJ_HEADER :
|
||||
public LGB_ENTRY_HEADER
|
||||
{
|
||||
uint32_t eobjId;
|
||||
uint32_t levelHierachyId;
|
||||
uint8_t unknown1[0xC];
|
||||
};
|
||||
|
||||
class LGB_EOBJ_ENTRY :
|
||||
public LGB_ENTRY
|
||||
{
|
||||
public:
|
||||
LGB_EOBJ_HEADER header;
|
||||
std::string name;
|
||||
|
||||
LGB_EOBJ_ENTRY( char* buf, uint32_t offset ) :
|
||||
LGB_ENTRY( buf, offset )
|
||||
{
|
||||
header = *reinterpret_cast< LGB_EOBJ_HEADER* >( buf + offset );
|
||||
//std::cout << "\t " << header.eobjId << " " << name << " unknown: " << header.unknown << "\n";
|
||||
name = std::string( buf + offset + header.nameOffset );
|
||||
};
|
||||
};
|
||||
|
||||
struct LGB_MAPRANGE_HEADER :
|
||||
public LGB_ENTRY_HEADER
|
||||
{
|
||||
uint32_t type;
|
||||
uint16_t unknown2;
|
||||
uint16_t unknown3;
|
||||
uint8_t unknown4[0x10];
|
||||
};
|
||||
|
||||
struct LGB_MAPRANGE_ENTRY :
|
||||
public LGB_ENTRY
|
||||
{
|
||||
public:
|
||||
LGB_MAPRANGE_HEADER header;
|
||||
std::string name;
|
||||
|
||||
LGB_MAPRANGE_ENTRY( char* buf, uint32_t offset ) :
|
||||
LGB_ENTRY( buf, offset )
|
||||
{
|
||||
header = *reinterpret_cast< LGB_MAPRANGE_HEADER* >( buf + offset );
|
||||
name = std::string( buf + offset + header.nameOffset );
|
||||
};
|
||||
};
|
||||
|
||||
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;
|
||||
int32_t groupNameOffset;
|
||||
int32_t entriesOffset;
|
||||
int32_t entryCount;
|
||||
uint32_t unknown2;
|
||||
uint32_t unknown3;
|
||||
uint32_t unknown4;
|
||||
uint32_t unknown5;
|
||||
uint32_t unknown6;
|
||||
uint32_t unknown7;
|
||||
uint32_t unknown8;
|
||||
uint32_t unknown9;
|
||||
uint32_t unknown10;
|
||||
};
|
||||
|
||||
struct LGB_GROUP
|
||||
{
|
||||
LGB_FILE* parent;
|
||||
LGB_GROUP_HEADER header;
|
||||
std::string name;
|
||||
std::vector< std::shared_ptr< LGB_ENTRY > > entries;
|
||||
|
||||
LGB_GROUP( char* buf, LGB_FILE* parentStruct, uint32_t offset )
|
||||
{
|
||||
parent = parentStruct;
|
||||
header = *reinterpret_cast< LGB_GROUP_HEADER* >( buf + offset );
|
||||
name = std::string( buf + offset + header.groupNameOffset );
|
||||
//entries.resize( header.entryCount );
|
||||
//std::cout << name << "\n\t unknown: " << header.unknown << "\n";
|
||||
const auto entriesOffset = offset + header.entriesOffset;
|
||||
for( auto i = 0; i < header.entryCount; ++i )
|
||||
{
|
||||
const auto entryOffset = entriesOffset + *reinterpret_cast< int32_t* >( buf + ( entriesOffset + i * 4 ) );
|
||||
|
||||
try
|
||||
{
|
||||
const auto type = *reinterpret_cast<LgbEntryType*>( buf + entryOffset );
|
||||
// garbage to skip model loading
|
||||
switch( type )
|
||||
{
|
||||
case LgbEntryType::BgParts:
|
||||
entries.push_back( std::make_shared< LGB_BGPARTS_ENTRY >( buf, entryOffset ) );
|
||||
break;
|
||||
case LgbEntryType::Gimmick:
|
||||
entries.push_back( std::make_shared< LGB_GIMMICK_ENTRY >( buf, entryOffset ) );
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
std::cout << ( name + " " + e.what() + "\n" );
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct LGB_FILE_HEADER
|
||||
{
|
||||
char magic[4]; // LGB 1
|
||||
uint32_t fileSize;
|
||||
uint32_t unknown;
|
||||
char magic2[4]; // LGP1
|
||||
uint32_t unknown2;
|
||||
uint32_t unknown3;
|
||||
uint32_t unknown4;
|
||||
uint32_t unknown5;
|
||||
int32_t groupCount;
|
||||
};
|
||||
|
||||
struct LGB_FILE
|
||||
{
|
||||
LGB_FILE_HEADER header;
|
||||
std::vector< LGB_GROUP > groups;
|
||||
|
||||
LGB_FILE( char* buf )
|
||||
{
|
||||
header = *reinterpret_cast< LGB_FILE_HEADER* >( buf );
|
||||
if( strncmp( &header.magic[ 0 ], "LGB1", 4 ) != 0 || strncmp( &header.magic2[ 0 ], "LGP1", 4 ) != 0 )
|
||||
throw std::runtime_error( "Invalid LGB file!" );
|
||||
|
||||
//groups.resize(header.groupCount);
|
||||
|
||||
constexpr auto baseOffset = sizeof( header );
|
||||
for( auto i = 0; i < header.groupCount; ++i )
|
||||
{
|
||||
const auto groupOffset = baseOffset + *reinterpret_cast< int32_t* >( buf + ( baseOffset + i * 4 ) );
|
||||
const auto group = LGB_GROUP( buf, this, groupOffset );
|
||||
groups.push_back( group );
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
503
src/tools/nav_export/main.cpp
Normal file
503
src/tools/nav_export/main.cpp
Normal file
|
@ -0,0 +1,503 @@
|
|||
#include <stdio.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
#include <Util/Util.h>
|
||||
|
||||
#include "exporter.h"
|
||||
#include "exportmgr.h"
|
||||
|
||||
#include "cache.h"
|
||||
#include "pcb.h"
|
||||
#include "lgb.h"
|
||||
#include "sgb.h"
|
||||
|
||||
#include <GameData.h>
|
||||
#include <File.h>
|
||||
#include <DatCat.h>
|
||||
#include <ExdData.h>
|
||||
#include <ExdCat.h>
|
||||
#include <Exd.h>
|
||||
|
||||
// garbage to ignore models
|
||||
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;
|
||||
|
||||
|
||||
enum class TerritoryTypeExdIndexes :
|
||||
size_t
|
||||
{
|
||||
TerritoryType = 0,
|
||||
Path = 1
|
||||
};
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
void initExd( const std::string& gamePath )
|
||||
{
|
||||
data1 = data1 ? data1 : new xiv::dat::GameData( gamePath );
|
||||
eData = eData ? eData : new xiv::exd::ExdData( *data1 );
|
||||
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 )
|
||||
{
|
||||
std::string path;
|
||||
bool found = false;
|
||||
|
||||
auto& cat = eData->get_category( "TerritoryType" );
|
||||
auto exd = static_cast< xiv::exd::Exd >( cat.get_data_ln( xiv::exd::Language::none ) );
|
||||
for( auto& row : exd.get_rows() )
|
||||
{
|
||||
auto& fields = row.second;
|
||||
auto teriName = std::get< std::string >(
|
||||
fields.at( static_cast< size_t >( TerritoryTypeExdIndexes::TerritoryType ) ) );
|
||||
if( teriName.empty() )
|
||||
continue;
|
||||
auto teriPath = std::get< std::string >( fields.at( static_cast< size_t >( TerritoryTypeExdIndexes::Path ) ) );
|
||||
if( !found && ( Sapphire::Util::toLowerCopy( name ) == Sapphire::Util::toLowerCopy( teriName ) ) )
|
||||
{
|
||||
path = teriPath;
|
||||
found = true;
|
||||
zoneId = row.first;
|
||||
}
|
||||
zoneNameMap[ row.first ] = teriName;
|
||||
}
|
||||
|
||||
if( found )
|
||||
{
|
||||
//path = path.substr( path.find_first_of( "/" ) + 1, path.size() - path.find_first_of( "/" ));
|
||||
//path = std::string( "ffxiv/" ) + path;
|
||||
path = std::string( "bg/" ) + path.substr( 0, path.find( "/level/" ) );
|
||||
printf( "[Info] Found path for %s\n", name.c_str() );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error( "Unable to find path for " + name +
|
||||
".\n\tPlease double check spelling." );
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
int totalModels = 0;
|
||||
void buildModelEntry( std::shared_ptr< PCB_FILE > pPcbFile, ExportedGroup& exportedGroup,
|
||||
const std::string& name, const std::string& groupName,
|
||||
const vec3* scale = nullptr,
|
||||
const vec3* rotation = nullptr,
|
||||
const vec3* translation = nullptr,
|
||||
const SGB_MODEL_ENTRY* pSgbEntry = nullptr )
|
||||
{
|
||||
auto& pcb_file = *pPcbFile.get();
|
||||
|
||||
ExportedModel model;
|
||||
model.name = name + "_" + std::to_string( totalModels++ );
|
||||
model.meshes.resize( pcb_file.entries.size() );
|
||||
|
||||
uint32_t meshCount = 0;
|
||||
for( const auto& entry : pcb_file.entries )
|
||||
{
|
||||
ExportedMesh mesh;
|
||||
|
||||
mesh.verts.resize( ( entry.header.num_vertices + entry.header.num_v16 ) * 3 );
|
||||
mesh.indices.resize( entry.header.num_indices * 3 );
|
||||
|
||||
float x_base = abs( float( entry.header.x1 - entry.header.x ) );
|
||||
float y_base = abs( float( entry.header.y1 - entry.header.y ) );
|
||||
float z_base = abs( float( entry.header.z1 - entry.header.z ) );
|
||||
|
||||
auto makeTranslation = [ & ]( vec3& v )
|
||||
{
|
||||
if( pSgbEntry )
|
||||
{
|
||||
v.x *= pSgbEntry->header.scale.x;
|
||||
v.y *= pSgbEntry->header.scale.y;
|
||||
v.z *= pSgbEntry->header.scale.z;
|
||||
|
||||
v = v * matrix4::rotateX( pSgbEntry->header.rotation.x );
|
||||
v = v * matrix4::rotateY( pSgbEntry->header.rotation.y );
|
||||
v = v * matrix4::rotateZ( pSgbEntry->header.rotation.z );
|
||||
|
||||
v.x += pSgbEntry->header.translation.x;
|
||||
v.y += pSgbEntry->header.translation.y;
|
||||
v.z += pSgbEntry->header.translation.z;
|
||||
}
|
||||
|
||||
if( scale )
|
||||
{
|
||||
v.x *= scale->x;
|
||||
v.y *= scale->y;
|
||||
v.z *= scale->z;
|
||||
|
||||
v = v * matrix4::rotateX( rotation->x );
|
||||
v = v * matrix4::rotateY( rotation->y );
|
||||
v = v * matrix4::rotateZ( rotation->z );
|
||||
|
||||
v.x += translation->x;
|
||||
v.y += translation->y;
|
||||
v.z += translation->z;
|
||||
}
|
||||
|
||||
};
|
||||
int verts = 0;
|
||||
int indices = 0;
|
||||
|
||||
for( auto& vertex : entry.data.vertices )
|
||||
{
|
||||
vec3 v( vertex.x, vertex.y, vertex.z );
|
||||
makeTranslation( v );
|
||||
|
||||
mesh.verts[ verts++ ] = v.x;
|
||||
mesh.verts[ verts++ ] = v.y;
|
||||
mesh.verts[ verts++ ] = v.z;
|
||||
}
|
||||
|
||||
for( const auto& link : entry.data.vertices_i16 )
|
||||
{
|
||||
vec3 v( float( link.x ) / 0xFFFF, float( link.y ) / 0xFFFF, float( link.z ) / 0xFFFF );
|
||||
|
||||
v.x = v.x * x_base + entry.header.x;
|
||||
v.y = v.y * y_base + entry.header.y;
|
||||
v.z = v.z * z_base + entry.header.z;
|
||||
|
||||
makeTranslation( v );
|
||||
|
||||
mesh.verts[ verts++ ] = v.x;
|
||||
mesh.verts[ verts++ ] = v.y;
|
||||
mesh.verts[ verts++ ] = v.z;
|
||||
}
|
||||
|
||||
for( const auto& index : entry.data.indices )
|
||||
{
|
||||
mesh.indices[ indices++ ] = index.index[ 0 ];
|
||||
mesh.indices[ indices++ ] = index.index[ 1 ];
|
||||
mesh.indices[ indices++ ] = index.index[ 2 ];
|
||||
// std::cout << std::to_string( index.unknown[0] )<< " " << std::to_string( index.unknown[1] )<< " " << std::to_string( index.unknown[2]) << std::endl;
|
||||
}
|
||||
model.meshes[ meshCount++ ] = mesh;
|
||||
}
|
||||
exportedGroup.models[model.name] = model;
|
||||
}
|
||||
|
||||
bool pcbTransformModel( const std::string& fileName, const vec3* scale, const vec3* rotation,
|
||||
const vec3* translation, ExportedGroup& exportgroup, const SGB_MODEL_ENTRY* pModel = nullptr )
|
||||
{
|
||||
if( auto pPcbFile = pCache->getPcbFile( fileName ) )
|
||||
{
|
||||
buildModelEntry( pPcbFile, exportgroup, fileName, exportgroup.name, scale, rotation, translation, pModel );
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
void exportSgbModel( const std::string& sgbFilePath, LGB_ENTRY* pGimmick, ExportedGroup& exportgroup, 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() );
|
||||
std::string 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, exportgroup, pSubModel );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pcbTransformModel( fileName, &pGimmick->header.scale, &pGimmick->header.rotation,
|
||||
&pGimmick->header.translation, exportgroup, pModel );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main( int argc, char* argv[] )
|
||||
{
|
||||
auto startTime = std::chrono::high_resolution_clock::now();
|
||||
auto entryStartTime = std::chrono::high_resolution_clock::now();
|
||||
|
||||
std::vector< std::string > argVec( argv + 1, argv + argc );
|
||||
std::string zoneName = "r2t2";
|
||||
|
||||
bool generateNavmesh = true;
|
||||
bool dumpAllZones = true;
|
||||
int nJobs = 4;
|
||||
|
||||
int exportFileType = 0;
|
||||
if( !noObj )
|
||||
exportFileType |= ExportFileType::WavefrontObj;
|
||||
if( generateNavmesh )
|
||||
exportFileType |= ExportFileType::Navmesh;
|
||||
|
||||
try
|
||||
{
|
||||
initExd( gamePath );
|
||||
getEobjSgbPath( 0 );
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
printf( "Unable to initialise EXD!\n Usage: pcb_reader <teri> \"path/to/FINAL FANTASY XIV - A REALM REBORN/game/sqpack\"\n" );
|
||||
return -1;
|
||||
}
|
||||
ExportMgr exportMgr( nJobs );
|
||||
zoneNameToPath( zoneName );
|
||||
|
||||
if( dumpAllZones )
|
||||
{
|
||||
for( const auto& zone : zoneNameMap )
|
||||
zoneDumpList.emplace( zone.second );
|
||||
}
|
||||
else
|
||||
{
|
||||
zoneDumpList.emplace( zoneName );
|
||||
}
|
||||
|
||||
int zoneCount = 0;
|
||||
for( auto& zoneName : zoneDumpList )
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto& zonePath = zoneNameToPath( zoneName );
|
||||
if( exportedTeriMap.find( zonePath ) != exportedTeriMap.end() )
|
||||
continue;
|
||||
|
||||
std::string zoneNameShort = zonePath.substr( zonePath.find_last_of( '/' ) );
|
||||
|
||||
ExportedZone exportedZone;
|
||||
exportedZone.name = zoneNameShort;
|
||||
exportedTeriMap[ zonePath ] = zoneNameShort;
|
||||
|
||||
std::string listPcbPath( zonePath + "/collision/list.pcb" );
|
||||
std::string bgLgbPath( zonePath + "/level/bg.lgb" );
|
||||
std::string planmapLgbPath( zonePath + "/level/planmap.lgb" );
|
||||
std::string collisionFilePath( zonePath + "/collision/" );
|
||||
std::vector< char > section;
|
||||
std::vector< char > section1;
|
||||
std::vector< char > section2;
|
||||
|
||||
const xiv::dat::Cat& test = data1->getCategory( "bg" );
|
||||
|
||||
auto test_file = data1->getFile( bgLgbPath );
|
||||
section = test_file->access_data_sections().at( 0 );
|
||||
|
||||
auto planmap_file = data1->getFile( planmapLgbPath );
|
||||
section2 = planmap_file->access_data_sections().at( 0 );
|
||||
|
||||
auto test_file1 = data1->getFile( listPcbPath );
|
||||
section1 = test_file1->access_data_sections().at( 0 );
|
||||
|
||||
std::vector< std::string > stringList;
|
||||
|
||||
int totalGroups = 0;
|
||||
int totalEntries = 0;
|
||||
|
||||
uint32_t offset1 = 0x20;
|
||||
|
||||
{
|
||||
for( ;; )
|
||||
{
|
||||
if( offset1 >= section1.size() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
uint16_t trId = *( uint16_t* ) §ion1[ offset1 ];
|
||||
|
||||
char someString[200];
|
||||
sprintf( someString, "%str%04d.pcb", collisionFilePath.c_str(), trId );
|
||||
stringList.push_back( std::string( someString ) );
|
||||
//std::cout << someString << "\n";
|
||||
offset1 += 0x20;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
LGB_FILE bgLgb( §ion[ 0 ] );
|
||||
LGB_FILE planmapLgb( §ion2[ 0 ] );
|
||||
|
||||
std::vector< LGB_FILE > lgbList{ bgLgb, planmapLgb };
|
||||
|
||||
ExportedGroup exportedTerrainGroup;
|
||||
exportedTerrainGroup.name = zoneName + "_terrain";
|
||||
for( const auto& fileName : stringList )
|
||||
{
|
||||
if( auto pPcbFile = pCache->getPcbFile( fileName ) )
|
||||
buildModelEntry( pPcbFile, exportedTerrainGroup, fileName, zoneNameShort );
|
||||
}
|
||||
exportedZone.groups.emplace( exportedTerrainGroup.name, exportedTerrainGroup );
|
||||
|
||||
for( const auto& lgb : lgbList )
|
||||
{
|
||||
for( const auto& group : lgb.groups )
|
||||
{
|
||||
ExportedGroup exportedGroup;
|
||||
exportedGroup.name = group.name;
|
||||
|
||||
//std::cout << "\t" << group.name << " Size " << group.header.entryCount << "\n";
|
||||
for( const auto& pEntry : group.entries )
|
||||
{
|
||||
std::string fileName( "" );
|
||||
fileName.resize( 256 );
|
||||
|
||||
// write files
|
||||
switch( pEntry->getType() )
|
||||
{
|
||||
case LgbEntryType::BgParts:
|
||||
{
|
||||
auto pBgParts = static_cast< LGB_BGPARTS_ENTRY* >( pEntry.get() );
|
||||
fileName = pBgParts->collisionFileName;
|
||||
pcbTransformModel( fileName, &pBgParts->header.scale, &pBgParts->header.rotation,
|
||||
&pBgParts->header.translation, exportedGroup );
|
||||
}
|
||||
break;
|
||||
|
||||
// gimmick entry
|
||||
case LgbEntryType::Gimmick:
|
||||
{
|
||||
auto pGimmick = static_cast< LGB_GIMMICK_ENTRY* >( pEntry.get() );
|
||||
|
||||
exportSgbModel( pGimmick->gimmickFileName, pGimmick, exportedGroup );
|
||||
}
|
||||
break;
|
||||
|
||||
case LgbEntryType::EventObject:
|
||||
{
|
||||
auto pEobj = static_cast< LGB_EOBJ_ENTRY* >( pEntry.get() );
|
||||
pcbTransformModel( fileName, &pEntry->header.scale, &pEntry->header.rotation, &pEntry->header.translation, exportedGroup );
|
||||
|
||||
auto sgbPath = getEobjSgbPath( pEobj->header.eobjId );
|
||||
if ( !sgbPath.empty() )
|
||||
{
|
||||
exportSgbModel( sgbPath, pEobj, exportedGroup, true );
|
||||
|
||||
if( auto pGimmick = pCache->getSgbFile( sgbPath ) )
|
||||
{
|
||||
for( const auto& offset1cFile : pGimmick->offset1cObjects )
|
||||
exportSgbModel( offset1cFile, pEobj, exportedGroup, true );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
exportedZone.groups.emplace( group.name, exportedGroup );
|
||||
}
|
||||
}
|
||||
exportMgr.exportZone( exportedZone, static_cast< ExportFileType >( exportFileType ) );
|
||||
exportedZone.groups.clear();
|
||||
|
||||
printf( "Built export struct for %s in %lu seconds \n",
|
||||
zoneName.c_str(),
|
||||
std::chrono::duration_cast< std::chrono::seconds >( std::chrono::high_resolution_clock::now() - entryStartTime ).count() );
|
||||
//if( zoneCount++ % nJobs == 0 )
|
||||
{
|
||||
exportMgr.restart();
|
||||
pCache->purge();
|
||||
}
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
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" );
|
||||
}
|
||||
}
|
||||
pCache->purge();
|
||||
exportMgr.waitForTasks();
|
||||
std::cout << "\n\n\n";
|
||||
|
||||
printf( "Finished all tasks in %lu seconds\n",
|
||||
std::chrono::duration_cast< std::chrono::seconds >( std::chrono::high_resolution_clock::now() - startTime ).count() );
|
||||
|
||||
delete eData;
|
||||
delete data1;
|
||||
|
||||
return 0;
|
||||
}
|
111
src/tools/nav_export/matrix4.h
Normal file
111
src/tools/nav_export/matrix4.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
#ifndef _MATRIX4_H
|
||||
#define _MATRIX4_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
|
||||
// https://github.com/jpd002/Play--Framework/tree/master/include/math
|
||||
struct matrix4
|
||||
{
|
||||
// 4x4
|
||||
float grid[16];
|
||||
|
||||
matrix4()
|
||||
{
|
||||
memset( &grid[ 0 ], 0, sizeof( grid ) );
|
||||
}
|
||||
|
||||
float operator()( int row, int col ) const
|
||||
{
|
||||
return grid[ ( row * 4 ) + col ];
|
||||
}
|
||||
|
||||
float& operator()( int row, int col )
|
||||
{
|
||||
return grid[ ( row * 4 ) + col ];
|
||||
}
|
||||
|
||||
static matrix4 rotateX( float angle )
|
||||
{
|
||||
matrix4 ret = matrix4();
|
||||
ret( 0, 0 ) = 1.000000000f;
|
||||
ret( 1, 1 ) = cos( angle );
|
||||
ret( 1, 2 ) = -sin( angle );
|
||||
ret( 2, 1 ) = sin( angle );
|
||||
ret( 2, 2 ) = cos( angle );
|
||||
ret( 3, 3 ) = 1.000000000f;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static matrix4 rotateY( float angle )
|
||||
{
|
||||
matrix4 ret = matrix4();
|
||||
ret( 0, 0 ) = cos( angle );
|
||||
ret( 0, 2 ) = sin( angle );
|
||||
ret( 1, 1 ) = 1.000000000f;
|
||||
ret( 2, 0 ) = -sin( angle );
|
||||
ret( 2, 2 ) = cos( angle );
|
||||
ret( 3, 3 ) = 1.000000000f;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static matrix4 rotateZ( float angle )
|
||||
{
|
||||
matrix4 ret = matrix4();
|
||||
ret( 0, 0 ) = cos( angle );
|
||||
ret( 0, 1 ) = -sin( angle );
|
||||
ret( 1, 0 ) = sin( angle );
|
||||
ret( 1, 1 ) = cos( angle );
|
||||
ret( 2, 2 ) = 1.000000000f;
|
||||
ret( 3, 3 ) = 1.000000000f;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static matrix4 scale( float x, float y, float z )
|
||||
{
|
||||
matrix4 ret = matrix4();
|
||||
ret( 0, 0 ) = x;
|
||||
ret( 1, 1 ) = y;
|
||||
ret( 2, 2 ) = z;
|
||||
ret( 3, 3 ) = 1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static matrix4 translate( float x, float y, float z )
|
||||
{
|
||||
matrix4 ret = matrix4();
|
||||
ret( 0, 0 ) = 1;
|
||||
ret( 1, 1 ) = 1;
|
||||
ret( 2, 2 ) = 1;
|
||||
ret( 3, 3 ) = 1;
|
||||
|
||||
ret( 3, 0 ) = x;
|
||||
ret( 3, 1 ) = y;
|
||||
ret( 3, 2 ) = z;
|
||||
return ret;
|
||||
}
|
||||
|
||||
matrix4 operator*( const matrix4& rhs ) const
|
||||
{
|
||||
matrix4 ret;
|
||||
for( unsigned int i = 0; i < 4; i++ )
|
||||
{
|
||||
ret( i, 0 ) =
|
||||
( *this )( i, 0 ) * rhs( 0, 0 ) + ( *this )( i, 1 ) * rhs( 1, 0 ) + ( *this )( i, 2 ) * rhs( 2, 0 ) +
|
||||
( *this )( i, 3 ) * rhs( 3, 0 );
|
||||
ret( i, 1 ) =
|
||||
( *this )( i, 0 ) * rhs( 0, 1 ) + ( *this )( i, 1 ) * rhs( 1, 1 ) + ( *this )( i, 2 ) * rhs( 2, 1 ) +
|
||||
( *this )( i, 3 ) * rhs( 3, 1 );
|
||||
ret( i, 2 ) =
|
||||
( *this )( i, 0 ) * rhs( 0, 2 ) + ( *this )( i, 1 ) * rhs( 1, 2 ) + ( *this )( i, 2 ) * rhs( 2, 2 ) +
|
||||
( *this )( i, 3 ) * rhs( 3, 2 );
|
||||
ret( i, 3 ) =
|
||||
( *this )( i, 0 ) * rhs( 0, 3 ) + ( *this )( i, 1 ) * rhs( 1, 3 ) + ( *this )( i, 2 ) * rhs( 2, 3 ) +
|
||||
( *this )( i, 3 ) * rhs( 3, 3 );
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
574
src/tools/nav_export/nav/TiledNavmeshGenerator.cpp
Normal file
574
src/tools/nav_export/nav/TiledNavmeshGenerator.cpp
Normal file
|
@ -0,0 +1,574 @@
|
|||
#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;
|
||||
|
||||
if( m_triareas )
|
||||
delete[] m_triareas;
|
||||
delete m_ctx;
|
||||
|
||||
rcFreeContourSet( m_cset );
|
||||
rcFreeHeightField( m_solid );
|
||||
rcFreeCompactHeightfield(m_chf);
|
||||
rcFreePolyMesh( m_pmesh );
|
||||
rcFreePolyMeshDetail( m_dmesh );
|
||||
dtFreeNavMesh( m_navMesh );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
123
src/tools/nav_export/nav/TiledNavmeshGenerator.h
Normal file
123
src/tools/nav_export/nav/TiledNavmeshGenerator.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
#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;
|
||||
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/nav_export/nav/ext/ChunkyTriMesh.cpp
Normal file
331
src/tools/nav_export/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/nav_export/nav/ext/ChunkyTriMesh.h
Normal file
68
src/tools/nav_export/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/nav_export/nav/ext/MeshLoaderObj.cpp
Normal file
252
src/tools/nav_export/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/nav_export/nav/ext/MeshLoaderObj.h
Normal file
82
src/tools/nav_export/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
|
59
src/tools/nav_export/navmesh_exporter.h
Normal file
59
src/tools/nav_export/navmesh_exporter.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#ifndef NAVMESH_EXPORTER_H
|
||||
#define NAVMESH_EXPORTER_H
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "exporter.h"
|
||||
#include "obj_exporter.h"
|
||||
#include "nav/TiledNavmeshGenerator.h"
|
||||
|
||||
#include <experimental/filesystem>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
class NavmeshExporter
|
||||
{
|
||||
public:
|
||||
static void exportZone( const ExportedZone& zone )
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
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;
|
||||
auto objName = fileName + ".obj";
|
||||
|
||||
std::error_code e;
|
||||
if( !fs::exists( objName, e ) )
|
||||
ObjExporter::exportZone( zone );
|
||||
|
||||
TiledNavmeshGenerator gen;
|
||||
|
||||
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", zone.name.c_str(),
|
||||
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||
}
|
||||
|
||||
};
|
||||
#endif // !OBJ_EXPORTER_H
|
137
src/tools/nav_export/obj_exporter.h
Normal file
137
src/tools/nav_export/obj_exporter.h
Normal file
|
@ -0,0 +1,137 @@
|
|||
#ifndef OBJ_EXPORTER_H
|
||||
#define OBJ_EXPORTER_H
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <experimental/filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
|
||||
#include "exporter.h"
|
||||
|
||||
|
||||
class ObjExporter
|
||||
{
|
||||
public:
|
||||
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 + "/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_directories( dir, e ) )
|
||||
{
|
||||
printf( "Unable to create directory '%s'", ( dir ).c_str() );
|
||||
return "";
|
||||
}
|
||||
}
|
||||
std::ofstream of( fileName, std::ios::trunc );
|
||||
int indicesOffset = 0;
|
||||
int meshesCount = 0;
|
||||
|
||||
if( of.good() )
|
||||
{
|
||||
of.close();
|
||||
of.open( fileName, std::ios::app );
|
||||
for( const auto& group : zone.groups )
|
||||
{
|
||||
exportGroup( group.second, of, indicesOffset, meshesCount );
|
||||
}
|
||||
of.flush();
|
||||
of.close();
|
||||
}
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 + "/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_directories( dir, e ) )
|
||||
{
|
||||
printf( "Unable to create directory '%s'", ( dir ).c_str() );
|
||||
return "";
|
||||
}
|
||||
}
|
||||
std::ofstream of( fileName, std::ios::trunc );
|
||||
int indicesOffset = 0;
|
||||
int modelCount = 0;
|
||||
|
||||
if( of.good() )
|
||||
{
|
||||
of.close();
|
||||
of.open( fileName, std::ios::app );
|
||||
exportGroup( group, of, indicesOffset, modelCount );
|
||||
of.flush();
|
||||
of.close();
|
||||
}
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
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:
|
||||
static void exportGroup( const ExportedGroup& group, std::ofstream& of, int& indicesOffset, int& modelCount )
|
||||
{
|
||||
int currModelCount = modelCount;
|
||||
|
||||
of << "o " << group.name << '_' << std::to_string( currModelCount ) << '\n';
|
||||
for( const auto& model : group.models )
|
||||
{
|
||||
modelCount++;
|
||||
of << "o " << model.second.name << '_' << std::to_string( currModelCount ) << '_' << std::to_string( modelCount ) << '\n';
|
||||
|
||||
int meshCount = 0;
|
||||
for( const auto& mesh : model.second.meshes )
|
||||
{
|
||||
for( int i = 0; i < mesh.verts.size(); i += 3 )
|
||||
{
|
||||
of << "v " <<
|
||||
std::to_string( mesh.verts[ i ] ) << ' ' <<
|
||||
std::to_string( mesh.verts[ i + 1 ] ) << ' ' <<
|
||||
std::to_string( mesh.verts[ i + 2 ] ) << '\n';
|
||||
}
|
||||
|
||||
of << "g " <<
|
||||
model.second.name << '_' <<
|
||||
std::to_string( currModelCount ) << '_' << std::to_string( modelCount ) << '_' << std::to_string( meshCount++ ) << '\n';
|
||||
|
||||
for( int i = 0; i < mesh.indices.size(); i += 3 )
|
||||
{
|
||||
of << "f " <<
|
||||
std::to_string( mesh.indices[ i ] + indicesOffset + 1 ) << ' ' <<
|
||||
std::to_string( mesh.indices[ i + 1 ] + indicesOffset + 1 ) << ' ' +
|
||||
std::to_string( mesh.indices[ i + 2 ] + indicesOffset + 1 ) << '\n';
|
||||
}
|
||||
indicesOffset += mesh.verts.size() / 3;
|
||||
}
|
||||
}
|
||||
//of.flush();
|
||||
}
|
||||
};
|
||||
#endif // !OBJ_EXPORTER_H
|
176
src/tools/nav_export/pcb.h
Normal file
176
src/tools/nav_export/pcb.h
Normal file
|
@ -0,0 +1,176 @@
|
|||
#ifndef _PCB_H
|
||||
#define _PCB_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
struct PCB_HEADER
|
||||
{
|
||||
uint32_t unknown_1;
|
||||
uint32_t unknown_2;
|
||||
uint32_t num_entries; // count starts at 0
|
||||
uint32_t total_indices;
|
||||
uint64_t padding;
|
||||
};
|
||||
|
||||
struct PCB_BLOCK_HEADER
|
||||
{
|
||||
uint32_t type; // 0 for entry, 0x30 for group
|
||||
uint32_t group_size; // when group size in bytes for the group block
|
||||
// bounding box
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float x1;
|
||||
float y1;
|
||||
float z1;
|
||||
// number of vertices packed into 16 bit
|
||||
uint16_t num_v16;
|
||||
// number of indices
|
||||
uint16_t num_indices;
|
||||
// number of normal floar vertices
|
||||
uint32_t num_vertices;
|
||||
};
|
||||
|
||||
struct PCB_VERTEXDATA
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
|
||||
struct PCB_INDEXDATA
|
||||
{
|
||||
uint8_t index[3];
|
||||
uint8_t unknown[3];
|
||||
uint8_t unknown1[6];
|
||||
};
|
||||
|
||||
struct PCB_VERTEXDATAI16
|
||||
{
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint16_t z;
|
||||
};
|
||||
|
||||
struct PCB_BLOCK_DATA
|
||||
{
|
||||
std::vector< PCB_VERTEXDATA > vertices;
|
||||
std::vector< PCB_VERTEXDATAI16 > vertices_i16;
|
||||
std::vector< PCB_INDEXDATA > indices;
|
||||
};
|
||||
|
||||
struct PCB_BLOCK_ENTRY
|
||||
{
|
||||
PCB_BLOCK_HEADER header;
|
||||
PCB_BLOCK_DATA data;
|
||||
};
|
||||
|
||||
struct PCB_FILE
|
||||
{
|
||||
PCB_HEADER header;
|
||||
std::vector< PCB_BLOCK_ENTRY > entries;
|
||||
|
||||
PCB_FILE( char* buf )
|
||||
{
|
||||
uint32_t offset = 0;
|
||||
memcpy( &header, buf, sizeof( header ));
|
||||
offset += sizeof( header );
|
||||
entries.resize( header.num_entries );
|
||||
bool isgroup = true;
|
||||
while( isgroup )
|
||||
{
|
||||
PCB_BLOCK_ENTRY block_entry;
|
||||
memcpy( &block_entry.header, buf + offset, sizeof( block_entry.header ) );
|
||||
isgroup = block_entry.header.type == 0x30;
|
||||
|
||||
//printf( "BLOCKHEADER_%X: type: %i, group_size: %i\n", offset, block_entry.header.type, block_entry.header.group_size );
|
||||
//
|
||||
if( isgroup )
|
||||
{
|
||||
parseBlockEntry( buf + offset + 0x30, entries, offset);
|
||||
offset += block_entry.header.group_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBlockEntry( buf + offset, entries, offset );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int parseBlockEntry( char* data, std::vector< PCB_BLOCK_ENTRY >& entries, int gOff )
|
||||
{
|
||||
int offset = 0;
|
||||
bool isgroup = true;
|
||||
while( isgroup )
|
||||
{
|
||||
PCB_BLOCK_ENTRY block_entry;
|
||||
memcpy( &block_entry.header, data + offset, sizeof( block_entry.header ) );
|
||||
isgroup = block_entry.header.type == 0x30;
|
||||
|
||||
//printf( " BLOCKHEADER_%X: type: %i, group_size: %i\n", gOff + offset, block_entry.header.type, block_entry.header.group_size );
|
||||
|
||||
if( isgroup )
|
||||
{
|
||||
parseBlockEntry( data + offset + 0x30, entries, gOff + offset );
|
||||
offset += block_entry.header.group_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* printf( "\tnum_v16: %i, num_indices: %i, num_vertices: %i\n\n",
|
||||
block_entry.header.num_v16, block_entry.header.num_indices, block_entry.header.num_vertices );*/
|
||||
int doffset = sizeof( block_entry.header ) + offset;
|
||||
uint16_t block_size = sizeof( block_entry.header ) +
|
||||
block_entry.header.num_vertices * 3 * 4 +
|
||||
block_entry.header.num_v16 * 6 +
|
||||
block_entry.header.num_indices * 6;
|
||||
|
||||
if( block_entry.header.num_vertices != 0 )
|
||||
{
|
||||
block_entry.data.vertices.resize( block_entry.header.num_vertices );
|
||||
|
||||
int32_t size_vertexbuffer = block_entry.header.num_vertices * 3;
|
||||
memcpy( &block_entry.data.vertices[ 0 ], data + doffset, size_vertexbuffer * 4 );
|
||||
doffset += size_vertexbuffer * 4;
|
||||
}
|
||||
if( block_entry.header.num_v16 != 0 )
|
||||
{
|
||||
block_entry.data.vertices_i16.resize( block_entry.header.num_v16 );
|
||||
int32_t size_unknownbuffer = block_entry.header.num_v16 * 6;
|
||||
memcpy( &block_entry.data.vertices_i16[ 0 ], data + doffset, size_unknownbuffer );
|
||||
doffset += block_entry.header.num_v16 * 6;
|
||||
}
|
||||
if( block_entry.header.num_indices != 0 )
|
||||
{
|
||||
block_entry.data.indices.resize( block_entry.header.num_indices );
|
||||
int32_t size_indexbuffer = block_entry.header.num_indices * 12;
|
||||
memcpy( &block_entry.data.indices[ 0 ], data + doffset, size_indexbuffer );
|
||||
doffset += size_indexbuffer;
|
||||
}
|
||||
entries.push_back( block_entry );
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct PCB_LIST_ENTRY
|
||||
{
|
||||
uint32_t id;
|
||||
float x, y, z, x2, y2, z2, rot;
|
||||
};
|
||||
|
||||
struct PCB_LIST_BASE_ENTRY
|
||||
{
|
||||
float x, y, z, x2, y2, z2, rot;
|
||||
};
|
||||
|
||||
struct PCB_LIST_FILE
|
||||
{
|
||||
uint32_t count;
|
||||
PCB_LIST_BASE_ENTRY entry;
|
||||
std::vector< PCB_LIST_ENTRY > entries;
|
||||
};
|
||||
#endif
|
276
src/tools/nav_export/sgb.h
Normal file
276
src/tools/nav_export/sgb.h
Normal file
|
@ -0,0 +1,276 @@
|
|||
#ifndef _SGB_H
|
||||
#define _SGB_H
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "vec3.h"
|
||||
|
||||
// garbage to skip model loading
|
||||
extern bool noObj;
|
||||
|
||||
//
|
||||
// ported from https://github.com/ufx/SaintCoinach/blob/master/SaintCoinach/Graphics/Sgb/SgbDataType.cs
|
||||
|
||||
struct SGB_FILE;
|
||||
struct SGB_HEADER;
|
||||
struct SGB_MODEL_ENTRY;
|
||||
struct SGB_MODEL_HEADER;
|
||||
struct SGB_GROUP;
|
||||
struct SGB_GROUP_HEADER;
|
||||
|
||||
|
||||
enum SgbDataType :
|
||||
uint32_t
|
||||
{
|
||||
Unknown0008 = 0x0008,
|
||||
Group = 0x0100,
|
||||
};
|
||||
|
||||
enum SgbGroupEntryType :
|
||||
uint32_t
|
||||
{
|
||||
Model = 0x01,
|
||||
Gimmick = 0x06,
|
||||
};
|
||||
|
||||
struct SGB_GROUP_HEADER
|
||||
{
|
||||
SgbDataType type;
|
||||
int32_t nameOffset;
|
||||
uint32_t unknown08;
|
||||
uint32_t unknown0C;
|
||||
|
||||
uint32_t unknown10;
|
||||
uint32_t unknown14;
|
||||
uint32_t unknown18;
|
||||
uint32_t unknown1C;
|
||||
|
||||
int32_t entryCount;
|
||||
uint32_t unknown24;
|
||||
uint32_t unknown28;
|
||||
uint32_t unknown2C;
|
||||
|
||||
uint32_t unknown30;
|
||||
uint32_t unknown34;
|
||||
uint32_t unknown38;
|
||||
uint32_t unknown3C;
|
||||
|
||||
uint32_t unknown40;
|
||||
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:
|
||||
char* m_buf;
|
||||
uint32_t m_offset;
|
||||
|
||||
SGB_GROUP_ENTRY()
|
||||
{
|
||||
m_buf = nullptr;
|
||||
m_offset = 0;
|
||||
};
|
||||
|
||||
SGB_GROUP_ENTRY( char* buf, uint32_t offset )
|
||||
{
|
||||
m_buf = buf;
|
||||
m_offset = offset;
|
||||
};
|
||||
|
||||
virtual ~SGB_GROUP_ENTRY()
|
||||
{
|
||||
};
|
||||
};
|
||||
|
||||
struct SGB_ENTRY_HEADER
|
||||
{
|
||||
SgbGroupEntryType type;
|
||||
uint32_t unknown2;
|
||||
int32_t nameOffset;
|
||||
vec3 translation;
|
||||
vec3 rotation;
|
||||
vec3 scale;
|
||||
};
|
||||
|
||||
struct SGB_MODEL_HEADER :
|
||||
public SGB_ENTRY_HEADER
|
||||
{
|
||||
int32_t modelFileOffset;
|
||||
int32_t collisionFileOffset;
|
||||
};
|
||||
|
||||
struct SGB_MODEL_ENTRY :
|
||||
public SGB_GROUP_ENTRY
|
||||
{
|
||||
SGB_MODEL_HEADER header;
|
||||
SgbGroupEntryType type;
|
||||
std::string name;
|
||||
std::string modelFileName;
|
||||
std::string collisionFileName;
|
||||
|
||||
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 );
|
||||
collisionFileName = std::string( buf + offset + header.collisionFileOffset );
|
||||
}
|
||||
};
|
||||
|
||||
struct SGB_GROUP
|
||||
{
|
||||
SGB_GROUP_HEADER header;
|
||||
std::string name;
|
||||
SGB_FILE* parent;
|
||||
std::vector< std::shared_ptr< SGB_GROUP_ENTRY > > entries;
|
||||
|
||||
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 );
|
||||
|
||||
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 || type == SgbGroupEntryType::Gimmick )
|
||||
{
|
||||
entries.push_back( std::make_shared< SGB_MODEL_ENTRY >( buf, entryOffset, ( SgbGroupEntryType )type ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// std::cout << "\t\tUnknown SGB entry! Group: " << name << " type: " << type << " index: " << i << " entryOffset: " << entryOffset << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct SGB_HEADER
|
||||
{
|
||||
char magic[4]; // SGB1
|
||||
uint32_t fileSize;
|
||||
uint32_t unknown1;
|
||||
char magic2[4]; // SCN1
|
||||
|
||||
uint32_t unknown10;
|
||||
int32_t sharedOffset;
|
||||
uint32_t unknown18;
|
||||
int32_t offset1C;
|
||||
|
||||
uint32_t unknown20;
|
||||
uint32_t unknown24;
|
||||
uint32_t unknown28;
|
||||
uint32_t unknown2C;
|
||||
|
||||
uint32_t unknown30;
|
||||
uint32_t unknown34;
|
||||
uint32_t unknown38;
|
||||
uint32_t unknown3C;
|
||||
|
||||
uint32_t unknown40;
|
||||
uint32_t unknown44;
|
||||
uint32_t unknown48;
|
||||
uint32_t unknown4C;
|
||||
|
||||
uint32_t unknown50;
|
||||
uint32_t unknown54;
|
||||
};
|
||||
|
||||
struct SGB_FILE
|
||||
{
|
||||
SGB_HEADER header;
|
||||
std::vector< SGB_GROUP > entries;
|
||||
std::set< std::string > offset1cObjects;
|
||||
|
||||
SGB_FILE()
|
||||
{
|
||||
memset( &header, 0, sizeof( header ) );
|
||||
}
|
||||
|
||||
SGB_FILE( char* buf )
|
||||
{
|
||||
constexpr int baseOffset = 0x14;
|
||||
header = *reinterpret_cast< SGB_HEADER* >( buf );
|
||||
|
||||
if( strncmp( &header.magic[ 0 ], "SGB1", 4 ) != 0 || strncmp( &header.magic2[ 0 ], "SCN1", 4 ) != 0 )
|
||||
throw std::runtime_error( "Unable to load SGB File!" );
|
||||
|
||||
try
|
||||
{
|
||||
auto group = SGB_GROUP( buf, this, &offset1cObjects, header.fileSize, baseOffset + header.sharedOffset );
|
||||
entries.push_back( group );
|
||||
auto group2 = SGB_GROUP( buf, this, &offset1cObjects, header.fileSize, baseOffset+ header.offset1C, true );
|
||||
entries.push_back( group2 );
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
std::cout << ( std::string( e.what() ) + "\n" );
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
#endif // !_SGB_H
|
108
src/tools/nav_export/threadpool.h
Normal file
108
src/tools/nav_export/threadpool.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
#ifndef THREADPOOL_H
|
||||
#define THREADPOOL_H
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
// credit to
|
||||
// https://riptutorial.com/cplusplus/example/15806/create-a-simple-thread-pool
|
||||
|
||||
class ThreadPool
|
||||
{
|
||||
public:
|
||||
ThreadPool()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~ThreadPool()
|
||||
{
|
||||
complete();
|
||||
}
|
||||
|
||||
void addWorkers( unsigned int num )
|
||||
{
|
||||
|
||||
std::unique_lock lock( m_mutex );
|
||||
m_runFlag = true;
|
||||
if( num == 0 )
|
||||
num = std::thread::hardware_concurrency() - 1;
|
||||
|
||||
for( auto i = 0; i < num; ++i )
|
||||
{
|
||||
m_workers.push_back( std::async( std::launch::async, [this]{ run(); } ) );
|
||||
}
|
||||
}
|
||||
|
||||
template< class Func, class Ret = std::result_of_t< Func&() > >
|
||||
std::future< Ret > queue( Func&& f )
|
||||
{
|
||||
std::packaged_task< Ret() > task( std::forward< Func >( f ) );
|
||||
auto ret = task.get_future();
|
||||
{
|
||||
std::unique_lock lock( m_mutex );
|
||||
m_pendingJobs.emplace_back( std::move( task ) );
|
||||
}
|
||||
m_cv.notify_one();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
{
|
||||
std::unique_lock lock( m_mutex );
|
||||
m_pendingJobs.clear();
|
||||
}
|
||||
complete();
|
||||
}
|
||||
|
||||
bool complete()
|
||||
{
|
||||
{
|
||||
std::unique_lock lock( m_mutex );
|
||||
for( auto&& worker : m_workers )
|
||||
{
|
||||
m_pendingJobs.push_back( {} );
|
||||
}
|
||||
}
|
||||
m_cv.notify_all();
|
||||
m_workers.clear();
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
void run()
|
||||
{
|
||||
while( 1 )
|
||||
{
|
||||
std::packaged_task< void() > func;
|
||||
{
|
||||
std::unique_lock lock( m_mutex );
|
||||
if( m_pendingJobs.empty() )
|
||||
{
|
||||
m_cv.wait( lock, [&](){ return !m_pendingJobs.empty(); } );
|
||||
}
|
||||
func = std::move( m_pendingJobs.front() );
|
||||
m_pendingJobs.pop_front();
|
||||
}
|
||||
if( !func.valid() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
bool m_runFlag{ true };
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
std::deque< std::packaged_task< void() > > m_pendingJobs;
|
||||
std::vector< std::future< void > > m_workers;
|
||||
};
|
||||
|
||||
#endif
|
34
src/tools/nav_export/vec3.h
Normal file
34
src/tools/nav_export/vec3.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#ifndef _VEC3_H
|
||||
#define _VEC3_H
|
||||
|
||||
#include <cstdint>
|
||||
#include "matrix4.h"
|
||||
|
||||
struct vec3
|
||||
{
|
||||
float x, y, z;
|
||||
|
||||
vec3()
|
||||
{
|
||||
x = 0.0f;
|
||||
y = 0.0f;
|
||||
z = 0.0f;
|
||||
}
|
||||
|
||||
vec3( float x, float y, float z )
|
||||
{
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
this->z = z;
|
||||
};
|
||||
};
|
||||
|
||||
static vec3 operator*( const vec3& lhs, const matrix4& rhs )
|
||||
{
|
||||
vec3 ret;
|
||||
ret.x = rhs( 0, 0 ) * lhs.x + rhs( 0, 1 ) * lhs.y + rhs( 0, 2 ) * lhs.z;
|
||||
ret.y = rhs( 1, 0 ) * lhs.x + rhs( 1, 1 ) * lhs.y + rhs( 1, 2 ) * lhs.z;
|
||||
ret.z = rhs( 2, 0 ) * lhs.x + rhs( 2, 1 ) * lhs.y + rhs( 2, 2 ) * lhs.z;
|
||||
return ret;
|
||||
};
|
||||
#endif
|
Loading…
Add table
Reference in a new issue