mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2025-04-24 10:38:53 +00:00
- removed global gltfParser so it cannot be used as an singleton anymore.
- fixed destruction and cleanup of gltfData and gltfParser - gltfParser always fixes up skeletonID's for skins that do not have it set. - fixed gltfData::GetAnimation to also check for duplicates taking multple targets for the same animation into account. - several boneless animation fixes [!] [ BUG WARNING ] Be aware -> for some reason models/meshes that use multple textures are not drawn correctly; ATM onlt single texure users are correct.
This commit is contained in:
parent
ecaf297ef6
commit
486315c7e7
7 changed files with 179 additions and 53 deletions
|
@ -1674,8 +1674,9 @@ bool idMapFile::Parse( const char* filename, bool ignoreRegion, bool osPath )
|
|||
}
|
||||
else if( isGTLF )
|
||||
{
|
||||
gltfParser->Load( fullName );
|
||||
idMapEntity::GetEntities( gltfParser->currentAsset, entities, gltfParser->currentAsset->GetSceneId( gltf_MapSceneName.GetString() ) );
|
||||
GLTF_Parser gltf;
|
||||
gltf.Load( fullName );
|
||||
idMapEntity::GetEntities( gltf.currentAsset, entities, gltf.currentAsset->GetSceneId( gltf_MapSceneName.GetString() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -55,6 +55,8 @@ MapPolygonMesh* MapPolygonMesh::ConvertFromMeshGltf( const gltfMesh_Primitive* p
|
|||
idFile_Memory idxBin = idFile_Memory( "gltfChunkIndices",
|
||||
( const char* )( ( data->GetData( bv->buffer ) + bv->byteOffset + accessor->byteOffset ) ), bv->byteLength );
|
||||
|
||||
|
||||
|
||||
for( int i = 0; i < accessor->count; i++ )
|
||||
{
|
||||
idxBin.Read( ( void* )( &indices[i] ), accessor->typeSize );
|
||||
|
@ -64,9 +66,15 @@ MapPolygonMesh* MapPolygonMesh::ConvertFromMeshGltf( const gltfMesh_Primitive* p
|
|||
}
|
||||
}
|
||||
|
||||
int polyCount = accessor->count /3;
|
||||
|
||||
mesh->polygons.AssureSize(polyCount );
|
||||
mesh->polygons.SetNum( polyCount );
|
||||
|
||||
int cnt = 0;
|
||||
for( int i = 0; i < accessor->count; i += 3 )
|
||||
{
|
||||
MapPolygon& polygon = mesh->polygons.Alloc();
|
||||
MapPolygon& polygon = mesh->polygons[cnt++];
|
||||
|
||||
if( mat != NULL )
|
||||
{
|
||||
|
@ -82,6 +90,8 @@ MapPolygonMesh* MapPolygonMesh::ConvertFromMeshGltf( const gltfMesh_Primitive* p
|
|||
polygon.AddIndex( indices[i + 0] );
|
||||
}
|
||||
|
||||
assert(cnt == polyCount );
|
||||
|
||||
Mem_Free( indices );
|
||||
bool sizeSet = false;
|
||||
|
||||
|
@ -273,9 +283,9 @@ void ProcessSceneNode( idMapEntity* newEntity, gltfNode* node, idMat4 trans, glt
|
|||
ProcessSceneNode( newEntity, nodeList[child], curTrans, data, isFuncStaticMesh );
|
||||
}
|
||||
|
||||
if( isFuncStaticMesh && node->mesh != -1 )
|
||||
if( node->mesh != -1 )
|
||||
{
|
||||
for( auto prim : data->MeshList()[node->mesh]->primitives )
|
||||
for( auto* prim : data->MeshList()[node->mesh]->primitives )
|
||||
{
|
||||
newEntity->AddPrimitive( MapPolygonMesh::ConvertFromMeshGltf( prim, data , curTrans ) );
|
||||
}
|
||||
|
|
|
@ -1135,8 +1135,14 @@ void gltfItem_texture_info_extensions::parse( idToken& token )
|
|||
|
||||
void GLTF_Parser::Shutdown()
|
||||
{
|
||||
parser.FreeSource();
|
||||
currentFile.FreeData();
|
||||
if( currentAsset )
|
||||
{
|
||||
delete currentAsset;
|
||||
}
|
||||
currentAsset = nullptr;
|
||||
buffersDone = false;
|
||||
bufferViewsDone = false;
|
||||
}
|
||||
GLTF_Parser::GLTF_Parser()
|
||||
: parser( LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ) , buffersDone( false ), bufferViewsDone( false ) { }
|
||||
|
@ -1914,6 +1920,12 @@ gltfProperty GLTF_Parser::ResolveProp( idToken& token )
|
|||
|
||||
bool GLTF_Parser::loadGLB( idStr filename )
|
||||
{
|
||||
if( fileSystem->GetFileLength( filename ) <= 0 )
|
||||
{
|
||||
common->Warning( " %s does not exist!", filename.c_str() );
|
||||
return false;
|
||||
}
|
||||
|
||||
idFile* file = fileSystem->OpenFileRead( filename );
|
||||
|
||||
if( file->Length() < 20 )
|
||||
|
@ -1959,7 +1971,6 @@ bool GLTF_Parser::loadGLB( idStr filename )
|
|||
length -= file->ReadUnsignedInt( chunk_type );
|
||||
|
||||
data = dataCache->AddData( chunk_length );
|
||||
dataCache->FileName( filename );
|
||||
|
||||
int read = file->Read( ( void* )data, chunk_length );
|
||||
if( read != chunk_length )
|
||||
|
@ -2076,12 +2087,12 @@ bool GLTF_Parser::Load( idStr filename )
|
|||
{
|
||||
|
||||
//.. and destroy data !!
|
||||
|
||||
gltfData* data = gltfData::Data( filename );
|
||||
currentFile = filename;
|
||||
if( data != nullptr )
|
||||
{
|
||||
currentAsset = data;
|
||||
parser.FreeSource();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2102,7 +2113,6 @@ bool GLTF_Parser::Load( idStr filename )
|
|||
}
|
||||
|
||||
data = gltfData::Data( filename , true );
|
||||
data->FileName( filename );
|
||||
byte* dataBuff = data->AddData( length );
|
||||
currentAsset = data;
|
||||
|
||||
|
@ -2134,18 +2144,29 @@ bool GLTF_Parser::Load( idStr filename )
|
|||
//fix up node hierarchy
|
||||
auto& nodeList = currentAsset->NodeList();
|
||||
for( auto& scene : currentAsset->SceneList() )
|
||||
{
|
||||
for( auto& node : scene->nodes )
|
||||
{
|
||||
SetNodeParent( nodeList[node] );
|
||||
}
|
||||
}
|
||||
|
||||
//set skeleton ID's
|
||||
for( auto* skin : currentAsset->SkinList() )
|
||||
{
|
||||
if( skin->skeleton == -1 )
|
||||
{
|
||||
skin->skeleton = currentAsset->GetNodeIndex( currentAsset->GetNode( skin->name ) );
|
||||
}
|
||||
}
|
||||
//prefix with id
|
||||
if( gltfParser_PrefixNodeWithID.GetBool() )
|
||||
{
|
||||
for( int i = 0; i < nodeList.Num(); i++ )
|
||||
{
|
||||
nodeList[i]->name = "[" + idStr( i ) + "]" + nodeList[i]->name;
|
||||
}
|
||||
|
||||
}
|
||||
//CreateBgfxData();
|
||||
return true;
|
||||
}
|
||||
|
@ -2284,20 +2305,45 @@ idList<idQuat*>& gltfData::GetAccessorView( gltfAccessor* accessor )
|
|||
|
||||
gltfData::~gltfData()
|
||||
{
|
||||
//hvg_todo
|
||||
//delete data, not only pointer
|
||||
common->Warning( "GLTF DATA NOT FREED" );
|
||||
|
||||
if( data )
|
||||
{
|
||||
delete[] data;
|
||||
while( totalChunks )
|
||||
{
|
||||
Mem_Free( data[--totalChunks] );
|
||||
}
|
||||
Mem_Free( data );
|
||||
}
|
||||
|
||||
//delete cameraManager;
|
||||
if( json )
|
||||
{
|
||||
Mem_Free( json );
|
||||
}
|
||||
|
||||
data = nullptr;
|
||||
json = nullptr;
|
||||
ClearData( fileName );
|
||||
|
||||
}
|
||||
|
||||
void gltfData::ClearData( idStr& fileName )
|
||||
{
|
||||
int key = fileDataHash.GenerateKey( fileName );
|
||||
int index = fileDataHash.GetFirst( key );
|
||||
|
||||
if( index != -1 )
|
||||
{
|
||||
dataList.RemoveIndex( index );
|
||||
dataList.Condense();
|
||||
fileDataHash.RemoveIndex( key, index );
|
||||
}
|
||||
else
|
||||
{
|
||||
common->DWarning( " tried to clear GLTF data while no data was loaded for %s", fileName.c_str( ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GLTF_Parser localGltfParser;
|
||||
GLTF_Parser* gltfParser = &localGltfParser;
|
||||
|
||||
#undef GLTFARRAYITEM
|
||||
#undef GLTFARRAYITEMREF
|
||||
|
@ -2307,7 +2353,8 @@ CONSOLE_COMMAND_COMPILE( LoadGLTF, "Loads an .gltf or .glb file", idCmdSystem::A
|
|||
|
||||
if( args.Argc() > 1 )
|
||||
{
|
||||
gltfParser->Load( args.Argv( 1 ) );
|
||||
GLTF_Parser gltf;
|
||||
gltf.Load( args.Argv( 1 ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -303,6 +303,10 @@ private:
|
|||
class GLTF_Parser
|
||||
{
|
||||
public:
|
||||
~GLTF_Parser()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
GLTF_Parser();
|
||||
void Shutdown();
|
||||
bool Parse();
|
||||
|
@ -350,6 +354,4 @@ class gltfManager
|
|||
{
|
||||
public:
|
||||
static bool ExtractIdentifier( idStr& filename , int& id, idStr& name );
|
||||
};
|
||||
|
||||
extern GLTF_Parser* gltfParser;
|
||||
};
|
|
@ -765,7 +765,7 @@ const inline idList<gltf##name*> & ##name##List() { return target; }
|
|||
class gltfData
|
||||
{
|
||||
public:
|
||||
gltfData() : fileNameHash( 0 ), json( nullptr ), data( nullptr ), totalChunks( -1 ) { };
|
||||
gltfData() : fileName( "" ), fileNameHash( 0 ), json( nullptr ), data( nullptr ), totalChunks( -1 ) { };
|
||||
~gltfData();
|
||||
byte* AddData( int size, int* bufferID = nullptr );
|
||||
byte* GetJsonData( int& size )
|
||||
|
@ -777,10 +777,10 @@ public:
|
|||
{
|
||||
return data[index];
|
||||
}
|
||||
void FileName( const idStr& file )
|
||||
void FileName( const idStr& file, int hash )
|
||||
{
|
||||
fileName = file;
|
||||
fileNameHash = fileDataHash.GenerateKey( file.c_str() );
|
||||
fileNameHash = hash;
|
||||
}
|
||||
int FileNameHash()
|
||||
{
|
||||
|
@ -796,6 +796,12 @@ public:
|
|||
//add data for filename
|
||||
static gltfData* Data( idStr& fileName, bool create = false )
|
||||
{
|
||||
static bool intialized = false;
|
||||
if( ! intialized )
|
||||
{
|
||||
dataList.SetGranularity( 1 );
|
||||
intialized = true;
|
||||
}
|
||||
int key = fileDataHash.GenerateKey( fileName );
|
||||
int index = fileDataHash.GetFirst( key );
|
||||
|
||||
|
@ -803,8 +809,8 @@ public:
|
|||
{
|
||||
index = dataList.Num( );
|
||||
dataList.AssureSizeAlloc( index + 1, idListNewElement<gltfData> );
|
||||
dataList[index]->FileName( fileName );
|
||||
fileDataHash.Add( fileDataHash.GenerateKey( fileName ), index );
|
||||
dataList[index]->FileName( fileName, key );
|
||||
fileDataHash.Add( key , index );
|
||||
}
|
||||
|
||||
if( !create && index < 0 )
|
||||
|
@ -818,10 +824,8 @@ public:
|
|||
{
|
||||
return dataList;
|
||||
}
|
||||
static void ClearData()
|
||||
{
|
||||
idLib::Warning( "TODO! DATA NOT FREED" );
|
||||
}
|
||||
|
||||
static void ClearData( idStr& fileName );
|
||||
|
||||
//return the GLTF nodes that control the given camera
|
||||
//return TRUE if the camera uses 2 nodes (like when blender exports gltfs with +Y..)
|
||||
|
@ -909,6 +913,29 @@ public:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
gltfNode* GetNode( idStr name, int* id = nullptr, bool caseSensitive = false )
|
||||
{
|
||||
assert( name[0] );
|
||||
|
||||
auto& nodeList = NodeList();
|
||||
for( auto* node : nodes )
|
||||
{
|
||||
int nodeId = GetNodeIndex( node );
|
||||
if( caseSensitive ? nodes[nodeId]->name.Cmp( name ) : nodes[nodeId]->name.Icmp( name ) == 0 )
|
||||
{
|
||||
if( id != nullptr )
|
||||
{
|
||||
*id = nodeId;
|
||||
}
|
||||
|
||||
return nodes[nodeId];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
gltfNode* GetNode( idStr sceneName, idStr name , int* id = nullptr , bool caseSensitive = false )
|
||||
{
|
||||
int sceneId = GetSceneId( sceneName );
|
||||
|
@ -968,13 +995,25 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
gltfAnimation* GetAnimation( idStr animName )
|
||||
gltfAnimation* GetAnimation( idStr animName, int target )
|
||||
{
|
||||
for( auto anim : animations )
|
||||
for( auto* anim : animations )
|
||||
{
|
||||
if( anim->name == animName )
|
||||
{
|
||||
return anim;
|
||||
bool hasTarget = false;
|
||||
for( auto* channel : anim->channels )
|
||||
{
|
||||
if( channel->target.node == target )
|
||||
{
|
||||
hasTarget = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( hasTarget )
|
||||
{
|
||||
return anim;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
|
|
@ -163,10 +163,11 @@ void idRenderModelGLTF::InitFromFile( const char* fileName )
|
|||
model_state = DM_STATIC;
|
||||
|
||||
gltfManager::ExtractIdentifier( gltfFileName, meshID, meshName );
|
||||
gltfParser->Load( gltfFileName );
|
||||
GLTF_Parser gltf;
|
||||
gltf.Load( gltfFileName );
|
||||
|
||||
timeStamp = fileSystem->GetTimestamp( gltfFileName );
|
||||
data = gltfParser->currentAsset;
|
||||
data = gltf.currentAsset;
|
||||
|
||||
bounds.Clear();
|
||||
|
||||
|
@ -228,8 +229,9 @@ void idRenderModelGLTF::InitFromFile( const char* fileName )
|
|||
animCount = data->GetAnimationIds( nodes[bones[0]] , animIds );
|
||||
}
|
||||
}
|
||||
else if( animCount )
|
||||
else
|
||||
{
|
||||
animCount = data->GetAnimationIds( tmpNode , animIds );
|
||||
bones.Append( meshID );
|
||||
}
|
||||
|
||||
|
@ -241,7 +243,7 @@ void idRenderModelGLTF::InitFromFile( const char* fileName )
|
|||
|
||||
ProcessNode( root, mat4_identity, data );
|
||||
|
||||
if( surfaces.Num( ) <= 0 || surfaces.Num( ) != MeshNodeIds.Num( ) )
|
||||
if( surfaces.Num( ) <= 0 )
|
||||
{
|
||||
common->Warning( "Couldn't load model: '%s'", name.c_str( ) );
|
||||
MakeDefaultModel( );
|
||||
|
@ -479,15 +481,14 @@ bool gatherBoneInfo( gltfData* data, gltfAnimation* gltfAnim, const idList<gltfN
|
|||
{
|
||||
//Gather Bones;
|
||||
bool boneLess = false;
|
||||
int targetNode = -1;
|
||||
int targetNode = lastMeshFromFile->rootID;
|
||||
|
||||
auto skin = data->GetSkin( gltfAnim );
|
||||
auto targets = data->GetAnimTargets( gltfAnim );
|
||||
|
||||
if( targets.Num( ) == 1 )
|
||||
if( skin == nullptr )
|
||||
{
|
||||
boneLess = true;
|
||||
targetNode = targets[0];
|
||||
}
|
||||
|
||||
//we cant be sure channels are sorted by bone?
|
||||
|
@ -495,7 +496,7 @@ bool gatherBoneInfo( gltfData* data, gltfAnimation* gltfAnim, const idList<gltfN
|
|||
{
|
||||
if( skin == nullptr )
|
||||
{
|
||||
skin = data->GetSkin( targets[0] );
|
||||
skin = data->GetSkin( targetNode );
|
||||
}
|
||||
assert( skin );
|
||||
bones.Append( skin->joints );
|
||||
|
@ -594,27 +595,34 @@ int copyBones( gltfData* data, const idList<int>& bones, idList<gltfNode>& out )
|
|||
|
||||
idFile_Memory* idRenderModelGLTF::GetAnimBin( idStr animName , const ID_TIME_T sourceTimeStamp )
|
||||
{
|
||||
assert( lastMeshFromFile );
|
||||
///keep in sync with game!
|
||||
static const byte B_ANIM_MD5_VERSION = 101;
|
||||
static const unsigned int B_ANIM_MD5_MAGIC = ( 'B' << 24 ) | ( 'M' << 16 ) | ( 'D' << 8 ) | B_ANIM_MD5_VERSION;
|
||||
|
||||
GLTF_Parser gltf;
|
||||
int id;
|
||||
idStr gltfFileName = idStr( animName );
|
||||
idStr name;
|
||||
gltfManager::ExtractIdentifier( gltfFileName, id, name );
|
||||
gltfParser->Load( gltfFileName );
|
||||
gltf.Load( gltfFileName );
|
||||
|
||||
gltfData* data = gltfParser->currentAsset;
|
||||
gltfData* data = gltf.currentAsset;
|
||||
auto& accessors = data->AccessorList( );
|
||||
auto& nodes = data->NodeList( );
|
||||
|
||||
auto gltfAnim = data->GetAnimation( name );
|
||||
gltfNode* nodeRoot = nodes[lastMeshFromFile->rootID];
|
||||
int boneRootNode = lastMeshFromFile->rootID;
|
||||
if( nodeRoot->skin > -1 )
|
||||
{
|
||||
boneRootNode = nodes[data->SkinList()[nodeRoot->skin]->skeleton]->children[0];
|
||||
}
|
||||
|
||||
auto gltfAnim = data->GetAnimation( name, boneRootNode );
|
||||
if( !gltfAnim )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto& accessors = data->AccessorList( );
|
||||
auto& nodes = data->NodeList( );
|
||||
|
||||
idList<int, TAG_MODEL> bones;
|
||||
idList<jointAnimInfo_t, TAG_MD5_ANIM> jointInfo;
|
||||
|
||||
|
@ -637,6 +645,11 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( idStr animName , const ID_TIME_T
|
|||
int channelCount = 0;
|
||||
for( auto channel : gltfAnim->channels )
|
||||
{
|
||||
if( !bones.Find( channel->target.node ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* sampler = gltfAnim->samplers[channel->sampler];
|
||||
|
||||
auto* input = accessors[sampler->input];
|
||||
|
@ -729,6 +742,7 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( idStr animName , const ID_TIME_T
|
|||
skin->joints.AssureSize( 1, data->GetNodeIndex( root ) );
|
||||
idMat4 trans = mat4_identity;
|
||||
data->ResolveNodeMatrix( root, &trans );
|
||||
trans *= axisTransform.Inverse();
|
||||
acc = new gltfAccessor( );
|
||||
acc->matView = new idList<idMat4>( 1 );
|
||||
acc->matView->AssureSize( 1, trans.Inverse( ).Transpose( ) );
|
||||
|
@ -744,6 +758,11 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( idStr animName , const ID_TIME_T
|
|||
{
|
||||
for( auto channel : gltfAnim->channels )
|
||||
{
|
||||
if( !bones.Find( channel->target.node ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto sampler = gltfAnim->samplers[channel->sampler];
|
||||
|
||||
auto* input = accessors[sampler->input];
|
||||
|
@ -778,7 +797,10 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( idStr animName , const ID_TIME_T
|
|||
break;
|
||||
case gltfAnimation_Channel_Target::scale:
|
||||
idList<idVec3*>& values = data->GetAccessorView<idVec3>( output );
|
||||
animBones[i][boneIndex].scale = *values[i] ;
|
||||
if( values.Num( ) > i )
|
||||
{
|
||||
animBones[i][boneIndex].scale = *values[i] ;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -807,6 +829,10 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( idStr animName , const ID_TIME_T
|
|||
if( node->parent == nullptr )
|
||||
{
|
||||
q = axisTransformAngels.ToQuat( ) * animBones[i][b].rotation;
|
||||
if( animBones[i].Num() == 1 )
|
||||
{
|
||||
q = -animBones[i][b].rotation;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1009,7 +1035,7 @@ void idRenderModelGLTF::WriteBinaryModel( idFile* file, ID_TIME_T* _timeStamp /*
|
|||
file->WriteBig( GLMB_MAGIC );
|
||||
file->WriteBig( model_state );
|
||||
file->WriteBig( rootID );
|
||||
file->WriteString( data->FileName() );
|
||||
file->WriteString( file->GetName() );
|
||||
|
||||
file->WriteBig( animIds.Num( ) );
|
||||
if( animIds.Num( ) )
|
||||
|
@ -1073,8 +1099,6 @@ void idRenderModelGLTF::PurgeModel()
|
|||
void idRenderModelGLTF::LoadModel()
|
||||
{
|
||||
int num;
|
||||
|
||||
gltfData* data = gltfParser->currentAsset;
|
||||
auto& accessors = data->AccessorList( );
|
||||
auto& nodes = data->NodeList( );
|
||||
gltfNode* meshRoot = data->GetNode( gltf_ModelSceneName.GetString(), meshName );
|
||||
|
@ -1481,6 +1505,9 @@ idRenderModel* idRenderModelGLTF::InstantiateDynamicModel( const struct renderEn
|
|||
if( purged )
|
||||
{
|
||||
common->DWarning( "model %s instantiated while purged", Name() );
|
||||
GLTF_Parser gltf;
|
||||
gltf.Load( name );
|
||||
data = gltf.currentAsset;
|
||||
LoadModel();
|
||||
}
|
||||
|
||||
|
|
|
@ -55,11 +55,11 @@ public:
|
|||
return true;
|
||||
}
|
||||
static idFile_Memory* GetAnimBin( idStr animName, const ID_TIME_T sourceTimeStamp );
|
||||
int rootID;
|
||||
private:
|
||||
void ProcessNode( gltfNode* modelNode, idMat4 trans, gltfData* data );
|
||||
void UpdateSurface( const struct renderEntity_s* ent, const idJointMat* entJoints, const idJointMat* entJointsInverted, modelSurface_t* surf );
|
||||
void UpdateMd5Joints();
|
||||
int rootID;
|
||||
gltfData* data;
|
||||
gltfNode* root;
|
||||
bool fileExclusive;
|
||||
|
|
Loading…
Reference in a new issue