如何计算两个矩阵之间的转换矩阵?

时间:2015-01-20 02:18:31

标签: actionscript-3 math matrix tween

如果我想将1个转换矩阵中的值设置为另一个转换矩阵,我怎样才能找到代表转换的转换矩阵?

AS3矩阵:

|  a  b  u  |
|  c  d  v  |
|  x  y  w  |

矩阵A(无刻度,倾斜或旋转)

|  1  0  0  |
|  0  1  0  |
|  0  0  1  |

矩阵B(无刻度或歪斜。旋转90°)

|  0  1  0  |
|  -1 0  0  |
|  0  0  1  |

为了找到中间的矩阵,这将是45°的旋转,我的第一个猜测是我应该简单地找到矩阵中每个值的中间值,这将是:

|  0.5  0.5  0  |
|  -0.5 0.5  0  |
|  0    0    1  |

然而,结果IS旋转了45°,但它也缩小了。

我已经发现这个案例的矩阵实际上应该接近这个:

|  0.7  0.7  0  |
|  -0.7 0.7  0  |
|  0    0    1  |

但是我可以使用什么公式或操作来获得任何2个矩阵的正确结果?

更新1 - 预期用途: 我需要这个适用于任何2个矩阵,这两个矩阵都可能有平移,缩放,倾斜和/或旋转。

我正在创建一个工具,用于检查Flash中时间轴上的关键帧,并导出要在其他环境中使用的转换值,最初是Unity中的C#。我使用矩阵而不是旋转,缩放和偏斜值的原因是Flash与报告偏斜和旋转的方式不一致,但报告的变换矩阵是可靠的。

同样在Unity中,我将变换应用于网格的点,因此将其作为矩阵是有帮助的。基本上是这样的:

x' = (x * a) - (y * c);
y' = (x * b) - (y * d);

所以我从这些非常有用的答案中思考的是,不是试图自己插入矩阵,也许我应该:

  1. 抓住转换矩阵。
  2. 基于矩阵,提取平移,缩放,倾斜和旋转的值。
  3. 在补间期间,插入这些值。
  4. 然后根据插值创建矩阵,并应用变换。

3 个答案:

答案 0 :(得分:0)

这是一个非常通用的数学答案。如果您只想在公共轴周围进行插值computing the angle of rotation,则插入它并为新角度构造rotation matrix可能就足够了。在这种情况下,请随意忽略这篇文章的其余部分。

看看this post of mine on Math SE。要在两个给定矩阵 A B 之间找到一个矩阵,你需要计算 C = B < i> A -1 diagonalize C = P D < i> P -1 ,然后在其中间获得矩阵,因为 E = P ½ P A 。即你取特征值的平方根,而特征值又可能是共轭复数。如果这些平方根很复杂,它们应该相互结合,所以适当选择complex square root的分支。

确保通过expresses your transformation操作,在homogeneous coordinates的方阵上执行所有这些操作。否则,您选择的坐标系将影响结果。如果你的3×3矩阵代表平面变换,我希望在右下角输入1。如果它们是空间中的线性运算,则通过向右和底部添加零来将它们扩展为4×4,但在右下角添加一个。

答案 1 :(得分:0)

您需要根据要插入的任何变换手动重新计算矩阵。比如,您希望对象慢慢缩小并旋转,然后使用一个函数,根据当前补间位置组成正确的矩阵。一个例子:

var my_tween:Tween=new Tween(clip_mc,'alpha',Strong.easeOut,0,1,1,true);
my_tween.addEventListener(TweenEvent.MOTION_CHANGE,tweenToFinal);
function tweenToFinal(event:TweenEvent):void
{
    var mat:Matrix=new Matrix();
    mat.scale(startScale+(endScale-startScale)*event.position,startScale+(endScale-startScale)*event.position);
    mat.rotate(startAngle+(endAngle-startAngle)*event.position);
    yourObject.transform.matrix=mat;
}

此补间将分配每个帧的事件,tweenToFinal计算所需变换的新矩阵。请注意,startScalestartAngle应该是对象开始转换的值,比如它是逆时针旋转45度,startAngle应该等于Math.PI/4

答案 2 :(得分:0)

我们可以做的具体事情比对角化矩阵的一般方法要好,假设你只做scaling, rotating, and translating : no shearing (a.k.a. skewing)

反射是(旋转,平移和组合)沿一个坐标缩放-1,因此我们可以安全地考虑它。如果您的转化包括剪切,最简单的方法是to refer to MvG's answer

但是,如果您知道起始和终止剪切值,您也可以使用下面的A0A1作为开始和结束矩阵,通过去除剪切(将它们乘以相反剪切的矩阵)两个点然后使用下面定义的矩阵乘以插值剪切。

拼图的碎片

根据我们的假设,给出了任何变换矩阵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);

现在我们需要知道旋转多少。假设对于t中的每个0..1,您有一个轮换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

中的旋转

然后,您将从移动的开始到结束应用的总旋转矩阵为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)

将它们放在一起

因此,假设您必须使用矩阵A0A1,则从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测试代码段,如下所示。我使用了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和{{ 1}}坐标,并将结果添加到翻译值yu,同时将vx设置为0.因此,在您执行任何操作之前:

y

请注意,如果需要,您可以保存这些值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以在动画完成后撤消此技巧。但说实话,一旦你进行了变换,坐标就是多余的。