@coconut-xr/xinteraction
Advanced tools
Comparing version 0.0.3 to 0.0.4
@@ -1,2 +0,1 @@ | ||
import { Camera } from "@react-three/fiber"; | ||
import { Intersection, Object3D, Quaternion, Vector3 } from "three"; | ||
@@ -10,10 +9,2 @@ export type ObjectEventTypes = "press" | "release" | "cancel" | "select" | "move" | "enter" | "leave" | "wheel" | "losteventcapture"; | ||
export declare function isXIntersection(val: Intersection): val is XIntersection; | ||
/** | ||
* | ||
* @param p1 point 1 in world coordinates | ||
* @param p2 point 2 in world coordinates | ||
* @param camera | ||
*/ | ||
export declare function getDistanceSquaredInNDC(camera: Camera, p1: Vector3, p2: Vector3): number; | ||
export declare function isDragDefault(camera: Camera, i1: XIntersection, i2: XIntersection): boolean; | ||
export type EventDispatcher<E, I extends XIntersection> = { | ||
@@ -32,3 +23,2 @@ [Key in ObjectEventTypes]: (object: Object3D, intersection: I, inputDeviceElementId?: number) => void; | ||
protected getPressedElementIds: (intersection?: I) => Iterable<number>; | ||
protected isDrag: (pressIntersection: I, currentIntersection: I) => boolean; | ||
protected getInputDeviceTransformation: (position: Vector3, rotation: Quaternion) => void; | ||
@@ -41,3 +31,3 @@ onIntersections?: ((intersections: ReadonlyArray<I>) => void) | undefined; | ||
private objectInteractionStateMap; | ||
constructor(inputDeviceId: number, dispatchPressAlways: boolean, eventDispatcher: EventDispatcher<E, I>, computeIntersections: (event: E, capturedEvents?: Map<Object3D, I>) => Array<I>, getPressedElementIds: (intersection?: I) => Iterable<number>, isDrag: (pressIntersection: I, currentIntersection: I) => boolean, getInputDeviceTransformation: (position: Vector3, rotation: Quaternion) => void, onIntersections?: ((intersections: ReadonlyArray<I>) => void) | undefined, filterIntersections?: ((intersections: Array<I>) => Array<I>) | undefined); | ||
constructor(inputDeviceId: number, dispatchPressAlways: boolean, eventDispatcher: EventDispatcher<E, I>, computeIntersections: (event: E, capturedEvents?: Map<Object3D, I>) => Array<I>, getPressedElementIds: (intersection?: I) => Iterable<number>, getInputDeviceTransformation: (position: Vector3, rotation: Quaternion) => void, onIntersections?: ((intersections: ReadonlyArray<I>) => void) | undefined, filterIntersections?: ((intersections: Array<I>) => Array<I>) | undefined); | ||
/** | ||
@@ -54,3 +44,2 @@ * called when the input device receives a press, release, or move event | ||
private updateElementStateMap; | ||
private checkDrag; | ||
private dispatchPress; | ||
@@ -72,1 +61,2 @@ private dispatchRelease; | ||
} | ||
export * from "./intersections/index.js"; |
@@ -5,23 +5,4 @@ import { Object3D, Quaternion, Vector3 } from "three"; | ||
} | ||
const p1Helper = new Vector3(); | ||
const p2Helper = new Vector3(); | ||
const inputSourcePositionHelper = new Vector3(); | ||
const inputSourceRotationHelper = new Quaternion(); | ||
/** | ||
* | ||
* @param p1 point 1 in world coordinates | ||
* @param p2 point 2 in world coordinates | ||
* @param camera | ||
*/ | ||
export function getDistanceSquaredInNDC(camera, p1, p2) { | ||
return p1Helper | ||
.copy(p1) | ||
.project(camera) | ||
.distanceToSquared(p2Helper.copy(p2).project(camera)); | ||
} | ||
const defaultDragDistanceSquared = 0.0001; //0.01 | ||
export function isDragDefault(camera, i1, i2) { | ||
return (getDistanceSquaredInNDC(camera, i1.point, i2.point) > | ||
defaultDragDistanceSquared); | ||
} | ||
const traversalIdSymbol = Symbol("traversal-id"); | ||
@@ -36,3 +17,2 @@ const emptySet = new Set(); | ||
getPressedElementIds; | ||
isDrag; | ||
getInputDeviceTransformation; | ||
@@ -46,3 +26,3 @@ onIntersections; | ||
objectInteractionStateMap = new Map(); | ||
constructor(inputDeviceId, dispatchPressAlways, eventDispatcher, computeIntersections, getPressedElementIds, isDrag, getInputDeviceTransformation, onIntersections, filterIntersections) { | ||
constructor(inputDeviceId, dispatchPressAlways, eventDispatcher, computeIntersections, getPressedElementIds, getInputDeviceTransformation, onIntersections, filterIntersections) { | ||
this.inputDeviceId = inputDeviceId; | ||
@@ -53,3 +33,2 @@ this.dispatchPressAlways = dispatchPressAlways; | ||
this.getPressedElementIds = getPressedElementIds; | ||
this.isDrag = isDrag; | ||
this.getInputDeviceTransformation = getInputDeviceTransformation; | ||
@@ -99,3 +78,3 @@ this.onIntersections = onIntersections; | ||
this.dispatchPress(eventObject, intersection, pressedElementIds, dispatchPressFor); | ||
this.dispatchRelease(eventObject, intersection, interactionState, pressedElementIds, currentTime); | ||
this.dispatchRelease(eventObject, intersection, interactionState, pressedElementIds); | ||
this.updateElementStateMap(intersection, interactionState, pressedElementIds, dispatchPressFor, currentTime); | ||
@@ -118,3 +97,3 @@ } | ||
} | ||
this.dispatchRelease(eventObject, intersection, interactionState, pressedElementIds, currentTime); | ||
this.dispatchRelease(eventObject, intersection, interactionState, pressedElementIds); | ||
this.eventDispatcher.leave(eventObject, intersection); | ||
@@ -162,14 +141,4 @@ interactionState.lastLeftTime = currentTime; | ||
} | ||
else { | ||
this.checkDrag(intersection, interactionState, pressedElementId, currentTime); | ||
} | ||
} | ||
} | ||
checkDrag(intersection, interactionState, pressedElementId, currentTime) { | ||
const elementState = interactionState.elementStateMap.get(pressedElementId); | ||
if (elementState != null && | ||
this.isDrag(elementState.lastPressEventIntersection, intersection)) { | ||
elementState.lastDragTime = currentTime; | ||
} | ||
} | ||
dispatchPress(eventObject, intersection, pressedElementIds, dispatchPressFor) { | ||
@@ -183,3 +152,3 @@ for (const pressedElementId of pressedElementIds) { | ||
} | ||
dispatchRelease(eventObject, intersection, interactionState, pressedElementIds, currentTime) { | ||
dispatchRelease(eventObject, intersection, interactionState, pressedElementIds) { | ||
for (const releasedElementId of interactionState.lastPressedElementIds) { | ||
@@ -189,3 +158,2 @@ if (pressedElementIds.has(releasedElementId)) { | ||
} | ||
this.checkDrag(intersection, interactionState, releasedElementId, currentTime); | ||
//pressedElementId was not pressed this time | ||
@@ -197,6 +165,4 @@ this.eventDispatcher.release(eventObject, intersection, releasedElementId); | ||
(interactionState.lastLeftTime == null || | ||
interactionState.lastLeftTime < elementState.lastPressEventTime) && | ||
(elementState.lastDragTime == null || | ||
elementState.lastDragTime < elementState.lastPressEventTime)) { | ||
//=> the object wasn't left and dragged since it was pressed last | ||
interactionState.lastLeftTime < elementState.lastPressEventTime)) { | ||
//=> the object wasn't left since it was pressed last | ||
this.eventDispatcher.select(eventObject, intersection, releasedElementId); | ||
@@ -291,1 +257,2 @@ } | ||
} | ||
export * from "./intersections/index.js"; |
import { Intersection, Object3D } from "three"; | ||
export declare function traverseUntilInteractable<T, R>(object: Object3D, isInteractable: (object: Object3D) => boolean, callback: (object: Object3D) => T, reduce: (prev: R, value: T) => R, initial: R): R; | ||
export declare function isIntersectionNotClipped(intersection: Intersection): boolean; | ||
export * from "./lines.js"; | ||
export * from "./ray.js"; | ||
export * from "./sphere.js"; |
@@ -25,1 +25,4 @@ import { Mesh } from "three"; | ||
} | ||
export * from "./lines.js"; | ||
export * from "./ray.js"; | ||
export * from "./sphere.js"; |
import { Object3D, Quaternion, Vector3 } from "three"; | ||
import { EventDispatcher, XIntersection } from "../index.js"; | ||
import { ThreeEvent } from "@react-three/fiber"; | ||
export type XLinesIntersection = XIntersection & { | ||
@@ -8,2 +9,2 @@ lineIndex: number; | ||
export declare function intersectLinesFromCapturedEvents(from: Object3D, fromPosition: Vector3, fromRotation: Quaternion, linePoints: Array<Vector3>, capturedEvents: Map<Object3D, XLinesIntersection>): Array<XLinesIntersection>; | ||
export declare function intersectLinesFromObject(from: Object3D, fromPosition: Vector3, fromRotation: Quaternion, linePoints: Array<Vector3>, on: Object3D, dispatcher: EventDispatcher<Event, XLinesIntersection>, filterClipped: boolean): Array<XLinesIntersection>; | ||
export declare function intersectLinesFromObject(from: Object3D, fromPosition: Vector3, fromRotation: Quaternion, linePoints: Array<Vector3>, on: Object3D, dispatcher: EventDispatcher<ThreeEvent<Event>, XLinesIntersection>, filterClipped: boolean): Array<XLinesIntersection>; |
import { Camera, Object3D, Quaternion, Vector2, Vector3 } from "three"; | ||
import { EventDispatcher, XIntersection } from "../index.js"; | ||
import { ThreeEvent } from "@react-three/fiber"; | ||
export type XCameraRayIntersection = XIntersection & { | ||
distanceViewPlane: number; | ||
}; | ||
export declare function intersectRayFromCapturedEvents(fromPosition: Vector3, fromRotation: Quaternion, capturedEvents: Map<Object3D, XIntersection>): Array<XIntersection>; | ||
export declare function intersectRayFromCapturedEvents(fromPosition: Vector3, fromRotation: Quaternion, capturedEvents: Map<Object3D, XIntersection>, direction: Vector3): Array<XIntersection>; | ||
export declare function intersectRayFromCameraCapturedEvents(camera: Camera, coords: Vector2, capturedEvents: Map<Object3D, XCameraRayIntersection>, worldPositionTarget: Vector3, worldQuaternionTarget: Quaternion): Array<XCameraRayIntersection>; | ||
export declare function intersectRayFromObject(fromPosition: Vector3, fromRotation: Quaternion, on: Object3D, dispatcher: EventDispatcher<Event, XIntersection>, filterClipped: boolean): Array<XIntersection>; | ||
export declare function intersectRayFromCamera(from: Camera, coords: Vector2, on: Object3D, dispatcher: EventDispatcher<Event, XCameraRayIntersection>, filterClipped: boolean, worldPositionTarget: Vector3, worldQuaternionTarget: Quaternion): Array<XCameraRayIntersection>; | ||
export declare function intersectRayFromObject(fromPosition: Vector3, fromRotation: Quaternion, on: Object3D, dispatcher: EventDispatcher<ThreeEvent<Event>, XIntersection>, filterClipped: boolean, direction: Vector3): Array<XIntersection>; | ||
export declare function intersectRayFromCamera(from: Camera, coords: Vector2, on: Object3D, dispatcher: EventDispatcher<ThreeEvent<Event>, XCameraRayIntersection>, filterClipped: boolean, worldPositionTarget: Vector3, worldQuaternionTarget: Quaternion): Array<XCameraRayIntersection>; |
@@ -6,4 +6,4 @@ import { Plane, Raycaster, Vector3, } from "three"; | ||
const planeHelper = new Plane(); | ||
export function intersectRayFromCapturedEvents(fromPosition, fromRotation, capturedEvents) { | ||
directionHelper.set(0, 0, 1).applyQuaternion(fromRotation); | ||
export function intersectRayFromCapturedEvents(fromPosition, fromRotation, capturedEvents, direction) { | ||
directionHelper.copy(direction).applyQuaternion(fromRotation); | ||
return Array.from(capturedEvents).map(([capturedObject, intersection]) => { | ||
@@ -24,4 +24,4 @@ return { | ||
raycaster.setFromCamera(coords, camera); | ||
worldPositionTarget.copy(raycaster.ray.origin); | ||
worldQuaternionTarget.setFromUnitVectors(ZAXIS, raycaster.ray.direction); | ||
camera.getWorldPosition(worldPositionTarget); | ||
camera.getWorldQuaternion(worldQuaternionTarget); | ||
camera.getWorldDirection(directionHelper); | ||
@@ -44,5 +44,5 @@ return Array.from(capturedEvents).map(([capturedObject, intersection]) => { | ||
} | ||
export function intersectRayFromObject(fromPosition, fromRotation, on, dispatcher, filterClipped) { | ||
export function intersectRayFromObject(fromPosition, fromRotation, on, dispatcher, filterClipped, direction) { | ||
raycaster.ray.origin.copy(fromPosition); | ||
raycaster.ray.direction.set(0, 0, 1).applyQuaternion(fromRotation); | ||
raycaster.ray.direction.copy(direction).applyQuaternion(fromRotation); | ||
let intersections = traverseUntilInteractable(on, dispatcher.hasEventHandlers.bind(dispatcher), (object) => raycaster.intersectObject(object, true).map((intersection) => Object.assign(intersection, { | ||
@@ -58,7 +58,6 @@ inputDevicePosition: fromPosition.clone(), | ||
} | ||
const ZAXIS = new Vector3(); | ||
export function intersectRayFromCamera(from, coords, on, dispatcher, filterClipped, worldPositionTarget, worldQuaternionTarget) { | ||
raycaster.setFromCamera(coords, from); | ||
worldPositionTarget.copy(raycaster.ray.origin); | ||
worldQuaternionTarget.setFromUnitVectors(ZAXIS, raycaster.ray.direction); | ||
from.getWorldPosition(worldPositionTarget); | ||
from.getWorldQuaternion(worldQuaternionTarget); | ||
planeHelper.setFromNormalAndCoplanarPoint(from.getWorldDirection(directionHelper), raycaster.ray.origin); | ||
@@ -65,0 +64,0 @@ let intersections = traverseUntilInteractable(on, dispatcher.hasEventHandlers.bind(dispatcher), (object) => raycaster.intersectObject(object, true).map((intersection) => Object.assign(intersection, { |
import { Object3D, Vector3, Quaternion } from "three"; | ||
import { EventDispatcher, XIntersection } from "../index.js"; | ||
import { ThreeEvent } from "@react-three/fiber"; | ||
export type XSphereIntersection = XIntersection & { | ||
@@ -10,2 +11,2 @@ /** | ||
export declare function intersectSphereFromCapturedEvents(fromPosition: Vector3, fromRotation: Quaternion, capturedEvents: Map<Object3D, XSphereIntersection>): Array<XSphereIntersection>; | ||
export declare function intersectSphereFromObject(fromPosition: Vector3, fromQuaternion: Quaternion, radius: number, on: Object3D, dispatcher: EventDispatcher<Event, XSphereIntersection>, filterClipped: boolean): Array<XSphereIntersection>; | ||
export declare function intersectSphereFromObject(fromPosition: Vector3, fromQuaternion: Quaternion, radius: number, on: Object3D, dispatcher: EventDispatcher<ThreeEvent<Event>, XSphereIntersection>, filterClipped: boolean): Array<XSphereIntersection>; |
import React from "react"; | ||
import { Vector3, Event } from "three"; | ||
import { XIntersection } from "../index.js"; | ||
import { XLinesIntersection } from "../intersections/lines.js"; | ||
@@ -15,4 +14,3 @@ import { InputDeviceFunctions } from "./index.js"; | ||
onClickMissed?: ((event: ThreeEvent<Event>) => void) | undefined; | ||
isDrag?: ((i1: XIntersection, i2: XIntersection) => boolean) | undefined; | ||
filterClipped?: boolean | undefined; | ||
} & React.RefAttributes<InputDeviceFunctions>>; |
/* eslint-disable react/display-name */ | ||
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, } from "react"; | ||
import { Quaternion, Vector3 } from "three"; | ||
import { EventTranslator, isDragDefault } from "../index.js"; | ||
import { EventTranslator } from "../index.js"; | ||
import { intersectLinesFromCapturedEvents, intersectLinesFromObject, } from "../intersections/lines.js"; | ||
@@ -11,3 +11,3 @@ import { R3FEventDispatcher } from "./index.js"; | ||
const worldRotationHelper = new Quaternion(); | ||
export const XCurvedPointer = forwardRef(({ id, points, onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, isDrag: customIsDrag, filterClipped = true, }, ref) => { | ||
export const XCurvedPointer = forwardRef(({ id, points, onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, filterClipped = true, }, ref) => { | ||
const objectRef = useRef(null); | ||
@@ -20,5 +20,4 @@ const store = useStore(); | ||
const pressedElementIds = useMemo(() => new Set(), []); | ||
const properties = useMemo(() => ({ points, customIsDrag, filterClipped }), []); | ||
const properties = useMemo(() => ({ points, filterClipped }), []); | ||
properties.points = points; | ||
properties.customIsDrag = customIsDrag; | ||
properties.filterClipped = filterClipped; | ||
@@ -35,5 +34,3 @@ const translator = useMemo(() => new EventTranslator(id, false, dispatcher, (_, capturedEvents) => { | ||
: intersectLinesFromCapturedEvents(objectRef.current, worldPositionHelper, worldRotationHelper, properties.points, capturedEvents); | ||
}, () => pressedElementIds, (i1, i2) => properties.customIsDrag == null | ||
? isDragDefault(store.getState().camera, i1, i2) | ||
: properties.customIsDrag(i1, i2), (position, rotation) => { | ||
}, () => pressedElementIds, (position, rotation) => { | ||
if (objectRef.current == null) { | ||
@@ -40,0 +37,0 @@ return; |
@@ -6,3 +6,3 @@ import { Object3D, Event } from "three"; | ||
export declare const noEvents: () => EventManager<HTMLElement>; | ||
export declare class R3FEventDispatcher<I extends XIntersection> implements EventDispatcher<Event, I> { | ||
export declare class R3FEventDispatcher<I extends XIntersection> implements EventDispatcher<ThreeEvent<Event>, I> { | ||
onPointerDownMissed?: ((event: ThreeEvent<Event>) => void) | undefined; | ||
@@ -26,3 +26,3 @@ onPointerUpMissed?: ((event: ThreeEvent<Event>) => void) | undefined; | ||
private createEvent; | ||
bind(event: Event, eventTranslator: EventTranslator<Event, I>): void; | ||
bind(event: ThreeEvent<Event>, eventTranslator: EventTranslator<ThreeEvent<Event>, I>): void; | ||
hasEventHandlers(object: Object3D<Event>): boolean; | ||
@@ -36,2 +36,3 @@ } | ||
}; | ||
export * from "./forward-events.js"; | ||
export * from "./web-pointers.js"; | ||
@@ -38,0 +39,0 @@ export * from "./straight-pointer.js"; |
@@ -108,2 +108,3 @@ import { voidObject, } from "../index.js"; | ||
} | ||
export * from "./forward-events.js"; | ||
export * from "./web-pointers.js"; | ||
@@ -110,0 +111,0 @@ export * from "./straight-pointer.js"; |
import { ThreeEvent } from "@react-three/fiber"; | ||
import React from "react"; | ||
import { Event } from "three"; | ||
import { XIntersection } from "../index.js"; | ||
import { InputDeviceFunctions } from "./index.js"; | ||
import { XSphereIntersection } from "../intersections/sphere.js"; | ||
export declare const XSphereCollider: React.ForwardRefExoticComponent<{ | ||
@@ -13,9 +13,8 @@ id: number; | ||
} | undefined; | ||
onIntersections?: ((intersections: ReadonlyArray<XIntersection>) => void) | undefined; | ||
filterIntersections?: ((intersections: Array<XIntersection>) => Array<XIntersection>) | undefined; | ||
onIntersections?: ((intersections: ReadonlyArray<XSphereIntersection>) => void) | undefined; | ||
filterIntersections?: ((intersections: Array<XSphereIntersection>) => Array<XSphereIntersection>) | undefined; | ||
onPointerDownMissed?: ((event: ThreeEvent<Event>) => void) | undefined; | ||
onPointerUpMissed?: ((event: ThreeEvent<Event>) => void) | undefined; | ||
onClickMissed?: ((event: ThreeEvent<Event>) => void) | undefined; | ||
isDrag?: ((i1: XIntersection, i2: XIntersection) => boolean) | undefined; | ||
filterClipped?: boolean | undefined; | ||
} & React.RefAttributes<InputDeviceFunctions>>; |
@@ -5,3 +5,3 @@ /* eslint-disable react/display-name */ | ||
import { Quaternion, Vector3 } from "three"; | ||
import { EventTranslator, isDragDefault } from "../index.js"; | ||
import { EventTranslator } from "../index.js"; | ||
import { R3FEventDispatcher } from "./index.js"; | ||
@@ -12,3 +12,3 @@ import { intersectSphereFromCapturedEvents, intersectSphereFromObject, } from "../intersections/sphere.js"; | ||
const worldRotationHelper = new Quaternion(); | ||
export const XSphereCollider = forwardRef(({ id, distanceElement, radius, onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, isDrag: customIsDrag, filterClipped = true, }, ref) => { | ||
export const XSphereCollider = forwardRef(({ id, distanceElement, radius, onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, filterClipped = true, }, ref) => { | ||
const objectRef = useRef(null); | ||
@@ -21,6 +21,5 @@ const store = useStore(); | ||
dispatcher.onClickMissed = onClickMissed; | ||
const properties = useMemo(() => ({ distanceElement, radius, customIsDrag, filterClipped }), []); | ||
const properties = useMemo(() => ({ distanceElement, radius, filterClipped }), []); | ||
properties.distanceElement = distanceElement; | ||
properties.radius = radius; | ||
properties.customIsDrag = customIsDrag; | ||
properties.filterClipped = filterClipped; | ||
@@ -53,5 +52,3 @@ const translator = useMemo(() => new EventTranslator(id, true, dispatcher, (_, capturedEvents) => { | ||
return pressedElementIds; | ||
}, (i1, i2) => properties.customIsDrag == null | ||
? isDragDefault(store.getState().camera, i1, i2) | ||
: properties.customIsDrag(i1, i2), (position, rotation) => { | ||
}, (position, rotation) => { | ||
if (objectRef.current == null) { | ||
@@ -58,0 +55,0 @@ return; |
import React from "react"; | ||
import { Event } from "three"; | ||
import { Vector3, Event } from "three"; | ||
import { XIntersection } from "../index.js"; | ||
@@ -14,3 +14,4 @@ import { InputDeviceFunctions } from "./index.js"; | ||
isDrag?: ((i1: XIntersection, i2: XIntersection) => boolean) | undefined; | ||
direction?: Vector3 | undefined; | ||
filterClipped?: boolean | undefined; | ||
} & React.RefAttributes<InputDeviceFunctions>>; |
/* eslint-disable react/display-name */ | ||
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, } from "react"; | ||
import { Quaternion, Vector3 } from "three"; | ||
import { EventTranslator, isDragDefault } from "../index.js"; | ||
import { EventTranslator } from "../index.js"; | ||
import { intersectRayFromCapturedEvents, intersectRayFromObject, } from "../intersections/ray.js"; | ||
@@ -11,3 +11,4 @@ import { R3FEventDispatcher } from "./index.js"; | ||
const worldRotationHelper = new Quaternion(); | ||
export const XStraightPointer = forwardRef(({ id, onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, isDrag: customIsDrag, filterClipped = true, }, ref) => { | ||
const ZAXIS = new Vector3(); | ||
export const XStraightPointer = forwardRef(({ id, onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, filterClipped = true, direction = ZAXIS, }, ref) => { | ||
const store = useStore(); | ||
@@ -20,5 +21,5 @@ const objectRef = useRef(null); | ||
const pressedElementIds = useMemo(() => new Set(), []); | ||
const properties = useMemo(() => ({ customIsDrag, filterClipped }), []); | ||
properties.customIsDrag = customIsDrag; | ||
const properties = useMemo(() => ({ filterClipped, direction }), []); | ||
properties.filterClipped = filterClipped; | ||
properties.direction = direction; | ||
const translator = useMemo(() => new EventTranslator(id, false, dispatcher, (events, capturedEvents) => { | ||
@@ -32,8 +33,6 @@ if (objectRef.current == null) { | ||
? //no events captured -> compute intersections normally | ||
intersectRayFromObject(worldPositionHelper, worldRotationHelper, store.getState().scene, dispatcher, properties.filterClipped) | ||
intersectRayFromObject(worldPositionHelper, worldRotationHelper, store.getState().scene, dispatcher, properties.filterClipped, properties.direction) | ||
: //events captured | ||
intersectRayFromCapturedEvents(worldPositionHelper, worldRotationHelper, capturedEvents); | ||
}, () => pressedElementIds, (i1, i2) => properties.customIsDrag == null | ||
? isDragDefault(store.getState().camera, i1, i2) | ||
: properties.customIsDrag(i1, i2), (position, rotation) => { | ||
intersectRayFromCapturedEvents(worldPositionHelper, worldRotationHelper, capturedEvents, properties.direction); | ||
}, () => pressedElementIds, (position, rotation) => { | ||
if (objectRef.current == null) { | ||
@@ -40,0 +39,0 @@ return; |
import { ThreeEvent } from "@react-three/fiber"; | ||
import { XIntersection } from "../index.js"; | ||
import { Event } from "three"; | ||
import { XCameraRayIntersection } from "../intersections/ray.js"; | ||
export declare function XWebPointers({ onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, isDrag: customIsDrag, filterClipped, }: { | ||
export declare function XWebPointers({ filterClipped, filterIntersections, onClickMissed, onIntersections, onPointerDownMissed, onPointerUpMissed, }: { | ||
onIntersections?: (id: number, intersections: ReadonlyArray<XCameraRayIntersection>) => void; | ||
filterIntersections?: (intersections: Array<XCameraRayIntersection>) => Array<XCameraRayIntersection>; | ||
filterIntersections?: (id: number, intersections: Array<XCameraRayIntersection>) => Array<XCameraRayIntersection>; | ||
onPointerDownMissed?: (event: ThreeEvent<Event>) => void; | ||
onPointerUpMissed?: (event: ThreeEvent<Event>) => void; | ||
onClickMissed?: (event: ThreeEvent<Event>) => void; | ||
isDrag?: (i1: XIntersection, i2: XIntersection) => boolean; | ||
filterClipped?: boolean; | ||
}): null; |
import { useStore, useThree } from "@react-three/fiber"; | ||
import { Vector2 } from "three"; | ||
import { intersectRayFromCamera, intersectRayFromCameraCapturedEvents, } from "../intersections/ray.js"; | ||
import { useForwardEvents } from "./forward-events.js"; | ||
import { useEffect, useMemo } from "react"; | ||
import { EventTranslator, isDragDefault } from "../index.js"; | ||
import { R3FEventDispatcher } from "./index.js"; | ||
import { Vector2, Vector3, Quaternion } from "three"; | ||
import { intersectRayFromCamera, intersectRayFromCameraCapturedEvents, } from "../intersections/ray.js"; | ||
export function XWebPointers({ onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, isDrag: customIsDrag, filterClipped = true, }) { | ||
const pointerMap = useMemo(() => new Map(), []); | ||
const emptyIntersection = []; | ||
export function XWebPointers({ filterClipped, filterIntersections, onClickMissed, onIntersections, onPointerDownMissed, onPointerUpMissed, }) { | ||
const store = useStore(); | ||
const dispatcher = useMemo(() => new R3FEventDispatcher(), []); | ||
dispatcher.onPointerDownMissed = onPointerDownMissed; | ||
dispatcher.onPointerUpMissed = onPointerUpMissed; | ||
dispatcher.onClickMissed = onClickMissed; | ||
//update properties for all pointers | ||
for (const [pointerId, entry] of pointerMap) { | ||
entry.translator.onIntersections = onIntersections?.bind(null, pointerId); | ||
entry.translator.filterIntersections = filterIntersections; | ||
entry.customIsDrag = customIsDrag; | ||
entry.filterClipped = filterClipped; | ||
} | ||
const canvas = useThree(({ gl }) => gl.domElement); | ||
const intersections = useMemo(() => computeIntersections.bind(null, store), [store]); | ||
const eventFunctions = useForwardEvents(intersections, onIntersections, filterIntersections, onPointerDownMissed, onPointerUpMissed, onClickMissed, filterClipped); | ||
useEffect(() => { | ||
const getOrCreate = (id) => getOrCreatePointerMapEntry(pointerMap, store, dispatcher, id); | ||
const pointercancel = (event) => { | ||
const { translator } = getOrCreate(event.pointerId); | ||
translator.cancel(event); | ||
}; | ||
const pointerdown = (event) => { | ||
const { pressedInputDeviceElements, translator } = getOrCreate(event.pointerId); | ||
updatePressedButtons(event.buttons, pressedInputDeviceElements); | ||
translator.update(event, false, true, event.button); | ||
}; | ||
const pointerup = (event) => { | ||
const { pressedInputDeviceElements, translator } = getOrCreate(event.pointerId); | ||
updatePressedButtons(event.buttons, pressedInputDeviceElements); | ||
translator.update(event, false, true); | ||
}; | ||
const pointerover = (event) => { | ||
const { translator, pressedInputDeviceElements } = getOrCreate(event.pointerId); | ||
updatePressedButtons(event.buttons, pressedInputDeviceElements); | ||
translator.update(event, true, true, event.button); | ||
}; | ||
const pointermove = (event) => { | ||
const { translator } = getOrCreate(event.pointerId); | ||
translator.update(event, true, false); | ||
}; | ||
const wheel = (event) => { | ||
for (const { translator } of pointerMap.values()) { | ||
translator.wheel(event); | ||
} | ||
}; | ||
const pointerout = (event) => { | ||
const { translator } = getOrCreate(event.pointerId); | ||
translator.leave(event); | ||
pointerMap.delete(event.pointerId); | ||
}; | ||
const blur = (event) => { | ||
for (const { translator } of pointerMap.values()) { | ||
translator.leave(event); | ||
} | ||
pointerMap.clear(); | ||
}; | ||
canvas.addEventListener("pointercancel", pointercancel); | ||
canvas.addEventListener("pointerdown", pointerdown); | ||
canvas.addEventListener("pointerup", pointerup); | ||
canvas.addEventListener("pointerover", pointerover); | ||
canvas.addEventListener("pointerout", pointerout); | ||
canvas.addEventListener("pointermove", pointermove); | ||
canvas.addEventListener("wheel", wheel); | ||
canvas.addEventListener("blur", blur); | ||
const pointerCancel = (e) => eventFunctions.cancel(e.pointerId, e); | ||
const pointerDown = (e) => eventFunctions.press(e.pointerId, e, e.button); | ||
const pointerUp = (e) => eventFunctions.release(e.pointerId, e, e.button); | ||
const pointerOver = (e) => eventFunctions.enter(e.pointerId, e); | ||
const pointerOut = (e) => eventFunctions.leave(e.pointerId, e); | ||
const pointerMove = (e) => eventFunctions.move(e.pointerId, e); | ||
canvas.addEventListener("pointercancel", pointerCancel); | ||
canvas.addEventListener("pointerdown", pointerDown); | ||
canvas.addEventListener("pointerup", pointerUp); | ||
canvas.addEventListener("pointerover", pointerOver); | ||
canvas.addEventListener("pointerout", pointerOut); | ||
canvas.addEventListener("pointermove", pointerMove); | ||
canvas.addEventListener("wheel", eventFunctions.wheel); | ||
canvas.addEventListener("blur", eventFunctions.blur); | ||
return () => { | ||
canvas.removeEventListener("pointercancel", pointercancel); | ||
canvas.removeEventListener("pointerdown", pointerdown); | ||
canvas.removeEventListener("pointerup", pointerup); | ||
canvas.removeEventListener("pointerover", pointerover); | ||
canvas.removeEventListener("pointerout", pointerout); | ||
canvas.removeEventListener("pointermove", pointermove); | ||
canvas.removeEventListener("wheel", wheel); | ||
canvas.removeEventListener("blur", blur); | ||
canvas.removeEventListener("pointercancel", pointerCancel); | ||
canvas.removeEventListener("pointerdown", pointerDown); | ||
canvas.removeEventListener("pointerup", pointerUp); | ||
canvas.removeEventListener("pointerover", pointerOver); | ||
canvas.removeEventListener("pointerout", pointerOut); | ||
canvas.removeEventListener("pointermove", pointerMove); | ||
canvas.removeEventListener("wheel", eventFunctions.wheel); | ||
canvas.removeEventListener("blur", eventFunctions.blur); | ||
}; | ||
}, [canvas, store]); | ||
}, [canvas, eventFunctions]); | ||
return null; | ||
} | ||
function updatePressedButtons(buttons, pressedInputDeviceElements) { | ||
let value = 1; | ||
//5 buttons can be expected https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons | ||
for (let i = 0; i < 5; i++) { | ||
const inputDeviceElementActive = (value & buttons) > 0; | ||
if (inputDeviceElementActive) { | ||
pressedInputDeviceElements.add(i); | ||
} | ||
else { | ||
pressedInputDeviceElements.delete(i); | ||
} | ||
value *= 2; | ||
function computeIntersections(store, event, capturedEvents, filterClipped, dispatcher, targetWorldPosition, targetWorldQuaternion) { | ||
if (!(event.target instanceof HTMLCanvasElement)) { | ||
return emptyIntersection; | ||
} | ||
const { camera, scene, size } = store.getState(); | ||
const coords = new Vector2((event.offsetX / size.width) * 2 - 1, -(event.offsetY / size.height) * 2 + 1); | ||
return capturedEvents == null | ||
? intersectRayFromCamera(camera, coords, scene, dispatcher, filterClipped, targetWorldPosition, targetWorldQuaternion) | ||
: intersectRayFromCameraCapturedEvents(camera, coords, capturedEvents, targetWorldPosition, targetWorldQuaternion); | ||
} | ||
function getOrCreatePointerMapEntry(pointerMap, store, dispatcher, pointerId) { | ||
let entry = pointerMap.get(pointerId); | ||
if (entry == null) { | ||
pointerMap.set(pointerId, (entry = createPointerMapEntry(pointerId, store, dispatcher))); | ||
} | ||
return entry; | ||
} | ||
const emptyIntersection = []; | ||
function createPointerMapEntry(pointerId, store, dispatcher) { | ||
const lastWorldPosition = new Vector3(); | ||
const lastWorldRotation = new Quaternion(); | ||
const pointerMapEntry = { | ||
filterClipped: true, | ||
pressedInputDeviceElements: new Set(), | ||
translator: new EventTranslator(pointerId, false, dispatcher, (event, capturedEvents) => { | ||
if (!(event.target instanceof HTMLCanvasElement)) { | ||
return emptyIntersection; | ||
} | ||
const { camera, scene, size } = store.getState(); | ||
const coords = new Vector2((event.offsetX / size.width) * 2 - 1, -(event.offsetY / size.height) * 2 + 1); | ||
return capturedEvents == null | ||
? intersectRayFromCamera(camera, coords, scene, dispatcher, pointerMapEntry.filterClipped, lastWorldPosition, lastWorldRotation) | ||
: intersectRayFromCameraCapturedEvents(camera, coords, capturedEvents, lastWorldPosition, lastWorldRotation); | ||
}, () => pointerMapEntry.pressedInputDeviceElements, (i1, i2) => pointerMapEntry.customIsDrag == null | ||
? isDragDefault(store.getState().camera, i1, i2) | ||
: pointerMapEntry.customIsDrag(i1, i2), (position, rotation) => { | ||
position.copy(lastWorldPosition); | ||
rotation.copy(lastWorldRotation); | ||
}), | ||
}; | ||
return pointerMapEntry; | ||
} |
{ | ||
"name": "@coconut-xr/xinteraction", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"homepage": "https://coconut-xr.github.io/xinteraction", | ||
@@ -5,0 +5,0 @@ "license": "SEE LICENSE IN LICENSE", |
67993
25
1266