/* 
 * Computer Graphics, Assignment, Triangle Rasterization
 * Filename ........ trirast.c
 * Description ..... Implements triangle rasterization
 * Date ............ 11.08.2008
 * Created by ...... Paul Melis
 *
 * Student name .... Sander van Veen & Taddeus Kroes
 * Student email ... sandervv@gmail.com & taddeuskroes@hotmail.com
 * Collegekaart .... 6167969 & 6054129
 * Date ............ 09.09.2010
 */

#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include <assert.h>

#include "types.h"

#define MIN(x, y, z) fminf(fminf(x, y), z)
#define MAX(x, y, z) fmaxf(fmaxf(x, y), z)

#define RAST(X, Y, a, b) ((y##a-y##b)*X+(x##b-x##a)*Y+x##a*y##b-x##b*y##a)

#define X_DIFF(a, b) (y##a-y##b)
#define Y_DIFF(a, b) (x##b-x##a - (x-x_min)*(y##a-y##b))

#define INIT_DELTA_RAST(a, b, c) {\
x_start = x##c; \
y_middle = y##b; \
\
dab = (x##b-x##a) / (y##b-y##a); \
dac = (x##c-x##a) / (y##c-y##a); \
dbc = (x##c-x##b) / (y##c-y##b); \
}

#define SWAP_X_AND_Y(a, b) \
{\
	x = x##a; \
	x##a = x##b; \
	x##b = x; \
	\
	x = y##a; \
	y##a = y##b; \
	y##b = x; \
}

/*
 * Rasterize a single triangle.
 * The triangle is specified by its corner coordinates
 * (x0,y0), (x1,y1) and (x2,y2).
 * The triangle is drawn in color (r,g,b).
 */

void draw_triangle(float x0, float y0, float x1, float y1, float x2, float y2,
    byte r, byte g, byte b)
{
	GLint x, y;
	GLfloat alpha, beta, gamma;
	
	// calculate bounding box
	GLint x_min = floor(MIN(x0, x1, x2)),
	      x_max = ceil(MAX(x0, x1, x2));
	
	GLint y_min = floor(MIN(y0, y1, y2)),
	      y_max = ceil(MAX(y0, y1, y2));
	
	// Precalculated vars        
	GLfloat rast_alpha = RAST(x0, y0, 1, 2),
	        rast_beta = RAST(x1, y1, 2, 0),
	        rast_gamma = RAST(x2, y2, 0, 1),
	        
	        rast_alpha_offset = rast_alpha * RAST(-1, -1, 1, 2),
	        rast_beta_offset = rast_beta * RAST(-1, -1, 2, 0),
	        rast_gamma_offset = rast_gamma * RAST(-1, -1, 0, 1);
	
	for( y = y_min; y <= y_max; y++ )
	{
		for( x = x_min; x <= x_max; x++ ) 
		{
			alpha = RAST(x, y, 1, 2) / rast_alpha;
			beta = RAST(x, y, 2, 0) / rast_beta;
			gamma = RAST(x, y, 0, 1) / rast_gamma;
			
			if( alpha >= 0 && beta >= 0 && gamma >= 0
			    && ( (alpha > 0 || rast_alpha_offset > 0)
			         && (beta > 0 || rast_beta_offset > 0)
			         && (gamma > 0 || rast_gamma_offset > 0) ) )
			{
				PutPixel(x, y, r, g, b);
			}
		}
	}
}

void draw_triangle_optimized(float x0, float y0, float x1, float y1,
    float x2, float y2, byte r, byte g, byte b)
{
	// calculate bounding box
	GLint x_min = floor(MIN(x0, x1, x2)),
	      x_max = ceil(MAX(x0, x1, x2));
	
	GLint y_min = floor(MIN(y0, y1, y2)),
	      y_max = ceil(MAX(y0, y1, y2));
	
	GLint x, y, drawing;
	
	// Precalculated variables
	GLfloat rast_alpha = RAST(x0, y0, 1, 2),
	        rast_beta = RAST(x1, y1, 2, 0),
	        rast_gamma = RAST(x2, y2, 0, 1),
	        
	        rast_alpha_div = 1 / rast_alpha,
	        rast_beta_div = 1 / rast_beta,
	        rast_gamma_div = 1 / rast_gamma,
	        
	        rast_alpha_offset = rast_alpha * RAST(-1, -1, 1, 2),
	        rast_beta_offset = rast_beta * RAST(-1, -1, 2, 0),
	        rast_gamma_offset = rast_gamma * RAST(-1, -1, 0, 1);
	
	// Initialize incremental multipliers at (0,0) of bounding box
	GLfloat alpha = RAST(x_min, y_min, 1, 2),
	        beta = RAST(x_min, y_min, 2, 0),
	        gamma = RAST(x_min, y_min, 0, 1),
	        k, l, m;
	
	// Loop through the triangle's bounding box
	for( y = y_min; y <= y_max; y++ )
	{
		drawing = 0;
		
		for( x = x_min; x <= x_max; x++ )
		{
			if( (k = alpha * rast_alpha_div) >= 0
			    && (l = beta * rast_beta_div) >= 0
			    && (m = gamma * rast_gamma_div) >= 0
			    && (k > 0 || rast_alpha_offset > 0)
			    && (l > 0 || rast_beta_offset > 0)
			    && (m > 0 || rast_gamma_offset > 0) )
			{
				PutPixel(x, y, r, g, b);
				drawing = 1;
			}
			else if( drawing )
				break;
			
			// Increment the multipliers horizontally
			alpha += X_DIFF(1, 2);
			beta += X_DIFF(2, 0);
			gamma += X_DIFF(0, 1);
		}
		
		// Increment the multipliers vertically
		alpha += Y_DIFF(1, 2);
		beta += Y_DIFF(2, 0);
		gamma += Y_DIFF(0, 1);
	}
}


void draw_triangle_optimized_incremental(float x0, float y0, float x1, float y1,
    float x2, float y2, byte r, byte g, byte b)
{
	// calculate bounding box
	GLint x_min = floor(MIN(x0, x1, x2)),
	      x_max = ceil(MAX(x0, x1, x2));
	
	GLint y_min = floor(MIN(y0, y1, y2)),
	      y_max = ceil(MAX(y0, y1, y2));
	
	GLint x, y;
	
	GLfloat x_start, x_stop;
	
	GLfloat dab, dac, dbc;
	
	// FIXME: temporary initializers
	dab = 0;
	dac = 0;
	dbc = 0;
	
	GLfloat y_middle = 0;
	
	// TODO: implement rast offset
	
	//if( !(r == 0 && g == 255 && b == 0) )
	//	return;
	
	// If the triangle has two corners at the same y coordinate and the third
	// corner is at the top, the variables x_start and x_stop cannot be equal.
	//if( ( y0 == y1 && y2 < y0 ) || ( y0 == y2 && y1 < y0 )
	//    || ( y1 == y2 && y0 < y1 ) )
	//{
	if( ( y0 == y1 ) || ( y0 == y2 )
	    || ( y1 == y2 ) )
	{
		// TODO: handle each case in the condition above
		/*
		x_start = floor(MIN(x0, x1, x2));
		x_stop = ceil(MAX(x0, x1, x2));
		
		dab = 0;
		dac = 0;
		dbc = 0;
		
		y_middle = 0;
		*/
		
		//printf("printf..\n");
		
		return;
	}
	else
	{
		//printf("(%f,%f), (%f,%f), (%f,%f)\n", x0, y0, x1, y1, x2, y2);
	
		// Sort the three points by their x coordinate
		
		/*
		if( x0 > x1 )
			SWAP_X_AND_Y(0, 1);
		
		if( x1 > x2 )
			SWAP_X_AND_Y(1, 2);
		
		if( x0 > x2 )
			SWAP_X_AND_Y(0, 2);
		
		assert( x0 < x1 && x1 < x2 && x0 < x2 );
		*/
		
		// Sort the three points by their y coordinate
		
		if( y2 > y1 )
		{
			if( y1 > y0 )
			{
				//assert(y2 > y1 && y1 > y0); // y2 > y1 > y0
				
				INIT_DELTA_RAST(2, 1, 0);
			}
			else if( y0 > y1 )
			{
				//assert(y2 > y1 && y0 > y1); // y2 > y0 > y1
				
				INIT_DELTA_RAST(2, 0, 1);
			}
			else
			{
				//assert(y2 > y1 && y0 > y2); // y0 > y2 > y1
				
				INIT_DELTA_RAST(0, 2, 1);
			}
		}
		else if( y2 > y0 )
		{
			//assert(y1 > y2 && y2 > y0 && y1 > y0 ); // y1 > y2 > y0
			
			INIT_DELTA_RAST(1, 2, 0);
		}
		else if( y1 > y0 )
		{
			//assert(y1 > y0 && y0 > y2); // y1 > y0 > y2
			
			INIT_DELTA_RAST(1, 0, 2);
		}
		else
		{
			//assert(y0 > y1 && y1 > y2); // y0 > y1 > y2
			
			INIT_DELTA_RAST(0, 1, 2);
		}
		
		x_stop = x_start;
	}
	
	/*
	printf("(%f,%f), (%f,%f), (%f,%f)\n", x0, y0, x1, y1, x2, y2);
	printf("x_start: %f, x_stop: %f\n", x_start, x_stop);
	printf("x_min: %d, x_max: %d\n", x_min, x_max);
	printf("dab: %f, dac: %f, dbc: %f\n", dab, dac, dbc);
	printf("r: %d, g: %d, b: %d\n", r, g, b);
	puts("--------------------");
	*/
	
	// Loop through the triangle's bounding box
	for( y = y_min; y <= y_max; y++ )
	{
		for( x = round(x_start); x <= round(x_stop); x++ )
		{
			//if( x >= x_min && x <= x_max )
			PutPixel(x, y, r, g, b);
		}
	
		// Increment
		if( dbc > dac )
		{
			x_stop += y < y_middle ? dbc : dab;
			x_start += dac;
		}
		else
		{
			x_start += y < y_middle ? dbc : dab;
			x_stop += dac;
		}
		
		//printf("y: %d => x_start: %f, x_stop: %f\n", y+1, x_start, x_stop);
		//assert( x_stop >= x_start && x_start >= x_min && x_stop <= x_max );
	}
}
