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 相关文章推荐
Ext.FormPanel 提交和 Ext.Ajax.request 异步提交函数的区别
Nov 12 Javascript
Jquery 数据选择插件Pickerbox使用介绍
Aug 24 Javascript
jquery实现固定顶部导航效果(仿蘑菇街)
Mar 21 Javascript
介绍一个简单的JavaScript类框架
Jun 24 Javascript
JavaScript浏览器对象之一Window对象详解
Jun 03 Javascript
一个仿微博登陆邮箱提示框js开发案例
Jul 28 Javascript
浅谈Angularjs link和compile的使用区别
Oct 21 Javascript
JavaScript数组复制详解
Feb 02 Javascript
兼容浏览器的js事件绑定函数(详解)
May 09 Javascript
webpack踩坑之路图片的路径与打包
Sep 05 Javascript
vue init失败简单解决方法(终极版)
Dec 22 Javascript
vue项目中使用Hbuilder打包app 设置沉浸式状态栏的方法
Oct 22 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支持页面回退的两种方法
2008/01/10 PHP
也谈php网站在线人数统计
2008/04/09 PHP
Mac环境下php操作mysql数据库的方法分享
2015/05/11 PHP
PHP使用逆波兰式计算工资的方法
2015/07/29 PHP
浅析PHP类的反射来实现依赖注入过程
2018/02/06 PHP
TP3.2框架分页相关实现方法分析
2020/06/03 PHP
jQuery Study Notes学习笔记 (二)
2010/08/04 Javascript
一个javascript图片阅览组件
2010/11/09 Javascript
利用jQuery操作对象数组的实现代码
2011/04/27 Javascript
JS随即打乱数组实现代码
2012/12/03 Javascript
js限制文本框只能输入数字方法小结
2014/06/16 Javascript
JS的事件绑定深入认识
2014/06/26 Javascript
php,js,css字符串截取的办法集锦
2014/09/26 Javascript
jQuery中:radio选择器用法实例
2015/01/03 Javascript
js使用cookie记录用户名的方法
2015/11/26 Javascript
基于jQuery实现淡入淡出效果轮播图
2020/07/31 Javascript
jQuery实现的分页功能示例
2017/01/22 Javascript
详解angular element()方法使用
2017/04/08 Javascript
详解extract-text-webpack-plugin 的使用及安装
2018/06/12 Javascript
5分钟学会Vue动画效果(小结)
2018/07/21 Javascript
JavaScript中如何调用Java方法
2020/09/16 Javascript
Nuxt的动态路由和参数校验操作
2020/11/09 Javascript
Django中使用locals()函数的技巧
2015/07/16 Python
Python Numpy 数组的初始化和基本操作
2018/03/13 Python
python+selenium打印当前页面的titl和url方法
2018/06/22 Python
使用EduBlock轻松学习Python编程
2018/10/08 Python
利用Python实现原创工具的Logo与Help
2018/12/03 Python
Python操作列表常用方法实例小结【创建、遍历、统计、切片等】
2019/10/25 Python
css3 自定义字体font-face使用介绍
2014/05/14 HTML / CSS
美国领先的商务贺卡出版商:The Gallery Collection
2018/02/13 全球购物
生产车间班组长岗位职责
2014/01/06 职场文书
致跳高运动员加油稿
2014/02/12 职场文书
小学教师先进事迹材料
2014/12/15 职场文书
优秀班组申报材料
2014/12/25 职场文书
golang elasticsearch Client的使用详解
2021/05/05 Golang
opencv检测动态物体的实现
2021/07/21 Python