PageRenderTime 167ms CodeModel.GetById 60ms app.highlight 67ms RepoModel.GetById 34ms app.codeStats 0ms

/Utilities/Datatypes/BoundingBox.cs

#
C# | 535 lines | 351 code | 44 blank | 140 comment | 31 complexity | c99231a867d9b2762cef456837dd8b3c MD5 | raw file
  1using System;
  2using System.Collections.Generic;
  3using System.IO;
  4using System.Runtime.InteropServices;
  5using Delta.Utilities.Datatypes.Advanced;
  6using Delta.Utilities.Helpers;
  7using NUnit.Framework;
  8
  9namespace Delta.Utilities.Datatypes
 10{
 11	/// <summary>
 12	/// Bounding box helper structure, basically just contains a min and max
 13	/// vector for the bounding. Can also be used to calculate a BoundingSphere.
 14	/// <para />
 15	/// Little ASCII art for the BoundingBox (Note: Does not translate well into
 16	/// the documentation, see the BoundingBox.cs file for details):
 17	///         o--------o                           
 18	///        /:       /|          Y (TOP)          
 19	///       / :      / |          |                
 20	///      o--------M  |          |                
 21	///      |  :     |  |          |                
 22	///      |  m.....|..o          o------X (RIGHT) 
 23	///      | '      | /          /                 
 24	///      |'       |/          /                  
 25	///      o--------o          Z (FRONT)           
 26	/// 
 27	/// m is the Min component, M is the Max component
 28	/// </summary>
 29	[Serializable]
 30	[StructLayout(LayoutKind.Sequential)]
 31	public struct BoundingBox
 32		: ISaveLoadBinary, IEquatable<BoundingBox>, IContains
 33	{
 34		#region Create (Static)
 35		/// <summary>
 36		/// Creates a bounding box around the given positions.
 37		/// </summary>
 38		/// <param name="setPositions">Set positions</param>
 39		/// <returns>The created bounding box</returns>
 40		public static BoundingBox Create(IList<Vector> setPositions)
 41		{
 42			// Initialize min and max for bounding box with the first position
 43			Vector min = setPositions[0];
 44			Vector max = setPositions[0];
 45
 46			// and increase / update the box with the following positions
 47			for (int num = 1; num < setPositions.Count; num++)
 48			{
 49				Vector pos = setPositions[num];
 50
 51				// update min
 52				if (pos.X < min.X)
 53				{
 54					min.X = pos.X;
 55				}
 56				if (pos.Y < min.Y)
 57				{
 58					min.Y = pos.Y;
 59				}
 60				if (pos.Z < min.Z)
 61				{
 62					min.Z = pos.Z;
 63				}
 64
 65				// update max
 66				if (pos.X > max.X)
 67				{
 68					max.X = pos.X;
 69				}
 70				if (pos.Y > max.Y)
 71				{
 72					max.Y = pos.Y;
 73				}
 74				if (pos.Z > max.Z)
 75				{
 76					max.Z = pos.Z;
 77				}
 78			}
 79
 80			// After processing the position checking the "Min/Max" values to avoid
 81			// annoying degenerate cases
 82			float epsilon = MathHelper.Epsilon;
 83			if (min.X == max.X)
 84			{
 85				min.X -= epsilon;
 86				max.X += epsilon;
 87			}
 88			if (min.Y == max.Y)
 89			{
 90				min.Y -= epsilon;
 91				max.Y += epsilon;
 92			}
 93			if (min.Z == max.Z)
 94			{
 95				min.Z -= epsilon;
 96				max.Z += epsilon;
 97			}
 98
 99			// before we finally create the bounding box
100			return new BoundingBox(min, max);
101		}
102		#endregion
103
104		#region Min (Public)
105		/// <summary>
106		/// Min values for this bounding box
107		/// </summary>
108		public Vector Min;
109		#endregion
110
111		#region Max (Public)
112		/// <summary>
113		/// Max values for this bounding box
114		/// </summary>
115		public Vector Max;
116		#endregion
117
118		#region Center (Public)
119		/// <summary>
120		/// Gets the center point of this bounding box.
121		/// </summary>
122		public Vector Center
123		{
124			get
125			{
126				return new Vector(
127					(Max.X + Min.X) * 0.5f,
128					(Max.Y + Min.Y) * 0.5f,
129					(Max.Z + Min.Z) * 0.5f);
130			}
131		}
132		#endregion
133
134		#region Constructors
135		/// <summary>
136		/// Create bounding box
137		/// </summary>
138		/// <param name="setMax">SetMax</param>
139		/// <param name="setMin">SetMin</param>
140		public BoundingBox(Vector setMin, Vector setMax)
141		{
142			Min = setMin;
143			Max = setMax;
144		}
145		#endregion
146
147		#region IContains Members
148		/// <summary>
149		/// Determines whether a Box contains another box
150		/// </summary>
151		/// <param name="box">Box to check against</param>
152		/// <returns>Containment type (fully, partial or none)</returns>
153		public ContainmentType Contains(BoundingBox box)
154		{
155			// If we are completely outside of the box, return none
156			if (Max.X < box.Min.X ||
157			    Min.X > box.Max.X ||
158			    Max.Y < box.Min.Y ||
159			    Min.Y > box.Max.Y ||
160			    Max.Z < box.Min.Z ||
161			    Min.Z > box.Max.Z)
162			{
163				return ContainmentType.None;
164			}
165			// Are we fully inside the box?
166			if (Min.X <= box.Min.X &&
167			    box.Max.X <= Max.X &&
168			    Min.Y <= box.Min.Y &&
169			    box.Max.Y <= Max.Y &&
170			    Min.Z <= box.Min.Z &&
171			    box.Max.Z <= Max.Z)
172			{
173				return ContainmentType.Fully;
174			}
175			// Nope? Then we must be partially contained!
176			return ContainmentType.Partial;
177		}
178
179		/// <summary>
180		/// Determines whether a box contains another sphere
181		/// </summary>
182		/// <param name="sphere">The sphere.</param>
183		/// <returns>Containment type (fully, partial or none)</returns>
184		public ContainmentType Contains(BoundingSphere sphere)
185		{
186			Vector closestPoint = Vector.Clamp(sphere.Center, Min, Max);
187			float distance = Vector.DistanceSquared(sphere.Center, closestPoint);
188
189			if (distance > sphere.Radius * sphere.Radius)
190			{
191				return ContainmentType.None;
192			}
193			if (Min.X + sphere.Radius <= sphere.Center.X &&
194			    sphere.Center.X <= Max.X - sphere.Radius &&
195			    Max.X - Min.X > sphere.Radius &&
196			    Min.Y + sphere.Radius <= sphere.Center.Y &&
197			    sphere.Center.Y <= Max.Y - sphere.Radius &&
198			    Max.Y - Min.Y > sphere.Radius &&
199			    Min.Z + sphere.Radius <= sphere.Center.Z &&
200			    sphere.Center.Z <= Max.Z - sphere.Radius &&
201			    Max.X - Min.X > sphere.Radius)
202			{
203				return ContainmentType.Fully;
204			}
205			return ContainmentType.Partial;
206		}
207
208		/// <summary>
209		/// Checks whether this box contains a vector position.
210		/// </summary>
211		/// <param name="position">Position to check against</param>
212		/// <returns>Either the position is inside the box or sphere (
213		/// <see cref="ContainmentType.Fully"/> is returned), or not (then
214		/// <see cref="ContainmentType.None"/> is returned).</returns>
215		public ContainmentType Contains(Vector position)
216		{
217			if (Min.X <= position.X &&
218			    Max.X >= position.X &&
219			    Min.Y <= position.Y &&
220			    Max.Y >= position.Y &&
221			    Min.Z <= position.Z &&
222			    Max.Z >= position.Z)
223			{
224				return ContainmentType.Fully;
225			}
226			return ContainmentType.None;
227		}
228		#endregion
229
230		#region IEquatable<BoundingBox> Members
231		/// <summary>
232		/// Equals
233		/// </summary>
234		/// <param name="other">Other</param>
235		/// <returns>Value indicating the equality of two vectors</returns>
236		public bool Equals(BoundingBox other)
237		{
238			return Min == other.Min &&
239			       Max == other.Max;
240		}
241		#endregion
242
243		#region ISaveLoadBinary Members
244		/// <summary>
245		/// Load the BoundingBox values (Min and Max vectors) from a stream.
246		/// </summary>
247		/// <param name="reader">The stream that will be used.</param>
248		public void Load(BinaryReader reader)
249		{
250			Min.Load(reader);
251			Max.Load(reader);
252		}
253
254		/// <summary>
255		/// Saves the BoundingBox (Min and Max vectors) to a stream.
256		/// </summary>
257		/// <param name="writer">The stream that will be used.</param>
258		public void Save(BinaryWriter writer)
259		{
260			Min.Save(writer);
261			Max.Save(writer);
262		}
263		#endregion
264
265		#region op_Equality (Operator)
266		/// <summary>
267		/// Check for equality
268		/// </summary>
269		/// <param name="value1">Value 1</param>
270		/// <param name="value2">Value 2</param>
271		/// <returns>True if the values are equal, false otherwise</returns>
272		public static bool operator ==(BoundingBox value1, BoundingBox value2)
273		{
274			return value1.Min == value2.Min &&
275			       value1.Max == value2.Max;
276		}
277		#endregion
278
279		#region op_Inequality (Operator)
280		/// <summary>
281		/// Check for inequality
282		/// </summary>
283		/// <param name="value1">Value 1</param>
284		/// <param name="value2">Value 2</param>
285		/// <returns>True if the values are not equal, false if they are</returns>
286		public static bool operator !=(BoundingBox value1, BoundingBox value2)
287		{
288			return value1.Min != value2.Min ||
289			       value1.Max != value2.Max;
290		}
291		#endregion
292
293		#region Equals (Public)
294		/// <summary>
295		/// Check if an object is equal to this bounding box.
296		/// </summary>
297		/// <param name="obj">Object to compare</param>
298		/// <returns>True if obj is a boundbing box and equals to this</returns>
299		public override bool Equals(object obj)
300		{
301			if (obj is BoundingBox)
302			{
303				return Equals((BoundingBox)obj);
304			}
305			return base.Equals(obj);
306		}
307		#endregion
308
309		#region GetHashCode (Public)
310		/// <summary>
311		/// Get hash code from min and max.
312		/// </summary>
313		/// <returns>Hash code</returns>
314		public override int GetHashCode()
315		{
316			return Min.GetHashCode() ^ Max.GetHashCode();
317		}
318		#endregion
319
320		#region Merge (Public)
321		/// <summary>
322		/// Merge two bounding boxes together building a bigger box.
323		/// </summary>
324		/// <param name="otherBox">Other box</param>
325		public void Merge(BoundingBox otherBox)
326		{
327			if (otherBox.Min.X < Min.X)
328			{
329				Min.X = otherBox.Min.X;
330			}
331			if (otherBox.Min.Y < Min.Y)
332			{
333				Min.Y = otherBox.Min.Y;
334			}
335			if (otherBox.Min.Z < Min.Z)
336			{
337				Min.Z = otherBox.Min.Z;
338			}
339			if (otherBox.Max.X > Max.X)
340			{
341				Max.X = otherBox.Max.X;
342			}
343			if (otherBox.Max.Y > Max.Y)
344			{
345				Max.Y = otherBox.Max.Y;
346			}
347			if (otherBox.Max.Z > Max.Z)
348			{
349				Max.Z = otherBox.Max.Z;
350			}
351		}
352		#endregion
353
354		#region ToBoundingSphere (Public)
355		/// <summary>
356		/// Create bounding sphere from Min and Max values of this box.
357		/// </summary>
358		/// <returns>BoundingSphere created from this bounding box size.</returns>
359		public BoundingSphere ToBoundingSphere()
360		{
361			Vector center = (Max + Min) * 0.5f;
362			Vector sideLengths = (Max - Min) * 0.5f;
363			float radius = sideLengths.Length;
364			return new BoundingSphere(center, radius);
365		}
366		#endregion
367
368		#region ToString (Public)
369		/// <summary>
370		/// To string
371		/// </summary>
372		/// <returns>String</returns>
373		public override string ToString()
374		{
375			return GetType().Name + "(Min=" + Min + ", Max=" + Max + ")";
376		}
377		#endregion
378
379		/// <summary>
380		/// Tests
381		/// </summary>
382		internal class BoundingBoxTests
383		{
384			#region Merge
385			/// <summary>
386			/// Merge
387			/// </summary>
388			[Test]
389			public void Merge()
390			{
391				var boxA = new BoundingBox();
392
393				boxA.Merge(new BoundingBox(Vector.Zero, Vector.One / 2.0f));
394				Assert.Equal(boxA, new BoundingBox(Vector.Zero, Vector.One / 2.0f));
395
396				boxA.Merge(new BoundingBox(-Vector.One, Vector.One));
397				Assert.Equal(boxA, new BoundingBox(-Vector.One, Vector.One));
398
399				boxA.Merge(new BoundingBox(Vector.Zero, Vector.Zero));
400				Assert.Equal(boxA, new BoundingBox(-Vector.One, Vector.One));
401
402				boxA.Merge(new BoundingBox(-Vector.One / 2.0f, Vector.One / 2.0f));
403				Assert.Equal(boxA, new BoundingBox(-Vector.One, Vector.One));
404			}
405			#endregion
406
407			#region Create
408			/// <summary>
409			/// Create
410			/// </summary>
411			[Test]
412			public void Create()
413			{
414				var postions = new[]
415				{
416					new Vector(1, 5, 9),
417					new Vector(6, 4, 2),
418					new Vector(3, 7, 3),
419				};
420
421				BoundingBox box = BoundingBox.Create(postions);
422				Assert.Equal(box.Min, new Vector(1, 4, 2));
423				Assert.Equal(box.Max, new Vector(6, 7, 9));
424			}
425			#endregion
426
427			#region Contains
428			/// <summary>
429			/// ContainsFully
430			/// </summary>
431			[Test]
432			public void Contains()
433			{
434				var box = new BoundingBox(Vector.Half, Vector.One);
435				var sphere = new BoundingSphere(Vector.Half, 0.5f);
436				Assert.Equal(box.Contains(sphere), ContainmentType.Partial);
437
438				// Box to box tests
439				box = new BoundingBox(Vector.Zero, Vector.One);
440				var fullyInsideBox = new BoundingBox(Vector.Half, Vector.One);
441				Assert.Equal(box.Contains(fullyInsideBox), ContainmentType.Fully);
442				var partiallyInsideBox1 = new BoundingBox(
443					new Vector(0.8f, 0.8f, 0.6f), new Vector(0.8f, 0.9f, 1.1f));
444				var partiallyInsideBox2 = new BoundingBox(
445					Vector.One, new Vector(0.8f, 0.9f, 1.1f));
446				Assert.Equal(box.Contains(partiallyInsideBox1),
447					ContainmentType.Partial);
448				Assert.Equal(box.Contains(partiallyInsideBox2),
449					ContainmentType.Partial);
450				var outsideBox1 = new BoundingBox(
451					new Vector(1.1f, 1.1f, 1.1f), new Vector(2.0f, 2.0f, 2.0f));
452				var outsideBox2 = new BoundingBox(
453					new Vector(0.1f, 0.8f, 1.1f), new Vector(0.2f, 1.0f, 2.0f));
454				var outsideBox3 = new BoundingBox(
455					new Vector(-1.0f, 1.0f, 1.0f), new Vector(-0.1f, 1.0f, 1.0f));
456				Assert.Equal(box.Contains(outsideBox1), ContainmentType.None);
457				Assert.Equal(box.Contains(outsideBox2), ContainmentType.None);
458				Assert.Equal(box.Contains(outsideBox3), ContainmentType.None);
459			}
460			#endregion
461
462			#region ToBoundingSphere
463			/// <summary>
464			/// Test Bounding spheres 
465			/// </summary>
466			[Test]
467			public void ToBoundingSphere()
468			{
469				var mi = new Vector(1, 2, 3);
470				var mx = new Vector(9, 11, 4);
471				var bBox = new BoundingBox(mi, mx);
472				bBox.ToBoundingSphere();
473				Vector mi1 = (mi + mx) * 0.5f;
474				Vector mi2 = (mx - mi) * 0.5f;
475				var bbox = new BoundingBox(mi1, mi2);
476				Assert.Equals(bBox, bbox);
477			}
478			#endregion
479
480			#region Equality
481			/// <summary>
482			/// Equality
483			/// </summary>
484			[Test]
485			public void Equality()
486			{
487				Assert.Equal(new BoundingBox
488					(Vector.Zero, Vector.Zero), new BoundingBox());
489			}
490			#endregion
491
492			#region SaveAndLoad
493			/// <summary>
494			/// Test to save and load spheres into a binary stream
495			/// </summary>
496			[Test]
497			public void SaveAndLoad()
498			{
499				var box = new BoundingBox
500					(Vector.Zero, Vector.One);
501
502				var memHandle = new MemoryStream();
503				Assert.Equal(0, memHandle.Position);
504
505				var writer = new BinaryWriter(memHandle);
506
507				// Saving Assertion
508				// save all the current data
509				box.Save(writer);
510
511				// and finally check (for saving) if the file was written correctly
512				Assert.NotEqual(0, memHandle.Length);
513
514				// Loading Assertion
515				// then we create an "empty" material
516				var loadBox = new BoundingBox
517					(Vector.Half, Vector.One);
518				memHandle.Position = 0;
519
520				// which we use to load the material values from the file
521				// Note: The using closes the file access too
522				var reader = new BinaryReader(memHandle);
523				loadBox.Load(reader);
524
525				// before we finally check if everything is loaded correctly
526				Assert.Equal(box, loadBox);
527
528				writer.Close();
529				reader.Close();
530				memHandle.Close();
531			}
532			#endregion
533		}
534	}
535}