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();
}
}