查找线段与框

时间:2017-06-21 22:52:51

标签: math language-agnostic geometry lines

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

说我有一个从(0,0)(26,16)的窗口,以及以下细分:

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

插图:

enter image description here

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

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

3 个答案:

答案 0 :(得分:0)

如果你有两个线段 P Q 带点

P0 - P1
Q0 - Q1

线方程是

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

然后在扩展后找出它们相交的位置,你需要为 t r

解决以下等式
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 ;

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

答案 1 :(得分:0)

我设法解决了这个问题。

我可以通过首先找到我的线的方程,然后求解每个边的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分。然后我可以用两点来创建我的新片段。

答案 2 :(得分:0)

Liang-Barsky算法的Wiki描述也不错,但代码存在缺陷。

注意:此算法希望尽快抛弃线而不会交叉。如果大多数线与矩形相交,那么从你的答案接近可能相当有效,否则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
}