旋轉
在大量的游戲開發過程當中,旋轉是經常被開發者使用的,通常需要得到旋轉后目標點的坐標。旋轉分很多種類:2D游戲世界中,以某一點為旋轉目標;3D游戲世界中,以軸為旋轉目標。所以本文將旋轉分為四類,涵蓋所有旋轉的情況:
繞點旋轉(2D)
繞坐標軸(x/y/z)旋轉(3D)
繞坐標軸的平行軸旋轉(3D)
繞任意軸旋轉(3D)
繞點旋轉
在繞點旋轉的時候,需要傳入兩個參數,一個是目標中心點p(即繞着哪個點旋轉),另一個參數是旋轉的角度theta。所以為Vector2擴展如下方法:
rotateSelf: function (p, theta) {
var v = this.sub(p);
theta *= Math.PI / 180;
var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]];
this.x = p.x + R[0][0] * v.x + R[0][1] * v.y;
this.y = p.y + R[1][0] * v.x + R[1][1] * v.y;
},
a.var v=this.sub(p)===>先過原點(把p點當作原點),v是相對於原點p點的坐標b.var R=[……..]===>根據角度生成旋轉矩陣c.求出v點繞着原點(p點)旋轉后的向量坐標(R[0][0] * v.x + R[0][1] * v.y,R[1][0] * v.x + R[1][1] * v.y)d.把v點的向量坐標累加回p點,即得出最后旋轉后點的坐標
要理解好過原點,要追溯到線性函數。最簡單的例子就是我們把f(x)=kx+b的b割舍掉,成為f(x)=kx的形式。只有過原點的直線才能被成為一元線性函數。因為不過原點的直線不滿足我們對線性函數比例性的要求。而矩陣是向量的數組,向量的表達方式是基於原點的。
所以:矩陣變換的核心和基礎就是理解好過原點,所以才會有上面來回移動的這個過程。
繞坐標軸(x/y/z)旋轉
在3D世界中,繞坐標軸旋轉的的本質就是3D中的2D切面中的旋轉。通常我們定義一個矩陣類來輔助向量類的計算:
Matrix.RotationX = function(t) {
var c = Math.cos(t), s = Math.sin(t);
return Matrix.create([
[ 1, 0, 0 ],
[ 0, c, -s ],
[ 0, s, c ]
]);
};
Matrix.RotationY = function(t) {
var c = Math.cos(t), s = Math.sin(t);
return Matrix.create([
[ c, 0, s ],
[ 0, 1, 0 ],
[ -s, 0, c ]
]);
};
Matrix.RotationZ = function(t) {
var c = Math.cos(t), s = Math.sin(t);
return Matrix.create([
[ c, -s, 0 ],
[ s, c, 0 ],
[ 0, 0, 1 ]
]);
};
繞着X軸旋轉矩陣變換,x坐標不變繞着Y軸旋轉矩陣變換,y坐標不變繞着Z軸旋轉矩陣變換,z坐標不變
繞坐標軸的平行軸旋轉
繞坐標軸的平行軸的思路和繞點旋轉的思路一致,我們為Vector3擴展如下方法:
rotateXSelf: function (p, theta) {
var v = this.sub(p);
theta *= Math.PI / 180;
var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]];
this.y = p.y + R[0][0] * v.y + R[0][1] * v.z;
this.z = p.z + R[1][0] * v.y + R[1][1] * v.z;
},
rotateYSelf: function (p, theta) {
var v = this.sub(p);
theta *= Math.PI / 180;
var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]];
this.x = p.x + R[0][0] * v.x + R[0][1] * v.z;
this.z = p.z + R[1][0] * v.x + R[1][1] * v.z;
},
rotateZSelf: function (p, theta) {
var v = this.sub(p);
theta *= Math.PI / 180;
var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]];
this.x = p.x + R[0][0] * v.x + R[0][1] * v.y;
this.y = p.y + R[1][0] * v.x + R[1][1] * v.y;
}
這里的p點滿足的條件是:要旋轉的點與p點的連線垂直於旋轉軸,旋轉軸過p點。
a.var v=this.sub(p)===>先過原點(把p點當作原點),v是相對於原點p點的坐標b.var R=[……..]===>根據角度生成旋轉矩陣c.求出v點繞着原點(p點)旋轉后的向量坐標d.把v點的向量坐標累加回p點,即得出最后旋轉后點的坐標
和2D繞點旋轉一樣。
繞任意軸旋轉
Matrix.Rotation = function(theta, a) {
var axis = a.dup();
if (axis.elements.length != 3) { return null; }
var mod = axis.modulus();
var x = axis.elements[0]/mod, y = axis.elements[1]/mod, z = axis.elements[2]/mod;
var s = Math.sin(theta), c = Math.cos(theta), t = 1 - c;
return Matrix.create([
[ t*x*x + c, t*x*y - s*z, t*x*z + s*y ],
[ t*x*y + s*z, t*y*y + c, t*y*z - s*x ],
[ t*x*z - s*y, t*y*z + s*x, t*z*z + c ]
]);
};
詳細推導過程傳送門:http://www.gamedev.net/reference/articles/article1199.asp
舉一個栗子
這是我制作《割繩子》的第一關中的部分效果,比IE官網的難度稍大一點,三顆星星不是閉着眼睛割就能夠得到,也要找准時機果斷割繩子。
小結
本文主要是通過一點點線性代數的知識,解決旋轉相關的問題。線性代數的應用非常廣泛,在光線追蹤、物理引擎、圖像識別、實時碰撞檢測等重要領域都有着不可替代的作用和地位。當然,除了計算機行業,在其他行業,比如電子工程、3D影片制作渲染、土木工程等,都有這重要的作用和地位。
更多干貨,敬請期待~~~