http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.20.tar.gz
[xscreensaver] / hacks / glx / molecule.c
1 /* molecule, Copyright (c) 2001-2004 Jamie Zawinski <jwz@jwz.org>
2  * Draws molecules, based on coordinates from PDB (Protein Data Base) files.
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13
14 /* Documentation on the PDB file format:
15    http://www.rcsb.org/pdb/docs/format/pdbguide2.2/guide2.2_frame.html
16
17    Good source of PDB files:
18    http://www.sci.ouc.bc.ca/chem/molecule/molecule.html
19  */
20
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <unistd.h>
24 #include <dirent.h>
25 #include <X11/Intrinsic.h>
26
27 #define PROGCLASS       "Molecule"
28 #define HACK_INIT       init_molecule
29 #define HACK_DRAW       draw_molecule
30 #define HACK_RESHAPE    reshape_molecule
31 #define HACK_HANDLE_EVENT molecule_handle_event
32 #define EVENT_MASK      PointerMotionMask
33 #define molecule_opts   xlockmore_opts
34
35 #define DEF_TIMEOUT     "20"
36 #define DEF_SPIN        "XYZ"
37 #define DEF_WANDER      "False"
38 #define DEF_LABELS      "True"
39 #define DEF_TITLES      "True"
40 #define DEF_ATOMS       "True"
41 #define DEF_BONDS       "True"
42 #define DEF_BBOX        "False"
43 #define DEF_MOLECULE    "(default)"
44 #define DEF_VERBOSE     "False"
45
46 #define DEFAULTS        "*delay:        10000         \n" \
47                         "*timeout:    " DEF_TIMEOUT  "\n" \
48                         "*showFPS:      False         \n" \
49                         "*wireframe:    False         \n" \
50                         "*verbose:    " DEF_VERBOSE  "\n" \
51                         "*molecule:   " DEF_MOLECULE "\n" \
52                         "*spin:       " DEF_SPIN     "\n" \
53                         "*wander:     " DEF_WANDER   "\n" \
54                         "*labels:     " DEF_LABELS   "\n" \
55                         "*atoms:      " DEF_ATOMS    "\n" \
56                         "*bonds:      " DEF_BONDS    "\n" \
57                         "*bbox:       " DEF_BBOX     "\n" \
58                         "*atomFont:   -*-times-bold-r-normal-*-240-*\n" \
59                         "*titleFont:  -*-times-bold-r-normal-*-180-*\n" \
60                         "*noLabelThreshold:    30     \n" \
61                         "*wireframeThreshold:  150    \n" \
62
63
64 #undef countof
65 #define countof(x) (sizeof((x))/sizeof((*x)))
66
67 #include "xlockmore.h"
68 #include "colors.h"
69 #include "sphere.h"
70 #include "tube.h"
71 #include "glxfonts.h"
72 #include "rotator.h"
73 #include "gltrackball.h"
74
75 #ifdef USE_GL /* whole file */
76
77 #include <stdlib.h>
78 #include <ctype.h>
79 #include <time.h>
80 #include <sys/time.h>
81 #include <GL/glu.h>
82
83 #define SPHERE_SLICES 24  /* how densely to render spheres */
84 #define SPHERE_STACKS 12
85
86 #define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */
87
88 #ifdef SMOOTH_TUBE
89 # define TUBE_FACES  12   /* how densely to render tubes */
90 #else
91 # define TUBE_FACES  8
92 #endif
93
94 static int scale_down;
95 #define SPHERE_SLICES_2  7
96 #define SPHERE_STACKS_2  4
97 #define TUBE_FACES_2     3
98
99
100 # ifdef __GNUC__
101   __extension__  /* don't warn about "string length is greater than the length
102                     ISO C89 compilers are required to support" when includng
103                     the following data file... */
104 # endif
105 const char * const builtin_pdb_data[] = {
106 # include "molecules.h"
107 };
108
109
110 typedef struct {
111   const char *name;
112   GLfloat size, size2;
113   const char *color;
114   const char *text_color;
115   GLfloat gl_color[8];
116 } atom_data;
117
118
119 /* These are the traditional colors used to render these atoms,
120    and their approximate size in angstroms.
121  */
122 static atom_data all_atom_data[] = {
123   { "H",    1.17,  0,  "White",           "Grey70",        { 0, }},
124   { "C",    1.75,  0,  "Grey60",          "White",         { 0, }},
125   { "CA",   1.80,  0,  "Blue",            "LightBlue",     { 0, }},
126   { "N",    1.55,  0,  "LightSteelBlue3", "SlateBlue1",    { 0, }},
127   { "O",    1.40,  0,  "Red",             "LightPink",     { 0, }},
128   { "P",    1.28,  0,  "MediumPurple",    "PaleVioletRed", { 0, }},
129   { "S",    1.80,  0,  "Yellow4",         "Yellow1",       { 0, }},
130   { "bond", 0,     0,  "Grey70",          "Yellow1",       { 0, }},
131   { "*",    1.40,  0,  "Green4",          "LightGreen",    { 0, }}
132 };
133
134
135 typedef struct {
136   int id;               /* sequence number in the PDB file */
137   const char *label;    /* The atom name */
138   GLfloat x, y, z;      /* position in 3-space (angstroms) */
139   atom_data *data;      /* computed: which style of atom this is */
140 } molecule_atom;
141
142 typedef struct {
143   int from, to;         /* atom sequence numbers */
144   int strength;         /* how many bonds are between these two atoms */
145 } molecule_bond;
146
147
148 typedef struct {
149   const char *label;            /* description of this compound */
150   int natoms, atoms_size;
151   int nbonds, bonds_size;
152   molecule_atom *atoms;
153   molecule_bond *bonds;
154 } molecule;
155
156
157 typedef struct {
158   GLXContext *glx_context;
159   rotator *rot;
160   trackball_state *trackball;
161   Bool button_down_p;
162
163   GLfloat molecule_size;           /* max dimension of molecule bounding box */
164
165   GLfloat no_label_threshold;      /* Things happen when molecules are huge */
166   GLfloat wireframe_threshold;
167
168   int which;                       /* which of the molecules is being shown */
169   int nmolecules;
170   molecule *molecules;
171
172   int mode;  /* 0 = normal, 1 = out, 2 = in */
173   int mode_tick;
174
175   GLuint molecule_dlist;
176
177   XFontStruct *xfont1, *xfont2;
178   GLuint font1_dlist, font2_dlist;
179
180 } molecule_configuration;
181
182
183 static molecule_configuration *mcs = NULL;
184
185 static int timeout;
186 static char *molecule_str;
187 static char *do_spin;
188 static Bool do_wander;
189 static Bool do_titles;
190 static Bool do_labels;
191 static Bool do_atoms;
192 static Bool do_bonds;
193 static Bool do_bbox;
194 static Bool verbose_p;
195
196 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
197
198
199 static XrmOptionDescRec opts[] = {
200   { "-molecule", ".molecule", XrmoptionSepArg, 0 },
201   { "-timeout",".timeout",XrmoptionSepArg, 0 },
202   { "-spin",   ".spin",   XrmoptionSepArg, 0 },
203   { "+spin",   ".spin",   XrmoptionNoArg, "" },
204   { "-wander", ".wander", XrmoptionNoArg, "True" },
205   { "+wander", ".wander", XrmoptionNoArg, "False" },
206   { "-labels", ".labels", XrmoptionNoArg, "True" },
207   { "+labels", ".labels", XrmoptionNoArg, "False" },
208   { "-titles", ".titles", XrmoptionNoArg, "True" },
209   { "+titles", ".titles", XrmoptionNoArg, "False" },
210   { "-atoms",  ".atoms",  XrmoptionNoArg, "True" },
211   { "+atoms",  ".atoms",  XrmoptionNoArg, "False" },
212   { "-bonds",  ".bonds",  XrmoptionNoArg, "True" },
213   { "+bonds",  ".bonds",  XrmoptionNoArg, "False" },
214   { "-bbox",   ".bbox",   XrmoptionNoArg, "True" },
215   { "+bbox",   ".bbox",   XrmoptionNoArg, "False" },
216   { "-verbose",".verbose",XrmoptionNoArg, "True" },
217 };
218
219 static argtype vars[] = {
220   {&molecule_str, "molecule",   "Molecule", DEF_MOLECULE,t_String},
221   {&timeout,   "timeout","Seconds",DEF_TIMEOUT,t_Int},
222   {&do_spin,   "spin",   "Spin",   DEF_SPIN,   t_String},
223   {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
224   {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
225   {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
226   {&do_atoms,  "atoms",  "Atoms",  DEF_ATOMS,  t_Bool},
227   {&do_bonds,  "bonds",  "Bonds",  DEF_BONDS,  t_Bool},
228   {&do_bbox,   "bbox",   "BBox",   DEF_BBOX,   t_Bool},
229   {&verbose_p, "verbose","Verbose",DEF_VERBOSE,t_Bool},
230 };
231
232 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
233
234
235
236 \f
237 /* shapes */
238
239 static void
240 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
241 {
242   int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
243   int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
244
245   glPushMatrix ();
246   glTranslatef (x, y, z);
247   glScalef (diameter, diameter, diameter);
248   unit_sphere (stacks, slices, wire);
249   glPopMatrix ();
250 }
251
252
253 static void
254 load_fonts (ModeInfo *mi)
255 {
256   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
257   load_font (mi->dpy, "atomFont",  &mc->xfont1, &mc->font1_dlist);
258   load_font (mi->dpy, "titleFont", &mc->xfont2, &mc->font2_dlist);
259 }
260
261
262 static atom_data *
263 get_atom_data (const char *atom_name)
264 {
265   int i;
266   atom_data *d = 0;
267   char *n = strdup (atom_name);
268   char *n2 = n;
269   int L;
270
271   while (!isalpha(*n)) n++;
272   L = strlen(n);
273   while (L > 0 && !isalpha(n[L-1]))
274     n[--L] = 0;
275
276   for (i = 0; i < countof(all_atom_data); i++)
277     {
278       d = &all_atom_data[i];
279       if (!strcasecmp (n, all_atom_data[i].name))
280         break;
281     }
282
283   free (n2);
284   return d;
285 }
286
287
288 static void
289 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
290 {
291   atom_data *d;
292   GLfloat *gl_color;
293
294   if (a)
295     d = a->data;
296   else
297     {
298       static atom_data *def_data = 0;
299       if (!def_data) def_data = get_atom_data ("bond");
300       d = def_data;
301     }
302
303   gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
304
305   if (gl_color[3] == 0)
306     {
307       const char *string = !font_p ? d->color : d->text_color;
308       XColor xcolor;
309       if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
310         {
311           fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
312                    (a ? a->label : d->name), string);
313           exit (1);
314         }
315
316       gl_color[0] = xcolor.red   / 65536.0;
317       gl_color[1] = xcolor.green / 65536.0;
318       gl_color[2] = xcolor.blue  / 65536.0;
319       gl_color[3] = 1.0;
320     }
321   
322   if (font_p)
323     glColor3f (gl_color[0], gl_color[1], gl_color[2]);
324   else
325     glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
326 }
327
328
329 static GLfloat
330 atom_size (molecule_atom *a)
331 {
332   if (do_bonds)
333     {
334       if (a->data->size2 == 0)
335         {
336           /* let the molecules have the same relative sizes, but scale
337              them to a smaller range, so that the bond-tubes are
338              actually visible...
339            */
340           GLfloat bot = 0.4;
341           GLfloat top = 0.6;
342           GLfloat min = 1.17;
343           GLfloat max = 1.80;
344           GLfloat ratio = (a->data->size - min) / (max - min);
345           a->data->size2 = bot + (ratio * (top - bot));
346         }
347       return a->data->size2;
348     }
349   else
350     return a->data->size;
351 }
352
353
354 static molecule_atom *
355 get_atom (molecule_atom *atoms, int natoms, int id)
356 {
357   int i;
358
359   /* quick short-circuit */
360   if (id < natoms)
361     {
362       if (atoms[id].id == id)
363         return &atoms[id];
364       if (id > 0 && atoms[id-1].id == id)
365         return &atoms[id-1];
366       if (id < natoms-1 && atoms[id+1].id == id)
367         return &atoms[id+1];
368     }
369
370   for (i = 0; i < natoms; i++)
371     if (id == atoms[i].id)
372       return &atoms[i];
373
374   fprintf (stderr, "%s: no atom %d\n", progname, id);
375   abort();
376 }
377
378
379 static void
380 molecule_bounding_box (ModeInfo *mi,
381                        GLfloat *x1, GLfloat *y1, GLfloat *z1,
382                        GLfloat *x2, GLfloat *y2, GLfloat *z2)
383 {
384   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
385   molecule *m = &mc->molecules[mc->which];
386   int i;
387
388   if (m->natoms == 0)
389     {
390       *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
391     }
392   else
393     {
394       *x1 = *x2 = m->atoms[0].x;
395       *y1 = *y2 = m->atoms[0].y;
396       *z1 = *z2 = m->atoms[0].z;
397     }
398
399   for (i = 1; i < m->natoms; i++)
400     {
401       if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
402       if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
403       if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
404
405       if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
406       if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
407       if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
408     }
409
410   *x1 -= 1;
411   *y1 -= 1;
412   *z1 -= 1;
413   *x2 += 1;
414   *y2 += 1;
415   *z2 += 1;
416 }
417
418
419 static void
420 draw_bounding_box (ModeInfo *mi)
421 {
422   static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
423   static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
424   int wire = MI_IS_WIREFRAME(mi);
425   GLfloat x1, y1, z1, x2, y2, z2;
426   molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
427
428   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
429   glFrontFace(GL_CCW);
430
431   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
432   glNormal3f(0, 1, 0);
433   glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
434   glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
435   glEnd();
436   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
437   glNormal3f(0, -1, 0);
438   glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
439   glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
440   glEnd();
441   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
442   glNormal3f(0, 0, 1);
443   glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
444   glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
445   glEnd();
446   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
447   glNormal3f(0, 0, -1);
448   glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
449   glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
450   glEnd();
451   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
452   glNormal3f(1, 0, 0);
453   glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
454   glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
455   glEnd();
456   glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
457   glNormal3f(-1, 0, 0);
458   glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
459   glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
460   glEnd();
461
462   glPushAttrib (GL_LIGHTING);
463   glDisable (GL_LIGHTING);
464
465   glColor3f (c2[0], c2[1], c2[2]);
466   glBegin(GL_LINES);
467   if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
468   if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
469   if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
470   glVertex3f(x1, 0,  0);  glVertex3f(x2, 0,  0); 
471   glVertex3f(0 , y1, 0);  glVertex3f(0,  y2, 0); 
472   glVertex3f(0,  0,  z1); glVertex3f(0,  0,  z2); 
473   glEnd();
474
475   glPopAttrib();
476 }
477
478
479 /* Since PDB files don't always have the molecule centered around the
480    origin, and since some molecules are pretty large, scale and/or
481    translate so that the whole molecule is visible in the window.
482  */
483 static void
484 ensure_bounding_box_visible (ModeInfo *mi)
485 {
486   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
487
488   GLfloat x1, y1, z1, x2, y2, z2;
489   GLfloat w, h, d;
490   GLfloat size;
491   GLfloat max_size = 10;  /* don't bother scaling down if the molecule
492                              is already smaller than this */
493
494   molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
495   w = x2-x1;
496   h = y2-y1;
497   d = z2-z1;
498
499   size = (w > h ? w : h);
500   size = (size > d ? size : d);
501
502   mc->molecule_size = size;
503
504   scale_down = 0;
505
506   if (size > max_size)
507     {
508       GLfloat scale = max_size / size;
509       glScalef (scale, scale, scale);
510
511       scale_down = scale < 0.3;
512     }
513
514   glTranslatef (-(x1 + w/2),
515                 -(y1 + h/2),
516                 -(z1 + d/2));
517 }
518
519
520
521 /* Constructs the GL shapes of the current molecule
522  */
523 static void
524 build_molecule (ModeInfo *mi)
525 {
526   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
527   int wire = MI_IS_WIREFRAME(mi);
528   int i;
529
530   molecule *m = &mc->molecules[mc->which];
531
532   if (wire)
533     {
534       glDisable(GL_CULL_FACE);
535       glDisable(GL_LIGHTING);
536       glDisable(GL_LIGHT0);
537       glDisable(GL_DEPTH_TEST);
538       glDisable(GL_NORMALIZE);
539       glDisable(GL_CULL_FACE);
540     }
541   else
542     {
543       glEnable(GL_CULL_FACE);
544       glEnable(GL_LIGHTING);
545       glEnable(GL_LIGHT0);
546       glEnable(GL_DEPTH_TEST);
547       glEnable(GL_NORMALIZE);
548       glEnable(GL_CULL_FACE);
549     }
550
551   if (!wire)
552     set_atom_color (mi, 0, False);
553
554   if (do_bonds)
555     for (i = 0; i < m->nbonds; i++)
556       {
557         molecule_bond *b = &m->bonds[i];
558         molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
559         molecule_atom *to   = get_atom (m->atoms, m->natoms, b->to);
560
561         if (wire)
562           {
563             glBegin(GL_LINES);
564             glVertex3f(from->x, from->y, from->z);
565             glVertex3f(to->x,   to->y,   to->z);
566             glEnd();
567           }
568         else
569           {
570             int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
571 # ifdef SMOOTH_TUBE
572             int smooth = True;
573 # else
574             int smooth = False;
575 # endif
576             GLfloat thickness = 0.07 * b->strength;
577             GLfloat cap_size = 0.03;
578             if (thickness > 0.3)
579               thickness = 0.3;
580
581             tube (from->x, from->y, from->z,
582                   to->x,   to->y,   to->z,
583                   thickness, cap_size,
584                   faces, smooth, False, wire);
585           }
586       }
587
588   if (!wire && do_atoms)
589     for (i = 0; i < m->natoms; i++)
590       {
591         molecule_atom *a = &m->atoms[i];
592         GLfloat size = atom_size (a);
593         set_atom_color (mi, a, False);
594         sphere (a->x, a->y, a->z, size, wire);
595       }
596
597   if (do_bbox)
598     draw_bounding_box (mi);
599
600   if (do_titles && m->label && *m->label)
601     {
602       set_atom_color (mi, 0, True);
603       print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
604                        mi->xgwa.width, mi->xgwa.height,
605                        10, mi->xgwa.height - 10,
606                        m->label);
607     }
608 }
609
610
611 \f
612 /* loading */
613
614 static void
615 push_atom (molecule *m,
616            int id, const char *label,
617            GLfloat x, GLfloat y, GLfloat z)
618 {
619   m->natoms++;
620   if (m->atoms_size < m->natoms)
621     {
622       m->atoms_size += 20;
623       m->atoms = (molecule_atom *) realloc (m->atoms,
624                                             m->atoms_size * sizeof(*m->atoms));
625     }
626   m->atoms[m->natoms-1].id = id;
627   m->atoms[m->natoms-1].label = label;
628   m->atoms[m->natoms-1].x = x;
629   m->atoms[m->natoms-1].y = y;
630   m->atoms[m->natoms-1].z = z;
631   m->atoms[m->natoms-1].data = get_atom_data (label);
632 }
633
634
635 static void
636 push_bond (molecule *m, int from, int to)
637 {
638   int i;
639
640   for (i = 0; i < m->nbonds; i++)
641     if ((m->bonds[i].from == from && m->bonds[i].to   == to) ||
642         (m->bonds[i].to   == from && m->bonds[i].from == to))
643       {
644         m->bonds[i].strength++;
645         return;
646       }
647
648   m->nbonds++;
649   if (m->bonds_size < m->nbonds)
650     {
651       m->bonds_size += 20;
652       m->bonds = (molecule_bond *) realloc (m->bonds,
653                                             m->bonds_size * sizeof(*m->bonds));
654     }
655   m->bonds[m->nbonds-1].from = from;
656   m->bonds[m->nbonds-1].to = to;
657   m->bonds[m->nbonds-1].strength = 1;
658 }
659
660
661
662 /* This function is crap.
663  */
664 static void
665 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
666 {
667   const char *s = data;
668   char *ss;
669   while (*s)
670     {
671       if ((!m->label || !*m->label) &&
672           (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
673         {
674           char *name = calloc (1, 100);
675           char *n2 = name;
676           int L = strlen(s);
677           if (L > 99) L = 99;
678
679           strncpy (n2, s, L);
680           n2 += 7;
681           while (isspace(*n2)) n2++;
682
683           ss = strchr (n2, '\n');
684           if (ss) *ss = 0;
685           ss = strchr (n2, '\r');
686           if (ss) *ss = 0;
687
688           ss = n2+strlen(n2)-1;
689           while (isspace(*ss) && ss > n2)
690             *ss-- = 0;
691
692           if (strlen (n2) > 4 &&
693               !strcmp (n2 + strlen(n2) - 4, ".pdb"))
694             n2[strlen(n2)-4] = 0;
695
696           if (m->label) free ((char *) m->label);
697           m->label = strdup (n2);
698           free (name);
699         }
700       else if (!strncmp (s, "TITLE ", 6) ||
701                !strncmp (s, "HEADER", 6) ||
702                !strncmp (s, "COMPND", 6) ||
703                !strncmp (s, "AUTHOR", 6) ||
704                !strncmp (s, "REVDAT", 6) ||
705                !strncmp (s, "SOURCE", 6) ||
706                !strncmp (s, "EXPDTA", 6) ||
707                !strncmp (s, "JRNL  ", 6) ||
708                !strncmp (s, "REMARK", 6) ||
709                !strncmp (s, "SEQRES", 6) ||
710                !strncmp (s, "HET   ", 6) ||
711                !strncmp (s, "FORMUL", 6) ||
712                !strncmp (s, "CRYST1", 6) ||
713                !strncmp (s, "ORIGX1", 6) ||
714                !strncmp (s, "ORIGX2", 6) ||
715                !strncmp (s, "ORIGX3", 6) ||
716                !strncmp (s, "SCALE1", 6) ||
717                !strncmp (s, "SCALE2", 6) ||
718                !strncmp (s, "SCALE3", 6) ||
719                !strncmp (s, "MASTER", 6) ||
720                !strncmp (s, "KEYWDS", 6) ||
721                !strncmp (s, "DBREF ", 6) ||
722                !strncmp (s, "HETNAM", 6) ||
723                !strncmp (s, "HETSYN", 6) ||
724                !strncmp (s, "HELIX ", 6) ||
725                !strncmp (s, "LINK  ", 6) ||
726                !strncmp (s, "MTRIX1", 6) ||
727                !strncmp (s, "MTRIX2", 6) ||
728                !strncmp (s, "MTRIX3", 6) ||
729                !strncmp (s, "SHEET ", 6) ||
730                !strncmp (s, "CISPEP", 6) ||
731                !strncmp (s, "GENERATED BY", 12) ||
732                !strncmp (s, "TER ", 4) ||
733                !strncmp (s, "END ", 4) ||
734                !strncmp (s, "TER\n", 4) ||
735                !strncmp (s, "END\n", 4) ||
736                !strncmp (s, "\n", 1))
737         /* ignored. */
738         ;
739       else if (!strncmp (s, "ATOM   ", 7))
740         {
741           int id;
742           char *name = (char *) calloc (1, 4);
743           GLfloat x = -999, y = -999, z = -999;
744
745           sscanf (s+7, " %d ", &id);
746
747           strncpy (name, s+12, 3);
748           while (isspace(*name)) name++;
749           ss = name + strlen(name)-1;
750           while (isspace(*ss) && ss > name)
751             *ss-- = 0;
752           ss = name + 1;
753           while(*ss)
754           {
755             *ss = tolower(*ss);
756             ss++;
757           }
758           sscanf (s + 32, " %f %f %f ", &x, &y, &z);
759 /*
760           fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
761                    progname, filename, line,
762                    id, name, x, y, z);
763 */
764           push_atom (m, id, name, x, y, z);
765         }
766       else if (!strncmp (s, "HETATM ", 7))
767         {
768           int id;
769           char *name = (char *) calloc (1, 4);
770           GLfloat x = -999, y = -999, z = -999;
771
772           sscanf (s+7, " %d ", &id);
773
774           strncpy (name, s+12, 3);
775           while (isspace(*name)) name++;
776           ss = name + strlen(name)-1;
777           while (isspace(*ss) && ss > name)
778             *ss-- = 0;
779           sscanf (s + 30, " %f %f %f ", &x, &y, &z);
780 /*
781           fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
782                    progname, filename, line,
783                    id, name, x, y, z);
784 */
785           push_atom (m, id, name, x, y, z);
786         }
787       else if (!strncmp (s, "CONECT ", 7))
788         {
789           int atoms[11];
790           int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
791                           &atoms[0], &atoms[1], &atoms[2], &atoms[3], 
792                           &atoms[4], &atoms[5], &atoms[6], &atoms[7], 
793                           &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
794           int j;
795           for (j = 1; j < i; j++)
796             if (atoms[j] > 0)
797               {
798 /*
799                 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
800                          progname, filename, line, atoms[0], atoms[j]);
801 */
802                 push_bond (m, atoms[0], atoms[j]);
803               }
804         }
805       else
806         {
807           char *s1 = strdup (s);
808           for (ss = s1; *ss && *ss != '\n'; ss++)
809             ;
810           *ss = 0;
811           fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
812                    progname, filename, line, s1);
813         }
814
815       while (*s && *s != '\n')
816         s++;
817       if (*s == '\n')
818         s++;
819       line++;
820     }
821 }
822
823
824 static int
825 parse_pdb_file (molecule *m, const char *name)
826 {
827   FILE *in;
828   int buf_size = 40960;
829   char *buf;
830   int line = 1;
831
832   in = fopen(name, "r");
833   if (!in)
834     {
835       char *buf = (char *) malloc(1024 + strlen(name));
836       sprintf(buf, "%s: error reading \"%s\"", progname, name);
837       perror(buf);
838       return -1;
839     }
840
841   buf = (char *) malloc (buf_size);
842
843   while (fgets (buf, buf_size-1, in))
844     {
845       char *s;
846       for (s = buf; *s; s++)
847         if (*s == '\r') *s = '\n';
848       parse_pdb_data (m, buf, name, line++);
849     }
850
851   free (buf);
852   fclose (in);
853
854   if (!m->natoms)
855     {
856       fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
857                progname, name);
858       return -1;
859     }
860
861   if (!m->nbonds && do_bonds)
862     {
863       fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
864                progname, name);
865       do_bonds = 0;
866     }
867
868   return 0;
869 }
870
871
872 typedef struct { char *atom; int count; } atom_and_count;
873
874 /* When listing the components of a molecule, the convention is to put the
875    carbon atoms first, the hydrogen atoms second, and the other atom types
876    sorted alphabetically after that (although for some molecules, the usual
877    order is different: we special-case a few of those.)
878  */
879 static int
880 cmp_atoms (const void *aa, const void *bb)
881 {
882   const atom_and_count *a = (atom_and_count *) aa;
883   const atom_and_count *b = (atom_and_count *) bb;
884   if (!a->atom) return  1;
885   if (!b->atom) return -1;
886   if (!strcmp(a->atom, "C")) return -1;
887   if (!strcmp(b->atom, "C")) return  1;
888   if (!strcmp(a->atom, "H")) return -1;
889   if (!strcmp(b->atom, "H")) return  1;
890   return strcmp (a->atom, b->atom);
891 }
892
893 static void special_case_formula (char *f);
894
895 static void
896 generate_molecule_formula (molecule *m)
897 {
898   char *buf = (char *) malloc (m->natoms * 10);
899   char *s = buf;
900   int i;
901   atom_and_count counts[200];
902   memset (counts, 0, sizeof(counts));
903   *s = 0;
904   for (i = 0; i < m->natoms; i++)
905     {
906       int j = 0;
907       char *a = (char *) m->atoms[i].label;
908       char *e;
909       while (!isalpha(*a)) a++;
910       a = strdup (a);
911       for (e = a; isalpha(*e); e++);
912       *e = 0;
913       while (counts[j].atom && !!strcmp(a, counts[j].atom))
914         j++;
915       if (counts[j].atom)
916         free (a);
917       else
918         counts[j].atom = a;
919       counts[j].count++;
920     }
921
922   i = 0;
923   while (counts[i].atom) i++;
924   qsort (counts, i, sizeof(*counts), cmp_atoms);
925
926   i = 0;
927   while (counts[i].atom)
928     {
929       strcat (s, counts[i].atom);
930       free (counts[i].atom);
931       s += strlen (s);
932       if (counts[i].count > 1)
933         sprintf (s, "[%d]", counts[i].count);  /* use [] to get subscripts */
934       s += strlen (s);
935       i++;
936     }
937
938   special_case_formula (buf);
939
940   if (!m->label) m->label = strdup("");
941   s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
942   strcpy (s, m->label);
943   strcat (s, "\n");
944   strcat (s, buf);
945   free ((char *) m->label);
946   free (buf);
947   m->label = s;
948 }
949
950 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
951 static void
952 special_case_formula (char *f)
953 {
954   if      (!strcmp(f, "H(2)Be"))   strcpy(f, "BeH(2)");
955   else if (!strcmp(f, "H(3)B"))    strcpy(f, "BH(3)");
956   else if (!strcmp(f, "H(3)N"))    strcpy(f, "NH(3)");
957   else if (!strcmp(f, "CHN"))      strcpy(f, "HCN");
958   else if (!strcmp(f, "CKN"))      strcpy(f, "KCN");
959   else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
960   else if (!strcmp(f, "Cl(3)P"))   strcpy(f, "PCl(3)");
961   else if (!strcmp(f, "Cl(5)P"))   strcpy(f, "PCl(5)");
962 }
963
964
965 static void
966 insert_vertical_whitespace (char *string)
967 {
968   while (*string)
969     {
970       if ((string[0] == ',' ||
971            string[0] == ';' ||
972            string[0] == ':') &&
973           string[1] == ' ')
974         string[0] = ' ', string[1] = '\n';
975       string++;
976     }
977 }
978
979
980 /* Construct the molecule data from either: the builtins; or from
981    the (one) .pdb file specified with -molecule.
982  */
983 static void
984 load_molecules (ModeInfo *mi)
985 {
986   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
987   int wire = MI_IS_WIREFRAME(mi);
988   int i;
989
990   mc->nmolecules = 0;
991   if (molecule_str && *molecule_str && 
992       strcmp(molecule_str, "(default)"))        /* try external PDB files */
993     {
994       /* The -molecule option can point to a .pdb file, or to
995          a directory of them.
996       */
997       struct stat st;
998       int nfiles = 0;
999       int list_size = 0;
1000       char **files = 0;
1001       int molecule_ctr;
1002
1003       if (!stat (molecule_str, &st) &&
1004           S_ISDIR (st.st_mode))
1005         {
1006           char buf [255];
1007           DIR *pdb_dir;
1008           struct dirent *dentry;
1009
1010           pdb_dir = opendir (molecule_str);
1011           if (! pdb_dir)
1012             {
1013               sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1014               perror (buf);
1015               exit (1);
1016             }
1017
1018           if (verbose_p)
1019             fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1020
1021           nfiles = 0;
1022           list_size = 100;
1023           files = (char **) calloc (sizeof(*files), list_size);
1024
1025           while ((dentry = readdir (pdb_dir)))
1026             {
1027               int L = strlen (dentry->d_name);
1028               if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1029                 {
1030                   char *fn;
1031                   if (nfiles >= list_size-1)
1032                     {
1033                       list_size = (list_size + 10) * 1.2;
1034                       files = (char **)
1035                         realloc (files, list_size * sizeof(*files));
1036                       if (!files)
1037                         {
1038                         OOM:
1039                           fprintf (stderr, "%s: out of memory (%d files)\n",
1040                                    progname, nfiles);
1041                           exit (1);
1042                         }
1043                     }
1044
1045                   fn = (char *) malloc (strlen (molecule_str) + L + 10);
1046                   if (!fn) goto OOM;
1047                   strcpy (fn, molecule_str);
1048                   if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1049                   strcat (fn, dentry->d_name);
1050                   files[nfiles++] = fn;
1051                   if (verbose_p)
1052                     fprintf (stderr, "%s: file %s\n", progname, fn);
1053                 }
1054             }
1055           closedir (pdb_dir);
1056
1057           if (nfiles == 0)
1058             fprintf (stderr, "%s: no .pdb files in directory %s\n",
1059                      progname, molecule_str);
1060         }
1061       else
1062         {
1063           files = (char **) malloc (sizeof (*files));
1064           nfiles = 1;
1065           files[0] = strdup (molecule_str);
1066           if (verbose_p)
1067             fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1068         }
1069
1070       mc->nmolecules = nfiles;
1071       mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1072       molecule_ctr = 0;
1073       for (i = 0; i < mc->nmolecules; i++)
1074         {
1075           if (verbose_p)
1076             fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1077           if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1078             {
1079               if ((wire || !do_atoms) &&
1080                   !do_labels &&
1081                   mc->molecules[molecule_ctr].nbonds == 0)
1082                 {
1083                   /* If we're not drawing atoms (e.g., wireframe mode), and
1084                      there is no bond info, then make sure labels are turned
1085                      on, or we'll be looking at a black screen... */
1086                   fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1087                            progname, files[i]);
1088                   do_labels = 1;
1089                 }
1090               free (files[i]);
1091               files[i] = 0;
1092               molecule_ctr++;
1093             }
1094         }
1095
1096       free (files);
1097       files = 0;
1098       mc->nmolecules = molecule_ctr;
1099     }
1100
1101   if (mc->nmolecules == 0)      /* do the builtins if no files */
1102     {
1103       mc->nmolecules = countof(builtin_pdb_data);
1104       mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1105       for (i = 0; i < mc->nmolecules; i++)
1106         {
1107           char name[100];
1108           sprintf (name, "<builtin-%d>", i);
1109           if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1110           parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1111         }
1112     }
1113
1114   for (i = 0; i < mc->nmolecules; i++)
1115     {
1116       generate_molecule_formula (&mc->molecules[i]);
1117       insert_vertical_whitespace ((char *) mc->molecules[i].label);
1118     }
1119 }
1120
1121
1122 \f
1123 /* Window management, etc
1124  */
1125 void
1126 reshape_molecule (ModeInfo *mi, int width, int height)
1127 {
1128   GLfloat h = (GLfloat) height / (GLfloat) width;
1129
1130   glViewport (0, 0, (GLint) width, (GLint) height);
1131
1132   glMatrixMode(GL_PROJECTION);
1133   glLoadIdentity();
1134   gluPerspective (30.0, 1/h, 20.0, 40.0);
1135
1136   glMatrixMode(GL_MODELVIEW);
1137   glLoadIdentity();
1138   gluLookAt( 0.0, 0.0, 30.0,
1139              0.0, 0.0, 0.0,
1140              0.0, 1.0, 0.0);
1141
1142   glClear(GL_COLOR_BUFFER_BIT);
1143 }
1144
1145
1146 static void
1147 gl_init (ModeInfo *mi)
1148 {
1149   static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1150   static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1151   static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1152   static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1153   glLightfv(GL_LIGHT0, GL_POSITION, pos);
1154   glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1155   glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1156   glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1157
1158   orig_do_labels = do_labels;
1159   orig_do_bonds = do_bonds;
1160   orig_wire = MI_IS_WIREFRAME(mi);
1161 }
1162
1163
1164 static void
1165 startup_blurb (ModeInfo *mi)
1166 {
1167   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1168   const char *s = "Constructing molecules...";
1169   print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1170                    mi->xgwa.width, mi->xgwa.height,
1171                    10, mi->xgwa.height - 10,
1172                    s);
1173   glFinish();
1174   glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1175 }
1176
1177 Bool
1178 molecule_handle_event (ModeInfo *mi, XEvent *event)
1179 {
1180   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1181
1182   if (event->xany.type == ButtonPress &&
1183       event->xbutton.button == Button1)
1184     {
1185       mc->button_down_p = True;
1186       gltrackball_start (mc->trackball,
1187                          event->xbutton.x, event->xbutton.y,
1188                          MI_WIDTH (mi), MI_HEIGHT (mi));
1189       return True;
1190     }
1191   else if (event->xany.type == ButtonRelease &&
1192            event->xbutton.button == Button1)
1193     {
1194       mc->button_down_p = False;
1195       return True;
1196     }
1197   else if (event->xany.type == ButtonPress &&
1198            (event->xbutton.button == Button4 ||
1199             event->xbutton.button == Button5))
1200     {
1201       gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1202                               !!event->xbutton.state);
1203       return True;
1204     }
1205   else if (event->xany.type == KeyPress)
1206     {
1207       KeySym keysym;
1208       char c = 0;
1209       XLookupString (&event->xkey, &c, 1, &keysym, 0);
1210
1211       if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1212         {
1213           GLfloat speed = 4.0;
1214           mc->mode = 1;
1215           mc->mode_tick = 10 * speed;
1216           return True;
1217         }
1218     }
1219   else if (event->xany.type == MotionNotify &&
1220            mc->button_down_p)
1221     {
1222       gltrackball_track (mc->trackball,
1223                          event->xmotion.x, event->xmotion.y,
1224                          MI_WIDTH (mi), MI_HEIGHT (mi));
1225       return True;
1226     }
1227
1228   return False;
1229 }
1230
1231
1232 void 
1233 init_molecule (ModeInfo *mi)
1234 {
1235   molecule_configuration *mc;
1236   int wire;
1237
1238   if (!mcs) {
1239     mcs = (molecule_configuration *)
1240       calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1241     if (!mcs) {
1242       fprintf(stderr, "%s: out of memory\n", progname);
1243       exit(1);
1244     }
1245   }
1246
1247   mc = &mcs[MI_SCREEN(mi)];
1248
1249   if ((mc->glx_context = init_GL(mi)) != NULL) {
1250     gl_init(mi);
1251     reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1252   }
1253
1254   load_fonts (mi);
1255   startup_blurb (mi);
1256
1257   wire = MI_IS_WIREFRAME(mi);
1258
1259   {
1260     Bool spinx=False, spiny=False, spinz=False;
1261     double spin_speed   = 0.5;
1262     double spin_accel   = 0.3;
1263     double wander_speed = 0.01;
1264
1265     char *s = do_spin;
1266     while (*s)
1267       {
1268         if      (*s == 'x' || *s == 'X') spinx = True;
1269         else if (*s == 'y' || *s == 'Y') spiny = True;
1270         else if (*s == 'z' || *s == 'Z') spinz = True;
1271         else
1272           {
1273             fprintf (stderr,
1274          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1275                      progname, do_spin);
1276             exit (1);
1277           }
1278         s++;
1279       }
1280
1281     mc->rot = make_rotator (spinx ? spin_speed : 0,
1282                             spiny ? spin_speed : 0,
1283                             spinz ? spin_speed : 0,
1284                             spin_accel,
1285                             do_wander ? wander_speed : 0,
1286                             (spinx && spiny && spinz));
1287     mc->trackball = gltrackball_init ();
1288   }
1289
1290   mc->molecule_dlist = glGenLists(1);
1291
1292   load_molecules (mi);
1293   mc->which = random() % mc->nmolecules;
1294
1295   mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1296                                                "NoLabelThreshold");
1297   mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1298                                                 "WireframeThreshold");
1299   mc->mode = 0;
1300
1301   if (wire)
1302     do_bonds = 1;
1303 }
1304
1305
1306 /* Put the labels on the atoms.
1307    This can't be a part of the display list because of the games
1308    we play with the translation matrix.
1309  */
1310 void
1311 draw_labels (ModeInfo *mi)
1312 {
1313   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1314   int wire = MI_IS_WIREFRAME(mi);
1315   molecule *m = &mc->molecules[mc->which];
1316   int i, j;
1317
1318   if (!do_labels)
1319     return;
1320
1321   if (!wire)
1322     glDisable (GL_LIGHTING);   /* don't light fonts */
1323
1324   for (i = 0; i < m->natoms; i++)
1325     {
1326       molecule_atom *a = &m->atoms[i];
1327       GLfloat size = atom_size (a);
1328       GLfloat m[4][4];
1329
1330       glPushMatrix();
1331
1332       if (!wire)
1333         set_atom_color (mi, a, True);
1334
1335       /* First, we translate the origin to the center of the atom.
1336
1337          Then we retrieve the prevailing modelview matrix (which
1338          includes any rotation, wandering, and user-trackball-rolling
1339          of the scene.
1340
1341          We set the top 3x3 cells of that matrix to be the identity
1342          matrix.  This removes all rotation from the matrix, while
1343          leaving the translation alone.  This has the effect of
1344          leaving the prevailing coordinate system perpendicular to
1345          the camera view: were we to draw a square face, it would
1346          be in the plane of the screen.
1347
1348          Now we translate by `size' toward the viewer -- so that the
1349          origin is *just in front* of the ball.
1350
1351          Then we draw the label text, allowing the depth buffer to
1352          do its work: that way, labels on atoms will be occluded
1353          properly when other atoms move in front of them.
1354
1355          This technique (of neutralizing rotation relative to the
1356          observer, after both rotations and translations have been
1357          applied) is known as "billboarding".
1358        */
1359
1360       glTranslatef(a->x, a->y, a->z);               /* get matrix */
1361       glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);  /* load rot. identity */
1362       m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1363       m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1364       m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1365       glLoadIdentity();                             /* reset modelview */
1366       glMultMatrixf (&m[0][0]);                     /* replace with ours */
1367
1368       glTranslatef (0, 0, (size * 1.1));           /* move toward camera */
1369
1370       glRasterPos3f (0, 0, 0);                     /* draw text here */
1371
1372       /* Before drawing the string, shift the origin to center
1373          the text over the origin of the sphere. */
1374       glBitmap (0, 0, 0, 0,
1375                 -string_width (mc->xfont1, a->label) / 2,
1376                 -mc->xfont1->descent,
1377                 NULL);
1378
1379       for (j = 0; j < strlen(a->label); j++)
1380         glCallList (mc->font1_dlist + (int)(a->label[j]));
1381
1382       glPopMatrix();
1383     }
1384
1385   /* More efficient to always call glEnable() with correct values
1386      than to call glPushAttrib()/glPopAttrib(), since reading
1387      attributes from GL does a round-trip and  stalls the pipeline.
1388    */
1389   if (!wire)
1390     glEnable (GL_LIGHTING);
1391 }
1392
1393
1394 static void
1395 pick_new_molecule (ModeInfo *mi, time_t last)
1396 {
1397   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1398
1399   if (mc->nmolecules == 1)
1400     {
1401       if (last != 0) return;
1402       mc->which = 0;
1403     }
1404   else if (last == 0)
1405     {
1406       mc->which = random() % mc->nmolecules;
1407     }
1408   else
1409     {
1410       int n = mc->which;
1411       while (n == mc->which)
1412         n = random() % mc->nmolecules;
1413       mc->which = n;
1414     }
1415           
1416   if (verbose_p)
1417     {
1418       char *name = strdup (mc->molecules[mc->which].label);
1419       char *s = strpbrk (name, "\r\n");
1420       if (s) *s = 0;
1421       fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1422       free (name);
1423     }
1424
1425   glNewList (mc->molecule_dlist, GL_COMPILE);
1426   ensure_bounding_box_visible (mi);
1427
1428   do_labels = orig_do_labels;
1429   do_bonds = orig_do_bonds;
1430   MI_IS_WIREFRAME(mi) = orig_wire;
1431
1432   if (mc->molecule_size > mc->no_label_threshold)
1433     do_labels = 0;
1434   if (mc->molecule_size > mc->wireframe_threshold)
1435     MI_IS_WIREFRAME(mi) = 1;
1436
1437   if (MI_IS_WIREFRAME(mi))
1438     do_bonds = 1;
1439
1440   build_molecule (mi);
1441   glEndList();
1442 }
1443
1444
1445 void
1446 draw_molecule (ModeInfo *mi)
1447 {
1448   static time_t last = 0;
1449   time_t now = time ((time_t *) 0);
1450   GLfloat speed = 4.0;  /* speed at which the zoom out/in happens */
1451
1452   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1453   Display *dpy = MI_DISPLAY(mi);
1454   Window window = MI_WINDOW(mi);
1455
1456   if (!mc->glx_context)
1457     return;
1458
1459   if (last == 0)
1460     {
1461       pick_new_molecule (mi, last);
1462       last = now;
1463     }
1464   else if (mc->mode == 0)
1465     {
1466       static int tick = 0;
1467       if (tick++ > 10)
1468         {
1469           time_t now = time((time_t *) 0);
1470           if (last == 0) last = now;
1471           tick = 0;
1472
1473           if (!mc->button_down_p &&
1474               mc->nmolecules > 1 &&
1475               last + timeout <= now)
1476             {
1477               /* randomize molecules every -timeout seconds */
1478               mc->mode = 1;    /* go out */
1479               mc->mode_tick = 10 * speed;
1480               last = now;
1481             }
1482         }
1483     }
1484   else if (mc->mode == 1)   /* out */
1485     {
1486       if (--mc->mode_tick <= 0)
1487         {
1488           mc->mode_tick = 10 * speed;
1489           mc->mode = 2;  /* go in */
1490           pick_new_molecule (mi, last);
1491           last = now;
1492         }
1493     }
1494   else if (mc->mode == 2)   /* in */
1495     {
1496       if (--mc->mode_tick <= 0)
1497         mc->mode = 0;  /* normal */
1498     }
1499   else
1500     abort();
1501
1502   glPushMatrix ();
1503   glScalef(1.1, 1.1, 1.1);
1504
1505   {
1506     double x, y, z;
1507     get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1508     glTranslatef((x - 0.5) * 9,
1509                  (y - 0.5) * 9,
1510                  (z - 0.5) * 9);
1511
1512     gltrackball_rotate (mc->trackball);
1513
1514     get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1515     glRotatef (x * 360, 1.0, 0.0, 0.0);
1516     glRotatef (y * 360, 0.0, 1.0, 0.0);
1517     glRotatef (z * 360, 0.0, 0.0, 1.0);
1518   }
1519
1520   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1521
1522   if (mc->mode != 0)
1523     {
1524       GLfloat s = (mc->mode == 1
1525                    ? mc->mode_tick / (10 * speed)
1526                    : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1527       glScalef (s, s, s);
1528     }
1529
1530   glCallList (mc->molecule_dlist);
1531
1532   if (mc->mode == 0)
1533     draw_labels (mi);
1534
1535   glPopMatrix ();
1536
1537   if (mi->fps_p) do_fps (mi);
1538   glFinish();
1539
1540   glXSwapBuffers(dpy, window);
1541 }
1542
1543 #endif /* USE_GL */