refactor: move world concepts into engine
This commit is contained in:
parent
eeedcb8e48
commit
2d15c7ab8d
52 changed files with 3298 additions and 1558 deletions
|
|
@ -1,15 +1,51 @@
|
|||
import { Vec3 } from "../../../engine/math/Vec3"
|
||||
import type { Mesh } from "../../../engine/scene/Mesh"
|
||||
import type { Tree, TreeSpecies } from "../Tree"
|
||||
import type { Material } from "../../../engine/render/Material"
|
||||
import type { Prefab } from "../../../engine/scene/Prefab"
|
||||
import type { Tree } from "../Tree"
|
||||
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 const birch: TreeSpecies = { kind: "birch", trunk: "birch", foliage: "leaf", build }
|
||||
export type Birch = Prefab<Tree>
|
||||
|
||||
function build(tree: Tree, trunk: Mesh, leaves: Mesh, lod: "full" | "impostor"): void {
|
||||
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 {
|
||||
const base = tree.position
|
||||
const g = tree.growth
|
||||
const rand = rng(tree.seed)
|
||||
|
|
@ -18,11 +54,25 @@ function build(tree: Tree, trunk: Mesh, leaves: Mesh, lod: "full" | "impostor"):
|
|||
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)
|
||||
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)
|
||||
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.
|
||||
|
|
@ -42,7 +92,11 @@ function build(tree: Tree, trunk: Mesh, leaves: Mesh, lod: "full" | "impostor"):
|
|||
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 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)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,48 @@
|
|||
import { Vec3 } from "../../../engine/math/Vec3"
|
||||
import type { Mesh } from "../../../engine/scene/Mesh"
|
||||
import type { Tree, TreeSpecies } from "../Tree"
|
||||
import type { Material } from "../../../engine/render/Material"
|
||||
import type { Prefab } from "../../../engine/scene/Prefab"
|
||||
import type { Tree } from "../Tree"
|
||||
import { blob, lerp, limb, TAU, rng } from "./treekit"
|
||||
|
||||
// Oak: short tapered trunk, a couple of branches, a broad cluster of rounded canopy
|
||||
// blobs (bushy, wider than tall). Trunk = brown bark, foliage = oak leaf.
|
||||
|
||||
export const oak: TreeSpecies = { kind: "oak", trunk: "bark", foliage: "leaf", build }
|
||||
export type Oak = Prefab<Tree>
|
||||
|
||||
function build(tree: Tree, trunk: Mesh, leaves: Mesh, lod: "full" | "impostor"): void {
|
||||
export namespace Oak {
|
||||
export function create(trunk: Material, foliage: Material): Oak {
|
||||
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(tree) {
|
||||
if (tree.growth <= 0.35) {
|
||||
return null
|
||||
}
|
||||
const radius = tree.growth * 0.3 + 0.15
|
||||
return {
|
||||
shape: "box",
|
||||
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 {
|
||||
const base = tree.position
|
||||
const g = tree.growth
|
||||
const rand = rng(tree.seed)
|
||||
|
|
@ -19,7 +53,14 @@ function build(tree: Tree, trunk: Mesh, leaves: Mesh, lod: "full" | "impostor"):
|
|||
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)
|
||||
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
|
||||
}
|
||||
|
|
@ -43,7 +84,11 @@ function build(tree: Tree, trunk: Mesh, leaves: Mesh, lod: "full" | "impostor"):
|
|||
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: 1.2, z: Math.sin(angle) })
|
||||
const dir = Vec3.normalize({
|
||||
x: Math.cos(angle),
|
||||
y: 1.2,
|
||||
z: Math.sin(angle),
|
||||
})
|
||||
const start = { x: base.x, y: base.y + h * 0.42, z: base.z }
|
||||
const end = Vec3.add(start, Vec3.scale(dir, h * 0.3))
|
||||
limb(trunk, start, end, rTrunk * 0.4, rTrunk * 0.2, 4)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,63 @@
|
|||
import type { Mesh } from "../../../engine/scene/Mesh"
|
||||
import type { Tree, TreeSpecies } from "../Tree"
|
||||
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 const spruce: TreeSpecies = { kind: "spruce", trunk: "bark", foliage: "needle", build }
|
||||
export type Spruce = Prefab<Tree>
|
||||
|
||||
function build(tree: Tree, trunk: Mesh, needles: Mesh, lod: "full" | "impostor"): void {
|
||||
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)
|
||||
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).
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Vec3 } from "../../../engine/math/Vec3"
|
||||
import { STRIDE, type Mesh } from "../../../engine/scene/Mesh"
|
||||
|
||||
// Shared faceted-geometry primitives + the per-tree RNG, used by the species
|
||||
// modules (Oak/Spruce/Birch). Kept separate so a species and the `Tree` registry
|
||||
// don't form an import cycle.
|
||||
// Shared faceted-geometry primitives + the per-tree RNG used by concrete tree
|
||||
// prefab modules.
|
||||
|
||||
export const TAU = Math.PI * 2
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue