15、《每周一点canvas动画》——碰撞检测(2)

每周一点canvas动画代码文件

如果你的画布上只有两个物体,那么他们之间的碰撞检测很容易就能实现。但是,当物体越来越多,你就需要一些必要的策略去检测物体之间的碰撞,不能漏掉任何的可能性,同时还要考虑性能问题,避免重复检测。

上一节我们介绍了物体间的碰撞检测方法。在这一节我们主要介绍:

  1. 多物体碰撞检测策略

  2. 高级碰撞检测方法

  3. 总结

1. 多物体碰撞检测策略

现在我们来简单分析一下多个物体之间碰撞检测的情况。当画布上只有两个物体AB时,只有一种碰撞情况A-B。那么,如果多加一个物体C呢?那就有A-B,A-C,B-C三种。同理以此类推,如果再加一个物体D,就有六种情况!这里,一定要理解A-BB-A是一种情况。考虑到这一点,可以极大的提升动画的性能,减少很多不必要的检测。

假如,现在你有6个物体

objects = [object0,object1,object2,object3,object4,object5]

它们在画布中自由运动,你要所要做的就是让它们知道在画布中碰上了彼此。这里我们要用到双重循环,首先object1与其他的物体发生碰撞检测,然后在是object2,object3…与其他物体。

objects.forEach(function(objectA, i){
    for(var j=0 len=object.length; j <len ; j++){
        var objectB = objects[j];
        if(hitTestObject(objectA, objectB)){ //如果碰上了
            //do something
        }
    }
})

大概算一下,6个物体,按这种方法就有36次检测,似乎看起来很合理,但是其实有很大的问题。

object0 vs object1, object2, object3, object4, object5
object1 vs object0, object2, object3, object4, object5
object2 vs object0, object1, object3, object4, object5
object3 vs object0, object1, object2, object4, object5
object4 vs object0, object1, object2, object3, object5
object5 vs object0, object1, object2, object3, object4
  • 问题一: object1object1,也就是说物体自身跟自身也做了检测。

  • 问题二: object1object2检测过后,我们又做了一次object2object1

现在,我们一一解决上面的问题。对于问题一我们可以这样做

objects.forEach(function(objectA, i){
    for(var j=0 len=object.length; j <len ; j++){
        var objectB = objects[j];
        if(i !==j && hitTestObject(objectA, objectB)){ //如果碰上了
            //do something
        }
    }
})

我们多加了一个条件,判断当前两个物体的索引值是否是一样的。如果一样则证明他们是同一个物体,就不会做碰撞检测。反之,则做碰撞检测。排除了与自身的检测,一下我们就减少了6次。现在检测次数降到了30次。

如果我们把问题二也解决了,那么碰撞列表就是这样了,一共还剩15次。也就是说总共减少了21次不必要的检测。

object0 vs object1, object2, object3, object4, object5
object1 vs object2, object3, object4, object5
object2 vs object3, object4, object5
object3 vs object4, object5
object4 vs object5
object5 vs nothing

那么我们就需要在代码上动动手脚,首先观察上表,当上层循环的i0时,下层循环的j1i1时,下层循环的j2…于是,我们可以总结出一个规律,上层索引值 i 与下层索引值 j 之间存在关系:j=i+1。这不仅解决了问题一,也就是说不需要再判断ij的索引值是否相等了,同时也完美的解决了问题二。

objects.forEach(function(objectA, i){
    for(var j=i+1 len=object.length; j <len ; j++){
        var objectB = objects[j];
        if(hitTestObject(objectA, objectB)){ //如果碰上了
            //do something
        }
    }
})

现在,我们已经完成了多物体碰撞检测的算法。it’s show time!我们要完成的效果就是:检测一堆大小不一的球体是否碰到了彼此。先上效果图

这里我只列出核心代码,具体代码看源文件multi-object-hit.html

 function checkCollision (ballA, i) {
        for (var ballB, j = i + 1; j < numBalls; j++) {
          ballB = balls[j];
          dx = ballB.x - ballA.x;
          dy = ballB.y - ballA.y;
          dist = Math.sqrt(dx * dx + dy * dy);
          min_dist = ballA.radius + ballB.radius;

          if (dist < min_dist) {    
            ballA.vx *= bounce;
            ballA.vy *= bounce;
            ballB.vx *= bounce;
            ballB.vy *= bounce;
          }
        }
      }

2. 高级碰撞检测方法

这一部分,主要介绍一些高级碰撞检测的概念。这些概念提供了一种更精确,更有效率的碰撞检测方法,希望能够为你的项目起到一定的帮助作用。

2.1 光线投射法

光线投射法的概念非常简单:画一条与物体速度向量相重合的线,然后再从待检测物体出发,绘制第二条线,根据两条线的交点位置来判定是否发生碰撞。

如果你觉得文字描述不好理解,可以看下图。图示演示了一个应用光线投射法来判断小球是否落入桶中的效果,这个程序很简单,左方是小球,右方是桶。当用户点击鼠标时程序将以小球与鼠标位置的连线为发射方向,将小球抛出去,抛射的力度以连线的长度成正比。

在上图中,#1是与小球速度向量相重合的线,#2线是我们从待测物体触发绘制的第二条线。在小球的飞行过程中,程序不断地擦除并重绘从小球到1,2号交点处的连线。




上面四幅图展示了一个完整的过程。通过图示,我们我们可以得出小球落入桶中的条件:

  1. 1号线与2号线的交点在桶口的左边沿与右边沿之间

  2. 小球位于2号线下方

光线投射法,从数学上来说就是判断两条直线的交点。直线的表达式有很多种,我们以斜截式为例,直线的表达式为:

y = kx + b

k表示斜率, b表示直线与y轴的截距,也就是直线与y轴交点的纵坐标。寻找两条直线的交点,就是寻找同时满足两条直线方程的点,我们假设这个点为(X0, Y0),两条直线方程分别为 y = (k1)X + b1, y = (k2)X + b2。交点就是同时满足的点,那么就有:

(k1)X0 + b1 = (k2)X0 + b2
 X0(k1 - k2) = b2 - b1

 得  X0 = (b2 - b1)/(k1 - k2)
     Y0 = (k1b2 - k2b1)/(k1 - k2)

具体的代码实现请看源码文件中的一个例子:ray-hit-test.js

注意:该方法也有弊端,当小球做水平运动或是垂直运动时,其斜率为0或是无穷,这时用该方法就不适用了。需要对这两种情况做特殊处理。

2.2 分离轴定理(SAT)

到此为止我们已经介绍了如何使用几何图形法(外接圆,外接矩形)和光线投射法来实现简单的碰撞检测。这些方法可以满足大多数情况,然而,却不适用与检测任意多边形之间的碰撞,这里我们只做简单的概念介绍。

首先,分离轴定理只适用于凸多边形,也就是所有内角均小于180度的多边形。比如矩形,三角形等。而如果有一个内角大于180度,就如吃豆人的形状,就不适合使用该定理。

分离轴定理概念:用通俗的语言来说就是把受测的两个物体置于一堵墙前面,然后用光线照射它们,根据阴影部分是否相交来判断二者有没有相撞。

第一种情况: 碰上了

第二种情况: 未碰上

我们可以看到只要有一侧的投影是分离的,那么他们就算没有碰上。搭配最小平移向量定理(MTV),可以处理他们碰撞后的的情况,如何反弹,反弹的角度等,这些都根据具体需要来实现,有兴趣你可以去研究一下。

3. 总结

本章到这里介绍了一些碰撞检测的方法,相对来说有些难懂。但是,潜心研究一下你会发现它也没有想象的那么神秘。






基于距离的碰撞检测

var dx = objectB.x - objectA.x,
dy = objectB.y - objectA.y;
var distance = Math.sqrt(dx*dx + dy*dy);
if(distance < objectA.radius + objectB.radius){
//do something
}

#### 多物体碰撞检测
objects.forEach(function(objectA, i){
    for(var j=i+1; i<objects.lebgth; i++){
        //do something
    }
})

本章到这就结束了,下一章我们讲碰撞后的事情,有些简单的物理概念需要你提前了解:比如动量守恒能量守恒,敬请期待!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值