PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

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