http://www.tienza.es/crux/src/www.jwz.org/xscreensaver/xscreensaver-5.05.tar.gz
[xscreensaver] / hacks / glx / molecule.c
index c6c59b868ed0e9e8de26793e7a4e72fb2db6ea7d..5178da62e0940a121ccf1db48fc03f249a039fdf 100644 (file)
@@ -1,4 +1,4 @@
-/* molecule, Copyright (c) 2001 Jamie Zawinski <jwz@jwz.org>
+/* molecule, Copyright (c) 2001-2006 Jamie Zawinski <jwz@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 on the PDB file format:
-   http://www.rcsb.org/pdb/docs/format/pdbguide2.2/guide2.2_frame.html
+   http://www.wwpdb.org/docs.html
+   http://www.rcsb.org/pdb/file_formats/pdb/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. 
-
-   GENERAL OPENGL NAIVETY:
-
-       I don't understand the *right* way to place text in front of the
-       atoms.  What I'm doing now is close, but has glitches.  I think I
-       understand glPolygonOffset(), but I think it doesn't help me.
-
-       Here's how I'd phrase the problem I'm trying to solve:
-
-       - I have a bunch of spherical objects of various sizes
-       - I want a piece of text in the scene, between each object
-         and the observer
-       - the position of this text should be apparently tangential 
-         to the surface of the sphere, so that:
-         - it is never inside the sphere;
-         - but can be occluded by other objects in the scene.
-
-       So I was trying to use glPolygonOffset() to say "pretend all
-       polygons are N units deeper than they actually are" where N was
-       somewhere around the maximal radius of the objects.  Which wasn't a
-       perfect solution, but was close.  But it turns out that can't work,
-       because the second arg to glPolygonOffset() is multiplied by some
-       minimal depth quantum which is not revealed, so I can't pass it an
-       offset in scene units -- only in multiples of the quantum.  So I
-       don't know how many quanta in radius my spheres are.
-
-       I think I need to position and render the text with glRasterPos3f()
-       so that the text is influenced by the depth buffer.  If I used 2f,
-       or an explicit constant Z value, then the text would always be in
-       front of each sphere, and text would be visible for spheres that
-       were fully occluded, which isn't what I want.
-
-       So my only guess at this point is that I need to position the text
-       exactly where I want it, tangential to the spheres -- but that
-       means I need to be able to compute that XYZ position, which is
-       dependent on the position of the observer!  Which means two things:
-       first, while generating my scene, I need to take into account the
-       position of the observer, and I don't have a clue how to do that;
-       and second, it means I can't put my whole molecule in a display
-       list, because the XYZ position of the text in the scene changes at
-       every frame, as the molecule rotates.
-
-       This just *can't* be as hard as it seems!
+   http://www.umass.edu/microbio/rasmol/whereget.htm
+   http://www.wwpdb.org/docs.html
  */
 
-#include <X11/Intrinsic.h>
-
-#define PROGCLASS      "Molecule"
-#define HACK_INIT      init_molecule
-#define HACK_DRAW      draw_molecule
-#define HACK_RESHAPE   reshape_molecule
-#define molecule_opts  xlockmore_opts
-
-#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)"
-
 #define DEFAULTS       "*delay:        10000         \n" \
-                       "*timeout:    " DEF_TIMEOUT  "\n" \
                        "*showFPS:      False         \n" \
                        "*wireframe:    False         \n" \
-                       "*molecule:   " DEF_MOLECULE "\n" \
-                       "*spin:       " DEF_SPIN     "\n" \
-                       "*wander:     " DEF_WANDER   "\n" \
-                       "*labels:     " DEF_LABELS   "\n" \
-                       "*atoms:      " DEF_ATOMS    "\n" \
-                       "*bonds:      " DEF_BONDS    "\n" \
-                       "*bbox:       " DEF_BBOX     "\n" \
                        "*atomFont:   -*-times-bold-r-normal-*-240-*\n" \
                        "*titleFont:  -*-times-bold-r-normal-*-180-*\n" \
                        "*noLabelThreshold:    30     \n" \
                        "*wireframeThreshold:  150    \n" \
 
-
+# define refresh_molecule 0
+# define release_molecule 0
 #undef countof
 #define countof(x) (sizeof((x))/sizeof((*x)))
 
 #include "colors.h"
 #include "sphere.h"
 #include "tube.h"
+#include "glxfonts.h"
+#include "rotator.h"
+#include "gltrackball.h"
 
 #ifdef USE_GL /* whole file */
 
-#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
 #include <ctype.h>
-#include <GL/glu.h>
 
-#define SPHERE_SLICES 16  /* how densely to render spheres */
-#define SPHERE_STACKS 10
+#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_SHELLS      "True"
+#define DEF_BBOX        "False"
+#define DEF_SHELL_ALPHA "0.3"
+#define DEF_MOLECULE    "(default)"
+#define DEF_VERBOSE     "False"
+
+#define SPHERE_SLICES 24  /* how densely to render spheres */
+#define SPHERE_STACKS 12
 
 #define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */
 
 # define TUBE_FACES  8
 #endif
 
-static int scale_down;
 #define SPHERE_SLICES_2  7
 #define SPHERE_STACKS_2  4
 #define TUBE_FACES_2     3
 
 
+# ifdef __GNUC__
+  __extension__  /* don't warn about "string length is greater than the length
+                    ISO C89 compilers are required to support" when includng
+                    the following data file... */
+# endif
 const char * const builtin_pdb_data[] = {
 # include "molecules.h"
 };
@@ -150,15 +100,16 @@ typedef struct {
 /* 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",           "Grey70",        { 0, }},
-  { "C",    1.75,  0,  "Grey60",          "White",         { 0, }},
-  { "N",    1.55,  0,  "LightSteelBlue3", "SlateBlue1",    { 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",          "LightGreen",    { 0, }}
+static const atom_data all_atom_data[] = {
+  { "H",    1.17,  0.40, "#FFFFFF", "#B3B3B3", { 0, }},
+  { "C",    1.75,  0.58, "#999999", "#FFFFFF", { 0, }},
+  { "CA",   1.80,  0.60, "#0000FF", "#ADD8E6", { 0, }},
+  { "N",    1.55,  0.52, "#A2B5CD", "#836FFF", { 0, }},
+  { "O",    1.40,  0.47, "#FF0000", "#FFB6C1", { 0, }},
+  { "P",    1.28,  0.43, "#9370DB", "#DB7093", { 0, }},
+  { "S",    1.80,  0.60, "#8B8B00", "#FFFF00", { 0, }},
+  { "bond", 0,     0,    "#B3B3B3", "#FFFF00", { 0, }},
+  { "*",    1.40,  0.47, "#008B00", "#90EE90", { 0, }}
 };
 
 
@@ -166,7 +117,7 @@ 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 */
+  const atom_data *data; /* computed: which style of atom this is */
 } molecule_atom;
 
 typedef struct {
@@ -186,13 +137,9 @@ typedef struct {
 
 typedef struct {
   GLXContext *glx_context;
-
-  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;
+  rotator *rot;
+  trackball_state *trackball;
+  Bool button_down_p;
 
   GLfloat molecule_size;          /* max dimension of molecule bounding box */
 
@@ -203,10 +150,20 @@ typedef struct {
   int nmolecules;
   molecule *molecules;
 
+  int mode;  /* 0 = normal, 1 = out, 2 = in */
+  int mode_tick;
+
   GLuint molecule_dlist;
+  GLuint shell_dlist;
 
   XFontStruct *xfont1, *xfont2;
   GLuint font1_dlist, font2_dlist;
+  int polygon_count;
+
+  time_t draw_time;
+  int draw_tick;
+
+  int scale_down;
 
 } molecule_configuration;
 
@@ -221,87 +178,75 @@ static Bool do_titles;
 static Bool do_labels;
 static Bool do_atoms;
 static Bool do_bonds;
+static Bool do_shells;
 static Bool do_bbox;
+static Bool verbose_p;
+static GLfloat shell_alpha;
 
-static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
+/* saved to reset */
+static Bool orig_do_labels, orig_do_atoms, orig_do_bonds, orig_do_shells,
+    orig_wire;
 
 
 static XrmOptionDescRec opts[] = {
-  { "-molecule", ".molecule", XrmoptionSepArg, 0 },
-  { "-timeout",".timeout",XrmoptionSepArg, 0 },
-  { "-spin",   ".spin",   XrmoptionSepArg, 0 },
-  { "+spin",   ".spin",   XrmoptionNoArg, "False" },
-  { "-wander", ".wander", XrmoptionNoArg, "True" },
-  { "+wander", ".wander", XrmoptionNoArg, "False" },
-  { "-labels", ".labels", XrmoptionNoArg, "True" },
-  { "+labels", ".labels", XrmoptionNoArg, "False" },
-  { "-titles", ".titles", XrmoptionNoArg, "True" },
-  { "+titles", ".titles", XrmoptionNoArg, "False" },
-  { "-atoms",  ".atoms",  XrmoptionNoArg, "True" },
-  { "+atoms",  ".atoms",  XrmoptionNoArg, "False" },
-  { "-bonds",  ".bonds",  XrmoptionNoArg, "True" },
-  { "+bonds",  ".bonds",  XrmoptionNoArg, "False" },
-  { "-bbox",   ".bbox",  XrmoptionNoArg, "True" },
-  { "+bbox",   ".bbox",  XrmoptionNoArg, "False" },
+  { "-molecule",       ".molecule",    XrmoptionSepArg, 0 },
+  { "-timeout",                ".timeout",     XrmoptionSepArg, 0 },
+  { "-spin",           ".spin",        XrmoptionSepArg, 0 },
+  { "+spin",           ".spin",        XrmoptionNoArg, "" },
+  { "-wander",         ".wander",      XrmoptionNoArg, "True" },
+  { "+wander",         ".wander",      XrmoptionNoArg, "False" },
+  { "-labels",         ".labels",      XrmoptionNoArg, "True" },
+  { "+labels",         ".labels",      XrmoptionNoArg, "False" },
+  { "-titles",         ".titles",      XrmoptionNoArg, "True" },
+  { "+titles",         ".titles",      XrmoptionNoArg, "False" },
+  { "-atoms",          ".atoms",       XrmoptionNoArg, "True" },
+  { "+atoms",          ".atoms",       XrmoptionNoArg, "False" },
+  { "-bonds",          ".bonds",       XrmoptionNoArg, "True" },
+  { "+bonds",          ".bonds",       XrmoptionNoArg, "False" },
+  { "-shells",         ".eshells",     XrmoptionNoArg, "True" },
+  { "+shells",         ".eshells",     XrmoptionNoArg, "False" },
+  { "-shell-alpha",    ".shellAlpha",  XrmoptionSepArg, 0 },
+  { "-bbox",           ".bbox",        XrmoptionNoArg, "True" },
+  { "+bbox",           ".bbox",        XrmoptionNoArg, "False" },
+  { "-verbose",                ".verbose",     XrmoptionNoArg, "True" },
 };
 
 static argtype vars[] = {
-  {(caddr_t *) &molecule_str, "molecule",   "Molecule", DEF_MOLECULE,t_String},
-  {(caddr_t *) &timeout,   "timeout","Seconds",DEF_TIMEOUT,t_Int},
-  {(caddr_t *) &do_spin,   "spin",   "Spin",   DEF_SPIN,   t_String},
-  {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
-  {(caddr_t *) &do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
-  {(caddr_t *) &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
-  {(caddr_t *) &do_atoms,  "atoms",  "Atoms",  DEF_ATOMS,  t_Bool},
-  {(caddr_t *) &do_bonds,  "bonds",  "Bonds",  DEF_BONDS,  t_Bool},
-  {(caddr_t *) &do_bbox,   "bbox",   "BBox",   DEF_BBOX,   t_Bool},
+  {&molecule_str, "molecule",  "Molecule",     DEF_MOLECULE, t_String},
+  {&timeout,     "timeout",    "Seconds",      DEF_TIMEOUT,  t_Int},
+  {&do_spin,     "spin",       "Spin",         DEF_SPIN,     t_String},
+  {&do_wander,   "wander",     "Wander",       DEF_WANDER,   t_Bool},
+  {&do_atoms,    "atoms",      "Atoms",        DEF_ATOMS,    t_Bool},
+  {&do_bonds,    "bonds",      "Bonds",        DEF_BONDS,    t_Bool},
+  {&do_shells,   "eshells",    "EShells",      DEF_SHELLS,   t_Bool},
+  {&do_labels,   "labels",     "Labels",       DEF_LABELS,   t_Bool},
+  {&do_titles,   "titles",     "Titles",       DEF_TITLES,   t_Bool},
+  {&do_bbox,     "bbox",       "BBox",         DEF_BBOX,     t_Bool},
+  {&shell_alpha,  "shellAlpha",        "ShellAlpha",   DEF_SHELL_ALPHA, t_Float},
+  {&verbose_p,   "verbose",    "Verbose",      DEF_VERBOSE,  t_Bool},
 };
 
-ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
+ENTRYPOINT ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
 
 
 
 \f
 /* shapes */
 
-static void
-sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
+static int
+sphere (molecule_configuration *mc,
+        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);
+  int stacks = (mc->scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
+  int slices = (mc->scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
 
   glPushMatrix ();
   glTranslatef (x, y, z);
   glScalef (diameter, diameter, diameter);
   unit_sphere (stacks, slices, wire);
   glPopMatrix ();
-}
-
-
-static void
-load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
-{
-  const char *font = get_string_resource (res, "Font");
-  XFontStruct *f;
-  Font id;
-  int first, last;
-
-  if (!font) font = "-*-times-bold-r-normal-*-180-*";
-
-  f = XLoadQueryFont(mi->dpy, font);
-  if (!f) f = XLoadQueryFont(mi->dpy, "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");
-  glXUseXFont(id, first, last-first+1, *dlistP + first);
-  check_gl_error ("glXUseXFont");
 
-  *fontP = f;
+  return stacks * slices;
 }
 
 
@@ -309,32 +254,16 @@ static void
 load_fonts (ModeInfo *mi)
 {
   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
-  load_font (mi, "atomFont",  &mc->xfont1, &mc->font1_dlist);
-  load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
-}
-
-
-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;
+  load_font (mi->dpy, "atomFont",  &mc->xfont1, &mc->font1_dlist);
+  load_font (mi->dpy, "titleFont", &mc->xfont2, &mc->font2_dlist);
 }
 
 
-static atom_data *
+static const atom_data *
 get_atom_data (const char *atom_name)
 {
   int i;
-  atom_data *d = 0;
+  const atom_data *d = 0;
   char *n = strdup (atom_name);
   char *n2 = n;
   int L;
@@ -347,7 +276,7 @@ get_atom_data (const char *atom_name)
   for (i = 0; i < countof(all_atom_data); i++)
     {
       d = &all_atom_data[i];
-      if (!strcmp (n, all_atom_data[i].name))
+      if (!strcasecmp (n, all_atom_data[i].name))
         break;
     }
 
@@ -357,22 +286,32 @@ get_atom_data (const char *atom_name)
 
 
 static void
-set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
+set_atom_color (ModeInfo *mi, const molecule_atom *a, 
+                Bool font_p, GLfloat alpha)
 {
-  atom_data *d;
-  GLfloat *gl_color;
+  const atom_data *d;
+  GLfloat gl_color[4];
 
   if (a)
     d = a->data;
+  else
+    d = get_atom_data ("bond");
+
+  if (font_p)
+    {
+      gl_color[0] = d->gl_color[4];
+      gl_color[1] = d->gl_color[5];
+      gl_color[2] = d->gl_color[6];
+      gl_color[3] = d->gl_color[7];
+    }
   else
     {
-      static atom_data *def_data = 0;
-      if (!def_data) def_data = get_atom_data ("bond");
-      d = def_data;
+      gl_color[0] = d->gl_color[0];
+      gl_color[1] = d->gl_color[1];
+      gl_color[2] = d->gl_color[2];
+      gl_color[3] = d->gl_color[3];
     }
 
-  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;
@@ -387,36 +326,22 @@ set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
       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;
     }
   
+  gl_color[3] = alpha;
+
   if (font_p)
-    glColor3f (gl_color[0], gl_color[1], gl_color[2]);
+    glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
   else
     glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
 }
 
 
 static GLfloat
-atom_size (molecule_atom *a)
+atom_size (const 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;
-    }
+    return a->data->size2;
   else
     return a->data->size;
 }
@@ -478,20 +403,20 @@ molecule_bounding_box (ModeInfo *mi,
       if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
     }
 
-  *x1 -= 1;
-  *y1 -= 1;
-  *z1 -= 1;
-  *x2 += 1;
-  *y2 += 1;
-  *z2 += 1;
+  *x1 -= 1.5;
+  *y1 -= 1.5;
+  *z1 -= 1.5;
+  *x2 += 1.5;
+  *y2 += 1.5;
+  *z2 += 1.5;
 }
 
 
 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 };
+  static const GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
+  static const GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
   int wire = MI_IS_WIREFRAME(mi);
   GLfloat x1, y1, z1, x2, y2, z2;
   molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
@@ -572,14 +497,14 @@ ensure_bounding_box_visible (ModeInfo *mi)
 
   mc->molecule_size = size;
 
-  scale_down = 0;
+  mc->scale_down = 0;
 
   if (size > max_size)
     {
       GLfloat scale = max_size / size;
       glScalef (scale, scale, scale);
 
-      scale_down = scale < 0.3;
+      mc->scale_down = scale < 0.3;
     }
 
   glTranslatef (-(x1 + w/2),
@@ -588,85 +513,17 @@ ensure_bounding_box_visible (ModeInfo *mi)
 }
 
 
-static void
-print_title_string (ModeInfo *mi, const char *string,
-                    GLfloat x, GLfloat y, XFontStruct *font)
-{
-  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
-  GLfloat line_height = font->ascent + font->descent;
-  GLfloat sub_shift = (line_height * 0.3);
-
-  y -= line_height;
-
-  glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
-                GL_ENABLE_BIT);     /* for various glDisable calls */
-  glDisable (GL_LIGHTING);
-  glDisable (GL_DEPTH_TEST);
-  {
-    glMatrixMode(GL_PROJECTION);
-    glPushMatrix();
-    {
-      glLoadIdentity();
-
-      glMatrixMode(GL_MODELVIEW);
-      glPushMatrix();
-      {
-        int i;
-        int x2 = x;
-        Bool sub_p = False;
-        glLoadIdentity();
-
-        gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
-
-        set_atom_color (mi, 0, True);
-
-        glRasterPos2f (x, y);
-        for (i = 0; i < strlen(string); i++)
-          {
-            char c = string[i];
-            if (c == '\n')
-              {
-                glRasterPos2f (x, (y -= line_height));
-                x2 = x;
-              }
-            else if (c == '(' && (isdigit (string[i+1])))
-              {
-                sub_p = True;
-                glRasterPos2f (x2, (y -= sub_shift));
-              }
-            else if (c == ')' && sub_p)
-              {
-                sub_p = False;
-                glRasterPos2f (x2, (y += sub_shift));
-              }
-            else
-              {
-                glCallList (mc->font2_dlist + (int)(c));
-                x2 += (font->per_char
-                       ? font->per_char[c - font->min_char_or_byte2].width
-                       : font->min_bounds.width);
-              }
-          }
-      }
-      glPopMatrix();
-    }
-    glMatrixMode(GL_PROJECTION);
-    glPopMatrix();
-  }
-  glPopAttrib();
-
-  glMatrixMode(GL_MODELVIEW);
-}
-
 
 /* Constructs the GL shapes of the current molecule
  */
 static void
-build_molecule (ModeInfo *mi)
+build_molecule (ModeInfo *mi, Bool transparent_p)
 {
   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
   int wire = MI_IS_WIREFRAME(mi);
   int i;
+  GLfloat alpha = transparent_p ? shell_alpha : 1.0;
+  int polys = 0;
 
   molecule *m = &mc->molecules[mc->which];
 
@@ -689,30 +546,15 @@ build_molecule (ModeInfo *mi)
       glEnable(GL_CULL_FACE);
     }
 
-#if 0
-  if (do_labels && !wire)
-    {
-      /* This is so all polygons are drawn slightly farther back in the depth
-         buffer, so that when we render text directly on top of the spheres,
-         it still shows up. */
-      glEnable (GL_POLYGON_OFFSET_FILL);
-      glPolygonOffset (1.0, (do_bonds ? 10.0 : 35.0));
-    }
-  else
-    {
-      glDisable (GL_POLYGON_OFFSET_FILL);
-    }
-#endif
-
   if (!wire)
-    set_atom_color (mi, 0, False);
+    set_atom_color (mi, 0, False, alpha);
 
   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);
-        molecule_atom *to   = get_atom (m->atoms, m->natoms, b->to);
+        const molecule_bond *b = &m->bonds[i];
+        const molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
+        const molecule_atom *to   = get_atom (m->atoms, m->natoms, b->to);
 
         if (wire)
           {
@@ -720,10 +562,11 @@ build_molecule (ModeInfo *mi)
             glVertex3f(from->x, from->y, from->z);
             glVertex3f(to->x,   to->y,   to->z);
             glEnd();
+            polys++;
           }
         else
           {
-            int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
+            int faces = (mc->scale_down ? TUBE_FACES_2 : TUBE_FACES);
 # ifdef SMOOTH_TUBE
             int smooth = True;
 # else
@@ -734,73 +577,29 @@ build_molecule (ModeInfo *mi)
             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, wire);
+            polys += tube (from->x, from->y, from->z,
+                           to->x,   to->y,   to->z,
+                           thickness, cap_size,
+                           faces, smooth, (!do_atoms || do_shells), wire);
           }
       }
 
   if (!wire && do_atoms)
     for (i = 0; i < m->natoms; i++)
       {
-        molecule_atom *a = &m->atoms[i];
+        const molecule_atom *a = &m->atoms[i];
         GLfloat size = atom_size (a);
-        set_atom_color (mi, a, False);
-        sphere (a->x, a->y, a->z, size, wire);
-      }
-
-  /* Second pass to draw labels, after all atoms and bonds are in place
-   */
-  if (do_labels)
-    for (i = 0; i < m->natoms; i++)
-      {
-        molecule_atom *a = &m->atoms[i];
-        int j;
-
-        if (!wire)
-          {
-            glDisable (GL_LIGHTING);
-#if 1
-            glDisable (GL_DEPTH_TEST);
-#endif
-          }
-
-        if (!wire)
-          set_atom_color (mi, a, True);
-
-        glRasterPos3f (a->x, a->y, a->z);
-
-        /* Before drawing the string, shift the origin to center
-           the text over the origin of the sphere. */
-        glBitmap (0, 0, 0, 0,
-                  -string_width (mc->xfont1, a->label) / 2,
-                  -mc->xfont1->descent,
-                  NULL);
-
-        for (j = 0; j < strlen(a->label); j++)
-          glCallList (mc->font1_dlist + (int)(a->label[j]));
-
-        /* More efficient to always call glEnable() with correct values
-           than to call glPushAttrib()/glPopAttrib(), since reading
-           attributes from GL does a round-trip and  stalls the pipeline.
-         */
-        if (!wire)
-          {
-            glEnable(GL_LIGHTING);
-#if 1
-            glEnable(GL_DEPTH_TEST);
-#endif
-          }
+        set_atom_color (mi, a, False, alpha);
+        polys += sphere (mc, a->x, a->y, a->z, size, wire);
       }
 
-  if (do_bbox)
-    draw_bounding_box (mi);
+  if (do_bbox && !transparent_p)
+    {
+      draw_bounding_box (mi);
+      polys += 4;
+    }
 
-  if (do_titles && m->label && *m->label)
-    print_title_string (mi, m->label,
-                        10, mi->xgwa.height - 10,
-                        mc->xfont2);
+  mc->polygon_count += polys;
 }
 
 
@@ -854,6 +653,14 @@ push_bond (molecule *m, int from, int to)
 }
 
 
+static void
+parse_error (const char *file, int lineno, const char *line)
+{
+  fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
+           progname, file, lineno, line);
+  exit (1);
+}
+
 
 /* This function is crap.
  */
@@ -924,6 +731,15 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
                !strncmp (s, "MTRIX3", 6) ||
                !strncmp (s, "SHEET ", 6) ||
                !strncmp (s, "CISPEP", 6) ||
+/*
+               !strncmp (s, "SEQADV", 6) ||
+               !strncmp (s, "SITE ",  5) ||
+               !strncmp (s, "FTNOTE", 6) ||
+               !strncmp (s, "MODEL ", 5) ||
+               !strncmp (s, "ENDMDL", 6) ||
+               !strncmp (s, "SPRSDE", 6) ||
+               !strncmp (s, "MODRES", 6) ||
+ */
                !strncmp (s, "GENERATED BY", 12) ||
                !strncmp (s, "TER ", 4) ||
                !strncmp (s, "END ", 4) ||
@@ -938,14 +754,23 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
           char *name = (char *) calloc (1, 4);
           GLfloat x = -999, y = -999, z = -999;
 
-          sscanf (s+7, " %d ", &id);
+          if (1 != sscanf (s+7, " %d ", &id))
+            parse_error (filename, line, s);
 
           strncpy (name, s+12, 3);
           while (isspace(*name)) name++;
           ss = name + strlen(name)-1;
           while (isspace(*ss) && ss > name)
             *ss-- = 0;
-          sscanf (s + 32, " %f %f %f ", &x, &y, &z);
+         ss = name + 1;
+         while(*ss)
+          {
+           *ss = tolower(*ss);
+            ss++;
+          }
+          if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
+            parse_error (filename, line, s);
+
 /*
           fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
                    progname, filename, line,
@@ -959,14 +784,16 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
           char *name = (char *) calloc (1, 4);
           GLfloat x = -999, y = -999, z = -999;
 
-          sscanf (s+7, " %d ", &id);
+          if (1 != sscanf (s+7, " %d ", &id))
+            parse_error (filename, line, s);
 
           strncpy (name, s+12, 3);
           while (isspace(*name)) name++;
           ss = name + strlen(name)-1;
           while (isspace(*ss) && ss > name)
             *ss-- = 0;
-          sscanf (s + 30, " %f %f %f ", &x, &y, &z);
+          if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
+            parse_error (filename, line, s);
 /*
           fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
                    progname, filename, line,
@@ -977,7 +804,7 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
       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 ",
+          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]);
@@ -1011,7 +838,7 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
 }
 
 
-static void
+static int
 parse_pdb_file (molecule *m, const char *name)
 {
   FILE *in;
@@ -1025,7 +852,7 @@ parse_pdb_file (molecule *m, const char *name)
       char *buf = (char *) malloc(1024 + strlen(name));
       sprintf(buf, "%s: error reading \"%s\"", progname, name);
       perror(buf);
-      exit (1);
+      return -1;
     }
 
   buf = (char *) malloc (buf_size);
@@ -1045,7 +872,7 @@ parse_pdb_file (molecule *m, const char *name)
     {
       fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
                progname, name);
-      exit (1);
+      return -1;
     }
 
   if (!m->nbonds && do_bonds)
@@ -1054,6 +881,8 @@ parse_pdb_file (molecule *m, const char *name)
                progname, name);
       do_bonds = 0;
     }
+
+  return 0;
 }
 
 
@@ -1118,7 +947,7 @@ generate_molecule_formula (molecule *m)
       free (counts[i].atom);
       s += strlen (s);
       if (counts[i].count > 1)
-        sprintf (s, "(%d)", counts[i].count);
+        sprintf (s, "[%d]", counts[i].count);  /* use [] to get subscripts */
       s += strlen (s);
       i++;
     }
@@ -1139,14 +968,14 @@ generate_molecule_formula (molecule *m)
 static void
 special_case_formula (char *f)
 {
-  if      (!strcmp(f, "H(2)Be"))   strcpy(f, "BeH(2)");
-  else if (!strcmp(f, "H(3)B"))    strcpy(f, "BH(3)");
-  else if (!strcmp(f, "H(3)N"))    strcpy(f, "NH(3)");
+  if      (!strcmp(f, "H[2]Be"))   strcpy(f, "BeH[2]");
+  else if (!strcmp(f, "H[3]B"))    strcpy(f, "BH[3]");
+  else if (!strcmp(f, "H[3]N"))    strcpy(f, "NH[3]");
   else if (!strcmp(f, "CHN"))      strcpy(f, "HCN");
   else if (!strcmp(f, "CKN"))      strcpy(f, "KCN");
-  else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
-  else if (!strcmp(f, "Cl(3)P"))   strcpy(f, "PCl(3)");
-  else if (!strcmp(f, "Cl(5)P"))   strcpy(f, "PCl(5)");
+  else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
+  else if (!strcmp(f, "Cl[3]P"))   strcpy(f, "PCl[3]");
+  else if (!strcmp(f, "Cl[5]P"))   strcpy(f, "PCl[5]");
 }
 
 
@@ -1173,41 +1002,136 @@ load_molecules (ModeInfo *mi)
 {
   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
   int wire = MI_IS_WIREFRAME(mi);
+  int i;
 
-  if (!molecule_str || !*molecule_str ||
-      !strcmp(molecule_str, "(default)"))      /* do the builtins */
+  mc->nmolecules = 0;
+  if (molecule_str && *molecule_str && 
+      strcmp(molecule_str, "(default)"))       /* try external PDB files */
+    {
+      /* The -molecule option can point to a .pdb file, or to
+         a directory of them.
+      */
+      struct stat st;
+      int nfiles = 0;
+      int list_size = 0;
+      char **files = 0;
+      int molecule_ctr;
+
+      if (!stat (molecule_str, &st) &&
+          S_ISDIR (st.st_mode))
+        {
+          char buf [255];
+          DIR *pdb_dir;
+          struct dirent *dentry;
+
+          pdb_dir = opendir (molecule_str);
+          if (! pdb_dir)
+            {
+              sprintf (buf, "%.100s: %.100s", progname, molecule_str);
+              perror (buf);
+              exit (1);
+            }
+
+          if (verbose_p)
+            fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
+
+          nfiles = 0;
+          list_size = 100;
+          files = (char **) calloc (sizeof(*files), list_size);
+
+          while ((dentry = readdir (pdb_dir)))
+            {
+              int L = strlen (dentry->d_name);
+              if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
+                {
+                  char *fn;
+                  if (nfiles >= list_size-1)
+                    {
+                      list_size = (list_size + 10) * 1.2;
+                      files = (char **)
+                        realloc (files, list_size * sizeof(*files));
+                      if (!files)
+                        {
+                        OOM:
+                          fprintf (stderr, "%s: out of memory (%d files)\n",
+                                   progname, nfiles);
+                          exit (1);
+                        }
+                    }
+
+                  fn = (char *) malloc (strlen (molecule_str) + L + 10);
+                  if (!fn) goto OOM;
+                  strcpy (fn, molecule_str);
+                  if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
+                  strcat (fn, dentry->d_name);
+                  files[nfiles++] = fn;
+                  if (verbose_p)
+                    fprintf (stderr, "%s: file %s\n", progname, fn);
+                }
+            }
+          closedir (pdb_dir);
+
+          if (nfiles == 0)
+            fprintf (stderr, "%s: no .pdb files in directory %s\n",
+                     progname, molecule_str);
+        }
+      else
+        {
+          files = (char **) malloc (sizeof (*files));
+          nfiles = 1;
+          files[0] = strdup (molecule_str);
+          if (verbose_p)
+            fprintf (stderr, "%s: file %s\n", progname, molecule_str);
+        }
+
+      mc->nmolecules = nfiles;
+      mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
+      molecule_ctr = 0;
+      for (i = 0; i < mc->nmolecules; i++)
+        {
+          if (verbose_p)
+            fprintf (stderr, "%s: reading %s\n", progname, files[i]);
+          if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
+            {
+              if ((wire || !do_atoms) &&
+                  !do_labels &&
+                  mc->molecules[molecule_ctr].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... */
+                  fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
+                           progname, files[i]);
+                  do_labels = 1;
+                }
+              free (files[i]);
+             files[i] = 0;
+              molecule_ctr++;
+           }
+        }
+
+      free (files);
+      files = 0;
+      mc->nmolecules = molecule_ctr;
+    }
+
+  if (mc->nmolecules == 0)     /* do the builtins if no files */
     {
-      int i;
       mc->nmolecules = countof(builtin_pdb_data);
       mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
       for (i = 0; i < mc->nmolecules; i++)
         {
           char name[100];
           sprintf (name, "<builtin-%d>", i);
+          if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
           parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
-          generate_molecule_formula (&mc->molecules[i]);
-          insert_vertical_whitespace ((char *) mc->molecules[i].label);
         }
     }
-  else                                         /* Load a file */
+
+  for (i = 0; i < mc->nmolecules; i++)
     {
-      int i = 0;
-      mc->nmolecules = 1;
-      mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
-      parse_pdb_file (&mc->molecules[i], molecule_str);
       generate_molecule_formula (&mc->molecules[i]);
       insert_vertical_whitespace ((char *) mc->molecules[i].label);
-
-      if ((wire || !do_atoms) &&
-          !do_labels &&
-          mc->molecules[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... */
-          fprintf (stderr, "%s: no bonds: turning -label on.\n", progname);
-          do_labels = 1;
-        }
     }
 }
 
@@ -1215,7 +1139,7 @@ load_molecules (ModeInfo *mi)
 \f
 /* Window management, etc
  */
-void
+ENTRYPOINT void
 reshape_molecule (ModeInfo *mi, int width, int height)
 {
   GLfloat h = (GLfloat) height / (GLfloat) width;
@@ -1224,15 +1148,13 @@ reshape_molecule (ModeInfo *mi, int width, int height)
 
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
-
-  gluPerspective( 30.0, 1/h, 20.0, 40.0 );
-  gluLookAt( 0.0, 0.0, 15.0,
-             0.0, 0.0, 0.0,
-             0.0, 1.0, 0.0);
+  gluPerspective (30.0, 1/h, 20.0, 100.0);
 
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
-  glTranslatef(0.0, 0.0, -15.0);
+  gluLookAt( 0.0, 0.0, 30.0,
+             0.0, 0.0, 0.0,
+             0.0, 1.0, 0.0);
 
   glClear(GL_COLOR_BUFFER_BIT);
 }
@@ -1241,106 +1163,88 @@ reshape_molecule (ModeInfo *mi, int width, int height)
 static void
 gl_init (ModeInfo *mi)
 {
-  static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
-  static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
-  static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
-  static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
+  static const GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
+  static const GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
+  static const GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
+  static const GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
   glLightfv(GL_LIGHT0, GL_POSITION, pos);
   glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
   glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
   glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
-
-  orig_do_labels = do_labels;
-  orig_do_bonds = do_bonds;
-  orig_wire = MI_IS_WIREFRAME(mi);
 }
 
 
-/* lifted from lament.c */
-#define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
-#define RANDSIGN() ((random() & 1) ? 1 : -1)
-
 static void
-rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v)
+startup_blurb (ModeInfo *mi)
 {
-  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) abort();
-  if (ppos > 1.0) abort();
-  *pos = (*pos > 0 ? ppos : -ppos);
+  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
+  const char *s = "Constructing molecules...";
+  print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
+                   mi->xgwa.width, mi->xgwa.height,
+                   10, mi->xgwa.height - 10,
+                   s);
+  glFinish();
+  glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
+}
 
-  /* accelerate */
-  *v += *dv;
+ENTRYPOINT Bool
+molecule_handle_event (ModeInfo *mi, XEvent *event)
+{
+  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
 
-  /* clamp velocity */
-  if (*v > max_v || *v < -max_v)
+  if (event->xany.type == ButtonPress &&
+      event->xbutton.button == Button1)
     {
-      *dv = -*dv;
+      mc->button_down_p = True;
+      gltrackball_start (mc->trackball,
+                         event->xbutton.x, event->xbutton.y,
+                         MI_WIDTH (mi), MI_HEIGHT (mi));
+      return True;
     }
-  /* If it stops, start it going in the other direction. */
-  else if (*v < 0)
+  else if (event->xany.type == ButtonRelease &&
+           event->xbutton.button == Button1)
     {
-      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;
-       }
+      mc->button_down_p = False;
+      return True;
     }
-
-  /* Alter direction of rotational acceleration randomly. */
-  if (! (random() % 120))
-    *dv = -*dv;
-
-  /* Change acceleration very occasionally. */
-  if (! (random() % 200))
+  else if (event->xany.type == ButtonPress &&
+           (event->xbutton.button == Button4 ||
+            event->xbutton.button == Button5 ||
+            event->xbutton.button == Button6 ||
+            event->xbutton.button == Button7))
     {
-      if (*dv == 0)
-       *dv = 0.00001;
-      else if (random() & 1)
-       *dv *= 1.2;
-      else
-       *dv *= 0.8;
+      gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
+                              !!event->xbutton.state);
+      return True;
     }
-}
+  else if (event->xany.type == KeyPress)
+    {
+      KeySym keysym;
+      char c = 0;
+      XLookupString (&event->xkey, &c, 1, &keysym, 0);
 
+      if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+        {
+          GLfloat speed = 4.0;
+          mc->mode = 1;
+          mc->mode_tick = 10 * speed;
+          return True;
+        }
+    }
+  else if (event->xany.type == MotionNotify &&
+           mc->button_down_p)
+    {
+      gltrackball_track (mc->trackball,
+                         event->xmotion.x, event->xmotion.y,
+                         MI_WIDTH (mi), MI_HEIGHT (mi));
+      return True;
+    }
 
-static void
-startup_blurb (ModeInfo *mi)
-{
-  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
-  const char *s = "Constructing molecules...";
-  print_title_string (mi, s,
-                      mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
-                      10 + mc->xfont2->ascent + mc->xfont2->descent,
-                      mc->xfont2);
-  glFinish();
-  glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
+  return False;
 }
 
-void 
+
+ENTRYPOINT void 
 init_molecule (ModeInfo *mi)
 {
   molecule_configuration *mc;
@@ -1367,28 +1271,19 @@ init_molecule (ModeInfo *mi)
 
   wire = MI_IS_WIREFRAME(mi);
 
-  mc->rotx = frand(1.0) * RANDSIGN();
-  mc->roty = frand(1.0) * RANDSIGN();
-  mc->rotz = frand(1.0) * RANDSIGN();
-
-  /* bell curve from 0-6 degrees, avg 3 */
-  mc->dx = (frand(1) + frand(1) + frand(1)) / (360/2);
-  mc->dy = (frand(1) + frand(1) + frand(1)) / (360/2);
-  mc->dz = (frand(1) + frand(1) + frand(1)) / (360/2);
-
-  mc->d_max = mc->dx * 2;
-
-  mc->ddx = 0.00006 + frand(0.00003);
-  mc->ddy = 0.00006 + frand(0.00003);
-  mc->ddz = 0.00006 + frand(0.00003);
-
   {
+    Bool spinx=False, spiny=False, spinz=False;
+    double spin_speed   = 0.5;
+    double spin_accel   = 0.3;
+    double wander_speed = 0.01;
+
     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;
+        if      (*s == 'x' || *s == 'X') spinx = True;
+        else if (*s == 'y' || *s == 'Y') spiny = True;
+        else if (*s == 'z' || *s == 'Z') spinz = True;
+        else if (*s == '0') ;
         else
           {
             fprintf (stderr,
@@ -1398,28 +1293,216 @@ init_molecule (ModeInfo *mi)
           }
         s++;
       }
+
+    mc->rot = make_rotator (spinx ? spin_speed : 0,
+                            spiny ? spin_speed : 0,
+                            spinz ? spin_speed : 0,
+                            spin_accel,
+                            do_wander ? wander_speed : 0,
+                            (spinx && spiny && spinz));
+    mc->trackball = gltrackball_init ();
   }
 
+  orig_do_labels = do_labels;
+  orig_do_atoms  = do_atoms;
+  orig_do_bonds  = do_bonds;
+  orig_do_shells = do_shells;
+  orig_wire = MI_IS_WIREFRAME(mi);
+
   mc->molecule_dlist = glGenLists(1);
+  if (do_shells)
+    mc->shell_dlist = glGenLists(1);
 
   load_molecules (mi);
   mc->which = random() % mc->nmolecules;
 
-  mc->no_label_threshold = get_float_resource ("noLabelThreshold",
+  mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
                                                "NoLabelThreshold");
-  mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
+  mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
                                                 "WireframeThreshold");
+  mc->mode = 0;
 
   if (wire)
     do_bonds = 1;
 }
 
 
-void
+/* Put the labels on the atoms.
+   This can't be a part of the display list because of the games
+   we play with the translation matrix.
+ */
+static void
+draw_labels (ModeInfo *mi)
+{
+  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
+  int wire = MI_IS_WIREFRAME(mi);
+  molecule *m = &mc->molecules[mc->which];
+  int i, j;
+
+  if (!do_labels)
+    return;
+
+  if (!wire)
+    glDisable (GL_LIGHTING);   /* don't light fonts */
+
+  for (i = 0; i < m->natoms; i++)
+    {
+      molecule_atom *a = &m->atoms[i];
+      GLfloat size = atom_size (a);
+      GLfloat m[4][4];
+
+      glPushMatrix();
+
+      if (!wire)
+        set_atom_color (mi, a, True, 1);
+
+      /* First, we translate the origin to the center of the atom.
+
+         Then we retrieve the prevailing modelview matrix (which
+         includes any rotation, wandering, and user-trackball-rolling
+         of the scene.
+
+         We set the top 3x3 cells of that matrix to be the identity
+         matrix.  This removes all rotation from the matrix, while
+         leaving the translation alone.  This has the effect of
+         leaving the prevailing coordinate system perpendicular to
+         the camera view: were we to draw a square face, it would
+         be in the plane of the screen.
+
+         Now we translate by `size' toward the viewer -- so that the
+         origin is *just in front* of the ball.
+
+         Then we draw the label text, allowing the depth buffer to
+         do its work: that way, labels on atoms will be occluded
+         properly when other atoms move in front of them.
+
+         This technique (of neutralizing rotation relative to the
+         observer, after both rotations and translations have been
+         applied) is known as "billboarding".
+       */
+
+      glTranslatef(a->x, a->y, a->z);               /* get matrix */
+      glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);  /* load rot. identity */
+      m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
+      m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
+      m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
+      glLoadIdentity();                             /* reset modelview */
+      glMultMatrixf (&m[0][0]);                     /* replace with ours */
+
+      glTranslatef (0, 0, (size * 1.1));           /* move toward camera */
+
+      glRasterPos3f (0, 0, 0);                     /* draw text here */
+
+      /* Before drawing the string, shift the origin to center
+         the text over the origin of the sphere. */
+      glBitmap (0, 0, 0, 0,
+                -string_width (mc->xfont1, a->label) / 2,
+                -mc->xfont1->descent,
+                NULL);
+
+      for (j = 0; j < strlen(a->label); j++)
+
+        glCallList (mc->font1_dlist + (int)(a->label[j]));
+
+      glPopMatrix();
+    }
+
+  /* More efficient to always call glEnable() with correct values
+     than to call glPushAttrib()/glPopAttrib(), since reading
+     attributes from GL does a round-trip and  stalls the pipeline.
+   */
+  if (!wire)
+    glEnable (GL_LIGHTING);
+}
+
+
+static void
+pick_new_molecule (ModeInfo *mi, time_t last)
+{
+  molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
+
+  if (mc->nmolecules == 1)
+    {
+      if (last != 0) return;
+      mc->which = 0;
+    }
+  else if (last == 0)
+    {
+      mc->which = random() % mc->nmolecules;
+    }
+  else
+    {
+      int n = mc->which;
+      while (n == mc->which)
+        n = random() % mc->nmolecules;
+      mc->which = n;
+    }
+          
+  if (verbose_p)
+    {
+      char *name = strdup (mc->molecules[mc->which].label);
+      char *s = strpbrk (name, "\r\n");
+      if (s) *s = 0;
+      fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
+      free (name);
+    }
+
+  mc->polygon_count = 0;
+
+  glNewList (mc->molecule_dlist, GL_COMPILE);
+  ensure_bounding_box_visible (mi);
+
+  do_labels = orig_do_labels;
+  do_atoms  = orig_do_atoms;
+  do_bonds  = orig_do_bonds;
+  do_shells = orig_do_shells;
+  MI_IS_WIREFRAME(mi) = orig_wire;
+
+  if (mc->molecule_size > mc->no_label_threshold)
+    do_labels = 0;
+  if (mc->molecule_size > mc->wireframe_threshold)
+    MI_IS_WIREFRAME(mi) = 1;
+
+  if (MI_IS_WIREFRAME(mi))
+    do_bonds = 1, do_shells = 0;
+
+  if (!do_bonds)
+    do_shells = 0;
+
+  if (! (do_bonds || do_atoms || do_labels))
+    {
+      /* Make sure *something* shows up! */
+      MI_IS_WIREFRAME(mi) = 1;
+      do_bonds = 1;
+    }
+
+  build_molecule (mi, False);
+  glEndList();
+
+  if (do_shells)
+    {
+      glNewList (mc->shell_dlist, GL_COMPILE);
+      ensure_bounding_box_visible (mi);
+
+      do_labels = 0;
+      do_atoms  = 1;
+      do_bonds  = 0;
+
+      build_molecule (mi, True);
+
+      glEndList();
+      do_bonds  = orig_do_bonds;
+      do_atoms  = orig_do_atoms;
+      do_labels = orig_do_labels;
+    }
+}
+
+
+ENTRYPOINT void
 draw_molecule (ModeInfo *mi)
 {
-  static time_t last = 0;
   time_t now = time ((time_t *) 0);
+  GLfloat speed = 4.0;  /* speed at which the zoom out/in happens */
 
   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
   Display *dpy = MI_DISPLAY(mi);
@@ -1428,95 +1511,128 @@ draw_molecule (ModeInfo *mi)
   if (!mc->glx_context)
     return;
 
-  if (last + timeout <= now)   /* randomize molecules every -timeout seconds */
+  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
+
+  if (mc->draw_time == 0)
     {
-      if (mc->nmolecules == 1)
-        {
-          if (last != 0) goto SKIP;
-          mc->which = 0;
-        }
-      else if (last == 0)
+      pick_new_molecule (mi, mc->draw_time);
+      mc->draw_time = now;
+    }
+  else if (mc->mode == 0)
+    {
+      if (mc->draw_tick++ > 10)
         {
-          mc->which = random() % mc->nmolecules;
+          time_t now = time((time_t *) 0);
+          if (mc->draw_time == 0) mc->draw_time = now;
+          mc->draw_tick = 0;
+
+          if (!mc->button_down_p &&
+              mc->nmolecules > 1 &&
+              mc->draw_time + timeout <= now)
+            {
+              /* randomize molecules every -timeout seconds */
+              mc->mode = 1;    /* go out */
+              mc->mode_tick = 10 * speed;
+              mc->draw_time = now;
+            }
         }
-      else
+    }
+  else if (mc->mode == 1)   /* out */
+    {
+      if (--mc->mode_tick <= 0)
         {
-          int n = mc->which;
-          while (n == mc->which)
-            n = random() % mc->nmolecules;
-          mc->which = n;
+          mc->mode_tick = 10 * speed;
+          mc->mode = 2;  /* go in */
+          pick_new_molecule (mi, mc->draw_time);
+          mc->draw_time = now;
         }
+    }
+  else if (mc->mode == 2)   /* in */
+    {
+      if (--mc->mode_tick <= 0)
+        mc->mode = 0;  /* normal */
+    }
+  else
+    abort();
 
-      last = now;
-
-
-      glNewList (mc->molecule_dlist, GL_COMPILE);
-      ensure_bounding_box_visible (mi);
-
-      do_labels = orig_do_labels;
-      do_bonds = orig_do_bonds;
-      MI_IS_WIREFRAME(mi) = orig_wire;
+  glPushMatrix ();
+  glScalef(1.1, 1.1, 1.1);
 
-      if (mc->molecule_size > mc->no_label_threshold)
-        do_labels = 0;
-      if (mc->molecule_size > mc->wireframe_threshold)
-        MI_IS_WIREFRAME(mi) = 1;
+  {
+    double x, y, z;
+    get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
+    glTranslatef((x - 0.5) * 9,
+                 (y - 0.5) * 9,
+                 (z - 0.5) * 9);
+
+    gltrackball_rotate (mc->trackball);
+
+    get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
+    glRotatef (x * 360, 1.0, 0.0, 0.0);
+    glRotatef (y * 360, 0.0, 1.0, 0.0);
+    glRotatef (z * 360, 0.0, 0.0, 1.0);
+  }
 
-      if (MI_IS_WIREFRAME(mi))
-        do_bonds = 1;
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
-      build_molecule (mi);
-      glEndList();
+  if (mc->mode != 0)
+    {
+      GLfloat s = (mc->mode == 1
+                   ? mc->mode_tick / (10 * speed)
+                   : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
+      glScalef (s, s, s);
     }
- SKIP:
 
-  glPushMatrix ();
-  glScalef(1.1, 1.1, 1.1);
+  glPushMatrix();
+  glCallList (mc->molecule_dlist);
 
-  {
-    GLfloat x, y, z;
+  if (mc->mode == 0)
+    {
+      molecule *m = &mc->molecules[mc->which];
 
-    if (do_wander)
-      {
-        static int frame = 0;
+      draw_labels (mi);
 
-#       define SINOID(SCALE,SIZE) \
-        ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2)
+      /* This can't go in the display list, or the characters are spaced
+         wrongly when the window is resized. */
+      if (do_titles && m->label && *m->label)
+        {
+          set_atom_color (mi, 0, True, 1);
+          print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
+                           mi->xgwa.width, mi->xgwa.height,
+                           10, mi->xgwa.height - 10,
+                           m->label);
+        }
+    }
+  glPopMatrix();
 
-        x = SINOID(0.031, 9.0);
-        y = SINOID(0.023, 9.0);
-        z = SINOID(0.017, 9.0);
-        frame++;
-        glTranslatef(x, y, z);
-      }
+  if (do_shells)
+    {
+      glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+      glPushMatrix();
+      glCallList (mc->shell_dlist);
+      glPopMatrix();
+      glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
 
-    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);
-        rotate(&mc->roty, &mc->dy, &mc->ddy, mc->d_max);
-        rotate(&mc->rotz, &mc->dz, &mc->ddz, mc->d_max);
-      }
-  }
+      glDepthFunc (GL_EQUAL);
+      glEnable (GL_BLEND);
+      glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+      glPushMatrix();
+      glCallList (mc->shell_dlist);
+      glPopMatrix();
+      glDepthFunc (GL_LESS);
+      glDisable (GL_BLEND);
+    }
 
-  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-  glCallList (mc->molecule_dlist);
   glPopMatrix ();
 
+  mi->polygon_count = mc->polygon_count;
+
   if (mi->fps_p) do_fps (mi);
   glFinish();
 
   glXSwapBuffers(dpy, window);
 }
 
+XSCREENSAVER_MODULE ("Molecule", molecule)
+
 #endif /* USE_GL */