decompose.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. define("dojox/gfx/decompose", ["./_base", "dojo/_base/lang", "./matrix"],
  2. function (g, lang, m){
  3. /*===== g = dojox.gfx =====*/
  4. function eq(/* Number */ a, /* Number */ b){
  5. // summary: compare two FP numbers for equality
  6. return Math.abs(a - b) <= 1e-6 * (Math.abs(a) + Math.abs(b)); // Boolean
  7. }
  8. function calcFromValues(/* Number */ r1, /* Number */ m1, /* Number */ r2, /* Number */ m2){
  9. // summary: uses two close FP ration and their original magnitudes to approximate the result
  10. if(!isFinite(r1)){
  11. return r2; // Number
  12. }else if(!isFinite(r2)){
  13. return r1; // Number
  14. }
  15. m1 = Math.abs(m1); m2 = Math.abs(m2);
  16. return (m1 * r1 + m2 * r2) / (m1 + m2); // Number
  17. }
  18. function transpose(/* dojox.gfx.matrix.Matrix2D */ matrix){
  19. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
  20. var M = new m.Matrix2D(matrix);
  21. return lang.mixin(M, {dx: 0, dy: 0, xy: M.yx, yx: M.xy}); // dojox.gfx.matrix.Matrix2D
  22. }
  23. function scaleSign(/* dojox.gfx.matrix.Matrix2D */ matrix){
  24. return (matrix.xx * matrix.yy < 0 || matrix.xy * matrix.yx > 0) ? -1 : 1; // Number
  25. }
  26. function eigenvalueDecomposition(/* dojox.gfx.matrix.Matrix2D */ matrix){
  27. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
  28. var M = m.normalize(matrix),
  29. b = -M.xx - M.yy,
  30. c = M.xx * M.yy - M.xy * M.yx,
  31. d = Math.sqrt(b * b - 4 * c),
  32. l1 = -(b + (b < 0 ? -d : d)) / 2,
  33. l2 = c / l1,
  34. vx1 = M.xy / (l1 - M.xx), vy1 = 1,
  35. vx2 = M.xy / (l2 - M.xx), vy2 = 1;
  36. if(eq(l1, l2)){
  37. vx1 = 1, vy1 = 0, vx2 = 0, vy2 = 1;
  38. }
  39. if(!isFinite(vx1)){
  40. vx1 = 1, vy1 = (l1 - M.xx) / M.xy;
  41. if(!isFinite(vy1)){
  42. vx1 = (l1 - M.yy) / M.yx, vy1 = 1;
  43. if(!isFinite(vx1)){
  44. vx1 = 1, vy1 = M.yx / (l1 - M.yy);
  45. }
  46. }
  47. }
  48. if(!isFinite(vx2)){
  49. vx2 = 1, vy2 = (l2 - M.xx) / M.xy;
  50. if(!isFinite(vy2)){
  51. vx2 = (l2 - M.yy) / M.yx, vy2 = 1;
  52. if(!isFinite(vx2)){
  53. vx2 = 1, vy2 = M.yx / (l2 - M.yy);
  54. }
  55. }
  56. }
  57. var d1 = Math.sqrt(vx1 * vx1 + vy1 * vy1),
  58. d2 = Math.sqrt(vx2 * vx2 + vy2 * vy2);
  59. if(!isFinite(vx1 /= d1)){ vx1 = 0; }
  60. if(!isFinite(vy1 /= d1)){ vy1 = 0; }
  61. if(!isFinite(vx2 /= d2)){ vx2 = 0; }
  62. if(!isFinite(vy2 /= d2)){ vy2 = 0; }
  63. return { // Object
  64. value1: l1,
  65. value2: l2,
  66. vector1: {x: vx1, y: vy1},
  67. vector2: {x: vx2, y: vy2}
  68. };
  69. }
  70. function decomposeSR(/* dojox.gfx.matrix.Matrix2D */ M, /* Object */ result){
  71. // summary: decomposes a matrix into [scale, rotate]; no checks are done.
  72. var sign = scaleSign(M),
  73. a = result.angle1 = (Math.atan2(M.yx, M.yy) + Math.atan2(-sign * M.xy, sign * M.xx)) / 2,
  74. cos = Math.cos(a), sin = Math.sin(a);
  75. result.sx = calcFromValues(M.xx / cos, cos, -M.xy / sin, sin);
  76. result.sy = calcFromValues(M.yy / cos, cos, M.yx / sin, sin);
  77. return result; // Object
  78. }
  79. function decomposeRS(/* dojox.gfx.matrix.Matrix2D */ M, /* Object */ result){
  80. // summary: decomposes a matrix into [rotate, scale]; no checks are done
  81. var sign = scaleSign(M),
  82. a = result.angle2 = (Math.atan2(sign * M.yx, sign * M.xx) + Math.atan2(-M.xy, M.yy)) / 2,
  83. cos = Math.cos(a), sin = Math.sin(a);
  84. result.sx = calcFromValues(M.xx / cos, cos, M.yx / sin, sin);
  85. result.sy = calcFromValues(M.yy / cos, cos, -M.xy / sin, sin);
  86. return result; // Object
  87. }
  88. return g.decompose = function(matrix){
  89. // summary: Decompose a 2D matrix into translation, scaling, and rotation components.
  90. // description: This function decompose a matrix into four logical components:
  91. // translation, rotation, scaling, and one more rotation using SVD.
  92. // The components should be applied in following order:
  93. // | [translate, rotate(angle2), scale, rotate(angle1)]
  94. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
  95. var M = m.normalize(matrix),
  96. result = {dx: M.dx, dy: M.dy, sx: 1, sy: 1, angle1: 0, angle2: 0};
  97. // detect case: [scale]
  98. if(eq(M.xy, 0) && eq(M.yx, 0)){
  99. return lang.mixin(result, {sx: M.xx, sy: M.yy}); // Object
  100. }
  101. // detect case: [scale, rotate]
  102. if(eq(M.xx * M.yx, -M.xy * M.yy)){
  103. return decomposeSR(M, result); // Object
  104. }
  105. // detect case: [rotate, scale]
  106. if(eq(M.xx * M.xy, -M.yx * M.yy)){
  107. return decomposeRS(M, result); // Object
  108. }
  109. // do SVD
  110. var MT = transpose(M),
  111. u = eigenvalueDecomposition([M, MT]),
  112. v = eigenvalueDecomposition([MT, M]),
  113. U = new m.Matrix2D({xx: u.vector1.x, xy: u.vector2.x, yx: u.vector1.y, yy: u.vector2.y}),
  114. VT = new m.Matrix2D({xx: v.vector1.x, xy: v.vector1.y, yx: v.vector2.x, yy: v.vector2.y}),
  115. S = new m.Matrix2D([m.invert(U), M, m.invert(VT)]);
  116. decomposeSR(VT, result);
  117. S.xx *= result.sx;
  118. S.yy *= result.sy;
  119. decomposeRS(U, result);
  120. S.xx *= result.sx;
  121. S.yy *= result.sy;
  122. return lang.mixin(result, {sx: S.xx, sy: S.yy}); // Object
  123. };
  124. });