//-
// ==========================================================================
// Copyright 1995,2006,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.
// ==========================================================================
//+

///////////////////////////////////////////////////////////////////////////////
//
// apiMeshCreator.cpp
//
////////////////////////////////////////////////////////////////////////////////

#include <math.h>
#include <maya/MIOStream.h>

#include <apiMeshCreator.h>           
#include <apiMeshData.h>
#include <api_macros.h>

#include <maya/MFnMesh.h>
#include <maya/MFnPluginData.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnEnumAttribute.h>
#include <maya/MFnNumericAttribute.h>

#ifndef M_PI
#define M_PI		3.14159265358979323846
#endif

#ifndef M_PI_2
#define M_PI_2		1.57079632679489661923
#endif

////////////////////////////////////////////////////////////////////////////////
//
// Shape implementation
//
////////////////////////////////////////////////////////////////////////////////

MObject apiMeshCreator::size;
MObject apiMeshCreator::shapeType;
MObject apiMeshCreator::inputMesh;
MObject apiMeshCreator::outputSurface;

MTypeId apiMeshCreator::id( 0x80089 );

apiMeshCreator::apiMeshCreator() {}
apiMeshCreator::~apiMeshCreator() {}

///////////////////////////////////////////////////////////////////////////////
//
// Overrides
//
///////////////////////////////////////////////////////////////////////////////

/* override */
MStatus apiMeshCreator::compute( const MPlug& plug, MDataBlock& datablock )
//
// Description
//
//    When input attributes are dirty this method will be called to
//    recompute the output attributes.
//
{ 
    MStatus stat;
    if ( plug == outputSurface ) {
		// Create some user defined geometry data and access the
		// geometry so we can set it
		//
		MFnPluginData fnDataCreator;
		MTypeId tmpid( apiMeshData::id );
		
		fnDataCreator.create( tmpid, &stat );
		MCHECKERROR( stat, "compute : error creating apiMeshData")

		apiMeshData * newData = (apiMeshData*)fnDataCreator.data( &stat );
		MCHECKERROR( stat, "compute : error gettin at proxy apiMeshData object")

		apiMeshGeom * geomPtr = newData->fGeometry;

		// If there is an input mesh then copy it's values
		// and construct some apiMeshGeom for it.
		//
		bool hasHistory = computeInputMesh( plug, datablock,
											geomPtr->vertices,
											geomPtr->face_counts,
											geomPtr->face_connects,
											geomPtr->normals, 
											geomPtr->uvcoords
			);
											
		// There is no input mesh so check the shapeType attribute
		// and create either a cube or a sphere.
		//
		if ( !hasHistory ) {
			MDataHandle sizeHandle = datablock.inputValue( size );
			double shape_size = sizeHandle.asDouble();
			MDataHandle typeHandle = datablock.inputValue( shapeType );
			short shape_type = typeHandle.asShort();

			switch( shape_type )
			{
				case 0 : // build a cube
					buildCube( shape_size,
							   geomPtr->vertices,
							   geomPtr->face_counts,
							   geomPtr->face_connects,
							   geomPtr->normals, 
							   geomPtr->uvcoords
						);
					break;
			
				case 1 : // buld a sphere
					buildSphere( shape_size,
								 32,
								 geomPtr->vertices,
								 geomPtr->face_counts,
								 geomPtr->face_connects,
								 geomPtr->normals, 
								 geomPtr->uvcoords
						);
					break;
			} // end switch
		}

		geomPtr->faceCount = geomPtr->face_counts.length();

		// Assign the new data to the outputSurface handle
		//
		MDataHandle outHandle = datablock.outputValue( outputSurface );
		outHandle.set( newData );
		datablock.setClean( plug );
		return MS::kSuccess;
    }
    else {
        return MS::kUnknownParameter;
    }
}

///////////////////////////////////////////////////////////////////////////////
//
// Helper routines
//
///////////////////////////////////////////////////////////////////////////////

void apiMeshCreator::buildCube( 
	double cube_size, 
	MPointArray& pa,
	MIntArray& faceCounts, 
	MIntArray& faceConnects,
	MVectorArray& normals, 
	apiMeshGeomUV& uvs
)
//
// Description
//
//    Constructs a cube
//
{
	const int num_faces			= 6;
	const int num_face_connects	= 24;
	const double normal_value   = 0.5775;
	const int uv_count			= 14; 
	
	pa.clear(); faceCounts.clear(); faceConnects.clear();
	uvs.reset(); 

	pa.append( MPoint( -cube_size, -cube_size, -cube_size ) );
	pa.append( MPoint(  cube_size, -cube_size, -cube_size ) );
	pa.append( MPoint(  cube_size, -cube_size,  cube_size ) );
	pa.append( MPoint( -cube_size, -cube_size,  cube_size ) );
	pa.append( MPoint( -cube_size,  cube_size, -cube_size ) );
	pa.append( MPoint( -cube_size,  cube_size,  cube_size ) );
	pa.append( MPoint(  cube_size,  cube_size,  cube_size ) );
	pa.append( MPoint(  cube_size,  cube_size, -cube_size ) );

	normals.append( MVector( -normal_value, -normal_value, -normal_value ) );
	normals.append( MVector(  normal_value, -normal_value, -normal_value ) );
	normals.append( MVector(  normal_value, -normal_value,  normal_value ) );
	normals.append( MVector( -normal_value, -normal_value,  normal_value ) );
	normals.append( MVector( -normal_value,  normal_value, -normal_value ) );
	normals.append( MVector( -normal_value,  normal_value,  normal_value ) );
	normals.append( MVector(  normal_value,  normal_value,  normal_value ) );
	normals.append( MVector(  normal_value,  normal_value, -normal_value ) );

	// Define the UVs for the cube. 
	//
	float uv_pts[uv_count * 2] = { 0.375, 0.0, 
								   0.625, 0.0,
								   0.625, 0.25, 
								   0.375, 0.25,
								   0.625, 0.5,
								   0.375, 0.5,
								   0.625, 0.75,
								   0.375, 0.75,
								   0.625, 1.0,
								   0.375, 1.0,
								   0.875, 0.0,
								   0.875, 0.25,
								   0.125, 0.0,
								   0.125, 0.25 
	}; 
	
	// UV Face Vertex Id. 
	//
	int uv_fvid [ num_face_connects ] = {  0, 1, 2, 3, 
										   3, 2, 4, 5, 
										   5, 4, 6, 7, 
										   7, 6, 8, 9, 
										   1, 10, 11, 2, 
										   12, 0, 3, 13 };

	int i;
	for ( i = 0; i < uv_count; i ++ ) {
		uvs.append_uv( uv_pts[i*2], uv_pts[i*2 + 1] ); 
	}
	
	for ( i = 0; i < num_face_connects; i ++ ) { 
		uvs.faceVertexIndex.append( uv_fvid[i] ); 
	}

	// Set up an array containing the number of vertices
	// for each of the 6 cube faces (4 verticies per face)
	//
	int face_counts[num_faces] = { 4, 4, 4, 4, 4, 4 };

	for ( i=0; i<num_faces; i++ )
	{
		faceCounts.append( face_counts[i] );
	}

	// Set up and array to assign vertices from pa to each face 
	//
	int face_connects[ num_face_connects ] = {	0, 1, 2, 3,
												4, 5, 6, 7,
												3, 2, 6, 5,
												0, 3, 5, 4,
												0, 4, 7, 1,
												1, 7, 6, 2	};
	for ( i=0; i<num_face_connects; i++ )
	{
		faceConnects.append( face_connects[i] );
	}
}

void apiMeshCreator::buildSphere( 
	double				rad, 
	int					div, 
	MPointArray &		vertices,
	MIntArray &			counts, 
	MIntArray &			connects,
	MVectorArray &		normals, 
	apiMeshGeomUV &		uvs
)
//
// Description
//
//    Create circles of vertices starting with 
//    the top pole ending with the botton pole
//
{
	double u = -M_PI_2;
	double v = -M_PI;
	double u_delta = M_PI / ((double)div); 
	double v_delta = 2 * M_PI / ((double)div); 

	MPoint topPole( 0.0, rad, 0.0 );
	MPoint botPole( 0.0, -rad, 0.0 );

	// Build the vertex and normal table
	//
	vertices.append( botPole );
	normals.append( botPole-MPoint::origin );
	int i;
	for ( i=0; i<(div-1); i++ )
	{
		u += u_delta;
		v = -M_PI;

		for ( int j=0; j<div; j++ )
		{
			double x = rad * cos(u) * cos(v);
			double y = rad * sin(u);
			double z = rad * cos(u) * sin(v) ;
			MPoint pnt( x, y, z );
			vertices.append( pnt );
			normals.append( pnt-MPoint::origin );
			v += v_delta;
		}
	}
	vertices.append( topPole );
	normals.append( topPole-MPoint::origin );

	// Create the connectivity lists
	//
	int vid = 1;
	int numV = 0;
	for ( i=0; i<div; i++ )
	{
		for ( int j=0; j<div; j++ )
		{
			if ( i==0 ) {
				counts.append( 3 );
				connects.append( 0 );
				connects.append( j+vid );
				connects.append( (j==(div-1)) ? vid : j+vid+1 );
			}
			else if ( i==(div-1) ) {
				counts.append( 3 );
				connects.append( j+vid+1-div );
				connects.append( vid+1 );
				connects.append( j==(div-1) ? vid+1-div : j+vid+2-div );
			}
			else {
				counts.append( 4 );
				connects.append( j + vid+1-div );
				connects.append( j + vid+1 );
				connects.append( j == (div-1) ? vid+1 : j+vid+2 );
				connects.append( j == (div-1) ? vid+1-div : j+vid+2-div );
			}
			numV++;
		}
		vid = numV;
	}

	// TODO: Define UVs for sphere ...
	//
}

MStatus apiMeshCreator::computeInputMesh( 
	const MPlug&		plug,
	MDataBlock&			datablock,
	MPointArray&		vertices,
	MIntArray&			counts,
	MIntArray&			connects,
	MVectorArray&		normals, 
	apiMeshGeomUV&		uvs
)
//
// Description
//
//     This function takes an input surface of type kMeshData and converts
//     the geometry into this nodes attributes.
//     Returns kFailure if nothing is connected.
//
{
	MStatus stat;

    // Get the input subdiv
    //        
    MDataHandle inputData = datablock.inputValue( inputMesh, &stat );
    MCHECKERROR( stat, "compute get inputMesh")
    MObject surf = inputData.asMesh();

	// Check if anything is connected
	//
	MObject thisObj = thisMObject();
	MPlug surfPlug( thisObj, inputMesh );
	if ( !surfPlug.isConnected() ) {
    	stat = datablock.setClean( plug );
	    MCHECKERROR( stat, "compute setClean" )
		return MS::kFailure;
	}

	// Extract the mesh data
	//
   	MFnMesh surfFn (surf, &stat);
	MCHECKERROR( stat, "compute - MFnMesh error" );
	stat = surfFn.getPoints( vertices, MSpace::kObject );
	MCHECKERROR( stat, "compute getPoints"); 

	// Check to see if we have UVs to copy. 
	//
	bool hasUVs = surfFn.numUVs() > 0; 	
	surfFn.getUVs( uvs.ucoord, uvs.vcoord ); 

	for ( int i=0; i<surfFn.numPolygons(); i++ )
	{
		MIntArray polyVerts;
		surfFn.getPolygonVertices( i, polyVerts );
		int pvc = polyVerts.length();
		counts.append( pvc );
		int uvId; 
		for ( int v=0; v<pvc; v++ ) {
			if ( hasUVs ) {
				surfFn.getPolygonUVid( i, v, uvId );
				uvs.faceVertexIndex.append( uvId );
			}
			connects.append( polyVerts[v] );
		}
	}

	for ( int n=0; n<(int)vertices.length(); n++ )
	{
		MVector normal;
		surfFn.getVertexNormal( n, normal );
		normals.append( normal );
	}

	return MS::kSuccess;
}

void* apiMeshCreator::creator()
//
// Description
//    Called internally to create a new instance of the users MPx node.
//
{
	return new apiMeshCreator();
}

MStatus apiMeshCreator::initialize()
//
// Description
//
//    Attribute (static) initialization. See api_macros.h.
//
{ 
	MStatus				stat;
    MFnTypedAttribute	typedAttr;
	MFnEnumAttribute	enumAttr;

	// ----------------------- INPUTS -------------------------

	MAKE_NUMERIC_ATTR(	size, "size", "sz",
						MFnNumericData::kDouble, 1,
						false, false, true );

	shapeType = enumAttr.create( "shapeType", "st", 0, &stat );
	MCHECKERROR( stat, "create shapeType attribute" );
	stat = enumAttr.addField( "cube", 0 );
	MCHECKERROR( stat, "add enum type cube" );
	stat = enumAttr.addField( "sphere", 1 );
	MCHECKERROR( stat, "add enum type sphere" );
    CHECK_MSTATUS( enumAttr.setHidden( false ) );
    CHECK_MSTATUS( enumAttr.setKeyable( true ) );
	ADD_ATTRIBUTE( shapeType );
	
	MAKE_TYPED_ATTR( inputMesh, "inputMesh", "im", MFnData::kMesh, NULL );

	// ----------------------- OUTPUTS -------------------------
    outputSurface = typedAttr.create( "outputSurface", "os",
									  apiMeshData::id,
									  MObject::kNullObj, &stat );
    MCHECKERROR( stat, "create outputSurface attribute" )
	typedAttr.setWritable( false );
    ADD_ATTRIBUTE( outputSurface );

	// ---------- Specify what inputs affect the outputs ----------
	//
    ATTRIBUTE_AFFECTS( inputMesh, outputSurface );
    ATTRIBUTE_AFFECTS( size, outputSurface );
    ATTRIBUTE_AFFECTS( shapeType, outputSurface );

	return MS::kSuccess;
}
