//------------------------------------------------------------------------------
// 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
 */

#ifndef SPHERE_H_
#define SPHERE_H_

#include <Core/Primitive/Vector3.h>
#include <Core/Primitive/Matrix33.h>
#include <Core/Primitive/Matrix34.h>
#include <Core/Primitive/Matrix44.h>

namespace Lamp{

class Intersection;
class AxisAlignedBox;
class Capsule;
class Cone;
class Line;
class OrientedBox;
class Plane;
class Ray;
class Segment;
class Triangle;

//------------------------------------------------------------------------------
/**
 * 球
 *
 * このクラスは継承しないで下さい。
 */
class Sphere{
public:
	//--------------------------------------------------------------------------
	// 定数
	//--------------------------------------------------------------------------
	/// ゼロ球
	static const Sphere zero;

	/// 単位球
	static const Sphere unit;

	//--------------------------------------------------------------------------
	// コンストラクタ
	//--------------------------------------------------------------------------
	/**
	 * コンストラクタ
	 *
	 * このコンストラクタは初期値の設定を行わないため値は不定です。
	 */
	inline Sphere(){}

	/**
	 * コンストラクタ
	 * @param center 中心の初期値
	 * @param radius 半径の初期値
	 */

	inline Sphere(const Vector3& center, float radius) :
		center_(center), radius_(radius){
	}

	/**
	 * コンストラクタ
	 * @param centerX 中心Xの初期値
	 * @param centerY 中心Yの初期値
	 * @param centerZ 中心Zの初期値
	 * @param radius 半径の初期値
	 */

	inline Sphere(float centerX, float centerY, float centerZ, float radius) :
		center_(centerX, centerY, centerZ), radius_(radius){
	}

	/**
	 * コンストラクタ
	 * @param source 初期値配列
	 */
	inline explicit Sphere(const float* const source) :
		center_(source[0], source[1], source[2]), radius_(source[3]){
	}

	//--------------------------------------------------------------------------
	// 値の設定
	//--------------------------------------------------------------------------
	/**
	 * 値の設定
	 * @param center 設定する中心
	 * @param radius 設定する半径
	 */
	inline void set(const Vector3& center, float radius){
		center_ = center;
		radius_ = radius;
	}

	/**
	 * 値の設定
	 * @param centerX 設定する中心X
	 * @param centerY 設定する中心Y
	 * @param centerZ 設定する中心Z
	 * @param radius 設定する半径
	 */
	inline void set(float centerX, float centerY, float centerZ, float radius){
		center_.set(centerX, centerY, centerZ);
		radius_ = radius;
	}

	/**
	 * 値の設定
	 * @param source 設定値配列
	 */
	inline void set(const float* const source){
		center_.set(source[0], source[1], source[2]);
		radius_ = source[3];
	}

	/**
	 * 中心の設定
	 * @param centerX 設定する中心X
	 * @param centerY 設定する中心Y
	 * @param centerZ 設定する中心Z
	 */
	inline void setCenter(float centerX, float centerY, float centerZ){
		center_.set(centerX, centerY, centerZ);
	}

	/**
	 * 中心の設定
	 * @param center 設定する中心
	 */
	inline void setCenter(const Vector3& center){ center_ = center; }

	/**
	 * 半径の設定
	 * @param radius 設定する半径
	 */
	inline void setRadius(float radius){ radius_ = radius; }

	//--------------------------------------------------------------------------
	// 値の取得
	//--------------------------------------------------------------------------
	/**
	 * 中心の取得
	 * @return 中心
	 */
	inline const Vector3& getCenter() const{ return center_; }

	/**
	 * 半径の取得
	 * @return 半径
	 */
	inline float getRadius() const{ return radius_; }

	//--------------------------------------------------------------------------
	// 球演算
	//--------------------------------------------------------------------------
	/**
	 * マージ
	 * @param target マージする球
	 * @return マージされた球
	 */
	inline const Sphere& merge(const Sphere& target){
		Vector3 direction = target.center_ - center_;
		// 同じ位置に存在する球
		if(direction.isZero()){
			if(target.radius_ > radius_){ radius_ = target.radius_; }
			return *this;
		}
		float distance = direction.getLength();
		// ターゲットが自球の内側にある
		if(radius_ >= target.radius_ + distance){ return *this; }
		// ターゲットの内側に自球がある
		if(target.radius_ >= radius_ + distance){
			*this = target;
			return *this;
		}
		// 球がお互い重なっていない
		direction *= (1.f / distance);
		center_ = ((center_ - direction * radius_) +
			(target.center_ + direction * target.radius_)) * 0.5f;
		radius_ = (radius_ + target.radius_ + distance) * 0.5f;
		return *this;
	}

	/**
	 * マージ
	 *
	 * 中心が動く。
	 * 適切な中心とappendを使用した場合と比べて、球が大きくなってしまう。
	 * @param target マージするベクトル
	 * @return マージされた球
	 */
	inline const Sphere& merge(const Vector3& target){
		Vector3 direction = target - center_;
		// 中心と同じ位置に存在
		if(direction.isZero()){ return *this; }
		float distance = direction.getLength();
		// ベクトルが球内なら何もしない
		if(distance <= radius_){ return *this; }
		// ベクトルが球外にあるので中心を動かし、調整する
		direction *= (1.f / distance);
		center_ = ((center_ - direction * radius_) + target) * 0.5f;
		radius_ = (radius_ + distance) * 0.5f;
		return *this;
	}

	/**
	 * 追加
	 *
	 * 中心は動かさない。適切な中心が設定されている状態で使用する。
	 * @param target 追加するベクトル
	 * @return 追加された球
	 */
	inline const Sphere& append(const Vector3& target){
		Vector3 direction = target - center_;
		// 中心と同じ位置に存在
		if(direction.isZero()){ return *this; }
		float distance = direction.getLength();
		// 球外なら拡張。中心は動かさない
		if(distance > radius_){ radius_ = distance; }
		return *this;
	}

	//--------------------------------------------------------------------------
	/**
	 * ゼロ球かどうか
	 * @return ゼロ球ならtrueを返す
	 */
	inline bool isZero() const{
		return (Math::abs(radius_) <= Math::epsilon);
	}

	/**
	 * 単位球かどうか
	 * @return 単位球ならtrueを返す
	 */
	inline bool isUnit() const{
		return (Math::abs(radius_ - 1.f) <= Math::epsilon);
	}

	//--------------------------------------------------------------------------
	// トランスフォーム
	//--------------------------------------------------------------------------
	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Sphere transform(const Matrix33& matrix) const{
		return Sphere(matrix * center_, radius_);
	}

	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Sphere transform(const Matrix34& matrix) const{
		return Sphere(matrix * center_, radius_);
	}

	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Sphere transform(const Matrix44& matrix) const{
		return Sphere(matrix * center_, radius_);
	}

	//--------------------------------------------------------------------------
	/**
	 * スケール有りトランスフォーム
	 *
	 * 一番スケールの大きな軸にあわせて半径が膨らみます。
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Sphere scaledTransform(const Matrix33& matrix) const{
		// ベクトル(1,0,0),(0,1,0),(0,0,1)を変換して最長の長さを半径に乗算
		float maxSquardLength = 0.f;
		for(int i = 0; i < 3; i++){
			float squaredLength =
				matrix.m[0][i] * matrix.m[0][i] +
				matrix.m[1][i] * matrix.m[1][i] +
				matrix.m[2][i] * matrix.m[2][i];
			if(squaredLength > maxSquardLength){
				maxSquardLength = squaredLength;
			}
		}
		return Sphere(matrix * center_, radius_ * Math::sqrt(maxSquardLength));
	}

	/**
	 * スケール有りトランスフォーム
	 *
	 * 一番スケールの大きな軸にあわせて半径が膨らみます。
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Sphere scaledTransform(const Matrix34& matrix) const{
		// ベクトル(1,0,0),(0,1,0),(0,0,1)を変換して最長の長さを半径に乗算
		float maxSquardLength = 0.f;
		for(int i = 0; i < 3; i++){
			float squaredLength =
				matrix.m[0][i] * matrix.m[0][i] +
				matrix.m[1][i] * matrix.m[1][i] +
				matrix.m[2][i] * matrix.m[2][i];
			if(squaredLength > maxSquardLength){
				maxSquardLength = squaredLength;
			}
		}
		return Sphere(matrix * center_, radius_ * Math::sqrt(maxSquardLength));
	}

	/**
	 * スケール有りトランスフォーム
	 *
	 * 一番スケールの大きな軸にあわせて半径が膨らみます。
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Sphere scaledTransform(const Matrix44& matrix) const{
		// ベクトル(1,0,0),(0,1,0),(0,0,1)を変換して最長の長さを半径に乗算
		float maxSquardLength = 0.f;
		for(int i = 0; i < 3; i++){
			float squaredLength =
				matrix.m[0][i] * matrix.m[0][i] +
				matrix.m[1][i] * matrix.m[1][i] +
				matrix.m[2][i] * matrix.m[2][i];
			if(squaredLength > maxSquardLength){
				maxSquardLength = squaredLength;
			}
		}
		return Sphere(matrix * center_, radius_ * Math::sqrt(maxSquardLength));
	}

	//--------------------------------------------------------------------------
	// 距離
	//--------------------------------------------------------------------------
	/**
	 * 点距離
	 * @param point 距離判定する点
	 * @return 距離
	 */
	float getDistance(const Vector3& point) const{
		return Math::sqrt(getSquaredDistance(point));
	}

	/**
	 * 点距離の二乗
	 * @param point 距離判定する点
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const Vector3& point) const;

	//--------------------------------------------------------------------------
	/**
	 * 軸沿いボックス距離
	 * @param axisAlignedBox 距離判定する軸沿いボックス
	 * @return 距離
	 */
	float getDistance(const AxisAlignedBox& axisAlignedBox) const{
		return Math::sqrt(getSquaredDistance(axisAlignedBox));
	}

	/**
	 * 軸沿いボックス距離の二乗
	 * @param axisAlignedBox 距離判定する軸沿いボックス
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const AxisAlignedBox& axisAlignedBox) const;

	//--------------------------------------------------------------------------
	/**
	 * カプセル距離
	 * @param capsule 距離判定するカプセル
	 * @return 距離
	 */
	float getDistance(const Capsule& capsule) const{
		return Math::sqrt(getSquaredDistance(capsule));
	}

	/**
	 * カプセル距離の二乗
	 * @param capsule 距離判定するカプセル
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const Capsule& capsule) const;

	//--------------------------------------------------------------------------
	/**
	 * コーン距離
	 * @param cone 距離判定するコーン
	 * @return 距離
	 */
	float getDistance(const Cone& cone) const{
		return Math::sqrt(getSquaredDistance(cone));
	}

	/**
	 * コーン距離の二乗
	 * @param cone 距離判定するコーン
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const Cone& cone) const;

	//--------------------------------------------------------------------------
	/**
	 * ライン距離
	 * @param line 距離判定するライン
	 * @return 距離
	 */
	float getDistance(const Line& line) const{
		return Math::sqrt(getSquaredDistance(line));
	}

	/**
	 * ライン距離の二乗
	 * @param line 距離判定するライン
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const Line& line) const;

	//--------------------------------------------------------------------------
	/**
	 * 指向性ボックス距離
	 * @param orientedBox 距離判定する指向性ボックス
	 * @return 距離
	 */
	float getDistance(const OrientedBox& orientedBox) const{
		return Math::sqrt(getSquaredDistance(orientedBox));
	}

	/**
	 * 指向性ボックス距離の二乗
	 * @param orientedBox 距離判定する指向性ボックス
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const OrientedBox& orientedBox) const;

	//--------------------------------------------------------------------------
	/**
	 * 平面距離
	 * @param plane 距離判定する平面
	 * @return 距離
	 */
	float getDistance(const Plane& plane) const;

	/**
	 * 平面距離の二乗
	 * @param plane 距離判定する平面
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const Plane& plane) const{
		float distance = getDistance(plane);
		return (distance * distance);
	}

	//--------------------------------------------------------------------------
	/**
	 * レイ距離
	 * @param ray 距離判定するレイ
	 * @return 距離
	 */
	float getDistance(const Ray& ray) const{
		return Math::sqrt(getSquaredDistance(ray));
	}

	/**
	 * レイ距離の二乗
	 * @param ray 距離判定するレイ
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const Ray& ray) const;

	//--------------------------------------------------------------------------
	/**
	 * セグメント距離
	 * @param segment 距離判定するセグメント
	 * @return 距離
	 */
	float getDistance(const Segment& segment) const{
		return Math::sqrt(getSquaredDistance(segment));
	}

	/**
	 * セグメント距離の二乗
	 * @param segment 距離判定するセグメント
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const Segment& segment) const;

	//--------------------------------------------------------------------------
	/**
	 * 球距離
	 * @param sphere 距離判定する球
	 * @return 距離
	 */
	float getDistance(const Sphere& sphere) const{
		return Math::sqrt(getSquaredDistance(sphere));
	}

	/**
	 * 球距離の二乗
	 * @param sphere 距離判定する球
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const Sphere& sphere) const;

	//--------------------------------------------------------------------------
	/**
	 * 三角距離
	 * @param triangle 距離判定する三角
	 * @return 距離
	 */
	float getDistance(const Triangle& triangle) const{
		return Math::sqrt(getSquaredDistance(triangle));
	}

	/**
	 * 三角距離の二乗
	 * @param triangle 距離判定する三角
	 * @return 距離の二乗
	 */
	float getSquaredDistance(const Triangle& triangle) const;

	//--------------------------------------------------------------------------
	// 交差
	//--------------------------------------------------------------------------
	/**
	 * 点交差
	 * @param point 交差判定する点
	 * @return 交差していればtrue
	 */
	bool intersect(const Vector3& point) const;

	//--------------------------------------------------------------------------
	/**
	 * 軸沿いボックス交差
	 * @param axisAlignedBox 交差判定する軸沿いボックス
	 * @return 交差していればtrue
	 */
	bool intersect(const AxisAlignedBox& axisAlignedBox) const;

	//--------------------------------------------------------------------------
	/**
	 * カプセル交差
	 * @param capsule 交差判定するカプセル
	 * @return 交差していればtrue
	 */
	bool intersect(const Capsule& capsule) const;

	//--------------------------------------------------------------------------
	/**
	 * コーン交差
	 * @param cone 交差判定するコーン
	 * @return 交差していればtrue
	 */
	bool intersect(const Cone& cone) const;

	//--------------------------------------------------------------------------
	/**
	 * ライン交差
	 * @param line 交差判定するライン
	 * @return 交差していればtrue
	 */
	bool intersect(const Line& line) const;

	//--------------------------------------------------------------------------
	/**
	 * 指向性ボックス交差
	 * @param orientedBox 交差判定する指向性ボックス
	 * @return 交差していればtrue
	 */
	bool intersect(const OrientedBox& orientedBox) const;

	//--------------------------------------------------------------------------
	/**
	 * 平面交差
	 * @param plane 交差判定する平面
	 * @return 交差していればtrue
	 */
	bool intersect(const Plane& plane) const;

	//--------------------------------------------------------------------------
	/**
	 * レイ交差
	 * @param ray 交差判定するレイ
	 * @return 交差していればtrue
	 */
	bool intersect(const Ray& ray) const;

	//--------------------------------------------------------------------------
	/**
	 * セグメント交差
	 * @param segment 交差判定するセグメント
	 * @return 交差していればtrue
	 */
	bool intersect(const Segment& segment) const;

	//--------------------------------------------------------------------------
	/**
	 * 球交差
	 * @param sphere 交差判定する球
	 * @return 交差していればtrue
	 */
	bool intersect(const Sphere& sphere) const;

	/**
	 * 球交差
	 * @param intersection 交差、返り値がtrueなら設定されている
	 * @param sphere 交差判定する球
	 * @return 交差していればtrue
	 */
	bool intersect(Intersection* intersection, const Sphere& sphere) const;

	//--------------------------------------------------------------------------
	/**
	 * 三角交差
	 * @param triangle 交差判定する三角
	 * @return 交差していればtrue
	 */
	bool intersect(const Triangle& triangle) const;

	/**
	 * 三角交差
	 * @param intersection 交差、返り値がtrueなら設定されている
	 * @param triangle 交差判定する三角
	 * @return 交差していればtrue
	 */
	bool intersect(Intersection* intersection, const Triangle& triangle) const;

	//--------------------------------------------------------------------------
	// 論理演算
	//--------------------------------------------------------------------------
	/**
	 * 球が同じかどうか
	 * @param target 比較する球
	 * @return 同じ値であればtrueを返す
	 */
	inline bool operator ==(const Sphere& target) const{
		return ((center_ == target.center_) && (radius_ == target.radius_));
	}

	/**
	 * 球が同じかどうか
	 * @param target 比較する球
	 * @param epsilon 誤差
	 * @return 誤差の範囲内で同じ値であればtrueを返す
	 */
	inline bool epsilonEquals(
		const Sphere& target, float epsilon) const{
		Assert(epsilon >= 0.f);
		return (center_.epsilonEquals(target.center_, epsilon) &&
			(Math::abs(radius_ - target.radius_) <= epsilon));
	}

	/**
	 * 球が同じでないかどうか
	 * @param target 比較する球
	 * @return 同じでない値であればtrueを返す
	 */
	inline bool operator !=(const Sphere& target) const{
		return ((center_ != target.center_) || (radius_ != target.radius_));
	}

	/**
	 * 球が同じでないかどうか
	 * @param target 比較する球
	 * @param epsilon 誤差
	 * @return 誤差の範囲内で同じでない値であればtrueを返す
	 */
	inline bool notEpsilonEquals(
		const Sphere& target, float epsilon) const{
		Assert(epsilon >= 0.f);
		return (center_.notEpsilonEquals(target.center_, epsilon) ||
			(Math::abs(radius_ - target.radius_) > epsilon));
	}

	//--------------------------------------------------------------------------
	// その他
	//--------------------------------------------------------------------------
	/**
	 * 文字列化
	 * @return 球の文字列表記
	 */
	inline String toString() const{
		String returnString;
		returnString.format("{ ( %.8f, %.8f, %.8f ) %.8f }",
			center_.x, center_.y, center_.z, radius_);
		return returnString;
	}

private:
	//--------------------------------------------------------------------------
	// メンバ変数
	//--------------------------------------------------------------------------
	// 中心
	Vector3 center_;
	// 半径
	float radius_;

};

//------------------------------------------------------------------------------
} // End of namespace Lamp
#endif // End of SPHERE_H_
//------------------------------------------------------------------------------
