import { useEffect , useRef } from 'react' ; import { useFrame } from '@react-three/fiber' ; import { useBox , useRaycastVehicle } from '@react-three/cannon' ; import { Chassis } from './Chassis' ; import { Wheel } from './Wheel' ; import type { BoxProps , WheelInfoOptions } from '@react-three/cannon' ; import type { Object3D } from 'three' ; export type VehicleProps = Required < Pick < BoxProps , 'angularVelocity' | 'position' | 'rotation' > > & { back ? : number ; force ? : number ; front ? : number ; height ? : number ; maxBrake ? : number ; radius ? : number ; steer ? : number ; width ? : number ; } ; function Vehicle ( { angularVelocity , back = - 1.15 , force = 1500 , front = 1.3 , height = - 0.04 , maxBrake = 50 , position , radius = 0.7 , rotation , steer = 0.5 , width = 1.2 , } ) { const chassisBody = useRef < Object3D > ( null ) ; const wheels = [ useRef < Object3D > ( null ) , useRef < Object3D > ( null ) , useRef < Object3D > ( null ) , useRef < Object3D > ( null ) , ] ; const controls = useControls ( ) ; const wheelInfo = { axleLocal : [ - 1 , 0 , 0 ] , customSlidingRotationalSpeed : - 30 , dampingCompression : 4.4 , dampingRelaxation : 10 , directionLocal : [ 0 , - 1 , 0 ] , frictionSlip : 2 , maxSuspensionForce : 1e4 , maxSuspensionTravel : 0.3 , radius , suspensionRestLength : 0.3 , suspensionStiffness : 30 , useCustomSlidingRotationalSpeed : true , } ; const wheelInfo1 = { ... wheelInfo , chassisConnectionPointLocal : [ - width / 2 , height , front ] , isFrontWheel : true , } ; const wheelInfo2 = { ... wheelInfo , chassisConnectionPointLocal : [ width / 2 , height , front ] , isFrontWheel : true , } ; const wheelInfo3 = { ... wheelInfo , chassisConnectionPointLocal : [ - width / 2 , height , back ] , isFrontWheel : false , } ; const wheelInfo4 = { ... wheelInfo , chassisConnectionPointLocal : [ width / 2 , height , back ] , isFrontWheel : false , } ; const [ , chassisApi ] = useBox ( ( ) => ( { allowSleep : false , angularVelocity , args : [ 1.7 , 1 , 4 ] , mass : 500 , onCollide : ( e ) => console . log ( 'bonk' , e . body . userData ) , position , rotation , } ) , chassisBody ) ; const [ vehicle , vehicleApi ] = useRaycastVehicle ( ( ) => ( { chassisBody , wheelInfos : [ wheelInfo1 , wheelInfo2 , wheelInfo3 , wheelInfo4 ] , wheels , } ) ) ; useEffect ( ( ) => vehicleApi . sliding . subscribe ( ( v ) => console . log ( 'sliding' , v ) ) , [ ] ) ; useFrame ( ( ) => { const { backward , brake , forward , left , reset , right } = controls . current ; for ( let e = 2 ; e < 4 ; e ++ ) { vehicleApi . applyEngineForce ( forward || backward ? force * ( forward && ! backward ? - 1 : 1 ) : 0 , 2 ) ; } for ( let s = 0 ; s < 2 ; s ++ ) { vehicleApi . setSteeringValue ( left || right ? steer * ( left && ! right ? 1 : - 1 ) : 0 , s ) ; } for ( let b = 2 ; b < 4 ; b ++ ) { vehicleApi . setBrake ( brake ? maxBrake : 0 , b ) ; } if ( reset ) { chassisApi . position . set ( ... position ) ; chassisApi . velocity . set ( 0 , 0 , 0 ) ; chassisApi . angularVelocity . set ( ... angularVelocity ) ; chassisApi . rotation . set ( ... rotation ) ; } } ) ; return ( < group ref = { vehicle } position = { [ 0 , - 0.4 , 0 ] } > < Chassis ref = { chassisBody } /> < Wheel ref = { wheels [ 0 ] } radius = { radius } leftSide / > < Wheel ref = { wheels [ 1 ] } radius = { radius } / > < Wheel ref = { wheels [ 2 ] } radius = { radius } leftSide / > < Wheel ref = { wheels [ 3 ] } radius = { radius } / > < /group>); } export default Vehicle ; export function useKeyPress ( target , event ) { useEffect ( ( ) => { const downHandler = ( { key } ) => target . indexOf ( key ) !== - 1 && event ( true ) ; const upHandler = ( { key } ) => target . indexOf ( key ) !== - 1 && event ( false ) ; window . addEventListener ( 'keydown' , downHandler ) ; window . addEventListener ( 'keyup' , upHandler ) ; return ( ) => { window . removeEventListener ( 'keydown' , downHandler ) ; window . removeEventListener ( 'keyup' , upHandler ) ; } ; } , [ ] ) ; } export function useControls ( ) { const keys = useRef ( { backward : false , brake : false , forward : false , left : false , reset : false , right : false , } ) ; useKeyPress ( [ 'ArrowUp' , 'w' ] , ( pressed ) => ( keys . current . forward = pressed ) ) ; useKeyPress ( [ 'ArrowDown' , 's' ] , ( pressed ) => ( keys . current . backward = pressed ) ) ; useKeyPress ( [ 'ArrowLeft' , 'a' ] , ( pressed ) => ( keys . current . left = pressed ) ) ; useKeyPress ( [ 'ArrowRight' , 'd' ] , ( pressed ) => ( keys . current . right = pressed ) ) ; useKeyPress ( [ ' ' ] , ( pressed ) => ( keys . current . brake = pressed ) ) ; useKeyPress ( [ 'r' ] , ( pressed ) => ( keys . current . reset = pressed ) ) ; return keys ; }