Skip to content

Commit 2d3cca2

Browse files
committed
wip
1 parent 039798c commit 2d3cca2

File tree

7 files changed

+374
-26
lines changed

7 files changed

+374
-26
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import * as THREE from 'three'
2+
import * as React from 'react'
3+
4+
import { Vector3 } from 'three'
5+
import { Meta, StoryObj } from '@storybook/react'
6+
7+
import { Setup } from '../Setup'
8+
9+
import { Raycaster } from '../../src'
10+
import { ComponentProps, useRef } from 'react'
11+
import { useFrame } from '@react-three/fiber'
12+
13+
export default {
14+
title: 'Abstractions/Raycaster',
15+
component: Raycaster,
16+
decorators: [
17+
(Story) => (
18+
<Setup cameraPosition={new Vector3(0, 0, 10)}>
19+
<Story />
20+
</Setup>
21+
),
22+
],
23+
argTypes: {
24+
near: { control: { type: 'range', min: 0, max: 15 } },
25+
far: { control: { type: 'range', min: 0, max: 15 } },
26+
},
27+
} satisfies Meta<typeof Raycaster>
28+
29+
type Story = StoryObj<typeof Raycaster>
30+
31+
function RaycasterScene(props: React.ComponentProps<typeof Raycaster>) {
32+
const raycasterRef = useRef<THREE.Raycaster>(null)
33+
34+
React.useEffect(() => {
35+
console.log('raycasterRef', raycasterRef)
36+
})
37+
38+
return (
39+
<>
40+
<color attach="background" args={['#303030']} />
41+
42+
<Raycaster ref={raycasterRef} {...props} />
43+
44+
<Capsule position-x={-2} />
45+
<Capsule />
46+
<Capsule position-x={2} />
47+
</>
48+
)
49+
}
50+
51+
export const RaycasterSt = {
52+
render: (args) => <RaycasterScene {...args} />,
53+
args: {
54+
origin: [-4, 0, 0],
55+
direction: [1, 0, 0],
56+
near: 1,
57+
far: 8,
58+
helper: [20],
59+
},
60+
61+
name: 'Default',
62+
} satisfies Story
63+
64+
const Capsule = ({
65+
// layers,
66+
...props
67+
}: ComponentProps<'mesh'>) => {
68+
const meshRef = useRef<THREE.Mesh>(null)
69+
70+
useFrame(({ clock }) => {
71+
if (!meshRef.current) return
72+
meshRef.current.position.y = Math.sin(clock.getElapsedTime() * 0.5 + meshRef.current.position.x)
73+
meshRef.current.rotation.z = Math.sin(clock.getElapsedTime() * 0.5) * Math.PI * 1
74+
})
75+
76+
return (
77+
<mesh ref={meshRef} {...props}>
78+
{/* <Layers layers={layers} /> */}
79+
80+
<capsuleGeometry args={[0.5, 0.5, 4, 32]} />
81+
<meshNormalMaterial side={THREE.DoubleSide} />
82+
</mesh>
83+
)
84+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
},
4747
"dependencies": {
4848
"@babel/runtime": "^7.26.0",
49+
"@gsimone/three-raycaster-helper": "^0.1.0",
4950
"@mediapipe/tasks-vision": "0.10.17",
5051
"@monogrid/gainmap-js": "^3.0.6",
5152
"@react-spring/three": "~9.7.5",

src/core/Helper.tsx

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,84 @@
1+
/* eslint react-hooks/exhaustive-deps: 1 */
12
import * as React from 'react'
23
import { Object3D } from 'three'
34
import { useThree, useFrame } from '@react-three/fiber'
45
import { Falsey } from 'utility-types'
56

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
99

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>>
1429
) {
15-
const helper = React.useRef<HelperType>()
30+
const helperRef = React.useRef<InstanceType<H>>(null!)
1631
const scene = useThree((state) => state.scene)
32+
1733
React.useLayoutEffect(() => {
18-
let currentHelper: HelperType = undefined!
34+
let currentHelper: InstanceType<H> = undefined!
1935

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>
2238
}
2339

2440
if (currentHelper) {
2541
// Prevent the helpers from blocking rays
2642
currentHelper.traverse((child) => (child.raycast = () => null))
2743
scene.add(currentHelper)
2844
return () => {
29-
helper.current = undefined
45+
helperRef.current = null!
3046
scene.remove(currentHelper)
3147
currentHelper.dispose?.()
3248
}
3349
}
34-
}, [scene, helperConstructor, object3D, ...args])
50+
}, [scene, helperConstructor, nodeRef, args])
3551

36-
useFrame(() => void helper.current?.update?.())
37-
return helper
52+
useFrame(() => void helperRef.current?.update?.())
53+
return helperRef
3854
}
3955

4056
//
4157

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>>
4563
}
4664

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>({
4870
type: helperConstructor,
4971
args = [] as never,
50-
}: HelperProps<T>) => {
51-
const thisRef = React.useRef<Object3D>(null!)
72+
}: HelperProps<H>) => {
5273
const parentRef = React.useRef<Object3D>(null!)
5374

54-
React.useLayoutEffect(() => {
55-
parentRef.current = thisRef.current.parent!
56-
})
57-
5875
useHelper(parentRef, helperConstructor, ...args)
5976

60-
return <object3D ref={thisRef} />
77+
return (
78+
<object3D
79+
ref={(obj) => {
80+
parentRef.current = obj?.parent!
81+
}}
82+
/>
83+
)
6184
}

src/core/Raycaster.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as THREE from 'three'
2+
import * as React from 'react'
3+
import { ComponentProps, forwardRef, useRef, useState } from 'react'
4+
import { useFrame, type Vector3 } from '@react-three/fiber'
5+
// import { RaycasterHelper } from '@gsimone/three-raycaster-helper'
6+
import { RaycasterHelper } from '../tmp/raycaster-helper'
7+
8+
import { useHelper } from '..'
9+
import { Falsey } from 'utility-types'
10+
11+
type HelperArgs<T> = T extends [any, ...infer R] ? R : never
12+
13+
type RaycasterProps = Omit<ComponentProps<'raycaster'>, 'args'> & {
14+
/** Origin of the raycaster */
15+
origin: Vector3
16+
/** Direction of the raycaster */
17+
direction: Vector3
18+
} & {
19+
/** Whether or not to display the RaycasterHelper - you can pass additional params for the ctor here */
20+
helper?: Falsey | HelperArgs<ConstructorParameters<typeof RaycasterHelper>>
21+
}
22+
23+
function toThreeVec3(v: Vector3) {
24+
return v instanceof THREE.Vector3 ? v : new THREE.Vector3(...(typeof v === 'number' ? [v, v, v] : v))
25+
}
26+
27+
/**
28+
* `<raycaster>` wrapper, with a `helper` prop to visualize it
29+
*/
30+
export const Raycaster = forwardRef<THREE.Raycaster, RaycasterProps>(
31+
({ origin: _origin, direction: _direction, near, far, helper = false, ...props }, fref) => {
32+
const origin = toThreeVec3(_origin)
33+
const direction = toThreeVec3(_direction)
34+
35+
const [r] = useState(() => new THREE.Raycaster(origin, direction))
36+
37+
const raycasterRef = useRef<THREE.Raycaster>(null)
38+
const ref = fref || raycasterRef
39+
const isCallbackRef = typeof ref === 'function'
40+
41+
const args = helper || []
42+
const raycasterHelperRef = useHelper(helper && !isCallbackRef && ref, RaycasterHelper, ...args)
43+
44+
// Update the hits with intersection results
45+
useFrame(({ scene }) => {
46+
if (!helper) return
47+
if (!ref || isCallbackRef) return
48+
49+
if (!raycasterHelperRef.current || !ref.current) return
50+
raycasterHelperRef.current.hits = ref.current.intersectObjects(scene.children)
51+
})
52+
53+
return <primitive ref={ref} object={r} {...{ origin, direction, near, far }} {...props} />
54+
}
55+
)

src/core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export * from './Svg'
2424
export * from './Gltf'
2525
export * from './AsciiRenderer'
2626
export * from './Splat'
27+
export * from './Raycaster'
2728

2829
// Cameras
2930
export * from './OrthographicCamera'

0 commit comments

Comments
 (0)