@ -146,7 +146,7 @@ static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, unsigned int
# endif
# if defined(SUPPORT_FILEFORMAT_GLTF)
static Model LoadGLTF ( const char * fileName ) ; / / Load GLTF mesh data
/ / static ModelAnimation * LoadModelAnimationGLTF ( const char * fileName , unsigned int * animCount ) ; / / Load GLTF animation data
static ModelAnimation * f ">LoadModelAnimations GLTF( const char * fileName , unsigned int * animCount ) ; / / Load GLTF animation data
# endif
# if defined(SUPPORT_FILEFORMAT_VOX)
static Model LoadVOX ( const char * filename ) ; / / Load VOX mesh data
@ -1955,7 +1955,7 @@ ModelAnimation *LoadModelAnimations(const char *fileName, unsigned int *animCoun
if ( IsFileExtension ( fileName , " .m3d " ) ) animations = LoadModelAnimationsM3D ( fileName , animCount ) ;
# endif
# if defined(SUPPORT_FILEFORMAT_GLTF)
o">/ / if ( IsFileExtension ( fileName , " .gltf;.glb " ) ) animations = LoadModelAnimationGLTF ( fileName , animCount ) ;
if ( IsFileExtension ( fileName , " .gltf;.glb " ) ) animations = LoadModelAnimations GLTF ( fileName , animCount ) ;
# endif
return animations ;
@ -2029,8 +2029,8 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame)
/ / Vertices processing
/ / NOTE : We use meshes . vertices ( default vertex position ) to calculate meshes . animVertices ( animated vertex position )
animVertex = ( Vector3 ) { mesh . vertices [ vCounter ] , mesh . vertices [ vCounter + 1 ] , mesh . vertices [ vCounter + 2 ] } ;
animVertex = Vector3Multiply ( animVertex , outScale ) ;
animVertex = Vector3Subtract ( animVertex , inTranslation ) ;
animVertex = Vector3Multiply ( animVertex , outScale ) ;
animVertex = Vector3RotateByQuaternion ( animVertex , QuaternionMultiply ( outRotation , QuaternionInvert ( inRotation ) ) ) ;
animVertex = Vector3Add ( animVertex , outTranslation ) ;
/ / animVertex = Vector3Transform ( animVertex , model . transform ) ;
@ -3829,6 +3829,25 @@ RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Ve
return collision ;
}
static void BuildPoseFromParentJoints ( BoneInfo * bones , int boneCount , Transform * transforms )
{
for ( int i = 0 ; i < boneCount ; i + + )
{
if ( bones [ i ] . parent > = 0 )
{
if ( bones [ i ] . parent > i )
{
TRACELOG ( LOG_WARNING , " Assumes bones are toplogically sorted, but bone %d has parent %d. Skipping. " , i , bones [ i ] . parent ) ;
continue ;
}
transforms [ i ] . rotation = QuaternionMultiply ( transforms [ bones [ i ] . parent ] . rotation , transforms [ i ] . rotation ) ;
transforms [ i ] . translation = Vector3RotateByQuaternion ( transforms [ i ] . translation , transforms [ bones [ i ] . parent ] . rotation ) ;
transforms [ i ] . translation = Vector3Add ( transforms [ i ] . translation , transforms [ bones [ i ] . parent ] . translation ) ;
transforms [ i ] . scale = Vector3Multiply ( transforms [ i ] . scale , transforms [ bones [ i ] . parent ] . scale ) ;
}
}
}
/ / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/ / Module specific Functions Definition
/ / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -4370,17 +4389,7 @@ static Model LoadIQM(const char *fileName)
model . bindPose [ i ] . scale . z = ijoint [ i ] . scale [ 2 ] ;
}
/ / Build bind pose from parent joints
for ( int i = 0 ; i < model . boneCount ; i + + )
{
if ( model . bones [ i ] . parent > = 0 )
{
model . bindPose [ i ] . rotation = QuaternionMultiply ( model . bindPose [ model . bones [ i ] . parent ] . rotation , model . bindPose [ i ] . rotation ) ;
model . bindPose [ i ] . translation = Vector3RotateByQuaternion ( model . bindPose [ i ] . translation , model . bindPose [ model . bones [ i ] . parent ] . rotation ) ;
model . bindPose [ i ] . translation = Vector3Add ( model . bindPose [ i ] . translation , model . bindPose [ model . bones [ i ] . parent ] . translation ) ;
model . bindPose [ i ] . scale = Vector3Multiply ( model . bindPose [ i ] . scale , model . bindPose [ model . bones [ i ] . parent ] . scale ) ;
}
}
BuildPoseFromParentJoints ( model . bones , model . boneCount , model . bindPose ) ;
RL_FREE ( fileData ) ;
@ -4681,6 +4690,33 @@ static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPat
return image ;
}
static BoneInfo * LoadGLTFBoneInfo ( cgltf_skin skin , int * boneCount )
{
* boneCount = skin . joints_count ;
BoneInfo * bones = RL_MALLOC ( skin . joints_count * sizeof ( BoneInfo ) ) ;
for ( unsigned int i = 0 ; i < skin . joints_count ; i + + )
{
cgltf_node node = * skin . joints [ i ] ;
strncpy ( bones [ i ] . name , node . name , sizeof ( bones [ i ] . name ) ) ;
/ / find parent bone index
unsigned int parentIndex = - 1 ;
for ( unsigned int j = 0 ; j < skin . joints_count ; j + + )
{
if ( skin . joints [ j ] = = node . parent )
{
parentIndex = j ;
break ;
}
}
bones [ i ] . parent = parentIndex ;
}
return bones ;
}
/ / Load glTF file into model struct , . gltf and . glb supported
static Model LoadGLTF ( const char * fileName )
{
@ -4695,6 +4731,7 @@ static Model LoadGLTF(const char *fileName)
- Supports PBR metallic / roughness flow , loads material textures , values and colors
PBR specular / glossiness flow and extended texture flows not supported
- Supports multiple meshes per model ( every primitives is loaded as a separate mesh )
- Supports basic animation
RESTRICTIONS :
- Only triangle meshes supported
@ -5039,11 +5076,41 @@ static Model LoadGLTF(const char *fileName)
}
}
/*
/ / TODO : Load glTF meshes animation data
/ / Load glTF meshes animation data
/ / REF : https : / / www . khronos . org / registry / glTF / specs / 2.0 / glTF - 2.0 . html # skins
/ / REF : https : / / www . khronos . org / registry / glTF / specs / 2.0 / glTF - 2.0 . html # skinned - mesh - attributes
/ / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if ( data - > skins_count = = 1 )
{
cgltf_skin skin = data - > skins [ 0 ] ;
model . bones = LoadGLTFBoneInfo ( skin , & model . boneCount ) ;
model . bindPose = RL_MALLOC ( model . boneCount * sizeof ( Transform ) ) ;
for ( unsigned int i = 0 ; i < model . boneCount ; i + + )
{
cgltf_node node = * skin . joints [ i ] ;
model . bindPose [ i ] . translation . x = node . translation [ 0 ] ;
model . bindPose [ i ] . translation . y = node . translation [ 1 ] ;
model . bindPose [ i ] . translation . z = node . translation [ 2 ] ;
model . bindPose [ i ] . rotation . x = node . rotation [ 0 ] ;
model . bindPose [ i ] . rotation . y = node . rotation [ 1 ] ;
model . bindPose [ i ] . rotation . z = node . rotation [ 2 ] ;
model . bindPose [ i ] . rotation . w = node . rotation [ 3 ] ;
model . bindPose [ i ] . scale . x = node . scale [ 0 ] ;
model . bindPose [ i ] . scale . y = node . scale [ 1 ] ;
model . bindPose [ i ] . scale . z = node . scale [ 2 ] ;
}
BuildPoseFromParentJoints ( model . bones , model . boneCount , model . bindPose ) ;
}
else if ( data - > skins_count > 1 )
{
TRACELOG ( LOG_ERROR , " MODEL: [%s] can only load one skin (armature) per model, but gltf skins_count == %i " , fileName , data - > skins_count ) ;
}
for ( unsigned int i = 0 , meshIndex = 0 ; i < data - > meshes_count ; i + + )
{
for ( unsigned int p = 0 ; p < data - > meshes [ i ] . primitives_count ; p + + )
@ -5065,7 +5132,6 @@ static Model LoadGLTF(const char *fileName)
model . meshes [ meshIndex ] . boneIds = RL_CALLOC ( model . meshes [ meshIndex ] . vertexCount * 4 , sizeof ( unsigned char ) ) ;
/ / Load 4 components of unsigned char data type into mesh . boneIds
/ / TODO : It seems LOAD_ATTRIBUTE ( ) macro does not work as expected in some cases ,
/ / for cgltf_attribute_type_joints we have :
/ / - data . meshes [ 0 ] ( 256 vertices )
/ / - 256 values , provided as cgltf_type_vec4 of bytes ( 4 byte per joint , stride 4 )
@ -5092,10 +5158,17 @@ static Model LoadGLTF(const char *fileName)
}
}
/ / Animated vertex data
model . meshes [ meshIndex ] . animVertices = RL_CALLOC ( model . meshes [ meshIndex ] . vertexCount * 3 , sizeof ( float ) ) ;
memcpy ( model . meshes [ meshIndex ] . animVertices , model . meshes [ meshIndex ] . vertices , model . meshes [ meshIndex ] . vertexCount * 3 * sizeof ( float ) ) ;
model . meshes [ meshIndex ] . animNormals = RL_CALLOC ( model . meshes [ meshIndex ] . vertexCount * 3 , sizeof ( float ) ) ;
memcpy ( model . meshes [ meshIndex ] . animNormals , model . meshes [ meshIndex ] . normals , model . meshes [ meshIndex ] . vertexCount * 3 * sizeof ( float ) ) ;
meshIndex + + ; / / Move to next mesh
}
}
*/
/ / Free all cgltf loaded data
cgltf_free ( data ) ;
}
@ -5106,6 +5179,217 @@ static Model LoadGLTF(const char *fileName)
return model ;
}
/ / Get interpolated pose for bone sampler at a specific time . Returns true on success .
static bool GetGLTFPoseAtTime ( cgltf_accessor * input , cgltf_accessor * output , float time , void * data )
{
/ / input and output should have the same count
float tstart = 0.0f ;
float tend = 0.0f ;
int keyframe = 0 ; / / defaults to first pose
for ( int i = 0 ; i < input - > count - 1 ; i + + )
{
cgltf_bool r1 = cgltf_accessor_read_float ( input , i , & tstart , 1 ) ;
if ( ! r1 ) return false ;
cgltf_bool r2 = cgltf_accessor_read_float ( input , i + 1 , & tend , 1 ) ;
if ( ! r2 ) return false ;
if ( ( tstart < = time ) & & ( time < tend ) )
{
keyframe = i ;
break ;
}
}
float t = ( time - tstart ) / ( tend - tstart ) ;
t = ( t < 0.0f ) ? 0.0f : t ;
t = ( t > 1.0f ) ? 1.0f : t ;
if ( output - > component_type ! = cgltf_component_type_r_32f ) return false ;
if ( output - > type = = cgltf_type_vec3 )
{
float tmp [ 3 ] = { 0.0f } ;
cgltf_accessor_read_float ( output , keyframe , tmp , 3 ) ;
Vector3 v1 = { tmp [ 0 ] , tmp [ 1 ] , tmp [ 2 ] } ;
cgltf_accessor_read_float ( output , keyframe + 1 , tmp , 3 ) ;
Vector3 v2 = { tmp [ 0 ] , tmp [ 1 ] , tmp [ 2 ] } ;
Vector3 * r = data ;
* r = Vector3Lerp ( v1 , v2 , t ) ;
}
else if ( output - > type = = cgltf_type_vec4 )
{
float tmp [ 4 ] = { 0.0f } ;
cgltf_accessor_read_float ( output , keyframe , tmp , 4 ) ;
Vector4 v1 = { tmp [ 0 ] , tmp [ 1 ] , tmp [ 2 ] , tmp [ 3 ] } ;
cgltf_accessor_read_float ( output , keyframe + 1 , tmp , 4 ) ;
Vector4 v2 = { tmp [ 0 ] , tmp [ 1 ] , tmp [ 2 ] , tmp [ 3 ] } ;
Vector4 * r = data ;
/ / only v4 is for rotations , so we know it ' s a quat .
* r = QuaternionSlerp ( v1 , v2 , t ) ;
}
return true ;
}
# define GLTF_ANIMDELAY 17 / / that's roughly ~1000 msec / 60 FPS (16.666666* msec)
static ModelAnimation * LoadModelAnimationsGLTF ( const char * fileName , unsigned int * animCount )
{
/ / glTF file loading
unsigned int dataSize = 0 ;
unsigned char * fileData = LoadFileData ( fileName , & dataSize ) ;
ModelAnimation * animations = NULL ;
/ / glTF data loading
cgltf_options options = { 0 } ;
cgltf_data * data = NULL ;
cgltf_result result = cgltf_parse ( & options , fileData , dataSize , & data ) ;
if ( result ! = cgltf_result_success )
{
TRACELOG ( LOG_WARNING , " MODEL: [%s] Failed to load glTF data " , fileName ) ;
* animCount = 0 ;
return NULL ;
}
result = cgltf_load_buffers ( & options , data , fileName ) ;
if ( result ! = cgltf_result_success ) TRACELOG ( LOG_INFO , " MODEL: [%s] Failed to load animation buffers " , fileName ) ;
if ( result = = cgltf_result_success )
{
if ( data - > skins_count = = 1 )
{
cgltf_skin skin = data - > skins [ 0 ] ;
* animCount = data - > animations_count ;
animations = RL_MALLOC ( data - > animations_count * sizeof ( ModelAnimation ) ) ;
for ( unsigned int i = 0 ; i < data - > animations_count ; i + + )
{
animations [ i ] . bones = LoadGLTFBoneInfo ( skin , & animations [ i ] . boneCount ) ;
cgltf_animation animData = data - > animations [ i ] ;
struct Channels {
cgltf_animation_channel * translate ;
cgltf_animation_channel * rotate ;
cgltf_animation_channel * scale ;
} ;
struct Channels * boneChannels = RL_CALLOC ( animations [ i ] . boneCount , sizeof ( struct Channels ) ) ;
float animDuration = 0.0f ;
for ( unsigned int j = 0 ; j < animData . channels_count ; j + + )
{
cgltf_animation_channel channel = animData . channels [ j ] ;
int boneIndex = - 1 ;
for ( unsigned int k = 0 ; k < skin . joints_count ; k + + )
{
if ( animData . channels [ j ] . target_node = = skin . joints [ k ] )
{
boneIndex = k ;
break ;
}
}
if ( boneIndex = = - 1 )
{
/ / animation channel for a node not in the armature .
continue ;
}
if ( animData . channels [ j ] . sampler - > interpolation = = cgltf_interpolation_type_linear )
{
if ( channel . target_path = = cgltf_animation_path_type_translation )
{
boneChannels [ boneIndex ] . translate = & animData . channels [ j ] ;
}
else if ( channel . target_path = = cgltf_animation_path_type_rotation )
{
boneChannels [ boneIndex ] . rotate = & animData . channels [ j ] ;
}
else if ( channel . target_path = = cgltf_animation_path_type_scale )
{
boneChannels [ boneIndex ] . scale = & animData . channels [ j ] ;
}
else
{
TRACELOG ( LOG_WARNING , " MODEL: [%s] Unsupported target_path on channel %d's sampler for animation %d. Skipping. " , fileName , j , i ) ;
}
} else TRACELOG ( LOG_WARNING , " MODEL: [%s] Only linear interpolation curves are supported for GLTF animation. " , fileName ) ;
float t = 0.0f ;
cgltf_bool r = cgltf_accessor_read_float ( channel . sampler - > input , channel . sampler - > input - > count - 1 , & t , 1 ) ;
if ( ! r )
{
TRACELOG ( LOG_WARNING , " MODEL: [%s] Failed to load input time " , fileName ) ;
continue ;
}
animDuration = ( t > animDuration ) ? t : animDuration ;
}
animations [ i ] . frameCount = ( int ) ( animDuration * 1000.0f / GLTF_ANIMDELAY ) ;
animations [ i ] . framePoses = RL_MALLOC ( animations [ i ] . frameCount * sizeof ( Transform * ) ) ;
for ( unsigned int j = 0 ; j < animations [ i ] . frameCount ; j + + )
{
animations [ i ] . framePoses [ j ] = RL_MALLOC ( animations [ i ] . boneCount * sizeof ( Transform ) ) ;
float time = ( ( float ) j * GLTF_ANIMDELAY ) / 1000.0f ;
for ( unsigned int k = 0 ; k < animations [ i ] . boneCount ; k + + )
{
Vector3 translation = { 0 , 0 , 0 } ;
Quaternion rotation = { 0 , 0 , 0 , 1 } ;
Vector3 scale = { 1 , 1 , 1 } ;
if ( boneChannels [ k ] . translate )
{
if ( ! GetGLTFPoseAtTime ( boneChannels [ k ] . translate - > sampler - > input ,
boneChannels [ k ] . translate - > sampler - > output ,
time ,
& translation ) )
{
TRACELOG ( LOG_INFO , " MODEL: [%s] Failed to load translate pose data for bone %s " , fileName , animations [ i ] . bones [ k ] . name ) ;
}
}
if ( boneChannels [ k ] . rotate )
{
if ( ! GetGLTFPoseAtTime ( boneChannels [ k ] . rotate - > sampler - > input ,
boneChannels [ k ] . rotate - > sampler - > output ,
time ,
& rotation ) )
{
TRACELOG ( LOG_INFO , " MODEL: [%s] Failed to load rotate pose data for bone %s " , fileName , animations [ i ] . bones [ k ] . name ) ;
}
}
if ( boneChannels [ k ] . scale )
{
if ( ! GetGLTFPoseAtTime ( boneChannels [ k ] . scale - > sampler - > input ,
boneChannels [ k ] . scale - > sampler - > output ,
time ,
& scale ) )
{
TRACELOG ( LOG_INFO , " MODEL: [%s] Failed to load scale pose data for bone %s " , fileName , animations [ i ] . bones [ k ] . name ) ;
}
}
animations [ i ] . framePoses [ j ] [ k ] = ( Transform ) {
. translation = translation ,
. rotation = rotation ,
. scale = scale } ;
}
BuildPoseFromParentJoints ( animations [ i ] . bones , animations [ i ] . boneCount , animations [ i ] . framePoses [ j ] ) ;
}
TRACELOG ( LOG_INFO , " MODEL: [%s] Loaded animation: %s (%d frames, %fs) " , fileName , animData . name , animations [ i ] . frameCount , animDuration ) ;
RL_FREE ( boneChannels ) ;
}
} else TRACELOG ( LOG_ERROR , " MODEL: [%s] expected exactly one skin to load animation data from, but found %i " , fileName , data - > skins_count ) ;
cgltf_free ( data ) ;
}
return animations ;
}
# endif
# if defined(SUPPORT_FILEFORMAT_VOX)