#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