MonoGame.Framework/Ray.cs
Rama develop · réplica de lectura del repositorio original.
// MIT License - Copyright (C) The Mono.Xna Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
namespace Microsoft.Xna.Framework
{
/// <summary>
/// Represents a ray with an origin and a direction in 3D space.
/// </summary>
[DataContract]
[DebuggerDisplay("{DebugDisplayString,nq}")]
public struct Ray : IEquatable<Ray>
{
#region Public Fields
/// <summary>
/// The direction of this <see cref="Ray"/>.
/// </summary>
[DataMember]
public Vector3 Direction;
/// <summary>
/// The origin of this <see cref="Ray"/>.
/// </summary>
[DataMember]
public Vector3 Position;
#endregion
#region Public Constructors
/// <summary>
/// Create a <see cref="Ray"/>.
/// </summary>
/// <param name="position">The origin of the <see cref="Ray"/>.</param>
/// <param name="direction">The direction of the <see cref="Ray"/>.</param>
public Ray(Vector3 position, Vector3 direction)
{
this.Position = position;
this.Direction = direction;
}
#endregion
#region Public Methods
/// <summary>
/// Check if the specified <see cref="Object"/> is equal to this <see cref="Ray"/>.
/// </summary>
/// <param name="obj">The <see cref="Object"/> to test for equality with this <see cref="Ray"/>.</param>
/// <returns>
/// <code>true</code> if the specified <see cref="Object"/> is equal to this <see cref="Ray"/>,
/// <code>false</code> if it is not.
/// </returns>
public override bool Equals(object obj)
{
return (obj is Ray) && this.Equals((Ray)obj);
}
/// <summary>
/// Check if the specified <see cref="Ray"/> is equal to this <see cref="Ray"/>.
/// </summary>
/// <param name="other">The <see cref="Ray"/> to test for equality with this <see cref="Ray"/>.</param>
/// <returns>
/// <code>true</code> if the specified <see cref="Ray"/> is equal to this <see cref="Ray"/>,
/// <code>false</code> if it is not.
/// </returns>
public bool Equals(Ray other)
{
return this.Position.Equals(other.Position) && this.Direction.Equals(other.Direction);
}
/// <summary>
/// Get a hash code for this <see cref="Ray"/>.
/// </summary>
/// <returns>A hash code for this <see cref="Ray"/>.</returns>
public override int GetHashCode()
{
return Position.GetHashCode() ^ Direction.GetHashCode();
}
// adapted from http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/
/// <summary>
/// Check if this <see cref="Ray"/> intersects the specified <see cref="BoundingBox"/>.
/// </summary>
/// <param name="box">The <see cref="BoundingBox"/> to test for intersection.</param>
/// <returns>
/// The distance along the ray of the intersection or <code>null</code> if this
/// <see cref="Ray"/> does not intersect the <see cref="BoundingBox"/>.
/// </returns>
public float? Intersects(BoundingBox box)
{
const float Epsilon = 1e-6f;
float? tMin = null, tMax = null;
if (Math.Abs(Direction.X) < Epsilon)
{
if (Position.X < box.Min.X || Position.X > box.Max.X)
return null;
}
else
{
tMin = (box.Min.X - Position.X) / Direction.X;
tMax = (box.Max.X - Position.X) / Direction.X;
if (tMin > tMax)
{
var temp = tMin;
tMin = tMax;
tMax = temp;
}
}
if (Math.Abs(Direction.Y) < Epsilon)
{
if (Position.Y < box.Min.Y || Position.Y > box.Max.Y)
return null;
}
else
{
var tMinY = (box.Min.Y - Position.Y) / Direction.Y;
var tMaxY = (box.Max.Y - Position.Y) / Direction.Y;
if (tMinY > tMaxY)
{
var temp = tMinY;
tMinY = tMaxY;
tMaxY = temp;
}
if ((tMin.HasValue && tMin > tMaxY) || (tMax.HasValue && tMinY > tMax))
return null;
if (!tMin.HasValue || tMinY > tMin) tMin = tMinY;
if (!tMax.HasValue || tMaxY < tMax) tMax = tMaxY;
}
if (Math.Abs(Direction.Z) < Epsilon)
{
if (Position.Z < box.Min.Z || Position.Z > box.Max.Z)
return null;
}
else
{
var tMinZ = (box.Min.Z - Position.Z) / Direction.Z;
var tMaxZ = (box.Max.Z - Position.Z) / Direction.Z;
if (tMinZ > tMaxZ)
{
var temp = tMinZ;
tMinZ = tMaxZ;
tMaxZ = temp;
}
if ((tMin.HasValue && tMin > tMaxZ) || (tMax.HasValue && tMinZ > tMax))
return null;
if (!tMin.HasValue || tMinZ > tMin) tMin = tMinZ;
if (!tMax.HasValue || tMaxZ < tMax) tMax = tMaxZ;
}
// having a positive tMax and a negative tMin means the ray is inside the box
// we expect the intesection distance to be 0 in that case
if ((tMin.HasValue && tMin < 0) && tMax > 0) return 0;
// a negative tMin means that the intersection point is behind the ray's origin
// we discard these as not hitting the AABB
if (tMin < 0) return null;
return tMin;
}
/// <summary>
/// Check if this <see cref="Ray"/> intersects the specified <see cref="BoundingBox"/>.
/// </summary>
/// <param name="box">The <see cref="BoundingBox"/> to test for intersection.</param>
/// <param name="result">
/// The distance along the ray of the intersection or <code>null</code> if this
/// <see cref="Ray"/> does not intersect the <see cref="BoundingBox"/>.
/// </param>
public void Intersects(ref BoundingBox box, out float? result)
{
result = Intersects(box);
}
/*
public float? Intersects(BoundingFrustum frustum)
{
if (frustum == null)
{
throw new ArgumentNullException("frustum");
}
return frustum.Intersects(this);
}
*/
/// <summary>
/// Check if this <see cref="Ray"/> intersects the specified <see cref="BoundingSphere"/>.
/// </summary>
/// <param name="sphere">The <see cref="BoundingBox"/> to test for intersection.</param>
/// <returns>
/// The distance along the ray of the intersection or <code>null</code> if this
/// <see cref="Ray"/> does not intersect the <see cref="BoundingSphere"/>.
/// </returns>
public float? Intersects(BoundingSphere sphere)
{
float? result;
Intersects(ref sphere, out result);
return result;
}
/// <summary>
/// Check if this <see cref="Ray"/> intersects the specified <see cref="Plane"/>.
/// </summary>
/// <param name="plane">The <see cref="Plane"/> to test for intersection.</param>
/// <returns>
/// The distance along the ray of the intersection or <code>null</code> if this
/// <see cref="Ray"/> does not intersect the <see cref="Plane"/>.
/// </returns>
public float? Intersects(Plane plane)
{
float? result;
Intersects(ref plane, out result);
return result;
}
/// <summary>
/// Check if this <see cref="Ray"/> intersects the specified <see cref="Plane"/>.
/// </summary>
/// <param name="plane">The <see cref="Plane"/> to test for intersection.</param>
/// <param name="result">
/// The distance along the ray of the intersection or <code>null</code> if this
/// <see cref="Ray"/> does not intersect the <see cref="Plane"/>.
/// </param>
public void Intersects(ref Plane plane, out float? result)
{
var den = Vector3.Dot(Direction, plane.Normal);
if (Math.Abs(den) < 0.00001f)
{
result = null;
return;
}
result = (-plane.D - Vector3.Dot(plane.Normal, Position)) / den;
if (result < 0.0f)
{
if (result < -0.00001f)
{
result = null;
return;
}
result = 0.0f;
}
}
/// <summary>
/// Check if this <see cref="Ray"/> intersects the specified <see cref="BoundingSphere"/>.
/// </summary>
/// <param name="sphere">The <see cref="BoundingBox"/> to test for intersection.</param>
/// <param name="result">
/// The distance along the ray of the intersection or <code>null</code> if this
/// <see cref="Ray"/> does not intersect the <see cref="BoundingSphere"/>.
/// </param>
public void Intersects(ref BoundingSphere sphere, out float? result)
{
// Find the vector between where the ray starts the the sphere's centre
Vector3 difference = sphere.Center - this.Position;
float differenceLengthSquared = difference.LengthSquared();
float sphereRadiusSquared = sphere.Radius * sphere.Radius;
float distanceAlongRay;
// If the distance between the ray start and the sphere's centre is less than
// the radius of the sphere, it means we've intersected. N.B. checking the LengthSquared is faster.
if (differenceLengthSquared < sphereRadiusSquared)
{
result = 0.0f;
return;
}
Vector3.Dot(ref this.Direction, ref difference, out distanceAlongRay);
// If the ray is pointing away from the sphere then we don't ever intersect
if (distanceAlongRay < 0)
{
result = null;
return;
}
// Next we kinda use Pythagoras to check if we are within the bounds of the sphere
// if x = radius of sphere
// if y = distance between ray position and sphere centre
// if z = the distance we've travelled along the ray
// if x^2 + z^2 - y^2 < 0, we do not intersect
float dist = sphereRadiusSquared + distanceAlongRay * distanceAlongRay - differenceLengthSquared;
result = (dist < 0) ? null : distanceAlongRay - (float?)MathF.Sqrt(dist);
}
/// <summary>
/// Check if two rays are not equal.
/// </summary>
/// <param name="a">A ray to check for inequality.</param>
/// <param name="b">A ray to check for inequality.</param>
/// <returns><code>true</code> if the two rays are not equal, <code>false</code> if they are.</returns>
public static bool operator !=(Ray a, Ray b)
{
return !a.Equals(b);
}
/// <summary>
/// Check if two rays are equal.
/// </summary>
/// <param name="a">A ray to check for equality.</param>
/// <param name="b">A ray to check for equality.</param>
/// <returns><code>true</code> if the two rays are equals, <code>false</code> if they are not.</returns>
public static bool operator ==(Ray a, Ray b)
{
return a.Equals(b);
}
internal string DebugDisplayString
{
get
{
return string.Concat(
"Pos( ", this.Position.DebugDisplayString, " ) \r\n",
"Dir( ", this.Direction.DebugDisplayString, " )"
);
}
}
/// <summary>
/// Get a <see cref="String"/> representation of this <see cref="Ray"/>.
/// </summary>
/// <returns>A <see cref="String"/> representation of this <see cref="Ray"/>.</returns>
public override string ToString()
{
return "{{Position:" + Position.ToString() + " Direction:" + Direction.ToString() + "}}";
}
/// <summary>
/// Deconstruction method for <see cref="Ray"/>.
/// </summary>
/// <param name="position">Receives the start position of the ray.</param>
/// <param name="direction">Receives the direction of the ray.</param>
public void Deconstruct(out Vector3 position, out Vector3 direction)
{
position = Position;
direction = Direction;
}
#endregion
}
}