/Utilities/Datatypes/BoundingBox.cs
C# | 535 lines | 351 code | 44 blank | 140 comment | 31 complexity | c99231a867d9b2762cef456837dd8b3c MD5 | raw file
Possible License(s): Apache-2.0
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Runtime.InteropServices;
- using Delta.Utilities.Datatypes.Advanced;
- using Delta.Utilities.Helpers;
- using NUnit.Framework;
-
- namespace Delta.Utilities.Datatypes
- {
- /// <summary>
- /// Bounding box helper structure, basically just contains a min and max
- /// vector for the bounding. Can also be used to calculate a BoundingSphere.
- /// <para />
- /// Little ASCII art for the BoundingBox (Note: Does not translate well into
- /// the documentation, see the BoundingBox.cs file for details):
- /// o--------o
- /// /: /| Y (TOP)
- /// / : / | |
- /// o--------M | |
- /// | : | | |
- /// | m.....|..o o------X (RIGHT)
- /// | ' | / /
- /// |' |/ /
- /// o--------o Z (FRONT)
- ///
- /// m is the Min component, M is the Max component
- /// </summary>
- [Serializable]
- [StructLayout(LayoutKind.Sequential)]
- public struct BoundingBox
- : ISaveLoadBinary, IEquatable<BoundingBox>, IContains
- {
- #region Create (Static)
- /// <summary>
- /// Creates a bounding box around the given positions.
- /// </summary>
- /// <param name="setPositions">Set positions</param>
- /// <returns>The created bounding box</returns>
- public static BoundingBox Create(IList<Vector> setPositions)
- {
- // Initialize min and max for bounding box with the first position
- Vector min = setPositions[0];
- Vector max = setPositions[0];
-
- // and increase / update the box with the following positions
- for (int num = 1; num < setPositions.Count; num++)
- {
- Vector pos = setPositions[num];
-
- // update min
- if (pos.X < min.X)
- {
- min.X = pos.X;
- }
- if (pos.Y < min.Y)
- {
- min.Y = pos.Y;
- }
- if (pos.Z < min.Z)
- {
- min.Z = pos.Z;
- }
-
- // update max
- if (pos.X > max.X)
- {
- max.X = pos.X;
- }
- if (pos.Y > max.Y)
- {
- max.Y = pos.Y;
- }
- if (pos.Z > max.Z)
- {
- max.Z = pos.Z;
- }
- }
-
- // After processing the position checking the "Min/Max" values to avoid
- // annoying degenerate cases
- float epsilon = MathHelper.Epsilon;
- if (min.X == max.X)
- {
- min.X -= epsilon;
- max.X += epsilon;
- }
- if (min.Y == max.Y)
- {
- min.Y -= epsilon;
- max.Y += epsilon;
- }
- if (min.Z == max.Z)
- {
- min.Z -= epsilon;
- max.Z += epsilon;
- }
-
- // before we finally create the bounding box
- return new BoundingBox(min, max);
- }
- #endregion
-
- #region Min (Public)
- /// <summary>
- /// Min values for this bounding box
- /// </summary>
- public Vector Min;
- #endregion
-
- #region Max (Public)
- /// <summary>
- /// Max values for this bounding box
- /// </summary>
- public Vector Max;
- #endregion
-
- #region Center (Public)
- /// <summary>
- /// Gets the center point of this bounding box.
- /// </summary>
- public Vector Center
- {
- get
- {
- return new Vector(
- (Max.X + Min.X) * 0.5f,
- (Max.Y + Min.Y) * 0.5f,
- (Max.Z + Min.Z) * 0.5f);
- }
- }
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create bounding box
- /// </summary>
- /// <param name="setMax">SetMax</param>
- /// <param name="setMin">SetMin</param>
- public BoundingBox(Vector setMin, Vector setMax)
- {
- Min = setMin;
- Max = setMax;
- }
- #endregion
-
- #region IContains Members
- /// <summary>
- /// Determines whether a Box contains another box
- /// </summary>
- /// <param name="box">Box to check against</param>
- /// <returns>Containment type (fully, partial or none)</returns>
- public ContainmentType Contains(BoundingBox box)
- {
- // If we are completely outside of the box, return none
- if (Max.X < box.Min.X ||
- Min.X > box.Max.X ||
- Max.Y < box.Min.Y ||
- Min.Y > box.Max.Y ||
- Max.Z < box.Min.Z ||
- Min.Z > box.Max.Z)
- {
- return ContainmentType.None;
- }
- // Are we fully inside the box?
- if (Min.X <= box.Min.X &&
- box.Max.X <= Max.X &&
- Min.Y <= box.Min.Y &&
- box.Max.Y <= Max.Y &&
- Min.Z <= box.Min.Z &&
- box.Max.Z <= Max.Z)
- {
- return ContainmentType.Fully;
- }
- // Nope? Then we must be partially contained!
- return ContainmentType.Partial;
- }
-
- /// <summary>
- /// Determines whether a box contains another sphere
- /// </summary>
- /// <param name="sphere">The sphere.</param>
- /// <returns>Containment type (fully, partial or none)</returns>
- public ContainmentType Contains(BoundingSphere sphere)
- {
- Vector closestPoint = Vector.Clamp(sphere.Center, Min, Max);
- float distance = Vector.DistanceSquared(sphere.Center, closestPoint);
-
- if (distance > sphere.Radius * sphere.Radius)
- {
- return ContainmentType.None;
- }
- if (Min.X + sphere.Radius <= sphere.Center.X &&
- sphere.Center.X <= Max.X - sphere.Radius &&
- Max.X - Min.X > sphere.Radius &&
- Min.Y + sphere.Radius <= sphere.Center.Y &&
- sphere.Center.Y <= Max.Y - sphere.Radius &&
- Max.Y - Min.Y > sphere.Radius &&
- Min.Z + sphere.Radius <= sphere.Center.Z &&
- sphere.Center.Z <= Max.Z - sphere.Radius &&
- Max.X - Min.X > sphere.Radius)
- {
- return ContainmentType.Fully;
- }
- return ContainmentType.Partial;
- }
-
- /// <summary>
- /// Checks whether this box contains a vector position.
- /// </summary>
- /// <param name="position">Position to check against</param>
- /// <returns>Either the position is inside the box or sphere (
- /// <see cref="ContainmentType.Fully"/> is returned), or not (then
- /// <see cref="ContainmentType.None"/> is returned).</returns>
- public ContainmentType Contains(Vector position)
- {
- if (Min.X <= position.X &&
- Max.X >= position.X &&
- Min.Y <= position.Y &&
- Max.Y >= position.Y &&
- Min.Z <= position.Z &&
- Max.Z >= position.Z)
- {
- return ContainmentType.Fully;
- }
- return ContainmentType.None;
- }
- #endregion
-
- #region IEquatable<BoundingBox> Members
- /// <summary>
- /// Equals
- /// </summary>
- /// <param name="other">Other</param>
- /// <returns>Value indicating the equality of two vectors</returns>
- public bool Equals(BoundingBox other)
- {
- return Min == other.Min &&
- Max == other.Max;
- }
- #endregion
-
- #region ISaveLoadBinary Members
- /// <summary>
- /// Load the BoundingBox values (Min and Max vectors) from a stream.
- /// </summary>
- /// <param name="reader">The stream that will be used.</param>
- public void Load(BinaryReader reader)
- {
- Min.Load(reader);
- Max.Load(reader);
- }
-
- /// <summary>
- /// Saves the BoundingBox (Min and Max vectors) to a stream.
- /// </summary>
- /// <param name="writer">The stream that will be used.</param>
- public void Save(BinaryWriter writer)
- {
- Min.Save(writer);
- Max.Save(writer);
- }
- #endregion
-
- #region op_Equality (Operator)
- /// <summary>
- /// Check for equality
- /// </summary>
- /// <param name="value1">Value 1</param>
- /// <param name="value2">Value 2</param>
- /// <returns>True if the values are equal, false otherwise</returns>
- public static bool operator ==(BoundingBox value1, BoundingBox value2)
- {
- return value1.Min == value2.Min &&
- value1.Max == value2.Max;
- }
- #endregion
-
- #region op_Inequality (Operator)
- /// <summary>
- /// Check for inequality
- /// </summary>
- /// <param name="value1">Value 1</param>
- /// <param name="value2">Value 2</param>
- /// <returns>True if the values are not equal, false if they are</returns>
- public static bool operator !=(BoundingBox value1, BoundingBox value2)
- {
- return value1.Min != value2.Min ||
- value1.Max != value2.Max;
- }
- #endregion
-
- #region Equals (Public)
- /// <summary>
- /// Check if an object is equal to this bounding box.
- /// </summary>
- /// <param name="obj">Object to compare</param>
- /// <returns>True if obj is a boundbing box and equals to this</returns>
- public override bool Equals(object obj)
- {
- if (obj is BoundingBox)
- {
- return Equals((BoundingBox)obj);
- }
- return base.Equals(obj);
- }
- #endregion
-
- #region GetHashCode (Public)
- /// <summary>
- /// Get hash code from min and max.
- /// </summary>
- /// <returns>Hash code</returns>
- public override int GetHashCode()
- {
- return Min.GetHashCode() ^ Max.GetHashCode();
- }
- #endregion
-
- #region Merge (Public)
- /// <summary>
- /// Merge two bounding boxes together building a bigger box.
- /// </summary>
- /// <param name="otherBox">Other box</param>
- public void Merge(BoundingBox otherBox)
- {
- if (otherBox.Min.X < Min.X)
- {
- Min.X = otherBox.Min.X;
- }
- if (otherBox.Min.Y < Min.Y)
- {
- Min.Y = otherBox.Min.Y;
- }
- if (otherBox.Min.Z < Min.Z)
- {
- Min.Z = otherBox.Min.Z;
- }
- if (otherBox.Max.X > Max.X)
- {
- Max.X = otherBox.Max.X;
- }
- if (otherBox.Max.Y > Max.Y)
- {
- Max.Y = otherBox.Max.Y;
- }
- if (otherBox.Max.Z > Max.Z)
- {
- Max.Z = otherBox.Max.Z;
- }
- }
- #endregion
-
- #region ToBoundingSphere (Public)
- /// <summary>
- /// Create bounding sphere from Min and Max values of this box.
- /// </summary>
- /// <returns>BoundingSphere created from this bounding box size.</returns>
- public BoundingSphere ToBoundingSphere()
- {
- Vector center = (Max + Min) * 0.5f;
- Vector sideLengths = (Max - Min) * 0.5f;
- float radius = sideLengths.Length;
- return new BoundingSphere(center, radius);
- }
- #endregion
-
- #region ToString (Public)
- /// <summary>
- /// To string
- /// </summary>
- /// <returns>String</returns>
- public override string ToString()
- {
- return GetType().Name + "(Min=" + Min + ", Max=" + Max + ")";
- }
- #endregion
-
- /// <summary>
- /// Tests
- /// </summary>
- internal class BoundingBoxTests
- {
- #region Merge
- /// <summary>
- /// Merge
- /// </summary>
- [Test]
- public void Merge()
- {
- var boxA = new BoundingBox();
-
- boxA.Merge(new BoundingBox(Vector.Zero, Vector.One / 2.0f));
- Assert.Equal(boxA, new BoundingBox(Vector.Zero, Vector.One / 2.0f));
-
- boxA.Merge(new BoundingBox(-Vector.One, Vector.One));
- Assert.Equal(boxA, new BoundingBox(-Vector.One, Vector.One));
-
- boxA.Merge(new BoundingBox(Vector.Zero, Vector.Zero));
- Assert.Equal(boxA, new BoundingBox(-Vector.One, Vector.One));
-
- boxA.Merge(new BoundingBox(-Vector.One / 2.0f, Vector.One / 2.0f));
- Assert.Equal(boxA, new BoundingBox(-Vector.One, Vector.One));
- }
- #endregion
-
- #region Create
- /// <summary>
- /// Create
- /// </summary>
- [Test]
- public void Create()
- {
- var postions = new[]
- {
- new Vector(1, 5, 9),
- new Vector(6, 4, 2),
- new Vector(3, 7, 3),
- };
-
- BoundingBox box = BoundingBox.Create(postions);
- Assert.Equal(box.Min, new Vector(1, 4, 2));
- Assert.Equal(box.Max, new Vector(6, 7, 9));
- }
- #endregion
-
- #region Contains
- /// <summary>
- /// ContainsFully
- /// </summary>
- [Test]
- public void Contains()
- {
- var box = new BoundingBox(Vector.Half, Vector.One);
- var sphere = new BoundingSphere(Vector.Half, 0.5f);
- Assert.Equal(box.Contains(sphere), ContainmentType.Partial);
-
- // Box to box tests
- box = new BoundingBox(Vector.Zero, Vector.One);
- var fullyInsideBox = new BoundingBox(Vector.Half, Vector.One);
- Assert.Equal(box.Contains(fullyInsideBox), ContainmentType.Fully);
- var partiallyInsideBox1 = new BoundingBox(
- new Vector(0.8f, 0.8f, 0.6f), new Vector(0.8f, 0.9f, 1.1f));
- var partiallyInsideBox2 = new BoundingBox(
- Vector.One, new Vector(0.8f, 0.9f, 1.1f));
- Assert.Equal(box.Contains(partiallyInsideBox1),
- ContainmentType.Partial);
- Assert.Equal(box.Contains(partiallyInsideBox2),
- ContainmentType.Partial);
- var outsideBox1 = new BoundingBox(
- new Vector(1.1f, 1.1f, 1.1f), new Vector(2.0f, 2.0f, 2.0f));
- var outsideBox2 = new BoundingBox(
- new Vector(0.1f, 0.8f, 1.1f), new Vector(0.2f, 1.0f, 2.0f));
- var outsideBox3 = new BoundingBox(
- new Vector(-1.0f, 1.0f, 1.0f), new Vector(-0.1f, 1.0f, 1.0f));
- Assert.Equal(box.Contains(outsideBox1), ContainmentType.None);
- Assert.Equal(box.Contains(outsideBox2), ContainmentType.None);
- Assert.Equal(box.Contains(outsideBox3), ContainmentType.None);
- }
- #endregion
-
- #region ToBoundingSphere
- /// <summary>
- /// Test Bounding spheres
- /// </summary>
- [Test]
- public void ToBoundingSphere()
- {
- var mi = new Vector(1, 2, 3);
- var mx = new Vector(9, 11, 4);
- var bBox = new BoundingBox(mi, mx);
- bBox.ToBoundingSphere();
- Vector mi1 = (mi + mx) * 0.5f;
- Vector mi2 = (mx - mi) * 0.5f;
- var bbox = new BoundingBox(mi1, mi2);
- Assert.Equals(bBox, bbox);
- }
- #endregion
-
- #region Equality
- /// <summary>
- /// Equality
- /// </summary>
- [Test]
- public void Equality()
- {
- Assert.Equal(new BoundingBox
- (Vector.Zero, Vector.Zero), new BoundingBox());
- }
- #endregion
-
- #region SaveAndLoad
- /// <summary>
- /// Test to save and load spheres into a binary stream
- /// </summary>
- [Test]
- public void SaveAndLoad()
- {
- var box = new BoundingBox
- (Vector.Zero, Vector.One);
-
- var memHandle = new MemoryStream();
- Assert.Equal(0, memHandle.Position);
-
- var writer = new BinaryWriter(memHandle);
-
- // Saving Assertion
- // save all the current data
- box.Save(writer);
-
- // and finally check (for saving) if the file was written correctly
- Assert.NotEqual(0, memHandle.Length);
-
- // Loading Assertion
- // then we create an "empty" material
- var loadBox = new BoundingBox
- (Vector.Half, Vector.One);
- memHandle.Position = 0;
-
- // which we use to load the material values from the file
- // Note: The using closes the file access too
- var reader = new BinaryReader(memHandle);
- loadBox.Load(reader);
-
- // before we finally check if everything is loaded correctly
- Assert.Equal(box, loadBox);
-
- writer.Close();
- reader.Close();
- memHandle.Close();
- }
- #endregion
- }
- }
- }