/* Primitive shape generating code. Copyright (c) 2004 Philip Boulain. */

/* WARNING: NORMALS ARE NOT GENERATED FOR SPHERES AND CONES, ONLY CYLINDERS! */
/* LIKEWISE TEXTURE CO-ORDINATES */

#ifdef DEBUG_CURVE
#include <stdio.h>
#endif
#include <math.h>    /* Required for sin(); cos() in curve() */
#include <string.h>  /* Required for memcpy() */
#include "smalloc.h" /* curve() allocates from heap */
#include "shapes.h"

#ifndef M_PI /* Not provided by all math.h implementations */
	#define M_PI 3.14159265358979323846264338327
#endif
#define QUARTER_PI (M_PI / 4.0)

/* Utility functions */

/* Returns an array of interleaved X-Y co-ordinates for a 2D circle of      */
/* radius 0.5 centred on the origin. It uses the trick given in the lecture */
/* on 2D circle rendering to avoid seven-eighths of the calculation, but    */
/* not the 'East or South-East' algorithm, as this would 'quantize' the     */
/* curve badly at higher segment counts (depends on discrete values).       */
/* Unlike the pixel-setting version, care must also be taken to store the   */
/* verticies in such a way that they end up in the correct order.           */
/* 'csize' is modified to contain the size of the array. The last point     */
/* will be the LAST one before the first -- not the first again.            */
/* It is used to generate curves for the cone and sphere in init (NOT draw, */
/* as it is relatively expensive [calls sin() and cos()]).                  */
/* ALLOCATES FROM HEAP. Be sure to free() the result once finished with it. */
GLfloat* curve(int segments, int* csize) {
	/* Due to annoying C90 standards (as followed by gcc's --pedantic */
	/* mode), variables need to be declared BEFORE any code.          */
	GLfloat *c;                     /* return values */
	double theta, deltatheta, x, y; /* current angle; angle delta; current point */
	int seg, microoffset, macrooffset, offsetskip, o;
	/* current segment; current offset from eighth boundary to store x in c; current offset to eighth boundary; increment for offset to get to the next TWO eighths; temporary variable to store computed offset */
	
	/* Segment is reflected eight times, each with two co-ordinates */
	*csize = segments * 8 * 2;
	c = smalloc( sizeof(GLfloat) * (*csize) );

	theta = 0.0; deltatheta = QUARTER_PI / segments;
	seg = 0; offsetskip = segments * 4;
	while(theta < QUARTER_PI) {
		/* Calculate (expensive!) */
		x = sin(theta) * 0.5;
		y = cos(theta) * 0.5;
		/* Store eight times (using reflections) */
		/* Four go in an 'increasing' direction; four in a 'decreasing' */
		microoffset = seg * 2; macrooffset = 0;
		o = macrooffset + microoffset;       c[ o ] =  x; c[ o + 1 ] =  y;
		macrooffset += offsetskip;
		o = macrooffset + microoffset;       c[ o ] =  y; c[ o + 1 ] = -x;
		o = macrooffset - (microoffset + 2); c[ o ] =  y; c[ o + 1 ] =  x;
		macrooffset += offsetskip;
		o = macrooffset + microoffset;       c[ o ] = -x; c[ o + 1 ] = -y;
		o = macrooffset - (microoffset + 2); c[ o ] =  x; c[ o + 1 ] = -y;
		macrooffset += offsetskip;
		o = macrooffset + microoffset;       c[ o ] = -y; c[ o + 1 ] =  x;
		o = macrooffset - (microoffset + 2); c[ o ] = -y; c[ o + 1 ] = -x;
		macrooffset += offsetskip;
		o = macrooffset - (microoffset + 2); c[ o ] = -x; c[ o + 1 ] =  y;
		/* Step */
		theta += deltatheta; seg++;
	}
#ifdef DEBUG_CURVE
	for(seg=0;seg<*csize;seg+=2){printf("\n[%2d]=(% f,% f)",seg,c[seg],c[seg+1]);}
#endif
	return c;
}

/* Implementations */

/* CONE *********************************************************************/
void primcone_destroy(primcone* c) {
	/* Release the memory allocated to the circle of points */
	free(c->circle);
}

void primcone_draw(primcone* c) {
	/* The cone is built up of two fans; one for the 'pointy bit', and  */
	/* one for the base. Both share a circle of points on the baseline. */

	int i, j;

	/* Pointy bit */
	glBegin(GL_TRIANGLE_FAN); /* First vertex is centre, rest are outer */
	glVertex3f(0.0, c->height, 0.0); /* point directly over base */
	j = 2;
	for(i = 0; i < c->circlepoints; i+=2) {
		glVertex3f(c->circle[i], 0.0, c->circle[i+1]);
	}
	/* Connect last point of circle back to first, to make it complete */
	glVertex3f(c->circle[0], 0.0, c->circle[1]);
	glEnd();

	/* Base */
	glBegin(GL_TRIANGLE_FAN);
	glVertex3f(0.0, 0.0, 0.0); /* based on the current origin */
	j = 2;
	for(i = 0; i < c->circlepoints; i+=2) {
		glVertex3f(c->circle[i], 0.0, c->circle[i+1]);
	}
	glVertex3f(c->circle[0], 0.0, c->circle[1]);
	glEnd();
}

void primcone_init(primcone* c, int segments, GLfloat height) {
	c->destroy = &primcone_destroy;
	c->draw = &primcone_draw;
	c->segments = segments;
	c->height = height;
	c->circle = curve(segments, &c->circlepoints);
}

/* SPHERE *******************************************************************/

void primsphere_destroy(primsphere *s) {
	/* Release the memory allocated to the circle of points */
	free(s->circle);
}

void primsphere_draw(primsphere *s) {
	int i, j, scoo, scio, co; /* Iterators; scaledcircle outer/inner offset, colour offset */
	GLfloat scale;
	GLfloat* scaledcircle;
	
	/* The sphere is built up of horizontal strips. The top and bottom  */
	/* could be fans, but generalize to strips with all verticies on    */
	/* one side co-existing (this keeps the code simpler).              */
	/* Each horizontal layer, plus the top and bottom 'fans', has its   */
	/* own colour. This includes the top/bottom verticies themselves.   */

	/* It is not possible to use glScale to appropriately 'contract'    */
	/* the horizontal strips, as it cannot be performed during a        */
	/* glBegin()/glEnd() block, yet even simple triangles require that  */
	/* verticies "zig-zag" between layers.                              */
	/* Hence, the bands are scaled manually, using precalculated values */
	/* from circle[]. To do it one pass, the sphere is built from the   */
	/* equator out to the tips.                                         */
	
	/* Displacement from the middle grows with the X co-ordinate of the */
	/* precalculated circle (sin()). The scale of the horizontal circle */
	/* shrinks with cos() relative to the original world: circle's Y*2. */

	/* Colouration: segments*2-1 is the centre row of verticies         */
	co = (s->segments*2)-1;

	/* Grab memory for two scaled versions of the circle */
	scaledcircle = smalloc(s->circlepoints * sizeof(GLfloat) * 2);
	scoo = 0; /* Offset to the outer scaled circle */
	scio = s->circlepoints; /* The inner scaled circle */
	/* Init the other half to be the equitorial circle (pointer arith!) */
	memcpy(scaledcircle + scio, s->circle, s->circlepoints * sizeof(GLfloat));
	
	/* Horizontal loop */
	for(i = 0; i < (s->circlepoints/4 - 2); i+=2) {
		scale = s->circle[i+3] * 2.0; /* Needs to skip one loop ahead */
		/* Upper loop, and circle scaling */
		glBegin(GL_TRIANGLE_STRIP);
		for(j = 0; j < s->circlepoints; j+=2) {
			scaledcircle[j  +scoo] = s->circle[j  ] * scale;
			scaledcircle[j+1+scoo] = s->circle[j+1] * scale;
			/* Outer */
			glVertex3f(scaledcircle[j+scoo], s->circle[i+2], scaledcircle[j+scoo+1]);
			/* Inner */
			glVertex3f(scaledcircle[j+scio], s->circle[i  ], scaledcircle[j+scio+1]);
		}
		/* Finish loop */
		glEnd();
		
		/* Lower loop -- annoyingly must be another code loop, as   */
		/* you can't work on two glBegin()/glEnd() entities at the  */
		/* same time.                                               */
		glBegin(GL_TRIANGLE_STRIP);
		for(j = 0; j < s->circlepoints; j+=2) {
			glVertex3f(scaledcircle[j+scoo], -s->circle[i+2], scaledcircle[j+scoo+1]);
			glVertex3f(scaledcircle[j+scio], -s->circle[i  ], scaledcircle[j+scio+1]);
		}
		glEnd();

		/* Swap to the other scaled circle as being the current one */
		if(scoo) {
			scio = scoo; scoo = 0;
		} else {
			scoo = scio; scio = 0;
		}
	}
	
	/* Release memory */
	free(scaledcircle);
}

void primsphere_init(primsphere *s, int segments) {
	s->destroy = &primsphere_destroy;
	s->draw = &primsphere_draw;
	s->segments = segments;
	s->circle = curve(segments, &s->circlepoints);
}

/* CYLINDER *****************************************************************/

void primcylinder_destroy(primcylinder *s) {
	/* Release the memory allocated to the circle of points */
	free(s->circle);
}

void primcylinder_drawx(primcylinder *c, GLfloat s) {
	/* A cylinder is just one, unscaled loop of sphere, capped with fans */
	/* Supports scaling of top circle with 's' */

	int i, seg8;
	GLfloat u;
	seg8 = c->segments * 8;

	/* Loop */
	glBegin(GL_TRIANGLE_STRIP);
	for(i = 0; i < c->circlepoints; i += 2) {
		/* Texture 'u' is wrapped once around the whole thing */
		u = ((GLfloat) i) / c->circlepoints;
		/* Normals will be one unit away in the direction from the origin of the object  */
		/* i.e., exactly the same as the points on the circle ^^ */
		glNormal3f(c->circle[i], 0.0, c->circle[i+1]); /* dy = 0 */
		/* Down */
		glTexCoord2f(u, 0.); /* For textures, 0,0 is the bottom-left */
		glVertex3f(c->circle[i], 0.0, c->circle[i+1]);
		/* Up */
		glTexCoord2f(u, 1.);
		glVertex3f(s * c->circle[i], 1.0, s * c->circle[i+1]);
	}
	glEnd();

	/* Top and bottom fan textures are 'cookie cut' from the texture */
	/* Bottom fan */
	glBegin(GL_TRIANGLE_FAN);
	glNormal3f(0.0,-1.0, 0.0);
	glTexCoord2f(0.5, 0.5);
	glVertex3f(0.0, 0.0, 0.0);
	for(i = 0; i < c->circlepoints; i += 2) {
		glTexCoord2f(c->circle[i] + .5, c->circle[i+1] + .5);
		glVertex3f(c->circle[i], 0.0, c->circle[i+1]);
	}
	glEnd();

	/* Top fan */
	glBegin(GL_TRIANGLE_FAN);
	glNormal3f(0.0, 1.0, 0.0);
	glTexCoord2f(0.5, 0.5);
	glVertex3f(0.0, 1.0, 0.0);
	for(i = c->circlepoints - 2; i >= 0; i -= 2) { /* Reverse poly order for CW winding */
		glTexCoord2f(c->circle[i] + .5, c->circle[i+1] + .5);
		glVertex3f(s * c->circle[i], 1.0, s * c->circle[i+1]);
	}
	glEnd();
}
void primcylinder_draw(primcylinder *c) {
	primcylinder_drawx(c, 1.0);
}

void primcylinder_init(primcylinder *s, int segments) {
	s->destroy = &primcylinder_destroy;
	s->draw = &primcylinder_draw;
	s->segments = segments;
	s->circle = curve(segments, &s->circlepoints);
}
