Merge pull request 'Update How It Works 3D page' (#1) from bharath-how-it-works into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
0
3d_scene_final.jsx
Normal file
0
3d_scene_final.jsx
Normal file
Binary file not shown.
@@ -66,6 +66,17 @@ export default function Experience3D() {
|
|||||||
return () => io.disconnect()
|
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).
|
// Own Lenis instance (global Lenis is gated off for this route).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const lenis = new Lenis({
|
const lenis = new Lenis({
|
||||||
@@ -77,18 +88,14 @@ export default function Experience3D() {
|
|||||||
setLenis(lenis)
|
setLenis(lenis)
|
||||||
lenis.on('scroll', ScrollTrigger.update)
|
lenis.on('scroll', ScrollTrigger.update)
|
||||||
|
|
||||||
let rafId
|
// Drive Lenis using GSAP's ticker to ensure synchronization with ScrollTrigger
|
||||||
function raf(time) {
|
const tickerCb = (time) => lenis.raf(time * 1000)
|
||||||
lenis.raf(time)
|
gsap.ticker.add(tickerCb)
|
||||||
rafId = requestAnimationFrame(raf)
|
|
||||||
}
|
|
||||||
rafId = requestAnimationFrame(raf)
|
|
||||||
|
|
||||||
gsap.ticker.lagSmoothing(0)
|
gsap.ticker.lagSmoothing(0)
|
||||||
ScrollTrigger.refresh()
|
ScrollTrigger.refresh()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelAnimationFrame(rafId)
|
gsap.ticker.remove(tickerCb)
|
||||||
lenis.destroy()
|
lenis.destroy()
|
||||||
setLenis(null)
|
setLenis(null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import gsap from 'gsap'
|
|||||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
||||||
import { useSceneStore } from '../store/useSceneStore'
|
import { useSceneStore } from '../store/useSceneStore'
|
||||||
import { animateDashboard } from '../animations/dashboardAnimation'
|
import { animateDashboard } from '../animations/dashboardAnimation'
|
||||||
import { playRevealChime } from '../utils/audioHelper'
|
|
||||||
|
|
||||||
gsap.registerPlugin(ScrollTrigger)
|
gsap.registerPlugin(ScrollTrigger)
|
||||||
|
|
||||||
@@ -53,7 +52,6 @@ export default function ScrollRig({ dashboardRefs, onPinState }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (section !== activeSectionRef.current) {
|
if (section !== activeSectionRef.current) {
|
||||||
playRevealChime()
|
|
||||||
activeSectionRef.current = section
|
activeSectionRef.current = section
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,8 +77,13 @@ export default function ScrollRig({ dashboardRefs, onPinState }) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
const refreshTimeout = setTimeout(() => {
|
||||||
|
ScrollTrigger.refresh()
|
||||||
|
}, 150)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
trigger.kill()
|
trigger.kill()
|
||||||
|
clearTimeout(refreshTimeout)
|
||||||
}
|
}
|
||||||
}, [setScrollProgress, setActiveSection, dashboardRefs, lenis, onPinState])
|
}, [setScrollProgress, setActiveSection, dashboardRefs, lenis, onPinState])
|
||||||
|
|
||||||
|
|||||||
@@ -32,10 +32,8 @@ export default function TruckAnimation({ truckRef, wheelRefs }) {
|
|||||||
// Track wheel rotation accumulation
|
// Track wheel rotation accumulation
|
||||||
const accumulatedRotationRef = useRef(0)
|
const accumulatedRotationRef = useRef(0)
|
||||||
const lastDampedProgressRef = useRef(0)
|
const lastDampedProgressRef = useRef(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useFrame((state, delta) => {
|
useFrame((state, delta) => {
|
||||||
|
|
||||||
if (!truckRef.current) return
|
if (!truckRef.current) return
|
||||||
|
|
||||||
// r3f can emit delta === 0 (coincident frames, the first frame, or after a
|
// 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
|
// Run one-time state initialization for progress trackers
|
||||||
if (!initialized.current) {
|
if (!initialized.current) {
|
||||||
dampedProgressRef.current = truckProgress
|
dampedProgressRef.current = truckProgress
|
||||||
|
dampedProgressRef.current_velocity = 0
|
||||||
lastDampedProgressRef.current = truckProgress
|
lastDampedProgressRef.current = truckProgress
|
||||||
lastScrollProgressRef.current = scrollProgress
|
lastScrollProgressRef.current = scrollProgress
|
||||||
isReversingRef.current = false
|
isReversingRef.current = false
|
||||||
extraRotationRef.current = 0
|
extraRotationRef.current = 0
|
||||||
|
extraRotationRef.current_velocity = 0
|
||||||
|
|
||||||
const position = truckPath.getPoint(dampedProgressRef.current)
|
const position = truckPath.getPoint(dampedProgressRef.current)
|
||||||
let lookAtTargetVector
|
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)
|
// Smoothly damp the extra rotation angle directly (prevents pitch/roll glitches or 3D target collapse)
|
||||||
easing.damp(extraRotationRef, 'current', targetExtraRotation, 0.20, dt)
|
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
|
// Apply the yaw pivot around the local vertical axis
|
||||||
truckRef.current.rotateY(extraRotationRef.current)
|
truckRef.current.rotateY(extraRotationRef.current)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Auto-generated by: https://github.com/pmndrs/gltfjsx
|
|||||||
import React, { useRef } from 'react'
|
import React, { useRef } from 'react'
|
||||||
import { useGLTF } from '@react-three/drei'
|
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')
|
const { nodes, materials } = useGLTF('/models/3d_scene_final.glb')
|
||||||
return (
|
return (
|
||||||
<group {...props} dispose={null}>
|
<group {...props} dispose={null}>
|
||||||
@@ -93,10 +93,16 @@ export function Model(props) {
|
|||||||
<mesh
|
<mesh
|
||||||
castShadow
|
castShadow
|
||||||
receiveShadow
|
receiveShadow
|
||||||
geometry={nodes.DoorMile_Logo_FirstWarehouse.geometry}
|
geometry={nodes.Uploaded_DoorMile_Logo_FirstWarehouse.geometry}
|
||||||
material={materials.DoorMile_Logo_Image_Material}
|
material={materials.DoorMile_Uploaded_Red_Logo_Material}
|
||||||
position={[-8.676, 0.035, -5.18]}
|
/>
|
||||||
rotation={[0, -0.121, 0]}
|
<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
|
<mesh
|
||||||
castShadow
|
castShadow
|
||||||
@@ -164,6 +170,13 @@ export function Model(props) {
|
|||||||
</group>
|
</group>
|
||||||
</group>
|
</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
|
<mesh
|
||||||
castShadow
|
castShadow
|
||||||
receiveShadow
|
receiveShadow
|
||||||
@@ -171,7 +184,7 @@ export function Model(props) {
|
|||||||
material={materials.Dark_Asphalt}
|
material={materials.Dark_Asphalt}
|
||||||
position={[0.013, -0.077, -0.026]}
|
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 rotation={[Math.PI / 2, 0, 0]}>
|
||||||
<group position={[0.013, 0.67, 0.006]} rotation={[0, -Math.PI / 2, 0]} scale={1.155}>
|
<group position={[0.013, 0.67, 0.006]} rotation={[0, -Math.PI / 2, 0]} scale={1.155}>
|
||||||
<mesh
|
<mesh
|
||||||
@@ -407,7 +420,7 @@ export function Model(props) {
|
|||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
ref={props.wheelRefs && props.wheelRefs[0]}
|
ref={wheelRefs?.[0]}
|
||||||
position={[1.873, 0.356, -0.899]}
|
position={[1.873, 0.356, -0.899]}
|
||||||
rotation={[-Math.PI / 2, 0, -Math.PI]}
|
rotation={[-Math.PI / 2, 0, -Math.PI]}
|
||||||
scale={[1.059, 1.044, 1.059]}>
|
scale={[1.059, 1.044, 1.059]}>
|
||||||
@@ -420,7 +433,7 @@ export function Model(props) {
|
|||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
ref={props.wheelRefs && props.wheelRefs[1]}
|
ref={wheelRefs?.[1]}
|
||||||
position={[1.873, 0.356, 0.91]}
|
position={[1.873, 0.356, 0.91]}
|
||||||
rotation={[Math.PI / 2, 0, Math.PI]}
|
rotation={[Math.PI / 2, 0, Math.PI]}
|
||||||
scale={[1.059, 1.044, 1.059]}>
|
scale={[1.059, 1.044, 1.059]}>
|
||||||
@@ -433,7 +446,7 @@ export function Model(props) {
|
|||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
ref={props.wheelRefs && props.wheelRefs[2]}
|
ref={wheelRefs?.[2]}
|
||||||
position={[-1.472, 0.356, -0.876]}
|
position={[-1.472, 0.356, -0.876]}
|
||||||
rotation={[-Math.PI / 2, 0, -Math.PI]}
|
rotation={[-Math.PI / 2, 0, -Math.PI]}
|
||||||
scale={[1.059, 0.662, 1.059]}>
|
scale={[1.059, 0.662, 1.059]}>
|
||||||
@@ -446,7 +459,7 @@ export function Model(props) {
|
|||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
ref={props.wheelRefs && props.wheelRefs[3]}
|
ref={wheelRefs?.[3]}
|
||||||
position={[-1.472, 0.356, 0.886]}
|
position={[-1.472, 0.356, 0.886]}
|
||||||
rotation={[Math.PI / 2, 0, Math.PI]}
|
rotation={[Math.PI / 2, 0, Math.PI]}
|
||||||
scale={[1.059, 0.662, 1.059]}>
|
scale={[1.059, 0.662, 1.059]}>
|
||||||
@@ -7526,20 +7539,12 @@ export function Model(props) {
|
|||||||
material={materials['DOORMILE bright red signage']}
|
material={materials['DOORMILE bright red signage']}
|
||||||
position={[-18.384, 4.82, -4.46]}
|
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
|
<mesh
|
||||||
castShadow
|
castShadow
|
||||||
receiveShadow
|
receiveShadow
|
||||||
geometry={nodes.white_in_transit_hub_operations_subtitle.geometry}
|
geometry={nodes.white_in_transit_hub_operations_subtitle.geometry}
|
||||||
material={materials['crisp white lettering and lines']}
|
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]}
|
rotation={[Math.PI / 2, 0, 0]}
|
||||||
/>
|
/>
|
||||||
<mesh
|
<mesh
|
||||||
@@ -10592,24 +10597,7 @@ export function Model(props) {
|
|||||||
rotation={[Math.PI, -1.081, Math.PI]}
|
rotation={[Math.PI, -1.081, Math.PI]}
|
||||||
scale={0.608}
|
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
|
<mesh
|
||||||
castShadow
|
castShadow
|
||||||
receiveShadow
|
receiveShadow
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user