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 AR Light Estimation #2046

Merged
merged 7 commits into from
May 19, 2020
Merged
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
1 change: 1 addition & 0 deletions build/dependencies.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
../src/xr/xr-input-source.js
../src/xr/xr-hit-test.js
../src/xr/xr-hit-test-source.js
../src/xr/xr-light-estimation.js
../src/net/http.js
../src/script/script.js
../src/script/script-type.js
Expand Down
220 changes: 220 additions & 0 deletions src/xr/xr-light-estimation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
Object.assign(pc, function () {
var vec3A = new pc.Vec3();
var vec3B = new pc.Vec3();
var mat4A = new pc.Mat4();
var mat4B = new pc.Mat4();

/**
* @class
* @name pc.XrLightEstimation
* @augments pc.EventHandler
* @classdesc Light Estimation provides illimunation data from the real world, which is estimated by the underlying AR system.
* It provides a reflection Cube Map, that represents the reflection estimation from the viewer position.
* A more simplified approximation of light is provided by L2 Spherical Harmonics data.
* And the most simple level of light estimation is the most prominent directional light, its rotation, intensity and color.
* @description Creates a new XrLightEstimation. Note that this is created internally by the {@link pc.XrManager}.
* @param {pc.XrManager} manager - WebXR Manager.
* @property {boolean} supported True if Light Estimation is supported. This information is available only during an active AR session.
* @property {number|null} intensity Intensity of what is estimated to be the most prominent directional light. Or null if data is not available.
* @property {pc.Color|null} color Color of what is estimated to be the most prominent directional light. Or null if data is not available.
* @property {pc.Quat|null} rotation Rotation of what is estimated to be the most prominent directional light. Or null if data is not available.
*/
var XrLightEstimation = function (manager) {
pc.EventHandler.call(this);

this._manager = manager;

this._supported = false;
this._available = false;

this._lightProbeRequested = false;
this._lightProbe = null;

this._intensity = 0;
this._rotation = new pc.Quat();
this._color = new pc.Color();

this._sphericalHarmonics = new Float32Array(27);

this._manager.on('start', this._onSessionStart, this);
this._manager.on('end', this._onSessionEnd, this);
};
XrLightEstimation.prototype = Object.create(pc.EventHandler.prototype);
XrLightEstimation.prototype.constructor = XrLightEstimation;

/**
* @event
* @name pc.XrLightEstimation#available
* @description Fired when light estimation data becomes available.
*/

/**
* @event
* @name pc.XrLightEstimation#error
* @param {Error} error - Error object related to failure of light estimation start.
* @description Fired when light estimation has failed to start.
* @example
* app.xr.lightEstimation.on('error', function (ex) {
* // has failed to start
* });
*/

XrLightEstimation.prototype._onSessionStart = function () {
var supported = !! this._manager.session.requestLightProbe;
if (! supported) return;
this._supported = true;
};

XrLightEstimation.prototype._onSessionEnd = function () {
this._supported = false;
this._available = false;

this._lightProbeRequested = false;
this._lightProbe = null;
};

/**
* @function
* @name pc.XrLightEstimation#start
* @description Start estimation of illimunation data.
* Availability of such data will come later and an `available` event will be fired.
* If it failed to start estimation, an `error` event will be fired.
* @example
* app.xr.on('start', function () {
* if (app.xr.lightEstimation.supported) {
* app.xr.lightEstimation.start();
* }
* });
*/
XrLightEstimation.prototype.start = function () {
var err;

if (! this._manager.session)
err = new Error('XR session is not running');

if (! err && this._manager.type !== pc.XRTYPE_AR)
err = new Error('XR session type is not AR');

if (! err && ! this._supported)
err = new Error('light-estimation is not supported');

if (! err && this._lightProbe || this._lightProbeRequested)
err = new Error('light estimation is already requested');

if (err) {
this.fire('error', err);
return;
}

var self = this;
this._lightProbeRequested = true;

this._manager.session.requestLightProbe(
).then(function (lightProbe) {
var wasRequested = self._lightProbeRequested;
self._lightProbeRequested = false;

if (self._manager.active) {
if (wasRequested) {
self._lightProbe = lightProbe;
}
} else {
self.fire('error', new Error('XR session is not active'));
}
}).catch(function (ex) {
self._lightProbeRequested = false;
self.fire('error', ex);
});
};

/**
* @function
* @name pc.XrLightEstimation#end
* @description End estimation of illumination data.
*/
XrLightEstimation.prototype.end = function () {
this._lightProbeRequested = false;
this._lightProbe = null;
this._available = false;
};

XrLightEstimation.prototype.update = function (frame) {
if (! this._lightProbe) return;

var lightEstimate = frame.getLightEstimate(this._lightProbe);
if (! lightEstimate) return;

if (! this._available) {
this._available = true;
this.fire('available');
}

// intensity
var pli = lightEstimate.primaryLightIntensity;
this._intensity = Math.max(1.0, Math.max(pli.x, Math.max(pli.y, pli.z)));

// color
vec3A.copy(pli).scale(1 / this._intensity);
this._color.set(vec3A.x, vec3A.y, vec3A.z);

// rotation
vec3A.set(0, 0, 0);
vec3B.copy(lightEstimate.primaryLightDirection);
mat4A.setLookAt(vec3B, vec3A, pc.Vec3.UP);
mat4B.setFromAxisAngle(pc.Vec3.RIGHT, 90); // direcitonal light is looking down
mat4A.mul(mat4B);
this._rotation.setFromMat4(mat4A);

// spherical harmonics
this._sphericalHarmonics.set(lightEstimate.sphericalHarmonicsCoefficients);
};

Object.defineProperty(XrLightEstimation.prototype, 'supported', {
get: function () {
return this._supported;
}
});

/**
* @name pc.XrLightEstimation#available
* @type {boolean}
* @description True if estimated light information is available.
* @example
* if (app.xr.lightEstimation.available) {
* entity.light.intensity = app.xr.lightEstimation.intensity;
* }
*/
Object.defineProperty(XrLightEstimation.prototype, 'available', {
get: function () {
return !! this._available;
}
});

Object.defineProperty(XrLightEstimation.prototype, 'intensity', {
get: function () {
return this._available ? this._intensity : null;
}
});

Object.defineProperty(XrLightEstimation.prototype, 'color', {
get: function () {
return this._available ? this._color : null;
}
});

Object.defineProperty(XrLightEstimation.prototype, 'rotation', {
get: function () {
return this._available ? this._rotation : null;
}
});

Object.defineProperty(XrLightEstimation.prototype, 'sphericalHarmonics', {
get: function () {
return this._available ? this._sphericalHarmonics : null;
}
});

return {
XrLightEstimation: XrLightEstimation
};
}());
19 changes: 16 additions & 3 deletions src/xr/xr-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ Object.assign(pc, function () {

this.input = new pc.XrInput(this);
this.hitTest = new pc.XrHitTest(this);
this.lightEstimation = new pc.XrLightEstimation(this);

this._camera = null;
this.views = [];
Expand Down Expand Up @@ -258,8 +259,14 @@ Object.assign(pc, function () {
// 3. probably immersive-vr will fail to be created
// 4. call makeXRCompatible, very likely will lead to context loss

var optionalFeatures = [];

if (type === pc.XRTYPE_AR)
optionalFeatures.push('light-estimation');

navigator.xr.requestSession(type, {
requiredFeatures: [spaceType]
requiredFeatures: [spaceType],
optionalFeatures: optionalFeatures
}).then(function (session) {
self._onSessionStart(session, spaceType, callback);
}).catch(function (ex) {
Expand Down Expand Up @@ -507,8 +514,14 @@ Object.assign(pc, function () {

this.input.update(frame);

if (this._type === pc.XRTYPE_AR && this.hitTest.supported)
this.hitTest.update(frame);
if (this._type === pc.XRTYPE_AR) {
if (this.hitTest.supported) {
this.hitTest.update(frame);
}
if (this.lightEstimation.supported) {
this.lightEstimation.update(frame);
}
}

this.fire('update');
};
Expand Down