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): adding CameraControls #1237

Merged
merged 1 commit into from
Jan 21, 2023
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
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",
abernier marked this conversation as resolved.
Show resolved Hide resolved
"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