{
  "name": "grass",
  "title": "Grass",
  "type": "registry:component",
  "description": "Instanced grass blades (seeded scatter).",
  "dependencies": [
    "@react-three/fiber@^9.6.1",
    "@runek/core@^0.12.0",
    "three@^0.184.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "Grass.tsx",
      "content": "import { useFrame } from '@react-three/fiber'\nimport { rng, useWorld, type Vec3 } from '@runek/core'\nimport { useEffect, useLayoutEffect, useMemo, useRef } from 'react'\nimport * as THREE from 'three'\n\nexport interface GrassProps {\n  position?: Vec3\n  rotation?: Vec3\n  /** Patch extent `[width, depth]`, in units. */\n  area?: [number, number]\n  count?: number\n  height?: number\n  /** Defaults to the world palette's `foliage` slot. */\n  color?: string\n  /** Wind sway strength; 0 disables the animation. */\n  sway?: number\n  seed?: number\n}\n\ninterface Blade {\n  x: number\n  z: number\n  rot: number\n  scale: number\n  shade: number\n}\n\n/** Decorative instanced blades with a vertex-shader wind sway — no collider. */\nexport function Grass({\n  position = [0, 0, 0],\n  rotation = [0, 0, 0],\n  area = [10, 10],\n  count = 600,\n  height = 0.35,\n  color,\n  sway = 1,\n  seed = 1,\n}: GrassProps) {\n  const { unit, palette } = useWorld()\n  const bladeColor = color ?? palette.foliage\n  const w = area[0] * unit\n  const d = area[1] * unit\n  const h = height * unit\n  const ref = useRef<THREE.InstancedMesh>(null)\n\n  // Shared uniform objects so the patched shader and useFrame see the same values.\n  const swayUniforms = useRef({\n    uTime: { value: 0 },\n    uBladeHeight: { value: h },\n    uSway: { value: sway * h * 0.18 },\n  })\n  swayUniforms.current.uBladeHeight.value = h\n  swayUniforms.current.uSway.value = sway * h * 0.18\n\n  const material = useMemo(() => {\n    const mat = new THREE.MeshStandardMaterial()\n    mat.onBeforeCompile = (shader) => {\n      shader.uniforms.uTime = swayUniforms.current.uTime\n      shader.uniforms.uBladeHeight = swayUniforms.current.uBladeHeight\n      shader.uniforms.uSway = swayUniforms.current.uSway\n      shader.vertexShader = `\n        uniform float uTime;\n        uniform float uBladeHeight;\n        uniform float uSway;\n        ${shader.vertexShader}\n      `.replace(\n        '#include <begin_vertex>',\n        /* glsl */ `\n        #include <begin_vertex>\n        #ifdef USE_INSTANCING\n          float swayPhase = instanceMatrix[3].x * 0.9 + instanceMatrix[3].z * 1.3;\n          float swayAmt = clamp(position.y / uBladeHeight + 0.5, 0.0, 1.0) * uSway;\n          transformed.x += sin(uTime * 1.7 + swayPhase) * swayAmt;\n          transformed.z += cos(uTime * 1.2 + swayPhase) * swayAmt * 0.6;\n        #endif\n        `,\n      )\n    }\n    mat.customProgramCacheKey = () => 'runek-grass-sway'\n    return mat\n  }, [])\n\n  useEffect(() => () => material.dispose(), [material])\n\n  useFrame((_, delta) => {\n    if (sway > 0) swayUniforms.current.uTime.value += delta\n  })\n\n  const blades = useMemo<Blade[]>(() => {\n    const next = rng(seed)\n    return Array.from({ length: count }, () => ({\n      x: (next() - 0.5) * w,\n      z: (next() - 0.5) * d,\n      rot: next() * Math.PI,\n      scale: 0.6 + next() * 0.8,\n      shade: 0.8 + next() * 0.4,\n    }))\n  }, [w, d, count, seed])\n\n  useLayoutEffect(() => {\n    const mesh = ref.current\n    if (!mesh) return\n    const dummy = new THREE.Object3D()\n    const base = new THREE.Color(bladeColor)\n    const tint = new THREE.Color()\n    blades.forEach((b, i) => {\n      dummy.position.set(b.x, (h * b.scale) / 2, b.z)\n      dummy.rotation.set(0, b.rot, 0)\n      dummy.scale.set(1, b.scale, 1)\n      dummy.updateMatrix()\n      mesh.setMatrixAt(i, dummy.matrix)\n      mesh.setColorAt(i, tint.copy(base).multiplyScalar(b.shade))\n    })\n    mesh.instanceMatrix.needsUpdate = true\n    if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true\n  }, [blades, h, bladeColor])\n\n  return (\n    <group position={position} rotation={rotation}>\n      <instancedMesh\n        ref={ref}\n        args={[undefined, undefined, count]}\n        material={material}\n        castShadow\n        receiveShadow\n      >\n        <coneGeometry args={[0.015 * unit, h, 4]} />\n      </instancedMesh>\n    </group>\n  )\n}\n",
      "type": "registry:component"
    }
  ]
}
