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