Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebXR Raw Camera Access (color) #5786

Merged
merged 15 commits into from
Nov 28, 2023
46 changes: 24 additions & 22 deletions examples/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

201 changes: 201 additions & 0 deletions examples/src/examples/xr/ar-camera-color.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import * as pc from 'playcanvas';

/**
* @typedef {import('../../options.mjs').ExampleOptions} ExampleOptions
* @param {import('../../options.mjs').ExampleOptions} options - The example options.
* @returns {Promise<pc.AppBase>} The example application.
*/
async function example({ canvas }) {
/**
* @param {string} msg - The message.
*/
const message = function (msg) {
/** @type {HTMLDivElement} */
let el = document.querySelector('.message');
if (!el) {
el = document.createElement('div');
el.classList.add('message');
el.style.position = 'absolute';
el.style.bottom = '96px';
el.style.right = '0';
el.style.padding = '8px 16px';
el.style.fontFamily = 'Helvetica, Arial, sans-serif';
el.style.color = '#fff';
el.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
document.body.append(el);
}
el.textContent = msg;
};

const app = new pc.Application(canvas, {
mouse: new pc.Mouse(canvas),
touch: new pc.TouchDevice(canvas),
keyboard: new pc.Keyboard(window),
graphicsDeviceOptions: { alpha: true }
});

app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);

// Ensure canvas is resized when window changes size
const resize = () => app.resizeCanvas();
window.addEventListener('resize', resize);
app.on('destroy', () => {
window.removeEventListener('resize', resize);
});

// use device pixel ratio
app.graphicsDevice.maxPixelRatio = window.devicePixelRatio;

app.start();

// create camera
const c = new pc.Entity();
c.addComponent('camera', {
clearColor: new pc.Color(0, 0, 0, 0),
farClip: 10000
});
app.root.addChild(c);

const l = new pc.Entity();
l.addComponent("light", {
type: "spot",
range: 30
});
l.translate(0, 10, 0);
app.root.addChild(l);

const material = new pc.StandardMaterial();

/**
* @param {number} x - The x coordinate.
* @param {number} y - The y coordinate.
* @param {number} z - The z coordinate.
*/
const createCube = function (x, y, z) {
const cube = new pc.Entity();
cube.addComponent("render", {
type: "box"
});
cube.render.material = material;
cube.setLocalScale(0.5, 0.5, 0.5);
cube.translate(x * 0.5, y, z * 0.5);
app.root.addChild(cube);
};

// create a grid of cubes
const SIZE = 4;
for (let x = 0; x < SIZE; x++) {
for (let y = 0; y < SIZE; y++) {
createCube(2 * x - SIZE, 0.25, 2 * y - SIZE);
}
}

if (app.xr.supported) {
const activate = function () {
if (app.xr.isAvailable(pc.XRTYPE_AR)) {
c.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
cameraColor: true, // request access to camera color
callback: function (err) {
if (err) message("WebXR Immersive AR failed to start: " + err.message);
}
});
} else {
message("Immersive AR is not available");
}
};

app.mouse.on("mousedown", function () {
if (!app.xr.active)
activate();
});

if (app.touch) {
app.touch.on("touchend", function (evt) {
if (!app.xr.active) {
// if not in VR, activate
activate();
} else {
// otherwise reset camera
c.camera.endXr();
}

evt.event.preventDefault();
evt.event.stopPropagation();
});
}

// end session by keyboard ESC
app.keyboard.on('keydown', function (evt) {
if (evt.key === pc.KEY_ESCAPE && app.xr.active) {
app.xr.end();
}
});

app.xr.on('start', function () {
message("Immersive AR session has started");
});
app.xr.on('end', function () {
message("Immersive AR session has ended");
});
app.xr.on('available:' + pc.XRTYPE_AR, function (available) {
if (available) {
if (!app.xr.views.supportedColor) {
message("AR Camera Color is not supported");
} else {
message("Touch screen to start AR session");
}
} else {
message("Immersive AR is not available");
}
});

app.on('update', () => {
// if camera color is available
if (app.xr.views.availableColor) {
for(let i = 0; i < app.xr.views.list.length; i++) {
const view = app.xr.views.list[i];
if (!view.textureColor) // check if color texture is available
continue;

// apply camera color texture to material diffuse channel
if (!material.diffuseMap) {
material.diffuseMap = view.textureColor;
material.update();
}

// debug draw camera color texture on the screen
app.drawTexture(0.5, -0.5, 1, 1, view.textureColor);
}
}
});

app.xr.on('end', () => {
if (!material.diffuseMap)
return;

// clear camera color texture when XR session ends
material.diffuseMap = null;
material.update();
})

if (!app.xr.isAvailable(pc.XRTYPE_AR)) {
message("Immersive AR is not available");
} else if (!app.xr.views.supportedColor) {
message("AR Camera Color is not supported");
} else {
message("Touch screen to start AR session");
}
} else {
message("WebXR is not supported");
}
return app;
}

class ArCameraColorExample {
static CATEGORY = 'XR';
static NAME = 'AR Camera Color';
static example = example;
}

export { ArCameraColorExample };
1 change: 1 addition & 0 deletions examples/src/examples/xr/index.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./ar-basic.mjs";
export * from "./ar-camera-color.mjs";
export * from "./ar-hit-test.mjs";
export * from "./ar-hit-test-anchors.mjs";
export * from "./ar-anchors-persistence.mjs";
Expand Down
21 changes: 21 additions & 0 deletions src/framework/xr/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,27 @@ export const XRTARGETRAY_SCREEN = 'screen';
*/
export const XRTARGETRAY_POINTER = 'tracked-pointer';

/**
* None - view associated with a monoscopic screen, such as mobile phone screens.
*
* @type {string}
*/
export const XREYE_NONE = 'none';

/**
* Left - view associated with left eye.
*
* @type {string}
*/
export const XREYE_LEFT = 'left';

/**
* Right - view associated with right eye.
*
* @type {string}
*/
export const XREYE_RIGHT = 'right';

/**
* None - input source is not meant to be held in hands.
*
Expand Down
Loading