/* DNA Logo, Copyright (c) 2001-2018 Jamie Zawinski <jwz@jwz.org>
 *
 *      DNA Lounge
 *
 *      Restaurant -- Bar -- Nightclub -- Cafe -- Est. 1985.
 *
 *      375 Eleventh Street
 *      San Francisco, CA
 *      94103
 *
 *      https://www.dnalounge.com/
 *      http://www.dnapizza.com/
 *
 * 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.
 */

#define DEFAULTS	__extension__ \
			"*delay:	    25000   \n" \
			"*showFPS:	    False   \n" \
			"*wireframe:	    False   \n" \
			"*doGasket:	    True    \n" \
			"*doHelix:	    True    \n" \
			"*doLadder:	    True    \n" \
			"*doFrame:	    True    \n" \
			"*wallFacets:	    360	    \n" \
			"*barFacets:	    90	    \n" \
			"*clockwise:	    False   \n" \
			"*turns:	    0.72    \n" \
			"*turnSpacing:	    2.3     \n" \
			"*barSpacing:	    0.268   \n" \
			"*wallHeight:	    0.42    \n" \
			"*wallThickness:    0.12    \n" \
			"*barThickness:     0.058   \n" \
			"*wallTaper:	    0.95    \n" \
			"*gasketSize:	    2.0     \n" \
			"*gasketDepth:	    0.15    \n" \
			"*gasketThickness:  0.4	    \n" \
			"*frameSize:	    1.28    \n" \
			"*frameDepth:	    0.01    \n" \
			"*frameThickness:   0.03    \n" \
			"*triangleSize:	    0.045   \n" \
			"*cwFacets:	    3	    \n" \
			"*cwDiscFacets:	    64	    \n" \
			"*cwSpread:	    0.5	    \n" \
			"*cwLineWidth:	    0.18    \n" \
			"*cwThickness:	    0.15    \n" \
			"*cwCapSize:	    0.4     \n" \
			"*text:		    CODEWORD\n" \
			"*speed:	    1.0	    \n" \
			"*mode:		    both"  "\n" \
			".background:	    #000000\n" \
			".foreground:	    #00AA00\n" \
			".cwForeground:	    #FCA816\n" \
			".cwBackground:	    #943225\n" \
		        "*cwFont:           " CWFONT "\n" \
			"*geometry:	    =640x640\n" \

#define CWFONT "Yearling 28, OCR A 24, OCR A Std 24, Courier 24"

# define release_logo 0

#undef DXF_OUTPUT_HACK

#ifdef DXF_OUTPUT_HACK   /* When this is defined, instead of rendering
                            to the screen, we write a DXF CAD file to stdout.
                            This is a kludge of shocking magnitude...
                            Maybe there's some other way to intercept all
                            glVertex3f calls than with a #define? */
# define unit_tube dxf_unit_tube
# define unit_cone dxf_unit_cone
# define tube_1    dxf_tube_1
# define tube      dxf_tube
# define cone      dxf_cone
#endif /* DXF_OUTPUT_HACK */

#include "xlockmore.h"
#include "normals.h"
#include "tube.h"
#include "sphere.h"
#include "rotator.h"
#include "gltrackball.h"
#include "utf8wc.h"
#include "texfont.h"

#ifdef USE_GL /* whole file */

#ifdef HAVE_JWZGLES
# include "dnapizza.h"
#else
# define HAVE_TESS
#endif

typedef enum {
  HELIX_IN, HELIX, HELIX_OUT,
  PIZZA_IN, PIZZA, PIZZA_OUT,
  HELIX_AND_PIZZA
# ifdef CW
  , CODEWORD_IN, CODEWORD, CODEWORD_OUT, CODEWORD_BLANK
# endif
} glyph_mode;

typedef struct {
  Bool spinning_p;
  GLfloat position;		/* 0.0 - 1.0 */
  GLfloat position_eased;	/* 0.0 - 1.0, eased in and out */
  GLfloat easement;		/* portion of path that is eased. <= 0.5 */
  GLfloat speed;		/* how far along the path (may be negative) */
  GLfloat probability;		/* relative likelyhood to start spinning */
} spinner;

typedef struct {
  GLXContext *glx_context;

  GLuint helix_list,  helix_list_wire,  helix_list_facetted;
  GLuint pizza_list,  pizza_list_wire,  pizza_list_facetted;
  GLuint gasket_list, gasket_list_wire;
  GLuint frame_list,  frame_list_wire;
  int polys[7];

  int wall_facets;
  int bar_facets;
  Bool clockwise;
  GLfloat color[4];

  GLfloat turns;
  GLfloat turn_spacing;
  GLfloat bar_spacing;
  GLfloat wall_height;
  GLfloat wall_thickness;
  GLfloat bar_thickness;
  GLfloat wall_taper;

  GLfloat gasket_size;
  GLfloat gasket_depth;
  GLfloat gasket_thickness;

  GLfloat frame_size;
  GLfloat frame_depth;
  GLfloat frame_thickness;
  GLfloat triangle_size;

# ifdef CW
  int codeword_facets, codeword_disc_facets;
  GLfloat codeword_spread, codeword_line_width, codeword_thickness;
  GLfloat codeword_cap_size;
  const char *codeword_text;
  char *codeword_text_out;
  int *codeword_text_points;
  XYZ *codeword_path;
  int codeword_path_npoints;
  int codeword_nguides;
  XYZ *codeword_guides;
  GLfloat codeword_color[4], codeword_bg[4];
  texture_font_data *font;
# endif

# ifdef DEBUG
  GLfloat persp_off, pos_off;
  texture_font_data *label_font;
# endif

  GLfloat speed;
  glyph_mode mode;
  glyph_mode anim_state;
  GLfloat anim_ratio;

  spinner gasket_spinnerx, gasket_spinnery, gasket_spinnerz;
  spinner scene_spinnerx,  scene_spinnery;	/* for DNA */
# ifdef CW
  rotator *scene_rot;				/* for Codeword */
# endif
  spinner helix_spinnerz;
  spinner pizza_spinnery, pizza_spinnerz;
  spinner frame_spinner;

  trackball_state *trackball;
  Bool button_down_p;

  int wire_overlay;  /* frame countdown */

} logo_configuration;

static logo_configuration *dcs = NULL;

static XrmOptionDescRec opts[] = {
  { "-speed",    ".speed",  XrmoptionSepArg, 0          },
  { "-mode",     ".mode",   XrmoptionSepArg, 0          },
  { "-pizza",    ".mode",   XrmoptionNoArg,  "pizza"    },
  { "-helix",    ".mode",   XrmoptionNoArg,  "helix"    },
  { "-both",     ".mode",   XrmoptionNoArg,  "both"     },
# ifdef CW
  { "-codeword", ".mode",   XrmoptionNoArg,  "codeword" },
  { "-cw",       ".mode",   XrmoptionNoArg,  "codeword" },
  { "-text",     ".text",   XrmoptionSepArg, 0          },
# endif
};

ENTRYPOINT ModeSpecOpt logo_opts = {countof(opts), opts, 0, NULL, NULL};

#define PROBABILITY_SCALE 600


#ifdef DXF_OUTPUT_HACK

# define glBegin         dxf_glBegin
# define glVertex3f      dxf_glVertex3f
# define glVertex3dv     dxf_glVertex3dv
# define glEnd           dxf_glEnd
# define glVertexPointer dxf_glVertexPointer
# define glDrawArrays    dxf_glDrawArrays

static int dxf_type, dxf_point, dxf_point_total, dxf_layer, dxf_color;
static GLfloat dxf_quads[4*4];

static void
dxf_glBegin (int type)
{
  dxf_type = type; 
  dxf_point = 0;
  dxf_point_total = 0;
}

static void
dxf_glVertex3f (GLfloat ox, GLfloat oy, GLfloat oz)
{
  int i = 0;
  GLfloat m[4*4];
  GLfloat x, y, z;

  /* Transform the point into modelview space. */
  glGetFloatv (GL_MODELVIEW_MATRIX, m);
  x = ox * m[0] + oy * m[4] + oz * m[8]  + m[12];
  y = ox * m[1] + oy * m[5] + oz * m[9]  + m[13];
  z = ox * m[2] + oy * m[6] + oz * m[10] + m[14];

  dxf_quads[dxf_point*3+0] = x;
  dxf_quads[dxf_point*3+1] = y;
  dxf_quads[dxf_point*3+2] = z;
  dxf_point++;
  dxf_point_total++;

  switch (dxf_type) {
  case GL_QUADS:
    if (dxf_point < 4) return;

    fprintf (stdout, "0\n3DFACE\n8\n%d\n62\n%d\n", dxf_layer, dxf_color);
    fprintf (stdout, "10\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "20\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "30\n%.6f\n", dxf_quads[i++]);

    fprintf (stdout, "11\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "21\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "31\n%.6f\n", dxf_quads[i++]);

    fprintf (stdout, "12\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "22\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "32\n%.6f\n", dxf_quads[i++]);

    fprintf (stdout, "13\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "23\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "33\n%.6f\n", dxf_quads[i++]);
    dxf_point = 0;
    break;

  case GL_QUAD_STRIP:
    if (dxf_point < 4) return;

    fprintf (stdout, "0\n3DFACE\n8\n%d\n62\n%d\n", dxf_layer, dxf_color);
    fprintf (stdout, "10\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "20\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "30\n%.6f\n", dxf_quads[i++]);

    fprintf (stdout, "11\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "21\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "31\n%.6f\n", dxf_quads[i++]);

    fprintf (stdout, "12\n%.6f\n", dxf_quads[i+3]);  /* funky quad strip */
    fprintf (stdout, "22\n%.6f\n", dxf_quads[i+4]);  /* vert order: 1243. */
    fprintf (stdout, "32\n%.6f\n", dxf_quads[i+5]);

    fprintf (stdout, "13\n%.6f\n", dxf_quads[i]);
    fprintf (stdout, "23\n%.6f\n", dxf_quads[i+1]);
    fprintf (stdout, "33\n%.6f\n", dxf_quads[i+2]);
    i += 6;

    dxf_quads[0] = dxf_quads[6];	/* copy point 3 to pos 1 */
    dxf_quads[1] = dxf_quads[7];
    dxf_quads[2] = dxf_quads[8];
    dxf_quads[3] = dxf_quads[9];	/* copy point 4 to pos 2 */
    dxf_quads[4] = dxf_quads[10];
    dxf_quads[5] = dxf_quads[11];
    dxf_point = 2;			/* leave those two points in queue */
    break;

  case GL_TRIANGLES:
  case GL_TRIANGLE_FAN:
    if (dxf_point < 3) return;

    fprintf (stdout, "0\n3DFACE\n8\n%d\n62\n%d\n", dxf_layer, dxf_color);
    fprintf (stdout, "10\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "20\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "30\n%.6f\n", dxf_quads[i++]);

    fprintf (stdout, "11\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "21\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "31\n%.6f\n", dxf_quads[i++]);

    fprintf (stdout, "12\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "22\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "32\n%.6f\n", dxf_quads[i++]);

    i -= 3;
    fprintf (stdout, "13\n%.6f\n", dxf_quads[i++]);  /* dup pt 4 as pt 3. */
    fprintf (stdout, "23\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "33\n%.6f\n", dxf_quads[i++]);

    dxf_point = 0;
    if (dxf_type == GL_TRIANGLE_FAN)
      {
        dxf_quads[3] = dxf_quads[6];	/* copy point 3 to point 2 */
        dxf_quads[4] = dxf_quads[7];
        dxf_quads[5] = dxf_quads[8];
        dxf_point = 2;			/* leave two points in queue */
      }
    break;

  case GL_TRIANGLE_STRIP:
    if (dxf_point < 3) return;

    fprintf (stdout, "0\n3DFACE\n8\n%d\n62\n%d\n", dxf_layer, dxf_color);

    fprintf (stdout, "10\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "20\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "30\n%.6f\n", dxf_quads[i++]);

    fprintf (stdout, "11\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "21\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "31\n%.6f\n", dxf_quads[i++]);

    fprintf (stdout, "12\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "22\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "32\n%.6f\n", dxf_quads[i++]);

    i -= 3;
    fprintf (stdout, "13\n%.6f\n", dxf_quads[i++]);  /* dup pt 4 as pt 3. */
    fprintf (stdout, "23\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "33\n%.6f\n", dxf_quads[i++]);

    dxf_quads[0] = dxf_quads[3];	/* copy point 2 to pos 1 */
    dxf_quads[1] = dxf_quads[4];
    dxf_quads[2] = dxf_quads[5];
    dxf_quads[3] = dxf_quads[6];	/* copy point 3 to pos 2 */
    dxf_quads[4] = dxf_quads[7];
    dxf_quads[5] = dxf_quads[8];
    dxf_point = 2;			/* leave those two points in queue */
    break;

  case GL_LINES:
  case GL_LINE_STRIP:
  case GL_LINE_LOOP:

    if (dxf_point_total == 1)
      {
        dxf_quads[6] = ox;
        dxf_quads[7] = oy;
        dxf_quads[8] = oz;
      }

    if (dxf_point < 2) return;

    fprintf (stdout, "0\nLINE\n8\n%d\n62\n%d\n", dxf_layer, dxf_color);

    fprintf (stdout, "10\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "20\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "30\n%.6f\n", dxf_quads[i++]);

    fprintf (stdout, "11\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "21\n%.6f\n", dxf_quads[i++]);
    fprintf (stdout, "31\n%.6f\n", dxf_quads[i++]);

    dxf_point = 0;
    if (dxf_type != GL_LINES)
      {
        dxf_quads[0] = dxf_quads[3];
        dxf_quads[1] = dxf_quads[4];
        dxf_quads[2] = dxf_quads[5];
        dxf_point = 1;
      }
    break;

  default:
    abort();
    break;
  }
}


static void
dxf_glVertex3dv (const GLdouble *v)
{
  glVertex3f (v[0], v[1], v[2]);
}


static void
dxf_glEnd(void)
{
  if (dxf_type == GL_LINE_LOOP)  /* close loop */
    glVertex3f (dxf_quads[6], dxf_quads[7], dxf_quads[8]);
  dxf_type = -1;
  dxf_point = 0;
  dxf_point_total = 0;
}


static void
dxf_start (void)
{
  fprintf (stdout, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n");
  fprintf (stdout, "0\nSECTION\n2\nENTITIES\n");
}

static void
dxf_end (void)
{
  fprintf (stdout, "0\nENDSEC\n0\nEOF\n");
  exit (0);
}


static const GLvoid *dxf_vp;
static GLsizei dxf_vp_size;
static GLsizei dxf_vp_stride;

static void
dxf_glVertexPointer (GLint size, GLenum type, GLsizei stride,
                     const GLvoid *pointer)
{
  if (type != GL_FLOAT) abort();
  if (stride <= 0) abort();
  dxf_vp = pointer;
  dxf_vp_size = size;
  dxf_vp_stride = stride;
}

static void
dxf_glDrawArrays (GLenum mode, GLint first, GLsizei count)
{
  int i;
  unsigned char *a = (unsigned char *) dxf_vp;
  dxf_glBegin (mode);
  for (i = first; i < first+count; i++)
    {
      GLfloat *fa = (GLfloat *) a;
      dxf_glVertex3f (fa[0], fa[1], fa[2]);
      a += dxf_vp_stride;
    }
  dxf_glEnd();
}


# define XYZ tube_XYZ   /* avoid conflict with normals.h */
# include "tube.c"      /* Yes, I really am including a C file. */
# undef XYZ
# define XYZ sphere_XYZ
# define unit_sphere unit_sphere_dxf
# define unit_dome unit_dome_dxf
# include "sphere.c"
# undef XYZ

#endif /* DXF_OUTPUT_HACK */



/* Calculate the angle (in radians) between two vectors.
 */
static GLfloat
vector_angle (double ax, double ay, double az,
              double bx, double by, double bz)
{
  double La = sqrt (ax*ax + ay*ay + az*az);
  double Lb = sqrt (bx*bx + by*by + bz*bz);
  double cc, angle;

  if (La == 0 || Lb == 0) return 0;
  if (ax == bx && ay == by && az == bz) return 0;

  /* dot product of two vectors is defined as:
       La * Lb * cos(angle between vectors)
     and is also defined as:
       ax*bx + ay*by + az*bz
     so:
      La * Lb * cos(angle) = ax*bx + ay*by + az*bz
      cos(angle)  = (ax*bx + ay*by + az*bz) / (La * Lb)
      angle = acos ((ax*bx + ay*by + az*bz) / (La * Lb));
  */
  cc = (ax*bx + ay*by + az*bz) / (La * Lb);
  if (cc > 1) cc = 1;  /* avoid fp rounding error (1.000001 => sqrt error) */
  angle = acos (cc);

  return (angle);
}


# ifdef CW

static void
normalize (XYZ *p)
{
  GLfloat d = sqrt (p->x*p->x + p->y*p->y + p->z*p->z);
  if (d != 0)
    {
      p->x /= d;
      p->y /= d;
      p->z /= d;
    }
  else
    {
      p->x = p->y = p->z = 0;
    }
}


static double
dot (const XYZ u, const XYZ v)
{
  return (u.x * v.x) + (u.y * v.y) + (u.z * v.z);
}

#endif /* CW */


/* Make the helix
 */

static int
make_helix (logo_configuration *dc, int facetted, int wire)
{
  int polys = 0;
  int wall_facets = dc->wall_facets / (facetted ? 10 : 1);
  GLfloat th;
  GLfloat max_th = M_PI * 2 * dc->turns;
  GLfloat th_inc = M_PI * 2 / wall_facets;

  GLfloat x1=0,  y1=0,  x2=0,  y2=0;
  GLfloat x1b=0, y1b=0, x2b=0, y2b=0;
  GLfloat z1=0, z2=0;
  GLfloat h1=0, h2=0;
  GLfloat h1off=0, h2off=0;
  GLfloat z_inc = dc->turn_spacing / wall_facets;

  th  = 0;
  x1  = 1;
  y1  = 0;
  x1b = 1;
  y1b = 0;

  z1 = -(dc->turn_spacing * dc->turns / 2);

  h1    = (dc->wall_taper > 0 ? 0 : dc->wall_height / 2);
  h1off = (dc->wall_taper > 0 ?    -dc->wall_height / 2 : 0);

  if (!dc->clockwise)
    z1 = -z1, z_inc = -z_inc, h1off = -h1off;

  /* Leading end-cap
   */
  if (!wire && h1 > 0)
    {
      GLfloat nx, ny;
      glFrontFace(GL_CCW);
      glBegin(GL_QUADS);
      nx = cos (th + M_PI/2);
      ny = sin (th + M_PI/2);
      glNormal3f(nx, ny, 0);
      glVertex3f( x1, y1,  z1 - h1 + h1off);
      glVertex3f( x1, y1,  z1 + h1 + h1off);
      glVertex3f(x1b, y1b, z1 + h1 + h1off);
      glVertex3f(x1b, y1b, z1 - h1 + h1off);
      polys++;
      glEnd();
    }

  while (th + th_inc <= max_th)
    {
      GLfloat thick = dc->wall_thickness;

      th += th_inc;

      x2 = cos (th);
      y2 = sin (th);
      z2 = z1 + z_inc;

      h2 = h1;
      h2off = h1off;

      if (dc->wall_taper > 0)
        {
          h2off = 0;
          if (th < dc->wall_taper)
            {
              h2 = dc->wall_height/2 * cos (M_PI / 2
                                            * (1 - (th / dc->wall_taper)));
              if (dc->clockwise)
                h2off = h2 - dc->wall_height/2;
              else
                h2off = dc->wall_height/2 - h2;

              if (th + th_inc <= 0)
                thick = 0;
              else
              thick *= cos (M_PI / 2 * (1 - (th / dc->wall_taper)));
            }
          else if (th >= max_th - dc->wall_taper)
            {
              if (th + th_inc > max_th) /* edge case: always come to a point */
                h2 = 0;
              else
                h2 = dc->wall_height/2 * cos (M_PI / 2
                                              * (1 - ((max_th - th)
                                                      / dc->wall_taper)));
              if (dc->clockwise)
                h2off = dc->wall_height/2 - h2;
              else
                h2off = h2 - dc->wall_height/2;

              if (th + th_inc > max_th)
                thick = 0;
              else
                thick *= cos(M_PI / 2 * (1 - ((max_th - th)/dc->wall_taper)));
            }
        }

      x2b = x2 * (1 - thick);
      y2b = y2 * (1 - thick);

      /* outer face
       */
      glFrontFace(GL_CW);
      glBegin(wire ? GL_LINES : GL_QUADS);
      glNormal3f(x1, y1, 0);
      glVertex3f(x1, y1, z1 - h1 + h1off);
      glVertex3f(x1, y1, z1 + h1 + h1off);
      glNormal3f(x2, y2, 0);
      glVertex3f(x2, y2, z2 + h2 + h2off);
      glVertex3f(x2, y2, z2 - h2 + h2off);
      polys++;
      glEnd();

      /* inner face
       */
      glFrontFace(GL_CCW);
      glBegin(wire ? GL_LINES : GL_QUADS);
      glNormal3f(-x1b, -y1b, 0);
      glVertex3f( x1b,  y1b, z1 - h1 + h1off);
      glVertex3f( x1b,  y1b, z1 + h1 + h1off);
      glNormal3f(-x2b, -y2b, 0);
      glVertex3f( x2b,  y2b, z2 + h2 + h2off);
      glVertex3f( x2b,  y2b, z2 - h2 + h2off);
      polys++;
      glEnd();

      /* top face
       */
      glFrontFace(GL_CCW);
      /* glNormal3f(0, 0, 1);*/
      do_normal (x2,   y2,  z2 + h2 + h2off,
                 x2b,  y2b, z2 + h2 + h2off,
                 x1b,  y1b, z1 + h1 + h1off);
      glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
      glVertex3f( x2,   y2,  z2 + h2 + h2off);
      glVertex3f( x2b,  y2b, z2 + h2 + h2off);
      glVertex3f( x1b,  y1b, z1 + h1 + h1off);
      glVertex3f( x1,   y1,  z1 + h1 + h1off);
      polys++;
      glEnd();

      /* bottom face
       */
      glFrontFace(GL_CCW);
      do_normal ( x1,   y1,  z1 - h1 + h1off,
                  x1b,  y1b, z1 - h1 + h1off,
                  x2b,  y2b, z2 - h2 + h2off);
      glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
      glNormal3f(0, 0, -1);
      glVertex3f( x1,   y1,  z1 - h1 + h1off);
      glVertex3f( x1b,  y1b, z1 - h1 + h1off);
      glVertex3f( x2b,  y2b, z2 - h2 + h2off);
      glVertex3f( x2,   y2,  z2 - h2 + h2off);
      polys++;
      glEnd();

      x1 = x2;
      y1 = y2;
      x1b = x2b;
      y1b = y2b;
      z1 += z_inc;
      h1 = h2;
      h1off = h2off;
    }

  /* Trailing end-cap
   */
  if (!wire && h2 > 0)
    {
      GLfloat nx, ny;
      glFrontFace(GL_CW);
      glBegin(GL_QUADS);
      nx = cos (th + M_PI/2);
      ny = sin (th + M_PI/2);
      glNormal3f(nx, ny, 0);
      glVertex3f(x2,  y2,  z1 - h2 + h2off);
      glVertex3f(x2,  y2,  z1 + h2 + h2off);
      glVertex3f(x2b, y2b, z1 + h2 + h2off);
      glVertex3f(x2b, y2b, z1 - h2 + h2off);
      polys++;
      glEnd();
    }
  return polys;
}


static int
make_ladder (logo_configuration *dc, int facetted, int wire)
{
  int polys = 0;
  GLfloat th;
  GLfloat max_th = dc->turns * M_PI * 2;
  GLfloat max_z  = dc->turns * dc->turn_spacing;
  GLfloat z_inc  = dc->bar_spacing;
  GLfloat th_inc = M_PI * 2 * (dc->bar_spacing / dc->turn_spacing);
  GLfloat x, y, z;

  /* skip forward to center the bars in the helix... */
  int i;
  GLfloat usable_th = max_th - dc->wall_taper;
  GLfloat usable_z  = max_z / (max_th / usable_th);
  int nbars = usable_z / dc->bar_spacing;
  GLfloat used_z, pad_z, pad_ratio;

  if (! (nbars & 1)) nbars--;  /* always an odd number of bars */

  used_z = (nbars - 1) * dc->bar_spacing;
  pad_z = max_z - used_z;
  pad_ratio = pad_z / max_z;

  th = (max_th * pad_ratio/2);
  z  = -(max_z / 2) + (max_z * pad_ratio/2);

  /* ##### WHYYYYYY */
  /* The image is not reflected across line y = -x and I don't know why. */
  th += M_PI * -0.035;
  z -= 0.08;

  if (!dc->clockwise)
    z = -z, z_inc = -z_inc;

  glFrontFace(GL_CCW);
  for (i = 0; i < nbars; i++)
    {
      int facets = dc->bar_facets / (facetted ? 14 : 1);
      if (facets <= 3) facets = 3;
      x = cos (th) * (1 - dc->wall_thickness);
      y = sin (th) * (1 - dc->wall_thickness);
      polys += tube ( x,  y, z,
                     -x, -y, z,
                      dc->bar_thickness, 0, facets,
                      True, True, wire);
      z  += z_inc;
      th += th_inc;
    }
  return polys;
}



/* Make the gasket
 */


static int
make_gasket (logo_configuration *dc, int wire)
{
  int polys = 0;
  int i;
  int points_size;
  int npoints = 0;
  int nctrls = 0;
  int res = 360/8;
  GLfloat d2r = M_PI / 180;

  GLfloat thick2 = (dc->gasket_thickness / dc->gasket_size) / 2;

  GLfloat *pointsx0, *pointsy0, *pointsx1, *pointsy1, *normals;

  GLfloat r0  = 0.750;  /* 395 */
  GLfloat r1a = 0.825;                /* bottom of wall below upper left hole */
  GLfloat r1b = 0.867;                /* center of upper left hole */
  GLfloat r1c = 0.909;                /* top of wall above hole */
  GLfloat r1  = 0.916;  /* 471 */
  GLfloat r2  = 0.963;  /* 490 */
  GLfloat r3  = 0.960;  /* 499 */
  GLfloat r4  = 1.000;  /* 507 */
  GLfloat r5  = 1.080;  /* 553 */

  GLfloat ctrl_r[100], ctrl_th[100];

  glPushMatrix();

# ifdef DXF_OUTPUT_HACK
  if (! wire) res *= 8;
# endif

# define POINT(r,th) \
    ctrl_r [nctrls] = r, \
    ctrl_th[nctrls] = (th * d2r), \
    nctrls++

  POINT (0.829, 0);      /* top indentation, right half */
  POINT (0.831, 0.85);
  POINT (0.835, 1.81);
  POINT (0.841, 2.65);
  POINT (0.851, 3.30);
  POINT (0.862, 3.81);
  POINT (0.872, 3.95);

  POINT (r4,    4.0);   /* moving clockwise... */
  POINT (r4,   47.0);
  POINT (r1,   47.0);
  POINT (r1,   53.0);
  POINT (r2,   55.5);
  POINT (r2,   72.3);
  POINT (r1,   74.0);
  POINT (r1,  100.0);
  POINT (r3,  102.5);
  POINT (r3,  132.0);
  POINT (r1,  133.0);

  POINT (r1,  180.7);
  POINT (r2,  183.6);
  POINT (r2,  210.0);
  POINT (r1,  212.0);
  POINT (r1,  223.2);
  POINT (r5,  223.2);
  POINT (r5,  225.0);
  POINT (r4,  225.0);

  POINT (r4,    316.8);      /* upper left indentation */
  POINT (0.990, 316.87);
  POINT (0.880, 317.21);
  POINT (0.872, 317.45);
  POINT (0.869, 317.80);
  POINT (0.867, 318.10);

  POINT (0.867, 318.85);
  POINT (0.869, 319.15);
  POINT (0.872, 319.50);
  POINT (0.880, 319.74);
  POINT (0.990, 320.08);

  POINT (r4,  338.0);
  if (! wire)
    {
      POINT (r1a, 338.0);      /* cut-out disc */
      POINT (r1a, 344.0);
    }
  POINT (r4,  344.0);
  POINT (r4,  356.0);

  POINT (0.872, 356.05);   /* top indentation, left half */
  POINT (0.862, 356.19);
  POINT (0.851, 356.70);
  POINT (0.841, 357.35);
  POINT (0.835, 358.19);
  POINT (0.831, 359.15);
  POINT (0.829, 360);
# undef POINT

  points_size = res + (nctrls * 2);
  pointsx0 = (GLfloat *) malloc (points_size * sizeof(GLfloat));
  pointsy0 = (GLfloat *) malloc (points_size * sizeof(GLfloat));
  pointsx1 = (GLfloat *) malloc (points_size * sizeof(GLfloat));
  pointsy1 = (GLfloat *) malloc (points_size * sizeof(GLfloat));
  normals  = (GLfloat *) malloc (points_size * sizeof(GLfloat) * 2);

  npoints = 0;
  for (i = 1; i < nctrls; i++)
    {
      GLfloat from_r  = ctrl_r [i-1];
      GLfloat from_th = ctrl_th[i-1];
      GLfloat to_r    = ctrl_r [i];
      GLfloat to_th   = ctrl_th[i];

      GLfloat step = 2*M_PI / res;
      int nsteps = 1 + ((to_th - from_th) / step);
      int j;

      for (j = 0; j < nsteps + (i == nctrls-1); j++)
        {
          GLfloat r  = from_r  + (j * (to_r  - from_r)  / nsteps);
          GLfloat th = from_th + (j * (to_th - from_th) / nsteps);

          GLfloat cth = cos(th) * dc->gasket_size;
          GLfloat sth = sin(th) * dc->gasket_size;

          pointsx0[npoints] = r0 * cth;  /* inner ring */
          pointsy0[npoints] = r0 * sth;
          pointsx1[npoints] = r  * cth;  /* outer ring */
          pointsy1[npoints] = r  * sth;
          npoints++;

          if (npoints >= points_size) abort();
        }
    }

  /* normals for the outer ring */
  for (i = 1; i < npoints; i++)
    {
      XYZ a, b, c, n;
      a.x = pointsx1[i-1];
      a.y = pointsy1[i-1];
      a.z = 0;
      b.x = pointsx1[i];
      b.y = pointsy1[i];
      b.z = 0;
      c = b;
      c.z = 1;
      n = calc_normal (a, b, c);
      normals[(i-1)*2  ] = n.x;
      normals[(i-1)*2+1] = n.y;
    }

  glRotatef(-90, 0, 1, 0);
  glRotatef(180, 0, 0, 1);

  if (wire)
    {
      GLfloat z;
      for (z = -thick2; z <= thick2; z += thick2*2)
        {
# if 1
          /* inside edge */
          glBegin (GL_LINE_LOOP);
          for (i = 0; i < npoints; i++)
            glVertex3f (pointsx0[i], pointsy0[i], z);
          polys += npoints;
          glEnd();

          /* outside edge */
          glBegin (GL_LINE_LOOP);
          for (i = 0; i < npoints; i++)
            glVertex3f (pointsx1[i], pointsy1[i], z);
          polys += npoints;
          glEnd();
# else
          for (i = 1; i < npoints; i++)
            {
              glBegin (GL_LINE_STRIP);
              glVertex3f (pointsx0[i-1], pointsy0[i-1], z);
              glVertex3f (pointsx0[i  ], pointsy0[i  ], z);
              glVertex3f (pointsx1[i  ], pointsy1[i  ], z);
              glVertex3f (pointsx1[i-1], pointsy1[i-1], z);
              glEnd();
            }
          polys += npoints;
# endif
        }
#if 1
      glBegin (GL_LINES);
      for (i = 0; i < npoints; i++)
        {
          /* inside rim */
          glVertex3f (pointsx0[i], pointsy0[i], -thick2);
          glVertex3f (pointsx0[i], pointsy0[i],  thick2);
          /* outside rim */
          glVertex3f (pointsx1[i], pointsy1[i], -thick2);
          glVertex3f (pointsx1[i], pointsy1[i],  thick2);
        }
      polys += npoints;
      glEnd();
#endif
    }
  else
    {
      /* top */
      glFrontFace(GL_CW);
      glNormal3f(0, 0, -1);
      glBegin (GL_QUAD_STRIP);
      for (i = 0; i < npoints; i++)
        {
          glVertex3f (pointsx0[i], pointsy0[i], -thick2);
          glVertex3f (pointsx1[i], pointsy1[i], -thick2);
        }
      polys += npoints;
      glEnd();

      /* bottom */
      glFrontFace(GL_CCW);
      glNormal3f(0, 0, 1);
      glBegin (GL_QUAD_STRIP);
      for (i = 0; i < npoints; i++)
        {
          glVertex3f (pointsx0[i], pointsy0[i], thick2);
          glVertex3f (pointsx1[i], pointsy1[i], thick2);
        }
      polys += npoints;
      glEnd();

      /* inside edge */
      glFrontFace(GL_CW);
      glBegin (GL_QUAD_STRIP);
      for (i = 0; i < npoints; i++)
        {
          glNormal3f (-pointsx0[i], -pointsy0[i],  0);
          glVertex3f ( pointsx0[i],  pointsy0[i],  thick2);
          glVertex3f ( pointsx0[i],  pointsy0[i], -thick2);
        }
      polys += npoints;
      glEnd();

      /* outside edge */
      glFrontFace(GL_CCW);
      glBegin (GL_QUADS);
      {
        for (i = 0; i < npoints-1; i++)
          {
            int ia = (i == 0 ? npoints-2 : i-1);
            int iz = (i == npoints-2 ? 0 : i+1);
            GLfloat  x = pointsx1[i];
            GLfloat  y = pointsy1[i];
            GLfloat xz = pointsx1[iz];
            GLfloat yz = pointsy1[iz];

            GLfloat nxa = normals[ia*2];   /* normal of [i-1 - i] face */
            GLfloat nya = normals[ia*2+1];
            GLfloat nx  = normals[i*2];    /* normal of [i - i+1] face */
            GLfloat ny  = normals[i*2+1];
            GLfloat nxz = normals[iz*2];    /* normal of [i+1 - i+2] face */
            GLfloat nyz = normals[iz*2+1];

            GLfloat anglea = vector_angle (nx, ny, 0, nxa, nya, 0);
            GLfloat anglez = vector_angle (nx, ny, 0, nxz, nyz, 0);
            GLfloat pointy = 0.6;

            if (anglea > pointy)
              {
                glNormal3f (nx, ny, 0);
                glVertex3f (x,  y,   thick2);
                glVertex3f (x,  y,  -thick2);
              }
            else
              {
                glNormal3f ((nxa + nx) / 2, (nya + ny) / 2, 0);
                glVertex3f (x,  y,   thick2);
                glVertex3f (x,  y,  -thick2);
              }

            if (anglez > pointy)
              {
                glNormal3f (nx, ny, 0);
                glVertex3f (xz, yz, -thick2);
                glVertex3f (xz, yz,  thick2);
              }
            else
              {
                glNormal3f ((nx + nxz) / 2, (ny + nyz) / 2, 0);
                glVertex3f (xz, yz, -thick2);
                glVertex3f (xz, yz,  thick2);
              }
          }
        polys += npoints;
      }
      glEnd();
    }

  /* Fill in the upper left hole...
   */
  {
    GLfloat th;
    npoints = 0;

    th = 338.0 * d2r;
    pointsx0[npoints] = r1c * cos(th) * dc->gasket_size;
    pointsy0[npoints] = r1c * sin(th) * dc->gasket_size;
    npoints++;
    pointsx0[npoints] = r4 * cos(th) * dc->gasket_size;
    pointsy0[npoints] = r4 * sin(th) * dc->gasket_size;
    npoints++;

    th = 344.0 * d2r;
    pointsx0[npoints] = r1c * cos(th) * dc->gasket_size;
    pointsy0[npoints] = r1c * sin(th) * dc->gasket_size;
    npoints++;
    pointsx0[npoints] = r4 * cos(th) * dc->gasket_size;
    pointsy0[npoints] = r4 * sin(th) * dc->gasket_size;

    if (! wire)
      {
        /* front wall */
        glNormal3f (0, 0, -1);
        glFrontFace(GL_CW);
        glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
        glVertex3f (pointsx0[0], pointsy0[0], -thick2);
        glVertex3f (pointsx0[1], pointsy0[1], -thick2);
        glVertex3f (pointsx0[3], pointsy0[3], -thick2);
        glVertex3f (pointsx0[2], pointsy0[2], -thick2);
        glEnd();
        polys++;

        /* back wall */
        glNormal3f (0, 0, 1);
        glFrontFace(GL_CCW);
        glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
        glVertex3f (pointsx0[0], pointsy0[0],  thick2);
        glVertex3f (pointsx0[1], pointsy0[1],  thick2);
        glVertex3f (pointsx0[3], pointsy0[3],  thick2);
        glVertex3f (pointsx0[2], pointsy0[2],  thick2);
        glEnd();
        polys++;
      }

    /* top wall */
    glFrontFace(GL_CW);
    glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
    glNormal3f (pointsx0[1], pointsy0[1], 0);
    glVertex3f (pointsx0[1], pointsy0[1],  thick2);
    glNormal3f (pointsx0[3], pointsy0[3], 0);
    glVertex3f (pointsx0[3], pointsy0[3],  thick2);
    glVertex3f (pointsx0[3], pointsy0[3], -thick2);
    glNormal3f (pointsx0[1], pointsy0[1], 0);
    glVertex3f (pointsx0[1], pointsy0[1], -thick2);
    glEnd();
    polys++;


    /* Now make a donut.
     */
    {
      int nsteps = (wire ? 12 : 64);
      GLfloat r0 = 0.04;
      GLfloat r1 = 0.070;
      GLfloat th, cth, sth;

      glPushMatrix ();

      th = ((339.0 + 343.0) / 2) * d2r;
      
      glTranslatef (r1b * cos(th) * dc->gasket_size,
                    r1b * sin(th) * dc->gasket_size,
                    0);

      npoints = 0;
      for (i = 0; i < nsteps; i++)
        {
          th = 2 * M_PI * i / nsteps;
          cth = cos (th) * dc->gasket_size;
          sth = sin (th) * dc->gasket_size;
          pointsx0[npoints] = r0 * cth;
          pointsy0[npoints] = r0 * sth;
          pointsx1[npoints] = r1 * cth;
          pointsy1[npoints] = r1 * sth;
          npoints++;
          polys++;
        }

      pointsx0[npoints] = pointsx0[0];
      pointsy0[npoints] = pointsy0[0];
      pointsx1[npoints] = pointsx1[0];
      pointsy1[npoints] = pointsy1[0];
      npoints++;

      if (wire)
        {
          glBegin (GL_LINE_LOOP);
          for (i = 0; i < npoints; i++)
            glVertex3f (pointsx0[i], pointsy0[i], -thick2);
          polys += npoints;
          glEnd();
          glBegin (GL_LINE_LOOP);
          for (i = 0; i < npoints; i++)
            glVertex3f (pointsx0[i], pointsy0[i],  thick2);
          polys += npoints;
          glEnd();
# if 0
          glBegin (GL_LINE_LOOP);
          for (i = 0; i < npoints; i++)
            glVertex3f (pointsx1[i], pointsy1[i], -thick2);
          polys += npoints;
          glEnd();
          glBegin (GL_LINE_LOOP);
          for (i = 0; i < npoints; i++)
            glVertex3f (pointsx1[i], pointsy1[i],  thick2);
          polys += npoints;
          glEnd();
# endif
        }
      else
        {
          /* top */
          glFrontFace(GL_CW);
          glNormal3f(0, 0, -1);
          glBegin (GL_QUAD_STRIP);
          for (i = 0; i < npoints; i++)
            {
              glVertex3f (pointsx0[i], pointsy0[i], -thick2);
              glVertex3f (pointsx1[i], pointsy1[i], -thick2);
            }
          polys += npoints;
          glEnd();

          /* bottom */
          glFrontFace(GL_CCW);
          glNormal3f(0, 0, 1);
          glBegin (GL_QUAD_STRIP);
          for (i = 0; i < npoints; i++)
            {
              glVertex3f (pointsx0[i], pointsy0[i],  thick2);
              glVertex3f (pointsx1[i], pointsy1[i],  thick2);
            }
          polys += npoints;
          glEnd();
        }

      /* inside edge */
      glFrontFace(GL_CW);
      glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
      for (i = 0; i < npoints; i++)
        {
          glNormal3f (-pointsx0[i], -pointsy0[i],  0);
          glVertex3f ( pointsx0[i],  pointsy0[i],  thick2);
          glVertex3f ( pointsx0[i],  pointsy0[i], -thick2);
        }
      polys += npoints;
      glEnd();

      glPopMatrix();
    }
  }


  /* Attach the bottom-right dingus...
   */
  {
    GLfloat w = 0.05;
    GLfloat h = 0.19;
    GLfloat th;

    glRotatef (49.5, 0, 0, 1);
    glScalef (dc->gasket_size, dc->gasket_size, 1);
    glTranslatef (0, (r0+r1)/2, 0);

    /* buried box */
    if (! wire)
      {
        glFrontFace(GL_CCW);
        glBegin (wire ? GL_LINE_STRIP : GL_QUADS);
        glNormal3f (0, 0, -1);
        glVertex3f (-w/2, -h/2, -thick2); glVertex3f (-w/2,  h/2, -thick2);
        glVertex3f ( w/2,  h/2, -thick2); glVertex3f ( w/2, -h/2, -thick2);
        glNormal3f (1, 0, 0);
        glVertex3f ( w/2, -h/2, -thick2); glVertex3f ( w/2,  h/2, -thick2);
        glVertex3f ( w/2,  h/2,  thick2); glVertex3f ( w/2, -h/2,  thick2);
        glNormal3f (0, 0, 1);
        glVertex3f ( w/2, -h/2,  thick2); glVertex3f ( w/2,  h/2,  thick2);
        glVertex3f (-w/2,  h/2,  thick2); glVertex3f (-w/2, -h/2,  thick2);
        glNormal3f (-1, 0, 0);
        glVertex3f (-w/2, -h/2,  thick2); glVertex3f (-w/2,  h/2,  thick2);
        glVertex3f (-w/2,  h/2, -thick2); glVertex3f (-w/2, -h/2, -thick2);
        polys++;
        glEnd();
      }

    npoints = 0;
    for (th = (wire ? 0 : -0.1);
         th <= M_PI + 0.1;
         th += (M_PI / (wire ? 5 : 32)))
      {
        pointsx0[npoints] = w/2 * cos(th);
        pointsy0[npoints] = w/2 * sin(th);
        npoints++;
        polys++;
      }

    /* front inside curve */
    glNormal3f (0, 0, -1);
    glFrontFace(GL_CW);
    glBegin (wire ? GL_LINE_STRIP : GL_TRIANGLE_FAN);
    if (! wire) glVertex3f (0, h/2, -thick2);
    for (i = 0; i < npoints; i++)
      glVertex3f (pointsx0[i], h/2 + pointsy0[i], -thick2);
    polys += npoints;
    glEnd();

    /* front outside curve */
    glFrontFace(GL_CCW);
    glBegin (wire ? GL_LINE_STRIP : GL_TRIANGLE_FAN);
    if (! wire) glVertex3f (0, -h/2, -thick2);
    for (i = 0; i < npoints; i++)
      glVertex3f (pointsx0[i], -h/2 - pointsy0[i], -thick2);
    polys += npoints;
    glEnd();

    /* back inside curve */
    glNormal3f (0, 0, 1);
    glFrontFace(GL_CCW);
    glBegin (wire ? GL_LINE_STRIP : GL_TRIANGLE_FAN);
    if (! wire) glVertex3f (0, h/2, thick2);
    for (i = 0; i < npoints; i++)
      glVertex3f (pointsx0[i], h/2 + pointsy0[i], thick2);
    polys += npoints;
    glEnd();

    /* back outside curve */
    glFrontFace(GL_CW);
    glBegin (wire ? GL_LINE_STRIP : GL_TRIANGLE_FAN);
    if (! wire) glVertex3f (0, -h/2, thick2);
    for (i = 0; i < npoints; i++)
      glVertex3f (pointsx0[i], -h/2 - pointsy0[i], thick2);
    polys += npoints;
    glEnd();

    /* inside curve */
    glFrontFace(GL_CCW);
    glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
    for (i = 0; i < npoints; i++)
      {
        glNormal3f (pointsx0[i], pointsy0[i], 0);
        glVertex3f (pointsx0[i], h/2 + pointsy0[i],  thick2);
        glVertex3f (pointsx0[i], h/2 + pointsy0[i], -thick2);
      }
    polys += npoints;
    glEnd();

    /* outside curve */
    glFrontFace(GL_CW);
    glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
    for (i = 0; i < npoints; i++)
      {
        glNormal3f (pointsx0[i], -pointsy0[i], 0);
        glVertex3f (pointsx0[i], -h/2 - pointsy0[i],  thick2);
        glVertex3f (pointsx0[i], -h/2 - pointsy0[i], -thick2);
      }
    polys += npoints;
    glEnd();
  }

  free (pointsx0);
  free (pointsy0);
  free (pointsx1);
  free (pointsy1);
  free (normals);

  glPopMatrix();
  return polys;
}

static int
make_frame (logo_configuration *dc, int wire)
{
  int polys = 0;
  int i, j;
  GLfloat x[20], y[20];
  GLfloat corner_cut = 0.5;

  glPushMatrix();
  glRotatef (90, 0, 1, 0);
  glScalef (4 * dc->frame_size,
            4 * dc->frame_size,
            4 * dc->frame_size);

  x[0] = -dc->frame_thickness;
  x[1] = -dc->frame_thickness * corner_cut;
  x[2] = 0;
  x[3] = 0.5 - dc->triangle_size;
  x[4] = 0.5;
  x[5] = 0.5 + dc->triangle_size;
  x[6] = 1;
  x[7] = 1 + dc->frame_thickness * corner_cut;
  x[8] = 1 + dc->frame_thickness;

  y[0] = -dc->frame_thickness;
  y[1] = -dc->frame_thickness * corner_cut;
  y[2] = 0;
  y[3] = dc->triangle_size;

  /* front and back
   */
  glTranslatef (-0.5, -0.5, dc->frame_depth / 4);
  if (! wire)
    for (j = 0; j <= 1; j++)
      {
        if (j) glTranslatef (0, 0, -dc->frame_depth / 2);
        glFrontFace (j ? GL_CCW : GL_CW);
        for (i = 0; i < 4; i++)
          {
            glNormal3f (0, 0, (j ? -1 : 1));
            glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
            glVertex3f (x[0], y[1], 0); glVertex3f (x[0], y[2], 0);
            glVertex3f (x[1], y[0], 0); glVertex3f (x[1], y[2], 0);
            glVertex3f (x[3], y[0], 0); glVertex3f (x[3], y[2], 0);
            glVertex3f (x[4], y[0], 0); glVertex3f (x[4], y[3], 0);
            glVertex3f (x[5], y[0], 0); glVertex3f (x[5], y[2], 0); 
            glVertex3f (x[7], y[0], 0); glVertex3f (x[7], y[2], 0);
            glVertex3f (x[8], y[1], 0); glVertex3f (x[8], y[2], 0);
            polys += 6;
            glEnd ();
            glTranslatef (0.5, 0.5, 0);
            glRotatef (90, 0, 0, 1);
            glTranslatef (-0.5, -0.5, 0);
          }
      }

  /* ledges
   */
  glFrontFace (GL_CCW);
  for (i = 0; i < 4; i++)
    {
      glNormal3f (0, 1, 0);
      glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
      glVertex3f (x[2], y[2], 0); glVertex3f (x[2], y[2], dc->frame_depth/2); 
      glVertex3f (x[3], y[2], 0); glVertex3f (x[3], y[2], dc->frame_depth/2); 
      glVertex3f (x[4], y[3], 0); glVertex3f (x[4], y[3], dc->frame_depth/2); 
      glVertex3f (x[5], y[2], 0); glVertex3f (x[5], y[2], dc->frame_depth/2); 
      glVertex3f (x[6], y[2], 0); glVertex3f (x[6], y[2], dc->frame_depth/2); 
      polys += 4;
      glEnd ();

      glNormal3f (0, -1, 0);
      glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
      glVertex3f (x[7], y[0], 0); 
      glVertex3f (x[7], y[0], dc->frame_depth/2); 
      glVertex3f (x[1], y[0], dc->frame_depth/2); 
      glVertex3f (x[1], y[0], 0); 
      polys++;
      glEnd ();

      glNormal3f (1, -1, 0);
      glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
      glVertex3f (x[8], y[1], 0); 
      glVertex3f (x[8], y[1], dc->frame_depth/2); 
      glVertex3f (x[7], y[0], dc->frame_depth/2); 
      glVertex3f (x[7], y[0], 0); 
      polys++;
      glEnd ();

      if (wire) 
        {
          glNormal3f (0, 1, 0);
          for (j = 0; j <= 1; j++)
            {
              glBegin (GL_LINE_STRIP);
              glVertex3f (x[2], y[2], j*dc->frame_depth/2);
              glVertex3f (x[3], y[2], j*dc->frame_depth/2);
              glVertex3f (x[4], y[3], j*dc->frame_depth/2);
              glVertex3f (x[5], y[2], j*dc->frame_depth/2);
              glVertex3f (x[6], y[2], j*dc->frame_depth/2);
              polys += 4;
              glEnd ();
            }
        }

      glTranslatef (0.5, 0.5, 0);
      glRotatef (90, 0, 0, 1);
      glTranslatef (-0.5, -0.5, 0);
    }

  glPopMatrix();
  return polys;
}


/* Make some pizza.
 */

#ifdef HAVE_TESS

typedef struct {
  GLdouble *points;
  int i;
} tess_out;


static void
tess_error_cb (GLenum errorCode)
{
  fprintf (stderr, "%s: tesselation error: %s\n",
           progname, gluErrorString(errorCode));
  exit (0);
}

static void
tess_combine_cb (GLdouble coords[3], GLdouble *d[4], GLfloat w[4], 
                 GLdouble **data_out)
{
  GLdouble *new = (GLdouble *) malloc (3 * sizeof(*new));
  new[0] = coords[0];
  new[1] = coords[1];
  new[2] = coords[2];
  *data_out = new;
}


#if 0
static void
tess_vertex_cb (void *vertex_data, void *closure)
{
  tess_out *to = (tess_out *) closure;
  GLdouble *v = (GLdouble *) vertex_data;
  to->points[to->i++] = v[0];
  to->points[to->i++] = v[1];
  to->points[to->i++] = v[2];
}
#endif

static void
tess_begin_cb (GLenum which)
{
  glBegin(which);
}

static void
tess_end_cb (void)
{
  glEnd();
}

#endif /* HAVE_TESS */


static int
make_pizza (logo_configuration *dc, int facetted, int wire)
{
  int polys = 0;
  int topfaces = (facetted ? 48 : 120);
  int discfaces = (facetted ? 12 : 120);
  int npoints = topfaces * 2 + 100;
  GLdouble *points = (GLdouble *) calloc (npoints * 3, sizeof(GLdouble));
  int nholes = 3;
  GLdouble *holes  = (GLdouble *) calloc (topfaces*nholes*3, sizeof(GLdouble));

  GLfloat step = M_PI * 2 / 6 / topfaces;
  GLfloat thick2 = (dc->gasket_thickness / dc->gasket_size) / 4;
  GLfloat th, x, y, s;
  int i, j, k;
  int endpoints;

# ifdef HAVE_TESS
  tess_out TO, *to = &TO;
  GLUtesselator *tess = gluNewTess();

  to->points = (GLdouble *) calloc (topfaces * 20, sizeof(GLdouble));
  to->i = 0;

#  ifndef  _GLUfuncptr
#   define _GLUfuncptr void(*)(void)
#  endif

  gluTessCallback(tess,GLU_TESS_BEGIN,      (_GLUfuncptr)tess_begin_cb);
  gluTessCallback(tess,GLU_TESS_VERTEX,     (_GLUfuncptr)glVertex3dv);
  gluTessCallback(tess,GLU_TESS_END,        (_GLUfuncptr)tess_end_cb);
  gluTessCallback(tess,GLU_TESS_COMBINE,    (_GLUfuncptr)tess_combine_cb);
  gluTessCallback(tess,GLU_TESS_ERROR,      (_GLUfuncptr)tess_error_cb);

  gluTessProperty (tess, GLU_TESS_BOUNDARY_ONLY, wire);
  gluTessProperty (tess,GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);

# endif /* HAVE_TESS */

  glPushMatrix();

  s = 1.9;
  glRotatef (180, 0, 0, 1);
  glScalef (s, s, s);
  glRotatef (90, 0, 1, 0);
  glTranslatef (-0.53, 0, 0);
  glRotatef (-30, 0, 0, 1);

  /* Compute the wedge */
  th = 0;
  j = 0;

  /* Edge 1 */
  {
    GLfloat edge[] = {
      0.000, 0.000,
      0.000, 0.210,
      0.042, 0.230,
      0.042, 0.616,
      0.000, 0.641,
    };
    for (i = 0; i < countof(edge)/2; i++)
      {
        points[j++] = edge[i*2+1];
        points[j++] = edge[i*2];
        points[j++] = 0;
      }
  }

  s = 0.798;  /* radius of end of slice, before crust gap */
  for (i = 0; i < topfaces; i++)
    {
      points[j++] = cos(th) * s;
      points[j++] = sin(th) * s;
      points[j++] = 0;
      th += step;
    }

  /* Edge 2 */
  {
    GLfloat edge[] = {
      0.613, 0.353,
      0.572, 0.376,
      0.455, 0.309,
      0.452, 0.260,
      0.332, 0.192,
      0.293, 0.216,
      0.178, 0.149,
      0.178, 0.102,
    };
    for (i = 0; i < countof(edge)/2; i++)
      {
        points[j++] = edge[i*2+1];
        points[j++] = edge[i*2];
        points[j++] = 0;
      }
    endpoints = j/3;
  }


  /* Draw the rim of the slice */
  glBegin (wire ? GL_LINES : GL_QUADS);
  x = points[0];
  y = points[1];
  for (i = (wire ? 0 : 1); i < endpoints; i++)
    {
      GLdouble *p = points + (i*3);

      do_normal (p[0], p[1],  -thick2,
                 p[0], p[1],   thick2,
                 x,    y,     thick2);
      if (!wire)
        {
          glVertex3f (x, y, -thick2);
          glVertex3f (x, y,  thick2);
        }
      glVertex3f (p[0], p[1],  thick2);
      glVertex3f (p[0], p[1], -thick2);
      x = p[0];
      y = p[1];
      polys++;
    }

  do_normal (points[0], points[1],  -thick2,
             points[0], points[1],   thick2,
             x,         y,           thick2);
  glVertex3f (x, y, -thick2);
  glVertex3f (x, y,  thick2);
  glVertex3f (points[0], points[1],  thick2);
  glVertex3f (points[0], points[1], -thick2);
  polys++;
  glEnd();

# ifndef HAVE_TESS
  if (wire)
    {
      /* Outline of slice */
      glBegin (GL_LINE_LOOP);
      for (i = 0; i < endpoints; i++)
        glVertex3f (points[i*3], points[i*3+1], -thick2);
      glEnd();
      glBegin (GL_LINE_LOOP);
      for (i = 0; i < endpoints; i++)
        glVertex3f (points[i*3], points[i*3+1], thick2);
      glEnd();
    }
# endif /* HAVE_TESS */

  /* Compute the holes */
  step = M_PI * 2 / discfaces;
  for (k = 0; k < nholes; k++)
    {
      GLdouble *p = holes + (discfaces * 3 * k);
      th = 0;
      j = 0;
      switch (k) {
        case 0: x = 0.34; y = 0.17; s = 0.05; break;
        case 1: x = 0.54; y = 0.17; s = 0.06; break;
        case 2: x = 0.55; y = 0.36; s = 0.06; break;
      default: abort(); break;
      }
      for (i = 0; i < discfaces; i++)
        {
          p[j++] = x + cos(M_PI*2 - th) * s;
          p[j++] = y + sin(M_PI*2 - th) * s;
          p[j++] = 0;
          th += step;
        }
    }


  /* Draw the inside rim of the holes */
  for (k = 0; k < nholes; k++)
    {
      GLdouble *p = holes + (discfaces * 3 * k);

      glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
      for (i = 0; i < discfaces; i++)
        {
          GLdouble *p2 = p + (i*3);
          if (i > 0)
            do_normal (p2[0],  p2[1],  -thick2,
                       p2[0],  p2[1],   thick2,
                       p2[-3], p2[-2],  thick2);
          glVertex3f (p2[0], p2[1], -thick2);
          glVertex3f (p2[0], p2[1],  thick2);
          polys++;
        }
      glVertex3f (p[0], p[1], -thick2);
      glVertex3f (p[0], p[1],  thick2);
      polys++;
      glEnd();
# ifndef HAVE_TESS
      if (wire)
        {
          /* Outline of holes */
          glBegin (GL_LINE_LOOP);
          for (i = 0; i < discfaces; i++)
            glVertex3f (p[i*3], p[i*3+1], -thick2);
          glEnd();
          glBegin (GL_LINE_LOOP);
          for (i = 0; i < discfaces; i++)
            glVertex3f (p[i*3], p[i*3+1], thick2);
          glEnd();
        }
# endif /* !HAVE_TESS */
    }

# ifdef HAVE_TESS
  glTranslatef (0, 0, -thick2);
  for (y = 0; y <= 1; y++)
    {
      if (y) glTranslatef (0, 0, thick2*2);

      /* A non-convex polygon */
      gluTessBeginPolygon (tess, to);

      glNormal3f (0, 0, (y > 0 ? 1 : -1));
      gluTessNormal (tess, 0, 0, (y > 0 ? 1 : -1));
      glFrontFace (GL_CCW);

      /* Tess the wedge */
      gluTessBeginContour (tess);
      for (i = 0; i < endpoints; i++)
        {
          GLdouble *p = points + (i*3);
          gluTessVertex (tess, p, p);
          polys++;
        }
      gluTessVertex (tess, points, points);
      gluTessEndContour (tess);

      /* Tess the holes */
      for (k = 0; k < nholes; k++)
        {
          GLdouble *p = holes + (discfaces * 3 * k);
          gluTessBeginContour (tess);
          for (i = 0; i < discfaces; i++)
            {
              GLdouble *p2 = p + (i*3);
              gluTessVertex (tess, p2, p2);
              polys++;
            }
          gluTessEndContour (tess);
        }

      gluTessEndPolygon (tess);
    }

  glTranslatef (0, 0, -thick2);

# else  /* !HAVE_TESS */
  if (! wire)
    {
      glTranslatef(0, 0, thick2);
      glNormal3f (0, 0, 1);
      glFrontFace (GL_CW);

      /* Sadly, jwzgl's glVertexPointer seems not to be recordable inside
         display lists. */
#  if 0
      glDisableClientState (GL_COLOR_ARRAY);
      glDisableClientState (GL_NORMAL_ARRAY);
      glDisableClientState (GL_TEXTURE_COORD_ARRAY);
      glEnableClientState (GL_VERTEX_ARRAY);
      glVertexPointer (3, GL_FLOAT, 0, dnapizza_triangles);
      glDrawArrays (GL_TRIANGLES, 0, countof (dnapizza_triangles) / 3);
#  else
      glBegin (GL_TRIANGLES);
      for (i = 0; i < countof (dnapizza_triangles); i += 3)
        glVertex3fv (dnapizza_triangles + i);
      glEnd();
#  endif

      glTranslatef(0, 0, -thick2*2);
      glNormal3f (0, 0, -1);
      glFrontFace (GL_CCW);

#  if 0
      glDrawArrays (GL_TRIANGLES, 0, countof (dnapizza_triangles) / 3);
#  else
      int i;
      glBegin (GL_TRIANGLES);
      for (i = 0; i < countof (dnapizza_triangles); i += 3)
        glVertex3fv (dnapizza_triangles + i);
      glEnd();
#  endif

      glTranslatef(0, 0, thick2);
    }
# endif /* !HAVE_TESS */


  /* Compute the crust */

  s = 0.861;  /* radius of inside of crust */
  step = M_PI * 2 / 6 / topfaces;
  th = 0;
  j = 0;
  for (i = 0; i < topfaces; i++)
    {
      points[j++] = cos(th) * s;
      points[j++] = sin(th) * s;
      points[j++] = 0;
      th += step;
    }

  s = 1;
  for (i = 0; i < topfaces; i++)
    {
      points[j++] = cos(th) * s;
      points[j++] = sin(th) * s;
      points[j++] = 0;
      th -= step;
    }

  /* Draw the rim of the crust */
  glFrontFace (GL_CCW);
  glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
  for (i = 0; i < topfaces * 2; i++)
    {
      GLdouble *p = points + (i*3);
      if (i == 0 || i == (topfaces*2)-1)
        glNormal3f (0, -1, 0);
      else if (i == topfaces-1 || i == topfaces)
        glNormal3f (0, 1, 0);
      else
        do_normal (p[-3], p[-2], thick2,
                   p[0], p[1],   thick2,
                   p[0], p[1],  -thick2);
      glVertex3f (p[0], p[1], -thick2);
      glVertex3f (p[0], p[1],  thick2);
      polys++;
    }
  glVertex3f (points[0], points[1], -thick2);
  glVertex3f (points[0], points[1],  thick2);
  polys++;
  glEnd();

  if (wire)
    {
      glBegin (GL_LINE_STRIP);
      for (i = 0; i < topfaces * 2; i++)
        {
          GLdouble *p = points + (i*3);
          glVertex3f (p[0], p[1], -thick2);
          polys++;
        }
      glVertex3f (points[0], points[1], -thick2);
      glEnd();

      glBegin (GL_LINE_STRIP);
      for (i = 0; i < topfaces * 2; i++)
        {
          GLdouble *p = points + (i*3);
          glVertex3f (p[0], p[1], thick2);
          polys++;
        }
      glVertex3f (points[0], points[1], thick2);
      glEnd();
    }

  /* Draw the top of the crust */
  if (! wire)
    {
      glFrontFace (GL_CW);
      glBegin (wire ? GL_LINE_STRIP : GL_QUAD_STRIP);
      glNormal3f (0, 0, -1);
      if (!wire)
        for (i = 0; i < topfaces; i++)
          {
            int ii = topfaces + (topfaces - i - 1);
            GLdouble *p1 = points + (i*3);
            GLdouble *p2 = points + (ii*3);
            glVertex3f (p1[0], p1[1], -thick2);
            glVertex3f (p2[0], p2[1], -thick2);
            polys++;
          }
      polys++;
      glEnd();

      /* Draw the bottom of the crust */
      glFrontFace (GL_CCW);
      glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
      glNormal3f (0, 0, 1);
      for (i = 0; i < topfaces; i++)
        {
          int ii = topfaces + (topfaces - i - 1);
          GLdouble *p1 = points + (i*3);
          GLdouble *p2 = points + (ii*3);
          glVertex3f (p1[0], p1[1], thick2);
          glVertex3f (p2[0], p2[1], thick2);
          polys++;
        }
      polys++;
      glEnd();
    }

# ifdef HAVE_TESS
  gluDeleteTess (tess);
  free (to->points);
# endif /* HAVE_TESS */

  free (points);
  free (holes);

  glPopMatrix();

  return polys;
}


# ifdef CW

/* Upcase string, convert Unicrud to ASCII, remove any non-letters.
 */
static char *
codeword_simplify_text (const char *s0)
{
  char *s1 = utf8_to_latin1 ((s0 ? s0 : ""), True);
  int L = strlen(s1);
  char *s2 = (char *) malloc (L + 10);
  char *s3 = s2;
  int i;
  for (i = 0; i < L; i++)
    {
      char c = s1[i];
      if (c >= 'a' && c <= 'z')
        c -= 'a'-'A';
      if (c >= 'A' && c <= 'Z')
        *s3++ = c;
    }
  *s3 = 0;
  if (! *s2)
    strcpy (s2, "CODEWORD");
  return s2;
}


static void
make_codeword_path (ModeInfo *mi)
{
  logo_configuration *dc = &dcs[MI_SCREEN(mi)];
  int letters = strlen (dc->codeword_text);

  GLfloat rtick = dc->codeword_spread;
  GLfloat iradius = rtick * dc->codeword_cap_size;

  int dial = 0;
  int letter;
  GLfloat last_r = 0;

  GLfloat inner_circum = M_PI * 2 * (iradius + rtick * 2);
  GLfloat outer_circum = M_PI * 2 * (iradius + rtick * (letters + 1));
  GLfloat facet_length = inner_circum / (26 * dc->codeword_facets);
  int outer_facets = ceil (outer_circum / facet_length);

  int *histo = (int *) calloc (letters * 26, sizeof(*histo));
  XYZ *points = (XYZ *) calloc (letters * outer_facets, sizeof (*points));
  int npoints = 0;

  for (letter = -1; letter < letters; letter++)
    {
      if (letter == -1)			/* Inner starting point */
        {
          points[npoints].x = iradius;
          points[npoints].y = 0;
          last_r = iradius;
          npoints++;
        }
      else				/* Add arc for this letter */
        {
          int direction = (letter & 1 ? -1 : 1);
          int v = (dc->codeword_text[letter] - 'A' + 1);
          int dial1 = dial + v * direction;

          GLfloat th;
          GLfloat th0 = M_PI * 2 / 26 * dial;
          GLfloat th1 = M_PI * 2 / 26 * dial1;
          GLfloat r  = iradius + rtick * (letter + 2);
          GLfloat circum = M_PI * 2 * r;
          GLfloat arc_length = circum * v / 26;
          int arc_facets = ceil (fabs (arc_length / facet_length));
          GLfloat facet_th = (th1 - th0) / arc_facets;

          if (arc_facets > outer_facets) abort();

          /* Let's put some intermediate facets on the crossbars too,
             so that the animation doesn't speed up on those. */
          {
            GLfloat rr;
            for (rr = last_r + facet_length;
                 rr <= r - facet_length;
                 rr += facet_length)
              {
                points[npoints].x = rr * cos (th0);
                points[npoints].y = rr * sin (th0);
                npoints++;
              }
            last_r = r;
          }


          for (th = th0;
               (th0 < th1
                ? th <= th1 + facet_th
                : th >= th1 + facet_th);
               th += facet_th)
            {
              GLfloat th2 = th;
              if (th0 < th1 && th > th1)
                th2 = th1;
              if (th0 > th1 && th < th1)
                th2 = th1;
              points[npoints].x = r * cos (th2);
              points[npoints].y = r * sin (th2);

              /* Ugh, add point only if it differs from prev.
                 Not sure how this happens. */
              if (npoints == 0 ||
                  points[npoints-1].x != points[npoints].x ||
                  points[npoints-1].y != points[npoints].y)
                npoints++;
            }

          /* Mark up the histo array to find the outer border. */
          {
            int i;
            for (i = dial;
                 (direction > 0
                  ? i <= dial1
                  : i >= dial1);
                  i += direction)
              {
                int x = (i + 26) % 26;
                int y;
                for (y = 0; y <= letter; y++)
                  histo[y * 26 + x]++;
              }
          }

          dc->codeword_text_points[letter] = npoints;

          dial = dial1;
        }
    }

  if (npoints >= letters * outer_facets) abort();

# if 0
  { /* Print histo */
    int x, y;
    for (y = 0; y < letters; y++)
      {
        fprintf (stderr, "%2d: ", y);
        for (x = 0; x < 26; x++)
          fprintf (stderr, "%x", histo[y * 26 + x]);
        fprintf (stderr, "\n");
      }
    fprintf (stderr, "\n");
  }
# endif


  /* Find a gap in the outer edge, to draw guide dots. */
  {
    int x, y;
    int last_row = letters;
    int start_dial = -1, end_dial = -1;

    for (y = letters-1; y >= 0; y--)
      {
        for (x = 0; x < 26; x++)
          {
            if (histo[y * 26 + x] == 0)
              {
                if (last_row != y)
                  start_dial = end_dial = -1;
                last_row = y;
                if (start_dial == -1)
                  start_dial = x;
                end_dial = x;
              }
          }
      }

    if (last_row < letters-1 && start_dial >= 0)
      {
        GLfloat r = iradius + rtick * (last_row + 2);
        int i;

        dc->codeword_nguides = 0;
        dc->codeword_guides = (XYZ *) 
          calloc (end_dial - start_dial + 1, sizeof (*dc->codeword_guides));
        for (i = start_dial; i <= end_dial; i++)
          {
            GLfloat th = i * M_PI * 2 / 26;
            GLfloat x = r * cos (th);
            GLfloat y = r * sin (th);
            dc->codeword_guides[dc->codeword_nguides].x = x;
            dc->codeword_guides[dc->codeword_nguides].y = y;
            dc->codeword_nguides++;
          }
      }
    free (histo);
    histo = 0;
  }

  dc->codeword_path_npoints = npoints;
  dc->codeword_path = points;
}


static int
draw_codeword_cap (ModeInfo *mi)
{
  logo_configuration *dc = &dcs[MI_SCREEN(mi)];
  int wire = MI_IS_WIREFRAME(mi);
  int polys = 0;

  int segments  = dc->codeword_disc_facets;
  GLfloat size  = dc->codeword_spread * dc->codeword_cap_size;
  GLfloat width = dc->codeword_line_width / 2;
  GLfloat thick = dc->codeword_thickness  / 2;
  GLfloat r1 = size + width/2;
  GLfloat r2 = size - width/2;
  GLfloat facet, th, z;

  if (wire) segments = 12;
  facet = M_PI * 2 / segments;

  glPushMatrix();

  /* Top and bottom */

  for (z = -thick; z <= thick; z += thick*2)
    {
      glNormal3f (0, 0, (z < 0 ? -1 : 1));
      glFrontFace (z < 0 ? GL_CCW : GL_CW);

      glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
      for (th = 0; th <= M_PI*2; th += facet)
        {
          GLfloat x = cos (th);
          GLfloat y = sin (th);
          glVertex3f (r1 * x, r1 * y, z);
          glVertex3f (r2 * x, r2 * y, z);
        }
      glEnd();

      if (wire)
        {
          glBegin (GL_LINE_LOOP);
          for (th = 0; th <= M_PI*2; th += facet)
            {
              GLfloat x = cos (th);
              GLfloat y = sin (th);
              glVertex3f (r1 * x, r1 * y, z);
            }
          glEnd();
          glBegin (GL_LINE_LOOP);
          for (th = 0; th <= M_PI*2; th += facet)
            {
              GLfloat x = cos (th);
              GLfloat y = sin (th);
              glVertex3f (r2 * x, r2 * y, z);
            }
          glEnd();
        }
    }

  /* Inside and outside */

  for (z = -1; z <= 1; z += 2)
    {
      glFrontFace (z < 0 ? GL_CCW : GL_CW);

      glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
      for (th = 0; th <= M_PI*2; th += facet)
        {
          GLfloat th1 = th + facet;
          GLfloat x0 = cos (th);
          GLfloat y0 = sin (th);
          GLfloat x1 = cos (th1);
          GLfloat y1 = sin (th1);
          GLfloat r = z < 0 ? r1 : r2;

          if (z < 0)
            do_normal (r * x0, r * y0,  thick,
                       r * x0, r * y0, -thick,
                       r * x1, r * y1, -thick);
          else
            do_normal (r * x1, r * y1,  thick,
                       r * x1, r * y1, -thick,
                       r * x0, r * y0, -thick);

          glVertex3f (r * x0, r * y0,  thick);
          glVertex3f (r * x0, r * y0, -thick);
        }
      glEnd();
    }

  glPopMatrix();

  return polys;
}


static int
draw_codeword_guides (ModeInfo *mi, GLfloat anim_ratio)
{
  logo_configuration *dc = &dcs[MI_SCREEN(mi)];
  int wire = MI_IS_WIREFRAME(mi);
  int polys = 0;

  int segments = dc->codeword_disc_facets;
  GLfloat s = dc->codeword_line_width / 2;
  GLfloat each = 1.0 / dc->codeword_nguides;
  int i;

  if (wire) segments = 6;

  for (i = 0; i < dc->codeword_nguides; i++)
    {
      GLfloat ratio;
      if      (anim_ratio <  i*each)     ratio = 0;
      else if (anim_ratio >= (i+1)*each) ratio = 1;
      else    ratio = (anim_ratio - i*each) / each;

      if (ratio <= 0) continue;
      if (ratio == 0) ratio = 0.001;

      glPushMatrix();
      glTranslatef (dc->codeword_guides[i].x,
                    dc->codeword_guides[i].y,
                    dc->codeword_guides[i].z);

      glScalef (ratio, ratio, ratio);

      /* If the line width and thickness are pretty close to each other,
         use spheres. Otherwise use tubes. 
       */
      if (dc->codeword_thickness < dc->codeword_line_width * 1.3 &&
          dc->codeword_thickness > dc->codeword_line_width / 1.3)
        {
          glScalef (s, s, s);
          glFrontFace (GL_CCW);
          polys += unit_sphere (segments, segments, wire);
        }
      else
        {
          polys += tube (0, 0, -dc->codeword_thickness / 2,
                         0, 0,  dc->codeword_thickness / 2,
                         s, 0, segments, True, True, wire);
        }

      glPopMatrix();
    }

  return polys;
}


/* Compute the characters at which the cursor is currently pointing,
   and render it on the logo. 
 */
static void
codeword_text_output (ModeInfo *mi, GLfloat anim_ratio)
{
  logo_configuration *dc = &dcs[MI_SCREEN(mi)];
  int i;
  int L = strlen (dc->codeword_text);
  int point = dc->codeword_path_npoints * anim_ratio;
  Bool hit = False;

  if (dc->anim_state == CODEWORD_BLANK)
    point = 0;

  for (i = 0; i < L; i++)
    {
      if (point >= dc->codeword_text_points[i])
        dc->codeword_text_out[i] = dc->codeword_text[i];
      else if (hit)
        dc->codeword_text_out[i] = 0;
      else
        {
          int steps = dc->codeword_text[i] - 'A' + 1;
          int last = (i > 0 ? dc->codeword_text_points[i-1] : 0);
          double ratio = ((point - last) /
                          (double) (dc->codeword_text_points[i] - last));
          char chr = 'A' + (ratio * steps);
          if (ratio < 0.1) chr = 0;
          dc->codeword_text_out[i] = chr;
          hit = True;
        }
    }
  dc->codeword_text_out[i] = 0;

  if (*dc->codeword_text_out &&
      !strcmp (dc->codeword_text, "CODEWORD"))
    {
      int i;
      int L2 = strlen (dc->codeword_text_out);
      GLfloat ss = 0.01;
      int ascent, descent;

      glPushMatrix();
      glColor4fv (dc->codeword_color);
      glRotatef (90, 0, 1, 0);
      glRotatef (-90, 0, 0, 1);

      for (i = 0; i < L2; i++)
        {
          XCharStruct e;
          char buf[2];
          glPushMatrix();
          glRotatef ((i + 0.5) * 360 / 26.0, 0, 0, 1);

# if 0
          {
            GLfloat th;
            glDisable(GL_LIGHTING);
            glBegin(GL_LINES);
            glVertex3f(0,0,0);
            glVertex3f(0,-4,0);
            glEnd();
            glBegin(GL_LINE_STRIP);
            for (th = M_PI * 1.45; th < M_PI * 1.55; th += 0.1)
              {
                GLfloat r = 3.85;
                glVertex3f (r * cos(th), r * sin(th), 0);
              }
            glEnd();
            if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
          }
# endif

          glTranslatef (0, -dc->codeword_spread * (L - 1), 0);
          glScalef (ss, ss, ss);
          buf[0] = dc->codeword_text_out[i] + ('a' - 'A');
          buf[1] = 0;
          texture_string_metrics (dc->font, buf, &e, &ascent, &descent);

          glTranslatef (-e.width * 1.0,
                        -(ascent + descent + e.descent * 2.4), /* #### WTF */
                          0);

          glScalef (2, 2, 2);

# if 0
          glDisable(GL_LIGHTING);
          glBegin(GL_LINE_LOOP);
          glVertex3f(0, 0, 0);
          glVertex3f(e.width, 0, 0);
          glVertex3f(e.width, e.ascent + e.descent, 0);
          glVertex3f(0, e.ascent + e.descent, 0);
          glEnd();
          if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
# endif

          glDisable(GL_CULL_FACE);  /* tell texfont.c to draw both sides */
          print_texture_string (dc->font, buf);
          glEnable(GL_CULL_FACE);

          glPopMatrix();
        }
      glPopMatrix();
    }
}


/* Convert the precomputed path to a thick line of polygons.
   We could actually precompute all of these polygons too,
   but it's fast enough.
 */
static int
draw_codeword_path (ModeInfo *mi)
{
  logo_configuration *dc = &dcs[MI_SCREEN(mi)];
  int wire = MI_IS_WIREFRAME(mi);
  int polys = 0;

  GLfloat anim_ratio = (dc->anim_state == CODEWORD_IN ?      dc->anim_ratio :
                        dc->anim_state == CODEWORD_OUT ? 1 - dc->anim_ratio :
                        dc->anim_state == CODEWORD_BLANK ? 0 :
                        1);
  int last_anim_point = 0;

  GLfloat width = dc->codeword_line_width / 2;
  GLfloat thick = dc->codeword_thickness  / 2;
  int i, k;
  GLfloat j;

  int quad_size = (dc->codeword_path_npoints + 1) * 2;
  XYZ *quads = (XYZ *) calloc (quad_size, sizeof(*quads));
  XYZ *norms = (XYZ *) calloc (quad_size, sizeof(*norms));
  int nquads = 0;

  for (i = 0; i < dc->codeword_path_npoints; i++)
    {
      XYZ p1 = dc->codeword_path[i];
      XYZ p2 = (i < dc->codeword_path_npoints-1
                ? dc->codeword_path[i+1]
                : dc->codeword_path[i-1]);
      XYZ p1a, p1b;

      XYZ n;          /* normal of the first line segment */
      n.x = -(p2.y - p1.y);
      n.y =  (p2.x - p1.x);
      n.z =  0;
      normalize (&n);

      if (i == 0)
        {
          p1a.x = p1.x - width / 2 * n.x;
          p1a.y = p1.y - width / 2 * n.y;
          p1a.z = 0;

          p1b.x = p1.x + width / 2 * n.x;
          p1b.y = p1.y + width / 2 * n.y;
          p1b.z = 0;
        }
      else if (i == dc->codeword_path_npoints - 1)
        {
          p1b.x = p1.x - width / 2 * n.x;
          p1b.y = p1.y - width / 2 * n.y;
          p1b.z = 0;

          p1a.x = p1.x + width / 2 * n.x;
          p1a.y = p1.y + width / 2 * n.y;
          p1a.z = 0;
        }
      else
        {
          XYZ p0 = dc->codeword_path[i-1];

          XYZ t, t0, t1;  /* tangent of corner between two line segments */
          XYZ m;	  /* miter line: normal of tangent */
          GLfloat d;	  /* length of miter */

          t0.x = p2.x - p1.x;
          t0.y = p2.y - p1.y;
          t0.z = p2.z - p1.z;
          normalize (&t0);

          t1.x = p1.x - p0.x;
          t1.y = p1.y - p0.y;
          t1.z = p1.z - p0.z;
          normalize (&t1);

          t.x = t0.x + t1.x;
          t.y = t0.y + t1.y;
          t.z = t0.z + t1.z;
          normalize (&t);

          m.x = -t.y;
          m.y =  t.x;
          m.z =  0;

          /* find length of miter by projecting it on one of the normals */
          d = width / 2 / dot (m, n);

          p1a.x = p1.x - d * m.x;
          p1a.y = p1.y - d * m.y;
          p1a.z = 0;

          p1b.x = p1.x + d * m.x;
          p1b.y = p1.y + d * m.y;
          p1b.z = 0;
        }

      quads[nquads++] = p1a;
      quads[nquads++] = p1b;

      if (nquads >= quad_size) abort();

      if (i / (double) dc->codeword_path_npoints > anim_ratio)
        break;

      last_anim_point = i;
    }


  /* Compute normals for each point along the interior edge */
  for (k = 0; k <= 1; k++)
    {
      for (i = k; i < nquads-2; i += 2)
        {
          XYZ p1a = quads[i];
          XYZ p2a = quads[i+2];
          XYZ p1b = p1a;
          XYZ p2b = p2a;
          p1a.z =  thick;  /* a: top */
          p1b.z = -thick;  /* b: bottom */
          p2a.z =  thick;
          p2b.z = -thick;

          norms[i] = (k == 0
                      ? calc_normal (p1a, p1b, p2b)
                      : calc_normal (p2a, p2b, p1a));
        }
    }

  glPushMatrix();
  glColor4fv (dc->codeword_color);
  glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, dc->codeword_color);

  {
    GLfloat s = (MI_WIDTH(mi) < MI_HEIGHT(mi)
                 ? (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi))
                 : 1);
    glScalef (s, s, s);
  }

  codeword_text_output (mi, anim_ratio);

  glRotatef (90, 1, 0, 0);
  glRotatef (90, 0, 1, 0);
  glRotatef (-90, 0, 0, 1);
  glScalef (0.8, 0.8, 0.8);

  glNormal3f (0, 0, -1);

  if (anim_ratio <= 0)
    {
      polys += draw_codeword_cap (mi);
      goto DONE;
    }

# if 0
  glColor3f (1, 0, 0);
  glBegin(GL_LINE_STRIP);
  for (i = 0; i < dc->codeword_path_npoints; i++)
    {
      glVertex3f (dc->codeword_path[i].x,
                  dc->codeword_path[i].y,
                  dc->codeword_path[i].z);
      polys++;
    }
  glEnd();
  glColor4fv (dc->codeword_color);
# endif

  if (wire)
    {
      int k;
      GLfloat j;
      for (i = 0; i <= 1; i++)
        for (j = -thick; j <= thick; j += thick*2)
          {
            glBegin (GL_LINE_STRIP);
            for (k = i; k < nquads; k += 2)
              {
                glVertex3f (quads[k].x, quads[k].y, j);
                polys++;
              }
            glEnd();
          }
    }

  /* Top and bottom */

  for (j = -thick; j <= thick; j += thick*2)
    {
      if (j < 0)
        {
          glNormal3f (0, 0, -1);
          glFrontFace (GL_CW);
        }
      else
        {
          glNormal3f (0, 0, 1);
          glFrontFace (GL_CCW);
        }
      glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
      for (i = 0; i < nquads; i += 2)
        {
          glVertex3f (quads[i+1].x, quads[i+1].y, j);
          glVertex3f (quads[i].x,   quads[i].y,   j);
          polys++;
        }
      glEnd();
    }

  /* Edges */

  for (k = 0; k <= 1; k++)
    {
      if (k > 0)
        {
          glNormal3f (0, 0, -1);
          glFrontFace (GL_CW);
        }
      else
        {
          glNormal3f (0, 0, 1);
          glFrontFace (GL_CCW);
        }

      glBegin (wire ? GL_LINES : GL_QUADS);
      for (i = k; i < nquads; i += 2)
        {
          XYZ p1a = quads[i];
          XYZ p2a = (i < nquads-2) ? quads[i+2] : p1a;
          XYZ p1b = p1a;
          XYZ p2b = p2a;

          XYZ n1 = norms[i];
          XYZ n2 = (i < nquads-2) ? norms[i+2] : n1;

          /* If the two normals are very similar, smooth the face.
             If they are different, it's a sharp turn, and use the
             same normal for both edges (not quite right, but close).
          */
          GLfloat angle = vector_angle (n1.x, n1.y, n1.z,
                                        n2.x, n2.y, n2.z);
          GLfloat pointy = 0.8;

          p1a.z =  thick;
          p1b.z = -thick;
          p2a.z =  thick;
          p2b.z = -thick;

          glNormal3f (n1.x, n1.y, n1.z);
          glVertex3f (p1a.x, p1a.y, p1a.z);
          glVertex3f (p1b.x, p1b.y, p1b.z);

          if (angle < pointy)
            glNormal3f (n2.x, n2.y, n2.z);
          glVertex3f (p2b.x, p2b.y, p2b.z);
          glVertex3f (p2a.x, p2a.y, p2a.z);
          polys++;
        }
      glEnd();
    }


  /* Only draw the guides when the path is almost complete;
     fade them in and out based on completeness. */
  {
    GLfloat size = 0.95;
    GLfloat r = (anim_ratio > size
                 ? (anim_ratio - size) / (1 - size)
                 : 0);
    polys += draw_codeword_guides (mi, r);
  }


  /* Draw the start and end caps */
  {
    int i;
    GLfloat x, y, z, x2, y2, z2, X, Y, Z;
    GLfloat r = dc->codeword_spread * dc->codeword_cap_size;

    i = 0;
    x = dc->codeword_path[i].x;
    y = dc->codeword_path[i].y;
    z = dc->codeword_path[i].z;

    x -= r;

    glPushMatrix();
    glTranslatef (x, y, z);
    polys += draw_codeword_cap (mi);
    glPopMatrix();

    /* end cap */

    i = last_anim_point + 1;
    if (i > dc->codeword_path_npoints - 1)
      i = dc->codeword_path_npoints - 1;

    x = dc->codeword_path[i].x;
    y = dc->codeword_path[i].y;
    z = dc->codeword_path[i].z;

    i--;
    x2 = dc->codeword_path[i].x;
    y2 = dc->codeword_path[i].y;
    z2 = dc->codeword_path[i].z;

    X = (x2 - x);
    Y = (y2 - y);
    Z = (z2 - z);

    glPushMatrix();
    glTranslatef (x, y, z);
    glRotatef (-atan2 (X, Y)               * (180 / M_PI), 0, 0, 1);
    glRotatef ( atan2 (Z, sqrt(X*X + Y*Y)) * (180 / M_PI), 1, 0, 0);
    glTranslatef (0, -r, 0);
    polys += draw_codeword_cap (mi);
    glPopMatrix();
  }

 DONE:

  glPopMatrix();

  free (quads);
  free (norms);

  return polys;
}

#endif /* CW */


ENTRYPOINT void
reshape_logo (ModeInfo *mi, int width, int height)
{
# ifdef DEBUG
  logo_configuration *dc = &dcs[MI_SCREEN(mi)];
# endif
  GLfloat h = (GLfloat) height / (GLfloat) width;
  GLfloat persp = 64;  /* 30 */
  GLfloat pos   = 13;  /* 30 */

# ifdef DEBUG
  persp += dc->persp_off;
  pos += dc->pos_off;
# endif

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

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective (persp, 1/h, 1, 100);

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

  {
    GLfloat s = (MI_WIDTH(mi) < MI_HEIGHT(mi)
                 ? (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi))
                 : 1);
    glScalef (s, s, s);
  }

  glClear(GL_COLOR_BUFFER_BIT);
}


static void
gl_init (ModeInfo *mi)
{
/*  logo_configuration *dc = &dcs[MI_SCREEN(mi)]; */
  int wire = MI_IS_WIREFRAME(mi);

  GLfloat position[]  = {0, 0, 0, 0};
  GLfloat direction[] = {3, -1, -3};

  position[0] = -direction[0];
  position[1] = -direction[1];
  position[2] = -direction[2];

  if (!wire)
    {
      glLightfv(GL_LIGHT0, GL_POSITION, position);
      glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, direction);
      glShadeModel(GL_SMOOTH);
      glEnable(GL_NORMALIZE);
      glEnable(GL_CULL_FACE);
      glEnable(GL_LIGHTING);
      glEnable(GL_LIGHT0);
      glEnable(GL_DEPTH_TEST);
    }
}


ENTRYPOINT void 
init_logo (ModeInfo *mi)
{
  logo_configuration *dc;
  int do_gasket = get_boolean_resource(mi->dpy, "doGasket", "Boolean");
  int do_helix = get_boolean_resource(mi->dpy, "doHelix", "Boolean");
  int do_ladder = (do_helix && 
                   get_boolean_resource(mi->dpy, "doLadder", "Boolean"));
  int do_frame = get_boolean_resource(mi->dpy, "doFrame", "Boolean");
  GLfloat helix_rot = 147.0;

  if (!do_gasket && !do_helix)
    {
      fprintf (stderr, "%s: no helix or gasket?\n", progname);
      exit (1);
    }

  MI_INIT (mi, dcs);

  dc = &dcs[MI_SCREEN(mi)];

  if ((dc->glx_context = init_GL(mi)) != NULL) {
    gl_init(mi);
    reshape_logo (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
  }

  dc->wall_facets    = get_integer_resource(mi->dpy, "wallFacets",  "Integer");
  dc->bar_facets     = get_integer_resource(mi->dpy, "barFacets",   "Integer");
  dc->clockwise      = get_boolean_resource(mi->dpy, "clockwise",   "Boolean");
  dc->turns          = get_float_resource(mi->dpy, "turns",         "Float");
  dc->turn_spacing   = get_float_resource(mi->dpy, "turnSpacing",   "Float");
  dc->bar_spacing    = get_float_resource(mi->dpy, "barSpacing",    "Float");
  dc->wall_height    = get_float_resource(mi->dpy, "wallHeight",    "Float");
  dc->wall_thickness = get_float_resource(mi->dpy, "wallThickness", "Float");
  dc->bar_thickness  = get_float_resource(mi->dpy, "barThickness",  "Float");
  dc->wall_taper     = get_float_resource(mi->dpy, "wallTaper",     "Float");

  dc->gasket_size      = get_float_resource(mi->dpy,"gasketSize",     "Float");
  dc->gasket_depth     = get_float_resource(mi->dpy,"gasketDepth",    "Float");
  dc->gasket_thickness = get_float_resource(mi->dpy,"gasketThickness","Float");

  dc->frame_size      = get_float_resource(mi->dpy, "frameSize",      "Float");
  dc->frame_depth     = get_float_resource(mi->dpy, "frameDepth",     "Float");
  dc->frame_thickness = get_float_resource(mi->dpy, "frameThickness", "Float");
  dc->triangle_size   = get_float_resource(mi->dpy, "triangleSize",   "Float");

  dc->speed           = get_float_resource(mi->dpy, "speed",          "Float");
# ifdef CW
  dc->codeword_text   = get_string_resource(mi->dpy, "text",         "String");
  dc->codeword_text   = codeword_simplify_text (dc->codeword_text);
  dc->codeword_text_out =
    calloc (strlen(dc->codeword_text) + 1, sizeof(*dc->codeword_text_out));
  dc->codeword_text_points =
    (int *) calloc (strlen(dc->codeword_text) + 1,
                    sizeof(*dc->codeword_text_points));

  dc->codeword_facets = get_integer_resource(mi->dpy, "cwFacets",  "Integer");
  dc->codeword_disc_facets = get_integer_resource(mi->dpy,
                                                  "cwDiscFacets",  "Integer");
  dc->codeword_spread = get_float_resource(mi->dpy, "cwSpread",    "Float");
  dc->codeword_line_width = get_float_resource(mi->dpy, "cwLineWidth", "Float");
  dc->codeword_thickness  = get_float_resource(mi->dpy, "cwThickness", "Float");
  dc->codeword_cap_size = get_float_resource(mi->dpy, "cwCapSize",     "Float");
# endif

  {
    char *s = get_string_resource (MI_DISPLAY (mi), "mode", "String");
    if (!s || !*s || !strcasecmp (s, "helix"))
      dc->mode = HELIX;
    else if (!strcasecmp (s, "pizza"))
      dc->mode = PIZZA;
    else if (!strcasecmp (s, "both"))
      dc->mode = HELIX_AND_PIZZA;
# ifdef CW
    else if (!strcasecmp (s, "codeword"))
      dc->mode = CODEWORD_IN;
# endif
    else
      {
        fprintf (stderr,
               "%s: mode must be helix, pizza or both, not \"%s\"\n", 
                 progname, s);
        exit (1);
      }
    if (s) free (s);

    dc->anim_state = (dc->mode == HELIX_AND_PIZZA
                      ? ((random() & 1) ? HELIX : PIZZA)
                      : dc->mode);
    dc->anim_ratio = 0;
  }

# ifdef CW
  if (dc->mode == CODEWORD_IN)
    dc->font = load_texture_font (MI_DISPLAY(mi), "cwFont");
# endif

# ifdef DEBUG
    dc->label_font = load_texture_font (MI_DISPLAY(mi), "fpsFont");
# endif

  {
    XColor xcolor;

    char *color_name =
      get_string_resource (mi->dpy, "foreground", "Foreground");
    char *s2;
    for (s2 = color_name + strlen(color_name) - 1; s2 > color_name; s2--)
      if (*s2 == ' ' || *s2 == '\t')
        *s2 = 0;
      else
        break;

    if (! XParseColor (MI_DISPLAY(mi), mi->xgwa.colormap, color_name, &xcolor))
      {
        fprintf (stderr, "%s: can't parse color %s\n", progname, color_name);
        exit (1);
      }

    dc->color[0] = xcolor.red   / 65535.0;
    dc->color[1] = xcolor.green / 65535.0;
    dc->color[2] = xcolor.blue  / 65535.0;
    dc->color[3] = 1.0;

    free (color_name);
    color_name = get_string_resource (mi->dpy, "cwForeground", "Foreground");
    for (s2 = color_name + strlen(color_name) - 1; s2 > color_name; s2--)
      if (*s2 == ' ' || *s2 == '\t')
        *s2 = 0;
      else
        break;

    if (! XParseColor (MI_DISPLAY(mi), mi->xgwa.colormap, color_name, &xcolor))
      {
        fprintf (stderr, "%s: can't parse color %s\n", progname, color_name);
        exit (1);
      }
    free (color_name);

# ifdef CW
    dc->codeword_color[0] = xcolor.red   / 65535.0;
    dc->codeword_color[1] = xcolor.green / 65535.0;
    dc->codeword_color[2] = xcolor.blue  / 65535.0;
    dc->codeword_color[3] = 1.0;

    color_name = get_string_resource (mi->dpy, "cwBackground", "Background");
    for (s2 = color_name + strlen(color_name) - 1; s2 > color_name; s2--)
      if (*s2 == ' ' || *s2 == '\t')
        *s2 = 0;
      else
        break;

    if (! XParseColor (MI_DISPLAY(mi), mi->xgwa.colormap, color_name, &xcolor))
      {
        fprintf (stderr, "%s: can't parse color %s\n", progname, color_name);
        exit (1);
      }
    free (color_name);

    dc->codeword_bg[0] = xcolor.red   / 65535.0;
    dc->codeword_bg[1] = xcolor.green / 65535.0;
    dc->codeword_bg[2] = xcolor.blue  / 65535.0;
    dc->codeword_bg[3] = 1.0;
# endif /* CW */
  }

  dc->trackball = gltrackball_init (False);

  dc->gasket_spinnerx.probability = 0.1;
  dc->gasket_spinnery.probability = 0.1;
  dc->gasket_spinnerz.probability = 1.0;
  dc->gasket_spinnerx.easement    = 0.08;
  dc->gasket_spinnery.easement    = 0.08;
  dc->gasket_spinnerz.easement    = 0.08;

  dc->helix_spinnerz.probability  = 0.6;
  dc->helix_spinnerz.easement     = 0.2;

  dc->pizza_spinnerz.probability  = 0.6;
  dc->pizza_spinnery.probability  = 0.6;
  dc->pizza_spinnerz.easement     = 0.2;
  dc->pizza_spinnery.easement     = 0.2;

  dc->frame_spinner.probability   = 5.0;
  dc->frame_spinner.easement      = 0.2;

  dc->scene_spinnerx.probability  = 0.1;
  dc->scene_spinnery.probability  = 0.0;
  dc->scene_spinnerx.easement     = 0.1;
  dc->scene_spinnery.easement     = 0.1;

# ifdef CW
  if (dc->mode == CODEWORD_IN)
    {
      double tilt_speed = 0.003;
      dc->scene_rot = make_rotator (0, 0, 0, 0, tilt_speed, True);
    }
# endif

  /* start the frame off-screen */
  dc->frame_spinner.spinning_p = True;
  dc->frame_spinner.position = 0.3;
  dc->frame_spinner.speed = 0.001;

  if (dc->speed > 0)    /* start off with the gasket in motion */
    {
      dc->gasket_spinnerz.spinning_p = True;
      dc->gasket_spinnerz.speed = (0.002
                                   * ((random() & 1) ? 1 : -1)
                                   * dc->speed);
    }

# ifdef DXF_OUTPUT_HACK
  {
#  if 0
    dc->frame_depth = dc->gasket_depth;
    dxf_layer = 1;
    dxf_color = 3;
    dxf_start();
    glPushMatrix();
    glRotatef(90, 1, 0, 0);
    glRotatef(90, 0, 0, 1);
    make_pizza (dc, 0, 0);

    glPushMatrix();
    glRotatef(helix_rot, 0, 0, 1);
    make_ladder (dc, 0, 0);
    make_helix  (dc, 0, 0);
    glRotatef (180, 0, 0, 1);
    make_helix  (dc, 0, 0);
    glPopMatrix();

    dxf_layer++;
    make_gasket (dc, 0);
    dxf_layer++;
    make_frame (dc, 0);
    glPopMatrix();
    dxf_end();
#  else
    dxf_start();
    glPushMatrix();
    glRotatef(90, 1, 0, 0);
    glRotatef(90, 0, 0, 1);
    dc->anim_state = CODEWORD;
    make_codeword_path (mi);
    draw_codeword_path (mi);
    glPopMatrix();
    dxf_end();
#  endif
  }
# endif

  glPushMatrix();
  dc->helix_list = glGenLists (1);
  glNewList (dc->helix_list, GL_COMPILE);
  glRotatef(helix_rot, 0, 0, 1);
  if (do_ladder) dc->polys[0] += make_ladder (dc, 0, 0);
  if (do_helix)  dc->polys[0] += make_helix  (dc, 0, 0);
  glRotatef(180, 0, 0, 1);
  if (do_helix)  dc->polys[0] += make_helix  (dc, 0, 0);
  glEndList ();
  glPopMatrix();

  glPushMatrix();
  dc->helix_list_wire = glGenLists (1);
  glNewList (dc->helix_list_wire, GL_COMPILE);
/*  glRotatef(helix_rot, 0, 0, 1); wtf? */
  if (do_ladder) dc->polys[1] += make_ladder (dc, 1, 1);
  if (do_helix)  dc->polys[1] += make_helix  (dc, 1, 1);
  glRotatef(180, 0, 0, 1);
  if (do_helix)  dc->polys[1] += make_helix  (dc, 1, 1);
  glEndList ();
  glPopMatrix();

  glPushMatrix();
  dc->helix_list_facetted = glGenLists (1);
  glNewList (dc->helix_list_facetted, GL_COMPILE);
  glRotatef(helix_rot, 0, 0, 1);
  if (do_ladder) dc->polys[2] += make_ladder (dc, 1, 0);
  if (do_helix)  dc->polys[2] += make_helix  (dc, 1, 0);
  glRotatef(180, 0, 0, 1);
  if (do_helix)  dc->polys[2] += make_helix  (dc, 1, 0);
  glEndList ();
  glPopMatrix();

  dc->pizza_list = glGenLists (1);
  glNewList (dc->pizza_list, GL_COMPILE);
  if (do_frame) dc->polys[5] += make_pizza (dc, 0, 0);
  glEndList ();

  dc->pizza_list_wire = glGenLists (1);
  glNewList (dc->pizza_list_wire, GL_COMPILE);
  if (do_frame) dc->polys[6] += make_pizza (dc, 1, 1);
  glEndList ();

  dc->pizza_list_facetted = glGenLists (1);
  glNewList (dc->pizza_list_facetted, GL_COMPILE);
  if (do_frame) dc->polys[6] += make_pizza (dc, 1, 0);
  glEndList ();

  dc->gasket_list = glGenLists (1);
  glNewList (dc->gasket_list, GL_COMPILE);
  if (do_gasket) dc->polys[3] += make_gasket (dc, 0);
  glEndList ();

  dc->gasket_list_wire = glGenLists (1);
  glNewList (dc->gasket_list_wire, GL_COMPILE);
  if (do_gasket) dc->polys[4] += make_gasket (dc, 1);
  glEndList ();

  dc->frame_list = glGenLists (1);
  glNewList (dc->frame_list, GL_COMPILE);
  if (do_frame) dc->polys[5] += make_frame (dc, 0);
  glEndList ();

  dc->frame_list_wire = glGenLists (1);
  glNewList (dc->frame_list_wire, GL_COMPILE);
  if (do_frame) dc->polys[6] += make_frame (dc, 1);
  glEndList ();

# ifdef CW
  make_codeword_path (mi);
# endif

  /* When drawing both solid and wireframe objects,
     make sure the wireframe actually shows up! */
  glEnable (GL_POLYGON_OFFSET_FILL);
  glPolygonOffset (1.0, 1.0);
}


ENTRYPOINT Bool
logo_handle_event (ModeInfo *mi, XEvent *event)
{
  logo_configuration *dc = &dcs[MI_SCREEN(mi)];

  if (gltrackball_event_handler (event, dc->trackball,
                                 MI_WIDTH (mi), MI_HEIGHT (mi),
                                 &dc->button_down_p))
    return True;
  else if (event->xany.type == KeyPress)
    {
      KeySym keysym;
      char c = 0;
      XLookupString (&event->xkey, &c, 1, &keysym, 0);

# ifdef DEBUG
      {
        GLfloat step = 0.1;
        if      (c == 'a') dc->persp_off += step;
        else if (c == 'z') dc->persp_off -= step;
        else if (c == 's') dc->pos_off   += step;
        else if (c == 'x') dc->pos_off   -= step;
        else return False;

        /* dc->pos_off = -dc->persp_off; */
        reshape_logo (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
        return True;
      }
# endif

      if (c == ' ' || c == '\t')
        {
          switch (dc->anim_state) {
          case HELIX:    
            dc->anim_state = HELIX_OUT;
            dc->anim_ratio = 0.0;
            return True;
          case PIZZA:
            dc->anim_state = PIZZA_OUT;
            dc->anim_ratio = 0.0;
            return True;
# ifdef CW
          case CODEWORD:
            dc->anim_state = CODEWORD_OUT;
            dc->anim_ratio = 0.0;
            return True;
# endif
          default:
            break;
          }
        }
    }

  return False;
}


static GLfloat
spinner_ease (GLfloat x)
{
  /* Smooth curve up, ending at slope = 1. */
  return cos ((x/2 + 1) * M_PI) + 1;
}


static void
tick_spinner (ModeInfo *mi, spinner *s)
{
  logo_configuration *dc = &dcs[MI_SCREEN(mi)];

  if (dc->speed == 0) return;
  if (dc->button_down_p) return;

  if (s->spinning_p)
    {
      s->position += s->speed;
      if (s->position >=  1.0 || s->position <= 0.0)
        {
          s->position = 0;
          s->position_eased = 0;
          s->spinning_p = False;
        }
      else if (s->easement > 0 && s->position <= s->easement)
        s->position_eased = (s->easement *
                             spinner_ease (s->position / s->easement));
      else if (s->easement > 0 && s->position >= 1-s->easement)
        s->position_eased = (1 - s->easement *
                             spinner_ease ((1 - s->position) / s->easement));
      else
        s->position_eased = s->position;
    }
  else if (s->probability &&
           (random() % (int) (PROBABILITY_SCALE / s->probability)) == 0)
    {
      GLfloat ss = 0.004;
      s->spinning_p = True;
      s->position = 0;
      do {
        s->speed = dc->speed * (frand(ss/3) + frand(ss/3) + frand(ss/3));
      } while (s->speed <= 0);
      if (random() & 1)
        {
          s->speed = -s->speed;
          s->position = 1.0;
        }
    }
}


static void
link_spinners (ModeInfo *mi, spinner *s0, spinner *s1)
{
  if (s0->spinning_p && !s1->spinning_p)
    {
      GLfloat op = s1->probability;
      s1->probability = PROBABILITY_SCALE;
      tick_spinner (mi, s1);
      s1->probability = op;
    }
}


ENTRYPOINT void
draw_logo (ModeInfo *mi)
{
  logo_configuration *dc = &dcs[MI_SCREEN(mi)];
  Display *dpy = MI_DISPLAY(mi);
  Window window = MI_WINDOW(mi);
  int wire = MI_IS_WIREFRAME(mi);
  GLfloat gcolor[4];
  GLfloat specular[]  = {0.8, 0.8, 0.8, 1.0};
  GLfloat shininess   = 50.0;
  Bool pizza_p;
# ifdef CW
  Bool codeword_p;
# endif

  if (!dc->glx_context)
    return;

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

  if (!wire &&
      dc->wire_overlay == 0 &&
      (random() % (int) (PROBABILITY_SCALE / 0.2)) == 0)
    dc->wire_overlay = ((random() % 200) +
                        (random() % 200) +
                        (random() % 200));
      
# ifndef DEBUG
  tick_spinner (mi, &dc->gasket_spinnerx);
  tick_spinner (mi, &dc->gasket_spinnery);
  tick_spinner (mi, &dc->gasket_spinnerz);
  tick_spinner (mi, &dc->helix_spinnerz);
  tick_spinner (mi, &dc->pizza_spinnery);
  tick_spinner (mi, &dc->pizza_spinnerz);
  tick_spinner (mi, &dc->scene_spinnerx);
  tick_spinner (mi, &dc->scene_spinnery);
  tick_spinner (mi, &dc->frame_spinner);
  link_spinners (mi, &dc->scene_spinnerx, &dc->scene_spinnery);
# endif /* DEBUG */

  switch (dc->anim_state)
    {
    case HELIX:
      if (dc->mode == HELIX_AND_PIZZA &&
          (random() % (int) (PROBABILITY_SCALE / 0.2)) == 0)
        dc->anim_state = HELIX_OUT;
      break;

    case HELIX_OUT:
      dc->anim_ratio += 0.1 * dc->speed;
      if (dc->anim_ratio >= 1.0)
        {
          dc->anim_ratio = 0.0;
          dc->anim_state = PIZZA_IN;
        }
      break;

    case PIZZA_IN:
      dc->anim_ratio += 0.1 * dc->speed;
      if (dc->anim_ratio >= 1.0)
        {
          dc->anim_ratio = 0.0;
          dc->anim_state = PIZZA;
        }
      break;

    case PIZZA:
      if (dc->mode == HELIX_AND_PIZZA &&
          (random() % (int) (PROBABILITY_SCALE / 0.2)) == 0)
        dc->anim_state = PIZZA_OUT;
      break;

    case PIZZA_OUT:
      dc->anim_ratio += 0.1 * dc->speed;
      if (dc->anim_ratio >= 1.0)
        {
          dc->anim_ratio = 0.0;
          dc->anim_state = HELIX_IN;
        }
      break;

    case HELIX_IN:
      dc->anim_ratio += 0.1 * dc->speed;
      if (dc->anim_ratio >= 1.0)
        {
          dc->anim_ratio = 0.0;
          dc->anim_state = HELIX;
        }
      break;


# ifdef CW
    case CODEWORD_IN:
      dc->scene_spinnerx.probability = 0.2;
      dc->scene_spinnery.probability = 0.05;
      if (! dc->button_down_p)
        dc->anim_ratio += 0.004 * dc->speed;
      if (dc->anim_ratio >= 1.0)
        {
          dc->anim_state = CODEWORD;
          dc->anim_ratio = frand (0.5);
        }
      break;

    case CODEWORD:
      dc->scene_spinnerx.probability = 0.5;
      dc->scene_spinnery.probability = 0.2;
      if (! dc->button_down_p)
        dc->anim_ratio += (0.0005 + frand(0.002)) * dc->speed;
      if (dc->anim_ratio >= 1.0)
        {
          dc->anim_ratio = 0.0;
          dc->anim_state = CODEWORD_OUT;
        }
      break;

    case CODEWORD_OUT:
      dc->scene_spinnerx.probability = 0;
      dc->scene_spinnery.probability = 0;
      if (! dc->button_down_p)
        dc->anim_ratio += 0.02 * dc->speed;
      if (dc->anim_ratio >= 1.0)
        {
          dc->anim_ratio = 0.0;
          dc->anim_state = CODEWORD_BLANK;
        }
      break;

    case CODEWORD_BLANK:
      dc->scene_spinnerx.probability = 0;
      dc->scene_spinnery.probability = 0;
      if (! dc->button_down_p)
        dc->anim_ratio += 0.01 * dc->speed;
      if (dc->anim_ratio >= 1.0)
        {
          dc->anim_ratio = 0.0;
          dc->anim_state = CODEWORD_IN;
        }
      break;
# endif /* CW */

    default:
      abort();
      break;
    }

# ifdef DEBUG
  dc->anim_state = HELIX;
  dc->wire_overlay = 0;
# endif

  pizza_p = (dc->anim_state == PIZZA ||
             dc->anim_state == PIZZA_IN ||
             dc->anim_state == PIZZA_OUT);

# ifdef CW
  codeword_p = (dc->anim_state == CODEWORD ||
                dc->anim_state == CODEWORD_IN ||
                dc->anim_state == CODEWORD_OUT ||
                dc->anim_state == CODEWORD_BLANK);
# endif

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glPushMatrix ();
  glRotatef(current_device_rotation(), 0, 0, 1);
  {
    GLfloat scale = 1.8;
    glScalef(scale, scale, scale);

    glColor3f(dc->color[0], dc->color[1], dc->color[2]);


    /* Draw frame before trackball rotation */
# ifdef CW
    if (! codeword_p)
# endif
      {
        GLfloat p = (dc->frame_spinner.position_eased >= 0
                     ? dc->frame_spinner.position_eased
                     : -dc->frame_spinner.position_eased);
        GLfloat size = (p > 0.5 ? 1-p : p);
        scale = 1 + (size * 10);
        glPushMatrix();
        /* gltrackball_rotate (dc->trackball); */
        glRotatef(90, 1, 0, 0);
        glRotatef(90, 0, 0, 1);

        glScalef (1, scale, scale);
        if (wire)
          {
            glDisable (GL_LIGHTING);
            glCallList (dc->frame_list_wire);
            mi->polygon_count += dc->polys[6];
          }
        else if (dc->wire_overlay != 0)
          {
            glCallList (dc->frame_list);
            glDisable (GL_LIGHTING);
            glColor3fv (dc->color);
            glCallList (dc->frame_list_wire);
            mi->polygon_count += dc->polys[6];
            if (!wire) glEnable (GL_LIGHTING);
          }
        else
          {
            glCallList (dc->frame_list);
            mi->polygon_count += dc->polys[5];
          }
        glPopMatrix();
      }

    gltrackball_rotate (dc->trackball);

    glRotatef(90, 1, 0, 0);
    glRotatef(90, 0, 0, 1);

# ifdef CW
    if (! codeword_p)
# endif
      {
        glRotatef (360 * dc->scene_spinnerx.position_eased, 0, 1, 0);
        glRotatef (360 * dc->scene_spinnery.position_eased, 0, 0, 1);

        glPushMatrix();

        glRotatef (360 * dc->gasket_spinnerx.position_eased, 0, 1, 0);
        glRotatef (360 * dc->gasket_spinnery.position_eased, 0, 0, 1);
        glRotatef (360 * dc->gasket_spinnerz.position_eased, 1, 0, 0);

        memcpy (gcolor, dc->color, sizeof (dc->color));
        if (dc->wire_overlay != 0)
          {
            gcolor[0]   = gcolor[1]   = gcolor[2]   = 0;
            specular[0] = specular[1] = specular[2] = 0;
            shininess = 0;
          }
        glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, gcolor);
        glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  specular);
        glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS, shininess);

        if (wire)
          {
            glDisable (GL_LIGHTING);
            glCallList (dc->gasket_list_wire);
            mi->polygon_count += dc->polys[4];
          }
        else if (dc->wire_overlay != 0)
          {
            glCallList (dc->gasket_list);
            glDisable (GL_LIGHTING);
            glColor3fv (dc->color);
            glCallList (dc->gasket_list_wire);
            mi->polygon_count += dc->polys[4];
            if (!wire) glEnable (GL_LIGHTING);
            glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, gcolor);
          }
        else
          {
            glCallList (dc->gasket_list);
            mi->polygon_count += dc->polys[3];
          }
        glPopMatrix();

        if (pizza_p)
          {
            glRotatef (360 * dc->pizza_spinnery.position_eased, 1, 0, 0);
            glRotatef (360 * dc->pizza_spinnerz.position_eased, 0, 0, 1);
          }
        else
          {
            glRotatef (360 * dc->helix_spinnerz.position_eased, 0, 0, 1);
          }

        scale = ((dc->anim_state == PIZZA_IN || dc->anim_state == HELIX_IN)
                 ? dc->anim_ratio
                 : ((dc->anim_state == PIZZA_OUT || dc->anim_state == HELIX_OUT)
                    ? 1.0 - dc->anim_ratio
                    : 1.0));
        if (scale <= 0) scale = 0.001;
        glScalef (scale, scale, scale);

        if (wire)
          {
            glDisable (GL_LIGHTING);
            if (pizza_p)
              glCallList (dc->pizza_list_wire);
            else
              glCallList (dc->helix_list_wire);
            mi->polygon_count += dc->polys[1];
          }
        else if (dc->wire_overlay != 0)
          {
            if (pizza_p)
              glCallList (dc->pizza_list_facetted);
            else
              glCallList (dc->helix_list_facetted);

            glDisable (GL_LIGHTING);
            glColor3fv (dc->color);

            if (pizza_p)
              glCallList (dc->pizza_list_wire);
            else
              glCallList (dc->helix_list_wire);

            mi->polygon_count += dc->polys[2];
            if (!wire) glEnable (GL_LIGHTING);
          }
        else
          {
            if (pizza_p)
              glCallList (dc->pizza_list);
            else
              glCallList (dc->helix_list);
            mi->polygon_count += dc->polys[0];
          }
      }
# ifdef CW
    else	/* codeword_p */
      {
#  if 0
        double max = 70;  /* face front */
        double x, y, z;
        get_position (dc->scene_rot, &x, &y, &z, !dc->button_down_p);
        glRotatef (max/2 - x*max, 0, 0, 1);
        glRotatef (max/2 - y*max, 0, 1, 0);
        /* glRotatef (max/2 - z*max, 1, 0, 0); */
#  else
        glRotatef (360 * dc->scene_spinnerx.position_eased, 0, 1, 0);
        glRotatef (360 * dc->scene_spinnery.position_eased, 0, 0, 1);
#  endif

        glClearColor (dc->codeword_bg[0],
                      dc->codeword_bg[1],
                      dc->codeword_bg[2],
                      dc->codeword_bg[3]);
        mi->polygon_count += draw_codeword_path (mi);
      }
# endif /* CW */
  }
  glPopMatrix();

  if (dc->wire_overlay > 0)
    dc->wire_overlay--;

# ifdef DEBUG
  {
    char s[1024];
    sprintf (s, "a/z, s/x; per = %0.2f pos = %0.2f",
             dc->persp_off, dc->pos_off);
    glColor3f (1,1,1);
    print_texture_label (dpy, dc->label_font, MI_WIDTH(mi), MI_HEIGHT(mi),
                         1, s);
  }
# endif

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

  glXSwapBuffers(dpy, window);
}


ENTRYPOINT void
free_logo (ModeInfo *mi)
{
  logo_configuration *dc = &dcs[MI_SCREEN(mi)];
  if (!dc->glx_context) return;
  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *dc->glx_context);
  if (dc->trackball) gltrackball_free (dc->trackball);
# ifdef CW
  if (dc->codeword_text) free (dc->codeword_text);
  if (dc->codeword_text_out) free (dc->codeword_text_out);
  if (dc->scene_rot) free_rotator (dc->scene_rot);
  if (dc->font) free_texture_font (dc->font);
# endif
# ifdef DEBUG
  if (dc->label_font) free_texture_font (dc->label_font);
# endif
  glDeleteLists(dc->helix_list, 1);
  glDeleteLists(dc->helix_list_wire, 1);
  glDeleteLists(dc->helix_list_facetted, 1);
  glDeleteLists(dc->pizza_list, 1);
  glDeleteLists(dc->pizza_list_wire, 1);
  glDeleteLists(dc->pizza_list_facetted, 1);
  glDeleteLists(dc->gasket_list, 1);
  glDeleteLists(dc->gasket_list_wire, 1);
  glDeleteLists(dc->frame_list, 1);
  glDeleteLists(dc->frame_list_wire, 1);
}

XSCREENSAVER_MODULE_2 ("DNALogo", dnalogo, logo)

#endif /* USE_GL */
