@playcanvas/blocks
Version:
High level abstract 3D primitives for React
85 lines • 4.5 kB
JavaScript
"use client";
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { Application } from "@playcanvas/react";
import { useApp, useSplat } from "@playcanvas/react/hooks";
import { GSplat } from "@playcanvas/react/components";
import { Suspense, useCallback, useEffect, useRef, useState } from "react";
import { AssetViewerProvider, useAssetViewer, useTimeline } from "./splat-viewer-context";
import { TooltipProvider } from "../components/ui/tooltip";
import { cn } from "../lib/utils";
import { SmartCamera } from "./smart-camera";
import { HelpDialog } from "./help-dialog";
import { FILLMODE_NONE } from "playcanvas";
import { RESOLUTION_AUTO } from "playcanvas";
import { useSubscribe } from "./hooks/use-subscribe";
const identity = (a) => a;
function SplatComponent({ src, variant = "neutral", onAssetProgress }) {
const isObject = typeof src === 'object';
const { asset, error, subscribe } = useSplat(isObject ? "vfs://force-use-sogs-parser.json" : src, isObject ? { data: src, options: { mapUrl: identity } } : {});
const { isInteracting } = useAssetViewer();
const { isPlaying } = useTimeline();
const app = useApp();
// unload the asset when the component is unmounted
useEffect(() => {
return () => asset?.unload();
}, [asset]);
useEffect(() => {
return subscribe(({ progress }) => onAssetProgress?.(progress));
}, [subscribe, onAssetProgress]);
// Hide the cursor when the timeline is playing and the user is not interacting
useEffect(() => {
if (app.graphicsDevice.canvas) {
app.graphicsDevice.canvas.style.cursor = isPlaying && !isInteracting ? 'none' : 'grab';
}
}, [isInteracting, app, isPlaying]);
// Update cursor on mouse down/up
useEffect(() => {
const canvas = app.graphicsDevice.canvas;
if (!canvas)
return;
const onMouseDown = () => {
canvas.style.cursor = 'grabbing';
};
const onMouseUp = () => {
canvas.style.cursor = isPlaying && !isInteracting ? 'none' : 'grab';
};
canvas.addEventListener('mousedown', onMouseDown);
canvas.addEventListener('mouseup', onMouseUp);
canvas.addEventListener('mouseleave', onMouseUp);
return () => {
canvas.removeEventListener('mousedown', onMouseDown);
canvas.removeEventListener('mouseup', onMouseUp);
canvas.removeEventListener('mouseleave', onMouseUp);
};
}, [app, isPlaying, isInteracting]);
if (error)
throw new Error(error);
if (!asset)
return null;
return (_jsxs(_Fragment, { children: [
// type === 'animation' ? <AnimationCamera fov={30} track={track} /> :
// type === 'orbit' ? <InteractiveCamera fov={30} /> :
// <InteractiveCamera fov={30} type={type} />
_jsx(SmartCamera, { fov: 30, variant: variant }), _jsx(GSplat, { asset: asset })] }));
}
function PosterComponent({ poster }) {
return (_jsx("img", { src: poster, alt: "poster" }));
}
/**
* The SplatViewer is a component that displays a Gaussian Splat.
*/
export function SplatViewer({ src, variant = "neutral", poster, mode = 'orbit', defaultMode = 'orbit', onTypeChange, className, children }) {
const { subscribe, notify } = useSubscribe();
const onAssetProgress = useCallback((progress) => notify(progress), [src, notify]);
const isControlled = !mode;
const containerRef = useRef(null);
const [uncontrolledMode, setUncontrolledMode] = useState(defaultMode);
const setCameraMode = useCallback((mode) => {
if (!isControlled)
setUncontrolledMode(mode);
onTypeChange?.(mode);
}, [isControlled, onTypeChange]);
const currentMode = isControlled ? mode : uncontrolledMode;
return (_jsx("div", { ref: containerRef, className: cn("relative overflow-hidden", className), children: _jsxs(AssetViewerProvider, { targetRef: containerRef, src: src, mode: currentMode, setMode: setCameraMode, subscribe: subscribe, children: [_jsxs(Suspense, { fallback: _jsx(PosterComponent, { poster: poster }), children: [_jsx(Application, { fillMode: FILLMODE_NONE, resolutionMode: RESOLUTION_AUTO, autoRender: false, children: _jsx(SplatComponent, { src: src, onAssetProgress: onAssetProgress, variant: variant }) }), _jsx(TooltipProvider, { children: children })] }), _jsx(HelpDialog, {})] }) }));
}
//# sourceMappingURL=splat-viewer.js.map