← gallery

environment

Rocks

Faceted rocks with convex-hull colliders (seeded scatter).

Install

npx runek add rocks

Pulls: core@react-three/rapier@^2.2.0

Props

export interface RocksProps {
  position?: Vec3
  rotation?: Vec3
  count?: number
  /** Cluster radius, in units. */
  spread?: number
  /** Mean rock radius, in units. */
  size?: number
  hue?: number
  seed?: number
}

Source

Rocks.tsx
import { RigidBody } from '@react-three/rapier'
import { rng, useWorld, type Vec3 } from '@runek/core'
import { useMemo } from 'react'

export interface RocksProps {
  position?: Vec3
  rotation?: Vec3
  count?: number
  /** Cluster radius, in units. */
  spread?: number
  /** Mean rock radius, in units. */
  size?: number
  hue?: number
  seed?: number
}

interface Rock {
  pos: Vec3
  scale: Vec3
  rot: Vec3
  radius: number
  lightness: number
}

export function Rocks({
  position = [0, 0, 0],
  rotation = [0, 0, 0],
  count = 6,
  spread = 3,
  size = 0.6,
  hue = 30,
  seed = 1,
}: RocksProps) {
  const { unit } = useWorld()

  const rocks = useMemo<Rock[]>(() => {
    const next = rng(seed)
    return Array.from({ length: count }, () => {
      const radius = size * unit * (0.6 + next() * 0.8)
      return {
        radius,
        pos: [(next() - 0.5) * spread * unit, radius * 0.3, (next() - 0.5) * spread * unit],
        scale: [0.8 + next() * 0.5, 0.6 + next() * 0.5, 0.8 + next() * 0.5],
        rot: [next() * Math.PI, next() * Math.PI, next() * Math.PI],
        lightness: 30 + next() * 18,
      }
    })
  }, [count, spread, size, seed, unit])

  return (
    <RigidBody type="fixed" colliders="hull" position={position} rotation={rotation}>
      {rocks.map((r) => (
        <mesh
          key={`rock-${r.pos[0].toFixed(3)}:${r.pos[2].toFixed(3)}`}
          castShadow
          receiveShadow
          position={r.pos}
          rotation={r.rot}
          scale={r.scale}
        >
          <icosahedronGeometry args={[r.radius, 0]} />
          <meshStandardMaterial
            color={`hsl(${hue}, 8%, ${r.lightness}%)`}
            flatShading
            roughness={1}
          />
        </mesh>
      ))}
    </RigidBody>
  )
}