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