// Sort of bad to introduce a dependency on the Path class, but this works. // The option to have this logic in QPExtractor is worse imo (does not promote code reuse and is hard to test, // because QPExtractor would not expose the method). public static Orientation GetOrientationOfCycle <TVertex>(this IReadOnlyUndirectedGraph <TVertex> graph, IEnumerable <TVertex> closedPath) where TVertex : IEquatable <TVertex>, IVertexInPlane { if (graph is null) { throw new ArgumentNullException(nameof(graph)); } if (closedPath is null) { throw new ArgumentNullException(nameof(closedPath)); } if (!closedPath.First().Equals(closedPath.Last())) { throw new ArgumentException($"The path is not closed.", nameof(closedPath)); } if (closedPath.Count() == 1) { throw new ArgumentException($"The path is stationary."); } var pathAsCircularListOfVertices = new CircularList <TVertex>(closedPath.SkipLast(1)); // Sum the external angle at every vertex double externalAngleSum = 0; for (int i = 0; i < pathAsCircularListOfVertices.Count; i++) { var vertex1 = pathAsCircularListOfVertices[i - 1]; var vertex2 = pathAsCircularListOfVertices[i]; var vertex3 = pathAsCircularListOfVertices[i + 1]; var pos1 = vertex1.Position; var pos2 = vertex2.Position; var pos3 = vertex3.Position; externalAngleSum += PlaneUtility.GetExternalAngle(pos1, pos2, pos3); } const double Tolerance = 0.01; if (Math.Abs(externalAngleSum - 2 * Math.PI) < Tolerance) { return(Orientation.Counterclockwise); } else if (Math.Abs(externalAngleSum + 2 * Math.PI) < Tolerance) { return(Orientation.Clockwise); } else { throw new OrientationException($"Failed to determine the orientation of {closedPath}; external angle sum was {externalAngleSum}."); } }
/// <summary> /// Determines whether two line segments intersect. /// </summary> /// <param name="lineSegment1">The first line segment.</param> /// <param name="lineSegment2">The second line segment.</param> /// <returns></returns> /// <remarks> /// <para>See <see href="https://www.cdn.geeksforgeeks.org/check-if-two-given-line-segments-intersect/"/> /// for the inner workings of this method (details which I have not worked out myself).</para></remarks> public static bool Intersect(OrientedLineSegment lineSegment1, OrientedLineSegment lineSegment2) { if (lineSegment1 is null) { throw new ArgumentNullException(nameof(lineSegment1)); } if (lineSegment2 is null) { throw new ArgumentNullException(nameof(lineSegment2)); } if (lineSegment1.Start == lineSegment1.End) { throw new NotImplementedException(); // Haven't checked that the method works in this case } if (lineSegment2.Start == lineSegment2.End) { throw new NotImplementedException(); // Haven't checked that the method works in this case } var ls1 = lineSegment1; var ls2 = lineSegment2; var o1 = PlaneUtility.GetOrientation(ls1.Start, ls1.End, ls2.Start); var o2 = PlaneUtility.GetOrientation(ls1.Start, ls1.End, ls2.End); var o3 = PlaneUtility.GetOrientation(ls2.Start, ls2.End, ls1.Start); var o4 = PlaneUtility.GetOrientation(ls2.Start, ls2.End, ls1.End); if (o1 != o2 && o3 != o4) { return(true); } if (o1 == TripletOrientation.Collinear && LineSegmentContainsPoint(ls1, ls2.Start)) { return(true); } if (o2 == TripletOrientation.Collinear && LineSegmentContainsPoint(ls1, ls2.End)) { return(true); } if (o3 == TripletOrientation.Collinear && LineSegmentContainsPoint(ls2, ls1.Start)) { return(true); } if (o4 == TripletOrientation.Collinear && LineSegmentContainsPoint(ls2, ls1.End)) { return(true); } return(false); // Idea (obsolete in favor of the above I guess): // Look at the common range of, say, the x-coordinates // If empty, then return false // If equal at either endpoint, return true (because they intersect in the endpoint) // If the first line segment is below/above the other in one endpoint and above/below the other in the other, return true // Else return false // The above doesn't work when one of the lines is vertical though // Assumes that p is on the line determined by ls (this assumes that ls is non-degenerate) bool LineSegmentContainsPoint(OrientedLineSegment ls, Point p) { return(Math.Min(ls.Start.X, ls.End.X) <= p.X && p.X <= Math.Max(ls.Start.X, ls.End.X) && Math.Min(ls.Start.Y, ls.End.Y) <= p.Y && p.Y <= Math.Max(ls.Start.Y, ls.End.Y)); } }
/// <summary> /// Determines whether two line segments intersect properly, in the sense that the /// interiors of the line segments intersect. /// </summary> /// <param name="lineSegment1">The first line segment.</param> /// <param name="lineSegment2">The second line segment.</param> /// <returns></returns> public static bool IntersectProperly(OrientedLineSegment lineSegment1, OrientedLineSegment lineSegment2) { if (lineSegment1 is null) { throw new ArgumentNullException(nameof(lineSegment1)); } if (lineSegment2 is null) { throw new ArgumentNullException(nameof(lineSegment2)); } if (lineSegment1.Start == lineSegment1.End) { return(false); // The interior of a degenerate line segment is empty } if (lineSegment2.Start == lineSegment2.End) { return(false); // The interior of a degenerate line segment is empty } var ls1 = lineSegment1; var ls2 = lineSegment2; var o1 = PlaneUtility.GetOrientation(ls1.Start, ls1.End, ls2.Start); var o2 = PlaneUtility.GetOrientation(ls1.Start, ls1.End, ls2.End); var o3 = PlaneUtility.GetOrientation(ls2.Start, ls2.End, ls1.Start); var o4 = PlaneUtility.GetOrientation(ls2.Start, ls2.End, ls1.End); // The line segments are collinear if (o1 == TripletOrientation.Collinear && o2 == TripletOrientation.Collinear) { return(ls1.IsEqualToAsUnorientedLineSegments(ls2) || LineSegmentInteriorContainsPoint(ls1, ls2.Start) || LineSegmentInteriorContainsPoint(ls1, ls2.End) || LineSegmentInteriorContainsPoint(ls2, ls1.Start) || LineSegmentInteriorContainsPoint(ls2, ls1.End)); } // The line segments are not collinear, so if any three points are collinear, the line segments just "touch" // each other, which does not constitute a proper intersection if (o1 == TripletOrientation.Collinear || o2 == TripletOrientation.Collinear || o3 == TripletOrientation.Collinear || o4 == TripletOrientation.Collinear) { return(false); } // Then just do "the usual" check return(o1 != o2 && o3 != o4); // Assumes that p is on the line determined by ls (this assumes that ls is non-degenerate) bool LineSegmentContainsPoint(OrientedLineSegment ls, Point p) { return(Math.Min(ls.Start.X, ls.End.X) <= p.X && p.X <= Math.Max(ls.Start.X, ls.End.X) && Math.Min(ls.Start.Y, ls.End.Y) <= p.Y && p.Y <= Math.Max(ls.Start.Y, ls.End.Y)); } // Assumes that p is on the line determined by ls (this assumes that ls is non-degenerate) bool LineSegmentInteriorContainsPoint(OrientedLineSegment ls, Point p) { return(LineSegmentContainsPoint(ls, p) && p != ls.Start && p != ls.End); } }