{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "wireframe-globe",
  "title": "Wireframe Globe",
  "description": "A wireframe world with places marked on it, and the same places written out underneath.",
  "dependencies": [
    "three@^0.185.1"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.tinkererslabs.com/r/render-surface.json"
  ],
  "files": [
    {
      "path": "registry/default/wireframe-globe/wireframe-globe.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as THREE from \"three\"\nimport {\n  RenderSurface,\n  useThemeColors,\n  type SurfaceColor,\n} from \"@/registry/default/render-surface/render-surface\"\nimport { cn } from \"@/lib/utils\"\n\nexport type GlobeMarker = {\n  id?: string\n  lat: number\n  lng: number\n  label: string\n}\n\nexport type GlobeArc = {\n  from: { lat: number; lng: number }\n  to: { lat: number; lng: number }\n}\n\nexport type WireframeGlobeProps = React.HTMLAttributes<HTMLDivElement> & {\n  markers?: readonly GlobeMarker[]\n  arcs?: readonly GlobeArc[]\n  /** Colour of the sphere's lines. */\n  color?: string\n  /** Colour of the markers and the arcs. */\n  accent?: string\n  /** Turns per second. */\n  speed?: number\n  /** Lets the pointer spin it. */\n  interactive?: boolean\n  paused?: boolean\n  surfaceClassName?: string\n}\n\ntype Scene = {\n  renderer: THREE.WebGLRenderer\n  scene: THREE.Scene\n  camera: THREE.PerspectiveCamera\n  world: THREE.Group\n  wireMaterial: THREE.LineBasicMaterial\n  accentMaterial: THREE.MeshBasicMaterial\n  arcMaterial: THREE.LineBasicMaterial\n  disposables: { dispose: () => void }[]\n  width: number\n  height: number\n  dpr: number\n}\n\nconst LATITUDES = 7\nconst MERIDIANS = 12\n\nfunction onSphere(lat: number, lng: number, radius: number) {\n  const phi = ((90 - lat) * Math.PI) / 180\n  const theta = ((lng + 180) * Math.PI) / 180\n\n  return new THREE.Vector3(\n    -radius * Math.sin(phi) * Math.cos(theta),\n    radius * Math.cos(phi),\n    radius * Math.sin(phi) * Math.sin(theta)\n  )\n}\n\nfunction toThree([r, g, b]: SurfaceColor) {\n  return new THREE.Color(r, g, b)\n}\n\n/**\n * A wireframe world with points on it. The list of places is also rendered as\n * text for anything that does not read a canvas, so the globe illustrates the\n * data rather than being the only copy of it.\n */\nexport function WireframeGlobe({\n  markers = [],\n  arcs = [],\n  color = \"--border\",\n  accent = \"--primary\",\n  speed = 0.06,\n  interactive = true,\n  paused,\n  className,\n  surfaceClassName,\n  children,\n  ...rootProps\n}: WireframeGlobeProps) {\n  const rootRef = React.useRef<HTMLDivElement>(null)\n  const drag = React.useRef({ spin: 0, velocity: 0, last: 0, active: false })\n\n  const tokens = React.useMemo(\n    () => [color, accent].filter((entry) => entry.startsWith(\"--\")),\n    [accent, color]\n  )\n\n  const resolved = useThemeColors(rootRef, tokens)\n  const lineColor = color.startsWith(\"--\") ? resolved[color] : undefined\n  const accentColor = accent.startsWith(\"--\") ? resolved[accent] : undefined\n  const ready = lineColor !== undefined && accentColor !== undefined\n\n  const setup = React.useCallback(\n    ({\n      canvas,\n      size,\n    }: {\n      canvas: HTMLCanvasElement\n      size: { width: number; height: number; dpr: number }\n    }): Scene => {\n      const renderer = new THREE.WebGLRenderer({\n        canvas,\n        alpha: true,\n        antialias: true,\n      })\n\n      renderer.setPixelRatio(size.dpr)\n      renderer.setSize(size.width, size.height, false)\n\n      const scene = new THREE.Scene()\n      const camera = new THREE.PerspectiveCamera(\n        38,\n        size.width / Math.max(size.height, 1),\n        0.1,\n        100\n      )\n      camera.position.set(0, 0, 3.6)\n\n      const world = new THREE.Group()\n      scene.add(world)\n\n      const disposables: { dispose: () => void }[] = []\n\n      // Latitude and longitude rings rather than a wireframe of the sphere's\n      // triangles, which reads as a mesh and crowds into a knot at the poles.\n      const wireMaterial = new THREE.LineBasicMaterial({\n        transparent: true,\n        opacity: 0.55,\n      })\n      disposables.push(wireMaterial)\n\n      const ring = (points: THREE.Vector3[], closed: boolean) => {\n        const geometry = new THREE.BufferGeometry().setFromPoints(points)\n        world.add(\n          closed\n            ? new THREE.LineLoop(geometry, wireMaterial)\n            : new THREE.Line(geometry, wireMaterial)\n        )\n        disposables.push(geometry)\n      }\n\n      for (let index = 1; index <= LATITUDES; index += 1) {\n        const lat = -90 + (index * 180) / (LATITUDES + 1)\n        const points: THREE.Vector3[] = []\n        for (let step = 0; step <= 64; step += 1) {\n          points.push(onSphere(lat, (step / 64) * 360 - 180, 1))\n        }\n        ring(points, true)\n      }\n\n      for (let index = 0; index < MERIDIANS; index += 1) {\n        const lng = (index * 360) / MERIDIANS - 180\n        const points: THREE.Vector3[] = []\n        for (let step = 0; step <= 48; step += 1) {\n          points.push(onSphere(-90 + (step / 48) * 180, lng, 1))\n        }\n        ring(points, false)\n      }\n\n      const accentMaterial = new THREE.MeshBasicMaterial()\n      const markerGeometry = new THREE.SphereGeometry(0.028, 12, 12)\n      disposables.push(accentMaterial, markerGeometry)\n\n      for (const marker of markers) {\n        const dot = new THREE.Mesh(markerGeometry, accentMaterial)\n        dot.position.copy(onSphere(marker.lat, marker.lng, 1.01))\n        world.add(dot)\n      }\n\n      const arcMaterial = new THREE.LineBasicMaterial({\n        transparent: true,\n        opacity: 0.75,\n      })\n      disposables.push(arcMaterial)\n\n      for (const arc of arcs) {\n        const start = onSphere(arc.from.lat, arc.from.lng, 1.01)\n        const end = onSphere(arc.to.lat, arc.to.lng, 1.01)\n        const lift = 1.15 + start.distanceTo(end) * 0.22\n        const middle = start.clone().add(end).normalize().multiplyScalar(lift)\n\n        const curve = new THREE.QuadraticBezierCurve3(start, middle, end)\n        const geometry = new THREE.BufferGeometry().setFromPoints(\n          curve.getPoints(48)\n        )\n\n        world.add(new THREE.Line(geometry, arcMaterial))\n        disposables.push(geometry)\n      }\n\n      world.rotation.x = 0.32\n\n      return {\n        renderer,\n        scene,\n        camera,\n        world,\n        wireMaterial,\n        accentMaterial,\n        arcMaterial,\n        disposables,\n        width: size.width,\n        height: size.height,\n        dpr: size.dpr,\n      }\n    },\n    [arcs, markers]\n  )\n\n  const draw = React.useCallback(\n    ({\n      size,\n      state,\n      delta,\n    }: {\n      size: { width: number; height: number; dpr: number }\n      state: Scene\n      delta: number\n    }) => {\n      if (\n        size.width !== state.width ||\n        size.height !== state.height ||\n        size.dpr !== state.dpr\n      ) {\n        state.width = size.width\n        state.height = size.height\n        state.dpr = size.dpr\n        state.renderer.setPixelRatio(size.dpr)\n        state.renderer.setSize(size.width, size.height, false)\n        state.camera.aspect = size.width / Math.max(size.height, 1)\n        state.camera.updateProjectionMatrix()\n      }\n\n      if (lineColor) state.wireMaterial.color.copy(toThree(lineColor))\n      if (accentColor) {\n        state.accentMaterial.color.copy(toThree(accentColor))\n        state.arcMaterial.color.copy(toThree(accentColor))\n      }\n\n      if (!drag.current.active) {\n        drag.current.spin += delta * speed * Math.PI * 2\n        drag.current.spin += drag.current.velocity * delta\n        drag.current.velocity *= 1 - Math.min(1, delta * 2.4)\n      }\n\n      state.world.rotation.y = drag.current.spin\n      state.renderer.render(state.scene, state.camera)\n    },\n    [accentColor, lineColor, speed]\n  )\n\n  const teardown = React.useCallback((state: Scene) => {\n    for (const item of state.disposables) item.dispose()\n    state.renderer.dispose()\n  }, [])\n\n  return (\n    <div\n      ref={rootRef}\n      data-slot=\"wireframe-globe\"\n      className={cn(\"relative isolate\", className)}\n      onPointerDown={(event) => {\n        if (!interactive) return\n        drag.current.active = true\n        drag.current.last = event.clientX\n        event.currentTarget.setPointerCapture(event.pointerId)\n      }}\n      onPointerMove={(event) => {\n        if (!interactive || !drag.current.active) return\n        const delta = (event.clientX - drag.current.last) * 0.01\n        drag.current.last = event.clientX\n        drag.current.spin += delta\n        drag.current.velocity = delta * 24\n      }}\n      onPointerUp={() => {\n        drag.current.active = false\n      }}\n      onPointerCancel={() => {\n        drag.current.active = false\n      }}\n      {...rootProps}\n    >\n      {ready && (\n        <RenderSurface<Scene, \"none\">\n          contextType=\"none\"\n          setup={setup}\n          draw={draw}\n          teardown={teardown}\n          rebuildOnResize={false}\n          paused={paused}\n          className={cn(\"absolute inset-0 -z-10 touch-none\", surfaceClassName)}\n        />\n      )}\n\n      {markers.length > 0 && (\n        <ul className=\"sr-only\">\n          {markers.map((marker) => (\n            <li key={marker.id ?? `${marker.lat},${marker.lng}`}>\n              {marker.label}\n            </li>\n          ))}\n        </ul>\n      )}\n\n      {children}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "scene",
    "3d",
    "globe",
    "data"
  ],
  "type": "registry:ui"
}