Armatures are a separate kind of thing, that includes its mesh

main
parent e203dcc88b
commit cbce6c9a81

@ -18,6 +18,8 @@ if os.path.exists(bpy.path.abspath(f"//{EXPORT_DIRECTORY}")):
shutil.rmtree(bpy.path.abspath(f"//{EXPORT_DIRECTORY}")) shutil.rmtree(bpy.path.abspath(f"//{EXPORT_DIRECTORY}"))
os.makedirs(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): def write_f32(f, number: float):
f.write(bytes(struct.pack("f", number))) f.write(bytes(struct.pack("f", number)))
@ -64,85 +66,71 @@ mapping = axis_conversion(
) )
mapping.resize_4x4() 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: for o in D.objects:
if o.type == "ARMATURE": if o.type == "MESH":
armature_name = o.data.name if o.parent and o.parent.type == "ARMATURE":
output_filepath = bpy.path.abspath(f"//{EXPORT_DIRECTORY}/{armature_name}.bin") mesh_object = o
print(f"Exporting armature to {output_filepath}") o = o.parent
with open(output_filepath, "wb") as f: object_transform_info = (mesh_name, mapping @ mesh_object.location, mesh_object.rotation_euler, mesh_object.scale)
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.users_collection[0].name == 'Level': if o.users_collection[0].name == 'Level':
print(f"Object {o.name} has mesh name {o.to_mesh().name}") 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")
assert(o.rotation_euler.order == 'XYZ')
level_object_data.append(object_transform_info)
else: else:
placed_entities.append((o.name,) + object_transform_info) placed_entities.append((mesh_object.name,) + object_transform_info)
armature_name = o.data.name
if mesh_name in saved_meshes: output_filepath = bpy.path.abspath(f"//{EXPORT_DIRECTORY}/{armature_name}.bin")
continue print(f"Exporting armature to {output_filepath}")
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: 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() bm = bmesh.new()
mesh = o.to_mesh() mesh = mesh_object.to_mesh()
bm.from_mesh(mesh) bm.from_mesh(mesh)
bmesh.ops.triangulate(bm, faces=bm.faces) bmesh.ops.triangulate(bm, faces=bm.faces)
bm.transform(mapping) bm.transform(mapping)
@ -171,6 +159,60 @@ for o in D.objects:
write_f32(f, uv.y) write_f32(f, uv.y)
print(f"Wrote {len(vertices)} vertices") 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: with open(bpy.path.abspath(f"//{EXPORT_DIRECTORY}/{LEVEL_EXPORT_NAME}.bin"), "wb") as f:
write_u64(f, len(level_object_data)) write_u64(f, len(level_object_data))
for o in level_object_data: for o in level_object_data:

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

Binary file not shown.

@ -804,12 +804,20 @@ void ser_Quat(SerState *ser, Quat *q)
ser_float(ser, &q->w); ser_float(ser, &q->w);
} }
#pragma pack(1)
typedef struct typedef struct
{ {
Vec3 pos; Vec3 pos;
Vec2 uv; // CANNOT have struct padding Vec2 uv; // CANNOT have struct padding
} Vertex; } Vertex;
#pragma pack(1)
typedef struct
{
Vec3 position;
Vec2 uv;
} ArmatureVertex;
SER_MAKE_FOR_TYPE(Vertex); SER_MAKE_FOR_TYPE(Vertex);
typedef struct Mesh typedef struct Mesh
@ -874,6 +882,11 @@ Mesh load_mesh(MD_Arena *arena, MD_String8 binary_file, MD_String8 mesh_name)
.serializing = false, .serializing = false,
}; };
Mesh out = {0}; Mesh out = {0};
bool is_armature;
ser_bool(&ser, &is_armature);
assert(!is_armature);
ser_MD_u64(&ser, &out.num_vertices); ser_MD_u64(&ser, &out.num_vertices);
Log("Mesh %.*s has %llu vertices\n", MD_S8VArg(mesh_name), 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; MD_String8 name;
} PoseBone; } PoseBone;
typedef struct typedef struct
{ {
Bone *bones; Bone *bones;
MD_u64 bones_length; MD_u64 bones_length;
PoseBone *poses; PoseBone *poses;
MD_u64 poses_length; MD_u64 poses_length;
ArmatureVertex *vertices;
MD_u64 vertices_length;
sg_buffer loaded_buffer;
MD_String8 name;
} Armature; } Armature;
Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armature_name) 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, .serializing = false,
}; };
Armature to_return = {0}; Armature to_return = {0};
bool is_armature;
ser_bool(&ser, &is_armature);
assert(is_armature);
ser_MD_u64(&ser, &to_return.bones_length); 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); to_return.bones = MD_PushArray(arena, Bone, to_return.bones_length);
for(MD_u64 i = 0; i < to_return.bones_length; i++) 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); next_bone->inverse_model_space_pos = blender_to_handmade_mat(inverse_model_space_pose);
} }
ser_MD_u64(&ser, &to_return.poses_length); ser_MD_u64(&ser, &to_return.poses_length);
to_return.poses = MD_PushArray(arena, PoseBone, 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); assert(!ser.cur_error.failed);
MD_ReleaseScratch(scratch); 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 // a sanity check
SLICE_ITER(Bone, to_return.bones) 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); 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) 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")); 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")); 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")); 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); Transform draw_with = entity_transform(it);
if(it->npc_kind == NPC_SimpleWorm) 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 else
{ {

Loading…
Cancel
Save