我们可以做比对角化矩阵的非常通用的方法更具体的事情,假设你只做scaling, rotating, and translating : no shearing (a.k.a. skewing)。
反射是(旋转、平移和)沿一个坐标按 -1 缩放的组合,因此我们可以放心地考虑它。如果您的转换包括剪切,最简单的方法是to refer to MvG's answer。
但是,如果您知道剪切的开始和结束值,您也可以使用下面的 A0 和 A1 作为开始和结束矩阵,方法是移除剪切(将它们乘以两个点的相反剪切矩阵)和然后使用下面定义的矩阵乘以插值剪切。
拼图
根据我们的假设,任意变换矩阵 A 都可以分解为:
| a c tx | | i*x -i*y u |
A = | b d ty | = | j*y j*x v |
| 0 0 1 | | 0 0 1 |
其中 x 和 y 分别是最终旋转角度的正弦和余弦。因此,要获得平移和缩放值,我们可以执行以下操作:
u = A.tx;
v = A.ty;
i = Math.sqrt(A.a * A.a + A.c * A.c);
j = Math.sqrt(A.b * A.b + A.d * A.d);
现在我们需要知道要旋转多少。假设对于0..1 中的每个t,您有一个旋转R(t),这样R(0) = matrix(a=x0, b=y0, c=y0, d=x0) 是初始矩阵A0 中的旋转,同样R(1) = matrix(a=x1, b=y1, c=y1, d=x1) 是最终矩阵A1 中的旋转/p>
那么您将从运动开始到结束应用的总旋转矩阵是T = R(0)^-1 R(1),并且由于R 是一个旋转,它的逆矩阵是它的转置。因此:
| x0 y0 0 | | x1 -y1 0 | | x0*x1 + y0*x1 -x0*y1 + y0*x1 0 |
T = | -y0 x0 0 | x | y1 x1 0 | = |-y0*x1 + x0*y1 y0*y1 + x0*x1 0 |
| 0 0 1 | | 0 0 1 | | 0 0 1 |
这仍然是一个旋转矩阵。因此我们可以得到 T 的旋转角度:
angle = atan2( x0 * y1 - y0 * x1, y0 * y1 + x0 * x1 )
可以重写,通过缩放两边j0*j1(分别从A0和A1定义,因为j是从上面的A定义的。缩放两个组件不会修改atan2的返回值)。 :
angle = atan2(A0.d*A1.b - A0.b*A1.d, A0.b*A1.b + A0.d*A1.d)
把它们放在一起
因此,假设您必须使用矩阵A0 和A1,您从 A0 处的旋转开始,并在缩放和平移坐标上进行简单的线性插值。然后你应用你想要的部分旋转,作为angle的一部分。
// compute i1 / i0 and j1 / j0
i1_0 = Math.sqrt( (A1.a * A1.a + A1.c * A1.c) / (A0.a * A0.a + A0.c * A0.c) );
j1_0 = Math.sqrt( (A1.b * A1.b + A1.d * A1.d) / (A0.b * A0.b + A0.d * A0.d) );
angle = Math.atan2(A0.d*A1.b - A0.b*A1.d, A0.b*A1.b + A0.d*A1.d);
for (i = 0; i < MAX; i++)
{
t = i/MAX;
// the values of i and j at t are scaled by i and j at 0
// in order to reuse easily A0's values, which include initial rotation
it = (1-t) + t * i1_0;
jt = (1-t) + t * j1_0;
At = new Matrix(
a = A0.a * it,
b = A0.b * jt,
c = A0.c * it,
d = A0.d * jt,
tx = (1-t) * A0.tx + t * A1.tx,
ty = (1-t) * A0.ty + t * A1.ty
);
At.rotate( t * angle );
// now apply matrix At instead of A0 or A1
obj.transform.matrix = At;
}
显然,如果你只想要中间的矩阵,只使用一个值为 0.5 的 t。 我们可以变得更聪明,使用 Carnot's linearization formulas for double angles 获得 T 的一半旋转,因此从来没有调用三角公式,但我认为这不值得。
此代码也很容易推广到任何其他编程语言,因为它只使用非常常见的功能。
展示这一点的最简单方法是包含一个 javascript 测试 sn-p,见下文。我使用 actionscript 命名约定来与帖子的其余部分保持一致(即 .a .tx 等)。
function rotate_mat(A, angle)
{
var x = Math.cos(angle);
var y = Math.sin(angle);
var B = {a:x*A.a+y*A.c, b:x*A.b+y*A.d, c:-y*A.a+x*A.c, d:-y*A.b+x*A.d, tx:A.tx, ty:A.ty};
return B;
}
function apply(id,A)
{
var mat="matrix("+A.a+","+A.b+","+A.c+","+A.d+","+A.tx+","+A.ty+")";
document.getElementById(id).style.transform=mat;
}
function val(str) { return document.getElementById(str).value; }
document.getElementById("go").onclick = function(options)
{
var A0 = {a:val("a0"), b:val("b0"), c:val("c0"), d:val("d0"), tx:val("tx0"), ty:val("ty0")};
var A1 = {a:val("a1"), b:val("b1"), c:val("c1"), d:val("d1"), tx:val("tx1"), ty:val("ty1")};
apply("guineapig", A0);
var i1_0 = Math.sqrt( (A1.a * A1.a + A1.c * A1.c) / (A0.a * A0.a + A0.c * A0.c) );
var j1_0 = Math.sqrt( (A1.b * A1.b + A1.d * A1.d) / (A0.b * A0.b + A0.d * A0.d) );
var angle = Math.atan2(A0.d*A1.b - A0.b*A1.d, A0.b*A1.b + A0.d*A1.d);
var timer;
var MAX = +val("MAX");
var i = 0;
function update()
{
if( ++i == MAX )
clearInterval(timer);
var t = i/MAX;
// the values of i and j at t are scaled by i and j at 0
// in order to reuse easily A0's values, which include initial rotation
var it = 1-t + t * i1_0;
var jt = 1-t + t * j1_0;
At = {
a : A0.a * it,
b : A0.b * jt,
c : A0.c * it,
d : A0.d * jt,
tx : (1-t) * A0.tx + t * A1.tx,
ty : (1-t) * A0.ty + t * A1.ty
};
//At.rotate( t * angle );
At = rotate_mat(At, t * angle);
// now apply matrix At instead of A0 or A1
//obj.transform.matrix = At;
apply("guineapig", At);
}
var step = +val("step");
setTimeout(function(){timer=setInterval(update,step);},10*step);
};
<p>Matrix A0 :
<label>a<input type="text" size="2" id ="a0" value="1" /></label>
<label>b<input type="text" size="2" id ="b0" value="0" /></label>
<label>c<input type="text" size="2" id ="c0" value="0" /></label>
<label>d<input type="text" size="2" id ="d0" value="1" /></label>
<label>tx<input type="text" size="2" id ="tx0" value="0" /></label>
<label>ty<input type="text" size="2" id ="ty0" value="0" /></label>
</p>
<p>Matrix A1 :
<label>a<input type="text" size="2" id ="a1" value="1.41421356237" /></label>
<label>b<input type="text" size="2" id ="b1" value="1.41421356237" /></label>
<label>c<input type="text" size="2" id ="c1" value="-1.41421356237" /></label>
<label>d<input type="text" size="2" id ="d1" value="1.41421356237" /></label>
<label>tx<input type="text" size="2" id ="tx1" value="40" /></label>
<label>ty<input type="text" size="2" id ="ty1" value="40" /></label>
</p>
<p><label>frames<input type="text" size="2" id="MAX" value="100" /></label><label>interval (ms)<input type="text" size="2" id="step" value="10" /></label><button id="go">Animate !</button></p>
<div id="c" style="height:200px;width:200px;border:thin blue solid">
<div id="guineapig" style="height:40px;width:40px;position:relative;top:80px;left:80px;background:green;" />
</div>
PS:您肯定需要 MvG 中提到的齐次坐标,否则您可能会遇到看起来更像螺旋弧的插值运动,而不是沿着线性运动进行旋转和缩放。
由于Adobe's documentation 对此不是很清楚,而且我知道 SVG 变换矩阵会发生这种情况,以下是如何弥补这一点:将变换应用于 x 和 y 坐标,并且将结果添加到转换值 u 和 v,同时将 x 和 y 设置为 0。因此,在您执行任何操作之前:
correction = A0.transformPoint(new Point(x=obj.x, y=obj.y));
A0.tx += correction.x;
A0.ty += correction.y;
correction = A1.transformPoint(new Point(x=obj.x, y=obj.y));
A1.tx += correction.x;
A1.ty += correction.y;
obj.x = 0;
obj.y = 0;
请注意,如果您愿意,可以在动画完成后保存这些值 x 和 y 以撤消此技巧。但老实说,一旦有了变换,坐标就变得多余了。