//	Options:
//
//		-s <frame>		The start frame.    Default to 1.(double)
//		-e <frame>		The end frame.	    Default to 60.(double)
//		-l			length of tail.     Default to 5.(double)
//		-w			width of tail.      Default to 2.(double)
//		-v			variation of tail.      Default to 1.(int)
//		-px			position of tail X. Default to 0.0.(double) 
//		-py			position of tail Y. Default to 0.0.(double)
//		-pz			position of tail Z. Default to 0.0.(double)
//

#include <iostream.h>

#include <maya/MFnPlugin.h>
#include <maya/MString.h>
#include <maya/MArgList.h>

#include <maya/MPxCommand.h>

#include <maya/MGlobal.h>
#include <maya/MTime.h>
#include <maya/MDagPath.h>
#include <maya/MDagPathArray.h>
#include <maya/MFnTransform.h>
#include <maya/MItSelectionList.h>
#include <maya/MSelectionList.h>

#include <maya/MPlug.h>

#include <maya/MPoint.h>
#include <maya/MPointArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MFnNurbsCurve.h>

#include <maya/MFnNurbsSurface.h>
#include <maya/MFnAnimCurve.h>
#include <maya/MMatrix.h>
#include <maya/MVector.h>
#include <maya/MVectorArray.h>
#include <maya/MQuaternion.h>

class tail : public MPxCommand
{
public:
					tail();
	virtual			~tail();

	MStatus			doIt( const MArgList& args );
	MStatus			createBaseCurve();

	static void*	creator();

private:

	double			start, end;	      // frame range
	double			length, width ;       // object option
	double                  posX, posY, posZ ;
	int			variation ;
};

tail::tail() {}

void* tail::creator()
{
	return new tail();
}

tail::~tail()
{
}

MStatus tail::doIt( const MArgList& args )
{
	start =3D 1.0;
	end =3D 60.0;
	
	length =3D 5.0 ;
	width =3D 2.0 ;
	variation =3D 1 ;

	posX =3D 0.0 ; posY =3D 0.0 ; posZ =3D 0.0 ; 

	MStatus stat;
	double tmp;
	unsigned i;
    // Parse the arguments.
    //
    for ( i =3D 0; i < args.length(); i++ )
	{
		if ( MString( "-s" ) =3D=3D args.asString( i, &stat ) &&
			 MS::kSuccess =3D=3D stat)
		{
			tmp =3D args.asDouble( ++i, &stat );
			if ( MS::kSuccess =3D=3D stat )
			start =3D tmp;
		}
		else if ( MString( "-e" ) =3D=3D args.asString( i, &stat ) &&
				  MS::kSuccess =3D=3D stat)
		{
			tmp =3D args.asDouble( ++i, &stat );
			if ( MS::kSuccess =3D=3D stat )
			end =3D tmp;
		}
		else if ( MString( "-l" ) =3D=3D args.asString( i, &stat ) &&
				  MS::kSuccess =3D=3D stat)
		{
			tmp =3D args.asDouble( ++i, &stat );
			if ( MS::kSuccess =3D=3D stat )
			length =3D tmp ;
			if( length > (end-start) )
				length =3D (end - start) ;
		}
		else if ( MString( "-w" ) =3D=3D args.asString( i, &stat ) &&
				  MS::kSuccess =3D=3D stat)
		{
			tmp =3D args.asDouble( ++i, &stat );
			if ( MS::kSuccess =3D=3D stat )
			width =3D tmp ;
		}
		else if ( MString( "-v" ) =3D=3D args.asString( i, &stat ) &&
				  MS::kSuccess =3D=3D stat)
		{
			tmp =3D args.asDouble( ++i, &stat );
			if ( MS::kSuccess =3D=3D stat )
			variation =3D (int)tmp ;
		}
		else if( MString( "-px" ) =3D=3D args.asString( i, &stat ) &&
				  MS::kSuccess =3D=3D stat)
		{
			tmp =3D args.asDouble( ++i, &stat ) ;
			if( MS::kSuccess =3D=3D stat )
			posX =3D tmp ;
		}
		else if( MString( "-py" ) =3D=3D args.asString( i, &stat ) &&
				  MS::kSuccess =3D=3D stat)
		{
			tmp =3D args.asDouble( ++i, &stat ) ;
			if( MS::kSuccess =3D=3D stat )
			posY =3D tmp ;
		}
		else if( MString( "-pz" ) =3D=3D args.asString( i, &stat ) &&
				  MS::kSuccess =3D=3D stat)
		{
			tmp =3D args.asDouble( ++i, &stat ) ;
			if( MS::kSuccess =3D=3D stat )
			posZ =3D tmp ;
		}
	}

	stat =3D createBaseCurve();

	return stat;
}

static void makeCurve( const MPointArray& cvs )
{
	MStatus stat;
	unsigned int deg =3D 1;
	MDoubleArray knots;

	unsigned int i;
	for ( i =3D 0; i < cvs.length(); i++ )
		knots.append( (double) i );
		
    // create the curve

    MFnNurbsCurve curveFn;

    MObject curve =3D curveFn.create( cvs,
				    knots, deg,
				    MFnNurbsCurve::kOpen,
				    false, false,
				    MObject::kNullObj,
				    &stat );

    if ( MS::kSuccess !=3D stat )
		printf("=07Error creating curve.\n");

}

static void makeSurface( const MPointArray& span, const double end, =
const double start, 
				const double tailLen, const double tailWid, const MPoint movePos )
{
	int i, j, n ;
	int numCVu =3D span.length() ;

	/* Vknot */
	const double vKnots[] =3D {
		0,0,0,1,1,1
	};
	MDoubleArray vKnotArray( vKnots, 6 );

	/* Uknot */
	MDoubleArray uKnotArray( tailLen+2 );
	for( n =3D 1, i =3D 3; n < tailLen-3 ; i++, n++ ) {
		uKnotArray[i] =3D n;
	} 
	for( ; i < tailLen+2 ; i++ ) {
		uKnotArray[i] =3D n;
	}
	
	/* CV */
	MVectorArray mVecArray( numCVu ) ;
	for( i =3D 0 ; i < numCVu-1 ; i++ )
	{
		mVecArray[i] =3D ( span[i+1] - span[i] ).normal() ;
	}
	mVecArray[i] =3D mVecArray[i-1] ;

	MVectorArray line( 4 ) ;
	double d ;
	for( i =3D 0 ; i < 4 ; i++ )
	{
		switch( i )
		{
			case 0 :
				d =3D -tailWid / 2 ;
				break ;
			case 1 :
				d =3D -tailWid / 6 ;
				break ;
			case 2 :
				d =3D tailWid / 6 ;
				break ;
			case 3 :
				d =3D tailWid / 2 ;
				break ;
			default :
				break ;
		}
		line[i].z =3D d ;
		line[i].y =3D 0.0 ;
		line[i].x =3D 0.0 ;
	}

	MMatrix mtx ;
	MVector fromVec( MVector::xAxis ), toVec ;
	MPointArray posArray ;
	for( i =3D 0 ; i < (int)end ; i++ )
	{
		toVec =3D mVecArray[i] ;
		mtx =3D mtx * MQuaternion( fromVec, toVec ).asMatrix() ;
		for( j =3D 0 ; j < 4 ; j++ )
		{
			posArray.append( line[j] * mtx + span[i] + movePos ) ;
		}
		fromVec =3D toVec ;
	} 

	MPointArray cvArray ;
	for( i =3D 0 ; i < (tailLen*4) ; i++ )
	{
		cvArray.append( posArray[ i ] );
	}

	/* Make Nurbs Surface */ 
	///////////////////////

	MFnNurbsSurface mfnNurbsSurf ;
	MObject mSurface ;
	MStatus stat ;

	mSurface =3D mfnNurbsSurf.create( cvArray, uKnotArray, vKnotArray, 3, 3,
			     		MFnNurbsSurface::kOpen,
			     		MFnNurbsSurface::kClosed,
			     		true, MObject::kNullObj, &stat ) ;
	if( MS::kSuccess !=3D stat )
		cerr << "make surface failed: status " << stat << endl ;


	MFnDependencyNode fnDependNode( mSurface, &stat ) ;
	fnDependNode.setName( MString("tailSurface"), &stat ) ;

	MGlobal::executeCommand( MString("defaultNavigation -ce -s |") + =
fnDependNode.name() 
				 + MString(" -d initialShadingGroup ;"), false, false ) ;

	/* Set Animation */
	//////////////////

	double x, y, z, frame ;
	int vCnt =3D 0, uCnt =3D 1, cvCnt, add ;

	MPlug cvs =3D mfnNurbsSurf.findPlug( "controlPoints", &stat ) ;
	if( MS::kSuccess !=3D stat ){
		MGlobal::displayError( stat.errorString() ) ;
		return ;
	}

	int cvsNum =3D cvs.numElements() ;
	for( cvCnt =3D 0 ; cvCnt < cvsNum ; cvCnt++ )
	{
		MPlug elem =3D cvs.elementByLogicalIndex( cvCnt ) ;

		MPlug Xvalue =3D elem.child(0) ;
		MFnAnimCurve acFnSetX ;
		acFnSetX.create( Xvalue, NULL, &stat ) ;
		if( MS::kSuccess !=3D stat ){
			cerr << "Failure creating MFnAnimCurve function set (xValue)\n" ;
			continue ;
		}

		MPlug Yvalue =3D elem.child(1) ;
		MFnAnimCurve acFnSetY ;
		acFnSetY.create( Yvalue, NULL, &stat ) ;
		if( MS::kSuccess !=3D stat ){
			cerr << "Failure creating MFnAnimCurve function set (yValue)\n" ;
			continue ;
		}

		MPlug Zvalue =3D elem.child(2) ;
		MFnAnimCurve acFnSetZ ;
		acFnSetZ.create( Zvalue, NULL, &stat ) ;
		if( MS::kSuccess !=3D stat ){
			cerr << "Failure creating MFnAnimCurve function set (zValue)\n" ;
			continue ;
		}

		add =3D 0 ;
		for( frame =3D start ; frame <=3D end ; frame +=3D 1.0 )
		{
			if( frame-start < uCnt )
			{
				x =3D posArray[ vCnt + add ].x ;
				y =3D posArray[ vCnt + add ].y ;
				z =3D posArray[ vCnt + add ].z ;
				add +=3D 4 ;
			}
			else if( frame-start <=3D tailLen )
			{
				x =3D posArray[ vCnt + add ].x ;
				y =3D posArray[ vCnt + add ].y ;
				z =3D posArray[ vCnt + add ].z ;
			}
			else
			{
				add +=3D 4 ;
				x =3D posArray[ vCnt + add ].x ;
				y =3D posArray[ vCnt + add ].y ;
				z =3D posArray[ vCnt + add ].z ;
			}
			MTime tm( frame, MTime::kFilm ) ;
				if( ( MS::kSuccess !=3D acFnSetX.addKeyframe( tm, x ) ) ||
		    		    ( MS::kSuccess !=3D acFnSetY.addKeyframe( tm, y ) ) ||
				    ( MS::kSuccess !=3D acFnSetZ.addKeyframe( tm, z ) ) ) 
				{
					cerr << "Error setting the keyframe" << endl ;
				}
		}

		vCnt ++ ;
		if( vCnt =3D=3D 4 )
		{
			vCnt =3D 0 ;
			uCnt++ ;
		}

	}

}

static void makeTube( const MPointArray& span, const double end, const =
double start, 
				const double tailLen, const double tailWid, const MPoint movePos )
{
	int i, j, n ;
	int numCVu =3D span.length() ;

	/* Vknot */
	const double vKnots[] =3D {
		0,1,2,3,4,5,6,7,8,9,10,11,12
	};
	MDoubleArray vKnotArray( vKnots, 13 );

	/* Uknot */
	MDoubleArray uKnotArray( tailLen+2 );
	for( n =3D 1, i =3D 3; n < tailLen-3 ; i++, n++ ) {
		uKnotArray[i] =3D n;
	} 
	for( ; i < tailLen+2 ; i++ ) {
		uKnotArray[i] =3D n;
	}
	
	/* CV */
	MVectorArray mVecArray( numCVu ) ;
	for( i =3D 0 ; i < numCVu-1 ; i++ )
	{
		mVecArray[i] =3D ( span[i+1] - span[i] ).normal() ;
	}
	mVecArray[i] =3D mVecArray[i-1] ;

	double pi =3D 3.14159265 ;
	double rot =3D 2 * pi / 8.0 ;

	MVectorArray circle( 11 ) ;
	for( i =3D 0 ; i < 8 ; i++ )
	{
		circle[i].z =3D cos(rot) * tailWid ;
		circle[i].y =3D sin(rot) * tailWid ;
		circle[i].x =3D 0.0 ;
		rot +=3D 2 * pi / 8.0 ;
	}
	for( ; i < 11 ; i++ )
	{
		circle[i].z =3D circle[i-8].z ;
		circle[i].y =3D circle[i-8].y ;
		circle[i].x =3D 0.0 ;
	}

	MMatrix mtx ;
	MVector fromVec( MVector::xAxis ), toVec ;
	MPointArray posArray ;
	for( i =3D 0 ; i < (int)end ; i++ )
	{
		toVec =3D mVecArray[i] ;
		mtx =3D mtx * MQuaternion( fromVec, toVec ).asMatrix() ;
		for( j =3D 0 ; j < 11 ; j++ )
		{
			posArray.append( circle[j] * mtx + span[i] + movePos ) ;
		}
		fromVec =3D toVec ;
	} 

	MPointArray cvArray ;
	for( i =3D 0 ; i < tailLen*11 ; i++ )
	{
		cvArray.append( posArray[ i ] ) ;
	}

	/* Make Nurbs Tube */ 
	////////////////////

	MFnNurbsSurface mfnNurbsSurf ;
	MObject mSurface ;
	MStatus stat ;

	mSurface =3D mfnNurbsSurf.create( cvArray, uKnotArray, vKnotArray, 3, 3,
			     		MFnNurbsSurface::kOpen,
			     		MFnNurbsSurface::kClosed,
			     		true, MObject::kNullObj, &stat ) ;
	if( MS::kSuccess !=3D stat )
		cerr << "make surface failed: status " << stat << endl ;

	MFnDependencyNode fnDependNode( mSurface, &stat ) ;
	fnDependNode.setName( MString("tailSurface"), &stat ) ;

	MGlobal::executeCommand( MString("defaultNavigation -ce -s |") + =
fnDependNode.name() 
				 + MString(" -d initialShadingGroup ;"), false, false ) ;

	/* Set Animation */
	//////////////////

	double x, y, z, frame ;
	int vCnt =3D 0, uCnt =3D 1, cvCnt, add ;

	MPlug cvs =3D mfnNurbsSurf.findPlug( "controlPoints", &stat ) ;
	if( MS::kSuccess !=3D stat ){
		MGlobal::displayError( stat.errorString() ) ;
		return ;
	}

	int cvsNum =3D cvs.numElements() ;
	for( cvCnt =3D 0 ; cvCnt < cvsNum ; cvCnt++ )
	{
		MPlug elem =3D cvs.elementByLogicalIndex( cvCnt ) ;

		MPlug Xvalue =3D elem.child(0) ;
		MFnAnimCurve acFnSetX ;
		acFnSetX.create( Xvalue, NULL, &stat ) ;
		if( MS::kSuccess !=3D stat ){
			cerr << "Failure creating MFnAnimCurve function set (xValue)\n" ;
			continue ;
		}

		MPlug Yvalue =3D elem.child(1) ;
		MFnAnimCurve acFnSetY ;
		acFnSetY.create( Yvalue, NULL, &stat ) ;
		if( MS::kSuccess !=3D stat ){
			cerr << "Failure creating MFnAnimCurve function set (yValue)\n" ;
			continue ;
		}

		MPlug Zvalue =3D elem.child(2) ;
		MFnAnimCurve acFnSetZ ;
		acFnSetZ.create( Zvalue, NULL, &stat ) ;
		if( MS::kSuccess !=3D stat ){
			cerr << "Failure creating MFnAnimCurve function set (zValue)\n" ;
			continue ;
		}

		add =3D 0 ;
		for( frame =3D start ; frame <=3D end ; frame +=3D 1.0 )
		{
			if( frame-start < uCnt )
			{
				x =3D posArray[ vCnt + add ].x ;
				y =3D posArray[ vCnt + add ].y ;
				z =3D posArray[ vCnt + add ].z ;
				add +=3D 11 ;
			}
			else if( frame-start <=3D tailLen )
			{
				x =3D posArray[ vCnt + add ].x ;
				y =3D posArray[ vCnt + add ].y ;
				z =3D posArray[ vCnt + add ].z ;
			}
			else
			{
				add +=3D 11 ;
				x =3D posArray[ vCnt + add ].x ;
				y =3D posArray[ vCnt + add ].y ;
				z =3D posArray[ vCnt + add ].z ;
			}
			MTime tm( frame, MTime::kFilm ) ;
				if( ( MS::kSuccess !=3D acFnSetX.addKeyframe( tm, x ) ) ||
		    		    ( MS::kSuccess !=3D acFnSetY.addKeyframe( tm, y ) ) ||
				    ( MS::kSuccess !=3D acFnSetZ.addKeyframe( tm, z ) ) ) 
				{
					cerr << "Error setting the keyframe" << endl ;
				}
		}

		vCnt ++ ;
		if( vCnt =3D=3D 11 )
		{
			vCnt =3D 0 ;
			uCnt++ ;
		}

	}

}


MStatus tail::createBaseCurve()
{
	MStatus stat;				// Status code

	MDagPathArray    picked ;
	MDagPath         mPath ;

	// Create a selection list iterator
	//
	MSelectionList slist;
	MGlobal::getActiveSelectionList( slist );
	MItSelectionList iter( slist, MFn::kInvalid,&stat );

	// Iterate over all selected dependency nodes
	// and save them in a list
	//
	for ( ; !iter.isDone(); iter.next() )
	{
		// Get the selected dag path
		//
		if ( MS::kSuccess !=3D iter.getDagPath( mPath ) )
		{
			cerr << "Error getting the dag path" << endl;
			continue;
		}
		picked.append( mPath );
	}

	// array of arrays for object position

	MPointArray *pointArrays =3D new MPointArray [ picked.length() * 3 ];

	unsigned int i;
	double time;

	for ( time =3D start; time <=3D end; time++ )
	{
		MTime timeval(time);

		MGlobal::viewFrame( timeval );

		// Iterate over selected dag path
		//

		for ( i =3D 0 ; i < picked.length() ; i++ )
		{
			mPath =3D picked[i];

			MFnTransform fnTransform( mPath ) ;
			MVector t =3D fnTransform.translation( MSpace::kWorld, &stat ) ;

#if 0
			fprintf( stderr,
				     "Time =3D %2.2lf, XYZ =3D ( %2.2lf, %2.2lf, %2.2lf )\n\n",
					 time, t.x, t.y, t.z );
#endif

			pointArrays[i].append( MPoint( t ) ) ;
		
		}
	}

	// make a path curve or surface or tube for each selected object

	MPoint movePos( posX, posY, posZ ) ; 
	for ( i =3D 0 ; i < picked.length() ; i++ )
	{
		switch( variation )
		{
			case 0:
				makeCurve( pointArrays[i] ) ;
				break ;
			case 1:
				makeSurface( pointArrays[i], end, start, length, width, movePos );
				break ;
			case 2:
				makeTube( pointArrays[i], end, start, length, width, movePos );
				break ;
			default:
				break ;
		}
	}

	delete [] pointArrays;

	return MS::kSuccess;
}

MStatus initializePlugin( MObject obj )
{
	MStatus   status;
	MFnPlugin plugin( obj, "", "3.0", "Any");

	status =3D plugin.registerCommand( "tail", tail::creator );
	if (!status) {
		status.perror("registerCommand");
		return status;
	}

	return status;
}

MStatus uninitializePlugin( MObject obj)
{
	MStatus   status;
	MFnPlugin plugin( obj );

	status =3D  plugin.deregisterCommand( "tail" );
	if (!status) {
		status.perror("registerCommand");
		return status;
	}

	return status;
}


