diff --git a/.storybook/stories/CameraControls.stories.tsx b/.storybook/stories/CameraControls.stories.tsx
index 40ec47c48..d8749951f 100644
--- a/.storybook/stories/CameraControls.stories.tsx
+++ b/.storybook/stories/CameraControls.stories.tsx
@@ -9,22 +9,17 @@ import { Box, CameraControls, PerspectiveCamera, Plane, useFBO } from '../../src
export default {
title: 'Controls/CameraControls',
component: CameraControls,
- decorators: [
- (Story) => (
-
-
-
- ),
- ],
} satisfies Meta
type Story = StoryObj
+//
+
function CameraControlsScene1(props: ComponentProps) {
const cameraControlRef = useRef(null)
return (
- <>
+
{
@@ -33,7 +28,7 @@ function CameraControlsScene1(props: ComponentProps) {
>
- >
+
)
}
@@ -42,6 +37,8 @@ export const CameraControlsSt1 = {
name: 'Default',
} satisfies Story
+//
+
const CameraControlsScene2 = (props: ComponentProps) => {
/**
* we will render our scene in a render target and use it as a map.
@@ -90,6 +87,47 @@ const CameraControlsScene2 = (props: ComponentProps) => {
}
export const CameraControlsSt2 = {
- render: (args) => ,
+ render: (args) => (
+
+
+
+ ),
name: 'Custom Camera',
} satisfies Story
+
+//
+
+function CameraControlsScene3(props: ComponentProps) {
+ const cameraControlRef = useRef(null)
+
+ return (
+ <>
+ console.log('wake')}
+ // onSleep={() => console.log('sleep')}
+ />
+ {
+ cameraControlRef.current?.rotate(Math.PI / 4, 0, true)
+ }}
+ >
+
+
+ >
+ )
+}
+
+export const CameraControlsSt3 = {
+ render: (args) => (
+
+
+
+ ),
+ name: 'frameloop="demand"',
+} satisfies Story
diff --git a/docs/controls/camera-controls.mdx b/docs/controls/camera-controls.mdx
index 306eecb7e..9d2601fd5 100644
--- a/docs/controls/camera-controls.mdx
+++ b/docs/controls/camera-controls.mdx
@@ -24,9 +24,14 @@ type CameraControlsProps = {
/** Reference this CameraControls instance as state's `controls` */
makeDefault?: boolean
/** Events callbacks, see: https://github.com/yomotsu/camera-controls#events */
- onStart?: (e?: { type: 'controlstart' }) => void
- onEnd?: (e?: { type: 'controlend' }) => void
- onChange?: (e?: { type: 'update' }) => void
+ onControlStart?: (e? { type: 'controlstart' }) => void
+ onControl?: (e? { type: 'control' }) => void
+ onControlEnd?: (e? { type: 'controlend' }) => void
+ onTransitionStart?: (e? { type: 'transitionstart' }) => void
+ onUpdate?: (e? { type: 'update' }) => void
+ onWake?: (e? { type: 'wake' }) => void
+ onRest?: (e? { type: 'rest' }) => void
+ onSleep?: (e? { type: 'sleep' }) => void
}
```
diff --git a/src/core/CameraControls.tsx b/src/core/CameraControls.tsx
index 6e86719f3..d38f65eab 100644
--- a/src/core/CameraControls.tsx
+++ b/src/core/CameraControls.tsx
@@ -1,3 +1,4 @@
+/* eslint react-hooks/exhaustive-deps: 1 */
import {
Box3,
EventDispatcher,
@@ -28,9 +29,23 @@ export type CameraControlsProps = Omit<
camera?: PerspectiveCamera | OrthographicCamera
domElement?: HTMLElement
makeDefault?: boolean
+
+ onControlStart?: (e?: { type: 'controlstart' }) => void
+ onControl?: (e?: { type: 'control' }) => void
+ onControlEnd?: (e?: { type: 'controlend' }) => void
+ onTransitionStart?: (e?: { type: 'transitionstart' }) => void
+ onUpdate?: (e?: { type: 'update' }) => void
+ onWake?: (e?: { type: 'wake' }) => void
+ onRest?: (e?: { type: 'rest' }) => void
+ onSleep?: (e?: { type: 'sleep' }) => void
+
+ /** @deprecated for OrbitControls compatibility: use `onControlStart` instead */
onStart?: (e?: { type: 'controlstart' }) => void
+ /** @deprecated for OrbitControls compatibility: use `onControlEnd` instead */
onEnd?: (e?: { type: 'controlend' }) => void
- onChange?: (e?: { type: 'update' }) => void
+ /** @deprecated for OrbitControls compatibility */
+ onChange?: (e?: { type: string }) => void
+
events?: boolean // Wether to enable events during controls interaction
regress?: boolean
}
@@ -65,7 +80,24 @@ export const CameraControls: ForwardRefComponent state.camera)
const gl = useThree((state) => state.gl)
@@ -91,36 +123,95 @@ export const CameraControls: ForwardRefComponent {
- const callback = (e) => {
+ function invalidateAndRegress() {
invalidate()
if (regress) performance.regress()
- if (onChange) onChange(e)
}
- const onStartCb: CameraControlsProps['onStart'] = (e) => {
- if (onStart) onStart(e)
+ const handleControlStart = (e: { type: 'controlstart' }) => {
+ invalidateAndRegress()
+ onControlStart?.(e)
+ onStart?.(e) // backwards compatibility
+ }
+
+ const handleControl = (e: { type: 'control' }) => {
+ invalidateAndRegress()
+ onControl?.(e)
+ onChange?.(e) // backwards compatibility
}
- const onEndCb: CameraControlsProps['onEnd'] = (e) => {
- if (onEnd) onEnd(e)
+ const handleControlEnd = (e: { type: 'controlend' }) => {
+ onControlEnd?.(e)
+ onEnd?.(e) // backwards compatibility
}
- controls.addEventListener('update', callback)
- controls.addEventListener('controlstart', onStartCb)
- controls.addEventListener('controlend', onEndCb)
- controls.addEventListener('control', callback)
- controls.addEventListener('transitionstart', callback)
- controls.addEventListener('wake', callback)
+ const handleTransitionStart = (e: { type: 'transitionstart' }) => {
+ invalidateAndRegress()
+ onTransitionStart?.(e)
+ onChange?.(e) // backwards compatibility
+ }
+
+ const handleUpdate = (e: { type: 'update' }) => {
+ invalidateAndRegress()
+ onUpdate?.(e)
+ onChange?.(e) // backwards compatibility
+ }
+
+ const handleWake = (e: { type: 'wake' }) => {
+ invalidateAndRegress()
+ onWake?.(e)
+ onChange?.(e) // backwards compatibility
+ }
+
+ const handleRest = (e: { type: 'rest' }) => {
+ onRest?.(e)
+ }
+
+ const handleSleep = (e: { type: 'sleep' }) => {
+ onSleep?.(e)
+ }
+
+ controls.addEventListener('controlstart', handleControlStart)
+ controls.addEventListener('control', handleControl)
+ controls.addEventListener('controlend', handleControlEnd)
+ controls.addEventListener('transitionstart', handleTransitionStart)
+ controls.addEventListener('update', handleUpdate)
+ controls.addEventListener('wake', handleWake)
+ controls.addEventListener('rest', handleRest)
+ controls.addEventListener('sleep', handleSleep)
return () => {
- controls.removeEventListener('update', callback)
- controls.removeEventListener('controlstart', onStartCb)
- controls.removeEventListener('controlend', onEndCb)
- controls.removeEventListener('control', callback)
- controls.removeEventListener('transitionstart', callback)
- controls.removeEventListener('wake', callback)
+ controls.removeEventListener('controlstart', handleControlStart)
+ controls.removeEventListener('control', handleControl)
+ controls.removeEventListener('controlend', handleControlEnd)
+ controls.removeEventListener('transitionstart', handleTransitionStart)
+ controls.removeEventListener('update', handleUpdate)
+ controls.removeEventListener('wake', handleWake)
+ controls.removeEventListener('rest', handleRest)
+ controls.removeEventListener('sleep', handleSleep)
}
- }, [controls, onStart, onEnd, invalidate, setEvents, regress, onChange])
+ }, [
+ controls,
+
+ invalidate,
+ setEvents,
+ regress,
+
+ performance,
+
+ onControlStart,
+ onControl,
+ onControlEnd,
+ onTransitionStart,
+ onUpdate,
+ onWake,
+ onRest,
+ onSleep,
+
+ onChange,
+ onStart,
+ onEnd,
+ ])
useEffect(() => {
if (makeDefault) {
@@ -128,6 +219,7 @@ export const CameraControls: ForwardRefComponent set({ controls: old })
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [makeDefault, controls])
return