Affine Transformation是一種二維坐標到二維坐標之間的線性變換,保持二維圖形的“平直性”和“平行性”。仿射變換可以通過一系列的原子變換的復合來實現,包括:平移(Translation)、縮放(Scale)、翻轉(Flip)、旋轉(Rotation)和錯切(Shear)。
在做2D圖形引擎時,仿射變換是非常重要的點,圖形的旋轉等各種表現都需要通過仿射變換來完成,比如在顯示列表樹中,父節點旋轉了,那么子節點在計算顯示時也要疊加上父節點的變換矩陣,這是疊加矩陣。還有計算2D空間內的點在經過仿射變換的圖形中的位置、鼠標是否點在經過仿射變換過的矩形中,等等都是需要仿射變換來完成計算。
定義一個矩陣類Matrix包含屬性如下:
| 參數 | 描述 |
|---|---|
| a | 水平縮放比例 |
| b | 垂直傾斜比例 |
| c | 水平傾斜比例 |
| d | 垂直縮放比例 |
| x | 水平偏移像素 |
| y | 垂直偏移像素 |
矩陣的默認值為Matrix(1,0,0,1,0,0),后面的變換以改變矩陣值的形式完成。通過H5中的canvas實現改變圖形:
var c=document.getElementById("canvas");
var ctx=c.getContext("2d");
ctx.fillStyle="yellow";
ctx.fillRect(0,0,250,100)
var matrix = RM.Matrix.create(1,0,0,1,0,0);
ctx.setTransform(matrix.a,matrix.b,matrix.c,matrix.d,matrix.x,matrix.y);
ctx.fillStyle="red";
ctx.fillRect(0,0,250,100);
平移
平移變換是一種“剛體變換”,並不會改變圖形的形狀。

//(平移到點40,50)
matrix.translate( 40, 50 );
涉及到函數的平移公式code:
/**平移x,y像素*/
public translate( x:number, y:number ):RM.Matrix {
this.x += x;
this.y += y;
return this;
}
縮放
縮放變換可以改變圖形的寬高比例,橫向縮放與縱向縮放。當值為
負數時反向縮放。

//(x軸縮放0.5,y軸縮放0.5)
matrix.scale( 0.5, 0.5 );
或
//(x軸縮放-1,y軸縮放1)
matrix.scale( -1, 1 );
涉及到函數的縮放公式code:
/**縮放,x、y軸方向縮放*/
public scale( scaleX:number, scaleY:number ):RM.Matrix {
this.a *= scaleX;
this.b *= scaleY;
this.c *= scaleX;
this.d *= scaleY;
this.x *= scaleX;
this.y *= scaleY;
return this;
}
旋轉
目標圖形圍繞(x,y)點順時針旋轉value弧度
旋轉矩陣為(cosA, sinA, -sinA, cosA, 0, 0)

//(順時針旋轉30角度)
matrix.rotate( 30 );
涉及到函數的旋轉公式code:
/**旋轉,單位是角度
* 旋轉矩陣( cosA, sinA, -sinA, cosA, 0, 0)
* */
public rotate( angle:number ):RM.Matrix {
angle = ( angle % 360 )
angle = RM.GFunction.angle2radian( angle );//角度轉弧度
var cos:number = Math.cos( angle );
var sin:number = Math.sin( angle );
var ta:number = this.a;
var tc:number = this.c;
var tx:number = this.x;
this.a = ta * cos - this.b * sin;
this.b = ta * sin + this.b * cos;
this.c = tc * cos - this.d * sin;
this.d = tc * sin + this.d * cos;
this.x = tx * cos - this.y * sin;
this.y = tx * sin + this.y * cos;
return this;
}
錯切
錯切變換指的是類似於四邊形不穩定性那種性質,菱形形狀。根據弧度順時針傾斜。
錯切矩陣為( 1, tanAy, tanAx, 1, 0, 0 )

//(x軸錯切30角度)
matrix.skew( 30, 45 );
涉及到函數的錯切公式code:
/**切變,單位是角度
* 切變矩陣 ( 1, tanAy, tanAx, 1, 0, 0)
* */
public skew( angleX:number, angleY:number ):RM.Matrix {
angleX = ( angleX % 90 );
angleY = ( angleY % 90 );
angleX = RM.GFunction.angle2radian( angleX );//角度轉弧度
angleY = RM.GFunction.angle2radian( angleY );//角度轉弧度
var tanAx:number = Math.tan( angleX );
var tanAy:number = Math.tan( angleY );
var ta:number = this.a;
var tc:number = this.c;
var tx:number = this.x;
this.a = ta + tanAx * this.b;
this.b = ta * tanAy + this.b;
this.c = tc + tanAx * this.d;
this.d = tc * tanAy + this.d;
this.x = tx + tanAx * this.y;
this.y = tx * tanAy + this.y;
return this;
}
屬性疊加
當設置仿射變換的多個屬性時,依據矩陣乘法的特性要遵循順序(縮放->錯切->旋轉->平移)依次變換。
如果不按照以上順序,產生的結果將會與預期大大不同。下圖以先x軸縮放0.5、錯切x軸-30y軸30、旋轉10度、平移(10,20)

錯切與旋轉的區別:錯切可以分別向兩方向傾斜不同的角度;旋轉是同時向兩方向傾斜相同的角度。
那么,可以把錯切與旋轉合並,錯切的默認x軸傾斜是正方向傾斜,也就是逆時針,改為與y軸傾斜相同的順時針方向。
當旋轉30度時,也就是x軸y軸同時順時針傾斜30度。

如圖所示,大矩形錯切x軸y軸各30度,小矩形旋轉30度,兩者的結果是一致的。
//大矩形
matrix.rightTransform( 0,0,1,1,30,30,0 );
//小矩形
matrix.rightTransform( 0,0,1,1,0,0,30 );
屬性疊加公式code:
/**轉換矩陣操作,順序為:縮放、切變、旋轉、平移*/
public rightTransform(x:number, y:number, scaleX:number, scaleY:number, skewX:number, skewY:number, rotate:number):RM.Matrix {
rotate = ( rotate % 360 );
rotate = RM.GFunction.angle2radian(rotate);
//旋轉與切變一起算
skewX = RM.GFunction.angle2radian(skewX) + rotate;
skewY = RM.GFunction.angle2radian(skewY) + rotate;
if (skewX || skewY) {
//矩陣乘法(右置矩陣、后置矩陣)
this.rightMultiply(Math.cos(skewY) * scaleX, Math.sin(skewY) * scaleX, -Math.sin(skewX) * scaleY, Math.cos(skewX) * scaleY, x, y);
}
else {
this.rightMultiply(scaleX, 0, 0, scaleY, x, y);
}
}
疊加矩陣
在顯示對象樹中,父節點旋轉30度,那么它的子節點是否也要旋轉到相對於父節點的位置呢?答案是肯定的,必須旋轉到相對位置。這就涉及到矩陣乘法,例如矩陣A*矩陣B得到的就是疊加矩陣,但是一定要注意的是 A*B ≠ B*A 。
矩陣乘法滿足結合律,但不滿足交換律。
因為矩陣A*B=C,C的結果是由A的行與B的列相乘和的結果;而B*A=D,D的結果是由B的行與A的列相乘和的結果。顯然,得到的結果C和D不一定相等。

顯然,大矩形為父節點,小矩形為大矩形的子節點,當大矩形旋轉60度時,小矩形相對於父節點為0度,
然后小矩形再旋轉60度,這是相對於父節點旋轉了60度,相對於原點旋轉了120度。大矩形以原點(0,0)點旋轉,小矩形以大矩形內的坐標(0,0)點旋轉。
parent.matrix = RM.Matrix.create(0,0,1,1,0,0,60);
//父節點的矩陣與子節點的矩陣相乘,便是子節點的真實矩陣
child.matrix = parent.matrix.rightTransform(0,0,1,1,0,0,60);
矩陣的運用
矩陣這東西在圖形引擎中的運用是很多的,上面的例圖就是使用自己寫好的圖形引擎,通過設置屬性再渲染到canvas中的。一張坐標軸地圖、一個虛線矩形、一個實線矩形完成演示。
矩陣的運用還是很多的,2D圖形引擎中坐標點的判斷,鼠標點擊是否在圖形上,臟矩形的范圍,子節點上的坐標與原點坐標系的轉換,等都是通過點與矩陣之間的二維空間轉換而得到確切的值。

