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