【问题标题】:Circle collision with compound object与复合对象的圆碰撞
【发布时间】:2018-04-15 04:12:10
【问题描述】:

我想在圆和圆环的截面之间进行碰撞检测。圆圈由它的position 位置和radius 定义。另一个对象由 innerouter 半径定义,然后是 startPointendPoint 两个 [x, y] 点。

在下面的示例中,this 是圆圈,other 是环形部分。

首先我只是检查它是否与完整的环相撞。这没有问题。

float mag = this.position.Magnitude();
if (mag < other.InnerRadius() - this.radius ||
    mag > other.OuterRadius() + this.radius) {
    return false;
}

但是我需要检查圆是在两点定义的部分之内还是之外。我能得到的最接近的是检查它是否没有与开始和结束向量发生冲突,但是当圆圈完全在环形部分内时,这会返回错误的结果。

auto dot1 = Vector::Dot(position, other.StartPoint());
auto projected1 = dot1 / Vector::Dot(other.StartPoint(), other.StartPoint()) * other.StartPoint();
auto distance1 = Vector::Distance(position, projected1);

auto dot2 = Vector::Dot(position, other.EndPoint());
auto projected2 = dot2 / Vector::Dot(other.EndPoint(), other.EndPoint()) * other.EndPoint();
auto distance2 = Vector::Distance(position, projected2);

return distance1 < radius || distance2 < radius;

检查一个圆是否与这两个向量定义的对象发生碰撞的最简单方法是什么?

编辑:我在这里使用的所有点对象都是我的自定义Vector 类,它已经实现了所有矢量操作。

Edit2: 澄清一下,环对象的来源是 [0, 0]

【问题讨论】:

  • 您能否更具体地了解“所有”向量操作?
  • 点和叉积、量级等
  • 听起来不错。能画个图吗?段的角如何映射到 x、y?为什么不使用角度?
  • 我也有点困惑。如果 position 存储为 [x, y] 向量,我不明白将其大小与另一个对象的半径进行比较如何告诉您一些有意义的事情:您将得到与 -this.position 相同的结果,因为它的大小仍然是相同,但该位置已完全移动到其他地方。我可能会误解这一点,但我希望如果我是这样,对我如何阅读您的问题的解释将帮助您将其编辑成更多人会理解的内容。
  • @ma 起点和终点是在对象构造时计算的,然后在对象围绕 [0, 0] 旋转时重新计算。我之所以选择这种方法,是因为我认为最好保存这些信息,而不是每次检查碰撞时从角度计算它。

标签: c++ geometry collision-detection


【解决方案1】:

这是一个简单的算法。

首先,让我们就变量名达成一致:

这里是r1 ≤ r2-π/2 ≤ a1 ≤ a2 ≤ π/2

(正如我在 cmets 中提醒的那样,您有起点和终点而不是角度,但我将使用角度,因为它们看起来更方便。您可以通过 atan2(y-ry, x-rx) 从点轻松获取角度,只需确保a1 ≤ a2。或者你可以重写算法以完全不使用角度。)

我们需要考虑 3 种不同的情况。具体情况取决于圆心相对于环段的位置:

在第一种情况下,正如您已经想到的那样,如果向量 (cx-rx, cy-ry) 的长度大于 r1-rc 且小于 r2+rc,则会发生冲突。

在第二种情况下,如果圆心和最近的直边之间的距离小于rc,则会发生碰撞。

在第 3 种情况下,如果圆心与最近的 4 个角之间的距离小于rc,则会发生碰撞。

这是一些伪代码:

rpos = vec2(rx,ry); // Ring segment center coordinates
cpos = vec2(cx,cy); // Circle coordinates

a = atan2(cy-ry, cx-rx); // Relative angle
r = length(cpos - rpos); // Distance between centers

if (a > a1 && a < a2) // Case 1
{
    does_collide = (r+rc > a1 && r-rc < a2);
}
else
{
    // Ring segment corners:
    p11 = vec2(cos(a1), sin(a1)) * r1;
    p12 = vec2(cos(a1), sin(a1)) * r2;
    p21 = vec2(cos(a2), sin(a2)) * r1;
    p22 = vec2(cos(a2), sin(a2)) * r2;

    if (((cpos-p11) · (p12-p11) > 0 && (cpos-p12) · (p11-p12) > 0) ||
        ((cpos-p21) · (p22-p21) > 0 && (cpos-p22) · (p21-p22) > 0)) // Case 2
    {
        // Normals of straight edges:
        n1 = normalize(vec2(p12.y - p11.y, p11.x - p12.x));
        n2 = normalize(vec2(p21.y - p22.y, p22.x - p21.x));

        // Distances to edges:
        d1 = n1 · (cpos - p11);
        d2 = n2 · (cpos - p21);

        does_collide = (min(d1, d2) < rc);
    }
    else // Case 3
    {
        // Squared distances to corners
        c1 = length_sqr(cpos-p11);
        c2 = length_sqr(cpos-p12);
        c3 = length_sqr(cpos-p21);
        c4 = length_sqr(cpos-p22);

        does_collide = (sqrt(min(c1, c2, c3, c4)) < rc);
    }
}

【讨论】:

  • 嘿。就在我设法发布自己的答案之前几分钟,而且比我要发布的要清晰得多......你忽略了这样一个事实,即 OP 将起点和终点作为向量而不是角度.从中计算角度很容易,但这意味着没有必要再次从角度计算角位置。但这只是意味着 OP 可以进一步简化您的答案,并不意味着您的答案有任何问题。
  • @hvd 是的,我完全无视这一点。 :P 我添加了一条通知,告诉 OP 他们应该先转换为角度或调整代码。
【解决方案2】:

将小圆比作射线:

首先查看圆是否包围了原点;如果是这样,那么它与射线相交。否则,请继续阅读。

考虑从原点到圆心的向量 v。对其进行归一化,对射线 R 进行归一化,然后取叉积 Rxv。如果为正,则v从R开始逆时针,否则从R顺时针。无论哪种方式,取acos得到它们之间的角度。

如果圆的半径为 r 且圆心距原点的距离为 d,则圆的角半宽度(从原点看)为 asin(r/d)。如果 R 和 v 之间的夹角小于该夹角,则圆与射线相交。

假设您知道对象从起点到终点是顺时针还是逆时针延伸。 (数字不会告诉您,您必须已经知道,否则问题无法解决。)在您的示例中,它是顺时针方向的。现在你必须小心;如果圆弧的角长外的较小扇区中。但假设对象跨度小于 pi,当且仅当它从起点顺时针方向和终点逆时针方向时,圆才在对象的扇区内(即射线之间)。

【讨论】:

    猜你喜欢
    • 2012-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-15
    • 2015-08-07
    • 2014-09-03
    相关资源
    最近更新 更多