http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.32.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
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_LIGHTING | GL_DEPTH_TEST);
556   glDisable (GL_LIGHTING);
557   glDisable (GL_DEPTH_TEST);
558   {
559     glMatrixMode(GL_PROJECTION);
560     glPushMatrix();
561     {
562       glLoadIdentity();
563
564       glMatrixMode(GL_MODELVIEW);
565       glPushMatrix();
566       {
567         int i;
568         glLoadIdentity();
569
570         gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
571
572         set_atom_color (mi, 0, True);
573
574         glRasterPos2f (x, y);
575         for (i = 0; i < strlen(string); i++)
576           {
577             char c = string[i];
578             if (c == '\n')
579               glRasterPos2f (x, (y -= line_height));
580             else
581               glCallList (mc->font2_dlist + (int)(c));
582           }
583       }
584       glPopMatrix();
585     }
586     glMatrixMode(GL_PROJECTION);
587     glPopMatrix();
588   }
589   glPopAttrib();
590
591   glMatrixMode(GL_MODELVIEW);
592 }
593
594
595 /* Constructs the GL shapes of the current molecule
596  */
597 static void
598 build_molecule (ModeInfo *mi)
599 {
600   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
601   int wire = MI_IS_WIREFRAME(mi);
602   int i;
603
604   molecule *m = &mc->molecules[mc->which];
605
606   if (wire)
607     {
608       glDisable(GL_CULL_FACE);
609       glDisable(GL_LIGHTING);
610       glDisable(GL_LIGHT0);
611       glDisable(GL_DEPTH_TEST);
612       glDisable(GL_NORMALIZE);
613       glDisable(GL_CULL_FACE);
614     }
615   else
616     {
617       glEnable(GL_CULL_FACE);
618       glEnable(GL_LIGHTING);
619       glEnable(GL_LIGHT0);
620       glEnable(GL_DEPTH_TEST);
621       glEnable(GL_NORMALIZE);
622       glEnable(GL_CULL_FACE);
623     }
624
625   if (do_labels && !wire)
626     {
627       /* This is so all polygons are drawn slightly farther back in the depth
628          buffer, so that when we render text directly on top of the spheres,
629          it still shows up. */
630       glEnable (GL_POLYGON_OFFSET_FILL);
631       glPolygonOffset (1.0, (do_bonds ? 10.0 : 35.0));
632     }
633   else
634     {
635       glDisable (GL_POLYGON_OFFSET_FILL);
636     }
637
638   if (!wire)
639     set_atom_color (mi, 0, False);
640
641   if (do_bonds)
642     for (i = 0; i < m->nbonds; i++)
643       {
644         molecule_bond *b = &m->bonds[i];
645         molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
646         molecule_atom *to   = get_atom (m->atoms, m->natoms, b->to);
647
648         if (wire)
649           {
650             glBegin(GL_LINES);
651             glVertex3f(from->x, from->y, from->z);
652             glVertex3f(to->x,   to->y,   to->z);
653             glEnd();
654           }
655         else
656           {
657             int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
658 # ifdef SMOOTH_TUBE
659             int smooth = True;
660 # else
661             int smooth = False;
662 # endif
663             GLfloat thickness = 0.07 * b->strength;
664             GLfloat cap_size = 0.03;
665             if (thickness > 0.3)
666               thickness = 0.3;
667
668             tube (from->x, from->y, from->z,
669                   to->x,   to->y,   to->z,
670                   thickness, cap_size,
671                   faces, smooth, wire);
672           }
673       }
674
675   if (!wire && do_atoms)
676     for (i = 0; i < m->natoms; i++)
677       {
678         molecule_atom *a = &m->atoms[i];
679         GLfloat size = atom_size (a);
680         set_atom_color (mi, a, False);
681         sphere (a->x, a->y, a->z, size, wire);
682       }
683
684   /* Second pass to draw labels, after all atoms and bonds are in place
685    */
686   if (do_labels)
687     for (i = 0; i < m->natoms; i++)
688       {
689         molecule_atom *a = &m->atoms[i];
690         int j;
691
692         glPushAttrib (GL_LIGHTING | GL_DEPTH_TEST);
693         glDisable (GL_LIGHTING);
694 /*        glDisable (GL_DEPTH_TEST);*/
695
696         if (!wire)
697           set_atom_color (mi, a, True);
698
699         glRasterPos3f (a->x, a->y, a->z);
700
701         /* Before drawing the string, shift the origin  to center
702            the text over the origin of the sphere. */
703         glBitmap (0, 0, 0, 0,
704                   -string_width (mc->xfont1, a->label) / 2,
705                   -mc->xfont1->descent,
706                   NULL);
707
708         for (j = 0; j < strlen(a->label); j++)
709           glCallList (mc->font1_dlist + (int)(a->label[j]));
710
711         glPopAttrib();
712       }
713
714   if (do_bbox)
715     draw_bounding_box (mi);
716
717   if (do_titles && m->label && *m->label)
718     print_title_string (mi, m->label,
719                         10, mi->xgwa.height - 10,
720                         mc->xfont2->ascent + mc->xfont2->descent);
721 }
722
723
724 \f
725 /* loading */
726
727 static void
728 push_atom (molecule *m,
729            int id, const char *label,
730            GLfloat x, GLfloat y, GLfloat z)
731 {
732   m->natoms++;
733   if (m->atoms_size < m->natoms)
734     {
735       m->atoms_size += 20;
736       m->atoms = (molecule_atom *) realloc (m->atoms,
737                                             m->atoms_size * sizeof(*m->atoms));
738     }
739   m->atoms[m->natoms-1].id = id;
740   m->atoms[m->natoms-1].label = label;
741   m->atoms[m->natoms-1].x = x;
742   m->atoms[m->natoms-1].y = y;
743   m->atoms[m->natoms-1].z = z;
744   m->atoms[m->natoms-1].data = get_atom_data (label);
745 }
746
747
748 static void
749 push_bond (molecule *m, int from, int to)
750 {
751   int i;
752
753   for (i = 0; i < m->nbonds; i++)
754     if ((m->bonds[i].from == from && m->bonds[i].to   == to) ||
755         (m->bonds[i].to   == from && m->bonds[i].from == to))
756       {
757         m->bonds[i].strength++;
758         return;
759       }
760
761   m->nbonds++;
762   if (m->bonds_size < m->nbonds)
763     {
764       m->bonds_size += 20;
765       m->bonds = (molecule_bond *) realloc (m->bonds,
766                                             m->bonds_size * sizeof(*m->bonds));
767     }
768   m->bonds[m->nbonds-1].from = from;
769   m->bonds[m->nbonds-1].to = to;
770   m->bonds[m->nbonds-1].strength = 1;
771 }
772
773
774
775 /* This function is crap.
776  */
777 static void
778 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
779 {
780   const char *s = data;
781   char *ss;
782   while (*s)
783     {
784       if ((!m->label || !*m->label) &&
785           (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
786         {
787           char *name = calloc (1, 100);
788           char *n2 = name;
789           int L = strlen(s);
790           if (L > 99) L = 99;
791
792           strncpy (n2, s, L);
793           n2 += 7;
794           while (isspace(*n2)) n2++;
795
796           ss = strchr (n2, '\n');
797           if (ss) *ss = 0;
798           ss = strchr (n2, '\r');
799           if (ss) *ss = 0;
800
801           ss = n2+strlen(n2)-1;
802           while (isspace(*ss) && ss > n2)
803             *ss-- = 0;
804
805           if (strlen (n2) > 4 &&
806               !strcmp (n2 + strlen(n2) - 4, ".pdb"))
807             n2[strlen(n2)-4] = 0;
808
809           if (m->label) free ((char *) m->label);
810           m->label = strdup (n2);
811           free (name);
812         }
813       else if (!strncmp (s, "TITLE ", 6) ||
814                !strncmp (s, "HEADER", 6) ||
815                !strncmp (s, "COMPND", 6) ||
816                !strncmp (s, "AUTHOR", 6) ||
817                !strncmp (s, "REVDAT", 6) ||
818                !strncmp (s, "SOURCE", 6) ||
819                !strncmp (s, "EXPDTA", 6) ||
820                !strncmp (s, "JRNL  ", 6) ||
821                !strncmp (s, "REMARK", 6) ||
822                !strncmp (s, "SEQRES", 6) ||
823                !strncmp (s, "HET   ", 6) ||
824                !strncmp (s, "FORMUL", 6) ||
825                !strncmp (s, "CRYST1", 6) ||
826                !strncmp (s, "ORIGX1", 6) ||
827                !strncmp (s, "ORIGX2", 6) ||
828                !strncmp (s, "ORIGX3", 6) ||
829                !strncmp (s, "SCALE1", 6) ||
830                !strncmp (s, "SCALE2", 6) ||
831                !strncmp (s, "SCALE3", 6) ||
832                !strncmp (s, "MASTER", 6) ||
833                !strncmp (s, "KEYWDS", 6) ||
834                !strncmp (s, "DBREF ", 6) ||
835                !strncmp (s, "HETNAM", 6) ||
836                !strncmp (s, "HETSYN", 6) ||
837                !strncmp (s, "HELIX ", 6) ||
838                !strncmp (s, "LINK  ", 6) ||
839                !strncmp (s, "MTRIX1", 6) ||
840                !strncmp (s, "MTRIX2", 6) ||
841                !strncmp (s, "MTRIX3", 6) ||
842                !strncmp (s, "SHEET ", 6) ||
843                !strncmp (s, "CISPEP", 6) ||
844                !strncmp (s, "GENERATED BY", 12) ||
845                !strncmp (s, "TER ", 4) ||
846                !strncmp (s, "END ", 4) ||
847                !strncmp (s, "TER\n", 4) ||
848                !strncmp (s, "END\n", 4) ||
849                !strncmp (s, "\n", 1))
850         /* ignored. */
851         ;
852       else if (!strncmp (s, "ATOM   ", 7))
853         {
854           int id;
855           char *name = (char *) calloc (1, 4);
856           GLfloat x = -999, y = -999, z = -999;
857
858           sscanf (s+7, " %d ", &id);
859
860           strncpy (name, s+12, 3);
861           while (isspace(*name)) name++;
862           ss = name + strlen(name)-1;
863           while (isspace(*ss) && ss > name)
864             *ss-- = 0;
865           sscanf (s + 32, " %f %f %f ", &x, &y, &z);
866 /*
867           fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
868                    progname, filename, line,
869                    id, name, x, y, z);
870 */
871           push_atom (m, id, name, x, y, z);
872         }
873       else if (!strncmp (s, "HETATM ", 7))
874         {
875           int id;
876           char *name = (char *) calloc (1, 4);
877           GLfloat x = -999, y = -999, z = -999;
878
879           sscanf (s+7, " %d ", &id);
880
881           strncpy (name, s+12, 3);
882           while (isspace(*name)) name++;
883           ss = name + strlen(name)-1;
884           while (isspace(*ss) && ss > name)
885             *ss-- = 0;
886           sscanf (s + 30, " %f %f %f ", &x, &y, &z);
887 /*
888           fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
889                    progname, filename, line,
890                    id, name, x, y, z);
891 */
892           push_atom (m, id, name, x, y, z);
893         }
894       else if (!strncmp (s, "CONECT ", 7))
895         {
896           int atoms[11];
897           int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d ",
898                           &atoms[0], &atoms[1], &atoms[2], &atoms[3], 
899                           &atoms[4], &atoms[5], &atoms[6], &atoms[7], 
900                           &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
901           int j;
902           for (j = 1; j < i; j++)
903             if (atoms[j] > 0)
904               {
905 /*
906                 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
907                          progname, filename, line, atoms[0], atoms[j]);
908 */
909                 push_bond (m, atoms[0], atoms[j]);
910               }
911         }
912       else
913         {
914           char *s1 = strdup (s);
915           for (ss = s1; *ss && *ss != '\n'; ss++)
916             ;
917           *ss = 0;
918           fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
919                    progname, filename, line, s1);
920         }
921
922       while (*s && *s != '\n')
923         s++;
924       if (*s == '\n')
925         s++;
926       line++;
927     }
928 }
929
930
931 static void
932 parse_pdb_file (molecule *m, const char *name)
933 {
934   FILE *in;
935   int buf_size = 40960;
936   char *buf;
937   int line = 1;
938
939   in = fopen(name, "r");
940   if (!in)
941     {
942       char *buf = (char *) malloc(1024 + strlen(name));
943       sprintf(buf, "%s: error reading \"%s\"", progname, name);
944       perror(buf);
945       exit (1);
946     }
947
948   buf = (char *) malloc (buf_size);
949
950   while (fgets (buf, buf_size-1, in))
951     {
952       char *s;
953       for (s = buf; *s; s++)
954         if (*s == '\r') *s = '\n';
955       parse_pdb_data (m, buf, name, line++);
956     }
957
958   free (buf);
959   fclose (in);
960
961   if (!m->natoms)
962     {
963       fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
964                progname, name);
965       exit (1);
966     }
967
968   if (!m->nbonds && do_bonds)
969     {
970       fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
971                progname, name);
972       do_bonds = 0;
973     }
974 }
975
976
977 static void
978 generate_molecule_formula (molecule *m)
979 {
980   char *buf = (char *) malloc (m->natoms * 10);
981   char *s = buf;
982   int i;
983   struct { char *atom; int count; } counts[200];
984   memset (counts, 0, sizeof(counts));
985   *s = 0;
986   for (i = 0; i < m->natoms; i++)
987     {
988       int j = 0;
989       char *a = (char *) m->atoms[i].label;
990       char *e;
991       while (!isalpha(*a)) a++;
992       a = strdup (a);
993       for (e = a; isalpha(*e); e++);
994       *e = 0;
995       while (counts[j].atom && !!strcmp(a, counts[j].atom))
996         j++;
997       if (counts[j].atom)
998         free (a);
999       else
1000         counts[j].atom = a;
1001       counts[j].count++;
1002     }
1003
1004   i = 0;
1005   while (counts[i].atom)
1006     {
1007       strcat (s, counts[i].atom);
1008       free (counts[i].atom);
1009       s += strlen (s);
1010       if (counts[i].count > 1)
1011         sprintf (s, "(%d)", counts[i].count);
1012       s += strlen (s);
1013       i++;
1014     }
1015
1016   if (!m->label) m->label = strdup("");
1017   s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1018   strcpy (s, m->label);
1019   strcat (s, "\n");
1020   strcat (s, buf);
1021   free ((char *) m->label);
1022   free (buf);
1023   m->label = s;
1024 }
1025
1026 static void
1027 insert_vertical_whitespace (char *string)
1028 {
1029   while (*string)
1030     {
1031       if ((string[0] == ',' ||
1032            string[0] == ';' ||
1033            string[0] == ':') &&
1034           string[1] == ' ')
1035         string[0] = ' ', string[1] = '\n';
1036       string++;
1037     }
1038 }
1039
1040
1041 /* Construct the molecule data from either: the builtins; or from
1042    the (one) .pdb file specified with -molecule.
1043  */
1044 static void
1045 load_molecules (ModeInfo *mi)
1046 {
1047   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1048   int wire = MI_IS_WIREFRAME(mi);
1049
1050   if (!molecule_str || !*molecule_str ||
1051       !strcmp(molecule_str, "(default)"))       /* do the builtins */
1052     {
1053       int i;
1054       mc->nmolecules = countof(builtin_pdb_data);
1055       mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1056       for (i = 0; i < mc->nmolecules; i++)
1057         {
1058           char name[100];
1059           sprintf (name, "<builtin-%d>", i);
1060           parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1061           generate_molecule_formula (&mc->molecules[i]);
1062           insert_vertical_whitespace ((char *) mc->molecules[i].label);
1063         }
1064     }
1065   else                                          /* Load a file */
1066     {
1067       int i = 0;
1068       mc->nmolecules = 1;
1069       mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1070       parse_pdb_file (&mc->molecules[i], molecule_str);
1071       generate_molecule_formula (&mc->molecules[i]);
1072       insert_vertical_whitespace ((char *) mc->molecules[i].label);
1073
1074       if ((wire || !do_atoms) &&
1075           !do_labels &&
1076           mc->molecules[i].nbonds == 0)
1077         {
1078           /* If we're not drawing atoms (e.g., wireframe mode), and
1079              there is no bond info, then make sure labels are turned on,
1080              or we'll be looking at a black screen... */
1081           fprintf (stderr, "%s: no bonds: turning -label on.\n", progname);
1082           do_labels = 1;
1083         }
1084     }
1085 }
1086
1087
1088 \f
1089 /* Window management, etc
1090  */
1091 void
1092 reshape_molecule (ModeInfo *mi, int width, int height)
1093 {
1094   GLfloat h = (GLfloat) height / (GLfloat) width;
1095
1096   glViewport (0, 0, (GLint) width, (GLint) height);
1097
1098   glMatrixMode(GL_PROJECTION);
1099   glLoadIdentity();
1100
1101   gluPerspective( 30.0, 1/h, 1.0, 100.0 );
1102   gluLookAt( 0.0, 0.0, 15.0,
1103              0.0, 0.0, 0.0,
1104              0.0, 1.0, 0.0);
1105
1106   glMatrixMode(GL_MODELVIEW);
1107   glLoadIdentity();
1108   glTranslatef(0.0, 0.0, -15.0);
1109
1110   glClear(GL_COLOR_BUFFER_BIT);
1111 }
1112
1113
1114 static void
1115 gl_init (ModeInfo *mi)
1116 {
1117   static GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
1118   glLightfv(GL_LIGHT0, GL_POSITION, pos);
1119
1120   orig_do_labels = do_labels;
1121   orig_do_bonds = do_bonds;
1122   orig_wire = MI_IS_WIREFRAME(mi);
1123 }
1124
1125
1126 /* lifted from lament.c */
1127 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
1128 #define RANDSIGN() ((random() & 1) ? 1 : -1)
1129
1130 static void
1131 rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v)
1132 {
1133   double ppos = *pos;
1134
1135   /* tick position */
1136   if (ppos < 0)
1137     ppos = -(ppos + *v);
1138   else
1139     ppos += *v;
1140
1141   if (ppos > 1.0)
1142     ppos -= 1.0;
1143   else if (ppos < 0)
1144     ppos += 1.0;
1145
1146   if (ppos < 0) abort();
1147   if (ppos > 1.0) abort();
1148   *pos = (*pos > 0 ? ppos : -ppos);
1149
1150   /* accelerate */
1151   *v += *dv;
1152
1153   /* clamp velocity */
1154   if (*v > max_v || *v < -max_v)
1155     {
1156       *dv = -*dv;
1157     }
1158   /* If it stops, start it going in the other direction. */
1159   else if (*v < 0)
1160     {
1161       if (random() % 4)
1162         {
1163           *v = 0;
1164
1165           /* keep going in the same direction */
1166           if (random() % 2)
1167             *dv = 0;
1168           else if (*dv < 0)
1169             *dv = -*dv;
1170         }
1171       else
1172         {
1173           /* reverse gears */
1174           *v = -*v;
1175           *dv = -*dv;
1176           *pos = -*pos;
1177         }
1178     }
1179
1180   /* Alter direction of rotational acceleration randomly. */
1181   if (! (random() % 120))
1182     *dv = -*dv;
1183
1184   /* Change acceleration very occasionally. */
1185   if (! (random() % 200))
1186     {
1187       if (*dv == 0)
1188         *dv = 0.00001;
1189       else if (random() & 1)
1190         *dv *= 1.2;
1191       else
1192         *dv *= 0.8;
1193     }
1194 }
1195
1196
1197 static void
1198 startup_blurb (ModeInfo *mi)
1199 {
1200   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1201   const char *s = "Constructing molecules...";
1202   print_title_string (mi, s,
1203                       mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1204                       10 + mc->xfont2->ascent + mc->xfont2->descent,
1205                       mc->xfont2->ascent + mc->xfont2->descent);
1206   glFinish();
1207   glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1208 }
1209
1210 void 
1211 init_molecule (ModeInfo *mi)
1212 {
1213   molecule_configuration *mc;
1214   int wire;
1215
1216   if (!mcs) {
1217     mcs = (molecule_configuration *)
1218       calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1219     if (!mcs) {
1220       fprintf(stderr, "%s: out of memory\n", progname);
1221       exit(1);
1222     }
1223   }
1224
1225   mc = &mcs[MI_SCREEN(mi)];
1226
1227   if ((mc->glx_context = init_GL(mi)) != NULL) {
1228     gl_init(mi);
1229     reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1230   }
1231
1232   load_fonts (mi);
1233   startup_blurb (mi);
1234
1235   wire = MI_IS_WIREFRAME(mi);
1236
1237   mc->rotx = frand(1.0) * RANDSIGN();
1238   mc->roty = frand(1.0) * RANDSIGN();
1239   mc->rotz = frand(1.0) * RANDSIGN();
1240
1241   /* bell curve from 0-6 degrees, avg 3 */
1242   mc->dx = (frand(1) + frand(1) + frand(1)) / (360/2);
1243   mc->dy = (frand(1) + frand(1) + frand(1)) / (360/2);
1244   mc->dz = (frand(1) + frand(1) + frand(1)) / (360/2);
1245
1246   mc->d_max = mc->dx * 2;
1247
1248   mc->ddx = 0.00006 + frand(0.00003);
1249   mc->ddy = 0.00006 + frand(0.00003);
1250   mc->ddz = 0.00006 + frand(0.00003);
1251
1252   {
1253     char *s = do_spin;
1254     while (*s)
1255       {
1256         if      (*s == 'x' || *s == 'X') mc->spin_x = 1;
1257         else if (*s == 'y' || *s == 'Y') mc->spin_y = 1;
1258         else if (*s == 'z' || *s == 'Z') mc->spin_z = 1;
1259         else
1260           {
1261             fprintf (stderr,
1262          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1263                      progname, do_spin);
1264             exit (1);
1265           }
1266         s++;
1267       }
1268   }
1269
1270   mc->molecule_dlist = glGenLists(1);
1271
1272   load_molecules (mi);
1273   mc->which = random() % mc->nmolecules;
1274
1275   mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1276                                                "NoLabelThreshold");
1277   mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1278                                                 "WireframeThreshold");
1279
1280   if (wire)
1281     do_bonds = 1;
1282 }
1283
1284
1285 void
1286 draw_molecule (ModeInfo *mi)
1287 {
1288   static time_t last = 0;
1289   time_t now = time ((time_t *) 0);
1290
1291   molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1292   Display *dpy = MI_DISPLAY(mi);
1293   Window window = MI_WINDOW(mi);
1294
1295   if (!mc->glx_context)
1296     return;
1297
1298   if (last + timeout <= now)   /* randomize molecules every -timeout seconds */
1299     {
1300       if (mc->nmolecules == 1)
1301         {
1302           if (last != 0) goto SKIP;
1303           mc->which = 0;
1304         }
1305       else if (last == 0)
1306         {
1307           mc->which = random() % mc->nmolecules;
1308         }
1309       else
1310         {
1311           int n = mc->which;
1312           while (n == mc->which)
1313             n = random() % mc->nmolecules;
1314           mc->which = n;
1315         }
1316
1317       last = now;
1318
1319
1320       glNewList (mc->molecule_dlist, GL_COMPILE);
1321       ensure_bounding_box_visible (mi);
1322
1323       do_labels = orig_do_labels;
1324       do_bonds = orig_do_bonds;
1325       MI_IS_WIREFRAME(mi) = orig_wire;
1326
1327       if (mc->molecule_size > mc->no_label_threshold)
1328         do_labels = 0;
1329       if (mc->molecule_size > mc->wireframe_threshold)
1330         MI_IS_WIREFRAME(mi) = 1;
1331
1332       if (MI_IS_WIREFRAME(mi))
1333         do_bonds = 1;
1334
1335       build_molecule (mi);
1336       glEndList();
1337     }
1338  SKIP:
1339
1340   glPushMatrix ();
1341   glScalef(1.1, 1.1, 1.1);
1342
1343   {
1344     GLfloat x, y, z;
1345
1346     if (do_wander)
1347       {
1348         static int frame = 0;
1349
1350 #       define SINOID(SCALE,SIZE) \
1351         ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2)
1352
1353         x = SINOID(0.031, 9.0);
1354         y = SINOID(0.023, 9.0);
1355         z = SINOID(0.017, 9.0);
1356         frame++;
1357         glTranslatef(x, y, z);
1358       }
1359
1360     if (mc->spin_x || mc->spin_y || mc->spin_z)
1361       {
1362         x = mc->rotx;
1363         y = mc->roty;
1364         z = mc->rotz;
1365         if (x < 0) x = 1 - (x + 1);
1366         if (y < 0) y = 1 - (y + 1);
1367         if (z < 0) z = 1 - (z + 1);
1368
1369         if (mc->spin_x) glRotatef(x * 360, 1.0, 0.0, 0.0);
1370         if (mc->spin_y) glRotatef(y * 360, 0.0, 1.0, 0.0);
1371         if (mc->spin_z) glRotatef(z * 360, 0.0, 0.0, 1.0);
1372
1373         rotate(&mc->rotx, &mc->dx, &mc->ddx, mc->d_max);
1374         rotate(&mc->roty, &mc->dy, &mc->ddy, mc->d_max);
1375         rotate(&mc->rotz, &mc->dz, &mc->ddz, mc->d_max);
1376       }
1377   }
1378
1379   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1380   glCallList (mc->molecule_dlist);
1381   glPopMatrix ();
1382
1383   if (mi->fps_p) do_fps (mi);
1384   glFinish();
1385
1386   glXSwapBuffers(dpy, window);
1387 }
1388
1389 #endif /* USE_GL */