{
  "name": "trees",
  "title": "Trees",
  "type": "registry:component",
  "description": "L-system trees grown by a 3D turtle, deterministic from seed.",
  "dependencies": [
    "@react-three/rapier@^2.2.0",
    "@runek/core@^0.12.0",
    "three@^0.184.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "Trees.tsx",
      "content": "import { CylinderCollider, RigidBody } from '@react-three/rapier'\nimport { rng, useWorld, type Vec3 } from '@runek/core'\nimport { useLayoutEffect, useMemo, useRef } from 'react'\nimport * as THREE from 'three'\n\nexport interface TreesProps {\n  position?: Vec3\n  rotation?: Vec3\n  seed?: number\n  iterations?: number\n  /** Base branch length, in units. */\n  segmentLength?: number\n  /** Branching angle, in radians. */\n  angle?: number\n  /** Defaults to the world palette's `bark` slot. */\n  trunkColor?: string\n  /** Defaults to the world palette's `foliage` slot. */\n  leafColor?: string\n}\n\ninterface Branch {\n  position: THREE.Vector3\n  quaternion: THREE.Quaternion\n  length: number\n  radius: number\n}\n\ninterface Leaf {\n  position: THREE.Vector3\n  size: number\n  /** Per-leaf brightness multiplier for color variation. */\n  shade: number\n}\n\nconst AXIOM = 'F'\nconst RULE = 'FF[+F][-F][&F][^F]'\nconst UP = new THREE.Vector3(0, 1, 0)\nconst YAW_AXIS = new THREE.Vector3(0, 0, 1)\nconst PITCH_AXIS = new THREE.Vector3(1, 0, 0)\n\nfunction expand(iterations: number): string {\n  let s = AXIOM\n  for (let i = 0; i < iterations; i++) s = s.replace(/F/g, RULE)\n  return s\n}\n\nfunction buildTree(seed: number, iterations: number, segLen: number, angle: number) {\n  const symbols = expand(iterations)\n  const next = rng(seed)\n  const jitter = () => (next() - 0.5) * angle * 0.5\n  const branches: Branch[] = []\n  const leaves: Leaf[] = []\n  const stack: { pos: THREE.Vector3; quat: THREE.Quaternion; depth: number }[] = []\n  const delta = new THREE.Quaternion()\n  let pos = new THREE.Vector3()\n  let quat = new THREE.Quaternion()\n  let depth = 0\n\n  const addLeaf = (at: THREE.Vector3) => {\n    leaves.push({ position: at.clone(), size: segLen * 0.45, shade: 0.8 + next() * 0.4 })\n  }\n\n  for (const ch of symbols) {\n    switch (ch) {\n      case 'F': {\n        const len = segLen * 0.8 ** depth * (0.85 + next() * 0.3)\n        const dir = UP.clone().applyQuaternion(quat)\n        const start = pos.clone()\n        pos = start.clone().addScaledVector(dir, len)\n        const mid = start.clone().add(pos).multiplyScalar(0.5)\n        branches.push({\n          position: mid,\n          quaternion: new THREE.Quaternion().setFromUnitVectors(UP, dir),\n          length: len,\n          radius: segLen * 0.12 * 0.7 ** depth,\n        })\n        break\n      }\n      case '+':\n        quat.multiply(delta.setFromAxisAngle(YAW_AXIS, angle + jitter()))\n        break\n      case '-':\n        quat.multiply(delta.setFromAxisAngle(YAW_AXIS, -angle + jitter()))\n        break\n      case '&':\n        quat.multiply(delta.setFromAxisAngle(PITCH_AXIS, angle + jitter()))\n        break\n      case '^':\n        quat.multiply(delta.setFromAxisAngle(PITCH_AXIS, -angle + jitter()))\n        break\n      case '[':\n        stack.push({ pos: pos.clone(), quat: quat.clone(), depth })\n        depth++\n        break\n      case ']': {\n        addLeaf(pos)\n        const saved = stack.pop()\n        if (saved) {\n          pos = saved.pos\n          quat = saved.quat\n          depth = saved.depth\n        }\n        break\n      }\n    }\n  }\n  addLeaf(pos)\n  return { branches, leaves }\n}\n\n/**\n * A single procedural tree grown from a bracketed L-system.\n * Branches and leaves render as one instanced mesh each, so a forest stays cheap.\n */\nexport function Trees({\n  position = [0, 0, 0],\n  rotation = [0, 0, 0],\n  seed = 1,\n  iterations = 2,\n  segmentLength = 0.7,\n  angle = 0.5,\n  trunkColor,\n  leafColor,\n}: TreesProps) {\n  const { unit, palette } = useWorld()\n  const bark = trunkColor ?? palette.bark\n  const foliage = leafColor ?? palette.foliage\n  const segLen = segmentLength * unit\n  const branchesRef = useRef<THREE.InstancedMesh>(null)\n  const leavesRef = useRef<THREE.InstancedMesh>(null)\n\n  const { branches, leaves } = useMemo(\n    () => buildTree(seed, iterations, segLen, angle),\n    [seed, iterations, segLen, angle],\n  )\n\n  useLayoutEffect(() => {\n    const mesh = branchesRef.current\n    if (!mesh) return\n    const dummy = new THREE.Object3D()\n    branches.forEach((b, i) => {\n      dummy.position.copy(b.position)\n      dummy.quaternion.copy(b.quaternion)\n      dummy.scale.set(b.radius, b.length, b.radius)\n      dummy.updateMatrix()\n      mesh.setMatrixAt(i, dummy.matrix)\n    })\n    mesh.count = branches.length\n    mesh.instanceMatrix.needsUpdate = true\n  }, [branches])\n\n  useLayoutEffect(() => {\n    const mesh = leavesRef.current\n    if (!mesh) return\n    const dummy = new THREE.Object3D()\n    const base = new THREE.Color(foliage)\n    const tint = new THREE.Color()\n    leaves.forEach((leaf, i) => {\n      dummy.position.copy(leaf.position)\n      dummy.rotation.set(0, 0, 0)\n      dummy.scale.setScalar(leaf.size)\n      dummy.updateMatrix()\n      mesh.setMatrixAt(i, dummy.matrix)\n      mesh.setColorAt(i, tint.copy(base).multiplyScalar(leaf.shade))\n    })\n    mesh.count = leaves.length\n    mesh.instanceMatrix.needsUpdate = true\n    if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true\n  }, [leaves, foliage])\n\n  return (\n    <RigidBody type=\"fixed\" colliders={false} position={position} rotation={rotation}>\n      <CylinderCollider args={[segLen, segLen * 0.2]} position={[0, segLen, 0]} />\n      <instancedMesh\n        key={`b${branches.length}`}\n        ref={branchesRef}\n        args={[undefined, undefined, branches.length]}\n        castShadow\n      >\n        {/* unit cylinder, tapered; instances scale x/z by radius and y by length */}\n        <cylinderGeometry args={[0.7, 1, 1, 5]} />\n        <meshStandardMaterial color={bark} roughness={0.95} />\n      </instancedMesh>\n      <instancedMesh\n        key={`l${leaves.length}`}\n        ref={leavesRef}\n        args={[undefined, undefined, leaves.length]}\n        castShadow\n      >\n        <icosahedronGeometry args={[1, 0]} />\n        <meshStandardMaterial flatShading />\n      </instancedMesh>\n    </RigidBody>\n  )\n}\n",
      "type": "registry:component"
    }
  ]
}
