sylvester.js 43 KB

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