import type { Vec3 } from "../../engine/math/Vec3" import { Mesh } from "../../engine/scene/Mesh" import type { Material } from "../../engine/render/Material" import type { Prefab } from "../../engine/scene/Prefab" const TAU = Math.PI * 2 /** Concrete atlas style referenced directly by flower instances. */ export type FlowerStyle = { bloomUv: [number, number] } /** 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 style: FlowerStyle 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 { export const white: FlowerStyle = { bloomUv: [0.75, 0.25] } export const red: FlowerStyle = { bloomUv: [0.25, 0.75] } export const yellow: FlowerStyle = { bloomUv: [0.75, 0.75] } /** uv center of the green stem region. */ const STEM_U = 0.25 const STEM_V = 0.25 export function create(material: Material): Prefab { return { position: (flower) => flower.position, bakeNear: (flower, batch) => build(flower, batch.mesh(material)), } } 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] = flower.style.bloomUv 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 } } }