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