/*
 * 		Shell Spiral SOP
 * 		
 * 		Deborah R. Fowler
 * 		Date: Dec 16, 2012
 *
 * 		HDK sop node to create a helico-spiral 
 *
 *		inspired by cmiVFX's David Garry tutorial on vex (201?)
 * 		based on 1992 Siggraph paper and C implementation code (1990's)
 *
 */


#include <limits.h>
#include <SOP/SOP_Node.h>
#include <UT/UT_DSOVersion.h>
#include <UT/UT_Math.h>
#include <UT/UT_Interrupt.h>
#include <GU/GU_Detail.h>
#include <GU/GU_PrimPoly.h>
#include <CH/CH_LocalVariable.h>
#include <PRM/PRM_Include.h>
#include <OP/OP_Operator.h>
#include <OP/OP_OperatorTable.h>

// File headers
#include "sopShellSpiral.h"

using namespace std;
using namespace HDK_Sample;


// This is standard declaration for the SOP_Node to add it into the Nodes UI panel
void newSopOperator(OP_OperatorTable *table)
{
    table->addOperator(	new OP_Operator("ShellSpiral",                		// Node Name - Default Name
                            			"ShellSpiralHDK",            		// UI name
                            			sopShellSpiral::myConstructor,  	// How to build the SOP
                            			sopShellSpiral::myTemplateList,  	// My UI parameters
                            			0,                         			// Min # of I/O sources
                            			0,                         			// Max # of I/O sources
                            			sopShellSpiral::myVariables,     	// Local variables
                            			OP_FLAG_GENERATOR)        		 	// Flag it as generator
    );
};



// This is the parameters for the UI and input variables for the Node...
static PRM_Name		names[] =
{
	PRM_Name("numberOfTurns","NumberOfTurns"),
	PRM_Name("divisions", "Divisions"),
	PRM_Name("radiusInit","RadiusInit"),
	PRM_Name("radiusGrowth","RadiusGrowth"),	
	PRM_Name("verticalInit","verticalInit"),
	PRM_Name("verticalGrowth","verticalGrowth"),
	PRM_Name("scaleInit","ScaleInit"),
	PRM_Name("scaleGrowth","ScaleGrowth"),
	PRM_Name(0)
};

// Set the defaults to a resonable linear shell as a starting point
static PRM_Default  variableDefaults[] =
{
	PRM_Default(6),
	PRM_Default(20),
	PRM_Default(0.111),
	PRM_Default(1.052),
	PRM_Default(0.473),
	PRM_Default(1.069),
	PRM_Default(1),
	PRM_Default(1.079),
	PRM_Default(0)
};
static PRM_Range	variableRange(PRM_RANGE_RESTRICTED, 1, PRM_RANGE_UI, 10);
static PRM_Range	variableRangeInit(PRM_RANGE_RESTRICTED, 0, PRM_RANGE_UI, 1);
static PRM_Range	variableRangeGrowth(PRM_RANGE_RESTRICTED, 1, PRM_RANGE_UI, 1.9);

 
PRM_Template sopShellSpiral::myTemplateList[] = {	
	PRM_Template( PRM_INT, 1, &names[0], &variableDefaults[0], 0,&variableRange ),	
	PRM_Template( PRM_INT, 1, &names[1], &variableDefaults[1], 0,&variableRange ),	
	PRM_Template( PRM_FLT, 1, &names[2], &variableDefaults[2], 0,&variableRangeInit ),
	PRM_Template( PRM_FLT, 1, &names[3], &variableDefaults[3], 0,&variableRangeGrowth ),
	PRM_Template( PRM_FLT, 1, &names[4], &variableDefaults[4], 0,&variableRangeInit ),
	PRM_Template( PRM_FLT, 1, &names[5], &variableDefaults[5], 0,&variableRangeGrowth ),
	PRM_Template( PRM_FLT, 1, &names[6], &variableDefaults[6], 0,&variableRangeInit ),
	PRM_Template( PRM_FLT, 1, &names[7], &variableDefaults[7], 0,&variableRangeGrowth ),
	PRM_Template()
};




// Here's how we parse out internal variables
enum { VAR_PT, VAR_NPT }; // $PT $NPT

CH_LocalVariable sopShellSpiral::myVariables[] = {	{ "PT", 	VAR_PT, 	0 },            		// The table provides a mapping
													{ "NPT", 	VAR_NPT, 	0 },           			// from text string to integer token
													{ 0, 		0, 			0 },
};

float sopShellSpiral::getVariableValue( int index, int thread )
{
    if (myCurrPoint < 0) return 0;
    switch (index)
    {
        case VAR_PT:    return myCurrPoint;
        case VAR_NPT:   return myTotalPoints;
    }
    return SOP_Node::getVariableValue(index, thread);
};



// Cconstructors

OP_Node * sopShellSpiral::myConstructor(OP_Network *net, const char *name, OP_Operator *op)
{
    return new sopShellSpiral(net, name, op);
};

sopShellSpiral::sopShellSpiral(OP_Network *net, const char *name, OP_Operator *op) : SOP_Node(net, name, op)
{

};

sopShellSpiral::~sopShellSpiral()
{
};

unsigned sopShellSpiral::disableParms()
{
    return 0;
};


// Cook the ShellSpiral SOP
OP_ERROR sopShellSpiral::cookMySop(OP_Context &context)
{
	// First grab the frame time and variables from the UI
    double              now;
    UT_Interrupt        *boss;

    now 				= context.getTime();
	
	divisions 			= evalInt("divisions",0,now);
	numTurns			= evalInt("numberOfTurns",0,now);
	radiusInit 			= evalFloat("radiusInit",0,now);
	radiusGrowth 		= evalFloat("radiusGrowth",0,now);	
	verticalInit		= evalFloat("verticalInit",0,now);
	verticalGrowth 		= evalFloat("verticalGrowth",0,now);	
	scaleInit	 		= evalFloat("scaleInit",0,now);
	scaleGrowth 		= evalFloat("scaleGrowth",0,now);
	
	numPoints = numTurns * divisions;
	
	
    // Before we do anything, we must lock our inputs.  Before returning,
    //  we have to make sure that the inputs get unlocked.
	// This is not needed in this example but left for later revisions
    if (lockInputs(context) >= UT_ERROR_ABORT)
	{
        return error();
	}
	

	// Create a vector that will store the point references
	vector <GEO_Point *> spiralPoints;
	// Create a velocity attribute
	GA_RWAttributeRef N;
	GA_RWAttributeRef pscale;

	//cout << "Test Message: Version 1";
	//cout.flush(); // not required on Windows, but usually needed on Linux

    // Check to see that there hasn't been a critical error in cooking the SOP.
    if (error() < UT_ERROR_ABORT)
    {
        boss = UTgetInterrupt();

        if (boss->opStart("Cooking SOP..."))
        {
			gdp->clearAndDestroy();
			
 			// Adding points to the detail and storing their references in *sphere_points
            for( int pc = 0; pc < numPoints; pc++ )
            { 
                //Creating point in the details
                //spiralPoints[pc] = gdp->appendPoint(); 
				spiralPoints.push_back( gdp->appendPoint() );
				// Creating a point normal
				N = gdp->addFloatTuple( GA_ATTRIB_POINT, "N", 3 );
				// The next line is optional
				// N.getAttribute()->setTypeInfo( GA_TYPE_NORMAL);
				// Adding a scale attribute
				pscale = gdp->addFloatTuple( GA_ATTRIB_POINT, "pscale", 1 );
				
            } 
			
			// Placing the points
			for ( int i = 0; i < numPoints; i++ )
            {		
				// compute angle theta
				theta = 2 * PI * (float) i / (float) divisions;
				
				// helico spiral
				// from equations for logarithmic spiral lying in a plane y = 0
				radius = radiusInit * pow( radiusGrowth, theta );
				scale = scaleInit * pow( scaleGrowth, theta );
				// equation to stretch the spiral downward along the y-axis, contributing the helical component
				vertical = verticalInit - verticalInit * pow( verticalGrowth, theta );
				
				// convert the values from polar to cartesian coordinates
				x = radius * cos( theta );
				y = vertical;
				z = radius * sin ( theta );
	
				// And, assuming y is up {0,1,0}
				nx = radius * sin( theta );
				ny = 0;
				nz = -radius * cos( theta );
				
				//Vector to store our result
				UT_Vector4 P;
				//Setting vector values
				P.assign(x,y,z,1);
															   
				//Setting the position of the points that were appended above                                           
				spiralPoints[i]->setPos(P);
				//We also need to see an attribute for normal and scale
				UT_Vector3 normal;
				normal.assign(nx,ny,nz);
				spiralPoints[i]->setValue(N,normal);
				
				spiralPoints[i]->setValue(pscale,scale);

			}		
        }
        boss->opEnd();
    };
	
    gdp->notifyCache(GU_CACHE_ALL);
    myCurrPoint = -1;
	
	unlockInputs();
    return error();
};




