//------------------------------------------------------------------------------
// 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 "System/stdafx.h"
#include "Translator/Model/TranslationCharacterModel.h"
#include "Translator/Model/TranslationBone.h"
#include "Translator/Mesh/TranslationMeshManager.h"
#include "Graphics/Scene/Scene.h"
#include "Graphics/Model/ModelManager.h"
#include "Graphics/Mesh/MeshManager.h"
#include "Animation/System/AnimationManager.h"
#include "Animation/System/AnimationSet.h"
#include "Animation/Model/CharacterModelAnimation.h"
#include "Animation/VectorInterpolator/VectorArrayInterpolator.h"
#include "Animation/RotationInterpolator/EulerArrayInterpolator.h"

namespace LampForMaya{

//------------------------------------------------------------------------------
// コンストラクタ
TranslationCharacterModel::TranslationCharacterModel(
	const MObject& initializeObject, const String& initializeName,
	const MObject& skinClusterObject) :
	TranslationModel(initializeObject, initializeName),
	skinClusterObject_(skinClusterObject),
	transBones_(NULL), transBoneCount_(0), transMeshes_(NULL){
	MayaStatusCheck(skinCluster_.setObject(skinClusterObject_));
}
//------------------------------------------------------------------------------
// デストラクタ
TranslationCharacterModel::~TranslationCharacterModel(){
	SafeArrayDelete(transBones_);
	SafeArrayDelete(transMeshes_);
}
//------------------------------------------------------------------------------
// 分析
bool TranslationCharacterModel::analyze(TranslationMeshManager* meshManager){
	// モデルの分析
	if(!analyzeModel()){ return false; }
	// 複数ジオメトリへのボーン変形は対応しない
	if(skinCluster_.numOutputConnections() != 1){
		MayaErrorOut(String("TranslationCharacterModel::analyze() ") + 
			name_ + "に複数のモデルが接続されています");
		return false;
	}
	// ボーンのアナライズ
	if(!analyzeBone()){ return false; }
	// メッシュのアナライズ
	if(!analyzeMesh(meshManager)){ return false; }
	// ウェイトのアナライズ
	if(!analyzeWeight()){ return false; }
	// メッシュの論理チェック
	for(int i = 0; i < meshes_.getCount(); i++){
		if(!meshes_.get(i)->logicalCheck()){ return false; }
	}
	return true;
}
//------------------------------------------------------------------------------
// ボーンのアナライズ
bool TranslationCharacterModel::analyzeBone(){
	MStatus result;
	String errorMessage;
	// ボーン数の調査
	MDagPathArray bonePathArray;
	transBoneCount_ = skinCluster_.influenceObjects(bonePathArray, &result);
	MayaStatusCheck(result);
	if(transBoneCount_ > CharacterModel::maxBoneCount){
		errorMessage.format("TranslationCharacterModel::analyzeBone() "
			"%sのボーン数が多すぎます ( %d / %d )",
			name_.getBytes(), transBoneCount_, CharacterModel::maxBoneCount);
		MayaErrorOut(errorMessage);
		return false;
	}else if(transBoneCount_ == 0){
		MayaErrorOut(String("TranslationCharacterModel::analyzeBone() ") +
			name_ + "にボーンがありません");
		return false;
	}
	// ボーンの構築
	transBones_ = new TranslationBone[transBoneCount_];
	for(int i = 0; i < transBoneCount_; i++){
		transBones_[i].analyze(bonePathArray[i]);
	}
	// 逆変換行列の算出
	transBones_[0].buildModelMatrix(this, Matrix34::unit);
	return true;
}
//------------------------------------------------------------------------------
// 変換ボーンの検索
TranslationBone* TranslationCharacterModel::searchBone(const String& boneName){
	// とりあえず線形探索。最大で256本なので多分大丈夫。問題があればHashMapにする。
	for(int i = 0; i < transBoneCount_; i++){
		if(transBones_[i].getName().equals(boneName)){
			return &transBones_[i];
		}
	}
	return NULL;
}
//------------------------------------------------------------------------------
// メッシュのアナライズ
bool TranslationCharacterModel::analyzeMesh(TranslationMeshManager* meshManager){
	MStatus result;
	String errorString;
	MFnMesh fnMesh(object_, &result);
	MayaStatusCheck(result);

	// UVセット数の取得
	MStringArray uvSetNames;
	MayaStatusCheck(fnMesh.getUVSetNames(uvSetNames));
	int uvSetCount = uvSetNames.length();

	// シェーダ情報の取得
	MObjectArray shaders;
	MIntArray shaderIndices;
	// 常にインスタンス番号0番のモデルデータを使用する
	result = fnMesh.getConnectedShaders(0, shaders, shaderIndices);
	MayaStatusCheck(result);

	// シェーダが割り当てられていないポリゴンがあればエラー
	for(u_int i = 0; i < shaderIndices.length(); i++){
		if(shaderIndices[i] == -1){
			MIntArray vertexIndex;
			MayaStatusCheck(fnMesh.getPolygonVertices(i, vertexIndex));
			int vertexCount = vertexIndex.length();
			errorString.format("TranslationCharacterModel::analyze() "
				"%sにシェーダの割り当てられいないポリゴンがあります ",
				name_.getBytes());
			for(int i = 0; i < vertexCount; i++){
				MPoint position;
				MayaStatusCheck(fnMesh.getPoint(vertexIndex[i], position));
				String temp;
				temp.format(" ( %.3f , %.3f , %.3f)",
					position.x, position.y, position.z);
				errorString += temp;
			}
			MayaErrorOut(errorString);
			return false;
		}
	}

	// シェーダの数だけメッシュを用意する
	transMeshCount_ = shaders.length();
	int nameCount = 0;
	transMeshes_ = new TranslationCharacterMesh*[transMeshCount_];
	for(int i = 0; i < transMeshCount_; i++){
		// 重複の無い名前を作成
		String meshName;
		while(true){
			meshName.format("%sM%d", name_.getBytes(), nameCount);
			nameCount++;
			TranslationMesh* exist = meshManager->search(meshName);
			if(exist == NULL){ break; }
		}
		transMeshes_[i] = meshManager->createCharacterMesh(meshName);
		// モデルにメッシュを追加する
		meshes_.add(transMeshes_[i]);
		// UVセット数を設定する
		transMeshes_[i]->setUVSetCount(uvSetCount);
		// シェーダ名を設定する
		transMeshes_[i]->setMaterialName(getShaderName(shaders[i]));
	}

	// ポリゴンはオリジナルメッシュから取得
	MObject inputShapeObject = skinCluster_.inputShapeAtIndex(0, &result);
	MayaStatusCheck(result);
	// ポリゴンをメッシュに割り振る
	MItMeshPolygon polygonIterator(inputShapeObject, &result);
	MayaStatusCheck(result);
	int polygonCount = 0;
	for( ; !polygonIterator.isDone(); polygonIterator.next()){
		int shaderIndex = shaderIndices[polygonCount];
		TranslationCharacterMesh* mesh = transMeshes_[shaderIndex];
		polygonCount++;
		// 位置
		MPointArray positions;
		polygonIterator.getPoints(positions, MSpace::kObject, &result);
		MayaStatusCheck(result);
// Weightのアナライズとの整合性を取る必要がある
//		bool isSquare = (positions.length() == 4);
//		if((positions.length() != 3) && (!isSquare)){
		if(positions.length() != 3){
			errorString.format("TranslationStandardModel::analyze() "
//				"%sが三角、四角以外のポリゴン(%d角形)を持っています\n",
				"%sが三角以外のポリゴン(%d角形)を持っています\n",
				name_.getBytes(), positions.length());
			for(u_int i = 0; i < positions.length(); i++){
				String temp;
				temp.format(" %d ( %.3f , %.3f , %.3f)",
					i, positions[i].x, positions[i].y, positions[i].z);
				errorString += temp;
			}
			MayaErrorOut(errorString);
			return false;
		}
		mesh->addPosition(Vector3((float)positions[0].x,
			(float)positions[0].y, (float)positions[0].z));
		mesh->addPosition(Vector3((float)positions[1].x,
			(float)positions[1].y, (float)positions[1].z));
		mesh->addPosition(Vector3((float)positions[2].x,
			(float)positions[2].y, (float)positions[2].z));
/*
		if(isSquare){
			mesh->addPosition(Vector3((float)positions[0].x,
				(float)positions[0].y, (float)positions[0].z));
			mesh->addPosition(Vector3((float)positions[2].x,
				(float)positions[2].y, (float)positions[2].z));
			mesh->addPosition(Vector3((float)positions[3].x,
				(float)positions[3].y, (float)positions[3].z));
		}
*/

		// 法線
		MVectorArray normals;
		result = polygonIterator.getNormals(normals, MSpace::kObject);
		MayaStatusCheck(result);
		mesh->addNormal(Vector3(
			(float)normals[0].x, (float)normals[0].y, (float)normals[0].z));
		mesh->addNormal(Vector3(
			(float)normals[1].x, (float)normals[1].y, (float)normals[1].z));
		mesh->addNormal(Vector3(
			(float)normals[2].x, (float)normals[2].y, (float)normals[2].z));
/*
		if(isSquare){
			mesh->addNormal(Vector3(
				(float)normals[0].x, (float)normals[0].y, (float)normals[0].z));
			mesh->addNormal(Vector3(
				(float)normals[2].x, (float)normals[2].y, (float)normals[2].z));
			mesh->addNormal(Vector3(
				(float)normals[3].x, (float)normals[3].y, (float)normals[3].z));
		}
*/

		// カラー
		bool hasColor = polygonIterator.hasColor(&result);
		MayaStatusCheck(result);
		if(hasColor){
			MColorArray colors;
			result = polygonIterator.getColors(colors);
			MayaStatusCheck(result);
			mesh->addColor(Color4f(
				colors[0].r, colors[0].g, colors[0].b, colors[0].a));
			mesh->addColor(Color4f(
				colors[1].r, colors[1].g, colors[1].b, colors[1].a));
			mesh->addColor(Color4f(
				colors[2].r, colors[2].g, colors[2].b, colors[2].a));
/*
			if(isSquare){
				mesh->addColor(Color4f(
					colors[0].r, colors[0].g, colors[0].b, colors[0].a));
				mesh->addColor(Color4f(
					colors[2].r, colors[2].g, colors[2].b, colors[2].a));
				mesh->addColor(Color4f(
					colors[3].r, colors[3].g, colors[3].b, colors[3].a));
			}
*/
		}

		// UV
		MFloatArray uArray, vArray;
		for(int i = 0; i < uvSetCount; i++){
			MayaStatusCheck(polygonIterator.getUVs(
				uArray, vArray, &uvSetNames[i]));
			// V座標を反転する
			mesh->addUV(TexCoord2(uArray[0], (vArray[0] - 1.f) * -1.f));
			mesh->addUV(TexCoord2(uArray[1], (vArray[1] - 1.f) * -1.f));
			mesh->addUV(TexCoord2(uArray[2], (vArray[2] - 1.f) * -1.f));
		}
/*
		if(isSquare){
			for(int i = 0; i < uvSetCount; i++){
			MayaStatusCheck(polygonIterator.getUVs(
				uArray, vArray, &uvSetNames[i]));
			// V座標を反転する
			mesh->addUV(TexCoord2(uArray[0], (vArray[0] - 1.f) * -1.f));
			mesh->addUV(TexCoord2(uArray[2], (vArray[2] - 1.f) * -1.f));
			mesh->addUV(TexCoord2(uArray[3], (vArray[3] - 1.f) * -1.f));
			}
		}
*/

		// 頂点インデックス
		u_int index0 = polygonIterator.vertexIndex(0, &result);
		u_int index1 = polygonIterator.vertexIndex(1, &result);
		u_int index2 = polygonIterator.vertexIndex(2, &result);
		mesh->addIndex(index0);
		mesh->addIndex(index1);
		mesh->addIndex(index2);
/*
		if(isSquare){
			u_int index3 = polygonIterator.vertexIndex(3, &result);
			mesh->addIndex(index0);
			mesh->addIndex(index2);
			mesh->addIndex(index3);
		}
*/

// ここでWeightを収集できれば四角対応可能
// MItMeshPolygon::polygon()をMFnSkinCluster::getWeights()に突っ込んでなんとかならないか
	}
	return true;
}
//------------------------------------------------------------------------------
// ウェイトのアナライズ
bool TranslationCharacterModel::analyzeWeight(){
	MStatus result;
	String errorMessage;
	bool returnValue = true;
	// ジオメトリイテレータを取得する
	u_int index = skinCluster_.indexForOutputConnection(0, &result);
	MayaStatusCheck(result);
	MDagPath skinPath;
	MayaStatusCheck(skinCluster_.getPathAtIndex(index, skinPath));
	MItGeometry geometries(skinPath);
	// ジオメトリイテレータからコンポーネントを取得し、Weightを入手する
	int vertexCount = geometries.count();
	float* weights = new float[transBoneCount_ * vertexCount];
	int * weightCounts = new int[vertexCount];
	u_int vertexCounter = 0;
	for( ; !geometries.isDone(); geometries.next(), vertexCounter++){
		MObject component = geometries.component(&result);
		MayaStatusCheck(result);
		MFloatArray weightArray;
		u_int boneCount;
		MayaStatusCheck(skinCluster_.getWeights(
			skinPath, component, weightArray, boneCount));
		if(transBoneCount_ != boneCount){
			MayaErrorOut(String("TranslationCharacterModel::analyzeWeight() ") + 
				name_ + "の頂点ボーン数と出力ボーン数に違いがあります");
			returnValue = false;
			break;
		}
		int offset = vertexCounter * transBoneCount_;
		weightCounts[vertexCounter] = 0;
		float totalWeight = 0.f;
		for(int i = 0; i < transBoneCount_; i++){
			weights[offset + i] = weightArray[i];
			// 0.1%以下のウェイトは無視する
			if(weights[offset + i] < 0.001f){ weights[offset + i] = 0.f; }
			if(weights[offset + i] != 0.f){ weightCounts[vertexCounter]++; }
			totalWeight += weights[offset + i];
		}
		// 頂点に対するウェイト数の範囲チェック
		if((weightCounts[vertexCounter] > CharacterMesh::maxWeightPerVertex) ||
			(weightCounts[vertexCounter] <= 0)){
			errorMessage.format("TranslationCharacterModel::analyzeWeight() "
				"%sの%d番目の頂点に%d個のウェイトが割り付けられています",
				name_.getBytes(), vertexCounter, weightCounts[vertexCounter]);
			MayaErrorOut(errorMessage);
			returnValue = false;
			break;
		}
		// ウェイト合計が1であるかチェック
		if(Math::abs(totalWeight - 1.f) > 0.001f){
			errorMessage.format("TranslationCharacterModel::analyzeWeight() "
				"%sの%d番目の頂点のウェイト合計が1でありません",
				name_.getBytes(), vertexCounter, totalWeight);
			MayaErrorOut(errorMessage);
			returnValue = false;
			break;
		}
	}
	if(!returnValue){
		delete[] weights;
		delete[] weightCounts;
		return returnValue;
	}
	// スティッチングフラグの取得
	MFnDependencyNode node(object_, &result);
	MayaStatusCheck(result);
	MObject stitchingAttribute = node.attribute("LampStitching", &result);
	bool stitchingFlag = false;
	if(result){
		MayaStatusCheck(result);
		MPlug stitchingPlug(object_, stitchingAttribute);
		result = stitchingPlug.getValue(stitchingFlag);
		MayaStatusCheck(result);
	}
	// 強制スティッチング化
	if(stitchingFlag){
		for(int i = 0; i < vertexCount; i++){
			int offset = i * transBoneCount_;
			int maxWeightIndex = 0;
			float maxWeight = weights[offset];
			weights[offset] = 0.f;
			for(int j = 1; j < transBoneCount_; j++){
				if(weights[offset + j] > maxWeight){
					maxWeight = weights[offset + j];
					maxWeightIndex = j;
				}
				weights[offset + j] = 0.f;
			}
			// 最大のWeightのみ1にする
			weights[offset + maxWeightIndex] = 1.f;
			weightCounts[i] = 1;
		}
	}
	// ウェイトデータの設定
	if(returnValue){
		for(int i = 0; i < transMeshCount_; i++){
			if(!transMeshes_[i]->setWeights(
				transBoneCount_, weights, weightCounts)){
				returnValue = false;
				break;
			}
		}
	}
	delete[] weights;
	delete[] weightCounts;
	return returnValue;
}
//------------------------------------------------------------------------------
// アニメーションの分析
bool TranslationCharacterModel::analyzeAnimation(){
	// シーケンスの分析、シーケンスが無ければアニメーション無し
	if(!sequence_.analyze(object_)){ return true; }
	int startTime = sequence_.getStartTime(0);
	int endTime = sequence_.getEndTime(sequence_.getSequenceCount() - 1);
	for(int i = 0; i < transBoneCount_; i++){
		transBones_[i].analyzeAnimation(startTime, endTime);
	}
	return true;
}
//------------------------------------------------------------------------------
// Lampへの変換
bool TranslationCharacterModel::convertToLamp(Scene* scene){
	ModelManager* modelManager = scene->getModelManager();
	CharacterModel* model = modelManager->createCharacterModel(name_);
	// 有効、無効フラグ
	model->setEnabled(visibility_);
	// メッシュとのリンク
	MeshManager* meshManager = scene->getMeshManager();
	int meshCount = meshes_.getCount();
	for(int i = 0; i < meshCount; i++){
		String meshName = meshes_.get(i)->getName();
		Mesh* mesh = meshManager->search(meshName);
		if(mesh == NULL){
			MayaErrorOut(String("TranslationCharacterModel::convertToLamp() "
				"メッシュが見つかりません ") + meshName);
			return false;
		}
		model->addMesh(mesh);
	}
	// ボーンの変換
	for(int i = 0; i < transBoneCount_; i++){
		if(!transBones_[i].convertToLamp(model)){ return false; }
	}
	// ボーンのリンク
	for(int i = 0; i < transBoneCount_; i++){
		if(!transBones_[i].boneLink(model, i)){ return false; }
	}
	return true;
}
//------------------------------------------------------------------------------
// アニメーションの変換
bool TranslationCharacterModel::convertAnimation(
	AnimationManager* animationManager, AnimationSet* animationSet){
	// シーケンスが無ければアニメーション無し
	if(!sequence_.hasSequence()){ return true; }
	CharacterModelAnimation* animation =
		animationManager->createCharacterModel(name_);
	if(animation->getName() != name_){
		MayaErrorOut("TranslationCharacterModel::convertAnimation() " +
			name_ + "の名前が重複しています ");
		return false;
	}
	CharacterModelAnimationData* data =
		animationManager->createCharacterModelData(name_);
	if(data->getName() != name_){
		MayaErrorOut("TranslationCharacterModel::convertAnimation() " +
			name_ + "の名前が重複しています ");
		return false;
	}
	// アニメーションの設定
	animation->setTargetName(name_);
	animation->setCharacterModelAnimationData(data);
	animation->setBoneCount(transBoneCount_);
	for(int i = 0; i < transBoneCount_; i++){
		animation->setBoneName(i, transBones_[i].getName());
	}
	animationSet->addAnimation(animation);
	// アニメーションデータの設定
	data->setBoneCount(transBoneCount_);
	int sequenceCount = sequence_.getSequenceCount();
	data->setSequenceCount(sequenceCount);
	for(int i = 0; i < sequenceCount; i++){
		int startTime = sequence_.getStartTime(i);
		int endTime = sequence_.getEndTime(i);
		for(int j = 0; j < transBoneCount_; j++){
			TranslationBone& bone = transBones_[j];
			// スケールアニメーション
			VectorInterpolator* scale =
				bone.getScaleAnimation(startTime, endTime);
			if(scale != NULL){ data->setScale(i, j, scale); }
			// 回転アニメーション
			RotationInterpolator* rotation =
				bone.getRotationAnimation(startTime, endTime);
			if(rotation != NULL){ data->setRotation(i, j, rotation); }
			// 移動アニメーション
			VectorInterpolator* translation =
				bone.getTranslationAnimation(startTime, endTime);
			if(translation != NULL){ data->setTranslation(i, j, translation); }
		}
		data->setLooped(i, sequence_.isLooped(i));
	}
	return true;
}
//------------------------------------------------------------------------------
} // End of namespace LampForMaya
//------------------------------------------------------------------------------
