UNPKG

@pmndrs/xr

Version:
108 lines (107 loc) 4.39 kB
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); } } }