#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Nuclex.Geometry.Volumes;
using Nuclex.Geometry.Areas;
namespace Nuclex.Geometry.Lines {
/// A Ray from some origin to infinity
#if !NO_SERIALIZATION
[Serializable]
#endif
public class Ray3 : ILine3 {
/// Initializes a new ray
[DebuggerStepThrough]
public Ray3() {
Origin = Vector3.Zero;
Direction = Vector3.Right;
}
/// Constructs a new line as copy of an existing instance
/// Existing instance to copy
[DebuggerStepThrough]
public Ray3(Ray3 other) {
Origin = other.Origin;
Direction = other.Direction;
}
/// Initializes a new ray
/// Location from which the ray originates
/// Direction into which the ray goes
[DebuggerStepThrough]
public Ray3(Vector3 origin, Vector3 direction) {
Origin = origin;
Direction = direction;
// Make sure the direction is normalized
Direction = Vector3.Normalize(Direction);
}
/// Determines the closest point on the ray to the specified location
/// Random location to which the closest point is determined
/// The closest point within the ray
public Vector3 ClosestPointTo(Vector3 location) {
// Calculate the position of an orthogonal vector on the ray pointing
// towards the location the caller specified
float position = Vector3.Dot(location - Origin, Direction);
// Clip the position in the negative direction so it can't go before the ray's origin
return Origin + Direction * Math.Max(position, 0.0f);
}
/// Checks two ray instances for inequality
/// First instance to be compared
/// Second instance fo tbe compared
/// True if the instances differ or exactly one reference is set to null
public static bool operator !=(Ray3 first, Ray3 second) {
return !(first == second);
}
/// Checks two ray instances for equality
/// First instance to be compared
/// Second instance fo tbe compared
/// True if both instances are equal or both references are null
public static bool operator ==(Ray3 first, Ray3 second) {
if(ReferenceEquals(first, null))
return ReferenceEquals(second, null);
return first.Equals(second);
}
/// Checks whether another instance is equal to this instance
/// Other instance to compare to this instance
/// True if the other instance is equal to this instance
public override bool Equals(object other) {
return Equals(other as Ray3);
}
/// Checks whether another instance is equal to this instance
/// Other instance to compare to this instance
/// True if the other instance is equal to this instance
public virtual bool Equals(Ray3 other) {
if(other == null)
return false;
else
return (this.Origin == other.Origin) && (this.Direction == other.Direction);
}
/// Obtains a hash code of this instance
/// The hash code of the instance
public override int GetHashCode() {
unchecked { return Origin.GetHashCode() + Direction.GetHashCode(); }
}
/// Determines where the range clips a sphere
/// Sphere that will be checked for intersection
/// The times at which the range enters or leaves the volume
public LineContacts FindContacts(Sphere3 sphere) {
return Collisions.Ray3Sphere3Collider.FindContacts(
Origin, Direction, sphere.Center, sphere.Radius
);
}
/// Determines where the range clips an axis aligned box
/// Box that will be checked for intersection
/// The times at which the range enters or leaves the volume
public LineContacts FindContacts(AxisAlignedBox3 box) {
return Collisions.Ray3Aabb3Collider.FindContacts(
Origin - box.Center, Direction, box.Extents
);
}
/// Determines where the range clips a box
/// Box that will be checked for intersection
/// The times at which the range enters or leaves the volume
public LineContacts FindContacts(Box3 box) {
// Convert line to box coordinates
Vector3 offset = Origin - box.Center;
Vector3 relativePosition = new Vector3(
Vector3.Dot(offset, box.Transform.Right),
Vector3.Dot(offset, box.Transform.Up),
Vector3.Dot(offset, box.Transform.Forward)
);
Vector3 relativeDirection = new Vector3(
Vector3.Dot(Direction, box.Transform.Right),
Vector3.Dot(Direction, box.Transform.Up),
Vector3.Dot(Direction, box.Transform.Forward)
);
return Collisions.Ray3Aabb3Collider.FindContacts(
relativePosition, relativeDirection, box.Dimensions / 2.0f
);
}
/// Determines where the range clips a plane
/// Plane that will be checked for intersection
/// The times at which the range touches the plane, if at all
public LineContacts FindContacts(Plane3 plane) {
LineContacts contacts = Collisions.Line3Plane3Collider.FindContacts(
Origin, Direction, plane.Offset, plane.Normal
);
limitContactToRay(ref contacts);
return contacts;
}
/// Determines where the range clips a triangle
/// Triangle that will be checked for intersection
/// The times at which the range touches the triangle, if at all
public LineContacts FindContacts(Triangle3 triangle) {
LineContacts contacts = Collisions.Line3Triangle3Collider.FindContacts(
Origin, Direction, triangle.A, triangle.B, triangle.C
);
limitContactToRay(ref contacts);
return contacts;
}
///
/// Limits the contact positions found in a line to the subsection of
/// the line covered by the line segment
///
/// Contacts that will be limited to the line segment
private static void limitContactToRay(ref LineContacts contacts) {
if(!float.IsNaN(contacts.EntryTime)) {
bool contactLiesOutsideOfRay =
(contacts.ExitTime < 0.0f);
if(contactLiesOutsideOfRay) {
contacts.EntryTime = float.NaN;
contacts.ExitTime = float.NaN;
} else {
if(contacts.EntryTime < 0.0f) {
contacts.EntryTime = 0.0f;
}
}
}
}
/// Filters the contacts of a line contact query for the ray
/// Contacts that will be filtered
/// The filtered contact list
private static float[] filterContacts(float[] lineContacts) {
if(lineContacts != null) {
// If the leaving contact lies before the beginning of the ray,
// there is no intersection
if(lineContacts[1] < 0.0f)
return null;
// Otherwise, if the volume was entered before the beginning of the ray,
// we need to adjust the first contact time since the ray begins within the volume
if(lineContacts[0] < 0.0f)
lineContacts[0] = 0.0f;
}
return lineContacts;
}
/// Origin of the ray
public Vector3 Origin;
/// Normalized direction into which the ray goes
public Vector3 Direction;
}
} // namespace Nuclex.Geometry.Ranges