草庐IT

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

coder 2025-03-06 原文

我有一个 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为例

和 endMatrix

线性组合看起来像:

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/

有关javascript - 变换矩阵线性组合的旋转动画导致放大缩小的更多相关文章

  1. 旋转矩阵的几何意义 - 2

    点向量坐标矩阵的几何意义介绍旋转矩阵的几何含义之前,先介绍一下点向量坐标矩阵的几何含义点:在一维空间下就是一个标量,如同一条直线上,以任意某一个位置为0点,以一定的尺度间隔为1,2,3...,相反方向为-1,-2,-3...;如此就形成了一维坐标系,这时候任何一个点都可以用一个数值表示,如点p1=5,即即从原点出发沿着x轴正方向移动5个尺度;点p2=-3,负方向移动3个尺度;     在一维坐标系上过原点做垂直于一维坐标系的直线,则形成了二维坐标系,此时描述一个点需要两个数值来表示点p3=(3,2),即从原点出发沿着x轴正方向移动3个尺度,在此基础上沿着y轴正方向移动两个尺度的位置就是点p3。

  2. Ruby 守护进程导致 ActiveRecord 记录器 IOError - 2

    我目前正在用Ruby编写一个项目,它使用ActiveRecordgem进行数据库交互,我正在尝试使用ActiveRecord::Base.logger记录所有数据库事件具有以下代码的属性ActiveRecord::Base.logger=Logger.new(File.open('logs/database.log','a'))这适用于迁移等(出于某种原因似乎需要启用日志记录,因为它在禁用时会出现NilClass错误)但是当我尝试运行包含调用ActiveRecord对象的线程守护程序的项目时脚本失败并出现以下错误/System/Library/Frameworks/Ruby.frame

  3. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  4. ruby - 从另一个私有(private)方法中使用 self.xxx() 调用私有(private)方法 xxx,导致错误 "private method ` xxx' called” - 2

    我正在尝试获得良好的Ruby编码风格。为防止意外调用具有相同名称的局部变量,我总是在适当的地方使用self.。但是现在我偶然发现了这个:classMyClass上面的代码导致错误privatemethodsanitize_namecalled但是当删除self.并仅使用sanitize_name时,它会起作用。这是为什么? 最佳答案 发生这种情况是因为无法使用显式接收器调用私有(private)方法,并且说self.sanitize_name是显式指定应该接收sanitize_name的对象(self),而不是依赖于隐式接收器(也是

  5. ruby - 最多 n 的组合 - 2

    给定一个数组a,什么是实现其组合直到第n的最佳方法?例如:a=%i[abc]n=2#Expected=>[[],[:a],[:b],[:c],[:a,b],[:b,:c],[:c,:a]] 最佳答案 做如下:a=%w[abc]n=30.upto(n).flat_map{|i|a.combination(i).to_a}#=>[[],["a"],["b"],["c"],["a","b"],#["a","c"],["b","c"],["a","b","c"]] 关于ruby-最多n的组合,我

  6. ruby - Rails 组合多个 activerecord 关系 - 2

    我想合并多个事件记录关系例如,apple_companies=Company.where("namelike?","%apple%")banana_companies=Company.where("namelike?","%banana%")我想结合这两个关系。不是合并,合并是apple_companies.merge(banana_companies)=>Company.where("namelike?andnamelike?","%apple%","%banana%")我要Company.where("名字像?还是名字像?","%apple%","%banana%")之后,我会写代

  7. ruby - 为什么 return 关键字会导致我的 'if block' 出现问题? - 2

    下面的代码工作正常:person={:a=>:A,:b=>:B,:c=>:C}berson={:a=>:A1,:b=>:B1,:c=>:C1}kerson=person.merge(berson)do|key,oldv,newv|ifkey==:aoldvelsifkey==:bnewvelsekeyendendputskerson.inspect但是如果我在“ifblock”中添加return,我会得到一个错误:person={:a=>:A,:b=>:B,:c=>:C}berson={:a=>:A1,:b=>:B1,:c=>:C1}kerson=person.merge(berson

  8. ruby-on-rails - 什么会导致与 APNS 的连接间歇性断开连接? - 2

    我有一个ruby​​脚本可以打开与Apple推送服务器的连接并发送所有待处理的通知。我看不出任何原因,但当Apple断开我的脚本时,我遇到了管道损坏错误。我已经编写了我的脚本来适应这种情况,但我宁愿只是找出它发生的原因,这样我就可以在第一时间避免它。它不会始终根据特定通知断开连接。它不会以特定的字节传输大小断开连接。一切似乎都是零星的。您可以在单个连接上发送的数据传输或有效负载计数是否有某些限制?看到人们的解决方案始终保持一个连接打开,我认为这不是问题所在。我看到连接在3次通知后断开,我看到它在14次通知后断开。我从未见过它能超过14点。有没有人遇到过这种类型的问题?如何处理?

  9. ruby - 如何在 ruby 中组合/排列? - 2

    我有一个熟悉的问题,看起来像是数学世界的排列/组合。如何通过ruby​​实现以下目标?badges="1-2-3"badge_cascade=[]badges.split("-").eachdo|b|badge_cascade["1","2","3"]ButIwantittobeis:=>["1","2","3","1-2","2-3","3-1","2-1","3-2","1-3","1-2-3","2-3-1","3-1-2"] 最佳答案 函数式方法:bs="1-2-3".split("-")strings=1.upto(bs.

  10. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

    我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

随机推荐