【问题标题】:How can you calculate a transformation matrix that is halfway between 2 other matrices?如何计算介于其他 2 个矩阵之间的变换矩阵?
【发布时间】:2015-01-20 02:18:30
【问题描述】:

如果我想将值从 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  |

但是,结果旋转了 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. 然后根据插值创建一个矩阵,并应用转换。

【问题讨论】:

  • 对于纯旋转的情况,您必须使用 Theta=(Alpha1 + Alpha2)/2 和 Cos(Theta), +/-Sin(Theta) 作为矩阵元素
  • 别忘了,w 在所有矩阵中都是 1。
  • 糟糕,更新了 w 的值。
  • 该死的,那个更新真的扼杀了我的答案。好吧,真可惜。
  • 我有一个解决方案 - 有点,为此;但它只会给你旋转而不是倾斜;如果您对部分解决方案感兴趣,我将在下面发布...mathb.in/51333 是其中的要点;如果您可以将矩阵转换为轴角,则在任何两个方向之间求解变得微不足道。

标签: actionscript-3 math matrix tween


【解决方案1】:

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

看看this post of mine on Math SE。要在两个给定矩阵 AB 之间找到一个矩阵,您需要计算 C=B A−1diagonalize 表示为 C=P D P-1,然后得到一个中间的矩阵为E=P D ½P A。 IE。你取特征值的平方根,这又可能是共轭复数。如果这些方形根部是复杂的,它们应该彼此共轭,因此适当地选择complex square root的分支。

确保通过对homogeneous coordinates 进行运算,在expresses your transformation 的方阵上执行所有这些操作。否则您选择的坐标系会影响结果。如果您的 3×3 矩阵要表示平面变换,我希望右下角的条目为 1。如果它们是空间中的线性运算,则通过在右侧和底部添加 0,但在右下角添加 1,将它们扩展到 4×4。

【讨论】:

  • 其实在AS3中最好使用定义形式的平移、缩放和旋转,而不是把矩阵当作复合来抓取。将涉及相同的数学,但构造正确的矩阵比从开始矩阵和结束(所需)矩阵派生要容易得多。不过,如果不知道“基本”参数,这个答案还是有用的。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    我们可以做比对角化矩阵的非常通用的方法更具体的事情,假设你只做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);
    

    现在我们需要知道要旋转多少。假设对于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)
    

    把它们放在一起

    因此,假设您必须使用矩阵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 测试 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 变换矩阵会发生这种情况,以下是如何弥补这一点:将变换应用于 xy 坐标,并且将结果添加到转换值 uv,同时将 xy 设置为 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;
    

    请注意,如果您愿意,可以在动画完成后保存这些值 xy 以撤消此技巧。但老实说,一旦有了变换,坐标就变得多余了。

    【讨论】:

      猜你喜欢
      • 2018-08-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-02
      • 2020-05-19
      • 1970-01-01
      • 2015-05-17
      • 1970-01-01
      相关资源
      最近更新 更多