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