diff --git a/packages/core/src/utils/SafeLoopArray.ts b/packages/core/src/utils/SafeLoopArray.ts index f8968e2cf..a5dbc7f7b 100644 --- a/packages/core/src/utils/SafeLoopArray.ts +++ b/packages/core/src/utils/SafeLoopArray.ts @@ -38,6 +38,17 @@ export class SafeLoopArray { this._loopArrayDirty = true; } + /** + * Remove item from array that pass the specified comparison function. + * @param filter - The comparison function + */ + findAndRemove(filter: (value: T) => boolean): void { + const array = this._array; + for (let i = array.length - 1; i >= 0; i--) { + filter(array[i]) && this.removeByIndex(i); + } + } + /** * The index of the item. * @param item - The item which want to get the index diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index ab2155d40..d670f3fe6 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1,3 +1,4 @@ export { ClearableObjectPool } from "./ClearableObjectPool"; export type { IPoolElement } from "./ObjectPool"; export { ReturnableObjectPool } from "./ReturnableObjectPool"; +export { SafeLoopArray } from "./SafeLoopArray"; diff --git a/packages/loader/src/PrefabLoader.ts b/packages/loader/src/PrefabLoader.ts index d0697f9ac..8eaeb792e 100644 --- a/packages/loader/src/PrefabLoader.ts +++ b/packages/loader/src/PrefabLoader.ts @@ -1,16 +1,7 @@ -import { - AssetPromise, - AssetType, - Engine, - EngineConfiguration, - Loader, - LoadItem, - resourceLoader, - ResourceManager -} from "@galacean/engine-core"; +import { AssetPromise, AssetType, Loader, LoadItem, resourceLoader, ResourceManager } from "@galacean/engine-core"; +import { PrefabParser } from "./prefab/PrefabParser"; import { PrefabResource } from "./prefab/PrefabResource"; import { IHierarchyFile } from "./resource-deserialize"; -import { PrefabParser } from "./prefab/PrefabParser"; @resourceLoader(AssetType.Prefab, ["prefab"]) export class PrefabLoader extends Loader { diff --git a/packages/loader/src/index.ts b/packages/loader/src/index.ts index 3005c869d..b1da2b95e 100644 --- a/packages/loader/src/index.ts +++ b/packages/loader/src/index.ts @@ -30,4 +30,3 @@ export { KTX2Loader, KTX2Transcoder } from "./ktx2/KTX2Loader"; export { KTX2TargetFormat } from "./ktx2/KTX2TargetFormat"; export * from "./resource-deserialize"; export * from "./prefab/PrefabResource"; -export { PrefabLoader } from "./PrefabLoader"; diff --git a/packages/loader/src/resource-deserialize/index.ts b/packages/loader/src/resource-deserialize/index.ts index e15048d71..67531f3e9 100644 --- a/packages/loader/src/resource-deserialize/index.ts +++ b/packages/loader/src/resource-deserialize/index.ts @@ -1,6 +1,6 @@ import { Engine } from "@galacean/engine-core"; import { BufferReader } from "./utils/BufferReader"; -import { decoderMap, decoder } from "./utils/Decorator"; +import { decoderMap } from "./utils/Decorator"; import { FileHeader } from "./utils/FileHeader"; export { MeshDecoder } from "./resources/mesh/MeshDecoder"; @@ -29,3 +29,7 @@ export * from "./resources/scene/SceneParser"; export * from "./resources/scene/MeshLoader"; export * from "./resources/scene/EditorTextureLoader"; export * from "./resources/parser/ParserContext"; + +export * from "./utils/BufferReader"; +export * from "./utils/Decorator"; +export * from "./utils/FileHeader"; diff --git a/packages/loader/src/resource-deserialize/resources/scene/SceneParser.ts b/packages/loader/src/resource-deserialize/resources/scene/SceneParser.ts index 9e358e614..8db0e8570 100644 --- a/packages/loader/src/resource-deserialize/resources/scene/SceneParser.ts +++ b/packages/loader/src/resource-deserialize/resources/scene/SceneParser.ts @@ -1,4 +1,4 @@ -import { Engine, ReferResource, Scene } from "@galacean/engine-core"; +import { Engine, Scene } from "@galacean/engine-core"; import type { IScene } from "../schema"; import { HierarchyParser } from "../parser/HierarchyParser"; import { ParserContext, ParserType } from "../parser/ParserContext"; diff --git a/packages/xr-webxr/src/WebXRDevice.ts b/packages/xr-webxr/src/WebXRDevice.ts index cc66ca502..f7df1ca50 100644 --- a/packages/xr-webxr/src/WebXRDevice.ts +++ b/packages/xr-webxr/src/WebXRDevice.ts @@ -25,7 +25,16 @@ export class WebXRDevice implements IXRDevice { } isSupportedFeature(type: XRFeatureType): boolean { - return true; + switch (type) { + case XRFeatureType.HitTest: + case XRFeatureType.PlaneTracking: + return typeof XRPlane !== "undefined"; + case XRFeatureType.AnchorTracking: + return typeof XRAnchor !== "undefined"; + case XRFeatureType.ImageTracking: + // @ts-ignore + return typeof XRImageTrackingResult !== "undefined"; + } } createPlatformFeature(type: XRFeatureType, ...args: any[]): WebXRFeature { diff --git a/packages/xr-webxr/src/WebXRSession.ts b/packages/xr-webxr/src/WebXRSession.ts index 468cfde6b..a35fecb02 100644 --- a/packages/xr-webxr/src/WebXRSession.ts +++ b/packages/xr-webxr/src/WebXRSession.ts @@ -140,7 +140,7 @@ export class WebXRSession implements IXRSession { session.removeEventListener("squeeze", onSessionEvent); session.removeEventListener("squeezestart", onSessionEvent); session.removeEventListener("squeezeend", onSessionEvent); - session.addEventListener("end", this._onSessionExit); + session.removeEventListener("end", this._onSessionExit); this._events.length = 0; } diff --git a/packages/xr/src/XRManagerExtended.ts b/packages/xr/src/XRManagerExtended.ts index 2376a09a9..e92a95e23 100644 --- a/packages/xr/src/XRManagerExtended.ts +++ b/packages/xr/src/XRManagerExtended.ts @@ -8,29 +8,22 @@ import { XRSessionManager } from "./session/XRSessionManager"; import { XRSessionMode } from "./session/XRSessionMode"; import { XRSessionState } from "./session/XRSessionState"; /** - * XRManager is the entry point of the XR system. + * @internal */ export class XRManagerExtended extends XRManager { /** @internal */ static _featureMap: Map, XRFeatureType> = new Map(); - /** Input manager for XR. */ override inputManager: XRInputManager; - /** Session manager for XR. */ override sessionManager: XRSessionManager; - /** Camera manager for XR. */ override cameraManager: XRCameraManager; + override readonly features: XRFeature[] = []; /** @internal */ _platformDevice: IXRDevice; private _origin: Entity; - private _features: XRFeature[]; - /** - * The current origin of XR space. - * @remarks The connection point between the virtual world and the real world ( XR Space ) - */ override get origin(): Entity { return this._origin; } @@ -42,45 +35,31 @@ export class XRManagerExtended extends XRManager { this._origin = value; } - /** - * Check if the specified feature is supported. - * @param type - The type of the feature - * @returns If the feature is supported - */ override isSupportedFeature(feature: TFeatureConstructor): boolean { return this._platformDevice.isSupportedFeature(XRManagerExtended._featureMap.get(feature)); } - /** - * Add feature based on the xr feature type. - * @param type - The type of the feature - * @param args - The constructor params of the feature - * @returns The feature which has been added - */ override addFeature XRFeature>( type: T, ...args: TFeatureConstructorArguments - ): XRFeature | null { + ): InstanceType | null { if (this.sessionManager._platformSession) { throw new Error("Cannot add feature when the session is initialized."); } - const { _features: features } = this; - for (let i = 0, n = features.length; i < n; i++) { - const feature = features[i]; - if (feature instanceof type) throw new Error("The feature has been added"); + if (!this._platformDevice.isSupportedFeature(XRManagerExtended._featureMap.get(type))) { + throw new Error("The feature is not supported"); } - const feature = new type(this, ...args); - this._features.push(feature); + const { features } = this; + for (let i = 0, n = features.length; i < n; i++) { + if (features[i] instanceof type) throw new Error("The feature has been added"); + } + const feature = new type(this, ...args) as InstanceType; + features.push(feature); return feature; } - /** - * Get feature which match the type. - * @param type - The type of the feature - * @returns The feature which match type - */ override getFeature(type: TFeatureConstructor): T | null { - const { _features: features } = this; + const { features } = this; for (let i = 0, n = features.length; i < n; i++) { const feature = features[i]; if (feature instanceof type) { @@ -89,26 +68,6 @@ export class XRManagerExtended extends XRManager { } } - override getFeatures(type: TFeatureConstructor, out?: T[]): T[] { - if (out) { - out.length = 0; - } else { - out = []; - } - const { _features: features } = this; - for (let i = 0, n = features.length; i < n; i--) { - const feature = features[i]; - feature instanceof type && out.push(feature); - } - return out; - } - - /** - * Enter XR immersive mode, when you call this method, it will initialize and display the XR virtual world. - * @param sessionMode - The mode of the session - * @param autoRun - Whether to automatically run the session, when `autoRun` is set to true, xr will start working immediately after initialization. Otherwise, you need to call `sessionManager.run` later to work. - * @returns A promise that resolves if the XR virtual world is entered, otherwise rejects - */ override enterXR(sessionMode: XRSessionMode, autoRun: boolean = true): Promise { const { sessionManager } = this; if (sessionManager._platformSession) { @@ -120,8 +79,9 @@ export class XRManagerExtended extends XRManager { return new Promise((resolve, reject) => { // 1. Check if this xr mode is supported sessionManager.isSupportedMode(sessionMode).then(() => { + sessionManager._setState(XRSessionState.Initializing); // 2. Initialize session - sessionManager._initialize(sessionMode, this._features).then(() => { + sessionManager._initialize(sessionMode, this.features).then(() => { autoRun && sessionManager.run(); resolve(); }, reject); @@ -129,10 +89,6 @@ export class XRManagerExtended extends XRManager { }); } - /** - * Exit XR immersive mode, when you call this method, it will destroy the XR virtual world. - * @returns A promise that resolves if the XR virtual world is destroyed, otherwise rejects - */ override exitXR(): Promise { return new Promise((resolve, reject) => { this.sessionManager._exit().then(() => { @@ -141,36 +97,26 @@ export class XRManagerExtended extends XRManager { }); } - /** - * @internal - */ override _initialize(engine: Engine, xrDevice: IXRDevice): void { - this._features = []; this._platformDevice = xrDevice; this.sessionManager = new XRSessionManager(this, engine); this.inputManager = new XRInputManager(this, engine); this.cameraManager = new XRCameraManager(this); } - /** - * @internal - */ override _update(): void { const { sessionManager } = this; if (sessionManager.state !== XRSessionState.Running) return; sessionManager._onUpdate(); this.inputManager._onUpdate(); this.cameraManager._onUpdate(); - const { _features: features } = this; + const { features } = this; for (let i = 0, n = features.length; i < n; i++) { const feature = features[i]; feature.enabled && feature._onUpdate(); } } - /** - * @internal - */ override _destroy(): void { if (this.sessionManager._platformSession) { this.exitXR().then(() => { @@ -185,23 +131,14 @@ export class XRManagerExtended extends XRManager { } } - /** - * @internal - */ override _getRequestAnimationFrame(): (callback: FrameRequestCallback) => number { return this.sessionManager._getRequestAnimationFrame(); } - /** - * @internal - */ override _getCancelAnimationFrame(): (id: number) => void { return this.sessionManager._getCancelAnimationFrame(); } - /** - * @internal - */ override _getCameraClearFlagsMask(type: CameraType): CameraClearFlags { return this.cameraManager._getCameraClearFlagsMask(type); } @@ -210,7 +147,7 @@ export class XRManagerExtended extends XRManager { * @internal */ _onSessionStop(): void { - const { _features: features } = this; + const { features } = this; for (let i = 0, n = features.length; i < n; i++) { const feature = features[i]; feature.enabled && feature._onSessionStop(); @@ -221,7 +158,7 @@ export class XRManagerExtended extends XRManager { * @internal */ _onSessionInit(): void { - const { _features: features } = this; + const { features } = this; for (let i = 0, n = features.length; i < n; i++) { const feature = features[i]; feature.enabled && feature._onSessionInit(); @@ -233,7 +170,7 @@ export class XRManagerExtended extends XRManager { */ _onSessionStart(): void { this.cameraManager._onSessionStart(); - const { _features: features } = this; + const { features } = this; for (let i = 0, n = features.length; i < n; i++) { const feature = features[i]; feature.enabled && feature._onSessionStart(); @@ -245,7 +182,7 @@ export class XRManagerExtended extends XRManager { */ _onSessionExit(): void { this.cameraManager._onSessionExit(); - const { _features: features } = this; + const { features } = this; for (let i = 0, n = features.length; i < n; i++) { const feature = features[i]; feature.enabled && feature._onSessionExit(); @@ -264,6 +201,11 @@ export function registerXRFeature(type: XRFeatureType): (fe }; } +export interface IXRListener { + fn: (...args: any[]) => any; + destroyed?: boolean; +} + type TFeatureConstructor = new (xrManager: XRManagerExtended, ...args: any[]) => T; type TFeatureConstructorArguments XRFeature> = @@ -277,6 +219,8 @@ declare module "@galacean/engine" { sessionManager: XRSessionManager; /** Camera manager for XR. */ cameraManager: XRCameraManager; + /** Initialized features. */ + readonly features: XRFeature[]; /** * The current origin of XR space. @@ -285,19 +229,6 @@ declare module "@galacean/engine" { get origin(): Entity; set origin(value: Entity); - /** - * Get all initialized features at this moment. - * @param type - The type of the feature - */ - getFeatures(type: TFeatureConstructor): T[]; - - /** - * Get all initialized features at this moment. - * @param type - The type of the feature - * @param out - Save all features in `out` - */ - getFeatures(type: TFeatureConstructor, out: T[]): T[]; - /** * Check if the specified feature is supported. * @param type - The type of the feature @@ -314,7 +245,7 @@ declare module "@galacean/engine" { addFeature XRFeature>( type: T, ...args: TFeatureConstructorArguments - ): XRFeature | null; + ): InstanceType | null; /** * Get feature which match the type. @@ -322,7 +253,6 @@ declare module "@galacean/engine" { * @returns The feature which match type */ getFeature(type: TFeatureConstructor): T | null; - getFeatures(type: TFeatureConstructor, out?: T[]): T[]; /** * Enter XR immersive mode, when you call this method, it will initialize and display the XR virtual world. * @param sessionMode - The mode of the session diff --git a/packages/xr/src/feature/trackable/XRTrackableFeature.ts b/packages/xr/src/feature/trackable/XRTrackableFeature.ts index 62c5fc085..ba3abe480 100644 --- a/packages/xr/src/feature/trackable/XRTrackableFeature.ts +++ b/packages/xr/src/feature/trackable/XRTrackableFeature.ts @@ -1,4 +1,6 @@ +import { SafeLoopArray } from "@galacean/engine"; import { IXRTrackablePlatformFeature } from "@galacean/engine-design"; +import { IXRListener } from "../../XRManagerExtended"; import { XRTrackingState } from "../../input/XRTrackingState"; import { XRFeature } from "../XRFeature"; import { XRFeatureType } from "../XRFeatureType"; @@ -9,9 +11,10 @@ import { XRTracked } from "./XRTracked"; /** * The base class of XR trackable manager. */ -export abstract class XRTrackableFeature> extends XRFeature< - IXRTrackablePlatformFeature -> { +export abstract class XRTrackableFeature< + T extends XRTracked = XRTracked, + K extends XRRequestTracking = XRRequestTracking +> extends XRFeature> { protected static _uuid = 0; protected _requestTrackings: K[] = []; @@ -20,14 +23,14 @@ export abstract class XRTrackableFeature = {}; - private _listeners: ((added: readonly T[], updated: readonly T[], removed: readonly T[]) => void)[] = []; + private _listeners: SafeLoopArray = new SafeLoopArray(); /** * Add a listening function for tracked object changes. * @param listener - The listening function */ addChangedListener(listener: (added: readonly T[], updated: readonly T[], removed: readonly T[]) => void): void { - this._listeners.push(listener); + this._listeners.push({ fn: listener }); } /** @@ -35,11 +38,7 @@ export abstract class XRTrackableFeature void): void { - const { _listeners: listeners } = this; - const index = listeners.indexOf(listener); - if (index >= 0) { - listeners.splice(index, 1); - } + this._listeners.findAndRemove((value) => (value.fn === listener ? (value.destroyed = true) : false)); } override _onUpdate(): void { @@ -47,7 +46,6 @@ export abstract class XRTrackableFeature 0 || updated.length > 0 || removed.length > 0) { + const listeners = this._listeners.getLoopArray(); for (let i = 0, n = listeners.length; i < n; i++) { - listeners[i](added, updated, removed); + const listener = listeners[i]; + !listener.destroyed && listener.fn(added, updated, removed); } } } @@ -120,12 +120,8 @@ export abstract class XRTrackableFeature (value.destroyed = true)); } protected _addRequestTracking(requestTracking: K): void { diff --git a/packages/xr/src/index.ts b/packages/xr/src/index.ts index e6a7db7c4..15e94bdbd 100644 --- a/packages/xr/src/index.ts +++ b/packages/xr/src/index.ts @@ -4,6 +4,8 @@ import "./XRManagerExtended"; export { XRPose } from "./XRPose"; // xr feature export { XRFeature } from "./feature/XRFeature"; +export { XRTrackableFeature } from "./feature/trackable/XRTrackableFeature"; +export { XRTracked } from "./feature/trackable/XRTracked"; // camera export { XRCameraManager } from "./feature/camera/XRCameraManager"; // hitTest @@ -38,3 +40,6 @@ export { XRFeatureType } from "./feature/XRFeatureType"; export { XRRequestTrackingState } from "./feature/trackable/XRRequestTrackingState"; export { XRInputEventType } from "./input/XRInputEventType"; export { XRTargetRayMode } from "./input/XRTargetRayMode"; + +export * from "./loader/XRReferenceImageDecoder"; +export * from "./loader/XRReferenceImageLoader"; diff --git a/packages/xr/src/loader/XRReferenceImageDecoder.ts b/packages/xr/src/loader/XRReferenceImageDecoder.ts new file mode 100644 index 000000000..b40eaed3f --- /dev/null +++ b/packages/xr/src/loader/XRReferenceImageDecoder.ts @@ -0,0 +1,17 @@ +import { BufferReader, Engine, decoder } from "@galacean/engine"; +import { XRReferenceImage } from "../feature/trackable/image/XRReferenceImage"; + +@decoder("XRReferenceImage") +export class XRReferenceImageDecoder { + static decode(engine: Engine, bufferReader: BufferReader): Promise { + return new Promise((resolve, reject) => { + const physicalWidth = bufferReader.nextFloat32(); + bufferReader.nextUint8(); + const img = new Image(); + img.onload = () => { + resolve(new XRReferenceImage("", img, physicalWidth)); + }; + img.src = URL.createObjectURL(new window.Blob([bufferReader.nextImagesData(1)[0]])); + }); + } +} diff --git a/packages/xr/src/loader/XRReferenceImageLoader.ts b/packages/xr/src/loader/XRReferenceImageLoader.ts new file mode 100644 index 000000000..91f39db55 --- /dev/null +++ b/packages/xr/src/loader/XRReferenceImageLoader.ts @@ -0,0 +1,17 @@ +import { AssetPromise, decode, Loader, LoadItem, resourceLoader, ResourceManager } from "@galacean/engine"; +import { XRReferenceImage } from "../feature/trackable/image/XRReferenceImage"; + +@resourceLoader("XRReferenceImage", []) +export class XRReferenceImageLoader extends Loader { + load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { + return new AssetPromise((resolve, reject) => { + this.request(item.url, { ...item, type: "arraybuffer" }) + .then((data) => { + decode(data, resourceManager.engine).then((referenceImage) => { + resolve(referenceImage); + }); + }) + .catch(reject); + }); + } +} diff --git a/packages/xr/src/session/XRSessionManager.ts b/packages/xr/src/session/XRSessionManager.ts index cf40c49f5..f56cbd678 100644 --- a/packages/xr/src/session/XRSessionManager.ts +++ b/packages/xr/src/session/XRSessionManager.ts @@ -1,6 +1,6 @@ -import { Engine } from "@galacean/engine"; +import { Engine, SafeLoopArray } from "@galacean/engine"; import { IHardwareRenderer, IXRSession } from "@galacean/engine-design"; -import { XRManagerExtended } from "../XRManagerExtended"; +import { IXRListener, XRManagerExtended } from "../XRManagerExtended"; import { XRFeature } from "../feature/XRFeature"; import { XRSessionMode } from "./XRSessionMode"; import { XRSessionState } from "./XRSessionState"; @@ -17,6 +17,7 @@ export class XRSessionManager { private _rhi: IHardwareRenderer; private _raf: (callback: FrameRequestCallback) => number; private _caf: (id: number) => void; + private _listeners: SafeLoopArray = new SafeLoopArray(); /** * The current session mode( AR or VR ). @@ -78,7 +79,7 @@ export class XRSessionManager { throw new Error("Without session to run."); } platformSession.start(); - this._state = XRSessionState.Running; + this._setState(XRSessionState.Running); this._xrManager._onSessionStart(); if (!engine.isPaused) { engine.pause(); @@ -100,7 +101,7 @@ export class XRSessionManager { rhi._mainFrameBuffer = null; rhi._mainFrameWidth = rhi._mainFrameHeight = 0; platformSession.stop(); - this._state = XRSessionState.Paused; + this._setState(XRSessionState.Paused); this._xrManager._onSessionStop(); if (!engine.isPaused) { engine.pause(); @@ -108,6 +109,34 @@ export class XRSessionManager { } } + /** + * Add a listening function for session state changes. + * @param listener - The listening function + */ + addStateChangedListener(listener: (state: XRSessionState) => void): void { + this._listeners.push({ fn: listener }); + } + + /** + * Remove a listening function of session state changes. + * @param listener - The listening function + */ + removeStateChangedListener(listener: (state: XRSessionState) => void): void { + this._listeners.findAndRemove((value) => (value.fn === listener ? (value.destroyed = true) : false)); + } + + /** + * @internal + */ + _setState(value: XRSessionState) { + this._state = value; + const listeners = this._listeners.getLoopArray(); + for (let i = 0, n = listeners.length; i < n; i++) { + const listener = listeners[i]; + !listener.destroyed && listener.fn(value); + } + } + /** * @internal */ @@ -125,7 +154,7 @@ export class XRSessionManager { .then((platformSession: IXRSession) => { this._mode = mode; this._platformSession = platformSession; - this._state = XRSessionState.Initialized; + this._setState(XRSessionState.Initialized); platformSession.setSessionExitCallBack(this._onSessionExit); platformSession.addEventListener(); xrManager._onSessionInit(); @@ -183,7 +212,7 @@ export class XRSessionManager { rhi._mainFrameWidth = rhi._mainFrameHeight = 0; platformSession.removeEventListener(); this._platformSession = null; - this._state = XRSessionState.None; + this._setState(XRSessionState.None); this._xrManager._onSessionExit(); if (!engine.isPaused) { engine.pause(); @@ -194,5 +223,8 @@ export class XRSessionManager { /** * @internal */ - _onDestroy(): void {} + _onDestroy(): void { + this._listeners.findAndRemove((value) => (value.destroyed = true)); + this._raf = this._caf = null; + } } diff --git a/packages/xr/src/session/XRSessionState.ts b/packages/xr/src/session/XRSessionState.ts index c31055104..7895f09c1 100644 --- a/packages/xr/src/session/XRSessionState.ts +++ b/packages/xr/src/session/XRSessionState.ts @@ -4,6 +4,8 @@ export enum XRSessionState { /** Not initialized. */ None, + /** Initializing session. */ + Initializing, /** Initialized but not started. */ Initialized, /** Running. */