/* Computer Graphics, Assignment, Intersections
 * Filename ........ normals.c
 * Description ..... Calculates Plane/Vertex normals
 * Date ............ 19.08.2008
 * 
 * Student name .... Sander van Veen & Taddeus Kroes
 * Student email ... sandervv@gmail.com & taddeuskroes@hotmail.com
 * Collegekaart .... 6167969 & 6054129
 * Date ............ 15.09.2010
 * Comments ........ 
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "normals.h"

#define EPSILON 1e-6

#define VECTOR_LENGTH(p) sqrt( p.x*p.x + p.y*p.y + p.z*p.z )

/*
 * Calculate flat shading normal for the given polygons. Note that 
 * you have to set normals for all *vertices* of the polygon, so you 
 * have to set the normal[] array in the poly struct.
 */
void calcNormalsFlat(polys* list)
{
	poly *item;
	point origin, r1, r2, normal, inside;
	
	for( GLint i = 0, len = list->length; i < len; i++ )
	{
		item = list->items+i;
		
		// Use the first 3 points of the polygon to determine the plane by
		// creating 2 directional vectors. Then, normalize the cross product
		// of those vectors to calculate the normal.
		origin = item->pts[0];
		r1 = substractVectors(item->pts[1], origin);
		r2 = substractVectors(item->pts[2], origin);
		inside = substractVectors(origin, item->inside);
		
		normal = normalizeVector(crossProduct(r1, r2));
		
		// Reverse the normal if it is pointing to the inside of the object
		if( dotProduct(normal, inside) < 0 )
		{
			normal = scalarMultiply(normal, -1);
		}
		
		// Set the normal to all points of the polygon
		for( GLint p = 0, l = item->points; p < l; p++ )
		{
			item->normal[p] = normal;
		}
	}
}

/*
 * Calculate normals for Gouraud shading. Again, the data to fill in
 * is each poly's normal[] array. You can use poly's pnormal field
 * to store temporary values if you like.
 */
void calcNormalsGouraud(polys* list)
{
	poly *item, *current;
	point normal;
	normals *group = NULL, *prev;
	
	// To have some form of shading on the sphere, as shown in
    // the assignment, we initally do flat shading here
	calcNormalsFlat(list);
	
	// Loop through all polygons, while saving groups of points that have
	// (roughly) the same coordinates
	for( int i = 0, len = list->length; i < len; i++ )
	{
		group = createNormals(len, group);
		
		current = list->items+i;
		
		// Check all consecutive polygons
		for( int j = i+1; j < len; j++ )
		{
			item = list->items+j;
			
			// Compare each point in the polygon to the points in the current
			for( int k = 0; k < item->points; k++ )
			{
				int found = 0;
				
				for( int m = 0; m < current->points; m++ )
				{
					if( equalPoint(item->pts[k], current->pts[m]) )
					{
						addToNormals(current->normal+m, group);
						found = 1;
					}
				}
				
				if( found )
					addToNormals(list->items[j].normal+k, group);
			}
		}
	}
	
	int n = 0;
	
	while( group != NULL )
	{
		if( group->length )
		{
			normal = *(group->normal[0]);
			
			for( int i = 1; i < group->length; i++ )
			{
				normal = addVectors(normal, *(group->normal[i]));
			}
			
			normal = normalizeVector(normal);
			
			for( int i = 0; i < group->length; i++ )
			{
				*(group->normal[i]) = normal;
				n++;
			}
		}
		
		free(group->normal);
		
		prev = group->prev;
		free(group);
		group = prev;
	}
	
	printf("interpolated %d points\n", n);
}

/*
 * Helper functions for grouped points
 */

normals* createNormals(int size, normals *prev)
{
	// Do not create a new group if the previous group is empty
	if( prev != NULL && !prev->length )
		return prev;
		
	normals *group = (normals*) malloc(sizeof(normals));
	
	group->normal = (point**) malloc(size * sizeof(point*));
	group->length = 0;
	group->prev = prev;
	
	return group;
}

void addToNormals(point *p, normals *group)
{
	group->normal[group->length++] = p;
}

/*
 * Helper functions for points 
 */
 
int equalPoint(point p1, point p2)
{
	return fabs(p1.x - p2.x) < EPSILON
	       && fabs(p1.y - p2.y) < EPSILON
	       && fabs(p1.z - p2.z) < EPSILON;
}

point createPoint(GLdouble x, GLdouble y, GLdouble z)
{
	point p = {x, y, z};
	return p;
}

/*
 * Basic vector operations
 */

point normalizeVector(point p)
{
	return scalarMultiply(p, 1 / VECTOR_LENGTH(p));
}

point addVectors(point p1, point p2)
{
	return createPoint(p1.x + p2.x, p1.y + p2.y, p1.z + p2.z);
}

point substractVectors(point p1, point p2)
{
	return createPoint(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z);
}

point scalarMultiply(point p, GLdouble scalar)
{
	return createPoint(p.x * scalar, p.y * scalar, p.z * scalar);
}

GLdouble dotProduct(point p1, point p2)
{
	return p1.x * p2.x + p1.y * p2.y + p1.z * p2.z;
}

point crossProduct(point p1, point p2)
{
	return createPoint( p1.y * p2.z - p1.z * p2.y,
	                    p1.z * p2.x - p1.x * p2.z,
	                    p1.x * p2.y - p1.y * p2.x );
}
