Skip to content

Commit 15e0e74

Browse files
WebXR AR Light Estimation (playcanvas#2046)
* light estimation * document events * stop > end, for API consistency * Small tweaks * spherical harmonics from light estimation Co-authored-by: Will Eastcott <will@playcanvas.com>
1 parent a273ce1 commit 15e0e74

File tree

3 files changed

+237
-3
lines changed

3 files changed

+237
-3
lines changed

build/dependencies.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
../src/xr/xr-input-source.js
119119
../src/xr/xr-hit-test.js
120120
../src/xr/xr-hit-test-source.js
121+
../src/xr/xr-light-estimation.js
121122
../src/net/http.js
122123
../src/script/script.js
123124
../src/script/script-type.js

src/xr/xr-light-estimation.js

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
Object.assign(pc, function () {
2+
var vec3A = new pc.Vec3();
3+
var vec3B = new pc.Vec3();
4+
var mat4A = new pc.Mat4();
5+
var mat4B = new pc.Mat4();
6+
7+
/**
8+
* @class
9+
* @name pc.XrLightEstimation
10+
* @augments pc.EventHandler
11+
* @classdesc Light Estimation provides illimunation data from the real world, which is estimated by the underlying AR system.
12+
* It provides a reflection Cube Map, that represents the reflection estimation from the viewer position.
13+
* A more simplified approximation of light is provided by L2 Spherical Harmonics data.
14+
* And the most simple level of light estimation is the most prominent directional light, its rotation, intensity and color.
15+
* @description Creates a new XrLightEstimation. Note that this is created internally by the {@link pc.XrManager}.
16+
* @param {pc.XrManager} manager - WebXR Manager.
17+
* @property {boolean} supported True if Light Estimation is supported. This information is available only during an active AR session.
18+
* @property {number|null} intensity Intensity of what is estimated to be the most prominent directional light. Or null if data is not available.
19+
* @property {pc.Color|null} color Color of what is estimated to be the most prominent directional light. Or null if data is not available.
20+
* @property {pc.Quat|null} rotation Rotation of what is estimated to be the most prominent directional light. Or null if data is not available.
21+
*/
22+
var XrLightEstimation = function (manager) {
23+
pc.EventHandler.call(this);
24+
25+
this._manager = manager;
26+
27+
this._supported = false;
28+
this._available = false;
29+
30+
this._lightProbeRequested = false;
31+
this._lightProbe = null;
32+
33+
this._intensity = 0;
34+
this._rotation = new pc.Quat();
35+
this._color = new pc.Color();
36+
37+
this._sphericalHarmonics = new Float32Array(27);
38+
39+
this._manager.on('start', this._onSessionStart, this);
40+
this._manager.on('end', this._onSessionEnd, this);
41+
};
42+
XrLightEstimation.prototype = Object.create(pc.EventHandler.prototype);
43+
XrLightEstimation.prototype.constructor = XrLightEstimation;
44+
45+
/**
46+
* @event
47+
* @name pc.XrLightEstimation#available
48+
* @description Fired when light estimation data becomes available.
49+
*/
50+
51+
/**
52+
* @event
53+
* @name pc.XrLightEstimation#error
54+
* @param {Error} error - Error object related to failure of light estimation start.
55+
* @description Fired when light estimation has failed to start.
56+
* @example
57+
* app.xr.lightEstimation.on('error', function (ex) {
58+
* // has failed to start
59+
* });
60+
*/
61+
62+
XrLightEstimation.prototype._onSessionStart = function () {
63+
var supported = !! this._manager.session.requestLightProbe;
64+
if (! supported) return;
65+
this._supported = true;
66+
};
67+
68+
XrLightEstimation.prototype._onSessionEnd = function () {
69+
this._supported = false;
70+
this._available = false;
71+
72+
this._lightProbeRequested = false;
73+
this._lightProbe = null;
74+
};
75+
76+
/**
77+
* @function
78+
* @name pc.XrLightEstimation#start
79+
* @description Start estimation of illimunation data.
80+
* Availability of such data will come later and an `available` event will be fired.
81+
* If it failed to start estimation, an `error` event will be fired.
82+
* @example
83+
* app.xr.on('start', function () {
84+
* if (app.xr.lightEstimation.supported) {
85+
* app.xr.lightEstimation.start();
86+
* }
87+
* });
88+
*/
89+
XrLightEstimation.prototype.start = function () {
90+
var err;
91+
92+
if (! this._manager.session)
93+
err = new Error('XR session is not running');
94+
95+
if (! err && this._manager.type !== pc.XRTYPE_AR)
96+
err = new Error('XR session type is not AR');
97+
98+
if (! err && ! this._supported)
99+
err = new Error('light-estimation is not supported');
100+
101+
if (! err && this._lightProbe || this._lightProbeRequested)
102+
err = new Error('light estimation is already requested');
103+
104+
if (err) {
105+
this.fire('error', err);
106+
return;
107+
}
108+
109+
var self = this;
110+
this._lightProbeRequested = true;
111+
112+
this._manager.session.requestLightProbe(
113+
).then(function (lightProbe) {
114+
var wasRequested = self._lightProbeRequested;
115+
self._lightProbeRequested = false;
116+
117+
if (self._manager.active) {
118+
if (wasRequested) {
119+
self._lightProbe = lightProbe;
120+
}
121+
} else {
122+
self.fire('error', new Error('XR session is not active'));
123+
}
124+
}).catch(function (ex) {
125+
self._lightProbeRequested = false;
126+
self.fire('error', ex);
127+
});
128+
};
129+
130+
/**
131+
* @function
132+
* @name pc.XrLightEstimation#end
133+
* @description End estimation of illumination data.
134+
*/
135+
XrLightEstimation.prototype.end = function () {
136+
this._lightProbeRequested = false;
137+
this._lightProbe = null;
138+
this._available = false;
139+
};
140+
141+
XrLightEstimation.prototype.update = function (frame) {
142+
if (! this._lightProbe) return;
143+
144+
var lightEstimate = frame.getLightEstimate(this._lightProbe);
145+
if (! lightEstimate) return;
146+
147+
if (! this._available) {
148+
this._available = true;
149+
this.fire('available');
150+
}
151+
152+
// intensity
153+
var pli = lightEstimate.primaryLightIntensity;
154+
this._intensity = Math.max(1.0, Math.max(pli.x, Math.max(pli.y, pli.z)));
155+
156+
// color
157+
vec3A.copy(pli).scale(1 / this._intensity);
158+
this._color.set(vec3A.x, vec3A.y, vec3A.z);
159+
160+
// rotation
161+
vec3A.set(0, 0, 0);
162+
vec3B.copy(lightEstimate.primaryLightDirection);
163+
mat4A.setLookAt(vec3B, vec3A, pc.Vec3.UP);
164+
mat4B.setFromAxisAngle(pc.Vec3.RIGHT, 90); // direcitonal light is looking down
165+
mat4A.mul(mat4B);
166+
this._rotation.setFromMat4(mat4A);
167+
168+
// spherical harmonics
169+
this._sphericalHarmonics.set(lightEstimate.sphericalHarmonicsCoefficients);
170+
};
171+
172+
Object.defineProperty(XrLightEstimation.prototype, 'supported', {
173+
get: function () {
174+
return this._supported;
175+
}
176+
});
177+
178+
/**
179+
* @name pc.XrLightEstimation#available
180+
* @type {boolean}
181+
* @description True if estimated light information is available.
182+
* @example
183+
* if (app.xr.lightEstimation.available) {
184+
* entity.light.intensity = app.xr.lightEstimation.intensity;
185+
* }
186+
*/
187+
Object.defineProperty(XrLightEstimation.prototype, 'available', {
188+
get: function () {
189+
return !! this._available;
190+
}
191+
});
192+
193+
Object.defineProperty(XrLightEstimation.prototype, 'intensity', {
194+
get: function () {
195+
return this._available ? this._intensity : null;
196+
}
197+
});
198+
199+
Object.defineProperty(XrLightEstimation.prototype, 'color', {
200+
get: function () {
201+
return this._available ? this._color : null;
202+
}
203+
});
204+
205+
Object.defineProperty(XrLightEstimation.prototype, 'rotation', {
206+
get: function () {
207+
return this._available ? this._rotation : null;
208+
}
209+
});
210+
211+
Object.defineProperty(XrLightEstimation.prototype, 'sphericalHarmonics', {
212+
get: function () {
213+
return this._available ? this._sphericalHarmonics : null;
214+
}
215+
});
216+
217+
return {
218+
XrLightEstimation: XrLightEstimation
219+
};
220+
}());

src/xr/xr-manager.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ Object.assign(pc, function () {
123123

124124
this.input = new pc.XrInput(this);
125125
this.hitTest = new pc.XrHitTest(this);
126+
this.lightEstimation = new pc.XrLightEstimation(this);
126127

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

262+
var optionalFeatures = [];
263+
264+
if (type === pc.XRTYPE_AR)
265+
optionalFeatures.push('light-estimation');
266+
261267
navigator.xr.requestSession(type, {
262-
requiredFeatures: [spaceType]
268+
requiredFeatures: [spaceType],
269+
optionalFeatures: optionalFeatures
263270
}).then(function (session) {
264271
self._onSessionStart(session, spaceType, callback);
265272
}).catch(function (ex) {
@@ -507,8 +514,14 @@ Object.assign(pc, function () {
507514

508515
this.input.update(frame);
509516

510-
if (this._type === pc.XRTYPE_AR && this.hitTest.supported)
511-
this.hitTest.update(frame);
517+
if (this._type === pc.XRTYPE_AR) {
518+
if (this.hitTest.supported) {
519+
this.hitTest.update(frame);
520+
}
521+
if (this.lightEstimation.supported) {
522+
this.lightEstimation.update(frame);
523+
}
524+
}
512525

513526
this.fire('update');
514527
};

0 commit comments

Comments
 (0)