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