XR editor adaptation (#2165)

* feat: xr support editor
This commit is contained in:
AZhan
2024-07-19 19:48:05 +08:00
committed by GitHub
parent f0655f9e8c
commit 243976270b
15 changed files with 152 additions and 138 deletions

View File

@@ -38,6 +38,17 @@ export class SafeLoopArray<T> {
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

View File

@@ -1,3 +1,4 @@
export { ClearableObjectPool } from "./ClearableObjectPool";
export type { IPoolElement } from "./ObjectPool";
export { ReturnableObjectPool } from "./ReturnableObjectPool";
export { SafeLoopArray } from "./SafeLoopArray";

View File

@@ -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<PrefabResource> {

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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<TFeatureConstructor<XRFeature>, 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<T extends XRFeature>(feature: TFeatureConstructor<T>): 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<T extends new (xrManager: XRManagerExtended, ...args: any[]) => XRFeature>(
type: T,
...args: TFeatureConstructorArguments<T>
): XRFeature | null {
): InstanceType<T> | 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<T>;
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<T extends XRFeature>(type: TFeatureConstructor<T>): 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<T extends XRFeature>(type: TFeatureConstructor<T>, 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<void> {
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<void> {
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<T extends XRFeature>(type: XRFeatureType): (fe
};
}
export interface IXRListener {
fn: (...args: any[]) => any;
destroyed?: boolean;
}
type TFeatureConstructor<T extends XRFeature> = new (xrManager: XRManagerExtended, ...args: any[]) => T;
type TFeatureConstructorArguments<T extends new (xrManager: XRManagerExtended, ...args: any[]) => 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<T extends XRFeature>(type: TFeatureConstructor<T>): T[];
/**
* Get all initialized features at this moment.
* @param type - The type of the feature
* @param out - Save all features in `out`
*/
getFeatures<T extends XRFeature>(type: TFeatureConstructor<T>, 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<T extends new (xrManager: XRManagerExtended, ...args: any[]) => XRFeature>(
type: T,
...args: TFeatureConstructorArguments<T>
): XRFeature | null;
): InstanceType<T> | null;
/**
* Get feature which match the type.
@@ -322,7 +253,6 @@ declare module "@galacean/engine" {
* @returns The feature which match type
*/
getFeature<T extends XRFeature>(type: TFeatureConstructor<T>): T | null;
getFeatures<T extends XRFeature>(type: TFeatureConstructor<T>, 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

View File

@@ -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<T extends XRTracked, K extends XRRequestTracking<T>> extends XRFeature<
IXRTrackablePlatformFeature<T, K>
> {
export abstract class XRTrackableFeature<
T extends XRTracked = XRTracked,
K extends XRRequestTracking<T> = XRRequestTracking<T>
> extends XRFeature<IXRTrackablePlatformFeature<T, K>> {
protected static _uuid = 0;
protected _requestTrackings: K[] = [];
@@ -20,14 +23,14 @@ export abstract class XRTrackableFeature<T extends XRTracked, K extends XRReques
protected _updated: T[] = [];
protected _removed: T[] = [];
protected _statusSnapshot: Record<number, XRTrackingState> = {};
private _listeners: ((added: readonly T[], updated: readonly T[], removed: readonly T[]) => void)[] = [];
private _listeners: SafeLoopArray<IXRListener> = new SafeLoopArray<IXRListener>();
/**
* 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<T extends XRTracked, K extends XRReques
* @param listener - The listening function
*/
removeChangedListener(listener: (added: readonly T[], updated: readonly T[], removed: readonly T[]) => 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<T extends XRTracked, K extends XRReques
const { frame: platformFrame } = platformSession;
const {
_platformFeature: platformFeature,
_listeners: listeners,
_requestTrackings: requestTrackings,
_statusSnapshot: statusSnapshot,
_tracked: allTracked,
@@ -108,8 +106,10 @@ export abstract class XRTrackableFeature<T extends XRTracked, K extends XRReques
requestTrackings[i].state === XRRequestTrackingState.Destroyed && requestTrackings.splice(i, 1);
}
if (added.length > 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<T extends XRTracked, K extends XRReques
override _onSessionExit(): void {
// prettier-ignore
this._requestTrackings.length = this._tracked.length = this._added.length = this._updated.length = this._removed.length = 0;
}
override _onDestroy(): void {
// prettier-ignore
this._requestTrackings.length = this._tracked.length = this._added.length = this._updated.length = this._removed.length = 0;
this._requestTrackings.length = this._tracked.length = this._added.length = this._updated.length = this._removed.length = 0;
this._listeners.findAndRemove((value) => (value.destroyed = true));
}
protected _addRequestTracking(requestTracking: K): void {

View File

@@ -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";

View File

@@ -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<XRReferenceImage> {
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]]));
});
}
}

View File

@@ -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<XRReferenceImage> {
load(item: LoadItem, resourceManager: ResourceManager): AssetPromise<XRReferenceImage> {
return new AssetPromise((resolve, reject) => {
this.request<ArrayBuffer>(item.url, { ...item, type: "arraybuffer" })
.then((data) => {
decode<XRReferenceImage>(data, resourceManager.engine).then((referenceImage) => {
resolve(referenceImage);
});
})
.catch(reject);
});
}
}

View File

@@ -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<IXRListener> = new SafeLoopArray<IXRListener>();
/**
* 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;
}
}

View File

@@ -4,6 +4,8 @@
export enum XRSessionState {
/** Not initialized. */
None,
/** Initializing session. */
Initializing,
/** Initialized but not started. */
Initialized,
/** Running. */