threejs-lessons-helper.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  1. /*
  2. * Copyright 2012, Gregg Tavares.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above
  12. * copyright notice, this list of conditions and the following disclaimer
  13. * in the documentation and/or other materials provided with the
  14. * distribution.
  15. * * Neither the name of Gregg Tavares. nor the names of his
  16. * contributors may be used to endorse or promote products derived from
  17. * this software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. (function(root, factory) { // eslint-disable-line
  32. if (typeof define === 'function' && define.amd) {
  33. // AMD. Register as an anonymous module.
  34. define([], function() {
  35. return factory.call(root);
  36. });
  37. } else {
  38. // Browser globals
  39. root.threejsLessonsHelper = factory.call(root);
  40. }
  41. }(this, function() {
  42. "use strict";
  43. var topWindow = this;
  44. /**
  45. * Check if the page is embedded.
  46. * @param {Window?) w window to check
  47. * @return {boolean} True of we are in an iframe
  48. */
  49. function isInIFrame(w) {
  50. w = w || topWindow;
  51. return w !== w.top;
  52. }
  53. function updateCSSIfInIFrame() {
  54. if (isInIFrame()) {
  55. try {
  56. document.getElementsByTagName("html")[0].className = "iframe";
  57. } catch (e) {
  58. // eslint-disable-line
  59. }
  60. try {
  61. document.body.className = "iframe";
  62. } catch (e) {
  63. // eslint-disable-line
  64. }
  65. }
  66. }
  67. function isInEditor() {
  68. return window.location.href.substring(0, 4) === "blob";
  69. }
  70. /**
  71. * Creates a webgl context. If creation fails it will
  72. * change the contents of the container of the <canvas>
  73. * tag to an error message with the correct links for WebGL.
  74. * @param {HTMLCanvasElement} canvas. The canvas element to
  75. * create a context from.
  76. * @param {WebGLContextCreationAttirbutes} opt_attribs Any
  77. * creation attributes you want to pass in.
  78. * @return {WebGLRenderingContext} The created context.
  79. * @memberOf module:webgl-utils
  80. */
  81. function showNeedWebGL(canvas) {
  82. var doc = canvas.ownerDocument;
  83. if (doc) {
  84. var div = doc.createElement("div");
  85. div.innerHTML = `
  86. <div style="
  87. position: absolute;
  88. left: 0;
  89. top: 0;
  90. background-color: #DEF;
  91. width: 100vw;
  92. height: 100vh;
  93. display: flex;
  94. flex-flow: column;
  95. justify-content: center;
  96. align-content: center;
  97. align-items: center;
  98. ">
  99. <div style="text-align: center;">
  100. It doesn't appear your browser supports WebGL.<br/>
  101. <a href="http://get.webgl.org" target="_blank">Click here for more information.</a>
  102. </div>
  103. </div>
  104. `;
  105. div = div.querySelector("div");
  106. doc.body.appendChild(div);
  107. }
  108. }
  109. var origConsole = {};
  110. function setupConsole() {
  111. var parent = document.createElement("div");
  112. parent.className = "console";
  113. Object.assign(parent.style, {
  114. fontFamily: 'monospace',
  115. fontSize: 'medium',
  116. maxHeight: '50%',
  117. position: 'fixed',
  118. bottom: 0,
  119. left: 0,
  120. width: '100%',
  121. overflow: 'auto',
  122. background: '#DDD',
  123. });
  124. var numLinesRemaining = 100;
  125. var added = false;
  126. function addLine(type, str) {
  127. var div = document.createElement("div");
  128. div.textContent = str;
  129. div.className = type;
  130. parent.appendChild(div);
  131. if (!added) {
  132. added = true;
  133. document.body.appendChild(parent);
  134. }
  135. }
  136. function addLines(type, str) {
  137. if (numLinesRemaining) {
  138. --numLinesRemaining;
  139. addLine(type, str);
  140. }
  141. }
  142. function wrapFunc(obj, funcName) {
  143. var oldFn = obj[funcName];
  144. origConsole[funcName] = oldFn.bind(obj);
  145. return function() {
  146. addLines(funcName, [].join.call(arguments, ' '));
  147. oldFn.apply(obj, arguments);
  148. };
  149. }
  150. window.console.log = wrapFunc(window.console, 'log');
  151. window.console.warn = wrapFunc(window.console, 'warn');
  152. window.console.error = wrapFunc(window.console, 'error');
  153. }
  154. /**
  155. * Gets a WebGL context.
  156. * makes its backing store the size it is displayed.
  157. * @param {HTMLCanvasElement} canvas a canvas element.
  158. * @memberOf module:webgl-utils
  159. */
  160. var setupLesson = function(canvas) {
  161. // only once
  162. setupLesson = function() {};
  163. if (canvas) {
  164. canvas.addEventListener('webglcontextlost', function(e) {
  165. // the default is to do nothing. Preventing the default
  166. // means allowing context to be restored
  167. e.preventDefault();
  168. var div = document.createElement("div");
  169. div.className = "contextlost";
  170. div.innerHTML = '<div>Context Lost: Click To Reload</div>';
  171. div.addEventListener('click', function() {
  172. window.location.reload();
  173. });
  174. document.body.appendChild(div);
  175. });
  176. canvas.addEventListener('webglcontextrestored', function() {
  177. // just reload the page. Easiest.
  178. window.location.reload();
  179. });
  180. }
  181. if (isInIFrame()) {
  182. updateCSSIfInIFrame();
  183. }
  184. };
  185. /**
  186. * Get's the iframe in the parent document
  187. * that is displaying the specified window .
  188. * @param {Window} window window to check.
  189. * @return {HTMLIFrameElement?) the iframe element if window is in an iframe
  190. */
  191. function getIFrameForWindow(window) {
  192. if (!isInIFrame(window)) {
  193. return;
  194. }
  195. var iframes = window.parent.document.getElementsByTagName("iframe");
  196. for (var ii = 0; ii < iframes.length; ++ii) {
  197. var iframe = iframes[ii];
  198. if (iframe.contentDocument === window.document) {
  199. return iframe; // eslint-disable-line
  200. }
  201. }
  202. }
  203. /**
  204. * Returns true if window is on screen. The main window is
  205. * always on screen windows in iframes might not be.
  206. * @param {Window} window the window to check.
  207. * @return {boolean} true if window is on screen.
  208. */
  209. function isFrameVisible(window) {
  210. try {
  211. var iframe = getIFrameForWindow(window);
  212. if (!iframe) {
  213. return true;
  214. }
  215. var bounds = iframe.getBoundingClientRect();
  216. var isVisible = bounds.top < window.parent.innerHeight && bounds.bottom >= 0 &&
  217. bounds.left < window.parent.innerWidth && bounds.right >= 0;
  218. return isVisible && isFrameVisible(window.parent);
  219. } catch (e) {
  220. return true; // We got a security error?
  221. }
  222. }
  223. /**
  224. * Returns true if element is on screen.
  225. * @param {HTMLElement} element the element to check.
  226. * @return {boolean} true if element is on screen.
  227. */
  228. function isOnScreen(element) {
  229. var isVisible = true;
  230. if (element) {
  231. var bounds = element.getBoundingClientRect();
  232. isVisible = bounds.top < topWindow.innerHeight && bounds.bottom >= 0;
  233. }
  234. return isVisible && isFrameVisible(topWindow);
  235. }
  236. // Replace requestAnimationFrame.
  237. if (topWindow.requestAnimationFrame) {
  238. topWindow.requestAnimationFrame = (function(oldRAF) {
  239. return function(callback, element) {
  240. var handler = function() {
  241. if (isOnScreen(element)) {
  242. oldRAF(callback, element);
  243. } else {
  244. oldRAF(handler, element);
  245. }
  246. };
  247. handler();
  248. };
  249. }(topWindow.requestAnimationFrame));
  250. }
  251. updateCSSIfInIFrame();
  252. //------------ [ from https://github.com/KhronosGroup/WebGLDeveloperTools ]
  253. /*
  254. ** Copyright (c) 2012 The Khronos Group Inc.
  255. **
  256. ** Permission is hereby granted, free of charge, to any person obtaining a
  257. ** copy of this software and/or associated documentation files (the
  258. ** "Materials"), to deal in the Materials without restriction, including
  259. ** without limitation the rights to use, copy, modify, merge, publish,
  260. ** distribute, sublicense, and/or sell copies of the Materials, and to
  261. ** permit persons to whom the Materials are furnished to do so, subject to
  262. ** the following conditions:
  263. **
  264. ** The above copyright notice and this permission notice shall be included
  265. ** in all copies or substantial portions of the Materials.
  266. **
  267. ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  268. ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  269. ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  270. ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  271. ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  272. ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  273. ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
  274. */
  275. /**
  276. * Types of contexts we have added to map
  277. */
  278. var mappedContextTypes = {};
  279. /**
  280. * Map of numbers to names.
  281. * @type {Object}
  282. */
  283. var glEnums = {};
  284. /**
  285. * Map of names to numbers.
  286. * @type {Object}
  287. */
  288. var enumStringToValue = {};
  289. /**
  290. * Initializes this module. Safe to call more than once.
  291. * @param {!WebGLRenderingContext} ctx A WebGL context. If
  292. * you have more than one context it doesn't matter which one
  293. * you pass in, it is only used to pull out constants.
  294. */
  295. function addEnumsForContext(ctx, type) {
  296. if (!mappedContextTypes[type]) {
  297. mappedContextTypes[type] = true;
  298. for (var propertyName in ctx) {
  299. if (typeof ctx[propertyName] === 'number') {
  300. glEnums[ctx[propertyName]] = propertyName;
  301. enumStringToValue[propertyName] = ctx[propertyName];
  302. }
  303. }
  304. }
  305. }
  306. function enumArrayToString(enums) {
  307. if (enums.length) {
  308. var enumStrings = [];
  309. for (var i = 0; i < enums.length; ++i) {
  310. enums.push(glEnumToString(enums[i])); // eslint-disable-line
  311. }
  312. return '[' + enumStrings.join(', ') + ']';
  313. }
  314. return enumStrings.toString();
  315. }
  316. function makeBitFieldToStringFunc(enums) {
  317. return function(value) {
  318. var orResult = 0;
  319. var orEnums = [];
  320. for (var i = 0; i < enums.length; ++i) {
  321. var enumValue = enumStringToValue[enums[i]];
  322. if ((value & enumValue) !== 0) {
  323. orResult |= enumValue;
  324. orEnums.push(glEnumToString(enumValue)); // eslint-disable-line
  325. }
  326. }
  327. if (orResult === value) {
  328. return orEnums.join(' | ');
  329. } else {
  330. return glEnumToString(value); // eslint-disable-line
  331. }
  332. };
  333. }
  334. var destBufferBitFieldToString = makeBitFieldToStringFunc([
  335. 'COLOR_BUFFER_BIT',
  336. 'DEPTH_BUFFER_BIT',
  337. 'STENCIL_BUFFER_BIT',
  338. ]);
  339. /**
  340. * Which arguments are enums based on the number of arguments to the function.
  341. * So
  342. * 'texImage2D': {
  343. * 9: { 0:true, 2:true, 6:true, 7:true },
  344. * 6: { 0:true, 2:true, 3:true, 4:true },
  345. * },
  346. *
  347. * means if there are 9 arguments then 6 and 7 are enums, if there are 6
  348. * arguments 3 and 4 are enums. Maybe a function as well in which case
  349. * value is passed to function and returns a string
  350. *
  351. * @type {!Object.<number, (!Object.<number, string>|function)}
  352. */
  353. var glValidEnumContexts = {
  354. // Generic setters and getters
  355. 'enable': {1: { 0:true }},
  356. 'disable': {1: { 0:true }},
  357. 'getParameter': {1: { 0:true }},
  358. // Rendering
  359. 'drawArrays': {3:{ 0:true }},
  360. 'drawElements': {4:{ 0:true, 2:true }},
  361. 'drawArraysInstanced': {4: { 0:true }},
  362. 'drawElementsInstanced': {5: {0:true, 2: true }},
  363. 'drawRangeElements': {6: {0:true, 4: true }},
  364. // Shaders
  365. 'createShader': {1: { 0:true }},
  366. 'getShaderParameter': {2: { 1:true }},
  367. 'getProgramParameter': {2: { 1:true }},
  368. 'getShaderPrecisionFormat': {2: { 0: true, 1:true }},
  369. // Vertex attributes
  370. 'getVertexAttrib': {2: { 1:true }},
  371. 'vertexAttribPointer': {6: { 2:true }},
  372. 'vertexAttribIPointer': {5: { 2:true }}, // WebGL2
  373. // Textures
  374. 'bindTexture': {2: { 0:true }},
  375. 'activeTexture': {1: { 0:true }},
  376. 'getTexParameter': {2: { 0:true, 1:true }},
  377. 'texParameterf': {3: { 0:true, 1:true }},
  378. 'texParameteri': {3: { 0:true, 1:true, 2:true }},
  379. 'texImage2D': {
  380. 9: { 0:true, 2:true, 6:true, 7:true },
  381. 6: { 0:true, 2:true, 3:true, 4:true },
  382. 10: { 0:true, 2:true, 6:true, 7:true }, // WebGL2
  383. },
  384. 'texImage3D': {
  385. 10: { 0:true, 2:true, 7:true, 8:true }, // WebGL2
  386. 11: { 0:true, 2:true, 7:true, 8:true }, // WebGL2
  387. },
  388. 'texSubImage2D': {
  389. 9: { 0:true, 6:true, 7:true },
  390. 7: { 0:true, 4:true, 5:true },
  391. 10: { 0:true, 6:true, 7:true }, // WebGL2
  392. },
  393. 'texSubImage3D': {
  394. 11: { 0:true, 8:true, 9:true }, // WebGL2
  395. 12: { 0:true, 8:true, 9:true }, // WebGL2
  396. },
  397. 'texStorage2D': { 5: { 0:true, 2:true }}, // WebGL2
  398. 'texStorage3D': { 6: { 0:true, 2:true }}, // WebGL2
  399. 'copyTexImage2D': {8: { 0:true, 2:true }},
  400. 'copyTexSubImage2D': {8: { 0:true }},
  401. 'copyTexSubImage3D': {9: { 0:true }}, // WebGL2
  402. 'generateMipmap': {1: { 0:true }},
  403. 'compressedTexImage2D': {
  404. 7: { 0: true, 2:true },
  405. 8: { 0: true, 2:true }, // WebGL2
  406. },
  407. 'compressedTexSubImage2D': {
  408. 8: { 0: true, 6:true },
  409. 9: { 0: true, 6:true }, // WebGL2
  410. },
  411. 'compressedTexImage3D': {
  412. 8: { 0: true, 2: true, }, // WebGL2
  413. 9: { 0: true, 2: true, }, // WebGL2
  414. },
  415. 'compressedTexSubImage3D': {
  416. 9: { 0: true, 8: true, }, // WebGL2
  417. 10: { 0: true, 8: true, }, // WebGL2
  418. },
  419. // Buffer objects
  420. 'bindBuffer': {2: { 0:true }},
  421. 'bufferData': {
  422. 3: { 0:true, 2:true },
  423. 4: { 0:true, 2:true }, // WebGL2
  424. 5: { 0:true, 2:true }, // WebGL2
  425. },
  426. 'bufferSubData': {
  427. 3: { 0:true },
  428. 4: { 0:true }, // WebGL2
  429. 5: { 0:true }, // WebGL2
  430. },
  431. 'copyBufferSubData': {
  432. 5: { 0:true }, // WeBGL2
  433. },
  434. 'getBufferParameter': {2: { 0:true, 1:true }},
  435. 'getBufferSubData': {
  436. 3: { 0: true, }, // WebGL2
  437. 4: { 0: true, }, // WebGL2
  438. 5: { 0: true, }, // WebGL2
  439. },
  440. // Renderbuffers and framebuffers
  441. 'pixelStorei': {2: { 0:true, 1:true }},
  442. 'readPixels': {
  443. 7: { 4:true, 5:true },
  444. 8: { 4:true, 5:true }, // WebGL2
  445. },
  446. 'bindRenderbuffer': {2: { 0:true }},
  447. 'bindFramebuffer': {2: { 0:true }},
  448. 'blitFramebuffer': {10: { 8: destBufferBitFieldToString, 9:true }}, // WebGL2
  449. 'checkFramebufferStatus': {1: { 0:true }},
  450. 'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},
  451. 'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},
  452. 'framebufferTextureLayer': {5: {0:true, 1:true }}, // WebGL2
  453. 'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},
  454. 'getInternalformatParameter': {3: {0:true, 1:true, 2:true }}, // WebGL2
  455. 'getRenderbufferParameter': {2: { 0:true, 1:true }},
  456. 'invalidateFramebuffer': {2: { 0:true, 1: enumArrayToString, }}, // WebGL2
  457. 'invalidateSubFramebuffer': {6: {0: true, 1: enumArrayToString, }}, // WebGL2
  458. 'readBuffer': {1: {0: true}}, // WebGL2
  459. 'renderbufferStorage': {4: { 0:true, 1:true }},
  460. 'renderbufferStorageMultisample': {5: { 0: true, 2: true }}, // WebGL2
  461. // Frame buffer operations (clear, blend, depth test, stencil)
  462. 'clear': {1: { 0: destBufferBitFieldToString }},
  463. 'depthFunc': {1: { 0:true }},
  464. 'blendFunc': {2: { 0:true, 1:true }},
  465. 'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
  466. 'blendEquation': {1: { 0:true }},
  467. 'blendEquationSeparate': {2: { 0:true, 1:true }},
  468. 'stencilFunc': {3: { 0:true }},
  469. 'stencilFuncSeparate': {4: { 0:true, 1:true }},
  470. 'stencilMaskSeparate': {2: { 0:true }},
  471. 'stencilOp': {3: { 0:true, 1:true, 2:true }},
  472. 'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
  473. // Culling
  474. 'cullFace': {1: { 0:true }},
  475. 'frontFace': {1: { 0:true }},
  476. // ANGLE_instanced_arrays extension
  477. 'drawArraysInstancedANGLE': {4: { 0:true }},
  478. 'drawElementsInstancedANGLE': {5: { 0:true, 2:true }},
  479. // EXT_blend_minmax extension
  480. 'blendEquationEXT': {1: { 0:true }},
  481. // Multiple Render Targets
  482. 'drawBuffersWebGL': {1: {0: enumArrayToString, }}, // WEBGL_draw_bufers
  483. 'drawBuffers': {1: {0: enumArrayToString, }}, // WebGL2
  484. 'clearBufferfv': {
  485. 4: {0: true }, // WebGL2
  486. 5: {0: true }, // WebGL2
  487. },
  488. 'clearBufferiv': {
  489. 4: {0: true }, // WebGL2
  490. 5: {0: true }, // WebGL2
  491. },
  492. 'clearBufferuiv': {
  493. 4: {0: true }, // WebGL2
  494. 5: {0: true }, // WebGL2
  495. },
  496. 'clearBufferfi': { 4: {0: true}}, // WebGL2
  497. // QueryObjects
  498. 'beginQuery': { 2: { 0: true }}, // WebGL2
  499. 'endQuery': { 1: { 0: true }}, // WebGL2
  500. 'getQuery': { 2: { 0: true, 1: true }}, // WebGL2
  501. 'getQueryParameter': { 2: { 1: true }}, // WebGL2
  502. // Sampler Objects
  503. 'samplerParameteri': { 3: { 1: true }}, // WebGL2
  504. 'samplerParameterf': { 3: { 1: true }}, // WebGL2
  505. 'getSamplerParameter': { 2: { 1: true }}, // WebGL2
  506. // Sync objects
  507. 'clientWaitSync': { 3: { 1: makeBitFieldToStringFunc(['SYNC_FLUSH_COMMANDS_BIT']) }}, // WebGL2
  508. 'fenceSync': { 2: { 0: true }}, // WebGL2
  509. 'getSyncParameter': { 2: { 1: true }}, // WebGL2
  510. // Transform Feedback
  511. 'bindTransformFeedback': { 2: { 0: true }}, // WebGL2
  512. 'beginTransformFeedback': { 1: { 0: true }}, // WebGL2
  513. // Uniform Buffer Objects and Transform Feedback Buffers
  514. 'bindBufferBase': { 3: { 0: true }}, // WebGL2
  515. 'bindBufferRange': { 5: { 0: true }}, // WebGL2
  516. 'getIndexedParameter': { 2: { 0: true }}, // WebGL2
  517. 'getActiveUniforms': { 3: { 2: true }}, // WebGL2
  518. 'getActiveUniformBlockParameter': { 3: { 2: true }}, // WebGL2
  519. };
  520. /**
  521. * Gets an string version of an WebGL enum.
  522. *
  523. * Example:
  524. * var str = WebGLDebugUtil.glEnumToString(ctx.getError());
  525. *
  526. * @param {number} value Value to return an enum for
  527. * @return {string} The string version of the enum.
  528. */
  529. function glEnumToString(value) {
  530. var name = glEnums[value];
  531. return (name !== undefined) ? ("gl." + name) :
  532. ("/*UNKNOWN WebGL ENUM*/ 0x" + value.toString(16) + "");
  533. }
  534. /**
  535. * Returns the string version of a WebGL argument.
  536. * Attempts to convert enum arguments to strings.
  537. * @param {string} functionName the name of the WebGL function.
  538. * @param {number} numArgs the number of arguments passed to the function.
  539. * @param {number} argumentIndx the index of the argument.
  540. * @param {*} value The value of the argument.
  541. * @return {string} The value as a string.
  542. */
  543. function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
  544. var funcInfos = glValidEnumContexts[functionName];
  545. if (funcInfos !== undefined) {
  546. var funcInfo = funcInfos[numArgs];
  547. if (funcInfo !== undefined) {
  548. var argType = funcInfo[argumentIndex];
  549. if (argType) {
  550. if (typeof argType === 'function') {
  551. return argType(value);
  552. } else {
  553. return glEnumToString(value);
  554. }
  555. }
  556. }
  557. }
  558. if (value === null) {
  559. return "null";
  560. } else if (value === undefined) {
  561. return "undefined";
  562. } else {
  563. return value.toString();
  564. }
  565. }
  566. /**
  567. * Converts the arguments of a WebGL function to a string.
  568. * Attempts to convert enum arguments to strings.
  569. *
  570. * @param {string} functionName the name of the WebGL function.
  571. * @param {number} args The arguments.
  572. * @return {string} The arguments as a string.
  573. */
  574. function glFunctionArgsToString(functionName, args) {
  575. // apparently we can't do args.join(",");
  576. var argStrs = [];
  577. var numArgs = args.length;
  578. for (var ii = 0; ii < numArgs; ++ii) {
  579. argStrs.push(glFunctionArgToString(functionName, numArgs, ii, args[ii]));
  580. }
  581. return argStrs.join(", ");
  582. }
  583. function makePropertyWrapper(wrapper, original, propertyName) {
  584. wrapper.__defineGetter__(propertyName, function() { // eslint-disable-line
  585. return original[propertyName];
  586. });
  587. // TODO(gmane): this needs to handle properties that take more than
  588. // one value?
  589. wrapper.__defineSetter__(propertyName, function(value) { // eslint-disable-line
  590. original[propertyName] = value;
  591. });
  592. }
  593. /**
  594. * Given a WebGL context returns a wrapped context that calls
  595. * gl.getError after every command and calls a function if the
  596. * result is not gl.NO_ERROR.
  597. *
  598. * @param {!WebGLRenderingContext} ctx The webgl context to
  599. * wrap.
  600. * @param {!function(err, funcName, args): void} opt_onErrorFunc
  601. * The function to call when gl.getError returns an
  602. * error. If not specified the default function calls
  603. * console.log with a message.
  604. * @param {!function(funcName, args): void} opt_onFunc The
  605. * function to call when each webgl function is called.
  606. * You can use this to log all calls for example.
  607. * @param {!WebGLRenderingContext} opt_err_ctx The webgl context
  608. * to call getError on if different than ctx.
  609. */
  610. function makeDebugContext(ctx, options) {
  611. options = options || {};
  612. var errCtx = options.errCtx || ctx;
  613. var onFunc = options.funcFunc;
  614. var sharedState = options.sharedState || {
  615. numDrawCallsRemaining: options.maxDrawCalls || -1,
  616. wrappers: {},
  617. };
  618. options.sharedState = sharedState;
  619. var errorFunc = options.errorFunc || function(err, functionName, args) {
  620. console.error("WebGL error " + glEnumToString(err) + " in " + functionName + // eslint-disable-line
  621. "(" + glFunctionArgsToString(functionName, args) + ")");
  622. };
  623. // Holds booleans for each GL error so after we get the error ourselves
  624. // we can still return it to the client app.
  625. var glErrorShadow = { };
  626. var wrapper = {};
  627. function removeChecks() {
  628. Object.keys(sharedState.wrappers).forEach(function(name) {
  629. var pair = sharedState.wrappers[name];
  630. var wrapper = pair.wrapper;
  631. var orig = pair.orig;
  632. for (var propertyName in wrapper) {
  633. if (typeof wrapper[propertyName] === 'function') {
  634. wrapper[propertyName] = orig[propertyName].bind(orig);
  635. }
  636. }
  637. });
  638. }
  639. function checkMaxDrawCalls() {
  640. if (sharedState.numDrawCallsRemaining === 0) {
  641. removeChecks();
  642. }
  643. --sharedState.numDrawCallsRemaining;
  644. }
  645. function noop() {
  646. }
  647. // Makes a function that calls a WebGL function and then calls getError.
  648. function makeErrorWrapper(ctx, functionName) {
  649. var check = functionName.substring(0, 4) === 'draw' ? checkMaxDrawCalls : noop;
  650. return function() {
  651. if (onFunc) {
  652. onFunc(functionName, arguments);
  653. }
  654. var result = ctx[functionName].apply(ctx, arguments);
  655. var err = errCtx.getError();
  656. if (err !== 0) {
  657. glErrorShadow[err] = true;
  658. errorFunc(err, functionName, arguments);
  659. }
  660. check();
  661. return result;
  662. };
  663. }
  664. function makeGetExtensionWrapper(ctx, wrapped) {
  665. return function() {
  666. var extensionName = arguments[0];
  667. var ext = sharedState.wrappers[extensionName];
  668. if (!ext) {
  669. ext = wrapped.apply(ctx, arguments);
  670. if (ext) {
  671. var origExt = ext;
  672. ext = makeDebugContext(ext, options);
  673. sharedState.wrappers[extensionName] = { wrapper: ext, orig: origExt };
  674. addEnumsForContext(origExt, extensionName);
  675. }
  676. }
  677. return ext;
  678. };
  679. }
  680. // Make a an object that has a copy of every property of the WebGL context
  681. // but wraps all functions.
  682. for (var propertyName in ctx) {
  683. if (typeof ctx[propertyName] === 'function') {
  684. if (propertyName !== 'getExtension') {
  685. wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
  686. } else {
  687. var wrapped = makeErrorWrapper(ctx, propertyName);
  688. wrapper[propertyName] = makeGetExtensionWrapper(ctx, wrapped);
  689. }
  690. } else {
  691. makePropertyWrapper(wrapper, ctx, propertyName);
  692. }
  693. }
  694. // Override the getError function with one that returns our saved results.
  695. if (wrapper.getError) {
  696. wrapper.getError = function() {
  697. for (var err in glErrorShadow) {
  698. if (glErrorShadow.hasOwnProperty(err)) {
  699. if (glErrorShadow[err]) {
  700. glErrorShadow[err] = false;
  701. return err;
  702. }
  703. }
  704. }
  705. return ctx.NO_ERROR;
  706. };
  707. }
  708. if (wrapper.bindBuffer) {
  709. sharedState.wrappers["webgl"] = { wrapper: wrapper, orig: ctx };
  710. addEnumsForContext(ctx, ctx.bindBufferBase ? "WebGL2" : "WebGL");
  711. }
  712. return wrapper;
  713. }
  714. //------------
  715. function captureJSErrors() {
  716. // capture JavaScript Errors
  717. window.addEventListener('error', function(e) {
  718. var msg = e.message || e.error;
  719. var url = e.filename;
  720. var lineNo = e.lineno || 1;
  721. var colNo = e.colno || 1;
  722. var isUserScript = (url === window.location.href);
  723. if (isUserScript) {
  724. try {
  725. lineNo = window.parent.getActualLineNumberAndMoveTo(lineNo, colNo);
  726. } catch (ex) {
  727. origConsole.error(ex);
  728. }
  729. }
  730. console.error("line:", lineNo, ":", msg); // eslint-disable-line
  731. origConsole.error(e.error);
  732. });
  733. }
  734. // adapted from http://stackoverflow.com/a/2401861/128511
  735. function getBrowser() {
  736. var userAgent = navigator.userAgent;
  737. var m = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
  738. if (/trident/i.test(m[1])) {
  739. m = /\brv[ :]+(\d+)/g.exec(userAgent) || [];
  740. return {
  741. name: 'IE',
  742. version: m[1],
  743. };
  744. }
  745. if (m[1] === 'Chrome') {
  746. var temp = userAgent.match(/\b(OPR|Edge)\/(\d+)/);
  747. if (temp) {
  748. return {
  749. name: temp[1].replace('OPR', 'Opera'),
  750. version: temp[2],
  751. };
  752. }
  753. }
  754. m = m[2] ? [m[1], m[2]] : [navigator.appName, navigator.appVersion, '-?'];
  755. var version = userAgent.match(/version\/(\d+)/i);
  756. if (version) {
  757. m.splice(1, 1, version[1]);
  758. }
  759. return {
  760. name: m[0],
  761. version: m[1],
  762. };
  763. }
  764. var isWebGLRE = /^(webgl|webgl2|experimental-webgl)$/i;
  765. function installWebGLLessonSetup() {
  766. HTMLCanvasElement.prototype.getContext = (function(oldFn) {
  767. return function() {
  768. var type = arguments[0];
  769. var isWebGL = isWebGLRE.test(type);
  770. if (isWebGL) {
  771. setupLesson(this);
  772. }
  773. var ctx = oldFn.apply(this, arguments);
  774. if (!ctx && isWebGL) {
  775. showNeedWebGL(this);
  776. }
  777. return ctx;
  778. };
  779. }(HTMLCanvasElement.prototype.getContext));
  780. }
  781. function installWebGLDebugContextCreator() {
  782. // capture GL errors
  783. HTMLCanvasElement.prototype.getContext = (function(oldFn) {
  784. return function() {
  785. var ctx = oldFn.apply(this, arguments);
  786. // Using bindTexture to see if it's WebGL. Could check for instanceof WebGLRenderingContext
  787. // but that might fail if wrapped by debugging extension
  788. if (ctx && ctx.bindTexture) {
  789. ctx = makeDebugContext(ctx, {
  790. maxDrawCalls: 100,
  791. errorFunc: function(err, funcName, args) {
  792. var numArgs = args.length;
  793. var enumedArgs = [].map.call(args, function(arg, ndx) {
  794. var str = glFunctionArgToString(funcName, numArgs, ndx, arg);
  795. // shorten because of long arrays
  796. if (str.length > 200) {
  797. str = str.substring(0, 200) + "...";
  798. }
  799. return str;
  800. });
  801. var browser = getBrowser();
  802. var lineNdx;
  803. var matcher;
  804. if ((/chrome|opera/i).test(browser.name)) {
  805. lineNdx = 3;
  806. matcher = function(line) {
  807. var m = /at ([^(]+)*\(*(.*?):(\d+):(\d+)/.exec(line);
  808. if (m) {
  809. var userFnName = m[1];
  810. var url = m[2];
  811. var lineNo = parseInt(m[3]);
  812. var colNo = parseInt(m[4]);
  813. if (url === '') {
  814. url = userFnName;
  815. userFnName = '';
  816. }
  817. return {
  818. url: url,
  819. lineNo: lineNo,
  820. colNo: colNo,
  821. funcName: userFnName,
  822. };
  823. }
  824. return undefined;
  825. };
  826. } else if ((/firefox|safari/i).test(browser.name)) {
  827. lineNdx = 2;
  828. matcher = function(line) {
  829. var m = /@(.*?):(\d+):(\d+)/.exec(line);
  830. if (m) {
  831. var url = m[1];
  832. var lineNo = parseInt(m[2]);
  833. var colNo = parseInt(m[3]);
  834. return {
  835. url: url,
  836. lineNo: lineNo,
  837. colNo: colNo,
  838. };
  839. }
  840. return undefined;
  841. };
  842. }
  843. var lineInfo = '';
  844. if (matcher) {
  845. try {
  846. var error = new Error();
  847. var lines = error.stack.split("\n");
  848. // window.fooLines = lines;
  849. // lines.forEach(function(line, ndx) {
  850. // origConsole.log("#", ndx, line);
  851. // });
  852. var info = matcher(lines[lineNdx]);
  853. if (info) {
  854. var lineNo = info.lineNo;
  855. var colNo = info.colNo;
  856. var url = info.url;
  857. var isUserScript = (url === window.location.href);
  858. if (isUserScript) {
  859. lineNo = window.parent.getActualLineNumberAndMoveTo(lineNo, colNo);
  860. }
  861. lineInfo = ' line:' + lineNo + ':' + colNo;
  862. }
  863. } catch (e) {
  864. origConsole.error(e);
  865. }
  866. }
  867. console.error( // eslint-disable-line
  868. "WebGL error" + lineInfo, glEnumToString(err), "in",
  869. funcName, "(", enumedArgs.join(", "), ")");
  870. },
  871. });
  872. }
  873. return ctx;
  874. };
  875. }(HTMLCanvasElement.prototype.getContext));
  876. }
  877. installWebGLLessonSetup();
  878. if (isInEditor()) {
  879. setupConsole();
  880. captureJSErrors();
  881. if (window.threejsLessonSettings === undefined || window.threejsLessonSettings.glDebug !== false) {
  882. installWebGLDebugContextCreator();
  883. }
  884. }
  885. return {
  886. setupLesson: setupLesson,
  887. showNeedWebGL: showNeedWebGL,
  888. makeDebugContext: makeDebugContext,
  889. glFunctionArgsToString: glFunctionArgsToString,
  890. };
  891. }));