component

Bush

Clustered foliage blobs, seeded; the mid-height density between Grass and Trees.

⌖ Explore the library in 3D → · View as Markdown

Tune every prop in the workshop →

Add it

npx @runek/cli add bush

Pulls @runek/core@^0.15.0, three@^0.184.0.

Use it

import { Bush } from './runek/Bush'

<Bush position={[0, 0, 0]} seed={1} />

Or as a node in a world file (worlds as data):

{ "type": "Bush", "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.
radius number 0.6 Overall radius, in units.
blobs number 7 Number of foliage blobs.
color color palette foliage 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/bush.json Self-contained JSON: inlined source plus resolved dependencies, exactly what runek add bush fetches.

Browse the whole catalog in the gallery →.

Source (Bush.tsx)
import { rng, useWorld, type Vec3 } from '@runek/core'
import { useMemo } from 'react'
import * as THREE from 'three'

export interface BushProps {
  position?: Vec3
  rotation?: Vec3
  /** Overall radius, in units. */
  radius?: number
  /** Number of foliage blobs. */
  blobs?: number
  /** Defaults to the world palette's `foliage` slot. */
  color?: string
  seed?: number
}

interface Blob {
  pos: Vec3
  r: number
  color: string
}

/** A clustered shrub of overlapping foliage blobs: the mid-height density between
 *  Grass and Trees. Soft, so decorative (no collider). */
export function Bush({
  position = [0, 0, 0],
  rotation = [0, 0, 0],
  radius = 0.6,
  blobs = 7,
  color,
  seed = 1,
}: BushProps) {
  const { unit, palette } = useWorld()
  const foliage = color ?? palette.foliage
  const R = radius * unit

  const items = useMemo<Blob[]>(() => {
    const next = rng(seed)
    const base = new THREE.Color(foliage)
    const tint = new THREE.Color()
    return Array.from({ length: blobs }, () => {
      const br = R * (0.45 + next() * 0.4)
      const a = next() * Math.PI * 2
      const rad = next() * R * 0.5
      const shade = 0.8 + next() * 0.4
      return {
        pos: [Math.cos(a) * rad, br * 0.7 + next() * R * 0.3, Math.sin(a) * rad] as Vec3,
        r: br,
        color: tint.copy(base).multiplyScalar(shade).getStyle(),
      }
    })
  }, [R, blobs, foliage, seed])

  return (
    <group position={position} rotation={rotation}>
      {items.map((b) => (
        <mesh
          key={`blob-${b.pos[0].toFixed(3)}:${b.pos[1].toFixed(3)}`}
          castShadow
          receiveShadow
          position={b.pos}
        >
          <icosahedronGeometry args={[b.r, 0]} />
          <meshStandardMaterial color={b.color} flatShading roughness={0.95} />
        </mesh>
      ))}
    </group>
  )
}

Bush.groundSitting = true