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