/*
 * Copyright (C) 2004 Clemens Fuchslocher
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include <math.h>

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

#include "GlCompileVisitor.h"


namespace nsSceneGraph {

GlCompileVisitor::~GlCompileVisitor(void)
{
	DEBUG_MESSAGE("GlSmoothVisitor::~GlSmoothVisitor()");
}


void GlCompileVisitor::visit(Group *group)
{
	DEBUG_MESSAGE("GlCompileVisitor::visit(Group *group)");
}


void GlCompileVisitor::visit(Material *material)
{
	DEBUG_MESSAGE("GlCompileVisitor::visit(Material *material)");

	if (material->isNodeCompiled())
	{
		return;
	}

	material->setCompiledStatus(true);
	material->setCompiledDisplayList(glGenLists(1));
	glNewList(material->getCompiledDisplayList(), GL_COMPILE);

	if (material->diffuseColor[0] != -1)
	{
		GLfloat diffuse[] = {
			material->diffuseColor[0],
			material->diffuseColor[1],
			material->diffuseColor[2],
			material->diffuseColor[3]
		};

		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
	}
	else
	{ // OpenGL defaults
		GLfloat glDiffuse[] = { 0.8, 0.8, 0.8, 1.0 };
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, glDiffuse);
	}

	if (material->emissiveColor[0] != -1)
	{
		GLfloat emissive[] = {
			material->emissiveColor[0],
			material->emissiveColor[1],
			material->emissiveColor[2],
			material->emissiveColor[3]
		};

		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emissive);
	}
	else
	{ // OpenGL defaults
		GLfloat glEmission[] = { 0.0, 0.0, 0.0, 1.0 };
		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, glEmission);
	}

	if (material->specularColor[0] != -1)
	{
		GLfloat specular[] = {
			material->specularColor[0],
			material->specularColor[1],
			material->specularColor[2],
			material->specularColor[3]
		};

		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
	}
	else
	{ // OpenGL defaults
		GLfloat glSpecular[] = { 0.0, 0.0, 0.0, 1.0 };
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, glSpecular);
	}

	if (material->ambientColor[0] != -1)
	{
		GLfloat ambient[] = {
			material->ambientColor[0],
			material->ambientColor[1],
			material->ambientColor[2],
			material->ambientColor[3]
		};

		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
	}
	else
	{ // OpenGL defaults
		GLfloat glAmbient[] = { 0.2, 0.2, 0.2, 1.0 };
		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, glAmbient);
	}

	if (material->shininess != -1)
	{
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material->shininess);
	}
	else
	{ // OpenGL defaults
		GLfloat glShininess = 0.0;
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, glShininess);
	}

	glEndList();

	GL_ERROR_CHECK
}


void GlCompileVisitor::visit(Transform *transform)
{
	DEBUG_MESSAGE("GlCompileVisitor::visit(Transform *transform)");

	if (transform->isNodeCompiled())
	{
		return;
	}

	transform->setCompiledStatus(true);
	transform->setCompiledDisplayList(glGenLists(1));
	glNewList(transform->getCompiledDisplayList(), GL_COMPILE);

	bool center = ((transform->center[0] != 0.0) || (transform->center[1] != 0.0) || (transform->center[2] != 0.0));

	if (center)
	{
		glTranslatef(transform->center[0], transform->center[1], transform->center[2]);
	}

	if ((transform->translation[0] != 0.0) || (transform->translation[1] != 0.0) || (transform->translation[2] != 0.0))
	{
		glTranslatef(transform->translation[0], transform->translation[1], transform->translation[2]);
	}

	if ((transform->rotation[0] != 0.0) || (transform->rotation[1] != 0.0) || (transform->rotation[2] != 0.0))
	{
		glRotatef(transform->rotation[3], transform->rotation[0], transform->rotation[1], transform->rotation[2]);
	}

	if (center)
	{
		glTranslatef(-transform->center[0], -transform->center[1], -transform->center[2]);
	}

	glEndList();

	GL_ERROR_CHECK
}


Vector3F GlCompileVisitor::calculateNormalizedNormalVector(Vertex3F *a, Vertex3F *b, Vertex3F *c)
{
	Vector3F u(
		b->x - a->x,
		b->y - a->y,
		b->z - a->z
	);

	Vector3F v(
		c->x - a->x,
		c->y - a->y,
		c->z - a->z
	);

	Vector3F n(
		u.y * v.z - u.z * v.y,
		u.z * v.x - u.x * v.z,
		u.x * v.y - u.y * v.x
	);

	double magnitude = sqrt(n.x * n.x + n.y * n.y + n.z * n.z);
	if (magnitude == 0)
	{
		return n;
	}

	Vector3F normalized(
		1 / magnitude * n.x,
		1 / magnitude * n.y,
		1 / magnitude * n.z
	);

	return normalized;
}


void GlCompileVisitor::calculateOneNormalForEachFace(VertexArray *array, std::vector<Vector3F> *normals)
{
	normals->reserve(array->vertexIndex.size());

	int end = array->vertexIndex.size();
	for (int n = 0; n < end; n++)
	{
		Vertex3F a(
			array->vertexArray[array->vertexIndex[n].a].x,
			array->vertexArray[array->vertexIndex[n].a].y,
			array->vertexArray[array->vertexIndex[n].a].z
		);

		Vertex3F b(
			array->vertexArray[array->vertexIndex[n].b].x,
			array->vertexArray[array->vertexIndex[n].b].y,
			array->vertexArray[array->vertexIndex[n].b].z
		);

		Vertex3F c(
			array->vertexArray[array->vertexIndex[n].c].x,
			array->vertexArray[array->vertexIndex[n].c].y,
			array->vertexArray[array->vertexIndex[n].c].z
		);

		normals->push_back(this->calculateNormalizedNormalVector(&a, &b, &c));
	}
}


}

