PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/Utilities/Datatypes/Ray.cs

#
C# | 493 lines | 351 code | 29 blank | 113 comment | 30 complexity | 5e4dfe9a7998bb045a033a4ffd9f44bf MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Runtime.InteropServices;
  5. using Delta.Utilities.Datatypes.Advanced;
  6. using NUnit.Framework;
  7. namespace Delta.Utilities.Datatypes
  8. {
  9. /// <summary>
  10. /// Ray struct, used to fire rays into a 3D scene to find out what we can
  11. /// hit with that ray (for mouse picking and other simple collision stuff)
  12. /// </summary>ZeroTolerance
  13. [StructLayout(LayoutKind.Sequential)]
  14. [DebuggerDisplay("Ray(RayStart={RayStart.X}, {RayStart.Y}, {RayStart.Z}, " +
  15. "Direction=({Direction.X}, {Direction.Y}, {Direction.Z})")]
  16. public struct Ray : ISaveLoadBinary, IEquatable<Ray>, IIntersects
  17. {
  18. #region Constants
  19. private const float ZeroTolerance = 0.00001f;
  20. #endregion
  21. #region Position (Public)
  22. /// <summary>
  23. /// Gets or sets the starting point of the ray
  24. /// </summary>
  25. public Vector Position;
  26. #endregion
  27. #region Direction (Public)
  28. /// <summary>
  29. /// Gets or sets the direction of the ray
  30. /// </summary>
  31. public Vector Direction;
  32. #endregion
  33. #region Constructors
  34. /// <summary>
  35. /// Create ray
  36. /// </summary>
  37. /// <param name="rayStart">Ray start</param>
  38. /// <param name="direction">Direction</param>
  39. public Ray(Vector rayStart, Vector direction)
  40. {
  41. Position = rayStart;
  42. Direction = direction;
  43. }
  44. #endregion
  45. #region IEquatable<Ray> Members
  46. /// <summary>
  47. /// Check if another ray is equal to this ray.
  48. /// </summary>
  49. /// <param name="other">Other ray</param>
  50. /// <returns>
  51. /// True if the other ray has the same values as this ray.
  52. /// </returns>
  53. public bool Equals(Ray other)
  54. {
  55. return this == other;
  56. }
  57. #endregion
  58. #region IIntersects Members
  59. /// <summary>
  60. /// Intersects the current object with specified plane and returns true
  61. /// plus the intersectionPosition if the plane intersects this object.
  62. /// </summary>
  63. /// <param name="plane">The plane to check with</param>
  64. /// <param name="intersectionPosition">The intersection vector</param>
  65. /// <returns>True if the plane intersected with this object</returns>
  66. public bool Intersects(Plane plane, out Vector intersectionPosition)
  67. {
  68. intersectionPosition = Vector.Zero;
  69. float direction = Vector.Dot(plane.Normal, Direction);
  70. if (Math.Abs(direction) < ZeroTolerance)
  71. {
  72. intersectionPosition = Vector.Zero;
  73. return false;
  74. }
  75. float position = Vector.Dot(plane.Normal, Position);
  76. float distance = (plane.Distance - position) / direction;
  77. if (distance < 0f)
  78. {
  79. if (distance < -ZeroTolerance)
  80. {
  81. return false;
  82. }
  83. return true;
  84. }
  85. intersectionPosition = Position + (Direction * distance);
  86. return true;
  87. }
  88. /// <summary>
  89. /// Intersects the current object with specified ray and returns true
  90. /// plus the intersectionPosition if the ray hits this object.
  91. /// </summary>
  92. /// <param name="ray">The ray to check with</param>
  93. /// <param name="intersectionPosition">The intersection vector</param>
  94. /// <returns>True if the ray intersected with this object</returns>
  95. public bool Intersects(Ray ray, out Vector intersectionPosition)
  96. {
  97. Vector cross = Vector.Cross(Direction, ray.Direction);
  98. intersectionPosition = Vector.Zero;
  99. float denominator = cross.Length;
  100. // 5Lines are parallel.
  101. if (Math.Abs(denominator) < ZeroTolerance)
  102. {
  103. // Lines are parallel and on top of each other.
  104. if (Math.Abs(ray.Position.X - Position.X) < ZeroTolerance &&
  105. Math.Abs(ray.Position.Y - Position.Y) < ZeroTolerance &&
  106. Math.Abs(ray.Position.Z - Position.Z) < ZeroTolerance)
  107. {
  108. return true;
  109. }
  110. }
  111. denominator = denominator * denominator;
  112. // 3x3 matrix for the first ray.
  113. float m11 = ray.Position.X - Position.X;
  114. float m12 = ray.Position.Y - Position.Y;
  115. float m13 = ray.Position.Z - Position.Z;
  116. float m21 = ray.Direction.X;
  117. float m22 = ray.Direction.Y;
  118. float m23 = ray.Direction.Z;
  119. float m31 = cross.X;
  120. float m32 = cross.Y;
  121. float m33 = cross.Z;
  122. // Determinant of first matrix.
  123. float dets =
  124. m11 * m22 * m33 +
  125. m12 * m23 * m31 +
  126. m13 * m21 * m32 -
  127. m11 * m23 * m32 -
  128. m12 * m21 * m33 -
  129. m13 * m22 * m31;
  130. // 3x3 matrix for the second ray.
  131. m21 = Direction.X;
  132. m22 = Direction.Y;
  133. m23 = Direction.Z;
  134. // Determinant of the second matrix.
  135. float dett =
  136. m11 * m22 * m33 +
  137. m12 * m23 * m31 +
  138. m13 * m21 * m32 -
  139. m11 * m23 * m32 -
  140. m12 * m21 * m33 -
  141. m13 * m22 * m31;
  142. // t values of the point of intersection.
  143. float s = dets / denominator;
  144. float t = dett / denominator;
  145. //The points of intersection.
  146. Vector point1 = Position + (s * Direction);
  147. Vector point2 = ray.Position + (t * ray.Direction);
  148. // If the points are not equal, no intersection has occurred.
  149. if (Math.Abs(point2.X - point1.X) > ZeroTolerance ||
  150. Math.Abs(point2.Y - point1.Y) > ZeroTolerance ||
  151. Math.Abs(point2.Z - point1.Z) > ZeroTolerance)
  152. {
  153. return false;
  154. }
  155. intersectionPosition = point1;
  156. return true;
  157. }
  158. /// <summary>
  159. /// Intersects the current object with the specified sphere.
  160. /// </summary>
  161. /// <param name="sphere">The sphere to check against</param>
  162. /// <returns>True if the sphere intersected with this object</returns>
  163. public bool Intersects(BoundingSphere sphere)
  164. {
  165. Vector distance;
  166. Vector.Subtract(ref Position, ref sphere.Center, out distance);
  167. float b = Vector.Dot(distance, Direction);
  168. float c = Vector.Dot(distance, distance) -
  169. (sphere.Radius * sphere.Radius);
  170. if (c > 0f && b > 0f)
  171. {
  172. return false;
  173. }
  174. float discriminant = b * b - c;
  175. if (discriminant < 0f)
  176. {
  177. return false;
  178. }
  179. float intersectionPoint = -b - (float)Math.Sqrt(discriminant);
  180. if (intersectionPoint < 0f)
  181. {
  182. return true;
  183. }
  184. return false;
  185. }
  186. /// <summary>
  187. /// Intersects the current object with the specified box.
  188. /// </summary>
  189. /// <param name="box">The box to check against</param>
  190. /// <returns>True if the box intersected with this object</returns>
  191. public bool Intersects(BoundingBox box)
  192. {
  193. float intersectionPoint = 0f;
  194. float tmax = float.MaxValue;
  195. if (Math.Abs(Direction.X) < ZeroTolerance)
  196. {
  197. if (Position.X < box.Min.X || Position.X > box.Max.X)
  198. {
  199. return false;
  200. }
  201. }
  202. else
  203. {
  204. float inverse = 1.0f / Direction.X;
  205. float t1 = (box.Min.X - Position.X) * inverse;
  206. float t2 = (box.Max.X - Position.X) * inverse;
  207. if (t1 > t2)
  208. {
  209. float temp = t1;
  210. t1 = t2;
  211. t2 = temp;
  212. }
  213. intersectionPoint = Math.Max(t1, intersectionPoint);
  214. tmax = Math.Min(t2, tmax);
  215. if (intersectionPoint > tmax)
  216. {
  217. return false;
  218. }
  219. }
  220. if (Math.Abs(Direction.Y) < ZeroTolerance)
  221. {
  222. if (Position.Y < box.Min.Y || Position.Y > box.Max.Y)
  223. {
  224. return false;
  225. }
  226. }
  227. else
  228. {
  229. float inverse = 1.0f / Direction.Y;
  230. float t1 = (box.Min.Y - Position.Y) * inverse;
  231. float t2 = (box.Max.Y - Position.Y) * inverse;
  232. if (t1 > t2)
  233. {
  234. float temp = t1;
  235. t1 = t2;
  236. t2 = temp;
  237. }
  238. intersectionPoint = Math.Max(t1, intersectionPoint);
  239. tmax = Math.Min(t2, tmax);
  240. if (intersectionPoint > tmax)
  241. {
  242. return false;
  243. }
  244. }
  245. if (Math.Abs(Direction.Z) < ZeroTolerance)
  246. {
  247. if (Position.Z < box.Min.Z || Position.Z > box.Max.Z)
  248. {
  249. return false;
  250. }
  251. }
  252. else
  253. {
  254. float inverse = 1.0f / Direction.Z;
  255. float t1 = (box.Min.Z - Position.Z) * inverse;
  256. float t2 = (box.Max.Z - Position.Z) * inverse;
  257. if (t1 > t2)
  258. {
  259. float temp = t1;
  260. t1 = t2;
  261. t2 = temp;
  262. }
  263. intersectionPoint = Math.Max(t1, intersectionPoint);
  264. tmax = Math.Min(t2, tmax);
  265. if (intersectionPoint > tmax)
  266. {
  267. return false;
  268. }
  269. }
  270. return true;
  271. }
  272. #endregion
  273. #region ISaveLoadBinary Members
  274. /// <summary>
  275. /// Load the position and direction of this ray from a stream.
  276. /// </summary>
  277. /// <param name="reader">reader</param>
  278. public void Load(BinaryReader reader)
  279. {
  280. Position.Load(reader);
  281. Direction.Load(reader);
  282. }
  283. /// <summary>
  284. /// Save the position and direction of this ray into a stream.
  285. /// </summary>
  286. /// <param name="writer">writer</param>
  287. public void Save(BinaryWriter writer)
  288. {
  289. Position.Save(writer);
  290. Direction.Save(writer);
  291. }
  292. #endregion
  293. #region op_Equality (Operator)
  294. /// <summary>
  295. /// Operator for equality
  296. /// </summary>
  297. /// <param name="value1">Ray 1</param>
  298. /// <param name="value2">Ray 2</param>
  299. /// <returns>True if both rays are the same</returns>
  300. public static bool operator ==(Ray value1, Ray value2)
  301. {
  302. return (value1.Position == value2.Position) &&
  303. (value1.Direction == value2.Direction);
  304. }
  305. #endregion
  306. #region op_Inequality (Operator)
  307. /// <summary>
  308. /// Operator for inequality
  309. /// </summary>
  310. /// <param name="value1">Ray 1</param>
  311. /// <param name="value2">Ray 2</param>
  312. /// <returns>True if both rays are not the same</returns>
  313. public static bool operator !=(Ray value1, Ray value2)
  314. {
  315. return !(value1 == value2);
  316. }
  317. #endregion
  318. #region Equals (Public)
  319. /// <summary>
  320. /// Equals
  321. /// </summary>
  322. /// <param name="obj">Other ray</param>
  323. /// <returns>
  324. /// True if the other ray has the same values as this ray.
  325. /// </returns>
  326. public override bool Equals(object obj)
  327. {
  328. bool flag = false;
  329. if (obj is Ray)
  330. {
  331. flag = Equals((Ray)obj);
  332. }
  333. return flag;
  334. }
  335. #endregion
  336. #region GetHashCode (Public)
  337. /// <summary>
  338. /// Get hash code
  339. /// </summary>
  340. public override int GetHashCode()
  341. {
  342. return Position.GetHashCode() ^ Direction.GetHashCode();
  343. }
  344. #endregion
  345. #region ToString (Public)
  346. /// <summary>
  347. /// To string
  348. /// </summary>
  349. /// <returns>string</returns>
  350. public override string ToString()
  351. {
  352. return "(Position:" + Position + " Direction:" + Direction + ")";
  353. }
  354. #endregion
  355. /// <summary>
  356. /// Tests
  357. /// </summary>
  358. internal class RayTests
  359. {
  360. #region TestProperties (Static)
  361. /// <summary>
  362. /// Test properties
  363. /// </summary>
  364. [Test]
  365. public static void TestProperties()
  366. {
  367. var testRay = new Ray(Vector.Zero, new Vector(1f, 0.4f, 0f));
  368. Assert.Equal(Vector.Zero, testRay.Position);
  369. Assert.Equal(new Vector(1f, 0.4f, 0f), testRay.Direction);
  370. }
  371. #endregion
  372. #region TestEquals (Static)
  373. /// <summary>
  374. /// Test equals
  375. /// </summary>
  376. [Test]
  377. public static void TestEquals()
  378. {
  379. var testRay1 = new Ray(Vector.Zero, new Vector(1f, 0.4f, 0f));
  380. var testRay2 = new Ray(Vector.UnitY, new Vector(1f, 0.4f, 0f));
  381. Assert.True(testRay1.Equals(testRay1));
  382. Assert.False(testRay1.Equals(testRay2));
  383. }
  384. #endregion
  385. #region TestToString (Static)
  386. /// <summary>
  387. /// Test to string
  388. /// </summary>
  389. [Test]
  390. public static void TestToString()
  391. {
  392. var testRay = new Ray(Vector.Zero, new Vector(1f, 0.4f, 0f));
  393. Assert.Equal(
  394. "(Position:(0.0000, 0.0000, 0.0000) Direction:(1.0000, 0.4000, 0.0000))",
  395. testRay.ToString());
  396. }
  397. #endregion
  398. #region TestIntersectPlane
  399. /// <summary>
  400. /// Test for Ray.Intersects(Plane)
  401. /// </summary>
  402. [Test]
  403. public void TestIntersectPlane()
  404. {
  405. Vector point;
  406. var testRay1 = new Ray(Vector.UnitZ, new Vector(0.1f, 0.1f, -0.2f));
  407. var testRay2 = new Ray(Vector.UnitZ, new Vector(0.1f, 0.1f, 0.1f));
  408. var testPlane = new Plane(Vector.UnitZ, 0);
  409. bool IsIntersect = testRay1.Intersects(testPlane, out point);
  410. bool IsIntersectNot = testRay2.Intersects(testPlane, out point);
  411. Assert.Equal(IsIntersect, true);
  412. Assert.Equal(IsIntersectNot, false);
  413. }
  414. #endregion
  415. #region TestIntersectsBox
  416. /// <summary>
  417. /// Test Ray.Intersects(Box)
  418. /// </summary>
  419. [Test]
  420. public void TestIntersectsBox()
  421. {
  422. var testRay1 = new Ray(new Vector(0f, -10f, 10f),
  423. new Vector(0f, 10f, -10f));
  424. var testRay2 = new Ray(new Vector(0f, -10f, 10f), Vector.Zero);
  425. var testBox = new BoundingBox(
  426. new Vector(-0.5f, -0.5f, -0.5f),
  427. new Vector(0.5f, 0.5f, 0.5f));
  428. Assert.True(testRay1.Intersects(testBox));
  429. Assert.False(testRay2.Intersects(testBox));
  430. }
  431. #endregion
  432. #region TestIntersectSphere
  433. /// <summary>
  434. /// Test Ray.Intersects(Sphere)
  435. /// </summary>
  436. [Test]
  437. public void TestIntersectSphere()
  438. {
  439. var testRay1 = new Ray(Vector.Half,
  440. new Vector(0f, 10f, 10f));
  441. var testRay2 = new Ray(new Vector(0f, -10f, 10f), Vector.Zero);
  442. var sphere = new BoundingSphere(Vector.Half, 5.0f);
  443. Assert.True(testRay1.Intersects(sphere));
  444. Assert.False(testRay2.Intersects(sphere));
  445. }
  446. #endregion
  447. #region TestIntersectRay
  448. /// <summary>
  449. /// Test Ray.Intersects(Ray)
  450. /// </summary>
  451. [Test]
  452. public void TestIntersectRay()
  453. {
  454. Vector point1, point2;
  455. var one = new Ray(new Vector(-1, -1, 2), new Vector(-2, -1, 2));
  456. var two = new Ray(new Vector(7, 7, 0), new Vector(4, 4, 0));
  457. var three = new Ray(new Vector(6, 5, 0), new Vector(4, 7, 0));
  458. Assert.True(two.Intersects(three, out point1));
  459. Assert.False(one.Intersects(two, out point2));
  460. }
  461. #endregion
  462. }
  463. }
  464. }