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