1 /* molecule, Copyright (c) 2001-2004 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
21 #include <sys/types.h>
25 #include <X11/Intrinsic.h>
27 #define PROGCLASS "Molecule"
28 #define HACK_INIT init_molecule
29 #define HACK_DRAW draw_molecule
30 #define HACK_RESHAPE reshape_molecule
31 #define HACK_HANDLE_EVENT molecule_handle_event
32 #define EVENT_MASK PointerMotionMask
33 #define molecule_opts xlockmore_opts
35 #define DEF_TIMEOUT "20"
36 #define DEF_SPIN "XYZ"
37 #define DEF_WANDER "False"
38 #define DEF_LABELS "True"
39 #define DEF_TITLES "True"
40 #define DEF_ATOMS "True"
41 #define DEF_BONDS "True"
42 #define DEF_BBOX "False"
43 #define DEF_MOLECULE "(default)"
45 #define DEFAULTS "*delay: 10000 \n" \
46 "*timeout: " DEF_TIMEOUT "\n" \
47 "*showFPS: False \n" \
48 "*wireframe: False \n" \
49 "*molecule: " DEF_MOLECULE "\n" \
50 "*spin: " DEF_SPIN "\n" \
51 "*wander: " DEF_WANDER "\n" \
52 "*labels: " DEF_LABELS "\n" \
53 "*atoms: " DEF_ATOMS "\n" \
54 "*bonds: " DEF_BONDS "\n" \
55 "*bbox: " DEF_BBOX "\n" \
56 "*atomFont: -*-times-bold-r-normal-*-240-*\n" \
57 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
58 "*noLabelThreshold: 30 \n" \
59 "*wireframeThreshold: 150 \n" \
63 #define countof(x) (sizeof((x))/sizeof((*x)))
65 #include "xlockmore.h"
70 #include "gltrackball.h"
72 #ifdef USE_GL /* whole file */
80 #define SPHERE_SLICES 16 /* how densely to render spheres */
81 #define SPHERE_STACKS 10
83 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
86 # define TUBE_FACES 12 /* how densely to render tubes */
91 static int scale_down;
92 #define SPHERE_SLICES_2 7
93 #define SPHERE_STACKS_2 4
94 #define TUBE_FACES_2 3
98 __extension__ /* don't warn about "string length is greater than the length
99 ISO C89 compilers are required to support" when includng
100 the following data file... */
102 const char * const builtin_pdb_data[] = {
103 # include "molecules.h"
111 const char *text_color;
116 /* These are the traditional colors used to render these atoms,
117 and their approximate size in angstroms.
119 static atom_data all_atom_data[] = {
120 { "H", 1.17, 0, "White", "Grey70", { 0, }},
121 { "C", 1.75, 0, "Grey60", "White", { 0, }},
122 { "CA", 1.80, 0, "Blue", "LightBlue", { 0, }},
123 { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }},
124 { "O", 1.40, 0, "Red", "LightPink", { 0, }},
125 { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }},
126 { "S", 1.80, 0, "Yellow4", "Yellow1", { 0, }},
127 { "bond", 0, 0, "Grey70", "Yellow1", { 0, }},
128 { "*", 1.40, 0, "Green4", "LightGreen", { 0, }}
133 int id; /* sequence number in the PDB file */
134 const char *label; /* The atom name */
135 GLfloat x, y, z; /* position in 3-space (angstroms) */
136 atom_data *data; /* computed: which style of atom this is */
140 int from, to; /* atom sequence numbers */
141 int strength; /* how many bonds are between these two atoms */
146 const char *label; /* description of this compound */
147 int natoms, atoms_size;
148 int nbonds, bonds_size;
149 molecule_atom *atoms;
150 molecule_bond *bonds;
155 GLXContext *glx_context;
157 trackball_state *trackball;
160 GLfloat molecule_size; /* max dimension of molecule bounding box */
162 GLfloat no_label_threshold; /* Things happen when molecules are huge */
163 GLfloat wireframe_threshold;
165 int which; /* which of the molecules is being shown */
169 GLuint molecule_dlist;
171 XFontStruct *xfont1, *xfont2;
172 GLuint font1_dlist, font2_dlist;
174 } molecule_configuration;
177 static molecule_configuration *mcs = NULL;
180 static char *molecule_str;
181 static char *do_spin;
182 static Bool do_wander;
183 static Bool do_titles;
184 static Bool do_labels;
185 static Bool do_atoms;
186 static Bool do_bonds;
189 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
192 static XrmOptionDescRec opts[] = {
193 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
194 { "-timeout",".timeout",XrmoptionSepArg, 0 },
195 { "-spin", ".spin", XrmoptionSepArg, 0 },
196 { "+spin", ".spin", XrmoptionNoArg, "" },
197 { "-wander", ".wander", XrmoptionNoArg, "True" },
198 { "+wander", ".wander", XrmoptionNoArg, "False" },
199 { "-labels", ".labels", XrmoptionNoArg, "True" },
200 { "+labels", ".labels", XrmoptionNoArg, "False" },
201 { "-titles", ".titles", XrmoptionNoArg, "True" },
202 { "+titles", ".titles", XrmoptionNoArg, "False" },
203 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
204 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
205 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
206 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
207 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
208 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
211 static argtype vars[] = {
212 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
213 {&timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
214 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
215 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
216 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
217 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
218 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
219 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
220 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
223 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
231 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
233 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
234 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
237 glTranslatef (x, y, z);
238 glScalef (diameter, diameter, diameter);
239 unit_sphere (stacks, slices, wire);
245 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
247 const char *font = get_string_resource (res, "Font");
252 if (!font) font = "-*-times-bold-r-normal-*-180-*";
254 f = XLoadQueryFont(mi->dpy, font);
255 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
258 first = f->min_char_or_byte2;
259 last = f->max_char_or_byte2;
262 *dlistP = glGenLists ((GLuint) last+1);
263 check_gl_error ("glGenLists");
264 glXUseXFont(id, first, last-first+1, *dlistP + first);
265 check_gl_error ("glXUseXFont");
272 load_fonts (ModeInfo *mi)
274 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
275 load_font (mi, "atomFont", &mc->xfont1, &mc->font1_dlist);
276 load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
281 string_width (XFontStruct *f, const char *c)
286 int cc = *((unsigned char *) c);
288 ? f->per_char[cc-f->min_char_or_byte2].rbearing
289 : f->min_bounds.rbearing);
297 get_atom_data (const char *atom_name)
301 char *n = strdup (atom_name);
305 while (!isalpha(*n)) n++;
307 while (L > 0 && !isalpha(n[L-1]))
310 for (i = 0; i < countof(all_atom_data); i++)
312 d = &all_atom_data[i];
313 if (!strcasecmp (n, all_atom_data[i].name))
323 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
332 static atom_data *def_data = 0;
333 if (!def_data) def_data = get_atom_data ("bond");
337 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
339 if (gl_color[3] == 0)
341 const char *string = !font_p ? d->color : d->text_color;
343 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
345 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
346 (a ? a->label : d->name), string);
350 gl_color[0] = xcolor.red / 65536.0;
351 gl_color[1] = xcolor.green / 65536.0;
352 gl_color[2] = xcolor.blue / 65536.0;
357 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
359 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
364 atom_size (molecule_atom *a)
368 if (a->data->size2 == 0)
370 /* let the molecules have the same relative sizes, but scale
371 them to a smaller range, so that the bond-tubes are
378 GLfloat ratio = (a->data->size - min) / (max - min);
379 a->data->size2 = bot + (ratio * (top - bot));
381 return a->data->size2;
384 return a->data->size;
388 static molecule_atom *
389 get_atom (molecule_atom *atoms, int natoms, int id)
393 /* quick short-circuit */
396 if (atoms[id].id == id)
398 if (id > 0 && atoms[id-1].id == id)
400 if (id < natoms-1 && atoms[id+1].id == id)
404 for (i = 0; i < natoms; i++)
405 if (id == atoms[i].id)
408 fprintf (stderr, "%s: no atom %d\n", progname, id);
414 molecule_bounding_box (ModeInfo *mi,
415 GLfloat *x1, GLfloat *y1, GLfloat *z1,
416 GLfloat *x2, GLfloat *y2, GLfloat *z2)
418 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
419 molecule *m = &mc->molecules[mc->which];
424 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
428 *x1 = *x2 = m->atoms[0].x;
429 *y1 = *y2 = m->atoms[0].y;
430 *z1 = *z2 = m->atoms[0].z;
433 for (i = 1; i < m->natoms; i++)
435 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
436 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
437 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
439 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
440 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
441 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
454 draw_bounding_box (ModeInfo *mi)
456 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
457 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
458 int wire = MI_IS_WIREFRAME(mi);
459 GLfloat x1, y1, z1, x2, y2, z2;
460 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
462 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
465 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
467 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
468 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
470 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
471 glNormal3f(0, -1, 0);
472 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
473 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
475 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
477 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
478 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
480 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
481 glNormal3f(0, 0, -1);
482 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
483 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
485 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
487 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
488 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
490 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
491 glNormal3f(-1, 0, 0);
492 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
493 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
496 glPushAttrib (GL_LIGHTING);
497 glDisable (GL_LIGHTING);
499 glColor3f (c2[0], c2[1], c2[2]);
501 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
502 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
503 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
504 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
505 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
506 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
513 /* Since PDB files don't always have the molecule centered around the
514 origin, and since some molecules are pretty large, scale and/or
515 translate so that the whole molecule is visible in the window.
518 ensure_bounding_box_visible (ModeInfo *mi)
520 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
522 GLfloat x1, y1, z1, x2, y2, z2;
525 GLfloat max_size = 10; /* don't bother scaling down if the molecule
526 is already smaller than this */
528 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
533 size = (w > h ? w : h);
534 size = (size > d ? size : d);
536 mc->molecule_size = size;
542 GLfloat scale = max_size / size;
543 glScalef (scale, scale, scale);
545 scale_down = scale < 0.3;
548 glTranslatef (-(x1 + w/2),
555 print_title_string (ModeInfo *mi, const char *string,
556 GLfloat x, GLfloat y, XFontStruct *font)
558 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
559 GLfloat line_height = font->ascent + font->descent;
560 GLfloat sub_shift = (line_height * 0.3);
564 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
565 GL_ENABLE_BIT); /* for various glDisable calls */
566 glDisable (GL_LIGHTING);
567 glDisable (GL_DEPTH_TEST);
569 glMatrixMode(GL_PROJECTION);
574 glMatrixMode(GL_MODELVIEW);
582 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
584 set_atom_color (mi, 0, True);
586 glRasterPos2f (x, y);
587 for (i = 0; i < strlen(string); i++)
592 glRasterPos2f (x, (y -= line_height));
595 else if (c == '(' && (isdigit (string[i+1])))
598 glRasterPos2f (x2, (y -= sub_shift));
600 else if (c == ')' && sub_p)
603 glRasterPos2f (x2, (y += sub_shift));
607 glCallList (mc->font2_dlist + (int)(c));
608 x2 += (font->per_char
609 ? font->per_char[c - font->min_char_or_byte2].width
610 : font->min_bounds.width);
616 glMatrixMode(GL_PROJECTION);
621 glMatrixMode(GL_MODELVIEW);
625 /* Constructs the GL shapes of the current molecule
628 build_molecule (ModeInfo *mi)
630 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
631 int wire = MI_IS_WIREFRAME(mi);
634 molecule *m = &mc->molecules[mc->which];
638 glDisable(GL_CULL_FACE);
639 glDisable(GL_LIGHTING);
640 glDisable(GL_LIGHT0);
641 glDisable(GL_DEPTH_TEST);
642 glDisable(GL_NORMALIZE);
643 glDisable(GL_CULL_FACE);
647 glEnable(GL_CULL_FACE);
648 glEnable(GL_LIGHTING);
650 glEnable(GL_DEPTH_TEST);
651 glEnable(GL_NORMALIZE);
652 glEnable(GL_CULL_FACE);
656 set_atom_color (mi, 0, False);
659 for (i = 0; i < m->nbonds; i++)
661 molecule_bond *b = &m->bonds[i];
662 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
663 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
668 glVertex3f(from->x, from->y, from->z);
669 glVertex3f(to->x, to->y, to->z);
674 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
680 GLfloat thickness = 0.07 * b->strength;
681 GLfloat cap_size = 0.03;
685 tube (from->x, from->y, from->z,
688 faces, smooth, False, wire);
692 if (!wire && do_atoms)
693 for (i = 0; i < m->natoms; i++)
695 molecule_atom *a = &m->atoms[i];
696 GLfloat size = atom_size (a);
697 set_atom_color (mi, a, False);
698 sphere (a->x, a->y, a->z, size, wire);
702 draw_bounding_box (mi);
704 if (do_titles && m->label && *m->label)
705 print_title_string (mi, m->label,
706 10, mi->xgwa.height - 10,
715 push_atom (molecule *m,
716 int id, const char *label,
717 GLfloat x, GLfloat y, GLfloat z)
720 if (m->atoms_size < m->natoms)
723 m->atoms = (molecule_atom *) realloc (m->atoms,
724 m->atoms_size * sizeof(*m->atoms));
726 m->atoms[m->natoms-1].id = id;
727 m->atoms[m->natoms-1].label = label;
728 m->atoms[m->natoms-1].x = x;
729 m->atoms[m->natoms-1].y = y;
730 m->atoms[m->natoms-1].z = z;
731 m->atoms[m->natoms-1].data = get_atom_data (label);
736 push_bond (molecule *m, int from, int to)
740 for (i = 0; i < m->nbonds; i++)
741 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
742 (m->bonds[i].to == from && m->bonds[i].from == to))
744 m->bonds[i].strength++;
749 if (m->bonds_size < m->nbonds)
752 m->bonds = (molecule_bond *) realloc (m->bonds,
753 m->bonds_size * sizeof(*m->bonds));
755 m->bonds[m->nbonds-1].from = from;
756 m->bonds[m->nbonds-1].to = to;
757 m->bonds[m->nbonds-1].strength = 1;
762 /* This function is crap.
765 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
767 const char *s = data;
771 if ((!m->label || !*m->label) &&
772 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
774 char *name = calloc (1, 100);
781 while (isspace(*n2)) n2++;
783 ss = strchr (n2, '\n');
785 ss = strchr (n2, '\r');
788 ss = n2+strlen(n2)-1;
789 while (isspace(*ss) && ss > n2)
792 if (strlen (n2) > 4 &&
793 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
794 n2[strlen(n2)-4] = 0;
796 if (m->label) free ((char *) m->label);
797 m->label = strdup (n2);
800 else if (!strncmp (s, "TITLE ", 6) ||
801 !strncmp (s, "HEADER", 6) ||
802 !strncmp (s, "COMPND", 6) ||
803 !strncmp (s, "AUTHOR", 6) ||
804 !strncmp (s, "REVDAT", 6) ||
805 !strncmp (s, "SOURCE", 6) ||
806 !strncmp (s, "EXPDTA", 6) ||
807 !strncmp (s, "JRNL ", 6) ||
808 !strncmp (s, "REMARK", 6) ||
809 !strncmp (s, "SEQRES", 6) ||
810 !strncmp (s, "HET ", 6) ||
811 !strncmp (s, "FORMUL", 6) ||
812 !strncmp (s, "CRYST1", 6) ||
813 !strncmp (s, "ORIGX1", 6) ||
814 !strncmp (s, "ORIGX2", 6) ||
815 !strncmp (s, "ORIGX3", 6) ||
816 !strncmp (s, "SCALE1", 6) ||
817 !strncmp (s, "SCALE2", 6) ||
818 !strncmp (s, "SCALE3", 6) ||
819 !strncmp (s, "MASTER", 6) ||
820 !strncmp (s, "KEYWDS", 6) ||
821 !strncmp (s, "DBREF ", 6) ||
822 !strncmp (s, "HETNAM", 6) ||
823 !strncmp (s, "HETSYN", 6) ||
824 !strncmp (s, "HELIX ", 6) ||
825 !strncmp (s, "LINK ", 6) ||
826 !strncmp (s, "MTRIX1", 6) ||
827 !strncmp (s, "MTRIX2", 6) ||
828 !strncmp (s, "MTRIX3", 6) ||
829 !strncmp (s, "SHEET ", 6) ||
830 !strncmp (s, "CISPEP", 6) ||
831 !strncmp (s, "GENERATED BY", 12) ||
832 !strncmp (s, "TER ", 4) ||
833 !strncmp (s, "END ", 4) ||
834 !strncmp (s, "TER\n", 4) ||
835 !strncmp (s, "END\n", 4) ||
836 !strncmp (s, "\n", 1))
839 else if (!strncmp (s, "ATOM ", 7))
842 char *name = (char *) calloc (1, 4);
843 GLfloat x = -999, y = -999, z = -999;
845 sscanf (s+7, " %d ", &id);
847 strncpy (name, s+12, 3);
848 while (isspace(*name)) name++;
849 ss = name + strlen(name)-1;
850 while (isspace(*ss) && ss > name)
858 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
860 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
861 progname, filename, line,
864 push_atom (m, id, name, x, y, z);
866 else if (!strncmp (s, "HETATM ", 7))
869 char *name = (char *) calloc (1, 4);
870 GLfloat x = -999, y = -999, z = -999;
872 sscanf (s+7, " %d ", &id);
874 strncpy (name, s+12, 3);
875 while (isspace(*name)) name++;
876 ss = name + strlen(name)-1;
877 while (isspace(*ss) && ss > name)
879 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
881 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
882 progname, filename, line,
885 push_atom (m, id, name, x, y, z);
887 else if (!strncmp (s, "CONECT ", 7))
890 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
891 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
892 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
893 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
895 for (j = 1; j < i; j++)
899 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
900 progname, filename, line, atoms[0], atoms[j]);
902 push_bond (m, atoms[0], atoms[j]);
907 char *s1 = strdup (s);
908 for (ss = s1; *ss && *ss != '\n'; ss++)
911 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
912 progname, filename, line, s1);
915 while (*s && *s != '\n')
925 parse_pdb_file (molecule *m, const char *name)
928 int buf_size = 40960;
932 in = fopen(name, "r");
935 char *buf = (char *) malloc(1024 + strlen(name));
936 sprintf(buf, "%s: error reading \"%s\"", progname, name);
941 buf = (char *) malloc (buf_size);
943 while (fgets (buf, buf_size-1, in))
946 for (s = buf; *s; s++)
947 if (*s == '\r') *s = '\n';
948 parse_pdb_data (m, buf, name, line++);
956 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
961 if (!m->nbonds && do_bonds)
963 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
972 typedef struct { char *atom; int count; } atom_and_count;
974 /* When listing the components of a molecule, the convention is to put the
975 carbon atoms first, the hydrogen atoms second, and the other atom types
976 sorted alphabetically after that (although for some molecules, the usual
977 order is different: we special-case a few of those.)
980 cmp_atoms (const void *aa, const void *bb)
982 const atom_and_count *a = (atom_and_count *) aa;
983 const atom_and_count *b = (atom_and_count *) bb;
984 if (!a->atom) return 1;
985 if (!b->atom) return -1;
986 if (!strcmp(a->atom, "C")) return -1;
987 if (!strcmp(b->atom, "C")) return 1;
988 if (!strcmp(a->atom, "H")) return -1;
989 if (!strcmp(b->atom, "H")) return 1;
990 return strcmp (a->atom, b->atom);
993 static void special_case_formula (char *f);
996 generate_molecule_formula (molecule *m)
998 char *buf = (char *) malloc (m->natoms * 10);
1001 atom_and_count counts[200];
1002 memset (counts, 0, sizeof(counts));
1004 for (i = 0; i < m->natoms; i++)
1007 char *a = (char *) m->atoms[i].label;
1009 while (!isalpha(*a)) a++;
1011 for (e = a; isalpha(*e); e++);
1013 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1023 while (counts[i].atom) i++;
1024 qsort (counts, i, sizeof(*counts), cmp_atoms);
1027 while (counts[i].atom)
1029 strcat (s, counts[i].atom);
1030 free (counts[i].atom);
1032 if (counts[i].count > 1)
1033 sprintf (s, "(%d)", counts[i].count);
1038 special_case_formula (buf);
1040 if (!m->label) m->label = strdup("");
1041 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1042 strcpy (s, m->label);
1045 free ((char *) m->label);
1050 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1052 special_case_formula (char *f)
1054 if (!strcmp(f, "H(2)Be")) strcpy(f, "BeH(2)");
1055 else if (!strcmp(f, "H(3)B")) strcpy(f, "BH(3)");
1056 else if (!strcmp(f, "H(3)N")) strcpy(f, "NH(3)");
1057 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1058 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1059 else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
1060 else if (!strcmp(f, "Cl(3)P")) strcpy(f, "PCl(3)");
1061 else if (!strcmp(f, "Cl(5)P")) strcpy(f, "PCl(5)");
1066 insert_vertical_whitespace (char *string)
1070 if ((string[0] == ',' ||
1072 string[0] == ':') &&
1074 string[0] = ' ', string[1] = '\n';
1080 /* Construct the molecule data from either: the builtins; or from
1081 the (one) .pdb file specified with -molecule.
1084 load_molecules (ModeInfo *mi)
1086 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1087 int wire = MI_IS_WIREFRAME(mi);
1088 Bool verbose_p = False;
1092 if (molecule_str && *molecule_str &&
1093 strcmp(molecule_str, "(default)")) /* try external PDB files */
1095 /* The -molecule option can point to a .pdb file, or to
1096 a directory of them.
1104 if (!stat (molecule_str, &st) &&
1105 S_ISDIR (st.st_mode))
1109 struct dirent *dentry;
1111 pdb_dir = opendir (molecule_str);
1114 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1120 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1124 files = (char **) calloc (sizeof(*files), list_size);
1126 while ((dentry = readdir (pdb_dir)))
1128 int L = strlen (dentry->d_name);
1129 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1132 if (nfiles >= list_size-1)
1134 list_size = (list_size + 10) * 1.2;
1136 realloc (files, list_size * sizeof(*files));
1140 fprintf (stderr, "%s: out of memory (%d files)\n",
1146 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1148 strcpy (fn, molecule_str);
1149 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1150 strcat (fn, dentry->d_name);
1151 files[nfiles++] = fn;
1153 fprintf (stderr, "%s: file %s\n", progname, fn);
1159 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1160 progname, molecule_str);
1164 files = (char **) malloc (sizeof (*files));
1166 files[0] = strdup (molecule_str);
1168 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1171 mc->nmolecules = nfiles;
1172 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1174 for (i = 0; i < mc->nmolecules; i++)
1177 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1178 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1180 if ((wire || !do_atoms) &&
1182 mc->molecules[molecule_ctr].nbonds == 0)
1184 /* If we're not drawing atoms (e.g., wireframe mode), and
1185 there is no bond info, then make sure labels are turned
1186 on, or we'll be looking at a black screen... */
1187 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1188 progname, files[i]);
1199 mc->nmolecules = molecule_ctr;
1202 if (mc->nmolecules == 0) /* do the builtins if no files */
1204 mc->nmolecules = countof(builtin_pdb_data);
1205 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1206 for (i = 0; i < mc->nmolecules; i++)
1209 sprintf (name, "<builtin-%d>", i);
1210 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1211 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1215 for (i = 0; i < mc->nmolecules; i++)
1217 generate_molecule_formula (&mc->molecules[i]);
1218 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1224 /* Window management, etc
1227 reshape_molecule (ModeInfo *mi, int width, int height)
1229 GLfloat h = (GLfloat) height / (GLfloat) width;
1231 glViewport (0, 0, (GLint) width, (GLint) height);
1233 glMatrixMode(GL_PROJECTION);
1235 gluPerspective (30.0, 1/h, 20.0, 40.0);
1237 glMatrixMode(GL_MODELVIEW);
1239 gluLookAt( 0.0, 0.0, 30.0,
1243 glClear(GL_COLOR_BUFFER_BIT);
1248 gl_init (ModeInfo *mi)
1250 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1251 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1252 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1253 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1254 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1255 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1256 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1257 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1259 orig_do_labels = do_labels;
1260 orig_do_bonds = do_bonds;
1261 orig_wire = MI_IS_WIREFRAME(mi);
1266 startup_blurb (ModeInfo *mi)
1268 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1269 const char *s = "Constructing molecules...";
1270 print_title_string (mi, s,
1271 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1272 10 + mc->xfont2->ascent + mc->xfont2->descent,
1275 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1279 molecule_handle_event (ModeInfo *mi, XEvent *event)
1281 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1283 if (event->xany.type == ButtonPress &&
1284 event->xbutton.button & Button1)
1286 mc->button_down_p = True;
1287 gltrackball_start (mc->trackball,
1288 event->xbutton.x, event->xbutton.y,
1289 MI_WIDTH (mi), MI_HEIGHT (mi));
1292 else if (event->xany.type == ButtonRelease &&
1293 event->xbutton.button & Button1)
1295 mc->button_down_p = False;
1298 else if (event->xany.type == MotionNotify &&
1301 gltrackball_track (mc->trackball,
1302 event->xmotion.x, event->xmotion.y,
1303 MI_WIDTH (mi), MI_HEIGHT (mi));
1312 init_molecule (ModeInfo *mi)
1314 molecule_configuration *mc;
1318 mcs = (molecule_configuration *)
1319 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1321 fprintf(stderr, "%s: out of memory\n", progname);
1326 mc = &mcs[MI_SCREEN(mi)];
1328 if ((mc->glx_context = init_GL(mi)) != NULL) {
1330 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1336 wire = MI_IS_WIREFRAME(mi);
1339 Bool spinx=False, spiny=False, spinz=False;
1340 double spin_speed = 2.0;
1341 double wander_speed = 0.03;
1346 if (*s == 'x' || *s == 'X') spinx = True;
1347 else if (*s == 'y' || *s == 'Y') spiny = True;
1348 else if (*s == 'z' || *s == 'Z') spinz = True;
1352 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1359 mc->rot = make_rotator (spinx ? spin_speed : 0,
1360 spiny ? spin_speed : 0,
1361 spinz ? spin_speed : 0,
1363 do_wander ? wander_speed : 0,
1364 (spinx && spiny && spinz));
1365 mc->trackball = gltrackball_init ();
1368 mc->molecule_dlist = glGenLists(1);
1370 load_molecules (mi);
1371 mc->which = random() % mc->nmolecules;
1373 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1374 "NoLabelThreshold");
1375 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1376 "WireframeThreshold");
1383 /* Put the labels on the atoms.
1384 This can't be a part of the display list because of the games
1385 we play with the translation matrix.
1388 draw_labels (ModeInfo *mi)
1390 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1391 int wire = MI_IS_WIREFRAME(mi);
1392 molecule *m = &mc->molecules[mc->which];
1399 glDisable (GL_LIGHTING); /* don't light fonts */
1401 for (i = 0; i < m->natoms; i++)
1403 molecule_atom *a = &m->atoms[i];
1404 GLfloat size = atom_size (a);
1410 set_atom_color (mi, a, True);
1412 /* First, we translate the origin to the center of the atom.
1414 Then we retrieve the prevailing modelview matrix (which
1415 includes any rotation, wandering, and user-trackball-rolling
1418 We set the top 3x3 cells of that matrix to be the identity
1419 matrix. This removes all rotation from the matrix, while
1420 leaving the translation alone. This has the effect of
1421 leaving the prevailing coordinate system perpendicular to
1422 the camera view: were we to draw a square face, it would
1423 be in the plane of the screen.
1425 Now we translate by `size' toward the viewer -- so that the
1426 origin is *just in front* of the ball.
1428 Then we draw the label text, allowing the depth buffer to
1429 do its work: that way, labels on atoms will be occluded
1430 properly when other atoms move in front of them.
1432 This technique (of neutralizing rotation relative to the
1433 observer, after both rotations and translations have been
1434 applied) is known as "billboarding".
1437 glTranslatef(a->x, a->y, a->z); /* get matrix */
1438 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1439 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1440 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1441 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1442 glLoadIdentity(); /* reset modelview */
1443 glMultMatrixf (&m[0][0]); /* replace with ours */
1445 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1447 glRasterPos3f (0, 0, 0); /* draw text here */
1449 /* Before drawing the string, shift the origin to center
1450 the text over the origin of the sphere. */
1451 glBitmap (0, 0, 0, 0,
1452 -string_width (mc->xfont1, a->label) / 2,
1453 -mc->xfont1->descent,
1456 for (j = 0; j < strlen(a->label); j++)
1457 glCallList (mc->font1_dlist + (int)(a->label[j]));
1462 /* More efficient to always call glEnable() with correct values
1463 than to call glPushAttrib()/glPopAttrib(), since reading
1464 attributes from GL does a round-trip and stalls the pipeline.
1467 glEnable (GL_LIGHTING);
1472 draw_molecule (ModeInfo *mi)
1474 static time_t last = 0;
1475 time_t now = time ((time_t *) 0);
1477 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1478 Display *dpy = MI_DISPLAY(mi);
1479 Window window = MI_WINDOW(mi);
1481 if (!mc->glx_context)
1484 if (last + timeout <= now && /* randomize molecules every -timeout seconds */
1487 if (mc->nmolecules == 1)
1489 if (last != 0) goto SKIP;
1494 mc->which = random() % mc->nmolecules;
1499 while (n == mc->which)
1500 n = random() % mc->nmolecules;
1507 glNewList (mc->molecule_dlist, GL_COMPILE);
1508 ensure_bounding_box_visible (mi);
1510 do_labels = orig_do_labels;
1511 do_bonds = orig_do_bonds;
1512 MI_IS_WIREFRAME(mi) = orig_wire;
1514 if (mc->molecule_size > mc->no_label_threshold)
1516 if (mc->molecule_size > mc->wireframe_threshold)
1517 MI_IS_WIREFRAME(mi) = 1;
1519 if (MI_IS_WIREFRAME(mi))
1522 build_molecule (mi);
1528 glScalef(1.1, 1.1, 1.1);
1532 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1533 glTranslatef((x - 0.5) * 9,
1537 gltrackball_rotate (mc->trackball);
1539 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1540 glRotatef (x * 360, 1.0, 0.0, 0.0);
1541 glRotatef (y * 360, 0.0, 1.0, 0.0);
1542 glRotatef (z * 360, 0.0, 0.0, 1.0);
1545 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1546 glCallList (mc->molecule_dlist);
1551 if (mi->fps_p) do_fps (mi);
1554 glXSwapBuffers(dpy, window);