Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions src/Cognitive3D.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,42 @@ export interface Cognitive3DConstructionProps {
* @zdefault false
*/
enableDebug: boolean;
/**
* @zui
* @zlabel Enable Room Capture
* @zdefault true
*/
enableRoomCapture: boolean;
/**
* @zui
* @zlabel Room Data Limit
* @zdefault 64
*/
roomDataLimit: number;
/**
* @zui
* @zlabel Enable Fixations
* @zdefault true
*/
enableFixations: boolean;
/**
* @zui
* @zlabel Allow Fixations Without Eye Tracking
* @zdefault false
*/
allowFixationWithoutEyeTracking: boolean;
/**
* @zui
* @zlabel Fixation Data Limit
* @zdefault 256
*/
fixationDataLimit: number;
/**
* @zui
* @zlabel Fetch Remote Variables
* @zdefault true
*/
autoFetchRemoteVariables: boolean;

}

Expand All @@ -57,6 +93,8 @@ export class Cognitive3D extends Behavior<Component> {
private sceneContext: ThreeSceneContext;
private _xrSession: XRSession | null = null;
private _xrSessionEndHandler: (() => void) | null = null;
private _originalRequestSession: ((mode: any, init?: any) => Promise<XRSession>) | null = null;
private _remoteVariablesHandler: (() => void) | null = null;

constructor(contextManager: ContextManager, instance: Component, protected constructorProps: Cognitive3DConstructionProps) {
super(contextManager, instance);
Expand All @@ -78,6 +116,12 @@ export class Cognitive3D extends Behavior<Component> {
APIKey: this.constructorProps.apiKey,
LOG: this.constructorProps.enableDebug,
gazeTrackingSource: "engine",
enableRoomCapture: this.constructorProps.enableRoomCapture !== false,
roomDataLimit: this.constructorProps.roomDataLimit || 64,
enableFixation: this.constructorProps.enableFixations !== false,
allowFixationWithoutEyeTracking: this.constructorProps.allowFixationWithoutEyeTracking === true,
fixationDataLimit: this.constructorProps.fixationDataLimit || 256,
Comment on lines +120 to +123
autoFetchRemoteVariables: this.constructorProps.autoFetchRemoteVariables !== false,
allSceneData: [{
sceneId: this.constructorProps.sceneId,
sceneName: this.constructorProps.sceneName,
Expand All @@ -99,6 +143,12 @@ export class Cognitive3D extends Behavior<Component> {
this.ctx.c3dAdapter = this.c3dAdapter;
this.ctx.sceneName = this.constructorProps.sceneName;
this.ctx.enableDebug = this.constructorProps.enableDebug;
this.ctx.roomCaptureEnabled = this.constructorProps.enableRoomCapture !== false;
this.ctx.fixationsEnabled = this.constructorProps.enableFixations !== false;

if (this.ctx.roomCaptureEnabled) {
this.installGeometryFeatures();
}
this.ctx.registerDynamicObject = (b) => this.registerDynamicObject(b);

if (this.xrContext) {
Expand All @@ -124,6 +174,61 @@ export class Cognitive3D extends Behavior<Component> {
}
}

private installGeometryFeatures() {
const xr = (navigator as any).xr;
if (!xr || typeof xr.requestSession !== "function" || this._originalRequestSession) {
return;
}
Comment on lines +177 to +181
const original = xr.requestSession.bind(xr);
this._originalRequestSession = original;
xr.requestSession = (mode: any, init?: any) => {
const next = Object.assign({}, init || {});
const features = Array.isArray(next.optionalFeatures) ? next.optionalFeatures.slice() : [];
["plane-detection", "mesh-detection"].forEach(feature => {
if (features.indexOf(feature) === -1) features.push(feature);
});
next.optionalFeatures = features;
return original(mode, next);
};
}

private removeGeometryFeatures() {
const xr = (navigator as any).xr;
if (xr && this._originalRequestSession) {
xr.requestSession = this._originalRequestSession;
}
this._originalRequestSession = null;
}

private setupRemoteVariables() {
const remote = this.c3d && this.c3d.remoteVariables;
if (!remote || typeof remote.onRemoteVariablesAvailable !== "function") {
return;
}

if (this._remoteVariablesHandler && typeof remote.offRemoteVariablesAvailable === "function") {
remote.offRemoteVariablesAvailable(this._remoteVariablesHandler);
}

this._remoteVariablesHandler = () => {
this.ctx.remoteVariablesReady = true;
this.ctx.debug(`Cognitive3D: ${this.ctx.listRemoteVariables().length} remote variables available.`);
this.ctx.onRemoteVariablesAvailable.emit();
};

remote.onRemoteVariablesAvailable(this._remoteVariablesHandler);
}

private reportGeometryFeatures(session: XRSession) {
const granted = (session as any).enabledFeatures ? Array.from((session as any).enabledFeatures) : [];
const hasPlanes = granted.indexOf("plane-detection") !== -1;
const hasMeshes = granted.indexOf("mesh-detection") !== -1;
this.ctx.debug(`Cognitive3D: plane-detection ${hasPlanes ? "granted" : "unavailable"}, mesh-detection ${hasMeshes ? "granted" : "unavailable"}.`);
if (this.ctx.roomCaptureEnabled && !hasPlanes && !hasMeshes) {
console.warn("Cognitive3D: Room Capture is enabled but the runtime granted no geometry features. On Quest this requires an immersive-ar session and a completed Space Setup.");
}
}

public registerDynamicObject(behavior: IDynamicObjectBehavior) {
// Add to the internal registry so we can re-initialize on session start
this.ctx.trackedBehaviors.add(behavior);
Expand Down Expand Up @@ -251,6 +356,8 @@ export class Cognitive3D extends Behavior<Component> {

if (success) {
this.ctx.debug("Cognitive3D: Session Started");
this.reportGeometryFeatures(session);
this.setupRemoteVariables();

const renderer = this.threeContext.renderer as THREE.WebGLRenderer;
const scene = this.sceneContext.scene;
Expand Down Expand Up @@ -463,6 +570,14 @@ export class Cognitive3D extends Behavior<Component> {
public override dispose() {
window.removeEventListener('keydown', this.handleKeyDown);

this.removeGeometryFeatures();

const remote = this.c3d && this.c3d.remoteVariables;
if (remote && this._remoteVariablesHandler && typeof remote.offRemoteVariablesAvailable === "function") {
remote.offRemoteVariablesAvailable(this._remoteVariablesHandler);
}
this._remoteVariablesHandler = null;

if (this._xrSession && this._xrSessionEndHandler) {
this._xrSession.removeEventListener("end", this._xrSessionEndHandler);
}
Expand All @@ -478,6 +593,7 @@ export class Cognitive3D extends Behavior<Component> {
this.ctx.registerDynamicObject = null;
this.ctx.trackedBehaviors.clear();
this.ctx.registeredWithSDK.clear();
this.ctx.remoteVariablesReady = false;

return super.dispose();
}
Expand Down
40 changes: 39 additions & 1 deletion src/Cognitive3DContext.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, ContextManager } from "@zcomponent/core";
import { Context, ContextManager, Event } from "@zcomponent/core";
import * as THREE from "three";

export interface IDynamicObjectBehavior {
Expand All @@ -18,6 +18,10 @@ export class Cognitive3DContext extends Context<Cognitive3DContextProps> {
public pendingRegistrations: IDynamicObjectBehavior[] = [];
public sceneName: string = "";
public enableDebug: boolean = false;
public roomCaptureEnabled: boolean = false;
public fixationsEnabled: boolean = false;
public remoteVariablesReady: boolean = false;
public onRemoteVariablesAvailable: Event<[]> = new Event();

/** Set by the Cognitive3D behavior so DynamicObjects can trigger full SDK registration. */
public registerDynamicObject: ((behavior: IDynamicObjectBehavior) => void) | null = null;
Expand Down Expand Up @@ -46,12 +50,46 @@ export class Cognitive3DContext extends Context<Cognitive3DContextProps> {
this.c3d.customEvent.send(category, position, properties);
}

public getRemoteVariable<T>(name: string, defaultValue: T): T {
const remote = this.c3d && this.c3d.remoteVariables;
if (!remote || typeof remote.getValue !== "function") return defaultValue;
try {
return remote.getValue(name, defaultValue) as T;
} catch (e) {
return defaultValue;
}
}

public listRemoteVariables(): any[] {
const remote = this.c3d && this.c3d.remoteVariables;
if (!remote || typeof remote.listAllVariables !== "function") return [];
try {
return remote.listAllVariables();
} catch (e) {
return [];
}
}

public fetchRemoteVariables(identifier?: string): Promise<boolean> {
const remote = this.c3d && this.c3d.remoteVariables;
if (!remote || typeof remote.fetchVariables !== "function") return Promise.resolve(false);
try {
return Promise.resolve(remote.fetchVariables(identifier));
} catch (e) {
return Promise.resolve(false);
}
}

dispose() {
this.c3d = null;
this.c3dAdapter = null;
this.trackedBehaviors.clear();
this.registeredWithSDK.clear();
this.pendingRegistrations = [];
this.roomCaptureEnabled = false;
this.fixationsEnabled = false;
this.remoteVariablesReady = false;
this.onRemoteVariablesAvailable.clearListeners();
return super.dispose();
}
}