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 HACK_HANDLE_EVENT molecule_handle_event
77 #define EVENT_MASK PointerMotionMask
78 #define molecule_opts xlockmore_opts
80 #define DEF_TIMEOUT "20"
81 #define DEF_SPIN "XYZ"
82 #define DEF_WANDER "False"
83 #define DEF_LABELS "True"
84 #define DEF_TITLES "True"
85 #define DEF_ATOMS "True"
86 #define DEF_BONDS "True"
87 #define DEF_BBOX "False"
88 #define DEF_MOLECULE "(default)"
90 #define DEFAULTS "*delay: 10000 \n" \
91 "*timeout: " DEF_TIMEOUT "\n" \
92 "*showFPS: False \n" \
93 "*wireframe: False \n" \
94 "*molecule: " DEF_MOLECULE "\n" \
95 "*spin: " DEF_SPIN "\n" \
96 "*wander: " DEF_WANDER "\n" \
97 "*labels: " DEF_LABELS "\n" \
98 "*atoms: " DEF_ATOMS "\n" \
99 "*bonds: " DEF_BONDS "\n" \
100 "*bbox: " DEF_BBOX "\n" \
101 "*atomFont: -*-times-bold-r-normal-*-240-*\n" \
102 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
103 "*noLabelThreshold: 30 \n" \
104 "*wireframeThreshold: 150 \n" \
108 #define countof(x) (sizeof((x))/sizeof((*x)))
110 #include "xlockmore.h"
115 #include "gltrackball.h"
117 #ifdef USE_GL /* whole file */
122 #include <sys/time.h>
125 #define SPHERE_SLICES 16 /* how densely to render spheres */
126 #define SPHERE_STACKS 10
128 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
131 # define TUBE_FACES 12 /* how densely to render tubes */
133 # define TUBE_FACES 8
136 static int scale_down;
137 #define SPHERE_SLICES_2 7
138 #define SPHERE_STACKS_2 4
139 #define TUBE_FACES_2 3
142 const char * const builtin_pdb_data[] = {
143 # include "molecules.h"
151 const char *text_color;
156 /* These are the traditional colors used to render these atoms,
157 and their approximate size in angstroms.
159 static atom_data all_atom_data[] = {
160 { "H", 1.17, 0, "White", "Grey70", { 0, }},
161 { "C", 1.75, 0, "Grey60", "White", { 0, }},
162 { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }},
163 { "O", 1.40, 0, "Red", "LightPink", { 0, }},
164 { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }},
165 { "S", 1.80, 0, "Yellow4", "Yellow1", { 0, }},
166 { "bond", 0, 0, "Grey70", "Yellow1", { 0, }},
167 { "*", 1.40, 0, "Green4", "LightGreen", { 0, }}
172 int id; /* sequence number in the PDB file */
173 const char *label; /* The atom name */
174 GLfloat x, y, z; /* position in 3-space (angstroms) */
175 atom_data *data; /* computed: which style of atom this is */
179 int from, to; /* atom sequence numbers */
180 int strength; /* how many bonds are between these two atoms */
185 const char *label; /* description of this compound */
186 int natoms, atoms_size;
187 int nbonds, bonds_size;
188 molecule_atom *atoms;
189 molecule_bond *bonds;
194 GLXContext *glx_context;
196 trackball_state *trackball;
199 GLfloat molecule_size; /* max dimension of molecule bounding box */
201 GLfloat no_label_threshold; /* Things happen when molecules are huge */
202 GLfloat wireframe_threshold;
204 int which; /* which of the molecules is being shown */
208 GLuint molecule_dlist;
210 XFontStruct *xfont1, *xfont2;
211 GLuint font1_dlist, font2_dlist;
213 } molecule_configuration;
216 static molecule_configuration *mcs = NULL;
219 static char *molecule_str;
220 static char *do_spin;
221 static Bool do_wander;
222 static Bool do_titles;
223 static Bool do_labels;
224 static Bool do_atoms;
225 static Bool do_bonds;
228 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
231 static XrmOptionDescRec opts[] = {
232 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
233 { "-timeout",".timeout",XrmoptionSepArg, 0 },
234 { "-spin", ".spin", XrmoptionSepArg, 0 },
235 { "+spin", ".spin", XrmoptionNoArg, "" },
236 { "-wander", ".wander", XrmoptionNoArg, "True" },
237 { "+wander", ".wander", XrmoptionNoArg, "False" },
238 { "-labels", ".labels", XrmoptionNoArg, "True" },
239 { "+labels", ".labels", XrmoptionNoArg, "False" },
240 { "-titles", ".titles", XrmoptionNoArg, "True" },
241 { "+titles", ".titles", XrmoptionNoArg, "False" },
242 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
243 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
244 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
245 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
246 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
247 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
250 static argtype vars[] = {
251 {(caddr_t *) &molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
252 {(caddr_t *) &timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
253 {(caddr_t *) &do_spin, "spin", "Spin", DEF_SPIN, t_String},
254 {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
255 {(caddr_t *) &do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
256 {(caddr_t *) &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
257 {(caddr_t *) &do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
258 {(caddr_t *) &do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
259 {(caddr_t *) &do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
262 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
270 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
272 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
273 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
276 glTranslatef (x, y, z);
277 glScalef (diameter, diameter, diameter);
278 unit_sphere (stacks, slices, wire);
284 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
286 const char *font = get_string_resource (res, "Font");
291 if (!font) font = "-*-times-bold-r-normal-*-180-*";
293 f = XLoadQueryFont(mi->dpy, font);
294 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
297 first = f->min_char_or_byte2;
298 last = f->max_char_or_byte2;
301 *dlistP = glGenLists ((GLuint) last+1);
302 check_gl_error ("glGenLists");
303 glXUseXFont(id, first, last-first+1, *dlistP + first);
304 check_gl_error ("glXUseXFont");
311 load_fonts (ModeInfo *mi)
313 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
314 load_font (mi, "atomFont", &mc->xfont1, &mc->font1_dlist);
315 load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
320 string_width (XFontStruct *f, const char *c)
325 int cc = *((unsigned char *) c);
327 ? f->per_char[cc-f->min_char_or_byte2].rbearing
328 : f->min_bounds.rbearing);
336 get_atom_data (const char *atom_name)
340 char *n = strdup (atom_name);
344 while (!isalpha(*n)) n++;
346 while (L > 0 && !isalpha(n[L-1]))
349 for (i = 0; i < countof(all_atom_data); i++)
351 d = &all_atom_data[i];
352 if (!strcmp (n, all_atom_data[i].name))
362 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
371 static atom_data *def_data = 0;
372 if (!def_data) def_data = get_atom_data ("bond");
376 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
378 if (gl_color[3] == 0)
380 const char *string = !font_p ? d->color : d->text_color;
382 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
384 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
385 (a ? a->label : d->name), string);
389 gl_color[0] = xcolor.red / 65536.0;
390 gl_color[1] = xcolor.green / 65536.0;
391 gl_color[2] = xcolor.blue / 65536.0;
396 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
398 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
403 atom_size (molecule_atom *a)
407 if (a->data->size2 == 0)
409 /* let the molecules have the same relative sizes, but scale
410 them to a smaller range, so that the bond-tubes are
417 GLfloat ratio = (a->data->size - min) / (max - min);
418 a->data->size2 = bot + (ratio * (top - bot));
420 return a->data->size2;
423 return a->data->size;
427 static molecule_atom *
428 get_atom (molecule_atom *atoms, int natoms, int id)
432 /* quick short-circuit */
435 if (atoms[id].id == id)
437 if (id > 0 && atoms[id-1].id == id)
439 if (id < natoms-1 && atoms[id+1].id == id)
443 for (i = 0; i < natoms; i++)
444 if (id == atoms[i].id)
447 fprintf (stderr, "%s: no atom %d\n", progname, id);
453 molecule_bounding_box (ModeInfo *mi,
454 GLfloat *x1, GLfloat *y1, GLfloat *z1,
455 GLfloat *x2, GLfloat *y2, GLfloat *z2)
457 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
458 molecule *m = &mc->molecules[mc->which];
463 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
467 *x1 = *x2 = m->atoms[0].x;
468 *y1 = *y2 = m->atoms[0].y;
469 *z1 = *z2 = m->atoms[0].z;
472 for (i = 1; i < m->natoms; i++)
474 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
475 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
476 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
478 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
479 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
480 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
493 draw_bounding_box (ModeInfo *mi)
495 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
496 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
497 int wire = MI_IS_WIREFRAME(mi);
498 GLfloat x1, y1, z1, x2, y2, z2;
499 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
501 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
504 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
506 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
507 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
509 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
510 glNormal3f(0, -1, 0);
511 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
512 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
514 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
516 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
517 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
519 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
520 glNormal3f(0, 0, -1);
521 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
522 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
524 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
526 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
527 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
529 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
530 glNormal3f(-1, 0, 0);
531 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
532 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
535 glPushAttrib (GL_LIGHTING);
536 glDisable (GL_LIGHTING);
538 glColor3f (c2[0], c2[1], c2[2]);
540 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
541 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
542 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
543 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
544 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
545 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
552 /* Since PDB files don't always have the molecule centered around the
553 origin, and since some molecules are pretty large, scale and/or
554 translate so that the whole molecule is visible in the window.
557 ensure_bounding_box_visible (ModeInfo *mi)
559 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
561 GLfloat x1, y1, z1, x2, y2, z2;
564 GLfloat max_size = 10; /* don't bother scaling down if the molecule
565 is already smaller than this */
567 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
572 size = (w > h ? w : h);
573 size = (size > d ? size : d);
575 mc->molecule_size = size;
581 GLfloat scale = max_size / size;
582 glScalef (scale, scale, scale);
584 scale_down = scale < 0.3;
587 glTranslatef (-(x1 + w/2),
594 print_title_string (ModeInfo *mi, const char *string,
595 GLfloat x, GLfloat y, XFontStruct *font)
597 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
598 GLfloat line_height = font->ascent + font->descent;
599 GLfloat sub_shift = (line_height * 0.3);
603 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
604 GL_ENABLE_BIT); /* for various glDisable calls */
605 glDisable (GL_LIGHTING);
606 glDisable (GL_DEPTH_TEST);
608 glMatrixMode(GL_PROJECTION);
613 glMatrixMode(GL_MODELVIEW);
621 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
623 set_atom_color (mi, 0, True);
625 glRasterPos2f (x, y);
626 for (i = 0; i < strlen(string); i++)
631 glRasterPos2f (x, (y -= line_height));
634 else if (c == '(' && (isdigit (string[i+1])))
637 glRasterPos2f (x2, (y -= sub_shift));
639 else if (c == ')' && sub_p)
642 glRasterPos2f (x2, (y += sub_shift));
646 glCallList (mc->font2_dlist + (int)(c));
647 x2 += (font->per_char
648 ? font->per_char[c - font->min_char_or_byte2].width
649 : font->min_bounds.width);
655 glMatrixMode(GL_PROJECTION);
660 glMatrixMode(GL_MODELVIEW);
664 /* Constructs the GL shapes of the current molecule
667 build_molecule (ModeInfo *mi)
669 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
670 int wire = MI_IS_WIREFRAME(mi);
673 molecule *m = &mc->molecules[mc->which];
677 glDisable(GL_CULL_FACE);
678 glDisable(GL_LIGHTING);
679 glDisable(GL_LIGHT0);
680 glDisable(GL_DEPTH_TEST);
681 glDisable(GL_NORMALIZE);
682 glDisable(GL_CULL_FACE);
686 glEnable(GL_CULL_FACE);
687 glEnable(GL_LIGHTING);
689 glEnable(GL_DEPTH_TEST);
690 glEnable(GL_NORMALIZE);
691 glEnable(GL_CULL_FACE);
695 if (do_labels && !wire)
697 /* This is so all polygons are drawn slightly farther back in the depth
698 buffer, so that when we render text directly on top of the spheres,
699 it still shows up. */
700 glEnable (GL_POLYGON_OFFSET_FILL);
701 glPolygonOffset (1.0, (do_bonds ? 10.0 : 35.0));
705 glDisable (GL_POLYGON_OFFSET_FILL);
710 set_atom_color (mi, 0, False);
713 for (i = 0; i < m->nbonds; i++)
715 molecule_bond *b = &m->bonds[i];
716 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
717 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
722 glVertex3f(from->x, from->y, from->z);
723 glVertex3f(to->x, to->y, to->z);
728 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
734 GLfloat thickness = 0.07 * b->strength;
735 GLfloat cap_size = 0.03;
739 tube (from->x, from->y, from->z,
742 faces, smooth, wire);
746 if (!wire && do_atoms)
747 for (i = 0; i < m->natoms; i++)
749 molecule_atom *a = &m->atoms[i];
750 GLfloat size = atom_size (a);
751 set_atom_color (mi, a, False);
752 sphere (a->x, a->y, a->z, size, wire);
755 /* Second pass to draw labels, after all atoms and bonds are in place
758 for (i = 0; i < m->natoms; i++)
760 molecule_atom *a = &m->atoms[i];
765 glDisable (GL_LIGHTING);
767 glDisable (GL_DEPTH_TEST);
772 set_atom_color (mi, a, True);
774 glRasterPos3f (a->x, a->y, a->z);
776 /* Before drawing the string, shift the origin to center
777 the text over the origin of the sphere. */
778 glBitmap (0, 0, 0, 0,
779 -string_width (mc->xfont1, a->label) / 2,
780 -mc->xfont1->descent,
783 for (j = 0; j < strlen(a->label); j++)
784 glCallList (mc->font1_dlist + (int)(a->label[j]));
786 /* More efficient to always call glEnable() with correct values
787 than to call glPushAttrib()/glPopAttrib(), since reading
788 attributes from GL does a round-trip and stalls the pipeline.
792 glEnable(GL_LIGHTING);
794 glEnable(GL_DEPTH_TEST);
800 draw_bounding_box (mi);
802 if (do_titles && m->label && *m->label)
803 print_title_string (mi, m->label,
804 10, mi->xgwa.height - 10,
813 push_atom (molecule *m,
814 int id, const char *label,
815 GLfloat x, GLfloat y, GLfloat z)
818 if (m->atoms_size < m->natoms)
821 m->atoms = (molecule_atom *) realloc (m->atoms,
822 m->atoms_size * sizeof(*m->atoms));
824 m->atoms[m->natoms-1].id = id;
825 m->atoms[m->natoms-1].label = label;
826 m->atoms[m->natoms-1].x = x;
827 m->atoms[m->natoms-1].y = y;
828 m->atoms[m->natoms-1].z = z;
829 m->atoms[m->natoms-1].data = get_atom_data (label);
834 push_bond (molecule *m, int from, int to)
838 for (i = 0; i < m->nbonds; i++)
839 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
840 (m->bonds[i].to == from && m->bonds[i].from == to))
842 m->bonds[i].strength++;
847 if (m->bonds_size < m->nbonds)
850 m->bonds = (molecule_bond *) realloc (m->bonds,
851 m->bonds_size * sizeof(*m->bonds));
853 m->bonds[m->nbonds-1].from = from;
854 m->bonds[m->nbonds-1].to = to;
855 m->bonds[m->nbonds-1].strength = 1;
860 /* This function is crap.
863 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
865 const char *s = data;
869 if ((!m->label || !*m->label) &&
870 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
872 char *name = calloc (1, 100);
879 while (isspace(*n2)) n2++;
881 ss = strchr (n2, '\n');
883 ss = strchr (n2, '\r');
886 ss = n2+strlen(n2)-1;
887 while (isspace(*ss) && ss > n2)
890 if (strlen (n2) > 4 &&
891 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
892 n2[strlen(n2)-4] = 0;
894 if (m->label) free ((char *) m->label);
895 m->label = strdup (n2);
898 else if (!strncmp (s, "TITLE ", 6) ||
899 !strncmp (s, "HEADER", 6) ||
900 !strncmp (s, "COMPND", 6) ||
901 !strncmp (s, "AUTHOR", 6) ||
902 !strncmp (s, "REVDAT", 6) ||
903 !strncmp (s, "SOURCE", 6) ||
904 !strncmp (s, "EXPDTA", 6) ||
905 !strncmp (s, "JRNL ", 6) ||
906 !strncmp (s, "REMARK", 6) ||
907 !strncmp (s, "SEQRES", 6) ||
908 !strncmp (s, "HET ", 6) ||
909 !strncmp (s, "FORMUL", 6) ||
910 !strncmp (s, "CRYST1", 6) ||
911 !strncmp (s, "ORIGX1", 6) ||
912 !strncmp (s, "ORIGX2", 6) ||
913 !strncmp (s, "ORIGX3", 6) ||
914 !strncmp (s, "SCALE1", 6) ||
915 !strncmp (s, "SCALE2", 6) ||
916 !strncmp (s, "SCALE3", 6) ||
917 !strncmp (s, "MASTER", 6) ||
918 !strncmp (s, "KEYWDS", 6) ||
919 !strncmp (s, "DBREF ", 6) ||
920 !strncmp (s, "HETNAM", 6) ||
921 !strncmp (s, "HETSYN", 6) ||
922 !strncmp (s, "HELIX ", 6) ||
923 !strncmp (s, "LINK ", 6) ||
924 !strncmp (s, "MTRIX1", 6) ||
925 !strncmp (s, "MTRIX2", 6) ||
926 !strncmp (s, "MTRIX3", 6) ||
927 !strncmp (s, "SHEET ", 6) ||
928 !strncmp (s, "CISPEP", 6) ||
929 !strncmp (s, "GENERATED BY", 12) ||
930 !strncmp (s, "TER ", 4) ||
931 !strncmp (s, "END ", 4) ||
932 !strncmp (s, "TER\n", 4) ||
933 !strncmp (s, "END\n", 4) ||
934 !strncmp (s, "\n", 1))
937 else if (!strncmp (s, "ATOM ", 7))
940 char *name = (char *) calloc (1, 4);
941 GLfloat x = -999, y = -999, z = -999;
943 sscanf (s+7, " %d ", &id);
945 strncpy (name, s+12, 3);
946 while (isspace(*name)) name++;
947 ss = name + strlen(name)-1;
948 while (isspace(*ss) && ss > name)
950 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
952 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
953 progname, filename, line,
956 push_atom (m, id, name, x, y, z);
958 else if (!strncmp (s, "HETATM ", 7))
961 char *name = (char *) calloc (1, 4);
962 GLfloat x = -999, y = -999, z = -999;
964 sscanf (s+7, " %d ", &id);
966 strncpy (name, s+12, 3);
967 while (isspace(*name)) name++;
968 ss = name + strlen(name)-1;
969 while (isspace(*ss) && ss > name)
971 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
973 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
974 progname, filename, line,
977 push_atom (m, id, name, x, y, z);
979 else if (!strncmp (s, "CONECT ", 7))
982 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
983 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
984 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
985 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
987 for (j = 1; j < i; j++)
991 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
992 progname, filename, line, atoms[0], atoms[j]);
994 push_bond (m, atoms[0], atoms[j]);
999 char *s1 = strdup (s);
1000 for (ss = s1; *ss && *ss != '\n'; ss++)
1003 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
1004 progname, filename, line, s1);
1007 while (*s && *s != '\n')
1017 parse_pdb_file (molecule *m, const char *name)
1020 int buf_size = 40960;
1024 in = fopen(name, "r");
1027 char *buf = (char *) malloc(1024 + strlen(name));
1028 sprintf(buf, "%s: error reading \"%s\"", progname, name);
1033 buf = (char *) malloc (buf_size);
1035 while (fgets (buf, buf_size-1, in))
1038 for (s = buf; *s; s++)
1039 if (*s == '\r') *s = '\n';
1040 parse_pdb_data (m, buf, name, line++);
1048 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
1053 if (!m->nbonds && do_bonds)
1055 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
1062 typedef struct { char *atom; int count; } atom_and_count;
1064 /* When listing the components of a molecule, the convention is to put the
1065 carbon atoms first, the hydrogen atoms second, and the other atom types
1066 sorted alphabetically after that (although for some molecules, the usual
1067 order is different: we special-case a few of those.)
1070 cmp_atoms (const void *aa, const void *bb)
1072 const atom_and_count *a = (atom_and_count *) aa;
1073 const atom_and_count *b = (atom_and_count *) bb;
1074 if (!a->atom) return 1;
1075 if (!b->atom) return -1;
1076 if (!strcmp(a->atom, "C")) return -1;
1077 if (!strcmp(b->atom, "C")) return 1;
1078 if (!strcmp(a->atom, "H")) return -1;
1079 if (!strcmp(b->atom, "H")) return 1;
1080 return strcmp (a->atom, b->atom);
1083 static void special_case_formula (char *f);
1086 generate_molecule_formula (molecule *m)
1088 char *buf = (char *) malloc (m->natoms * 10);
1091 atom_and_count counts[200];
1092 memset (counts, 0, sizeof(counts));
1094 for (i = 0; i < m->natoms; i++)
1097 char *a = (char *) m->atoms[i].label;
1099 while (!isalpha(*a)) a++;
1101 for (e = a; isalpha(*e); e++);
1103 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1113 while (counts[i].atom) i++;
1114 qsort (counts, i, sizeof(*counts), cmp_atoms);
1117 while (counts[i].atom)
1119 strcat (s, counts[i].atom);
1120 free (counts[i].atom);
1122 if (counts[i].count > 1)
1123 sprintf (s, "(%d)", counts[i].count);
1128 special_case_formula (buf);
1130 if (!m->label) m->label = strdup("");
1131 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1132 strcpy (s, m->label);
1135 free ((char *) m->label);
1140 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1142 special_case_formula (char *f)
1144 if (!strcmp(f, "H(2)Be")) strcpy(f, "BeH(2)");
1145 else if (!strcmp(f, "H(3)B")) strcpy(f, "BH(3)");
1146 else if (!strcmp(f, "H(3)N")) strcpy(f, "NH(3)");
1147 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1148 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1149 else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
1150 else if (!strcmp(f, "Cl(3)P")) strcpy(f, "PCl(3)");
1151 else if (!strcmp(f, "Cl(5)P")) strcpy(f, "PCl(5)");
1156 insert_vertical_whitespace (char *string)
1160 if ((string[0] == ',' ||
1162 string[0] == ':') &&
1164 string[0] = ' ', string[1] = '\n';
1170 /* Construct the molecule data from either: the builtins; or from
1171 the (one) .pdb file specified with -molecule.
1174 load_molecules (ModeInfo *mi)
1176 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1177 int wire = MI_IS_WIREFRAME(mi);
1179 if (!molecule_str || !*molecule_str ||
1180 !strcmp(molecule_str, "(default)")) /* do the builtins */
1183 mc->nmolecules = countof(builtin_pdb_data);
1184 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1185 for (i = 0; i < mc->nmolecules; i++)
1188 sprintf (name, "<builtin-%d>", i);
1189 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1190 generate_molecule_formula (&mc->molecules[i]);
1191 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1194 else /* Load a file */
1198 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1199 parse_pdb_file (&mc->molecules[i], molecule_str);
1200 generate_molecule_formula (&mc->molecules[i]);
1201 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1203 if ((wire || !do_atoms) &&
1205 mc->molecules[i].nbonds == 0)
1207 /* If we're not drawing atoms (e.g., wireframe mode), and
1208 there is no bond info, then make sure labels are turned on,
1209 or we'll be looking at a black screen... */
1210 fprintf (stderr, "%s: no bonds: turning -label on.\n", progname);
1218 /* Window management, etc
1221 reshape_molecule (ModeInfo *mi, int width, int height)
1223 GLfloat h = (GLfloat) height / (GLfloat) width;
1225 glViewport (0, 0, (GLint) width, (GLint) height);
1227 glMatrixMode(GL_PROJECTION);
1229 gluPerspective (30.0, 1/h, 20.0, 40.0);
1231 glMatrixMode(GL_MODELVIEW);
1233 gluLookAt( 0.0, 0.0, 30.0,
1237 glClear(GL_COLOR_BUFFER_BIT);
1242 gl_init (ModeInfo *mi)
1244 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1245 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1246 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1247 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1248 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1249 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1250 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1251 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1253 orig_do_labels = do_labels;
1254 orig_do_bonds = do_bonds;
1255 orig_wire = MI_IS_WIREFRAME(mi);
1260 startup_blurb (ModeInfo *mi)
1262 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1263 const char *s = "Constructing molecules...";
1264 print_title_string (mi, s,
1265 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1266 10 + mc->xfont2->ascent + mc->xfont2->descent,
1269 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1273 molecule_handle_event (ModeInfo *mi, XEvent *event)
1275 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1277 if (event->xany.type == ButtonPress &&
1278 event->xbutton.button & Button1)
1280 mc->button_down_p = True;
1281 gltrackball_start (mc->trackball,
1282 event->xbutton.x, event->xbutton.y,
1283 MI_WIDTH (mi), MI_HEIGHT (mi));
1286 else if (event->xany.type == ButtonRelease &&
1287 event->xbutton.button & Button1)
1289 mc->button_down_p = False;
1292 else if (event->xany.type == MotionNotify &&
1295 gltrackball_track (mc->trackball,
1296 event->xmotion.x, event->xmotion.y,
1297 MI_WIDTH (mi), MI_HEIGHT (mi));
1306 init_molecule (ModeInfo *mi)
1308 molecule_configuration *mc;
1312 mcs = (molecule_configuration *)
1313 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1315 fprintf(stderr, "%s: out of memory\n", progname);
1320 mc = &mcs[MI_SCREEN(mi)];
1322 if ((mc->glx_context = init_GL(mi)) != NULL) {
1324 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1330 wire = MI_IS_WIREFRAME(mi);
1333 Bool spinx=False, spiny=False, spinz=False;
1334 double spin_speed = 2.0;
1335 double wander_speed = 0.03;
1340 if (*s == 'x' || *s == 'X') spinx = True;
1341 else if (*s == 'y' || *s == 'Y') spiny = True;
1342 else if (*s == 'z' || *s == 'Z') spinz = True;
1346 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1353 mc->rot = make_rotator (spinx ? spin_speed : 0,
1354 spiny ? spin_speed : 0,
1355 spinz ? spin_speed : 0,
1357 do_wander ? wander_speed : 0,
1358 (spinx && spiny && spinz));
1359 mc->trackball = gltrackball_init ();
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 */
1393 if (mc->nmolecules == 1)
1395 if (last != 0) goto SKIP;
1400 mc->which = random() % mc->nmolecules;
1405 while (n == mc->which)
1406 n = random() % mc->nmolecules;
1413 glNewList (mc->molecule_dlist, GL_COMPILE);
1414 ensure_bounding_box_visible (mi);
1416 do_labels = orig_do_labels;
1417 do_bonds = orig_do_bonds;
1418 MI_IS_WIREFRAME(mi) = orig_wire;
1420 if (mc->molecule_size > mc->no_label_threshold)
1422 if (mc->molecule_size > mc->wireframe_threshold)
1423 MI_IS_WIREFRAME(mi) = 1;
1425 if (MI_IS_WIREFRAME(mi))
1428 build_molecule (mi);
1434 glScalef(1.1, 1.1, 1.1);
1438 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1439 glTranslatef((x - 0.5) * 9,
1443 gltrackball_rotate (mc->trackball);
1445 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1446 glRotatef (x * 360, 1.0, 0.0, 0.0);
1447 glRotatef (y * 360, 0.0, 1.0, 0.0);
1448 glRotatef (z * 360, 0.0, 0.0, 1.0);
1451 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1452 glCallList (mc->molecule_dlist);
1455 if (mi->fps_p) do_fps (mi);
1458 glXSwapBuffers(dpy, window);