From 53c04d573b0fd95c962f3340c02b2caf8e447355 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Sun, 2 Jul 2023 19:42:31 -0700 Subject: [PATCH] Export animations from blender, render correctly with small # of bones! --- art/Exporter.py | 64 ++++++++++++++++++++++++++++++++-------------- art/art.blend | 4 +-- main.c | 67 +++++++++++++++++++++++++++++++++++++------------ 3 files changed, 98 insertions(+), 37 deletions(-) diff --git a/art/Exporter.py b/art/Exporter.py index ad65e4b..2383689 100644 --- a/art/Exporter.py +++ b/art/Exporter.py @@ -89,7 +89,7 @@ for o in D.objects: o = o.parent object_transform_info = (mesh_name, mapping @ o.location, o.rotation_euler, o.scale) if o.users_collection[0].name == 'Level': - assert(False, "Cannot put armatures in the level. The level is for static placed meshes. For dynamic entities, you put them outside of the level collection, their entity kind is encoded, and the game code decides how to draw them") + assert False, "Cannot put armatures in the level. The level is for static placed meshes. For dynamic entities, you put them outside of the level collection, their entity kind is encoded, and the game code decides how to draw them" else: placed_entities.append((mesh_object.name,) + object_transform_info) armature_name = o.data.name @@ -114,7 +114,7 @@ for o in D.objects: parent_index = i break if parent_index == -1: - assert(false, f"Couldn't find parent of bone {b}") + assert False, f"Couldn't find parent of bone {b}" print(f"Parent of bone {b.name} is index {parent_index} in list {bones_in_armature}") write_i32(f, parent_index) @@ -123,26 +123,52 @@ for o in D.objects: write_f32(f, b.length) # write the pose information - write_u64(f, len(o.pose.bones)) - for pose_bone in o.pose.bones: + + # it's very important that the array of pose bones contains the same amount of bones + # as there are in the edit bones. Because the edit bones are exported, etc etc. Cowabunga! + assert(len(o.pose.bones) == len(bones_in_armature)) - parent_space_pose = None + armature = o + for animation in bpy.data.actions: + armature.animation_data.action = animation + startFrame = int(animation.frame_range.x) + endFrame = int(animation.frame_range.y) + total_frames = (endFrame - startFrame) + 1 # the end frame is inclusive + print(f"Exporting animation {animation.name} with {total_frames} frames") - if pose_bone.parent: - parent_space_pose = pose_bone.parent.matrix.inverted() @ pose_bone.matrix - else: - parent_space_pose = mapping @ pose_bone.matrix - #parent_space_pose = pose_bone.matrix - print("parent_space_pose of the bone with no parent:") - print(parent_space_pose) + write_u64(f, total_frames) + + time_per_anim_frame = 1.0 / float(bpy.context.scene.render.fps) + for frame in range(startFrame, endFrame+1): + time_through_this_frame_occurs_at = (frame - startFrame) * time_per_anim_frame + bpy.context.scene.frame_set(frame) + + write_f32(f, time_through_this_frame_occurs_at) + for pose_bone_i in range(len(o.pose.bones)): + pose_bone = o.pose.bones[pose_bone_i] + + # in the engine, it's assumed that the poses are in the same order as the bones + # they're referring to. This checks that that is the case. + assert(pose_bone.bone == bones_in_armature[pose_bone_i]) + + parent_space_pose = None + + if pose_bone.parent: + parent_space_pose = pose_bone.parent.matrix.inverted() @ pose_bone.matrix + else: + parent_space_pose = mapping @ pose_bone.matrix + #parent_space_pose = pose_bone.matrix + #print("parent_space_pose of the bone with no parent:") + #print(parent_space_pose) - #parent_space_pose = mapping @ pose_bone.matrix - translation = parent_space_pose.to_translation() - rotation = parent_space_pose.to_quaternion() - scale = parent_space_pose.to_scale() - write_v3(f, translation) - write_quat(f, rotation) - write_v3(f, scale) + #parent_space_pose = mapping @ pose_bone.matrix + translation = parent_space_pose.to_translation() + rotation = parent_space_pose.to_quaternion() + scale = parent_space_pose.to_scale() + + write_v3(f, translation) + write_quat(f, rotation) + write_v3(f, scale) # write the mesh data for the armature bm = bmesh.new() diff --git a/art/art.blend b/art/art.blend index 64f6f62..e973f7b 100644 --- a/art/art.blend +++ b/art/art.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a35534a95be924967140ae58b67752a24ec2b0f333c357149a5890dfed4c879 -size 13201896 +oid sha256:84e78be53e858b252b6db329c4e66ca681bb9e35e9f0b1be0fad1c0bdd9df83c +size 13211972 diff --git a/main.c b/main.c index f71507a..084531e 100644 --- a/main.c +++ b/main.c @@ -837,13 +837,15 @@ typedef struct Mesh typedef struct PoseBone { + float time; // time through animation this pose occurs at Mat4 parent_space_pose; } PoseBone; typedef struct Bone { struct Bone *parent; - PoseBone pose_bone; + PoseBone *anim_poses; + MD_u64 anim_poses_length; Mat4 matrix_local; Mat4 inverse_model_space_pos; float length; @@ -1014,20 +1016,31 @@ Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armat } } - MD_u64 poses_length; - ser_MD_u64(&ser, &poses_length); + MD_u64 frames_in_anim; + ser_MD_u64(&ser, &frames_in_anim); + Log("There are %llu animation frames\n", frames_in_anim); - assert(poses_length == to_return.bones_length); - for(MD_u64 i = 0; i < poses_length; i++) + for(MD_u64 i = 0; i < to_return.bones_length; i++) { - PoseBone *next_pose_bone = &to_return.bones[i].pose_bone; + to_return.bones[i].anim_poses = MD_PushArray(arena, PoseBone, frames_in_anim); + to_return.bones[i].anim_poses_length = frames_in_anim; + } - Transform t; - ser_Vec3(&ser, &t.offset); - ser_Quat(&ser, &t.rotation); - ser_Vec3(&ser, &t.scale); + for(MD_u64 anim_i = 0; anim_i < frames_in_anim; anim_i++) + { + float time_through; + ser_float(&ser, &time_through); + for(MD_u64 pose_bone_i = 0; pose_bone_i < to_return.bones_length; pose_bone_i++) + { + PoseBone *next_pose_bone = &to_return.bones[pose_bone_i].anim_poses[anim_i]; + Transform t; + ser_Vec3(&ser, &t.offset); + ser_Quat(&ser, &t.rotation); + ser_Vec3(&ser, &t.scale); - next_pose_bone->parent_space_pose = transform_to_mat(t); + next_pose_bone->time = time_through; + next_pose_bone->parent_space_pose = transform_to_mat(t); + } } ser_MD_u64(&ser, &to_return.vertices_length); @@ -2641,7 +2654,27 @@ void draw_placed(Mat4 view, Mat4 projection, PlacedMesh *cur) sg_draw(0, (int)drawing->num_vertices, 1); } -void draw_armature(Mat4 view, Mat4 projection, Transform t, Armature *armature) +Mat4 get_transform_along_time(Bone *bone, float time) +{ + float total_anim_time = bone->anim_poses[bone->anim_poses_length - 1].time; + assert(total_anim_time > 0.0f); + time = fmodf(time, total_anim_time); + for(MD_u64 i = 0; i < bone->anim_poses_length - 1; i++) + { + if(bone->anim_poses[i].time <= time && time <= bone->anim_poses[i + 1].time) + { + float gap_btwn_keyframes = bone->anim_poses[i + 1].time - bone->anim_poses[i].time; + float t = (time - bone->anim_poses[i].time)/gap_btwn_keyframes; + assert(t >= 0.0f); + assert(t <= 1.0f); + return bone->anim_poses[i].parent_space_pose; + } + } + assert(false); + return M4D(1.0f); +} + +void draw_armature(Mat4 view, Mat4 projection, Transform t, Armature *armature, float elapsed_time) { state.threedee_bind.vertex_buffers[0] = armature->loaded_buffer; sg_apply_pipeline(state.armature_pip); @@ -2662,7 +2695,8 @@ void draw_armature(Mat4 view, Mat4 projection, Transform t, Armature *armature) 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->pose_bone.parent_space_pose, final); + //final = MulM4(cur_in_hierarchy->anim_poses[0].parent_space_pose, final); + final = MulM4(get_transform_along_time(cur_in_hierarchy, elapsed_time), final); } memcpy(params.bones[i], (float*)&final, sizeof(final)); @@ -4708,7 +4742,7 @@ void frame(void) // debug draw armature for(MD_u64 i = 0; i < armature.bones_length; i++) { - PoseBone *cur_pose_bone = &armature.bones[i].pose_bone; + PoseBone *cur_pose_bone = &armature.bones[i].anim_poses[0]; Bone *cur = &armature.bones[i]; Vec3 offset = V3(1.5, 0, 5); @@ -4749,7 +4783,8 @@ void frame(void) 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) { - final_mat = MulM4(cur_in_hierarchy->pose_bone.parent_space_pose, final_mat); + final_mat = MulM4(get_transform_along_time(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 @@ -4798,7 +4833,7 @@ void frame(void) Transform draw_with = entity_transform(it); if(it->npc_kind == NPC_SimpleWorm) { - draw_armature(view, projection, draw_with, &armature); + draw_armature(view, projection, draw_with, &armature, (float)elapsed_time); } else {