diff --git a/animation.c b/animation.c index 4340554..23e401d 100644 --- a/animation.c +++ b/animation.c @@ -1,9 +1,44 @@ #include "animation.h" #include +#include #include #include +static vec3_t lerp(vec3_t v, vec3_t u, double t) +{ + return vec3_add(vec3_scale(1 - t, v), vec3_scale(t, u)); +} + +static vec4_t slerp(vec4_t v, vec4_t u, double t) +{ + float dot = vec4_dot(v, u); + if (dot < 0) { + dot = -dot; + u = vec4_scale(-1, u); + } + + if (dot < 0.95) { + const float a = acosf(dot); + return vec4_scale( + 1 / sin(a), + vec4_add( + vec4_scale(sinf(a * (1 - t)), v), + vec4_scale(sinf(a * t), u))); + } else { + return vec4_add(vec4_scale(1 - t, v), vec4_scale(t, u)); + } +} + +static void interpolate( + bone_t *dst, const bone_t *as, const bone_t *bs, size_t n, double t) +{ + for (unsigned i = 0; i < n; ++i) { + dst[i].translation = lerp(as[i].translation, bs[i].translation, t); + dst[i].rotation = slerp(as[i].rotation, bs[i].rotation, t); + } +} + static mat4_t bone_transform(bone_t bone) { return mat4_mul( @@ -27,6 +62,27 @@ static void compute_bone_matrices( matrices[i] = mat4_mul(matrices[i], inverse_bind_matrices[i]); } +static void load_keyframe(animation_state_t *state, const model_t *model) +{ + const uint32_t keyframe_count = model->animation.keyframe_count; + if (state->current_keyframe + 1 < keyframe_count) { + state->next_keyframe = state->current_keyframe + 1; + state->duration_us = model->animation.times_us[state->next_keyframe] + - model->animation.times_us[state->current_keyframe]; + } else { + // Handle loop-around + state->next_keyframe = 0; + state->duration_us = model->animation.length_us + - model->animation.times_us[state->current_keyframe] + + model->animation.times_us[state->next_keyframe]; + } + + state->current = model->animation.keyframes + + state->current_keyframe * model->bone_count; + state->next = model->animation.keyframes + + state->next_keyframe * model->bone_count; +} + animation_state_t animation_init(const model_t *model) { mat4_t *bone_matrices = malloc(model->bone_count * sizeof(mat4_t)); @@ -42,16 +98,12 @@ void animation_cleanup(animation_state_t *state) } } -void animation_start(animation_state_t *state, const model_t *model) +void animation_start( + animation_state_t *state, const model_t *model, uint32_t start_keyframe) { - state->current_keyframe = model->animation.keyframe_count - 1; - state->next_keyframe = 0; - - state->current = model->bones; - state->next = model->animation.keyframes; - - state->duration_us = model->animation.times_us[0]; state->elapsed_us = 0; + state->current_keyframe = start_keyframe; + load_keyframe(state, model); } void animation_update( @@ -60,27 +112,16 @@ void animation_update( state->elapsed_us += dt_us; if (state->elapsed_us >= state->duration_us) { // Move to next keyframe state->elapsed_us -= state->duration_us; - state->current_keyframe = state->next_keyframe; - const uint32_t keyframe_count = model->animation.keyframe_count; - if (state->current_keyframe + 1 < keyframe_count) { - state->next_keyframe = state->current_keyframe + 1; - state->duration_us - = model->animation.times_us[state->next_keyframe] - - model->animation.times_us[state->current_keyframe]; - } else { - // Handle loop-around - state->next_keyframe = 0; - state->duration_us - = model->animation.times_us[state->next_keyframe]; - } - - state->current = state->next; - const uint32_t offset = state->next_keyframe * model->bone_count; - state->next = model->animation.keyframes + offset; + load_keyframe(state, model); } + bone_t bones[MAX_BONE_COUNT]; + interpolate( + bones, state->current, state->next, model->bone_count, + (double)state->elapsed_us / state->duration_us); + compute_bone_matrices( - state->current, model->bone_count, model->bone_parents, + bones, model->bone_count, model->bone_parents, model->inverse_bind_matrices, state->bone_matrices); } diff --git a/animation.h b/animation.h index f33f02b..5be1b5e 100644 --- a/animation.h +++ b/animation.h @@ -13,7 +13,8 @@ typedef struct { animation_state_t animation_init(const model_t *model); void animation_cleanup(animation_state_t *state); -void animation_start(animation_state_t *state, const model_t *model); +void animation_start( + animation_state_t *state, const model_t *model, uint32_t start_keyframe); void animation_update( animation_state_t *state, const model_t *model, uint32_t dt_us); diff --git a/linear_alg.c b/linear_alg.c index 6cbbaee..610f74b 100644 --- a/linear_alg.c +++ b/linear_alg.c @@ -2,11 +2,21 @@ #include +vec3_t vec3_add(vec3_t v, vec3_t u) +{ + return (vec3_t) { v.x + u.x, v.y + u.y, v.z + u.z }; +} + vec3_t vec3_sub(vec3_t v, vec3_t u) { return (vec3_t) { v.x - u.x, v.y - u.y, v.z - u.z }; } +vec3_t vec3_scale(float s, vec3_t v) +{ + return (vec3_t) { s * v.x, s * v.y, s * v.z }; +} + float vec3_len(vec3_t v) { return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); @@ -15,7 +25,7 @@ float vec3_len(vec3_t v) vec3_t vec3_norm(vec3_t v) { const float l = vec3_len(v); - return (vec3_t) { v.x / l, v.y / l, v.z / l }; + return vec3_scale(1 / l, v); } float vec3_dot(vec3_t v, vec3_t u) @@ -32,6 +42,21 @@ vec3_t vec3_cross(vec3_t v, vec3_t u) }; } +vec4_t vec4_add(vec4_t v, vec4_t u) +{ + return (vec4_t) { v.x + u.x, v.y + u.y, v.z + u.z, v.w + u.w }; +} + +vec4_t vec4_scale(float s, vec4_t v) +{ + return (vec4_t) { s * v.x, s * v.y, s * v.z, s * v.w }; +} + +float vec4_dot(vec4_t v, vec4_t u) +{ + return v.x * u.x + v.y * u.y + v.z * u.z + v.w * u.w; +} + mat4_t mat4_mul(mat4_t m, mat4_t n) { #define ELEM(i, j) m.x.i *n.j.x + m.y.i *n.j.y + m.z.i *n.j.z + m.w.i *n.j.w diff --git a/linear_alg.h b/linear_alg.h index 8eb5d52..6bcf192 100644 --- a/linear_alg.h +++ b/linear_alg.h @@ -71,12 +71,18 @@ typedef struct { } // clang-format on +vec3_t vec3_add(vec3_t v, vec3_t u); vec3_t vec3_sub(vec3_t v, vec3_t u); +vec3_t vec3_scale(float s, vec3_t v); float vec3_len(vec3_t v); vec3_t vec3_norm(vec3_t v); float vec3_dot(vec3_t v, vec3_t u); vec3_t vec3_cross(vec3_t v, vec3_t u); +vec4_t vec4_add(vec4_t v, vec4_t u); +vec4_t vec4_scale(float s, vec4_t v); +float vec4_dot(vec4_t v, vec4_t u); + mat4_t mat4_mul(mat4_t m, mat4_t n); mat4_t mat4_lookat(vec3_t camera, vec3_t target, vec3_t up); mat4_t mat4_perspective(float aspect, float fov, float near, float far); diff --git a/model.h b/model.h index 7e01ca2..3cdceb9 100644 --- a/model.h +++ b/model.h @@ -6,6 +6,8 @@ #include #include +#define MAX_BONE_COUNT 64 + typedef uint16_t index_t; typedef struct { diff --git a/model_format_glb.c b/model_format_glb.c index 1fae449..f24a918 100644 --- a/model_format_glb.c +++ b/model_format_glb.c @@ -29,7 +29,6 @@ static_assert( #define VERSION 2 #define MAX_STRING_SIZE 128 -#define MAX_BONE_COUNT 32 enum { CHUNK_TYPE_JSON = 0x4e4f534a, // "JSON"