meat/game/actors/trees/Birch.ts

107 lines
3 KiB
TypeScript
Raw Normal View History

2026-08-24 15:54:44 +02:00
import { Vec3 } from "engine/math/Vec3"
import type { Mesh } from "engine/scene/Mesh"
import type { Material } from "engine/render/Material"
import type { Prefab } from "engine/scene/Prefab"
import type { Tree } from "../Tree"
2026-08-07 19:11:43 +02:00
import { blob, lerp, limb, TAU, rng } from "./treekit"
// Silver birch: tall, slender, near-straight trunk under an airy, high, slightly
// drooping canopy -- a lean silhouette between the broad oak and conical spruce.
// Trunk = white birch bark, foliage = oak leaf (the white trunk carries the read).
export type Birch = Prefab<Tree>
export namespace Birch {
export function create(trunk: Material, foliage: Material): Birch {
return {
position: (tree) => tree.position,
bakeNear: (tree, batch) =>
build(tree, batch.mesh(trunk), batch.mesh(foliage), "full"),
bakeFar: (tree, batch) =>
build(tree, batch.mesh(trunk), batch.mesh(foliage), "impostor"),
collider: treeCollider,
}
}
function treeCollider(tree: Tree) {
if (tree.growth <= 0.35) {
return null
}
const radius = tree.growth * 0.2 + 0.15
return {
shape: "box" as const,
minX: tree.position.x - radius,
maxX: tree.position.x + radius,
minZ: tree.position.z - radius,
maxZ: tree.position.z + radius,
top: tree.position.y + 3,
standable: false,
}
}
}
function build(
tree: Tree,
trunk: Mesh,
leaves: Mesh,
lod: "full" | "impostor",
): void {
2026-08-07 19:11:43 +02:00
const base = tree.position
const g = tree.growth
const rand = rng(tree.seed)
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,
)
2026-08-07 19:11:43 +02:00
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,
)
2026-08-07 19:11:43 +02:00
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),
})
2026-08-07 19:11:43 +02:00
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)
}
}
}