1 /* molecule, Copyright (c) 2001-2006 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
27 #define DEFAULTS "*delay: 10000 \n" \
28 "*showFPS: False \n" \
29 "*wireframe: False \n" \
30 "*atomFont: -*-helvetica-medium-r-normal-*-180-*\n" \
31 "*atomFont2: -*-helvetica-bold-r-normal-*-80-*\n" \
32 "*titleFont: -*-helvetica-medium-r-normal-*-180-*\n" \
33 "*noLabelThreshold: 30 \n" \
34 "*wireframeThreshold: 150 \n" \
36 # define refresh_molecule 0
37 # define release_molecule 0
39 #define countof(x) (sizeof((x))/sizeof((*x)))
41 #include "xlockmore.h"
47 #include "gltrackball.h"
49 #ifdef USE_GL /* whole file */
51 #include <sys/types.h>
56 #define DEF_TIMEOUT "20"
57 #define DEF_SPIN "XYZ"
58 #define DEF_WANDER "False"
59 #define DEF_LABELS "True"
60 #define DEF_TITLES "True"
61 #define DEF_ATOMS "True"
62 #define DEF_BONDS "True"
63 #define DEF_ESHELLS "True"
64 #define DEF_BBOX "False"
65 #define DEF_SHELL_ALPHA "0.3"
66 #define DEF_MOLECULE "(default)"
67 #define DEF_VERBOSE "False"
69 #define SPHERE_SLICES 48 /* how densely to render spheres */
70 #define SPHERE_STACKS 24
72 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
75 # define TUBE_FACES 12 /* how densely to render tubes */
80 #define SPHERE_SLICES_2 14
81 #define SPHERE_STACKS_2 8
82 #define TUBE_FACES_2 6
86 __extension__ /* don't warn about "string length is greater than the length
87 ISO C89 compilers are required to support" when includng
88 the following data file... */
90 const char * const builtin_pdb_data[] = {
91 # include "molecules.h"
99 const char *text_color;
104 /* These are the traditional colors used to render these atoms,
105 and their approximate size in angstroms.
107 static const atom_data all_atom_data[] = {
108 { "H", 1.17, 0.40, "#FFFFFF", "#000000", { 0, }},
109 { "C", 1.75, 0.58, "#999999", "#FFFFFF", { 0, }},
110 { "CA", 1.80, 0.60, "#0000FF", "#ADD8E6", { 0, }},
111 { "N", 1.55, 0.52, "#A2B5CD", "#EE99FF", { 0, }},
112 { "O", 1.40, 0.47, "#FF0000", "#FFB6C1", { 0, }},
113 { "P", 1.28, 0.43, "#9370DB", "#DB7093", { 0, }},
114 { "S", 1.80, 0.60, "#8B8B00", "#FFFF00", { 0, }},
115 { "bond", 0, 0, "#B3B3B3", "#FFFF00", { 0, }},
116 { "*", 1.40, 0.47, "#008B00", "#90EE90", { 0, }}
121 int id; /* sequence number in the PDB file */
122 const char *label; /* The atom name */
123 GLfloat x, y, z; /* position in 3-space (angstroms) */
124 const atom_data *data; /* computed: which style of atom this is */
128 int from, to; /* atom sequence numbers */
129 int strength; /* how many bonds are between these two atoms */
134 const char *label; /* description of this compound */
135 int natoms, atoms_size;
136 int nbonds, bonds_size;
137 molecule_atom *atoms;
138 molecule_bond *bonds;
143 GLXContext *glx_context;
145 trackball_state *trackball;
148 GLfloat molecule_size; /* max dimension of molecule bounding box */
150 GLfloat no_label_threshold; /* Things happen when molecules are huge */
151 GLfloat wireframe_threshold;
153 int which; /* which of the molecules is being shown */
157 int mode; /* 0 = normal, 1 = out, 2 = in */
160 GLuint molecule_dlist;
163 XFontStruct *xfont1, *xfont2, *xfont3;
164 GLuint font1_dlist, font2_dlist, font3_dlist;
172 } molecule_configuration;
175 static molecule_configuration *mcs = NULL;
178 static char *molecule_str;
179 static char *do_spin;
180 static Bool do_wander;
181 static Bool do_titles;
182 static Bool do_labels;
183 static Bool do_atoms;
184 static Bool do_bonds;
185 static Bool do_shells;
187 static Bool verbose_p;
188 static GLfloat shell_alpha;
191 static Bool orig_do_labels, orig_do_atoms, orig_do_bonds, orig_do_shells,
195 static XrmOptionDescRec opts[] = {
196 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
197 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
198 { "-spin", ".spin", XrmoptionSepArg, 0 },
199 { "+spin", ".spin", XrmoptionNoArg, "" },
200 { "-wander", ".wander", XrmoptionNoArg, "True" },
201 { "+wander", ".wander", XrmoptionNoArg, "False" },
202 { "-labels", ".labels", XrmoptionNoArg, "True" },
203 { "+labels", ".labels", XrmoptionNoArg, "False" },
204 { "-titles", ".titles", XrmoptionNoArg, "True" },
205 { "+titles", ".titles", XrmoptionNoArg, "False" },
206 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
207 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
208 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
209 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
210 { "-shells", ".eshells", XrmoptionNoArg, "True" },
211 { "+shells", ".eshells", XrmoptionNoArg, "False" },
212 { "-shell-alpha", ".shellAlpha", XrmoptionSepArg, 0 },
213 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
214 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
215 { "-verbose", ".verbose", XrmoptionNoArg, "True" },
218 static argtype vars[] = {
219 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE, t_String},
220 {&timeout, "timeout", "Seconds", DEF_TIMEOUT, t_Int},
221 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
222 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
223 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
224 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
225 {&do_shells, "eshells", "EShells", DEF_ESHELLS, t_Bool},
226 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
227 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
228 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
229 {&shell_alpha, "shellAlpha", "ShellAlpha", DEF_SHELL_ALPHA, t_Float},
230 {&verbose_p, "verbose", "Verbose", DEF_VERBOSE, t_Bool},
233 ENTRYPOINT ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
241 sphere (molecule_configuration *mc,
242 GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
244 int stacks = (mc->scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
245 int slices = (mc->scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
248 glTranslatef (x, y, z);
249 glScalef (diameter, diameter, diameter);
250 unit_sphere (stacks, slices, wire);
253 return stacks * slices;
258 load_fonts (ModeInfo *mi)
260 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
261 load_font (mi->dpy, "atomFont", &mc->xfont1, &mc->font1_dlist);
262 load_font (mi->dpy, "atomFont2", &mc->xfont2, &mc->font2_dlist);
263 load_font (mi->dpy, "titleFont", &mc->xfont3, &mc->font3_dlist);
267 static const atom_data *
268 get_atom_data (const char *atom_name)
271 const atom_data *d = 0;
272 char *n = strdup (atom_name);
276 while (!isalpha(*n)) n++;
278 while (L > 0 && !isalpha(n[L-1]))
281 for (i = 0; i < countof(all_atom_data); i++)
283 d = &all_atom_data[i];
284 if (!strcasecmp (n, all_atom_data[i].name))
294 set_atom_color (ModeInfo *mi, const molecule_atom *a,
295 Bool font_p, GLfloat alpha)
303 d = get_atom_data ("bond");
307 gl_color[0] = d->gl_color[4];
308 gl_color[1] = d->gl_color[5];
309 gl_color[2] = d->gl_color[6];
310 gl_color[3] = d->gl_color[7];
314 gl_color[0] = d->gl_color[0];
315 gl_color[1] = d->gl_color[1];
316 gl_color[2] = d->gl_color[2];
317 gl_color[3] = d->gl_color[3];
320 if (gl_color[3] == 0)
322 const char *string = !font_p ? d->color : d->text_color;
324 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
326 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
327 (a ? a->label : d->name), string);
331 gl_color[0] = xcolor.red / 65536.0;
332 gl_color[1] = xcolor.green / 65536.0;
333 gl_color[2] = xcolor.blue / 65536.0;
338 /* If we're not drawing atoms, and the color is black, use white instead.
339 This is a kludge so that H can have black text over its white ball,
340 but the text still shows up if balls are off.
342 if (font_p && !do_atoms &&
343 gl_color[0] == 0 && gl_color[1] == 0 && gl_color[2] == 0)
345 gl_color[0] = gl_color[1] = gl_color[2] = 1;
349 glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
351 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
356 atom_size (const molecule_atom *a)
359 return a->data->size2;
361 return a->data->size;
365 static molecule_atom *
366 get_atom (molecule_atom *atoms, int natoms, int id)
370 /* quick short-circuit */
373 if (atoms[id].id == id)
375 if (id > 0 && atoms[id-1].id == id)
377 if (id < natoms-1 && atoms[id+1].id == id)
381 for (i = 0; i < natoms; i++)
382 if (id == atoms[i].id)
385 fprintf (stderr, "%s: no atom %d\n", progname, id);
391 molecule_bounding_box (ModeInfo *mi,
392 GLfloat *x1, GLfloat *y1, GLfloat *z1,
393 GLfloat *x2, GLfloat *y2, GLfloat *z2)
395 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
396 molecule *m = &mc->molecules[mc->which];
401 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
405 *x1 = *x2 = m->atoms[0].x;
406 *y1 = *y2 = m->atoms[0].y;
407 *z1 = *z2 = m->atoms[0].z;
410 for (i = 1; i < m->natoms; i++)
412 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
413 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
414 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
416 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
417 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
418 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
431 draw_bounding_box (ModeInfo *mi)
433 static const GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
434 static const GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
435 int wire = MI_IS_WIREFRAME(mi);
436 GLfloat x1, y1, z1, x2, y2, z2;
437 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
439 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
442 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
444 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
445 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
447 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
448 glNormal3f(0, -1, 0);
449 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
450 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
452 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
454 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
455 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
457 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
458 glNormal3f(0, 0, -1);
459 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
460 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
462 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
464 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
465 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
467 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
468 glNormal3f(-1, 0, 0);
469 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
470 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
473 glPushAttrib (GL_LIGHTING);
474 glDisable (GL_LIGHTING);
476 glColor3f (c2[0], c2[1], c2[2]);
478 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
479 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
480 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
481 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
482 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
483 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
490 /* Since PDB files don't always have the molecule centered around the
491 origin, and since some molecules are pretty large, scale and/or
492 translate so that the whole molecule is visible in the window.
495 ensure_bounding_box_visible (ModeInfo *mi)
497 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
499 GLfloat x1, y1, z1, x2, y2, z2;
502 GLfloat max_size = 10; /* don't bother scaling down if the molecule
503 is already smaller than this */
505 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
510 size = (w > h ? w : h);
511 size = (size > d ? size : d);
513 mc->molecule_size = size;
519 GLfloat scale = max_size / size;
520 glScalef (scale, scale, scale);
522 mc->scale_down = scale < 0.3;
525 glTranslatef (-(x1 + w/2),
532 /* Constructs the GL shapes of the current molecule
535 build_molecule (ModeInfo *mi, Bool transparent_p)
537 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
538 int wire = MI_IS_WIREFRAME(mi);
540 GLfloat alpha = transparent_p ? shell_alpha : 1.0;
543 molecule *m = &mc->molecules[mc->which];
547 glDisable(GL_CULL_FACE);
548 glDisable(GL_LIGHTING);
549 glDisable(GL_LIGHT0);
550 glDisable(GL_DEPTH_TEST);
551 glDisable(GL_NORMALIZE);
552 glDisable(GL_CULL_FACE);
556 glEnable(GL_CULL_FACE);
557 glEnable(GL_LIGHTING);
559 glEnable(GL_DEPTH_TEST);
560 glEnable(GL_NORMALIZE);
561 glEnable(GL_CULL_FACE);
565 set_atom_color (mi, 0, False, alpha);
568 for (i = 0; i < m->nbonds; i++)
570 const molecule_bond *b = &m->bonds[i];
571 const molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
572 const molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
577 glVertex3f(from->x, from->y, from->z);
578 glVertex3f(to->x, to->y, to->z);
584 int faces = (mc->scale_down ? TUBE_FACES_2 : TUBE_FACES);
590 GLfloat thickness = 0.07 * b->strength;
591 GLfloat cap_size = 0.03;
595 polys += tube (from->x, from->y, from->z,
598 faces, smooth, (!do_atoms || do_shells), wire);
602 if (!wire && do_atoms)
603 for (i = 0; i < m->natoms; i++)
605 const molecule_atom *a = &m->atoms[i];
606 GLfloat size = atom_size (a);
607 set_atom_color (mi, a, False, alpha);
608 polys += sphere (mc, a->x, a->y, a->z, size, wire);
611 if (do_bbox && !transparent_p)
613 draw_bounding_box (mi);
617 mc->polygon_count += polys;
625 push_atom (molecule *m,
626 int id, const char *label,
627 GLfloat x, GLfloat y, GLfloat z)
630 if (m->atoms_size < m->natoms)
633 m->atoms = (molecule_atom *) realloc (m->atoms,
634 m->atoms_size * sizeof(*m->atoms));
636 m->atoms[m->natoms-1].id = id;
637 m->atoms[m->natoms-1].label = label;
638 m->atoms[m->natoms-1].x = x;
639 m->atoms[m->natoms-1].y = y;
640 m->atoms[m->natoms-1].z = z;
641 m->atoms[m->natoms-1].data = get_atom_data (label);
646 push_bond (molecule *m, int from, int to)
650 for (i = 0; i < m->nbonds; i++)
651 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
652 (m->bonds[i].to == from && m->bonds[i].from == to))
654 m->bonds[i].strength++;
659 if (m->bonds_size < m->nbonds)
662 m->bonds = (molecule_bond *) realloc (m->bonds,
663 m->bonds_size * sizeof(*m->bonds));
665 m->bonds[m->nbonds-1].from = from;
666 m->bonds[m->nbonds-1].to = to;
667 m->bonds[m->nbonds-1].strength = 1;
672 parse_error (const char *file, int lineno, const char *line)
674 fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
675 progname, file, lineno, line);
680 /* This function is crap.
683 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
685 const char *s = data;
689 if ((!m->label || !*m->label) &&
690 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
692 char *name = calloc (1, 100);
699 while (isspace(*n2)) n2++;
701 ss = strchr (n2, '\n');
703 ss = strchr (n2, '\r');
706 ss = n2+strlen(n2)-1;
707 while (isspace(*ss) && ss > n2)
710 if (strlen (n2) > 4 &&
711 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
712 n2[strlen(n2)-4] = 0;
714 if (m->label) free ((char *) m->label);
715 m->label = strdup (n2);
718 else if (!strncmp (s, "TITLE ", 6) ||
719 !strncmp (s, "HEADER", 6) ||
720 !strncmp (s, "COMPND", 6) ||
721 !strncmp (s, "AUTHOR", 6) ||
722 !strncmp (s, "REVDAT", 6) ||
723 !strncmp (s, "SOURCE", 6) ||
724 !strncmp (s, "EXPDTA", 6) ||
725 !strncmp (s, "JRNL ", 6) ||
726 !strncmp (s, "REMARK", 6) ||
727 !strncmp (s, "SEQRES", 6) ||
728 !strncmp (s, "HET ", 6) ||
729 !strncmp (s, "FORMUL", 6) ||
730 !strncmp (s, "CRYST1", 6) ||
731 !strncmp (s, "ORIGX1", 6) ||
732 !strncmp (s, "ORIGX2", 6) ||
733 !strncmp (s, "ORIGX3", 6) ||
734 !strncmp (s, "SCALE1", 6) ||
735 !strncmp (s, "SCALE2", 6) ||
736 !strncmp (s, "SCALE3", 6) ||
737 !strncmp (s, "MASTER", 6) ||
738 !strncmp (s, "KEYWDS", 6) ||
739 !strncmp (s, "DBREF ", 6) ||
740 !strncmp (s, "HETNAM", 6) ||
741 !strncmp (s, "HETSYN", 6) ||
742 !strncmp (s, "HELIX ", 6) ||
743 !strncmp (s, "LINK ", 6) ||
744 !strncmp (s, "MTRIX1", 6) ||
745 !strncmp (s, "MTRIX2", 6) ||
746 !strncmp (s, "MTRIX3", 6) ||
747 !strncmp (s, "SHEET ", 6) ||
748 !strncmp (s, "CISPEP", 6) ||
750 !strncmp (s, "SEQADV", 6) ||
751 !strncmp (s, "SITE ", 5) ||
752 !strncmp (s, "FTNOTE", 6) ||
753 !strncmp (s, "MODEL ", 5) ||
754 !strncmp (s, "ENDMDL", 6) ||
755 !strncmp (s, "SPRSDE", 6) ||
756 !strncmp (s, "MODRES", 6) ||
758 !strncmp (s, "GENERATED BY", 12) ||
759 !strncmp (s, "TER ", 4) ||
760 !strncmp (s, "END ", 4) ||
761 !strncmp (s, "TER\n", 4) ||
762 !strncmp (s, "END\n", 4) ||
763 !strncmp (s, "\n", 1))
766 else if (!strncmp (s, "ATOM ", 7))
769 const char *end = strchr (s, '\n');
771 char *name = (char *) calloc (1, 4);
772 GLfloat x = -999, y = -999, z = -999;
774 if (1 != sscanf (s+7, " %d ", &id))
775 parse_error (filename, line, s);
777 /* Use the "atom name" field if that is all that is available. */
778 strncpy (name, s+12, 3);
780 /* But prefer the "element" field. */
781 if (L > 77 && !isspace(s[77])) {
782 /* fprintf(stderr, " \"%s\" -> ", name); */
786 /* fprintf(stderr, "\"%s\"\n", name); */
789 while (isspace(*name)) name++;
790 ss = name + strlen(name)-1;
791 while (isspace(*ss) && ss > name)
799 if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
800 parse_error (filename, line, s);
803 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
804 progname, filename, line,
807 push_atom (m, id, name, x, y, z);
809 else if (!strncmp (s, "HETATM ", 7))
812 char *name = (char *) calloc (1, 4);
813 GLfloat x = -999, y = -999, z = -999;
815 if (1 != sscanf (s+7, " %d ", &id))
816 parse_error (filename, line, s);
818 strncpy (name, s+12, 3);
819 while (isspace(*name)) name++;
820 ss = name + strlen(name)-1;
821 while (isspace(*ss) && ss > name)
823 if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
824 parse_error (filename, line, s);
826 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
827 progname, filename, line,
830 push_atom (m, id, name, x, y, z);
832 else if (!strncmp (s, "CONECT ", 7))
835 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
836 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
837 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
838 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
840 for (j = 1; j < i; j++)
844 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
845 progname, filename, line, atoms[0], atoms[j]);
847 push_bond (m, atoms[0], atoms[j]);
852 char *s1 = strdup (s);
853 for (ss = s1; *ss && *ss != '\n'; ss++)
856 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
857 progname, filename, line, s1);
860 while (*s && *s != '\n')
870 parse_pdb_file (molecule *m, const char *name)
873 int buf_size = 40960;
877 in = fopen(name, "r");
880 char *buf = (char *) malloc(1024 + strlen(name));
881 sprintf(buf, "%s: error reading \"%s\"", progname, name);
886 buf = (char *) malloc (buf_size);
888 while (fgets (buf, buf_size-1, in))
891 for (s = buf; *s; s++)
892 if (*s == '\r') *s = '\n';
893 parse_pdb_data (m, buf, name, line++);
901 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
906 if (!m->nbonds && do_bonds)
908 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
917 typedef struct { char *atom; int count; } atom_and_count;
919 /* When listing the components of a molecule, the convention is to put the
920 carbon atoms first, the hydrogen atoms second, and the other atom types
921 sorted alphabetically after that (although for some molecules, the usual
922 order is different: we special-case a few of those.)
925 cmp_atoms (const void *aa, const void *bb)
927 const atom_and_count *a = (atom_and_count *) aa;
928 const atom_and_count *b = (atom_and_count *) bb;
929 if (!a->atom) return 1;
930 if (!b->atom) return -1;
931 if (!strcmp(a->atom, "C")) return -1;
932 if (!strcmp(b->atom, "C")) return 1;
933 if (!strcmp(a->atom, "H")) return -1;
934 if (!strcmp(b->atom, "H")) return 1;
935 return strcmp (a->atom, b->atom);
938 static void special_case_formula (char *f);
941 generate_molecule_formula (molecule *m)
943 char *buf = (char *) malloc (m->natoms * 10);
946 atom_and_count counts[200];
947 memset (counts, 0, sizeof(counts));
949 for (i = 0; i < m->natoms; i++)
952 char *a = (char *) m->atoms[i].label;
954 while (!isalpha(*a)) a++;
956 for (e = a; isalpha(*e); e++);
958 while (counts[j].atom && !!strcmp(a, counts[j].atom))
968 while (counts[i].atom) i++;
969 qsort (counts, i, sizeof(*counts), cmp_atoms);
972 while (counts[i].atom)
974 strcat (s, counts[i].atom);
975 free (counts[i].atom);
977 if (counts[i].count > 1)
978 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
983 special_case_formula (buf);
985 if (!m->label) m->label = strdup("");
986 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
987 strcpy (s, m->label);
990 free ((char *) m->label);
995 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
997 special_case_formula (char *f)
999 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
1000 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
1001 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
1002 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1003 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1004 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
1005 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
1006 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
1011 insert_vertical_whitespace (char *string)
1015 if ((string[0] == ',' ||
1017 string[0] == ':') &&
1019 string[0] = ' ', string[1] = '\n';
1025 /* Construct the molecule data from either: the builtins; or from
1026 the (one) .pdb file specified with -molecule.
1029 load_molecules (ModeInfo *mi)
1031 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1032 int wire = MI_IS_WIREFRAME(mi);
1036 if (molecule_str && *molecule_str &&
1037 strcmp(molecule_str, "(default)")) /* try external PDB files */
1039 /* The -molecule option can point to a .pdb file, or to
1040 a directory of them.
1048 if (!stat (molecule_str, &st) &&
1049 S_ISDIR (st.st_mode))
1053 struct dirent *dentry;
1055 pdb_dir = opendir (molecule_str);
1058 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1064 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1068 files = (char **) calloc (sizeof(*files), list_size);
1070 while ((dentry = readdir (pdb_dir)))
1072 int L = strlen (dentry->d_name);
1073 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1076 if (nfiles >= list_size-1)
1078 list_size = (list_size + 10) * 1.2;
1080 realloc (files, list_size * sizeof(*files));
1084 fprintf (stderr, "%s: out of memory (%d files)\n",
1090 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1092 strcpy (fn, molecule_str);
1093 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1094 strcat (fn, dentry->d_name);
1095 files[nfiles++] = fn;
1097 fprintf (stderr, "%s: file %s\n", progname, fn);
1103 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1104 progname, molecule_str);
1108 files = (char **) malloc (sizeof (*files));
1110 files[0] = strdup (molecule_str);
1112 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1115 mc->nmolecules = nfiles;
1116 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1118 for (i = 0; i < mc->nmolecules; i++)
1121 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1122 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1124 if ((wire || !do_atoms) &&
1126 mc->molecules[molecule_ctr].nbonds == 0)
1128 /* If we're not drawing atoms (e.g., wireframe mode), and
1129 there is no bond info, then make sure labels are turned
1130 on, or we'll be looking at a black screen... */
1131 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1132 progname, files[i]);
1143 mc->nmolecules = molecule_ctr;
1146 if (mc->nmolecules == 0) /* do the builtins if no files */
1148 mc->nmolecules = countof(builtin_pdb_data);
1149 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1150 for (i = 0; i < mc->nmolecules; i++)
1153 sprintf (name, "<builtin-%d>", i);
1154 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1155 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1159 for (i = 0; i < mc->nmolecules; i++)
1161 generate_molecule_formula (&mc->molecules[i]);
1162 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1168 /* Window management, etc
1171 reshape_molecule (ModeInfo *mi, int width, int height)
1173 GLfloat h = (GLfloat) height / (GLfloat) width;
1175 glViewport (0, 0, (GLint) width, (GLint) height);
1177 glMatrixMode(GL_PROJECTION);
1179 gluPerspective (30.0, 1/h, 20.0, 100.0);
1181 glMatrixMode(GL_MODELVIEW);
1183 gluLookAt( 0.0, 0.0, 30.0,
1187 glClear(GL_COLOR_BUFFER_BIT);
1192 gl_init (ModeInfo *mi)
1194 static const GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1195 static const GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1196 static const GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1197 static const GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1198 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1199 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1200 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1201 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1206 startup_blurb (ModeInfo *mi)
1208 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1209 const char *s = "Constructing molecules...";
1210 print_gl_string (mi->dpy, mc->xfont3, mc->font3_dlist,
1211 mi->xgwa.width, mi->xgwa.height,
1212 10, mi->xgwa.height - 10,
1215 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1219 molecule_handle_event (ModeInfo *mi, XEvent *event)
1221 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1223 if (event->xany.type == ButtonPress &&
1224 event->xbutton.button == Button1)
1226 mc->button_down_p = True;
1227 gltrackball_start (mc->trackball,
1228 event->xbutton.x, event->xbutton.y,
1229 MI_WIDTH (mi), MI_HEIGHT (mi));
1232 else if (event->xany.type == ButtonRelease &&
1233 event->xbutton.button == Button1)
1235 mc->button_down_p = False;
1238 else if (event->xany.type == ButtonPress &&
1239 (event->xbutton.button == Button4 ||
1240 event->xbutton.button == Button5 ||
1241 event->xbutton.button == Button6 ||
1242 event->xbutton.button == Button7))
1244 gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1245 !!event->xbutton.state);
1248 else if (event->xany.type == KeyPress)
1252 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1254 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1256 GLfloat speed = 4.0;
1258 mc->mode_tick = 10 * speed;
1262 else if (event->xany.type == MotionNotify &&
1265 gltrackball_track (mc->trackball,
1266 event->xmotion.x, event->xmotion.y,
1267 MI_WIDTH (mi), MI_HEIGHT (mi));
1276 init_molecule (ModeInfo *mi)
1278 molecule_configuration *mc;
1282 mcs = (molecule_configuration *)
1283 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1285 fprintf(stderr, "%s: out of memory\n", progname);
1290 mc = &mcs[MI_SCREEN(mi)];
1292 if ((mc->glx_context = init_GL(mi)) != NULL) {
1294 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1300 wire = MI_IS_WIREFRAME(mi);
1303 Bool spinx=False, spiny=False, spinz=False;
1304 double spin_speed = 0.5;
1305 double spin_accel = 0.3;
1306 double wander_speed = 0.01;
1311 if (*s == 'x' || *s == 'X') spinx = True;
1312 else if (*s == 'y' || *s == 'Y') spiny = True;
1313 else if (*s == 'z' || *s == 'Z') spinz = True;
1314 else if (*s == '0') ;
1318 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1325 mc->rot = make_rotator (spinx ? spin_speed : 0,
1326 spiny ? spin_speed : 0,
1327 spinz ? spin_speed : 0,
1329 do_wander ? wander_speed : 0,
1330 (spinx && spiny && spinz));
1331 mc->trackball = gltrackball_init ();
1334 orig_do_labels = do_labels;
1335 orig_do_atoms = do_atoms;
1336 orig_do_bonds = do_bonds;
1337 orig_do_shells = do_shells;
1338 orig_wire = MI_IS_WIREFRAME(mi);
1340 mc->molecule_dlist = glGenLists(1);
1342 mc->shell_dlist = glGenLists(1);
1344 load_molecules (mi);
1345 mc->which = random() % mc->nmolecules;
1347 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1348 "NoLabelThreshold");
1349 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1350 "WireframeThreshold");
1358 /* Put the labels on the atoms.
1359 This can't be a part of the display list because of the games
1360 we play with the translation matrix.
1363 draw_labels (ModeInfo *mi)
1365 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1366 int wire = MI_IS_WIREFRAME(mi);
1367 molecule *m = &mc->molecules[mc->which];
1368 XFontStruct *xfont = (mc->scale_down ? mc->xfont2 : mc->xfont1);
1369 GLuint font_dlist = (mc->scale_down ? mc->font2_dlist : mc->font1_dlist);
1376 glDisable (GL_LIGHTING); /* don't light fonts */
1378 for (i = 0; i < m->natoms; i++)
1380 molecule_atom *a = &m->atoms[i];
1381 GLfloat size = atom_size (a);
1387 set_atom_color (mi, a, True, 1);
1389 /* First, we translate the origin to the center of the atom.
1391 Then we retrieve the prevailing modelview matrix (which
1392 includes any rotation, wandering, and user-trackball-rolling
1395 We set the top 3x3 cells of that matrix to be the identity
1396 matrix. This removes all rotation from the matrix, while
1397 leaving the translation alone. This has the effect of
1398 leaving the prevailing coordinate system perpendicular to
1399 the camera view: were we to draw a square face, it would
1400 be in the plane of the screen.
1402 Now we translate by `size' toward the viewer -- so that the
1403 origin is *just in front* of the ball.
1405 Then we draw the label text, allowing the depth buffer to
1406 do its work: that way, labels on atoms will be occluded
1407 properly when other atoms move in front of them.
1409 This technique (of neutralizing rotation relative to the
1410 observer, after both rotations and translations have been
1411 applied) is known as "billboarding".
1414 glTranslatef(a->x, a->y, a->z); /* get matrix */
1415 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1416 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1417 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1418 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1419 glLoadIdentity(); /* reset modelview */
1420 glMultMatrixf (&m[0][0]); /* replace with ours */
1422 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1424 glRasterPos3f (0, 0, 0); /* draw text here */
1426 /* Before drawing the string, shift the origin to center
1427 the text over the origin of the sphere. */
1428 glBitmap (0, 0, 0, 0,
1429 -string_width (xfont, a->label, 0) / 2,
1433 for (j = 0; j < strlen(a->label); j++)
1435 glCallList (font_dlist + (int)(a->label[j]));
1440 /* More efficient to always call glEnable() with correct values
1441 than to call glPushAttrib()/glPopAttrib(), since reading
1442 attributes from GL does a round-trip and stalls the pipeline.
1445 glEnable (GL_LIGHTING);
1450 pick_new_molecule (ModeInfo *mi, time_t last)
1452 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1454 if (mc->nmolecules == 1)
1456 if (last != 0) return;
1461 mc->which = random() % mc->nmolecules;
1466 while (n == mc->which)
1467 n = random() % mc->nmolecules;
1473 char *name = strdup (mc->molecules[mc->which].label);
1474 char *s = strpbrk (name, "\r\n");
1476 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1480 mc->polygon_count = 0;
1482 glNewList (mc->molecule_dlist, GL_COMPILE);
1483 ensure_bounding_box_visible (mi);
1485 do_labels = orig_do_labels;
1486 do_atoms = orig_do_atoms;
1487 do_bonds = orig_do_bonds;
1488 do_shells = orig_do_shells;
1489 MI_IS_WIREFRAME(mi) = orig_wire;
1491 if (mc->molecule_size > mc->no_label_threshold)
1493 if (mc->molecule_size > mc->wireframe_threshold)
1494 MI_IS_WIREFRAME(mi) = 1;
1496 if (MI_IS_WIREFRAME(mi))
1497 do_bonds = 1, do_shells = 0;
1502 if (! (do_bonds || do_atoms || do_labels))
1504 /* Make sure *something* shows up! */
1505 MI_IS_WIREFRAME(mi) = 1;
1509 build_molecule (mi, False);
1514 glNewList (mc->shell_dlist, GL_COMPILE);
1515 ensure_bounding_box_visible (mi);
1521 build_molecule (mi, True);
1524 do_bonds = orig_do_bonds;
1525 do_atoms = orig_do_atoms;
1526 do_labels = orig_do_labels;
1532 draw_molecule (ModeInfo *mi)
1534 time_t now = time ((time_t *) 0);
1535 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1537 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1538 Display *dpy = MI_DISPLAY(mi);
1539 Window window = MI_WINDOW(mi);
1541 if (!mc->glx_context)
1544 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1546 if (mc->draw_time == 0)
1548 pick_new_molecule (mi, mc->draw_time);
1549 mc->draw_time = now;
1551 else if (mc->mode == 0)
1553 if (mc->draw_tick++ > 10)
1555 time_t now = time((time_t *) 0);
1556 if (mc->draw_time == 0) mc->draw_time = now;
1559 if (!mc->button_down_p &&
1560 mc->nmolecules > 1 &&
1561 mc->draw_time + timeout <= now)
1563 /* randomize molecules every -timeout seconds */
1564 mc->mode = 1; /* go out */
1565 mc->mode_tick = 10 * speed;
1566 mc->draw_time = now;
1570 else if (mc->mode == 1) /* out */
1572 if (--mc->mode_tick <= 0)
1574 mc->mode_tick = 10 * speed;
1575 mc->mode = 2; /* go in */
1576 pick_new_molecule (mi, mc->draw_time);
1577 mc->draw_time = now;
1580 else if (mc->mode == 2) /* in */
1582 if (--mc->mode_tick <= 0)
1583 mc->mode = 0; /* normal */
1589 glScalef(1.1, 1.1, 1.1);
1593 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1594 glTranslatef((x - 0.5) * 9,
1598 gltrackball_rotate (mc->trackball);
1600 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1601 glRotatef (x * 360, 1.0, 0.0, 0.0);
1602 glRotatef (y * 360, 0.0, 1.0, 0.0);
1603 glRotatef (z * 360, 0.0, 0.0, 1.0);
1606 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1610 GLfloat s = (mc->mode == 1
1611 ? mc->mode_tick / (10 * speed)
1612 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1617 glCallList (mc->molecule_dlist);
1621 molecule *m = &mc->molecules[mc->which];
1625 /* This can't go in the display list, or the characters are spaced
1626 wrongly when the window is resized. */
1627 if (do_titles && m->label && *m->label)
1629 set_atom_color (mi, 0, True, 1);
1630 print_gl_string (mi->dpy, mc->xfont3, mc->font3_dlist,
1631 mi->xgwa.width, mi->xgwa.height,
1632 10, mi->xgwa.height - 10,
1640 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1642 glCallList (mc->shell_dlist);
1644 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1646 glDepthFunc (GL_EQUAL);
1647 glEnable (GL_BLEND);
1648 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1650 glCallList (mc->shell_dlist);
1652 glDepthFunc (GL_LESS);
1653 glDisable (GL_BLEND);
1658 mi->polygon_count = mc->polygon_count;
1660 if (mi->fps_p) do_fps (mi);
1663 glXSwapBuffers(dpy, window);
1666 XSCREENSAVER_MODULE ("Molecule", molecule)