diff --git a/neo/d3xp/Camera.cpp b/neo/d3xp/Camera.cpp index 6e9d2435..d858700d 100644 --- a/neo/d3xp/Camera.cpp +++ b/neo/d3xp/Camera.cpp @@ -3,6 +3,7 @@ Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. +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"). @@ -30,7 +31,10 @@ If you have questions concerning this license or the applicable additional terms #pragma hdrstop #include "Game_local.h" +#include "gltfParser.h" + +static const idMat4 blenderToDoomTransform( idAngles( 0.0f, 0.0f, 90 ).ToMat3(), vec3_origin ); /* =============================================================================== @@ -363,6 +367,10 @@ void idCameraAnim::LoadAnim() int i; idStr filename; const char* key; + idStr gltfFileName; + int animID = -1; + idStr animName; + bool isGLTF = false; key = spawnArgs.GetString( "anim" ); if( !key ) @@ -373,86 +381,102 @@ void idCameraAnim::LoadAnim() filename = spawnArgs.GetString( va( "anim %s", key ) ); if( !filename.Length() ) { - // RB: TrenchBroom interop use anim. instead so we can build this up using the FGD files - filename = spawnArgs.GetString( va( "anim.%s", key ) ); - if( !filename.Length() ) + gltfFileName = key; + gltfManager::ExtractIdentifier( gltfFileName, animID, animName ); + if( animName.Length() ) { - gameLocal.Error( "Missing 'anim.%s' key on '%s'", key, name.c_str() ); + isGLTF = true; } - // RB end - } - - filename.SetFileExtension( MD5_CAMERA_EXT ); - if( !parser.LoadFile( filename ) ) - { - gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() ); - } - - cameraCuts.Clear(); - cameraCuts.SetGranularity( 1 ); - camera.Clear(); - camera.SetGranularity( 1 ); - - parser.ExpectTokenString( MD5_VERSION_STRING ); - version = parser.ParseInt(); - if( version != MD5_VERSION ) - { - parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); - } - - // skip the commandline - parser.ExpectTokenString( "commandline" ); - parser.ReadToken( &token ); - - // parse num frames - parser.ExpectTokenString( "numFrames" ); - numFrames = parser.ParseInt(); - if( numFrames <= 0 ) - { - parser.Error( "Invalid number of frames: %d", numFrames ); - } - - // parse framerate - parser.ExpectTokenString( "frameRate" ); - frameRate = parser.ParseInt(); - if( frameRate <= 0 ) - { - parser.Error( "Invalid framerate: %d", frameRate ); - } - - // parse num cuts - parser.ExpectTokenString( "numCuts" ); - numCuts = parser.ParseInt(); - if( ( numCuts < 0 ) || ( numCuts > numFrames ) ) - { - parser.Error( "Invalid number of camera cuts: %d", numCuts ); - } - - // parse the camera cuts - parser.ExpectTokenString( "cuts" ); - parser.ExpectTokenString( "{" ); - cameraCuts.SetNum( numCuts ); - for( i = 0; i < numCuts; i++ ) - { - cameraCuts[ i ] = parser.ParseInt(); - if( ( cameraCuts[ i ] < 1 ) || ( cameraCuts[ i ] >= numFrames ) ) + else { - parser.Error( "Invalid camera cut" ); + // RB: TrenchBroom interop use anim. instead so we can build this up using the FGD files + filename = spawnArgs.GetString( va( "anim.%s", key ) ); + if( !filename.Length() ) + { + gameLocal.Error( "Missing 'anim.%s' key on '%s'", key, name.c_str() ); + } + // RB end } } - parser.ExpectTokenString( "}" ); - // parse the camera frames - parser.ExpectTokenString( "camera" ); - parser.ExpectTokenString( "{" ); - camera.SetNum( numFrames ); - for( i = 0; i < numFrames; i++ ) + if( isGLTF ) { - parser.Parse1DMatrix( 3, camera[ i ].t.ToFloatPtr() ); - parser.Parse1DMatrix( 3, camera[ i ].q.ToFloatPtr() ); - camera[ i ].fov = parser.ParseFloat(); + gltfLoadAnim( gltfFileName, animName ); + } + else + { + filename.SetFileExtension( MD5_CAMERA_EXT ); + if( !parser.LoadFile( filename ) ) + { + gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() ); + } + + cameraCuts.Clear(); + cameraCuts.SetGranularity( 1 ); + camera.Clear(); + camera.SetGranularity( 1 ); + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + if( version != MD5_VERSION ) + { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if( numFrames <= 0 ) + { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse framerate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if( frameRate <= 0 ) + { + parser.Error( "Invalid framerate: %d", frameRate ); + } + + // parse num cuts + parser.ExpectTokenString( "numCuts" ); + numCuts = parser.ParseInt(); + if( ( numCuts < 0 ) || ( numCuts > numFrames ) ) + { + parser.Error( "Invalid number of camera cuts: %d", numCuts ); + } + + // parse the camera cuts + parser.ExpectTokenString( "cuts" ); + parser.ExpectTokenString( "{" ); + cameraCuts.SetNum( numCuts ); + for( i = 0; i < numCuts; i++ ) + { + cameraCuts[i] = parser.ParseInt(); + if( ( cameraCuts[i] < 1 ) || ( cameraCuts[i] >= numFrames ) ) + { + parser.Error( "Invalid camera cut" ); + } + } + parser.ExpectTokenString( "}" ); + + // parse the camera frames + parser.ExpectTokenString( "camera" ); + parser.ExpectTokenString( "{" ); + camera.SetNum( numFrames ); + for( i = 0; i < numFrames; i++ ) + { + parser.Parse1DMatrix( 3, camera[i].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, camera[i].q.ToFloatPtr() ); + camera[i].fov = parser.ParseFloat(); + } + parser.ExpectTokenString( "}" ); } - parser.ExpectTokenString( "}" ); } /* @@ -692,6 +716,123 @@ void idCameraAnim::Event_Activate( idEntity* _activator ) } } +void idCameraAnim::gltfLoadAnim( idStr gltfFileName, idStr animName ) +{ + //we dont wat to load the gltb all the time. write custom binary format ! + GLTF_Parser gltf; + if( gltf.Load( gltfFileName ) ) + { + ID_TIME_T timeStamp = fileSystem->GetTimestamp( gltfFileName ); + gltfData* data = gltf.currentAsset; + auto& accessors = data->AccessorList(); + auto& nodes = data->NodeList(); + + gltfNode* cameraNode = data->GetNode( name ); + assert( cameraNode ); + + gltfAnimation* anim = data->GetAnimation( animName, data->GetNodeIndex( cameraNode ) ); + assert( anim ); + + cameraCuts.Clear(); + cameraCuts.SetGranularity( 1 ); + camera.Clear(); + camera.SetGranularity( 1 ); + frameRate = 24; + for( auto channel : anim->channels ) + { + auto* sampler = anim->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 frames = timeStamps.Num(); + if( !camera.Num() ) + { + cameraFrame_t t; + t.fov = 90; + t.q = mat3_identity.ToCQuat(); + t.t = vec3_origin; + for( int i = 0; i < frames; i++ ) + { + camera.Alloc() = t; + } + } + //This has to be replaced for correct interpolation between frames + for( int i = 0; i < frames; i++ ) + { + cameraFrame_t& cameraFrame = camera[i]; + + cameraFrame.fov = 90.0f; + 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 ) + { + + idQuat q = ( *values[i] ); + q = idAngles( 90.0f, 0.0, -90.0f ).ToQuat() + * q.Inverse() + * blenderToDoomTransform.ToMat3().ToQuat(); + cameraFrame.q = q.ToCQuat(); + + //This has to be replaced with correct interpolation between frames. + if( ( ( i + 1 ) == frames ) && ( frames < camera.Num() ) ) + { + while( i++ < camera.Num() - 1 ) + { + camera[i].q = cameraFrame.q; + } + } + } + + } + break; + case gltfAnimation_Channel_Target::translation: + { + idList& values = data->GetAccessorView( output ); + if( values.Num() > i ) + { + idVec3 val = *values[i]; + cameraFrame.t = blenderToDoomTransform * val; + + //This has to be replaced with correct interpolation between frames. + if( ( ( i + 1 ) == frames ) && ( frames < camera.Num() ) ) + { + while( i++ < camera.Num() - 1 ) + { + camera[i].t = cameraFrame.t; + } + } + } + } + break; + case gltfAnimation_Channel_Target::scale: + idList& values = data->GetAccessorView( output ); + if( values.Num() > i ) + { + gameLocal.Printf( "^5Frame: ^7%i ignored scale on /%s \n\n\n", i, anim->name.c_str() ); + } + break; + } + } + } + } + else + { + gameLocal.Error( "Missing 'anim' key on '%s'", name.c_str() ); + } + + + +} + /* =============== idCameraAnim::Event_Start diff --git a/neo/d3xp/Camera.h b/neo/d3xp/Camera.h index fc3e32ff..600da6ec 100644 --- a/neo/d3xp/Camera.h +++ b/neo/d3xp/Camera.h @@ -131,6 +131,8 @@ private: void Event_Stop(); void Event_SetCallback(); void Event_Activate( idEntity* activator ); + + void gltfLoadAnim( idStr gltfFileName, idStr animName ); }; #endif /* !__GAME_CAMERA_H__ */ diff --git a/neo/idlib/MapFile_gltf.cpp b/neo/idlib/MapFile_gltf.cpp index 182d3f00..c4812030 100644 --- a/neo/idlib/MapFile_gltf.cpp +++ b/neo/idlib/MapFile_gltf.cpp @@ -459,11 +459,11 @@ void ResolveLight( gltfData* data, idMapEntity* newEntity, gltfNode* node ) { idMat4 entityToWorldTransform = mat4_identity; gltfData::ResolveNodeMatrix( node, &entityToWorldTransform ); - idMat4 worldToEntityTransform = entityToWorldTransform.Inverse(); - float fov = tan( light->spot.outerConeAngle ) / 2 ; - idMat3 axis = idAngles( 0.0f, 90.0f, 90.0f ).ToMat3() * entityToWorldTransform.ToMat3(); + idQuat q = (entityToWorldTransform).ToMat3().ToQuat(); + q = idAngles( 90.0f, 0.0, -90.0f ).ToQuat() * q * idAngles( 180.0f, 180.0f, -90.0f ).ToQuat(); + idMat3 axis = q.ToMat3(); newEntity->epairs.SetVector( "light_target", axis[0] ); newEntity->epairs.SetVector( "light_right", axis[1] * -fov ); newEntity->epairs.SetVector( "light_up", axis[2] * fov ); @@ -506,8 +506,9 @@ void ResolveEntity( gltfData* data, idMapEntity* newEntity, gltfNode* node ) newPairs.SetDefaults( &newEntity->epairs ); newEntity->epairs = newPairs; + idMat4 entityTransform = mat4_identity; // gather entity transform and bring it into id Tech 4 space - gltfData::ResolveNodeMatrix( node ); + gltfData::ResolveNodeMatrix( node, &entityTransform ); // set entity transform in a way the game and physics code understand it idVec3 origin = blenderToDoomTransform * node->translation; @@ -518,12 +519,29 @@ void ResolveEntity( gltfData* data, idMapEntity* newEntity, gltfNode* node ) ResolveLight( data, newEntity, node ); } - // TODO set rotation key to store rotation and scaling - //if (idStr::Icmp(classname, "info_player_start") == 0) - // if( !node->matrix.IsIdentity() ) - //{ - // newEntity->epairs.SetMatrix("rotation", axis ); - //} + if( node->camera >= 0 && !newEntity->epairs.FindKey( "rotation" ) ) + { + idQuat q = entityTransform.ToMat3().ToQuat(); + q = idAngles( 90.0f, 0.0, -90.0f ).ToQuat() * q * blenderToDoomTransform.ToMat3().ToQuat(); + newEntity->epairs.SetMatrix( "rotation", q.ToMat3() ); + } + else if( idStr::Icmp( classname, "info_player_start" ) == 0 && !newEntity->epairs.FindKey( "rotation" ) ) + { + idQuat q = entityTransform.ToMat3().ToQuat(); + q = idAngles( -90.0f, 0.0, -90.0f ).ToQuat() * q * blenderToDoomTransform.ToMat3().ToQuat(); + newEntity->epairs.SetMatrix( "rotation", q.ToMat3() ); + } + else if( node->extras.strPairs.GetBool( "useNodeOrientation", false ) ) + { + //Nodes that are an instance of an collection containing a mesh that is not inline, ea; a gltfModel; static _or_ dynamic, + //which has its transformations applied on vertex level so we do not apply it here. + origin = blenderToDoomTransform * ( node->translation * ( entityTransform * node->matrix.Inverse() ) ); + newEntity->epairs.SetVector( "origin", origin ); + + idQuat q = ( entityTransform * node->matrix.Inverse() ).ToMat3().ToQuat(); + q = blenderToDoomTransform.Inverse().ToMat3().ToQuat() * q * blenderToDoomTransform.ToMat3().ToQuat(); + newEntity->epairs.SetMatrix( "rotation", q.ToMat3() ); + } #if 0 for( int i = 0; i < newEntity->epairs.GetNumKeyVals(); i++ )