//------------------------------------------------------------------------------
// Lamp : Open source game middleware
// Copyright (C) 2004  Junpei Ohtani ( Email : junpee@users.sourceforge.jp )
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 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 GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//------------------------------------------------------------------------------

/** @file
 * 球交差実装
 * @author Junpee
 */

#include "LampBasic.h"
#include "Geometry/Intersection/SphereIntersection.h"
#include "Geometry/System/Intersection.h"

#include "Geometry/Primitive/Triangle.h"

namespace Lamp{

//------------------------------------------------------------------------------
// 点
//------------------------------------------------------------------------------
// 点交差
bool SphereIntersection::intersect(const Sphere& sphere, const Vector3& point){
	Assert(false);
	return false;
}
//------------------------------------------------------------------------------
// 球
//------------------------------------------------------------------------------
// 球交差
bool SphereIntersection::intersect(
	const Sphere& sphere0, const Sphere& sphere1){
	// 半径と距離を比較する
	float radius = sphere0.getRadius() + sphere1.getRadius();
	Vector3 direction = sphere1.getCenter() - sphere0.getCenter();
	if(direction.getSquaredLength() > (radius * radius)){ return false; }
	return true;
}
//------------------------------------------------------------------------------
// 球交差
bool SphereIntersection::intersect(Intersection* intersection,
	const Sphere& sphere0, const Sphere& sphere1){
	const Vector3& center0 = sphere0.getCenter();
	float radius0 = sphere0.getRadius();
	const Vector3& center1 = sphere1.getCenter();
	float radius1 = sphere1.getRadius();
	// 半径と距離を比較する
	float totalRadius = radius0 + radius1;
	Vector3 distance = center1 - center0;
	float squaredLength = distance.getSquaredLength();
	if(squaredLength > (totalRadius * totalRadius)){ return false; }
	// 交差位置と反射の設定
	if(distance.isZero()){ distance = Vector3::unitY; }
	else{ distance.normalize(); }
	intersection->set(center0 + (distance * radius0),
		distance * (totalRadius - Math::sqrt(squaredLength)));
	return true;
}
//------------------------------------------------------------------------------
// 三角
//------------------------------------------------------------------------------
// 三角交差
bool SphereIntersection::intersect(
	const Sphere& sphere, const Triangle& triangle){
	const Vector3& center = sphere.getCenter();
	float radius = sphere.getRadius();
	const Vector3& vertex0 = triangle.getVertex(0);
	const Vector3& vertex1 = triangle.getVertex(1);
	const Vector3& vertex2 = triangle.getVertex(2);

	// 面法線を求める
	Vector3 edge0 = vertex1 - vertex0;
	Vector3 edge1 = vertex2 - vertex1;
	Vector3 normal = edge0.crossProduct(edge1).normalize();

	// 平面と球の距離を求める
	float constant = -normal.dotProduct(vertex0);
	float distance = normal.dotProduct(center) + constant;

	// 球の中心が面に接しているか、負の方向にある場合は当たらない。
	if(distance <= 0.f){ return false; }
	// 半径以上はなれていれば当たらない
	if(distance > radius){ return false; }

	// 球が三角内部に存在するか
	Vector3 edge2 = vertex0 - vertex2;
	bool outside = false;
	Vector3 distance0 = center - vertex0;
	outside |= (normal.dotProduct(edge0.crossProduct(distance0)) < 0.f);
	Vector3 distance1 = center - vertex1;
	outside |= (normal.dotProduct(edge1.crossProduct(distance1)) < 0.f);
	Vector3 distance2 = center - vertex2;
	outside |= (normal.dotProduct(edge2.crossProduct(distance2)) < 0.f);
	// 三角の内側なので交差する
	if(!outside){
		// 交差位置は球中心から面法線の逆方向にdistance伸ばしたところ
		return true;
	}

	// セグメント0との比較
	float squaredRadius = radius * radius;
	float t0 = distance0.dotProduct(edge0);
	if(t0 > 0.f){
		float edgeSquaredLength = edge0.getSquaredLength();
		if(t0 >= edgeSquaredLength){
			// t0 = 1.f; // 接触点を求める場合は設定する
			distance0 -= edge0;
		}else{
			t0 /= edgeSquaredLength;
			distance0 -= t0 * edge0;
		}
	}
	// セグメントとの距離が半径以下
	if(distance0.getSquaredLength() <= squaredRadius){
		// 交差位置はvertex0 + t0 * edge0
		return true;
	}

	// セグメント1との比較
	float t1 = distance1.dotProduct(edge1);
	if(t1 > 0.f){
		float edgeSquaredLength = edge1.getSquaredLength();
		if(t1 >= edgeSquaredLength){
			// t1 = 1.f; // 接触点を求める場合は設定する
			distance1 -= edge1;
		}else{
			t1 /= edgeSquaredLength;
			distance1 -= t1 * edge1;
		}
	}
	// セグメントとの距離が半径以下
	if(distance1.getSquaredLength() <= squaredRadius){
		// 交差位置はvertex1 + t1 * edge1
		return true;
	}

	// セグメント2との比較
	float t2 = distance2.dotProduct(edge2);
	if(t2 > 0.f){
		float edgeSquaredLength = edge2.getSquaredLength();
		if(t2 >= edgeSquaredLength){
			// t2 = 1.f; // 接触点を求める場合は設定する
			distance2 -= edge2;
		}else{
			t2 /= edgeSquaredLength;
			distance2 -= t2 * edge2;
		}
	}
	// セグメントとの距離が半径以下
	if(distance2.getSquaredLength() <= squaredRadius){
		// 交差位置はvertex2 + t2 * edge2
		return true;
	}

	return false;
}
//------------------------------------------------------------------------------
// 三角交差
bool SphereIntersection::intersect(Intersection* intersection,
	const Sphere& sphere, const Triangle& triangle){
	const Vector3& center = sphere.getCenter();
	float radius = sphere.getRadius();
	const Vector3& vertex0 = triangle.getVertex(0);
	const Vector3& vertex1 = triangle.getVertex(1);
	const Vector3& vertex2 = triangle.getVertex(2);

	// 面法線を求める
	Vector3 edge0 = vertex1 - vertex0;
	Vector3 edge1 = vertex2 - vertex1;
	Vector3 normal = edge0.crossProduct(edge1).normalize();

	// 平面と球の距離を求める
	float constant = -normal.dotProduct(vertex0);
	float distance = normal.dotProduct(center) + constant;

	// 球の中心が面に接しているか、負の方向にある場合は当たらない。
	if(distance <= 0.f){ return false; }
	// 半径以上はなれていれば当たらない
	if(distance > radius){ return false; }

	// 球が三角内部に存在するか
	Vector3 edge2 = vertex0 - vertex2;
	bool outside = false;
	Vector3 distance0 = center - vertex0;
	outside |= (normal.dotProduct(edge0.crossProduct(distance0)) < 0.f);
	Vector3 distance1 = center - vertex1;
	outside |= (normal.dotProduct(edge1.crossProduct(distance1)) < 0.f);
	Vector3 distance2 = center - vertex2;
	outside |= (normal.dotProduct(edge2.crossProduct(distance2)) < 0.f);
	// 三角の内側なので交差する
	if(!outside){
		// 交差位置は球中心から面方向へ半径分移動した地点
		// 反射は面の逆方向で(半径-距離)の長さ
		Vector3 inverseNormal(-normal);
		intersection->set(center + inverseNormal * radius,
			inverseNormal * (radius - distance));
		return true;
	}

	// セグメント0との比較
	float squaredRadius = radius * radius;
	float t0 = distance0.dotProduct(edge0);
	if(t0 > 0.f){
		float edgeSquaredLength = edge0.getSquaredLength();
		if(t0 >= edgeSquaredLength){
			t0 = 1.f;
			distance0 -= edge0;
		}else{
			t0 /= edgeSquaredLength;
			distance0 -= t0 * edge0;
		}
	}
	// セグメント0との距離が半径以下
	float squaredLength0 = distance0.getSquaredLength();
	if(squaredLength0 <= squaredRadius){
		// 交差位置は球中心に(-distance0).setLength(radius)を足した位置
		// 反射は(-distance).setLength(radius - length0)
		Vector3 position0 = -distance0;
		float length0 = radius - Math::sqrt(squaredLength0);
		if(position0.isZero()){
			// エッジが球中心に重なっている
			position0 = Vector3::unitY * radius;
			intersection->set(center + position0, position0);
		}else if(length0 <= Math::epsilon){
			// エッジが球に接している
			intersection->set(center + position0.setLength(radius),
				Vector3::zero);
		}else{
			// エッジが球にめり込んでいる
			position0.normalize();
			intersection->set(center + position0 * radius,
				position0 * length0);
		}
		return true;
	}

	// セグメント1との比較
	float t1 = distance1.dotProduct(edge1);
	if(t1 > 0.f){
		float edgeSquaredLength = edge1.getSquaredLength();
		if(t1 >= edgeSquaredLength){
			t1 = 1.f;
			distance1 -= edge1;
		}else{
			t1 /= edgeSquaredLength;
			distance1 -= t1 * edge1;
		}
	}
	// セグメント1との距離が半径以下
	float squaredLength1 = distance1.getSquaredLength();
	if(squaredLength1 <= squaredRadius){
		// 交差位置は球中心に(-distance1).setLength(radius)を足した位置
		// 反射は(-distance).setLength(radius - length1)
		Vector3 position1 = -distance1;
		float length1 = radius - Math::sqrt(squaredLength1);
		if(position1.isZero()){
			// エッジが球中心に重なっている
			position1 = Vector3::unitY * radius;
			intersection->set(center + position1, position1);
		}else if(length1 <= Math::epsilon){
			// エッジが球に接している
			intersection->set(center + position1.setLength(radius),
				Vector3::zero);
		}else{
			// エッジが球にめり込んでいる
			position1.normalize();
			intersection->set(center + position1 * radius,
				position1 * length1);
		}
		return true;
	}

	// セグメント2との比較
	float t2 = distance2.dotProduct(edge2);
	if(t2 > 0.f){
		float edgeSquaredLength = edge2.getSquaredLength();
		if(t2 >= edgeSquaredLength){
			t2 = 1.f;
			distance2 -= edge2;
		}else{
			t2 /= edgeSquaredLength;
			distance2 -= t2 * edge2;
		}
	}
	// セグメント2との距離が半径以下
	float squaredLength2 = distance2.getSquaredLength();
	if(squaredLength2 <= squaredRadius){
		// 交差位置は球中心に(-distance2).setLength(radius)を足した位置
		// 反射は(-distance).setLength(radius - length2)
		Vector3 position2 = -distance2;
		float length2 = radius - Math::sqrt(squaredLength2);
		if(position2.isZero()){
			// エッジが球中心に重なっている
			position2 = Vector3::unitY * radius;
			intersection->set(center + position2, position2);
		}else if(length2 <= Math::epsilon){
			// エッジが球に接している
			intersection->set(center + position2.setLength(radius),
				Vector3::zero);
		}else{
			// エッジが球にめり込んでいる
			position2.normalize();
			intersection->set(center + position2 * radius,
				position2 * length2);
		}
		return true;
	}

	return false;
}
//------------------------------------------------------------------------------
} // End of namespace Lamp
//------------------------------------------------------------------------------
