Animation blending, fix facing direction of model

main
parent fcd7b99f43
commit 563a2a69b0

BIN
art/art.blend (Stored with Git LFS)

Binary file not shown.

395
main.c

@ -955,7 +955,7 @@ Mat4 blender_to_handmade_mat(BlenderMat b)
memcpy(&to_return, &b, sizeof(to_return));
return TransposeM4(to_return);
}
Mat4 transform_to_mat(Transform t)
Mat4 transform_to_matrix(Transform t)
{
Mat4 to_return = M4D(1.0f);
@ -973,19 +973,35 @@ Transform lerp_transforms(Transform from, float t, Transform to)
.scale = LerpV3(from.scale, t, to.scale),
};
}
Transform default_transform()
{
return (Transform){.rotation = Make_Q(0,0,0,1)};
}
typedef struct
{
MD_String8 name;
Bone *bones;
MD_u64 bones_length;
Animation *animations;
MD_u64 animations_length;
// when set, blends to that animation next time this armature is processed for that
MD_String8 go_to_animation;
Transform *current_poses; // allocated on loading of the armature
MD_String8 target_animation; // CANNOT be null.
float animation_blend_t; // [0,1] how much between current_animation and target_animation. Once >= 1, current = target and target = null.
Transform *anim_blended_poses; // recalculated once per frame depending on above parameters, which at the same code location are calculated. Is `bones_length` long
ArmatureVertex *vertices;
MD_u64 vertices_length;
sg_buffer loaded_buffer;
sg_image bones_texture;
MD_String8 name;
int bones_texture_width;
int bones_texture_height;
} Armature;
@ -1039,6 +1055,9 @@ Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armat
}
}
to_return.current_poses = MD_PushArray(arena, Transform, to_return.bones_length);
to_return.anim_blended_poses = MD_PushArray(arena, Transform, to_return.bones_length);
ser_MD_u64(&ser, &to_return.animations_length);
Log("Armature %.*s has %llu animations\n", MD_S8VArg(armature_name), to_return.animations_length);
to_return.animations = MD_PushArray(arena, Animation, to_return.animations_length);
@ -2720,7 +2739,7 @@ void draw_shadow(Mat4 view, Mat4 projection, PlacedMesh *cur)
bindings.vertex_buffers[0] = drawing->loaded_buffer;
sg_apply_bindings(&bindings);
Mat4 model = transform_to_mat(cur->t);
Mat4 model = transform_to_matrix(cur->t);
shadow_mapper_vs_params_t vs_params = {0};
memcpy(vs_params.model, (float*)&model, sizeof(model));
@ -2753,7 +2772,7 @@ void draw_placed(Mat4 view, Mat4 projection, Mat4 light_matrix, PlacedMesh *cur)
state.threedee_bind.vertex_buffers[0] = drawing->loaded_buffer;
sg_apply_bindings(&state.threedee_bind);
Mat4 model = transform_to_mat(cur->t);
Mat4 model = transform_to_matrix(cur->t);
threedee_vs_params_t vs_params = {0};
memcpy(vs_params.model, (float*)&model, sizeof(model));
@ -2773,8 +2792,38 @@ void draw_placed(Mat4 view, Mat4 projection, Mat4 light_matrix, PlacedMesh *cur)
sg_draw(0, (int)drawing->num_vertices, 1);
}
Mat4 get_animated_bone_transform(AnimationTrack *track, Bone *bone, float time)
// if it's an invalid anim name, it just returns the idle animation
Animation *get_anim_by_name(Armature *armature, MD_String8 anim_name)
{
for(MD_u64 i = 0; i < armature->animations_length; i++)
{
if(MD_S8Match(armature->animations[i].name, anim_name, 0))
{
return &armature->animations[i];
}
}
if(anim_name.size > 0)
{
Log("No animation found '%.*s'\n", MD_S8VArg(anim_name));
}
for(MD_u64 i = 0; i < armature->animations_length; i++)
{
if(MD_S8Match(armature->animations[i].name, MD_S8Lit("Idle"), 0))
{
return &armature->animations[i];
}
}
assert(false); // no animation named 'Idle'
return 0;
}
// you can pass a time greater than the animation length, it's fmodded to wrap no matter what.
Transform get_animated_bone_transform(AnimationTrack *track, Bone *bone, float time)
{
assert(track);
float total_anim_time = track->poses[track->poses_length - 1].time;
assert(total_anim_time > 0.0f);
time = fmodf(time, total_anim_time);
@ -2788,11 +2837,11 @@ Mat4 get_animated_bone_transform(AnimationTrack *track, Bone *bone, float time)
float t = (time - from.time)/gap_btwn_keyframes;
assert(t >= 0.0f);
assert(t <= 1.0f);
return transform_to_mat(lerp_transforms(from.parent_space_pose, t, to.parent_space_pose));
return lerp_transforms(from.parent_space_pose, t, to.parent_space_pose);
}
}
assert(false);
return M4D(1.0f);
return default_transform();
}
typedef struct
@ -2847,80 +2896,6 @@ float decode_normalized_float32(PixelData encoded)
}
void draw_armature(Mat4 view, Mat4 projection, Transform t, Armature *armature, float elapsed_time)
{
MD_ArenaTemp scratch = MD_GetScratch(0, 0);
sg_apply_pipeline(state.armature_pip);
Mat4 model = transform_to_mat(t);
armature_vs_params_t params = {0};
memcpy(params.model, (float*)&model, sizeof(model));
memcpy(params.view, (float*)&view, sizeof(view));
memcpy(params.projection, (float*)&projection, sizeof(projection));
params.bones_tex_size[0] = (float)armature->bones_texture_width;
params.bones_tex_size[1] = (float)armature->bones_texture_height;
int bones_tex_size = 4 * armature->bones_texture_width * armature->bones_texture_height;
MD_u8 *bones_tex = MD_ArenaPush(scratch.arena, bones_tex_size);
for(MD_u64 i = 0; i < armature->bones_length; i++)
{
Bone *cur = &armature->bones[i];
Mat4 final = M4D(1.0f);
final = MulM4(cur->inverse_model_space_pos, final);
for(Bone *cur_in_hierarchy = cur; cur_in_hierarchy; cur_in_hierarchy = cur_in_hierarchy->parent)
{
//final = MulM4(cur_in_hierarchy->anim_poses[0].parent_space_pose, final);
int bone_index = (int)(cur_in_hierarchy - armature->bones);
final = MulM4(get_animated_bone_transform(&armature->animations[0].tracks[bone_index], cur_in_hierarchy, elapsed_time), final);
}
for(int col = 0; col < 4; col++)
{
Vec4 to_upload = final.Columns[col];
int bytes_per_pixel = 4;
int bytes_per_column_of_mat = bytes_per_pixel * 4;
int bytes_per_row = bytes_per_pixel * armature->bones_texture_width;
for(int elem = 0; elem < 4; elem++)
{
float after_decoding = decode_normalized_float32(encode_normalized_float32(to_upload.Elements[elem]));
assert(fabsf(after_decoding - to_upload.Elements[elem]) < 0.01f);
}
memcpy(&bones_tex[bytes_per_column_of_mat*col + bytes_per_row*i + bytes_per_pixel*0], encode_normalized_float32(to_upload.Elements[0]).rgba, bytes_per_pixel);
memcpy(&bones_tex[bytes_per_column_of_mat*col + bytes_per_row*i + bytes_per_pixel*1], encode_normalized_float32(to_upload.Elements[1]).rgba, bytes_per_pixel);
memcpy(&bones_tex[bytes_per_column_of_mat*col + bytes_per_row*i + bytes_per_pixel*2], encode_normalized_float32(to_upload.Elements[2]).rgba, bytes_per_pixel);
memcpy(&bones_tex[bytes_per_column_of_mat*col + bytes_per_row*i + bytes_per_pixel*3], encode_normalized_float32(to_upload.Elements[3]).rgba, bytes_per_pixel);
}
}
sg_update_image(armature->bones_texture, &(sg_image_data){
.subimage[0][0] = (sg_range){bones_tex, bones_tex_size},
});
ARR_ITER(sg_image, state.threedee_bind.vs_images)
{
*it = (sg_image){0};
}
ARR_ITER(sg_image, state.threedee_bind.fs_images)
{
*it = (sg_image){0};
}
state.threedee_bind.vertex_buffers[0] = armature->loaded_buffer;
state.threedee_bind.vs_images[SLOT_armature_bones_tex] = armature->bones_texture;
state.threedee_bind.fs_images[SLOT_armature_tex] = image_gigatexture;
sg_apply_bindings(&state.threedee_bind);
sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_armature_vs_params, &SG_RANGE(params));
num_draw_calls += 1;
num_vertices += (int)armature->vertices_length;
sg_draw(0, (int)armature->vertices_length, 1);
MD_ReleaseScratch(scratch);
}
void audio_stream_callback(float *buffer, int num_frames, int num_channels)
@ -4019,6 +3994,108 @@ void dbgplanerect(AABB aabb)
dbgplaneline(q.ll, q.ul);
}
void draw_armature(Mat4 view, Mat4 projection, Transform t, Armature *armature)
{
MD_ArenaTemp scratch = MD_GetScratch(0, 0);
sg_apply_pipeline(state.armature_pip);
Mat4 model = transform_to_matrix(t);
armature_vs_params_t params = {0};
memcpy(params.model, (float*)&model, sizeof(model));
memcpy(params.view, (float*)&view, sizeof(view));
memcpy(params.projection, (float*)&projection, sizeof(projection));
params.bones_tex_size[0] = (float)armature->bones_texture_width;
params.bones_tex_size[1] = (float)armature->bones_texture_height;
int bones_tex_size = 4 * armature->bones_texture_width * armature->bones_texture_height;
MD_u8 *bones_tex = MD_ArenaPush(scratch.arena, bones_tex_size);
for(MD_u64 i = 0; i < armature->bones_length; i++)
{
Bone *cur = &armature->bones[i];
// for debug drawing
Vec3 from = MulM4V3(cur->matrix_local, V3(0,0,0));
Vec3 x = MulM4V3(cur->matrix_local, V3(cur->length,0,0));
Vec3 y = MulM4V3(cur->matrix_local, V3(0,cur->length,0));
Vec3 z = MulM4V3(cur->matrix_local, V3(0,0,cur->length));
Mat4 final = M4D(1.0f);
final = MulM4(cur->inverse_model_space_pos, final);
for(Bone *cur_in_hierarchy = cur; cur_in_hierarchy; cur_in_hierarchy = cur_in_hierarchy->parent)
{
//final = MulM4(cur_in_hierarchy->anim_poses[0].parent_space_pose, final);
int bone_index = (int)(cur_in_hierarchy - armature->bones);
//final = MulM4(get_animated_bone_transform(&armature->animations[0].tracks[bone_index], cur_in_hierarchy, elapsed_time), final);
final = MulM4(transform_to_matrix(armature->anim_blended_poses[bone_index]), final);
}
from = MulM4V3(final, from);
x = MulM4V3(final, x);
y = MulM4V3(final, y);
z = MulM4V3(final, z);
Mat4 transform_matrix = transform_to_matrix(t);
from = MulM4V3(transform_matrix, from);
x = MulM4V3(transform_matrix, x);
y = MulM4V3(transform_matrix, y);
z = MulM4V3(transform_matrix, z);
dbgcol(LIGHTBLUE)
dbgsquare3d(y);
dbgcol(RED)
dbg3dline(from, x);
dbgcol(GREEN)
dbg3dline(from, y);
dbgcol(BLUE)
dbg3dline(from, z);
for(int col = 0; col < 4; col++)
{
Vec4 to_upload = final.Columns[col];
int bytes_per_pixel = 4;
int bytes_per_column_of_mat = bytes_per_pixel * 4;
int bytes_per_row = bytes_per_pixel * armature->bones_texture_width;
for(int elem = 0; elem < 4; elem++)
{
float after_decoding = decode_normalized_float32(encode_normalized_float32(to_upload.Elements[elem]));
assert(fabsf(after_decoding - to_upload.Elements[elem]) < 0.01f);
}
memcpy(&bones_tex[bytes_per_column_of_mat*col + bytes_per_row*i + bytes_per_pixel*0], encode_normalized_float32(to_upload.Elements[0]).rgba, bytes_per_pixel);
memcpy(&bones_tex[bytes_per_column_of_mat*col + bytes_per_row*i + bytes_per_pixel*1], encode_normalized_float32(to_upload.Elements[1]).rgba, bytes_per_pixel);
memcpy(&bones_tex[bytes_per_column_of_mat*col + bytes_per_row*i + bytes_per_pixel*2], encode_normalized_float32(to_upload.Elements[2]).rgba, bytes_per_pixel);
memcpy(&bones_tex[bytes_per_column_of_mat*col + bytes_per_row*i + bytes_per_pixel*3], encode_normalized_float32(to_upload.Elements[3]).rgba, bytes_per_pixel);
}
}
sg_update_image(armature->bones_texture, &(sg_image_data){
.subimage[0][0] = (sg_range){bones_tex, bones_tex_size},
});
ARR_ITER(sg_image, state.threedee_bind.vs_images)
{
*it = (sg_image){0};
}
ARR_ITER(sg_image, state.threedee_bind.fs_images)
{
*it = (sg_image){0};
}
state.threedee_bind.vertex_buffers[0] = armature->loaded_buffer;
state.threedee_bind.vs_images[SLOT_armature_bones_tex] = armature->bones_texture;
state.threedee_bind.fs_images[SLOT_armature_tex] = image_gigatexture;
sg_apply_bindings(&state.threedee_bind);
sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_armature_vs_params, &SG_RANGE(params));
num_draw_calls += 1;
num_vertices += (int)armature->vertices_length;
sg_draw(0, (int)armature->vertices_length, 1);
MD_ReleaseScratch(scratch);
}
typedef struct TextParams
{
bool dry_run;
@ -4858,36 +4935,36 @@ Transform entity_transform(Entity *e)
}
void do_shadow_pass(Shadow_State* shadow_state, Mat4 shadow_view_matrix, Mat4 shadow_projection_matrix) {
sg_begin_pass(shadow_state->pass, &shadow_state->pass_action);
void do_shadow_pass(Shadow_State* shadow_state, Mat4 shadow_view_matrix, Mat4 shadow_projection_matrix)
{
sg_begin_pass(shadow_state->pass, &shadow_state->pass_action);
sg_apply_pipeline(shadow_state->pip);
sg_apply_pipeline(shadow_state->pip);
//We use the texture, just in case we want to do alpha cards. I.e. we need to test alpha for leaf quads etc.
state.threedee_bind.fs_images[SLOT_threedee_tex] = image_gigatexture;
state.threedee_bind.fs_images[SLOT_threedee_shadow_map].id = 0;
for(PlacedMesh *cur = level_threedee.placed_mesh_list; cur; cur = cur->next)
{
draw_shadow(shadow_view_matrix, shadow_projection_matrix, cur);
}
for(PlacedMesh *cur = level_threedee.placed_mesh_list; cur; cur = cur->next)
{
draw_shadow(shadow_view_matrix, shadow_projection_matrix, cur);
}
ENTITIES_ITER(gs.entities)
{
if(it->is_npc || it->is_character)
{
Transform draw_with = entity_transform(it);
ENTITIES_ITER(gs.entities)
{
if(it->is_npc || it->is_character)
{
Transform draw_with = entity_transform(it);
if(it->npc_kind == NPC_SimpleWorm)
{
// draw_armature_shadow(shadow_view_matrix, shadow_projection_matrix, draw_with, &armature, (float)elapsed_time);
} else {
draw_shadow(shadow_view_matrix, shadow_projection_matrix, &(PlacedMesh){.draw_with = &mesh_player, .t = draw_with, });
}
}
}
}
}
sg_end_pass();
sg_end_pass();
}
@ -5156,6 +5233,50 @@ void frame(void)
movement = NormV2(movement);
}
// progress the animation, then blend the two animations if necessary, and finally
// output into anim_blended_poses
{
Armature *cur = &armature;
if(cur->go_to_animation.size > 0)
{
if(MD_S8Match(cur->go_to_animation, cur->target_animation, 0))
{
}
else
{
memcpy(cur->current_poses, cur->anim_blended_poses, cur->bones_length * sizeof(*cur->current_poses));
cur->target_animation = cur->go_to_animation;
cur->animation_blend_t = 0.0f;
cur->go_to_animation = (MD_String8){0};
}
}
if(cur->animation_blend_t < 1.0f)
{
cur->animation_blend_t += dt / ANIMATION_BLEND_TIME;
Animation *to_anim = get_anim_by_name(cur, cur->target_animation);
assert(to_anim);
for(MD_u64 i = 0; i < cur->bones_length; i++)
{
Transform *output_transform = &cur->anim_blended_poses[i];
Transform from_transform = cur->current_poses[i];
Transform to_transform = get_animated_bone_transform(&to_anim->tracks[i], &cur->bones[i], (float)elapsed_time);
*output_transform = lerp_transforms(from_transform, cur->animation_blend_t, to_transform);
}
}
else
{
Animation *cur_anim = get_anim_by_name(cur, cur->target_animation);
for(MD_u64 i = 0; i < cur->bones_length; i++)
{
cur->anim_blended_poses[i] = get_animated_bone_transform(&cur_anim->tracks[i], &cur->bones[i], (float)elapsed_time);
}
}
}
float spin_factor = 0.5f;
float t = (float)elapsed_time * spin_factor;
@ -5163,7 +5284,7 @@ void frame(void)
float x = cosf(t);
float z = sinf(t);
Vec3 light_dir = NormV3(V3(x, -0.5, z));
Vec3 light_dir = NormV3(V3(x, -0.5f, z));
Shadow_Volume_Params svp = calculate_shadow_volume_params(light_dir);
@ -5201,84 +5322,23 @@ void frame(void)
}
projection = Perspective_RH_NO(FIELD_OF_VIEW, screen_size().x / screen_size().y, NEAR_PLANE_DISTANCE, FAR_PLANE_DISTANCE);
// debug draw armature
for(MD_u64 i = 0; i < armature.bones_length; i++)
{
Bone *cur = &armature.bones[i];
Vec3 offset = V3(1.5, 0, 5);
Vec3 from = MulM4V3(cur->matrix_local, V3(0,0,0));
Vec3 x = MulM4V3(cur->matrix_local, V3(cur->length,0,0));
Vec3 y = MulM4V3(cur->matrix_local, V3(0,cur->length,0));
Vec3 z = MulM4V3(cur->matrix_local, V3(0,0,cur->length));
Vec3 dot = MulM4V3(cur->matrix_local, V3(cur->length,0,cur->length));
// from, x, y, and z are like vertex points. They are model-space
// points *around* the bones they should be influenced by. Now we
// need to transform them according to how much the pose bones
// have moved and in the way they moved.
Mat4 final_mat = M4D(1.0f);
final_mat = MulM4(cur->inverse_model_space_pos, final_mat);
for(Bone *cur_in_hierarchy = cur; cur_in_hierarchy; cur_in_hierarchy = cur_in_hierarchy->parent)
{
int bone_index = (int)(cur_in_hierarchy - armature.bones);
final_mat = MulM4(get_animated_bone_transform(&armature.animations[0].tracks[bone_index], cur_in_hierarchy, (float)elapsed_time), final_mat);
//final_mat = MulM4(cur_in_hierarchy->anim_poses[0].parent_space_pose, final_mat);
}
// uncommenting this skips the pose transform, showing the debug skeleton
// as if it were in "edit mode" in blender
//final_mat = M4D(1.0f);
from = MulM4V3(final_mat, from);
x = MulM4V3(final_mat, x);
y = MulM4V3(final_mat, y);
z = MulM4V3(final_mat, z);
dot = MulM4V3(final_mat, dot);
from = AddV3(from, offset);
x = AddV3(x, offset);
y = AddV3(y, offset);
z = AddV3(z, offset);
dot = AddV3(dot, offset);
dbgcol(LIGHTBLUE)
dbgsquare3d(y);
dbgcol(RED)
dbg3dline(from, x);
dbgcol(GREEN)
dbg3dline(from, y);
dbgcol(BLUE)
dbg3dline(from, z);
dbgcol(YELLOW)
dbg3dline(from, dot);
dbgcol(PINK)
dbgsquare3d(dot);
}
for(PlacedMesh *cur = level_threedee.placed_mesh_list; cur; cur = cur->next)
{
draw_placed(view, projection, light_space_matrix, cur);
}
ENTITIES_ITER(gs.entities)
{
if(it->is_npc || it->is_character)
{
Transform draw_with = entity_transform(it);
if(it->npc_kind == NPC_SimpleWorm)
if(it->npc_kind == NPC_Player)
{
draw_armature(view, projection, draw_with, &armature, (float)elapsed_time);
draw_armature(view, projection, draw_with, &armature);
}
else
{
draw_placed(view, projection, light_space_matrix, &(PlacedMesh){.draw_with = &mesh_player, .t = draw_with, });
draw_placed(view, projection, light_space_matrix, &(PlacedMesh){.draw_with = &mesh_player, .t = draw_with,});
}
}
}
@ -6298,6 +6358,15 @@ void frame(void)
{
Vec2 target_vel = { 0 };
if(gs.player->state == CHARACTER_WALKING)
{
armature.go_to_animation = MD_S8Lit("Walking");
}
else
{
armature.go_to_animation = MD_S8Lit("Idle");
}
if (gs.player->state == CHARACTER_WALKING)
{
speed = PLAYER_SPEED;

@ -35,6 +35,7 @@
#define SENTENCE_CONST(txt) { .data = txt, .cur_index = sizeof(txt) }
#define SENTENCE_CONST_CAST(txt) (Sentence)SENTENCE_CONST(txt)
#define ANIMATION_BLEND_TIME 0.1f
#define REMEMBERED_MEMORIES 32
#define REMEMBERED_ERRORS 6
@ -56,4 +57,4 @@
#define FIELD_OF_VIEW (PI32/4.0f)
#define NEAR_PLANE_DISTANCE (0.01f)
#define FAR_PLANE_DISTANCE (1000.0f)
#define SHADOW_MAP_DIMENSION (2048)
#define SHADOW_MAP_DIMENSION (2048)

Loading…
Cancel
Save