Skip to content

Commit

Permalink
compute nearest uv when pointer is captured
Browse files Browse the repository at this point in the history
  • Loading branch information
bbohlender committed Nov 3, 2024
1 parent 1e78c7e commit bfcef6d
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 127 deletions.
6 changes: 3 additions & 3 deletions examples/uikit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"dependencies": {
"@pmndrs/pointer-events": "workspace:^",
"@react-three/drei": "^9.108.3",
"@react-three/uikit": "^0.5.3",
"@react-three/uikit-default": "^0.5.3",
"@react-three/uikit-lucide": "^0.5.3",
"@react-three/uikit": "^0.7.0",
"@react-three/uikit-default": "^0.7.0",
"@react-three/uikit-lucide": "^0.7.0",
"@react-three/xr": "workspace:^",
"leva": "^0.9.35"
},
Expand Down
47 changes: 46 additions & 1 deletion packages/pointer-events/src/event.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { BaseEvent, Face, Object3D, Quaternion, Ray, Vector2, Vector3 } from 'three'
import { Intersection as ThreeIntersection } from './intersections/index.js'
import { Pointer } from './pointer.js'
import { getObjectListeners } from './utils.js'
import type { Camera, IntersectionEvent, Intersection } from '@react-three/fiber/dist/declarations/src/core/events.js'
import { HtmlEvent, Properties } from './html-event.js'

Expand Down Expand Up @@ -268,3 +267,49 @@ function emitPointerEventRec(baseEvent: PointerEvent<NativeEvent>, currentObject
}
emitPointerEventRec(baseEvent, currentObject.parent)
}

const r3fEventToHandlerMap: Record<keyof PointerEventsMap, string> = {
click: 'onClick',
contextmenu: 'onContextMenu',
dblclick: 'onDoubleClick',
pointercancel: 'onPointerCancel',
pointerdown: 'onPointerDown',
pointerenter: 'onPointerEnter',
pointerleave: 'onPointerLeave',
pointermove: 'onPointerMove',
pointerout: 'onPointerOut',
pointerover: 'onPointerOver',
pointerup: 'onPointerUp',
wheel: 'onWheel',
}

export const listenerNames = Object.keys(r3fEventToHandlerMap)

declare module 'three' {
interface Object3D {
_listeners?: Record<string, Array<(event: unknown) => void> | undefined>
}
}

function getObjectListeners<E>(
object: Object3D,
forEvent: keyof PointerEventsMap,
): Array<(event: E) => void> | undefined {
if (object._listeners != null && forEvent in object._listeners) {
return object._listeners[forEvent]
}

//R3F compatibility
let handler: ((e: any) => void) | undefined
if (object.isVoidObject && forEvent === 'click' && object.parent?.__r3f != null) {
handler = object.parent.__r3f.root.getState().onPointerMissed
}
if (object.__r3f != null) {
handler = object.__r3f.handlers[r3fEventToHandlerMap[forEvent]]
}

if (handler == null) {
return undefined
}
return [handler]
}
6 changes: 2 additions & 4 deletions packages/pointer-events/src/forward.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,10 @@ function portalEventToCoords(e: unknown, target: Vector2): Vector2 {
if (!(e instanceof PointerEvent)) {
return target.set(0, 0)
}
if (!(e.object instanceof Mesh)) {
if (e.uv == null) {
return target.set(0, 0)
}
getClosestUV(target, e.point, e.object)
target.multiplyScalar(2).addScalar(-1)
return target
return target.copy(e.uv).multiplyScalar(2).addScalar(-1)
}

export function forwardObjectEvents(
Expand Down
39 changes: 38 additions & 1 deletion packages/pointer-events/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,44 @@
import type { Root } from '@react-three/fiber/dist/declarations/src/core/renderer.js'
import type { AllowedPointerEvents, AllowedPointerEventsType } from './pointer.js'

declare module 'three' {
interface Object3D {
__r3f?: {
eventCount: number
handlers: Record<string, ((e: any) => void) | undefined>
root: Root['store']
}
/**
* undefined and true means the transformation is ready
* false means transformation is not ready
*/
transformReady?: boolean

/**
* @default parent.pointerEvents ?? this.defaultPointerEvents
*/
pointerEvents?: AllowedPointerEvents
/**
* @default "listener"
*/
defaultPointerEvents?: AllowedPointerEvents
/**
* @default "all"
*/
pointerEventsType?: AllowedPointerEventsType
/**
* @default 0
* sorted by highest number first
* (just like a higher renderOrder number will result in rendering over the previous - if depthTest is false)
*/
pointerEventsOrder?: number
isVoidObject?: boolean
}
}

export * from './pointer.js'
export * from './event.js'
export * from './intersections/index.js'
export * from './forward.js'
export * from './pointer/index.js'
export * from './combine.js'
export { getClosestUV } from './utils.js'
10 changes: 9 additions & 1 deletion packages/pointer-events/src/intersections/lines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
Vector3,
Intersection as ThreeIntersection,
Object3D,
Mesh,
Vector2,
} from 'three'
import {
computeIntersectionWorldPlane,
Expand All @@ -18,12 +20,13 @@ import {
import type { PointerCapture } from '../pointer.js'
import { Intersector } from './intersector.js'
import { Intersection, IntersectionOptions } from '../index.js'
import { updateAndCheckWorldTransformation } from '../utils.js'
import { getClosestUV, updateAndCheckWorldTransformation } from '../utils.js'

const invertedMatrixHelper = new Matrix4()
const lineHelper = new Line3()
const planeHelper = new Plane()
const rayHelper = new Ray()
const point2Helper = new Vector2()
const defaultLinePoints = [new Vector3(0, 0, 0), new Vector3(0, 0, 1)]

export class LinesIntersector implements Intersector {
Expand Down Expand Up @@ -74,8 +77,13 @@ export class LinesIntersector implements Intersector {
const point = lineHelper.at(details.distanceOnLine / lineHelper.distance(), new Vector3())
computeIntersectionWorldPlane(planeHelper, intersection, object)
const pointOnFace = rayHelper.intersectPlane(planeHelper, new Vector3()) ?? point
let uv = intersection.uv
if (intersection.object instanceof Mesh && getClosestUV(point2Helper, point, intersection.object)) {
uv = point2Helper.clone()
}
return {
...intersection,
uv,
pointOnFace,
point,
pointerPosition: new Vector3().setFromMatrixPosition(this.fromMatrixWorld),
Expand Down
17 changes: 15 additions & 2 deletions packages/pointer-events/src/intersections/ray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Object3D,
Camera,
Vector2,
Mesh,
} from 'three'
import { Intersection, IntersectionOptions } from './index.js'
import { type PointerCapture } from '../pointer.js'
Expand All @@ -18,13 +19,14 @@ import {
voidObjectIntersectionFromRay,
} from './utils.js'
import { Intersector } from './intersector.js'
import { updateAndCheckWorldTransformation } from '../utils.js'
import { getClosestUV, updateAndCheckWorldTransformation } from '../utils.js'

const invertedMatrixHelper = new Matrix4()
const scaleHelper = new Vector3()
const NegZAxis = new Vector3(0, 0, -1)
const directionHelper = new Vector3()
const planeHelper = new Plane()
const point2Helper = new Vector2()

export class RayIntersector implements Intersector {
private readonly raycaster = new Raycaster()
Expand Down Expand Up @@ -72,11 +74,17 @@ export class RayIntersector implements Intersector {
computeIntersectionWorldPlane(planeHelper, intersection, object)
const { ray } = this.raycaster
const pointOnFace = ray.intersectPlane(planeHelper, new Vector3()) ?? intersection.point
const point = ray.direction.clone().multiplyScalar(intersection.distance).add(ray.origin)
let uv = intersection.uv
if (intersection.object instanceof Mesh && getClosestUV(point2Helper, point, intersection.object)) {
uv = point2Helper.clone()
}
return {
...intersection,
uv,
object,
pointOnFace,
point: ray.direction.clone().multiplyScalar(intersection.distance).add(ray.origin),
point,
pointerPosition: ray.origin.clone(),
pointerQuaternion: this.raycasterQuaternion.clone(),
}
Expand Down Expand Up @@ -173,8 +181,13 @@ export class CameraRayIntersector implements Intersector {
}

computeIntersectionWorldPlane(this.viewPlane, intersection, object)
let uv = intersection.uv
if (intersection.object instanceof Mesh && getClosestUV(point2Helper, point, intersection.object)) {
uv = point2Helper.clone()
}
return {
...intersection,
uv,
object,
point,
pointOnFace: point,
Expand Down
10 changes: 9 additions & 1 deletion packages/pointer-events/src/intersections/sphere.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import {
Quaternion,
Intersection as ThreeIntersection,
Plane,
Vector2,
} from 'three'
import { computeIntersectionWorldPlane, getDominantIntersectionIndex, pushTimes } from './utils.js'
import type { PointerCapture } from '../pointer.js'
import { Intersector } from './intersector.js'
import { getVoidObject, Intersection, IntersectionOptions } from '../index.js'
import { updateAndCheckWorldTransformation } from '../utils.js'
import { getClosestUV, updateAndCheckWorldTransformation } from '../utils.js'

const scaleHelper = new Vector3()
const point2Helper = new Vector2()

export class SphereIntersector implements Intersector {
private readonly fromPosition = new Vector3()
Expand Down Expand Up @@ -70,10 +72,16 @@ export class SphereIntersector implements Intersector {

const pointOnFace = planeHelper.projectPoint(this.fromPosition, new Vector3())

let uv = intersection.uv
if (intersection.object instanceof Mesh && getClosestUV(point2Helper, point, intersection.object)) {
uv = point2Helper.clone()
}

return {
details: {
type: 'sphere',
},
uv,
distance: intersection.distance,
pointerPosition: this.fromPosition.clone(),
pointerQuaternion: this.fromQuaternion.clone(),
Expand Down
23 changes: 22 additions & 1 deletion packages/pointer-events/src/intersections/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Plane, Intersection as ThreeIntersection, Object3D, Vector3, Ray, Quaternion } from 'three'
import { Intersection, IntersectionOptions } from './index.js'
import { AllowedPointerEventsType, Pointer, type AllowedPointerEvents } from '../pointer.js'
import { hasObjectListeners } from '../utils.js'
import { getVoidObject, VoidObjectCollider } from './intersector.js'
import { listenerNames } from '../event.js'

export function computeIntersectionWorldPlane(target: Plane, intersection: Intersection, object: Object3D): boolean {
const normal = intersection.normal ?? intersection.face?.normal
Expand Down Expand Up @@ -102,6 +102,27 @@ export function intersectPointerEventTargets(
}
}

function hasObjectListeners({ _listeners, __r3f }: Object3D): boolean {
if (__r3f != null && __r3f?.eventCount > 0) {
return true
}
if (_listeners == null) {
return false
}
const entries = Object.entries(_listeners)
const length = entries.length
for (let i = 0; i < length; i++) {
const entry = entries[i]
if (!listenerNames.includes(entry[0])) {
continue
}
if (entry[1] != null && entry[1].length > 0) {
return true
}
}
return false
}

function filterAndInteresct(
{ intersector, options }: Pointer,
object: Object3D,
Expand Down
20 changes: 0 additions & 20 deletions packages/pointer-events/src/pointer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,8 @@ export type AllowedPointerEventsType =

declare module 'three' {
interface Object3D {
_listeners?: Record<string, Array<(event: unknown) => void> | undefined>
/**
* @default parent.pointerEvents ?? this.defaultPointerEvents
*/
pointerEvents?: AllowedPointerEvents
/**
* @default "listener"
*/
defaultPointerEvents?: AllowedPointerEvents
/**
* @default "all"
*/
pointerEventsType?: AllowedPointerEventsType
/**
* @default 0
* sorted by highest number first
* (just like a higher renderOrder number will result in rendering over the previous - if depthTest is false)
*/
pointerEventsOrder?: number
[buttonsDownTimeKey]?: ButtonsTime
[buttonsClickTimeKey]?: ButtonsTime
isVoidObject?: boolean
}
}

Expand Down
Loading

0 comments on commit bfcef6d

Please sign in to comment.