playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
238 lines (237 loc) • 6.89 kB
JavaScript
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
};