meat/game/actors/Flower.ts

79 lines
2.9 KiB
TypeScript

import type { Vec3 } from "../../engine/math/Vec3"
import { Mesh } from "../../engine/scene/Mesh"
const TAU = Math.PI * 2
/** Flower bloom color, indexing a region of the `flower` texture atlas. */
export type FlowerColor = "white" | "red" | "yellow"
/** A single small flower: a thin crossed-quad stem plus a shallow fan of petals.
* Tiny, so it is drawn double-sided (no backface cull) and carries no collider.
* `size` is roughly its height; `seed` jitters the petals. */
export type Flower = {
position: Vec3
color: FlowerColor
size: number
seed: number
}
/**
* Low-poly flower geometry. The `flower` texture is a 2x2 color atlas -- green
* (stem) plus white / red / yellow blooms -- and every vertex samples the flat
* center of one region, so a flower is solid-colored with no per-flower texture
* or draw call. `build` appends into one shared flower mesh.
*/
export namespace Flower {
/** uv center of each bloom color's atlas region (tile units). */
const BLOOM_UV: Record<FlowerColor, [number, number]> = {
white: [0.75, 0.25],
red: [0.25, 0.75],
yellow: [0.75, 0.75],
}
/** uv center of the green stem region. */
const STEM_U = 0.25
const STEM_V = 0.25
export function build(flower: Flower, mesh: Mesh): void {
const rand = rng(flower.seed)
const p = flower.position
const height = flower.size * (0.8 + rand() * 0.4)
const bloomY = p.y + height
const w = flower.size * 0.04
// Stem: two thin crossed quads so it reads from any angle.
stem(mesh, p.x, p.y, p.z, bloomY, w, 0)
stem(mesh, p.x, p.y, p.z, bloomY, 0, w)
// Bloom: a shallow fan of petals, center raised a touch so it domes.
const [bu, bv] = BLOOM_UV[flower.color]
const rad = flower.size * 0.38
const center = Mesh.push(mesh, p.x, bloomY + rad * 0.3, p.z, bu, bv)
const ring = center + 1
const petals = 5
for (let i = 0; i <= petals; i++) {
const angle = (i / petals) * TAU + rand() * 0.4
Mesh.push(mesh, p.x + Math.cos(angle) * rad, bloomY, p.z + Math.sin(angle) * rad, bu, bv)
}
for (let i = 0; i < petals; i++) {
mesh.indices.push(center, ring + i, ring + i + 1)
}
}
/** A thin vertical quad from the ground to `y1`, width along (dx, dz). */
function stem(mesh: Mesh, x: number, y0: number, z: number, y1: number, dx: number, dz: number): void {
const a = Mesh.push(mesh, x - dx, y0, z - dz, STEM_U, STEM_V)
const b = Mesh.push(mesh, x + dx, y0, z + dz, STEM_U, STEM_V)
const c = Mesh.push(mesh, x + dx, y1, z + dz, STEM_U, STEM_V)
const d = Mesh.push(mesh, x - dx, y1, z - dz, STEM_U, STEM_V)
mesh.indices.push(a, b, c, a, c, d)
}
/** Deterministic 0..1 generator (mulberry32) seeded per flower. */
function rng(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
}
}
}