You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
861 lines
18 KiB
JavaScript
861 lines
18 KiB
JavaScript
/*
|
|
* Copyright 2012, Gregg Tavares.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Gregg Tavares. nor the names of his
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/* global define */
|
|
|
|
(function(root, factory) { // eslint-disable-line
|
|
if ( typeof define === 'function' && define.amd ) {
|
|
|
|
// AMD. Register as an anonymous module.
|
|
define( [], function () {
|
|
|
|
return factory.call( root );
|
|
|
|
} );
|
|
|
|
} else {
|
|
|
|
// Browser globals
|
|
root.lessonsHelper = factory.call( root );
|
|
|
|
}
|
|
|
|
}( this, function () {
|
|
|
|
'use strict'; // eslint-disable-line
|
|
|
|
const lessonSettings = window.lessonSettings || {};
|
|
const topWindow = this;
|
|
|
|
/**
|
|
* Check if the page is embedded.
|
|
* @param {Window?) w window to check
|
|
* @return {boolean} True of we are in an iframe
|
|
*/
|
|
function isInIFrame( w ) {
|
|
|
|
w = w || topWindow;
|
|
return w !== w.top;
|
|
|
|
}
|
|
|
|
function updateCSSIfInIFrame() {
|
|
|
|
if ( isInIFrame() ) {
|
|
|
|
try {
|
|
|
|
document.getElementsByTagName( 'html' )[ 0 ].className = 'iframe';
|
|
|
|
} catch ( e ) {
|
|
// eslint-disable-line
|
|
}
|
|
|
|
try {
|
|
|
|
document.body.className = 'iframe';
|
|
|
|
} catch ( e ) {
|
|
// eslint-disable-line
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function isInEditor() {
|
|
|
|
return window.location.href.substring( 0, 4 ) === 'blob';
|
|
|
|
}
|
|
|
|
/**
|
|
* Creates a webgl context. If creation fails it will
|
|
* change the contents of the container of the <canvas>
|
|
* tag to an error message with the correct links for WebGL.
|
|
* @param {HTMLCanvasElement} canvas. The canvas element to
|
|
* create a context from.
|
|
* @param {WebGLContextCreationAttributes} opt_attribs Any
|
|
* creation attributes you want to pass in.
|
|
* @return {WebGLRenderingContext} The created context.
|
|
* @memberOf module:webgl-utils
|
|
*/
|
|
function showNeedWebGL( canvas ) {
|
|
|
|
const doc = canvas.ownerDocument;
|
|
if ( doc ) {
|
|
|
|
const temp = doc.createElement( 'div' );
|
|
temp.innerHTML = `
|
|
<div style="
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
background-color: #DEF;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-flow: column;
|
|
justify-content: center;
|
|
align-content: center;
|
|
align-items: center;
|
|
">
|
|
<div style="text-align: center;">
|
|
It doesn't appear your browser supports WebGL.<br/>
|
|
<a href="http://get.webgl.org" target="_blank">Click here for more information.</a>
|
|
</div>
|
|
</div>
|
|
`;
|
|
const div = temp.querySelector( 'div' );
|
|
doc.body.appendChild( div );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const origConsole = {};
|
|
|
|
function setupConsole() {
|
|
|
|
const style = document.createElement( 'style' );
|
|
style.innerText = `
|
|
.console {
|
|
font-family: monospace;
|
|
font-size: medium;
|
|
max-height: 50%;
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
overflow: auto;
|
|
background: rgba(221, 221, 221, 0.9);
|
|
}
|
|
.console .console-line {
|
|
white-space: pre-line;
|
|
}
|
|
.console .log .warn {
|
|
color: black;
|
|
}
|
|
.console .error {
|
|
color: red;
|
|
}
|
|
`;
|
|
const parent = document.createElement( 'div' );
|
|
parent.className = 'console';
|
|
const toggle = document.createElement( 'div' );
|
|
let show = false;
|
|
Object.assign( toggle.style, {
|
|
position: 'absolute',
|
|
right: 0,
|
|
bottom: 0,
|
|
background: '#EEE',
|
|
'font-size': 'smaller',
|
|
cursor: 'pointer',
|
|
} );
|
|
toggle.addEventListener( 'click', showHideConsole );
|
|
|
|
function showHideConsole() {
|
|
|
|
show = ! show;
|
|
toggle.textContent = show ? '☒' : '☐';
|
|
parent.style.display = show ? '' : 'none';
|
|
|
|
}
|
|
|
|
showHideConsole();
|
|
|
|
const maxLines = 100;
|
|
const lines = [];
|
|
let added = false;
|
|
|
|
function addLine( type, str, prefix ) {
|
|
|
|
const div = document.createElement( 'div' );
|
|
div.textContent = ( prefix + str ) || ' ';
|
|
div.className = `console-line ${type}`;
|
|
parent.appendChild( div );
|
|
lines.push( div );
|
|
if ( ! added ) {
|
|
|
|
added = true;
|
|
document.body.appendChild( style );
|
|
document.body.appendChild( parent );
|
|
document.body.appendChild( toggle );
|
|
|
|
}
|
|
// scrollIntoView only works in Chrome
|
|
// In Firefox and Safari scrollIntoView inside an iframe moves
|
|
// that element into the view. It should arguably only move that
|
|
// element inside the iframe itself, otherwise that's giving
|
|
// any random iframe control to bring itself into view against
|
|
// the parent's wishes.
|
|
//
|
|
// note that even if we used a solution (which is to manually set
|
|
// scrollTop) there's a UI issue that if the user manually scrolls
|
|
// we want to stop scrolling automatically and if they move back
|
|
// to the bottom we want to pick up scrolling automatically.
|
|
// Kind of a PITA so TBD
|
|
//
|
|
// div.scrollIntoView();
|
|
|
|
}
|
|
|
|
function addLines( type, str, prefix ) {
|
|
|
|
while ( lines.length > maxLines ) {
|
|
|
|
const div = lines.shift();
|
|
div.parentNode.removeChild( div );
|
|
|
|
}
|
|
|
|
addLine( type, str, prefix );
|
|
|
|
}
|
|
|
|
const threePukeRE = /WebGLRenderer.*?extension not supported/;
|
|
function wrapFunc( obj, funcName, prefix ) {
|
|
|
|
const oldFn = obj[ funcName ];
|
|
origConsole[ funcName ] = oldFn.bind( obj );
|
|
return function ( ...args ) {
|
|
|
|
// three.js pukes all over so filter here
|
|
const src = [ ...args ].join( ' ' );
|
|
if ( ! threePukeRE.test( src ) ) {
|
|
|
|
addLines( funcName, src, prefix );
|
|
|
|
}
|
|
|
|
oldFn.apply( obj, arguments );
|
|
|
|
};
|
|
|
|
}
|
|
|
|
window.console.log = wrapFunc( window.console, 'log', '' );
|
|
window.console.warn = wrapFunc( window.console, 'warn', '⚠' );
|
|
window.console.error = wrapFunc( window.console, 'error', '❌' );
|
|
|
|
}
|
|
|
|
function reportJSError( url, lineNo, colNo, msg ) {
|
|
|
|
try {
|
|
|
|
const { origUrl, actualLineNo } = window.parent.getActualLineNumberAndMoveTo( url, lineNo, colNo );
|
|
url = origUrl;
|
|
lineNo = actualLineNo;
|
|
|
|
} catch ( ex ) {
|
|
|
|
origConsole.error( ex );
|
|
|
|
}
|
|
console.error(url, "line:", lineNo, ":", msg); // eslint-disable-line
|
|
}
|
|
|
|
/**
|
|
* @typedef {Object} StackInfo
|
|
* @property {string} url Url of line
|
|
* @property {number} lineNo line number of error
|
|
* @property {number} colNo column number of error
|
|
* @property {string} [funcName] name of function
|
|
*/
|
|
|
|
/**
|
|
* @parameter {string} stack A stack string as in `(new Error()).stack`
|
|
* @returns {StackInfo}
|
|
*/
|
|
const parseStack = function () {
|
|
|
|
const browser = getBrowser();
|
|
let lineNdx;
|
|
let matcher;
|
|
if ( ( /chrome|opera/i ).test( browser.name ) ) {
|
|
|
|
lineNdx = 3;
|
|
matcher = function ( line ) {
|
|
|
|
const m = /at ([^(]*?)\(*(.*?):(\d+):(\d+)/.exec( line );
|
|
if ( m ) {
|
|
|
|
let userFnName = m[ 1 ];
|
|
let url = m[ 2 ];
|
|
const lineNo = parseInt( m[ 3 ] );
|
|
const colNo = parseInt( m[ 4 ] );
|
|
if ( url === '' ) {
|
|
|
|
url = userFnName;
|
|
userFnName = '';
|
|
|
|
}
|
|
|
|
return {
|
|
url: url,
|
|
lineNo: lineNo,
|
|
colNo: colNo,
|
|
funcName: userFnName,
|
|
};
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
};
|
|
|
|
} else if ( ( /firefox|safari/i ).test( browser.name ) ) {
|
|
|
|
lineNdx = 2;
|
|
matcher = function ( line ) {
|
|
|
|
const m = /@(.*?):(\d+):(\d+)/.exec( line );
|
|
if ( m ) {
|
|
|
|
const url = m[ 1 ];
|
|
const lineNo = parseInt( m[ 2 ] );
|
|
const colNo = parseInt( m[ 3 ] );
|
|
return {
|
|
url: url,
|
|
lineNo: lineNo,
|
|
colNo: colNo,
|
|
};
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return function stackParser( stack ) {
|
|
|
|
if ( matcher ) {
|
|
|
|
try {
|
|
|
|
const lines = stack.split( '\n' );
|
|
// window.fooLines = lines;
|
|
// lines.forEach(function(line, ndx) {
|
|
// origConsole.log("#", ndx, line);
|
|
// });
|
|
return matcher( lines[ lineNdx ] );
|
|
|
|
} catch ( e ) {
|
|
// do nothing
|
|
}
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
};
|
|
|
|
}();
|
|
|
|
function setupWorkerSupport() {
|
|
|
|
function log( data ) {
|
|
|
|
const { logType, msg } = data;
|
|
console[ logType ]( '[Worker]', msg ); /* eslint-disable-line no-console */
|
|
|
|
}
|
|
|
|
function lostContext( /* data */ ) {
|
|
|
|
addContextLostHTML();
|
|
|
|
}
|
|
|
|
function jsError( data ) {
|
|
|
|
const { url, lineNo, colNo, msg } = data;
|
|
reportJSError( url, lineNo, colNo, msg );
|
|
|
|
}
|
|
|
|
function jsErrorWithStack( data ) {
|
|
|
|
const { url, stack, msg } = data;
|
|
const errorInfo = parseStack( stack );
|
|
if ( errorInfo ) {
|
|
|
|
reportJSError( errorInfo.url || url, errorInfo.lineNo, errorInfo.colNo, msg );
|
|
|
|
} else {
|
|
|
|
console.error(errorMsg) // eslint-disable-line
|
|
}
|
|
|
|
}
|
|
|
|
const handlers = {
|
|
log,
|
|
lostContext,
|
|
jsError,
|
|
jsErrorWithStack,
|
|
};
|
|
const OrigWorker = self.Worker;
|
|
class WrappedWorker extends OrigWorker {
|
|
|
|
constructor( url, ...args ) {
|
|
|
|
super( url, ...args );
|
|
let listener;
|
|
this.onmessage = function ( e ) {
|
|
|
|
if ( ! e || ! e.data || e.data.type !== '___editor___' ) {
|
|
|
|
if ( listener ) {
|
|
|
|
listener( e );
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
e.stopImmediatePropagation();
|
|
const data = e.data.data;
|
|
const fn = handlers[ data.type ];
|
|
if ( typeof fn !== 'function' ) {
|
|
|
|
origConsole.error( 'unknown editor msg:', data.type );
|
|
|
|
} else {
|
|
|
|
fn( data );
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
Object.defineProperty( this, 'onmessage', {
|
|
get() {
|
|
|
|
return listener;
|
|
|
|
},
|
|
set( fn ) {
|
|
|
|
listener = fn;
|
|
|
|
},
|
|
} );
|
|
|
|
}
|
|
|
|
}
|
|
self.Worker = WrappedWorker;
|
|
|
|
}
|
|
|
|
function addContextLostHTML() {
|
|
|
|
const div = document.createElement( 'div' );
|
|
div.className = 'contextlost';
|
|
div.innerHTML = '<div>Context Lost: Click To Reload</div>';
|
|
div.addEventListener( 'click', function () {
|
|
|
|
window.location.reload();
|
|
|
|
} );
|
|
document.body.appendChild( div );
|
|
|
|
}
|
|
|
|
/**
|
|
* Gets a WebGL context.
|
|
* makes its backing store the size it is displayed.
|
|
* @param {HTMLCanvasElement} canvas a canvas element.
|
|
* @memberOf module:webgl-utils
|
|
*/
|
|
let setupLesson = function ( canvas ) {
|
|
|
|
// only once
|
|
setupLesson = function () {};
|
|
|
|
if ( canvas ) {
|
|
|
|
canvas.addEventListener( 'webglcontextlost', function () {
|
|
|
|
// the default is to do nothing. Preventing the default
|
|
// means allowing context to be restored
|
|
// e.preventDefault(); // can't do this because firefox bug - https://bugzilla.mozilla.org/show_bug.cgi?id=1633280
|
|
addContextLostHTML();
|
|
|
|
} );
|
|
/* can't do this because firefox bug - https://bugzilla.mozilla.org/show_bug.cgi?id=1633280
|
|
canvas.addEventListener('webglcontextrestored', function() {
|
|
// just reload the page. Easiest.
|
|
window.location.reload();
|
|
});
|
|
*/
|
|
|
|
}
|
|
|
|
if ( isInIFrame() ) {
|
|
|
|
updateCSSIfInIFrame();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// Replace requestAnimationFrame and cancelAnimationFrame with one
|
|
// that only executes when the body is visible (we're in an iframe).
|
|
// It's frustrating that th browsers don't do this automatically.
|
|
// It's half of the point of rAF that it shouldn't execute when
|
|
// content is not visible but browsers execute rAF in iframes even
|
|
// if they are not visible.
|
|
if ( topWindow.requestAnimationFrame ) {
|
|
|
|
topWindow.requestAnimationFrame = ( function ( oldRAF, oldCancelRAF ) {
|
|
|
|
let nextFakeRAFId = 1;
|
|
const fakeRAFIdToCallbackMap = new Map();
|
|
let rafRequestId;
|
|
let isBodyOnScreen;
|
|
|
|
function rAFHandler( time ) {
|
|
|
|
rafRequestId = undefined;
|
|
const ids = [ ...fakeRAFIdToCallbackMap.keys() ]; // WTF! Map.keys() iterates over live keys!
|
|
for ( const id of ids ) {
|
|
|
|
const callback = fakeRAFIdToCallbackMap.get( id );
|
|
fakeRAFIdToCallbackMap.delete( id );
|
|
if ( callback ) {
|
|
|
|
callback( time );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function startRAFIfIntersectingAndNeeded() {
|
|
|
|
if ( ! rafRequestId && isBodyOnScreen && fakeRAFIdToCallbackMap.size > 0 ) {
|
|
|
|
rafRequestId = oldRAF( rAFHandler );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function stopRAF() {
|
|
|
|
if ( rafRequestId ) {
|
|
|
|
oldCancelRAF( rafRequestId );
|
|
rafRequestId = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function initIntersectionObserver() {
|
|
|
|
const intersectionObserver = new IntersectionObserver( ( entries ) => {
|
|
|
|
entries.forEach( entry => {
|
|
|
|
isBodyOnScreen = entry.isIntersecting;
|
|
|
|
} );
|
|
if ( isBodyOnScreen ) {
|
|
|
|
startRAFIfIntersectingAndNeeded();
|
|
|
|
} else {
|
|
|
|
stopRAF();
|
|
|
|
}
|
|
|
|
} );
|
|
intersectionObserver.observe( document.body );
|
|
|
|
}
|
|
|
|
function betterRAF( callback ) {
|
|
|
|
const fakeRAFId = nextFakeRAFId ++;
|
|
fakeRAFIdToCallbackMap.set( fakeRAFId, callback );
|
|
startRAFIfIntersectingAndNeeded();
|
|
return fakeRAFId;
|
|
|
|
}
|
|
|
|
function betterCancelRAF( id ) {
|
|
|
|
fakeRAFIdToCallbackMap.delete( id );
|
|
|
|
}
|
|
|
|
topWindow.cancelAnimationFrame = betterCancelRAF;
|
|
|
|
return function ( callback ) {
|
|
|
|
// we need to lazy init this because this code gets parsed
|
|
// before body exists. We could fix it by moving lesson-helper.js
|
|
// after <body> but that would require changing 100s of examples
|
|
initIntersectionObserver();
|
|
topWindow.requestAnimationFrame = betterRAF;
|
|
return betterRAF( callback );
|
|
|
|
};
|
|
|
|
}( topWindow.requestAnimationFrame, topWindow.cancelAnimationFrame ) );
|
|
|
|
}
|
|
|
|
updateCSSIfInIFrame();
|
|
|
|
function captureJSErrors() {
|
|
|
|
// capture JavaScript Errors
|
|
window.addEventListener( 'error', function ( e ) {
|
|
|
|
const msg = e.message || e.error;
|
|
const url = e.filename;
|
|
const lineNo = e.lineno || 1;
|
|
const colNo = e.colno || 1;
|
|
reportJSError( url, lineNo, colNo, msg );
|
|
origConsole.error( e.error );
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
// adapted from http://stackoverflow.com/a/2401861/128511
|
|
function getBrowser() {
|
|
|
|
const userAgent = navigator.userAgent;
|
|
let m = userAgent.match( /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i ) || [];
|
|
if ( /trident/i.test( m[ 1 ] ) ) {
|
|
|
|
m = /\brv[ :]+(\d+)/g.exec( userAgent ) || [];
|
|
return {
|
|
name: 'IE',
|
|
version: m[ 1 ],
|
|
};
|
|
|
|
}
|
|
|
|
if ( m[ 1 ] === 'Chrome' ) {
|
|
|
|
const temp = userAgent.match( /\b(OPR|Edge)\/(\d+)/ );
|
|
if ( temp ) {
|
|
|
|
return {
|
|
name: temp[ 1 ].replace( 'OPR', 'Opera' ),
|
|
version: temp[ 2 ],
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m = m[ 2 ] ? [ m[ 1 ], m[ 2 ] ] : [ navigator.appName, navigator.appVersion, '-?' ];
|
|
const version = userAgent.match( /version\/(\d+)/i );
|
|
if ( version ) {
|
|
|
|
m.splice( 1, 1, version[ 1 ] );
|
|
|
|
}
|
|
|
|
return {
|
|
name: m[ 0 ],
|
|
version: m[ 1 ],
|
|
};
|
|
|
|
}
|
|
|
|
const canvasesToTimeoutMap = new Map();
|
|
const isWebGLRE = /^(webgl|webgl2|experimental-webgl)$/i;
|
|
const isWebGL2RE = /^webgl2$/i;
|
|
function installWebGLLessonSetup() {
|
|
|
|
HTMLCanvasElement.prototype.getContext = ( function ( oldFn ) {
|
|
|
|
return function () {
|
|
|
|
const timeoutId = canvasesToTimeoutMap.get( this );
|
|
if ( timeoutId ) {
|
|
|
|
clearTimeout( timeoutId );
|
|
|
|
}
|
|
|
|
const type = arguments[ 0 ];
|
|
const isWebGL1or2 = isWebGLRE.test( type );
|
|
const isWebGL2 = isWebGL2RE.test( type );
|
|
if ( isWebGL1or2 ) {
|
|
|
|
setupLesson( this );
|
|
|
|
}
|
|
|
|
const args = [].slice.apply( arguments );
|
|
args[ 1 ] = {
|
|
powerPreference: 'low-power',
|
|
...args[ 1 ],
|
|
};
|
|
const ctx = oldFn.apply( this, args );
|
|
if ( ! ctx ) {
|
|
|
|
if ( isWebGL2 ) {
|
|
|
|
// three tries webgl2 then webgl1
|
|
// so wait 1/2 a second before showing the failure
|
|
// message. If we get success on the same canvas
|
|
// we'll cancel this.
|
|
canvasesToTimeoutMap.set( this, setTimeout( () => {
|
|
|
|
canvasesToTimeoutMap.delete( this );
|
|
showNeedWebGL( this );
|
|
|
|
}, 500 ) );
|
|
|
|
} else {
|
|
|
|
showNeedWebGL( this );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ctx;
|
|
|
|
};
|
|
|
|
}( HTMLCanvasElement.prototype.getContext ) );
|
|
|
|
}
|
|
|
|
function installWebGLDebugContextCreator() {
|
|
|
|
if ( ! self.webglDebugHelper ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const {
|
|
makeDebugContext,
|
|
glFunctionArgToString,
|
|
glEnumToString,
|
|
} = self.webglDebugHelper;
|
|
|
|
// capture GL errors
|
|
HTMLCanvasElement.prototype.getContext = ( function ( oldFn ) {
|
|
|
|
return function () {
|
|
|
|
let ctx = oldFn.apply( this, arguments );
|
|
// Using bindTexture to see if it's WebGL. Could check for instanceof WebGLRenderingContext
|
|
// but that might fail if wrapped by debugging extension
|
|
if ( ctx && ctx.bindTexture ) {
|
|
|
|
ctx = makeDebugContext( ctx, {
|
|
maxDrawCalls: 100,
|
|
errorFunc: function ( err, funcName, args ) {
|
|
|
|
const numArgs = args.length;
|
|
const enumedArgs = [].map.call( args, function ( arg, ndx ) {
|
|
|
|
let str = glFunctionArgToString( funcName, numArgs, ndx, arg );
|
|
// shorten because of long arrays
|
|
if ( str.length > 200 ) {
|
|
|
|
str = str.substring( 0, 200 ) + '...';
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
} );
|
|
const errorMsg = `WebGL error ${glEnumToString( err )} in ${funcName}(${enumedArgs.join( ', ' )})`;
|
|
const errorInfo = parseStack( ( new Error() ).stack );
|
|
if ( errorInfo ) {
|
|
|
|
reportJSError( errorInfo.url, errorInfo.lineNo, errorInfo.colNo, errorMsg );
|
|
|
|
} else {
|
|
|
|
console.error(errorMsg) // eslint-disable-line
|
|
}
|
|
|
|
},
|
|
} );
|
|
|
|
}
|
|
|
|
return ctx;
|
|
|
|
};
|
|
|
|
}( HTMLCanvasElement.prototype.getContext ) );
|
|
|
|
}
|
|
|
|
installWebGLLessonSetup();
|
|
|
|
if ( isInEditor() ) {
|
|
|
|
setupWorkerSupport();
|
|
setupConsole();
|
|
captureJSErrors();
|
|
if ( lessonSettings.glDebug !== false ) {
|
|
|
|
installWebGLDebugContextCreator();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
setupLesson: setupLesson,
|
|
showNeedWebGL: showNeedWebGL,
|
|
};
|
|
|
|
} ) );
|
|
|