【问题标题】:2D collision between a moving circle and a fixed line segment运动圆和固定线段之间的 2D 碰撞
【发布时间】:2011-08-14 23:44:20
【问题描述】:

在游戏程序的上下文中,我有一个移动的圆和一个固定的线段。该段可以具有任意大小和方向。

  • 我知道圆的半径:r
  • 我知道移动前圆的坐标:(xC1, yC1)
  • 我知道移动后圆的坐标:(xC2, yC2)
  • 我知道线段末端的坐标:(xL1, yL1) - (xL2, yL2)

我在尝试计算时遇到了困难:

  • 布尔值:如果圆的任何部分在从 (xC1, yC1) 移动到 (xC2, yC2) 时碰到线段
  • 如果布尔值为真,圆心在碰到线段时的坐标(x,y)(我的意思是当圆第一次与线段相切时)

【问题讨论】:

  • 从技术上讲,您需要处理向量 xC1/yC1 和 xC2/yC2 之间的每个插值点,这就是我要使用的搜索词。但是,这是一个编程主题的编程板,这不是我们可以在这里回答的问题,我们不知道您使用的是哪种技术/语言/API。对不起。
  • @Russ C:事实上它确实属于这个社区,因为它涉及到一个软件算法。因此,它(至少)适用于任何可用于游戏编程的编程语言,所以问题是“与语言无关”。
  • 不,它涉及数学辩论。没有讨论技术或 API 或 OP 试图解决的问题。充其量是社区 wiki,最坏的情况是它可以变成关于最佳或最差尝试的辩论。有人可以发布一个很好的答案并将其否决,因为他们使用极坐标而不是笛卡尔坐标。请参阅stackoverflow.com/faq#dontask。我同意我应该更具体地措辞我的评论,以要求乔尔完善他的问题。

标签: algorithm 2d collision-detection


【解决方案1】:

我将使用伪算法来回答 - 无需任何代码。在我看来,有两种情况我们可能返回 true,如下图所示:

这里蓝色是你的圆圈,虚线是轨迹线,红线是你给定的线。

  • 我们构建了一条辅助轨迹线,从两个圆的中心到两个圆的中心。如果这条轨迹线与给定的线相交 - 返回 true。请参阅this question,了解如何计算该交集。
  • 在第二种情况下,第一次测试未能通过我们,但可能只是因为圆圈在轨道上通过时轻推了线。我们将需要以下构造:

从轨迹我们建立法线到每个点 A 和 B。然后这些线被切碎或延伸为辅助线(HaHb),因此它们从 AB 的长度为正好是圆的半径。然后我们检查这些辅助线中的每一个是否与轨迹线相交。如果他们确实返回 true。

  • 否则返回false

【讨论】:

  • 我认为这是比所选答案更好的答案。它更平易近人,并帮助读者更详细、直观地理解数学应该如何执行等
  • 听起来你对这个问题有很好的理解,但我不知道你的轻推案例方法是如何工作的。您能否解释一下为什么会这样或者更详细地解释一下。
  • 你的方法是否计算线段和圆的交点?
  • 如果任何一条线是垂直的或水平的怎么办?或者如果这条线试图烤圆?
【解决方案2】:

看这里:

Line segment / Circle intersection

如果 x 或 y 的平方根下得到的值为负数,则该线段不相交。除此之外,您可以在获得 x 和 y 后停止计算(注意:您可能会得到两个答案)

更新我已经修改了我的答案,以非常具体地解决您的问题。我将这个解决方案归功于 Doswa,因为我几乎一直在为 C# 编写它。基本策略是我们将定位线段中离圆心最近的点。基于此,我们将查看该最近点的距离,如果它在半径范围内,则沿着与圆半径最近的点的方向定位该点。

// I'll bet you already have one of these.
public class Vec : Tuple<double, double>
{
  public Vec(double item1, double item2) : base(item1, item2) { }
  public double Dot(Vec other) 
    { return Item1*other.Item1 + Item2*other.Item2; }
  public static Vec operator-(Vec first, Vec second) 
    { return new Vec(first.Item1 - second.Item1, first.Item2 - second.Item2);}
  public static Vec operator+(Vec first, Vec second) 
    { return new Vec(first.Item1 + second.Item1, first.Item2 + second.Item2);}
  public static Vec operator*(double first, Vec second) 
    { return new Vec(first * second.Item1, first * second.Item2);}
  public double Length() { return Math.Sqrt(Dot(this)); }
  public Vec Normalize() { return (1 / Length()) * this; }
}

public bool IntersectCircle(Vec origin, Vec lineStart, 
      Vec lineEnd, Vec circle, double radius, out Vec circleWhenHit)
{
    circleWhenHit = null;

    // find the closest point on the line segment to the center of the circle
    var line = lineEnd - lineStart;
    var lineLength = line.Length();
    var lineNorm = (1/lineLength)*line;
    var segmentToCircle = circle - lineStart;
    var closestPointOnSegment = segmentToCircle.Dot(line) / lineLength;

    // Special cases where the closest point happens to be the end points
    Vec closest;
    if (closestPointOnSegment < 0) closest = lineStart;
    else if (closestPointOnSegment > lineLength) closest = lineEnd;
    else closest = lineStart + closestPointOnSegment*lineNorm;

    // Find that distance.  If it is less than the radius, then we 
    // are within the circle
    var distanceFromClosest = circle - closest;
    var distanceFromClosestLength = distanceFromClosest.Length();
    if (distanceFromClosestLength > radius) return false;

    // So find the distance that places the intersection point right at 
    // the radius.  This is the center of the circle at the time of collision
    // and is different than the result from Doswa
    var offset = (radius - distanceFromClosestLength) *
                 ((1/distanceFromClosestLength)*distanceFromClosest);
    circleWhenHit = circle - offset;

    return true;
}

【讨论】:

  • 除了这个问题,圆圈在移动。您是否建议在模拟的每一步模拟运动并计算这个交叉点?
  • 是的。就计算而言,点积和平方是便宜的。我使用 GPU 进行实时光线追踪,我对点积的调用次数非常多。射线与球体相交方程是工具带中最有用的方程之一。唯一更好的可能是 Axis-Oriented-Bounding-Box (ABB)。球体和 ABB 通常用于在场景中包装更复杂的对象,以将场景划分为较小的测试用例集,因为它们的计算成本相对较低。
  • edit -- 该死...我的意思是 AABB(轴对齐边界框)。哦,好吧,答案是正确的。
【解决方案3】:

这里有一些 Java 计算点到线的距离(这不完整,但会给你基本的图片)。代码来自一个名为 '向量'。假设是向量对象被初始化为线向量。方法'distance'接受线向量开始的点(当然称为'at')和兴趣点。它计算并返回从该点到直线的距离。

public class Vector
{
double x_ = 0;
double y_ = 0;
double magnitude_ = 1;

public Vector()
{
}

public Vector(double x,double y)
{
    x_ = x;
    y_ = y;
}

public Vector(Vector other)
{
    x_ = other.x_;
    y_ = other.y_;
}

public void add(Vector other)
{
    x_ += other.x_;
    y_ += other.y_;
}

public void scale(double val)
{
    x_ *= val;
    y_ *= val;
}

public double dot(Vector other)
{
    return x_*other.x_+y_*other.y_;
}

public void cross(Vector other)
{
    x_ = x_*other.y_ - y_*other.x_;
}

public void unit()
{
    magnitude_ = Math.sqrt(x_*x_+y_*y_);
    x_/=magnitude_;
    y_/=magnitude_;
}

public double distance(Vector at,Vector point)
{
    //
    // Create a perpendicular vector
    //
    Vector perp = new Vector();
    perp.perpendicular(this);
    perp.unit();

    Vector offset = new Vector(point.x_ - at.x_,point.y_ - at.y_);
    double d = Math.abs(offset.dot(perp));

    double m = magnitude();
    double t = dot(offset)/(m*m);
    if(t < 0)
    {
        offset.x_ -= at.x_;
        offset.y_ -= at.y_;
        d = offset.magnitude();
    }
    if(t > 1)
    {
        offset.x_ -= at.x_+x_;
        offset.y_ -= at.y_+y_;
        d = offset.magnitude();
    }
    return d;
}

private void perpendicular(Vector other)
{
    x_ = -other.y_;
    y_ = other.x_;
}

public double magnitude()
{
    magnitude_ = Math.sqrt(x_*x_+y_*y_);
    return magnitude_;
}
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-02-17
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多