/* gibson, Copyright © 2020-2025 Jamie Zawinski <jwz@jwz.org>
 *
 * 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.
 *
 * Hacking the Gibson, as per the 1995 classic film, HACKERS.
 *
 * In the movie, this was primarily a practical effect: the towers were
 * edge-lit etched perspex, each about four feet tall.
 */

#define TOWER_FONT "sans-serif bold 48"

#define DEFAULTS	"*delay:	20000       \n" \
			"*groundColor:  #8A2BE2"   "\n" \
			"*towerColor:   #4444FF"   "\n" \
			"*towerText:    #DDDDFF"   "\n" \
			"*towerText2:   #FF0000"   "\n" \
			"*towerFont:  " TOWER_FONT "\n" \
			"*showFPS:      False       \n" \
			"*wireframe:    False       \n" \

# define release_gibson 0

#include "xlockmore.h"
#include "colors.h"
#include "rotator.h"
#include "texfont.h"
#include "easing.h"
#include <ctype.h>

#ifdef USE_GL /* whole file */


#define DEF_SPEED	 "1.0"
#define DEF_TEXTURE      "True"
#define DEF_GRID_WIDTH	 "6"
#define DEF_GRID_HEIGHT	 "7"
#define DEF_GRID_DEPTH	 "6"
#define DEF_GRID_SPACING "2.0"
#define DEF_COLUMNS	 "5"

#define GROUND_QUAD_SIZE 30

typedef struct {
  GLfloat x, y, h;
  GLuint fg_dlists[5], bg_dlists[5];
  int fg_polys, bg_polys;
  unsigned int face_mode;   /* 5 bit field */
} tower;

typedef struct {
  GLXContext *glx_context;
  Bool button_down_p;
  rotator *rot, *rot2;
  GLfloat xscroll, yscroll;
  GLfloat oxscroll, oyscroll;

  GLuint ground_dlist;
  GLuint tower_dlist;
  int ground_polys, tower_polys;
  GLfloat ground_y;
  GLfloat billboard_y;
  const char *billboard_text;

  int ntowers;
  tower *towers;
  GLfloat tower_color[4];
  GLfloat tower_color2[4];
  GLfloat edge_color[4];
  GLfloat bg_color[4];
  Bool startup_p;

  struct {
    GLuint texid;
    XCharStruct metrics;
    int width, height;
    texture_font_data *font_data;
    int ascent, descent, em_width;
    char *text;
  } text[2];

} gibson_configuration;

static gibson_configuration *ccs = NULL;

static GLfloat speed;
static Bool do_tex;
static int grid_width;
static int grid_height;
static int grid_depth;
static GLfloat grid_spacing;
static int columns;

static XrmOptionDescRec opts[] = {
  { "-speed",        ".speed",       XrmoptionSepArg, 0 },
  { "-texture",      ".texture",     XrmoptionNoArg,  "True" },
  { "+texture",      ".texture",     XrmoptionNoArg,  "False" },
  { "-grid-width",   ".gridWidth",   XrmoptionSepArg, 0 },
  { "-grid-height",  ".gridHeight",  XrmoptionSepArg, 0 },
  { "-grid-depth",   ".gridDepth",   XrmoptionSepArg, 0 },
  { "-spacing",      ".gridSpacing", XrmoptionSepArg, 0 },
  { "-columns",      ".columns",     XrmoptionSepArg, 0 },
};

static argtype vars[] = {
  {&speed,        "speed",       "Speed",       DEF_SPEED,        t_Float},
  {&do_tex,       "texture",     "Texture",     DEF_TEXTURE,      t_Bool},
  {&grid_width,   "gridWidth",   "GridWidth",   DEF_GRID_WIDTH,   t_Int},
  {&grid_height,  "gridHeight",  "GridHeight",  DEF_GRID_HEIGHT,  t_Int},
  {&grid_depth,   "gridDepth",   "GridDepth",   DEF_GRID_DEPTH,   t_Int},
  {&grid_spacing, "gridSpacing", "GridSpacing", DEF_GRID_SPACING, t_Float},
  {&columns,      "columns",     "Columns",     DEF_COLUMNS,      t_Int},
};

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


ENTRYPOINT void
reshape_gibson (ModeInfo *mi, int width, int height)
{
  GLfloat h = (GLfloat) height / (GLfloat) width;
  int y = 0;

  glViewport (0, y, (GLint) width, (GLint) height);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
# ifdef DEBUG
  gluPerspective (30, 1/h, 1, 100);
# else
  gluPerspective (100, 1/h/4,
                  1.0, 
                  20 * grid_depth * 1.5 * (1 + grid_spacing));
# endif

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt( 0, 0, 1,
             0, 0, 0,
             0, 1, 0);

  glClear(GL_COLOR_BUFFER_BIT);
}


/* Copied from gltrackball.c */
static void
adjust_for_device_rotation (double *x, double *y, double *w, double *h)
{
  int rot = (int) current_device_rotation();
  int swap;

  while (rot <= -180) rot += 360;
  while (rot >   180) rot -= 360;

  if (rot > 135 || rot < -135)		/* 180 */
    {
      *x = *w - *x;
      *y = *h - *y;
    }
  else if (rot > 45)			/* 90 */
    {
      swap = *x; *x = *y; *y = swap;
      swap = *w; *w = *h; *h = swap;
      *x = *w - *x;
    }
  else if (rot < -45)			/* 270 */
    {
      swap = *x; *x = *y; *y = swap;
      swap = *w; *w = *h; *h = swap;
      *y = *h - *y;
    }
}


ENTRYPOINT Bool
gibson_handle_event (ModeInfo *mi, XEvent *event)
{
  gibson_configuration *bp = &ccs[MI_SCREEN(mi)];
  double w = MI_WIDTH(mi);
  double h = MI_HEIGHT(mi);
  double xoff = 0, yoff = 0;

  if (event->xany.type == ButtonPress ||
      event->xany.type == ButtonRelease)
    {
      double x = event->xbutton.x;
      double y = event->xbutton.y;
      adjust_for_device_rotation (&x, &y, &w, &h);
      xoff = (x / w) - 0.5;
      yoff = (event->xbutton.y / h) - 0.5;
      bp->button_down_p = (event->xany.type == ButtonPress);
      bp->oxscroll = xoff;
      bp->oyscroll = yoff;

      return True;
    }
  else if (event->xany.type == MotionNotify)
    {
      double x = event->xmotion.x;
      double y = event->xmotion.y;
      adjust_for_device_rotation (&x, &y, &w, &h);
      xoff = (x / w) - 0.5;
      yoff = (y / h) - 0.5;
      if (bp->button_down_p)
        {
          bp->xscroll += xoff - bp->oxscroll;
          bp->yscroll += yoff - bp->oyscroll;
          bp->oxscroll = xoff;
          bp->oyscroll = yoff;
        }
      return True;
    }

  return False;
}


static void
parse_color (ModeInfo *mi, char *key, GLfloat color[4])
{
  XColor xcolor;
  char *string = get_string_resource (mi->dpy, key, "Color");
  if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
    {
      fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
               key, string);
      exit (1);
    }
  free (string);

  color[0] = xcolor.red   / 65536.0;
  color[1] = xcolor.green / 65536.0;
  color[2] = xcolor.blue  / 65536.0;
  color[3] = 1;
}


static int
draw_ground (ModeInfo *mi)
{
  int wire = MI_IS_WIREFRAME(mi);
  int polys = 0;
  int x, y;
  int cells = 20;
  GLfloat color[4];
  GLfloat color0[4];
  GLfloat cell_size = 1.0;
  GLfloat z = -0.005;

  parse_color (mi, "groundColor", color);
  parse_color (mi, "towerColor", color0);
  color0[0] *= 0.05;
  color0[1] *= 0.05;
  color0[2] *= 0.3;
  color0[3] = 1;

  if (!wire)
    {
      GLfloat fog_color[4] = { 0, 0, 0, 1 };

      glFogi (GL_FOG_MODE, GL_EXP2);
      glFogfv (GL_FOG_COLOR, fog_color);
      glFogf (GL_FOG_DENSITY, 0.015);
      glFogf (GL_FOG_START, -cells/2 * cell_size);
      glEnable (GL_FOG);
    }

  glPushMatrix();
  glScalef (1.0/cells, 1.0/cells, 1);
  glTranslatef (-cells/2.0, -cells/2.0, 0);
  glTranslatef (0.5, 0, 0);

  glBegin (GL_QUADS);	/* clipping quad */
  glColor4fv (color0);
  glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color0);

  glVertex3f (0, 0, z);
  glVertex3f (cells * cell_size, 0, z);
  glVertex3f (cells * cell_size, cells * cell_size, z);
  glVertex3f (0, cells * cell_size, z);
  glEnd();
  polys++;

  glColor4fv (color);
  glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);

  for (y = 0; y < cells; y++)
    for (x = 0; x < cells; x++)
      {
        GLfloat a = 0;
        GLfloat b = 1.0/3;
        GLfloat c = 2.0/3;
        GLfloat d = 1.0;
        GLfloat w = 0.02;

        glPushMatrix();
        glTranslatef (x, y, 0);

        glNormal3f (0, 0, 1);

        switch (random() % 4) {
        case 0:
          glRotatef (90, 0, 0, 1);
          glTranslatef (0, -1, 0);
          break;
        case 1:
          glRotatef (-90, 0, 0, 1);
          glTranslatef (-1, 0, 0);
          break;
        case 2:
          glRotatef (180, 0, 0, 1);
          glTranslatef (-1, -1, 0);
          break;
        default: break;
        }

        switch (random() % 2) {
        case 0:
          glScalef (-1, -1, 1);
          glTranslatef (-1, -1, 0);
          break;
        default: break;
        }

        switch (random() % 2) {
        case 0:

          glBegin (wire ? GL_LINE_LOOP : GL_QUAD_STRIP);
          glVertex3f (a, b+w, 0);
          glVertex3f (a, b-w, 0); polys++;
          glVertex3f (b+w, a, 0); polys++;
          glVertex3f (b-w, a, 0); polys++;
          glEnd();

          glBegin (wire ? GL_LINE_LOOP : GL_QUAD_STRIP);
          glVertex3f (a,   c+w, 0);
          glVertex3f (a,   c-w, 0); polys++;
          glVertex3f (b+w, c+w, 0); polys++;
          glVertex3f (b,   c-w, 0); polys++;
          glVertex3f (c+w, b+w, 0); polys++;
          glVertex3f (c-w, b,   0); polys++;
          glVertex3f (c+w, a,   0); polys++;
          glVertex3f (c-w, a,   0); polys++;
          glEnd();

/*
          glBegin (wire ? GL_LINE_LOOP : GL_QUAD_STRIP);
          glVertex3f (c+w, d,   0);
          glVertex3f (c-w, d,   0); polys++;
          glVertex3f (d,   c+w, 0); polys++;
          glVertex3f (d,   c-w, 0); polys++;
          glEnd();
*/
          break;

        default:
          glBegin (wire ? GL_LINE_LOOP : GL_QUAD_STRIP);
          glVertex3f (a+w, d,   0);
          glVertex3f (a,   d,   0); polys++;
          glVertex3f (a+w, d,   0);
          glVertex3f (a,   d-w, 0); polys++;
          glVertex3f (b+w, c-w, 0); polys++;
          glVertex3f (b-w, c-w, 0); polys++;
          glVertex3f (b+w, a,   0); polys++;
          glVertex3f (b-w, a,   0); polys++;
          glEnd();

          glBegin (wire ? GL_LINE_LOOP : GL_QUAD_STRIP);
          glVertex3f (b+w, d,   0);
          glVertex3f (b-w, d,   0); polys++;
          glVertex3f (c+w, c-w, 0); polys++;
          glVertex3f (c-w, c-w, 0); polys++;
          glVertex3f (c+w, a,   0); polys++;
          glVertex3f (c-w, a,   0); polys++;
          glEnd();
          break;
        }

        glPopMatrix();
      }
  glPopMatrix();

  if (!wire)
    {
      glDisable (GL_BLEND);
      glDisable (GL_FOG);
    }

  return polys;
}


/* qsort comparator for sorting towers by y position */
static int
cmp_towers (const void *aa, const void *bb)
{
  const tower *a = (tower *) aa;
  const tower *b = (tower *) bb;
  return ((int) (b->y * 10000) -
          (int) (a->y * 10000));
}


/* Draws the text quads on the face.
   First pass is for the small background text, second is for the big block.
 */
static int
draw_tower_face_text (ModeInfo *mi, GLfloat height, Bool which)
{
  gibson_configuration *bp = &ccs[MI_SCREEN(mi)];
  int wire = MI_IS_WIREFRAME(mi);
  int polys = 0;
  Bool wire2 = False;   /* Debugging quads */
  Bool bg_p = (which == 1 && do_tex && !wire);

  /* The texture is a tex_width x tex_height rectangle, of which we 
     only use the rbearing+lbearing x ascent+descent sub-rectangle.
     Texture coordinates reference the tex_width x tex_height rectangle
     as a 0.0 - 1.0 coordinate.
   */
  int n = which ? 1 : 0;
  GLfloat twratio = ((bp->text[n].metrics.rbearing +
                      bp->text[n].metrics.lbearing) /
                     (GLfloat) bp->text[n].width);
  GLfloat thratio = ((bp->text[n].metrics.ascent +
                      bp->text[n].metrics.descent) /
                     (GLfloat) bp->text[n].height);
  GLfloat aspect = ((bp->text[n].ascent + bp->text[n].descent) /
                    (GLfloat) bp->text[n].em_width);

  GLfloat sx  = 1.0 / (which ? 1 : columns);
  GLfloat sy  = (which
                 ? height * 0.8
                 : sx * 4);  /* Tweaked to match gluPerspective */

  GLfloat lines_in_tex = ((bp->text[n].metrics.ascent +
                           bp->text[n].metrics.descent) /
                          (GLfloat)
                          (bp->text[n].ascent + bp->text[n].descent));
  GLfloat tex_lines = (which ? 3 : 8);  /* Put this many lines in each quad */

  GLfloat tsx = sx * twratio;
  GLfloat tsy = sy * thratio * tex_lines / lines_in_tex * aspect;
  GLfloat x1, tx1;
  GLfloat margin = 0.2;
  GLfloat m2 = margin/2 / (which ? 1 : columns);
  GLfloat m3 = m2 / (which ? 1 : height);
  GLfloat h2 = height * (which ? 1-margin : 1);

  glColor4fv (which ? bp->tower_color2 : bp->tower_color);
  glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 
                which ? bp->tower_color2 : bp->tower_color);

  glBindTexture (GL_TEXTURE_2D, bp->text[n].texid);

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

  if (!wire && !wire2 && !bg_p) glBegin (GL_QUADS);
  for (x1 = 0, tx1 = 0; x1 < 1.0; x1 += sx, tx1 += tsx)
    {
      GLfloat x2  = x1 + sx;
      GLfloat tx2 = tx1 + tsx;
      GLfloat y2, ty2;
      GLfloat z = (which ? 0.05 : 0);

      tx1 = 0;
      tx2 = twratio;

      for (y2 = h2, ty2 = thratio;
           y2 > 0;
           y2 -= sy, ty2 -= tsy)
        {
          GLfloat y1  = y2 - sy * (1-margin);
          GLfloat ty1 = ty2 - tsy;
          GLfloat toff = frand ((bp->text[n].metrics.ascent + 
                                 bp->text[n].metrics.descent)
                                * 0.8);

          if (y1 < 0)  /* Clip the panel to the bottom of the tower face */
            {
              tsy = y2 / (y2 - y1);
              y1 = 0;
            }

          ty1 = toff;
          ty2 = ty1 + tsy;

          if (wire2 && which) glColor3f (1,0,0);
          if (wire || wire2 || bg_p)
            glBegin (!wire && (wire2 || bg_p) ? GL_QUADS : GL_LINE_LOOP);
          glTexCoord2f(tx1, ty2); glVertex3f (x1+m2, y1+m3, z);
          glTexCoord2f(tx2, ty2); glVertex3f (x2-m2, y1+m3, z);
          glTexCoord2f(tx2, ty1); glVertex3f (x2-m2, y2-m3, z);
          glTexCoord2f(tx1, ty1); glVertex3f (x1+m2, y2-m3, z);
          if (wire || wire2 || bg_p)
            glEnd();
          polys++;

          if (bg_p)
            {
              GLfloat bg[4] = { 1, 1, 1, 0.2 };
              z  -= 0.1;
              m2 -= 0.03;
              m3 -= 0.03;
              glColor4fv (bg);
              glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bg);
            }

          if ((wire2 || bg_p) && !wire)
            {
              if (do_tex) glDisable(GL_TEXTURE_2D);
              glBegin (bg_p ? GL_QUADS : GL_LINE_LOOP);
              glVertex3f (x1+m2, y1+m3, z);
              glVertex3f (x2-m2, y1+m3, z);
              glVertex3f (x2-m2, y2-m3, z);
              glVertex3f (x1+m2, y2-m3, z);
              glEnd();
              polys++;
              if (do_tex) glEnable(GL_TEXTURE_2D);
            }

          if (which) break;
        }
    }
  if (!wire && !wire2 && !bg_p) glEnd();

  return polys;
}


/* Draws the wall of the face, and the edges, then the text quads on it.
 */
static int
draw_tower_face (ModeInfo *mi, GLfloat height, int mode)
{
  gibson_configuration *bp = &ccs[MI_SCREEN(mi)];
  int wire = MI_IS_WIREFRAME(mi);
  int polys = 0;

  switch (mode) {
  case 0:
    if (! wire)
      {
        GLfloat m =  0.015;
        GLfloat z = -0.0005;
        if (do_tex) glDisable (GL_TEXTURE_2D);

        glNormal3f (0, 0, 1);

        glColor4fv (bp->bg_color);
        glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                      bp->bg_color);

        glBegin (GL_QUADS);
        glVertex3f (0, 0, z*2);			/* background */
        glVertex3f (1, 0, z*2);
        glVertex3f (1, height, z*2);
        glVertex3f (0, height, z*2);
        polys++;
        glEnd();

        glColor4fv (bp->edge_color);
        glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                      bp->edge_color);

        glBegin (GL_QUADS);
        glVertex3f (0, 0, z);			/* left */
        glVertex3f (m, 0, z);
        glVertex3f (m, height, z);
        glVertex3f (0, height, z);
        polys++;

        glVertex3f (1-m, 0, 0);			/* right */
        glVertex3f (1,   0, 0);
        glVertex3f (1,   height, z);
        glVertex3f (1-m, height, z);
        polys++;

        glVertex3f (m,   0, 0);			/* bottom */
        glVertex3f (1-m, 0, 0);
        glVertex3f (1-m, m, 0);
        glVertex3f (m,   m, 0);
        polys++;

        glVertex3f (m,   height-m, z);		/* top */
        glVertex3f (1-m, height-m, z);
        glVertex3f (1-m, height,   z);
        glVertex3f (m,   height,   z);
        polys++;
        glEnd();

        if (do_tex) glEnable (GL_TEXTURE_2D);
      }
    break;
  case 1:
    polys += draw_tower_face_text (mi, height, 0);
    break;
  case 2:
    polys += draw_tower_face_text (mi, height * 0.7, 1);
    break;
  default:
    abort();
    break;
  }

  return polys;
}


/* Mode 0: draws 5 sides of the box
   Mode 1: just background text
   Mode 2: just foreground text
 */
static int
draw_tower (ModeInfo *mi, tower *t, int mode, int face)
{
  GLfloat height = grid_height;

  int polys = 0;

  glPushMatrix();
  glTranslatef (-0.5, 0.5, 0);

  if (face == 0 || face == -1)
    {
      glPushMatrix();				/* top */
      glTranslatef (0, 0, height);
      polys += draw_tower_face (mi, 1.0, mode);
      glPopMatrix();
    }

  if (face == 1 || face == -1)
    {
      glPushMatrix();				/* left */
      glRotatef ( 90, 1, 0, 0);
      glRotatef (-90, 0, 1, 0);
      glTranslatef (-1, 0, 0);
      polys += draw_tower_face (mi, height, mode);
      glPopMatrix();
    }

  if (face == 2 || face == -1)
    {
      glPushMatrix();				/* back */
      glRotatef ( 90, 1, 0, 0);
      glRotatef (180, 0, 1, 0);
      glTranslatef (-1, 0, 1);
      polys += draw_tower_face (mi, height, mode);
      glPopMatrix();
    }

  if (face == 3 || face == -1)
    {
      glPushMatrix();				/* right */
      glRotatef ( 90, 1, 0, 0);
      glRotatef ( 90, 0, 1, 0);
      glTranslatef (0, 0, 1);
      polys += draw_tower_face (mi, height, mode);
      glPopMatrix();
    }

  if (face == 4 || face == -1)
    {
      glPushMatrix();				/* front */
      glRotatef ( 90, 1, 0, 0);
      polys += draw_tower_face (mi, height, mode);
      glPopMatrix();
    }

  if (face < -1 || face > 4) abort();

  glPopMatrix();
  return polys;
}


static void
animate_towers (ModeInfo *mi)
{
  gibson_configuration *bp = &ccs[MI_SCREEN(mi)];
  int ii;
  GLfloat min = -3;
  GLfloat max = grid_depth * (1 + grid_spacing) - grid_spacing - 1;
  GLfloat yspeed = speed * 0.05;

  for (ii = 0; ii < 20; ii++)
    {
      int jj, kk;

      /* randomly trade two towers' fg dlists */
      if (0 == (random() % 20))
        {
          int i = random() % bp->ntowers;
          int j = random() % bp->ntowers;
          int k = random() % countof(bp->towers[i].fg_dlists);
          GLuint d1 = bp->towers[i].bg_dlists[k];
          GLuint d2 = bp->towers[j].bg_dlists[k];
          bp->towers[i].bg_dlists[k] = d2;
          bp->towers[j].bg_dlists[k] = d1;
        }

      /* randomly trade two towers' bg dlists */
      if (1) /* (0 == (random() % 3)) */
        {
          int i = random() % bp->ntowers;
          int j = random() % bp->ntowers;
          int k = random() % countof(bp->towers[i].fg_dlists);
          GLuint d1 = bp->towers[i].fg_dlists[k];
          GLuint d2 = bp->towers[j].fg_dlists[k];
          bp->towers[i].fg_dlists[k] = d2;
          bp->towers[j].fg_dlists[k] = d1;
        }

      /* Randomize whether it's displaying fg text or bg text */
      for (jj = 0; jj < bp->ntowers; jj++)
        for (kk = 0; kk < countof(bp->towers[jj].fg_dlists); kk++)
          {
            /* Re-choose every N frames.  Display fg 1 in M. */
            int frames = 500;
            int fg_chance = (kk == 0 ? 100000 : 10);
            unsigned int o = !!(bp->towers[jj].face_mode & (1 << kk));
            unsigned int n = !!((random() % frames) ? o : 
                                (0 == (random() % fg_chance)));
            bp->towers[jj].face_mode =
              ((bp->towers[jj].face_mode & ~(1 << kk)) | (n << kk));
          }
    }

  for (ii = 0; ii < bp->ntowers; ii++)
    {
      tower *t = &bp->towers[ii];
      t->h += speed * 0.01;
      if (t->h > 1) t->h = 1;

      t->y -= yspeed;

      if (t->y < min)
        {
          t->h = 0;
          t->y = max;
        }
    }

  /* Sorting by depth improves frame rate slightly. */
  qsort (bp->towers, bp->ntowers, sizeof(*bp->towers), cmp_towers);

  bp->ground_y -= yspeed / GROUND_QUAD_SIZE;
  if (bp->ground_y < 1)
    bp->ground_y += 1;

  bp->billboard_y -= yspeed;
  if (bp->billboard_y < min || !bp->billboard_text)
    {
      const char *const ss[] = {
        "ACCESS GRANTED",
        "ACCESS GRANTED",
        "ACCESS DENIED",
        "ACCESS DENIED",
        "ACCESS DENIED",
        "ACCESS DENIED",
        "ACCESS DENIED",
        "PASSWORD ACCEPTED",
        " GIVE ME\nA COOKIE",
        "MESS WITH THE BEST\n  DIE LIKE THE REST",
      };

      bp->billboard_y = max * (1 + frand(8));
      bp->billboard_text = ss[random() % countof(ss)];
    }
}


static int
draw_billboard (ModeInfo *mi)
{
  gibson_configuration *bp = &ccs[MI_SCREEN(mi)];
  int polys = 0;
  int wire = MI_IS_WIREFRAME(mi);
  GLfloat w, h, s, margin, margin2;
  XCharStruct metrics;
  int ascent, descent;
  texture_font_data *font = bp->text[1].font_data;
  GLfloat color[4];
  GLfloat y = grid_height * 0.3;

  texture_string_metrics (font, bp->billboard_text,
                          &metrics, &ascent, &descent);
  w = metrics.lbearing + metrics.rbearing;
  h = metrics.ascent   + metrics.descent;
  s = 1.0 / w;
  s *= 0.95;

  margin = w * 0.1;
  margin2 = margin * 1.7;

  glPushMatrix();

  glTranslatef (-0.5, bp->billboard_y, y);
  glRotatef (90, 1, 0, 0);
  glScalef (s, s * 1.5, s);

  memcpy (color, bp->tower_color2, sizeof(color));
  color[3] = 0.6;
  glColor4fv (color);
  glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);

  if (do_tex && !wire)
    glDisable (GL_TEXTURE_2D);

  glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
  glNormal3f (0, 0, 1);
  glVertex3f (-margin,  -margin2,  0);
  glVertex3f (-margin,  h+margin2, 0);
  glVertex3f (w+margin, h+margin2, 0);
  glVertex3f (w+margin, -margin2,  0);
  glEnd();
  polys++;

  if (do_tex && !wire)
    {
      color[3] = 1;
      glColor4fv (color);
      glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
      glEnable (GL_TEXTURE_2D);
      glTranslatef (-metrics.lbearing, metrics.descent, 0);
      print_texture_string (font, bp->billboard_text);
      polys++;
    }

  glPopMatrix();
  return polys;
}


static void
init_text (ModeInfo *mi)
{
  gibson_configuration *bp = &ccs[MI_SCREEN(mi)];
  int lines = 20;
  int i;
  char *s;

  const char *const ss[] = {
   "\n"
   "ACCESS TO THIS COMPUTER AND\n"
   "ITS DATA IS RESTRICTED TO\n"
   "AUTHORIZED PERSONNEL ONLY\n"
   "\n",
   "\n"
   "  PASSWORD ACCEPTED\n"
   "             GOD\n"
   "\n",
   "PERSONNEL   >>>\n",
   "SEA ROUTINGS   >>>\n",
   "GARBAGE   >>>\n",
   "COMP. SERVICING   >>>\n",
   "COMPANY BUDGETS   >>>\n",
   "SCIENTIFIC BUDGETS   >>>\n",
   "COMPANY POLICIES   >>>\n",
   "ANNUAL RETURNS   >>>\n",
   "MINE RESEARCH   >>>\n",
   "CENTRAL LIBRARY   >>>\n",
   "QUANTATIVE SPEC.   >>>\n",
   "PAYMENT LEVELS   >>>\n",
   "CENTRAL SERVER   >>>\n",
   "GARBAGE   >>>\n",
   "KNMTS. DVPNT.   >>>\n",
   "LICENSING   >>>\n",
   "RELATIONS   >>>\n",
   "TIME SHEET RECS.   >>>\n",
   "RD. PRT. ROUTINGS   >>>\n",
   "RECRUITMENT   >>>\n",
   "TNKR. EXPENDITURE   >>>\n",
   "MINE DEVELOPMENT   >>>\n",
   "GARBAGE   >>>\n",
   "ANNUAL BUDGETS   >>>\n",
   "OIL LOCATIONS   >>>\n",
   "TIME SHEET RECS.   >>>\n",
   "RD. PRT. ROUTINGS   >>>\n",
   "KINEMATICS   >>>\n",
   "TPS. REPORTS   >>>\n",
   "BLAST FRNC. STATUS   >>>\n",
   "ACCOUNTANTS   >>>\n",
   "SHIPPING FORCASTS   >>>\n",
   "INDST. REPORTS   >>>\n",
   "EXPLOR. DVLT.   >>>\n",
   "WRHSE. EXPEND.   >>>\n",
   "GARBAGE   >>>\n",
   "RELOCATIONS   >>>\n",
   "AIRFREIGHT STATUS   >>>\n",
   "TPGC. EXPEND.   >>>\n",
   "SEA-BOARD LAWS   >>>\n",
   "COMPOSITE PLANTS   >>>\n",
   "NUCLEAR RESEARCH   >>>\n",
   "BALLAST REPORTS   >>>\n",
   "\n"
   "CONFIDENTAL\n"
   "FILES\n"
   "DO NOT DELETE\n"
   "BEFORE FINAL\n"
   "BACKUP IS COMPLETED\n"
   "\n",
   "\n"
   "FILE 1\n"
   "WAITING FOR BACK-UP\n"
   "\n"
   "FILE 2\n"
   "WAITING FOR BACK-UP\n"
   "\n"
   "FILE 3\n"
   "WAITING FOR BACK-UP\n"
   "\n"
   "FILE 4\n"
   "WAITING FOR BACK-UP\n"
   "\n"
   };


  bp->text[1].text = s = calloc (countof(ss) * 2 * 40, 1);
  for (i = 0; i < countof(ss); i++)
    {
      int n = random() % countof(ss);
      strcat (s, ss[n]);
      s += strlen(s);
    }

  bp->text[0].text = s = calloc (lines * 40, 1);
  for (i = 0; i < lines; i++)
    {
      switch (random() % 11) {
      case 0: sprintf (s, "%X\n", random() % 0xFFFFFFFF); break;
      case 1: sprintf (s, "%X\n", random() % 0xFFFFFF); break;
      case 2: sprintf (s, "%X\n", random() % 0xFFFF); break;
      case 3: sprintf (s, "%d\n", random() % 0xFFFFFF); break;
      case 4: sprintf (s, "%d\n", random() % 0xFFFF); break;
      case 5: sprintf (s, "%d\n", random() % 0xFFF); break;
      case 6: strcat (s, "00000000\n"); break;
      case 7: sprintf (s, "{{{{{{{{\n"); break;
      case 8: sprintf (s, "[][][][][][]\n"); break;
      case 9: sprintf (s, "DEFAULT\n"); break;
      case 10: sprintf (s, "\n"); break;
      }
      s += strlen(s);
    }
}


static void
init_textures (ModeInfo *mi)
{
  gibson_configuration *bp = &ccs[MI_SCREEN(mi)];
  int i;
  for (i = 0; i < countof(bp->text); i++)
    {
      glGenTextures (1, &bp->text[i].texid);
      glBindTexture (GL_TEXTURE_2D, bp->text[i].texid);
      texture_string_metrics (bp->text[i].font_data, " ",
                              &bp->text[i].metrics,
                              &bp->text[i].ascent,
                              &bp->text[i].descent);
      bp->text[i].em_width = bp->text[i].metrics.width;
      string_to_texture (bp->text[i].font_data, bp->text[i].text,
                         &bp->text[i].metrics,
                         &bp->text[i].width,
                         &bp->text[i].height);
    }
  glBindTexture (GL_TEXTURE_2D, 0);
}


ENTRYPOINT void 
init_gibson (ModeInfo *mi)
{
  gibson_configuration *bp;

  MI_INIT (mi, ccs);

  bp = &ccs[MI_SCREEN(mi)];

  if ((bp->glx_context = init_GL(mi)) != NULL) {
    reshape_gibson (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
  }

  parse_color (mi, "towerText",  bp->tower_color);
  parse_color (mi, "towerText2", bp->tower_color2);
  parse_color (mi, "towerColor", bp->bg_color);
  memcpy (bp->edge_color, bp->bg_color, sizeof(bp->tower_color));
  bp->edge_color  [3] = 0.7;
  bp->bg_color    [3] = 1.0;
  bp->tower_color [3] = 1.0;
  bp->tower_color2[3] = 1.0;

  if (grid_spacing < 1) grid_spacing = 1;
  if (grid_width   < 1) grid_width   = 1;
  if (grid_height  < 1) grid_height  = 1;
  if (grid_depth   < 1) grid_depth   = 1;
  if (columns      < 1) columns      = 1;
  bp->ntowers = grid_width * grid_depth;
  bp->towers = (tower *) calloc (sizeof(tower), bp->ntowers);
  bp->startup_p = True;

  {
    double wander_speed = 0.007 * speed;
    double tilt_speed   = 0.01 * speed;
    bp->rot  = make_rotator (0, 0, 0, 0, wander_speed, True);
    bp->rot2 = make_rotator (0, 0, 0, 0, tilt_speed,   True);
  }

  bp->text[0].font_data = load_texture_font (mi->dpy, "towerFont");
  bp->text[1].font_data = load_texture_font (mi->dpy, "towerFont");
  init_text (mi);
  init_textures (mi);

  bp->ground_dlist = glGenLists (1);
  glNewList (bp->ground_dlist, GL_COMPILE);
  bp->ground_polys = draw_ground (mi);
  glEndList ();

  bp->tower_dlist = glGenLists (1);
  glNewList (bp->tower_dlist, GL_COMPILE);
  bp->tower_polys = draw_tower (mi, &bp->towers[0], 0, -1);
  glEndList ();

  {
    int x, y;
    GLfloat ww = grid_width * (1 + grid_spacing) - grid_spacing;
    GLfloat hh = grid_depth * (1 + grid_spacing) - grid_spacing;
    for (y = 0; y < grid_depth; y++)
      for (x = 0; x < grid_width; x++)
        {
          int i;
          tower *t = &bp->towers[y * grid_width + x];
          t->x = (x * ww / (grid_width - 1)) - ww/2;
          t->y = (y * hh / grid_depth) + 6;
          t->h = 0 - y / (GLfloat) grid_depth / 2;

          for (i = 0; i < countof(t->fg_dlists); i++)
            {
              t->bg_dlists[i] = glGenLists (1);
              glNewList (t->bg_dlists[i], GL_COMPILE);
              t->bg_polys = draw_tower (mi, t, 1, i);
              glEndList ();

              t->fg_dlists[i] = glGenLists (1);
              glNewList (t->fg_dlists[i], GL_COMPILE);
              t->fg_polys += draw_tower (mi, t, 2, i);
              glEndList ();
            }
        }
  }

  animate_towers (mi);
}


ENTRYPOINT void
draw_gibson (ModeInfo *mi)
{
  gibson_configuration *bp = &ccs[MI_SCREEN(mi)];
  Display *dpy = MI_DISPLAY(mi);
  Window window = MI_WINDOW(mi);
  int wire = MI_IS_WIREFRAME(mi);
  GLfloat s;
  int i;

  static const GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
  static const GLfloat bshiny    = 128.0;
  GLfloat bcolor[4] = { 0.7, 0.7, 1.0, 1.0 };

  if (!bp->glx_context)
    return;

  mi->polygon_count = 0;
  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);

  glShadeModel (GL_SMOOTH);
  glEnable (GL_NORMALIZE);
  glEnable (GL_CULL_FACE);
  glDisable (GL_TEXTURE_2D);
  glEnable (GL_DEPTH_TEST);

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  if (!wire)
    {
      GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
      GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
      GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
      GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};

      glEnable(GL_LIGHTING);
      glEnable(GL_LIGHT0);
      glLightfv(GL_LIGHT0, GL_POSITION, pos);
      glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
      glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
      glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
    }

  glPushMatrix ();

  glRotatef(current_device_rotation(), 0, 0, 1);

# ifdef DEBUG
  s = 0.02;
# else
  s = 10;
# endif

  glScalef (s, s, s);

  glTranslatef (0, -1, 0);

# ifndef DEBUG
  glRotatef (-82, 1, 0, 0);

  {
    double maxx = 40;   /* up/down */
    double maxy = 1.5;  /* tilt */
    double maxz = 100;  /* left/right */

    double x, y, z;
    double minh = -(grid_height / 2.0);
    double maxh = -(grid_height / 20.0);

    get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
    x -= 0.5;
    z = minh + (z * (maxh - minh));
    glTranslatef(x * grid_spacing * 0.005, 0, z);

    get_position (bp->rot2, &x, &y, &z, !bp->button_down_p);

    z += (bp->xscroll / 2.0);
    x += (bp->yscroll / 2.0);

    glRotatef (maxx/2 - x*maxx, 1, 0, 0);
    glRotatef (maxy/2 - y*maxy, 0, 1, 0);
    glRotatef (maxz/2 - z*maxz, 0, 0, 1);
  }
# endif /* DEBUG */

  glPushMatrix();
  glScalef (GROUND_QUAD_SIZE, GROUND_QUAD_SIZE, 1);

  glTranslatef (0, bp->ground_y - 1.5, 0);
  glCallList (bp->ground_dlist);
  mi->polygon_count += bp->ground_polys;

  glTranslatef (0, 1, 0);
  glCallList (bp->ground_dlist);
  mi->polygon_count += bp->ground_polys;
  glPopMatrix();

  glMaterialfv (GL_FRONT, GL_SPECULAR,            bspec);
  glMateriali  (GL_FRONT, GL_SHININESS,           bshiny);
  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, bcolor);

  glPushMatrix();

  if (grid_width & 1)  /* Stay between towers */
    glTranslatef ((grid_spacing + 1) / 2.0, 0, 0);


  if (!wire)
    {
      GLfloat fog_color[4] = { 0, 0, 0, 1 };
      glFogfv (GL_FOG_COLOR, fog_color);
      /* I so don't understand how to choose the fog parameters. */
      glFogi (GL_FOG_MODE, GL_LINEAR);
      glFogf (GL_FOG_START, 0);
      glFogf (GL_FOG_END, 100);
      glEnable (GL_FOG);
    }

  /* Clear the floor under the tower bases */

  {
    GLfloat color0[4] = { 0, 0, 0, 1 };
    GLfloat z = 0.01;

    if (do_tex && !wire) glDisable (GL_TEXTURE_2D);
    glColor4fv (color0);
    glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color0);
    glDisable (GL_BLEND);
    glDisable (GL_DEPTH_TEST);

    for (i = 0; i < bp->ntowers; i++)
      {
        tower *t = &bp->towers[i];
        glPushMatrix();
        glTranslatef (t->x, t->y, 0);

        glNormal3f (0, 0, 1);
        glBegin (wire ? GL_LINE_LOOP : GL_QUADS);	/* clipping quad */
        glVertex3f (-0.5, -0.5, z);
        glVertex3f ( 0.5, -0.5, z);
        glVertex3f ( 0.5,  0.5, z);
        glVertex3f (-0.5,  0.5, z);
        glEnd();
        mi->polygon_count++;
        glPopMatrix();
      }
  }

  glEnable (GL_DEPTH_TEST);

  if (!wire)
    {
      if (do_tex)
        {
          glEnable (GL_TEXTURE_2D);
          enable_texture_string_parameters (bp->text[0].font_data);
        }
      glEnable (GL_BLEND);
      glBlendFunc (GL_SRC_ALPHA, GL_ONE);
      glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
      glDisable (GL_CULL_FACE);
      if (bp->startup_p)
        glEnable (GL_DEPTH_TEST);
      else
        glDisable (GL_DEPTH_TEST);
    }

  /* Draw the towers */

  for (i = 0; i < bp->ntowers; i++)
    {
      tower *t = &bp->towers[i];
      glPushMatrix();
      glTranslatef (t->x, t->y-1,
                    -grid_height * ease (EASE_IN_OUT_SINE, 1 - t->h));

      glCallList (bp->tower_dlist);
      mi->polygon_count += bp->tower_polys;

      if (wire || do_tex)
        {
          int j;
          for (j = 0; j < countof(t->fg_dlists); j++)
            {
              if (! (t->face_mode & (1 << j)))
                {
                  glCallList (t->bg_dlists[j]);
                  mi->polygon_count += t->bg_polys;
                }
              else
                {
                  glCallList (t->fg_dlists[j]);
                  mi->polygon_count += t->fg_polys;
                }
            }
        }
      glPopMatrix();
    }

  glPopMatrix();

  mi->polygon_count += draw_billboard (mi);
  glPopMatrix();

  if (!bp->button_down_p)
    animate_towers (mi);

  if (bp->startup_p && bp->towers[bp->ntowers-1].h >= 1)
    bp->startup_p = False;

  if (mi->fps_p) do_fps (mi);
  glFinish();

  glXSwapBuffers(dpy, window);
}


ENTRYPOINT void
free_gibson (ModeInfo *mi)
{
  int i, j;
  gibson_configuration *bp = &ccs[MI_SCREEN(mi)];
  if (!bp->glx_context) return;
  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);

  if (bp->rot)  free_rotator (bp->rot);
  if (bp->rot2) free_rotator (bp->rot2);
  if (glIsList(bp->ground_dlist)) glDeleteLists(bp->ground_dlist, 1);
  if (glIsList(bp->tower_dlist)) glDeleteLists(bp->tower_dlist, 1);
  for (i = 0; i < countof(bp->text); i++)
    {
      if (bp->text[i].font_data) free_texture_font (bp->text[i].font_data);
      if (bp->text[i].text) free (bp->text[i].text);
    }
  if (bp->towers)
    for (i = 0; i < bp->ntowers; i++)
      {
        for (j = 0; j < countof(bp->towers[i].fg_dlists); j++)
          {
            if (glIsList(bp->towers[i].fg_dlists[j]))
              glDeleteLists(bp->towers[i].fg_dlists[j], 1);
            if (glIsList(bp->towers[i].bg_dlists[j]))
              glDeleteLists(bp->towers[i].bg_dlists[j], 1);
          }
      }
}


XSCREENSAVER_MODULE ("Gibson", gibson)
/* Greets to Crash Override, The Phantom Freak, and also Joey */

#endif /* USE_GL */
