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