
#include <math.h>

#include "rc_graphics.h"
#include "rc_globals.h"
#include "bounds3d.h"
#include "bounds4d.h"
#include "ray.h"
#include "camera.h"
#include "frustum.h"
#include "lights.h"

#include <GL/gl.h>
#include <GL/glu.h>


void InitRCGraphics(int ray_display_list_id) {

// Create ray display list:

	GLUquadricObj *qobj;
	qobj = gluNewQuadric();

	glNewList(ray_display_list_id, GL_COMPILE);

	gluQuadricOrientation(qobj, GLU_OUTSIDE);
	gluCylinder(qobj, 0.05, 0.05, 0.9, 16, 1);
	gluQuadricOrientation(qobj, GLU_INSIDE);
	gluDisk(qobj, 0, 0.05, 16, 1);

	glTranslatef(0, 0, 0.85);

	gluQuadricOrientation(qobj, GLU_OUTSIDE);
	gluCylinder(qobj, 0.15, 0.005, 0.15, 16, 1);
	gluQuadricOrientation(qobj, GLU_INSIDE);
	gluDisk(qobj, 0, 0.15, 16, 1);

	glEndList();

	gluDeleteQuadric(qobj);
}

void SetupLighting(Lights &L) {

	float dif[4];
	float pos[4];

	glEnable(GL_LIGHTING);

	for (int i=0; i<L.NumLights(); i++) {
		dif[0] = L[i]._color[0];
		dif[1] = L[i]._color[1];
		dif[2] = L[i]._color[2];
		dif[3] = 1;
		glLightfv(GL_LIGHT0+i, GL_DIFFUSE, dif);
		pos[0] = L[i]._direction.x();
		pos[1] = L[i]._direction.y();
		pos[2] = L[i]._direction.z();
		pos[3] = 0;
		glLightfv(GL_LIGHT0+i, GL_POSITION, pos);
		glEnable(GL_LIGHT0+i);
	}
}

void DrawBoxFrame(const Bounds3d &B) {

	glBegin(GL_LINE_STRIP);
	glVertex3f(B.x1(), B.y1(), B.z1());
	glVertex3f(B.x2(), B.y1(), B.z1());
	glVertex3f(B.x2(), B.y2(), B.z1());
	glVertex3f(B.x1(), B.y2(), B.z1());
	glVertex3f(B.x1(), B.y1(), B.z1());
	glVertex3f(B.x1(), B.y1(), B.z2());
	glVertex3f(B.x2(), B.y1(), B.z2());
	glVertex3f(B.x2(), B.y2(), B.z2());
	glVertex3f(B.x1(), B.y2(), B.z2());
	glVertex3f(B.x1(), B.y1(), B.z2());
	glEnd();

	glBegin(GL_LINES);
	glVertex3f(B.x2(), B.y1(), B.z1());
	glVertex3f(B.x2(), B.y1(), B.z2());
	glVertex3f(B.x2(), B.y2(), B.z1());
	glVertex3f(B.x2(), B.y2(), B.z2());
	glVertex3f(B.x1(), B.y2(), B.z1());
	glVertex3f(B.x1(), B.y2(), B.z2());
	glEnd();
}

void Bounds4d::Draw(int major_plane, const Bounds3d &B) {

	switch (major_plane) {

		// remember, z's are reversed for XY

		case MP_XY:
			glBegin(GL_LINE_STRIP);
			glVertex3f(B.x1() * (1.0f - _a1) + B.x2() * _a1,
					   B.y1() * (1.0f - _b1) + B.y2() * _b1,
					   B.z2());
			glVertex3f(B.x1() * (1.0f - _a2) + B.x2() * _a2,
					   B.y1() * (1.0f - _b1) + B.y2() * _b1,
					   B.z2());
			glVertex3f(B.x1() * (1.0f - _a2) + B.x2() * _a2,
					   B.y1() * (1.0f - _b2) + B.y2() * _b2,
					   B.z2());
			glVertex3f(B.x1() * (1.0f - _a1) + B.x2() * _a1,
					   B.y1() * (1.0f - _b2) + B.y2() * _b2,
					   B.z2());
			glVertex3f(B.x1() * (1.0f - _a1) + B.x2() * _a1,
					   B.y1() * (1.0f - _b1) + B.y2() * _b1,
					   B.z2());
			glEnd();
			glBegin(GL_LINE_STRIP);
			glVertex3f(B.x1() * (1.0f - _c1) + B.x2() * _c1,
					   B.y1() * (1.0f - _d1) + B.y2() * _d1,
					   B.z1());
			glVertex3f(B.x1() * (1.0f - _c2) + B.x2() * _c2,
					   B.y1() * (1.0f - _d1) + B.y2() * _d1,
					   B.z1());
			glVertex3f(B.x1() * (1.0f - _c2) + B.x2() * _c2,
					   B.y1() * (1.0f - _d2) + B.y2() * _d2,
					   B.z1());
			glVertex3f(B.x1() * (1.0f - _c1) + B.x2() * _c1,
					   B.y1() * (1.0f - _d2) + B.y2() * _d2,
					   B.z1());
			glVertex3f(B.x1() * (1.0f - _c1) + B.x2() * _c1,
					   B.y1() * (1.0f - _d1) + B.y2() * _d1,
					   B.z1());
			glEnd();
			break;

		case MP_XZ:
			glBegin(GL_LINE_STRIP);
			glVertex3f(B.x1() * (1.0f - _a1) + B.x2() * _a1,
					   B.y2(),
					   B.z1() * (1.0f - _b1) + B.z2() * _b1);
			glVertex3f(B.x1() * (1.0f - _a2) + B.x2() * _a2,
					   B.y2(),
					   B.z1() * (1.0f - _b1) + B.z2() * _b1);
			glVertex3f(B.x1() * (1.0f - _a2) + B.x2() * _a2,
					   B.y2(),
					   B.z1() * (1.0f - _b2) + B.z2() * _b2);
			glVertex3f(B.x1() * (1.0f - _a1) + B.x2() * _a1,
					   B.y2(),
					   B.z1() * (1.0f - _b2) + B.z2() * _b2);
			glVertex3f(B.x1() * (1.0f - _a1) + B.x2() * _a1,
					   B.y2(),
					   B.z1() * (1.0f - _b1) + B.z2() * _b1);
			glEnd();
			glBegin(GL_LINE_STRIP);
			glVertex3f(B.x1() * (1.0f - _c1) + B.x2() * _c1,
					   B.y1(),
					   B.z1() * (1.0f - _d1) + B.z2() * _d1);
			glVertex3f(B.x1() * (1.0f - _c2) + B.x2() * _c2,
					   B.y1(),
					   B.z1() * (1.0f - _d1) + B.z2() * _d1);
			glVertex3f(B.x1() * (1.0f - _c2) + B.x2() * _c2,
					   B.y1(),
					   B.z1() * (1.0f - _d2) + B.z2() * _d2);
			glVertex3f(B.x1() * (1.0f - _c1) + B.x2() * _c1,
					   B.y1(),
					   B.z1() * (1.0f - _d2) + B.z2() * _d2);
			glVertex3f(B.x1() * (1.0f - _c1) + B.x2() * _c1,
					   B.y1(),
					   B.z1() * (1.0f - _d1) + B.z2() * _d1);
			glEnd();
			break;

		case MP_YZ:
			glBegin(GL_LINE_STRIP);
			glVertex3f(B.x1(),
					   B.y1() * (1.0f - _b1) + B.y2() * _b1,
					   B.z1() * (1.0f - _a1) + B.z2() * _a1);
			glVertex3f(B.x1(),
					   B.y1() * (1.0f - _b2) + B.y2() * _b2,
					   B.z1() * (1.0f - _a1) + B.z2() * _a1);
			glVertex3f(B.x1(),
					   B.y1() * (1.0f - _b2) + B.y2() * _b2,
					   B.z1() * (1.0f - _a2) + B.z2() * _a2);
			glVertex3f(B.x1(),
					   B.y1() * (1.0f - _b1) + B.y2() * _b1,
					   B.z1() * (1.0f - _a2) + B.z2() * _a2);
			glVertex3f(B.x1(),
					   B.y1() * (1.0f - _b1) + B.y2() * _b1,
					   B.z1() * (1.0f - _a1) + B.z2() * _a1);
			glEnd();
			glBegin(GL_LINE_STRIP);
			glVertex3f(B.x2(),
					   B.y1() * (1.0f - _d1) + B.y2() * _d1,
					   B.z1() * (1.0f - _c1) + B.z2() * _c1);
			glVertex3f(B.x2(),
					   B.y1() * (1.0f - _d2) + B.y2() * _d2,
					   B.z1() * (1.0f - _c1) + B.z2() * _c1);
			glVertex3f(B.x2(),
					   B.y1() * (1.0f - _d2) + B.y2() * _d2,
					   B.z1() * (1.0f - _c2) + B.z2() * _c2);
			glVertex3f(B.x2(),
					   B.y1() * (1.0f - _d1) + B.y2() * _d1,
					   B.z1() * (1.0f - _c2) + B.z2() * _c2);
			glVertex3f(B.x2(),
					   B.y1() * (1.0f - _d1) + B.y2() * _d1,
					   B.z1() * (1.0f - _c1) + B.z2() * _c1);
			glEnd();
			break;

		default:
			cerr << "Bounds4D::Draw() not finished" << endl;
	}
}

void Ray::DrawFull(float size) const {

	Vec4 Z(0, 0, 1);
	Vec4 cross;

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();

	glTranslatef(_R.x(), _R.y(), _R.z());

	Vec4FastCross3(cross, _D, Z);
	float l = cross.Length();

	if (_D.Dot3(Z) < 0) {
		if (l)
			glRotatef(-180 + asin(l / _D.Length())*180/M_PI,
					  cross.x()/l, cross.y()/l, cross.z()/l);
		else
			glRotatef(180, 1, 0, 0);
	}
	else {
		if (l)
			glRotatef(-asin(l / _D.Length())*180/M_PI,
					  cross.x()/l, cross.y()/l, cross.z()/l);
	}

	glScalef(size, size, size);
	glCallList(2);

	glPopMatrix();
}

void Ray::DrawLineToT(real max) const {

	Vec4 Z(0, 0, 1);
	Vec4 cross;

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();

	glTranslatef(_R.x(), _R.y(), _R.z());

	Vec4FastCross3(cross, _D, Z);
	float l = cross.Length();

	if (_D.Dot3(Z) < 0) {
		if (l)
			glRotatef(-180 + asin(l / _D.Length())*180/M_PI,
					  cross.x()/l, cross.y()/l, cross.z()/l);
		else
			glRotatef(180, 1, 0, 0);
	}
	else {
		if (l)
			glRotatef(-asin(l / _D.Length())*180/M_PI,
					  cross.x()/l, cross.y()/l, cross.z()/l);
	}

/*
	if (_t > 1) {
		float t = (_t < max) ? _t : max;
		glBegin(GL_LINE_STRIP);
		for (int i=0; i<=10; i++)
			glVertex3f(0, 0, (t*i/10.0f) + (1-i/10.0f));
		glEnd();
	}
*/

	if (_t > 0) {
		float t = (_t < max) ? _t : max;
		glBegin(GL_LINE_STRIP);
		for (int i=0; i<=10; i++)
			glVertex3f(0, 0, (t*i/10.0f));
		glEnd();
	}

	glPopMatrix();
}

void Camera::Draw() {

	glBegin(GL_LINES);
	glVertex3f(_R.x(), _R.y(), _R.z());
	glVertex3f(_R.x()+_D.x()*_dist, _R.y()+_D.y()*_dist, _R.z()+_D.z()*_dist);
	glVertex3f(_R.x(), _R.y(), _R.z());
	glVertex3f(c00().x(), c00().y(), c00().z());
	glVertex3f(_R.x(), _R.y(), _R.z());
	glVertex3f(c10().x(), c10().y(), c10().z());
	glVertex3f(_R.x(), _R.y(), _R.z());
	glVertex3f(c11().x(), c11().y(), c11().z());
	glVertex3f(_R.x(), _R.y(), _R.z());
	glVertex3f(c01().x(), c01().y(), c01().z());
	glEnd();

	glBegin(GL_LINE_LOOP);
	glVertex3f(c00().x(), c00().y(), c00().z());
	glVertex3f(c10().x(), c10().y(), c10().z());
	glVertex3f(c11().x(), c11().y(), c11().z());
	glVertex3f(c01().x(), c01().y(), c01().z());
	glEnd();
}

void Camera::DrawImage(GLPic &P) {

	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexImage2D(GL_TEXTURE_2D,
				 0,
				 3,
				 P.x(),
				 P.y(),
				 0,

#ifdef GL_ABGR_EXT
				 GL_ABGR_EXT,
#else
				 GL_RGB,
#endif

				 GL_UNSIGNED_BYTE,
				 P.GetBuffer());

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
	glEnable(GL_TEXTURE_2D);

	glShadeModel(GL_FLAT);
	glBegin(GL_QUADS);
	glTexCoord2f(0, 0);
	glVertex3f(c00().x(), c00().y(), c00().z());
	glTexCoord2f(1, 0);
	glVertex3f(c10().x(), c10().y(), c10().z());
	glTexCoord2f(1, 1);
	glVertex3f(c11().x(), c11().y(), c11().z());
	glTexCoord2f(0, 1);
	glVertex3f(c01().x(), c01().y(), c01().z());
	glEnd();
	glShadeModel(GL_SMOOTH);

	glDisable(GL_TEXTURE_2D);
}

#ifdef COMPL_FRUSTUM
void Frustum::Draw() {

	const Vec4 _R = _camera->R();
	const Vec4 _D = _camera->D();
	real _dist = _camera->Dist();

	Vec4 c00, c10, c01, c11;

	CalcCorners(c00, c10, c01, c11);

	glBegin(GL_LINES);
	glVertex3f(_R.x(), _R.y(), _R.z());
	glVertex3f(_R.x()+_D.x()*_dist, _R.y()+_D.y()*_dist, _R.z()+_D.z()*_dist);
	glVertex3f(_R.x(), _R.y(), _R.z());
	glVertex3f(c00.x(), c00.y(), c00.z());
	glVertex3f(_R.x(), _R.y(), _R.z());
	glVertex3f(c10.x(), c10.y(), c10.z());
	glVertex3f(_R.x(), _R.y(), _R.z());
	glVertex3f(c11.x(), c11.y(), c11.z());
	glVertex3f(_R.x(), _R.y(), _R.z());
	glVertex3f(c01.x(), c01.y(), c01.z());
	glEnd();

	glBegin(GL_LINE_LOOP);
	glVertex3f(c00.x(), c00.y(), c00.z());
	glVertex3f(c10.x(), c10.y(), c10.z());
	glVertex3f(c11.x(), c11.y(), c11.z());
	glVertex3f(c01.x(), c01.y(), c01.z());
	glEnd();
}

void Frustum::DrawNormals() {

	real dist = _camera->Dist();

	Vec4 c00, c10, c01, c11, v;
	CalcCorners(c00, c10, c01, c11);

	glBegin(GL_LINES);

	Vec4FastAverage(v, c00, c01);
	glVertex3f(v.x(), v.y(), v.z());
	Vec4FastAddScale(v, v, _N_in_left, -dist);
	glVertex3f(v.x(), v.y(), v.z());

	Vec4FastAverage(v, c10, c11);
	glVertex3f(v.x(), v.y(), v.z());
	Vec4FastAddScale(v, v, _N_in_right, -dist);
	glVertex3f(v.x(), v.y(), v.z());

	Vec4FastAverage(v, c00, c10);
	glVertex3f(v.x(), v.y(), v.z());
	Vec4FastAddScale(v, v, _N_in_bottom, -dist);
	glVertex3f(v.x(), v.y(), v.z());

	Vec4FastAverage(v, c01, c11);
	glVertex3f(v.x(), v.y(), v.z());
	Vec4FastAddScale(v, v, _N_in_top, -dist);
	glVertex3f(v.x(), v.y(), v.z());

	glEnd();
}
#endif

void DisplayParams4d(int major_plane,
					 real a, real b, real c, real d,
					 const Bounds3d &B) {

	Vec4 v;

	glMatrixMode(GL_MODELVIEW);

	B.CalcPointOnSurface(major_plane, a, b, v, 0);
	glPushMatrix();
	glTranslatef(v.x(), v.y(), v.z());
	glScalef(0.05, 0.05, 0.05);
	glCallList(1);
	glPopMatrix();

	B.CalcPointOnSurface(major_plane, c, d, v, 1);
	glPushMatrix();
	glTranslatef(v.x(), v.y(), v.z());
	glScalef(0.05, 0.05, 0.05);
	glCallList(1);
	glPopMatrix();
}

void DrawMinMaxVolume(float min00, float max00,
					  float min10, float max10,
					  float min01, float max01,
					  float min11, float max11,
					  float scale) {

	glBegin(GL_LINE_STRIP);
	glVertex3f(1, min11 * scale, 1);
	glVertex3f(0, min00 * scale, 0);
	glVertex3f(1, min10 * scale, 0);
	glVertex3f(1, min11 * scale, 1);
	glVertex3f(0, min01 * scale, 1);
	glVertex3f(0, min00 * scale, 0);
	glVertex3f(0, max00 * scale, 0);
	glVertex3f(1, max10 * scale, 0);
	glVertex3f(1, max11 * scale, 1);
	glVertex3f(0, max01 * scale, 1);
	glVertex3f(0, max00 * scale, 0);
	glVertex3f(1, max11 * scale, 1);
	glEnd();

	glBegin(GL_LINES);
	glVertex3f(1, min10 * scale, 0);
	glVertex3f(1, max10 * scale, 0);
	glVertex3f(0, min01 * scale, 1);
	glVertex3f(0, max01 * scale, 1);
	glVertex3f(1, min11 * scale, 1);
	glVertex3f(1, max11 * scale, 1);
	glEnd();
}

void DrawDispMapWireframe(float *dmap, int size, float scale) {

	int i, j;
	float fi, fj;

	for (i=0; i<size; i++) {

		fi = float(i) / float(size-1);

		glBegin(GL_LINE_STRIP);

		for (j=0; j<size; j++) {
			fj = (float(j) / float(size-1));
			glVertex3f(fi, dmap[i + j*size] * scale, fj);
		}

		glEnd();
	}

	for (j=0; j<size; j++) {

		fj = (float(j) / float(size-1));

		glBegin(GL_LINE_STRIP);

		for (i=0; i<size; i++) {
			fi = float(i) / float(size-1);
			glVertex3f(fi, dmap[i + j*size] * scale, fj);
		}

		glEnd();
	}
}

void DrawDispMapWireframeWithDiag(float *dmap, int size, float scale) {

	int i, j;
	float fi, fj;

	for (i=0; i<size; i++) {

		fi = float(i) / float(size-1);

		glBegin(GL_LINE_STRIP);

		for (j=0; j<size; j++) {
			fj = (float(j) / float(size-1));
			glVertex3f(fi, dmap[i + j*size] * scale, fj);
		}

		glEnd();
	}

	for (j=0; j<size; j++) {

		fj = (float(j) / float(size-1));

		glBegin(GL_LINE_STRIP);

		for (i=0; i<size; i++) {
			fi = float(i) / float(size-1);
			glVertex3f(fi, dmap[i + j*size] * scale, fj);
		}

		glEnd();
	}

	for (int k=1; k<size*2; k++) {

		i = k;
		j = 0;

		if (i > (size-1)) {
			j = (k - (size-1));
			i = size-1;
		}

		glBegin(GL_LINE_STRIP);

		while ((i >= 0) && (j < size)) {
			fi = float(i) / float(size-1);
			fj = (float(j) / float(size-1));
			glVertex3f(fi, dmap[i + j*size] * scale, fj);
			i--;
			j++;
		}

		glEnd();
	}
}

void DrawDispMapSurface(float *dmap, int size, float scale) {

	int i, j;
	float fi0, fi1, fj;

	for (i=0; i<size-1; i++) {

		fi0 = float(i) / float(size-1);
		fi1 = float(i+1) / float(size-1);

		glBegin(GL_QUAD_STRIP);

		for (j=0; j<size; j++) {
			fj = (float(j) / float(size-1));
			glVertex3f(fi0, dmap[i + j*size] * scale, fj);
			glVertex3f(fi1, dmap[i+1 + j*size] * scale, fj);
		}

		glEnd();
	}
}
