• 码农干货系列【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影片制作渲染、土木工程等,都有这重要的作用和地位。

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

  • 相关阅读:
    将代码托管到github服务器之SSH验证
    将代码托管到github服务器之HTTPS验证
    git的基本介绍和使用
    iOS之UITableView组头组尾视图/标题悬停
    iOS事件传递->处理->响应
    NSRunLoop
    Podfile使用说明
    cocoapods安装
    block
    自定义UIBarButtonItem
  • 原文地址:https://www.cnblogs.com/iamzhanglei/p/2544304.html
Copyright © 2020-2023  润新知