{
  "name": "roof",
  "title": "Roof",
  "type": "registry:component",
  "description": "Flat or gable roof.",
  "dependencies": [
    "@react-three/rapier@^2.2.0",
    "@runek/core@^0.12.0",
    "three@^0.184.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "Roof.tsx",
      "content": "import { RigidBody } from '@react-three/rapier'\nimport { useWorld, type Vec3 } from '@runek/core'\nimport { useMemo } from 'react'\nimport { ExtrudeGeometry, Shape } from 'three'\n\nexport type RoofStyle = 'flat' | 'gable'\n\nexport interface RoofProps {\n  position?: Vec3\n  rotation?: Vec3\n  /** `[width, depth]` in units. The roof rests with its base at the component origin. */\n  size?: [number, number]\n  style?: RoofStyle\n  /** Ridge height for a gable roof, in units. */\n  peak?: number\n  thickness?: number\n  overhang?: number\n  /** Cap the triangular gable ends so the attic isn't open to the outside. */\n  ends?: boolean\n  /** Defaults to the world palette's `roof` slot. */\n  color?: string\n  /** Gable end caps; defaults to the world palette's `wall` slot. */\n  endColor?: string\n}\n\nexport function Roof({\n  position = [0, 0, 0],\n  rotation = [0, 0, 0],\n  size = [8, 8],\n  style = 'gable',\n  peak = 1.6,\n  thickness = 0.18,\n  overhang = 0.3,\n  ends = true,\n  color,\n  endColor,\n}: RoofProps) {\n  const { unit, palette } = useWorld()\n  const roofColor = color ?? palette.roof\n  const w = size[0] * unit + overhang * 2 * unit\n  const d = size[1] * unit + overhang * 2 * unit\n  const t = thickness * unit\n  const ridge = peak * unit\n  const gabled = style === 'gable'\n\n  // Gable end cap: a triangular prism spanning the un-overhung depth, apex at the\n  // ridge. The base sits inside the roof's eave overhang, so it lands on the walls.\n  const innerHalfD = (size[1] / 2) * unit\n  const endGeometry = useMemo(() => {\n    if (!gabled || !ends) return null\n    const shape = new Shape()\n    shape.moveTo(-innerHalfD, 0)\n    shape.lineTo(innerHalfD, 0)\n    shape.lineTo(0, ridge)\n    shape.closePath()\n    return new ExtrudeGeometry(shape, { depth: t, bevelEnabled: false })\n  }, [gabled, ends, innerHalfD, ridge, t])\n\n  if (!gabled) {\n    return (\n      <RigidBody type=\"fixed\" colliders=\"cuboid\" position={position} rotation={rotation}>\n        <mesh castShadow receiveShadow position={[0, t / 2, 0]}>\n          <boxGeometry args={[w, t, d]} />\n          <meshStandardMaterial color={roofColor} />\n        </mesh>\n      </RigidBody>\n    )\n  }\n\n  const slope = Math.hypot(d / 2, ridge)\n  const angle = Math.atan2(ridge, d / 2)\n  const capX = (size[0] / 2) * unit\n\n  return (\n    <RigidBody type=\"fixed\" colliders=\"cuboid\" position={position} rotation={rotation}>\n      <mesh castShadow receiveShadow position={[0, ridge / 2, d / 4]} rotation={[angle, 0, 0]}>\n        <boxGeometry args={[w, t, slope]} />\n        <meshStandardMaterial color={roofColor} />\n      </mesh>\n      <mesh castShadow receiveShadow position={[0, ridge / 2, -d / 4]} rotation={[-angle, 0, 0]}>\n        <boxGeometry args={[w, t, slope]} />\n        <meshStandardMaterial color={roofColor} />\n      </mesh>\n      {endGeometry &&\n        [capX, -capX].map((x) => (\n          <mesh\n            key={x}\n            castShadow\n            receiveShadow\n            geometry={endGeometry}\n            position={[x - t / 2, 0, 0]}\n            rotation={[0, Math.PI / 2, 0]}\n          >\n            <meshStandardMaterial color={endColor ?? palette.wall} />\n          </mesh>\n        ))}\n    </RigidBody>\n  )\n}\n",
      "type": "registry:component"
    }
  ]
}
