spherecast

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

commit 2a172e5f13f6c512a989033eadf080139e738ab2
parent 0c1d771d0aac601d906647dee11e80b5c0c5b9d4
Author: amrfti <andrew@kloet.net>
Date:   Sun, 28 Dec 2025 15:38:50 -0500

fix micro sliding on slopes

Diffstat:
Mconfig.h | 4++--
Mmain.c | 5+++--
Mphysics.c | 54++++++++++++++++++++++++++++++++++++------------------
3 files changed, 41 insertions(+), 22 deletions(-)

diff --git a/config.h b/config.h @@ -29,10 +29,10 @@ // camera #define BASE_FOV 70.0f -#define SPRINT_FOV 80.0f +#define SPRINT_FOV 85.0f #define MAX_PITCH 1.5f #define FOV_LERP_SPEED 30.0f -#define ROLL_FACTOR 5.0f +#define ROLL_FACTOR 6.0f #define ROLL_LERP_SPEED 20.0f #define MOUSE_SENSITIVITY 0.003f #define SPRINT_FOV_LERP_SPEED 10.0f diff --git a/main.c b/main.c @@ -111,8 +111,9 @@ int main(void) { targetVel.z = wishDir.z * speed; } - velocity.x = targetVel.x; - velocity.z = targetVel.z; + 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; diff --git a/physics.c b/physics.c @@ -5,6 +5,9 @@ #define SQR(x) ((x) * (x)) +/* + * Check for if point p is within the bounds of triangle t with radius r + */ static inline bool TriangleLikelyContains(Vector3 p, float r, const Triangle *t) { float r2 = r + SKIN; @@ -68,7 +71,7 @@ bool SphereCastTriangle(Vector3 pos, Vector3 dir, float radius, float maxDist, const Triangle *tri, HitInfo *hit) { Vector3 n = tri->normal; - // 1. Intersect with the infinite plane + // intersect with the infinite plane float denom = Vector3DotProduct(n, dir); if (denom >= -EPSILON) @@ -80,16 +83,16 @@ bool SphereCastTriangle(Vector3 pos, Vector3 dir, float radius, float maxDist, if (tPlane > maxDist) return false; - // 2. Determine where the sphere touches the plane + // determine where the sphere touches the plane Vector3 planeIntersectionPoint = Vector3Add(pos, Vector3Scale(dir, tPlane)); Vector3 centerOnPlane = Vector3Subtract(planeIntersectionPoint, Vector3Scale(n, radius)); - // 3. Find closest point on the actual triangle + // find closest point on the actual triangle Vector3 closestPt = ClosestPointOnTriangle(centerOnPlane, tri->a, tri->b, tri->c); - // 4. Check distance + // check distance float distSq = Vector3DistanceSqr(centerOnPlane, closestPt); // CASE A: Hit the FACE @@ -160,6 +163,11 @@ void ResolveOverlaps(Vector3 *pos, float radius, Triangle *tris, int triCount, for (int i = 0; i < triCount; i++) { Triangle *t = &tris[i]; + /* + * Broad phase AABB check. If the position isn't even + * within the bounding box of triangle t then skip it + * TODO: replace with O(1) BVH + */ if (!TriangleLikelyContains(*pos, radius, t)) continue; @@ -167,22 +175,27 @@ void ResolveOverlaps(Vector3 *pos, float radius, Triangle *tris, int triCount, Vector3 pushVec = Vector3Subtract(*pos, closest); float d2 = Vector3LengthSqr(pushVec); - // allow for floating point error at the exact surface boundary - if (d2 > 0 && d2 < rSq + EPSILON) { - float d = sqrtf(d2); - Vector3 n = Vector3Scale(pushVec, 1.0f / d); + if (d2 <= 0 || d2 >= rSq + EPSILON) + continue; - // push out - float penetration = radius - d + SKIN; + float d = sqrtf(d2); + 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. + */ + if (n.y > GROUND_NORMAL_Y) { + pos->y += penetration / n.y; + *hitFloor = true; + } else *pos = Vector3Add(*pos, Vector3Scale(n, penetration)); - if (n.y > GROUND_NORMAL_Y) - *hitFloor = true; - else if (t->normal.y > GROUND_NORMAL_Y && n.y > EPSILON) - *hitFloor = true; - - collisionFound = true; - } + collisionFound = true; } if (!collisionFound) @@ -190,6 +203,10 @@ void ResolveOverlaps(Vector3 *pos, float radius, Triangle *tris, int triCount, } } +/* + * Snaps the player to the ground, this is useful so we don't get + * micro air time while running down slopes or climbing over the top. + */ void SnapToGround(Vector3 *pos, float radius, Triangle *tris, int triCount, bool *touchingGround) { HitInfo hit; @@ -222,8 +239,9 @@ void Physics_MoveCharacter(Vector3 *pos, Vector3 *velocity, float dt, if (effectivelyGrounded && velocity->y <= 0) { SnapToGround(pos, radius, tris, triCount, &touchingGround); velocity->y = 0; - } else + } else { velocity->y -= GRAVITY * dt; + } if (touchingGround) groundedTimer = COYOTE_TIME;