
//**************************************************************************/
// Copyright (c) 2008 Autodesk, Inc.
// All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk license
// agreement provided at the time of installation or download, or which
// otherwise accompanies this software in either electronic or hard copy form.
//
//**************************************************************************/
// DESCRIPTION:
// CREATED: October 2008
//**************************************************************************/

#include "rply.h"

#include <stdio.h>

// This is the SDK header, all plugins have to include it.

#if defined(JAMBUILD)
#include <Mudbox/mudbox.h>
#else
#include "../../include/Mudbox/mudbox.h"
#endif

using namespace mudbox;

// This macro makes a record in the memory to describe the plugin.
MB_PLUGIN( "PLY Importer", "PLY file import/export plugin", "Autodesk", "http://www.mudbox3d.com", 0 );

// The following class will implement the Importer interface.
class PLYImporter : public Importer
{
public:

	// The following line must be used in every class which wants to use RTTI.
	DECLARE_CLASS;

	// Just a plain empty constructor to initialize some data.
	PLYImporter( void );
	// And a destructor to destroy some.
	~PLYImporter( void );
	// The following function is responsible to return the supported file extension(s) by the plugin.
	virtual QString Extension( void ) { return "ply"; };
	// This will return a description of the file type we are importing.
	virtual QString Description( void ) { return "Polygon File"; };
	// This is the main function, this will be called when a file should be imported.
	void Import( const QString &sFileName );

	// Internal stuff follows. The next function will be called when some new vertex data (coordinate) is read from the file.
	void OnVertexData( float fData );
	// This is called when new face data (vertex index) is read.
	void OnFaceData( int iIndex, int iFaceSize, int iData );

	// Pointer to the result mesh.
	Mesh *m_pMesh;
	// Index of the current vertex which is being read.
	unsigned int m_iVertexIndex;
	// Index of the current corner of the triangle which is being read (possible values are 0, 1, 2).
	unsigned int m_iCornerIndex;
	// Index of the current triangle which is being read.
	unsigned int m_iTriangleIndex;
	// Coordinates of the current vertex.
	Vector m_vPosition;
};

// The PLY import code uses C functions from the RPly library. Since we cannot pass instances
// of Importers into these functions we define a global pointer to the current ply importer.
PLYImporter* g_pImporter = 0;

PLYImporter::PLYImporter( void )
{
	Kernel()->Log( "PLY Import plugin initialized\n" );
	m_pMesh = 0;
	g_pImporter = this;
	m_iVertexIndex = 0;
	m_iCornerIndex = 0;
	m_iTriangleIndex = 0;
};

PLYImporter::~PLYImporter( void )
{
	if( g_pImporter == this )
		g_pImporter = 0;
};

// The code reads one coordinate of a vertex at a time, so this function is called three times for each vertex.
void PLYImporter::OnVertexData( float fData )
{
	// Store the incoming data to the current vector, and step to the next coordinate of the vector.
	m_vPosition[m_iCornerIndex++] = fData;

	// If the vector is ready (i.e. it has all the three coorinates filled) then store it in the mesh.
	if ( m_iCornerIndex == 3 )
	{	
		// Restart filling up the vector
		m_iCornerIndex = 0;

		// Just to be sure we check the number of vertices.
		if ( m_iVertexIndex < m_pMesh->VertexCount() )
		{
			m_pMesh->SetVertexPosition( m_iVertexIndex++, m_vPosition );
			Kernel()->ProgressSet( m_iVertexIndex );
		}
		else
			MB_ERROR( "Too much vertex in file" );
	};
};

// Same as above, the code reads one index at a time, so this function will be called three times for a triangle.
void PLYImporter::OnFaceData( int iIndex, int iFaceSize, int iData )
{
	if ( iIndex == -1 )
	{
		if ( iFaceSize == 3 )
			return;
		else
			MB_ERROR( "Face with more than 3 vertex found in file (not supported)" );
	};

	if ( m_iTriangleIndex < m_pMesh->FaceCount() && iIndex < 3 && iIndex >= 0 )
		m_pMesh->SetTriangleIndex( m_iTriangleIndex, iIndex, iData );
	else
		MB_ERROR( "Vertex index out of range" );

	if ( iIndex == 2 )
	{
		m_iTriangleIndex++;
		Kernel()->ProgressSet( m_iVertexIndex+m_iTriangleIndex );
	};
};

// This is a callback function for the PLY reader code to use for vertices.
static int vertex_cb(p_ply_argument argument) 
{
	g_pImporter->OnVertexData( (float)ply_get_argument_value(argument) );
	return 1;
};

// And this is for faces.
static int face_cb(p_ply_argument argument) 
{
	long iLength, iValueIndex;
	ply_get_argument_property(argument, 0, &iLength, &iValueIndex);
	g_pImporter->OnFaceData( iValueIndex, iLength, (unsigned int)ply_get_argument_value(argument) );
	return 1;
};

// This is the main function which will do the importing.
void PLYImporter::Import( const QString &sFileName )
{
	unsigned int iVertexCount, iTriangleCount;

	// Open the file.
	p_ply ply = ply_open(sFileName.toAscii().constData(), 0);

	// Check for errors. If something is wrong we use the MB_ERROR macro, which will throw an exception, so the execution of the function will be stopped here.
	if ( !ply )
		MB_ERROR( "Invalid file: %s", (const char *) sFileName.toAscii() );
	if ( !ply_read_header(ply) )
		MB_ERROR( "Invalid file: %s", (const char*) sFileName.toAscii());

	// Initialize vertex reading for three coordinates with the same callback.
	iVertexCount = ply_set_read_cb(ply, "vertex", "x", vertex_cb, 0, 0);
	ply_set_read_cb(ply, "vertex", "y", vertex_cb, 0, 0);
	ply_set_read_cb(ply, "vertex", "z", vertex_cb, 0, 1);

	// Initialize face reading.
	iTriangleCount = ply_set_read_cb(ply, "face", "vertex_indices", face_cb, 0, 0);

	// Log some information, just for debugging purposes.
	Kernel()->Log( "\t%d vertices, %d triangles.", iVertexCount, iTriangleCount );

	// Create a mesh.
	m_pMesh = Kernel()->CreateMesh( Topology::typeTriangular );
	m_pMesh->SetName("PlyMesh");
	// Initialize the mesh to match the specs from the file.
	m_pMesh->SetFaceCount( iTriangleCount );
	m_pMesh->SetVertexCount( iVertexCount );

	// Start a progress bar in Mudbox.
	Kernel()->ProgressStart( "importing...", iVertexCount+iTriangleCount );

	// Try to read the file. If something went wrong, just throw an exception.
	if (!ply_read(ply)) 
		MB_ERROR( "Invalid file: %s", (const char*) sFileName.toAscii());

	// Close the file.
	ply_close(ply);

	// Remove the progress bar.
	Kernel()->ProgressEnd();

	// Importing is done. Add the converted mesh to the Importer's scene.
	// Mudbox will then merge that scene into it's scene, after this call is done.
	// Note that the importer's scene will be automatically destroyed by Mudbox 
	// when it  is no longer needed. Plugin writers do not need to manage memory 
	// of objects in the importer's scene.
	AddMesh( m_pMesh );
};

// This macro is needed for all classes which want to use RTTI
IMPLEMENT_CLASS( PLYImporter, Importer, "PLY Importer" );

// Implementation of the Exporter interface, to handle writting to PLY files.
class PLYExporter : public Exporter
{
public:
	// Needed for RTTI.
	DECLARE_CLASS;

	// Pass file extension to Mudbox.
	QString Extension( void ) { return "ply"; };
	// And file description. These will be visible in the browse dialog.
	QString Description( void ) { return "Polygon file"; };

	// Mudbox will call these two functions, to let the exporter know which meshes should be exported. First it tells the number of meshes,
	void SetMeshCount( unsigned int iMeshCount ) { m_aMeshes.SetItemCount( iMeshCount ); };
	// And then a pointer to each mesh.
	void SetMesh( unsigned int iIndex, const Mesh *pMesh ) { m_aMeshes[iIndex] = pMesh; };

	// Internal function, used to write a single line into a text file (such as the PLY).
	void Write( const QString &sText ) 
	{ 
		QString s = sText + "\n";
		fwrite( (const char *)s.toAscii(), s.size(), 1, m_pFile ); 
	};

	// Main function, this is doing the export itself. The function gets the filename as a parameter.
	void Export( const QString &sFileName )
	{
		// If there is no mesh to export, we do nothing.
		if ( m_aMeshes.ItemCount() == 0 )
			return;
		// If there are more than one mesh to export, we report an error since this implementation only supports a single mesh.
		if ( m_aMeshes.ItemCount() > 1 )
			MB_ERROR( "Only a single mesh can be exported to PLY file format." );

		// Create and open the file as usual.
		m_pFile = fopen( sFileName.toAscii().constData(), "wb" );

		// Write the header of the file. This is hardcoded, this plugin only writes the most simple format of PLY files.
		Write( "ply" );
		Write( "format ascii 1.0" );
		Write( QString("element vertex %1").arg(m_aMeshes[0]->VertexCount() ) );
		Write( "property float x" );
		Write( "property float y" );
		Write( "property float z" );
		Write( QString("element face %1").arg( m_aMeshes[0]->FaceCount() ) );
		Write( "property list uchar int vertex_indices" );
		Write( "end_header" );

		// Write all the vertex positions into the file.
		for ( unsigned int v = 0; v < m_aMeshes[0]->VertexCount(); v++ )
		{
			Vector p = m_aMeshes[0]->VertexPosition( v );
			Write( QString("%1 %2 %3").arg(p.x).arg(p.y).arg(p.z ) );
		};

		// And then write the vertex indices from the faces.
		if ( m_aMeshes[0]->Type() == Mesh::typeTriangular )
		{
			// If the mesh has triangles, write three indices in a row.
			for ( unsigned int f = 0; f < m_aMeshes[0]->FaceCount(); f++ )
				Write( QString("3 %1 %2 %3").arg(m_aMeshes[0]->TriangleIndex( f, 0 )).arg( m_aMeshes[0]->TriangleIndex( f, 1 )).arg( m_aMeshes[0]->TriangleIndex( f, 2 ) ) );
		}
		else
		{
			// And write four indices if there are quads in the mesh.
			for ( unsigned int f = 0; f < m_aMeshes[0]->FaceCount(); f++ )
				Write( QString("4 %1 %2 %3 %4").arg(m_aMeshes[0]->QuadIndex( f, 0 )).arg( m_aMeshes[0]->QuadIndex( f, 1 )).arg( m_aMeshes[0]->QuadIndex( f, 2 )).arg( m_aMeshes[0]->QuadIndex( f, 3 ) ) );
		};

		// Thats all, close the file and we are ready.
		fclose( m_pFile );
	};

	// This array will be used to store the list of meshes which has to be exported.
	Store<const Mesh *> m_aMeshes;
	// A simple handler to the file we are writting to.
	FILE *m_pFile;
};

// RTTI information about the PLYExporter class.
IMPLEMENT_CLASS( PLYExporter, Exporter, "PLY Exporter" );
