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 清空File控件的路径值
Jul 08 Javascript
javascript自启动函数的问题探讨
Oct 05 Javascript
简单的两种Extjs formpanel加载数据的方式
Nov 09 Javascript
JS测试显示屏分辨率以及屏幕尺寸的方法
Nov 22 Javascript
使用JavaScript实现网页版Pongo设计思路及源代码分享
Jun 16 Javascript
js的toUpperCase方法用法实例
Jan 27 Javascript
EasyUI的doCellTip实现鼠标放到单元格上提示单元格内容
Aug 24 Javascript
JS编写函数实现对身份证号码最后一位的验证功能
Dec 29 Javascript
Javascript ES6中数据类型Symbol的使用详解
May 02 Javascript
JavaScript异步加载问题总结
Feb 17 Javascript
Servlet3.0与纯javascript通过Ajax交互的实例详解
Mar 18 Javascript
微信小程序的部署方法步骤
Sep 04 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
解析在zend Farmework下如何创立一个FORM表单
2013/06/28 PHP
php 检查电子邮件函数(自写)
2014/01/16 PHP
php实现图片添加描边字和马赛克的方法
2014/12/10 PHP
php图片的二进制转换实现方法
2014/12/15 PHP
php源码 fsockopen获取网页内容实例详解
2016/09/24 PHP
php版阿里大于(阿里大鱼)短信发送实例详解
2016/11/30 PHP
Yii2实现多域名跨域同步登录退出
2017/02/04 PHP
Gambit vs ForZe BO3 第二场 2.13
2021/03/10 DOTA
Javascript 解疑
2009/11/11 Javascript
js遍历、动态的添加数据的小例子
2013/06/22 Javascript
JS获得浏览器版本和操作系统版本的例子
2014/05/13 Javascript
JavaScript获取元素尺寸和大小操作总结
2015/02/27 Javascript
JavaScript实现将xml转换成html table表格的方法
2015/04/17 Javascript
jquery+css实现的红色线条横向二级菜单效果
2015/08/22 Javascript
vue实现图片加载完成前的loading组件方法
2018/02/05 Javascript
JavaScript DOM元素常见操作详解【添加、删除、修改等】
2018/05/09 Javascript
使用puppeteer爬取网站并抓出404无效链接
2018/12/20 Javascript
vue插槽slot的理解和使用方法
2019/04/03 Javascript
vue 搭建后台系统模块化开发详解
2019/05/01 Javascript
基于vue-draggable 实现三级拖动排序效果
2020/01/10 Javascript
JS获取一个字符串中指定字符串第n次出现的位置
2021/02/10 Javascript
python打开文件并获取文件相关属性的方法
2015/04/23 Python
编写Python脚本使得web页面上的代码高亮显示
2015/04/24 Python
Python基于pygame实现的font游戏字体(附源码)
2015/11/11 Python
pandas DataFrame实现几列数据合并成为新的一列方法
2018/06/08 Python
python3学习之Splash的安装与实例教程
2018/07/09 Python
python3.4 将16进制转成字符串的实例
2019/06/12 Python
基于Python新建用户并产生随机密码过程解析
2019/10/08 Python
selenium+python配置chrome浏览器的选项的实现
2020/03/18 Python
浅谈keras中的keras.utils.to_categorical用法
2020/07/02 Python
创业计划书——互联网商机
2014/01/12 职场文书
学校募捐倡议书
2014/05/14 职场文书
“四风”问题整改措施和努力方向
2014/09/20 职场文书
KTV门卫岗位职责
2014/10/09 职场文书
优秀团队申报材料
2014/12/26 职场文书
Mysql中的触发器定义及语法介绍
2022/06/25 MySQL