2026-08-08 14:15:32 +02:00
|
|
|
import type { Terrain } from "../Terrain"
|
|
|
|
|
import type { Vec3 } from "../../engine/math/Vec3"
|
|
|
|
|
import type { Mesh } from "../../engine/scene/Mesh"
|
|
|
|
|
import type { Entity } from "../../engine/scene/Actor"
|
2026-08-07 19:11:43 +02:00
|
|
|
import { frog } from "./mobs/Frog"
|
|
|
|
|
import { bee } from "./mobs/Bee"
|
|
|
|
|
import { robin } from "./mobs/Robin"
|
2026-08-07 12:49:13 +02:00
|
|
|
|
|
|
|
|
/** A roaming creature drawn as a moving low-poly mesh (unlike the static baked
|
2026-08-07 19:11:43 +02:00
|
|
|
* world). Each kind is an `Entity` definition (geometry + behavior + bounds) living
|
|
|
|
|
* in its own module under `mobs/`; this file just assembles them into a registry
|
|
|
|
|
* and exposes a thin per-kind dispatch. Adding a kind = add a `mobs/<Kind>.ts` +
|
|
|
|
|
* one entry in `MOB_KINDS`/`DEFS`.
|
2026-08-07 12:49:13 +02:00
|
|
|
*
|
2026-08-07 19:11:43 +02:00
|
|
|
* A mob's geometry is a **canonical local-space mesh** built once per kind (front =
|
|
|
|
|
* +Z, frog/robin feet / bee body at the origin); the live `position`/`heading`/
|
|
|
|
|
* `scale` are turned into a per-frame model matrix by the renderer. All wander
|
|
|
|
|
* state lives on the instance so `update` is a pure stepping function of the mob +
|
|
|
|
|
* dt (deterministic via the evolving `seed`), which keeps the sim on the main
|
|
|
|
|
* thread and cloneable-free. */
|
2026-08-07 13:37:30 +02:00
|
|
|
export type MobKind = "frog" | "bee" | "robin"
|
2026-08-07 12:49:13 +02:00
|
|
|
|
|
|
|
|
export type Mob = {
|
|
|
|
|
kind: MobKind
|
|
|
|
|
/** Leash anchor (where it was scattered); wandering is pulled back toward it. */
|
|
|
|
|
home: Vec3
|
2026-08-07 13:37:30 +02:00
|
|
|
/** Live feet-center (frog/robin) / body-center (bee), advanced each frame. */
|
2026-08-07 12:49:13 +02:00
|
|
|
position: Vec3
|
|
|
|
|
/** Facing yaw; the mesh's front is local +Z, so world dir = (sin h, 0, cos h). */
|
|
|
|
|
heading: number
|
|
|
|
|
/** Per-instance size multiplier. */
|
|
|
|
|
scale: number
|
|
|
|
|
/** Evolving RNG state (mutated by `update`) -- keeps the sim deterministic. */
|
|
|
|
|
seed: number
|
2026-08-07 13:37:30 +02:00
|
|
|
/** Horizontal velocity (frog/robin: mid-hop or -flight; bee: cruise). */
|
2026-08-07 12:49:13 +02:00
|
|
|
vx: number
|
|
|
|
|
vz: number
|
2026-08-07 13:37:30 +02:00
|
|
|
/** Vertical velocity (frog/robin ballistic hop/flight; bee stays 0, uses a bob). */
|
2026-08-07 12:49:13 +02:00
|
|
|
vy: number
|
2026-08-07 13:37:30 +02:00
|
|
|
/** Countdown to the next decision (frog/robin: next hop; bee: next heading change). */
|
2026-08-07 12:49:13 +02:00
|
|
|
timer: number
|
2026-08-07 13:37:30 +02:00
|
|
|
/** Per-kind scratch clock: the bee's hover-bob phase; the robin's remaining
|
|
|
|
|
* powered-flight cruise time (>0 while gliding between perches). */
|
2026-08-07 12:49:13 +02:00
|
|
|
phase: number
|
2026-08-07 13:37:30 +02:00
|
|
|
/** Frog/robin: resting on the ground vs airborne (a hop or a flight). */
|
2026-08-07 12:49:13 +02:00
|
|
|
grounded: boolean
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-07 19:11:43 +02:00
|
|
|
/** Canonical kind order. **The index is the id packed into the mob SAB** (see
|
|
|
|
|
* renderer/worker), so this order must be identical in every context and must not
|
|
|
|
|
* change under existing kinds -- `mobs.test.ts` guards it. Append new kinds. */
|
|
|
|
|
export const MOB_KINDS: MobKind[] = ["frog", "bee", "robin"]
|
|
|
|
|
|
|
|
|
|
/** The per-kind `Entity` definitions, one module each. Imported (not cloned) into
|
|
|
|
|
* whatever context uses it, so it works the same on the main thread and in workers. */
|
|
|
|
|
const DEFS: Record<MobKind, Entity<Mob, Terrain>> = { frog, bee, robin }
|
2026-08-07 12:49:13 +02:00
|
|
|
|
|
|
|
|
export namespace Mob {
|
2026-08-07 19:11:43 +02:00
|
|
|
/** The definition for a kind (geometry, behavior, bounds). */
|
|
|
|
|
export function def(kind: MobKind): Entity<Mob, Terrain> {
|
|
|
|
|
return DEFS[kind]
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-07 12:49:13 +02:00
|
|
|
/** Advance one mob by `dt` seconds, sampling `terrain` for ground height. */
|
|
|
|
|
export function update(mob: Mob, dt: number, terrain: Terrain): void {
|
2026-08-07 19:11:43 +02:00
|
|
|
DEFS[mob.kind].update(mob, dt, terrain)
|
2026-08-07 12:49:13 +02:00
|
|
|
}
|
|
|
|
|
|
2026-08-07 19:11:43 +02:00
|
|
|
/** Append the canonical local-space mesh for `kind` into `mesh` (once per kind at
|
|
|
|
|
* load; every instance shares it, differing only by transform). */
|
2026-08-07 12:49:13 +02:00
|
|
|
export function build(kind: MobKind, mesh: Mesh): void {
|
2026-08-07 19:11:43 +02:00
|
|
|
DEFS[kind].build(mesh)
|
2026-08-07 12:49:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Local bounding radius (pre-scale), for building the per-frame cull AABB. */
|
|
|
|
|
export function boundingRadius(kind: MobKind): number {
|
2026-08-07 19:11:43 +02:00
|
|
|
return DEFS[kind].boundingRadius
|
2026-08-07 12:49:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Local body height (pre-scale), for the top of the stand-on collider. */
|
|
|
|
|
export function bodyHeight(kind: MobKind): number {
|
2026-08-07 19:11:43 +02:00
|
|
|
return DEFS[kind].bodyHeight
|
2026-08-07 12:49:13 +02:00
|
|
|
}
|
|
|
|
|
}
|