decompose.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.gfx.decompose"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.gfx.decompose"] = true;
  8. dojo.provide("dojox.gfx.decompose");
  9. dojo.require("dojox.gfx.matrix");
  10. (function(){
  11. var m = dojox.gfx.matrix;
  12. function eq(/* Number */ a, /* Number */ b){
  13. // summary: compare two FP numbers for equality
  14. return Math.abs(a - b) <= 1e-6 * (Math.abs(a) + Math.abs(b)); // Boolean
  15. }
  16. function calcFromValues(/* Number */ r1, /* Number */ m1, /* Number */ r2, /* Number */ m2){
  17. // summary: uses two close FP ration and their original magnitudes 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. function transpose(/* 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, {dx: 0, dy: 0, xy: M.yx, yx: M.xy}); // dojox.gfx.matrix.Matrix2D
  30. }
  31. function scaleSign(/* dojox.gfx.matrix.Matrix2D */ matrix){
  32. return (matrix.xx * matrix.yy < 0 || matrix.xy * matrix.yx > 0) ? -1 : 1; // Number
  33. }
  34. function eigenvalueDecomposition(/* dojox.gfx.matrix.Matrix2D */ matrix){
  35. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
  36. var M = m.normalize(matrix),
  37. b = -M.xx - M.yy,
  38. c = M.xx * M.yy - M.xy * M.yx,
  39. d = Math.sqrt(b * b - 4 * c),
  40. l1 = -(b + (b < 0 ? -d : d)) / 2,
  41. l2 = c / l1,
  42. vx1 = M.xy / (l1 - M.xx), vy1 = 1,
  43. vx2 = M.xy / (l2 - M.xx), vy2 = 1;
  44. if(eq(l1, l2)){
  45. vx1 = 1, vy1 = 0, vx2 = 0, vy2 = 1;
  46. }
  47. if(!isFinite(vx1)){
  48. vx1 = 1, vy1 = (l1 - M.xx) / M.xy;
  49. if(!isFinite(vy1)){
  50. vx1 = (l1 - M.yy) / M.yx, vy1 = 1;
  51. if(!isFinite(vx1)){
  52. vx1 = 1, vy1 = M.yx / (l1 - M.yy);
  53. }
  54. }
  55. }
  56. if(!isFinite(vx2)){
  57. vx2 = 1, vy2 = (l2 - M.xx) / M.xy;
  58. if(!isFinite(vy2)){
  59. vx2 = (l2 - M.yy) / M.yx, vy2 = 1;
  60. if(!isFinite(vx2)){
  61. vx2 = 1, vy2 = M.yx / (l2 - M.yy);
  62. }
  63. }
  64. }
  65. var d1 = Math.sqrt(vx1 * vx1 + vy1 * vy1),
  66. d2 = Math.sqrt(vx2 * vx2 + vy2 * vy2);
  67. if(!isFinite(vx1 /= d1)){ vx1 = 0; }
  68. if(!isFinite(vy1 /= d1)){ vy1 = 0; }
  69. if(!isFinite(vx2 /= d2)){ vx2 = 0; }
  70. if(!isFinite(vy2 /= d2)){ vy2 = 0; }
  71. return { // Object
  72. value1: l1,
  73. value2: l2,
  74. vector1: {x: vx1, y: vy1},
  75. vector2: {x: vx2, y: vy2}
  76. };
  77. }
  78. function decomposeSR(/* dojox.gfx.matrix.Matrix2D */ M, /* Object */ result){
  79. // summary: decomposes a matrix into [scale, rotate]; no checks are done.
  80. var sign = scaleSign(M),
  81. a = result.angle1 = (Math.atan2(M.yx, M.yy) + Math.atan2(-sign * M.xy, sign * M.xx)) / 2,
  82. cos = Math.cos(a), sin = Math.sin(a);
  83. result.sx = calcFromValues(M.xx / cos, cos, -M.xy / sin, sin);
  84. result.sy = calcFromValues(M.yy / cos, cos, M.yx / sin, sin);
  85. return result; // Object
  86. }
  87. function decomposeRS(/* dojox.gfx.matrix.Matrix2D */ M, /* Object */ result){
  88. // summary: decomposes a matrix into [rotate, scale]; no checks are done
  89. var sign = scaleSign(M),
  90. a = result.angle2 = (Math.atan2(sign * M.yx, sign * M.xx) + Math.atan2(-M.xy, M.yy)) / 2,
  91. cos = Math.cos(a), sin = Math.sin(a);
  92. result.sx = calcFromValues(M.xx / cos, cos, M.yx / sin, sin);
  93. result.sy = calcFromValues(M.yy / cos, cos, -M.xy / sin, sin);
  94. return result; // Object
  95. }
  96. dojox.gfx.decompose = function(matrix){
  97. // summary: decompose a 2D matrix into translation, scaling, and rotation components
  98. // description: this function decompose a matrix into four logical components:
  99. // translation, rotation, scaling, and one more rotation using SVD.
  100. // The components should be applied in following order:
  101. // | [translate, rotate(angle2), scale, rotate(angle1)]
  102. // matrix: dojox.gfx.matrix.Matrix2D: a 2D matrix-like object
  103. var M = m.normalize(matrix),
  104. result = {dx: M.dx, dy: M.dy, sx: 1, sy: 1, angle1: 0, angle2: 0};
  105. // detect case: [scale]
  106. if(eq(M.xy, 0) && eq(M.yx, 0)){
  107. return dojo.mixin(result, {sx: M.xx, sy: M.yy}); // Object
  108. }
  109. // detect case: [scale, rotate]
  110. if(eq(M.xx * M.yx, -M.xy * M.yy)){
  111. return decomposeSR(M, result); // Object
  112. }
  113. // detect case: [rotate, scale]
  114. if(eq(M.xx * M.xy, -M.yx * M.yy)){
  115. return decomposeRS(M, result); // Object
  116. }
  117. // do SVD
  118. var MT = transpose(M),
  119. u = eigenvalueDecomposition([M, MT]),
  120. v = eigenvalueDecomposition([MT, M]),
  121. U = new m.Matrix2D({xx: u.vector1.x, xy: u.vector2.x, yx: u.vector1.y, yy: u.vector2.y}),
  122. VT = new m.Matrix2D({xx: v.vector1.x, xy: v.vector1.y, yx: v.vector2.x, yy: v.vector2.y}),
  123. S = new m.Matrix2D([m.invert(U), M, m.invert(VT)]);
  124. decomposeSR(VT, result);
  125. S.xx *= result.sx;
  126. S.yy *= result.sy;
  127. decomposeRS(U, result);
  128. S.xx *= result.sx;
  129. S.yy *= result.sy;
  130. return dojo.mixin(result, {sx: S.xx, sy: S.yy}); // Object
  131. };
  132. })();
  133. }