Merge branch 'master' into 635-nvrhi5

This commit is contained in:
Robert Beckebans 2022-12-22 16:49:18 +01:00
commit 21947e8f08
10 changed files with 724 additions and 152 deletions

View file

@ -34,6 +34,9 @@ If you have questions concerning this license or the applicable additional terms
#include "gltfParser.h"
static const byte BCANIM_VERSION = 100;
static const unsigned int BCANIM_MAGIC = ( 'A' << 24 ) | ( 'C' << 16 ) | ( 'B' << 8 ) | BCANIM_VERSION;
#define CAMERA_ANIM_BINARYFILE_EXT "bcanim"
static const idMat4 blenderToDoomTransform( idAngles( 0.0f, 0.0f, 90 ).ToMat3(), vec3_origin );
/*
===============================================================================
@ -399,83 +402,117 @@ void idCameraAnim::LoadAnim()
}
}
// check for generated file
idStrStatic< MAX_OSPATH > generatedFileName = key;
generatedFileName.Insert( "generated/", 0 );
generatedFileName.SetFileExtension( CAMERA_ANIM_BINARYFILE_EXT );
ID_TIME_T currentTimeStamp = FILE_NOT_FOUND_TIMESTAMP;
if( isGLTF )
{
gltfLoadAnim( gltfFileName, animName );
currentTimeStamp = fileSystem->GetTimestamp( gltfFileName );
}
else
{
filename.SetFileExtension( MD5_CAMERA_EXT );
if( !parser.LoadFile( filename ) )
currentTimeStamp = fileSystem->GetTimestamp( key );
}
// if we are reloading the same map, check the timestamp
// and try to skip all the work
ID_TIME_T generatedTimeStamp = fileSystem->GetTimestamp( generatedFileName );
ID_TIME_T sourceTimeStamp = currentTimeStamp;
if( ( generatedTimeStamp != FILE_NOT_FOUND_TIMESTAMP ) && ( sourceTimeStamp != 0 ) && ( sourceTimeStamp != generatedTimeStamp ) )
{
idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) );
LoadBinaryCamAnim( file, currentTimeStamp );
}
else
{
if( isGLTF )
{
gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() );
gltfLoadAnim( gltfFileName, animName );
}
cameraCuts.Clear();
cameraCuts.SetGranularity( 1 );
camera.Clear();
camera.SetGranularity( 1 );
parser.ExpectTokenString( MD5_VERSION_STRING );
version = parser.ParseInt();
if( version != MD5_VERSION )
else
{
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 ) )
filename.SetFileExtension( MD5_CAMERA_EXT );
if( !parser.LoadFile( filename ) )
{
parser.Error( "Invalid camera cut" );
gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() );
}
}
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();
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( "}" );
idFileLocal file( fileSystem->OpenFileWrite( generatedFileName, "fs_basepath" ) );
WriteBinaryCamAnim( file );
}
}
@ -718,7 +755,7 @@ 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 !
// we dont want to load the gltb all the time. write custom binary format !
GLTF_Parser gltf;
if( gltf.Load( gltfFileName ) )
{
@ -731,7 +768,11 @@ void idCameraAnim::gltfLoadAnim( idStr gltfFileName, idStr animName )
assert( cameraNode );
gltfAnimation* anim = data->GetAnimation( animName, data->GetNodeIndex( cameraNode ) );
assert( anim );
if( anim == nullptr )
{
gameLocal.Error( "Missing 'anim.%s' on '%s'", animName, gltfFileName.c_str() );
}
cameraCuts.Clear();
cameraCuts.SetGranularity( 1 );
@ -828,9 +869,71 @@ void idCameraAnim::gltfLoadAnim( idStr gltfFileName, idStr animName )
{
gameLocal.Error( "Missing 'anim' key on '%s'", name.c_str() );
}
}
void idCameraAnim::WriteBinaryCamAnim( idFile* file, ID_TIME_T* _timeStamp /*= NULL*/ )
{
if( file != NULL )
{
file->WriteBig( BCANIM_MAGIC );
file->WriteInt( frameRate );
file->WriteInt( cameraCuts.Num() );
for( auto cut : cameraCuts )
{
file->WriteInt( cut );
}
file->WriteInt( camera.Num() );
for( auto cam : camera )
{
file->WriteBig( cam.fov );
file->WriteBigArray( cam.q.ToFloatPtr(), 3 );
file->WriteVec3( cam.t );
}
}
}
bool idCameraAnim::LoadBinaryCamAnim( idFile* file, const ID_TIME_T sourceTimeStamp )
{
if( file != NULL )
{
unsigned int magic = 0;
file->ReadBig( magic );
if( magic != BCANIM_MAGIC )
{
return false;
}
file->ReadInt( frameRate );
int count = 0;
file->ReadInt( count );
for( int i = 0; i < count; i++ )
{
file->ReadInt( cameraCuts.Alloc() );
}
count = 0;
file->ReadInt( count );
int i = 0;
for( i = 0; i < count; i++ )
{
cameraFrame_t& cam = camera.Alloc();
file->ReadBig( cam.fov );
file->ReadBigArray( cam.q.ToFloatPtr(), 3 );
file->ReadVec3( cam.t );
}
assert( i == count );
return true;
}
return false;
}
/*

View file

@ -133,6 +133,8 @@ private:
void Event_Activate( idEntity* activator );
void gltfLoadAnim( idStr gltfFileName, idStr animName );
void WriteBinaryCamAnim( idFile* file, ID_TIME_T* _timeStamp = NULL );
bool LoadBinaryCamAnim( idFile* file, const ID_TIME_T sourceTimeStamp );
};
#endif /* !__GAME_CAMERA_H__ */

View file

@ -1919,21 +1919,21 @@ void idPlayer::Init()
hipJoint = animator.GetJointHandle( value );
if( hipJoint == INVALID_JOINT )
{
gameLocal.Error( "Joint '%s' not found for 'bone_hips' on '%s'", value, name.c_str() );
gameLocal.Warning( "Joint '%s' not found for 'bone_hips' on '%s'", value, name.c_str() );
}
value = spawnArgs.GetString( "bone_chest", "" );
chestJoint = animator.GetJointHandle( value );
if( chestJoint == INVALID_JOINT )
{
gameLocal.Error( "Joint '%s' not found for 'bone_chest' on '%s'", value, name.c_str() );
gameLocal.Warning( "Joint '%s' not found for 'bone_chest' on '%s'", value, name.c_str() );
}
value = spawnArgs.GetString( "bone_head", "" );
headJoint = animator.GetJointHandle( value );
if( headJoint == INVALID_JOINT )
{
gameLocal.Error( "Joint '%s' not found for 'bone_head' on '%s'", value, name.c_str() );
gameLocal.Warning( "Joint '%s' not found for 'bone_head' on '%s'", value, name.c_str() );
}
// initialize the script variables

View file

@ -209,13 +209,32 @@ bool idMD5Anim::LoadAnim( const char* filename, const idImportOptions* options )
sourceTimeStamp = fileSystem->GetTimestamp( filename );
}
// glTF2 animations will be saved straight to .bMD5anim files instead of the old ASCII format
idFile* fileptr = fileSystem->OpenFileReadMemory( generatedFileName );
idFile* fileptr = nullptr;
bool doWrite = false;
if( fileptr == nullptr && isGLTF )
// glTF2 animations will be saved straight to .bMD5anim files instead of the old ASCII format,
// so to correctly regenerate newly exported animations, we need to check here.
ID_TIME_T binTimeStamp = fileSystem->GetTimestamp( generatedFileName );
if( isGLTF )
{
fileptr = idRenderModelGLTF::GetAnimBin( filenameStr, sourceTimeStamp, options );
doWrite = fileptr != nullptr;
if( sourceTimeStamp < binTimeStamp )
{
fileptr = fileSystem->OpenFileReadMemory( generatedFileName );
}
else
{
idLib::Printf( "Regenerating %s\n", generatedFileName.c_str() );
}
if( fileptr == nullptr )
{
fileptr = idRenderModelGLTF::GetAnimBin( filenameStr, sourceTimeStamp, options );
doWrite = fileptr != nullptr;
}
}
else
{
fileptr = fileSystem->OpenFileReadMemory( generatedFileName );
}
idFileLocal file( fileptr );
@ -1140,16 +1159,14 @@ void idMD5Anim::CheckModelHierarchy( const idRenderModel* model ) const
{
gameLocal.Error( "Model '%s''s joint names don't match anim '%s''s", model->Name(), name.c_str() );
}
int parent;
int parent = -1;
if( modelJoints[ i ].parent )
{
parent = modelJoints[ i ].parent - modelJoints;
}
else
{
parent = -1;
}
if( parent != jointInfo[ i ].parentNum )
if( i > 0 && parent != jointInfo[ i ].parentNum )
{
gameLocal.Error( "Model '%s' has different joint hierarchy than anim '%s'", model->Name(), name.c_str() );
}

View file

@ -560,7 +560,7 @@ void ResolveEntity( gltfData* data, idMapEntity* newEntity, gltfNode* node )
#endif
}
int FindEntities( gltfData* data, idMapEntity::EntityListRef entities, gltfNode* node )
int FindEntities( gltfData* data, idMapEntity::EntityListRef entities, gltfNode* node , idDict epairs )
{
int entityCount = 0;
@ -574,16 +574,28 @@ int FindEntities( gltfData* data, idMapEntity::EntityListRef entities, gltfNode*
// skip everything that is not an entity
if( !classnameStr.IsEmpty() )
{
idMapEntity* newEntity = new( TAG_IDLIB_GLTF ) idMapEntity();
auto* newEntity = new( TAG_IDLIB_GLTF ) idMapEntity();
entities.Append( newEntity );
newEntity->epairs.Copy( epairs );
epairs.Clear();
ResolveEntity( data, newEntity, node );
entityCount++;
}
else
{
idStr bindTarget = node->extras.strPairs.GetString( "bind" );
if( !bindTarget.IsEmpty() )
{
epairs.Set( "bind", bindTarget );
}
}
}
for( auto& child : node->children )
{
entityCount += FindEntities( data, entities, data->NodeList()[child] );
entityCount += FindEntities( data, entities, data->NodeList()[child], epairs );
}
return entityCount;
@ -621,7 +633,7 @@ int idMapEntity::GetEntities( gltfData* data, EntityListRef entities, int sceneI
{
idStr classnameStr = node->extras.strPairs.GetString( "classname" );
bool skipInline = !node->extras.strPairs.GetBool( "inline", true );
idDict epairs;
// skip everything that is not an entity
if( !classnameStr.IsEmpty() )
{
@ -640,10 +652,19 @@ int idMapEntity::GetEntities( gltfData* data, EntityListRef entities, int sceneI
entityCount++;
}
else
{
idStr bindTarget = node->extras.strPairs.GetString( "bind" );
if( !bindTarget.IsEmpty() )
{
epairs.Copy( node->extras.strPairs );
}
}
// add entities from all subnodes
for( auto& child : node->children )
{
entityCount += FindEntities( data, entities, data->NodeList()[child] );
entityCount += FindEntities( data, entities, data->NodeList()[child] , epairs );
}
}
}

View file

@ -1051,6 +1051,19 @@ public:
}
}
gltfSkin* GetSkin( idStr name )
{
for( auto skin : skins )
{
if( skin->name == name )
{
return skin;
}
}
return nullptr;
}
gltfSkin* GetSkin( int boneNodeId )
{
for( auto skin : skins )

View file

@ -1163,23 +1163,27 @@ void idImportOptions::Init( const char* commandline, const char* ospath )
idStr destDir;
//Reset( commandline );
scale = 1.0f;
//type = WRITE_MESH;
startframe = -1;
endframe = -1;
ignoreMeshes = false;
clearOrigin = false;
clearOriginAxis = false;
framerate = 24;
align = "";
rotate = 0.0f;
commandLine = commandline;
prefix = "";
jointThreshold = 0.05f;
ignoreScale = false;
xyzPrecision = DEFAULT_ANIM_EPSILON;
quatPrecision = DEFAULT_QUAT_EPSILON;
cycleStart = -1;
scale = 1.0f;
//type = WRITE_MESH;
startframe = -1;
endframe = -1;
ignoreMeshes = false;
clearOrigin = false;
clearOriginAxis = false;
addOrigin = false;
transferRootMotion = "";
framerate = 24;
align = "";
rotate = 0.0f;
commandLine = commandline;
prefix = "";
jointThreshold = 0.05f;
ignoreScale = false;
xyzPrecision = DEFAULT_ANIM_EPSILON;
quatPrecision = DEFAULT_QUAT_EPSILON;
cycleStart = -1;
reOrient = ang_zero;
armature = "";
src.Clear();
dest.Clear();
@ -1330,6 +1334,15 @@ void idImportOptions::Init( const char* commandline, const char* ospath )
clearOriginAxis = true;
}
else if( token == "addorigin" )
{
addOrigin = true;
}
else if( token == "transfermotion" )
{
token = tokens.NextToken( "Missing value for -transfermotion. Usage: -transfermotion [bonename]" );
transferRootMotion = token;
}
else if( token == "ignorescale" )
{
ignoreScale = true;
@ -1450,6 +1463,27 @@ void idImportOptions::Init( const char* commandline, const char* ospath )
keepjoints.AddUnique( token );
}
}
else if( token == "reorient" )
{
while( tokens.TokenAvailable() )
{
idAngles angle;
float x = atof( tokens.NextToken() );
float y = atof( tokens.NextToken() );
float z = atof( tokens.NextToken() );
reOrient = idAngles( x, y, z );
token = tokens.NextToken();
if( token[0] == '-' )
{
tokens.UnGetToken();
break;
}
}
}
else if( token == "armature" )
{
armature = tokens.NextToken( "Missing skin name for -armature. Usage: -armature [gltfSkin name]" );
}
else
{
MayaError( "Unknown option '%s'", token.c_str() );

View file

@ -69,6 +69,8 @@ public:
bool ignoreMeshes;
bool clearOrigin;
bool clearOriginAxis;
bool addOrigin;
idStr transferRootMotion;
bool ignoreScale;
int startframe;
int endframe;
@ -86,6 +88,8 @@ public:
float rotate;
float jointThreshold;
int cycleStart;
idAngles reOrient;
idStr armature;
void Init( const char* commandline, const char* ospath );

View file

@ -40,12 +40,12 @@ If you have questions concerning this license or the applicable additional terms
#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_ModelSceneName( "gltf_ModelSceneName", "Scene", 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 = 101;
static const byte GLMB_VERSION = 102;
static const unsigned int GLMB_MAGIC = ( 'M' << 24 ) | ( 'L' << 16 ) | ( 'G' << 8 ) | GLMB_VERSION;
static const char* GLTF_SnapshotName = "_GLTF_Snapshot_";
static const idMat4 blenderToDoomTransform( idAngles( 0.0f, 0.0f, 90 ).ToMat3(), vec3_origin );
@ -129,6 +129,183 @@ void idRenderModelGLTF::ProcessNode_r( gltfNode* modelNode, const idMat4& parent
ProcessNode_r( nodeList[child], nodeToWorldTransform, globalTransform, data );
}
}
static void KeepNodes( gltfData* data, const idStrList& keepList, idList<int, TAG_MODEL>& boneList )
{
idStrList finalList;
idStr nodeName;
idList<int> nodesToKeep;
for( int i = 0 ; i < keepList.Num(); i++ )
{
bool parentWildcard = false;
bool childWildcard = false;
if( keepList[i] == "*" )
{
parentWildcard = true;
i++;
}
const idStr item = keepList[i];
gltfNode* node = data->GetNode( item );
if( node == nullptr )
{
continue;
}
int idx = data->GetNodeIndex( node );
if( boneList.Find( idx ) )
{
nodesToKeep.Insert( idx );
}
if( ( i < ( keepList.Num() - 1 ) ) && keepList[i + 1] == "*" )
{
childWildcard = true;
i++;
}
if( parentWildcard && node->parent )
{
gltfNode* parent = node->parent;
while( parent )
{
idx = data->GetNodeIndex( parent );
if( boneList.Find( idx ) )
{
nodesToKeep.Insert( idx );
}
}
}
if( childWildcard && node->children.Num() )
{
idList<int> tmpIdx;
tmpIdx.Append( node->children );
for( int j = 0; j < tmpIdx.Num(); j++ )
{
idx = tmpIdx[j];
if( boneList.Find( idx ) )
{
nodesToKeep.Insert( idx );
gltfNode* tmpNode = data->NodeList()[idx];
if( tmpNode )
{
for( int child : tmpNode->children )
{
tmpIdx.AddUnique( child );
}
}
}
}
}
}
#if 0
common->SetRefreshOnPrint( true );
common->DPrintf( "=====\n" );
for( int nodeID : boneList )
{
if( nodesToKeep.Find( nodeID ) )
{
common->DPrintf( "^7[^2Kept^7]\t\t bone \'%s\'\n",
data->NodeList()[nodeID]->name.c_str() );
}
else
{
common->DPrintf( "^7[^1Discard^7]\t bone \'%s\'\n ",
data->NodeList()[nodeID]->name.c_str() );
}
}
common->DPrintf( "=====\n" );
common->SetRefreshOnPrint( false );
#endif
boneList = nodesToKeep;
}
static gltfNode* GetBoneNode( gltfData* data, const idList<int, TAG_MODEL>& boneList, const idStr& name )
{
auto& nodelist = data->NodeList();
for( int boneId : boneList )
{
gltfNode* boneNode = nodelist[boneId];
if( boneNode->name == name )
{
return boneNode;
}
}
return nullptr;
}
static void RemapNodes( gltfData* data, const idList<idNamePair>& remapList, const idList<int, TAG_MODEL>& boneList )
{
//we need to be _very_ careful with modifying the GLTF data since it is not saved or cached!!!
auto& nodeList = data->NodeList();
for( const auto& remap : remapList )
{
gltfNode* from = GetBoneNode( data, boneList, remap.from );
gltfNode* to = GetBoneNode( data, boneList, remap.to );
if( !from || !to )
{
common->Error( "Invalid remap name pair \'%s\'[\"%s\"] : \'%s\'[\"%s\"]",
remap.from.c_str(), from ? from->name.c_str() : "Not Found",
remap.to.c_str(), to ? to->name.c_str() : "Not Found" );
}
common->Warning( "Remapping.. setting \'%s\' as parent of \'%s\' ",
remap.to.c_str(), remap.from.c_str() );
from->parent = to;
to->children.Alloc() = data->GetNodeIndex( from );
}
}
static int AddOriginBone( gltfData* data, idList<int, TAG_MODEL>& bones, gltfNode* root )
{
//we need to be _very_ careful with modifying the GLTF data since it is not saved or cached!!!
auto& nodeList = data->NodeList();
gltfNode* newNode = data->Node();
int newIdx = nodeList.Num() - 1;
bones.Insert( newIdx );
newNode->name = "origin";
//patch children
for( int childId : root->children )
{
newNode->children.Alloc() = childId;
gltfNode* childNode = nodeList[childId];
childNode->parent = newNode;
}
root->children.Clear();
root->children.Alloc() = nodeList.Num() - 1;
newNode->parent = root;
common->Warning( "Added origin bone!" );
return newIdx;
}
static void RenameNodes( gltfData* data, const idList<idNamePair>& renameList, const idList<int, TAG_MODEL>& boneList )
{
//we need to be _very_ careful with modifying the GLTF data since it is not saved or cached!!!
auto& nodeList = data->NodeList();
for( const auto& rename : renameList )
{
gltfNode* from = GetBoneNode( data, boneList, rename.from );
if( !from )
{
common->Error( "Invalid rename name pair from \'%s\'[\"%s\"] ",
rename.from.c_str(), from ? from->name.c_str() : "Not Found" );
}
common->Warning( "Renaming.. \'%s\' to \'%s\' ",
rename.from.c_str(), rename.to.c_str() );
from->name = rename.to;
}
}
//constructs a renderModel from a gltfScene node found in the "models" scene of the given gltfFile.
// override with gltf_ModelSceneName
@ -144,7 +321,7 @@ void idRenderModelGLTF::InitFromFile( const char* fileName, const idImportOption
int meshID = -1;
name = fileName;
currentSkin = nullptr;
globalTransform = blenderToDoomTransform;
idImportOptions* localOptions = nullptr;
PurgeModel();
@ -153,6 +330,20 @@ void idRenderModelGLTF::InitFromFile( const char* fileName, const idImportOption
gltfFileName = idStr( fileName );
model_state = DM_STATIC;
if( options )
{
commandLine = options->commandLine;
localOptions = const_cast<idImportOptions*>( options );
}
else
{
if( !commandLine.IsEmpty() )
{
localOptions = new idImportOptions();
localOptions->Init( commandLine, fileName );
}
}
gltfManager::ExtractIdentifier( gltfFileName, meshID, meshName );
GLTF_Parser gltf;
gltf.Load( gltfFileName );
@ -172,13 +363,17 @@ void idRenderModelGLTF::InitFromFile( const char* fileName, const idImportOption
assert( nodes.Num() );
//determine root node
if( !meshName[0] )
if( !meshName[0] && data->MeshList().Num() )
{
root = new gltfNode();
root->name = scene->name;
root->children.Append( scene->nodes );
rootID = -1;
gltfMesh* firstMesh = data->MeshList()[0];
fileExclusive = true;
root = data->GetNode( scene, firstMesh, &rootID );
if( root )
{
rootID = data->GetNodeIndex( root );
meshName = root->name;
}
}
else
{
@ -193,6 +388,12 @@ void idRenderModelGLTF::InitFromFile( const char* fileName, const idImportOption
{
common->Warning( "Couldn't find model: '%s'", name.c_str() );
MakeDefaultModel();
if( localOptions && !options )
{
delete localOptions;
}
return;
}
@ -219,9 +420,33 @@ void idRenderModelGLTF::InitFromFile( const char* fileName, const idImportOption
bones.Append( currentSkin->joints );
animCount = data->GetAnimationIds( nodes[bones[0]] , animIds );
}
if( localOptions )
{
if( localOptions->keepjoints.Num() )
{
KeepNodes( data, localOptions->keepjoints, bones );
}
if( localOptions->addOrigin )
{
AddOriginBone( data, bones, nodes[bones[0]]->parent );
}
if( localOptions->remapjoints.Num() )
{
RemapNodes( data, localOptions->remapjoints, bones );
}
if( localOptions->renamejoints.Num() )
{
RenameNodes( data, localOptions->renamejoints, bones );
}
}
}
else
{
//Boneless TRS animation.
animCount = data->GetAnimationIds( tmpNode , animIds );
bones.Append( meshID );
}
@ -231,17 +456,22 @@ void idRenderModelGLTF::InitFromFile( const char* fileName, const idImportOption
hasAnimations = totalAnims > 0;
model_state = hasAnimations ? DM_CACHED : DM_STATIC;
globalTransform = blenderToDoomTransform;
//idMat4 globalTransform = blenderToDoomTransform;
if( options )
if( localOptions )
{
const auto blenderToDoomRotation = idAngles( 0.0f, 0.0f, 90 ).ToMat3();
idMat3 reOrientationMat = blenderToDoomRotation;
float scale = options->scale;
if( localOptions->reOrient != ang_zero )
{
reOrientationMat = localOptions->reOrient.ToMat3();
}
float scale = localOptions->scale;
idMat3 scaleMat( scale, 0, 0, 0, scale, 0, 0, 0, scale );
globalTransform = idMat4( scaleMat * blenderToDoomRotation, vec3_origin );
globalTransform = idMat4( reOrientationMat * scaleMat, vec3_origin );
}
ProcessNode_r( root, mat4_identity, globalTransform, data );
@ -251,10 +481,31 @@ void idRenderModelGLTF::InitFromFile( const char* fileName, const idImportOption
common->Warning( "Couldn't load model: '%s'", name.c_str() );
MakeDefaultModel();
data = nullptr;
if( localOptions && !options )
{
delete localOptions;
}
return;
}
if( options )
{
if( options->addOrigin )
{
//patch bone indices; all indices are offset with 1 after adding a bone
for( auto& surf : surfaces )
{
for( int i = 0; i < surf.geometry->numVerts; i++ )
{
idDrawVert& base = surf.geometry->verts[i];
base.color[0] += 1;
base.color[1] += 1;
base.color[2] += 1;
base.color[3] += 1;
}
}
}
}
// derive mikktspace tangents from normals
FinishSurfaces( true );
@ -263,6 +514,10 @@ void idRenderModelGLTF::InitFromFile( const char* fileName, const idImportOption
// it is now available for use
lastMeshFromFile = this;
if( localOptions && !options )
{
delete localOptions;
}
}
bool idRenderModelGLTF::LoadBinaryModel( idFile* file, const ID_TIME_T sourceTimeStamp )
@ -286,6 +541,7 @@ bool idRenderModelGLTF::LoadBinaryModel( idFile* file, const ID_TIME_T sourceTim
return false;
}
file->ReadString( commandLine );
file->ReadBig( model_state );
file->ReadBig( rootID );
@ -383,6 +639,7 @@ void idRenderModelGLTF::UpdateMd5Joints()
md5joints.Resize( bones.Num() );
md5joints.SetNum( bones.Num() );
idStr ovrBoneName;
int overrideIdx = -1;
auto& nodeList = data->NodeList();
for( int i = 0 ; i < bones.Num(); i++ )
@ -394,11 +651,8 @@ void idRenderModelGLTF::UpdateMd5Joints()
{
if( node->mesh == -1 )
{
common->Warning( "First bone of model is not named \"origin\", name forced!" );
common->Warning( "First bone of model \'%s\' is not named \"origin\"!", name.c_str() );
}
ovrBoneName = node->name;
md5joints[i].name = "origin";
}
else
{
@ -409,16 +663,9 @@ void idRenderModelGLTF::UpdateMd5Joints()
for( int i = 0 ; i < bones.Num(); i++ )
{
gltfNode* node = nodeList[bones[i]];
if( i && node->parent && node->parent != root )
if( i && node->parent && node->parent != root && ( currentSkin && ( node->parent->name != currentSkin->name ) ) )
{
if( node->parent->name == ovrBoneName )
{
md5joints[i].parent = FindMD5Joint( idStr( "origin" ) );
}
else
{
md5joints[i].parent = FindMD5Joint( node->parent->name );
}
md5joints[i].parent = FindMD5Joint( node->parent->name );
}
}
@ -484,7 +731,7 @@ void idRenderModelGLTF::DrawJoints( const struct renderEntity_s* ent, const view
}
}
static bool GatherBoneInfo( gltfData* data, gltfAnimation* gltfAnim, const idList<gltfNode*>& nodes , idList<int, TAG_MODEL>& bones, idList<jointAnimInfo_t, TAG_MD5_ANIM>& jointInfo )
static bool GatherBoneInfo( gltfData* data, gltfAnimation* gltfAnim, const idList<gltfNode*>& nodes , idList<int, TAG_MODEL>& bones, idList<jointAnimInfo_t, TAG_MD5_ANIM>& jointInfo , const idImportOptions* options )
{
//Gather Bones;
bool boneLess = false;
@ -492,7 +739,7 @@ static bool GatherBoneInfo( gltfData* data, gltfAnimation* gltfAnim, const idLis
auto skin = data->GetSkin( gltfAnim );
auto targets = data->GetAnimTargets( gltfAnim );
auto& nodeList = data->NodeList();
if( skin == nullptr )
{
boneLess = true;
@ -513,13 +760,39 @@ static bool GatherBoneInfo( gltfData* data, gltfAnimation* gltfAnim, const idLis
bones.Append( targetNode );
}
if( options )
{
if( options->keepjoints.Num() )
{
KeepNodes( data, options->keepjoints, bones );
}
if( options->addOrigin )
{
AddOriginBone( data, bones, data->NodeList()[bones[0]]->parent );
}
if( options->remapjoints.Num() )
{
RemapNodes( data, options->remapjoints, bones );
}
if( options->renamejoints.Num() )
{
RenameNodes( data, options->renamejoints, bones );
}
}
//create jointInfo
jointInfo.SetGranularity( 1 );
jointInfo.SetNum( bones.Num() );
int idx = 0;
for( auto& joint : jointInfo )
{
joint.animBits = 0;
joint.firstComponent = -1;
joint.nameIndex = animationLib.JointIndex( nodeList[bones[idx++]]->name );
}
return boneLess;
@ -611,6 +884,8 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
// convert animName to original glTF2 filename and load it
GLTF_Parser gltf;
int rootMotionCopyTargetId = -1;
int id;
idStr gltfFileName = idStr( animName );
idStr name;
@ -620,24 +895,73 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
gltfData* data = gltf.currentAsset;
auto& accessors = data->AccessorList();
auto& nodes = data->NodeList();
int rootID = -1;
gltfNode* nodeRoot = nodes[lastMeshFromFile->rootID];
int boneRootNode = lastMeshFromFile->rootID;
if( nodeRoot->skin > -1 )
idStr lastGltfFileName = idStr( lastMeshFromFile->name );
idStr lastName;
gltfManager::ExtractIdentifier( lastGltfFileName, id, lastName );
if( options != nullptr && !options->armature.IsEmpty() )
{
boneRootNode = nodes[data->SkinList()[nodeRoot->skin]->skeleton]->children[0];
gameLocal.Printf( "Looking for armature %s\n", options->armature.c_str() );
gltfSkin* skin = data->GetSkin( options->armature );
if( skin && ( skin->skeleton > -1 && skin->skeleton < nodes.Num() ) )
{
rootID = nodes[skin->skeleton]->children[0];
}
}
else if( lastMeshFromFile == nullptr || lastGltfFileName != gltfFileName )
{
//treat the gltf file as one that only has 1 scene and 1 model, aka a fileExclusive model and
//try to use node from first model in first scene in gltf file
common->Warning( "Loading %s as if it was a gltf with only 1 mesh, 1 scene and 1 armature", gltfFileName.c_str() );
if( data->MeshList().Num() < 1 || data->SceneList().Num() < 1 )
{
common->Warning( "Could not determine root" );
return nullptr;
}
gltfMesh* firstMesh = data->MeshList()[0];
gltfScene* scenePtr = data->SceneList()[0];
gltfNode* root = data->GetNode( scenePtr, firstMesh, &rootID );
if( root )
{
rootID = data->GetNodeIndex( root );
}
}
else
{
rootID = lastMeshFromFile->rootID;
gltfNode* nodeRoot = nullptr;
if( rootID != -1 )
{
nodeRoot = nodes[rootID];
}
if( nodeRoot != nullptr && nodeRoot->skin > -1 )
{
rootID = nodes[data->SkinList()[nodeRoot->skin]->skeleton]->children[0];
}
}
auto gltfAnim = data->GetAnimation( name, boneRootNode );
if( rootID == -1 )
{
common->Warning( "Could not determine root" );
return nullptr;
}
auto gltfAnim = data->GetAnimation( name, rootID );
if( !gltfAnim )
{
common->Warning( "Could not find action %s in %s !", name.c_str(), gltfFileName.c_str() );
return nullptr;
}
idList<int, TAG_MODEL> bones;
idList<jointAnimInfo_t, TAG_MD5_ANIM> jointInfo;
bool boneLess = GatherBoneInfo( data, gltfAnim, nodes, bones, jointInfo );
bool boneLess = GatherBoneInfo( data, gltfAnim, nodes, bones, jointInfo, options );
idList<idList<gltfNode>> animBones;
idList<float, TAG_MD5_ANIM> componentFrames;
@ -651,8 +975,33 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
gameLocal.Printf( "Generating MD5Anim for GLTF anim %s from scene %s\n", name.c_str(), gltf_ModelSceneName.GetString() );
// TODO use idImportOptions to build globalTransform
idMat4 globalTransform = blenderToDoomTransform;
if( options )
{
if( !options->transferRootMotion.IsEmpty() )
{
gltfNode* target = data->GetNode( options->transferRootMotion );
if( !target )
{
common->Warning( "Target bone to copy root motion from is not found" );
}
rootMotionCopyTargetId = data->GetNodeIndex( target );
}
const auto blenderToDoomRotation = idAngles( 0.0f, 0.0f, 90 ).ToMat3();
idMat3 reOrientationMat = blenderToDoomRotation;
if( options->reOrient != ang_zero )
{
reOrientationMat = options->reOrient.ToMat3();
}
float scale = options->scale;
idMat3 scaleMat( scale, 0, 0, 0, scale, 0, 0, 0, scale );
globalTransform = idMat4( reOrientationMat * scaleMat, vec3_origin );
}
gltfNode* root = nullptr;
int channelCount = 0;
@ -688,6 +1037,8 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
newJoint->nameIndex = animationLib.JointIndex( boneLess ? "origin" : target->name );
newJoint->parentNum = bones.FindIndex( parentIndex );
//assert( newJoint->parentNum >= 0 );
if( newJoint->firstComponent == -1 )
{
newJoint->firstComponent = numAnimatedComponents;
@ -717,6 +1068,19 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
channelCount++;
}
if( options && !options->transferRootMotion.IsEmpty() )
{
jointAnimInfo_t* newJoint = &( jointInfo[0] );
newJoint->animBits = ANIM_TX | ANIM_TY | ANIM_TZ;
numAnimatedComponents += 3;
newJoint->firstComponent = -3;
for( auto& joint : jointInfo )
{
joint.firstComponent += 3;
}
}
animBones.AssureSize( numFrames );
animBones.SetNum( numFrames );
for( int i = 0; i < numFrames; i++ )
@ -748,7 +1112,7 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
baseFrame.SetNum( bones.Num() );
idJointMat* poseMat = ( idJointMat* ) _alloca16( bones.Num() * sizeof( poseMat[0] ) );
baseFrame = GetPose( animBones[0], poseMat, blenderToDoomTransform );
baseFrame = GetPose( animBones[0], poseMat, globalTransform );
componentFrames.SetGranularity( 1 );
componentFrames.SetNum( ( ( numAnimatedComponents * numFrames ) ) + 1 );
@ -793,7 +1157,16 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
idList<idVec3*>& values = data->GetAccessorView<idVec3>( output );
if( values.Num() > i )
{
animBones[i][boneIndex].translation = *values[i];
if( channel->target.node == rootMotionCopyTargetId )
{
animBones[i][boneIndex].translation.y = values[i]->y;
animBones[i][0].translation = *values[i];
animBones[i][0].translation.y = 0;
}
else
{
animBones[i][boneIndex].translation = *values[i];
}
}
break;
}
@ -822,7 +1195,7 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
{
if( node->parent == nullptr )
{
t = blenderToDoomTransform * t;
t = globalTransform * t;
}
componentFrames[componentFrameIndex++] = t.x;
@ -834,7 +1207,7 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
{
if( node->parent == nullptr )
{
q = blenderToDoomTransform.ToMat3().ToQuat() * animBones[i][b].rotation;
q = globalTransform.ToMat3().ToQuat() * animBones[i][b].rotation;
}
else
{
@ -864,12 +1237,12 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
{
if( animBones[i][b].parent == nullptr )
{
animBones[i][b].translation = blenderToDoomTransform * animBones[i][b].translation;
animBones[i][b].translation = globalTransform * animBones[i][b].translation;
}
}
idList<idJointMat> joints;
GetPose( animBones[i], currJoints, blenderToDoomTransform );
GetPose( animBones[i], currJoints, globalTransform );
for( int b = 0; b < animBones[i].Num(); b++ )
{
idJointMat mat = poseMat[b];
@ -975,7 +1348,11 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
}
else
{
componentPtr = &componentFrames[jointInfo[0].firstComponent];
if( jointInfo[0].animBits )
{
componentPtr = &componentFrames[jointInfo[0].firstComponent];
}
//of there is root movement on a different bone , for example when adding a root bone, this wil fail.
if( jointInfo[0].animBits & ANIM_TX )
{
for( int i = 0; i < numFrames; i++ )
@ -1024,7 +1401,7 @@ idFile_Memory* idRenderModelGLTF::GetAnimBin( const idStr& animName, const ID_TI
void idRenderModelGLTF::WriteBinaryModel( idFile* file, ID_TIME_T* _timeStamp /*= NULL */ ) const
{
idRenderModelStatic::WriteBinaryModel( file );
idRenderModelStatic::WriteBinaryModel( file , _timeStamp );
if( file == NULL )
{
@ -1032,6 +1409,7 @@ void idRenderModelGLTF::WriteBinaryModel( idFile* file, ID_TIME_T* _timeStamp /*
}
file->WriteBig( GLMB_MAGIC );
file->WriteString( commandLine );
file->WriteBig( model_state );
file->WriteBig( rootID );
file->WriteString( file->GetName() );
@ -1089,6 +1467,7 @@ void idRenderModelGLTF::PurgeModel()
bones.Clear();
MeshNodeIds.Clear();
gltfFileName.Clear();
meshName.Clear();
//if no root id was set, it is a generated one.
if( rootID == -1 && root )
@ -1100,8 +1479,6 @@ void idRenderModelGLTF::PurgeModel()
void idRenderModelGLTF::LoadModel()
{
assert( data );
int num;
auto& accessors = data->AccessorList();
auto& nodes = data->NodeList();
@ -1109,7 +1486,6 @@ void idRenderModelGLTF::LoadModel()
if( !fileExclusive )
{
meshRoot = data->GetNode( gltf_ModelSceneName.GetString(), meshName );
assert( meshRoot );
}
gltfSkin* skin = nullptr;
@ -1148,7 +1524,8 @@ void idRenderModelGLTF::LoadModel()
//check for TRS anim and its artficial root bone
if( bones.Num() == 0 && node->mesh != -1 )
{
md5joints[i].name = "origin";
assert( 0 );
//md5joints[i].name = "origin";
}
else
{
@ -1160,7 +1537,7 @@ void idRenderModelGLTF::LoadModel()
{
auto* node = nodes[bones[i]];
if( i && node->parent && node->parent != root )
if( i && node->parent && node->parent != root && ( currentSkin && ( node->parent->name != currentSkin->name ) ) )
{
md5joints[i].parent = FindMD5Joint( node->parent->name );
}

View file

@ -74,6 +74,7 @@ private:
dynamicModel_t model_state;
idStr meshName;
idStr gltfFileName;
idStr commandLine;
idList<idMD5Joint, TAG_MODEL> md5joints;
idList<idJointQuat, TAG_MODEL> defaultPose;