diff --git a/makefile b/makefile index 0aa33dc..32c22d3 100755 --- a/makefile +++ b/makefile @@ -14,7 +14,7 @@ OBJDIRS := $(patsubst $(SRC)%, $(OBJ)%, $(shell find $(SRC) -type d)) CLEANDIRS := $(addsuffix /.clean, $(OBJDIRS)); # Explicit targets -.PHONY: all release clean +.PHONY: all release clean rebuild run .DEFAULT_GOAL := all # Build executable @@ -39,3 +39,11 @@ clean: $(CLEANDIRS) rm -f $(EXE) $(REL) %.clean: rm -f $**.o + +rebuild: + make clean + make all + +run: + make all + ./$(EXE) diff --git a/res/model/human.glb b/res/model/human.glb index 7536681..485d825 100644 Binary files a/res/model/human.glb and b/res/model/human.glb differ diff --git a/src/camera.c b/src/camera.c index 3122814..320df68 100644 --- a/src/camera.c +++ b/src/camera.c @@ -10,11 +10,8 @@ void Camera_Init(Camera* c) c->width[1] = 5.8f; c->width[2] = 0.6f; glm_vec3_zero(c->velocity); - glm_vec3_zero(c->look); - glm_vec3_zero(c->up); - glm_vec3_zero(c->front); - glm_vec3_zero(c->right); - glm_vec3_zero(c->rotation); + glm_quat_identity(c->rotation); + Camera_UpdateVectors(c); } void Camera_GetViewMatrix(Camera* c, mat4 m) @@ -24,30 +21,10 @@ void Camera_GetViewMatrix(Camera* c, mat4 m) void Camera_UpdateVectors(Camera* c) { - if (c->rotation[0] > 180.0f) c->rotation[0] -= 360.0f; - else if (c->rotation[0] <= -180.0f) c->rotation[0] += 360.0f; - - c->rotation[1] = glm_clamp(c->rotation[1], -89.0f, 89.0f); - c->rotation[2] = glm_clamp(c->rotation[2], -30.0f, 30.0f); - - // calculate the front facing unit vector by yaw and pitch - c->front[0] = cos(glm_rad(c->rotation[0])) * cos(glm_rad(c->rotation[1])); - c->front[1] = sin(glm_rad(c->rotation[1])); - c->front[2] = sin(glm_rad(c->rotation[0])) * cos(glm_rad(c->rotation[1])); - + glm_quat_rotatev(c->rotation, GLM_FORWARD, c->front); + glm_quat_rotatev(c->rotation, GLM_YUP, c->up); + glm_quat_rotatev(c->rotation, GLM_XUP, c->right); + // the look vector is the position + front glm_vec3_add(c->position, c->front, c->look); - - // reset the up vector - c->up[0] = 0.0f; - c->up[1] = 1.0f; - c->up[2] = 0.0f; - // cross product points to the right, perpendicular to both front and up - glm_vec3_cross(c->front, c->up, c->right); - // pitch up or down, around the axis of the cross product - glm_vec3_rotate(c->up, glm_rad(c->rotation[1]), c->right); - // roll around the axis of the front vector - glm_vec3_rotate(c->up, glm_rad(c->rotation[2]), c->front); - // recalculate right vector after rolling - glm_vec3_cross(c->front, c->up, c->right); } diff --git a/src/camera.h b/src/camera.h index a8eefeb..7081cdd 100644 --- a/src/camera.h +++ b/src/camera.h @@ -11,7 +11,7 @@ typedef struct vec3 up; vec3 front; vec3 right; - vec3 rotation; + versor rotation; } Camera; void Camera_Init(Camera* c); diff --git a/src/game.c b/src/game.c index 36e8c00..2e9f8bf 100644 --- a/src/game.c +++ b/src/game.c @@ -1,5 +1,6 @@ #include #include +#include #include "SDL2/SDL_timer.h" #include "game.h" #include "render.h" @@ -13,6 +14,7 @@ GameState *Game_New() if (!Render_Init(gs)) return NULL; gs->running = true; + gs->input.freeLook = true; return gs; } @@ -38,7 +40,6 @@ void Game_Update(GameState *gs) Camera_UpdateVectors(&gs->camera); - const float speed = 0.3f; vec3 move, front, right; glm_vec3_zero(move); front[0] = gs->camera.front[0]; @@ -50,6 +51,7 @@ void Game_Update(GameState *gs) right[2] = gs->camera.right[2]; glm_vec3_normalize(right); + // get player's desired movement direction if (gs->input.w) glm_vec3_add(move, front, move); else if (gs->input.s) glm_vec3_sub(move, front, move); if (gs->input.a) glm_vec3_sub(move, right, move); @@ -57,15 +59,70 @@ void Game_Update(GameState *gs) if (gs->input.q) move[1] = 1.0f; else if (gs->input.e) move[1] = -1.0f; - glm_vec3_scale_as(move, speed, move); + gs->charIsMoving = gs->input.w || gs->input.s || gs->input.a || gs->input.d; + // blend idle and run animations + if (gs->charIsMoving) + { + gs->animBlend += delta / 500.0f; + if (gs->animBlend > 1.0f) gs->animBlend = 1.0f; + } + else + { + gs->animBlend -= delta / 500.0f; + if (gs->animBlend < 0.0f) gs->animBlend = 0.0f; + } + + // apply acceleration ShapeInstance *target = &(gs->testModel.instance); + float accel = 0.005f * delta; + glm_vec3_scale_as(move, accel, move); + glm_vec3_add(target->velocity, move, target->velocity); + glm_vec3_clamp(target->velocity, -1.0f, 1.0f); + + // apply velocity, make camera follow vec3 displacement; glm_vec3_scale(gs->camera.front, -30.0f, displacement); displacement[1] += 10.0f; - glm_vec3_add(target->position, move, target->position); + glm_vec3_add(target->position, target->velocity, target->position); glm_vec3_add(target->position, displacement, gs->camera.position); + // decelerate + glm_vec3_scale(target->velocity, 1.0f / ((0.01f * delta) + 1.0f), target->velocity); + + if (gs->charIsMoving) + { + // gradually rotate model to face toward direction of velocity + float alpha = delta > 200.0f ? 1.0f : delta / 200.0f; + versor velQuat; + glm_quat_for(target->velocity, GLM_YUP, velQuat); + glm_quat_slerp(target->rotation, velQuat, alpha, target->rotation); + } + + if (!gs->input.freeLook) + { + // stay within a certain angle relative to the camera yaw + const float pi = 3.14159265358979323846f; + const float turnThreshold = pi / 3.0f; + + versor camRot, inverse, difference; + glm_quat_from_vecs(GLM_FORWARD, front, camRot); + glm_quat_inv(target->rotation, inverse); + glm_quat_mul(camRot, inverse, difference); + + vec3 axis; + glm_quat_axis(difference, axis); + float angle = glm_quat_angle(difference); + if (angle > pi) angle -= pi + pi; + + if (fabsf(angle) > turnThreshold) + { + angle += angle < 0.0f ? turnThreshold : -turnThreshold; + glm_quatv(difference, angle, axis); + glm_quat_mul(target->rotation, difference, target->rotation); + } + } + Camera_UpdateVectors(&gs->camera); } diff --git a/src/game.h b/src/game.h index 5ef2d34..e2f841f 100644 --- a/src/game.h +++ b/src/game.h @@ -22,6 +22,8 @@ typedef struct typedef struct { bool running; + bool charIsMoving; + float animBlend; uint64_t previousTicks; InputState input; SDL_Window *window; diff --git a/src/input.c b/src/input.c index 85485af..e35bc52 100644 --- a/src/input.c +++ b/src/input.c @@ -48,10 +48,26 @@ static void HandleKeyUp(GameState *gs, SDL_KeyCode sym) static void HandleMouseMotion(SDL_MouseMotionEvent e, GameState* gs) { - const float sensitivity = 0.4f; + const float sensitivity = 0.01f; Camera *camera = &gs->camera; - camera->rotation[0] += e.xrel * sensitivity; - camera->rotation[1] -= e.yrel * sensitivity; + vec3 front; + glm_vec3_copy(camera->front, front); + + // extract the pitch angle and clamp it so the camera can't go upside down + float pitch = glm_deg(glm_rad(90) - glm_vec3_angle(GLM_YUP, front) - (e.yrel * sensitivity)); + pitch = glm_rad(glm_clamp(pitch, -89.0f, 89.0f)); + + // apply the new pitch using the existing "right" vector + front[1] = 0.0f; + glm_vec3_normalize(front); + glm_vec3_rotate(front, pitch, camera->right); + + // apply yaw (invalidates the "right" vector, that's why pitch is applied first) + glm_vec3_rotate(front, -e.xrel * sensitivity, GLM_YUP); + + // update camera + glm_quat_for(front, GLM_YUP, camera->rotation); + Camera_UpdateVectors(camera); } static void HandleMouseDown(SDL_MouseButtonEvent e, GameState* gs) diff --git a/src/model.c b/src/model.c index 3a9caed..456c1e9 100644 --- a/src/model.c +++ b/src/model.c @@ -87,6 +87,7 @@ Model Model_LoadGltf(char *path) printf("Loading glTF model file...\n"); Model model = {0}; model.instance.scale = 4.0f; + glm_quat_identity(model.instance.rotation); cgltf_options options = {0}; cgltf_data *data = NULL; cgltf_result result = cgltf_parse_file(&options, path, &data); @@ -217,6 +218,22 @@ Model Model_LoadGltf(char *path) printf("skin: %s, joints: %d\n", skin->name, skin->joints_count); model.jointMatrices = calloc(skin->joints_count, sizeof(mat4)); + for (int i = 0; i < data->animations_count; i++) + { + cgltf_animation *anim = data->animations + i; + + if (0 == strncmp(anim->name, "run", 3)) + { + printf("run animation: %s\n", anim->name); + model.run = anim; + } + else if (0 == strncmp(anim->name, "idle", 4)) + { + printf("idle animation: %s\n", anim->name); + model.idle = anim; + } + } + for (int i = 0; i < skin->joints_count; i++) { cgltf_node *joint = skin->joints[i]; diff --git a/src/model.h b/src/model.h index 66676cc..5e79fce 100644 --- a/src/model.h +++ b/src/model.h @@ -9,6 +9,8 @@ typedef struct void *data; void *skin; void *head; + void *idle; + void *run; mat4 *jointMatrices; GLuint *vbos; GLuint vao; diff --git a/src/render.c b/src/render.c index 1371e23..d3d5254 100644 --- a/src/render.c +++ b/src/render.c @@ -1,6 +1,7 @@ #include #include #include +#include #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 diff --git a/src/shape.c b/src/shape.c index 4f8024a..8e3e7da 100644 --- a/src/shape.c +++ b/src/shape.c @@ -32,7 +32,7 @@ Shape *Shape_New(GLfloat *vertices, int numVertices, GLushort *indices, int numI instance->scale = 2.0f; instance->collisionRadius = 1.0f; glm_vec3_zero(instance->velocity); - glm_vec3_zero(instance->rotation); + glm_quat_identity(instance->rotation); float *textureId = shape->instanceData + (i * 17); *textureId = 6; mat4 *matrix = (void*)(textureId + 1); @@ -105,7 +105,7 @@ Shape *Shape_MakePyramid(int numInstances) instance->scale = 1.0f; instance->collisionRadius = 1.0f; glm_vec3_zero(instance->velocity); - glm_vec3_zero(instance->rotation); + glm_quat_identity(instance->rotation); float *textureId = shape->instanceData + (i * 17); *textureId = i; mat4 *matrix = (void*)(textureId + 1); @@ -154,7 +154,7 @@ Shape *Shape_MakePlane() instance->scale = 1.0f; instance->collisionRadius = 1.0f; glm_vec3_zero(instance->velocity); - glm_vec3_zero(instance->rotation); + glm_quat_identity(instance->rotation); float *textureId = shape->instanceData + (0 * 17); *textureId = 5; mat4 *matrix = (void*)(textureId + 1); diff --git a/src/shape.h b/src/shape.h index c7dd1d4..332c240 100644 --- a/src/shape.h +++ b/src/shape.h @@ -11,10 +11,10 @@ enum typedef struct { vec3 position; - vec3 rotation; vec3 velocity; float scale; float collisionRadius; + versor rotation; } ShapeInstance; typedef struct