1 /* molecule, Copyright (c) 2001 Jamie Zawinski <jwz@jwz.org>
2 * Draws molecules, based on coordinates from PDB (Protein Data Base) files.
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
14 /* Documentation on the PDB file format:
15 http://www.rcsb.org/pdb/docs/format/pdbguide2.2/guide2.2_frame.html
17 Good source of PDB files:
18 http://www.sci.ouc.bc.ca/chem/molecule/molecule.html
21 #include <X11/Intrinsic.h>
23 #define PROGCLASS "Molecule"
24 #define HACK_INIT init_molecule
25 #define HACK_DRAW draw_molecule
26 #define HACK_RESHAPE reshape_molecule
27 #define HACK_HANDLE_EVENT molecule_handle_event
28 #define EVENT_MASK PointerMotionMask
29 #define molecule_opts xlockmore_opts
31 #define DEF_TIMEOUT "20"
32 #define DEF_SPIN "XYZ"
33 #define DEF_WANDER "False"
34 #define DEF_LABELS "True"
35 #define DEF_TITLES "True"
36 #define DEF_ATOMS "True"
37 #define DEF_BONDS "True"
38 #define DEF_BBOX "False"
39 #define DEF_MOLECULE "(default)"
41 #define DEFAULTS "*delay: 10000 \n" \
42 "*timeout: " DEF_TIMEOUT "\n" \
43 "*showFPS: False \n" \
44 "*wireframe: False \n" \
45 "*molecule: " DEF_MOLECULE "\n" \
46 "*spin: " DEF_SPIN "\n" \
47 "*wander: " DEF_WANDER "\n" \
48 "*labels: " DEF_LABELS "\n" \
49 "*atoms: " DEF_ATOMS "\n" \
50 "*bonds: " DEF_BONDS "\n" \
51 "*bbox: " DEF_BBOX "\n" \
52 "*atomFont: -*-times-bold-r-normal-*-240-*\n" \
53 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
54 "*noLabelThreshold: 30 \n" \
55 "*wireframeThreshold: 150 \n" \
59 #define countof(x) (sizeof((x))/sizeof((*x)))
61 #include "xlockmore.h"
66 #include "gltrackball.h"
68 #ifdef USE_GL /* whole file */
76 #define SPHERE_SLICES 16 /* how densely to render spheres */
77 #define SPHERE_STACKS 10
79 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
82 # define TUBE_FACES 12 /* how densely to render tubes */
87 static int scale_down;
88 #define SPHERE_SLICES_2 7
89 #define SPHERE_STACKS_2 4
90 #define TUBE_FACES_2 3
93 const char * const builtin_pdb_data[] = {
94 # include "molecules.h"
102 const char *text_color;
107 /* These are the traditional colors used to render these atoms,
108 and their approximate size in angstroms.
110 static atom_data all_atom_data[] = {
111 { "H", 1.17, 0, "White", "Grey70", { 0, }},
112 { "C", 1.75, 0, "Grey60", "White", { 0, }},
113 { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }},
114 { "O", 1.40, 0, "Red", "LightPink", { 0, }},
115 { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }},
116 { "S", 1.80, 0, "Yellow4", "Yellow1", { 0, }},
117 { "bond", 0, 0, "Grey70", "Yellow1", { 0, }},
118 { "*", 1.40, 0, "Green4", "LightGreen", { 0, }}
123 int id; /* sequence number in the PDB file */
124 const char *label; /* The atom name */
125 GLfloat x, y, z; /* position in 3-space (angstroms) */
126 atom_data *data; /* computed: which style of atom this is */
130 int from, to; /* atom sequence numbers */
131 int strength; /* how many bonds are between these two atoms */
136 const char *label; /* description of this compound */
137 int natoms, atoms_size;
138 int nbonds, bonds_size;
139 molecule_atom *atoms;
140 molecule_bond *bonds;
145 GLXContext *glx_context;
147 trackball_state *trackball;
150 GLfloat molecule_size; /* max dimension of molecule bounding box */
152 GLfloat no_label_threshold; /* Things happen when molecules are huge */
153 GLfloat wireframe_threshold;
155 int which; /* which of the molecules is being shown */
159 GLuint molecule_dlist;
161 XFontStruct *xfont1, *xfont2;
162 GLuint font1_dlist, font2_dlist;
164 } molecule_configuration;
167 static molecule_configuration *mcs = NULL;
170 static char *molecule_str;
171 static char *do_spin;
172 static Bool do_wander;
173 static Bool do_titles;
174 static Bool do_labels;
175 static Bool do_atoms;
176 static Bool do_bonds;
179 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
182 static XrmOptionDescRec opts[] = {
183 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
184 { "-timeout",".timeout",XrmoptionSepArg, 0 },
185 { "-spin", ".spin", XrmoptionSepArg, 0 },
186 { "+spin", ".spin", XrmoptionNoArg, "" },
187 { "-wander", ".wander", XrmoptionNoArg, "True" },
188 { "+wander", ".wander", XrmoptionNoArg, "False" },
189 { "-labels", ".labels", XrmoptionNoArg, "True" },
190 { "+labels", ".labels", XrmoptionNoArg, "False" },
191 { "-titles", ".titles", XrmoptionNoArg, "True" },
192 { "+titles", ".titles", XrmoptionNoArg, "False" },
193 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
194 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
195 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
196 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
197 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
198 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
201 static argtype vars[] = {
202 {(caddr_t *) &molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
203 {(caddr_t *) &timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
204 {(caddr_t *) &do_spin, "spin", "Spin", DEF_SPIN, t_String},
205 {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
206 {(caddr_t *) &do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
207 {(caddr_t *) &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
208 {(caddr_t *) &do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
209 {(caddr_t *) &do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
210 {(caddr_t *) &do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
213 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
221 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
223 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
224 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
227 glTranslatef (x, y, z);
228 glScalef (diameter, diameter, diameter);
229 unit_sphere (stacks, slices, wire);
235 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
237 const char *font = get_string_resource (res, "Font");
242 if (!font) font = "-*-times-bold-r-normal-*-180-*";
244 f = XLoadQueryFont(mi->dpy, font);
245 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
248 first = f->min_char_or_byte2;
249 last = f->max_char_or_byte2;
252 *dlistP = glGenLists ((GLuint) last+1);
253 check_gl_error ("glGenLists");
254 glXUseXFont(id, first, last-first+1, *dlistP + first);
255 check_gl_error ("glXUseXFont");
262 load_fonts (ModeInfo *mi)
264 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
265 load_font (mi, "atomFont", &mc->xfont1, &mc->font1_dlist);
266 load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
271 string_width (XFontStruct *f, const char *c)
276 int cc = *((unsigned char *) c);
278 ? f->per_char[cc-f->min_char_or_byte2].rbearing
279 : f->min_bounds.rbearing);
287 get_atom_data (const char *atom_name)
291 char *n = strdup (atom_name);
295 while (!isalpha(*n)) n++;
297 while (L > 0 && !isalpha(n[L-1]))
300 for (i = 0; i < countof(all_atom_data); i++)
302 d = &all_atom_data[i];
303 if (!strcmp (n, all_atom_data[i].name))
313 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
322 static atom_data *def_data = 0;
323 if (!def_data) def_data = get_atom_data ("bond");
327 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
329 if (gl_color[3] == 0)
331 const char *string = !font_p ? d->color : d->text_color;
333 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
335 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
336 (a ? a->label : d->name), string);
340 gl_color[0] = xcolor.red / 65536.0;
341 gl_color[1] = xcolor.green / 65536.0;
342 gl_color[2] = xcolor.blue / 65536.0;
347 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
349 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
354 atom_size (molecule_atom *a)
358 if (a->data->size2 == 0)
360 /* let the molecules have the same relative sizes, but scale
361 them to a smaller range, so that the bond-tubes are
368 GLfloat ratio = (a->data->size - min) / (max - min);
369 a->data->size2 = bot + (ratio * (top - bot));
371 return a->data->size2;
374 return a->data->size;
378 static molecule_atom *
379 get_atom (molecule_atom *atoms, int natoms, int id)
383 /* quick short-circuit */
386 if (atoms[id].id == id)
388 if (id > 0 && atoms[id-1].id == id)
390 if (id < natoms-1 && atoms[id+1].id == id)
394 for (i = 0; i < natoms; i++)
395 if (id == atoms[i].id)
398 fprintf (stderr, "%s: no atom %d\n", progname, id);
404 molecule_bounding_box (ModeInfo *mi,
405 GLfloat *x1, GLfloat *y1, GLfloat *z1,
406 GLfloat *x2, GLfloat *y2, GLfloat *z2)
408 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
409 molecule *m = &mc->molecules[mc->which];
414 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
418 *x1 = *x2 = m->atoms[0].x;
419 *y1 = *y2 = m->atoms[0].y;
420 *z1 = *z2 = m->atoms[0].z;
423 for (i = 1; i < m->natoms; i++)
425 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
426 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
427 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
429 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
430 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
431 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
444 draw_bounding_box (ModeInfo *mi)
446 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
447 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
448 int wire = MI_IS_WIREFRAME(mi);
449 GLfloat x1, y1, z1, x2, y2, z2;
450 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
452 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
455 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
457 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
458 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
460 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
461 glNormal3f(0, -1, 0);
462 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
463 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
465 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
467 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
468 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
470 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
471 glNormal3f(0, 0, -1);
472 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
473 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
475 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
477 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
478 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
480 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
481 glNormal3f(-1, 0, 0);
482 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
483 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
486 glPushAttrib (GL_LIGHTING);
487 glDisable (GL_LIGHTING);
489 glColor3f (c2[0], c2[1], c2[2]);
491 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
492 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
493 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
494 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
495 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
496 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
503 /* Since PDB files don't always have the molecule centered around the
504 origin, and since some molecules are pretty large, scale and/or
505 translate so that the whole molecule is visible in the window.
508 ensure_bounding_box_visible (ModeInfo *mi)
510 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
512 GLfloat x1, y1, z1, x2, y2, z2;
515 GLfloat max_size = 10; /* don't bother scaling down if the molecule
516 is already smaller than this */
518 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
523 size = (w > h ? w : h);
524 size = (size > d ? size : d);
526 mc->molecule_size = size;
532 GLfloat scale = max_size / size;
533 glScalef (scale, scale, scale);
535 scale_down = scale < 0.3;
538 glTranslatef (-(x1 + w/2),
545 print_title_string (ModeInfo *mi, const char *string,
546 GLfloat x, GLfloat y, XFontStruct *font)
548 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
549 GLfloat line_height = font->ascent + font->descent;
550 GLfloat sub_shift = (line_height * 0.3);
554 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
555 GL_ENABLE_BIT); /* for various glDisable calls */
556 glDisable (GL_LIGHTING);
557 glDisable (GL_DEPTH_TEST);
559 glMatrixMode(GL_PROJECTION);
564 glMatrixMode(GL_MODELVIEW);
572 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
574 set_atom_color (mi, 0, True);
576 glRasterPos2f (x, y);
577 for (i = 0; i < strlen(string); i++)
582 glRasterPos2f (x, (y -= line_height));
585 else if (c == '(' && (isdigit (string[i+1])))
588 glRasterPos2f (x2, (y -= sub_shift));
590 else if (c == ')' && sub_p)
593 glRasterPos2f (x2, (y += sub_shift));
597 glCallList (mc->font2_dlist + (int)(c));
598 x2 += (font->per_char
599 ? font->per_char[c - font->min_char_or_byte2].width
600 : font->min_bounds.width);
606 glMatrixMode(GL_PROJECTION);
611 glMatrixMode(GL_MODELVIEW);
615 /* Constructs the GL shapes of the current molecule
618 build_molecule (ModeInfo *mi)
620 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
621 int wire = MI_IS_WIREFRAME(mi);
624 molecule *m = &mc->molecules[mc->which];
628 glDisable(GL_CULL_FACE);
629 glDisable(GL_LIGHTING);
630 glDisable(GL_LIGHT0);
631 glDisable(GL_DEPTH_TEST);
632 glDisable(GL_NORMALIZE);
633 glDisable(GL_CULL_FACE);
637 glEnable(GL_CULL_FACE);
638 glEnable(GL_LIGHTING);
640 glEnable(GL_DEPTH_TEST);
641 glEnable(GL_NORMALIZE);
642 glEnable(GL_CULL_FACE);
646 set_atom_color (mi, 0, False);
649 for (i = 0; i < m->nbonds; i++)
651 molecule_bond *b = &m->bonds[i];
652 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
653 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
658 glVertex3f(from->x, from->y, from->z);
659 glVertex3f(to->x, to->y, to->z);
664 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
670 GLfloat thickness = 0.07 * b->strength;
671 GLfloat cap_size = 0.03;
675 tube (from->x, from->y, from->z,
678 faces, smooth, False, wire);
682 if (!wire && do_atoms)
683 for (i = 0; i < m->natoms; i++)
685 molecule_atom *a = &m->atoms[i];
686 GLfloat size = atom_size (a);
687 set_atom_color (mi, a, False);
688 sphere (a->x, a->y, a->z, size, wire);
692 draw_bounding_box (mi);
694 if (do_titles && m->label && *m->label)
695 print_title_string (mi, m->label,
696 10, mi->xgwa.height - 10,
705 push_atom (molecule *m,
706 int id, const char *label,
707 GLfloat x, GLfloat y, GLfloat z)
710 if (m->atoms_size < m->natoms)
713 m->atoms = (molecule_atom *) realloc (m->atoms,
714 m->atoms_size * sizeof(*m->atoms));
716 m->atoms[m->natoms-1].id = id;
717 m->atoms[m->natoms-1].label = label;
718 m->atoms[m->natoms-1].x = x;
719 m->atoms[m->natoms-1].y = y;
720 m->atoms[m->natoms-1].z = z;
721 m->atoms[m->natoms-1].data = get_atom_data (label);
726 push_bond (molecule *m, int from, int to)
730 for (i = 0; i < m->nbonds; i++)
731 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
732 (m->bonds[i].to == from && m->bonds[i].from == to))
734 m->bonds[i].strength++;
739 if (m->bonds_size < m->nbonds)
742 m->bonds = (molecule_bond *) realloc (m->bonds,
743 m->bonds_size * sizeof(*m->bonds));
745 m->bonds[m->nbonds-1].from = from;
746 m->bonds[m->nbonds-1].to = to;
747 m->bonds[m->nbonds-1].strength = 1;
752 /* This function is crap.
755 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
757 const char *s = data;
761 if ((!m->label || !*m->label) &&
762 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
764 char *name = calloc (1, 100);
771 while (isspace(*n2)) n2++;
773 ss = strchr (n2, '\n');
775 ss = strchr (n2, '\r');
778 ss = n2+strlen(n2)-1;
779 while (isspace(*ss) && ss > n2)
782 if (strlen (n2) > 4 &&
783 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
784 n2[strlen(n2)-4] = 0;
786 if (m->label) free ((char *) m->label);
787 m->label = strdup (n2);
790 else if (!strncmp (s, "TITLE ", 6) ||
791 !strncmp (s, "HEADER", 6) ||
792 !strncmp (s, "COMPND", 6) ||
793 !strncmp (s, "AUTHOR", 6) ||
794 !strncmp (s, "REVDAT", 6) ||
795 !strncmp (s, "SOURCE", 6) ||
796 !strncmp (s, "EXPDTA", 6) ||
797 !strncmp (s, "JRNL ", 6) ||
798 !strncmp (s, "REMARK", 6) ||
799 !strncmp (s, "SEQRES", 6) ||
800 !strncmp (s, "HET ", 6) ||
801 !strncmp (s, "FORMUL", 6) ||
802 !strncmp (s, "CRYST1", 6) ||
803 !strncmp (s, "ORIGX1", 6) ||
804 !strncmp (s, "ORIGX2", 6) ||
805 !strncmp (s, "ORIGX3", 6) ||
806 !strncmp (s, "SCALE1", 6) ||
807 !strncmp (s, "SCALE2", 6) ||
808 !strncmp (s, "SCALE3", 6) ||
809 !strncmp (s, "MASTER", 6) ||
810 !strncmp (s, "KEYWDS", 6) ||
811 !strncmp (s, "DBREF ", 6) ||
812 !strncmp (s, "HETNAM", 6) ||
813 !strncmp (s, "HETSYN", 6) ||
814 !strncmp (s, "HELIX ", 6) ||
815 !strncmp (s, "LINK ", 6) ||
816 !strncmp (s, "MTRIX1", 6) ||
817 !strncmp (s, "MTRIX2", 6) ||
818 !strncmp (s, "MTRIX3", 6) ||
819 !strncmp (s, "SHEET ", 6) ||
820 !strncmp (s, "CISPEP", 6) ||
821 !strncmp (s, "GENERATED BY", 12) ||
822 !strncmp (s, "TER ", 4) ||
823 !strncmp (s, "END ", 4) ||
824 !strncmp (s, "TER\n", 4) ||
825 !strncmp (s, "END\n", 4) ||
826 !strncmp (s, "\n", 1))
829 else if (!strncmp (s, "ATOM ", 7))
832 char *name = (char *) calloc (1, 4);
833 GLfloat x = -999, y = -999, z = -999;
835 sscanf (s+7, " %d ", &id);
837 strncpy (name, s+12, 3);
838 while (isspace(*name)) name++;
839 ss = name + strlen(name)-1;
840 while (isspace(*ss) && ss > name)
842 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
844 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
845 progname, filename, line,
848 push_atom (m, id, name, x, y, z);
850 else if (!strncmp (s, "HETATM ", 7))
853 char *name = (char *) calloc (1, 4);
854 GLfloat x = -999, y = -999, z = -999;
856 sscanf (s+7, " %d ", &id);
858 strncpy (name, s+12, 3);
859 while (isspace(*name)) name++;
860 ss = name + strlen(name)-1;
861 while (isspace(*ss) && ss > name)
863 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
865 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
866 progname, filename, line,
869 push_atom (m, id, name, x, y, z);
871 else if (!strncmp (s, "CONECT ", 7))
874 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
875 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
876 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
877 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
879 for (j = 1; j < i; j++)
883 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
884 progname, filename, line, atoms[0], atoms[j]);
886 push_bond (m, atoms[0], atoms[j]);
891 char *s1 = strdup (s);
892 for (ss = s1; *ss && *ss != '\n'; ss++)
895 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
896 progname, filename, line, s1);
899 while (*s && *s != '\n')
909 parse_pdb_file (molecule *m, const char *name)
912 int buf_size = 40960;
916 in = fopen(name, "r");
919 char *buf = (char *) malloc(1024 + strlen(name));
920 sprintf(buf, "%s: error reading \"%s\"", progname, name);
925 buf = (char *) malloc (buf_size);
927 while (fgets (buf, buf_size-1, in))
930 for (s = buf; *s; s++)
931 if (*s == '\r') *s = '\n';
932 parse_pdb_data (m, buf, name, line++);
940 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
945 if (!m->nbonds && do_bonds)
947 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
954 typedef struct { char *atom; int count; } atom_and_count;
956 /* When listing the components of a molecule, the convention is to put the
957 carbon atoms first, the hydrogen atoms second, and the other atom types
958 sorted alphabetically after that (although for some molecules, the usual
959 order is different: we special-case a few of those.)
962 cmp_atoms (const void *aa, const void *bb)
964 const atom_and_count *a = (atom_and_count *) aa;
965 const atom_and_count *b = (atom_and_count *) bb;
966 if (!a->atom) return 1;
967 if (!b->atom) return -1;
968 if (!strcmp(a->atom, "C")) return -1;
969 if (!strcmp(b->atom, "C")) return 1;
970 if (!strcmp(a->atom, "H")) return -1;
971 if (!strcmp(b->atom, "H")) return 1;
972 return strcmp (a->atom, b->atom);
975 static void special_case_formula (char *f);
978 generate_molecule_formula (molecule *m)
980 char *buf = (char *) malloc (m->natoms * 10);
983 atom_and_count counts[200];
984 memset (counts, 0, sizeof(counts));
986 for (i = 0; i < m->natoms; i++)
989 char *a = (char *) m->atoms[i].label;
991 while (!isalpha(*a)) a++;
993 for (e = a; isalpha(*e); e++);
995 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1005 while (counts[i].atom) i++;
1006 qsort (counts, i, sizeof(*counts), cmp_atoms);
1009 while (counts[i].atom)
1011 strcat (s, counts[i].atom);
1012 free (counts[i].atom);
1014 if (counts[i].count > 1)
1015 sprintf (s, "(%d)", counts[i].count);
1020 special_case_formula (buf);
1022 if (!m->label) m->label = strdup("");
1023 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1024 strcpy (s, m->label);
1027 free ((char *) m->label);
1032 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1034 special_case_formula (char *f)
1036 if (!strcmp(f, "H(2)Be")) strcpy(f, "BeH(2)");
1037 else if (!strcmp(f, "H(3)B")) strcpy(f, "BH(3)");
1038 else if (!strcmp(f, "H(3)N")) strcpy(f, "NH(3)");
1039 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1040 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1041 else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
1042 else if (!strcmp(f, "Cl(3)P")) strcpy(f, "PCl(3)");
1043 else if (!strcmp(f, "Cl(5)P")) strcpy(f, "PCl(5)");
1048 insert_vertical_whitespace (char *string)
1052 if ((string[0] == ',' ||
1054 string[0] == ':') &&
1056 string[0] = ' ', string[1] = '\n';
1062 /* Construct the molecule data from either: the builtins; or from
1063 the (one) .pdb file specified with -molecule.
1066 load_molecules (ModeInfo *mi)
1068 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1069 int wire = MI_IS_WIREFRAME(mi);
1071 if (!molecule_str || !*molecule_str ||
1072 !strcmp(molecule_str, "(default)")) /* do the builtins */
1075 mc->nmolecules = countof(builtin_pdb_data);
1076 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1077 for (i = 0; i < mc->nmolecules; i++)
1080 sprintf (name, "<builtin-%d>", i);
1081 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1082 generate_molecule_formula (&mc->molecules[i]);
1083 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1086 else /* Load a file */
1090 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1091 parse_pdb_file (&mc->molecules[i], molecule_str);
1092 generate_molecule_formula (&mc->molecules[i]);
1093 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1095 if ((wire || !do_atoms) &&
1097 mc->molecules[i].nbonds == 0)
1099 /* If we're not drawing atoms (e.g., wireframe mode), and
1100 there is no bond info, then make sure labels are turned on,
1101 or we'll be looking at a black screen... */
1102 fprintf (stderr, "%s: no bonds: turning -label on.\n", progname);
1110 /* Window management, etc
1113 reshape_molecule (ModeInfo *mi, int width, int height)
1115 GLfloat h = (GLfloat) height / (GLfloat) width;
1117 glViewport (0, 0, (GLint) width, (GLint) height);
1119 glMatrixMode(GL_PROJECTION);
1121 gluPerspective (30.0, 1/h, 20.0, 40.0);
1123 glMatrixMode(GL_MODELVIEW);
1125 gluLookAt( 0.0, 0.0, 30.0,
1129 glClear(GL_COLOR_BUFFER_BIT);
1134 gl_init (ModeInfo *mi)
1136 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1137 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1138 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1139 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1140 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1141 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1142 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1143 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1145 orig_do_labels = do_labels;
1146 orig_do_bonds = do_bonds;
1147 orig_wire = MI_IS_WIREFRAME(mi);
1152 startup_blurb (ModeInfo *mi)
1154 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1155 const char *s = "Constructing molecules...";
1156 print_title_string (mi, s,
1157 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1158 10 + mc->xfont2->ascent + mc->xfont2->descent,
1161 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1165 molecule_handle_event (ModeInfo *mi, XEvent *event)
1167 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1169 if (event->xany.type == ButtonPress &&
1170 event->xbutton.button & Button1)
1172 mc->button_down_p = True;
1173 gltrackball_start (mc->trackball,
1174 event->xbutton.x, event->xbutton.y,
1175 MI_WIDTH (mi), MI_HEIGHT (mi));
1178 else if (event->xany.type == ButtonRelease &&
1179 event->xbutton.button & Button1)
1181 mc->button_down_p = False;
1184 else if (event->xany.type == MotionNotify &&
1187 gltrackball_track (mc->trackball,
1188 event->xmotion.x, event->xmotion.y,
1189 MI_WIDTH (mi), MI_HEIGHT (mi));
1198 init_molecule (ModeInfo *mi)
1200 molecule_configuration *mc;
1204 mcs = (molecule_configuration *)
1205 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1207 fprintf(stderr, "%s: out of memory\n", progname);
1212 mc = &mcs[MI_SCREEN(mi)];
1214 if ((mc->glx_context = init_GL(mi)) != NULL) {
1216 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1222 wire = MI_IS_WIREFRAME(mi);
1225 Bool spinx=False, spiny=False, spinz=False;
1226 double spin_speed = 2.0;
1227 double wander_speed = 0.03;
1232 if (*s == 'x' || *s == 'X') spinx = True;
1233 else if (*s == 'y' || *s == 'Y') spiny = True;
1234 else if (*s == 'z' || *s == 'Z') spinz = True;
1238 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1245 mc->rot = make_rotator (spinx ? spin_speed : 0,
1246 spiny ? spin_speed : 0,
1247 spinz ? spin_speed : 0,
1249 do_wander ? wander_speed : 0,
1250 (spinx && spiny && spinz));
1251 mc->trackball = gltrackball_init ();
1254 mc->molecule_dlist = glGenLists(1);
1256 load_molecules (mi);
1257 mc->which = random() % mc->nmolecules;
1259 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1260 "NoLabelThreshold");
1261 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1262 "WireframeThreshold");
1269 /* Put the labels on the atoms.
1270 This can't be a part of the display list because of the games
1271 we play with the translation matrix.
1274 draw_labels (ModeInfo *mi)
1276 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1277 int wire = MI_IS_WIREFRAME(mi);
1278 molecule *m = &mc->molecules[mc->which];
1285 glDisable (GL_LIGHTING); /* don't light fonts */
1287 for (i = 0; i < m->natoms; i++)
1289 molecule_atom *a = &m->atoms[i];
1290 GLfloat size = atom_size (a);
1296 set_atom_color (mi, a, True);
1298 /* First, we translate the origin to the center of the atom.
1300 Then we retrieve the prevailing modelview matrix (which
1301 includes any rotation, wandering, and user-trackball-rolling
1304 We set the top 3x3 cells of that matrix to be the identity
1305 matrix. This removes all rotation from the matrix, while
1306 leaving the translation alone. This has the effect of
1307 leaving the prevailing coordinate system perpendicular to
1308 the camera view: were we to draw a square face, it would
1309 be in the plane of the screen.
1311 Now we translate by `size' toward the viewer -- so that the
1312 origin is *just in front* of the ball.
1314 Then we draw the label text, allowing the depth buffer to
1315 do its work: that way, labels on atoms will be occluded
1316 properly when other atoms move in front of them.
1318 This technique (of neutralizing rotation relative to the
1319 observer, after both rotations and translations have been
1320 applied) is known as "billboarding".
1323 glTranslatef(a->x, a->y, a->z); /* get matrix */
1324 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1325 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1326 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1327 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1328 glLoadIdentity(); /* reset modelview */
1329 glMultMatrixf (&m[0][0]); /* replace with ours */
1331 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1333 glRasterPos3f (0, 0, 0); /* draw text here */
1335 /* Before drawing the string, shift the origin to center
1336 the text over the origin of the sphere. */
1337 glBitmap (0, 0, 0, 0,
1338 -string_width (mc->xfont1, a->label) / 2,
1339 -mc->xfont1->descent,
1342 for (j = 0; j < strlen(a->label); j++)
1343 glCallList (mc->font1_dlist + (int)(a->label[j]));
1348 /* More efficient to always call glEnable() with correct values
1349 than to call glPushAttrib()/glPopAttrib(), since reading
1350 attributes from GL does a round-trip and stalls the pipeline.
1353 glEnable (GL_LIGHTING);
1358 draw_molecule (ModeInfo *mi)
1360 static time_t last = 0;
1361 time_t now = time ((time_t *) 0);
1363 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1364 Display *dpy = MI_DISPLAY(mi);
1365 Window window = MI_WINDOW(mi);
1367 if (!mc->glx_context)
1370 if (last + timeout <= now && /* randomize molecules every -timeout seconds */
1373 if (mc->nmolecules == 1)
1375 if (last != 0) goto SKIP;
1380 mc->which = random() % mc->nmolecules;
1385 while (n == mc->which)
1386 n = random() % mc->nmolecules;
1393 glNewList (mc->molecule_dlist, GL_COMPILE);
1394 ensure_bounding_box_visible (mi);
1396 do_labels = orig_do_labels;
1397 do_bonds = orig_do_bonds;
1398 MI_IS_WIREFRAME(mi) = orig_wire;
1400 if (mc->molecule_size > mc->no_label_threshold)
1402 if (mc->molecule_size > mc->wireframe_threshold)
1403 MI_IS_WIREFRAME(mi) = 1;
1405 if (MI_IS_WIREFRAME(mi))
1408 build_molecule (mi);
1414 glScalef(1.1, 1.1, 1.1);
1418 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1419 glTranslatef((x - 0.5) * 9,
1423 gltrackball_rotate (mc->trackball);
1425 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1426 glRotatef (x * 360, 1.0, 0.0, 0.0);
1427 glRotatef (y * 360, 0.0, 1.0, 0.0);
1428 glRotatef (z * 360, 0.0, 0.0, 1.0);
1431 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1432 glCallList (mc->molecule_dlist);
1437 if (mi->fps_p) do_fps (mi);
1440 glXSwapBuffers(dpy, window);