matrix4x4.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. /*
  2. * Copyright (c) 2009, Mozilla Corp
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the <organization> nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY <copyright holder> ''AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. /*
  28. * Based on sample code from the OpenGL(R) ES 2.0 Programming Guide, which carriers
  29. * the following header:
  30. *
  31. * Book: OpenGL(R) ES 2.0 Programming Guide
  32. * Authors: Aaftab Munshi, Dan Ginsburg, Dave Shreiner
  33. * ISBN-10: 0321502795
  34. * ISBN-13: 9780321502797
  35. * Publisher: Addison-Wesley Professional
  36. * URLs: http://safari.informit.com/9780321563835
  37. * http://www.opengles-book.com
  38. */
  39. //
  40. // A simple 4x4 Matrix utility class
  41. //
  42. function Matrix4x4() {
  43. this.elements = Array(16);
  44. this.loadIdentity();
  45. }
  46. Matrix4x4.prototype = {
  47. scale: function (sx, sy, sz) {
  48. this.elements[0*4+0] *= sx;
  49. this.elements[0*4+1] *= sx;
  50. this.elements[0*4+2] *= sx;
  51. this.elements[0*4+3] *= sx;
  52. this.elements[1*4+0] *= sy;
  53. this.elements[1*4+1] *= sy;
  54. this.elements[1*4+2] *= sy;
  55. this.elements[1*4+3] *= sy;
  56. this.elements[2*4+0] *= sz;
  57. this.elements[2*4+1] *= sz;
  58. this.elements[2*4+2] *= sz;
  59. this.elements[2*4+3] *= sz;
  60. return this;
  61. },
  62. translate: function (tx, ty, tz) {
  63. this.elements[3*4+0] += this.elements[0*4+0] * tx + this.elements[1*4+0] * ty + this.elements[2*4+0] * tz;
  64. this.elements[3*4+1] += this.elements[0*4+1] * tx + this.elements[1*4+1] * ty + this.elements[2*4+1] * tz;
  65. this.elements[3*4+2] += this.elements[0*4+2] * tx + this.elements[1*4+2] * ty + this.elements[2*4+2] * tz;
  66. this.elements[3*4+3] += this.elements[0*4+3] * tx + this.elements[1*4+3] * ty + this.elements[2*4+3] * tz;
  67. return this;
  68. },
  69. rotate: function (angle, x, y, z) {
  70. var mag = Math.sqrt(x*x + y*y + z*z);
  71. var sinAngle = Math.sin(angle * Math.PI / 180.0);
  72. var cosAngle = Math.cos(angle * Math.PI / 180.0);
  73. if (mag > 0) {
  74. var xx, yy, zz, xy, yz, zx, xs, ys, zs;
  75. var oneMinusCos;
  76. var rotMat;
  77. x /= mag;
  78. y /= mag;
  79. z /= mag;
  80. xx = x * x;
  81. yy = y * y;
  82. zz = z * z;
  83. xy = x * y;
  84. yz = y * z;
  85. zx = z * x;
  86. xs = x * sinAngle;
  87. ys = y * sinAngle;
  88. zs = z * sinAngle;
  89. oneMinusCos = 1.0 - cosAngle;
  90. rotMat = new Matrix4x4();
  91. rotMat.elements[0*4+0] = (oneMinusCos * xx) + cosAngle;
  92. rotMat.elements[0*4+1] = (oneMinusCos * xy) - zs;
  93. rotMat.elements[0*4+2] = (oneMinusCos * zx) + ys;
  94. rotMat.elements[0*4+3] = 0.0;
  95. rotMat.elements[1*4+0] = (oneMinusCos * xy) + zs;
  96. rotMat.elements[1*4+1] = (oneMinusCos * yy) + cosAngle;
  97. rotMat.elements[1*4+2] = (oneMinusCos * yz) - xs;
  98. rotMat.elements[1*4+3] = 0.0;
  99. rotMat.elements[2*4+0] = (oneMinusCos * zx) - ys;
  100. rotMat.elements[2*4+1] = (oneMinusCos * yz) + xs;
  101. rotMat.elements[2*4+2] = (oneMinusCos * zz) + cosAngle;
  102. rotMat.elements[2*4+3] = 0.0;
  103. rotMat.elements[3*4+0] = 0.0;
  104. rotMat.elements[3*4+1] = 0.0;
  105. rotMat.elements[3*4+2] = 0.0;
  106. rotMat.elements[3*4+3] = 1.0;
  107. rotMat = rotMat.multiply(this);
  108. this.elements = rotMat.elements;
  109. }
  110. return this;
  111. },
  112. frustum: function (left, right, bottom, top, nearZ, farZ) {
  113. var deltaX = right - left;
  114. var deltaY = top - bottom;
  115. var deltaZ = farZ - nearZ;
  116. var frust;
  117. if ( (nearZ <= 0.0) || (farZ <= 0.0) ||
  118. (deltaX <= 0.0) || (deltaY <= 0.0) || (deltaZ <= 0.0) )
  119. return this;
  120. frust = new Matrix4x4();
  121. frust.elements[0*4+0] = 2.0 * nearZ / deltaX;
  122. frust.elements[0*4+1] = frust.elements[0*4+2] = frust.elements[0*4+3] = 0.0;
  123. frust.elements[1*4+1] = 2.0 * nearZ / deltaY;
  124. frust.elements[1*4+0] = frust.elements[1*4+2] = frust.elements[1*4+3] = 0.0;
  125. frust.elements[2*4+0] = (right + left) / deltaX;
  126. frust.elements[2*4+1] = (top + bottom) / deltaY;
  127. frust.elements[2*4+2] = -(nearZ + farZ) / deltaZ;
  128. frust.elements[2*4+3] = -1.0;
  129. frust.elements[3*4+2] = -2.0 * nearZ * farZ / deltaZ;
  130. frust.elements[3*4+0] = frust.elements[3*4+1] = frust.elements[3*4+3] = 0.0;
  131. frust = frust.multiply(this);
  132. this.elements = frust.elements;
  133. return this;
  134. },
  135. perspective: function (fovy, aspect, nearZ, farZ) {
  136. var frustumH = Math.tan(fovy / 360.0 * Math.PI) * nearZ;
  137. var frustumW = frustumH * aspect;
  138. return this.frustum(-frustumW, frustumW, -frustumH, frustumH, nearZ, farZ);
  139. },
  140. ortho: function (left, right, bottom, top, nearZ, farZ) {
  141. var deltaX = right - left;
  142. var deltaY = top - bottom;
  143. var deltaZ = farZ - nearZ;
  144. var ortho = new Matrix4x4();
  145. if ( (deltaX == 0.0) || (deltaY == 0.0) || (deltaZ == 0.0) )
  146. return this;
  147. ortho.elements[0*4+0] = 2.0 / deltaX;
  148. ortho.elements[3*4+0] = -(right + left) / deltaX;
  149. ortho.elements[1*4+1] = 2.0 / deltaY;
  150. ortho.elements[3*4+1] = -(top + bottom) / deltaY;
  151. ortho.elements[2*4+2] = -2.0 / deltaZ;
  152. ortho.elements[3*4+2] = -(nearZ + farZ) / deltaZ;
  153. ortho = ortho.multiply(this);
  154. this.elements = ortho.elements;
  155. return this;
  156. },
  157. multiply: function (right) {
  158. var tmp = new Matrix4x4();
  159. for (var i = 0; i < 4; i++) {
  160. tmp.elements[i*4+0] =
  161. (this.elements[i*4+0] * right.elements[0*4+0]) +
  162. (this.elements[i*4+1] * right.elements[1*4+0]) +
  163. (this.elements[i*4+2] * right.elements[2*4+0]) +
  164. (this.elements[i*4+3] * right.elements[3*4+0]) ;
  165. tmp.elements[i*4+1] =
  166. (this.elements[i*4+0] * right.elements[0*4+1]) +
  167. (this.elements[i*4+1] * right.elements[1*4+1]) +
  168. (this.elements[i*4+2] * right.elements[2*4+1]) +
  169. (this.elements[i*4+3] * right.elements[3*4+1]) ;
  170. tmp.elements[i*4+2] =
  171. (this.elements[i*4+0] * right.elements[0*4+2]) +
  172. (this.elements[i*4+1] * right.elements[1*4+2]) +
  173. (this.elements[i*4+2] * right.elements[2*4+2]) +
  174. (this.elements[i*4+3] * right.elements[3*4+2]) ;
  175. tmp.elements[i*4+3] =
  176. (this.elements[i*4+0] * right.elements[0*4+3]) +
  177. (this.elements[i*4+1] * right.elements[1*4+3]) +
  178. (this.elements[i*4+2] * right.elements[2*4+3]) +
  179. (this.elements[i*4+3] * right.elements[3*4+3]) ;
  180. }
  181. this.elements = tmp.elements;
  182. return this;
  183. },
  184. copy: function () {
  185. var tmp = new Matrix4x4();
  186. for (var i = 0; i < 16; i++) {
  187. tmp.elements[i] = this.elements[i];
  188. }
  189. return tmp;
  190. },
  191. get: function (row, col) {
  192. return this.elements[4*row+col];
  193. },
  194. // In-place inversion
  195. invert: function () {
  196. var tmp_0 = this.get(2,2) * this.get(3,3);
  197. var tmp_1 = this.get(3,2) * this.get(2,3);
  198. var tmp_2 = this.get(1,2) * this.get(3,3);
  199. var tmp_3 = this.get(3,2) * this.get(1,3);
  200. var tmp_4 = this.get(1,2) * this.get(2,3);
  201. var tmp_5 = this.get(2,2) * this.get(1,3);
  202. var tmp_6 = this.get(0,2) * this.get(3,3);
  203. var tmp_7 = this.get(3,2) * this.get(0,3);
  204. var tmp_8 = this.get(0,2) * this.get(2,3);
  205. var tmp_9 = this.get(2,2) * this.get(0,3);
  206. var tmp_10 = this.get(0,2) * this.get(1,3);
  207. var tmp_11 = this.get(1,2) * this.get(0,3);
  208. var tmp_12 = this.get(2,0) * this.get(3,1);
  209. var tmp_13 = this.get(3,0) * this.get(2,1);
  210. var tmp_14 = this.get(1,0) * this.get(3,1);
  211. var tmp_15 = this.get(3,0) * this.get(1,1);
  212. var tmp_16 = this.get(1,0) * this.get(2,1);
  213. var tmp_17 = this.get(2,0) * this.get(1,1);
  214. var tmp_18 = this.get(0,0) * this.get(3,1);
  215. var tmp_19 = this.get(3,0) * this.get(0,1);
  216. var tmp_20 = this.get(0,0) * this.get(2,1);
  217. var tmp_21 = this.get(2,0) * this.get(0,1);
  218. var tmp_22 = this.get(0,0) * this.get(1,1);
  219. var tmp_23 = this.get(1,0) * this.get(0,1);
  220. var t0 = ((tmp_0 * this.get(1,1) + tmp_3 * this.get(2,1) + tmp_4 * this.get(3,1)) -
  221. (tmp_1 * this.get(1,1) + tmp_2 * this.get(2,1) + tmp_5 * this.get(3,1)));
  222. var t1 = ((tmp_1 * this.get(0,1) + tmp_6 * this.get(2,1) + tmp_9 * this.get(3,1)) -
  223. (tmp_0 * this.get(0,1) + tmp_7 * this.get(2,1) + tmp_8 * this.get(3,1)));
  224. var t2 = ((tmp_2 * this.get(0,1) + tmp_7 * this.get(1,1) + tmp_10 * this.get(3,1)) -
  225. (tmp_3 * this.get(0,1) + tmp_6 * this.get(1,1) + tmp_11 * this.get(3,1)));
  226. var t3 = ((tmp_5 * this.get(0,1) + tmp_8 * this.get(1,1) + tmp_11 * this.get(2,1)) -
  227. (tmp_4 * this.get(0,1) + tmp_9 * this.get(1,1) + tmp_10 * this.get(2,1)));
  228. var d = 1.0 / (this.get(0,0) * t0 + this.get(1,0) * t1 + this.get(2,0) * t2 + this.get(3,0) * t3);
  229. var out_00 = d * t0;
  230. var out_01 = d * t1;
  231. var out_02 = d * t2;
  232. var out_03 = d * t3;
  233. var out_10 = d * ((tmp_1 * this.get(1,0) + tmp_2 * this.get(2,0) + tmp_5 * this.get(3,0)) -
  234. (tmp_0 * this.get(1,0) + tmp_3 * this.get(2,0) + tmp_4 * this.get(3,0)));
  235. var out_11 = d * ((tmp_0 * this.get(0,0) + tmp_7 * this.get(2,0) + tmp_8 * this.get(3,0)) -
  236. (tmp_1 * this.get(0,0) + tmp_6 * this.get(2,0) + tmp_9 * this.get(3,0)));
  237. var out_12 = d * ((tmp_3 * this.get(0,0) + tmp_6 * this.get(1,0) + tmp_11 * this.get(3,0)) -
  238. (tmp_2 * this.get(0,0) + tmp_7 * this.get(1,0) + tmp_10 * this.get(3,0)));
  239. var out_13 = d * ((tmp_4 * this.get(0,0) + tmp_9 * this.get(1,0) + tmp_10 * this.get(2,0)) -
  240. (tmp_5 * this.get(0,0) + tmp_8 * this.get(1,0) + tmp_11 * this.get(2,0)));
  241. var out_20 = d * ((tmp_12 * this.get(1,3) + tmp_15 * this.get(2,3) + tmp_16 * this.get(3,3)) -
  242. (tmp_13 * this.get(1,3) + tmp_14 * this.get(2,3) + tmp_17 * this.get(3,3)));
  243. var out_21 = d * ((tmp_13 * this.get(0,3) + tmp_18 * this.get(2,3) + tmp_21 * this.get(3,3)) -
  244. (tmp_12 * this.get(0,3) + tmp_19 * this.get(2,3) + tmp_20 * this.get(3,3)));
  245. var out_22 = d * ((tmp_14 * this.get(0,3) + tmp_19 * this.get(1,3) + tmp_22 * this.get(3,3)) -
  246. (tmp_15 * this.get(0,3) + tmp_18 * this.get(1,3) + tmp_23 * this.get(3,3)));
  247. var out_23 = d * ((tmp_17 * this.get(0,3) + tmp_20 * this.get(1,3) + tmp_23 * this.get(2,3)) -
  248. (tmp_16 * this.get(0,3) + tmp_21 * this.get(1,3) + tmp_22 * this.get(2,3)));
  249. var out_30 = d * ((tmp_14 * this.get(2,2) + tmp_17 * this.get(3,2) + tmp_13 * this.get(1,2)) -
  250. (tmp_16 * this.get(3,2) + tmp_12 * this.get(1,2) + tmp_15 * this.get(2,2)));
  251. var out_31 = d * ((tmp_20 * this.get(3,2) + tmp_12 * this.get(0,2) + tmp_19 * this.get(2,2)) -
  252. (tmp_18 * this.get(2,2) + tmp_21 * this.get(3,2) + tmp_13 * this.get(0,2)));
  253. var out_32 = d * ((tmp_18 * this.get(1,2) + tmp_23 * this.get(3,2) + tmp_15 * this.get(0,2)) -
  254. (tmp_22 * this.get(3,2) + tmp_14 * this.get(0,2) + tmp_19 * this.get(1,2)));
  255. var out_33 = d * ((tmp_22 * this.get(2,2) + tmp_16 * this.get(0,2) + tmp_21 * this.get(1,2)) -
  256. (tmp_20 * this.get(1,2) + tmp_23 * this.get(2,2) + tmp_17 * this.get(0,2)));
  257. this.elements[0*4+0] = out_00;
  258. this.elements[0*4+1] = out_01;
  259. this.elements[0*4+2] = out_02;
  260. this.elements[0*4+3] = out_03;
  261. this.elements[1*4+0] = out_10;
  262. this.elements[1*4+1] = out_11;
  263. this.elements[1*4+2] = out_12;
  264. this.elements[1*4+3] = out_13;
  265. this.elements[2*4+0] = out_20;
  266. this.elements[2*4+1] = out_21;
  267. this.elements[2*4+2] = out_22;
  268. this.elements[2*4+3] = out_23;
  269. this.elements[3*4+0] = out_30;
  270. this.elements[3*4+1] = out_31;
  271. this.elements[3*4+2] = out_32;
  272. this.elements[3*4+3] = out_33;
  273. return this;
  274. },
  275. // Returns new matrix which is the inverse of this
  276. inverse: function () {
  277. var tmp = this.copy();
  278. return tmp.invert();
  279. },
  280. // In-place transpose
  281. transpose: function () {
  282. var tmp = this.elements[0*4+1];
  283. this.elements[0*4+1] = this.elements[1*4+0];
  284. this.elements[1*4+0] = tmp;
  285. tmp = this.elements[0*4+2];
  286. this.elements[0*4+2] = this.elements[2*4+0];
  287. this.elements[2*4+0] = tmp;
  288. tmp = this.elements[0*4+3];
  289. this.elements[0*4+3] = this.elements[3*4+0];
  290. this.elements[3*4+0] = tmp;
  291. tmp = this.elements[1*4+2];
  292. this.elements[1*4+2] = this.elements[2*4+1];
  293. this.elements[2*4+1] = tmp;
  294. tmp = this.elements[1*4+3];
  295. this.elements[1*4+3] = this.elements[3*4+1];
  296. this.elements[3*4+1] = tmp;
  297. tmp = this.elements[2*4+3];
  298. this.elements[2*4+3] = this.elements[3*4+2];
  299. this.elements[3*4+2] = tmp;
  300. return this;
  301. },
  302. loadIdentity: function () {
  303. for (var i = 0; i < 16; i++)
  304. this.elements[i] = 0;
  305. this.elements[0*4+0] = 1.0;
  306. this.elements[1*4+1] = 1.0;
  307. this.elements[2*4+2] = 1.0;
  308. this.elements[3*4+3] = 1.0;
  309. return this;
  310. }
  311. };