//------------------------------------------------------------------------------
// 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 AXIS_ALIGNED_BOX_H_
#define AXIS_ALIGNED_BOX_H_

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

namespace Lamp{

class Capsule;
class Cone;
class Line;
class OrientedBox;
class Plane;
class Ray;
class Segment;
class Sphere;
class Triangle;

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

	/// 単位ボックス
	static const AxisAlignedBox unit;

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

	/**
	 * コンストラクタ
	 * @param minimum 最小値の初期値
	 * @param maximum 最大値の初期値
	 */
	inline AxisAlignedBox(const Vector3& minimum, const Vector3& maximum) :
		minimum_(minimum), maximum_(maximum){
		Assert(minimum_.x <= maximum_.x);
		Assert(minimum_.y <= maximum_.y);
		Assert(minimum_.z <= maximum_.z);
	}

	/**
	 * コンストラクタ
	 * @param minimumX 最小値Xの初期値
	 * @param minimumY 最小値Yの初期値
	 * @param minimumZ 最小値Zの初期値
	 * @param maximumX 最大値Xの初期値
	 * @param maximumY 最大値Yの初期値
	 * @param maximumZ 最大値Zの初期値
	 */
	inline AxisAlignedBox(
		float minimumX, float minimumY, float minimumZ,
		float maximumX, float maximumY, float maximumZ) :
		minimum_(minimumX, minimumY, minimumZ),
		maximum_(maximumX, maximumY, maximumZ){
		Assert(minimum_.x <= maximum_.x);
		Assert(minimum_.y <= maximum_.y);
		Assert(minimum_.z <= maximum_.z);
	}

	/**
	 * コンストラクタ
	 * @param source 初期値配列
	 */
	inline explicit AxisAlignedBox(const float* const source) :
		minimum_(source[0], source[1], source[2]),
		maximum_(source[3], source[4], source[5]){
		Assert(minimum_.x <= maximum_.x);
		Assert(minimum_.y <= maximum_.y);
		Assert(minimum_.z <= maximum_.z);
	}

	//--------------------------------------------------------------------------
	// 値の設定
	//--------------------------------------------------------------------------
	/**
	 * 値の設定
	 * @param minimum 設定する最小値
	 * @param maximum 設定する最大値
	 */
	inline void set(const Vector3& minimum, const Vector3& maximum){
		minimum_ = minimum;
		maximum_ = maximum;
		Assert(minimum_.x <= maximum_.x);
		Assert(minimum_.y <= maximum_.y);
		Assert(minimum_.z <= maximum_.z);
	}

	/**
	 * 値の設定
	 * @param minimumX 設定する最小値X
	 * @param minimumY 設定する最小値Y
	 * @param minimumZ 設定する最小値Z
	 * @param maximumX 設定する最大値X
	 * @param maximumY 設定する最大値Y
	 * @param maximumZ 設定する最大値Z
	 */
	inline void set(
		float minimumX, float minimumY, float minimumZ,
		float maximumX, float maximumY, float maximumZ){
		minimum_.set(minimumX, minimumY, minimumZ);
		maximum_.set(maximumX, maximumY, maximumZ);
		Assert(minimum_.x <= maximum_.x);
		Assert(minimum_.y <= maximum_.y);
		Assert(minimum_.z <= maximum_.z);
	}

	/**
	 * 値の設定
	 * @param source 設定値配列
	 */
	inline void set(const float* const source){
		minimum_.set(source[0], source[1], source[2]);
		maximum_.set(source[3], source[4], source[5]);
		Assert(minimum_.x <= maximum_.x);
		Assert(minimum_.y <= maximum_.y);
		Assert(minimum_.z <= maximum_.z);
	}

	//--------------------------------------------------------------------------
	// 値の取得
	//--------------------------------------------------------------------------
	/**
	 * 最小値の取得
	 * @return 最小値
	 */
	inline const Vector3& getMinimum() const{ return minimum_; }

	/**
	 * 最大値の取得
	 * @return 最大値
	 */
	inline const Vector3& getMaximum() const{ return maximum_; }

	/**
	 * サイズの取得
	 * @return サイズ
	 */
	inline Vector3 getSize() const{ return (maximum_ - minimum_); }

	/**
	 * 中心の取得
	 * @return 中心
	 */
	inline Vector3 getCenter() const{ return (maximum_ + minimum_) * 0.5f; }

	/**
	 * コーナーの取得
	 *
	 * 以下の図のインデックスに沿ってコーナーを取得します。
	 * 0が最小値で4が最大値です。
	 * <pre>
	 *     y+
	 *     |
	 *     1----2
	 *    /|   /|
	 *   5-+--4 |
	 *   | 0--+-3-- x+
	 *   |/   |/
	 *   6----7
	 *  /
	 * z+
	 * </pre>
	 * @param index インデックス
	 * @return コーナー
	 */
	inline Vector3 getCorner(int index) const{
		if(index == 0){
			return minimum_;
		}else if(index == 1){
			return Vector3(minimum_.x, maximum_.y, minimum_.z);
		}else if(index == 2){
			return Vector3(maximum_.x, maximum_.y, minimum_.z);
		}else if(index == 3){
			return Vector3(maximum_.x, minimum_.y, minimum_.z);
		}else if(index == 4){
			return maximum_;
		}else if(index == 5){
			return Vector3(minimum_.x, maximum_.y, maximum_.z);
		}else if(index == 6){
			return Vector3(minimum_.x, minimum_.y, maximum_.z);
		}else if(index == 7){
			return Vector3(maximum_.x, minimum_.y, maximum_.z);
		}
		ErrorOut("AxisAlignedBox::getCorner() Out of index");
		return Vector3(0.f, 0.f, 0.f);
	}

	/**
	 * コーナー配列の取得
	 *
	 * 以下の図のインデックスに沿ってコーナーを取得します。
	 * 0が最小値で4が最大値です。
	 * <pre>
	 *     y+
	 *     |
	 *     1----2
	 *    /|   /|
	 *   5-+--4 |
	 *   | 0--+-3-- x+
	 *   |/   |/
	 *   6----7
	 *  /
	 * z+
	 * </pre>
	 * @param corner [out] コーナー配列
	 */
	inline void getCornerArray(Vector3 corner[8]) const{
		corner[0] = minimum_;
		corner[1].set(minimum_.x, maximum_.y, minimum_.z);
		corner[2].set(maximum_.x, maximum_.y, minimum_.z);
		corner[3].set(maximum_.x, minimum_.y, minimum_.z);
		corner[4] = maximum_;
		corner[5].set(minimum_.x, maximum_.y, maximum_.z);
		corner[6].set(minimum_.x, minimum_.y, maximum_.z);
		corner[7].set(maximum_.x, minimum_.y, maximum_.z);
	}

	//--------------------------------------------------------------------------
	// ボックス演算
	//--------------------------------------------------------------------------
	/**
	 * マージ
	 * @param box マージするボックス
	 * @return マージされたボックス
	 */
	inline const AxisAlignedBox& merge(const AxisAlignedBox& box){
		if(minimum_.x > box.minimum_.x){ minimum_.x = box.minimum_.x; }
		if(maximum_.x < box.maximum_.x){ maximum_.x = box.maximum_.x; }
		if(minimum_.y > box.minimum_.y){ minimum_.y = box.minimum_.y; }
		if(maximum_.y < box.maximum_.y){ maximum_.y = box.maximum_.y; }
		if(minimum_.z > box.minimum_.z){ minimum_.z = box.minimum_.z; }
		if(maximum_.z < box.maximum_.z){ maximum_.z = box.maximum_.z; }
		return *this;
	}

	/**
	 * マージ
	 * @param vector マージするベクトル
	 * @return マージされたボックス
	 */
	inline const AxisAlignedBox& merge(const Vector3& vector){
		if(minimum_.x > vector.x){ minimum_.x = vector.x; }
		else if(maximum_.x < vector.x){ maximum_.x = vector.x; }
		if(minimum_.y > vector.y){ minimum_.y = vector.y; }
		else if(maximum_.y < vector.y){ maximum_.y = vector.y; }
		if(minimum_.z > vector.z){ minimum_.z = vector.z; }
		else if(maximum_.z < vector.z){ maximum_.z = vector.z; }
		return *this;
	}

	//--------------------------------------------------------------------------
	/**
	 * ゼロボックスかどうか
	 * @return ゼロボックスならtrueを返す
	 */
	inline bool isZero() const{
		return epsilonEquals(zero, Math::epsilon);
	}

	/**
	 * 単位ボックスかどうか
	 * @return 単位ボックスならtrueを返す
	 */
	inline bool isUnit() const{
		return epsilonEquals(unit, Math::epsilon);
	}

	//--------------------------------------------------------------------------
	// トランスフォーム
	//--------------------------------------------------------------------------
	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の軸沿いボックス
	 */
	inline AxisAlignedBox transform(const Matrix33& matrix) const{
		AxisAlignedBox result;
		Vector3 corner[8];
		getCornerArray(corner);
		result.maximum_ = result.minimum_ = matrix * corner[7];
		for(int i = 0; i < 7; i++){ result.merge(matrix * corner[i]); }
		return result;
	}

	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の軸沿いボックス
	 */
	inline AxisAlignedBox transform(const Matrix34& matrix) const{
		AxisAlignedBox result;
		Vector3 corner[8];
		getCornerArray(corner);
		result.maximum_ = result.minimum_ = matrix * corner[7];
		for(int i = 0; i < 7; i++){ result.merge(matrix * corner[i]); }
		return result;
	}

	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の軸沿いボックス
	 */
	inline AxisAlignedBox transform(const Matrix44& matrix) const{
		AxisAlignedBox result;
		Vector3 corner[8];
		getCornerArray(corner);
		result.maximum_ = result.minimum_ = matrix * corner[7];
		for(int i = 0; i < 7; i++){ result.merge(matrix * corner[i]); }
		return result;
	}

	//--------------------------------------------------------------------------
	// 距離
	//--------------------------------------------------------------------------
	/**
	 * 点距離
	 * @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 triangle 交差判定する三角
	 * @return 交差していればtrue
	 */
	bool intersect(const Triangle& triangle) const;

	//--------------------------------------------------------------------------
	// 論理演算
	//--------------------------------------------------------------------------
	/**
	 * 軸沿いボックスが同じかどうか
	 * @param target 比較する軸沿いボックス
	 * @return 同じ値であればtrueを返す
	 */
	inline bool operator ==(const AxisAlignedBox& target) const{
		return ((minimum_ == target.minimum_) && (maximum_ == target.maximum_));
	}

	/**
	 * 軸沿いボックスが同じかどうか
	 * @param target 比較する軸沿いボックス
	 * @param epsilon 誤差
	 * @return 誤差の範囲内で同じ値であればtrueを返す
	 */
	inline bool epsilonEquals(
		const AxisAlignedBox& target, float epsilon) const{
		Assert(epsilon >= 0.f);
		return (minimum_.epsilonEquals(target.minimum_, epsilon) &&
			maximum_.epsilonEquals(target.maximum_, epsilon));
	}

	/**
	 * 軸沿いボックスが同じでないかどうか
	 * @param target 比較する軸沿いボックス
	 * @return 同じでない値であればtrueを返す
	 */
	inline bool operator !=(const AxisAlignedBox& target) const{
		return ((minimum_ != target.minimum_) || (maximum_ != target.maximum_));
	}

	/**
	 * 軸沿いボックスが同じでないかどうか
	 * @param target 比較する軸沿いボックス
	 * @param epsilon 誤差
	 * @return 誤差の範囲内で同じでない値であればtrueを返す
	 */
	inline bool notEpsilonEquals(
		const AxisAlignedBox& target, float epsilon) const{
		Assert(epsilon >= 0.f);
		return (minimum_.notEpsilonEquals(target.minimum_, epsilon) ||
			maximum_.notEpsilonEquals(target.maximum_, epsilon));
	}

	//--------------------------------------------------------------------------
	// その他
	//--------------------------------------------------------------------------
	/**
	 * 文字列化
	 * @return 軸沿いボックスの文字列表記
	 */
	inline String toString() const{
		String returnString;
		returnString.format("{ ( %.8f, %.8f, %.8f ) ( %.8f, %.8f, %.8f ) }",
			minimum_.x, minimum_.y, minimum_.z,
			maximum_.x, maximum_.y, maximum_.z);
		return returnString;
	}

private:
	//--------------------------------------------------------------------------
	// メンバ変数
	//--------------------------------------------------------------------------
	// 最小値
	Vector3 minimum_;
	// 最大値
	Vector3 maximum_;

};

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