Skip to content

Commit 9a40b9f

Browse files
f-frhsjkalias
andauthored
Circle2D intersection with Line(Segment)2D (#237)
* Feat: Line2D.DistanceTo(point) * feat: Circle2D.IntersectWith(Line2D) * doc: fix xml comment * refactor: extract the private method findParameterTs(Line2D) Aleady existing test cases must be changed because of the implementation of FindRoots.Polynomial(coeffs), that is, the order of the solutions. * feat: Circle2D.IntersectWith(LineSegment2D) * doc: fix the variable-name list in comment * fix: a typo * change: use the fact that dot-product of the unit vector is always 1.0 * Revert "Feat: Line2D.DistanceTo(point)" This reverts commit 0a2521d. * remove: src/Spatial/Extensions/DoubleExtensions.cs * doc: revise xml comments * fix a typo * test: fix testcases to test number of intersections * fix: IntersectionWith() to return a single point when a line tangent to the circle * test: Add tests for intersection of Circle and LineSegment2D for the case where LineSegment contains some of obtained intersections. * test: add TODO comment * refactor: rename line to segment * fix: type declaration (from 1d to 1.0) * test: fix testcases to use expected points * test: fix testcases to use expected points * test: add testcases where the line is parallel to the Y-axis * test: add testcases parallel to Y-axis and general cases * test: add testcases for Intersection of Circle2D with LIneSegment2D - parallel to X-axis - parallel to Y-axis - general cases (eg. x-y+c=0) test: rename method's name * test: replace +01 to +1 * Minor changes in test --------- Co-authored-by: jkaliak <[email protected]>
1 parent 8cae855 commit 9a40b9f

File tree

3 files changed

+189
-1
lines changed

3 files changed

+189
-1
lines changed

src/Spatial.Tests/Euclidean/Circle2DTests.cs

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
using System;
2+
using System.Linq;
23
using MathNet.Spatial.Euclidean;
34
using NUnit.Framework;
45

@@ -58,5 +59,125 @@ public void CircleFromThreePointsArgumentException()
5859

5960
Assert.Throws<ArgumentException>(() => { Circle2D.FromPoints(p1, p2, p3); });
6061
}
62+
63+
//parallel to the X-axis
64+
[TestCase("0,0", 1, "-10,-10", "+10,-10", null)]
65+
[TestCase("0,0", 1, "-10,-1", "+10,-1", "0,-1")]
66+
[TestCase("0,0", 1, "-10,0", "+10,0", "+1,0;-1,0")]
67+
[TestCase("0,0", 1, "-10,+1", "+10,+1", "0,+1")]
68+
[TestCase("0,0", 1, "-10,+10", "+10,+10", null)]
69+
//parallel to the Y-axis
70+
[TestCase("0,0", 1, "-10,-10", "-10,+10", null)]
71+
[TestCase("0,0", 1, "-1,-10", "-1,+10", "-1,0")]
72+
[TestCase("0,0", 1, "0,-10", "0,+10", "0,+1;0,-1")]
73+
[TestCase("0,0", 1, "+1,-10", "+1,+10", "+1,0")]
74+
[TestCase("0,0", 1, "+10,-10", "+10,+10", null)]
75+
//general cases
76+
[TestCase("0,0", 1, "-10,+10", "+10,+10", null)]
77+
[TestCase("0,0", 1, "-1.414213562373095,0", "0,+1.414213562373095", "-0.707,0.707")]
78+
[TestCase("0,0", 1, "-10,-10", "+10,+10", "+0.707,+0.707;-0.707,-0.707")]
79+
[TestCase("0,0", 1, "0,-1.41421356", "+1.41421356,0", "+0.707,-0.707")]
80+
[TestCase("0,0", 1, "0,-10", "+10,0", null)]
81+
public void CircleIntersectWithLine2D(string sc, double radius, string sps, string spe, string intersections)
82+
{
83+
var circle = new Circle2D(Point2D.Parse(sc), radius);
84+
var line = new Line2D(Point2D.Parse(sps), Point2D.Parse(spe));
85+
86+
var actual = circle.IntersectWith(line);
87+
88+
var expected = parseToPointsArray(intersections);
89+
for (int i = 0; i < Math.Min(actual.Length, expected.Length); i++)
90+
{
91+
var a = actual[i];
92+
var e = expected[i];
93+
AssertGeometry.AreEqual(a, e, 1e-3); //needs to fix for the default tolerance
94+
}
95+
}
96+
//parallel to X-axis
97+
////segment contains the all intersections(same to the cases of circle and line)
98+
[TestCase("0,0", 1, "-10,+10", "+10,+10", null)]
99+
[TestCase("0,0", 1, "-10,+1", "+10,+1", "0,+1")]
100+
[TestCase("0,0", 1, "-10,0", "+10,0", "+1,0;-1,0")]
101+
[TestCase("0,0", 1, "-10,-1", "+10,-1", "0,-1")]
102+
[TestCase("0,0", 1, "-10,-10", "+10,-10", null)]
103+
////segments cross the circle's contour just 1 time
104+
[TestCase("0,0", 1, "+0,+10", "+10,+10", null)]
105+
[TestCase("0,0", 1, "+0,+1", "+10,+1", "0,1")]
106+
[TestCase("0,0", 1, "+0,+0", "+10,+0", "1,0")]
107+
[TestCase("0,0", 1, "+0,-1", "+10,-1", "0,-1")]
108+
[TestCase("0,0", 1, "+0,-10", "+10,-10", null)]
109+
////segment contains no intersections(px of the startingPoint is too big to intersect with the circle)
110+
[TestCase("0,0", 1, "+10,+10", "+100,+10", null)]
111+
[TestCase("0,0", 1, "+10,+1", "+100,+1", null)]
112+
[TestCase("0,0", 1, "+10,+0", "+100,0", null)]
113+
[TestCase("0,0", 1, "+10,-1", "+100,-1", null)]
114+
[TestCase("0,0", 1, "+10,-10", "+100,-10", null)]
115+
//parallel to Y-axis
116+
////segment contains the all intersections(same to the cases of circle and line)
117+
[TestCase("0,0", 1, "-10,-10", "-10,+10", null)]
118+
[TestCase("0,0", 1, "-1,-10", "-1,+10", "-1,0")]
119+
[TestCase("0,0", 1, "+0,-10", "+0,+10", "0,+1;0,-1")]
120+
[TestCase("0,0", 1, "+1,-10", "+1,+10", "+1,0")]
121+
[TestCase("0,0", 1, "+10,-10", "+10,+10", null)]
122+
////segments cross the circle's contour just 1 time
123+
[TestCase("0,0", 1, "+10,0", "+10,+10", null)]
124+
[TestCase("0,0", 1, "+1,0", "+1,+10", "+1,0")]
125+
[TestCase("0,0", 1, "+0,0", "+0,+10", "0,+1")]
126+
[TestCase("0,0", 1, "-1,0", "-1,+10", "-1,0")]
127+
[TestCase("0,0", 1, "-10,0", "-10,+10", null)]
128+
////segment contains no intersections(py of the startingPoint is too big to intersect with the circle)
129+
[TestCase("0,0", 1, "+10,+10", "+10,+100", null)]
130+
[TestCase("0,0", 1, "+1,+10", "+1,+100", null)]
131+
[TestCase("0,0", 1, "+0,+10", "+0,+100", null)]
132+
[TestCase("0,0", 1, "-1,+10", "-1,+100", null)]
133+
[TestCase("0,0", 1, "-10,+10", "-10,+100", null)]
134+
//general cases
135+
////segment contains the all intersections(same to the cases of circle and line)
136+
[TestCase("0,0", 1, "-10,+10", "+10,+10", null)]
137+
[TestCase("0,0", 1, "-1.414213562373095,0", "0,+1.414213562373095", "-0.707,0.707")]
138+
[TestCase("0,0", 1, "-10,-10", "+10,+10", "+0.707,+0.707;-0.707,-0.707")]
139+
[TestCase("0,0", 1, "0,-1.41421356", "+1.41421356,0", "+0.707,-0.707")]
140+
[TestCase("0,0", 1, "0,-10", "+10,0", null)]
141+
////segments cross the circle's contour just 1 time
142+
[TestCase("0,0", 1, "+10,0", "+10,+10", null)]
143+
[TestCase("0,0", 1, "+1,0", "+1,+10", "+1,0")]
144+
[TestCase("0,0", 1, "+0,0", "+0,+10", "0,+1")]
145+
[TestCase("0,0", 1, "-1,0", "-1,+10", "-1,0")]
146+
[TestCase("0,0", 1, "-10,0", "-10,+10", null)]
147+
////segment contains no intersections(py of the startingPoint is too big to intersect with the circle)
148+
[TestCase("0,0", 1, "+10,+10", "+10,+100", null)]
149+
[TestCase("0,0", 1, "+1,+10", "+1,+100", null)]
150+
[TestCase("0,0", 1, "+0,+10", "+0,+100", null)]
151+
[TestCase("0,0", 1, "-1,+10", "-1,+100", null)]
152+
[TestCase("0,0", 1, "-10,+10", "-10,+100", null)]
153+
public void CircleIntersectWithLineSegment2D(string sCenter, double radius, string sStart, string sEnd, string intersections)
154+
{
155+
var circle = new Circle2D(Point2D.Parse(sCenter), radius);
156+
var segment = new LineSegment2D(Point2D.Parse(sStart), Point2D.Parse(sEnd));
157+
158+
var actual = circle.IntersectWith(segment);
159+
160+
var expected = parseToPointsArray(intersections);
161+
for (int i = 0; i < Math.Min(actual.Length, expected.Length); i++)
162+
{
163+
var a = actual[i];
164+
var e = expected[i];
165+
AssertGeometry.AreEqual(a, e, 1e-3); //FIXME!
166+
}
167+
}
168+
169+
private Point2D[] parseToPointsArray(string input)
170+
{
171+
if (input == null)
172+
{
173+
return new Point2D[] { };
174+
}
175+
176+
var result = input.Split(';')
177+
.Select(s => Point2D.Parse(s))
178+
.ToArray();
179+
180+
return result;
181+
}
61182
}
62183
}

src/Spatial/Euclidean/Circle2D.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using MathNet.Spatial.Internals;
22
using System;
33
using System.Diagnostics.Contracts;
4+
using System.Linq;
45
using System.Xml;
56
using System.Xml.Schema;
67
using System.Xml.Serialization;
8+
using MathNet.Numerics;
79
using HashCode = MathNet.Spatial.Internals.HashCode;
810

911
namespace MathNet.Spatial.Euclidean
@@ -131,6 +133,67 @@ public static Circle2D FromPoints(Point2D pointA, Point2D pointB, Point2D pointC
131133
return new Circle2D(center, radius);
132134
}
133135

136+
/// <summary>
137+
/// Returns the intersections of this circle with the given line.
138+
/// </summary>
139+
/// <param name="line">the given line</param>
140+
/// <returns>intersections as a Point2D Array, depending on the count.</returns>
141+
public Point2D[] IntersectWith(Line2D line)
142+
{
143+
var ts = this.findParameterTs(line);
144+
var result = ts
145+
.Select(t => line.StartPoint + t * line.Direction)
146+
.ToArray();
147+
return result;
148+
}
149+
150+
private double[] findParameterTs(Line2D line)
151+
{
152+
// These 2 equations in vector form can be described
153+
// (p-cc)^2=r^2 (eq1)
154+
// p=s+t*d (eq2)
155+
// , where p is the point on the line and/or circle,
156+
// cc is the center of the circle,
157+
// r is the radius of the circle,
158+
// s is the starting point of the line,
159+
// t is the parameter and
160+
// d is the line direction.
161+
// Substituting (eq2) into (eq1) yields:
162+
// ((s+t*d)-cc)^2=r^2 (eq3)
163+
// (eq3) reduces to the following quadratic equation: a*t^2 + b*t + c==0
164+
165+
var cc = this.Center.ToVector2D(); //center of circle
166+
var s = line.StartPoint.ToVector2D();
167+
var d = line.Direction;
168+
var r = this.Radius;
169+
170+
var a = 1.0;
171+
var b = 2 * (s.DotProduct(d) - d.DotProduct(cc));
172+
var c = (s - cc).DotProduct(s - cc) - r * r;
173+
174+
var solutions = FindRoots.Polynomial(new[] { c, b, a });
175+
var ts = solutions
176+
.Where(z => z.IsReal())
177+
.Select(z => z.Real)
178+
.Distinct()
179+
.ToArray();
180+
return ts;
181+
}
182+
183+
/// <summary>
184+
/// Returns the intersections of this circle with the given line segment, which lie within the segment.
185+
/// </summary>
186+
/// <param name="segment">the given line-segment</param>
187+
/// <returns>intersections as a Point2D Array, depending on the count.</returns>
188+
public Point2D[] IntersectWith(LineSegment2D segment)
189+
{
190+
var ts = findParameterTs(segment.ToLine2D())
191+
.Where(t => 0 <= t && t <= segment.Length);
192+
var result = ts.Select(t => segment.StartPoint + t * segment.Direction).ToArray();
193+
return result;
194+
}
195+
196+
134197
/// <summary>
135198
/// Returns a value to indicate if a pair of circles are equal
136199
/// </summary>

src/Spatial/Euclidean/LineSegment2D.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,5 +236,9 @@ void IXmlSerializable.WriteXml(XmlWriter writer)
236236
writer.WriteElement("StartPoint", StartPoint);
237237
writer.WriteElement("EndPoint", EndPoint);
238238
}
239+
240+
/// <summary>convert this to Line2D </summary>
241+
/// <returns>converted Line2D object</returns>
242+
public Line2D ToLine2D() => new Line2D(StartPoint, EndPoint);
239243
}
240244
}

0 commit comments

Comments
 (0)