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