refactor: move world concepts into engine
This commit is contained in:
parent
eeedcb8e48
commit
2d15c7ab8d
52 changed files with 3298 additions and 1558 deletions
116
engine/world/Collider.ts
Normal file
116
engine/world/Collider.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue