component
Flowers
Instanced seeded wildflowers (stem + colored head) scattered over a patch.
⌖ Explore the library in 3D → · View as Markdown
Add it
npx @runek/cli add flowers
Pulls @runek/core@^0.15.0, three@^0.184.0.
Use it
import { Flowers } from './runek/Flowers'
<Flowers position={[0, 0, 0]} seed={1} />
Or as a node in a world file (worlds as data):
{ "type": "Flowers", "props": { "position": [0, 0, 0], "seed": 1 } }
Props
| Prop | Type | Default | Description |
|---|---|---|---|
position |
[x, y, z] |
[0, 0, 0] |
Position [x, y, z] in units (1 unit = 1 m, Y-up). |
rotation |
[x, y, z] |
[0, 0, 0] |
Euler rotation [x, y, z] in radians. |
area |
[number, number] |
[6, 6] |
Patch extent [width, depth], in units. |
count |
number |
60 |
|
height |
number |
0.4 |
Height, in units. |
stemColor |
color |
palette foliage |
Stem color; defaults to the world palette’s foliage slot. |
seed |
number |
1 |
Seed for the deterministic variation: same seed, same result. |
Registry manifest
registry manifest /r/components/flowers.json Self-contained JSON: inlined source plus resolved dependencies, exactly whatrunek add flowers fetches.
Browse the whole catalog in the gallery →.
Source (Flowers.tsx)
import { rng, useWorld, type Vec3 } from '@runek/core'
import { useLayoutEffect, useMemo, useRef } from 'react'
import * as THREE from 'three'
export interface FlowersProps {
position?: Vec3
rotation?: Vec3
/** Patch extent `[width, depth]`, in units. */
area?: [number, number]
count?: number
height?: number
/** Stem color; defaults to the world palette's `foliage` slot. */
stemColor?: string
seed?: number
}
interface Bloom {
x: number
z: number
h: number
hue: number
}
const HUES = [12, 45, 0, 330, 280, 200]
/** Instanced wildflowers scattered over a patch: a green stem plus a colored
* head, seeded so the same seed plants the same meadow. Decorative (no collider). */
export function Flowers({
position = [0, 0, 0],
rotation = [0, 0, 0],
area = [6, 6],
count = 60,
height = 0.4,
stemColor,
seed = 1,
}: FlowersProps) {
const { unit, palette } = useWorld()
const stem = stemColor ?? palette.foliage
const w = area[0] * unit
const d = area[1] * unit
const h = height * unit
const stemRef = useRef<THREE.InstancedMesh>(null)
const headRef = useRef<THREE.InstancedMesh>(null)
const blooms = useMemo<Bloom[]>(() => {
const next = rng(seed)
return Array.from({ length: count }, () => ({
x: (next() - 0.5) * w,
z: (next() - 0.5) * d,
h: h * (0.7 + next() * 0.6),
hue: next(),
}))
}, [w, d, h, count, seed])
useLayoutEffect(() => {
const sMesh = stemRef.current
const hMesh = headRef.current
if (!sMesh || !hMesh) return
const dummy = new THREE.Object3D()
const col = new THREE.Color()
blooms.forEach((b, i) => {
dummy.position.set(b.x, b.h / 2, b.z)
dummy.scale.set(1, b.h / h, 1)
dummy.updateMatrix()
sMesh.setMatrixAt(i, dummy.matrix)
dummy.position.set(b.x, b.h, b.z)
dummy.scale.set(1, 1, 1)
dummy.updateMatrix()
hMesh.setMatrixAt(i, dummy.matrix)
hMesh.setColorAt(i, col.setHSL(HUES[Math.floor(b.hue * HUES.length)] / 360, 0.7, 0.6))
})
sMesh.instanceMatrix.needsUpdate = true
hMesh.instanceMatrix.needsUpdate = true
if (hMesh.instanceColor) hMesh.instanceColor.needsUpdate = true
}, [blooms, h])
return (
<group position={position} rotation={rotation}>
<instancedMesh ref={stemRef} args={[undefined, undefined, count]} castShadow>
<cylinderGeometry args={[0.01 * unit, 0.012 * unit, h, 5]} />
<meshStandardMaterial color={stem} />
</instancedMesh>
<instancedMesh ref={headRef} args={[undefined, undefined, count]} castShadow>
<icosahedronGeometry args={[0.05 * unit, 0]} />
<meshStandardMaterial flatShading roughness={0.8} />
</instancedMesh>
</group>
)
}