Skip to content

Commit

Permalink
feat(Controls): adding CameraControls
Browse files Browse the repository at this point in the history
  • Loading branch information
abernier committed Jan 21, 2023
1 parent 175206d commit b28adc7
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 2 deletions.
89 changes: 89 additions & 0 deletions .storybook/stories/CameraControls.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { createPortal, useFrame } from '@react-three/fiber'
import React, { useRef, useState } from 'react'
import { Scene } from 'three'

import { Setup } from '../Setup'
import { Box, CameraControls, PerspectiveCamera, Plane, useFBO } from '../../src'

import type { Camera } from 'three'
import type { CameraControlsProps } from '../../src'

const args = {}

export const CameraControlsStory = (props: CameraControlsProps) => {
const cameraControlRef = useRef<CameraControls | null>(null)

return (
<>
<CameraControls ref={cameraControlRef} {...props} />
<Box
onClick={() => {
cameraControlRef.current?.rotate(Math.PI / 4, 0, true)
}}
>
<meshBasicMaterial wireframe />
</Box>
</>
)
}

CameraControlsStory.args = args
CameraControlsStory.storyName = 'Default'

export default {
title: 'Controls/CameraControls',
component: CameraControls,
decorators: [(storyFn) => <Setup controls={false}>{storyFn()}</Setup>],
}

const CustomCamera = (props: CameraControlsProps) => {
/**
* we will render our scene in a render target and use it as a map.
*/
const fbo = useFBO(400, 400)
const virtualCamera = useRef<CameraControls['camera']>()
const [virtualScene] = useState(() => new Scene())
const cameraControlRef = useRef<CameraControls | null>(null)

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

gl.setRenderTarget(null)
}
})

return (
<>
<Plane
args={[4, 4, 4]}
onClick={() => {
cameraControlRef.current?.rotate(Math.PI / 4, 0, true)
}}
>
<meshBasicMaterial map={fbo.texture} />
</Plane>

{createPortal(
<>
<Box>
<meshBasicMaterial wireframe />
</Box>

<PerspectiveCamera name="FBO Camera" ref={virtualCamera} position={[0, 0, 5]} />
<CameraControls ref={cameraControlRef} camera={virtualCamera.current} {...props} />

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

export const CustomCameraStory = (props: CameraControlsProps) => <CustomCamera {...props} />

CustomCameraStory.args = args
CustomCameraStory.storyName = 'Custom Camera'
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ If available controls have damping enabled by default, they manage their own upd
Some controls allow you to set `makeDefault`, similar to, for instance, PerspectiveCamera. This will set @react-three/fiber's `controls` field in the root store. This can make it easier in situations where you want controls to be known and other parts of the app could respond to it. Some drei controls already take it into account, like CameraShake, Gizmo and TransformControls.
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, ArcballControls, FlyControls, DeviceOrientationControls, PointerLockControls [![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/controls-pointerlockcontrols--pointer-lock-controls-scene-st), FirstPersonControls [![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/controls-firstpersoncontrols--first-person-controls-story)
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, ArcballControls, FlyControls, DeviceOrientationControls, PointerLockControls [![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/controls-pointerlockcontrols--pointer-lock-controls-scene-st), FirstPersonControls [![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/controls-firstpersoncontrols--first-person-controls-story) and [CameraControls](https://github.com/yomotsu/camera-controls) [![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/controls-cameracontrols--camera-controls-story)
All controls react to the default camera. If you have a `<PerspectiveCamera makeDefault />` in your scene, they will control it. If you need to inject an imperative camera or one that isn't the default, use the `camera` prop: `<OrbitControls camera={MyCamera} />`.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@
"@babel/runtime": "^7.11.2",
"@react-spring/three": "^9.3.1",
"@use-gesture/react": "^10.2.0",
"camera-controls": "^1.37.6",
"detect-gpu": "^5.0.5",
"glsl-noise": "^0.0.0",
"lodash.clamp": "^4.0.3",
"lodash.omit": "^4.5.0",
"lodash.pick": "^4.4.0",
"maath": "^0.5.1",
"meshline": "^3.1.6",
"react-composer": "^5.0.3",
"react-merge-refs": "^1.1.0",
Expand All @@ -71,7 +73,6 @@
"three-stdlib": "^2.20.4",
"troika-three-text": "^0.47.1",
"utility-types": "^3.10.0",
"maath": "^0.5.1",
"zustand": "^3.5.13"
},
"devDependencies": {
Expand Down
50 changes: 50 additions & 0 deletions src/core/CameraControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as THREE from 'three'
import type { PerspectiveCamera, OrthographicCamera } from 'three'

import * as React from 'react'
import { forwardRef, useMemo, useEffect } from 'react'
import { extend, useFrame, useThree, ReactThreeFiber, EventManager } from '@react-three/fiber'

import CameraControlsImpl from 'camera-controls'

export type CameraControlsProps = Omit<
ReactThreeFiber.Overwrite<
ReactThreeFiber.Node<CameraControlsImpl, typeof CameraControlsImpl>,
{
camera?: PerspectiveCamera | OrthographicCamera
domElement?: HTMLElement
}
>,
'ref'
>

export const CameraControls = forwardRef<CameraControlsImpl, CameraControlsProps>((props, ref) => {
useMemo(() => {
CameraControlsImpl.install({ THREE })
extend({ CameraControlsImpl })
}, [])

const { camera, domElement, ...restProps } = props

const defaultCamera = useThree((state) => state.camera)
const gl = useThree((state) => state.gl)
const invalidate = useThree((state) => state.invalidate)
const events = useThree((state) => state.events) as EventManager<HTMLElement>

const explCamera = camera || defaultCamera
const explDomElement = (domElement || events.connected || gl.domElement) as HTMLElement

const cameraControls = useMemo(() => new CameraControlsImpl(explCamera, explDomElement), [explCamera, explDomElement])

useFrame((state, delta) => {
if (cameraControls.enabled) cameraControls.update(delta)
}, -1)

useEffect(() => {
return () => void cameraControls.dispose()
}, [explDomElement, cameraControls, invalidate])

return <primitive ref={ref} object={cameraControls} {...restProps} />
})

export type CameraControls = CameraControlsImpl
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export * from './ArcballControls'
export * from './TransformControls'
export * from './PointerLockControls'
export * from './FirstPersonControls'
export * from './CameraControls'

// Gizmos
export * from './GizmoHelper'
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4595,6 +4595,11 @@ camelcase@^6.0.0, camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==

camera-controls@^1.37.6:
version "1.37.6"
resolved "https://registry.yarnpkg.com/camera-controls/-/camera-controls-1.37.6.tgz#d632f58e3b118921609908b53fbc328844d0e904"
integrity sha512-Fpppn3RwHgmGPfnjRVtK9AlpjcPdYo/6lFTqsSJ+gk9jRi48VmLFEBZ6uLLmTQiKiKjrs906ZMaAJW3fXIChdA==

caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001317:
version "1.0.30001322"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001322.tgz#2e4c09d11e1e8f852767dab287069a8d0c29d623"
Expand Down

0 comments on commit b28adc7

Please sign in to comment.