简体   繁体   English

libgdx中的Circle-Rectangle碰撞侧检测

[英]Circle-Rectangle collision side detection in libgdx

I have spent hours looking for the solution to this: I am developing a little top-down game with libgdx (maybe it matters what engine i am using). 我花了几个小时寻找解决方案:我正在用libgdx开发一个小型自上而下的游戏(也许这对我使用的引擎很重要)。 Now i have to implement the collision detection between my character (circle) and the wall (rectangle). 现在我必须在我的角色(圆圈)和墙壁(矩形)之间实现碰撞检测。 I want the character to slide along the wall on collision, if sliding is possible. 如果可以滑动,我希望角色在碰撞时沿着墙壁滑动。 Let me explain: 让我解释:

  • If i am moving 45 degrees right up i can collide with the down, the left or the corner of a wall. 如果我向上移动45度,我可以与墙的左下角碰撞。
  • If i collide with the left i want to stop x-movement and move only up. 如果我与左边碰撞,我想停止x移动并向上移动。 If i leave the wall then i want to go on moving right up. 如果我离开墙壁,那么我想继续前进。 The same with the down side (stop y-movement) 与下侧相同(停止y运动)
  • If i collide with the Corner i want to stop movement (sliding not possible). 如果我与角落碰撞,我想停止运动(滑动不可能)。

What i am doing actually is to check if the left line of the rectangle intersects my circle. 我实际上在做的是检查矩形的左边是否与我的圆相交。 Then i check intersection between the left line of wall and my circle and the bottom line of wall and my circle. 然后我检查左边的墙和我的圆圈以及墙的底线和我的圆圈之间的交叉点。 Depending on which intersection occuret i set back x/y possition of my circle and set x/y Speed to 0. The Problem is, that most times not a collision bt an overlap occures. 根据哪个交点发生,我设置了我的圆的x / y,并将x / y速度设置为0.问题是,大多数情况下不是碰撞而是发生 重叠 So the bottom check returns true, even if in reality the circle would only collide with the right. 所以底部检查返回true,即使实际上圆圈只会与右边碰撞。 In this case both intersection test would return true and i would reset both speeds like on the Corner collision. 在这种情况下,两个交叉点测试都将返回true,我将重置两个速度,就像在Corner碰撞中一样。 How can i solve this Problem? 我怎么解决这个问题? Is ther a better way to detect collision and collision side or corner? 是否有更好的方法来检测碰撞和碰撞的侧面或角落? I don't Need the exact Point of collision just the side of the rectangle. 我不需要在矩形的一侧确切的碰撞点。

Edit: I have to say, that the rects aren't rotated just parallel to the x-axis. 编辑:我不得不说,rects不是平行于x轴旋转。

You can find an explanation for circle/rectangle collision below, but please note that this type of collision might not be necessary for your needs. 您可以在下面找到圆/矩形碰撞的解释,但请注意,这种类型的碰撞可能不是您的需要所必需的。 If, for example, you had a rectangle bounding box for your character the algorithm would be simpler and faster. 例如,如果您的角色有一个矩形边界框,则算法会更简单,更快捷。 Even if you are using a circle, it is probable that there is a simpler approach that is good enough for your purposes. 即使您使用的是圆形,也可能有一种更简单的方法可以满足您的需要。

I though about writing the code for this, but it would take too long so here is only an explanation: 我虽然为此编写代码,但这需要太长时间,所以这里只是一个解释:

Here is a example movement of your character circle, with its last (previous) and current positions. 以下是角色圈的示例移动,包括其最后(前一个)和当前位置。 Wall rectangle is displayed above it. 墙上方显示墙面矩形。


在此输入图像描述


Here is that same movement, dotted lines represent the area the circle sweeps in this move. 这是相同的运动,虚线表示圆圈在此移动中扫过的区域。 The sweep area is capsule shaped. 扫掠区域是胶囊形状。


在此输入图像描述

It would be difficult to calculate the collision of these two object, so we need to do this differently. 计算这两个对象的碰撞是很困难的,所以我们需要以不同的方式做到这一点。 If you look at the capsule on the previous image, you will see that it is simply the movement line extended in every direction by the radius of the circle. 如果你看一下上一张图像上的胶囊,你会看到它只是在每个方向上由圆的半径延伸的运动线。 We can move that "extension" from the movement line to the wall rectangle. 我们可以将“扩展”从移动线移动到墙矩形。 This way we get a rounded rectangle like on the image below. 这样我们得到一个圆角矩形,如下图所示。


在此输入图像描述

The movement line will collide with this extended (rounded) rectangle if and only if the capsule collides with the wall rectangle, so they are somehow equivalent and interchangeable. 当且仅当胶囊与墙矩形碰撞时,移动线将与此扩展(圆角)矩形碰撞,因此它们在某种程度上是等效且可互换的。

Since this collision calculation is still non-trivial and relatively expensive, you can first do a fast collision check between the extended wall rectangle (non-rounded this time) and the bounding rectangle of the movement line. 由于此碰撞计算仍然非常重要且相对昂贵,因此您可以先在扩展墙矩形(此时为非舍入)和运动线的边界矩形之间进行快速碰撞检查。 You can see these rectangles on the image below - they are both dotted. 您可以在下面的图像上看到这些矩形 - 它们都是点缀的。 This is a fast and easy calculation, and while you play the game there will probably NOT be an overlap with a specific wall rectangle >99% of the time and collision calculation will stop here. 这是一个快速简便的计算,当您玩游戏时,可能不会与特定的墙矩形重叠> 99%的时间,碰撞计算将在此处停止。


在此输入图像描述

If however there is an overlap, there is probably a collision of the character circle with wall rectangle, but it is not certain as will be demonstrated later. 然而,如果存在重叠,则可能存在字符圆与墙矩形的碰撞,但是不确定,如稍后将说明的。

Now you need to calculate the intersection between the movement line itself (not its bounding box) and the extended wall rectangle. 现在,您需要计算移动线本身(不是其边界框)与扩展墙矩形之间的交点。 You can probably find an algorithm how to do this online, search for line/rectangle intersection, or line/aabb intersection (aabb = Axis Aligned Bounding Box). 您可以找到一个算法如何在线执行此操作,搜索线/矩形交点或线/ aabb交点(aabb = Axis Aligned Bounding Box)。 The rectangle is axis-aligned and this makes the calculation simpler. 矩形是轴对齐的,这使计算更简单。 The algorithm can give you intersection point or points since it is possible that there are two - in this case you choose the closest one to the starting point of the line. 该算法可以为您提供一个或多个交叉点,因为有可能有两个 - 在这种情况下,您可以选择最接近该线的起点。 Below is an example of this intersection/collision. 以下是此交叉/碰撞的示例。


在此输入图像描述

When you get an intersection point, it should be easy to calculate on which part of the extended rectangle this intersection is located. 当你得到一个交叉点时,应该很容易计算这个交叉点所在的扩展矩形的哪个部分。 You can see these parts on the image above, separated by red lines and marked with one or two letters (l - left, r - right, b - bottom, t - top, tl - top and left etc). 您可以在上面的图像上看到这些部分,用红线分隔并标有一个或两个字母(l - 左,r - 右,b - 底部,t - 顶部,tl - 顶部和左侧等)。
If the intersection is on parts l, r, b or t (the single letter ones, in the middle) then you are done. 如果交叉点位于部分l,r,b或t(中间的单字母),那么您就完成了。 There is definitely a collision between character circle and wall rectangle, and you know on which side. 字符圈和墙矩形之间肯定存在碰撞,你知道在哪一边。 In the example above, it is on the bottom side. 在上面的例子中,它位于底部。 You should probably use 4 variables called something like isLeftCollision , isRightCollision , isBottomCollsion and isTopCollision . 你应该使用4个变量,如isLeftCollisionisRightCollisionisBottomCollsionisTopCollision In this case you would set isBottomCollision to true, while the other 3 would remain at false. 在这种情况下,您将isBottomCollision设置为true,而其他3将保持为false。

However, if the intersection is on the corner, on the two-letter sections, additional calculations are needed to determine if there is an actual collision between character circle and wall rectangle. 但是,如果交叉点位于角上,在两个字母的部分上,则需要进行其他计算以确定字符圆和墙矩形之间是否存在实际碰撞。 Image below shows 3 such intersections on the corners, but there is an actual circle-rectangle collision on only 2 of them. 下图显示了角落上的3个这样的交叉点,但只有2个实际的圆形 - 矩形碰撞。


在此输入图像描述

To determine if there is a collision, you need to find an intersection between the movement line and the circle centered in the closest corner of the original non-extended wall rectangle. 要确定是否存在碰撞,您需要在移动线和以原始非扩展墙矩形的最近角为中心的圆之间找到交点。 The radius of this circle is equal to the radius of character circle. 该圆的半径等于字符圆的半径。 Again, you can google for line/circle intersection algorithm (maybe even libgdx has one), it isn't complex and shouldn't be hard to find. 再一次,你可以google for line / circle intersection算法(甚至libgdx也有),它并不复杂,不应该很难找到。
There is no line/circle intersection (and no circle/rectangle collision) on bl part, and there are intersections/collisions on br and tr parts. bl部分没有直线/圆形交叉(并且没有圆/矩形碰撞),并且br和tr部分存在交叉/碰撞。
In the br case you set both isRightCollision , isBottomCollsion to true and in the tr case you set both isRightCollision and isTopCollision to true. 在这种情况下,你将isRightCollisionisBottomCollsion都设置为true,在tr情况下你将isRightCollisionisTopCollision设置为true。

There is also one edge case you need to look out for, and you can see it on the image below. 您还需要注意一个边缘情况,您可以在下面的图像中看到它。


在此输入图像描述

This can happen if the movement of previous step ends in the corner of the the extended rectangle, but outside the radius of the inner rectangle corner (there was no collision). 如果前一步的移动在扩展矩形的角落中结束,但在内部矩形角的半径之外(没有碰撞),则会发生这种情况。
To determine if this is the case, simply check if movement staring point is inside the extended rectangle. 要确定是否是这种情况,只需检查运动起始点是否在扩展矩形内。
If it is, after the initial rectangle overlap test (between extended wall rectangle and bounding rectangle of movement line), you should skip line/rectangle intersection test (because in this case there might not be any intersection AND still be a circle/rectangle collision), and also simply based on movement stating point determine which corner you are in, and then only check for line/circle intersection with that corner's circle. 如果是,在初始矩形重叠测试之后(在扩展墙矩形和移动线的边界矩形之间),你应该跳过线/矩形交叉测试(因为在这种情况下可能没有任何交叉并且仍然是圆/矩形碰撞),并且还简单地根据移动说明点确定您所在的角落,然后仅检查与该角落圆圈的线/圆交叉点。 If there is intersection, there is a character circle/wall rectangle collision, otherwise not. 如果有交叉点,则会出现字符圆/墙矩形碰撞,否则不会。

After all of this, the collision code should be simple: 毕竟,碰撞代码应该很简单:

// x, y - character coordinates
// r - character circle radius
// speedX, speedY - character speed
// intersectionX, intersectionY - intersection coordinates
// left, right, bottom, top - wall rect positions

// I strongly recomment using a const "EPSILON" value
// set it to something like 1e-5 or 1e-4
// floats can be tricky and you could find yourself on the inside of the wall
// or something similar if you don't use it :)

if (isLeftCollision) {
    x = intersectionX - EPSILON;
    if (speedX > 0) {
        speedX = 0;
    }
} else if (isRightCollision) {
    x = intersectionX + EPSILON;
    if (speedX < 0) {
        speedX = 0;
    }
}

if (isBottomCollision) {
    y = intersectionY - EPSILON;
    if (speedY > 0) {
        speedY = 0;
    }
} else if (isTopCollision) {
    y = intersectionY + EPSILON;
    if (speedY < 0) {
        speedY = 0;
    }
}

[Update] [更新]

Here is a simple and I believe efficient implementation of segment-aabb intersection that should be good enough for your purposes. 这是一个简单的,我认为有效的段 - aabb交叉实现,应该足够您的目的。 It is a slightly modified Cohen-Sutherland algorithm . 这是一个略微修改的Cohen-Sutherland算法 Also you can check out the second part of this answer . 您也可以查看此答案的第二部分。

public final class SegmentAabbIntersector {

    private static final int INSIDE = 0x0000;
    private static final int LEFT = 0x0001;
    private static final int RIGHT = 0x0010;
    private static final int BOTTOM = 0x0100;
    private static final int TOP = 0x1000;

    // Cohen–Sutherland clipping algorithm (adjusted for our needs)
    public static boolean cohenSutherlandIntersection(float x1, float y1, float x2, float y2, Rectangle r, Vector2 intersection) {

        int regionCode1 = calculateRegionCode(x1, y1, r);
        int regionCode2 = calculateRegionCode(x2, y2, r);

        float xMin = r.x;
        float xMax = r.x + r.width;
        float yMin = r.y;
        float yMax = r.y + r.height;

        while (true) {
            if (regionCode1 == INSIDE) {
                intersection.x = x1;
                intersection.y = y1;
                return true;
            } else if ((regionCode1 & regionCode2) != 0) {
                return false;
            } else {
                float x = 0.0f;
                float y = 0.0f;

                if ((regionCode1 & TOP) != 0) {
                    x = x1 + (x2 - x1) / (y2 - y1) * (yMax - y1);
                    y = yMax;
                } else if ((regionCode1 & BOTTOM) != 0) {
                    x = x1 + (x2 - x1) / (y2 - y1) * (yMin - y1);
                    y = yMin;
                } else if ((regionCode1 & RIGHT) != 0) {
                    y = y1 + (y2 - y1) / (x2 - x1) * (xMax - x1);
                    x = xMax;
                } else if ((regionCode1 & LEFT) != 0) {
                    y = y1 + (y2 - y1) / (x2 - x1) * (xMin - x1);
                    x = xMin;
                }

                x1 = x;
                y1 = y;
                regionCode1 = calculateRegionCode(x1, y1, r);
            }
        }
    }

    private static int calculateRegionCode(double x, double y, Rectangle r) {
        int code = INSIDE;

        if (x < r.x) {
            code |= LEFT;
        } else if (x > r.x + r.width) {
            code |= RIGHT;
        }

        if (y < r.y) {
            code |= BOTTOM;
        } else if (y > r.y + r.height) {
            code |= TOP;
        }

        return code;
    }
}

Here is some code example usage: 以下是一些代码示例用法:

public final class Program {

    public static void main(String[] args) {

        float radius = 5.0f;

        float x1 = -10.0f;
        float y1 = -10.0f;
        float x2 = 31.0f;
        float y2 = 13.0f;

        Rectangle r = new Rectangle(3.0f, 3.0f, 20.0f, 10.0f);
        Rectangle expandedR = new Rectangle(r.x - radius, r.y - radius, r.width + 2.0f * radius, r.height + 2.0f * radius);

        Vector2 intersection = new Vector2();

        boolean isIntersection = SegmentAabbIntersector.cohenSutherlandIntersection(x1, y1, x2, y2, expandedR, intersection);
        if (isIntersection) {
            boolean isLeft = intersection.x < r.x;
            boolean isRight = intersection.x > r.x + r.width;
            boolean isBottom = intersection.y < r.y;
            boolean isTop = intersection.y > r.y + r.height;

            String message = String.format("Intersection point: %s; isLeft: %b; isRight: %b; isBottom: %b, isTop: %b",
                    intersection, isLeft, isRight, isBottom, isTop);
            System.out.println(message);
        }

        long startTime = System.nanoTime();
        int numCalls = 10000000;
        for (int i = 0; i < numCalls; i++) {
            SegmentAabbIntersector.cohenSutherlandIntersection(x1, y1, x2, y2, expandedR, intersection);
        }
        long endTime = System.nanoTime();
        double durationMs = (endTime - startTime) / 1e6;

        System.out.println(String.format("Duration of %d calls: %f ms", numCalls, durationMs));
    }
}

This is the result I get from executing this: 这是我从执行此操作得到的结果:

Intersection point: [4.26087:-2.0]; isLeft: false; isRight: false; isBottom: true, isTop: false
Duration of 10000000 calls: 279,932343 ms

Please note that this is desktop performance, on an i5-2400 CPU. 请注意,这是i5-2400 CPU上的桌面性能。 It will probably be much slower on Android devices, but I believe still more than sufficient. 在Android设备上它可能会慢得多,但我相信还是绰绰有余。
I only tested this superficially, so if you find any errors, let me know. 我只是在表面上测试过,所以如果你发现任何错误,请告诉我。

If you use this algorithm, I believe you don't need special handling for that case where starting point is in the corner of the extended wall rectangle, since in this case you will get the intersection point at line start, and the collision detection procedure will continue to the next step (line-circle collision). 如果您使用此算法,我相信您不需要特殊处理,因为起始点位于扩展墙矩形的角落,因为在这种情况下,您将获得行开始处的交叉点,以及碰撞检测程序将继续下一步(线圈碰撞)。

I suppose you determine the collision by calculating the distance of the circles center with the lines. 我想你通过计算圆心与线的距离来确定碰撞。 We can simplify the case and tell that the circle colliding with the corner if both distances are equal and smaller than the radius. 如果距离相等且小于半径,我们可以简化情况并告知圆与角相撞。 The equality should have a tolerance of course. 平等当然应该有宽容。

More - may be not necessary- realistic approach would be to consider x,y speed and factor it in the equality check. 更多 - 可能不是必要的 - 现实的方法是考虑x,y速度并将其考虑在等式检查中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM