矩形区域与圆心区域的碰撞检测

这里写图片描述
这是一篇关于矩形与圆形区域碰撞检测的文章。虽然这常常被认为很复杂,但实际上却相当简单。

首先,你也许知道怎样检测圆与点是否碰撞:测试圆心与点的距离是否小于等于圆的半径。

DeltaX = CircleX - PointX;
DeltaY = CircleY - PointY;
return (DeltaX * DeltaX + DeltaY * DeltaY) < (CircleRadius * CircleRadius);

其实,矩形与圆的碰撞检测的原理和这没多大不同:首先找到矩形中离圆心最近的点,然后判断该点是否在圆内。

如果矩形没有被旋转,则使用如下公式即可找到矩形内离圆心最近的点:

NearestX = Max(RectX, Min(CircleX, RectX + RectWidth));
NearestY = Max(RectY, Min(CircleY, RectY + RectHeight));

注意:
1. (RectX, RectY) 对于X轴正向向右,Y轴正向向下时是矩形的左上角。
2. (RectX, RectY) 对于X轴正向向右,Y轴正向向上时是矩形的左下角。

结合以上两个步骤,使用如下3行代码即可检测矩形区域与圆形区域是否碰撞。

DeltaX = CircleX - Max(RectX, Min(CircleX, RectX + RectWidth));
DeltaY = CircleY - Max(RectY, Min(CircleY, RectY + RectHeight));
return (DeltaX * DeltaX + DeltaY * DeltaY) < (CircleRadius * CircleRadius);

以下是示例程序:
可以用鼠标拖拽以及伸缩矩形和圆,碰撞时会高亮显示。

<canvas id="canvas" width="600" height="300"></canvas>
<script type="text/javascript">
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var rx1 = 140, ry1 = 100, rx2 = 260, ry2 = 220, cx = 440, cy = 160, cr = 40;
    var mx = 0, my = 0, qx = 0, qy = 0;
    var min = Math.min;
    var max = Math.max;
    function clamp(v, x, y) {
        return v < x ? x : v > y ? y : v;
    }
    function dist(x1, y1, x2, y2) {
        var dx = x1 - x2, dy = y1 - y2;
        return Math.sqrt(dx * dx + dy * dy);
    }
    function p2c(px, py, cx, cy, cr) {
        return dist(px, py, cx, cy) < cr;
    }
    function r2c(rx1, ry1, rx2, ry2, cx, cy, cr) {
        return dist(clamp(cx, rx1, rx2), clamp(cy, ry1, ry2), cx, cy) < cr;
    }
    function p2r(px, py, rx1, ry1, rx2, ry2) {
        return px > rx1 && py > ry1 && px < rx2 && py < ry2;
    }
    context.strokeCircle = function(x, y, r) {
        this.beginPath();
        this.arc(x, y, r, 0, Math.PI * 2, false);
        this.closePath();
        this.stroke();
    }
    var hx, hy, hr = 7; // handle data
    var over = -1, dragging = false;
    function render() {
        var time = 0 | new Date(), x, y;
        updateTime = time + 25;
        //
        context.clearRect(0, 0, 640, 320);
        //
        if (over >= 0 && dragging) {
            x = mx - qx;
            y = my - qy;
            switch (over) {
                case 0:
                    cx += x - hx; qx = mx - hx;
                    cy += y - hy; qy = my - hy;
                    break;
                case 1: cr = max(16, dist(cx, cy, x, y)); break;
                case 2: rx1 = min(x, rx2 - 16); ry1 = min(y, ry2 - 16); break;
                case 3: rx2 = max(x, rx1 + 16); ry1 = min(y, ry2 - 16); break;
                case 4: rx1 = min(x, rx2 - 16); ry2 = max(y, ry1 + 16); break;
                case 5: rx2 = max(x, rx1 + 16); ry2 = max(y, ry1 + 16); break;
                case 6: ry1 = min(y, ry2 - 16); break;
                case 7: ry2 = max(y, ry1 + 16); break;
                case 8: rx1 = min(x, rx2 - 16); break;
                case 9: rx2 = max(x, rx1 + 16); break;
                case 10:
                    rx1 += x - hx;
                    ry1 += y - hy;
                    rx2 += x - hx;
                    ry2 += y - hy;
                    qx = mx - hx;
                    qy = my - hy;
                    break;
            }
        }
        //
        var co = r2c(rx1, ry1, rx2, ry2, cx, cy, cr);
        context.strokeStyle = co ? "red" : "blue";
        context.strokeCircle(cx, cy, cr);
        context.strokeCircle(cx, cy, 1);
        context.strokeRect(rx1, ry1, rx2 - rx1, ry2 - ry1);
        if (!co) {
            x = clamp(cx, rx1, rx2);
            y = clamp(cy, ry1, ry2);
            context.strokeCircle(x, y, 4);
            var dx = x - cx;
            var dy = y - cy;
            var dd = Math.sqrt(dx * dx + dy * dy);
            var csx = cx + dx / dd * cr;
            var csy = cy + dy / dd * cr;
            context.strokeCircle(csx, csy, 4);
            //
            context.beginPath();
            context.moveTo(x, y);
            context.lineTo(csx, csy);
            context.closePath();
            context.stroke();
        }
        //
        if (over >= 0 && !dragging) {
            context.strokeStyle = "red";
            context.strokeCircle(hx, hy, hr);
        }
    }
    var interval = null;
    var redraw = false;
    function update() {
        if (redraw) {
            redraw = false;
            render();
        }
    }
    function hp(i, x, y, c) {
        if (p2c(mx, my, x, y, hr)) {
            hx = x;
            hy = y;
            over = i;
            canvas.style.cursor = c || "move";
            return true;
        } else return false;
    }
    function handleMouse(e) {
        mx = e.offsetX;
        my = e.offsetY;
        if (!dragging) {
            var cdx = mx - cx;
            var cdy = my - cy;
            var cd = dist(mx, my, cx, cy);
            var cex = cx + cdx / cd * cr;
            var cey = cy + cdy / cd * cr;
            if (!((cd < cr - hr && hp(0, mx, my))
                || hp(1, cex, cey)
                || hp(2, rx1, ry1, "nw-resize")
                || hp(3, rx2, ry1, "ne-resize")
                || hp(4, rx1, ry2, "sw-resize")
                || hp(5, rx2, ry2, "se-resize")
                || hp(6, clamp(mx, rx1, rx2), ry1, "n-resize")
                || hp(7, clamp(mx, rx1, rx2), ry2, "s-resize")
                || hp(8, rx1, clamp(my, ry1, ry2), "w-resize")
                || hp(9, rx2, clamp(my, ry1, ry2), "e-resize")
                || hp(10, clamp(mx, rx1, rx2), clamp(my, ry1, ry2))
            )) {
                canvas.style.cursor = "";
                over = -1;
            }
        }
    }
    canvas.onmousemove = function(e) {
        handleMouse(e);
        redraw = true;
    }
    canvas.onmousedown = function(e) {
        e.preventDefault();
        handleMouse(e);
        if (over >= 0) {
            dragging = true;
            qx = mx - hx;
            qy = my - hy;
            redraw = true;
        }
    }
    canvas.onmouseup = function(e) {
        handleMouse(e);
        if (dragging) {
            dragging = false;
            over = -1;
            redraw = true;
        }
    }
    canvas.onmouseover = function(_) {
        if (interval == null) interval = setInterval(update, 10);
    }
    canvas.onmouseout = function(_) {
        if (interval != null) {
            clearInterval(interval);
            interval = null;
        }
    }
    render();
</script>

翻译自: https://yal.cc/rectangle-circle-intersection-test/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值