/******************************************************************************\

  Copyright 1995 The University of North Carolina at Chapel Hill.
  All Rights Reserved.

  Permission to use, copy, modify and distribute this software and its
  documentation for educational, research and non-profit purposes, without
  fee, and without a written agreement is hereby granted, provided that the
  above copyright notice and the following three paragraphs appear in all
  copies.

  IN NO EVENT SHALL THE UNIVERSITY OF NORTH CAROLINA
  AT CHAPEL HILL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL,
  OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS
  SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY
  OF NORTH CAROLINA HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.


  Permission to use, copy, modify and distribute this software and its
  documentation for educational, research and non-profit purposes, without
  fee, and without a written agreement is hereby granted, provided that the
  above copyright notice and the following three paragraphs appear in all
  copies.

  THE UNIVERSITY OF NORTH CAROLINA SPECIFICALLY
  DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED
  HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF NORTH CAROLINA HAS NO OBLIGATION
S
  TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

  The authors may be contacted via:

  US Mail:             J. Cohen/M. Lin/D. Manocha/K. Ponamgi
                       Department of Computer Science
                      Sitterson Hall, CB #3175
                       University of N. Carolina
                       Chapel Hill, NC 27599-3175

  Phone:               (919)962-1749

  EMail:              {cohenj,lin,manocha,ponamgi}@cs.unc.edu


\*****************************************************************************/


/*****************************************************************************\
  sgi.c
  --
  Description : SGI graphics module using OpenGL

\*****************************************************************************/



/*----------------------------- Local Includes -----------------------------*/

#include "polytope.h"
#include "sgi.h"    

#ifdef SGI_GRAPHICS

#include <stdio.h> 
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <collision.h>

#include "my_aux.h"
#include <GL/gl.h>


/*----------------------------- Local Constants -----------------------------*/

#define WINDOW_WIDTH  640
#define WINDOW_HEIGHT 512

/*------------------------------ Local Macros -------------------------------*/

#define allocFnode (col_Fnode *) calloc(1, sizeof(col_Fnode))

#define LIST_LENGTH(pointer, count) \
        { \
              col_Fnode *safe_pointer = (pointer); \
              for ((count)=0; safe_pointer; \
                   safe_pointer=safe_pointer->next, (count)++); \
        }

/*------------------------------- Local Types -------------------------------*/

/* a single triangle fan */
typedef struct Fan
{
    col_Fnode       *verts;
    col_Fnode       *faces;
    int         num_faces;
    char        closed;
    struct Fan  *next;
} Fan;

/* a set of triangle fans for constructing a polytope */
typedef struct PolytopeFans
{
    int num_fans;
    Fan *fans;
} PolytopeFans;

/* display list ids for a polytope - one for when the polytope is colliding,
   and one for when it is not colliding */
typedef struct dlist_ids
{
   int      non_colliding_id;
   int      colliding_id;
} DisplayListIds;

/*------------------------ Local Function Prototypes ------------------------*/

static void compute_good_fans(__col_Polyhedron *poly, PolytopeFans *poly_fans);
static int create_polytope_display_list(__col_Polyhedron *ph,
					int *xform, int *col_xform,
					PolytopeFans *poly_fans);
static void create_simulation_box();


/*------------------------------ Local Globals ------------------------------*/

static PolytopeFans *poly_fans=0;       
static DisplayListIds *dlist_ids=0;

extern double cube_bound;

extern __col_Polyhedron      __col_polyhedronLibrary[1000];
extern __col_State  state;

/*---------------------------------Functions-------------------------------- */


/*****************************************************************************\
 @ compute_good_fans()
 -----------------------------------------------------------------------------
 description : This is a greedy algorithm for turning a polytope into a set of
               triangle fans, which can be rendered faster than individual
	       triangles.  I use triangle fans instead of triangle strips
	       because it didn't seem as easy to make a greedy algorithm to
	       generate a decent set of triangle strips.
 input       : 
 output      : 
 notes       : all of this assumes that all the faces of this polyhedron are
               triangles, not general polygons
\*****************************************************************************/
static void compute_good_fans(__col_Polyhedron *poly, PolytopeFans *poly_fans)
{
    PolytopeFans used_fans, unused_fans;
    col_Fnode *face, *vert, *prev_face, *edge, *face2, *face3;
    col_Fnode *vert1, *vert2, *vert3;
    Fan *fan, *max_fan, *current_fan, *prev_fan, *new_fan;
    __col_Face *orig_face;
    int   num_verts;
    
    /* first verify that all faces are triangles */
    for (face = poly->faces; face; face = face->next)
    {
	LIST_LENGTH(face->f.f->verts, num_verts);
	if (num_verts != 3) break;
    }
    if (face)
    {
	return;
    }
    
    used_fans.num_fans = 0;
    used_fans.fans = 0;

    unused_fans.num_fans = 0;
    unused_fans.fans = 0;

    /* build initial fan for each vertex */
    for (vert = poly->verts; vert; vert = vert->next)
    {
	fan = (Fan *)calloc(1, sizeof(Fan));
	fan->verts = allocFnode;
	fan->verts->f.v = vert->f.v;
	orig_face = vert->f.v->edges->f.e->fl;
	fan->faces = allocFnode;
	fan->faces->f.f = orig_face;
	fan->num_faces = 1;
	for (prev_face = fan->faces; 1; prev_face = face)
	{
	    for (edge = prev_face->f.f->edges; edge; edge = edge->next)
	    {
		if ((edge->f.e->v1 == fan->verts->f.v) &&
		    (edge->f.e->fr == prev_face->f.f))
		{
		    face = allocFnode;
		    face->f.f = edge->f.e->fl;
		    break;
		}
		else if ((edge->f.e->v2 == fan->verts->f.v) &&
			 (edge->f.e->fl == prev_face->f.f))
		{
		    face = allocFnode;
		    face->f.f = edge->f.e->fr;
		    break;
		}
	    }
	    if (face->f.f == orig_face)
	    {
		free(face);
		break;
	    }
	    prev_face->next = face;
	    fan->num_faces++;
	}
	
	fan->closed = 1;
	fan->next = unused_fans.fans;
	unused_fans.fans = fan;
	unused_fans.num_fans++;

    }


    while(unused_fans.num_fans)
    {

	/* find maximum sized unused fan */
	max_fan = unused_fans.fans;
	for (current_fan = unused_fans.fans; current_fan; current_fan =
	     current_fan->next)
	{
	    if (current_fan->num_faces > max_fan->num_faces)
		max_fan = current_fan;
	}


	/* remove fan from unused fan list */
	if (max_fan == unused_fans.fans)
	    unused_fans.fans = max_fan->next;
	else
	{
	    for (current_fan = unused_fans.fans; current_fan->next != max_fan;
		 current_fan = current_fan->next);
	    current_fan->next = max_fan->next;
	}
	unused_fans.num_fans--;

	/* add fan to used fan list */
	max_fan->next = used_fans.fans;
	used_fans.fans = max_fan;
	used_fans.num_fans++;


	/* break up any unused fans containing a face from the fan we just
	   used up */
	for (face = max_fan->faces; face; face = face->next)
	{
	    for (current_fan = unused_fans.fans; current_fan;
		 current_fan = current_fan->next)
	    {
		for (prev_face = 0, face2 = current_fan->faces;
		     face2; prev_face = face2, face2 = face2->next)
		    if (face2->f.f == face->f.f) break;

		if (!face2) continue;

		if (current_fan->num_faces == 1)
		{
		    if (unused_fans.fans == current_fan)
			unused_fans.fans = current_fan->next;
		    else
		    {
			for (prev_fan = unused_fans.fans;
			     prev_fan->next != current_fan;
			     prev_fan = prev_fan->next);
			prev_fan->next = current_fan->next;
			free(current_fan->faces);
			free(current_fan->verts);
			free(current_fan);
		    }
		    unused_fans.num_fans--;
		}
		else if (current_fan->closed)
		{
		    if (prev_face) prev_face->next = 0;
		    if (face2->next)
		    {
			if (prev_face)
			{
			    for (face3 = face2; face3->next; face3 = face3->next);
			    face3->next = current_fan->faces;
			}
			current_fan->faces = face2->next;
		    }
		    free(face2);
		    current_fan->closed = 0;
		    current_fan->num_faces--;
		}
		else if (face2 == current_fan->faces)
		{
		    current_fan->faces = face2->next;
		    free(face2);
		    current_fan->num_faces--;

		}
		else if (!face2->next)
		{
		    prev_face->next = 0;
		    free(face2);
		    current_fan->num_faces--;
		}
		else
		{
		    prev_face->next = 0;
		    LIST_LENGTH(current_fan->faces, current_fan->num_faces);
		    new_fan = (Fan *)calloc(1, sizeof(Fan));
		    new_fan->verts = allocFnode;
		    new_fan->verts->f.v = current_fan->verts->f.v;
		    new_fan->faces = face2->next;
		    LIST_LENGTH(new_fan->faces, new_fan->num_faces);
		    new_fan->next = current_fan->next;
		    current_fan->next = new_fan;
		    free(face2);
		    unused_fans.num_fans++;
		}
	    }
	}
    }

    *poly_fans = used_fans;

    /* find vertices for the fans -- assume I've sorted the fan's faces into
       CCW order and I want the vertices in CCW order */
    for (current_fan = poly_fans->fans; current_fan; current_fan =
	 current_fan->next)
    {
	vert1 = current_fan->verts;
	vert2 = allocFnode;
	vert1->next = vert2;
	face = current_fan->faces;
	if (face->f.f->verts->f.v == vert1->f.v)
	    vert2->f.v = face->f.f->verts->next->f.v; 
	else if (face->f.f->verts->next->f.v == vert1->f.v)
	    vert2->f.v = face->f.f->verts->next->next->f.v;
	else
	    vert2->f.v = face->f.f->verts->f.v;
	for (face = current_fan->faces; face; face = face->next)
	{
	    vert3 = allocFnode;
	    if (face->f.f->verts->f.v == vert1->f.v)
		vert3->f.v = face->f.f->verts->next->next->f.v;
	    else if (face->f.f->verts->next->f.v == vert1->f.v)
		vert3->f.v = face->f.f->verts->f.v;
	    else
		vert3->f.v = face->f.f->verts->next->f.v;
	    vert2->next = vert3;
	    vert2 = vert3;
	}
    }
} /** compute_good_fans() **/



/*****************************************************************************\
 @ sgi_graphics_per_library_object_init()
 -----------------------------------------------------------------------------
 description : Create triangle fan representations
 input       : 
 output      : 
 notes       :
\*****************************************************************************/
void sgi_graphics_per_library_object_init(Status *status)
{
    int i;
    
    ALLOCATE(poly_fans, PolytopeFans, status->num_lib_polytopes);
    
    for (i= 0; i < status->num_lib_polytopes; ++i)
	compute_good_fans(&(__col_polyhedronLibrary[i]), &(poly_fans[i]));

    return;
} /** End of sgi_graphics_per_library_object_init() **/

/*****************************************************************************\
 @ sgi_graphics_per_object_instance_init()
 -----------------------------------------------------------------------------
 description : Create display lists
 input       : 
 output      : 
 notes       : We only realy need a seperate display list for every instance
               because we use different random colors for each object.

	       This routine makes use of the fact that the nbody application
	       instances n objects by cycling through the m objects in the
	       library.
\*****************************************************************************/
void sgi_graphics_per_object_instance_init(Status *status)
{
    int i;
    
    ALLOCATE(dlist_ids, DisplayListIds, status->num_polytopes);

    for (i=0; i < status->num_polytopes; i++)
    {
	create_polytope_display_list(state.polytopes[i].polytope,
				     &(dlist_ids[i].non_colliding_id),
				     &(dlist_ids[i].colliding_id),
				     &(poly_fans[i%status->num_lib_polytopes]));
    }
    
    return;

} /** End of sgi_graphics_per_object_instance_init() **/

/*****************************************************************************\
 @ create_simulation_box()
 -----------------------------------------------------------------------------
 description : Create the graphics representation of the simulation volume
 input       : 
 output      : 
 notes       :
\*****************************************************************************/
static void create_simulation_box()
{
  glNewList(1,GL_COMPILE);
  
  glColor3ub(200,30,88);
  
  glBegin(GL_LINE_LOOP);
  {
      glVertex3f(-cube_bound,cube_bound,cube_bound);  
      glVertex3f(cube_bound,cube_bound,cube_bound);  
      glVertex3f(cube_bound,cube_bound,-cube_bound);  
      glVertex3f(-cube_bound,cube_bound,-cube_bound);
  }
  glEnd();
  
  glBegin(GL_LINE_LOOP);
  {
      glVertex3f(-cube_bound,-cube_bound,cube_bound);  
      glVertex3f(cube_bound,-cube_bound,cube_bound);  
      glVertex3f(cube_bound,-cube_bound,-cube_bound);  
      glVertex3f(-cube_bound,-cube_bound,-cube_bound);  
  }
  glEnd();
  
  glBegin(GL_LINES);
  {
      glVertex3f(-cube_bound,-cube_bound,-cube_bound);  
      glVertex3f(-cube_bound,cube_bound,-cube_bound);  
      glVertex3f(cube_bound,-cube_bound,-cube_bound);  
      glVertex3f(cube_bound,cube_bound,-cube_bound);  
      glVertex3f(cube_bound,-cube_bound,cube_bound);  
      glVertex3f(cube_bound,cube_bound,cube_bound);  
      glVertex3f(-cube_bound,-cube_bound,cube_bound);  
      glVertex3f(-cube_bound,cube_bound,cube_bound);  
  }
  glEnd();
  
  glEndList();
} /** End of create_simulation_box() **/


/*****************************************************************************\
 @ sgi_graphics_draw_scene()
 -----------------------------------------------------------------------------
 description : Draw out the scene
 input       : 
 output      : 
 notes       :
\*****************************************************************************/
void sgi_graphics_draw_scene(Status *status, Polytope *poly)
{
    static float   color=0.0;
    static float   delta=0.1;
    static int     first_time=1;
    int            i, j, k;
    GLfloat        matrix[16];
#ifdef SAVE_FRAMES
    static GLubyte pixels[100*100*3*sizeof(GLubyte)];
#endif
    
    glClearColor(color, color, color, 0.);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    if (first_time)
    {
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60., (double)WINDOW_WIDTH/(double)WINDOW_HEIGHT,
		       .5*cube_bound, 8.5*cube_bound);
	glTranslatef(0., 0., -3*cube_bound);

	glMatrixMode(GL_MODELVIEW);
	create_simulation_box();
	first_time = 0;
    }

    
    glLoadIdentity();
    glCallList(1);

    matrix[3] = matrix[7] = matrix[11] = 0.0;
    matrix[15] = 1.0;
    for (i=0; i< status->num_polytopes; i++)
    {
	for (j=0; j<3; j++)
	    for (k=0; k<4; k++)
		matrix[k*4+j] = poly[i].tot[j][k];

	glLoadMatrixf(matrix);

	if (poly[i].colliding != -1)
	    glCallList(dlist_ids[i].colliding_id);
	else
	    glCallList(dlist_ids[i].non_colliding_id);
    }

    glFlush();

#ifdef SAVE_FRAMES
    glReadPixels(0,0,100,100, GL_RGB, GL_UNSIGNED_BYTE, pixels);
#endif    

    glXSwapBuffers(auxXDisplay(), auxXWindow());
} /** End of sgi_graphics_draw_scene() **/


/*****************************************************************************\
 @ create_polytope_display_list()
 -----------------------------------------------------------------------------
 description : Create 2 OpenGL display lists for each a polytope - one with
               random face colors (so we can see how complicated the objects
	       are) and the other solid red (to indicate a collision).
 input       : 
 output      : 
 notes       : 
\*****************************************************************************/
static int create_polytope_display_list(__col_Polyhedron *ph,
					int *xform, int *col_xform,
					PolytopeFans *poly_fans)
{
    static int list_num = 100; /* leave 100 entries for use by other parts of
				  the program */
    int              i;
    int              red, blue, green;
    col_Fnode            *faces;
    col_Fnode            *verts;
    Fan              *fan;
    col_Fnode            *vert;
    
    faces = ph->faces;
    *xform = list_num;

    glNewList(list_num, GL_COMPILE);
    red =   (int)(random() % 150);
    green = (int)(random() % 150);
    blue =  (int)(random() % 150);
    glColor3ub(red,green,blue);
    /* for now, it's fan random, not face random */
    for (fan = poly_fans->fans; fan; fan = fan->next)
    {
	glBegin(GL_TRIANGLE_FAN);
	for (vert = fan->verts; vert; vert = vert->next)
	{
	    red =   (int)(random() % 150);
	    green = (int)(random() % 150);
	    blue =  (int)(random() % 150);
	    glColor3ub(red,green,blue);
	    glVertex3dv(vert->f.v->coords);
	}
	
	glEnd();
    }
    glEndList();

    faces = ph->faces; /* making second list of all red polytopes to show collision */
    *col_xform = 100000 + list_num;

    glNewList(*col_xform, GL_COMPILE);
    red =   150;
    green = 0;
    blue =  0;
    glColor3ub(red,green,blue);
    for (fan = poly_fans->fans; fan; fan = fan->next)
    {
	glBegin(GL_TRIANGLE_FAN);
	for (vert = fan->verts; vert; vert = vert->next)
	    glVertex3dv(vert->f.v->coords);
	glEnd();
    }
    glEndList();
    list_num++;
} /** End of create_polytope_display_list() **/



/*****************************************************************************\
 @ graphics_init()
 -----------------------------------------------------------------------------
 description : Create the graphics window and set the OpenGL state to some
               sane state. 
 input       : 
 output      : 
 notes       :
\*****************************************************************************/
void sgi_graphics_init(char *window_title)
{
  auxInitDisplayMode(AUX_DOUBLE|AUX_RBGA|AUX_DEPTH);
  auxInitPosition(0,0, WINDOW_WIDTH, WINDOW_HEIGHT);
  auxInitWindow(window_title);


  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_LIGHTING);
  glShadeModel(GL_FLAT);
  glLineWidth(2.0);
  
  return;
} /** End of graphics_init() **/

#endif /* (GRAPHICS = SGI_GRAPHICS) */


/*****************************************************************************\
\*****************************************************************************/

