New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@coconut-xr/xinteraction

Package Overview
Dependencies
Maintainers
2
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@coconut-xr/xinteraction - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

31

dist/index.d.ts

@@ -0,1 +1,2 @@

import { Camera } from "@react-three/fiber";
import { Intersection, Object3D, Quaternion, Vector3 } from "three";

@@ -9,2 +10,10 @@ 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> = {

@@ -16,2 +25,3 @@ [Key in ObjectEventTypes]: (object: Object3D, intersection: I, inputDeviceElementId?: number) => void;

};
export declare const voidObject: Object3D<import("three").Event>;
export declare class EventTranslator<E = Event, I extends XIntersection = XIntersection> {

@@ -23,11 +33,11 @@ readonly inputDeviceId: number;

protected getPressedElementIds: (intersection?: I) => Iterable<number>;
protected onPressMissed?: ((event: E) => void) | undefined;
protected onReleaseMissed?: ((event: E) => void) | undefined;
protected onSelectMissed?: ((event: E) => void) | undefined;
protected isDrag: (pressIntersection: I, currentIntersection: I) => boolean;
protected getInputDeviceTransformation: (position: Vector3, rotation: Quaternion) => void;
onIntersections?: ((intersections: ReadonlyArray<I>) => void) | undefined;
filterIntersections?: ((intersections: Array<I>) => Array<I>) | undefined;
intersections: Array<I>;
private lastPositionChangeTime;
private capturedEvents;
private objectInteractionDataMap;
private voidInteractionData;
constructor(inputDeviceId: number, dispatchPressAlways: boolean, eventDispatcher: EventDispatcher<E, I>, computeIntersections: (event: E, capturedEvents?: Map<Object3D, I>) => Array<I>, getPressedElementIds: (intersection?: I) => Iterable<number>, onPressMissed?: ((event: E) => void) | undefined, onReleaseMissed?: ((event: E) => void) | undefined, onSelectMissed?: ((event: E) => 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);
/**

@@ -43,7 +53,10 @@ * called when the input device receives a press, release, or move event

leave(event: E): void;
private dispatchPressAndRelease;
private updateElementStateMap;
private checkDrag;
private dispatchPress;
private dispatchRelease;
/**
* @returns if the object was entered
*/
private dispatchEnterAndMove;
private dispatchEnterOrMove;
addEventCapture(eventObject: Object3D, intersection: I): void;

@@ -57,3 +70,3 @@ removeEventCapture(eventObject: Object3D): void;

blockFollowingIntersections(eventObject: Object3D): void;
private getInteractionData;
private getInteractionState;
}

@@ -0,6 +1,29 @@

import { Object3D, Quaternion, Vector3 } from "three";
export function isXIntersection(val) {
return "inputDevicePosition" in val;
}
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");
const emptySet = new Set();
export const voidObject = new Object3D();
export class EventTranslator {

@@ -12,5 +35,6 @@ inputDeviceId;

getPressedElementIds;
onPressMissed;
onReleaseMissed;
onSelectMissed;
isDrag;
getInputDeviceTransformation;
onIntersections;
filterIntersections;
//state

@@ -20,8 +44,4 @@ intersections = [];

capturedEvents;
objectInteractionDataMap = new Map();
voidInteractionData = {
lastPressedElementIds: emptySet,
lastPressedElementEventTimeMap: new Map(),
};
constructor(inputDeviceId, dispatchPressAlways, eventDispatcher, computeIntersections, getPressedElementIds, onPressMissed, onReleaseMissed, onSelectMissed) {
objectInteractionStateMap = new Map();
constructor(inputDeviceId, dispatchPressAlways, eventDispatcher, computeIntersections, getPressedElementIds, isDrag, getInputDeviceTransformation, onIntersections, filterIntersections) {
this.inputDeviceId = inputDeviceId;

@@ -32,5 +52,6 @@ this.dispatchPressAlways = dispatchPressAlways;

this.getPressedElementIds = getPressedElementIds;
this.onPressMissed = onPressMissed;
this.onReleaseMissed = onReleaseMissed;
this.onSelectMissed = onSelectMissed;
this.isDrag = isDrag;
this.getInputDeviceTransformation = getInputDeviceTransformation;
this.onIntersections = onIntersections;
this.filterIntersections = filterIntersections;
}

@@ -50,63 +71,34 @@ /**

this.intersections = this.computeIntersections(event, this.capturedEvents);
if (this.intersections.length > 0) {
//leave void
this.voidInteractionData.lastLeftTime = currentTime;
this.voidInteractionData.lastPressedElementIds = emptySet;
//filter insections when not events captured
if (this.capturedEvents == null && this.filterIntersections != null) {
this.intersections = this.filterIntersections(this.intersections);
}
}
//TODO: refactor (the following code is the same for "objects")
//onPressMissed, onReleaseMissed, onSelectMissed
if (pressChanged) {
const pressedElementIds = new Set(this.getPressedElementIds());
//dispatch onPressMissed if intersected with nothing
this.onIntersections?.(this.intersections);
if (this.intersections.length === 0) {
const lastPressedElementIds = new Set(this.voidInteractionData.lastPressedElementIds);
for (const pressedElementId of pressedElementIds) {
lastPressedElementIds.delete(pressedElementId);
if (dispatchPressFor.includes(pressedElementId) ||
this.dispatchPressAlways) {
this.onPressMissed?.(event);
}
}
for (const releasedElementId of lastPressedElementIds) {
this.onReleaseMissed?.(event);
const lastPressedElementEventTime = this.voidInteractionData.lastPressedElementEventTimeMap.get(releasedElementId);
if (lastPressedElementEventTime != null &&
(this.voidInteractionData.lastLeftTime == null ||
this.voidInteractionData.lastLeftTime <
lastPressedElementEventTime)) {
this.onSelectMissed?.(event);
}
}
//update lastPressedElementIds
this.voidInteractionData.lastPressedElementIds = pressedElementIds;
//update lastPressedElementTimeMap
for (const pressedElementId of pressedElementIds) {
if (dispatchPressFor.includes(pressedElementId) ||
this.dispatchPressAlways) {
this.voidInteractionData.lastPressedElementEventTimeMap.set(pressedElementId, currentTime);
}
}
this.getInputDeviceTransformation(inputSourcePositionHelper, inputSourceRotationHelper);
this.intersections = [
{
distance: Infinity,
inputDevicePosition: inputSourcePositionHelper.clone(),
inputDeviceRotation: inputSourceRotationHelper.clone(),
object: voidObject,
point: inputSourcePositionHelper.clone(),
},
];
}
}
//enter, move, press, release, click, losteventcapture events
this.traverseIntersections(this.intersections, (eventObject, interactionData, intersection, intersectionIndex, pressedElementIds) => {
this.traverseIntersections(this.intersections, (eventObject, interactionState, intersection, intersectionIndex, pressedElementIds) => {
if (positionChanged) {
this.dispatchEnterAndMove(eventObject, interactionData, intersection);
this.dispatchEnterOrMove(eventObject, interactionState, intersection);
//update last intersection time
interactionData.lastIntersectedTime = currentTime;
interactionState.lastIntersectedTime = currentTime;
}
if (pressChanged) {
this.dispatchPressAndRelease(eventObject, interactionData, intersection, pressedElementIds, dispatchPressFor);
//update lastPressedElementIds
interactionData.lastPressedElementIds = pressedElementIds;
//update lastPressedElementTimeMap
for (const pressedElementId of pressedElementIds) {
if (dispatchPressFor.includes(pressedElementId) ||
this.dispatchPressAlways) {
interactionData.lastPressedElementEventTimeMap.set(pressedElementId, currentTime);
}
}
this.dispatchPress(eventObject, intersection, pressedElementIds, dispatchPressFor);
this.dispatchRelease(eventObject, intersection, interactionState, pressedElementIds, currentTime);
this.updateElementStateMap(intersection, interactionState, pressedElementIds, dispatchPressFor, currentTime);
}
if (interactionData.blockFollowingIntersections) {
interactionState.lastPressedElementIds = pressedElementIds;
if (interactionState.blockFollowingIntersections) {
//we remove the intersections that happen after

@@ -118,11 +110,13 @@ this.intersections.length = intersectionIndex + 1;

if (positionChanged) {
const pressedElementIds = new Set(this.getPressedElementIds());
//leave events
this.traverseIntersections(prevIntersections, (eventObject, interactionData, intersection) => {
if (interactionData.lastIntersectedTime === currentTime) {
this.traverseIntersections(prevIntersections, (eventObject, interactionState, intersection) => {
if (interactionState.lastIntersectedTime === currentTime) {
//object was intersected this time –> therefore also all the ancestors –> can stop bubbeling up here
return false;
}
this.dispatchRelease(eventObject, intersection, interactionState, pressedElementIds, currentTime);
this.eventDispatcher.leave(eventObject, intersection);
interactionData.lastLeftTime = currentTime;
interactionData.lastPressedElementIds = emptySet;
interactionState.lastLeftTime = currentTime;
interactionState.lastPressedElementIds = emptySet;
return true;

@@ -135,3 +129,3 @@ });

this.eventDispatcher.bind(event, this);
this.traverseIntersections(this.intersections, (eventObject, interactionData, intersection) => {
this.traverseIntersections(this.intersections, (eventObject, interactionState, intersection) => {
this.eventDispatcher.cancel(eventObject, intersection);

@@ -143,3 +137,3 @@ return true;

this.eventDispatcher.bind(event, this);
this.traverseIntersections(this.intersections, (eventObject, interactionData, intersection) => {
this.traverseIntersections(this.intersections, (eventObject, interactionState, intersection) => {
this.eventDispatcher.wheel(eventObject, intersection);

@@ -151,3 +145,3 @@ return true;

this.eventDispatcher.bind(event, this);
this.traverseIntersections(this.intersections, (eventObject, interactionData, intersection) => {
this.traverseIntersections(this.intersections, (eventObject, interactionState, intersection) => {
this.eventDispatcher.leave(eventObject, intersection);

@@ -161,10 +155,25 @@ return true;

}
dispatchPressAndRelease(eventObject, interactionData, intersection, pressedElementIds, dispatchPressFor) {
const lastPressedElementIds = new Set(interactionData.lastPressedElementIds);
updateElementStateMap(intersection, interactionState, pressedElementIds, dispatchPressFor, currentTime) {
for (const pressedElementId of pressedElementIds) {
if (lastPressedElementIds.delete(pressedElementId)) {
//was pressed last time
continue;
if (dispatchPressFor.includes(pressedElementId) ||
this.dispatchPressAlways) {
interactionState.elementStateMap.set(pressedElementId, {
lastPressEventTime: currentTime,
lastPressEventIntersection: intersection,
});
}
//pressedElementId was not pressed last time
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) {
for (const pressedElementId of pressedElementIds) {
if (this.dispatchPressAlways ||

@@ -175,13 +184,21 @@ dispatchPressFor.includes(pressedElementId)) {

}
for (const releasedElementId of lastPressedElementIds) {
}
dispatchRelease(eventObject, intersection, interactionState, pressedElementIds, currentTime) {
for (const releasedElementId of interactionState.lastPressedElementIds) {
if (pressedElementIds.has(releasedElementId)) {
continue;
}
this.checkDrag(intersection, interactionState, releasedElementId, currentTime);
//pressedElementId was not pressed this time
this.eventDispatcher.release(eventObject, intersection, releasedElementId);
const lastPressedElementEventTime = interactionData.lastPressedElementEventTimeMap.get(releasedElementId);
if (lastPressedElementEventTime != null &&
(interactionData.lastLeftTime == null ||
interactionData.lastLeftTime < lastPressedElementEventTime)) {
//the object wasn't left since it was pressed last
this.removeEventCapture(eventObject);
const elementState = interactionState.elementStateMap.get(releasedElementId);
if (elementState != null &&
(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
this.eventDispatcher.select(eventObject, intersection, releasedElementId);
}
this.removeEventCapture(eventObject);
}

@@ -192,5 +209,5 @@ }

*/
dispatchEnterAndMove(eventObject, interactionData, intersection) {
if (interactionData.lastIntersectedTime != null &&
interactionData.lastIntersectedTime === this.lastPositionChangeTime) {
dispatchEnterOrMove(eventObject, interactionState, intersection) {
if (interactionState.lastIntersectedTime != null &&
interactionState.lastIntersectedTime === this.lastPositionChangeTime) {
//object was intersected last time

@@ -201,3 +218,3 @@ this.eventDispatcher.move(eventObject, intersection);

//reset to not block the following intersections
interactionData.blockFollowingIntersections = false;
interactionState.blockFollowingIntersections = false;
//object was not intersected last time

@@ -242,4 +259,4 @@ this.eventDispatcher.enter(eventObject, intersection);

if (this.eventDispatcher.hasEventHandlers(eventObject)) {
const interactionData = this.getInteractionData(eventObject);
const continueUpwards = callback(eventObject, interactionData, intersection, intersectionIndex, info);
const interactionState = this.getInteractionState(eventObject);
const continueUpwards = callback(eventObject, interactionState, intersection, intersectionIndex, info);
if (!continueUpwards) {

@@ -254,10 +271,10 @@ continue outer;

blockFollowingIntersections(eventObject) {
const interactionData = this.getInteractionData(eventObject);
interactionData.blockFollowingIntersections = true;
const interactionState = this.getInteractionState(eventObject);
interactionState.blockFollowingIntersections = true;
}
getInteractionData(eventObject) {
let data = this.objectInteractionDataMap.get(eventObject);
if (data == null) {
this.objectInteractionDataMap.set(eventObject, (data = {
lastPressedElementEventTimeMap: new Map(),
getInteractionState(eventObject) {
let interactionState = this.objectInteractionStateMap.get(eventObject);
if (interactionState == null) {
this.objectInteractionStateMap.set(eventObject, (interactionState = {
elementStateMap: new Map(),
lastPressedElementIds: emptySet,

@@ -267,3 +284,3 @@ blockFollowingIntersections: false,

}
return data;
return interactionState;
}

@@ -270,0 +287,0 @@ }

@@ -1,2 +0,3 @@

import { Object3D } from "three";
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;

@@ -0,1 +1,2 @@

import { Mesh } from "three";
export function traverseUntilInteractable(object, isInteractable, callback, reduce, initial) {

@@ -11,1 +12,14 @@ if (isInteractable(object)) {

}
export function isIntersectionNotClipped(intersection) {
if (!(intersection.object instanceof Mesh) ||
intersection.object.material.clippingPlanes == null) {
return true;
}
const planes = intersection.object.material.clippingPlanes;
for (const plane of planes) {
if (plane.distanceToPoint(intersection.point) < 0) {
return false;
}
}
return true;
}

@@ -8,2 +8,2 @@ import { Object3D, Quaternion, Vector3 } from "three";

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>, filterIntersections?: (intersections: Array<XLinesIntersection>) => Array<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>;
import { Line3, Raycaster, Vector3 } from "three";
import { traverseUntilInteractable } from "./index.js";
import { isIntersectionNotClipped, traverseUntilInteractable, } from "./index.js";
const raycaster = new Raycaster();

@@ -22,3 +22,3 @@ const directionHelper = new Vector3();

}
export function intersectLinesFromObject(from, fromPosition, fromRotation, linePoints, on, dispatcher, filterIntersections) {
export function intersectLinesFromObject(from, fromPosition, fromRotation, linePoints, on, dispatcher, filterClipped) {
let intersections = traverseUntilInteractable(on, dispatcher.hasEventHandlers.bind(dispatcher), (object) => {

@@ -58,5 +58,7 @@ const intersections = [];

}, (prev, cur) => prev.concat(cur), []);
intersections = filterIntersections?.(intersections) ?? intersections;
if (filterClipped) {
intersections = intersections.filter(isIntersectionNotClipped);
}
//sort smallest distance first
return intersections.sort((a, b) => a.distance - b.distance);
}

@@ -7,4 +7,4 @@ import { Camera, Object3D, Quaternion, Vector2, Vector3 } from "three";

export declare function intersectRayFromCapturedEvents(fromPosition: Vector3, fromRotation: Quaternion, capturedEvents: Map<Object3D, XIntersection>): Array<XIntersection>;
export declare function intersectRayFromCameraCapturedEvents(camera: Camera, coords: Vector2, capturedEvents: Map<Object3D, XCameraRayIntersection>): Array<XCameraRayIntersection>;
export declare function intersectRayFromObject(fromPosition: Vector3, fromRotation: Quaternion, on: Object3D, dispatcher: EventDispatcher<Event, XIntersection>, filterIntersections?: (intersections: Array<XIntersection>) => Array<XIntersection>): Array<XIntersection>;
export declare function intersectRayFromCamera(from: Camera, coords: Vector2, on: Object3D, dispatcher: EventDispatcher<Event, XCameraRayIntersection>, filterIntersections?: (intersections: Array<XCameraRayIntersection>) => Array<XCameraRayIntersection>): Array<XCameraRayIntersection>;
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>;

@@ -1,3 +0,3 @@

import { Plane, Quaternion, Raycaster, Vector3, } from "three";
import { traverseUntilInteractable } from "./index.js";
import { Plane, Raycaster, Vector3, } from "three";
import { isIntersectionNotClipped, traverseUntilInteractable, } from "./index.js";
const raycaster = new Raycaster();

@@ -21,5 +21,6 @@ const directionHelper = new Vector3();

}
export function intersectRayFromCameraCapturedEvents(camera, coords, capturedEvents) {
export function intersectRayFromCameraCapturedEvents(camera, coords, capturedEvents, worldPositionTarget, worldQuaternionTarget) {
raycaster.setFromCamera(coords, camera);
rayQuaternion.setFromUnitVectors(ZAXIS, raycaster.ray.direction);
worldPositionTarget.copy(raycaster.ray.origin);
worldQuaternionTarget.setFromUnitVectors(ZAXIS, raycaster.ray.direction);
camera.getWorldDirection(directionHelper);

@@ -36,4 +37,4 @@ return Array.from(capturedEvents).map(([capturedObject, intersection]) => {

point,
inputDevicePosition: raycaster.ray.origin.clone(),
inputDeviceRotation: rayQuaternion.clone(),
inputDevicePosition: worldPositionTarget.clone(),
inputDeviceRotation: worldQuaternionTarget.clone(),
capturedObject,

@@ -43,3 +44,3 @@ };

}
export function intersectRayFromObject(fromPosition, fromRotation, on, dispatcher, filterIntersections) {
export function intersectRayFromObject(fromPosition, fromRotation, on, dispatcher, filterClipped) {
raycaster.ray.origin.copy(fromPosition);

@@ -51,20 +52,24 @@ raycaster.ray.direction.set(0, 0, 1).applyQuaternion(fromRotation);

})), (prev, cur) => prev.concat(cur), []);
intersections = filterIntersections?.(intersections) ?? intersections;
if (filterClipped) {
intersections = intersections.filter(isIntersectionNotClipped);
}
//sort smallest distance first
return intersections.sort((a, b) => a.distance - b.distance);
}
const rayQuaternion = new Quaternion();
const ZAXIS = new Vector3();
export function intersectRayFromCamera(from, coords, on, dispatcher, filterIntersections) {
export function intersectRayFromCamera(from, coords, on, dispatcher, filterClipped, worldPositionTarget, worldQuaternionTarget) {
raycaster.setFromCamera(coords, from);
rayQuaternion.setFromUnitVectors(ZAXIS, raycaster.ray.direction);
worldPositionTarget.copy(raycaster.ray.origin);
worldQuaternionTarget.setFromUnitVectors(ZAXIS, raycaster.ray.direction);
planeHelper.setFromNormalAndCoplanarPoint(from.getWorldDirection(directionHelper), raycaster.ray.origin);
let intersections = traverseUntilInteractable(on, dispatcher.hasEventHandlers.bind(dispatcher), (object) => raycaster.intersectObject(object, true).map((intersection) => Object.assign(intersection, {
inputDevicePosition: raycaster.ray.origin.clone(),
inputDeviceRotation: rayQuaternion.clone(),
inputDevicePosition: worldPositionTarget.clone(),
inputDeviceRotation: worldQuaternionTarget.clone(),
distanceViewPlane: planeHelper.distanceToPoint(intersection.point),
})), (prev, cur) => prev.concat(cur), []);
intersections = filterIntersections?.(intersections) ?? intersections;
if (filterClipped) {
intersections = intersections.filter(isIntersectionNotClipped);
}
//sort smallest distance first
return intersections.sort((a, b) => a.distance - b.distance);
}
import { Object3D, Vector3, Quaternion } from "three";
import { EventDispatcher, XIntersection } from "../index.js";
export declare function intersectSphereFromCapturedEvents(fromPosition: Vector3, fromRotation: Quaternion, capturedEvents: Map<Object3D, XIntersection>): Array<XIntersection>;
export declare function intersectSphereFromObject(fromPosition: Vector3, fromQuaternion: Quaternion, radius: number, on: Object3D, dispatcher: EventDispatcher<Event, XIntersection>, filterIntersections?: (intersections: Array<XIntersection>) => Array<XIntersection>): Array<XIntersection>;
export type XSphereIntersection = XIntersection & {
/**
* set when the event is captured because the "distance" property is only the distance to a "expected intersection"
*/
actualDistance?: number;
};
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>;
import { InstancedMesh, Matrix4, Mesh, Vector3, Sphere, Quaternion, } from "three";
import { traverseUntilInteractable } from "./index.js";
import { isIntersectionNotClipped, traverseUntilInteractable, } from "./index.js";
const oldInputDevicePointOffset = new Vector3();

@@ -30,11 +30,41 @@ const inputDeviceQuaternionOffset = new Quaternion();

capturedObject,
actualDistance: computeActualDistance(fromPosition, intersection),
};
});
}
function computeActualDistance(fromPosition, intersection) {
const object = intersection.object;
if (intersection.instanceId != null && object instanceof InstancedMesh) {
if (object.geometry.boundingBox == null) {
object.geometry.computeBoundingBox();
}
object.getMatrixAt(intersection.instanceId, matrixHelper);
matrixHelper.premultiply(object.matrixWorld);
invertedMatrixHelper.copy(matrixHelper).invert();
vectorHelper.copy(fromPosition).applyMatrix4(invertedMatrixHelper);
object.geometry.boundingBox.clampPoint(vectorHelper, vectorHelper);
vectorHelper.applyMatrix4(matrixHelper);
return vectorHelper.distanceTo(fromPosition);
}
if (object instanceof Mesh) {
if (object.geometry.boundingBox == null) {
object.geometry.computeBoundingBox();
}
invertedMatrixHelper.copy(object.matrixWorld).invert();
vectorHelper.copy(fromPosition).applyMatrix4(invertedMatrixHelper);
object.geometry.boundingBox.clampPoint(vectorHelper, vectorHelper);
vectorHelper.applyMatrix4(object.matrixWorld);
return vectorHelper.distanceTo(fromPosition);
}
//not lösung - emergency solution
return object.getWorldPosition(vectorHelper).distanceTo(fromPosition);
}
const collisionSphere = new Sphere();
export function intersectSphereFromObject(fromPosition, fromQuaternion, radius, on, dispatcher, filterIntersections) {
export function intersectSphereFromObject(fromPosition, fromQuaternion, radius, on, dispatcher, filterClipped) {
collisionSphere.center.copy(fromPosition);
collisionSphere.radius = radius;
let intersections = traverseUntilInteractable(on, dispatcher.hasEventHandlers.bind(dispatcher), (object) => intersectSphereRecursive(object, fromQuaternion), (prev, cur) => prev.concat(cur), []);
intersections = filterIntersections?.(intersections) ?? intersections;
if (filterClipped) {
intersections = intersections.filter(isIntersectionNotClipped);
}
//sort smallest distance first

@@ -82,8 +112,7 @@ return intersections.sort((a, b) => a.distance - b.distance);

object.getMatrixAt(i, matrixHelper);
invertedMatrixHelper.copy(matrixHelper);
invertedMatrixHelper.premultiply(object.matrixWorld);
if (!intersectSphereSphere(invertedMatrixHelper, object.geometry)) {
matrixHelper.premultiply(object.matrixWorld);
if (!intersectSphereSphere(matrixHelper, object.geometry)) {
continue;
}
invertedMatrixHelper.invert();
invertedMatrixHelper.copy(matrixHelper).invert();
const intersection = intersectSphereBox(object, collisionSphere.center, inputDeviceRotation, matrixHelper, invertedMatrixHelper, object.geometry, i);

@@ -90,0 +119,0 @@ if (intersection != null) {

import React from "react";
import { Vector3 } from "three";
import { Vector3, Event } from "three";
import { XIntersection } from "../index.js";
import { XLinesIntersection } from "../intersections/lines.js";
import { InputDeviceFunctions } from "./index.js";
import { ThreeEvent } from "@react-three/fiber";
export declare const XCurvedPointer: React.ForwardRefExoticComponent<{

@@ -10,2 +12,7 @@ id: number;

filterIntersections?: ((intersections: Array<XLinesIntersection>) => Array<XLinesIntersection>) | 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>>;
/* eslint-disable react/display-name */
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, } from "react";
import { Quaternion, Vector3 } from "three";
import { EventTranslator } from "../index.js";
import { EventTranslator, isDragDefault } from "../index.js";
import { intersectLinesFromCapturedEvents, intersectLinesFromObject, } from "../intersections/lines.js";
import { R3FEventDispatcher } from "./index.js";
import { useFrame, useThree } from "@react-three/fiber";
import { useFrame, useStore } from "@react-three/fiber";
const emptyIntersections = [];
const worldPositionHelper = new Vector3();
const worldRotationHelper = new Quaternion();
export const XCurvedPointer = forwardRef(({ id, points, onIntersections, filterIntersections }, ref) => {
export const XCurvedPointer = forwardRef(({ id, points, onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, isDrag: customIsDrag, filterClipped = true, }, ref) => {
const objectRef = useRef(null);
const scene = useThree(({ scene }) => scene);
const store = useStore();
const dispatcher = useMemo(() => new R3FEventDispatcher(), []);
dispatcher.onPointerDownMissed = onPointerDownMissed;
dispatcher.onPointerUpMissed = onPointerUpMissed;
dispatcher.onClickMissed = onClickMissed;
const pressedElementIds = useMemo(() => new Set(), []);
const translator = useMemo(() => {
const dispatcher = new R3FEventDispatcher();
return new EventTranslator(id, false, dispatcher, (_, capturedEvents) => {
if (objectRef.current == null) {
return emptyIntersections;
}
objectRef.current.getWorldPosition(worldPositionHelper);
objectRef.current.getWorldQuaternion(worldRotationHelper);
if (capturedEvents == null) {
return intersectLinesFromObject(objectRef.current, worldPositionHelper, worldRotationHelper, points, scene, dispatcher, filterIntersections);
}
return intersectLinesFromCapturedEvents(objectRef.current, worldPositionHelper, worldRotationHelper, points, capturedEvents);
}, () => pressedElementIds);
}, [id, filterIntersections, points, scene]);
const properties = useMemo(() => ({ points, customIsDrag, filterClipped }), []);
properties.points = points;
properties.customIsDrag = customIsDrag;
properties.filterClipped = filterClipped;
const translator = useMemo(() => new EventTranslator(id, false, dispatcher, (_, capturedEvents) => {
if (objectRef.current == null) {
return emptyIntersections;
}
objectRef.current.getWorldPosition(worldPositionHelper);
objectRef.current.getWorldQuaternion(worldRotationHelper);
return capturedEvents == null
? //events not captured -> compute normally
intersectLinesFromObject(objectRef.current, worldPositionHelper, worldRotationHelper, properties.points, store.getState().scene, dispatcher, properties.filterClipped)
: 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) => {
if (objectRef.current == null) {
return;
}
objectRef.current.getWorldPosition(position);
objectRef.current.getWorldQuaternion(rotation);
}), [id, store]);
translator.onIntersections = onIntersections;
translator.filterIntersections = filterIntersections;
useImperativeHandle(ref, () => ({

@@ -44,9 +59,8 @@ press: (id, event) => {

//cleanup translator
useEffect(() => () => translator.leave({}), [translator]);
useEffect(() => translator.leave.bind(translator, {}), [translator]);
//update translator every frame
useFrame(() => {
translator.update({}, true, false);
onIntersections?.(translator.intersections);
});
return React.createElement("object3D", { ref: objectRef });
});
import { Object3D, Event } from "three";
import { EventDispatcher, EventTranslator, XIntersection } from "../index.js";
import { ThreeEvent } from "@react-three/fiber";
import type { EventManager } from "@react-three/fiber/dist/declarations/src/core/events.js";
export declare const noEvents: () => EventManager<HTMLElement>;
export declare class R3FEventDispatcher<I extends XIntersection> implements EventDispatcher<Event, I> {
onPointerDownMissed?: ((event: ThreeEvent<Event>) => void) | undefined;
onPointerUpMissed?: ((event: ThreeEvent<Event>) => void) | undefined;
onClickMissed?: ((event: ThreeEvent<Event>) => void) | undefined;
private stoppedEventTypeSet;
private event;
private translator;
constructor(onPointerDownMissed?: ((event: ThreeEvent<Event>) => void) | undefined, onPointerUpMissed?: ((event: ThreeEvent<Event>) => void) | undefined, onClickMissed?: ((event: ThreeEvent<Event>) => void) | undefined);
press: (eventObject: Object3D<Event>, intersection: I, inputDeviceElementId?: number | undefined) => void;

@@ -8,0 +15,0 @@ release: (eventObject: Object3D<Event>, intersection: I, inputDeviceElementId?: number | undefined) => void;

@@ -0,5 +1,18 @@

import { voidObject, } from "../index.js";
export const noEvents = () => ({
enabled: false,
priority: 0,
});
export class R3FEventDispatcher {
onPointerDownMissed;
onPointerUpMissed;
onClickMissed;
stoppedEventTypeSet;
event;
translator;
constructor(onPointerDownMissed, onPointerUpMissed, onClickMissed) {
this.onPointerDownMissed = onPointerDownMissed;
this.onPointerUpMissed = onPointerUpMissed;
this.onClickMissed = onClickMissed;
}
press = this.dispatch.bind(this, ["onPointerDown"]);

@@ -21,2 +34,16 @@ release = this.dispatch.bind(this, ["onPointerUp"]);

}
if (eventObject == voidObject) {
switch (name) {
case "onClick":
case "onPointerDown":
case "onPointerUp": {
const handler = this[`${name}Missed`];
if (handler == null) {
return;
}
handler(this.createEvent(name, eventObject, intersection, inputDeviceElementId));
}
}
return;
}
const instance = eventObject.__r3f;

@@ -75,2 +102,5 @@ instance.handlers[name]?.(this.createEvent(name, eventObject, intersection, inputDeviceElementId));

hasEventHandlers(object) {
if (object === voidObject) {
return true;
}
const instance = object.__r3f;

@@ -77,0 +107,0 @@ return instance != null && instance.eventCount > 0;

@@ -0,2 +1,4 @@

import { ThreeEvent } from "@react-three/fiber";
import React from "react";
import { Event } from "three";
import { XIntersection } from "../index.js";

@@ -11,4 +13,9 @@ import { InputDeviceFunctions } from "./index.js";

} | undefined;
onIntersections?: ((intersections: Array<XIntersection>) => void) | undefined;
onIntersections?: ((intersections: ReadonlyArray<XIntersection>) => void) | undefined;
filterIntersections?: ((intersections: Array<XIntersection>) => Array<XIntersection>) | 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>>;
/* eslint-disable react/display-name */
import { useThree, useFrame } from "@react-three/fiber";
import { useFrame, useStore } from "@react-three/fiber";
import React, { useRef, useMemo, useEffect, forwardRef, useImperativeHandle, } from "react";
import { Quaternion, Vector3 } from "three";
import { EventTranslator } from "../index.js";
import { EventTranslator, isDragDefault } from "../index.js";
import { R3FEventDispatcher } from "./index.js";

@@ -11,32 +11,52 @@ import { intersectSphereFromCapturedEvents, intersectSphereFromObject, } from "../intersections/sphere.js";

const worldRotationHelper = new Quaternion();
export const XSphereCollider = forwardRef(({ id, distanceElement, radius, onIntersections, filterIntersections }, ref) => {
export const XSphereCollider = forwardRef(({ id, distanceElement, radius, onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, isDrag: customIsDrag, filterClipped = true, }, ref) => {
const objectRef = useRef(null);
const scene = useThree(({ scene }) => scene);
const store = useStore();
const pressedElementIds = useMemo(() => new Set(), []);
const translator = useMemo(() => {
const dispatcher = new R3FEventDispatcher();
return new EventTranslator(id, true, dispatcher, (_, capturedEvents) => {
if (objectRef.current == null) {
return emptyIntersections;
}
objectRef.current.getWorldPosition(worldPositionHelper);
objectRef.current.getWorldQuaternion(worldRotationHelper);
if (capturedEvents == null) {
//events not captured -> compute intersections normally
return intersectSphereFromObject(worldPositionHelper, worldRotationHelper, radius, scene, dispatcher, filterIntersections);
}
return intersectSphereFromCapturedEvents(worldPositionHelper, worldRotationHelper, capturedEvents);
}, (intersection) => {
if (distanceElement == null || intersection == null) {
return pressedElementIds;
}
if (intersection.distance <= distanceElement.downRadius) {
pressedElementIds.add(distanceElement.id);
}
else {
pressedElementIds.delete(distanceElement.id);
}
const dispatcher = useMemo(() => new R3FEventDispatcher(), []);
dispatcher.onPointerDownMissed = onPointerDownMissed;
dispatcher.onPointerUpMissed = onPointerUpMissed;
dispatcher.onClickMissed = onClickMissed;
const properties = useMemo(() => ({ distanceElement, radius, customIsDrag, filterClipped }), []);
properties.distanceElement = distanceElement;
properties.radius = radius;
properties.customIsDrag = customIsDrag;
properties.filterClipped = filterClipped;
const translator = useMemo(() => new EventTranslator(id, true, dispatcher, (_, capturedEvents) => {
if (objectRef.current == null) {
return emptyIntersections;
}
objectRef.current.getWorldPosition(worldPositionHelper);
objectRef.current.getWorldQuaternion(worldRotationHelper);
return capturedEvents == null
? //events not captured -> compute intersections normally
intersectSphereFromObject(worldPositionHelper, worldRotationHelper, properties.radius, store.getState().scene, dispatcher, properties.filterClipped)
: //event captured
intersectSphereFromCapturedEvents(worldPositionHelper, worldRotationHelper, capturedEvents);
}, (intersection) => {
if (properties.distanceElement == null || intersection == null) {
return pressedElementIds;
});
}, [id, filterIntersections, radius, distanceElement, scene]);
}
if (intersection.distance <= properties.distanceElement.downRadius &&
// either the intersection is not captured (=> actualDistance == null) OR the actual distance to the object is smaller then 2x downRadius => if not we release the capture
(intersection.actualDistance == null ||
intersection.actualDistance <
2 * properties.distanceElement.downRadius * 2)) {
pressedElementIds.add(properties.distanceElement.id);
}
else {
pressedElementIds.delete(properties.distanceElement.id);
}
return pressedElementIds;
}, (i1, i2) => properties.customIsDrag == null
? isDragDefault(store.getState().camera, i1, i2)
: properties.customIsDrag(i1, i2), (position, rotation) => {
if (objectRef.current == null) {
return;
}
objectRef.current.getWorldPosition(position);
objectRef.current.getWorldQuaternion(rotation);
}), [id, store]);
translator.onIntersections = onIntersections;
translator.filterIntersections = filterIntersections;
useEffect(() => () => {

@@ -63,8 +83,7 @@ if (distanceElement == null) {

//cleanup translator
useEffect(() => () => translator.leave({}), [translator]);
useEffect(() => translator.leave.bind(translator, {}), [translator]);
useFrame(() => {
translator.update({}, true, distanceElement != null);
onIntersections?.(translator.intersections);
});
return React.createElement("object3D", { ref: objectRef });
});
import React from "react";
import { Event } from "three";
import { XIntersection } from "../index.js";
import { InputDeviceFunctions } from "./index.js";
import { ThreeEvent } from "@react-three/fiber";
export declare const XStraightPointer: React.ForwardRefExoticComponent<{
id: number;
onIntersections?: ((intersections: Array<XIntersection>) => void) | undefined;
onIntersections?: ((intersections: ReadonlyArray<XIntersection>) => void) | undefined;
filterIntersections?: ((intersections: Array<XIntersection>) => Array<XIntersection>) | 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>>;
/* eslint-disable react/display-name */
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, } from "react";
import { Quaternion, Vector3 } from "three";
import { EventTranslator } from "../index.js";
import { EventTranslator, isDragDefault } from "../index.js";
import { intersectRayFromCapturedEvents, intersectRayFromObject, } from "../intersections/ray.js";
import { R3FEventDispatcher } from "./index.js";
import { useFrame, useThree } from "@react-three/fiber";
import { useFrame, useStore } from "@react-three/fiber";
const emptyIntersections = [];
const worldPositionHelper = new Vector3();
const worldRotationHelper = new Quaternion();
export const XStraightPointer = forwardRef(({ id, onIntersections, filterIntersections }, ref) => {
export const XStraightPointer = forwardRef(({ id, onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, isDrag: customIsDrag, filterClipped = true, }, ref) => {
const store = useStore();
const objectRef = useRef(null);
const scene = useThree(({ scene }) => scene);
const dispatcher = useMemo(() => new R3FEventDispatcher(), []);
dispatcher.onPointerDownMissed = onPointerDownMissed;
dispatcher.onPointerUpMissed = onPointerUpMissed;
dispatcher.onClickMissed = onClickMissed;
const pressedElementIds = useMemo(() => new Set(), []);
const translator = useMemo(() => {
const dispatcher = new R3FEventDispatcher();
return new EventTranslator(id, false, dispatcher, (_, capturedEvents) => {
if (objectRef.current == null) {
return emptyIntersections;
}
objectRef.current.getWorldPosition(worldPositionHelper);
objectRef.current.getWorldQuaternion(worldRotationHelper);
if (capturedEvents != null) {
return intersectRayFromCapturedEvents(worldPositionHelper, worldRotationHelper, capturedEvents);
}
return intersectRayFromObject(worldPositionHelper, worldRotationHelper, scene, dispatcher, filterIntersections);
}, () => pressedElementIds);
}, [id, filterIntersections, scene]);
const properties = useMemo(() => ({ customIsDrag, filterClipped }), []);
properties.customIsDrag = customIsDrag;
properties.filterClipped = filterClipped;
const translator = useMemo(() => new EventTranslator(id, false, dispatcher, (events, capturedEvents) => {
if (objectRef.current == null) {
return emptyIntersections;
}
objectRef.current.getWorldPosition(worldPositionHelper);
objectRef.current.getWorldQuaternion(worldRotationHelper);
return capturedEvents == null
? //no events captured -> compute intersections normally
intersectRayFromObject(worldPositionHelper, worldRotationHelper, store.getState().scene, dispatcher, properties.filterClipped)
: //events captured
intersectRayFromCapturedEvents(worldPositionHelper, worldRotationHelper, capturedEvents);
}, () => pressedElementIds, (i1, i2) => properties.customIsDrag == null
? isDragDefault(store.getState().camera, i1, i2)
: properties.customIsDrag(i1, i2), (position, rotation) => {
if (objectRef.current == null) {
return;
}
objectRef.current.getWorldPosition(position);
objectRef.current.getWorldQuaternion(rotation);
}), [id, store]);
translator.onIntersections = onIntersections;
translator.filterIntersections = filterIntersections;
useImperativeHandle(ref, () => ({

@@ -44,9 +59,8 @@ press: (id, event) => {

//cleanup translator
useEffect(() => () => translator.leave({}), [translator]);
useEffect(() => translator.leave.bind(translator, {}), [translator]);
//update translator every frame
useFrame(() => {
translator.update({}, true, false);
onIntersections?.(translator.intersections);
});
return React.createElement("object3D", { ref: objectRef });
});

@@ -0,5 +1,13 @@

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, }: {
onIntersections?: (id: number, intersections: Array<XCameraRayIntersection>) => void;
export declare function XWebPointers({ onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, isDrag: customIsDrag, filterClipped, }: {
onIntersections?: (id: number, intersections: ReadonlyArray<XCameraRayIntersection>) => void;
filterIntersections?: (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 { useEffect, useMemo } from "react";
import { EventTranslator } from "../index.js";
import { EventTranslator, isDragDefault } from "../index.js";
import { R3FEventDispatcher } from "./index.js";
import { Vector2 } from "three";
import { Vector2, Vector3, Quaternion } from "three";
import { intersectRayFromCamera, intersectRayFromCameraCapturedEvents, } from "../intersections/ray.js";
export function XWebPointers({ onIntersections, filterIntersections, }) {
const canvas = useThree(({ gl }) => gl.domElement);
export function XWebPointers({ onIntersections, filterIntersections, onClickMissed, onPointerDownMissed, onPointerUpMissed, isDrag: customIsDrag, filterClipped = true, }) {
const pointerMap = useMemo(() => new Map(), []);
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);
useEffect(() => {
const getOrCreate = getOrCreatePointerMapEntry.bind(null, pointerMap, () => store.getState());
const getOrCreate = (id) => getOrCreatePointerMapEntry(pointerMap, store, dispatcher, id);
const pointercancel = (event) => {
const { translator } = getOrCreate(event.pointerId, filterIntersections);
const { translator } = getOrCreate(event.pointerId);
translator.cancel(event);
};
const pointerdown = (event) => {
const { pressedInputDeviceElements, translator } = getOrCreate(event.pointerId, filterIntersections);
const { pressedInputDeviceElements, translator } = getOrCreate(event.pointerId);
updatePressedButtons(event.buttons, pressedInputDeviceElements);

@@ -23,3 +34,3 @@ translator.update(event, false, true, event.button);

const pointerup = (event) => {
const { pressedInputDeviceElements, translator } = getOrCreate(event.pointerId, filterIntersections);
const { pressedInputDeviceElements, translator } = getOrCreate(event.pointerId);
updatePressedButtons(event.buttons, pressedInputDeviceElements);

@@ -29,11 +40,9 @@ translator.update(event, false, true);

const pointerover = (event) => {
const { translator, pressedInputDeviceElements } = getOrCreate(event.pointerId, filterIntersections);
const { translator, pressedInputDeviceElements } = getOrCreate(event.pointerId);
updatePressedButtons(event.buttons, pressedInputDeviceElements);
translator.update(event, true, true, event.button);
onIntersections?.(event.pointerId, translator.intersections);
};
const pointermove = (event) => {
const { translator } = getOrCreate(event.pointerId, filterIntersections);
const { translator } = getOrCreate(event.pointerId);
translator.update(event, true, false);
onIntersections?.(event.pointerId, translator.intersections);
};

@@ -46,6 +55,5 @@ const wheel = (event) => {

const pointerout = (event) => {
const { translator } = getOrCreate(event.pointerId, filterIntersections);
const { translator } = getOrCreate(event.pointerId);
translator.leave(event);
pointerMap.delete(event.pointerId);
onIntersections?.(event.pointerId, emptyIntersection);
};

@@ -76,3 +84,3 @@ const blur = (event) => {

};
}, [canvas, filterIntersections, store]);
}, [canvas, store]);
return null;

@@ -94,6 +102,6 @@ }

}
function getOrCreatePointerMapEntry(pointerMap, getState, pointerId, filterIntersections) {
function getOrCreatePointerMapEntry(pointerMap, store, dispatcher, pointerId) {
let entry = pointerMap.get(pointerId);
if (entry == null) {
pointerMap.set(pointerId, (entry = createPointerMapEntry(pointerId, getState, filterIntersections)));
pointerMap.set(pointerId, (entry = createPointerMapEntry(pointerId, store, dispatcher)));
}

@@ -103,20 +111,25 @@ return entry;

const emptyIntersection = [];
function createPointerMapEntry(pointerId, getState, filterIntersections) {
const pressedInputDeviceElements = new Set();
const dispatcher = new R3FEventDispatcher();
const translator = new EventTranslator(pointerId, false, dispatcher, (event, capturedEvents) => {
if (!(event.target instanceof HTMLCanvasElement)) {
return emptyIntersection;
}
const { camera, scene, size } = getState();
const coords = new Vector2((event.offsetX / size.width) * 2 - 1, -(event.offsetY / size.height) * 2 + 1);
if (capturedEvents == null) {
return intersectRayFromCamera(camera, coords, scene, dispatcher, filterIntersections);
}
return intersectRayFromCameraCapturedEvents(camera, coords, capturedEvents);
}, () => pressedInputDeviceElements);
return {
pressedInputDeviceElements,
translator,
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.2",
"version": "0.0.3",
"homepage": "https://coconut-xr.github.io/xinteraction",

@@ -5,0 +5,0 @@ "license": "SEE LICENSE IN LICENSE",

@@ -12,3 +12,3 @@ ![header image](./images/header.jpg)

**xinteraction** translates events from input devices (e.g. Mouse, 6DOF Controller, Hand) into events on 3D Objects in a Three.js Scene.
**xinteraction** translates events from input devices (e.g., Mouse, 6DOF Controller, Hand) into events on 3D Objects in a Three.js Scene.

@@ -28,2 +28,2 @@

* [Distance Based Input Device](https://coconut-xr.github.io/xinteraction/#/distance.md) Explains interactions like touching or grabbing
* [Event Capture](https://coconut-xr.github.io/xinteraction/#/event-capture.md) Explains event capture for dragging
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc