{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"webgl-scene","type":"registry:component","title":"Webgl Scene","description":"A viewport with its own scene and camera on the shared canvas.","meta":{"pro":false},"docs":"Usage: https://atelier-ui.com/r/webgl-scene.md\nDocs: https://atelier-ui.com/docs/foundation/primitive/webgl-scene","dependencies":["three","@types/three","@react-three/fiber","@react-three/drei"],"registryDependencies":["https://atelier-ui.com/r/webgl-portal.json","https://atelier-ui.com/r/agent-rules.json"],"files":[{"path":"registry/webgl-scene/webgl-scene.tsx","type":"registry:component","target":"components/webgl-scene/webgl-scene.tsx","content":"\"use client\"\n\nimport { shaderMaterial, useFBO } from \"@react-three/drei\"\nimport { createPortal, extend, type ThreeElement, useFrame, useThree } from \"@react-three/fiber\"\nimport { type ReactNode, type RefObject, useLayoutEffect, useMemo, useRef } from \"react\"\nimport { type Mesh, PerspectiveCamera, Scene, Texture } from \"three\"\nimport { webglTeleport } from \"../webgl-portal/webgl-portal\"\n\nconst DisplayMaterial = shaderMaterial(\n    { uMap: new Texture() },\n    /* glsl */ `\n        varying vec2 vUv;\n        void main() {\n            vUv = uv;\n            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n        }\n    `,\n    /* glsl */ `\n        uniform sampler2D uMap;\n        varying vec2 vUv;\n        void main() {\n            gl_FragColor = texture2D(uMap, vUv);\n        }\n    `,\n)\n\nextend({ DisplayMaterial })\n\ndeclare module \"@react-three/fiber\" {\n    interface ThreeElements {\n        displayMaterial: ThreeElement<typeof DisplayMaterial>\n    }\n}\n\nexport type WebglSceneProps = {\n    track: RefObject<HTMLElement | null>\n    children: ReactNode\n    camera?: PerspectiveCamera\n    /**\n     * - texture: children render into an FBO each frame: Global post-processing will work on it.\n     * - scissor: a scissored pass painted on top of the composed frame. lighter, but excluded from global post-processing.\n     */\n    mode?: \"texture\" | \"scissor\"\n    priority?: number\n    zIndex?: number\n    transparent?: boolean\n    /**\n     * Re-measures the DOM rect every frame so the plane follows animated parents (motion, parallax).\n     * Costs one layout read per frame, so only enable it when needed.\n     */\n    autoReflow?: boolean\n}\n\nfunction WebglScenePortal({\n    track,\n    children,\n    camera: propCamera,\n    mode = \"scissor\",\n    priority,\n    zIndex = 0,\n    transparent = true,\n    autoReflow = false,\n}: WebglSceneProps) {\n    const defaultCamera = useMemo(() => {\n        const cam = new PerspectiveCamera(75, 1, 0.1, 1000)\n        cam.position.z = 5\n        return cam\n    }, [])\n\n    const scene = useMemo(() => new Scene(), [])\n    const camera = propCamera ?? defaultCamera\n    const bounds = useRef({\n        x: 0,\n        y: 0,\n        width: 0,\n        height: 0,\n    })\n\n    const gl = useThree((s) => s.gl)\n    const size = useThree((s) => s.size)\n    const viewport = useThree((s) => s.viewport)\n    const displayMesh = useRef<Mesh>(null)\n    const fbo = useFBO(1, 1, { samples: 4 })\n\n    useLayoutEffect(() => {\n        fbo.texture.colorSpace = gl.outputColorSpace\n    }, [fbo, gl])\n\n    useLayoutEffect(() => {\n        const target = track.current\n        if (!target) return\n\n        const measure = () => {\n            const rect = target.getBoundingClientRect()\n            bounds.current.x = rect.left + window.scrollX\n            bounds.current.y = rect.top + window.scrollY\n            bounds.current.width = rect.width\n            bounds.current.height = rect.height\n        }\n\n        measure()\n        const resizeObserver = new ResizeObserver(measure)\n        resizeObserver.observe(target)\n        resizeObserver.observe(document.body)\n        return () => resizeObserver.disconnect()\n    }, [track])\n\n    const renderPriority = priority ?? (mode === \"texture\" ? 0 : 2)\n\n    useFrame(() => {\n        const transitioning = document.documentElement.hasAttribute(\"data-atelier-transitioning\")\n\n        let left: number\n        let top: number\n        let width: number\n        let height: number\n\n        if ((autoReflow || transitioning) && track.current) {\n            const rect = track.current.getBoundingClientRect()\n            left = rect.left\n            top = rect.top\n            width = rect.width\n            height = rect.height\n        } else {\n            const b = bounds.current\n            left = b.x - window.scrollX\n            top = b.y - window.scrollY\n            width = b.width\n            height = b.height\n        }\n\n        if (width === 0 || height === 0) return\n\n        const aspect = width / height\n        if (camera.aspect !== aspect) {\n            camera.aspect = aspect\n            camera.updateProjectionMatrix()\n        }\n\n        if (mode === \"scissor\") {\n            const canvasHeight = gl.domElement.clientHeight\n            const canvasWidth = gl.domElement.clientWidth\n\n            const previousAutoClear = gl.autoClear\n            gl.autoClear = false\n            gl.setViewport(left, canvasHeight - (top + height), width, height)\n            gl.setScissor(left, canvasHeight - (top + height), width, height)\n            gl.setScissorTest(true)\n            gl.clear()\n            gl.render(scene, camera)\n            gl.setScissorTest(false)\n            gl.setViewport(0, 0, canvasWidth, canvasHeight)\n            gl.setScissor(0, 0, canvasWidth, canvasHeight)\n            gl.autoClear = previousAutoClear\n            return\n        }\n\n        const pixelRatio = gl.getPixelRatio()\n        const fboWidth = Math.max(1, Math.ceil(width * pixelRatio))\n        const fboHeight = Math.max(1, Math.ceil(height * pixelRatio))\n\n        if (fbo.width !== fboWidth || fbo.height !== fboHeight) {\n            fbo.setSize(fboWidth, fboHeight)\n        }\n\n        const previousClearAlpha = gl.getClearAlpha()\n        const previousAutoClear = gl.autoClear\n\n        gl.autoClear = true\n        gl.setRenderTarget(fbo)\n        gl.setClearAlpha(transparent ? 0 : 1)\n        gl.clear()\n        gl.render(scene, camera)\n        gl.setRenderTarget(null)\n        gl.setClearAlpha(previousClearAlpha)\n        gl.autoClear = previousAutoClear\n\n        const mesh = displayMesh.current\n\n        if (mesh) {\n            const pxToWorld = viewport.height / size.height\n            mesh.position.x = (left + width / 2 - size.width / 2) * pxToWorld\n            mesh.position.y = -(top + height / 2 - size.height / 2) * pxToWorld\n            mesh.scale.x = width * pxToWorld\n            mesh.scale.y = height * pxToWorld\n        }\n    }, renderPriority)\n\n    const portal = createPortal(children, scene, {\n        camera,\n        events: {\n            compute: (event, state) => {\n                const rect = track.current?.getBoundingClientRect()\n                if (!rect) return\n                state.pointer.set(\n                    ((event.clientX - rect.left) / rect.width) * 2 - 1,\n                    -(((event.clientY - rect.top) / rect.height) * 2 - 1),\n                )\n                state.raycaster.setFromCamera(state.pointer, camera)\n            },\n        },\n    })\n\n    return (\n        <>\n            {portal}\n            {mode === \"texture\" && (\n                <mesh ref={displayMesh} renderOrder={zIndex}>\n                    <planeGeometry args={[1, 1]} />\n                    <displayMaterial\n                        key={DisplayMaterial.key}\n                        uMap={fbo.texture}\n                        transparent\n                        premultipliedAlpha\n                        depthTest={false}\n                        depthWrite={false}\n                    />\n                </mesh>\n            )}\n        </>\n    )\n}\n\nexport function WebglScene(props: WebglSceneProps) {\n    return (\n        <webglTeleport.In>\n            <WebglScenePortal {...props} />\n        </webglTeleport.In>\n    )\n}\n"}]}