feat: trees
This commit is contained in:
parent
61389f091c
commit
9aa0ec6a3f
10 changed files with 311 additions and 10 deletions
|
|
@ -1,13 +1,19 @@
|
|||
import type { Texture } from "../engine/render/Texture"
|
||||
import barkUrl from "../assets/bark.png"
|
||||
import crateUrl from "../assets/crate.png"
|
||||
import floorUrl from "../assets/floor.png"
|
||||
import grassUrl from "../assets/grass.png"
|
||||
import leafUrl from "../assets/leaf.png"
|
||||
import needleUrl from "../assets/needle.png"
|
||||
import npcUrl from "../assets/npc.png"
|
||||
import wallUrl from "../assets/wall.png"
|
||||
|
||||
export type Textures = {
|
||||
floor: Texture
|
||||
grass: Texture
|
||||
bark: Texture
|
||||
leaf: Texture
|
||||
needle: Texture
|
||||
wall: Texture
|
||||
crate: Texture
|
||||
npc: Texture
|
||||
|
|
@ -15,14 +21,17 @@ export type Textures = {
|
|||
|
||||
/** Load every game texture up front. Call once before starting the loop. */
|
||||
export async function loadTextures(): Promise<Textures> {
|
||||
const [floor, grass, wall, crate, npc] = await Promise.all([
|
||||
const [floor, grass, bark, leaf, needle, wall, crate, npc] = await Promise.all([
|
||||
loadTexture(floorUrl),
|
||||
loadTexture(grassUrl),
|
||||
loadTexture(barkUrl),
|
||||
loadTexture(leafUrl),
|
||||
loadTexture(needleUrl),
|
||||
loadTexture(wallUrl),
|
||||
loadTexture(crateUrl),
|
||||
loadTexture(npcUrl),
|
||||
])
|
||||
return { floor, grass, wall, crate, npc }
|
||||
return { floor, grass, bark, leaf, needle, wall, crate, npc }
|
||||
}
|
||||
|
||||
function loadTexture(url: string): Promise<Texture> {
|
||||
|
|
|
|||
62
app/level.ts
62
app/level.ts
|
|
@ -2,6 +2,7 @@ import { Color } from "../engine/render/Color"
|
|||
import type { CloudLayer, SkyConfig } from "../engine/render/Sky"
|
||||
import type { Mesh } from "../engine/scene/Mesh"
|
||||
import { Terrain } from "../engine/scene/Terrain"
|
||||
import { Tree } from "../engine/scene/Tree"
|
||||
|
||||
type Corner = [number, number, number]
|
||||
|
||||
|
|
@ -24,6 +25,12 @@ export type Level = {
|
|||
walls: Mesh
|
||||
crate: Mesh
|
||||
ground: Mesh
|
||||
/** All tree trunks + branches (bark texture). */
|
||||
trunks: Mesh
|
||||
/** Oak canopies (leaf texture) and spruce foliage (needle texture), split so
|
||||
* each takes its own texture in one draw call. */
|
||||
oakFoliage: Mesh
|
||||
spruceFoliage: Mesh
|
||||
colliders: Aabb[]
|
||||
npcPosition: { x: number; y: number; z: number }
|
||||
terrain: Terrain
|
||||
|
|
@ -57,6 +64,13 @@ const TERRAIN: Terrain = {
|
|||
peakStart: 0.45,
|
||||
}
|
||||
|
||||
/** Forest: how many trees to scatter on the grass, and the seed for their
|
||||
* placement/kind/growth. Trees ring the room out to `TREE_REACH` of the world;
|
||||
* each rolls oak-or-spruce and a growth 0..1 (sapling .. full grown). */
|
||||
const TREE_COUNT = 500
|
||||
const TREE_SEED = 0x5EED
|
||||
const TREE_REACH = 0.6
|
||||
|
||||
/** Outdoor ground mesh resolution. A fixed grid over the whole world, so cell
|
||||
* size (and cost) is set here, not by the world's size: bigger `outer` gives
|
||||
* chunkier terrain, not more triangles. `GROUND_UV` sets texture tiles/unit. */
|
||||
|
|
@ -136,7 +150,53 @@ export function buildLevel(): Level {
|
|||
|
||||
const npcPosition = { x: 2, y: 0, z: -1 }
|
||||
|
||||
return { floor, walls, crate, ground, colliders, npcPosition, terrain: TERRAIN, sky }
|
||||
// Scatter a forest on the grass; appends into the tree meshes + trunk colliders.
|
||||
const trunks = mesh()
|
||||
const oakFoliage = mesh()
|
||||
const spruceFoliage = mesh()
|
||||
scatterTrees(trunks, oakFoliage, spruceFoliage, colliders)
|
||||
|
||||
return { floor, walls, crate, ground, trunks, oakFoliage, spruceFoliage, colliders, npcPosition, terrain: TERRAIN, sky }
|
||||
}
|
||||
|
||||
/** Place `TREE_COUNT` trees around the room on walkable grass: each sits on the
|
||||
* terrain, rolls oak/spruce and a growth stage, and (once past sapling size)
|
||||
* drops a trunk collider so you can't walk through it. */
|
||||
function scatterTrees(trunks: Mesh, oakFoliage: Mesh, spruceFoliage: Mesh, colliders: Aabb[]): void {
|
||||
const rand = mulberry(TREE_SEED)
|
||||
const maxDist = TERRAIN.outer * TREE_REACH
|
||||
let placed = 0
|
||||
for (let guard = 0; placed < TREE_COUNT && guard < TREE_COUNT * 20; guard++) {
|
||||
const angle = rand() * Math.PI * 2
|
||||
const dist = ARENA + 5 + rand() * (maxDist - ARENA - 5)
|
||||
const x = Math.cos(angle) * dist
|
||||
const z = Math.sin(angle) * dist
|
||||
// Stay out of the room clearing and its flat rim.
|
||||
if (Math.max(Math.abs(x), Math.abs(z)) < TERRAIN.inner + 3) {
|
||||
continue
|
||||
}
|
||||
const kind = rand() < 0.5 ? "oak" : "spruce"
|
||||
const growth = 0.08 + rand() * 0.92
|
||||
const position = { x, y: Terrain.height(TERRAIN, x, z), z }
|
||||
Tree.build({ kind, position, growth, seed: (rand() * 0xFFFFFFFF) | 0 }, trunks, kind === "oak" ? oakFoliage : spruceFoliage)
|
||||
// Saplings are passable; grown trunks block. Square footprint, non-standable.
|
||||
if (growth > 0.35) {
|
||||
const r = growth * (kind === "oak" ? 0.3 : 0.2) + 0.15
|
||||
colliders.push({ minX: x - r, maxX: x + r, minZ: z - r, maxZ: z + r, top: position.y + 3, standable: false })
|
||||
}
|
||||
placed++
|
||||
}
|
||||
}
|
||||
|
||||
/** Deterministic 0..1 generator (mulberry32) for tree placement. */
|
||||
function mulberry(seed: number): () => number {
|
||||
let a = seed >>> 0
|
||||
return () => {
|
||||
a = (a + 0x6D2B79F5) | 0
|
||||
let t = Math.imul(a ^ (a >>> 15), 1 | a)
|
||||
t ^= t + Math.imul(t ^ (t >>> 7), 61 | t)
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
||||
}
|
||||
}
|
||||
|
||||
function mesh(): Mesh {
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ async function main(): Promise<void> {
|
|||
Rasterizer.draw(fb, level.floor, textures.floor, viewProj, config)
|
||||
Rasterizer.draw(fb, level.walls, textures.wall, viewProj, config)
|
||||
Rasterizer.draw(fb, level.crate, textures.crate, viewProj, config)
|
||||
Rasterizer.draw(fb, level.trunks, textures.bark, viewProj, config)
|
||||
Rasterizer.draw(fb, level.oakFoliage, textures.leaf, viewProj, config)
|
||||
Rasterizer.draw(fb, level.spruceFoliage, textures.needle, viewProj, config)
|
||||
Rasterizer.draw(fb, Sprite.billboard(npc, camera), npc.texture, viewProj, config)
|
||||
Framebuffer.quantize(fb, config)
|
||||
present()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const SPEED = 6
|
|||
* solid even at big multipliers. */
|
||||
const RUN_MULTIPLIER = 2
|
||||
const GRAVITY = 22
|
||||
const JUMP_SPEED = 20
|
||||
const JUMP_SPEED = 14
|
||||
const NPC_RADIUS = 0.5
|
||||
|
||||
export namespace Player {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue