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",
970 typedef struct { char *atom; int count; } atom_and_count;
972 /* When listing the components of a molecule, the convention is to put the
973 carbon atoms first, the hydrogen atoms second, and the other atom types
974 sorted alphabetically after that (although for some molecules, the usual
975 order is different: we special-case a few of those.)
978 cmp_atoms (const void *aa, const void *bb)
980 const atom_and_count *a = (atom_and_count *) aa;
981 const atom_and_count *b = (atom_and_count *) bb;
982 if (!a->atom) return 1;
983 if (!b->atom) return -1;
984 if (!strcmp(a->atom, "C")) return -1;
985 if (!strcmp(b->atom, "C")) return 1;
986 if (!strcmp(a->atom, "H")) return -1;
987 if (!strcmp(b->atom, "H")) return 1;
988 return strcmp (a->atom, b->atom);
991 static void special_case_formula (char *f);
994 generate_molecule_formula (molecule *m)
996 char *buf = (char *) malloc (m->natoms * 10);
999 atom_and_count counts[200];
1000 memset (counts, 0, sizeof(counts));
1002 for (i = 0; i < m->natoms; i++)
1005 char *a = (char *) m->atoms[i].label;
1007 while (!isalpha(*a)) a++;
1009 for (e = a; isalpha(*e); e++);
1011 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1021 while (counts[i].atom) i++;
1022 qsort (counts, i, sizeof(*counts), cmp_atoms);
1025 while (counts[i].atom)
1027 strcat (s, counts[i].atom);
1028 free (counts[i].atom);
1030 if (counts[i].count > 1)
1031 sprintf (s, "(%d)", counts[i].count);
1036 special_case_formula (buf);
1038 if (!m->label) m->label = strdup("");
1039 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1040 strcpy (s, m->label);
1043 free ((char *) m->label);
1048 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1050 special_case_formula (char *f)
1052 if (!strcmp(f, "H(2)Be")) strcpy(f, "BeH(2)");
1053 else if (!strcmp(f, "H(3)B")) strcpy(f, "BH(3)");
1054 else if (!strcmp(f, "H(3)N")) strcpy(f, "NH(3)");
1055 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1056 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1057 else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
1058 else if (!strcmp(f, "Cl(3)P")) strcpy(f, "PCl(3)");
1059 else if (!strcmp(f, "Cl(5)P")) strcpy(f, "PCl(5)");
1064 insert_vertical_whitespace (char *string)
1068 if ((string[0] == ',' ||
1070 string[0] == ':') &&
1072 string[0] = ' ', string[1] = '\n';
1078 /* Construct the molecule data from either: the builtins; or from
1079 the (one) .pdb file specified with -molecule.
1082 load_molecules (ModeInfo *mi)
1084 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1085 int wire = MI_IS_WIREFRAME(mi);
1086 Bool verbose_p = False;
1089 if (!molecule_str || !*molecule_str ||
1090 !strcmp(molecule_str, "(default)")) /* do the builtins */
1092 mc->nmolecules = countof(builtin_pdb_data);
1093 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1094 for (i = 0; i < mc->nmolecules; i++)
1097 sprintf (name, "<builtin-%d>", i);
1098 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1099 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1102 else /* Load a file */
1104 /* The -molecule option can point to a .pdb file, or to
1105 a directory of them.
1112 if (!stat (molecule_str, &st) &&
1113 S_ISDIR (st.st_mode))
1117 struct dirent *dentry;
1119 pdb_dir = opendir (molecule_str);
1122 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1128 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1132 files = (char **) calloc (sizeof(*files), list_size);
1134 while ((dentry = readdir (pdb_dir)))
1136 int L = strlen (dentry->d_name);
1137 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1140 if (nfiles >= list_size-1)
1142 list_size = (list_size + 10) * 1.2;
1144 realloc (files, list_size * sizeof(*files));
1148 fprintf (stderr, "%s: out of memory (%d files)\n",
1154 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1156 strcpy (fn, molecule_str);
1157 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1158 strcat (fn, dentry->d_name);
1159 files[nfiles++] = fn;
1161 fprintf (stderr, "%s: file %s\n", progname, fn);
1168 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1169 progname, molecule_str);
1175 files = (char **) malloc (sizeof (*files));
1177 files[0] = strdup (molecule_str);
1179 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1182 mc->nmolecules = nfiles;
1183 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1184 for (i = 0; i < mc->nmolecules; i++)
1187 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1188 parse_pdb_file (&mc->molecules[i], files[i]);
1190 if ((wire || !do_atoms) &&
1192 mc->molecules[i].nbonds == 0)
1194 /* If we're not drawing atoms (e.g., wireframe mode), and
1195 there is no bond info, then make sure labels are turned on,
1196 or we'll be looking at a black screen... */
1197 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1198 progname, files[i]);
1210 for (i = 0; i < mc->nmolecules; i++)
1212 generate_molecule_formula (&mc->molecules[i]);
1213 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1219 /* Window management, etc
1222 reshape_molecule (ModeInfo *mi, int width, int height)
1224 GLfloat h = (GLfloat) height / (GLfloat) width;
1226 glViewport (0, 0, (GLint) width, (GLint) height);
1228 glMatrixMode(GL_PROJECTION);
1230 gluPerspective (30.0, 1/h, 20.0, 40.0);
1232 glMatrixMode(GL_MODELVIEW);
1234 gluLookAt( 0.0, 0.0, 30.0,
1238 glClear(GL_COLOR_BUFFER_BIT);
1243 gl_init (ModeInfo *mi)
1245 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1246 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1247 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1248 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1249 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1250 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1251 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1252 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1254 orig_do_labels = do_labels;
1255 orig_do_bonds = do_bonds;
1256 orig_wire = MI_IS_WIREFRAME(mi);
1261 startup_blurb (ModeInfo *mi)
1263 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1264 const char *s = "Constructing molecules...";
1265 print_title_string (mi, s,
1266 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1267 10 + mc->xfont2->ascent + mc->xfont2->descent,
1270 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1274 molecule_handle_event (ModeInfo *mi, XEvent *event)
1276 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1278 if (event->xany.type == ButtonPress &&
1279 event->xbutton.button & Button1)
1281 mc->button_down_p = True;
1282 gltrackball_start (mc->trackball,
1283 event->xbutton.x, event->xbutton.y,
1284 MI_WIDTH (mi), MI_HEIGHT (mi));
1287 else if (event->xany.type == ButtonRelease &&
1288 event->xbutton.button & Button1)
1290 mc->button_down_p = False;
1293 else if (event->xany.type == MotionNotify &&
1296 gltrackball_track (mc->trackball,
1297 event->xmotion.x, event->xmotion.y,
1298 MI_WIDTH (mi), MI_HEIGHT (mi));
1307 init_molecule (ModeInfo *mi)
1309 molecule_configuration *mc;
1313 mcs = (molecule_configuration *)
1314 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1316 fprintf(stderr, "%s: out of memory\n", progname);
1321 mc = &mcs[MI_SCREEN(mi)];
1323 if ((mc->glx_context = init_GL(mi)) != NULL) {
1325 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1331 wire = MI_IS_WIREFRAME(mi);
1334 Bool spinx=False, spiny=False, spinz=False;
1335 double spin_speed = 2.0;
1336 double wander_speed = 0.03;
1341 if (*s == 'x' || *s == 'X') spinx = True;
1342 else if (*s == 'y' || *s == 'Y') spiny = True;
1343 else if (*s == 'z' || *s == 'Z') spinz = True;
1347 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1354 mc->rot = make_rotator (spinx ? spin_speed : 0,
1355 spiny ? spin_speed : 0,
1356 spinz ? spin_speed : 0,
1358 do_wander ? wander_speed : 0,
1359 (spinx && spiny && spinz));
1360 mc->trackball = gltrackball_init ();
1363 mc->molecule_dlist = glGenLists(1);
1365 load_molecules (mi);
1366 mc->which = random() % mc->nmolecules;
1368 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1369 "NoLabelThreshold");
1370 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1371 "WireframeThreshold");
1378 /* Put the labels on the atoms.
1379 This can't be a part of the display list because of the games
1380 we play with the translation matrix.
1383 draw_labels (ModeInfo *mi)
1385 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1386 int wire = MI_IS_WIREFRAME(mi);
1387 molecule *m = &mc->molecules[mc->which];
1394 glDisable (GL_LIGHTING); /* don't light fonts */
1396 for (i = 0; i < m->natoms; i++)
1398 molecule_atom *a = &m->atoms[i];
1399 GLfloat size = atom_size (a);
1405 set_atom_color (mi, a, True);
1407 /* First, we translate the origin to the center of the atom.
1409 Then we retrieve the prevailing modelview matrix (which
1410 includes any rotation, wandering, and user-trackball-rolling
1413 We set the top 3x3 cells of that matrix to be the identity
1414 matrix. This removes all rotation from the matrix, while
1415 leaving the translation alone. This has the effect of
1416 leaving the prevailing coordinate system perpendicular to
1417 the camera view: were we to draw a square face, it would
1418 be in the plane of the screen.
1420 Now we translate by `size' toward the viewer -- so that the
1421 origin is *just in front* of the ball.
1423 Then we draw the label text, allowing the depth buffer to
1424 do its work: that way, labels on atoms will be occluded
1425 properly when other atoms move in front of them.
1427 This technique (of neutralizing rotation relative to the
1428 observer, after both rotations and translations have been
1429 applied) is known as "billboarding".
1432 glTranslatef(a->x, a->y, a->z); /* get matrix */
1433 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1434 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1435 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1436 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1437 glLoadIdentity(); /* reset modelview */
1438 glMultMatrixf (&m[0][0]); /* replace with ours */
1440 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1442 glRasterPos3f (0, 0, 0); /* draw text here */
1444 /* Before drawing the string, shift the origin to center
1445 the text over the origin of the sphere. */
1446 glBitmap (0, 0, 0, 0,
1447 -string_width (mc->xfont1, a->label) / 2,
1448 -mc->xfont1->descent,
1451 for (j = 0; j < strlen(a->label); j++)
1452 glCallList (mc->font1_dlist + (int)(a->label[j]));
1457 /* More efficient to always call glEnable() with correct values
1458 than to call glPushAttrib()/glPopAttrib(), since reading
1459 attributes from GL does a round-trip and stalls the pipeline.
1462 glEnable (GL_LIGHTING);
1467 draw_molecule (ModeInfo *mi)
1469 static time_t last = 0;
1470 time_t now = time ((time_t *) 0);
1472 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1473 Display *dpy = MI_DISPLAY(mi);
1474 Window window = MI_WINDOW(mi);
1476 if (!mc->glx_context)
1479 if (last + timeout <= now && /* randomize molecules every -timeout seconds */
1482 if (mc->nmolecules == 1)
1484 if (last != 0) goto SKIP;
1489 mc->which = random() % mc->nmolecules;
1494 while (n == mc->which)
1495 n = random() % mc->nmolecules;
1502 glNewList (mc->molecule_dlist, GL_COMPILE);
1503 ensure_bounding_box_visible (mi);
1505 do_labels = orig_do_labels;
1506 do_bonds = orig_do_bonds;
1507 MI_IS_WIREFRAME(mi) = orig_wire;
1509 if (mc->molecule_size > mc->no_label_threshold)
1511 if (mc->molecule_size > mc->wireframe_threshold)
1512 MI_IS_WIREFRAME(mi) = 1;
1514 if (MI_IS_WIREFRAME(mi))
1517 build_molecule (mi);
1523 glScalef(1.1, 1.1, 1.1);
1527 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1528 glTranslatef((x - 0.5) * 9,
1532 gltrackball_rotate (mc->trackball);
1534 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1535 glRotatef (x * 360, 1.0, 0.0, 0.0);
1536 glRotatef (y * 360, 0.0, 1.0, 0.0);
1537 glRotatef (z * 360, 0.0, 0.0, 1.0);
1540 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1541 glCallList (mc->molecule_dlist);
1546 if (mi->fps_p) do_fps (mi);
1549 glXSwapBuffers(dpy, window);