import type { Vec3 } from "../math/Vec3" /** A 2.5D box: horizontal footprint plus top height. */ export type BoxCollider = { shape: "box" minX: number maxX: number minZ: number maxZ: number top: number standable: boolean } /** A 2.5D circle: horizontal footprint plus top height. */ export type CircleCollider = { shape: "circle" x: number z: number radius: number top: number standable: boolean } /** Finite engine collision capabilities, not game-content identity. */ export type Collider = BoxCollider | CircleCollider export namespace Collider { export function centerX(collider: Collider): number { return collider.shape === "circle" ? collider.x : (collider.minX + collider.maxX) * 0.5 } export function centerZ(collider: Collider): number { return collider.shape === "circle" ? collider.z : (collider.minZ + collider.maxZ) * 0.5 } export function contains(collider: Collider, x: number, z: number): boolean { if (collider.shape === "circle") { const dx = x - collider.x const dz = z - collider.z return dx * dx + dz * dz <= collider.radius * collider.radius } return ( x >= collider.minX && x <= collider.maxX && z >= collider.minZ && z <= collider.maxZ ) } /** Push a horizontal player circle out of one collider. */ export function pushOut( collider: Collider, position: Vec3, radius: number, ): void { if (collider.shape === "circle") { pushFromCircle(position, radius, collider) return } pushFromBox(position, radius, collider) } function pushFromBox(position: Vec3, radius: number, box: BoxCollider): void { const cx = Math.max(box.minX, Math.min(box.maxX, position.x)) const cz = Math.max(box.minZ, Math.min(box.maxZ, position.z)) const dx = position.x - cx const dz = position.z - cz const distanceSquared = dx * dx + dz * dz if (distanceSquared >= radius * radius) { return } if (distanceSquared > 1e-6) { const distance = Math.sqrt(distanceSquared) const push = (radius - distance) / distance position.x += dx * push position.z += dz * push return } const left = position.x - box.minX const right = box.maxX - position.x const near = position.z - box.minZ const far = box.maxZ - position.z const nearest = Math.min(left, right, near, far) if (nearest === left) { position.x = box.minX - radius } else if (nearest === right) { position.x = box.maxX + radius } else if (nearest === near) { position.z = box.minZ - radius } else { position.z = box.maxZ + radius } } function pushFromCircle( position: Vec3, radius: number, circle: CircleCollider, ): void { const dx = position.x - circle.x const dz = position.z - circle.z const reach = radius + circle.radius const distanceSquared = dx * dx + dz * dz if (distanceSquared >= reach * reach || distanceSquared < 1e-6) { return } const distance = Math.sqrt(distanceSquared) const push = (reach - distance) / distance position.x += dx * push position.z += dz * push } }