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