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