打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
JavaScript实现碰撞检测(分离轴定理)

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

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

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

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

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

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

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

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

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

 

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

 

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

 

 

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

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

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

那么如何处理圆呢?

 

 

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

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

 

 

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

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

二、代码解析

1、html代码如下:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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主要是控制拖动圆点时,外框的颜色等,部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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是控制第二个圆的外框颜色等,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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是控制第一个圆的外框颜色等,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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是控制整体外框的属性,比如颜色边框等,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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文件即可运行看效果

 

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
【three.js详解之一】入门篇
一个不错的HTML5 Canvas多层点击事件监听实例
WebGL中文网
码农干货系列【1】
HTML5 canvas纸片3D旋转动画
Unity3d 动态读取外部文件
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服