UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

224 lines (221 loc) 7.42 kB
import { EventHandler } from '../../core/event-handler.js'; import { Color } from '../../core/math/color.js'; import { Mat4 } from '../../core/math/mat4.js'; import { Quat } from '../../core/math/quat.js'; import { Vec3 } from '../../core/math/vec3.js'; import { XRTYPE_AR } from './constants.js'; /** * @import { XrManager } from './xr-manager.js' */ const vec3A = new Vec3(); const vec3B = new Vec3(); const mat4A = new Mat4(); const mat4B = new Mat4(); /** * Light Estimation provides illumination 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. * * @category XR */ class XrLightEstimation extends EventHandler { static{ /** * Fired when light estimation data becomes available. * * @event * @example * app.xr.lightEstimation.on('available', () => { * console.log('Light estimation is available'); * }); */ this.EVENT_AVAILABLE = 'available'; } static{ /** * Fired when light estimation has failed to start. The handler is passed the Error object * related to failure of light estimation start. * * @event * @example * app.xr.lightEstimation.on('error', (error) => { * console.error(error.message); * }); */ this.EVENT_ERROR = 'error'; } /** * Create a new XrLightEstimation instance. * * @param {XrManager} manager - WebXR Manager. * @ignore */ constructor(manager){ super(), /** * @type {boolean} * @private */ this._supported = false, /** * @type {boolean} * @private */ this._available = false, /** * @type {boolean} * @private */ this._lightProbeRequested = false, /** * @type {XRLightProbe|null} * @private */ this._lightProbe = null, /** * @type {number} * @private */ this._intensity = 0, /** * @type {Quat} * @private */ this._rotation = new Quat(), /** * @type {Color} * @private */ this._color = new Color(), /** * @type {Float32Array} * @private */ this._sphericalHarmonics = new Float32Array(27); this._manager = manager; this._manager.on('start', this._onSessionStart, this); this._manager.on('end', this._onSessionEnd, this); } /** @private */ _onSessionStart() { const supported = !!this._manager.session.requestLightProbe; if (!supported) return; this._supported = true; } /** @private */ _onSessionEnd() { this._supported = false; this._available = false; this._lightProbeRequested = false; this._lightProbe = null; } /** * Start estimation of illumination 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', () => { * if (app.xr.lightEstimation.supported) { * app.xr.lightEstimation.start(); * } * }); */ start() { let err; if (!this._manager.session) { err = new Error('XR session is not running'); } if (!err && this._manager.type !== 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; } this._lightProbeRequested = true; this._manager.session.requestLightProbe().then((lightProbe)=>{ const wasRequested = this._lightProbeRequested; this._lightProbeRequested = false; if (this._manager.active) { if (wasRequested) { this._lightProbe = lightProbe; } } else { this.fire('error', new Error('XR session is not active')); } }).catch((ex)=>{ this._lightProbeRequested = false; this.fire('error', ex); }); } /** * End estimation of illumination data. */ end() { this._lightProbeRequested = false; this._lightProbe = null; this._available = false; } /** * @param {XRFrame} frame - XRFrame from requestAnimationFrame callback. * @ignore */ update(frame) { if (!this._lightProbe) return; const lightEstimate = frame.getLightEstimate(this._lightProbe); if (!lightEstimate) return; if (!this._available) { this._available = true; this.fire('available'); } // intensity const pli = lightEstimate.primaryLightIntensity; this._intensity = Math.max(1.0, Math.max(pli.x, Math.max(pli.y, pli.z))); // color vec3A.copy(pli).mulScalar(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, Vec3.UP); mat4B.setFromAxisAngle(Vec3.RIGHT, 90); // directional light is looking down mat4A.mul(mat4B); this._rotation.setFromMat4(mat4A); // spherical harmonics this._sphericalHarmonics.set(lightEstimate.sphericalHarmonicsCoefficients); } /** * True if Light Estimation is supported. This information is available only during an active AR * session. * * @type {boolean} */ get supported() { return this._supported; } /** * True if estimated light information is available. * * @type {boolean} * @example * if (app.xr.lightEstimation.available) { * entity.light.intensity = app.xr.lightEstimation.intensity; * } */ get available() { return this._available; } /** * Intensity of what is estimated to be the most prominent directional light. Or null if data * is not available. * * @type {number|null} */ get intensity() { return this._available ? this._intensity : null; } /** * Color of what is estimated to be the most prominent directional light. Or null if data is * not available. * * @type {Color|null} */ get color() { return this._available ? this._color : null; } /** * Rotation of what is estimated to be the most prominent directional light. Or null if data is * not available. * * @type {Quat|null} */ get rotation() { return this._available ? this._rotation : null; } /** * Spherical harmonic coefficients of estimated ambient light. Or null if data is not available. * * @type {Float32Array|null} */ get sphericalHarmonics() { return this._available ? this._sphericalHarmonics : null; } } export { XrLightEstimation };