sylvester.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263
  1. define([], function(){
  2. // === Sylvester ===
  3. // Vector and Matrix mathematics modules for JavaScript
  4. // Copyright (c) 2007 James Coglan
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining
  7. // a copy of this software and associated documentation files (the "Software"),
  8. // to deal in the Software without restriction, including without limitation
  9. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  10. // and/or sell copies of the Software, and to permit persons to whom the
  11. // Software is furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included
  14. // in all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  17. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  19. // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  21. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  22. // DEALINGS IN THE SOFTWARE.
  23. var Sylvester = {
  24. version: '0.1.3',
  25. precision: 1e-6
  26. };
  27. function Vector() {}
  28. Vector.prototype = {
  29. // Returns element i of the vector
  30. e: function(i) {
  31. return (i < 1 || i > this.elements.length) ? null : this.elements[i-1];
  32. },
  33. // Returns the number of elements the vector has
  34. dimensions: function() {
  35. return this.elements.length;
  36. },
  37. // Returns the modulus ('length') of the vector
  38. modulus: function() {
  39. return Math.sqrt(this.dot(this));
  40. },
  41. // Returns true iff the vector is equal to the argument
  42. eql: function(vector) {
  43. var n = this.elements.length;
  44. var V = vector.elements || vector;
  45. if (n != V.length) { return false; }
  46. do {
  47. if (Math.abs(this.elements[n-1] - V[n-1]) > Sylvester.precision) { return false; }
  48. } while (--n);
  49. return true;
  50. },
  51. // Returns a copy of the vector
  52. dup: function() {
  53. return Vector.create(this.elements);
  54. },
  55. // Maps the vector to another vector according to the given function
  56. map: function(fn) {
  57. var elements = [];
  58. this.each(function(x, i) {
  59. elements.push(fn(x, i));
  60. });
  61. return Vector.create(elements);
  62. },
  63. // Calls the iterator for each element of the vector in turn
  64. each: function(fn) {
  65. var n = this.elements.length, k = n, i;
  66. do { i = k - n;
  67. fn(this.elements[i], i+1);
  68. } while (--n);
  69. },
  70. // Returns a new vector created by normalizing the receiver
  71. toUnitVector: function() {
  72. var r = this.modulus();
  73. if (r === 0) { return this.dup(); }
  74. return this.map(function(x) { return x/r; });
  75. },
  76. // Returns the angle between the vector and the argument (also a vector)
  77. angleFrom: function(vector) {
  78. var V = vector.elements || vector;
  79. var n = this.elements.length, k = n, i;
  80. if (n != V.length) { return null; }
  81. var dot = 0, mod1 = 0, mod2 = 0;
  82. // Work things out in parallel to save time
  83. this.each(function(x, i) {
  84. dot += x * V[i-1];
  85. mod1 += x * x;
  86. mod2 += V[i-1] * V[i-1];
  87. });
  88. mod1 = Math.sqrt(mod1); mod2 = Math.sqrt(mod2);
  89. if (mod1*mod2 === 0) { return null; }
  90. var theta = dot / (mod1*mod2);
  91. if (theta < -1) { theta = -1; }
  92. if (theta > 1) { theta = 1; }
  93. return Math.acos(theta);
  94. },
  95. // Returns true iff the vector is parallel to the argument
  96. isParallelTo: function(vector) {
  97. var angle = this.angleFrom(vector);
  98. return (angle === null) ? null : (angle <= Sylvester.precision);
  99. },
  100. // Returns true iff the vector is antiparallel to the argument
  101. isAntiparallelTo: function(vector) {
  102. var angle = this.angleFrom(vector);
  103. return (angle === null) ? null : (Math.abs(angle - Math.PI) <= Sylvester.precision);
  104. },
  105. // Returns true iff the vector is perpendicular to the argument
  106. isPerpendicularTo: function(vector) {
  107. var dot = this.dot(vector);
  108. return (dot === null) ? null : (Math.abs(dot) <= Sylvester.precision);
  109. },
  110. // Returns the result of adding the argument to the vector
  111. add: function(vector) {
  112. var V = vector.elements || vector;
  113. if (this.elements.length != V.length) { return null; }
  114. return this.map(function(x, i) { return x + V[i-1]; });
  115. },
  116. // Returns the result of subtracting the argument from the vector
  117. subtract: function(vector) {
  118. var V = vector.elements || vector;
  119. if (this.elements.length != V.length) { return null; }
  120. return this.map(function(x, i) { return x - V[i-1]; });
  121. },
  122. // Returns the result of multiplying the elements of the vector by the argument
  123. multiply: function(k) {
  124. return this.map(function(x) { return x*k; });
  125. },
  126. x: function(k) { return this.multiply(k); },
  127. // Returns the scalar product of the vector with the argument
  128. // Both vectors must have equal dimensionality
  129. dot: function(vector) {
  130. var V = vector.elements || vector;
  131. var i, product = 0, n = this.elements.length;
  132. if (n != V.length) { return null; }
  133. do { product += this.elements[n-1] * V[n-1]; } while (--n);
  134. return product;
  135. },
  136. // Returns the vector product of the vector with the argument
  137. // Both vectors must have dimensionality 3
  138. cross: function(vector) {
  139. var B = vector.elements || vector;
  140. if (this.elements.length != 3 || B.length != 3) { return null; }
  141. var A = this.elements;
  142. return Vector.create([
  143. (A[1] * B[2]) - (A[2] * B[1]),
  144. (A[2] * B[0]) - (A[0] * B[2]),
  145. (A[0] * B[1]) - (A[1] * B[0])
  146. ]);
  147. },
  148. // Returns the (absolute) largest element of the vector
  149. max: function() {
  150. var m = 0, n = this.elements.length, k = n, i;
  151. do { i = k - n;
  152. if (Math.abs(this.elements[i]) > Math.abs(m)) { m = this.elements[i]; }
  153. } while (--n);
  154. return m;
  155. },
  156. // Returns the index of the first match found
  157. indexOf: function(x) {
  158. var index = null, n = this.elements.length, k = n, i;
  159. do { i = k - n;
  160. if (index === null && this.elements[i] == x) {
  161. index = i + 1;
  162. }
  163. } while (--n);
  164. return index;
  165. },
  166. // Returns a diagonal matrix with the vector's elements as its diagonal elements
  167. toDiagonalMatrix: function() {
  168. return Matrix.Diagonal(this.elements);
  169. },
  170. // Returns the result of rounding the elements of the vector
  171. round: function() {
  172. return this.map(function(x) { return Math.round(x); });
  173. },
  174. // Returns a copy of the vector with elements set to the given value if they
  175. // differ from it by less than Sylvester.precision
  176. snapTo: function(x) {
  177. return this.map(function(y) {
  178. return (Math.abs(y - x) <= Sylvester.precision) ? x : y;
  179. });
  180. },
  181. // Returns the vector's distance from the argument, when considered as a point in space
  182. distanceFrom: function(obj) {
  183. if (obj.anchor) { return obj.distanceFrom(this); }
  184. var V = obj.elements || obj;
  185. if (V.length != this.elements.length) { return null; }
  186. var sum = 0, part;
  187. this.each(function(x, i) {
  188. part = x - V[i-1];
  189. sum += part * part;
  190. });
  191. return Math.sqrt(sum);
  192. },
  193. // Returns true if the vector is point on the given line
  194. liesOn: function(line) {
  195. return line.contains(this);
  196. },
  197. // Return true iff the vector is a point in the given plane
  198. liesIn: function(plane) {
  199. return plane.contains(this);
  200. },
  201. // Rotates the vector about the given object. The object should be a
  202. // point if the vector is 2D, and a line if it is 3D. Be careful with line directions!
  203. rotate: function(t, obj) {
  204. var V, R, x, y, z;
  205. switch (this.elements.length) {
  206. case 2:
  207. V = obj.elements || obj;
  208. if (V.length != 2) { return null; }
  209. R = Matrix.Rotation(t).elements;
  210. x = this.elements[0] - V[0];
  211. y = this.elements[1] - V[1];
  212. return Vector.create([
  213. V[0] + R[0][0] * x + R[0][1] * y,
  214. V[1] + R[1][0] * x + R[1][1] * y
  215. ]);
  216. break;
  217. case 3:
  218. if (!obj.direction) { return null; }
  219. var C = obj.pointClosestTo(this).elements;
  220. R = Matrix.Rotation(t, obj.direction).elements;
  221. x = this.elements[0] - C[0];
  222. y = this.elements[1] - C[1];
  223. z = this.elements[2] - C[2];
  224. return Vector.create([
  225. C[0] + R[0][0] * x + R[0][1] * y + R[0][2] * z,
  226. C[1] + R[1][0] * x + R[1][1] * y + R[1][2] * z,
  227. C[2] + R[2][0] * x + R[2][1] * y + R[2][2] * z
  228. ]);
  229. break;
  230. default:
  231. return null;
  232. }
  233. },
  234. // Returns the result of reflecting the point in the given point, line or plane
  235. reflectionIn: function(obj) {
  236. if (obj.anchor) {
  237. // obj is a plane or line
  238. var P = this.elements.slice();
  239. var C = obj.pointClosestTo(P).elements;
  240. return Vector.create([C[0] + (C[0] - P[0]), C[1] + (C[1] - P[1]), C[2] + (C[2] - (P[2] || 0))]);
  241. } else {
  242. // obj is a point
  243. var Q = obj.elements || obj;
  244. if (this.elements.length != Q.length) { return null; }
  245. return this.map(function(x, i) { return Q[i-1] + (Q[i-1] - x); });
  246. }
  247. },
  248. // Utility to make sure vectors are 3D. If they are 2D, a zero z-component is added
  249. to3D: function() {
  250. var V = this.dup();
  251. switch (V.elements.length) {
  252. case 3: break;
  253. case 2: V.elements.push(0); break;
  254. default: return null;
  255. }
  256. return V;
  257. },
  258. // Returns a string representation of the vector
  259. inspect: function() {
  260. return '[' + this.elements.join(', ') + ']';
  261. },
  262. // Set vector's elements from an array
  263. setElements: function(els) {
  264. this.elements = (els.elements || els).slice();
  265. return this;
  266. }
  267. };
  268. // Constructor function
  269. Vector.create = function(elements) {
  270. var V = new Vector();
  271. return V.setElements(elements);
  272. };
  273. // i, j, k unit vectors
  274. Vector.i = Vector.create([1,0,0]);
  275. Vector.j = Vector.create([0,1,0]);
  276. Vector.k = Vector.create([0,0,1]);
  277. // Random vector of size n
  278. Vector.Random = function(n) {
  279. var elements = [];
  280. do { elements.push(Math.random());
  281. } while (--n);
  282. return Vector.create(elements);
  283. };
  284. // Vector filled with zeros
  285. Vector.Zero = function(n) {
  286. var elements = [];
  287. do { elements.push(0);
  288. } while (--n);
  289. return Vector.create(elements);
  290. };
  291. function Matrix() {}
  292. Matrix.prototype = {
  293. // Returns element (i,j) of the matrix
  294. e: function(i,j) {
  295. if (i < 1 || i > this.elements.length || j < 1 || j > this.elements[0].length) { return null; }
  296. return this.elements[i-1][j-1];
  297. },
  298. // Returns row k of the matrix as a vector
  299. row: function(i) {
  300. if (i > this.elements.length) { return null; }
  301. return Vector.create(this.elements[i-1]);
  302. },
  303. // Returns column k of the matrix as a vector
  304. col: function(j) {
  305. if (j > this.elements[0].length) { return null; }
  306. var col = [], n = this.elements.length, k = n, i;
  307. do { i = k - n;
  308. col.push(this.elements[i][j-1]);
  309. } while (--n);
  310. return Vector.create(col);
  311. },
  312. // Returns the number of rows/columns the matrix has
  313. dimensions: function() {
  314. return {rows: this.elements.length, cols: this.elements[0].length};
  315. },
  316. // Returns the number of rows in the matrix
  317. rows: function() {
  318. return this.elements.length;
  319. },
  320. // Returns the number of columns in the matrix
  321. cols: function() {
  322. return this.elements[0].length;
  323. },
  324. // Returns true iff the matrix is equal to the argument. You can supply
  325. // a vector as the argument, in which case the receiver must be a
  326. // one-column matrix equal to the vector.
  327. eql: function(matrix) {
  328. var M = matrix.elements || matrix;
  329. if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
  330. if (this.elements.length != M.length ||
  331. this.elements[0].length != M[0].length) { return false; }
  332. var ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
  333. do { i = ki - ni;
  334. nj = kj;
  335. do { j = kj - nj;
  336. if (Math.abs(this.elements[i][j] - M[i][j]) > Sylvester.precision) { return false; }
  337. } while (--nj);
  338. } while (--ni);
  339. return true;
  340. },
  341. // Returns a copy of the matrix
  342. dup: function() {
  343. return Matrix.create(this.elements);
  344. },
  345. // Maps the matrix to another matrix (of the same dimensions) according to the given function
  346. map: function(fn) {
  347. var els = [], ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
  348. do { i = ki - ni;
  349. nj = kj;
  350. els[i] = [];
  351. do { j = kj - nj;
  352. els[i][j] = fn(this.elements[i][j], i + 1, j + 1);
  353. } while (--nj);
  354. } while (--ni);
  355. return Matrix.create(els);
  356. },
  357. // Returns true iff the argument has the same dimensions as the matrix
  358. isSameSizeAs: function(matrix) {
  359. var M = matrix.elements || matrix;
  360. if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
  361. return (this.elements.length == M.length &&
  362. this.elements[0].length == M[0].length);
  363. },
  364. // Returns the result of adding the argument to the matrix
  365. add: function(matrix) {
  366. var M = matrix.elements || matrix;
  367. if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
  368. if (!this.isSameSizeAs(M)) { return null; }
  369. return this.map(function(x, i, j) { return x + M[i-1][j-1]; });
  370. },
  371. // Returns the result of subtracting the argument from the matrix
  372. subtract: function(matrix) {
  373. var M = matrix.elements || matrix;
  374. if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
  375. if (!this.isSameSizeAs(M)) { return null; }
  376. return this.map(function(x, i, j) { return x - M[i-1][j-1]; });
  377. },
  378. // Returns true iff the matrix can multiply the argument from the left
  379. canMultiplyFromLeft: function(matrix) {
  380. var M = matrix.elements || matrix;
  381. if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
  382. // this.columns should equal matrix.rows
  383. return (this.elements[0].length == M.length);
  384. },
  385. // Returns the result of multiplying the matrix from the right by the argument.
  386. // If the argument is a scalar then just multiply all the elements. If the argument is
  387. // a vector, a vector is returned, which saves you having to remember calling
  388. // col(1) on the result.
  389. multiply: function(matrix) {
  390. if (!matrix.elements) {
  391. return this.map(function(x) { return x * matrix; });
  392. }
  393. var returnVector = matrix.modulus ? true : false;
  394. var M = matrix.elements || matrix;
  395. if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
  396. if (!this.canMultiplyFromLeft(M)) { return null; }
  397. var ni = this.elements.length, ki = ni, i, nj, kj = M[0].length, j;
  398. var cols = this.elements[0].length, elements = [], sum, nc, c;
  399. do { i = ki - ni;
  400. elements[i] = [];
  401. nj = kj;
  402. do { j = kj - nj;
  403. sum = 0;
  404. nc = cols;
  405. do { c = cols - nc;
  406. sum += this.elements[i][c] * M[c][j];
  407. } while (--nc);
  408. elements[i][j] = sum;
  409. } while (--nj);
  410. } while (--ni);
  411. var M = Matrix.create(elements);
  412. return returnVector ? M.col(1) : M;
  413. },
  414. x: function(matrix) { return this.multiply(matrix); },
  415. // Returns a submatrix taken from the matrix
  416. // Argument order is: start row, start col, nrows, ncols
  417. // Element selection wraps if the required index is outside the matrix's bounds, so you could
  418. // use this to perform row/column cycling or copy-augmenting.
  419. minor: function(a, b, c, d) {
  420. var elements = [], ni = c, i, nj, j;
  421. var rows = this.elements.length, cols = this.elements[0].length;
  422. do { i = c - ni;
  423. elements[i] = [];
  424. nj = d;
  425. do { j = d - nj;
  426. elements[i][j] = this.elements[(a+i-1)%rows][(b+j-1)%cols];
  427. } while (--nj);
  428. } while (--ni);
  429. return Matrix.create(elements);
  430. },
  431. // Returns the transpose of the matrix
  432. transpose: function() {
  433. var rows = this.elements.length, cols = this.elements[0].length;
  434. var elements = [], ni = cols, i, nj, j;
  435. do { i = cols - ni;
  436. elements[i] = [];
  437. nj = rows;
  438. do { j = rows - nj;
  439. elements[i][j] = this.elements[j][i];
  440. } while (--nj);
  441. } while (--ni);
  442. return Matrix.create(elements);
  443. },
  444. // Returns true iff the matrix is square
  445. isSquare: function() {
  446. return (this.elements.length == this.elements[0].length);
  447. },
  448. // Returns the (absolute) largest element of the matrix
  449. max: function() {
  450. var m = 0, ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
  451. do { i = ki - ni;
  452. nj = kj;
  453. do { j = kj - nj;
  454. if (Math.abs(this.elements[i][j]) > Math.abs(m)) { m = this.elements[i][j]; }
  455. } while (--nj);
  456. } while (--ni);
  457. return m;
  458. },
  459. // Returns the indeces of the first match found by reading row-by-row from left to right
  460. indexOf: function(x) {
  461. var index = null, ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
  462. do { i = ki - ni;
  463. nj = kj;
  464. do { j = kj - nj;
  465. if (this.elements[i][j] == x) { return {i: i+1, j: j+1}; }
  466. } while (--nj);
  467. } while (--ni);
  468. return null;
  469. },
  470. // If the matrix is square, returns the diagonal elements as a vector.
  471. // Otherwise, returns null.
  472. diagonal: function() {
  473. if (!this.isSquare) { return null; }
  474. var els = [], n = this.elements.length, k = n, i;
  475. do { i = k - n;
  476. els.push(this.elements[i][i]);
  477. } while (--n);
  478. return Vector.create(els);
  479. },
  480. // Make the matrix upper (right) triangular by Gaussian elimination.
  481. // This method only adds multiples of rows to other rows. No rows are
  482. // scaled up or switched, and the determinant is preserved.
  483. toRightTriangular: function() {
  484. var M = this.dup(), els;
  485. var n = this.elements.length, k = n, i, np, kp = this.elements[0].length, p;
  486. do { i = k - n;
  487. if (M.elements[i][i] == 0) {
  488. for (j = i + 1; j < k; j++) {
  489. if (M.elements[j][i] != 0) {
  490. els = []; np = kp;
  491. do { p = kp - np;
  492. els.push(M.elements[i][p] + M.elements[j][p]);
  493. } while (--np);
  494. M.elements[i] = els;
  495. break;
  496. }
  497. }
  498. }
  499. if (M.elements[i][i] != 0) {
  500. for (j = i + 1; j < k; j++) {
  501. var multiplier = M.elements[j][i] / M.elements[i][i];
  502. els = []; np = kp;
  503. do { p = kp - np;
  504. // Elements with column numbers up to an including the number
  505. // of the row that we're subtracting can safely be set straight to
  506. // zero, since that's the point of this routine and it avoids having
  507. // to loop over and correct rounding errors later
  508. els.push(p <= i ? 0 : M.elements[j][p] - M.elements[i][p] * multiplier);
  509. } while (--np);
  510. M.elements[j] = els;
  511. }
  512. }
  513. } while (--n);
  514. return M;
  515. },
  516. toUpperTriangular: function() { return this.toRightTriangular(); },
  517. // Returns the determinant for square matrices
  518. determinant: function() {
  519. if (!this.isSquare()) { return null; }
  520. var M = this.toRightTriangular();
  521. var det = M.elements[0][0], n = M.elements.length - 1, k = n, i;
  522. do { i = k - n + 1;
  523. det = det * M.elements[i][i];
  524. } while (--n);
  525. return det;
  526. },
  527. det: function() { return this.determinant(); },
  528. // Returns true iff the matrix is singular
  529. isSingular: function() {
  530. return (this.isSquare() && this.determinant() === 0);
  531. },
  532. // Returns the trace for square matrices
  533. trace: function() {
  534. if (!this.isSquare()) { return null; }
  535. var tr = this.elements[0][0], n = this.elements.length - 1, k = n, i;
  536. do { i = k - n + 1;
  537. tr += this.elements[i][i];
  538. } while (--n);
  539. return tr;
  540. },
  541. tr: function() { return this.trace(); },
  542. // Returns the rank of the matrix
  543. rank: function() {
  544. var M = this.toRightTriangular(), rank = 0;
  545. var ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
  546. do { i = ki - ni;
  547. nj = kj;
  548. do { j = kj - nj;
  549. if (Math.abs(M.elements[i][j]) > Sylvester.precision) { rank++; break; }
  550. } while (--nj);
  551. } while (--ni);
  552. return rank;
  553. },
  554. rk: function() { return this.rank(); },
  555. // Returns the result of attaching the given argument to the right-hand side of the matrix
  556. augment: function(matrix) {
  557. var M = matrix.elements || matrix;
  558. if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
  559. var T = this.dup(), cols = T.elements[0].length;
  560. var ni = T.elements.length, ki = ni, i, nj, kj = M[0].length, j;
  561. if (ni != M.length) { return null; }
  562. do { i = ki - ni;
  563. nj = kj;
  564. do { j = kj - nj;
  565. T.elements[i][cols + j] = M[i][j];
  566. } while (--nj);
  567. } while (--ni);
  568. return T;
  569. },
  570. // Returns the inverse (if one exists) using Gauss-Jordan
  571. inverse: function() {
  572. if (!this.isSquare() || this.isSingular()) { return null; }
  573. var ni = this.elements.length, ki = ni, i, j;
  574. var M = this.augment(Matrix.I(ni)).toRightTriangular();
  575. var np, kp = M.elements[0].length, p, els, divisor;
  576. var inverse_elements = [], new_element;
  577. // Matrix is non-singular so there will be no zeros on the diagonal
  578. // Cycle through rows from last to first
  579. do { i = ni - 1;
  580. // First, normalise diagonal elements to 1
  581. els = []; np = kp;
  582. inverse_elements[i] = [];
  583. divisor = M.elements[i][i];
  584. do { p = kp - np;
  585. new_element = M.elements[i][p] / divisor;
  586. els.push(new_element);
  587. // Shuffle of the current row of the right hand side into the results
  588. // array as it will not be modified by later runs through this loop
  589. if (p >= ki) { inverse_elements[i].push(new_element); }
  590. } while (--np);
  591. M.elements[i] = els;
  592. // Then, subtract this row from those above it to
  593. // give the identity matrix on the left hand side
  594. for (j = 0; j < i; j++) {
  595. els = []; np = kp;
  596. do { p = kp - np;
  597. els.push(M.elements[j][p] - M.elements[i][p] * M.elements[j][i]);
  598. } while (--np);
  599. M.elements[j] = els;
  600. }
  601. } while (--ni);
  602. return Matrix.create(inverse_elements);
  603. },
  604. inv: function() { return this.inverse(); },
  605. // Returns the result of rounding all the elements
  606. round: function() {
  607. return this.map(function(x) { return Math.round(x); });
  608. },
  609. // Returns a copy of the matrix with elements set to the given value if they
  610. // differ from it by less than Sylvester.precision
  611. snapTo: function(x) {
  612. return this.map(function(p) {
  613. return (Math.abs(p - x) <= Sylvester.precision) ? x : p;
  614. });
  615. },
  616. // Returns a string representation of the matrix
  617. inspect: function() {
  618. var matrix_rows = [];
  619. var n = this.elements.length, k = n, i;
  620. do { i = k - n;
  621. matrix_rows.push(Vector.create(this.elements[i]).inspect());
  622. } while (--n);
  623. return matrix_rows.join('\n');
  624. },
  625. // Set the matrix's elements from an array. If the argument passed
  626. // is a vector, the resulting matrix will be a single column.
  627. setElements: function(els) {
  628. var i, elements = els.elements || els;
  629. if (typeof(elements[0][0]) != 'undefined') {
  630. var ni = elements.length, ki = ni, nj, kj, j;
  631. this.elements = [];
  632. do { i = ki - ni;
  633. nj = elements[i].length; kj = nj;
  634. this.elements[i] = [];
  635. do { j = kj - nj;
  636. this.elements[i][j] = elements[i][j];
  637. } while (--nj);
  638. } while(--ni);
  639. return this;
  640. }
  641. var n = elements.length, k = n;
  642. this.elements = [];
  643. do { i = k - n;
  644. this.elements.push([elements[i]]);
  645. } while (--n);
  646. return this;
  647. }
  648. };
  649. // Constructor function
  650. Matrix.create = function(elements) {
  651. var M = new Matrix();
  652. return M.setElements(elements);
  653. };
  654. // Identity matrix of size n
  655. Matrix.I = function(n) {
  656. var els = [], k = n, i, nj, j;
  657. do { i = k - n;
  658. els[i] = []; nj = k;
  659. do { j = k - nj;
  660. els[i][j] = (i == j) ? 1 : 0;
  661. } while (--nj);
  662. } while (--n);
  663. return Matrix.create(els);
  664. };
  665. // Diagonal matrix - all off-diagonal elements are zero
  666. Matrix.Diagonal = function(elements) {
  667. var n = elements.length, k = n, i;
  668. var M = Matrix.I(n);
  669. do { i = k - n;
  670. M.elements[i][i] = elements[i];
  671. } while (--n);
  672. return M;
  673. };
  674. // Rotation matrix about some axis. If no axis is
  675. // supplied, assume we're after a 2D transform
  676. Matrix.Rotation = function(theta, a) {
  677. if (!a) {
  678. return Matrix.create([
  679. [Math.cos(theta), -Math.sin(theta)],
  680. [Math.sin(theta), Math.cos(theta)]
  681. ]);
  682. }
  683. var axis = a.dup();
  684. if (axis.elements.length != 3) { return null; }
  685. var mod = axis.modulus();
  686. var x = axis.elements[0]/mod, y = axis.elements[1]/mod, z = axis.elements[2]/mod;
  687. var s = Math.sin(theta), c = Math.cos(theta), t = 1 - c;
  688. // Formula derived here: http://www.gamedev.net/reference/articles/article1199.asp
  689. // That proof rotates the co-ordinate system so theta
  690. // becomes -theta and sin becomes -sin here.
  691. return Matrix.create([
  692. [ t*x*x + c, t*x*y - s*z, t*x*z + s*y ],
  693. [ t*x*y + s*z, t*y*y + c, t*y*z - s*x ],
  694. [ t*x*z - s*y, t*y*z + s*x, t*z*z + c ]
  695. ]);
  696. };
  697. // Special case rotations
  698. Matrix.RotationX = function(t) {
  699. var c = Math.cos(t), s = Math.sin(t);
  700. return Matrix.create([
  701. [ 1, 0, 0 ],
  702. [ 0, c, -s ],
  703. [ 0, s, c ]
  704. ]);
  705. };
  706. Matrix.RotationY = function(t) {
  707. var c = Math.cos(t), s = Math.sin(t);
  708. return Matrix.create([
  709. [ c, 0, s ],
  710. [ 0, 1, 0 ],
  711. [ -s, 0, c ]
  712. ]);
  713. };
  714. Matrix.RotationZ = function(t) {
  715. var c = Math.cos(t), s = Math.sin(t);
  716. return Matrix.create([
  717. [ c, -s, 0 ],
  718. [ s, c, 0 ],
  719. [ 0, 0, 1 ]
  720. ]);
  721. };
  722. // Random matrix of n rows, m columns
  723. Matrix.Random = function(n, m) {
  724. return Matrix.Zero(n, m).map(
  725. function() { return Math.random(); }
  726. );
  727. };
  728. // Matrix filled with zeros
  729. Matrix.Zero = function(n, m) {
  730. var els = [], ni = n, i, nj, j;
  731. do { i = n - ni;
  732. els[i] = [];
  733. nj = m;
  734. do { j = m - nj;
  735. els[i][j] = 0;
  736. } while (--nj);
  737. } while (--ni);
  738. return Matrix.create(els);
  739. };
  740. function Line() {}
  741. Line.prototype = {
  742. // Returns true if the argument occupies the same space as the line
  743. eql: function(line) {
  744. return (this.isParallelTo(line) && this.contains(line.anchor));
  745. },
  746. // Returns a copy of the line
  747. dup: function() {
  748. return Line.create(this.anchor, this.direction);
  749. },
  750. // Returns the result of translating the line by the given vector/array
  751. translate: function(vector) {
  752. var V = vector.elements || vector;
  753. return Line.create([
  754. this.anchor.elements[0] + V[0],
  755. this.anchor.elements[1] + V[1],
  756. this.anchor.elements[2] + (V[2] || 0)
  757. ], this.direction);
  758. },
  759. // Returns true if the line is parallel to the argument. Here, 'parallel to'
  760. // means that the argument's direction is either parallel or antiparallel to
  761. // the line's own direction. A line is parallel to a plane if the two do not
  762. // have a unique intersection.
  763. isParallelTo: function(obj) {
  764. if (obj.normal) { return obj.isParallelTo(this); }
  765. var theta = this.direction.angleFrom(obj.direction);
  766. return (Math.abs(theta) <= Sylvester.precision || Math.abs(theta - Math.PI) <= Sylvester.precision);
  767. },
  768. // Returns the line's perpendicular distance from the argument,
  769. // which can be a point, a line or a plane
  770. distanceFrom: function(obj) {
  771. if (obj.normal) { return obj.distanceFrom(this); }
  772. if (obj.direction) {
  773. // obj is a line
  774. if (this.isParallelTo(obj)) { return this.distanceFrom(obj.anchor); }
  775. var N = this.direction.cross(obj.direction).toUnitVector().elements;
  776. var A = this.anchor.elements, B = obj.anchor.elements;
  777. return Math.abs((A[0] - B[0]) * N[0] + (A[1] - B[1]) * N[1] + (A[2] - B[2]) * N[2]);
  778. } else {
  779. // obj is a point
  780. var P = obj.elements || obj;
  781. var A = this.anchor.elements, D = this.direction.elements;
  782. var PA1 = P[0] - A[0], PA2 = P[1] - A[1], PA3 = (P[2] || 0) - A[2];
  783. var modPA = Math.sqrt(PA1*PA1 + PA2*PA2 + PA3*PA3);
  784. if (modPA === 0) return 0;
  785. // Assumes direction vector is normalized
  786. var cosTheta = (PA1 * D[0] + PA2 * D[1] + PA3 * D[2]) / modPA;
  787. var sin2 = 1 - cosTheta*cosTheta;
  788. return Math.abs(modPA * Math.sqrt(sin2 < 0 ? 0 : sin2));
  789. }
  790. },
  791. // Returns true iff the argument is a point on the line
  792. contains: function(point) {
  793. var dist = this.distanceFrom(point);
  794. return (dist !== null && dist <= Sylvester.precision);
  795. },
  796. // Returns true iff the line lies in the given plane
  797. liesIn: function(plane) {
  798. return plane.contains(this);
  799. },
  800. // Returns true iff the line has a unique point of intersection with the argument
  801. intersects: function(obj) {
  802. if (obj.normal) { return obj.intersects(this); }
  803. return (!this.isParallelTo(obj) && this.distanceFrom(obj) <= Sylvester.precision);
  804. },
  805. // Returns the unique intersection point with the argument, if one exists
  806. intersectionWith: function(obj) {
  807. if (obj.normal) { return obj.intersectionWith(this); }
  808. if (!this.intersects(obj)) { return null; }
  809. var P = this.anchor.elements, X = this.direction.elements,
  810. Q = obj.anchor.elements, Y = obj.direction.elements;
  811. var X1 = X[0], X2 = X[1], X3 = X[2], Y1 = Y[0], Y2 = Y[1], Y3 = Y[2];
  812. var PsubQ1 = P[0] - Q[0], PsubQ2 = P[1] - Q[1], PsubQ3 = P[2] - Q[2];
  813. var XdotQsubP = - X1*PsubQ1 - X2*PsubQ2 - X3*PsubQ3;
  814. var YdotPsubQ = Y1*PsubQ1 + Y2*PsubQ2 + Y3*PsubQ3;
  815. var XdotX = X1*X1 + X2*X2 + X3*X3;
  816. var YdotY = Y1*Y1 + Y2*Y2 + Y3*Y3;
  817. var XdotY = X1*Y1 + X2*Y2 + X3*Y3;
  818. var k = (XdotQsubP * YdotY / XdotX + XdotY * YdotPsubQ) / (YdotY - XdotY * XdotY);
  819. return Vector.create([P[0] + k*X1, P[1] + k*X2, P[2] + k*X3]);
  820. },
  821. // Returns the point on the line that is closest to the given point or line
  822. pointClosestTo: function(obj) {
  823. if (obj.direction) {
  824. // obj is a line
  825. if (this.intersects(obj)) { return this.intersectionWith(obj); }
  826. if (this.isParallelTo(obj)) { return null; }
  827. var D = this.direction.elements, E = obj.direction.elements;
  828. var D1 = D[0], D2 = D[1], D3 = D[2], E1 = E[0], E2 = E[1], E3 = E[2];
  829. // Create plane containing obj and the shared normal and intersect this with it
  830. // Thank you: http://www.cgafaq.info/wiki/Line-line_distance
  831. var x = (D3 * E1 - D1 * E3), y = (D1 * E2 - D2 * E1), z = (D2 * E3 - D3 * E2);
  832. var N = Vector.create([x * E3 - y * E2, y * E1 - z * E3, z * E2 - x * E1]);
  833. var P = Plane.create(obj.anchor, N);
  834. return P.intersectionWith(this);
  835. } else {
  836. // obj is a point
  837. var P = obj.elements || obj;
  838. if (this.contains(P)) { return Vector.create(P); }
  839. var A = this.anchor.elements, D = this.direction.elements;
  840. var D1 = D[0], D2 = D[1], D3 = D[2], A1 = A[0], A2 = A[1], A3 = A[2];
  841. var x = D1 * (P[1]-A2) - D2 * (P[0]-A1), y = D2 * ((P[2] || 0) - A3) - D3 * (P[1]-A2),
  842. z = D3 * (P[0]-A1) - D1 * ((P[2] || 0) - A3);
  843. var V = Vector.create([D2 * x - D3 * z, D3 * y - D1 * x, D1 * z - D2 * y]);
  844. var k = this.distanceFrom(P) / V.modulus();
  845. return Vector.create([
  846. P[0] + V.elements[0] * k,
  847. P[1] + V.elements[1] * k,
  848. (P[2] || 0) + V.elements[2] * k
  849. ]);
  850. }
  851. },
  852. // Returns a copy of the line rotated by t radians about the given line. Works by
  853. // finding the argument's closest point to this line's anchor point (call this C) and
  854. // rotating the anchor about C. Also rotates the line's direction about the argument's.
  855. // Be careful with this - the rotation axis' direction affects the outcome!
  856. rotate: function(t, line) {
  857. // If we're working in 2D
  858. if (typeof(line.direction) == 'undefined') { line = Line.create(line.to3D(), Vector.k); }
  859. var R = Matrix.Rotation(t, line.direction).elements;
  860. var C = line.pointClosestTo(this.anchor).elements;
  861. var A = this.anchor.elements, D = this.direction.elements;
  862. var C1 = C[0], C2 = C[1], C3 = C[2], A1 = A[0], A2 = A[1], A3 = A[2];
  863. var x = A1 - C1, y = A2 - C2, z = A3 - C3;
  864. return Line.create([
  865. C1 + R[0][0] * x + R[0][1] * y + R[0][2] * z,
  866. C2 + R[1][0] * x + R[1][1] * y + R[1][2] * z,
  867. C3 + R[2][0] * x + R[2][1] * y + R[2][2] * z
  868. ], [
  869. R[0][0] * D[0] + R[0][1] * D[1] + R[0][2] * D[2],
  870. R[1][0] * D[0] + R[1][1] * D[1] + R[1][2] * D[2],
  871. R[2][0] * D[0] + R[2][1] * D[1] + R[2][2] * D[2]
  872. ]);
  873. },
  874. // Returns the line's reflection in the given point or line
  875. reflectionIn: function(obj) {
  876. if (obj.normal) {
  877. // obj is a plane
  878. var A = this.anchor.elements, D = this.direction.elements;
  879. var A1 = A[0], A2 = A[1], A3 = A[2], D1 = D[0], D2 = D[1], D3 = D[2];
  880. var newA = this.anchor.reflectionIn(obj).elements;
  881. // Add the line's direction vector to its anchor, then mirror that in the plane
  882. var AD1 = A1 + D1, AD2 = A2 + D2, AD3 = A3 + D3;
  883. var Q = obj.pointClosestTo([AD1, AD2, AD3]).elements;
  884. var newD = [Q[0] + (Q[0] - AD1) - newA[0], Q[1] + (Q[1] - AD2) - newA[1], Q[2] + (Q[2] - AD3) - newA[2]];
  885. return Line.create(newA, newD);
  886. } else if (obj.direction) {
  887. // obj is a line - reflection obtained by rotating PI radians about obj
  888. return this.rotate(Math.PI, obj);
  889. } else {
  890. // obj is a point - just reflect the line's anchor in it
  891. var P = obj.elements || obj;
  892. return Line.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.direction);
  893. }
  894. },
  895. // Set the line's anchor point and direction.
  896. setVectors: function(anchor, direction) {
  897. // Need to do this so that line's properties are not
  898. // references to the arguments passed in
  899. anchor = Vector.create(anchor);
  900. direction = Vector.create(direction);
  901. if (anchor.elements.length == 2) {anchor.elements.push(0); }
  902. if (direction.elements.length == 2) { direction.elements.push(0); }
  903. if (anchor.elements.length > 3 || direction.elements.length > 3) { return null; }
  904. var mod = direction.modulus();
  905. if (mod === 0) { return null; }
  906. this.anchor = anchor;
  907. this.direction = Vector.create([
  908. direction.elements[0] / mod,
  909. direction.elements[1] / mod,
  910. direction.elements[2] / mod
  911. ]);
  912. return this;
  913. }
  914. };
  915. // Constructor function
  916. Line.create = function(anchor, direction) {
  917. var L = new Line();
  918. return L.setVectors(anchor, direction);
  919. };
  920. // Axes
  921. Line.X = Line.create(Vector.Zero(3), Vector.i);
  922. Line.Y = Line.create(Vector.Zero(3), Vector.j);
  923. Line.Z = Line.create(Vector.Zero(3), Vector.k);
  924. function Plane() {}
  925. Plane.prototype = {
  926. // Returns true iff the plane occupies the same space as the argument
  927. eql: function(plane) {
  928. return (this.contains(plane.anchor) && this.isParallelTo(plane));
  929. },
  930. // Returns a copy of the plane
  931. dup: function() {
  932. return Plane.create(this.anchor, this.normal);
  933. },
  934. // Returns the result of translating the plane by the given vector
  935. translate: function(vector) {
  936. var V = vector.elements || vector;
  937. return Plane.create([
  938. this.anchor.elements[0] + V[0],
  939. this.anchor.elements[1] + V[1],
  940. this.anchor.elements[2] + (V[2] || 0)
  941. ], this.normal);
  942. },
  943. // Returns true iff the plane is parallel to the argument. Will return true
  944. // if the planes are equal, or if you give a line and it lies in the plane.
  945. isParallelTo: function(obj) {
  946. var theta;
  947. if (obj.normal) {
  948. // obj is a plane
  949. theta = this.normal.angleFrom(obj.normal);
  950. return (Math.abs(theta) <= Sylvester.precision || Math.abs(Math.PI - theta) <= Sylvester.precision);
  951. } else if (obj.direction) {
  952. // obj is a line
  953. return this.normal.isPerpendicularTo(obj.direction);
  954. }
  955. return null;
  956. },
  957. // Returns true iff the receiver is perpendicular to the argument
  958. isPerpendicularTo: function(plane) {
  959. var theta = this.normal.angleFrom(plane.normal);
  960. return (Math.abs(Math.PI/2 - theta) <= Sylvester.precision);
  961. },
  962. // Returns the plane's distance from the given object (point, line or plane)
  963. distanceFrom: function(obj) {
  964. if (this.intersects(obj) || this.contains(obj)) { return 0; }
  965. if (obj.anchor) {
  966. // obj is a plane or line
  967. var A = this.anchor.elements, B = obj.anchor.elements, N = this.normal.elements;
  968. return Math.abs((A[0] - B[0]) * N[0] + (A[1] - B[1]) * N[1] + (A[2] - B[2]) * N[2]);
  969. } else {
  970. // obj is a point
  971. var P = obj.elements || obj;
  972. var A = this.anchor.elements, N = this.normal.elements;
  973. return Math.abs((A[0] - P[0]) * N[0] + (A[1] - P[1]) * N[1] + (A[2] - (P[2] || 0)) * N[2]);
  974. }
  975. },
  976. // Returns true iff the plane contains the given point or line
  977. contains: function(obj) {
  978. if (obj.normal) { return null; }
  979. if (obj.direction) {
  980. return (this.contains(obj.anchor) && this.contains(obj.anchor.add(obj.direction)));
  981. } else {
  982. var P = obj.elements || obj;
  983. var A = this.anchor.elements, N = this.normal.elements;
  984. var diff = Math.abs(N[0]*(A[0] - P[0]) + N[1]*(A[1] - P[1]) + N[2]*(A[2] - (P[2] || 0)));
  985. return (diff <= Sylvester.precision);
  986. }
  987. },
  988. // Returns true iff the plane has a unique point/line of intersection with the argument
  989. intersects: function(obj) {
  990. if (typeof(obj.direction) == 'undefined' && typeof(obj.normal) == 'undefined') { return null; }
  991. return !this.isParallelTo(obj);
  992. },
  993. // Returns the unique intersection with the argument, if one exists. The result
  994. // will be a vector if a line is supplied, and a line if a plane is supplied.
  995. intersectionWith: function(obj) {
  996. if (!this.intersects(obj)) { return null; }
  997. if (obj.direction) {
  998. // obj is a line
  999. var A = obj.anchor.elements, D = obj.direction.elements,
  1000. P = this.anchor.elements, N = this.normal.elements;
  1001. var multiplier = (N[0]*(P[0]-A[0]) + N[1]*(P[1]-A[1]) + N[2]*(P[2]-A[2])) / (N[0]*D[0] + N[1]*D[1] + N[2]*D[2]);
  1002. return Vector.create([A[0] + D[0]*multiplier, A[1] + D[1]*multiplier, A[2] + D[2]*multiplier]);
  1003. } else if (obj.normal) {
  1004. // obj is a plane
  1005. var direction = this.normal.cross(obj.normal).toUnitVector();
  1006. // To find an anchor point, we find one co-ordinate that has a value
  1007. // of zero somewhere on the intersection, and remember which one we picked
  1008. var N = this.normal.elements, A = this.anchor.elements,
  1009. O = obj.normal.elements, B = obj.anchor.elements;
  1010. var solver = Matrix.Zero(2,2), i = 0;
  1011. while (solver.isSingular()) {
  1012. i++;
  1013. solver = Matrix.create([
  1014. [ N[i%3], N[(i+1)%3] ],
  1015. [ O[i%3], O[(i+1)%3] ]
  1016. ]);
  1017. }
  1018. // Then we solve the simultaneous equations in the remaining dimensions
  1019. var inverse = solver.inverse().elements;
  1020. var x = N[0]*A[0] + N[1]*A[1] + N[2]*A[2];
  1021. var y = O[0]*B[0] + O[1]*B[1] + O[2]*B[2];
  1022. var intersection = [
  1023. inverse[0][0] * x + inverse[0][1] * y,
  1024. inverse[1][0] * x + inverse[1][1] * y
  1025. ];
  1026. var anchor = [];
  1027. for (var j = 1; j <= 3; j++) {
  1028. // This formula picks the right element from intersection by
  1029. // cycling depending on which element we set to zero above
  1030. anchor.push((i == j) ? 0 : intersection[(j + (5 - i)%3)%3]);
  1031. }
  1032. return Line.create(anchor, direction);
  1033. }
  1034. },
  1035. // Returns the point in the plane closest to the given point
  1036. pointClosestTo: function(point) {
  1037. var P = point.elements || point;
  1038. var A = this.anchor.elements, N = this.normal.elements;
  1039. var dot = (A[0] - P[0]) * N[0] + (A[1] - P[1]) * N[1] + (A[2] - (P[2] || 0)) * N[2];
  1040. return Vector.create([P[0] + N[0] * dot, P[1] + N[1] * dot, (P[2] || 0) + N[2] * dot]);
  1041. },
  1042. // Returns a copy of the plane, rotated by t radians about the given line
  1043. // See notes on Line#rotate.
  1044. rotate: function(t, line) {
  1045. var R = Matrix.Rotation(t, line.direction).elements;
  1046. var C = line.pointClosestTo(this.anchor).elements;
  1047. var A = this.anchor.elements, N = this.normal.elements;
  1048. var C1 = C[0], C2 = C[1], C3 = C[2], A1 = A[0], A2 = A[1], A3 = A[2];
  1049. var x = A1 - C1, y = A2 - C2, z = A3 - C3;
  1050. return Plane.create([
  1051. C1 + R[0][0] * x + R[0][1] * y + R[0][2] * z,
  1052. C2 + R[1][0] * x + R[1][1] * y + R[1][2] * z,
  1053. C3 + R[2][0] * x + R[2][1] * y + R[2][2] * z
  1054. ], [
  1055. R[0][0] * N[0] + R[0][1] * N[1] + R[0][2] * N[2],
  1056. R[1][0] * N[0] + R[1][1] * N[1] + R[1][2] * N[2],
  1057. R[2][0] * N[0] + R[2][1] * N[1] + R[2][2] * N[2]
  1058. ]);
  1059. },
  1060. // Returns the reflection of the plane in the given point, line or plane.
  1061. reflectionIn: function(obj) {
  1062. if (obj.normal) {
  1063. // obj is a plane
  1064. var A = this.anchor.elements, N = this.normal.elements;
  1065. var A1 = A[0], A2 = A[1], A3 = A[2], N1 = N[0], N2 = N[1], N3 = N[2];
  1066. var newA = this.anchor.reflectionIn(obj).elements;
  1067. // Add the plane's normal to its anchor, then mirror that in the other plane
  1068. var AN1 = A1 + N1, AN2 = A2 + N2, AN3 = A3 + N3;
  1069. var Q = obj.pointClosestTo([AN1, AN2, AN3]).elements;
  1070. var newN = [Q[0] + (Q[0] - AN1) - newA[0], Q[1] + (Q[1] - AN2) - newA[1], Q[2] + (Q[2] - AN3) - newA[2]];
  1071. return Plane.create(newA, newN);
  1072. } else if (obj.direction) {
  1073. // obj is a line
  1074. return this.rotate(Math.PI, obj);
  1075. } else {
  1076. // obj is a point
  1077. var P = obj.elements || obj;
  1078. return Plane.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.normal);
  1079. }
  1080. },
  1081. // Sets the anchor point and normal to the plane. If three arguments are specified,
  1082. // the normal is calculated by assuming the three points should lie in the same plane.
  1083. // If only two are sepcified, the second is taken to be the normal. Normal vector is
  1084. // normalised before storage.
  1085. setVectors: function(anchor, v1, v2) {
  1086. anchor = Vector.create(anchor);
  1087. anchor = anchor.to3D(); if (anchor === null) { return null; }
  1088. v1 = Vector.create(v1);
  1089. v1 = v1.to3D(); if (v1 === null) { return null; }
  1090. if (typeof(v2) == 'undefined') {
  1091. v2 = null;
  1092. } else {
  1093. v2 = Vector.create(v2);
  1094. v2 = v2.to3D(); if (v2 === null) { return null; }
  1095. }
  1096. var A1 = anchor.elements[0], A2 = anchor.elements[1], A3 = anchor.elements[2];
  1097. var v11 = v1.elements[0], v12 = v1.elements[1], v13 = v1.elements[2];
  1098. var normal, mod;
  1099. if (v2 !== null) {
  1100. var v21 = v2.elements[0], v22 = v2.elements[1], v23 = v2.elements[2];
  1101. normal = Vector.create([
  1102. (v12 - A2) * (v23 - A3) - (v13 - A3) * (v22 - A2),
  1103. (v13 - A3) * (v21 - A1) - (v11 - A1) * (v23 - A3),
  1104. (v11 - A1) * (v22 - A2) - (v12 - A2) * (v21 - A1)
  1105. ]);
  1106. mod = normal.modulus();
  1107. if (mod === 0) { return null; }
  1108. normal = Vector.create([normal.elements[0] / mod, normal.elements[1] / mod, normal.elements[2] / mod]);
  1109. } else {
  1110. mod = Math.sqrt(v11*v11 + v12*v12 + v13*v13);
  1111. if (mod === 0) { return null; }
  1112. normal = Vector.create([v1.elements[0] / mod, v1.elements[1] / mod, v1.elements[2] / mod]);
  1113. }
  1114. this.anchor = anchor;
  1115. this.normal = normal;
  1116. return this;
  1117. }
  1118. };
  1119. // Constructor function
  1120. Plane.create = function(anchor, v1, v2) {
  1121. var P = new Plane();
  1122. return P.setVectors(anchor, v1, v2);
  1123. };
  1124. // X-Y-Z planes
  1125. Plane.XY = Plane.create(Vector.Zero(3), Vector.k);
  1126. Plane.YZ = Plane.create(Vector.Zero(3), Vector.i);
  1127. Plane.ZX = Plane.create(Vector.Zero(3), Vector.j);
  1128. Plane.YX = Plane.XY; Plane.ZY = Plane.YZ; Plane.XZ = Plane.ZX;
  1129. return {
  1130. $V : Vector.create,
  1131. $M : Matrix.create,
  1132. $L : Line.create,
  1133. $P : Plane.create,
  1134. Matrix: Matrix,
  1135. Vector: Vector,
  1136. Plane: Plane
  1137. }
  1138. });