/* Surface --- Parametric 3d surfaces visualization */

/*
 * Revision History:
 * 2000: written by Andrey Mirtchovski <mirtchov@cpsc.ucalgary.ca>
 *       
 * 01-Mar-2003  mirtchov    Modified as a xscreensaver hack.
 * 01-jan-2009  steger      Renamed from klein.c to surfaces.c.
 *                          Removed the Klein bottle.
 *                          Added many new surfaces.
 *                          Added many command line options.
 *
 */

/* surfaces to draw */
#define SURFACE_RANDOM            -1
#define SURFACE_DINI              0
#define SURFACE_ENNEPER           1
#define SURFACE_KUEN              2
#define SURFACE_MOEBIUS           3
#define SURFACE_SEASHELL          4
#define SURFACE_SWALLOWTAIL       5
#define SURFACE_BOHEMIAN          6
#define SURFACE_WHITNEY           7
#define SURFACE_PLUECKER          8
#define SURFACE_HENNEBERG         9
#define SURFACE_CATALAN           10
#define SURFACE_CORKSCREW         11
#define NUM_SURFACES              12

/* primitives to draw with 
 * note that we skip the polygons and
 * triangle fans -- too slow
 *
 * also removed triangle_strip and quads -- 
 * just doesn't look good enough
 */
#define RENDER_RANDOM             -1
#define RENDER_POINTS             0
#define RENDER_LINES              1
#define RENDER_LINE_LOOP          2
#define NUM_RENDER                3

#ifdef STANDALONE
# define DEFAULTS                   "*delay:        20000   \n" \
                                    "*showFPS:      False   \n"

# define refresh_surface 0
# include "xlockmore.h"     /* from the xscreensaver distribution */
#else  /* !STANDALONE */
# include "xlock.h"         /* from the xlockmore distribution */
#endif /* !STANDALONE */

#ifdef USE_GL

#define DEF_SURFACE      "random"
#define DEF_MODE         "random"
#define DEF_SPIN         "True"
#define DEF_WANDER       "False"
#define DEF_SPEED        "300"

#include "rotator.h"
#include "gltrackball.h"

#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))


static char *surface_type;
static char *render_mode;
static int render;
static int speed;
static Bool do_spin;
static Bool do_wander;

static XrmOptionDescRec opts[] = {
  { "-surface",        ".surface", XrmoptionSepArg, 0 },
  { "-random-surface", ".surface", XrmoptionNoArg,  "random" },
  { "-dini",           ".surface", XrmoptionNoArg,  "dini" },
  { "-enneper",        ".surface", XrmoptionNoArg,  "enneper" },
  { "-kuen",           ".surface", XrmoptionNoArg,  "kuen" },
  { "-moebius",        ".surface", XrmoptionNoArg,  "moebius" },
  { "-seashell",       ".surface", XrmoptionNoArg,  "seashell" },
  { "-swallowtail",    ".surface", XrmoptionNoArg,  "swallowtail" },
  { "-bohemian",       ".surface", XrmoptionNoArg,  "bohemian" },
  { "-whitney",        ".surface", XrmoptionNoArg,  "whitney" },
  { "-pluecker",       ".surface", XrmoptionNoArg,  "pluecker" },
  { "-henneberg",      ".surface", XrmoptionNoArg,  "henneberg" },
  { "-catalan",        ".surface", XrmoptionNoArg,  "catalan" },
  { "-corkscrew",      ".surface", XrmoptionNoArg,  "corkscrew" },
  { "-mode",           ".mode",    XrmoptionSepArg, 0 },
  { "-random-mode",    ".mode",    XrmoptionNoArg,  "random" },
  { "-points",         ".mode",    XrmoptionNoArg,  "points" },
  { "-lines",          ".mode",    XrmoptionNoArg,  "lines" },
  { "-line-loops",     ".mode",    XrmoptionNoArg,  "line-loops" },
  { "-speed",          ".speed",   XrmoptionSepArg, 0 },
  { "-spin",           ".spin",    XrmoptionNoArg, "True" },
  { "+spin",           ".spin",    XrmoptionNoArg, "False" },
  { "-wander",         ".wander",  XrmoptionNoArg, "True" },
  { "+wander",         ".wander",  XrmoptionNoArg, "False" },
};

static argtype vars[] = {
  {&surface_type, "surface", "Surface", DEF_SURFACE, t_String },
  {&render_mode,  "mode",    "Mode",    DEF_MODE,    t_String },
  {&do_spin,      "spin",    "Spin",    DEF_SPIN,    t_Bool },
  {&do_wander,    "wander",  "Wander",  DEF_WANDER,  t_Bool },
  {&speed,        "speed",   "Speed",   DEF_SPEED,   t_Int },
};


ENTRYPOINT ModeSpecOpt surface_opts =
{countof(opts), opts, countof(vars), vars, NULL};



typedef struct {
  GLfloat x;
  GLfloat y;
  GLfloat z;
} GL_VECTOR;

typedef struct {
  GLXContext *glx_context;
  Window      window;
  rotator    *rot;
  trackball_state *trackball;
  Bool        button_down_p;

  int  render;
  Bool random_render;
  int  surface;
  Bool random_surface;
  int  frame;

  float du, dv;
  float a, b, c;

  float draw_step;
} surfacestruct;

static surfacestruct *surface = NULL;


static void draw(ModeInfo *mi)
{
  surfacestruct *sp = &surface[MI_SCREEN(mi)];
  double u, v;
  float coord[3];
  int render;

  mi->polygon_count = 0;
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
  glEnable(GL_CULL_FACE);

  glPushMatrix();

  {
    double x, y, z;
    get_position(sp->rot, &x, &y, &z, !sp->button_down_p);
    glTranslatef((x-0.5)*10, (y-0.5)*10, (z-0.5)*20);

    gltrackball_rotate(sp->trackball);

    get_rotation(sp->rot, &x, &y, &z, !sp->button_down_p);
    glRotatef(x*360, 1.0, 0.0, 0.0);
    glRotatef(y*360, 0.0, 1.0, 0.0);
    glRotatef(z*360, 0.0, 0.0, 1.0);
  }

  glScalef(4.0, 4.0, 4.0);

  switch(sp->surface)
  {
    case SURFACE_DINI:
      for (v=0.11; v<=2.0; v+=sp->dv)
      {
        glBegin(sp->render);
        for (u=0; u<=6.0*M_PI; u+=sp->du)
        {
          coord[0] = sp->a*cos(u)*sin(v);
          coord[1] = sp->a*sin(u)*sin(v);
          coord[2] = sp->a*(cos(v)+log(tan(0.5*v)))+0.2*sp->b*u;
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_ENNEPER:
      for (u=-M_PI; u<=M_PI; u+=sp->du)
      {
        glBegin(sp->render);
        for (v=-M_PI; v<M_PI; v+=sp->dv)
        {
          coord[0] = sp->a*(u-(1.0/3.0*u*u*u)+u*v*v);
          coord[1] = sp->b*(v-(1.0/3.0*v*v*v)+u*u*v);
          coord[2] = u*u-v*v;
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_KUEN:
      for (u=-4.48; u<=4.48; u+=sp->du)
      {
        glBegin(sp->render);
        for (v=M_PI/51; v<M_PI; v+=sp->dv)
        {
          coord[0] = 2*(cos(u)+u*sin(u))*sin(v)/(1+u*u*sin(v)*sin(v));
          coord[1] = 2*(sin(u)-u*cos(u))*sin(v)/(1+u*u*sin(v)*sin(v));
          coord[2] = log(tan(0.5*v))+2*cos(v)/(1+u*u*sin(v)*sin(v));
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_MOEBIUS:
      for (u=-M_PI; u<M_PI; u+=sp->du)
      {
        glBegin(sp->render);
        for (v=-0.735; v<0.74; v+=sp->dv)
        {
          coord[0] = cos(u)+v*cos(u/2)*cos(u);
          coord[1] = sin(u)+v*cos(u/2)*sin(u);
          coord[2] = v*sin(u/2);
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_SEASHELL:
      for (u=0; u<2*M_PI; u+=sp->du)
      {
        glBegin(sp->render);
        for (v=0; v<2*M_PI; v+=sp->dv)
        {
          coord[0] = sp->a*(1-v/(2*M_PI))*cos(2*v)*(1+cos(u))+sp->c*cos(2*v);
          coord[1] = sp->a*(1-v/(2*M_PI))*sin(2*v)*(1+cos(u))+sp->c*sin(2*v);
          coord[2] = 2*sp->b*v/(2*M_PI)+sp->a*(1-v/(2*M_PI))*sin(u);
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_SWALLOWTAIL:
      for (u=-2.5; u<2.0; u+=sp->du)
      {
        glBegin(sp->render);
        for (v=-1.085; v<1.09; v+=sp->dv)
        {
          coord[0] = u*v*v+3*v*v*v*v;
          coord[1] = -2*u*v-4*v*v*v;
          coord[2] = u;
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_BOHEMIAN:
      for (u=-M_PI; u<M_PI; u+=sp->du)
      {
        glBegin(sp->render);
        for (v=-M_PI; v<M_PI; v+=sp->dv)
        {
          coord[0] = sp->a*cos(u);
          coord[1] = sp->b*cos(v)+sp->a*sin(u);
          coord[2] = sin(v);
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_WHITNEY:
      for (v=-1.995; v<2.0; v+=sp->dv)
      {
        glBegin(sp->render);
        for (u=-1.995; u<2.0; u+=sp->du)
        {
          coord[0] = u*v;
          coord[1] = u;
          coord[2] = v*v-2;
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_PLUECKER:
      for (u=0; u<2.5; u+=sp->dv)
      {
        glBegin(sp->render);
        for (v=-M_PI; v<M_PI; v+=sp->du)
        {
          coord[0] = u*cos(v);
          coord[1] = u*sin(v);
          coord[2] = 2*cos(v)*sin(v);
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_HENNEBERG:
      for (u=0.9; u<2.55; u+=sp->dv)
      {
        glBegin(sp->render);
        for (v=-M_PI; v<M_PI; v+=sp->du)
        {
          coord[0] = sinh(1.0/3.0*u)*cos(v)-1.0/3.0*sinh(u)*cos(3.0*v);
          coord[1] = sinh(1.0/3.0*u)*sin(v)+1.0/3.0*sinh(u)*sin(3.0*v);
          coord[2] = cosh(2.0/3.0*u)*cos(2.0*v);
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_CATALAN:
      for (v=-2; v<2; v+=sp->du)
      {
        glBegin(sp->render);
        for (u=-2*M_PI; u<2*M_PI+0.05; u+=sp->dv)
        {
          coord[0] = 0.33*(u-sin(u)*cosh(v));
          coord[1] = 0.33*(1.0-cos(u)*cosh(v));
          coord[2] = 0.33*4.0*sin(0.5*u)*sinh(0.5*v);
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
    case SURFACE_CORKSCREW:
      for (v=-M_PI; v<M_PI; v+=sp->du)
      {
        glBegin(sp->render);
        for (u=-M_PI; u<M_PI; u+=sp->dv)
        {
          coord[0] = 0.5*(sp->a+2.0)*cos(u)*cos(v);
          coord[1] = 0.5*(sp->a+2.0)*sin(u)*cos(v);
          coord[2] = 0.5*(sp->a+2.0)*sin(v)+u;
          glColor3f(coord[0]+0.7, coord[1]+0.7, coord[2]+0.7);
          glVertex3fv(coord);
          mi->polygon_count++;
        }
        glEnd();
      }
      break;
  }
  glPopMatrix();

  if (sp->render == GL_LINES)
    mi->polygon_count /= 2;

  sp->a = sin(sp->draw_step+=0.01);
  sp->b = cos(sp->draw_step+=0.01);
  sp->c = sin(sp->draw_step+0.25*M_PI);

  if (sp->random_surface || sp->random_render)
  {
    sp->frame++;
    if (sp->frame >= speed)
    {
      sp->frame = 0;
      if (sp->random_surface)
        sp->surface = random() % NUM_SURFACES;
      if (sp->random_render)
      {
        render = random() % NUM_RENDER;
        switch (render)
        {
          case RENDER_POINTS:
            sp->render = GL_POINTS;
            break;
          case RENDER_LINES:
            sp->render = GL_LINES;
            break;
          case RENDER_LINE_LOOP:
            if (sp->surface == SURFACE_BOHEMIAN ||
                sp->surface == SURFACE_PLUECKER ||
                sp->surface == SURFACE_HENNEBERG)
              sp->render = GL_LINE_LOOP;
            else
              sp->render = GL_LINE_STRIP;
            break;
          default:
            sp->render = GL_LINE_LOOP;
            break;
        }
      }
    }
  }
}


/* new window size or exposure */
ENTRYPOINT void reshape_surface(ModeInfo *mi, int width, int height)
{
  GLfloat h = (GLfloat) height / (GLfloat) width;

  glViewport(0, 0, (GLint) width, (GLint) height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective (30.0, 1/h, 1.0, 100.0);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0.0, 0.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    
  glClear(GL_COLOR_BUFFER_BIT);
}


ENTRYPOINT Bool surface_handle_event(ModeInfo *mi, XEvent *event)
{
  surfacestruct *sp = &surface[MI_SCREEN(mi)];

  if (event->xany.type == ButtonPress && event->xbutton.button == Button1)
  {
    sp->button_down_p = True;
    gltrackball_start(sp->trackball, event->xbutton.x, event->xbutton.y,
                      MI_WIDTH (mi), MI_HEIGHT (mi));
    return True;
  }
  else if (event->xany.type == ButtonRelease &&
           event->xbutton.button == Button1)
  {
    sp->button_down_p = False;
    return True;
  }
  else if (event->xany.type == ButtonPress &&
           (event->xbutton.button == Button4 ||
            event->xbutton.button == Button5 ||
            event->xbutton.button == Button6 ||
            event->xbutton.button == Button7)) {
    gltrackball_mousewheel(sp->trackball, event->xbutton.button, 10,
                           !!event->xbutton.state);
    return True;
  }
  else if (event->xany.type == MotionNotify && sp->button_down_p)
  {
    gltrackball_track (sp->trackball, event->xmotion.x, event->xmotion.y,
                       MI_WIDTH (mi), MI_HEIGHT (mi));
    return True;
  }

  return False;
}


ENTRYPOINT void init_surface(ModeInfo *mi)
{
  int    screen = MI_SCREEN(mi);
  surfacestruct *sp;

  if (surface == NULL)
  {
    if ((surface = (surfacestruct *) calloc(MI_NUM_SCREENS(mi),
                                            sizeof(surfacestruct))) == NULL)
      return;
  }
  sp = &surface[screen];

  sp->window = MI_WINDOW(mi);

  {
    double spin_speed    = 1.0;
    double wander_speed = 0.03;
    sp->rot = make_rotator(do_spin ? spin_speed : 0,
                           do_spin ? spin_speed : 0,
                           do_spin ? spin_speed : 0,
                           1.0,
                           do_wander ? wander_speed : 0,
                           True);
    sp->trackball = gltrackball_init ();
  }

  if (!strcasecmp(surface_type,"random"))
  {
    sp->random_surface = True;
    sp->surface = random() % NUM_SURFACES;
  }
  else if (!strcasecmp(surface_type,"dini"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_DINI;
  }
  else if (!strcasecmp(surface_type,"enneper"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_ENNEPER;
  }
  else if (!strcasecmp(surface_type,"kuen"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_KUEN;
  }
  else if (!strcasecmp(surface_type,"moebius"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_MOEBIUS;
  }
  else if (!strcasecmp(surface_type,"seashell"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_SEASHELL;
  }
  else if (!strcasecmp(surface_type,"swallowtail"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_SWALLOWTAIL;
  }
  else if (!strcasecmp(surface_type,"bohemian"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_BOHEMIAN;
  }
  else if (!strcasecmp(surface_type,"whitney"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_WHITNEY;
  }
  else if (!strcasecmp(surface_type,"pluecker"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_PLUECKER;
  }
  else if (!strcasecmp(surface_type,"henneberg"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_HENNEBERG;
  }
  else if (!strcasecmp(surface_type,"catalan"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_CATALAN;
  }
  else if (!strcasecmp(surface_type,"corkscrew"))
  {
    sp->random_surface = False;
    sp->surface = SURFACE_CORKSCREW;
  }
  else
  {
    sp->random_surface = True;
    sp->surface = random() % NUM_SURFACES;
  }

  if (!strcasecmp(render_mode,"random"))
  {
    sp->random_render = True;
    render = random() % NUM_RENDER;
  }
  else if (!strcasecmp(render_mode,"points"))
  {
    sp->random_render = False;
    render = RENDER_POINTS;
  }
  else if (!strcasecmp(render_mode,"lines"))
  {
    sp->random_render = False;
    render = RENDER_LINES;
  }
  else if (!strcasecmp(render_mode,"line-loops"))
  {
    sp->random_render = False;
    render = RENDER_LINE_LOOP;
  }
  else
  {
    sp->random_render = True;
    render = random() % NUM_RENDER;
  }

  switch (render)
  {
    case RENDER_POINTS:
      sp->render = GL_POINTS;
      break;
    case RENDER_LINES:
      sp->render = GL_LINES;
      break;
    case RENDER_LINE_LOOP:
      if (sp->surface == SURFACE_BOHEMIAN ||
          sp->surface == SURFACE_PLUECKER ||
          sp->surface == SURFACE_HENNEBERG)
        sp->render = GL_LINE_LOOP;
      else
        sp->render = GL_LINE_STRIP;
      break;
    default:
      sp->render = GL_LINE_LOOP;
      break;
  }

  sp->frame = 0;

  sp->du = 0.07;
  sp->dv = 0.07;
  sp->a = sp->b = 1;
  sp->c = 0.1;

  if ((sp->glx_context = init_GL(mi)) != NULL)
  {
    reshape_surface(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
  }
  else
  {
    MI_CLEARWINDOW(mi);
  }
}


ENTRYPOINT void draw_surface(ModeInfo * mi)
{
  surfacestruct *sp = &surface[MI_SCREEN(mi)];
  Display *display = MI_DISPLAY(mi);
  Window window = MI_WINDOW(mi);

  if (!sp->glx_context)
    return;

  glDrawBuffer(GL_BACK);

  glXMakeCurrent(display, window, *(sp->glx_context));
  draw(mi);
  if (mi->fps_p)
    do_fps(mi);
  glFinish();
  glXSwapBuffers(display, window);
}


ENTRYPOINT void release_surface(ModeInfo * mi)
{
  if (surface != NULL)
  {
    int  screen;

    for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
    {
      surfacestruct *sp = &surface[screen];

      if (sp->glx_context)
      {
        /* Display lists MUST be freed while their glXContext is current. */
        glXMakeCurrent(MI_DISPLAY(mi), sp->window, *(sp->glx_context));
      }
    }
    (void) free((void *)surface);
    surface = NULL;
  }
  FreeAllGL(mi);
}


XSCREENSAVER_MODULE_2("Surfaces", surfaces, surface)

#endif
