|
| 1 | +/* eslint react-hooks/exhaustive-deps: 1 */ |
1 | 2 | import * as React from 'react'
|
2 | 3 | import { Object3D } from 'three'
|
3 | 4 | import { useThree, useFrame } from '@react-three/fiber'
|
4 | 5 | import { Falsey } from 'utility-types'
|
5 | 6 |
|
6 |
| -type HelperType = Object3D & { update: () => void; dispose: () => void } |
7 |
| -type HelperConstructor = new (...args: any[]) => any |
8 |
| -type HelperArgs<T> = T extends [infer _, ...infer R] ? R : never |
| 7 | +type HelperConstructor = new (...args: any[]) => Object3D & { update: () => void; dispose?: () => void } |
| 8 | +type HelperArgs<T> = T extends [any, ...infer R] ? R : never |
9 | 9 |
|
10 |
| -export function useHelper<T extends HelperConstructor>( |
11 |
| - object3D: React.MutableRefObject<Object3D> | Falsey, |
12 |
| - helperConstructor: T, |
13 |
| - ...args: HelperArgs<ConstructorParameters<T>> |
| 10 | +/** |
| 11 | + * Instantiate a `THREE.*Helper` for an existing node and add it to the scene. |
| 12 | + * |
| 13 | + * Examples: |
| 14 | + * |
| 15 | + * ```ts |
| 16 | + * useHelper(sphereRef, BoxHelper, 'royalblue') |
| 17 | + * useHelper(sphereRef, VertexNormalsHelper, 1, 0xff0000) |
| 18 | +
|
| 19 | + * useHelper(raycasterRef, RaycasterHelper, 20) |
| 20 | + * ``` |
| 21 | + */ |
| 22 | +export function useHelper<H extends HelperConstructor>( |
| 23 | + /** A ref to the node the helper is instantiate on (type inferred from H's ctor 1st param) */ |
| 24 | + nodeRef: React.RefObject<ConstructorParameters<H>[0]> | Falsey, |
| 25 | + /** `*Helper` class */ |
| 26 | + helperConstructor: H, |
| 27 | + /** Rest of arguments for H (types inferred from H's ctor params, omitting first) */ |
| 28 | + ...args: HelperArgs<ConstructorParameters<H>> |
14 | 29 | ) {
|
15 |
| - const helper = React.useRef<HelperType>() |
| 30 | + const helperRef = React.useRef<InstanceType<H>>(null!) |
16 | 31 | const scene = useThree((state) => state.scene)
|
| 32 | + |
17 | 33 | React.useLayoutEffect(() => {
|
18 |
| - let currentHelper: HelperType = undefined! |
| 34 | + let currentHelper: InstanceType<H> = undefined! |
19 | 35 |
|
20 |
| - if (object3D && object3D?.current && helperConstructor) { |
21 |
| - helper.current = currentHelper = new (helperConstructor as any)(object3D.current, ...args) |
| 36 | + if (nodeRef && nodeRef?.current && helperConstructor) { |
| 37 | + helperRef.current = currentHelper = new helperConstructor(nodeRef.current, ...args) as InstanceType<H> |
22 | 38 | }
|
23 | 39 |
|
24 | 40 | if (currentHelper) {
|
25 | 41 | // Prevent the helpers from blocking rays
|
26 | 42 | currentHelper.traverse((child) => (child.raycast = () => null))
|
27 | 43 | scene.add(currentHelper)
|
28 | 44 | return () => {
|
29 |
| - helper.current = undefined |
| 45 | + helperRef.current = null! |
30 | 46 | scene.remove(currentHelper)
|
31 | 47 | currentHelper.dispose?.()
|
32 | 48 | }
|
33 | 49 | }
|
34 |
| - }, [scene, helperConstructor, object3D, ...args]) |
| 50 | + }, [scene, helperConstructor, nodeRef, args]) |
35 | 51 |
|
36 |
| - useFrame(() => void helper.current?.update?.()) |
37 |
| - return helper |
| 52 | + useFrame(() => void helperRef.current?.update?.()) |
| 53 | + return helperRef |
38 | 54 | }
|
39 | 55 |
|
40 | 56 | //
|
41 | 57 |
|
42 |
| -export type HelperProps<T extends HelperConstructor> = { |
43 |
| - type: T |
44 |
| - args?: HelperArgs<ConstructorParameters<T>> |
| 58 | +export type HelperProps<H extends HelperConstructor> = { |
| 59 | + /** `*Helper` class */ |
| 60 | + type: H |
| 61 | + /** Rest of arguments for H (types inferred from H's ctor params, omitting first) */ |
| 62 | + args?: HelperArgs<ConstructorParameters<H>> |
45 | 63 | }
|
46 | 64 |
|
47 |
| -export const Helper = <T extends HelperConstructor>({ |
| 65 | +/** |
| 66 | + * Instantiate a `THREE.*Helper` for parent node and add it to the scene. |
| 67 | + */ |
| 68 | + |
| 69 | +export const Helper = <H extends HelperConstructor>({ |
48 | 70 | type: helperConstructor,
|
49 | 71 | args = [] as never,
|
50 |
| -}: HelperProps<T>) => { |
51 |
| - const thisRef = React.useRef<Object3D>(null!) |
| 72 | +}: HelperProps<H>) => { |
52 | 73 | const parentRef = React.useRef<Object3D>(null!)
|
53 | 74 |
|
54 |
| - React.useLayoutEffect(() => { |
55 |
| - parentRef.current = thisRef.current.parent! |
56 |
| - }) |
57 |
| - |
58 | 75 | useHelper(parentRef, helperConstructor, ...args)
|
59 | 76 |
|
60 |
| - return <object3D ref={thisRef} /> |
| 77 | + return ( |
| 78 | + <object3D |
| 79 | + ref={(obj) => { |
| 80 | + parentRef.current = obj?.parent! |
| 81 | + }} |
| 82 | + /> |
| 83 | + ) |
61 | 84 | }
|
0 commit comments