/* -*- Mode: C; tab-width: 4 -*- */
/* molecule --- 3D molecules */

#if 0
static const char sccsid[] = "@(#)molecule.c	5.01 2001/04/12 xlockmore";
#endif

/* molecule, Copyright (c) 2001 Jamie Zawinski <jwz AT jwz.org>
 * Draws molecules, based on coordinates from PDB (Protein Data Base) files.
 *
 * 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.
 *
 *
 * Revision History:
 * 16-May-01:  tschmidt AT micron.com
 *                              Fix remaining memory leaks. Fix occasional SEGV.
 * 19-Apr-01:  tschmidt AT micron.com
 *                              Fixed most memory leaks
 * 19-Apr-01:  rolf@groppe.de   fixed some allocation problems
 *                              Startup message now only at first call
 * 12-Apr-01:  rolf@groppe.de   Ported this mode from xscreensaver to xlock.
 *                              Use of parameter -cycles instead of timeout,
 *                              when used as xlock module.
 *                              Some minor changes for better display in
 *                              iconified state.
 */


/* Documentation on the PDB file format:
   http://www.rcsb.org/pdb/docs/format/pdbguide2.2/guide2.2_frame.html

   Good source of PDB files:
   http://www.sci.ouc.bc.ca/chem/molecule/molecule.html

   TO DO:

     - I'm not sure the text labels are being done in the best way;
       they are sometimes, but not always, occluded by spheres that
       pass in front of them.
 */

#ifdef VMS
#include <types.h>
#endif

#define DEF_TIMEOUT     "20"
#define DEF_SPIN        "XYZ"
#define DEF_WANDER      "False"
#define DEF_LABELS      "True"
#define DEF_TITLES      "True"
#define DEF_ATOMS       "True"
#define DEF_BONDS       "True"
#define DEF_BBOX        "False"
#define DEF_MOLECULE    "(default)"


#ifdef STANDALONE
# define MODE_molecule
# define DEFAULTS	"*delay:	10000         \n" \
			"*showFPS:      False         \n" \
			"*wireframe:    False         \n" \
			"*atomFont:   -*-times-bold-r-normal-*-240-*\n" \
			"*titleFont:  -*-times-bold-r-normal-*-180-*\n" \
			"*noLabelThreshold:    30     \n" \
			"*wireframeThreshold:  150    \n" \

# define free_molecule 0
# define molecule_handle_event 0
# include "xlockmore.h"
# include "colors.h"
#else  /* !STANDALONE */
# include "xlock.h"				/* from the xlockmore distribution */
# include "visgl.h"
#endif /* !STANDALONE */
#include "sphere.h"
#include "tube.h"

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

#ifdef MODE_molecule /* whole file */

#include <ctype.h>
#include <GL/glu.h>
#include <time.h>

#define SPHERE_SLICES 16  /* how densely to render spheres */
#define SPHERE_STACKS 10

#define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */

#ifdef SMOOTH_TUBE
# define TUBE_FACES  12   /* how densely to render tubes */
#else
# define TUBE_FACES  8
#endif

#define FLOATRAND(a) (((double)LRAND() / (double)MAXRAND) * a)

static int scale_down;
#define SPHERE_SLICES_2  7
#define SPHERE_STACKS_2  4
#define TUBE_FACES_2     3


static const char * const builtin_pdb_data[] = {
# include "molecules.h"
};


typedef struct {
  const char *name;
  GLfloat size, size2;
  const char *color;
  const char *text_color;
  GLfloat gl_color[8];
} atom_data;


/* These are the traditional colors used to render these atoms,
   and their approximate size in angstroms.
 */
static atom_data all_atom_data[] = {
  { "H",    1.17,  0,  "White",           "Grey60",        { 0, }},
  { "C",    1.75,  0,  "Grey50",          "White",         { 0, }},
  { "N",    1.55,  0,  "RoyalBlue1",      "NavyBlue",      { 0, }},
  { "O",    1.40,  0,  "Red",             "LightPink",     { 0, }},
  { "P",    1.28,  0,  "MediumPurple",    "PaleVioletRed", { 0, }},
  { "S",    1.80,  0,  "Yellow4",         "Yellow1",       { 0, }},
  { "bond", 0,     0,  "Grey70",          "Yellow1",       { 0, }},
  { "*",    1.40,  0,  "Green4",          "PaleGreen2",    { 0, }}
};


typedef struct {
  int id;		/* sequence number in the PDB file */
  const char *label;	/* The atom name */
  GLfloat x, y, z;	/* position in 3-space (angstroms) */
  atom_data *data;	/* computed: which style of atom this is */
} molecule_atom;

typedef struct {
  int from, to;		/* atom sequence numbers */
  int strength;		/* how many bonds are between these two atoms */
} molecule_bond;


typedef struct {
  const char *label;		/* description of this compound */
  int natoms, atoms_size;
  int nbonds, bonds_size;
  molecule_atom *atoms;
  molecule_bond *bonds;
} molecule;


typedef struct {
#ifdef WIN32
  HGLRC glx_context;
#else
  GLXContext *glx_context;
#endif
  GLfloat rotx, roty, rotz;	   /* current object rotation */
  GLfloat dx, dy, dz;		   /* current rotational velocity */
  GLfloat ddx, ddy, ddz;	   /* current rotational acceleration */
  GLfloat d_max;		   /* max velocity */

  Bool spin_x, spin_y, spin_z;

  GLfloat molecule_size;	   /* max dimension of molecule bounding box */

  GLfloat no_label_threshold;	   /* Things happen when molecules are huge */
  GLfloat wireframe_threshold;

  int which;			   /* which of the molecules is being shown */
  int nmolecules;
  molecule *molecules;

  GLuint molecule_dlist;
  XFontStruct *xfont1, *xfont2;
  GLuint font1_dlist, font2_dlist;

} molecule_configuration;


static molecule_configuration *mcs = (molecule_configuration *) NULL;
static molecule *mols = (molecule *) NULL;

static int timeout;
static char *molecule_str;
static char *do_spin;
static Bool do_wander;
static Bool do_titles;
static Bool do_labels;
static Bool do_atoms;
static Bool do_bonds;
static Bool do_bbox;

static Bool orig_do_labels, orig_do_titles, orig_do_bonds, cur_wire, orig_wire; /* saved to reset */
static Bool not_first_gl = False;
static Bool firstcall = True;

static XrmOptionDescRec opts[] = {
  {(char *) "-molecule", (char *) ".molecule.molecule", XrmoptionSepArg, 0 },
#ifdef STANDALONE
  {(char *) "-timeout", (char *) ".molecule.timeout",XrmoptionSepArg, 0 },
#endif
  {(char *) "-spin",   (char *) ".molecule.spin",   XrmoptionSepArg, 0 },
  {(char *) "+spin",   (char *) ".molecule.spin",   XrmoptionNoArg, (caddr_t) NULL},
  {(char *) "-wander", (char *) ".molecule.wander", XrmoptionNoArg, (caddr_t) "on" },
  {(char *) "+wander", (char *) ".molecule.wander", XrmoptionNoArg, (caddr_t) "off" },
  {(char *) "-labels", (char *) ".molecule.labels", XrmoptionNoArg, (caddr_t) "on" },
  {(char *) "+labels", (char *) ".molecule.labels", XrmoptionNoArg, (caddr_t) "off" },
  {(char *) "-titles", (char *) ".molecule.titles", XrmoptionNoArg, (caddr_t) "on" },
  {(char *) "+titles", (char *) ".molecule.titles", XrmoptionNoArg, (caddr_t) "off" },
  {(char *) "-atoms",  (char *) ".molecule.atoms",  XrmoptionNoArg, (caddr_t) "on" },
  {(char *) "+atoms",  (char *) ".molecule.atoms",  XrmoptionNoArg, (caddr_t) "off" },
  {(char *) "-bonds",  (char *) ".molecule.bonds",  XrmoptionNoArg, (caddr_t) "on" },
  {(char *) "+bonds",  (char *) ".molecule.bonds",  XrmoptionNoArg, (caddr_t) "off" },
  {(char *) "-bbox",   (char *) ".molecule.bbox",  XrmoptionNoArg, (caddr_t) "on" },
  {(char *) "+bbox",   (char *) ".molecule.bbox",  XrmoptionNoArg, (caddr_t) "off" },
};

static argtype vars[] = {
  {(void *) &molecule_str, (char *) "molecule",   (char *) "Molecule", (char *) DEF_MOLECULE,t_String},
#ifdef STANDALONE
  {(void *) &timeout,   (char *) "timeout",(char *) "Seconds", (char *) DEF_TIMEOUT,t_Int},
#endif
  {(void *) &do_spin,   (char *) "spin",   (char *) "Spin",   (char *) DEF_SPIN,   t_String},
  {(void *) &do_wander, (char *) "wander", (char *) "Wander", (char *) DEF_WANDER, t_Bool},
  {(void *) &do_labels, (char *) "labels", (char *) "Labels", (char *) DEF_LABELS, t_Bool},
  {(void *) &do_titles, (char *) "titles", (char *) "Titles", (char *) DEF_TITLES, t_Bool},
  {(void *) &do_atoms,  (char *) "atoms",  (char *) "Atoms",  (char *) DEF_ATOMS,  t_Bool},
  {(void *) &do_bonds,  (char *) "bonds",  (char *) "Bonds",  (char *) DEF_BONDS,  t_Bool},
  {(void *) &do_bbox,   (char *) "bbox",   (char *) "BBox",   (char *) DEF_BBOX,   t_Bool},
};

static OptionStruct desc[] = {
  {(char *) "-molecule", (char *) "set molecule"},
#ifdef STANDALONE
  {(char *) "-timeout", (char *) "set timeout"},
#endif
  {(char *) "-/+spin", (char *) "turn on/off spin"},
  {(char *) "-/+wander", (char *) "turn on/off wander"},
  {(char *) "-/+labels", (char *) "turn on/off labels"},
  {(char *) "-/+titles", (char *) "turn on/off titles"},
  {(char *) "-/+atoms", (char *) "turn on/off atoms"},
  {(char *) "-/+bonds", (char *) "turn on/off bonds"},
  {(char *) "-/+bbox", (char *) "turn on/off bbox"},
};

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

#ifdef USE_MODULES
ModStruct   molecule_description =
{"molecule", "init_molecule", "draw_molecule", "release_molecule",
 "draw_molecule", "init_molecule", (char *) NULL, &molecule_opts,
 50000, 1, 20, 1, 64, 1.0, "",
 "Draws molecules", 0, NULL};
#endif

static time_t last = 0;


/* shapes */

static void
sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
{
  int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
  int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);

  glPushMatrix ();
  glTranslatef (x, y, z);
  glScalef (diameter, diameter, diameter);
  unit_sphere (stacks, slices, wire);
  glPopMatrix ();
}


static Bool
load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
{
#ifdef STANDALONE
  const char *font = get_string_resource (MI_DISPLAY(mi), res, "Font");
#else
  const char *font = res;
#endif
  XFontStruct *f;
  Font id;
  int first, last;

  if (!font) font = "-*-times-bold-r-normal-*-180-*";

  f = XLoadQueryFont(MI_DISPLAY(mi), font);
  if (!f) f = XLoadQueryFont(MI_DISPLAY(mi), "fixed");

  id = f->fid;
  first = f->min_char_or_byte2;
  last = f->max_char_or_byte2;

  clear_gl_error ();
  *dlistP = glGenLists ((GLuint) last+1);
  check_gl_error ("glGenLists");
  /*-
   * PURIFY reports a cumulative memory leak on the next line with Mesa 3.4.1
   * on Solaris 2.X and SunOS 4.1.X. This can be fixed with a patch to Mesa
   * 3.4.1. OpenGL on Solaris does not have the memory leak.
   */
  glXUseXFont(id, first, last-first+1, *dlistP + first);
  check_gl_error ("glXUseXFont");

  *fontP = f;
  return True;
}


static Bool
load_fonts (ModeInfo *mi)
{
  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
#ifdef STANDALONE
  if (!load_font (mi, (char *) "atomFont",  &mc->xfont1, &mc->font1_dlist))
	return False;
  if (!load_font (mi, (char *) "titleFont", &mc->xfont2, &mc->font2_dlist))
	return False;
#else
  if (!load_font (mi, (char *) "-*-times-bold-r-normal-*-240-*",
		&mc->xfont1, &mc->font1_dlist))
	return False;
  if (!load_font (mi, (char *) "-*-times-bold-r-normal-*-180-*",
		&mc->xfont2, &mc->font2_dlist))
	return False;
#endif
  return True;
}

static void
free_fonts (ModeInfo *mi)
{
  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];

  clear_gl_error ();
  if (glIsList(mc->molecule_dlist)) {
	glDeleteLists(mc->molecule_dlist, 1);
	(void) check_gl_error ("glDeleteLists");
	mc->molecule_dlist = 0;
  }
  if (mc->xfont1) {
	int last;

	last = mc->xfont1->max_char_or_byte2;
	clear_gl_error ();
	if (glIsList(mc->font1_dlist)) {
		glDeleteLists (mc->font1_dlist,(GLuint) last+1);
		(void) check_gl_error ("glDeleteLists");
		mc->font1_dlist = 0;
	}
	XFreeFont(MI_DISPLAY(mi),mc->xfont1);
	mc->xfont1 = (XFontStruct *) NULL;
  }
  if (mc->xfont2) {
	int last;

	last = mc->xfont2->max_char_or_byte2;
	clear_gl_error ();
	if (glIsList(mc->font2_dlist)) {
		glDeleteLists (mc->font2_dlist,(GLuint) last+1);
		(void) check_gl_error ("glDeleteLists");
		mc->font2_dlist = 0;
	}
	XFreeFont(MI_DISPLAY(mi),mc->xfont2);
	mc->xfont2 = (XFontStruct *) NULL;
  }
}

static int
string_width (XFontStruct *f, const char *c)
{
  int w = 0;
  while (*c)
    {
      int cc = *((unsigned char *) c);
      w += (f->per_char
            ? f->per_char[cc-f->min_char_or_byte2].rbearing
            : f->min_bounds.rbearing);
      c++;
    }
  return w;
}


static atom_data *
get_atom_data (const char *atom_name)
{
  int i;
  atom_data *d = 0;
  char *n = (char *) strdup (atom_name);
  char *n2 = n;
  int L;

  while (!isalpha((int) *n)) n++;
  L = strlen(n);
  while (L > 0 && !isalpha((int) n[L-1]))
    n[--L] = 0;

  for (i = 0; i < (int) countof(all_atom_data); i++)
    {
      d = &all_atom_data[i];
      if (!strcmp (n, all_atom_data[i].name))
        break;
    }

  free(n2);
  return d;
}


static void
set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
{
  atom_data *d;
  GLfloat *gl_color;

  if (a)
    d = a->data;
  else
    {
      static atom_data *def_data = 0;
      if (!def_data) def_data = get_atom_data ("bond");
      d = def_data;
    }

  gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));

  if (gl_color[3] == 0)
    {
      const char *string = !font_p ? d->color : d->text_color;
      XColor xcolor;
      if (!XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), string, &xcolor))
        {
          (void) fprintf (stderr, "molecule: unparsable color in %s: %s\n",
                   (a ? a->label : d->name), string);
          /* exit (1); */
        }

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

  if (font_p)
    glColor3f (gl_color[0], gl_color[1], gl_color[2]);
  else
    glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
}


static GLfloat
atom_size (molecule_atom *a)
{
  if (do_bonds)
    {
      if (a->data->size2 == 0)
        {
          /* let the molecules have the same relative sizes, but scale
             them to a smaller range, so that the bond-tubes are
             actually visible...
           */
          GLfloat bot = 0.4;
          GLfloat top = 0.6;
          GLfloat min = 1.17;
          GLfloat max = 1.80;
          GLfloat ratio = (a->data->size - min) / (max - min);
          a->data->size2 = bot + (ratio * (top - bot));
        }
      return a->data->size2;
    }
  else
    return a->data->size;
}


static molecule_atom *
get_atom (molecule_atom *atoms, int natoms, int id, Bool verbose)
{
  int i;

  /* quick short-circuit */
  if (id < natoms)
    {
      if (atoms[id].id == id)
        return &atoms[id];
      if (id > 0 && atoms[id-1].id == id)
        return &atoms[id-1];
      if (id < natoms-1 && atoms[id+1].id == id)
        return &atoms[id+1];
    }

  for (i = 0; i < natoms; i++)
    if (id == atoms[i].id)
      return &atoms[i];

  if (verbose) {
    (void) fprintf (stderr, "molecule: no atom %d\n", id);
  }
  return (molecule_atom *) NULL;
}


static void
molecule_bounding_box (ModeInfo *mi,
                       GLfloat *x1, GLfloat *y1, GLfloat *z1,
                       GLfloat *x2, GLfloat *y2, GLfloat *z2)
{
  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
  molecule *m = &mc->molecules[mc->which];
  int i;

  if (m->natoms == 0)
    {
      *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
    }
  else
    {
      *x1 = *x2 = m->atoms[0].x;
      *y1 = *y2 = m->atoms[0].y;
      *z1 = *z2 = m->atoms[0].z;
    }

  for (i = 1; i < m->natoms; i++)
    {
      if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
      if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
      if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;

      if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
      if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
      if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
    }

  *x1 -= 1;
  *y1 -= 1;
  *z1 -= 1;
  *x2 += 1;
  *y2 += 1;
  *z2 += 1;
}


static void
draw_bounding_box (ModeInfo *mi)
{
  static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
  static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
  int wire = cur_wire;
  GLfloat x1, y1, z1, x2, y2, z2;
  molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);

  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
  glFrontFace(GL_CCW);

  glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
  glNormal3f(0, 1, 0);
  glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
  glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
  glEnd();
  glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
  glNormal3f(0, -1, 0);
  glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
  glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
  glEnd();
  glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
  glNormal3f(0, 0, 1);
  glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
  glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
  glEnd();
  glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
  glNormal3f(0, 0, -1);
  glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
  glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
  glEnd();
  glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
  glNormal3f(1, 0, 0);
  glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
  glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
  glEnd();
  glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
  glNormal3f(-1, 0, 0);
  glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
  glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
  glEnd();

  glPushAttrib (GL_LIGHTING_BIT);
  glDisable (GL_LIGHTING);

  glColor3f (c2[0], c2[1], c2[2]);
  glBegin(GL_LINES);
  if (x1 > 0) x1 = 0;
  if (x2 < 0) x2 = 0;
  if (y1 > 0) y1 = 0;
  if (y2 < 0) y2 = 0;
  if (z1 > 0) z1 = 0;
  if (z2 < 0) z2 = 0;
  glVertex3f(x1, 0,  0);  glVertex3f(x2, 0,  0);
  glVertex3f(0 , y1, 0);  glVertex3f(0,  y2, 0);
  glVertex3f(0,  0,  z1); glVertex3f(0,  0,  z2);
  glEnd();

  glPopAttrib();
}


/* Since PDB files don't always have the molecule centered around the
   origin, and since some molecules are pretty large, scale and/or
   translate so that the whole molecule is visible in the window.
 */
static void
ensure_bounding_box_visible (ModeInfo *mi)
{
  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];

  GLfloat x1, y1, z1, x2, y2, z2;
  GLfloat w, h, d;
  GLfloat size;
  GLfloat max_size = 10;  /* don't bother scaling down if the molecule
                             is already smaller than this */

  molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
  w = x2-x1;
  h = y2-y1;
  d = z2-z1;

  size = (w > h ? w : h);
  size = (size > d ? size : d);

  mc->molecule_size = size;

  scale_down = 0;

  if (size > max_size)
    {
      GLfloat scale = max_size / size;
      glScalef (scale, scale, scale);

      scale_down = scale < 0.3;
    }

  glTranslatef (-(x1 + w/2),
                -(y1 + h/2),
                -(z1 + d/2));
}


static void
print_title_string (ModeInfo *mi, const char *string,
                    GLfloat x, GLfloat y, GLfloat line_height)
{
  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];

  y -= line_height;

  glPushAttrib (GL_LIGHTING_BIT | GL_DEPTH_BUFFER_BIT);
  glDisable (GL_LIGHTING);
  glDisable (GL_DEPTH_TEST);
  {
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    {
      glLoadIdentity();

      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      {
        int i;
        glLoadIdentity();

        gluOrtho2D (0, MI_WIDTH(mi), 0, MI_HEIGHT(mi));

        set_atom_color (mi, 0, True);

        glRasterPos2f (x, y);
        for (i = 0; i < (int) strlen(string); i++)
          {
            char c = string[i];
            if (c == '\n')
              glRasterPos2f (x, (y -= line_height));
            else
              glCallList (mc->font2_dlist + (int)(c));
          }
      }
      glPopMatrix();
    }
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
  }
  glPopAttrib();

  glMatrixMode(GL_MODELVIEW);
}


/* Constructs the GL shapes of the current molecule
 */
static void
build_molecule (ModeInfo *mi)
{
  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
  int wire = cur_wire;
  int i;

  molecule *m = &mc->molecules[mc->which];

  if (wire)
    {
      glDisable(GL_CULL_FACE);
      glDisable(GL_LIGHTING);
      glDisable(GL_LIGHT0);
      glDisable(GL_DEPTH_TEST);
      glDisable(GL_NORMALIZE);
      glDisable(GL_CULL_FACE);
    }
  else
    {
      glEnable(GL_CULL_FACE);
      glEnable(GL_LIGHTING);
      glEnable(GL_LIGHT0);
      glEnable(GL_DEPTH_TEST);
      glEnable(GL_NORMALIZE);
      glEnable(GL_CULL_FACE);
    }

  if (!wire)
    set_atom_color (mi, 0, False);

  if (do_bonds)
    for (i = 0; i < m->nbonds; i++)
      {
        molecule_bond *b = &m->bonds[i];
        molecule_atom *from = get_atom(m->atoms, m->natoms, b->from,
		MI_IS_VERBOSE(mi));
        molecule_atom *to   = get_atom(m->atoms, m->natoms, b->to,
		MI_IS_VERBOSE(mi));

        if (wire)
          {
            glBegin(GL_LINES);
            glVertex3f(from->x, from->y, from->z);
            glVertex3f(to->x,   to->y,   to->z);
            glEnd();
          }
        else
          {
            int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
# ifdef SMOOTH_TUBE
            int smooth = True;
# else
            int smooth = False;
# endif
            GLfloat thickness = 0.07 * b->strength;
            GLfloat cap_size = 0.03;
            if (thickness > 0.3)
              thickness = 0.3;

            tube (from->x, from->y, from->z,
                  to->x,   to->y,   to->z,
                  thickness, cap_size,
                  faces, smooth, !do_atoms, wire);
          }
      }

  for (i = 0; i < m->natoms; i++)
    {
      molecule_atom *a = &m->atoms[i];
      int i;

      if (!wire && do_atoms)
        {
          GLfloat size = atom_size (a);
          set_atom_color (mi, a, False);
          sphere (a->x, a->y, a->z, size, wire);
        }

      if (do_labels)
        {
          glPushAttrib (GL_LIGHTING_BIT | GL_DEPTH_BUFFER_BIT);
          glDisable (GL_LIGHTING);
          glDisable (GL_DEPTH_TEST);

          if (!wire)
            set_atom_color (mi, a, True);

          glRasterPos3f (a->x, a->y, a->z);

          {
            GLdouble mm[17], pm[17];
            GLint vp[5];
            GLdouble wx=-1, wy=-1, wz=-1;
            glGetDoublev (GL_MODELVIEW_MATRIX, mm);
            glGetDoublev (GL_PROJECTION_MATRIX, pm);
            glGetIntegerv (GL_VIEWPORT, vp);

            /* Convert 3D coordinates to window coordinates */
            gluProject (a->x, a->y, a->z, mm, pm, vp, &wx, &wy, &wz);

            /* Fudge the window coordinates to center the string */
            wx -= string_width (mc->xfont1, a->label) / 2;
            wy -= mc->xfont1->descent;

            /* Convert new window coordinates back to 3D coordinates */
            gluUnProject (wx, wy, wz, mm, pm, vp, &wx, &wy, &wz);
            glRasterPos3f (wx, wy, wz);
          }

          for (i = 0; i < (int) strlen(a->label); i++)
            glCallList (mc->font1_dlist + (int)(a->label[i]));

          glPopAttrib();
        }
    }

  if (do_bbox)
    draw_bounding_box (mi);

  if (do_titles && m->label && *m->label)
    print_title_string (mi, m->label,
                        10, MI_HEIGHT(mi) - 10,
                        mc->xfont2->ascent + mc->xfont2->descent);
}



/* loading */

static void
push_atom (molecule *m,
           int id, const char *label,
           GLfloat x, GLfloat y, GLfloat z)
{
  m->natoms++;
  if (m->atoms_size < m->natoms)
    {
      m->atoms_size += 20;
      m->atoms = (molecule_atom *) realloc (m->atoms,
                                            m->atoms_size * sizeof(*m->atoms));
    }
  m->atoms[m->natoms-1].id = id;
  m->atoms[m->natoms-1].label = label;
  m->atoms[m->natoms-1].x = x;
  m->atoms[m->natoms-1].y = y;
  m->atoms[m->natoms-1].z = z;
  m->atoms[m->natoms-1].data = get_atom_data (label);
}


static void
push_bond (molecule *m, int from, int to)
{
  int i;

  for (i = 0; i < m->nbonds; i++)
    if ((m->bonds[i].from == from && m->bonds[i].to   == to) ||
        (m->bonds[i].to   == from && m->bonds[i].from == to))
      {
        m->bonds[i].strength++;
        return;
      }

  m->nbonds++;
  if (m->bonds_size < m->nbonds)
    {
      m->bonds_size += 20;
      m->bonds = (molecule_bond *) realloc (m->bonds,
                                            m->bonds_size * sizeof(*m->bonds));
    }
  m->bonds[m->nbonds-1].from = from;
  m->bonds[m->nbonds-1].to = to;
  m->bonds[m->nbonds-1].strength = 1;
}



/* This function is crap.
 */
static void
parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
{
  const char *s = data;
  char *ss;
  while (*s)
    {
      if ((!m->label || !*m->label) &&
          (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
        {
          char *name = (char *) calloc (1, 100);
          char *n2 = name;
          int L = strlen(s);

          if (L > 99)
          	(void) strncpy(n2, s, 99);
          else
          	(void) strcpy(n2, s);
          n2 += 7;
          while (isspace((int) *n2)) n2++;

          ss = strchr (n2, '\n');
          if (ss) *ss = 0;
          ss = strchr (n2, '\r');
          if (ss) *ss = 0;

          ss = n2 + strlen(n2)-1;
          while (isspace((int) *ss) && ss > n2)
            *ss-- = 0;

          if (strlen(n2) > 4 &&
              !strcmp (n2 + strlen(n2) - 4, ".pdb"))
            n2[strlen(n2)-4] = 0;

          if (m->label) free((char *) m->label);
          m->label = (char *) strdup (n2);
          free(name);
        }
      else if (!strncmp (s, "TITLE ", 6) ||
               !strncmp (s, "HEADER", 6) ||
               !strncmp (s, "COMPND", 6) ||
               !strncmp (s, "AUTHOR", 6) ||
               !strncmp (s, "REVDAT", 6) ||
               !strncmp (s, "SOURCE", 6) ||
               !strncmp (s, "EXPDTA", 6) ||
               !strncmp (s, "JRNL  ", 6) ||
               !strncmp (s, "REMARK", 6) ||
               !strncmp (s, "SEQRES", 6) ||
               !strncmp (s, "HET   ", 6) ||
               !strncmp (s, "FORMUL", 6) ||
               !strncmp (s, "CRYST1", 6) ||
               !strncmp (s, "ORIGX1", 6) ||
               !strncmp (s, "ORIGX2", 6) ||
               !strncmp (s, "ORIGX3", 6) ||
               !strncmp (s, "SCALE1", 6) ||
               !strncmp (s, "SCALE2", 6) ||
               !strncmp (s, "SCALE3", 6) ||
               !strncmp (s, "MASTER", 6) ||
               !strncmp (s, "KEYWDS", 6) ||
               !strncmp (s, "DBREF ", 6) ||
               !strncmp (s, "HETNAM", 6) ||
               !strncmp (s, "HETSYN", 6) ||
               !strncmp (s, "HELIX ", 6) ||
               !strncmp (s, "LINK  ", 6) ||
               !strncmp (s, "MTRIX1", 6) ||
               !strncmp (s, "MTRIX2", 6) ||
               !strncmp (s, "MTRIX3", 6) ||
               !strncmp (s, "SHEET ", 6) ||
               !strncmp (s, "CISPEP", 6) ||
               !strncmp (s, "GENERATED BY", 12) ||
               !strncmp (s, "TER ", 4) ||
               !strncmp (s, "END ", 4) ||
               !strncmp (s, "TER\n", 4) ||
               !strncmp (s, "END\n", 4) ||
               !strncmp (s, "\n", 1))
        /* ignored. */
        ;
      else if (!strncmp (s, "ATOM   ", 7))
        {
          int id;
	  /* PURIFY reports a cumulative potential memory leak on the next line */
          char *name = (char *) calloc (1, 4);
          GLfloat x = -999, y = -999, z = -999;

          (void) sscanf (s+7, " %d ", &id);

          (void) strncpy (name, s+12, 3);
          while (isspace((int) *name)) name++;
          ss = name + strlen(name)-1;
          while (isspace((int) *ss) && ss > name)
            *ss-- = 0;
          (void) sscanf (s + 32, " %f %f %f ", &x, &y, &z);
/*
          (void) fprintf (stderr, "molecule: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
                   filename, line,
                   id, name, x, y, z);
*/
          push_atom (m, id, name, x, y, z);
        }
      else if (!strncmp (s, "HETATM ", 7))
        {
          int id;
	  /* PURIFY reports a cumulative potential memory leak on the next line */
          char *name = (char *) calloc (1, 4);
          GLfloat x = -999, y = -999, z = -999;

          (void) sscanf (s+7, " %d ", &id);

          (void) strncpy (name, s+12, 3);
          while (isspace((int) *name)) name++;
          ss = name + strlen(name)-1;
          while (isspace((int) *ss) && ss > name)
            *ss-- = 0;
          (void) sscanf (s + 30, " %f %f %f ", &x, &y, &z);
/*
          (void) fprintf (stderr, "molecule: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
                   filename, line,
                   id, name, x, y, z);
*/
          push_atom (m, id, name, x, y, z);
        }
      else if (!strncmp (s, "CONECT ", 7))
        {
          int atoms[11];
          int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
                          &atoms[0], &atoms[1], &atoms[2], &atoms[3],
                          &atoms[4], &atoms[5], &atoms[6], &atoms[7],
                          &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
          int j;
          for (j = 1; j < i; j++)
            if (atoms[j] > 0)
              {
/*
                (void) fprintf (stderr, "molecule: %s: %d: bond: %d %d\n",
                         filename, line, atoms[0], atoms[j]);
*/
                push_bond (m, atoms[0], atoms[j]);
              }
        }
      else
        {
          char *s1 = (char *) strdup (s);
          for (ss = s1; *ss && *ss != '\n'; ss++)
            ;
          *ss = 0;
          (void) fprintf (stderr, "molecule: %s: %d: unrecognised line: %s\n",
                   filename, line, s1);
        }

      while (*s && *s != '\n')
        s++;
      if (*s == '\n')
        s++;
      line++;
    }
}


static void
parse_pdb_file (molecule *m, const char *name)
{
  FILE *in;
  int buf_size = 40960;
  char *buf;
  int line = 1;

  in = fopen(name, "r");
  if (!in)
    {
      char *buf = (char *) malloc(1024 + strlen(name));
      (void) sprintf(buf, "molecule: error reading \"%s\"", name);
      perror(buf);
      free(buf);
      /* exit (1); */
    }

  buf = (char *) malloc (buf_size);

  while (fgets (buf, buf_size-1, in))
    {
      char *s;
      for (s = buf; *s; s++)
        if (*s == '\r') *s = '\n';
      parse_pdb_data (m, buf, name, line++);
    }

  free (buf);
  (void) fclose (in);

  if (!m->natoms)
    {
      (void) fprintf (stderr, "molecule: file %s contains no atomic coordinates!\n",
               name);
      /* exit (1); */
    }

  if (!m->nbonds && do_bonds)
    {
      (void) fprintf (stderr, "molecule: warning: file %s contains no atomic bond info.\n",
               name);
      do_bonds = 0;
    }
}


static void
generate_molecule_formula (molecule *m)
{
  char *buf = (char *) malloc (m->natoms * 10);
  char *s = buf;
  int i;
  struct { char *atom; int count; } counts[200];
  memset (counts, 0, sizeof(counts));
  *s = 0;
  for (i = 0; i < m->natoms; i++)
    {
      int j = 0;
      char *a = (char *) m->atoms[i].label;
      char *e;
      while (!isalpha((int) *a)) a++;
      a = (char *) strdup (a);
      for (e = a; isalpha((int) *e); e++);
      *e = 0;
      while (counts[j].atom && !!strcmp(a, counts[j].atom))
        j++;
      if (counts[j].atom)
        free(a);
      else
        counts[j].atom = a;
      counts[j].count++;
    }

  i = 0;
  while (counts[i].atom)
    {
      (void) strcat (s, counts[i].atom);
      free(counts[i].atom);
      s += strlen (s);
      if (counts[i].count > 1)
        (void) sprintf (s, "(%d)", counts[i].count);
      s += strlen (s);
      i++;
    }

  if (!m->label) m->label = (char *) strdup("");
  s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
  (void) strcpy (s, m->label);
  (void) strcat (s, "\n");
  (void) strcat (s, buf);
  free((char*) m->label);
  free (buf);
  m->label = s;
}

static void
insert_vertical_whitespace (char *string)
{
  while (*string)
    {
      if ((string[0] == ',' ||
           string[0] == ';' ||
           string[0] == ':') &&
          string[1] == ' ')
        string[0] = ' ', string[1] = '\n';
      string++;
    }
}


/* Construct the molecule data from either: the builtins; or from
   the (one) .pdb file specified with -molecule.
 */
static void
load_molecules (ModeInfo *mi)
{
  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
  int wire = cur_wire;

  if (!molecule_str || !*molecule_str ||
      !strcmp(molecule_str, "(default)"))	/* do the builtins */
    {
      int i;
      mc->nmolecules = countof(builtin_pdb_data);
      if (!mols) {
        mols = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
        for (i = 0; i < mc->nmolecules; i++)
          {
            char name[100];
            (void) sprintf (name, "<builtin-%d>", i);
            parse_pdb_data (&mols[i], builtin_pdb_data[i], name, 1);
            generate_molecule_formula (&mols[i]);
            insert_vertical_whitespace ((char *) mols[i].label);
          }
      }
      mc->molecules = mols;
    }
  else						/* Load a file */
    {
      int i = 0;
      mc->nmolecules = 1;
      if (!mols) {
        mols = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
        parse_pdb_file (&mols[i], molecule_str);
        generate_molecule_formula (&mols[i]);
        insert_vertical_whitespace ((char *) mols[i].label);

        if ((wire || !do_atoms) &&
            !do_labels &&
            mols[i].nbonds == 0)
          {
            /* If we're not drawing atoms (e.g., wireframe mode), and
               there is no bond info, then make sure labels are turned on,
               or we'll be looking at a black screen... */
            (void) fprintf (stderr, "molecule: no bonds: turning -label on.\n");
            do_labels = 1;
          }
       }
       mc->molecules = mols;
    }
}



/* Window management, etc
 */
static void
reshape_molecule (ModeInfo *mi, int width, int height)
{
  GLfloat h = (GLfloat) height / (GLfloat) width;

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

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  gluPerspective( 30.0, 1/h, 1.0, 100.0 );
  gluLookAt( 0.0, 0.0, 15.0,
             0.0, 0.0, 0.0,
             0.0, 1.0, 0.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0, 0.0, -15.0);

  glClear(GL_COLOR_BUFFER_BIT);
}


static void
gl_init(void)
{
  static GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
  glLightfv(GL_LIGHT0, GL_POSITION, pos);
  if (!not_first_gl)
   {
    orig_do_labels = do_labels;
    orig_do_bonds = do_bonds;
    orig_wire = cur_wire;
    orig_do_titles = do_titles;
    not_first_gl = True;
   }
}


/* lifted from lament.c */
#define RAND(n) ((long) ((LRAND() & 0x7fffffff) % ((long) (n))))
#ifndef RANDSIGN
#define RANDSIGN() ((LRAND() & 1) ? 1 : -1)
#endif

static void
rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v, Bool verbose)
{
  double ppos = *pos;

  /* tick position */
  if (ppos < 0)
    ppos = -(ppos + *v);
  else
    ppos += *v;

  if (ppos > 1.0)
    ppos -= 1.0;
  else if (ppos < 0)
    ppos += 1.0;

  if ((ppos < 0.0) || (ppos > 1.0)) {
    if (verbose) {
      (void) fprintf(stderr, "Weirdness in rotate()\n");
      (void) fprintf(stderr, "ppos = %g\n", ppos);
    }
    return;
  }
  *pos = (*pos > 0 ? ppos : -ppos);

  /* accelerate */
  *v += *dv;

  /* clamp velocity */
  if (*v > max_v || *v < -max_v)
    {
      *dv = -*dv;
    }
  /* If it stops, start it going in the other direction. */
  else if (*v < 0)
    {
      if (random() % 4)
	{
	  *v = 0;

	  /* keep going in the same direction */
	  if (random() % 2)
	    *dv = 0;
	  else if (*dv < 0)
	    *dv = -*dv;
	}
      else
	{
	  /* reverse gears */
	  *v = -*v;
	  *dv = -*dv;
	  *pos = -*pos;
	}
    }

  /* Alter direction of rotational acceleration randomly. */
  if (! (random() % 120))
    *dv = -*dv;

  /* Change acceleration very occasionally. */
  if (! (random() % 200))
    {
      if (*dv == 0)
	*dv = 0.00001;
      else if (random() & 1)
	*dv *= 1.2;
      else
	*dv *= 0.8;
    }
}


static void
startup_blurb (ModeInfo *mi)
{
  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
  const char *s = "Constructing molecules...";
  print_title_string (mi, s,
                      MI_WIDTH(mi) - (string_width (mc->xfont2, s) + 40),
                      10 + mc->xfont2->ascent + mc->xfont2->descent,
                      mc->xfont2->ascent + mc->xfont2->descent);
  glFinish();
  glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
  firstcall = False;
}

ENTRYPOINT void
release_molecule(ModeInfo * mi)
{
  if (mcs != NULL) {
	int         screen;

	for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
	  molecule_configuration *mc = &mcs[screen];

#if 0
	int         i;

	  for (i = 0; i < mc->nmolecules; i++) {
	    molecule *m = &mc->molecules[i];

	    if (m->atoms) {
		free(m->atoms);
		m->atoms = (molecule_atom *) NULL;
	    }
	    if (m->bonds) {
		free(m->bonds);
		m->bonds = (molecule_bond *) NULL;
	    }
	    if (m->label) {
		free(m->label);
		m->label = (char *) NULL;
	    }
	  }
	  if (mc->molecules) {
		free(mc->molecules);
		mc->molecules = (molecule *) NULL;
	  }
#endif

	  if (mc->glx_context) {
		/* Display lists MUST be freed while their glXContext is current. */
#ifdef WIN32
		wglMakeCurrent(hdc, mc->glx_context);
#else
		glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
#endif
		/* Free font stuff */
		free_fonts (mi);
	  }
	}
	free(mcs);
	mcs = (molecule_configuration *) NULL;
  }
  FreeAllGL(mi);
}

ENTRYPOINT void
init_molecule (ModeInfo *mi)
{
  molecule_configuration *mc;
  int wire;

#ifndef STANDALONE
  timeout = MI_CYCLES(mi);
#endif
  MI_INIT(mi, mcs);
  mc = &mcs[MI_SCREEN(mi)];
  if (mc->glx_context) {
	/* Free font stuff */
	free_fonts (mi);
  }

  if ((mc->glx_context = init_GL(mi)) != NULL) {
    glDrawBuffer(GL_BACK);
    gl_init();
    last = 0;
    reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
  }

  if (!load_fonts (mi)) {
	release_molecule(mi);
	return;
  }
  if (firstcall)
  startup_blurb (mi);
  cur_wire = MI_IS_WIREFRAME(mi);
  wire = cur_wire;

  mc->rotx = FLOATRAND(1.0) * RANDSIGN();
  mc->roty = FLOATRAND(1.0) * RANDSIGN();
  mc->rotz = FLOATRAND(1.0) * RANDSIGN();

  /* bell curve from 0-6 degrees, avg 3 */
  mc->dx = (FLOATRAND(0.1) + FLOATRAND(0.1) + FLOATRAND(0.1)) / (360/2);
  mc->dy = (FLOATRAND(0.1) + FLOATRAND(0.1) + FLOATRAND(0.1)) / (360/2);
  mc->dz = (FLOATRAND(0.1) + FLOATRAND(0.1) + FLOATRAND(0.1)) / (360/2);

  mc->d_max = mc->dx * 8;

  mc->ddx = 0.00006 + FLOATRAND(0.00003);
  mc->ddy = 0.00006 + FLOATRAND(0.00003);
  mc->ddz = 0.00006 + FLOATRAND(0.00003);

  {
    char *s = do_spin;
    while (*s)
      {
        if      (*s == 'x' || *s == 'X') mc->spin_x = 1;
        else if (*s == 'y' || *s == 'Y') mc->spin_y = 1;
        else if (*s == 'z' || *s == 'Z') mc->spin_z = 1;
        else
          {
            (void) fprintf (stderr,
         "molecule: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
                     do_spin);
            /* exit (1); */
          }
        s++;
      }
  }

  mc->molecule_dlist = glGenLists(1);

  load_molecules (mi);
  mc->which =  NRAND(mc->nmolecules);

#ifdef STANDALONE
  mc->no_label_threshold = get_float_resource (MI_DISPLAY(mi),
                                               "noLabelThreshold",
                                               "NoLabelThreshold");
  mc->wireframe_threshold = get_float_resource (MI_DISPLAY(mi),
                                                "wireframeThreshold",
                                                "WireframeThreshold");
#else
  mc->no_label_threshold = 30;
  mc->wireframe_threshold = 150;
#endif

  if (wire)
    do_bonds = 1;
}


ENTRYPOINT void
draw_molecule (ModeInfo *mi)
{
/*  static time_t last = 0; */
  time_t now = time ((time_t *) 0);
  molecule_configuration *mc;
  Display *dpy = MI_DISPLAY(mi);
  Window window = MI_WINDOW(mi);

  if (mcs == NULL)
	return;
  mc = &mcs[MI_SCREEN(mi)];
  if (!mc->glx_context)
    return;

  MI_IS_DRAWN(mi) = True;

  if (last + timeout <= now)   /* randomize molecules every -timeout seconds */
    {
      if (mc->nmolecules == 1)
        {
          if (last != 0) goto SKIP;
          mc->which = 0;
        }
      else if (last == 0)
        {
          mc->which = NRAND(mc->nmolecules);
        }
      else
        {
          int n = mc->which;
          while (n == mc->which)
            n = NRAND(mc->nmolecules);
          mc->which = n;
        }

      last = now;


      glNewList (mc->molecule_dlist, GL_COMPILE);
      ensure_bounding_box_visible (mi);
      if (MI_IS_ICONIC(mi))
        {do_labels = False;
	 do_bonds  = True;
         do_titles = False;
	}
      else
        {
      do_labels = orig_do_labels;
      do_bonds = orig_do_bonds;
      do_titles = orig_do_titles;
        }
      cur_wire = orig_wire;

      if (mc->molecule_size > mc->no_label_threshold)
        do_labels = 0;
      if (mc->molecule_size > mc->wireframe_threshold)
        cur_wire = 1;

      if (cur_wire)
        do_bonds = 1;

      build_molecule (mi);

      glEndList();
    }
 SKIP:

  glPushMatrix ();
  glScalef(1.1, 1.1, 1.1);

  {
    GLfloat x, y, z;

    if (do_wander)
      {
        static int frame = 0;

#       define SINOID(SCALE,SIZE) \
        ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2)

        x = SINOID(0.031, 9.0);
        y = SINOID(0.023, 9.0);
        z = SINOID(0.017, 9.0);
        frame++;
        glTranslatef(x, y, z);
      }

    if (mc->spin_x || mc->spin_y || mc->spin_z)
      {
        x = mc->rotx;
        y = mc->roty;
        z = mc->rotz;
        if (x < 0) x = 1 - (x + 1);
        if (y < 0) y = 1 - (y + 1);
        if (z < 0) z = 1 - (z + 1);

        if (mc->spin_x) glRotatef(x * 360, 1.0, 0.0, 0.0);
        if (mc->spin_y) glRotatef(y * 360, 0.0, 1.0, 0.0);
        if (mc->spin_z) glRotatef(z * 360, 0.0, 0.0, 1.0);

        rotate(&mc->rotx, &mc->dx, &mc->ddx, mc->d_max, MI_IS_VERBOSE(mi));
        rotate(&mc->roty, &mc->dy, &mc->ddy, mc->d_max, MI_IS_VERBOSE(mi));
        rotate(&mc->rotz, &mc->dz, &mc->ddz, mc->d_max, MI_IS_VERBOSE(mi));
      }
  }

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glCallList (mc->molecule_dlist);
  glPopMatrix ();

  if (MI_IS_FPS(mi)) do_fps (mi);
  glFinish();

  glXSwapBuffers(dpy, window);
}

XSCREENSAVER_MODULE ("Molecule", molecule)

#endif /* MODE_molecule */
