meat/engine/world/Collider.ts

116 lines
3.1 KiB
TypeScript

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
}
}