//------------------------------------------------------------------------------
// 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/Model/CharacterModel.h"
#include "Graphics/Scene/Scene.h"
#include "Graphics/Model/ModelManager.h"
#include "Graphics/Mesh/Mesh.h"

namespace Lamp{

//------------------------------------------------------------------------------
// コンストラクタ
CharacterModel::CharacterModel(const String& name, Scene* scene) :
	Model(name, scene), boneHash_(maxBoneCount, 1.f), boneArray_(maxBoneCount),
	positionDeformMatrixArray_(NULL), normalDeformMatrixArray_(NULL),
	deformMatrixArraySize_(0), boneScaled_(false){
	buildBoneMatrixTick_ = scene_->getTick() - 1;
	deformMatrixArrayTick_ = scene_->getTick() - 1;
}
//------------------------------------------------------------------------------
// デストラクタ
CharacterModel::~CharacterModel(){
	SafeArrayDelete(normalDeformMatrixArray_);
	SafeArrayDelete(positionDeformMatrixArray_);
	clearBone();
}
//------------------------------------------------------------------------------
// キャラクタモデルのコピー
CharacterModel* CharacterModel::copyCharacterModel(u_int copyMask) const{
	ModelManager* manager = scene_->getModelManager();
	CharacterModel* copyModel =
		manager->createCharacterModel(manager->rename(name_));
	// モデルの値コピー
	copyModelValue(copyModel, copyMask);
	// ボーンのコピー
	int boneCount = getBoneCount();
	for(int i = 0; i < boneCount; i++){
		Bone* sourceBone = getBone(i);
		Bone* destinationBone = copyModel->createBone(sourceBone->getName());
		sourceBone->copyBoneValue(destinationBone);
	}
	// ボーンのリンクコピー
	for(int i = 0; i < boneCount; i++){
		Bone* sourceBone = getBone(i);
		Bone* destinationBone = copyModel->getBone(i);
		int childCount = sourceBone->getBoneCount();
		for(int j = 0; j < childCount; j++){
			Bone* childBone = copyModel->searchBone(
				sourceBone->getBone(j)->getName());
			destinationBone->addBone(childBone);
		}
	}
	return copyModel;
}
//------------------------------------------------------------------------------
// ボーン行列の構築
void CharacterModel::buildBoneMatrix(bool forceCalculation){
	// 強制計算フラグが立ってなく、シーンチックが同じであれば計算しない
	u_int sceneTick = scene_->getTick();
	if((!forceCalculation) && (buildBoneMatrixTick_ == sceneTick)){ return; }
	buildBoneMatrixTick_ = sceneTick;
	boneScaled_ = getBone(0)->buildBoneMatrix(Matrix34::unit);
}
//------------------------------------------------------------------------------
// 位置変形行列配列の取得
const Matrix34* CharacterModel::getPositionDeformMatrixArray(
	bool forceCalculation){
	// 強制計算フラグが立ってなく、シーンチックが同じであれば計算しない
	if((!forceCalculation) && (deformMatrixArrayTick_ == scene_->getTick())){
		return positionDeformMatrixArray_;
	}
	// 変形行列配列の構築
	buildDeformMatrixArray();
	return positionDeformMatrixArray_;
}
//------------------------------------------------------------------------------
// 法線変形行列配列の取得
const Matrix33* CharacterModel::getNormalDeformMatrixArray(
	bool forceCalculation){
	Assert(boneScaled_);
	// 強制計算フラグが立ってなく、シーンチックが同じであれば計算しない
	if((!forceCalculation) && (deformMatrixArrayTick_ == scene_->getTick())){
		return normalDeformMatrixArray_;
	}
	// 変形行列配列の構築
	buildDeformMatrixArray();
	return normalDeformMatrixArray_;
}
//------------------------------------------------------------------------------
// 変形行列配列の構築
void CharacterModel::buildDeformMatrixArray(){
	// 配列の確保
	int boneCount = getBoneCount();
	if((positionDeformMatrixArray_ == NULL) ||
		(deformMatrixArraySize_ != boneCount)){
		SafeArrayDelete(positionDeformMatrixArray_);
		deformMatrixArraySize_ = boneCount;
		positionDeformMatrixArray_ = new Matrix34[deformMatrixArraySize_];
		if(boneScaled_){
			normalDeformMatrixArray_ = new Matrix33[deformMatrixArraySize_];
		}
	}
	// データ設定
	for(int i = 0; i < boneCount; i++){
		positionDeformMatrixArray_[i] = getBone(i)->getDeformMatrix();
	}
	if(boneScaled_){
		for(int i = 0; i < boneCount; i++){
			normalDeformMatrixArray_[i].set(positionDeformMatrixArray_[i]);
			normalDeformMatrixArray_[i].invert();
			normalDeformMatrixArray_[i].transpose();
		}
	}
	deformMatrixArrayTick_ = scene_->getTick();
}
//------------------------------------------------------------------------------
// ボーンの作成
Bone* CharacterModel::createBone(const String& boneName){
	Assert(boneArray_.getCount() < maxBoneCount);
	// 名前のサイズチェック
	if(boneName.getSize() == 0){
		ErrorOut("CharacterModel::createBone() boneName.getSize() == 0");
	}
	Bone* bone = new Bone(boneName);
	if(!boneHash_.put(boneName, bone)){
		// 名前に重複があった
		ErrorOut("CharacterModel::createBone() repetition name %s",
			boneName.getBytes());
		delete bone;
		return NULL;
	}
	boneArray_.add(bone);
	return bone;
}
//------------------------------------------------------------------------------
// ボーンの破棄
void CharacterModel::destroyBone(Bone* bone){
	if(boneHash_.remove(bone->getName()) == NULL){
		ErrorOut("CharacterModel::destroyBone() not found %s",
			bone->getName().getBytes());
	}
	boneArray_.removeByValue(bone);
	delete bone;
}
//------------------------------------------------------------------------------
// ボーンのクリア
int CharacterModel::clearBone(){
	int result = getBoneCount();
	for(int i = 0; i < result; i++){ delete getBone(i); }
	boneHash_.clear();
	boneArray_.clear();
	return result;
}
//------------------------------------------------------------------------------
// メッシュの追加
void CharacterModel::addMesh(Mesh* mesh){
	Assert(mesh->isCharacterMesh());
	Model::addMesh(mesh);
}
//------------------------------------------------------------------------------
} // End of namespace Lamp
//------------------------------------------------------------------------------
