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