概述

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

详细

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

(翻译至http://www.sevenson.com.au/actionscript/sat/)

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

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

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

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

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

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

这是一个简易但比较啰嗦的方法,以下是基本的步骤:

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

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

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

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

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

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

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

那么如何处理圆呢?

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

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

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

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

二、代码解析

1、html代码如下:

<canvas width="800" height="500" id="mycanvas">Loading...</canvas>
<div id="select-box"></div>

2、main.js主要是控制位移以及圆圈大小,代码如下:

var SHAPE_SIZE = 80, SHAPE_HANDLE_SIZE = 10;
var CANVAS_WIDTH, CANVAS_HEIGHT; var renderer;
var shA = null, shB = null; window.onload = function () {
var canvasTag = document.getElementById("mycanvas");
var canvas = canvasTag.getContext("2d"); CANVAS_WIDTH = canvasTag.width;
CANVAS_HEIGHT = canvasTag.height; renderer = new Renderer(canvas); setInterval(function () {
renderer.loopDraw();
}, 30); MouseEvent.addEvents(canvasTag); main();
}; function main () {
UIUtils.createSelect(); shA = UIUtils.createShape(150, 250, "shA-select");
renderer.add(shA); shB = UIUtils.createShape(540, 250, "shB-select");
renderer.add(shB);
} function getPolygonVertices (edges, r) {
var ca = 0, aiv = 360 / edges, ata = Math.PI / 180, list = new Array(); for (var k = 0; k < edges; k++) {
var x = Math.cos(ca * ata) * r,
y = Math.sin(ca * ata) * r; list.push(new Vec2(x, y)); ca += aiv;
} return list;
}

SHAPE_SIZE = 80, SHAPE_HANDLE_SIZE = 10 (SHAPE_SIZE设置外圆环大小,SHAPE_HANDLE_SIZE设置内圆大小),UIUtils.createShape(150, 250, "shA-select")设置第一个圆的x轴、y轴位移,还有外圆环选择的形状是什么,UIUtils.createShape(540, 250, "shB-select")设置第二个圆的x轴、y轴位移,还有外圆环选择的形状是什么。

3、SAT.js主要是控制拖动圆点时,外框的颜色等,部分代码如下:

var SAT = (function () {
function testCollision (A, B) {
var res, color = "#333333"; if (A.type == "polygon" && B.type == "polygon") {
res = polygonsCollisionTest(A, B);
} else if (A.type == "circle" && B.type == "circle") {
res = circlesCollisionTest(A, B);
} else {
var c, p;
if (A.type == "circle") {
c = A;
p = B;
} else {
c = B;
p = A;
} res = circlePolygonCollisionTest(c, p);
} if (res) {
color = "#FF0000";
} A.color = B.color = color;
}

4、Circle.js是控制第二个圆的外框颜色等,代码如下:

function Circle (r) {
this.objectIndex = Renderer.objectIndex++;
this.type = "circle";
this.r = r;
this.x = 0;
this.y = 0;
this.color = "#333333";
} Circle.prototype = {
draw : function (c) {
c.arc(0, 0, this.r, 0, Math.PI * 2);
}, getProjection : function (axis) {
var pro = Vec2.dot(new Vec2(this.x, this.y), axis) / axis.length(); return {min : pro - this.r, max : pro + this.r};
}
};

5、Polygon.js是控制第一个圆的外框颜色等,代码如下:

function Polygon (list) {
this.objectIndex = Renderer.objectIndex++;
this.type = "polygon";
this.vertices = list;
this.x = 0;
this.y = 0;
this.color = "#333333";
} Polygon.prototype = {
getRootCoordinate : function () {
var list = this.vertices, res = new Array(); for (var i = 0, l = list.length; i < l; i++) {
var coord = list[i]; res.push(new Vec2(coord.x + this.x, coord.y + this.y));
} return res;
}, draw : function (c) {
var list = this.vertices; if (list.length <= 1) {
return;
} c.moveTo(list[0].x, list[0].y); for (var i = 1, l = list.length; i < l; i++) {
var coord = list[i]; c.lineTo(coord.x, coord.y);
} c.closePath();
}, getSides : function () {
var list = this.vertices,
l = list.length,
res = new Array(); if (l >= 3) {
for (var j = 1, pre = list[0]; j < l; j++) {
var p = list[j]; res.push(Vec2.substract(p, pre)); pre = p;
} res.push(Vec2.substract(list[0], list[l - 1]));
} return res;
}, getProjection : function (axis) {
var list = this.getRootCoordinate(), min = null, max = null; for (var i = 0, l = list.length; i < l; i++) {
var p = list[i]; var pro = Vec2.dot(p, axis) / axis.length(); if (min === null || pro < min) {
min = pro;
} if (max === null || pro > max) {
max = pro;
}
} return {min : min, max : max};
}, getNearestPoint : function (p1) {
var list = this.getRootCoordinate(), rP = list[0], minDis = Vec2.distance(p1, rP); for (var i = 1, l = list.length; i < l; i++) {
var p2 = list[i], d = Vec2.distance(p1, p2); if (d < minDis) {
minDis = d; rP = p2;
}
} return rP;
}
};

6、Renderer.js是控制整体外框的属性,比如颜色边框等,代码如下:

Renderer.prototype = {
loopDraw : function () {
var c = this.canvas; c.fillStyle = "#ff0000";
c.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); for (var i = 0, l = this.displayList.length; i < l; i++) {
var o = this.displayList[i]; c.save();
c.translate(o.x, o.y); c.beginPath();
c.globalAlpha = 0.6;
c.arc(0, 0, SHAPE_HANDLE_SIZE, 0, Math.PI * 2);
c.fillStyle = "#0000FF";
c.fill(); c.beginPath();
c.globalAlpha = 1; o.draw(c); c.strokeStyle = o.color;
c.lineWidth = 2;
c.stroke();
c.restore();
}
}, add : function (o) {
this.displayList.push(o);
}, remove : function (o) {
for (var i = 0, l = this.displayList.length; i < l; i++) {
var child = this.displayList[i]; if (child.objectIndex == o.objectIndex) {
this.displayList.splice(i, 1); break;
}
}
}
};

三、文件以及演示截图

1、文件截图

2、演示截图

3、双击index.html文件即可运行看效果

四、兼容性

兼容主流浏览器

注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

JavaScript实现碰撞检测(分离轴定理)的更多相关文章

  1. javascript判断碰撞检测

    javascript判断碰撞检测 点与矩形的碰撞检测 <pre> /** * * @param x1 点 * @param y1 点 * @param x2 矩形view x * @par ...

  2. JavaScript动画-碰撞检测

    ▓▓▓▓▓▓ 大致介绍 碰撞检测是指在页面中有多个元素时,拖拽一个元素会出现碰撞问题,碰撞检测是以模拟拖拽和磁性吸附中的范围限定为基础的 效果:碰撞检测 ▓▓▓▓▓▓ 碰撞检测 先来看看碰撞检测的原理 ...

  3. javascript 实现加法分离。 plus(3)(4); // => 得到 7

    原文地址:http://cnodejs.org/topic/5230d5f0101e574521c86ff4 JavaScript 的设计是典型的函数式的编程范式匿名函数 JSON数据本身就是字符串, ...

  4. 使用jquery-tmpl使JavaScript与HTML分离

    背景:由于对JavaScript字符串拼接JavaScript变量产生了反感,也想用用JavaScript模板库,看了几个,由于时间原因选择了jQuery.tmpl.js,因为Visual Studi ...

  5. javascript九宫格碰撞检测

      JS九宫格碰撞检测这个东西 以前学过  这次主要是做面试项目web版的win10 桌面图片需要用碰撞检测 再写的时候竟然完全忘记了碰撞检测原理 和怎么写 综合来说还是写的太少  今天再学了一下 理 ...

  6. HTML5 Canvas核心技术—图形、动画与游戏开发.pdf8

    第6章 精灵 精灵(sprite),它是一种可以集成入动画之中的图像对象,赋予它们各种行为,精灵并非Canvas API的一部分,,但都是从它衍生而来 本章将会实现三种设计模式:策略模式(精灵与绘制器 ...

  7. “等一下,我碰!”——常见的2D碰撞检测

    转自:https://aotu.io/notes/2017/02/16/2d-collision-detection/ 在 2D 环境下,常见的碰撞检测方法如下: 外接图形判别法 轴对称包围盒(Axi ...

  8. JavaScript事件---事件入门

    内容提纲: 1.事件介绍 2.内联模型 3.脚本模型 4.事件处理函数 JavaScript事件是由访问Web页面的用户引起的一系列操作,例如:用户点击.当用户执行某些操作的时候,再去执行一系列代码. ...

  9. javascript中的cookie,以及事件解析

    Cookie: 它的意思是在本地的客户端的磁盘上以很小的文件形式保存数据,Cookie的处理原则上需要在服务器环境下运行,目前Chrome不可以在客户端操作Cookie,其他浏览器均可以,   Coo ...

随机推荐

  1. NodeJS学习资料合集

    1. 官网 nodejs 2.  How do I get started with Node.js,stackoverflow提问,收集非常多实用的网站 3.  node-books.github收 ...

  2. Spring3数据库事务管理机制

    Spring对事务的解决办法其实分为2种:编程式实现事务,AOP配置声明式解决方案. http://jinnianshilongnian.iteye.com/blog/1496953 Spring提供 ...

  3. Oracle中Instr用法

    在项目中用到了Oracle中 Instr 这个函数,顺便仔细的再次学习了一下这个知识. Oracle中,可以使用 Instr 函数对某个字符串进行判断,判断其是否含有指定的字符. 其语法为:Instr ...

  4. select case when if 的一些用法

    概述:sql语句中的case语句与高级语言中的switch语句,是标准sql的语法,适用于一个条件判断有多种值的情况下分别执行不同的操作. 首先,让我们看一下CASE的语法.在一般的SELECT中,其 ...

  5. Guava Files 源码分析(一)

    Files中的工厂 Files类中对InputStream, OutputStream以及Reader,Writer的操作封装了抽象工厂模式,抽象工厂是InputSupplier与OutputSupp ...

  6. 【BestCoder】【Round#41】

    枚举+组合数?+DP+数学问题 http://bestcoder.hdu.edu.cn/contests/contest_show.php?cid=582 QAQ许久没打过比赛,来一发BC,结果还是只 ...

  7. Strings of Power

    B. Strings of Power Volodya likes listening to heavy metal and (occasionally) reading. No wonder Vol ...

  8. [19] 半球形(Hemisphere)图形的生成算法

    顶点数据的生成 bool YfBuildHemisphereVertices ( Yreal radius, Yuint slices, Yuint stacks, YeOriginPose orig ...

  9. kth-smallest-element-in-a-sorted-matrix

    //有很多讨论,比如 // https://discuss.leetcode.com/topic/52865/my-solution-using-binary-search-in-c // https ...

  10. Eclipse 中java跨工程调用类

    在Eclipse中,有时候需要跨工程调用其他工程中的方法.如下面有两个Java Project : 如果要在A工程中调用B工程中的类,可以将B工程添加到A工程中: A---- >Build Pa ...