From 336e3ddb63986b99c4014e12f348566ba9cd56ce Mon Sep 17 00:00:00 2001 From: tuyenio Date: Wed, 24 Jun 2026 16:07:17 +0700 Subject: [PATCH] =?UTF-8?q?perf:=20render=20theo=20nhu=20c=E1=BA=A7u=20+?= =?UTF-8?q?=20throttle=20cameraYaw=20(gi=E1=BA=A3m=20t=E1=BA=A3i=20khi=20?= =?UTF-8?q?=C4=91=E1=BB=A9ng=20y=C3=AAn)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app/components/VirtualTour.tsx | 38 ++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/app/components/VirtualTour.tsx b/src/app/components/VirtualTour.tsx index c6f8086..1f9a285 100644 --- a/src/app/components/VirtualTour.tsx +++ b/src/app/components/VirtualTour.tsx @@ -958,6 +958,8 @@ function TourExperience({ const transitionLockRef = useRef(false); const currentFovRef = useRef(DEFAULT_FOV); const targetFovRef = useRef(DEFAULT_FOV); + const needsRenderRef = useRef(true); + const lastYawRef = useRef(0); const lastPinchDistanceRef = useRef(null); const infoMarkerRefs = useRef>(new Map()); const [activeInfoMarker, setActiveInfoMarker] = useState(null); @@ -1428,6 +1430,11 @@ function TourExperience({ } }; + const handleControlsChange = () => { + needsRenderRef.current = true; + }; + controls.addEventListener("change", handleControlsChange); + renderer.domElement.addEventListener("wheel", handleWheel, { passive: false }); renderer.domElement.addEventListener("touchstart", handleTouchStart, { passive: false }); renderer.domElement.addEventListener("touchmove", handleTouchMove, { passive: false }); @@ -1455,8 +1462,9 @@ function TourExperience({ const animate = () => { const targetFov = targetFovRef.current; + const fovAnimating = Math.abs(currentFovRef.current - targetFov) > 0.01; - if (Math.abs(currentFovRef.current - targetFov) > 0.01) { + if (fovAnimating) { currentFovRef.current += (targetFov - currentFovRef.current) * 0.16; camera.fov = currentFovRef.current; camera.updateProjectionMatrix(); @@ -1464,20 +1472,35 @@ function TourExperience({ currentFovRef.current = targetFov; camera.fov = targetFov; camera.updateProjectionMatrix(); + needsRenderRef.current = true; } - if (!vrModeRef.current) { - controls.update(); - } + // controls.update() trả về true khi camera vừa thay đổi (kể cả damping đang lắng). + const controlsChanged = vrModeRef.current ? false : controls.update(); + const transitionActive = transitionFrameRef.current !== null; + const active = + needsRenderRef.current || + fovAnimating || + controlsChanged || + controls.autoRotate || + vrModeRef.current || + transitionActive; - // Update camera yaw for mini-map rotation + if (!active) { + return; + } + needsRenderRef.current = false; + + // Cập nhật yaw cho mini-map — chỉ setState khi đổi đủ lớn để tránh re-render thừa. const direction = new THREE.Vector3(); camera.getWorldDirection(direction); const yaw = yawFromDirection(direction); - setCameraYaw(yaw); + if (Math.abs(normalizeYaw(yaw - lastYawRef.current)) > 0.15) { + lastYawRef.current = yaw; + setCameraYaw(yaw); + } updateHotspots(); - renderer.render(threeScene, camera); }; @@ -1495,6 +1518,7 @@ function TourExperience({ renderer.domElement.removeEventListener("pointermove", handlePointerMove); renderer.domElement.removeEventListener("pointerup", handlePointerEnd); renderer.domElement.removeEventListener("pointercancel", handlePointerEnd); + controls.removeEventListener("change", handleControlsChange); renderer.setAnimationLoop(null); controls.dispose(); geometry.dispose();