medfall

A super great game engine
Log | Files | Refs

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:
ACesiumMan.glb | 0
Mgltf.cc | 233++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mlinear_algebra.h | 8++++++++
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?