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