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