Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
abernier committed Dec 11, 2024
1 parent 039798c commit be81fa4
Show file tree
Hide file tree
Showing 7 changed files with 365 additions and 26 deletions.
85 changes: 85 additions & 0 deletions .storybook/stories/Raycaster.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as THREE from 'three'
import * as React from 'react'

import { Vector3 } from 'three'
import { Meta, StoryObj } from '@storybook/react'

import { Setup } from '../Setup'

import { Raycaster } from '../../src'
import { ComponentProps, useRef } from 'react'
import { useFrame } from '@react-three/fiber'

export default {
title: 'Abstractions/Raycaster',
component: Raycaster,
decorators: [
(Story) => (
<Setup cameraPosition={new Vector3(0, 0, 10)}>
<Story />
</Setup>
),
],
argTypes: {
near: { control: { type: 'range', min: 0, max: 15 } },
far: { control: { type: 'range', min: 0, max: 15 } },
helper: { control: { type: 'boolean' } },
},
} satisfies Meta<typeof Raycaster>

type Story = StoryObj<typeof Raycaster>

function RaycasterScene(props: React.ComponentProps<typeof Raycaster>) {
const raycasterRef = useRef<THREE.Raycaster>(null)

React.useEffect(() => {
console.log('raycasterRef', raycasterRef)
})

return (
<>
<color attach="background" args={['#303030']} />

<Raycaster ref={raycasterRef} {...props} />

<Capsule position-x={-2} />
<Capsule />
<Capsule position-x={2} />
</>
)
}

export const RaycasterSt = {
render: (args) => <RaycasterScene {...args} />,
args: {
origin: [-4, 0, 0],
direction: [1, 0, 0],
near: 1,
far: 8,
helper: true,
},

name: 'Default',
} satisfies Story

const Capsule = ({
// layers,
...props
}: ComponentProps<'mesh'>) => {
const meshRef = useRef<THREE.Mesh>(null)

useFrame(({ clock }) => {
if (!meshRef.current) return
meshRef.current.position.y = Math.sin(clock.getElapsedTime() * 0.5 + meshRef.current.position.x)
meshRef.current.rotation.z = Math.sin(clock.getElapsedTime() * 0.5) * Math.PI * 1
})

return (
<mesh ref={meshRef} {...props}>
{/* <Layers layers={layers} /> */}

<capsuleGeometry args={[0.5, 0.5, 4, 32]} />
<meshNormalMaterial side={THREE.DoubleSide} />
</mesh>
)
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
},
"dependencies": {
"@babel/runtime": "^7.26.0",
"@gsimone/three-raycaster-helper": "^0.1.0",
"@mediapipe/tasks-vision": "0.10.17",
"@monogrid/gainmap-js": "^3.0.6",
"@react-spring/three": "~9.7.5",
Expand Down
75 changes: 49 additions & 26 deletions src/core/Helper.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,84 @@
/* eslint react-hooks/exhaustive-deps: 1 */
import * as React from 'react'
import { Object3D } from 'three'
import { useThree, useFrame } from '@react-three/fiber'
import { Falsey } from 'utility-types'

type HelperType = Object3D & { update: () => void; dispose: () => void }
type HelperConstructor = new (...args: any[]) => any
type HelperArgs<T> = T extends [infer _, ...infer R] ? R : never
type HelperConstructor = new (...args: any[]) => Object3D & { update: () => void; dispose?: () => void }
type HelperArgs<T> = T extends [any, ...infer R] ? R : never

export function useHelper<T extends HelperConstructor>(
object3D: React.MutableRefObject<Object3D> | Falsey,
helperConstructor: T,
...args: HelperArgs<ConstructorParameters<T>>
/**
* Instantiate a `THREE.*Helper` for an existing node and add it to the scene.
*
* Examples:
*
* ```ts
* useHelper(sphereRef, BoxHelper, 'royalblue')
* useHelper(sphereRef, VertexNormalsHelper, 1, 0xff0000)
* useHelper(raycasterRef, RaycasterHelper, 20)
* ```
*/
export function useHelper<H extends HelperConstructor>(
/** A ref to the node the helper is instantiate on (type inferred from H's ctor 1st param) */
nodeRef: React.RefObject<ConstructorParameters<H>[0]> | Falsey,
/** `*Helper` class */
helperConstructor: H,
/** Rest of arguments for H (types inferred from H's ctor params, omitting first) */
...args: HelperArgs<ConstructorParameters<H>>
) {
const helper = React.useRef<HelperType>()
const helperRef = React.useRef<InstanceType<H>>(null!)
const scene = useThree((state) => state.scene)

React.useLayoutEffect(() => {
let currentHelper: HelperType = undefined!
let currentHelper: InstanceType<H> = undefined!

if (object3D && object3D?.current && helperConstructor) {
helper.current = currentHelper = new (helperConstructor as any)(object3D.current, ...args)
if (nodeRef && nodeRef?.current && helperConstructor) {
helperRef.current = currentHelper = new helperConstructor(nodeRef.current, ...args) as InstanceType<H>
}

if (currentHelper) {
// Prevent the helpers from blocking rays
currentHelper.traverse((child) => (child.raycast = () => null))
scene.add(currentHelper)
return () => {
helper.current = undefined
helperRef.current = null!
scene.remove(currentHelper)
currentHelper.dispose?.()
}
}
}, [scene, helperConstructor, object3D, ...args])
}, [scene, helperConstructor, nodeRef, args])

useFrame(() => void helper.current?.update?.())
return helper
useFrame(() => void helperRef.current?.update?.())
return helperRef
}

//

export type HelperProps<T extends HelperConstructor> = {
type: T
args?: HelperArgs<ConstructorParameters<T>>
export type HelperProps<H extends HelperConstructor> = {
/** `*Helper` class */
type: H
/** Rest of arguments for H (types inferred from H's ctor params, omitting first) */
args?: HelperArgs<ConstructorParameters<H>>
}

export const Helper = <T extends HelperConstructor>({
/**
* Instantiate a `THREE.*Helper` for parent node and add it to the scene.
*/

export const Helper = <H extends HelperConstructor>({
type: helperConstructor,
args = [] as never,
}: HelperProps<T>) => {
const thisRef = React.useRef<Object3D>(null!)
}: HelperProps<H>) => {
const parentRef = React.useRef<Object3D>(null!)

React.useLayoutEffect(() => {
parentRef.current = thisRef.current.parent!
})

useHelper(parentRef, helperConstructor, ...args)

return <object3D ref={thisRef} />
return (
<object3D
ref={(obj) => {
parentRef.current = obj?.parent!
}}
/>
)
}
45 changes: 45 additions & 0 deletions src/core/Raycaster.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as THREE from 'three'
import * as React from 'react'
import { ComponentProps, forwardRef, useRef, useState } from 'react'
import { useFrame, type Vector3 } from '@react-three/fiber'
// import { RaycasterHelper } from '@gsimone/three-raycaster-helper'
import { RaycasterHelper } from '../tmp/raycaster-helper'

import { useHelper } from '..'

type RaycasterProps = Omit<ComponentProps<'raycaster'>, 'args'> & {
origin: Vector3
direction: Vector3
} & {
helper?: boolean
}

function toThreeVec3(v: Vector3) {
return v instanceof THREE.Vector3 ? v : new THREE.Vector3(...(typeof v === 'number' ? [v, v, v] : v))
}

export const Raycaster = forwardRef<THREE.Raycaster, RaycasterProps>(
({ origin: _origin, direction: _direction, near, far, helper, ...props }, fref) => {
const origin = toThreeVec3(_origin)
const direction = toThreeVec3(_direction)

const [r] = useState(() => new THREE.Raycaster(origin, direction))

const raycasterRef = useRef<THREE.Raycaster>(null)
const ref = fref || raycasterRef
const isCallbackRef = typeof ref === 'function'

const raycasterHelperRef = useHelper(helper && !isCallbackRef && ref, RaycasterHelper)

// Update the hits with intersection results
useFrame(({ scene }) => {
if (!helper) return
if (!ref || isCallbackRef) return

if (!raycasterHelperRef.current || !ref.current) return
raycasterHelperRef.current.hits = ref.current.intersectObjects(scene.children)
})

return <primitive ref={ref} object={r} {...{ origin, direction, near, far }} {...props} />
}
)
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export * from './Svg'
export * from './Gltf'
export * from './AsciiRenderer'
export * from './Splat'
export * from './Raycaster'

// Cameras
export * from './OrthographicCamera'
Expand Down
Loading

0 comments on commit be81fa4

Please sign in to comment.