feat: threads

This commit is contained in:
Dan Finch 2026-08-05 09:24:13 +02:00
parent 6fb46573da
commit 46e7070b6b
18 changed files with 878 additions and 125 deletions

View file

@ -22,10 +22,11 @@ export type Boulder = {
* field of boulders batches into a single draw call.
*/
export namespace Boulder {
export function build(boulder: Boulder, mesh: Mesh): void {
/** `lod` "impostor" bakes a coarser rock (fewer facets) for far chunks. */
export function build(boulder: Boulder, mesh: Mesh, lod: "full" | "impostor" = "full"): void {
const rand = rng(boulder.seed)
const seg = 5
const rings = 4
const seg = lod === "impostor" ? 4 : 5
const rings = lod === "impostor" ? 2 : 4
const r = boulder.radius
// Squat and slightly oval, so it reads as a rock, not a ball.
const sx = r * (0.8 + rand() * 0.5)

View file

@ -28,24 +28,32 @@ export type Tree = {
export namespace Tree {
/** Append one tree into the shared `trunk` (bark) mesh and the `foliage` mesh
* for its kind (oak leaf vs spruce needle). */
export function build(tree: Tree, trunk: Mesh, foliage: Mesh): void {
/** `lod` "impostor" bakes a much cheaper stand-in (few tris, same textures +
* faceted look, same height/position) for far chunks; "full" is up close. */
export function build(tree: Tree, trunk: Mesh, foliage: Mesh, lod: "full" | "impostor" = "full"): void {
const rand = rng(tree.seed)
if (tree.kind === "oak") {
oak(tree.position, tree.growth, rand, trunk, foliage)
oak(tree.position, tree.growth, rand, trunk, foliage, lod)
} else {
spruce(tree.position, tree.growth, rand, trunk, foliage)
spruce(tree.position, tree.growth, rand, trunk, foliage, lod)
}
}
function oak(base: Vec3, g: number, rand: () => number, trunk: Mesh, leaves: Mesh): void {
function oak(base: Vec3, g: number, rand: () => number, trunk: Mesh, leaves: Mesh, lod: "full" | "impostor"): void {
const h = lerp(0.8, 7, g)
const rTrunk = lerp(0.04, 0.32, g)
const forkY = base.y + h * 0.5
const canopyY = base.y + h * 0.72
const blobR = h * 0.3
if (lod === "impostor") {
// One low-poly blob on a stubby trunk -- reads as an oak at distance.
limb(trunk, base, { x: base.x, y: forkY, z: base.z }, rTrunk, rTrunk * 0.6, 3)
blob(leaves, { x: base.x, y: canopyY, z: base.z }, blobR * 1.15, rand, 4, 2)
return
}
limb(trunk, base, { x: base.x, y: forkY, z: base.z }, rTrunk, rTrunk * 0.6, 5)
const blobR = h * 0.3
const spread = h * 0.32
const canopyY = base.y + h * 0.72
// Central blob plus, as it grows, a couple offset ones -> broad bushy crown.
const blobs = 1 + Math.round(g * 2)
for (let i = 0; i < blobs; i++) {
@ -72,13 +80,16 @@ export namespace Tree {
}
}
function spruce(base: Vec3, g: number, rand: () => number, trunk: Mesh, needles: Mesh): void {
function spruce(base: Vec3, g: number, rand: () => number, trunk: Mesh, needles: Mesh, lod: "full" | "impostor"): void {
const h = lerp(0.6, 9, g)
const rTrunk = lerp(0.03, 0.2, g)
limb(trunk, base, { x: base.x, y: base.y + h, z: base.z }, rTrunk, rTrunk * 0.25, 5)
const impostor = lod === "impostor"
limb(trunk, base, { x: base.x, y: base.y + h, z: base.z }, rTrunk, rTrunk * 0.25, impostor ? 3 : 5)
// Stacked cones: widest low, shrinking to a point up top -> conical tiers.
const tiers = 2 + Math.round(g * 3)
// The impostor keeps the first two tiers at low sides (same seed => aligned).
const tiers = impostor ? 2 : 2 + Math.round(g * 3)
const sides = impostor ? 4 : 6
const bottom = base.y + h * 0.1
const span = h * 0.9
for (let i = 0; i < tiers; i++) {
@ -86,7 +97,7 @@ export namespace Tree {
const y = bottom + t * span * 0.82
const radius = lerp(h * 0.3, h * 0.05, t) * (0.9 + rand() * 0.2)
const coneH = (span / tiers) * 1.9
cone(needles, { x: base.x, y, z: base.z }, coneH, radius, 6)
cone(needles, { x: base.x, y, z: base.z }, coneH, radius, sides)
}
}
@ -127,9 +138,7 @@ export namespace Tree {
/** A lumpy low-poly sphere (one oak canopy blob). Per-ring radius wobble keeps
* it organic without cracking the longitude seam. */
function blob(mesh: Mesh, center: Vec3, radius: number, rand: () => number): void {
const seg = 5
const rings = 3
function blob(mesh: Mesh, center: Vec3, radius: number, rand: () => number, seg = 5, rings = 3): void {
const start = mesh.verts.length / STRIDE
for (let r = 0; r <= rings; r++) {
const phi = (r / rings) * Math.PI