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 24 /* how densely to render spheres */
81 #define SPHERE_STACKS 12
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 int mode; /* 0 = normal, 1 = out, 2 = in */
172 GLuint molecule_dlist;
174 XFontStruct *xfont1, *xfont2;
175 GLuint font1_dlist, font2_dlist;
177 } molecule_configuration;
180 static molecule_configuration *mcs = NULL;
183 static char *molecule_str;
184 static char *do_spin;
185 static Bool do_wander;
186 static Bool do_titles;
187 static Bool do_labels;
188 static Bool do_atoms;
189 static Bool do_bonds;
192 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
195 static XrmOptionDescRec opts[] = {
196 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
197 { "-timeout",".timeout",XrmoptionSepArg, 0 },
198 { "-spin", ".spin", XrmoptionSepArg, 0 },
199 { "+spin", ".spin", XrmoptionNoArg, "" },
200 { "-wander", ".wander", XrmoptionNoArg, "True" },
201 { "+wander", ".wander", XrmoptionNoArg, "False" },
202 { "-labels", ".labels", XrmoptionNoArg, "True" },
203 { "+labels", ".labels", XrmoptionNoArg, "False" },
204 { "-titles", ".titles", XrmoptionNoArg, "True" },
205 { "+titles", ".titles", XrmoptionNoArg, "False" },
206 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
207 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
208 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
209 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
210 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
211 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
214 static argtype vars[] = {
215 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
216 {&timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
217 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
218 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
219 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
220 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
221 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
222 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
223 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
226 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
234 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
236 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
237 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
240 glTranslatef (x, y, z);
241 glScalef (diameter, diameter, diameter);
242 unit_sphere (stacks, slices, wire);
248 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
250 const char *font = get_string_resource (res, "Font");
255 if (!font) font = "-*-times-bold-r-normal-*-180-*";
257 f = XLoadQueryFont(mi->dpy, font);
258 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
261 first = f->min_char_or_byte2;
262 last = f->max_char_or_byte2;
265 *dlistP = glGenLists ((GLuint) last+1);
266 check_gl_error ("glGenLists");
267 glXUseXFont(id, first, last-first+1, *dlistP + first);
268 check_gl_error ("glXUseXFont");
275 load_fonts (ModeInfo *mi)
277 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
278 load_font (mi, "atomFont", &mc->xfont1, &mc->font1_dlist);
279 load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
284 string_width (XFontStruct *f, const char *c)
289 int cc = *((unsigned char *) c);
291 ? f->per_char[cc-f->min_char_or_byte2].rbearing
292 : f->min_bounds.rbearing);
300 get_atom_data (const char *atom_name)
304 char *n = strdup (atom_name);
308 while (!isalpha(*n)) n++;
310 while (L > 0 && !isalpha(n[L-1]))
313 for (i = 0; i < countof(all_atom_data); i++)
315 d = &all_atom_data[i];
316 if (!strcasecmp (n, all_atom_data[i].name))
326 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
335 static atom_data *def_data = 0;
336 if (!def_data) def_data = get_atom_data ("bond");
340 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
342 if (gl_color[3] == 0)
344 const char *string = !font_p ? d->color : d->text_color;
346 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
348 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
349 (a ? a->label : d->name), string);
353 gl_color[0] = xcolor.red / 65536.0;
354 gl_color[1] = xcolor.green / 65536.0;
355 gl_color[2] = xcolor.blue / 65536.0;
360 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
362 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
367 atom_size (molecule_atom *a)
371 if (a->data->size2 == 0)
373 /* let the molecules have the same relative sizes, but scale
374 them to a smaller range, so that the bond-tubes are
381 GLfloat ratio = (a->data->size - min) / (max - min);
382 a->data->size2 = bot + (ratio * (top - bot));
384 return a->data->size2;
387 return a->data->size;
391 static molecule_atom *
392 get_atom (molecule_atom *atoms, int natoms, int id)
396 /* quick short-circuit */
399 if (atoms[id].id == id)
401 if (id > 0 && atoms[id-1].id == id)
403 if (id < natoms-1 && atoms[id+1].id == id)
407 for (i = 0; i < natoms; i++)
408 if (id == atoms[i].id)
411 fprintf (stderr, "%s: no atom %d\n", progname, id);
417 molecule_bounding_box (ModeInfo *mi,
418 GLfloat *x1, GLfloat *y1, GLfloat *z1,
419 GLfloat *x2, GLfloat *y2, GLfloat *z2)
421 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
422 molecule *m = &mc->molecules[mc->which];
427 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
431 *x1 = *x2 = m->atoms[0].x;
432 *y1 = *y2 = m->atoms[0].y;
433 *z1 = *z2 = m->atoms[0].z;
436 for (i = 1; i < m->natoms; i++)
438 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
439 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
440 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
442 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
443 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
444 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
457 draw_bounding_box (ModeInfo *mi)
459 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
460 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
461 int wire = MI_IS_WIREFRAME(mi);
462 GLfloat x1, y1, z1, x2, y2, z2;
463 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
465 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
468 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
470 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
471 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
473 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
474 glNormal3f(0, -1, 0);
475 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
476 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
478 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
480 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
481 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
483 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
484 glNormal3f(0, 0, -1);
485 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
486 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
488 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
490 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
491 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
493 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
494 glNormal3f(-1, 0, 0);
495 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
496 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
499 glPushAttrib (GL_LIGHTING);
500 glDisable (GL_LIGHTING);
502 glColor3f (c2[0], c2[1], c2[2]);
504 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
505 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
506 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
507 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
508 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
509 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
516 /* Since PDB files don't always have the molecule centered around the
517 origin, and since some molecules are pretty large, scale and/or
518 translate so that the whole molecule is visible in the window.
521 ensure_bounding_box_visible (ModeInfo *mi)
523 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
525 GLfloat x1, y1, z1, x2, y2, z2;
528 GLfloat max_size = 10; /* don't bother scaling down if the molecule
529 is already smaller than this */
531 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
536 size = (w > h ? w : h);
537 size = (size > d ? size : d);
539 mc->molecule_size = size;
545 GLfloat scale = max_size / size;
546 glScalef (scale, scale, scale);
548 scale_down = scale < 0.3;
551 glTranslatef (-(x1 + w/2),
558 print_title_string (ModeInfo *mi, const char *string,
559 GLfloat x, GLfloat y, XFontStruct *font)
561 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
562 GLfloat line_height = font->ascent + font->descent;
563 GLfloat sub_shift = (line_height * 0.3);
567 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
568 GL_ENABLE_BIT); /* for various glDisable calls */
569 glDisable (GL_LIGHTING);
570 glDisable (GL_DEPTH_TEST);
572 glMatrixMode(GL_PROJECTION);
577 glMatrixMode(GL_MODELVIEW);
585 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
587 set_atom_color (mi, 0, True);
589 glRasterPos2f (x, y);
590 for (i = 0; i < strlen(string); i++)
595 glRasterPos2f (x, (y -= line_height));
598 else if (c == '(' && (isdigit (string[i+1])))
601 glRasterPos2f (x2, (y -= sub_shift));
603 else if (c == ')' && sub_p)
606 glRasterPos2f (x2, (y += sub_shift));
610 glCallList (mc->font2_dlist + (int)(c));
611 x2 += (font->per_char
612 ? font->per_char[c - font->min_char_or_byte2].width
613 : font->min_bounds.width);
619 glMatrixMode(GL_PROJECTION);
624 glMatrixMode(GL_MODELVIEW);
628 /* Constructs the GL shapes of the current molecule
631 build_molecule (ModeInfo *mi)
633 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
634 int wire = MI_IS_WIREFRAME(mi);
637 molecule *m = &mc->molecules[mc->which];
641 glDisable(GL_CULL_FACE);
642 glDisable(GL_LIGHTING);
643 glDisable(GL_LIGHT0);
644 glDisable(GL_DEPTH_TEST);
645 glDisable(GL_NORMALIZE);
646 glDisable(GL_CULL_FACE);
650 glEnable(GL_CULL_FACE);
651 glEnable(GL_LIGHTING);
653 glEnable(GL_DEPTH_TEST);
654 glEnable(GL_NORMALIZE);
655 glEnable(GL_CULL_FACE);
659 set_atom_color (mi, 0, False);
662 for (i = 0; i < m->nbonds; i++)
664 molecule_bond *b = &m->bonds[i];
665 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
666 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
671 glVertex3f(from->x, from->y, from->z);
672 glVertex3f(to->x, to->y, to->z);
677 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
683 GLfloat thickness = 0.07 * b->strength;
684 GLfloat cap_size = 0.03;
688 tube (from->x, from->y, from->z,
691 faces, smooth, False, wire);
695 if (!wire && do_atoms)
696 for (i = 0; i < m->natoms; i++)
698 molecule_atom *a = &m->atoms[i];
699 GLfloat size = atom_size (a);
700 set_atom_color (mi, a, False);
701 sphere (a->x, a->y, a->z, size, wire);
705 draw_bounding_box (mi);
707 if (do_titles && m->label && *m->label)
708 print_title_string (mi, m->label,
709 10, mi->xgwa.height - 10,
718 push_atom (molecule *m,
719 int id, const char *label,
720 GLfloat x, GLfloat y, GLfloat z)
723 if (m->atoms_size < m->natoms)
726 m->atoms = (molecule_atom *) realloc (m->atoms,
727 m->atoms_size * sizeof(*m->atoms));
729 m->atoms[m->natoms-1].id = id;
730 m->atoms[m->natoms-1].label = label;
731 m->atoms[m->natoms-1].x = x;
732 m->atoms[m->natoms-1].y = y;
733 m->atoms[m->natoms-1].z = z;
734 m->atoms[m->natoms-1].data = get_atom_data (label);
739 push_bond (molecule *m, int from, int to)
743 for (i = 0; i < m->nbonds; i++)
744 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
745 (m->bonds[i].to == from && m->bonds[i].from == to))
747 m->bonds[i].strength++;
752 if (m->bonds_size < m->nbonds)
755 m->bonds = (molecule_bond *) realloc (m->bonds,
756 m->bonds_size * sizeof(*m->bonds));
758 m->bonds[m->nbonds-1].from = from;
759 m->bonds[m->nbonds-1].to = to;
760 m->bonds[m->nbonds-1].strength = 1;
765 /* This function is crap.
768 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
770 const char *s = data;
774 if ((!m->label || !*m->label) &&
775 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
777 char *name = calloc (1, 100);
784 while (isspace(*n2)) n2++;
786 ss = strchr (n2, '\n');
788 ss = strchr (n2, '\r');
791 ss = n2+strlen(n2)-1;
792 while (isspace(*ss) && ss > n2)
795 if (strlen (n2) > 4 &&
796 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
797 n2[strlen(n2)-4] = 0;
799 if (m->label) free ((char *) m->label);
800 m->label = strdup (n2);
803 else if (!strncmp (s, "TITLE ", 6) ||
804 !strncmp (s, "HEADER", 6) ||
805 !strncmp (s, "COMPND", 6) ||
806 !strncmp (s, "AUTHOR", 6) ||
807 !strncmp (s, "REVDAT", 6) ||
808 !strncmp (s, "SOURCE", 6) ||
809 !strncmp (s, "EXPDTA", 6) ||
810 !strncmp (s, "JRNL ", 6) ||
811 !strncmp (s, "REMARK", 6) ||
812 !strncmp (s, "SEQRES", 6) ||
813 !strncmp (s, "HET ", 6) ||
814 !strncmp (s, "FORMUL", 6) ||
815 !strncmp (s, "CRYST1", 6) ||
816 !strncmp (s, "ORIGX1", 6) ||
817 !strncmp (s, "ORIGX2", 6) ||
818 !strncmp (s, "ORIGX3", 6) ||
819 !strncmp (s, "SCALE1", 6) ||
820 !strncmp (s, "SCALE2", 6) ||
821 !strncmp (s, "SCALE3", 6) ||
822 !strncmp (s, "MASTER", 6) ||
823 !strncmp (s, "KEYWDS", 6) ||
824 !strncmp (s, "DBREF ", 6) ||
825 !strncmp (s, "HETNAM", 6) ||
826 !strncmp (s, "HETSYN", 6) ||
827 !strncmp (s, "HELIX ", 6) ||
828 !strncmp (s, "LINK ", 6) ||
829 !strncmp (s, "MTRIX1", 6) ||
830 !strncmp (s, "MTRIX2", 6) ||
831 !strncmp (s, "MTRIX3", 6) ||
832 !strncmp (s, "SHEET ", 6) ||
833 !strncmp (s, "CISPEP", 6) ||
834 !strncmp (s, "GENERATED BY", 12) ||
835 !strncmp (s, "TER ", 4) ||
836 !strncmp (s, "END ", 4) ||
837 !strncmp (s, "TER\n", 4) ||
838 !strncmp (s, "END\n", 4) ||
839 !strncmp (s, "\n", 1))
842 else if (!strncmp (s, "ATOM ", 7))
845 char *name = (char *) calloc (1, 4);
846 GLfloat x = -999, y = -999, z = -999;
848 sscanf (s+7, " %d ", &id);
850 strncpy (name, s+12, 3);
851 while (isspace(*name)) name++;
852 ss = name + strlen(name)-1;
853 while (isspace(*ss) && ss > name)
861 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
863 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
864 progname, filename, line,
867 push_atom (m, id, name, x, y, z);
869 else if (!strncmp (s, "HETATM ", 7))
872 char *name = (char *) calloc (1, 4);
873 GLfloat x = -999, y = -999, z = -999;
875 sscanf (s+7, " %d ", &id);
877 strncpy (name, s+12, 3);
878 while (isspace(*name)) name++;
879 ss = name + strlen(name)-1;
880 while (isspace(*ss) && ss > name)
882 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
884 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
885 progname, filename, line,
888 push_atom (m, id, name, x, y, z);
890 else if (!strncmp (s, "CONECT ", 7))
893 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
894 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
895 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
896 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
898 for (j = 1; j < i; j++)
902 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
903 progname, filename, line, atoms[0], atoms[j]);
905 push_bond (m, atoms[0], atoms[j]);
910 char *s1 = strdup (s);
911 for (ss = s1; *ss && *ss != '\n'; ss++)
914 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
915 progname, filename, line, s1);
918 while (*s && *s != '\n')
928 parse_pdb_file (molecule *m, const char *name)
931 int buf_size = 40960;
935 in = fopen(name, "r");
938 char *buf = (char *) malloc(1024 + strlen(name));
939 sprintf(buf, "%s: error reading \"%s\"", progname, name);
944 buf = (char *) malloc (buf_size);
946 while (fgets (buf, buf_size-1, in))
949 for (s = buf; *s; s++)
950 if (*s == '\r') *s = '\n';
951 parse_pdb_data (m, buf, name, line++);
959 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
964 if (!m->nbonds && do_bonds)
966 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
975 typedef struct { char *atom; int count; } atom_and_count;
977 /* When listing the components of a molecule, the convention is to put the
978 carbon atoms first, the hydrogen atoms second, and the other atom types
979 sorted alphabetically after that (although for some molecules, the usual
980 order is different: we special-case a few of those.)
983 cmp_atoms (const void *aa, const void *bb)
985 const atom_and_count *a = (atom_and_count *) aa;
986 const atom_and_count *b = (atom_and_count *) bb;
987 if (!a->atom) return 1;
988 if (!b->atom) return -1;
989 if (!strcmp(a->atom, "C")) return -1;
990 if (!strcmp(b->atom, "C")) return 1;
991 if (!strcmp(a->atom, "H")) return -1;
992 if (!strcmp(b->atom, "H")) return 1;
993 return strcmp (a->atom, b->atom);
996 static void special_case_formula (char *f);
999 generate_molecule_formula (molecule *m)
1001 char *buf = (char *) malloc (m->natoms * 10);
1004 atom_and_count counts[200];
1005 memset (counts, 0, sizeof(counts));
1007 for (i = 0; i < m->natoms; i++)
1010 char *a = (char *) m->atoms[i].label;
1012 while (!isalpha(*a)) a++;
1014 for (e = a; isalpha(*e); e++);
1016 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1026 while (counts[i].atom) i++;
1027 qsort (counts, i, sizeof(*counts), cmp_atoms);
1030 while (counts[i].atom)
1032 strcat (s, counts[i].atom);
1033 free (counts[i].atom);
1035 if (counts[i].count > 1)
1036 sprintf (s, "(%d)", counts[i].count);
1041 special_case_formula (buf);
1043 if (!m->label) m->label = strdup("");
1044 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1045 strcpy (s, m->label);
1048 free ((char *) m->label);
1053 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1055 special_case_formula (char *f)
1057 if (!strcmp(f, "H(2)Be")) strcpy(f, "BeH(2)");
1058 else if (!strcmp(f, "H(3)B")) strcpy(f, "BH(3)");
1059 else if (!strcmp(f, "H(3)N")) strcpy(f, "NH(3)");
1060 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1061 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1062 else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
1063 else if (!strcmp(f, "Cl(3)P")) strcpy(f, "PCl(3)");
1064 else if (!strcmp(f, "Cl(5)P")) strcpy(f, "PCl(5)");
1069 insert_vertical_whitespace (char *string)
1073 if ((string[0] == ',' ||
1075 string[0] == ':') &&
1077 string[0] = ' ', string[1] = '\n';
1083 /* Construct the molecule data from either: the builtins; or from
1084 the (one) .pdb file specified with -molecule.
1087 load_molecules (ModeInfo *mi)
1089 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1090 int wire = MI_IS_WIREFRAME(mi);
1091 Bool verbose_p = False;
1095 if (molecule_str && *molecule_str &&
1096 strcmp(molecule_str, "(default)")) /* try external PDB files */
1098 /* The -molecule option can point to a .pdb file, or to
1099 a directory of them.
1107 if (!stat (molecule_str, &st) &&
1108 S_ISDIR (st.st_mode))
1112 struct dirent *dentry;
1114 pdb_dir = opendir (molecule_str);
1117 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1123 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1127 files = (char **) calloc (sizeof(*files), list_size);
1129 while ((dentry = readdir (pdb_dir)))
1131 int L = strlen (dentry->d_name);
1132 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1135 if (nfiles >= list_size-1)
1137 list_size = (list_size + 10) * 1.2;
1139 realloc (files, list_size * sizeof(*files));
1143 fprintf (stderr, "%s: out of memory (%d files)\n",
1149 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1151 strcpy (fn, molecule_str);
1152 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1153 strcat (fn, dentry->d_name);
1154 files[nfiles++] = fn;
1156 fprintf (stderr, "%s: file %s\n", progname, fn);
1162 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1163 progname, molecule_str);
1167 files = (char **) malloc (sizeof (*files));
1169 files[0] = strdup (molecule_str);
1171 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1174 mc->nmolecules = nfiles;
1175 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1177 for (i = 0; i < mc->nmolecules; i++)
1180 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1181 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1183 if ((wire || !do_atoms) &&
1185 mc->molecules[molecule_ctr].nbonds == 0)
1187 /* If we're not drawing atoms (e.g., wireframe mode), and
1188 there is no bond info, then make sure labels are turned
1189 on, or we'll be looking at a black screen... */
1190 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1191 progname, files[i]);
1202 mc->nmolecules = molecule_ctr;
1205 if (mc->nmolecules == 0) /* do the builtins if no files */
1207 mc->nmolecules = countof(builtin_pdb_data);
1208 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1209 for (i = 0; i < mc->nmolecules; i++)
1212 sprintf (name, "<builtin-%d>", i);
1213 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1214 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1218 for (i = 0; i < mc->nmolecules; i++)
1220 generate_molecule_formula (&mc->molecules[i]);
1221 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1227 /* Window management, etc
1230 reshape_molecule (ModeInfo *mi, int width, int height)
1232 GLfloat h = (GLfloat) height / (GLfloat) width;
1234 glViewport (0, 0, (GLint) width, (GLint) height);
1236 glMatrixMode(GL_PROJECTION);
1238 gluPerspective (30.0, 1/h, 20.0, 40.0);
1240 glMatrixMode(GL_MODELVIEW);
1242 gluLookAt( 0.0, 0.0, 30.0,
1246 glClear(GL_COLOR_BUFFER_BIT);
1251 gl_init (ModeInfo *mi)
1253 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1254 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1255 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1256 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1257 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1258 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1259 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1260 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1262 orig_do_labels = do_labels;
1263 orig_do_bonds = do_bonds;
1264 orig_wire = MI_IS_WIREFRAME(mi);
1269 startup_blurb (ModeInfo *mi)
1271 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1272 const char *s = "Constructing molecules...";
1273 print_title_string (mi, s,
1274 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1275 10 + mc->xfont2->ascent + mc->xfont2->descent,
1278 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1282 molecule_handle_event (ModeInfo *mi, XEvent *event)
1284 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1286 if (event->xany.type == ButtonPress &&
1287 event->xbutton.button & Button1)
1289 mc->button_down_p = True;
1290 gltrackball_start (mc->trackball,
1291 event->xbutton.x, event->xbutton.y,
1292 MI_WIDTH (mi), MI_HEIGHT (mi));
1295 else if (event->xany.type == ButtonRelease &&
1296 event->xbutton.button & Button1)
1298 mc->button_down_p = False;
1301 else if (event->xany.type == MotionNotify &&
1304 gltrackball_track (mc->trackball,
1305 event->xmotion.x, event->xmotion.y,
1306 MI_WIDTH (mi), MI_HEIGHT (mi));
1315 init_molecule (ModeInfo *mi)
1317 molecule_configuration *mc;
1321 mcs = (molecule_configuration *)
1322 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1324 fprintf(stderr, "%s: out of memory\n", progname);
1329 mc = &mcs[MI_SCREEN(mi)];
1331 if ((mc->glx_context = init_GL(mi)) != NULL) {
1333 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1339 wire = MI_IS_WIREFRAME(mi);
1342 Bool spinx=False, spiny=False, spinz=False;
1343 double spin_speed = 0.5;
1344 double spin_accel = 0.3;
1345 double wander_speed = 0.01;
1350 if (*s == 'x' || *s == 'X') spinx = True;
1351 else if (*s == 'y' || *s == 'Y') spiny = True;
1352 else if (*s == 'z' || *s == 'Z') spinz = True;
1356 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1363 mc->rot = make_rotator (spinx ? spin_speed : 0,
1364 spiny ? spin_speed : 0,
1365 spinz ? spin_speed : 0,
1367 do_wander ? wander_speed : 0,
1368 (spinx && spiny && spinz));
1369 mc->trackball = gltrackball_init ();
1372 mc->molecule_dlist = glGenLists(1);
1374 load_molecules (mi);
1375 mc->which = random() % mc->nmolecules;
1377 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1378 "NoLabelThreshold");
1379 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1380 "WireframeThreshold");
1388 /* Put the labels on the atoms.
1389 This can't be a part of the display list because of the games
1390 we play with the translation matrix.
1393 draw_labels (ModeInfo *mi)
1395 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1396 int wire = MI_IS_WIREFRAME(mi);
1397 molecule *m = &mc->molecules[mc->which];
1404 glDisable (GL_LIGHTING); /* don't light fonts */
1406 for (i = 0; i < m->natoms; i++)
1408 molecule_atom *a = &m->atoms[i];
1409 GLfloat size = atom_size (a);
1415 set_atom_color (mi, a, True);
1417 /* First, we translate the origin to the center of the atom.
1419 Then we retrieve the prevailing modelview matrix (which
1420 includes any rotation, wandering, and user-trackball-rolling
1423 We set the top 3x3 cells of that matrix to be the identity
1424 matrix. This removes all rotation from the matrix, while
1425 leaving the translation alone. This has the effect of
1426 leaving the prevailing coordinate system perpendicular to
1427 the camera view: were we to draw a square face, it would
1428 be in the plane of the screen.
1430 Now we translate by `size' toward the viewer -- so that the
1431 origin is *just in front* of the ball.
1433 Then we draw the label text, allowing the depth buffer to
1434 do its work: that way, labels on atoms will be occluded
1435 properly when other atoms move in front of them.
1437 This technique (of neutralizing rotation relative to the
1438 observer, after both rotations and translations have been
1439 applied) is known as "billboarding".
1442 glTranslatef(a->x, a->y, a->z); /* get matrix */
1443 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1444 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1445 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1446 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1447 glLoadIdentity(); /* reset modelview */
1448 glMultMatrixf (&m[0][0]); /* replace with ours */
1450 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1452 glRasterPos3f (0, 0, 0); /* draw text here */
1454 /* Before drawing the string, shift the origin to center
1455 the text over the origin of the sphere. */
1456 glBitmap (0, 0, 0, 0,
1457 -string_width (mc->xfont1, a->label) / 2,
1458 -mc->xfont1->descent,
1461 for (j = 0; j < strlen(a->label); j++)
1462 glCallList (mc->font1_dlist + (int)(a->label[j]));
1467 /* More efficient to always call glEnable() with correct values
1468 than to call glPushAttrib()/glPopAttrib(), since reading
1469 attributes from GL does a round-trip and stalls the pipeline.
1472 glEnable (GL_LIGHTING);
1477 pick_new_molecule (ModeInfo *mi, time_t last)
1479 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1481 if (mc->nmolecules == 1)
1483 if (last != 0) return;
1488 mc->which = random() % mc->nmolecules;
1493 while (n == mc->which)
1494 n = random() % mc->nmolecules;
1498 glNewList (mc->molecule_dlist, GL_COMPILE);
1499 ensure_bounding_box_visible (mi);
1501 do_labels = orig_do_labels;
1502 do_bonds = orig_do_bonds;
1503 MI_IS_WIREFRAME(mi) = orig_wire;
1505 if (mc->molecule_size > mc->no_label_threshold)
1507 if (mc->molecule_size > mc->wireframe_threshold)
1508 MI_IS_WIREFRAME(mi) = 1;
1510 if (MI_IS_WIREFRAME(mi))
1513 build_molecule (mi);
1519 draw_molecule (ModeInfo *mi)
1521 static time_t last = 0;
1522 time_t now = time ((time_t *) 0);
1523 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1525 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1526 Display *dpy = MI_DISPLAY(mi);
1527 Window window = MI_WINDOW(mi);
1529 if (!mc->glx_context)
1534 pick_new_molecule (mi, last);
1537 else if (mc->mode == 0)
1539 static int tick = 0;
1542 time_t now = time((time_t *) 0);
1543 if (last == 0) last = now;
1546 if (!mc->button_down_p &&
1547 mc->nmolecules > 1 &&
1548 last + timeout <= now)
1550 /* randomize molecules every -timeout seconds */
1551 mc->mode = 1; /* go out */
1552 mc->mode_tick = 10 * speed;
1557 else if (mc->mode == 1) /* out */
1559 if (--mc->mode_tick <= 0)
1561 mc->mode_tick = 10 * speed;
1562 mc->mode = 2; /* go in */
1563 pick_new_molecule (mi, last);
1567 else if (mc->mode == 2) /* in */
1569 if (--mc->mode_tick <= 0)
1570 mc->mode = 0; /* normal */
1576 glScalef(1.1, 1.1, 1.1);
1580 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1581 glTranslatef((x - 0.5) * 9,
1585 gltrackball_rotate (mc->trackball);
1587 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1588 glRotatef (x * 360, 1.0, 0.0, 0.0);
1589 glRotatef (y * 360, 0.0, 1.0, 0.0);
1590 glRotatef (z * 360, 0.0, 0.0, 1.0);
1593 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1597 GLfloat s = (mc->mode == 1
1598 ? mc->mode_tick / (10 * speed)
1599 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1603 glCallList (mc->molecule_dlist);
1610 if (mi->fps_p) do_fps (mi);
1613 glXSwapBuffers(dpy, window);