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" import { cone, lerp, limb, rng } from "./treekit" // Spruce: tall thin trunk under stacked cones that narrow to a point (tiered, taller // than wide). Trunk = brown bark, foliage = spruce needle. export type Spruce = Prefab export namespace Spruce { export function create(trunk: Material, foliage: Material): Spruce { 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, needles: Mesh, lod: "full" | "impostor", ): void { const base = tree.position const g = tree.growth const rand = rng(tree.seed) const h = lerp(0.6, 9, g) const rTrunk = lerp(0.03, 0.2, g) 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. 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++) { const t = i / tiers 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, sides) } }