/*
 *   Polygon Reduction Demo by Stan Melax (c) 1998
 *  Permission to use any of this code wherever you want is granted..
 *  Although, please do acknowledge authorship if appropriate.
 *
 *  This module initializes the bunny model data and calls
 *  the polygon reduction routine.  At each frame the RenderModel()
 *  routine is called to draw the model.  This module also
 *  animates the parameters (such as number of vertices to
 *  use) to show the model at various levels of detail.
 */

#include <windows.h>
#include <stdio.h>  
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <GL/gl.h>
#pragma warning(disable : 4244)

#include "vector.h"
#include "font.h"
#include "progmesh.h"
#include "rabdata.h"

extern float DeltaT;  // change in time since last frame
int   render_num;   // number of vertices to draw with
float lodbase=0.5f; // the fraction of vertices used to morph toward
float morph=1.0f;   // where to render between 2 levels of detail
List<Vector> vert;       // global list of vertices
List<tridata> tri;       // global list of triangles
List<int> collapse_map;  // to which neighbor each vertex collapses
int renderpolycount=0;   // polygons rendered in the current frame
Vector model_position;         // position of bunny
Quaternion model_orientation;  // orientation of bunny

// Note that the use of the Map() function and the collapse_map
// list isn't part of the polygon reduction algorithm.
// We just set up this system here in this module
// so that we could retrieve the model at any desired vertex count.
// Therefore if this part of the program confuses you, then
// dont worry about it.  It might help to look over the progmesh.cpp
// module first.

//       Map()
//
// When the model is rendered using a maximum of mx vertices
// then it is vertices 0 through mx-1 that are used.  
// We are able to do this because the vertex list 
// gets sorted according to the collapse order.
// The Map() routine takes a vertex number 'a' and the
// maximum number of vertices 'mx' and returns the 
// appropriate vertex in the range 0 to mx-1.
// When 'a' is greater than 'mx' the Map() routine
// follows the chain of edge collapses until a vertex
// within the limit is reached.
//   An example to make this clear: assume there is
//   a triangle with vertices 1, 3 and 12.  But when
//   rendering the model we limit ourselves to 10 vertices.
//   In that case we find out how vertex 12 was removed
//   by the polygon reduction algorithm.  i.e. which
//   edge was collapsed.  Lets say that vertex 12 was collapsed
//   to vertex number 7.  This number would have been stored
//   in the collapse_map array (i.e. collapse_map[12]==7).
//   Since vertex 7 is in range (less than max of 10) we
//   will want to render the triangle 1,3,7.  
//   Pretend now that we want to limit ourselves to 5 vertices.
//   and vertex 7 was collapsed to vertex 3 
//   (i.e. collapse_map[7]==3).  Then triangle 1,3,12 would now be
//   triangle 1,3,3.  i.e. this polygon was removed by the
//   progressive mesh polygon reduction algorithm by the time
//   it had gotten down to 5 vertices.
//   No need to draw a one dimensional polygon. :-)
int Map(int a,int mx) {
	if(mx<=0) return 0;
	while(a>=mx) {  
		a=collapse_map[a];
	}
	return a;
}

void DrawModelTriangles() {
	assert(collapse_map.num);
	renderpolycount=0;
	int i=0;
	for(i=0;i<tri.num;i++) {
		int p0= Map(tri[i].v[0],render_num);
		int p1= Map(tri[i].v[1],render_num);
		int p2= Map(tri[i].v[2],render_num);
		// note:  serious optimization opportunity here,
		//  by sorting the triangles the following "continue" 
		//  could have been made into a "break" statement.
		if(p0==p1 || p1==p2 || p2==p0) continue;
		renderpolycount++;
		// if we are not currenly morphing between 2 levels of detail
		// (i.e. if morph=1.0) then q0,q1, and q2 are not necessary.
		int q0= Map(p0,(int)(render_num*lodbase));
		int q1= Map(p1,(int)(render_num*lodbase));
		int q2= Map(p2,(int)(render_num*lodbase));
		Vector v0,v1,v2; 
		v0 = vert[p0]*morph + vert[q0]*(1-morph);
		v1 = vert[p1]*morph + vert[q1]*(1-morph);
		v2 = vert[p2]*morph + vert[q2]*(1-morph);
		glBegin(GL_POLYGON);
			// the purpose of the demo is to show polygons
			// therefore just use 1 face normal (flat shading)
			Vector nrml = (v1-v0) * (v2-v1);  // cross product
			if(0<magnitude(nrml)) {
				glNormal3fv(normalize(nrml));
			}
			glVertex3fv(v0);
			glVertex3fv(v1);
			glVertex3fv(v2);
		glEnd();
	}
}


void PermuteVertices(List<int> &permutation) {
	// rearrange the vertex list 
	List<Vector> temp_list;
	int i;
	assert(permutation.num==vert.num);
	for(i=0;i<vert.num;i++) {
		temp_list.Add(vert[i]);
	}
	for(i=0;i<vert.num;i++) {
		vert[permutation[i]]=temp_list[i];
	}
	// update the changes in the entries in the triangle list
	for(i=0;i<tri.num;i++) {
		for(int j=0;j<3;j++) {
			tri[i].v[j] = permutation[tri[i].v[j]];
		}
	}
}

void GetRabbitData(){
	// Copy the geometry from the arrays of data in rabdata.cpp into
	// the vert and tri lists which we send to the reduction routine
	int i;
	for(i=0;i<RABBIT_VERTEX_NUM;i++) {
		float *vp=rabbit_vertices[i];
		vert.Add(Vector(vp[0],vp[1],vp[2]));
	}
	for(i=0;i<RABBIT_TRIANGLE_NUM;i++) {
		tridata td;
		td.v[0]=rabbit_triangles[i][0];
		td.v[1]=rabbit_triangles[i][1];
		td.v[2]=rabbit_triangles[i][2];
		tri.Add(td);
	}
	render_num=vert.num;  // by default lets use all the model to render
}


void InitModel() {
	List<int> permutation;
	GetRabbitData();  
	ProgressiveMesh(vert,tri,collapse_map,permutation);
	PermuteVertices(permutation);
	model_position    = Vector(0,0,-3);
	Quaternion yaw(Vector(0,1,0),-3.14f/4);    // 45 degrees
	Quaternion pitch(Vector(1,0,0),3.14f/12);  // 15 degrees 
	model_orientation = pitch*yaw;
}

void StatusDraw() {
	//  Draw a slider type widget looking thing
	// to show portion of vertices being used
	float b = (float)render_num/(float)vert.num;
	float a = b*(lodbase );
	glDisable(GL_LIGHTING);
	glMatrixMode( GL_PROJECTION );
	glPushMatrix();
	glLoadIdentity();
	glOrtho(-0.15,15,-0.1,1.1,-0.1,100);
	glMatrixMode( GL_MODELVIEW );

	glPushMatrix();
	glLoadIdentity();
	glBegin(GL_POLYGON);
	glColor3f(1,0,0);
	glVertex2f(0,0);
	glVertex2f(1,0);
	glVertex2f(1,a);
	glVertex2f(0,a);
	glEnd();
	glBegin(GL_POLYGON);
	glColor3f(1,0,0);
	glVertex2f(0,a);
	glVertex2f(morph,a);
	glVertex2f(morph,b);
	glVertex2f(0,b);
	glEnd();
	glBegin(GL_POLYGON);
	glColor3f(0,0,1);
	glVertex2f(morph,a);
	glVertex2f(1,a);
	glVertex2f(1,b);
	glVertex2f(morph,b);
	glEnd();
	glBegin(GL_POLYGON);
	glColor3f(0,0,1);
	glVertex2f(0,b);
	glVertex2f(1,b);
	glVertex2f(1,1);
	glVertex2f(0,1);
	glEnd();
	glPopMatrix();
	glMatrixMode( GL_PROJECTION );
	glPopMatrix();
	glMatrixMode( GL_MODELVIEW );
}

/*
 *  The following is just a quick hack to animate
 *  the object through various polygon reduced versions.
 */
struct keyframethings {
	float t;   // timestamp
	float n;   // portion of vertices used to start
	float dn;  // rate of change in "n"
	float m;   // morph value
	float dm;  // rate of change in "m"
} keys[]={
	{0 ,1   ,0 ,1, 0},
	{2 ,1   ,-1,1, 0},
	{10,0   ,1 ,1, 0},
	{18,1   ,0 ,1, 0},
	{20,1   ,0 ,1,-1},
	{24,0.5 ,0 ,1, 0},
	{26,0.5 ,0 ,1,-1},
	{30,0.25,0 ,1, 0},
	{32,0.25,0 ,1,-1},
	{36,0.125,0,1, 0},
	{38,0.25,0 ,0, 1},
	{42,0.5 ,0 ,0, 1},
	{46,1   ,0 ,0, 1},
	{50,1   ,0 ,1, 0},
};
void AnimateParameters() {
	static float time=0;  // global time - used for animation
	time+=DeltaT;
	if(time>=50) time=0;  // repeat cycle every so many seconds
	int k=0;
	while(time>keys[k+1].t) {
		k++;
	}
	float interp = (time-keys[k].t)/(keys[k+1].t-keys[k].t);
	render_num = vert.num*(keys[k].n + interp*keys[k].dn);
	morph    = keys[k].m + interp*keys[k].dm;
	morph = (morph>1.0f) ? 1.0f : morph;  // clamp value
	if(render_num>vert.num) render_num=vert.num;
	if(render_num<0       ) render_num=0;
}

void RenderModel() {
	AnimateParameters();
	
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glColor3f(1,1,1);
	glPushMatrix();
	glTranslatef(model_position.x,model_position.y,model_position.z);
	// Rotate by quaternion: model_orientation
	Vector axis=model_orientation.axis();
	float angle=model_orientation.angle()*180.0f/3.14f;
	glRotatef(angle,axis.x,axis.y,axis.z);
	DrawModelTriangles();
	StatusDraw();
	glPopMatrix();

	char buf[256];
	sprintf(buf,"Polys: %d  Vertices: %d ",renderpolycount,render_num);
	if(morph<1.0) {
		sprintf(buf+strlen(buf),"<-> %d  morph: %4.2f ",
		        (int)(lodbase *render_num),morph); 
	}
	PostString(buf,0,-2,5);
}


// ************************ vim: set sw=4 sts=4 et: ************************ //
