Make it immersive
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import type { OrbitControls as OrbitControlsImpl } from 'three-stdlib';
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { OrbitControls as OrbitControlsImpl } from 'three-stdlib';
|
||||
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 { RotateCw, Play, Pause, Film, Shrink, Expand } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -17,10 +18,10 @@ import { useNavigate } from 'react-router';
|
||||
|
||||
|
||||
// Model component
|
||||
function Model({ url, onAnimationsLoaded, onNodesLoaded }: {
|
||||
function Model({ url, onAnimationsLoaded, isAR }: {
|
||||
url: string;
|
||||
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 { actions } = useAnimations(animations, scene);
|
||||
@@ -30,16 +31,17 @@ function Model({ url, onAnimationsLoaded, onNodesLoaded }: {
|
||||
onAnimationsLoaded(animations.map(anim => anim.name), actions);
|
||||
}
|
||||
|
||||
if (scene.children) {
|
||||
const nodeList = scene.children.map(node => node.name);
|
||||
const nodePositions = scene.children.reduce((acc, node) => {
|
||||
acc[node.name] = [node.position.x, node.position.y, node.position.z];
|
||||
return acc;
|
||||
}, {} as Record<string, [number, number, number]>);
|
||||
|
||||
onNodesLoaded(nodeList, nodePositions);
|
||||
if (isAR) {
|
||||
// 100 times smaller
|
||||
scene.scale.set(0.01, 0.01, 0.01);
|
||||
scene.position.set(0, 0, 0);
|
||||
}
|
||||
}, [actions, animations, onAnimationsLoaded, onNodesLoaded]);
|
||||
else {
|
||||
scene.scale.set(1, 1, 1);
|
||||
scene.position.set(0, 0, 0);
|
||||
}
|
||||
|
||||
}, [actions, animations, onAnimationsLoaded, isAR]);
|
||||
|
||||
return (
|
||||
<Bounds fit margin={.9}>
|
||||
@@ -50,20 +52,18 @@ function Model({ url, onAnimationsLoaded, onNodesLoaded }: {
|
||||
);
|
||||
}
|
||||
|
||||
const store = createXRStore();
|
||||
export default function Previewer({ modelUrl }: { modelUrl: string }) {
|
||||
const controlsRef = useRef<OrbitControlsImpl>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [animations, setAnimations] = useState<string[]>([]);
|
||||
const [actions, setActions] = useState<Record<string, any>>({});
|
||||
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();
|
||||
|
||||
nodePositions;
|
||||
|
||||
const playAnimation = (name: string) => {
|
||||
if (actions[name]) {
|
||||
@@ -79,24 +79,6 @@ export default function Previewer({ modelUrl }: { modelUrl: string }) {
|
||||
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 (
|
||||
<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">
|
||||
@@ -144,14 +126,20 @@ export default function Previewer({ modelUrl }: { modelUrl: string }) {
|
||||
</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"
|
||||
onClick={toggleFullscreen} >
|
||||
{isFullscreen ? <Shrink className="w-5 h-5" /> : <Expand className="w-5 h-5" />}
|
||||
{isFullscreen ? "Salir" : "Pantalla Completa"}
|
||||
onClick={() => {
|
||||
store.enterAR();
|
||||
setIsAR(!isAR);
|
||||
}}
|
||||
>
|
||||
{isAR ? <Shrink className="w-5 h-5" /> : <Expand className="w-5 h-5" />}
|
||||
{isAR ? "Salir de AR" : "Ver en AR"}
|
||||
|
||||
</Button>
|
||||
</div>
|
||||
<div className="w-full h-full">
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Canvas className="w-full h-full">
|
||||
<XR store={store}>
|
||||
<Environment preset="city" />
|
||||
<OrbitControls ref={controlsRef} minPolarAngle={Math.PI / 4} maxPolarAngle={Math.PI / 1.5} />
|
||||
<Model
|
||||
@@ -161,11 +149,10 @@ export default function Previewer({ modelUrl }: { modelUrl: string }) {
|
||||
setAnimations(animNames);
|
||||
setActions(actionMap);
|
||||
}}
|
||||
onNodesLoaded={(nodeList, nodePos) => {
|
||||
setNodes(nodeList);
|
||||
setNodePositions(nodePos);
|
||||
}}
|
||||
|
||||
isAR={isAR}
|
||||
/>
|
||||
</XR>
|
||||
</Canvas>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user