| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- using System;
- using System.Diagnostics;
- using System.Diagnostics.CodeAnalysis;
- using System.Runtime.Serialization;
- using Microsoft.Xna.Framework;
- namespace MonoGame.Extended;
- /// <summary>
- /// Represents a ray defined by an origin point and a direction vector.
- /// </summary>
- /// <remarks>
- /// A ray extends infinitely in one direction from its origin point. Unlike line segments, rays have no endpoint and
- /// continue indefinitely along their direction vector.
- /// </remarks>
- [DataContract]
- [DebuggerDisplay("(DebugDisplayString,nq)")]
- public struct Ray : IEquatable<Ray>
- {
- #region Fields
- private static readonly Ray s_empty = new Ray(Vector2.Zero, Vector2.Zero);
- /// <summary>
- /// The starting point of the ray.
- /// </summary>
- [DataMember]
- public Vector2 Origin;
- /// <summary>
- /// The direction vector that defines which way the ray extends from its origin.
- /// </summary>
- [DataMember]
- public Vector2 Direction;
- #endregion
- #region Properties
- /// <summary>
- /// Gets an empty ray representing a degenerate case with zero direction.
- /// </summary>
- public static Ray Empty => s_empty;
- /// <summary>
- /// Gets the direction vector normalized to unit length.
- /// </summary>
- public readonly Vector2 DirectionNormalized => Vector2.Normalize(Direction);
- /// <summary>
- /// Gets a value indicating whether this ray represents a degenerate case with no direction, having
- /// zero-length direction vector.
- /// </summary>
- public readonly bool IsEmpty => Direction.LengthSquared() <= 0.0f;
- #endregion
- #region Constructors
- /// <summary>
- /// Initializes a new ray with the specified origin and direction vector.
- /// </summary>
- /// <param name="origin">The starting point of the ray.</param>
- /// <param name="direction">The direction vector defining the ray's extension.</param>
- public Ray(Vector2 origin, Vector2 direction)
- {
- Origin = origin;
- Direction = direction;
- }
- #endregion
- #region Create Methods
- /// <summary>
- /// Creates a new ray with the specified origin and a normalized direction vector.
- /// </summary>
- /// <param name="origin">The starting point of the ray.</param>
- /// <param name="direction">The direction vector to normalize and use for the ray.</param>
- /// <returns>A new ray with the normalized direction vector.</returns>
- /// <remarks>
- /// This method ensures the direction vector has unit length for consistent
- /// distance calculations along the ray.
- /// </remarks>
- public static Ray CreateNormalized(Vector2 origin, Vector2 direction)
- {
- direction.Normalize();
- return new Ray(origin, direction);
- }
- #endregion
- #region Intersection Methods
- /// <summary>
- /// Determines whether this ray intersects with another ray.
- /// </summary>
- /// <param name="other">The other ray to test for intersection.</param>
- /// <returns>
- /// <see langword="true"/> if this ray intersects the other; otherwise, <see langword="false"/>.
- /// </returns>
- public readonly bool Intersects(Ray other) => IntersectionTests.RayRay(in this, in other);
- /// <summary>
- /// Determines whether this ray intersects with a circle.
- /// </summary>
- /// <param name="circle">The circle to test for intersection.</param>
- /// <returns>
- /// <see langword="true"/> if this ray intersects the circle; otherwise, <see langword="false"/>.
- /// </returns>
- public readonly bool Intersects(Circle circle) => IntersectionTests.CircleRay(in circle, in this);
- /// <summary>
- /// Determines whether this ray intersects with an ellipse.
- /// </summary>
- /// <param name="ellipse">The ellipse to test for intersection.</param>
- /// <returns>
- /// <see langword="true"/> if this ray intersects the ellipse; otherwise, <see langword="false"/>.
- /// </returns>
- public readonly bool Intersects(Ellipse ellipse) => IntersectionTests.EllipseRay(in ellipse, in this);
- /// <summary>
- /// Determines whether this ray intersects with a rectangle.
- /// </summary>
- /// <param name="rectangle">The rectangle to test for intersection.</param>
- /// <returns>
- /// <see langword="true"/> if this ray intersects the rectangle; otherwise, <see langword="false"/>.
- /// </returns>
- public readonly bool Intersects(RectangleF rectangle) => IntersectionTests.RectangleFRay(in rectangle, in this);
- /// <summary>
- /// Determines whether this ray intersects with a line segment.
- /// </summary>
- /// <param name="lineSegment">The line segment to test for intersection.</param>
- /// <returns>
- /// <see langword="true"/> if this ray intersects the line segment; otherwise, <see langword="false"/>.
- /// </returns>
- public readonly bool Intersects(LineSegment lineSegment) => IntersectionTests.RayLineSegment(in this, in lineSegment);
- #endregion
- #region Closest Point To Methods
- /// <summary>
- /// Computes the point on the ray at the specified distance from the origin.
- /// </summary>
- /// <param name="distance">The distance along the ray from the origin point.</param>
- /// <returns>The point on the ray at the specified distance.</returns>
- /// <remarks>
- /// Negative distance values will return points in the opposite direction of the ray, effectively extending
- /// the ray backwards from its origin.
- /// </remarks>
- public readonly Vector2 GetPointAt(float distance)
- {
- return Origin + Direction * distance;
- }
- /// <summary>
- /// Computes the closest point on the ray to the specified point.
- /// </summary>
- /// <param name="point">The query point for closest-point computation.</param>
- /// <returns>The point on the ray closest to the query point.</returns>
- /// <remarks>
- /// If the closest point would be behind the ray's origin (negative distance), the origin point is returned
- /// instead, as rays only extend forward.
- /// </remarks>
- public readonly Vector2 ClosestPointTo(Vector2 point)
- {
- float t = Vector2.Dot(point - Origin, Direction);
- // Clamp to ray (t >= 0)
- if (t < 0.0f)
- {
- return Origin;
- }
- return Origin + Direction * t;
- }
- #endregion
- #region Offset Methods
- /// <summary>
- /// Translates the ray by moving its origin position, preserving direction.
- /// </summary>
- /// <param name="offset">The translation vector to apply to the origin.</param>
- public void Offset(Vector2 offset)
- {
- Origin += offset;
- }
- /// <summary>
- /// Creates a new ray translated by the specified offset vector.
- /// </summary>
- /// <param name="ray">The ray to translate.</param>
- /// <param name="offset">The translation vector to apply.</param>
- /// <returns>A new ray with the translated origin position.</returns>
- public static Ray Offset(Ray ray, Vector2 offset)
- {
- return ray with { Origin = ray.Origin + offset };
- }
- #endregion
- #region Normalize Methods
- /// <summary>
- /// Normalizes the direction vector to unit length.
- /// </summary>
- public void Normalize()
- {
- Direction.Normalize();
- }
- /// <summary>
- /// Creates a new ray with the direction vector normalized to unit length.
- /// </summary>
- /// <param name="ray">The ray to normalize.</param>
- /// <returns>A new ray with normalized direction vector.</returns>
- public static Ray Normalize(Ray ray)
- {
- return ray with { Direction = Vector2.Normalize(ray.Direction) };
- }
- #endregion
- #region Transform Methods
- /// <summary>
- /// Applies a 2D transformation matrix to the ray, transforming both origin and direction.
- /// </summary>
- /// <param name="matrix">The transformation matrix to apply.</param>
- public void Transform(Matrix3x2 matrix)
- {
- Transform(ref matrix);
- }
- /// <summary>
- /// Applies a 2D transformation matrix to the ray using reference parameters.
- /// </summary>
- /// <param name="matrix">The transformation matrix to apply.</param>
- /// <remarks>
- /// The direction vector is transformed as a vector (ignoring translation components)
- /// to preserve its directional properties.
- /// </remarks>
- public void Transform(ref Matrix3x2 matrix)
- {
- Origin = matrix.Transform(Origin);
- // Transform direction (as a vector, not a point (ignore translation))
- float directionX = Direction.X * matrix.M11 + Direction.Y * matrix.M21;
- float directionY = Direction.X * matrix.M12 + Direction.Y * matrix.M22;
- Direction = new Vector2(directionX, directionY);
- }
- /// <summary>
- /// Creates a new ray by applying a 2D transformation matrix.
- /// </summary>
- /// <param name="ray">The ray to transform.</param>
- /// <param name="matrix">The transformation matrix to apply.</param>
- /// <returns>A new transformed ray.</returns>
- public static Ray Transform(Ray ray, Matrix3x2 matrix)
- {
- return Transform(ray, ref matrix);
- }
- /// <summary>
- /// Computes a transformed ray using reference parameters for performance.
- /// </summary>
- /// <param name="ray">The ray to transform.</param>
- /// <param name="matrix">The transformation matrix to apply.</param>
- public static Ray Transform(Ray ray, ref Matrix3x2 matrix)
- {
- Vector2 origin = matrix.Transform(ray.Origin);
- // Transform direction (as a vector, not a point (ignore translation))
- float directionX = ray.Direction.X * matrix.M11 + ray.Direction.Y * matrix.M21;
- float directionY = ray.Direction.X * matrix.M12 + ray.Direction.Y * matrix.M22;
- Vector2 direction = new Vector2(directionX, directionY);
- return new Ray(origin, direction);
- }
- #endregion
- #region Equality Methods
- /// <summary>
- /// Determines whether this ray is equal to the specified object.
- /// </summary>
- /// <param name="obj">The object to compare.</param>
- /// <returns>
- /// <c>true</c> if the object is a <see cref="Ray"/> with the same origin and direction; otherwise, <c>false</c>.
- /// </returns>
- public override readonly bool Equals([NotNullWhen(true)] object obj)
- {
- return obj is Ray other && Equals(other);
- }
- /// <summary>
- /// Determines whether this ray is equal to another ray.
- /// </summary>
- /// <param name="other">The ray to compare with this ray.</param>
- /// <returns><c>true</c> if the rays have the same origin and direction; otherwise, <c>false</c>.</returns>
- public readonly bool Equals(Ray other)
- {
- return Equals(other);
- }
- #endregion
- /// <summary>
- /// Computes the hash code for this ray.
- /// </summary>
- /// <returns>A hash code derived from the origin and direction.</returns>
- public override readonly int GetHashCode()
- {
- return HashCode.Combine(Origin, Direction);
- }
- /// <summary>
- /// Creates a string representation of this ray.
- /// </summary>
- /// <returns>A formatted string containing the origin and direction values.</returns>
- public override readonly string ToString()
- {
- return $"Ray(Origin: {Origin}, Direction: {Direction})";
- }
- internal readonly string DebugDisplayString => ToString();
- #region Operators
- /// <summary>
- /// Tests whether two rays are equal.
- /// </summary>
- /// <param name="left">The first ray.</param>
- /// <param name="right">The second ray.</param>
- /// <returns><c>true</c> if both rays have the same origin and direction; otherwise, <c>false</c>.</returns>
- public static bool operator ==(Ray left, Ray right)
- {
- return left.Equals(right);
- }
- /// <summary>
- /// Tests whether two rays are not equal.
- /// </summary>
- /// <param name="left">The first ray.</param>
- /// <param name="right">The second ray.</param>
- /// <returns><c>true</c> if the rays have different origins or directions; otherwise, <c>false</c>.</returns>
- public static bool operator !=(Ray left, Ray right)
- {
- return !left.Equals(right);
- }
- #endregion
- }
|