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