我试图找出一堆线段夹在它们周围的窗口的位置。我看到了Liang–Barsky算法,但似乎假设这些段已经剪切了窗口的边缘,而这些并没有。
说我有一个从(0,0)
到(26,16)
的窗口,以及以下细分:
(7,6) - (16,3)
(10,6) - (19,6)
(13,10) - (21,3)
(16,12) - (19,14)
插图:
我想我需要将片段扩展到某个X
或Y
点,直到它们到达窗口的边缘,但我不知道如何。
如何找到这些线段(转换为线?)的点到窗口边缘?我将在C#中实现它,但这与语言无关。
答案 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)
我设法解决了这个问题。
我可以通过首先找到我的线的方程,然后求解每个边的X
和Y
来获得它们的对应点,从而将我的线延伸到方框的边缘。这需要将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
值,然后我可以将其与我传入函数的相应最小或最大Y
或X
值配对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
}