/* -*- Mode: C; tab-width: 4 -*- emacs friendly */
/* gflux - creates a fluctuating 3D grid 
 * requires OpenGL or MesaGL
 * 
 * Copyright (c) Josiah Pease, 2000, 2003
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 *
 * Thanks go to all those who worked on...
 * MesaGL, OpenGL, UtahGLX, XFree86, gcc, vim, rxvt, the PNM (anymap) format
 * xscreensaver and the thousands of other tools, apps and daemons that make
 * linux usable  
 * Personal thanks to Kevin Moss, Paul Sheahee and Jamie Zawinski
 * 
 * some xscreensaver code lifted from superquadrics.  Most other glx hacks 
 * used as reference at some time.
 *
 * This hack and others can cause UtahGLX to crash my X server
 * wireframe looks good with software only rendering anyway
 * If anyone can work out why and supply a fix I'd love to hear from them
 * 
 * Josiah Pease <gfluxcode@jpease.force9.co.uk> 21 July 2000
 * 
 * History 
 * 10 June 2000 : wireframe rippling grid standalone written
 * 18 June 2000 : xscreensaver code added
 * 25 June 2000 : solid and light added
 * 04 July 2000 : majour bug hunt, xscreensaver code rewritten
 * 08 July 2000 : texture mapping, rotation and zoom added
 * 21 July 2000 : cleaned up code from bug hunts, manpage written
 * 24 November 2000 : fixed x co-ord calculation in solid - textured
 * 05 March 2001 : put back non pnmlib code with #ifdefs
 * 11 May 2002 : fixed image problems with large images
 */


#ifdef STANDALONE
#define DEFAULTS                        "*delay:		20000\n" \
										"*showFPS:      False\n" \
                                        "*mode:         light\n" \
                                        "*useSHM:       True \n" 


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

#ifdef USE_GL /* whole file */

#ifdef HAVE_XMU
# ifndef VMS
#  include <X11/Xmu/Drawing.h>
#else  /* VMS */
#  include <Xmu/Drawing.h>
# endif /* VMS */
#endif

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

#include <stdlib.h>
#include <stdio.h>

#include <math.h>

#include "grab-ximage.h"
#include "gltrackball.h"


static enum {wire=0,solid,light,checker,grab} _draw;

static int _squares;                        /* grid size */
static int _resolution;                    /* wireframe resolution */
static int _flat;

static float _speed;
static float _rotationx;
static float _rotationy;
static float _rotationz;
static float _zoom;

static int _waves;
static int _waveChange;
static float _waveHeight;
static float _waveFreq;


#define WIDTH 320
#define HEIGHT 240

static XrmOptionDescRec opts[] = {
    {"-squares", ".gflux.squares", XrmoptionSepArg, 0},
    {"-resolution", ".gflux.resolution", XrmoptionSepArg, 0},
/*    {"-draw", ".gflux.draw", XrmoptionSepArg, 0},*/
    {"-mode", ".gflux.mode", XrmoptionSepArg, 0},
    {"-wireframe", ".gflux.mode", XrmoptionNoArg, "wire"},
    {"-flat", ".gflux.flat", XrmoptionSepArg, 0},
    {"-speed", ".gflux.speed", XrmoptionSepArg, 0},
    {"-rotationx", ".gflux.rotationx", XrmoptionSepArg, 0},
    {"-rotationy", ".gflux.rotationy", XrmoptionSepArg, 0},
    {"-rotationz", ".gflux.rotationz", XrmoptionSepArg, 0},
    {"-waves", ".gflux.waves", XrmoptionSepArg, 0},
    {"-waveChange", ".gflux.waveChange", XrmoptionSepArg, 0},
    {"-waveHeight", ".gflux.waveHeight", XrmoptionSepArg, 0},
    {"-waveFreq", ".gflux.waveFreq", XrmoptionSepArg, 0},
    {"-zoom", ".gflux.zoom", XrmoptionSepArg, 0},
};


static argtype vars[] = {
    {&_squares, "squares", "Squares", "19", t_Int},
    {&_resolution, "resolution", "Resolution", "4", t_Int},
/*    {&_draw, "draw", "Draw", "2", t_Int},*/
    {&_flat, "flat", "Flat", "0", t_Int},
    {&_speed, "speed", "Speed", "0.05", t_Float},
    {&_rotationx, "rotationx", "Rotationx", "0.01", t_Float},
    {&_rotationy, "rotationy", "Rotationy", "0.0", t_Float},
    {&_rotationz, "rotationz", "Rotationz", "0.1", t_Float},
    {&_waves, "waves", "Waves", "3", t_Int},
    {&_waveChange, "waveChange", "WaveChange", "50", t_Int},
    {&_waveHeight, "waveHeight", "WaveHeight", "1.0", t_Float},
    {&_waveFreq, "waveFreq", "WaveFreq", "3.0", t_Float},
    {&_zoom, "zoom", "Zoom", "1.0", t_Float},
};


static OptionStruct desc[] =
{
    {"-squares num", "size of grid in squares (19)"},
    {"-resolution num", "detail of lines making grid, wireframe only (4)"},
/*    {"-draw num", "draw method to use: 0=wireframe 1=solid 2=lit (0)"},*/
    {"-flat num", "shading method, not wireframe: 0=smooth 1=flat (0)"},
    {"-speed num", "speed of waves (0.05)"},
    {"-rotationx num", "speed of xrotation (0.01)"},
    {"-rotationy num", "speed of yrotation (0.00)"},
    {"-rotationz num", "speed of zrotation (0.10)"},
    {"-waves num", "number of simultanious waves (3)"},
    {"-waveChange num", "number of cyles for a wave to change (50)"},
    {"-waveHeight num", "height of waves (1.0)"},
    {"-waveFreq num", "max frequency of a wave (3.0)"},
    {"-zoom num", "camera control (1.0)"},
};

ENTRYPOINT ModeSpecOpt gflux_opts = {countof(opts), opts, countof(vars), vars, desc};

#ifdef USE_MODULES
ModStruct   gflux_description =
{"gflux", "init_gflux", "draw_gflux", "release_gflux",
 "draw_gflux", "init_gflux", NULL, &gflux_opts,
 1000, 1, 2, 1, 4, 1.0, "",
 "Gflux: an OpenGL gflux", 0, NULL};
#endif

/* structure for holding the gflux data */
typedef struct gfluxstruct {
    ModeInfo *modeinfo;
    int screen_width, screen_height;
    GLXContext *glx_context;
    Window window;
    XColor fg, bg;
#define MAXWAVES 10   /* should be dynamic    */
    double wa[MAXWAVES];
    double freq[MAXWAVES];
    double dispy[MAXWAVES];
    double dispx[MAXWAVES];
    GLfloat colour[3];
    GLuint texName;
    GLfloat tex_xscale;
    GLfloat tex_yscale;
    XRectangle img_geom;
    int img_width, img_height;
    void (*drawFunc)(struct gfluxstruct *);

    trackball_state *trackball;
    Bool button_down_p;

    double time;
    double anglex;
    double angley;
    double anglez;

    int counter;
    int newWave;

    Bool mipmap_p;
    Bool waiting_for_image_p;

} gfluxstruct;
static gfluxstruct *gfluxes = NULL;

/* prototypes */
static void initLighting(void);
static void grabTexture(gfluxstruct *);
static void createTexture(gfluxstruct *);
static void displaySolid(gfluxstruct *);     /* drawFunc implementations */
static void displayLight(gfluxstruct *);
static void displayTexture(gfluxstruct *);
static void displayWire(gfluxstruct *);
static void calcGrid(gfluxstruct *);
static double getGrid(gfluxstruct *,double,double,double);

/* as macro for speed */
/* could do with colour cycling here */
/* void genColour(double);*/
#define genColour(X) \
{\
    gp->colour[0] = 0.0;\
    gp->colour[1] = 0.5+0.5*(X);\
    gp->colour[2] = 0.5-0.5*(X);\
}

/* BEGINNING OF FUNCTIONS */


ENTRYPOINT Bool
gflux_handle_event (ModeInfo *mi, XEvent *event)
{
  gfluxstruct *gp = &gfluxes[MI_SCREEN(mi)];

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

  return False;
}


static void
userRot(gfluxstruct *gp)
{
  gltrackball_rotate (gp->trackball);
}

/* draw the gflux once */
ENTRYPOINT void draw_gflux(ModeInfo * mi)
{
    gfluxstruct *gp = &gfluxes[MI_SCREEN(mi)];
    Display    *display = MI_DISPLAY(mi);
    Window      window = MI_WINDOW(mi);

    if (!gp->glx_context) return;

    /* Just keep running before the texture has come in. */
    /* if (gp->waiting_for_image_p) return; */

    glXMakeCurrent(display, window, *(gp->glx_context));

    calcGrid(gp);
    gp->drawFunc(gp);
    if (mi->fps_p) do_fps (mi);
    glXSwapBuffers(display, window);
}


/* reset the projection matrix */
static void resetProjection(void) 
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-_zoom,_zoom,-0.8*_zoom,0.8*_zoom,2,6);
    glTranslatef(0.0,0.0,-4.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/* Standard reshape function */
ENTRYPOINT void
reshape_gflux(ModeInfo *mi, int width, int height)
{
    glViewport( 0, 0, width, height );
    resetProjection();
}


/* main OpenGL initialization routine */
static void initializeGL(ModeInfo *mi, GLsizei width, GLsizei height) 
{
  gfluxstruct *gp = &gfluxes[MI_SCREEN(mi)];

  reshape_gflux(mi, width, height);
  glViewport( 0, 0, width, height ); 

  gp->tex_xscale = 1.0;  /* maybe changed later */
  gp->tex_yscale = 1.0;

  switch(_draw) {
    case solid :
      gp->drawFunc = (displaySolid);
      glEnable(GL_DEPTH_TEST);
    break;
    case light :
      gp->drawFunc = (displayLight);
      glEnable(GL_DEPTH_TEST);
      initLighting();
	break;
	case checker :
      gp->drawFunc = (displayTexture);
      glEnable(GL_DEPTH_TEST);
      createTexture(gp);
      initLighting();
    break;
	case grab :
      gp->drawFunc = (displayTexture);
      glEnable(GL_DEPTH_TEST);
      grabTexture(gp);
      initLighting();
    break;
    case wire :
	default :
      gp->drawFunc = (displayWire);
      glDisable(GL_DEPTH_TEST);
    break;
  }

  if(_flat) glShadeModel(GL_FLAT);
  else glShadeModel(GL_SMOOTH);

}


/* xgflux initialization routine */
ENTRYPOINT void init_gflux(ModeInfo * mi)
{
    int screen = MI_SCREEN(mi);
    gfluxstruct *gp;

    if (gfluxes == NULL) {
        if ((gfluxes = (gfluxstruct *) 
                 calloc(MI_NUM_SCREENS(mi), sizeof (gfluxstruct))) == NULL)
            return;
    }
    gp = &gfluxes[screen];

    gp->trackball = gltrackball_init ();

    gp->time = frand(1000.0);  /* don't run two screens in lockstep */

    {
      char *s = get_string_resource (mi->dpy, "mode", "Mode");
      if (!s || !*s)                       _draw = wire;
      else if (!strcasecmp (s, "wire"))    _draw = wire;
      else if (!strcasecmp (s, "solid"))   _draw = solid;
      else if (!strcasecmp (s, "light"))   _draw = light;
      else if (!strcasecmp (s, "checker")) _draw = checker;
      else if (!strcasecmp (s, "grab"))    _draw = grab;
      else
        {
          fprintf (stderr,
                   "%s: mode must be one of: wire, solid, "
                   "light, checker, or grab; not \"%s\"\n",
                   progname, s);
          exit (1);
        }
    }

    gp->modeinfo = mi;
    gp->window = MI_WINDOW(mi);
    if ((gp->glx_context = init_GL(mi)) != NULL) {
        reshape_gflux(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
        initializeGL(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
    } else {
        MI_CLEARWINDOW(mi);
    }
}

/* cleanup code */
ENTRYPOINT void release_gflux(ModeInfo * mi)
{
    if (gfluxes != NULL) {
      free((void *) gfluxes);
      gfluxes = NULL;
    }
    FreeAllGL(mi);
}


static void createTexture(gfluxstruct *gp)
{
  int size = 4;
  unsigned int data[] = { 0xFFFFFFFF, 0xAAAAAAAA, 0xFFFFFFFF, 0xAAAAAAAA,
                          0xAAAAAAAA, 0xFFFFFFFF, 0xAAAAAAAA, 0xFFFFFFFF,
                          0xFFFFFFFF, 0xAAAAAAAA, 0xFFFFFFFF, 0xAAAAAAAA,
                          0xAAAAAAAA, 0xFFFFFFFF, 0xAAAAAAAA, 0xFFFFFFFF };

  gp->tex_xscale = size;
  gp->tex_yscale = size;

  glGenTextures (1, &gp->texName);
  glBindTexture (GL_TEXTURE_2D, gp->texName);

  glTexImage2D (GL_TEXTURE_2D, 0, 3, size, size, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, data);

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}


static void
image_loaded_cb (const char *filename, XRectangle *geometry,
                 int image_width, int image_height, 
                 int texture_width, int texture_height,
                 void *closure)
{
  gfluxstruct *gp = (gfluxstruct *) closure;
  gp->img_geom = *geometry;

  gp->tex_xscale =  (GLfloat) image_width  / texture_width;
  gp->tex_yscale = -(GLfloat) image_height / texture_height;
  gp->img_width  = image_width;
  gp->img_height = image_height;
   
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                   (gp->mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR));

  gp->waiting_for_image_p = False;
}

static void
grabTexture(gfluxstruct *gp)
{
  if (MI_IS_WIREFRAME(gp->modeinfo))
    return;

  gp->waiting_for_image_p = True;
  gp->mipmap_p = True;
  load_texture_async (gp->modeinfo->xgwa.screen, gp->modeinfo->window,
                      *gp->glx_context, 0, 0, gp->mipmap_p, gp->texName,
                      image_loaded_cb, gp);
}


static void initLighting(void)
{
    static const float ambientA[]  = {0.0, 0.0, 0.0, 1.0};
    static const float diffuseA[]  = {1.0, 1.0, 1.0, 1.0};
    static const float positionA[] = {5.0, 5.0, 15.0, 1.0};

    static const float front_mat_shininess[] = {30.0};
    static const float front_mat_specular[]  = {0.5, 0.5, 0.5, 1.0};

    static const float mat_diffuse[] = {0.5, 0.5, 0.5, 1.0};

    glMaterialfv(GL_FRONT, GL_SHININESS, front_mat_shininess);
    glMaterialfv(GL_FRONT, GL_SPECULAR, front_mat_specular);

    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mat_diffuse);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientA);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseA);
    glLightfv(GL_LIGHT0, GL_POSITION, positionA);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glLightModelf(GL_LIGHT_MODEL_TWO_SIDE,1);

    glEnable(GL_NORMALIZE);         /* would it be faster ...   */
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
}

/************************************/
/* draw implementations             */
/* somewhat inefficient since they  */
/* all calculate previously         */
/* calculated values again          */
/* storing the values in an array   */
/* is a posibility                  */
/************************************/
static void displayTexture(gfluxstruct *gp)
{
    double x,y,u,v;
    double z;
    double dx = 2.0/((double)_squares);
    double dy = 2.0/((double)_squares);

    double du = 2.0/((double)_squares);
    double dv = 2.0/((double)_squares);

    double xs = gp->tex_xscale;
    double ys = gp->tex_yscale;

    double minx, miny, maxx, maxy;
    double minu, minv;

#if 0
    minx = (GLfloat) gp->img_geom.x / gp->img_width;
    miny = (GLfloat) gp->img_geom.y / gp->img_height;
    maxx = ((GLfloat) (gp->img_geom.x + gp->img_geom.width) /
            gp->img_width);
    maxy = ((GLfloat) (gp->img_geom.y + gp->img_geom.height) /
            gp->img_height);
    minu = minx;
    minv = miny;
    minx = (minx * 2) - 1;
    miny = (miny * 2) - 1;
    maxx = (maxx * 2) - 1;
    maxy = (maxy * 2) - 1;
#else
    minx = -1;
    miny = -1;
    maxx = 1;
    maxy = 1;
    minv = 0;
    minu = 0;
#endif

	glMatrixMode (GL_TEXTURE);
	glLoadIdentity ();
	glTranslatef(-1,-1,0);
	glScalef(0.5,0.5,1);
	glMatrixMode (GL_MODELVIEW);

    glLoadIdentity();
    userRot(gp);
    glRotatef(gp->anglex,1,0,0);
    glRotatef(gp->angley,0,1,0);
    glRotatef(gp->anglez,0,0,1);
    glScalef(1,1,(GLfloat)_waveHeight);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	glEnable(GL_TEXTURE_2D);

    clear_gl_error();
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glBindTexture(GL_TEXTURE_2D, gp->texName);
    check_gl_error("texture binding");

	glColor3f(0.5,0.5,0.5);
 
    for(x = minx, u = minu; x < maxx - 0.01; x += dx, u += du) {
        glBegin(GL_QUAD_STRIP);
        for (y = miny, v = minv; y <= maxy + 0.01; y += dy, v += dv) {
            z = getGrid(gp,x,y,gp->time);
            glTexCoord2f(u*xs,v*ys);
            glNormal3f(
                getGrid(gp,x+dx,y,gp->time)-getGrid(gp, x-dx,y,gp->time),
                getGrid(gp,x,y+dy,gp->time)-getGrid(gp, x,y-dy,gp->time),
                1
            );
            glVertex3f(x,y,z);

            z = getGrid(gp,x+dx,y,gp->time);
            glTexCoord2f((u+du)*xs,v*ys);
            glNormal3f(
                getGrid(gp,x+dx+dx,y,gp->time)-getGrid(gp, x,y,gp->time),
                getGrid(gp,x+dx,y+dy,gp->time)-getGrid(gp, x+dx,y-dy,gp->time),
                1
            );
            glVertex3f(x+dx,y,z);
        }
        glEnd();
    }

    /* Draw a border around the grid.
     */
    glColor3f(0.4, 0.4, 0.4);
    glDisable(GL_TEXTURE_2D);
    glEnable (GL_LINE_SMOOTH);

    glBegin(GL_LINE_LOOP);
    y = miny;
    for (x = minx; x <= maxx; x += dx)
      glVertex3f (x, y, getGrid (gp, x, y, gp->time));
    x = maxx;
    for (y = miny; y <= maxy; y += dy)
      glVertex3f (x, y, getGrid (gp, x, y, gp->time));
    y = maxy;
    for (x = maxx; x >= minx; x -= dx)
      glVertex3f (x, y, getGrid (gp, x, y, gp->time));
    x = minx;
    for (y = maxy; y >= miny; y -= dy)
      glVertex3f (x, y, getGrid (gp, x, y, gp->time));

    glEnd();
    glEnable(GL_TEXTURE_2D);

    if (! gp->button_down_p) {
      gp->time -= _speed;
      gp->anglex -= _rotationx;
      gp->angley -= _rotationy;
      gp->anglez -= _rotationz;
    }
}
static void displaySolid(gfluxstruct *gp)
{
    double x,y;
    double z;
    double dx = 2.0/((double)_squares);
    double dy = 2.0/((double)_squares);

    glLoadIdentity();
    glRotatef(gp->anglex,1,0,0);
    glRotatef(gp->angley,0,1,0);
    glRotatef(gp->anglez,0,0,1);
    userRot(gp);
    glScalef(1,1,(GLfloat)_waveHeight);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    for(x=-1;x<0.9999;x+=dx) {
        glBegin(GL_QUAD_STRIP);
        for(y=-1;y<=1;y+=dy) {
            z = getGrid(gp, x,y,gp->time);
            genColour(z);
            glColor3fv(gp->colour);
            glVertex3f(x,y,z);

            z = getGrid(gp, x+dx,y,gp->time);
            genColour(z);
            glColor3fv(gp->colour);
            glVertex3f(x+dx,y,z);
        }
        glEnd();
    }

    if (! gp->button_down_p) {
      gp->time -= _speed;
      gp->anglex -= _rotationx;
      gp->angley -= _rotationy;
      gp->anglez -= _rotationz;
    }

}

static void displayLight(gfluxstruct *gp)
{
    double x,y;
    double z;
    double dx = 2.0/((double)_squares);
    double dy = 2.0/((double)_squares);

    glLoadIdentity();
    glRotatef(gp->anglex,1,0,0);
    glRotatef(gp->angley,0,1,0);
    glRotatef(gp->anglez,0,0,1);
    userRot(gp);
    glScalef(1,1,(GLfloat)_waveHeight);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    for(x=-1;x<0.9999;x+=dx) {
        glBegin(GL_QUAD_STRIP);
        for(y=-1;y<=1;y+=dy) {
            z = getGrid(gp, x,y,gp->time);
            genColour(z);
            glColor3fv(gp->colour);
            glNormal3f(
                getGrid(gp, x+dx,y,gp->time)-getGrid(gp, x-dx,y,gp->time),
                getGrid(gp, x,y+dy,gp->time)-getGrid(gp, x,y-dy,gp->time),
                1
            );
            glVertex3f(x,y,z);

            z = getGrid(gp, x+dx,y,gp->time);
            genColour(z);
            glColor3fv(gp->colour);
            glNormal3f(
                getGrid(gp, x+dx+dx,y,gp->time)-getGrid(gp, x,y,gp->time),
                getGrid(gp, x+dx,y+dy,gp->time)-getGrid(gp, x+dx,y-dy,gp->time),
                1
            );
            glVertex3f(x+dx,y,z);
        }
        glEnd();
    }

    if (! gp->button_down_p) {
      gp->time -= _speed;
      gp->anglex -= _rotationx;
      gp->angley -= _rotationy;
      gp->anglez -= _rotationz;
    }
}

static void displayWire(gfluxstruct *gp)
{
    double x,y;
    double z;
    double dx1 = 2.0/((double)(_squares*_resolution)) - 0.00001;
    double dy1 = 2.0/((double)(_squares*_resolution)) - 0.00001;
    double dx2 = 2.0/((double)_squares) - 0.00001;
    double dy2 = 2.0/((double)_squares) - 0.00001;

    glLoadIdentity();
    glRotatef(gp->anglex,1,0,0);
    glRotatef(gp->angley,0,1,0);
    glRotatef(gp->anglez,0,0,1);
    userRot(gp);
    glScalef(1,1,(GLfloat)_waveHeight);
    glClear(GL_COLOR_BUFFER_BIT);

    for(x=-1;x<=1;x+=dx2) {
        glBegin(GL_LINE_STRIP);
        for(y=-1;y<=1;y+=dy1) {
            z = getGrid(gp, x,y,gp->time);
            genColour(z);
            glColor3fv(gp->colour);
            glVertex3f(x,y,z);
        }
        glEnd();
    }
    for(y=-1;y<=1;y+=dy2) {
        glBegin(GL_LINE_STRIP);
        for(x=-1;x<=1;x+=dx1) {
            z = getGrid(gp, x,y,gp->time);
            genColour(z);
            glColor3fv(gp->colour);
            glVertex3f(x,y,z);
        }
        glEnd();
    }

    if (! gp->button_down_p) {
      gp->time -= _speed;
      gp->anglex -= _rotationx;
      gp->angley -= _rotationy;
      gp->anglez -= _rotationz;
    }
}

/* generates new ripples */
static void calcGrid(gfluxstruct *gp)
{
    double tmp;

    if (gp->button_down_p) return;

    tmp = 1.0/((double)_waveChange);
    if(!(gp->counter%_waveChange)) {
        gp->newWave = ((int)(gp->counter*tmp))%_waves;
        gp->dispx[gp->newWave] = -frand(1.0);
        gp->dispy[gp->newWave] = -frand(1.0);
        gp->freq[gp->newWave] = _waveFreq * frand(1.0);
        gp->wa[gp->newWave] = 0.0;
    }
    gp->counter++;
    gp->wa[gp->newWave] += tmp;
    gp->wa[(gp->newWave+1)%_waves] -= tmp;
}

/* returns a height for the grid given time and x,y space co-ords */
static double getGrid(gfluxstruct *gp, double x, double y, double a)
{
    register int i;
    double retval=0.0;
    double tmp;

    tmp = 1.0/((float)_waves);
    for(i=0;i<_waves;i++) {
      retval += gp->wa[i] * tmp * sin( gp->freq[i]
              * ( (x+gp->dispx[i]) * (x+gp->dispx[i]) 
                + (y+gp->dispy[i]) * (y+gp->dispy[i]) +a ) );
    }
    return(retval);
}


XSCREENSAVER_MODULE ("Gflux", gflux)

#endif
