Skip to content

Commit

Permalink
feat: Controls can now receive a custom camera as prop (#321)
Browse files Browse the repository at this point in the history
* feat: Controls can now receive a custom camera as prop

* docs: Adds paragraph on using a custom camera with controls
  • Loading branch information
gsimone authored Mar 6, 2021
1 parent be9ae69 commit e494021
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 29 deletions.
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

1 comment on commit e494021

@vercel
Copy link

@vercel vercel bot commented on e494021 Mar 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.