通过线性组合转换矩阵的旋转动画可以实现放大缩小

Ale*_*lex 5 javascript animation transform matrix

我有一个3x3矩阵(startMatrix),它代表图像的实际视图(平移,旋转和缩放).现在我创建一个新的矩阵(endMatrix),其中包含一个identitymatrix,新的x和y坐标,新的角度和新的比例,如:

endMatrix = translate(identityMatrix, -x, -y);  
endMatrix = rotate(endMatrix, angle);  
endMatrix = scale(endMatrix, scale);
endMatrix = translate(endMatrix,(screen.width/2)/scale,screen.height/2)/scale);
Run Code Online (Sandbox Code Playgroud)

和功能(标准的东西)

function scale(m,s) {
    var n = new Matrix([
        [s, 0, 0],
        [0, s, 0],
        [0, 0, s]
    ]);
    return n.multiply(m);
}
function rotate(m, theta) {
    var n = new Matrix([
        [Math.cos(theta), -Math.sin(theta), 0],
        [Math.sin(theta), Math.cos(theta), 0],
        [0, 0, 1]
    ]);
    return n.multiply(m);
}
function translate(m, x, y) {
    var n = new Matrix([
        [1, 0, x],
        [0, 1, y],
        [0, 0, 1]
    ]);
    return n.multiply(m);
}
Run Code Online (Sandbox Code Playgroud)

之后,我使用css transform matrix3d转换图像(3d仅用于硬件加速).使用requestAnimationFrame动画此变换.

我的startMatrix就是例如

在此输入图像描述

和endMatrix

在此输入图像描述

线性组合看起来像:

在此输入图像描述

t从0到1

变换矩阵的线性组合(得到的图像位置)的结果是正确的,我现在的问题是:如果新角度与实际角度相差大约180度,则endMatrix值从正变为负(或者反之)周围).这导致变换图像的动画中的放大缩小效果.

有没有办法防止这种情况,最好使用一个矩阵进行转换?

Ter*_*nen 4

如果直接插值矩阵值将会出现问题,对于非常小的角度,无法观察到不准确性,但从长远来看,您将面临问题。即使您对矩阵进行归一化,更大的角度也会使问题在视觉上变得明显。

2D 旋转非常简单,因此即使不使用旋转矩阵方法也可以做得很好。最好的方法可能是使用四元数,但四元数可能更适合 3D 转换。

采取的步骤是:

  1. 计算旋转、缩放和变换值。如果您已经有了这些,则可以跳过此步骤。对于 2D 矩阵变换,将这些值分开可能是最简单的方法。
  2. 然后对这些值应用插值
  3. 根据计算构造一个新矩阵

在动画开始时,您必须计算步骤 1 中的值一次,然后在每一帧应用步骤 2 和 3。

第1步:获取旋转、缩放、变换

假设起始矩阵为S,结束矩阵为E。

转换值只是最后一列,例如

var start_tx = S[0][2];
var start_ty = S[1][2];
var end_tx = E[0][2];
var end_ty = E[1][2];   
Run Code Online (Sandbox Code Playgroud)

例如,非倾斜二维矩阵的尺度只是矩阵所跨越的空间中任一基向量的长度

// scale is just the length of the rotation matrixes vector
var startScale = Math.sqrt( S[0][0]*S[0][0] + S[1][0]*S[1][0]);
var endScale = Math.sqrt( E[0][0]*E[0][0] + E[1][0]*E[1][0]);
Run Code Online (Sandbox Code Playgroud)

最难的部分是获取矩阵的旋转值。好处是每次插值只需计算一次。

两个二维矩阵的旋转角度可以根据矩阵列创建的向量之间的角度来计算。如果没有旋转,第一列的值 (1,0) 代表 x 轴,第二列的值 (0,1) 代表 y 轴。

通常,矩阵 S 的 x 轴位置表示为

(S[0][0],S[0][1])

y轴指向方向

(S[1][0],S[1][1])

对于任何 2D 3x3 矩阵也是如此,例如 E。

使用此信息,您可以仅使用标准向量数学来确定两个矩阵之间的旋转角度 - 如果我们假设没有倾斜。

// normalize column vectors
var s00 = S[0][0]/ startScale;  // x-component
var s01 = S[0][1]/ startScale;  // y-component
var e00 = E[0][0]/ endScale;    // x-component
var e01 = E[0][1]/ endScale;    // y-component
// calculate dot product which is the cos of the angle
var dp_start   = s00*1 + s01*0;     // base rotation, dot prod against x-axis
var dp_between = s00*e00 + s01*e01; // between matrices
var startRotation  = Math.acos( dp_start );
var deltaRotation  = Math.acos( dp_between );

// if detect clockwise rotation, the y -comp of x-axis < 0
if(S[0][1]<0) startRotation = -1*startRotation;

// for the delta rotation calculate cross product
var cp_between = s00*e01 - s01*e00;
if(cp_between<0) deltaRotation = deltaRotation*-1;

var endRotation = startRotation + deltaRotation;
Run Code Online (Sandbox Code Playgroud)

这里的startRotation仅根据矩阵第一个值的acos计算。然而,第二列的第一个值,即-sin(angle)大于零,则矩阵已顺时针旋转,并且角度必须为负。必须这样做,因为 acos 只给出正值。

另一种思考方式是考虑叉积 s00*e01 - s01*e00,其中起始位置 (s00,s01) 是 x 轴,其中 s00 == 1 且 s01 == 0,结束位置 (e00, e01) 是( S[0][0], S[0][1] ) 创建叉积

 1 * S[0][1] - 0 * S[0][0]
Run Code Online (Sandbox Code Playgroud)

即S[0][1]。如果该值为负,则 x 轴已转向顺时针方向。

对于 endRotation,我们需要从 S 到 E 的增量旋转。这可以通过矩阵所跨越的向量之间的点积类似地计算。同样,我们测试叉积以查看旋转方向是否为顺时针(负角度)。

第 2 步:插值

在动画期间获取新值是微不足道的插值:

var scale = startScale + t*(endScale-startScale);
var rotation = startRotation + t*(endRotation-startRotation);
var tx = start_tx + t*(end_tx-start_tx);
var ty = start_ty + t*(end_ty-start_ty);
Run Code Online (Sandbox Code Playgroud)

步骤3构造矩阵

对于每个帧构造最终矩阵,您只需将值放入变换矩阵矩阵中

var cs = Math.cos(rotation);
var sn = Math.sin(rotation);
var matrix_values = [[scale*cs, -scale*sn, tx], [scale*sn, scale*cs, ty], [0,0,1]]
Run Code Online (Sandbox Code Playgroud)

然后您就有了一个 2D 矩阵,它也很容易为任何 3D 硬件加速器提供数据。

免责声明:有些代码已经过测试,有些代码尚未经过测试,因此很可能会发现错误。

  • 这是第一个给我们正确提示的答案:不能通过单一的线性组合来保持尺度。这也是我们问题的解决方案,所以我会把赏金奖励给你。但它带来了新的问题:旋转中心任意且不可控。为了解决这个问题,我们使用了 @miks 解决方案的调整。现在,我们计算每个动画步骤的旋转中心,并为每个 t 构建一个矩阵。 (2认同)