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