代码改变世界

码农干货系列【3】--割绳子(cut the rope)制作点滴:旋转(rotation)

2012-06-11 07:44  【当耐特】  阅读(6338)  评论(17编辑  收藏  举报

旋转

在大量的游戏开发过程当中,旋转是经常被开发者使用的,通常需要得到旋转后目标点的坐标。旋转分很多种类: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影片制作渲染、土木工程等,都有这重要的作用和地位。

更多干货,敬请期待~~~