From http://www.jwz.org/xscreensaver/xscreensaver-5.37.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   MI_INIT (mi, mcs, NULL);
1292
1293   mc = &mcs[MI_SCREEN(mi)];
1294
1295   if ((mc->glx_context = init_GL(mi)) != NULL) {
1296     gl_init(mi);
1297     reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1298   }
1299
1300   load_fonts (mi);
1301   startup_blurb (mi);
1302
1303   wire = MI_IS_WIREFRAME(mi);
1304
1305   {
1306     Bool spinx=False, spiny=False, spinz=False;
1307     double spin_speed   = 0.5;
1308     double spin_accel   = 0.3;
1309     double wander_speed = 0.01;
1310
1311     char *s = do_spin;
1312     while (*s)
1313       {
1314         if      (*s == 'x' || *s == 'X') spinx = True;
1315         else if (*s == 'y' || *s == 'Y') spiny = True;
1316         else if (*s == 'z' || *s == 'Z') spinz = True;
1317         else if (*s == '0') ;
1318         else
1319           {
1320             fprintf (stderr,
1321          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1322                      progname, do_spin);
1323             exit (1);
1324           }
1325         s++;
1326       }
1327
1328     mc->rot = make_rotator (spinx ? spin_speed : 0,
1329                             spiny ? spin_speed : 0,
1330                             spinz ? spin_speed : 0,
1331                             spin_accel,
1332                             do_wander ? wander_speed : 0,
1333                             (spinx && spiny && spinz));
1334     mc->trackball = gltrackball_init (True);
1335   }
1336
1337   orig_do_labels = do_labels;
1338   orig_do_atoms  = do_atoms;
1339   orig_do_bonds  = do_bonds;
1340   orig_do_shells = do_shells;
1341   orig_wire = MI_IS_WIREFRAME(mi);
1342
1343   mc->molecule_dlist = glGenLists(1);
1344   if (do_shells)
1345     mc->shell_dlist = glGenLists(1);
1346
1347   load_molecules (mi);
1348   mc->which = random() % mc->nmolecules;
1349
1350   mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1351                                                "NoLabelThreshold");
1352   mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1353                                                 "WireframeThreshold");
1354   mc->mode = 0;
1355
1356   if (wire)
1357     do_bonds = 1;
1358 }
1359
1360
1361 /* Put the labels on the atoms.
1362    This can't be a part of the display list because of the games
1363    we play with the translation matrix.
1364  */
1365 static void
1366 draw_labels (ModeInfo *mi)
1367 {
1368   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1369   int wire = MI_IS_WIREFRAME(mi);
1370   molecule *m = &mc->molecules[mc->which];
1371   int i;
1372
1373   if (!do_labels)
1374     return;
1375
1376   for (i = 0; i < m->natoms; i++)
1377     {
1378       molecule_atom *a = &m->atoms[i];
1379       GLfloat size = atom_size (a);
1380       GLfloat m[4][4];
1381
1382       glPushMatrix();
1383
1384       if (!wire)
1385         set_atom_color (mi, a, True, 1);
1386
1387       /* First, we translate the origin to the center of the atom.
1388
1389          Then we retrieve the prevailing modelview matrix, which
1390          includes any rotation, wandering, and user-trackball-rolling
1391          of the scene.
1392
1393          We set the top 3x3 cells of that matrix to be the identity
1394          matrix.  This removes all rotation from the matrix, while
1395          leaving the translation alone.  This has the effect of
1396          leaving the prevailing coordinate system perpendicular to
1397          the camera view: were we to draw a square face, it would
1398          be in the plane of the screen.
1399
1400          Now we translate by `size' toward the viewer -- so that the
1401          origin is *just in front* of the ball.
1402
1403          Then we draw the label text, allowing the depth buffer to
1404          do its work: that way, labels on atoms will be occluded
1405          properly when other atoms move in front of them.
1406
1407          This technique (of neutralizing rotation relative to the
1408          observer, after both rotations and translations have been
1409          applied) is known as "billboarding".
1410        */
1411
1412       glTranslatef(a->x, a->y, a->z);               /* get matrix */
1413       glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);  /* load rot. identity */
1414       m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1415       m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1416       m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1417       glLoadIdentity();                             /* reset modelview */
1418       glMultMatrixf (&m[0][0]);                     /* replace with ours */
1419
1420       glTranslatef (0, 0, (size * 1.1));           /* move toward camera */
1421
1422       glRotatef (current_device_rotation(), 0, 0, 1);  /* right side up */
1423
1424       {
1425         XCharStruct e;
1426         int w, h;
1427         GLfloat s;
1428
1429         texture_string_metrics (mc->atom_font, a->label, &e, 0, 0);
1430         w = e.width;
1431         h = e.ascent + e.descent;
1432
1433         s = 1.0 / h;                    /* Scale to unit */
1434         s *= mc->overall_scale;         /* Scale to size of atom */
1435         s *= 0.8;                       /* Shrink a bit */
1436         glScalef (s, s, 1);
1437         glTranslatef (-w/2, -h/2, 0);
1438         print_texture_string (mc->atom_font, a->label);
1439       }
1440
1441       glPopMatrix();
1442     }
1443 }
1444
1445
1446 static void
1447 pick_new_molecule (ModeInfo *mi, time_t last)
1448 {
1449   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1450
1451   if (mc->nmolecules == 1)
1452     {
1453       if (last != 0) return;
1454       mc->which = 0;
1455     }
1456   else if (last == 0)
1457     {
1458       mc->which = random() % mc->nmolecules;
1459     }
1460   else if (mc->next < 0)
1461     {
1462       mc->which--;
1463       if (mc->which < 0) mc->which = mc->nmolecules-1;
1464       mc->next = 0;
1465     }
1466   else if (mc->next > 0)
1467     {
1468       mc->which++;
1469       if (mc->which >= mc->nmolecules) mc->which = 0;
1470       mc->next = 0;
1471     }
1472   else
1473     {
1474       int n = mc->which;
1475       while (n == mc->which)
1476         n = random() % mc->nmolecules;
1477       mc->which = n;
1478     }
1479           
1480   if (verbose_p)
1481     {
1482       char *name = strdup (mc->molecules[mc->which].label);
1483       char *s = strpbrk (name, "\r\n");
1484       if (s) *s = 0;
1485       fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1486       free (name);
1487     }
1488
1489   mc->polygon_count = 0;
1490
1491   glNewList (mc->molecule_dlist, GL_COMPILE);
1492   ensure_bounding_box_visible (mi);
1493
1494   do_labels = orig_do_labels;
1495   do_atoms  = orig_do_atoms;
1496   do_bonds  = orig_do_bonds;
1497   do_shells = orig_do_shells;
1498   MI_IS_WIREFRAME(mi) = orig_wire;
1499
1500   if (mc->molecule_size > mc->no_label_threshold)
1501     do_labels = 0;
1502   if (mc->molecule_size > mc->wireframe_threshold)
1503     MI_IS_WIREFRAME(mi) = 1;
1504
1505   if (MI_IS_WIREFRAME(mi))
1506     do_bonds = 1, do_shells = 0;
1507
1508   if (!do_bonds)
1509     do_shells = 0;
1510
1511   if (! (do_bonds || do_atoms || do_labels))
1512     {
1513       /* Make sure *something* shows up! */
1514       MI_IS_WIREFRAME(mi) = 1;
1515       do_bonds = 1;
1516     }
1517
1518   build_molecule (mi, False);
1519   glEndList();
1520
1521   if (do_shells)
1522     {
1523       glNewList (mc->shell_dlist, GL_COMPILE);
1524       ensure_bounding_box_visible (mi);
1525
1526       do_labels = 0;
1527       do_atoms  = 1;
1528       do_bonds  = 0;
1529
1530       build_molecule (mi, True);
1531
1532       glEndList();
1533       do_bonds  = orig_do_bonds;
1534       do_atoms  = orig_do_atoms;
1535       do_labels = orig_do_labels;
1536     }
1537 }
1538
1539
1540 ENTRYPOINT void
1541 draw_molecule (ModeInfo *mi)
1542 {
1543   time_t now = time ((time_t *) 0);
1544   GLfloat speed = 4.0;  /* speed at which the zoom out/in happens */
1545
1546   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1547   Display *dpy = MI_DISPLAY(mi);
1548   Window window = MI_WINDOW(mi);
1549
1550   if (!mc->glx_context)
1551     return;
1552
1553   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1554
1555   if (mc->draw_time == 0)
1556     {
1557       pick_new_molecule (mi, mc->draw_time);
1558       mc->draw_time = now;
1559     }
1560   else if (mc->mode == 0)
1561     {
1562       if (mc->draw_tick++ > 10)
1563         {
1564           time_t now = time((time_t *) 0);
1565           if (mc->draw_time == 0) mc->draw_time = now;
1566           mc->draw_tick = 0;
1567
1568           if (!mc->button_down_p &&
1569               mc->nmolecules > 1 &&
1570               mc->draw_time + timeout <= now)
1571             {
1572               /* randomize molecules every -timeout seconds */
1573               mc->mode = 1;    /* go out */
1574               mc->mode_tick = 80 / speed;
1575               mc->draw_time = now;
1576             }
1577         }
1578     }
1579   else if (mc->mode == 1)   /* out */
1580     {
1581       if (--mc->mode_tick <= 0)
1582         {
1583           mc->mode_tick = 80 / speed;
1584           mc->mode = 2;  /* go in */
1585           pick_new_molecule (mi, mc->draw_time);
1586         }
1587     }
1588   else if (mc->mode == 2)   /* in */
1589     {
1590       if (--mc->mode_tick <= 0)
1591         mc->mode = 0;  /* normal */
1592     }
1593   else
1594     abort();
1595
1596   glPushMatrix ();
1597   glScalef(1.1, 1.1, 1.1);
1598
1599   {
1600     double x, y, z;
1601     get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1602     glTranslatef((x - 0.5) * 9,
1603                  (y - 0.5) * 9,
1604                  (z - 0.5) * 9);
1605
1606     gltrackball_rotate (mc->trackball);
1607
1608     get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1609     glRotatef (x * 360, 1.0, 0.0, 0.0);
1610     glRotatef (y * 360, 0.0, 1.0, 0.0);
1611     glRotatef (z * 360, 0.0, 0.0, 1.0);
1612   }
1613
1614   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1615
1616   if (mc->mode != 0)
1617     {
1618       GLfloat s = (mc->mode == 1
1619                    ? mc->mode_tick / (80 / speed)
1620                    : ((80 / speed) - mc->mode_tick + 1) / (80 / speed));
1621       glScalef (s, s, s);
1622     }
1623
1624   glPushMatrix();
1625   glCallList (mc->molecule_dlist);
1626
1627   if (mc->mode == 0)
1628     {
1629       molecule *m = &mc->molecules[mc->which];
1630
1631       draw_labels (mi);
1632
1633       /* This can't go in the display list, or the characters are spaced
1634          wrongly when the window is resized. */
1635       if (do_titles && m->label && *m->label)
1636         {
1637           set_atom_color (mi, 0, True, 1);
1638           print_texture_label (mi->dpy, mc->title_font,
1639                                mi->xgwa.width, mi->xgwa.height,
1640                                1, m->label);
1641         }
1642     }
1643   glPopMatrix();
1644
1645   if (do_shells)
1646     {
1647       glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1648       glPushMatrix();
1649       glCallList (mc->shell_dlist);
1650       glPopMatrix();
1651       glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1652
1653       glDepthFunc (GL_EQUAL);
1654       glEnable (GL_BLEND);
1655       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1656       glPushMatrix();
1657       glCallList (mc->shell_dlist);
1658       glPopMatrix();
1659       glDepthFunc (GL_LESS);
1660       glDisable (GL_BLEND);
1661     }
1662
1663   glPopMatrix ();
1664
1665   mi->polygon_count = mc->polygon_count;
1666
1667   if (mi->fps_p) do_fps (mi);
1668   glFinish();
1669
1670   glXSwapBuffers(dpy, window);
1671 }
1672
1673 XSCREENSAVER_MODULE ("Molecule", molecule)
1674
1675 #endif /* USE_GL */