通過Matrix進行二維圖形仿射變換


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

平移

平移變換是一種“剛體變換”,並不會改變圖形的形狀。

x

//(平移到點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

//(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)

x

//(順時針旋轉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

//(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

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

x

如圖所示,大矩形錯切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不一定相等

x

顯然,大矩形為父節點,小矩形為大矩形的子節點,當大矩形旋轉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圖形引擎中坐標點的判斷,鼠標點擊是否在圖形上,臟矩形的范圍,子節點上的坐標與原點坐標系的轉換,等都是通過點與矩陣之間的二維空間轉換而得到確切的值。

x


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM