UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

238 lines (237 loc) 6.89 kB
import { EventHandler } from "../../core/event-handler.js"; import { Mat4 } from "../../core/math/mat4.js"; import { Quat } from "../../core/math/quat.js"; import { Vec3 } from "../../core/math/vec3.js"; import { Ray } from "../../core/shape/ray.js"; import { XrHand } from "./xr-hand.js"; import { now } from "../../core/time.js"; const vec3A = new Vec3(); const quat = new Quat(); let ids = 0; class XrInputSource extends EventHandler { static EVENT_REMOVE = "remove"; static EVENT_SELECT = "select"; static EVENT_SELECTSTART = "selectstart"; static EVENT_SELECTEND = "selectend"; static EVENT_SQUEEZE = "squeeze"; static EVENT_SQUEEZESTART = "squeezestart"; static EVENT_SQUEEZEEND = "squeezeend"; static EVENT_HITTESTADD = "hittest:add"; static EVENT_HITTESTREMOVE = "hittest:remove"; static EVENT_HITTESTRESULT = "hittest:result"; _id; _manager; _xrInputSource; _ray = new Ray(); _rayLocal = new Ray(); _grip = false; _hand = null; _velocitiesAvailable = false; _velocitiesTimestamp = now(); _localTransform = null; _worldTransform = null; _position = new Vec3(); _rotation = new Quat(); _localPosition = null; _localPositionLast = null; _localRotation = null; _linearVelocity = null; _dirtyLocal = true; _dirtyRay = false; _selecting = false; _squeezing = false; _elementInput = true; _elementEntity = null; _hitTestSources = []; constructor(manager, xrInputSource) { super(); this._id = ++ids; this._manager = manager; this._xrInputSource = xrInputSource; if (xrInputSource.hand) { this._hand = new XrHand(this); } } get id() { return this._id; } get inputSource() { return this._xrInputSource; } get targetRayMode() { return this._xrInputSource.targetRayMode; } get handedness() { return this._xrInputSource.handedness; } get profiles() { return this._xrInputSource.profiles; } get grip() { return this._grip; } get hand() { return this._hand; } get gamepad() { return this._xrInputSource.gamepad || null; } get selecting() { return this._selecting; } get squeezing() { return this._squeezing; } set elementInput(value) { if (this._elementInput === value) { return; } this._elementInput = value; if (!this._elementInput) { this._elementEntity = null; } } get elementInput() { return this._elementInput; } get elementEntity() { return this._elementEntity; } get hitTestSources() { return this._hitTestSources; } update(frame) { if (this._hand) { this._hand.update(frame); } else { const gripSpace = this._xrInputSource.gripSpace; if (gripSpace) { const gripPose = frame.getPose(gripSpace, this._manager._referenceSpace); if (gripPose) { if (!this._grip) { this._grip = true; this._localTransform = new Mat4(); this._worldTransform = new Mat4(); this._localPositionLast = new Vec3(); this._localPosition = new Vec3(); this._localRotation = new Quat(); this._linearVelocity = new Vec3(); } const timestamp = now(); const dt = (timestamp - this._velocitiesTimestamp) / 1e3; this._velocitiesTimestamp = timestamp; this._dirtyLocal = true; this._localPositionLast.copy(this._localPosition); this._localPosition.copy(gripPose.transform.position); this._localRotation.copy(gripPose.transform.orientation); this._velocitiesAvailable = true; if (this._manager.input.velocitiesSupported && gripPose.linearVelocity) { this._linearVelocity.copy(gripPose.linearVelocity); } else if (dt > 0) { vec3A.sub2(this._localPosition, this._localPositionLast).divScalar(dt); this._linearVelocity.lerp(this._linearVelocity, vec3A, 0.15); } } else { this._velocitiesAvailable = false; } } const targetRayPose = frame.getPose(this._xrInputSource.targetRaySpace, this._manager._referenceSpace); if (targetRayPose) { this._dirtyRay = true; this._rayLocal.origin.copy(targetRayPose.transform.position); this._rayLocal.direction.set(0, 0, -1); quat.copy(targetRayPose.transform.orientation); quat.transformVector(this._rayLocal.direction, this._rayLocal.direction); } } } _updateTransforms() { if (this._dirtyLocal) { this._dirtyLocal = false; this._localTransform.setTRS(this._localPosition, this._localRotation, Vec3.ONE); } const parent = this._manager.camera.parent; if (parent) { this._worldTransform.mul2(parent.getWorldTransform(), this._localTransform); } else { this._worldTransform.copy(this._localTransform); } } _updateRayTransforms() { const dirty = this._dirtyRay; this._dirtyRay = false; const parent = this._manager.camera.parent; if (parent) { const parentTransform = parent.getWorldTransform(); parentTransform.getTranslation(this._position); this._rotation.setFromMat4(parentTransform); this._rotation.transformVector(this._rayLocal.origin, this._ray.origin); this._ray.origin.add(this._position); this._rotation.transformVector(this._rayLocal.direction, this._ray.direction); } else if (dirty) { this._ray.origin.copy(this._rayLocal.origin); this._ray.direction.copy(this._rayLocal.direction); } } getPosition() { if (!this._grip) return null; this._updateTransforms(); this._worldTransform.getTranslation(this._position); return this._position; } getLocalPosition() { return this._localPosition; } getRotation() { if (!this._grip) return null; this._updateTransforms(); this._rotation.setFromMat4(this._worldTransform); return this._rotation; } getLocalRotation() { return this._localRotation; } getLinearVelocity() { if (!this._velocitiesAvailable) { return null; } return this._linearVelocity; } getOrigin() { this._updateRayTransforms(); return this._ray.origin; } getDirection() { this._updateRayTransforms(); return this._ray.direction; } hitTestStart(options = {}) { options.inputSource = this; options.profile = this._xrInputSource.profiles[0]; const callback = options.callback; options.callback = (err, hitTestSource) => { if (hitTestSource) this.onHitTestSourceAdd(hitTestSource); if (callback) callback(err, hitTestSource); }; this._manager.hitTest.start(options); } onHitTestSourceAdd(hitTestSource) { this._hitTestSources.push(hitTestSource); this.fire("hittest:add", hitTestSource); hitTestSource.on("result", (position, rotation, inputSource, hitTestResult) => { if (inputSource !== this) return; this.fire("hittest:result", hitTestSource, position, rotation, hitTestResult); }); hitTestSource.once("remove", () => { this.onHitTestSourceRemove(hitTestSource); this.fire("hittest:remove", hitTestSource); }); } onHitTestSourceRemove(hitTestSource) { const ind = this._hitTestSources.indexOf(hitTestSource); if (ind !== -1) this._hitTestSources.splice(ind, 1); } } export { XrInputSource };