Make it immersive

This commit is contained in:
2025-03-14 16:32:22 -06:00
parent 22cd57c55c
commit 676bbfe9a8

View File

@@ -1,8 +1,9 @@
"use client"; "use client";
import type { OrbitControls as OrbitControlsImpl } from 'three-stdlib'; import { OrbitControls as OrbitControlsImpl } from 'three-stdlib';
import { Canvas } from "@react-three/fiber";
import { useGLTF, useAnimations, Environment, Loader, OrbitControls, Bounds } from "@react-three/drei"; import { useGLTF, useAnimations, Environment, Loader, OrbitControls, Bounds } from "@react-three/drei";
import { Canvas } from "@react-three/fiber";
import { createXRStore, XR } from "@react-three/xr";
import { Suspense, useEffect, useRef, useState } from "react"; import { Suspense, useEffect, useRef, useState } from "react";
import { RotateCw, Play, Pause, Film, Shrink, Expand } from "lucide-react"; import { RotateCw, Play, Pause, Film, Shrink, Expand } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -17,10 +18,10 @@ import { useNavigate } from 'react-router';
// Model component // Model component
function Model({ url, onAnimationsLoaded, onNodesLoaded }: { function Model({ url, onAnimationsLoaded, isAR }: {
url: string; url: string;
onAnimationsLoaded: (animations: string[], actions: Record<string, any>) => void; onAnimationsLoaded: (animations: string[], actions: Record<string, any>) => void;
onNodesLoaded: (nodes: string[], nodePositions: Record<string, [number, number, number]>) => void; isAR: boolean;
}) { }) {
const { scene, animations } = useGLTF(url); const { scene, animations } = useGLTF(url);
const { actions } = useAnimations(animations, scene); const { actions } = useAnimations(animations, scene);
@@ -30,16 +31,17 @@ function Model({ url, onAnimationsLoaded, onNodesLoaded }: {
onAnimationsLoaded(animations.map(anim => anim.name), actions); onAnimationsLoaded(animations.map(anim => anim.name), actions);
} }
if (scene.children) { if (isAR) {
const nodeList = scene.children.map(node => node.name); // 100 times smaller
const nodePositions = scene.children.reduce((acc, node) => { scene.scale.set(0.01, 0.01, 0.01);
acc[node.name] = [node.position.x, node.position.y, node.position.z]; scene.position.set(0, 0, 0);
return acc;
}, {} as Record<string, [number, number, number]>);
onNodesLoaded(nodeList, nodePositions);
} }
}, [actions, animations, onAnimationsLoaded, onNodesLoaded]); else {
scene.scale.set(1, 1, 1);
scene.position.set(0, 0, 0);
}
}, [actions, animations, onAnimationsLoaded, isAR]);
return ( return (
<Bounds fit margin={.9}> <Bounds fit margin={.9}>
@@ -50,20 +52,18 @@ function Model({ url, onAnimationsLoaded, onNodesLoaded }: {
); );
} }
const store = createXRStore();
export default function Previewer({ modelUrl }: { modelUrl: string }) { export default function Previewer({ modelUrl }: { modelUrl: string }) {
const controlsRef = useRef<OrbitControlsImpl>(null); const controlsRef = useRef<OrbitControlsImpl>(null);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [animations, setAnimations] = useState<string[]>([]); const [animations, setAnimations] = useState<string[]>([]);
const [actions, setActions] = useState<Record<string, any>>({}); const [actions, setActions] = useState<Record<string, any>>({});
const [currentAnimation, setCurrentAnimation] = useState<string | null>(null); const [currentAnimation, setCurrentAnimation] = useState<string | null>(null);
const [isAR, setIsAR] = useState(false);
const [_, setNodes] = useState<string[]>([]);
const [nodePositions, setNodePositions] = useState<Record<string, [number, number, number]>>({});
const [isFullscreen, setIsFullscreen] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
nodePositions;
const playAnimation = (name: string) => { const playAnimation = (name: string) => {
if (actions[name]) { if (actions[name]) {
@@ -79,24 +79,6 @@ export default function Previewer({ modelUrl }: { modelUrl: string }) {
setCurrentAnimation(null); setCurrentAnimation(null);
} }
}; };
// Handle Fullscreen Mode
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
containerRef.current?.requestFullscreen().then(() => {
setIsFullscreen(true);
}).catch(err => {
console.error("Error entering fullscreen:", err);
});
} else {
document.exitFullscreen().then(() => {
setIsFullscreen(false);
}).catch(err => {
console.error("Error exiting fullscreen:", err);
});
}
};
return ( return (
<div ref={containerRef} className="relative w-full grow bg-white/10 shadow-xl shadow-gray-300/25 rounded-lg p-6 backdrop-blur-lg h-100"> <div ref={containerRef} className="relative w-full grow bg-white/10 shadow-xl shadow-gray-300/25 rounded-lg p-6 backdrop-blur-lg h-100">
<div className="z-10 absolute top-4 w-100 flex wrap gap-5"> <div className="z-10 absolute top-4 w-100 flex wrap gap-5">
@@ -144,28 +126,33 @@ export default function Previewer({ modelUrl }: { modelUrl: string }) {
</Button> </Button>
<Button <Button
className="bg-gradient-to-r from-gray-700 to-gray-900 w-40 text-white px-5 py-3 rounded-lg shadow-lg transition-all duration-300 hover:from-gray-600 hover:to-gray-800 active:scale-95 flex items-center gap-2 justify-center" className="bg-gradient-to-r from-gray-700 to-gray-900 w-40 text-white px-5 py-3 rounded-lg shadow-lg transition-all duration-300 hover:from-gray-600 hover:to-gray-800 active:scale-95 flex items-center gap-2 justify-center"
onClick={toggleFullscreen} > onClick={() => {
{isFullscreen ? <Shrink className="w-5 h-5" /> : <Expand className="w-5 h-5" />} store.enterAR();
{isFullscreen ? "Salir" : "Pantalla Completa"} setIsAR(!isAR);
}}
>
{isAR ? <Shrink className="w-5 h-5" /> : <Expand className="w-5 h-5" />}
{isAR ? "Salir de AR" : "Ver en AR"}
</Button> </Button>
</div> </div>
<div className="w-full h-full"> <div className="w-full h-full">
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<Canvas className="w-full h-full"> <Canvas className="w-full h-full">
<Environment preset="city" /> <XR store={store}>
<OrbitControls ref={controlsRef} minPolarAngle={Math.PI / 4} maxPolarAngle={Math.PI / 1.5} /> <Environment preset="city" />
<Model <OrbitControls ref={controlsRef} minPolarAngle={Math.PI / 4} maxPolarAngle={Math.PI / 1.5} />
url={modelUrl} <Model
url={modelUrl}
onAnimationsLoaded={(animNames, actionMap) => { onAnimationsLoaded={(animNames, actionMap) => {
setAnimations(animNames); setAnimations(animNames);
setActions(actionMap); setActions(actionMap);
}} }}
onNodesLoaded={(nodeList, nodePos) => {
setNodes(nodeList); isAR={isAR}
setNodePositions(nodePos); />
}} </XR>
/>
</Canvas> </Canvas>
</Suspense> </Suspense>
</div> </div>