spherecast

wip first person shooter engine
Download | Log | Files | Refs

commit 1ef521e3f19c49ae5b61f95c3c6794565fee909c
parent cfcbdd934672703ce6e2106610cb45a22a108496
Author: amrfti <andrew@kloet.net>
Date:   Wed, 31 Dec 2025 16:31:29 -0500

Add more detailed player control. Starting to encapsulate data better.
Added a slide for when the player is running and crouches anddddd
probably a bunch of other stuff I forgot.

Diffstat:
MMakefile | 2+-
Mconfig.h | 38+++++++++++++++++++++++++++++---------
Mmain.c | 142+++++++++++++++++++++++++++++++------------------------------------------------
Mphysics.c | 110++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mphysics.h | 17++++++++---------
Aplayer.c | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplayer.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 403 insertions(+), 158 deletions(-)

diff --git a/Makefile b/Makefile @@ -3,7 +3,7 @@ CFLAGS = -Wall -Wextra -pedantic -std=c99 -O3 CFLAGS += -D_POSIX_C_SOURCE=200809L LDFLAGS = -lraylib -lm -SRC = main.c physics.c +SRC = main.c physics.c player.c OBJ = $(SRC:.c=.o) EXE = main diff --git a/config.h b/config.h @@ -8,29 +8,49 @@ // physics #define MAX_SLIDES 4 #define SKIN 0.001f -#define GRAVITY 40.0f +#define GRAVITY 50.0f #define MAX_GROUND_ANGLE_DEG 50.0f #define GROUND_NORMAL_Y (cosf(MAX_GROUND_ANGLE_DEG * DEG2RAD)) #define DOWN (Vector3){0, -1, 0} #define UP (Vector3){0, 1, 0} -#define GROUND_SNAP_DIST 0.5f +#define GROUND_SNAP_DIST 0.1f #define COYOTE_TIME 0.1f // movement #define PLAYER_RADIUS 0.3f #define PLAYER_HEIGHT 0.8f + +#define DECELERATION 3.0f +#define STOP_SPEED 0.01f #define WALK_SPEED 4.0f -#define SPRINT_SPEED 6.0f +#define CROUCH_SPEED WALK_SPEED +#define AIR_SPEED 4.0f +#define SPRINT_SPEED 1.5f * WALK_SPEED +#define SLIDE_SPEED SPRINT_SPEED + +#define WALK_ACCEL 8.0f +#define AIR_ACCEL 0.25f * WALK_ACCEL +#define SPRINT_ACCEL 1.5f * WALK_ACCEL +#define CROUCH_ACCEL 0.5f * WALK_SPEED +#define SLIDE_ACCEL 0.1f * WALK_SPEED + +#define GROUND_FRICTION 10.0f +#define AIR_FRICTION 0.5f #define JUMP_FORCE 10.0f -#define ACCELERATION 8.0f -#define AIR_ACCELERATION ACCELERATION // could change control in air +#define SLIDE_FRICTION 1.5f + +#define SLIDE_CONTROL 8.0f // camera -#define BASE_FOV 70.0f -#define SPRINT_FOV 85.0f +#define BASE_FOV 90.0f +#define SPRINT_FOV 100.0f + #define MAX_PITCH 1.5f + #define FOV_LERP_SPEED 30.0f -#define ROLL_FACTOR 6.0f #define ROLL_LERP_SPEED 20.0f -#define MOUSE_SENSITIVITY 0.003f #define SPRINT_FOV_LERP_SPEED 10.0f +#define CROUCH_LERP_SPEED 10.0f + +#define ROLL_FACTOR 1.0f +#define MOUSE_SENSITIVITY 0.003f diff --git a/main.c b/main.c @@ -6,12 +6,7 @@ #include "config.h" #include "physics.h" - -#define UNGROUND_VELOCITY_EPSILON 0.5f - -float camYaw = 0.0f; -float camPitch = 0.0f; -float camRoll = 0.0f; +#include "player.h" Triangle *BuildTriangleCache(Model *model, int *outCount) { int total = 0; @@ -50,6 +45,23 @@ Triangle *BuildTriangleCache(Model *model, int *outCount) { return tris; } +void UpdatePlayerCamera(Camera3D *c, Player p, float deltaTime) { + static float smoothHeight = 0.0f; + smoothHeight += (p.height - smoothHeight) * CROUCH_LERP_SPEED * deltaTime; + + Vector3 eyePos = {p.pos.x, p.pos.y + smoothHeight, p.pos.z}; + c->position = eyePos; + + Vector3 forward = {cosf(p.rot.y) * sinf(p.rot.z), sinf(p.rot.y), + cosf(p.rot.y) * cosf(p.rot.z)}; + + c->target = Vector3Add(eyePos, forward); + + Vector3 targetUp = Vector3RotateByAxisAngle(UP, forward, p.rot.x); + float upLerpSpeed = 0.1f; + c->up = Vector3Lerp(c->up, targetUp, upLerpSpeed); +} + int main(void) { InitWindow(WIDTH, HEIGHT, "Sphere Character Controller"); DisableCursor(); @@ -67,94 +79,49 @@ int main(void) { int triCount = 0; Triangle *triangles = BuildTriangleCache(&model, &triCount); - Vector3 playerPos = {0, 0, 0}; - Vector3 velocity = {0}; - bool grounded = false; + static Player player; + static enum Input input; + player.vel = Vector3Zero(); + player.pos = Vector3Zero(); + player.mode = MOVE_AIR; + player.height = PLAYER_HEIGHT; + player.radius = PLAYER_RADIUS; while (!WindowShouldClose()) { float dt = GetFrameTime(); + Vector2 md = GetMouseDelta(); - Vector2 mouseDelta = GetMouseDelta(); - camYaw -= mouseDelta.x * MOUSE_SENSITIVITY; - camPitch -= mouseDelta.y * MOUSE_SENSITIVITY; - camPitch = Clamp(camPitch, -MAX_PITCH, MAX_PITCH); - - Vector3 forward = {cosf(camPitch) * sinf(camYaw), sinf(camPitch), - cosf(camPitch) * cosf(camYaw)}; - - Vector3 right = {sinf(camYaw - PI / 2.0f), 0.0f, cosf(camYaw - PI / 2.0f)}; + player.rot.z -= md.x * MOUSE_SENSITIVITY; + player.rot.y -= md.y * MOUSE_SENSITIVITY; + player.rot.y = Clamp(player.rot.y, -MAX_PITCH, MAX_PITCH); - Vector3 input = {0}; + input = 0; if (IsKeyDown(KEY_W)) - input.z += 1; + input |= INPUT_FORWARD; if (IsKeyDown(KEY_S)) - input.z -= 1; - if (IsKeyDown(KEY_D)) - input.x += 1; + input |= INPUT_BACKWARD; if (IsKeyDown(KEY_A)) - input.x -= 1; - - bool sprinting = IsKeyDown(KEY_LEFT_SHIFT); - float speed = sprinting ? SPRINT_SPEED : WALK_SPEED; - - Vector3 targetVel = {0}; - - if (Vector3Length(input) > 0) { - input = Vector3Normalize(input); - Vector3 wishDir = Vector3Add(Vector3Scale(forward, input.z), - Vector3Scale(right, input.x)); - wishDir.y = 0; - wishDir = Vector3Normalize(wishDir); - - targetVel.x = wishDir.x * speed; - targetVel.z = wishDir.z * speed; - } - - float accel = grounded ? ACCELERATION : AIR_ACCELERATION; - velocity.x = Lerp(velocity.x, targetVel.x, accel * dt); - velocity.z = Lerp(velocity.z, targetVel.z, accel * dt); - - if (grounded && IsKeyPressed(KEY_SPACE)) - velocity.y = JUMP_FORCE; + input |= INPUT_LEFT; + if (IsKeyDown(KEY_D)) + input |= INPUT_RIGHT; + if (IsKeyDown(KEY_C)) + input |= INPUT_CROUCH; + if (IsKeyDown(KEY_LEFT_SHIFT)) + input |= INPUT_SPRINT; + if (IsKeyDown(KEY_SPACE)) + input |= INPUT_JUMP; if (IsKeyPressed(KEY_R)) { - playerPos = (Vector3){0, 0, 0}; - velocity = (Vector3){0}; + player.pos = player.vel = Vector3Zero(); } - Physics_MoveCharacter(&playerPos, &velocity, dt, PLAYER_RADIUS, - PLAYER_HEIGHT, triangles, triCount, &grounded); - - Vector3 eyePos = Vector3Add(playerPos, (Vector3){0, PLAYER_HEIGHT, 0}); - - camera.position = eyePos; - camera.target = Vector3Add(eyePos, forward); - - Vector3 forwardDir = - Vector3Normalize(Vector3Subtract(camera.target, camera.position)); - Vector3 rightDir = - Vector3Normalize(Vector3CrossProduct(forwardDir, (Vector3){0, 1, 0})); - - float lateralSpeed = Vector3DotProduct(velocity, rightDir); - float targetRoll = lateralSpeed * ROLL_FACTOR / SPRINT_SPEED; - camRoll = Lerp(camRoll, targetRoll, dt * ROLL_LERP_SPEED); - - camera.fovy = - Lerp(camera.fovy, - sprinting && Vector3Length(input) > 0 ? SPRINT_FOV : BASE_FOV, - dt * SPRINT_FOV_LERP_SPEED); - - float rad = camRoll * DEG2RAD; - - Vector3 rotatedUp = { - UP.x * cosf(rad) + - (forwardDir.y * UP.z - forwardDir.z * UP.y) * sinf(rad), - UP.y * cosf(rad) + - (forwardDir.z * UP.x - forwardDir.x * UP.z) * sinf(rad), - UP.z * cosf(rad) + - (forwardDir.x * UP.y - forwardDir.y * UP.x) * sinf(rad)}; + PlayerUpdate(&player, input, dt); + PlayerApplyFriction(&player, dt); + PlayerApplyAcceleration(&player, input, dt); + player.pos = Vector3Add(player.pos, Vector3Scale(player.vel, dt)); + PlayerCollide(&player, triangles, triCount, dt); - camera.up = Vector3Normalize(rotatedUp); + UpdatePlayerCamera(&camera, player, dt); BeginDrawing(); ClearBackground(RAYWHITE); @@ -164,15 +131,16 @@ int main(void) { EndMode3D(); - DrawText(TextFormat("Pos: %.2f %.2f %.2f", playerPos.x, playerPos.y, - playerPos.z), + DrawText(TextFormat("Pos: %.2f %.2f %.2f", player.pos.x, player.pos.y, + player.pos.z), 10, 10, 20, BLACK); - DrawText( - TextFormat("Vel: %.4f %.4f %.4f", velocity.x, velocity.y, velocity.z), - 10, 30, 20, BLACK); + DrawText(TextFormat("Vel: %.4f %.4f %.4f", player.vel.x, player.vel.y, + player.vel.z), + 10, 30, 20, BLACK); - DrawText(TextFormat("Grounded: %d", grounded), 10, 50, 20, BLACK); + DrawText(TextFormat("Grounded: %b", player.mode == MOVE_GROUND), 10, 50, 20, + BLACK); EndDrawing(); } diff --git a/physics.c b/physics.c @@ -1,7 +1,9 @@ -#include "physics.h" -#include "config.h" #include <float.h> -#include <raymath.h> +#include <raylib.h> +#include <stdio.h> + +#include "config.h" +#include "physics.h" #define SQR(x) ((x) * (x)) @@ -171,8 +173,8 @@ bool SphereCast(Vector3 pos, Vector3 dir, float dist, float radius, /* * Resolves capsule overlaps. Pos is the center of the BOTTOM sphere. */ -void ResolveOverlaps(Vector3 *pos, float height, float radius, Triangle *tris, - int triCount, bool *hitFloor) { +void ResolveOverlaps(Vector3 *pos, Vector3 *vel, float height, float radius, + Triangle *tris, int triCount, GroundState *state) { float rSq = SQR(radius); /* Define capsule axis relative to pos */ Vector3 capsTopOffset = {0, height, 0}; @@ -196,20 +198,22 @@ void ResolveOverlaps(Vector3 *pos, float height, float radius, Triangle *tris, continue; /* - * Find the point on the triangle closest to the capsule's central - * segment Heuristic: Check closest point from the capsule's true center + * Find the point on the triangle closest to the + * capsule's central segment Heuristic: Check closest + * point from the capsule's true center */ Vector3 midPoint = Vector3Scale(Vector3Add(pBottom, pTop), 0.5f); Vector3 closestOnTri = ClosestPointOnTriangle(midPoint, t->a, t->b, t->c); - /* Find the point on the capsule segment closest to that triangle point */ + /* Find the point on the capsule segment closest to that + * triangle point */ Vector3 closestOnSegment = ClosestPointOnSegment(closestOnTri, pBottom, pTop); /* - * Re-refine the point on the triangle based on the specific segment - * point This helps when the capsule is tall and the triangle is near the - * feet or head + * Re-refine the point on the triangle based on the + * specific segment point This helps when the capsule is + * tall and the triangle is near the feet or head */ closestOnTri = ClosestPointOnTriangle(closestOnSegment, t->a, t->b, t->c); @@ -217,29 +221,60 @@ void ResolveOverlaps(Vector3 *pos, float height, float radius, Triangle *tris, Vector3 pushVec = Vector3Subtract(closestOnSegment, closestOnTri); float d2 = Vector3LengthSqr(pushVec); - if (d2 <= 0 || d2 >= rSq + EPSILON) + // Added check for very small d2 to prevent division by zero in + // normalization + if (d2 <= EPSILON || d2 >= rSq + EPSILON) continue; float d = sqrtf(d2); + // Robust normalization Vector3 n = Vector3Scale(pushVec, 1.0f / d); - float penetration = radius - d + SKIN; /* - * If it is ground, only push UP. - * We convert the normal penetration into vertical penetration: - * dist_vertical = dist_normal / cos(theta) - * n.y is actually cos(theta) between Up and Normal. + * TODO: ALL slopes should be treated as "steep/slippery" when + * sliding. Gotta figure out where to handle that logic as collision + * doesn't receive player state. */ if (n.y > GROUND_NORMAL_Y) { - pos->y += penetration / n.y; - *hitFloor = true; + /* GROUND CASE */ + *state = GROUND_GROUNDED; + + /* + Apply Vertical Depenetration logic. + Slope definition: penetration_vertical = penetration_normal / + cos(theta) n.y is cos(theta). + */ + float verticalPush = penetration / n.y; + + // Safety: Don't push up if the slope is too crazy (math breakdown) + if (verticalPush < radius * 2.0f) { + pos->y += verticalPush; + } else { + // Fallback to normal push if vertical push is astronomical + *pos = Vector3Add(*pos, Vector3Scale(n, penetration)); + } + + /* Only kill velocity if we are falling into the ground */ + /* Allow jumping UP a slope. */ + if (vel->y < 0) + vel->y = 0; } else { + /* WALL/CEILING CASE */ + + /* 1. Frictionless slide (remove velocity component into wall) */ + float vn = Vector3DotProduct(*vel, n); + if (vn < 0.0f) { + *vel = Vector3Subtract(*vel, Vector3Scale(n, vn)); + } + + /* 2. Push out along normal */ *pos = Vector3Add(*pos, Vector3Scale(n, penetration)); - pBottom = *pos; - pTop = Vector3Add(pBottom, capsTopOffset); } + pBottom = *pos; + pTop = Vector3Add(pBottom, capsTopOffset); + collisionFound = true; } @@ -253,7 +288,7 @@ void ResolveOverlaps(Vector3 *pos, float height, float radius, Triangle *tris, * micro air time while running down slopes or climbing over the top. */ void SnapToGround(Vector3 *pos, float radius, Triangle *tris, int triCount, - bool *touchingGround) { + GroundState *state) { HitInfo hit; if (!SphereCast(*pos, DOWN, GROUND_SNAP_DIST, radius, tris, triCount, &hit)) return; @@ -262,34 +297,5 @@ void SnapToGround(Vector3 *pos, float radius, Triangle *tris, int triCount, return; *pos = Vector3Add(*pos, Vector3Scale(DOWN, hit.distance)); - *touchingGround = true; -} - -void Physics_MoveCharacter(Vector3 *pos, Vector3 *velocity, float dt, - float radius, float height, Triangle *tris, - int triCount, bool *groundedOut) { - static float groundedTimer = 0.0f; - - Vector3 moveStep = Vector3Scale(*velocity, dt); - *pos = Vector3Add(*pos, moveStep); - - groundedTimer -= dt; - if (groundedTimer < 0) - groundedTimer = 0; - bool effectivelyGrounded = (groundedTimer > 0); - - bool touchingGround = false; - ResolveOverlaps(pos, height, radius, tris, triCount, &touchingGround); - - if (effectivelyGrounded && velocity->y <= 0) { - SnapToGround(pos, radius, tris, triCount, &touchingGround); - velocity->y = 0; - } else { - velocity->y -= GRAVITY * dt; - } - - if (touchingGround) - groundedTimer = COYOTE_TIME; - - *groundedOut = effectivelyGrounded; + *state = GROUND_GROUNDED; } diff --git a/physics.h b/physics.h @@ -1,9 +1,12 @@ #pragma once +#include <raylib.h> #include <raymath.h> -#include <stdbool.h> + #define MAX_ITERS 3 +typedef enum { GROUND_AIRBORNE = 0, GROUND_GROUNDED } GroundState; + typedef struct { bool hit; float distance; @@ -25,14 +28,10 @@ bool SphereCastTriangle(Vector3 pos, Vector3 dir, float radius, float dist, bool SphereCast(Vector3 pos, Vector3 dir, float dist, float radius, Triangle *tris, int triCount, HitInfo *outHit); -void ResolveOverlaps(Vector3 *pos, float radius, float height, Triangle *tris, - int triCount, bool *hitFloor); - -bool CheckGrounded(Vector3 pos, float radius, Triangle *tris, int triCount); +void ResolveOverlaps(Vector3 *pos, Vector3 *vel, float radius, float height, + Triangle *tris, int triCount, GroundState *state); void SnapToGround(Vector3 *pos, float radius, Triangle *tris, int triCount, - bool *hitFloor); + GroundState *state); -void Physics_MoveCharacter(Vector3 *pos, Vector3 *velocity, float dt, - float radius, float height, Triangle *tris, - int triCount, bool *groundedOut); +void ApplyGravity(Vector3 *velocity, float dt); diff --git a/player.c b/player.c @@ -0,0 +1,195 @@ +#include <float.h> +#include <raylib.h> +#include <raymath.h> +#include <stdint.h> +#include <stdio.h> + +#include "config.h" +#include "physics.h" +#include "player.h" + +static PlayerIntent GetIntent(enum Input i) { + return (PlayerIntent){ + .move = i & (INPUT_FORWARD | INPUT_BACKWARD | INPUT_LEFT | INPUT_RIGHT), + .sprint = i & INPUT_SPRINT, + .crouch = i & INPUT_CROUCH, + .jump = i & INPUT_JUMP}; +} + +void PlayerApplyAcceleration(Player *p, enum Input i, float dt) { + Vector3 forward = {cosf(p->rot.y) * sinf(p->rot.z), sinf(p->rot.y), + cosf(p->rot.x) * cosf(p->rot.z)}; + + Vector3 right = {sinf(p->rot.z - PI / 2.0f), 0.0f, + cosf(p->rot.z - PI / 2.0f)}; + + Vector3 input = Vector3Zero(); + if (i & INPUT_FORWARD) + input.z += 1; + if (i & INPUT_BACKWARD) + input.z -= 1; + if (i & INPUT_LEFT) + input.x -= 1; + if (i & INPUT_RIGHT) + input.x += 1; + + if (Vector3Length(input) == 0.0f) + return; + + Vector3 wishDir = + Vector3Add(Vector3Scale(forward, input.z), Vector3Scale(right, input.x)); + + wishDir.y = 0.0f; + wishDir = Vector3Normalize(wishDir); + + float currentSpeed = Vector3DotProduct(p->vel, wishDir); + + float addSpeed = p->maxSpeed - currentSpeed; + if (addSpeed <= 0.0f) + return; + + float accelSpeed = p->accel * p->maxSpeed * dt; + if (accelSpeed > addSpeed) + accelSpeed = addSpeed; + + p->vel = Vector3Add(p->vel, Vector3Scale(wishDir, accelSpeed)); +} + +void PlayerCollide(Player *p, Triangle *tris, int triCount, float dt) { + GroundState groundState = GROUND_AIRBORNE; + ResolveOverlaps(&p->pos, &p->vel, p->height, p->radius, tris, triCount, + &groundState); + + if (p->mode != MOVE_AIR && p->vel.y < 0) { + SnapToGround(&p->pos, p->radius, tris, triCount, &groundState); + } else { + p->vel.y -= GRAVITY * dt; + } + + if (groundState == GROUND_GROUNDED) + p->groundedTimer = COYOTE_TIME; +} + +/* + * This function does nothing but set/unset player states + */ +void PlayerUpdateMode(Player *p, PlayerIntent in) { + bool grounded = p->groundedTimer > 0; + + switch (p->mode) { + + case MOVE_GROUND: + if (!grounded) { + p->mode = MOVE_AIR; + break; + } + + if (in.move && in.sprint && in.crouch && Vector3Length(p->vel) > 4.0f) { + p->mode = MOVE_SLIDE; + break; + } + + if (in.crouch) { + p->mode = MOVE_CROUCH; + break; + } + + if (in.jump) { + p->mode = MOVE_AIR; + p->vel.y += JUMP_FORCE; + break; + } + break; + + case MOVE_CROUCH: + if (!in.crouch) { + p->mode = MOVE_GROUND; + break; + } + break; + + case MOVE_SLIDE: + if (!in.crouch || Vector3Length(p->vel) < 3.0f) { + p->mode = MOVE_GROUND; + break; + } + break; + + case MOVE_AIR: + if (grounded && p->vel.y < 0) { + p->mode = MOVE_GROUND; + break; + } + break; + } +} + +void PlayerHandleMovement(Player *p, PlayerIntent in) { + p->rot.x = 0; + + switch (p->mode) { + + case MOVE_GROUND: + printf("STATE GROUND\n"); + p->maxSpeed = in.sprint ? SPRINT_SPEED : WALK_SPEED; + p->accel = in.sprint ? SPRINT_ACCEL : WALK_ACCEL; + p->height = in.crouch ? PLAYER_HEIGHT / 2 : PLAYER_HEIGHT; + break; + + case MOVE_CROUCH: + printf("STATE CROUCH\n"); + p->maxSpeed = CROUCH_SPEED; + p->accel = CROUCH_ACCEL; + p->height = PLAYER_HEIGHT / 2; + break; + + case MOVE_SLIDE: + printf("STATE SLIDE\n"); + p->maxSpeed = SLIDE_SPEED; + p->accel = SLIDE_ACCEL; + p->height = PLAYER_HEIGHT / 2; + p->rot.x = PI / 12; + break; + + case MOVE_AIR: + printf("STATE AIR\n"); + p->maxSpeed = AIR_SPEED; + p->accel = AIR_ACCEL; + break; + } +} + +void PlayerUpdate(Player *p, enum Input i, float dt) { + p->groundedTimer = Clamp(p->groundedTimer - dt, 0, COYOTE_TIME); + + PlayerIntent intent = GetIntent(i); + + PlayerUpdateMode(p, intent); + PlayerHandleMovement(p, intent); +} + +void PlayerApplyFriction(Player *p, float dt) { + float friction = AIR_FRICTION; + if (p->mode == MOVE_GROUND || p->mode == MOVE_CROUCH) + friction = GROUND_FRICTION; + + Vector3 horiz = {p->vel.x, 0, p->vel.z}; + float speed = Vector3Length(horiz); + + if (speed < STOP_SPEED) { + p->vel = (Vector3){0, p->vel.y, 0}; + return; + } + + float control = speed < STOP_SPEED ? STOP_SPEED : speed; + float drop = control * friction * dt; + + float newSpeed = speed - drop; + if (newSpeed < 0) + newSpeed = 0; + + horiz = Vector3Scale(horiz, newSpeed / speed); + + p->vel.x = horiz.x; + p->vel.z = horiz.z; +} diff --git a/player.h b/player.h @@ -0,0 +1,57 @@ +#pragma once + +#include "physics.h" +#include <raylib.h> + +enum Input { + INPUT_FORWARD = 1 << 0, + INPUT_BACKWARD = 1 << 1, + INPUT_LEFT = 1 << 2, + INPUT_RIGHT = 1 << 3, + INPUT_JUMP = 1 << 4, + INPUT_SPRINT = 1 << 5, + INPUT_CROUCH = 1 << 6, +}; + +typedef enum { MOVE_GROUND, MOVE_AIR, MOVE_CROUCH, MOVE_SLIDE } MoveMode; + +typedef struct { + bool move; + bool sprint; + bool crouch; + bool jump; +} PlayerIntent; + +/* + * NOTE: Players will eventually be serialized and networked so everything + * in here should be strictly relevant for interactivity on other clients. + * For example camera data like fov shouldn't be stored here as it's either + * irrelevant data on remote clients or it can be derived from other details + * like velocity or player state. + */ +typedef struct { + float height; + float radius; + + float maxSpeed; + float accel; + Vector3 vel; + Vector3 rot; + Vector3 pos; + + MoveMode mode; + + float groundedTimer; +} Player; + +void PlayerCollide(Player *p, Triangle *tris, int triCount, float dt); + +void PlayerUpdateState(Player *p, enum Input i, float dt); + +void PlayerApplyAcceleration(Player *p, enum Input i, float dt); + +void PlayerUpdate(Player *p, enum Input i, float dt); + +void PlayerHandleMovement(Player *p, PlayerIntent in); + +void PlayerApplyFriction(Player *p, float dt);