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

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

namespace Lamp{

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

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

	/// X単位平面
	static const Plane unitX;

	/// Y単位平面
	static const Plane unitY;

	/// Z単位平面
	static const Plane unitZ;

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

	/**
	 * コンストラクタ
	 * @param normal 法線の初期値
	 * @param constant 定数の初期値
	 */

	inline Plane(const Vector3& normal, float constant) :
		normal_(normal), constant_(constant){
	}

	/**
	 * コンストラクタ
	 * @param normalX 法線Xの初期値
	 * @param normalY 法線Yの初期値
	 * @param normalZ 法線Zの初期値
	 * @param constant 定数の初期値
	 */

	inline Plane(float normalX, float normalY, float normalZ, float constant) :
		normal_(normalX, normalY, normalZ), constant_(constant){
	}

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

	/**
	 * コンストラクタ
	 * @param vertex0 平面上の頂点0
	 * @param vertex1 平面上の頂点1
	 * @param vertex2 平面上の頂点2
	 */
	inline Plane(
		const Vector3& vertex0, const Vector3& vertex1, const Vector3& vertex2){
		normal_ = Math3D::calculateNormal(vertex0, vertex1, vertex2);
		constant_ = -(normal_.dotProduct(vertex0));
	}

	/**
	 * コンストラクタ
	 * @param normal 法線の初期値
	 * @param vertex 平面上の頂点
	 */
	inline Plane(const Vector3& normal, const Vector3& vertex) :
		normal_(normal){
		constant_ = -(normal_.dotProduct(vertex));
	}

	//--------------------------------------------------------------------------
	// 値の設定
	//--------------------------------------------------------------------------
	/**
	 * 値の設定
	 * @param normal 設定する法線
	 * @param constant 設定する定数
	 */
	inline void set(const Vector3& normal, float constant){
		normal_ = normal;
		constant_ = constant;
	}

	/**
	 * 値の設定
	 * @param normalX 設定する法線X
	 * @param normalY 設定する法線Y
	 * @param normalZ 設定する法線Z
	 * @param constant 設定する定数
	 */
	inline void set(
		float normalX, float normalY, float normalZ, float constant){
		normal_.set(normalX, normalY, normalZ);
		constant_ = constant;
	}

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

	/**
	 * 値の設定
	 * @param vertex0 平面上の頂点0
	 * @param vertex1 平面上の頂点1
	 * @param vertex2 平面上の頂点2
	 */
	inline void set(
		const Vector3& vertex0, const Vector3& vertex1, const Vector3& vertex2){
		normal_ = Math3D::calculateNormal(vertex0, vertex1, vertex2);
		constant_ = -(normal_.dotProduct(vertex0));
	}

	/**
	 * 値の設定
	 * @param normal 設定する法線
	 * @param vertex 平面上の頂点
	 */
	inline void set(const Vector3& normal, const Vector3& vertex){
		normal_ = normal;
		constant_ = -(normal_.dotProduct(vertex));
	}

	/**
	 * 法線の設定
	 * @param normalX 設定する法線X
	 * @param normalY 設定する法線Y
	 * @param normalZ 設定する法線Z
	 */
	inline void setNormal(float normalX, float normalY, float normalZ){
		normal_.set(normalX, normalY, normalZ);
	}

	/**
	 * 法線の設定
	 * @param normal 設定する法線
	 */
	inline void setNormal(const Vector3& normal){ normal_ = normal; }

	/**
	 * 定数の設定
	 * @param constant 設定する定数
	 */
	inline void setConstant(float constant){ constant_ = constant; }

	/**
	 * 長さの設定
	 * @param length 設定する長さ
	 */
	inline const Plane& setLength(float length){
		float nowLength = normal_.getLength();
		// 長さが0の場合における対処はアプリケーションによって違うのでAssert
		Assert(nowLength >= Math::epsilon);
		float inverseLength = (length / nowLength);
		normal_ *= inverseLength;
		constant_ *= inverseLength;
		return *this;
	}

	//--------------------------------------------------------------------------
	// 値の取得
	//--------------------------------------------------------------------------
	/**
	 * 法線の取得
	 * @return 法線
	 */
	inline const Vector3& getNormal() const{ return normal_; }

	/**
	 * 定数の取得
	 * @return 定数
	 */
	inline float getConstant() const{ return constant_; }

	/**
	 * 長さの取得
	 * @return 長さ
	 */
	inline float getLength() const{
		return Math::sqrt(normal_.x * normal_.x +
			normal_.y * normal_.y + normal_.z * normal_.z);
	}

	/**
	 * 長さの二乗を取得
	 * @return 長さの二乗
	 */
	inline float getSquaredLength() const{
		return (normal_.x * normal_.x +
			normal_.y * normal_.y + normal_.z * normal_.z);
	}

	//--------------------------------------------------------------------------
	// 平面演算
	//--------------------------------------------------------------------------
	/**
	 * 正規化
	 * @return 正規化された平面
	 */
	inline const Plane& normalize(){ return setLength(1.f); }

	/**
	 * 内積を行う
	 * @return 内積した値
	 */
	inline float dotProduct(const Vector3& vector) const{
		return normal_.dotProduct(vector) + constant_;
	}

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

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

	//--------------------------------------------------------------------------
	// トランスフォーム
	//--------------------------------------------------------------------------
	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Plane transform(const Matrix33& matrix) const{
		Plane result;
		result.normal_ = matrix * normal_;
		result.constant_ = constant_;
		return result;
	}

	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Plane transform(const Matrix34& matrix) const{
		Plane result;
		result.normal_ = matrix.multiply33(normal_);
		Vector3 trans = matrix.getTranslation();
		trans.set(
			matrix.m00 * trans.x + matrix.m10 * trans.y + matrix.m20 * trans.z,
			matrix.m01 * trans.x + matrix.m11 * trans.y + matrix.m21 * trans.z,
			matrix.m02 * trans.x + matrix.m12 * trans.y + matrix.m22 * trans.z);
		result.constant_ = constant_ - trans.dotProduct(normal_);
		return result;
	}

	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Plane transform(const Matrix44& matrix) const{
		Plane result;
		result.normal_ = matrix.multiply33(normal_);
		Vector3 trans = matrix.getTranslation();
		trans.set(
			matrix.m00 * trans.x + matrix.m10 * trans.y + matrix.m20 * trans.z,
			matrix.m01 * trans.x + matrix.m11 * trans.y + matrix.m21 * trans.z,
			matrix.m02 * trans.x + matrix.m12 * trans.y + matrix.m22 * trans.z);
		result.constant_ = constant_ - trans.dotProduct(normal_);
		return result;
	}

	//--------------------------------------------------------------------------
	/**
	 * スケール有りトランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Plane scaledTransform(const Matrix33& matrix) const{
		Plane result;
		Matrix33 normalMatrix;
		matrix.invert(&normalMatrix);
		normalMatrix.transpose();
		result.normal_ = normalMatrix * normal_;
		result.constant_ = constant_;
		return result;
	}

	/**
	 * スケール有りトランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Plane scaledTransform(const Matrix34& matrix) const{
		Plane result;
		Matrix33 normalMatrix;
		normalMatrix.set(matrix);
		normalMatrix.invert();
		result.constant_ = constant_ -
			(normalMatrix * matrix.getTranslation()).dotProduct(normal_);
		normalMatrix.transpose();
		result.normal_ = normalMatrix * normal_;
		return result;
	}

	/**
	 * スケール有りトランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後の球
	 */
	inline Plane scaledTransform(const Matrix44& matrix) const{
		Plane result;
		Matrix33 normalMatrix;
		normalMatrix.set(matrix);
		normalMatrix.invert();
		result.constant_ = constant_ -
			(normalMatrix * matrix.getTranslation()).dotProduct(normal_);
		normalMatrix.transpose();
		result.normal_ = normalMatrix * normal_;
		return result;
	}

	//--------------------------------------------------------------------------
	// 距離
	//--------------------------------------------------------------------------
	/**
	 * 点距離
	 * @param point 距離判定する点
	 * @return 距離
	 */
	float getDistance(const Vector3& point) const;

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

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

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

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

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

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

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

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

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

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

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

	//--------------------------------------------------------------------------
	/**
	 * 平面距離
	 * @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;

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

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

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

	//--------------------------------------------------------------------------
	/**
	 * 球距離
	 * @param sphere 距離判定する球
	 * @return 距離
	 */
	float getDistance(const Sphere& sphere) const;

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

	//--------------------------------------------------------------------------
	/**
	 * 三角距離
	 * @param triangle 距離判定する三角
	 * @return 距離
	 */
	float getDistance(const Triangle& triangle) const;

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

	//--------------------------------------------------------------------------
	// 交差
	//--------------------------------------------------------------------------
	/**
	 * 点交差
	 * @param point 交差判定する点
	 * @param range 交差範囲
	 * @return 交差していればtrue
	 */
	bool intersect(const Vector3& point, float range = Math::epsilon) 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 Plane& target) const{
		return ((normal_ == target.normal_) && (constant_ == target.constant_));
	}

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

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

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

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

private:
	//--------------------------------------------------------------------------
	// メンバ変数
	//--------------------------------------------------------------------------
	// 法線
	Vector3 normal_;
	// 定数
	float constant_;

};

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