Add unit tests for player locomotion and update collision handling logic

This commit is contained in:
2026-04-11 19:54:24 +02:00
parent 17ee177ea2
commit cb24c0afe8
4 changed files with 250 additions and 7 deletions

View File

@@ -22,7 +22,6 @@ const JUMP_SPEED = 7.2;
const SPRINT_SPEED_MULTIPLIER = 1.65;
const CROUCH_SPEED_MULTIPLIER = 0.45;
const GROUND_PROBE_DISTANCE = 0.12;
const GROUND_STICK_DISTANCE = 0.08;
const VERTICAL_ASCENT_EPSILON = 1e-4;
const IDLE_SPEED_EPSILON = 0.05;
@@ -214,6 +213,25 @@ function computePlanarMotion(
};
}
function alignPlanarMotionToGround(
planarMotion: Vec3,
groundNormal: Vec3 | null
): Vec3 {
if (groundNormal === null || groundNormal.y <= VERTICAL_ASCENT_EPSILON) {
return planarMotion;
}
return {
x: planarMotion.x,
y:
-(
groundNormal.x * planarMotion.x +
groundNormal.z * planarMotion.z
) / groundNormal.y,
z: planarMotion.z
};
}
function resolveContactState(
resolvedMotion: ResolvedPlayerMotion,
groundProbe: PlayerGroundProbeResult,
@@ -319,6 +337,13 @@ export function stepPlayerLocomotion(
currentlyGrounded &&
!currentVolumeState.inWater &&
activeShape.mode !== "none";
const groundedPlanarMotion =
currentlyGrounded && !jumpTriggered
? alignPlanarMotionToGround(
planarMotion.motion,
currentGroundProbe.normal
)
: planarMotion.motion;
let verticalVelocity = options.verticalVelocity;
let verticalDisplacement = 0;
@@ -330,7 +355,7 @@ export function stepPlayerLocomotion(
verticalDisplacement = verticalVelocity * options.dt;
} else if (currentlyGrounded) {
verticalVelocity = 0;
verticalDisplacement = options.dt > 0 ? -GROUND_STICK_DISTANCE : 0;
verticalDisplacement = 0;
} else {
verticalVelocity -= GRAVITY * options.dt;
verticalDisplacement = verticalVelocity * options.dt;
@@ -339,9 +364,9 @@ export function stepPlayerLocomotion(
const resolvedMotion = options.resolveMotion(
options.feetPosition,
{
x: planarMotion.motion.x,
y: verticalDisplacement,
z: planarMotion.motion.z
x: groundedPlanarMotion.x,
y: verticalDisplacement + groundedPlanarMotion.y,
z: groundedPlanarMotion.z
},
activeShape
);

View File

@@ -22,7 +22,8 @@ const CHARACTER_CONTROLLER_OFFSET = 0.01;
const CHARACTER_CONTROLLER_SNAP_TO_GROUND_DISTANCE = 0.2;
const COLLISION_EPSILON = 1e-5;
const CAMERA_COLLISION_EPSILON = 1e-3;
const GROUND_NORMAL_Y_THRESHOLD = 0.45;
const MAX_WALKABLE_SLOPE_RADIANS = Math.PI * 0.25;
const GROUND_NORMAL_Y_THRESHOLD = Math.cos(MAX_WALKABLE_SLOPE_RADIANS);
const IDENTITY_ROTATION = {
x: 0,
y: 0,
@@ -352,7 +353,7 @@ export class RapierCollisionWorld {
CHARACTER_CONTROLLER_SNAP_TO_GROUND_DISTANCE
);
characterController.enableAutostep(0.35, 0.15, false);
characterController.setMaxSlopeClimbAngle(Math.PI * 0.45);
characterController.setMaxSlopeClimbAngle(MAX_WALKABLE_SLOPE_RADIANS);
characterController.setMinSlopeSlideAngle(Math.PI * 0.5);
}