{
  "name": "hut",
  "title": "Hut",
  "type": "registry:component",
  "description": "A round hut: a post-ribbed wall on a stone base, a framed doorway and window, under a shaggy thatch cone; one cylinder collider.",
  "dependencies": [
    "@react-three/rapier@^2.2.0",
    "@runek/core@^0.12.0",
    "three@^0.184.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "Hut.tsx",
      "content": "import { CylinderCollider, RigidBody } from '@react-three/rapier'\nimport { rng, useWorld, type WorldComponentProps } from '@runek/core'\nimport { useEffect, useMemo } from 'react'\nimport { ConeGeometry, DoubleSide } from 'three'\n\nexport interface HutProps extends WorldComponentProps {\n  /** Wall radius, in units. */\n  radius?: number\n  /** Wall height at the eaves, in units. */\n  wallHeight?: number\n  /** Conical roof height above the eaves, in units. */\n  roofHeight?: number\n  /** Doorway width at the front (local +Z), in units. */\n  doorWidth?: number\n  /** Wall color; defaults to the palette's `wall`. */\n  wallColor?: string\n  /** Roof color; defaults to the palette's `roof`. */\n  roofColor?: string\n}\n\n/**\n * A round hut: a cylindrical wall with a front doorway under a shaggy conical thatch roof. The\n * wall is ringed by post ribs on a low stone base, the roof is seed-jittered with a ragged fringe,\n * protruding rafter tips, and a topknot finial, and a small framed window sits at the back. The\n * doorway faces local +Z. A single cylinder collider makes it a solid obstacle (the doorway is\n * visual — the hut is composed, not entered). For a square house with a gable roof, use `House`.\n */\nexport function Hut({\n  position = [0, 0, 0],\n  rotation = [0, 0, 0],\n  radius = 3,\n  wallHeight = 2.9,\n  roofHeight = 1.5,\n  doorWidth = 1.3,\n  wallColor,\n  roofColor,\n  seed = 1,\n}: HutProps) {\n  const { unit, palette } = useWorld()\n  const wall = wallColor ?? palette.wall\n  const roof = roofColor ?? palette.roof\n  const post = palette.woodDark\n  const R = radius * unit\n  const H = wallHeight * unit\n  const roofH = roofHeight * unit\n  const postR = 0.1 * unit\n\n  // Leave a gap in the wall centred on +Z (three's cylinder: x=R·sinθ, z=R·cosθ, so θ=0 is +Z).\n  const half = Math.asin(Math.min(0.9, doorWidth / 2 / radius))\n  const postZ = R * Math.cos(half)\n  const postX = R * Math.sin(half)\n  const doorH = Math.min(2 * unit, H - 0.4 * unit)\n\n  const coneR = R * 1.28\n  const slant = Math.hypot(coneR, roofH)\n\n  // Shaggy thatch: a cone whose vertices are seed-jittered, with the rim drooping unevenly so the\n  // silhouette reads as a straw fringe rather than a machined lampshade.\n  const thatch = useMemo(() => {\n    const g = new ConeGeometry(coneR, roofH, 20, 4)\n    g.translate(0, roofH / 2, 0) // base at local y=0 (the eaves)\n    const next = rng(seed)\n    const amp = coneR * 0.045\n    const pos = g.attributes.position\n    for (let i = 0; i < pos.count; i++) {\n      const y = pos.getY(i)\n      const apex = y > roofH - 0.01\n      if (apex) continue\n      pos.setX(i, pos.getX(i) + (next() * 2 - 1) * amp)\n      pos.setZ(i, pos.getZ(i) + (next() * 2 - 1) * amp)\n      const rim = y < 0.01\n      pos.setY(i, y + (next() * 2 - 1) * amp * 0.6 - (rim ? next() * roofH * 0.14 : 0))\n    }\n    pos.needsUpdate = true\n    g.computeVertexNormals()\n    return g\n  }, [coneR, roofH, seed])\n\n  useEffect(() => () => thatch.dispose(), [thatch])\n\n  // Post ribs around the wall (skipping the doorway arc) and rafter tips under the fringe.\n  const ribs = useMemo(() => {\n    const arc = Math.PI * 2 - half * 2 - 0.5\n    return Array.from({ length: 9 }, (_, i) => half + 0.25 + (arc * (i + 0.5)) / 9)\n  }, [half])\n  const rafters = useMemo(() => Array.from({ length: 8 }, (_, i) => (Math.PI * 2 * i) / 8), [])\n  const rafterTilt = Math.atan2(coneR, roofH)\n\n  return (\n    <RigidBody type=\"fixed\" colliders={false} position={position} rotation={rotation}>\n      <CylinderCollider args={[H / 2, R]} position={[0, H / 2, 0]} />\n\n      {/* low stone base the wall sits on */}\n      <mesh position={[0, 0.15 * unit, 0]} castShadow receiveShadow>\n        <cylinderGeometry args={[R * 1.05, R * 1.1, 0.3 * unit, 24]} />\n        <meshStandardMaterial color={palette.stone} roughness={1} flatShading />\n      </mesh>\n\n      {/* a dark interior floor, so the doorway reads as an opening into the hut */}\n      <mesh position={[0, 0.31 * unit, 0]} receiveShadow>\n        <cylinderGeometry args={[R * 0.94, R * 0.94, 0.06 * unit, 24]} />\n        <meshStandardMaterial color={palette.bark} roughness={1} />\n      </mesh>\n\n      {/* wall: a cylindrical shell with the doorway arc omitted */}\n      <mesh position={[0, H / 2, 0]} castShadow receiveShadow>\n        <cylinderGeometry args={[R, R, H, 32, 1, true, half, Math.PI * 2 - half * 2]} />\n        <meshStandardMaterial color={wall} side={DoubleSide} roughness={0.9} />\n      </mesh>\n\n      {/* wall filling above the doorway — the opening stops at the lintel */}\n      <mesh position={[0, (doorH + H) / 2, 0]} castShadow receiveShadow>\n        <cylinderGeometry args={[R, R, H - doorH, 8, 1, true, -half, half * 2]} />\n        <meshStandardMaterial color={wall} side={DoubleSide} roughness={0.9} />\n      </mesh>\n\n      {/* post ribs around the wall, so it reads as timber-and-daub rather than smooth plaster */}\n      {ribs.map((a) => (\n        <mesh\n          key={a}\n          position={[Math.sin(a) * (R + 0.02 * unit), H / 2, Math.cos(a) * (R + 0.02 * unit)]}\n          castShadow\n        >\n          <cylinderGeometry args={[0.055 * unit, 0.07 * unit, H * 0.97, 6]} />\n          <meshStandardMaterial color={palette.wood} roughness={0.9} />\n        </mesh>\n      ))}\n\n      {/* lintel + door posts framing the opening */}\n      <mesh position={[0, doorH, postZ]} castShadow>\n        <boxGeometry args={[postX * 2 + postR * 2, 0.14 * unit, postR * 2]} />\n        <meshStandardMaterial color={post} roughness={0.8} />\n      </mesh>\n      <mesh position={[postX, doorH / 2, postZ]} castShadow>\n        <cylinderGeometry args={[postR, postR, doorH, 8]} />\n        <meshStandardMaterial color={post} roughness={0.8} />\n      </mesh>\n      <mesh position={[-postX, doorH / 2, postZ]} castShadow>\n        <cylinderGeometry args={[postR, postR, doorH, 8]} />\n        <meshStandardMaterial color={post} roughness={0.8} />\n      </mesh>\n\n      {/* a small framed window at the back */}\n      <group rotation={[0, Math.PI, 0]}>\n        <mesh position={[0, H * 0.55, R]} castShadow>\n          <boxGeometry args={[0.7 * unit, 0.8 * unit, 0.06 * unit]} />\n          <meshStandardMaterial color={post} roughness={0.8} />\n        </mesh>\n        <mesh position={[0, H * 0.55, R + 0.03 * unit]}>\n          <boxGeometry args={[0.52 * unit, 0.62 * unit, 0.06 * unit]} />\n          <meshStandardMaterial color={palette.bark} roughness={1} />\n        </mesh>\n      </group>\n\n      {/* shaggy thatch cone, overhanging the eaves */}\n      <mesh geometry={thatch} position={[0, H, 0]} castShadow>\n        <meshStandardMaterial color={roof} roughness={1} flatShading side={DoubleSide} />\n      </mesh>\n\n      {/* rafter tips poking out under the fringe */}\n      {rafters.map((a) => (\n        <group key={a} rotation={[0, a, 0]}>\n          <mesh\n            position={[coneR * 0.65, H + roofH * 0.35, 0]}\n            rotation={[0, 0, rafterTilt]}\n            castShadow\n          >\n            <cylinderGeometry args={[0.04 * unit, 0.05 * unit, slant * 0.85, 6]} />\n            <meshStandardMaterial color={post} roughness={0.9} />\n          </mesh>\n        </group>\n      ))}\n\n      {/* topknot finial where the thatch is bound off */}\n      <mesh position={[0, H + roofH + 0.12 * unit, 0]} castShadow>\n        <cylinderGeometry args={[0.05 * unit, 0.05 * unit, 0.55 * unit, 6]} />\n        <meshStandardMaterial color={post} roughness={0.9} />\n      </mesh>\n      <mesh position={[0, H + roofH + 0.1 * unit, 0]} castShadow>\n        <coneGeometry args={[0.2 * unit, 0.3 * unit, 8]} />\n        <meshStandardMaterial color={roof} roughness={1} flatShading />\n      </mesh>\n    </RigidBody>\n  )\n}\n",
      "type": "registry:component"
    }
  ]
}
