// == LICENSE INFORMATION ==
/*
 * First author tiritomato 2013.
 * This program is distributed under the GNU General Public License(GPL).
 * support blog (Japanese only) http://d.hatena.ne.jp/tiri_tomato/
 */
// == LICENSE INFORMATION ==

#include <list>
#include "CustomControls.hpp"
#using <system.web.dll>

namespace UVTexIntegra {

	using namespace System;
	using namespace System::Windows::Forms;
	using namespace OpenTK::Graphics;
	using namespace OpenTK::Graphics::OpenGL;

	// private type define //

	template <typename T> inline bool TryParse(System::String^ src, T% result ) {
		if ( System::String::IsNullOrEmpty(src) ) return false; return T::TryParse( src, result );
	}

	private ref class ImageIntegrator {
	public:
		static bool IsNullOrEmpty( System::Drawing::Bitmap^ img ) {
			if ( img != nullptr ) try { return (img->Width <= 0) || (img->Height <= 0); } catch ( System::Exception^ ) {}
			return true;
		}

		ref class DestImageItem {
		public:
			const bool isClearOnIntegrate;
			const System::Int32 clrColor;
			property int expectHeight { int get(){ return m_expectHeight; } }
			property int expectWidth { int get(){ return m_expectWidth; } }
			property System::Drawing::Imaging::ImageFormat^ fileFormat { System::Drawing::Imaging::ImageFormat^ get(){ return m_fileFormat; } }
			property System::Drawing::Bitmap^ img { System::Drawing::Bitmap^ get() { return m_img; } }
			property bool isNullOrEmpty { bool get() { return IsNullOrEmpty(m_img); } }
			property std::list<const MQMaterial> *ownerMat { std::list<const MQMaterial>* get() { return m_ownerMat; } }
			property System::String^ path { System::String^ get() { return m_path; } }
			~DestImageItem() { this->!DestImageItem(); }
			!DestImageItem() { if ( m_ownerMat != NULL ) { delete m_ownerMat; m_ownerMat = NULL; } }
			DestImageItem(bool _isClearOnIntegrate, System::Int32 _clrColor, System::String^ set_path) :
				isClearOnIntegrate(_isClearOnIntegrate), clrColor(_clrColor), m_ownerMat(new std::list<const MQMaterial>()),
				m_expectWidth(0), m_expectHeight(0), m_path(set_path), m_fileFormat(nullptr), m_img(nullptr),
				m_pixelFormat( System::Drawing::Imaging::PixelFormat::Format32bppArgb )
			{
				if ( System::IO::File::Exists( path ) ) {
					System::IO::FileStream^ srcStream = nullptr;
					System::Drawing::Bitmap^ srcBmpFromStream = nullptr;
					try {
						/* bitmap cloning from file to memory buffer */
						srcStream = gcnew System::IO::FileStream(path,System::IO::FileMode::Open);
						srcBmpFromStream = gcnew System::Drawing::Bitmap(srcStream);
						m_fileFormat = gcnew System::Drawing::Imaging::ImageFormat(srcBmpFromStream->RawFormat->Guid);
						m_pixelFormat = srcBmpFromStream->PixelFormat;
						switch ( srcBmpFromStream->PixelFormat ) {
						case System::Drawing::Imaging::PixelFormat::Indexed:
						case System::Drawing::Imaging::PixelFormat::Gdi:
						case System::Drawing::Imaging::PixelFormat::Format1bppIndexed:
						case System::Drawing::Imaging::PixelFormat::Format4bppIndexed:
						case System::Drawing::Imaging::PixelFormat::Format8bppIndexed:
						case System::Drawing::Imaging::PixelFormat::Undefined:
						//case System::Drawing::Imaging::PixelFormat::DontCare:
						case System::Drawing::Imaging::PixelFormat::Format16bppArgb1555:
						case System::Drawing::Imaging::PixelFormat::Format16bppGrayScale:
							break;
						default:
							m_pixelFormat = srcBmpFromStream->PixelFormat;
							break;
						}
						m_img = srcBmpFromStream->Clone( System::Drawing::Rectangle(0,0,srcBmpFromStream->Width,srcBmpFromStream->Height), System::Drawing::Imaging::PixelFormat::Format32bppArgb );
					}
					catch (System::Exception^) { m_img = nullptr; m_fileFormat = nullptr; }
					finally {
						if ( srcBmpFromStream != nullptr ) { delete srcBmpFromStream; srcBmpFromStream = nullptr; }
						if ( srcStream != nullptr ) { srcStream->Close(); delete srcStream; srcStream = nullptr; }
					}
				}
			}
			void Apply( const MQMaterial mat, System::Drawing::Size sz ) {
				for ( std::list<const MQMaterial>::iterator itr = m_ownerMat->begin(); itr != m_ownerMat->end(); itr++ ) if ( mat == *itr ) return;
				m_ownerMat->push_back(mat);
				m_expectHeight = System::Math::Max(m_expectHeight, sz.Height);
				m_expectWidth = System::Math::Max(m_expectWidth, sz.Width);
			}
			void Resize( int x, int y ) { m_img = gcnew System::Drawing::Bitmap( std::abs(x), std::abs(y), m_pixelFormat ); }
			void TryClearTexture() {
				if ( isClearOnIntegrate && (m_img!=nullptr) ) {
					System::Drawing::Graphics^ gDest = nullptr;
					try {
						gDest = System::Drawing::Graphics::FromImage( m_img );
						gDest->Clear(System::Drawing::Color::FromArgb(clrColor));
					}
					catch ( System::Exception^ ) { throw; }
					finally { if ( gDest!=nullptr) delete gDest; }
				}
			}
			void TryDraw( System::Drawing::Bitmap^ src, float MoveU, float MoveV, float ScaleU, float ScaleV ) {
				System::Drawing::Graphics^ gDest = nullptr;
				if ( (src!=nullptr)&&(m_img!=nullptr) ) try {
					gDest = System::Drawing::Graphics::FromImage( m_img );
					gDest->Clip = gcnew System::Drawing::Region( System::Drawing::Rectangle( 0, 0, m_img->Width, m_img->Height ) );
					gDest->DrawImage( src, System::Drawing::RectangleF( m_img->Width * MoveU, m_img->Height * MoveV, m_img->Width * ScaleU, m_img->Height * ScaleV ) );
				}
				catch ( System::Exception^ ) {}
				finally { if ( gDest!=nullptr) delete gDest; }
			}
			bool TrySave() {
				if ( System::String::IsNullOrEmpty(m_path) || (m_fileFormat==nullptr) || (m_img==nullptr) ) return false;
				System::Drawing::Bitmap^ tmp = nullptr;
				try {
					tmp = img->Clone( System::Drawing::Rectangle(0,0,img->Width,img->Height), m_pixelFormat );
					tmp->Save(m_path,m_fileFormat);
				}
				catch ( System::Exception^ ex ) {
					MessageBox::Show( DotNetEx::String::Extention::ToNestedMultilineString( ex ) );
					return false;
				}
				finally { if ( tmp != nullptr ) delete tmp; }
				return true;
			}
		private:
			int m_expectHeight;
			int m_expectWidth;
			std::list<const MQMaterial> *m_ownerMat;
			System::String^ m_path;
			System::Drawing::Imaging::ImageFormat^ m_fileFormat;
			System::Drawing::Imaging::PixelFormat m_pixelFormat;
			System::Drawing::Bitmap^ m_img;
		};

		ref class SourceDictionary : System::Collections::Generic::Dictionary<System::String^,DotNetEx::Drawing::StreamedBitmap^> {
		public:
			~SourceDictionary(){ this->!SourceDictionary(); }
			!SourceDictionary() {
				for each ( System::String^ path in Keys ) if ( (path != nullptr) &&  ContainsKey( path ) ) delete this[path];
				Clear();
			}
			DotNetEx::Drawing::StreamedBitmap^ Set( const MQDocument doc, const MQMaterial mat, DWORD map_type ) {
				//return Set( gcnew MQCLIEX::StreamedImageItem( MQCLIEX::GetMaterialTextureAbsName(doc,mat,map_type) ) );
				return Set( DotNetEx::Drawing::StreamedBitmap::FromFile( MQCLIEX::GetMaterialTextureAbsName(doc,mat,map_type) ) );
			}
			DotNetEx::Drawing::StreamedBitmap^ Set( DotNetEx::Drawing::StreamedBitmap^ item ) {
				if ( (item == nullptr) || System::String::IsNullOrEmpty(item->path) || (item->img == nullptr) ) return nullptr;
				if ( ContainsKey( item->path ) ) this[item->path] = item;
				else Add( item->path, item );
				return item;
			}
		};

		ref class DestinationDictionary : System::Collections::Generic::Dictionary<System::String^,DestImageItem^> {
		public:
			DestImageItem^ Set( const MQDocument doc, const MQMaterial toMat, DWORD map_type, System::Text::StringBuilder^ errEmptyPath,
				DotNetEx::Drawing::StreamedBitmap^ srcItem, float ScaleU, float ScaleV, bool IsClrTextureOnIntegrate, System::Int32 clrTexColor ) {
				if ( ( doc == NULL) || ( toMat == NULL ) || (srcItem==nullptr) || (srcItem->img==nullptr) ) return nullptr;
				System::String^ path = MQCLIEX::GetMaterialTextureAbsName(doc,toMat,map_type);
				if ( System::String::IsNullOrEmpty( path ) ) {
					if ( errEmptyPath != nullptr ) errEmptyPath->AppendLine( gcnew System::String( MQ0x::GetName( toMat ).c_str() ) );
					return nullptr;
				}
				DestImageItem^ item = nullptr;
				if ( ContainsKey( path ) ) item = this[path];
				else Add( path, item = gcnew DestImageItem(IsClrTextureOnIntegrate,clrTexColor,path) );
				item->Apply( toMat,
						System::Drawing::Size
						(
							MQCLIEX::TryExpLogCeil2(int(std::ceil(srcItem->img->Width/ScaleU))),
							MQCLIEX::TryExpLogCeil2(int(std::ceil(srcItem->img->Height/ScaleV)))
						)
					);
				return item;
			}
		};

		property DestinationDictionary^ destinations { DestinationDictionary^ get() { return m_destinations; } }
		property System::String^ errorMessage { System::String^ get(); }
		property SourceDictionary^ sources { SourceDictionary^ get() { return m_sources; } }
		
		ImageIntegrator( const MQDocument doc, DocumentFormat^ payload );
		~ImageIntegrator() { this->!ImageIntegrator(); }
		!ImageIntegrator() {
			if ( m_sources != nullptr ) { delete m_sources; m_sources = nullptr; }
			if ( m_destinations != nullptr ) { delete m_destinations; m_destinations = nullptr; }
		}

		void ExecuteIntegrate();

	private:
		void Set( const MQDocument doc, DWORD map_type, bool IsClrTextureOnIntegrate, System::Int32 clrTexColor, EditData^ data );
		void TryImageIntegrate( const MQMaterial srcMat, const MQMaterial destMat, DWORD map_type, EditData^ data, char* buf1, char* buf2 );
		DocumentFormat^ m_payload;
		const MQDocument m_doc;
		SourceDictionary^ m_sources;
		DestinationDictionary^ m_destinations;
		System::Text::StringBuilder^ m_errEmptyPath;
		System::Text::StringBuilder^ m_errCirculation;
	};

	System::String^ ImageIntegrator::errorMessage::get() {
		System::String^ strErrEmptyPath = m_errEmptyPath->ToString();
		System::String^ strErrCirculation = m_errCirculation->ToString();
		if ( System::String::IsNullOrEmpty(strErrEmptyPath) && System::String::IsNullOrEmpty(strErrCirculation) ) return System::String::Empty;
		System::Text::StringBuilder^ errMsg = gcnew System::Text::StringBuilder();
		if (System::String::IsNullOrEmpty( strErrEmptyPath ) == false)
		{
			errMsg->AppendLine("以下のマテリアルに出力先のテクスチャファイルパスが設定されていません。");
			errMsg->AppendLine(strErrEmptyPath);
		}
		if (System::String::IsNullOrEmpty( strErrCirculation ) == false)
		{
			errMsg->AppendLine("以下の画像は統合先に指定されていますが、統合元にも指定されています。（循環エラー）");
			errMsg->AppendLine(strErrCirculation);
		}
		return errMsg->ToString();
	}

	ImageIntegrator::ImageIntegrator( const MQDocument doc, DocumentFormat^ payload ) :
		m_doc( doc ), m_payload( payload ),
		m_sources( gcnew ImageIntegrator::SourceDictionary() ),
		m_destinations( gcnew ImageIntegrator::DestinationDictionary() ),
		m_errEmptyPath( gcnew System::Text::StringBuilder() ),
		m_errCirculation( gcnew System::Text::StringBuilder() )
	{
		if ( (doc==NULL) || (payload == nullptr) || (payload->EditDataItems==nullptr) ) return;
		for each ( EditData^ data in payload->EditDataItems )
		{
			// open source bitmap file to path dictionary
			if ( payload->IsIntegrateTexture ) Set( doc, MQMAPPING_TEXTURE, payload->IsClrTextureOnIntegrate, payload->TextureClearColor, data );
			if ( payload->IsIntegrateAlpha ) Set( doc, MQMAPPING_ALPHA, payload->IsClrAlphaOnIntegrate, payload->AlphaClearColor, data );
			if ( payload->IsIntegrateBump ) Set( doc, MQMAPPING_BUMP, payload->IsClrBumpOnIntegrate, payload->BumpClearColor, data );
		}
		for each ( System::String^ path in m_destinations->Keys )
		{
			if (m_sources->ContainsKey(path))
			{
				std::list<const MQMaterial> *ownerMat = m_destinations[path]->ownerMat;
				if ( ownerMat != NULL ) for ( std::list<const MQMaterial>::iterator itrMat = ownerMat->begin(); itrMat != ownerMat->end(); itrMat++)
					m_errCirculation->AppendLine( System::String::Format( "{0} [{1}]", gcnew System::String( MQ0x::GetName(*itrMat).c_str() ), path ) );
			}
		}
	}

	void ImageIntegrator::ExecuteIntegrate()
	{
		if ( (m_doc==NULL)||(m_payload==nullptr)||(m_payload->EditDataItems==nullptr) ) return;
		char buf1[MAX_PATH], buf2[MAX_PATH];
		for each (DestImageItem^ destItem in m_destinations->Values) if (destItem != nullptr) destItem->TryClearTexture();
		for each (EditData^ data in m_payload->EditDataItems)
		{
			if ( (data == nullptr) || (data->IsTransExecutableStatus==false) ) continue;
			const MQMaterial mat = MQ0x::GetMaterial( m_doc, data->UniqueID );
			const MQMaterial toMat = MQ0x::GetMaterial( m_doc, data->ToMaterialID );
			if ( (mat==NULL) || (toMat==NULL) ) continue;
			if ( m_payload->IsIntegrateTexture ) TryImageIntegrate( mat, toMat, MQMAPPING_TEXTURE, data, buf1, buf2 );
			if ( m_payload->IsIntegrateAlpha ) TryImageIntegrate( mat, toMat, MQMAPPING_ALPHA, data, buf1, buf2 );
			if ( m_payload->IsIntegrateBump ) TryImageIntegrate( mat, toMat, MQMAPPING_BUMP, data, buf1, buf2 );
		}
	}

	void ImageIntegrator::Set(const MQDocument doc, DWORD map_type, bool IsClrTextureOnIntegrate, System::Int32 clrTexColor, EditData^ data)
	{
		if ((data == nullptr) || (data->IsTransExecutableStatus==false)) return;
		MQMaterial mat = MQ0x::GetMaterial(doc, data->UniqueID);
		MQMaterial toMat = MQ0x::GetMaterial(doc, data->ToMaterialID);
		if ((mat==NULL) || (toMat==NULL)) return;
		DestImageItem^ item = m_destinations->Set(doc, toMat, map_type, m_errEmptyPath, m_sources->Set( doc, mat, map_type ), data->ScaleU, data->ScaleV, IsClrTextureOnIntegrate, clrTexColor);
	}

	void ImageIntegrator::TryImageIntegrate(const MQMaterial srcMat, const MQMaterial destMat,
		DWORD map_type, EditData^ data, char* buf1, char* buf2)
	{
		if (m_doc->FindMappingFile(buf1, MQ0x::GetMaterialTextureName(srcMat, map_type).c_str(), map_type) == FALSE) return;
		if (m_doc->FindMappingFile(buf2, MQ0x::GetMaterialTextureName(destMat, map_type).c_str(), map_type) == FALSE) return;
		System::String^ srcPath = gcnew System::String(buf1);
		System::String^ destPath = gcnew System::String(buf2);
		if ((m_sources->ContainsKey(srcPath)==false) || (m_destinations->ContainsKey(destPath)==false)) return;
		m_destinations[destPath]->TryDraw(m_sources[srcPath]->img, data->MoveU, data->MoveV, data->ScaleU, data->ScaleV);
	}

	bool CustomControls::MainEditForm::Document::IsIncludeNotChangeMaterialSetting()
	{
		if (MQDocument != NULL) for each (EditData^ data in m_payload->EditDataItems)
		{
			if (data != nullptr)
			{
				if ((MQDocument!=NULL) && (MQ0x::GetMaterial(MQDocument,data->ToMaterialID)==NULL)) return true; // not found material
				if (data->UniqueID == data->ToMaterialID) return true; // explicit same material
			}
		}
		return false;
	}

	void CustomControls::MainEditForm::Document::OnCustomBake(MainEditForm^ dlg)
	{
		if (dlg == nullptr) return;
		VertexColorCustomBakeForm^ form = nullptr;
		
		// file param
		System::Text::Encoding^ enc = System::Text::Encoding::UTF8;
		System::String^ configPath = GetCustomBakeConfigPath();
		System::String^ vertexColorCustomBakeScriptSerialize = nullptr;
		
		// config try read
		System::IO::StreamReader^ reader = nullptr;
		if (System::IO::File::Exists(configPath))
		{
			try
			{
				reader = gcnew System::IO::StreamReader(configPath, enc);
				vertexColorCustomBakeScriptSerialize = reader->ReadToEnd();
			}
			finally
			{
				if (reader != nullptr)
				{
					reader->Close();
					delete reader;
				}
			}
		}

		bool mustClose = (dlg->CanvasRestoreWidth <= 0);
		try
		{
			dlg->Visible = false;
			if (mustClose) dlg->CanvasClose(0);
			form = gcnew VertexColorCustomBakeForm(
				dlg->pnlGL->GLBackgroundImage, dlg->pnlGL->GLBackColor,
				vertexColorCustomBakeScriptSerialize, gcnew ConfigAdapter(dlg));
			GC::Collect();
			GC::WaitForPendingFinalizers();
			form->OpenFileDialogDirectory = dlg->m_customBakeScriptDirectory;
			form->OpenFileDialogScriptFilterIndex = dlg->m_customBakeScriptFileFilterIndex;
			form->CompileResultWindowSize = System::Drawing::Size(dlg->m_buildResultWinX, dlg->m_buildResultWinY);
			form->ShowDialog(dlg);
		}
		catch (System::Exception^) { throw; }
		finally
		{
			if ( form != nullptr )
			{
				System::Drawing::Size sz = form->CompileResultWindowSize;
				dlg->m_buildResultWinX = sz.Width;
				dlg->m_buildResultWinY = sz.Height;
				dlg->m_customBakeScriptDirectory = form->OpenFileDialogDirectory;
				dlg->m_customBakeScriptFileFilterIndex = form->OpenFileDialogScriptFilterIndex;
				// config try write
				try {
					System::IO::StreamWriter^ writer = nullptr;
					try
					{
						writer = gcnew System::IO::StreamWriter(configPath, false, enc);
						writer->Write(form->ToAssemblySerializedString());
					}
					finally
					{
						if (writer != nullptr)
						{
							writer->Close();
							delete writer;
						}
					}
				}
				catch (System::Exception^)
				{
					System::Text::StringBuilder^ sb = gcnew System::Text::StringBuilder();
					sb->AppendLine("カスタムベイク終了時に、設定ファイルが書き込み可能な状態ではありませんでした。");
					sb->AppendLine(configPath);
					System::Windows::Forms::MessageBox::Show(sb->ToString(), "スクリプト状態の保存に失敗しました。");
				}
				delete form;
			}
			if (mustClose) dlg->CanvasOpen();
			dlg->Visible = true;
		}
		RefreshDialog(dlg, -1);
	}

	void CustomControls::MainEditForm::Document::OnInputChanged( MainEditForm^ dlg ) 
	{
		if ((MQDocument == NULL) || (dlg==nullptr) || (0 < m_refreshingLock->KeyCount)) return;
		
		EditData::SelectedCollection^ selectedItems = ItemsOnSelected();

		System::Int32 colTex, colAlpha, colBump;
		System::Int32 bakeVColorW, bakeVColorH, bakeVColorClearColor;
		float bakeVColorBorder;

		bool isInvalidInput = (
			System::Int32::TryParse( dlg->txtCmbTexClearColor->Text, System::Globalization::NumberStyles::AllowHexSpecifier, nullptr, colTex ) &&
			System::Int32::TryParse( dlg->txtCmbAlphaClearColor->Text, System::Globalization::NumberStyles::AllowHexSpecifier, nullptr, colAlpha) &&
			System::Int32::TryParse( dlg->txtCmbBumpClearColor->Text, System::Globalization::NumberStyles::AllowHexSpecifier, nullptr, colBump ) &&
			System::Int32::TryParse( dlg->txtBakeVColorClear->Text, System::Globalization::NumberStyles::AllowHexSpecifier, nullptr, bakeVColorClearColor ) &&
			System::Int32::TryParse( dlg->txtBakeVColorW->Text, bakeVColorW ) && System::Int32::TryParse( dlg->txtBakeVColorH->Text, bakeVColorH ) &&
			float::TryParse( dlg->txtBakeVColorBorder->Text, bakeVColorBorder )
			);

		if (isInvalidInput)
		{
			bakeVColorW = std::abs(bakeVColorW);
			bakeVColorH = std::abs(bakeVColorH);
			bakeVColorBorder = std::abs(bakeVColorBorder);
		}

		if (1 < selectedItems->Count)
		{
			/* multi selected */
			if ( isInvalidInput )
			{
				IsCloneTrans = dlg->chkClone->Checked;
				IsRecursiveTrans = dlg->chkRecursive->Checked;
				IsIntegrateTexture = dlg->chkCmbTex->Checked;
				IsClrTextureOnIntegrate = dlg->chkCmbTexClear->Checked;
				TextureClearColor = colTex;
				IsIntegrateAlpha = dlg->chkCmbAlpha->Checked;
				IsClrAlphaOnIntegrate = dlg->chkCmbAlphaClear->Checked;
				AlphaClearColor = colAlpha;
				IsIntegrateBump = dlg->chkCmbBump->Checked;
				IsClrBumpOnIntegrate = dlg->chkCmbBumpClear->Checked;
				BumpClearColor = colBump;
				dlg->BakeTextureBaseColor = bakeVColorClearColor;
				dlg->BakeTextureBorder = bakeVColorBorder;
				dlg->BakeTextureW = bakeVColorW;
				dlg->BakeTextureH = bakeVColorH;
			}

			float bufF;
			if ( dlg->chkSelMatTrans->CheckState != System::Windows::Forms::CheckState::Indeterminate )
				selectedItems->IsTransExecute = (dlg->chkSelMatTrans->CheckState == System::Windows::Forms::CheckState::Checked);
			if ( TryParse(dlg->txtSclU->Text, bufF) ) selectedItems->ScaleU = bufF;
			if ( TryParse(dlg->txtSclV->Text, bufF) ) selectedItems->ScaleV = bufF;
			if ( TryParse(dlg->txtMovU->Text, bufF) ) selectedItems->MoveU = bufF;
			if ( TryParse(dlg->txtMovV->Text, bufF) ) selectedItems->MoveV = bufF;
			if ( TryParse(dlg->txtPadding->Text, bufF) ) selectedItems->Padding = bufF;
			EditData^ toData = ItemFromIndex( dlg->cmbToMat->SelectedIndex );
			if ( toData != nullptr ) {
				selectedItems->ToMaterialID = toData->UniqueID;
				selectedItems->ToMaterialName = toData->MaterialName;
			}
		}
		else {
			/* single selected */
			EditData^ data = ItemFromIndex( dlg->cmbSelMat->SelectedIndex );
			EditData^ tmp = nullptr;

			if ( isInvalidInput && ( data != nullptr ) ) {
				tmp = gcnew EditData();
				isInvalidInput &= float::TryParse(dlg->txtSclU->Text,tmp->ScaleU) &&
						float::TryParse(dlg->txtSclV->Text,tmp->ScaleV) &&
						float::TryParse(dlg->txtMovU->Text,tmp->MoveU) &&
						float::TryParse(dlg->txtMovV->Text,tmp->MoveV) &&
						float::TryParse(dlg->txtPadding->Text,tmp->Padding);
			}
		
			if ( isInvalidInput ) {
				IsCloneTrans = dlg->chkClone->Checked;
				IsRecursiveTrans = dlg->chkRecursive->Checked;
				IsIntegrateTexture = dlg->chkCmbTex->Checked;
				IsClrTextureOnIntegrate = dlg->chkCmbTexClear->Checked;
				TextureClearColor = colTex;
				IsIntegrateAlpha = dlg->chkCmbAlpha->Checked;
				IsClrAlphaOnIntegrate = dlg->chkCmbAlphaClear->Checked;
				AlphaClearColor = colAlpha;
				IsIntegrateBump = dlg->chkCmbBump->Checked;
				IsClrBumpOnIntegrate = dlg->chkCmbBumpClear->Checked;
				BumpClearColor = colBump;
				dlg->BakeTextureBaseColor = bakeVColorClearColor;
				dlg->BakeTextureBorder = bakeVColorBorder;
				dlg->BakeTextureW = bakeVColorW;
				dlg->BakeTextureH = bakeVColorH;
				if ( data != nullptr ) {
					data->ScaleU = tmp->ScaleU;
					data->ScaleV = tmp->ScaleV;
					data->MoveU = tmp->MoveU;
					data->MoveV = tmp->MoveV;
					data->Padding = System::Math::Abs(tmp->Padding);
					data->IsTransExecute = ( dlg->chkSelMatTrans->CheckState == System::Windows::Forms::CheckState::Checked );
					EditData^ toData = ItemFromIndex( dlg->cmbToMat->SelectedIndex );
					if ( toData != nullptr ) {
						data->ToMaterialID = toData->UniqueID;
						data->ToMaterialName = toData->MaterialName;
					}
				}
			}
		}

		RefreshDialog(dlg, -1);
	}

	void CustomControls::MainEditForm::Document::OnMQOLoaded(MainEditForm^ dlg, MQXmlElement elem)
	{
		// clear
		m_payload->EditDataItems->Clear();
		if ( dlg != nullptr ) {
			dlg->cmbSelMat->Items->Clear();
			dlg->cmbToMat->Items->Clear();
		}
		MQXmlElement xmlRoot = NULL;
		if ( elem != NULL ) xmlRoot = elem->FirstChildElement("UVTexIntegra");
		if ( xmlRoot != NULL ) {
			std::string value = xmlRoot->GetText();
			if ( 0 < value.length() ) {
				DocumentFormat^ readPayload = nullptr;
				System::IO::StringReader^ reader;
				System::Xml::Serialization::XmlSerializer^ serializer
					= gcnew System::Xml::Serialization::XmlSerializer( DocumentFormat::typeid );
				try {
					System::String^ strDecoded = System::Web::HttpUtility::UrlDecode (
						msclr::interop::marshal_as<System::String^>(value),
						gcnew System::Text::UTF8Encoding()
					);
					reader = gcnew System::IO::StringReader(strDecoded);
					readPayload = dynamic_cast<DocumentFormat^>(serializer->Deserialize(reader));
				}
				catch (System::Exception^ ex) {
					::MessageBox(0,"ドキュメントの読み込みでズッコケました。",0,0);
					System::Windows::Forms::MessageBox::Show( ex->ToString() );
				}
				finally { if ( reader != nullptr ) { reader->Close(); reader = nullptr; } }
				if (readPayload != nullptr) {
					m_payload = readPayload;
					// switch to name search
					for each ( EditData^ data in m_payload->EditDataItems ) {
						if ( data == nullptr ) continue;
						data->UniqueID = -1;
						data->ToMaterialID = -1;
					}
				}
			}
			else ::MessageBox(0,"属性消失",0,0);
		}
		OnMQOModified(dlg, false);
	}

	void CustomControls::MainEditForm::Document::OnMQOModified(MainEditForm^ dlg, bool isCreateOnEmptyList)
	{
		if ((MQDocument==NULL) || (dlg==nullptr)) return;

		IsComboboxIndexSelecting = false;

		isCreateOnEmptyList |= (IsEmpty == false);

		// regist selected ID
		int regSelectedMaterialID = -1;
		int indexCurrentMaterial = MQDocument->GetCurrentMaterialIndex();
		if (0 <= indexCurrentMaterial) regSelectedMaterialID = MQDocument->GetMaterial(indexCurrentMaterial)->GetUniqueID(); // from current material
		else if ((0 < dlg->cmbSelMat->Items->Count) && (0 <= dlg->cmbSelMat->SelectedIndex) &&
				(dlg->cmbSelMat->SelectedIndex < dlg->cmbSelMat->Items->Count))
		{
			// from combo box select
			if (dlg->cmbSelMat->SelectedIndex < m_payload->EditDataItems->Count) regSelectedMaterialID = m_payload->EditDataItems[dlg->cmbSelMat->SelectedIndex]->UniqueID;
		}
		
		// rebuild dictionary(id and data) and swap
		int resetIndex = -1, addIndex = 0;
		dlg->cmbSelMat->Items->Clear();
		dlg->cmbToMat->Items->Clear();
		System::Collections::Generic::List<EditData^>^ rebuildIDAndData = gcnew System::Collections::Generic::List<EditData^>();
		if ((MQDocument != NULL) && isCreateOnEmptyList)
		{
			const int ctMat = MQDocument->GetMaterialCount();
			for ( int index = 0; index < ctMat; index++ )
			{
				MQMaterial mat = MQDocument->GetMaterial(index);
				if (mat != NULL)
				{
					UINT UniqueID = mat->GetUniqueID();
					System::String^ MaterialName = msclr::interop::marshal_as<System::String^>(MQ0x::GetName(mat));
					EditData^ data = ItemFromID(UniqueID); // search old data by id
					if ( data == nullptr )
					{
						data = ItemFromName(MaterialName); // search old data by name
						if ( data != nullptr ) data->UniqueID = UniqueID;
						else data = gcnew EditData(UniqueID,nullptr);
					}
					data->MaterialName = MaterialName;
					rebuildIDAndData->Add(data);
					if (UniqueID == regSelectedMaterialID) resetIndex = addIndex;
					dlg->cmbSelMat->Items->Add(data->MaterialName);
					dlg->cmbToMat->Items->Add(data->MaterialName);
					addIndex++;
				}
			}
		}

		m_payload->EditDataItems = rebuildIDAndData; // swap!

		for each (EditData^ data in m_payload->EditDataItems)
		{
			EditData^ fromID = ItemFromID(data->ToMaterialID);
			if ( fromID != nullptr ) data->ToMaterialName = fromID->MaterialName;
			else
			{
				EditData^ fromName = ItemFromName(data->ToMaterialName);
				if ( fromName == nullptr ) {
					data->ToMaterialID = -1;
					data->ToMaterialName = nullptr;
				}
				else data->ToMaterialID = fromName->UniqueID;
			}
		}

		// display refresh
		RefreshDialog(dlg, resetIndex);
	}

	void CustomControls::MainEditForm::Document::OnMQOSave( MainEditForm^ dlg, MQXmlElement elem ) {
		if ((elem==NULL)||(dlg==nullptr)) return;
		// clear old data
		MQXmlElement xmlRoot = elem->FirstChildElement("UVTexIntegra");
		if ( xmlRoot != NULL ) {
			elem->RemoveChildElement(xmlRoot);
			xmlRoot = NULL;
		}
		// build new data
		if ( 0 < m_payload->EditDataItems->Count ) {
			System::IO::StringWriter^ writer = gcnew System::IO::StringWriter();
			std::string strEncoded;
			try {
				System::Xml::Serialization::XmlSerializer^ serializer
					= gcnew System::Xml::Serialization::XmlSerializer( DocumentFormat::typeid );
				serializer->Serialize(writer, m_payload);
				strEncoded = msclr::interop::marshal_as<std::string>(
					System::Web::HttpUtility::UrlEncode (
							writer->ToString()->Replace(System::Environment::NewLine, ""),
							gcnew System::Text::UTF8Encoding()
						)
				);
			}
			catch ( System::Exception^ ex ) {
				::MessageBox(0,"ドキュメントの書き出しでズッコケました。",0,0);
				System::Windows::Forms::MessageBox::Show( ex->ToString() );
			}
			finally { writer->Close(); }
			if ( 0 < strEncoded.length() ) elem->AddChildElement("UVTexIntegra")->SetText(strEncoded.c_str());
		}
	}

	void CustomControls::MainEditForm::Document::Paint(MainEditForm^ dlg, System::Drawing::Graphics^ g, System::Drawing::Size size, bool isDraging, bool isBorderHighLight)
	{
		if ( (MQDocument==NULL) || (dlg==nullptr) || (g==nullptr) || (dlg->m_pUVBuffer==NULL) ) return;
		
		System::Collections::Generic::List<UINT>^ IDs = MQCLIEX::GetSelectedObjectIDs( MQDocument, IsRecursiveTrans );
		dlg->m_pUVBuffer->Clear(this);
		for each ( UINT id in IDs ) dlg->m_pUVBuffer->AddObject(MQDocument->GetObjectFromUniqueID( id ), IsRecursiveTrans);
		
		if ( dlg->rdbDisplayVColorPreview->Checked ) BakeTexturePaint(dlg, g, dlg->pnlGL->GLSize);
		PaintDefault(dlg, g, isDraging, isBorderHighLight);
	}

	void CustomControls::MainEditForm::Document::BakeTexturePaint(MainEditForm^ dlg, System::Drawing::Graphics^ g, System::Drawing::Size size) {
		
		if ( (MQDocument == NULL) || (dlg == nullptr) || (g == nullptr) || (dlg->pnlGL->CanFocus == false)) return;
		
		const int SqSize = std::max( size.Width, size.Height );
		const int FboSqSize = MQCLIEX::TryExpLogCeil2(SqSize);
 
		unsigned int FboHandle = 0;
		unsigned int ColorTexture = 0;
		unsigned int DepthRenderbuffer = 0;
		OpenTK::Graphics::OpenGL::ErrorCode err = OpenTK::Graphics::OpenGL::ErrorCode::NoError;
		OpenTK::Graphics::OpenGL::FramebufferErrorCode err_fb = OpenTK::Graphics::OpenGL::FramebufferErrorCode::FramebufferCompleteExt;
		try {
			dlg->pnlGL->GLContext->MakeCurrent(dlg->pnlGL->GLWindowInfo);
			if ( (err = GL::GetError()) != OpenTK::Graphics::OpenGL::ErrorCode::NoError ) throw gcnew System::Exception("MakeCurrent");
			
			if (dlg->m_glShaderProgram == 0) throw gcnew System::Exception("ShaderProgram Not Ready");

			GL::UseProgram(dlg->m_glShaderProgram);
			if ( (err = GL::GetError()) != OpenTK::Graphics::OpenGL::ErrorCode::NoError ) throw gcnew System::Exception("UseProgram");
			
			// Create Color Texture
			GL::GenTextures( 1, ColorTexture );
			GL::BindTexture( TextureTarget::Texture2D, ColorTexture );
			GL::TexParameter( TextureTarget::Texture2D, TextureParameterName::TextureMinFilter, (int) TextureMinFilter::Nearest );
			GL::TexParameter( TextureTarget::Texture2D, TextureParameterName::TextureMagFilter, (int) TextureMagFilter::Nearest );
			GL::TexParameter( TextureTarget::Texture2D, TextureParameterName::TextureWrapS, (int) TextureWrapMode::Clamp );
			GL::TexParameter( TextureTarget::Texture2D, TextureParameterName::TextureWrapT, (int) TextureWrapMode::Clamp );
			GL::TexImage2D( TextureTarget::Texture2D, 0, PixelInternalFormat::Rgba8, FboSqSize, FboSqSize, 0, PixelFormat::Rgba, PixelType::UnsignedByte, IntPtr::Zero );
			if ( (err = GL::GetError()) != OpenTK::Graphics::OpenGL::ErrorCode::NoError ) throw gcnew System::Exception("CreateColorTexture/TexImage2D");
			GL::BindTexture( TextureTarget::Texture2D, 0 ); // prevent feedback, reading and writing to the same image is a bad idea
			if ( (err = GL::GetError()) != OpenTK::Graphics::OpenGL::ErrorCode::NoError ) throw gcnew System::Exception("CreateColorTexture/BindTexture0");
 
			// Create Depth Renderbuffer
			GL::Ext::GenRenderbuffers( 1, DepthRenderbuffer );
			GL::Ext::BindRenderbuffer( RenderbufferTarget::RenderbufferExt, DepthRenderbuffer );
			GL::Ext::RenderbufferStorage(RenderbufferTarget::RenderbufferExt, (RenderbufferStorage)All::DepthComponent16, FboSqSize, FboSqSize);
			if ( (err = GL::GetError()) != OpenTK::Graphics::OpenGL::ErrorCode::NoError ) throw gcnew System::Exception("CreateDepthBuffer/RenderbufferStorage");
 
			// test for GL Error here (might be unsupported format)
 
			// Create a FBO and attach the textures
			GL::Ext::GenFramebuffers( 1, FboHandle );
			GL::Ext::BindFramebuffer( FramebufferTarget::FramebufferExt, FboHandle );
			GL::Ext::FramebufferTexture2D( FramebufferTarget::FramebufferExt, FramebufferAttachment::ColorAttachment0Ext, TextureTarget::Texture2D, ColorTexture, 0 );
			GL::Ext::FramebufferRenderbuffer( FramebufferTarget::FramebufferExt, FramebufferAttachment::DepthAttachmentExt, RenderbufferTarget::RenderbufferExt, DepthRenderbuffer );
			err_fb = GL::Ext::CheckFramebufferStatus( FramebufferTarget::FramebufferExt );
			if ( err_fb != OpenTK::Graphics::OpenGL::FramebufferErrorCode::FramebufferCompleteExt ) throw gcnew System::Exception("CreateFBO/CheckFramebufferStatus");
 
			// since there's only 1 Color buffer attached this is not explicitly required
			GL::DrawBuffer( (DrawBufferMode)FramebufferAttachment::ColorAttachment0Ext );

			//GL::PushAttrib( AttribMask::ViewportBit ); // stores GL::Viewport() parameters
			GL::Viewport( 0, 0, SqSize, SqSize );
 
			// render whatever your heart desires, when done ::::::
			
			GL::ShadeModel( OpenTK::Graphics::OpenGL::ShadingModel::Smooth );
			GL::Enable(EnableCap::DepthTest);
			GL::DepthRange( -1.0, 1.0 );
			GL::ClearDepth( 1.0 );
			GL::Disable( EnableCap::CullFace );
			GL::ClearColor(System::Drawing::Color::FromArgb(dlg->BakeTextureBaseColor));
			GL::Finish();
			GL::Clear(ClearBufferMask::ColorBufferBit | ClearBufferMask::DepthBufferBit);

			const MQ0x::Polygon::UVFaceBuffer::UVPoint* pPoints[4];
			const int point_fan_count = 64;
			float point_fan[point_fan_count+1][3];
			const MQ0x::Polygon::UVFaceBuffer& uvFaceBuffer = dlg->m_pUVBuffer->UVFaceBuffer();
			GL::DepthFunc( OpenTK::Graphics::OpenGL::DepthFunction::Always );
			//for each ( UINT id in IDs ) BakeTextureRecursiveFillTriangle( MQDocument, MQDocument->GetObjectFromUniqueID( id ), dlg, g );
			for ( MQ0x::Polygon::UVFaceBuffer::Face::Buffer::Index ctFace = uvFaceBuffer.FaceBuffer().BeginIndex(); ctFace < uvFaceBuffer.FaceBuffer().EndIndex(); ctFace++ ) {
				const MQ0x::Polygon::UVFaceBuffer::Face* pFace = uvFaceBuffer.FaceBuffer(ctFace);
				if ( pFace == NULL ) continue;
				const std::size_t ctFacePointMax = pFace->PointsCount();
				if ( (ctFacePointMax<3) || (4<ctFacePointMax) ) continue;
				bool isInvalid = false;
				for ( std::size_t ct = 0; ct < ctFacePointMax; ct++ ) {
					pPoints[ct] = uvFaceBuffer.UVBuffer(pFace->Points(ct).index);
					if ( pPoints[ct] == NULL ){ isInvalid = true; break; }
				}
				if ( isInvalid ) continue;
				if ( ctFacePointMax == 3 ) GL::Begin( OpenTK::Graphics::OpenGL::BeginMode::Triangles );
				else GL::Begin( OpenTK::Graphics::OpenGL::BeginMode::TriangleFan );
				for ( std::size_t ct = 0; ct < ctFacePointMax; ct++ ) {
					GL::Color4( MQCLIEX::FromMQVertexColor( pFace->Points(ct).color ) );
					GL::Vertex3( pPoints[ct]->uv.u, pPoints[ct]->uv.v, -1.0f );
				}
				GL::End();
			}

			if ( float::Epsilon <= (dlg->BakeTextureBorder) ) {
				GL::DepthFunc( OpenTK::Graphics::OpenGL::DepthFunction::Lequal );
				for ( MQ0x::Polygon::UVFaceBuffer::Edge::Buffer::ConstIterator itrEdge = uvFaceBuffer.EdgeBuffer().Begin(); itrEdge != uvFaceBuffer.EdgeBuffer().End(); itrEdge++ ) {
					if ( itrEdge->Owners().Count() <= 0 ) continue;
					const MQ0x::Polygon::UVFaceBuffer::Face* pOwnerFace = uvFaceBuffer.FaceBuffer(itrEdge->Owners(0));
					if ( (pOwnerFace == NULL) || (pOwnerFace->PointsCount() < 3) || (4 < pOwnerFace->PointsCount()) ) continue;
					const MQ0x::Polygon::UVFaceBuffer::UVPoint* pFirst = uvFaceBuffer.UVBuffer( itrEdge->IndexPair(0) );
					const MQ0x::Polygon::UVFaceBuffer::UVPoint* pSecond = uvFaceBuffer.UVBuffer( itrEdge->IndexPair(1) );
					const MQ0x::Polygon::UVFaceBuffer::Face::Point* pFaceEdgeFirst = pOwnerFace->GetPointFrom( itrEdge->IndexPair(0) );
					const MQ0x::Polygon::UVFaceBuffer::Face::Point* pFaceEdgeSecond = pOwnerFace->GetPointFrom( itrEdge->IndexPair(1) );
					if ( (pFirst == NULL) || (pSecond == NULL) || (pFaceEdgeFirst == NULL) || (pFaceEdgeSecond == NULL) ) continue;

					std::size_t center_triangle_begin = 1;
					if ( (pOwnerFace->PointsCount()==4) && (itrEdge->IndexPair().Contains(pOwnerFace->Points(3).index)) ) {
						center_triangle_begin = 2;
					}
					const MQ0x::Polygon::UVFaceBuffer::UVPoint* pPoint = uvFaceBuffer.UVBuffer(pOwnerFace->Points(0).index);
					if ( pPoint == NULL ) continue;
					UVCoordinate center( pPoint->uv );
					pPoint = uvFaceBuffer.UVBuffer(pOwnerFace->Points(center_triangle_begin).index);
					if ( pPoint == NULL ) continue;
					center.Add( pPoint->uv );
					pPoint = uvFaceBuffer.UVBuffer(pOwnerFace->Points(center_triangle_begin+1).index);
					if ( pPoint == NULL ) continue;
					center.Add( pPoint->uv );
					center.Mul( 1.0 / 3.0 );

					const UVCoordinate vCenterToFirst( pFirst->uv.u - center.u, pFirst->uv.v - center.v );
					UVCoordinate vFirstToSecond( pSecond->uv.u - pFirst->uv.u, pSecond->uv.v - pFirst->uv.v );
					vFirstToSecond.Magnitude( dlg->BakeTextureBorder ); // set length
					if ( vFirstToSecond.SqrMagnitude() <= double::Epsilon ) continue;

					// vCross is cross to edge and outside dircetion from inside triangle
					UVCoordinate vCross( vFirstToSecond.v, -vFirstToSecond.u );
					if ( (vCross.u * vCenterToFirst.u + vCross.v * vCenterToFirst.v) < 0.0 ) {
						vCross.u = -vFirstToSecond.v;
						vCross.v = vFirstToSecond.u;
					}

					GL::Begin( OpenTK::Graphics::OpenGL::BeginMode::TriangleFan );
					GL::Color4( MQCLIEX::FromMQVertexColor( pFaceEdgeFirst->color ) );
					GL::Vertex3( pFirst->uv.u, pFirst->uv.v, -1.0f );
					GL::Vertex3( pFirst->uv.u + vCross.u, pFirst->uv.v + vCross.v, 0.0 );
					GL::Color4( MQCLIEX::FromMQVertexColor( pFaceEdgeSecond->color ) );
					GL::Vertex3( pSecond->uv.u + vCross.u, pSecond->uv.v + vCross.v, 0.0 );
					GL::Vertex3( pSecond->uv.u, pSecond->uv.v, -1.0f );
					GL::End();

					for ( int ctEdgePoint = 0; ctEdgePoint < 2; ctEdgePoint++ ) {
						const MQ0x::Polygon::UVFaceBuffer::UVPoint* pEdge = ctEdgePoint == 0 ? pFirst : pSecond;
						const MQ0x::Polygon::UVFaceBuffer::Face::Point* pFaceEdge = ctEdgePoint == 0 ? pFaceEdgeFirst : pFaceEdgeSecond;
						point_fan[0][0] = pEdge->uv.u; // x
						point_fan[0][1] = pEdge->uv.v; // y
						point_fan[0][2] = -1.0f; // z
						for ( int ct = 0; ct < (point_fan_count-1); ct++ ) {
							float angle = 360.0f * (float)ct / (float)(point_fan_count-1);
							if ( 180.0 < angle ) angle -= 360.0f;
							const double rad_angle = MQ0x::Math::Deg2Rad( double(angle) );
							const double cos = std::cos( rad_angle );
							const double sin = std::sin( rad_angle );
							point_fan[1+ct][0] = float(vCross.u * cos - vCross.v * sin + point_fan[0][0]);
							point_fan[1+ct][1] = float(vCross.u * sin + vCross.v * cos + point_fan[0][1]);
							point_fan[1+ct][2] = std::abs(angle) / 180.0f;
						}
						point_fan[point_fan_count][0] = point_fan[1][0]; // x
						point_fan[point_fan_count][1] = point_fan[1][1]; // y
						point_fan[point_fan_count][2] = point_fan[1][2]; // z
						GL::Begin( OpenTK::Graphics::OpenGL::BeginMode::TriangleFan );
						GL::Color4( MQCLIEX::FromMQVertexColor(pFaceEdge->color) );
						for ( int ct = 0; ct < (point_fan_count + 1); ct++ ) GL::Vertex3( point_fan[ct] );
						GL::End();
					}
				}
			}
			GL::Finish();
			
			System::Drawing::Bitmap^ bmp = gcnew System::Drawing::Bitmap(SqSize, SqSize);
			System::Drawing::Imaging::BitmapData^ data = bmp->LockBits(System::Drawing::Rectangle(0,0,SqSize,SqSize),
				System::Drawing::Imaging::ImageLockMode::WriteOnly, System::Drawing::Imaging::PixelFormat::Format32bppArgb);
			GL::ReadPixels(0, 0, SqSize, SqSize, PixelFormat::Bgra, PixelType::UnsignedByte, data->Scan0);
			GL::Finish();
			bmp->UnlockBits(data);
			g->DrawImage( bmp, System::Drawing::Rectangle( 0, 0, size.Width, size.Height ) );
			delete data;
			delete bmp;
		}
		catch ( System::Exception^ ex ) {
			dlg->rdbDisplayNormal->Checked = true;
			dlg->rdbDisplayVColorPreview->Enabled = false;
			if ( err != OpenTK::Graphics::OpenGL::ErrorCode::NoError ) MessageBox::Show(ex->Message + ":" + err.ToString(), "OpenGL Error");
			else if ( err_fb != OpenTK::Graphics::OpenGL::FramebufferErrorCode::FramebufferCompleteExt )
				MessageBox::Show(ex->Message + ":" + err_fb.ToString(), "OpenGL Error");
			else throw;
		}
		finally {
			GL::Ext::BindFramebuffer( FramebufferTarget::FramebufferExt, 0 ); // return to visible framebuffer
			if ( FboHandle != 0 ) GL::Ext::DeleteFramebuffers( 1, &FboHandle );
			if ( ColorTexture != 0 ) GL::DeleteTextures( 1, &ColorTexture );
			if ( DepthRenderbuffer != 0 ) GL::Ext::DeleteRenderbuffers( 1, &DepthRenderbuffer );
		}
	}
	
	void CustomControls::MainEditForm::Document::PaintDefault(MainEditForm^ dlg, System::Drawing::Graphics^ g, bool isDraging, bool isBorderHighLight)
	{
		if ( (MQDocument==NULL) || (dlg==nullptr) || (g==nullptr) ) return;
		MaterialDrawData^ lastDraw = nullptr;
		System::Collections::Generic::Dictionary<UINT,MaterialDrawData^>^ dicDraw = gcnew System::Collections::Generic::Dictionary<UINT,MaterialDrawData^>();
		UINT currentMaterialID = -1;
		if (isDraging == false)
		{
			if (IsComboboxIndexSelecting)
			{
				EditData^ item = ItemFromIndex(dlg->cmbSelMat->SelectedIndex);
				if (item != nullptr) currentMaterialID = item->UniqueID;
			}
			if (currentMaterialID == -1)
			{
				int currentMaterialIndex = MQDocument->GetCurrentMaterialIndex();
				if (-1 < currentMaterialIndex) currentMaterialID = MQDocument->GetMaterial(currentMaterialIndex)->GetUniqueID();
			}
		}
		try {
			for each ( EditData^ data in m_payload->EditDataItems ) {
				if ( (data == nullptr) || (data->IsTransExecutableStatus==false) ) continue;
				MQMaterial mat = MQ0x::GetMaterial( MQDocument, data->UniqueID );
				if ( mat != NULL ) {
					// make fill rectangle
					const double pad = std::abs( data->Padding );
					const UVCoordinate vMul((data->ScaleU - pad * 2.0), (data->ScaleV - pad * 2.0));
					const UVCoordinate vAdd(data->MoveU + pad, data->MoveV + pad );
					float left = float(dlg->pnlGL->GLSize.Width * vAdd.u);
					float top = float(dlg->pnlGL->GLSize.Height * vAdd.v);
					float right = float(dlg->pnlGL->GLSize.Width * (vMul.u + vAdd.u) );
					float bottom = float(dlg->pnlGL->GLSize.Height * (vMul.v + vAdd.v) );
					// make fill color and border color
					MQColor mqCol = mat->GetColor();
					System::Drawing::Color matColor = System::Drawing::Color::FromArgb(
						data->UniqueID == currentMaterialID ? 0xFF : (0xFF>>2), int(0xFF * mqCol.r), int(0xFF * mqCol.g), int(0xFF * mqCol.b) );
					System::Drawing::Pen^ drawPen = nullptr;
					if ( (data->UniqueID == currentMaterialID) && isBorderHighLight )
						drawPen = gcnew System::Drawing::Pen( System::Drawing::SystemColors::Highlight, 2 );
					else {
						MQ0x::HSV<float> hsv(mqCol);
						if ( hsv.isGrayScale() ) hsv.v = 1.0f - hsv.v;
						else {	
							hsv.s = MQ0x::MoveTo(hsv.s, 1.0f-hsv.s, 0.25f);
							hsv.v = hsv.v * -0.5f + 0.75f;
						}
						const MQColor invColor = hsv.toMQColor();
						drawPen = gcnew System::Drawing::Pen(
							System::Drawing::Color::FromArgb(int(invColor.r * 0xFF),int(invColor.g * 0xFF),int(invColor.b * 0xFF)),1);
					}
					MaterialDrawData^ drawData = gcnew MaterialDrawData( System::Drawing::RectangleF(left,top,right-left,bottom-top),
												gcnew System::Drawing::SolidBrush(matColor), drawPen );
					if ( data->UniqueID == currentMaterialID ) {
						drawData->isCurrent = true;
						lastDraw = drawData;
					}
					dicDraw->Add( data->UniqueID, drawData );
				}
			}
			if ((dlg->chkDisplayFill->Checked) && (dlg->pnlGL->GLBackgroundImage != nullptr) && (dlg->rdbDisplayNormal->Checked))
			{
				for each (MaterialDrawData^ drawdata in dicDraw->Values)
				{
					if (drawdata->isCurrent == false) g->FillRectangle(System::Drawing::Brushes::White, drawdata->rect);
				}
			}
			if (dlg->chkDisplayPoly->Checked)
			{
				const MQ0x::Polygon::UVFaceBuffer& uvFaceBuffer = dlg->m_pUVBuffer->UVFaceBuffer();
				const double scale_width = double(dlg->pnlGL->GLSize.Width) * 0.5;
				const double scale_height = double(dlg->pnlGL->GLSize.Height) * 0.5;
				for (MQ0x::Polygon::UVFaceBuffer::Edge::Buffer::ConstIterator itrEdge = uvFaceBuffer.EdgeBuffer().Begin(); itrEdge != uvFaceBuffer.EdgeBuffer().End(); itrEdge++)
				{
					if (itrEdge->Owners().Count() <= 0) continue;
					const MQ0x::Polygon::UVFaceBuffer::Face* pOwnerFace = uvFaceBuffer.FaceBuffer(itrEdge->Owners(0));
					if (pOwnerFace == NULL) continue;
					const MQ0x::Polygon::UVFaceBuffer::UVPoint* pFirst = uvFaceBuffer.UVBuffer(itrEdge->IndexPair(0));
					const MQ0x::Polygon::UVFaceBuffer::UVPoint* pSecond = uvFaceBuffer.UVBuffer(itrEdge->IndexPair(1));
					const MQObject obj = MQDocument->GetObjectFromUniqueID(pOwnerFace->object_uid);
					if ((pFirst == NULL) || (pSecond == NULL) || (obj == NULL)) continue;
					const MQMaterial mat = MQDocument->GetMaterial(obj->GetFaceMaterial(obj->GetFaceIndexFromUniqueID(pOwnerFace->face_uid)));
					if (mat == NULL) continue;
					const UINT id = mat->GetUniqueID();
					if (dicDraw->ContainsKey(id))
					{
						g->DrawLine(dicDraw[id]->matPen,
							float(pFirst->uv.u * scale_width + scale_width),
							float(pFirst->uv.v * scale_height + scale_height),
							float(pSecond->uv.u * scale_width + scale_width),
							float(pSecond->uv.v * scale_height + scale_height)
							);
					}
				}
			}
			if ((dlg->chkDisplayFill->Checked)) for each (MaterialDrawData^ drawdata in dicDraw->Values) {
				if (drawdata->isCurrent == false) g->FillRectangle(drawdata->matBrush, drawdata->rect);
			}
			if (lastDraw != nullptr) g->FillRectangle(lastDraw->matBrush, lastDraw->rect);
			if (dlg->chkDisplayBorder->Checked)
			{
				for each (MaterialDrawData^ drawdata in dicDraw->Values)
				{
					if (drawdata->isCurrent == false)
					{
						g->DrawRectangle( drawdata->matPen, drawdata->rect.Left, drawdata->rect.Top, drawdata->rect.Width, drawdata->rect.Height );
					}
				}
			}
			if (lastDraw != nullptr)
			{
				g->DrawRectangle(lastDraw->matPen, lastDraw->rect.Left, lastDraw->rect.Top, lastDraw->rect.Width, lastDraw->rect.Height);
			}
		}
		catch ( System::Exception^ ) { throw; }
		finally {
			for each ( MaterialDrawData^ data in dicDraw->Values ) if ( data != nullptr ) delete data;
			dicDraw->Clear();
			if ( lastDraw != nullptr ) { delete lastDraw; lastDraw = nullptr; }
		}
	}

	void CustomControls::MainEditForm::Document::RefreshDialog(MainEditForm^ dlg, int materialIndex)
	{
		if ((dlg == nullptr) || (0 < m_refreshingLock->KeyCount)) return;
		DotNetEx::CounterKey::Lock eventLock(m_refreshingLock);
		System::Collections::Generic::List<UINT>^ IDs = MQCLIEX::GetSelectedObjectIDs(MQDocument, IsRecursiveTrans);
		if (MQDocument != NULL)
		{
			MQObject obj = MQDocument->GetObject(MQDocument->GetCurrentObjectIndex());
			if (IsRecursiveTrans)
			{
				for(; obj != NULL; obj = MQDocument->GetParentObject(obj))
				{
					const UINT UniqueID = obj->GetUniqueID();
					if (IDs->Contains(UniqueID)) { IDs->Remove(UniqueID); IDs->Insert(0,UniqueID); break; }
				}
			}
			else if ( obj != NULL ) {
				const UINT UniqueID = obj->GetUniqueID();
				if ( IDs->Contains(UniqueID) ) { IDs->Remove(UniqueID); IDs->Insert(0,UniqueID); }
			}
		}

		System::Collections::Generic::List<System::String^>^ selectedObjectNames = gcnew System::Collections::Generic::List<System::String^>(IDs->Count);
		for each ( UINT id in IDs )
		{
			System::String^ item = gcnew System::String(MQ0x::GetName(MQDocument->GetObjectFromUniqueID(id)).c_str());
			if (System::String::IsNullOrEmpty(item)==false) selectedObjectNames->Add(item);
		}
		dlg->lblCurObjName->Text = System::String::Join(", ", selectedObjectNames);
		dlg->btnTrans->Enabled = (0 < IDs->Count);

		if (0 < m_payload->EditDataItems->Count)
		{
			dlg->tblEditEnabled = true;
			dlg->btnPilotSwitch->Text = PILOT_SWITCH_STRING_CLEAR;
			dlg->chkClone->Checked = IsCloneTrans;
			dlg->chkRecursive->Checked = IsRecursiveTrans;
			dlg->chkCmbTex->Checked = IsIntegrateTexture;
			dlg->chkCmbTexClear->Checked = IsClrTextureOnIntegrate;
			dlg->txtCmbTexClearColor->Text = TextureClearColor.ToString(dlg->txtCmbTexClearColor->Tag->ToString());
			dlg->chkCmbAlpha->Checked = IsIntegrateAlpha;
			dlg->chkCmbAlphaClear->Checked = IsClrAlphaOnIntegrate;
			dlg->txtCmbAlphaClearColor->Text = AlphaClearColor.ToString(dlg->txtCmbAlphaClearColor->Tag->ToString());
			dlg->chkCmbBump->Checked = IsIntegrateBump;
			dlg->chkCmbBumpClear->Checked = IsClrBumpOnIntegrate;
			dlg->txtCmbBumpClearColor->Text = BumpClearColor.ToString(dlg->txtCmbBumpClearColor->Tag->ToString());
			dlg->txtBakeVColorW->Text = dlg->BakeTextureW.ToString();
			dlg->txtBakeVColorH->Text = dlg->BakeTextureH.ToString();
			dlg->txtBakeVColorClear->Text = dlg->BakeTextureBaseColor.ToString(dlg->txtBakeVColorClear->Tag->ToString());
			dlg->txtBakeVColorBorder->Text = dlg->BakeTextureBorder.ToString(dlg->txtBakeVColorBorder->Tag->ToString());
		}
		else
		{
			dlg->tblEditEnabled = false;
			dlg->btnPilotSwitch->Text = PILOT_SWITCH_STRING_CREATE;
			dlg->chkClone->Checked = false;
			dlg->chkRecursive->Checked = false;
			dlg->chkCmbTex->Checked = false;
			dlg->chkCmbTexClear->Checked = false;
			dlg->txtCmbTexClearColor->Text = nullptr;
			dlg->chkCmbAlpha->Checked = false;
			dlg->chkCmbAlphaClear->Checked = false;
			dlg->txtCmbAlphaClearColor->Text = nullptr;
			dlg->chkCmbBump->Checked = false;
			dlg->chkCmbBumpClear->Checked = false;
			dlg->txtCmbBumpClearColor->Text = nullptr;
			dlg->txtBakeVColorW->Text = nullptr;
			dlg->txtBakeVColorH->Text = nullptr;
			dlg->txtBakeVColorClear->Text = nullptr;
			dlg->txtBakeVColorBorder->Text = nullptr;
		}
			
		bool failedOnRefresh = true;
		EditData::SelectedCollection^ selectedItems = nullptr;
		if (IsComboboxIndexSelecting)
		{
			System::Collections::Generic::List<EditData^>^ itemList = gcnew System::Collections::Generic::List<EditData^>();
			EditData^ item = ItemFromIndex(dlg->cmbSelMat->SelectedIndex);
			if (item != nullptr) itemList->Add(item);
			selectedItems = gcnew EditData::SelectedCollection(itemList->AsReadOnly());
		}
		else selectedItems = ItemsOnSelected(dlg->cmbSelMat->SelectedIndex);

		if (dlg->tblEditEnabled)
		{
			if (1 < selectedItems->Count)
			{
				/* multi selected */
				dlg->cmbSelMat->SelectedIndex = -1;
				dlg->chkSelMatTrans->ThreeState = true;
				bool^ chk = selectedItems->IsTransExecute;
				if (chk == nullptr)
				{
					dlg->chkSelMatTrans->ThreeState = true;
					dlg->chkSelMatTrans->CheckState = System::Windows::Forms::CheckState::Indeterminate;
				}
				else
				{
					dlg->chkSelMatTrans->Checked = *chk;
					dlg->chkSelMatTrans->ThreeState = false;
				}
				dlg->cmbToMat->SelectedIndex = ItemIndexFromID(selectedItems->ToMaterialID);
				float flt;
				if (float::IsNaN(flt = selectedItems->ScaleU)) dlg->txtSclU->Text = nullptr;
				else dlg->txtSclU->Text = flt.ToString(dlg->txtSclU->Tag->ToString());
				if (float::IsNaN(flt = selectedItems->ScaleV)) dlg->txtSclV->Text = nullptr;
				else dlg->txtSclV->Text = flt.ToString(dlg->txtSclV->Tag->ToString());
				if (float::IsNaN(flt = selectedItems->MoveU)) dlg->txtMovU->Text = nullptr;
				else dlg->txtMovU->Text = flt.ToString(dlg->txtMovU->Tag->ToString());
				if (float::IsNaN(flt = selectedItems->MoveV)) dlg->txtMovV->Text = nullptr;
				else dlg->txtMovV->Text = flt.ToString(dlg->txtMovV->Tag->ToString());
				if (float::IsNaN(flt = selectedItems->Padding)) dlg->txtPadding->Text = nullptr;
				else dlg->txtPadding->Text = flt.ToString(dlg->txtPadding->Tag->ToString());
				dlg->tblMaterialEditEnabled = true;
				failedOnRefresh = false;
			}
			else
			{
				/* single selected */
				int newIndex = materialIndex;
				EditData^ data = ItemFromIndex(newIndex);
				if (data == nullptr)
				{
					data = ItemFromIndex(newIndex = dlg->cmbSelMat->SelectedIndex);
					if (data == nullptr)
					{
						if (0 < selectedItems->Count) data = ItemFromIndex(newIndex = ItemIndexFromID(selectedItems[0]->UniqueID));
						if (data == nullptr) data = ItemFromIndex(newIndex = 0);
					}
				}

				if (data != nullptr)
				{
					if (dlg->cmbSelMat->SelectedIndex != newIndex) dlg->cmbSelMat->SelectedIndex = newIndex;
					dlg->chkSelMatTrans->Checked = data->IsTransExecute;
					dlg->chkSelMatTrans->ThreeState = false;
					dlg->cmbToMat->SelectedIndex = ItemIndexFromID(data->ToMaterialID);
					dlg->txtSclU->Text = data->ScaleU.ToString(dlg->txtSclU->Tag->ToString());
					dlg->txtSclV->Text = data->ScaleV.ToString(dlg->txtSclV->Tag->ToString());
					dlg->txtMovU->Text = data->MoveU.ToString(dlg->txtMovV->Tag->ToString());
					dlg->txtMovV->Text = data->MoveV.ToString(dlg->txtMovU->Tag->ToString());
					dlg->txtPadding->Text = data->Padding.ToString(dlg->txtPadding->Tag->ToString());
					dlg->tblMaterialEditEnabled = true;
					failedOnRefresh = false;
				}
			}
		}

		if (failedOnRefresh)
		{
			/* disabled */
			dlg->cmbSelMat->SelectedIndex = -1;
			dlg->chkSelMatTrans->Checked = false;
			dlg->chkSelMatTrans->ThreeState = false;
			dlg->cmbToMat->SelectedIndex = -1;
			dlg->txtSclU->Text = nullptr;
			dlg->txtSclV->Text = nullptr;
			dlg->txtMovU->Text = nullptr;
			dlg->txtMovV->Text = nullptr;
			dlg->txtPadding->Text = nullptr;
			dlg->tblMaterialEditEnabled = false;
		}
		if (dlg->tblEditEnabled) dlg->pnlGL->Invalidate();
	}

	void CustomControls::MainEditForm::Document::SelectedMaterialsReset(MainEditForm^ dlg)
	{
		if ( MQDocument == NULL ) return;
		EditData::SelectedCollection^ selectedItems = ItemsOnSelected();
		for each ( EditData^ data in selectedItems ) data->Reset();
		if ( 0 < selectedItems->Count ) RefreshDialog(dlg, -1);
	}

	void CustomControls::MainEditForm::Document::SelectedMaterialsToMatClear(MainEditForm^ dlg)
	{
		if (MQDocument == NULL) return;
		EditData::SelectedCollection^ selectedItems = ItemsOnSelected();
		for each (EditData^ data in selectedItems)
		{
			data->ToMaterialID = -1;
			data->ToMaterialName = nullptr;
		}
		if (0 < selectedItems->Count) RefreshDialog(dlg, -1);
	}

	void CustomControls::MainEditForm::Document::TryTrans(MainEditForm^ dlg)
	{
		if ( (MQDocument==NULL) || (dlg==nullptr) ) return;

		System::Collections::Generic::List<UINT>^ selectedObjectIDs = MQCLIEX::GetSelectedObjectIDs(MQDocument, IsRecursiveTrans);
		if ( selectedObjectIDs->Count <= 0 )
		{
			MessageBox::Show(dlg,"オブジェクトが選択されていません");
			return;
		}
		
		ImageIntegrator imgIntegra(MQDocument, m_payload);
		System::String^ strImgIntegraError = imgIntegra.errorMessage;
		if ( System::String::IsNullOrEmpty( strImgIntegraError ) == false ) {
			MessageBox::Show( strImgIntegraError, "テクスチャ統合が指定されていますが、統合できない設定が見つかりました。", System::Windows::Forms::MessageBoxButtons::OK );
			return;
		}

		{
			System::Text::StringBuilder^ warning = gcnew System::Text::StringBuilder();
			if ( IsCloneTrans == false ) warning->AppendLine("【オブジェクト複製OFF】変換時にオブジェクトを複製しません。元のオブジェクトのUV情報が直接書き換わります。");
			if ( IsIncludeNotChangeMaterialSetting() ) warning->AppendLine("【マテリアル変更OFF】変換時にマテリアル変更をしない設定が含まれています。");
			if ( System::String::IsNullOrEmpty(warning->ToString()) == false ) {
				warning->AppendLine();
				warning->Append("変換しますか？");
				if ( MessageBox::Show( warning->ToString(), "確認", System::Windows::Forms::MessageBoxButtons::OKCancel )
					== System::Windows::Forms::DialogResult::Cancel ) return;
			}
		}

		for each( ImageIntegrator::DestImageItem^ data in imgIntegra.destinations->Values ) {
			if ( data != nullptr ) while ( data->isNullOrEmpty ) {
				TextureSetForm texSetDlg(MQDocument);
				texSetDlg.Text = "統合先の画像サイズを設定してください。";
				System::String^ strMsg = "使用中のマテリアル：";
				std::list<const MQMaterial> *ownerMat = data->ownerMat;
				if ( ownerMat != NULL ) for ( std::list<const MQMaterial>::iterator itrMat = ownerMat->begin(); itrMat != ownerMat->end(); itrMat++) {
					strMsg += System::String::Format( "[{0}]", gcnew System::String( MQ0x::GetName(*itrMat).c_str() ) );
				}
				if ( (0<dlg->TexSetFormSize.Width)&&(0<dlg->TexSetFormSize.Height) ) texSetDlg.Size = dlg->TexSetFormSize;
				texSetDlg.Message = strMsg;
				texSetDlg.U = data->expectWidth;
				texSetDlg.V = data->expectHeight;
				texSetDlg.ImagePath = data->path;
				System::Windows::Forms::DialogResult ret = texSetDlg.ShowDialog(dlg);
				dlg->TexSetFormSize = texSetDlg.Size;
				if ( ret == System::Windows::Forms::DialogResult::OK ) {
					if ( (0 < texSetDlg.U) && (0 < texSetDlg.V) ) data->Resize(texSetDlg.U, texSetDlg.V);
					else MessageBox::Show( "無効な値が入力されました。" );
				}
				else {
					MessageBox::Show( "処理をキャンセルしました。" );
					return;
				}
			}
		}

		// do trans objects
		int ctTrans = 0;
		{
			std::vector<char> buf;
			for each ( UINT id in selectedObjectIDs ) ctTrans += RecursiveTrans(MQDocument->GetObjectFromUniqueID( id ), IsRecursiveTrans ? 0 : -1, &buf);
		}
		
		imgIntegra.ExecuteIntegrate();
		int ctTexIntegrate = 0;
		for each ( ImageIntegrator::DestImageItem^ destItem in imgIntegra.destinations->Values ) {
			if ( destItem != nullptr ) {
				if ( destItem->TrySave() ) ctTexIntegrate++;
				else {
					System::Text::StringBuilder^ failedSaveImgMsg = gcnew System::Text::StringBuilder();
					failedSaveImgMsg->AppendLine("テクスチャ画像の保存に失敗しました。");
					failedSaveImgMsg->AppendLine(destItem->path);
					System::String^ strMsg = "使用中のマテリアル：";
					std::list<const MQMaterial> *ownerMat = destItem->ownerMat;
					if ( ownerMat != NULL ) for ( std::list<const MQMaterial>::iterator itrMat = ownerMat->begin(); itrMat != ownerMat->end(); itrMat++) {
						strMsg += System::String::Format( "[{0}]", gcnew System::String( MQ0x::GetName(*itrMat).c_str() ) );
					}
					failedSaveImgMsg->AppendLine(strMsg);
				}
			}
		}

		System::Text::StringBuilder^ report = gcnew System::Text::StringBuilder();
		report->AppendLine( ctTrans.ToString() + "個のオブジェクトを変換しました。" );
		report->AppendLine( ctTexIntegrate.ToString() + "個の画像を更新しました。" );
		MessageBox::Show( report->ToString() );
	}

	/// @brief 再帰変換処理。depthマイナスの場合は再帰処理せずdepth設定を行わない。クローニング設定チェックしてクローニング処理の有無はこの関数内部で応ずる。
	int CustomControls::MainEditForm::Document::RecursiveTrans(const MQObject obj, const int depth, std::vector<char>* const buf)
	{
		int ret = 0;
		
		if ( (MQDocument!=NULL) && (obj != NULL) ) {
			
			MQObject objTrans = obj; // target of trans
			if ( IsCloneTrans ) {
				// clone and insert
				std::string rename = MQ0x::GetCountUpCloneableUniqueName( MQDocument, obj, buf );
				objTrans = obj->Clone();
				if ( objTrans == NULL ) return 0;
				objTrans->SetName(rename.c_str());
				if ( MQDocument->AddObject(objTrans) < 0 ) {
					objTrans->DeleteThis();
					return 0;
				}
				// set param
				if ( 0 <= depth ) objTrans->SetDepth(depth);
			}

			// trans objTrans
			{
				ret++;
				int ctMat = MQDocument->GetMaterialCount();
				int ctFace = objTrans->GetFaceCount();
				std::vector<MQCoordinate> uvBuf(4);
				for ( int idxFace = 0; idxFace < ctFace; idxFace++ ) {
					EditData^ data = nullptr;
					MQMaterial mat = MQDocument->GetMaterial( objTrans->GetFaceMaterial(idxFace) );
					if ( mat != NULL ) data = ItemFromID(mat->GetUniqueID());
					if ( (data != nullptr) && data->IsTransExecutableStatus ) {
						const double pad = std::abs(data->Padding);
						const UVCoordinate vMul( data->ScaleU - pad * 2.0, data->ScaleV - pad * 2.0 );
						const UVCoordinate vAdd( data->MoveU + pad, data->MoveV + pad );
						const int ctFacePoint = objTrans->GetFacePointCount(idxFace);
						if ( (long)uvBuf.size() < (long)ctFacePoint ) uvBuf.resize(ctFacePoint);
						objTrans->GetFaceCoordinateArray(idxFace,&uvBuf[0]);
						for ( int idxPt = 0; idxPt < ctFacePoint; idxPt++ ) {
							UVCoordinate uv(uvBuf[idxPt]);
							uv.Mul( vMul );
							uv.Add( vAdd );
							uv.CopyTo(uvBuf[idxPt]);
						}
						objTrans->SetFaceCoordinateArray(idxFace,&uvBuf[0]);
						int toMatIndex = MQ0x::GetMaterialIndex(MQDocument,data->ToMaterialID);
						if ( 0 <= toMatIndex ) objTrans->SetFaceMaterial(idxFace,toMatIndex);
					}
				}
			}

			// do recursive
			if ( 0 <= depth ) {
				const int ctChild = MQDocument->GetChildObjectCount(obj);
				for ( int indexChild = 0; indexChild < ctChild; indexChild++ )
				{
					MQObject objChild = MQDocument->GetChildObject( obj, indexChild );
					if ( objChild == NULL ) continue;
					ret += RecursiveTrans(objChild, depth + 1, buf);
				}
			}
			
		}

		return ret;
	}
}
