/*===================================================================

  
  digital liberation front 2001
  
  _______    ______      _______
 /______/\  |______|    /\______\  
|       \ \ |      |   / /       |    
|	      \| |      |  |/         |  
|_____    \ |      |_ /    ______|       
 ____|    | |      |_||    |_____          
     |____| |________||____|                
           



Code by Nicholas Chapman[/ Ono-Sendai]
nickamy@paradise.net.nz

You may use this code for any non-commercial project,
as long as you do not remove this description.

You may not use this code for any commercial project.
====================================================================*/
#ifndef __PLANE_H__
#define __PLANE_H__


#include "vec3.h"
#include "mathstypes.h"
class CoordFrame;

class Plane
{
public:
	inline Plane();
	inline Plane(const Vec3& origin, const Vec3& normal);
	inline Plane(const Vec3& normal, float dist_from_origin);
	inline Plane(const Plane& rhs);
	inline ~Plane();

//	Plane& operator = (const Plane& rhs);

	inline void set(const Vec3& normal, float dist_from_origin);
	inline void set(const Vec3& origin, const Vec3& normal);

	inline void setUnnormalised(const Vec3& origin, const Vec3& nonunit_normal);

	


	inline bool pointOnFrontSide(const Vec3& point) const;

	inline bool pointOnBackSide(const Vec3& point) const;

	inline bool isPointOnPlane(const Vec3& point) const;

	inline const Vec3 calcOrigin() const;//SLOW!

	inline const Vec3 projOnPlane(const Vec3& vec) const;

	inline const Vec3 compNormalToPlane(const Vec3& vec) const;

	inline float signedDistToPoint(const Vec3& p) const;

	inline const Vec3 closestPointOnPlane(const Vec3& p) const;

	inline float getDist() const { return d; }
	inline float getD() const { return d; }

	inline const Vec3& getNormal() const { return normal; }

	//returns fraction of ray travelled. Will be in range [0, 1] if ray hit
	inline float finiteRayIntersect(const Vec3& raystart, const Vec3& rayend) const;

	inline float rayIntersect(const Vec3& raystart, const Vec3 ray_unitdir) const;

	const Plane transformToLocal(const CoordFrame& coordframe) const;

	inline bool isSpherePartiallyOnFrontSide(const Vec3& sphere_center, float radius) const;
	inline bool isSphereTotallyOnFrontSide(const Vec3& sphere_center, float radius) const;

	inline bool isPlaneOnFrontSide(const Plane& p) const;

private:
	float d;
	Vec3 normal;
};


Plane::Plane()
{}

Plane::Plane(const Vec3& origin, const Vec3& normal_)
{
	normal = normal_;

	assert( epsEqual(normal.length(), 1.0f) );

	d = dot(origin, normal);
}

Plane::Plane(const Vec3& normal_, float dist_from_origin)
{
	normal = normal_;

	assert( epsEqual(normal.length(), 1.0f) );

	d = dist_from_origin;
}

Plane::Plane(const Plane& rhs)
{
	normal = rhs.normal;
	d = rhs.d;
}

Plane::~Plane()
{}

/*Plane& Plane::operator = (const Plane& rhs)
{
	normal = rhs.normal;
	d = rhs.d;

	return *this;
}*/
void Plane::set(const Vec3& normal_, float dist_from_origin)
{
	normal = normal_;

	assert( epsEqual(normal.length(), 1.0f) );

	d = dist_from_origin;
}



void Plane::set(const Vec3& origin, const Vec3& normal_)
{
	normal = normal_;

	assert( epsEqual(normal.length(), 1.0f) );

	d = dot(origin, normal);
}

void Plane::setUnnormalised(const Vec3& origin, const Vec3& nonunit_normal)
{
	normal = nonunit_normal;

	normal.normalise();

	d = dot(origin, normal);
}

bool Plane::pointOnFrontSide(const Vec3& point) const
{
	return (dot(point, normal) >= d);
}

bool Plane::pointOnBackSide(const Vec3& point) const
{
	return (dot(point, normal) < d);
}

bool Plane::isPointOnPlane(const Vec3& point) const 
{
	if(fabs(signedDistToPoint(point)) < 0.00001f)
		return true;
	else
		return false;
}


const Vec3 Plane::calcOrigin() const
{
	return normal * d;
}

const Vec3 Plane::projOnPlane(const Vec3& vec) const
{
	return vec - compNormalToPlane(vec);
}

const Vec3 Plane::compNormalToPlane(const Vec3& vec) const
{
	return dot(vec, normal) * normal;
}

float Plane::signedDistToPoint(const Vec3& p) const
{
	return dot(p, normal) - d;
}

const Vec3 Plane::closestPointOnPlane(const Vec3& p) const
{
	return p - (signedDistToPoint(p) * normal);
}

float Plane::finiteRayIntersect(const Vec3& raystart, const Vec3& rayend) const
{
	const float raystart_dot_n = dot(raystart, normal);

	const float rayend_dot_n = dot(rayend, normal);

	const float denom = rayend_dot_n - raystart_dot_n;

	if(denom == 0.0f)
		return -1.0f;

	return (d - raystart_dot_n) / denom;

}

float Plane::rayIntersect(const Vec3& raystart, const Vec3 ray_unitdir) const
{

	const float start_to_plane_dist = signedDistToPoint(raystart);

	return start_to_plane_dist / -dot(ray_unitdir, normal);

	//NOTE: deal with div by 0?

}

/*
const Plane Plane::transformToLocal(const CoordFrame& coordframe) const
{
	const Vec3 pointonplane = closestPointOnPlane(Vec3(0,0,0));

	const Vec3& transformed_point = coordframe.transformPointToLocal(pointonplane);

	const Vec3& transformed_normal = coordframe.transformVecToLocal(getNormal());

	return Plane(transformed_point, transformed_normal);

	//NOTE: check this is right
}*/

bool Plane::isSpherePartiallyOnFrontSide(const Vec3& sphere_center, float radius) const
{
	return signedDistToPoint(sphere_center) <= radius * -1.0f;
}

bool Plane::isSphereTotallyOnFrontSide(const Vec3& sphere_center, float radius) const
{
	return signedDistToPoint(sphere_center) >= radius;
}

bool Plane::isPlaneOnFrontSide(const Plane& p) const
{
	return epsEqual(p.getNormal(), this->getNormal())
				&& p.getDist() > this->getDist();
}



#endif //__PLANE_H__
