import type { KinematicCharacterController } from "@dimforge/rapier3d-compat"; import { PLAYER_GROUND_MIN_NORMAL_Y } from "./playerJump"; export const PLAYER_GRAVITY = -18; export const PLAYER_CHARACTER_CONTROLLER_OFFSET = 0.02; export const PLAYER_STEP_HEIGHT = 0.55; export const PLAYER_STEP_MIN_WIDTH = 0.15; export const PLAYER_GROUND_SNAP_DISTANCE = PLAYER_STEP_HEIGHT; export const PLAYER_GROUND_STICK_VELOCITY = -0.5; export const PLAYER_MAX_FRAME_DELTA_SECONDS = 0.05; export const PLAYER_TERMINAL_FALL_VELOCITY = -32; /** * Configures Rapier's kinematic controller for MMO-style traversal. Ordinary * stairs and curb-sized seams are walkable, while taller obstacles remain * blocking geometry and still require a real jump or another route. */ export function configurePlayerCharacterController( controller: KinematicCharacterController, ): KinematicCharacterController { controller.setUp({ x: 0, y: 1, z: 0 }); controller.setSlideEnabled(true); controller.enableAutostep(PLAYER_STEP_HEIGHT, PLAYER_STEP_MIN_WIDTH, false); controller.enableSnapToGround(PLAYER_GROUND_SNAP_DISTANCE); controller.setMaxSlopeClimbAngle(Math.acos(PLAYER_GROUND_MIN_NORMAL_Y)); return controller; } export function playerFrameDeltaSeconds(deltaSeconds: number): number { if (!Number.isFinite(deltaSeconds) || deltaSeconds <= 0) return 0; return Math.min(deltaSeconds, PLAYER_MAX_FRAME_DELTA_SECONDS); } export function airbornePlayerVerticalVelocity( currentVelocity: number, deltaSeconds: number, ): number { const velocity = Number.isFinite(currentVelocity) ? currentVelocity : 0; const delta = playerFrameDeltaSeconds(deltaSeconds); return Math.max( PLAYER_TERMINAL_FALL_VELOCITY, velocity + PLAYER_GRAVITY * delta, ); }