Update How It Works 3D page

This commit is contained in:
R-Bharathraj
2026-06-09 15:32:58 +05:30
parent 0ef51540e9
commit 45b4e7a109
7 changed files with 55 additions and 139 deletions

0
3d_scene_final.jsx Normal file
View File

Binary file not shown.

View File

@@ -66,6 +66,17 @@ export default function Experience3D() {
return () => io.disconnect()
}, [])
// Refresh ScrollTrigger when the scene actually mounts. WebGL canvas mounting
// can block the main thread and shift elements, so a refresh here is critical.
useEffect(() => {
if (mountScene) {
const timer = setTimeout(() => {
ScrollTrigger.refresh()
}, 150)
return () => clearTimeout(timer)
}
}, [mountScene])
// Own Lenis instance (global Lenis is gated off for this route).
useEffect(() => {
const lenis = new Lenis({
@@ -77,18 +88,14 @@ export default function Experience3D() {
setLenis(lenis)
lenis.on('scroll', ScrollTrigger.update)
let rafId
function raf(time) {
lenis.raf(time)
rafId = requestAnimationFrame(raf)
}
rafId = requestAnimationFrame(raf)
// Drive Lenis using GSAP's ticker to ensure synchronization with ScrollTrigger
const tickerCb = (time) => lenis.raf(time * 1000)
gsap.ticker.add(tickerCb)
gsap.ticker.lagSmoothing(0)
ScrollTrigger.refresh()
return () => {
cancelAnimationFrame(rafId)
gsap.ticker.remove(tickerCb)
lenis.destroy()
setLenis(null)
}

View File

@@ -3,7 +3,6 @@ import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { useSceneStore } from '../store/useSceneStore'
import { animateDashboard } from '../animations/dashboardAnimation'
import { playRevealChime } from '../utils/audioHelper'
gsap.registerPlugin(ScrollTrigger)
@@ -53,7 +52,6 @@ export default function ScrollRig({ dashboardRefs, onPinState }) {
}
if (section !== activeSectionRef.current) {
playRevealChime()
activeSectionRef.current = section
}
@@ -79,8 +77,13 @@ export default function ScrollRig({ dashboardRefs, onPinState }) {
}
},
})
const refreshTimeout = setTimeout(() => {
ScrollTrigger.refresh()
}, 150)
return () => {
trigger.kill()
clearTimeout(refreshTimeout)
}
}, [setScrollProgress, setActiveSection, dashboardRefs, lenis, onPinState])

View File

@@ -32,10 +32,8 @@ export default function TruckAnimation({ truckRef, wheelRefs }) {
// Track wheel rotation accumulation
const accumulatedRotationRef = useRef(0)
const lastDampedProgressRef = useRef(0)
useFrame((state, delta) => {
if (!truckRef.current) return
// r3f can emit delta === 0 (coincident frames, the first frame, or after a
@@ -78,10 +76,12 @@ export default function TruckAnimation({ truckRef, wheelRefs }) {
// Run one-time state initialization for progress trackers
if (!initialized.current) {
dampedProgressRef.current = truckProgress
dampedProgressRef.current_velocity = 0
lastDampedProgressRef.current = truckProgress
lastScrollProgressRef.current = scrollProgress
isReversingRef.current = false
extraRotationRef.current = 0
extraRotationRef.current_velocity = 0
const position = truckPath.getPoint(dampedProgressRef.current)
let lookAtTargetVector
@@ -146,6 +146,13 @@ export default function TruckAnimation({ truckRef, wheelRefs }) {
// Smoothly damp the extra rotation angle directly (prevents pitch/roll glitches or 3D target collapse)
easing.damp(extraRotationRef, 'current', targetExtraRotation, 0.20, dt)
// Defensive: reset extra rotation if it becomes NaN
if (!Number.isFinite(extraRotationRef.current)) {
extraRotationRef.current = targetExtraRotation
extraRotationRef.current_velocity = 0
if (extraRotationRef.__damp) extraRotationRef.__damp = {}
}
// Apply the yaw pivot around the local vertical axis
truckRef.current.rotateY(extraRotationRef.current)

View File

@@ -5,7 +5,7 @@ Auto-generated by: https://github.com/pmndrs/gltfjsx
import React, { useRef } from 'react'
import { useGLTF } from '@react-three/drei'
export function Model(props) {
export function Model({ truckRef, wheelRefs, dashboardRefs, ...props }) {
const { nodes, materials } = useGLTF('/models/3d_scene_final.glb')
return (
<group {...props} dispose={null}>
@@ -93,10 +93,16 @@ export function Model(props) {
<mesh
castShadow
receiveShadow
geometry={nodes.DoorMile_Logo_FirstWarehouse.geometry}
material={materials.DoorMile_Logo_Image_Material}
position={[-8.676, 0.035, -5.18]}
rotation={[0, -0.121, 0]}
geometry={nodes.Uploaded_DoorMile_Logo_FirstWarehouse.geometry}
material={materials.DoorMile_Uploaded_Red_Logo_Material}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.Uploaded_DoorMile_Logo_FrontFascia.geometry}
material={materials.DoorMile_Uploaded_White_Logo_Material}
position={[-7.462, 2.155, -1.793]}
scale={0.597}
/>
<mesh
castShadow
@@ -164,6 +170,13 @@ export function Model(props) {
</group>
</group>
</group>
<mesh
castShadow
receiveShadow
geometry={nodes.ABC_Factory_HubSign_TextPanel.geometry}
material={materials.ABC_Factory_TextPanel_Material}
position={[0, -0.141, 0]}
/>
<mesh
castShadow
receiveShadow
@@ -171,7 +184,7 @@ export function Model(props) {
material={materials.Dark_Asphalt}
position={[0.013, -0.077, -0.026]}
/>
<group ref={props.truckRef} position={[14.891, 0.284, -25.037]} rotation={[-Math.PI / 2, 0, -2.318]} scale={0.66}>
<group ref={truckRef} position={[14.891, 0.284, -25.037]} rotation={[-Math.PI / 2, 0, -2.318]} scale={0.66}>
<group rotation={[Math.PI / 2, 0, 0]}>
<group position={[0.013, 0.67, 0.006]} rotation={[0, -Math.PI / 2, 0]} scale={1.155}>
<mesh
@@ -407,7 +420,7 @@ export function Model(props) {
/>
</group>
<group
ref={props.wheelRefs && props.wheelRefs[0]}
ref={wheelRefs?.[0]}
position={[1.873, 0.356, -0.899]}
rotation={[-Math.PI / 2, 0, -Math.PI]}
scale={[1.059, 1.044, 1.059]}>
@@ -420,7 +433,7 @@ export function Model(props) {
/>
</group>
<group
ref={props.wheelRefs && props.wheelRefs[1]}
ref={wheelRefs?.[1]}
position={[1.873, 0.356, 0.91]}
rotation={[Math.PI / 2, 0, Math.PI]}
scale={[1.059, 1.044, 1.059]}>
@@ -433,7 +446,7 @@ export function Model(props) {
/>
</group>
<group
ref={props.wheelRefs && props.wheelRefs[2]}
ref={wheelRefs?.[2]}
position={[-1.472, 0.356, -0.876]}
rotation={[-Math.PI / 2, 0, -Math.PI]}
scale={[1.059, 0.662, 1.059]}>
@@ -446,7 +459,7 @@ export function Model(props) {
/>
</group>
<group
ref={props.wheelRefs && props.wheelRefs[3]}
ref={wheelRefs?.[3]}
position={[-1.472, 0.356, 0.886]}
rotation={[Math.PI / 2, 0, Math.PI]}
scale={[1.059, 0.662, 1.059]}>
@@ -7526,20 +7539,12 @@ export function Model(props) {
material={materials['DOORMILE bright red signage']}
position={[-18.384, 4.82, -4.46]}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.white_DOORMILE_brand_letters.geometry}
material={materials['crisp white lettering and lines']}
position={[-18.384, 5.08, -4.285]}
rotation={[Math.PI / 2, 0, 0]}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.white_in_transit_hub_operations_subtitle.geometry}
material={materials['crisp white lettering and lines']}
position={[-18.384, 4.5, -4.282]}
position={[-18.384, 4.5, -4.33]}
rotation={[Math.PI / 2, 0, 0]}
/>
<mesh
@@ -10592,24 +10597,7 @@ export function Model(props) {
rotation={[Math.PI, -1.081, Math.PI]}
scale={0.608}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.large_white_DOORMILE_letters.geometry}
material={materials['white raised sign lettering']}
position={[11.723, 4.419, 13.869]}
rotation={[Math.PI / 2, 0, 2.061]}
scale={0.608}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.white_delivery_to_customer_tagline.geometry}
material={materials['white raised sign lettering']}
position={[11.718, 4.078, 13.866]}
rotation={[Math.PI / 2, 0, 2.061]}
scale={0.608}
/>
<mesh
castShadow
receiveShadow

View File

@@ -1,89 +0,0 @@
let audioContext = null;
let isUnlocked = false;
// Initialize and unlock audio context
export const initAudio = () => {
if (isUnlocked) return;
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
if (!AudioContextClass) return;
if (!audioContext) {
audioContext = new AudioContextClass();
}
// Resume context if suspended (browser autoplay policy)
if (audioContext.state === 'suspended') {
audioContext.resume().then(() => {
isUnlocked = true;
cleanupListeners();
}).catch(() => {});
} else {
isUnlocked = true;
cleanupListeners();
}
};
const cleanupListeners = () => {
window.removeEventListener('click', initAudio);
window.removeEventListener('keydown', initAudio);
window.removeEventListener('touchstart', initAudio);
window.removeEventListener('wheel', initAudio);
};
// Add listeners for early activation
if (typeof window !== 'undefined') {
window.addEventListener('click', initAudio, { passive: true });
window.addEventListener('keydown', initAudio, { passive: true });
window.addEventListener('touchstart', initAudio, { passive: true });
window.addEventListener('wheel', initAudio, { passive: true });
}
// Play a high-tech UI chime sound for card reveal
export const playRevealChime = () => {
try {
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
if (!AudioContextClass) return;
if (!audioContext) {
audioContext = new AudioContextClass();
}
if (audioContext.state === 'suspended') {
audioContext.resume().catch(() => {});
}
const now = audioContext.currentTime;
// Master Volume node with exponential decay
const masterGain = audioContext.createGain();
masterGain.gain.setValueAtTime(0, now);
masterGain.gain.linearRampToValueAtTime(0.15, now + 0.04); // subtle fade-in to avoid clicking
masterGain.gain.exponentialRampToValueAtTime(0.0001, now + 0.4); // smooth tail decay
// Warm base oscillator (triangle wave)
const baseOsc = audioContext.createOscillator();
baseOsc.type = 'triangle';
baseOsc.frequency.setValueAtTime(329.63, now); // E4 pitch
baseOsc.frequency.exponentialRampToValueAtTime(523.25, now + 0.25); // Slide up to C5
// High harmonic chime oscillator (sine wave)
const chimeOsc = audioContext.createOscillator();
chimeOsc.type = 'sine';
chimeOsc.frequency.setValueAtTime(659.25, now); // E5 pitch
chimeOsc.frequency.exponentialRampToValueAtTime(1046.50, now + 0.25); // Slide up to C6
// Connect nodes
baseOsc.connect(masterGain);
chimeOsc.connect(masterGain);
masterGain.connect(audioContext.destination);
// Play oscillators
baseOsc.start(now);
baseOsc.stop(now + 0.4);
chimeOsc.start(now);
chimeOsc.stop(now + 0.4);
} catch (error) {
console.warn('Playback of reveal chime failed:', error);
}
};