66 lines
2.1 KiB
TypeScript
66 lines
2.1 KiB
TypeScript
export type PlanarVector = readonly [x: number, z: number];
|
|
export const DEFAULT_CAMERA_PITCH = 0.32;
|
|
|
|
export function clamp(value: number, minimum: number, maximum: number): number {
|
|
return Math.min(maximum, Math.max(minimum, value));
|
|
}
|
|
|
|
export function applyRadialDeadzone(
|
|
x: number,
|
|
y: number,
|
|
deadzone = 0.18,
|
|
): PlanarVector {
|
|
if (!Number.isFinite(x) || !Number.isFinite(y)) return [0, 0];
|
|
const magnitude = Math.hypot(x, y);
|
|
if (magnitude <= deadzone || magnitude === 0) return [0, 0];
|
|
|
|
const remappedMagnitude = clamp((magnitude - deadzone) / (1 - deadzone), 0, 1);
|
|
const scale = remappedMagnitude / magnitude;
|
|
return [x * scale, y * scale];
|
|
}
|
|
|
|
export function normalizePlanar(x: number, z: number): PlanarVector {
|
|
if (!Number.isFinite(x) || !Number.isFinite(z)) return [0, 0];
|
|
const magnitude = Math.hypot(x, z);
|
|
if (magnitude <= 1) return [x, z];
|
|
return [x / magnitude, z / magnitude];
|
|
}
|
|
|
|
/** Converts input into world movement for a Three.js +Z-forward player. */
|
|
export function cameraRelativeMovement(
|
|
moveX: number,
|
|
moveForward: number,
|
|
yaw: number,
|
|
): PlanarVector {
|
|
const [x, forward] = normalizePlanar(moveX, moveForward);
|
|
const safeYaw = Number.isFinite(yaw) ? yaw : 0;
|
|
const sinYaw = Math.sin(safeYaw);
|
|
const cosYaw = Math.cos(safeYaw);
|
|
return [
|
|
-x * cosYaw + forward * sinYaw,
|
|
x * sinYaw + forward * cosYaw,
|
|
];
|
|
}
|
|
|
|
/** Applies device-right look using the screen-right basis of the +Z camera rig. */
|
|
export function cameraYawAfterHorizontalLook(yaw: number, horizontalLook: number): number {
|
|
const safeYaw = Number.isFinite(yaw) ? yaw : 0;
|
|
const safeLook = Number.isFinite(horizontalLook) ? horizontalLook : 0;
|
|
return wrapAngle(safeYaw - safeLook);
|
|
}
|
|
|
|
export function clampCameraPitch(pitch: number): number {
|
|
return Number.isFinite(pitch) ? clamp(pitch, -0.18, 0.88) : DEFAULT_CAMERA_PITCH;
|
|
}
|
|
|
|
export function wrapAngle(angle: number): number {
|
|
return Number.isFinite(angle) ? Math.atan2(Math.sin(angle), Math.cos(angle)) : 0;
|
|
}
|
|
|
|
export function planarDistance(
|
|
from: readonly [number, number, number],
|
|
to: readonly [number, number, number],
|
|
): number {
|
|
return Math.hypot(to[0] - from[0], to[2] - from[2]);
|
|
}
|