From cbce6c9a8166b71514530bac4b15a40e650e41ae Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Sat, 1 Jul 2023 22:55:27 -0700 Subject: [PATCH] Armatures are a separate kind of thing, that includes its mesh --- art/Exporter.py | 190 +++++++++++++++++++++++++++++------------------- art/art.blend | 4 +- main.c | 65 +++++++++++++++-- 3 files changed, 177 insertions(+), 82 deletions(-) diff --git a/art/Exporter.py b/art/Exporter.py index 67c59b0..3e255fe 100644 --- a/art/Exporter.py +++ b/art/Exporter.py @@ -18,6 +18,8 @@ if os.path.exists(bpy.path.abspath(f"//{EXPORT_DIRECTORY}")): shutil.rmtree(bpy.path.abspath(f"//{EXPORT_DIRECTORY}")) os.makedirs(bpy.path.abspath(f"//{EXPORT_DIRECTORY}")) +def write_b8(f, boolean: bool): + f.write(bytes(struct.pack("?", boolean))) def write_f32(f, number: float): f.write(bytes(struct.pack("f", number))) @@ -64,85 +66,71 @@ mapping = axis_conversion( ) mapping.resize_4x4() +# meshes can either be Meshes, or Armatures. Armatures contain all mesh data to draw it, and any anims it has + for o in D.objects: - if o.type == "ARMATURE": - armature_name = o.data.name - output_filepath = bpy.path.abspath(f"//{EXPORT_DIRECTORY}/{armature_name}.bin") - print(f"Exporting armature to {output_filepath}") - with open(output_filepath, "wb") as f: - bones_in_armature = [] - for b in o.data.bones: - bones_in_armature.append(b) - - # the inverse model space pos of the bones - write_u64(f, len(bones_in_armature)) - for b in bones_in_armature: - model_space_pose = mapping @ b.matrix_local - print(b.name) - print(b.matrix_local) - inverse_model_space_pose = (mapping @ b.matrix_local).inverted() - - write_4x4matrix(f, model_space_pose) - write_4x4matrix(f, inverse_model_space_pose) - write_f32(f, b.length) - - # write the pose information - write_u64(f, len(o.pose.bones)) - for pose_bone in o.pose.bones: - parent_index = -1 - if pose_bone.parent: - for i in range(len(bones_in_armature)): - if bones_in_armature[i] == pose_bone.parent.bone: - parent_index = i - break - if parent_index == -1: - assert(false, f"Couldn't find parent of bone {pose_bone}") - print(f"Parent of bone {pose_bone.name} is index {parent_index} in list {bones_in_armature}") - 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 - print("parent_space_pose of the bone with no parent:") - print(parent_space_pose) - - write_string(f, pose_bone.bone.name) - write_i32(f, parent_index) - #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) - - elif o.type == "MESH": - - mesh_name = o.to_mesh().name # use this over o.name so instanced objects which refer to the same mesh, both use the same serialized mesh. - - object_transform_info = (mesh_name, mapping @ o.location, o.rotation_euler, o.scale) - - if o.users_collection[0].name == 'Level' and mesh_name == "CollisionCube": - collision_cubes.append((o.location, o.dimensions)) - else: + if o.type == "MESH": + if o.parent and o.parent.type == "ARMATURE": + mesh_object = o + o = o.parent + object_transform_info = (mesh_name, mapping @ mesh_object.location, mesh_object.rotation_euler, mesh_object.scale) if o.users_collection[0].name == 'Level': - print(f"Object {o.name} has mesh name {o.to_mesh().name}") - assert(o.rotation_euler.order == 'XYZ') - level_object_data.append(object_transform_info) + 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") else: - placed_entities.append((o.name,) + object_transform_info) - - if mesh_name in saved_meshes: - continue - saved_meshes.add(mesh_name) - - assert(mesh_name != LEVEL_EXPORT_NAME) - output_filepath = bpy.path.abspath(f"//{EXPORT_DIRECTORY}/{mesh_name}.bin") - print(f"Exporting mesh to {output_filepath}") + placed_entities.append((mesh_object.name,) + object_transform_info) + armature_name = o.data.name + output_filepath = bpy.path.abspath(f"//{EXPORT_DIRECTORY}/{armature_name}.bin") + print(f"Exporting armature to {output_filepath}") with open(output_filepath, "wb") as f: + write_b8(f, True) + bones_in_armature = [] + for b in o.data.bones: + bones_in_armature.append(b) + + # the inverse model space pos of the bones + write_u64(f, len(bones_in_armature)) + for b in bones_in_armature: + model_space_pose = mapping @ b.matrix_local + inverse_model_space_pose = (mapping @ b.matrix_local).inverted() + + write_4x4matrix(f, model_space_pose) + write_4x4matrix(f, inverse_model_space_pose) + write_f32(f, b.length) + + # write the pose information + write_u64(f, len(o.pose.bones)) + for pose_bone in o.pose.bones: + parent_index = -1 + if pose_bone.parent: + for i in range(len(bones_in_armature)): + if bones_in_armature[i] == pose_bone.parent.bone: + parent_index = i + break + if parent_index == -1: + assert(false, f"Couldn't find parent of bone {pose_bone}") + print(f"Parent of bone {pose_bone.name} is index {parent_index} in list {bones_in_armature}") + 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 + print("parent_space_pose of the bone with no parent:") + print(parent_space_pose) + + write_string(f, pose_bone.bone.name) + write_i32(f, parent_index) + #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() - mesh = o.to_mesh() + mesh = mesh_object.to_mesh() bm.from_mesh(mesh) bmesh.ops.triangulate(bm, faces=bm.faces) bm.transform(mapping) @@ -171,6 +159,60 @@ for o in D.objects: write_f32(f, uv.y) print(f"Wrote {len(vertices)} vertices") + else: # if the parent type isn't an armature, i.e just a bog standard mesh + mesh_name = o.to_mesh().name # use this over o.name so instanced objects which refer to the same mesh, both use the same serialized mesh. + + object_transform_info = (mesh_name, mapping @ o.location, o.rotation_euler, o.scale) + + if o.users_collection[0].name == 'Level' and mesh_name == "CollisionCube": + collision_cubes.append((o.location, o.dimensions)) + else: + if o.users_collection[0].name == 'Level': + print(f"Object {o.name} has mesh name {o.to_mesh().name}") + assert(o.rotation_euler.order == 'XYZ') + level_object_data.append(object_transform_info) + else: + placed_entities.append((o.name,) + object_transform_info) + + if mesh_name in saved_meshes: + continue + saved_meshes.add(mesh_name) + + assert(mesh_name != LEVEL_EXPORT_NAME) + output_filepath = bpy.path.abspath(f"//{EXPORT_DIRECTORY}/{mesh_name}.bin") + print(f"Exporting mesh to {output_filepath}") + with open(output_filepath, "wb") as f: + write_b8(f, False) + bm = bmesh.new() + mesh = o.to_mesh() + bm.from_mesh(mesh) + bmesh.ops.triangulate(bm, faces=bm.faces) + bm.transform(mapping) + bm.to_mesh(mesh) + + + vertices = [] + + for polygon in mesh.polygons: + if len(polygon.loop_indices) == 3: + for loopIndex in polygon.loop_indices: + loop = mesh.loops[loopIndex] + position = mesh.vertices[loop.vertex_index].undeformed_co + uv = mesh.uv_layers.active.data[loop.index].uv + normal = loop.normal + + vertices.append((position, uv)) + + write_u64(f, len(vertices)) + for v_and_uv in vertices: + v, uv = v_and_uv + write_f32(f, v.x) + write_f32(f, v.y) + write_f32(f, v.z) + write_f32(f, uv.x) + write_f32(f, uv.y) + print(f"Wrote {len(vertices)} vertices") + with open(bpy.path.abspath(f"//{EXPORT_DIRECTORY}/{LEVEL_EXPORT_NAME}.bin"), "wb") as f: write_u64(f, len(level_object_data)) for o in level_object_data: diff --git a/art/art.blend b/art/art.blend index 4777d4f..a808e9b 100644 --- a/art/art.blend +++ b/art/art.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1f6422c992140c6ca7e59f88de11a4d150c7e43de42b83a9db1a0e8310c7841 -size 7201180 +oid sha256:8c67d82d883089b6ddc73ac444a7a215f7c53fab24d4ec99e7037ab99c76ce9a +size 7208696 diff --git a/main.c b/main.c index 228cca1..b586812 100644 --- a/main.c +++ b/main.c @@ -804,12 +804,20 @@ void ser_Quat(SerState *ser, Quat *q) ser_float(ser, &q->w); } +#pragma pack(1) typedef struct { Vec3 pos; Vec2 uv; // CANNOT have struct padding } Vertex; +#pragma pack(1) +typedef struct +{ + Vec3 position; + Vec2 uv; +} ArmatureVertex; + SER_MAKE_FOR_TYPE(Vertex); typedef struct Mesh @@ -874,6 +882,11 @@ Mesh load_mesh(MD_Arena *arena, MD_String8 binary_file, MD_String8 mesh_name) .serializing = false, }; Mesh out = {0}; + + bool is_armature; + ser_bool(&ser, &is_armature); + assert(!is_armature); + ser_MD_u64(&ser, &out.num_vertices); Log("Mesh %.*s has %llu vertices\n", MD_S8VArg(mesh_name), out.num_vertices); @@ -938,12 +951,17 @@ typedef struct PoseBone MD_String8 name; } PoseBone; + typedef struct { Bone *bones; MD_u64 bones_length; PoseBone *poses; MD_u64 poses_length; + ArmatureVertex *vertices; + MD_u64 vertices_length; + sg_buffer loaded_buffer; + MD_String8 name; } Armature; Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armature_name) @@ -957,9 +975,13 @@ Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armat .serializing = false, }; Armature to_return = {0}; + + bool is_armature; + ser_bool(&ser, &is_armature); + assert(is_armature); ser_MD_u64(&ser, &to_return.bones_length); - Log("Armature %.*s has %llu vertices\n", MD_S8VArg(armature_name), to_return.bones_length); + Log("Armature %.*s has %llu bones\n", MD_S8VArg(armature_name), to_return.bones_length); to_return.bones = MD_PushArray(arena, Bone, to_return.bones_length); for(MD_u64 i = 0; i < to_return.bones_length; i++) @@ -977,7 +999,6 @@ Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armat next_bone->inverse_model_space_pos = blender_to_handmade_mat(inverse_model_space_pose); } - ser_MD_u64(&ser, &to_return.poses_length); to_return.poses = MD_PushArray(arena, PoseBone, to_return.poses_length); @@ -1009,9 +1030,25 @@ Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armat } } } + + ser_MD_u64(&ser, &to_return.vertices_length); + to_return.vertices = MD_PushArray(arena, ArmatureVertex, to_return.vertices_length); + for(MD_u64 i = 0; i < to_return.vertices_length; i++) + { + ser_Vec3(&ser, &to_return.vertices[i].position); + ser_Vec2(&ser, &to_return.vertices[i].uv); + } + Log("Armature %.*s has %llu vertices\n", MD_S8VArg(armature_name), to_return.vertices_length); + assert(!ser.cur_error.failed); MD_ReleaseScratch(scratch); + to_return.loaded_buffer = sg_make_buffer(&(sg_buffer_desc) + { + .usage = SG_USAGE_IMMUTABLE, + .data = (sg_range){.ptr = to_return.vertices, .size = to_return.vertices_length * sizeof(ArmatureVertex)}, + .label = (const char*)nullterm(arena, MD_S8Fmt(arena, "%.*s-vertices", MD_S8VArg(armature_name))).str, + }); // a sanity check SLICE_ITER(Bone, to_return.bones) @@ -2588,6 +2625,24 @@ 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) +{ + state.threedee_bind.vertex_buffers[0] = armature->loaded_buffer; + sg_apply_bindings(&state.threedee_bind); + + Mat4 model = transform_to_mat(t); + + threedee_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)); + + sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_threedee_vs_params, &SG_RANGE(params)); + num_draw_calls += 1; + num_vertices += (int)armature->vertices_length; + sg_draw(0, (int)armature->vertices_length, 1); +} + void audio_stream_callback(float *buffer, int num_frames, int num_channels) { @@ -2820,11 +2875,9 @@ void init(void) MD_String8 binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/Player.bin")); mesh_player = load_mesh(persistent_arena, binary_file, MD_S8Lit("Player.bin")); - binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/SimpleWorm.bin")); - mesh_simple_worm = load_mesh(persistent_arena, binary_file, MD_S8Lit("SimpleWorm.bin")); binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/Armature.bin")); - armature = load_armature(persistent_arena, binary_file, MD_S8Lit("Player.bin")); + armature = load_armature(persistent_arena, binary_file, MD_S8Lit("Armature.bin")); @@ -4661,7 +4714,7 @@ void frame(void) Transform draw_with = entity_transform(it); if(it->npc_kind == NPC_SimpleWorm) { - draw_placed(view, projection, &(PlacedMesh){.draw_with = &mesh_simple_worm, .t = draw_with, }); + draw_armature(view, projection, draw_with, &armature); } else {