#include "Destiny3DExporter.hpp"

#include "deFileSystem.hpp"
#include "deFile.hpp"
#include "deWorld.hpp"
#include "deBitmap.hpp"
#include "deMesh.hpp"
#include "deSurface.hpp"
#include "deScene.hpp"
#include "deConsole.hpp"
#include "deHash.hpp"
#include <algorithm>

static Destiny3DFormat	gDe3DExporter;		// Global exporter instance
static ExportSettings	gSettings;			// Global export settings
static const int		gMaxOutputLevel = 4;

void LogExportMsg(int level, char* format, ...)
{
	va_list Marker;
	static char Buffer[2048];
	
	// Write text to string
	// and append to edit control
	va_start(Marker, format);
	_vsnprintf(Buffer, 2048, format, Marker);
	va_end(Marker);
	Buffer[2047] = '\0';

	if (level <= gMaxOutputLevel)
		gDe3DExporter.AddLogString(Buffer);
	deConsoleOut("%s", Buffer);
}

// FLEXPORTER Identification Callbacks

//////////////////////////////////////////////////////////////////////////////
/**
*	Flexporter callback.
*	Gives a brief exporter description. That string is displayed inside MAX, in the Options Panel.
*	\relates	VoidFormat
*	\fn			ExporterDescription()
*	\return		a description string
*/
//////////////////////////////////////////////////////////////////////////////
const char* ExporterDescription()
{ 
	return "Destiny3D DWorld Exporter";
}

//////////////////////////////////////////////////////////////////////////////
/**
*	Flexporter callback.
*	Returns the format's extension. That string is displayed inside MAX, in the Options Panel.
*	\relates	VoidFormat
*	\fn			FormatExtension()
*	\return		an extension string
*/
//////////////////////////////////////////////////////////////////////////////
const char* FormatExtension()
{
	return "dworld";
}

//////////////////////////////////////////////////////////////////////////////
/**
*	Flexporter callback.
*	Returns the one and only exporter instance.
*	\relates	VoidFormat
*	\fn			GetExporter()
*	\return		pointer to the global exporter instance.
*/
//////////////////////////////////////////////////////////////////////////////
ExportFormat* GetExporter()
{
	return &gDe3DExporter;
}

//////////////////////////////////////////////////////////////////////////////
/**
*	Flexporter callback.
*	Returns the default exportation settings for this format. This is the right place to initialize the default settings for your format.
*	\relates	VoidFormat
*	\fn			GetDefaultSettings()
*	\return		pointer to the global ExportSettings instance.
*/
//////////////////////////////////////////////////////////////////////////////
ExportSettings* GetDefaultSettings()
{
	// General settings
	gSettings.mExportWholeScene				= true;
	gSettings.mExportVisTrack				= false;
	gSettings.mExportHiddenNodes			= false;
	gSettings.mAutoUnload					= true;
	gSettings.mAutoLoad						= true;
	gSettings.mGlobalScale					= 0.125f;
	gSettings.mLocale						= FLX_LOCALE_ENGLISH;
	gSettings.mExportPHYSIQUE				= false;
	gSettings.mExportSKIN					= false;
	gSettings.mExportBIPED					= false;
	gSettings.mAlwaysExportMotion			= false;
	// Mesh settings
	gSettings.mExpUVW						= true;
	gSettings.mDiscardW						= true;
	gSettings.mExpVTXColor					= true;
	gSettings.mUseSmgrp						= true;
	gSettings.mRemoveScaling				= true;
	gSettings.mConvertToD3D					= true;
	gSettings.mAbsolutePRS					= true;
	gSettings.mConvexHull					= false;
	gSettings.mBoundingSphere				= false;
	gSettings.mInertiaTensor				= false;
	gSettings.mEdgeVis						= false;
	gSettings.mMakeManifold					= true;
	gSettings.mExpTargetNodes				= true;
	gSettings.mDiscardInstances				= true;
	// Consolidation settings
	gSettings.mConsolidateMesh				= true;
	gSettings.mComputeFaceNormals			= false;
	gSettings.mComputeVertexNormals			= true;
	gSettings.mExportNormalInfo				= false;
	gSettings.mWeightNormalsByArea			= true;
	gSettings.mCacheOptimize				= false;
	// Material settings
	gSettings.mForceAmbient					= false;
	gSettings.mForceDiffuse					= false;
	gSettings.mForceSpecular				= false;
	gSettings.mForceFilter					= false;
	gSettings.mExpMtlCtrl					= false;
	gSettings.mMaterialSharing				= true;
	// Texture settings
	gSettings.mTexnameOnly					= true;
	gSettings.mOpacityInAlpha				= false;
	gSettings.mKeepAmbientTexture			= false;
	gSettings.mKeepDiffuseTexture			= true;
	gSettings.mKeepSpecularTexture			= false;
	gSettings.mKeepShininessTexture			= false;
	gSettings.mKeepShiningStrengthTexture	= false;
	gSettings.mKeepSelfIllumTexture			= false;
	gSettings.mKeepOpacityTexture			= true;
	gSettings.mKeepFilterTexture			= false;
	gSettings.mKeepBumpTexture				= true;
	gSettings.mKeepReflexionTexture			= false;
	gSettings.mKeepRefractionTexture		= false;
	gSettings.mKeepDisplacementTexture		= false;
	gSettings.mTexMaxSize					= 2048;
	gSettings.mFlipHorizontal				= false;
	gSettings.mFlipVertical					= false;
	gSettings.mQuantizeTextures				= false;
	// Camera settings
	gSettings.mExpCameras					= true;
	gSettings.mExpFOVCtrl					= true;
	gSettings.mExpCamCtrl					= true;
	// Light settings
	gSettings.mExpLights					= true;
	gSettings.mExpLitCtrl					= false;
	gSettings.mExpProjMaps					= false;
	gSettings.mComputeVtxColors				= false;
	gSettings.mComputeShadows				= false;
	gSettings.mColorSmoothing				= false;
	// Animation settings
	gSettings.mSingleFrame					= true;
	gSettings.mSampling						= true;
	gSettings.mSamplingRate					= 1;
	// Shape settings
	gSettings.mExpShapes					= true;
	gSettings.mShapeToMesh					= true;
	// Helper settings
	gSettings.mExpHelpers					= true;
	// IK settings
	gSettings.mExportIK						= false;
	
	return &gSettings;
}

//////////////////////////////////////////////////////////////////////////////
/**
*	Flexporter callback.
*	Returns the enabled/disabled status for all settings. This is the right place to disable options your own format doesn't support.
*	\relates	VoidFormat
*	\fn			GetEnabledSettings()
*	\return		pointer to a global Enabled instance.
*/
//////////////////////////////////////////////////////////////////////////////
Enabled* GetEnabledSettings()
{
	static Enabled Settings;
	Settings.mFlipHorizontal				= false;
	Settings.mFlipVertical					= false;
	Settings.mQuantizeTextures				= false;
	Settings.mExportPHYSIQUE				= false;
	Settings.mExportSKIN					= false;
	Settings.mExportBIPED					= false;
	Settings.mAlwaysExportMotion			= false;
	Settings.mConvexHull					= false;
	Settings.mInertiaTensor					= false;
	Settings.mEdgeVis						= false;
	Settings.mTexMaxSize					= 1;
	return &Settings;
}

//////////////////////////////////////////////////////////////////////////////
/**
*	Flexporter callback.
*	Returns the FLEXPORTER SDK Version.
*	\relates	VoidFormat
*	\fn			Version()
*	\return		FLEXPORTER_VERSION
*/
//////////////////////////////////////////////////////////////////////////////
int Version()
{
	return FLEXPORTER_VERSION;
}






//////////////////////////////////////////////////////////////////////////////
/**
*	A Destiny3D DWorld exporter plug-in for Flexporter..
*	\class		VoidFormat
*	\author		Pierre Terdiman
*	\version	1.0
*/
//////////////////////////////////////////////////////////////////////////////

Destiny3DFormat::Destiny3DFormat()
{
	m_IsMotionFile = false;
	m_WorldID = 0;
	m_EngineInited = false;
}

Destiny3DFormat::~Destiny3DFormat()
{
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	Plug-in initialization method.
 *	This method is called once before each export. When this method is called, the mSettings and mFilename members of the base format are valid.
 *	\param		motion		[in] true for pure motion files.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::Init(bool /*motion*/)
{
	if (m_EngineInited)
	{
		IdeBitmap_Initialize();
		IdeBrush_Initialize();
		IdeScene_Initialize();
		IdeDriver_Initialize();
	}
	{
		IdeWorldManager* manager = IdeWorld_GetManager();
		if (!manager)
			return false;
		m_WorldID = manager->CreateWorld();
		if (!m_WorldID)
			return false;
	}

	m_Meshes.resize(0);

	deConsole_MakeConsole();
	//deConsoleXY(0,0);
	LogExportMsg(1, "Starting Destiny3D Export Process\n");

	m_NullTexture = IdeRenderState_CreateRenderTexture();
	m_SceneRoom = IdeSceneRoom_CreateRoom();
	m_ABT = IdeSceneObject_CreateSceneABT();
	m_SceneRoom->AddSceneObject(m_ABT);

	m_NullTexture->SetWorldID(m_WorldID);
	m_SceneRoom->SetWorldID(m_WorldID);
	m_ABT->SetWorldID(m_WorldID);
	
	LogExportMsg(2, "Containers created\n");

	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	Scene export method.
 *	This method is called once to export the scene global information.
 *	\param		maininfo	[in] main info structure
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::SetSceneInfo(const MainDescriptor& /*maininfo*/)
{
	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	Camera export method.
 *	This method is called once for each exported camera.
 *	\param		camera		[in] a structure filled with current camera information.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::ExportCamera(const CameraDescriptor& /*camera*/)
{
	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	Controller export method.
 *	This method is called once for each exported controller.
 *	\param		controller		[in] a structure filled with current controller information.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::ExportController(const ControllerDescriptor& /*controller*/)
{
	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	Helper export method.
 *	This method is called once for each exported helper.
 *	\param		helper		[in] a structure filled with current helper information.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::ExportHelper(const HelperDescriptor& /*helper*/)
{
	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	Motion export method.
 *	This method is called once for each exported motion.
 *	\param		motion		[in] a structure filled with current motion information.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::ExportMotion(const MotionDescriptor& /*motion*/)
{
	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	Shape export method.
 *	This method is called once for each exported shape.
 *	\param		shape		[in] a structure filled with current shape information.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::ExportShape(const ShapeDescriptor& /*shape*/)
{
	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
*	Space warp export method.
*	This method is called once for each exported space warp.
*	\param		space_warp	[in] a structure filled with current space_warp information.
*	\return		true if success.
*/
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::ExportSpaceWarp(const SpaceWarpDescriptor& /*space_warp*/)
{
	return true;
}



//////////////////////////////////////////////////////////////////////////////
/**
 *	Light export method.
 *	This method is called once for each exported light.
 *	\param		light		[in] a structure filled with current light information.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::ExportLight(const LightDescriptor& /*light*/)
{
	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	Mesh export method.
 *	This method is called once for each exported mesh.
 *	\param		mesh		[in] a structure filled with current mesh information.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::ExportMesh(const MeshDescriptor& mesh)
{
	if (mesh.mIsInstance)
	{
		LogExportMsg(2, "  Instanced mesh, ID=%d, name='%s'\n", mesh.mObjectID, mesh.mName);
		FlexPortInstanceMesh instance;
		instance.ThisID = mesh.mObjectID;
		instance.MasterID = mesh.mMasterID;
		instance.MeshData = NULL;
		instance.Position = mesh.mPrs;
		m_InstanceMeshes.AddElement(instance);
	}
	else if (1)
	{
		LogExportMsg(2, "  Master mesh, ID=%d, name='%s'\n", mesh.mObjectID, mesh.mName);
		int k;

		if (!mesh.mCleanMesh)
		{
			LogExportMsg(0, "Mesh was not consolidated\n");
			return false;
		}
		if (!mesh.mCleanMesh->Geometry.Verts)
		{
			LogExportMsg(0, "Mesh has no verts\n");
			return false;
		}
		if (!mesh.mCleanMesh->Topology.VRefs)
		{
			LogExportMsg(0, "Mesh has no indices\n");
			return false;
		}

#pragma todo(handle submesh material differences)

		LogExportMsg(2, "    Building mesh data (%d submeshes)\n", mesh.mCleanMesh->Topology.NbSubmeshes);
		if (mesh.mCleanMesh->Topology.NbSubmeshes <= 0)
		{
			LogExportMsg(0, "Mesh (ID=%d, name='%s') has no submeshes\n", mesh.mObjectID, mesh.mName);
			return false;
		}
		if (gSettings.mExpVTXColor && !(mesh.mCleanMesh->Geometry.CVerts))
		{
			LogExportMsg(1, "    *** Wanted vertex colors, but none exported\n");
		}
		// structures for keeping track of how many vertices were used by each material
		deTArray<FlexPortMeshData> *pMeshData = new deTArray<FlexPortMeshData>;
		for (k = 0; k < (int)mesh.mCleanMesh->Topology.NbSubmeshes; ++k)
		{
			if (mesh.mCleanMesh->Topology.SubmeshProperties[k].NbFaces == 0)
			{
				LogExportMsg(2, "      No faces, skipping submesh");
				continue;
			}
			int j, len;
			sdword MatID = mesh.mCleanMesh->Topology.SubmeshProperties[k].MatID;
			len = pMeshData->size();
			for (j = 0; j < len; ++j)
			{
				if (MatID == (*pMeshData)[j].Material.MatID)
				{
					break;
				}
			}
			if (j >= len)
			{
				deTArray<FlexPortMeshData>::iterator newInfo = pMeshData->IncrementSizeQuick(deTRUE);
				newInfo->Material.MatID = MatID;
				// numverts & numfaces zeroed
			}
			(*pMeshData)[j].Material.NumVerts += mesh.mCleanMesh->Topology.SubmeshProperties[k].NbVerts;
			(*pMeshData)[j].Material.NumFaces += mesh.mCleanMesh->Topology.SubmeshProperties[k].NbFaces;
			LogExportMsg(2, "      Added submesh %d: %d vertices, %d faces, material %d\n",
				k, 
				mesh.mCleanMesh->Topology.SubmeshProperties[k].NbVerts, 
				mesh.mCleanMesh->Topology.SubmeshProperties[k].NbFaces,
				MatID);
		}
		LogExportMsg(2, "      Materials found: %d\n", pMeshData->size());
		for (k = 0; k < pMeshData->size(); ++k)
		{
			int j;
			LogExportMsg(2, "      Processing vertex data for material %d\n", k);
			deMeshVertexData * pVertData = &((*pMeshData)[k].Vertices);
			pVertData->numtexstages = 1;
			pVertData->numpoints = (*pMeshData)[k].Material.NumVerts;
			LogExportMsg(2, "      Found %d vertices\n", pVertData->numpoints);
			if (pVertData->numpoints > 65535)
			{
				LogExportMsg(0, "Mesh (ID=%d, name='%s') had more than 65535 vertices (%d actually)\n in material #%d, failed conversion\n", 
					mesh.mObjectID, mesh.mName, pVertData->numpoints, k);
				return false;
			}
			// allocate memory for storing all vertices
			pVertData->positions = new deVertex[pVertData->numpoints];
			pVertData->uvcoords  = new deTexCoord[pVertData->numpoints];
			pVertData->normals   = new deVertex[pVertData->numpoints];
			pVertData->colors	 = new deARGB[pVertData->numpoints];
#pragma todo (deallocate all this data once we finish with it)
#pragma todo (something more optimal than exploding all vertices)
			for (j = 0; j < (int)pVertData->numpoints; ++j)
			{
				udword index = mesh.mCleanMesh->Geometry.VertsRefs[j];
				pVertData->positions[j].x = mesh.mCleanMesh->Geometry.Verts[index*3];
				pVertData->positions[j].y = mesh.mCleanMesh->Geometry.Verts[index*3+1];
				pVertData->positions[j].z = mesh.mCleanMesh->Geometry.Verts[index*3+2];
				if (mesh.mCleanMesh->Geometry.Normals)
				{
					index = j;
					pVertData->normals[j].x = mesh.mCleanMesh->Geometry.Normals[index*3];
					pVertData->normals[j].y = mesh.mCleanMesh->Geometry.Normals[index*3+1];
					pVertData->normals[j].z = mesh.mCleanMesh->Geometry.Normals[index*3+2];
				}
				else
				{
					pVertData->normals[j].x = 0;
					pVertData->normals[j].y = 0;
					pVertData->normals[j].z = 0;
				}
				if (mesh.mCleanMesh->Geometry.TVerts)
				{
					index = mesh.mCleanMesh->Geometry.TVertsRefs[j];
					pVertData->uvcoords[j].u = mesh.mCleanMesh->Geometry.TVerts[index*2];
					pVertData->uvcoords[j].v = 1 - mesh.mCleanMesh->Geometry.TVerts[index*2+1];
				}
				else
				{
					pVertData->uvcoords[j].u = 0;
					pVertData->uvcoords[j].v = 0;
				}
				if (mesh.mCleanMesh->Geometry.CVerts)
				{
					index = mesh.mCleanMesh->Geometry.ColorRefs[j];
					index *= 3;
					deColor c;
					c.a = 1.0;
					c.r = mesh.mCleanMesh->Geometry.CVerts[index+0];
					c.g = mesh.mCleanMesh->Geometry.CVerts[index+1];
					c.b = mesh.mCleanMesh->Geometry.CVerts[index+2];
					pVertData->colors[j] = deMake_Color(c);
				}
				else
				{
					pVertData->colors[j] = 0xffffffff;
				}
				//LogExportMsg(3, "pos: %f %f %f ", pVertData->positions[j].x, pVertData->positions[j].y, pVertData->positions[j].z);
				//LogExportMsg(3, "norm:%f %f %f ", pVertData->normals[j].x, pVertData->normals[j].y, pVertData->normals[j].z);
				//LogExportMsg(3, "uv:  %f %f\n", pVertData->uvcoords[j].u, pVertData->uvcoords[j].v);
				if (j % 51 == 0)
					LogExportMsg(3, "argb:%.8x\n", pVertData->colors[j]);
			}
		}
		// now that we know how many faces each material in this mesh has
		// we can allocate data for each of them
		for (k = 0; k < pMeshData->size(); ++k) // foreach material
		{
			sdword MatID = (*pMeshData)[k].Material.MatID;
			LogExportMsg(2, "      Processing material with ID=%d, faces=%d, vertices=%d\n", MatID, (*pMeshData)[k].Material.NumFaces, (*pMeshData)[k].Material.NumVerts);
			deMeshIndexData* pIndexData = &((*pMeshData)[k].Indices);
			int j;
			int indices_used, num_faces;
			indices_used = 0;
			num_faces = 0;
			pIndexData->numindices = 3*((*pMeshData)[k].Material.NumFaces);
			pIndexData->indices16  = new unsigned short[pIndexData->numindices];
			// iterate through all the submeshes, finding ones that match this material ID
			for (j = 0; j < (int)mesh.mCleanMesh->Topology.NbSubmeshes; ++j) //foreach submesh
			{
				if (MatID == mesh.mCleanMesh->Topology.SubmeshProperties[j].MatID)
				{
					int n, numindices, num_verts;
					numindices = 3 * mesh.mCleanMesh->Topology.SubmeshProperties[j].NbFaces;
					num_verts = 3 * num_faces;
#pragma fixme(counting vertices here only works if uniform material is used)
					for (n = 0; n < numindices; n += 3) // foreach face
					{
						// (0,1,2) = (0,2,1)
						pIndexData->indices16[indices_used+n  ] = (unsigned short)(mesh.mCleanMesh->Topology.VRefs[num_verts+n]);
						pIndexData->indices16[indices_used+n+1] = (unsigned short)(mesh.mCleanMesh->Topology.VRefs[num_verts+n+2]);
						pIndexData->indices16[indices_used+n+2] = (unsigned short)(mesh.mCleanMesh->Topology.VRefs[num_verts+n+1]);
					}
					indices_used += numindices;
				}
				num_faces += mesh.mCleanMesh->Topology.SubmeshProperties[j].NbFaces;
			}
		}
		
/*
		blist.numtexstages = 1;
		blist.numindices = 3*(mesh.mCleanMesh->Topology.NbFaces);
		blist.numpoints  = mesh.mCleanMesh->Geometry.NbVerts;
		if (blist.numpoints > 65535)
			return false;
		blist.indices   = new unsigned short[blist.numindices];
		blist.positions = new deVertex[blist.numpoints];
		blist.uvcoords  = new deTexCoord[blist.numpoints];
		blist.normals   = new deVertex[blist.numpoints];
		LogExportMsg(1, "Copying index buffer (%d indices)\n", blist.numindices);
		for (k = 0; k < blist.numindices; k += 3)
		{
			blist.indices[k  ] = (unsigned short)(mesh.mCleanMesh->Topology.VRefs[k]);
			blist.indices[k+1] = (unsigned short)(mesh.mCleanMesh->Topology.VRefs[k+2]);
			blist.indices[k+2] = (unsigned short)(mesh.mCleanMesh->Topology.VRefs[k+1]);
//			LogExportMsg(1, "%d %d %d\n", blist.indices[k], blist.indices[k+1], blist.indices[k+2]);
		}
//		LogExportMsg(1, "\n");
		LogExportMsg(1, "Copying data streams (%d vertices)\n", blist.numpoints);
		for (k = 0; k < blist.numpoints; ++k)
		{
			index = mesh.mCleanMesh->Geometry.VertsRefs[k];
			blist.positions[k].x = mesh.mCleanMesh->Geometry.Verts[index*3];
			blist.positions[k].y = mesh.mCleanMesh->Geometry.Verts[index*3+1];
			blist.positions[k].z = mesh.mCleanMesh->Geometry.Verts[index*3+2];
			if (mesh.mCleanMesh->Geometry.Normals)
			{
				blist.normals[k].x = mesh.mCleanMesh->Geometry.Normals[index*3];
				blist.normals[k].y = mesh.mCleanMesh->Geometry.Normals[index*3+1];
				blist.normals[k].z = mesh.mCleanMesh->Geometry.Normals[index*3+2];
			}
			else
			{
				blist.normals[k].x = 0;
				blist.normals[k].y = 0;
				blist.normals[k].z = 0;
			}
			if (mesh.mCleanMesh->Geometry.TVerts)
			{
				index = mesh.mCleanMesh->Geometry.TVertsRefs[k];
				blist.uvcoords[k].u = mesh.mCleanMesh->Geometry.TVerts[index*2];
				blist.uvcoords[k].v = mesh.mCleanMesh->Geometry.TVerts[index*2+1];
			}
			else
			{
				blist.uvcoords[k].u = 0;
				blist.uvcoords[k].v = 0;
			}
//			LogExportMsg(1, "pos: %f %f %f ", blist.positions[k].x, blist.positions[k].y, blist.positions[k].z);
//			LogExportMsg(1, "norm:%f %f %f ", blist.normals[k].x, blist.normals[k].y, blist.normals[k].z);
//			LogExportMsg(1, "uv:  %f %f\n", blist.uvcoords[k].u, blist.uvcoords[k].v);
		}
		LogExportMsg(1, "Building brush(1)\n");
		pStatic->SetTextureState(m_NullTexture);
		pStatic->AddIndexedBuffer(blist);
		LogExportMsg(1, "Building brush(2)\n");
		pStatic->BuildIndexedBuffers();
		pStatic->SetWorldID(m_WorldID);
		LogExportMsg(1, "Brush built\n");
		delete[] blist.normals;
		delete[] blist.uvcoords;
		delete[] blist.positions;
		delete[] blist.indices;
*/
		LogExportMsg(2, "    Storing mesh into array slot %d\n", mesh.mObjectID);
		FlexPortInstanceMesh imesh;
		imesh.ObjectID = mesh.mObjectID;
		imesh.Name = new char[strlen((char*)mesh.mName)+1];
		strcpy(imesh.Name, (char*)mesh.mName);
		imesh.ThisID = mesh.mObjectID;
		imesh.MasterID = -1;
		imesh.MeshData = pMeshData;
		imesh.Position = mesh.mPrs;
		m_Meshes.PushBack(imesh);
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	Material export method.
 *	This method is called once for each exported material.
 *	\param		material		[in] a structure filled with current material information.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::ExportMaterial(const MaterialDescriptor& material)
{
	LogExportMsg(2, "  Exporting material with ID=%d, name='%s'\n", material.mObjectID, material.mName);

	FlexPortMaterial mymat;
	// record ID information
	mymat.ObjectID = material.mObjectID;
	mymat.Name = new char[strlen((char*)material.mName)+1];
	strcpy(mymat.Name, (char*)material.mName);
	// material transform
	mymat.Transform = material.mTransform;
	// map ID's
	mymat.DiffuseMapID = material.mDiffuseMapID;
	mymat.OpacityMapID = material.mOpacityMapID;
	mymat.BumpMapID = material.mBumpMapID;
	// material colors
	mymat.AmbientColor = Make_deColor(material.mMtlAmbientColor);
	mymat.DiffuseColor = Make_deColor(material.mMtlDiffuseColor);
	mymat.SpecularColor = Make_deColor(material.mMtlSpecularColor);
	if (material.mSelfIllumOn)
		mymat.EmissiveColor = Make_deColor(material.mSelfIllumColor);
	else
	{
		mymat.EmissiveColor.r = material.mSelfIllumValue;
		mymat.EmissiveColor.g = material.mSelfIllumValue;
		mymat.EmissiveColor.b = material.mSelfIllumValue;
		mymat.EmissiveColor.a = 0;
	}

	// store this object
	m_Materials.PushBack(mymat);

	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	Texture export method.
 *	This method is called once for each exported texture.
 *	\param		texture		[in] a structure filled with current texture information.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::ExportTexture(const TextureDescriptor& texture)
{
	TexIterator tex, tex_end;
	tex_end = m_Textures.end();
	for (tex = m_Textures.begin(); tex != tex_end; ++tex)
	{
		if (tex->ObjectID == texture.mObjectID)
		{
			LogExportMsg(1, "    Texture already exported\n");
			return true;
		}
	}
	LogExportMsg(2, "    Exporting texture with ID=%d, name='%s' ... ", texture.mObjectID, texture.mName);

	FlexPortTexture mytex;
	// record ID information
	mytex.ObjectID = texture.mObjectID;
	mytex.Name = new char[strlen((char*)texture.mName)+1];
	strcpy(mytex.Name, (char*)texture.mName);
	// texture transform for UV's
	mytex.Transform = texture.mTransform;
	// UV channel to use for this texturemap
	mytex.MapChannel = texture.mMapChannel;

	// texture addressing modes stored in the transform... bizarre
	mytex.AddressU = texture.mTransform.mTextureTilingU;
	mytex.AddressV = texture.mTransform.mTextureTilingV;
	
	// grab a proxy for this texture
	mytex.BitmapProxy = IdeBitmap_CreateProxy();
	if (mytex.BitmapProxy)
	{
		if (gSettings.mTexnameOnly)
		{
			mytex.BitmapProxy->SetFilename(mytex.Name);
			mytex.BitmapProxy->SettingSavePixels(deFALSE);
		}
		else
		{
			IdeBitmap* bmp = IdeBitmap_CreateBitmapWithInfo(texture.mWidth, texture.mHeight, IdeDriver::BPP_A8R8G8B8, deFALSE);
			mytex.BitmapProxy->SetBitmap(bmp);
			if (bmp)
			{
				deARGB* pixeldata = new deARGB[texture.mWidth * texture.mHeight];
				for (int y = 0; y < texture.mHeight; ++y)
				{
					for (int x = 0; x < texture.mWidth; ++x)
					{
						ubyte* pixel = &texture.mPixels[4 * (y*texture.mWidth + x)];
						pixeldata[y*texture.mWidth + x] = ARGB(pixel[3], pixel[0], pixel[1], pixel[2]);
					}
				}
				bmp->SetData(pixeldata, 4 * texture.mWidth * texture.mHeight, 0);
				bmp->Release();
				delete[] pixeldata;
			}
			mytex.BitmapProxy->SettingSavePixels(deTRUE);
		}
		mytex.BitmapProxy->SetWorldID(m_WorldID);
	}

	// store this object
	m_Textures.PushBack(mytex);
	
	LogExportMsg(2, "done");

	return true;
}

//////////////////////////////////////////////////////////////////////////////
/**
 *	End of export notification.
 *	This method is called once all nodes have been exported. This is a convenient place to free all used ram, etc.
 *	\param		stats		[in] a structure filled with some export statistics.
 *	\return		true if success.
 */
//////////////////////////////////////////////////////////////////////////////
bool Destiny3DFormat::EndExport(const StatsDescriptor& /*stats*/)
{
//#pragma todo(loop through all the meshes and fix them) // done
/*
{
deTransform mat;
deQuaternion quat;
deVec3d vec;
// rotation
quat.x = mesh.mPrs.mRotation.x;
quat.y = mesh.mPrs.mRotation.y;
quat.z = mesh.mPrs.mRotation.z;
quat.w = mesh.mPrs.mRotation.w;
quat.ToMatrix(mat);
// scale
vec.x = mesh.mPrs.mScale.x;
vec.y = mesh.mPrs.mScale.y;
vec.z = mesh.mPrs.mScale.z;
mat.ApplyScaling(vec);
// position
mat.Translation.x = mesh.mPrs.mPosition.x;
mat.Translation.y = mesh.mPrs.mPosition.y;
mat.Translation.z = mesh.mPrs.mPosition.z;
// apply it
pSolidB->SetWorldTransform(mat);
}
*/
//#pragma todo(material application to meshes) // done
//#pragma todo(texture application to materials) //done
#pragma todo(material and texture matrix application to meshes)

	LogExportMsg(1, "\n\nFinishing export. materials=%d, textures=%d, meshes=%d\n", 
		m_Materials.size(),
		m_Textures.size(),
		m_Meshes.size());

	LogExportMsg(2, "Materials: %d\n", m_Materials.size());
	MatIterator mat, mat_end;
	TexIterator tex, tex_end;
	tex_end = m_Textures.end();
	mat_end = m_Materials.end();
	for (mat = m_Materials.begin(); mat != mat_end; ++mat)
	{
		//FlexPortMaterial * mat;
		//FlexPortTexture * tex;
		LogExportMsg(2, "  Processing material with ID=%d, name='%s'\n", mat->ObjectID, mat->Name);

		IdeRenderMaterial * pRM = IdeRenderState_CreateRenderMaterial();
		IdeRenderTexture * pRT = IdeRenderState_CreateRenderTexture();

		// create a couple of engine-specific structures now we have all the data collected

		if (!pRM || !pRT)
		{
			LogExportMsg(0, "failure creating rendermaterial or rendertexture\n");
			return false;
		}

		// set up the material lighting info
		if (!pRM->Lock())
		{
			return false;
		}
		pRM->SetAmbientColor(&mat->AmbientColor);
		pRM->SetDiffuseColor(&mat->DiffuseColor);
		pRM->SetSpecularColor(&mat->SpecularColor);
		pRM->SetEmissiveColor(&mat->EmissiveColor);
		pRM->SetDiffuseSource(IdeRenderMaterial::MATSOURCE_MATERIAL);
		pRM->Unlock();

		// texture information
		if (!pRT->Lock())
		{
			return false;
		}
		IdeBitmapProxy * Diffuse = NULL;
		IdeBitmapProxy * Opacity = NULL;
		IdeBitmapProxy * BumpMap = NULL;
		for (tex = m_Textures.begin(); tex != tex_end; ++tex)
		{
			if (!Diffuse && tex->ObjectID == mat->DiffuseMapID)
			{
				LogExportMsg(2, "    Found diffuse texture: %s\n", tex->Name);
				Diffuse = tex->BitmapProxy;
			}
			if (!Opacity && tex->ObjectID == mat->OpacityMapID)
			{
				LogExportMsg(2, "    Found opacity texture: %s\n", tex->Name);
				Opacity = tex->BitmapProxy;
			}
			if (!BumpMap && tex->ObjectID == mat->BumpMapID)
			{
				LogExportMsg(2, "    Found bumpmap texture: %s\n", tex->Name);
				BumpMap = tex->BitmapProxy;
			}
			if (Diffuse && Opacity && BumpMap)
				break;
		}
		if (tex == tex_end)
		{
			if (!Diffuse && mat->DiffuseMapID >= 0)
			{
				LogExportMsg(1, "Could not find diffuse map with ID=%d\n", mat->DiffuseMapID);
			}
			if (!Opacity && mat->OpacityMapID >= 0)
			{
				LogExportMsg(1, "Could not find opacity map with ID=%d\n", mat->OpacityMapID);
			}
			if (!BumpMap && mat->BumpMapID >= 0)
			{
				LogExportMsg(1, "Could not find bumpmap with ID=%d\n", mat->BumpMapID);
			}
		}
		// set up the fixed-function texture stages
		pRT->SetBitmap(0, Diffuse);
		pRT->SetBitmap(1, Opacity);
		pRT->SetTexCoordSource(1, 0); // stage 1 uses stage 0 mapping
		// vertex_diffuse * diffuse_map
		pRT->SetColorArg(0, 1, IdeRenderTexture::TEXTUREARG_DIFFUSE);
		pRT->SetColorArg(0, 2, IdeRenderTexture::TEXTUREARG_TEXTURE);
		pRT->SetColorOperation(0, IdeRenderTexture::TEXTUREOP_MODULATE);
		pRT->SetAlphaOperation(0, IdeRenderTexture::TEXTUREOP_DISABLE);
		// opacity alpha
		if (Opacity)
		{
			if (mat->DiffuseMapID == mat->OpacityMapID)
			{
				LogExportMsg(2, "    Diffuse and Opacity are the same, using diffuse alpha channel\n");
				pRT->SetAlphaArg(0, 1, IdeRenderTexture::TEXTUREARG_TEXTURE);
				pRT->SetAlphaOperation(0, IdeRenderTexture::TEXTUREOP_SELECTARG1);
			}
			else
			{
				pRT->SetColorArg(1, 1, IdeRenderTexture::TEXTUREARG_PREVIOUS);
				pRT->SetColorOperation(1, IdeRenderTexture::TEXTUREOP_SELECTARG1);
				pRT->SetAlphaArg(1, 1, IdeRenderTexture::TEXTUREARG_TEXTURE);
				pRT->SetAlphaOperation(1, IdeRenderTexture::TEXTUREOP_SELECTARG1);
			}
		}
		pRT->Unlock();

		pRM->SetWorldID(m_WorldID);
		pRT->SetWorldID(m_WorldID);

		IdeSurface * surface = IdeSurface_CreateSurface();
		surface->SetRenderMaterial(pRM);
		surface->SetRenderTexture(pRT);
		surface->SetWorldID(m_WorldID);

		int a,b,c;
		a = pRM->Release();
		b = pRT->Release();
		surface->Claim();
		c = surface->Release();

		//LogExportMsg(3, "    refs: %d, %d, %d\n", a, b, c);

		mat->FinalSurface = surface;

		//repeat on next material
	}

	LogExportMsg(2, "Meshes: %d\n", m_Meshes.size());
	MeshIterator mesh, mesh_end;
	mesh_end = m_Meshes.end();
	LogExportMsg(3, "mesh_end = 0x%.8x\n", &*mesh_end);
	for (mesh = m_Meshes.begin(); mesh != mesh_end; ++mesh)
	{
		LogExportMsg(3, "mesh     = 0x%.8x\n", &*mesh);
		LogExportMsg(2, "  Processing mesh with ID=%d, name='%s'\n", mesh->ObjectID, mesh->Name);
		if (!mesh->MeshData)
		{
			// 0 seems to be reserved specially, so we'll just ignore "special" ones
			LogExportMsg(2, "    Skipping mesh\n");
			continue;
		}

		long k;
		u32 NumFaces = 0;
		IdeMesh* pStaticMesh = IdeMesh_CreateMesh();
		for (k = 0; k < mesh->MeshData->size(); ++k)
		{
			LogExportMsg(2, "    Handling material %d ... ", k);
			long offset = 0;
			if (!pStaticMesh->AddVertices((*mesh->MeshData)[k].Vertices, offset))
			{
				LogExportMsg(0, "pStaticMesh->AddVertices() failed for mesh ID=%d", mesh->ObjectID);
				return false;
			}
			deMeshIndexData & indices = (*mesh->MeshData)[k].Indices;
			long MatID = (*mesh->MeshData)[k].Material.MatID;
			for (mat = m_Materials.begin(); mat != mat_end; ++mat)
			{
				if (mat->ObjectID == MatID)
				{
					indices.surface = mat->FinalSurface;
					break;
				}
			}
			if (mat == mat_end)
			{
				LogExportMsg(1, "Could not find matching material ID=%d, generating blank mat\n", MatID);
				FlexPortMaterial mymat;
				memset(&mymat, 0, sizeof(FlexPortMaterial));
				mat = m_Materials.push_back(mymat);
				mat->ObjectID = MatID;
			}
			NumFaces += indices.numindices/3;
			LogExportMsg(2, "%d verts, %d indices\n", (*mesh->MeshData)[k].Vertices.numpoints, indices.numindices);
			//indices.indexoffset = offset;
			indices.indexoffset = 0; // already have the offset taken care of
			if (!pStaticMesh->AddIndices(indices))
			{
				LogExportMsg(0, "pStaticMesh->AddIndices() failed for mesh ID=%d", mesh->ObjectID);
				return false;
			}
		}
		deTransform meshpos;
		{
			deQuaternion quat;
			deVec3d vec;
			// rotation
			quat.x = mesh->Position.mRotation.x;
			quat.y = mesh->Position.mRotation.y;
			quat.z = mesh->Position.mRotation.z;
			quat.w = mesh->Position.mRotation.w;
			quat.ToMatrix(meshpos);
			// scale
			vec.x = mesh->Position.mScale.x;
			vec.y = mesh->Position.mScale.y;
			vec.z = mesh->Position.mScale.z;
			meshpos.ApplyScaling(vec);
			// position
			meshpos.Translation.x = mesh->Position.mPosition.x;
			meshpos.Translation.y = mesh->Position.mPosition.y;
			meshpos.Translation.z = mesh->Position.mPosition.z;
			// apply it
		}

		LogExportMsg(2, "    Building mesh - %d faces\n", NumFaces);
		if (!pStaticMesh->BuildFromVertices())
		{
			LogExportMsg(0, "Mesh build failed for mesh (ID=%d, name='%s')\n", mesh->ObjectID, mesh->Name);
			return false;
		}

		LogExportMsg(2, "    Adding geometry\n");
		if (!m_ABT->AddGeometry(pStaticMesh, meshpos))
		{
			LogExportMsg(0, "Adding geometry failed for mesh (ID=%d, name='%s')\n", mesh->ObjectID, mesh->Name);
			return false;
		}

		pStaticMesh->Release();
	}

	{
		LogExportMsg(1, "Setting up File\n");
		// save the stuff to a dworld file
		IdeFileSystem* pFS = NULL;
		IdeFile* pFile = NULL;
		IdeWorld* pWorld = NULL;
		IdeWorldManager* manager = IdeWorld_GetManager();
		if (!manager)
			return false;
		pWorld = manager->GetWorld(m_WorldID);
		if (!pWorld)
			goto endexport_fail;
		pFS = IdeFileSystem_CreateFSReal();
		if (!pFS)
			goto endexport_fail;
		pFile = pFS->Open(mFilename, (deFile_Open_CreateAlways | deFile_Open_Write));
		if (!pFile)
			goto endexport_fail;
		LogExportMsg(1, "Saving DWorld\n");
		if (!pWorld->Serialize(pFile))
			goto endexport_fail;
		pFile->Close();
		pFile->Release();
		pFS->Release();
		pWorld->Release();

		LogExportMsg(1, "Export Complete\n\n");

//		Sleep(5000);
		
		deConsole_DestroyConsole();
		
		return true;

endexport_fail:
		LogExportMsg(0, "failure exporting file\n");
		deConsole_DestroyConsole();
		if (pFile)
		{
			pFile->Close();
			pFile->Release();
		}
		if (pFS)
			pFS->Release();
		if (pWorld)
			pWorld->Release();
		return false;
	}
}

void Destiny3DFormat::About()
{
	IceCore::MessageBox(null, "Destiny3D's DWorld file format", "About...", MB_OK);
}
