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 */
118 #define SPHERE_SLICES 16 /* how densely to render spheres */
119 #define SPHERE_STACKS 10
121 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
124 # define TUBE_FACES 12 /* how densely to render tubes */
126 # define TUBE_FACES 8
129 static int scale_down;
130 #define SPHERE_SLICES_2 7
131 #define SPHERE_STACKS_2 4
132 #define TUBE_FACES_2 3
135 const char * const builtin_pdb_data[] = {
136 # include "molecules.h"
144 const char *text_color;
149 /* These are the traditional colors used to render these atoms,
150 and their approximate size in angstroms.
152 static atom_data all_atom_data[] = {
153 { "H", 1.17, 0, "White", "Grey70", { 0, }},
154 { "C", 1.75, 0, "Grey60", "White", { 0, }},
155 { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }},
156 { "O", 1.40, 0, "Red", "LightPink", { 0, }},
157 { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }},
158 { "S", 1.80, 0, "Yellow4", "Yellow1", { 0, }},
159 { "bond", 0, 0, "Grey70", "Yellow1", { 0, }},
160 { "*", 1.40, 0, "Green4", "LightGreen", { 0, }}
165 int id; /* sequence number in the PDB file */
166 const char *label; /* The atom name */
167 GLfloat x, y, z; /* position in 3-space (angstroms) */
168 atom_data *data; /* computed: which style of atom this is */
172 int from, to; /* atom sequence numbers */
173 int strength; /* how many bonds are between these two atoms */
178 const char *label; /* description of this compound */
179 int natoms, atoms_size;
180 int nbonds, bonds_size;
181 molecule_atom *atoms;
182 molecule_bond *bonds;
187 GLXContext *glx_context;
189 GLfloat rotx, roty, rotz; /* current object rotation */
190 GLfloat dx, dy, dz; /* current rotational velocity */
191 GLfloat ddx, ddy, ddz; /* current rotational acceleration */
192 GLfloat d_max; /* max velocity */
194 Bool spin_x, spin_y, spin_z;
196 GLfloat molecule_size; /* max dimension of molecule bounding box */
198 GLfloat no_label_threshold; /* Things happen when molecules are huge */
199 GLfloat wireframe_threshold;
201 int which; /* which of the molecules is being shown */
205 GLuint molecule_dlist;
207 XFontStruct *xfont1, *xfont2;
208 GLuint font1_dlist, font2_dlist;
210 } molecule_configuration;
213 static molecule_configuration *mcs = NULL;
216 static char *molecule_str;
217 static char *do_spin;
218 static Bool do_wander;
219 static Bool do_titles;
220 static Bool do_labels;
221 static Bool do_atoms;
222 static Bool do_bonds;
225 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
228 static XrmOptionDescRec opts[] = {
229 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
230 { "-timeout",".timeout",XrmoptionSepArg, 0 },
231 { "-spin", ".spin", XrmoptionSepArg, 0 },
232 { "+spin", ".spin", XrmoptionNoArg, "" },
233 { "-wander", ".wander", XrmoptionNoArg, "True" },
234 { "+wander", ".wander", XrmoptionNoArg, "False" },
235 { "-labels", ".labels", XrmoptionNoArg, "True" },
236 { "+labels", ".labels", XrmoptionNoArg, "False" },
237 { "-titles", ".titles", XrmoptionNoArg, "True" },
238 { "+titles", ".titles", XrmoptionNoArg, "False" },
239 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
240 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
241 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
242 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
243 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
244 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
247 static argtype vars[] = {
248 {(caddr_t *) &molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
249 {(caddr_t *) &timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
250 {(caddr_t *) &do_spin, "spin", "Spin", DEF_SPIN, t_String},
251 {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
252 {(caddr_t *) &do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
253 {(caddr_t *) &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
254 {(caddr_t *) &do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
255 {(caddr_t *) &do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
256 {(caddr_t *) &do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
259 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
267 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
269 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
270 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
273 glTranslatef (x, y, z);
274 glScalef (diameter, diameter, diameter);
275 unit_sphere (stacks, slices, wire);
281 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
283 const char *font = get_string_resource (res, "Font");
288 if (!font) font = "-*-times-bold-r-normal-*-180-*";
290 f = XLoadQueryFont(mi->dpy, font);
291 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
294 first = f->min_char_or_byte2;
295 last = f->max_char_or_byte2;
298 *dlistP = glGenLists ((GLuint) last+1);
299 check_gl_error ("glGenLists");
300 glXUseXFont(id, first, last-first+1, *dlistP + first);
301 check_gl_error ("glXUseXFont");
308 load_fonts (ModeInfo *mi)
310 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
311 load_font (mi, "atomFont", &mc->xfont1, &mc->font1_dlist);
312 load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
317 string_width (XFontStruct *f, const char *c)
322 int cc = *((unsigned char *) c);
324 ? f->per_char[cc-f->min_char_or_byte2].rbearing
325 : f->min_bounds.rbearing);
333 get_atom_data (const char *atom_name)
337 char *n = strdup (atom_name);
341 while (!isalpha(*n)) n++;
343 while (L > 0 && !isalpha(n[L-1]))
346 for (i = 0; i < countof(all_atom_data); i++)
348 d = &all_atom_data[i];
349 if (!strcmp (n, all_atom_data[i].name))
359 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
368 static atom_data *def_data = 0;
369 if (!def_data) def_data = get_atom_data ("bond");
373 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
375 if (gl_color[3] == 0)
377 const char *string = !font_p ? d->color : d->text_color;
379 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
381 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
382 (a ? a->label : d->name), string);
386 gl_color[0] = xcolor.red / 65536.0;
387 gl_color[1] = xcolor.green / 65536.0;
388 gl_color[2] = xcolor.blue / 65536.0;
393 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
395 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
400 atom_size (molecule_atom *a)
404 if (a->data->size2 == 0)
406 /* let the molecules have the same relative sizes, but scale
407 them to a smaller range, so that the bond-tubes are
414 GLfloat ratio = (a->data->size - min) / (max - min);
415 a->data->size2 = bot + (ratio * (top - bot));
417 return a->data->size2;
420 return a->data->size;
424 static molecule_atom *
425 get_atom (molecule_atom *atoms, int natoms, int id)
429 /* quick short-circuit */
432 if (atoms[id].id == id)
434 if (id > 0 && atoms[id-1].id == id)
436 if (id < natoms-1 && atoms[id+1].id == id)
440 for (i = 0; i < natoms; i++)
441 if (id == atoms[i].id)
444 fprintf (stderr, "%s: no atom %d\n", progname, id);
450 molecule_bounding_box (ModeInfo *mi,
451 GLfloat *x1, GLfloat *y1, GLfloat *z1,
452 GLfloat *x2, GLfloat *y2, GLfloat *z2)
454 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
455 molecule *m = &mc->molecules[mc->which];
460 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
464 *x1 = *x2 = m->atoms[0].x;
465 *y1 = *y2 = m->atoms[0].y;
466 *z1 = *z2 = m->atoms[0].z;
469 for (i = 1; i < m->natoms; i++)
471 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
472 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
473 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
475 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
476 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
477 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
490 draw_bounding_box (ModeInfo *mi)
492 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
493 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
494 int wire = MI_IS_WIREFRAME(mi);
495 GLfloat x1, y1, z1, x2, y2, z2;
496 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
498 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
501 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
503 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
504 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
506 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
507 glNormal3f(0, -1, 0);
508 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
509 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
511 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
513 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
514 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
516 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
517 glNormal3f(0, 0, -1);
518 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
519 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
521 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
523 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
524 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
526 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
527 glNormal3f(-1, 0, 0);
528 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
529 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
532 glPushAttrib (GL_LIGHTING);
533 glDisable (GL_LIGHTING);
535 glColor3f (c2[0], c2[1], c2[2]);
537 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
538 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
539 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
540 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
541 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
542 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
549 /* Since PDB files don't always have the molecule centered around the
550 origin, and since some molecules are pretty large, scale and/or
551 translate so that the whole molecule is visible in the window.
554 ensure_bounding_box_visible (ModeInfo *mi)
556 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
558 GLfloat x1, y1, z1, x2, y2, z2;
561 GLfloat max_size = 10; /* don't bother scaling down if the molecule
562 is already smaller than this */
564 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
569 size = (w > h ? w : h);
570 size = (size > d ? size : d);
572 mc->molecule_size = size;
578 GLfloat scale = max_size / size;
579 glScalef (scale, scale, scale);
581 scale_down = scale < 0.3;
584 glTranslatef (-(x1 + w/2),
591 print_title_string (ModeInfo *mi, const char *string,
592 GLfloat x, GLfloat y, GLfloat line_height)
594 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
598 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
599 GL_ENABLE_BIT); /* for various glDisable calls */
600 glDisable (GL_LIGHTING);
601 glDisable (GL_DEPTH_TEST);
603 glMatrixMode(GL_PROJECTION);
608 glMatrixMode(GL_MODELVIEW);
614 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
616 set_atom_color (mi, 0, True);
618 glRasterPos2f (x, y);
619 for (i = 0; i < strlen(string); i++)
623 glRasterPos2f (x, (y -= line_height));
625 glCallList (mc->font2_dlist + (int)(c));
630 glMatrixMode(GL_PROJECTION);
635 glMatrixMode(GL_MODELVIEW);
639 /* Constructs the GL shapes of the current molecule
642 build_molecule (ModeInfo *mi)
644 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
645 int wire = MI_IS_WIREFRAME(mi);
648 molecule *m = &mc->molecules[mc->which];
652 glDisable(GL_CULL_FACE);
653 glDisable(GL_LIGHTING);
654 glDisable(GL_LIGHT0);
655 glDisable(GL_DEPTH_TEST);
656 glDisable(GL_NORMALIZE);
657 glDisable(GL_CULL_FACE);
661 glEnable(GL_CULL_FACE);
662 glEnable(GL_LIGHTING);
664 glEnable(GL_DEPTH_TEST);
665 glEnable(GL_NORMALIZE);
666 glEnable(GL_CULL_FACE);
670 if (do_labels && !wire)
672 /* This is so all polygons are drawn slightly farther back in the depth
673 buffer, so that when we render text directly on top of the spheres,
674 it still shows up. */
675 glEnable (GL_POLYGON_OFFSET_FILL);
676 glPolygonOffset (1.0, (do_bonds ? 10.0 : 35.0));
680 glDisable (GL_POLYGON_OFFSET_FILL);
685 set_atom_color (mi, 0, False);
688 for (i = 0; i < m->nbonds; i++)
690 molecule_bond *b = &m->bonds[i];
691 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
692 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
697 glVertex3f(from->x, from->y, from->z);
698 glVertex3f(to->x, to->y, to->z);
703 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
709 GLfloat thickness = 0.07 * b->strength;
710 GLfloat cap_size = 0.03;
714 tube (from->x, from->y, from->z,
717 faces, smooth, wire);
721 if (!wire && do_atoms)
722 for (i = 0; i < m->natoms; i++)
724 molecule_atom *a = &m->atoms[i];
725 GLfloat size = atom_size (a);
726 set_atom_color (mi, a, False);
727 sphere (a->x, a->y, a->z, size, wire);
730 /* Second pass to draw labels, after all atoms and bonds are in place
733 for (i = 0; i < m->natoms; i++)
735 molecule_atom *a = &m->atoms[i];
740 glDisable (GL_LIGHTING);
742 glDisable (GL_DEPTH_TEST);
747 set_atom_color (mi, a, True);
749 glRasterPos3f (a->x, a->y, a->z);
751 /* Before drawing the string, shift the origin to center
752 the text over the origin of the sphere. */
753 glBitmap (0, 0, 0, 0,
754 -string_width (mc->xfont1, a->label) / 2,
755 -mc->xfont1->descent,
758 for (j = 0; j < strlen(a->label); j++)
759 glCallList (mc->font1_dlist + (int)(a->label[j]));
761 /* More efficient to always call glEnable() with correct values
762 than to call glPushAttrib()/glPopAttrib(), since reading
763 attributes from GL does a round-trip and stalls the pipeline.
767 glEnable(GL_LIGHTING);
769 glEnable(GL_DEPTH_TEST);
775 draw_bounding_box (mi);
777 if (do_titles && m->label && *m->label)
778 print_title_string (mi, m->label,
779 10, mi->xgwa.height - 10,
780 mc->xfont2->ascent + mc->xfont2->descent);
788 push_atom (molecule *m,
789 int id, const char *label,
790 GLfloat x, GLfloat y, GLfloat z)
793 if (m->atoms_size < m->natoms)
796 m->atoms = (molecule_atom *) realloc (m->atoms,
797 m->atoms_size * sizeof(*m->atoms));
799 m->atoms[m->natoms-1].id = id;
800 m->atoms[m->natoms-1].label = label;
801 m->atoms[m->natoms-1].x = x;
802 m->atoms[m->natoms-1].y = y;
803 m->atoms[m->natoms-1].z = z;
804 m->atoms[m->natoms-1].data = get_atom_data (label);
809 push_bond (molecule *m, int from, int to)
813 for (i = 0; i < m->nbonds; i++)
814 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
815 (m->bonds[i].to == from && m->bonds[i].from == to))
817 m->bonds[i].strength++;
822 if (m->bonds_size < m->nbonds)
825 m->bonds = (molecule_bond *) realloc (m->bonds,
826 m->bonds_size * sizeof(*m->bonds));
828 m->bonds[m->nbonds-1].from = from;
829 m->bonds[m->nbonds-1].to = to;
830 m->bonds[m->nbonds-1].strength = 1;
835 /* This function is crap.
838 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
840 const char *s = data;
844 if ((!m->label || !*m->label) &&
845 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
847 char *name = calloc (1, 100);
854 while (isspace(*n2)) n2++;
856 ss = strchr (n2, '\n');
858 ss = strchr (n2, '\r');
861 ss = n2+strlen(n2)-1;
862 while (isspace(*ss) && ss > n2)
865 if (strlen (n2) > 4 &&
866 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
867 n2[strlen(n2)-4] = 0;
869 if (m->label) free ((char *) m->label);
870 m->label = strdup (n2);
873 else if (!strncmp (s, "TITLE ", 6) ||
874 !strncmp (s, "HEADER", 6) ||
875 !strncmp (s, "COMPND", 6) ||
876 !strncmp (s, "AUTHOR", 6) ||
877 !strncmp (s, "REVDAT", 6) ||
878 !strncmp (s, "SOURCE", 6) ||
879 !strncmp (s, "EXPDTA", 6) ||
880 !strncmp (s, "JRNL ", 6) ||
881 !strncmp (s, "REMARK", 6) ||
882 !strncmp (s, "SEQRES", 6) ||
883 !strncmp (s, "HET ", 6) ||
884 !strncmp (s, "FORMUL", 6) ||
885 !strncmp (s, "CRYST1", 6) ||
886 !strncmp (s, "ORIGX1", 6) ||
887 !strncmp (s, "ORIGX2", 6) ||
888 !strncmp (s, "ORIGX3", 6) ||
889 !strncmp (s, "SCALE1", 6) ||
890 !strncmp (s, "SCALE2", 6) ||
891 !strncmp (s, "SCALE3", 6) ||
892 !strncmp (s, "MASTER", 6) ||
893 !strncmp (s, "KEYWDS", 6) ||
894 !strncmp (s, "DBREF ", 6) ||
895 !strncmp (s, "HETNAM", 6) ||
896 !strncmp (s, "HETSYN", 6) ||
897 !strncmp (s, "HELIX ", 6) ||
898 !strncmp (s, "LINK ", 6) ||
899 !strncmp (s, "MTRIX1", 6) ||
900 !strncmp (s, "MTRIX2", 6) ||
901 !strncmp (s, "MTRIX3", 6) ||
902 !strncmp (s, "SHEET ", 6) ||
903 !strncmp (s, "CISPEP", 6) ||
904 !strncmp (s, "GENERATED BY", 12) ||
905 !strncmp (s, "TER ", 4) ||
906 !strncmp (s, "END ", 4) ||
907 !strncmp (s, "TER\n", 4) ||
908 !strncmp (s, "END\n", 4) ||
909 !strncmp (s, "\n", 1))
912 else if (!strncmp (s, "ATOM ", 7))
915 char *name = (char *) calloc (1, 4);
916 GLfloat x = -999, y = -999, z = -999;
918 sscanf (s+7, " %d ", &id);
920 strncpy (name, s+12, 3);
921 while (isspace(*name)) name++;
922 ss = name + strlen(name)-1;
923 while (isspace(*ss) && ss > name)
925 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
927 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
928 progname, filename, line,
931 push_atom (m, id, name, x, y, z);
933 else if (!strncmp (s, "HETATM ", 7))
936 char *name = (char *) calloc (1, 4);
937 GLfloat x = -999, y = -999, z = -999;
939 sscanf (s+7, " %d ", &id);
941 strncpy (name, s+12, 3);
942 while (isspace(*name)) name++;
943 ss = name + strlen(name)-1;
944 while (isspace(*ss) && ss > name)
946 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
948 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
949 progname, filename, line,
952 push_atom (m, id, name, x, y, z);
954 else if (!strncmp (s, "CONECT ", 7))
957 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d ",
958 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
959 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
960 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
962 for (j = 1; j < i; j++)
966 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
967 progname, filename, line, atoms[0], atoms[j]);
969 push_bond (m, atoms[0], atoms[j]);
974 char *s1 = strdup (s);
975 for (ss = s1; *ss && *ss != '\n'; ss++)
978 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
979 progname, filename, line, s1);
982 while (*s && *s != '\n')
992 parse_pdb_file (molecule *m, const char *name)
995 int buf_size = 40960;
999 in = fopen(name, "r");
1002 char *buf = (char *) malloc(1024 + strlen(name));
1003 sprintf(buf, "%s: error reading \"%s\"", progname, name);
1008 buf = (char *) malloc (buf_size);
1010 while (fgets (buf, buf_size-1, in))
1013 for (s = buf; *s; s++)
1014 if (*s == '\r') *s = '\n';
1015 parse_pdb_data (m, buf, name, line++);
1023 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
1028 if (!m->nbonds && do_bonds)
1030 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
1038 generate_molecule_formula (molecule *m)
1040 char *buf = (char *) malloc (m->natoms * 10);
1043 struct { char *atom; int count; } counts[200];
1044 memset (counts, 0, sizeof(counts));
1046 for (i = 0; i < m->natoms; i++)
1049 char *a = (char *) m->atoms[i].label;
1051 while (!isalpha(*a)) a++;
1053 for (e = a; isalpha(*e); e++);
1055 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1065 while (counts[i].atom)
1067 strcat (s, counts[i].atom);
1068 free (counts[i].atom);
1070 if (counts[i].count > 1)
1071 sprintf (s, "(%d)", counts[i].count);
1076 if (!m->label) m->label = strdup("");
1077 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1078 strcpy (s, m->label);
1081 free ((char *) m->label);
1087 insert_vertical_whitespace (char *string)
1091 if ((string[0] == ',' ||
1093 string[0] == ':') &&
1095 string[0] = ' ', string[1] = '\n';
1101 /* Construct the molecule data from either: the builtins; or from
1102 the (one) .pdb file specified with -molecule.
1105 load_molecules (ModeInfo *mi)
1107 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1108 int wire = MI_IS_WIREFRAME(mi);
1110 if (!molecule_str || !*molecule_str ||
1111 !strcmp(molecule_str, "(default)")) /* do the builtins */
1114 mc->nmolecules = countof(builtin_pdb_data);
1115 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1116 for (i = 0; i < mc->nmolecules; i++)
1119 sprintf (name, "<builtin-%d>", i);
1120 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1121 generate_molecule_formula (&mc->molecules[i]);
1122 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1125 else /* Load a file */
1129 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1130 parse_pdb_file (&mc->molecules[i], molecule_str);
1131 generate_molecule_formula (&mc->molecules[i]);
1132 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1134 if ((wire || !do_atoms) &&
1136 mc->molecules[i].nbonds == 0)
1138 /* If we're not drawing atoms (e.g., wireframe mode), and
1139 there is no bond info, then make sure labels are turned on,
1140 or we'll be looking at a black screen... */
1141 fprintf (stderr, "%s: no bonds: turning -label on.\n", progname);
1149 /* Window management, etc
1152 reshape_molecule (ModeInfo *mi, int width, int height)
1154 GLfloat h = (GLfloat) height / (GLfloat) width;
1156 glViewport (0, 0, (GLint) width, (GLint) height);
1158 glMatrixMode(GL_PROJECTION);
1161 gluPerspective( 30.0, 1/h, 20.0, 40.0 );
1162 gluLookAt( 0.0, 0.0, 15.0,
1166 glMatrixMode(GL_MODELVIEW);
1168 glTranslatef(0.0, 0.0, -15.0);
1170 glClear(GL_COLOR_BUFFER_BIT);
1175 gl_init (ModeInfo *mi)
1177 static GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
1178 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1180 orig_do_labels = do_labels;
1181 orig_do_bonds = do_bonds;
1182 orig_wire = MI_IS_WIREFRAME(mi);
1186 /* lifted from lament.c */
1187 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
1188 #define RANDSIGN() ((random() & 1) ? 1 : -1)
1191 rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v)
1197 ppos = -(ppos + *v);
1206 if (ppos < 0) abort();
1207 if (ppos > 1.0) abort();
1208 *pos = (*pos > 0 ? ppos : -ppos);
1213 /* clamp velocity */
1214 if (*v > max_v || *v < -max_v)
1218 /* If it stops, start it going in the other direction. */
1225 /* keep going in the same direction */
1240 /* Alter direction of rotational acceleration randomly. */
1241 if (! (random() % 120))
1244 /* Change acceleration very occasionally. */
1245 if (! (random() % 200))
1249 else if (random() & 1)
1258 startup_blurb (ModeInfo *mi)
1260 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1261 const char *s = "Constructing molecules...";
1262 print_title_string (mi, s,
1263 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1264 10 + mc->xfont2->ascent + mc->xfont2->descent,
1265 mc->xfont2->ascent + mc->xfont2->descent);
1267 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1271 init_molecule (ModeInfo *mi)
1273 molecule_configuration *mc;
1277 mcs = (molecule_configuration *)
1278 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1280 fprintf(stderr, "%s: out of memory\n", progname);
1285 mc = &mcs[MI_SCREEN(mi)];
1287 if ((mc->glx_context = init_GL(mi)) != NULL) {
1289 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1295 wire = MI_IS_WIREFRAME(mi);
1297 mc->rotx = frand(1.0) * RANDSIGN();
1298 mc->roty = frand(1.0) * RANDSIGN();
1299 mc->rotz = frand(1.0) * RANDSIGN();
1301 /* bell curve from 0-6 degrees, avg 3 */
1302 mc->dx = (frand(1) + frand(1) + frand(1)) / (360/2);
1303 mc->dy = (frand(1) + frand(1) + frand(1)) / (360/2);
1304 mc->dz = (frand(1) + frand(1) + frand(1)) / (360/2);
1306 mc->d_max = mc->dx * 2;
1308 mc->ddx = 0.00006 + frand(0.00003);
1309 mc->ddy = 0.00006 + frand(0.00003);
1310 mc->ddz = 0.00006 + frand(0.00003);
1316 if (*s == 'x' || *s == 'X') mc->spin_x = 1;
1317 else if (*s == 'y' || *s == 'Y') mc->spin_y = 1;
1318 else if (*s == 'z' || *s == 'Z') mc->spin_z = 1;
1322 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1330 mc->molecule_dlist = glGenLists(1);
1332 load_molecules (mi);
1333 mc->which = random() % mc->nmolecules;
1335 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1336 "NoLabelThreshold");
1337 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1338 "WireframeThreshold");
1346 draw_molecule (ModeInfo *mi)
1348 static time_t last = 0;
1349 time_t now = time ((time_t *) 0);
1351 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1352 Display *dpy = MI_DISPLAY(mi);
1353 Window window = MI_WINDOW(mi);
1355 if (!mc->glx_context)
1358 if (last + timeout <= now) /* randomize molecules every -timeout seconds */
1360 if (mc->nmolecules == 1)
1362 if (last != 0) goto SKIP;
1367 mc->which = random() % mc->nmolecules;
1372 while (n == mc->which)
1373 n = random() % mc->nmolecules;
1380 glNewList (mc->molecule_dlist, GL_COMPILE);
1381 ensure_bounding_box_visible (mi);
1383 do_labels = orig_do_labels;
1384 do_bonds = orig_do_bonds;
1385 MI_IS_WIREFRAME(mi) = orig_wire;
1387 if (mc->molecule_size > mc->no_label_threshold)
1389 if (mc->molecule_size > mc->wireframe_threshold)
1390 MI_IS_WIREFRAME(mi) = 1;
1392 if (MI_IS_WIREFRAME(mi))
1395 build_molecule (mi);
1401 glScalef(1.1, 1.1, 1.1);
1408 static int frame = 0;
1410 # define SINOID(SCALE,SIZE) \
1411 ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2)
1413 x = SINOID(0.031, 9.0);
1414 y = SINOID(0.023, 9.0);
1415 z = SINOID(0.017, 9.0);
1417 glTranslatef(x, y, z);
1420 if (mc->spin_x || mc->spin_y || mc->spin_z)
1425 if (x < 0) x = 1 - (x + 1);
1426 if (y < 0) y = 1 - (y + 1);
1427 if (z < 0) z = 1 - (z + 1);
1429 if (mc->spin_x) glRotatef(x * 360, 1.0, 0.0, 0.0);
1430 if (mc->spin_y) glRotatef(y * 360, 0.0, 1.0, 0.0);
1431 if (mc->spin_z) glRotatef(z * 360, 0.0, 0.0, 1.0);
1433 rotate(&mc->rotx, &mc->dx, &mc->ddx, mc->d_max);
1434 rotate(&mc->roty, &mc->dy, &mc->ddy, mc->d_max);
1435 rotate(&mc->rotz, &mc->dz, &mc->ddz, mc->d_max);
1439 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1440 glCallList (mc->molecule_dlist);
1443 if (mi->fps_p) do_fps (mi);
1446 glXSwapBuffers(dpy, window);