Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Controls can now receive a custom camera as prop #321

Merged
merged 2 commits into from
Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion .storybook/stories/OrbitControls.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as React from 'react'
import * as THREE from 'three'
import { withKnobs, boolean } from '@storybook/addon-knobs'

import { Setup } from '../Setup'

import { OrbitControls, Box } from '../../src'
import { OrbitControls, Box, useFBO, Plane, PerspectiveCamera } from '../../src'
import { createPortal, useFrame, useResource } from 'react-three-fiber'

export function OrbitControlsStory() {
return (
Expand All @@ -27,3 +29,54 @@ export default {
component: OrbitControls,
decorators: [withKnobs, (storyFn) => <Setup controls={false}>{storyFn()}</Setup>],
}

function CustomCamera() {
/**
* we will render our scene in a render target and use it as a map.
*/
const fbo = useFBO(400, 400)
const virtualCamera = useResource<THREE.Camera>()
const [virtualScene] = React.useState(() => new THREE.Scene())

useFrame(({ gl }) => {
if (virtualCamera.current) {
gl.setRenderTarget(fbo)
gl.render(virtualScene, virtualCamera.current)

gl.setRenderTarget(null)
}
})

return (
<>
<Plane args={[4, 4, 4]}>
<meshBasicMaterial map={fbo.texture} />
</Plane>

{createPortal(
<>
<Box>
<meshBasicMaterial attach="material" wireframe />
</Box>

<PerspectiveCamera name="FBO Camera" ref={virtualCamera} position={[0, 0, 5]} />

<OrbitControls
camera={virtualCamera.current}
enablePan={boolean('Pan', true)}
enableZoom={boolean('Zoom', true)}
enableRotate={boolean('Rotate', true)}
/>

{/* @ts-ignore */}
<color attach="background" args={['hotpink']} />
</>,
virtualScene
)}
</>
)
}

export const CustomCameraStory = () => <CustomCamera />

CustomCameraStory.storyName = 'Custom Camera'
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,19 @@ If available controls have damping enabled by default, they manage their own upd

Drei currently exports OrbitControls [![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/controls-orbitcontrols--orbit-controls-story), MapControls [![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/controls-mapcontrols--map-controls-scene-st), TrackballControls, FlyControls, DeviceOrientationControls, TransformControls [![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/controls-transformcontrols--transform-controls-story), PointerLockControls [![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/controls-pointerlockcontrols--pointer-lock-controls-scene-st)

Every control component can be used with a custom camera using the `camera` prop:

```jsx
const myCamera = useResource()

return (
<>
<PerspectiveCamera ref={myCamera} position={[0, 5, 5]} />
<OrbitControls camera={myCamera.current} />
</>
)
```

PointerLockControls additionally supports a `selector` prop, which enables the binding of `click` event handlers for control activation to other elements than `document` (e.g. a 'Click here to play' button).

# Shapes
Expand Down
20 changes: 16 additions & 4 deletions src/core/DeviceOrientationControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,23 @@ import useEffectfulState from '../helpers/useEffectfulState'
export type DeviceOrientationControls = ReactThreeFiber.Object3DNode<
DeviceOrientationControlsImp,
typeof DeviceOrientationControlsImp
>
> & {
camera?: THREE.Camera
}

export const DeviceOrientationControls = React.forwardRef((props: DeviceOrientationControls, ref) => {
const { camera, invalidate } = useThree()
const controls = useEffectfulState(() => new DeviceOrientationControlsImp(camera), [camera], ref as any)
const { camera, ...rest } = props
const { camera: defaultCamera, invalidate } = useThree()
const explCamera = camera || defaultCamera
const controls = useEffectfulState(
() => {
if (explCamera) {
return new DeviceOrientationControlsImp(explCamera)
}
},
[explCamera],
ref as any
)

React.useEffect(() => {
controls?.addEventListener?.('change', invalidate)
Expand All @@ -25,5 +37,5 @@ export const DeviceOrientationControls = React.forwardRef((props: DeviceOrientat
return () => current?.dispose()
}, [controls])

return controls ? <primitive dispose={undefined} object={controls} {...props} /> : null
return controls ? <primitive dispose={undefined} object={controls} {...rest} /> : null
})
18 changes: 14 additions & 4 deletions src/core/MapControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import useEffectfulState from '../helpers/useEffectfulState'

export type MapControls = Overwrite<
ReactThreeFiber.Object3DNode<MapControlsImpl, typeof MapControlsImpl>,
{ target?: ReactThreeFiber.Vector3 }
{ target?: ReactThreeFiber.Vector3; camera?: THREE.Camera }
>

declare global {
Expand All @@ -17,14 +17,24 @@ declare global {
}

export const MapControls = React.forwardRef((props: MapControls = { enableDamping: true }, ref) => {
const { camera, gl, invalidate } = useThree()
const controls = useEffectfulState(() => new MapControlsImpl(camera, gl.domElement), [camera, gl], ref as any)
const { camera, ...rest } = props
const { camera: defaultCamera, gl, invalidate } = useThree()
const explCamera = camera || defaultCamera
const controls = useEffectfulState(
() => {
if (explCamera) {
return new MapControlsImpl(explCamera, gl.domElement)
}
},
[explCamera, gl],
ref as any
)

useFrame(() => controls?.update())
React.useEffect(() => {
controls?.addEventListener?.('change', invalidate)
return () => controls?.removeEventListener?.('change', invalidate)
}, [controls, invalidate])

return controls ? <primitive dispose={undefined} object={controls} enableDamping {...props} /> : null
return controls ? <primitive dispose={undefined} object={controls} enableDamping {...rest} /> : null
})
22 changes: 16 additions & 6 deletions src/core/OrbitControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import useEffectfulState from '../helpers/useEffectfulState'

export type OrbitControls = Overwrite<
ReactThreeFiber.Object3DNode<OrbitControlsImpl, typeof OrbitControlsImpl>,
{ target?: ReactThreeFiber.Vector3 }
{
target?: ReactThreeFiber.Vector3
camera?: THREE.Camera
}
>

declare global {
Expand All @@ -17,10 +20,17 @@ declare global {
}

export const OrbitControls = React.forwardRef((props: OrbitControls = { enableDamping: true }, ref) => {
const { camera, gl, invalidate } = useThree()
const controls = useEffectfulState<OrbitControlsImpl>(
() => new OrbitControlsImpl(camera, gl.domElement),
[camera, gl],
const { camera, ...rest } = props
const { camera: defaultCamera, gl, invalidate } = useThree()
const explCamera = camera || defaultCamera

const controls = useEffectfulState<OrbitControlsImpl | undefined>(
() => {
if (explCamera) {
return new OrbitControlsImpl(explCamera, gl.domElement)
}
},
[explCamera, gl],
ref as any
)

Expand All @@ -31,5 +41,5 @@ export const OrbitControls = React.forwardRef((props: OrbitControls = { enableDa
return () => controls?.removeEventListener?.('change', invalidate)
}, [controls, invalidate])

return controls ? <primitive dispose={undefined} object={controls} enableDamping {...props} /> : null
return controls ? <primitive dispose={undefined} object={controls} enableDamping {...rest} /> : null
})
17 changes: 12 additions & 5 deletions src/core/PointerLockControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@ import useEffectfulState from '../helpers/useEffectfulState'

export type PointerLockControls = ReactThreeFiber.Object3DNode<PointerLockControlsImpl, typeof PointerLockControlsImpl>

export type PointerLockControlsProps = PointerLockControls & { selector?: string }
export type PointerLockControlsProps = PointerLockControls & { selector?: string; camera?: THREE.Camera }

export const PointerLockControls = React.forwardRef(({ selector, ...props }: PointerLockControlsProps, ref) => {
const { camera, gl, invalidate } = useThree()
const { camera, ...rest } = props
const { camera: defaultCamera, gl, invalidate } = useThree()
const explCamera = camera || defaultCamera

const controls = useEffectfulState(
() => new PointerLockControlsImpl(camera, gl.domElement),
[camera, gl.domElement],
() => {
if (explCamera) {
return new PointerLockControlsImpl(explCamera, gl.domElement)
}
},
[explCamera, gl.domElement],
ref as any
)

Expand All @@ -27,5 +34,5 @@ export const PointerLockControls = React.forwardRef(({ selector, ...props }: Poi
return () => (element ? element.removeEventListener('click', handler) : undefined)
}, [controls, selector])

return controls ? <primitive dispose={undefined} object={controls} {...props} /> : null
return controls ? <primitive dispose={undefined} object={controls} {...rest} /> : null
})
19 changes: 15 additions & 4 deletions src/core/TrackballControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import useEffectfulState from '../helpers/useEffectfulState'

export type TrackballControls = Overwrite<
ReactThreeFiber.Object3DNode<TrackballControlsImpl, typeof TrackballControlsImpl>,
{ target?: ReactThreeFiber.Vector3 }
{ target?: ReactThreeFiber.Vector3; camera?: THREE.Camera }
>

declare global {
Expand All @@ -17,14 +17,25 @@ declare global {
}

export const TrackballControls = React.forwardRef((props: TrackballControls, ref) => {
const { camera, gl, invalidate } = useThree()
const controls = useEffectfulState(() => new TrackballControlsImpl(camera, gl.domElement), [camera, gl], ref as any)
const { camera, ...rest } = props
const { camera: defaultCamera, gl, invalidate } = useThree()
const explCamera = camera || defaultCamera

const controls = useEffectfulState(
() => {
if (explCamera) {
return new TrackballControlsImpl(explCamera, gl.domElement)
}
},
[explCamera, gl],
ref as any
)

useFrame(() => controls?.update())
React.useEffect(() => {
controls?.addEventListener?.('change', invalidate)
return () => controls?.removeEventListener?.('change', invalidate)
}, [controls, invalidate])

return controls ? <primitive dispose={undefined} object={controls} {...props} /> : null
return controls ? <primitive dispose={undefined} object={controls} {...rest} /> : null
})
18 changes: 13 additions & 5 deletions src/core/TransformControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,21 @@ export const TransformControls = React.forwardRef(
'showY',
'showZ',
]
const transformProps = pick(props, transformOnlyPropNames)
const objectProps = omit(props, transformOnlyPropNames)

const { camera, gl, invalidate } = useThree()
const { camera, ...rest } = props
const transformProps = pick(rest, transformOnlyPropNames)
const objectProps = omit(rest, transformOnlyPropNames)

const { camera: defaultCamera, gl, invalidate } = useThree()
const explCamera = camera || defaultCamera

const controls = useEffectfulState(
() => new TransformControlsImpl(camera, gl.domElement),
[camera, gl.domElement],
() => {
if (explCamera) {
return new TransformControlsImpl(explCamera, gl.domElement)
}
},
[explCamera, gl.domElement],
ref as any
)

Expand Down