http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.01.tar.gz
[xscreensaver] / hacks / glx / molecule.c
index a65e918ccc5171e6126611f5f081bfded2b93f10..02b8362461a5285bf26fd8661f602fb715da9085 100644 (file)
      - 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 <X11/Intrinsic.h>
 
 #include "xlockmore.h"
 #include "colors.h"
+#include "sphere.h"
+#include "tube.h"
 
 #ifdef USE_GL /* whole file */
 
+#include <stdlib.h>
 #include <ctype.h>
 #include <GL/glu.h>
 
-
 #define SPHERE_SLICES 16  /* how densely to render spheres */
 #define SPHERE_STACKS 10
 
-
 #define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */
 
 #ifdef SMOOTH_TUBE
@@ -186,11 +230,13 @@ static XrmOptionDescRec opts[] = {
   { "-molecule", ".molecule", XrmoptionSepArg, 0 },
   { "-timeout",".timeout",XrmoptionSepArg, 0 },
   { "-spin",   ".spin",   XrmoptionSepArg, 0 },
-  { "+spin",   ".spin",   XrmoptionNoArg, "" },
+  { "+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" },
@@ -219,192 +265,15 @@ ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
 /* shapes */
 
 static void
-unit_tube (Bool wire)
-{
-  int i;
-  int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
-  GLfloat step = M_PI * 2 / faces;
-  GLfloat th;
-  int z = 0;
-
-  /* side walls
-   */
-  glFrontFace(GL_CCW);
-
-# ifdef SMOOTH_TUBE
-  glBegin(wire ? GL_LINES : GL_QUAD_STRIP);
-# else
-  glBegin(wire ? GL_LINES : GL_QUADS);
-# endif
-
-  for (i = 0, th = 0; i <= faces; i++)
-    {
-      GLfloat x = cos (th);
-      GLfloat y = sin (th);
-      glNormal3f(x, 0, y);
-      glVertex3f(x, 0.0, y);
-      glVertex3f(x, 1.0, y);
-      th += step;
-
-# ifndef SMOOTH_TUBE
-      x = cos (th);
-      y = sin (th);
-      glVertex3f(x, 1.0, y);
-      glVertex3f(x, 0.0, y);
-# endif
-    }
-  glEnd();
-
-  /* End caps
-   */
-  for (z = 0; z <= 1; z++)
-    {
-      glFrontFace(z == 0 ? GL_CCW : GL_CW);
-      glNormal3f(0, (z == 0 ? -1 : 1), 0);
-      glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
-      if (! wire) glVertex3f(0, z, 0);
-      for (i = 0, th = 0; i <= faces; i++)
-        {
-          GLfloat x = cos (th);
-          GLfloat y = sin (th);
-          glVertex3f(x, z, y);
-          th += step;
-        }
-      glEnd();
-    }
-}
-
-
-static void
-tube (GLfloat x1, GLfloat y1, GLfloat z1,
-      GLfloat x2, GLfloat y2, GLfloat z2,
-      GLfloat diameter, GLfloat cap_size,
-      Bool wire)
-{
-  GLfloat length, angle, a, b, c;
-
-  if (diameter <= 0) abort();
-
-  a = (x2 - x1);
-  b = (y2 - y1);
-  c = (z2 - z1);
-
-  length = sqrt (a*a + b*b + c*c);
-  angle = acos (a / length);
-
-  glPushMatrix();
-  glTranslatef(x1, y1, z1);
-  glScalef (length, length, length);
-
-  if (c == 0 && b == 0)
-    glRotatef (angle / (M_PI / 180), 0, 1, 0);
-  else
-    glRotatef (angle / (M_PI / 180), 0, -c, b);
-
-  glRotatef (-90, 0, 0, 1);
-  glScalef (diameter/length, 1, diameter/length);
-
-  /* extend the endpoints of the tube by the cap size in both directions */
-  if (cap_size != 0)
-    {
-      GLfloat c = cap_size/length;
-      glTranslatef (0, -c, 0);
-      glScalef (1, 1+c+c, 1);
-    }
-
-  unit_tube (wire);
-  glPopMatrix();
-}
-
-
-/* lifted from glplanet */
-/* Function for determining points on the surface of the sphere */
-static void
-parametric_sphere (float theta, float rho, GLfloat *vector)
-{
-  vector[0] = -sin(theta) * sin(rho);
-  vector[1] = cos(theta) * sin(rho);
-  vector[2] = cos(rho);
-}
-
-/* lifted from glplanet */
-static void
-unit_sphere (Bool wire)
+sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
 {
   int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
   int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
 
-  int i, j;
-  float drho, dtheta;
-  float rho, theta;
-  GLfloat vector[3];
-  GLfloat ds, dt, t, s;
-
-  if (!do_bonds && !scale_down)  /* if balls are bigger, be smoother... */
-    slices *= 2, stacks *= 2;
-
-  if (!wire)
-    glShadeModel(GL_SMOOTH);
-
-  /* Generate a sphere with quadrilaterals.
-   * Quad vertices are determined using a parametric sphere function.
-   * For fun, you could generate practically any parameteric surface and
-   * map an image onto it. 
-   */
-  drho = M_PI / stacks;
-  dtheta = 2.0 * M_PI / slices;
-  ds = 1.0 / slices;
-  dt = 1.0 / stacks;
-
-  glFrontFace(GL_CCW);
-  glBegin( wire ? GL_LINE_LOOP : GL_QUADS );
-
-  t = 0.0;
-  for (i=0; i < stacks; i++) {
-    rho = i * drho;
-    s = 0.0;
-    for (j=0; j < slices; j++) {
-      theta = j * dtheta;
-
-      glTexCoord2f (s,t);
-      parametric_sphere (theta, rho, vector);
-      glNormal3fv (vector);
-      parametric_sphere (theta, rho, vector);
-      glVertex3f (vector[0], vector[1], vector[2]);
-
-      glTexCoord2f (s,t+dt);
-      parametric_sphere (theta, rho+drho, vector);
-      glNormal3fv (vector);
-      parametric_sphere (theta, rho+drho, vector);
-      glVertex3f (vector[0], vector[1], vector[2]);
-
-      glTexCoord2f (s+ds,t+dt);
-      parametric_sphere (theta + dtheta, rho+drho, vector);
-      glNormal3fv (vector);
-      parametric_sphere (theta + dtheta, rho+drho, vector);
-      glVertex3f (vector[0], vector[1], vector[2]);
-
-      glTexCoord2f (s+ds, t);
-      parametric_sphere (theta + dtheta, rho, vector);
-      glNormal3fv (vector);
-      parametric_sphere (theta + dtheta, rho, vector);
-      glVertex3f (vector[0], vector[1], vector[2]);
-
-      s = s + ds;
-    }
-    t = t + dt;
-  }
-  glEnd();
-}
-
-
-static void
-sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
-{
   glPushMatrix ();
   glTranslatef (x, y, z);
   glScalef (diameter, diameter, diameter);
-  unit_sphere (wire);
+  unit_sphere (stacks, slices, wire);
   glPopMatrix ();
 }
 
@@ -727,7 +596,8 @@ print_title_string (ModeInfo *mi, const char *string,
   
   y -= line_height;
 
-  glPushAttrib (GL_LIGHTING | GL_DEPTH_TEST);
+  glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
+                GL_ENABLE_BIT);     /* for various glDisable calls */
   glDisable (GL_LIGHTING);
   glDisable (GL_DEPTH_TEST);
   {
@@ -797,6 +667,21 @@ 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);
 
@@ -816,65 +701,76 @@ build_molecule (ModeInfo *mi)
           }
         else
           {
+            int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
+# ifdef SMOOTH_TUBE
+            int smooth = True;
+# else
+            int smooth = False;
+# endif
             GLfloat thickness = 0.07 * b->strength;
             GLfloat cap_size = 0.03;
             if (thickness > 0.3)
               thickness = 0.3;
+
             tube (from->x, from->y, from->z,
                   to->x,   to->y,   to->z,
-                  thickness, cap_size, wire);
+                  thickness, cap_size,
+                  faces, smooth, wire);
           }
       }
 
-  for (i = 0; i < m->natoms; i++)
-    {
-      molecule_atom *a = &m->atoms[i];
-      int i;
+  if (!wire && do_atoms)
+    for (i = 0; i < m->natoms; i++)
+      {
+        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);
+      }
 
-      if (!wire && do_atoms)
-        {
-          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 (do_labels)
-        {
-          glPushAttrib (GL_LIGHTING | GL_DEPTH_TEST);
-          glDisable (GL_LIGHTING);
-          glDisable (GL_DEPTH_TEST);
+        if (!wire)
+          {
+            glDisable (GL_LIGHTING);
+#if 1
+            glDisable (GL_DEPTH_TEST);
+#endif
+          }
 
-          if (!wire)
-            set_atom_color (mi, a, True);
+        if (!wire)
+          set_atom_color (mi, a, True);
 
-          glRasterPos3f (a->x, a->y, a->z);
+        glRasterPos3f (a->x, a->y, a->z);
 
-          {
-            GLdouble mm[17], pm[17];
-            GLint vp[5];
-            GLdouble wx=-1, wy=-1, wz=-1;
-            glGetDoublev (GL_MODELVIEW_MATRIX, mm);
-            glGetDoublev (GL_PROJECTION_MATRIX, pm);
-            glGetIntegerv (GL_VIEWPORT, vp);
-
-            /* Convert 3D coordinates to window coordinates */
-            gluProject (a->x, a->y, a->z, mm, pm, vp, &wx, &wy, &wz);
-
-            /* Fudge the window coordinates to center the string */
-            wx -= string_width (mc->xfont1, a->label) / 2;
-            wy -= mc->xfont1->descent;
-
-            /* Convert new window coordinates back to 3D coordinates */
-            gluUnProject (wx, wy, wz, mm, pm, vp, &wx, &wy, &wz);
-            glRasterPos3f (wx, wy, wz);
-          }
+        /* 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 (i = 0; i < strlen(a->label); i++)
-            glCallList (mc->font1_dlist + (int)(a->label[i]));
+        for (j = 0; j < strlen(a->label); j++)
+          glCallList (mc->font1_dlist + (int)(a->label[j]));
 
-          glPopAttrib();
-        }
-    }
+        /* 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);
@@ -1139,13 +1035,34 @@ parse_pdb_file (molecule *m, const char *name)
 }
 
 
+typedef struct { char *atom; int count; } atom_and_count;
+
+/* When listing the components of a molecule, the convention is to put the
+   carbon atoms first, the hydrogen atoms second, and the other atom types
+   sorted alphabetically after that (although for some molecules, the usual
+   order is different, like for NH(3), but we don't special-case those.)
+ */
+static int
+cmp_atoms (const void *aa, const void *bb)
+{
+  const atom_and_count *a = (atom_and_count *) aa;
+  const atom_and_count *b = (atom_and_count *) bb;
+  if (!a->atom) return  1;
+  if (!b->atom) return -1;
+  if (!strcmp(a->atom, "C")) return -1;
+  if (!strcmp(b->atom, "C")) return  1;
+  if (!strcmp(a->atom, "H")) return -1;
+  if (!strcmp(b->atom, "H")) return  1;
+  return strcmp (a->atom, b->atom);
+}
+
 static void
 generate_molecule_formula (molecule *m)
 {
   char *buf = (char *) malloc (m->natoms * 10);
   char *s = buf;
   int i;
-  struct { char *atom; int count; } counts[200];
+  atom_and_count counts[200];
   memset (counts, 0, sizeof(counts));
   *s = 0;
   for (i = 0; i < m->natoms; i++)
@@ -1166,6 +1083,10 @@ generate_molecule_formula (molecule *m)
       counts[j].count++;
     }
 
+  i = 0;
+  while (counts[i].atom) i++;
+  qsort (counts, i, sizeof(*counts), cmp_atoms);
+
   i = 0;
   while (counts[i].atom)
     {
@@ -1263,10 +1184,11 @@ reshape_molecule (ModeInfo *mi, int width, int height)
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
 
-  gluPerspective( 30.0, 1/h, 1.0, 100.0 );
+  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);
+
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef(0.0, 0.0, -15.0);