-
Notifications
You must be signed in to change notification settings - Fork 717
/
CameraShake.tsx
102 lines (91 loc) · 3.22 KB
/
CameraShake.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import * as React from 'react'
import { useFrame, useThree } from '@react-three/fiber'
import { Vector3, Euler } from 'three'
import { SimplexNoise } from 'three-stdlib'
import { ForwardRefComponent } from '../helpers/ts-utils'
export interface ShakeController {
getIntensity: () => number
setIntensity: (val: number) => void
}
type ControlsProto = {
update(): void
target: Vector3
addEventListener: (event: string, callback: (event: any) => void) => void
removeEventListener: (event: string, callback: (event: any) => void) => void
}
export interface CameraShakeProps {
intensity?: number
decay?: boolean
decayRate?: number
maxYaw?: number
maxPitch?: number
maxRoll?: number
yawFrequency?: number
pitchFrequency?: number
rollFrequency?: number
}
export const CameraShake: ForwardRefComponent<CameraShakeProps, ShakeController | undefined> =
/* @__PURE__ */ React.forwardRef<ShakeController | undefined, CameraShakeProps>(
(
{
intensity = 1,
decay,
decayRate = 0.65,
maxYaw = 0.1,
maxPitch = 0.1,
maxRoll = 0.1,
yawFrequency = 0.1,
pitchFrequency = 0.1,
rollFrequency = 0.1,
},
ref
) => {
const camera = useThree((state) => state.camera)
const defaultControls = useThree((state) => state.controls) as unknown as ControlsProto
const intensityRef = React.useRef<number>(intensity)
const initialRotation = React.useRef<Euler>(camera.rotation.clone())
const [yawNoise] = React.useState(() => new SimplexNoise())
const [pitchNoise] = React.useState(() => new SimplexNoise())
const [rollNoise] = React.useState(() => new SimplexNoise())
const constrainIntensity = () => {
if (intensityRef.current < 0 || intensityRef.current > 1) {
intensityRef.current = intensityRef.current < 0 ? 0 : 1
}
}
React.useImperativeHandle(
ref,
() => ({
getIntensity: (): number => intensityRef.current,
setIntensity: (val: number): void => {
intensityRef.current = val
constrainIntensity()
},
}),
[]
)
React.useEffect(() => {
if (defaultControls) {
const callback = () => void (initialRotation.current = camera.rotation.clone())
defaultControls.addEventListener('change', callback)
callback()
return () => void defaultControls.removeEventListener('change', callback)
}
}, [camera, defaultControls])
useFrame((state, delta) => {
const shake = Math.pow(intensityRef.current, 2)
const yaw = maxYaw * shake * yawNoise.noise(state.clock.elapsedTime * yawFrequency, 1)
const pitch = maxPitch * shake * pitchNoise.noise(state.clock.elapsedTime * pitchFrequency, 1)
const roll = maxRoll * shake * rollNoise.noise(state.clock.elapsedTime * rollFrequency, 1)
camera.rotation.set(
initialRotation.current.x + pitch,
initialRotation.current.y + yaw,
initialRotation.current.z + roll
)
if (decay && intensityRef.current > 0) {
intensityRef.current -= decayRate * delta
constrainIntensity()
}
})
return null
}
)