Source: WebGL-Editor-utils.js

  1. /**
  2. * @description Access to the WebGLRenderingContext interface.
  3. * @readonly
  4. * @type {WebGLRenderingContext}
  5. */
  6. var gl;
  7. /**
  8. * @description The HTMLCanvasElement.
  9. * @readonly
  10. * @type {HTMLCanvasElement}
  11. */
  12. var canvas;
  13. /**
  14. * @description Indicates if an object is currently loading or not. True when loading, otherwise false.
  15. * @readonly
  16. */
  17. var _OBJisLoading = false;
  18. /**
  19. * @description Indicates if an texture is currently loading or not. True when loading, otherwise false.
  20. * @readonly
  21. */
  22. var _TEXisLoading = false;
  23. /**
  24. * @instance controller
  25. * @description The main controller Object which handles mouse, keyboard and touch events.
  26. *
  27. * <pre>
  28. * Mouse:
  29. * Left-Mousebutton and drag | Rotation
  30. * Right-Mousebutton and drag | Translation
  31. * Mousewheel | Zoom/Object scaling
  32. *
  33. * Keyboard:
  34. * Arrow-Keys | Translation
  35. * +/- Keys | Zoom/Object scaling
  36. *
  37. * Touch-Gestures:
  38. * Touch and drag | Rotation
  39. * Double-Tap and drag | Translation
  40. * Two-finger pinch | Zoom/Object scaling
  41. *
  42. * </pre>
  43. *
  44. * @type {Object}
  45. */
  46. var controller;
  47. /*---------------Further JsDoc for the used controller. All Variables can be accessed via controller.[variableName]*/
  48. /**
  49. * @extends controller
  50. * @var controller:xRot
  51. * @description The overall amount of x-Rotation of all interactions.
  52. * @type {Number}
  53. *
  54. */
  55. /**
  56. * @extends controller
  57. * @var controller:yRot
  58. * @description The overall amount of y-Rotation of all interactions.
  59. * @type {Number}
  60. *
  61. */
  62. /**
  63. * @extends controller
  64. * @var controller:xTrans
  65. * @description The overall amount of x-Translation of all interactions.
  66. * @type {Number}
  67. *
  68. */
  69. /**
  70. * @extends controller
  71. * @var controller:yTrans
  72. * @description The overall amount of y-Translation of all interactions.
  73. * @type {Number}
  74. *
  75. */
  76. /**
  77. * @extends controller
  78. * @var controller:zTrans
  79. * @description The overall amount of z-Translation of all interactions.
  80. * @type {Number}
  81. *
  82. */
  83. /**
  84. * @extends controller
  85. * @var controller:velocity
  86. * @description Sets the sensitivity of interactions. Typical values are 1/x, where x is a number.
  87. * @type {Number}
  88. *
  89. */
  90. /**
  91. * @extends controller
  92. * @var controller:deltaXRot
  93. * @description The delta x-Rotation of the current interaction.
  94. * @type {Number}
  95. *
  96. */
  97. /**
  98. * @extends controller
  99. * @var controller:deltaYRot
  100. * @description The delta y-Rotation of the current interaction.
  101. * @type {Number}
  102. *
  103. */
  104. /**
  105. * @extends controller
  106. * @var controller:deltaXTrans
  107. * @description The delta x-Translation of the current interaction.
  108. * @type {Number}
  109. *
  110. */
  111. /**
  112. * @extends controller
  113. * @var controller:deltaYTrans
  114. * @description The delta y-Translation of the current interaction.
  115. * @type {Number}
  116. *
  117. */
  118. /**
  119. * @extends controller
  120. * @var controller:deltaZTrans
  121. * @description The delta z-Translation of the current interaction.
  122. * @type {Number}
  123. *
  124. */
  125. /**
  126. * @extends controller
  127. * @var controller:objectScale
  128. * @description The scaling factor.
  129. * @type {Number}
  130. *
  131. */
  132. //******************Creating an EDITOR module, it maintains a private internal state using the closure of the anonymous function***********************//
  133. (function (scope) {
  134. 'use strict';
  135. var EDITOR = {};
  136. scope.EDITOR = EDITOR;
  137. EDITOR._run = false;
  138. EDITOR.gl_requestId = 0;
  139. EDITOR._error = false;
  140. EDITOR._shaderError = false;
  141. EDITOR._pause = false;
  142. EDITOR.selectedTab = 0;
  143. EDITOR._pausedByMenu = false;
  144. EDITOR.initEditor = function () {
  145. canvas = document.getElementById("gl-canvas");
  146. $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', onFullScreenChange);
  147. //INITIALIZE Render-Monitor
  148. EDITOR.stats = new Stats();
  149. EDITOR.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
  150. document.getElementById("canvasBody").appendChild(EDITOR.stats.dom);
  151. //initialize the controller object
  152. controller = new Controller(canvas);
  153. //initialize the Spinner object to animate a pending download of objects and textures
  154. EDITOR._spinner = new Spinner({
  155. lines: 13 // The number of lines to draw
  156. , length: 28 // The length of each line
  157. , width: 10 // The line thickness
  158. , radius: 42 // The radius of the inner circle
  159. , scale: 1 // Scales overall size of the spinner
  160. , corners: 1 // Corner roundness (0..1)
  161. , color: '#999' // #rgb or #rrggbb or array of colors
  162. , opacity: 0.25 // Opacity of the lines
  163. , rotate: 23 // The rotation offset
  164. , direction: 1 // 1: clockwise, -1: counterclockwise
  165. , speed: 1.7 // Rounds per second
  166. , trail: 60 // Afterglow percentage
  167. , fps: 20 // Frames per second when using setTimeout() as a fallback for CSS
  168. , zIndex: 2e9 // The z-index (defaults to 2000000000)
  169. , className: 'spinner' // The CSS class to assign to the spinner
  170. , top: '49%' // Top position relative to parent
  171. , left: '50%' // Left position relative to parent
  172. , shadow: true // Whether to render a shadow
  173. , hwaccel: false // Whether to use hardware acceleration
  174. , position: 'absolute' // Element positioning
  175. });
  176. initCtx();
  177. };
  178. function initCtx() {
  179. //gl is the rendering context
  180. var ctx = WebGLUtils.setupWebGL(canvas, {preserveDrawingBuffer: true});
  181. gl = WebGLDebugUtils.makeDebugContext(ctx);
  182. gl = WebGLDebugUtils.makeDebugContext(gl, throwOnGLError, validateNoneOfTheArgsAreUndefined);
  183. if (!gl) {
  184. EDITOR.writeError("WebGL context isn't available");
  185. } else {
  186. EDITOR.writeCompletionMessage("WebGL context initialized successfully");
  187. // resizeCanvas();
  188. EDITOR.isMobile = false;
  189. // device detection
  190. if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|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|phone)|xda|xiino/i.test(navigator.userAgent)
  191. || /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(navigator.userAgent.substr(0, 4))) EDITOR.isMobile = true;
  192. if (!EDITOR.isMobile) {
  193. EDITOR.writeSystemMessage("CURRENT DEVICE: Desktop-PC");
  194. //allowing keyboard input onto canvas only for desktop devices
  195. canvas.contentEditable = true;
  196. } else {
  197. EDITOR.writeSystemMessage("CURRENT DEVICE: MOBILE");
  198. }
  199. EDITOR.writeSystemMessage("VENDOR: " + gl.getParameter(gl.VENDOR));
  200. EDITOR.writeSystemMessage("VERSION: " + gl.getParameter(gl.VERSION));
  201. }
  202. //configure WebGL
  203. gl.clearColor(0.3, 0.3, 0.3, 1);
  204. //set the viewport
  205. gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
  206. initAce();
  207. }
  208. function initAce() {
  209. EDITOR.webglEditor = ace.edit("editorWebGL");
  210. initEditor(EDITOR.webglEditor, 1);
  211. EDITOR.vsEditor = ace.edit("editorVS");
  212. initEditor(EDITOR.vsEditor, 0);
  213. EDITOR.fsEditor = ace.edit("editorFS");
  214. initEditor(EDITOR.fsEditor, 0);
  215. if (localStorage.getItem("webgl") !== null && localStorage.getItem("vs") !== null && localStorage.getItem("fs") !== null) {
  216. EDITOR.writeSystemMessage("Restoring your last known program state. Please keep in mind that the program state is lost once you clean your browsers cache!");
  217. EDITOR.webglEditor.setValue(localStorage.getItem("webgl"), -1);
  218. EDITOR.vsEditor.setValue(localStorage.getItem("vs"), -1);
  219. EDITOR.fsEditor.setValue(localStorage.getItem("fs"), -1);
  220. } else {
  221. EDITOR.setEditorTextFromFile("./src/demo/ColorizedCube/vertexShader.vert", EDITOR.vsEditor);
  222. EDITOR.setEditorTextFromFile("./src/demo/ColorizedCube/fragmentShader.frag", EDITOR.fsEditor);
  223. EDITOR.setEditorTextFromFile("./src/demo/ColorizedCube/webgl.js", EDITOR.webglEditor);
  224. }
  225. }
  226. function initEditor(editor, type) {
  227. editor.setTheme("ace/theme/chaos");
  228. editor.setOptions({
  229. enableBasicAutocompletion: true,
  230. enableLiveAutocompletion: true,
  231. enableSnippets: true,
  232. showPrintMargin: false
  233. });
  234. editor.resize();
  235. if (type === 0) {
  236. editor.getSession().setMode("ace/mode/glsl");
  237. } else {
  238. editor.getSession().setMode("ace/mode/javascript");
  239. }
  240. editor.commands.addCommand({
  241. name: 'commentLineCommand',
  242. bindKey: {win: 'Ctrl-M', mac: 'Command-M'},
  243. exec: function (e) {
  244. e.toggleCommentLines();
  245. },
  246. readOnly: false
  247. });
  248. }
  249. EDITOR.processJs = function (e) {
  250. var file = e.target.result, results;
  251. results = file.split("\n");
  252. var res = "";
  253. for (var i = 1; i < results.length; i++) {
  254. res += results[i];
  255. }
  256. EDITOR.webglEditor.setValue(res,-1);
  257. };
  258. EDITOR.processVert = function (e) {
  259. var file = e.target.result, results;
  260. results = file.split("\n");
  261. var res = "";
  262. for (var i = 1; i < results.length; i++) {
  263. res += results[i];
  264. }
  265. EDITOR.vsEditor.setValue(res,-1);
  266. };
  267. EDITOR.processFrag = function (e) {
  268. var file = e.target.result, results;
  269. results = file.split("\n");
  270. var res = "";
  271. for (var i = 1; i < results.length; i++) {
  272. res += results[i];
  273. }
  274. EDITOR.fsEditor.setValue(res,-1);
  275. };
  276. /**
  277. * Requests file contents and writes them into editor.
  278. *
  279. * @param {String} fileName The source file
  280. * @param {(EDITOR.webglEditor|EDITOR.vsEditor|EDITOR.fsEditor)} editor The target editor
  281. */
  282. EDITOR.setEditorTextFromFile = function (fileName, editor) {
  283. var request = new XMLHttpRequest();
  284. try {
  285. request.responseType = 'text';
  286. } catch (e) {
  287. }
  288. request.open("GET", fileName, true);
  289. request.onreadystatechange = function () {
  290. if (request.readyState === 4) {
  291. editor.setValue(request.responseText, -1);
  292. }
  293. };
  294. request.send();
  295. };
  296. EDITOR.fullscreen = function () {
  297. // go fullscreen, depending on Browser-Vendor
  298. if (canvas.requestFullscreen) {
  299. canvas.requestFullscreen();
  300. } else if (canvas.webkitRequestFullscreen) {
  301. canvas.webkitRequestFullscreen();
  302. } else if (canvas.mozRequestFullScreen) {
  303. canvas.mozRequestFullScreen();
  304. } else if (canvas.msRequestFullscreen) {
  305. canvas.msRequestFullscreen();
  306. }
  307. };
  308. function onFullScreenChange() {
  309. var fullscreenElement = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement;
  310. // if in fullscreen mode fullscreenElement won't be null
  311. if (fullscreenElement !== undefined && fullscreenElement !== null) {
  312. var deviceWidth = $(window).width();
  313. var deviceHeight = $(window).height();
  314. gl.canvas.width = deviceWidth;
  315. gl.canvas.height = deviceHeight;
  316. gl.viewport(0, 0, deviceWidth, deviceHeight);
  317. if (EDITOR._run && !EDITOR._pause)
  318. EDITOR.compileRun();
  319. }
  320. else {
  321. EDITOR.resizeCanvas();
  322. if (EDITOR._run && !EDITOR._pause)
  323. EDITOR.compileRun();
  324. }
  325. }
  326. EDITOR.resizeCanvas = function () {
  327. // Lookup the size the browser is displaying the canvas.
  328. var rect = canvas.getBoundingClientRect();
  329. var displayWidth = rect.width;
  330. var displayHeight = rect.height;
  331. // Make the canvas the same size
  332. canvas.width = displayWidth;
  333. canvas.height = displayHeight;
  334. gl.viewport(0, 0, displayWidth, displayHeight);
  335. $('connection').connections('update');
  336. };
  337. //-------------------------------------------------------ERROR HANDLING-----------------------------------------------------------//
  338. window.onerror = function (msg, url, line, col, error) {
  339. // Note that col & error are new to the HTML 5 spec and may not be
  340. // supported in every browser. It worked for me in Chrome.
  341. var extra = !col ? '' : '\ncolumn: ' + col;
  342. extra += !error ? '' : '\nerror: ' + error;
  343. EDITOR.writeError(msg);
  344. if (msg.indexOf("render") === -1)
  345. EDITOR.writeError("Line: " + line + " " + extra);
  346. //Report this error in order to keep track of what pages have JS issues
  347. if (url !== '' && msg.indexOf("render") === -1)
  348. EDITOR.writeError("URL: " + url);
  349. var suppressErrorAlert = true;
  350. // If you return true, then error alerts (like in older versions of
  351. // Internet Explorer) will be suppressed.
  352. return suppressErrorAlert;
  353. };
  354. function validateNoneOfTheArgsAreUndefined(functionName, args) {
  355. for (var ii = 0; ii < args.length; ++ii) {
  356. if (args[ii] === undefined) {
  357. EDITOR.writeError("Undefined argument passed to gl." + functionName + "(" +
  358. WebGLDebugUtils.glFunctionArgsToString(functionName, args) + ")");
  359. }
  360. }
  361. }
  362. function throwOnGLError(err, funcName, args) {
  363. var errorMsg;
  364. var error = WebGLDebugUtils.glEnumToString(err);
  365. switch (error) {
  366. case "gl.INVALID_OPERATION":
  367. errorMsg = "INVALID_OPERATION: The specified command is not allowed for the current state";
  368. break;
  369. case "gl.INVALID_ENUM":
  370. errorMsg = "INVALID_ENUM: An unacceptable value has been specified for an enumerated argument";
  371. break;
  372. case "gl.INVALID_VALUE":
  373. errorMsg = "INVALID_VALUE: A numeric argument is out of range";
  374. break;
  375. case "gl.INVALID_FRAMEBUFFER_OPERATION":
  376. errorMsg = "INVALID_FRAMEBUFFER_OPERATION: The currently bound framebuffer is not framebuffer complete when trying to render to or to read from it";
  377. break;
  378. case "gl.OUT_OF_MEMORY":
  379. errorMsg = "OUT_OF_MEMORY: Not enough memory is left to execute the command";
  380. break;
  381. case "gl.CONTEXT_LOST_WEBGL":
  382. errorMsg = "CONTEXT_LOST_WEBGL: WebGL context is lost";
  383. break;
  384. }
  385. EDITOR.writeError(errorMsg);
  386. }
  387. //---------------------------------------------------------------------------------------------------------------------------//
  388. EDITOR.compileRun = function () {
  389. EDITOR.cleanLog();
  390. EDITOR.cancelRun();
  391. var webGLCode = EDITOR.webglEditor.getValue();
  392. //preprocessing the webgl.js code to prevent further errors.
  393. if (webGLCode.indexOf("EDITOR") !== -1) {
  394. EDITOR.writeError("Accessing the 'EDITOR'-Module is not allowed. Execution terminated.");
  395. EDITOR._run = true;
  396. return;
  397. } /*else if (webGLCode.indexOf("render") === -1) {
  398. EDITOR.writeError("Function 'render(){...}' not defined. This function will be executed once for each frame! Execution terminated.");
  399. EDITOR._run = true;
  400. return;
  401. } */else if (webGLCode.indexOf("lookAtArcballCamera") !== -1 && webGLCode.indexOf("modelInteraction") !== -1) {
  402. EDITOR.writeError("The usage of both functions 'lookAtArcballCamera' and 'modelInteraction' at one time is not allowed. Execution terminated.");
  403. EDITOR._run = true;
  404. return;
  405. }
  406. EDITOR._run = true;
  407. // canvas.focus();
  408. var script = document.createElement("script");
  409. script.setAttribute("id", "webGLCode");
  410. var container = document.getElementById("body");
  411. var text = document.createTextNode(webGLCode);
  412. script.appendChild(text);
  413. container.appendChild(script);
  414. if (!EDITOR._pause && !EDITOR._error && !EDITOR._shaderError) {
  415. EDITOR.gl_requestId = requestAnimationFrame(EDITOR.renderLoop);
  416. }
  417. };
  418. EDITOR.cancelRun = function () {
  419. if (EDITOR._run) {
  420. cancelAnimationFrame(EDITOR.gl_requestId);
  421. EDITOR.gl_requestId = 0;
  422. EDITOR._spinner.stop();
  423. EDITOR._error = false;
  424. EDITOR._shaderError = false;
  425. _OBJisLoading = false;
  426. _TEXisLoading = false;
  427. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  428. resetControllerState();
  429. var x = document.getElementById("body");
  430. var y = document.getElementById("webGLCode");
  431. if( y !== null){
  432. x.removeChild(y);
  433. }
  434. EDITOR._run = false;
  435. WebGLDebugUtils.resetToInitialState(gl);
  436. EDITOR._pause = false;
  437. }
  438. };
  439. EDITOR.pauseRun = function () {
  440. cancelAnimationFrame(EDITOR.gl_requestId);
  441. EDITOR.gl_requestId = 0;
  442. EDITOR._pause = true;
  443. };
  444. EDITOR.unPause = function () {
  445. EDITOR._pause = false;
  446. EDITOR.gl_requestId = requestAnimationFrame(EDITOR.renderLoop);
  447. };
  448. //You should let your WebGL-rendering callback be a requestAnimationFrame callback: if you do so,
  449. // the browser will take care of the complex details of animation scheduling for you.
  450. EDITOR.renderLoop = function () {
  451. if (!_TEXisLoading && !_OBJisLoading) {
  452. EDITOR.stats.begin();
  453. render();
  454. EDITOR.stats.end();
  455. }
  456. if (!EDITOR._error && !EDITOR._shaderError) {
  457. EDITOR.gl_requestId = requestAnimationFrame(EDITOR.renderLoop);
  458. }
  459. };
  460. EDITOR.scrollLoggerToBottom = function () {
  461. $('#errorLogContainer').stop().animate({
  462. scrollTop: $('#errorLogContainer')[0].scrollHeight
  463. }, 800);
  464. };
  465. EDITOR.styleShaderError = function (errorMsg) {
  466. var pos = errorMsg.lastIndexOf("ERROR");
  467. if (pos !== -1) {
  468. var restMsg = cut(errorMsg, pos);
  469. EDITOR.styleShaderError(restMsg);
  470. }
  471. };
  472. function cut(str, cutStart) {
  473. var tmp = str.substring(cutStart, str.length);
  474. tmp = tmp.replace("ERROR", "LINE");
  475. EDITOR.writeError(tmp);
  476. return str.substring(0, cutStart);
  477. }
  478. //red
  479. EDITOR.writeError = function (message) {
  480. var container = document.getElementById("errorLogContainer");
  481. var newP = document.createElement("p");
  482. newP.style.color = "red";
  483. cancelAnimationFrame(EDITOR.gl_requestId);
  484. EDITOR.gl_requestId = 0;
  485. EDITOR._error = true;
  486. EDITOR._spinner.stop();
  487. //falls ein shadererror entsteht, entstehen folgefehler in der WebGL applikation daher mĂĽssen diese nicht gezeigt werden.
  488. if (!EDITOR._shaderError) {
  489. var newBr = document.createElement("br");
  490. var time = new Date().toLocaleTimeString();
  491. var text = document.createTextNode(time + ": " + message);
  492. newP.appendChild(text);
  493. newP.appendChild(newBr);
  494. container.appendChild(newP, container.lastChild);
  495. EDITOR.scrollLoggerToBottom();
  496. }
  497. };
  498. //yellow
  499. EDITOR.writeSystemMessage = function (message) {
  500. var container = document.getElementById("errorLogContainer");
  501. var newP = document.createElement("p");
  502. newP.style.color = "yellow";
  503. var newBr = document.createElement("br");
  504. var time = new Date().toLocaleTimeString();
  505. var text = document.createTextNode(time + ": " + message);
  506. newP.appendChild(text);
  507. newP.appendChild(newBr);
  508. container.appendChild(newP, container.lastChild);
  509. EDITOR.scrollLoggerToBottom();
  510. };
  511. //green
  512. EDITOR.writeCompletionMessage = function (message) {
  513. var container = document.getElementById("errorLogContainer");
  514. var newP = document.createElement("p");
  515. newP.style.color = "green";
  516. var newBr = document.createElement("br");
  517. var time = new Date().toLocaleTimeString();
  518. var text = document.createTextNode(time + ": " + message);
  519. newP.appendChild(text);
  520. newP.appendChild(newBr);
  521. container.appendChild(newP, container.lastChild);
  522. EDITOR.scrollLoggerToBottom();
  523. };
  524. EDITOR.cleanLog = function () {
  525. var container = document.getElementById("errorLogContainer");
  526. while (container.hasChildNodes()) {
  527. container.removeChild(container.childNodes[0]);
  528. }
  529. };
  530. window.onbeforeunload = function () {
  531. if (typeof(Storage) !== "undefined") {
  532. if (localStorage.getItem("webgl") !== null || localStorage.getItem("vs") !== null || localStorage.getItem("fs") !== null) {
  533. localStorage.removeItem("webgl");
  534. localStorage.removeItem("vs");
  535. localStorage.removeItem("fs");
  536. }
  537. //else add it
  538. localStorage.setItem("webgl", EDITOR.webglEditor.getValue());
  539. localStorage.setItem("vs", EDITOR.vsEditor.getValue());
  540. localStorage.setItem("fs", EDITOR.fsEditor.getValue());
  541. // Code for localStorage/sessionStorage.
  542. } else {
  543. EDITOR.writeSystemMessage("Your browser does not support local web storage! Changes will not be saved!");
  544. return "Do you want to leave this site?"; //Prevent Ctrl+W/leaving without saving
  545. // Sorry! No Web Storage support..
  546. }
  547. };
  548. document.onkeyup = function (e) {
  549. var e = e || window.event; // for IE to cover IEs window event-object
  550. e.preventDefault();
  551. if (e.altKey && e.which === 49) {
  552. $("#webGL").click();
  553. } else if (e.altKey && e.which === 50) {
  554. $("#vs").click();
  555. } else if (e.altKey && e.which === 51) {
  556. $("#fs").click();
  557. } else if (e.altKey && e.which === 82) {
  558. EDITOR.compileRun();
  559. }
  560. };
  561. document.onkeydown = function (e) {
  562. var e = e || window.event;
  563. if (e.ctrlKey) {
  564. var c = e.which || e.keyCode;//Get key code
  565. switch (c) {
  566. case 83://Block Ctrl+S
  567. case 9://Block Ctrl+W --Does not work in Chrome
  568. e.preventDefault();
  569. e.stopPropagation();
  570. break;
  571. }
  572. }
  573. };
  574. //reads files as string and saves it in the zip folder
  575. EDITOR.textFromFile = function (url, fileName, folder) {
  576. $.ajax({
  577. url: url,
  578. type: 'get',
  579. async: false,
  580. success: function (data) {
  581. if (fileName === 'index.html') {
  582. data = data.replace("//VSPLACEHOLDER", EDITOR.vsEditor.getValue());
  583. data = data.replace("//FSPLACEHOLDER", EDITOR.fsEditor.getValue());
  584. data = data.replace("//WEBGLPLACEHOLDER", EDITOR.webglEditor.getValue());
  585. }
  586. folder.file(fileName, data); //store the string
  587. }
  588. });
  589. };
  590. EDITOR.textFromFileRestore = function (url) {
  591. $.ajax({
  592. url: url,
  593. type: 'get',
  594. async: false,
  595. success: function (data) {
  596. }
  597. });
  598. };
  599. EDITOR.imageFromFile = function (folder, zip) {
  600. var count = EDITOR.img.length;
  601. EDITOR.img.forEach(function (fileName) {
  602. JSZipUtils.getBinaryContent('src/texture/' + fileName, function (err, data) {
  603. if (err) {
  604. writeLog("Problem happened while loading image: " + url);
  605. } else {
  606. folder.file(fileName, data, {binary: true});
  607. count--;
  608. if (count === 0) {
  609. //saving with images
  610. EDITOR.save(zip);
  611. //deferred.resolve(zip);
  612. }
  613. }
  614. });
  615. });
  616. };
  617. EDITOR.img = [];
  618. EDITOR.availableImages = ["amiga.jpg", "brick_bump.jpg", "brick_diffuse.jpg", "brick_rough.jpg",
  619. "bump.jpg", "cerberus.jpg", "circle-tex.jpg", "cloud.png", "crate.gif", "disturb.jpg", "earth.jpg",
  620. "lavatile.jpg", "metal.png", "perlin.png", "transition1.png", "transition2.png", "transition3.png", "transition4.png", "transition5.png",
  621. "transition6.png", "UV_Grid_Sm.jpg", "water.jpg", "water_normals.jpg", "vive.png"];
  622. EDITOR.findUsedImages = function () {
  623. var code = EDITOR.webglEditor.getValue();
  624. for (var i = 0; i < EDITOR.availableImages.length; i++) {
  625. if (code.indexOf(EDITOR.availableImages[i]) !== -1) {
  626. EDITOR.img.push(EDITOR.availableImages[i]);
  627. }
  628. }
  629. };
  630. EDITOR.obj = [];
  631. EDITOR.availableObjects = ["car.obj", "cerberus.obj", "character.obj", "cube.obj", "deer.obj", "dna.obj", "globe.obj", "male-Body.obj", "mill.obj",
  632. "n64.obj", "skeleton.obj", "teapot.obj", "tuna.obj", "webgl-logo.obj", "zeppelin.obj", "bunny.obj", "vive.obj"];
  633. EDITOR.findUsedObjects = function () {
  634. var code = EDITOR.webglEditor.getValue();
  635. for (var i = 0; i < EDITOR.availableObjects.length; i++) {
  636. if (code.indexOf(EDITOR.availableObjects[i]) !== -1) {
  637. EDITOR.obj.push(EDITOR.availableObjects[i]);
  638. }
  639. }
  640. };
  641. EDITOR.saveSubroutine = function (objFolder, textureFolder, zip) {
  642. EDITOR.obj.forEach(function (fileName) {
  643. var url = 'src/object/' + fileName;
  644. EDITOR.textFromFile(url, fileName, objFolder);
  645. });
  646. if (EDITOR.img.length > 0) {
  647. EDITOR.imageFromFile(textureFolder, zip);
  648. } else {
  649. //saving without images
  650. EDITOR.save(zip);
  651. }
  652. };
  653. EDITOR.save = function (zip) {
  654. if (EDITOR._run) {
  655. //saving the drawn canvas
  656. canvas.toBlobHD(function (blob) {
  657. zip.file("canvas.png", blob);
  658. writeLog("Creating zip file...");
  659. zip.generateAsync({type: "blob"}).then(function (content) {
  660. // see FileSaver.js
  661. EDITOR.writeCompletionMessage("Initializing download...");
  662. EDITOR.writeSystemMessage("File saved to the default path defined in your browser settings.");
  663. //Not possible to define the saving path with javascript, You need to change your own browser settings.
  664. // This would enter the realm of privileged code, of which website content code cannot touch.
  665. saveAs(content, "WebGL-Editor.zip");
  666. document.getElementById("save_program").disabled = false;
  667. });
  668. }, "image/png");
  669. } else {
  670. writeLog("Creating zip file...");
  671. // see FileSaver.js
  672. zip.generateAsync({type: "blob"}).then(function (content) {
  673. EDITOR.writeCompletionMessage("Initializing download...");
  674. EDITOR.writeSystemMessage("File saved to the default path defined in your browser settings.");
  675. //Not possible to define the saving path with javascript, You need to change your own browser settings.
  676. // This would enter the realm of privileged code, of which website content code cannot touch.
  677. saveAs(content, "WebGL-Editor.zip");
  678. document.getElementById("save_program").disabled = false;
  679. });
  680. }
  681. EDITOR.obj = [];
  682. EDITOR.img = [];
  683. };
  684. function resetControllerState() {
  685. controller.onchange = null;
  686. controller.xRot = 0;
  687. controller.yRot = 0;
  688. controller.curX = 0;
  689. controller.curY = 0;
  690. controller.xTrans = 0;
  691. controller.yTrans = 0;
  692. controller.zTrans = 0;
  693. controller.velocity = 1 / 100;
  694. controller.deltaXRot = 0.0;
  695. controller.deltaYRot = 0.0;
  696. controller.deltaXTrans = 0.0;
  697. controller.deltaYTrans = 0.0;
  698. controller.deltaZTrans = 0.0;
  699. controller.objectScale = 1;
  700. }
  701. })(this); //End of Anonymous Closure//
  702. //---------------------------------GLOBAL Helper functions the user can use!--------------------------------//
  703. /**
  704. * Takes in the WebGLRenderingContext and starts to compile, attach and link the Vertex- and Fragment Shader.
  705. * @param {WebGLRenderingContext} gl is the WebGLRenderingContext instance
  706. * @return {WebGLProgram} A WebGLProgram object that is a combination of two compiled WebGLShaders consisting of a vertex shader and
  707. * a fragment shader (both written in GLSL). These are then linked into a usable program.
  708. */
  709. function initShaders(gl) {
  710. var failed = 0;
  711. var vertShdr = gl.createShader(gl.VERTEX_SHADER);
  712. gl.shaderSource(vertShdr, EDITOR.vsEditor.getValue());
  713. gl.compileShader(vertShdr);
  714. if (!gl.getShaderParameter(vertShdr, gl.COMPILE_STATUS)) {
  715. EDITOR._spinner.stop();
  716. EDITOR.writeError("Vertex shader failed to compile:");
  717. EDITOR.styleShaderError(gl.getShaderInfoLog(vertShdr));
  718. failed = -1;
  719. EDITOR._shaderError = true;
  720. }
  721. var fragShdr = gl.createShader(gl.FRAGMENT_SHADER);
  722. gl.shaderSource(fragShdr, EDITOR.fsEditor.getValue());
  723. gl.compileShader(fragShdr);
  724. if (!gl.getShaderParameter(fragShdr, gl.COMPILE_STATUS)) {
  725. EDITOR._spinner.stop();
  726. EDITOR.writeError("Fragment shader failed to compile:");
  727. EDITOR.styleShaderError(gl.getShaderInfoLog(fragShdr));
  728. failed = -1;
  729. EDITOR._shaderError = true;
  730. }
  731. if (failed !== -1) {
  732. var program = gl.createProgram();
  733. gl.attachShader(program, vertShdr);
  734. gl.attachShader(program, fragShdr);
  735. gl.linkProgram(program);
  736. if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  737. EDITOR.writeError("Shader program failed to link. ");
  738. EDITOR._shaderError = true;
  739. EDITOR._spinner.stop();
  740. EDITOR.writeError(gl.getProgramInfoLog(program));
  741. return -1;
  742. }
  743. EDITOR.writeCompletionMessage("Shaders compiled successfully");
  744. }
  745. return program;
  746. }
  747. /**
  748. * @description
  749. * Returns the vertex shader code in String representation provided in the VS form.
  750. *
  751. * @return {String} String representation of the vertex shader code.
  752. */
  753. function getVertexShaderSource() {
  754. return EDITOR.vsEditor.getValue();
  755. }
  756. /**
  757. * @description
  758. * Returns the fragment shader code in String representation provided in the FS form.
  759. *
  760. * @return {String} String representation of the fragment shader code.
  761. */
  762. function getFragmentShaderSource() {
  763. return EDITOR.fsEditor.getValue();
  764. }
  765. /**
  766. * @description
  767. * Takes in a javascript array and copies it into a Float32Array.
  768. *
  769. * @param {Array} m An array in javascript representation
  770. * @return {Float32Array} The newly created Float32Array
  771. */
  772. function flatten(m) {
  773. var result = new Float32Array(m[0].length * m.length);
  774. var i, j;
  775. for (i = 0; i < m.length; i++) {
  776. for (j = 0; j < m[0].length; j++) {
  777. result[m[0].length * i + j] = m[i][j];
  778. }
  779. }
  780. return result;
  781. }
  782. /**
  783. * @description
  784. * Logging function which prints the value of any given Type to the status-window.
  785. *
  786. * @param {*} message The message to be displayed
  787. */
  788. function writeLog(message) {
  789. var container = document.getElementById("errorLogContainer");
  790. var newP = document.createElement("p");
  791. var newBr = document.createElement("br");
  792. var time = new Date().toLocaleTimeString();
  793. var text = document.createTextNode(time + ": " + message);
  794. newP.appendChild(text);
  795. newP.appendChild(newBr);
  796. container.appendChild(newP, container.lastChild);
  797. EDITOR.scrollLoggerToBottom();
  798. }
  799. /**
  800. * @description
  801. * Generates a mat4 viewing matrix and connects it with the control of an arcball camera, by using the Object literals of the controller Object.
  802. *
  803. * <pre>
  804. * Note: In order to see the changes when interacting with the canvas, the provided matrix has to be send to the vertex shader (e.g. as an uniform)
  805. * in the render-loop.
  806. * </pre>
  807. *
  808. * @param {mat4} matrix Placeholder matrix in which the viewing matrix will be stored
  809. * @param {vec3} eye The starting camera position
  810. * @param {vec3} at The starting focus point
  811. * @param {vec3} up The starting roll of the camera
  812. */
  813. function lookAtArcballCamera(matrix, eye, at, up) {
  814. var camera = createArcballCamera(eye, at, up);
  815. camera.view(matrix);
  816. controller.onchange = function () {
  817. if (!EDITOR._pause) {
  818. camera.pan([-controller.deltaXTrans / 5, -controller.deltaYTrans / 5]);
  819. camera.zoom(controller.deltaZTrans * 10);
  820. camera.rotate([-controller.deltaXRot / 2, -controller.deltaYRot / 2], [0, 0]);
  821. camera.view(matrix);
  822. }
  823. };
  824. }
  825. /**
  826. * @description
  827. * Takes in a mat4 model matrix and manipulates it by using the Object literals of the controller Object.
  828. *
  829. * <pre>
  830. * Note: In order to see the changes when interacting with the canvas, the provided matrix has to be send to the vertex shader (e.g. as an uniform)
  831. * in the render-loop.
  832. * </pre>
  833. *
  834. * @param {mat4} matrix Placeholder matrix in which the model matrix will be stored
  835. */
  836. function modelInteraction(matrix) {
  837. var xRotMatrix = mat4.create();
  838. var yRotMatrix = mat4.create();
  839. var translationMatrix = mat4.create();
  840. var scaleMatrix = mat4.create();
  841. controller.onchange = function () {
  842. if (!EDITOR._pause) {
  843. //Clamp the x-Rotation (do not flip the model)
  844. /* if (controller.xRot >= Math.PI / 2) {
  845. controller.xRot = Math.PI / 2
  846. } else if (controller.xRot <= -Math.PI / 2) {
  847. controller.xRot = -Math.PI / 2;
  848. }*/
  849. //ROTATE
  850. mat4.fromYRotation(yRotMatrix, -controller.yRot);
  851. mat4.fromXRotation(xRotMatrix, -controller.xRot);
  852. mat4.multiply(matrix, xRotMatrix, yRotMatrix);
  853. //TRANSLATE
  854. mat4.fromTranslation(translationMatrix, vec3.fromValues(-controller.xTrans, controller.yTrans, 0));
  855. mat4.multiply(matrix, translationMatrix, matrix);
  856. //SCALE
  857. mat4.fromScaling(scaleMatrix, vec3.fromValues(controller.objectScale, controller.objectScale, controller.objectScale));
  858. mat4.multiply(matrix, scaleMatrix, matrix);
  859. }
  860. };
  861. }