Skip to content

Commit

Permalink
feat: void object for e.g. support for click missed and much more
Browse files Browse the repository at this point in the history
  • Loading branch information
bbohlender committed Oct 5, 2024
1 parent 2c76841 commit 1793fe3
Show file tree
Hide file tree
Showing 19 changed files with 232 additions and 98 deletions.
9 changes: 6 additions & 3 deletions examples/pointer-events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
WebGLRenderTarget,
WebGLRenderer,
} from 'three'
import { PointerEventsMap, forwardHtmlEvents, forwardObjectEvents } from '@pmndrs/pointer-events'
import { PointerEventsMap, getVoidObject, forwardHtmlEvents, forwardObjectEvents } from '@pmndrs/pointer-events'

const camera = new PerspectiveCamera(70, 1, 0.01, 100)
camera.position.z = 2
Expand All @@ -23,8 +23,8 @@ const plane = new Mesh(new PlaneGeometry(), new MeshBasicMaterial({ map: frambuf
plane.scale.setScalar(2)
scene.add(plane)

plane.addEventListener('pointerover', () => (innerScene.background = new Color('orange')))
plane.addEventListener('pointerout', () => (innerScene.background = new Color('white')))
plane.addEventListener('pointerenter', () => (innerScene.background = new Color('orange')))
plane.addEventListener('pointerleave', () => (innerScene.background = new Color('white')))

const innerScene = new Scene()
innerScene.background = new Color('white')
Expand All @@ -45,6 +45,9 @@ box.addEventListener('pointerdown', (e) => {
})
box.addEventListener('pointerout', () => boxMaterial.color.set('red'))

getVoidObject(innerScene).addEventListener('click', () => console.log('click inner'))
getVoidObject(scene).addEventListener('click', () => console.log('click outer'))

const canvas = document.getElementById('root') as HTMLCanvasElement

forwardHtmlEvents(canvas, () => camera, scene)
Expand Down
3 changes: 2 additions & 1 deletion examples/rag-doll/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"dependencies": {
"@react-three/cannon": "^6.6.0",
"@react-three/drei": "^9.108.3",
"@react-three/xr": "workspace:^"
"@react-three/xr": "workspace:^",
"@pmndrs/pointer-events": "workspace:^"
},
"type": "module",
"scripts": {
Expand Down
22 changes: 19 additions & 3 deletions examples/rag-doll/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Canvas } from '@react-three/fiber'
import { Canvas, useThree } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'
import { Physics, usePlane } from '@react-three/cannon'
import { Cursor } from './helpers/Drag.js'
import { Guy } from './components/Guy.jsx'
import { Mug, Chair, Table, Lamp } from './components/Furniture.jsx'
import { createXRStore, useControllerLocomotion, XR, XROrigin } from '@react-three/xr'
import { useRef, Suspense } from 'react'
import { useRef, Suspense, useEffect } from 'react'
import { Group } from 'three'
import { forwardHtmlEvents } from '@pmndrs/pointer-events'

const store = createXRStore({
hand: { touchPointer: false },
Expand Down Expand Up @@ -37,7 +38,14 @@ export function App() {
>
Enter VR
</button>
<Canvas dpr={[1, 2]} shadows camera={{ position: [-40, 40, 40], fov: 25 }}>
<Canvas
onPointerMissed={() => console.log('missed')}
dpr={[1, 2]}
shadows
events={() => ({ enabled: false, priority: 0 })}
camera={{ position: [-40, 40, 40], fov: 25 }}
>
<SwitchToXRPointerEvents />
<OrbitControls />
<XR store={store}>
<color attach="background" args={['#171720']} />
Expand Down Expand Up @@ -89,3 +97,11 @@ function Floor(props) {
</mesh>
)
}

export function SwitchToXRPointerEvents() {
const domElement = useThree((s) => s.gl.domElement)
const camera = useThree((s) => s.camera)
const scene = useThree((s) => s.scene)
useEffect(() => forwardHtmlEvents(domElement, () => camera, scene), [domElement, camera, scene])
return null
}
9 changes: 7 additions & 2 deletions packages/pointer-events/src/combine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ export class CombinedPointer {
pointer.computeActivePointer()
}
const intersection = pointer.getIntersection()
const distance = pointer.getPointerCapture() != null ? -Infinity : (intersection?.distance ?? Infinity)
const distance =
pointer.getPointerCapture() != null
? -Infinity
: intersection?.object.isVoidObject
? Infinity
: (intersection?.distance ?? Infinity)
const isDefault = this.isDefaults[i]
if (smallestDistance == null || (isDefault && distance === smallestDistance) || distance < smallestDistance) {
this.activePointer = pointer
Expand Down Expand Up @@ -128,7 +133,7 @@ export class CombinedPointer {
const nonCapturedPointerLength = this.nonCapturedPointers.length
for (let i = 0; i < nonCapturedPointerLength; i++) {
const pointer = this.nonCapturedPointers[i]
pointer.setIntersection(pointer.intersector.finalizeIntersection())
pointer.setIntersection(pointer.intersector.finalizeIntersection(scene))
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/pointer-events/src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export class PointerEvent<E extends NativeEvent = globalThis.PointerEvent>
public readonly object: Object3D = currentObject,
private readonly propagationState: { stopped: boolean; stoppedImmediate: boolean } = {
stopped: !bubbles,
stoppedImmediate: !bubbles,
stoppedImmediate: false,
},
) {
super(nativeEvent)
Expand Down
5 changes: 3 additions & 2 deletions packages/pointer-events/src/forward.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ function htmlEventToCoords(element: HTMLElement, e: unknown, target: Vector2): V
*/
export function forwardHtmlEvents(
fromElement: HTMLElement,
getCamera: GetCamera,
getCamera: GetCamera | OrthographicCamera | PerspectiveCamera,
scene: Object3D,
options?: ForwardEventsOptions,
) {
return forwardEvents(
fromElement,
getCamera,
//backwards compatibility
typeof getCamera === 'function' ? getCamera : () => getCamera,
scene,
htmlEventToCoords.bind(null, fromElement),
fromElement.setPointerCapture.bind(fromElement),
Expand Down
21 changes: 2 additions & 19 deletions packages/pointer-events/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,5 @@
export {
Pointer,
PointerCapture,
AllowedPointerEvents,
AllowedPointerEventsType,
GetCamera,
PointerOptions,
getPointerById,
} from './pointer.js'
export {
type EventHandlerToEventName,
type NativeEvent as NativePointerEvent,
type NativeWheelEvent,
type PointerEventsHandlers,
type PointerEventsMap,
type NativeEvent,
WheelEvent,
PointerEvent,
} from './event.js'
export * from './pointer.js'
export * from './event.js'
export * from './intersections/index.js'
export * from './forward.js'
export * from './pointer/index.js'
Expand Down
1 change: 1 addition & 0 deletions packages/pointer-events/src/intersections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type IntersectionOptions = {
) => number
}

export * from './intersector.js'
export * from './lines.js'
export * from './ray.js'
export * from './sphere.js'
30 changes: 23 additions & 7 deletions packages/pointer-events/src/intersections/intersector.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
import { Object3D, Intersection as ThreeIntersection } from 'three'
import { Mesh, Object3D, Sphere, SphereGeometry, Intersection as ThreeIntersection } from 'three'
import { Intersection } from '../index.js'
import { PointerCapture } from '../pointer.js'

const VoidObjectRadius = 1000000
export const VoidObjectCollider = new Sphere(undefined, VoidObjectRadius)
const VoidObjectGeometry = new SphereGeometry(VoidObjectRadius)

const sceneVoidObjectMap = new Map<Object3D, Object3D>()

export function getVoidObject(scene: Object3D): Object3D {
let entry = sceneVoidObjectMap.get(scene)
if (entry == null) {
entry = new Mesh(VoidObjectGeometry)
entry.isVoidObject = true
entry.parent = scene
//makes sure all other intersections are always prioritized
entry.pointerEventsOrder = -Infinity
sceneVoidObjectMap.set(scene, entry)
}
return entry
}

export abstract class Intersector {
//state of the current intersection
protected intersection: ThreeIntersection | undefined
Expand All @@ -13,16 +32,13 @@ export abstract class Intersector {
this.prepareIntersection(nativeEvent)
}

public abstract intersectPointerCapture(
pointerCapture: PointerCapture,
nativeEvent: unknown,
): Intersection | undefined
public abstract intersectPointerCapture(pointerCapture: PointerCapture, nativeEvent: unknown): Intersection

public abstract isReady(): boolean

protected abstract prepareIntersection(nativeEvent: unknown): void

public abstract executeIntersection(object: Object3D, objectPointerEventsOrder: number): void
public abstract executeIntersection(scene: Object3D, objectPointerEventsOrder: number): void

public abstract finalizeIntersection(): Intersection | undefined
public abstract finalizeIntersection(scene: Object3D): Intersection
}
39 changes: 31 additions & 8 deletions packages/pointer-events/src/intersections/lines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Intersection as ThreeIntersection,
Object3D,
} from 'three'
import { computeIntersectionWorldPlane, getDominantIntersectionIndex } from './utils.js'
import { computeIntersectionWorldPlane, getDominantIntersectionIndex, voidObjectIntersectionFromRay } from './utils.js'
import type { PointerCapture } from '../pointer.js'
import { Intersector } from './intersector.js'
import { Intersection, IntersectionOptions } from '../index.js'
Expand All @@ -25,6 +25,8 @@ const defaultLinePoints = [new Vector3(0, 0, 0), new Vector3(0, 0, 1)]
export class LinesIntersector extends Intersector {
private raycasters: Array<Raycaster> = []
private fromMatrixWorld = new Matrix4()

//state
private intersectionLineIndex: number = 0
private intersectionDistanceOnLine: number = 0

Expand Down Expand Up @@ -54,13 +56,15 @@ export class LinesIntersector extends Intersector {
return true
}

public intersectPointerCapture({ intersection, object }: PointerCapture): Intersection | undefined {
public intersectPointerCapture({ intersection, object }: PointerCapture): Intersection {
const details = intersection.details
if (details.type != 'lines') {
return undefined
throw new Error(
`unable to process a pointer capture of type "${intersection.details.type}" with a lines intersector`,
)
}
if (!this.prepareTransformation()) {
return undefined
return intersection
}
const linePoints = this.options.linePoints ?? defaultLinePoints
lineHelper.set(linePoints[details.lineIndex], linePoints[details.lineIndex + 1]).applyMatrix4(this.fromMatrixWorld)
Expand Down Expand Up @@ -133,9 +137,28 @@ export class LinesIntersector extends Intersector {
}
}

public finalizeIntersection(): Intersection | undefined {
public finalizeIntersection(scene: Object3D): Intersection {
const pointerPosition = new Vector3().setFromMatrixPosition(this.fromMatrixWorld)
const pointerQuaternion = new Quaternion().setFromRotationMatrix(this.fromMatrixWorld)
if (this.intersection == null) {
return undefined
const lastRaycasterIndex = this.raycasters.length - 1
const prevDistance = this.raycasters.reduce(
(prev, caster, i) => (i === lastRaycasterIndex ? prev : prev + caster.far),
0,
)
const lastRaycaster = this.raycasters[lastRaycasterIndex]
return voidObjectIntersectionFromRay(
scene,
lastRaycaster.ray,
(distanceOnLine) => ({
lineIndex: this.raycasters.length - 1,
distanceOnLine,
type: 'lines' as const,
}),
pointerPosition,
pointerQuaternion,
prevDistance,
)
}
//TODO: consider maxLength
return Object.assign(this.intersection, {
Expand All @@ -144,8 +167,8 @@ export class LinesIntersector extends Intersector {
distanceOnLine: this.intersectionDistanceOnLine,
type: 'lines' as const,
},
pointerPosition: new Vector3().setFromMatrixPosition(this.fromMatrixWorld),
pointerQuaternion: new Quaternion().setFromRotationMatrix(this.fromMatrixWorld),
pointerPosition,
pointerQuaternion,
pointOnFace: this.intersection.point,
localPoint: this.intersection.point
.clone()
Expand Down
Loading

0 comments on commit 1793fe3

Please sign in to comment.