【问题标题】:Find if point lies on line segment查找点是否在线段上
【发布时间】:2011-10-26 09:12:36
【问题描述】:

我有由这两点定义的线段:A(x1,y1,z1)B(x2,y2,z2)。我有一点 p(x,y,z)。如何检查点是否在线段上?

【问题讨论】:

  • 因为我需要 c# 中的任何示例代码
  • 是的,这对我来说听起来很明显:)
  • 我试图回复 MetaMapper 的帖子,但我没有 50 的声望。 MetaMapper 的解决方案是错误的。我个人花了很多时间进行调试,我不希望其他人不得不经历同样的事情。安迪的解决方案是正确的。它只需要转换为 C#。我希望这可以节省一些时间。

标签: c# algorithm 3d point


【解决方案1】:

求点P到线端点A、B的距离。如果AB = AP + PB,则P在线段AB上。

AB = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)+(z2-z1)*(z2-z1));
AP = sqrt((x-x1)*(x-x1)+(y-y1)*(y-y1)+(z-z1)*(z-z1));
PB = sqrt((x2-x)*(x2-x)+(y2-y)*(y2-y)+(z2-z)*(z2-z));
if(AB == AP + PB)
    return true;

【讨论】:

  • 我知道这已经很晚了,但是这个答案比公认的答案好得多。特别是当一个点在线段开始或结束时它起作用。
  • 优秀的答案。您可能要考虑的一件事是浮点舍入误差。假设 AB = 12.0000001 和 AP + PB = 12.000003,您仍然可能需要考虑“足够接近”,这取决于您在做什么。
  • 它的速度怎么样?与除法相比,Sqrt 相当慢。
  • 完全没有,处理器有专门的 Math.Sqrt() 指令。它需要和除法一样长的时间。
  • 没有平方根就不能做到这一点吗?既然你可以取双方的平方来获得AB^2 = AP^2 + PB^2 作为一个小的性能提升?
【解决方案2】:

如果点线上,那么:

(x - x1) / (x2 - x1) = (y - y1) / (y2 - y1) = (z - z1) / (z2 - z1)

计算所有三个值,如果它们相同(有一定程度的容差),你的点就在线。

要测试点是否在线段中,而不仅仅是在线,您可以检查

x1 < x < x2, assuming x1 < x2, or
y1 < y < y2, assuming y1 < y2, or
z1 < z < z2, assuming z1 < z2

【讨论】:

  • 其中一个是您要检查的点,另外两个是线的端点。你给每个点取什么名字并不重要,只要你是一致的。
  • AMH 是的 - 对于任何点 (x,y,z),只有当该点在线时,此等式才成立。它基本上是@Konstantin 的参数线方程答案,但消除了参数 p。您并不真正关心 p 的确切值,只关心 x、y 和 z 的值相同。
  • 如果 x1 == x2 或 y1 == y2,您的测试将失败
  • 只是为了完成这个答案,here你可以找到完整的数学解释
  • 如果 x 接近 x1 或 y 接近 y1 或 z 接近 z1 由于无法修复的浮点精度问题,则失败。不要使用此解决方案。数学考试很好,但 C# 代码的答案完全错误。
【解决方案3】:

首先take the cross product of AB and AP。如果它们是共线的,那么它将是 0。

此时,它仍然可能在延伸超过 B 或 A 之前的更大线上,所以我认为你应该能够检查 pz 是否在 az 和 bz 之间。

这个appears to be a duplicate,实际上,正如答案之一提到的,它在Beautiful Code

【讨论】:

【解决方案4】:

如果有人在寻找内联版本:

public static bool PointOnLine2D (this Vector2 p, Vector2 a, Vector2 b, float t = 1E-03f)
{
    // ensure points are collinear
    var zero = (b.x - a.x) * (p.y - a.y) - (p.x - a.x) * (b.y - a.y);
    if (zero > t || zero < -t) return false;

    // check if x-coordinates are not equal
    if (a.x - b.x > t || b.x - a.x > t)
        // ensure x is between a.x & b.x (use tolerance)
        return a.x > b.x
            ? p.x + t > b.x && p.x - t < a.x
            : p.x + t > a.x && p.x - t < b.x;

    // ensure y is between a.y & b.y (use tolerance)
    return a.y > b.y
        ? p.y + t > b.y && p.y - t < a.y
        : p.y + t > a.y && p.y - t < b.y;
}

【讨论】:

  • 排除您的 epsilon (ie. t) 零检查,共线检查可以写成 if (Vector.crossProduct(u = new Vector(a, b), new Vector(u, new Vector( a, p))) != 0) 返回 false;
【解决方案5】:

您的细分最好由参数方程定义

对于分段上的所有点,以下等式成立: x = x1 + (x2 - x1) * p y = y1 + (y2 - y1) * p z = z1 + (z2 - z1) * p

其中 p 是 [0;1] 中的数字

所以,如果有一个 p 使得你的点坐标满足那些 3个方程,你的观点就在这条线上。它 p 在 0 和 1 之间 - 也在线段上

【讨论】:

  • 你的意思是我使用 p 例如等于 1 并检查
  • 不,您只需针对 p 求解 3 个方程 - 如果所有 3 个值都在合理的误差范围内相等(它是浮点数 - 不会有精确匹配),那么您的点就在那条直线上。如果 p 介于 0 和 1 之间,则它在段内
  • @KonstantinPribluda - 感谢您的解释。我根据您的回答添加了一个答案。
【解决方案6】:

这里有一些用于 2D 案例的 C# 代码:

public static bool PointOnLineSegment(PointD pt1, PointD pt2, PointD pt, double epsilon = 0.001)
{
  if (pt.X - Math.Max(pt1.X, pt2.X) > epsilon || 
      Math.Min(pt1.X, pt2.X) - pt.X > epsilon || 
      pt.Y - Math.Max(pt1.Y, pt2.Y) > epsilon || 
      Math.Min(pt1.Y, pt2.Y) - pt.Y > epsilon)
    return false;

  if (Math.Abs(pt2.X - pt1.X) < epsilon)
    return Math.Abs(pt1.X - pt.X) < epsilon || Math.Abs(pt2.X - pt.X) < epsilon;
  if (Math.Abs(pt2.Y - pt1.Y) < epsilon)
    return Math.Abs(pt1.Y - pt.Y) < epsilon || Math.Abs(pt2.Y - pt.Y) < epsilon;

  double x = pt1.X + (pt.Y - pt1.Y) * (pt2.X - pt1.X) / (pt2.Y - pt1.Y);
  double y = pt1.Y + (pt.X - pt1.X) * (pt2.Y - pt1.Y) / (pt2.X - pt1.X);

  return Math.Abs(pt.X - x) < epsilon || Math.Abs(pt.Y - y) < epsilon;
}

【讨论】:

    【解决方案7】:

    如果使用 Visual Studio 使用 GraphicsPath,则让 dotnet 为您完成繁重的工作

    这也将允许您添加公差,如果只是在线外单击。

    using (Drawing2D.GraphicsPath gp = new Drawing2D.GraphicsPath())
    {
        gp.AddLine(new Point(x1, y1), new Point(x2, y2));
    
        // Make the line as wide as needed (make this larger to allow clicking slightly outside the line) 
        using (Pen objPen = new Pen(Color.Black, 6))
        {
            gp.Widen(objPen);
        }
    
        if (gp.IsVisible(Mouse.x, Mouse.y))
        {
            // The line was clicked
        }
    }
    

    【讨论】:

      【解决方案8】:

      叉积 (B - A) × (p - A) 应该比 B - A 短得多。理想情况下,叉积为零,但在有限精度浮点硬件上不太可能。

      【讨论】:

        【解决方案9】:

        我用它来计算点 a 和 b 之间的距离 AB。

        static void Main(string[] args)
        {
                double AB = segment(0, 1, 0, 4);
                Console.WriteLine("Length of segment AB: {0}",AB);
        }
        
        static double segment (int ax,int ay, int bx, int by)
        {
            Vector a = new Vector(ax,ay);
            Vector b = new Vector(bx,by);
            Vector c = (a & b);
            return Math.Sqrt(c.X + c.Y);
        }
        
        struct Vector
        {
            public readonly float X;
            public readonly float Y;
        
            public Vector(float x, float y)
            {
                this.X = x;
                this.Y = y;
            }
            public static Vector operator &(Vector a, Vector b)  
            {
                return new Vector((b.X - a.X) * (b.X - a.X), (b.Y - a.Y) * (b.Y - a.Y));
            }
        }
        

        基于Calculate a point along the line A-B at a given distance from A

        【讨论】:

          【解决方案10】:

          设 V1 为向量 (B-A),V2 = (p-A),对 V1 和 V2 进行归一化。

          如果 V1==(-V2) 则点 p 在线上,但在 A 之前,因此不在线段中。 如果 V1==V2 点 p 在线上。获取 (p-A) 的长度并检查它是否小于或等于 (B-A) 的长度,如果是,则该点在该段上,否则它超过 B。

          【讨论】:

            【解决方案11】:

            这是我可以在 WPF 中运行的代码

                public static class Math2DExtensions
                {
                    public static bool CheckIsPointOnLineSegment(Point point, Line line, double epsilon = 0.1)
                    {
                        // Thank you @Rob Agar           
                        // (x - x1) / (x2 - x1) = (y - y1) / (y2 - y1)
                        // x1 < x < x2, assuming x1 < x2
                        // y1 < y < y2, assuming y1 < y2          
            
                        var minX = Math.Min(line.APoint.X, line.BPoint.X);
                        var maxX = Math.Max(line.APoint.X, line.BPoint.X);
            
                        var minY = Math.Min(line.APoint.Y, line.BPoint.Y);
                        var maxY = Math.Max(line.APoint.Y, line.BPoint.Y);
            
                        if (!(minX <= point.X) || !(point.X <= maxX) || !(minY <= point.Y) || !(point.Y <= maxY))
                        {
                            return false;
                        }
                        
                        if (Math.Abs(line.APoint.X - line.BPoint.X) < epsilon)
                        {
                            return Math.Abs(line.APoint.X - point.X) < epsilon || Math.Abs(line.BPoint.X - point.X) < epsilon;
                        }
            
                        if (Math.Abs(line.APoint.Y - line.BPoint.Y) < epsilon)
                        {
                            return Math.Abs(line.APoint.Y - point.Y) < epsilon || Math.Abs(line.BPoint.Y - point.Y) < epsilon;
                        }
            
                        if (Math.Abs((point.X - line.APoint.X) / (line.BPoint.X - line.APoint.X) - (point.Y - line.APoint.Y) / (line.BPoint.Y - line.APoint.Y)) < epsilon)
                        {
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
            
                public record Line
                {
                    public Point APoint { get; init; }
            
                    public Point BPoint { get; init; }
                }
            

            我的代码在github

            谢谢@Rob Agar 和@MetaMapper

            【讨论】:

              【解决方案12】:

              您可以检查该点是否位于由 point1 和 point2 定义的两个平面以及线方向之间:

              ///  Returns the closest point from @a point to this line on this line.
              vector3 <Type>
              line3d <Type>::closest_point (const vector3 <Type> & point) const
              {
                  return this -> point () + direction () * dot (point - this -> point (), direction ());
              }
              
              ///  Returns true if @a point lies between point1 and point2.
              template <class Type>
              bool
              line_segment3 <Type>::is_between (const vector3 <Type> & point) const
              {
                  const auto closest = line () .closest_point (point);
                  return abs ((closest - point0 ()) + (closest - point1 ())) <= abs (point0 () - point1 ());
              }
              

              【讨论】:

              • 这根本不是 C# 代码 - 所以对 this 问题没有用 - 对于同一个 Q 的 C/C++ 版本可能没问题...而且解释不是很好对普通人友好。
              猜你喜欢
              • 2013-02-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-12-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多