/*
 *	ExportWire.cpp
 *
 *	P/ECE CLiP Library 3D Object Exporter
 *	Copyright (C) 2006 Naoyuki Sawa
 *
 *	* Thu Jun 29 11:55:47 JST 2006 Naoyuki Sawa
 *	- ワイヤフレームエクスポート機能を作成開始。
 *	* Wed Jul 26 06:27:42 JST 2006 Naoyuki Sawa
 *	- オプションダイアログにて、拡大率と反転軸を指定できるよう、変更しました。
 */
#include "app.h"

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

/*{{clipwire.cより*/
#define NV_MAX 938 //(sizeof _def_vbuff / sizeof(vector)) /* 128×88÷12=938 */
/*}}clipwire.cより*/
#define NS_MAX 32767 /* WIRE.nsがshortなのでセグメント数上限はSHRT_MAX=32767 */

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

/* ★重要★
 * * P/ECE CLiP Library Wireの頂点座標数、セグメント数の上限は、Meshと違って255ではないことに注意してください!!
 * - Meshの場合、これらの数を符号無し1バイト値で格納しているので、上限255ですが、
 *   Wireの場合、頂点座標数は作業用メモリいっぱいの938頂点まで、セグメント数はshort上限の32767まで対応できます。
 * - 以上のような理由により、このソースファイルにおいては、"255"という固定値の記述は現れないはずです。
 *   "255"の文字列サーチを行い、もしも残っていたら、ExportMesh.cppから流用したコードのチェック処理の変更忘れである可能性大です。
 *   プログラム更新時には、"255"の文字列サーチを行って、ヒットしないことを確認してください。
 */

/* 本関数の中で確保するリソースはありません。
 * ファイルオープンと作業用オブジェクトの確保と解放は、呼び出し側(MQExportFile())で行っています。
 * 従って、本関数内でのエラー発生時は、リソースリークを気にせずに、直ちに"return FALSE"できます。
 */
BOOL
DoExportWire(FILE* fp, MQDocument doc, MQObject obj, struct _OPTION* option)
{
	int n_objs;
	int i_obj;
	MQObject obj_tmp;
	//
	int n_vectors;				/* 0〜NV_MAX (P/ECE CLiP Library Wireの仕様) */
	int i_vector;
	static MQPoint vectors[NV_MAX];		memset(vectors, 0, sizeof vectors);
	//
	int n_faces;
	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);
	//
	int n_wsegs;				/* 0〜NS_MAX (P/ECE CLiP Library Wireの仕様) */
	int i_wseg;
	static short wsegs[NS_MAX][2];		memset(wsegs, 0, sizeof wsegs);

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

	/* 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 > NV_MAX) { /* P/ECE CLiP Library Wireの仕様 */
		MessageBox(MQ_GetWindowHandle(),
			"頂点座標が多すぎます。",
			APP_TITLE, MB_ICONEXCLAMATION | MB_OK);
		return FALSE;
	}

	/* int MQObject::GetFaceCount()
	 *
	 * 面数を取得します。
	 * 実際に存在する面数ではなく配列要素の大きさであるので、正確な面数を得るには各面ごとにMQObject::GetFacePointCount()で0以外が返されるかどうかを調べる必要があります。
	 * [out]
	 *	戻り値		面配列の要素数。
	 */
	n_faces = obj->GetFaceCount();

	/* 重複しないセグメント配列を作成します。
	 * - Metasequoia Ver2.3.4に存在する、[オブジェクト]→[Wired]機能を適用すれば、Metasequoiaが重複しないワイヤーフレーム化を行ってくれます。
	 *   しかし、通常のポリゴンモデルからもワイヤーフレームデータを生成できるよう、今回の実装では、自前で重複しないセグメント配列作成処理を持つことにしました。
	 * - [オブジェクト]→[Wired]機能を適用した後に、本プラグインにてワイヤーフレームデータを出力することもできます。
	 *   単に、以下の重複しないセグメント配列作成処理が、無駄処理となるだけです。
	 */
	n_wsegs = 0;
	for(i_face = 0; i_face < n_faces; 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);

		/* 既に同じセグメントがあれば何もせず、初出ならばセグメント配列に追加します。 */
		for(i_face_vertex = 0; i_face_vertex < n_face_vertices; i_face_vertex++) {
			int iv0 = face_vertices[ i_face_vertex + 0                   ];
			int iv1 = face_vertices[(i_face_vertex + 1) % n_face_vertices];
			for(i_wseg = 0; i_wseg < n_wsegs; i_wseg++) {
				if((wsegs[i_wseg][0] == iv0 && wsegs[i_wseg][1] == iv1) ||
				   (wsegs[i_wseg][0] == iv1 && wsegs[i_wseg][1] == iv0)) { /* 逆順でも一致 */
					break;
				}
			}
			if(i_wseg == n_wsegs) { /* 同じセグメントが見つからなかった? */
				if(n_wsegs == NS_MAX) { /* 既にセグメント数上限に達している? (P/ECE CLiP Library Wireの仕様) */
					MessageBox(MQ_GetWindowHandle(),
						"セグメント数が多すぎます。",
						APP_TITLE, MB_ICONEXCLAMATION | MB_OK);
					return FALSE;
				}
				wsegs[n_wsegs][0] = iv0;
				wsegs[n_wsegs][1] = iv1;
				n_wsegs++;
			}
		}
	}

	/*{{出力*/
	fprintf(fp, "<wire nv='%d' ns='%d'>\n", n_vectors, n_wsegs);
	/*}}出力*/

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

#if 0 /*{{2006/07/26*/
//	/* * 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;
//	}
#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;
		fprintf(fp, "\t<vector x='%d' y='%d' z='%d' /> <!-- %f %f %f -->\n", fld(x), fld(y), fld(z), x, y, z);
	}
	for(i_wseg = 0; i_wseg < n_wsegs; i_wseg++) {
		fprintf(fp, "\t<wseg iv0='%d' iv1='%d' />\n", wsegs[i_wseg][0], wsegs[i_wseg][1]);
	}
	/*}}出力*/

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

	return TRUE;
}

