diff --git a/.storybook/stories/Raycaster.stories.tsx b/.storybook/stories/Raycaster.stories.tsx
new file mode 100644
index 000000000..776f44604
--- /dev/null
+++ b/.storybook/stories/Raycaster.stories.tsx
@@ -0,0 +1,67 @@
+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) => (
+
+
+
+ ),
+ ],
+} satisfies Meta
+
+type Story = StoryObj
+
+function RaycasterScene({ origin, direction, ...props }: React.ComponentProps) {
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export const RaycasterSt = {
+ render: (args) => ,
+
+ name: 'Default',
+} satisfies Story
+
+const El = ({
+ // layers,
+ ...props
+}: ComponentProps<'mesh'>) => {
+ const $mesh = useRef(null)
+
+ useFrame(({ clock }) => {
+ if (!$mesh.current) return
+ $mesh.current.position.y = Math.sin(clock.getElapsedTime() * 0.5 + $mesh.current.position.x)
+ $mesh.current.rotation.z = Math.sin(clock.getElapsedTime() * 0.5) * Math.PI * 1
+ })
+
+ return (
+
+ {/* */}
+
+
+
+
+ )
+}
diff --git a/package.json b/package.json
index aa7360d9e..968925b73 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/core/Helper.tsx b/src/core/Helper.tsx
index 599cb42a3..8bebe3d9f 100644
--- a/src/core/Helper.tsx
+++ b/src/core/Helper.tsx
@@ -3,19 +3,19 @@ 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 extends [infer _, ...infer R] ? R : never
-
-export function useHelper(
- object3D: React.MutableRefObject | Falsey,
- helperConstructor: T,
- ...args: HelperArgs>
+type HelperType = Object3D & { update: () => void; dispose?: () => void }
+type HelperConstructor = new (...args: any[]) => T
+type HelperArgs = T extends [any, ...infer R] ? R : never
+
+export function useHelper>(
+ object3D: React.RefObject[0]> | Falsey,
+ helperConstructor: H,
+ ...args: HelperArgs>
) {
- const helper = React.useRef()
+ const helper = React.useRef()
const scene = useThree((state) => state.scene)
React.useLayoutEffect(() => {
- let currentHelper: HelperType = undefined!
+ let currentHelper: T = undefined!
if (object3D && object3D?.current && helperConstructor) {
helper.current = currentHelper = new (helperConstructor as any)(object3D.current, ...args)
@@ -39,15 +39,15 @@ export function useHelper(
//
-export type HelperProps = {
- type: T
- args?: HelperArgs>
+export type HelperProps = {
+ type: H
+ args?: HelperArgs>
}
-export const Helper = ({
+export const Helper = ({
type: helperConstructor,
args = [] as never,
-}: HelperProps) => {
+}: HelperProps) => {
const thisRef = React.useRef(null!)
const parentRef = React.useRef(null!)
diff --git a/src/core/Raycaster.tsx b/src/core/Raycaster.tsx
new file mode 100644
index 000000000..cc8a7874f
--- /dev/null
+++ b/src/core/Raycaster.tsx
@@ -0,0 +1,32 @@
+import * as THREE from 'three'
+import * as React from 'react'
+import { ComponentProps, forwardRef, useRef, useState } from 'react'
+import { useFrame } from '@react-three/fiber'
+import { RaycasterHelper } from '@gsimone/three-raycaster-helper'
+
+import { useHelper } from '..'
+
+type RaycasterProps = Omit, 'args'> & {
+ origin: NonNullable['args']>[0]
+ direction: NonNullable['args']>[1]
+} & {
+ helper?: boolean
+}
+
+export const Raycaster = forwardRef(
+ ({ origin, direction, helper, ...props }, fref) => {
+ const [r] = useState(() => new THREE.Raycaster(origin, direction))
+
+ const raycasterRef = useRef(null)
+
+ const raycasterHelper = useHelper(!!helper && raycasterRef, RaycasterHelper)
+ useFrame(({ scene }) => {
+ if (!raycasterHelper.current || !raycasterRef.current) return
+ // @ts-ignore
+ raycasterHelper.current.hits = raycasterRef.current.intersectObjects(scene.children)
+ })
+
+ // return
+ return
+ }
+)
diff --git a/src/core/index.ts b/src/core/index.ts
index 1842b9299..1b98f7057 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -24,6 +24,7 @@ export * from './Svg'
export * from './Gltf'
export * from './AsciiRenderer'
export * from './Splat'
+export * from './Raycaster'
// Cameras
export * from './OrthographicCamera'
diff --git a/yarn.lock b/yarn.lock
index a2d08b545..f88214bda 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2133,6 +2133,15 @@ __metadata:
languageName: node
linkType: hard
+"@gsimone/three-raycaster-helper@npm:^0.1.0":
+ version: 0.1.0
+ resolution: "@gsimone/three-raycaster-helper@npm:0.1.0"
+ peerDependencies:
+ three: ^0.139.2
+ checksum: 10c0/adefc5b44a449d0ef1540e46454b81b6ff6c267dfb1ad187a1f64da723d6dabc77158958cdd1cb0a63295ecfd9c2e5a4d08577b6821653963e334ef8e4d54b90
+ languageName: node
+ linkType: hard
+
"@humanfs/core@npm:^0.19.1":
version: 0.19.1
resolution: "@humanfs/core@npm:0.19.1"
@@ -2837,6 +2846,7 @@ __metadata:
"@eslint/compat": "npm:^1.2.3"
"@eslint/eslintrc": "npm:^3.2.0"
"@eslint/js": "npm:^9.15.0"
+ "@gsimone/three-raycaster-helper": "npm:^0.1.0"
"@mediapipe/tasks-vision": "npm:0.10.17"
"@monogrid/gainmap-js": "npm:^3.0.6"
"@playwright/test": "npm:^1.45.2"