Skip to content

Commit 4ae523a

Browse files
authored
feat: CameraControls[impl] custom subclass (#2466)
* CameraControls[impl] subclass * . * doc * .
1 parent 0641186 commit 4ae523a

File tree

3 files changed

+76
-24
lines changed

3 files changed

+76
-24
lines changed

.storybook/stories/CameraControls.stories.tsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as THREE from 'three'
44
import { Meta, StoryObj } from '@storybook/react-vite'
55

66
import { Setup } from '../Setup'
7-
import { Box, CameraControls, PerspectiveCamera, Plane, useFBO } from '../../src'
7+
import { Box, CameraControls, CameraControlsImpl, PerspectiveCamera, Plane, useFBO } from '../../src'
88

99
export default {
1010
title: 'Controls/CameraControls',
@@ -131,3 +131,34 @@ export const CameraControlsSt3 = {
131131
),
132132
name: 'frameloop="demand"',
133133
} satisfies Story
134+
135+
//
136+
137+
function CameraControlsScene4(props: ComponentProps<typeof CameraControls>) {
138+
const cameraControlRef = useRef<CameraControls>(null)
139+
140+
return (
141+
<Setup controls={false}>
142+
<CameraControls ref={cameraControlRef} {...props} />
143+
<Box
144+
onClick={() => {
145+
cameraControlRef.current?.rotate(Math.PI / 4, 0, true)
146+
}}
147+
>
148+
<meshBasicMaterial wireframe />
149+
</Box>
150+
</Setup>
151+
)
152+
}
153+
154+
class MyCameraControls extends CameraControlsImpl {
155+
override rotate(...args: Parameters<CameraControlsImpl['rotate']>) {
156+
console.log('rotate', ...args)
157+
return super.rotate(...args)
158+
}
159+
}
160+
161+
export const CameraControlsSt4 = {
162+
render: (args) => <CameraControlsScene4 impl={MyCameraControls} {...args} />,
163+
name: 'Subclass',
164+
} satisfies Story

docs/controls/camera-controls.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ This is an implementation of the [camera-controls](https://github.com/yomotsu/ca
1717

1818
```tsx
1919
type CameraControlsProps = {
20+
/** Optional CameraControls subclass, default to `CameraControlsImpl` official class */
21+
impl?: typeof CameraControlsImpl
2022
/** The camera to control, default to the state's `camera` */
2123
camera?: PerspectiveCamera | OrthographicCamera
2224
/** DOM element to connect to, default to the state's `gl` renderer */
@@ -77,3 +79,18 @@ const { ACTION } = CameraControlsImpl;
7779
}}
7880
/>
7981
```
82+
83+
### `[impl]` custom subclass
84+
85+
You can pass a custom subclass of `CameraControlsImpl` to the `impl` prop. This allows you to override methods or add new functionality:
86+
87+
```tsx
88+
class MyCameraControls extends CameraControlsImpl {
89+
override rotate(...args: Parameters<CameraControlsImpl['rotate']>) {
90+
console.log('my rotate', ...args)
91+
return super.rotate(...args)
92+
}
93+
}
94+
95+
<CameraControls impl={MyCameraControls} />
96+
```

src/core/CameraControls.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type CameraControlsProps = Omit<
2626
Overwrite<
2727
ThreeElement<typeof CameraControlsImpl>,
2828
{
29+
impl?: typeof CameraControlsImpl
2930
camera?: PerspectiveCamera | OrthographicCamera
3031
domElement?: HTMLElement
3132
makeDefault?: boolean
@@ -57,6 +58,28 @@ export const CameraControls: ForwardRefComponent<CameraControlsProps, CameraCont
5758
CameraControlsImpl,
5859
CameraControlsProps
5960
>((props, ref) => {
61+
const {
62+
impl: SubclassImpl,
63+
camera,
64+
domElement,
65+
makeDefault,
66+
onControlStart,
67+
onControl,
68+
onControlEnd,
69+
onTransitionStart,
70+
onUpdate,
71+
onWake,
72+
onRest,
73+
onSleep,
74+
onStart,
75+
onEnd,
76+
onChange,
77+
regress,
78+
...restProps
79+
} = props
80+
81+
const Impl = SubclassImpl ?? CameraControlsImpl
82+
6083
// useMemo is used here instead of useEffect, otherwise the useMemo below runs first and throws
6184
useMemo(() => {
6285
// to allow for tree shaking, we only import the subset of THREE that is used by camera-controls
@@ -76,28 +99,9 @@ export const CameraControls: ForwardRefComponent<CameraControlsProps, CameraCont
7699
Vector4,
77100
}
78101

79-
CameraControlsImpl.install({ THREE: subsetOfTHREE })
80-
extend({ CameraControlsImpl })
81-
}, [])
82-
83-
const {
84-
camera,
85-
domElement,
86-
makeDefault,
87-
onControlStart,
88-
onControl,
89-
onControlEnd,
90-
onTransitionStart,
91-
onUpdate,
92-
onWake,
93-
onRest,
94-
onSleep,
95-
onStart,
96-
onEnd,
97-
onChange,
98-
regress,
99-
...restProps
100-
} = props
102+
Impl.install({ THREE: subsetOfTHREE })
103+
extend({ CameraControlsImpl: Impl })
104+
}, [Impl])
101105

102106
const defaultCamera = useThree((state) => state.camera)
103107
const gl = useThree((state) => state.gl)
@@ -111,7 +115,7 @@ export const CameraControls: ForwardRefComponent<CameraControlsProps, CameraCont
111115
const explCamera = camera || defaultCamera
112116
const explDomElement = (domElement || events.connected || gl.domElement) as HTMLElement
113117

114-
const controls = useMemo(() => new CameraControlsImpl(explCamera), [explCamera])
118+
const controls = useMemo(() => new Impl(explCamera), [Impl, explCamera])
115119

116120
useFrame((state, delta) => {
117121
controls.update(delta)

0 commit comments

Comments
 (0)