通过变换矩阵的线性组合进行旋转动画会导致放大-缩小

亚历克斯

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

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

和功能(标准的东西)

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);
}

之后,我使用css转换matrix3d(仅用于硬件加速的3d)来转换图像。此转换使用requestAnimationFrame进行动画处理

以我的startMatrix为例

在此处输入图片说明

和endMatrix

在此处输入图片说明

线性组合如下所示:

在此处输入图片说明

t从0到1

变换矩阵的线性组合(最终的图像位置)的结果是正确的,我现在的问题是:如果新角度与实际角度相差约180度,则endMatrix值将从正变为负(或以其他方式改变)大约)。这会在转换后的图像的动画中产生放大缩小效果。

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

特罗·托洛宁

如果直接插值矩阵值,将会出现问题,对于很小的角度,无法观察到误差,但是从长远来看,您将面临问题。即使将矩阵归一化,较大的角度也会使问题在视觉上显而易见。

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];   

例如,非倾斜2D矩阵的标度只是该矩阵所跨越的空间中基矢量之一的长度。

// 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]);

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

可以基于矩阵列正在创建的向量之间的角度来计算两个2D矩阵的旋转角度。如果没有旋转,则第一列的值(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;

在这里,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]

这是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);

步骤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]]

然后,您将拥有一个2D矩阵,该矩阵也可以轻松地添加到任何3D硬件加速器中。

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章