commit f7899e648e5a74e8ec00f8795586338a663a436c
parent c9131d1c8bcd79ac8adba74d6dedd59d7986fe34
Author: Michael Savage <mikejsavage@gmail.com>
Date: Thu, 30 May 2019 19:18:28 +0300
Support out of order skeletons and proper channels
Diffstat:
3 files changed, 132 insertions(+), 109 deletions(-)
diff --git a/CesiumMan.glb b/CesiumMan.glb
Binary files differ.
diff --git a/gltf.cc b/gltf.cc
@@ -25,14 +25,6 @@ struct SQT {
float scale;
};
-SQT Lerp( const SQT & from, float t, const SQT & to ) {
- SQT res;
- res.rotation = NLerp( from.rotation, t, to.rotation );
- res.translation = Lerp( from.translation, t, to.translation );
- res.scale = Lerp( from.scale, t, to.scale );
- return res;
-}
-
inline void format( FormatBuffer * fb, const SQT & sqt, const FormatOpts & opts ) {
format( fb, "SQT(" );
format( fb, sqt.rotation, opts );
@@ -69,33 +61,33 @@ static Mat4 SQTToMat4( const SQT & sqt ) {
);
}
-struct AnimationSample {
- SQT * joint_poses;
-};
-
struct AnimationReel {
+ template< typename T >
+ struct Channel {
+ T * samples;
+ float * times;
+ u32 num_samples;
+ };
+
struct Joint {
- Mat4 node_transform;
Mat4 joint_to_bind;
u8 parent;
- };
+ u8 next;
- struct Sample {
- SQT * joint_poses;
+ Channel< Quaternion > rotations;
+ Channel< Vec3 > translations;
+ Channel< float > scales;
};
struct Clip {
- u32 first_sample;
- u32 num_frames;
- float fps;
+ float start_time;
+ float duration;
bool loop;
};
Joint * joints;
u8 num_joints;
-
- Sample * samples;
- u32 num_samples;
+ u8 root_joint;
Clip * clips;
u32 num_clips;
@@ -108,36 +100,53 @@ float PositiveMod( float x, float y ) {
return res;
}
+void SampleAnimationChannel( const float * times, u32 n, float t, u32 * sample, float * lerp_frac ) {
+ t = clamp( times[ 0 ], t, times[ n - 1 ] );
+
+ *sample = 0;
+ for( u32 i = 1; i < n; i++ ) {
+ if( times[ i ] >= t ) {
+ *sample = i - 1;
+ break;
+ }
+ }
+
+ *lerp_frac = ( t - times[ *sample ] ) / ( times[ *sample + 1 ] - times[ *sample ] );
+}
+
void SampleAnimationClip( const AnimationReel & reel, u32 clip_idx, float t, float start_time, SQT * joint_poses ) {
AnimationReel::Clip clip = reel.clips[ clip_idx ];
- float clip_length = clip.num_frames / clip.fps;
float local_time;
if( clip.loop ) {
- local_time = PositiveMod( t - start_time, clip_length );
+ local_time = PositiveMod( t - start_time, clip.duration );
}
else {
- local_time = clamp( 0.0f, t - start_time, clip_length );
+ local_time = clamp( 0.0f, t - start_time, clip.duration );
}
- float local_frame = local_time * clip.fps;
- float lerp_frac = local_frame - floorf( local_frame );
+ for( u32 i = 0; i < reel.num_joints; i++ ) {
+ AnimationReel::Joint & joint = reel.joints[ i ];
+ u32 rotation_sample, translation_sample, scale_sample;
+ float rotation_lerp_frac, translation_lerp_frac, scale_lerp_frac;
- u32 prev_sample = u32( local_frame );
- u32 next_sample = prev_sample + 1;
- if( clip.loop )
- next_sample %= clip.num_frames;
+ SampleAnimationChannel( joint.rotations.times, joint.rotations.num_samples, local_time, &rotation_sample, &rotation_lerp_frac );
+ SampleAnimationChannel( joint.translations.times, joint.translations.num_samples, local_time, &translation_sample, &translation_lerp_frac );
+ SampleAnimationChannel( joint.scales.times, joint.scales.num_samples, local_time, &scale_sample, &scale_lerp_frac );
- for( u32 i = 0; i < reel.num_joints; i++ ) {
- joint_poses[ i ] = Lerp( reel.samples[ prev_sample ].joint_poses[ i ], lerp_frac, reel.samples[ next_sample ].joint_poses[ i ] );
+ joint_poses[ i ].rotation = NLerp( joint.rotations.samples[ rotation_sample ], rotation_lerp_frac, joint.rotations.samples[ rotation_sample ] );
+ joint_poses[ i ].translation = Lerp( joint.translations.samples[ translation_sample ], translation_lerp_frac, joint.translations.samples[ translation_sample ] );
+ joint_poses[ i ].scale = Lerp( joint.scales.samples[ scale_sample ], scale_lerp_frac, joint.scales.samples[ scale_sample ] );
}
}
void ComputeMatrixPalette( const AnimationReel & animation, const SQT * sqts, Mat4 * joint_poses, Mat4 * skinning_matrices ) {
- joint_poses[ 0 ] = SQTToMat4( sqts[ 0 ] );
+ u8 joint_idx = animation.root_joint;
+ joint_poses[ joint_idx ] = SQTToMat4( sqts[ joint_idx ] );
for( u32 i = 1; i < animation.num_joints; i++ ) {
- u8 parent = animation.joints[ i ].parent;
- joint_poses[ i ] = joint_poses[ parent ] * SQTToMat4( sqts[ i ] );
+ joint_idx = animation.joints[ joint_idx ].next;
+ u8 parent = animation.joints[ joint_idx ].parent;
+ joint_poses[ joint_idx ] = joint_poses[ parent ] * SQTToMat4( sqts[ joint_idx ] );
}
for( u32 i = 0; i < animation.num_joints; i++ ) {
@@ -171,45 +180,38 @@ static bool LoadBinaryBuffers( cgltf_data * data ) {
static AnimationReel::Clip CLIP;
static GLTFModel model;
-static void LoadSkin( const cgltf_skin * skin ) {
- printf( "skeleton %p\n", skin->skeleton );
+static void AddJoint( const cgltf_skin * skin, cgltf_node * node, u8 ** prev ) {
+ u8 joint_idx = u8( uintptr_t( node->camera ) - 1 );
+ **prev = joint_idx;
+ *prev = &model.animation.joints[ joint_idx ].next;
+ model.animation.joints[ joint_idx ].parent = u8( uintptr_t( node->parent->camera ) - 1 );
+
+ cgltf_bool ok = cgltf_accessor_read_float( skin->inverse_bind_matrices, joint_idx, model.animation.joints[ joint_idx ].joint_to_bind.ptr(), 16 );
+ ASSERT( ok != 0 );
+
+ for( size_t i = 0; i < node->children_count; i++ ) {
+ AddJoint( skin, node->children[ i ], prev );
+ }
+}
+
+static void LoadSkin( const cgltf_skin * skin ) {
model.animation.joints = ( AnimationReel::Joint * ) malloc( sizeof( AnimationReel::Joint ) * skin->joints_count );
+ model.animation.num_joints = skin->joints_count;
// we want to find the root of the subtree representing the skeleton
// mark all subtree nodes and then walk up the tree to find the root
for( size_t i = 0; i < skin->joints_count; i++ ) {
- ASSERT( skin->joints[ i ]->camera == NULL );
- skin->joints[ i ]->camera = ( cgltf_camera * ) 1;
+ skin->joints[ i ]->camera = ( cgltf_camera * ) ( i + 1 );
}
- const cgltf_node * root = skin->joints[ 0 ];
- while( root->parent != NULL && root->parent->camera == NULL ) {
+ cgltf_node * root = skin->joints[ 0 ];
+ while( root->parent != NULL && root->parent->camera != NULL ) {
root = root->parent;
}
- // todo1;
- // find root of skeleton, not necessarily root of hierarchy
- // bake transforms from root of skeleton to root of hierarchy into root joint
- // depth first fill in joints from node hierarchy to get rid of the ordering assert
- for( size_t i = 0; i < skin->joints_count; i++ ) {
- printf( "joint %zu: %p\n", i, skin->joints[ i ] );
-
- skin->joints[ i ]->camera = ( cgltf_camera * ) ( i + 1 );
-
- if( i == 0 ) {
- model.animation.joints[ i ].parent = 0;
- }
- else {
- model.animation.joints[ i ].parent = u8( uintptr_t( skin->joints[ i ]->parent->camera ) - 1 );
- ASSERT( model.animation.joints[ i ].parent < i );
- }
- cgltf_node_transform_local( skin->joints[ i ], &model.animation.joints[ i ].node_transform.col0.x );
- cgltf_bool ok = cgltf_accessor_read_float( skin->inverse_bind_matrices, i, &model.animation.joints[ i ].joint_to_bind.col0.x, 16 );
- ASSERT( ok != 0 );
- }
-
- model.animation.num_joints = skin->joints_count;
+ u8 * prev_ptr = &model.animation.root_joint;
+ AddJoint( skin, root, &prev_ptr );
}
const char * pathtype( cgltf_animation_path_type type ) {
@@ -220,60 +222,71 @@ const char * pathtype( cgltf_animation_path_type type ) {
return "invalid";
}
+template< typename T >
+static void LoadChannel( const cgltf_animation_channel * chan, AnimationReel::Channel< T > * out_channel ) {
+ constexpr size_t lanes = sizeof( T ) / sizeof( float );
+ size_t n = chan->sampler->input->count;
+
+ float * memory = ( float * ) malloc( n * ( lanes + 1 ) * sizeof( float ) );
+ out_channel->times = memory;
+ out_channel->samples = ( T * ) ( memory + n );
+ out_channel->num_samples = n;
+
+ for( size_t i = 0; i < n; i++ ) {
+ cgltf_bool ok = cgltf_accessor_read_float( chan->sampler->input, i, &out_channel->times[ i ], 1 );
+ ok = ok && cgltf_accessor_read_float( chan->sampler->output, i, out_channel->samples[ i ].ptr(), lanes );
+ ASSERT( ok != 0 );
+ }
+}
+
+static void LoadScaleChannel( const cgltf_animation_channel * chan, AnimationReel::Channel< float > * out_channel ) {
+ size_t n = chan->sampler->input->count;
+
+ float * memory = ( float * ) malloc( n * 2 * sizeof( float ) );
+ out_channel->times = memory;
+ out_channel->samples = memory + n;
+ out_channel->num_samples = n;
+
+ for( size_t i = 0; i < n; i++ ) {
+ cgltf_bool ok = cgltf_accessor_read_float( chan->sampler->input, i, &out_channel->times[ i ], 1 );
+ ASSERT( ok != 0 );
+
+ float scale[ 3 ];
+ ok = ok && cgltf_accessor_read_float( chan->sampler->output, i, scale, 3 );
+ ASSERT( ok != 0 );
+
+ ASSERT( fabsf( scale[ 0 ] / scale[ 1 ] - 1.0f ) < 0.001f );
+ ASSERT( fabsf( scale[ 0 ] / scale[ 2 ] - 1.0f ) < 0.001f );
+
+ out_channel->samples[ i ] = scale[ 0 ];
+ }
+}
+
static void LoadAnimationReel( MemoryArena * arena, const cgltf_animation * animation ) {
ggprint( "LoadAnimationReel\n" );
+
for( size_t i = 0; i < animation->channels_count; i++ ) {
const cgltf_animation_channel * chan = &animation->channels[ i ];
- const cgltf_animation_sampler * sampler = chan->sampler;
-
- ggprint( "channel {} {}\n", i, pathtype( chan->target_path ) );
ASSERT( chan->target_node->camera != NULL );
u8 joint_idx = u8( uintptr_t( chan->target_node->camera ) - 1 );
- ggprint( "interpolation {}\n", sampler->interpolation );
- const cgltf_accessor * samples = sampler->output;
-
- if( model.animation.samples == NULL ) {
- model.animation.num_samples = samples->count;
- model.animation.samples = ( AnimationReel::Sample * ) malloc( sizeof( AnimationReel::Sample * ) * model.animation.num_samples );
-
- for( u32 j = 0; j < model.animation.num_samples; j++ ) {
- model.animation.samples[ j ].joint_poses = ( SQT * ) malloc( sizeof( SQT ) * model.animation.num_joints );
- }
+ if( chan->target_path == cgltf_animation_path_type_translation ) {
+ LoadChannel( chan, &model.animation.joints[ joint_idx ].translations );
}
- else {
- ASSERT( samples->count == model.animation.num_samples );
+ else if( chan->target_path == cgltf_animation_path_type_rotation ) {
+ LoadChannel( chan, &model.animation.joints[ joint_idx ].rotations );
}
-
- for( size_t j = 0; j < samples->count; j++ ) {
- if( chan->target_path == cgltf_animation_path_type_translation ) {
- cgltf_bool ok = cgltf_accessor_read_float( samples, j, &model.animation.samples[ j ].joint_poses[ joint_idx ].translation.x, 3 );
- ASSERT( ok != 0 );
- }
- else if( chan->target_path == cgltf_animation_path_type_rotation ) {
- cgltf_bool ok = cgltf_accessor_read_float( samples, j, &model.animation.samples[ j ].joint_poses[ joint_idx ].rotation.x, 4 );
- ASSERT( ok != 0 );
- }
- else if( chan->target_path == cgltf_animation_path_type_scale ) {
- float scale[ 3 ];
- cgltf_bool ok = cgltf_accessor_read_float( samples, j, scale, 3 );
- ASSERT( ok != 0 );
-
- ASSERT( fabsf( scale[ 0 ] / scale[ 1 ] - 1.0f ) < 0.001 );
- ASSERT( fabsf( scale[ 0 ] / scale[ 2 ] - 1.0f ) < 0.001 );
-
- model.animation.samples[ j ].joint_poses[ joint_idx ].scale = scale[ 0 ];
- }
- else {
- FATAL( "bad path type" );
- }
+ else if( chan->target_path == cgltf_animation_path_type_scale ) {
+ LoadScaleChannel( chan, &model.animation.joints[ joint_idx ].scales );
+ }
+ else {
+ FATAL( "bad path type" );
}
}
- CLIP.first_sample = 0;
- CLIP.num_frames = 2;
- CLIP.fps = 1;
+ CLIP.start_time = 0;
+ CLIP.duration = 2;
CLIP.loop = true;
model.animation.clips = &CLIP;
@@ -297,7 +310,7 @@ static void LoadNode( MemoryArena * arena, const cgltf_node * node, bool animate
0, 0, 0, 1
);
- cgltf_node_transform_local( node, &transform.col0.x );
+ cgltf_node_transform_local( node, transform.ptr() );
// transform = y_up_to_z_up * transform;
}
@@ -374,7 +387,7 @@ GAME_INIT( game_init ) {
cgltf_options options = { };
cgltf_data * data;
- if( cgltf_parse_file( &options, "RiggedFigure.glb", &data ) != cgltf_result_success )
+ if( cgltf_parse_file( &options, "CesiumMan.glb", &data ) != cgltf_result_success )
FATAL( "cgltf_parse_file" );
if( cgltf_load_buffers( &options, data, "./" ) != cgltf_result_success )
FATAL( "cgltf_load_buffers" );
@@ -384,6 +397,7 @@ GAME_INIT( game_init ) {
FATAL( "cgltf_validate" );
ASSERT( data->animations_count <= 1 );
+ ASSERT( data->cameras_count == 0 );
model.num_meshes = 0;
@@ -454,6 +468,7 @@ GAME_FRAME( game_frame ) {
render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
render_state.uniforms[ UNIFORMS_JOINT_POSES ] = joints_uniforms;
render_state.textures[ 0 ] = renderer_blue_noise();
+ render_state.wireframe = input->keys[ KEY_M ];
for( u32 i = 0; i < model.num_meshes; i++ ) {
renderer_draw_mesh( model.meshes[ i ], render_state );
@@ -479,10 +494,10 @@ GAME_FRAME( game_frame ) {
for( u32 i = 0; i < model.animation.num_joints; i++ ) {
v3 pos = ( joint_matrices[ i ] * v4( 0, 0, 0, 1 ) ).xyz();
v3 parent_pos = ( joint_matrices[ model.animation.joints[ i ].parent ] * v4( 0, 0, 0, 1 ) ).xyz();
- v3 forward = ( joint_matrices[ i ] * v4( 0, 1, 0, 0 ) ).xyz();
immediate_sphere( pos, 0.025, v4( 1 ) );
- immediate_arrow( pos, forward, 0.2, v4( 0.5, 0.5, 0.5, 1 ) );
+ immediate_arrow( pos, joint_matrices[ i ].col1.xyz(), 0.2, v4( 0.5, 0.5, 0.5, 1 ) );
+ immediate_arrow( pos, joint_matrices[ i ].col0.xyz(), 0.2, v4( 0, 0, 1, 1 ) );
if( i != 0 )
immediate_arrow( pos, ( parent_pos - pos ), 0.1, v4( 1, 0, 0, 1 ) );
}
diff --git a/linear_algebra.h b/linear_algebra.h
@@ -130,6 +130,8 @@ struct v3 {
}
v2 xy() const { return v2( x, y ); }
+
+ float * ptr() { return &x; }
};
// AUTOVISITOR
@@ -231,6 +233,8 @@ struct ALIGNTO_SSE v4 {
v2 xy() const { return v2( x, y ); }
v3 xyz() const { return v3( x, y, z ); }
+
+ float * ptr() { return &x; }
};
// AUTOVISITOR
@@ -262,6 +266,8 @@ struct ALIGNTO_SSE m4 {
v4 row1() const { return v4( col0.y, col1.y, col2.y, col3.y ); }
v4 row2() const { return v4( col0.z, col1.z, col2.z, col3.z ); }
v4 row3() const { return v4( col0.w, col1.w, col2.w, col3.w ); }
+
+ float * ptr() { return col0.ptr(); }
};
forceinline m3::m3( const m4 & m ) {
@@ -282,6 +288,8 @@ struct quat {
z = c;
w = d;
}
+
+ float * ptr() { return &x; }
};
// TODO: v3x4, v4x4, simd matrices?