/*
 *
 * 		My HDK Circle Node for Houdini
 * 			   www.kamid.net
 *
 * Original Author: Dave Kin
 * Modified Dec 17, 2012: Deborah R. Fowler
 *
 * minor modifications listed below:
 *
 * changed values to 180.0 and 360.0 so result would be float not int with certainty 
 * (otherwise this will result in sin function call errors and odd looking divisions of your geometry)
 * type cast arguments to sin and cos where there is an expression
 * array for points must be declared with a constant ie) GEO_Point *points[ 1000 ];
 * better still - change this to a vector
 * GEOPRIMPOLY changed to GEO_PRIMPOLY
 * changed UI name to HDKcircleNode, names of files
 */


// Include Standard C Package
#include <stdio.h>
#include <stdlib.h>

// Include Houdini (HDK) Package
// If you have errors below, make sure to add the "houdini_installation_folder / devkit / include" path
// to your project properties
#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_PrimPoly.h>
#include <GU/GU_Detail.h>
#include <CH/CH_LocalVariable.h>
#include <PRM/PRM_Include.h>
#include <OP/OP_Operator.h>
#include <OP/OP_OperatorTable.h>

// File headers
#include "sopCircleNode.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("CircleNode",                		// Node Name - Default Name
                            				"CircleNodeHDK",            		// UI name (tabe menu)
                            				sopCircleNode::myConstructor,  	// How to build the SOP
                            				sopCircleNode::myTemplateList,  	// My UI parameters
                            				0,                         			// Min # of I/O sources
                            				0,                         			// Max # of I/O sources
                            				sopCircleNode::myVariables,     	// Local variables
                            				OP_FLAG_GENERATOR)        		 	// Flag it as generator
    );
};



// This is the parameters for the UI and input variables for the Node...

// Firstly, lets add properties to our circle by customizing our myTemplatesList
// and name the Sides and Radius UI elements
static PRM_Name       sides_name("nsides", "Sides");
static PRM_Default    sides_defaults(12);
static PRM_Range      sides_range(PRM_RANGE_RESTRICTED, 3, PRM_RANGE_UI, 50);

static PRM_Name       radius_name("radius", "Radius");
static PRM_Default    radius_defaults(1);

// Notice that I also added a PRM_RANGE_RESTRICT to make sure our values do not go below 3, but used a
// More flexible RANGE_UI for the max value instead
// And now, as a little gimmick, I am also added Magnitude, Frequency and Offset variables

static PRM_Name		magnitude_name("magnitude", "Magnitude");
static PRM_Default	magnitude_defaults(0);
static PRM_Range	magnitude_range(PRM_RANGE_RESTRICTED, 0, PRM_RANGE_UI, 1);

static PRM_Name 	freq_name("frequency", "Frequency");
static PRM_Default	freq_defaults(1);
static PRM_Range	freq_range(PRM_RANGE_RESTRICTED, 0, PRM_RANGE_UI, 5);

static PRM_Name		offset_name("offset", "Offset");
static PRM_Default	offset_defaults(0);
static PRM_Range	offset_range(PRM_RANGE_RESTRICTED, 0, PRM_RANGE_UI, 10);


// Now that we have declared the PRM standards, now let us push them into Houdini with the template list.
PRM_Template sopCircleNode::myTemplateList[] = {	
	PRM_Template( PRM_INT, 1, &sides_name, &sides_defaults, 0, &sides_range ),
	PRM_Template( PRM_FLT, 1, &radius_name, &radius_defaults ),
	PRM_Template( PRM_FLT, 1, &magnitude_name, &magnitude_defaults, 0, &magnitude_range ),
	PRM_Template( PRM_INT, 1, &freq_name, &freq_defaults, 0, &freq_range ),
	PRM_Template( PRM_INT, 1, &offset_name, &offset_defaults, 0, &offset_range ),
	PRM_Template()
};





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

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

float sopCircleNode::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);
};






// These are the constructors...

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

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

};

sopCircleNode::~sopCircleNode()
{
};

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







// This is what COOKs the SOP
OP_ERROR sopCircleNode::cookMySop(OP_Context &context)
{

	// First, let us grab all the information from the UI

	double		now;
	int			sides,
				frequency,
				offset;
	float		radius,
				magnitude;

	// You will notice that I am using Houdini specific eval *** functions and calling up names from our PRM_Name containers
	// And if you are wondering, now represents current frame time... so If you run a fixed value, the UI value will be un-keyable
	// and the 0s in the evals are actually the data value... an example where it will be 1 or 2 would be if the UI is a vector with 3
	// values

	now       = context.getTime();
	sides     = evalInt			("nsides", 0, now);
	radius    = evalFloat   	("radius", 0, now);
	magnitude = evalFloat  	 	("magnitude", 0, now);
	frequency = evalInt     	("frequency", 0, now);
	offset    = evalInt      	("offset", 0, now);

	// clears the GU_Detail container.. remember that gdp is declared by default
	gdp->clearAndDestroy();

    // Check to see that there hasn't been a critical error in cooking the SOP.
	UT_Interrupt        *boss;

    if (error() < UT_ERROR_ABORT)
    {
        boss = UTgetInterrupt();

        if (boss->opStart("Cooking SOP..."))
        {
			cout << "\nModified Version";
        	// First, lets create the data to store points
        	myTotalPoints = sides + 1;
        	// GEO_Point            *points[ myTotalPoints ];
			// GEO_Point            *points[ 1000 ];
			vector < GEO_Point * > points;

        	for ( int i = 0; i < myTotalPoints; i++ ) 
			{
				// points[i] = gdp->appendPoint();
				points.push_back( gdp->appendPoint() );
			}

        	// Then let us create the coordinates for the points
        	UT_Vector4           pointPos;

        	// UT_Vector4 is Houdini specific, it stores X Y Z and W which stands for weight
        	// Over here, we assign point 0 to the origin.
        	pointPos.assign(0, 0, 0, 1);
        	points[0]->setPos(pointPos);

        	// Next, run loop to generate positions for the Circle
        	const float PI = 3.141592653589;
        	float	current_angle,
        			x, z,
        			radian;

        	for ( int i = 1; i < myTotalPoints; i++ )
        	{
        		// We will divide the angles of a circle into equal pieces then convert it into radians (i - 1) because 0 is the center
        		current_angle   = 360.0 / sides * ( i - 1);
        		radian			= current_angle * PI / 180.0;

        		// Then we will convert the radian to a vector
        		// This vector will then be multipled with radius + fancy distance control by a sin wave
        		x	= cos( radian ) * ( radius + ( sin( (float) (i * frequency + offset) ) * magnitude ) );
        		z	= sin ( radian ) * ( radius + ( sin( (float) (i * frequency + offset) ) * magnitude ) );

        		pointPos.assign(x, 0, z, 1);
        		points[ i ]->setPos(pointPos);
        	};

        	// After the points are set, we now move to create polygon
        	GEO_PrimPoly         *polygon;


        	for ( int i = 0; i < sides; i++ )
        	{
        		// Begin building Polygon
        		polygon = dynamic_cast <GEO_PrimPoly*>(gdp->appendPrimitive(GEO_PRIMPOLY));
        		polygon->setSize(0);

        		// For each poly, I am building a TRI starting from the origin point, to the point that coincides with the side's point
        		// However should the side be the last one, loop back the value back onto the first side point.
        		polygon->appendVertex( points[ 0 ] );
        		polygon->appendVertex( points[ i + 1 ] );

        		if ( i + 1 >= sides )
        		{
        			polygon->appendVertex( points[ 1 ] );
        		}
        		else
        		{
        			polygon->appendVertex( points[ i + 2 ] );
        		}

        		// Finish building Polygon
        		polygon->close();

        	};
			// Finish Cook process and return priority back to Houdini Platform
			boss->opEnd();
        };
    };

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




