/** * @license * webxr-polyfill * Copyright (c) 2017 Google * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @license * cardboard-vr-display * Copyright (c) 2015-2017 Google * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @license * webvr-polyfill-dpdb * Copyright (c) 2017 Google * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @license * wglu-preserve-state * Copyright (c) 2016, Brandon Jones. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * @license * nosleep.js * Copyright (c) 2017, Rich Tibbett * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ const _global = typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {}; const PRIVATE = Symbol('@@webxr-polyfill/EventTarget'); class EventTarget { constructor() { this[PRIVATE] = { listeners: new Map(), }; } addEventListener(type, listener) { if (typeof type !== 'string') { throw new Error('`type` must be a string'); } if (typeof listener !== 'function') { throw new Error('`listener` must be a function'); } const typedListeners = this[PRIVATE].listeners.get(type) || []; typedListeners.push(listener); this[PRIVATE].listeners.set(type, typedListeners); } removeEventListener(type, listener) { if (typeof type !== 'string') { throw new Error('`type` must be a string'); } if (typeof listener !== 'function') { throw new Error('`listener` must be a function'); } const typedListeners = this[PRIVATE].listeners.get(type) || []; for (let i = typedListeners.length; i >= 0; i--) { if (typedListeners[i] === listener) { typedListeners.pop(); } } } dispatchEvent(type, event) { const typedListeners = this[PRIVATE].listeners.get(type) || []; const queue = []; for (let i = 0; i < typedListeners.length; i++) { queue[i] = typedListeners[i]; } for (let listener of queue) { listener(event); } if (typeof this[`on${type}`] === 'function') { this[`on${type}`](event); } } } const EPSILON = 0.000001; let ARRAY_TYPE = (typeof Float32Array !== 'undefined') ? Float32Array : Array; const degree = Math.PI / 180; function create() { let out = new ARRAY_TYPE(16); if(ARRAY_TYPE != Float32Array) { out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; } out[0] = 1; out[5] = 1; out[10] = 1; out[15] = 1; return out; } function copy(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; } function identity(out) { out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; } function invert(out, a) { let a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; let a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; let a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; let a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; let b00 = a00 * a11 - a01 * a10; let b01 = a00 * a12 - a02 * a10; let b02 = a00 * a13 - a03 * a10; let b03 = a01 * a12 - a02 * a11; let b04 = a01 * a13 - a03 * a11; let b05 = a02 * a13 - a03 * a12; let b06 = a20 * a31 - a21 * a30; let b07 = a20 * a32 - a22 * a30; let b08 = a20 * a33 - a23 * a30; let b09 = a21 * a32 - a22 * a31; let b10 = a21 * a33 - a23 * a31; let b11 = a22 * a33 - a23 * a32; let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; if (!det) { return null; } det = 1.0 / det; out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; return out; } function multiply(out, a, b) { let a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; let a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; let a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; let a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; return out; } function fromRotationTranslation(out, q, v) { let x = q[0], y = q[1], z = q[2], w = q[3]; let x2 = x + x; let y2 = y + y; let z2 = z + z; let xx = x * x2; let xy = x * y2; let xz = x * z2; let yy = y * y2; let yz = y * z2; let zz = z * z2; let wx = w * x2; let wy = w * y2; let wz = w * z2; out[0] = 1 - (yy + zz); out[1] = xy + wz; out[2] = xz - wy; out[3] = 0; out[4] = xy - wz; out[5] = 1 - (xx + zz); out[6] = yz + wx; out[7] = 0; out[8] = xz + wy; out[9] = yz - wx; out[10] = 1 - (xx + yy); out[11] = 0; out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; out[15] = 1; return out; } function getTranslation(out, mat) { out[0] = mat[12]; out[1] = mat[13]; out[2] = mat[14]; return out; } function getRotation(out, mat) { let trace = mat[0] + mat[5] + mat[10]; let S = 0; if (trace > 0) { S = Math.sqrt(trace + 1.0) * 2; out[3] = 0.25 * S; out[0] = (mat[6] - mat[9]) / S; out[1] = (mat[8] - mat[2]) / S; out[2] = (mat[1] - mat[4]) / S; } else if ((mat[0] > mat[5]) && (mat[0] > mat[10])) { S = Math.sqrt(1.0 + mat[0] - mat[5] - mat[10]) * 2; out[3] = (mat[6] - mat[9]) / S; out[0] = 0.25 * S; out[1] = (mat[1] + mat[4]) / S; out[2] = (mat[8] + mat[2]) / S; } else if (mat[5] > mat[10]) { S = Math.sqrt(1.0 + mat[5] - mat[0] - mat[10]) * 2; out[3] = (mat[8] - mat[2]) / S; out[0] = (mat[1] + mat[4]) / S; out[1] = 0.25 * S; out[2] = (mat[6] + mat[9]) / S; } else { S = Math.sqrt(1.0 + mat[10] - mat[0] - mat[5]) * 2; out[3] = (mat[1] - mat[4]) / S; out[0] = (mat[8] + mat[2]) / S; out[1] = (mat[6] + mat[9]) / S; out[2] = 0.25 * S; } return out; } function perspective(out, fovy, aspect, near, far) { let f = 1.0 / Math.tan(fovy / 2), nf; out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[11] = -1; out[12] = 0; out[13] = 0; out[15] = 0; if (far != null && far !== Infinity) { nf = 1 / (near - far); out[10] = (far + near) * nf; out[14] = (2 * far * near) * nf; } else { out[10] = -1; out[14] = -2 * near; } return out; } function create$1() { let out = new ARRAY_TYPE(3); if(ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; out[2] = 0; } return out; } function clone$1(a) { var out = new ARRAY_TYPE(3); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; } function length(a) { let x = a[0]; let y = a[1]; let z = a[2]; return Math.sqrt(x*x + y*y + z*z); } function fromValues$1(x, y, z) { let out = new ARRAY_TYPE(3); out[0] = x; out[1] = y; out[2] = z; return out; } function copy$1(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; } function add$1(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; out[2] = a[2] + b[2]; return out; } function scale$1(out, a, b) { out[0] = a[0] * b; out[1] = a[1] * b; out[2] = a[2] * b; return out; } function normalize(out, a) { let x = a[0]; let y = a[1]; let z = a[2]; let len = x*x + y*y + z*z; if (len > 0) { len = 1 / Math.sqrt(len); out[0] = a[0] * len; out[1] = a[1] * len; out[2] = a[2] * len; } return out; } function dot(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } function cross(out, a, b) { let ax = a[0], ay = a[1], az = a[2]; let bx = b[0], by = b[1], bz = b[2]; out[0] = ay * bz - az * by; out[1] = az * bx - ax * bz; out[2] = ax * by - ay * bx; return out; } function transformQuat(out, a, q) { let qx = q[0], qy = q[1], qz = q[2], qw = q[3]; let x = a[0], y = a[1], z = a[2]; let uvx = qy * z - qz * y, uvy = qz * x - qx * z, uvz = qx * y - qy * x; let uuvx = qy * uvz - qz * uvy, uuvy = qz * uvx - qx * uvz, uuvz = qx * uvy - qy * uvx; let w2 = qw * 2; uvx *= w2; uvy *= w2; uvz *= w2; uuvx *= 2; uuvy *= 2; uuvz *= 2; out[0] = x + uvx + uuvx; out[1] = y + uvy + uuvy; out[2] = z + uvz + uuvz; return out; } function angle(a, b) { let tempA = fromValues$1(a[0], a[1], a[2]); let tempB = fromValues$1(b[0], b[1], b[2]); normalize(tempA, tempA); normalize(tempB, tempB); let cosine = dot(tempA, tempB); if(cosine > 1.0) { return 0; } else if(cosine < -1.0) { return Math.PI; } else { return Math.acos(cosine); } } const len = length; const forEach = (function() { let vec = create$1(); return function(a, stride, offset, count, fn, arg) { let i, l; if(!stride) { stride = 3; } if(!offset) { offset = 0; } if(count) { l = Math.min((count * stride) + offset, a.length); } else { l = a.length; } for(i = offset; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i+1]; vec[2] = a[i+2]; fn(vec, vec, arg); a[i] = vec[0]; a[i+1] = vec[1]; a[i+2] = vec[2]; } return a; }; })(); function create$2() { let out = new ARRAY_TYPE(9); if(ARRAY_TYPE != Float32Array) { out[1] = 0; out[2] = 0; out[3] = 0; out[5] = 0; out[6] = 0; out[7] = 0; } out[0] = 1; out[4] = 1; out[8] = 1; return out; } function create$3() { let out = new ARRAY_TYPE(4); if(ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 0; } return out; } function clone$3(a) { let out = new ARRAY_TYPE(4); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; return out; } function fromValues$3(x, y, z, w) { let out = new ARRAY_TYPE(4); out[0] = x; out[1] = y; out[2] = z; out[3] = w; return out; } function copy$3(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; return out; } function normalize$1(out, a) { let x = a[0]; let y = a[1]; let z = a[2]; let w = a[3]; let len = x*x + y*y + z*z + w*w; if (len > 0) { len = 1 / Math.sqrt(len); out[0] = x * len; out[1] = y * len; out[2] = z * len; out[3] = w * len; } return out; } const forEach$1 = (function() { let vec = create$3(); return function(a, stride, offset, count, fn, arg) { let i, l; if(!stride) { stride = 4; } if(!offset) { offset = 0; } if(count) { l = Math.min((count * stride) + offset, a.length); } else { l = a.length; } for(i = offset; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i+1]; vec[2] = a[i+2]; vec[3] = a[i+3]; fn(vec, vec, arg); a[i] = vec[0]; a[i+1] = vec[1]; a[i+2] = vec[2]; a[i+3] = vec[3]; } return a; }; })(); function create$4() { let out = new ARRAY_TYPE(4); if(ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; out[2] = 0; } out[3] = 1; return out; } function setAxisAngle(out, axis, rad) { rad = rad * 0.5; let s = Math.sin(rad); out[0] = s * axis[0]; out[1] = s * axis[1]; out[2] = s * axis[2]; out[3] = Math.cos(rad); return out; } function multiply$4(out, a, b) { let ax = a[0], ay = a[1], az = a[2], aw = a[3]; let bx = b[0], by = b[1], bz = b[2], bw = b[3]; out[0] = ax * bw + aw * bx + ay * bz - az * by; out[1] = ay * bw + aw * by + az * bx - ax * bz; out[2] = az * bw + aw * bz + ax * by - ay * bx; out[3] = aw * bw - ax * bx - ay * by - az * bz; return out; } function slerp(out, a, b, t) { let ax = a[0], ay = a[1], az = a[2], aw = a[3]; let bx = b[0], by = b[1], bz = b[2], bw = b[3]; let omega, cosom, sinom, scale0, scale1; cosom = ax * bx + ay * by + az * bz + aw * bw; if ( cosom < 0.0 ) { cosom = -cosom; bx = - bx; by = - by; bz = - bz; bw = - bw; } if ( (1.0 - cosom) > EPSILON ) { omega = Math.acos(cosom); sinom = Math.sin(omega); scale0 = Math.sin((1.0 - t) * omega) / sinom; scale1 = Math.sin(t * omega) / sinom; } else { scale0 = 1.0 - t; scale1 = t; } out[0] = scale0 * ax + scale1 * bx; out[1] = scale0 * ay + scale1 * by; out[2] = scale0 * az + scale1 * bz; out[3] = scale0 * aw + scale1 * bw; return out; } function invert$2(out, a) { let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; let dot$$1 = a0*a0 + a1*a1 + a2*a2 + a3*a3; let invDot = dot$$1 ? 1.0/dot$$1 : 0; out[0] = -a0*invDot; out[1] = -a1*invDot; out[2] = -a2*invDot; out[3] = a3*invDot; return out; } function fromMat3(out, m) { let fTrace = m[0] + m[4] + m[8]; let fRoot; if ( fTrace > 0.0 ) { fRoot = Math.sqrt(fTrace + 1.0); out[3] = 0.5 * fRoot; fRoot = 0.5/fRoot; out[0] = (m[5]-m[7])*fRoot; out[1] = (m[6]-m[2])*fRoot; out[2] = (m[1]-m[3])*fRoot; } else { let i = 0; if ( m[4] > m[0] ) i = 1; if ( m[8] > m[i*3+i] ) i = 2; let j = (i+1)%3; let k = (i+2)%3; fRoot = Math.sqrt(m[i*3+i]-m[j*3+j]-m[k*3+k] + 1.0); out[i] = 0.5 * fRoot; fRoot = 0.5 / fRoot; out[3] = (m[j*3+k] - m[k*3+j]) * fRoot; out[j] = (m[j*3+i] + m[i*3+j]) * fRoot; out[k] = (m[k*3+i] + m[i*3+k]) * fRoot; } return out; } function fromEuler(out, x, y, z) { let halfToRad = 0.5 * Math.PI / 180.0; x *= halfToRad; y *= halfToRad; z *= halfToRad; let sx = Math.sin(x); let cx = Math.cos(x); let sy = Math.sin(y); let cy = Math.cos(y); let sz = Math.sin(z); let cz = Math.cos(z); out[0] = sx * cy * cz - cx * sy * sz; out[1] = cx * sy * cz + sx * cy * sz; out[2] = cx * cy * sz - sx * sy * cz; out[3] = cx * cy * cz + sx * sy * sz; return out; } const clone$4 = clone$3; const fromValues$4 = fromValues$3; const copy$4 = copy$3; const normalize$2 = normalize$1; const rotationTo = (function() { let tmpvec3 = create$1(); let xUnitVec3 = fromValues$1(1,0,0); let yUnitVec3 = fromValues$1(0,1,0); return function(out, a, b) { let dot$$1 = dot(a, b); if (dot$$1 < -0.999999) { cross(tmpvec3, xUnitVec3, a); if (len(tmpvec3) < 0.000001) cross(tmpvec3, yUnitVec3, a); normalize(tmpvec3, tmpvec3); setAxisAngle(out, tmpvec3, Math.PI); return out; } else if (dot$$1 > 0.999999) { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 1; return out; } else { cross(tmpvec3, a, b); out[0] = tmpvec3[0]; out[1] = tmpvec3[1]; out[2] = tmpvec3[2]; out[3] = 1 + dot$$1; return normalize$2(out, out); } }; })(); const sqlerp = (function () { let temp1 = create$4(); let temp2 = create$4(); return function (out, a, b, c, d, t) { slerp(temp1, a, d, t); slerp(temp2, b, c, t); slerp(out, temp1, temp2, 2 * t * (1 - t)); return out; }; }()); const setAxes = (function() { let matr = create$2(); return function(out, view, right, up) { matr[0] = right[0]; matr[3] = right[1]; matr[6] = right[2]; matr[1] = up[0]; matr[4] = up[1]; matr[7] = up[2]; matr[2] = -view[0]; matr[5] = -view[1]; matr[8] = -view[2]; return normalize$2(out, fromMat3(out, matr)); }; })(); const PRIVATE$1 = Symbol('@@webxr-polyfill/XRRigidTransform'); class XRRigidTransform$1 { constructor() { this[PRIVATE$1] = { matrix: null, position: null, orientation: null, inverse: null, }; if (arguments.length === 0) { this[PRIVATE$1].matrix = identity(new Float32Array(16)); } else if (arguments.length === 1) { if (arguments[0] instanceof Float32Array) { this[PRIVATE$1].matrix = arguments[0]; } else { this[PRIVATE$1].position = this._getPoint(arguments[0]); this[PRIVATE$1].orientation = DOMPointReadOnly.fromPoint({ x: 0, y: 0, z: 0, w: 1 }); } } else if (arguments.length === 2) { this[PRIVATE$1].position = this._getPoint(arguments[0]); this[PRIVATE$1].orientation = this._getPoint(arguments[1]); } else { throw new Error("Too many arguments!"); } if (this[PRIVATE$1].matrix) { let position = create$1(); getTranslation(position, this[PRIVATE$1].matrix); this[PRIVATE$1].position = DOMPointReadOnly.fromPoint({ x: position[0], y: position[1], z: position[2] }); let orientation = create$4(); getRotation(orientation, this[PRIVATE$1].matrix); this[PRIVATE$1].orientation = DOMPointReadOnly.fromPoint({ x: orientation[0], y: orientation[1], z: orientation[2], w: orientation[3] }); } else { this[PRIVATE$1].matrix = identity(new Float32Array(16)); fromRotationTranslation( this[PRIVATE$1].matrix, fromValues$4( this[PRIVATE$1].orientation.x, this[PRIVATE$1].orientation.y, this[PRIVATE$1].orientation.z, this[PRIVATE$1].orientation.w), fromValues$1( this[PRIVATE$1].position.x, this[PRIVATE$1].position.y, this[PRIVATE$1].position.z) ); } } _getPoint(arg) { if (arg instanceof DOMPointReadOnly) { return arg; } return DOMPointReadOnly.fromPoint(arg); } get matrix() { return this[PRIVATE$1].matrix; } get position() { return this[PRIVATE$1].position; } get orientation() { return this[PRIVATE$1].orientation; } get inverse() { if (this[PRIVATE$1].inverse === null) { let invMatrix = identity(new Float32Array(16)); invert(invMatrix, this[PRIVATE$1].matrix); this[PRIVATE$1].inverse = new XRRigidTransform$1(invMatrix); this[PRIVATE$1].inverse[PRIVATE$1].inverse = this; } return this[PRIVATE$1].inverse; } } const PRIVATE$2 = Symbol('@@webxr-polyfill/XRSpace'); class XRSpace { constructor(specialType = null, inputSource = null) { this[PRIVATE$2] = { specialType, inputSource, baseMatrix: null, inverseBaseMatrix: null, lastFrameId: -1 }; } get _specialType() { return this[PRIVATE$2].specialType; } get _inputSource() { return this[PRIVATE$2].inputSource; } _ensurePoseUpdated(device, frameId) { if (frameId == this[PRIVATE$2].lastFrameId) return; this[PRIVATE$2].lastFrameId = frameId; this._onPoseUpdate(device); } _onPoseUpdate(device) { if (this[PRIVATE$2].specialType == 'viewer') { this._baseMatrix = device.getBasePoseMatrix(); } } set _baseMatrix(matrix) { this[PRIVATE$2].baseMatrix = matrix; this[PRIVATE$2].inverseBaseMatrix = null; } get _baseMatrix() { if (!this[PRIVATE$2].baseMatrix) { if (this[PRIVATE$2].inverseBaseMatrix) { this[PRIVATE$2].baseMatrix = new Float32Array(16); invert(this[PRIVATE$2].baseMatrix, this[PRIVATE$2].inverseBaseMatrix); } } return this[PRIVATE$2].baseMatrix; } set _inverseBaseMatrix(matrix) { this[PRIVATE$2].inverseBaseMatrix = matrix; this[PRIVATE$2].baseMatrix = null; } get _inverseBaseMatrix() { if (!this[PRIVATE$2].inverseBaseMatrix) { if (this[PRIVATE$2].baseMatrix) { this[PRIVATE$2].inverseBaseMatrix = new Float32Array(16); invert(this[PRIVATE$2].inverseBaseMatrix, this[PRIVATE$2].baseMatrix); } } return this[PRIVATE$2].inverseBaseMatrix; } _getSpaceRelativeTransform(space) { if (!this._inverseBaseMatrix || !space._baseMatrix) { return null; } let out = new Float32Array(16); multiply(out, this._inverseBaseMatrix, space._baseMatrix); return new XRRigidTransform$1(out); } } const DEFAULT_EMULATION_HEIGHT = 1.6; const PRIVATE$3 = Symbol('@@webxr-polyfill/XRReferenceSpace'); const XRReferenceSpaceTypes = [ 'viewer', 'local', 'local-floor', 'bounded-floor', 'unbounded' ]; function isFloor(type) { return type === 'bounded-floor' || type === 'local-floor'; } class XRReferenceSpace extends XRSpace { constructor(type, transform = null) { if (!XRReferenceSpaceTypes.includes(type)) { throw new Error(`XRReferenceSpaceType must be one of ${XRReferenceSpaceTypes}`); } super(type); if (type === 'bounded-floor' && !transform) { throw new Error(`XRReferenceSpace cannot use 'bounded-floor' type if the platform does not provide the floor level`); } if (isFloor(type) && !transform) { transform = identity(new Float32Array(16)); transform[13] = DEFAULT_EMULATION_HEIGHT; } this._inverseBaseMatrix = transform || identity(new Float32Array(16)); this[PRIVATE$3] = { type, transform, originOffset : identity(new Float32Array(16)), }; } _transformBasePoseMatrix(out, pose) { multiply(out, this._inverseBaseMatrix, pose); } _originOffsetMatrix() { return this[PRIVATE$3].originOffset; } _adjustForOriginOffset(transformMatrix) { let inverseOriginOffsetMatrix = new Float32Array(16); invert(inverseOriginOffsetMatrix, this[PRIVATE$3].originOffset); multiply(transformMatrix, inverseOriginOffsetMatrix, transformMatrix); } _getSpaceRelativeTransform(space) { let transform = super._getSpaceRelativeTransform(space); this._adjustForOriginOffset(transform.matrix); return new XRRigidTransform(transform.matrix); } getOffsetReferenceSpace(additionalOffset) { let newSpace = new XRReferenceSpace( this[PRIVATE$3].type, this[PRIVATE$3].transform, this[PRIVATE$3].bounds); multiply(newSpace[PRIVATE$3].originOffset, this[PRIVATE$3].originOffset, additionalOffset.matrix); return newSpace; } } const PRIVATE$4 = Symbol('@@webxr-polyfill/XR'); const XRSessionModes = ['inline', 'immersive-vr', 'immersive-ar']; const DEFAULT_SESSION_OPTIONS = { 'inline': { requiredFeatures: ['viewer'], optionalFeatures: [], }, 'immersive-vr': { requiredFeatures: ['viewer', 'local'], optionalFeatures: [], }, 'immersive-ar': { requiredFeatures: ['viewer', 'local'], optionalFeatures: [], } }; const POLYFILL_REQUEST_SESSION_ERROR = `Polyfill Error: Must call navigator.xr.isSessionSupported() with any XRSessionMode or navigator.xr.requestSession('inline') prior to requesting an immersive session. This is a limitation specific to the WebXR Polyfill and does not apply to native implementations of the API.`; class XRSystem extends EventTarget { constructor(devicePromise) { super(); this[PRIVATE$4] = { device: null, devicePromise, immersiveSession: null, inlineSessions: new Set(), }; devicePromise.then((device) => { this[PRIVATE$4].device = device; }); } async isSessionSupported(mode) { if (!this[PRIVATE$4].device) { await this[PRIVATE$4].devicePromise; } if (mode != 'inline') { return Promise.resolve(this[PRIVATE$4].device.isSessionSupported(mode)); } return Promise.resolve(true); } async requestSession(mode, options) { if (!this[PRIVATE$4].device) { if (mode != 'inline') { throw new Error(POLYFILL_REQUEST_SESSION_ERROR); } else { await this[PRIVATE$4].devicePromise; } } if (!XRSessionModes.includes(mode)) { throw new TypeError( `The provided value '${mode}' is not a valid enum value of type XRSessionMode`); } const defaultOptions = DEFAULT_SESSION_OPTIONS[mode]; const requiredFeatures = defaultOptions.requiredFeatures.concat( options && options.requiredFeatures ? options.requiredFeatures : []); const optionalFeatures = defaultOptions.optionalFeatures.concat( options && options.optionalFeatures ? options.optionalFeatures : []); const enabledFeatures = new Set(); let requirementsFailed = false; for (let feature of requiredFeatures) { if (!this[PRIVATE$4].device.isFeatureSupported(feature)) { console.error(`The required feature '${feature}' is not supported`); requirementsFailed = true; } else { enabledFeatures.add(feature); } } if (requirementsFailed) { throw new DOMException('Session does not support some required features', 'NotSupportedError'); } for (let feature of optionalFeatures) { if (!this[PRIVATE$4].device.isFeatureSupported(feature)) { console.log(`The optional feature '${feature}' is not supported`); } else { enabledFeatures.add(feature); } } const sessionId = await this[PRIVATE$4].device.requestSession(mode, enabledFeatures); const session = new XRSession(this[PRIVATE$4].device, mode, sessionId); if (mode == 'inline') { this[PRIVATE$4].inlineSessions.add(session); } else { this[PRIVATE$4].immersiveSession = session; } const onSessionEnd = () => { if (mode == 'inline') { this[PRIVATE$4].inlineSessions.delete(session); } else { this[PRIVATE$4].immersiveSession = null; } session.removeEventListener('end', onSessionEnd); }; session.addEventListener('end', onSessionEnd); return session; } } let now; if ('performance' in _global === false) { let startTime = Date.now(); now = () => Date.now() - startTime; } else { now = () => performance.now(); } var now$1 = now; const PRIVATE$5 = Symbol('@@webxr-polyfill/XRPose'); class XRPose$1 { constructor(transform, emulatedPosition) { this[PRIVATE$5] = { transform, emulatedPosition, }; } get transform() { return this[PRIVATE$5].transform; } get emulatedPosition() { return this[PRIVATE$5].emulatedPosition; } } const PRIVATE$6 = Symbol('@@webxr-polyfill/XRViewerPose'); class XRViewerPose extends XRPose$1 { constructor(transform, views, emulatedPosition = false) { super(transform, emulatedPosition); this[PRIVATE$6] = { views }; } get views() { return this[PRIVATE$6].views; } } const PRIVATE$7 = Symbol('@@webxr-polyfill/XRViewport'); class XRViewport { constructor(target) { this[PRIVATE$7] = { target }; } get x() { return this[PRIVATE$7].target.x; } get y() { return this[PRIVATE$7].target.y; } get width() { return this[PRIVATE$7].target.width; } get height() { return this[PRIVATE$7].target.height; } } const XREyes = ['left', 'right', 'none']; const PRIVATE$8 = Symbol('@@webxr-polyfill/XRView'); class XRView { constructor(device, transform, eye, sessionId, viewIndex) { if (!XREyes.includes(eye)) { throw new Error(`XREye must be one of: ${XREyes}`); } const temp = Object.create(null); const viewport = new XRViewport(temp); this[PRIVATE$8] = { device, eye, viewport, temp, sessionId, transform, viewIndex, }; } get eye() { return this[PRIVATE$8].eye; } get projectionMatrix() { return this[PRIVATE$8].device.getProjectionMatrix(this.eye, this[PRIVATE$8].viewIndex); } get transform() { return this[PRIVATE$8].transform; } _getViewport(layer) { if (this[PRIVATE$8].device.getViewport(this[PRIVATE$8].sessionId, this.eye, layer, this[PRIVATE$8].temp, this[PRIVATE$8].viewIndex)) { return this[PRIVATE$8].viewport; } return undefined; } } const PRIVATE$9 = Symbol('@@webxr-polyfill/XRFrame'); const NON_ACTIVE_MSG = "XRFrame access outside the callback that produced it is invalid."; const NON_ANIMFRAME_MSG = "getViewerPose can only be called on XRFrame objects passed to XRSession.requestAnimationFrame callbacks."; let NEXT_FRAME_ID = 0; class XRFrame { constructor(device, session, sessionId) { this[PRIVATE$9] = { id: ++NEXT_FRAME_ID, active: false, animationFrame: false, device, session, sessionId }; } get session() { return this[PRIVATE$9].session; } getViewerPose(referenceSpace) { if (!this[PRIVATE$9].animationFrame) { throw new DOMException(NON_ANIMFRAME_MSG, 'InvalidStateError'); } if (!this[PRIVATE$9].active) { throw new DOMException(NON_ACTIVE_MSG, 'InvalidStateError'); } const device = this[PRIVATE$9].device; const session = this[PRIVATE$9].session; session[PRIVATE$14].viewerSpace._ensurePoseUpdated(device, this[PRIVATE$9].id); referenceSpace._ensurePoseUpdated(device, this[PRIVATE$9].id); let viewerTransform = referenceSpace._getSpaceRelativeTransform(session[PRIVATE$14].viewerSpace); const views = []; for (const viewSpace of session[PRIVATE$14].viewSpaces) { viewSpace._ensurePoseUpdated(device, this[PRIVATE$9].id); let viewTransform = referenceSpace._getSpaceRelativeTransform(viewSpace); let view = new XRView(device, viewTransform, viewSpace.eye, this[PRIVATE$9].sessionId, viewSpace.viewIndex); views.push(view); } let viewerPose = new XRViewerPose(viewerTransform, views, false ); return viewerPose; } getPose(space, baseSpace) { if (!this[PRIVATE$9].active) { throw new DOMException(NON_ACTIVE_MSG, 'InvalidStateError'); } const device = this[PRIVATE$9].device; if (space._specialType === "target-ray" || space._specialType === "grip") { return device.getInputPose( space._inputSource, baseSpace, space._specialType); } else { space._ensurePoseUpdated(device, this[PRIVATE$9].id); baseSpace._ensurePoseUpdated(device, this[PRIVATE$9].id); let transform = baseSpace._getSpaceRelativeTransform(space); if (!transform) { return null; } return new XRPose(transform, false ); } return null; } } const PRIVATE$10 = Symbol('@@webxr-polyfill/XRRenderState'); const XRRenderStateInit = Object.freeze({ depthNear: 0.1, depthFar: 1000.0, inlineVerticalFieldOfView: null, baseLayer: null }); class XRRenderState { constructor(stateInit = {}) { const config = Object.assign({}, XRRenderStateInit, stateInit); this[PRIVATE$10] = { config }; } get depthNear() { return this[PRIVATE$10].config.depthNear; } get depthFar() { return this[PRIVATE$10].config.depthFar; } get inlineVerticalFieldOfView() { return this[PRIVATE$10].config.inlineVerticalFieldOfView; } get baseLayer() { return this[PRIVATE$10].config.baseLayer; } } const PRIVATE$11 = Symbol('@@webxr-polyfill/XRInputSourceEvent'); class XRInputSourceEvent extends Event { constructor(type, eventInitDict) { super(type, eventInitDict); this[PRIVATE$11] = { frame: eventInitDict.frame, inputSource: eventInitDict.inputSource }; Object.setPrototypeOf(this, XRInputSourceEvent.prototype); } get frame() { return this[PRIVATE$11].frame; } get inputSource() { return this[PRIVATE$11].inputSource; } } const PRIVATE$12 = Symbol('@@webxr-polyfill/XRSessionEvent'); class XRSessionEvent extends Event { constructor(type, eventInitDict) { super(type, eventInitDict); this[PRIVATE$12] = { session: eventInitDict.session }; Object.setPrototypeOf(this, XRSessionEvent.prototype); } get session() { return this[PRIVATE$12].session; } } const PRIVATE$13 = Symbol('@@webxr-polyfill/XRInputSourcesChangeEvent'); class XRInputSourcesChangeEvent extends Event { constructor(type, eventInitDict) { super(type, eventInitDict); this[PRIVATE$13] = { session: eventInitDict.session, added: eventInitDict.added, removed: eventInitDict.removed }; Object.setPrototypeOf(this, XRInputSourcesChangeEvent.prototype); } get session() { return this[PRIVATE$13].session; } get added() { return this[PRIVATE$13].added; } get removed() { return this[PRIVATE$13].removed; } } const PRIVATE$14 = Symbol('@@webxr-polyfill/XRSession'); class XRViewSpace extends XRSpace { constructor(eye) { super(eye); } get eye() { return this._specialType; } _onPoseUpdate(device) { this._inverseBaseMatrix = device.getBaseViewMatrix(this._specialType); } } class XRSession$1 extends EventTarget { constructor(device, mode, id) { super(); let immersive = mode != 'inline'; let initialRenderState = new XRRenderState({ inlineVerticalFieldOfView: immersive ? null : Math.PI * 0.5 }); const defaultViewSpaces = immersive ? [new XRViewSpace('left'), new XRViewSpace('right')] : [new XRViewSpace('none')]; Object.freeze(defaultViewSpaces); this[PRIVATE$14] = { device, mode, immersive, ended: false, suspended: false, frameCallbacks: [], currentFrameCallbacks: null, frameHandle: 0, deviceFrameHandle: null, id, activeRenderState: initialRenderState, pendingRenderState: null, viewerSpace: new XRReferenceSpace("viewer"), get viewSpaces() { return device.getViewSpaces(mode) || defaultViewSpaces; }, currentInputSources: [] }; this[PRIVATE$14].onDeviceFrame = () => { if (this[PRIVATE$14].ended || this[PRIVATE$14].suspended) { return; } this[PRIVATE$14].deviceFrameHandle = null; this[PRIVATE$14].startDeviceFrameLoop(); if (this[PRIVATE$14].pendingRenderState !== null) { this[PRIVATE$14].activeRenderState = new XRRenderState(this[PRIVATE$14].pendingRenderState); this[PRIVATE$14].pendingRenderState = null; if (this[PRIVATE$14].activeRenderState.baseLayer) { this[PRIVATE$14].device.onBaseLayerSet( this[PRIVATE$14].id, this[PRIVATE$14].activeRenderState.baseLayer); } } if (this[PRIVATE$14].activeRenderState.baseLayer === null) { return; } const frame = new XRFrame(device, this, this[PRIVATE$14].id); const callbacks = this[PRIVATE$14].currentFrameCallbacks = this[PRIVATE$14].frameCallbacks; this[PRIVATE$14].frameCallbacks = []; frame[PRIVATE$9].active = true; frame[PRIVATE$9].animationFrame = true; this[PRIVATE$14].device.onFrameStart(this[PRIVATE$14].id, this[PRIVATE$14].activeRenderState); this._checkInputSourcesChange(); const rightNow = now$1(); for (let i = 0; i < callbacks.length; i++) { try { if (!callbacks[i].cancelled && typeof callbacks[i].callback === 'function') { callbacks[i].callback(rightNow, frame); } } catch(err) { console.error(err); } } this[PRIVATE$14].currentFrameCallbacks = null; frame[PRIVATE$9].active = false; this[PRIVATE$14].device.onFrameEnd(this[PRIVATE$14].id); }; this[PRIVATE$14].startDeviceFrameLoop = () => { if (this[PRIVATE$14].deviceFrameHandle === null) { this[PRIVATE$14].deviceFrameHandle = this[PRIVATE$14].device.requestAnimationFrame( this[PRIVATE$14].onDeviceFrame ); } }; this[PRIVATE$14].stopDeviceFrameLoop = () => { const handle = this[PRIVATE$14].deviceFrameHandle; if (handle !== null) { this[PRIVATE$14].device.cancelAnimationFrame(handle); this[PRIVATE$14].deviceFrameHandle = null; } }; this[PRIVATE$14].onPresentationEnd = sessionId => { if (sessionId !== this[PRIVATE$14].id) { this[PRIVATE$14].suspended = false; this[PRIVATE$14].startDeviceFrameLoop(); this.dispatchEvent('focus', { session: this }); return; } this[PRIVATE$14].ended = true; this[PRIVATE$14].stopDeviceFrameLoop(); device.removeEventListener('@@webxr-polyfill/vr-present-end', this[PRIVATE$14].onPresentationEnd); device.removeEventListener('@@webxr-polyfill/vr-present-start', this[PRIVATE$14].onPresentationStart); device.removeEventListener('@@webxr-polyfill/input-select-start', this[PRIVATE$14].onSelectStart); device.removeEventListener('@@webxr-polyfill/input-select-end', this[PRIVATE$14].onSelectEnd); this.dispatchEvent('end', new XRSessionEvent('end', { session: this })); }; device.addEventListener('@@webxr-polyfill/vr-present-end', this[PRIVATE$14].onPresentationEnd); this[PRIVATE$14].onPresentationStart = sessionId => { if (sessionId === this[PRIVATE$14].id) { return; } this[PRIVATE$14].suspended = true; this[PRIVATE$14].stopDeviceFrameLoop(); this.dispatchEvent('blur', { session: this }); }; device.addEventListener('@@webxr-polyfill/vr-present-start', this[PRIVATE$14].onPresentationStart); this[PRIVATE$14].onSelectStart = evt => { if (evt.sessionId !== this[PRIVATE$14].id) { return; } this[PRIVATE$14].dispatchInputSourceEvent('selectstart', evt.inputSource); }; device.addEventListener('@@webxr-polyfill/input-select-start', this[PRIVATE$14].onSelectStart); this[PRIVATE$14].onSelectEnd = evt => { if (evt.sessionId !== this[PRIVATE$14].id) { return; } this[PRIVATE$14].dispatchInputSourceEvent('select', evt.inputSource); this[PRIVATE$14].dispatchInputSourceEvent('selectend', evt.inputSource); }; device.addEventListener('@@webxr-polyfill/input-select-end', this[PRIVATE$14].onSelectEnd); this[PRIVATE$14].onSqueezeStart = evt => { if (evt.sessionId !== this[PRIVATE$14].id) { return; } this[PRIVATE$14].dispatchInputSourceEvent('squeezestart', evt.inputSource); }; device.addEventListener('@@webxr-polyfill/input-squeeze-start', this[PRIVATE$14].onSqueezeStart); this[PRIVATE$14].onSqueezeEnd = evt => { if (evt.sessionId !== this[PRIVATE$14].id) { return; } this[PRIVATE$14].dispatchInputSourceEvent('squeezeend', evt.inputSource); this[PRIVATE$14].dispatchInputSourceEvent('squeeze', evt.inputSource); }; device.addEventListener('@@webxr-polyfill/input-squeeze-end', this[PRIVATE$14].onSqueezeEnd); this[PRIVATE$14].dispatchInputSourceEvent = (type, inputSource) => { const frame = new XRFrame(device, this, this[PRIVATE$14].id); const event = new XRInputSourceEvent(type, { frame, inputSource }); frame[PRIVATE$9].active = true; this.dispatchEvent(type, event); frame[PRIVATE$9].active = false; }; this[PRIVATE$14].startDeviceFrameLoop(); this.onblur = undefined; this.onfocus = undefined; this.onresetpose = undefined; this.onend = undefined; this.onselect = undefined; this.onselectstart = undefined; this.onselectend = undefined; } get renderState() { return this[PRIVATE$14].activeRenderState; } get environmentBlendMode() { return this[PRIVATE$14].device.environmentBlendMode || 'opaque'; } async requestReferenceSpace(type) { if (this[PRIVATE$14].ended) { return; } if (!XRReferenceSpaceTypes.includes(type)) { throw new TypeError(`XRReferenceSpaceType must be one of ${XRReferenceSpaceTypes}`); } if (!this[PRIVATE$14].device.doesSessionSupportReferenceSpace(this[PRIVATE$14].id, type)) { throw new DOMException(`The ${type} reference space is not supported by this session.`, 'NotSupportedError'); } if (type === 'viewer') { return this[PRIVATE$14].viewerSpace; } let transform = await this[PRIVATE$14].device.requestFrameOfReferenceTransform(type); if (type === 'bounded-floor') { if (!transform) { throw new DOMException(`${type} XRReferenceSpace not supported by this device.`, 'NotSupportedError'); } let bounds = this[PRIVATE$14].device.requestStageBounds(); if (!bounds) { throw new DOMException(`${type} XRReferenceSpace not supported by this device.`, 'NotSupportedError'); } throw new DOMException(`The WebXR polyfill does not support the ${type} reference space yet.`, 'NotSupportedError'); } return new XRReferenceSpace(type, transform); } requestAnimationFrame(callback) { if (this[PRIVATE$14].ended) { return; } const handle = ++this[PRIVATE$14].frameHandle; this[PRIVATE$14].frameCallbacks.push({ handle, callback, cancelled: false }); return handle; } cancelAnimationFrame(handle) { let callbacks = this[PRIVATE$14].frameCallbacks; let index = callbacks.findIndex(d => d && d.handle === handle); if (index > -1) { callbacks[index].cancelled = true; callbacks.splice(index, 1); } callbacks = this[PRIVATE$14].currentFrameCallbacks; if (callbacks) { index = callbacks.findIndex(d => d && d.handle === handle); if (index > -1) { callbacks[index].cancelled = true; } } } get inputSources() { return this[PRIVATE$14].device.getInputSources(); } async end() { if (this[PRIVATE$14].ended) { return; } if (this[PRIVATE$14].immersive) { this[PRIVATE$14].ended = true; this[PRIVATE$14].device.removeEventListener('@@webxr-polyfill/vr-present-start', this[PRIVATE$14].onPresentationStart); this[PRIVATE$14].device.removeEventListener('@@webxr-polyfill/vr-present-end', this[PRIVATE$14].onPresentationEnd); this[PRIVATE$14].device.removeEventListener('@@webxr-polyfill/input-select-start', this[PRIVATE$14].onSelectStart); this[PRIVATE$14].device.removeEventListener('@@webxr-polyfill/input-select-end', this[PRIVATE$14].onSelectEnd); this.dispatchEvent('end', new XRSessionEvent('end', { session: this })); } this[PRIVATE$14].stopDeviceFrameLoop(); return this[PRIVATE$14].device.endSession(this[PRIVATE$14].id); } updateRenderState(newState) { if (this[PRIVATE$14].ended) { const message = "Can't call updateRenderState on an XRSession " + "that has already ended."; throw new Error(message); } if (newState.baseLayer && (newState.baseLayer._session !== this)) { const message = "Called updateRenderState with a base layer that was " + "created by a different session."; throw new Error(message); } const fovSet = (newState.inlineVerticalFieldOfView !== null) && (newState.inlineVerticalFieldOfView !== undefined); if (fovSet) { if (this[PRIVATE$14].immersive) { const message = "inlineVerticalFieldOfView must not be set for an " + "XRRenderState passed to updateRenderState for an " + "immersive session."; throw new Error(message); } else { newState.inlineVerticalFieldOfView = Math.min( 3.13, Math.max(0.01, newState.inlineVerticalFieldOfView)); } } if (this[PRIVATE$14].pendingRenderState === null) { const activeRenderState = this[PRIVATE$14].activeRenderState; this[PRIVATE$14].pendingRenderState = { depthNear: activeRenderState.depthNear, depthFar: activeRenderState.depthFar, inlineVerticalFieldOfView: activeRenderState.inlineVerticalFieldOfView, baseLayer: activeRenderState.baseLayer }; } Object.assign(this[PRIVATE$14].pendingRenderState, newState); } _checkInputSourcesChange() { const added = []; const removed = []; const newInputSources = this.inputSources; const oldInputSources = this[PRIVATE$14].currentInputSources; for (const newInputSource of newInputSources) { if (!oldInputSources.includes(newInputSource)) { added.push(newInputSource); } } for (const oldInputSource of oldInputSources) { if (!newInputSources.includes(oldInputSource)) { removed.push(oldInputSource); } } if (added.length > 0 || removed.length > 0) { this.dispatchEvent('inputsourceschange', new XRInputSourcesChangeEvent('inputsourceschange', { session: this, added: added, removed: removed })); } this[PRIVATE$14].currentInputSources.length = 0; for (const newInputSource of newInputSources) { this[PRIVATE$14].currentInputSources.push(newInputSource); } } } const PRIVATE$15 = Symbol('@@webxr-polyfill/XRInputSource'); class XRInputSource { constructor(impl) { this[PRIVATE$15] = { impl, gripSpace: new XRSpace("grip", this), targetRaySpace: new XRSpace("target-ray", this) }; } get handedness() { return this[PRIVATE$15].impl.handedness; } get targetRayMode() { return this[PRIVATE$15].impl.targetRayMode; } get gripSpace() { let mode = this[PRIVATE$15].impl.targetRayMode; if (mode === "gaze" || mode === "screen") { return null; } return this[PRIVATE$15].gripSpace; } get targetRaySpace() { return this[PRIVATE$15].targetRaySpace; } get profiles() { return this[PRIVATE$15].impl.profiles; } get gamepad() { return this[PRIVATE$15].impl.gamepad; } } const POLYFILLED_XR_COMPATIBLE = Symbol('@@webxr-polyfill/polyfilled-xr-compatible'); const XR_COMPATIBLE = Symbol('@@webxr-polyfill/xr-compatible'); const PRIVATE$16 = Symbol('@@webxr-polyfill/XRWebGLLayer'); const XRWebGLLayerInit = Object.freeze({ antialias: true, depth: true, stencil: false, alpha: true, multiview: false, ignoreDepthValues: false, framebufferScaleFactor: 1.0, }); class XRWebGLLayer { constructor(session, context, layerInit={}) { const config = Object.assign({}, XRWebGLLayerInit, layerInit); if (!(session instanceof XRSession$1)) { throw new Error('session must be a XRSession'); } if (session.ended) { throw new Error(`InvalidStateError`); } if (context[POLYFILLED_XR_COMPATIBLE]) { if (context[XR_COMPATIBLE] !== true) { throw new Error(`InvalidStateError`); } } this[PRIVATE$16] = { context, config, session, }; } get context() { return this[PRIVATE$16].context; } get antialias() { return this[PRIVATE$16].config.antialias; } get ignoreDepthValues() { return true; } get framebuffer() { return null; } get framebufferWidth() { return this[PRIVATE$16].context.drawingBufferWidth; } get framebufferHeight() { return this[PRIVATE$16].context.drawingBufferHeight; } get _session() { return this[PRIVATE$16].session; } getViewport(view) { return view._getViewport(this); } static getNativeFramebufferScaleFactor(session) { if (!session) { throw new TypeError('getNativeFramebufferScaleFactor must be passed a session.') } if (session[PRIVATE$14].ended) { return 0.0; } return 1.0; } } const PRIVATE$17 = Symbol('@@webxr-polyfill/XRReferenceSpaceEvent'); class XRReferenceSpaceEvent extends Event { constructor(type, eventInitDict) { super(type, eventInitDict); this[PRIVATE$17] = { referenceSpace: eventInitDict.referenceSpace, transform: eventInitDict.transform || null }; Object.setPrototypeOf(this, XRReferenceSpaceEvent.prototype); } get referenceSpace() { return this[PRIVATE$17].referenceSpace; } get transform() { return this[PRIVATE$17].transform; } } var API = { XRSystem, XRSession: XRSession$1, XRSessionEvent, XRFrame, XRView, XRViewport, XRViewerPose, XRWebGLLayer, XRSpace, XRReferenceSpace, XRReferenceSpaceEvent, XRInputSource, XRInputSourceEvent, XRInputSourcesChangeEvent, XRRenderState, XRRigidTransform: XRRigidTransform$1, XRPose: XRPose$1, }; const polyfillMakeXRCompatible = Context => { if (typeof Context.prototype.makeXRCompatible === 'function') { return false; } Context.prototype.makeXRCompatible = function () { this[XR_COMPATIBLE] = true; return Promise.resolve(); }; return true; }; const polyfillGetContext = (Canvas) => { const getContext = Canvas.prototype.getContext; Canvas.prototype.getContext = function (contextType, glAttribs) { const ctx = getContext.call(this, contextType, glAttribs); if (ctx) { ctx[POLYFILLED_XR_COMPATIBLE] = true; if (glAttribs && ('xrCompatible' in glAttribs)) { ctx[XR_COMPATIBLE] = glAttribs.xrCompatible; } } return ctx; }; }; const isImageBitmapSupported = global => !!(global.ImageBitmapRenderingContext && global.createImageBitmap); const isMobile = global => { var check = false; (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true;})(global.navigator.userAgent||global.navigator.vendor||global.opera); return check; }; const applyCanvasStylesForMinimalRendering = canvas => { canvas.style.display = 'block'; canvas.style.position = 'absolute'; canvas.style.width = canvas.style.height = '1px'; canvas.style.top = canvas.style.left = '0px'; }; var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function unwrapExports (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var cardboardVrDisplay = createCommonjsModule(function (module, exports) { (function (global, factory) { module.exports = factory(); }(commonjsGlobal, (function () { var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; var createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var MIN_TIMESTEP = 0.001; var MAX_TIMESTEP = 1; var dataUri = function dataUri(mimeType, svg) { return 'data:' + mimeType + ',' + encodeURIComponent(svg); }; var lerp = function lerp(a, b, t) { return a + (b - a) * t; }; var isIOS = function () { var isIOS = /iPad|iPhone|iPod/.test(navigator.platform); return function () { return isIOS; }; }(); var isWebViewAndroid = function () { var isWebViewAndroid = navigator.userAgent.indexOf('Version') !== -1 && navigator.userAgent.indexOf('Android') !== -1 && navigator.userAgent.indexOf('Chrome') !== -1; return function () { return isWebViewAndroid; }; }(); var isSafari = function () { var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); return function () { return isSafari; }; }(); var isFirefoxAndroid = function () { var isFirefoxAndroid = navigator.userAgent.indexOf('Firefox') !== -1 && navigator.userAgent.indexOf('Android') !== -1; return function () { return isFirefoxAndroid; }; }(); var getChromeVersion = function () { var match = navigator.userAgent.match(/.*Chrome\/([0-9]+)/); var value = match ? parseInt(match[1], 10) : null; return function () { return value; }; }(); var isSafariWithoutDeviceMotion = function () { var value = false; value = isIOS() && isSafari() && navigator.userAgent.indexOf('13_4') !== -1; return function () { return value; }; }(); var isChromeWithoutDeviceMotion = function () { var value = false; if (getChromeVersion() === 65) { var match = navigator.userAgent.match(/.*Chrome\/([0-9\.]*)/); if (match) { var _match$1$split = match[1].split('.'), _match$1$split2 = slicedToArray(_match$1$split, 4), major = _match$1$split2[0], minor = _match$1$split2[1], branch = _match$1$split2[2], build = _match$1$split2[3]; value = parseInt(branch, 10) === 3325 && parseInt(build, 10) < 148; } } return function () { return value; }; }(); var isR7 = function () { var isR7 = navigator.userAgent.indexOf('R7 Build') !== -1; return function () { return isR7; }; }(); var isLandscapeMode = function isLandscapeMode() { var rtn = window.orientation == 90 || window.orientation == -90; return isR7() ? !rtn : rtn; }; var isTimestampDeltaValid = function isTimestampDeltaValid(timestampDeltaS) { if (isNaN(timestampDeltaS)) { return false; } if (timestampDeltaS <= MIN_TIMESTEP) { return false; } if (timestampDeltaS > MAX_TIMESTEP) { return false; } return true; }; var getScreenWidth = function getScreenWidth() { return Math.max(window.screen.width, window.screen.height) * window.devicePixelRatio; }; var getScreenHeight = function getScreenHeight() { return Math.min(window.screen.width, window.screen.height) * window.devicePixelRatio; }; var requestFullscreen = function requestFullscreen(element) { if (isWebViewAndroid()) { return false; } if (element.requestFullscreen) { element.requestFullscreen(); } else if (element.webkitRequestFullscreen) { element.webkitRequestFullscreen(); } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen(); } else if (element.msRequestFullscreen) { element.msRequestFullscreen(); } else { return false; } return true; }; var exitFullscreen = function exitFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } else { return false; } return true; }; var getFullscreenElement = function getFullscreenElement() { return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; }; var linkProgram = function linkProgram(gl, vertexSource, fragmentSource, attribLocationMap) { var vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexSource); gl.compileShader(vertexShader); var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentSource); gl.compileShader(fragmentShader); var program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); for (var attribName in attribLocationMap) { gl.bindAttribLocation(program, attribLocationMap[attribName], attribName); }gl.linkProgram(program); gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); return program; }; var getProgramUniforms = function getProgramUniforms(gl, program) { var uniforms = {}; var uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); var uniformName = ''; for (var i = 0; i < uniformCount; i++) { var uniformInfo = gl.getActiveUniform(program, i); uniformName = uniformInfo.name.replace('[0]', ''); uniforms[uniformName] = gl.getUniformLocation(program, uniformName); } return uniforms; }; var orthoMatrix = function orthoMatrix(out, left, right, bottom, top, near, far) { var lr = 1 / (left - right), bt = 1 / (bottom - top), nf = 1 / (near - far); out[0] = -2 * lr; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = -2 * bt; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 2 * nf; out[11] = 0; out[12] = (left + right) * lr; out[13] = (top + bottom) * bt; out[14] = (far + near) * nf; out[15] = 1; return out; }; var isMobile = function isMobile() { var check = false; (function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })(navigator.userAgent || navigator.vendor || window.opera); return check; }; var extend = function extend(dest, src) { for (var key in src) { if (src.hasOwnProperty(key)) { dest[key] = src[key]; } } return dest; }; var safariCssSizeWorkaround = function safariCssSizeWorkaround(canvas) { if (isIOS()) { var width = canvas.style.width; var height = canvas.style.height; canvas.style.width = parseInt(width) + 1 + 'px'; canvas.style.height = parseInt(height) + 'px'; setTimeout(function () { canvas.style.width = width; canvas.style.height = height; }, 100); } window.canvas = canvas; }; var frameDataFromPose = function () { var piOver180 = Math.PI / 180.0; var rad45 = Math.PI * 0.25; function mat4_perspectiveFromFieldOfView(out, fov, near, far) { var upTan = Math.tan(fov ? fov.upDegrees * piOver180 : rad45), downTan = Math.tan(fov ? fov.downDegrees * piOver180 : rad45), leftTan = Math.tan(fov ? fov.leftDegrees * piOver180 : rad45), rightTan = Math.tan(fov ? fov.rightDegrees * piOver180 : rad45), xScale = 2.0 / (leftTan + rightTan), yScale = 2.0 / (upTan + downTan); out[0] = xScale; out[1] = 0.0; out[2] = 0.0; out[3] = 0.0; out[4] = 0.0; out[5] = yScale; out[6] = 0.0; out[7] = 0.0; out[8] = -((leftTan - rightTan) * xScale * 0.5); out[9] = (upTan - downTan) * yScale * 0.5; out[10] = far / (near - far); out[11] = -1.0; out[12] = 0.0; out[13] = 0.0; out[14] = far * near / (near - far); out[15] = 0.0; return out; } function mat4_fromRotationTranslation(out, q, v) { var x = q[0], y = q[1], z = q[2], w = q[3], x2 = x + x, y2 = y + y, z2 = z + z, xx = x * x2, xy = x * y2, xz = x * z2, yy = y * y2, yz = y * z2, zz = z * z2, wx = w * x2, wy = w * y2, wz = w * z2; out[0] = 1 - (yy + zz); out[1] = xy + wz; out[2] = xz - wy; out[3] = 0; out[4] = xy - wz; out[5] = 1 - (xx + zz); out[6] = yz + wx; out[7] = 0; out[8] = xz + wy; out[9] = yz - wx; out[10] = 1 - (xx + yy); out[11] = 0; out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; out[15] = 1; return out; } function mat4_translate(out, a, v) { var x = v[0], y = v[1], z = v[2], a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23; if (a === out) { out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; } else { a00 = a[0];a01 = a[1];a02 = a[2];a03 = a[3]; a10 = a[4];a11 = a[5];a12 = a[6];a13 = a[7]; a20 = a[8];a21 = a[9];a22 = a[10];a23 = a[11]; out[0] = a00;out[1] = a01;out[2] = a02;out[3] = a03; out[4] = a10;out[5] = a11;out[6] = a12;out[7] = a13; out[8] = a20;out[9] = a21;out[10] = a22;out[11] = a23; out[12] = a00 * x + a10 * y + a20 * z + a[12]; out[13] = a01 * x + a11 * y + a21 * z + a[13]; out[14] = a02 * x + a12 * y + a22 * z + a[14]; out[15] = a03 * x + a13 * y + a23 * z + a[15]; } return out; } function mat4_invert(out, a) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15], b00 = a00 * a11 - a01 * a10, b01 = a00 * a12 - a02 * a10, b02 = a00 * a13 - a03 * a10, b03 = a01 * a12 - a02 * a11, b04 = a01 * a13 - a03 * a11, b05 = a02 * a13 - a03 * a12, b06 = a20 * a31 - a21 * a30, b07 = a20 * a32 - a22 * a30, b08 = a20 * a33 - a23 * a30, b09 = a21 * a32 - a22 * a31, b10 = a21 * a33 - a23 * a31, b11 = a22 * a33 - a23 * a32, det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; if (!det) { return null; } det = 1.0 / det; out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; return out; } var defaultOrientation = new Float32Array([0, 0, 0, 1]); var defaultPosition = new Float32Array([0, 0, 0]); function updateEyeMatrices(projection, view, pose, fov, offset, vrDisplay) { mat4_perspectiveFromFieldOfView(projection, fov || null, vrDisplay.depthNear, vrDisplay.depthFar); var orientation = pose.orientation || defaultOrientation; var position = pose.position || defaultPosition; mat4_fromRotationTranslation(view, orientation, position); if (offset) mat4_translate(view, view, offset); mat4_invert(view, view); } return function (frameData, pose, vrDisplay) { if (!frameData || !pose) return false; frameData.pose = pose; frameData.timestamp = pose.timestamp; updateEyeMatrices(frameData.leftProjectionMatrix, frameData.leftViewMatrix, pose, vrDisplay._getFieldOfView("left"), vrDisplay._getEyeOffset("left"), vrDisplay); updateEyeMatrices(frameData.rightProjectionMatrix, frameData.rightViewMatrix, pose, vrDisplay._getFieldOfView("right"), vrDisplay._getEyeOffset("right"), vrDisplay); return true; }; }(); var isInsideCrossOriginIFrame = function isInsideCrossOriginIFrame() { var isFramed = window.self !== window.top; var refOrigin = getOriginFromUrl(document.referrer); var thisOrigin = getOriginFromUrl(window.location.href); return isFramed && refOrigin !== thisOrigin; }; var getOriginFromUrl = function getOriginFromUrl(url) { var domainIdx; var protoSepIdx = url.indexOf("://"); if (protoSepIdx !== -1) { domainIdx = protoSepIdx + 3; } else { domainIdx = 0; } var domainEndIdx = url.indexOf('/', domainIdx); if (domainEndIdx === -1) { domainEndIdx = url.length; } return url.substring(0, domainEndIdx); }; var getQuaternionAngle = function getQuaternionAngle(quat) { if (quat.w > 1) { console.warn('getQuaternionAngle: w > 1'); return 0; } var angle = 2 * Math.acos(quat.w); return angle; }; var warnOnce = function () { var observedWarnings = {}; return function (key, message) { if (observedWarnings[key] === undefined) { console.warn('webvr-polyfill: ' + message); observedWarnings[key] = true; } }; }(); var deprecateWarning = function deprecateWarning(deprecated, suggested) { var alternative = suggested ? 'Please use ' + suggested + ' instead.' : ''; warnOnce(deprecated, deprecated + ' has been deprecated. ' + 'This may not work on native WebVR displays. ' + alternative); }; function WGLUPreserveGLState(gl, bindings, callback) { if (!bindings) { callback(gl); return; } var boundValues = []; var activeTexture = null; for (var i = 0; i < bindings.length; ++i) { var binding = bindings[i]; switch (binding) { case gl.TEXTURE_BINDING_2D: case gl.TEXTURE_BINDING_CUBE_MAP: var textureUnit = bindings[++i]; if (textureUnit < gl.TEXTURE0 || textureUnit > gl.TEXTURE31) { console.error("TEXTURE_BINDING_2D or TEXTURE_BINDING_CUBE_MAP must be followed by a valid texture unit"); boundValues.push(null, null); break; } if (!activeTexture) { activeTexture = gl.getParameter(gl.ACTIVE_TEXTURE); } gl.activeTexture(textureUnit); boundValues.push(gl.getParameter(binding), null); break; case gl.ACTIVE_TEXTURE: activeTexture = gl.getParameter(gl.ACTIVE_TEXTURE); boundValues.push(null); break; default: boundValues.push(gl.getParameter(binding)); break; } } callback(gl); for (var i = 0; i < bindings.length; ++i) { var binding = bindings[i]; var boundValue = boundValues[i]; switch (binding) { case gl.ACTIVE_TEXTURE: break; case gl.ARRAY_BUFFER_BINDING: gl.bindBuffer(gl.ARRAY_BUFFER, boundValue); break; case gl.COLOR_CLEAR_VALUE: gl.clearColor(boundValue[0], boundValue[1], boundValue[2], boundValue[3]); break; case gl.COLOR_WRITEMASK: gl.colorMask(boundValue[0], boundValue[1], boundValue[2], boundValue[3]); break; case gl.CURRENT_PROGRAM: gl.useProgram(boundValue); break; case gl.ELEMENT_ARRAY_BUFFER_BINDING: gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, boundValue); break; case gl.FRAMEBUFFER_BINDING: gl.bindFramebuffer(gl.FRAMEBUFFER, boundValue); break; case gl.RENDERBUFFER_BINDING: gl.bindRenderbuffer(gl.RENDERBUFFER, boundValue); break; case gl.TEXTURE_BINDING_2D: var textureUnit = bindings[++i]; if (textureUnit < gl.TEXTURE0 || textureUnit > gl.TEXTURE31) break; gl.activeTexture(textureUnit); gl.bindTexture(gl.TEXTURE_2D, boundValue); break; case gl.TEXTURE_BINDING_CUBE_MAP: var textureUnit = bindings[++i]; if (textureUnit < gl.TEXTURE0 || textureUnit > gl.TEXTURE31) break; gl.activeTexture(textureUnit); gl.bindTexture(gl.TEXTURE_CUBE_MAP, boundValue); break; case gl.VIEWPORT: gl.viewport(boundValue[0], boundValue[1], boundValue[2], boundValue[3]); break; case gl.BLEND: case gl.CULL_FACE: case gl.DEPTH_TEST: case gl.SCISSOR_TEST: case gl.STENCIL_TEST: if (boundValue) { gl.enable(binding); } else { gl.disable(binding); } break; default: console.log("No GL restore behavior for 0x" + binding.toString(16)); break; } if (activeTexture) { gl.activeTexture(activeTexture); } } } var glPreserveState = WGLUPreserveGLState; var distortionVS = ['attribute vec2 position;', 'attribute vec3 texCoord;', 'varying vec2 vTexCoord;', 'uniform vec4 viewportOffsetScale[2];', 'void main() {', ' vec4 viewport = viewportOffsetScale[int(texCoord.z)];', ' vTexCoord = (texCoord.xy * viewport.zw) + viewport.xy;', ' gl_Position = vec4( position, 1.0, 1.0 );', '}'].join('\n'); var distortionFS = ['precision mediump float;', 'uniform sampler2D diffuse;', 'varying vec2 vTexCoord;', 'void main() {', ' gl_FragColor = texture2D(diffuse, vTexCoord);', '}'].join('\n'); function CardboardDistorter(gl, cardboardUI, bufferScale, dirtySubmitFrameBindings) { this.gl = gl; this.cardboardUI = cardboardUI; this.bufferScale = bufferScale; this.dirtySubmitFrameBindings = dirtySubmitFrameBindings; this.ctxAttribs = gl.getContextAttributes(); this.instanceExt = gl.getExtension('ANGLE_instanced_arrays'); this.meshWidth = 20; this.meshHeight = 20; this.bufferWidth = gl.drawingBufferWidth; this.bufferHeight = gl.drawingBufferHeight; this.realBindFramebuffer = gl.bindFramebuffer; this.realEnable = gl.enable; this.realDisable = gl.disable; this.realColorMask = gl.colorMask; this.realClearColor = gl.clearColor; this.realViewport = gl.viewport; if (!isIOS()) { this.realCanvasWidth = Object.getOwnPropertyDescriptor(gl.canvas.__proto__, 'width'); this.realCanvasHeight = Object.getOwnPropertyDescriptor(gl.canvas.__proto__, 'height'); } this.isPatched = false; this.lastBoundFramebuffer = null; this.cullFace = false; this.depthTest = false; this.blend = false; this.scissorTest = false; this.stencilTest = false; this.viewport = [0, 0, 0, 0]; this.colorMask = [true, true, true, true]; this.clearColor = [0, 0, 0, 0]; this.attribs = { position: 0, texCoord: 1 }; this.program = linkProgram(gl, distortionVS, distortionFS, this.attribs); this.uniforms = getProgramUniforms(gl, this.program); this.viewportOffsetScale = new Float32Array(8); this.setTextureBounds(); this.vertexBuffer = gl.createBuffer(); this.indexBuffer = gl.createBuffer(); this.indexCount = 0; this.renderTarget = gl.createTexture(); this.framebuffer = gl.createFramebuffer(); this.depthStencilBuffer = null; this.depthBuffer = null; this.stencilBuffer = null; if (this.ctxAttribs.depth && this.ctxAttribs.stencil) { this.depthStencilBuffer = gl.createRenderbuffer(); } else if (this.ctxAttribs.depth) { this.depthBuffer = gl.createRenderbuffer(); } else if (this.ctxAttribs.stencil) { this.stencilBuffer = gl.createRenderbuffer(); } this.patch(); this.onResize(); } CardboardDistorter.prototype.destroy = function () { var gl = this.gl; this.unpatch(); gl.deleteProgram(this.program); gl.deleteBuffer(this.vertexBuffer); gl.deleteBuffer(this.indexBuffer); gl.deleteTexture(this.renderTarget); gl.deleteFramebuffer(this.framebuffer); if (this.depthStencilBuffer) { gl.deleteRenderbuffer(this.depthStencilBuffer); } if (this.depthBuffer) { gl.deleteRenderbuffer(this.depthBuffer); } if (this.stencilBuffer) { gl.deleteRenderbuffer(this.stencilBuffer); } if (this.cardboardUI) { this.cardboardUI.destroy(); } }; CardboardDistorter.prototype.onResize = function () { var gl = this.gl; var self = this; var glState = [gl.RENDERBUFFER_BINDING, gl.TEXTURE_BINDING_2D, gl.TEXTURE0]; glPreserveState(gl, glState, function (gl) { self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, null); if (self.scissorTest) { self.realDisable.call(gl, gl.SCISSOR_TEST); } self.realColorMask.call(gl, true, true, true, true); self.realViewport.call(gl, 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); self.realClearColor.call(gl, 0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, self.framebuffer); gl.bindTexture(gl.TEXTURE_2D, self.renderTarget); gl.texImage2D(gl.TEXTURE_2D, 0, self.ctxAttribs.alpha ? gl.RGBA : gl.RGB, self.bufferWidth, self.bufferHeight, 0, self.ctxAttribs.alpha ? gl.RGBA : gl.RGB, gl.UNSIGNED_BYTE, null); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, self.renderTarget, 0); if (self.ctxAttribs.depth && self.ctxAttribs.stencil) { gl.bindRenderbuffer(gl.RENDERBUFFER, self.depthStencilBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, self.bufferWidth, self.bufferHeight); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, self.depthStencilBuffer); } else if (self.ctxAttribs.depth) { gl.bindRenderbuffer(gl.RENDERBUFFER, self.depthBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, self.bufferWidth, self.bufferHeight); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, self.depthBuffer); } else if (self.ctxAttribs.stencil) { gl.bindRenderbuffer(gl.RENDERBUFFER, self.stencilBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, self.bufferWidth, self.bufferHeight); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, self.stencilBuffer); } if (!gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) { console.error('Framebuffer incomplete!'); } self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, self.lastBoundFramebuffer); if (self.scissorTest) { self.realEnable.call(gl, gl.SCISSOR_TEST); } self.realColorMask.apply(gl, self.colorMask); self.realViewport.apply(gl, self.viewport); self.realClearColor.apply(gl, self.clearColor); }); if (this.cardboardUI) { this.cardboardUI.onResize(); } }; CardboardDistorter.prototype.patch = function () { if (this.isPatched) { return; } var self = this; var canvas = this.gl.canvas; var gl = this.gl; if (!isIOS()) { canvas.width = getScreenWidth() * this.bufferScale; canvas.height = getScreenHeight() * this.bufferScale; Object.defineProperty(canvas, 'width', { configurable: true, enumerable: true, get: function get() { return self.bufferWidth; }, set: function set(value) { self.bufferWidth = value; self.realCanvasWidth.set.call(canvas, value); self.onResize(); } }); Object.defineProperty(canvas, 'height', { configurable: true, enumerable: true, get: function get() { return self.bufferHeight; }, set: function set(value) { self.bufferHeight = value; self.realCanvasHeight.set.call(canvas, value); self.onResize(); } }); } this.lastBoundFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); if (this.lastBoundFramebuffer == null) { this.lastBoundFramebuffer = this.framebuffer; this.gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); } this.gl.bindFramebuffer = function (target, framebuffer) { self.lastBoundFramebuffer = framebuffer ? framebuffer : self.framebuffer; self.realBindFramebuffer.call(gl, target, self.lastBoundFramebuffer); }; this.cullFace = gl.getParameter(gl.CULL_FACE); this.depthTest = gl.getParameter(gl.DEPTH_TEST); this.blend = gl.getParameter(gl.BLEND); this.scissorTest = gl.getParameter(gl.SCISSOR_TEST); this.stencilTest = gl.getParameter(gl.STENCIL_TEST); gl.enable = function (pname) { switch (pname) { case gl.CULL_FACE: self.cullFace = true;break; case gl.DEPTH_TEST: self.depthTest = true;break; case gl.BLEND: self.blend = true;break; case gl.SCISSOR_TEST: self.scissorTest = true;break; case gl.STENCIL_TEST: self.stencilTest = true;break; } self.realEnable.call(gl, pname); }; gl.disable = function (pname) { switch (pname) { case gl.CULL_FACE: self.cullFace = false;break; case gl.DEPTH_TEST: self.depthTest = false;break; case gl.BLEND: self.blend = false;break; case gl.SCISSOR_TEST: self.scissorTest = false;break; case gl.STENCIL_TEST: self.stencilTest = false;break; } self.realDisable.call(gl, pname); }; this.colorMask = gl.getParameter(gl.COLOR_WRITEMASK); gl.colorMask = function (r, g, b, a) { self.colorMask[0] = r; self.colorMask[1] = g; self.colorMask[2] = b; self.colorMask[3] = a; self.realColorMask.call(gl, r, g, b, a); }; this.clearColor = gl.getParameter(gl.COLOR_CLEAR_VALUE); gl.clearColor = function (r, g, b, a) { self.clearColor[0] = r; self.clearColor[1] = g; self.clearColor[2] = b; self.clearColor[3] = a; self.realClearColor.call(gl, r, g, b, a); }; this.viewport = gl.getParameter(gl.VIEWPORT); gl.viewport = function (x, y, w, h) { self.viewport[0] = x; self.viewport[1] = y; self.viewport[2] = w; self.viewport[3] = h; self.realViewport.call(gl, x, y, w, h); }; this.isPatched = true; safariCssSizeWorkaround(canvas); }; CardboardDistorter.prototype.unpatch = function () { if (!this.isPatched) { return; } var gl = this.gl; var canvas = this.gl.canvas; if (!isIOS()) { Object.defineProperty(canvas, 'width', this.realCanvasWidth); Object.defineProperty(canvas, 'height', this.realCanvasHeight); } canvas.width = this.bufferWidth; canvas.height = this.bufferHeight; gl.bindFramebuffer = this.realBindFramebuffer; gl.enable = this.realEnable; gl.disable = this.realDisable; gl.colorMask = this.realColorMask; gl.clearColor = this.realClearColor; gl.viewport = this.realViewport; if (this.lastBoundFramebuffer == this.framebuffer) { gl.bindFramebuffer(gl.FRAMEBUFFER, null); } this.isPatched = false; setTimeout(function () { safariCssSizeWorkaround(canvas); }, 1); }; CardboardDistorter.prototype.setTextureBounds = function (leftBounds, rightBounds) { if (!leftBounds) { leftBounds = [0, 0, 0.5, 1]; } if (!rightBounds) { rightBounds = [0.5, 0, 0.5, 1]; } this.viewportOffsetScale[0] = leftBounds[0]; this.viewportOffsetScale[1] = leftBounds[1]; this.viewportOffsetScale[2] = leftBounds[2]; this.viewportOffsetScale[3] = leftBounds[3]; this.viewportOffsetScale[4] = rightBounds[0]; this.viewportOffsetScale[5] = rightBounds[1]; this.viewportOffsetScale[6] = rightBounds[2]; this.viewportOffsetScale[7] = rightBounds[3]; }; CardboardDistorter.prototype.submitFrame = function () { var gl = this.gl; var self = this; var glState = []; if (!this.dirtySubmitFrameBindings) { glState.push(gl.CURRENT_PROGRAM, gl.ARRAY_BUFFER_BINDING, gl.ELEMENT_ARRAY_BUFFER_BINDING, gl.TEXTURE_BINDING_2D, gl.TEXTURE0); } glPreserveState(gl, glState, function (gl) { self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, null); var positionDivisor = 0; var texCoordDivisor = 0; if (self.instanceExt) { positionDivisor = gl.getVertexAttrib(self.attribs.position, self.instanceExt.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE); texCoordDivisor = gl.getVertexAttrib(self.attribs.texCoord, self.instanceExt.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE); } if (self.cullFace) { self.realDisable.call(gl, gl.CULL_FACE); } if (self.depthTest) { self.realDisable.call(gl, gl.DEPTH_TEST); } if (self.blend) { self.realDisable.call(gl, gl.BLEND); } if (self.scissorTest) { self.realDisable.call(gl, gl.SCISSOR_TEST); } if (self.stencilTest) { self.realDisable.call(gl, gl.STENCIL_TEST); } self.realColorMask.call(gl, true, true, true, true); self.realViewport.call(gl, 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); if (self.ctxAttribs.alpha || isIOS()) { self.realClearColor.call(gl, 0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); } gl.useProgram(self.program); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.indexBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, self.vertexBuffer); gl.enableVertexAttribArray(self.attribs.position); gl.enableVertexAttribArray(self.attribs.texCoord); gl.vertexAttribPointer(self.attribs.position, 2, gl.FLOAT, false, 20, 0); gl.vertexAttribPointer(self.attribs.texCoord, 3, gl.FLOAT, false, 20, 8); if (self.instanceExt) { if (positionDivisor != 0) { self.instanceExt.vertexAttribDivisorANGLE(self.attribs.position, 0); } if (texCoordDivisor != 0) { self.instanceExt.vertexAttribDivisorANGLE(self.attribs.texCoord, 0); } } gl.activeTexture(gl.TEXTURE0); gl.uniform1i(self.uniforms.diffuse, 0); gl.bindTexture(gl.TEXTURE_2D, self.renderTarget); gl.uniform4fv(self.uniforms.viewportOffsetScale, self.viewportOffsetScale); gl.drawElements(gl.TRIANGLES, self.indexCount, gl.UNSIGNED_SHORT, 0); if (self.cardboardUI) { self.cardboardUI.renderNoState(); } self.realBindFramebuffer.call(self.gl, gl.FRAMEBUFFER, self.framebuffer); if (!self.ctxAttribs.preserveDrawingBuffer) { self.realClearColor.call(gl, 0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); } if (!self.dirtySubmitFrameBindings) { self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, self.lastBoundFramebuffer); } if (self.cullFace) { self.realEnable.call(gl, gl.CULL_FACE); } if (self.depthTest) { self.realEnable.call(gl, gl.DEPTH_TEST); } if (self.blend) { self.realEnable.call(gl, gl.BLEND); } if (self.scissorTest) { self.realEnable.call(gl, gl.SCISSOR_TEST); } if (self.stencilTest) { self.realEnable.call(gl, gl.STENCIL_TEST); } self.realColorMask.apply(gl, self.colorMask); self.realViewport.apply(gl, self.viewport); if (self.ctxAttribs.alpha || !self.ctxAttribs.preserveDrawingBuffer) { self.realClearColor.apply(gl, self.clearColor); } if (self.instanceExt) { if (positionDivisor != 0) { self.instanceExt.vertexAttribDivisorANGLE(self.attribs.position, positionDivisor); } if (texCoordDivisor != 0) { self.instanceExt.vertexAttribDivisorANGLE(self.attribs.texCoord, texCoordDivisor); } } }); if (isIOS()) { var canvas = gl.canvas; if (canvas.width != self.bufferWidth || canvas.height != self.bufferHeight) { self.bufferWidth = canvas.width; self.bufferHeight = canvas.height; self.onResize(); } } }; CardboardDistorter.prototype.updateDeviceInfo = function (deviceInfo) { var gl = this.gl; var self = this; var glState = [gl.ARRAY_BUFFER_BINDING, gl.ELEMENT_ARRAY_BUFFER_BINDING]; glPreserveState(gl, glState, function (gl) { var vertices = self.computeMeshVertices_(self.meshWidth, self.meshHeight, deviceInfo); gl.bindBuffer(gl.ARRAY_BUFFER, self.vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); if (!self.indexCount) { var indices = self.computeMeshIndices_(self.meshWidth, self.meshHeight); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); self.indexCount = indices.length; } }); }; CardboardDistorter.prototype.computeMeshVertices_ = function (width, height, deviceInfo) { var vertices = new Float32Array(2 * width * height * 5); var lensFrustum = deviceInfo.getLeftEyeVisibleTanAngles(); var noLensFrustum = deviceInfo.getLeftEyeNoLensTanAngles(); var viewport = deviceInfo.getLeftEyeVisibleScreenRect(noLensFrustum); var vidx = 0; for (var e = 0; e < 2; e++) { for (var j = 0; j < height; j++) { for (var i = 0; i < width; i++, vidx++) { var u = i / (width - 1); var v = j / (height - 1); var s = u; var t = v; var x = lerp(lensFrustum[0], lensFrustum[2], u); var y = lerp(lensFrustum[3], lensFrustum[1], v); var d = Math.sqrt(x * x + y * y); var r = deviceInfo.distortion.distortInverse(d); var p = x * r / d; var q = y * r / d; u = (p - noLensFrustum[0]) / (noLensFrustum[2] - noLensFrustum[0]); v = (q - noLensFrustum[3]) / (noLensFrustum[1] - noLensFrustum[3]); u = (viewport.x + u * viewport.width - 0.5) * 2.0; v = (viewport.y + v * viewport.height - 0.5) * 2.0; vertices[vidx * 5 + 0] = u; vertices[vidx * 5 + 1] = v; vertices[vidx * 5 + 2] = s; vertices[vidx * 5 + 3] = t; vertices[vidx * 5 + 4] = e; } } var w = lensFrustum[2] - lensFrustum[0]; lensFrustum[0] = -(w + lensFrustum[0]); lensFrustum[2] = w - lensFrustum[2]; w = noLensFrustum[2] - noLensFrustum[0]; noLensFrustum[0] = -(w + noLensFrustum[0]); noLensFrustum[2] = w - noLensFrustum[2]; viewport.x = 1 - (viewport.x + viewport.width); } return vertices; }; CardboardDistorter.prototype.computeMeshIndices_ = function (width, height) { var indices = new Uint16Array(2 * (width - 1) * (height - 1) * 6); var halfwidth = width / 2; var halfheight = height / 2; var vidx = 0; var iidx = 0; for (var e = 0; e < 2; e++) { for (var j = 0; j < height; j++) { for (var i = 0; i < width; i++, vidx++) { if (i == 0 || j == 0) continue; if (i <= halfwidth == j <= halfheight) { indices[iidx++] = vidx; indices[iidx++] = vidx - width - 1; indices[iidx++] = vidx - width; indices[iidx++] = vidx - width - 1; indices[iidx++] = vidx; indices[iidx++] = vidx - 1; } else { indices[iidx++] = vidx - 1; indices[iidx++] = vidx - width; indices[iidx++] = vidx; indices[iidx++] = vidx - width; indices[iidx++] = vidx - 1; indices[iidx++] = vidx - width - 1; } } } } return indices; }; CardboardDistorter.prototype.getOwnPropertyDescriptor_ = function (proto, attrName) { var descriptor = Object.getOwnPropertyDescriptor(proto, attrName); if (descriptor.get === undefined || descriptor.set === undefined) { descriptor.configurable = true; descriptor.enumerable = true; descriptor.get = function () { return this.getAttribute(attrName); }; descriptor.set = function (val) { this.setAttribute(attrName, val); }; } return descriptor; }; var uiVS = ['attribute vec2 position;', 'uniform mat4 projectionMat;', 'void main() {', ' gl_Position = projectionMat * vec4( position, -1.0, 1.0 );', '}'].join('\n'); var uiFS = ['precision mediump float;', 'uniform vec4 color;', 'void main() {', ' gl_FragColor = color;', '}'].join('\n'); var DEG2RAD = Math.PI / 180.0; var kAnglePerGearSection = 60; var kOuterRimEndAngle = 12; var kInnerRimBeginAngle = 20; var kOuterRadius = 1; var kMiddleRadius = 0.75; var kInnerRadius = 0.3125; var kCenterLineThicknessDp = 4; var kButtonWidthDp = 28; var kTouchSlopFactor = 1.5; function CardboardUI(gl) { this.gl = gl; this.attribs = { position: 0 }; this.program = linkProgram(gl, uiVS, uiFS, this.attribs); this.uniforms = getProgramUniforms(gl, this.program); this.vertexBuffer = gl.createBuffer(); this.gearOffset = 0; this.gearVertexCount = 0; this.arrowOffset = 0; this.arrowVertexCount = 0; this.projMat = new Float32Array(16); this.listener = null; this.onResize(); } CardboardUI.prototype.destroy = function () { var gl = this.gl; if (this.listener) { gl.canvas.removeEventListener('click', this.listener, false); } gl.deleteProgram(this.program); gl.deleteBuffer(this.vertexBuffer); }; CardboardUI.prototype.listen = function (optionsCallback, backCallback) { var canvas = this.gl.canvas; this.listener = function (event) { var midline = canvas.clientWidth / 2; var buttonSize = kButtonWidthDp * kTouchSlopFactor; if (event.clientX > midline - buttonSize && event.clientX < midline + buttonSize && event.clientY > canvas.clientHeight - buttonSize) { optionsCallback(event); } else if (event.clientX < buttonSize && event.clientY < buttonSize) { backCallback(event); } }; canvas.addEventListener('click', this.listener, false); }; CardboardUI.prototype.onResize = function () { var gl = this.gl; var self = this; var glState = [gl.ARRAY_BUFFER_BINDING]; glPreserveState(gl, glState, function (gl) { var vertices = []; var midline = gl.drawingBufferWidth / 2; var physicalPixels = Math.max(screen.width, screen.height) * window.devicePixelRatio; var scalingRatio = gl.drawingBufferWidth / physicalPixels; var dps = scalingRatio * window.devicePixelRatio; var lineWidth = kCenterLineThicknessDp * dps / 2; var buttonSize = kButtonWidthDp * kTouchSlopFactor * dps; var buttonScale = kButtonWidthDp * dps / 2; var buttonBorder = (kButtonWidthDp * kTouchSlopFactor - kButtonWidthDp) * dps; vertices.push(midline - lineWidth, buttonSize); vertices.push(midline - lineWidth, gl.drawingBufferHeight); vertices.push(midline + lineWidth, buttonSize); vertices.push(midline + lineWidth, gl.drawingBufferHeight); self.gearOffset = vertices.length / 2; function addGearSegment(theta, r) { var angle = (90 - theta) * DEG2RAD; var x = Math.cos(angle); var y = Math.sin(angle); vertices.push(kInnerRadius * x * buttonScale + midline, kInnerRadius * y * buttonScale + buttonScale); vertices.push(r * x * buttonScale + midline, r * y * buttonScale + buttonScale); } for (var i = 0; i <= 6; i++) { var segmentTheta = i * kAnglePerGearSection; addGearSegment(segmentTheta, kOuterRadius); addGearSegment(segmentTheta + kOuterRimEndAngle, kOuterRadius); addGearSegment(segmentTheta + kInnerRimBeginAngle, kMiddleRadius); addGearSegment(segmentTheta + (kAnglePerGearSection - kInnerRimBeginAngle), kMiddleRadius); addGearSegment(segmentTheta + (kAnglePerGearSection - kOuterRimEndAngle), kOuterRadius); } self.gearVertexCount = vertices.length / 2 - self.gearOffset; self.arrowOffset = vertices.length / 2; function addArrowVertex(x, y) { vertices.push(buttonBorder + x, gl.drawingBufferHeight - buttonBorder - y); } var angledLineWidth = lineWidth / Math.sin(45 * DEG2RAD); addArrowVertex(0, buttonScale); addArrowVertex(buttonScale, 0); addArrowVertex(buttonScale + angledLineWidth, angledLineWidth); addArrowVertex(angledLineWidth, buttonScale + angledLineWidth); addArrowVertex(angledLineWidth, buttonScale - angledLineWidth); addArrowVertex(0, buttonScale); addArrowVertex(buttonScale, buttonScale * 2); addArrowVertex(buttonScale + angledLineWidth, buttonScale * 2 - angledLineWidth); addArrowVertex(angledLineWidth, buttonScale - angledLineWidth); addArrowVertex(0, buttonScale); addArrowVertex(angledLineWidth, buttonScale - lineWidth); addArrowVertex(kButtonWidthDp * dps, buttonScale - lineWidth); addArrowVertex(angledLineWidth, buttonScale + lineWidth); addArrowVertex(kButtonWidthDp * dps, buttonScale + lineWidth); self.arrowVertexCount = vertices.length / 2 - self.arrowOffset; gl.bindBuffer(gl.ARRAY_BUFFER, self.vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); }); }; CardboardUI.prototype.render = function () { var gl = this.gl; var self = this; var glState = [gl.CULL_FACE, gl.DEPTH_TEST, gl.BLEND, gl.SCISSOR_TEST, gl.STENCIL_TEST, gl.COLOR_WRITEMASK, gl.VIEWPORT, gl.CURRENT_PROGRAM, gl.ARRAY_BUFFER_BINDING]; glPreserveState(gl, glState, function (gl) { gl.disable(gl.CULL_FACE); gl.disable(gl.DEPTH_TEST); gl.disable(gl.BLEND); gl.disable(gl.SCISSOR_TEST); gl.disable(gl.STENCIL_TEST); gl.colorMask(true, true, true, true); gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); self.renderNoState(); }); }; CardboardUI.prototype.renderNoState = function () { var gl = this.gl; gl.useProgram(this.program); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.enableVertexAttribArray(this.attribs.position); gl.vertexAttribPointer(this.attribs.position, 2, gl.FLOAT, false, 8, 0); gl.uniform4f(this.uniforms.color, 1.0, 1.0, 1.0, 1.0); orthoMatrix(this.projMat, 0, gl.drawingBufferWidth, 0, gl.drawingBufferHeight, 0.1, 1024.0); gl.uniformMatrix4fv(this.uniforms.projectionMat, false, this.projMat); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); gl.drawArrays(gl.TRIANGLE_STRIP, this.gearOffset, this.gearVertexCount); gl.drawArrays(gl.TRIANGLE_STRIP, this.arrowOffset, this.arrowVertexCount); }; function Distortion(coefficients) { this.coefficients = coefficients; } Distortion.prototype.distortInverse = function (radius) { var r0 = 0; var r1 = 1; var dr0 = radius - this.distort(r0); while (Math.abs(r1 - r0) > 0.0001 ) { var dr1 = radius - this.distort(r1); var r2 = r1 - dr1 * ((r1 - r0) / (dr1 - dr0)); r0 = r1; r1 = r2; dr0 = dr1; } return r1; }; Distortion.prototype.distort = function (radius) { var r2 = radius * radius; var ret = 0; for (var i = 0; i < this.coefficients.length; i++) { ret = r2 * (ret + this.coefficients[i]); } return (ret + 1) * radius; }; var degToRad = Math.PI / 180; var radToDeg = 180 / Math.PI; var Vector3 = function Vector3(x, y, z) { this.x = x || 0; this.y = y || 0; this.z = z || 0; }; Vector3.prototype = { constructor: Vector3, set: function set(x, y, z) { this.x = x; this.y = y; this.z = z; return this; }, copy: function copy(v) { this.x = v.x; this.y = v.y; this.z = v.z; return this; }, length: function length() { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); }, normalize: function normalize() { var scalar = this.length(); if (scalar !== 0) { var invScalar = 1 / scalar; this.multiplyScalar(invScalar); } else { this.x = 0; this.y = 0; this.z = 0; } return this; }, multiplyScalar: function multiplyScalar(scalar) { this.x *= scalar; this.y *= scalar; this.z *= scalar; }, applyQuaternion: function applyQuaternion(q) { var x = this.x; var y = this.y; var z = this.z; var qx = q.x; var qy = q.y; var qz = q.z; var qw = q.w; var ix = qw * x + qy * z - qz * y; var iy = qw * y + qz * x - qx * z; var iz = qw * z + qx * y - qy * x; var iw = -qx * x - qy * y - qz * z; this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; return this; }, dot: function dot(v) { return this.x * v.x + this.y * v.y + this.z * v.z; }, crossVectors: function crossVectors(a, b) { var ax = a.x, ay = a.y, az = a.z; var bx = b.x, by = b.y, bz = b.z; this.x = ay * bz - az * by; this.y = az * bx - ax * bz; this.z = ax * by - ay * bx; return this; } }; var Quaternion = function Quaternion(x, y, z, w) { this.x = x || 0; this.y = y || 0; this.z = z || 0; this.w = w !== undefined ? w : 1; }; Quaternion.prototype = { constructor: Quaternion, set: function set(x, y, z, w) { this.x = x; this.y = y; this.z = z; this.w = w; return this; }, copy: function copy(quaternion) { this.x = quaternion.x; this.y = quaternion.y; this.z = quaternion.z; this.w = quaternion.w; return this; }, setFromEulerXYZ: function setFromEulerXYZ(x, y, z) { var c1 = Math.cos(x / 2); var c2 = Math.cos(y / 2); var c3 = Math.cos(z / 2); var s1 = Math.sin(x / 2); var s2 = Math.sin(y / 2); var s3 = Math.sin(z / 2); this.x = s1 * c2 * c3 + c1 * s2 * s3; this.y = c1 * s2 * c3 - s1 * c2 * s3; this.z = c1 * c2 * s3 + s1 * s2 * c3; this.w = c1 * c2 * c3 - s1 * s2 * s3; return this; }, setFromEulerYXZ: function setFromEulerYXZ(x, y, z) { var c1 = Math.cos(x / 2); var c2 = Math.cos(y / 2); var c3 = Math.cos(z / 2); var s1 = Math.sin(x / 2); var s2 = Math.sin(y / 2); var s3 = Math.sin(z / 2); this.x = s1 * c2 * c3 + c1 * s2 * s3; this.y = c1 * s2 * c3 - s1 * c2 * s3; this.z = c1 * c2 * s3 - s1 * s2 * c3; this.w = c1 * c2 * c3 + s1 * s2 * s3; return this; }, setFromAxisAngle: function setFromAxisAngle(axis, angle) { var halfAngle = angle / 2, s = Math.sin(halfAngle); this.x = axis.x * s; this.y = axis.y * s; this.z = axis.z * s; this.w = Math.cos(halfAngle); return this; }, multiply: function multiply(q) { return this.multiplyQuaternions(this, q); }, multiplyQuaternions: function multiplyQuaternions(a, b) { var qax = a.x, qay = a.y, qaz = a.z, qaw = a.w; var qbx = b.x, qby = b.y, qbz = b.z, qbw = b.w; this.x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; this.y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; this.z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; this.w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; return this; }, inverse: function inverse() { this.x *= -1; this.y *= -1; this.z *= -1; this.normalize(); return this; }, normalize: function normalize() { var l = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); if (l === 0) { this.x = 0; this.y = 0; this.z = 0; this.w = 1; } else { l = 1 / l; this.x = this.x * l; this.y = this.y * l; this.z = this.z * l; this.w = this.w * l; } return this; }, slerp: function slerp(qb, t) { if (t === 0) return this; if (t === 1) return this.copy(qb); var x = this.x, y = this.y, z = this.z, w = this.w; var cosHalfTheta = w * qb.w + x * qb.x + y * qb.y + z * qb.z; if (cosHalfTheta < 0) { this.w = -qb.w; this.x = -qb.x; this.y = -qb.y; this.z = -qb.z; cosHalfTheta = -cosHalfTheta; } else { this.copy(qb); } if (cosHalfTheta >= 1.0) { this.w = w; this.x = x; this.y = y; this.z = z; return this; } var halfTheta = Math.acos(cosHalfTheta); var sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta); if (Math.abs(sinHalfTheta) < 0.001) { this.w = 0.5 * (w + this.w); this.x = 0.5 * (x + this.x); this.y = 0.5 * (y + this.y); this.z = 0.5 * (z + this.z); return this; } var ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta, ratioB = Math.sin(t * halfTheta) / sinHalfTheta; this.w = w * ratioA + this.w * ratioB; this.x = x * ratioA + this.x * ratioB; this.y = y * ratioA + this.y * ratioB; this.z = z * ratioA + this.z * ratioB; return this; }, setFromUnitVectors: function () { var v1, r; var EPS = 0.000001; return function (vFrom, vTo) { if (v1 === undefined) v1 = new Vector3(); r = vFrom.dot(vTo) + 1; if (r < EPS) { r = 0; if (Math.abs(vFrom.x) > Math.abs(vFrom.z)) { v1.set(-vFrom.y, vFrom.x, 0); } else { v1.set(0, -vFrom.z, vFrom.y); } } else { v1.crossVectors(vFrom, vTo); } this.x = v1.x; this.y = v1.y; this.z = v1.z; this.w = r; this.normalize(); return this; }; }() }; function Device(params) { this.width = params.width || getScreenWidth(); this.height = params.height || getScreenHeight(); this.widthMeters = params.widthMeters; this.heightMeters = params.heightMeters; this.bevelMeters = params.bevelMeters; } var DEFAULT_ANDROID = new Device({ widthMeters: 0.110, heightMeters: 0.062, bevelMeters: 0.004 }); var DEFAULT_IOS = new Device({ widthMeters: 0.1038, heightMeters: 0.0584, bevelMeters: 0.004 }); var Viewers = { CardboardV1: new CardboardViewer({ id: 'CardboardV1', label: 'Cardboard I/O 2014', fov: 40, interLensDistance: 0.060, baselineLensDistance: 0.035, screenLensDistance: 0.042, distortionCoefficients: [0.441, 0.156], inverseCoefficients: [-0.4410035, 0.42756155, -0.4804439, 0.5460139, -0.58821183, 0.5733938, -0.48303202, 0.33299083, -0.17573841, 0.0651772, -0.01488963, 0.001559834] }), CardboardV2: new CardboardViewer({ id: 'CardboardV2', label: 'Cardboard I/O 2015', fov: 60, interLensDistance: 0.064, baselineLensDistance: 0.035, screenLensDistance: 0.039, distortionCoefficients: [0.34, 0.55], inverseCoefficients: [-0.33836704, -0.18162185, 0.862655, -1.2462051, 1.0560602, -0.58208317, 0.21609078, -0.05444823, 0.009177956, -9.904169E-4, 6.183535E-5, -1.6981803E-6] }) }; function DeviceInfo(deviceParams, additionalViewers) { this.viewer = Viewers.CardboardV2; this.updateDeviceParams(deviceParams); this.distortion = new Distortion(this.viewer.distortionCoefficients); for (var i = 0; i < additionalViewers.length; i++) { var viewer = additionalViewers[i]; Viewers[viewer.id] = new CardboardViewer(viewer); } } DeviceInfo.prototype.updateDeviceParams = function (deviceParams) { this.device = this.determineDevice_(deviceParams) || this.device; }; DeviceInfo.prototype.getDevice = function () { return this.device; }; DeviceInfo.prototype.setViewer = function (viewer) { this.viewer = viewer; this.distortion = new Distortion(this.viewer.distortionCoefficients); }; DeviceInfo.prototype.determineDevice_ = function (deviceParams) { if (!deviceParams) { if (isIOS()) { console.warn('Using fallback iOS device measurements.'); return DEFAULT_IOS; } else { console.warn('Using fallback Android device measurements.'); return DEFAULT_ANDROID; } } var METERS_PER_INCH = 0.0254; var metersPerPixelX = METERS_PER_INCH / deviceParams.xdpi; var metersPerPixelY = METERS_PER_INCH / deviceParams.ydpi; var width = getScreenWidth(); var height = getScreenHeight(); return new Device({ widthMeters: metersPerPixelX * width, heightMeters: metersPerPixelY * height, bevelMeters: deviceParams.bevelMm * 0.001 }); }; DeviceInfo.prototype.getDistortedFieldOfViewLeftEye = function () { var viewer = this.viewer; var device = this.device; var distortion = this.distortion; var eyeToScreenDistance = viewer.screenLensDistance; var outerDist = (device.widthMeters - viewer.interLensDistance) / 2; var innerDist = viewer.interLensDistance / 2; var bottomDist = viewer.baselineLensDistance - device.bevelMeters; var topDist = device.heightMeters - bottomDist; var outerAngle = radToDeg * Math.atan(distortion.distort(outerDist / eyeToScreenDistance)); var innerAngle = radToDeg * Math.atan(distortion.distort(innerDist / eyeToScreenDistance)); var bottomAngle = radToDeg * Math.atan(distortion.distort(bottomDist / eyeToScreenDistance)); var topAngle = radToDeg * Math.atan(distortion.distort(topDist / eyeToScreenDistance)); return { leftDegrees: Math.min(outerAngle, viewer.fov), rightDegrees: Math.min(innerAngle, viewer.fov), downDegrees: Math.min(bottomAngle, viewer.fov), upDegrees: Math.min(topAngle, viewer.fov) }; }; DeviceInfo.prototype.getLeftEyeVisibleTanAngles = function () { var viewer = this.viewer; var device = this.device; var distortion = this.distortion; var fovLeft = Math.tan(-degToRad * viewer.fov); var fovTop = Math.tan(degToRad * viewer.fov); var fovRight = Math.tan(degToRad * viewer.fov); var fovBottom = Math.tan(-degToRad * viewer.fov); var halfWidth = device.widthMeters / 4; var halfHeight = device.heightMeters / 2; var verticalLensOffset = viewer.baselineLensDistance - device.bevelMeters - halfHeight; var centerX = viewer.interLensDistance / 2 - halfWidth; var centerY = -verticalLensOffset; var centerZ = viewer.screenLensDistance; var screenLeft = distortion.distort((centerX - halfWidth) / centerZ); var screenTop = distortion.distort((centerY + halfHeight) / centerZ); var screenRight = distortion.distort((centerX + halfWidth) / centerZ); var screenBottom = distortion.distort((centerY - halfHeight) / centerZ); var result = new Float32Array(4); result[0] = Math.max(fovLeft, screenLeft); result[1] = Math.min(fovTop, screenTop); result[2] = Math.min(fovRight, screenRight); result[3] = Math.max(fovBottom, screenBottom); return result; }; DeviceInfo.prototype.getLeftEyeNoLensTanAngles = function () { var viewer = this.viewer; var device = this.device; var distortion = this.distortion; var result = new Float32Array(4); var fovLeft = distortion.distortInverse(Math.tan(-degToRad * viewer.fov)); var fovTop = distortion.distortInverse(Math.tan(degToRad * viewer.fov)); var fovRight = distortion.distortInverse(Math.tan(degToRad * viewer.fov)); var fovBottom = distortion.distortInverse(Math.tan(-degToRad * viewer.fov)); var halfWidth = device.widthMeters / 4; var halfHeight = device.heightMeters / 2; var verticalLensOffset = viewer.baselineLensDistance - device.bevelMeters - halfHeight; var centerX = viewer.interLensDistance / 2 - halfWidth; var centerY = -verticalLensOffset; var centerZ = viewer.screenLensDistance; var screenLeft = (centerX - halfWidth) / centerZ; var screenTop = (centerY + halfHeight) / centerZ; var screenRight = (centerX + halfWidth) / centerZ; var screenBottom = (centerY - halfHeight) / centerZ; result[0] = Math.max(fovLeft, screenLeft); result[1] = Math.min(fovTop, screenTop); result[2] = Math.min(fovRight, screenRight); result[3] = Math.max(fovBottom, screenBottom); return result; }; DeviceInfo.prototype.getLeftEyeVisibleScreenRect = function (undistortedFrustum) { var viewer = this.viewer; var device = this.device; var dist = viewer.screenLensDistance; var eyeX = (device.widthMeters - viewer.interLensDistance) / 2; var eyeY = viewer.baselineLensDistance - device.bevelMeters; var left = (undistortedFrustum[0] * dist + eyeX) / device.widthMeters; var top = (undistortedFrustum[1] * dist + eyeY) / device.heightMeters; var right = (undistortedFrustum[2] * dist + eyeX) / device.widthMeters; var bottom = (undistortedFrustum[3] * dist + eyeY) / device.heightMeters; return { x: left, y: bottom, width: right - left, height: top - bottom }; }; DeviceInfo.prototype.getFieldOfViewLeftEye = function (opt_isUndistorted) { return opt_isUndistorted ? this.getUndistortedFieldOfViewLeftEye() : this.getDistortedFieldOfViewLeftEye(); }; DeviceInfo.prototype.getFieldOfViewRightEye = function (opt_isUndistorted) { var fov = this.getFieldOfViewLeftEye(opt_isUndistorted); return { leftDegrees: fov.rightDegrees, rightDegrees: fov.leftDegrees, upDegrees: fov.upDegrees, downDegrees: fov.downDegrees }; }; DeviceInfo.prototype.getUndistortedFieldOfViewLeftEye = function () { var p = this.getUndistortedParams_(); return { leftDegrees: radToDeg * Math.atan(p.outerDist), rightDegrees: radToDeg * Math.atan(p.innerDist), downDegrees: radToDeg * Math.atan(p.bottomDist), upDegrees: radToDeg * Math.atan(p.topDist) }; }; DeviceInfo.prototype.getUndistortedViewportLeftEye = function () { var p = this.getUndistortedParams_(); var viewer = this.viewer; var device = this.device; var eyeToScreenDistance = viewer.screenLensDistance; var screenWidth = device.widthMeters / eyeToScreenDistance; var screenHeight = device.heightMeters / eyeToScreenDistance; var xPxPerTanAngle = device.width / screenWidth; var yPxPerTanAngle = device.height / screenHeight; var x = Math.round((p.eyePosX - p.outerDist) * xPxPerTanAngle); var y = Math.round((p.eyePosY - p.bottomDist) * yPxPerTanAngle); return { x: x, y: y, width: Math.round((p.eyePosX + p.innerDist) * xPxPerTanAngle) - x, height: Math.round((p.eyePosY + p.topDist) * yPxPerTanAngle) - y }; }; DeviceInfo.prototype.getUndistortedParams_ = function () { var viewer = this.viewer; var device = this.device; var distortion = this.distortion; var eyeToScreenDistance = viewer.screenLensDistance; var halfLensDistance = viewer.interLensDistance / 2 / eyeToScreenDistance; var screenWidth = device.widthMeters / eyeToScreenDistance; var screenHeight = device.heightMeters / eyeToScreenDistance; var eyePosX = screenWidth / 2 - halfLensDistance; var eyePosY = (viewer.baselineLensDistance - device.bevelMeters) / eyeToScreenDistance; var maxFov = viewer.fov; var viewerMax = distortion.distortInverse(Math.tan(degToRad * maxFov)); var outerDist = Math.min(eyePosX, viewerMax); var innerDist = Math.min(halfLensDistance, viewerMax); var bottomDist = Math.min(eyePosY, viewerMax); var topDist = Math.min(screenHeight - eyePosY, viewerMax); return { outerDist: outerDist, innerDist: innerDist, topDist: topDist, bottomDist: bottomDist, eyePosX: eyePosX, eyePosY: eyePosY }; }; function CardboardViewer(params) { this.id = params.id; this.label = params.label; this.fov = params.fov; this.interLensDistance = params.interLensDistance; this.baselineLensDistance = params.baselineLensDistance; this.screenLensDistance = params.screenLensDistance; this.distortionCoefficients = params.distortionCoefficients; this.inverseCoefficients = params.inverseCoefficients; } DeviceInfo.Viewers = Viewers; var format = 1; var last_updated = "2019-11-09T17:36:14Z"; var devices = [{"type":"android","rules":[{"mdmh":"asus/*/Nexus 7/*"},{"ua":"Nexus 7"}],"dpi":[320.8,323],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"asus/*/ASUS_X00PD/*"},{"ua":"ASUS_X00PD"}],"dpi":245,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"asus/*/ASUS_X008D/*"},{"ua":"ASUS_X008D"}],"dpi":282,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"asus/*/ASUS_Z00AD/*"},{"ua":"ASUS_Z00AD"}],"dpi":[403,404.6],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Google/*/Pixel 2 XL/*"},{"ua":"Pixel 2 XL"}],"dpi":537.9,"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Google/*/Pixel 3 XL/*"},{"ua":"Pixel 3 XL"}],"dpi":[558.5,553.8],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Google/*/Pixel XL/*"},{"ua":"Pixel XL"}],"dpi":[537.9,533],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Google/*/Pixel 3/*"},{"ua":"Pixel 3"}],"dpi":442.4,"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Google/*/Pixel 2/*"},{"ua":"Pixel 2"}],"dpi":441,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"Google/*/Pixel/*"},{"ua":"Pixel"}],"dpi":[432.6,436.7],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"HTC/*/HTC6435LVW/*"},{"ua":"HTC6435LVW"}],"dpi":[449.7,443.3],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"HTC/*/HTC One XL/*"},{"ua":"HTC One XL"}],"dpi":[315.3,314.6],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"htc/*/Nexus 9/*"},{"ua":"Nexus 9"}],"dpi":289,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"HTC/*/HTC One M9/*"},{"ua":"HTC One M9"}],"dpi":[442.5,443.3],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"HTC/*/HTC One_M8/*"},{"ua":"HTC One_M8"}],"dpi":[449.7,447.4],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"HTC/*/HTC One/*"},{"ua":"HTC One"}],"dpi":472.8,"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Huawei/*/Nexus 6P/*"},{"ua":"Nexus 6P"}],"dpi":[515.1,518],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Huawei/*/BLN-L24/*"},{"ua":"HONORBLN-L24"}],"dpi":480,"bw":4,"ac":500},{"type":"android","rules":[{"mdmh":"Huawei/*/BKL-L09/*"},{"ua":"BKL-L09"}],"dpi":403,"bw":3.47,"ac":500},{"type":"android","rules":[{"mdmh":"LENOVO/*/Lenovo PB2-690Y/*"},{"ua":"Lenovo PB2-690Y"}],"dpi":[457.2,454.713],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"LGE/*/Nexus 5X/*"},{"ua":"Nexus 5X"}],"dpi":[422,419.9],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"LGE/*/LGMS345/*"},{"ua":"LGMS345"}],"dpi":[221.7,219.1],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"LGE/*/LG-D800/*"},{"ua":"LG-D800"}],"dpi":[422,424.1],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"LGE/*/LG-D850/*"},{"ua":"LG-D850"}],"dpi":[537.9,541.9],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"LGE/*/VS985 4G/*"},{"ua":"VS985 4G"}],"dpi":[537.9,535.6],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"LGE/*/Nexus 5/*"},{"ua":"Nexus 5 B"}],"dpi":[442.4,444.8],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"LGE/*/Nexus 4/*"},{"ua":"Nexus 4"}],"dpi":[319.8,318.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"LGE/*/LG-P769/*"},{"ua":"LG-P769"}],"dpi":[240.6,247.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"LGE/*/LGMS323/*"},{"ua":"LGMS323"}],"dpi":[206.6,204.6],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"LGE/*/LGLS996/*"},{"ua":"LGLS996"}],"dpi":[403.4,401.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Micromax/*/4560MMX/*"},{"ua":"4560MMX"}],"dpi":[240,219.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Micromax/*/A250/*"},{"ua":"Micromax A250"}],"dpi":[480,446.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Micromax/*/Micromax AQ4501/*"},{"ua":"Micromax AQ4501"}],"dpi":240,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"motorola/*/G5/*"},{"ua":"Moto G (5) Plus"}],"dpi":[403.4,403],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"motorola/*/DROID RAZR/*"},{"ua":"DROID RAZR"}],"dpi":[368.1,256.7],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"motorola/*/XT830C/*"},{"ua":"XT830C"}],"dpi":[254,255.9],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"motorola/*/XT1021/*"},{"ua":"XT1021"}],"dpi":[254,256.7],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"motorola/*/XT1023/*"},{"ua":"XT1023"}],"dpi":[254,256.7],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"motorola/*/XT1028/*"},{"ua":"XT1028"}],"dpi":[326.6,327.6],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"motorola/*/XT1034/*"},{"ua":"XT1034"}],"dpi":[326.6,328.4],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"motorola/*/XT1053/*"},{"ua":"XT1053"}],"dpi":[315.3,316.1],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"motorola/*/XT1562/*"},{"ua":"XT1562"}],"dpi":[403.4,402.7],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"motorola/*/Nexus 6/*"},{"ua":"Nexus 6 B"}],"dpi":[494.3,489.7],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"motorola/*/XT1063/*"},{"ua":"XT1063"}],"dpi":[295,296.6],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"motorola/*/XT1064/*"},{"ua":"XT1064"}],"dpi":[295,295.6],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"motorola/*/XT1092/*"},{"ua":"XT1092"}],"dpi":[422,424.1],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"motorola/*/XT1095/*"},{"ua":"XT1095"}],"dpi":[422,423.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"motorola/*/G4/*"},{"ua":"Moto G (4)"}],"dpi":401,"bw":4,"ac":1000},{"type":"android","rules":[{"mdmh":"OnePlus/*/A0001/*"},{"ua":"A0001"}],"dpi":[403.4,401],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONE E1001/*"},{"ua":"ONE E1001"}],"dpi":[442.4,441.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONE E1003/*"},{"ua":"ONE E1003"}],"dpi":[442.4,441.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONE E1005/*"},{"ua":"ONE E1005"}],"dpi":[442.4,441.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONE A2001/*"},{"ua":"ONE A2001"}],"dpi":[391.9,405.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONE A2003/*"},{"ua":"ONE A2003"}],"dpi":[391.9,405.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONE A2005/*"},{"ua":"ONE A2005"}],"dpi":[391.9,405.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONEPLUS A3000/*"},{"ua":"ONEPLUS A3000"}],"dpi":401,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONEPLUS A3003/*"},{"ua":"ONEPLUS A3003"}],"dpi":401,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONEPLUS A3010/*"},{"ua":"ONEPLUS A3010"}],"dpi":401,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONEPLUS A5000/*"},{"ua":"ONEPLUS A5000 "}],"dpi":[403.411,399.737],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONE A5010/*"},{"ua":"ONEPLUS A5010"}],"dpi":[403,400],"bw":2,"ac":1000},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONEPLUS A6000/*"},{"ua":"ONEPLUS A6000"}],"dpi":401,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONEPLUS A6003/*"},{"ua":"ONEPLUS A6003"}],"dpi":401,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONEPLUS A6010/*"},{"ua":"ONEPLUS A6010"}],"dpi":401,"bw":2,"ac":500},{"type":"android","rules":[{"mdmh":"OnePlus/*/ONEPLUS A6013/*"},{"ua":"ONEPLUS A6013"}],"dpi":401,"bw":2,"ac":500},{"type":"android","rules":[{"mdmh":"OPPO/*/X909/*"},{"ua":"X909"}],"dpi":[442.4,444.1],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/GT-I9082/*"},{"ua":"GT-I9082"}],"dpi":[184.7,185.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G360P/*"},{"ua":"SM-G360P"}],"dpi":[196.7,205.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/Nexus S/*"},{"ua":"Nexus S"}],"dpi":[234.5,229.8],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/GT-I9300/*"},{"ua":"GT-I9300"}],"dpi":[304.8,303.9],"bw":5,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-T230NU/*"},{"ua":"SM-T230NU"}],"dpi":216,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SGH-T399/*"},{"ua":"SGH-T399"}],"dpi":[217.7,231.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SGH-M919/*"},{"ua":"SGH-M919"}],"dpi":[440.8,437.7],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-N9005/*"},{"ua":"SM-N9005"}],"dpi":[386.4,387],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SAMSUNG-SM-N900A/*"},{"ua":"SAMSUNG-SM-N900A"}],"dpi":[386.4,387.7],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/GT-I9500/*"},{"ua":"GT-I9500"}],"dpi":[442.5,443.3],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/GT-I9505/*"},{"ua":"GT-I9505"}],"dpi":439.4,"bw":4,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G900F/*"},{"ua":"SM-G900F"}],"dpi":[415.6,431.6],"bw":5,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G900M/*"},{"ua":"SM-G900M"}],"dpi":[415.6,431.6],"bw":5,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G800F/*"},{"ua":"SM-G800F"}],"dpi":326.8,"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G906S/*"},{"ua":"SM-G906S"}],"dpi":[562.7,572.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/GT-I9300/*"},{"ua":"GT-I9300"}],"dpi":[306.7,304.8],"bw":5,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-T535/*"},{"ua":"SM-T535"}],"dpi":[142.6,136.4],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-N920C/*"},{"ua":"SM-N920C"}],"dpi":[515.1,518.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-N920P/*"},{"ua":"SM-N920P"}],"dpi":[386.3655,390.144],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-N920W8/*"},{"ua":"SM-N920W8"}],"dpi":[515.1,518.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/GT-I9300I/*"},{"ua":"GT-I9300I"}],"dpi":[304.8,305.8],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/GT-I9195/*"},{"ua":"GT-I9195"}],"dpi":[249.4,256.7],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SPH-L520/*"},{"ua":"SPH-L520"}],"dpi":[249.4,255.9],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SAMSUNG-SGH-I717/*"},{"ua":"SAMSUNG-SGH-I717"}],"dpi":285.8,"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SPH-D710/*"},{"ua":"SPH-D710"}],"dpi":[217.7,204.2],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/GT-N7100/*"},{"ua":"GT-N7100"}],"dpi":265.1,"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SCH-I605/*"},{"ua":"SCH-I605"}],"dpi":265.1,"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/Galaxy Nexus/*"},{"ua":"Galaxy Nexus"}],"dpi":[315.3,314.2],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-N910H/*"},{"ua":"SM-N910H"}],"dpi":[515.1,518],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-N910C/*"},{"ua":"SM-N910C"}],"dpi":[515.2,520.2],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G130M/*"},{"ua":"SM-G130M"}],"dpi":[165.9,164.8],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G928I/*"},{"ua":"SM-G928I"}],"dpi":[515.1,518.4],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G920F/*"},{"ua":"SM-G920F"}],"dpi":580.6,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G920P/*"},{"ua":"SM-G920P"}],"dpi":[522.5,577],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G925F/*"},{"ua":"SM-G925F"}],"dpi":580.6,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G925V/*"},{"ua":"SM-G925V"}],"dpi":[522.5,576.6],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G930F/*"},{"ua":"SM-G930F"}],"dpi":576.6,"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G935F/*"},{"ua":"SM-G935F"}],"dpi":533,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G950F/*"},{"ua":"SM-G950F"}],"dpi":[562.707,565.293],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G955U/*"},{"ua":"SM-G955U"}],"dpi":[522.514,525.762],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G955F/*"},{"ua":"SM-G955F"}],"dpi":[522.514,525.762],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G960F/*"},{"ua":"SM-G960F"}],"dpi":[569.575,571.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G9600/*"},{"ua":"SM-G9600"}],"dpi":[569.575,571.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G960T/*"},{"ua":"SM-G960T"}],"dpi":[569.575,571.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G960N/*"},{"ua":"SM-G960N"}],"dpi":[569.575,571.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G960U/*"},{"ua":"SM-G960U"}],"dpi":[569.575,571.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G9608/*"},{"ua":"SM-G9608"}],"dpi":[569.575,571.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G960FD/*"},{"ua":"SM-G960FD"}],"dpi":[569.575,571.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G960W/*"},{"ua":"SM-G960W"}],"dpi":[569.575,571.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G965F/*"},{"ua":"SM-G965F"}],"dpi":529,"bw":2,"ac":1000},{"type":"android","rules":[{"mdmh":"Sony/*/C6903/*"},{"ua":"C6903"}],"dpi":[442.5,443.3],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"Sony/*/D6653/*"},{"ua":"D6653"}],"dpi":[428.6,427.6],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Sony/*/E6653/*"},{"ua":"E6653"}],"dpi":[428.6,425.7],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Sony/*/E6853/*"},{"ua":"E6853"}],"dpi":[403.4,401.9],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Sony/*/SGP321/*"},{"ua":"SGP321"}],"dpi":[224.7,224.1],"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"TCT/*/ALCATEL ONE TOUCH Fierce/*"},{"ua":"ALCATEL ONE TOUCH Fierce"}],"dpi":[240,247.5],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"THL/*/thl 5000/*"},{"ua":"thl 5000"}],"dpi":[480,443.3],"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"Fly/*/IQ4412/*"},{"ua":"IQ4412"}],"dpi":307.9,"bw":3,"ac":1000},{"type":"android","rules":[{"mdmh":"ZTE/*/ZTE Blade L2/*"},{"ua":"ZTE Blade L2"}],"dpi":240,"bw":3,"ac":500},{"type":"android","rules":[{"mdmh":"BENEVE/*/VR518/*"},{"ua":"VR518"}],"dpi":480,"bw":3,"ac":500},{"type":"ios","rules":[{"res":[640,960]}],"dpi":[325.1,328.4],"bw":4,"ac":1000},{"type":"ios","rules":[{"res":[640,1136]}],"dpi":[317.1,320.2],"bw":3,"ac":1000},{"type":"ios","rules":[{"res":[750,1334]}],"dpi":326.4,"bw":4,"ac":1000},{"type":"ios","rules":[{"res":[1242,2208]}],"dpi":[453.6,458.4],"bw":4,"ac":1000},{"type":"ios","rules":[{"res":[1125,2001]}],"dpi":[410.9,415.4],"bw":4,"ac":1000},{"type":"ios","rules":[{"res":[1125,2436]}],"dpi":458,"bw":4,"ac":1000},{"type":"android","rules":[{"mdmh":"Huawei/*/EML-L29/*"},{"ua":"EML-L29"}],"dpi":428,"bw":3.45,"ac":500},{"type":"android","rules":[{"mdmh":"Nokia/*/Nokia 7.1/*"},{"ua":"Nokia 7.1"}],"dpi":[432,431.9],"bw":3,"ac":500},{"type":"ios","rules":[{"res":[1242,2688]}],"dpi":458,"bw":4,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G570M/*"},{"ua":"SM-G570M"}],"dpi":320,"bw":3.684,"ac":1000},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G970F/*"},{"ua":"SM-G970F"}],"dpi":438,"bw":2.281,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G973F/*"},{"ua":"SM-G973F"}],"dpi":550,"bw":2.002,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G975F/*"},{"ua":"SM-G975F"}],"dpi":522,"bw":2.054,"ac":500},{"type":"android","rules":[{"mdmh":"samsung/*/SM-G977F/*"},{"ua":"SM-G977F"}],"dpi":505,"bw":2.334,"ac":500},{"type":"ios","rules":[{"res":[828,1792]}],"dpi":326,"bw":5,"ac":500}]; var DPDB_CACHE = { format: format, last_updated: last_updated, devices: devices }; function Dpdb(url, onDeviceParamsUpdated) { this.dpdb = DPDB_CACHE; this.recalculateDeviceParams_(); if (url) { this.onDeviceParamsUpdated = onDeviceParamsUpdated; var xhr = new XMLHttpRequest(); var obj = this; xhr.open('GET', url, true); xhr.addEventListener('load', function () { obj.loading = false; if (xhr.status >= 200 && xhr.status <= 299) { obj.dpdb = JSON.parse(xhr.response); obj.recalculateDeviceParams_(); } else { console.error('Error loading online DPDB!'); } }); xhr.send(); } } Dpdb.prototype.getDeviceParams = function () { return this.deviceParams; }; Dpdb.prototype.recalculateDeviceParams_ = function () { var newDeviceParams = this.calcDeviceParams_(); if (newDeviceParams) { this.deviceParams = newDeviceParams; if (this.onDeviceParamsUpdated) { this.onDeviceParamsUpdated(this.deviceParams); } } else { console.error('Failed to recalculate device parameters.'); } }; Dpdb.prototype.calcDeviceParams_ = function () { var db = this.dpdb; if (!db) { console.error('DPDB not available.'); return null; } if (db.format != 1) { console.error('DPDB has unexpected format version.'); return null; } if (!db.devices || !db.devices.length) { console.error('DPDB does not have a devices section.'); return null; } var userAgent = navigator.userAgent || navigator.vendor || window.opera; var width = getScreenWidth(); var height = getScreenHeight(); if (!db.devices) { console.error('DPDB has no devices section.'); return null; } for (var i = 0; i < db.devices.length; i++) { var device = db.devices[i]; if (!device.rules) { console.warn('Device[' + i + '] has no rules section.'); continue; } if (device.type != 'ios' && device.type != 'android') { console.warn('Device[' + i + '] has invalid type.'); continue; } if (isIOS() != (device.type == 'ios')) continue; var matched = false; for (var j = 0; j < device.rules.length; j++) { var rule = device.rules[j]; if (this.ruleMatches_(rule, userAgent, width, height)) { matched = true; break; } } if (!matched) continue; var xdpi = device.dpi[0] || device.dpi; var ydpi = device.dpi[1] || device.dpi; return new DeviceParams({ xdpi: xdpi, ydpi: ydpi, bevelMm: device.bw }); } console.warn('No DPDB device match.'); return null; }; Dpdb.prototype.ruleMatches_ = function (rule, ua, screenWidth, screenHeight) { if (!rule.ua && !rule.res) return false; if (rule.ua && rule.ua.substring(0, 2) === 'SM') rule.ua = rule.ua.substring(0, 7); if (rule.ua && ua.indexOf(rule.ua) < 0) return false; if (rule.res) { if (!rule.res[0] || !rule.res[1]) return false; var resX = rule.res[0]; var resY = rule.res[1]; if (Math.min(screenWidth, screenHeight) != Math.min(resX, resY) || Math.max(screenWidth, screenHeight) != Math.max(resX, resY)) { return false; } } return true; }; function DeviceParams(params) { this.xdpi = params.xdpi; this.ydpi = params.ydpi; this.bevelMm = params.bevelMm; } function SensorSample(sample, timestampS) { this.set(sample, timestampS); } SensorSample.prototype.set = function (sample, timestampS) { this.sample = sample; this.timestampS = timestampS; }; SensorSample.prototype.copy = function (sensorSample) { this.set(sensorSample.sample, sensorSample.timestampS); }; function ComplementaryFilter(kFilter, isDebug) { this.kFilter = kFilter; this.isDebug = isDebug; this.currentAccelMeasurement = new SensorSample(); this.currentGyroMeasurement = new SensorSample(); this.previousGyroMeasurement = new SensorSample(); if (isIOS()) { this.filterQ = new Quaternion(-1, 0, 0, 1); } else { this.filterQ = new Quaternion(1, 0, 0, 1); } this.previousFilterQ = new Quaternion(); this.previousFilterQ.copy(this.filterQ); this.accelQ = new Quaternion(); this.isOrientationInitialized = false; this.estimatedGravity = new Vector3(); this.measuredGravity = new Vector3(); this.gyroIntegralQ = new Quaternion(); } ComplementaryFilter.prototype.addAccelMeasurement = function (vector, timestampS) { this.currentAccelMeasurement.set(vector, timestampS); }; ComplementaryFilter.prototype.addGyroMeasurement = function (vector, timestampS) { this.currentGyroMeasurement.set(vector, timestampS); var deltaT = timestampS - this.previousGyroMeasurement.timestampS; if (isTimestampDeltaValid(deltaT)) { this.run_(); } this.previousGyroMeasurement.copy(this.currentGyroMeasurement); }; ComplementaryFilter.prototype.run_ = function () { if (!this.isOrientationInitialized) { this.accelQ = this.accelToQuaternion_(this.currentAccelMeasurement.sample); this.previousFilterQ.copy(this.accelQ); this.isOrientationInitialized = true; return; } var deltaT = this.currentGyroMeasurement.timestampS - this.previousGyroMeasurement.timestampS; var gyroDeltaQ = this.gyroToQuaternionDelta_(this.currentGyroMeasurement.sample, deltaT); this.gyroIntegralQ.multiply(gyroDeltaQ); this.filterQ.copy(this.previousFilterQ); this.filterQ.multiply(gyroDeltaQ); var invFilterQ = new Quaternion(); invFilterQ.copy(this.filterQ); invFilterQ.inverse(); this.estimatedGravity.set(0, 0, -1); this.estimatedGravity.applyQuaternion(invFilterQ); this.estimatedGravity.normalize(); this.measuredGravity.copy(this.currentAccelMeasurement.sample); this.measuredGravity.normalize(); var deltaQ = new Quaternion(); deltaQ.setFromUnitVectors(this.estimatedGravity, this.measuredGravity); deltaQ.inverse(); if (this.isDebug) { console.log('Delta: %d deg, G_est: (%s, %s, %s), G_meas: (%s, %s, %s)', radToDeg * getQuaternionAngle(deltaQ), this.estimatedGravity.x.toFixed(1), this.estimatedGravity.y.toFixed(1), this.estimatedGravity.z.toFixed(1), this.measuredGravity.x.toFixed(1), this.measuredGravity.y.toFixed(1), this.measuredGravity.z.toFixed(1)); } var targetQ = new Quaternion(); targetQ.copy(this.filterQ); targetQ.multiply(deltaQ); this.filterQ.slerp(targetQ, 1 - this.kFilter); this.previousFilterQ.copy(this.filterQ); }; ComplementaryFilter.prototype.getOrientation = function () { return this.filterQ; }; ComplementaryFilter.prototype.accelToQuaternion_ = function (accel) { var normAccel = new Vector3(); normAccel.copy(accel); normAccel.normalize(); var quat = new Quaternion(); quat.setFromUnitVectors(new Vector3(0, 0, -1), normAccel); quat.inverse(); return quat; }; ComplementaryFilter.prototype.gyroToQuaternionDelta_ = function (gyro, dt) { var quat = new Quaternion(); var axis = new Vector3(); axis.copy(gyro); axis.normalize(); quat.setFromAxisAngle(axis, gyro.length() * dt); return quat; }; function PosePredictor(predictionTimeS, isDebug) { this.predictionTimeS = predictionTimeS; this.isDebug = isDebug; this.previousQ = new Quaternion(); this.previousTimestampS = null; this.deltaQ = new Quaternion(); this.outQ = new Quaternion(); } PosePredictor.prototype.getPrediction = function (currentQ, gyro, timestampS) { if (!this.previousTimestampS) { this.previousQ.copy(currentQ); this.previousTimestampS = timestampS; return currentQ; } var axis = new Vector3(); axis.copy(gyro); axis.normalize(); var angularSpeed = gyro.length(); if (angularSpeed < degToRad * 20) { if (this.isDebug) { console.log('Moving slowly, at %s deg/s: no prediction', (radToDeg * angularSpeed).toFixed(1)); } this.outQ.copy(currentQ); this.previousQ.copy(currentQ); return this.outQ; } var predictAngle = angularSpeed * this.predictionTimeS; this.deltaQ.setFromAxisAngle(axis, predictAngle); this.outQ.copy(this.previousQ); this.outQ.multiply(this.deltaQ); this.previousQ.copy(currentQ); this.previousTimestampS = timestampS; return this.outQ; }; function FusionPoseSensor(kFilter, predictionTime, yawOnly, isDebug) { this.yawOnly = yawOnly; this.accelerometer = new Vector3(); this.gyroscope = new Vector3(); this.filter = new ComplementaryFilter(kFilter, isDebug); this.posePredictor = new PosePredictor(predictionTime, isDebug); this.isFirefoxAndroid = isFirefoxAndroid(); this.isIOS = isIOS(); var chromeVersion = getChromeVersion(); this.isDeviceMotionInRadians = !this.isIOS && chromeVersion && chromeVersion < 66; this.isWithoutDeviceMotion = isChromeWithoutDeviceMotion() || isSafariWithoutDeviceMotion(); this.filterToWorldQ = new Quaternion(); if (isIOS()) { this.filterToWorldQ.setFromAxisAngle(new Vector3(1, 0, 0), Math.PI / 2); } else { this.filterToWorldQ.setFromAxisAngle(new Vector3(1, 0, 0), -Math.PI / 2); } this.inverseWorldToScreenQ = new Quaternion(); this.worldToScreenQ = new Quaternion(); this.originalPoseAdjustQ = new Quaternion(); this.originalPoseAdjustQ.setFromAxisAngle(new Vector3(0, 0, 1), -window.orientation * Math.PI / 180); this.setScreenTransform_(); if (isLandscapeMode()) { this.filterToWorldQ.multiply(this.inverseWorldToScreenQ); } this.resetQ = new Quaternion(); this.orientationOut_ = new Float32Array(4); this.start(); } FusionPoseSensor.prototype.getPosition = function () { return null; }; FusionPoseSensor.prototype.getOrientation = function () { var orientation = void 0; if (this.isWithoutDeviceMotion && this._deviceOrientationQ) { this.deviceOrientationFixQ = this.deviceOrientationFixQ || function () { var z = new Quaternion().setFromAxisAngle(new Vector3(0, 0, -1), 0); var y = new Quaternion(); if (window.orientation === -90) { y.setFromAxisAngle(new Vector3(0, 1, 0), Math.PI / -2); } else { y.setFromAxisAngle(new Vector3(0, 1, 0), Math.PI / 2); } return z.multiply(y); }(); this.deviceOrientationFilterToWorldQ = this.deviceOrientationFilterToWorldQ || function () { var q = new Quaternion(); q.setFromAxisAngle(new Vector3(1, 0, 0), -Math.PI / 2); return q; }(); orientation = this._deviceOrientationQ; var out = new Quaternion(); out.copy(orientation); out.multiply(this.deviceOrientationFilterToWorldQ); out.multiply(this.resetQ); out.multiply(this.worldToScreenQ); out.multiplyQuaternions(this.deviceOrientationFixQ, out); if (this.yawOnly) { out.x = 0; out.z = 0; out.normalize(); } this.orientationOut_[0] = out.x; this.orientationOut_[1] = out.y; this.orientationOut_[2] = out.z; this.orientationOut_[3] = out.w; return this.orientationOut_; } else { var filterOrientation = this.filter.getOrientation(); orientation = this.posePredictor.getPrediction(filterOrientation, this.gyroscope, this.previousTimestampS); } var out = new Quaternion(); out.copy(this.filterToWorldQ); out.multiply(this.resetQ); out.multiply(orientation); out.multiply(this.worldToScreenQ); if (this.yawOnly) { out.x = 0; out.z = 0; out.normalize(); } this.orientationOut_[0] = out.x; this.orientationOut_[1] = out.y; this.orientationOut_[2] = out.z; this.orientationOut_[3] = out.w; return this.orientationOut_; }; FusionPoseSensor.prototype.resetPose = function () { this.resetQ.copy(this.filter.getOrientation()); this.resetQ.x = 0; this.resetQ.y = 0; this.resetQ.z *= -1; this.resetQ.normalize(); if (isLandscapeMode()) { this.resetQ.multiply(this.inverseWorldToScreenQ); } this.resetQ.multiply(this.originalPoseAdjustQ); }; FusionPoseSensor.prototype.onDeviceOrientation_ = function (e) { this._deviceOrientationQ = this._deviceOrientationQ || new Quaternion(); var alpha = e.alpha, beta = e.beta, gamma = e.gamma; alpha = (alpha || 0) * Math.PI / 180; beta = (beta || 0) * Math.PI / 180; gamma = (gamma || 0) * Math.PI / 180; this._deviceOrientationQ.setFromEulerYXZ(beta, alpha, -gamma); }; FusionPoseSensor.prototype.onDeviceMotion_ = function (deviceMotion) { this.updateDeviceMotion_(deviceMotion); }; FusionPoseSensor.prototype.updateDeviceMotion_ = function (deviceMotion) { var accGravity = deviceMotion.accelerationIncludingGravity; var rotRate = deviceMotion.rotationRate; var timestampS = deviceMotion.timeStamp / 1000; var deltaS = timestampS - this.previousTimestampS; if (deltaS < 0) { warnOnce('fusion-pose-sensor:invalid:non-monotonic', 'Invalid timestamps detected: non-monotonic timestamp from devicemotion'); this.previousTimestampS = timestampS; return; } else if (deltaS <= MIN_TIMESTEP || deltaS > MAX_TIMESTEP) { warnOnce('fusion-pose-sensor:invalid:outside-threshold', 'Invalid timestamps detected: Timestamp from devicemotion outside expected range.'); this.previousTimestampS = timestampS; return; } this.accelerometer.set(-accGravity.x, -accGravity.y, -accGravity.z); if (rotRate) { if (isR7()) { this.gyroscope.set(-rotRate.beta, rotRate.alpha, rotRate.gamma); } else { this.gyroscope.set(rotRate.alpha, rotRate.beta, rotRate.gamma); } if (!this.isDeviceMotionInRadians) { this.gyroscope.multiplyScalar(Math.PI / 180); } this.filter.addGyroMeasurement(this.gyroscope, timestampS); } this.filter.addAccelMeasurement(this.accelerometer, timestampS); this.previousTimestampS = timestampS; }; FusionPoseSensor.prototype.onOrientationChange_ = function (screenOrientation) { this.setScreenTransform_(); }; FusionPoseSensor.prototype.onMessage_ = function (event) { var message = event.data; if (!message || !message.type) { return; } var type = message.type.toLowerCase(); if (type !== 'devicemotion') { return; } this.updateDeviceMotion_(message.deviceMotionEvent); }; FusionPoseSensor.prototype.setScreenTransform_ = function () { this.worldToScreenQ.set(0, 0, 0, 1); switch (window.orientation) { case 0: break; case 90: this.worldToScreenQ.setFromAxisAngle(new Vector3(0, 0, 1), -Math.PI / 2); break; case -90: this.worldToScreenQ.setFromAxisAngle(new Vector3(0, 0, 1), Math.PI / 2); break; case 180: break; } this.inverseWorldToScreenQ.copy(this.worldToScreenQ); this.inverseWorldToScreenQ.inverse(); }; FusionPoseSensor.prototype.start = function () { this.onDeviceMotionCallback_ = this.onDeviceMotion_.bind(this); this.onOrientationChangeCallback_ = this.onOrientationChange_.bind(this); this.onMessageCallback_ = this.onMessage_.bind(this); this.onDeviceOrientationCallback_ = this.onDeviceOrientation_.bind(this); if (isIOS() && isInsideCrossOriginIFrame()) { window.addEventListener('message', this.onMessageCallback_); } window.addEventListener('orientationchange', this.onOrientationChangeCallback_); if (this.isWithoutDeviceMotion) { window.addEventListener('deviceorientation', this.onDeviceOrientationCallback_); } else { window.addEventListener('devicemotion', this.onDeviceMotionCallback_); } }; FusionPoseSensor.prototype.stop = function () { window.removeEventListener('devicemotion', this.onDeviceMotionCallback_); window.removeEventListener('deviceorientation', this.onDeviceOrientationCallback_); window.removeEventListener('orientationchange', this.onOrientationChangeCallback_); window.removeEventListener('message', this.onMessageCallback_); }; var SENSOR_FREQUENCY = 60; var X_AXIS = new Vector3(1, 0, 0); var Z_AXIS = new Vector3(0, 0, 1); var SENSOR_TO_VR = new Quaternion(); SENSOR_TO_VR.setFromAxisAngle(X_AXIS, -Math.PI / 2); SENSOR_TO_VR.multiply(new Quaternion().setFromAxisAngle(Z_AXIS, Math.PI / 2)); var PoseSensor = function () { function PoseSensor(config) { classCallCheck(this, PoseSensor); this.config = config; this.sensor = null; this.fusionSensor = null; this._out = new Float32Array(4); this.api = null; this.errors = []; this._sensorQ = new Quaternion(); this._outQ = new Quaternion(); this._onSensorRead = this._onSensorRead.bind(this); this._onSensorError = this._onSensorError.bind(this); this.init(); } createClass(PoseSensor, [{ key: 'init', value: function init() { var sensor = null; try { sensor = new RelativeOrientationSensor({ frequency: SENSOR_FREQUENCY, referenceFrame: 'screen' }); sensor.addEventListener('error', this._onSensorError); } catch (error) { this.errors.push(error); if (error.name === 'SecurityError') { console.error('Cannot construct sensors due to the Feature Policy'); console.warn('Attempting to fall back using "devicemotion"; however this will ' + 'fail in the future without correct permissions.'); this.useDeviceMotion(); } else if (error.name === 'ReferenceError') { this.useDeviceMotion(); } else { console.error(error); } } if (sensor) { this.api = 'sensor'; this.sensor = sensor; this.sensor.addEventListener('reading', this._onSensorRead); this.sensor.start(); } } }, { key: 'useDeviceMotion', value: function useDeviceMotion() { this.api = 'devicemotion'; this.fusionSensor = new FusionPoseSensor(this.config.K_FILTER, this.config.PREDICTION_TIME_S, this.config.YAW_ONLY, this.config.DEBUG); if (this.sensor) { this.sensor.removeEventListener('reading', this._onSensorRead); this.sensor.removeEventListener('error', this._onSensorError); this.sensor = null; } } }, { key: 'getOrientation', value: function getOrientation() { if (this.fusionSensor) { return this.fusionSensor.getOrientation(); } if (!this.sensor || !this.sensor.quaternion) { this._out[0] = this._out[1] = this._out[2] = 0; this._out[3] = 1; return this._out; } var q = this.sensor.quaternion; this._sensorQ.set(q[0], q[1], q[2], q[3]); var out = this._outQ; out.copy(SENSOR_TO_VR); out.multiply(this._sensorQ); if (this.config.YAW_ONLY) { out.x = out.z = 0; out.normalize(); } this._out[0] = out.x; this._out[1] = out.y; this._out[2] = out.z; this._out[3] = out.w; return this._out; } }, { key: '_onSensorError', value: function _onSensorError(event) { this.errors.push(event.error); if (event.error.name === 'NotAllowedError') { console.error('Permission to access sensor was denied'); } else if (event.error.name === 'NotReadableError') { console.error('Sensor could not be read'); } else { console.error(event.error); } this.useDeviceMotion(); } }, { key: '_onSensorRead', value: function _onSensorRead() {} }]); return PoseSensor; }(); var rotateInstructionsAsset = ""; function RotateInstructions() { this.loadIcon_(); var overlay = document.createElement('div'); var s = overlay.style; s.position = 'fixed'; s.top = 0; s.right = 0; s.bottom = 0; s.left = 0; s.backgroundColor = 'gray'; s.fontFamily = 'sans-serif'; s.zIndex = 1000000; var img = document.createElement('img'); img.src = this.icon; var s = img.style; s.marginLeft = '25%'; s.marginTop = '25%'; s.width = '50%'; overlay.appendChild(img); var text = document.createElement('div'); var s = text.style; s.textAlign = 'center'; s.fontSize = '16px'; s.lineHeight = '24px'; s.margin = '24px 25%'; s.width = '50%'; text.innerHTML = 'Place your phone into your Cardboard viewer.'; overlay.appendChild(text); var snackbar = document.createElement('div'); var s = snackbar.style; s.backgroundColor = '#CFD8DC'; s.position = 'fixed'; s.bottom = 0; s.width = '100%'; s.height = '48px'; s.padding = '14px 24px'; s.boxSizing = 'border-box'; s.color = '#656A6B'; overlay.appendChild(snackbar); var snackbarText = document.createElement('div'); snackbarText.style.float = 'left'; snackbarText.innerHTML = 'No Cardboard viewer?'; var snackbarButton = document.createElement('a'); snackbarButton.href = 'https://www.google.com/get/cardboard/get-cardboard/'; snackbarButton.innerHTML = 'get one'; snackbarButton.target = '_blank'; var s = snackbarButton.style; s.float = 'right'; s.fontWeight = 600; s.textTransform = 'uppercase'; s.borderLeft = '1px solid gray'; s.paddingLeft = '24px'; s.textDecoration = 'none'; s.color = '#656A6B'; snackbar.appendChild(snackbarText); snackbar.appendChild(snackbarButton); this.overlay = overlay; this.text = text; this.hide(); } RotateInstructions.prototype.show = function (parent) { if (!parent && !this.overlay.parentElement) { document.body.appendChild(this.overlay); } else if (parent) { if (this.overlay.parentElement && this.overlay.parentElement != parent) this.overlay.parentElement.removeChild(this.overlay); parent.appendChild(this.overlay); } this.overlay.style.display = 'block'; var img = this.overlay.querySelector('img'); var s = img.style; if (isLandscapeMode()) { s.width = '20%'; s.marginLeft = '40%'; s.marginTop = '3%'; } else { s.width = '50%'; s.marginLeft = '25%'; s.marginTop = '25%'; } }; RotateInstructions.prototype.hide = function () { this.overlay.style.display = 'none'; }; RotateInstructions.prototype.showTemporarily = function (ms, parent) { this.show(parent); this.timer = setTimeout(this.hide.bind(this), ms); }; RotateInstructions.prototype.disableShowTemporarily = function () { clearTimeout(this.timer); }; RotateInstructions.prototype.update = function () { this.disableShowTemporarily(); if (!isLandscapeMode() && isMobile()) { this.show(); } else { this.hide(); } }; RotateInstructions.prototype.loadIcon_ = function () { this.icon = dataUri('image/svg+xml', rotateInstructionsAsset); }; var DEFAULT_VIEWER = 'CardboardV1'; var VIEWER_KEY = 'WEBVR_CARDBOARD_VIEWER'; var CLASS_NAME = 'webvr-polyfill-viewer-selector'; function ViewerSelector(defaultViewer) { try { this.selectedKey = localStorage.getItem(VIEWER_KEY); } catch (error) { console.error('Failed to load viewer profile: %s', error); } if (!this.selectedKey) { this.selectedKey = defaultViewer || DEFAULT_VIEWER; } this.dialog = this.createDialog_(DeviceInfo.Viewers); this.root = null; this.onChangeCallbacks_ = []; } ViewerSelector.prototype.show = function (root) { this.root = root; root.appendChild(this.dialog); var selected = this.dialog.querySelector('#' + this.selectedKey); selected.checked = true; this.dialog.style.display = 'block'; }; ViewerSelector.prototype.hide = function () { if (this.root && this.root.contains(this.dialog)) { this.root.removeChild(this.dialog); } this.dialog.style.display = 'none'; }; ViewerSelector.prototype.getCurrentViewer = function () { return DeviceInfo.Viewers[this.selectedKey]; }; ViewerSelector.prototype.getSelectedKey_ = function () { var input = this.dialog.querySelector('input[name=field]:checked'); if (input) { return input.id; } return null; }; ViewerSelector.prototype.onChange = function (cb) { this.onChangeCallbacks_.push(cb); }; ViewerSelector.prototype.fireOnChange_ = function (viewer) { for (var i = 0; i < this.onChangeCallbacks_.length; i++) { this.onChangeCallbacks_[i](viewer); } }; ViewerSelector.prototype.onSave_ = function () { this.selectedKey = this.getSelectedKey_(); if (!this.selectedKey || !DeviceInfo.Viewers[this.selectedKey]) { console.error('ViewerSelector.onSave_: this should never happen!'); return; } this.fireOnChange_(DeviceInfo.Viewers[this.selectedKey]); try { localStorage.setItem(VIEWER_KEY, this.selectedKey); } catch (error) { console.error('Failed to save viewer profile: %s', error); } this.hide(); }; ViewerSelector.prototype.createDialog_ = function (options) { var container = document.createElement('div'); container.classList.add(CLASS_NAME); container.style.display = 'none'; var overlay = document.createElement('div'); var s = overlay.style; s.position = 'fixed'; s.left = 0; s.top = 0; s.width = '100%'; s.height = '100%'; s.background = 'rgba(0, 0, 0, 0.3)'; overlay.addEventListener('click', this.hide.bind(this)); var width = 280; var dialog = document.createElement('div'); var s = dialog.style; s.boxSizing = 'border-box'; s.position = 'fixed'; s.top = '24px'; s.left = '50%'; s.marginLeft = -width / 2 + 'px'; s.width = width + 'px'; s.padding = '24px'; s.overflow = 'hidden'; s.background = '#fafafa'; s.fontFamily = "'Roboto', sans-serif"; s.boxShadow = '0px 5px 20px #666'; dialog.appendChild(this.createH1_('Select your viewer')); for (var id in options) { dialog.appendChild(this.createChoice_(id, options[id].label)); } dialog.appendChild(this.createButton_('Save', this.onSave_.bind(this))); container.appendChild(overlay); container.appendChild(dialog); return container; }; ViewerSelector.prototype.createH1_ = function (name) { var h1 = document.createElement('h1'); var s = h1.style; s.color = 'black'; s.fontSize = '20px'; s.fontWeight = 'bold'; s.marginTop = 0; s.marginBottom = '24px'; h1.innerHTML = name; return h1; }; ViewerSelector.prototype.createChoice_ = function (id, name) { var div = document.createElement('div'); div.style.marginTop = '8px'; div.style.color = 'black'; var input = document.createElement('input'); input.style.fontSize = '30px'; input.setAttribute('id', id); input.setAttribute('type', 'radio'); input.setAttribute('value', id); input.setAttribute('name', 'field'); var label = document.createElement('label'); label.style.marginLeft = '4px'; label.setAttribute('for', id); label.innerHTML = name; div.appendChild(input); div.appendChild(label); return div; }; ViewerSelector.prototype.createButton_ = function (label, onclick) { var button = document.createElement('button'); button.innerHTML = label; var s = button.style; s.float = 'right'; s.textTransform = 'uppercase'; s.color = '#1094f7'; s.fontSize = '14px'; s.letterSpacing = 0; s.border = 0; s.background = 'none'; s.marginTop = '16px'; button.addEventListener('click', onclick); return button; }; var commonjsGlobal$$1 = typeof window !== 'undefined' ? window : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof self !== 'undefined' ? self : {}; function unwrapExports$$1 (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function createCommonjsModule$$1(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var NoSleep = createCommonjsModule$$1(function (module, exports) { (function webpackUniversalModuleDefinition(root, factory) { module.exports = factory(); })(commonjsGlobal$$1, function() { return (function(modules) { var installedModules = {}; function __webpack_require__(moduleId) { if(installedModules[moduleId]) { return installedModules[moduleId].exports; } var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); module.l = true; return module.exports; } __webpack_require__.m = modules; __webpack_require__.c = installedModules; __webpack_require__.d = function(exports, name, getter) { if(!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { configurable: false, enumerable: true, get: getter }); } }; __webpack_require__.n = function(module) { var getter = module && module.__esModule ? function getDefault() { return module['default']; } : function getModuleExports() { return module; }; __webpack_require__.d(getter, 'a', getter); return getter; }; __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; __webpack_require__.p = ""; return __webpack_require__(__webpack_require__.s = 0); }) ([ (function(module, exports, __webpack_require__) { var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var mediaFile = __webpack_require__(1); var oldIOS = typeof navigator !== 'undefined' && parseFloat(('' + (/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0, ''])[1]).replace('undefined', '3_2').replace('_', '.').replace('_', '')) < 10 && !window.MSStream; var NoSleep = function () { function NoSleep() { _classCallCheck(this, NoSleep); if (oldIOS) { this.noSleepTimer = null; } else { this.noSleepVideo = document.createElement('video'); this.noSleepVideo.setAttribute('playsinline', ''); this.noSleepVideo.setAttribute('src', mediaFile); this.noSleepVideo.addEventListener('timeupdate', function (e) { if (this.noSleepVideo.currentTime > 0.5) { this.noSleepVideo.currentTime = Math.random(); } }.bind(this)); } } _createClass(NoSleep, [{ key: 'enable', value: function enable() { if (oldIOS) { this.disable(); this.noSleepTimer = window.setInterval(function () { window.location.href = '/'; window.setTimeout(window.stop, 0); }, 15000); } else { this.noSleepVideo.play(); } } }, { key: 'disable', value: function disable() { if (oldIOS) { if (this.noSleepTimer) { window.clearInterval(this.noSleepTimer); this.noSleepTimer = null; } } else { this.noSleepVideo.pause(); } } }]); return NoSleep; }(); module.exports = NoSleep; }), (function(module, exports, __webpack_require__) { module.exports = 'data:video/mp4;base64,AAAAIGZ0eXBtcDQyAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACKBtZGF0AAAC8wYF///v3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0MiByMjQ3OSBkZDc5YTYxIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTEgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9MiBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0wIHRocmVhZHM9NiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MSBrZXlpbnQ9MzAwIGtleWludF9taW49MzAgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD0xMCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIwLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IHZidl9tYXhyYXRlPTIwMDAwIHZidl9idWZzaXplPTI1MDAwIGNyZl9tYXg9MC4wIG5hbF9ocmQ9bm9uZSBmaWxsZXI9MCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAA3//p+C7v8tDDSTjf97w55i3SbRPO4ZY+hkjD5hbkAkL3zpJ6h/LR1CAABzgB1kqqzUorlhQAAAAxBmiQYhn/+qZYADLgAAAAJQZ5CQhX/AAj5IQADQGgcIQADQGgcAAAACQGeYUQn/wALKCEAA0BoHAAAAAkBnmNEJ/8ACykhAANAaBwhAANAaBwAAAANQZpoNExDP/6plgAMuSEAA0BoHAAAAAtBnoZFESwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBnqVEJ/8ACykhAANAaBwAAAAJAZ6nRCf/AAsoIQADQGgcIQADQGgcAAAADUGarDRMQz/+qZYADLghAANAaBwAAAALQZ7KRRUsK/8ACPkhAANAaBwAAAAJAZ7pRCf/AAsoIQADQGgcIQADQGgcAAAACQGe60Qn/wALKCEAA0BoHAAAAA1BmvA0TEM//qmWAAy5IQADQGgcIQADQGgcAAAAC0GfDkUVLCv/AAj5IQADQGgcAAAACQGfLUQn/wALKSEAA0BoHCEAA0BoHAAAAAkBny9EJ/8ACyghAANAaBwAAAANQZs0NExDP/6plgAMuCEAA0BoHAAAAAtBn1JFFSwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBn3FEJ/8ACyghAANAaBwAAAAJAZ9zRCf/AAsoIQADQGgcIQADQGgcAAAADUGbeDRMQz/+qZYADLkhAANAaBwAAAALQZ+WRRUsK/8ACPghAANAaBwhAANAaBwAAAAJAZ+1RCf/AAspIQADQGgcAAAACQGft0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bm7w0TEM//qmWAAy4IQADQGgcAAAAC0Gf2kUVLCv/AAj5IQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHAAAAAkBn/tEJ/8ACykhAANAaBwAAAANQZvgNExDP/6plgAMuSEAA0BoHCEAA0BoHAAAAAtBnh5FFSwr/wAI+CEAA0BoHAAAAAkBnj1EJ/8ACyghAANAaBwhAANAaBwAAAAJAZ4/RCf/AAspIQADQGgcAAAADUGaJDRMQz/+qZYADLghAANAaBwAAAALQZ5CRRUsK/8ACPkhAANAaBwhAANAaBwAAAAJAZ5hRCf/AAsoIQADQGgcAAAACQGeY0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bmmg0TEM//qmWAAy5IQADQGgcAAAAC0GehkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGepUQn/wALKSEAA0BoHAAAAAkBnqdEJ/8ACyghAANAaBwAAAANQZqsNExDP/6plgAMuCEAA0BoHCEAA0BoHAAAAAtBnspFFSwr/wAI+SEAA0BoHAAAAAkBnulEJ/8ACyghAANAaBwhAANAaBwAAAAJAZ7rRCf/AAsoIQADQGgcAAAADUGa8DRMQz/+qZYADLkhAANAaBwhAANAaBwAAAALQZ8ORRUsK/8ACPkhAANAaBwAAAAJAZ8tRCf/AAspIQADQGgcIQADQGgcAAAACQGfL0Qn/wALKCEAA0BoHAAAAA1BmzQ0TEM//qmWAAy4IQADQGgcAAAAC0GfUkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGfcUQn/wALKCEAA0BoHAAAAAkBn3NEJ/8ACyghAANAaBwhAANAaBwAAAANQZt4NExC//6plgAMuSEAA0BoHAAAAAtBn5ZFFSwr/wAI+CEAA0BoHCEAA0BoHAAAAAkBn7VEJ/8ACykhAANAaBwAAAAJAZ+3RCf/AAspIQADQGgcAAAADUGbuzRMQn/+nhAAYsAhAANAaBwhAANAaBwAAAAJQZ/aQhP/AAspIQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHAAACiFtb292AAAAbG12aGQAAAAA1YCCX9WAgl8AAAPoAAAH/AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAGGlvZHMAAAAAEICAgAcAT////v7/AAAF+XRyYWsAAABcdGtoZAAAAAPVgIJf1YCCXwAAAAEAAAAAAAAH0AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAygAAAMoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAB9AAABdwAAEAAAAABXFtZGlhAAAAIG1kaGQAAAAA1YCCX9WAgl8AAV+QAAK/IFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAUcbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAAE3HN0YmwAAACYc3RzZAAAAAAAAAABAAAAiGF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAygDKAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAyYXZjQwFNQCj/4QAbZ01AKOyho3ySTUBAQFAAAAMAEAAr8gDxgxlgAQAEaO+G8gAAABhzdHRzAAAAAAAAAAEAAAA8AAALuAAAABRzdHNzAAAAAAAAAAEAAAABAAAB8GN0dHMAAAAAAAAAPAAAAAEAABdwAAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAAC7gAAAAAQAAF3AAAAABAAAAAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAEEc3RzegAAAAAAAAAAAAAAPAAAAzQAAAAQAAAADQAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAANAAAADQAAAQBzdGNvAAAAAAAAADwAAAAwAAADZAAAA3QAAAONAAADoAAAA7kAAAPQAAAD6wAAA/4AAAQXAAAELgAABEMAAARcAAAEbwAABIwAAAShAAAEugAABM0AAATkAAAE/wAABRIAAAUrAAAFQgAABV0AAAVwAAAFiQAABaAAAAW1AAAFzgAABeEAAAX+AAAGEwAABiwAAAY/AAAGVgAABnEAAAaEAAAGnQAABrQAAAbPAAAG4gAABvUAAAcSAAAHJwAAB0AAAAdTAAAHcAAAB4UAAAeeAAAHsQAAB8gAAAfjAAAH9gAACA8AAAgmAAAIQQAACFQAAAhnAAAIhAAACJcAAAMsdHJhawAAAFx0a2hkAAAAA9WAgl/VgIJfAAAAAgAAAAAAAAf8AAAAAAAAAAAAAAABAQAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAACsm1kaWEAAAAgbWRoZAAAAADVgIJf1YCCXwAArEQAAWAAVcQAAAAAACdoZGxyAAAAAAAAAABzb3VuAAAAAAAAAAAAAAAAU3RlcmVvAAAAAmNtaW5mAAAAEHNtaGQAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAidzdGJsAAAAZ3N0c2QAAAAAAAAAAQAAAFdtcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAArEQAAAAAADNlc2RzAAAAAAOAgIAiAAIABICAgBRAFQAAAAADDUAAAAAABYCAgAISEAaAgIABAgAAABhzdHRzAAAAAAAAAAEAAABYAAAEAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAAGAAAAWAAAAXBzdGNvAAAAAAAAAFgAAAOBAAADhwAAA5oAAAOtAAADswAAA8oAAAPfAAAD5QAAA/gAAAQLAAAEEQAABCgAAAQ9AAAEUAAABFYAAARpAAAEgAAABIYAAASbAAAErgAABLQAAATHAAAE3gAABPMAAAT5AAAFDAAABR8AAAUlAAAFPAAABVEAAAVXAAAFagAABX0AAAWDAAAFmgAABa8AAAXCAAAFyAAABdsAAAXyAAAF+AAABg0AAAYgAAAGJgAABjkAAAZQAAAGZQAABmsAAAZ+AAAGkQAABpcAAAauAAAGwwAABskAAAbcAAAG7wAABwYAAAcMAAAHIQAABzQAAAc6AAAHTQAAB2QAAAdqAAAHfwAAB5IAAAeYAAAHqwAAB8IAAAfXAAAH3QAAB/AAAAgDAAAICQAACCAAAAg1AAAIOwAACE4AAAhhAAAIeAAACH4AAAiRAAAIpAAACKoAAAiwAAAItgAACLwAAAjCAAAAFnVkdGEAAAAObmFtZVN0ZXJlbwAAAHB1ZHRhAAAAaG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAO2lsc3QAAAAzqXRvbwAAACtkYXRhAAAAAQAAAABIYW5kQnJha2UgMC4xMC4yIDIwMTUwNjExMDA='; }) ]); }); }); var NoSleep$1 = unwrapExports$$1(NoSleep); var nextDisplayId = 1000; var defaultLeftBounds = [0, 0, 0.5, 1]; var defaultRightBounds = [0.5, 0, 0.5, 1]; var raf = window.requestAnimationFrame; var caf = window.cancelAnimationFrame; function VRFrameData() { this.leftProjectionMatrix = new Float32Array(16); this.leftViewMatrix = new Float32Array(16); this.rightProjectionMatrix = new Float32Array(16); this.rightViewMatrix = new Float32Array(16); this.pose = null; } function VRDisplayCapabilities(config) { Object.defineProperties(this, { hasPosition: { writable: false, enumerable: true, value: config.hasPosition }, hasExternalDisplay: { writable: false, enumerable: true, value: config.hasExternalDisplay }, canPresent: { writable: false, enumerable: true, value: config.canPresent }, maxLayers: { writable: false, enumerable: true, value: config.maxLayers }, hasOrientation: { enumerable: true, get: function get() { deprecateWarning('VRDisplayCapabilities.prototype.hasOrientation', 'VRDisplay.prototype.getFrameData'); return config.hasOrientation; } } }); } function VRDisplay(config) { config = config || {}; var USE_WAKELOCK = 'wakelock' in config ? config.wakelock : true; this.isPolyfilled = true; this.displayId = nextDisplayId++; this.displayName = ''; this.depthNear = 0.01; this.depthFar = 10000.0; this.isPresenting = false; Object.defineProperty(this, 'isConnected', { get: function get() { deprecateWarning('VRDisplay.prototype.isConnected', 'VRDisplayCapabilities.prototype.hasExternalDisplay'); return false; } }); this.capabilities = new VRDisplayCapabilities({ hasPosition: false, hasOrientation: false, hasExternalDisplay: false, canPresent: false, maxLayers: 1 }); this.stageParameters = null; this.waitingForPresent_ = false; this.layer_ = null; this.originalParent_ = null; this.fullscreenElement_ = null; this.fullscreenWrapper_ = null; this.fullscreenElementCachedStyle_ = null; this.fullscreenEventTarget_ = null; this.fullscreenChangeHandler_ = null; this.fullscreenErrorHandler_ = null; if (USE_WAKELOCK && isMobile()) { this.wakelock_ = new NoSleep$1(); } } VRDisplay.prototype.getFrameData = function (frameData) { return frameDataFromPose(frameData, this._getPose(), this); }; VRDisplay.prototype.getPose = function () { deprecateWarning('VRDisplay.prototype.getPose', 'VRDisplay.prototype.getFrameData'); return this._getPose(); }; VRDisplay.prototype.resetPose = function () { deprecateWarning('VRDisplay.prototype.resetPose'); return this._resetPose(); }; VRDisplay.prototype.getImmediatePose = function () { deprecateWarning('VRDisplay.prototype.getImmediatePose', 'VRDisplay.prototype.getFrameData'); return this._getPose(); }; VRDisplay.prototype.requestAnimationFrame = function (callback) { return raf(callback); }; VRDisplay.prototype.cancelAnimationFrame = function (id) { return caf(id); }; VRDisplay.prototype.wrapForFullscreen = function (element) { if (isIOS()) { return element; } if (!this.fullscreenWrapper_) { this.fullscreenWrapper_ = document.createElement('div'); var cssProperties = ['height: ' + Math.min(screen.height, screen.width) + 'px !important', 'top: 0 !important', 'left: 0 !important', 'right: 0 !important', 'border: 0', 'margin: 0', 'padding: 0', 'z-index: 999999 !important', 'position: fixed']; this.fullscreenWrapper_.setAttribute('style', cssProperties.join('; ') + ';'); this.fullscreenWrapper_.classList.add('webvr-polyfill-fullscreen-wrapper'); } if (this.fullscreenElement_ == element) { return this.fullscreenWrapper_; } if (this.fullscreenElement_) { if (this.originalParent_) { this.originalParent_.appendChild(this.fullscreenElement_); } else { this.fullscreenElement_.parentElement.removeChild(this.fullscreenElement_); } } this.fullscreenElement_ = element; this.originalParent_ = element.parentElement; if (!this.originalParent_) { document.body.appendChild(element); } if (!this.fullscreenWrapper_.parentElement) { var parent = this.fullscreenElement_.parentElement; parent.insertBefore(this.fullscreenWrapper_, this.fullscreenElement_); parent.removeChild(this.fullscreenElement_); } this.fullscreenWrapper_.insertBefore(this.fullscreenElement_, this.fullscreenWrapper_.firstChild); this.fullscreenElementCachedStyle_ = this.fullscreenElement_.getAttribute('style'); var self = this; function applyFullscreenElementStyle() { if (!self.fullscreenElement_) { return; } var cssProperties = ['position: absolute', 'top: 0', 'left: 0', 'width: ' + Math.max(screen.width, screen.height) + 'px', 'height: ' + Math.min(screen.height, screen.width) + 'px', 'border: 0', 'margin: 0', 'padding: 0']; self.fullscreenElement_.setAttribute('style', cssProperties.join('; ') + ';'); } applyFullscreenElementStyle(); return this.fullscreenWrapper_; }; VRDisplay.prototype.removeFullscreenWrapper = function () { if (!this.fullscreenElement_) { return; } var element = this.fullscreenElement_; if (this.fullscreenElementCachedStyle_) { element.setAttribute('style', this.fullscreenElementCachedStyle_); } else { element.removeAttribute('style'); } this.fullscreenElement_ = null; this.fullscreenElementCachedStyle_ = null; var parent = this.fullscreenWrapper_.parentElement; this.fullscreenWrapper_.removeChild(element); if (this.originalParent_ === parent) { parent.insertBefore(element, this.fullscreenWrapper_); } else if (this.originalParent_) { this.originalParent_.appendChild(element); } parent.removeChild(this.fullscreenWrapper_); return element; }; VRDisplay.prototype.requestPresent = function (layers) { var wasPresenting = this.isPresenting; var self = this; if (!(layers instanceof Array)) { deprecateWarning('VRDisplay.prototype.requestPresent with non-array argument', 'an array of VRLayers as the first argument'); layers = [layers]; } return new Promise(function (resolve, reject) { if (!self.capabilities.canPresent) { reject(new Error('VRDisplay is not capable of presenting.')); return; } if (layers.length == 0 || layers.length > self.capabilities.maxLayers) { reject(new Error('Invalid number of layers.')); return; } var incomingLayer = layers[0]; if (!incomingLayer.source) { resolve(); return; } var leftBounds = incomingLayer.leftBounds || defaultLeftBounds; var rightBounds = incomingLayer.rightBounds || defaultRightBounds; if (wasPresenting) { var layer = self.layer_; if (layer.source !== incomingLayer.source) { layer.source = incomingLayer.source; } for (var i = 0; i < 4; i++) { layer.leftBounds[i] = leftBounds[i]; layer.rightBounds[i] = rightBounds[i]; } self.wrapForFullscreen(self.layer_.source); self.updatePresent_(); resolve(); return; } self.layer_ = { predistorted: incomingLayer.predistorted, source: incomingLayer.source, leftBounds: leftBounds.slice(0), rightBounds: rightBounds.slice(0) }; self.waitingForPresent_ = false; if (self.layer_ && self.layer_.source) { var fullscreenElement = self.wrapForFullscreen(self.layer_.source); var onFullscreenChange = function onFullscreenChange() { var actualFullscreenElement = getFullscreenElement(); self.isPresenting = fullscreenElement === actualFullscreenElement; if (self.isPresenting) { if (screen.orientation && screen.orientation.lock) { screen.orientation.lock('landscape-primary').catch(function (error) { console.error('screen.orientation.lock() failed due to', error.message); }); } self.waitingForPresent_ = false; self.beginPresent_(); resolve(); } else { if (screen.orientation && screen.orientation.unlock) { screen.orientation.unlock(); } self.removeFullscreenWrapper(); self.disableWakeLock(); self.endPresent_(); self.removeFullscreenListeners_(); } self.fireVRDisplayPresentChange_(); }; var onFullscreenError = function onFullscreenError() { if (!self.waitingForPresent_) { return; } self.removeFullscreenWrapper(); self.removeFullscreenListeners_(); self.disableWakeLock(); self.waitingForPresent_ = false; self.isPresenting = false; reject(new Error('Unable to present.')); }; self.addFullscreenListeners_(fullscreenElement, onFullscreenChange, onFullscreenError); if (requestFullscreen(fullscreenElement)) { self.enableWakeLock(); self.waitingForPresent_ = true; } else if (isIOS() || isWebViewAndroid()) { self.enableWakeLock(); self.isPresenting = true; self.beginPresent_(); self.fireVRDisplayPresentChange_(); resolve(); } } if (!self.waitingForPresent_ && !isIOS()) { exitFullscreen(); reject(new Error('Unable to present.')); } }); }; VRDisplay.prototype.exitPresent = function () { var wasPresenting = this.isPresenting; var self = this; this.isPresenting = false; this.layer_ = null; this.disableWakeLock(); return new Promise(function (resolve, reject) { if (wasPresenting) { if (!exitFullscreen() && isIOS()) { self.endPresent_(); self.fireVRDisplayPresentChange_(); } if (isWebViewAndroid()) { self.removeFullscreenWrapper(); self.removeFullscreenListeners_(); self.endPresent_(); self.fireVRDisplayPresentChange_(); } resolve(); } else { reject(new Error('Was not presenting to VRDisplay.')); } }); }; VRDisplay.prototype.getLayers = function () { if (this.layer_) { return [this.layer_]; } return []; }; VRDisplay.prototype.fireVRDisplayPresentChange_ = function () { var event = new CustomEvent('vrdisplaypresentchange', { detail: { display: this } }); window.dispatchEvent(event); }; VRDisplay.prototype.fireVRDisplayConnect_ = function () { var event = new CustomEvent('vrdisplayconnect', { detail: { display: this } }); window.dispatchEvent(event); }; VRDisplay.prototype.addFullscreenListeners_ = function (element, changeHandler, errorHandler) { this.removeFullscreenListeners_(); this.fullscreenEventTarget_ = element; this.fullscreenChangeHandler_ = changeHandler; this.fullscreenErrorHandler_ = errorHandler; if (changeHandler) { if (document.fullscreenEnabled) { element.addEventListener('fullscreenchange', changeHandler, false); } else if (document.webkitFullscreenEnabled) { element.addEventListener('webkitfullscreenchange', changeHandler, false); } else if (document.mozFullScreenEnabled) { document.addEventListener('mozfullscreenchange', changeHandler, false); } else if (document.msFullscreenEnabled) { element.addEventListener('msfullscreenchange', changeHandler, false); } } if (errorHandler) { if (document.fullscreenEnabled) { element.addEventListener('fullscreenerror', errorHandler, false); } else if (document.webkitFullscreenEnabled) { element.addEventListener('webkitfullscreenerror', errorHandler, false); } else if (document.mozFullScreenEnabled) { document.addEventListener('mozfullscreenerror', errorHandler, false); } else if (document.msFullscreenEnabled) { element.addEventListener('msfullscreenerror', errorHandler, false); } } }; VRDisplay.prototype.removeFullscreenListeners_ = function () { if (!this.fullscreenEventTarget_) return; var element = this.fullscreenEventTarget_; if (this.fullscreenChangeHandler_) { var changeHandler = this.fullscreenChangeHandler_; element.removeEventListener('fullscreenchange', changeHandler, false); element.removeEventListener('webkitfullscreenchange', changeHandler, false); document.removeEventListener('mozfullscreenchange', changeHandler, false); element.removeEventListener('msfullscreenchange', changeHandler, false); } if (this.fullscreenErrorHandler_) { var errorHandler = this.fullscreenErrorHandler_; element.removeEventListener('fullscreenerror', errorHandler, false); element.removeEventListener('webkitfullscreenerror', errorHandler, false); document.removeEventListener('mozfullscreenerror', errorHandler, false); element.removeEventListener('msfullscreenerror', errorHandler, false); } this.fullscreenEventTarget_ = null; this.fullscreenChangeHandler_ = null; this.fullscreenErrorHandler_ = null; }; VRDisplay.prototype.enableWakeLock = function () { if (this.wakelock_) { this.wakelock_.enable(); } }; VRDisplay.prototype.disableWakeLock = function () { if (this.wakelock_) { this.wakelock_.disable(); } }; VRDisplay.prototype.beginPresent_ = function () { }; VRDisplay.prototype.endPresent_ = function () { }; VRDisplay.prototype.submitFrame = function (pose) { }; VRDisplay.prototype.getEyeParameters = function (whichEye) { return null; }; var config = { ADDITIONAL_VIEWERS: [], DEFAULT_VIEWER: '', MOBILE_WAKE_LOCK: true, DEBUG: false, DPDB_URL: 'https://dpdb.webvr.rocks/dpdb.json', K_FILTER: 0.98, PREDICTION_TIME_S: 0.040, CARDBOARD_UI_DISABLED: false, ROTATE_INSTRUCTIONS_DISABLED: false, YAW_ONLY: false, BUFFER_SCALE: 0.5, DIRTY_SUBMIT_FRAME_BINDINGS: false }; var Eye = { LEFT: 'left', RIGHT: 'right' }; function CardboardVRDisplay(config$$1) { var defaults = extend({}, config); config$$1 = extend(defaults, config$$1 || {}); VRDisplay.call(this, { wakelock: config$$1.MOBILE_WAKE_LOCK }); this.config = config$$1; this.displayName = 'Cardboard VRDisplay'; this.capabilities = new VRDisplayCapabilities({ hasPosition: false, hasOrientation: true, hasExternalDisplay: false, canPresent: true, maxLayers: 1 }); this.stageParameters = null; this.bufferScale_ = this.config.BUFFER_SCALE; this.poseSensor_ = new PoseSensor(this.config); this.distorter_ = null; this.cardboardUI_ = null; this.dpdb_ = new Dpdb(this.config.DPDB_URL, this.onDeviceParamsUpdated_.bind(this)); this.deviceInfo_ = new DeviceInfo(this.dpdb_.getDeviceParams(), config$$1.ADDITIONAL_VIEWERS); this.viewerSelector_ = new ViewerSelector(config$$1.DEFAULT_VIEWER); this.viewerSelector_.onChange(this.onViewerChanged_.bind(this)); this.deviceInfo_.setViewer(this.viewerSelector_.getCurrentViewer()); if (!this.config.ROTATE_INSTRUCTIONS_DISABLED) { this.rotateInstructions_ = new RotateInstructions(); } if (isIOS()) { window.addEventListener('resize', this.onResize_.bind(this)); } } CardboardVRDisplay.prototype = Object.create(VRDisplay.prototype); CardboardVRDisplay.prototype._getPose = function () { return { position: null, orientation: this.poseSensor_.getOrientation(), linearVelocity: null, linearAcceleration: null, angularVelocity: null, angularAcceleration: null }; }; CardboardVRDisplay.prototype._resetPose = function () { if (this.poseSensor_.resetPose) { this.poseSensor_.resetPose(); } }; CardboardVRDisplay.prototype._getFieldOfView = function (whichEye) { var fieldOfView; if (whichEye == Eye.LEFT) { fieldOfView = this.deviceInfo_.getFieldOfViewLeftEye(); } else if (whichEye == Eye.RIGHT) { fieldOfView = this.deviceInfo_.getFieldOfViewRightEye(); } else { console.error('Invalid eye provided: %s', whichEye); return null; } return fieldOfView; }; CardboardVRDisplay.prototype._getEyeOffset = function (whichEye) { var offset; if (whichEye == Eye.LEFT) { offset = [-this.deviceInfo_.viewer.interLensDistance * 0.5, 0.0, 0.0]; } else if (whichEye == Eye.RIGHT) { offset = [this.deviceInfo_.viewer.interLensDistance * 0.5, 0.0, 0.0]; } else { console.error('Invalid eye provided: %s', whichEye); return null; } return offset; }; CardboardVRDisplay.prototype.getEyeParameters = function (whichEye) { var offset = this._getEyeOffset(whichEye); var fieldOfView = this._getFieldOfView(whichEye); var eyeParams = { offset: offset, renderWidth: this.deviceInfo_.device.width * 0.5 * this.bufferScale_, renderHeight: this.deviceInfo_.device.height * this.bufferScale_ }; Object.defineProperty(eyeParams, 'fieldOfView', { enumerable: true, get: function get() { deprecateWarning('VRFieldOfView', 'VRFrameData\'s projection matrices'); return fieldOfView; } }); return eyeParams; }; CardboardVRDisplay.prototype.onDeviceParamsUpdated_ = function (newParams) { if (this.config.DEBUG) { console.log('DPDB reported that device params were updated.'); } this.deviceInfo_.updateDeviceParams(newParams); if (this.distorter_) { this.distorter_.updateDeviceInfo(this.deviceInfo_); } }; CardboardVRDisplay.prototype.updateBounds_ = function () { if (this.layer_ && this.distorter_ && (this.layer_.leftBounds || this.layer_.rightBounds)) { this.distorter_.setTextureBounds(this.layer_.leftBounds, this.layer_.rightBounds); } }; CardboardVRDisplay.prototype.beginPresent_ = function () { var gl = this.layer_.source.getContext('webgl'); if (!gl) gl = this.layer_.source.getContext('experimental-webgl'); if (!gl) gl = this.layer_.source.getContext('webgl2'); if (!gl) return; if (this.layer_.predistorted) { if (!this.config.CARDBOARD_UI_DISABLED) { gl.canvas.width = getScreenWidth() * this.bufferScale_; gl.canvas.height = getScreenHeight() * this.bufferScale_; this.cardboardUI_ = new CardboardUI(gl); } } else { if (!this.config.CARDBOARD_UI_DISABLED) { this.cardboardUI_ = new CardboardUI(gl); } this.distorter_ = new CardboardDistorter(gl, this.cardboardUI_, this.config.BUFFER_SCALE, this.config.DIRTY_SUBMIT_FRAME_BINDINGS); this.distorter_.updateDeviceInfo(this.deviceInfo_); } if (this.cardboardUI_) { this.cardboardUI_.listen(function (e) { this.viewerSelector_.show(this.layer_.source.parentElement); e.stopPropagation(); e.preventDefault(); }.bind(this), function (e) { this.exitPresent(); e.stopPropagation(); e.preventDefault(); }.bind(this)); } if (this.rotateInstructions_) { if (isLandscapeMode() && isMobile()) { this.rotateInstructions_.showTemporarily(3000, this.layer_.source.parentElement); } else { this.rotateInstructions_.update(); } } this.orientationHandler = this.onOrientationChange_.bind(this); window.addEventListener('orientationchange', this.orientationHandler); this.vrdisplaypresentchangeHandler = this.updateBounds_.bind(this); window.addEventListener('vrdisplaypresentchange', this.vrdisplaypresentchangeHandler); this.fireVRDisplayDeviceParamsChange_(); }; CardboardVRDisplay.prototype.endPresent_ = function () { if (this.distorter_) { this.distorter_.destroy(); this.distorter_ = null; } if (this.cardboardUI_) { this.cardboardUI_.destroy(); this.cardboardUI_ = null; } if (this.rotateInstructions_) { this.rotateInstructions_.hide(); } this.viewerSelector_.hide(); window.removeEventListener('orientationchange', this.orientationHandler); window.removeEventListener('vrdisplaypresentchange', this.vrdisplaypresentchangeHandler); }; CardboardVRDisplay.prototype.updatePresent_ = function () { this.endPresent_(); this.beginPresent_(); }; CardboardVRDisplay.prototype.submitFrame = function (pose) { if (this.distorter_) { this.updateBounds_(); this.distorter_.submitFrame(); } else if (this.cardboardUI_ && this.layer_) { var gl = this.layer_.source.getContext('webgl'); if (!gl) gl = this.layer_.source.getContext('experimental-webgl'); if (!gl) gl = this.layer_.source.getContext('webgl2'); var canvas = gl.canvas; if (canvas.width != this.lastWidth || canvas.height != this.lastHeight) { this.cardboardUI_.onResize(); } this.lastWidth = canvas.width; this.lastHeight = canvas.height; this.cardboardUI_.render(); } }; CardboardVRDisplay.prototype.onOrientationChange_ = function (e) { this.viewerSelector_.hide(); if (this.rotateInstructions_) { this.rotateInstructions_.update(); } this.onResize_(); }; CardboardVRDisplay.prototype.onResize_ = function (e) { if (this.layer_) { var gl = this.layer_.source.getContext('webgl'); if (!gl) gl = this.layer_.source.getContext('experimental-webgl'); if (!gl) gl = this.layer_.source.getContext('webgl2'); var cssProperties = ['position: absolute', 'top: 0', 'left: 0', 'width: 100vw', 'height: 100vh', 'border: 0', 'margin: 0', 'padding: 0px', 'box-sizing: content-box']; gl.canvas.setAttribute('style', cssProperties.join('; ') + ';'); safariCssSizeWorkaround(gl.canvas); } }; CardboardVRDisplay.prototype.onViewerChanged_ = function (viewer) { this.deviceInfo_.setViewer(viewer); if (this.distorter_) { this.distorter_.updateDeviceInfo(this.deviceInfo_); } this.fireVRDisplayDeviceParamsChange_(); }; CardboardVRDisplay.prototype.fireVRDisplayDeviceParamsChange_ = function () { var event = new CustomEvent('vrdisplaydeviceparamschange', { detail: { vrdisplay: this, deviceInfo: this.deviceInfo_ } }); window.dispatchEvent(event); }; CardboardVRDisplay.VRFrameData = VRFrameData; CardboardVRDisplay.VRDisplay = VRDisplay; return CardboardVRDisplay; }))); }); var CardboardVRDisplay = unwrapExports(cardboardVrDisplay); class XRDevice extends EventTarget { constructor(global) { super(); this.global = global; this.onWindowResize = this.onWindowResize.bind(this); this.global.window.addEventListener('resize', this.onWindowResize); this.environmentBlendMode = 'opaque'; } onBaseLayerSet(sessionId, layer) { throw new Error('Not implemented'); } isSessionSupported(mode) { throw new Error('Not implemented'); } isFeatureSupported(featureDescriptor) { throw new Error('Not implemented'); } async requestSession(mode, enabledFeatures) { throw new Error('Not implemented'); } requestAnimationFrame(callback) { throw new Error('Not implemented'); } onFrameStart(sessionId) { throw new Error('Not implemented'); } onFrameEnd(sessionId) { throw new Error('Not implemented'); } doesSessionSupportReferenceSpace(sessionId, type) { throw new Error('Not implemented'); } requestStageBounds() { throw new Error('Not implemented'); } async requestFrameOfReferenceTransform(type, options) { return undefined; } cancelAnimationFrame(handle) { throw new Error('Not implemented'); } endSession(sessionId) { throw new Error('Not implemented'); } getViewSpaces(mode) { return undefined; } getViewport(sessionId, eye, layer, target, viewIndex) { throw new Error('Not implemented'); } getProjectionMatrix(eye, viewIndex) { throw new Error('Not implemented'); } getBasePoseMatrix() { throw new Error('Not implemented'); } getBaseViewMatrix(eye) { throw new Error('Not implemented'); } getInputSources() { throw new Error('Not implemented'); } getInputPose(inputSource, coordinateSystem, poseType) { throw new Error('Not implemented'); } onWindowResize() { this.onWindowResize(); } } let daydream = { mapping: '', profiles: ['google-daydream', 'generic-trigger-touchpad'], buttons: { length: 3, 0: null, 1: null, 2: 0 }, }; let viveFocus = { mapping: 'xr-standard', profiles: ['htc-vive-focus', 'generic-trigger-touchpad'], buttons: { length: 3, 0: 1, 1: null, 2: 0 }, }; let oculusGo = { mapping: 'xr-standard', profiles: ['oculus-go', 'generic-trigger-touchpad'], buttons: { length: 3, 0: 1, 1: null, 2: 0 }, gripTransform: { orientation: [Math.PI * 0.11, 0, 0, 1] } }; let oculusTouch = { mapping: 'xr-standard', displayProfiles: { 'Oculus Quest': ['oculus-touch-v2', 'oculus-touch', 'generic-trigger-squeeze-thumbstick'] }, profiles: ['oculus-touch', 'generic-trigger-squeeze-thumbstick'], axes: { length: 4, 0: 2, 1: 3, 2: 0, 3: 1 }, buttons: { length: 7, 0: 1, 1: 2, 2: null, 3: 0, 4: 3, 5: 4, 6: null }, gripTransform: { position: [0, -0.02, 0.04, 1], orientation: [Math.PI * 0.11, 0, 0, 1] } }; let oculusTouchV2 = { mapping: 'xr-standard', displayProfiles: { 'Oculus Quest': ['oculus-touch-v2', 'oculus-touch', 'generic-trigger-squeeze-thumbstick'] }, profiles: ['oculus-touch-v2', 'oculus-touch', 'generic-trigger-squeeze-thumbstick'], axes: { length: 4, 0: 2, 1: 3, 2: 0, 3: 1 }, buttons: { length: 7, 0: 1, 1: 2, 2: null, 3: 0, 4: 3, 5: 4, 6: null }, gripTransform: { position: [0, -0.02, 0.04, 1], orientation: [Math.PI * 0.11, 0, 0, 1] } }; let oculusTouchV3 = { mapping: 'xr-standard', displayProfiles: { 'Oculus Quest': ['oculus-touch-v2', 'oculus-touch', 'generic-trigger-squeeze-thumbstick'] }, profiles: ['oculus-touch-v3', 'oculus-touch-v2', 'oculus-touch', 'generic-trigger-squeeze-thumbstick'], axes: { length: 4, 0: 2, 1: 3, 2: 0, 3: 1 }, buttons: { length: 7, 0: 1, 1: 2, 2: null, 3: 0, 4: 3, 5: 4, 6: null }, gripTransform: { position: [0, -0.02, 0.04, 1], orientation: [Math.PI * 0.11, 0, 0, 1] } }; let openVr = { mapping: 'xr-standard', profiles: ['htc-vive', 'generic-trigger-squeeze-touchpad'], displayProfiles: { 'HTC Vive': ['htc-vive', 'generic-trigger-squeeze-touchpad'], 'HTC Vive DVT': ['htc-vive', 'generic-trigger-squeeze-touchpad'], 'Valve Index': ['valve-index', 'generic-trigger-squeeze-touchpad-thumbstick'] }, buttons: { length: 3, 0: 1, 1: 2, 2: 0 }, gripTransform: { position: [0, 0, 0.05, 1], }, targetRayTransform: { orientation: [Math.PI * -0.08, 0, 0, 1] }, userAgentOverrides: { "Firefox": { axes: { invert: [1, 3] } } } }; let samsungGearVR = { mapping: 'xr-standard', profiles: ['samsung-gearvr', 'generic-trigger-touchpad'], buttons: { length: 3, 0: 1, 1: null, 2: 0 }, gripTransform: { orientation: [Math.PI * 0.11, 0, 0, 1] } }; let samsungOdyssey = { mapping: 'xr-standard', profiles: ['samsung-odyssey', 'microsoft-mixed-reality', 'generic-trigger-squeeze-touchpad-thumbstick'], buttons: { length: 4, 0: 1, 1: 0, 2: 2, 3: 4, }, gripTransform: { position: [0, -0.02, 0.04, 1], orientation: [Math.PI * 0.11, 0, 0, 1] } }; let windowsMixedReality = { mapping: 'xr-standard', profiles: ['microsoft-mixed-reality', 'generic-trigger-squeeze-touchpad-thumbstick'], buttons: { length: 4, 0: 1, 1: 0, 2: 2, 3: 4, }, gripTransform: { position: [0, -0.02, 0.04, 1], orientation: [Math.PI * 0.11, 0, 0, 1] } }; let GamepadMappings = { 'Daydream Controller': daydream, 'Gear VR Controller': samsungGearVR, 'HTC Vive Focus Controller': viveFocus, 'Oculus Go Controller': oculusGo, 'Oculus Touch (Right)': oculusTouch, 'Oculus Touch (Left)': oculusTouch, 'Oculus Touch V2 (Right)': oculusTouchV2, 'Oculus Touch V2 (Left)': oculusTouchV2, 'Oculus Touch V3 (Right)': oculusTouchV3, 'Oculus Touch V3 (Left)': oculusTouchV3, 'OpenVR Gamepad': openVr, 'Spatial Controller (Spatial Interaction Source) 045E-065A': windowsMixedReality, 'Spatial Controller (Spatial Interaction Source) 045E-065D': samsungOdyssey, 'Windows Mixed Reality (Right)': windowsMixedReality, 'Windows Mixed Reality (Left)': windowsMixedReality, }; const HEAD_ELBOW_OFFSET_RIGHTHANDED = fromValues$1(0.155, -0.465, -0.15); const HEAD_ELBOW_OFFSET_LEFTHANDED = fromValues$1(-0.155, -0.465, -0.15); const ELBOW_WRIST_OFFSET = fromValues$1(0, 0, -0.25); const WRIST_CONTROLLER_OFFSET = fromValues$1(0, 0, 0.05); const ARM_EXTENSION_OFFSET = fromValues$1(-0.08, 0.14, 0.08); const ELBOW_BEND_RATIO = 0.4; const EXTENSION_RATIO_WEIGHT = 0.4; const MIN_ANGULAR_SPEED = 0.61; const MIN_ANGLE_DELTA = 0.175; const MIN_EXTENSION_COS = 0.12; const MAX_EXTENSION_COS = 0.87; const RAD_TO_DEG = 180 / Math.PI; function eulerFromQuaternion(out, q, order) { function clamp(value, min$$1, max$$1) { return (value < min$$1 ? min$$1 : (value > max$$1 ? max$$1 : value)); } var sqx = q[0] * q[0]; var sqy = q[1] * q[1]; var sqz = q[2] * q[2]; var sqw = q[3] * q[3]; if ( order === 'XYZ' ) { out[0] = Math.atan2( 2 * ( q[0] * q[3] - q[1] * q[2] ), ( sqw - sqx - sqy + sqz ) ); out[1] = Math.asin( clamp( 2 * ( q[0] * q[2] + q[1] * q[3] ), -1, 1 ) ); out[2] = Math.atan2( 2 * ( q[2] * q[3] - q[0] * q[1] ), ( sqw + sqx - sqy - sqz ) ); } else if ( order === 'YXZ' ) { out[0] = Math.asin( clamp( 2 * ( q[0] * q[3] - q[1] * q[2] ), -1, 1 ) ); out[1] = Math.atan2( 2 * ( q[0] * q[2] + q[1] * q[3] ), ( sqw - sqx - sqy + sqz ) ); out[2] = Math.atan2( 2 * ( q[0] * q[1] + q[2] * q[3] ), ( sqw - sqx + sqy - sqz ) ); } else if ( order === 'ZXY' ) { out[0] = Math.asin( clamp( 2 * ( q[0] * q[3] + q[1] * q[2] ), -1, 1 ) ); out[1] = Math.atan2( 2 * ( q[1] * q[3] - q[2] * q[0] ), ( sqw - sqx - sqy + sqz ) ); out[2] = Math.atan2( 2 * ( q[2] * q[3] - q[0] * q[1] ), ( sqw - sqx + sqy - sqz ) ); } else if ( order === 'ZYX' ) { out[0] = Math.atan2( 2 * ( q[0] * q[3] + q[2] * q[1] ), ( sqw - sqx - sqy + sqz ) ); out[1] = Math.asin( clamp( 2 * ( q[1] * q[3] - q[0] * q[2] ), -1, 1 ) ); out[2] = Math.atan2( 2 * ( q[0] * q[1] + q[2] * q[3] ), ( sqw + sqx - sqy - sqz ) ); } else if ( order === 'YZX' ) { out[0] = Math.atan2( 2 * ( q[0] * q[3] - q[2] * q[1] ), ( sqw - sqx + sqy - sqz ) ); out[1] = Math.atan2( 2 * ( q[1] * q[3] - q[0] * q[2] ), ( sqw + sqx - sqy - sqz ) ); out[2] = Math.asin( clamp( 2 * ( q[0] * q[1] + q[2] * q[3] ), -1, 1 ) ); } else if ( order === 'XZY' ) { out[0] = Math.atan2( 2 * ( q[0] * q[3] + q[1] * q[2] ), ( sqw - sqx + sqy - sqz ) ); out[1] = Math.atan2( 2 * ( q[0] * q[2] + q[1] * q[3] ), ( sqw + sqx - sqy - sqz ) ); out[2] = Math.asin( clamp( 2 * ( q[2] * q[3] - q[0] * q[1] ), -1, 1 ) ); } else { console.log('No order given for quaternion to euler conversion.'); return; } } class OrientationArmModel { constructor() { this.hand = 'right'; this.headElbowOffset = HEAD_ELBOW_OFFSET_RIGHTHANDED; this.controllerQ = create$4(); this.lastControllerQ = create$4(); this.headQ = create$4(); this.headPos = create$1(); this.elbowPos = create$1(); this.wristPos = create$1(); this.time = null; this.lastTime = null; this.rootQ = create$4(); this.position = create$1(); } setHandedness(hand) { if (this.hand != hand) { this.hand = hand; if (this.hand == 'left') { this.headElbowOffset = HEAD_ELBOW_OFFSET_LEFTHANDED; } else { this.headElbowOffset = HEAD_ELBOW_OFFSET_RIGHTHANDED; } } } update(controllerOrientation, headPoseMatrix) { this.time = now$1(); if (controllerOrientation) { copy$4(this.lastControllerQ, this.controllerQ); copy$4(this.controllerQ, controllerOrientation); } if (headPoseMatrix) { getTranslation(this.headPos, headPoseMatrix); getRotation(this.headQ, headPoseMatrix); } let headYawQ = this.getHeadYawOrientation_(); let angleDelta = this.quatAngle_(this.lastControllerQ, this.controllerQ); let timeDelta = (this.time - this.lastTime) / 1000; let controllerAngularSpeed = angleDelta / timeDelta; if (controllerAngularSpeed > MIN_ANGULAR_SPEED) { slerp(this.rootQ, this.rootQ, headYawQ, Math.min(angleDelta / MIN_ANGLE_DELTA, 1.0)); } else { copy$4(this.rootQ, headYawQ); } let controllerForward = fromValues$1(0, 0, -1.0); transformQuat(controllerForward, controllerForward, this.controllerQ); let controllerDotY = dot(controllerForward, [0, 1, 0]); let extensionRatio = this.clamp_( (controllerDotY - MIN_EXTENSION_COS) / MAX_EXTENSION_COS, 0.0, 1.0); let controllerCameraQ = clone$4(this.rootQ); invert$2(controllerCameraQ, controllerCameraQ); multiply$4(controllerCameraQ, controllerCameraQ, this.controllerQ); let elbowPos = this.elbowPos; copy$1(elbowPos, this.headPos); add$1(elbowPos, elbowPos, this.headElbowOffset); let elbowOffset = clone$1(ARM_EXTENSION_OFFSET); scale$1(elbowOffset, elbowOffset, extensionRatio); add$1(elbowPos, elbowPos, elbowOffset); let totalAngle = this.quatAngle_(controllerCameraQ, create$4()); let totalAngleDeg = totalAngle * RAD_TO_DEG; let lerpSuppression = 1 - Math.pow(totalAngleDeg / 180, 4);let elbowRatio = ELBOW_BEND_RATIO; let wristRatio = 1 - ELBOW_BEND_RATIO; let lerpValue = lerpSuppression * (elbowRatio + wristRatio * extensionRatio * EXTENSION_RATIO_WEIGHT); let wristQ = create$4(); slerp(wristQ, wristQ, controllerCameraQ, lerpValue); let invWristQ = invert$2(create$4(), wristQ); let elbowQ = clone$4(controllerCameraQ); multiply$4(elbowQ, elbowQ, invWristQ); let wristPos = this.wristPos; copy$1(wristPos, WRIST_CONTROLLER_OFFSET); transformQuat(wristPos, wristPos, wristQ); add$1(wristPos, wristPos, ELBOW_WRIST_OFFSET); transformQuat(wristPos, wristPos, elbowQ); add$1(wristPos, wristPos, elbowPos); let offset = clone$1(ARM_EXTENSION_OFFSET); scale$1(offset, offset, extensionRatio); add$1(this.position, this.wristPos, offset); transformQuat(this.position, this.position, this.rootQ); this.lastTime = this.time; } getPosition() { return this.position; } getHeadYawOrientation_() { let headEuler = create$1(); eulerFromQuaternion(headEuler, this.headQ, 'YXZ'); let destinationQ = fromEuler(create$4(), 0, headEuler[1] * RAD_TO_DEG, 0); return destinationQ; } clamp_(value, min$$1, max$$1) { return Math.min(Math.max(value, min$$1), max$$1); } quatAngle_(q1, q2) { let vec1 = [0, 0, -1]; let vec2 = [0, 0, -1]; transformQuat(vec1, vec1, q1); transformQuat(vec2, vec2, q2); return angle(vec1, vec2); } } const PRIVATE$18 = Symbol('@@webxr-polyfill/XRRemappedGamepad'); const PLACEHOLDER_BUTTON = { pressed: false, touched: false, value: 0.0 }; Object.freeze(PLACEHOLDER_BUTTON); class XRRemappedGamepad { constructor(gamepad, display, map) { if (!map) { map = {}; } if (map.userAgentOverrides) { for (let agent in map.userAgentOverrides) { if (navigator.userAgent.includes(agent)) { let override = map.userAgentOverrides[agent]; for (let key in override) { if (key in map) { Object.assign(map[key], override[key]); } else { map[key] = override[key]; } } break; } } } let axes = new Array(map.axes && map.axes.length ? map.axes.length : gamepad.axes.length); let buttons = new Array(map.buttons && map.buttons.length ? map.buttons.length : gamepad.buttons.length); let gripTransform = null; if (map.gripTransform) { let orientation = map.gripTransform.orientation || [0, 0, 0, 1]; gripTransform = create(); fromRotationTranslation( gripTransform, normalize$2(orientation, orientation), map.gripTransform.position || [0, 0, 0] ); } let targetRayTransform = null; if (map.targetRayTransform) { let orientation = map.targetRayTransform.orientation || [0, 0, 0, 1]; targetRayTransform = create(); fromRotationTranslation( targetRayTransform, normalize$2(orientation, orientation), map.targetRayTransform.position || [0, 0, 0] ); } let profiles = map.profiles; if (map.displayProfiles) { if (display.displayName in map.displayProfiles) { profiles = map.displayProfiles[display.displayName]; } } this[PRIVATE$18] = { gamepad, map, profiles: profiles || [gamepad.id], mapping: map.mapping || gamepad.mapping, axes, buttons, gripTransform, targetRayTransform, }; this._update(); } _update() { let gamepad = this[PRIVATE$18].gamepad; let map = this[PRIVATE$18].map; let axes = this[PRIVATE$18].axes; for (let i = 0; i < axes.length; ++i) { if (map.axes && i in map.axes) { if (map.axes[i] === null) { axes[i] = 0; } else { axes[i] = gamepad.axes[map.axes[i]]; } } else { axes[i] = gamepad.axes[i]; } } if (map.axes && map.axes.invert) { for (let axis of map.axes.invert) { if (axis < axes.length) { axes[axis] *= -1; } } } let buttons = this[PRIVATE$18].buttons; for (let i = 0; i < buttons.length; ++i) { if (map.buttons && i in map.buttons) { if (map.buttons[i] === null) { buttons[i] = PLACEHOLDER_BUTTON; } else { buttons[i] = gamepad.buttons[map.buttons[i]]; } } else { buttons[i] = gamepad.buttons[i]; } } } get id() { return ''; } get _profiles() { return this[PRIVATE$18].profiles; } get index() { return -1; } get connected() { return this[PRIVATE$18].gamepad.connected; } get timestamp() { return this[PRIVATE$18].gamepad.timestamp; } get mapping() { return this[PRIVATE$18].mapping; } get axes() { return this[PRIVATE$18].axes; } get buttons() { return this[PRIVATE$18].buttons; } get hapticActuators() { return this[PRIVATE$18].gamepad.hapticActuators; } } class GamepadXRInputSource { constructor(polyfill, display, primaryButtonIndex = 0, primarySqueezeButtonIndex = -1) { this.polyfill = polyfill; this.display = display; this.nativeGamepad = null; this.gamepad = null; this.inputSource = new XRInputSource(this); this.lastPosition = create$1(); this.emulatedPosition = false; this.basePoseMatrix = create(); this.outputMatrix = create(); this.primaryButtonIndex = primaryButtonIndex; this.primaryActionPressed = false; this.primarySqueezeButtonIndex = primarySqueezeButtonIndex; this.primarySqueezeActionPressed = false; this.handedness = ''; this.targetRayMode = 'gaze'; this.armModel = null; } get profiles() { return this.gamepad ? this.gamepad._profiles : []; } updateFromGamepad(gamepad) { if (this.nativeGamepad !== gamepad) { this.nativeGamepad = gamepad; if (gamepad) { this.gamepad = new XRRemappedGamepad(gamepad, this.display, GamepadMappings[gamepad.id]); } else { this.gamepad = null; } } this.handedness = gamepad.hand === '' ? 'none' : gamepad.hand; if (this.gamepad) { this.gamepad._update(); } if (gamepad.pose) { this.targetRayMode = 'tracked-pointer'; this.emulatedPosition = !gamepad.pose.hasPosition; } else if (gamepad.hand === '') { this.targetRayMode = 'gaze'; this.emulatedPosition = false; } } updateBasePoseMatrix() { if (this.nativeGamepad && this.nativeGamepad.pose) { let pose = this.nativeGamepad.pose; let position = pose.position; let orientation = pose.orientation; if (!position && !orientation) { return; } if (!position) { if (!pose.hasPosition) { if (!this.armModel) { this.armModel = new OrientationArmModel(); } this.armModel.setHandedness(this.nativeGamepad.hand); this.armModel.update(orientation, this.polyfill.getBasePoseMatrix()); position = this.armModel.getPosition(); } else { position = this.lastPosition; } } else { this.lastPosition[0] = position[0]; this.lastPosition[1] = position[1]; this.lastPosition[2] = position[2]; } fromRotationTranslation(this.basePoseMatrix, orientation, position); } else { copy(this.basePoseMatrix, this.polyfill.getBasePoseMatrix()); } return this.basePoseMatrix; } getXRPose(coordinateSystem, poseType) { this.updateBasePoseMatrix(); switch(poseType) { case "target-ray": coordinateSystem._transformBasePoseMatrix(this.outputMatrix, this.basePoseMatrix); if (this.gamepad && this.gamepad[PRIVATE$18].targetRayTransform) { multiply(this.outputMatrix, this.outputMatrix, this.gamepad[PRIVATE$18].targetRayTransform); } break; case "grip": if (!this.nativeGamepad || !this.nativeGamepad.pose) { return null; } coordinateSystem._transformBasePoseMatrix(this.outputMatrix, this.basePoseMatrix); if (this.gamepad && this.gamepad[PRIVATE$18].gripTransform) { multiply(this.outputMatrix, this.outputMatrix, this.gamepad[PRIVATE$18].gripTransform); } break; default: return null; } coordinateSystem._adjustForOriginOffset(this.outputMatrix); return new XRPose(new XRRigidTransform(this.outputMatrix), this.emulatedPosition); } } const TEST_ENV = "production" === 'test'; const EXTRA_PRESENTATION_ATTRIBUTES = { highRefreshRate: true, }; const PRIMARY_BUTTON_MAP = { oculus: 1, openvr: 1, 'spatial controller (spatial interaction source)': 1 }; let SESSION_ID = 0; class Session { constructor(mode, enabledFeatures, polyfillOptions={}) { this.mode = mode; this.enabledFeatures = enabledFeatures; this.outputContext = null; this.immersive = mode == 'immersive-vr' || mode == 'immersive-ar'; this.ended = null; this.baseLayer = null; this.id = ++SESSION_ID; this.modifiedCanvasLayer = false; if (this.outputContext && !TEST_ENV) { const renderContextType = polyfillOptions.renderContextType || '2d'; this.renderContext = this.outputContext.canvas.getContext(renderContextType); } } } class WebVRDevice extends XRDevice { constructor(global, display) { const { canPresent } = display.capabilities; super(global); this.display = display; this.frame = new global.VRFrameData(); this.sessions = new Map(); this.immersiveSession = null; this.canPresent = canPresent; this.baseModelMatrix = create(); this.gamepadInputSources = {}; this.tempVec3 = new Float32Array(3); this.onVRDisplayPresentChange = this.onVRDisplayPresentChange.bind(this); global.window.addEventListener('vrdisplaypresentchange', this.onVRDisplayPresentChange); this.CAN_USE_GAMEPAD = global.navigator && ('getGamepads' in global.navigator); this.HAS_BITMAP_SUPPORT = isImageBitmapSupported(global); } get depthNear() { return this.display.depthNear; } set depthNear(val) { this.display.depthNear = val; } get depthFar() { return this.display.depthFar; } set depthFar(val) { this.display.depthFar = val; } onBaseLayerSet(sessionId, layer) { const session = this.sessions.get(sessionId); const canvas = layer.context.canvas; if (session.immersive) { const left = this.display.getEyeParameters('left'); const right = this.display.getEyeParameters('right'); canvas.width = Math.max(left.renderWidth, right.renderWidth) * 2; canvas.height = Math.max(left.renderHeight, right.renderHeight); this.display.requestPresent([{ source: canvas, attributes: EXTRA_PRESENTATION_ATTRIBUTES }]).then(() => { if (!TEST_ENV && !this.global.document.body.contains(canvas)) { session.modifiedCanvasLayer = true; this.global.document.body.appendChild(canvas); applyCanvasStylesForMinimalRendering(canvas); } session.baseLayer = layer; }); } else { session.baseLayer = layer; } } isSessionSupported(mode) { if (mode == 'immersive-ar') { return false; } if (mode == 'immersive-vr' && this.canPresent === false) { return false; } return true; } isFeatureSupported(featureDescriptor) { switch(featureDescriptor) { case 'viewer': return true; case 'local': return true; case 'local-floor': return true; case 'bounded': return false; case 'unbounded': return false; default: return false; } } async requestSession(mode, enabledFeatures) { if (!this.isSessionSupported(mode)) { return Promise.reject(); } let immersive = mode == 'immersive-vr'; if (immersive) { const canvas = this.global.document.createElement('canvas'); if (!TEST_ENV) { const ctx = canvas.getContext('webgl'); } await this.display.requestPresent([{ source: canvas, attributes: EXTRA_PRESENTATION_ATTRIBUTES }]); } const session = new Session(mode, enabledFeatures, { renderContextType: this.HAS_BITMAP_SUPPORT ? 'bitmaprenderer' : '2d' }); this.sessions.set(session.id, session); if (immersive) { this.immersiveSession = session; this.dispatchEvent('@@webxr-polyfill/vr-present-start', session.id); } return Promise.resolve(session.id); } requestAnimationFrame(callback) { return this.display.requestAnimationFrame(callback); } getPrimaryButtonIndex(gamepad) { let primaryButton = 0; let name = gamepad.id.toLowerCase(); for (let key in PRIMARY_BUTTON_MAP) { if (name.includes(key)) { primaryButton = PRIMARY_BUTTON_MAP[key]; break; } } return Math.min(primaryButton, gamepad.buttons.length - 1); } onFrameStart(sessionId, renderState) { this.display.depthNear = renderState.depthNear; this.display.depthFar = renderState.depthFar; this.display.getFrameData(this.frame); const session = this.sessions.get(sessionId); if (session.immersive && this.CAN_USE_GAMEPAD) { let prevInputSources = this.gamepadInputSources; this.gamepadInputSources = {}; let gamepads = this.global.navigator.getGamepads(); for (let i = 0; i < gamepads.length; ++i) { let gamepad = gamepads[i]; if (gamepad && gamepad.displayId > 0) { let inputSourceImpl = prevInputSources[i]; if (!inputSourceImpl) { inputSourceImpl = new GamepadXRInputSource(this, this.display, this.getPrimaryButtonIndex(gamepad)); } inputSourceImpl.updateFromGamepad(gamepad); this.gamepadInputSources[i] = inputSourceImpl; if (inputSourceImpl.primaryButtonIndex != -1) { let primaryActionPressed = gamepad.buttons[inputSourceImpl.primaryButtonIndex].pressed; if (primaryActionPressed && !inputSourceImpl.primaryActionPressed) { this.dispatchEvent('@@webxr-polyfill/input-select-start', { sessionId: session.id, inputSource: inputSourceImpl.inputSource }); } else if (!primaryActionPressed && inputSourceImpl.primaryActionPressed) { this.dispatchEvent('@@webxr-polyfill/input-select-end', { sessionId: session.id, inputSource: inputSourceImpl.inputSource }); } inputSourceImpl.primaryActionPressed = primaryActionPressed; } if (inputSourceImpl.primarySqueezeButtonIndex != -1) { let primarySqueezeActionPressed = gamepad.buttons[inputSourceImpl.primarySqueezeButtonIndex].pressed; if (primarySqueezeActionPressed && !inputSourceImpl.primarySqueezeActionPressed) { this.dispatchEvent('@@webxr-polyfill/input-squeeze-start', { sessionId: session.id, inputSource: inputSourceImpl.inputSource }); } else if (!primarySqueezeActionPressed && inputSourceImpl.primarySqueezeActionPressed) { this.dispatchEvent('@@webxr-polyfill/input-squeeze-end', { sessionId: session.id, inputSource: inputSourceImpl.inputSource }); } inputSourceImpl.primarySqueezeActionPressed = primarySqueezeActionPressed; } } } } if (TEST_ENV) { return; } if (!session.immersive && session.baseLayer) { const canvas = session.baseLayer.context.canvas; perspective(this.frame.leftProjectionMatrix, renderState.inlineVerticalFieldOfView, canvas.width/canvas.height, renderState.depthNear, renderState.depthFar); } } onFrameEnd(sessionId) { const session = this.sessions.get(sessionId); if (session.ended || !session.baseLayer) { return; } if (session.outputContext && !(session.immersive && !this.display.capabilities.hasExternalDisplay)) { const mirroring = session.immersive && this.display.capabilities.hasExternalDisplay; const iCanvas = session.baseLayer.context.canvas; const iWidth = mirroring ? iCanvas.width / 2 : iCanvas.width; const iHeight = iCanvas.height; if (!TEST_ENV) { const oCanvas = session.outputContext.canvas; const oWidth = oCanvas.width; const oHeight = oCanvas.height; const renderContext = session.renderContext; if (this.HAS_BITMAP_SUPPORT) { if (iCanvas.transferToImageBitmap) { renderContext.transferFromImageBitmap(iCanvas.transferToImageBitmap()); } else { this.global.createImageBitmap(iCanvas, 0, 0, iWidth, iHeight, { resizeWidth: oWidth, resizeHeight: oHeight, }).then(bitmap => renderContext.transferFromImageBitmap(bitmap)); } } else { renderContext.drawImage(iCanvas, 0, 0, iWidth, iHeight, 0, 0, oWidth, oHeight); } } } if (session.immersive && session.baseLayer) { this.display.submitFrame(); } } cancelAnimationFrame(handle) { this.display.cancelAnimationFrame(handle); } async endSession(sessionId) { const session = this.sessions.get(sessionId); if (session.ended) { return; } if (session.immersive) { return this.display.exitPresent(); } else { session.ended = true; } } doesSessionSupportReferenceSpace(sessionId, type) { const session = this.sessions.get(sessionId); if (session.ended) { return false; } return session.enabledFeatures.has(type); } requestStageBounds() { if (this.display.stageParameters) { const width = this.display.stageParameters.sizeX; const depth = this.display.stageParameters.sizeZ; const data = []; data.push(-width / 2); data.push(-depth / 2); data.push(width / 2); data.push(-depth / 2); data.push(width / 2); data.push(depth / 2); data.push(-width / 2); data.push(depth / 2); return data; } return null; } async requestFrameOfReferenceTransform(type, options) { if ((type === 'local-floor' || type === 'bounded-floor') && this.display.stageParameters && this.display.stageParameters.sittingToStandingTransform) { return this.display.stageParameters.sittingToStandingTransform; } return null; } getProjectionMatrix(eye) { if (eye === 'left') { return this.frame.leftProjectionMatrix; } else if (eye === 'right') { return this.frame.rightProjectionMatrix; } else if (eye === 'none') { return this.frame.leftProjectionMatrix; } else { throw new Error(`eye must be of type 'left' or 'right'`); } } getViewport(sessionId, eye, layer, target) { const session = this.sessions.get(sessionId); const { width, height } = layer.context.canvas; if (!session.immersive) { target.x = target.y = 0; target.width = width; target.height = height; return true; } if (eye === 'left' || eye === 'none') { target.x = 0; } else if (eye === 'right') { target.x = width / 2; } else { return false; } target.y = 0; target.width = width / 2; target.height = height; return true; } getBasePoseMatrix() { let { position, orientation } = this.frame.pose; if (!position && !orientation) { return this.baseModelMatrix; } if (!position) { position = this.tempVec3; position[0] = position[1] = position[2] = 0; } fromRotationTranslation(this.baseModelMatrix, orientation, position); return this.baseModelMatrix; } getBaseViewMatrix(eye) { if (eye === 'left' || eye === 'none') { return this.frame.leftViewMatrix; } else if (eye === 'right') { return this.frame.rightViewMatrix; } else { throw new Error(`eye must be of type 'left' or 'right'`); } } getInputSources() { let inputSources = []; for (let i in this.gamepadInputSources) { inputSources.push(this.gamepadInputSources[i].inputSource); } return inputSources; } getInputPose(inputSource, coordinateSystem, poseType) { if (!coordinateSystem) { return null; } for (let i in this.gamepadInputSources) { let inputSourceImpl = this.gamepadInputSources[i]; if (inputSourceImpl.inputSource === inputSource) { return inputSourceImpl.getXRPose(coordinateSystem, poseType); } } return null; } onWindowResize() { } onVRDisplayPresentChange(e) { if (!this.display.isPresenting) { this.sessions.forEach(session => { if (session.immersive && !session.ended) { if (session.modifiedCanvasLayer) { const canvas = session.baseLayer.context.canvas; document.body.removeChild(canvas); canvas.setAttribute('style', ''); } if (this.immersiveSession === session) { this.immersiveSession = null; } this.dispatchEvent('@@webxr-polyfill/vr-present-end', session.id); } }); } } } class CardboardXRDevice extends WebVRDevice { constructor(global, cardboardConfig) { const display = new CardboardVRDisplay(cardboardConfig || {}); super(global, display); this.display = display; this.frame = { rightViewMatrix: new Float32Array(16), leftViewMatrix: new Float32Array(16), rightProjectionMatrix: new Float32Array(16), leftProjectionMatrix: new Float32Array(16), pose: null, timestamp: null, }; } } const TEST_ENV$1 = "production" === 'test'; let SESSION_ID$1 = 0; class Session$1 { constructor(mode, enabledFeatures) { this.mode = mode; this.enabledFeatures = enabledFeatures; this.ended = null; this.baseLayer = null; this.id = ++SESSION_ID$1; } } class InlineDevice extends XRDevice { constructor(global) { super(global); this.sessions = new Map(); this.projectionMatrix = create(); this.identityMatrix = create(); } onBaseLayerSet(sessionId, layer) { const session = this.sessions.get(sessionId); session.baseLayer = layer; } isSessionSupported(mode) { return mode == 'inline'; } isFeatureSupported(featureDescriptor) { switch(featureDescriptor) { case 'viewer': return true; default: return false; } } async requestSession(mode, enabledFeatures) { if (!this.isSessionSupported(mode)) { return Promise.reject(); } const session = new Session$1(mode, enabledFeatures); this.sessions.set(session.id, session); return Promise.resolve(session.id); } requestAnimationFrame(callback) { return window.requestAnimationFrame(callback); } cancelAnimationFrame(handle) { window.cancelAnimationFrame(handle); } onFrameStart(sessionId, renderState) { if (TEST_ENV$1) { return; } const session = this.sessions.get(sessionId); if (session.baseLayer) { const canvas = session.baseLayer.context.canvas; perspective(this.projectionMatrix, renderState.inlineVerticalFieldOfView, canvas.width/canvas.height, renderState.depthNear, renderState.depthFar); } } onFrameEnd(sessionId) { } async endSession(sessionId) { const session = this.sessions.get(sessionId); session.ended = true; } doesSessionSupportReferenceSpace(sessionId, type) { const session = this.sessions.get(sessionId); if (session.ended) { return false; } return session.enabledFeatures.has(type); } requestStageBounds() { return null; } async requestFrameOfReferenceTransform(type, options) { return null; } getProjectionMatrix(eye) { return this.projectionMatrix; } getViewport(sessionId, eye, layer, target) { const session = this.sessions.get(sessionId); const { width, height } = layer.context.canvas; target.x = target.y = 0; target.width = width; target.height = height; return true; } getBasePoseMatrix() { return this.identityMatrix; } getBaseViewMatrix(eye) { return this.identityMatrix; } getInputSources() { return []; } getInputPose(inputSource, coordinateSystem, poseType) { return null; } onWindowResize() { } } const getWebVRDevice = async function (global) { let device = null; if ('getVRDisplays' in global.navigator) { try { const displays = await global.navigator.getVRDisplays(); if (displays && displays.length) { device = new WebVRDevice(global, displays[0]); } } catch (e) {} } return device; }; const requestXRDevice = async function (global, config) { if (config.webvr) { let xr = await getWebVRDevice(global); if (xr) { return xr; } } let mobile = isMobile(global); if ((mobile && config.cardboard) || (!mobile && config.allowCardboardOnDesktop)) { if (!global.VRFrameData) { global.VRFrameData = function () { this.rightViewMatrix = new Float32Array(16); this.leftViewMatrix = new Float32Array(16); this.rightProjectionMatrix = new Float32Array(16); this.leftProjectionMatrix = new Float32Array(16); this.pose = null; }; } return new CardboardXRDevice(global, config.cardboardConfig); } return new InlineDevice(global); }; const CONFIG_DEFAULTS = { global: _global, webvr: true, cardboard: true, cardboardConfig: null, allowCardboardOnDesktop: false, }; const partials = ['navigator', 'HTMLCanvasElement', 'WebGLRenderingContext']; class WebXRPolyfill { constructor(config={}) { this.config = Object.freeze(Object.assign({}, CONFIG_DEFAULTS, config)); this.global = this.config.global; this.nativeWebXR = 'xr' in this.global.navigator; this.injected = false; if (!this.nativeWebXR) { this._injectPolyfill(this.global); } else { this._injectCompatibilityShims(this.global); } } _injectPolyfill(global) { if (!partials.every(iface => !!global[iface])) { throw new Error(`Global must have the following attributes : ${partials}`); } for (const className of Object.keys(API)) { if (global[className] !== undefined) { console.warn(`${className} already defined on global.`); } else { global[className] = API[className]; } } { const polyfilledCtx = polyfillMakeXRCompatible(global.WebGLRenderingContext); if (polyfilledCtx) { polyfillGetContext(global.HTMLCanvasElement); if (global.OffscreenCanvas) { polyfillGetContext(global.OffscreenCanvas); } if (global.WebGL2RenderingContext){ polyfillMakeXRCompatible(global.WebGL2RenderingContext); } if (!window.isSecureContext) { console.warn(`WebXR Polyfill Warning: This page is not running in a secure context (https:// or localhost)! This means that although the page may be able to use the WebXR Polyfill it will not be able to use native WebXR implementations, and as such will not be able to access dedicated VR or AR hardware, and will not be able to take advantage of any performance improvements a native WebXR implementation may offer. Please host this content on a secure origin for the best user experience. `); } } } this.injected = true; this._patchNavigatorXR(); } _patchNavigatorXR() { let devicePromise = requestXRDevice(this.global, this.config); this.xr = new API.XRSystem(devicePromise); Object.defineProperty(this.global.navigator, 'xr', { value: this.xr, configurable: true, }); } _injectCompatibilityShims(global) { if (!partials.every(iface => !!global[iface])) { throw new Error(`Global must have the following attributes : ${partials}`); } if (global.navigator.xr && 'supportsSession' in global.navigator.xr && !('isSessionSupported' in global.navigator.xr)) { let originalSupportsSession = global.navigator.xr.supportsSession; global.navigator.xr.isSessionSupported = function(mode) { return originalSupportsSession.call(this, mode).then(() => { return true; }).catch(() => { return false; }); }; global.navigator.xr.supportsSession = function(mode) { console.warn("navigator.xr.supportsSession() is deprecated. Please " + "call navigator.xr.isSessionSupported() instead and check the boolean " + "value returned when the promise resolves."); return originalSupportsSession.call(this, mode); }; } } } export default WebXRPolyfill;