Skip to content

Commit

Permalink
Merge pull request #215 from TiborUdvari/fix/ar-mode
Browse files Browse the repository at this point in the history
Refactor / p5xr consolidation / Fix AR mode
  • Loading branch information
TiborUdvari authored Jul 29, 2024
2 parents c7d33ce + 1cd7192 commit 26f918b
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 227 deletions.
14 changes: 14 additions & 0 deletions examples/ar/hello-cube/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function preload() {
createARCanvas();
}

function setup() {
describe("A cube waiting to be seen");
}

function draw() {
push();
translate(0, 0, -0.4);
box(0.1, 0.1, 0.1);
pop();
}
17 changes: 17 additions & 0 deletions examples/ar/hello-cube/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no'>
<meta name='mobile-web-app-capable' content='yes'>
<meta name='apple-mobile-web-app-capable' content='yes'>

<title>AR EXAMPLE #1 : Hello Cube</title>
<script src='../../../node_modules/p5/lib/p5.js'></script>
<script src='../../../dist/p5xr.min.js'></script>
</head>
<body>
<header></header>
<script src="example.js"></script>
</body>
</html>
6 changes: 2 additions & 4 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ window.p5xr = {
* @section VR
* @category Initialization
*/
p5.prototype.createVRCanvas = function (xrButton) {
p5.prototype.createVRCanvas = function () {
noLoop();
p5xr.instance = new p5vr(xrButton);
p5xr.instance.__initVR();
p5xr.instance = new p5vr();
};

/**
Expand All @@ -46,7 +45,6 @@ p5.prototype.createVRCanvas = function (xrButton) {
p5.prototype.createARCanvas = function () {
noLoop();
p5xr.instance = new p5ar();
p5xr.instance.initAR();
};

/**
Expand Down
190 changes: 162 additions & 28 deletions src/p5xr/core/p5xr.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ import p5xrInput from './p5xrInput';
*
* @constructor
*
* @param {Object} [options={}] - Configuration options for the XR session
* @param {Array<"anchors" | "bounded-floor" | "depth-sensing" | "dom-overlay" |
* "hand-tracking" | "hit-test" | "layers" | "light-estimation" | "local" |
* "local-floor" | "secondary-views" | "unbounded" | "viewer">} [options.requiredFeatures=[]] - Required features
* @param {Object} [options={}] - Configuration options for the XR session
* @param {Array<"anchors" | "bounded-floor" | "depth-sensing" | "dom-overlay" |
* "hand-tracking" | "hit-test" | "layers" | "light-estimation" | "local" |
* "local-floor" | "secondary-views" | "unbounded" | "viewer">} [options.optionalFeatures=[]] - Optional features
*
* @property mode {"inline" | "immersive-ar" | "immersive-vr"} WebXR session mode
* @property vrDevice {XRDevice} the current VR compatible device
* @property vrSession {XRSession} the current VR session
* @property vrFrameOfRef {XRFrameOfReference} the current VR frame of reference
Expand All @@ -16,11 +26,17 @@ import p5xrInput from './p5xrInput';
* @property curClearColor {Color} background clear color set by global `setVRBackgroundColor`
*/
export default class p5xr {
constructor(xrButton) {
constructor(options = {}) {
const {
requiredFeatures = [],
optionalFeatures = [],
} = options;

this.xrDevice = null;
this.xrButton = xrButton || null;
this.isVR = null;
this.mode = 'inline';
this.hasImmersive = null;
this.isImmersive = false;
this.xrSession = null;
this.xrRefSpace = null;
this.xrViewerSpace = null;
Expand All @@ -29,6 +45,9 @@ export default class p5xr {
this.gl = null;
this.curClearColor = color(255, 255, 255);
this.viewer = new p5xrViewer();

this.requiredFeatures = requiredFeatures;
this.optionalFeatures = optionalFeatures;
}

/**
Expand Down Expand Up @@ -125,17 +144,135 @@ export default class p5xr {
// WebXR availabilty
if (navigator?.xr) {
console.log('XR Available');
const mode = this.isVR ? 'VR' : 'AR';
const session = this.isVR ? 'immersive-vr' : 'immersive-ar';
const supported = await navigator.xr.isSessionSupported(session);
const supported = await navigator.xr.isSessionSupported(this.mode);
this.hasImmersive = supported;
this.xrButton.setAvailable(supported, mode);
this.xrButton.setAvailable(supported, this.mode);
} else {
console.log('XR Not Available');
this.xrButton.disable();
}
}

/**
* Helper function to reset XR and GL, should be called between
* ending an XR session and starting a new XR session
* @method resetXR
*/
resetXR() {
this.xrDevice = null;
this.xrSession = null;
this.xrRefSpace = null;
this.xrViewerSpace = null;
this.xrHitTestSource = null;
this.gl = null;
this.frame = null;
}

/**
* `navigator.xr.requestSession()` must be called within a user gesture event.
* @private
* @ignore
*/
__onXRButtonClicked() {
if (this.hasImmersive) {
console.log(`Requesting session with mode: ${this.mode}`);
this.isImmersive = true;
this.resetXR();
navigator.xr
.requestSession(this.mode, {
requiredFeatures: this.requiredFeatures,
optionalFeatures: this.optionalFeatures,
})
.then(this.__startSketch.bind(this))
.catch((error) => {
console.error(`An error occured activating ${this.mode}: ${error}`);
});
} else {
this.xrButton.hide();
}
}

/**
* This is where the actual p5 canvas is first created, and
* the GL rendering context is accessed by p5vr.
* The current XRSession also gets a frame of reference and
* base rendering layer. <br>
* @param {XRSession}
* @private
* @ignore
*/
__startSketch(session) {
this.xrSession = session;
this.canvas = p5.instance.canvas;
this.canvas.style.visibility = 'visible';

if (typeof window.setup === 'function') {
window.setup();
p5.instance._millisStart = window.performance.now();
}
const refSpaceRequest = this.isImmersive ? 'local' : 'viewer';
this.xrSession.requestReferenceSpace(refSpaceRequest).then((refSpace) => {
this.xrRefSpace = refSpace;
// Inform the session that we're ready to begin drawing.
this.xrSession.requestAnimationFrame(this.__onXRFrame.bind(this));
if (!this.isImmersive) {
this.xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(this.xrSession, this.gl),
inlineVerticalFieldOfView: 70 * (Math.PI / 180),
});
this.addInlineViewListeners(this.canvas);
}
});
this.__onRequestSession();
}

/**
* Requests a reference space and makes the p5's WebGL layer XR compatible.
* @private
* @ignore
*/
__onRequestSession() {
p5.instance._renderer._curCamera.cameraType = 'custom';
const refSpaceRequest = this.isImmersive ? 'local' : 'viewer';

this.gl = this.canvas.getContext(p5.instance.webglVersion);
this.gl
.makeXRCompatible()
.then(() => {
// Use the p5's WebGL context to create a XRWebGLLayer and set it as the
// sessions baseLayer. This allows any content rendered to the layer to
// be displayed on the XRDevice;
this.xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(this.xrSession, this.gl),
});
// TODO : need better way to handle feature-specific actions
if (this.requiredFeatures.includes('hit-test')) {
this.xrSession.requestReferenceSpace('viewer').then((refSpace) => {
this.xrViewerSpace = refSpace;
this.xrSession
.requestHitTestSource({ space: this.xrViewerSpace })
.then((hitTestSource) => {
this.xrHitTestSource = hitTestSource;
});
});
}

// Get a frame of reference, which is required for querying poses.
// 'local' places the initial pose relative to initial location of viewer
// 'viewer' is only for inline experiences and only allows rotation
this.xrSession
.requestReferenceSpace(refSpaceRequest)
.then((refSpace) => {
this.xrRefSpace = refSpace;
// Request initial animation frame
this.xrSession.requestAnimationFrame(this.__onXRFrame.bind(this));
});
})
.catch((e) => {
console.log(e);
});
}

/**
* This is the method that is attached to the event that announces
* availability of a new frame. The next animation frame is requested here,
Expand Down Expand Up @@ -180,6 +317,20 @@ export default class p5xr {
this.clearVR();
}

const context = window;
const userCalculate = context.calculate;
if (this.viewer.pose.views.length > 1) {
if (typeof userCalculate === 'function') {
userCalculate();
}
const now = window.performance.now();
p5.instance.deltaTime = now - p5.instance._lastFrameTime;
p5.instance._frameRate = 1000.0 / p5.instance.deltaTime;
p5.instance._setProperty('deltaTime', p5.instance.deltaTime);
p5.instance._lastFrameTime = now;
context._setProperty('frameCount', context.frameCount + 1);
}

let i = 0;
for (const view of this.viewer.pose.views) {
this.viewer.view = view;
Expand All @@ -189,7 +340,7 @@ export default class p5xr {
viewport.x,
viewport.y,
viewport.width * scaleFactor,
viewport.height * scaleFactor
viewport.height * scaleFactor,
);
this.__updateViewport(viewport);

Expand Down Expand Up @@ -219,28 +370,11 @@ export default class p5xr {
* @private
* @ignore
*/
__drawEye(eyeIndex) {
__drawEye() {
const context = window;
const userSetup = context.setup;
const userDraw = context.draw;
const userCalculate = context.calculate;

if (this.isVR) {
if (eyeIndex === 0) {
if (typeof userCalculate === 'function') {
userCalculate();
}
const now = window.performance.now();
p5.instance.deltaTime = now - p5.instance._lastFrameTime;
p5.instance._frameRate = 1000.0 / p5.instance.deltaTime;
p5.instance._setProperty('deltaTime', p5.instance.deltaTime);
p5.instance._lastFrameTime = now;
context._setProperty('frameCount', context.frameCount + 1);
}
} else {
// Scale is much smaller in AR
scale(0.01);
}
// 2D Mode should use graphics object
if (!p5.instance._renderer.isP3D) {
console.error('Sketch does not have 3D Renderer');
Expand Down Expand Up @@ -312,9 +446,9 @@ export default class p5xr {
*/
printUnsupportedMessage() {
console.warn(
'Your browser/hardware does not work with AR Mode currently. This is' +
' undergoing heavy development currently.' +
'You may be able to fix this by enabling WebXR flags in Chrome.'
'Your browser/hardware does not work with AR Mode currently. This is'
+ ' undergoing heavy development currently.'
+ 'You may be able to fix this by enabling WebXR flags in Chrome.',
);
}

Expand Down
7 changes: 4 additions & 3 deletions src/p5xr/core/p5xrButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,18 +422,19 @@ class p5xrButton {
* Set button state based on mode support
*/
setAvailable(isAvailable, mode) {
const displayMode = mode.slice(-2).toUpperCase();
if (isAvailable) {
const msg = `Enter ${mode}`;
const msg = `Enter ${displayMode}`;
this.setTitle(msg);
this.setTooltip(msg);
this.enable();
console.log(`${mode} supported`);
this.setDevice(true);
} else if (mode === 'VR') {
} else if (displayMode === 'VR') {
console.log('VR not supported. Falling back to inline mode.');
this.hide();
} else {
const msg = `${mode} not supported`;
const msg = `${displayMode} not supported`;
this.setTitle(msg);
this.setTooltip(msg);
this.disable();
Expand Down
Loading

0 comments on commit 26f918b

Please sign in to comment.