diff --git a/neo/CMakeLists.txt b/neo/CMakeLists.txt index 86d56322..54dc2978 100644 --- a/neo/CMakeLists.txt +++ b/neo/CMakeLists.txt @@ -554,6 +554,8 @@ endif (RAPIDJSON_FOUND) add_subdirectory(idlib) +file(GLOB NATVIS_SOURCES .natvis) + file(GLOB AAS_INCLUDES aas/*.h) file(GLOB AAS_SOURCES aas/*.cpp) @@ -1473,6 +1475,7 @@ set(RBDOOM3_INCLUDES ) set(RBDOOM3_SOURCES + ${NATVIS_SOURCES} ${AAS_SOURCES} ${CM_SOURCES} ${FRAMEWORK_SOURCES} diff --git a/neo/cm/CollisionModel_files.cpp b/neo/cm/CollisionModel_files.cpp index e3418945..636f5376 100644 --- a/neo/cm/CollisionModel_files.cpp +++ b/neo/cm/CollisionModel_files.cpp @@ -633,7 +633,7 @@ bool idCollisionModelManagerLocal::LoadCollisionModelFile( const char* name, uns int id; idStr tmp; idStr file = fileName; - gltfManager::ExtractMeshIdentifier( file, id, tmp ); + gltfManager::ExtractIdentifier( file, id, tmp ); currentTimeStamp = fileSystem->GetTimestamp( file ); } diff --git a/neo/cm/CollisionModel_load.cpp b/neo/cm/CollisionModel_load.cpp index e70745f6..dcdbfe69 100644 --- a/neo/cm/CollisionModel_load.cpp +++ b/neo/cm/CollisionModel_load.cpp @@ -4573,7 +4573,7 @@ cmHandle_t idCollisionModelManagerLocal::LoadModel( const char* modelName, const int id; idStr tmp; idStr file = modelName; - gltfManager::ExtractMeshIdentifier( file, id, tmp ); + gltfManager::ExtractIdentifier( file, id, tmp ); sourceTimeStamp = fileSystem->GetTimestamp( file ); } diff --git a/neo/d3xp/Entity.cpp b/neo/d3xp/Entity.cpp index cd8ea2a8..233c0bc8 100644 --- a/neo/d3xp/Entity.cpp +++ b/neo/d3xp/Entity.cpp @@ -283,7 +283,15 @@ void idGameEdit::ParseSpawnArgsToRenderEntity( const idDict* args, renderEntity_ if( renderEntity->hModel ) { - renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + // RB: glTF2 models can be static and cached + if( renderEntity->hModel->IsDynamicModel() == DM_STATIC ) + { + renderEntity->bounds = renderEntity->hModel->Bounds( NULL ); + } + else + { + renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + } } else { diff --git a/neo/d3xp/anim/Anim.cpp b/neo/d3xp/anim/Anim.cpp index e116d0d1..f002b7fe 100644 --- a/neo/d3xp/anim/Anim.cpp +++ b/neo/d3xp/anim/Anim.cpp @@ -31,6 +31,8 @@ If you have questions concerning this license or the applicable additional terms #include "../Game_local.h" +//should go before release? +#include "renderer/Model_gltf.h" idCVar binaryLoadAnim( "binaryLoadAnim", "1", 0, "enable binary load/write of idMD5Anim" ); @@ -177,18 +179,47 @@ idMD5Anim::LoadAnim */ bool idMD5Anim::LoadAnim( const char* filename ) { - idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); idToken token; + idStr extension; + idStr filenameStr = idStr( filename ); + filenameStr.ExtractFileExtension( extension ); idStr generatedFileName = "generated/anim/"; generatedFileName.AppendPath( filename ); generatedFileName.SetFileExtension( ".bMD5anim" ); - // Get the timestamp on the original file, if it's newer than what is stored in binary model, regenerate it - ID_TIME_T sourceTimeStamp = fileSystem->GetTimestamp( filename ); + idStr gltfFileName = idStr( filename ); + int gltfAnimId = -1; + idStr gltfAnimName; + + + // Get the timestamp on the original file, if it's newer than what is stored in binary model, regenerate it + ID_TIME_T sourceTimeStamp; + + bool isGLTF = ( extension.Icmp( GLTF_GLB_EXT ) == 0 ) || ( extension.Icmp( GLTF_EXT ) == 0 ) ; + + if( isGLTF ) + { + gltfManager::ExtractIdentifier( gltfFileName, gltfAnimId, gltfAnimName ); + + sourceTimeStamp = fileSystem->GetTimestamp( gltfFileName ); + } + else + { + sourceTimeStamp = fileSystem->GetTimestamp( filename ); + } + + idFile* fileptr = fileSystem->OpenFileReadMemory( generatedFileName ); + bool doWrite = false; + if( fileptr == nullptr && isGLTF ) + { + fileptr = idRenderModelGLTF::GetAnimBin( filenameStr , sourceTimeStamp ); + doWrite = fileptr != nullptr; + } + + idFileLocal file( fileptr ); - idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) ); if( binaryLoadAnim.GetBool() && LoadBinary( file, sourceTimeStamp ) ) { name = filename; @@ -197,6 +228,14 @@ bool idMD5Anim::LoadAnim( const char* filename ) // for resource gathering write this anim to the preload file for this map fileSystem->AddAnimPreload( name ); } + if( doWrite && binaryLoadAnim.GetBool() ) + { + idLib::Printf( "Writing %s\n", generatedFileName.c_str() ); + fileptr->Seek( 0, FS_SEEK_SET ); + idFile_Memory* memFile = static_cast( fileptr ); + fileSystem->WriteFile( generatedFileName, memFile->GetDataPtr(), memFile->GetAllocated(), "fs_basepath" ); + } + return true; } diff --git a/neo/d3xp/anim/Anim_Blend.cpp b/neo/d3xp/anim/Anim_Blend.cpp index ac0798f2..e66c1d1a 100644 --- a/neo/d3xp/anim/Anim_Blend.cpp +++ b/neo/d3xp/anim/Anim_Blend.cpp @@ -3361,7 +3361,7 @@ bool idDeclModelDef::Parse( const char* text, const int textLength, bool allowBi // get the number of joints num = modelHandle->NumJoints(); - if( !num ) + if( !num && !isGltf ) { src.Warning( "Model '%s' has no joints", filename.c_str() ); } @@ -3373,7 +3373,7 @@ bool idDeclModelDef::Parse( const char* text, const int textLength, bool allowBi joints.SetNum( num ); jointParents.SetNum( num ); channelJoints[0].SetNum( num ); - md5joints = modelHandle->GetJoints( ); + md5joints = modelHandle->GetJoints(); md5joint = md5joints; for( i = 0; i < num; i++, md5joint++ ) { diff --git a/neo/idStuff.natvis b/neo/idStuff.natvis new file mode 100644 index 00000000..5b4ab57d --- /dev/null +++ b/neo/idStuff.natvis @@ -0,0 +1,63 @@ + + + + + + + + {{Size={num} Capacity={size}}} + + num + size + + num + list + + + + + + {{{x,g},{y,g},{z,g},{w,g}}} + + + + {{{x},{y}}} + + + + {data,s} + + + + {name} + + sprite + edittext + font + text + imageSize + name + + + + + + value.object + value.function + value.string + value.f + value + value.b + value.i + value.i + + + + + [{name}]{value} + + + + [{name}] + + \ No newline at end of file diff --git a/neo/idlib/MapFile.cpp b/neo/idlib/MapFile.cpp index 6ddd54e6..94ca74c6 100644 --- a/neo/idlib/MapFile.cpp +++ b/neo/idlib/MapFile.cpp @@ -5,6 +5,7 @@ Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. Copyright (C) 2015-2022 Robert Beckebans Copyright (C) 2020 Admer (id Tech Fox) +Copyright (C) 2022 Harrie van Ginneken This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). @@ -1673,13 +1674,9 @@ bool idMapFile::Parse( const char* filename, bool ignoreRegion, bool osPath ) } else if( isGTLF ) { - gltfParser->Load( fullName ); - idMapEntity::GetEntities( gltfParser->currentAsset, entities, 0 ); - } - 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 { diff --git a/neo/idlib/MapFile_gltf.cpp b/neo/idlib/MapFile_gltf.cpp index 8f1960c5..0afa42d0 100644 --- a/neo/idlib/MapFile_gltf.cpp +++ b/neo/idlib/MapFile_gltf.cpp @@ -39,7 +39,7 @@ MapPolygonMesh* MapPolygonMesh::ConvertFromMeshGltf( const gltfMesh_Primitive* p gltfData* data = bv->parent; // files import as y-up. Use this transform to change the model to z-up. - idMat3 rotation = idAngles( 0.0f, 0.0f, 90.0f ).ToMat3( ); + idMat3 rotation = idAngles( 0.0f, 0.0f, 90.0f ).ToMat3(); idMat4 axisTransform( rotation, vec3_origin ); gltfMaterial* mat = NULL; @@ -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; @@ -204,30 +214,72 @@ MapPolygonMesh* MapPolygonMesh::ConvertFromMeshGltf( const gltfMesh_Primitive* p } break; } - //case gltfMesh_Primitive_Attribute::Type::Weight: - //{ - // for ( int i = 0; i < attrAcc->count; i++ ) { - // bin.Read( ( void * ) ( &vtxData[i].weight.x ), attrAcc->typeSize ); - // bin.Read( ( void * ) ( &vtxData[i].weight.y ), attrAcc->typeSize ); - // bin.Read( ( void * ) ( &vtxData[i].weight.z ), attrAcc->typeSize ); - // bin.Read( ( void * ) ( &vtxData[i].weight.w ), attrAcc->typeSize ); - // if ( attrBv->byteStride ) - // bin.Seek( attrBv->byteStride - ( attrib->elementSize * attrAcc->typeSize ), FS_SEEK_CUR ); - // } - // break; - //} - //case gltfMesh_Primitive_Attribute::Type::Indices: - //{ - // for ( int i = 0; i < attrAcc->count; i++ ) { - // bin.Read( ( void * ) ( &vtxData[i].boneIndex.x ), attrAcc->typeSize ); - // bin.Read( ( void * ) ( &vtxData[i].boneIndex.y ), attrAcc->typeSize ); - // bin.Read( ( void * ) ( &vtxData[i].boneIndex.z ), attrAcc->typeSize ); - // bin.Read( ( void * ) ( &vtxData[i].boneIndex.w ), attrAcc->typeSize ); - // if ( attrBv->byteStride ) - // bin.Seek( attrBv->byteStride - ( attrib->elementSize * attrAcc->typeSize ), FS_SEEK_CUR ); - // } - // break; - //} + case gltfMesh_Primitive_Attribute::Type::Weight: + { + idVec4 vec; + for( int i = 0; i < attrAcc->count; i++ ) + { + bin.Read( ( void* )( &vec.x ), attrAcc->typeSize ); + bin.Read( ( void* )( &vec.y ), attrAcc->typeSize ); + bin.Read( ( void* )( &vec.z ), attrAcc->typeSize ); + bin.Read( ( void* )( &vec.w ), attrAcc->typeSize ); + if( attrBv->byteStride ) + { + bin.Seek( attrBv->byteStride - ( attrib->elementSize * attrAcc->typeSize ), FS_SEEK_CUR ); + } + + mesh->verts[i].SetColor2( PackColor( vec ) ); + + } + break; + } + case gltfMesh_Primitive_Attribute::Type::Indices: + { + if( attrAcc->typeSize == 2 ) + { + uint16_t vec[4]; + for( int i = 0; i < attrAcc->count; i++ ) + { + bin.Read( ( void* )( &vec[0] ), attrAcc->typeSize ); + bin.Read( ( void* )( &vec[1] ), attrAcc->typeSize ); + bin.Read( ( void* )( &vec[2] ), attrAcc->typeSize ); + bin.Read( ( void* )( &vec[3] ), attrAcc->typeSize ); + if( attrBv->byteStride ) + { + bin.Seek( attrBv->byteStride - ( attrib->elementSize * attrAcc->typeSize ), FS_SEEK_CUR ); + } + + mesh->verts[i].color[0] = vec[0]; + mesh->verts[i].color[1] = vec[1]; + mesh->verts[i].color[2] = vec[2]; + mesh->verts[i].color[3] = vec[3]; + } + } + else + { + uint8_t vec[4]; + for( int i = 0; i < attrAcc->count; i++ ) + { + assert( sizeof( vec ) == attrAcc->typeSize ); + + bin.Read( ( void* )( &vec[0] ), attrAcc->typeSize ); + bin.Read( ( void* )( &vec[1] ), attrAcc->typeSize ); + bin.Read( ( void* )( &vec[2] ), attrAcc->typeSize ); + bin.Read( ( void* )( &vec[3] ), attrAcc->typeSize ); + if( attrBv->byteStride ) + { + bin.Seek( attrBv->byteStride - ( attrib->elementSize * attrAcc->typeSize ), FS_SEEK_CUR ); + } + + mesh->verts[i].color[0] = vec[0]; + mesh->verts[i].color[1] = vec[1]; + mesh->verts[i].color[2] = vec[2]; + mesh->verts[i].color[3] = vec[3]; + } + } + break; + + } } } @@ -256,9 +308,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 ) ); } @@ -284,7 +336,7 @@ void ProcessSceneNode( idMapEntity* newEntity, gltfNode* node, idMat4 trans, glt origin.z = node->translation.z; // files import as y-up. Use this transform to change the model to z-up. - idMat3 rotation = idAngles( 0.0f, 0.0f, 90.0f ).ToMat3( ); + idMat3 rotation = idAngles( 0.0f, 0.0f, 90.0f ).ToMat3(); idMat4 axisTransform( rotation, vec3_origin ); origin *= axisTransform; @@ -299,7 +351,7 @@ void Map_AddMeshes( idMapEntity* _Entity, gltfNode* _Node, idMat4& _Trans, gltfD if( _Node->mesh != -1 ) { - for( auto prim : _Data->MeshList( )[_Node->mesh]->primitives ) + for( auto prim : _Data->MeshList()[_Node->mesh]->primitives ) { _Entity->AddPrimitive( MapPolygonMesh::ConvertFromMeshGltf( prim, _Data, curTrans ) ); } @@ -307,7 +359,7 @@ void Map_AddMeshes( idMapEntity* _Entity, gltfNode* _Node, idMat4& _Trans, gltfD for( auto& child : _Node->children ) { - Map_AddMeshes( _Entity, _Data->NodeList( )[child], curTrans, _Data ); + Map_AddMeshes( _Entity, _Data->NodeList()[child], curTrans, _Data ); } }; diff --git a/neo/idlib/gltfParser.cpp b/neo/idlib/gltfParser.cpp index d80dd118..50ae2953 100644 --- a/neo/idlib/gltfParser.cpp +++ b/neo/idlib/gltfParser.cpp @@ -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 ) @@ -1948,7 +1960,7 @@ bool GLTF_Parser::loadGLB( idStr filename ) unsigned int chunk_type = 0; // 4 bytes unsigned int chunk_length = 0; // 4 bytes byte* data = nullptr; - gltfData* dataCache = gltfData::Data( filename ); + gltfData* dataCache = gltfData::Data( filename, true ); currentAsset = dataCache; int chunkCount = 0; @@ -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 ) @@ -2074,21 +2085,18 @@ bool GLTF_Parser::Parse() bool GLTF_Parser::Load( idStr filename ) { - //seriously fix this; proper gltf data cache. - //.. and destroy it properly too!! - static idStr lastFile = ""; - //next line still has to be fixed. - //gfx is not updated on command - common->SetRefreshOnPrint( true ); - - if( lastFile == filename ) + //.. and destroy data !! + gltfData* data = gltfData::Data( filename ); + currentFile = filename; + if( data != nullptr ) { - common->Warning( "Did not parse %s again", filename.c_str() ); + currentAsset = data; + parser.FreeSource(); return true; } - lastFile = filename; - currentFile = filename; + + common->SetRefreshOnPrint( true ); if( filename.CheckExtension( ".glb" ) ) { if( !loadGLB( filename ) ) @@ -2104,8 +2112,7 @@ bool GLTF_Parser::Load( idStr filename ) common->FatalError( "Failed to read file" ); } - gltfData* data = gltfData::Data( filename ); - data->FileName( filename ); + data = gltfData::Data( filename , true ); byte* dataBuff = data->AddData( length ); currentAsset = data; @@ -2137,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; } @@ -2287,20 +2305,45 @@ idList& 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 @@ -2310,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 ) ); } } @@ -2318,39 +2362,39 @@ CONSOLE_COMMAND_COMPILE( LoadGLTF, "Loads an .gltf or .glb file", idCmdSystem::A // not dots allowed in [%s]! -// [filename].[%i|%s].[gltf/glb] -bool gltfManager::ExtractMeshIdentifier( idStr& filename, int& meshId, idStr& meshName ) +// [filename].[%i|%s].[gltf|glb] +bool gltfManager::ExtractIdentifier( idStr& filename, int& id, idStr& name ) { idStr extension; - idStr meshStr; + idStr targetStr; filename.ExtractFileExtension( extension ); idStr file = filename; file = file.StripFileExtension(); - file.ExtractFileExtension( meshStr ); + file.ExtractFileExtension( targetStr ); if( !extension.Length() ) { - idLib::Warning( "no gltf mesh identifier" ); + idLib::Warning( "no gltf identifier" ); return false; } - if( meshStr.Length() ) + if( targetStr.Length() ) { - filename = file.Left( file.Length() - meshStr.Length() - 1 ) + "." + extension; + filename = file.Left( file.Length() - targetStr.Length() - 1 ) + "." + extension; idParser parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS ); - parser.LoadMemory( meshStr.c_str(), meshStr.Size(), "model:GltfmeshID" ); + parser.LoadMemory( targetStr.c_str(), targetStr.Size(), "model:GltfID" ); idToken token; if( parser.ExpectAnyToken( &token ) ) { if( ( token.type == TT_NUMBER ) && ( token.subtype & TT_INTEGER ) ) { - meshId = token.GetIntValue(); + id = token.GetIntValue(); } else if( token.type == TT_NAME ) { - meshName = token; + name = token; } else { diff --git a/neo/idlib/gltfParser.h b/neo/idlib/gltfParser.h index 1d7e25a3..7eb9a048 100644 --- a/neo/idlib/gltfParser.h +++ b/neo/idlib/gltfParser.h @@ -303,6 +303,10 @@ private: class GLTF_Parser { public: + ~GLTF_Parser() + { + Shutdown(); + } GLTF_Parser(); void Shutdown(); bool Parse(); @@ -349,7 +353,5 @@ private: class gltfManager { public: - static bool ExtractMeshIdentifier( idStr& filename , int& meshId, idStr& meshName ); -}; - -extern GLTF_Parser* gltfParser; \ No newline at end of file + static bool ExtractIdentifier( idStr& filename , int& id, idStr& name ); +}; \ No newline at end of file diff --git a/neo/idlib/gltfProperties.h b/neo/idlib/gltfProperties.h index 3eff84c3..4102c123 100644 --- a/neo/idlib/gltfProperties.h +++ b/neo/idlib/gltfProperties.h @@ -134,6 +134,11 @@ public: gltfNode() : camera( -1 ), skin( -1 ), matrix( mat4_zero ), mesh( -1 ), rotation( 0.f, 0.f, 0.f, 1.f ), scale( 1.f, 1.f, 1.f ), translation( vec3_zero ), parent( nullptr ), dirty( true ) { } + //Only checks name! + bool operator == ( const gltfNode& rhs ) + { + return name == rhs.name; + } int camera; idList children; int skin; @@ -760,7 +765,7 @@ const inline idList & ##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 ) @@ -772,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() { @@ -788,27 +793,39 @@ public: static idHashIndex fileDataHash; static idList dataList; - //add data from filename - static gltfData* Data( idStr& fileName ) + //add data for filename + static gltfData* Data( idStr& fileName, bool create = false ) { - dataList.AssureSizeAlloc( dataList.Num() + 1, idListNewElement ); - dataList[dataList.Num() - 1]->FileName( fileName ); - fileDataHash.Add( fileDataHash.GenerateKey( fileName ), dataList.Num() - 1 ); - return dataList[dataList.Num() - 1]; - } - //find data; - static gltfData* Data( const char* filename ) - { - return dataList[fileDataHash.First( fileDataHash.GenerateKey( filename ) )]; + static bool intialized = false; + if( ! intialized ) + { + dataList.SetGranularity( 1 ); + intialized = true; + } + int key = fileDataHash.GenerateKey( fileName ); + int index = fileDataHash.GetFirst( key ); + + if( create && index == -1 ) + { + index = dataList.Num(); + dataList.AssureSizeAlloc( index + 1, idListNewElement ); + dataList[index]->FileName( fileName, key ); + fileDataHash.Add( key , index ); + } + + if( !create && index < 0 ) + { + return nullptr; + } + + return dataList[index]; } static const idList& DataList() { 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..) @@ -866,27 +883,10 @@ public: return nullptr; } - gltfNode* GetNode( gltfScene* scene, idStr name ) - { - assert( scene ); - assert( name[0] ); - - auto& nodeList = scene->nodes; - for( auto& nodeId : nodeList ) - { - if( nodes[nodeId]->name == name ) - { - return nodes[nodeId]; - } - } - - return nullptr; - } - gltfNode* GetNode( idStr sceneName, int id, idStr* name = nullptr ) { int sceneId = GetSceneId( sceneName ); - if( sceneId < 0 || sceneId > scenes.Num( ) ) + if( sceneId < 0 || sceneId > scenes.Num() ) { return nullptr; } @@ -913,7 +913,30 @@ public: return nullptr; } - gltfNode* GetNode( idStr sceneName, idStr name , int* id = 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 ); if( sceneId < 0 || sceneId > scenes.Num() ) @@ -927,9 +950,9 @@ public: assert( name[0] ); auto& nodeList = scene->nodes; - for( auto& nodeId : nodeList ) + for( auto nodeId : nodeList ) { - if( nodes[nodeId]->name.Icmp( name ) == 0 ) + if( caseSensitive ? nodes[nodeId]->name.Cmp( name ) : nodes[nodeId]->name.Icmp( name ) == 0 ) { if( id != nullptr ) { @@ -943,6 +966,20 @@ public: return nullptr; } + int GetNodeIndex( gltfNode* node ) + { + int index = -1; + for( auto& it : nodes ) + { + index++; + if( it == node ) + { + return index; + } + } + return -1; + } + bool HasAnimation( int nodeID ) { for( auto anim : animations ) @@ -958,18 +995,142 @@ public: return false; } - int GetSceneId( idStr sceneName ) const + gltfAnimation* GetAnimation( idStr animName, int target ) + { + for( auto* anim : animations ) + { + if( anim->name == animName ) + { + bool hasTarget = false; + for( auto* channel : anim->channels ) + { + if( channel->target.node == target ) + { + hasTarget = true; + break; + } + } + if( hasTarget ) + { + return anim; + } + } + } + return nullptr; + } + + int GetSceneId( idStr sceneName , gltfScene* result = nullptr ) const { for( int i = 0; i < scenes.Num(); i++ ) { if( scenes[i]->name == sceneName ) { + if( result != nullptr ) + { + result = scenes[i]; + } + return i; } } return -1; } + void GetAllMeshes( gltfNode* node, idList& meshIds ) + { + if( node->mesh != -1 ) + { + meshIds.Append( GetNodeIndex( node ) ); + } + + for( auto child : node->children ) + { + GetAllMeshes( nodes[child], meshIds ); + } + } + + gltfSkin* GetSkin( int boneNodeId ) + { + for( auto skin : skins ) + { + if( skin->joints.Find( boneNodeId ) ) + { + return skin; + } + } + + return nullptr; + } + + gltfSkin* GetSkin( gltfAnimation* anim ) + { + auto animTargets = GetAnimTargets( anim ); + + if( !animTargets.Num() ) + { + return nullptr; + } + + for( int nodeID : animTargets ) + { + gltfSkin* foundSkin = GetSkin( nodeID ); + if( foundSkin != nullptr ) + { + return foundSkin; + } + } + + return nullptr; + } + + idList GetAnimTargets( gltfAnimation* anim ) const + { + idList result; + for( auto channel : anim->channels ) + { + result.AddUnique( channel->target.node ); + } + return result; + } + + idList GetChannelIds( gltfAnimation* anim , gltfNode* node ) const + { + idList result; + int channelIdx = 0; + for( auto channel : anim->channels ) + { + if( channel->target.node >= 0 && nodes[channel->target.node] == node ) + { + result.Append( channelIdx ); + break; + } + channelIdx++; + } + return result; + } + + int GetAnimationIds( gltfNode* node , idList& result ) + { + + int animIdx = 0; + for( auto anim : animations ) + { + for( auto channel : anim->channels ) + { + if( channel->target.node >= 0 && nodes[channel->target.node] == node ) + { + result.AddUnique( animIdx ); + } + } + animIdx++; + } + for( int nodeId : node->children ) + { + GetAnimationIds( nodes[nodeId], result ); + } + return result.Num(); + } + idMat4 GetViewMatrix( int camId ) const { //if (cameraManager->HasOverideID(camId) ) diff --git a/neo/idlib/math/Math.h b/neo/idlib/math/Math.h index 2031ac91..50e0b7cc 100644 --- a/neo/idlib/math/Math.h +++ b/neo/idlib/math/Math.h @@ -360,6 +360,8 @@ public: static void Init(); + static float RSqrt( float x ); // reciprocal square root, returns huge number when x == 0.0 + static float InvSqrt( float x ); // inverse square root with 32 bits precision, returns huge number when x == 0.0 static float InvSqrt16( float x ); // inverse square root with 16 bits precision, returns huge number when x == 0.0 @@ -502,6 +504,21 @@ ID_INLINE byte CLAMP_BYTE( int x ) return ( ( x ) < 0 ? ( 0 ) : ( ( x ) > 255 ? 255 : ( byte )( x ) ) ); } + +ID_INLINE float idMath::RSqrt( float x ) +{ + int i; + float y, r; + + y = x * 0.5f; + i = *reinterpret_cast< int* >( &x ); + i = 0x5f3759df - ( i >> 1 ); + r = *reinterpret_cast< float* >( &i ); + r = r * ( 1.5f - r * r * y ); + return r; +} + + /* ======================== idMath::InvSqrt diff --git a/neo/idlib/math/VectorI.h b/neo/idlib/math/VectorI.h index 05f938dc..95a9595b 100644 --- a/neo/idlib/math/VectorI.h +++ b/neo/idlib/math/VectorI.h @@ -280,4 +280,258 @@ public: } }; + +//=============================================================== +// +// idVec4i - 4D vector +// +//=============================================================== + +class idVec4i +{ +public: + uint8 x; + uint8 y; + uint8 z; + uint8 w; + + idVec4i( void ); + explicit idVec4i( const uint8 x ) + { + Set( x, x, x, x ); + } + explicit idVec4i( const uint8 x, const uint8 y, const uint8 z, const uint8 w ); + + void Set( const uint8 x, const uint8 y, const uint8 z, const uint8 w ); + void Zero( void ); + + int operator[]( const int index ) const; + uint8& operator[]( const int index ); + idVec4i operator-() const; + uint8 operator*( const idVec4i& a ) const; + idVec4i operator*( const uint8 a ) const; + idVec4i operator/( const uint8 a ) const; + idVec4i operator+( const idVec4i& a ) const; + idVec4i operator-( const idVec4i& a ) const; + idVec4i& operator+=( const idVec4i& a ); + idVec4i& operator-=( const idVec4i& a ); + idVec4i& operator/=( const idVec4i& a ); + idVec4i& operator/=( const uint8 a ); + idVec4i& operator*=( const uint8 a ); + + friend idVec4i operator*( const uint8 a, const idVec4i b ); + + idVec4i Multiply( const idVec4i& a ) const; + bool Compare( const idVec4i& a ) const; // exact compare, no epsilon + + bool operator==( const idVec4i& a ) const; // exact compare, no epsilon + bool operator!=( const idVec4i& a ) const; // exact compare, no epsilon + + float Length( void ) const; + float LengthSqr( void ) const; + float Normalize( void ); // returns length + float NormalizeFast( void ); // returns length + + int GetDimension( void ) const; + + const uint8* ToIntPtr( void ) const; + uint8* ToIntPtr( void ); + const char* ToString( int precision = 2 ) const; + + void Lerp( const idVec4i& v1, const idVec4i& v2, const float l ); +}; + + +ID_INLINE idVec4i::idVec4i( void ) { } + +ID_INLINE idVec4i::idVec4i( const uint8 x, const uint8 y, const uint8 z, const uint8 w ) +{ + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE void idVec4i::Set( const uint8 x, const uint8 y, const uint8 z, const uint8 w ) +{ + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE void idVec4i::Zero( void ) +{ + x = y = z = w = 0.0f; +} + +ID_INLINE int idVec4i::operator[]( int index ) const +{ + return ( &x )[index]; +} + +ID_INLINE uint8& idVec4i::operator[]( int index ) +{ + return ( &x )[index]; +} + +ID_INLINE idVec4i idVec4i::operator-() const +{ + return idVec4i( -x, -y, -z, -w ); +} + +ID_INLINE idVec4i idVec4i::operator-( const idVec4i& a ) const +{ + return idVec4i( x - a.x, y - a.y, z - a.z, w - a.w ); +} + +ID_INLINE uint8 idVec4i::operator*( const idVec4i& a ) const +{ + return x * a.x + y * a.y + z * a.z + w * a.w; +} + +ID_INLINE idVec4i idVec4i::operator*( const uint8 a ) const +{ + return idVec4i( x * a, y * a, z * a, w * a ); +} + +ID_INLINE idVec4i idVec4i::operator/( const uint8 a ) const +{ + float inva = 1.0f / a; + return idVec4i( x * inva, y * inva, z * inva, w * inva ); +} + +ID_INLINE idVec4i operator*( const int a, const idVec4i b ) +{ + return idVec4i( b.x * a, b.y * a, b.z * a, b.w * a ); +} + +ID_INLINE idVec4i idVec4i::operator+( const idVec4i& a ) const +{ + return idVec4i( x + a.x, y + a.y, z + a.z, w + a.w ); +} + +ID_INLINE idVec4i& idVec4i::operator+=( const idVec4i& a ) +{ + x += a.x; + y += a.y; + z += a.z; + w += a.w; + + return *this; +} + +ID_INLINE idVec4i& idVec4i::operator/=( const idVec4i& a ) +{ + x /= a.x; + y /= a.y; + z /= a.z; + w /= a.w; + + return *this; +} + +ID_INLINE idVec4i& idVec4i::operator/=( const uint8 a ) +{ + float inva = 1.0f / a; + x *= inva; + y *= inva; + z *= inva; + w *= inva; + + return *this; +} + +ID_INLINE idVec4i& idVec4i::operator-=( const idVec4i& a ) +{ + x -= a.x; + y -= a.y; + z -= a.z; + w -= a.w; + + return *this; +} + +ID_INLINE idVec4i& idVec4i::operator*=( const uint8 a ) +{ + x *= a; + y *= a; + z *= a; + w *= a; + + return *this; +} + +ID_INLINE idVec4i idVec4i::Multiply( const idVec4i& a ) const +{ + return idVec4i( x * a.x, y * a.y, z * a.z, w * a.w ); +} + +ID_INLINE bool idVec4i::Compare( const idVec4i& a ) const +{ + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) && w == a.w ); +} + +ID_INLINE bool idVec4i::operator==( const idVec4i& a ) const +{ + return Compare( a ); +} + +ID_INLINE bool idVec4i::operator!=( const idVec4i& a ) const +{ + return !Compare( a ); +} + +ID_INLINE float idVec4i::Length( void ) const +{ + return ( float ) idMath::Sqrt( x * x + y * y + z * z + w * w ); +} + +ID_INLINE float idVec4i::LengthSqr( void ) const +{ + return ( x * x + y * y + z * z + w * w ); +} + +ID_INLINE float idVec4i::Normalize( void ) +{ + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z + w * w; + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + w *= invLength; + return invLength * sqrLength; +} + +ID_INLINE float idVec4i::NormalizeFast( void ) +{ + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z + w * w; + invLength = idMath::RSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + w *= invLength; + return invLength * sqrLength; +} + +ID_INLINE int idVec4i::GetDimension( void ) const +{ + return 4; +} + +ID_INLINE const uint8* idVec4i::ToIntPtr( void ) const +{ + return &x; +} + +ID_INLINE uint8* idVec4i::ToIntPtr( void ) +{ + return &x; +} + + #endif diff --git a/neo/renderer/Model_gltf.cpp b/neo/renderer/Model_gltf.cpp index 1e4b0c3f..e45df578 100644 --- a/neo/renderer/Model_gltf.cpp +++ b/neo/renderer/Model_gltf.cpp @@ -35,13 +35,22 @@ If you have questions concerning this license or the applicable additional terms #include "Model_local.h" #include "RenderCommon.h" +//HVG_TODO: this has to be moved out before release +#include "d3xp/anim/Anim.h" +#include "d3xp/Game_local.h" + idCVar gltf_ForceBspMeshTexture( "gltf_ForceBspMeshTexture", "0", CVAR_SYSTEM | CVAR_BOOL, "all world geometry has the same forced texture" ); idCVar gltf_ModelSceneName( "gltf_ModelSceneName", "models", CVAR_SYSTEM , "Scene to use when loading specific models" ); +idCVar gltf_AnimSampleRate( "gltf_AnimSampleRate", "24", CVAR_SYSTEM | CVAR_INTEGER , "The frame rate of the converted md5anim" ); + static const byte GLMB_VERSION = 100; static const unsigned int GLMB_MAGIC = ( 'M' << 24 ) | ( 'L' << 16 ) | ( 'G' << 8 ) | GLMB_VERSION; static const char* GLTF_SnapshotName = "_GLTF_Snapshot_"; +static const idAngles axisTransformAngels = idAngles( 0.0f, 0.0f, 90 ); +static const idMat4 axisTransform( axisTransformAngels.ToMat3(), vec3_origin ); +static idRenderModelGLTF* lastMeshFromFile = nullptr; bool idRenderModelStatic::ConvertGltfMeshToModelsurfaces( const gltfMesh* mesh ) { @@ -50,20 +59,8 @@ bool idRenderModelStatic::ConvertGltfMeshToModelsurfaces( const gltfMesh* mesh ) void idRenderModelGLTF::ProcessNode( gltfNode* modelNode, idMat4 trans, gltfData* data ) { - auto& meshList = data->MeshList( ); - auto& nodeList = data->NodeList( ); - - //find all animations - for( auto anim : data->AnimationList( ) ) - { - for( auto channel : anim->channels ) - { - if( channel->target.node >= 0 && nodeList[channel->target.node] == modelNode ) - { - animIds.Alloc( ) = channel->target.node; - } - } - } + auto& meshList = data->MeshList(); + auto& nodeList = data->NodeList(); gltfData::ResolveNodeMatrix( modelNode ); @@ -73,25 +70,29 @@ void idRenderModelGLTF::ProcessNode( gltfNode* modelNode, idMat4 trans, gltfData { gltfMesh* targetMesh = meshList[modelNode->mesh]; - idMat4 newTrans = mat4_identity; + idMat4 newTrans; if( !animIds.Num() ) { newTrans = curTrans; } + else + { + newTrans = mat4_identity; + } for( auto prim : targetMesh->primitives ) { //ConvertFromMeshGltf should only be used for the map, ConvertGltfMeshToModelsurfaces should be used. - auto* newMesh = MapPolygonMesh::ConvertFromMeshGltf( prim, data, newTrans ); + auto* mesh = MapPolygonMesh::ConvertFromMeshGltf( prim, data, newTrans ); modelSurface_t surf; gltfMaterial* mat = NULL; if( prim->material != -1 ) { - mat = data->MaterialList( )[prim->material]; + mat = data->MaterialList()[prim->material]; } - if( mat != NULL && !gltf_ForceBspMeshTexture.GetBool( ) ) + if( mat != NULL && !gltf_ForceBspMeshTexture.GetBool() ) { surf.shader = declManager->FindMaterial( mat->name ); } @@ -99,29 +100,30 @@ void idRenderModelGLTF::ProcessNode( gltfNode* modelNode, idMat4 trans, gltfData { surf.shader = declManager->FindMaterial( "textures/base_wall/snpanel2rust" ); } - surf.id = this->NumSurfaces( ); + surf.id = this->NumSurfaces(); - srfTriangles_t* tri = R_AllocStaticTriSurf( ); - tri->numIndexes = newMesh->GetNumPolygons( ) * 3; - tri->numVerts = newMesh->GetNumVertices( ); + srfTriangles_t* tri = R_AllocStaticTriSurf(); + tri->numIndexes = mesh->GetNumPolygons() * 3; + tri->numVerts = mesh->GetNumVertices(); R_AllocStaticTriSurfIndexes( tri, tri->numIndexes ); R_AllocStaticTriSurfVerts( tri, tri->numVerts ); int indx = 0; - for( int i = 0; i < newMesh->GetNumPolygons( ); i++ ) + for( int i = 0; i < mesh->GetNumPolygons(); i++ ) { - auto& face = newMesh->GetFace( i ); - auto& faceIdxs = face.GetIndexes( ); + auto& face = mesh->GetFace( i ); + auto& faceIdxs = face.GetIndexes(); tri->indexes[indx] = faceIdxs[0]; tri->indexes[indx + 1] = faceIdxs[1]; tri->indexes[indx + 2] = faceIdxs[2]; indx += 3; } + tri->bounds.Clear(); for( int i = 0; i < tri->numVerts; ++i ) { - tri->verts[i] = newMesh->GetDrawVerts( )[i]; + tri->verts[i] = mesh->GetDrawVerts()[i]; tri->bounds.AddPoint( tri->verts[i].xyz ); } @@ -129,6 +131,7 @@ void idRenderModelGLTF::ProcessNode( gltfNode* modelNode, idMat4 trans, gltfData surf.geometry = tri; AddSurface( surf ); + delete mesh; } } @@ -138,13 +141,6 @@ void idRenderModelGLTF::ProcessNode( gltfNode* modelNode, idMat4 trans, gltfData } } -void idRenderModelGLTF::MakeMD5Mesh( ) -{ - - meshes.SetGranularity( 1 ); - //meshes.SetNum( num ); -} - //constructs a renderModel from a gltfScene node found in the "models" scene of the given gltfFile. // override with gltf_ModelSceneName // warning : nodeName cannot have dots! @@ -152,61 +148,46 @@ void idRenderModelGLTF::MakeMD5Mesh( ) //If no nodeName/nodeId is given, all primitives active in default scene will be added as surfaces. void idRenderModelGLTF::InitFromFile( const char* fileName ) { - name = fileName; hasAnimations = false; fileExclusive = false; root = nullptr; + rootID = -1; int meshID = -1; - idStr meshName; + name = fileName; + currentSkin = nullptr; + + PurgeModel(); + + //FIXME FIXME FIXME + maxJointVertDist = 10; idStr gltfFileName = idStr( fileName ); model_state = DM_STATIC; - gltfManager::ExtractMeshIdentifier( gltfFileName, meshID, meshName ); - - if( gltfParser->currentFile.Length() ) - { - if( gltfParser->currentAsset && gltfParser->currentFile != gltfFileName ) - { - common->FatalError( "multiple GLTF file loading not supported" ); - } - } - else - { - gltfParser->Load( gltfFileName ); - } + gltfManager::ExtractIdentifier( gltfFileName, meshID, meshName ); + GLTF_Parser gltf; + gltf.Load( gltfFileName ); timeStamp = fileSystem->GetTimestamp( gltfFileName ); - data = gltfParser->currentAsset; + data = gltf.currentAsset; bounds.Clear(); int sceneId = data->DefaultScene(); - assert( sceneId >= 0 ); + auto scene = data->SceneList()[sceneId]; + assert( scene ); + + auto nodes = data->NodeList(); + assert( nodes.Num() ); + + //determine root node if( !meshName[0] ) { + root = new gltfNode(); + root->name = scene->name; + root->children.Append( scene->nodes ); rootID = -1; - - //this needs to be fixed to correctly support multiple meshes. - // atm this will only correctlty with static models. - // we could do gltfMeshes a la md5 - // or re-use this class - auto& nodeList = data->NodeList(); - for( auto& nodeID : data->SceneList()[sceneId]->nodes ) - { - gltfNode* modelNode = nodeList[nodeID]; - if( modelNode ) - { - ProcessNode( modelNode, mat4_identity, data ); - - if( rootID == -1 ) - { - root = modelNode; - rootID = nodeID; - } - } - } fileExclusive = true; } else @@ -215,28 +196,70 @@ void idRenderModelGLTF::InitFromFile( const char* fileName ) if( modelNode ) { root = modelNode; - ProcessNode( modelNode, mat4_identity, data ); } - } - if( surfaces.Num( ) <= 0 ) + if( !root ) { - common->Warning( "Couldn't load model: '%s'", name.c_str( ) ); - MakeDefaultModel( ); + common->Warning( "Couldn't find model: '%s'", name.c_str() ); + MakeDefaultModel(); return; } - model_state = animIds.Num() ? DM_CONTINUOUS : DM_STATIC; + + //get allmeshes in hierachy, starting at root. + MeshNodeIds.Clear(); + data->GetAllMeshes( root, MeshNodeIds ); + + //find all animations and bones + bones.Clear(); + int totalAnims = 0; + for( int meshID : MeshNodeIds ) + { + gltfNode* tmpNode = nodes[meshID]; + int animCount = data->GetAnimationIds( tmpNode , animIds ); + + //check if this model has a skeleton/bones + //if not but it has an anim, create a bone from the target mesh-node as origin. + if( tmpNode->skin >= 0 ) + { + currentSkin = data->SkinList()[tmpNode->skin]; + assert( currentSkin ); + if( currentSkin->joints.Num() ) + { + bones.Append( currentSkin->joints ); + animCount = data->GetAnimationIds( nodes[bones[0]] , animIds ); + } + } + else + { + animCount = data->GetAnimationIds( tmpNode , animIds ); + bones.Append( meshID ); + } + + totalAnims += animCount; + } + + hasAnimations = totalAnims > 0; + model_state = hasAnimations ? DM_CACHED : DM_STATIC; + + ProcessNode( root, mat4_identity, data ); + + if( surfaces.Num() <= 0 ) + { + common->Warning( "Couldn't load model: '%s'", name.c_str() ); + MakeDefaultModel(); + return; + } + // derive mikktspace tangents from normals FinishSurfaces( true ); - // it is now available for use - purged = false; + LoadModel(); + UpdateMd5Joints(); - //skin - //gltfNode * modelNode = data->GetNode(data->SceneList()[data->GetSceneId("models")],targetMesh); - //__debugbreak(); + // it is now available for use + lastMeshFromFile = this; } bool idRenderModelGLTF::LoadBinaryModel( idFile* file, const ID_TIME_T sourceTimeStamp ) @@ -244,9 +267,7 @@ bool idRenderModelGLTF::LoadBinaryModel( idFile* file, const ID_TIME_T sourceTim hasAnimations = false; fileExclusive = false; // not written. root = nullptr; - prevTrans = mat4_identity; - // we should still load the scene information ? if( !idRenderModelStatic::LoadBinaryModel( file, sourceTimeStamp ) ) { return false; @@ -263,41 +284,746 @@ bool idRenderModelGLTF::LoadBinaryModel( idFile* file, const ID_TIME_T sourceTim file->ReadBig( model_state ); file->ReadBig( rootID ); - // TODO get rid of loading the original .glb here -#if 1 idStr dataFilename; file->ReadString( dataFilename ); - name = dataFilename; - - if( gltfParser->currentFile.Length( ) ) - { - if( gltfParser->currentAsset && gltfParser->currentFile != dataFilename ) - { - common->FatalError( "multiple GLTF file loading not supported" ); - } - } - else - { - gltfParser->Load( dataFilename ); - } - - data = gltfParser->currentAsset; - root = data->GetNode( gltf_ModelSceneName.GetString(), rootID ); - assert( root ); -#endif int animCnt; file->ReadBig( animCnt ); if( animCnt > 0 ) { animIds.Resize( animCnt, 1 ); - file->ReadBigArray( animIds.Ptr( ), animCnt ); + file->ReadBigArray( animIds.Ptr(), animCnt ); animIds.SetNum( animCnt ); } hasAnimations = animCnt > 0; + + int tempNum; + file->ReadBig( tempNum ); + md5joints.SetNum( tempNum ); + for( int i = 0; i < md5joints.Num(); i++ ) + { + file->ReadString( md5joints[i].name ); + int offset; + file->ReadBig( offset ); + if( offset >= 0 ) + { + md5joints[i].parent = md5joints.Ptr() + offset; + } + else + { + md5joints[i].parent = NULL; + } + } + + int boneCnt; + file->ReadBig( boneCnt ); + if( boneCnt > 0 ) + { + bones.Resize( boneCnt, 1 ); + file->ReadBigArray( bones.Ptr(), boneCnt ); + bones.SetNum( boneCnt ); + } + else + { + if( hasAnimations && !bones.Num() ) + { + bones.Clear(); + bones.Append( rootID ); + } + } + + file->ReadBig( tempNum ); + defaultPose.SetNum( tempNum ); + for( int i = 0; i < defaultPose.Num(); i++ ) + { + file->ReadBig( defaultPose[i].q.x ); + file->ReadBig( defaultPose[i].q.y ); + file->ReadBig( defaultPose[i].q.z ); + file->ReadBig( defaultPose[i].q.w ); + file->ReadVec3( defaultPose[i].t ); + } + + file->ReadBig( tempNum ); + invertedDefaultPose.SetNum( tempNum ); + for( int i = 0; i < invertedDefaultPose.Num(); i++ ) + { + file->ReadBigArray( invertedDefaultPose[i].ToFloatPtr(), JOINTMAT_TYPESIZE ); + } + SIMD_INIT_LAST_JOINT( invertedDefaultPose.Ptr(), md5joints.Num() ); + + model_state = hasAnimations ? DM_CONTINUOUS : DM_STATIC; + + lastMeshFromFile = this; return true; } +void idRenderModelGLTF::UpdateMd5Joints() +{ + md5joints.Clear(); + md5joints.Resize( bones.Num() ); + md5joints.SetNum( bones.Num() ); + idStr ovrBoneName; + auto& nodeList = data->NodeList(); + + for( int i = 0 ; i < bones.Num(); i++ ) + { + gltfNode* node = nodeList[bones[i]]; + + //check for TRS anim and its artificial root bone + if( i == 0 && node->name != "origin" ) + { + if( node->mesh == -1 ) + { + common->Warning( "First bone of model is not named \"origin\", name forced!" ); + } + + ovrBoneName = node->name; + md5joints[i].name = "origin"; + } + else + { + md5joints[i].name = node->name; + } + } + + auto findMd5Joint = [&]( idStr & name ) -> auto + { + for( auto& joint : md5joints ) + { + if( joint.name == name ) + { + return &joint; + } + } + assert( 0 ); + static idMD5Joint staticJoint; + return &staticJoint; + }; + + for( int i = 0 ; i < bones.Num(); i++ ) + { + gltfNode* node = nodeList[bones[i]]; + if( i && node->parent && node->parent != root ) + { + if( node->parent->name == ovrBoneName ) + { + md5joints[i].parent = findMd5Joint( idStr( "origin" ) ); + } + else + { + md5joints[i].parent = findMd5Joint( node->parent->name ); + } + } + } + + if( bones.Num() == 1 ) + { + //patch bone indices + for( auto& surf : surfaces ) + { + for( int i = 0; i < surf.geometry->numVerts; i++ ) + { + idDrawVert& base = surf.geometry->verts[i]; + base.SetColor( PackColor( ( vec4_zero ) ) ); + base.SetColor2( PackColor( ( vec4_one / 4 ) ) ); + } + } + } +} + +void idRenderModelGLTF::DrawJoints( const struct renderEntity_s* ent, const viewDef_t* view ) +{ + int i; + int num; + idVec3 pos; + const idJointMat* joint; + const idMD5Joint* md5Joint; + int parentNum; + + num = ent->numJoints; + joint = ent->joints; + md5Joint = md5joints.Ptr(); + for( i = 0; i < num; i++, joint++, md5Joint++ ) + { + pos = ent->origin + joint->ToVec3() * ent->axis; + if( md5Joint->parent ) + { + parentNum = md5Joint->parent - md5joints.Ptr(); + common->RW()->DebugLine( colorWhite, ent->origin + ent->joints[parentNum].ToVec3() * ent->axis, pos ); + } + + common->RW()->DebugLine( colorRed, pos, pos + joint->ToMat3()[0] * 2.0f * ent->axis ); + common->RW()->DebugLine( colorGreen, pos, pos + joint->ToMat3()[1] * 2.0f * ent->axis ); + common->RW()->DebugLine( colorBlue, pos, pos + joint->ToMat3()[2] * 2.0f * ent->axis ); + } + + idBounds bounds; + + bounds.FromTransformedBounds( ent->bounds, vec3_zero, ent->axis ); + common->RW()->DebugBounds( colorMagenta, bounds, ent->origin ); + + if( ( r_jointNameScale.GetFloat() != 0.0f ) && ( bounds.Expand( 128.0f ).ContainsPoint( view->renderView.vieworg - ent->origin ) ) ) + { + idVec3 offset( 0, 0, r_jointNameOffset.GetFloat() ); + float scale; + + scale = r_jointNameScale.GetFloat(); + joint = ent->joints; + num = ent->numJoints; + for( i = 0; i < num; i++, joint++ ) + { + pos = ent->origin + joint->ToVec3() * ent->axis; + common->RW()->DrawText( md5joints[i].name, pos + offset, scale, colorWhite, view->renderView.viewaxis, 1 ); + } + } +} + +bool gatherBoneInfo( gltfData* data, gltfAnimation* gltfAnim, const idList& nodes , idList& bones, idList& jointInfo ) +{ + //Gather Bones; + bool boneLess = false; + int targetNode = lastMeshFromFile->rootID; + + auto skin = data->GetSkin( gltfAnim ); + auto targets = data->GetAnimTargets( gltfAnim ); + + if( skin == nullptr ) + { + boneLess = true; + } + + //we cant be sure channels are sorted by bone? + if( !boneLess ) + { + if( skin == nullptr ) + { + skin = data->GetSkin( targetNode ); + } + assert( skin ); + bones.Append( skin->joints ); + } + else + { + bones.Append( targetNode ); + } + + //create jointInfo + jointInfo.SetGranularity( 1 ); + jointInfo.SetNum( bones.Num() ); + for( auto& joint : jointInfo ) + { + joint.animBits = 0; + joint.firstComponent = -1; + } + + return boneLess; +} + +idList GetPose( idList& bones, idJointMat* poseMat ) +{ + idList ret; + ret.AssureSize( bones.Num() ); + + for( int i = 0; i < bones.Num(); i++ ) + { + auto* node = &bones[i]; + + idMat4 trans = mat4_identity; + gltfData::ResolveNodeMatrix( node, &trans, &bones[0] ); + + if( node->parent == nullptr ) + { + node->matrix *= axisTransform; + } + + idJointQuat& pose = ret[i]; + pose.q = ( trans.ToMat3().Transpose().ToQuat() ); + pose.t = idVec3( trans[0][3], trans[1][3], trans[2][3] ); + pose.w = pose.q.CalcW(); + } + + for( int i = 0; i < bones.Num(); i++ ) + { + const gltfNode* joint = &bones[i]; + idJointQuat* pose = &ret[i]; + poseMat[i].SetRotation( pose->q.ToMat3() ); + poseMat[i].SetTranslation( pose->t ); + if( joint->parent ) + { + int parentNum = bones.FindIndex( *joint->parent ); + pose->q = ( poseMat[i].ToMat3() * poseMat[parentNum].ToMat3().Transpose() ).ToQuat(); + pose->t = ( poseMat[i].ToVec3() - poseMat[parentNum].ToVec3() ) * poseMat[parentNum].ToMat3().Transpose(); + } + } + + return ret; +} + +int copyBones( gltfData* data, const idList& bones, idList& out ) +{ + out.Clear(); + + auto nodes = data->NodeList(); + for( auto jointId : bones ) + { + auto* newNode = &out.Alloc(); + *newNode = *nodes[jointId]; + } + + //patch parents + for( auto& bone : out ) + { + bool found = false; + for( int i = 0; i < out.Num(); i++ ) + { + if( bone.parent && bone.parent->name == out[i].name ) + { + bone.parent = &out[i]; + found = true; + break; + } + } + + if( !found ) + { + bone.parent = nullptr; + } + } + //patch childs! + // -> skipping because not used. + return out.Num(); +} + +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 ); + gltf.Load( gltfFileName ); + + gltfData* data = gltf.currentAsset; + auto& accessors = data->AccessorList(); + auto& nodes = data->NodeList(); + + 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; + } + + idList bones; + idList jointInfo; + + + bool boneLess = gatherBoneInfo( data, gltfAnim, nodes, bones, jointInfo ); + + idList> animBones; + idList componentFrames; + idList baseFrame; + + idList bounds; + int numFrames = 0; + int frameRate = 0; + int numJoints = bones.Num(); + int numAnimatedComponents = 0; + + gameLocal.Printf( "Generating MD5Anim for GLTF anim %s from scene %s\n", name.c_str(), gltf_ModelSceneName.GetString() ); + + gltfNode* root = nullptr; + 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]; + auto* output = accessors[sampler->output]; + auto* target = nodes[channel->target.node]; + jointAnimInfo_t* newJoint = &( jointInfo[ bones.FindIndex( channel->target.node ) ] ); + + idList& timeStamps = data->GetAccessorView( input ); + root = target; + int frames = timeStamps.Num(); + + if( numFrames != 0 && numFrames != frames ) + { + common->Warning( "Not all channel animations have the same amount of frames" ); + } + + if( frames > numFrames ) + { + numFrames = frames; + } + + int parentIndex = data->GetNodeIndex( target->parent ); + newJoint->nameIndex = animationLib.JointIndex( boneLess ? "origin" : target->name ); + newJoint->parentNum = bones.FindIndex( parentIndex ); + + if( newJoint->firstComponent == -1 ) + { + newJoint->firstComponent = numAnimatedComponents; + } + + switch( channel->target.TRS ) + { + default: + break; + case gltfAnimation_Channel_Target::none: + break; + case gltfAnimation_Channel_Target::rotation: + newJoint->animBits |= ANIM_QX | ANIM_QY | ANIM_QZ; + numAnimatedComponents += 3; + break; + case gltfAnimation_Channel_Target::translation: + newJoint->animBits |= ANIM_TX | ANIM_TY | ANIM_TZ; + numAnimatedComponents += 3; + break; + case gltfAnimation_Channel_Target::scale: // this is not supported by engine, but it should be for gltf + break; + } + + channelCount++; + } + animBones.AssureSize( numFrames ); + animBones.SetNum( numFrames ); + for( int i = 0; i < numFrames; i++ ) + { + int totalCopied = copyBones( data, bones, animBones[i] ); + assert( totalCopied ); + } + + gameLocal.Printf( "Total bones %i \n", bones.Num() ); + + //we can calculate frame rate by: + // max_timestamp_value / totalFrames + // but keeping it fixed for now. + frameRate = gltf_AnimSampleRate.GetInteger(); + INT animLength = ( ( numFrames - 1 ) * 1000 + frameRate - 1 ) / frameRate; + + for( int i = 0; i < jointInfo.Num(); i++ ) + { + jointAnimInfo_t& j = jointInfo[i]; + idStr jointName = animationLib.JointName( j.nameIndex ); + if( i == 0 && ( jointName != "origin" ) ) + { + gameLocal.Warning( "Renaming bone 0 from %s to %s \n", jointName.c_str(), "origin" ); + jointName = "origin"; + } + } + + baseFrame.SetGranularity( 1 ); + baseFrame.SetNum( bones.Num() ); + + gltfSkin* skin = data->GetSkin( gltfAnim );; + gltfAccessor* acc = nullptr; + if( skin != nullptr ) + { + acc = data->AccessorList()[skin->inverseBindMatrices]; + } + else + { + skin = new gltfSkin; + 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( 1 ); + acc->matView->AssureSize( 1, trans.Inverse().Transpose() ); + } + + idJointMat* poseMat = ( idJointMat* ) _alloca16( bones.Num() * sizeof( poseMat[0] ) ); + baseFrame = GetPose( animBones[0], poseMat ); + + componentFrames.SetGranularity( 1 ); + componentFrames.SetNum( ( ( numAnimatedComponents * numFrames ) ) + 1 ); + int componentFrameIndex = 0; + for( int i = 0; i < numFrames; i++ ) + { + for( auto channel : gltfAnim->channels ) + { + if( !bones.Find( channel->target.node ) ) + { + continue; + } + + auto sampler = gltfAnim->samplers[channel->sampler]; + + auto* input = accessors[sampler->input]; + auto* output = accessors[sampler->output]; + auto* target = nodes[channel->target.node]; + idList& timeStamps = data->GetAccessorView( input ); + int boneIndex = bones.FindIndex( channel->target.node ); + + switch( channel->target.TRS ) + { + default: + break; + case gltfAnimation_Channel_Target::none: + break; + case gltfAnimation_Channel_Target::rotation: + { + idList& values = data->GetAccessorView( output ); + if( values.Num() > i ) + { + animBones[i][boneIndex].rotation = *values[i]; + } + } + break; + case gltfAnimation_Channel_Target::translation: + { + idList& values = data->GetAccessorView( output ); + if( values.Num() > i ) + { + animBones[i][boneIndex].translation = *values[i]; + } + } + break; + case gltfAnimation_Channel_Target::scale: + idList& values = data->GetAccessorView( output ); + if( values.Num() > i ) + { + animBones[i][boneIndex].scale = *values[i] ; + } + break; + } + } + for( int b = 0; b < bones.Num(); b++ ) + { + auto* node = &animBones[i][b]; + jointAnimInfo_t* joint = &( jointInfo[b] ); + + idQuat q = node->rotation; + idVec3 t = node->translation; + + if( joint->animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) + { + if( node->parent == nullptr ) + { + t = axisTransform * t; + } + + componentFrames[componentFrameIndex++] = t.x; + componentFrames[componentFrameIndex++] = t.y; + componentFrames[componentFrameIndex++] = t.z; + } + if( joint->animBits & ( ANIM_QX | ANIM_QY | ANIM_QZ ) ) + { + + if( node->parent == nullptr ) + { + q = axisTransformAngels.ToQuat() * animBones[i][b].rotation; + if( animBones[i].Num() == 1 ) + { + q = -animBones[i][b].rotation; + } + } + else + { + q = -animBones[i][b].rotation; + } + + componentFrames[componentFrameIndex++] = q.x; + componentFrames[componentFrameIndex++] = q.y; + componentFrames[componentFrameIndex++] = q.z; + } + } + } + + assert( componentFrames.Num() == ( componentFrameIndex + 1 ) ); + + bounds.SetGranularity( 1 ); + bounds.AssureSize( numFrames ); + bounds.SetNum( numFrames ); + + //do software skinning to determine bounds. + idJointMat* currJoints = ( idJointMat* ) _alloca16( bones.Num() * sizeof( poseMat[0] ) ); + for( int i = 0; i < numFrames; i++ ) + { + bounds[i].Clear(); + + for( int b = 0; b < animBones[i].Num(); b++ ) + { + if( animBones[i][b].parent == nullptr ) + { + animBones[i][b].translation = axisTransform * animBones[i][b].translation; + } + } + + idList joints; + GetPose( animBones[i], currJoints ); + for( int b = 0; b < animBones[i].Num(); b++ ) + { + idJointMat mat = poseMat[b]; + mat.Invert(); + idJointMat::Multiply( joints.Alloc(), currJoints[b], mat ); + } + + // an mesh entry _should_ always be before an anim entry! + // use those verts as base. + if( lastMeshFromFile != nullptr ) + { + for( modelSurface_t& surf : lastMeshFromFile->surfaces ) + { + idDrawVert* verts = surf.geometry->verts; + int numVerts = surf.geometry->numVerts; + + for( int v = 0; v < numVerts; v++ ) + { + const idDrawVert& base = verts[v]; + + const idJointMat& j0 = joints[base.color[0]]; + const idJointMat& j1 = joints[base.color[1]]; + const idJointMat& j2 = joints[base.color[2]]; + const idJointMat& j3 = joints[base.color[3]]; + + const float w0 = base.color2[0] * ( 1.0f / 255.0f ); + const float w1 = base.color2[1] * ( 1.0f / 255.0f ); + const float w2 = base.color2[2] * ( 1.0f / 255.0f ); + const float w3 = base.color2[3] * ( 1.0f / 255.0f ); + + idJointMat accum; + idJointMat::Mul( accum, j0, w0 ); + idJointMat::Mad( accum, j1, w1 ); + idJointMat::Mad( accum, j2, w2 ); + idJointMat::Mad( accum, j3, w3 ); + + idVec3 pos = accum * idVec4( base.xyz.x, base.xyz.y, base.xyz.z, 1.0f ); + bounds[i].AddPoint( pos ); + } + } + } + } + + ////////////////////////////////////////////////////////////////////////// + /// Start writing //////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + idFile_Memory* file = new idFile_Memory(); + file->WriteBig( B_ANIM_MD5_MAGIC ); + file->WriteBig( sourceTimeStamp ); + + file->WriteBig( numFrames ); + file->WriteBig( frameRate ); + file->WriteBig( animLength ); + file->WriteBig( numJoints ); + file->WriteBig( numAnimatedComponents ); + + + + + file->WriteBig( bounds.Num() ); + for( int i = 0; i < bounds.Num(); i++ ) + { + idBounds& b = bounds[i]; + file->WriteBig( b[0] ); + file->WriteBig( b[1] ); + } + + //namestr list + file->WriteBig( jointInfo.Num() ); + for( int i = 0; i < jointInfo.Num(); i++ ) + { + jointAnimInfo_t& j = jointInfo[i]; + idStr jointName = animationLib.JointName( j.nameIndex ); + + file->WriteString( jointName ); + file->WriteBig( j.parentNum ); + file->WriteBig( j.animBits ); + file->WriteBig( j.firstComponent ); + } + + //base frame + file->WriteBig( baseFrame.Num() ); + for( int i = 0; i < baseFrame.Num(); i++ ) + { + idJointQuat& j = baseFrame[i]; + file->WriteBig( j.q.x ); + file->WriteBig( j.q.y ); + file->WriteBig( j.q.z ); + file->WriteBig( j.q.w ); + file->WriteVec3( j.t ); + } + + //per joint timestamp values, T R + file->WriteBig( componentFrames.Num() - 1 ); + for( int i = 0; i < componentFrames.Num(); i++ ) + { + file->WriteFloat( componentFrames[i] ); + } + + float* componentPtr = componentFrames.Ptr(); + idVec3 totaldelta; + // get total move delta + if( !numAnimatedComponents ) + { + totaldelta.Zero(); + } + else + { + componentPtr = &componentFrames[jointInfo[0].firstComponent]; + if( jointInfo[0].animBits & ANIM_TX ) + { + for( int i = 0; i < numFrames; i++ ) + { + componentPtr[numAnimatedComponents * i] -= baseFrame[0].t.x; + } + totaldelta.x = componentPtr[numAnimatedComponents * ( numFrames - 1 )]; + componentPtr++; + } + else + { + totaldelta.x = 0.0f; + } + if( jointInfo[0].animBits & ANIM_TY ) + { + for( int i = 0; i < numFrames; i++ ) + { + componentPtr[numAnimatedComponents * i] -= baseFrame[0].t.y; + } + totaldelta.y = componentPtr[numAnimatedComponents * ( numFrames - 1 )]; + componentPtr++; + } + else + { + totaldelta.y = 0.0f; + } + if( jointInfo[0].animBits & ANIM_TZ ) + { + for( int i = 0; i < numFrames; i++ ) + { + componentPtr[numAnimatedComponents * i] -= baseFrame[0].t.z; + } + totaldelta.z = componentPtr[numAnimatedComponents * ( numFrames - 1 )]; + } + else + { + totaldelta.z = 0.0f; + } + } + + file->WriteVec3( totaldelta ); + file->Seek( 0, FS_SEEK_SET ); + file->TakeDataOwnership(); + return file; +} + void idRenderModelGLTF::WriteBinaryModel( idFile* file, ID_TIME_T* _timeStamp /*= NULL */ ) const { idRenderModelStatic::WriteBinaryModel( file ); @@ -310,143 +1036,209 @@ 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( ) ) + file->WriteBig( animIds.Num() ); + if( animIds.Num() ) { - file->WriteBigArray( animIds.Ptr(), animIds.Size() ); + file->WriteBigArray( animIds.Ptr(), animIds.Num() ); } - //check if this model has a skeleton - //if ( root->skin >= 0 ) - //{ - // gltfSkin *skin = data->SkinList( )[root->skin]; - // auto &nodeList = data->NodeList( ); - // file->WriteBig( skin->joints.Num( ) ); - // for ( int i = 0; i < skin->joints.Num( ); i++ ) { + file->WriteBig( md5joints.Num() ); + for( int i = 0; i < md5joints.Num(); i++ ) + { + file->WriteString( md5joints[i].name ); + int offset = -1; + if( md5joints[i].parent != NULL ) + { + offset = md5joints[i].parent - md5joints.Ptr(); + } + file->WriteBig( offset ); + } - // gltfNode &target = *nodeList[skin->joints[i]]; - // file->WriteString( target.name ); - // int offset = -1; - // if ( target.parent != NULL ) { - // offset = target.parent - skin->joints.Ptr( ); - // } - // file->WriteBig( offset ); - // } - //} - // file->WriteBig( defaultPose.Num( ) ); - // for ( int i = 0; i < defaultPose.Num( ); i++ ) { - // file->WriteBig( defaultPose[i].q.x ); - // file->WriteBig( defaultPose[i].q.y ); - // file->WriteBig( defaultPose[i].q.z ); - // file->WriteBig( defaultPose[i].q.w ); - // file->WriteVec3( defaultPose[i].t ); - // } + file->WriteBig( bones.Num() ); + if( bones.Num() ) + { + file->WriteBigArray( bones.Ptr(), bones.Num() ); + } - // file->WriteBig( invertedDefaultPose.Num( ) ); - // for ( int i = 0; i < invertedDefaultPose.Num( ); i++ ) { - // file->WriteBigArray( invertedDefaultPose[i].ToFloatPtr( ), JOINTMAT_TYPESIZE ); - // } - //} + file->WriteBig( defaultPose.Num() ); + for( int i = 0; i < defaultPose.Num(); i++ ) + { + file->WriteBig( defaultPose[i].q.x ); + file->WriteBig( defaultPose[i].q.y ); + file->WriteBig( defaultPose[i].q.z ); + file->WriteBig( defaultPose[i].q.w ); + file->WriteVec3( defaultPose[i].t ); + } - - - //file->WriteBig( meshes.Num( ) ); - //for ( int i = 0; i < meshes.Num( ); i++ ) { - - // if ( meshes[i].shader != NULL && meshes[i].shader->GetName( ) != NULL ) { - // file->WriteString( meshes[i].shader->GetName( ) ); - // } else { - // file->WriteString( "" ); - // } - - // file->WriteBig( meshes[i].numVerts ); - // file->WriteBig( meshes[i].numTris ); - - // file->WriteBig( meshes[i].numMeshJoints ); - // file->WriteBigArray( meshes[i].meshJoints, meshes[i].numMeshJoints ); - // file->WriteBig( meshes[i].maxJointVertDist ); - - // deformInfo_t &deform = *meshes[i].deformInfo; - - // file->WriteBig( deform.numSourceVerts ); - // file->WriteBig( deform.numOutputVerts ); - // file->WriteBig( deform.numIndexes ); - // file->WriteBig( deform.numMirroredVerts ); - // file->WriteBig( deform.numDupVerts ); - // file->WriteBig( deform.numSilEdges ); - - // if ( deform.numOutputVerts > 0 ) { - // file->WriteBigArray( deform.verts, deform.numOutputVerts ); - // } - - // if ( deform.numIndexes > 0 ) { - // file->WriteBigArray( deform.indexes, deform.numIndexes ); - // file->WriteBigArray( deform.silIndexes, deform.numIndexes ); - // } - - // if ( deform.numMirroredVerts > 0 ) { - // file->WriteBigArray( deform.mirroredVerts, deform.numMirroredVerts ); - // } - - // if ( deform.numDupVerts > 0 ) { - // file->WriteBigArray( deform.dupVerts, deform.numDupVerts * 2 ); - // } - - // if ( deform.numSilEdges > 0 ) { - // for ( int j = 0; j < deform.numSilEdges; j++ ) { - // file->WriteBig( deform.silEdges[j].p1 ); - // file->WriteBig( deform.silEdges[j].p2 ); - // file->WriteBig( deform.silEdges[j].v1 ); - // file->WriteBig( deform.silEdges[j].v2 ); - // } - // } - - // file->WriteBig( meshes[i].surfaceNum ); - //} + file->WriteBig( invertedDefaultPose.Num() ); + for( int i = 0; i < invertedDefaultPose.Num(); i++ ) + { + file->WriteBigArray( invertedDefaultPose[i].ToFloatPtr(), JOINTMAT_TYPESIZE ); + } } void idRenderModelGLTF::PurgeModel() { - common->Warning( "idRenderModelGLTF::PurgeModel is not implemented." ); + purged = true; + md5joints.Clear(); + defaultPose.Clear(); + invertedDefaultPose.Clear(); + + animIds.Clear(); + bones.Clear(); + MeshNodeIds.Clear(); + + //if no root id was set, it is a generated one. + if( rootID == -1 && root ) + { + delete root; + } } void idRenderModelGLTF::LoadModel() { - common->Warning( "The method or operation is not implemented." ); + int num; + auto& accessors = data->AccessorList(); + auto& nodes = data->NodeList(); + gltfNode* meshRoot = root; + if( !fileExclusive ) + { + meshRoot = data->GetNode( gltf_ModelSceneName.GetString(), meshName ); + } + + gltfSkin* skin = nullptr; + gltfAccessor* acc = nullptr; + if( currentSkin != nullptr ) + { + skin = currentSkin; + acc = data->AccessorList()[skin->inverseBindMatrices]; + } + else + { + skin = new gltfSkin; + skin->joints.AssureSize( 1, rootID ); + idMat4 trans = mat4_identity; + data->ResolveNodeMatrix( meshRoot, &trans ); + acc = new gltfAccessor(); + acc->matView = new idList( 1 ); + acc->matView->AssureSize( 1, trans.Inverse() ); + } + + if( skin && skin->skeleton == -1 ) + { + skin->skeleton = skin->joints[0]; + } + + num = bones.Num(); + md5joints.SetGranularity( 1 ); + md5joints.SetNum( num ); + defaultPose.SetGranularity( 1 ); + defaultPose.SetNum( num ); + + for( int i = 0; i < bones.Num(); i++ ) + { + gltfNode* node = nodes[bones[i]]; + + //check for TRS anim and its artficial root bone + if( bones.Num() == 0 && node->mesh != -1 ) + { + md5joints[i].name = "origin"; + } + else + { + md5joints[i].name = node->name; + } + } + + auto findMd5Joint = [&]( idStr & name ) -> auto + { + for( auto& joint : md5joints ) + { + if( joint.name == name ) + { + return &joint; + } + } + assert( 0 ); + static idMD5Joint staticJoint; + return &staticJoint; + }; + + + for( int i = 0; i < bones.Num(); i++ ) + { + auto* node = nodes[bones[i]]; + + if( i && node->parent && node->parent != root ) + { + md5joints[i].parent = findMd5Joint( node->parent->name ); + } + } + + idJointMat* poseMat = ( idJointMat* ) _alloca16( bones.Num() * sizeof( poseMat[0] ) ); + idList animBones; + int totalCopied = copyBones( data, bones, animBones ); + defaultPose = GetPose( animBones, poseMat ); + + if( !currentSkin ) + { + delete skin; + delete acc; + } + + //----------------------------------------- + // create the inverse of the base pose joints to support tech6 style deformation + // of base pose vertexes, normals, and tangents. + // + // vertex * joints * inverseJoints == vertex when joints is the base pose + // When the joints are in another pose, it gives the animated vertex position + //----------------------------------------- + invertedDefaultPose.SetNum( SIMD_ROUND_JOINTS( md5joints.Num() ) ); + for( int i = 0; i < md5joints.Num(); i++ ) + { + invertedDefaultPose[i] = poseMat[i]; + invertedDefaultPose[i].Invert(); + } + SIMD_INIT_LAST_JOINT( invertedDefaultPose.Ptr(), md5joints.Num() ); + + //auto deformInfo = R_BuildDeformInfo( texCoords.Num(), basePose, tris.Num(), tris.Ptr(), + // shader->UseUnsmoothedTangents() ); + + model_state = hasAnimations ? DM_CACHED : DM_STATIC; + + // set the timestamp for reloadmodels + fileSystem->ReadFile( name, NULL, &timeStamp ); + + purged = false; + + common->UpdateLevelLoadPacifier(); } void idRenderModelGLTF::TouchData() { - common->Warning( "The method or operation is not implemented." ); + for( int i = 0; i < surfaces.Num(); i++ ) + { + declManager->FindMaterial( surfaces[i].shader->GetName() ); + } } -/* -void idRenderModelGLTF::CreateBuffers() -{ - common->Warning( "The method or operation is not implemented." ); -} -*/ - void idRenderModelGLTF::Print() const { idRenderModelStatic::Print(); - // TODO } void idRenderModelGLTF::List() const { idRenderModelStatic::List(); - // TODO } int idRenderModelGLTF::Memory() const { return idRenderModelStatic::Memory(); - // TODO } @@ -455,32 +1247,255 @@ dynamicModel_t idRenderModelGLTF::IsDynamicModel() const return model_state; } -void TransformVertsAndTangents( idDrawVert* targetVerts, const int numVerts, idMat4 trans ) +idList TransformVertsAndTangents_GLTF( idDrawVert* targetVerts, const int numVerts, const idDrawVert* baseVerts, const idJointMat* joints ) { + idList jointIds; for( int i = 0; i < numVerts; i++ ) { + const idDrawVert& base = baseVerts[i]; + + const idJointMat& j0 = joints[base.color[0]]; + const idJointMat& j1 = joints[base.color[1]]; + const idJointMat& j2 = joints[base.color[2]]; + const idJointMat& j3 = joints[base.color[3]]; + + for( int j = 0; j < 4; j++ ) + { + jointIds.AddUnique( base.color[0] ); + } + + const float w0 = base.color2[0] * ( 1.0f / 255.0f ); + const float w1 = base.color2[1] * ( 1.0f / 255.0f ); + const float w2 = base.color2[2] * ( 1.0f / 255.0f ); + const float w3 = base.color2[3] * ( 1.0f / 255.0f ); + + idJointMat accum; + idJointMat::Mul( accum, j0, w0 ); + idJointMat::Mad( accum, j1, w1 ); + idJointMat::Mad( accum, j2, w2 ); + idJointMat::Mad( accum, j3, w3 ); + + targetVerts[i].xyz = accum * idVec4( base.xyz.x, base.xyz.y, base.xyz.z, 1.0f ); + targetVerts[i].SetNormal( accum * base.GetNormal() ); + targetVerts[i].SetTangent( accum * base.GetTangent() ); + targetVerts[i].tangent[3] = base.tangent[3]; - targetVerts[i].xyz *= trans;// * idVec3( base.xyz.x, base.xyz.y, base.xyz.z); - targetVerts[i].SetNormal( trans.ToMat3() * targetVerts[i].GetNormal( ) ); - targetVerts[i].SetTangent( trans.ToMat3() * targetVerts[i].GetTangent( ) ); } + return jointIds; } -void idRenderModelGLTF::UpdateSurface( const struct renderEntity_s* ent, idMat4 trans, modelSurface_t* surf ) +void idRenderModelGLTF::UpdateSurface( const struct renderEntity_s* ent, const idJointMat* entJoints, const idJointMat* entJointsInverted, modelSurface_t* surf, const modelSurface_t& sourceSurf ) { +#if defined(USE_INTRINSICS_SSE) + static const __m128 vector_float_posInfinity = { idMath::INFINITUM, idMath::INFINITUM, idMath::INFINITUM, idMath::INFINITUM }; + static const __m128 vector_float_negInfinity = { -idMath::INFINITUM, -idMath::INFINITUM, -idMath::INFINITUM, -idMath::INFINITUM }; +#endif + //add skinning if( surf->geometry != NULL ) { R_FreeStaticTriSurfVertexCaches( surf->geometry ); } else { - surf->geometry = R_AllocStaticTriSurf( ); + surf->geometry = R_AllocStaticTriSurf(); } srfTriangles_t* tri = surf->geometry; - TransformVertsAndTangents( tri->verts, tri->numVerts, trans ); + int numVerts = sourceSurf.geometry->numVerts; + idDrawVert* verts = sourceSurf.geometry->verts; + tri->referencedIndexes = true; + tri->numIndexes = sourceSurf.geometry->numIndexes; + tri->indexes = sourceSurf.geometry->indexes; + tri->silIndexes = sourceSurf.geometry->silIndexes; + tri->numMirroredVerts = sourceSurf.geometry->numMirroredVerts; + tri->mirroredVerts = sourceSurf.geometry->mirroredVerts; + tri->numDupVerts = sourceSurf.geometry->numDupVerts; + tri->dupVerts = sourceSurf.geometry->dupVerts; + tri->numSilEdges = sourceSurf.geometry->numSilEdges; + tri->silEdges = sourceSurf.geometry->silEdges; + + tri->indexCache = sourceSurf.geometry->indexCache; + + tri->numVerts = numVerts; + + idList jointIds; + + if( r_useGPUSkinning.GetBool() && glConfig.gpuSkinningAvailable ) + { + if( tri->verts != NULL && tri->verts != verts ) + { + R_FreeStaticTriSurfVerts( tri ); + } + tri->verts = verts; + tri->ambientCache = sourceSurf.geometry->ambientCache; + tri->shadowCache = sourceSurf.geometry->shadowCache; + tri->referencedVerts = true; + } + else + { + if( tri->verts == NULL || tri->verts == verts ) + { + tri->verts = NULL; + R_AllocStaticTriSurfVerts( tri, numVerts ); + assert( tri->verts != NULL ); // quiet analyze warning + memcpy( tri->verts, verts, numVerts * sizeof( verts[0] ) ); // copy over the texture coordinates + tri->referencedVerts = false; + } + + jointIds = TransformVertsAndTangents_GLTF( tri->verts, numVerts, verts, entJointsInverted ); + } + tri->tangentsCalculated = true; + +#if defined(USE_INTRINSICS_SSE) + __m128 minX = vector_float_posInfinity; + __m128 minY = vector_float_posInfinity; + __m128 minZ = vector_float_posInfinity; + __m128 maxX = vector_float_negInfinity; + __m128 maxY = vector_float_negInfinity; + __m128 maxZ = vector_float_negInfinity; + for( int i = 0; i < jointIds.Num(); i++ ) + { + const idJointMat& joint = entJoints[i]; + __m128 x = _mm_load_ps( joint.ToFloatPtr() + 0 * 4 ); + __m128 y = _mm_load_ps( joint.ToFloatPtr() + 1 * 4 ); + __m128 z = _mm_load_ps( joint.ToFloatPtr() + 2 * 4 ); + minX = _mm_min_ps( minX, x ); + minY = _mm_min_ps( minY, y ); + minZ = _mm_min_ps( minZ, z ); + maxX = _mm_max_ps( maxX, x ); + maxY = _mm_max_ps( maxY, y ); + maxZ = _mm_max_ps( maxZ, z ); + } + __m128 expand = _mm_splat_ps( _mm_load_ss( &maxJointVertDist ), 0 ); + minX = _mm_sub_ps( minX, expand ); + minY = _mm_sub_ps( minY, expand ); + minZ = _mm_sub_ps( minZ, expand ); + maxX = _mm_add_ps( maxX, expand ); + maxY = _mm_add_ps( maxY, expand ); + maxZ = _mm_add_ps( maxZ, expand ); + _mm_store_ss( tri->bounds.ToFloatPtr() + 0, _mm_splat_ps( minX, 3 ) ); + _mm_store_ss( tri->bounds.ToFloatPtr() + 1, _mm_splat_ps( minY, 3 ) ); + _mm_store_ss( tri->bounds.ToFloatPtr() + 2, _mm_splat_ps( minZ, 3 ) ); + _mm_store_ss( tri->bounds.ToFloatPtr() + 3, _mm_splat_ps( maxX, 3 ) ); + _mm_store_ss( tri->bounds.ToFloatPtr() + 4, _mm_splat_ps( maxY, 3 ) ); + _mm_store_ss( tri->bounds.ToFloatPtr() + 5, _mm_splat_ps( maxZ, 3 ) ); + +#else + bounds.Clear(); + for( int i = 0; i < jointIds.Num(); i++ ) + { + const idJointMat& joint = entJoints[i]; + bounds.AddPoint( joint.GetTranslation() ); + } + bounds.ExpandSelf( maxJointVertDist ); + +#endif + +} + +/* +==================== +TransformJoints +==================== +*/ +static void TransformJointsFast( idJointMat* __restrict outJoints, const int numJoints, const idJointMat* __restrict inJoints1, const idJointMat* __restrict inJoints2 ) +{ + + float* outFloats = outJoints->ToFloatPtr(); + const float* inFloats1 = inJoints1->ToFloatPtr(); + const float* inFloats2 = inJoints2->ToFloatPtr(); + + assert_16_byte_aligned( outFloats ); + assert_16_byte_aligned( inFloats1 ); + assert_16_byte_aligned( inFloats2 ); + +#if defined(USE_INTRINSICS_SSE) + + const __m128 mask_keep_last = __m128c( _mm_set_epi32( 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000 ) ); + + for( int i = 0; i < numJoints; i += 2, inFloats1 += 2 * 12, inFloats2 += 2 * 12, outFloats += 2 * 12 ) + { + __m128 m1a0 = _mm_load_ps( inFloats1 + 0 * 12 + 0 ); + __m128 m1b0 = _mm_load_ps( inFloats1 + 0 * 12 + 4 ); + __m128 m1c0 = _mm_load_ps( inFloats1 + 0 * 12 + 8 ); + __m128 m1a1 = _mm_load_ps( inFloats1 + 1 * 12 + 0 ); + __m128 m1b1 = _mm_load_ps( inFloats1 + 1 * 12 + 4 ); + __m128 m1c1 = _mm_load_ps( inFloats1 + 1 * 12 + 8 ); + + __m128 m2a0 = _mm_load_ps( inFloats2 + 0 * 12 + 0 ); + __m128 m2b0 = _mm_load_ps( inFloats2 + 0 * 12 + 4 ); + __m128 m2c0 = _mm_load_ps( inFloats2 + 0 * 12 + 8 ); + __m128 m2a1 = _mm_load_ps( inFloats2 + 1 * 12 + 0 ); + __m128 m2b1 = _mm_load_ps( inFloats2 + 1 * 12 + 4 ); + __m128 m2c1 = _mm_load_ps( inFloats2 + 1 * 12 + 8 ); + + __m128 tj0 = _mm_and_ps( m1a0, mask_keep_last ); + __m128 tk0 = _mm_and_ps( m1b0, mask_keep_last ); + __m128 tl0 = _mm_and_ps( m1c0, mask_keep_last ); + __m128 tj1 = _mm_and_ps( m1a1, mask_keep_last ); + __m128 tk1 = _mm_and_ps( m1b1, mask_keep_last ); + __m128 tl1 = _mm_and_ps( m1c1, mask_keep_last ); + + __m128 ta0 = _mm_splat_ps( m1a0, 0 ); + __m128 td0 = _mm_splat_ps( m1b0, 0 ); + __m128 tg0 = _mm_splat_ps( m1c0, 0 ); + __m128 ta1 = _mm_splat_ps( m1a1, 0 ); + __m128 td1 = _mm_splat_ps( m1b1, 0 ); + __m128 tg1 = _mm_splat_ps( m1c1, 0 ); + + __m128 ra0 = _mm_add_ps( tj0, _mm_mul_ps( ta0, m2a0 ) ); + __m128 rd0 = _mm_add_ps( tk0, _mm_mul_ps( td0, m2a0 ) ); + __m128 rg0 = _mm_add_ps( tl0, _mm_mul_ps( tg0, m2a0 ) ); + __m128 ra1 = _mm_add_ps( tj1, _mm_mul_ps( ta1, m2a1 ) ); + __m128 rd1 = _mm_add_ps( tk1, _mm_mul_ps( td1, m2a1 ) ); + __m128 rg1 = _mm_add_ps( tl1, _mm_mul_ps( tg1, m2a1 ) ); + + __m128 tb0 = _mm_splat_ps( m1a0, 1 ); + __m128 te0 = _mm_splat_ps( m1b0, 1 ); + __m128 th0 = _mm_splat_ps( m1c0, 1 ); + __m128 tb1 = _mm_splat_ps( m1a1, 1 ); + __m128 te1 = _mm_splat_ps( m1b1, 1 ); + __m128 th1 = _mm_splat_ps( m1c1, 1 ); + + __m128 rb0 = _mm_add_ps( ra0, _mm_mul_ps( tb0, m2b0 ) ); + __m128 re0 = _mm_add_ps( rd0, _mm_mul_ps( te0, m2b0 ) ); + __m128 rh0 = _mm_add_ps( rg0, _mm_mul_ps( th0, m2b0 ) ); + __m128 rb1 = _mm_add_ps( ra1, _mm_mul_ps( tb1, m2b1 ) ); + __m128 re1 = _mm_add_ps( rd1, _mm_mul_ps( te1, m2b1 ) ); + __m128 rh1 = _mm_add_ps( rg1, _mm_mul_ps( th1, m2b1 ) ); + + __m128 tc0 = _mm_splat_ps( m1a0, 2 ); + __m128 tf0 = _mm_splat_ps( m1b0, 2 ); + __m128 ti0 = _mm_splat_ps( m1c0, 2 ); + __m128 tf1 = _mm_splat_ps( m1b1, 2 ); + __m128 ti1 = _mm_splat_ps( m1c1, 2 ); + __m128 tc1 = _mm_splat_ps( m1a1, 2 ); + + __m128 rc0 = _mm_add_ps( rb0, _mm_mul_ps( tc0, m2c0 ) ); + __m128 rf0 = _mm_add_ps( re0, _mm_mul_ps( tf0, m2c0 ) ); + __m128 ri0 = _mm_add_ps( rh0, _mm_mul_ps( ti0, m2c0 ) ); + __m128 rc1 = _mm_add_ps( rb1, _mm_mul_ps( tc1, m2c1 ) ); + __m128 rf1 = _mm_add_ps( re1, _mm_mul_ps( tf1, m2c1 ) ); + __m128 ri1 = _mm_add_ps( rh1, _mm_mul_ps( ti1, m2c1 ) ); + + _mm_store_ps( outFloats + 0 * 12 + 0, rc0 ); + _mm_store_ps( outFloats + 0 * 12 + 4, rf0 ); + _mm_store_ps( outFloats + 0 * 12 + 8, ri0 ); + _mm_store_ps( outFloats + 1 * 12 + 0, rc1 ); + _mm_store_ps( outFloats + 1 * 12 + 4, rf1 ); + _mm_store_ps( outFloats + 1 * 12 + 8, ri1 ); + } + +#else + + for( int i = 0; i < numJoints; i++ ) + { + idJointMat::Multiply( outJoints[i], inJoints1[i], inJoints2[i] ); + } + +#endif } idRenderModel* idRenderModelGLTF::InstantiateDynamicModel( const struct renderEntity_s* ent, const viewDef_t* view, idRenderModel* cachedModel ) @@ -494,104 +1509,210 @@ 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(); } + if( !ent->joints ) + { + common->Printf( "idRenderModelGLTF::InstantiateDynamicModel: NULL joints on renderEntity for '%s'\n", Name() ); + delete cachedModel; + return NULL; + } + else if( ent->numJoints != md5joints.Num() ) + { + common->Printf( "idRenderModelGLTF::InstantiateDynamicModel: renderEntity has different number of joints than model for '%s'\n", Name() ); + delete cachedModel; + return NULL; + } + idRenderModelStatic* staticModel; if( cachedModel != NULL ) { assert( dynamic_cast< idRenderModelStatic* >( cachedModel ) != NULL ); - assert( idStr::Icmp( cachedModel->Name( ), GLTF_SnapshotName ) == 0 ); + assert( idStr::Icmp( cachedModel->Name(), GLTF_SnapshotName ) == 0 ); staticModel = static_cast< idRenderModelStatic* >( cachedModel ); } else { staticModel = new( TAG_MODEL ) idRenderModelStatic; staticModel->InitEmpty( GLTF_SnapshotName ); + staticModel->jointsInverted = NULL;; } - idStr prevName = name; - name = GLTF_SnapshotName; - *staticModel = *this; - name = prevName; + staticModel->bounds.Clear(); - idMat3 rotation = idAngles( 0.0f, 0.0f, 90.0f ).ToMat3( ); - idMat4 axisTransform( rotation, vec3_origin ); - idMat4 trans = prevTrans; - bool wasDirty = root->dirty; - - gltfData::ResolveNodeMatrix( root, &trans ); - - if( wasDirty ) + if( r_showSkel.GetInteger() ) { - trans *= axisTransform; + if( ( view != NULL ) && ( !r_skipSuppress.GetBool() || !ent->suppressSurfaceInViewID || ( ent->suppressSurfaceInViewID != view->renderView.viewID ) ) ) + { + // only draw the skeleton + DrawJoints( ent, view ); + } + + if( r_showSkel.GetInteger() > 1 ) + { + // turn off the model when showing the skeleton + staticModel->InitEmpty( GLTF_SnapshotName ); + return staticModel; + } } + // update the GPU joints array + const int numInvertedJoints = SIMD_ROUND_JOINTS( md5joints.Num() ); + if( staticModel->jointsInverted == NULL ) + { + staticModel->numInvertedJoints = numInvertedJoints; + staticModel->jointsInverted = ( idJointMat* ) Mem_ClearedAlloc( numInvertedJoints * sizeof( idJointMat ), TAG_JOINTMAT ); + staticModel->jointsInvertedBuffer = 0; + } + else + { + assert( staticModel->numInvertedJoints == numInvertedJoints ); + } + + TransformJointsFast( staticModel->jointsInverted, md5joints.Num(), ent->joints, invertedDefaultPose.Ptr() ); + + if( !staticModel->surfaces.Num() ) + { + for( int i = 0 ; i < surfaces.Num(); i++ ) + { + modelSurface_t* newSurf = &staticModel->surfaces.Alloc(); + newSurf->geometry = NULL; + newSurf->shader = surfaces[i].shader; + } + } + + int surfIdx = 0; for( modelSurface_t& surf : staticModel->surfaces ) { - UpdateSurface( ent, trans, &surf ); + + const idMaterial* shader = surf.shader; + shader = R_RemapShaderBySkin( shader, ent->customSkin, ent->customShader ); + + if( !shader || ( !shader->IsDrawn() && !shader->SurfaceCastsShadow() ) ) + { + staticModel->DeleteSurfaceWithId( surfIdx++ ); + continue; + } + + UpdateSurface( ent, ent->joints, staticModel->jointsInverted, &surf, surfaces[surfIdx++] ); + assert( surf.geometry != NULL ); + surf.geometry->staticModelWithJoints = staticModel; + staticModel->bounds.AddBounds( surf.geometry->bounds ); } - prevTrans = root->matrix.Inverse(); - - //staticModel->bounds *= trans.ToMat3( ); - //bounds.Translate( idVec3( trans[0][3], trans[1][3], trans[2][3] ) ); - return staticModel; } int idRenderModelGLTF::NumJoints() const { - if( !root ) - { - common->FatalError( "Trying to determine Joint count without a model node loaded" ); - } - if( root->skin >= 0 ) - { - gltfSkin* skin = data->SkinList( )[root->skin]; - return skin->joints.Num(); - } - - return 0; + return bones.Num(); } const idMD5Joint* idRenderModelGLTF::GetJoints() const { - common->Warning( "The method or operation is not implemented." ); - return nullptr; + idMD5Joint* result = nullptr; + if( md5joints.Num() ) + { + return &md5joints[0]; + } + else + { + common->Warning( "GltfModel has no Joints" ); + return nullptr; + } } jointHandle_t idRenderModelGLTF::GetJointHandle( const char* name ) const { - common->Warning( "The method or operation is not implemented." ); - return jointHandle_t(); + const idMD5Joint* joint = md5joints.Ptr(); + for( int i = 0; i < md5joints.Num(); i++, joint++ ) + { + if( idStr::Icmp( joint->name.c_str(), name ) == 0 ) + { + return ( jointHandle_t ) i; + } + } + + return INVALID_JOINT; } const char* idRenderModelGLTF::GetJointName( jointHandle_t handle ) const { - common->Warning( "The method or operation is not implemented." ); - return ""; + if( ( handle < 0 ) || ( handle >= md5joints.Num() ) ) + { + return ""; + } + + return md5joints[handle].name; } const idJointQuat* idRenderModelGLTF::GetDefaultPose() const { - common->Warning( "The method or operation is not implemented." ); - return nullptr; + return defaultPose.Ptr(); } int idRenderModelGLTF::NearestJoint( int surfaceNum, int a, int b, int c ) const { - common->Warning( "The method or operation is not implemented." ); - return -1; + for( modelSurface_t& surf : surfaces ) + { + idDrawVert* verts = surf.geometry->verts; + int numVerts = surf.geometry->numVerts; + + for( int v = 0; v < numVerts; v++ ) + { + // duplicated vertices might not have weights + int vertNum; + if( a >= 0 && a < numVerts ) + { + vertNum = a; + } + else if( b >= 0 && b < numVerts ) + { + vertNum = b; + } + else if( c >= 0 && c < numVerts ) + { + vertNum = c; + } + else + { + // all vertices are duplicates which shouldn't happen + return 0; + } + + const idDrawVert& vert = verts[vertNum]; + + int bestWeight = 0; + int bestJoint = 0; + for( int i = 0; i < 4; i++ ) + { + if( vert.color2[i] > bestWeight ) + { + bestWeight = vert.color2[i]; + bestJoint = vert.color[i]; + } + } + + return bestJoint; + } + } + + common->Warning( "Couldn't find NearestJoint for : '%s'", name.c_str() ); + return 0; } idBounds idRenderModelGLTF::Bounds( const struct renderEntity_s* ent ) const { - return bounds; -} - -idGltfMesh::idGltfMesh( gltfMesh* _mesh, gltfData* _data ) : mesh( _mesh ), data( _data ) -{ + if( ent == NULL ) + { + // this is the bounds for the reference pose + return bounds; + } + return ent->bounds; } diff --git a/neo/renderer/Model_gltf.h b/neo/renderer/Model_gltf.h index 5a4f6cdc..83b79ccc 100644 --- a/neo/renderer/Model_gltf.h +++ b/neo/renderer/Model_gltf.h @@ -29,19 +29,6 @@ If you have questions concerning this license or the applicable additional terms #pragma once #include "Model_local.h" - - -class idGltfMesh -{ -public: - idGltfMesh( ) {}; - idGltfMesh( gltfMesh* _mesh, gltfData* _data ); -private: - gltfMesh* mesh; - gltfData* data; - idMD5Mesh md5Mesh; -}; - class idRenderModelGLTF : public idRenderModelStatic { public: @@ -67,20 +54,28 @@ public: { return true; } - - void MakeMD5Mesh() ; + 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, idMat4 trans, modelSurface_t* surf ); - int rootID; - + void UpdateSurface( const struct renderEntity_s* ent, const idJointMat* entJoints, const idJointMat* entJointsInverted, modelSurface_t* surf, const modelSurface_t& sourceSurf ); + void UpdateMd5Joints(); gltfData* data; gltfNode* root; bool fileExclusive; bool hasAnimations; - idList animIds; - idList meshes; + + float maxJointVertDist; // maximum distance a vertex is separated from a joint + idList animIds; + idList bones; + idList MeshNodeIds; dynamicModel_t model_state; - idMat4 prevTrans; - idList SkeletonNodes; + idStr meshName; + + idList md5joints; + idList defaultPose; + idList invertedDefaultPose; + gltfSkin* currentSkin; +private: + void DrawJoints( const struct renderEntity_s* ent, const viewDef_t* view ); }; \ No newline at end of file diff --git a/neo/tools/compilers/aas/AASBuild.cpp b/neo/tools/compilers/aas/AASBuild.cpp index d294145b..0d4b8fdd 100644 --- a/neo/tools/compilers/aas/AASBuild.cpp +++ b/neo/tools/compilers/aas/AASBuild.cpp @@ -3,6 +3,8 @@ Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. +Copyright (C) 2022 Harrie van Ginneken +Copyright (C) 2022 Robert Beckebans This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). @@ -417,6 +419,115 @@ idBrushList idAASBuild::AddBrushesForMapBrush( const idMapBrush* mapBrush, const return brushList; } +/* +============ +idAASBuild::AddBrushesForMapPolygonMesh +============ +*/ + +idBrushList idAASBuild::AddBrushesForMapPolygonMesh( const MapPolygonMesh* mapMesh, const idVec3& origin, const idMat3& axis, int entityNum, int primitiveNum, idBrushList brushList ) +{ + int contents = 0; + int validBrushes = 0; + + idFixedWinding w; + idPlane plane; + idVec3 d1, d2; + idBrush* brush; + const idMaterial* mat; + + //per map polygon + for( int p = 0 ; p < mapMesh->GetNumPolygons(); p++ ) + { + const MapPolygon& face = mapMesh->GetFace( p ); + + mat = declManager->FindMaterial( face.GetMaterial() ); + contents = ContentsForAAS( mat->GetContentFlags() ); + + if( !contents ) + { + return brushList; + } + + const idList& verts = mapMesh->GetDrawVerts(); + const idList& indices = face.GetIndexes(); + + idVec3 triNormal; + int v1 = 0; + int v2 = 1; + int v3 = 2; + + //create brush with 2 triangles + // 1 frontface from the mappoly verts + // 1 backface, offset in direction off frontface normal at unit distance + + //Front face + d1 = verts[indices[1]].xyz - verts[indices[0]].xyz; + d2 = verts[indices[2]].xyz - verts[indices[0]].xyz; + plane.SetNormal( d1.Cross( d2 ) ); + if( plane.Normalize() != 0.0f ) + { + plane.FitThroughPoint( verts[indices[2]].xyz ); + + w.Clear(); + w += verts[indices[0]].xyz; + w += verts[indices[1]].xyz; + w += verts[indices[2]].xyz; + + brush = new idBrush(); + brush->SetContents( contents ); + if( brush->FromWinding( w, plane ) ) + { + brush->SetEntityNum( entityNum ); + brush->SetPrimitiveNum( primitiveNum ); + brush->SetFlag( BFL_PATCH ); + brush->Transform( origin, axis ); + brushList.AddToTail( brush ); + validBrushes++; + } + else + { + delete brush; + } + } + + //Back face + triNormal = plane.Normal(); + plane.SetNormal( d2.Cross( d1 ) ); + if( plane.Normalize() != 0.0f ) + { + plane.FitThroughPoint( verts[indices[0]].xyz ); + + w.Clear(); + w += verts[indices[2]].xyz + triNormal; + w += verts[indices[1]].xyz + triNormal; + w += verts[indices[0]].xyz + triNormal; + + brush = new idBrush(); + brush->SetContents( contents ); + if( brush->FromWinding( w, plane ) ) + { + brush->SetEntityNum( entityNum ); + brush->SetPrimitiveNum( primitiveNum ); + brush->SetFlag( BFL_PATCH ); + brush->Transform( origin, axis ); + brushList.AddToTail( brush ); + validBrushes++; + } + else + { + delete brush; + } + } + } + + if( !validBrushes ) + { + common->Warning( "map polygon primitive %d on entity %d is completely degenerate", primitiveNum, entityNum ); + } + + return brushList; +} /* ============ @@ -613,6 +724,11 @@ idBrushList idAASBuild::AddBrushesForMapEntity( const idMapEntity* mapEnt, int e } continue; } + //HVG: Map polygon mesh support + if( mapPrim->GetType() == idMapPrimitive::TYPE_MESH ) + { + brushList = AddBrushesForMapPolygonMesh( static_cast< MapPolygonMesh* >( mapPrim ), origin, axis, entityNum, i, brushList ); + } } return brushList; diff --git a/neo/tools/compilers/aas/AASBuild_local.h b/neo/tools/compilers/aas/AASBuild_local.h index dd1b42e3..d0907d63 100644 --- a/neo/tools/compilers/aas/AASBuild_local.h +++ b/neo/tools/compilers/aas/AASBuild_local.h @@ -3,6 +3,8 @@ Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. +Copyright (C) 2022 Harrie van Ginneken +Copyright (C) 2022 Robert Beckebans This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). @@ -104,6 +106,7 @@ private: // map loading int ContentsForAAS( int contents ); idBrushList AddBrushesForMapBrush( const idMapBrush* mapBrush, const idVec3& origin, const idMat3& axis, int entityNum, int primitiveNum, idBrushList brushList ); idBrushList AddBrushesForMapPatch( const idMapPatch* mapPatch, const idVec3& origin, const idMat3& axis, int entityNum, int primitiveNum, idBrushList brushList ); + idBrushList AddBrushesForMapPolygonMesh( const MapPolygonMesh* mapMesh, const idVec3& origin, const idMat3& axis, int entityNum, int primitiveNum, idBrushList brushList ); idBrushList AddBrushesForMapEntity( const idMapEntity* mapEnt, int entityNum, idBrushList brushList ); idBrushList AddBrushesForMapFile( const idMapFile* mapFile, idBrushList brushList ); bool CheckForEntities( const idMapFile* mapFile, idStrList& entityClassNames ) const;