/*
   Copyright (C) 2001-2006, William Joseph.
   All Rights Reserved.

   This file is part of GtkRadiant.

   GtkRadiant is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   GtkRadiant 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
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#if !defined( INCLUDED_MATH_LINE_H )
#define INCLUDED_MATH_LINE_H

/// \file
/// \brief Line data types and related operations.

#include "math/vector.h"
#include "math/plane.h"

/// \brief A line segment defined by a start point and and end point.
class Line
{
public:
Vector3 start, end;

Line(){
}
Line( const Vector3& start_, const Vector3& end_ ) : start( start_ ), end( end_ ){
}
};

inline Vector3 line_closest_point( const Line& line, const Vector3& point ){
	Vector3 v = line.end - line.start;
	Vector3 w = point - line.start;

	double c1 = vector3_dot( w,v );
	if ( c1 <= 0 ) {
		return line.start;
	}

	double c2 = vector3_dot( v,v );
	if ( c2 <= c1 ) {
		return line.end;
	}

	return Vector3( line.start + v * ( c1 / c2 ) );
}


class Segment
{
public:
Vector3 origin, extents;

Segment(){
}
Segment( const Vector3& origin_, const Vector3& extents_ ) :
	origin( origin_ ), extents( extents_ ){
}
};


inline Segment segment_for_startend( const Vector3& start, const Vector3& end ){
	Segment segment;
	segment.origin = vector3_mid( start, end );
	segment.extents = vector3_subtracted( end, segment.origin );
	return segment;
}

inline unsigned int segment_classify_plane( const Segment& segment, const Plane3& plane ){
	double distance_origin = vector3_dot( plane.normal(), segment.origin ) + plane.dist();

	if ( fabs( distance_origin ) < fabs( vector3_dot( plane.normal(), segment.extents ) ) ) {
		return 1; // partially inside
	}
	else if ( distance_origin < 0 ) {
		return 2; // totally inside
	}
	return 0; // totally outside
}


class Ray
{
public:
Vector3 origin, direction;

Ray(){
}
Ray( const Vector3& origin_, const Vector3& direction_ ) :
	origin( origin_ ), direction( direction_ ){
}
};

inline Ray ray_for_points( const Vector3& origin, const Vector3& p2 ){
	return Ray( origin, vector3_normalised( vector3_subtracted( p2, origin ) ) );
}

inline void ray_transform( Ray& ray, const Matrix4& matrix ){
	matrix4_transform_point( matrix, ray.origin );
	matrix4_transform_direction( matrix, ray.direction );
}

// closest-point-on-line
inline double ray_squared_distance_to_point( const Ray& ray, const Vector3& point ){
	return vector3_length_squared(
			   vector3_subtracted(
				   point,
				   vector3_added(
					   ray.origin,
					   vector3_scaled(
						   ray.direction,
						   vector3_dot(
							   vector3_subtracted( point, ray.origin ),
							   ray.direction
							   )
						   )
					   )
				   )
			   );
}

inline double ray_distance_to_plane( const Ray& ray, const Plane3& plane ){
	return -( vector3_dot( plane.normal(), ray.origin ) - plane.dist() ) / vector3_dot( ray.direction, plane.normal() );
}

#endif
