/******************************************************************************
** xwave - A 3-d perspective mesh for X
**
** Authors:
** Mike Friedman (mikef@umbc3.umbc.edu)
** Paul Riddle (paulr@umbc3.umbc.edu)
**
** University of Maryland, Baltimore County
******************************************************************************/

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <stdio.h>
#include <math.h>

#include "patchlevel.h"

#define   STARTX     20
#define   STARTY     50
#define   MAXROWS    18
#define   MAXCOLS    25
#define   GENS       2
#define   TWOPI10    400    /* 62*5 */
#define   TWOPI      6.283
#define   WIDTH      400
#define   HEIGHT     500

#define   ROOT       0x1
#define   THREED     0x2

int   FD = 25, PD = 15;

Colormap cmap;
XColor red, blue;

GC	gc;
Window	win;
Display	*dpy;
int	screen, scrnWidth = WIDTH, scrnHeight = HEIGHT;
Pixmap  buffer;

unsigned short flags = 0;

/******************************************************************************
** module:  initColor
**
** This loads the red and blue colors into the color map.
******************************************************************************/

void initColor ()
{
   cmap = DefaultColormap (dpy, screen);
   red.red   = 40000;
   red.blue  = 0;
   red.green = 0;
   blue.red   = 0;
   blue.blue  = 60000;
   blue.green = 0;

   if (!(XAllocColor (dpy, cmap, &red)) ||
       !(XAllocColor (dpy, cmap, &blue))) {
      (void) fprintf (stderr, "Error:  Cannot allocate colors\n");
      exit (1);
   }
}

/******************************************************************************
** module:  initialize
**
** This sets the window up.
******************************************************************************/

void initialize ()
{
   if (!(dpy = XOpenDisplay (""))) {
      (void) fprintf (stderr, "Error:  Can't open display\n");
      exit (1);
   }
   screen = DefaultScreen (dpy);
   if (flags & ROOT) {
      win = DefaultRootWindow (dpy);
      scrnWidth = DisplayWidth (dpy, screen);
      scrnHeight = DisplayHeight (dpy, screen);
   }
   else {
      win = XCreateSimpleWindow (dpy, DefaultRootWindow (dpy),
				 0, 0, scrnWidth, scrnHeight, 1,
				 WhitePixel (dpy, screen),
				 BlackPixel (dpy, screen));
      XSelectInput (dpy, win, ExposureMask | StructureNotifyMask);
      XStoreName (dpy, win, "XWave");
      XMapWindow (dpy, win);
   }
   gc = XCreateGC (dpy, win, 0L, (XGCValues *) 0);
   if (flags & THREED)
      initColor ();
   buffer = XCreatePixmap (dpy, DefaultRootWindow (dpy), scrnWidth,
			   scrnHeight, DefaultDepth (dpy, screen));
}

/******************************************************************************
** module:  setupSinTable
**
** This routine initializes the sine table.  The table is accessed during
** computation to speed things up somewhat.
******************************************************************************/

void setupSinTable (sinTab)
   double *sinTab;
{
   int i;
   double t = 0.0;

   for (i = 0; i < TWOPI10; i++, t += TWOPI / TWOPI10)
      sinTab[i] = sin (t);
}

/*
 * Perspective projections of points in 3-space to screen coordinates.
 * These are implemented as macros to avoid function call overhead without
 * (well, almost without..) sacrificing readability of the code.
 */

#define xrpers(x,y,z) ((int) (PD - FD * (PD - (x)) / (FD - (z)) + 100))
#define yrpers(x,y,z) ((int) (FD * (y) / (FD - (z)) + 150))
#define xlpers(x,y,z) ((int) (-PD - FD * (-PD - (x)) / (FD - (z)) + 100))
#define ylpers(x,y,z) ((int) (FD * (y) / (FD - (z)) + 150))

/*
 * Line plotting routines.  Again, these are all macros.
 */

#define plotLineRed(x0,y0,z0,x1,y1,z1) \
   XSetForeground (dpy, gc, red.pixel); \
   XDrawLine (dpy, buffer, gc, \
	      xlpers ((float) (x0), (float) (y0), (float) (z0)), \
	      ylpers ((float) (x0), (float) (y0), (float) (z0)), \
	      xlpers ((float) (x1), (float) (y1), (float) (z1)), \
	      ylpers ((float) (x1), (float) (y1), (float) (z1)))

#define plotLineBlue(x0,y0,z0,x1,y1,z1) \
   XSetForeground (dpy, gc, blue.pixel); \
   XDrawLine (dpy, buffer, gc, \
	      xrpers ((float) (x0), (float) (y0), (float) (z0)), \
	      yrpers ((float) (x0), (float) (y0), (float) (z0)), \
	      xrpers ((float) (x1), (float) (y1), (float) (z1)), \
	      yrpers ((float) (x1), (float) (y1), (float) (z1)))

#define plotLine(x0,y0,z0,x1,y1,z1) \
   if (flags & THREED) { \
      plotLineRed((x0),(y0),(z0),(x1),(y1),(z1)); \
      plotLineBlue((x0),(y0),(z0),(x1),(y1),(z1)); \
   } else \
      XDrawLine (dpy, buffer, gc, \
		 xlpers ((float) (x0), (float) (y0), (float) (z0)), \
		 ylpers ((float) (x0), (float) (y0), (float) (z0)), \
		 xlpers ((float) (x1), (float) (y1), (float) (z1)), \
		 ylpers ((float) (x1), (float) (y1), (float) (z1)))

main (argc, argv)
   int argc;
   char *argv[];
{
   double sinTab[TWOPI10];
   register int t, gap = 10;
   int r, c, px, py, junk, kb;
   int amplitude1 = 150, amplitude2 = 100, cur = 0, next = 1, inc1 = -1,
       inc2 = -1;
   int Generation[MAXROWS][MAXCOLS][GENS];
   Window junk_win;

   /*
    * Check args and set appropriate program flags
    */
   while (++argv, --argc) {
      if (!strcmp (*argv, "-root"))
	 flags |= ROOT;
      else if (!strcmp (*argv, "-3d"))
	 flags |= THREED;
      else {
	 (void) fprintf (stderr, "Usage:  xwave [-root] [-3d]\n");
	 exit (1);
      }
   }

   /*
    * Zero out the generation matrix
    */
   for (r = 0; r < MAXROWS; r++)
      for (c = 0; c < MAXCOLS; c++)
	 Generation[r][c][0] = Generation[r][c][1] = 0;
   
   /*
    * Set up the window and the sine table
    */
   initialize ();
   setupSinTable (sinTab);

   /*
    * Grab an expose event from the server, then we can get started
    */
   if (!(flags & ROOT)) {
      for (;;) {
	 XEvent event;
	 XNextEvent(dpy, &event);
	 if (event.type == Expose) break;
      }
      XWarpPointer (dpy, None, win, 0, 0, 0, 0, scrnWidth / 2,
		    scrnHeight / 2);
   }

   /*
    * Finally, go into an infinite loop of computing and plotting generations
    */
   for (;;)
      for (t = 0;t < TWOPI10; t++) {

	 /*
	  * Handle resizes.  When a resize happens, we need to re-allocate
	  * the background pixmap.  Note that the bigger the window, the
	  * larger the area that needs to get copied, and therefore the
	  * slower the program gets.
	  */
	 if (QLength (dpy)) {
	    XEvent event;
	    XNextEvent (dpy, &event);
	    switch (event.type) {
	    case ConfigureNotify:
	       if (event.xconfigure.width != scrnWidth ||
		   event.xconfigure.height != scrnHeight) {
		  XFreePixmap (dpy, buffer);
		  scrnWidth = event.xconfigure.width;
		  scrnHeight = event.xconfigure.height;
		  buffer = XCreatePixmap (dpy, DefaultRootWindow (dpy),
					  scrnWidth, scrnHeight,
					  DefaultDepth (dpy, screen));
	       }
	    default:
	       break;
	    }
	 }
         XQueryPointer (dpy, win, &junk_win, &junk_win, &junk, &junk,
			&px, &py, &kb);
	 px -= 275;
	 py -= 350;

	 /*
	  * Give the user something to do :-)
	  */
	 if ((kb & Button1Mask) && gap > 10) gap--;
	 if ((kb & Button3Mask) && gap < 50) gap++;

	 /*
	  * Load some of the cells
	  */
         Generation[1][2][cur]=(int)(amplitude1*sinTab[(30*t)%TWOPI10]);
         Generation[2][4][cur]=(int)(amplitude2*sinTab[(25*t)%TWOPI10]);
         Generation[1][6][cur]=(int)(amplitude1*sinTab[19*(10+t)%TWOPI10]);
         Generation[2][8][cur]=(int)(amplitude2*sinTab[(20*t)%TWOPI10]);
         Generation[15][8][cur]=(int)(amplitude2*sinTab[(5*t)%TWOPI10]);
         Generation[7][10][cur]=(int)(amplitude2*sinTab[(30*t)%TWOPI10]);
         Generation[9][14][cur]=(int)(amplitude2*sinTab[(20*t)%TWOPI10]);
         Generation[12][16][cur]=(int)((amplitude2+50)*sinTab[(3*t)%TWOPI10]);
         Generation[7][3][cur]=(int)(amplitude2*sinTab[(40*t)%TWOPI10]);

	 /*
	  * Increment and/or decrement our amplitudes
	  */
         if (amplitude2 == 0)
            inc2 = 5;
         else if (amplitude2 == 350)
            inc2 = -1;
	 amplitude2 += inc2;
         if (amplitude1 == 0)
            inc1 = 5;
         else if (amplitude1 == 200)
            inc1 = -1;
	 amplitude1 += inc1;

	 /*
	  * Interpolate the values of all the other cells in the matrix
	  */
         for (r = 1; r < MAXROWS - 1; r++)
            for (c = 1; c < MAXCOLS - 1; c++)
               Generation[r][c][next] =
		  (int) (0.25 * (Generation[r - 1][c][cur]
				 + Generation[r][c - 1][cur]
				 + Generation[r][c + 1][cur]
				 + Generation[r + 1][c][cur]));

	 /*
	  * Plot the new generation in our background pixmap
	  */
         for (r = 1; r < MAXROWS - 1; r++) {
            for (c = 0; c < MAXCOLS - 1; c++) {
               int xCoord, yCoord;
               xCoord = gap * c + STARTX;
               yCoord = Generation[r][c][next] + gap * r + STARTY;

	       plotLine (xCoord + px, yCoord + py, r,
			 gap * (c + 1) + STARTX + px,
			 Generation[r][c + 1][next] +
			 gap * r + STARTY + py, r);
	       plotLine (xCoord + px, yCoord + py, r, xCoord + px,
			 Generation[r + 1][c][next] +
			 gap * (r + 1) + STARTY + py, r + 1);
	    }
	 }
         cur ^= 1;
         next ^= 1;

	 /*
	  * Swap the background buffer and the window so the new generation
	  * shows up on the window.
	  */
         XCopyArea (dpy, buffer, win, gc, 0, 0, scrnWidth, scrnHeight, 0, 0);
         XSetForeground (dpy, gc, BlackPixel (dpy, screen));
         XFillRectangle (dpy, buffer, gc, 0, 0, scrnWidth, scrnHeight);
         XSetForeground (dpy, gc, WhitePixel (dpy, screen));
      }
}
