/*
 *
 * SOP Sphere Example - currently this only gives a Poly Representation and does not fuse the polar ends properly 
 * 								but does display poles correctly. WIP - emphasis is on learning the gdp.
 *
 * Deborah R. Fowler
 * Dec 15, 2012
 *
 * From the odforce forums - taking the idea from 
 * http://forums.odforce.net/index.php?/topic/13850-drawing-geometry-in-houdini/
 * I have filled in the blanks on the idea of drawing a sphere etc. at HDK level 
 * For example, the "do some math ... part is based
 * on the parametric equation for a sphere wiki entry http://en.wikipedia.org/wiki/Sphere
 *
 */


#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>

#include "sopSphereExample.h"

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("SphereExample",                		// Node Name - Default Name
                            				"SphereExampleHDK",            			// UI name
                            				sopSphereExample::myConstructor,  	// How to build the SOP
                            				sopSphereExample::myTemplateList,  	// My UI parameters
                            				0,                         			// Min # of I/O sources
                            				0,                         			// Max # of I/O sources
                            				sopSphereExample::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         variable_name("radius", "Radius");
static PRM_Default      variable_defaults(1);
static PRM_Range		variable_range(PRM_RANGE_RESTRICTED, 0, PRM_RANGE_UI, 100);
static PRM_Name			res_name("res", "Res");
static PRM_Default 		res_defaults(16);
static PRM_Range		res_range(PRM_RANGE_RESTRICTED, 16, PRM_RANGE_UI, 64);


PRM_Template sopSphereExample::myTemplateList[] = {	
				PRM_Template( PRM_XYZ, 3, &PRMcenterName ),
				PRM_Template( PRM_FLT, 		1, &variable_name, 	&variable_defaults,	0,	&variable_range ),
				PRM_Template( PRM_INT, 1, &res_name, &res_defaults, 0, &res_range ),
				PRM_Template()
};






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

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

float sopSphereExample::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 * sopSphereExample::myConstructor(OP_Network *net, const char *name, OP_Operator *op)
{
    return new sopSphereExample(net, name, op);
};

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

};

sopSphereExample::~sopSphereExample()
{
};

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







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

	// First grab the frame time and variables form the UI
    double              now;
    UT_Interrupt        *boss;
	
	// Create an array that will store the points references
	const int MAXTotalPoints = 64;

	GEO_Point *sphere_points[MAXTotalPoints * MAXTotalPoints];
	int number_faces = myTotalPoints * myTotalPoints - 1;
	
	
    now 				= context.getTime();
	
	int res = evalInt("res",0,now);
	myTotalPoints = res;
	number_faces = res * res - 1;
	
	// What is the current values of center
	fpreal tx = evalFloat("t",0,now);
	fpreal ty = evalFloat("t",1,now);
	fpreal tz = evalFloat("t",2,now);
	
	fpreal r = evalFloat("radius",0,now);

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

	gdp->clearAndDestroy();

    // 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...") )
        {
			
			// Adding points to the detail and storing their references in *sphere_points
            for( int pc = 0; pc < myTotalPoints * myTotalPoints; pc++ )
            { 
                //Creating point in the details
                sphere_points[pc] = gdp->appendPoint(); 				
            } 
			
			// Once you have that you need to calculate your points values. (This depends on what you are trying to draw)
			// In a sphere it is as follows
			const float PI = 3.141592653589;
			float angle = (2.0 * PI) / (myTotalPoints);  // from 0 to 2 pi
			float otherangle = PI / (myTotalPoints-1); // from 0 to pi 
			float x = 0;
			float y = 0;
			float z = 0;
		
			for ( int i = 0; i < myTotalPoints; i++ )
            {		
				for( int j = 0; j < myTotalPoints; j++ )
				{
					 x = tx + r * cos(angle * i) * sin(otherangle * j);
					 y = ty + r * sin(angle * i) * sin(otherangle * j);
					 z = tz + r * cos(otherangle * j);

					 //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                                           
					 sphere_points[i*myTotalPoints+j]->setPos(P);
				 }
            }
			
			// Print the points for debugging purposes
			/*
			for( int i = 0; i < myTotalPoints * myTotalPoints; i++ )
			{
				cout << "point number " << i <<  " = " << sphere_points[i]->getPos() << endl;
			}
			*/
			
		
			// So far this will draw only points (the GEO_Point is now in gdp
			// The set of points are defined for the sphere, but to draw the surfaces, you need to create a primitive
			GEO_PrimPoly* prim_poly_ptr;
			
			int p1_index = 0;
			int p2_index =  myTotalPoints;
			int p3_index =  myTotalPoints+1;
			int p4_index = 1;
			// Drawing surfaces
			for( int fn = 0; fn < number_faces; fn++ )                                    
            {                                          
                //Creating a polygon and setting its size to zero as is shown in the api example
                //This polygon has no vertices yet
                prim_poly_ptr = dynamic_cast <GEO_PrimPoly*>(gdp->appendPrimitive(GEO_PRIMPOLY));                                        
                prim_poly_ptr->setSize(0);
                                        
                //Now you append  vertices to that primitive polygon and pass them a reference to the point that they correspond from our sphere point list
                //In this example I draw my sphere as quads since it was easier to procedural map the points to an index                             
              
			    // check if we are at the poles, otherwise go ahead 
				if (p1_index %  myTotalPoints == 0)
				{
					//Point 1
					prim_poly_ptr->appendVertex(sphere_points[0]);
					//Point 3;
					prim_poly_ptr->appendVertex(sphere_points[p3_index]);
					//Point 4;
					prim_poly_ptr->appendVertex(sphere_points[p4_index]);
				}
				else {
					if ((p3_index + 1) %  myTotalPoints == 0)
					{
						//Point 1
						prim_poly_ptr->appendVertex(sphere_points[p1_index]);
						//Point 2
						prim_poly_ptr->appendVertex(sphere_points[p2_index]);
						//Point 3
						prim_poly_ptr->appendVertex(sphere_points[ myTotalPoints-1]);
					}
					else 
					{
						//Point 1
						prim_poly_ptr->appendVertex(sphere_points[p1_index]);
						//Point 2
						prim_poly_ptr->appendVertex(sphere_points[p2_index]);
						//Point 3;
						prim_poly_ptr->appendVertex(sphere_points[p3_index]);
						//Point 4;
						prim_poly_ptr->appendVertex(sphere_points[p4_index]);
					}
				}
				
                //Closing the primitive to make it a polygon instead of a poly line
                prim_poly_ptr->close();

				
				p1_index += 1;
				p2_index += 1;
				p3_index += 1;
				p4_index += 1;
				if (p1_index >=  myTotalPoints *  myTotalPoints ) p1_index = 0;
				if (p2_index >=  myTotalPoints *  myTotalPoints) p2_index = 0;
				if (p3_index >=  myTotalPoints *  myTotalPoints) p3_index = 0;
				if (p4_index >=  myTotalPoints *  myTotalPoints) p4_index = 1;		

            }
			
        }
        boss->opEnd();
    };

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




