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

@ -1,6 +1,7 @@
import type { Texture } from "../engine/render/Texture"
import barkUrl from "../assets/bark.png"
import beeUrl from "../assets/bee.png"
import birchUrl from "../assets/birch.png"
import crateUrl from "../assets/crate.png"
import floorUrl from "../assets/floor.png"
import flowerUrl from "../assets/flower.png"
@ -16,6 +17,7 @@ export type Textures = {
floor: Texture
grass: Texture
bark: Texture
birch: Texture
leaf: Texture
needle: Texture
rock: Texture
@ -29,10 +31,11 @@ export type Textures = {
/** Load every game texture up front. Call once before starting the loop. */
export async function loadTextures(): Promise<Textures> {
const [floor, grass, bark, leaf, needle, rock, flower, wall, crate, npc, frog, bee] = await Promise.all([
const [floor, grass, bark, birch, leaf, needle, rock, flower, wall, crate, npc, frog, bee] = await Promise.all([
loadTexture(floorUrl),
loadTexture(grassUrl),
loadTexture(barkUrl),
loadTexture(birchUrl),
loadTexture(leafUrl),
loadTexture(needleUrl),
loadTexture(rockUrl),
@ -43,7 +46,7 @@ export async function loadTextures(): Promise<Textures> {
loadTexture(frogUrl),
loadTexture(beeUrl),
])
return { floor, grass, bark, leaf, needle, rock, flower, wall, crate, npc, frog, bee }
return { floor, grass, bark, birch, leaf, needle, rock, flower, wall, crate, npc, frog, bee }
}
function loadTexture(url: string): Promise<Texture> {

View file

@ -35,6 +35,9 @@ export type Chunk = {
maxZ: number
grass: Mesh
bark: Mesh
/** Birch trunks (white bark texture) -- separate mesh since a draw is one mesh +
* one texture, and silver birch bark can't share the brown oak/spruce bark. */
birchBark: Mesh
leaf: Mesh
needle: Mesh
rock: Mesh
@ -42,8 +45,9 @@ export type Chunk = {
flowers: Mesh
/** Cheap low-poly impostors of the same trees/boulders, drawn instead of the
* full meshes once the chunk is past `config.lodDistance` (renderScene). Same
* textures as bark/leaf/needle/rock. Bushes/flowers have no far version. */
* textures as bark/birch/leaf/needle/rock. Bushes/flowers have no far version. */
barkFar: Mesh
birchBarkFar: Mesh
leafFar: Mesh
needleFar: Mesh
rockFar: Mesh
@ -231,21 +235,27 @@ function buildChunks(trees: Tree[], boulders: Boulder[], bushes: Bush[], flowers
const z1 = z0 + cell
const grass = mesh()
const bark = mesh()
const birchBark = mesh()
const leaf = mesh()
const needle = mesh()
const rock = mesh()
const flowerMesh = mesh()
const barkFar = mesh()
const birchBarkFar = mesh()
const leafFar = mesh()
const needleFar = mesh()
const rockFar = mesh()
Terrain.patch(TERRAIN, grass, x0, z0, x1, z1, TERRAIN_SUBDIV, TERRAIN_SUBDIV, GROUND_UV)
for (const tree of trees) {
if (inCell(tree.position, x0, z0, x1, z1)) {
const foliage = tree.kind === "oak" ? leaf : needle
const foliageFar = tree.kind === "oak" ? leafFar : needleFar
Tree.build(tree, bark, foliage)
Tree.build(tree, barkFar, foliageFar, "impostor")
// Birch trunks go to their own white-bark mesh; oak + birch share the oak
// leaf foliage, spruce keeps its needles.
const trunk = tree.kind === "birch" ? birchBark : bark
const trunkFar = tree.kind === "birch" ? birchBarkFar : barkFar
const foliage = tree.kind === "spruce" ? needle : leaf
const foliageFar = tree.kind === "spruce" ? needleFar : leafFar
Tree.build(tree, trunk, foliage)
Tree.build(tree, trunkFar, foliageFar, "impostor")
}
}
for (const boulder of boulders) {
@ -265,11 +275,11 @@ function buildChunks(trees: Tree[], boulders: Boulder[], bushes: Bush[], flowers
Flower.build(flower, flowerMesh)
}
}
const b = bounds([grass, bark, leaf, needle, rock, flowerMesh])
const b = bounds([grass, bark, birchBark, leaf, needle, rock, flowerMesh])
if (b === null) {
continue
}
chunks.push({ ...b, grass, bark, leaf, needle, rock, flowers: flowerMesh, barkFar, leafFar, needleFar, rockFar })
chunks.push({ ...b, grass, bark, birchBark, leaf, needle, rock, flowers: flowerMesh, barkFar, birchBarkFar, leafFar, needleFar, rockFar })
}
}
return chunks
@ -320,7 +330,8 @@ function placeTrees(colliders: Aabb[]): Tree[] {
if (Math.max(Math.abs(x), Math.abs(z)) < TERRAIN.inner + 3) {
continue
}
const kind = rand() < 0.5 ? "oak" : "spruce"
const roll = rand()
const kind = roll < 0.4 ? "oak" : roll < 0.72 ? "spruce" : "birch"
const growth = 0.08 + rand() * 0.92
const position = { x, y: Terrain.height(TERRAIN, x, z), z }
trees.push({ kind, position, growth, seed: (rand() * 0xFFFFFFFF) | 0 })

View file

@ -161,8 +161,8 @@ async function main(): Promise<void> {
t += c.grass.indices.length + c.flowers.indices.length
const far = chunkFar(c, cam.position, config.lodDistance)
t += far
? c.barkFar.indices.length + c.leafFar.indices.length + c.needleFar.indices.length + c.rockFar.indices.length
: c.bark.indices.length + c.leaf.indices.length + c.needle.indices.length + c.rock.indices.length
? c.barkFar.indices.length + c.birchBarkFar.indices.length + c.leafFar.indices.length + c.needleFar.indices.length + c.rockFar.indices.length
: c.bark.indices.length + c.birchBark.indices.length + c.leaf.indices.length + c.needle.indices.length + c.rock.indices.length
}
return (t / 3) | 0
}

View file

@ -104,11 +104,13 @@ export function renderBand(
if (chunkFar(c, camera.position, config.lodDistance)) {
Rasterizer.draw(fb, c.rockFar, tx.rock, viewProj, config, true, y0, y1)
Rasterizer.draw(fb, c.barkFar, tx.bark, viewProj, config, true, y0, y1)
Rasterizer.draw(fb, c.birchBarkFar, tx.birch, viewProj, config, true, y0, y1)
Rasterizer.draw(fb, c.leafFar, tx.leaf, viewProj, config, true, y0, y1)
Rasterizer.draw(fb, c.needleFar, tx.needle, viewProj, config, true, y0, y1)
} else {
Rasterizer.draw(fb, c.rock, tx.rock, viewProj, config, true, y0, y1)
Rasterizer.draw(fb, c.bark, tx.bark, viewProj, config, true, y0, y1)
Rasterizer.draw(fb, c.birchBark, tx.birch, viewProj, config, true, y0, y1)
Rasterizer.draw(fb, c.leaf, tx.leaf, viewProj, config, true, y0, y1)
Rasterizer.draw(fb, c.needle, tx.needle, viewProj, config, true, y0, y1)
Rasterizer.draw(fb, c.flowers, tx.flower, viewProj, config, false, y0, y1)