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