您的位置:首页 > 职场人生

码农干货系列【1】--方向包围盒(OBB)碰撞检测

2013-09-27 22:24 716 查看

简介

包围体是一个简单的几何空间,里面包含着复杂形状的物体。为物体添加包围体的目的是快速的进行碰撞检测或者进行精确的碰撞检测之前进行过滤(即当包围体碰撞,才进行精确碰撞检测和处理)。包围体类型包括球体、轴对齐包围盒(AABB)、有向包围盒(OBB)、8-DOP以及凸壳。如图1所示。




图1 依次是球体、AABB、OBB
可以看到图1是3D包围体,在2D包围体如图2所示:



图2 依次是球体、AABB、OBB


OBB

方向包围盒(Oriented bounding box),简称OBB。方向包围盒类似于AABB,但是具有方向性、可以旋转,AABB不能旋转。如图3所示。




图3 矩形和矩形投影检测的四条轴
要计算两个OBB是否碰撞,只需要计算他们在图3上的4个坐标轴上的投影是否有重叠,如果有,则两多边形有接触。这也可以扩展到任意多边形,如图4所示。




图4 矩形和三角形投影检测的五条轴
投影轴来自于多边形自身边的垂线。

判定方式:两个多边形在所有轴上的投影都发生重叠,则判定为碰撞;否则,没有发生碰撞。

OBB存在多种的表达方式,这里使用最常用的一种:一个中心点、2个矩形的边长、两个旋转轴(该轴垂直于多边形自身的边,用于投影计算)。代码如下所示:

(function (window) {

    var OBB = function (centerPoint, width, height, rotation) {

        this.centerPoint = centerPoint;
        this.extents = [width / 2, height / 2];
        this.axes = [new Vector2(Math.cos(rotation), Math.sin(rotation)), new Vector2(-1 * Math.sin(rotation), Math.cos(rotation))];

        this._width = width;
        this._height = height;
        this._rotation = rotation;
    }

    window.OBB = OBB;
})(window);


其所依赖的Vector2这个类如下所示:

(function (window) {
    Vector2 = function (x, y) {
        this.x = x || 0;
        this.y = y || 0;
    };

    Vector2.prototype = {
        sub: function (v) {
            return new Vector2(this.x - v.x, this.y - v.y)
        },
        dot: function (v) {
            return this.x * v.x + this.y * v.y;
        }
    };
    window.Vector2 = Vector2;
} (window))


然后基于这个数据结构,进行OBB之间的相交测试。为OBB扩展一个方法,即或者在任意轴上的投影半径:

OBB.prototype = {
    getProjectionRadius: function (axis) {
        returnthis.extents[0] * Math.abs(axis.dot(this.axes[0])) + this.extents[1] * Math.abs(axis.dot(this.axes[1]));
    }
}


这里你可能需要读者了解Vector2.dot的几何意义:若b为单位矢量,则a与b的点积即为a在方向b的投影

有了这些,就可以进行相交检测。由上面的判定方式,可以得出,两个矩形之间的碰撞检测需要判断四次(每个投影轴一次)。完整检测代码如下所示:

(function (window) {

    var CollisionDetector = {

        detectorOBBvsOBB: function (OBB1, OBB2) {
            var nv = OBB1.centerPoint.sub(OBB2.centerPoint);
            var axisA1 = OBB1.axes[0];
            if (OBB1.getProjectionRadius(axisA1) + OBB2.getProjectionRadius(axisA1) <= Math.abs(nv.dot(axisA1))) return false;
            var axisA2 = OBB1.axes[1];
            if (OBB1.getProjectionRadius(axisA2) + OBB2.getProjectionRadius(axisA2) <= Math.abs(nv.dot(axisA2))) return false;
            var axisB1 = OBB2.axes[0];
            if (OBB1.getProjectionRadius(axisB1) + OBB2.getProjectionRadius(axisB1) <= Math.abs(nv.dot(axisB1))) return false;
            var axisB2 = OBB2.axes[1];
            if (OBB1.getProjectionRadius(axisB2) + OBB2.getProjectionRadius(axisB2) <= Math.abs(nv.dot(axisB2))) return false;
            return true;

        }
    }

    window.CollisionDetector = CollisionDetector;
})(window)


这里拿两个OBB的中心点连线在坐标轴上的投影长度和两个矩形投影半径之和进行对比,如果半径之后都小于或者等于中心连线之后才判定为碰撞,否则判定为分离状态。

集成图形化测试接口

为了更加直观的测试OBB碰撞检测方法,使用Easeljs输出碰撞的状态。当两个矩形没有发生碰撞的时候,两矩形呈现蓝色;当两个矩形发生碰撞的时候,两矩形呈现红色。先引入相关的脚本库以及用于显示的canvas画布:

<script src="Vector2.js" type="text/javascript"></script>
<script src="OBB.js" type="text/javascript"></script>
<script src="CollisionDetector.js" type="text/javascript"></script>
<script src="easel.js" type="text/javascript"></script>
<canvas id="testCanvas" width="980" height="580">

然后进行OBB初始化以及碰撞检测:

var OBB1, OBB1x = 100, OBB1y = 150, OBB1w = 30, OBB1h = 140, OBB1r = 30;
var OBB2, OBB2x = 100, OBB2y = 70, OBB2w = 40, OBB2h = 110, OBB2r = 40;
var canvas;
var stage;
var color;

function init() {

    canvas = document.getElementById("testCanvas");
    stage = new Stage(canvas);

    Ticker.addListener(window);
}
     
function tick() {
    stage.removeAllChildren();

    OBB1r += 2;
    OBB2r += 1;
    OBB1 = new OBB(new Vector2(OBB1x, OBB1y), OBB1w, OBB1h, OBB1r * Math.PI / 180);
    OBB2 = new OBB(new Vector2(OBB2x, OBB2y), OBB2w, OBB2h, OBB2r * Math.PI / 180);
    var r = CollisionDetector.detectorOBBvsOBB(OBB1, OBB2);

    color=r?"red":"#00F";
    OBB1 = new Container();
    stage.addChild(OBB1);
    OBB1.x = OBB1x;
    OBB1.y = OBB1y;
    var frame1 = new Shape();
    frame1.graphics.beginFill(color).drawRect(0, 0, OBB1w, OBB1h);
    frame1.rotation = OBB1r;
    frame1.regX = OBB1w / 2;
    frame1.regY = OBB1h / 2;
    OBB1.addChild(frame1);

    OBB2 = new Container();
    stage.addChild(OBB2);
    OBB2.x = OBB2x;
    OBB2.y = OBB2y;
    var frame2 = new Shape();
    frame2.graphics.beginFill(color).drawRect(0, 0, OBB2w, OBB2h);
    frame2.rotation = OBB2r;
    frame2.regX = OBB2w / 2;
    frame2.regY = OBB2h / 2;
    OBB2.addChild(frame2);

    stage.update();
}
init();


以上代码定义了两个旋转的OBB包围盒,当他们发生碰撞则改变绘制的颜色,使其成为红色。运行代码,效果图5和6所示。




图5 未发生碰撞



图6 发生碰撞
这里是2D情况下的OBB碰撞检测,对于3D OBB碰撞检测,更为复杂。需要测试15个分离轴以确定OBB的相交状态,两个OBB的坐标轴各3个,以及垂直于每个轴的9个轴。除了坐标轴个数不一样,其相交测试思路和本文一致,本文不再探讨。

在线演示

更多干货敬请期待~~~~~

作者:张磊

出处:http://www.cnblogs.com/iamzhanglei

本文版权归【当耐特砖家】和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: