// metaball.cpp
// by Glenn G. Chappell
// March 2004
//
// For CS 481/681
// Demo of Metaballs Using Marching Cubes

#include "cube.cpp"  // We do this odd include because we use templates
#include <iostream>
using std::cerr;
using std::endl;
#include <string>
using std::string;
#include <sstream>
#include <math.h>  // Some versions of MS/Visual C++ have broken <cmath>
//using std::sqrt;
//using std::exp;
//using std::log;
#include <stdlib.h>
//using std::exit;
#include <GL/glut.h> // GLUT stuff - includes OpenGL headers as well


// Global variables
// Window/viewport
const int startwinsize = 400; // Starting window width & height, in pixels
const double neardist = 1.0;  // Near & far clipping distances
const double fardist = 10.0;

// Keyboard
const int ESCKEY = 27;        // ASCII value of escape character

// For scene
double angle = 0.0;           // Rotation amount
const double anglestep = 1.0; // Step for above
bool rotate = false;          // True if rotating
bool vertnormflag = true;     // true for vertex normals, false for facet normals
bool showwireframe = false;   // true if wire-frame outline is shown

// For Cube
const int CUBEDEPTH = 5;      // Depth for Cube; size is approx 2^depth
Cube<CUBEDEPTH> * thecube;    // The Cube itself; holds potentials, computes surf
Potentialtype thresh = 50;    // Current threshold
const Potentialtype threshchange = 2.;
                              // Amount to change above

// For metaballs
const double ballradius = 0.1;  // The radius at threshold 50
const double ballpositions[][3] = {  // x, y, z of centers. Should be in [-1,1]
   {  0,-.6, .6 },
   { .6,  0, .6 },
   { .6, .6,  0 },
   {  0, .6,-.6 },
   {-.6,  0,-.6 },
   {-.6,-.6,  0 },
   { 99, 99, 99 } };  // End mark


// handle_triangle
// Gets a triangle from marching cubes code.
// Draws it.
void handle_triangle(Triangle & t)
{
   glBegin(GL_TRIANGLES);
      if (!vertnormflag) glNormal3fv(t.facenorm);  // Normcoordtype is float
      for(int kk=0; kk<3; ++kk)
      {
         if (vertnormflag) glNormal3fv(t.norms[kk]);
         glVertex3fv(t.verts[kk]);  // Vertcoordtype is float
      }
   glEnd();
}


// tostring
// Convert argument to string class using operator<<
// Must include <sstream>
template<typename T>
std::string tostring(const T & input)
{
   std::ostringstream os;
   os << input;
   return os.str();
}


// printbitmap
// Prints the given string at the given raster position
//  using GLUT bitmap fonts.
// You probably don't want any rotations in the model/view
//  transformation when calling this function.
void printbitmap(const string msg, double x, double y)
{
   glRasterPos2d(x, y);
   for (string::const_iterator ii = msg.begin();
        ii != msg.end();
        ++ii)
   {
      glutBitmapCharacter(GLUT_BITMAP_9_BY_15, *ii);
   }
}


// display
// The GLUT display function
void display()
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
      glTranslated(0., 0., -4.);
      glRotated(angle, 0.5, 1.0, 0.0);

      // Draw surface
      thecube->surfspit(handle_triangle);

      // Draw wire-frame outline, if shown
      if (showwireframe)
      {
         glDisable(GL_LIGHTING);
         glColor3d(1.0, 1.0, 0.0);
         glBegin(GL_LINE_LOOP);
            glVertex3d(-1.0, -1.0, -1.0);
            glVertex3d(1.0, -1.0, -1.0);
            glVertex3d(1.0, 1.0, -1.0);
            glVertex3d(-1.0, 1.0, -1.0);
         glEnd();
         glBegin(GL_LINE_LOOP);
            glVertex3d(-1.0, -1.0, 1.0);
            glVertex3d(1.0, -1.0, 1.0);
            glVertex3d(1.0, 1.0, 1.0);
            glVertex3d(-1.0, 1.0, 1.0);
         glEnd();
         glBegin(GL_LINES);
            glVertex3d(-1.0, -1.0, -1.0);
            glVertex3d(-1.0, -1.0, 1.0);

            glVertex3d(1.0, -1.0, -1.0);
            glVertex3d(1.0, -1.0, 1.0);

            glVertex3d(1.0, 1.0, -1.0);
            glVertex3d(1.0, 1.0, 1.0);

            glVertex3d(-1.0, 1.0, -1.0);
            glVertex3d(-1.0, 1.0, 1.0);
         glEnd();
         glEnable(GL_LIGHTING);
      }
   glPopMatrix();

   // Draw instructions
   glDisable(GL_LIGHTING);
   glDisable(GL_DEPTH_TEST);
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
      glLoadIdentity();
      gluOrtho2D(-1., 1., -1., 1.);
      glColor3d(1.0, 1.0, 1.0);
      printbitmap("Metaballs Demo", -0.9, 0.9);
      printbitmap("R      Toggle rotation", -0.9, 0.8);
      printbitmap("N      Switch vertex <-> facet normals", -0.9, 0.7);
      printbitmap("C      Toggle wire-frame cube", -0.9, 0.6);
      printbitmap("<- ->  Change threshold [" + tostring(thresh) + "]", -0.9, 0.5);
      printbitmap("ESC    Quit", -0.9, 0.4);
   glPopMatrix();
   glMatrixMode(GL_MODELVIEW);
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_LIGHTING);

   glutSwapBuffers();
}


// idle
// The GLUT idle function
void idle()
{
   // Print OpenGL errors, if there are any (for debugging)
   if (GLenum err = glGetError())
   {
      cerr << "OpenGL ERROR: " << gluErrorString(err) << endl;
   }

   // Do rotation
   if (rotate)
   {
      angle += anglestep;
      glutPostRedisplay();
   }
}


// reshape
// The GLUT reshape function
void reshape(int w, int h)
{
   glViewport(0, 0, w, h);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(50, double(w)/h, neardist, fardist);
}


// keyboard
// The GLUT keyboard function
void keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case ESCKEY:  // ESC: Quit
      exit(0);
      break;
   case 'r':     // R: Toggle rotation
   case 'R':
      rotate = !rotate;
      break;
   case 'n':     // N: Switch between vertex & facet normals
   case 'N':
      vertnormflag = !vertnormflag;
      glutPostRedisplay();
      break;
   case 'c':     // C: Toggle wire-frame outline
   case 'C':
      showwireframe = !showwireframe;
      glutPostRedisplay();
      break;
   }
}


// special
// The GLUT special function
void special(int key, int x, int y)
{
   switch (key)
   {
   case GLUT_KEY_RIGHT:   // ->: Increase threshold
      thresh += threshchange;
      thecube->setthreshold(thresh);
      thecube->update();
      glutPostRedisplay();
      break;
   case GLUT_KEY_LEFT:  // <-: Decrease threshold
      thresh -= threshchange;
      thecube->setthreshold(thresh);
      thecube->update();
      glutPostRedisplay();
      break;
   }
}


// setupcube
// Sets up potentials in cube
// Potentials should range from 0 to 100 (?)
void setupcube()
{
   thecube->setthreshold(thresh);
   for (int ii=0; ii<thecube->POTSIDE; ++ii)
   {
      for (int jj=0; jj<thecube->POTSIDE; ++jj)
      {
         for (int kk=0; kk<thecube->POTSIDE; ++kk)
         {
            double x = double(ii)/(thecube->POTSIDE-1)*2.-1.;  // x, y, z in [-1,1]
            double y = double(jj)/(thecube->POTSIDE-1)*2.-1.;
            double z = double(kk)/(thecube->POTSIDE-1)*2.-1.;
            double f = 0.0;
            for (int ss=0; ballpositions[ss][0] < 90; ++ss)
            {
               // At its radius, ball adds 1/2 to potential (1/2 * 100 = 50).
               // Formula is a * (1/2a)^(distance/radius).
               const double a = 0.7;
               const double l2pla = log(2) + log(a);
               double dx = x-ballpositions[ss][0];
               double dy = y-ballpositions[ss][1];
               double dz = z-ballpositions[ss][2];
               double dist = sqrt(dx*dx+dy*dy+dz*dz);
               f += a * exp(dist * -l2pla/ballradius);
            }
            thecube->setpotential(ii, jj, kk, f*100);
         }
      }
   }
   thecube->update();
}


// init
// Initialization
// Called by main after window creation
void init()
{
   setupcube();
    
   // Misc OpenGL
   glEnable(GL_DEPTH_TEST);  // We're doing 3-D

   glClearColor(0.2, 0.2, 0.2, 1.0);  // Background color

   // Set up front material
   GLfloat diffuse_f[] = { 0.9, 0.2, 0.5, 1.0 };
   GLfloat specular_f[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat shininess_f[] = { 100. };
   glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, diffuse_f);
   glMaterialfv(GL_FRONT, GL_SPECULAR, specular_f);
   glMaterialfv(GL_FRONT, GL_SHININESS, shininess_f);

   // Set up back material
   GLfloat diffuse_b[] = { 0.2, 0.7, 0.5, 1.0 };
   GLfloat specular_b[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat shininess_b[] = { 100. };
   glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, diffuse_b);
   glMaterialfv(GL_BACK, GL_SPECULAR, specular_b);
   glMaterialfv(GL_BACK, GL_SHININESS, shininess_b);

   // Set up a light
   GLfloat light_position[] = { -1.0, 1.3, 1.0, 0.0 };
   glLightfv(GL_LIGHT0, GL_POSITION, light_position);
   glEnable(GL_LIGHT0);

   // General lighting stuff
   glEnable(GL_LIGHTING);
   GLfloat lm_ambient[] = { 0.4, 0.4, 0.4, 1.0 };
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lm_ambient);
   glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
}


int main(int argc, char ** argv)
{
   thecube = new Cube<CUBEDEPTH>(thresh, 0, 2, -1, -1, -1); assert(thecube != NULL);
    
   // Initialize OpenGL/GLUT
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

   // Make a window
   glutInitWindowSize(startwinsize, startwinsize);
   glutInitWindowPosition(50, 50);
   glutCreateWindow("CS 481/681 - Marching Cubes Demo");
    
   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display);
   glutIdleFunc(idle);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutSpecialFunc(special);

   // Do something
   glutMainLoop();

   return 0;
}
