#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; using System.Text; #endregion namespace MoonWorks.Math { /// /// Defines a viewing frustum for intersection operations. /// [DebuggerDisplay("{DebugDisplayString,nq}")] public class BoundingFrustum : IEquatable { #region Public Properties /// /// Gets or sets the of the frustum. /// public Matrix4x4 Matrix { get { return this.matrix; } set { /* FIXME: The odds are the planes will be used a lot more often than * the matrix is updated, so this should help performance. I hope. ;) */ this.matrix = value; this.CreatePlanes(); this.CreateCorners(); } } /// /// Gets the near plane of the frustum. /// public Plane Near { get { return this.planes[0]; } } /// /// Gets the far plane of the frustum. /// public Plane Far { get { return this.planes[1]; } } /// /// Gets the left plane of the frustum. /// public Plane Left { get { return this.planes[2]; } } /// /// Gets the right plane of the frustum. /// public Plane Right { get { return this.planes[3]; } } /// /// Gets the top plane of the frustum. /// public Plane Top { get { return this.planes[4]; } } /// /// Gets the bottom plane of the frustum. /// public Plane Bottom { get { return this.planes[5]; } } #endregion #region Internal Properties internal string DebugDisplayString { get { return string.Concat( "Near( ", planes[0].DebugDisplayString, " ) \r\n", "Far( ", planes[1].DebugDisplayString, " ) \r\n", "Left( ", planes[2].DebugDisplayString, " ) \r\n", "Right( ", planes[3].DebugDisplayString, " ) \r\n", "Top( ", planes[4].DebugDisplayString, " ) \r\n", "Bottom( ", planes[5].DebugDisplayString, " ) " ); } } #endregion #region Public Fields /// /// The number of corner points in the frustum. /// public const int CornerCount = 8; #endregion #region Private Fields private Matrix4x4 matrix; private readonly Vector3[] corners = new Vector3[CornerCount]; private readonly Plane[] planes = new Plane[PlaneCount]; /// /// The number of planes in the frustum. /// private const int PlaneCount = 6; #endregion #region Public Constructors /// /// Constructs the frustum by extracting the view planes from a matrix. /// /// Combined matrix which usually is (View * Projection). public BoundingFrustum(Matrix4x4 value) { this.matrix = value; this.CreatePlanes(); this.CreateCorners(); } #endregion #region Public Methods /// /// Containment test between this and specified . /// /// A for testing. /// Result of testing for containment between this and specified . public ContainmentType Contains(BoundingFrustum frustum) { if (this == frustum) { return ContainmentType.Contains; } bool intersects = false; for (int i = 0; i < PlaneCount; i += 1) { PlaneIntersectionType planeIntersectionType; frustum.Intersects(ref planes[i], out planeIntersectionType); if (planeIntersectionType == PlaneIntersectionType.Front) { return ContainmentType.Disjoint; } else if (planeIntersectionType == PlaneIntersectionType.Intersecting) { intersects = true; } } return intersects ? ContainmentType.Intersects : ContainmentType.Contains; } /// /// Containment test between this and specified . /// /// A for testing. /// Result of testing for containment between this and specified . public ContainmentType Contains(BoundingBox box) { ContainmentType result = default(ContainmentType); this.Contains(ref box, out result); return result; } /// /// Containment test between this and specified . /// /// A for testing. /// Result of testing for containment between this and specified as an output parameter. public void Contains(ref BoundingBox box, out ContainmentType result) { bool intersects = false; for (int i = 0; i < PlaneCount; i += 1) { PlaneIntersectionType planeIntersectionType = default(PlaneIntersectionType); box.Intersects(ref this.planes[i], out planeIntersectionType); switch (planeIntersectionType) { case PlaneIntersectionType.Front: result = ContainmentType.Disjoint; return; case PlaneIntersectionType.Intersecting: intersects = true; break; } } result = intersects ? ContainmentType.Intersects : ContainmentType.Contains; } /// /// Containment test between this and specified . /// /// A for testing. /// Result of testing for containment between this and specified . public ContainmentType Contains(BoundingSphere sphere) { ContainmentType result = default(ContainmentType); this.Contains(ref sphere, out result); return result; } /// /// Containment test between this and specified . /// /// A for testing. /// Result of testing for containment between this and specified as an output parameter. public void Contains(ref BoundingSphere sphere, out ContainmentType result) { bool intersects = false; for (int i = 0; i < PlaneCount; i += 1) { PlaneIntersectionType planeIntersectionType = default(PlaneIntersectionType); // TODO: We might want to inline this for performance reasons. sphere.Intersects(ref this.planes[i], out planeIntersectionType); switch (planeIntersectionType) { case PlaneIntersectionType.Front: result = ContainmentType.Disjoint; return; case PlaneIntersectionType.Intersecting: intersects = true; break; } } result = intersects ? ContainmentType.Intersects : ContainmentType.Contains; } /// /// Containment test between this and specified . /// /// A for testing. /// Result of testing for containment between this and specified . public ContainmentType Contains(Vector3 point) { ContainmentType result = default(ContainmentType); this.Contains(ref point, out result); return result; } /// /// Containment test between this and specified . /// /// A for testing. /// Result of testing for containment between this and specified as an output parameter. public void Contains(ref Vector3 point, out ContainmentType result) { bool intersects = false; for (int i = 0; i < PlaneCount; i += 1) { float classifyPoint = ( (point.X * planes[i].Normal.X) + (point.Y * planes[i].Normal.Y) + (point.Z * planes[i].Normal.Z) + planes[i].D ); if (classifyPoint > 0) { result = ContainmentType.Disjoint; return; } else if (classifyPoint == 0) { intersects = true; break; } } result = intersects ? ContainmentType.Intersects : ContainmentType.Contains; } /// /// Returns a copy of internal corners array. /// /// The array of corners. public Vector3[] GetCorners() { return (Vector3[]) this.corners.Clone(); } /// /// Returns a copy of internal corners array. /// /// The array which values will be replaced to corner values of this instance. It must have size of . public void GetCorners(Vector3[] corners) { if (corners == null) { throw new ArgumentNullException("corners"); } if (corners.Length < CornerCount) { throw new ArgumentOutOfRangeException("corners"); } this.corners.CopyTo(corners, 0); } /// /// Gets whether or not a specified intersects with this . /// /// An other for intersection test. /// true if other intersects with this ; false otherwise. public bool Intersects(BoundingFrustum frustum) { return (Contains(frustum) != ContainmentType.Disjoint); } /// /// Gets whether or not a specified intersects with this . /// /// A for intersection test. /// true if specified intersects with this ; false otherwise. public bool Intersects(BoundingBox box) { bool result = false; this.Intersects(ref box, out result); return result; } /// /// Gets whether or not a specified intersects with this . /// /// A for intersection test. /// true if specified intersects with this ; false otherwise as an output parameter. public void Intersects(ref BoundingBox box, out bool result) { ContainmentType containment = default(ContainmentType); this.Contains(ref box, out containment); result = containment != ContainmentType.Disjoint; } /// /// Gets whether or not a specified intersects with this . /// /// A for intersection test. /// true if specified intersects with this ; false otherwise. public bool Intersects(BoundingSphere sphere) { bool result = default(bool); this.Intersects(ref sphere, out result); return result; } /// /// Gets whether or not a specified intersects with this . /// /// A for intersection test. /// true if specified intersects with this ; false otherwise as an output parameter. public void Intersects(ref BoundingSphere sphere, out bool result) { ContainmentType containment = default(ContainmentType); this.Contains(ref sphere, out containment); result = containment != ContainmentType.Disjoint; } /// /// Gets type of intersection between specified and this . /// /// A for intersection test. /// A plane intersection type. public PlaneIntersectionType Intersects(Plane plane) { PlaneIntersectionType result; Intersects(ref plane, out result); return result; } /// /// Gets type of intersection between specified and this . /// /// A for intersection test. /// A plane intersection type as an output parameter. public void Intersects(ref Plane plane, out PlaneIntersectionType result) { result = plane.Intersects(ref corners[0]); for (int i = 1; i < corners.Length; i += 1) { if (plane.Intersects(ref corners[i]) != result) { result = PlaneIntersectionType.Intersecting; } } } /// /// Gets the distance of intersection of and this or null if no intersection happens. /// /// A for intersection test. /// Distance at which ray intersects with this or null if no intersection happens. public float? Intersects(Ray ray) { float? result; Intersects(ref ray, out result); return result; } /// /// Gets the distance of intersection of and this or null if no intersection happens. /// /// A for intersection test. /// Distance at which ray intersects with this or null if no intersection happens as an output parameter. public void Intersects(ref Ray ray, out float? result) { ContainmentType ctype; Contains(ref ray.Position, out ctype); if (ctype == ContainmentType.Disjoint) { result = null; return; } if (ctype == ContainmentType.Contains) { result = 0.0f; return; } if (ctype != ContainmentType.Intersects) { throw new ArgumentOutOfRangeException("ctype"); } throw new NotImplementedException(); } #endregion #region Private Methods private void CreateCorners() { IntersectionPoint( ref this.planes[0], ref this.planes[2], ref this.planes[4], out this.corners[0] ); IntersectionPoint( ref this.planes[0], ref this.planes[3], ref this.planes[4], out this.corners[1] ); IntersectionPoint( ref this.planes[0], ref this.planes[3], ref this.planes[5], out this.corners[2] ); IntersectionPoint( ref this.planes[0], ref this.planes[2], ref this.planes[5], out this.corners[3] ); IntersectionPoint( ref this.planes[1], ref this.planes[2], ref this.planes[4], out this.corners[4] ); IntersectionPoint( ref this.planes[1], ref this.planes[3], ref this.planes[4], out this.corners[5] ); IntersectionPoint( ref this.planes[1], ref this.planes[3], ref this.planes[5], out this.corners[6] ); IntersectionPoint( ref this.planes[1], ref this.planes[2], ref this.planes[5], out this.corners[7] ); } private void CreatePlanes() { this.planes[0] = new Plane( -this.matrix.M13, -this.matrix.M23, -this.matrix.M33, -this.matrix.M43 ); this.planes[1] = new Plane( this.matrix.M13 - this.matrix.M14, this.matrix.M23 - this.matrix.M24, this.matrix.M33 - this.matrix.M34, this.matrix.M43 - this.matrix.M44 ); this.planes[2] = new Plane( -this.matrix.M14 - this.matrix.M11, -this.matrix.M24 - this.matrix.M21, -this.matrix.M34 - this.matrix.M31, -this.matrix.M44 - this.matrix.M41 ); this.planes[3] = new Plane( this.matrix.M11 - this.matrix.M14, this.matrix.M21 - this.matrix.M24, this.matrix.M31 - this.matrix.M34, this.matrix.M41 - this.matrix.M44 ); this.planes[4] = new Plane( this.matrix.M12 - this.matrix.M14, this.matrix.M22 - this.matrix.M24, this.matrix.M32 - this.matrix.M34, this.matrix.M42 - this.matrix.M44 ); this.planes[5] = new Plane( -this.matrix.M14 - this.matrix.M12, -this.matrix.M24 - this.matrix.M22, -this.matrix.M34 - this.matrix.M32, -this.matrix.M44 - this.matrix.M42 ); this.NormalizePlane(ref this.planes[0]); this.NormalizePlane(ref this.planes[1]); this.NormalizePlane(ref this.planes[2]); this.NormalizePlane(ref this.planes[3]); this.NormalizePlane(ref this.planes[4]); this.NormalizePlane(ref this.planes[5]); } private void NormalizePlane(ref Plane p) { float factor = 1f / p.Normal.Length(); p.Normal.X *= factor; p.Normal.Y *= factor; p.Normal.Z *= factor; p.D *= factor; } #endregion #region Private Static Methods private static void IntersectionPoint( ref Plane a, ref Plane b, ref Plane c, out Vector3 result ) { /* Formula used * d1 ( N2 * N3 ) + d2 ( N3 * N1 ) + d3 ( N1 * N2 ) * P = ------------------------------------------------------------------- * N1 . ( N2 * N3 ) * * Note: N refers to the normal, d refers to the displacement. '.' means dot * product. '*' means cross product */ Vector3 v1, v2, v3; Vector3 cross; Vector3.Cross(ref b.Normal, ref c.Normal, out cross); float f; Vector3.Dot(ref a.Normal, ref cross, out f); f *= -1.0f; Vector3.Cross(ref b.Normal, ref c.Normal, out cross); Vector3.Multiply(ref cross, a.D, out v1); // v1 = (a.D * (Vector3.Cross(b.Normal, c.Normal))); Vector3.Cross(ref c.Normal, ref a.Normal, out cross); Vector3.Multiply(ref cross, b.D, out v2); // v2 = (b.D * (Vector3.Cross(c.Normal, a.Normal))); Vector3.Cross(ref a.Normal, ref b.Normal, out cross); Vector3.Multiply(ref cross, c.D, out v3); // v3 = (c.D * (Vector3.Cross(a.Normal, b.Normal))); result.X = (v1.X + v2.X + v3.X) / f; result.Y = (v1.Y + v2.Y + v3.Y) / f; result.Z = (v1.Z + v2.Z + v3.Z) / f; } #endregion #region Public Static Operators and Override Methods /// /// Compares whether two instances are equal. /// /// instance on the left of the equal sign. /// instance on the right of the equal sign. /// true if the instances are equal; false otherwise. public static bool operator ==(BoundingFrustum a, BoundingFrustum b) { if (object.Equals(a, null)) { return (object.Equals(b, null)); } if (object.Equals(b, null)) { return (object.Equals(a, null)); } return a.matrix == (b.matrix); } /// /// Compares whether two instances are not equal. /// /// instance on the left of the not equal sign. /// instance on the right of the not equal sign. /// true if the instances are not equal; false otherwise. public static bool operator !=(BoundingFrustum a, BoundingFrustum b) { return !(a == b); } /// /// Compares whether current instance is equal to specified . /// /// The to compare. /// true if the instances are equal; false otherwise. public bool Equals(BoundingFrustum other) { return (this == other); } /// /// Compares whether current instance is equal to specified . /// /// The to compare. /// true if the instances are equal; false otherwise. public override bool Equals(object obj) { return (obj is BoundingFrustum) && Equals((BoundingFrustum) obj); } /// /// Returns a representation of this in the format: /// {Near:[nearPlane] Far:[farPlane] Left:[leftPlane] Right:[rightPlane] Top:[topPlane] Bottom:[bottomPlane]} /// /// representation of this . public override string ToString() { StringBuilder sb = new StringBuilder(256); sb.Append("{Near:"); sb.Append(this.planes[0].ToString()); sb.Append(" Far:"); sb.Append(this.planes[1].ToString()); sb.Append(" Left:"); sb.Append(this.planes[2].ToString()); sb.Append(" Right:"); sb.Append(this.planes[3].ToString()); sb.Append(" Top:"); sb.Append(this.planes[4].ToString()); sb.Append(" Bottom:"); sb.Append(this.planes[5].ToString()); sb.Append("}"); return sb.ToString(); } /// /// Gets the hash code of this . /// /// Hash code of this . public override int GetHashCode() { return this.matrix.GetHashCode(); } #endregion } }