lightgl.js 70 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250
  1. /*
  2. * lightgl.js
  3. * http://github.com/evanw/lightgl.js/
  4. *
  5. * Copyright 2011 Evan Wallace
  6. * Released under the MIT license
  7. */
  8. var GL = (function() {
  9. // src/texture.js
  10. // Provides a simple wrapper around WebGL textures that supports render-to-texture.
  11. // ### new GL.Texture(width, height[, options])
  12. //
  13. // The arguments `width` and `height` give the size of the texture in texels.
  14. // WebGL texture dimensions must be powers of two unless `filter` is set to
  15. // either `gl.NEAREST` or `gl.LINEAR` and `wrap` is set to `gl.CLAMP_TO_EDGE`
  16. // (which they are by default).
  17. //
  18. // Texture parameters can be passed in via the `options` argument.
  19. // Example usage:
  20. //
  21. // var t = new GL.Texture(256, 256, {
  22. // // Defaults to gl.LINEAR, set both at once with "filter"
  23. // magFilter: gl.NEAREST,
  24. // minFilter: gl.LINEAR,
  25. //
  26. // // Defaults to gl.CLAMP_TO_EDGE, set both at once with "wrap"
  27. // wrapS: gl.REPEAT,
  28. // wrapT: gl.REPEAT,
  29. //
  30. // format: gl.RGB, // Defaults to gl.RGBA
  31. // type: gl.FLOAT // Defaults to gl.UNSIGNED_BYTE
  32. // });
  33. function Texture(width, height, options) {
  34. options = options || {};
  35. this.id = gl.createTexture();
  36. this.width = width;
  37. this.height = height;
  38. this.format = options.format || gl.RGBA;
  39. this.type = options.type || gl.UNSIGNED_BYTE;
  40. gl.bindTexture(gl.TEXTURE_2D, this.id);
  41. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
  42. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, options.filter || options.magFilter || gl.LINEAR);
  43. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, options.filter || options.minFilter || gl.LINEAR);
  44. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options.wrap || options.wrapS || gl.CLAMP_TO_EDGE);
  45. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options.wrap || options.wrapT || gl.CLAMP_TO_EDGE);
  46. gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, null);
  47. }
  48. var framebuffer;
  49. var renderbuffer;
  50. var checkerboardCanvas;
  51. Texture.prototype = {
  52. // ### .bind([unit])
  53. //
  54. // Bind this texture to the given texture unit (0-7, defaults to 0).
  55. bind: function(unit) {
  56. gl.activeTexture(gl.TEXTURE0 + (unit || 0));
  57. gl.bindTexture(gl.TEXTURE_2D, this.id);
  58. },
  59. // ### .unbind([unit])
  60. //
  61. // Clear the given texture unit (0-7, defaults to 0).
  62. unbind: function(unit) {
  63. gl.activeTexture(gl.TEXTURE0 + (unit || 0));
  64. gl.bindTexture(gl.TEXTURE_2D, null);
  65. },
  66. // ### .drawTo(callback[, options])
  67. //
  68. // Render all draw calls in `callback` to this texture. This method
  69. // sets up a framebuffer with this texture as the color attachment
  70. // and a renderbuffer as the depth attachment. The viewport is
  71. // temporarily changed to the size of the texture.
  72. //
  73. // The depth buffer can be omitted via `options` as shown in the
  74. // example below:
  75. //
  76. // texture.drawTo(function() {
  77. // gl.clearColor(1, 0, 0, 1);
  78. // gl.clear(gl.COLOR_BUFFER_BIT);
  79. // }, { depth: false });
  80. drawTo: function(callback, options) {
  81. options = options || {};
  82. var v = gl.getParameter(gl.VIEWPORT);
  83. gl.viewport(0, 0, this.width, this.height);
  84. framebuffer = framebuffer || gl.createFramebuffer();
  85. gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
  86. gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.id, 0);
  87. if(options.depth !== false) {
  88. renderbuffer = renderbuffer || gl.createRenderbuffer();
  89. gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
  90. if(this.width != renderbuffer.width || this.height != renderbuffer.height) {
  91. renderbuffer.width = this.width;
  92. renderbuffer.height = this.height;
  93. gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height);
  94. }
  95. gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);
  96. }
  97. callback();
  98. gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  99. gl.bindRenderbuffer(gl.RENDERBUFFER, null);
  100. gl.viewport(v[0], v[1], v[2], v[3]);
  101. },
  102. // ### .swapWith(other)
  103. //
  104. // Switch this texture with `other`, useful for the ping-pong rendering
  105. // technique used in multi-stage rendering.
  106. swapWith: function(other) {
  107. var temp;
  108. temp = other.id;
  109. other.id = this.id;
  110. this.id = temp;
  111. temp = other.width;
  112. other.width = this.width;
  113. this.width = temp;
  114. temp = other.height;
  115. other.height = this.height;
  116. this.height = temp;
  117. }
  118. };
  119. // ### GL.Texture.fromImage(image[, options])
  120. //
  121. // Return a new image created from `image`, an `<img>` tag.
  122. Texture.fromImage = function(image, options) {
  123. options = options || {};
  124. var texture = new Texture(image.width, image.height, options);
  125. try {
  126. gl.texImage2D(gl.TEXTURE_2D, 0, texture.format, texture.format, texture.type, image);
  127. } catch(e) {
  128. if(window.location.protocol == 'file:') {
  129. throw 'image not loaded for security reasons (serve this page over "http://" instead)';
  130. } else {
  131. throw 'image not loaded for security reasons (image must originate from the same ' +
  132. 'domain as this page or use Cross-Origin Resource Sharing)';
  133. }
  134. }
  135. if(options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR) {
  136. gl.generateMipmap(gl.TEXTURE_2D);
  137. }
  138. return texture;
  139. };
  140. // ### GL.Texture.fromURL(url[, options])
  141. //
  142. // Returns a checkerboard texture that will switch to the correct texture when
  143. // it loads.
  144. Texture.fromURL = function(url, options) {
  145. checkerboardCanvas = checkerboardCanvas || (function() {
  146. var c = document.createElement('canvas').getContext('2d');
  147. c.canvas.width = c.canvas.height = 128;
  148. for(var y = 0; y < c.canvas.height; y += 16) {
  149. for(var x = 0; x < c.canvas.width; x += 16) {
  150. c.fillStyle = (x ^ y) & 16 ? '#FFF' : '#DDD';
  151. c.fillRect(x, y, 16, 16);
  152. }
  153. }
  154. return c.canvas;
  155. })();
  156. var texture = Texture.fromImage(checkerboardCanvas, options);
  157. var image = new Image();
  158. var context = gl;
  159. image.onload = function() {
  160. context.makeCurrent();
  161. Texture.fromImage(image, options).swapWith(texture);
  162. };
  163. image.src = url;
  164. return texture;
  165. };
  166. // src/mesh.js
  167. // Represents indexed triangle geometry with arbitrary additional attributes.
  168. // You need a shader to draw a mesh; meshes can't draw themselves.
  169. //
  170. // A mesh is a collection of `GL.Buffer` objects which are either vertex buffers
  171. // (holding per-vertex attributes) or index buffers (holding the order in which
  172. // vertices are rendered). By default, a mesh has a position vertex buffer called
  173. // `vertices` and a triangle index buffer called `triangles`. New buffers can be
  174. // added using `addVertexBuffer()` and `addIndexBuffer()`. Two strings are
  175. // required when adding a new vertex buffer, the name of the data array on the
  176. // mesh instance and the name of the GLSL attribute in the vertex shader.
  177. //
  178. // Example usage:
  179. //
  180. // var mesh = new GL.Mesh({ coords: true, lines: true });
  181. //
  182. // // Default attribute "vertices", available as "gl_Vertex" in
  183. // // the vertex shader
  184. // mesh.vertices = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]];
  185. //
  186. // // Optional attribute "coords" enabled in constructor,
  187. // // available as "gl_TexCoord" in the vertex shader
  188. // mesh.coords = [[0, 0], [1, 0], [0, 1], [1, 1]];
  189. //
  190. // // Custom attribute "weights", available as "weight" in the
  191. // // vertex shader
  192. // mesh.addVertexBuffer('weights', 'weight');
  193. // mesh.weights = [1, 0, 0, 1];
  194. //
  195. // // Default index buffer "triangles"
  196. // mesh.triangles = [[0, 1, 2], [2, 1, 3]];
  197. //
  198. // // Optional index buffer "lines" enabled in constructor
  199. // mesh.lines = [[0, 1], [0, 2], [1, 3], [2, 3]];
  200. //
  201. // // Upload provided data to GPU memory
  202. // mesh.compile();
  203. // ### new GL.Indexer()
  204. //
  205. // Generates indices into a list of unique objects from a stream of objects
  206. // that may contain duplicates. This is useful for generating compact indexed
  207. // meshes from unindexed data.
  208. function Indexer() {
  209. this.unique = [];
  210. this.indices = [];
  211. this.map = {};
  212. }
  213. Indexer.prototype = {
  214. // ### .add(v)
  215. //
  216. // Adds the object `obj` to `unique` if it hasn't already been added. Returns
  217. // the index of `obj` in `unique`.
  218. add: function(obj) {
  219. var key = JSON.stringify(obj);
  220. if(!(key in this.map)) {
  221. this.map[key] = this.unique.length;
  222. this.unique.push(obj);
  223. }
  224. return this.map[key];
  225. }
  226. };
  227. // ### new GL.Buffer(target, type)
  228. //
  229. // Provides a simple method of uploading data to a GPU buffer. Example usage:
  230. //
  231. // var vertices = new GL.Buffer(gl.ARRAY_BUFFER, Float32Array);
  232. // var indices = new GL.Buffer(gl.ELEMENT_ARRAY_BUFFER, Uint16Array);
  233. // vertices.data = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]];
  234. // indices.data = [[0, 1, 2], [2, 1, 3]];
  235. // vertices.compile();
  236. // indices.compile();
  237. //
  238. function Buffer(target, type) {
  239. this.buffer = null;
  240. this.target = target;
  241. this.type = type;
  242. this.data = [];
  243. }
  244. Buffer.prototype = {
  245. // ### .compile(type)
  246. //
  247. // Upload the contents of `data` to the GPU in preparation for rendering. The
  248. // data must be a list of lists where each inner list has the same length. For
  249. // example, each element of data for vertex normals would be a list of length three.
  250. // This will remember the data length and element length for later use by shaders.
  251. // The type can be either `gl.STATIC_DRAW` or `gl.DYNAMIC_DRAW`, and defaults to
  252. // `gl.STATIC_DRAW`.
  253. //
  254. // This could have used `[].concat.apply([], this.data)` to flatten
  255. // the array but Google Chrome has a maximum number of arguments so the
  256. // concatenations are chunked to avoid that limit.
  257. compile: function(type) {
  258. var data = [];
  259. for(var i = 0, chunk = 10000; i < this.data.length; i += chunk) {
  260. data = Array.prototype.concat.apply(data, this.data.slice(i, i + chunk));
  261. }
  262. var spacing = this.data.length ? data.length / this.data.length : 0;
  263. if(spacing != Math.round(spacing)) throw 'buffer elements not of consistent size, average size is ' + spacing;
  264. this.buffer = this.buffer || gl.createBuffer();
  265. this.buffer.length = data.length;
  266. this.buffer.spacing = spacing;
  267. gl.bindBuffer(this.target, this.buffer);
  268. gl.bufferData(this.target, new this.type(data), type || gl.STATIC_DRAW);
  269. }
  270. };
  271. // ### new GL.Mesh([options])
  272. //
  273. // Represents a collection of vertex buffers and index buffers. Each vertex
  274. // buffer maps to one attribute in GLSL and has a corresponding property set
  275. // on the Mesh instance. There is one vertex buffer by default: `vertices`,
  276. // which maps to `gl_Vertex`. The `coords`, `normals`, and `colors` vertex
  277. // buffers map to `gl_TexCoord`, `gl_Normal`, and `gl_Color` respectively,
  278. // and can be enabled by setting the corresponding options to true. There are
  279. // two index buffers, `triangles` and `lines`, which are used for rendering
  280. // `gl.TRIANGLES` and `gl.LINES`, respectively. Only `triangles` is enabled by
  281. // default, although `computeWireframe()` will add a normal buffer if it wasn't
  282. // initially enabled.
  283. function Mesh(options) {
  284. options = options || {};
  285. this.vertexBuffers = {};
  286. this.indexBuffers = {};
  287. this.addVertexBuffer('vertices', 'gl_Vertex');
  288. if(options.coords) this.addVertexBuffer('coords', 'gl_TexCoord');
  289. if(options.normals) this.addVertexBuffer('normals', 'gl_Normal');
  290. if(options.colors) this.addVertexBuffer('colors', 'gl_Color');
  291. if(!('triangles' in options) || options.triangles) this.addIndexBuffer('triangles');
  292. if(options.lines) this.addIndexBuffer('lines');
  293. }
  294. Mesh.prototype = {
  295. // ### .addVertexBuffer(name, attribute)
  296. //
  297. // Add a new vertex buffer with a list as a property called `name` on this object
  298. // and map it to the attribute called `attribute` in all shaders that draw this mesh.
  299. addVertexBuffer: function(name, attribute) {
  300. var buffer = this.vertexBuffers[attribute] = new Buffer(gl.ARRAY_BUFFER, Float32Array);
  301. buffer.name = name;
  302. this[name] = [];
  303. },
  304. // ### .addIndexBuffer(name)
  305. //
  306. // Add a new index buffer with a list as a property called `name` on this object.
  307. addIndexBuffer: function(name) {
  308. this.indexBuffers[name] = new Buffer(gl.ELEMENT_ARRAY_BUFFER, Uint16Array);
  309. this[name] = [];
  310. },
  311. // ### .compile()
  312. //
  313. // Upload all attached buffers to the GPU in preparation for rendering. This
  314. // doesn't need to be called every frame, only needs to be done when the data
  315. // changes.
  316. compile: function() {
  317. for(var attribute in this.vertexBuffers) {
  318. var buffer = this.vertexBuffers[attribute];
  319. buffer.data = this[buffer.name];
  320. buffer.compile();
  321. }
  322. for(var name in this.indexBuffers) {
  323. var buffer = this.indexBuffers[name];
  324. buffer.data = this[name];
  325. buffer.compile();
  326. }
  327. },
  328. // ### .transform(matrix)
  329. //
  330. // Transform all vertices by `matrix` and all normals by the inverse transpose
  331. // of `matrix`.
  332. transform: function(matrix) {
  333. this.vertices = this.vertices.map(function(v) {
  334. return matrix.transformPoint(Vector.fromArray(v)).toArray();
  335. });
  336. if(this.normals) {
  337. var invTrans = matrix.inverse().transpose();
  338. this.normals = this.normals.map(function(n) {
  339. return invTrans.transformVector(Vector.fromArray(n)).unit().toArray();
  340. });
  341. }
  342. this.compile();
  343. return this;
  344. },
  345. // ### .computeNormals()
  346. //
  347. // Computes a new normal for each vertex from the average normal of the
  348. // neighboring triangles. This means adjacent triangles must share vertices
  349. // for the resulting normals to be smooth.
  350. computeNormals: function() {
  351. if(!this.normals) this.addVertexBuffer('normals', 'gl_Normal');
  352. for(var i = 0; i < this.vertices.length; i++) {
  353. this.normals[i] = new Vector();
  354. }
  355. for(var i = 0; i < this.triangles.length; i++) {
  356. var t = this.triangles[i];
  357. var a = Vector.fromArray(this.vertices[t[0]]);
  358. var b = Vector.fromArray(this.vertices[t[1]]);
  359. var c = Vector.fromArray(this.vertices[t[2]]);
  360. var normal = b.subtract(a).cross(c.subtract(a)).unit();
  361. this.normals[t[0]] = this.normals[t[0]].add(normal);
  362. this.normals[t[1]] = this.normals[t[1]].add(normal);
  363. this.normals[t[2]] = this.normals[t[2]].add(normal);
  364. }
  365. for(var i = 0; i < this.vertices.length; i++) {
  366. this.normals[i] = this.normals[i].unit().toArray();
  367. }
  368. this.compile();
  369. return this;
  370. },
  371. // ### .computeWireframe()
  372. //
  373. // Populate the `lines` index buffer from the `triangles` index buffer.
  374. computeWireframe: function() {
  375. var indexer = new Indexer();
  376. for(var i = 0; i < this.triangles.length; i++) {
  377. var t = this.triangles[i];
  378. for(var j = 0; j < t.length; j++) {
  379. var a = t[j],
  380. b = t[(j + 1) % t.length];
  381. indexer.add([Math.min(a, b), Math.max(a, b)]);
  382. }
  383. }
  384. if(!this.lines) this.addIndexBuffer('lines');
  385. this.lines = indexer.unique;
  386. this.compile();
  387. return this;
  388. },
  389. // ### .getAABB()
  390. //
  391. // Computes the axis-aligned bounding box, which is an object whose `min` and
  392. // `max` properties contain the minimum and maximum coordinates of all vertices.
  393. getAABB: function() {
  394. var aabb = {
  395. min: new Vector(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE)
  396. };
  397. aabb.max = aabb.min.negative();
  398. for(var i = 0; i < this.vertices.length; i++) {
  399. var v = Vector.fromArray(this.vertices[i]);
  400. aabb.min = Vector.min(aabb.min, v);
  401. aabb.max = Vector.max(aabb.max, v);
  402. }
  403. return aabb;
  404. },
  405. // ### .getBoundingSphere()
  406. //
  407. // Computes a sphere that contains all vertices (not necessarily the smallest
  408. // sphere). The returned object has two properties, `center` and `radius`.
  409. getBoundingSphere: function() {
  410. var aabb = this.getAABB();
  411. var sphere = {
  412. center: aabb.min.add(aabb.max).divide(2),
  413. radius: 0
  414. };
  415. for(var i = 0; i < this.vertices.length; i++) {
  416. sphere.radius = Math.max(sphere.radius, Vector.fromArray(this.vertices[i]).subtract(sphere.center).length());
  417. }
  418. return sphere;
  419. }
  420. };
  421. // ### GL.Mesh.plane([options])
  422. //
  423. // Generates a square 2x2 mesh the xy plane centered at the origin. The
  424. // `options` argument specifies options to pass to the mesh constructor.
  425. // Additional options include `detailX` and `detailY`, which set the tesselation
  426. // in x and y, and `detail`, which sets both `detailX` and `detailY` at once.
  427. // Two triangles are generated by default.
  428. // Example usage:
  429. //
  430. // var mesh1 = GL.Mesh.plane();
  431. // var mesh2 = GL.Mesh.plane({ detail: 5 });
  432. // var mesh3 = GL.Mesh.plane({ detailX: 20, detailY: 40 });
  433. //
  434. Mesh.plane = function(options) {
  435. options = options || {};
  436. var mesh = new Mesh(options),
  437. detailX = options.detailX || options.detail || 1,
  438. detailY = options.detailY || options.detail || 1;
  439. for(var y = 0; y <= detailY; y++) {
  440. var t = y / detailY;
  441. for(var x = 0; x <= detailX; x++) {
  442. var s = x / detailX;
  443. mesh.vertices.push([2 * s - 1, 2 * t - 1, 0]);
  444. if(mesh.coords) mesh.coords.push([s, t]);
  445. if(mesh.normals) mesh.normals.push([0, 0, 1]);
  446. if(x < detailX && y < detailY) {
  447. var i = x + y * (detailX + 1);
  448. mesh.triangles.push([i, i + 1, i + detailX + 1]);
  449. mesh.triangles.push([i + detailX + 1, i + 1, i + detailX + 2]);
  450. }
  451. }
  452. }
  453. mesh.compile();
  454. return mesh;
  455. };
  456. var cubeData = [
  457. [0, 4, 2, 6, -1, 0, 0], // -x
  458. [1, 3, 5, 7, +1, 0, 0], // +x
  459. [0, 1, 4, 5, 0, -1, 0], // -y
  460. [2, 6, 3, 7, 0, +1, 0], // +y
  461. [0, 2, 1, 3, 0, 0, -1], // -z
  462. [4, 5, 6, 7, 0, 0, +1] // +z
  463. ];
  464. function pickOctant(i) {
  465. return new Vector((i & 1) * 2 - 1, (i & 2) - 1, (i & 4) / 2 - 1);
  466. }
  467. // ### GL.Mesh.cube([options])
  468. //
  469. // Generates a 2x2x2 box centered at the origin. The `options` argument
  470. // specifies options to pass to the mesh constructor.
  471. Mesh.cube = function(options) {
  472. var mesh = new Mesh(options);
  473. for(var i = 0; i < cubeData.length; i++) {
  474. var data = cubeData[i],
  475. v = i * 4;
  476. for(var j = 0; j < 4; j++) {
  477. var d = data[j];
  478. mesh.vertices.push(pickOctant(d).toArray());
  479. if(mesh.coords) mesh.coords.push([j & 1, (j & 2) / 2]);
  480. if(mesh.normals) mesh.normals.push(data.slice(4, 7));
  481. }
  482. mesh.triangles.push([v, v + 1, v + 2]);
  483. mesh.triangles.push([v + 2, v + 1, v + 3]);
  484. }
  485. mesh.compile();
  486. return mesh;
  487. };
  488. // ### GL.Mesh.sphere([options])
  489. //
  490. // Generates a geodesic sphere of radius 1. The `options` argument specifies
  491. // options to pass to the mesh constructor in addition to the `detail` option,
  492. // which controls the tesselation level. The detail is `6` by default.
  493. // Example usage:
  494. //
  495. // var mesh1 = GL.Mesh.sphere();
  496. // var mesh2 = GL.Mesh.sphere({ detail: 2 });
  497. //
  498. Mesh.sphere = function(options) {
  499. function tri(a, b, c) {
  500. return flip ? [a, c, b] : [a, b, c];
  501. }
  502. function fix(x) {
  503. return x + (x - x * x) / 2;
  504. }
  505. options = options || {};
  506. var mesh = new Mesh(options);
  507. var indexer = new Indexer(),
  508. detail = options.detail || 6;
  509. for(var octant = 0; octant < 8; octant++) {
  510. var scale = pickOctant(octant);
  511. var flip = scale.x * scale.y * scale.z > 0;
  512. var data = [];
  513. for(var i = 0; i <= detail; i++) {
  514. // Generate a row of vertices on the surface of the sphere
  515. // using barycentric coordinates.
  516. for(var j = 0; i + j <= detail; j++) {
  517. var a = i / detail;
  518. var b = j / detail;
  519. var c = (detail - i - j) / detail;
  520. var vertex = {
  521. vertex: new Vector(fix(a), fix(b), fix(c)).unit().multiply(scale).toArray()
  522. };
  523. if(mesh.coords) vertex.coord = scale.y > 0 ? [1 - a, c] : [c, 1 - a];
  524. data.push(indexer.add(vertex));
  525. }
  526. // Generate triangles from this row and the previous row.
  527. if(i > 0) {
  528. for(var j = 0; i + j <= detail; j++) {
  529. var a = (i - 1) * (detail + 1) + ((i - 1) - (i - 1) * (i - 1)) / 2 + j;
  530. var b = i * (detail + 1) + (i - i * i) / 2 + j;
  531. mesh.triangles.push(tri(data[a], data[a + 1], data[b]));
  532. if(i + j < detail) {
  533. mesh.triangles.push(tri(data[b], data[a + 1], data[b + 1]));
  534. }
  535. }
  536. }
  537. }
  538. }
  539. // Reconstruct the geometry from the indexer.
  540. mesh.vertices = indexer.unique.map(function(v) {
  541. return v.vertex;
  542. });
  543. if(mesh.coords) mesh.coords = indexer.unique.map(function(v) {
  544. return v.coord;
  545. });
  546. if(mesh.normals) mesh.normals = mesh.vertices;
  547. mesh.compile();
  548. return mesh;
  549. };
  550. // ### GL.Mesh.load(json[, options])
  551. //
  552. // Creates a mesh from the JSON generated by the `convert/convert.py` script.
  553. // Example usage:
  554. //
  555. // var data = {
  556. // vertices: [[0, 0, 0], [1, 0, 0], [0, 1, 0]],
  557. // triangles: [[0, 1, 2]]
  558. // };
  559. // var mesh = GL.Mesh.load(data);
  560. //
  561. Mesh.load = function(json, options) {
  562. options = options || {};
  563. if(!('coords' in options)) options.coords = !! json.coords;
  564. if(!('normals' in options)) options.normals = !! json.normals;
  565. if(!('colors' in options)) options.colors = !! json.colors;
  566. if(!('triangles' in options)) options.triangles = !! json.triangles;
  567. if(!('lines' in options)) options.lines = !! json.lines;
  568. var mesh = new Mesh(options);
  569. mesh.vertices = json.vertices;
  570. if(mesh.coords) mesh.coords = json.coords;
  571. if(mesh.normals) mesh.normals = json.normals;
  572. if(mesh.colors) mesh.colors = json.colors;
  573. if(mesh.triangles) mesh.triangles = json.triangles;
  574. if(mesh.lines) mesh.lines = json.lines;
  575. mesh.compile();
  576. return mesh;
  577. };
  578. // src/vector.js
  579. // Provides a simple 3D vector class. Vector operations can be done using member
  580. // functions, which return new vectors, or static functions, which reuse
  581. // existing vectors to avoid generating garbage.
  582. function Vector(x, y, z) {
  583. this.x = x || 0;
  584. this.y = y || 0;
  585. this.z = z || 0;
  586. }
  587. // ### Instance Methods
  588. // The methods `add()`, `subtract()`, `multiply()`, and `divide()` can all
  589. // take either a vector or a number as an argument.
  590. Vector.prototype = {
  591. negative: function() {
  592. return new Vector(-this.x, -this.y, -this.z);
  593. },
  594. add: function(v) {
  595. if(v instanceof Vector) return new Vector(this.x + v.x, this.y + v.y, this.z + v.z);
  596. else return new Vector(this.x + v, this.y + v, this.z + v);
  597. },
  598. subtract: function(v) {
  599. if(v instanceof Vector) return new Vector(this.x - v.x, this.y - v.y, this.z - v.z);
  600. else return new Vector(this.x - v, this.y - v, this.z - v);
  601. },
  602. multiply: function(v) {
  603. if(v instanceof Vector) return new Vector(this.x * v.x, this.y * v.y, this.z * v.z);
  604. else return new Vector(this.x * v, this.y * v, this.z * v);
  605. },
  606. divide: function(v) {
  607. if(v instanceof Vector) return new Vector(this.x / v.x, this.y / v.y, this.z / v.z);
  608. else return new Vector(this.x / v, this.y / v, this.z / v);
  609. },
  610. equals: function(v) {
  611. return this.x == v.x && this.y == v.y && this.z == v.z;
  612. },
  613. dot: function(v) {
  614. return this.x * v.x + this.y * v.y + this.z * v.z;
  615. },
  616. cross: function(v) {
  617. return new Vector(
  618. this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x);
  619. },
  620. length: function() {
  621. return Math.sqrt(this.dot(this));
  622. },
  623. unit: function() {
  624. return this.divide(this.length());
  625. },
  626. min: function() {
  627. return Math.min(Math.min(this.x, this.y), this.z);
  628. },
  629. max: function() {
  630. return Math.max(Math.max(this.x, this.y), this.z);
  631. },
  632. toAngles: function() {
  633. return {
  634. theta: Math.atan2(this.z, this.x),
  635. phi: Math.asin(this.y / this.length())
  636. };
  637. },
  638. toArray: function(n) {
  639. return [this.x, this.y, this.z].slice(0, n || 3);
  640. },
  641. clone: function() {
  642. return new Vector(this.x, this.y, this.z);
  643. },
  644. init: function(x, y, z) {
  645. this.x = x;
  646. this.y = y;
  647. this.z = z;
  648. return this;
  649. }
  650. };
  651. // ### Static Methods
  652. // `Vector.randomDirection()` returns a vector with a length of 1 and a
  653. // statistically uniform direction. `Vector.lerp()` performs linear
  654. // interpolation between two vectors.
  655. Vector.negative = function(a, b) {
  656. b.x = -a.x;
  657. b.y = -a.y;
  658. b.z = -a.z;
  659. return b;
  660. };
  661. Vector.add = function(a, b, c) {
  662. if(b instanceof Vector) {
  663. c.x = a.x + b.x;
  664. c.y = a.y + b.y;
  665. c.z = a.z + b.z;
  666. } else {
  667. c.x = a.x + b;
  668. c.y = a.y + b;
  669. c.z = a.z + b;
  670. }
  671. return c;
  672. };
  673. Vector.subtract = function(a, b, c) {
  674. if(b instanceof Vector) {
  675. c.x = a.x - b.x;
  676. c.y = a.y - b.y;
  677. c.z = a.z - b.z;
  678. } else {
  679. c.x = a.x - b;
  680. c.y = a.y - b;
  681. c.z = a.z - b;
  682. }
  683. return c;
  684. };
  685. Vector.multiply = function(a, b, c) {
  686. if(b instanceof Vector) {
  687. c.x = a.x * b.x;
  688. c.y = a.y * b.y;
  689. c.z = a.z * b.z;
  690. } else {
  691. c.x = a.x * b;
  692. c.y = a.y * b;
  693. c.z = a.z * b;
  694. }
  695. return c;
  696. };
  697. Vector.divide = function(a, b, c) {
  698. if(b instanceof Vector) {
  699. c.x = a.x / b.x;
  700. c.y = a.y / b.y;
  701. c.z = a.z / b.z;
  702. } else {
  703. c.x = a.x / b;
  704. c.y = a.y / b;
  705. c.z = a.z / b;
  706. }
  707. return c;
  708. };
  709. Vector.cross = function(a, b, c) {
  710. c.x = a.y * b.z - a.z * b.y;
  711. c.y = a.z * b.x - a.x * b.z;
  712. c.z = a.x * b.y - a.y * b.x;
  713. return c;
  714. };
  715. Vector.unit = function(a, b) {
  716. var length = a.length();
  717. b.x = a.x / length;
  718. b.y = a.y / length;
  719. b.z = a.z / length;
  720. return b;
  721. };
  722. Vector.fromAngles = function(theta, phi) {
  723. return new Vector(Math.cos(theta) * Math.cos(phi), Math.sin(phi), Math.sin(theta) * Math.cos(phi));
  724. };
  725. Vector.randomDirection = function() {
  726. return Vector.fromAngles(Math.random() * Math.PI * 2, Math.asin(Math.random() * 2 - 1));
  727. };
  728. Vector.min = function(a, b) {
  729. return new Vector(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z));
  730. };
  731. Vector.max = function(a, b) {
  732. return new Vector(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z));
  733. };
  734. Vector.lerp = function(a, b, fraction) {
  735. return b.subtract(a).multiply(fraction).add(a);
  736. };
  737. Vector.fromArray = function(a) {
  738. return new Vector(a[0], a[1], a[2]);
  739. };
  740. // src/shader.js
  741. // Provides a convenient wrapper for WebGL shaders. A few uniforms and attributes,
  742. // prefixed with `gl_`, are automatically added to all shader sources to make
  743. // simple shaders easier to write.
  744. //
  745. // Example usage:
  746. //
  747. // var shader = new GL.Shader('\
  748. // void main() {\
  749. // gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
  750. // }\
  751. // ', '\
  752. // uniform vec4 color;\
  753. // void main() {\
  754. // gl_FragColor = color;\
  755. // }\
  756. // ');
  757. //
  758. // shader.uniforms({
  759. // color: [1, 0, 0, 1]
  760. // }).draw(mesh);
  761. function regexMap(regex, text, callback) {
  762. var result;
  763. while((result = regex.exec(text)) !== null) {
  764. callback(result);
  765. }
  766. }
  767. // Non-standard names beginning with `gl_` must be mangled because they will
  768. // otherwise cause a compiler error.
  769. var LIGHTGL_PREFIX = 'LIGHTGL';
  770. // ### new GL.Shader(vertexSource, fragmentSource)
  771. //
  772. // Compiles a shader program using the provided vertex and fragment shaders.
  773. function Shader(vertexSource, fragmentSource) {
  774. // Allow passing in the id of an HTML script tag with the source
  775. function followScriptTagById(id) {
  776. var element = document.getElementById(id);
  777. return element ? element.text : id;
  778. }
  779. vertexSource = followScriptTagById(vertexSource);
  780. fragmentSource = followScriptTagById(fragmentSource);
  781. // Headers are prepended to the sources to provide some automatic functionality.
  782. var header = 'uniform mat3 gl_NormalMatrix;' +
  783. "uniform mat4 gl_ModelViewMatrix;" +
  784. "uniform mat4 gl_ProjectionMatrix;" +
  785. "uniform mat4 gl_ModelViewProjectionMatrix;" +
  786. "uniform mat4 gl_ModelViewMatrixInverse;" +
  787. "uniform mat4 gl_ProjectionMatrixInverse;" +
  788. "uniform mat4 gl_ModelViewProjectionMatrixInverse;";
  789. var vertexHeader = header +
  790. "attribute vec4 gl_Vertex;" +
  791. "attribute vec4 gl_TexCoord;" +
  792. "attribute vec3 gl_Normal;" +
  793. "attribute vec4 gl_Color;" +
  794. "vec4 ftransform() {" +
  795. " return gl_ModelViewProjectionMatrix * gl_Vertex;" +
  796. "}";
  797. var fragmentHeader = 'precision highp float;' + header;
  798. // Check for the use of built-in matrices that require expensive matrix
  799. // multiplications to compute, and record these in `usedMatrices`.
  800. var source = vertexSource + fragmentSource;
  801. var usedMatrices = {};
  802. regexMap(/\b(gl_[^;]*)\b;/g, header, function(groups) {
  803. var name = groups[1];
  804. if(source.indexOf(name) != -1) {
  805. var capitalLetters = name.replace(/[a-z_]/g, '');
  806. usedMatrices[capitalLetters] = LIGHTGL_PREFIX + name;
  807. }
  808. });
  809. if(source.indexOf('ftransform') != -1) usedMatrices.MVPM = LIGHTGL_PREFIX + 'gl_ModelViewProjectionMatrix';
  810. this.usedMatrices = usedMatrices;
  811. // The `gl_` prefix must be substituted for something else to avoid compile
  812. // errors, since it's a reserved prefix. This prefixes all reserved names with
  813. // `_`. The header is inserted after any extensions, since those must come
  814. // first.
  815. function fix(header, source) {
  816. var replaced = {};
  817. var match = /^((\s*\/\/.*\n|\s*#extension.*\n)+)\^*$/.exec(source);
  818. source = match ? match[1] + header + source.substr(match[1].length) : header + source;
  819. regexMap(/\bgl_\w+\b/g, header, function(result) {
  820. if(!(result in replaced)) {
  821. source = source.replace(new RegExp('\\b' + result + '\\b', 'g'), LIGHTGL_PREFIX + result);
  822. replaced[result] = true;
  823. }
  824. });
  825. return source;
  826. }
  827. vertexSource = fix(vertexHeader, vertexSource);
  828. fragmentSource = fix(fragmentHeader, fragmentSource);
  829. // Compile and link errors are thrown as strings.
  830. function compileSource(type, source) {
  831. var shader = gl.createShader(type);
  832. gl.shaderSource(shader, source);
  833. gl.compileShader(shader);
  834. if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
  835. throw 'compile error: ' + gl.getShaderInfoLog(shader);
  836. }
  837. return shader;
  838. }
  839. this.program = gl.createProgram();
  840. gl.attachShader(this.program, compileSource(gl.VERTEX_SHADER, vertexSource));
  841. gl.attachShader(this.program, compileSource(gl.FRAGMENT_SHADER, fragmentSource));
  842. gl.linkProgram(this.program);
  843. if(!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
  844. throw 'link error: ' + gl.getProgramInfoLog(this.program);
  845. }
  846. this.attributes = {};
  847. this.uniformLocations = {};
  848. // Sampler uniforms need to be uploaded using `gl.uniform1i()` instead of `gl.uniform1f()`.
  849. // To do this automatically, we detect and remember all uniform samplers in the source code.
  850. var isSampler = {};
  851. regexMap(/uniform\s+sampler(1D|2D|3D|Cube)\s+(\w+)\s*;/g, vertexSource + fragmentSource, function(groups) {
  852. isSampler[groups[2]] = 1;
  853. });
  854. this.isSampler = isSampler;
  855. }
  856. function isArray(obj) {
  857. var str = Object.prototype.toString.call(obj);
  858. return str == '[object Array]' || str == '[object Float32Array]';
  859. }
  860. function isNumber(obj) {
  861. var str = Object.prototype.toString.call(obj);
  862. return str == '[object Number]' || str == '[object Boolean]';
  863. }
  864. Shader.prototype = {
  865. // ### .uniforms(uniforms)
  866. //
  867. // Set a uniform for each property of `uniforms`. The correct `gl.uniform*()` method is
  868. // inferred from the value types and from the stored uniform sampler flags.
  869. uniforms: function(uniforms) {
  870. gl.useProgram(this.program);
  871. for(var name in uniforms) {
  872. var location = this.uniformLocations[name] || gl.getUniformLocation(this.program, name);
  873. if(!location) continue;
  874. this.uniformLocations[name] = location;
  875. var value = uniforms[name];
  876. if(value instanceof Vector) {
  877. value = [value.x, value.y, value.z];
  878. } else if(value instanceof Matrix) {
  879. value = value.m;
  880. }
  881. if(isArray(value)) {
  882. switch(value.length) {
  883. case 1:
  884. gl.uniform1fv(location, new Float32Array(value));
  885. break;
  886. case 2:
  887. gl.uniform2fv(location, new Float32Array(value));
  888. break;
  889. case 3:
  890. gl.uniform3fv(location, new Float32Array(value));
  891. break;
  892. case 4:
  893. gl.uniform4fv(location, new Float32Array(value));
  894. break;
  895. // Matrices are automatically transposed, since WebGL uses column-major
  896. // indices instead of row-major indices.
  897. case 9:
  898. gl.uniformMatrix3fv(location, false, new Float32Array([
  899. value[0], value[3], value[6], value[1], value[4],
  900. value[7], value[2], value[5], value[8]]));
  901. break;
  902. case 16:
  903. gl.uniformMatrix4fv(location, false, new Float32Array([
  904. value[0], value[4], value[8], value[12],
  905. value[1], value[5], value[9], value[13],
  906. value[2], value[6], value[10], value[14],
  907. value[3], value[7], value[11], value[15]]));
  908. break;
  909. default:
  910. throw 'don\'t know how to load uniform "' + name + '" of length ' + value.length;
  911. }
  912. } else if(isNumber(value)) {
  913. (this.isSampler[name] ? gl.uniform1i : gl.uniform1f).call(gl, location, value);
  914. } else {
  915. throw 'attempted to set uniform "' + name + '" to invalid value ' + value;
  916. }
  917. }
  918. return this;
  919. },
  920. // ### .draw(mesh[, mode])
  921. //
  922. // Sets all uniform matrix attributes, binds all relevant buffers, and draws the
  923. // mesh geometry as indexed triangles or indexed lines. Set `mode` to `gl.LINES`
  924. // (and either add indices to `lines` or call `computeWireframe()`) to draw the
  925. // mesh in wireframe.
  926. draw: function(mesh, mode) {
  927. this.drawBuffers(
  928. mesh.vertexBuffers,
  929. mesh.indexBuffers[mode == gl.LINES ? 'lines' : 'triangles'],
  930. arguments.length < 2 ? gl.TRIANGLES : mode
  931. );
  932. },
  933. // ### .drawBuffers(vertexBuffers, indexBuffer, mode)
  934. //
  935. // Sets all uniform matrix attributes, binds all relevant buffers, and draws the
  936. // indexed mesh geometry. The `vertexBuffers` argument is a map from attribute
  937. // names to `Buffer` objects of type `gl.ARRAY_BUFFER`, `indexBuffer` is a `Buffer`
  938. // object of type `gl.ELEMENT_ARRAY_BUFFER`, and `mode` is a WebGL primitive mode
  939. // like `gl.TRIANGLES` or `gl.LINES`. This method automatically creates and caches
  940. // vertex attribute pointers for attributes as needed.
  941. drawBuffers: function(vertexBuffers, indexBuffer, mode) {
  942. // Only construct up the built-in matrices we need for this shader.
  943. var used = this.usedMatrices;
  944. var MVM = gl.modelviewMatrix;
  945. var PM = gl.projectionMatrix;
  946. var MVMI = (used.MVMI || used.NM) ? MVM.inverse() : null;
  947. var PMI = (used.PMI) ? PM.inverse() : null;
  948. var MVPM = (used.MVPM || used.MVPMI) ? PM.multiply(MVM) : null;
  949. var matrices = {};
  950. if(used.MVM) matrices[used.MVM] = MVM;
  951. if(used.MVMI) matrices[used.MVMI] = MVMI;
  952. if(used.PM) matrices[used.PM] = PM;
  953. if(used.PMI) matrices[used.PMI] = PMI;
  954. if(used.MVPM) matrices[used.MVPM] = MVPM;
  955. if(used.MVPMI) matrices[used.MVPMI] = MVPM.inverse();
  956. if(used.NM) {
  957. var m = MVMI.m;
  958. matrices[used.NM] = [m[0], m[4], m[8], m[1], m[5], m[9], m[2], m[6], m[10]];
  959. }
  960. this.uniforms(matrices);
  961. // Create and enable attribute pointers as necessary.
  962. var length = 0;
  963. for(var attribute in vertexBuffers) {
  964. var buffer = vertexBuffers[attribute];
  965. var location = this.attributes[attribute] ||
  966. gl.getAttribLocation(
  967. this.program,
  968. attribute.replace(/^(gl_.*)$/, LIGHTGL_PREFIX + '$1')
  969. );
  970. if(location == -1 || !buffer.buffer)
  971. continue;
  972. this.attributes[attribute] = location;
  973. gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);
  974. gl.enableVertexAttribArray(location);
  975. gl.vertexAttribPointer(location, buffer.buffer.spacing, gl.FLOAT, false, 0, 0);
  976. length = buffer.buffer.length / buffer.buffer.spacing;
  977. }
  978. // Disable unused attribute pointers.
  979. for(var attribute in this.attributes) {
  980. if(!(attribute in vertexBuffers)) {
  981. gl.disableVertexAttribArray(this.attributes[attribute]);
  982. }
  983. }
  984. // Draw the geometry.
  985. if(length && (!indexBuffer || indexBuffer.buffer)) {
  986. if(indexBuffer) {
  987. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer);
  988. gl.drawElements(mode, indexBuffer.buffer.length, gl.UNSIGNED_SHORT, 0);
  989. } else {
  990. gl.drawArrays(mode, 0, length);
  991. }
  992. }
  993. return this;
  994. }
  995. };
  996. // ### GL.Shader.fromURL(vsURL, fsURL)
  997. //
  998. // Compiles a shader program using the provided vertex and fragment
  999. // shaders. The shaders are loaded synchronously from the given URLs.
  1000. //
  1001. Shader.fromURL = function(vsURL, fsURL) {
  1002. var XMLHttpRequestGet = function(uri) {
  1003. var mHttpReq = new XMLHttpRequest();
  1004. mHttpReq.open("GET", uri, false);
  1005. mHttpReq.send(null);
  1006. if(mHttpReq.status !== 200) {
  1007. throw 'could not load ' + uri;
  1008. }
  1009. return mHttpReq.responseText;
  1010. };
  1011. var vsSource = XMLHttpRequestGet(vsURL);
  1012. var fsSource = XMLHttpRequestGet(fsURL);
  1013. return new Shader(vsSource, fsSource);
  1014. };
  1015. Shader.from = function(vsURLorID, fsURLorID) {
  1016. try {
  1017. return new Shader(vsURLorID, fsURLorID);
  1018. } catch(e) {
  1019. return Shader.fromURL(vsURLorID, fsURLorID);
  1020. }
  1021. };
  1022. // src/main.js
  1023. // The internal `gl` variable holds the current WebGL context.
  1024. var gl;
  1025. var GL = {
  1026. // ### Initialization
  1027. //
  1028. // `GL.create()` creates a new WebGL context and augments it with
  1029. // more methods. Uses the HTML canvas given in 'options' or creates
  1030. // a new one if necessary. The alpha channel is disabled by default
  1031. // because it usually causes unintended transparencies in the
  1032. // canvas.
  1033. create: function(options) {
  1034. options = options || {};
  1035. var canvas = options.canvas;
  1036. if(!canvas) {
  1037. canvas = document.createElement('canvas');
  1038. canvas.width = options.width || 800;
  1039. canvas.height = options.height || 600;
  1040. }
  1041. if(!('alpha' in options)) options.alpha = false;
  1042. try {
  1043. gl = canvas.getContext('webgl', options);
  1044. } catch(e) {}
  1045. try {
  1046. gl = gl || canvas.getContext('experimental-webgl', options);
  1047. } catch(e) {}
  1048. if(!gl) throw 'WebGL not supported';
  1049. addMatrixStack();
  1050. addImmediateMode();
  1051. addEventListeners();
  1052. addOtherMethods();
  1053. return gl;
  1054. },
  1055. // `GL.keys` contains a mapping of key codes to booleans indicating whether
  1056. // that key is currently pressed.
  1057. keys: {},
  1058. // Export all external classes.
  1059. Matrix: Matrix,
  1060. Indexer: Indexer,
  1061. Buffer: Buffer,
  1062. Mesh: Mesh,
  1063. HitTest: HitTest,
  1064. Raytracer: Raytracer,
  1065. Shader: Shader,
  1066. Texture: Texture,
  1067. Vector: Vector
  1068. };
  1069. // ### Matrix stack
  1070. //
  1071. // Implement the OpenGL modelview and projection matrix stacks, along with some
  1072. // other useful GLU matrix functions.
  1073. function addMatrixStack() {
  1074. gl.MODELVIEW = ENUM | 1;
  1075. gl.PROJECTION = ENUM | 2;
  1076. var tempMatrix = new Matrix();
  1077. var resultMatrix = new Matrix();
  1078. gl.modelviewMatrix = new Matrix();
  1079. gl.projectionMatrix = new Matrix();
  1080. var modelviewStack = [];
  1081. var projectionStack = [];
  1082. var matrix, stack;
  1083. gl.matrixMode = function(mode) {
  1084. switch(mode) {
  1085. case gl.MODELVIEW:
  1086. matrix = 'modelviewMatrix';
  1087. stack = modelviewStack;
  1088. break;
  1089. case gl.PROJECTION:
  1090. matrix = 'projectionMatrix';
  1091. stack = projectionStack;
  1092. break;
  1093. default:
  1094. throw 'invalid matrix mode ' + mode;
  1095. }
  1096. };
  1097. gl.loadIdentity = function() {
  1098. Matrix.identity(gl[matrix]);
  1099. };
  1100. gl.loadMatrix = function(m) {
  1101. var from = m.m,
  1102. to = gl[matrix].m;
  1103. for(var i = 0; i < 16; i++) {
  1104. to[i] = from[i];
  1105. }
  1106. };
  1107. gl.multMatrix = function(m) {
  1108. gl.loadMatrix(Matrix.multiply(gl[matrix], m, resultMatrix));
  1109. };
  1110. gl.perspective = function(fov, aspect, near, far) {
  1111. gl.multMatrix(Matrix.perspective(fov, aspect, near, far, tempMatrix));
  1112. };
  1113. gl.frustum = function(l, r, b, t, n, f) {
  1114. gl.multMatrix(Matrix.frustum(l, r, b, t, n, f, tempMatrix));
  1115. };
  1116. gl.ortho = function(l, r, b, t, n, f) {
  1117. gl.multMatrix(Matrix.ortho(l, r, b, t, n, f, tempMatrix));
  1118. };
  1119. gl.scale = function(x, y, z) {
  1120. gl.multMatrix(Matrix.scale(x, y, z, tempMatrix));
  1121. };
  1122. gl.translate = function(x, y, z) {
  1123. gl.multMatrix(Matrix.translate(x, y, z, tempMatrix));
  1124. };
  1125. gl.rotate = function(a, x, y, z) {
  1126. gl.multMatrix(Matrix.rotate(a, x, y, z, tempMatrix));
  1127. };
  1128. gl.lookAt = function(ex, ey, ez, cx, cy, cz, ux, uy, uz) {
  1129. gl.multMatrix(Matrix.lookAt(ex, ey, ez, cx, cy, cz, ux, uy, uz, tempMatrix));
  1130. };
  1131. gl.pushMatrix = function() {
  1132. stack.push(Array.prototype.slice.call(gl[matrix].m));
  1133. };
  1134. gl.popMatrix = function() {
  1135. var m = stack.pop();
  1136. gl[matrix].m = hasFloat32Array ? new Float32Array(m) : m;
  1137. };
  1138. gl.project = function(objX, objY, objZ, modelview, projection, viewport) {
  1139. modelview = modelview || gl.modelviewMatrix;
  1140. projection = projection || gl.projectionMatrix;
  1141. viewport = viewport || gl.getParameter(gl.VIEWPORT);
  1142. var point = projection.transformPoint(modelview.transformPoint(new Vector(objX, objY, objZ)));
  1143. return new Vector(
  1144. viewport[0] + viewport[2] * (point.x * 0.5 + 0.5), viewport[1] + viewport[3] * (point.y * 0.5 + 0.5), point.z * 0.5 + 0.5);
  1145. };
  1146. gl.unProject = function(winX, winY, winZ, modelview, projection, viewport) {
  1147. modelview = modelview || gl.modelviewMatrix;
  1148. projection = projection || gl.projectionMatrix;
  1149. viewport = viewport || gl.getParameter(gl.VIEWPORT);
  1150. var point = new Vector(
  1151. (winX - viewport[0]) / viewport[2] * 2 - 1, (winY - viewport[1]) / viewport[3] * 2 - 1, winZ * 2 - 1);
  1152. return Matrix.inverse(Matrix.multiply(projection, modelview, tempMatrix), resultMatrix).transformPoint(point);
  1153. };
  1154. gl.matrixMode(gl.MODELVIEW);
  1155. }
  1156. // ### Immediate mode
  1157. //
  1158. // Provide an implementation of OpenGL's deprecated immediate mode. This is
  1159. // depricated for a reason: constantly re-specifying the geometry is a bad
  1160. // idea for performance. You should use a `GL.Mesh` instead, which specifies
  1161. // the geometry once and caches it on the graphics card. Still, nothing
  1162. // beats a quick `gl.begin(gl.POINTS); gl.vertex(1, 2, 3); gl.end();` for
  1163. // debugging. This intentionally doesn't implement fixed-function lighting
  1164. // because it's only meant for quick debugging tasks.
  1165. function addImmediateMode() {
  1166. var immediateMode = {
  1167. mesh: new Mesh({
  1168. coords: true,
  1169. colors: true,
  1170. triangles: false
  1171. }),
  1172. mode: -1,
  1173. coord: [0, 0, 0, 0],
  1174. color: [1, 1, 1, 1],
  1175. pointSize: 1,
  1176. shader: new Shader('' +
  1177. 'uniform float pointSize;' +
  1178. 'varying vec4 color;' +
  1179. 'varying vec4 coord;' +
  1180. 'void main() {' +
  1181. 'color = gl_Color;' +
  1182. 'coord = gl_TexCoord;' +
  1183. 'gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;' +
  1184. 'gl_PointSize = pointSize;' +
  1185. '}' , 'uniform sampler2D texture;' +
  1186. 'uniform float pointSize;' +
  1187. 'uniform bool useTexture;' +
  1188. 'varying vec4 color;' +
  1189. 'varying vec4 coord;' +
  1190. 'void main() {' +
  1191. 'gl_FragColor = color;' +
  1192. 'if (useTexture) gl_FragColor *= texture2D(texture, coord.xy);' +
  1193. '}')
  1194. };
  1195. gl.pointSize = function(pointSize) {
  1196. immediateMode.shader.uniforms({
  1197. pointSize: pointSize
  1198. });
  1199. };
  1200. gl.begin = function(mode) {
  1201. if(immediateMode.mode != -1) throw 'mismatched gl.begin() and gl.end() calls';
  1202. immediateMode.mode = mode;
  1203. immediateMode.mesh.colors = [];
  1204. immediateMode.mesh.coords = [];
  1205. immediateMode.mesh.vertices = [];
  1206. };
  1207. gl.color = function(r, g, b, a) {
  1208. immediateMode.color = (arguments.length == 1) ? r.toArray().concat(1) : [r, g, b, a || 1];
  1209. };
  1210. gl.texCoord = function(s, t) {
  1211. immediateMode.coord = (arguments.length == 1) ? s.toArray(2) : [s, t];
  1212. };
  1213. gl.vertex = function(x, y, z) {
  1214. immediateMode.mesh.colors.push(immediateMode.color);
  1215. immediateMode.mesh.coords.push(immediateMode.coord);
  1216. immediateMode.mesh.vertices.push(arguments.length == 1 ? x.toArray() : [x, y, z]);
  1217. };
  1218. gl.end = function() {
  1219. if(immediateMode.mode == -1) throw 'mismatched gl.begin() and gl.end() calls';
  1220. immediateMode.mesh.compile();
  1221. immediateMode.shader.uniforms({
  1222. useTexture: !! gl.getParameter(gl.TEXTURE_BINDING_2D)
  1223. }).draw(immediateMode.mesh, immediateMode.mode);
  1224. immediateMode.mode = -1;
  1225. };
  1226. }
  1227. // ### Improved mouse events
  1228. //
  1229. // This adds event listeners on the `gl.canvas` element that call
  1230. // `gl.onmousedown()`, `gl.onmousemove()`, and `gl.onmouseup()` with an
  1231. // augmented event object. The event object also has the properties `x`, `y`,
  1232. // `deltaX`, `deltaY`, and `dragging`.
  1233. function addEventListeners() {
  1234. var context = gl,
  1235. oldX = 0,
  1236. oldY = 0,
  1237. buttons = {},
  1238. hasOld = false;
  1239. var has = Object.prototype.hasOwnProperty;
  1240. function isDragging() {
  1241. for(var b in buttons) {
  1242. if(has.call(buttons, b) && buttons[b]) return true;
  1243. }
  1244. return false;
  1245. }
  1246. function augment(original) {
  1247. // Make a copy of original, a native `MouseEvent`, so we can overwrite
  1248. // WebKit's non-standard read-only `x` and `y` properties (which are just
  1249. // duplicates of `pageX` and `pageY`). We can't just use
  1250. // `Object.create(original)` because some `MouseEvent` functions must be
  1251. // called in the context of the original event object.
  1252. var e = {};
  1253. for(var name in original) {
  1254. if(typeof original[name] == 'function') {
  1255. e[name] = (function(callback) {
  1256. return function() {
  1257. callback.apply(original, arguments);
  1258. };
  1259. })(original[name]);
  1260. } else {
  1261. e[name] = original[name];
  1262. }
  1263. }
  1264. e.original = original;
  1265. e.x = e.pageX;
  1266. e.y = e.pageY;
  1267. for(var obj = gl.canvas; obj; obj = obj.offsetParent) {
  1268. e.x -= obj.offsetLeft;
  1269. e.y -= obj.offsetTop;
  1270. }
  1271. if(hasOld) {
  1272. e.deltaX = e.x - oldX;
  1273. e.deltaY = e.y - oldY;
  1274. } else {
  1275. e.deltaX = 0;
  1276. e.deltaY = 0;
  1277. hasOld = true;
  1278. }
  1279. oldX = e.x;
  1280. oldY = e.y;
  1281. e.dragging = isDragging();
  1282. e.preventDefault = function() {
  1283. e.original.preventDefault();
  1284. };
  1285. e.stopPropagation = function() {
  1286. e.original.stopPropagation();
  1287. };
  1288. return e;
  1289. }
  1290. function augmentTouchEvent(original) {
  1291. var e = {};
  1292. for(var name in original) {
  1293. if(typeof original[name] == 'function') {
  1294. e[name] = (function(callback) {
  1295. return function() {
  1296. callback.apply(original, arguments);
  1297. };
  1298. })(original[name]);
  1299. } else {
  1300. e[name] = original[name];
  1301. }
  1302. }
  1303. e.original = original;
  1304. if(e.targetTouches.length > 0) {
  1305. var touch = e.targetTouches[0];
  1306. e.x = touch.pageX;
  1307. e.y = touch.pageY;
  1308. for(var obj = gl.canvas; obj; obj = obj.offsetParent) {
  1309. e.x -= obj.offsetLeft;
  1310. e.y -= obj.offsetTop;
  1311. }
  1312. if(hasOld) {
  1313. e.deltaX = e.x - oldX;
  1314. e.deltaY = e.y - oldY;
  1315. } else {
  1316. e.deltaX = 0;
  1317. e.deltaY = 0;
  1318. hasOld = true;
  1319. }
  1320. oldX = e.x;
  1321. oldY = e.y;
  1322. e.dragging = true;
  1323. }
  1324. e.preventDefault = function() {
  1325. e.original.preventDefault();
  1326. };
  1327. e.stopPropagation = function() {
  1328. e.original.stopPropagation();
  1329. };
  1330. return e;
  1331. }
  1332. function mousedown(e) {
  1333. gl = context;
  1334. if(!isDragging()) {
  1335. // Expand the event handlers to the document to handle dragging off canvas.
  1336. on(document, 'mousemove', mousemove);
  1337. on(document, 'mouseup', mouseup);
  1338. off(gl.canvas, 'mousemove', mousemove);
  1339. off(gl.canvas, 'mouseup', mouseup);
  1340. }
  1341. buttons[e.which] = true;
  1342. e = augment(e);
  1343. if(gl.onmousedown) gl.onmousedown(e);
  1344. e.preventDefault();
  1345. }
  1346. function mousemove(e) {
  1347. gl = context;
  1348. e = augment(e);
  1349. if(gl.onmousemove) gl.onmousemove(e);
  1350. e.preventDefault();
  1351. }
  1352. function mouseup(e) {
  1353. gl = context;
  1354. buttons[e.which] = false;
  1355. if(!isDragging()) {
  1356. // Shrink the event handlers back to the canvas when dragging ends.
  1357. off(document, 'mousemove', mousemove);
  1358. off(document, 'mouseup', mouseup);
  1359. on(gl.canvas, 'mousemove', mousemove);
  1360. on(gl.canvas, 'mouseup', mouseup);
  1361. }
  1362. e = augment(e);
  1363. if(gl.onmouseup) gl.onmouseup(e);
  1364. e.preventDefault();
  1365. }
  1366. function mousewheel(e) {
  1367. gl = context;
  1368. e = augment(e);
  1369. if(gl.onmousewheel) gl.onmousewheel(e);
  1370. e.preventDefault();
  1371. }
  1372. function touchstart(e) {
  1373. resetAll();
  1374. // Expand the event handlers to the document to handle dragging off canvas.
  1375. on(document, 'touchmove', touchmove);
  1376. on(document, 'touchend', touchend);
  1377. off(gl.canvas, 'touchmove', touchmove);
  1378. off(gl.canvas, 'touchend', touchend);
  1379. gl = context;
  1380. e = augmentTouchEvent(e);
  1381. if(gl.ontouchstart) gl.ontouchstart(e);
  1382. e.preventDefault();
  1383. }
  1384. function touchmove(e) {
  1385. gl = context;
  1386. if(e.targetTouches.length === 0) {
  1387. touchend(e);
  1388. }
  1389. e = augmentTouchEvent(e);
  1390. if(gl.ontouchmove) gl.ontouchmove(e);
  1391. e.preventDefault();
  1392. }
  1393. function touchend(e) {
  1394. // Shrink the event handlers back to the canvas when dragging ends.
  1395. off(document, 'touchmove', touchmove);
  1396. off(document, 'touchend', touchend);
  1397. on(gl.canvas, 'touchmove', touchmove);
  1398. on(gl.canvas, 'touchend', touchend);
  1399. gl = context;
  1400. e = augmentTouchEvent(e);
  1401. if(gl.ontouchend) gl.ontouchend(e);
  1402. e.preventDefault();
  1403. }
  1404. function reset() {
  1405. hasOld = false;
  1406. }
  1407. function resetAll() {
  1408. buttons = {};
  1409. hasOld = false;
  1410. }
  1411. // We can keep mouse and touch events enabled at the same time,
  1412. // because Google Chrome will apparently never fire both of them.
  1413. on(gl.canvas, 'mousedown', mousedown);
  1414. on(gl.canvas, 'mousemove', mousemove);
  1415. on(gl.canvas, 'mouseup', mouseup);
  1416. on(gl.canvas, 'mousewheel', mousewheel);
  1417. on(gl.canvas, 'DOMMouseScroll', mousewheel);
  1418. on(gl.canvas, 'mouseover', reset);
  1419. on(gl.canvas, 'mouseout', reset);
  1420. on(gl.canvas, 'touchstart', touchstart);
  1421. on(gl.canvas, 'touchmove', touchmove);
  1422. on(gl.canvas, 'touchend', touchend);
  1423. on(document, 'contextmenu', resetAll);
  1424. }
  1425. // ### Automatic keyboard state
  1426. //
  1427. // The current keyboard state is stored in `GL.keys`, a map of integer key
  1428. // codes to booleans indicating whether that key is currently pressed. Certain
  1429. // keys also have named identifiers that can be used directly, such as
  1430. // `GL.keys.SPACE`. Values in `GL.keys` are initially undefined until that
  1431. // key is pressed for the first time. If you need a boolean value, you can
  1432. // cast the value to boolean by applying the not operator twice (as in
  1433. // `!!GL.keys.SPACE`).
  1434. function mapKeyCode(code) {
  1435. var named = {
  1436. 8: 'BACKSPACE',
  1437. 9: 'TAB',
  1438. 13: 'ENTER',
  1439. 16: 'SHIFT',
  1440. 27: 'ESCAPE',
  1441. 32: 'SPACE',
  1442. 37: 'LEFT',
  1443. 38: 'UP',
  1444. 39: 'RIGHT',
  1445. 40: 'DOWN'
  1446. };
  1447. return named[code] || (code >= 65 && code <= 90 ? String.fromCharCode(code) : null);
  1448. }
  1449. function on(element, name, callback) {
  1450. element.addEventListener(name, callback);
  1451. }
  1452. function off(element, name, callback) {
  1453. element.removeEventListener(name, callback);
  1454. }
  1455. on(document, 'keydown', function(e) {
  1456. if(!e.altKey && !e.ctrlKey && !e.metaKey) {
  1457. var key = mapKeyCode(e.keyCode);
  1458. if(key) GL.keys[key] = true;
  1459. GL.keys[e.keyCode] = true;
  1460. }
  1461. });
  1462. on(document, 'keyup', function(e) {
  1463. if(!e.altKey && !e.ctrlKey && !e.metaKey) {
  1464. var key = mapKeyCode(e.keyCode);
  1465. if(key) GL.keys[key] = false;
  1466. GL.keys[e.keyCode] = false;
  1467. }
  1468. });
  1469. function addOtherMethods() {
  1470. // ### Multiple contexts
  1471. //
  1472. // When using multiple contexts in one web page, `gl.makeCurrent()` must be
  1473. // called before issuing commands to a different context.
  1474. (function(context) {
  1475. gl.makeCurrent = function() {
  1476. gl = context;
  1477. };
  1478. })(gl);
  1479. // ### Animation
  1480. //
  1481. // Call `gl.animate()` to provide an animation loop that repeatedly calls
  1482. // `gl.onupdate()` and `gl.ondraw()`.
  1483. gl.animate = function() {
  1484. var post = window.requestAnimationFrame ||
  1485. window.mozRequestAnimationFrame ||
  1486. window.webkitRequestAnimationFrame ||
  1487. function(callback) {
  1488. setTimeout(callback, 1000 / 60);
  1489. };
  1490. var time = new Date().getTime();
  1491. var context = gl;
  1492. function update() {
  1493. gl = context;
  1494. var now = new Date().getTime();
  1495. if(gl.onupdate) gl.onupdate((now - time) / 1000);
  1496. if(gl.ondraw) gl.ondraw();
  1497. post(update);
  1498. time = now;
  1499. }
  1500. update();
  1501. };
  1502. // ### Fullscreen
  1503. //
  1504. // Provide an easy way to get a fullscreen app running, including an
  1505. // automatic 3D perspective projection matrix by default. This should be
  1506. // called once.
  1507. //
  1508. // Just fullscreen, no automatic camera:
  1509. //
  1510. // gl.fullscreen({ camera: false });
  1511. //
  1512. // Adjusting field of view, near plane distance, and far plane distance:
  1513. //
  1514. // gl.fullscreen({ fov: 45, near: 0.1, far: 1000 });
  1515. //
  1516. // Adding padding from the edge of the window:
  1517. //
  1518. // gl.fullscreen({ paddingLeft: 250, paddingBottom: 60 });
  1519. //
  1520. gl.fullscreen = function(options) {
  1521. options = options || {};
  1522. var top = options.paddingTop || 0;
  1523. var left = options.paddingLeft || 0;
  1524. var right = options.paddingRight || 0;
  1525. var bottom = options.paddingBottom || 0;
  1526. if(!document.body) {
  1527. throw 'document.body doesn\'t exist yet (call gl.fullscreen() from ' + 'window.onload() or from inside the <body> tag)';
  1528. }
  1529. document.body.appendChild(gl.canvas);
  1530. document.body.style.overflow = 'hidden';
  1531. gl.canvas.style.position = 'absolute';
  1532. gl.canvas.style.left = left + 'px';
  1533. gl.canvas.style.top = top + 'px';
  1534. function resize() {
  1535. gl.canvas.width = window.innerWidth - left - right;
  1536. gl.canvas.height = window.innerHeight - top - bottom;
  1537. gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  1538. if(options.camera || !('camera' in options)) {
  1539. gl.matrixMode(gl.PROJECTION);
  1540. gl.loadIdentity();
  1541. gl.perspective(options.fov || 45, gl.canvas.width / gl.canvas.height, options.near || 0.1, options.far || 1000);
  1542. gl.matrixMode(gl.MODELVIEW);
  1543. }
  1544. if(gl.onresize) gl.onresize();
  1545. if(gl.ondraw) gl.ondraw();
  1546. }
  1547. on(window, 'resize', resize);
  1548. resize();
  1549. };
  1550. }
  1551. // A value to bitwise-or with new enums to make them distinguishable from the
  1552. // standard WebGL enums.
  1553. var ENUM = 0x12340000;
  1554. // src/matrix.js
  1555. // Represents a 4x4 matrix stored in row-major order that uses Float32Arrays
  1556. // when available. Matrix operations can either be done using convenient
  1557. // methods that return a new matrix for the result or optimized methods
  1558. // that store the result in an existing matrix to avoid generating garbage.
  1559. var hasFloat32Array = (typeof Float32Array != 'undefined');
  1560. // ### new GL.Matrix([elements])
  1561. //
  1562. // This constructor takes 16 arguments in row-major order, which can be passed
  1563. // individually, as a list, or even as four lists, one for each row. If the
  1564. // arguments are omitted then the identity matrix is constructed instead.
  1565. function Matrix() {
  1566. var m = Array.prototype.concat.apply([], arguments);
  1567. if(!m.length) {
  1568. m = [
  1569. 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
  1570. }
  1571. this.m = hasFloat32Array ? new Float32Array(m) : m;
  1572. }
  1573. Matrix.prototype = {
  1574. // ### .inverse()
  1575. //
  1576. // Returns the matrix that when multiplied with this matrix results in the
  1577. // identity matrix.
  1578. inverse: function() {
  1579. return Matrix.inverse(this, new Matrix());
  1580. },
  1581. // ### .transpose()
  1582. //
  1583. // Returns this matrix, exchanging columns for rows.
  1584. transpose: function() {
  1585. return Matrix.transpose(this, new Matrix());
  1586. },
  1587. // ### .multiply(matrix)
  1588. //
  1589. // Returns the concatenation of the transforms for this matrix and `matrix`.
  1590. // This emulates the OpenGL function `glMultMatrix()`.
  1591. multiply: function(matrix) {
  1592. return Matrix.multiply(this, matrix, new Matrix());
  1593. },
  1594. // ### .transformPoint(point)
  1595. //
  1596. // Transforms the vector as a point with a w coordinate of 1. This
  1597. // means translations will have an effect, for example.
  1598. transformPoint: function(v) {
  1599. var m = this.m;
  1600. return new Vector(
  1601. m[0] * v.x + m[1] * v.y + m[2] * v.z + m[3],
  1602. m[4] * v.x + m[5] * v.y + m[6] * v.z + m[7],
  1603. m[8] * v.x + m[9] * v.y + m[10] * v.z + m[11]
  1604. ).divide(m[12] * v.x + m[13] * v.y + m[14] * v.z + m[15]);
  1605. },
  1606. // ### .transformPoint(vector)
  1607. //
  1608. // Transforms the vector as a vector with a w coordinate of 0. This
  1609. // means translations will have no effect, for example.
  1610. transformVector: function(v) {
  1611. var m = this.m;
  1612. return new Vector(
  1613. m[0] * v.x + m[1] * v.y + m[2] * v.z,
  1614. m[4] * v.x + m[5] * v.y + m[6] * v.z,
  1615. m[8] * v.x + m[9] * v.y + m[10] * v.z
  1616. );
  1617. }
  1618. };
  1619. // ### GL.Matrix.inverse(matrix[, result])
  1620. //
  1621. // Returns the matrix that when multiplied with `matrix` results in the
  1622. // identity matrix. You can optionally pass an existing matrix in `result`
  1623. // to avoid allocating a new matrix. This implementation is from the Mesa
  1624. // OpenGL function `__gluInvertMatrixd()` found in `project.c`.
  1625. Matrix.inverse = function(matrix, result) {
  1626. result = result || new Matrix();
  1627. var m = matrix.m,
  1628. r = result.m;
  1629. r[0] = m[5] * m[10] * m[15] - m[5] * m[14] * m[11] - m[6] * m[9] * m[15] + m[6] * m[13] * m[11] + m[7] * m[9] * m[14] - m[7] * m[13] * m[10];
  1630. r[1] = -m[1] * m[10] * m[15] + m[1] * m[14] * m[11] + m[2] * m[9] * m[15] - m[2] * m[13] * m[11] - m[3] * m[9] * m[14] + m[3] * m[13] * m[10];
  1631. r[2] = m[1] * m[6] * m[15] - m[1] * m[14] * m[7] - m[2] * m[5] * m[15] + m[2] * m[13] * m[7] + m[3] * m[5] * m[14] - m[3] * m[13] * m[6];
  1632. r[3] = -m[1] * m[6] * m[11] + m[1] * m[10] * m[7] + m[2] * m[5] * m[11] - m[2] * m[9] * m[7] - m[3] * m[5] * m[10] + m[3] * m[9] * m[6];
  1633. r[4] = -m[4] * m[10] * m[15] + m[4] * m[14] * m[11] + m[6] * m[8] * m[15] - m[6] * m[12] * m[11] - m[7] * m[8] * m[14] + m[7] * m[12] * m[10];
  1634. r[5] = m[0] * m[10] * m[15] - m[0] * m[14] * m[11] - m[2] * m[8] * m[15] + m[2] * m[12] * m[11] + m[3] * m[8] * m[14] - m[3] * m[12] * m[10];
  1635. r[6] = -m[0] * m[6] * m[15] + m[0] * m[14] * m[7] + m[2] * m[4] * m[15] - m[2] * m[12] * m[7] - m[3] * m[4] * m[14] + m[3] * m[12] * m[6];
  1636. r[7] = m[0] * m[6] * m[11] - m[0] * m[10] * m[7] - m[2] * m[4] * m[11] + m[2] * m[8] * m[7] + m[3] * m[4] * m[10] - m[3] * m[8] * m[6];
  1637. r[8] = m[4] * m[9] * m[15] - m[4] * m[13] * m[11] - m[5] * m[8] * m[15] + m[5] * m[12] * m[11] + m[7] * m[8] * m[13] - m[7] * m[12] * m[9];
  1638. r[9] = -m[0] * m[9] * m[15] + m[0] * m[13] * m[11] + m[1] * m[8] * m[15] - m[1] * m[12] * m[11] - m[3] * m[8] * m[13] + m[3] * m[12] * m[9];
  1639. r[10] = m[0] * m[5] * m[15] - m[0] * m[13] * m[7] - m[1] * m[4] * m[15] + m[1] * m[12] * m[7] + m[3] * m[4] * m[13] - m[3] * m[12] * m[5];
  1640. r[11] = -m[0] * m[5] * m[11] + m[0] * m[9] * m[7] + m[1] * m[4] * m[11] - m[1] * m[8] * m[7] - m[3] * m[4] * m[9] + m[3] * m[8] * m[5];
  1641. r[12] = -m[4] * m[9] * m[14] + m[4] * m[13] * m[10] + m[5] * m[8] * m[14] - m[5] * m[12] * m[10] - m[6] * m[8] * m[13] + m[6] * m[12] * m[9];
  1642. r[13] = m[0] * m[9] * m[14] - m[0] * m[13] * m[10] - m[1] * m[8] * m[14] + m[1] * m[12] * m[10] + m[2] * m[8] * m[13] - m[2] * m[12] * m[9];
  1643. r[14] = -m[0] * m[5] * m[14] + m[0] * m[13] * m[6] + m[1] * m[4] * m[14] - m[1] * m[12] * m[6] - m[2] * m[4] * m[13] + m[2] * m[12] * m[5];
  1644. r[15] = m[0] * m[5] * m[10] - m[0] * m[9] * m[6] - m[1] * m[4] * m[10] + m[1] * m[8] * m[6] + m[2] * m[4] * m[9] - m[2] * m[8] * m[5];
  1645. var det = m[0] * r[0] + m[1] * r[4] + m[2] * r[8] + m[3] * r[12];
  1646. for(var i = 0; i < 16; i++) r[i] /= det;
  1647. return result;
  1648. };
  1649. // ### GL.Matrix.transpose(matrix[, result])
  1650. //
  1651. // Returns `matrix`, exchanging columns for rows. You can optionally pass an
  1652. // existing matrix in `result` to avoid allocating a new matrix.
  1653. Matrix.transpose = function(matrix, result) {
  1654. result = result || new Matrix();
  1655. var m = matrix.m,
  1656. r = result.m;
  1657. r[0] = m[0];
  1658. r[1] = m[4];
  1659. r[2] = m[8];
  1660. r[3] = m[12];
  1661. r[4] = m[1];
  1662. r[5] = m[5];
  1663. r[6] = m[9];
  1664. r[7] = m[13];
  1665. r[8] = m[2];
  1666. r[9] = m[6];
  1667. r[10] = m[10];
  1668. r[11] = m[14];
  1669. r[12] = m[3];
  1670. r[13] = m[7];
  1671. r[14] = m[11];
  1672. r[15] = m[15];
  1673. return result;
  1674. };
  1675. // ### GL.Matrix.multiply(left, right[, result])
  1676. //
  1677. // Returns the concatenation of the transforms for `left` and `right`. You can
  1678. // optionally pass an existing matrix in `result` to avoid allocating a new
  1679. // matrix. This emulates the OpenGL function `glMultMatrix()`.
  1680. Matrix.multiply = function(left, right, result) {
  1681. result = result || new Matrix();
  1682. var a = left.m,
  1683. b = right.m,
  1684. r = result.m;
  1685. r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12];
  1686. r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13];
  1687. r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14];
  1688. r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15];
  1689. r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12];
  1690. r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13];
  1691. r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14];
  1692. r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15];
  1693. r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12];
  1694. r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13];
  1695. r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14];
  1696. r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15];
  1697. r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12];
  1698. r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13];
  1699. r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14];
  1700. r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15];
  1701. return result;
  1702. };
  1703. // ### GL.Matrix.identity([result])
  1704. //
  1705. // Returns an identity matrix. You can optionally pass an existing matrix in
  1706. // `result` to avoid allocating a new matrix. This emulates the OpenGL function
  1707. // `glLoadIdentity()`.
  1708. Matrix.identity = function(result) {
  1709. result = result || new Matrix();
  1710. var m = result.m;
  1711. m[0] = m[5] = m[10] = m[15] = 1;
  1712. m[1] = m[2] = m[3] = m[4] = m[6] = m[7] = m[8] = m[9] = m[11] = m[12] = m[13] = m[14] = 0;
  1713. return result;
  1714. };
  1715. // ### GL.Matrix.perspective(fov, aspect, near, far[, result])
  1716. //
  1717. // Returns a perspective transform matrix, which makes far away objects appear
  1718. // smaller than nearby objects. The `aspect` argument should be the width
  1719. // divided by the height of your viewport and `fov` is the top-to-bottom angle
  1720. // of the field of view in degrees. You can optionally pass an existing matrix
  1721. // in `result` to avoid allocating a new matrix. This emulates the OpenGL
  1722. // function `gluPerspective()`.
  1723. Matrix.perspective = function(fov, aspect, near, far, result) {
  1724. var y = Math.tan(fov * Math.PI / 360) * near;
  1725. var x = y * aspect;
  1726. return Matrix.frustum(-x, x, -y, y, near, far, result);
  1727. };
  1728. // ### GL.Matrix.frustum(left, right, bottom, top, near, far[, result])
  1729. //
  1730. // Sets up a viewing frustum, which is shaped like a truncated pyramid with the
  1731. // camera where the point of the pyramid would be. You can optionally pass an
  1732. // existing matrix in `result` to avoid allocating a new matrix. This emulates
  1733. // the OpenGL function `glFrustum()`.
  1734. Matrix.frustum = function(l, r, b, t, n, f, result) {
  1735. result = result || new Matrix();
  1736. var m = result.m;
  1737. m[0] = 2 * n / (r - l);
  1738. m[1] = 0;
  1739. m[2] = (r + l) / (r - l);
  1740. m[3] = 0;
  1741. m[4] = 0;
  1742. m[5] = 2 * n / (t - b);
  1743. m[6] = (t + b) / (t - b);
  1744. m[7] = 0;
  1745. m[8] = 0;
  1746. m[9] = 0;
  1747. m[10] = -(f + n) / (f - n);
  1748. m[11] = -2 * f * n / (f - n);
  1749. m[12] = 0;
  1750. m[13] = 0;
  1751. m[14] = -1;
  1752. m[15] = 0;
  1753. return result;
  1754. };
  1755. // ### GL.Matrix.ortho(left, right, bottom, top, near, far[, result])
  1756. //
  1757. // Returns an orthographic projection, in which objects are the same size no
  1758. // matter how far away or nearby they are. You can optionally pass an existing
  1759. // matrix in `result` to avoid allocating a new matrix. This emulates the OpenGL
  1760. // function `glOrtho()`.
  1761. Matrix.ortho = function(l, r, b, t, n, f, result) {
  1762. result = result || new Matrix();
  1763. var m = result.m;
  1764. m[0] = 2 / (r - l);
  1765. m[1] = 0;
  1766. m[2] = 0;
  1767. m[3] = -(r + l) / (r - l);
  1768. m[4] = 0;
  1769. m[5] = 2 / (t - b);
  1770. m[6] = 0;
  1771. m[7] = -(t + b) / (t - b);
  1772. m[8] = 0;
  1773. m[9] = 0;
  1774. m[10] = -2 / (f - n);
  1775. m[11] = -(f + n) / (f - n);
  1776. m[12] = 0;
  1777. m[13] = 0;
  1778. m[14] = 0;
  1779. m[15] = 1;
  1780. return result;
  1781. };
  1782. // ### GL.Matrix.scale(x, y, z[, result])
  1783. //
  1784. // This emulates the OpenGL function `glScale()`. You can optionally pass an
  1785. // existing matrix in `result` to avoid allocating a new matrix.
  1786. Matrix.scale = function(x, y, z, result) {
  1787. result = result || new Matrix();
  1788. var m = result.m;
  1789. m[0] = x;
  1790. m[1] = 0;
  1791. m[2] = 0;
  1792. m[3] = 0;
  1793. m[4] = 0;
  1794. m[5] = y;
  1795. m[6] = 0;
  1796. m[7] = 0;
  1797. m[8] = 0;
  1798. m[9] = 0;
  1799. m[10] = z;
  1800. m[11] = 0;
  1801. m[12] = 0;
  1802. m[13] = 0;
  1803. m[14] = 0;
  1804. m[15] = 1;
  1805. return result;
  1806. };
  1807. // ### GL.Matrix.translate(x, y, z[, result])
  1808. //
  1809. // This emulates the OpenGL function `glTranslate()`. You can optionally pass
  1810. // an existing matrix in `result` to avoid allocating a new matrix.
  1811. Matrix.translate = function(x, y, z, result) {
  1812. result = result || new Matrix();
  1813. var m = result.m;
  1814. m[0] = 1;
  1815. m[1] = 0;
  1816. m[2] = 0;
  1817. m[3] = x;
  1818. m[4] = 0;
  1819. m[5] = 1;
  1820. m[6] = 0;
  1821. m[7] = y;
  1822. m[8] = 0;
  1823. m[9] = 0;
  1824. m[10] = 1;
  1825. m[11] = z;
  1826. m[12] = 0;
  1827. m[13] = 0;
  1828. m[14] = 0;
  1829. m[15] = 1;
  1830. return result;
  1831. };
  1832. // ### GL.Matrix.rotate(a, x, y, z[, result])
  1833. //
  1834. // Returns a matrix that rotates by `a` degrees around the vector `x, y, z`.
  1835. // You can optionally pass an existing matrix in `result` to avoid allocating
  1836. // a new matrix. This emulates the OpenGL function `glRotate()`.
  1837. Matrix.rotate = function(a, x, y, z, result) {
  1838. if(!a || (!x && !y && !z)) {
  1839. return Matrix.identity(result);
  1840. }
  1841. result = result || new Matrix();
  1842. var m = result.m;
  1843. var d = Math.sqrt(x * x + y * y + z * z);
  1844. a *= Math.PI / 180;
  1845. x /= d;
  1846. y /= d;
  1847. z /= d;
  1848. var c = Math.cos(a),
  1849. s = Math.sin(a),
  1850. t = 1 - c;
  1851. m[0] = x * x * t + c;
  1852. m[1] = x * y * t - z * s;
  1853. m[2] = x * z * t + y * s;
  1854. m[3] = 0;
  1855. m[4] = y * x * t + z * s;
  1856. m[5] = y * y * t + c;
  1857. m[6] = y * z * t - x * s;
  1858. m[7] = 0;
  1859. m[8] = z * x * t - y * s;
  1860. m[9] = z * y * t + x * s;
  1861. m[10] = z * z * t + c;
  1862. m[11] = 0;
  1863. m[12] = 0;
  1864. m[13] = 0;
  1865. m[14] = 0;
  1866. m[15] = 1;
  1867. return result;
  1868. };
  1869. // ### GL.Matrix.lookAt(ex, ey, ez, cx, cy, cz, ux, uy, uz[, result])
  1870. //
  1871. // Returns a matrix that puts the camera at the eye point `ex, ey, ez` looking
  1872. // toward the center point `cx, cy, cz` with an up direction of `ux, uy, uz`.
  1873. // You can optionally pass an existing matrix in `result` to avoid allocating
  1874. // a new matrix. This emulates the OpenGL function `gluLookAt()`.
  1875. Matrix.lookAt = function(ex, ey, ez, cx, cy, cz, ux, uy, uz, result) {
  1876. result = result || new Matrix();
  1877. var m = result.m;
  1878. var e = new Vector(ex, ey, ez);
  1879. var c = new Vector(cx, cy, cz);
  1880. var u = new Vector(ux, uy, uz);
  1881. var f = e.subtract(c).unit();
  1882. var s = u.cross(f).unit();
  1883. var t = f.cross(s).unit();
  1884. m[0] = s.x;
  1885. m[1] = s.y;
  1886. m[2] = s.z;
  1887. m[3] = -s.dot(e);
  1888. m[4] = t.x;
  1889. m[5] = t.y;
  1890. m[6] = t.z;
  1891. m[7] = -t.dot(e);
  1892. m[8] = f.x;
  1893. m[9] = f.y;
  1894. m[10] = f.z;
  1895. m[11] = -f.dot(e);
  1896. m[12] = 0;
  1897. m[13] = 0;
  1898. m[14] = 0;
  1899. m[15] = 1;
  1900. return result;
  1901. };
  1902. // src/raytracer.js
  1903. // Provides a convenient raytracing interface.
  1904. // ### new GL.HitTest([t, hit, normal])
  1905. //
  1906. // This is the object used to return hit test results. If there are no
  1907. // arguments, the constructed argument represents a hit infinitely far
  1908. // away.
  1909. function HitTest(t, hit, normal) {
  1910. this.t = arguments.length ? t : Number.MAX_VALUE;
  1911. this.hit = hit;
  1912. this.normal = normal;
  1913. }
  1914. // ### .mergeWith(other)
  1915. //
  1916. // Changes this object to be the closer of the two hit test results.
  1917. HitTest.prototype = {
  1918. mergeWith: function(other) {
  1919. if(other.t > 0 && other.t < this.t) {
  1920. this.t = other.t;
  1921. this.hit = other.hit;
  1922. this.normal = other.normal;
  1923. }
  1924. }
  1925. };
  1926. // ### new GL.Raytracer()
  1927. //
  1928. // This will read the current modelview matrix, projection matrix, and viewport,
  1929. // reconstruct the eye position, and store enough information to later generate
  1930. // per-pixel rays using `getRayForPixel()`.
  1931. //
  1932. // Example usage:
  1933. //
  1934. // var tracer = new GL.Raytracer();
  1935. // var ray = tracer.getRayForPixel(
  1936. // gl.canvas.width / 2,
  1937. // gl.canvas.height / 2);
  1938. // var result = GL.Raytracer.hitTestSphere(
  1939. // tracer.eye, ray, new GL.Vector(0, 0, 0), 1);
  1940. function Raytracer() {
  1941. var v = gl.getParameter(gl.VIEWPORT);
  1942. var m = gl.modelviewMatrix.m;
  1943. var axisX = new Vector(m[0], m[4], m[8]);
  1944. var axisY = new Vector(m[1], m[5], m[9]);
  1945. var axisZ = new Vector(m[2], m[6], m[10]);
  1946. var offset = new Vector(m[3], m[7], m[11]);
  1947. this.eye = new Vector(-offset.dot(axisX), -offset.dot(axisY), -offset.dot(axisZ));
  1948. var minX = v[0],
  1949. maxX = minX + v[2];
  1950. var minY = v[1],
  1951. maxY = minY + v[3];
  1952. this.ray00 = gl.unProject(minX, minY, 1).subtract(this.eye);
  1953. this.ray10 = gl.unProject(maxX, minY, 1).subtract(this.eye);
  1954. this.ray01 = gl.unProject(minX, maxY, 1).subtract(this.eye);
  1955. this.ray11 = gl.unProject(maxX, maxY, 1).subtract(this.eye);
  1956. this.viewport = v;
  1957. }
  1958. Raytracer.prototype = {
  1959. // ### .getRayForPixel(x, y)
  1960. //
  1961. // Returns the ray originating from the camera and traveling through the pixel `x, y`.
  1962. getRayForPixel: function(x, y) {
  1963. x = (x - this.viewport[0]) / this.viewport[2];
  1964. y = 1 - (y - this.viewport[1]) / this.viewport[3];
  1965. var ray0 = Vector.lerp(this.ray00, this.ray10, x);
  1966. var ray1 = Vector.lerp(this.ray01, this.ray11, x);
  1967. return Vector.lerp(ray0, ray1, y).unit();
  1968. }
  1969. };
  1970. // ### GL.Raytracer.hitTestBox(origin, ray, min, max)
  1971. //
  1972. // Traces the ray starting from `origin` along `ray` against the axis-aligned box
  1973. // whose coordinates extend from `min` to `max`. Returns a `HitTest` with the
  1974. // information or `null` for no intersection.
  1975. //
  1976. // This implementation uses the [slab intersection method](http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter3.htm).
  1977. Raytracer.hitTestBox = function(origin, ray, min, max) {
  1978. var tMin = min.subtract(origin).divide(ray);
  1979. var tMax = max.subtract(origin).divide(ray);
  1980. var t1 = Vector.min(tMin, tMax);
  1981. var t2 = Vector.max(tMin, tMax);
  1982. var tNear = t1.max();
  1983. var tFar = t2.min();
  1984. if(tNear > 0 && tNear < tFar) {
  1985. var epsilon = 1.0e-6,
  1986. hit = origin.add(ray.multiply(tNear));
  1987. min = min.add(epsilon);
  1988. max = max.subtract(epsilon);
  1989. return new HitTest(tNear, hit, new Vector(
  1990. (hit.x > max.x) - (hit.x < min.x), (hit.y > max.y) - (hit.y < min.y), (hit.z > max.z) - (hit.z < min.z)));
  1991. }
  1992. return null;
  1993. };
  1994. // ### GL.Raytracer.hitTestSphere(origin, ray, center, radius)
  1995. //
  1996. // Traces the ray starting from `origin` along `ray` against the sphere defined
  1997. // by `center` and `radius`. Returns a `HitTest` with the information or `null`
  1998. // for no intersection.
  1999. Raytracer.hitTestSphere = function(origin, ray, center, radius) {
  2000. var offset = origin.subtract(center);
  2001. var a = ray.dot(ray);
  2002. var b = 2 * ray.dot(offset);
  2003. var c = offset.dot(offset) - radius * radius;
  2004. var discriminant = b * b - 4 * a * c;
  2005. if(discriminant > 0) {
  2006. var t = (-b - Math.sqrt(discriminant)) / (2 * a),
  2007. hit = origin.add(ray.multiply(t));
  2008. return new HitTest(t, hit, hit.subtract(center).divide(radius));
  2009. }
  2010. return null;
  2011. };
  2012. // ### GL.Raytracer.hitTestTriangle(origin, ray, a, b, c)
  2013. //
  2014. // Traces the ray starting from `origin` along `ray` against the triangle defined
  2015. // by the points `a`, `b`, and `c`. Returns a `HitTest` with the information or
  2016. // `null` for no intersection.
  2017. Raytracer.hitTestTriangle = function(origin, ray, a, b, c) {
  2018. var ab = b.subtract(a);
  2019. var ac = c.subtract(a);
  2020. var normal = ab.cross(ac).unit();
  2021. var t = normal.dot(a.subtract(origin)) / normal.dot(ray);
  2022. if(t > 0) {
  2023. var hit = origin.add(ray.multiply(t));
  2024. var toHit = hit.subtract(a);
  2025. var dot00 = ac.dot(ac);
  2026. var dot01 = ac.dot(ab);
  2027. var dot02 = ac.dot(toHit);
  2028. var dot11 = ab.dot(ab);
  2029. var dot12 = ab.dot(toHit);
  2030. var divide = dot00 * dot11 - dot01 * dot01;
  2031. var u = (dot11 * dot02 - dot01 * dot12) / divide;
  2032. var v = (dot00 * dot12 - dot01 * dot02) / divide;
  2033. if(u >= 0 && v >= 0 && u + v <= 1) return new HitTest(t, hit, normal);
  2034. }
  2035. return null;
  2036. };
  2037. return GL;
  2038. })();