//------------------------------------------------------------------------------
// 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 SEGMENT_H_
#define SEGMENT_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 Plane;
class Ray;
class Sphere;
class Triangle;

//------------------------------------------------------------------------------
/**
 * セグメント
 *
 * このクラスは継承しないで下さい。
 */
class Segment{
public:
	//--------------------------------------------------------------------------
	// 定数
	//--------------------------------------------------------------------------
	/// ゼロセグメント
	static const Segment zero;

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

	/**
	 * コンストラクタ
	 * @param origin 原点の初期値
	 * @param direction 方向の初期値
	 */
	inline Segment(const Vector3& origin, const Vector3& direction) :
		origin_(origin), direction_(direction){
	}

	/**
	 * コンストラクタ
	 * @param originX 原点Xの初期値
	 * @param originY 原点Yの初期値
	 * @param originZ 原点Zの初期値
	 * @param directionX 方向Xの初期値
	 * @param directionY 方向Yの初期値
	 * @param directionZ 方向Zの初期値
	 */
	inline Segment(float originX, float originY, float originZ,
		float directionX, float directionY, float directionZ) :
		origin_(originX, originY, originZ),
		direction_(directionX, directionY, directionZ){
	}

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

	//--------------------------------------------------------------------------
	// 値の設定
	//--------------------------------------------------------------------------
	/**
	 * 値の設定
	 * @param origin 設定する原点
	 * @param direction 設定する方向
	 */
	inline void set(const Vector3& origin, const Vector3& direction){
		origin_ = origin;
		direction_ = direction;
	}

	/**
	 * 値の設定
	 * @param originX 設定する原点X
	 * @param originY 設定する原点Y
	 * @param originZ 設定する原点Z
	 * @param directionX 設定する方向X
	 * @param directionY 設定する方向Y
	 * @param directionZ 設定する方向Z
	 */
	inline void set(float originX, float originY, float originZ,
		float directionX, float directionY, float directionZ){
		origin_.set(originX, originY, originZ);
		direction_.set(directionX, directionY, directionZ);
	}

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

	//--------------------------------------------------------------------------
	/**
	 * 原点の設定
	 * @param origin 設定する原点
	 */
	inline void setOrigin(const Vector3& origin){ origin_ = origin; }

	/**
	 * 方向の設定
	 * @param direction 設定する方向
	 */
	inline void setDirection(const Vector3& direction){
		direction_ = direction;
	}

	//--------------------------------------------------------------------------
	/**
	 * 位置の設定
	 * @param source ソース位置
	 * @param target ターゲット位置
	 */
	inline void setPositions(const Vector3& source, const Vector3& target){
		origin_ = source;
		direction_ = (target - source);
	}

	//--------------------------------------------------------------------------
	// 値の取得
	//--------------------------------------------------------------------------
	/**
	 * 原点の取得
	 * @return 原点
	 */
	inline const Vector3& getOrigin() const{ return origin_; }

	/**
	 * 方向の取得
	 * @return 方向
	 */
	inline const Vector3& getDirection() const{ return direction_; }

	//--------------------------------------------------------------------------
	/**
	 * ソース位置の取得
	 * @return ソース位置
	 */
	inline const Vector3& getSourcePosition() const{ return origin_; }

	/**
	 * ターゲット位置の取得
	 * @return ターゲット位置
	 */
	inline Vector3 getTargetPosition() const{ return (origin_ + direction_); }

	//--------------------------------------------------------------------------
	// セグメント演算
	//--------------------------------------------------------------------------
	/**
	 * ゼロセグメントかどうか
	 * @return ゼロセグメントならtrue
	 */
	inline bool isZero() const{
		return (direction_.epsilonEquals(Vector3::zero, Math::epsilon));
	}

	//--------------------------------------------------------------------------
	// トランスフォーム
	//--------------------------------------------------------------------------
	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後のセグメント
	 */
	inline Segment transform(const Matrix33& matrix) const{
		return Segment(matrix * origin_, matrix * direction_);
	}

	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後のセグメント
	 */
	inline Segment transform(const Matrix34& matrix) const{
		return Segment(matrix * origin_, matrix.multiply33(direction_));
	}

	/**
	 * トランスフォーム
	 * @param matrix 乗算する行列
	 * @return 変換後のセグメント
	 */
	inline Segment transform(const Matrix44& matrix) const{
		return Segment(matrix * origin_, matrix.multiply33(direction_));
	}

	//--------------------------------------------------------------------------
	// 距離
	//--------------------------------------------------------------------------
	/**
	 * 点距離
	 * @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 交差判定する点
	 * @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 交差判定するライン
	 * @param range 交差範囲
	 * @return 交差していればtrue
	 */
	bool intersect(const Line& line, float range = Math::epsilon) const;

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

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

	//--------------------------------------------------------------------------
	/**
	 * レイ交差
	 * @param ray 交差判定するレイ
	 * @param range 交差範囲
	 * @return 交差していればtrue
	 */
	bool intersect(const Ray& ray, float range = Math::epsilon) const;

	//--------------------------------------------------------------------------
	/**
	 * セグメント交差
	 * @param segment 交差判定するセグメント
	 * @param range 交差範囲
	 * @return 交差していればtrue
	 */
	bool intersect(const Segment& segment, float range = Math::epsilon) 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 Segment& target) const{
		return ((origin_ == target.origin_) &&
			(direction_ == target.direction_));
	}

	/**
	 * セグメントが同じかどうか
	 * @param target 比較するセグメント
	 * @param epsilon 誤差
	 * @return 誤差の範囲内で同じ値であればtrueを返す
	 */
	inline bool epsilonEquals(
		const Segment& target, float epsilon) const{
		Assert(epsilon >= 0.f);
		return (origin_.epsilonEquals(target.origin_, epsilon) &&
			direction_.epsilonEquals(target.direction_, epsilon));
	}

	/**
	 * セグメントが同じでないかどうか
	 * @param target 比較するセグメント
	 * @return 同じでない値であればtrueを返す
	 */
	inline bool operator !=(const Segment& target) const{
		return ((origin_ != target.origin_) ||
			(direction_ != target.direction_));
	}

	/**
	 * セグメントが同じでないかどうか
	 * @param target 比較するセグメント
	 * @param epsilon 誤差
	 * @return 誤差の範囲内で同じでない値であればtrueを返す
	 */
	inline bool notEpsilonEquals(
		const Segment& target, float epsilon) const{
		Assert(epsilon >= 0.f);
		return (origin_.notEpsilonEquals(target.origin_, epsilon) ||
			direction_.notEpsilonEquals(target.direction_, epsilon));
	}

	//--------------------------------------------------------------------------
	// その他
	//--------------------------------------------------------------------------
	/**
	 * 文字列化
	 * @return セグメントの文字列表記
	 */
	inline String toString() const{
		String returnString;
		returnString.format("{ ( %.8f, %.8f, %.8f ) ( %.8f, %.8f, %.8f ) }",
			origin_.x, origin_.y, origin_.z,
			direction_.x, direction_.y, direction_.z);
		return returnString;
	}

private:
	//--------------------------------------------------------------------------
	// メンバ変数
	//--------------------------------------------------------------------------
	// 原点
	Vector3 origin_;
	// 方向
	Vector3 direction_;

};

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