关于typescript:有点简单不靠矩阵也能旋转和平移

3次阅读

共计 3374 个字符,预计需要花费 9 分钟才能阅读完成。

2D 矩阵对旋转的局限性在之前的文章中咱们探讨过旋转这件事,也说过用 2 阶矩阵形容的旋转:
教练我想学矩阵
很多成熟的框架 / 库也是基于矩阵这套逻辑来写的,比如说我膜拜的 pixi.js

{
            // get the matrix values of the displayobject based on its transform properties..
            lt.a = this._cx * this.scale.x;
            lt.b = this._sx * this.scale.x;
            lt.c = this._cy * this.scale.y;
            lt.d = this._sy * this.scale.y;

            lt.tx = this.position.x - ((this.pivot.x * lt.a) + (this.pivot.y * lt.c));
            lt.ty = this.position.y - ((this.pivot.x * lt.b) + (this.pivot.y * lt.d));
            this._currentLocalID = this._localID;

            // force an update..
            this._parentID = -1;
}

然而很显著应用二阶矩阵有两个问题,第一旋转操作只能利用于原点,咱们无奈应用二阶矩阵实现绕任意点转动的计算,第二遇到平移操作时咱们须要拓展成齐次矩阵(这部分能够回看之前的文章)第一个问题其实在引入奇次矩阵后就能够解决,只有先把旋转点 A 平移到 0,绕原点旋转𝛉,再把 0 平移回旋转点 A。

这套逻辑尽管也够用,但更多状况下咱们只须要解决绕某个原点外的点旋转的逻辑即可。
比方下图模仿一个地月日零碎时解决月球绕地球的旋转。

有没有更优雅的模式呢?还真有那用复数。复数与复立体复数是什么?

复数与复立体

复数就是一个定义在复立体上的数,复立体相似咱们的直角坐标系,只不过 x 轴上是实数单位,y 轴上是虚数单位(就是高中数学外面那个√-1)

这个复立体上的一个点示意办法又很多比方能够用 x 轴 y 轴对应的长度示意成数系:(a,b)
能够分解成 xy 轴 (相似物理力的合成 \():a cos\theta + b\bar{i}sin\theta \)
留神这里的加号并不示意两者能相加只是个记号
还能够示意成相似极坐标的模式:\(re^{i\theta} \)
同时因为复数是一个数,它还能够进行四则运算。
加法:
\(z_{A}+z_{B} = (x_{A},y_{A})+(x_{B},y_{B}) = (x_{A}+x_{B},y_{A}+y_{B}) \)

这不就是平移操作?就是 A 点向 x 方向平移了 \(x_{B} \)向 y 方向平移了 \(y_{B} \)
乘法:
\(z_{A}z_{B}=(Ae^{i\alpha})(Be^{i\beta}) = ABe^{i(\alpha+\beta)} \)
他的几何意义:

A 绕原点逆时针旋转了 \(\beta\)而长度减少了 B 倍。看到这里是不是 DNA 动了?

复数体系内缩放 / 旋转 / 平移是闭合的。不过咱们明天不聊缩放只着墨于旋转 + 平移。

复数形容平移与旋转

平移操作

咱们应用记号 \(T_{v}(z) \)示意将立体上的一个复数 z 挪动 v,即:\(T_{v}(z) = z + v \)。\(T_{v} \)的逆向操作就是 \(T_{v}^{-1} \)。
因为 \(T_{v}(z)T_{v}^{-1} (z)= T_{v}^{-1}(z)T_{v}(z)=z \)所以 \(T_{v}^{-1}= T_{-v} \)
而复合的平移能够写成:
\(T_{v}\circ T_{w}(z) = T_{v}(z+w)=z+(w+v) \)

旋转操作

对于旋转操作咱们能够定义绕 a 旋转𝛉为 \(R_{a}^{\theta} \)而显然 \(R_{a}^{\theta}R_{a}^{\vartheta}=R_{a}^{\theta+\vartheta} \),\((R_{a}^{\theta})^{-1} = R_{a}^{-\theta} \)。而绕原点的旋转则能够写成
\(R_{0}^{\theta}(z)=e^{i\theta}z \)
那么怎么求出这个 \(R_{a}^{\theta} \),咱们能够套用矩阵时的思路,把先把 a 平移到 0, 绕原点旋转𝛉, 再把 0 平移回 a:
\(R_{a}^{\theta}(z)=T_{a}\circ R_{a}^{\theta}\circ T_{a}^{-1}(z)=e^{i\theta}(z-a)+a=e^{i\theta}z+k \)
其中 \(k=a(1-e^{i\theta}) \)
这就意味着,绕任何点的旋转都能够写成绕原点的旋转再加上一个平移量。而后这个操作还是关闭的。

那么间断绕两个不同的点旋转?
\((R_{a}^{\alpha}\circ R_{b}^{\beta}) (z)= e^{i(\alpha+\beta)}z+v 其中 v=be^{i\alpha}(1-e^{i\beta})+a(1-e^{\alpha}) \)
\(当(\alpha+\beta) \ne 2k\pi 时:\)
\((R_{a}^{\alpha}\circ R_{b}^{\beta}) (z)=R_{c}^{\alpha+\beta} 其中 c= \frac{v}{1-e^{i(\alpha+\beta)}}=\frac{be^{i\alpha}(1-e^{i\beta})+a(1-e^{\alpha})}{1-e^{i(\alpha+\beta)}} \)
\(当(\alpha+\beta) = 2k\pi 时 e^{i(\alpha+\beta)}=1\)
\((R_{a}^{\alpha}\circ R_{b}^{\beta}) =T_{c} 其中 c=(1-e^{i\alpha})(b-a) \)

几何解释

最初咱们再从几何直观上了解下这两个过程,绕点 a 旋转后再绕点 b 旋转,在数学上能够证实总能找到另外一点 c 使得绕 c 点旋转 \((\alpha+\beta) \)与绕点 a 旋转后再绕点 b 旋转等价:

而 时,就是按连贯第一个旋转核心到第二个旋转核心的复数的 2 倍作平移:

代码层面

代码层面反而是最简略的,乞丐版只须要实现一个类 complexNumber 即可,在外部有一个办法 add 和 multi,对外只须要裸露一个 rotate 办法即可:

class complexNumber {
  _real: number;
  _imaginary: number;
  constructor(real: number, imaginary: number) {
    this._real = real;
    this._imaginary = imaginary;
  }
  private _add(a:complexNumber,b:complexNumber):complexNumber{return new complexNumber(a._real+b._real,a._imaginary+b._imaginary);
  }
  private _mul(a:complexNumber,b:complexNumber):complexNumber{
    const real = a._real*b._real-a._imaginary*b._imaginary;
    const imaginary = a._real*b._imaginary+a._imaginary*b._real;
    return new complexNumber(real,imaginary);
  }
  private sub(a:complexNumber,b:complexNumber):complexNumber{return new complexNumber(a._real-b._real,a._imaginary-b._imaginary);
  }
  public rotate(angle:number,z:complexNumber):[number,number]{const p = new complexNumber(Math.cos(angle),Math.sin(angle));
    const v:complexNumber = this._mul(z,this.sub(new complexNumber(1,0),p));
    const result:complexNumber = this._add(this._mul(p,this),v);
    return [result._real,result._imaginary]
  }
}
正文完
 0