@pmndrs/xr
Version:
VR/AR for threejs
108 lines (107 loc) • 4.39 kB
JavaScript
import { MeshLineGeometry, MeshLineMaterial } from 'meshline';
import { Euler, Mesh, QuadraticBezierCurve3, Quaternion, Vector3, } from 'three';
import { clamp } from 'three/src/math/MathUtils.js';
/**
* marks its children as teleportable
*/
export function makeTeleportTarget(root, camera, onTeleport) {
root.traverse((object) => (object.userData.teleportTarget = true));
const listener = (e) => {
if ('point' in e && e.point instanceof Vector3) {
const c = typeof camera === 'function' ? camera() : camera;
const point = new Vector3().setFromMatrixPosition(c.matrix).negate().setComponent(1, 0).add(e.point);
onTeleport(point, e);
}
};
root.addEventListener('pointerup', listener);
return () => {
root.traverse((object) => (object.userData.teleportTarget = false));
root.removeEventListener('pointerup', listener);
};
}
const eulerHelper = new Euler(0, 0, 0, 'YXZ');
const quaternionHelper = new Quaternion();
/**
* @param space
* @param rayGroup must be placed directly into the scene
*/
export function syncTeleportPointerRayGroup(space, rayGroup, deltaTimeMs) {
space.updateWorldMatrix(true, false);
space.matrixWorld.decompose(rayGroup.position, quaternionHelper, rayGroup.scale);
eulerHelper.setFromQuaternion(quaternionHelper);
eulerHelper.z = 0;
eulerHelper.x = clamp(eulerHelper.x - (10 * Math.PI) / 180, -Math.PI / 2, (1.1 * Math.PI) / 4);
quaternionHelper.setFromEuler(eulerHelper);
rayGroup.quaternion.slerp(quaternionHelper, deltaTimeMs / 100);
}
/**
* check if the object is marked as teleportable
*/
export function isTeleportTarget(object) {
return object.userData.teleportTarget === true;
}
export function buildTeleportTargetFilter(options = {}) {
return (object, pointerEvents, pointerEventsType, pointerEventsOrder) => {
if (!isTeleportTarget(object)) {
return false;
}
if (options.filter != null && !options.filter(object, pointerEvents, pointerEventsType, pointerEventsOrder)) {
return false;
}
return true;
};
}
export function createTeleportRayLine() {
const curve = new QuadraticBezierCurve3(new Vector3(0, 0, 0), new Vector3(0, 0, -8), new Vector3(0, -20, -15));
return curve.getPoints(20);
}
export class TeleportPointerRayModel extends Mesh {
multiplier;
lineLengths;
options = {};
constructor(points) {
const geometry = new MeshLineGeometry();
const float32Array = new Float32Array(points.length * 3);
for (let i = 0; i < points.length; i++) {
points[i].toArray(float32Array, i * 3);
}
geometry.setPoints(float32Array);
const multiplier = (points.length * 3 - 3) / (points.length * 3 - 1);
const material = new MeshLineMaterial({
lineWidth: 0.1,
resolution: undefined,
visibility: multiplier,
});
super(geometry, material);
this.material.transparent = true;
this.multiplier = multiplier;
this.material = material;
this.lineLengths = points.slice(0, -1).map((p, i) => p.distanceTo(points[i + 1]));
}
update(pointer) {
const enabled = pointer.getEnabled();
const intersection = pointer.getIntersection();
if (!enabled || pointer.getButtonsDown().size === 0 || intersection == null) {
this.visible = false;
return;
}
this.visible = true;
if (intersection.details.type != 'lines') {
this.material.visibility = this.multiplier;
return;
}
const { distanceOnLine, lineIndex } = intersection.details;
const lineLength = this.lineLengths[lineIndex];
this.material.visibility = (this.multiplier * (lineIndex + distanceOnLine / lineLength)) / this.lineLengths.length;
const { color = 'white', opacity = 0.4, size = 0.01 } = this.options;
this.material.lineWidth = size;
this.material.opacity = typeof opacity === 'function' ? opacity(pointer) : opacity;
const resolvedColor = typeof color === 'function' ? color(pointer) : color;
if (Array.isArray(resolvedColor)) {
this.material.color.set(...resolvedColor);
}
else {
this.material.color.set(resolvedColor);
}
}
}