theatre/packages/r3f/src/drei/PerspectiveCamera.tsx
Andrew Prifer 6497bf2097
Add instant editable cameras to the r3f extension (#315)
Add spruced up editable orthographic/perspective camera
2022-10-13 18:14:53 +00:00

75 lines
2.5 KiB
TypeScript

import * as React from 'react'
import type {PerspectiveCamera as PerspectiveCameraImpl, Object3D} from 'three'
import {useFrame, useThree} from '@react-three/fiber'
import mergeRefs from 'react-merge-refs'
import {editable} from '../index'
import {Vector3} from 'three'
import {editorStore} from '../main/store'
import type {MutableRefObject} from 'react'
export type PerspectiveCameraProps = Omit<
JSX.IntrinsicElements['perspectiveCamera'],
'lookAt'
> & {
lookAt?:
| [number, number, number]
| Vector3
| MutableRefObject<Object3D | null | undefined>
makeDefault?: boolean
manual?: boolean
children?: React.ReactNode
}
export const PerspectiveCamera = editable(
React.forwardRef(
({makeDefault, lookAt, ...props}: PerspectiveCameraProps, ref) => {
const set = useThree(({set}) => set)
const camera = useThree(({camera}) => camera)
const size = useThree(({size}) => size)
const cameraRef = React.useRef<PerspectiveCameraImpl>(null!)
React.useLayoutEffect(() => {
if (!props.manual) {
cameraRef.current.aspect = size.width / size.height
}
}, [size, props])
React.useLayoutEffect(() => {
cameraRef.current.updateProjectionMatrix()
})
React.useLayoutEffect(() => {
if (makeDefault) {
const oldCam = camera
set(() => ({camera: cameraRef.current!}))
return () => set(() => ({camera: oldCam}))
}
// The camera should not be part of the dependency list because this components camera is a stable reference
// that must exchange the default, and clean up after itself on unmount.
}, [cameraRef, makeDefault, set])
useFrame(() => {
if (lookAt && cameraRef.current) {
cameraRef.current.lookAt(
Array.isArray(lookAt)
? new Vector3(...lookAt)
: (lookAt as MutableRefObject<Object3D>).current
? (lookAt as MutableRefObject<Object3D>).current.position
: (lookAt as Vector3),
)
// how could we make it possible for users to do something like this too?
const snapshot = editorStore.getState().editablesSnapshot
if (snapshot) {
snapshot[
cameraRef.current.userData.__storeKey
].proxyObject?.rotation.copy(cameraRef.current.rotation)
}
}
})
return <perspectiveCamera ref={mergeRefs([cameraRef, ref])} {...props} />
},
),
'perspectiveCamera',
)