using System; using Microsoft.Xna.Framework; namespace MonoGame.Extended { // Real-Time Collision Detection, Christer Ericson, 2005. Chapter 3.5; A Math and Geometry Primer - Lines, Rays, and Segments. pg 53-54 /// /// A two dimensional line segment defined by two structures, a starting point and an ending /// point. /// /// /// public struct Segment2 : IEquatable, IEquatableByRef { /// /// The starting of this . /// public Vector2 Start; /// /// The ending of this . /// public Vector2 End; /// /// Initializes a new instance of the structure from the specified starting and ending /// structures. /// /// The starting point. /// The ending point. public Segment2(Vector2 start, Vector2 end) { Start = start; End = end; } /// /// Initializes a new instance of the structure. /// /// The starting x-coordinate. /// The starting y-coordinate. /// The ending x-coordinate. /// The ending y-coordinate. public Segment2(float x1, float y1, float x2, float y2) : this(new Vector2(x1, y1), new Vector2(x2, y2)) { } // Real-Time Collision Detection, Christer Ericson, 2005. Chapter 5.1.2; Basic Primitive Tests - Closest Point on Line Segment to Point. pg 127-130 /// /// Computes the closest on this to a specified . /// /// The point. /// The closest on this to the . public Vector2 ClosestPointTo(Vector2 point) { // Computes the parameterized position: d(t) = Start + t * (End – Start) var startToEnd = End - Start; var startToPoint = point - Start; // Project arbitrary point onto the line segment, deferring the division var t = startToEnd.Dot(startToPoint); // If outside segment, clamp t (and therefore d) to the closest endpoint if (t <= 0) return Start; // Always nonnegative since denom = (||vector||)^2 var denominator = startToEnd.Dot(startToEnd); if (t >= denominator) return End; // The point projects inside the [Start, End] interval, must do deferred division now t /= denominator; startToEnd *= t; return new Vector2(Start.X + startToEnd.X, Start.Y + startToEnd.Y); } // Real-Time Collision Detection, Christer Ericson, 2005. Chapter 5.1.2.1; Basic Primitive Tests - Distance of Point to Segment. pg 127-130 /// /// Computes the squared distance from this to a specified . /// /// The point. /// The squared distance from this to a specified . public float SquaredDistanceTo(Vector2 point) { var startToEnd = End - Start; var startToPoint = point - Start; var endToPoint = point - End; // Handle cases where the point projects outside the line segment var dot = startToPoint.Dot(startToEnd); var startToPointDistanceSquared = startToPoint.Dot(startToPoint); if (dot <= 0.0f) return startToPointDistanceSquared; var startToEndDistanceSquared = startToEnd.Dot(startToEnd); if (dot >= startToEndDistanceSquared) endToPoint.Dot(endToPoint); // Handle the case where the point projects onto the line segment return startToPointDistanceSquared - dot*dot/startToEndDistanceSquared; } /// /// Computes the distance from this to a specified . /// /// The point. /// The distance from this to a specified . public float DistanceTo(Vector2 point) { return (float) Math.Sqrt(SquaredDistanceTo(point)); } /// /// Determines whether this intersects with the specified . /// /// The bounding box. /// /// When this method returns, contains the of intersection, if an /// intersection was found; otherwise, the . This parameter is passed uninitialized. /// /// /// true if this intersects with ; otherwise, /// false. /// public bool Intersects(RectangleF rectangle, out Vector2 intersectionPoint) { // Real-Time Collision Detection, Christer Ericson, 2005. Chapter 5.3; Basic Primitive Tests - Intersecting Lines, Rays, and (Directed Segments). pg 179-181 var minimumPoint = rectangle.TopLeft; var maximumPoint = rectangle.BottomRight; var minimumDistance = float.MinValue; var maximumDistance = float.MaxValue; var direction = End - Start; if ( !PrimitivesHelper.IntersectsSlab(Start.X, direction.X, minimumPoint.X, maximumPoint.X, ref minimumDistance, ref maximumDistance)) { intersectionPoint = new Vector2(float.NaN); return false; } if ( !PrimitivesHelper.IntersectsSlab(Start.Y, direction.Y, minimumPoint.Y, maximumPoint.Y, ref minimumDistance, ref maximumDistance)) { intersectionPoint = new Vector2(float.NaN); return false; } // Segment intersects the 2 slabs. if (minimumDistance <= 0) intersectionPoint = Start; else { intersectionPoint = minimumDistance * direction; intersectionPoint.X += Start.X; intersectionPoint.Y += Start.Y; } return true; } /// /// Determines whether this intersects with the specified . /// /// The bounding box. /// /// When this method returns, contains the of intersection, if an /// intersection was found; otherwise, the . This parameter is passed uninitialized. /// /// /// true if this intersects with ; otherwise, /// false. /// public bool Intersects(BoundingRectangle boundingRectangle, out Vector2 intersectionPoint) { // Real-Time Collision Detection, Christer Ericson, 2005. Chapter 5.3; Basic Primitive Tests - Intersecting Lines, Rays, and (Directed Segments). pg 179-181 var minimumPoint = boundingRectangle.Center - boundingRectangle.HalfExtents; var maximumPoint = boundingRectangle.Center + boundingRectangle.HalfExtents; var minimumDistance = float.MinValue; var maximumDistance = float.MaxValue; var direction = End - Start; if ( !PrimitivesHelper.IntersectsSlab(Start.X, direction.X, minimumPoint.X, maximumPoint.X, ref minimumDistance, ref maximumDistance)) { intersectionPoint = new Vector2(float.NaN); return false; } if ( !PrimitivesHelper.IntersectsSlab(Start.Y, direction.Y, minimumPoint.Y, maximumPoint.Y, ref minimumDistance, ref maximumDistance)) { intersectionPoint = new Vector2(float.NaN); return false; } // Segment intersects the 2 slabs. if (minimumDistance <= 0) intersectionPoint = Start; else { intersectionPoint = minimumDistance*direction; intersectionPoint.X += Start.X; intersectionPoint.Y += Start.Y; } return true; } /// /// Compares two structures. The result specifies /// whether the values of the and /// fields of the two /// structures are equal. /// /// The first segment. /// The second segment. /// /// true if the and /// fields of the two /// structures are equal; otherwise, false. /// public static bool operator ==(Segment2 first, Segment2 second) { return first.Equals(ref second); } /// /// Indicates whether this is equal to another . /// /// The segment. /// /// true if this is equal to the ; otherwise, false. /// public bool Equals(Segment2 segment) { return Equals(ref segment); } /// /// Indicates whether this is equal to another . /// /// The segment. /// /// true if this is equal to the parameter; otherwise, /// false. /// public bool Equals(ref Segment2 segment) { return (Start == segment.Start) && (End == segment.End); } /// /// Returns a value indicating whether this is equal to a specified object. /// /// The object to make the comparison with. /// /// true if this is equal to ; otherwise, false. /// public override bool Equals(object obj) { if (obj is Segment2) return Equals((Segment2) obj); return false; } /// /// Compares two structures. The result specifies /// whether the values of the and /// fields of the two /// structures are unequal. /// /// The first point. /// The second point. /// /// true if the and /// fields of the two /// structures are unequal; otherwise, false. /// public static bool operator !=(Segment2 first, Segment2 second) { return !(first == second); } /// /// Returns a hash code of this suitable for use in hashing algorithms and data /// structures like a hash table. /// /// /// A hash code of this . /// public override int GetHashCode() { unchecked { return (Start.GetHashCode()*397) ^ End.GetHashCode(); } } /// /// Returns a that represents this . /// /// /// A that represents this . /// public override string ToString() { return $"{Start} -> {End}"; } internal string DebugDisplayString => ToString(); } }