【问题标题】:Find where line-segments intersect with a box查找线段与框相交的位置
【发布时间】:2017-11-25 00:40:18
【问题描述】:

我试图找出一堆线段剪辑到它们周围的窗口中的位置。我看到了Liang–Barsky 算法,但这似乎假设段已经剪裁了窗口的边缘,而这些没有。

假设我有一个从(0,0)(26,16) 的窗口,以及以下部分:

(7,6) - (16,3)
(10,6) - (19,6)
(13,10) - (21,3)
(16,12) - (19,14)

插图:

我想我需要将片段扩展到某个 XY 点,直到它们碰到窗口的边缘,但我不知道如何。

如何找到这些线段(转换为线?)夹在窗口边缘的点?我将在 C# 中实现它,但这与语言无关。

【问题讨论】:

  • 你的问题不清楚。您有时会询问“线”,但您没有定义任何,仅定义“线段”。您是在问包含线段的线将与查看窗口的边界相交的位置,还是其他什么? “剪辑”通常意味着删除位于观察窗口之外的图形部分,但这似乎不是您的意思。您永远不会为您的示例提供查看窗口。以此类推。
  • @RoryDaulton 我的几何术语不符合标准,我写错了。我所拥有的只是线段。这就是为什么我包含注释以确保在我的措辞不正确时清晰。
  • 投反对票的人,想解释一下为什么这个问题离题或不清楚吗? @RoryDaulton 图片清楚地显示了窗口,应该简洁地弥补我对描述我的问题的正确措辞的无知。如果我获取这些片段并扩展它们,我如何找到点击该图像中的窗口/框的点。我不确定您如何难以理解这个问题:/

标签: math language-agnostic geometry lines


【解决方案1】:

如果你有两条线段PQ带点

P0 - P1
Q0 - Q1

线方程是

P = P0 + t(P1 - P0)
Q = Q0 + r(Q1 - Q0)

然后要找出它们在扩展后相交的位置,您需要为 tr

求解以下方程
P0 + t(P1 - P0) = Q0 + r(Q1 - Q0)

下面的代码可以做到这一点。 (摘自我自己的代码库)

    public static (double t, double r )? SolveIntersect(this Segment2D P, Segment2D Q)
    {
        // a-d are the entries of a 2x2 matrix
        var a = P.P1.X - P.P0.X;
        var b = -Q.P1.X + Q.P0.X;
        var c = P.P1.Y - P.P0.Y;
        var d = -Q.P1.Y + Q.P0.Y;

        var det = a*d - b*c;
        if (Math.Abs( det ) < Utility.ZERO_TOLERANCE)
            return null;

        var x = Q.P0.X - P.P0.X;
        var y = Q.P0.Y - P.P0.Y;

        var t = 1/det*(d*x - b*y);
        var r = 1/det*(-c*x + a*y);

        return (t, r);

    }

如果函数返回 null 则表示线是平行的,不能相交。如果返回结果,则可以这样做。

    var result = SolveIntersect( P, Q );
    if (result != null)
    {
        var ( t, r) = result.Value;
        var p = P.P0 + t * (P.P1 - P.P0);
        var q = Q.P0 + t * (Q.P1 - Q.P0);
        // p and q are the same point of course
    }

延伸的线段通常会与多个盒子边缘相交,但只有其中一个相交会在盒子内。您可以轻松检查。

   bool IsInBox(Point corner0, Point corner1, Point test) =>
      (test.X > corner0.X && test.X < corner1.X && test.Y > corner0.Y && test.Y < corner1.Y ;

这应该为您提供将线条延伸到盒子边缘所需的一切。

【讨论】:

    【解决方案2】:

    我设法弄清楚了。

    我可以通过首先找到我的线条方程,然后求解每个边的XY 来将我的线条延伸到盒子的边缘以获得它们的对应点。这需要将 max 和 min Y 以及 max 和 min X 传递给以下函数,返回 4 个值。如果该点在框的边界之外,则可以忽略。

    我的代码是用 C# 编写的,并且正在为 EMGU 的LineSegment2D 制作扩展方法。这是 OpenCv 的 .NET 包装器。

    我的代码:

        public static float GetYIntersection(this LineSegment2D line, float x)
        {
            Point p1 = line.P1;
            Point p2 = line.P2;
    
            float dx = p2.X - p1.X;
            if(dx == 0)
            {
                return float.NaN;
            }
    
            float m = (p2.Y - p1.Y) / dx; //Slope
            float b = p1.Y - (m * p1.X); //Y-Intercept
            return m * x + b;
        }
    
        public static float GetXIntersection(this LineSegment2D line, float y)
        {
            Point p1 = line.P1;
            Point p2 = line.P2;
    
            float dx = p2.X - p1.X;
            if (dx == 0)
            {
                return float.NaN;
            }
    
            float m = (p2.Y - p1.Y) / dx; //Slope
            float b = p1.Y - (m * p1.X); //Y-Intercept
            return (y - b) / m;
        }
    

    然后我可以获取这些点,检查它们是否在框的边界内,丢弃不在的点,删除重复的点(线直接进入角落)。这将给我留下一个x 和一个y 值,然后我可以将它们与我传递给函数的相应最小或最大YX 值配对以获得2 分。然后我可以用这两点制作我的新片段。

    【讨论】:

      【解决方案3】:

      Liang-Barsky算法的维基描述还不错,但代码有缺陷。

      注意:此算法旨在尽快抛出没有交叉的线。如果大多数线与矩形相交,那么从您的答案中接近可能会相当有效,否则 L-B 算法会获胜。

      This page详细描述了方法并包含简洁有效的代码:

      // Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html
      // This function inputs 8 numbers, and outputs 4 new numbers (plus a boolean value to say whether the clipped line is drawn at all).
      //
      bool LiangBarsky (double edgeLeft, double edgeRight, double edgeBottom, double edgeTop,   // Define the x/y clipping values for the border.
                        double x0src, double y0src, double x1src, double y1src,                 // Define the start and end points of the line.
                        double &x0clip, double &y0clip, double &x1clip, double &y1clip)         // The output values, so declare these outside.
      {
      
          double t0 = 0.0;    double t1 = 1.0;
          double xdelta = x1src-x0src;
          double ydelta = y1src-y0src;
          double p,q,r;
      
          for(int edge=0; edge<4; edge++) {   // Traverse through left, right, bottom, top edges.
              if (edge==0) {  p = -xdelta;    q = -(edgeLeft-x0src);  }
              if (edge==1) {  p = xdelta;     q =  (edgeRight-x0src); }
              if (edge==2) {  p = -ydelta;    q = -(edgeBottom-y0src);}
              if (edge==3) {  p = ydelta;     q =  (edgeTop-y0src);   }   
      
              if(p==0 && q<0) return false;   // Don't draw line at all. (parallel line outside)
      
              r = q/p; 
      
              if(p<0) {
                  if(r>t1) return false;         // Don't draw line at all.
                  else if(r>t0) t0=r;            // Line is clipped!
              } else if(p>0) {
                  if(r<t0) return false;      // Don't draw line at all.
                  else if(r<t1) t1=r;         // Line is clipped!
              }
          }
      
          x0clip = x0src + t0*xdelta;
          y0clip = y0src + t0*ydelta;
          x1clip = x0src + t1*xdelta;
          y1clip = y0src + t1*ydelta;
      
          return true;        // (clipped) line is drawn
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-12-24
        • 2013-06-12
        • 2016-12-22
        • 1970-01-01
        • 2011-03-23
        • 1970-01-01
        • 2012-09-02
        相关资源
        最近更新 更多