Primitivas
This commit is contained in:
@@ -2,25 +2,23 @@
|
||||
|
||||
import type { OrbitControls as OrbitControlsImpl } from 'three-stdlib';
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { useGLTF, useAnimations, Environment, Loader, OrbitControls, Html } from "@react-three/drei";
|
||||
import { useGLTF, useAnimations, Environment, Loader, OrbitControls, Bounds } from "@react-three/drei";
|
||||
import { Suspense, useEffect, useRef, useState } from "react";
|
||||
import { RotateCw, Play, Pause, Film, ListTree, Shrink, Expand } from "lucide-react";
|
||||
import { Play, Pause, Film, Expand, } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
|
||||
import { createXRStore, XR, XRControllerModel, XRHandModel } from "@react-three/xr";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
|
||||
const store = createXRStore();
|
||||
|
||||
// Model component
|
||||
function Model({ url, position, rotation, scale, onAnimationsLoaded, onNodesLoaded }: {
|
||||
function Model({ url, onAnimationsLoaded, onNodesLoaded }: {
|
||||
url: string;
|
||||
position: [number, number, number];
|
||||
rotation: number;
|
||||
scale: number;
|
||||
onAnimationsLoaded: (animations: string[], actions: Record<string, any>) => void;
|
||||
onNodesLoaded: (nodes: string[], nodePositions: Record<string, [number, number, number]>) => void;
|
||||
}) {
|
||||
@@ -45,26 +43,15 @@ function Model({ url, position, rotation, scale, onAnimationsLoaded, onNodesLoad
|
||||
|
||||
return (
|
||||
<>
|
||||
<primitive object={scene} scale={scale} position={position} rotation={[0, rotation, 0]} />
|
||||
{scene.children.map((node, index) => (
|
||||
<Html key={index} position={[node.position.x, node.position.y + 0.5, node.position.z]}>
|
||||
<div className="bg-blue-500 text-white px-2 py-1 rounded-md text-xs shadow-md">
|
||||
{node.name}
|
||||
</div>
|
||||
</Html>
|
||||
))}
|
||||
<Bounds fit margin={.9}>
|
||||
<primitive object={scene} />
|
||||
</Bounds>
|
||||
</>
|
||||
);
|
||||
}
|
||||
// Preload the model
|
||||
// useGLTF.preload("/3d/motor_de_combustion/scene.gltf");
|
||||
|
||||
const InitialParams = {
|
||||
position: [0, -2, 0] as [number, number, number],
|
||||
rotation: -Math.PI / 2,
|
||||
scale: 0.01,
|
||||
};
|
||||
|
||||
// Main 3D Viewer
|
||||
export default function Previewer({ modelUrl }: { modelUrl: string }) {
|
||||
const controlsRef = useRef<OrbitControlsImpl>(null);
|
||||
@@ -73,19 +60,12 @@ export default function Previewer({ modelUrl }: { modelUrl: string }) {
|
||||
const [actions, setActions] = useState<Record<string, any>>({});
|
||||
const [currentAnimation, setCurrentAnimation] = useState<string | null>(null);
|
||||
|
||||
const [nodes, setNodes] = useState<string[]>([]);
|
||||
const [_, setNodes] = useState<string[]>([]);
|
||||
const [nodePositions, setNodePositions] = useState<Record<string, [number, number, number]>>({});
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
nodePositions;
|
||||
|
||||
|
||||
const resetView = () => {
|
||||
if (controlsRef.current) {
|
||||
controlsRef.current.reset();
|
||||
}
|
||||
};
|
||||
|
||||
const playAnimation = (name: string) => {
|
||||
if (actions[name]) {
|
||||
Object.values(actions).forEach(action => action.stop()); // Stop other animations
|
||||
@@ -101,24 +81,6 @@ export default function Previewer({ modelUrl }: { modelUrl: string }) {
|
||||
}
|
||||
};
|
||||
|
||||
// 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">
|
||||
@@ -156,7 +118,7 @@ export default function Previewer({ modelUrl }: { modelUrl: string }) {
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<Popover>
|
||||
{/* <Popover>
|
||||
<PopoverTrigger className="flex items-center gap-2 bg-gradient-to-r from-blue-100 to-blue-50 text-blue-500 px-4 py-2 rounded-lg shadow-lg transition-all duration-300 hover:from-blue-100 hover:to-blue-200">
|
||||
<ListTree />
|
||||
Partes del modelo
|
||||
@@ -176,48 +138,46 @@ export default function Previewer({ modelUrl }: { modelUrl: string }) {
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</Popover> */}
|
||||
</div>
|
||||
<div className="absolute bottom-4 right-4 z-10 flex gap-4">
|
||||
<Button
|
||||
className="bg-gradient-to-r from-red-600 to-red-800 w-40 text-white px-5 py-3 rounded-lg shadow-lg transition-all duration-300 hover:from-red-500 hover:to-red-700 active:scale-95 flex items-center gap-2 justify-center"
|
||||
onClick={resetView}
|
||||
>
|
||||
<RotateCw className="w-5 h-5" />
|
||||
Centrar cámara
|
||||
</Button>
|
||||
|
||||
{/* Botón de pantalla completa */}
|
||||
<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()}>
|
||||
<Expand className="w-5 h-5" />
|
||||
Entrar en AR
|
||||
</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={() => store.enterVR()}>
|
||||
<Expand className="w-5 h-5" />
|
||||
Entrar en VR
|
||||
</Button>
|
||||
</div>
|
||||
<div className="w-full h-full">
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Canvas camera={{ position: [0, 0, 12], fov: 20 }}>
|
||||
<Environment preset="city" />
|
||||
<OrbitControls ref={controlsRef} />
|
||||
<Model
|
||||
url={modelUrl}
|
||||
position={InitialParams.position}
|
||||
rotation={InitialParams.rotation}
|
||||
scale={InitialParams.scale}
|
||||
onAnimationsLoaded={(animNames, actionMap) => {
|
||||
setAnimations(animNames);
|
||||
setActions(actionMap);
|
||||
}}
|
||||
onNodesLoaded={(nodeList, nodePos) => {
|
||||
setNodes(nodeList);
|
||||
setNodePositions(nodePos);
|
||||
}}
|
||||
/>
|
||||
<Canvas className="w-full h-full">
|
||||
<XR store={store}>
|
||||
<Environment preset="city" />
|
||||
<OrbitControls ref={controlsRef} />
|
||||
|
||||
<Model
|
||||
url={modelUrl}
|
||||
onAnimationsLoaded={(animNames, actionMap) => {
|
||||
setAnimations(animNames);
|
||||
setActions(actionMap);
|
||||
}}
|
||||
onNodesLoaded={(nodeList, nodePos) => {
|
||||
setNodes(nodeList);
|
||||
setNodePositions(nodePos);
|
||||
}}
|
||||
/>
|
||||
|
||||
</XR>
|
||||
</Canvas>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user