#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 Microsoft.Xna.Framework; using Nuclex.Geometry.Volumes; using Nuclex.Geometry.Areas; namespace Nuclex.Geometry.Lines { /// Segment of a line (Typical line with starting and ending location) #if !NO_SERIALIZATION [Serializable] #endif public class Segment3 : ILine3 { /// Initializes a new line segment [System.Diagnostics.DebuggerStepThrough] public Segment3() { Start = Vector3.Zero; End = Vector3.Zero; } /// Constructs a new line as copy of an existing instance /// Existing instance to copy [System.Diagnostics.DebuggerStepThrough] public Segment3(Segment3 other) { Start = other.Start; End = other.End; } /// Initializes a new line segment /// Starting location of the line segment /// Ending location of the line segment [System.Diagnostics.DebuggerStepThrough] public Segment3(Vector3 start, Vector3 end) { this.Start = start; this.End = end; } /// Locates the nearest point on the line to some arbitrary location /// Location to which the closest point is determined /// The closest point on the line to the specified location public Vector3 ClosestPointTo(Vector3 location) { // If the line's start and end location are identical we would be dividing by // zero further down, so we'll handle this case seperately if(this.Start == this.End) return Start; // Calculate the position of an orthogonal vector on the line pointing // towards the location that the caller specified Vector3 direction = Vector3.Normalize(End - Start); float position = Vector3.Dot(location - Start, direction); // Clip the position onto the length of the line segment return this.Start + direction * Math.Min(Math.Max(position, 0.0f), 1.0f); } /// The length of the line public float Length { get { return (this.End - this.Start).Length(); } } /// Checks two segment 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 !=(Segment3 first, Segment3 second) { return !(first == second); } /// Checks two segment 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 ==(Segment3 first, Segment3 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 Segment3); } /// 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(Segment3 other) { if(ReferenceEquals(other, null)) return false; else return (this.Start == other.Start) && (this.End == other.End); } /// Obtains a hash code of this instance /// The hash code of the instance public override int GetHashCode() { unchecked { return Start.GetHashCode() + End.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) { throw new NotImplementedException(); /* return filterContacts( Collisions.Line3Sphere3Collider.FindContacts( this.Start, this.End - this.Start, 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) { throw new NotImplementedException(); /* return filterContacts( Collisions.Line3Aabb3Collider.FindContacts( this.Start - box.Center, this.End - this.Start, box.Dimensions / 2.0f ) ); */ } /// 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) { throw new NotImplementedException(); /* // Convert line to box coordinates Vector3 offset = Start - 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 direction = End - Start; Vector3 relativeDirection = new Vector3( Vector3.Dot(direction, box.Transform.Right), Vector3.Dot(direction, box.Transform.Up), Vector3.Dot(direction, box.Transform.Forward) ); return filterContacts( Collisions.Line3Aabb3Collider.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( Start, End - Start, plane.Offset, plane.Normal ); limitContactToLineSegment(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( Start, End - Start, triangle.A, triangle.B, triangle.C ); limitContactToLineSegment(ref contacts); return contacts; } /// 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 /// /// Taken from the article "Simple Intersection Tests for Games" on /// Gamasutra by Gomez et al. /// public bool Intersects(AxisAlignedBox3 box) { Vector3 lineDirection = (End - Start); float lineHalfLength = lineDirection.Length(); lineDirection /= lineHalfLength; lineHalfLength /= 2.0f; // ALGORITHM: Use the separating axis theorem to see if the line segment and // the box overlap. A line segment is a degenerate OBB. Vector3 distance = box.Center - (Start + End) / 2.0f; float r; // Do any of the principal axes form a separating axis? Vector3 scaledDimensions = new Vector3( box.Dimensions.X * lineHalfLength * Math.Abs(lineDirection.X), box.Dimensions.Y * lineHalfLength * Math.Abs(lineDirection.Y), box.Dimensions.Z * lineHalfLength * Math.Abs(lineDirection.Z) ); if(Math.Abs(distance.X) > scaledDimensions.X) return false; if(Math.Abs(distance.Y) > scaledDimensions.Y) return false; if(Math.Abs(distance.Z) > scaledDimensions.Z) return false; // NOTE: Since the separating axis is perpendicular to the line in these // last four cases, the line does not contribute to the projection. //l.cross(x-axis)? r = box.Dimensions.Y * Math.Abs(lineDirection.Z) + box.Dimensions.Z * Math.Abs(lineDirection.Y); if(Math.Abs(distance.Y * lineDirection.Z - distance.Z * lineDirection.Y) > r) return false; //l.cross(y-axis)? r = box.Dimensions.X * Math.Abs(lineDirection.Z) + box.Dimensions.Z * Math.Abs(lineDirection.X); if(Math.Abs(distance.Z * lineDirection.X - distance.X * lineDirection.Z) > r) return false; //l.cross(z-axis)? r = box.Dimensions.X * Math.Abs(lineDirection.Y) + box.Dimensions.Y * Math.Abs(lineDirection.X); if(Math.Abs(distance.X * lineDirection.Y - distance.Y * lineDirection.X) > r) return false; return true; } /// /// 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 limitContactToLineSegment(ref LineContacts contacts) { if(!float.IsNaN(contacts.EntryTime)) { bool contactLiesOutsideOfSegment = (contacts.EntryTime > 1.0f) || (contacts.ExitTime < 0.0f); if(contactLiesOutsideOfSegment) { contacts.EntryTime = float.NaN; contacts.ExitTime = float.NaN; } else { if(contacts.EntryTime < 0.0f) { contacts.EntryTime = 0.0f; } if(contacts.ExitTime > 1.0f) { contacts.ExitTime = 1.0f; } } } } /// Filters the contacts of a line contact query for the segment /// Contacts that will be filtered /// The filtered contact list private float[] filterContacts(float[] lineContacts) { // TODO: This is just another variant of limitContactToLineSegment() // Remove when switching everything to the ContactPoints structure if(lineContacts != null) { // If the leaving contact lies before the beginning of the segment, // there is no intersection if(lineContacts[1] < 0.0f) return null; // If the entering contact lies after the end of the segment, // there is no intersection if(lineContacts[0] > 1.0f) return null; // Otherwise, if the volume was entered before the beginning of the segment, // we need to adjust the first contact time since the segment begins within the volume if(lineContacts[0] < 0.0f) lineContacts[0] = 0.0f; // If the volume was left after the end of the segment, the segment ends // inside the volume and the exit time needs to be adjusted if(lineContacts[1] > 1.0f) lineContacts[1] = 1.0f; } return lineContacts; } /// The starting point of the line segment public Vector3 Start; /// The ending point of the line segment public Vector3 End; } } // namespace Nuclex.Geometry.Ranges