JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】


Posted in Javascript onDecember 13, 2018

本文实例讲述了JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法。分享给大家供大家参考,具体如下:

概述

分离轴定理是一项用于检测碰撞的算法。其适用范围较广,涵盖检测圆与多边形,多边形与多边形的碰撞;缺点在于无法检测凹多边形的碰撞。本demo使用Js进行算法实现,HTML5 canvas进行渲染。

详细

一、准备工作,熟悉分离轴定理 算法原理

从根本上来讲,分离轴定理(以及其他碰撞算法)的用途就是去检测并判断两个图形之间是否有间隙。分离轴定理中用到的方法使算法本身显得十分独特。

我所听到过分离轴定理的最好类比方式是这样的:

假想你拿一个电筒从不同的角度照射到两个图形上,那么会有怎样的一系列的阴影投射到它们之后的墙壁上呢?

JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】

如果你用这个方式从每一个角度上对这两个图形进行处理,并都找不到任何的间隙,那么这两个图形就一定接触。如果你找到了一个间隙,那么这两个图形就显而易见地没有接触。

从编程的角度来讲,从每个可能的角度上去检测会使处理变得十分密集。不过幸运的是,由于多边形的性质,你只需要检测其中几个关键的角度。

你需要检测的角度数量就正是这个多边形的边数。也就是说,你所需检测的角度最大数量就是你要检测碰撞的两个多边形边数之和。举个例子,两个五边形就需要检测10个角度。

JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】

这是一个简易但比较??碌姆椒ǎ?韵率腔?镜牟街瑁?/p>

步骤一:从需要检测的多边形中取出一条边,并找出它的法向量(垂直于它的向量),这个向量将会是我们的一个“投影轴”。

JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】

步骤二:循环获取第一个多边形的每个点,并将它们投影到这个轴上。(记录这个多边形投影到轴上的最高和最低点)

JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】

步骤三:对第二个多边形做同样的处理。

JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】

步骤四:分别得到这两个多边形的投影,并检测这两段投影是否重叠。

JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】

如果你发现了这两个投影到轴上的“阴影”有间隙,那么这两个图形一定没有相交。但如果没有间隙,那么它们则可能接触,你需要继续检测直到把两个多边形的每条边都检测完。如果你检测完每条边后,都没有发现任何间隙,那么它们是相互碰撞的。

这个算法基本就是如此的。

顺带提一下,如果你记录了哪个轴上的投影重叠值最小(以及重叠了多少),那么你就能用这个值来分开这两个图形。

那么如何处理圆呢?

在分离轴定理中,检测圆与检测多边形相比,会有点点奇异,但仍然是可以实现的。

最值得注意的是,圆是没有任何的边,所以是没有明显的用于投影的轴。但它有一条“不是很明显的”的投影轴。这条轴就是途经圆心和多边形上离圆心最近的顶点的直线。

JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】

在这以后就是按套路遍历另一个多边形的每条投影轴,并检测是否有投影重叠。

噢,对了,万一你想知道如何把圆投影到轴上,那你只用简单地把圆心投影上去,然后加上和减去半径就能得到投影长度了。

二、完整实例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
  <meta charset="UTF-8">
  <title>盒包围碰撞算法-凸多边形分离轴检测算法</title>
  <style>
    #stage {
      border: 1px solid lightgray;
    }
  </style>
</head>
<body>
<h1>是否碰撞:<span class="hitTest">否</span></h1>
<canvas id="stage"></canvas>
</body>
<script>
  window.onload = function () {
    var stage = document.querySelector('#stage'),
      ctx = stage.getContext('2d');
    stage.width = 400;
    stage.height = 400;
    var starPointArr = [];
//    绘制五角星
    function drawStar(ctx,r,R,x,y,rot,c){
      ctx.beginPath();
      for(var i =0;i<5;i++){
        var startPosX = Math.cos((18 + i*72 - rot)/180 * Math.PI )*R + x,
          startPosY = - Math.sin((18 + i*72 - rot)/180 * Math.PI)*R + y,
          endPosX = Math.cos((54 + i*72 - rot)/180 * Math.PI )*r + x,
          endPosY = - Math.sin((54 + i*72 - rot)/180 * Math.PI)*r + y;
        ctx.lineTo(startPosX,startPosY);
        ctx.lineTo(endPosX, endPosY);
        starPointArr.push(startPosX,startPosY,endPosX,endPosY);
      }
      ctx.closePath();
      ctx.fillStyle = c;
      ctx.lineWidth = 3;
      ctx.lineJoin = "round";
      ctx.fill();
      ctx.stroke();
    }
    var polygonPointArr = [];
//    绘制多边形
    function drawpolygon(numSides,radius,x,y){
      ctx.beginPath();
      for(i = 1;i<=numSides; i++){
        var xPos = x+radius*Math.cos(2*Math.PI*i/numSides);
        var yPos = x+radius*Math.sin(2*Math.PI*i/numSides);
        polygonPointArr.push(xPos,yPos);
        ctx.lineTo(xPos,yPos);
      }
      //创建完成 闭合路径
      ctx.closePath();
      ctx.lineWidth = 3;  //线宽
      ctx.lineJoin = "round";
      ctx.fillStyle = '#00f';
      ctx.fill();
      ctx.stroke();
    }
    //两个向量的点积
    function dotV2(v1,v2) {
      return v1.x*v2.x+v1.y*v2.y;
    }
    //计算polyArr在轴线axis上的投影,polyArr是一系列点坐标的集合,数组表示
    function calcProj(axis,polyArr) {
      var v = {"x":polyArr[0],"y":polyArr[1]};
      var d,min,max;
      min = max = dotV2(axis,v);//计算投影轴与第一个坐标点的点积
      for(var i=2;i<polyArr.length-1;i+=2) {
        v.x=polyArr[i];
        v.y=polyArr[i+1];
        d = dotV2(axis,v);//计算v到投影轴的距离,遍历出最小和最大区间
        min = (d<min)?d:min;
        max = (d>max)?d:max;
      }
      return [min,max];
    }
    //计算同一个轴上线段的距离s1(min1,max1),s2(min2,max2),如果距离小于0则表示两线段有相交;
    function segDist(min1,max1,min2,max2) {
      if(min1<min2)
      {
        return min2-max1;
      }
      else
      {
        return min1-max2;
      }
    }
    //判断两个多边形是否相交碰撞,p1,p2用于保存多边形点的数组
    function isCollide(p1,p2) {
      //定义法向量
      var e = {"x":0,"y":0};
      var p = p1,idx=0,len1=p1.length,len2=p2.length,px,py;//p缓存形状p1的数据
      for(var i=0,len = len1+len2;i<len-1;i+=2)//遍历所有坐标点,i+=2代表xy轴两个坐标点
      {
        idx = i;
        //计算两个多边形每条边
        if(i>len1) {//当p1遍历完毕后,p缓存形状p2的数据,从新遍历
          p=p2;
          idx=(i-len1);//len2
        }
        if(i===p.length-2) {//p包含的点数据组成的最后一个坐标点
          px=p[0]-p[idx];//首尾的x轴相连
          py=p[1]-p[idx+1];//首尾的y轴相连
        } else {
          px = p[idx+2]-p[idx];//递增的x轴相连
          py = p[idx+3]-p[idx+1];//递减的y轴相连
        }
        //得到边的法向量【垂直相交】,即投影轴
        e.x = -py;
        e.y = px;
        //计算两个多边形在法向量上的投影
        var pp1 = calcProj(e,p1);//涵盖到投影轴的最小值与最大值
        var pp2 = calcProj(e,p2);
        //计算两个线段在法向量上距离,如果大于0则可以退出,表示无相交
        if(segDist(pp1[0],pp1[1],pp2[0],pp2[1])>0) {
          return false;
        }
      }
      return true;
    }
    document.onkeydown = function (event) {
      var e = event || window.event || arguments.callee.caller.arguments[0];
      //根据地图数组碰撞将测
      switch (e.keyCode) {
        case 37:
          console.log("Left");
          if (starPosX > 0) {
            starPosX -= 2;
          }
          break;
        case 38:
          console.log("Top");
          if (starPosY > 0) {
            starPosY -= 2;
          }
          break;
        case 39:
          console.log("Right");
          if (starPosX < stage.width) {
            starPosX += 2;
          }
          break;
        case 40:
          console.log("Bottom");
          if (starPosY < stage.height) {
            starPosY += 2;
          }
          break;
        default:
          return false;
      }
    };
    var starPosX = stage.width/2,starPosY = stage.height/2;
    stage.addEventListener('click', function (event) {
      var x = event.clientX - stage.getBoundingClientRect().left;
      var y = event.clientY - stage.getBoundingClientRect().top;
      starPosX = x;
      starPosY = y;
    });
    function update() {
      ctx.clearRect(0, 0, 400, 400);
      starPointArr = [];
      polygonPointArr = [];
      drawpolygon(7,50,300,300);
      drawStar(ctx,30,50,starPosX,starPosY,30,"yellow");
      document.querySelector('.hitTest').innerHTML = "否";
      var flag = isCollide(starPointArr, polygonPointArr);
      if (flag) {
        document.querySelector('.hitTest').innerHTML = "是";
      }
      requestAnimationFrame(update);
    }
    update();
  };
</script>
</html>

这里使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun 测试上述代码运行结果如下:

JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】

github地址:https://github.com/krapnikkk/JS-gameMathematics

参考文章:http://www.demodashi.com/demo/10423.html

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
js实现文字闪烁特效的方法
Dec 17 Javascript
理解javascript对象继承
Apr 17 Javascript
Bootstrap弹出框(modal)垂直居中的问题及解决方案详解
Jun 12 Javascript
jQuery+CSS3文字跑马灯特效的简单实现
Jun 25 Javascript
JavaScript组件开发之输入框加候选框
Mar 10 Javascript
jQuery中layer分页器的使用
Mar 13 Javascript
vue+ElementUI实现订单页动态添加产品数据效果实例代码
Jul 13 Javascript
element ui 表格动态列显示空白bug 修复方法
Sep 04 Javascript
vue-cli webpack 引入swiper的操作方法
Sep 15 Javascript
在Vant的基础上封装下拉日期控件的代码示例
Dec 05 Javascript
Windows下Node爬虫神器Puppeteer安装记
Jan 09 Javascript
vue实现的多页面项目如何优化打包的步骤详解
Jul 19 Javascript
JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【矩形情况】
Dec 13 #Javascript
详解Express笔记之动态渲染HTML(新手入坑)
Dec 13 #Javascript
js实现黑白div块画空心的图形
Dec 13 #Javascript
JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【圆形情况】
Dec 13 #Javascript
示例vue 的keep-alive缓存功能的实现
Dec 13 #Javascript
Element UI框架中巧用树选择器的实现
Dec 12 #Javascript
vue-cli中安装方法(图文详细步骤)
Dec 12 #Javascript
You might like
PHP MYSQL乱码问题,使用SET NAMES utf8校正
2009/11/30 PHP
如何使用php判断服务器是否是HTTPS连接
2013/07/05 PHP
详解PHP+AJAX无刷新分页实现方法
2015/11/03 PHP
使用 PHP Masked Package 屏蔽敏感数据的实现方法
2019/10/15 PHP
20个非常棒的 jQuery 幻灯片插件和教程分享
2011/08/23 Javascript
jquery load事件(callback/data)使用方法及注意事项
2013/02/06 Javascript
node在两个div之间移动,用ztree实现
2013/03/06 Javascript
Jquery时间验证和转换工具小例子
2013/07/01 Javascript
JS命名空间的另一种实现
2013/08/09 Javascript
JS中引用百度地图并将百度地图的logo和信息去掉
2013/09/29 Javascript
jquery队列queue与原生模仿其实现方法分享
2014/03/25 Javascript
jquery通过visible来判断标签是否显示或隐藏
2014/05/08 Javascript
JQuery复制DOM节点的方法
2015/06/11 Javascript
jQuery 3.0 的变化及使用方法
2016/02/01 Javascript
关于js二维数组和多维数组的定义声明(详解)
2016/10/02 Javascript
最好用的Bootstrap fileinput.js文件上传组件
2016/12/12 Javascript
javascript实现文字无缝滚动
2016/12/27 Javascript
使用jQuery实现简单的tab框实例
2017/08/22 jQuery
React组件refs的使用详解
2018/02/09 Javascript
解决Vue中引入swiper,在数据渲染的时候,发生不滑动的问题
2018/09/27 Javascript
微信小程序websocket实现即时聊天功能
2019/05/21 Javascript
Vue数据绑定实例写法
2019/08/06 Javascript
小程序瀑布流组件实现翻页与图片懒加载
2020/05/19 Javascript
Python获取linux主机ip的简单实现方法
2016/04/18 Python
python实现二维码扫码自动登录淘宝
2016/12/27 Python
利用nohup来开启python文件的方法
2019/01/14 Python
python去除删除数据中\u0000\u0001等unicode字符串的代码
2020/03/06 Python
Python requests模块cookie实例解析
2020/04/14 Python
伦敦香水公司:The London Perfume Company
2019/11/13 全球购物
美国户外服装和装备购物网站:Outland USA
2020/03/22 全球购物
酒店管理毕业生自我鉴定
2014/03/02 职场文书
主持词开场白
2014/03/17 职场文书
步步惊心观后感
2015/06/12 职场文书
2015年音乐教学工作总结
2015/07/22 职场文书
Python中else的三种使用场景
2021/06/16 Python
Redis分布式锁的7种实现
2022/04/01 Redis