1 /* molecule, Copyright (c) 2001 Jamie Zawinski <jwz@jwz.org>
2 * Draws molecules, based on coordinates from PDB (Protein Data Base) files.
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
14 /* Documentation on the PDB file format:
15 http://www.rcsb.org/pdb/docs/format/pdbguide2.2/guide2.2_frame.html
17 Good source of PDB files:
18 http://www.sci.ouc.bc.ca/chem/molecule/molecule.html
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.
26 GENERAL OPENGL NAIVETY:
28 I don't understand the *right* way to place text in front of the
29 atoms. What I'm doing now is close, but has glitches. I think I
30 understand glPolygonOffset(), but I think it doesn't help me.
32 Here's how I'd phrase the problem I'm trying to solve:
34 - I have a bunch of spherical objects of various sizes
35 - I want a piece of text in the scene, between each object
37 - the position of this text should be apparently tangential
38 to the surface of the sphere, so that:
39 - it is never inside the sphere;
40 - but can be occluded by other objects in the scene.
42 So I was trying to use glPolygonOffset() to say "pretend all
43 polygons are N units deeper than they actually are" where N was
44 somewhere around the maximal radius of the objects. Which wasn't a
45 perfect solution, but was close. But it turns out that can't work,
46 because the second arg to glPolygonOffset() is multiplied by some
47 minimal depth quantum which is not revealed, so I can't pass it an
48 offset in scene units -- only in multiples of the quantum. So I
49 don't know how many quanta in radius my spheres are.
51 I think I need to position and render the text with glRasterPos3f()
52 so that the text is influenced by the depth buffer. If I used 2f,
53 or an explicit constant Z value, then the text would always be in
54 front of each sphere, and text would be visible for spheres that
55 were fully occluded, which isn't what I want.
57 So my only guess at this point is that I need to position the text
58 exactly where I want it, tangential to the spheres -- but that
59 means I need to be able to compute that XYZ position, which is
60 dependent on the position of the observer! Which means two things:
61 first, while generating my scene, I need to take into account the
62 position of the observer, and I don't have a clue how to do that;
63 and second, it means I can't put my whole molecule in a display
64 list, because the XYZ position of the text in the scene changes at
65 every frame, as the molecule rotates.
67 This just *can't* be as hard as it seems!
70 #include <X11/Intrinsic.h>
72 #define PROGCLASS "Molecule"
73 #define HACK_INIT init_molecule
74 #define HACK_DRAW draw_molecule
75 #define HACK_RESHAPE reshape_molecule
76 #define molecule_opts xlockmore_opts
78 #define DEF_TIMEOUT "20"
79 #define DEF_SPIN "XYZ"
80 #define DEF_WANDER "False"
81 #define DEF_LABELS "True"
82 #define DEF_TITLES "True"
83 #define DEF_ATOMS "True"
84 #define DEF_BONDS "True"
85 #define DEF_BBOX "False"
86 #define DEF_MOLECULE "(default)"
88 #define DEFAULTS "*delay: 10000 \n" \
89 "*timeout: " DEF_TIMEOUT "\n" \
90 "*showFPS: False \n" \
91 "*wireframe: False \n" \
92 "*molecule: " DEF_MOLECULE "\n" \
93 "*spin: " DEF_SPIN "\n" \
94 "*wander: " DEF_WANDER "\n" \
95 "*labels: " DEF_LABELS "\n" \
96 "*atoms: " DEF_ATOMS "\n" \
97 "*bonds: " DEF_BONDS "\n" \
98 "*bbox: " DEF_BBOX "\n" \
99 "*atomFont: -*-times-bold-r-normal-*-240-*\n" \
100 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
101 "*noLabelThreshold: 30 \n" \
102 "*wireframeThreshold: 150 \n" \
106 #define countof(x) (sizeof((x))/sizeof((*x)))
108 #include "xlockmore.h"
113 #ifdef USE_GL /* whole file */
119 #define SPHERE_SLICES 16 /* how densely to render spheres */
120 #define SPHERE_STACKS 10
122 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
125 # define TUBE_FACES 12 /* how densely to render tubes */
127 # define TUBE_FACES 8
130 static int scale_down;
131 #define SPHERE_SLICES_2 7
132 #define SPHERE_STACKS_2 4
133 #define TUBE_FACES_2 3
136 const char * const builtin_pdb_data[] = {
137 # include "molecules.h"
145 const char *text_color;
150 /* These are the traditional colors used to render these atoms,
151 and their approximate size in angstroms.
153 static atom_data all_atom_data[] = {
154 { "H", 1.17, 0, "White", "Grey70", { 0, }},
155 { "C", 1.75, 0, "Grey60", "White", { 0, }},
156 { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }},
157 { "O", 1.40, 0, "Red", "LightPink", { 0, }},
158 { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }},
159 { "S", 1.80, 0, "Yellow4", "Yellow1", { 0, }},
160 { "bond", 0, 0, "Grey70", "Yellow1", { 0, }},
161 { "*", 1.40, 0, "Green4", "LightGreen", { 0, }}
166 int id; /* sequence number in the PDB file */
167 const char *label; /* The atom name */
168 GLfloat x, y, z; /* position in 3-space (angstroms) */
169 atom_data *data; /* computed: which style of atom this is */
173 int from, to; /* atom sequence numbers */
174 int strength; /* how many bonds are between these two atoms */
179 const char *label; /* description of this compound */
180 int natoms, atoms_size;
181 int nbonds, bonds_size;
182 molecule_atom *atoms;
183 molecule_bond *bonds;
188 GLXContext *glx_context;
190 GLfloat rotx, roty, rotz; /* current object rotation */
191 GLfloat dx, dy, dz; /* current rotational velocity */
192 GLfloat ddx, ddy, ddz; /* current rotational acceleration */
193 GLfloat d_max; /* max velocity */
195 Bool spin_x, spin_y, spin_z;
197 GLfloat molecule_size; /* max dimension of molecule bounding box */
199 GLfloat no_label_threshold; /* Things happen when molecules are huge */
200 GLfloat wireframe_threshold;
202 int which; /* which of the molecules is being shown */
206 GLuint molecule_dlist;
208 XFontStruct *xfont1, *xfont2;
209 GLuint font1_dlist, font2_dlist;
211 } molecule_configuration;
214 static molecule_configuration *mcs = NULL;
217 static char *molecule_str;
218 static char *do_spin;
219 static Bool do_wander;
220 static Bool do_titles;
221 static Bool do_labels;
222 static Bool do_atoms;
223 static Bool do_bonds;
226 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
229 static XrmOptionDescRec opts[] = {
230 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
231 { "-timeout",".timeout",XrmoptionSepArg, 0 },
232 { "-spin", ".spin", XrmoptionSepArg, 0 },
233 { "+spin", ".spin", XrmoptionNoArg, "False" },
234 { "-wander", ".wander", XrmoptionNoArg, "True" },
235 { "+wander", ".wander", XrmoptionNoArg, "False" },
236 { "-labels", ".labels", XrmoptionNoArg, "True" },
237 { "+labels", ".labels", XrmoptionNoArg, "False" },
238 { "-titles", ".titles", XrmoptionNoArg, "True" },
239 { "+titles", ".titles", XrmoptionNoArg, "False" },
240 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
241 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
242 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
243 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
244 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
245 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
248 static argtype vars[] = {
249 {(caddr_t *) &molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
250 {(caddr_t *) &timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
251 {(caddr_t *) &do_spin, "spin", "Spin", DEF_SPIN, t_String},
252 {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
253 {(caddr_t *) &do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
254 {(caddr_t *) &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
255 {(caddr_t *) &do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
256 {(caddr_t *) &do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
257 {(caddr_t *) &do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
260 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
268 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
270 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
271 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
274 glTranslatef (x, y, z);
275 glScalef (diameter, diameter, diameter);
276 unit_sphere (stacks, slices, wire);
282 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
284 const char *font = get_string_resource (res, "Font");
289 if (!font) font = "-*-times-bold-r-normal-*-180-*";
291 f = XLoadQueryFont(mi->dpy, font);
292 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
295 first = f->min_char_or_byte2;
296 last = f->max_char_or_byte2;
299 *dlistP = glGenLists ((GLuint) last+1);
300 check_gl_error ("glGenLists");
301 glXUseXFont(id, first, last-first+1, *dlistP + first);
302 check_gl_error ("glXUseXFont");
309 load_fonts (ModeInfo *mi)
311 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
312 load_font (mi, "atomFont", &mc->xfont1, &mc->font1_dlist);
313 load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
318 string_width (XFontStruct *f, const char *c)
323 int cc = *((unsigned char *) c);
325 ? f->per_char[cc-f->min_char_or_byte2].rbearing
326 : f->min_bounds.rbearing);
334 get_atom_data (const char *atom_name)
338 char *n = strdup (atom_name);
342 while (!isalpha(*n)) n++;
344 while (L > 0 && !isalpha(n[L-1]))
347 for (i = 0; i < countof(all_atom_data); i++)
349 d = &all_atom_data[i];
350 if (!strcmp (n, all_atom_data[i].name))
360 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
369 static atom_data *def_data = 0;
370 if (!def_data) def_data = get_atom_data ("bond");
374 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
376 if (gl_color[3] == 0)
378 const char *string = !font_p ? d->color : d->text_color;
380 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
382 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
383 (a ? a->label : d->name), string);
387 gl_color[0] = xcolor.red / 65536.0;
388 gl_color[1] = xcolor.green / 65536.0;
389 gl_color[2] = xcolor.blue / 65536.0;
394 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
396 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
401 atom_size (molecule_atom *a)
405 if (a->data->size2 == 0)
407 /* let the molecules have the same relative sizes, but scale
408 them to a smaller range, so that the bond-tubes are
415 GLfloat ratio = (a->data->size - min) / (max - min);
416 a->data->size2 = bot + (ratio * (top - bot));
418 return a->data->size2;
421 return a->data->size;
425 static molecule_atom *
426 get_atom (molecule_atom *atoms, int natoms, int id)
430 /* quick short-circuit */
433 if (atoms[id].id == id)
435 if (id > 0 && atoms[id-1].id == id)
437 if (id < natoms-1 && atoms[id+1].id == id)
441 for (i = 0; i < natoms; i++)
442 if (id == atoms[i].id)
445 fprintf (stderr, "%s: no atom %d\n", progname, id);
451 molecule_bounding_box (ModeInfo *mi,
452 GLfloat *x1, GLfloat *y1, GLfloat *z1,
453 GLfloat *x2, GLfloat *y2, GLfloat *z2)
455 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
456 molecule *m = &mc->molecules[mc->which];
461 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
465 *x1 = *x2 = m->atoms[0].x;
466 *y1 = *y2 = m->atoms[0].y;
467 *z1 = *z2 = m->atoms[0].z;
470 for (i = 1; i < m->natoms; i++)
472 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
473 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
474 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
476 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
477 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
478 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
491 draw_bounding_box (ModeInfo *mi)
493 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
494 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
495 int wire = MI_IS_WIREFRAME(mi);
496 GLfloat x1, y1, z1, x2, y2, z2;
497 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
499 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
502 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
504 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
505 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
507 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
508 glNormal3f(0, -1, 0);
509 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
510 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
512 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
514 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
515 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
517 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
518 glNormal3f(0, 0, -1);
519 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
520 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
522 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
524 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
525 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
527 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
528 glNormal3f(-1, 0, 0);
529 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
530 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
533 glPushAttrib (GL_LIGHTING);
534 glDisable (GL_LIGHTING);
536 glColor3f (c2[0], c2[1], c2[2]);
538 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
539 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
540 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
541 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
542 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
543 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
550 /* Since PDB files don't always have the molecule centered around the
551 origin, and since some molecules are pretty large, scale and/or
552 translate so that the whole molecule is visible in the window.
555 ensure_bounding_box_visible (ModeInfo *mi)
557 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
559 GLfloat x1, y1, z1, x2, y2, z2;
562 GLfloat max_size = 10; /* don't bother scaling down if the molecule
563 is already smaller than this */
565 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
570 size = (w > h ? w : h);
571 size = (size > d ? size : d);
573 mc->molecule_size = size;
579 GLfloat scale = max_size / size;
580 glScalef (scale, scale, scale);
582 scale_down = scale < 0.3;
585 glTranslatef (-(x1 + w/2),
592 print_title_string (ModeInfo *mi, const char *string,
593 GLfloat x, GLfloat y, GLfloat line_height)
595 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
599 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
600 GL_ENABLE_BIT); /* for various glDisable calls */
601 glDisable (GL_LIGHTING);
602 glDisable (GL_DEPTH_TEST);
604 glMatrixMode(GL_PROJECTION);
609 glMatrixMode(GL_MODELVIEW);
615 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
617 set_atom_color (mi, 0, True);
619 glRasterPos2f (x, y);
620 for (i = 0; i < strlen(string); i++)
624 glRasterPos2f (x, (y -= line_height));
626 glCallList (mc->font2_dlist + (int)(c));
631 glMatrixMode(GL_PROJECTION);
636 glMatrixMode(GL_MODELVIEW);
640 /* Constructs the GL shapes of the current molecule
643 build_molecule (ModeInfo *mi)
645 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
646 int wire = MI_IS_WIREFRAME(mi);
649 molecule *m = &mc->molecules[mc->which];
653 glDisable(GL_CULL_FACE);
654 glDisable(GL_LIGHTING);
655 glDisable(GL_LIGHT0);
656 glDisable(GL_DEPTH_TEST);
657 glDisable(GL_NORMALIZE);
658 glDisable(GL_CULL_FACE);
662 glEnable(GL_CULL_FACE);
663 glEnable(GL_LIGHTING);
665 glEnable(GL_DEPTH_TEST);
666 glEnable(GL_NORMALIZE);
667 glEnable(GL_CULL_FACE);
671 if (do_labels && !wire)
673 /* This is so all polygons are drawn slightly farther back in the depth
674 buffer, so that when we render text directly on top of the spheres,
675 it still shows up. */
676 glEnable (GL_POLYGON_OFFSET_FILL);
677 glPolygonOffset (1.0, (do_bonds ? 10.0 : 35.0));
681 glDisable (GL_POLYGON_OFFSET_FILL);
686 set_atom_color (mi, 0, False);
689 for (i = 0; i < m->nbonds; i++)
691 molecule_bond *b = &m->bonds[i];
692 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
693 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
698 glVertex3f(from->x, from->y, from->z);
699 glVertex3f(to->x, to->y, to->z);
704 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
710 GLfloat thickness = 0.07 * b->strength;
711 GLfloat cap_size = 0.03;
715 tube (from->x, from->y, from->z,
718 faces, smooth, wire);
722 if (!wire && do_atoms)
723 for (i = 0; i < m->natoms; i++)
725 molecule_atom *a = &m->atoms[i];
726 GLfloat size = atom_size (a);
727 set_atom_color (mi, a, False);
728 sphere (a->x, a->y, a->z, size, wire);
731 /* Second pass to draw labels, after all atoms and bonds are in place
734 for (i = 0; i < m->natoms; i++)
736 molecule_atom *a = &m->atoms[i];
741 glDisable (GL_LIGHTING);
743 glDisable (GL_DEPTH_TEST);
748 set_atom_color (mi, a, True);
750 glRasterPos3f (a->x, a->y, a->z);
752 /* Before drawing the string, shift the origin to center
753 the text over the origin of the sphere. */
754 glBitmap (0, 0, 0, 0,
755 -string_width (mc->xfont1, a->label) / 2,
756 -mc->xfont1->descent,
759 for (j = 0; j < strlen(a->label); j++)
760 glCallList (mc->font1_dlist + (int)(a->label[j]));
762 /* More efficient to always call glEnable() with correct values
763 than to call glPushAttrib()/glPopAttrib(), since reading
764 attributes from GL does a round-trip and stalls the pipeline.
768 glEnable(GL_LIGHTING);
770 glEnable(GL_DEPTH_TEST);
776 draw_bounding_box (mi);
778 if (do_titles && m->label && *m->label)
779 print_title_string (mi, m->label,
780 10, mi->xgwa.height - 10,
781 mc->xfont2->ascent + mc->xfont2->descent);
789 push_atom (molecule *m,
790 int id, const char *label,
791 GLfloat x, GLfloat y, GLfloat z)
794 if (m->atoms_size < m->natoms)
797 m->atoms = (molecule_atom *) realloc (m->atoms,
798 m->atoms_size * sizeof(*m->atoms));
800 m->atoms[m->natoms-1].id = id;
801 m->atoms[m->natoms-1].label = label;
802 m->atoms[m->natoms-1].x = x;
803 m->atoms[m->natoms-1].y = y;
804 m->atoms[m->natoms-1].z = z;
805 m->atoms[m->natoms-1].data = get_atom_data (label);
810 push_bond (molecule *m, int from, int to)
814 for (i = 0; i < m->nbonds; i++)
815 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
816 (m->bonds[i].to == from && m->bonds[i].from == to))
818 m->bonds[i].strength++;
823 if (m->bonds_size < m->nbonds)
826 m->bonds = (molecule_bond *) realloc (m->bonds,
827 m->bonds_size * sizeof(*m->bonds));
829 m->bonds[m->nbonds-1].from = from;
830 m->bonds[m->nbonds-1].to = to;
831 m->bonds[m->nbonds-1].strength = 1;
836 /* This function is crap.
839 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
841 const char *s = data;
845 if ((!m->label || !*m->label) &&
846 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
848 char *name = calloc (1, 100);
855 while (isspace(*n2)) n2++;
857 ss = strchr (n2, '\n');
859 ss = strchr (n2, '\r');
862 ss = n2+strlen(n2)-1;
863 while (isspace(*ss) && ss > n2)
866 if (strlen (n2) > 4 &&
867 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
868 n2[strlen(n2)-4] = 0;
870 if (m->label) free ((char *) m->label);
871 m->label = strdup (n2);
874 else if (!strncmp (s, "TITLE ", 6) ||
875 !strncmp (s, "HEADER", 6) ||
876 !strncmp (s, "COMPND", 6) ||
877 !strncmp (s, "AUTHOR", 6) ||
878 !strncmp (s, "REVDAT", 6) ||
879 !strncmp (s, "SOURCE", 6) ||
880 !strncmp (s, "EXPDTA", 6) ||
881 !strncmp (s, "JRNL ", 6) ||
882 !strncmp (s, "REMARK", 6) ||
883 !strncmp (s, "SEQRES", 6) ||
884 !strncmp (s, "HET ", 6) ||
885 !strncmp (s, "FORMUL", 6) ||
886 !strncmp (s, "CRYST1", 6) ||
887 !strncmp (s, "ORIGX1", 6) ||
888 !strncmp (s, "ORIGX2", 6) ||
889 !strncmp (s, "ORIGX3", 6) ||
890 !strncmp (s, "SCALE1", 6) ||
891 !strncmp (s, "SCALE2", 6) ||
892 !strncmp (s, "SCALE3", 6) ||
893 !strncmp (s, "MASTER", 6) ||
894 !strncmp (s, "KEYWDS", 6) ||
895 !strncmp (s, "DBREF ", 6) ||
896 !strncmp (s, "HETNAM", 6) ||
897 !strncmp (s, "HETSYN", 6) ||
898 !strncmp (s, "HELIX ", 6) ||
899 !strncmp (s, "LINK ", 6) ||
900 !strncmp (s, "MTRIX1", 6) ||
901 !strncmp (s, "MTRIX2", 6) ||
902 !strncmp (s, "MTRIX3", 6) ||
903 !strncmp (s, "SHEET ", 6) ||
904 !strncmp (s, "CISPEP", 6) ||
905 !strncmp (s, "GENERATED BY", 12) ||
906 !strncmp (s, "TER ", 4) ||
907 !strncmp (s, "END ", 4) ||
908 !strncmp (s, "TER\n", 4) ||
909 !strncmp (s, "END\n", 4) ||
910 !strncmp (s, "\n", 1))
913 else if (!strncmp (s, "ATOM ", 7))
916 char *name = (char *) calloc (1, 4);
917 GLfloat x = -999, y = -999, z = -999;
919 sscanf (s+7, " %d ", &id);
921 strncpy (name, s+12, 3);
922 while (isspace(*name)) name++;
923 ss = name + strlen(name)-1;
924 while (isspace(*ss) && ss > name)
926 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
928 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
929 progname, filename, line,
932 push_atom (m, id, name, x, y, z);
934 else if (!strncmp (s, "HETATM ", 7))
937 char *name = (char *) calloc (1, 4);
938 GLfloat x = -999, y = -999, z = -999;
940 sscanf (s+7, " %d ", &id);
942 strncpy (name, s+12, 3);
943 while (isspace(*name)) name++;
944 ss = name + strlen(name)-1;
945 while (isspace(*ss) && ss > name)
947 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
949 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
950 progname, filename, line,
953 push_atom (m, id, name, x, y, z);
955 else if (!strncmp (s, "CONECT ", 7))
958 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d ",
959 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
960 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
961 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
963 for (j = 1; j < i; j++)
967 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
968 progname, filename, line, atoms[0], atoms[j]);
970 push_bond (m, atoms[0], atoms[j]);
975 char *s1 = strdup (s);
976 for (ss = s1; *ss && *ss != '\n'; ss++)
979 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
980 progname, filename, line, s1);
983 while (*s && *s != '\n')
993 parse_pdb_file (molecule *m, const char *name)
996 int buf_size = 40960;
1000 in = fopen(name, "r");
1003 char *buf = (char *) malloc(1024 + strlen(name));
1004 sprintf(buf, "%s: error reading \"%s\"", progname, name);
1009 buf = (char *) malloc (buf_size);
1011 while (fgets (buf, buf_size-1, in))
1014 for (s = buf; *s; s++)
1015 if (*s == '\r') *s = '\n';
1016 parse_pdb_data (m, buf, name, line++);
1024 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
1029 if (!m->nbonds && do_bonds)
1031 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
1038 typedef struct { char *atom; int count; } atom_and_count;
1040 /* When listing the components of a molecule, the convention is to put the
1041 carbon atoms first, the hydrogen atoms second, and the other atom types
1042 sorted alphabetically after that (although for some molecules, the usual
1043 order is different, like for NH(3), but we don't special-case those.)
1046 cmp_atoms (const void *aa, const void *bb)
1048 const atom_and_count *a = (atom_and_count *) aa;
1049 const atom_and_count *b = (atom_and_count *) bb;
1050 if (!a->atom) return 1;
1051 if (!b->atom) return -1;
1052 if (!strcmp(a->atom, "C")) return -1;
1053 if (!strcmp(b->atom, "C")) return 1;
1054 if (!strcmp(a->atom, "H")) return -1;
1055 if (!strcmp(b->atom, "H")) return 1;
1056 return strcmp (a->atom, b->atom);
1060 generate_molecule_formula (molecule *m)
1062 char *buf = (char *) malloc (m->natoms * 10);
1065 atom_and_count counts[200];
1066 memset (counts, 0, sizeof(counts));
1068 for (i = 0; i < m->natoms; i++)
1071 char *a = (char *) m->atoms[i].label;
1073 while (!isalpha(*a)) a++;
1075 for (e = a; isalpha(*e); e++);
1077 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1087 while (counts[i].atom) i++;
1088 qsort (counts, i, sizeof(*counts), cmp_atoms);
1091 while (counts[i].atom)
1093 strcat (s, counts[i].atom);
1094 free (counts[i].atom);
1096 if (counts[i].count > 1)
1097 sprintf (s, "(%d)", counts[i].count);
1102 if (!m->label) m->label = strdup("");
1103 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1104 strcpy (s, m->label);
1107 free ((char *) m->label);
1113 insert_vertical_whitespace (char *string)
1117 if ((string[0] == ',' ||
1119 string[0] == ':') &&
1121 string[0] = ' ', string[1] = '\n';
1127 /* Construct the molecule data from either: the builtins; or from
1128 the (one) .pdb file specified with -molecule.
1131 load_molecules (ModeInfo *mi)
1133 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1134 int wire = MI_IS_WIREFRAME(mi);
1136 if (!molecule_str || !*molecule_str ||
1137 !strcmp(molecule_str, "(default)")) /* do the builtins */
1140 mc->nmolecules = countof(builtin_pdb_data);
1141 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1142 for (i = 0; i < mc->nmolecules; i++)
1145 sprintf (name, "<builtin-%d>", i);
1146 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1147 generate_molecule_formula (&mc->molecules[i]);
1148 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1151 else /* Load a file */
1155 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1156 parse_pdb_file (&mc->molecules[i], molecule_str);
1157 generate_molecule_formula (&mc->molecules[i]);
1158 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1160 if ((wire || !do_atoms) &&
1162 mc->molecules[i].nbonds == 0)
1164 /* If we're not drawing atoms (e.g., wireframe mode), and
1165 there is no bond info, then make sure labels are turned on,
1166 or we'll be looking at a black screen... */
1167 fprintf (stderr, "%s: no bonds: turning -label on.\n", progname);
1175 /* Window management, etc
1178 reshape_molecule (ModeInfo *mi, int width, int height)
1180 GLfloat h = (GLfloat) height / (GLfloat) width;
1182 glViewport (0, 0, (GLint) width, (GLint) height);
1184 glMatrixMode(GL_PROJECTION);
1187 gluPerspective( 30.0, 1/h, 20.0, 40.0 );
1188 gluLookAt( 0.0, 0.0, 15.0,
1192 glMatrixMode(GL_MODELVIEW);
1194 glTranslatef(0.0, 0.0, -15.0);
1196 glClear(GL_COLOR_BUFFER_BIT);
1201 gl_init (ModeInfo *mi)
1203 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1204 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1205 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1206 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1207 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1208 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1209 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1210 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1212 orig_do_labels = do_labels;
1213 orig_do_bonds = do_bonds;
1214 orig_wire = MI_IS_WIREFRAME(mi);
1218 /* lifted from lament.c */
1219 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
1220 #define RANDSIGN() ((random() & 1) ? 1 : -1)
1223 rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v)
1229 ppos = -(ppos + *v);
1238 if (ppos < 0) abort();
1239 if (ppos > 1.0) abort();
1240 *pos = (*pos > 0 ? ppos : -ppos);
1245 /* clamp velocity */
1246 if (*v > max_v || *v < -max_v)
1250 /* If it stops, start it going in the other direction. */
1257 /* keep going in the same direction */
1272 /* Alter direction of rotational acceleration randomly. */
1273 if (! (random() % 120))
1276 /* Change acceleration very occasionally. */
1277 if (! (random() % 200))
1281 else if (random() & 1)
1290 startup_blurb (ModeInfo *mi)
1292 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1293 const char *s = "Constructing molecules...";
1294 print_title_string (mi, s,
1295 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1296 10 + mc->xfont2->ascent + mc->xfont2->descent,
1297 mc->xfont2->ascent + mc->xfont2->descent);
1299 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1303 init_molecule (ModeInfo *mi)
1305 molecule_configuration *mc;
1309 mcs = (molecule_configuration *)
1310 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1312 fprintf(stderr, "%s: out of memory\n", progname);
1317 mc = &mcs[MI_SCREEN(mi)];
1319 if ((mc->glx_context = init_GL(mi)) != NULL) {
1321 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1327 wire = MI_IS_WIREFRAME(mi);
1329 mc->rotx = frand(1.0) * RANDSIGN();
1330 mc->roty = frand(1.0) * RANDSIGN();
1331 mc->rotz = frand(1.0) * RANDSIGN();
1333 /* bell curve from 0-6 degrees, avg 3 */
1334 mc->dx = (frand(1) + frand(1) + frand(1)) / (360/2);
1335 mc->dy = (frand(1) + frand(1) + frand(1)) / (360/2);
1336 mc->dz = (frand(1) + frand(1) + frand(1)) / (360/2);
1338 mc->d_max = mc->dx * 2;
1340 mc->ddx = 0.00006 + frand(0.00003);
1341 mc->ddy = 0.00006 + frand(0.00003);
1342 mc->ddz = 0.00006 + frand(0.00003);
1348 if (*s == 'x' || *s == 'X') mc->spin_x = 1;
1349 else if (*s == 'y' || *s == 'Y') mc->spin_y = 1;
1350 else if (*s == 'z' || *s == 'Z') mc->spin_z = 1;
1354 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1362 mc->molecule_dlist = glGenLists(1);
1364 load_molecules (mi);
1365 mc->which = random() % mc->nmolecules;
1367 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1368 "NoLabelThreshold");
1369 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1370 "WireframeThreshold");
1378 draw_molecule (ModeInfo *mi)
1380 static time_t last = 0;
1381 time_t now = time ((time_t *) 0);
1383 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1384 Display *dpy = MI_DISPLAY(mi);
1385 Window window = MI_WINDOW(mi);
1387 if (!mc->glx_context)
1390 if (last + timeout <= now) /* randomize molecules every -timeout seconds */
1392 if (mc->nmolecules == 1)
1394 if (last != 0) goto SKIP;
1399 mc->which = random() % mc->nmolecules;
1404 while (n == mc->which)
1405 n = random() % mc->nmolecules;
1412 glNewList (mc->molecule_dlist, GL_COMPILE);
1413 ensure_bounding_box_visible (mi);
1415 do_labels = orig_do_labels;
1416 do_bonds = orig_do_bonds;
1417 MI_IS_WIREFRAME(mi) = orig_wire;
1419 if (mc->molecule_size > mc->no_label_threshold)
1421 if (mc->molecule_size > mc->wireframe_threshold)
1422 MI_IS_WIREFRAME(mi) = 1;
1424 if (MI_IS_WIREFRAME(mi))
1427 build_molecule (mi);
1433 glScalef(1.1, 1.1, 1.1);
1440 static int frame = 0;
1442 # define SINOID(SCALE,SIZE) \
1443 ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2)
1445 x = SINOID(0.031, 9.0);
1446 y = SINOID(0.023, 9.0);
1447 z = SINOID(0.017, 9.0);
1449 glTranslatef(x, y, z);
1452 if (mc->spin_x || mc->spin_y || mc->spin_z)
1457 if (x < 0) x = 1 - (x + 1);
1458 if (y < 0) y = 1 - (y + 1);
1459 if (z < 0) z = 1 - (z + 1);
1461 if (mc->spin_x) glRotatef(x * 360, 1.0, 0.0, 0.0);
1462 if (mc->spin_y) glRotatef(y * 360, 0.0, 1.0, 0.0);
1463 if (mc->spin_z) glRotatef(z * 360, 0.0, 0.0, 1.0);
1465 rotate(&mc->rotx, &mc->dx, &mc->ddx, mc->d_max);
1466 rotate(&mc->roty, &mc->dy, &mc->ddy, mc->d_max);
1467 rotate(&mc->rotz, &mc->dz, &mc->ddz, mc->d_max);
1471 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1472 glCallList (mc->molecule_dlist);
1475 if (mi->fps_p) do_fps (mi);
1478 glXSwapBuffers(dpy, window);