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, XFontStruct *font)
595 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
596 GLfloat line_height = font->ascent + font->descent;
597 GLfloat sub_shift = (line_height * 0.3);
601 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
602 GL_ENABLE_BIT); /* for various glDisable calls */
603 glDisable (GL_LIGHTING);
604 glDisable (GL_DEPTH_TEST);
606 glMatrixMode(GL_PROJECTION);
611 glMatrixMode(GL_MODELVIEW);
619 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
621 set_atom_color (mi, 0, True);
623 glRasterPos2f (x, y);
624 for (i = 0; i < strlen(string); i++)
629 glRasterPos2f (x, (y -= line_height));
632 else if (c == '(' && (isdigit (string[i+1])))
635 glRasterPos2f (x2, (y -= sub_shift));
637 else if (c == ')' && sub_p)
640 glRasterPos2f (x2, (y += sub_shift));
644 glCallList (mc->font2_dlist + (int)(c));
645 x2 += (font->per_char
646 ? font->per_char[c - font->min_char_or_byte2].width
647 : font->min_bounds.width);
653 glMatrixMode(GL_PROJECTION);
658 glMatrixMode(GL_MODELVIEW);
662 /* Constructs the GL shapes of the current molecule
665 build_molecule (ModeInfo *mi)
667 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
668 int wire = MI_IS_WIREFRAME(mi);
671 molecule *m = &mc->molecules[mc->which];
675 glDisable(GL_CULL_FACE);
676 glDisable(GL_LIGHTING);
677 glDisable(GL_LIGHT0);
678 glDisable(GL_DEPTH_TEST);
679 glDisable(GL_NORMALIZE);
680 glDisable(GL_CULL_FACE);
684 glEnable(GL_CULL_FACE);
685 glEnable(GL_LIGHTING);
687 glEnable(GL_DEPTH_TEST);
688 glEnable(GL_NORMALIZE);
689 glEnable(GL_CULL_FACE);
693 if (do_labels && !wire)
695 /* This is so all polygons are drawn slightly farther back in the depth
696 buffer, so that when we render text directly on top of the spheres,
697 it still shows up. */
698 glEnable (GL_POLYGON_OFFSET_FILL);
699 glPolygonOffset (1.0, (do_bonds ? 10.0 : 35.0));
703 glDisable (GL_POLYGON_OFFSET_FILL);
708 set_atom_color (mi, 0, False);
711 for (i = 0; i < m->nbonds; i++)
713 molecule_bond *b = &m->bonds[i];
714 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
715 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
720 glVertex3f(from->x, from->y, from->z);
721 glVertex3f(to->x, to->y, to->z);
726 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
732 GLfloat thickness = 0.07 * b->strength;
733 GLfloat cap_size = 0.03;
737 tube (from->x, from->y, from->z,
740 faces, smooth, wire);
744 if (!wire && do_atoms)
745 for (i = 0; i < m->natoms; i++)
747 molecule_atom *a = &m->atoms[i];
748 GLfloat size = atom_size (a);
749 set_atom_color (mi, a, False);
750 sphere (a->x, a->y, a->z, size, wire);
753 /* Second pass to draw labels, after all atoms and bonds are in place
756 for (i = 0; i < m->natoms; i++)
758 molecule_atom *a = &m->atoms[i];
763 glDisable (GL_LIGHTING);
765 glDisable (GL_DEPTH_TEST);
770 set_atom_color (mi, a, True);
772 glRasterPos3f (a->x, a->y, a->z);
774 /* Before drawing the string, shift the origin to center
775 the text over the origin of the sphere. */
776 glBitmap (0, 0, 0, 0,
777 -string_width (mc->xfont1, a->label) / 2,
778 -mc->xfont1->descent,
781 for (j = 0; j < strlen(a->label); j++)
782 glCallList (mc->font1_dlist + (int)(a->label[j]));
784 /* More efficient to always call glEnable() with correct values
785 than to call glPushAttrib()/glPopAttrib(), since reading
786 attributes from GL does a round-trip and stalls the pipeline.
790 glEnable(GL_LIGHTING);
792 glEnable(GL_DEPTH_TEST);
798 draw_bounding_box (mi);
800 if (do_titles && m->label && *m->label)
801 print_title_string (mi, m->label,
802 10, mi->xgwa.height - 10,
811 push_atom (molecule *m,
812 int id, const char *label,
813 GLfloat x, GLfloat y, GLfloat z)
816 if (m->atoms_size < m->natoms)
819 m->atoms = (molecule_atom *) realloc (m->atoms,
820 m->atoms_size * sizeof(*m->atoms));
822 m->atoms[m->natoms-1].id = id;
823 m->atoms[m->natoms-1].label = label;
824 m->atoms[m->natoms-1].x = x;
825 m->atoms[m->natoms-1].y = y;
826 m->atoms[m->natoms-1].z = z;
827 m->atoms[m->natoms-1].data = get_atom_data (label);
832 push_bond (molecule *m, int from, int to)
836 for (i = 0; i < m->nbonds; i++)
837 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
838 (m->bonds[i].to == from && m->bonds[i].from == to))
840 m->bonds[i].strength++;
845 if (m->bonds_size < m->nbonds)
848 m->bonds = (molecule_bond *) realloc (m->bonds,
849 m->bonds_size * sizeof(*m->bonds));
851 m->bonds[m->nbonds-1].from = from;
852 m->bonds[m->nbonds-1].to = to;
853 m->bonds[m->nbonds-1].strength = 1;
858 /* This function is crap.
861 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
863 const char *s = data;
867 if ((!m->label || !*m->label) &&
868 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
870 char *name = calloc (1, 100);
877 while (isspace(*n2)) n2++;
879 ss = strchr (n2, '\n');
881 ss = strchr (n2, '\r');
884 ss = n2+strlen(n2)-1;
885 while (isspace(*ss) && ss > n2)
888 if (strlen (n2) > 4 &&
889 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
890 n2[strlen(n2)-4] = 0;
892 if (m->label) free ((char *) m->label);
893 m->label = strdup (n2);
896 else if (!strncmp (s, "TITLE ", 6) ||
897 !strncmp (s, "HEADER", 6) ||
898 !strncmp (s, "COMPND", 6) ||
899 !strncmp (s, "AUTHOR", 6) ||
900 !strncmp (s, "REVDAT", 6) ||
901 !strncmp (s, "SOURCE", 6) ||
902 !strncmp (s, "EXPDTA", 6) ||
903 !strncmp (s, "JRNL ", 6) ||
904 !strncmp (s, "REMARK", 6) ||
905 !strncmp (s, "SEQRES", 6) ||
906 !strncmp (s, "HET ", 6) ||
907 !strncmp (s, "FORMUL", 6) ||
908 !strncmp (s, "CRYST1", 6) ||
909 !strncmp (s, "ORIGX1", 6) ||
910 !strncmp (s, "ORIGX2", 6) ||
911 !strncmp (s, "ORIGX3", 6) ||
912 !strncmp (s, "SCALE1", 6) ||
913 !strncmp (s, "SCALE2", 6) ||
914 !strncmp (s, "SCALE3", 6) ||
915 !strncmp (s, "MASTER", 6) ||
916 !strncmp (s, "KEYWDS", 6) ||
917 !strncmp (s, "DBREF ", 6) ||
918 !strncmp (s, "HETNAM", 6) ||
919 !strncmp (s, "HETSYN", 6) ||
920 !strncmp (s, "HELIX ", 6) ||
921 !strncmp (s, "LINK ", 6) ||
922 !strncmp (s, "MTRIX1", 6) ||
923 !strncmp (s, "MTRIX2", 6) ||
924 !strncmp (s, "MTRIX3", 6) ||
925 !strncmp (s, "SHEET ", 6) ||
926 !strncmp (s, "CISPEP", 6) ||
927 !strncmp (s, "GENERATED BY", 12) ||
928 !strncmp (s, "TER ", 4) ||
929 !strncmp (s, "END ", 4) ||
930 !strncmp (s, "TER\n", 4) ||
931 !strncmp (s, "END\n", 4) ||
932 !strncmp (s, "\n", 1))
935 else if (!strncmp (s, "ATOM ", 7))
938 char *name = (char *) calloc (1, 4);
939 GLfloat x = -999, y = -999, z = -999;
941 sscanf (s+7, " %d ", &id);
943 strncpy (name, s+12, 3);
944 while (isspace(*name)) name++;
945 ss = name + strlen(name)-1;
946 while (isspace(*ss) && ss > name)
948 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
950 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
951 progname, filename, line,
954 push_atom (m, id, name, x, y, z);
956 else if (!strncmp (s, "HETATM ", 7))
959 char *name = (char *) calloc (1, 4);
960 GLfloat x = -999, y = -999, z = -999;
962 sscanf (s+7, " %d ", &id);
964 strncpy (name, s+12, 3);
965 while (isspace(*name)) name++;
966 ss = name + strlen(name)-1;
967 while (isspace(*ss) && ss > name)
969 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
971 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
972 progname, filename, line,
975 push_atom (m, id, name, x, y, z);
977 else if (!strncmp (s, "CONECT ", 7))
980 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d ",
981 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
982 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
983 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
985 for (j = 1; j < i; j++)
989 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
990 progname, filename, line, atoms[0], atoms[j]);
992 push_bond (m, atoms[0], atoms[j]);
997 char *s1 = strdup (s);
998 for (ss = s1; *ss && *ss != '\n'; ss++)
1001 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
1002 progname, filename, line, s1);
1005 while (*s && *s != '\n')
1015 parse_pdb_file (molecule *m, const char *name)
1018 int buf_size = 40960;
1022 in = fopen(name, "r");
1025 char *buf = (char *) malloc(1024 + strlen(name));
1026 sprintf(buf, "%s: error reading \"%s\"", progname, name);
1031 buf = (char *) malloc (buf_size);
1033 while (fgets (buf, buf_size-1, in))
1036 for (s = buf; *s; s++)
1037 if (*s == '\r') *s = '\n';
1038 parse_pdb_data (m, buf, name, line++);
1046 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
1051 if (!m->nbonds && do_bonds)
1053 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
1060 typedef struct { char *atom; int count; } atom_and_count;
1062 /* When listing the components of a molecule, the convention is to put the
1063 carbon atoms first, the hydrogen atoms second, and the other atom types
1064 sorted alphabetically after that (although for some molecules, the usual
1065 order is different: we special-case a few of those.)
1068 cmp_atoms (const void *aa, const void *bb)
1070 const atom_and_count *a = (atom_and_count *) aa;
1071 const atom_and_count *b = (atom_and_count *) bb;
1072 if (!a->atom) return 1;
1073 if (!b->atom) return -1;
1074 if (!strcmp(a->atom, "C")) return -1;
1075 if (!strcmp(b->atom, "C")) return 1;
1076 if (!strcmp(a->atom, "H")) return -1;
1077 if (!strcmp(b->atom, "H")) return 1;
1078 return strcmp (a->atom, b->atom);
1081 static void special_case_formula (char *f);
1084 generate_molecule_formula (molecule *m)
1086 char *buf = (char *) malloc (m->natoms * 10);
1089 atom_and_count counts[200];
1090 memset (counts, 0, sizeof(counts));
1092 for (i = 0; i < m->natoms; i++)
1095 char *a = (char *) m->atoms[i].label;
1097 while (!isalpha(*a)) a++;
1099 for (e = a; isalpha(*e); e++);
1101 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1111 while (counts[i].atom) i++;
1112 qsort (counts, i, sizeof(*counts), cmp_atoms);
1115 while (counts[i].atom)
1117 strcat (s, counts[i].atom);
1118 free (counts[i].atom);
1120 if (counts[i].count > 1)
1121 sprintf (s, "(%d)", counts[i].count);
1126 special_case_formula (buf);
1128 if (!m->label) m->label = strdup("");
1129 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1130 strcpy (s, m->label);
1133 free ((char *) m->label);
1138 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1140 special_case_formula (char *f)
1142 if (!strcmp(f, "H(2)Be")) strcpy(f, "BeH(2)");
1143 else if (!strcmp(f, "H(3)B")) strcpy(f, "BH(3)");
1144 else if (!strcmp(f, "H(3)N")) strcpy(f, "NH(3)");
1145 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1146 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1147 else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
1148 else if (!strcmp(f, "Cl(3)P")) strcpy(f, "PCl(3)");
1149 else if (!strcmp(f, "Cl(5)P")) strcpy(f, "PCl(5)");
1154 insert_vertical_whitespace (char *string)
1158 if ((string[0] == ',' ||
1160 string[0] == ':') &&
1162 string[0] = ' ', string[1] = '\n';
1168 /* Construct the molecule data from either: the builtins; or from
1169 the (one) .pdb file specified with -molecule.
1172 load_molecules (ModeInfo *mi)
1174 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1175 int wire = MI_IS_WIREFRAME(mi);
1177 if (!molecule_str || !*molecule_str ||
1178 !strcmp(molecule_str, "(default)")) /* do the builtins */
1181 mc->nmolecules = countof(builtin_pdb_data);
1182 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1183 for (i = 0; i < mc->nmolecules; i++)
1186 sprintf (name, "<builtin-%d>", i);
1187 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1188 generate_molecule_formula (&mc->molecules[i]);
1189 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1192 else /* Load a file */
1196 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1197 parse_pdb_file (&mc->molecules[i], molecule_str);
1198 generate_molecule_formula (&mc->molecules[i]);
1199 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1201 if ((wire || !do_atoms) &&
1203 mc->molecules[i].nbonds == 0)
1205 /* If we're not drawing atoms (e.g., wireframe mode), and
1206 there is no bond info, then make sure labels are turned on,
1207 or we'll be looking at a black screen... */
1208 fprintf (stderr, "%s: no bonds: turning -label on.\n", progname);
1216 /* Window management, etc
1219 reshape_molecule (ModeInfo *mi, int width, int height)
1221 GLfloat h = (GLfloat) height / (GLfloat) width;
1223 glViewport (0, 0, (GLint) width, (GLint) height);
1225 glMatrixMode(GL_PROJECTION);
1228 gluPerspective( 30.0, 1/h, 20.0, 40.0 );
1229 gluLookAt( 0.0, 0.0, 15.0,
1233 glMatrixMode(GL_MODELVIEW);
1235 glTranslatef(0.0, 0.0, -15.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);
1259 /* lifted from lament.c */
1260 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
1261 #define RANDSIGN() ((random() & 1) ? 1 : -1)
1264 rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v)
1270 ppos = -(ppos + *v);
1279 if (ppos < 0) abort();
1280 if (ppos > 1.0) abort();
1281 *pos = (*pos > 0 ? ppos : -ppos);
1286 /* clamp velocity */
1287 if (*v > max_v || *v < -max_v)
1291 /* If it stops, start it going in the other direction. */
1298 /* keep going in the same direction */
1313 /* Alter direction of rotational acceleration randomly. */
1314 if (! (random() % 120))
1317 /* Change acceleration very occasionally. */
1318 if (! (random() % 200))
1322 else if (random() & 1)
1331 startup_blurb (ModeInfo *mi)
1333 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1334 const char *s = "Constructing molecules...";
1335 print_title_string (mi, s,
1336 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1337 10 + mc->xfont2->ascent + mc->xfont2->descent,
1340 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1344 init_molecule (ModeInfo *mi)
1346 molecule_configuration *mc;
1350 mcs = (molecule_configuration *)
1351 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1353 fprintf(stderr, "%s: out of memory\n", progname);
1358 mc = &mcs[MI_SCREEN(mi)];
1360 if ((mc->glx_context = init_GL(mi)) != NULL) {
1362 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1368 wire = MI_IS_WIREFRAME(mi);
1370 mc->rotx = frand(1.0) * RANDSIGN();
1371 mc->roty = frand(1.0) * RANDSIGN();
1372 mc->rotz = frand(1.0) * RANDSIGN();
1374 /* bell curve from 0-6 degrees, avg 3 */
1375 mc->dx = (frand(1) + frand(1) + frand(1)) / (360/2);
1376 mc->dy = (frand(1) + frand(1) + frand(1)) / (360/2);
1377 mc->dz = (frand(1) + frand(1) + frand(1)) / (360/2);
1379 mc->d_max = mc->dx * 2;
1381 mc->ddx = 0.00006 + frand(0.00003);
1382 mc->ddy = 0.00006 + frand(0.00003);
1383 mc->ddz = 0.00006 + frand(0.00003);
1389 if (*s == 'x' || *s == 'X') mc->spin_x = 1;
1390 else if (*s == 'y' || *s == 'Y') mc->spin_y = 1;
1391 else if (*s == 'z' || *s == 'Z') mc->spin_z = 1;
1395 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1403 mc->molecule_dlist = glGenLists(1);
1405 load_molecules (mi);
1406 mc->which = random() % mc->nmolecules;
1408 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1409 "NoLabelThreshold");
1410 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1411 "WireframeThreshold");
1419 draw_molecule (ModeInfo *mi)
1421 static time_t last = 0;
1422 time_t now = time ((time_t *) 0);
1424 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1425 Display *dpy = MI_DISPLAY(mi);
1426 Window window = MI_WINDOW(mi);
1428 if (!mc->glx_context)
1431 if (last + timeout <= now) /* randomize molecules every -timeout seconds */
1433 if (mc->nmolecules == 1)
1435 if (last != 0) goto SKIP;
1440 mc->which = random() % mc->nmolecules;
1445 while (n == mc->which)
1446 n = random() % mc->nmolecules;
1453 glNewList (mc->molecule_dlist, GL_COMPILE);
1454 ensure_bounding_box_visible (mi);
1456 do_labels = orig_do_labels;
1457 do_bonds = orig_do_bonds;
1458 MI_IS_WIREFRAME(mi) = orig_wire;
1460 if (mc->molecule_size > mc->no_label_threshold)
1462 if (mc->molecule_size > mc->wireframe_threshold)
1463 MI_IS_WIREFRAME(mi) = 1;
1465 if (MI_IS_WIREFRAME(mi))
1468 build_molecule (mi);
1474 glScalef(1.1, 1.1, 1.1);
1481 static int frame = 0;
1483 # define SINOID(SCALE,SIZE) \
1484 ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2)
1486 x = SINOID(0.031, 9.0);
1487 y = SINOID(0.023, 9.0);
1488 z = SINOID(0.017, 9.0);
1490 glTranslatef(x, y, z);
1493 if (mc->spin_x || mc->spin_y || mc->spin_z)
1498 if (x < 0) x = 1 - (x + 1);
1499 if (y < 0) y = 1 - (y + 1);
1500 if (z < 0) z = 1 - (z + 1);
1502 if (mc->spin_x) glRotatef(x * 360, 1.0, 0.0, 0.0);
1503 if (mc->spin_y) glRotatef(y * 360, 0.0, 1.0, 0.0);
1504 if (mc->spin_z) glRotatef(z * 360, 0.0, 0.0, 1.0);
1506 rotate(&mc->rotx, &mc->dx, &mc->ddx, mc->d_max);
1507 rotate(&mc->roty, &mc->dy, &mc->ddy, mc->d_max);
1508 rotate(&mc->rotz, &mc->dz, &mc->ddz, mc->d_max);
1512 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1513 glCallList (mc->molecule_dlist);
1516 if (mi->fps_p) do_fps (mi);
1519 glXSwapBuffers(dpy, window);