Animation blending

- transition smoothly between idle and run animations
- use quaternions (versors) instead of yaw/pitch/roll
- hide mouse and enable free-look mode by default on startup
- rotate model 180 degrees in Blender so it faces away from the camera by default
- add some makefile commands
This commit is contained in:
var
2026-05-03 14:38:57 -05:00
parent 0f38e9b4a2
commit d1530525ca
12 changed files with 161 additions and 96 deletions

View File

@@ -1,6 +1,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "SDL2/SDL.h"
#include "GL/glew.h"
#include "SDL2/SDL_opengl.h"
@@ -220,6 +221,7 @@ bool Render_Init(GameState *gs)
gs->window = SDL_CreateWindow("Sandbox", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1920, 1080, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);;
gs->glContext = SDL_GL_CreateContext(gs->window);
SDL_SetRelativeMouseMode(SDL_TRUE);
printf("Created OpenGL window.\n");
if (SDL_GL_SetSwapInterval(1) == 0) printf("Enabled VSync.\n");
@@ -318,10 +320,7 @@ static void Transform(ShapeInstance *instance, mat4 *matrix)
glm_mat4_identity(tempMat);
glm_translate(tempMat, instance->position);
glm_rotate_x(tempMat, instance->rotation[0], tempMat);
glm_rotate_y(tempMat, instance->rotation[1], tempMat);
glm_rotate_z(tempMat, instance->rotation[2], tempMat);
glm_quat_rotate(tempMat, instance->rotation, tempMat);
vec3 scale;
scale[0] = instance->scale;
@@ -332,43 +331,8 @@ static void Transform(ShapeInstance *instance, mat4 *matrix)
memcpy(matrix, tempMat, sizeof(mat4));
}
static void DrawModel(GameState *gs)
static void ApplyAnim(cgltf_animation *anim, float t, float blend)
{
Model *model = &gs->testModel;
ShapeInstance instance = model->instance;
cgltf_data *data = model->data;
cgltf_skin *skin = model->skin;
cgltf_animation *anim = data->animations;
mat4 modelMatrix;
glm_mat4_identity(modelMatrix);
if (!gs->input.freeLook)
{
// rotate the whole model to stay within this angle relative to the camera yaw
const float turnThreshold = 1.047197f; // 60 degrees
const float deg180 = 3.14159f;
const float deg360 = 6.28318f;
// convert camera yaw (rotation around Y axis) to radians and add 90 degrees so it aligns with the gltf model
float camYawRadsGltf = (gs->camera.rotation[0] / -57.295828f) + 1.570795f;
if (camYawRadsGltf > deg180) camYawRadsGltf = camYawRadsGltf - deg360;
float *modelYaw = model->instance.rotation + 1;
float diff = camYawRadsGltf - *modelYaw;
if (diff > deg180) diff -= deg360;
else if (diff < -deg180) diff += deg360;
if (turnThreshold < diff)
*modelYaw = camYawRadsGltf - turnThreshold;
else if (diff < -turnThreshold)
*modelYaw = camYawRadsGltf + turnThreshold;
}
Transform(&model->instance, &modelMatrix);
uint64_t ticks = SDL_GetTicks64();
float t = ticks / 1000.0f;
while (t > 1.0f) t -= 1.0f;
// apply local transformations for each bone for the current animation frame
for (int i = 0; i < anim->channels_count; i++)
{
cgltf_animation_channel* channel = &anim->channels[i];
@@ -406,7 +370,9 @@ static void DrawModel(GameState *gs)
cgltf_accessor_read_float(sampler->output, k + 1, p1, 3);
glm_vec3_lerp(p0, p1, alpha, result);
// glm_vec3_copy is not used here due to potential struct alignment issues
for (int x = 0; x < 3; x++) node->translation[x] = result[x];
memcpy(p0, node->translation, sizeof(vec3));
glm_vec3_lerp(p0, result, blend, result);
memcpy(node->translation, result, sizeof(vec3));
node->has_translation = true;
}
break;
@@ -417,7 +383,10 @@ static void DrawModel(GameState *gs)
cgltf_accessor_read_float(sampler->output, k + 1, q1, 4);
// slerp (not lerp) for rotation
glm_quat_slerp(q0, q1, alpha, result);
for (int x = 0; x < 4; x++) node->rotation[x] = result[x];
memcpy(q0, node->rotation, sizeof(versor));
glm_quat_slerp(q0, result, blend, result);
memcpy(node->rotation, result, sizeof(versor));
node->has_rotation = true;
}
break;
@@ -427,7 +396,9 @@ static void DrawModel(GameState *gs)
cgltf_accessor_read_float(sampler->output, k, s0, 3);
cgltf_accessor_read_float(sampler->output, k + 1, s1, 3);
glm_vec3_lerp(s0, s1, alpha, result);
for (int x = 0; x < 3; x++) node->scale[x] = result[x];
memcpy(s0, node->scale, sizeof(vec3));
glm_vec3_lerp(s0, result, blend, result);
memcpy(node->scale, result, sizeof(vec3));
node->has_scale = true;
}
break;
@@ -435,25 +406,40 @@ static void DrawModel(GameState *gs)
break;
}
}
}
static void DrawModel(GameState *gs)
{
Model *model = &gs->testModel;
ShapeInstance *instance = &model->instance;
cgltf_data *data = model->data;
cgltf_skin *skin = model->skin;
mat4 modelMatrix;
glm_mat4_identity(modelMatrix);
Transform(instance, &modelMatrix);
uint64_t ticks = SDL_GetTicks64();
float t = ticks / 1000.0f;
while (t > 1.0f) t -= 1.0f;
// apply local transformations for each bone for the current animation frame
ApplyAnim(model->idle, t, 1.0f);
ApplyAnim(model->run, t, gs->animBlend);
if (!gs->input.freeLook)
{
// make head look where camera is looking
cgltf_node *head = model->head;
vec3 a, b, yAxis;
glm_vec3_zero(a);
glm_vec3_zero(b);
glm_vec3_zero(yAxis);
yAxis[1] = 1.0f;
a[2] = 1.0f;
glm_vec3_rotate(a, model->instance.rotation[1], yAxis);
b[0] = gs->camera.front[0];
b[2] = gs->camera.front[2];
vec3 modelFront, camFront;
glm_quat_rotatev(instance->rotation, GLM_FORWARD, modelFront);
glm_vec3_copy(gs->camera.front, camFront);
modelFront[1] = 0.0f;
camFront[1] = 0.0f;
versor desiredRotation, temp;
glm_quat_from_vecs(a, b, desiredRotation);
for (int i = 0; i < 4; i++) temp[i] = head->rotation[i];
glm_quat_from_vecs(modelFront, camFront, desiredRotation);
memcpy(temp, head->rotation, sizeof(versor));
glm_quat_mul(temp, desiredRotation, temp);
for (int i = 0; i < 4; i++) head->rotation[i] = temp[i];
memcpy(head->rotation, temp, sizeof(versor));
}
// recalculate the joint matrices