{
  "name": "tabletennis",
  "title": "TableTennis",
  "type": "registry:component",
  "description": "A regulation table tennis table with its lines, a net, a folding undercarriage, and paddles and a ball left on it.",
  "dependencies": [
    "@react-three/rapier@^2.2.0",
    "@runek/core@^0.15.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "TableTennis.tsx",
      "content": "import { CuboidCollider, RigidBody } from '@react-three/rapier'\nimport { pick, range, rng, sub, useWorld, type WorldComponentProps } from '@runek/core'\nimport { useMemo } from 'react'\n\nexport interface TableTennisProps extends WorldComponentProps {\n  /** The playing top. Seeded (blue or green) when unset. */\n  color?: string\n  /** White lines along the edges and down the middle. */\n  lineColor?: string\n  /** Legs and net posts; defaults to the palette's `metal`. */\n  frameColor?: string\n  /** Two paddles lying on the table. */\n  paddles?: boolean\n  /** A ball on the table. */\n  ball?: boolean\n}\n\nconst TOPS = ['#1f4f8a', '#1f4f8a', '#2f6b4a', '#2b3a4f']\n// Regulation size in meters: 2.74 long (local X), 1.525 wide, the top 0.76 up.\nconst L = 2.74\nconst W = 1.525\nconst H = 0.76\nconst T = 0.025\n\n/**\n * A table tennis table at regulation size, long along local X: a painted top with its white\n * lines, a net with posts across the middle, a folding undercarriage, and a pair of paddles and a\n * ball left on it at seeded spots. One cuboid collider for the table; it stands on the ground.\n */\nexport function TableTennis({\n  position = [0, 0, 0],\n  rotation = [0, 0, 0],\n  seed = 1,\n  color,\n  lineColor = '#f4f4f4',\n  frameColor,\n  paddles = true,\n  ball = true,\n}: TableTennisProps) {\n  const { unit, palette } = useWorld()\n  const u = unit\n  const frame = frameColor ?? palette.metal\n\n  const look = useMemo(() => {\n    const r = rng(sub(seed, 0))\n    const side = r() < 0.5 ? -1 : 1\n    return {\n      top: color ?? pick(r, TOPS),\n      paddles: [\n        { x: side * range(r, 0.5, 1.1), z: range(r, -0.5, 0.5), turn: r() * Math.PI * 2 },\n        { x: -side * range(r, 0.5, 1.1), z: range(r, -0.5, 0.5), turn: r() * Math.PI * 2 },\n      ],\n      ball: { x: (r() < 0.5 ? -1 : 1) * range(r, 0.15, 1.15), z: range(r, -0.6, 0.6) },\n    }\n  }, [seed, color])\n\n  const top = H * u\n  const line = 0.02 * u\n  const lines: { pos: [number, number]; size: [number, number] }[] = [\n    { pos: [0, (W / 2 - 0.01) * u], size: [L * u, line] },\n    { pos: [0, -(W / 2 - 0.01) * u], size: [L * u, line] },\n    { pos: [(L / 2 - 0.01) * u, 0], size: [line, W * u] },\n    { pos: [-(L / 2 - 0.01) * u, 0], size: [line, W * u] },\n    { pos: [0, 0], size: [L * u, 0.003 * u] },\n  ]\n  const legX = (L / 2 - 0.35) * u\n  const legZ = (W / 2 - 0.12) * u\n  const netH = 0.1525 * u\n  const netW = (W + 0.3) * u\n\n  return (\n    <RigidBody type=\"fixed\" colliders={false} position={position} rotation={rotation}>\n      <CuboidCollider args={[(L / 2) * u, top / 2, (W / 2) * u]} position={[0, top / 2, 0]} />\n      <mesh position={[0, top - (T * u) / 2, 0]} castShadow receiveShadow>\n        <boxGeometry args={[L * u, T * u, W * u]} />\n        <meshStandardMaterial color={look.top} roughness={0.55} />\n      </mesh>\n      {lines.map((l) => (\n        <mesh\n          key={`${l.pos[0]}:${l.pos[1]}`}\n          position={[l.pos[0], top + 0.0005 * u, l.pos[1]]}\n          rotation={[-Math.PI / 2, 0, 0]}\n        >\n          <planeGeometry args={l.size} />\n          <meshStandardMaterial color={lineColor} roughness={0.5} />\n        </mesh>\n      ))}\n\n      {/* the undercarriage: four legs braced in pairs, and a center rail */}\n      {[-1, 1].map((sx) => (\n        <group key={sx} position={[sx * legX, 0, 0]}>\n          {[-1, 1].map((sz) => (\n            <mesh key={sz} position={[0, (top - T * u) / 2, sz * legZ]} castShadow>\n              <boxGeometry args={[0.04 * u, top - T * u, 0.04 * u]} />\n              <meshStandardMaterial color={frame} metalness={0.4} roughness={0.5} />\n            </mesh>\n          ))}\n          <mesh position={[0, 0.15 * u, 0]}>\n            <boxGeometry args={[0.03 * u, 0.03 * u, legZ * 2]} />\n            <meshStandardMaterial color={frame} metalness={0.4} roughness={0.5} />\n          </mesh>\n          {[-1, 1].map((sz) => (\n            <mesh key={`w${sz}`} position={[0, 0.03 * u, sz * legZ]} rotation={[0, 0, Math.PI / 2]}>\n              <cylinderGeometry args={[0.03 * u, 0.03 * u, 0.025 * u, 12]} />\n              <meshStandardMaterial color=\"#1b1b1e\" roughness={0.8} />\n            </mesh>\n          ))}\n        </group>\n      ))}\n      <mesh position={[0, top - T * u - 0.04 * u, 0]}>\n        <boxGeometry args={[legX * 2, 0.05 * u, 0.04 * u]} />\n        <meshStandardMaterial color={frame} metalness={0.4} roughness={0.5} />\n      </mesh>\n\n      {/* the net: a dark mesh panel with a white tape on top, clamped by two posts */}\n      <mesh position={[0, top + netH / 2, 0]}>\n        <boxGeometry args={[0.004 * u, netH, netW]} />\n        <meshStandardMaterial color=\"#1a1c22\" roughness={0.9} transparent opacity={0.75} />\n      </mesh>\n      <mesh position={[0, top + netH - 0.008 * u, 0]}>\n        <boxGeometry args={[0.006 * u, 0.016 * u, netW]} />\n        <meshStandardMaterial color={lineColor} roughness={0.6} />\n      </mesh>\n      {[-1, 1].map((s) => (\n        <mesh key={`post${s}`} position={[0, top + netH / 2 - 0.01 * u, s * (netW / 2)]}>\n          <cylinderGeometry args={[0.008 * u, 0.008 * u, netH + 0.02 * u, 8]} />\n          <meshStandardMaterial color={frame} metalness={0.5} roughness={0.4} />\n        </mesh>\n      ))}\n\n      {paddles &&\n        look.paddles.map((p, i) => (\n          <group\n            key={`${p.x.toFixed(3)}:${p.z.toFixed(3)}`}\n            position={[p.x * u, top + 0.006 * u, p.z * u]}\n            rotation={[0, p.turn, 0]}\n          >\n            <mesh castShadow>\n              <cylinderGeometry args={[0.075 * u, 0.075 * u, 0.008 * u, 24]} />\n              <meshStandardMaterial color={i ? '#1b1b1e' : '#c0262d'} roughness={0.7} />\n            </mesh>\n            <mesh position={[0.1 * u, 0.003 * u, 0]} castShadow>\n              <boxGeometry args={[0.1 * u, 0.02 * u, 0.028 * u]} />\n              <meshStandardMaterial color=\"#c9a774\" roughness={0.6} />\n            </mesh>\n          </group>\n        ))}\n      {ball && (\n        <mesh position={[look.ball.x * u, top + 0.02 * u, look.ball.z * u]} castShadow>\n          <sphereGeometry args={[0.02 * u, 14, 10]} />\n          <meshStandardMaterial color=\"#f7f4ee\" roughness={0.4} />\n        </mesh>\n      )}\n    </RigidBody>\n  )\n}\n\nTableTennis.groundSitting = true\n",
      "type": "registry:component"
    }
  ]
}
