{
  "name": "tableware",
  "title": "Tableware",
  "type": "registry:component",
  "description": "Tableware to set a table: a plate, bowl, glass, cup, or water jug turned from a profile, or a whole place setting. Drinks fill to a line; plates and bowls stack.",
  "dependencies": [
    "@runek/core@^0.15.0",
    "three@^0.184.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "Tableware.tsx",
      "content": "import { pick, range, rng, sub, useWorld, type WorldComponentProps } from '@runek/core'\nimport { useMemo } from 'react'\nimport { LatheGeometry, Vector2 } from 'three'\n\n/** One piece, or a whole place `setting` (a plate, a glass, a knife, and a fork). */\nexport type TablewareKind = 'plate' | 'bowl' | 'glass' | 'cup' | 'jug' | 'setting'\n\nexport interface TablewareProps extends WorldComponentProps {\n  /** Which piece; a plate by default. */\n  kind?: TablewareKind\n  /** Scale on the piece's true size (a 26 cm plate, an 11 cm glass, a 23 cm jug). */\n  size?: number\n  /** Plates or bowls stacked on each other. */\n  stack?: number\n  /** How full a glass, cup, or jug is, 0 to 1. Seeded when unset. */\n  fill?: number\n  /** The drink: water by default. */\n  drinkColor?: string\n  /** Glaze for plates, bowls, and cups. Seeded (mostly white) when unset. */\n  color?: string\n  /** Glass for glasses and the jug. */\n  glassColor?: string\n}\n\ntype Profile = [number, number][]\n\n// Closed outlines in meters, (radius, height), from the axis at the foot round to the axis on top.\n// `inner` is the cavity wall from the bottom up, which the drink fills.\nconst SHAPES: Record<Exclude<TablewareKind, 'setting'>, { outline: Profile; inner?: Profile }> = {\n  plate: {\n    outline: [\n      [0, 0],\n      [0.08, 0],\n      [0.085, 0.006],\n      [0.13, 0.02],\n      [0.127, 0.024],\n      [0.082, 0.01],\n      [0, 0.01],\n    ],\n  },\n  bowl: {\n    outline: [\n      [0, 0],\n      [0.035, 0],\n      [0.04, 0.008],\n      [0.075, 0.05],\n      [0.08, 0.07],\n      [0.075, 0.07],\n      [0.07, 0.052],\n      [0.035, 0.012],\n      [0, 0.012],\n    ],\n  },\n  glass: {\n    outline: [\n      [0, 0],\n      [0.03, 0],\n      [0.036, 0.11],\n      [0.033, 0.11],\n      [0.027, 0.008],\n      [0, 0.008],\n    ],\n    inner: [\n      [0.027, 0.008],\n      [0.033, 0.11],\n    ],\n  },\n  cup: {\n    outline: [\n      [0, 0],\n      [0.038, 0],\n      [0.041, 0.09],\n      [0.037, 0.09],\n      [0.034, 0.008],\n      [0, 0.008],\n    ],\n    inner: [\n      [0.034, 0.008],\n      [0.037, 0.09],\n    ],\n  },\n  jug: {\n    outline: [\n      [0, 0],\n      [0.058, 0],\n      [0.068, 0.05],\n      [0.064, 0.14],\n      [0.052, 0.2],\n      [0.058, 0.23],\n      [0.054, 0.23],\n      [0.048, 0.2],\n      [0.06, 0.14],\n      [0.064, 0.05],\n      [0.055, 0.006],\n      [0, 0.006],\n    ],\n    inner: [\n      [0.055, 0.006],\n      [0.064, 0.05],\n      [0.06, 0.14],\n      [0.048, 0.2],\n      [0.054, 0.23],\n    ],\n  },\n}\n\nconst GLAZES = ['#f4f1ea', '#f4f1ea', '#ece6d8', '#dfe8ee', '#2f4a6b', '#c96f4a', '#3f6b52']\nconst SILVER = '#c8ccd2'\n\nfunction lathe(points: Profile, k: number) {\n  return new LatheGeometry(\n    points.map(([r, y]) => new Vector2(r * k, y * k)),\n    28,\n  )\n}\n\n/** The drink: the cavity's wall cut at the fill line, closed to the axis top and bottom. */\nfunction liquid(inner: Profile, fill: number, k: number) {\n  const top = inner[0][1] + (inner[inner.length - 1][1] - inner[0][1]) * fill * 0.92\n  const pts: Profile = [[0, inner[0][1]]]\n  for (let i = 0; i < inner.length; i++) {\n    const [r, y] = inner[i]\n    if (y <= top) {\n      pts.push([r * 0.97, y])\n      continue\n    }\n    const [r0, y0] = inner[i - 1]\n    pts.push([(r0 + ((r - r0) * (top - y0)) / (y - y0)) * 0.97, top])\n    break\n  }\n  if (pts[pts.length - 1][1] < top) pts.push([pts[pts.length - 1][0], top])\n  pts.push([0, top])\n  return lathe(pts, k)\n}\n\nfunction Piece({\n  kind,\n  k,\n  glaze,\n  glass,\n  drink,\n  fill,\n  stack = 1,\n}: {\n  kind: Exclude<TablewareKind, 'setting'>\n  k: number\n  glaze: string\n  glass: string\n  drink: string\n  fill: number\n  stack?: number\n}) {\n  const shape = SHAPES[kind]\n  const body = useMemo(() => lathe(shape.outline, k), [shape, k])\n  const water = useMemo(\n    () => (shape.inner && fill > 0 ? liquid(shape.inner, fill, k) : null),\n    [shape, fill, k],\n  )\n  const glassy = kind === 'glass' || kind === 'jug'\n  const material = glassy ? (\n    <meshPhysicalMaterial\n      color={glass}\n      roughness={0.05}\n      metalness={0}\n      transparent\n      opacity={0.35}\n      depthWrite={false}\n      side={2}\n    />\n  ) : (\n    <meshStandardMaterial color={glaze} roughness={0.25} side={2} />\n  )\n  const step = (kind === 'bowl' ? 0.03 : 0.012) * k\n  const layers = kind === 'plate' || kind === 'bowl' ? Math.max(1, Math.round(stack)) : 1\n\n  return (\n    <group>\n      {Array.from({ length: layers }, (_, i) => (\n        <mesh key={i} geometry={body} position={[0, i * step, 0]} castShadow>\n          {material}\n        </mesh>\n      ))}\n      {water && (\n        <mesh geometry={water} renderOrder={-1}>\n          <meshStandardMaterial color={drink} roughness={0.1} transparent opacity={0.75} />\n        </mesh>\n      )}\n      {(kind === 'cup' || kind === 'jug') && (\n        // the handle: a half ring on the +X side\n        <mesh\n          position={[(kind === 'cup' ? 0.04 : 0.06) * k, (kind === 'cup' ? 0.048 : 0.13) * k, 0]}\n          rotation={[0, 0, -Math.PI / 2]}\n          castShadow\n        >\n          <torusGeometry args={[(kind === 'cup' ? 0.022 : 0.045) * k, 0.005 * k, 8, 16, Math.PI]} />\n          {material}\n        </mesh>\n      )}\n    </group>\n  )\n}\n\n/**\n * Tableware to set a `Table` or a `Counter`: a plate, a bowl, a glass, a cup, or a water jug,\n * each turned from a profile, or a whole place `setting`. Glasses, cups, and the jug hold a drink\n * to a `fill` line. Its origin is the foot, so it sits on the top it is placed on; a setting faces\n * the diner along local +Z. Plates and bowls stack. Decorative: no collider.\n */\nexport function Tableware({\n  position = [0, 0, 0],\n  rotation = [0, 0, 0],\n  seed = 1,\n  kind = 'plate',\n  size = 1,\n  stack = 1,\n  fill,\n  drinkColor = '#9fd3e8',\n  color,\n  glassColor = '#d8eef6',\n}: TablewareProps) {\n  const { unit } = useWorld()\n  const k = size * unit\n\n  const look = useMemo(() => {\n    const r = rng(sub(seed, 0))\n    return { glaze: color ?? pick(r, GLAZES), fill: fill ?? range(r, 0.35, 0.85) }\n  }, [seed, color, fill])\n\n  const shared = { k, glaze: look.glaze, glass: glassColor, drink: drinkColor, fill: look.fill }\n\n  if (kind !== 'setting') {\n    return (\n      <group position={position} rotation={rotation}>\n        <Piece kind={kind} stack={stack} {...shared} />\n      </group>\n    )\n  }\n\n  const flat = 0.004 * k\n  return (\n    <group position={position} rotation={rotation}>\n      <Piece kind=\"plate\" {...shared} />\n      <group position={[0.15 * k, 0, -0.14 * k]}>\n        <Piece kind=\"glass\" {...shared} />\n      </group>\n      {/* knife on the right: the handle toward the diner, the blade away */}\n      <group position={[0.165 * k, flat / 2, 0]}>\n        <mesh position={[0, 0, 0.05 * k]} castShadow>\n          <boxGeometry args={[0.014 * k, flat, 0.1 * k]} />\n          <meshStandardMaterial color={SILVER} metalness={0.4} roughness={0.3} />\n        </mesh>\n        <mesh position={[-0.002 * k, 0, -0.055 * k]} castShadow>\n          <boxGeometry args={[0.02 * k, flat * 0.6, 0.11 * k]} />\n          <meshStandardMaterial color={SILVER} metalness={0.4} roughness={0.3} />\n        </mesh>\n      </group>\n      {/* fork on the left: a handle, a neck, four tines */}\n      <group position={[-0.165 * k, flat / 2, 0]}>\n        <mesh position={[0, 0, 0.055 * k]} castShadow>\n          <boxGeometry args={[0.012 * k, flat, 0.1 * k]} />\n          <meshStandardMaterial color={SILVER} metalness={0.4} roughness={0.3} />\n        </mesh>\n        <mesh position={[0, 0, -0.015 * k]}>\n          <boxGeometry args={[0.022 * k, flat, 0.04 * k]} />\n          <meshStandardMaterial color={SILVER} metalness={0.4} roughness={0.3} />\n        </mesh>\n        {[-1.5, -0.5, 0.5, 1.5].map((t) => (\n          <mesh key={t} position={[t * 0.0055 * k, 0, -0.06 * k]}>\n            <boxGeometry args={[0.003 * k, flat, 0.05 * k]} />\n            <meshStandardMaterial color={SILVER} metalness={0.4} roughness={0.3} />\n          </mesh>\n        ))}\n      </group>\n    </group>\n  )\n}\n",
      "type": "registry:component"
    }
  ]
}
