matrix.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. define("dojox/gfx/matrix", ["./_base","dojo/_base/lang"],
  2. function(g, lang){
  3. var m = g.matrix = {};
  4. /*===== g = dojox.gfx; m = dojox.gfx.matrix =====*/
  5. // candidates for dojox.math:
  6. var _degToRadCache = {};
  7. m._degToRad = function(degree){
  8. return _degToRadCache[degree] || (_degToRadCache[degree] = (Math.PI * degree / 180));
  9. };
  10. m._radToDeg = function(radian){ return radian / Math.PI * 180; };
  11. m.Matrix2D = function(arg){
  12. // summary:
  13. // a 2D matrix object
  14. // description: Normalizes a 2D matrix-like object. If arrays is passed,
  15. // all objects of the array are normalized and multiplied sequentially.
  16. // arg: Object
  17. // a 2D matrix-like object, a number, or an array of such objects
  18. if(arg){
  19. if(typeof arg == "number"){
  20. this.xx = this.yy = arg;
  21. }else if(arg instanceof Array){
  22. if(arg.length > 0){
  23. var matrix = m.normalize(arg[0]);
  24. // combine matrices
  25. for(var i = 1; i < arg.length; ++i){
  26. var l = matrix, r = m.normalize(arg[i]);
  27. matrix = new m.Matrix2D();
  28. matrix.xx = l.xx * r.xx + l.xy * r.yx;
  29. matrix.xy = l.xx * r.xy + l.xy * r.yy;
  30. matrix.yx = l.yx * r.xx + l.yy * r.yx;
  31. matrix.yy = l.yx * r.xy + l.yy * r.yy;
  32. matrix.dx = l.xx * r.dx + l.xy * r.dy + l.dx;
  33. matrix.dy = l.yx * r.dx + l.yy * r.dy + l.dy;
  34. }
  35. lang.mixin(this, matrix);
  36. }
  37. }else{
  38. lang.mixin(this, arg);
  39. }
  40. }
  41. };
  42. // the default (identity) matrix, which is used to fill in missing values
  43. lang.extend(m.Matrix2D, {xx: 1, xy: 0, yx: 0, yy: 1, dx: 0, dy: 0});
  44. lang.mixin(m, {
  45. // summary: class constants, and methods of dojox.gfx.matrix
  46. // matrix constants
  47. // identity: dojox.gfx.matrix.Matrix2D
  48. // an identity matrix constant: identity * (x, y) == (x, y)
  49. identity: new m.Matrix2D(),
  50. // flipX: dojox.gfx.matrix.Matrix2D
  51. // a matrix, which reflects points at x = 0 line: flipX * (x, y) == (-x, y)
  52. flipX: new m.Matrix2D({xx: -1}),
  53. // flipY: dojox.gfx.matrix.Matrix2D
  54. // a matrix, which reflects points at y = 0 line: flipY * (x, y) == (x, -y)
  55. flipY: new m.Matrix2D({yy: -1}),
  56. // flipXY: dojox.gfx.matrix.Matrix2D
  57. // a matrix, which reflects points at the origin of coordinates: flipXY * (x, y) == (-x, -y)
  58. flipXY: new m.Matrix2D({xx: -1, yy: -1}),
  59. // matrix creators
  60. translate: function(a, b){
  61. // summary: forms a translation matrix
  62. // description: The resulting matrix is used to translate (move) points by specified offsets.
  63. // a: Number: an x coordinate value
  64. // b: Number: a y coordinate value
  65. if(arguments.length > 1){
  66. return new m.Matrix2D({dx: a, dy: b}); // dojox.gfx.matrix.Matrix2D
  67. }
  68. // branch
  69. // a: dojox.gfx.Point: a point-like object, which specifies offsets for both dimensions
  70. // b: null
  71. return new m.Matrix2D({dx: a.x, dy: a.y}); // dojox.gfx.matrix.Matrix2D
  72. },
  73. scale: function(a, b){
  74. // summary: forms a scaling matrix
  75. // description: The resulting matrix is used to scale (magnify) points by specified offsets.
  76. // a: Number: a scaling factor used for the x coordinate
  77. // b: Number: a scaling factor used for the y coordinate
  78. if(arguments.length > 1){
  79. return new m.Matrix2D({xx: a, yy: b}); // dojox.gfx.matrix.Matrix2D
  80. }
  81. if(typeof a == "number"){
  82. // branch
  83. // a: Number: a uniform scaling factor used for the both coordinates
  84. // b: null
  85. return new m.Matrix2D({xx: a, yy: a}); // dojox.gfx.matrix.Matrix2D
  86. }
  87. // branch
  88. // a: dojox.gfx.Point: a point-like object, which specifies scale factors for both dimensions
  89. // b: null
  90. return new m.Matrix2D({xx: a.x, yy: a.y}); // dojox.gfx.matrix.Matrix2D
  91. },
  92. rotate: function(angle){
  93. // summary: forms a rotating matrix
  94. // description: The resulting matrix is used to rotate points
  95. // around the origin of coordinates (0, 0) by specified angle.
  96. // angle: Number: an angle of rotation in radians (>0 for CW)
  97. var c = Math.cos(angle);
  98. var s = Math.sin(angle);
  99. return new m.Matrix2D({xx: c, xy: -s, yx: s, yy: c}); // dojox.gfx.matrix.Matrix2D
  100. },
  101. rotateg: function(degree){
  102. // summary: forms a rotating matrix
  103. // description: The resulting matrix is used to rotate points
  104. // around the origin of coordinates (0, 0) by specified degree.
  105. // See dojox.gfx.matrix.rotate() for comparison.
  106. // degree: Number: an angle of rotation in degrees (>0 for CW)
  107. return m.rotate(m._degToRad(degree)); // dojox.gfx.matrix.Matrix2D
  108. },
  109. skewX: function(angle) {
  110. // summary: forms an x skewing matrix
  111. // description: The resulting matrix is used to skew points in the x dimension
  112. // around the origin of coordinates (0, 0) by specified angle.
  113. // angle: Number: an skewing angle in radians
  114. return new m.Matrix2D({xy: Math.tan(angle)}); // dojox.gfx.matrix.Matrix2D
  115. },
  116. skewXg: function(degree){
  117. // summary: forms an x skewing matrix
  118. // description: The resulting matrix is used to skew points in the x dimension
  119. // around the origin of coordinates (0, 0) by specified degree.
  120. // See dojox.gfx.matrix.skewX() for comparison.
  121. // degree: Number: an skewing angle in degrees
  122. return m.skewX(m._degToRad(degree)); // dojox.gfx.matrix.Matrix2D
  123. },
  124. skewY: function(angle){
  125. // summary: forms a y skewing matrix
  126. // description: The resulting matrix is used to skew points in the y dimension
  127. // around the origin of coordinates (0, 0) by specified angle.
  128. // angle: Number: an skewing angle in radians
  129. return new m.Matrix2D({yx: Math.tan(angle)}); // dojox.gfx.matrix.Matrix2D
  130. },
  131. skewYg: function(degree){
  132. // summary: forms a y skewing matrix
  133. // description: The resulting matrix is used to skew points in the y dimension
  134. // around the origin of coordinates (0, 0) by specified degree.
  135. // See dojox.gfx.matrix.skewY() for comparison.
  136. // degree: Number: an skewing angle in degrees
  137. return m.skewY(m._degToRad(degree)); // dojox.gfx.matrix.Matrix2D
  138. },
  139. reflect: function(a, b){
  140. // summary: forms a reflection matrix
  141. // description: The resulting matrix is used to reflect points around a vector,
  142. // which goes through the origin.
  143. // a: dojox.gfx.Point: a point-like object, which specifies a vector of reflection
  144. // b: null
  145. if(arguments.length == 1){
  146. b = a.y;
  147. a = a.x;
  148. }
  149. // branch
  150. // a: Number: an x coordinate value
  151. // b: Number: a y coordinate value
  152. // make a unit vector
  153. var a2 = a * a, b2 = b * b, n2 = a2 + b2, xy = 2 * a * b / n2;
  154. return new m.Matrix2D({xx: 2 * a2 / n2 - 1, xy: xy, yx: xy, yy: 2 * b2 / n2 - 1}); // dojox.gfx.matrix.Matrix2D
  155. },
  156. project: function(a, b){
  157. // summary: forms an orthogonal projection matrix
  158. // description: The resulting matrix is used to project points orthogonally on a vector,
  159. // which goes through the origin.
  160. // a: dojox.gfx.Point: a point-like object, which specifies a vector of projection
  161. // b: null
  162. if(arguments.length == 1){
  163. b = a.y;
  164. a = a.x;
  165. }
  166. // branch
  167. // a: Number: an x coordinate value
  168. // b: Number: a y coordinate value
  169. // make a unit vector
  170. var a2 = a * a, b2 = b * b, n2 = a2 + b2, xy = a * b / n2;
  171. return new m.Matrix2D({xx: a2 / n2, xy: xy, yx: xy, yy: b2 / n2}); // dojox.gfx.matrix.Matrix2D
  172. },
  173. // ensure matrix 2D conformance
  174. normalize: function(matrix){
  175. // summary: converts an object to a matrix, if necessary
  176. // description: Converts any 2D matrix-like object or an array of
  177. // such objects to a valid dojox.gfx.matrix.Matrix2D object.
  178. // matrix: Object: an object, which is converted to a matrix, if necessary
  179. return (matrix instanceof m.Matrix2D) ? matrix : new m.Matrix2D(matrix); // dojox.gfx.matrix.Matrix2D
  180. },
  181. // common operations
  182. clone: function(matrix){
  183. // summary: creates a copy of a 2D matrix
  184. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object to be cloned
  185. var obj = new m.Matrix2D();
  186. for(var i in matrix){
  187. if(typeof(matrix[i]) == "number" && typeof(obj[i]) == "number" && obj[i] != matrix[i]) obj[i] = matrix[i];
  188. }
  189. return obj; // dojox.gfx.matrix.Matrix2D
  190. },
  191. invert: function(matrix){
  192. // summary: inverts a 2D matrix
  193. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object to be inverted
  194. var M = m.normalize(matrix),
  195. D = M.xx * M.yy - M.xy * M.yx;
  196. M = new m.Matrix2D({
  197. xx: M.yy/D, xy: -M.xy/D,
  198. yx: -M.yx/D, yy: M.xx/D,
  199. dx: (M.xy * M.dy - M.yy * M.dx) / D,
  200. dy: (M.yx * M.dx - M.xx * M.dy) / D
  201. });
  202. return M; // dojox.gfx.matrix.Matrix2D
  203. },
  204. _multiplyPoint: function(matrix, x, y){
  205. // summary: applies a matrix to a point
  206. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix object to be applied
  207. // x: Number: an x coordinate of a point
  208. // y: Number: a y coordinate of a point
  209. return {x: matrix.xx * x + matrix.xy * y + matrix.dx, y: matrix.yx * x + matrix.yy * y + matrix.dy}; // dojox.gfx.Point
  210. },
  211. multiplyPoint: function(matrix, /* Number||Point */ a, /* Number? */ b){
  212. // summary: applies a matrix to a point
  213. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix object to be applied
  214. // a: Number: an x coordinate of a point
  215. // b: Number?: a y coordinate of a point
  216. var M = m.normalize(matrix);
  217. if(typeof a == "number" && typeof b == "number"){
  218. return m._multiplyPoint(M, a, b); // dojox.gfx.Point
  219. }
  220. // branch
  221. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix object to be applied
  222. // a: dojox.gfx.Point: a point
  223. // b: null
  224. return m._multiplyPoint(M, a.x, a.y); // dojox.gfx.Point
  225. },
  226. multiply: function(matrix){
  227. // summary: combines matrices by multiplying them sequentially in the given order
  228. // matrix: dojox.gfx.matrix.Matrix2D...: a 2D matrix-like object,
  229. // all subsequent arguments are matrix-like objects too
  230. var M = m.normalize(matrix);
  231. // combine matrices
  232. for(var i = 1; i < arguments.length; ++i){
  233. var l = M, r = m.normalize(arguments[i]);
  234. M = new m.Matrix2D();
  235. M.xx = l.xx * r.xx + l.xy * r.yx;
  236. M.xy = l.xx * r.xy + l.xy * r.yy;
  237. M.yx = l.yx * r.xx + l.yy * r.yx;
  238. M.yy = l.yx * r.xy + l.yy * r.yy;
  239. M.dx = l.xx * r.dx + l.xy * r.dy + l.dx;
  240. M.dy = l.yx * r.dx + l.yy * r.dy + l.dy;
  241. }
  242. return M; // dojox.gfx.matrix.Matrix2D
  243. },
  244. // high level operations
  245. _sandwich: function(matrix, x, y){
  246. // summary: applies a matrix at a centrtal point
  247. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object, which is applied at a central point
  248. // x: Number: an x component of the central point
  249. // y: Number: a y component of the central point
  250. return m.multiply(m.translate(x, y), matrix, m.translate(-x, -y)); // dojox.gfx.matrix.Matrix2D
  251. },
  252. scaleAt: function(a, b, c, d){
  253. // summary: scales a picture using a specified point as a center of scaling
  254. // description: Compare with dojox.gfx.matrix.scale().
  255. // a: Number: a scaling factor used for the x coordinate
  256. // b: Number: a scaling factor used for the y coordinate
  257. // c: Number: an x component of a central point
  258. // d: Number: a y component of a central point
  259. // accepts several signatures:
  260. // 1) uniform scale factor, Point
  261. // 2) uniform scale factor, x, y
  262. // 3) x scale, y scale, Point
  263. // 4) x scale, y scale, x, y
  264. switch(arguments.length){
  265. case 4:
  266. // a and b are scale factor components, c and d are components of a point
  267. return m._sandwich(m.scale(a, b), c, d); // dojox.gfx.matrix.Matrix2D
  268. case 3:
  269. if(typeof c == "number"){
  270. // branch
  271. // a: Number: a uniform scaling factor used for both coordinates
  272. // b: Number: an x component of a central point
  273. // c: Number: a y component of a central point
  274. // d: null
  275. return m._sandwich(m.scale(a), b, c); // dojox.gfx.matrix.Matrix2D
  276. }
  277. // branch
  278. // a: Number: a scaling factor used for the x coordinate
  279. // b: Number: a scaling factor used for the y coordinate
  280. // c: dojox.gfx.Point: a central point
  281. // d: null
  282. return m._sandwich(m.scale(a, b), c.x, c.y); // dojox.gfx.matrix.Matrix2D
  283. }
  284. // branch
  285. // a: Number: a uniform scaling factor used for both coordinates
  286. // b: dojox.gfx.Point: a central point
  287. // c: null
  288. // d: null
  289. return m._sandwich(m.scale(a), b.x, b.y); // dojox.gfx.matrix.Matrix2D
  290. },
  291. rotateAt: function(angle, a, b){
  292. // summary: rotates a picture using a specified point as a center of rotation
  293. // description: Compare with dojox.gfx.matrix.rotate().
  294. // angle: Number: an angle of rotation in radians (>0 for CW)
  295. // a: Number: an x component of a central point
  296. // b: Number: a y component of a central point
  297. // accepts several signatures:
  298. // 1) rotation angle in radians, Point
  299. // 2) rotation angle in radians, x, y
  300. if(arguments.length > 2){
  301. return m._sandwich(m.rotate(angle), a, b); // dojox.gfx.matrix.Matrix2D
  302. }
  303. // branch
  304. // angle: Number: an angle of rotation in radians (>0 for CCW)
  305. // a: dojox.gfx.Point: a central point
  306. // b: null
  307. return m._sandwich(m.rotate(angle), a.x, a.y); // dojox.gfx.matrix.Matrix2D
  308. },
  309. rotategAt: function(degree, a, b){
  310. // summary: rotates a picture using a specified point as a center of rotation
  311. // description: Compare with dojox.gfx.matrix.rotateg().
  312. // degree: Number: an angle of rotation in degrees (>0 for CW)
  313. // a: Number: an x component of a central point
  314. // b: Number: a y component of a central point
  315. // accepts several signatures:
  316. // 1) rotation angle in degrees, Point
  317. // 2) rotation angle in degrees, x, y
  318. if(arguments.length > 2){
  319. return m._sandwich(m.rotateg(degree), a, b); // dojox.gfx.matrix.Matrix2D
  320. }
  321. // branch
  322. // degree: Number: an angle of rotation in degrees (>0 for CCW)
  323. // a: dojox.gfx.Point: a central point
  324. // b: null
  325. return m._sandwich(m.rotateg(degree), a.x, a.y); // dojox.gfx.matrix.Matrix2D
  326. },
  327. skewXAt: function(angle, a, b){
  328. // summary: skews a picture along the x axis using a specified point as a center of skewing
  329. // description: Compare with dojox.gfx.matrix.skewX().
  330. // angle: Number: an skewing angle in radians
  331. // a: Number: an x component of a central point
  332. // b: Number: a y component of a central point
  333. // accepts several signatures:
  334. // 1) skew angle in radians, Point
  335. // 2) skew angle in radians, x, y
  336. if(arguments.length > 2){
  337. return m._sandwich(m.skewX(angle), a, b); // dojox.gfx.matrix.Matrix2D
  338. }
  339. // branch
  340. // angle: Number: an skewing angle in radians
  341. // a: dojox.gfx.Point: a central point
  342. // b: null
  343. return m._sandwich(m.skewX(angle), a.x, a.y); // dojox.gfx.matrix.Matrix2D
  344. },
  345. skewXgAt: function(degree, a, b){
  346. // summary: skews a picture along the x axis using a specified point as a center of skewing
  347. // description: Compare with dojox.gfx.matrix.skewXg().
  348. // degree: Number: an skewing angle in degrees
  349. // a: Number: an x component of a central point
  350. // b: Number: a y component of a central point
  351. // accepts several signatures:
  352. // 1) skew angle in degrees, Point
  353. // 2) skew angle in degrees, x, y
  354. if(arguments.length > 2){
  355. return m._sandwich(m.skewXg(degree), a, b); // dojox.gfx.matrix.Matrix2D
  356. }
  357. // branch
  358. // degree: Number: an skewing angle in degrees
  359. // a: dojox.gfx.Point: a central point
  360. // b: null
  361. return m._sandwich(m.skewXg(degree), a.x, a.y); // dojox.gfx.matrix.Matrix2D
  362. },
  363. skewYAt: function(angle, a, b){
  364. // summary: skews a picture along the y axis using a specified point as a center of skewing
  365. // description: Compare with dojox.gfx.matrix.skewY().
  366. // angle: Number: an skewing angle in radians
  367. // a: Number: an x component of a central point
  368. // b: Number: a y component of a central point
  369. // accepts several signatures:
  370. // 1) skew angle in radians, Point
  371. // 2) skew angle in radians, x, y
  372. if(arguments.length > 2){
  373. return m._sandwich(m.skewY(angle), a, b); // dojox.gfx.matrix.Matrix2D
  374. }
  375. // branch
  376. // angle: Number: an skewing angle in radians
  377. // a: dojox.gfx.Point: a central point
  378. // b: null
  379. return m._sandwich(m.skewY(angle), a.x, a.y); // dojox.gfx.matrix.Matrix2D
  380. },
  381. skewYgAt: function(/* Number */ degree, /* Number||Point */ a, /* Number? */ b){
  382. // summary: skews a picture along the y axis using a specified point as a center of skewing
  383. // description: Compare with dojox.gfx.matrix.skewYg().
  384. // degree: Number: an skewing angle in degrees
  385. // a: Number: an x component of a central point
  386. // b: Number?: a y component of a central point
  387. // accepts several signatures:
  388. // 1) skew angle in degrees, Point
  389. // 2) skew angle in degrees, x, y
  390. if(arguments.length > 2){
  391. return m._sandwich(m.skewYg(degree), a, b); // dojox.gfx.matrix.Matrix2D
  392. }
  393. // branch
  394. // degree: Number: an skewing angle in degrees
  395. // a: dojox.gfx.Point: a central point
  396. // b: null
  397. return m._sandwich(m.skewYg(degree), a.x, a.y); // dojox.gfx.matrix.Matrix2D
  398. }
  399. //TODO: rect-to-rect mapping, scale-to-fit (isotropic and anisotropic versions)
  400. });
  401. // propagate Matrix2D up
  402. g.Matrix2D = m.Matrix2D;
  403. return m;
  404. });