Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import { setBroadcastSource } from '@/lib/webrtc/broadcastSource';
|
||||
|
||||
interface XRBroadcastMirrorProps {
|
||||
enabled: boolean;
|
||||
width?: number;
|
||||
height?: number;
|
||||
/** Solid background color used in place of real passthrough (privacy + clarity). */
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the current scene to an OFFSCREEN opaque canvas every frame so a
|
||||
* MediaStream can capture it. The main XR canvas uses alpha:true for
|
||||
* passthrough — viewers receiving captureStream() of that would see only
|
||||
* the 3D content over transparency. This mirror gives them a usable picture.
|
||||
*/
|
||||
export function XRBroadcastMirror({
|
||||
enabled,
|
||||
width = 1280,
|
||||
height = 720,
|
||||
backgroundColor = '#1a1a1a',
|
||||
}: XRBroadcastMirrorProps) {
|
||||
const { gl, scene } = useThree();
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
|
||||
const mirrorCamRef = useRef<THREE.PerspectiveCamera | null>(null);
|
||||
|
||||
// Create / dispose the offscreen renderer
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvasRef.current = canvas;
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
canvas,
|
||||
alpha: false,
|
||||
antialias: true,
|
||||
powerPreference: 'high-performance',
|
||||
preserveDrawingBuffer: false,
|
||||
});
|
||||
renderer.setPixelRatio(1);
|
||||
renderer.setSize(width, height, false);
|
||||
renderer.setClearColor(new THREE.Color(backgroundColor), 1);
|
||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||
rendererRef.current = renderer;
|
||||
|
||||
mirrorCamRef.current = new THREE.PerspectiveCamera(70, width / height, 0.01, 100);
|
||||
|
||||
setBroadcastSource(canvas);
|
||||
|
||||
return () => {
|
||||
setBroadcastSource(null);
|
||||
renderer.dispose();
|
||||
rendererRef.current = null;
|
||||
canvasRef.current = null;
|
||||
mirrorCamRef.current = null;
|
||||
};
|
||||
}, [enabled, width, height, backgroundColor]);
|
||||
|
||||
// Per-frame mirror render (throttled to ~24 fps to save GPU)
|
||||
const lastTime = useRef(0);
|
||||
useFrame(() => {
|
||||
if (!enabled) return;
|
||||
const renderer = rendererRef.current;
|
||||
const mirrorCam = mirrorCamRef.current;
|
||||
if (!renderer || !mirrorCam) return;
|
||||
|
||||
const now = performance.now();
|
||||
if (now - lastTime.current < 42) return; // ~24 fps cap
|
||||
lastTime.current = now;
|
||||
|
||||
// Pick the best available camera: XR camera if in session, else main camera
|
||||
let srcCam: THREE.Camera | null = null;
|
||||
const xrCam = (gl.xr as unknown as { isPresenting?: boolean; getCamera?: () => THREE.ArrayCamera }).getCamera?.();
|
||||
const isPresenting = (gl.xr as unknown as { isPresenting?: boolean }).isPresenting;
|
||||
if (isPresenting && xrCam && (xrCam as THREE.ArrayCamera).cameras?.length) {
|
||||
srcCam = (xrCam as THREE.ArrayCamera).cameras[0]; // left eye
|
||||
}
|
||||
|
||||
if (srcCam instanceof THREE.PerspectiveCamera) {
|
||||
mirrorCam.fov = srcCam.fov;
|
||||
mirrorCam.near = srcCam.near;
|
||||
mirrorCam.far = srcCam.far;
|
||||
}
|
||||
if (srcCam) {
|
||||
srcCam.updateMatrixWorld(true);
|
||||
mirrorCam.position.setFromMatrixPosition(srcCam.matrixWorld);
|
||||
mirrorCam.quaternion.setFromRotationMatrix(srcCam.matrixWorld);
|
||||
} else {
|
||||
// Fallback: use the main R3F camera (desktop preview)
|
||||
const main = (gl as unknown as { __mainCam?: THREE.Camera }).__mainCam;
|
||||
if (main) {
|
||||
main.updateMatrixWorld(true);
|
||||
mirrorCam.position.setFromMatrixPosition(main.matrixWorld);
|
||||
mirrorCam.quaternion.setFromRotationMatrix(main.matrixWorld);
|
||||
if (main instanceof THREE.PerspectiveCamera) {
|
||||
mirrorCam.fov = main.fov;
|
||||
mirrorCam.near = main.near;
|
||||
mirrorCam.far = main.far;
|
||||
}
|
||||
}
|
||||
}
|
||||
mirrorCam.aspect = width / height;
|
||||
mirrorCam.updateProjectionMatrix();
|
||||
|
||||
try {
|
||||
renderer.render(scene, mirrorCam);
|
||||
} catch (e) {
|
||||
console.warn('[XRBroadcastMirror] render failed', e);
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user