diff --git a/armature.glsl b/armature.glsl index 884436e..2d24dc6 100644 --- a/armature.glsl +++ b/armature.glsl @@ -17,38 +17,33 @@ uniform vs_params { }; uniform sampler2D bones_tex; -// in textures, color elements are delivered as unsigend normalize floats -// in [0, 1]. This makes them into [-1, 1] as the bone matrices require -// such values to be correct -vec4 make_signed_again(vec4 v) { - v.x = 2.0 * v.x - 1.0; - v.y = 2.0 * v.y - 1.0; - v.z = 2.0 * v.z - 1.0; - v.w = 2.0 * v.w - 1.0; - return v; +float decode_normalized_float32(vec4 v) +{ + float sign = 2.0 * v.x - 1.0; + + return sign * (v.z*255.0 + v.y); } void main() { vec4 total_position = vec4(0.0f); - for(int i = 0; i < 4; i++) + for(int bone_influence_index = 0; bone_influence_index < 4; bone_influence_index++) { - float index_float = indices_in[i]; + float index_float = indices_in[bone_influence_index]; int index = int(index_float * 65535.0); - float weight = weights_in[i]; + float weight = weights_in[bone_influence_index]; float y_coord = (0.5 + index)/bones_tex_size.y; - vec4 col0 = texture(bones_tex, vec2((0.5 + 0)/bones_tex_size.x, y_coord)); - vec4 col1 = texture(bones_tex, vec2((0.5 + 1)/bones_tex_size.x, y_coord)); - vec4 col2 = texture(bones_tex, vec2((0.5 + 2)/bones_tex_size.x, y_coord)); - vec4 col3 = texture(bones_tex, vec2((0.5 + 3)/bones_tex_size.x, y_coord)); - col0 = make_signed_again(col0); - col1 = make_signed_again(col1); - col2 = make_signed_again(col2); - col3 = make_signed_again(col3); + mat4 bone_mat; - mat4 bone_mat = mat4(col0, col1, col2, col3); + for(int row = 0; row < 4; row++) + { + for(int col = 0; col < 4; col++) + { + bone_mat[col][row] = decode_normalized_float32(texture(bones_tex, vec2((0.5 + col*4 + row)/bones_tex_size.x, y_coord))); + } + } vec4 local_position = bone_mat * vec4(pos_in, 1.0f); total_position += local_position * weight; diff --git a/art/Exporter.py b/art/Exporter.py index 2383689..e8b2510 100644 --- a/art/Exporter.py +++ b/art/Exporter.py @@ -115,7 +115,7 @@ for o in D.objects: break if parent_index == -1: 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}") + #print(f"Parent of bone {b.name} is index {parent_index} in list {bones_in_armature}") write_i32(f, parent_index) write_4x4matrix(f, model_space_pose) @@ -129,7 +129,16 @@ for o in D.objects: assert(len(o.pose.bones) == len(bones_in_armature)) armature = o - for animation in bpy.data.actions: + anims = [] + assert armature.animation_data, "Armatures are assumed to have an animation right now" + for track in armature.animation_data.nla_tracks: + for strip in track.strips: + anims.append(strip.action) + print(f"Writing {len(anims)} animations") + write_u64(f, len(anims)) + for animation in anims: + write_string(f, animation.name) + armature.animation_data.action = animation startFrame = int(animation.frame_range.x) endFrame = int(animation.frame_range.y) diff --git a/art/art.blend b/art/art.blend index e973f7b..bc581b4 100644 --- a/art/art.blend +++ b/art/art.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84e78be53e858b252b6db329c4e66ca681bb9e35e9f0b1be0fad1c0bdd9df83c -size 13211972 +oid sha256:dd25f050fa91146c0150b0f0097fddf2ad55d26bef40de19f1efa7f5066da32b +size 17655220 diff --git a/main.c b/main.c index 6d80ec9..5cf92e2 100644 --- a/main.c +++ b/main.c @@ -851,13 +851,23 @@ typedef struct PoseBone typedef struct Bone { struct Bone *parent; - PoseBone *anim_poses; - MD_u64 anim_poses_length; Mat4 matrix_local; Mat4 inverse_model_space_pos; float length; } Bone; +typedef struct AnimationTrack +{ + PoseBone *poses; + MD_u64 poses_length; +} AnimationTrack; + +typedef struct Animation +{ + MD_String8 name; + // assumed to be the same as the number of bones in the armature the animation is in + AnimationTrack *tracks; +} Animation; typedef struct { @@ -969,6 +979,8 @@ typedef struct { Bone *bones; MD_u64 bones_length; + Animation *animations; + MD_u64 animations_length; ArmatureVertex *vertices; MD_u64 vertices_length; sg_buffer loaded_buffer; @@ -1027,29 +1039,43 @@ Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armat } } - MD_u64 frames_in_anim; - ser_MD_u64(&ser, &frames_in_anim); - Log("There are %llu animation frames\n", frames_in_anim); + 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); - for(MD_u64 i = 0; i < to_return.bones_length; i++) + for(MD_u64 i = 0; i < to_return.animations_length; i++) { - to_return.bones[i].anim_poses = MD_PushArray(arena, PoseBone, frames_in_anim); - to_return.bones[i].anim_poses_length = frames_in_anim; - } + Animation *new_anim = &to_return.animations[i]; + *new_anim = (Animation){0}; - 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++) + ser_MD_String8(&ser, &new_anim->name, arena); + + new_anim->tracks = MD_PushArray(arena, AnimationTrack, to_return.bones_length); + + MD_u64 frames_in_anim; + ser_MD_u64(&ser, &frames_in_anim); + Log("There are %llu animation frames in animation '%.*s'\n", frames_in_anim, MD_S8VArg(new_anim->name)); + + for(MD_u64 i = 0; i < to_return.bones_length; i++) + { + new_anim->tracks[i].poses = MD_PushArray(arena, PoseBone, frames_in_anim); + new_anim->tracks[i].poses_length = frames_in_anim; + } + + for(MD_u64 anim_i = 0; anim_i < frames_in_anim; anim_i++) { - PoseBone *next_pose_bone = &to_return.bones[pose_bone_i].anim_poses[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 = &new_anim->tracks[pose_bone_i].poses[anim_i]; - ser_Vec3(&ser, &next_pose_bone->parent_space_pose.offset); - ser_Quat(&ser, &next_pose_bone->parent_space_pose.rotation); - ser_Vec3(&ser, &next_pose_bone->parent_space_pose.scale); + ser_Vec3(&ser, &next_pose_bone->parent_space_pose.offset); + ser_Quat(&ser, &next_pose_bone->parent_space_pose.rotation); + ser_Vec3(&ser, &next_pose_bone->parent_space_pose.scale); - next_pose_bone->time = time_through; + next_pose_bone->time = time_through; + } } } @@ -1083,7 +1109,7 @@ Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armat .label = (const char*)nullterm(arena, MD_S8Fmt(arena, "%.*s-vertices", MD_S8VArg(armature_name))).str, }); - to_return.bones_texture_width = 4; + to_return.bones_texture_width = 16; to_return.bones_texture_height = (int)to_return.bones_length; Log("Amrature %.*s has bones texture size (%d, %d)\n", MD_S8VArg(armature_name), to_return.bones_texture_width, to_return.bones_texture_height); @@ -1093,6 +1119,12 @@ Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armat .pixel_format = SG_PIXELFORMAT_RGBA8, .min_filter = SG_FILTER_NEAREST, .mag_filter = SG_FILTER_NEAREST, + + // for webgl NPOT texures https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL + .wrap_u = SG_WRAP_CLAMP_TO_EDGE, + .wrap_v = SG_WRAP_CLAMP_TO_EDGE, + .wrap_w = SG_WRAP_CLAMP_TO_EDGE, + .usage = SG_USAGE_STREAM, }); @@ -1104,13 +1136,14 @@ Armature load_armature(MD_Arena *arena, MD_String8 binary_file, MD_String8 armat { for(int c = 0; c < 4; c++) { + const float eps = 0.0001f; if(r == c) { - assert(should_be_identity.Elements[c][r] == 1.0f); + assert(fabsf(should_be_identity.Elements[c][r] - 1.0f) < eps); } else { - assert(should_be_identity.Elements[c][r] == 0.0f); + assert(fabsf(should_be_identity.Elements[c][r] - 0.0f) < eps); } } } @@ -2740,17 +2773,17 @@ 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(Bone *bone, float time) +Mat4 get_animated_bone_transform(AnimationTrack *track, Bone *bone, float time) { - float total_anim_time = bone->anim_poses[bone->anim_poses_length - 1].time; + float total_anim_time = track->poses[track->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++) + for(MD_u64 i = 0; i < track->poses_length - 1; i++) { - if(bone->anim_poses[i].time <= time && time <= bone->anim_poses[i + 1].time) + if(track->poses[i].time <= time && time <= track->poses[i + 1].time) { - PoseBone from = bone->anim_poses[i]; - PoseBone to = bone->anim_poses[i + 1]; + PoseBone from = track->poses[i]; + PoseBone to = track->poses[i + 1]; float gap_btwn_keyframes = to.time - from.time; float t = (time - from.time)/gap_btwn_keyframes; assert(t >= 0.0f); @@ -2762,6 +2795,58 @@ Mat4 get_animated_bone_transform(Bone *bone, float time) return M4D(1.0f); } +typedef struct +{ + MD_u8 rgba[4]; +} PixelData; + +PixelData encode_normalized_float32(float to_encode) +{ + Vec4 to_return_vector = {0}; + + // x is just -1.0f or 1.0f, encoded as a [0,1] normalized float. + if(to_encode < 0.0f) to_return_vector.x = -1.0f; + else to_return_vector.x = 1.0f; + to_return_vector.x = to_return_vector.x / 2.0f + 0.5f; + + float without_sign = fabsf(to_encode); + to_return_vector.y = without_sign - floorf(without_sign); + + to_return_vector.z = fabsf(to_encode) - to_return_vector.y; + assert(to_return_vector.z < 255.0f); + to_return_vector.z /= 255.0f; + + // w is unused for now, but is 1.0f (and is the alpha channel in Vec4) so that it displays properly as a texture + to_return_vector.w = 1.0f; + + + PixelData to_return = {0}; + + for(int i = 0; i < 4; i++) + { + assert(0.0f <= to_return_vector.Elements[i] && to_return_vector.Elements[i] <= 1.0f); + to_return.rgba[i] = (MD_u8)(to_return_vector.Elements[i] * 255.0f); + } + + return to_return; +} + +float decode_normalized_float32(PixelData encoded) +{ + Vec4 v = {0}; + for(int i = 0; i < 4; i++) + { + v.Elements[i] = (float)encoded.rgba[i] / 255.0f; + } + + float sign = 2.0f * v.x - 1.0f; + + float to_return = sign * (v.z*255.0f + v.y); + + return to_return; +} + + void draw_armature(Mat4 view, Mat4 projection, Transform t, Armature *armature, float elapsed_time) { MD_ArenaTemp scratch = MD_GetScratch(0, 0); @@ -2788,34 +2873,26 @@ void draw_armature(Mat4 view, Mat4 projection, Transform t, Armature *armature, 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); - final = MulM4(get_animated_bone_transform(cur_in_hierarchy, elapsed_time), 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]; - assert(-1.1f <= to_upload.x && to_upload.x <= 1.1f); - assert(-1.1f <= to_upload.y && to_upload.y <= 1.1f); - assert(-1.1f <= to_upload.z && to_upload.z <= 1.1f); - assert(-1.1f <= to_upload.w && to_upload.w <= 1.1f); - - // make them normalized - to_upload.x = to_upload.x/2.0f + 0.5f; - to_upload.y = to_upload.y/2.0f + 0.5f; - to_upload.z = to_upload.z/2.0f + 0.5f; - to_upload.w = to_upload.w/2.0f + 0.5f; - - to_upload.x = clamp01(to_upload.x); - to_upload.y = clamp01(to_upload.y); - to_upload.z = clamp01(to_upload.z); - to_upload.w = clamp01(to_upload.w); int bytes_per_pixel = 4; - int bytes_per_row = bytes_per_pixel * 4; - bones_tex[bytes_per_pixel*col + bytes_per_row*i + 0] = (MD_u8)(to_upload.x * 255.0); - bones_tex[bytes_per_pixel*col + bytes_per_row*i + 1] = (MD_u8)(to_upload.y * 255.0); - bones_tex[bytes_per_pixel*col + bytes_per_row*i + 2] = (MD_u8)(to_upload.z * 255.0); - bones_tex[bytes_per_pixel*col + bytes_per_row*i + 3] = (MD_u8)(to_upload.w * 255.0); + 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){ @@ -3023,6 +3100,22 @@ void do_serialization_tests() MD_ReleaseScratch(scratch); } + +void do_float_encoding_tests() +{ + float to_test[] = { + 7.5f, + -2.12f, + 100.2f, + -5.35f, + }; + ARR_ITER(float, to_test) + { + PixelData encoded = encode_normalized_float32(*it); + float decoded = decode_normalized_float32(encoded); + assert(fabsf(decoded - *it) < 0.01f); + } +} #endif Armature armature = {0}; @@ -3083,8 +3176,8 @@ void init(void) mesh_player = load_mesh(persistent_arena, binary_file, MD_S8Lit("Player.bin")); - binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/Armature.bin")); - armature = load_armature(persistent_arena, binary_file, MD_S8Lit("Armature.bin")); + binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/WalkingArmature.bin")); + armature = load_armature(persistent_arena, binary_file, MD_S8Lit("WalkingArmature.bin")); @@ -3096,6 +3189,7 @@ void init(void) do_metadesk_tests(); do_parsing_tests(); do_serialization_tests(); + do_float_encoding_tests(); #endif #ifdef WEB @@ -5108,7 +5202,6 @@ void frame(void) // debug draw armature for(MD_u64 i = 0; i < armature.bones_length; i++) { - PoseBone *cur_pose_bone = &armature.bones[i].anim_poses[0]; Bone *cur = &armature.bones[i]; Vec3 offset = V3(1.5, 0, 5); @@ -5119,27 +5212,6 @@ void frame(void) Vec3 z = MulM4V3(cur->matrix_local, V3(0,0,cur->length)); Vec3 dot = MulM4V3(cur->matrix_local, V3(cur->length,0,cur->length)); - Vec3 should_be_zero = MulM4V3(cur->inverse_model_space_pos, from); - assert(should_be_zero.x == 0.0); - assert(should_be_zero.y == 0.0); - assert(should_be_zero.z == 0.0); - - if(cur->parent == 0) - { - // do some testing on the bone with no parent - Vec3 should_be_zero = MulM4V3(transform_to_mat(cur_pose_bone->parent_space_pose), V3(0,0,0)); - - // there is another bone, that's not at (0,0,0) in model space on the model - // for debugging purposes right now - - assert(should_be_zero.y == 0.0); - - /* - assert(should_be_zero.x == 0.0); - assert(should_be_zero.z == 0.0); - */ - } - // 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 @@ -5149,7 +5221,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(get_animated_bone_transform(cur_in_hierarchy, (float)elapsed_time), final_mat); + 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); } diff --git a/tuning.h b/tuning.h index 9c1b8a5..67e9fcd 100644 --- a/tuning.h +++ b/tuning.h @@ -12,7 +12,7 @@ #define ARROW_SPEED 200.0f #define SECONDS_PER_ARROW 1.3f -#define ARENA_SIZE (1024*1024) +#define ARENA_SIZE (1024*1024*10) #define BIG_ARENA_SIZE (ARENA_SIZE * 8) #ifdef DEVTOOLS