From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / molecule.c
index 62929647ad7511b34f07e81c983a576b93c41978..57827b2b3d2e6fdbbbf30e374454653218ed5639 100644 (file)
@@ -1,4 +1,4 @@
-/* molecule, Copyright (c) 2001-2006 Jamie Zawinski <jwz@jwz.org>
+/* molecule, Copyright (c) 2001-2016 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:
+   https://en.wikipedia.org/wiki/Protein_Data_Bank_%28file_format%29
+   http://www.wwpdb.org/docs.html
+   http://www.wwpdb.org/documentation/format32/v3.2.html
+   http://www.wwpdb.org/documentation/format32/sect9.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
+   http://www.umass.edu/microbio/rasmol/whereget.htm
+   http://www.wwpdb.org/docs.html
  */
 
 #define DEFAULTS       "*delay:        10000         \n" \
                        "*showFPS:      False         \n" \
                        "*wireframe:    False         \n" \
-                       "*atomFont:   -*-times-bold-r-normal-*-240-*\n" \
-                       "*titleFont:  -*-times-bold-r-normal-*-180-*\n" \
-                       "*noLabelThreshold:    30     \n" \
+       "*atomFont:   -*-helvetica-medium-r-normal-*-*-240-*-*-*-*-*-*\n" \
+       "*titleFont:  -*-helvetica-medium-r-normal-*-*-180-*-*-*-*-*-*\n" \
+                       "*noLabelThreshold:    150    \n" \
                        "*wireframeThreshold:  150    \n" \
+                       "*suppressRotationAnimation: True\n" \
 
 # define refresh_molecule 0
 # define release_molecule 0
@@ -35,7 +42,7 @@
 #include "colors.h"
 #include "sphere.h"
 #include "tube.h"
-#include "glxfonts.h"
+#include "texfont.h"
 #include "rotator.h"
 #include "gltrackball.h"
 
 #define DEF_TITLES      "True"
 #define DEF_ATOMS       "True"
 #define DEF_BONDS       "True"
-#define DEF_SHELLS      "True"
+#define DEF_ESHELLS     "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 SPHERE_SLICES 48  /* how densely to render spheres */
+#define SPHERE_STACKS 24
 
 #define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */
 
@@ -70,9 +77,9 @@
 # define TUBE_FACES  8
 #endif
 
-#define SPHERE_SLICES_2  7
-#define SPHERE_STACKS_2  4
-#define TUBE_FACES_2     3
+#define SPHERE_SLICES_2  14
+#define SPHERE_STACKS_2  8
+#define TUBE_FACES_2     6
 
 
 # ifdef __GNUC__
                     ISO C89 compilers are required to support" when includng
                     the following data file... */
 # endif
-const char * const builtin_pdb_data[] = {
+static const char * const builtin_pdb_data[] = {
 # include "molecules.h"
 };
 
 
+#ifndef HAVE_MOBILE
+# define LOAD_FILES
+#endif
+
+
 typedef struct {
   const char *name;
   GLfloat size, size2;
@@ -98,10 +110,10 @@ typedef struct {
    and their approximate size in angstroms.
  */
 static const atom_data all_atom_data[] = {
-  { "H",    1.17,  0.40, "#FFFFFF", "#B3B3B3", { 0, }},
+  { "H",    1.17,  0.40, "#FFFFFF", "#000000", { 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, }},
+  { "N",    1.55,  0.52, "#A2B5CD", "#EE99FF", { 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, }},
@@ -149,18 +161,20 @@ typedef struct {
 
   int mode;  /* 0 = normal, 1 = out, 2 = in */
   int mode_tick;
+  int next;  /* 0 = random, -1 = back, 1 = forward */
 
   GLuint molecule_dlist;
   GLuint shell_dlist;
 
-  XFontStruct *xfont1, *xfont2;
-  GLuint font1_dlist, font2_dlist;
+  texture_font_data *atom_font, *title_font;
+
   int polygon_count;
 
   time_t draw_time;
   int draw_tick;
 
-  int scale_down;
+  GLfloat overall_scale;
+  int low_rez_p;
 
 } molecule_configuration;
 
@@ -215,7 +229,7 @@ static argtype vars[] = {
   {&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_shells,   "eshells",    "EShells",      DEF_ESHELLS,  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},
@@ -234,8 +248,8 @@ static int
 sphere (molecule_configuration *mc,
         GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
 {
-  int stacks = (mc->scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
-  int slices = (mc->scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
+  int stacks = (mc->low_rez_p ? SPHERE_STACKS_2 : SPHERE_STACKS);
+  int slices = (mc->low_rez_p ? SPHERE_SLICES_2 : SPHERE_SLICES);
 
   glPushMatrix ();
   glTranslatef (x, y, z);
@@ -251,8 +265,8 @@ static void
 load_fonts (ModeInfo *mi)
 {
   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
-  load_font (mi->dpy, "atomFont",  &mc->xfont1, &mc->font1_dlist);
-  load_font (mi->dpy, "titleFont", &mc->xfont2, &mc->font2_dlist);
+  mc->atom_font = load_texture_font (mi->dpy, "atomFont");
+  mc->title_font = load_texture_font (mi->dpy, "titleFont");
 }
 
 
@@ -327,6 +341,16 @@ set_atom_color (ModeInfo *mi, const molecule_atom *a,
   
   gl_color[3] = alpha;
 
+  /* If we're not drawing atoms, and the color is black, use white instead.
+     This is a kludge so that H can have black text over its white ball,
+     but the text still shows up if balls are off.
+   */
+  if (font_p && !do_atoms &&
+      gl_color[0] == 0 && gl_color[1] == 0 && gl_color[2] == 0)
+    {
+      gl_color[0] = gl_color[1] = gl_color[2] = 1;
+    }
+
   if (font_p)
     glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
   else
@@ -452,20 +476,23 @@ draw_bounding_box (ModeInfo *mi)
   glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
   glEnd();
 
-  glPushAttrib (GL_LIGHTING);
   glDisable (GL_LIGHTING);
 
   glColor3f (c2[0], c2[1], c2[2]);
   glBegin(GL_LINES);
-  if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
-  if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
-  if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
+  if (x1 > 0) x1 = 0;
+  if (x2 < 0) x2 = 0;
+  if (y1 > 0) y1 = 0;
+  if (y2 < 0) y2 = 0;
+  if (z1 > 0) z1 = 0;
+  if (z2 < 0) z2 = 0;
   glVertex3f(x1, 0,  0);  glVertex3f(x2, 0,  0); 
   glVertex3f(0 , y1, 0);  glVertex3f(0,  y2, 0); 
   glVertex3f(0,  0,  z1); glVertex3f(0,  0,  z2); 
   glEnd();
 
-  glPopAttrib();
+  if (!wire)
+    glEnable (GL_LIGHTING);
 }
 
 
@@ -494,14 +521,15 @@ ensure_bounding_box_visible (ModeInfo *mi)
 
   mc->molecule_size = size;
 
-  mc->scale_down = 0;
+  mc->low_rez_p = 0;
+  mc->overall_scale = 1;
 
   if (size > max_size)
     {
-      GLfloat scale = max_size / size;
-      glScalef (scale, scale, scale);
+      mc->overall_scale = max_size / size;
+      glScalef (mc->overall_scale, mc->overall_scale, mc->overall_scale);
 
-      mc->scale_down = scale < 0.3;
+      mc->low_rez_p = mc->overall_scale < 0.3;
     }
 
   glTranslatef (-(x1 + w/2),
@@ -563,22 +591,23 @@ build_molecule (ModeInfo *mi, Bool transparent_p)
           }
         else
           {
-            int faces = (mc->scale_down ? TUBE_FACES_2 : TUBE_FACES);
+            int faces = (mc->low_rez_p ? TUBE_FACES_2 : TUBE_FACES);
 # ifdef SMOOTH_TUBE
             int smooth = True;
 # else
             int smooth = False;
 # endif
-            GLfloat thickness = 0.07 * b->strength;
-            GLfloat cap_size = 0.03;
+            Bool cap_p = (!do_atoms || do_shells);
+            GLfloat base = 0.07;
+            GLfloat thickness = base * b->strength;
+            GLfloat cap_size = (cap_p ? base / 2 : 0);
             if (thickness > 0.3)
               thickness = 0.3;
 
-            tube (from->x, from->y, from->z,
-                  to->x,   to->y,   to->z,
-                  thickness, cap_size,
-                  faces, smooth, (!do_atoms || do_shells), wire);
-            polys += faces;
+            polys += tube (from->x, from->y, from->z,
+                           to->x,   to->y,   to->z,
+                           thickness, cap_size,
+                           faces, smooth, cap_p, wire);
           }
       }
 
@@ -749,13 +778,26 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
       else if (!strncmp (s, "ATOM   ", 7))
         {
           int id;
+          const char *end = strchr (s, '\n');
+          int L = end - s;
           char *name = (char *) calloc (1, 4);
           GLfloat x = -999, y = -999, z = -999;
 
           if (1 != sscanf (s+7, " %d ", &id))
             parse_error (filename, line, s);
 
+          /* Use the "atom name" field if that is all that is available. */
           strncpy (name, s+12, 3);
+
+          /* But prefer the "element" field. */
+          if (L > 77 && !isspace(s[77])) {
+            /* fprintf(stderr, "  \"%s\" -> ", name); */
+            name[0] = s[76];
+            name[1] = s[77];
+            name[2] = 0;
+            /* fprintf(stderr, "\"%s\"\n", name); */
+          }
+
           while (isspace(*name)) name++;
           ss = name + strlen(name)-1;
           while (isspace(*ss) && ss > name)
@@ -836,6 +878,7 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
 }
 
 
+#ifdef LOAD_FILES
 static int
 parse_pdb_file (molecule *m, const char *name)
 {
@@ -882,6 +925,7 @@ parse_pdb_file (molecule *m, const char *name)
 
   return 0;
 }
+#endif /* LOAD_FILES */
 
 
 typedef struct { char *atom; int count; } atom_and_count;
@@ -999,16 +1043,17 @@ static void
 load_molecules (ModeInfo *mi)
 {
   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
-  int wire = MI_IS_WIREFRAME(mi);
   int i;
 
   mc->nmolecules = 0;
+# ifdef LOAD_FILES
   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.
       */
+      int wire = MI_IS_WIREFRAME(mi);
       struct stat st;
       int nfiles = 0;
       int list_size = 0;
@@ -1112,6 +1157,7 @@ load_molecules (ModeInfo *mi)
       files = 0;
       mc->nmolecules = molecule_ctr;
     }
+# endif /* LOAD_FILES */
 
   if (mc->nmolecules == 0)     /* do the builtins if no files */
     {
@@ -1154,6 +1200,14 @@ reshape_molecule (ModeInfo *mi, int width, int height)
              0.0, 0.0, 0.0,
              0.0, 1.0, 0.0);
 
+# ifdef HAVE_MOBILE    /* Keep it the same relative size when rotated. */
+  {
+    int o = (int) current_device_rotation();
+    if (o != 0 && o != 180 && o != -180)
+      glScalef (1/h, 1/h, 1/h);
+  }
+# endif
+
   glClear(GL_COLOR_BUFFER_BIT);
 }
 
@@ -1177,10 +1231,9 @@ startup_blurb (ModeInfo *mi)
 {
   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);
+  print_texture_label (mi->dpy, mc->title_font,
+                       mi->xgwa.width, mi->xgwa.height,
+                       0, s);
   glFinish();
   glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
 }
@@ -1190,51 +1243,40 @@ molecule_handle_event (ModeInfo *mi, XEvent *event)
 {
   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
 
-  if (event->xany.type == ButtonPress &&
-      event->xbutton.button == Button1)
-    {
-      mc->button_down_p = True;
-      gltrackball_start (mc->trackball,
-                         event->xbutton.x, event->xbutton.y,
-                         MI_WIDTH (mi), MI_HEIGHT (mi));
-      return True;
-    }
-  else if (event->xany.type == ButtonRelease &&
-           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)
+  if (gltrackball_event_handler (event, mc->trackball,
+                                 MI_WIDTH (mi), MI_HEIGHT (mi),
+                                 &mc->button_down_p))
+    return True;
+  else
     {
-      KeySym keysym;
-      char c = 0;
-      XLookupString (&event->xkey, &c, 1, &keysym, 0);
+      if (event->xany.type == KeyPress)
+        {
+          KeySym keysym;
+          char c = 0;
+          XLookupString (&event->xkey, &c, 1, &keysym, 0);
+          if (c == '<' || c == ',' || c == '-' || c == '_' ||
+              keysym == XK_Left || keysym == XK_Up || keysym == XK_Prior)
+            {
+              mc->next = -1;
+              goto SWITCH;
+            }
+          else if (c == '>' || c == '.' || c == '=' || c == '+' ||
+                   keysym == XK_Right || keysym == XK_Down ||
+                   keysym == XK_Next)
+            {
+              mc->next = 1;
+              goto SWITCH;
+            }
+        }
 
-      if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+      if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
         {
-          GLfloat speed = 4.0;
+        SWITCH:
           mc->mode = 1;
-          mc->mode_tick = 10 * speed;
+          mc->mode_tick = 4;
           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;
-    }
 
   return False;
 }
@@ -1246,14 +1288,7 @@ init_molecule (ModeInfo *mi)
   molecule_configuration *mc;
   int wire;
 
-  if (!mcs) {
-    mcs = (molecule_configuration *)
-      calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
-    if (!mcs) {
-      fprintf(stderr, "%s: out of memory\n", progname);
-      exit(1);
-    }
-  }
+  MI_INIT (mi, mcs, NULL);
 
   mc = &mcs[MI_SCREEN(mi)];
 
@@ -1296,7 +1331,7 @@ init_molecule (ModeInfo *mi)
                             spin_accel,
                             do_wander ? wander_speed : 0,
                             (spinx && spiny && spinz));
-    mc->trackball = gltrackball_init ();
+    mc->trackball = gltrackball_init (True);
   }
 
   orig_do_labels = do_labels;
@@ -1333,14 +1368,11 @@ 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;
+  int i;
 
   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];
@@ -1354,7 +1386,7 @@ draw_labels (ModeInfo *mi)
 
       /* First, we translate the origin to the center of the atom.
 
-         Then we retrieve the prevailing modelview matrix (which
+         Then we retrieve the prevailing modelview matrixwhich
          includes any rotation, wandering, and user-trackball-rolling
          of the scene.
 
@@ -1387,28 +1419,27 @@ draw_labels (ModeInfo *mi)
 
       glTranslatef (0, 0, (size * 1.1));           /* move toward camera */
 
-      glRasterPos3f (0, 0, 0);                     /* draw text here */
+      glRotatef (current_device_rotation(), 0, 0, 1);  /* right side up */
 
-      /* 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]));
+      {
+        XCharStruct e;
+        int w, h;
+        GLfloat s;
+
+        texture_string_metrics (mc->atom_font, a->label, &e, 0, 0);
+        w = e.width;
+        h = e.ascent + e.descent;
+
+        s = 1.0 / h;                   /* Scale to unit */
+        s *= mc->overall_scale;                /* Scale to size of atom */
+        s *= 0.8;                      /* Shrink a bit */
+        glScalef (s, s, 1);
+        glTranslatef (-w/2, -h/2, 0);
+        print_texture_string (mc->atom_font, a->label);
+      }
 
       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);
 }
 
 
@@ -1426,6 +1457,18 @@ pick_new_molecule (ModeInfo *mi, time_t last)
     {
       mc->which = random() % mc->nmolecules;
     }
+  else if (mc->next < 0)
+    {
+      mc->which--;
+      if (mc->which < 0) mc->which = mc->nmolecules-1;
+      mc->next = 0;
+    }
+  else if (mc->next > 0)
+    {
+      mc->which++;
+      if (mc->which >= mc->nmolecules) mc->which = 0;
+      mc->next = 0;
+    }
   else
     {
       int n = mc->which;
@@ -1528,7 +1571,7 @@ draw_molecule (ModeInfo *mi)
             {
               /* randomize molecules every -timeout seconds */
               mc->mode = 1;    /* go out */
-              mc->mode_tick = 10 * speed;
+              mc->mode_tick = 80 / speed;
               mc->draw_time = now;
             }
         }
@@ -1537,10 +1580,9 @@ draw_molecule (ModeInfo *mi)
     {
       if (--mc->mode_tick <= 0)
         {
-          mc->mode_tick = 10 * speed;
+          mc->mode_tick = 80 / speed;
           mc->mode = 2;  /* go in */
           pick_new_molecule (mi, mc->draw_time);
-          mc->draw_time = now;
         }
     }
   else if (mc->mode == 2)   /* in */
@@ -1574,8 +1616,8 @@ draw_molecule (ModeInfo *mi)
   if (mc->mode != 0)
     {
       GLfloat s = (mc->mode == 1
-                   ? mc->mode_tick / (10 * speed)
-                   : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
+                   ? mc->mode_tick / (80 / speed)
+                   : ((80 / speed) - mc->mode_tick + 1) / (80 / speed));
       glScalef (s, s, s);
     }
 
@@ -1593,10 +1635,9 @@ draw_molecule (ModeInfo *mi)
       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);
+          print_texture_label (mi->dpy, mc->title_font,
+                               mi->xgwa.width, mi->xgwa.height,
+                               1, m->label);
         }
     }
   glPopMatrix();