From 1f17676f9052a9ee0fd8cd1c8925cdf63baf811b Mon Sep 17 00:00:00 2001 From: Ivan <45982459+ivntsng@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:28:27 -0800 Subject: [PATCH] Added full-screen functionality (#549) --- .../src/components/files/URDFRenderer.tsx | 150 ++++++++++++++++-- 1 file changed, 134 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/files/URDFRenderer.tsx b/frontend/src/components/files/URDFRenderer.tsx index 24af0df7..19000639 100644 --- a/frontend/src/components/files/URDFRenderer.tsx +++ b/frontend/src/components/files/URDFRenderer.tsx @@ -1,5 +1,12 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { FaChevronLeft, FaChevronRight, FaPlay, FaUndo } from "react-icons/fa"; +import { + FaChevronLeft, + FaChevronRight, + FaCompress, + FaExpand, + FaPlay, + FaUndo, +} from "react-icons/fa"; import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; @@ -47,6 +54,11 @@ const URDFRenderer = ({ const [isInStartPosition, setIsInStartPosition] = useState(true); const [urdfInfo, setUrdfInfo] = useState(null); const [orientation, setOrientation] = useState("Z-up"); + const [isFullScreen, setIsFullScreen] = useState(false); + const parentRef = useRef(null); + const [forceRerender, setForceRerender] = useState(0); + const jointPositionsRef = useRef<{ name: string; value: number }[]>([]); + const rendererRef = useRef(null); const applyTheme = useCallback((theme: VisualizationTheme) => { if (!sceneRef.current) return; @@ -91,6 +103,27 @@ const URDFRenderer = ({ } }, []); + useEffect(() => { + const handleFullScreenChange = () => { + const isNowFullScreen = !!document.fullscreenElement; + + if (isNowFullScreen) { + setIsFullScreen(true); + } else { + jointPositionsRef.current = jointControls.map((joint) => ({ + name: joint.name, + value: joint.value, + })); + setIsFullScreen(false); + setForceRerender((prev) => prev + 1); + } + }; + + document.addEventListener("fullscreenchange", handleFullScreenChange); + return () => + document.removeEventListener("fullscreenchange", handleFullScreenChange); + }, [jointControls]); + useEffect(() => { if (!containerRef.current) return; @@ -183,8 +216,15 @@ const URDFRenderer = ({ const joint = child as URDFJoint; const min = Number(joint.limit.lower); const max = Number(joint.limit.upper); - // Initialize to 0 if it's within the joint limits, otherwise use midpoint - const initialValue = min <= 0 && max >= 0 ? 0 : (min + max) / 2; + const storedPosition = jointPositionsRef.current.find( + (pos) => pos.name === joint.name, + ); + const initialValue = storedPosition + ? storedPosition.value + : min <= 0 && max >= 0 + ? 0 + : (min + max) / 2; + joints.push({ name: joint.name, min: min, @@ -194,6 +234,7 @@ const URDFRenderer = ({ joint.setJointValue(initialValue); } }); + // Sort joints alphabetically by name joints.sort((a, b) => a.name.localeCompare(b.name)); setJointControls(joints); @@ -260,7 +301,7 @@ const URDFRenderer = ({ window.removeEventListener("resize", handleResize); updateOrientation("Z-up"); // Reset orientation on unmount }; - }, [urdfContent, files, orientation]); + }, [urdfContent, files, orientation, forceRerender]); const handleJointChange = (index: number, value: number) => { setJointControls((prevControls) => { @@ -369,23 +410,100 @@ const URDFRenderer = ({ } }; - return ( -
-
+ const resetViewerState = useCallback(() => { + if ( + !containerRef.current || + !robotRef.current || + !sceneRef.current || + !rendererRef.current + ) + return; + + const renderer = rendererRef.current; + renderer.setSize( + containerRef.current.clientWidth, + containerRef.current.clientHeight, + ); + + const camera = new THREE.PerspectiveCamera( + 50, + containerRef.current.clientWidth / containerRef.current.clientHeight, + 0.1, + 1000, + ); + const distance = 10; + camera.position.set(0, distance / 2, -distance); + camera.lookAt(0, 0, 0); + + const robot = robotRef.current; + const box = new THREE.Box3().setFromObject(robot); + const center = box.getCenter(new THREE.Vector3()); + const size = box.getSize(new THREE.Vector3()); + const maxDim = Math.max(size.x, size.y, size.z); + const scale = 5 / maxDim; + robot.scale.setScalar(scale); + robot.position.sub(center.multiplyScalar(scale)); - + renderer.render(sceneRef.current, camera); + }, []); + + useEffect(() => { + const handleFullScreenChange = () => { + const isNowFullScreen = !!document.fullscreenElement; + setIsFullScreen(isNowFullScreen); + + if (!isNowFullScreen) { + setTimeout(() => { + resetViewerState(); + }, 100); + } + }; + + document.addEventListener("fullscreenchange", handleFullScreenChange); + return () => + document.removeEventListener("fullscreenchange", handleFullScreenChange); + }, [resetViewerState]); + + const toggleFullScreen = useCallback(() => { + if (!parentRef.current) return; + + if (!document.fullscreenElement) { + parentRef.current.requestFullscreen(); + setIsFullScreen(true); + } else { + document.exitFullscreen(); + setIsFullScreen(false); + } + }, []); + + return ( +
+
+
+ + +
+
{useControls && ( <>
@@ -456,7 +574,7 @@ const URDFRenderer = ({ {!showControls && (