//------------------------------------------------------------------------------
// 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 "Graphics/Camera/Camera.h"
#include "Graphics/Scene/Scene.h"
#include "Graphics/Camera/CameraManager.h"

namespace Lamp{

//------------------------------------------------------------------------------
// コンストラクタ
Camera::Camera(const String& name, Scene* scene) :
	SceneObject(name, scene),
	projectionMatrix_(Matrix44::unit), viewMatrix_(Matrix44::unit),
	left_(-0.76980036f), right_(0.76980036f),
	bottom_(-0.57735026f), top_(0.57735026f),
	nearClip_(1.f), farClip_(10000.f), isPerspective_(true){
	buildPerspectiveMatrix();
}
//------------------------------------------------------------------------------
// デストラクタ
Camera::~Camera(){
}
//------------------------------------------------------------------------------
// コピー
Camera* Camera::copy() const{
	CameraManager* manager = scene_->getCameraManager();
	Camera* copyCamera = manager->createCamera(manager->rename(name_));
	return copyCamera;
}
//------------------------------------------------------------------------------
// 射影行列の構築
void Camera::buildPerspectiveMatrix(){
	// 射影行列の構築
	if(isPerspective_){
		float zx = (right_ + left_) / (right_ - left_);
		float zy = (top_ + bottom_) / (top_ - bottom_);
		projectionMatrix_.set(
			2.f * nearClip_ / (right_ - left_), 0.f, zx, 0.f,
			0.f, 2.f * nearClip_ / (top_ - bottom_), zy, 0.f,
			0.f, 0.f, farClip_ / (nearClip_ - farClip_),
				nearClip_ * farClip_ / (nearClip_ - farClip_),
			0.f, 0.f, -1.f, 0.f);
	}else{
		float wx = (left_ + right_) / (left_ - right_);
		float wy = (bottom_ + top_) / (bottom_ - top_);
		projectionMatrix_.set(
			2.f / (right_ - left_), 0.f, 0.f, wx,
			0.f, 2.f / (top_ - bottom_), 0.f, wy,
			0.f, 0.f, 1.f / (nearClip_ - farClip_),
				nearClip_ / (nearClip_ - farClip_),
			0.f, 0.f, 0.f, 1.f);
	}
	clippingSetup();
}
//------------------------------------------------------------------------------
// ビュー行列の設定
void Camera::setViewMatrix(const Matrix44& viewMatrix){
	viewMatrix_ = viewMatrix;
	clippingSetup();
}
//------------------------------------------------------------------------------
// トランスフォーメーションの設定
void Camera::setTransformation(
	const Vector3& rotationXYZ, const Vector3& position){
	viewMatrix_.setTranslation(-position);
	viewMatrix_.addRotationZYX(-rotationXYZ);
	clippingSetup();
}
//------------------------------------------------------------------------------
// トランスフォーメーションの設定
void Camera::setTransformation(
	const Quaternion& rotation, const Vector3& position){
	viewMatrix_.setTranslation(-position);
	Quaternion invertRotation(rotation);
	invertRotation.unitInvert();
	viewMatrix_.addRotationQuaternion(invertRotation);
	clippingSetup();
}
//------------------------------------------------------------------------------
// ルックアットの設定
void Camera::setLookAt(
	const Vector3& position, const Vector3& target, const Vector3& up){
	Vector3 zAxis = position - target;
	zAxis.normalize();
	Vector3 xAxis = up.crossProduct(zAxis);
	xAxis.normalize();
	Vector3 yAxis = zAxis.crossProduct(xAxis);
	viewMatrix_.set(
		xAxis.x, xAxis.y, xAxis.z, -xAxis.dotProduct(position),
		yAxis.x, yAxis.y, yAxis.z, -yAxis.dotProduct(position),
		zAxis.x, zAxis.y, zAxis.z, -zAxis.dotProduct(position),
		0.f, 0.f, 0.f, 1.f);
	clippingSetup();
	Assert(position_.epsilonEquals(position, 0.001f));
}
//------------------------------------------------------------------------------
// クリッピングのセットアップ
void Camera::clippingSetup(){
	// コーナーの設定
	corner_[0].set(left_, bottom_, -nearClip_);
	corner_[1].set(left_, top_, -nearClip_);
	corner_[2].set(right_, top_, -nearClip_);
	corner_[3].set(right_, bottom_, -nearClip_);
	if(isPerspective_){
		float scale = farClip_ / nearClip_;
		corner_[4] = corner_[2] * scale;
		corner_[5] = corner_[1] * scale;
		corner_[6] = corner_[0] * scale;
		corner_[7] = corner_[3] * scale;
	}else{
		corner_[4].set(right_, top_, -farClip_);
		corner_[5].set(left_, top_, -farClip_);
		corner_[6].set(left_, bottom_, -farClip_);
		corner_[7].set(right_, bottom_, -farClip_);
	}
	Matrix44 inverseView(viewMatrix_);
	inverseView.invert();
	for(int i = 0; i < 8; i++){
		corner_[i] = inverseView * corner_[i];
	}
	// クリップ面の設定
	clipPlane_[0].set(corner_[0], corner_[1], corner_[2]);
	clipPlane_[1].set(corner_[4], corner_[5], corner_[6]);
	clipPlane_[2].set(corner_[0], corner_[6], corner_[5]);
	clipPlane_[3].set(corner_[2], corner_[4], corner_[7]);
	clipPlane_[4].set(corner_[0], corner_[3], corner_[7]);
	clipPlane_[5].set(corner_[1], corner_[5], corner_[4]);

	// 位置、回転の設定
	position_ = inverseView.getTranslation();
	inverseView.getRotationXYZ(&rotation_);

	// バウンディングスフィアの作成
	Vector3 center;
	float radius;
	if(isPerspective_){
		// パースペクティブな場合は中心がずれていることも考慮しないといけない
		Vector3 nearClip(Vector3::zero), farClip(Vector3::zero);
		for(int i = 0; i < 4; i++){ nearClip += corner_[i]; }
		for(int i = 4; i < 8; i++){ farClip += corner_[i]; }
		nearClip *= 0.25f;
		farClip *= 0.25f;
		// ニアクリップの中心とファークリップの中心の間を地道にしらべる
		int divisionNumber = 32;
		Vector3 divisionVector = (farClip - nearClip) * (1.f / divisionNumber);
		center = nearClip;
		radius = (corner_[0] - center).getSquaredLength();
		for(int i = 1; i < 8; i++){
			float squaredRadius = (corner_[i] - center).getSquaredLength();
			if(squaredRadius > radius){ radius = squaredRadius; }
		}
		for(int i = 1; i <= divisionNumber; i++){
			Vector3 position = nearClip + divisionVector * (float)i;
			float maxRadius = (corner_[0] - position).getSquaredLength();
			for(int j = 1; j < 8; j++){
				float squaredRadius =
					(corner_[j] - position).getSquaredLength();
				if(squaredRadius > maxRadius){ maxRadius = squaredRadius; }
			}
			if(maxRadius < radius){
				center = position;
				radius = maxRadius;
			}
		}
		radius = Math::sqrt(radius);
	}else{
		// 並行投影ならどんな設定でも1頂点との距離をとればよい
		center = Vector3::zero;
		for(int i = 0; i < 8; i++){ center += corner_[i]; }
		center *= 0.125f;
		radius = (corner_[0] - center).getLength();
	}
	boundingSphere_.set(center, radius);
}
//------------------------------------------------------------------------------
// クリッピング
Clipping::State Camera::clipping(const Sphere& boundingSphere){
	// カメラのバウンディングスフィアと交差していなければ見えない
	if(!boundingSphere_.intersect(boundingSphere)){
		return Clipping::invisible;
	}
	bool intersect = false;
	Vector3 center = boundingSphere.getCenter();
	float radius = boundingSphere.getRadius();
	for(int i = 0; i < 6; i++){
		float distance = clipPlane_[i].getDistance(center);
		// 距離がマイナス半径未満ならば見えない
		if(distance < -radius){ return Clipping::invisible; }
		// 距離が半径未満なら一部しか見えない可能性がある
		if(distance < radius){ intersect = true; }
	}
	if(intersect){ return Clipping::intersect; }
	return Clipping::visible;
}
//------------------------------------------------------------------------------
// クリッピング
Clipping::State Camera::clipping(const AxisAlignedBox& boundingBox){
	// 完全ではない。視錐台の外側で平面に交差するボックスが残る。
	// 遅くなるかもしれないが、Segmentを使用すれば完全になるかも。
	// スフィアチェックのあとに使用する。
	Vector3 corner[8];
	boundingBox.getCornerArray(corner);
	int visibleCount = 0;
	for(int i = 0; i < 6; i++){
		bool visible = true;
		int invisibleCount = 0;
		for(int j = 0; j < 8; j++){
			float distance = clipPlane_[i].getDistance(corner[j]);
			if(distance < 0.f){
				visible = false;
				invisibleCount++;
			}
		}
		if(invisibleCount == 8){ return Clipping::invisible; }
		if(visible){ visibleCount++; }
	}
	if(visibleCount == 6){ return Clipping::visible; }
	return Clipping::intersect;
}
//------------------------------------------------------------------------------
} // End of namespace Lamp
//------------------------------------------------------------------------------
