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

@pmndrs/pointer-events

Package Overview
Dependencies
Maintainers
0
Versions
52
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@pmndrs/pointer-events - npm Package Compare versions

Comparing version 6.2.2 to 6.2.3

dist/intersections/intersector.d.ts

27

dist/combine.d.ts
import { Object3D } from 'three';
import { NativeEvent } from './event.js';
import { Pointer } from './pointer.js';
import { Pointer, PointerCapture } from './pointer.js';
import { Intersection } from './index.js';
export declare class CombinedPointer {
private pointers;
private isDefaults;
private readonly enableMultiplePointers;
private readonly pointers;
private readonly isDefaults;
private enabled;
register(pointer: Pointer, isDefault: boolean): () => void;
private activePointer;
private readonly nonCapturedPointers;
constructor(enableMultiplePointers: boolean);
register(pointer: Pointer | CombinedPointer, isDefault?: boolean): () => void;
private unregister;
private startIntersection;
/**
* only for internal use
*/
getIntersection(): Intersection | undefined;
/**
* only for internal use
*/
getPointerCapture(): PointerCapture | undefined;
private computeActivePointer;
/**
* only for internal use
*/
commit(nativeEvent: NativeEvent, computeActivePointer?: boolean): void;
move(scene: Object3D, nativeEvent: NativeEvent): void;
setEnabled(enabled: boolean, nativeEvent: NativeEvent): void;
}

@@ -0,6 +1,13 @@

import { intersectPointerEventTargets } from './intersections/utils.js';
export class CombinedPointer {
enableMultiplePointers;
pointers = [];
isDefaults = [];
enabled = true;
register(pointer, isDefault) {
activePointer;
nonCapturedPointers = [];
constructor(enableMultiplePointers) {
this.enableMultiplePointers = enableMultiplePointers;
}
register(pointer, isDefault = false) {
this.pointers.push(pointer);

@@ -18,29 +25,94 @@ this.isDefaults.push(isDefault);

}
move(scene, nativeEvent) {
if (!this.enabled) {
return;
startIntersection(nonCapturedPointers, nativeEvent) {
const length = this.pointers.length;
for (let i = 0; i < length; i++) {
const pointer = this.pointers[i];
if (pointer instanceof CombinedPointer) {
pointer.startIntersection(nonCapturedPointers, nativeEvent);
continue;
}
const pointerCapture = pointer.getPointerCapture();
if (pointerCapture != null) {
pointer.setIntersection(pointer.intersector.intersectPointerCapture(pointerCapture, nativeEvent));
continue;
}
nonCapturedPointers.push(pointer);
pointer.intersector.startIntersection(nativeEvent);
}
}
/**
* only for internal use
*/
getIntersection() {
return this.activePointer?.getIntersection();
}
/**
* only for internal use
*/
getPointerCapture() {
return this.activePointer?.getPointerCapture();
}
computeActivePointer() {
let smallestDistance;
this.activePointer = undefined;
const length = this.pointers.length;
if (length === 0) {
return;
}
for (let i = 0; i < length; i++) {
this.pointers[i].computeMove(scene, nativeEvent);
}
let smallestIndex = 0;
let smallestDistance = this.pointers[0].getIntersection()?.distance ?? Infinity;
for (let i = 1; i < length; i++) {
const distance = this.pointers[i].getIntersection()?.distance ?? Infinity;
const pointer = this.pointers[i];
if (pointer instanceof CombinedPointer) {
pointer.computeActivePointer();
}
const intersection = pointer.getIntersection();
const distance = pointer.getPointerCapture() != null ? -Infinity : (intersection?.distance ?? Infinity);
const isDefault = this.isDefaults[i];
if ((isDefault && distance === smallestDistance) || distance < smallestDistance) {
smallestIndex = i;
if (smallestDistance == null || (isDefault && distance === smallestDistance) || distance < smallestDistance) {
this.activePointer = pointer;
smallestDistance = distance;
}
}
}
/**
* only for internal use
*/
commit(nativeEvent, computeActivePointer = true) {
if (this.enableMultiplePointers) {
const length = this.pointers.length;
for (let i = 0; i < length; i++) {
this.pointers[i].commit(nativeEvent);
}
return;
}
if (computeActivePointer) {
this.computeActivePointer();
}
//commit all pointers, enable the active pointer, and disable all other pointers
const length = this.pointers.length;
for (let i = 0; i < length; i++) {
const pointer = this.pointers[i];
pointer.setEnabled(i === smallestIndex, nativeEvent, false);
pointer.commit(nativeEvent);
pointer.setEnabled(pointer === this.activePointer, nativeEvent, false);
pointer.commit(nativeEvent, false);
}
}
move(scene, nativeEvent) {
if (!this.enabled) {
return;
}
/*
slow version that stays in here for benchmarking
for (let i = 0; i < this.pointers.length; i++) {
this.pointers[i].move(scene, nativeEvent)
}*/
//start intersection, build nonCapturedPointers list, and compute the intersection for all captured pointers
this.nonCapturedPointers.length = 0;
this.startIntersection(this.nonCapturedPointers, nativeEvent);
//intersect scene using the non captured pointers
intersectPointerEventTargets(scene, this.nonCapturedPointers);
//finalize the intersection for the non captured pointers
const nonCapturedPointerLength = this.nonCapturedPointers.length;
for (let i = 0; i < nonCapturedPointerLength; i++) {
const pointer = this.nonCapturedPointers[i];
pointer.setIntersection(pointer.intersector.finalizeIntersection());
}
//commit the intersection, compute active pointers, and enabling/disabling pointers
this.commit(nativeEvent);
}
setEnabled(enabled, nativeEvent) {

@@ -50,5 +122,6 @@ this.enabled = enabled;

for (let i = 0; i < length; i++) {
this.pointers[i].setEnabled(enabled, nativeEvent);
const pointer = this.pointers[i];
pointer.setEnabled(enabled && (this.enableMultiplePointers || pointer == this.activePointer), nativeEvent);
}
}
}

13

dist/forward.js

@@ -1,9 +0,5 @@

import { Quaternion, Vector2, Vector3 } from 'three';
import { Pointer } from './pointer.js';
import { PointerEvent } from './event.js';
import { intersectRayFromCamera } from './intersections/ray.js';
import { CameraRayIntersector } from './intersections/ray.js';
import { generateUniquePointerId } from './pointer/index.js';
const vectorHelper = new Vector3();
const vector2Helper = new Vector2();
const quaternionHelper = new Quaternion();
function htmlEventToCoords(element, e, target) {

@@ -52,5 +48,6 @@ if (!(e instanceof globalThis.MouseEvent)) {

}
pointerType = `${pointerTypePrefix}${pointerType}`;
const computeIntersection = (scene, nativeEvent, pointerCapture) => intersectRayFromCamera(toCamera, toCoords(nativeEvent, vector2Helper), toCamera.getWorldPosition(vectorHelper), toCamera.getWorldQuaternion(quaternionHelper), scene, pointerId, pointerType, pointerState, pointerCapture, options);
pointerMap.set(pointerId, (innerPointer = new Pointer(generateUniquePointerId(), pointerType, pointerState, computeIntersection, undefined, forwardPointerCapture ? setPointerCapture.bind(null, pointerId) : undefined, forwardPointerCapture ? releasePointerCapture.bind(null, pointerId) : undefined, options)));
pointerMap.set(pointerId, (innerPointer = new Pointer(generateUniquePointerId(), `${pointerTypePrefix}${pointerType}`, pointerState, new CameraRayIntersector((nativeEvent, coords) => {
toCoords(nativeEvent, coords);
return toCamera;
}, options), undefined, forwardPointerCapture ? setPointerCapture.bind(null, pointerId) : undefined, forwardPointerCapture ? releasePointerCapture.bind(null, pointerId) : undefined, options)));
return innerPointer;

@@ -57,0 +54,0 @@ };

@@ -13,4 +13,4 @@ import { Intersection as ThreeIntersection, Quaternion, Vector3 } from 'three';

type: 'lines';
distanceOnLine: number;
lineIndex: number;
distanceOnLine: number;
} | {

@@ -17,0 +17,0 @@ type: 'camera-ray';

import { Matrix4, Vector3, Object3D } from 'three';
import { Intersection, IntersectionOptions } from './index.js';
import type { PointerCapture } from '../pointer.js';
export declare function intersectLines(fromMatrixWorld: Matrix4, linePoints: Array<Vector3>, scene: Object3D, pointerId: number, pointerType: string, pointerState: unknown, pointerCapture: PointerCapture | undefined, options: IntersectionOptions | undefined): Intersection | undefined;
import { Intersector } from './intersector.js';
import { Intersection, IntersectionOptions } from '../index.js';
export declare class LinesIntersector extends Intersector {
private readonly prepareTransformation;
private readonly options;
private raycasters;
private fromMatrixWorld;
private intersectionLineIndex;
private intersectionDistanceOnLine;
constructor(prepareTransformation: (nativeEvent: unknown, fromMatrixWorld: Matrix4) => boolean, options: IntersectionOptions & {
linePoints?: Array<Vector3>;
minDistance?: number;
});
intersectPointerCapture({ intersection, object }: PointerCapture, nativeEvent: unknown): Intersection | undefined;
protected prepareIntersection(nativeEvent: unknown): boolean;
executeIntersection(object: Object3D, objectPointerEventsOrder: number | undefined): void;
finalizeIntersection(): Intersection | undefined;
}
import { Line3, Matrix4, Plane, Quaternion, Ray, Raycaster, Vector3, } from 'three';
import { computeIntersectionWorldPlane, getDominantIntersectionIndex, traversePointerEventTargets } from './utils.js';
const raycaster = new Raycaster();
import { computeIntersectionWorldPlane, getDominantIntersectionIndex } from './utils.js';
import { Intersector } from './intersector.js';
const invertedMatrixHelper = new Matrix4();
const intersectsHelper = [];
export function intersectLines(fromMatrixWorld, linePoints, scene, pointerId, pointerType, pointerState, pointerCapture, options) {
if (pointerCapture != null) {
return intersectLinesPointerCapture(fromMatrixWorld, linePoints, pointerCapture);
const lineHelper = new Line3();
const planeHelper = new Plane();
const rayHelper = new Ray();
const defaultLinePoints = [new Vector3(0, 0, 0), new Vector3(0, 0, 1)];
export class LinesIntersector extends Intersector {
prepareTransformation;
options;
raycasters = [];
fromMatrixWorld = new Matrix4();
intersectionLineIndex = 0;
intersectionDistanceOnLine = 0;
constructor(prepareTransformation, options) {
super();
this.prepareTransformation = prepareTransformation;
this.options = options;
}
let intersection;
let pointerEventsOrder;
traversePointerEventTargets(scene, pointerId, pointerType, pointerState, (object, objectPointerEventsOrder) => {
let prevAccLineLength = 0;
const length = (intersection?.details.lineIndex ?? linePoints.length - 2) + 2;
for (let i = 1; i < length; i++) {
const start = linePoints[i - 1];
const end = linePoints[i];
intersectPointerCapture({ intersection, object }, nativeEvent) {
const details = intersection.details;
if (details.type != 'lines') {
return undefined;
}
if (!this.prepareTransformation(nativeEvent, this.fromMatrixWorld)) {
return undefined;
}
const linePoints = this.options.linePoints ?? defaultLinePoints;
lineHelper.set(linePoints[details.lineIndex], linePoints[details.lineIndex + 1]).applyMatrix4(this.fromMatrixWorld);
const point = lineHelper.at(details.distanceOnLine / lineHelper.distance(), new Vector3());
computeIntersectionWorldPlane(planeHelper, intersection, object);
const pointOnFace = rayHelper.intersectPlane(planeHelper, new Vector3()) ?? point;
return {
...intersection,
pointOnFace,
point,
pointerPosition: new Vector3().setFromMatrixPosition(this.fromMatrixWorld),
pointerQuaternion: new Quaternion().setFromRotationMatrix(this.fromMatrixWorld),
};
}
prepareIntersection(nativeEvent) {
if (!this.prepareTransformation(nativeEvent, this.fromMatrixWorld)) {
return false;
}
const linePoints = this.options.linePoints ?? defaultLinePoints;
const length = linePoints.length - 1;
for (let i = 0; i < length; i++) {
const start = linePoints[i];
const end = linePoints[i + 1];
const raycaster = this.raycasters[i] ?? (this.raycasters[i] = new Raycaster());
//transform from local object to world
raycaster.ray.origin.copy(start).applyMatrix4(fromMatrixWorld);
raycaster.ray.direction.copy(end).applyMatrix4(fromMatrixWorld);
raycaster.ray.origin.copy(start).applyMatrix4(this.fromMatrixWorld);
raycaster.ray.direction.copy(end).applyMatrix4(this.fromMatrixWorld);
//compute length & normalized direction

@@ -26,74 +61,46 @@ raycaster.ray.direction.sub(raycaster.ray.origin);

raycaster.far = lineLength;
}
this.raycasters.length = length;
return true;
}
executeIntersection(object, objectPointerEventsOrder) {
let lineLengthSum = 0;
const length = this.raycasters.length;
//TODO: optimize - we only need to intersect with raycasters before or equal to the raycaster that did the current intersection
for (let i = 0; i < length; i++) {
const raycaster = this.raycasters[i];
object.raycast(raycaster, intersectsHelper);
//we're adding the details and the prev acc line length so that the intersections are correctly sorted
const length = intersectsHelper.length;
for (let intersectionIndex = 0; intersectionIndex < length; intersectionIndex++) {
const int = intersectsHelper[intersectionIndex];
const distanceOnLine = int.distance;
int.distance += prevAccLineLength;
Object.assign(int, {
details: {
lineIndex: i - 1,
distanceOnLine,
},
});
for (const intersection of intersectsHelper) {
intersection.distance += lineLengthSum;
}
const index = getDominantIntersectionIndex(intersection, pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, options);
const index = getDominantIntersectionIndex(this.intersection, this.pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, this.options);
if (index != null) {
intersection = intersectsHelper[index];
pointerEventsOrder = objectPointerEventsOrder;
this.intersection = intersectsHelper[index];
this.intersectionLineIndex = i;
this.intersectionDistanceOnLine = this.intersection.distance - raycaster.far;
this.pointerEventsOrder = objectPointerEventsOrder;
}
intersectsHelper.length = 0;
prevAccLineLength += lineLength;
lineLengthSum += raycaster.far;
}
});
if (intersection == null) {
return undefined;
}
return Object.assign(intersection, {
details: {
...intersection.details,
type: 'lines',
},
pointerPosition: new Vector3().setFromMatrixPosition(fromMatrixWorld),
pointerQuaternion: new Quaternion().setFromRotationMatrix(fromMatrixWorld),
pointOnFace: intersection.point,
localPoint: intersection.point
.clone()
.applyMatrix4(invertedMatrixHelper.copy(intersection.object.matrixWorld).invert()),
});
}
const lineHelper = new Line3();
const planeHelper = new Plane();
function intersectLinesPointerCapture(fromMatrixWorld, linePoints, { intersection, object }) {
const details = intersection.details;
if (details.type != 'lines') {
return undefined;
}
lineHelper.set(linePoints[details.lineIndex], linePoints[details.lineIndex + 1]).applyMatrix4(fromMatrixWorld);
const point = lineHelper.at(details.distanceOnLine / lineHelper.distance(), new Vector3());
computeIntersectionWorldPlane(planeHelper, intersection, object);
const pointOnFace = backwardsIntersectionLinesWithPlane(fromMatrixWorld, linePoints, planeHelper) ?? point;
return {
...intersection,
pointOnFace,
point,
pointerPosition: new Vector3().setFromMatrixPosition(fromMatrixWorld),
pointerQuaternion: new Quaternion().setFromRotationMatrix(fromMatrixWorld),
};
}
const vectorHelper = new Vector3();
const rayHelper = new Ray();
function backwardsIntersectionLinesWithPlane(fromMatrixWorld, linePoints, plane) {
for (let i = linePoints.length - 1; i > 0; i--) {
const start = linePoints[i - 1];
const end = linePoints[i];
rayHelper.origin.copy(start).applyMatrix4(fromMatrixWorld);
rayHelper.direction.copy(end).applyMatrix4(fromMatrixWorld).sub(raycaster.ray.origin).normalize();
const point = rayHelper.intersectPlane(plane, vectorHelper);
if (point != null) {
return vectorHelper.clone();
finalizeIntersection() {
if (this.intersection == null) {
return undefined;
}
//TODO: consider maxLength
return Object.assign(this.intersection, {
details: {
lineIndex: this.intersectionLineIndex,
distanceOnLine: this.intersectionDistanceOnLine,
type: 'lines',
},
pointerPosition: new Vector3().setFromMatrixPosition(this.fromMatrixWorld),
pointerQuaternion: new Quaternion().setFromRotationMatrix(this.fromMatrixWorld),
pointOnFace: this.intersection.point,
localPoint: this.intersection.point
.clone()
.applyMatrix4(invertedMatrixHelper.copy(this.intersection.object.matrixWorld).invert()),
});
}
return undefined;
}

@@ -1,5 +0,33 @@

import { Camera, Quaternion, Vector2, Vector3, Object3D } from 'three';
import { Matrix4, Vector3, Object3D, Camera, Vector2 } from 'three';
import { Intersection, IntersectionOptions } from './index.js';
import type { PointerCapture } from '../pointer.js';
export declare function intersectRay(fromPosition: Vector3, fromQuaternion: Quaternion, direction: Vector3, scene: Object3D, pointerId: number, pointerType: string, pointerState: unknown, pointerCapture: PointerCapture | undefined, options: IntersectionOptions | undefined): Intersection | undefined;
export declare function intersectRayFromCamera(from: Camera, coords: Vector2, fromPosition: Vector3, fromQuaternion: Quaternion, scene: Object3D, pointerId: number, pointerType: string, pointerState: unknown, pointerCapture: PointerCapture | undefined, options: IntersectionOptions | undefined): Intersection | undefined;
import { type PointerCapture } from '../pointer.js';
import { Intersector } from './intersector.js';
export declare class RayIntersector extends Intersector {
private readonly prepareTransformation;
private readonly options;
private readonly raycaster;
private readonly raycasterQuaternion;
private worldScale;
constructor(prepareTransformation: (nativeEvent: unknown, matrixWorld: Matrix4) => boolean, options: IntersectionOptions & {
minDistance?: number;
direction?: Vector3;
});
intersectPointerCapture({ intersection, object }: PointerCapture, nativeEvent: unknown): Intersection | undefined;
protected prepareIntersection(nativeEvent: unknown): boolean;
executeIntersection(object: Object3D, objectPointerEventsOrder: number | undefined): void;
finalizeIntersection(): Intersection | undefined;
}
export declare class CameraRayIntersector extends Intersector {
private readonly prepareTransformation;
private readonly options;
private readonly raycaster;
private readonly fromPosition;
private readonly fromQuaternion;
private readonly coords;
private viewPlane;
constructor(prepareTransformation: (nativeEvent: unknown, coords: Vector2) => Camera | undefined, options: IntersectionOptions);
intersectPointerCapture({ intersection, object }: PointerCapture, nativeEvent: unknown): Intersection | undefined;
protected prepareIntersection(nativeEvent: unknown): boolean;
executeIntersection(object: Object3D, objectPointerEventsOrder: number | undefined): void;
finalizeIntersection(): Intersection | undefined;
}

@@ -1,115 +0,153 @@

import { Matrix4, Plane, Ray, Raycaster, Vector3, } from 'three';
import { computeIntersectionWorldPlane, getDominantIntersectionIndex, traversePointerEventTargets } from './utils.js';
const raycaster = new Raycaster();
import { Matrix4, Plane, Quaternion, Raycaster, Vector3, Vector2, } from 'three';
import { computeIntersectionWorldPlane, getDominantIntersectionIndex } from './utils.js';
import { Intersector } from './intersector.js';
const invertedMatrixHelper = new Matrix4();
const intersectsHelper = [];
const matrixHelper = new Matrix4();
const scaleHelper = new Vector3();
const NegZAxis = new Vector3(0, 0, -1);
const directionHelper = new Vector3();
const planeHelper = new Plane();
const invertedMatrixHelper = new Matrix4();
const intersectsHelper = [];
export function intersectRay(fromPosition, fromQuaternion, direction, scene, pointerId, pointerType, pointerState, pointerCapture, options) {
if (pointerCapture != null) {
return intersectRayPointerCapture(fromPosition, fromQuaternion, direction, pointerCapture);
export class RayIntersector extends Intersector {
prepareTransformation;
options;
raycaster = new Raycaster();
raycasterQuaternion = new Quaternion();
worldScale = 0;
constructor(prepareTransformation, options) {
super();
this.prepareTransformation = prepareTransformation;
this.options = options;
}
let intersection;
let pointerEventsOrder;
raycaster.ray.origin.copy(fromPosition);
raycaster.ray.direction.copy(direction).applyQuaternion(fromQuaternion);
traversePointerEventTargets(scene, pointerId, pointerType, pointerState, (object, objectPointerEventsOrder) => {
object.raycast(raycaster, intersectsHelper);
const index = getDominantIntersectionIndex(intersection, pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, options);
intersectPointerCapture({ intersection, object }, nativeEvent) {
if (intersection.details.type != 'ray') {
return undefined;
}
if (!this.prepareIntersection(nativeEvent)) {
return undefined;
}
computeIntersectionWorldPlane(planeHelper, intersection, object);
const { ray } = this.raycaster;
const pointOnFace = ray.intersectPlane(planeHelper, new Vector3()) ?? intersection.point;
return {
...intersection,
object,
pointOnFace,
point: ray.direction.clone().multiplyScalar(intersection.distance).add(ray.origin),
pointerPosition: ray.origin.clone(),
pointerQuaternion: this.raycasterQuaternion.clone(),
};
}
prepareIntersection(nativeEvent) {
if (!this.prepareTransformation(nativeEvent, matrixHelper)) {
return false;
}
matrixHelper.decompose(this.raycaster.ray.origin, this.raycasterQuaternion, scaleHelper);
this.worldScale = scaleHelper.x;
this.raycaster.ray.direction.copy(this.options?.direction ?? NegZAxis).applyQuaternion(this.raycasterQuaternion);
return true;
}
executeIntersection(object, objectPointerEventsOrder) {
object.raycast(this.raycaster, intersectsHelper);
const index = getDominantIntersectionIndex(this.intersection, this.pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, this.options);
if (index != null) {
intersection = intersectsHelper[index];
pointerEventsOrder = objectPointerEventsOrder;
this.intersection = intersectsHelper[index];
this.pointerEventsOrder = objectPointerEventsOrder;
}
intersectsHelper.length = 0;
});
if (intersection == null) {
return undefined;
}
return Object.assign(intersection, {
details: {
type: 'ray',
},
pointerPosition: fromPosition.clone(),
pointerQuaternion: fromQuaternion.clone(),
pointOnFace: intersection.point,
localPoint: intersection.point
.clone()
.applyMatrix4(invertedMatrixHelper.copy(intersection.object.matrixWorld).invert()),
});
}
const rayHelper = new Ray();
function intersectRayPointerCapture(fromPosition, fromQuaternion, direction, { intersection, object }) {
if (intersection.details.type != 'ray') {
return undefined;
finalizeIntersection() {
if (this.intersection == null) {
return undefined;
}
if (this.options.minDistance != null && this.intersection.distance * this.worldScale < this.options.minDistance) {
return undefined;
}
return Object.assign(this.intersection, {
details: {
type: 'ray',
},
pointerPosition: this.raycaster.ray.origin.clone(),
pointerQuaternion: this.raycasterQuaternion.clone(),
pointOnFace: this.intersection.point,
localPoint: this.intersection.point
.clone()
.applyMatrix4(invertedMatrixHelper.copy(this.intersection.object.matrixWorld).invert()),
});
}
directionHelper.copy(direction).applyQuaternion(fromQuaternion);
rayHelper.set(fromPosition, directionHelper);
computeIntersectionWorldPlane(planeHelper, intersection, object);
const pointOnFace = rayHelper.intersectPlane(planeHelper, new Vector3()) ?? intersection.point;
return {
...intersection,
object,
pointOnFace,
point: directionHelper.clone().multiplyScalar(intersection.distance).add(fromPosition),
pointerPosition: fromPosition.clone(),
pointerQuaternion: fromQuaternion.clone(),
};
}
export function intersectRayFromCamera(from, coords, fromPosition, fromQuaternion, scene, pointerId, pointerType, pointerState, pointerCapture, options) {
if (pointerCapture != null) {
return intersectRayFromCameraPointerCapture(from, coords, fromPosition, fromQuaternion, pointerCapture);
export class CameraRayIntersector extends Intersector {
prepareTransformation;
options;
raycaster = new Raycaster();
fromPosition = new Vector3();
fromQuaternion = new Quaternion();
coords = new Vector2();
viewPlane = new Plane();
constructor(prepareTransformation, options) {
super();
this.prepareTransformation = prepareTransformation;
this.options = options;
}
let intersection;
let pointerEventsOrder;
raycaster.setFromCamera(coords, from);
planeHelper.setFromNormalAndCoplanarPoint(from.getWorldDirection(directionHelper), raycaster.ray.origin);
traversePointerEventTargets(scene, pointerId, pointerType, pointerState, (object, objectPointerEventsOrder) => {
object.raycast(raycaster, intersectsHelper);
const index = getDominantIntersectionIndex(intersection, pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, options);
intersectPointerCapture({ intersection, object }, nativeEvent) {
const details = intersection.details;
if (details.type != 'camera-ray') {
return undefined;
}
if (!this.prepareIntersection(nativeEvent)) {
return undefined;
}
this.viewPlane.constant -= details.distanceViewPlane;
//find captured intersection point by intersecting the ray to the plane of the camera
const point = this.raycaster.ray.intersectPlane(this.viewPlane, new Vector3());
if (point == null) {
return undefined;
}
computeIntersectionWorldPlane(this.viewPlane, intersection, object);
const pointOnFace = this.raycaster.ray.intersectPlane(this.viewPlane, new Vector3()) ?? point;
return {
...intersection,
object,
point,
pointOnFace,
pointerPosition: this.fromPosition.clone(),
pointerQuaternion: this.fromQuaternion.clone(),
};
}
prepareIntersection(nativeEvent) {
const from = this.prepareTransformation(nativeEvent, this.coords);
if (from == null) {
return false;
}
from.matrixWorld.decompose(this.fromPosition, this.fromQuaternion, scaleHelper);
from.updateWorldMatrix(true, false);
this.raycaster.setFromCamera(this.coords, from);
this.viewPlane.setFromNormalAndCoplanarPoint(from.getWorldDirection(directionHelper), this.raycaster.ray.origin);
return true;
}
executeIntersection(object, objectPointerEventsOrder) {
object.raycast(this.raycaster, intersectsHelper);
const index = getDominantIntersectionIndex(this.intersection, this.pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, this.options);
if (index != null) {
intersection = intersectsHelper[index];
pointerEventsOrder = objectPointerEventsOrder;
this.intersection = intersectsHelper[index];
this.pointerEventsOrder = objectPointerEventsOrder;
}
intersectsHelper.length = 0;
});
if (intersection == null) {
return undefined;
}
invertedMatrixHelper.copy(intersection.object.matrixWorld).invert();
return Object.assign(intersection, {
details: {
type: 'camera-ray',
distanceViewPlane: planeHelper.distanceToPoint(intersection.point),
},
pointOnFace: intersection.point,
pointerPosition: fromPosition.clone(),
pointerQuaternion: fromQuaternion.clone(),
localPoint: intersection.point.clone().applyMatrix4(invertedMatrixHelper),
});
}
function intersectRayFromCameraPointerCapture(from, coords, fromPosition, fromQuaternion, { intersection, object }) {
const details = intersection.details;
if (details.type != 'camera-ray') {
return undefined;
finalizeIntersection() {
if (this.intersection == null) {
return undefined;
}
invertedMatrixHelper.copy(this.intersection.object.matrixWorld).invert();
return Object.assign(this.intersection, {
details: {
type: 'camera-ray',
distanceViewPlane: this.viewPlane.distanceToPoint(this.intersection.point),
},
pointOnFace: this.intersection.point,
pointerPosition: this.fromPosition.clone(),
pointerQuaternion: this.fromQuaternion.clone(),
localPoint: this.intersection.point.clone().applyMatrix4(invertedMatrixHelper),
});
}
raycaster.setFromCamera(coords, from);
from.getWorldDirection(directionHelper);
//set the plane to the viewPlane + the distance of the prev intersection in the camera distance
planeHelper.setFromNormalAndCoplanarPoint(directionHelper, raycaster.ray.origin);
planeHelper.constant -= details.distanceViewPlane;
//find captured intersection point by intersecting the ray to the plane of the camera
const point = raycaster.ray.intersectPlane(planeHelper, new Vector3());
if (point == null) {
return undefined;
}
computeIntersectionWorldPlane(planeHelper, intersection, object);
const pointOnFace = raycaster.ray.intersectPlane(planeHelper, new Vector3()) ?? point;
return {
...intersection,
object,
point,
pointOnFace,
pointerPosition: fromPosition.clone(),
pointerQuaternion: fromQuaternion.clone(),
};
}
import { Object3D, Vector3, Quaternion } from 'three';
import { Intersection, IntersectionOptions } from './index.js';
import type { PointerCapture } from '../pointer.js';
export declare function intersectSphere(fromPosition: Vector3, fromQuaternion: Quaternion, radius: number, scene: Object3D, pointerId: number, pointerType: string, pointerState: unknown, pointerCapture: PointerCapture | undefined, options: IntersectionOptions | undefined): Intersection | undefined;
import { Intersector } from './intersector.js';
import { Intersection, IntersectionOptions } from '../index.js';
export declare class SphereIntersector extends Intersector {
/**
* @returns the sphere radius
*/
private readonly prepareTransformation;
private readonly options;
private readonly fromPosition;
private readonly fromQuaternion;
constructor(
/**
* @returns the sphere radius
*/
prepareTransformation: (nativeEvent: unknown, fromPosition: Vector3, fromQuaternion: Quaternion) => number | undefined, options: IntersectionOptions);
intersectPointerCapture({ intersection, object }: PointerCapture, nativeEvent: unknown): Intersection | undefined;
protected prepareIntersection(nativeEvent: unknown): boolean;
executeIntersection(object: Object3D, objectPointerEventsOrder: number | undefined): void;
finalizeIntersection(): Intersection | undefined;
}
import { InstancedMesh, Matrix4, Mesh, Vector3, Sphere, Quaternion, Plane, } from 'three';
import { computeIntersectionWorldPlane, getDominantIntersectionIndex, traversePointerEventTargets } from './utils.js';
import { computeIntersectionWorldPlane, getDominantIntersectionIndex } from './utils.js';
import { Intersector } from './intersector.js';
const collisionSphere = new Sphere();
const intersectsHelper = [];
export function intersectSphere(fromPosition, fromQuaternion, radius, scene, pointerId, pointerType, pointerState, pointerCapture, options) {
if (pointerCapture != null) {
return intersectSpherePointerCapture(fromPosition, fromQuaternion, pointerCapture);
export class SphereIntersector extends Intersector {
prepareTransformation;
options;
fromPosition = new Vector3();
fromQuaternion = new Quaternion();
constructor(
/**
* @returns the sphere radius
*/
prepareTransformation, options) {
super();
this.prepareTransformation = prepareTransformation;
this.options = options;
}
let intersection;
let pointerEventsOrder;
collisionSphere.center.copy(fromPosition);
collisionSphere.radius = radius;
traversePointerEventTargets(scene, pointerId, pointerType, pointerState, (object, objectPointerEventsOrder) => {
intersectPointerCapture({ intersection, object }, nativeEvent) {
if (intersection.details.type != 'sphere') {
return undefined;
}
if (this.prepareTransformation(nativeEvent, this.fromPosition, this.fromQuaternion) == null) {
return undefined;
}
//compute old inputDevicePosition-point offset
oldInputDevicePointOffset.copy(intersection.point).sub(intersection.pointerPosition);
//compute oldInputDeviceQuaternion-newInputDeviceQuaternion offset
inputDeviceQuaternionOffset.copy(intersection.pointerQuaternion).invert().multiply(this.fromQuaternion);
//apply quaternion offset to old inputDevicePosition-point offset and add to new inputDevicePosition
const point = oldInputDevicePointOffset.clone().applyQuaternion(inputDeviceQuaternionOffset).add(this.fromPosition);
computeIntersectionWorldPlane(planeHelper, intersection, object);
const pointOnFace = planeHelper.projectPoint(this.fromPosition, new Vector3());
return {
details: {
type: 'sphere',
},
distance: intersection.distance,
pointerPosition: this.fromPosition.clone(),
pointerQuaternion: this.fromQuaternion.clone(),
object,
point,
pointOnFace,
face: intersection.face,
localPoint: intersection.localPoint,
};
}
prepareIntersection(nativeEvent) {
const radius = this.prepareTransformation(nativeEvent, this.fromPosition, this.fromQuaternion);
if (radius == null) {
return false;
}
collisionSphere.center.copy(this.fromPosition);
collisionSphere.radius = radius;
return true;
}
executeIntersection(object, objectPointerEventsOrder) {
intersectSphereWithObject(collisionSphere, object, intersectsHelper);
const index = getDominantIntersectionIndex(intersection, pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, options);
const index = getDominantIntersectionIndex(this.intersection, this.pointerEventsOrder, intersectsHelper, objectPointerEventsOrder, this.options);
if (index != null) {
intersection = intersectsHelper[index];
pointerEventsOrder = objectPointerEventsOrder;
this.intersection = intersectsHelper[index];
this.pointerEventsOrder = objectPointerEventsOrder;
}
intersectsHelper.length = 0;
});
if (intersection == null) {
return undefined;
}
return Object.assign(intersection, {
details: {
type: 'sphere',
},
pointOnFace: intersection.point,
pointerPosition: fromPosition.clone(),
pointerQuaternion: fromQuaternion.clone(),
localPoint: intersection.point
.clone()
.applyMatrix4(invertedMatrixHelper.copy(intersection.object.matrixWorld).invert()),
});
finalizeIntersection() {
if (this.intersection == null) {
return undefined;
}
return Object.assign(this.intersection, {
details: {
type: 'sphere',
},
pointOnFace: this.intersection.point,
pointerPosition: this.fromPosition.clone(),
pointerQuaternion: this.fromQuaternion.clone(),
localPoint: this.intersection.point
.clone()
.applyMatrix4(invertedMatrixHelper.copy(this.intersection.object.matrixWorld).invert()),
});
}
}

@@ -83,28 +130,2 @@ const matrixHelper = new Matrix4();

const planeHelper = new Plane();
function intersectSpherePointerCapture(fromPosition, fromQuaterion, { intersection, object }) {
if (intersection.details.type != 'sphere') {
return undefined;
}
//compute old inputDevicePosition-point offset
oldInputDevicePointOffset.copy(intersection.point).sub(intersection.pointerPosition);
//compute oldInputDeviceQuaternion-newInputDeviceQuaternion offset
inputDeviceQuaternionOffset.copy(intersection.pointerQuaternion).invert().multiply(fromQuaterion);
//apply quaternion offset to old inputDevicePosition-point offset and add to new inputDevicePosition
const point = oldInputDevicePointOffset.clone().applyQuaternion(inputDeviceQuaternionOffset).add(fromPosition);
computeIntersectionWorldPlane(planeHelper, intersection, object);
const pointOnFace = planeHelper.projectPoint(fromPosition, new Vector3());
return {
details: {
type: 'sphere',
},
distance: intersection.distance,
pointerPosition: fromPosition.clone(),
pointerQuaternion: fromQuaterion.clone(),
object,
point,
pointOnFace,
face: intersection.face,
localPoint: intersection.localPoint,
};
}
const helperSphere = new Sphere();

@@ -111,0 +132,0 @@ function isSphereIntersectingMesh(pointerSphere, { geometry }, meshMatrixWorld) {

import { Plane, Intersection as ThreeIntersection, Object3D } from 'three';
import { Intersection, IntersectionOptions } from './index.js';
import { AllowedPointerEventsType, type AllowedPointerEvents } from '../pointer.js';
import { AllowedPointerEventsType, Pointer, type AllowedPointerEvents } from '../pointer.js';
export declare function computeIntersectionWorldPlane(target: Plane, intersection: Intersection, object: Object3D): boolean;
export declare function traversePointerEventTargets(object: Object3D, pointerId: number, pointerType: string, pointerState: unknown, callback: (object: Object3D, pointerEventsOrder: number | undefined) => void, parentHasListener?: boolean, parentPointerEvents?: AllowedPointerEvents, parentPointerEventsType?: AllowedPointerEventsType, parentPointerEventsOrder?: number): void;
export declare function intersectPointerEventTargets(object: Object3D, pointers: Array<Pointer>, parentHasListener?: boolean, parentPointerEvents?: AllowedPointerEvents, parentPointerEventsType?: AllowedPointerEventsType, parentPointerEventsOrder?: number): void;
/**
* @returns undefined if `i1` is the dominant intersection
* @param i2DistanceOffset modifies i2 and adds the i2DistanceOffset to the current distance
*/
export declare function getDominantIntersectionIndex<T extends ThreeIntersection>(i1: T | undefined, pointerEventsOrder1: number | undefined, i2: Array<T>, pointerEventsOrder2: number | undefined, { customFilter, customSort: compare }?: IntersectionOptions): number | undefined;

@@ -10,3 +10,3 @@ import { hasObjectListeners } from '../utils.js';

}
function isPointerEventsAllowed(hasListener, pointerEvents, pointerEventsType, pointerId, pointerType, pointerState) {
function isPointerEventsAllowed(hasListener, pointerEvents, pointerEventsType) {
if (pointerEvents === 'none') {

@@ -22,3 +22,3 @@ return false;

if (typeof pointerEventsType === 'function') {
return pointerEventsType(pointerId, pointerType, pointerState);
return ({ id, type, state }) => pointerEventsType(id, type, state);
}

@@ -35,27 +35,39 @@ let value;

}
let result;
if (Array.isArray(value)) {
result = value.includes(pointerType);
return (pointer) => invertIf(value.includes(pointer.type), invert);
}
else {
result = value === pointerType;
}
return invert ? !result : result;
return (pointer) => invertIf(value === pointer.type, invert);
}
export function traversePointerEventTargets(object, pointerId, pointerType, pointerState, callback, parentHasListener = false, parentPointerEvents, parentPointerEventsType, parentPointerEventsOrder) {
function invertIf(toInvert, ifIsTrue) {
return ifIsTrue ? !toInvert : toInvert;
}
export function intersectPointerEventTargets(object, pointers, parentHasListener = false, parentPointerEvents, parentPointerEventsType, parentPointerEventsOrder) {
const hasListener = parentHasListener || hasObjectListeners(object);
const pointerEvents = object.pointerEvents ?? parentPointerEvents;
const pointerEventsType = object.pointerEventsType ?? parentPointerEventsType;
const pointerEvents = object.pointerEvents ?? parentPointerEvents ?? 'listener';
const pointerEventsType = object.pointerEventsType ?? parentPointerEventsType ?? 'all';
const pointerEventsOrder = object.pointerEventsOrder ?? parentPointerEventsOrder;
const isAllowed = isPointerEventsAllowed(hasListener, pointerEvents ?? 'listener', pointerEventsType ?? 'all', pointerId, pointerType, pointerState);
if (isAllowed) {
callback(object, pointerEventsOrder);
const isAllowed = isPointerEventsAllowed(hasListener, pointerEvents, pointerEventsType);
const length = pointers.length;
if (isAllowed === true) {
for (let i = 0; i < length; i++) {
pointers[i].intersector.executeIntersection(object, pointerEventsOrder);
}
}
const length = object.children.length;
for (let i = 0; i < length; i++) {
traversePointerEventTargets(object.children[i], pointerId, pointerType, pointerState, callback, hasListener, pointerEvents, pointerEventsType, pointerEventsOrder);
else if (typeof isAllowed === 'function') {
for (let i = 0; i < length; i++) {
const pointer = pointers[i];
if (!isAllowed(pointer)) {
continue;
}
pointers[i].intersector.executeIntersection(object, pointerEventsOrder);
}
}
const childrenLength = object.children.length;
for (let i = 0; i < childrenLength; i++) {
intersectPointerEventTargets(object.children[i], pointers, hasListener, pointerEvents, pointerEventsType, pointerEventsOrder);
}
}
/**
* @returns undefined if `i1` is the dominant intersection
* @param i2DistanceOffset modifies i2 and adds the i2DistanceOffset to the current distance
*/

@@ -62,0 +74,0 @@ export function getDominantIntersectionIndex(i1, pointerEventsOrder1, i2, pointerEventsOrder2, { customFilter, customSort: compare = defaultSort } = {}) {

import { Object3D } from 'three';
import { Intersection } from './intersections/index.js';
import { NativeEvent, NativeWheelEvent } from './event.js';
import { Intersector } from './intersections/intersector.js';
declare const buttonsDownTimeKey: unique symbol;

@@ -64,3 +65,3 @@ declare const buttonsClickTimeKey: unique symbol;

readonly state: any;
private readonly computeIntersection;
readonly intersector: Intersector;
private readonly onMoveCommited?;

@@ -84,3 +85,4 @@ private readonly parentSetPointerCapture?;

private onFirstMove;
constructor(id: number, type: string, state: any, computeIntersection: (scene: Object3D, nativeEvent: unknown, pointerCapture: PointerCapture | undefined) => Intersection | undefined, onMoveCommited?: ((pointer: Pointer) => void) | undefined, parentSetPointerCapture?: (() => void) | undefined, parentReleasePointerCapture?: (() => void) | undefined, options?: PointerOptions);
constructor(id: number, type: string, state: any, intersector: Intersector, onMoveCommited?: ((pointer: Pointer) => void) | undefined, parentSetPointerCapture?: (() => void) | undefined, parentReleasePointerCapture?: (() => void) | undefined, options?: PointerOptions);
getPointerCapture(): PointerCapture | undefined;
hasCaptured(object: Object3D): boolean;

@@ -92,2 +94,4 @@ setCapture(object: Object3D | undefined): void;

setEnabled(enabled: boolean, nativeEvent: NativeEvent, commit?: boolean): void;
private computeIntersection;
setIntersection(intersection: Intersection | undefined): void;
/**

@@ -94,0 +98,0 @@ * allows to separately compute and afterwards commit a move

import { Object3D } from 'three';
import { PointerEvent, WheelEvent, emitPointerEvent } from './event.js';
import { intersectPointerEventTargets } from './intersections/utils.js';
const buttonsDownTimeKey = Symbol('buttonsDownTime');

@@ -26,3 +27,3 @@ const buttonsClickTimeKey = Symbol('buttonsClickTime');

state;
computeIntersection;
intersector;
onMoveCommited;

@@ -49,7 +50,7 @@ parentSetPointerCapture;

onFirstMove = [];
constructor(id, type, state, computeIntersection, onMoveCommited, parentSetPointerCapture, parentReleasePointerCapture, options = {}) {
constructor(id, type, state, intersector, onMoveCommited, parentSetPointerCapture, parentReleasePointerCapture, options = {}) {
this.id = id;
this.type = type;
this.state = state;
this.computeIntersection = computeIntersection;
this.intersector = intersector;
this.onMoveCommited = onMoveCommited;

@@ -61,2 +62,5 @@ this.parentSetPointerCapture = parentSetPointerCapture;

}
getPointerCapture() {
return this.pointerCapture;
}
hasCaptured(object) {

@@ -100,2 +104,13 @@ return this.pointerCapture?.object === object;

}
computeIntersection(scene, nativeEvent) {
if (this.pointerCapture != null) {
return this.intersector.intersectPointerCapture(this.pointerCapture, nativeEvent);
}
this.intersector.startIntersection(nativeEvent);
intersectPointerEventTargets(scene, [this]);
return this.intersector.finalizeIntersection();
}
setIntersection(intersection) {
this.intersection = intersection;
}
/**

@@ -107,3 +122,3 @@ * allows to separately compute and afterwards commit a move

computeMove(scene, nativeEvent) {
this.intersection = this.computeIntersection(scene, nativeEvent, this.pointerCapture);
this.intersection = this.computeIntersection(scene, nativeEvent);
}

@@ -237,3 +252,3 @@ commit(nativeEvent) {

if (!useCurrentIntersection) {
intersection = this.computeIntersection(scene, nativeEvent, this.pointerCapture);
intersection = this.computeIntersection(scene, nativeEvent);
}

@@ -240,0 +255,0 @@ if (!this.wasMoved && useCurrentIntersection) {

@@ -10,7 +10,4 @@ import { Object3D } from 'three';

} & PointerOptions & IntersectionOptions;
export declare const defaultGrabPointerOptions: {
radius: number;
};
export declare function createGrabPointer(space: {
current?: Object3D | null;
}, pointerState: any, options?: GrabPointerOptions, pointerType?: string): Pointer;

@@ -1,13 +0,6 @@

import { Quaternion, Vector3 } from 'three';
import { Pointer } from '../pointer.js';
import { intersectSphere } from '../intersections/sphere.js';
import { SphereIntersector } from '../intersections/sphere.js';
import { generateUniquePointerId } from './index.js';
export const defaultGrabPointerOptions = {
radius: 0.07,
};
export function createGrabPointer(space, pointerState, options = defaultGrabPointerOptions, pointerType = 'grab') {
const fromPosition = new Vector3();
const fromQuaternion = new Quaternion();
const poinerId = generateUniquePointerId();
return new Pointer(poinerId, pointerType, pointerState, (scene, _, pointerCapture) => {
export function createGrabPointer(space, pointerState, options = {}, pointerType = 'grab') {
return new Pointer(generateUniquePointerId(), pointerType, pointerState, new SphereIntersector((_nativeEvent, fromPosition, fromQuaternion) => {
const spaceObject = space.current;

@@ -20,4 +13,4 @@ if (spaceObject == null) {

fromQuaternion.setFromRotationMatrix(spaceObject.matrixWorld);
return intersectSphere(fromPosition, fromQuaternion, options.radius ?? defaultGrabPointerOptions.radius, scene, poinerId, pointerType, pointerState, pointerCapture, options);
}, undefined, undefined, undefined, options);
return options.radius ?? 0.07;
}, options), undefined, undefined, undefined, options);
}
export declare function generateUniquePointerId(): number;
export * from './grab.js';
export * from './ray.js';
export * from './lines.js';
export * from './touch.js';

@@ -7,2 +7,3 @@ let pointerIdCounter = 23412;

export * from './ray.js';
export * from './lines.js';
export * from './touch.js';

@@ -11,6 +11,2 @@ import { Object3D, Vector3 } from 'three';

/**
* @default null
*/
linePoints?: Array<Vector3> | null;
/**
* @default NegZAxis

@@ -20,9 +16,4 @@ */

} & PointerOptions & IntersectionOptions;
export declare const defaultRayPointerOptions: {
direction: Vector3;
minDistance: number;
linePoints: null;
};
export declare function createRayPointer(space: {
current?: Object3D | null;
}, pointerState: any, options?: RayPointerOptions, pointerType?: string): Pointer;

@@ -1,41 +0,12 @@

import { Quaternion, Vector3 } from 'three';
import { Pointer } from '../pointer.js';
import { intersectLines, intersectRay } from '../intersections/index.js';
import { RayIntersector } from '../intersections/index.js';
import { generateUniquePointerId } from './index.js';
const NegZAxis = new Vector3(0, 0, -1);
const vectorHelper = new Vector3();
export const defaultRayPointerOptions = {
direction: NegZAxis,
minDistance: 0,
linePoints: null,
};
export function createRayPointer(space, pointerState, options = defaultRayPointerOptions, pointerType = 'ray') {
const fromPosition = new Vector3();
const fromQuaternion = new Quaternion();
const pointerId = generateUniquePointerId();
return new Pointer(pointerId, pointerType, pointerState, (scene, _, pointerCapture) => {
const spaceObject = space.current;
if (spaceObject == null) {
return undefined;
export function createRayPointer(space, pointerState, options = {}, pointerType = 'ray') {
return new Pointer(generateUniquePointerId(), pointerType, pointerState, new RayIntersector((_nativeEvent, matrixWorld) => {
if (space.current == null) {
return false;
}
spaceObject.updateWorldMatrix(true, false);
let intersection;
const linePoints = options.linePoints ?? defaultRayPointerOptions.linePoints;
if (linePoints == null) {
fromPosition.setFromMatrixPosition(spaceObject.matrixWorld);
fromQuaternion.setFromRotationMatrix(spaceObject.matrixWorld);
intersection = intersectRay(fromPosition, fromQuaternion, options.direction ?? defaultRayPointerOptions.direction, scene, pointerId, pointerType, pointerState, pointerCapture, options);
}
else {
intersection = intersectLines(spaceObject.matrixWorld, linePoints, scene, pointerId, pointerType, pointerState, pointerCapture, options);
}
if (intersection == null) {
return undefined;
}
const localDistance = intersection.distance * spaceObject.getWorldScale(vectorHelper).x;
if (localDistance < (options.minDistance ?? defaultRayPointerOptions.minDistance)) {
return undefined;
}
return intersection;
}, undefined, undefined, undefined, options);
matrixWorld.copy(space.current.matrixWorld);
return true;
}, options), undefined, undefined, undefined, options);
}

@@ -18,9 +18,4 @@ import { Object3D } from 'three';

} & PointerOptions & IntersectionOptions;
export declare const defaultTouchPointerOptions: {
button: number;
downRadius: number;
hoverRadius: number;
};
export declare function createTouchPointer(space: {
current?: Object3D | null;
}, pointerState: any, options?: TouchPointerOptions, pointerType?: string): Pointer;

@@ -1,15 +0,6 @@

import { Quaternion, Vector3 } from 'three';
import { Pointer } from '../pointer.js';
import { intersectSphere } from '../intersections/index.js';
import { SphereIntersector } from '../intersections/index.js';
import { generateUniquePointerId } from './index.js';
export const defaultTouchPointerOptions = {
button: 0,
downRadius: 0.03,
hoverRadius: 0.1,
};
export function createTouchPointer(space, pointerState, options = defaultTouchPointerOptions, pointerType = 'touch') {
const fromPosition = new Vector3();
const fromQuaternion = new Quaternion();
const pointerId = generateUniquePointerId();
return new Pointer(pointerId, pointerType, pointerState, (scene, _, pointerCapture) => {
export function createTouchPointer(space, pointerState, options = {}, pointerType = 'touch') {
return new Pointer(generateUniquePointerId(), pointerType, pointerState, new SphereIntersector((_nativeEvent, fromPosition, fromQuaternion) => {
const spaceObject = space.current;

@@ -22,6 +13,6 @@ if (spaceObject == null) {

fromQuaternion.setFromRotationMatrix(spaceObject.matrixWorld);
return intersectSphere(fromPosition, fromQuaternion, options.hoverRadius ?? defaultTouchPointerOptions.hoverRadius, scene, pointerId, pointerType, pointerState, pointerCapture, options);
}, createUpdateTouchPointer(options), undefined, undefined, options);
return options.hoverRadius ?? 0.1;
}, options), createUpdateTouchPointer(options), undefined, undefined, options);
}
function createUpdateTouchPointer(options = defaultTouchPointerOptions) {
function createUpdateTouchPointer(options) {
let wasPointerDown = false;

@@ -33,7 +24,7 @@ return (pointer) => {

const intersection = pointer.getIntersection();
const isPointerDown = computeIsPointerDown(intersection, options.downRadius ?? defaultTouchPointerOptions.downRadius);
const isPointerDown = computeIsPointerDown(intersection, options.downRadius ?? 0.03);
if (isPointerDown === wasPointerDown) {
return;
}
const nativeEvent = { timeStamp: performance.now(), button: options.button ?? defaultTouchPointerOptions.button };
const nativeEvent = { timeStamp: performance.now(), button: options.button ?? 0 };
if (isPointerDown) {

@@ -40,0 +31,0 @@ pointer.down(nativeEvent);

@@ -5,3 +5,3 @@ {

"license": "SEE LICENSE IN LICENSE",
"version": "6.2.2",
"version": "6.2.3",
"homepage": "https://github.com/pmndrs/xr",

@@ -32,8 +32,9 @@ "author": "Bela Bohlender",

"vite": "^5.2.11",
"vitest": "^1.6.0"
"vitest": "^2.0.5"
},
"scripts": {
"build": "tsc -p build.tsconfig.json",
"test": "vitest run",
"bench": "vitest bench",
"test": "vitest run --printConsoleTrace",
"bench-write": "vitest bench --output-json bench.json --run",
"bench-compare": "vitest bench --compare bench.json --run",
"example": "vite example --host",

@@ -40,0 +41,0 @@ "example:build": "vite build example"

# pointer-events
*framework agnostic pointer-events implementation for three.js*
_framework agnostic pointer-events implementation for three.js_

@@ -10,24 +10,25 @@ based on [🎯 Designing Pointer-events for 3D & XR](https://polar.sh/bbohlender/posts/designing-pointer-events-for-3d)

```js
import * as THREE from 'three';
import { forwardHtmlEvents } from '@pmndrs/pointer-events';
import * as THREE from 'three'
import { forwardHtmlEvents } from '@pmndrs/pointer-events'
const canvas = document.getElementById("canvas")
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 70, width / height, 0.01, 10 );
camera.position.z = 1;
const canvas = document.getElementById('canvas')
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10)
camera.position.z = 1
forwardHtmlEvents(canvas, camera, scene)
const width = window.innerWidth, height = window.innerHeight;
const width = window.innerWidth,
height = window.innerHeight
const geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 );
const material = new THREE.MeshBasicMaterial({ color: new THREE.Color("red") });
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2)
const material = new THREE.MeshBasicMaterial({ color: new THREE.Color('red') })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
mesh.addEventListener("pointerover", () => material.color.set("blue"))
mesh.addEventListener("pointerout", () => material.color.set("red"))
mesh.addEventListener('pointerover', () => material.color.set('blue'))
mesh.addEventListener('pointerout', () => material.color.set('red'))
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( width, height );
renderer.setAnimationLoop(() => renderer.render( scene, camera ));
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(width, height)
renderer.setAnimationLoop(() => renderer.render(scene, camera))
```

@@ -40,6 +41,6 @@

```js
object.pointerEvents = "none";
object.pointerEvents = 'none'
```
The values `none` and `auto` correspond to the css properties, where `none` means that an object is not directly targetted and `auto` means the object is always targetted for events. The additional value `listener`, which is the default value, expresses that the object is only targetted by events if the object has any listeners. In 3D scenes this default is more reasonable than `auto`, which is the default in the web, because 3D scenes often contain semi-transparent content, such as particles, that should not catch pointer events by default.
The values `none` and `auto` correspond to the css properties, where `none` means that an object is not directly targetted and `auto` means the object is always targetted for events. The additional value `listener`, which is the default value, expresses that the object is only targetted by events if the object has any listeners. In 3D scenes this default is more reasonable than `auto`, which is the default in the web, because 3D scenes often contain semi-transparent content, such as particles, that should not catch pointer events by default.

@@ -50,2 +51,33 @@ In addition to the `pointerEvents` property, each 3D object can also filter events based on the `pointerType` with the `pointerEventsType` property. This property defaults to the value `all`, which expresses that pointer events from pointers of all types should be accepted. To filter specific pointer types, such as `screen-mouse`, which represents a normal mouse used through a 2D screen, `pointerEventsType` can be set to `{ allow: "screen-mouse" }` or `{ deny: "screen-touch" }`. `pointerEventsType`'s `allow` and `deny` accept strings and array of strings. In case more custom logic is needed, `pointerEventsType` also accepts a function. In general the pointer types `screen-touch`, `screen-pen`, `ray`, `grab`, and `touch` are used by default. For pointer events that were forwarded through a portal using `forwardObjectEvents`, their `pointerType` is prefixed with `forward-`, while events forwarded from the dom to the scene are prefixed with `screen-`.

Create your own `Pointer` that can represent a WebXR controller or something else. These `Pointer` can use a normal `Ray` for intersection, or a set of `Lines`, or even a `Sphere`, for grab and touch events.
Create your own `Pointer` that can represent a WebXR controller or something else. These `Pointer` can use a normal `Ray` for intersection, or a set of `Lines`, or even a `Sphere`, for grab and touch events.
## Performance
In some cases multi-modal interactivity requires multiple pointers at the same time. Executing `pointer.move`, such as in the following example, can lead to performance issues because the scene graph will be traversed several times.
```ts
leftGrabPointer.move()
leftTouchPointer.move()
leftRayPointer.move()
rightGrabPointer.move()
rightTouchPointer.move()
rightRayPointer.move()
```
In this case, performance can be improved by combining the pointer using `CombinedPointer`, which will traverse the scene graph once per combined pointer, calculating the intersections for each pointer on each object.
```ts
const leftPointer = new CombinedPointer()
const rightPointer = new CombinedPointer()
leftPointer.register(leftGrabPointer)
leftPointer.register(leftTouchPointer)
leftPointer.register(leftRayPointer)
rightPointer.register(rightGrabPointer)
rightPointer.register(rightTouchPointer)
rightPointer.register(rightRayPointer)
leftPointer.move()
rightPointer.move()
```
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