http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.16.tar.gz
[xscreensaver] / hacks / glx / molecule.c
index 75010b8ee0b8c17cd798be8a3b10b12fe13ab1f1..992ff7ca1417f4bdf198ef6fa0fb77e53729d84b 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
 
    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!
  */
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
 #include <X11/Intrinsic.h>
 
 #define PROGCLASS      "Molecule"
@@ -139,6 +94,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"
 };
@@ -159,6 +119,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, }},
@@ -248,15 +209,15 @@ static XrmOptionDescRec opts[] = {
 };
 
 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},
 };
 
 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
@@ -349,7 +310,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;
     }
 
@@ -691,21 +652,6 @@ 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);
 
@@ -739,7 +685,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);
           }
       }
 
@@ -752,50 +698,6 @@ build_molecule (ModeInfo *mi)
         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
-          }
-      }
-
   if (do_bbox)
     draw_bounding_box (mi);
 
@@ -947,6 +849,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",
@@ -1013,7 +921,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;
@@ -1027,7 +935,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);
@@ -1047,7 +955,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)
@@ -1056,6 +964,8 @@ parse_pdb_file (molecule *m, const char *name)
                progname, name);
       do_bonds = 0;
     }
+
+  return 0;
 }
 
 
@@ -1175,41 +1085,137 @@ load_molecules (ModeInfo *mi)
 {
   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
   int wire = MI_IS_WIREFRAME(mi);
+  Bool verbose_p = False;
+  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;
-        }
     }
 }
 
@@ -1355,7 +1361,7 @@ init_molecule (ModeInfo *mi)
                             spinz ? spin_speed : 0,
                             1.0,
                             do_wander ? wander_speed : 0,
-                            True);
+                            (spinx && spiny && spinz));
     mc->trackball = gltrackball_init ();
   }
 
@@ -1374,6 +1380,94 @@ init_molecule (ModeInfo *mi)
 }
 
 
+/* 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.
+ */
+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);
+
+      /* 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);
+}
+
+
 void
 draw_molecule (ModeInfo *mi)
 {
@@ -1450,6 +1544,8 @@ draw_molecule (ModeInfo *mi)
 
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glCallList (mc->molecule_dlist);
+  draw_labels (mi);
+
   glPopMatrix ();
 
   if (mi->fps_p) do_fps (mi);