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