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