/*
 *	ExportMesh.cpp
 *
 *	P/ECE CLiP Library 3D Object Exporter
 *	Copyright (C) 2006 Naoyuki Sawa
 *
 *	* Thu Jun 29 11:46:08 JST 2006 Naoyuki Sawa
 *	- メッシュエクスポートを別ファイルに分離しました。
 *	* Wed Jul 26 06:27:42 JST 2006 Naoyuki Sawa
 *	- オプションダイアログにて、拡大率と反転軸を指定できるよう、変更しました。
 *	* Sun Aug 10 21:42:25 JST 2008 Naoyuki Sawa
 *	- <vector>要素を固定小数点でなく浮動小数点で出力するよう変更しました。
 *	  浮動小数点MESHとして使用する場合は、浮動小数点のまま読み込んでください。
 *	  固定小数点MESHとして使用する場合は、読み込み時に固定小数点に変換してください。
 *	* Sun Aug 10 21:42:25 JST 2008 Naoyuki Sawa
 *	- <vector>要素を固定小数点でなく浮動小数点で出力するよう変更しました。
 *	  浮動小数点MESHとして使用する場合は、浮動小数点のまま読み込んでください。
 *	  固定小数点MESHとして使用する場合は、読み込み時に固定小数点に変換してください。
 */
#include "app.h"

/*****************************************************************************
 *	
 *****************************************************************************/

#define N_MATS_MAX	255	/* マテリアル数の上限。これぐらいあれば充分?(※調整可) */

/*****************************************************************************
 *	
 *****************************************************************************/

/* 本関数の中で確保するリソースはありません。
 * ファイルオープンと作業用オブジェクトの確保と解放は、呼び出し側(MQExportFile())で行っています。
 * 従って、本関数内でのエラー発生時は、リソースリークを気にせずに、直ちに"return FALSE"できます。
 */
BOOL
DoExportMesh(FILE* fp, MQDocument doc, MQObject obj, struct _OPTION* option)
{
#if 0 /*{{MQ_ExportAxis()の座標軸変換は仕様がよくわからないので、自前で処理することにしました。*/
//	MQFileDialogInfo info;
#endif/*}}MQ_ExportAxis()の座標軸変換は仕様がよくわからないので、自前で処理することにしました。*/
	//
	int n_mats;				/* 0〜N_MATS_MAX */
	int i_mat;
	static MQMaterial mats[N_MATS_MAX];	memset(mats, 0, sizeof mats);
	static MQColor mat_colors[N_MATS_MAX];	memset(mat_colors, 0, sizeof mat_colors);
	static int mat_brights[N_MATS_MAX];	memset(mat_brights, 0, sizeof mat_brights);
	static SIZE mat_texsizes[N_MATS_MAX];	memset(mat_texsizes, 0, sizeof mat_texsizes);
	//
	int n_objs;
	int i_obj;
	MQObject obj_tmp;
	//
	int n_vectors;				/* 0〜255 (P/ECE CLiP Library Meshの仕様) */
	int i_vector;
	static MQPoint vectors[255];		memset(vectors, 0, sizeof vectors);
	//
	int n_faces;				/* 0〜255 (P/ECE CLiP Library Meshの仕様) */
	int i_face;
	//
	int n_face_vertices;			/* 0 or 2 or 3 or 4 (MQObject::GetFacePointCount()の仕様) */
	int i_face_vertex;
	static int face_vertices[4];		memset(face_vertices, 0, sizeof face_vertices);
	static MQCoordinate face_coords[4];	memset(face_coords, 0, sizeof face_coords);
	//
	int n_coords;				/* 0〜255 (P/ECE CLiP Library Meshの仕様) */
	int i_coord;
	static POINT coords[255];		memset(coords, 0, sizeof coords);
	static unsigned char fvs_ic[255][4];	memset(fvs_ic, 0, sizeof fvs_ic);

#if 0 /*{{MQ_ExportAxis()の座標軸変換は仕様がよくわからないので、自前で処理することにしました。*/
//	/* 座標軸変換などの機能を持ったファイルダイアログを表示します。
//	 * * ダイアログには、「左右を反転する」「面方向を反転する」というチェックボックスが有りますが、
//	 *   これらのチェックボックスを変更してもMQFileDialogInfo構造体に反映されないようです。
//	 *   結果として、MQ_ExportAxis()にも伝達されず、出力ファイルに違いは有りません。
//	 * - インターネットで公開されている既存のプラグインも、同様の動作みたいなので、たぶん仕様です。
//	 *   何のために「左右を反転する」「面方向を反転する」というチェックボックスが有るのか、謎です。
//	 * - ところが、Metasequoia本体の「3D Studio (*.3ds)」出力では、この設定が反映されるみたいです。
//	 *   どうやって、チェックボックスの状態を取得しているのでしょうか?
//	 *   Metasequoia本体からの呼び出しと、外部プラグインとでは、インターフェイスが違うのでしょうか?
//	 * * MQFileDialogInfo.typeは、何に使うのか不明です。
//	 *   Plug-in SDK サンプルでも設定していないし、とりあえず0のままにしておくことにします。
//	 *   MQFileDialogInfo.softnameも、何に使うのか不明です。
//	 *   既存ソフトウェアの名称に一致した場合のみ「座標軸」コンボボックスに反映されますが、それってあんまり意味が無いような気がします。。。
//	 */
//	memset(&info, 0, sizeof info);
//	info.dwSize = sizeof info;
//	info.axis_x = MQFILE_TYPE_RIGHT;
//	info.axis_y = MQFILE_TYPE_UP;
//	info.axis_z = MQFILE_TYPE_BACK;
//	MQ_ShowFileDialog("P/ECE CLiP Library Mesh Export", &info);
//	/* MQ_ShowFileDialog()が処理を返すと、scale,axis_x,axis_y,axis_zに変更が反映されています。 */
#endif/*}}MQ_ExportAxis()の座標軸変換は仕様がよくわからないので、自前で処理することにしました。*/

	/*{{出力*/
	fprintf(fp, "<?xml version='1.0' ?>\n");
	/*}}出力*/

	/* int MQDocument::GetMaterialCount()
	 *
	 * ドキュメント中のマテリアルの数を取得します。
	 * 実際に存在するマテリアル数ではなく配列要素の大きさが返されるので、MQDocument::DeleteMaterial()によりNULLになったものも数として入れられます。
	 * 正確な数を得るにはMQDocument::GetMaterial()によりNULL以外のものを数える必要があります。
	 * [out]
	 *	戻り値		マテリアルの配列要素数。
	 */
	n_mats = doc->GetMaterialCount();
	if(n_mats > N_MATS_MAX) { /* N_MATS_MAXを調整してください */
		MessageBox(MQ_GetWindowHandle(),
			"マテリアルが多すぎます。",
			APP_TITLE, MB_ICONEXCLAMATION | MB_OK);
		return FALSE;
	}

	for(i_mat = 0; i_mat < n_mats; i_mat++) {

		/* MQMaterial MQDocument::GetMaterial(int material)
		 * 
		 * マテリアルクラスへのポインタを取得します。
		 * 指定したインデクスにマテリアルが存在しない場合はNULLが返されます。
		 * [in]
		 *	material	マテリアルのインデクス。
		 * [out]
		 *	戻り値		マテリアルクラス。
		 */
		mats[i_mat] = doc->GetMaterial(i_mat);
		if(mats[i_mat]) {
			mat_colors[i_mat] = mats[i_mat]->GetColor();
			mat_brights[i_mat] = (int)(((mat_colors[i_mat].r * 0.30) +
						    (mat_colors[i_mat].g * 0.59) +
						    (mat_colors[i_mat].b * 0.11)) * 4);
			if(mat_brights[i_mat] < 1) {		// R G B 0.30R+0.59G+0.11B=?    *4=? c Metasequoia→P/ECE
				mat_brights[i_mat] = 3;		// 0 0 0                  =0.00 0.00 3          黒→黒
			} else if(mat_brights[i_mat] < 2) {	// 0 0 1             0.11 =0.11 0.44 3          青→黒
				mat_brights[i_mat] = 2;		// 0 1 0       0.59       =0.59 2.36 1          緑→明
			} else if(mat_brights[i_mat] < 3) {	// 0 1 1       0.59+ 0.11 =0.70 2.80 1      シアン→明
				mat_brights[i_mat] = 1;		// 1 0 0 0.30+             0.30 1.20 2          赤→暗
			} else {				// 1 0 1 0.30+       0.11 =0.41 1.64 2    マゼンタ→暗
				mat_brights[i_mat] = 0;		// 1 1 0 0.30+ 0.59       =0.89 3.56 0          黄→白
			}					// 1 1 1 0.30+ 0.59+ 0.11 =1.00 4.00 0          白→白

			/* テクスチャサイズを取得します。
			 * - テクスチャの無いマテリアルや、存在しない画像ファイルを参照している場合は、
			 *   MQDocument::FindMappingFile()、または、MQ_LoadImage()が失敗するはずです。
			 *   テクスチャのロードを試みて失敗した場合は、mat_texsizes[i_mat]={0,0}のままとし、
			 *   テクスチャの無いマテリアルであることを示すことにします。
			 */
			char filename[MAX_PATH]; /* 相対パス取得用 */
			char path[MAX_PATH];     /* 絶対パス取得用 */
			mats[i_mat]->GetTextureName(filename, sizeof filename); /* テクスチャが無い場合は空文字列が返されます */
			if(doc->FindMappingFile(path, filename, MQMAPPING_TEXTURE)) {
				BITMAPINFOHEADER* header; /* SDKドキュメントには書いてないけど、BITMAPINFOHEADERが返される仕様みたいです */
				void* buffer;
				if(MQ_LoadImage(path, (void**)&header, (void**)&buffer, 0)) {
					mat_texsizes[i_mat].cx = header->biWidth;
					mat_texsizes[i_mat].cy = header->biHeight;
					LocalFree(header); /* 画像ヘッダはもう不要 */
					LocalFree(buffer); /* 画像データは使わない */
				}
			}
		}
	}

	/* int MQDocument::GetObjectCount()
	 *
	 * ドキュメント中のオブジェクトの数を取得します。
	 * 実際に存在するオブジェクト数ではなく配列要素の大きさが返されるので、MQDocument::DeleteObject()によりNULLになったものも数として入れられます。
	 * 正確な数を得るにはMQDocument::GetObject()によりNULL以外のものを数える必要があります。
	 * [out]
	 *	戻り値		オブジェクトの配列要素数。
	 */
	n_objs = doc->GetObjectCount();

	/* 作業用オブジェクトに、すべてのオブジェクトを合成します。 */
	for(i_obj = 0; i_obj < n_objs; i_obj++) {

		/* MQObject MQDocument::GetObject(int index)
		 *
		 * オブジェクトクラスへのポインタを取得します。
		 * 指定したインデクスにオブジェクトが存在しない場合はNULLが返されます。
		 * [in]
		 *	index		オブジェクトのインデクス。
		 * [out]
		 *	戻り値		オブジェクトクラス。
		 */
		obj_tmp = doc->GetObject(i_obj);
		if(!obj_tmp) {
			continue; /* 有り得ます!! MQDocument::GetObjectCount()のコメント参照 */
		}

		/* 非表示のオブジェクトは出力しないことにします。 */
		if(!obj_tmp->GetVisible()) {
			continue;
		}

		/* MQObject::Freeze()により内容が変更されるので、オブジェクトを複製しておきます。 */
		obj_tmp = obj_tmp->Clone();

		/* 曲面や鏡面などの属性をフリーズさせて完全にポリゴン化します。 */
		obj_tmp->Freeze(MQOBJECT_FREEZE_ALL);

		/* 作業用オブジェクトに合成します。 */
		obj->Merge(obj_tmp);

		/* void MQObject::DeleteThis()
		 *
		 * オブジェクトクラスをデリート(消滅)させます。
		 * この関数はドキュメントに登録されていないオブジェクトにのみ有効で、ドキュメントに登録されたオブジェクトを消滅させる場合はMQDocement::DeleteObject()を使用しなければなりません。
		 */
		obj_tmp->DeleteThis();
	}

	/* int MQObject::GetVertexCount()
	 *
	 * 頂点の数を取得します。
	 * 実際に存在する頂点数ではなく配列要素の大きさであるので、正確な頂点数を調べるには各頂点ごとにMQObject::GetVertexRefCount()で0以外が返されるかどうかを調べる必要があります。
	 * [out]
	 *	戻り値		頂点配列の要素。
	 */
	n_vectors = obj->GetVertexCount();
	if(n_vectors > 255) { /* P/ECE CLiP Library Meshの仕様 */
		MessageBox(MQ_GetWindowHandle(),
			"頂点座標が多すぎます。",
			APP_TITLE, MB_ICONEXCLAMATION | MB_OK);
		return FALSE;
	}

	/* int MQObject::GetFaceCount()
	 *
	 * 面数を取得します。
	 * 実際に存在する面数ではなく配列要素の大きさであるので、正確な面数を得るには各面ごとにMQObject::GetFacePointCount()で0以外が返されるかどうかを調べる必要があります。
	 * [out]
	 *	戻り値		面配列の要素数。
	 */
	n_faces = obj->GetFaceCount();
	if(n_faces > 255) { /* P/ECE CLiP Library Meshの仕様 */
		MessageBox(MQ_GetWindowHandle(),
			"フェイスが多すぎます。",
			APP_TITLE, MB_ICONEXCLAMATION | MB_OK);
		return FALSE;
	}

	/* 重複しないテクスチャ座標配列を作成します。 */
	n_coords = 0;
	for(i_face = 0; i_face < n_faces; i_face++) {

		/* int MQObject::GetFaceMaterial(int face)
		 *
		 * 面に割り当てられたマテリアルのインデクスを取得します。
		 * インデクスはオブジェクトが属するドキュメント中のものになります。
		 * どのマテリアルも割り当てられていない「未着色面」の場合は-1が返されます。
		 * [in]
		 *	face		面のインデクス。
		 * [out]
		 *	戻り値		マテリアルのインデクス。
		 */
		i_mat = obj->GetFaceMaterial(i_face);

		if(i_mat >= 0 && mats[i_mat]) { /* 未着色フェイスは除外 */
			SIZE texsize = mat_texsizes[i_mat];
			if(texsize.cx && texsize.cy) { /* テクスチャの無いマテリアルは除外 */

				/* int MQObject::GetFacePointCount(int face)
				 *
				 * 指定した面に属する頂点の数を取得します。
				 * 0,2,3,4のいずれかが返されます。
				 * 0の場合はMQObject::DeleteFace()により既に破棄され存在していないことを示します。
				 * [in]
				 *	face		面のインデクス。
				 * [out]
				 *	戻り値		頂点数
				 */
				n_face_vertices = obj->GetFacePointCount(i_face);
				if(n_face_vertices > 4) { /* 有り得ない */
					MessageBox(MQ_GetWindowHandle(),
						"予期しないフェイス頂点数です。",
						APP_TITLE, MB_ICONEXCLAMATION | MB_OK);
					return FALSE;
				}

				/* void MQObject::GetFaceCoordinateArray(int face, MQCoordinate* uvarray)
				 *
				 * 指定した面の各頂点のUV値を配列として取得します。
				 * uvarrayはMQObject::GetFacePointCount()で得られる数以上の大きさをもつ配列でなければなりません。
				 *
				 * [in]
				 *	face		面のインデクス。
				 *	uvarray		UV値を格納するバッファ。
				 */
				obj->GetFaceCoordinateArray(i_face, face_coords);

				/* 既に同じテクスチャ座標があればそれを採用、初出ならばテクスチャ座標配列に加えます。 */
				for(i_face_vertex = 0; i_face_vertex < n_face_vertices; i_face_vertex++) {
					POINT coord;
					coord.x = (unsigned char)(face_coords[i_face_vertex].u * (texsize.cx - 1/*Metasequoiaはグリッド座標→P/ECEはピクセル座標*/));
					coord.y = (unsigned char)(face_coords[i_face_vertex].v * (texsize.cy - 1/*Metasequoiaはグリッド座標→P/ECEはピクセル座標*/));
					for(i_coord = 0; i_coord < n_coords; i_coord++) {
						if(coords[i_coord].x == coord.x &&
						   coords[i_coord].y == coord.y) {
							break;
						}
					}
					if(i_coord == n_coords) {     /* 同じテクスチャ座標が見つからなかった? */
						if(n_coords == 255) { /* 既にテクスチャ座標数上限に達している? (P/ECE CLiP Library Meshの仕様) */
							MessageBox(MQ_GetWindowHandle(),
								"テクスチャ座標が多すぎます。",
								APP_TITLE, MB_ICONEXCLAMATION | MB_OK);
							return FALSE;
						}
						coords[n_coords++] = coord;
					}
					fvs_ic[i_face][i_face_vertex] = i_coord; /* このフェイス頂点のテクスチャ座標インデクスを記憶 */
				}
			}
		}
	}

	/*{{出力*/
	fprintf(fp, "<mesh nv='%d' nc='%d' nf='%d'>\n", n_vectors, n_coords, n_faces);
	/*}}出力*/

	/* void MQObject::GetVertexArray(MQPoint* ptsarray);
	 *
	 * オブジェクト中のすべての頂点の位置を配列として一括取得します。
	 * ptsarrayの配列要素数はMQObject::GetVertexCount()で返される数以上でなければなりません。
	 * [in]
	 *	ptsarray	位置配列。
	 */
	obj->GetVertexArray(vectors);

#if 0 /*{{2006/07/26*/
//#if 0 /*{{MQ_ExportAxis()の座標軸変換は仕様がよくわからないので、自前で処理することにしました。*/
////	/* void MQ_ImportAxis(MQFileDialogInfo* info, MQPoint* pts, int pts_count)
////	 * void MQ_ExportAxis(MQFileDialogInfo* info, MQPoint* pts, int pts_count)
////	 *
////	 * ファイルダイアログの座標軸データを基に頂点位置を変換します。
////	 * 入力用と出力用でそれぞれ異なります。
////	 */
////	MQ_ExportAxis(&info, vectors, n_vectors);
//#else
//	/* * MQ_ExportAxis()は、Z軸反転を指示すると、勝手にX軸まで反転してしまうみたいです。
//	 *   仕様がよくわからないので、自前で座標軸変換を行い、Z軸反転だけ行うことにします。
//	 * * Metasequoiaの標準的なスケールは大きすぎるので、100分の1で出力することにします。
//	 *   この変換処理は、MQFileDialogInfo.scaleの設定に相当します。
//	 *   ダイアログで変更することができなくなってしまいましたが、とりあえず固定とします。
//	 *   いずれ、カスタムダイアログを作って、変更できるようにしようと思います。(※TODO:)
//	 */
//	for(i_vector = 0; i_vector < n_vectors; i_vector++) {
//		vectors[i_vector].x =  vectors[i_vector].x / 100;
//		vectors[i_vector].y =  vectors[i_vector].y / 100;
//		vectors[i_vector].z = -vectors[i_vector].z / 100;
//	}
//#endif/*}}MQ_ExportAxis()の座標軸変換は仕様がよくわからないので、自前で処理することにしました。*/
#else /*}}2006/07/26{{*/
	/* * Wed Jul 26 06:27:42 JST 2006 Naoyuki Sawa
	 * - オプションダイアログにて、拡大率と反転軸を指定できるよう、変更しました。
	 */
	double scale = option->scale * pow(10, option->exp);
	double scale_x = option->axis == 0 ? -scale : scale;
	double scale_y = option->axis == 1 ? -scale : scale;
	double scale_z = option->axis == 2 ? -scale : scale;
	for(i_vector = 0; i_vector < n_vectors; i_vector++) {
		vectors[i_vector].x = (float)(vectors[i_vector].x * scale_x);
		vectors[i_vector].y = (float)(vectors[i_vector].y * scale_y);
		vectors[i_vector].z = (float)(vectors[i_vector].z * scale_z);
	}
#endif /*}}2006/07/26*/

	/*{{出力*/
	for(i_vector = 0; i_vector < n_vectors; i_vector++) {
		float x = vectors[i_vector].x;
		float y = vectors[i_vector].y;
		float z = vectors[i_vector].z;
//{{2008/08/10:変更
//		fprintf(fp, "\t<vector x='%d' y='%d' z='%d' /> <!-- %f %f %f -->\n", fld(x), fld(y), fld(z), x, y, z);
//↓2008/08/10:変更
		fprintf(fp, "\t<vector x='%f' y='%f' z='%f' />\n", x, y, z);
//}}2008/08/10:変更
	}
	for(i_coord = 0; i_coord < n_coords; i_coord++) {
		POINT coord = coords[i_coord];
		fprintf(fp, "\t<tcoord s='%d' t='%d' />\n", coord.x, coord.y);
	}
	/*}}出力*/

	for(i_face = 0; i_face < n_faces; i_face++) {

		/* int MQObject::GetFaceMaterial(int face)
		 *
		 * 面に割り当てられたマテリアルのインデクスを取得します。
		 * インデクスはオブジェクトが属するドキュメント中のものになります。
		 * どのマテリアルも割り当てられていない「未着色面」の場合は-1が返されます。
		 * [in]
		 *	face		面のインデクス。
		 * [out]
		 *	戻り値		マテリアルのインデクス。
		 */
		i_mat = obj->GetFaceMaterial(i_face);

		/* int MQObject::GetFacePointCount(int face)
		 *
		 * 指定した面に属する頂点の数を取得します。
		 * 0,2,3,4のいずれかが返されます。
		 * 0の場合はMQObject::DeleteFace()により既に破棄され存在していないことを示します。
		 * [in]
		 *	face		面のインデクス。
		 * [out]
		 *	戻り値		頂点数
		 */
		n_face_vertices = obj->GetFacePointCount(i_face);
		if(n_face_vertices > 4) { /* 有り得ない */
			MessageBox(MQ_GetWindowHandle(),
				"予期しないフェイス頂点数です。",
				APP_TITLE, MB_ICONEXCLAMATION | MB_OK);
			return FALSE;
		}

		/* void MQObject::GetFacePointArray(int face, int* vertex)
		 *
		 * 指定した面の頂点インデクスを配列として取得します。
		 * vertexはMQObject::GetFacePointCount()で得られる数以上の大きさをもつ配列でなければなりません。
		 *
		 * [in]
		 *	face		面のインデクス。
		 *	vertex		頂点インデクスを格納するバッファ。
		 */
		obj->GetFacePointArray(i_face, face_vertices);

		/*{{出力*/
		if(i_mat >= 0 && mats[i_mat]) {
			fprintf(fp, "\t<face nfv='%d' c='%d'> <!-- %f %f %f -->\n", n_face_vertices, mat_brights[i_mat], mat_colors[i_mat].r, mat_colors[i_mat].g, mat_colors[i_mat].b);
		} else {
			fprintf(fp, "\t<face nfv='%d' c='%d'> <!-- no-material -->\n", n_face_vertices, mat_brights[i_mat]);
		}
#if 0 /*{{MQ_ExportAxis()の座標軸変換は仕様がよくわからないので、自前で処理することにしました。*/
//		/* Metasequoiaは右手座標系で、左回りのフェイスが表です。
//		 * P/ECE CLiP Library Meshは左手座標系で、右回りのフェイスが表です。
//		 * MQ_ExportAxis()により、右手座標系→左手座標系変換(Z軸反転)は行われていますが、左回り→右回り変換は行われません。
//		 * 以下のループで、明示的に左回り→右回り変換を行い、出力することにします。
//		 */
//		for(i_face_vertex = n_face_vertices - 1; i_face_vertex >= 0; i_face_vertex--) {
//			fprintf(fp, "\t\t<facevertex iv='%d' ic='%d' />\n", face_vertices[i_face_vertex], i_coord);
//		}
#else
		/* 明示的な左回り→右回り変換は、もう、不要になりました。
		 * * 上の無効化したブロックに書いたコメントは、少し間違っていたみたいです。
		 *   前述の通り、MQ_ExportAxis()は、Z軸反転を指示すると、勝手にX軸まで反転してしまうみたいです。
		 *   二軸反転となるので、左回りのままになっていたみたいです。
		 *   今後は、自前でZ軸反転だけを行うので、一軸反転となり、そのままの順序で右回り記述となります。
		 *   従って、明示的に左回り→右回り変換を行う必要はありません。
		 */
		for(i_face_vertex = 0; i_face_vertex < n_face_vertices; i_face_vertex++) {
			fprintf(fp, "\t\t<facevertex iv='%d' ic='%d' />\n", face_vertices[i_face_vertex], fvs_ic[i_face][i_face_vertex]);
		}
#endif/*}}MQ_ExportAxis()の座標軸変換は仕様がよくわからないので、自前で処理することにしました。*/
		fprintf(fp, "\t</face>\n");
		/*}}出力*/
	}

	/*{{出力*/
	fprintf(fp, "</mesh>\n");
	/*}}出力*/

	return TRUE;
}

