import { useEffect, useRef } from "react"; import { useFrame } from "@react-three/fiber"; import { useTexture } from "@react-three/drei"; import * as THREE from "three"; const SPEEDOMETER_DIAL_TEXTURE = "/assets/world/gps/cadran.png"; const SPEEDOMETER_NEEDLE_TEXTURE = "/assets/world/gps/fleche.png"; const SPEEDOMETER_MIN_ANGLE = Math.PI / 2; const SPEEDOMETER_MAX_ANGLE = -Math.PI / 2; const SPEEDOMETER_RENDER_ORDER = 10_000; interface EbikeSpeedometerProps { width?: number; height?: number; } export function EbikeSpeedometer({ width = 0.9, height = 0.5, }: EbikeSpeedometerProps): React.JSX.Element { const needleGroupRef = useRef(null); const speedFactorRef = useRef(0); const [dialTexture, needleTexture] = useTexture([ SPEEDOMETER_DIAL_TEXTURE, SPEEDOMETER_NEEDLE_TEXTURE, ]) as [THREE.Texture, THREE.Texture]; const needleWidth = width * 0.68; const needleHeight = needleWidth / 2; useEffect(() => { [dialTexture, needleTexture].forEach((texture) => { texture.colorSpace = THREE.SRGBColorSpace; texture.needsUpdate = true; }); }, [dialTexture, needleTexture]); useFrame((_, delta) => { const targetSpeedFactor = THREE.MathUtils.clamp( window.ebikeSpeedFactor ?? 0, 0, 1, ); speedFactorRef.current = THREE.MathUtils.lerp( speedFactorRef.current, targetSpeedFactor, Math.min(1, delta * 10), ); if (needleGroupRef.current) { needleGroupRef.current.rotation.z = THREE.MathUtils.lerp( SPEEDOMETER_MIN_ANGLE, SPEEDOMETER_MAX_ANGLE, speedFactorRef.current, ); } }); return ( ); }