Skip to content

Commit

Permalink
feat(Controls): adding CameraControls (#1237)
Browse files Browse the repository at this point in the history
  • Loading branch information
abernier authored Jan 21, 2023
1 parent 175206d commit 206f827
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

1 comment on commit 206f827

@vercel
Copy link

@vercel vercel bot commented on 206f827 Jan 21, 2023

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.