/// <summary> /// Determines if line1 intersects line2, when line1 is offset by pos1 and line2 /// is offset by pos2. /// </summary> /// <param name="line1">Line 1</param> /// <param name="line2">Line 2</param> /// <param name="pos1">Origin of line 1</param> /// <param name="pos2">Origin of line 2</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If line1 intersects line2</returns> public static bool Intersects(Line2 line1, Line2 line2, Vector2 pos1, Vector2 pos2, bool strict) { if (line1.Horizontal && line2.Horizontal) { return(AxisAlignedLine2.Intersects(line1.MinX + pos1.X, line1.MaxX + pos1.X, line2.MinX + pos2.X, line2.MaxX + pos2.X, strict, false)); } else if (line1.Vertical && line2.Vertical) { return(AxisAlignedLine2.Intersects(line1.MinY + pos1.Y, line1.MaxY + pos1.Y, line2.MinY + pos2.Y, line2.MaxY + pos2.Y, strict, false)); } else if (line1.Horizontal || line2.Horizontal) { if (line2.Horizontal) { // swap line 1 and 2 to prevent duplicating everything var tmp = line1; var tmpp = pos1; line1 = line2; pos1 = pos2; line2 = tmp; pos2 = tmpp; } if (line2.Vertical) { return(AxisAlignedLine2.Contains(line1.MinX + pos1.X, line1.MaxX + pos1.X, line2.Start.X + pos2.X, strict, false) && AxisAlignedLine2.Contains(line2.MinY + pos2.Y, line2.MaxY + pos2.Y, line1.Start.Y + pos1.Y, strict, false)); } else { // recalculate line2 y intercept // y = mx + b // b = y - mx var line2YIntInner = line2.Start.Y + pos2.Y - line2.Slope * (line2.Start.X + pos2.X); // check line2.x at line1.y // line2.y = line2.slope * line2.x + line2.yintercept // line1.y = line2.slope * line2.x + line2.yintercept // line1.y - line2.yintercept = line2.slope * line2.x // (line1.y - line2.yintercept) / line2.slope = line2.x var line2XAtLine1Y = (line1.Start.Y + pos1.Y - line2YIntInner) / line2.Slope; return(AxisAlignedLine2.Contains(line1.MinX + pos1.X, line1.MaxX + pos1.X, line2XAtLine1Y, strict, false) && AxisAlignedLine2.Contains(line2.MinX + pos2.X, line2.MaxX + pos2.X, line2XAtLine1Y, strict, false)); } } else if (line1.Vertical) { // vertical line with regular line var line2YIntInner = line2.Start.Y + pos2.Y - line2.Slope * (line2.Start.X + pos2.X); var line2YAtLine1X = line2.Slope * (line1.Start.X + pos1.X) + line2YIntInner; return(AxisAlignedLine2.Contains(line1.MinY + pos1.Y, line1.MaxY + pos1.Y, line2YAtLine1X, strict, false) && AxisAlignedLine2.Contains(line2.MinY + pos2.Y, line2.MaxY + pos2.Y, line2YAtLine1X, strict, false)); } // two non-vertical, non-horizontal lines var line1YInt = line1.Start.Y + pos1.Y - line1.Slope * (line1.Start.X + pos1.X); var line2YInt = line2.Start.Y + pos2.Y - line2.Slope * (line2.Start.X + pos2.X); if (Math.Abs(line1.Slope - line2.Slope) <= Math2.DEFAULT_EPSILON) { // parallel lines if (line1YInt != line2YInt) { return(false); // infinite lines don't intersect } // parallel lines with equal y intercept. Intersect if ever at same X coordinate. return(AxisAlignedLine2.Intersects(line1.MinX + pos1.X, line1.MaxX + pos1.X, line2.MinX + pos2.X, line2.MaxX + pos2.X, strict, false)); } else { // two non-parallel lines. Only one possible intersection point // y1 = y2 // line1.Slope * x + line1.YIntercept = line2.Slope * x + line2.YIntercept // line1.Slope * x - line2.Slope * x = line2.YIntercept - line1.YIntercept // x (line1.Slope - line2.Slope) = line2.YIntercept - line1.YIntercept // x = (line2.YIntercept - line1.YIntercept) / (line1.Slope - line2.Slope) var x = (line2YInt - line1YInt) / (line1.Slope - line2.Slope); return(AxisAlignedLine2.Contains(line1.MinX + pos1.X, line1.MaxX + pos1.X, x, strict, false) && AxisAlignedLine2.Contains(line2.MinX + pos1.X, line2.MaxX + pos2.X, x, strict, false)); } }
/// <summary> /// Finds the line of overlap between the the two lines if there is /// one. If the two lines are not coincident (i.e., if the infinite /// lines are not the same) then they don't share a line of points. /// If they are coincident, they may still share no points (two /// seperate but coincident line segments), one point (they share /// an edge), or infinitely many points (the share a coincident /// line segment). In all but the last case, this returns false /// and overlap is set to null. In the last case this returns true /// and overlap is set to the line of overlap. /// </summary> /// <param name="a">The first line</param> /// <param name="b">The second line</param> /// <param name="pos1">The position of the first line</param> /// <param name="pos2">the position of the second line</param> /// <param name="overlap">Set to null or the line of overlap</param> /// <returns>True if a and b overlap at infinitely many points, /// false otherwise</returns> public static unsafe bool LineOverlap(Line2 a, Line2 b, Vector2 pos1, Vector2 pos2, out Line2 overlap) { if (!Parallel(a, b)) { overlap = null; return(false); } if (!AlongInfiniteLine(a, pos1, b.Start + pos2)) { overlap = null; return(false); } Vector2 relOrigin = a.Start + pos1; float *projs = stackalloc float[4] { 0, a.Magnitude, Math2.Dot((b.Start + pos2) - relOrigin, a.Axis), Math2.Dot((b.End + pos2) - relOrigin, a.Axis) }; bool *isFromLine1 = stackalloc bool[4] { true, true, false, false }; FindSortedOverlap(projs, isFromLine1); if (isFromLine1[0] == isFromLine1[1]) { // at best we overlap at one point, most likely no overlap overlap = null; return(false); } if (Math2.Approximately(projs[1], projs[2])) { // Overlap at one point overlap = null; return(false); } overlap = new Line2( relOrigin + projs[1] * a.Axis, relOrigin + projs[2] * a.Axis ); return(true); }
/// <summary> /// Determines if the given point is along the infinite line described /// by the given line shifted the given amount. /// </summary> /// <param name="line">The line</param> /// <param name="pos">The shift for the line</param> /// <param name="pt">The point</param> /// <returns>True if pt is on the infinite line extension of the segment</returns> public static bool AlongInfiniteLine(Line2 line, Vector2 pos, Vector2 pt) { float normalPart = Vector2.Dot(pt - pos - line.Start, line.Normal); return(Math2.Approximately(normalPart, 0)); }
/// <summary> /// Initializes a polygon with the specified vertices /// </summary> /// <param name="vertices">Vertices</param> /// <exception cref="ArgumentNullException">If vertices is null</exception> public Polygon2(Vector2[] vertices) { if (vertices == null) { throw new ArgumentNullException(nameof(vertices)); } Vertices = vertices; Normals = new List <Vector2>(); Vector2 tmp; for (int i = 1; i < vertices.Length; i++) { tmp = Math2.MakeStandardNormal(Vector2.Normalize(Math2.Perpendicular(vertices[i] - vertices[i - 1]))); if (!Normals.Contains(tmp)) { Normals.Add(tmp); } } tmp = Math2.MakeStandardNormal(Vector2.Normalize(Math2.Perpendicular(vertices[0] - vertices[vertices.Length - 1]))); if (!Normals.Contains(tmp)) { Normals.Add(tmp); } var min = new Vector2(vertices[0].X, vertices[0].Y); var max = new Vector2(min.X, min.Y); for (int i = 1; i < vertices.Length; i++) { min.X = Math.Min(min.X, vertices[i].X); min.Y = Math.Min(min.Y, vertices[i].Y); max.X = Math.Max(max.X, vertices[i].X); max.Y = Math.Max(max.Y, vertices[i].Y); } AABB = new Rect2(min, max); _LongestAxisLength = -1; // Center, area, and lines TrianglePartition = new Triangle2[Vertices.Length - 2]; float[] triangleSortKeys = new float[TrianglePartition.Length]; float area = 0; Lines = new Line2[Vertices.Length]; Lines[0] = new Line2(Vertices[Vertices.Length - 1], Vertices[0]); var last = Vertices[0]; Center = new Vector2(0, 0); for (int i = 1; i < Vertices.Length - 1; i++) { var next = Vertices[i]; var next2 = Vertices[i + 1]; Lines[i] = new Line2(last, next); var tri = new Triangle2(new Vector2[] { Vertices[0], next, next2 }); TrianglePartition[i - 1] = tri; triangleSortKeys[i - 1] = -tri.Area; area += tri.Area; Center += tri.Center * tri.Area; last = next; } Lines[Vertices.Length - 1] = new Line2(Vertices[Vertices.Length - 2], Vertices[Vertices.Length - 1]); Array.Sort(triangleSortKeys, TrianglePartition); Area = area; Center /= area; last = Vertices[Vertices.Length - 1]; var centToLast = (last - Center); var angLast = Rotation2.Standardize((float)Math.Atan2(centToLast.Y, centToLast.X)); var cwCounter = 0; var ccwCounter = 0; var foundDefinitiveResult = false; for (int i = 0; i < Vertices.Length; i++) { var curr = Vertices[i]; var centToCurr = (curr - Center); var angCurr = Rotation2.Standardize((float)Math.Atan2(centToCurr.Y, centToCurr.X)); var clockwise = (angCurr < angLast && (angCurr - angLast) < Math.PI) || (angCurr - angLast) > Math.PI; if (clockwise) { cwCounter++; } else { ccwCounter++; } Clockwise = clockwise; if (Math.Abs(angLast - angCurr) > Math2.DEFAULT_EPSILON) { foundDefinitiveResult = true; break; } last = curr; centToLast = centToCurr; angLast = angCurr; } if (!foundDefinitiveResult) { Clockwise = cwCounter > ccwCounter; } }