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
94 __extension__ /* don't warn about "string length is greater than the length
95 ISO C89 compilers are required to support" when includng
96 the following data file... */
98 const char * const builtin_pdb_data[] = {
99 # include "molecules.h"
107 const char *text_color;
112 /* These are the traditional colors used to render these atoms,
113 and their approximate size in angstroms.
115 static atom_data all_atom_data[] = {
116 { "H", 1.17, 0, "White", "Grey70", { 0, }},
117 { "C", 1.75, 0, "Grey60", "White", { 0, }},
118 { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }},
119 { "O", 1.40, 0, "Red", "LightPink", { 0, }},
120 { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }},
121 { "S", 1.80, 0, "Yellow4", "Yellow1", { 0, }},
122 { "bond", 0, 0, "Grey70", "Yellow1", { 0, }},
123 { "*", 1.40, 0, "Green4", "LightGreen", { 0, }}
128 int id; /* sequence number in the PDB file */
129 const char *label; /* The atom name */
130 GLfloat x, y, z; /* position in 3-space (angstroms) */
131 atom_data *data; /* computed: which style of atom this is */
135 int from, to; /* atom sequence numbers */
136 int strength; /* how many bonds are between these two atoms */
141 const char *label; /* description of this compound */
142 int natoms, atoms_size;
143 int nbonds, bonds_size;
144 molecule_atom *atoms;
145 molecule_bond *bonds;
150 GLXContext *glx_context;
152 trackball_state *trackball;
155 GLfloat molecule_size; /* max dimension of molecule bounding box */
157 GLfloat no_label_threshold; /* Things happen when molecules are huge */
158 GLfloat wireframe_threshold;
160 int which; /* which of the molecules is being shown */
164 GLuint molecule_dlist;
166 XFontStruct *xfont1, *xfont2;
167 GLuint font1_dlist, font2_dlist;
169 } molecule_configuration;
172 static molecule_configuration *mcs = NULL;
175 static char *molecule_str;
176 static char *do_spin;
177 static Bool do_wander;
178 static Bool do_titles;
179 static Bool do_labels;
180 static Bool do_atoms;
181 static Bool do_bonds;
184 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
187 static XrmOptionDescRec opts[] = {
188 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
189 { "-timeout",".timeout",XrmoptionSepArg, 0 },
190 { "-spin", ".spin", XrmoptionSepArg, 0 },
191 { "+spin", ".spin", XrmoptionNoArg, "" },
192 { "-wander", ".wander", XrmoptionNoArg, "True" },
193 { "+wander", ".wander", XrmoptionNoArg, "False" },
194 { "-labels", ".labels", XrmoptionNoArg, "True" },
195 { "+labels", ".labels", XrmoptionNoArg, "False" },
196 { "-titles", ".titles", XrmoptionNoArg, "True" },
197 { "+titles", ".titles", XrmoptionNoArg, "False" },
198 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
199 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
200 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
201 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
202 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
203 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
206 static argtype vars[] = {
207 {(caddr_t *) &molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
208 {(caddr_t *) &timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
209 {(caddr_t *) &do_spin, "spin", "Spin", DEF_SPIN, t_String},
210 {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
211 {(caddr_t *) &do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
212 {(caddr_t *) &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
213 {(caddr_t *) &do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
214 {(caddr_t *) &do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
215 {(caddr_t *) &do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
218 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
226 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
228 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
229 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
232 glTranslatef (x, y, z);
233 glScalef (diameter, diameter, diameter);
234 unit_sphere (stacks, slices, wire);
240 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
242 const char *font = get_string_resource (res, "Font");
247 if (!font) font = "-*-times-bold-r-normal-*-180-*";
249 f = XLoadQueryFont(mi->dpy, font);
250 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
253 first = f->min_char_or_byte2;
254 last = f->max_char_or_byte2;
257 *dlistP = glGenLists ((GLuint) last+1);
258 check_gl_error ("glGenLists");
259 glXUseXFont(id, first, last-first+1, *dlistP + first);
260 check_gl_error ("glXUseXFont");
267 load_fonts (ModeInfo *mi)
269 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
270 load_font (mi, "atomFont", &mc->xfont1, &mc->font1_dlist);
271 load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
276 string_width (XFontStruct *f, const char *c)
281 int cc = *((unsigned char *) c);
283 ? f->per_char[cc-f->min_char_or_byte2].rbearing
284 : f->min_bounds.rbearing);
292 get_atom_data (const char *atom_name)
296 char *n = strdup (atom_name);
300 while (!isalpha(*n)) n++;
302 while (L > 0 && !isalpha(n[L-1]))
305 for (i = 0; i < countof(all_atom_data); i++)
307 d = &all_atom_data[i];
308 if (!strcmp (n, all_atom_data[i].name))
318 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
327 static atom_data *def_data = 0;
328 if (!def_data) def_data = get_atom_data ("bond");
332 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
334 if (gl_color[3] == 0)
336 const char *string = !font_p ? d->color : d->text_color;
338 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
340 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
341 (a ? a->label : d->name), string);
345 gl_color[0] = xcolor.red / 65536.0;
346 gl_color[1] = xcolor.green / 65536.0;
347 gl_color[2] = xcolor.blue / 65536.0;
352 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
354 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
359 atom_size (molecule_atom *a)
363 if (a->data->size2 == 0)
365 /* let the molecules have the same relative sizes, but scale
366 them to a smaller range, so that the bond-tubes are
373 GLfloat ratio = (a->data->size - min) / (max - min);
374 a->data->size2 = bot + (ratio * (top - bot));
376 return a->data->size2;
379 return a->data->size;
383 static molecule_atom *
384 get_atom (molecule_atom *atoms, int natoms, int id)
388 /* quick short-circuit */
391 if (atoms[id].id == id)
393 if (id > 0 && atoms[id-1].id == id)
395 if (id < natoms-1 && atoms[id+1].id == id)
399 for (i = 0; i < natoms; i++)
400 if (id == atoms[i].id)
403 fprintf (stderr, "%s: no atom %d\n", progname, id);
409 molecule_bounding_box (ModeInfo *mi,
410 GLfloat *x1, GLfloat *y1, GLfloat *z1,
411 GLfloat *x2, GLfloat *y2, GLfloat *z2)
413 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
414 molecule *m = &mc->molecules[mc->which];
419 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
423 *x1 = *x2 = m->atoms[0].x;
424 *y1 = *y2 = m->atoms[0].y;
425 *z1 = *z2 = m->atoms[0].z;
428 for (i = 1; i < m->natoms; i++)
430 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
431 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
432 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
434 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
435 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
436 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
449 draw_bounding_box (ModeInfo *mi)
451 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
452 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
453 int wire = MI_IS_WIREFRAME(mi);
454 GLfloat x1, y1, z1, x2, y2, z2;
455 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
457 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
460 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
462 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
463 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
465 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
466 glNormal3f(0, -1, 0);
467 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
468 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
470 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
472 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
473 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
475 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
476 glNormal3f(0, 0, -1);
477 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
478 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
480 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
482 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
483 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
485 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
486 glNormal3f(-1, 0, 0);
487 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
488 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
491 glPushAttrib (GL_LIGHTING);
492 glDisable (GL_LIGHTING);
494 glColor3f (c2[0], c2[1], c2[2]);
496 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
497 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
498 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
499 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
500 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
501 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
508 /* Since PDB files don't always have the molecule centered around the
509 origin, and since some molecules are pretty large, scale and/or
510 translate so that the whole molecule is visible in the window.
513 ensure_bounding_box_visible (ModeInfo *mi)
515 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
517 GLfloat x1, y1, z1, x2, y2, z2;
520 GLfloat max_size = 10; /* don't bother scaling down if the molecule
521 is already smaller than this */
523 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
528 size = (w > h ? w : h);
529 size = (size > d ? size : d);
531 mc->molecule_size = size;
537 GLfloat scale = max_size / size;
538 glScalef (scale, scale, scale);
540 scale_down = scale < 0.3;
543 glTranslatef (-(x1 + w/2),
550 print_title_string (ModeInfo *mi, const char *string,
551 GLfloat x, GLfloat y, XFontStruct *font)
553 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
554 GLfloat line_height = font->ascent + font->descent;
555 GLfloat sub_shift = (line_height * 0.3);
559 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
560 GL_ENABLE_BIT); /* for various glDisable calls */
561 glDisable (GL_LIGHTING);
562 glDisable (GL_DEPTH_TEST);
564 glMatrixMode(GL_PROJECTION);
569 glMatrixMode(GL_MODELVIEW);
577 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
579 set_atom_color (mi, 0, True);
581 glRasterPos2f (x, y);
582 for (i = 0; i < strlen(string); i++)
587 glRasterPos2f (x, (y -= line_height));
590 else if (c == '(' && (isdigit (string[i+1])))
593 glRasterPos2f (x2, (y -= sub_shift));
595 else if (c == ')' && sub_p)
598 glRasterPos2f (x2, (y += sub_shift));
602 glCallList (mc->font2_dlist + (int)(c));
603 x2 += (font->per_char
604 ? font->per_char[c - font->min_char_or_byte2].width
605 : font->min_bounds.width);
611 glMatrixMode(GL_PROJECTION);
616 glMatrixMode(GL_MODELVIEW);
620 /* Constructs the GL shapes of the current molecule
623 build_molecule (ModeInfo *mi)
625 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
626 int wire = MI_IS_WIREFRAME(mi);
629 molecule *m = &mc->molecules[mc->which];
633 glDisable(GL_CULL_FACE);
634 glDisable(GL_LIGHTING);
635 glDisable(GL_LIGHT0);
636 glDisable(GL_DEPTH_TEST);
637 glDisable(GL_NORMALIZE);
638 glDisable(GL_CULL_FACE);
642 glEnable(GL_CULL_FACE);
643 glEnable(GL_LIGHTING);
645 glEnable(GL_DEPTH_TEST);
646 glEnable(GL_NORMALIZE);
647 glEnable(GL_CULL_FACE);
651 set_atom_color (mi, 0, False);
654 for (i = 0; i < m->nbonds; i++)
656 molecule_bond *b = &m->bonds[i];
657 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
658 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
663 glVertex3f(from->x, from->y, from->z);
664 glVertex3f(to->x, to->y, to->z);
669 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
675 GLfloat thickness = 0.07 * b->strength;
676 GLfloat cap_size = 0.03;
680 tube (from->x, from->y, from->z,
683 faces, smooth, False, wire);
687 if (!wire && do_atoms)
688 for (i = 0; i < m->natoms; i++)
690 molecule_atom *a = &m->atoms[i];
691 GLfloat size = atom_size (a);
692 set_atom_color (mi, a, False);
693 sphere (a->x, a->y, a->z, size, wire);
697 draw_bounding_box (mi);
699 if (do_titles && m->label && *m->label)
700 print_title_string (mi, m->label,
701 10, mi->xgwa.height - 10,
710 push_atom (molecule *m,
711 int id, const char *label,
712 GLfloat x, GLfloat y, GLfloat z)
715 if (m->atoms_size < m->natoms)
718 m->atoms = (molecule_atom *) realloc (m->atoms,
719 m->atoms_size * sizeof(*m->atoms));
721 m->atoms[m->natoms-1].id = id;
722 m->atoms[m->natoms-1].label = label;
723 m->atoms[m->natoms-1].x = x;
724 m->atoms[m->natoms-1].y = y;
725 m->atoms[m->natoms-1].z = z;
726 m->atoms[m->natoms-1].data = get_atom_data (label);
731 push_bond (molecule *m, int from, int to)
735 for (i = 0; i < m->nbonds; i++)
736 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
737 (m->bonds[i].to == from && m->bonds[i].from == to))
739 m->bonds[i].strength++;
744 if (m->bonds_size < m->nbonds)
747 m->bonds = (molecule_bond *) realloc (m->bonds,
748 m->bonds_size * sizeof(*m->bonds));
750 m->bonds[m->nbonds-1].from = from;
751 m->bonds[m->nbonds-1].to = to;
752 m->bonds[m->nbonds-1].strength = 1;
757 /* This function is crap.
760 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
762 const char *s = data;
766 if ((!m->label || !*m->label) &&
767 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
769 char *name = calloc (1, 100);
776 while (isspace(*n2)) n2++;
778 ss = strchr (n2, '\n');
780 ss = strchr (n2, '\r');
783 ss = n2+strlen(n2)-1;
784 while (isspace(*ss) && ss > n2)
787 if (strlen (n2) > 4 &&
788 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
789 n2[strlen(n2)-4] = 0;
791 if (m->label) free ((char *) m->label);
792 m->label = strdup (n2);
795 else if (!strncmp (s, "TITLE ", 6) ||
796 !strncmp (s, "HEADER", 6) ||
797 !strncmp (s, "COMPND", 6) ||
798 !strncmp (s, "AUTHOR", 6) ||
799 !strncmp (s, "REVDAT", 6) ||
800 !strncmp (s, "SOURCE", 6) ||
801 !strncmp (s, "EXPDTA", 6) ||
802 !strncmp (s, "JRNL ", 6) ||
803 !strncmp (s, "REMARK", 6) ||
804 !strncmp (s, "SEQRES", 6) ||
805 !strncmp (s, "HET ", 6) ||
806 !strncmp (s, "FORMUL", 6) ||
807 !strncmp (s, "CRYST1", 6) ||
808 !strncmp (s, "ORIGX1", 6) ||
809 !strncmp (s, "ORIGX2", 6) ||
810 !strncmp (s, "ORIGX3", 6) ||
811 !strncmp (s, "SCALE1", 6) ||
812 !strncmp (s, "SCALE2", 6) ||
813 !strncmp (s, "SCALE3", 6) ||
814 !strncmp (s, "MASTER", 6) ||
815 !strncmp (s, "KEYWDS", 6) ||
816 !strncmp (s, "DBREF ", 6) ||
817 !strncmp (s, "HETNAM", 6) ||
818 !strncmp (s, "HETSYN", 6) ||
819 !strncmp (s, "HELIX ", 6) ||
820 !strncmp (s, "LINK ", 6) ||
821 !strncmp (s, "MTRIX1", 6) ||
822 !strncmp (s, "MTRIX2", 6) ||
823 !strncmp (s, "MTRIX3", 6) ||
824 !strncmp (s, "SHEET ", 6) ||
825 !strncmp (s, "CISPEP", 6) ||
826 !strncmp (s, "GENERATED BY", 12) ||
827 !strncmp (s, "TER ", 4) ||
828 !strncmp (s, "END ", 4) ||
829 !strncmp (s, "TER\n", 4) ||
830 !strncmp (s, "END\n", 4) ||
831 !strncmp (s, "\n", 1))
834 else if (!strncmp (s, "ATOM ", 7))
837 char *name = (char *) calloc (1, 4);
838 GLfloat x = -999, y = -999, z = -999;
840 sscanf (s+7, " %d ", &id);
842 strncpy (name, s+12, 3);
843 while (isspace(*name)) name++;
844 ss = name + strlen(name)-1;
845 while (isspace(*ss) && ss > name)
847 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
849 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
850 progname, filename, line,
853 push_atom (m, id, name, x, y, z);
855 else if (!strncmp (s, "HETATM ", 7))
858 char *name = (char *) calloc (1, 4);
859 GLfloat x = -999, y = -999, z = -999;
861 sscanf (s+7, " %d ", &id);
863 strncpy (name, s+12, 3);
864 while (isspace(*name)) name++;
865 ss = name + strlen(name)-1;
866 while (isspace(*ss) && ss > name)
868 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
870 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
871 progname, filename, line,
874 push_atom (m, id, name, x, y, z);
876 else if (!strncmp (s, "CONECT ", 7))
879 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
880 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
881 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
882 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
884 for (j = 1; j < i; j++)
888 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
889 progname, filename, line, atoms[0], atoms[j]);
891 push_bond (m, atoms[0], atoms[j]);
896 char *s1 = strdup (s);
897 for (ss = s1; *ss && *ss != '\n'; ss++)
900 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
901 progname, filename, line, s1);
904 while (*s && *s != '\n')
914 parse_pdb_file (molecule *m, const char *name)
917 int buf_size = 40960;
921 in = fopen(name, "r");
924 char *buf = (char *) malloc(1024 + strlen(name));
925 sprintf(buf, "%s: error reading \"%s\"", progname, name);
930 buf = (char *) malloc (buf_size);
932 while (fgets (buf, buf_size-1, in))
935 for (s = buf; *s; s++)
936 if (*s == '\r') *s = '\n';
937 parse_pdb_data (m, buf, name, line++);
945 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
950 if (!m->nbonds && do_bonds)
952 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
959 typedef struct { char *atom; int count; } atom_and_count;
961 /* When listing the components of a molecule, the convention is to put the
962 carbon atoms first, the hydrogen atoms second, and the other atom types
963 sorted alphabetically after that (although for some molecules, the usual
964 order is different: we special-case a few of those.)
967 cmp_atoms (const void *aa, const void *bb)
969 const atom_and_count *a = (atom_and_count *) aa;
970 const atom_and_count *b = (atom_and_count *) bb;
971 if (!a->atom) return 1;
972 if (!b->atom) return -1;
973 if (!strcmp(a->atom, "C")) return -1;
974 if (!strcmp(b->atom, "C")) return 1;
975 if (!strcmp(a->atom, "H")) return -1;
976 if (!strcmp(b->atom, "H")) return 1;
977 return strcmp (a->atom, b->atom);
980 static void special_case_formula (char *f);
983 generate_molecule_formula (molecule *m)
985 char *buf = (char *) malloc (m->natoms * 10);
988 atom_and_count counts[200];
989 memset (counts, 0, sizeof(counts));
991 for (i = 0; i < m->natoms; i++)
994 char *a = (char *) m->atoms[i].label;
996 while (!isalpha(*a)) a++;
998 for (e = a; isalpha(*e); e++);
1000 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1010 while (counts[i].atom) i++;
1011 qsort (counts, i, sizeof(*counts), cmp_atoms);
1014 while (counts[i].atom)
1016 strcat (s, counts[i].atom);
1017 free (counts[i].atom);
1019 if (counts[i].count > 1)
1020 sprintf (s, "(%d)", counts[i].count);
1025 special_case_formula (buf);
1027 if (!m->label) m->label = strdup("");
1028 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1029 strcpy (s, m->label);
1032 free ((char *) m->label);
1037 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1039 special_case_formula (char *f)
1041 if (!strcmp(f, "H(2)Be")) strcpy(f, "BeH(2)");
1042 else if (!strcmp(f, "H(3)B")) strcpy(f, "BH(3)");
1043 else if (!strcmp(f, "H(3)N")) strcpy(f, "NH(3)");
1044 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1045 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1046 else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
1047 else if (!strcmp(f, "Cl(3)P")) strcpy(f, "PCl(3)");
1048 else if (!strcmp(f, "Cl(5)P")) strcpy(f, "PCl(5)");
1053 insert_vertical_whitespace (char *string)
1057 if ((string[0] == ',' ||
1059 string[0] == ':') &&
1061 string[0] = ' ', string[1] = '\n';
1067 /* Construct the molecule data from either: the builtins; or from
1068 the (one) .pdb file specified with -molecule.
1071 load_molecules (ModeInfo *mi)
1073 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1074 int wire = MI_IS_WIREFRAME(mi);
1076 if (!molecule_str || !*molecule_str ||
1077 !strcmp(molecule_str, "(default)")) /* do the builtins */
1080 mc->nmolecules = countof(builtin_pdb_data);
1081 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1082 for (i = 0; i < mc->nmolecules; i++)
1085 sprintf (name, "<builtin-%d>", i);
1086 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1087 generate_molecule_formula (&mc->molecules[i]);
1088 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1091 else /* Load a file */
1095 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1096 parse_pdb_file (&mc->molecules[i], molecule_str);
1097 generate_molecule_formula (&mc->molecules[i]);
1098 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1100 if ((wire || !do_atoms) &&
1102 mc->molecules[i].nbonds == 0)
1104 /* If we're not drawing atoms (e.g., wireframe mode), and
1105 there is no bond info, then make sure labels are turned on,
1106 or we'll be looking at a black screen... */
1107 fprintf (stderr, "%s: no bonds: turning -label on.\n", progname);
1115 /* Window management, etc
1118 reshape_molecule (ModeInfo *mi, int width, int height)
1120 GLfloat h = (GLfloat) height / (GLfloat) width;
1122 glViewport (0, 0, (GLint) width, (GLint) height);
1124 glMatrixMode(GL_PROJECTION);
1126 gluPerspective (30.0, 1/h, 20.0, 40.0);
1128 glMatrixMode(GL_MODELVIEW);
1130 gluLookAt( 0.0, 0.0, 30.0,
1134 glClear(GL_COLOR_BUFFER_BIT);
1139 gl_init (ModeInfo *mi)
1141 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1142 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1143 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1144 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1145 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1146 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1147 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1148 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1150 orig_do_labels = do_labels;
1151 orig_do_bonds = do_bonds;
1152 orig_wire = MI_IS_WIREFRAME(mi);
1157 startup_blurb (ModeInfo *mi)
1159 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1160 const char *s = "Constructing molecules...";
1161 print_title_string (mi, s,
1162 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1163 10 + mc->xfont2->ascent + mc->xfont2->descent,
1166 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1170 molecule_handle_event (ModeInfo *mi, XEvent *event)
1172 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1174 if (event->xany.type == ButtonPress &&
1175 event->xbutton.button & Button1)
1177 mc->button_down_p = True;
1178 gltrackball_start (mc->trackball,
1179 event->xbutton.x, event->xbutton.y,
1180 MI_WIDTH (mi), MI_HEIGHT (mi));
1183 else if (event->xany.type == ButtonRelease &&
1184 event->xbutton.button & Button1)
1186 mc->button_down_p = False;
1189 else if (event->xany.type == MotionNotify &&
1192 gltrackball_track (mc->trackball,
1193 event->xmotion.x, event->xmotion.y,
1194 MI_WIDTH (mi), MI_HEIGHT (mi));
1203 init_molecule (ModeInfo *mi)
1205 molecule_configuration *mc;
1209 mcs = (molecule_configuration *)
1210 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1212 fprintf(stderr, "%s: out of memory\n", progname);
1217 mc = &mcs[MI_SCREEN(mi)];
1219 if ((mc->glx_context = init_GL(mi)) != NULL) {
1221 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1227 wire = MI_IS_WIREFRAME(mi);
1230 Bool spinx=False, spiny=False, spinz=False;
1231 double spin_speed = 2.0;
1232 double wander_speed = 0.03;
1237 if (*s == 'x' || *s == 'X') spinx = True;
1238 else if (*s == 'y' || *s == 'Y') spiny = True;
1239 else if (*s == 'z' || *s == 'Z') spinz = True;
1243 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1250 mc->rot = make_rotator (spinx ? spin_speed : 0,
1251 spiny ? spin_speed : 0,
1252 spinz ? spin_speed : 0,
1254 do_wander ? wander_speed : 0,
1255 (spinx && spiny && spinz));
1256 mc->trackball = gltrackball_init ();
1259 mc->molecule_dlist = glGenLists(1);
1261 load_molecules (mi);
1262 mc->which = random() % mc->nmolecules;
1264 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1265 "NoLabelThreshold");
1266 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1267 "WireframeThreshold");
1274 /* Put the labels on the atoms.
1275 This can't be a part of the display list because of the games
1276 we play with the translation matrix.
1279 draw_labels (ModeInfo *mi)
1281 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1282 int wire = MI_IS_WIREFRAME(mi);
1283 molecule *m = &mc->molecules[mc->which];
1290 glDisable (GL_LIGHTING); /* don't light fonts */
1292 for (i = 0; i < m->natoms; i++)
1294 molecule_atom *a = &m->atoms[i];
1295 GLfloat size = atom_size (a);
1301 set_atom_color (mi, a, True);
1303 /* First, we translate the origin to the center of the atom.
1305 Then we retrieve the prevailing modelview matrix (which
1306 includes any rotation, wandering, and user-trackball-rolling
1309 We set the top 3x3 cells of that matrix to be the identity
1310 matrix. This removes all rotation from the matrix, while
1311 leaving the translation alone. This has the effect of
1312 leaving the prevailing coordinate system perpendicular to
1313 the camera view: were we to draw a square face, it would
1314 be in the plane of the screen.
1316 Now we translate by `size' toward the viewer -- so that the
1317 origin is *just in front* of the ball.
1319 Then we draw the label text, allowing the depth buffer to
1320 do its work: that way, labels on atoms will be occluded
1321 properly when other atoms move in front of them.
1323 This technique (of neutralizing rotation relative to the
1324 observer, after both rotations and translations have been
1325 applied) is known as "billboarding".
1328 glTranslatef(a->x, a->y, a->z); /* get matrix */
1329 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1330 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1331 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1332 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1333 glLoadIdentity(); /* reset modelview */
1334 glMultMatrixf (&m[0][0]); /* replace with ours */
1336 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1338 glRasterPos3f (0, 0, 0); /* draw text here */
1340 /* Before drawing the string, shift the origin to center
1341 the text over the origin of the sphere. */
1342 glBitmap (0, 0, 0, 0,
1343 -string_width (mc->xfont1, a->label) / 2,
1344 -mc->xfont1->descent,
1347 for (j = 0; j < strlen(a->label); j++)
1348 glCallList (mc->font1_dlist + (int)(a->label[j]));
1353 /* More efficient to always call glEnable() with correct values
1354 than to call glPushAttrib()/glPopAttrib(), since reading
1355 attributes from GL does a round-trip and stalls the pipeline.
1358 glEnable (GL_LIGHTING);
1363 draw_molecule (ModeInfo *mi)
1365 static time_t last = 0;
1366 time_t now = time ((time_t *) 0);
1368 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1369 Display *dpy = MI_DISPLAY(mi);
1370 Window window = MI_WINDOW(mi);
1372 if (!mc->glx_context)
1375 if (last + timeout <= now && /* randomize molecules every -timeout seconds */
1378 if (mc->nmolecules == 1)
1380 if (last != 0) goto SKIP;
1385 mc->which = random() % mc->nmolecules;
1390 while (n == mc->which)
1391 n = random() % mc->nmolecules;
1398 glNewList (mc->molecule_dlist, GL_COMPILE);
1399 ensure_bounding_box_visible (mi);
1401 do_labels = orig_do_labels;
1402 do_bonds = orig_do_bonds;
1403 MI_IS_WIREFRAME(mi) = orig_wire;
1405 if (mc->molecule_size > mc->no_label_threshold)
1407 if (mc->molecule_size > mc->wireframe_threshold)
1408 MI_IS_WIREFRAME(mi) = 1;
1410 if (MI_IS_WIREFRAME(mi))
1413 build_molecule (mi);
1419 glScalef(1.1, 1.1, 1.1);
1423 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1424 glTranslatef((x - 0.5) * 9,
1428 gltrackball_rotate (mc->trackball);
1430 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1431 glRotatef (x * 360, 1.0, 0.0, 0.0);
1432 glRotatef (y * 360, 0.0, 1.0, 0.0);
1433 glRotatef (z * 360, 0.0, 0.0, 1.0);
1436 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1437 glCallList (mc->molecule_dlist);
1442 if (mi->fps_p) do_fps (mi);
1445 glXSwapBuffers(dpy, window);