http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.20.tar.gz
[xscreensaver] / hacks / glx / molecule.c
index 56085ac72560a07f6dd04fb8b508c964f69cd000..4b99e09290e58284c42b7add5dcf2d761900d46e 100644 (file)
@@ -1,4 +1,4 @@
-/* molecule, Copyright (c) 2001 Jamie Zawinski <jwz@jwz.org>
+/* molecule, Copyright (c) 2001-2004 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
    http://www.sci.ouc.bc.ca/chem/molecule/molecule.html
  */
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
 #include <X11/Intrinsic.h>
 
 #define PROGCLASS      "Molecule"
 #define DEF_BONDS       "True"
 #define DEF_BBOX        "False"
 #define DEF_MOLECULE    "(default)"
+#define DEF_VERBOSE     "False"
 
 #define DEFAULTS       "*delay:        10000         \n" \
                        "*timeout:    " DEF_TIMEOUT  "\n" \
                        "*showFPS:      False         \n" \
                        "*wireframe:    False         \n" \
+                        "*verbose:    " DEF_VERBOSE  "\n" \
                        "*molecule:   " DEF_MOLECULE "\n" \
                        "*spin:       " DEF_SPIN     "\n" \
                        "*wander:     " DEF_WANDER   "\n" \
@@ -62,6 +68,7 @@
 #include "colors.h"
 #include "sphere.h"
 #include "tube.h"
+#include "glxfonts.h"
 #include "rotator.h"
 #include "gltrackball.h"
 
@@ -73,8 +80,8 @@
 #include <sys/time.h>
 #include <GL/glu.h>
 
-#define SPHERE_SLICES 16  /* how densely to render spheres */
-#define SPHERE_STACKS 10
+#define SPHERE_SLICES 24  /* how densely to render spheres */
+#define SPHERE_STACKS 12
 
 #define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */
 
@@ -90,6 +97,11 @@ static int scale_down;
 #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"
 };
@@ -110,6 +122,7 @@ typedef struct {
 static atom_data all_atom_data[] = {
   { "H",    1.17,  0,  "White",           "Grey70",        { 0, }},
   { "C",    1.75,  0,  "Grey60",          "White",         { 0, }},
+  { "CA",   1.80,  0,  "Blue",            "LightBlue",     { 0, }},
   { "N",    1.55,  0,  "LightSteelBlue3", "SlateBlue1",    { 0, }},
   { "O",    1.40,  0,  "Red",             "LightPink",     { 0, }},
   { "P",    1.28,  0,  "MediumPurple",    "PaleVioletRed", { 0, }},
@@ -156,6 +169,9 @@ typedef struct {
   int nmolecules;
   molecule *molecules;
 
+  int mode;  /* 0 = normal, 1 = out, 2 = in */
+  int mode_tick;
+
   GLuint molecule_dlist;
 
   XFontStruct *xfont1, *xfont2;
@@ -175,6 +191,7 @@ static Bool do_labels;
 static Bool do_atoms;
 static Bool do_bonds;
 static Bool do_bbox;
+static Bool verbose_p;
 
 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
 
@@ -194,20 +211,22 @@ static XrmOptionDescRec opts[] = {
   { "+atoms",  ".atoms",  XrmoptionNoArg, "False" },
   { "-bonds",  ".bonds",  XrmoptionNoArg, "True" },
   { "+bonds",  ".bonds",  XrmoptionNoArg, "False" },
-  { "-bbox",   ".bbox",  XrmoptionNoArg, "True" },
-  { "+bbox",   ".bbox",  XrmoptionNoArg, "False" },
+  { "-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_labels, "labels", "Labels", DEF_LABELS, t_Bool},
+  {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
+  {&do_atoms,  "atoms",  "Atoms",  DEF_ATOMS,  t_Bool},
+  {&do_bonds,  "bonds",  "Bonds",  DEF_BONDS,  t_Bool},
+  {&do_bbox,   "bbox",   "BBox",   DEF_BBOX,   t_Bool},
+  {&verbose_p, "verbose","Verbose",DEF_VERBOSE,t_Bool},
 };
 
 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
@@ -231,55 +250,12 @@ sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
 }
 
 
-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;
-}
-
-
 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);
 }
 
 
@@ -300,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;
     }
 
@@ -541,76 +517,6 @@ 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
  */
@@ -675,7 +581,7 @@ build_molecule (ModeInfo *mi)
             tube (from->x, from->y, from->z,
                   to->x,   to->y,   to->z,
                   thickness, cap_size,
-                  faces, smooth, wire);
+                  faces, smooth, False, wire);
           }
       }
 
@@ -692,9 +598,13 @@ build_molecule (ModeInfo *mi)
     draw_bounding_box (mi);
 
   if (do_titles && m->label && *m->label)
-    print_title_string (mi, m->label,
-                        10, mi->xgwa.height - 10,
-                        mc->xfont2);
+    {
+      set_atom_color (mi, 0, True);
+      print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
+                       mi->xgwa.width, mi->xgwa.height,
+                       10, mi->xgwa.height - 10,
+                       m->label);
+    }
 }
 
 
@@ -839,6 +749,12 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
           ss = name + strlen(name)-1;
           while (isspace(*ss) && ss > name)
             *ss-- = 0;
+         ss = name + 1;
+         while(*ss)
+          {
+           *ss = tolower(*ss);
+            ss++;
+          }
           sscanf (s + 32, " %f %f %f ", &x, &y, &z);
 /*
           fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
@@ -905,7 +821,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;
@@ -919,7 +835,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);
@@ -939,7 +855,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)
@@ -948,6 +864,8 @@ parse_pdb_file (molecule *m, const char *name)
                progname, name);
       do_bonds = 0;
     }
+
+  return 0;
 }
 
 
@@ -1012,7 +930,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++;
     }
@@ -1067,41 +985,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;
-        }
     }
 }
 
@@ -1153,10 +1166,10 @@ 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);
+  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));
 }
@@ -1167,7 +1180,7 @@ molecule_handle_event (ModeInfo *mi, XEvent *event)
   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
 
   if (event->xany.type == ButtonPress &&
-      event->xbutton.button & Button1)
+      event->xbutton.button == Button1)
     {
       mc->button_down_p = True;
       gltrackball_start (mc->trackball,
@@ -1176,11 +1189,33 @@ molecule_handle_event (ModeInfo *mi, XEvent *event)
       return True;
     }
   else if (event->xany.type == ButtonRelease &&
-           event->xbutton.button & Button1)
+           event->xbutton.button == Button1)
     {
       mc->button_down_p = False;
       return True;
     }
+  else if (event->xany.type == ButtonPress &&
+           (event->xbutton.button == Button4 ||
+            event->xbutton.button == Button5))
+    {
+      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)
     {
@@ -1223,8 +1258,9 @@ init_molecule (ModeInfo *mi)
 
   {
     Bool spinx=False, spiny=False, spinz=False;
-    double spin_speed   = 2.0;
-    double wander_speed = 0.03;
+    double spin_speed   = 0.5;
+    double spin_accel   = 0.3;
+    double wander_speed = 0.01;
 
     char *s = do_spin;
     while (*s)
@@ -1245,7 +1281,7 @@ init_molecule (ModeInfo *mi)
     mc->rot = make_rotator (spinx ? spin_speed : 0,
                             spiny ? spin_speed : 0,
                             spinz ? spin_speed : 0,
-                            1.0,
+                            spin_accel,
                             do_wander ? wander_speed : 0,
                             (spinx && spiny && spinz));
     mc->trackball = gltrackball_init ();
@@ -1260,6 +1296,7 @@ init_molecule (ModeInfo *mi)
                                                "NoLabelThreshold");
   mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
                                                 "WireframeThreshold");
+  mc->mode = 0;
 
   if (wire)
     do_bonds = 1;
@@ -1354,11 +1391,63 @@ draw_labels (ModeInfo *mi)
 }
 
 
+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);
+    }
+
+  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;
+
+  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;
+
+  build_molecule (mi);
+  glEndList();
+}
+
+
 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);
@@ -1367,48 +1456,48 @@ draw_molecule (ModeInfo *mi)
   if (!mc->glx_context)
     return;
 
-  if (last + timeout <= now && /* randomize molecules every -timeout seconds */
-      !mc->button_down_p)
+  if (last == 0)
     {
-      if (mc->nmolecules == 1)
-        {
-          if (last != 0) goto SKIP;
-          mc->which = 0;
-        }
-      else if (last == 0)
+      pick_new_molecule (mi, last);
+      last = now;
+    }
+  else if (mc->mode == 0)
+    {
+      static int tick = 0;
+      if (tick++ > 10)
         {
-          mc->which = random() % mc->nmolecules;
+          time_t now = time((time_t *) 0);
+          if (last == 0) last = now;
+          tick = 0;
+
+          if (!mc->button_down_p &&
+              mc->nmolecules > 1 &&
+              last + timeout <= now)
+            {
+              /* randomize molecules every -timeout seconds */
+              mc->mode = 1;    /* go out */
+              mc->mode_tick = 10 * speed;
+              last = 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, last);
+          last = now;
         }
-
-      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;
-
-      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;
-
-      build_molecule (mi);
-      glEndList();
     }
- SKIP:
+  else if (mc->mode == 2)   /* in */
+    {
+      if (--mc->mode_tick <= 0)
+        mc->mode = 0;  /* normal */
+    }
+  else
+    abort();
 
   glPushMatrix ();
   glScalef(1.1, 1.1, 1.1);
@@ -1429,8 +1518,19 @@ draw_molecule (ModeInfo *mi)
   }
 
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  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);
+    }
+
   glCallList (mc->molecule_dlist);
-  draw_labels (mi);
+
+  if (mc->mode == 0)
+    draw_labels (mi);
 
   glPopMatrix ();