UNPKG

@playcanvas/blocks

Version:

High level abstract 3D primitives for React

132 lines 6.22 kB
"use client"; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { useEffect, useRef, useState } from "react"; import { Entity } from "@playcanvas/react"; import { Camera, Script } from "@playcanvas/react/components"; import { useTimeline, useAssetViewer } from "./splat-viewer-context"; import { Vec3 } from "playcanvas"; import { AnimCamera, createRotationTrack } from "./utils/animation"; // assumed import { computeStartingPose, Pose } from "./utils/pose"; import { useApp, useParent } from "@playcanvas/react/hooks"; import { StaticPostEffects } from "./utils/effects"; import { paris, neutral, noir } from "./utils/style"; // @ts-expect-error There is no type definition for the camera-controls script import { CameraControls } from "playcanvas/scripts/esm/camera-controls.mjs"; import { useRenderOnCameraChange } from "./hooks/use-render-on-camera-change"; const variants = new Map([ ['paris', paris], ['neutral', neutral], ['noir', noir] ]); function CameraController({ focus = [0, 0, 0], ...props }) { const entity = useParent(); useEffect(() => { /** * FIXME: * `cameraControls` name will be manged in many bundlers. This needs to be updated when * PlayCanvas engine > v2.7.5 is released. See https://github.com/playcanvas/engine/pull/7593 */ // @ts-expect-error CameraControls is not defined in the script const controls = (entity.script?.cameraControls || entity.script?._CameraControls); if (controls) { controls.focus(new Vec3().fromArray(focus), null, false); } }, [...focus]); return (_jsx(_Fragment, { children: _jsx(Script, { script: CameraControls, rotateSpeed: 0.5, rotateDamping: 0.985, ...props }) })); } export function SmartCamera({ fov = 30, animationTrack, variant = "neutral" }) { const entityRef = useRef(null); const { subscribe, isPlaying } = useTimeline(); const { mode } = useAssetViewer(); const timeoutRef = useRef(0); const [shouldUseRenderOnCameraChange, setShouldUseRenderOnCameraChange] = useState(false); // const [mode] = useState<"interactive" | "transition" | "animation">(isPlaying ? "animation" : "interactive"); const app = useApp(); useEffect(() => { timeoutRef.current = setTimeout(() => { setShouldUseRenderOnCameraChange(true); }, 200); return () => clearTimeout(timeoutRef.current); }, []); useRenderOnCameraChange(shouldUseRenderOnCameraChange ? entityRef.current : null); const [pose, setPose] = useState({ position: [2, 1, 2], target: [0, 0, 0] }); const [animation, setAnimation] = useState(animationTrack ? AnimCamera.fromTrack(animationTrack) : null); useEffect(() => { const gsplat = app.root.findComponent('gsplat'); if (!gsplat) { throw new Error("GSplat not found"); } const initialPose = computeStartingPose(gsplat, fov); setPose(initialPose); if (!animation) { const actualPose = new Pose().fromLookAt(new Vec3().fromArray(initialPose.position), new Vec3().fromArray(initialPose.target)); const track = createRotationTrack(actualPose); setAnimation(track); } }, [app]); useEffect(() => { app.renderNextFrame = true; }, [variant]); // Listen to timeline changes useEffect(() => { if (!animation || !isPlaying) return; const pose = new Pose(); // const rot = new Quat(); const unsub = subscribe((t) => { animation.cursor.value = t * animation.frameRate; animation.update(); animation.getPose(pose); // if (mode === "animation") { entityRef.current?.setPosition(animation.position); entityRef.current?.setRotation(pose.rotation); // } else if (mode === "transition") { // // blend from previous to this // const { pos: fromPos, rot: fromRot } = transitionStartRef.current!; // const currentPos = new Vec3().lerp(fromPos, pose, 0.1); // const currentRot = new Quat().slerp(fromRot, rot, 0.1); // entityRef.current?.setPosition(currentPos); // entityRef.current?.setRotation(currentRot); // if ( // currentPos.distance(pose) < 0.01 && // Quat.dot(currentRot, rot) > 0.999 // ) { // setMode("animation"); // } // } }); return unsub; }, [mode, animation, isPlaying]); // When timeline starts playing, begin transition // useEffect(() => { // if (isPlaying && mode === "interactive" && animation) { // transitionStartRef.current = { // pos: entityRef.current?.getPosition().clone(), // rot: entityRef.current?.getRotation().clone(), // }; // setMode("transition"); // } // }, [isPlaying]); // if (!animationTrack || mode === "interactive") { // return ( // <Entity name="camera" ref={entityRef} position={pose.position}> // <Camera fov={fov} clearColor="#f3e8ff" /> // <CameraController // focus={pose.target} // enablePan={type === "fly"} // enableFly={type === "fly"} // enableOrbit={type === "orbit"} // /> // <StaticPostEffects /> // </Entity> // ); // } // If the variant is a string, use the default variant, otherwise use the variant object passed in const style = typeof variant === 'string' ? variants.get(variant) ?? neutral : variant; // const toneMapping = style.rendering.toneMapping ?? 4; return (_jsxs(Entity, { name: "camera", ref: entityRef, position: pose.position, children: [_jsx(Camera, { fov: fov, clearColor: "#f3e8ff" }), _jsx(CameraController, { focus: pose.target, enablePan: mode === "fly", enableFly: true, enableOrbit: mode === "orbit", enabled: !isPlaying }), variant !== 'none' && _jsx(StaticPostEffects, { ...style })] })); } //# sourceMappingURL=smart-camera.js.map