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