在上一节中我们介绍了一些碰撞检测的方法。这一节本来打算讲解一个基于距离碰撞检测的小游戏。但是,因为最近比较忙,一直没来的及把游戏的整个过程完整的写出来。所以,这一节我们继续介绍下一项新技术——坐标旋转,它可能相对枯燥一些,而且有一些大家十分讨厌的数学公式。但是,它是我们后面高级动画的基石。所以,看的时候还请耐心一点,关于碰撞检测的游戏示例,我会在本周发出。
本章主要内容:
简单的坐标旋转
高级的坐标旋转
坐标旋转是一个非常有用的技术,它主要是让坐标围绕某个点旋转。通过它我们能实现很对有意思的效果,比如,一个物体与一个非水平的表面发生碰撞后,物体的反弹方向,反弹速度等。现在有没有注意到,我们的整个系列文章其实是一个循序渐进的过程,上一章我们介绍如何判断两个物体发生碰撞,而这一节我们介绍的就是两个物体发生碰撞后的事情。
1.简单的坐标旋转
在《每周一点canvas动画》——三角函数(1)这一节中我们介绍了关于三角函数的使用。不知道你是否还能回忆起让一个物体做圆周运动的条件是什么?
首先我们得有一个中心点(center point),一个物体(object),然后是半径(radius),角度(angle)。通过增加或减少angle的值,使用基本的三角函数我们就可以让物体围绕某个点做圆周运动。这里我们回忆一下:
object
vr = 0.1
angle = 0
radius = 100
centerX = 0
centerY = 0
object.x = centerX + Math.sin(angle)*radius
object.y = centerY + Math.cos(angle)*radius
angle += vr
上述代码只是展示了我们以前让物体做圆周运动的方法,并没有把它放在动画循环中。so, 看看我们让一个物体做圆周运动需要多少条件吧!如果你知道半径(radius)和角度(angle),上面的方法应该说是相当不错。
但是,如果你只知道中心点(center point)和物体的位置,还想要物体做圆周运动?该怎么做呢?
当然,如果你还是想要使用上面的方法,那也不难,你还是可以通过这两个已知量来计算我们需要的条件:角度(angle)和半径(radius)都是没问题的。
var dx = objext.x - center.x,
dy = object.y - center.y,
angle = Math.atan2(dy, dx),
radius = Math.sqrt(dx*dx + dy*dy);
对于单个物体的旋转,使用这种方法非常不错,尤其是半径(radius)和角度(angle)这两变量,只需要设定一次的情况下。但是,在大多数的情况下,你可能有很多物体需要做旋转,而且他们距离中心点的相对位置也可能发生变化。so,你需要去计算距离,角度,半径,然后让角度在每一帧都加上vr,最终才能在每一帧得到物体的新坐标。
这个方法怎么说呢?既不高效,也不优雅。所以,我们需要一个新方法。
2.高级的坐标旋转
一提到高级,肯定离不开数学这货。
好吧,我只是给你打个预防针。要想让方法既优雅,又高效,我们必须调整思路。怎样用最少的条件得到我们想要的结果,这时候数学就要上场了。
这里我们不会用到多么高深的大学数学知识,只是简单的中学三角函数变换。你会发现原来老师整天唠叨的公式尽然会如此有用。
这个公式只需要物体的x,y坐标,和每一帧物体旋转的角度(角速度)。
newX = x * cos(rotation) - y * sin(rotation);
newY = y * cos(rotation) + x * sin(rotation);
如果你设置了一个中心点,上述公式可以变为
newX = (x - centerX) * cos(rotation) - (y - centerY) * sin(rotation);
newY = (y - centerY) * cos(rotation) + (x - centerX) * sin(rotation);
明白其中的原理了吗?学霸就直接跳过,没明白的听我细细讲来。
上图展示了该公式基于的原理图。物体旋转了一个很小的角度rotation, 那么它的位置该如何计算呢?
//物体的原始位置,距离中心点的距离radius
x = radius * cos(angle);
y = radius * sin(angle);
newX = radius * cos(angle + rotation);
newY = raidus * sin(angle + rotation);
我们知道
cos(a + b) = cos(a) * cos(b) - sin(a) * sin(b);
sin(a + b) = sin(a) * cos(b) + cos(a) * sin(b);
所以面的公式可以化简成如下形式
newX = radius * cos(angle) * cos(rotation) - raidus * sin(angle) * sin(rotation);
newY = raidus * sin(angle) * cos(rotation) + raidus * cos(angle) * sin(rotation);
又因为 x = radius cos(angle); y = radius sin(angle); 将其带入得
newX = x * cos(rotation) - y * sin(rotation);
newY = y * cos(rotation) + x * sin(rotation);
也就是说我们只需要知道物体的位置,设置它每秒钟要旋转的角度(角速度vr),就可以完成以前圆周运动的效果,至于中心点就看你自己的设置了。是不是很优雅,感谢一下你的数学老师吧!下面,我们就运用这个公式来完成一个简单的效果。
2.1 单物体旋转
也就是我们用它来代替以前的圆周运动
辅助线条是我加上去的,你不用太在意,具体看源代码文件:高级坐标旋转.html
我们只关心核心代码
<canvas id="canvas" width="400" height="400" style="background:#000;"></canvas>
<script src="../js/utils.js"></script>
<script src="../js/ball.js"></script>
<script>
window.onload = function(){
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
ball = new Ball(20, "red"),
vr = 0.05, //每一帧转动的弧度值
cos = Math.cos(vr), // 得到cos值
sin = Math.sin(vr), // 得到sin值
centerX = canvas.width/2,
centerY = canvas.height/2,
canvasWidth = canvas.width,
canvasHeight = canvas.height;
ball.x = Math.random()*canvasWidth;
ball.y = Math.random()*canvasHeight;
(function drawFrame(){
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
var x1 = ball.x - centerX; //相对中心点的位置
var y1 = ball.y - centerY;
var newX = x1*cos - y1*sin; //旋转一定角度后的位置
var newY = y1*cos + x1*sin;
ball.x = centerX + newX; //更新球的位置
ball.y = centerY + newY;
ball.draw(context);
}());
}
<script>
2.2 多物体旋转
那么怎样将该公式运用到多个物体上呢?这个其实在以前的例子中我们已经讲过很多遍了
...
var cos = Math.cos(vr),
sin = math.sin(vr);
balls.forEash(function(ball){
var x1 = ball.x - center.x,
y1 = ball.y - center.y;
var newX = x1 * cos - y1 * sin,
newY = y1 * cos + x1 * sin;
ball.x = centerX + newX;
ball.y = centerY + newY;
})
...
下一节,我们讲与之紧密相关的角度反弹,敬请期待!