//-
// ==========================================================================
// 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.
// ==========================================================================
//+

////////////////////////////////////////////////////////////////////////
//
// helixTool.cpp
//
// Description:
//     Interactive tool to draw a helix.
//     Uses OpenGL to draw a guideline for the helix.
//
//
// Steps in creating a tool command
//
// 1. Create a tool command class. 
//    Same process as an MPxCommand except define 2 methods
//    for interactive use: cancel, and finalize.
//    There is also an addition constructor MPxToolCommand(), which 
//    is called from your context when the command needs to be invoked.
//
// 2. Define your context.
//    This is accomplished by deriving off of MPxContext and overriding
//    whatever methods you need.
//
// 3. Create a command class to create your context.
//    You will call this command in Maya to create and name a context.
//
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <maya/MIOStream.h>
#include <math.h>

#include <maya/MString.h>
#include <maya/MArgList.h>
#include <maya/MEvent.h>
#include <maya/MGlobal.h>
#include <maya/M3dView.h>
#include <maya/MPoint.h>
#include <maya/MPointArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MDagModifier.h>
#include <maya/MDagPath.h>

#include <maya/MPxContext.h>
#include <maya/MPxContextCommand.h>
#include <maya/MPxToolCommand.h> 
#include <maya/MToolsInfo.h>

#include <maya/MFnPlugin.h>
#include <maya/MFnNurbsCurve.h> 

#include <maya/MSyntax.h>
#include <maya/MArgParser.h>
#include <maya/MArgDatabase.h>
#include <maya/MCursor.h>

#if defined(OSMac_MachO_)
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#endif

#define kPitchFlag			"-p"
#define kPitchFlagLong		"-pitch"
#define kRadiusFlag			"-r"
#define kRadiusFlagLong		"-radius"
#define kNumberCVsFlag		"-ncv"
#define kNumberCVsFlagLong	"-numCVs"
#define kUpsideDownFlag		"-ud"
#define kUpsideDownFlagLong	"-upsideDown"

/////////////////////////////////////////////////////////////
// The users tool command
/////////////////////////////////////////////////////////////

#define		NUMBER_OF_CVS		20

class helixTool : public MPxToolCommand
{
public:
					helixTool(); 
	virtual			~helixTool(); 
	static void*	creator();

	MStatus			doIt(const MArgList& args);
	MStatus			parseArgs(const MArgList& args);
	MStatus			redoIt();
	MStatus			undoIt();
	bool			isUndoable() const;
	MStatus			finalize();
	static MSyntax	newSyntax();
	
	void			setRadius(double newRadius);
	void			setPitch(double newPitch);
	void			setNumCVs(unsigned newNumCVs);
	void			setUpsideDown(bool newUpsideDown);

private:
	double			radius;     	// Helix radius
	double			pitch;      	// Helix pitch
	unsigned		numCV;			// Helix number of CVs
	bool			upDown;			// Helix upsideDown
	MDagPath		path;			// The dag path to the curve.
									// Don't save the pointer!
	MDagModifier	modifier;
	bool			modifierCreated;
};


void* helixTool::creator()
{
	return new helixTool;
}

helixTool::~helixTool() {}

helixTool::helixTool()
{
	numCV = 20;
	upDown = false;
	setCommandString("helixToolCmd");
	modifierCreated = false;
}
	
MSyntax helixTool::newSyntax()
{
	MSyntax syntax;

	syntax.addFlag(kPitchFlag, kPitchFlagLong, MSyntax::kDouble);
	syntax.addFlag(kRadiusFlag, kRadiusFlagLong, MSyntax::kDouble);
	syntax.addFlag(kNumberCVsFlag, kNumberCVsFlagLong, MSyntax::kUnsigned);
	syntax.addFlag(kUpsideDownFlag, kUpsideDownFlagLong, MSyntax::kBoolean);
	
	return syntax;
}

MStatus helixTool::doIt(const MArgList &args)
//
// Description
//     Sets up the helix parameters from arguments passed to the
//     MEL command.
//
{
	MStatus status;

	status = parseArgs(args);

	if (MS::kSuccess != status)
		return status;

	return redoIt();
}

MStatus helixTool::parseArgs(const MArgList &args)
{
	MStatus status;
	MArgDatabase argData(syntax(), args);

	if (argData.isFlagSet(kPitchFlag)) {
		double tmp;
		status = argData.getFlagArgument(kPitchFlag, 0, tmp);
		if (!status) {
			status.perror("pitch flag parsing failed");
			return status;
		}
		pitch = tmp;
	}
	
	if (argData.isFlagSet(kRadiusFlag)) {
		double tmp;
		status = argData.getFlagArgument(kRadiusFlag, 0, tmp);
		if (!status) {
			status.perror("radius flag parsing failed");
			return status;
		}
		radius = tmp;
	}
	
	if (argData.isFlagSet(kNumberCVsFlag)) {
		unsigned tmp;
		status = argData.getFlagArgument(kNumberCVsFlag, 0, tmp);
		if (!status) {
			status.perror("numCVs flag parsing failed");
			return status;
		}
		numCV = tmp;
	}
	
	if (argData.isFlagSet(kUpsideDownFlag)) {
		bool tmp;
		status = argData.getFlagArgument(kUpsideDownFlag, 0, tmp);
		if (!status) {
			status.perror("upside down flag parsing failed");
			return status;
		}
		upDown = tmp;
	}

	return MS::kSuccess;
}	


MStatus helixTool::redoIt()
//
// Description
//     This method creates the helix curve from the
//     pitch and radius values
//
{
	MStatus status;
	if (modifierCreated) {
		status = modifier.undoIt();
	}
	else {
		const unsigned  deg     = 3;            // Curve Degree
		const unsigned  ncvs    = numCV;		// Number of CVs
		const unsigned  spans   = ncvs - deg;   // Number of spans
		const unsigned  nknots  = spans+2*deg-1;// Number of knots
		unsigned	    i;
		MPointArray		controlVertices;
		MDoubleArray	knotSequences;

		int upFactor;
		if (upDown) upFactor = -1;
		else upFactor = 1;

		// Set up cvs and knots for the helix
		//
		for (i = 0; i < ncvs; i++)
			controlVertices.append(MPoint(radius * cos((double) i),
			upFactor * pitch * (double) i, 
			radius * sin((double) i)));

		for (i = 0; i < nknots; i++)
			knotSequences.append((double) i);

		// Now create the curve
		//
		MFnNurbsCurve curveFn;

		curveFn.create(controlVertices, knotSequences, deg, 
			MFnNurbsCurve::kOpen, false, false, 
			MObject::kNullObj, &status);

		if (status) {
			status = curveFn.getPath(path);
		}
		else {
			status.perror("Error creating curve");
		}
	}

	return status;
}

MStatus helixTool::undoIt()
//
// Description
//     Removes the helix curve from the model.
//
{
	if (!modifierCreated) {
		modifier.deleteNode(path.transform());
		modifierCreated = true;
	}
	return modifier.doIt();
}

bool helixTool::isUndoable() const
//
// Description
//     Set this command to be undoable.
//
{
	return true;	
}

MStatus helixTool::finalize()
//
// Description
//     Command is finished, construct a string for the command
//     for journaling.
//
{
	MArgList command;
	command.addArg(commandString());
	command.addArg(MString(kRadiusFlag));
	command.addArg(radius);
	command.addArg(MString(kPitchFlag));
	command.addArg(pitch);
	command.addArg(MString(kNumberCVsFlag));
	command.addArg((int) numCV);
	command.addArg(MString(kUpsideDownFlag));
	command.addArg(upDown);
	return MPxToolCommand::doFinalize( command );
}

void helixTool::setRadius(double newRadius)
{
	radius = newRadius;
}

void helixTool::setPitch(double newPitch)
{
	pitch = newPitch;
}

void helixTool::setNumCVs(unsigned newNumCVs)
{
	numCV = newNumCVs;
}

void helixTool::setUpsideDown(bool newUpsideDown)
{
	upDown = newUpsideDown;
}


/////////////////////////////////////////////////////////////
//
// The user Context
//
//   Contexts give the user the ability to write functions
//   for handling events.
//
//   Contexts aren't registered in the plugin, instead a
//   command class (MPxContextCommand) is registered and is used
//   to create instances of the context.
//
/////////////////////////////////////////////////////////////

const char helpString[] = "Click and drag to draw helix";

class helixContext : public MPxContext
{
public:
					helixContext();
	virtual void	toolOnSetup(MEvent &event);
	virtual MStatus doPress(MEvent &event);
	virtual MStatus doDrag(MEvent &event);
	virtual MStatus doRelease(MEvent &event);
	virtual MStatus doEnterRegion(MEvent &event);

	virtual	void	getClassName(MString & name) const;

	void			setNumCVs(unsigned newNumCVs);
	void			setUpsideDown(bool newUpsideDown);
	unsigned		numCVs();
	bool			upsideDown();

private:
	void			drawGuide();

	bool			firstDraw;
	short			startPos_x, startPos_y;
	short			endPos_x, endPos_y;
	unsigned		numCV;
	bool			upDown;
	M3dView			view;
	GLdouble		height,radius;
	
};

helixContext::helixContext() 
{
	numCV = 20;
	upDown = false;
	setTitleString("Helix Tool");

	setCursor( MCursor::defaultCursor );

	// Tell the context which XPM to use so the tool can properly
	// be a candidate for the 6th position on the mini-bar.
	setImage("helixTool.xpm", MPxContext::kImage1 );
}

void helixContext::toolOnSetup(MEvent &)
{
	setHelpString(helpString);
}

MStatus helixContext::doPress(MEvent &event)
{
    event.getPosition(startPos_x, startPos_y);
    view = M3dView::active3dView();
	firstDraw = true;
    return MS::kSuccess;
}


void helixContext::drawGuide()
{
	int upFactor;
	if (upDown) upFactor = 1;
	else upFactor = -1;

    // Draw the guide cylinder
    //
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
        glRotatef(upFactor*90.0f, 1.0f, 0.0f, 0.0f);
        GLUquadricObj *qobj = gluNewQuadric();
        gluQuadricDrawStyle(qobj, GLU_LINE);
        GLdouble factor = (GLdouble)numCV;
        radius = double(abs(endPos_x - startPos_x))/factor + 0.1;
        height = double(abs(endPos_y - startPos_y))/factor + 0.1;
        gluCylinder( qobj, radius, radius, height, 8, 1 );
    glPopMatrix();
}

MStatus helixContext::doDrag(MEvent & event)
{
	view.beginXorDrawing(false);

	if (!firstDraw) {
		//	Clear the guide from the old position.
		drawGuide();
	} else {
		firstDraw = false;
	}

    event.getPosition(endPos_x, endPos_y);

	//	Draw the guide at the new position.
	drawGuide();

	view.endXorDrawing();

	return MS::kSuccess;
}

MStatus helixContext::doRelease( MEvent & )
{
	//	Clear the guide from its last position.
	if (!firstDraw) {
		view.beginXorDrawing(false);
		drawGuide();
		view.endXorDrawing();
	}

	helixTool * cmd = (helixTool*)newToolCommand();
	cmd->setPitch( height/numCV );
	cmd->setRadius( radius );
	cmd->setNumCVs( numCV );
	cmd->setUpsideDown( upDown );
	cmd->redoIt();
	cmd->finalize();
	return MS::kSuccess;
}

MStatus helixContext::doEnterRegion( MEvent & )
{
	return setHelpString( helpString );
}

void helixContext::getClassName( MString & name ) const
{
	name.set("helix");
}

void helixContext::setNumCVs( unsigned newNumCVs )
{
	numCV = newNumCVs;
	MToolsInfo::setDirtyFlag(*this);
}

void helixContext::setUpsideDown( bool newUpsideDown )
{
	upDown = newUpsideDown;
	MToolsInfo::setDirtyFlag(*this);
}

unsigned helixContext::numCVs()
{
	return numCV;
}

bool helixContext::upsideDown()
{
	return upDown;
}

/////////////////////////////////////////////////////////////
//
// Context creation command
//
//  This is the command that will be used to create instances
//  of our context.
//
/////////////////////////////////////////////////////////////


class helixContextCmd : public MPxContextCommand
{
public:	
						helixContextCmd();
	virtual	MStatus		doEditFlags();
	virtual MStatus		doQueryFlags();
	virtual MPxContext* makeObj();
	virtual MStatus		appendSyntax();
	static void*		creator();

protected:
    helixContext*		fHelixContext;

};

helixContextCmd::helixContextCmd() {}

MPxContext* helixContextCmd::makeObj()
//
// Description
//    When the context command is executed in maya, this method
//    be used to create a context.
//
{
	fHelixContext = new helixContext();
	return fHelixContext;
}

void* helixContextCmd::creator()
//
// Description
//    This method creates the context command.
//
{
	return new helixContextCmd;
}


MStatus helixContextCmd::doEditFlags()
{
	MStatus status = MS::kSuccess;
	
	MArgParser argData = parser();
	
	if (argData.isFlagSet(kNumberCVsFlag)) {
		unsigned numCVs;
		status = argData.getFlagArgument(kNumberCVsFlag, 0, numCVs);
		if (!status) {
			status.perror("numCVs flag parsing failed.");
			return status;
		}
		fHelixContext->setNumCVs(numCVs);
	}

	if (argData.isFlagSet(kUpsideDownFlag)) {
		bool upsideDown;
		status = argData.getFlagArgument(kUpsideDownFlag, 0, upsideDown);
		if (!status) {
			status.perror("upsideDown flag parsing failed.");
			return status;
		}
		fHelixContext->setUpsideDown(upsideDown);
	}
	
	return MS::kSuccess;
}

MStatus helixContextCmd::doQueryFlags()
{
	MArgParser argData = parser();
	
	if (argData.isFlagSet(kNumberCVsFlag)) {
		setResult((int) fHelixContext->numCVs());
	}
	if (argData.isFlagSet(kUpsideDownFlag)) {
		setResult(fHelixContext->upsideDown());
	}
	
	return MS::kSuccess;
}

MStatus helixContextCmd::appendSyntax()
{
	MSyntax mySyntax = syntax();
	
	if (MS::kSuccess != mySyntax.addFlag(kNumberCVsFlag, kNumberCVsFlagLong,
										 MSyntax::kUnsigned)) {
		return MS::kFailure;
	}
	if (MS::kSuccess != 
		mySyntax.addFlag(kUpsideDownFlag, kUpsideDownFlagLong,
						 MSyntax::kBoolean)) {
		return MS::kFailure;
	}

	return MS::kSuccess;
}

///////////////////////////////////////////////////////////////////////
//
// The following routines are used to register/unregister
// the commands we are creating within Maya
//
///////////////////////////////////////////////////////////////////////
MStatus initializePlugin( MObject obj )
{
	MStatus status;
	MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.0", "Any");

	// Register the context creation command and the tool command 
	// that the helixContext will use.
	// 
	status = plugin.registerContextCommand("helixToolContext",
										   helixContextCmd::creator,
										   "helixToolCmd",
										   helixTool::creator,
										   helixTool::newSyntax);
	if (!status) {
		status.perror("registerContextCommand");
		return status;
	}

	return status;
}

MStatus uninitializePlugin( MObject obj)
{
	MStatus status;
	MFnPlugin plugin( obj );

	// Deregister the tool command and the context creation command
	//
	status = plugin.deregisterContextCommand( "helixToolContext",
											  "helixToolCmd" );
	if (!status) {
		status.perror("deregisterContextCommand");
		return status;
	}

	return status;
}
