#region License /* MoonWorks - Game Development Framework * Copyright 2021 Evan Hemsley */ /* Derived from code by Ethan Lee (Copyright 2009-2021). * Released under the Microsoft Public License. * See fna.LICENSE for details. * Derived from code by the Mono.Xna Team (Copyright 2006). * Released under the MIT License. See monoxna.LICENSE for details. */ #endregion #region Using Statements using System; using System.Diagnostics; #endregion namespace MoonWorks.Math { [Serializable] [DebuggerDisplay("{DebugDisplayString,nq}")] public struct Ray : IEquatable { #region Internal Properties internal string DebugDisplayString { get { return string.Concat( "Pos( ", Position.DebugDisplayString, " ) \r\n", "Dir( ", Direction.DebugDisplayString, " )" ); } } #endregion #region Public Fields public Vector3 Position; public Vector3 Direction; #endregion #region Public Constructors public Ray(Vector3 position, Vector3 direction) { Position = position; Direction = direction; } #endregion #region Public Methods public override bool Equals(object obj) { return (obj is Ray) && Equals((Ray) obj); } public bool Equals(Ray other) { return (this.Position.Equals(other.Position) && this.Direction.Equals(other.Direction)); } public override int GetHashCode() { return Position.GetHashCode() ^ Direction.GetHashCode(); } // Adapted from http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/ public float? Intersects(BoundingBox box) { float? tMin = null, tMax = null; if (MathHelper.WithinEpsilon(Direction.X, 0.0f)) { if (Position.X < box.Min.X || Position.X > box.Max.X) { return null; } } else { tMin = (box.Min.X - Position.X) / Direction.X; tMax = (box.Max.X - Position.X) / Direction.X; if (tMin > tMax) { float? temp = tMin; tMin = tMax; tMax = temp; } } if (MathHelper.WithinEpsilon(Direction.Y, 0.0f)) { if (Position.Y < box.Min.Y || Position.Y > box.Max.Y) { return null; } } else { float tMinY = (box.Min.Y - Position.Y) / Direction.Y; float tMaxY = (box.Max.Y - Position.Y) / Direction.Y; if (tMinY > tMaxY) { float temp = tMinY; tMinY = tMaxY; tMaxY = temp; } if ((tMin.HasValue && tMin > tMaxY) || (tMax.HasValue && tMinY > tMax)) { return null; } if (!tMin.HasValue || tMinY > tMin) tMin = tMinY; if (!tMax.HasValue || tMaxY < tMax) tMax = tMaxY; } if (MathHelper.WithinEpsilon(Direction.Z, 0.0f)) { if (Position.Z < box.Min.Z || Position.Z > box.Max.Z) { return null; } } else { float tMinZ = (box.Min.Z - Position.Z) / Direction.Z; float tMaxZ = (box.Max.Z - Position.Z) / Direction.Z; if (tMinZ > tMaxZ) { float temp = tMinZ; tMinZ = tMaxZ; tMaxZ = temp; } if ((tMin.HasValue && tMin > tMaxZ) || (tMax.HasValue && tMinZ > tMax)) { return null; } if (!tMin.HasValue || tMinZ > tMin) tMin = tMinZ; if (!tMax.HasValue || tMaxZ < tMax) tMax = tMaxZ; } /* Having a positive tMin and a negative tMax means the ray is inside the * box we expect the intesection distance to be 0 in that case. */ if ((tMin.HasValue && tMin < 0) && tMax > 0) return 0; /* A negative tMin means that the intersection point is behind the ray's * origin. We discard these as not hitting the AABB. */ if (tMin < 0) return null; return tMin; } public void Intersects(ref BoundingBox box, out float? result) { result = Intersects(box); } public float? Intersects(BoundingSphere sphere) { float? result; Intersects(ref sphere, out result); return result; } public float? Intersects(Plane plane) { float? result; Intersects(ref plane, out result); return result; } public float? Intersects(BoundingFrustum frustum) { float? result; frustum.Intersects(ref this, out result); return result; } public void Intersects(ref Plane plane, out float? result) { float den = Vector3.Dot(Direction, plane.Normal); if (System.Math.Abs(den) < 0.00001f) { result = null; return; } result = (-plane.D - Vector3.Dot(plane.Normal, Position)) / den; if (result < 0.0f) { if (result < -0.00001f) { result = null; return; } result = 0.0f; } } public void Intersects(ref BoundingSphere sphere, out float? result) { // Find the vector between where the ray starts the the sphere's center. Vector3 difference = sphere.Center - this.Position; float differenceLengthSquared = difference.LengthSquared(); float sphereRadiusSquared = sphere.Radius * sphere.Radius; float distanceAlongRay; /* If the distance between the ray start and the sphere's center is less than * the radius of the sphere, it means we've intersected. Checking the * LengthSquared is faster. */ if (differenceLengthSquared < sphereRadiusSquared) { result = 0.0f; return; } Vector3.Dot(ref this.Direction, ref difference, out distanceAlongRay); // If the ray is pointing away from the sphere then we don't ever intersect. if (distanceAlongRay < 0) { result = null; return; } /* Next we kinda use Pythagoras to check if we are within the bounds of the * sphere. * if x = radius of sphere * if y = distance between ray position and sphere centre * if z = the distance we've travelled along the ray * if x^2 + z^2 - y^2 < 0, we do not intersect */ float dist = ( sphereRadiusSquared + (distanceAlongRay * distanceAlongRay) - differenceLengthSquared ); result = (dist < 0) ? null : distanceAlongRay - (float?) System.Math.Sqrt(dist); } #endregion #region Public Static Methods public static bool operator !=(Ray a, Ray b) { return !a.Equals(b); } public static bool operator ==(Ray a, Ray b) { return a.Equals(b); } public override string ToString() { return ( "{{Position:" + Position.ToString() + " Direction:" + Direction.ToString() + "}}" ); } #endregion } }