1 /* molecule, Copyright (c) 2001-2012 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://en.wikipedia.org/wiki/Protein_Data_Bank_%28file_format%29
16 http://www.wwpdb.org/docs.html
17 http://www.wwpdb.org/documentation/format32/v3.2.html
18 http://www.wwpdb.org/documentation/format32/sect9.html
19 http://www.rcsb.org/pdb/file_formats/pdb/pdbguide2.2/guide2.2_frame.html
21 Good source of PDB files:
22 http://www.sci.ouc.bc.ca/chem/molecule/molecule.html
23 http://www.umass.edu/microbio/rasmol/whereget.htm
24 http://www.wwpdb.org/docs.html
28 # define ATOM_FONT "-*-helvetica-medium-r-normal-*-180-*"
30 # define ATOM_FONT "-*-helvetica-medium-r-normal-*-240-*"
33 #define DEFAULTS "*delay: 10000 \n" \
34 "*showFPS: False \n" \
35 "*wireframe: False \n" \
36 "*atomFont: " ATOM_FONT "\n" \
37 "*atomFont2: -*-helvetica-bold-r-normal-*-80-*\n" \
38 "*titleFont: -*-helvetica-medium-r-normal-*-180-*\n" \
39 "*noLabelThreshold: 30 \n" \
40 "*wireframeThreshold: 150 \n" \
42 # define refresh_molecule 0
43 # define release_molecule 0
45 #define countof(x) (sizeof((x))/sizeof((*x)))
47 #include "xlockmore.h"
53 #include "gltrackball.h"
55 #ifdef USE_GL /* whole file */
57 #include <sys/types.h>
62 #define DEF_TIMEOUT "20"
63 #define DEF_SPIN "XYZ"
64 #define DEF_WANDER "False"
65 #define DEF_LABELS "True"
66 #define DEF_TITLES "True"
67 #define DEF_ATOMS "True"
68 #define DEF_BONDS "True"
69 #define DEF_ESHELLS "True"
70 #define DEF_BBOX "False"
71 #define DEF_SHELL_ALPHA "0.3"
72 #define DEF_MOLECULE "(default)"
73 #define DEF_VERBOSE "False"
75 #define SPHERE_SLICES 48 /* how densely to render spheres */
76 #define SPHERE_STACKS 24
78 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
81 # define TUBE_FACES 12 /* how densely to render tubes */
86 #define SPHERE_SLICES_2 14
87 #define SPHERE_STACKS_2 8
88 #define TUBE_FACES_2 6
92 __extension__ /* don't warn about "string length is greater than the length
93 ISO C89 compilers are required to support" when includng
94 the following data file... */
96 const char * const builtin_pdb_data[] = {
97 # include "molecules.h"
105 const char *text_color;
110 /* These are the traditional colors used to render these atoms,
111 and their approximate size in angstroms.
113 static const atom_data all_atom_data[] = {
114 { "H", 1.17, 0.40, "#FFFFFF", "#000000", { 0, }},
115 { "C", 1.75, 0.58, "#999999", "#FFFFFF", { 0, }},
116 { "CA", 1.80, 0.60, "#0000FF", "#ADD8E6", { 0, }},
117 { "N", 1.55, 0.52, "#A2B5CD", "#EE99FF", { 0, }},
118 { "O", 1.40, 0.47, "#FF0000", "#FFB6C1", { 0, }},
119 { "P", 1.28, 0.43, "#9370DB", "#DB7093", { 0, }},
120 { "S", 1.80, 0.60, "#8B8B00", "#FFFF00", { 0, }},
121 { "bond", 0, 0, "#B3B3B3", "#FFFF00", { 0, }},
122 { "*", 1.40, 0.47, "#008B00", "#90EE90", { 0, }}
127 int id; /* sequence number in the PDB file */
128 const char *label; /* The atom name */
129 GLfloat x, y, z; /* position in 3-space (angstroms) */
130 const atom_data *data; /* computed: which style of atom this is */
134 int from, to; /* atom sequence numbers */
135 int strength; /* how many bonds are between these two atoms */
140 const char *label; /* description of this compound */
141 int natoms, atoms_size;
142 int nbonds, bonds_size;
143 molecule_atom *atoms;
144 molecule_bond *bonds;
149 GLXContext *glx_context;
151 trackball_state *trackball;
154 GLfloat molecule_size; /* max dimension of molecule bounding box */
156 GLfloat no_label_threshold; /* Things happen when molecules are huge */
157 GLfloat wireframe_threshold;
159 int which; /* which of the molecules is being shown */
163 int mode; /* 0 = normal, 1 = out, 2 = in */
166 GLuint molecule_dlist;
169 # ifdef HAVE_GLBITMAP
170 XFontStruct *xfont1, *xfont2, *xfont3;
171 GLuint font1_dlist, font2_dlist, font3_dlist;
173 texture_font_data *font1_data, *font2_data, *font3_data;
184 } molecule_configuration;
187 static molecule_configuration *mcs = NULL;
190 static char *molecule_str;
191 static char *do_spin;
192 static Bool do_wander;
193 static Bool do_titles;
194 static Bool do_labels;
195 static Bool do_atoms;
196 static Bool do_bonds;
197 static Bool do_shells;
199 static Bool verbose_p;
200 static GLfloat shell_alpha;
203 static Bool orig_do_labels, orig_do_atoms, orig_do_bonds, orig_do_shells,
207 static XrmOptionDescRec opts[] = {
208 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
209 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
210 { "-spin", ".spin", XrmoptionSepArg, 0 },
211 { "+spin", ".spin", XrmoptionNoArg, "" },
212 { "-wander", ".wander", XrmoptionNoArg, "True" },
213 { "+wander", ".wander", XrmoptionNoArg, "False" },
214 { "-labels", ".labels", XrmoptionNoArg, "True" },
215 { "+labels", ".labels", XrmoptionNoArg, "False" },
216 { "-titles", ".titles", XrmoptionNoArg, "True" },
217 { "+titles", ".titles", XrmoptionNoArg, "False" },
218 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
219 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
220 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
221 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
222 { "-shells", ".eshells", XrmoptionNoArg, "True" },
223 { "+shells", ".eshells", XrmoptionNoArg, "False" },
224 { "-shell-alpha", ".shellAlpha", XrmoptionSepArg, 0 },
225 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
226 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
227 { "-verbose", ".verbose", XrmoptionNoArg, "True" },
230 static argtype vars[] = {
231 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE, t_String},
232 {&timeout, "timeout", "Seconds", DEF_TIMEOUT, t_Int},
233 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
234 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
235 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
236 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
237 {&do_shells, "eshells", "EShells", DEF_ESHELLS, t_Bool},
238 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
239 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
240 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
241 {&shell_alpha, "shellAlpha", "ShellAlpha", DEF_SHELL_ALPHA, t_Float},
242 {&verbose_p, "verbose", "Verbose", DEF_VERBOSE, t_Bool},
245 ENTRYPOINT ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
253 sphere (molecule_configuration *mc,
254 GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
256 int stacks = (mc->scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
257 int slices = (mc->scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
260 glTranslatef (x, y, z);
261 glScalef (diameter, diameter, diameter);
262 unit_sphere (stacks, slices, wire);
265 return stacks * slices;
270 load_fonts (ModeInfo *mi)
272 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
273 # ifdef HAVE_GLBITMAP
274 load_font (mi->dpy, "atomFont", &mc->xfont1, &mc->font1_dlist);
275 load_font (mi->dpy, "atomFont2", &mc->xfont2, &mc->font2_dlist);
276 load_font (mi->dpy, "titleFont", &mc->xfont3, &mc->font3_dlist);
278 mc->font1_data = load_texture_font (mi->dpy, "atomFont");
279 mc->font2_data = load_texture_font (mi->dpy, "atomFont2");
280 mc->font3_data = load_texture_font (mi->dpy, "titleFont");
285 static const atom_data *
286 get_atom_data (const char *atom_name)
289 const atom_data *d = 0;
290 char *n = strdup (atom_name);
294 while (!isalpha(*n)) n++;
296 while (L > 0 && !isalpha(n[L-1]))
299 for (i = 0; i < countof(all_atom_data); i++)
301 d = &all_atom_data[i];
302 if (!strcasecmp (n, all_atom_data[i].name))
312 set_atom_color (ModeInfo *mi, const molecule_atom *a,
313 Bool font_p, GLfloat alpha)
321 d = get_atom_data ("bond");
325 gl_color[0] = d->gl_color[4];
326 gl_color[1] = d->gl_color[5];
327 gl_color[2] = d->gl_color[6];
328 gl_color[3] = d->gl_color[7];
332 gl_color[0] = d->gl_color[0];
333 gl_color[1] = d->gl_color[1];
334 gl_color[2] = d->gl_color[2];
335 gl_color[3] = d->gl_color[3];
338 if (gl_color[3] == 0)
340 const char *string = !font_p ? d->color : d->text_color;
342 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
344 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
345 (a ? a->label : d->name), string);
349 gl_color[0] = xcolor.red / 65536.0;
350 gl_color[1] = xcolor.green / 65536.0;
351 gl_color[2] = xcolor.blue / 65536.0;
356 /* If we're not drawing atoms, and the color is black, use white instead.
357 This is a kludge so that H can have black text over its white ball,
358 but the text still shows up if balls are off.
360 if (font_p && !do_atoms &&
361 gl_color[0] == 0 && gl_color[1] == 0 && gl_color[2] == 0)
363 gl_color[0] = gl_color[1] = gl_color[2] = 1;
367 glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
369 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
374 atom_size (const molecule_atom *a)
377 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 const GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
452 static const 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 glDisable (GL_LIGHTING);
493 glColor3f (c2[0], c2[1], c2[2]);
495 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
496 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
497 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
498 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
499 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
500 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
504 glEnable (GL_LIGHTING);
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 mc->scale_down = scale < 0.3;
543 glTranslatef (-(x1 + w/2),
550 /* Constructs the GL shapes of the current molecule
553 build_molecule (ModeInfo *mi, Bool transparent_p)
555 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
556 int wire = MI_IS_WIREFRAME(mi);
558 GLfloat alpha = transparent_p ? shell_alpha : 1.0;
561 molecule *m = &mc->molecules[mc->which];
565 glDisable(GL_CULL_FACE);
566 glDisable(GL_LIGHTING);
567 glDisable(GL_LIGHT0);
568 glDisable(GL_DEPTH_TEST);
569 glDisable(GL_NORMALIZE);
570 glDisable(GL_CULL_FACE);
574 glEnable(GL_CULL_FACE);
575 glEnable(GL_LIGHTING);
577 glEnable(GL_DEPTH_TEST);
578 glEnable(GL_NORMALIZE);
579 glEnable(GL_CULL_FACE);
583 set_atom_color (mi, 0, False, alpha);
586 for (i = 0; i < m->nbonds; i++)
588 const molecule_bond *b = &m->bonds[i];
589 const molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
590 const molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
595 glVertex3f(from->x, from->y, from->z);
596 glVertex3f(to->x, to->y, to->z);
602 int faces = (mc->scale_down ? TUBE_FACES_2 : TUBE_FACES);
608 GLfloat thickness = 0.07 * b->strength;
609 GLfloat cap_size = 0.03;
613 polys += tube (from->x, from->y, from->z,
616 faces, smooth, (!do_atoms || do_shells), wire);
620 if (!wire && do_atoms)
621 for (i = 0; i < m->natoms; i++)
623 const molecule_atom *a = &m->atoms[i];
624 GLfloat size = atom_size (a);
625 set_atom_color (mi, a, False, alpha);
626 polys += sphere (mc, a->x, a->y, a->z, size, wire);
629 if (do_bbox && !transparent_p)
631 draw_bounding_box (mi);
635 mc->polygon_count += polys;
643 push_atom (molecule *m,
644 int id, const char *label,
645 GLfloat x, GLfloat y, GLfloat z)
648 if (m->atoms_size < m->natoms)
651 m->atoms = (molecule_atom *) realloc (m->atoms,
652 m->atoms_size * sizeof(*m->atoms));
654 m->atoms[m->natoms-1].id = id;
655 m->atoms[m->natoms-1].label = label;
656 m->atoms[m->natoms-1].x = x;
657 m->atoms[m->natoms-1].y = y;
658 m->atoms[m->natoms-1].z = z;
659 m->atoms[m->natoms-1].data = get_atom_data (label);
664 push_bond (molecule *m, int from, int to)
668 for (i = 0; i < m->nbonds; i++)
669 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
670 (m->bonds[i].to == from && m->bonds[i].from == to))
672 m->bonds[i].strength++;
677 if (m->bonds_size < m->nbonds)
680 m->bonds = (molecule_bond *) realloc (m->bonds,
681 m->bonds_size * sizeof(*m->bonds));
683 m->bonds[m->nbonds-1].from = from;
684 m->bonds[m->nbonds-1].to = to;
685 m->bonds[m->nbonds-1].strength = 1;
690 parse_error (const char *file, int lineno, const char *line)
692 fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
693 progname, file, lineno, line);
698 /* This function is crap.
701 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
703 const char *s = data;
707 if ((!m->label || !*m->label) &&
708 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
710 char *name = calloc (1, 100);
717 while (isspace(*n2)) n2++;
719 ss = strchr (n2, '\n');
721 ss = strchr (n2, '\r');
724 ss = n2+strlen(n2)-1;
725 while (isspace(*ss) && ss > n2)
728 if (strlen (n2) > 4 &&
729 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
730 n2[strlen(n2)-4] = 0;
732 if (m->label) free ((char *) m->label);
733 m->label = strdup (n2);
736 else if (!strncmp (s, "TITLE ", 6) ||
737 !strncmp (s, "HEADER", 6) ||
738 !strncmp (s, "COMPND", 6) ||
739 !strncmp (s, "AUTHOR", 6) ||
740 !strncmp (s, "REVDAT", 6) ||
741 !strncmp (s, "SOURCE", 6) ||
742 !strncmp (s, "EXPDTA", 6) ||
743 !strncmp (s, "JRNL ", 6) ||
744 !strncmp (s, "REMARK", 6) ||
745 !strncmp (s, "SEQRES", 6) ||
746 !strncmp (s, "HET ", 6) ||
747 !strncmp (s, "FORMUL", 6) ||
748 !strncmp (s, "CRYST1", 6) ||
749 !strncmp (s, "ORIGX1", 6) ||
750 !strncmp (s, "ORIGX2", 6) ||
751 !strncmp (s, "ORIGX3", 6) ||
752 !strncmp (s, "SCALE1", 6) ||
753 !strncmp (s, "SCALE2", 6) ||
754 !strncmp (s, "SCALE3", 6) ||
755 !strncmp (s, "MASTER", 6) ||
756 !strncmp (s, "KEYWDS", 6) ||
757 !strncmp (s, "DBREF ", 6) ||
758 !strncmp (s, "HETNAM", 6) ||
759 !strncmp (s, "HETSYN", 6) ||
760 !strncmp (s, "HELIX ", 6) ||
761 !strncmp (s, "LINK ", 6) ||
762 !strncmp (s, "MTRIX1", 6) ||
763 !strncmp (s, "MTRIX2", 6) ||
764 !strncmp (s, "MTRIX3", 6) ||
765 !strncmp (s, "SHEET ", 6) ||
766 !strncmp (s, "CISPEP", 6) ||
768 !strncmp (s, "SEQADV", 6) ||
769 !strncmp (s, "SITE ", 5) ||
770 !strncmp (s, "FTNOTE", 6) ||
771 !strncmp (s, "MODEL ", 5) ||
772 !strncmp (s, "ENDMDL", 6) ||
773 !strncmp (s, "SPRSDE", 6) ||
774 !strncmp (s, "MODRES", 6) ||
776 !strncmp (s, "GENERATED BY", 12) ||
777 !strncmp (s, "TER ", 4) ||
778 !strncmp (s, "END ", 4) ||
779 !strncmp (s, "TER\n", 4) ||
780 !strncmp (s, "END\n", 4) ||
781 !strncmp (s, "\n", 1))
784 else if (!strncmp (s, "ATOM ", 7))
787 const char *end = strchr (s, '\n');
789 char *name = (char *) calloc (1, 4);
790 GLfloat x = -999, y = -999, z = -999;
792 if (1 != sscanf (s+7, " %d ", &id))
793 parse_error (filename, line, s);
795 /* Use the "atom name" field if that is all that is available. */
796 strncpy (name, s+12, 3);
798 /* But prefer the "element" field. */
799 if (L > 77 && !isspace(s[77])) {
800 /* fprintf(stderr, " \"%s\" -> ", name); */
804 /* fprintf(stderr, "\"%s\"\n", name); */
807 while (isspace(*name)) name++;
808 ss = name + strlen(name)-1;
809 while (isspace(*ss) && ss > name)
817 if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
818 parse_error (filename, line, s);
821 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
822 progname, filename, line,
825 push_atom (m, id, name, x, y, z);
827 else if (!strncmp (s, "HETATM ", 7))
830 char *name = (char *) calloc (1, 4);
831 GLfloat x = -999, y = -999, z = -999;
833 if (1 != sscanf (s+7, " %d ", &id))
834 parse_error (filename, line, s);
836 strncpy (name, s+12, 3);
837 while (isspace(*name)) name++;
838 ss = name + strlen(name)-1;
839 while (isspace(*ss) && ss > name)
841 if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
842 parse_error (filename, line, s);
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, "CONECT ", 7))
853 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
854 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
855 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
856 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
858 for (j = 1; j < i; j++)
862 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
863 progname, filename, line, atoms[0], atoms[j]);
865 push_bond (m, atoms[0], atoms[j]);
870 char *s1 = strdup (s);
871 for (ss = s1; *ss && *ss != '\n'; ss++)
874 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
875 progname, filename, line, s1);
878 while (*s && *s != '\n')
888 parse_pdb_file (molecule *m, const char *name)
891 int buf_size = 40960;
895 in = fopen(name, "r");
898 char *buf = (char *) malloc(1024 + strlen(name));
899 sprintf(buf, "%s: error reading \"%s\"", progname, name);
904 buf = (char *) malloc (buf_size);
906 while (fgets (buf, buf_size-1, in))
909 for (s = buf; *s; s++)
910 if (*s == '\r') *s = '\n';
911 parse_pdb_data (m, buf, name, line++);
919 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
924 if (!m->nbonds && do_bonds)
926 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
935 typedef struct { char *atom; int count; } atom_and_count;
937 /* When listing the components of a molecule, the convention is to put the
938 carbon atoms first, the hydrogen atoms second, and the other atom types
939 sorted alphabetically after that (although for some molecules, the usual
940 order is different: we special-case a few of those.)
943 cmp_atoms (const void *aa, const void *bb)
945 const atom_and_count *a = (atom_and_count *) aa;
946 const atom_and_count *b = (atom_and_count *) bb;
947 if (!a->atom) return 1;
948 if (!b->atom) return -1;
949 if (!strcmp(a->atom, "C")) return -1;
950 if (!strcmp(b->atom, "C")) return 1;
951 if (!strcmp(a->atom, "H")) return -1;
952 if (!strcmp(b->atom, "H")) return 1;
953 return strcmp (a->atom, b->atom);
956 static void special_case_formula (char *f);
959 generate_molecule_formula (molecule *m)
961 char *buf = (char *) malloc (m->natoms * 10);
964 atom_and_count counts[200];
965 memset (counts, 0, sizeof(counts));
967 for (i = 0; i < m->natoms; i++)
970 char *a = (char *) m->atoms[i].label;
972 while (!isalpha(*a)) a++;
974 for (e = a; isalpha(*e); e++);
976 while (counts[j].atom && !!strcmp(a, counts[j].atom))
986 while (counts[i].atom) i++;
987 qsort (counts, i, sizeof(*counts), cmp_atoms);
990 while (counts[i].atom)
992 strcat (s, counts[i].atom);
993 free (counts[i].atom);
995 if (counts[i].count > 1)
996 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
1001 special_case_formula (buf);
1003 if (!m->label) m->label = strdup("");
1004 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1005 strcpy (s, m->label);
1008 free ((char *) m->label);
1013 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1015 special_case_formula (char *f)
1017 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
1018 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
1019 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
1020 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1021 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1022 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
1023 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
1024 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
1029 insert_vertical_whitespace (char *string)
1033 if ((string[0] == ',' ||
1035 string[0] == ':') &&
1037 string[0] = ' ', string[1] = '\n';
1043 /* Construct the molecule data from either: the builtins; or from
1044 the (one) .pdb file specified with -molecule.
1047 load_molecules (ModeInfo *mi)
1049 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1050 int wire = MI_IS_WIREFRAME(mi);
1054 if (molecule_str && *molecule_str &&
1055 strcmp(molecule_str, "(default)")) /* try external PDB files */
1057 /* The -molecule option can point to a .pdb file, or to
1058 a directory of them.
1066 if (!stat (molecule_str, &st) &&
1067 S_ISDIR (st.st_mode))
1071 struct dirent *dentry;
1073 pdb_dir = opendir (molecule_str);
1076 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1082 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1086 files = (char **) calloc (sizeof(*files), list_size);
1088 while ((dentry = readdir (pdb_dir)))
1090 int L = strlen (dentry->d_name);
1091 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1094 if (nfiles >= list_size-1)
1096 list_size = (list_size + 10) * 1.2;
1098 realloc (files, list_size * sizeof(*files));
1102 fprintf (stderr, "%s: out of memory (%d files)\n",
1108 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1110 strcpy (fn, molecule_str);
1111 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1112 strcat (fn, dentry->d_name);
1113 files[nfiles++] = fn;
1115 fprintf (stderr, "%s: file %s\n", progname, fn);
1121 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1122 progname, molecule_str);
1126 files = (char **) malloc (sizeof (*files));
1128 files[0] = strdup (molecule_str);
1130 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1133 mc->nmolecules = nfiles;
1134 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1136 for (i = 0; i < mc->nmolecules; i++)
1139 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1140 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1142 if ((wire || !do_atoms) &&
1144 mc->molecules[molecule_ctr].nbonds == 0)
1146 /* If we're not drawing atoms (e.g., wireframe mode), and
1147 there is no bond info, then make sure labels are turned
1148 on, or we'll be looking at a black screen... */
1149 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1150 progname, files[i]);
1161 mc->nmolecules = molecule_ctr;
1164 if (mc->nmolecules == 0) /* do the builtins if no files */
1166 mc->nmolecules = countof(builtin_pdb_data);
1167 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1168 for (i = 0; i < mc->nmolecules; i++)
1171 sprintf (name, "<builtin-%d>", i);
1172 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1173 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1177 for (i = 0; i < mc->nmolecules; i++)
1179 generate_molecule_formula (&mc->molecules[i]);
1180 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1186 /* Window management, etc
1189 reshape_molecule (ModeInfo *mi, int width, int height)
1191 GLfloat h = (GLfloat) height / (GLfloat) width;
1193 glViewport (0, 0, (GLint) width, (GLint) height);
1195 glMatrixMode(GL_PROJECTION);
1197 gluPerspective (30.0, 1/h, 20.0, 100.0);
1199 glMatrixMode(GL_MODELVIEW);
1201 gluLookAt( 0.0, 0.0, 30.0,
1205 glClear(GL_COLOR_BUFFER_BIT);
1210 gl_init (ModeInfo *mi)
1212 static const GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1213 static const GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1214 static const GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1215 static const GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1216 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1217 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1218 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1219 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1224 startup_blurb (ModeInfo *mi)
1226 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1227 const char *s = "Constructing molecules...";
1228 print_gl_string (mi->dpy,
1229 # ifdef HAVE_GLBITMAP
1230 mc->xfont3, mc->font3_dlist,
1234 mi->xgwa.width, mi->xgwa.height,
1235 10, mi->xgwa.height - 10,
1238 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1242 molecule_handle_event (ModeInfo *mi, XEvent *event)
1244 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1246 if (event->xany.type == ButtonPress &&
1247 event->xbutton.button == Button1)
1249 mc->button_down_p = True;
1250 gltrackball_start (mc->trackball,
1251 event->xbutton.x, event->xbutton.y,
1252 MI_WIDTH (mi), MI_HEIGHT (mi));
1255 else if (event->xany.type == ButtonRelease &&
1256 event->xbutton.button == Button1)
1258 mc->button_down_p = False;
1261 else if (event->xany.type == ButtonPress &&
1262 (event->xbutton.button == Button4 ||
1263 event->xbutton.button == Button5 ||
1264 event->xbutton.button == Button6 ||
1265 event->xbutton.button == Button7))
1267 gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1268 !!event->xbutton.state);
1271 else if (event->xany.type == KeyPress)
1275 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1277 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1279 GLfloat speed = 4.0;
1281 mc->mode_tick = 10 * speed;
1285 else if (event->xany.type == MotionNotify &&
1288 gltrackball_track (mc->trackball,
1289 event->xmotion.x, event->xmotion.y,
1290 MI_WIDTH (mi), MI_HEIGHT (mi));
1299 init_molecule (ModeInfo *mi)
1301 molecule_configuration *mc;
1305 mcs = (molecule_configuration *)
1306 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1308 fprintf(stderr, "%s: out of memory\n", progname);
1313 mc = &mcs[MI_SCREEN(mi)];
1315 if ((mc->glx_context = init_GL(mi)) != NULL) {
1317 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1323 wire = MI_IS_WIREFRAME(mi);
1326 Bool spinx=False, spiny=False, spinz=False;
1327 double spin_speed = 0.5;
1328 double spin_accel = 0.3;
1329 double wander_speed = 0.01;
1334 if (*s == 'x' || *s == 'X') spinx = True;
1335 else if (*s == 'y' || *s == 'Y') spiny = True;
1336 else if (*s == 'z' || *s == 'Z') spinz = True;
1337 else if (*s == '0') ;
1341 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1348 mc->rot = make_rotator (spinx ? spin_speed : 0,
1349 spiny ? spin_speed : 0,
1350 spinz ? spin_speed : 0,
1352 do_wander ? wander_speed : 0,
1353 (spinx && spiny && spinz));
1354 mc->trackball = gltrackball_init ();
1357 orig_do_labels = do_labels;
1358 orig_do_atoms = do_atoms;
1359 orig_do_bonds = do_bonds;
1360 orig_do_shells = do_shells;
1361 orig_wire = MI_IS_WIREFRAME(mi);
1363 mc->molecule_dlist = glGenLists(1);
1365 mc->shell_dlist = glGenLists(1);
1367 load_molecules (mi);
1368 mc->which = random() % mc->nmolecules;
1370 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1371 "NoLabelThreshold");
1372 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1373 "WireframeThreshold");
1381 /* Put the labels on the atoms.
1382 This can't be a part of the display list because of the games
1383 we play with the translation matrix.
1386 draw_labels (ModeInfo *mi)
1388 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1389 int wire = MI_IS_WIREFRAME(mi);
1390 molecule *m = &mc->molecules[mc->which];
1391 # ifdef HAVE_GLBITMAP
1392 XFontStruct *xfont = (mc->scale_down ? mc->xfont2 : mc->xfont1);
1393 GLuint font_dlist = (mc->scale_down ? mc->font2_dlist : mc->font1_dlist);
1395 texture_font_data *font_data = mc->font1_data; /* don't scale down */
1403 glDisable (GL_LIGHTING); /* don't light fonts */
1405 for (i = 0; i < m->natoms; i++)
1407 molecule_atom *a = &m->atoms[i];
1408 GLfloat size = atom_size (a);
1414 set_atom_color (mi, a, True, 1);
1416 /* First, we translate the origin to the center of the atom.
1418 Then we retrieve the prevailing modelview matrix (which
1419 includes any rotation, wandering, and user-trackball-rolling
1422 We set the top 3x3 cells of that matrix to be the identity
1423 matrix. This removes all rotation from the matrix, while
1424 leaving the translation alone. This has the effect of
1425 leaving the prevailing coordinate system perpendicular to
1426 the camera view: were we to draw a square face, it would
1427 be in the plane of the screen.
1429 Now we translate by `size' toward the viewer -- so that the
1430 origin is *just in front* of the ball.
1432 Then we draw the label text, allowing the depth buffer to
1433 do its work: that way, labels on atoms will be occluded
1434 properly when other atoms move in front of them.
1436 This technique (of neutralizing rotation relative to the
1437 observer, after both rotations and translations have been
1438 applied) is known as "billboarding".
1441 glTranslatef(a->x, a->y, a->z); /* get matrix */
1442 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1443 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1444 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1445 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1446 glLoadIdentity(); /* reset modelview */
1447 glMultMatrixf (&m[0][0]); /* replace with ours */
1449 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1451 # ifdef HAVE_GLBITMAP
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 (xfont, a->label, 0) / 2,
1462 for (j = 0; j < strlen(a->label); j++)
1463 glCallList (font_dlist + (int)(a->label[j]));
1468 int w = texture_string_width (font_data, a->label, &h);
1469 GLfloat s = 1.0 / h;
1470 GLfloat max = 18; /* max point size to avoid pixellated text */
1471 if (h > max) s *= max/h;
1473 glTranslatef (-w/2, h*2/3, 0);
1474 print_gl_string (mi->dpy,
1475 # ifdef HAVE_GLBITMAP
1488 /* More efficient to always call glEnable() with correct values
1489 than to call glPushAttrib()/glPopAttrib(), since reading
1490 attributes from GL does a round-trip and stalls the pipeline.
1493 glEnable (GL_LIGHTING);
1498 pick_new_molecule (ModeInfo *mi, time_t last)
1500 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1502 if (mc->nmolecules == 1)
1504 if (last != 0) return;
1509 mc->which = random() % mc->nmolecules;
1514 while (n == mc->which)
1515 n = random() % mc->nmolecules;
1521 char *name = strdup (mc->molecules[mc->which].label);
1522 char *s = strpbrk (name, "\r\n");
1524 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1528 mc->polygon_count = 0;
1530 glNewList (mc->molecule_dlist, GL_COMPILE);
1531 ensure_bounding_box_visible (mi);
1533 do_labels = orig_do_labels;
1534 do_atoms = orig_do_atoms;
1535 do_bonds = orig_do_bonds;
1536 do_shells = orig_do_shells;
1537 MI_IS_WIREFRAME(mi) = orig_wire;
1539 if (mc->molecule_size > mc->no_label_threshold)
1541 if (mc->molecule_size > mc->wireframe_threshold)
1542 MI_IS_WIREFRAME(mi) = 1;
1544 if (MI_IS_WIREFRAME(mi))
1545 do_bonds = 1, do_shells = 0;
1550 if (! (do_bonds || do_atoms || do_labels))
1552 /* Make sure *something* shows up! */
1553 MI_IS_WIREFRAME(mi) = 1;
1557 build_molecule (mi, False);
1562 glNewList (mc->shell_dlist, GL_COMPILE);
1563 ensure_bounding_box_visible (mi);
1569 build_molecule (mi, True);
1572 do_bonds = orig_do_bonds;
1573 do_atoms = orig_do_atoms;
1574 do_labels = orig_do_labels;
1580 draw_molecule (ModeInfo *mi)
1582 time_t now = time ((time_t *) 0);
1583 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1585 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1586 Display *dpy = MI_DISPLAY(mi);
1587 Window window = MI_WINDOW(mi);
1589 if (!mc->glx_context)
1592 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1594 if (mc->draw_time == 0)
1596 pick_new_molecule (mi, mc->draw_time);
1597 mc->draw_time = now;
1599 else if (mc->mode == 0)
1601 if (mc->draw_tick++ > 10)
1603 time_t now = time((time_t *) 0);
1604 if (mc->draw_time == 0) mc->draw_time = now;
1607 if (!mc->button_down_p &&
1608 mc->nmolecules > 1 &&
1609 mc->draw_time + timeout <= now)
1611 /* randomize molecules every -timeout seconds */
1612 mc->mode = 1; /* go out */
1613 mc->mode_tick = 10 * speed;
1614 mc->draw_time = now;
1618 else if (mc->mode == 1) /* out */
1620 if (--mc->mode_tick <= 0)
1622 mc->mode_tick = 10 * speed;
1623 mc->mode = 2; /* go in */
1624 pick_new_molecule (mi, mc->draw_time);
1625 mc->draw_time = now;
1628 else if (mc->mode == 2) /* in */
1630 if (--mc->mode_tick <= 0)
1631 mc->mode = 0; /* normal */
1637 glScalef(1.1, 1.1, 1.1);
1641 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1642 glTranslatef((x - 0.5) * 9,
1646 gltrackball_rotate (mc->trackball);
1648 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1649 glRotatef (x * 360, 1.0, 0.0, 0.0);
1650 glRotatef (y * 360, 0.0, 1.0, 0.0);
1651 glRotatef (z * 360, 0.0, 0.0, 1.0);
1654 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1658 GLfloat s = (mc->mode == 1
1659 ? mc->mode_tick / (10 * speed)
1660 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1665 glCallList (mc->molecule_dlist);
1669 molecule *m = &mc->molecules[mc->which];
1673 /* This can't go in the display list, or the characters are spaced
1674 wrongly when the window is resized. */
1675 if (do_titles && m->label && *m->label)
1677 set_atom_color (mi, 0, True, 1);
1678 print_gl_string (mi->dpy,
1679 # ifdef HAVE_GLBITMAP
1680 mc->xfont3, mc->font3_dlist,
1684 mi->xgwa.width, mi->xgwa.height,
1685 10, mi->xgwa.height - 10,
1693 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1695 glCallList (mc->shell_dlist);
1697 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1699 glDepthFunc (GL_EQUAL);
1700 glEnable (GL_BLEND);
1701 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1703 glCallList (mc->shell_dlist);
1705 glDepthFunc (GL_LESS);
1706 glDisable (GL_BLEND);
1711 mi->polygon_count = mc->polygon_count;
1713 if (mi->fps_p) do_fps (mi);
1716 glXSwapBuffers(dpy, window);
1719 XSCREENSAVER_MODULE ("Molecule", molecule)