decompose.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. if (!dojo._hasResource["dojox.gfx.decompose"]) { // _hasResource checks added
  2. // by build. Do not use
  3. // _hasResource directly in
  4. // your code.
  5. dojo._hasResource["dojox.gfx.decompose"] = true;
  6. dojo.provide("dojox.gfx.decompose");
  7. dojo.require("dojox.gfx.matrix");
  8. (function() {
  9. var m = dojox.gfx.matrix;
  10. var eq = function(/* Number */a, /* Number */b) {
  11. // summary: compare two FP numbers for equality
  12. return Math.abs(a - b) <= 1e-6 * (Math.abs(a) + Math.abs(b)); // Boolean
  13. };
  14. var calcFromValues = function(/* Number */r1, /* Number */m1, /* Number */
  15. r2, /* Number */m2) {
  16. // summary: uses two close FP ration and their original magnitudes
  17. // to approximate the result
  18. if (!isFinite(r1)) {
  19. return r2; // Number
  20. } else if (!isFinite(r2)) {
  21. return r1; // Number
  22. }
  23. m1 = Math.abs(m1), m2 = Math.abs(m2);
  24. return (m1 * r1 + m2 * r2) / (m1 + m2); // Number
  25. };
  26. var transpose = function(/* dojox.gfx.matrix.Matrix2D */matrix) {
  27. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
  28. var M = new m.Matrix2D(matrix);
  29. return dojo.mixin(M, {
  30. dx : 0,
  31. dy : 0,
  32. xy : M.yx,
  33. yx : M.xy
  34. }); // dojox.gfx.matrix.Matrix2D
  35. };
  36. var scaleSign = function(/* dojox.gfx.matrix.Matrix2D */matrix) {
  37. return (matrix.xx * matrix.yy < 0 || matrix.xy * matrix.yx > 0)
  38. ? -1
  39. : 1; // Number
  40. };
  41. var eigenvalueDecomposition = function(
  42. /* dojox.gfx.matrix.Matrix2D */matrix) {
  43. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
  44. var M = m.normalize(matrix), b = -M.xx - M.yy, c = M.xx * M.yy
  45. - M.xy * M.yx, d = Math.sqrt(b * b - 4 * c), l1 = -(b + (b < 0
  46. ? -d
  47. : d))
  48. / 2, l2 = c / l1, vx1 = M.xy / (l1 - M.xx), vy1 = 1, vx2 = M.xy
  49. / (l2 - M.xx), vy2 = 1;
  50. if (eq(l1, l2)) {
  51. vx1 = 1, vy1 = 0, vx2 = 0, vy2 = 1;
  52. }
  53. if (!isFinite(vx1)) {
  54. vx1 = 1, vy1 = (l1 - M.xx) / M.xy;
  55. if (!isFinite(vy1)) {
  56. vx1 = (l1 - M.yy) / M.yx, vy1 = 1;
  57. if (!isFinite(vx1)) {
  58. vx1 = 1, vy1 = M.yx / (l1 - M.yy);
  59. }
  60. }
  61. }
  62. if (!isFinite(vx2)) {
  63. vx2 = 1, vy2 = (l2 - M.xx) / M.xy;
  64. if (!isFinite(vy2)) {
  65. vx2 = (l2 - M.yy) / M.yx, vy2 = 1;
  66. if (!isFinite(vx2)) {
  67. vx2 = 1, vy2 = M.yx / (l2 - M.yy);
  68. }
  69. }
  70. }
  71. var d1 = Math.sqrt(vx1 * vx1 + vy1 * vy1), d2 = Math.sqrt(vx2 * vx2
  72. + vy2 * vy2);
  73. if (!isFinite(vx1 /= d1)) {
  74. vx1 = 0;
  75. }
  76. if (!isFinite(vy1 /= d1)) {
  77. vy1 = 0;
  78. }
  79. if (!isFinite(vx2 /= d2)) {
  80. vx2 = 0;
  81. }
  82. if (!isFinite(vy2 /= d2)) {
  83. vy2 = 0;
  84. }
  85. return { // Object
  86. value1 : l1,
  87. value2 : l2,
  88. vector1 : {
  89. x : vx1,
  90. y : vy1
  91. },
  92. vector2 : {
  93. x : vx2,
  94. y : vy2
  95. }
  96. };
  97. };
  98. var decomposeSR = function(/* dojox.gfx.matrix.Matrix2D */M, /* Object */
  99. result) {
  100. // summary: decomposes a matrix into [scale, rotate]; no checks are
  101. // done.
  102. var sign = scaleSign(M), a = result.angle1 = (Math
  103. .atan2(M.yx, M.yy) + Math.atan2(-sign * M.xy, sign * M.xx))
  104. / 2, cos = Math.cos(a), sin = Math.sin(a);
  105. result.sx = calcFromValues(M.xx / cos, cos, -M.xy / sin, sin);
  106. result.sy = calcFromValues(M.yy / cos, cos, M.yx / sin, sin);
  107. return result; // Object
  108. };
  109. var decomposeRS = function(/* dojox.gfx.matrix.Matrix2D */M, /* Object */
  110. result) {
  111. // summary: decomposes a matrix into [rotate, scale]; no checks are
  112. // done
  113. var sign = scaleSign(M), a = result.angle2 = (Math.atan2(sign
  114. * M.yx, sign * M.xx) + Math.atan2(-M.xy, M.yy))
  115. / 2, cos = Math.cos(a), sin = Math.sin(a);
  116. result.sx = calcFromValues(M.xx / cos, cos, M.yx / sin, sin);
  117. result.sy = calcFromValues(M.yy / cos, cos, -M.xy / sin, sin);
  118. return result; // Object
  119. };
  120. dojox.gfx.decompose = function(matrix) {
  121. // summary: decompose a 2D matrix into translation, scaling, and
  122. // rotation components
  123. // description: this function decompose a matrix into four logical
  124. // components:
  125. // translation, rotation, scaling, and one more rotation using SVD.
  126. // The components should be applied in following order:
  127. // | [translate, rotate(angle2), scale, rotate(angle1)]
  128. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
  129. var M = m.normalize(matrix), result = {
  130. dx : M.dx,
  131. dy : M.dy,
  132. sx : 1,
  133. sy : 1,
  134. angle1 : 0,
  135. angle2 : 0
  136. };
  137. // detect case: [scale]
  138. if (eq(M.xy, 0) && eq(M.yx, 0)) {
  139. return dojo.mixin(result, {
  140. sx : M.xx,
  141. sy : M.yy
  142. }); // Object
  143. }
  144. // detect case: [scale, rotate]
  145. if (eq(M.xx * M.yx, -M.xy * M.yy)) {
  146. return decomposeSR(M, result); // Object
  147. }
  148. // detect case: [rotate, scale]
  149. if (eq(M.xx * M.xy, -M.yx * M.yy)) {
  150. return decomposeRS(M, result); // Object
  151. }
  152. // do SVD
  153. var MT = transpose(M), u = eigenvalueDecomposition([M, MT]), v = eigenvalueDecomposition([
  154. MT, M]), U = new m.Matrix2D({
  155. xx : u.vector1.x,
  156. xy : u.vector2.x,
  157. yx : u.vector1.y,
  158. yy : u.vector2.y
  159. }), VT = new m.Matrix2D({
  160. xx : v.vector1.x,
  161. xy : v.vector1.y,
  162. yx : v.vector2.x,
  163. yy : v.vector2.y
  164. }), S = new m.Matrix2D([m.invert(U), M, m.invert(VT)]);
  165. decomposeSR(VT, result);
  166. S.xx *= result.sx;
  167. S.yy *= result.sy;
  168. decomposeRS(U, result);
  169. S.xx *= result.sx;
  170. S.yy *= result.sy;
  171. return dojo.mixin(result, {
  172. sx : S.xx,
  173. sy : S.yy
  174. }); // Object
  175. };
  176. })();
  177. }