/**
 * main.cpp
 *
 * Computer Graphics 2
 * A simple ray tracer
 * 12/20/08
 *
 * Authors:
 * 	leaf corcoran
 * 	adam damiano
 *
 */

#include <stdio.h>
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>

using namespace std;

#define WIDTH 400 
#define HEIGHT 400 

struct pixel {
	unsigned char r, g, b;
};

struct point {
	float x, y, z;
};

pixel white = {255,255,255};
pixel blue = {0,0,255};
pixel green = {0,255,0};
pixel black = {0,0,0};
pixel dbg = {255,0,100}; // default background

/**
 * A vector 
 */
class Vector
{
public:
	float x,y,z;
	/**
	 * create a new vector from two points
	 */
	Vector(float x1, float y1, float z1,
		float x2, float y2, float z2)
	{
		x = x2 - x1;
		y = y2 - y1;
		z = z2 - z1;
	}

	Vector(float x, float y, float z) 
		: x(x), y(y), z(z)
	{
		// ~
	}

	/**
	 * return the length of this vector
	 */
	float length()
	{
		return sqrt(x*x + y*y + z*z);
	}

	/**
	 * calculate the dot product
	 */
	float dot(Vector v)
	{
		return (x*v.x) + (y*v.y) + (z*v.z);
	}

	/**
	 * return this vector normalized
	 */
	Vector normalize()
	{
		float l = length();
		return Vector(x/l, y/l, z/l);
	}

	void print()
	{
		cout << "Vector(" << x << ", " << y << ", " << z << ")" << endl;
	}
};

/**
 * A ray consists of an origin point
 * and a direction vector
 */
class Ray
{
public:
	// coordinates of the origin
	int x0,y0,z0;
	// direction of the ray
	Vector direction;	

	Ray(int ox, int oy, int oz, Vector dir)
		: x0(ox), y0(oy), z0(oz), direction(dir)
	{
		// ~	
	}	

	// get the coordinate at a certain t value
	point getPoint(float t)
	{
		point p = { 
			x0 + (direction.x * t),
			y0 + (direction.y * t),
			z0 + (direction.z * t)
		};
			
		return p;
	}
	
	void print()
	{
		cout << "Ray: o(" << x0 << ", " << y0 << ", " << z0 << ") d(" <<
			direction.x << ", " << direction.y << ", " << 
			direction.z << ")" << endl;
	}
};


/**
 * an object is something in the scene that
 * can collide with a ray via the intersect
 * function
 */
class Object
{
public:
	// color of the object
	pixel color;

	// returns the t value for the ray intersects
	// t is negative 1 if there is no intersection or it is behind
	virtual float intersect(Ray r) 
	{
		return -1;
	}
};


/** 
 * a plane is a type of object with an origin
 * point and a normal vector
 * all planes are truncated to certain x values 
 * for the time being
 */
class Plane : public Object
{
public:
	// a point on the plane
	float x,y,z;	
	// the normal vector
	Vector n;

	float max_x, min_x;

	Plane(float x, float y, float z, Vector n, pixel c)
		: x(x), y(y), z(z), n(n)
	{
		color = c;

		min_x = -1;
		max_x = 2;

	}

	virtual float intersect(Ray r)
	{
		// vector of the point on the plane
		Vector a(x,y,z);

		float d = a.dot(n);

		float denom = n.dot(r.direction);
		if (denom == 0) {
			// parallel to plane
			return -1;
		}

		float t = (n.dot(Vector(r.x0, r.y0, r.z0)) + d) / denom;

		// lets trim the edge of the plane to make it more turner whitted
		point i = r.getPoint(t);
		if (i.x < min_x || i.x > max_x) return -1;



		return t;
	}

};

/**
 * a sphere is a type of object with an origin
 * and a radius
 */
class Sphere : public Object
{
public:
	// center
	float x,y,z;
	// radius
	float r;

	Sphere(float x, float y, float z, float r, pixel c)
		: x(x), y(y), z(z), r(r)
	{
		color = c;
	}
	
	virtual float intersect(Ray ray)
	{
		float xx = ray.x0 - x;
		float yy = ray.y0 - y;
		float zz = ray.z0 - z;

		Vector & rd = ray.direction;

		// calculate the B value
		float b = 2 * ((rd.x*xx) + (rd.y*yy) + (rd.z*zz));	
		float c = (xx*xx) + (yy*yy) + (zz*zz) - (r*r); 

		// see where the vector goes
		float com = (b*b) - (4*c);
		if (com < 0)
			return -1; // no intersection

		float t1 = (-b + sqrt(com))/2;
		float t2 = (-b - sqrt(com))/2;

		if (t1 < 0 && t2 < 0)
			return -1; // it is behind us

		if (t1 < 0)
			return t2;

		if (t2 < 0)
			return t1;

		// both are positive, return lesser of two
		return t1 < t2 ? t1 : t2;
	}
};

/**
 * the world contains all of our objects and 
 * it also spawns the rays
 *
 */
class World
{
public:
	// the screen raster data
	pixel screen[WIDTH*HEIGHT];
	// background color for missed rays
	pixel background; 

	// camera point of convergence
	point c;


	// all the objects in the world
	vector<Object*> objects;

	World()
	{
		// default background and camera
		background = dbg;
		c.x = 0; 
		c.y = 0;
		c.z = -1;

		// clear the screen with the dbg
		for (int i = 0; i < WIDTH * HEIGHT; i++)
			screen[i] = background;


		Object *o = new Sphere(0,0,1,0.5, blue);
		objects.push_back(o);
		o = new Sphere(0.5,-0.5,2,0.5, green);
		objects.push_back(o);
		o = new Plane(0, -1, 0, Vector(0,1,0), black);
		objects.push_back(o);
		
	}
	
	/** spawn rays for each pixel in the screen and find out
	 * if they collide with anything
	 */
	void render() {


		/**
		Ray r(c.x, c.y, c.z, Vector(0,0,1));
		r.print();
		float t = s.intersect(r);
		cout << t << endl;
		*/



		for (int y = 0; y < HEIGHT; y++) 
		{
			for (int x = 0; x < WIDTH; x++)
			{
				pixel & me = screen[y*HEIGHT + x];

				// find the offsets in x and y directions
				// our viewport is 2x2 centered on origin

				float ox = 2*(1.0*x/WIDTH) -1;
				float oy = 2*(1.0*y/HEIGHT) -1;


				Vector v(c.x, c.y, c.z, 
							ox, oy, 0);

				Ray r(c.x, c.y, c.z, v.normalize());
				
				float t = 9999;
				Object *winner = NULL;
				for (vector<Object*>::iterator i = objects.begin();
					i != objects.end(); i++)
				{
					float tt = (*i)->intersect(r);
					if (tt > 0 && tt < t) {
						t = tt;
						winner = *i;
					}
					
				}

				if (winner)
					me = winner->color;

			}
		}

		// draw the screen
		glRasterPos2i(0,0);
		glDrawPixels(WIDTH, HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, screen);

	}
};


World w;

void init()
{
	glClearColor (0.3, 0.1, 0.1, 0.0);
}

void render()
{
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// render the world
	w.render();

	glutSwapBuffers();
}


void reshape(int w, int h)
{
	glViewport (0, 0, (GLsizei) w, (GLsizei) h);
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0,WIDTH,0,HEIGHT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

static void Key(unsigned char key, int x, int y)
{
	switch (key) {
		case 27:
			exit(0);
	}
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize (WIDTH, HEIGHT); 
	glutCreateWindow ("Ray Tracer");

	init();

	glutDisplayFunc(render); 
	// glutIdleFunc(render);
	glutReshapeFunc(reshape);

	glutKeyboardFunc(Key);
	glutMainLoop();
	return 0;
}

