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)"
44 #define DEF_VERBOSE "False"
46 #define DEFAULTS "*delay: 10000 \n" \
47 "*timeout: " DEF_TIMEOUT "\n" \
48 "*showFPS: False \n" \
49 "*wireframe: False \n" \
50 "*verbose: " DEF_VERBOSE "\n" \
51 "*molecule: " DEF_MOLECULE "\n" \
52 "*spin: " DEF_SPIN "\n" \
53 "*wander: " DEF_WANDER "\n" \
54 "*labels: " DEF_LABELS "\n" \
55 "*atoms: " DEF_ATOMS "\n" \
56 "*bonds: " DEF_BONDS "\n" \
57 "*bbox: " DEF_BBOX "\n" \
58 "*atomFont: -*-times-bold-r-normal-*-240-*\n" \
59 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
60 "*noLabelThreshold: 30 \n" \
61 "*wireframeThreshold: 150 \n" \
65 #define countof(x) (sizeof((x))/sizeof((*x)))
67 #include "xlockmore.h"
72 #include "gltrackball.h"
74 #ifdef USE_GL /* whole file */
82 #define SPHERE_SLICES 24 /* how densely to render spheres */
83 #define SPHERE_STACKS 12
85 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
88 # define TUBE_FACES 12 /* how densely to render tubes */
93 static int scale_down;
94 #define SPHERE_SLICES_2 7
95 #define SPHERE_STACKS_2 4
96 #define TUBE_FACES_2 3
100 __extension__ /* don't warn about "string length is greater than the length
101 ISO C89 compilers are required to support" when includng
102 the following data file... */
104 const char * const builtin_pdb_data[] = {
105 # include "molecules.h"
113 const char *text_color;
118 /* These are the traditional colors used to render these atoms,
119 and their approximate size in angstroms.
121 static atom_data all_atom_data[] = {
122 { "H", 1.17, 0, "White", "Grey70", { 0, }},
123 { "C", 1.75, 0, "Grey60", "White", { 0, }},
124 { "CA", 1.80, 0, "Blue", "LightBlue", { 0, }},
125 { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }},
126 { "O", 1.40, 0, "Red", "LightPink", { 0, }},
127 { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }},
128 { "S", 1.80, 0, "Yellow4", "Yellow1", { 0, }},
129 { "bond", 0, 0, "Grey70", "Yellow1", { 0, }},
130 { "*", 1.40, 0, "Green4", "LightGreen", { 0, }}
135 int id; /* sequence number in the PDB file */
136 const char *label; /* The atom name */
137 GLfloat x, y, z; /* position in 3-space (angstroms) */
138 atom_data *data; /* computed: which style of atom this is */
142 int from, to; /* atom sequence numbers */
143 int strength; /* how many bonds are between these two atoms */
148 const char *label; /* description of this compound */
149 int natoms, atoms_size;
150 int nbonds, bonds_size;
151 molecule_atom *atoms;
152 molecule_bond *bonds;
157 GLXContext *glx_context;
159 trackball_state *trackball;
162 GLfloat molecule_size; /* max dimension of molecule bounding box */
164 GLfloat no_label_threshold; /* Things happen when molecules are huge */
165 GLfloat wireframe_threshold;
167 int which; /* which of the molecules is being shown */
171 int mode; /* 0 = normal, 1 = out, 2 = in */
174 GLuint molecule_dlist;
176 XFontStruct *xfont1, *xfont2;
177 GLuint font1_dlist, font2_dlist;
179 } molecule_configuration;
182 static molecule_configuration *mcs = NULL;
185 static char *molecule_str;
186 static char *do_spin;
187 static Bool do_wander;
188 static Bool do_titles;
189 static Bool do_labels;
190 static Bool do_atoms;
191 static Bool do_bonds;
193 static Bool verbose_p;
195 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
198 static XrmOptionDescRec opts[] = {
199 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
200 { "-timeout",".timeout",XrmoptionSepArg, 0 },
201 { "-spin", ".spin", XrmoptionSepArg, 0 },
202 { "+spin", ".spin", XrmoptionNoArg, "" },
203 { "-wander", ".wander", XrmoptionNoArg, "True" },
204 { "+wander", ".wander", XrmoptionNoArg, "False" },
205 { "-labels", ".labels", XrmoptionNoArg, "True" },
206 { "+labels", ".labels", XrmoptionNoArg, "False" },
207 { "-titles", ".titles", XrmoptionNoArg, "True" },
208 { "+titles", ".titles", XrmoptionNoArg, "False" },
209 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
210 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
211 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
212 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
213 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
214 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
215 { "-verbose",".verbose",XrmoptionNoArg, "True" },
218 static argtype vars[] = {
219 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
220 {&timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
221 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
222 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
223 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
224 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
225 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
226 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
227 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
228 {&verbose_p, "verbose","Verbose",DEF_VERBOSE,t_Bool},
231 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
239 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
241 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
242 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
245 glTranslatef (x, y, z);
246 glScalef (diameter, diameter, diameter);
247 unit_sphere (stacks, slices, wire);
253 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
255 const char *font = get_string_resource (res, "Font");
260 if (!font) font = "-*-times-bold-r-normal-*-180-*";
262 f = XLoadQueryFont(mi->dpy, font);
263 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
266 first = f->min_char_or_byte2;
267 last = f->max_char_or_byte2;
270 *dlistP = glGenLists ((GLuint) last+1);
271 check_gl_error ("glGenLists");
272 glXUseXFont(id, first, last-first+1, *dlistP + first);
273 check_gl_error ("glXUseXFont");
280 load_fonts (ModeInfo *mi)
282 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
283 load_font (mi, "atomFont", &mc->xfont1, &mc->font1_dlist);
284 load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
289 string_width (XFontStruct *f, const char *c)
294 int cc = *((unsigned char *) c);
296 ? f->per_char[cc-f->min_char_or_byte2].rbearing
297 : f->min_bounds.rbearing);
305 get_atom_data (const char *atom_name)
309 char *n = strdup (atom_name);
313 while (!isalpha(*n)) n++;
315 while (L > 0 && !isalpha(n[L-1]))
318 for (i = 0; i < countof(all_atom_data); i++)
320 d = &all_atom_data[i];
321 if (!strcasecmp (n, all_atom_data[i].name))
331 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
340 static atom_data *def_data = 0;
341 if (!def_data) def_data = get_atom_data ("bond");
345 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
347 if (gl_color[3] == 0)
349 const char *string = !font_p ? d->color : d->text_color;
351 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
353 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
354 (a ? a->label : d->name), string);
358 gl_color[0] = xcolor.red / 65536.0;
359 gl_color[1] = xcolor.green / 65536.0;
360 gl_color[2] = xcolor.blue / 65536.0;
365 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
367 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
372 atom_size (molecule_atom *a)
376 if (a->data->size2 == 0)
378 /* let the molecules have the same relative sizes, but scale
379 them to a smaller range, so that the bond-tubes are
386 GLfloat ratio = (a->data->size - min) / (max - min);
387 a->data->size2 = bot + (ratio * (top - bot));
389 return a->data->size2;
392 return a->data->size;
396 static molecule_atom *
397 get_atom (molecule_atom *atoms, int natoms, int id)
401 /* quick short-circuit */
404 if (atoms[id].id == id)
406 if (id > 0 && atoms[id-1].id == id)
408 if (id < natoms-1 && atoms[id+1].id == id)
412 for (i = 0; i < natoms; i++)
413 if (id == atoms[i].id)
416 fprintf (stderr, "%s: no atom %d\n", progname, id);
422 molecule_bounding_box (ModeInfo *mi,
423 GLfloat *x1, GLfloat *y1, GLfloat *z1,
424 GLfloat *x2, GLfloat *y2, GLfloat *z2)
426 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
427 molecule *m = &mc->molecules[mc->which];
432 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
436 *x1 = *x2 = m->atoms[0].x;
437 *y1 = *y2 = m->atoms[0].y;
438 *z1 = *z2 = m->atoms[0].z;
441 for (i = 1; i < m->natoms; i++)
443 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
444 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
445 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
447 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
448 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
449 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
462 draw_bounding_box (ModeInfo *mi)
464 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
465 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
466 int wire = MI_IS_WIREFRAME(mi);
467 GLfloat x1, y1, z1, x2, y2, z2;
468 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
470 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
473 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
475 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
476 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
478 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
479 glNormal3f(0, -1, 0);
480 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
481 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
483 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
485 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
486 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
488 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
489 glNormal3f(0, 0, -1);
490 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
491 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
493 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
495 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
496 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
498 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
499 glNormal3f(-1, 0, 0);
500 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
501 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
504 glPushAttrib (GL_LIGHTING);
505 glDisable (GL_LIGHTING);
507 glColor3f (c2[0], c2[1], c2[2]);
509 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
510 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
511 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
512 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
513 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
514 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
521 /* Since PDB files don't always have the molecule centered around the
522 origin, and since some molecules are pretty large, scale and/or
523 translate so that the whole molecule is visible in the window.
526 ensure_bounding_box_visible (ModeInfo *mi)
528 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
530 GLfloat x1, y1, z1, x2, y2, z2;
533 GLfloat max_size = 10; /* don't bother scaling down if the molecule
534 is already smaller than this */
536 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
541 size = (w > h ? w : h);
542 size = (size > d ? size : d);
544 mc->molecule_size = size;
550 GLfloat scale = max_size / size;
551 glScalef (scale, scale, scale);
553 scale_down = scale < 0.3;
556 glTranslatef (-(x1 + w/2),
563 print_title_string (ModeInfo *mi, const char *string,
564 GLfloat x, GLfloat y, XFontStruct *font)
566 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
567 GLfloat line_height = font->ascent + font->descent;
568 GLfloat sub_shift = (line_height * 0.3);
572 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
573 GL_ENABLE_BIT); /* for various glDisable calls */
574 glDisable (GL_LIGHTING);
575 glDisable (GL_DEPTH_TEST);
577 glMatrixMode(GL_PROJECTION);
582 glMatrixMode(GL_MODELVIEW);
590 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
592 set_atom_color (mi, 0, True);
594 glRasterPos2f (x, y);
595 for (i = 0; i < strlen(string); i++)
600 glRasterPos2f (x, (y -= line_height));
603 else if (c == '(' && (isdigit (string[i+1])))
606 glRasterPos2f (x2, (y -= sub_shift));
608 else if (c == ')' && sub_p)
611 glRasterPos2f (x2, (y += sub_shift));
615 glCallList (mc->font2_dlist + (int)(c));
616 x2 += (font->per_char
617 ? font->per_char[c - font->min_char_or_byte2].width
618 : font->min_bounds.width);
624 glMatrixMode(GL_PROJECTION);
629 glMatrixMode(GL_MODELVIEW);
633 /* Constructs the GL shapes of the current molecule
636 build_molecule (ModeInfo *mi)
638 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
639 int wire = MI_IS_WIREFRAME(mi);
642 molecule *m = &mc->molecules[mc->which];
646 glDisable(GL_CULL_FACE);
647 glDisable(GL_LIGHTING);
648 glDisable(GL_LIGHT0);
649 glDisable(GL_DEPTH_TEST);
650 glDisable(GL_NORMALIZE);
651 glDisable(GL_CULL_FACE);
655 glEnable(GL_CULL_FACE);
656 glEnable(GL_LIGHTING);
658 glEnable(GL_DEPTH_TEST);
659 glEnable(GL_NORMALIZE);
660 glEnable(GL_CULL_FACE);
664 set_atom_color (mi, 0, False);
667 for (i = 0; i < m->nbonds; i++)
669 molecule_bond *b = &m->bonds[i];
670 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
671 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
676 glVertex3f(from->x, from->y, from->z);
677 glVertex3f(to->x, to->y, to->z);
682 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
688 GLfloat thickness = 0.07 * b->strength;
689 GLfloat cap_size = 0.03;
693 tube (from->x, from->y, from->z,
696 faces, smooth, False, wire);
700 if (!wire && do_atoms)
701 for (i = 0; i < m->natoms; i++)
703 molecule_atom *a = &m->atoms[i];
704 GLfloat size = atom_size (a);
705 set_atom_color (mi, a, False);
706 sphere (a->x, a->y, a->z, size, wire);
710 draw_bounding_box (mi);
712 if (do_titles && m->label && *m->label)
713 print_title_string (mi, m->label,
714 10, mi->xgwa.height - 10,
723 push_atom (molecule *m,
724 int id, const char *label,
725 GLfloat x, GLfloat y, GLfloat z)
728 if (m->atoms_size < m->natoms)
731 m->atoms = (molecule_atom *) realloc (m->atoms,
732 m->atoms_size * sizeof(*m->atoms));
734 m->atoms[m->natoms-1].id = id;
735 m->atoms[m->natoms-1].label = label;
736 m->atoms[m->natoms-1].x = x;
737 m->atoms[m->natoms-1].y = y;
738 m->atoms[m->natoms-1].z = z;
739 m->atoms[m->natoms-1].data = get_atom_data (label);
744 push_bond (molecule *m, int from, int to)
748 for (i = 0; i < m->nbonds; i++)
749 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
750 (m->bonds[i].to == from && m->bonds[i].from == to))
752 m->bonds[i].strength++;
757 if (m->bonds_size < m->nbonds)
760 m->bonds = (molecule_bond *) realloc (m->bonds,
761 m->bonds_size * sizeof(*m->bonds));
763 m->bonds[m->nbonds-1].from = from;
764 m->bonds[m->nbonds-1].to = to;
765 m->bonds[m->nbonds-1].strength = 1;
770 /* This function is crap.
773 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
775 const char *s = data;
779 if ((!m->label || !*m->label) &&
780 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
782 char *name = calloc (1, 100);
789 while (isspace(*n2)) n2++;
791 ss = strchr (n2, '\n');
793 ss = strchr (n2, '\r');
796 ss = n2+strlen(n2)-1;
797 while (isspace(*ss) && ss > n2)
800 if (strlen (n2) > 4 &&
801 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
802 n2[strlen(n2)-4] = 0;
804 if (m->label) free ((char *) m->label);
805 m->label = strdup (n2);
808 else if (!strncmp (s, "TITLE ", 6) ||
809 !strncmp (s, "HEADER", 6) ||
810 !strncmp (s, "COMPND", 6) ||
811 !strncmp (s, "AUTHOR", 6) ||
812 !strncmp (s, "REVDAT", 6) ||
813 !strncmp (s, "SOURCE", 6) ||
814 !strncmp (s, "EXPDTA", 6) ||
815 !strncmp (s, "JRNL ", 6) ||
816 !strncmp (s, "REMARK", 6) ||
817 !strncmp (s, "SEQRES", 6) ||
818 !strncmp (s, "HET ", 6) ||
819 !strncmp (s, "FORMUL", 6) ||
820 !strncmp (s, "CRYST1", 6) ||
821 !strncmp (s, "ORIGX1", 6) ||
822 !strncmp (s, "ORIGX2", 6) ||
823 !strncmp (s, "ORIGX3", 6) ||
824 !strncmp (s, "SCALE1", 6) ||
825 !strncmp (s, "SCALE2", 6) ||
826 !strncmp (s, "SCALE3", 6) ||
827 !strncmp (s, "MASTER", 6) ||
828 !strncmp (s, "KEYWDS", 6) ||
829 !strncmp (s, "DBREF ", 6) ||
830 !strncmp (s, "HETNAM", 6) ||
831 !strncmp (s, "HETSYN", 6) ||
832 !strncmp (s, "HELIX ", 6) ||
833 !strncmp (s, "LINK ", 6) ||
834 !strncmp (s, "MTRIX1", 6) ||
835 !strncmp (s, "MTRIX2", 6) ||
836 !strncmp (s, "MTRIX3", 6) ||
837 !strncmp (s, "SHEET ", 6) ||
838 !strncmp (s, "CISPEP", 6) ||
839 !strncmp (s, "GENERATED BY", 12) ||
840 !strncmp (s, "TER ", 4) ||
841 !strncmp (s, "END ", 4) ||
842 !strncmp (s, "TER\n", 4) ||
843 !strncmp (s, "END\n", 4) ||
844 !strncmp (s, "\n", 1))
847 else if (!strncmp (s, "ATOM ", 7))
850 char *name = (char *) calloc (1, 4);
851 GLfloat x = -999, y = -999, z = -999;
853 sscanf (s+7, " %d ", &id);
855 strncpy (name, s+12, 3);
856 while (isspace(*name)) name++;
857 ss = name + strlen(name)-1;
858 while (isspace(*ss) && ss > name)
866 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
868 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
869 progname, filename, line,
872 push_atom (m, id, name, x, y, z);
874 else if (!strncmp (s, "HETATM ", 7))
877 char *name = (char *) calloc (1, 4);
878 GLfloat x = -999, y = -999, z = -999;
880 sscanf (s+7, " %d ", &id);
882 strncpy (name, s+12, 3);
883 while (isspace(*name)) name++;
884 ss = name + strlen(name)-1;
885 while (isspace(*ss) && ss > name)
887 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
889 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
890 progname, filename, line,
893 push_atom (m, id, name, x, y, z);
895 else if (!strncmp (s, "CONECT ", 7))
898 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
899 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
900 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
901 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
903 for (j = 1; j < i; j++)
907 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
908 progname, filename, line, atoms[0], atoms[j]);
910 push_bond (m, atoms[0], atoms[j]);
915 char *s1 = strdup (s);
916 for (ss = s1; *ss && *ss != '\n'; ss++)
919 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
920 progname, filename, line, s1);
923 while (*s && *s != '\n')
933 parse_pdb_file (molecule *m, const char *name)
936 int buf_size = 40960;
940 in = fopen(name, "r");
943 char *buf = (char *) malloc(1024 + strlen(name));
944 sprintf(buf, "%s: error reading \"%s\"", progname, name);
949 buf = (char *) malloc (buf_size);
951 while (fgets (buf, buf_size-1, in))
954 for (s = buf; *s; s++)
955 if (*s == '\r') *s = '\n';
956 parse_pdb_data (m, buf, name, line++);
964 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
969 if (!m->nbonds && do_bonds)
971 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
980 typedef struct { char *atom; int count; } atom_and_count;
982 /* When listing the components of a molecule, the convention is to put the
983 carbon atoms first, the hydrogen atoms second, and the other atom types
984 sorted alphabetically after that (although for some molecules, the usual
985 order is different: we special-case a few of those.)
988 cmp_atoms (const void *aa, const void *bb)
990 const atom_and_count *a = (atom_and_count *) aa;
991 const atom_and_count *b = (atom_and_count *) bb;
992 if (!a->atom) return 1;
993 if (!b->atom) return -1;
994 if (!strcmp(a->atom, "C")) return -1;
995 if (!strcmp(b->atom, "C")) return 1;
996 if (!strcmp(a->atom, "H")) return -1;
997 if (!strcmp(b->atom, "H")) return 1;
998 return strcmp (a->atom, b->atom);
1001 static void special_case_formula (char *f);
1004 generate_molecule_formula (molecule *m)
1006 char *buf = (char *) malloc (m->natoms * 10);
1009 atom_and_count counts[200];
1010 memset (counts, 0, sizeof(counts));
1012 for (i = 0; i < m->natoms; i++)
1015 char *a = (char *) m->atoms[i].label;
1017 while (!isalpha(*a)) a++;
1019 for (e = a; isalpha(*e); e++);
1021 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1031 while (counts[i].atom) i++;
1032 qsort (counts, i, sizeof(*counts), cmp_atoms);
1035 while (counts[i].atom)
1037 strcat (s, counts[i].atom);
1038 free (counts[i].atom);
1040 if (counts[i].count > 1)
1041 sprintf (s, "(%d)", counts[i].count);
1046 special_case_formula (buf);
1048 if (!m->label) m->label = strdup("");
1049 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1050 strcpy (s, m->label);
1053 free ((char *) m->label);
1058 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1060 special_case_formula (char *f)
1062 if (!strcmp(f, "H(2)Be")) strcpy(f, "BeH(2)");
1063 else if (!strcmp(f, "H(3)B")) strcpy(f, "BH(3)");
1064 else if (!strcmp(f, "H(3)N")) strcpy(f, "NH(3)");
1065 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1066 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1067 else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
1068 else if (!strcmp(f, "Cl(3)P")) strcpy(f, "PCl(3)");
1069 else if (!strcmp(f, "Cl(5)P")) strcpy(f, "PCl(5)");
1074 insert_vertical_whitespace (char *string)
1078 if ((string[0] == ',' ||
1080 string[0] == ':') &&
1082 string[0] = ' ', string[1] = '\n';
1088 /* Construct the molecule data from either: the builtins; or from
1089 the (one) .pdb file specified with -molecule.
1092 load_molecules (ModeInfo *mi)
1094 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1095 int wire = MI_IS_WIREFRAME(mi);
1099 if (molecule_str && *molecule_str &&
1100 strcmp(molecule_str, "(default)")) /* try external PDB files */
1102 /* The -molecule option can point to a .pdb file, or to
1103 a directory of them.
1111 if (!stat (molecule_str, &st) &&
1112 S_ISDIR (st.st_mode))
1116 struct dirent *dentry;
1118 pdb_dir = opendir (molecule_str);
1121 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1127 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1131 files = (char **) calloc (sizeof(*files), list_size);
1133 while ((dentry = readdir (pdb_dir)))
1135 int L = strlen (dentry->d_name);
1136 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1139 if (nfiles >= list_size-1)
1141 list_size = (list_size + 10) * 1.2;
1143 realloc (files, list_size * sizeof(*files));
1147 fprintf (stderr, "%s: out of memory (%d files)\n",
1153 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1155 strcpy (fn, molecule_str);
1156 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1157 strcat (fn, dentry->d_name);
1158 files[nfiles++] = fn;
1160 fprintf (stderr, "%s: file %s\n", progname, fn);
1166 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1167 progname, molecule_str);
1171 files = (char **) malloc (sizeof (*files));
1173 files[0] = strdup (molecule_str);
1175 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1178 mc->nmolecules = nfiles;
1179 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1181 for (i = 0; i < mc->nmolecules; i++)
1184 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1185 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1187 if ((wire || !do_atoms) &&
1189 mc->molecules[molecule_ctr].nbonds == 0)
1191 /* If we're not drawing atoms (e.g., wireframe mode), and
1192 there is no bond info, then make sure labels are turned
1193 on, or we'll be looking at a black screen... */
1194 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1195 progname, files[i]);
1206 mc->nmolecules = molecule_ctr;
1209 if (mc->nmolecules == 0) /* do the builtins if no files */
1211 mc->nmolecules = countof(builtin_pdb_data);
1212 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1213 for (i = 0; i < mc->nmolecules; i++)
1216 sprintf (name, "<builtin-%d>", i);
1217 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1218 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1222 for (i = 0; i < mc->nmolecules; i++)
1224 generate_molecule_formula (&mc->molecules[i]);
1225 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1231 /* Window management, etc
1234 reshape_molecule (ModeInfo *mi, int width, int height)
1236 GLfloat h = (GLfloat) height / (GLfloat) width;
1238 glViewport (0, 0, (GLint) width, (GLint) height);
1240 glMatrixMode(GL_PROJECTION);
1242 gluPerspective (30.0, 1/h, 20.0, 40.0);
1244 glMatrixMode(GL_MODELVIEW);
1246 gluLookAt( 0.0, 0.0, 30.0,
1250 glClear(GL_COLOR_BUFFER_BIT);
1255 gl_init (ModeInfo *mi)
1257 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1258 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1259 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1260 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1261 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1262 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1263 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1264 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1266 orig_do_labels = do_labels;
1267 orig_do_bonds = do_bonds;
1268 orig_wire = MI_IS_WIREFRAME(mi);
1273 startup_blurb (ModeInfo *mi)
1275 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1276 const char *s = "Constructing molecules...";
1277 print_title_string (mi, s,
1278 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1279 10 + mc->xfont2->ascent + mc->xfont2->descent,
1282 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1286 molecule_handle_event (ModeInfo *mi, XEvent *event)
1288 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1290 if (event->xany.type == ButtonPress &&
1291 event->xbutton.button & Button1)
1293 mc->button_down_p = True;
1294 gltrackball_start (mc->trackball,
1295 event->xbutton.x, event->xbutton.y,
1296 MI_WIDTH (mi), MI_HEIGHT (mi));
1299 else if (event->xany.type == ButtonRelease &&
1300 event->xbutton.button & Button1)
1302 mc->button_down_p = False;
1305 else if (event->xany.type == MotionNotify &&
1308 gltrackball_track (mc->trackball,
1309 event->xmotion.x, event->xmotion.y,
1310 MI_WIDTH (mi), MI_HEIGHT (mi));
1319 init_molecule (ModeInfo *mi)
1321 molecule_configuration *mc;
1325 mcs = (molecule_configuration *)
1326 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1328 fprintf(stderr, "%s: out of memory\n", progname);
1333 mc = &mcs[MI_SCREEN(mi)];
1335 if ((mc->glx_context = init_GL(mi)) != NULL) {
1337 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1343 wire = MI_IS_WIREFRAME(mi);
1346 Bool spinx=False, spiny=False, spinz=False;
1347 double spin_speed = 0.5;
1348 double spin_accel = 0.3;
1349 double wander_speed = 0.01;
1354 if (*s == 'x' || *s == 'X') spinx = True;
1355 else if (*s == 'y' || *s == 'Y') spiny = True;
1356 else if (*s == 'z' || *s == 'Z') spinz = True;
1360 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1367 mc->rot = make_rotator (spinx ? spin_speed : 0,
1368 spiny ? spin_speed : 0,
1369 spinz ? spin_speed : 0,
1371 do_wander ? wander_speed : 0,
1372 (spinx && spiny && spinz));
1373 mc->trackball = gltrackball_init ();
1376 mc->molecule_dlist = glGenLists(1);
1378 load_molecules (mi);
1379 mc->which = random() % mc->nmolecules;
1381 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1382 "NoLabelThreshold");
1383 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1384 "WireframeThreshold");
1392 /* Put the labels on the atoms.
1393 This can't be a part of the display list because of the games
1394 we play with the translation matrix.
1397 draw_labels (ModeInfo *mi)
1399 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1400 int wire = MI_IS_WIREFRAME(mi);
1401 molecule *m = &mc->molecules[mc->which];
1408 glDisable (GL_LIGHTING); /* don't light fonts */
1410 for (i = 0; i < m->natoms; i++)
1412 molecule_atom *a = &m->atoms[i];
1413 GLfloat size = atom_size (a);
1419 set_atom_color (mi, a, True);
1421 /* First, we translate the origin to the center of the atom.
1423 Then we retrieve the prevailing modelview matrix (which
1424 includes any rotation, wandering, and user-trackball-rolling
1427 We set the top 3x3 cells of that matrix to be the identity
1428 matrix. This removes all rotation from the matrix, while
1429 leaving the translation alone. This has the effect of
1430 leaving the prevailing coordinate system perpendicular to
1431 the camera view: were we to draw a square face, it would
1432 be in the plane of the screen.
1434 Now we translate by `size' toward the viewer -- so that the
1435 origin is *just in front* of the ball.
1437 Then we draw the label text, allowing the depth buffer to
1438 do its work: that way, labels on atoms will be occluded
1439 properly when other atoms move in front of them.
1441 This technique (of neutralizing rotation relative to the
1442 observer, after both rotations and translations have been
1443 applied) is known as "billboarding".
1446 glTranslatef(a->x, a->y, a->z); /* get matrix */
1447 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1448 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1449 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1450 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1451 glLoadIdentity(); /* reset modelview */
1452 glMultMatrixf (&m[0][0]); /* replace with ours */
1454 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1456 glRasterPos3f (0, 0, 0); /* draw text here */
1458 /* Before drawing the string, shift the origin to center
1459 the text over the origin of the sphere. */
1460 glBitmap (0, 0, 0, 0,
1461 -string_width (mc->xfont1, a->label) / 2,
1462 -mc->xfont1->descent,
1465 for (j = 0; j < strlen(a->label); j++)
1466 glCallList (mc->font1_dlist + (int)(a->label[j]));
1471 /* More efficient to always call glEnable() with correct values
1472 than to call glPushAttrib()/glPopAttrib(), since reading
1473 attributes from GL does a round-trip and stalls the pipeline.
1476 glEnable (GL_LIGHTING);
1481 pick_new_molecule (ModeInfo *mi, time_t last)
1483 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1485 if (mc->nmolecules == 1)
1487 if (last != 0) return;
1492 mc->which = random() % mc->nmolecules;
1497 while (n == mc->which)
1498 n = random() % mc->nmolecules;
1504 char *name = strdup (mc->molecules[mc->which].label);
1505 char *s = strpbrk (name, "\r\n");
1507 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1511 glNewList (mc->molecule_dlist, GL_COMPILE);
1512 ensure_bounding_box_visible (mi);
1514 do_labels = orig_do_labels;
1515 do_bonds = orig_do_bonds;
1516 MI_IS_WIREFRAME(mi) = orig_wire;
1518 if (mc->molecule_size > mc->no_label_threshold)
1520 if (mc->molecule_size > mc->wireframe_threshold)
1521 MI_IS_WIREFRAME(mi) = 1;
1523 if (MI_IS_WIREFRAME(mi))
1526 build_molecule (mi);
1532 draw_molecule (ModeInfo *mi)
1534 static time_t last = 0;
1535 time_t now = time ((time_t *) 0);
1536 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1538 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1539 Display *dpy = MI_DISPLAY(mi);
1540 Window window = MI_WINDOW(mi);
1542 if (!mc->glx_context)
1547 pick_new_molecule (mi, last);
1550 else if (mc->mode == 0)
1552 static int tick = 0;
1555 time_t now = time((time_t *) 0);
1556 if (last == 0) last = now;
1559 if (!mc->button_down_p &&
1560 mc->nmolecules > 1 &&
1561 last + timeout <= now)
1563 /* randomize molecules every -timeout seconds */
1564 mc->mode = 1; /* go out */
1565 mc->mode_tick = 10 * speed;
1570 else if (mc->mode == 1) /* out */
1572 if (--mc->mode_tick <= 0)
1574 mc->mode_tick = 10 * speed;
1575 mc->mode = 2; /* go in */
1576 pick_new_molecule (mi, last);
1580 else if (mc->mode == 2) /* in */
1582 if (--mc->mode_tick <= 0)
1583 mc->mode = 0; /* normal */
1589 glScalef(1.1, 1.1, 1.1);
1593 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1594 glTranslatef((x - 0.5) * 9,
1598 gltrackball_rotate (mc->trackball);
1600 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1601 glRotatef (x * 360, 1.0, 0.0, 0.0);
1602 glRotatef (y * 360, 0.0, 1.0, 0.0);
1603 glRotatef (z * 360, 0.0, 0.0, 1.0);
1606 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1610 GLfloat s = (mc->mode == 1
1611 ? mc->mode_tick / (10 * speed)
1612 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1616 glCallList (mc->molecule_dlist);
1623 if (mi->fps_p) do_fps (mi);
1626 glXSwapBuffers(dpy, window);