Обнаружение совпадающего подмножества двух совпадающих отрезков
Этот вопрос связан с:
Но обратите внимание, что интересная подзадача полностью затухает в большинстве решений, которые просто возвращают null для совпадающего случая, даже если есть три подсекции:
- совпадающие, но не перекрывающиеся
- касание только точек и совпадений
- сегмент перекрытия/совпадающей строки
Например, мы могли бы создать функцию С# следующим образом:
public static PointF[] Intersection(PointF a1, PointF a2, PointF b1, PointF b2)
где (a1, a2) - один отрезок линии и (b1, b2) - другой.
Эта функция должна охватывать все странные случаи, когда большинство реализаций или объяснений затухают. Чтобы учесть странность совпадающих строк, функция могла возвращать массив объектов PointF:
- нулевые точки результата (или нуль), если строки параллельны или не пересекаются (бесконечные линии пересекаются, но сегменты линии не пересекаются, или строки параллель)
- одна точка результата (содержащая местоположение пересечения), если они пересекаются или если они совпадают в одной точке
- две точки результата (для перекрытия части сегментов линии), если две строки совпадают
Ответы
Ответ 1
Похоже, у вас есть свое решение, и это здорово. У меня есть некоторые предложения по его улучшению.
Метод имеет основную проблему юзабилити, поскольку очень сложно понять (1), что параметры идут в среднем, и (2) то, что означают результаты. Оба являются небольшими загадками, которые вы должны выяснить, хотите ли вы использовать этот метод.
Я был бы более склонен использовать систему типов, чтобы сделать ее более понятной, что делает этот метод.
Я бы начал с определения типа - возможно, структуры, особенно если он будет неизменным - называется LineSegment. LineSegment состоит из двух структур PointF, представляющих конечную точку.
Во-вторых, я бы определил абстрактный базовый тип "Locus" и производные типы EmptyLocus, PointLocus, LineSegmentLocus и, возможно, UnionLocus, если вам нужно представить локус, который является объединением двух или более локусов. Пустой локус - это просто синглтон, точечный локус - всего лишь одна точка и т.д.
Теперь ваша подпись метода становится более понятной:
static Locus Intersect(LineSegment l1, LineSegment l2)
Этот метод принимает два сегмента линии и вычисляет локус точек, который является их пересечением - либо пустым, либо одной точкой, либо сегментом линии.
Обратите внимание, что вы можете обобщить этот метод. Вычисление пересечения линейного сегмента с отрезком линии является сложным, но вычисление пересечения отрезка с точкой или точкой с точкой или чем-либо с пустым локусом легко. И нетрудно расширить пересечение к произвольным объединениям локусов. Поэтому вы могли бы написать:
static Locus Intersect(Locus l1, Locus l2)
И теперь, теперь становится ясно, что Intersect может быть методом расширения на локусе:
static Locus Intersect(this Locus l1, Locus l2)
Добавьте неявное преобразование из PointF в PointLocus и LineSegment в LineSegmentLocus, и вы можете сказать такие вещи, как
var point = new PointF(whatever);
var lineseg = new LineSegment(somepoint, someotherpoint);
var intersection = lineseg.Intersect(point);
if (intersection is EmptyLocus) ...
Использование системы типов хорошо может значительно улучшить читаемость программы.
Ответ 2
// port of this JavaScript code with some changes:
// http://www.kevlindev.com/gui/math/intersection/Intersection.js
// found here:
// http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/563240#563240
public class Intersector
{
static double MyEpsilon = 0.00001;
private static float[] OverlapIntervals(float ub1, float ub2)
{
float l = Math.Min(ub1, ub2);
float r = Math.Max(ub1, ub2);
float A = Math.Max(0, l);
float B = Math.Min(1, r);
if (A > B) // no intersection
return new float[] { };
else if (A == B)
return new float[] { A };
else // if (A < B)
return new float[] { A, B };
}
// IMPORTANT: a1 and a2 cannot be the same, e.g. a1--a2 is a true segment, not a point
// b1/b2 may be the same (b1--b2 is a point)
private static PointF[] OneD_Intersection(PointF a1, PointF a2, PointF b1, PointF b2)
{
//float ua1 = 0.0f; // by definition
//float ua2 = 1.0f; // by definition
float ub1, ub2;
float denomx = a2.X - a1.X;
float denomy = a2.Y - a1.Y;
if (Math.Abs(denomx) > Math.Abs(denomy))
{
ub1 = (b1.X - a1.X) / denomx;
ub2 = (b2.X - a1.X) / denomx;
}
else
{
ub1 = (b1.Y - a1.Y) / denomy;
ub2 = (b2.Y - a1.Y) / denomy;
}
List<PointF> ret = new List<PointF>();
float[] interval = OverlapIntervals(ub1, ub2);
foreach (float f in interval)
{
float x = a2.X * f + a1.X * (1.0f - f);
float y = a2.Y * f + a1.Y * (1.0f - f);
PointF p = new PointF(x, y);
ret.Add(p);
}
return ret.ToArray();
}
private static bool PointOnLine(PointF p, PointF a1, PointF a2)
{
float dummyU = 0.0f;
double d = DistFromSeg(p, a1, a2, MyEpsilon, ref dummyU);
return d < MyEpsilon;
}
private static double DistFromSeg(PointF p, PointF q0, PointF q1, double radius, ref float u)
{
// formula here:
//http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
// where x0,y0 = p
// x1,y1 = q0
// x2,y2 = q1
double dx21 = q1.X - q0.X;
double dy21 = q1.Y - q0.Y;
double dx10 = q0.X - p.X;
double dy10 = q0.Y - p.Y;
double segLength = Math.Sqrt(dx21 * dx21 + dy21 * dy21);
if (segLength < MyEpsilon)
throw new Exception("Expected line segment, not point.");
double num = Math.Abs(dx21 * dy10 - dx10 * dy21);
double d = num / segLength;
return d;
}
// this is the general case. Really really general
public static PointF[] Intersection(PointF a1, PointF a2, PointF b1, PointF b2)
{
if (a1.Equals(a2) && b1.Equals(b2))
{
// both "segments" are points, return either point
if (a1.Equals(b1))
return new PointF[] { a1 };
else // both "segments" are different points, return empty set
return new PointF[] { };
}
else if (b1.Equals(b2)) // b is a point, a is a segment
{
if (PointOnLine(b1, a1, a2))
return new PointF[] { b1 };
else
return new PointF[] { };
}
else if (a1.Equals(a2)) // a is a point, b is a segment
{
if (PointOnLine(a1, b1, b2))
return new PointF[] { a1 };
else
return new PointF[] { };
}
// at this point we know both a and b are actual segments
float ua_t = (b2.X - b1.X) * (a1.Y - b1.Y) - (b2.Y - b1.Y) * (a1.X - b1.X);
float ub_t = (a2.X - a1.X) * (a1.Y - b1.Y) - (a2.Y - a1.Y) * (a1.X - b1.X);
float u_b = (b2.Y - b1.Y) * (a2.X - a1.X) - (b2.X - b1.X) * (a2.Y - a1.Y);
// Infinite lines intersect somewhere
if (!(-MyEpsilon < u_b && u_b < MyEpsilon)) // e.g. u_b != 0.0
{
float ua = ua_t / u_b;
float ub = ub_t / u_b;
if (0.0f <= ua && ua <= 1.0f && 0.0f <= ub && ub <= 1.0f)
{
// Intersection
return new PointF[] {
new PointF(a1.X + ua * (a2.X - a1.X),
a1.Y + ua * (a2.Y - a1.Y)) };
}
else
{
// No Intersection
return new PointF[] { };
}
}
else // lines (not just segments) are parallel or the same line
{
// Coincident
// find the common overlapping section of the lines
// first find the distance (squared) from one point (a1) to each point
if ((-MyEpsilon < ua_t && ua_t < MyEpsilon)
|| (-MyEpsilon < ub_t && ub_t < MyEpsilon))
{
if (a1.Equals(a2)) // danger!
return OneD_Intersection(b1, b2, a1, a2);
else // safe
return OneD_Intersection(a1, a2, b1, b2);
}
else
{
// Parallel
return new PointF[] { };
}
}
}
}
Вот тестовый код:
public class IntersectTest
{
public static void PrintPoints(PointF[] pf)
{
if (pf == null || pf.Length < 1)
System.Console.WriteLine("Doesn't intersect");
else if (pf.Length == 1)
{
System.Console.WriteLine(pf[0]);
}
else if (pf.Length == 2)
{
System.Console.WriteLine(pf[0] + " -- " + pf[1]);
}
}
public static void TestIntersect(PointF a1, PointF a2, PointF b1, PointF b2)
{
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("Does " + a1 + " -- " + a2);
System.Console.WriteLine("intersect " + b1 + " -- " + b2 + " and if so, where?");
System.Console.WriteLine("");
PointF[] result = Intersect.Intersection(a1, a2, b1, b2);
PrintPoints(result);
}
public static void Main()
{
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("line segments intersect");
TestIntersect(new PointF(0, 0),
new PointF(100, 100),
new PointF(100, 0),
new PointF(0, 100));
TestIntersect(new PointF(5, 17),
new PointF(100, 100),
new PointF(100, 29),
new PointF(8, 100));
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("");
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("just touching points and lines cross");
TestIntersect(new PointF(0, 0),
new PointF(25, 25),
new PointF(25, 25),
new PointF(100, 75));
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("");
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("parallel");
TestIntersect(new PointF(0, 0),
new PointF(0, 100),
new PointF(100, 0),
new PointF(100, 100));
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("");
System.Console.WriteLine("----");
System.Console.WriteLine("lines cross but segments don't intersect");
TestIntersect(new PointF(50, 50),
new PointF(100, 100),
new PointF(0, 25),
new PointF(25, 0));
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("");
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("coincident but do not overlap!");
TestIntersect(new PointF(0, 0),
new PointF(25, 25),
new PointF(75, 75),
new PointF(100, 100));
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("");
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("touching points and coincident!");
TestIntersect(new PointF(0, 0),
new PointF(25, 25),
new PointF(25, 25),
new PointF(100, 100));
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("");
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("overlap/coincident");
TestIntersect(new PointF(0, 0),
new PointF(75, 75),
new PointF(25, 25),
new PointF(100, 100));
TestIntersect(new PointF(0, 0),
new PointF(100, 100),
new PointF(0, 0),
new PointF(100, 100));
System.Console.WriteLine("----------------------------------------------------------");
System.Console.WriteLine("");
while (!System.Console.KeyAvailable) { }
}
}
и вот результат:
----------------------------------------------------------
line segments intersect
----------------------------------------------------------
Does {X=0, Y=0} -- {X=100, Y=100}
intersect {X=100, Y=0} -- {X=0, Y=100} and if so, where?
{X=50, Y=50}
----------------------------------------------------------
Does {X=5, Y=17} -- {X=100, Y=100}
intersect {X=100, Y=29} -- {X=8, Y=100} and if so, where?
{X=56.85001, Y=62.30054}
----------------------------------------------------------
----------------------------------------------------------
just touching points and lines cross
----------------------------------------------------------
Does {X=0, Y=0} -- {X=25, Y=25}
intersect {X=25, Y=25} -- {X=100, Y=75} and if so, where?
{X=25, Y=25}
----------------------------------------------------------
----------------------------------------------------------
parallel
----------------------------------------------------------
Does {X=0, Y=0} -- {X=0, Y=100}
intersect {X=100, Y=0} -- {X=100, Y=100} and if so, where?
Doesn't intersect
----------------------------------------------------------
----
lines cross but segments don't intersect
----------------------------------------------------------
Does {X=50, Y=50} -- {X=100, Y=100}
intersect {X=0, Y=25} -- {X=25, Y=0} and if so, where?
Doesn't intersect
----------------------------------------------------------
----------------------------------------------------------
coincident but do not overlap!
----------------------------------------------------------
Does {X=0, Y=0} -- {X=25, Y=25}
intersect {X=75, Y=75} -- {X=100, Y=100} and if so, where?
Doesn't intersect
----------------------------------------------------------
----------------------------------------------------------
touching points and coincident!
----------------------------------------------------------
Does {X=0, Y=0} -- {X=25, Y=25}
intersect {X=25, Y=25} -- {X=100, Y=100} and if so, where?
{X=25, Y=25}
----------------------------------------------------------
----------------------------------------------------------
overlap/coincident
----------------------------------------------------------
Does {X=0, Y=0} -- {X=75, Y=75}
intersect {X=25, Y=25} -- {X=100, Y=100} and if so, where?
{X=25, Y=25} -- {X=75, Y=75}
----------------------------------------------------------
Does {X=0, Y=0} -- {X=100, Y=100}
intersect {X=0, Y=0} -- {X=100, Y=100} and if so, where?
{X=0, Y=0} -- {X=100, Y=100}
----------------------------------------------------------
Ответ 3
@Джаред, Отличный вопрос и отличный ответ.
Эту проблему можно упростить, представив положение точки вдоль линии как функцию одного параметра, как описано в разделе часто задаваемых вопросов Джозефа О'Рурка по CGA здесь.
Позвольте r быть параметром, чтобы указать местоположение P вдоль линии, содержащей AB, со следующим значением:
r=0 P = A
r=1 P = B
r<0 P is on the backward extension of AB
r>1 P is on the forward extension of AB
0<r<1 P is interior to AB
Размышляя в том же духе, для любой точки C (cx, cy) мы вычисляем r следующим образом:
double deltax = bx - ax;
double deltay = by - ay;
double l2 = deltax * deltax + deltay * deltay;
double r = ((ay - cy) * (ay - by) - (ax - cx) * (bx - ax)) / l2;
Это должно облегчить расчет перекрывающегося сегмента.
Обратите внимание, что мы избегаем брать квадратные корни, потому что требуется только квадрат длины.
Ответ 4
Это действительно очень просто. Если у вас есть две линии, вы можете найти два уравнения в виде y = mx + b. Например:
y = 2x + 5
y = x - 3
Итак, две линии пересекаются, когда y1 = y2 в одной и той же координате x, поэтому...
2x + 5 = x - 3
x + 5 = -3
x = -8
Когда x = -8 y1 = y2, и вы нашли точку пересечения. Это должно быть очень тривиально, чтобы перевести на код. Если точка пересечения отсутствует, то наклон m каждой линии будет равен, и в этом случае вам даже не нужно выполнять расчет.