123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- import { WebXRFeaturesManager, WebXRFeatureName } from "../webXRFeaturesManager";
- import { WebXRSessionManager } from "../webXRSessionManager";
- import { Observable } from "../../Misc/observable";
- import { Vector3, Matrix, Quaternion } from "../../Maths/math.vector";
- import { WebXRAbstractFeature } from "./WebXRAbstractFeature";
- import { IWebXRLegacyHitTestOptions, IWebXRLegacyHitResult, IWebXRHitTestFeature } from "./WebXRHitTestLegacy";
- import { Tools } from "../../Misc/tools";
- import { Nullable } from "../../types";
- /**
- * Options used for hit testing (version 2)
- */
- export interface IWebXRHitTestOptions extends IWebXRLegacyHitTestOptions {
- /**
- * Do not create a permanent hit test. Will usually be used when only
- * transient inputs are needed.
- */
- disablePermanentHitTest?: boolean;
- /**
- * Enable transient (for example touch-based) hit test inspections
- */
- enableTransientHitTest?: boolean;
- /**
- * Offset ray for the permanent hit test
- */
- offsetRay?: Vector3;
- /**
- * Offset ray for the transient hit test
- */
- transientOffsetRay?: Vector3;
- /**
- * Instead of using viewer space for hit tests, use the reference space defined in the session manager
- */
- useReferenceSpace?: boolean;
- /**
- * Override the default entity type(s) of the hit-test result
- */
- entityTypes?: XRHitTestTrackableType[];
- }
- /**
- * Interface defining the babylon result of hit-test
- */
- export interface IWebXRHitResult extends IWebXRLegacyHitResult {
- /**
- * The input source that generated this hit test (if transient)
- */
- inputSource?: XRInputSource;
- /**
- * Is this a transient hit test
- */
- isTransient?: boolean;
- /**
- * Position of the hit test result
- */
- position: Vector3;
- /**
- * Rotation of the hit test result
- */
- rotationQuaternion: Quaternion;
- /**
- * The native hit test result
- */
- xrHitResult: XRHitTestResult;
- }
- /**
- * The currently-working hit-test module.
- * Hit test (or Ray-casting) is used to interact with the real world.
- * For further information read here - https://github.com/immersive-web/hit-test
- *
- * Tested on chrome (mobile) 80.
- */
- export class WebXRHitTest extends WebXRAbstractFeature implements IWebXRHitTestFeature<IWebXRHitResult> {
- private _tmpMat: Matrix = new Matrix();
- private _tmpPos: Vector3 = new Vector3();
- private _tmpQuat: Quaternion = new Quaternion();
- private _transientXrHitTestSource: Nullable<XRTransientInputHitTestSource>;
- // in XR space z-forward is negative
- private _xrHitTestSource: Nullable<XRHitTestSource>;
- private initHitTestSource = (referenceSpace: XRReferenceSpace) => {
- if (!referenceSpace) {
- return;
- }
- const offsetRay = new XRRay(this.options.offsetRay || {});
- const hitTestOptions: XRHitTestOptionsInit = {
- space: this.options.useReferenceSpace ? referenceSpace : this._xrSessionManager.viewerReferenceSpace,
- offsetRay: offsetRay,
- };
- if (this.options.entityTypes) {
- hitTestOptions.entityTypes = this.options.entityTypes;
- }
- if (!hitTestOptions.space) {
- Tools.Warn("waiting for viewer reference space to initialize");
- return;
- }
- this._xrSessionManager.session.requestHitTestSource(hitTestOptions).then((hitTestSource) => {
- if (this._xrHitTestSource) {
- this._xrHitTestSource.cancel();
- }
- this._xrHitTestSource = hitTestSource;
- });
- };
- /**
- * The module's name
- */
- public static readonly Name = WebXRFeatureName.HIT_TEST;
- /**
- * The (Babylon) version of this module.
- * This is an integer representing the implementation version.
- * This number does not correspond to the WebXR specs version
- */
- public static readonly Version = 2;
- /**
- * When set to true, each hit test will have its own position/rotation objects
- * When set to false, position and rotation objects will be reused for each hit test. It is expected that
- * the developers will clone them or copy them as they see fit.
- */
- public autoCloneTransformation: boolean = false;
- /**
- * Triggered when new babylon (transformed) hit test results are available
- */
- public onHitTestResultObservable: Observable<IWebXRHitResult[]> = new Observable();
- /**
- * Use this to temporarily pause hit test checks.
- */
- public paused: boolean = false;
- /**
- * Creates a new instance of the hit test feature
- * @param _xrSessionManager an instance of WebXRSessionManager
- * @param options options to use when constructing this feature
- */
- constructor(
- _xrSessionManager: WebXRSessionManager,
- /**
- * options to use when constructing this feature
- */
- public readonly options: IWebXRHitTestOptions = {}
- ) {
- super(_xrSessionManager);
- this.xrNativeFeatureName = "hit-test";
- Tools.Warn("Hit test is an experimental and unstable feature.");
- }
- /**
- * attach this feature
- * Will usually be called by the features manager
- *
- * @returns true if successful.
- */
- public attach(): boolean {
- if (!super.attach()) {
- return false;
- }
- // Feature enabled, but not available
- if (!this._xrSessionManager.session.requestHitTestSource) {
- return false;
- }
- if (!this.options.disablePermanentHitTest) {
- if (this._xrSessionManager.referenceSpace) {
- this.initHitTestSource(this._xrSessionManager.referenceSpace);
- }
- this._xrSessionManager.onXRReferenceSpaceChanged.add(this.initHitTestSource);
- }
- if (this.options.enableTransientHitTest) {
- const offsetRay = new XRRay(this.options.transientOffsetRay || {});
- this._xrSessionManager.session
- .requestHitTestSourceForTransientInput({
- profile: "generic-touchscreen",
- offsetRay,
- entityTypes: this.options.entityTypes
- })
- .then((hitSource) => {
- this._transientXrHitTestSource = hitSource;
- });
- }
- return true;
- }
- /**
- * detach this feature.
- * Will usually be called by the features manager
- *
- * @returns true if successful.
- */
- public detach(): boolean {
- if (!super.detach()) {
- return false;
- }
- if (this._xrHitTestSource) {
- this._xrHitTestSource.cancel();
- this._xrHitTestSource = null;
- }
- this._xrSessionManager.onXRReferenceSpaceChanged.removeCallback(this.initHitTestSource);
- if (this._transientXrHitTestSource) {
- this._transientXrHitTestSource.cancel();
- this._transientXrHitTestSource = null;
- }
- return true;
- }
- /**
- * Dispose this feature and all of the resources attached
- */
- public dispose(): void {
- super.dispose();
- this.onHitTestResultObservable.clear();
- }
- protected _onXRFrame(frame: XRFrame) {
- // make sure we do nothing if (async) not attached
- if (!this.attached || this.paused) {
- return;
- }
- if (this._xrHitTestSource) {
- const results = frame.getHitTestResults(this._xrHitTestSource);
- if (results.length) {
- this._processWebXRHitTestResult(results);
- }
- }
- if (this._transientXrHitTestSource) {
- let hitTestResultsPerInputSource = frame.getHitTestResultsForTransientInput(this._transientXrHitTestSource);
- hitTestResultsPerInputSource.forEach((resultsPerInputSource) => {
- if (resultsPerInputSource.results.length > 0) {
- this._processWebXRHitTestResult(resultsPerInputSource.results, resultsPerInputSource.inputSource);
- }
- });
- }
- }
- private _processWebXRHitTestResult(hitTestResults: XRHitTestResult[], inputSource?: XRInputSource) {
- const results: IWebXRHitResult[] = [];
- hitTestResults.forEach((hitTestResult) => {
- const pose = hitTestResult.getPose(this._xrSessionManager.referenceSpace);
- if (!pose) {
- return;
- }
- const pos = pose.transform.position;
- const quat = pose.transform.orientation;
- this._tmpPos.set(pos.x, pos.y, pos.z);
- this._tmpQuat.set(quat.x, quat.y, quat.z, quat.w);
- Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMat);
- if (!this._xrSessionManager.scene.useRightHandedSystem) {
- this._tmpPos.z *= -1;
- this._tmpQuat.z *= -1;
- this._tmpQuat.w *= -1;
- this._tmpMat.toggleModelMatrixHandInPlace();
- }
- const result: IWebXRHitResult = {
- position: this.autoCloneTransformation ? this._tmpPos.clone() : this._tmpPos,
- rotationQuaternion: this.autoCloneTransformation ? this._tmpQuat.clone() : this._tmpQuat,
- transformationMatrix: this.autoCloneTransformation ? this._tmpMat.clone() : this._tmpMat,
- inputSource: inputSource,
- isTransient: !!inputSource,
- xrHitResult: hitTestResult,
- };
- results.push(result);
- });
- if (results.length) {
- this.onHitTestResultObservable.notifyObservers(results);
- }
- }
- }
- //register the plugin versions
- WebXRFeaturesManager.AddWebXRFeature(
- WebXRHitTest.Name,
- (xrSessionManager, options) => {
- return () => new WebXRHitTest(xrSessionManager, options);
- },
- WebXRHitTest.Version,
- false
- );
|