feat: birch

This commit is contained in:
Dan Finch 2026-08-07 13:01:32 +02:00
parent 16f205babe
commit a4490f7a20
11 changed files with 110 additions and 24 deletions

View file

@ -7,7 +7,7 @@ const TAU = Math.PI * 2
* scales height and girth and adds canopy blobs (oak) / tiers (spruce). `seed`
* drives the per-tree random wobble so a forest doesn't look cloned. */
export type Tree = {
kind: "oak" | "spruce"
kind: "oak" | "spruce" | "birch"
/** Trunk base, sitting on the ground. */
position: Vec3
growth: number
@ -34,8 +34,10 @@ export namespace Tree {
const rand = rng(tree.seed)
if (tree.kind === "oak") {
oak(tree.position, tree.growth, rand, trunk, foliage, lod)
} else {
} else if (tree.kind === "spruce") {
spruce(tree.position, tree.growth, rand, trunk, foliage, lod)
} else {
birch(tree.position, tree.growth, rand, trunk, foliage, lod)
}
}
@ -101,6 +103,48 @@ export namespace Tree {
}
}
function birch(base: Vec3, g: number, rand: () => number, trunk: Mesh, leaves: Mesh, lod: "full" | "impostor"): void {
// Silver birch: tall, slender, near-straight white trunk under an airy, high,
// slightly drooping canopy of small leaf tufts -- a lean silhouette between the
// broad oak and the conical spruce (the white bark texture does the rest).
const h = lerp(1, 8.5, g)
const rTrunk = lerp(0.03, 0.16, g)
const canopyY = base.y + h * 0.75
const blobR = h * 0.22
if (lod === "impostor") {
limb(trunk, base, { x: base.x, y: base.y + h * 0.9, z: base.z }, rTrunk, rTrunk * 0.5, 3)
blob(leaves, { x: base.x, y: canopyY, z: base.z }, blobR * 1.1, rand, 4, 2)
return
}
limb(trunk, base, { x: base.x, y: base.y + h * 0.88, z: base.z }, rTrunk, rTrunk * 0.35, 5)
const spread = h * 0.22
// Sparse small blobs clustered high, biased downward so the crown droops.
const blobs = 2 + Math.round(g * 2)
for (let i = 0; i < blobs; i++) {
const angle = rand() * TAU
const rad = i === 0 ? 0 : spread * (0.5 + rand() * 0.5)
const center = {
x: base.x + Math.cos(angle) * rad,
y: canopyY + (rand() - 0.6) * spread,
z: base.z + Math.sin(angle) * rad,
}
blob(leaves, center, blobR * (0.7 + rand() * 0.4), rand)
}
// Grown birches trail a few thin, near-horizontal drooping twigs.
if (g > 0.5) {
const branches = 2 + Math.round(rand())
for (let i = 0; i < branches; i++) {
const angle = rand() * TAU
const dir = Vec3.normalize({ x: Math.cos(angle), y: 0.6, z: Math.sin(angle) })
const start = { x: base.x, y: base.y + h * 0.7, z: base.z }
const end = Vec3.add(start, Vec3.scale(dir, h * 0.22))
limb(trunk, start, end, rTrunk * 0.4, rTrunk * 0.15, 4)
blob(leaves, end, blobR * 0.55, rand)
}
}
}
/** A tapered tube between two points (trunk or branch), `sides`-gonal. */
function limb(mesh: Mesh, a: Vec3, b: Vec3, ra: number, rb: number, sides: number): void {
const axis = Vec3.normalize(Vec3.sub(b, a))