import { useMemo } from 'react' import * as THREE from 'three' import { truckPath } from '../curves/truckPath' import { clamp } from '../utils/helpers' export const useCameraAnimation = (scrollProgress) => { const cameraState = useMemo(() => { // 1. Calculate the truck position corresponding to the current scroll progress // Use the exact same piecewise mapping to keep camera follow 100% synchronized let truckProgress = 0 if (scrollProgress < 0.14) { truckProgress = 0.0 } else if (scrollProgress >= 0.14 && scrollProgress < 0.38) { truckProgress = 0.5 * (scrollProgress - 0.14) / 0.24 } else if (scrollProgress >= 0.38 && scrollProgress < 0.50) { truckProgress = 0.5 } else if (scrollProgress >= 0.50 && scrollProgress < 0.76) { truckProgress = 0.5 + 0.5 * (scrollProgress - 0.50) / 0.26 } else { truckProgress = 1.0 } const truckPos = truckPath.getPoint(truckProgress) const firstMileViewWhole = { position: new THREE.Vector3(38.0, 15.0, -10.0), target: new THREE.Vector3(24.377, 4.0, -39.303) } const firstMileViewFront = { position: new THREE.Vector3(7.0, 3.0, -19.0), target: new THREE.Vector3(15.5, 1.5, -26.5) } const midMileView = { position: new THREE.Vector3(-7.0, 7.5, 8.0), target: new THREE.Vector3(-19.146, 2.5, -9.0) } const lastMileViewClose = { position: new THREE.Vector3(-3.5, 4.0, 15.0), target: new THREE.Vector3(8.0, 2.0, 20.0) } const lastMileViewZoomedOut = { position: new THREE.Vector3(-10.4, 5.2, 12.0), target: new THREE.Vector3(8.0, 2.0, 20.0) } const analyticsView = { position: new THREE.Vector3(-13.5, 5.0, 31.0), target: new THREE.Vector3(-7.7, 3.5, 25.4) } // 3. Calculate local coordinate axes of the truck based on the spline tangent const forward = truckPath.getTangent(truckProgress).normalize() const up = new THREE.Vector3(0, 1, 0) const right = new THREE.Vector3().crossVectors(forward, up).normalize() // Cruise 1: Front-left follow perspective (facing the oncoming truck, zoomed out follow) const cruise1Pos = truckPos.clone() .addScaledVector(forward, 7.2) .addScaledVector(up, 3.2) .addScaledVector(right, -3.0) const cruise1Target = truckPos.clone() // Cruise 2: Front-right follow perspective (facing the oncoming truck, zoomed out follow) const cruise2Pos = truckPos.clone() .addScaledVector(forward, 7.2) .addScaledVector(up, 3.2) .addScaledVector(right, 3.0) const cruise2Target = truckPos.clone() const position = new THREE.Vector3() const target = new THREE.Vector3() // 4. Smoothly blend positions and targets depending on active scroll boundaries if (scrollProgress < 0.04) { // Step 1: Zoomed out overview of the whole building position.copy(firstMileViewWhole.position) target.copy(firstMileViewWhole.target) } else if (scrollProgress >= 0.04 && scrollProgress < 0.14) { // Step 2: Camera moves to the front close-up view of the building const alpha = (scrollProgress - 0.04) / 0.10 const smoothAlpha = alpha * alpha * (3 - 2 * alpha) position.lerpVectors(firstMileViewWhole.position, firstMileViewFront.position, smoothAlpha) target.lerpVectors(firstMileViewWhole.target, firstMileViewFront.target, smoothAlpha) } else if (scrollProgress >= 0.14 && scrollProgress < 0.18) { // Step 3: Truck starts moving, camera blends to close follow tracking const alpha = (scrollProgress - 0.14) / 0.04 const smoothAlpha = alpha * alpha * (3 - 2 * alpha) position.lerpVectors(firstMileViewFront.position, cruise1Pos, smoothAlpha) target.lerpVectors(firstMileViewFront.target, cruise1Target, smoothAlpha) } else if (scrollProgress >= 0.18 && scrollProgress < 0.34) { // Cruise 1: Close follow tracking position.copy(cruise1Pos) target.copy(cruise1Target) } else if (scrollProgress >= 0.34 && scrollProgress < 0.38) { // Blend: Cruise 1 Follow -> Mid Mile Building const alpha = (scrollProgress - 0.34) / 0.04 const smoothAlpha = alpha * alpha * (3 - 2 * alpha) position.lerpVectors(cruise1Pos, midMileView.position, smoothAlpha) target.lerpVectors(cruise1Target, midMileView.target, smoothAlpha) } else if (scrollProgress >= 0.38 && scrollProgress < 0.50) { // Mid Mile Building focus position.copy(midMileView.position) target.copy(midMileView.target) } else if (scrollProgress >= 0.50 && scrollProgress < 0.54) { // Blend: Mid Mile Building -> Cruise 2 Follow const alpha = (scrollProgress - 0.50) / 0.04 const smoothAlpha = alpha * alpha * (3 - 2 * alpha) position.lerpVectors(midMileView.position, cruise2Pos, smoothAlpha) target.lerpVectors(midMileView.target, cruise2Target, smoothAlpha) } else if (scrollProgress >= 0.54 && scrollProgress < 0.72) { // Cruise 2: Close follow tracking position.copy(cruise2Pos) target.copy(cruise2Target) } else if (scrollProgress >= 0.72 && scrollProgress < 0.76) { // Blend: Cruise 2 Follow -> Last Mile Building Close-up const alpha = (scrollProgress - 0.72) / 0.04 const smoothAlpha = alpha * alpha * (3 - 2 * alpha) position.lerpVectors(cruise2Pos, lastMileViewClose.position, smoothAlpha) target.lerpVectors(cruise2Target, lastMileViewClose.target, smoothAlpha) } else if (scrollProgress >= 0.76 && scrollProgress < 0.92) { // Last Mile Building Stop Sequence: // - 0.76 to 0.80: Parked close-up view of the truck and building // - 0.80 to 0.84: Zoom out transition back along the camera viewing axis // - 0.84 to 0.92: Zoomed-out overview of the final delivery stage (card stays frozen here) if (scrollProgress < 0.80) { position.copy(lastMileViewClose.position) target.copy(lastMileViewClose.target) } else if (scrollProgress >= 0.80 && scrollProgress < 0.84) { const alpha = (scrollProgress - 0.80) / 0.04 const smoothAlpha = alpha * alpha * (3 - 2 * alpha) position.lerpVectors(lastMileViewClose.position, lastMileViewZoomedOut.position, smoothAlpha) target.lerpVectors(lastMileViewClose.target, lastMileViewZoomedOut.target, smoothAlpha) } else { position.copy(lastMileViewZoomedOut.position) target.copy(lastMileViewZoomedOut.target) } } else if (scrollProgress >= 0.92 && scrollProgress < 0.96) { // Blend: Last Mile Building Zoomed-Out -> Analytics Dashboard screen const alpha = (scrollProgress - 0.92) / 0.04 const smoothAlpha = alpha * alpha * (3 - 2 * alpha) position.lerpVectors(lastMileViewZoomedOut.position, analyticsView.position, smoothAlpha) target.lerpVectors(lastMileViewZoomedOut.target, analyticsView.target, smoothAlpha) } else { // Analytics Dashboard screen focus position.copy(analyticsView.position) target.copy(analyticsView.target) } return { position, target } }, [scrollProgress]) return cameraState } export default useCameraAnimation