碼農干貨系列【3】--割繩子(cut the rope)制作點滴:旋轉(rotation)


旋轉

在大量的游戲開發過程當中,旋轉是經常被開發者使用的,通常需要得到旋轉后目標點的坐標。旋轉分很多種類:2D游戲世界中,以某一點為旋轉目標;3D游戲世界中,以軸為旋轉目標。所以本文將旋轉分為四類,涵蓋所有旋轉的情況:

繞點旋轉(2D)

繞坐標軸(x/y/z)旋轉(3D)

繞坐標軸的平行軸旋轉(3D)

繞任意軸旋轉(3D)

image

繞點旋轉

在繞點旋轉的時候,需要傳入兩個參數,一個是目標中心點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 ]
]);
};

image025

詳細推導過程傳送門:http://www.gamedev.net/reference/articles/article1199.asp

舉一個栗子

這是我制作《割繩子》的第一關中的部分效果,比IE官網的難度稍大一點,三顆星星不是閉着眼睛割就能夠得到,也要找准時機果斷割繩子。

請使用現代瀏覽器觀看在線演示!

小結

本文主要是通過一點點線性代數的知識,解決旋轉相關的問題。線性代數的應用非常廣泛,在光線追蹤、物理引擎、圖像識別、實時碰撞檢測等重要領域都有着不可替代的作用和地位。當然,除了計算機行業,在其他行業,比如電子工程、3D影片制作渲染、土木工程等,都有這重要的作用和地位。

更多干貨,敬請期待~~~


免責聲明!

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



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