javascript - 变换矩阵线性组合的旋转动画导致放大缩小

标签 javascript animation matrix transform

我有一个 3x3 矩阵 (startMatrix),它表示图像的实际 View (平移、旋转和缩放)。现在我创建一个新矩阵 (endMatrix),它有一个恒等矩阵、新的 x 和 y 坐标、新的 Angular 和新的比例,例如:

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 transform matrix3d(3d 仅用于硬件加速)转换图像。此转换使用 requestAnimationFrame 进行动画处理.

以我的startMatrix为例

enter image description here

和 endMatrix

enter image description here

线性组合看起来像:

enter image description here

t 从 0 到 1

变换矩阵线性组合的结果(得到的图像位置)是正确的,我现在的问题是:如果新 Angular 与实际 Angular 相差大约180度,则endMatrix值由正变为负(或另一种方式)。这会导致转换图像的动画中出现放大缩小效果。

有没有办法通过使用一个矩阵进行转换来防止这种情况发生?

最佳答案

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

2D 旋转非常简单,因此无需旋转矩阵方法也能很好地完成。最好的方法可能是使用四元数,但四元数可能更适合 3D 转换。

要采取的步骤是:

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

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

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

假设起始矩阵为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]);

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

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

一般矩阵S的x轴位置表示为

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

y轴指向方向

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

对于任何 2D 3x3 矩阵都是一样的,比如 E。

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

// 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) 大于零,则矩阵已顺时针旋转, Angular 必须为负。必须这样做,因为 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 的增量旋转。这可以通过矩阵跨越的向量之间的点积类似地计算。同样,我们测试叉积以查看旋转方向是否为顺时针(负 Angular )。

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

第三步构造矩阵

对于每一帧构建最终矩阵,您只需将值放入转换矩阵矩阵

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 硬件加速器提供数据。

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

关于javascript - 变换矩阵线性组合的旋转动画导致放大缩小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35914137/

相关文章:

ios - 使用 UIView Animation 在 Xcode 中制作动画时,按钮不执行被点击的操作

perl - 在 perl 中转置

python - 如何在 python 中向量化一组项目

javascript - jquery 查找函数

javascript - 面向对象的 javascript 和 requestAnimationFrame

javascript - Ui-router - {slug} 和 :slug 之间的区别

ios - 从右点更改 UISearchBar 宽度

android - 如何动画 fragment 移除

r - 将列向量乘以 RcppArmadillo 中的数值标量

javascript - 单击时不会触发功能 - AJAX/CSS