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-*-240-*\n" \
31 "*titleFont: -*-helvetica-medium-r-normal-*-180-*\n" \
32 "*noLabelThreshold: 30 \n" \
33 "*wireframeThreshold: 150 \n" \
35 # define refresh_molecule 0
36 # define release_molecule 0
38 #define countof(x) (sizeof((x))/sizeof((*x)))
40 #include "xlockmore.h"
46 #include "gltrackball.h"
48 #ifdef USE_GL /* whole file */
50 #include <sys/types.h>
55 #define DEF_TIMEOUT "20"
56 #define DEF_SPIN "XYZ"
57 #define DEF_WANDER "False"
58 #define DEF_LABELS "True"
59 #define DEF_TITLES "True"
60 #define DEF_ATOMS "True"
61 #define DEF_BONDS "True"
62 #define DEF_ESHELLS "True"
63 #define DEF_BBOX "False"
64 #define DEF_SHELL_ALPHA "0.3"
65 #define DEF_MOLECULE "(default)"
66 #define DEF_VERBOSE "False"
68 #define SPHERE_SLICES 24 /* how densely to render spheres */
69 #define SPHERE_STACKS 12
71 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
74 # define TUBE_FACES 12 /* how densely to render tubes */
79 #define SPHERE_SLICES_2 7
80 #define SPHERE_STACKS_2 4
81 #define TUBE_FACES_2 3
85 __extension__ /* don't warn about "string length is greater than the length
86 ISO C89 compilers are required to support" when includng
87 the following data file... */
89 const char * const builtin_pdb_data[] = {
90 # include "molecules.h"
98 const char *text_color;
103 /* These are the traditional colors used to render these atoms,
104 and their approximate size in angstroms.
106 static const atom_data all_atom_data[] = {
107 { "H", 1.17, 0.40, "#FFFFFF", "#B3B3B3", { 0, }},
108 { "C", 1.75, 0.58, "#999999", "#FFFFFF", { 0, }},
109 { "CA", 1.80, 0.60, "#0000FF", "#ADD8E6", { 0, }},
110 { "N", 1.55, 0.52, "#A2B5CD", "#836FFF", { 0, }},
111 { "O", 1.40, 0.47, "#FF0000", "#FFB6C1", { 0, }},
112 { "P", 1.28, 0.43, "#9370DB", "#DB7093", { 0, }},
113 { "S", 1.80, 0.60, "#8B8B00", "#FFFF00", { 0, }},
114 { "bond", 0, 0, "#B3B3B3", "#FFFF00", { 0, }},
115 { "*", 1.40, 0.47, "#008B00", "#90EE90", { 0, }}
120 int id; /* sequence number in the PDB file */
121 const char *label; /* The atom name */
122 GLfloat x, y, z; /* position in 3-space (angstroms) */
123 const atom_data *data; /* computed: which style of atom this is */
127 int from, to; /* atom sequence numbers */
128 int strength; /* how many bonds are between these two atoms */
133 const char *label; /* description of this compound */
134 int natoms, atoms_size;
135 int nbonds, bonds_size;
136 molecule_atom *atoms;
137 molecule_bond *bonds;
142 GLXContext *glx_context;
144 trackball_state *trackball;
147 GLfloat molecule_size; /* max dimension of molecule bounding box */
149 GLfloat no_label_threshold; /* Things happen when molecules are huge */
150 GLfloat wireframe_threshold;
152 int which; /* which of the molecules is being shown */
156 int mode; /* 0 = normal, 1 = out, 2 = in */
159 GLuint molecule_dlist;
162 XFontStruct *xfont1, *xfont2;
163 GLuint font1_dlist, font2_dlist;
171 } molecule_configuration;
174 static molecule_configuration *mcs = NULL;
177 static char *molecule_str;
178 static char *do_spin;
179 static Bool do_wander;
180 static Bool do_titles;
181 static Bool do_labels;
182 static Bool do_atoms;
183 static Bool do_bonds;
184 static Bool do_shells;
186 static Bool verbose_p;
187 static GLfloat shell_alpha;
190 static Bool orig_do_labels, orig_do_atoms, orig_do_bonds, orig_do_shells,
194 static XrmOptionDescRec opts[] = {
195 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
196 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
197 { "-spin", ".spin", XrmoptionSepArg, 0 },
198 { "+spin", ".spin", XrmoptionNoArg, "" },
199 { "-wander", ".wander", XrmoptionNoArg, "True" },
200 { "+wander", ".wander", XrmoptionNoArg, "False" },
201 { "-labels", ".labels", XrmoptionNoArg, "True" },
202 { "+labels", ".labels", XrmoptionNoArg, "False" },
203 { "-titles", ".titles", XrmoptionNoArg, "True" },
204 { "+titles", ".titles", XrmoptionNoArg, "False" },
205 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
206 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
207 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
208 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
209 { "-shells", ".eshells", XrmoptionNoArg, "True" },
210 { "+shells", ".eshells", XrmoptionNoArg, "False" },
211 { "-shell-alpha", ".shellAlpha", XrmoptionSepArg, 0 },
212 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
213 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
214 { "-verbose", ".verbose", XrmoptionNoArg, "True" },
217 static argtype vars[] = {
218 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE, t_String},
219 {&timeout, "timeout", "Seconds", DEF_TIMEOUT, t_Int},
220 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
221 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
222 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
223 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
224 {&do_shells, "eshells", "EShells", DEF_ESHELLS, t_Bool},
225 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
226 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
227 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
228 {&shell_alpha, "shellAlpha", "ShellAlpha", DEF_SHELL_ALPHA, t_Float},
229 {&verbose_p, "verbose", "Verbose", DEF_VERBOSE, t_Bool},
232 ENTRYPOINT ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
240 sphere (molecule_configuration *mc,
241 GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
243 int stacks = (mc->scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
244 int slices = (mc->scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
247 glTranslatef (x, y, z);
248 glScalef (diameter, diameter, diameter);
249 unit_sphere (stacks, slices, wire);
252 return stacks * slices;
257 load_fonts (ModeInfo *mi)
259 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
260 load_font (mi->dpy, "atomFont", &mc->xfont1, &mc->font1_dlist);
261 load_font (mi->dpy, "titleFont", &mc->xfont2, &mc->font2_dlist);
265 static const atom_data *
266 get_atom_data (const char *atom_name)
269 const atom_data *d = 0;
270 char *n = strdup (atom_name);
274 while (!isalpha(*n)) n++;
276 while (L > 0 && !isalpha(n[L-1]))
279 for (i = 0; i < countof(all_atom_data); i++)
281 d = &all_atom_data[i];
282 if (!strcasecmp (n, all_atom_data[i].name))
292 set_atom_color (ModeInfo *mi, const molecule_atom *a,
293 Bool font_p, GLfloat alpha)
301 d = get_atom_data ("bond");
305 gl_color[0] = d->gl_color[4];
306 gl_color[1] = d->gl_color[5];
307 gl_color[2] = d->gl_color[6];
308 gl_color[3] = d->gl_color[7];
312 gl_color[0] = d->gl_color[0];
313 gl_color[1] = d->gl_color[1];
314 gl_color[2] = d->gl_color[2];
315 gl_color[3] = d->gl_color[3];
318 if (gl_color[3] == 0)
320 const char *string = !font_p ? d->color : d->text_color;
322 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
324 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
325 (a ? a->label : d->name), string);
329 gl_color[0] = xcolor.red / 65536.0;
330 gl_color[1] = xcolor.green / 65536.0;
331 gl_color[2] = xcolor.blue / 65536.0;
337 glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
339 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
344 atom_size (const molecule_atom *a)
347 return a->data->size2;
349 return a->data->size;
353 static molecule_atom *
354 get_atom (molecule_atom *atoms, int natoms, int id)
358 /* quick short-circuit */
361 if (atoms[id].id == id)
363 if (id > 0 && atoms[id-1].id == id)
365 if (id < natoms-1 && atoms[id+1].id == id)
369 for (i = 0; i < natoms; i++)
370 if (id == atoms[i].id)
373 fprintf (stderr, "%s: no atom %d\n", progname, id);
379 molecule_bounding_box (ModeInfo *mi,
380 GLfloat *x1, GLfloat *y1, GLfloat *z1,
381 GLfloat *x2, GLfloat *y2, GLfloat *z2)
383 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
384 molecule *m = &mc->molecules[mc->which];
389 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
393 *x1 = *x2 = m->atoms[0].x;
394 *y1 = *y2 = m->atoms[0].y;
395 *z1 = *z2 = m->atoms[0].z;
398 for (i = 1; i < m->natoms; i++)
400 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
401 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
402 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
404 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
405 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
406 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
419 draw_bounding_box (ModeInfo *mi)
421 static const GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
422 static const GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
423 int wire = MI_IS_WIREFRAME(mi);
424 GLfloat x1, y1, z1, x2, y2, z2;
425 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
427 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
430 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
432 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
433 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
435 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
436 glNormal3f(0, -1, 0);
437 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
438 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
440 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
442 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
443 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
445 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
446 glNormal3f(0, 0, -1);
447 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
448 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
450 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
452 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
453 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
455 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
456 glNormal3f(-1, 0, 0);
457 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
458 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
461 glPushAttrib (GL_LIGHTING);
462 glDisable (GL_LIGHTING);
464 glColor3f (c2[0], c2[1], c2[2]);
466 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
467 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
468 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
469 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
470 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
471 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
478 /* Since PDB files don't always have the molecule centered around the
479 origin, and since some molecules are pretty large, scale and/or
480 translate so that the whole molecule is visible in the window.
483 ensure_bounding_box_visible (ModeInfo *mi)
485 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
487 GLfloat x1, y1, z1, x2, y2, z2;
490 GLfloat max_size = 10; /* don't bother scaling down if the molecule
491 is already smaller than this */
493 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
498 size = (w > h ? w : h);
499 size = (size > d ? size : d);
501 mc->molecule_size = size;
507 GLfloat scale = max_size / size;
508 glScalef (scale, scale, scale);
510 mc->scale_down = scale < 0.3;
513 glTranslatef (-(x1 + w/2),
520 /* Constructs the GL shapes of the current molecule
523 build_molecule (ModeInfo *mi, Bool transparent_p)
525 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
526 int wire = MI_IS_WIREFRAME(mi);
528 GLfloat alpha = transparent_p ? shell_alpha : 1.0;
531 molecule *m = &mc->molecules[mc->which];
535 glDisable(GL_CULL_FACE);
536 glDisable(GL_LIGHTING);
537 glDisable(GL_LIGHT0);
538 glDisable(GL_DEPTH_TEST);
539 glDisable(GL_NORMALIZE);
540 glDisable(GL_CULL_FACE);
544 glEnable(GL_CULL_FACE);
545 glEnable(GL_LIGHTING);
547 glEnable(GL_DEPTH_TEST);
548 glEnable(GL_NORMALIZE);
549 glEnable(GL_CULL_FACE);
553 set_atom_color (mi, 0, False, alpha);
556 for (i = 0; i < m->nbonds; i++)
558 const molecule_bond *b = &m->bonds[i];
559 const molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
560 const molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
565 glVertex3f(from->x, from->y, from->z);
566 glVertex3f(to->x, to->y, to->z);
572 int faces = (mc->scale_down ? TUBE_FACES_2 : TUBE_FACES);
578 GLfloat thickness = 0.07 * b->strength;
579 GLfloat cap_size = 0.03;
583 polys += tube (from->x, from->y, from->z,
586 faces, smooth, (!do_atoms || do_shells), wire);
590 if (!wire && do_atoms)
591 for (i = 0; i < m->natoms; i++)
593 const molecule_atom *a = &m->atoms[i];
594 GLfloat size = atom_size (a);
595 set_atom_color (mi, a, False, alpha);
596 polys += sphere (mc, a->x, a->y, a->z, size, wire);
599 if (do_bbox && !transparent_p)
601 draw_bounding_box (mi);
605 mc->polygon_count += polys;
613 push_atom (molecule *m,
614 int id, const char *label,
615 GLfloat x, GLfloat y, GLfloat z)
618 if (m->atoms_size < m->natoms)
621 m->atoms = (molecule_atom *) realloc (m->atoms,
622 m->atoms_size * sizeof(*m->atoms));
624 m->atoms[m->natoms-1].id = id;
625 m->atoms[m->natoms-1].label = label;
626 m->atoms[m->natoms-1].x = x;
627 m->atoms[m->natoms-1].y = y;
628 m->atoms[m->natoms-1].z = z;
629 m->atoms[m->natoms-1].data = get_atom_data (label);
634 push_bond (molecule *m, int from, int to)
638 for (i = 0; i < m->nbonds; i++)
639 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
640 (m->bonds[i].to == from && m->bonds[i].from == to))
642 m->bonds[i].strength++;
647 if (m->bonds_size < m->nbonds)
650 m->bonds = (molecule_bond *) realloc (m->bonds,
651 m->bonds_size * sizeof(*m->bonds));
653 m->bonds[m->nbonds-1].from = from;
654 m->bonds[m->nbonds-1].to = to;
655 m->bonds[m->nbonds-1].strength = 1;
660 parse_error (const char *file, int lineno, const char *line)
662 fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
663 progname, file, lineno, line);
668 /* This function is crap.
671 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
673 const char *s = data;
677 if ((!m->label || !*m->label) &&
678 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
680 char *name = calloc (1, 100);
687 while (isspace(*n2)) n2++;
689 ss = strchr (n2, '\n');
691 ss = strchr (n2, '\r');
694 ss = n2+strlen(n2)-1;
695 while (isspace(*ss) && ss > n2)
698 if (strlen (n2) > 4 &&
699 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
700 n2[strlen(n2)-4] = 0;
702 if (m->label) free ((char *) m->label);
703 m->label = strdup (n2);
706 else if (!strncmp (s, "TITLE ", 6) ||
707 !strncmp (s, "HEADER", 6) ||
708 !strncmp (s, "COMPND", 6) ||
709 !strncmp (s, "AUTHOR", 6) ||
710 !strncmp (s, "REVDAT", 6) ||
711 !strncmp (s, "SOURCE", 6) ||
712 !strncmp (s, "EXPDTA", 6) ||
713 !strncmp (s, "JRNL ", 6) ||
714 !strncmp (s, "REMARK", 6) ||
715 !strncmp (s, "SEQRES", 6) ||
716 !strncmp (s, "HET ", 6) ||
717 !strncmp (s, "FORMUL", 6) ||
718 !strncmp (s, "CRYST1", 6) ||
719 !strncmp (s, "ORIGX1", 6) ||
720 !strncmp (s, "ORIGX2", 6) ||
721 !strncmp (s, "ORIGX3", 6) ||
722 !strncmp (s, "SCALE1", 6) ||
723 !strncmp (s, "SCALE2", 6) ||
724 !strncmp (s, "SCALE3", 6) ||
725 !strncmp (s, "MASTER", 6) ||
726 !strncmp (s, "KEYWDS", 6) ||
727 !strncmp (s, "DBREF ", 6) ||
728 !strncmp (s, "HETNAM", 6) ||
729 !strncmp (s, "HETSYN", 6) ||
730 !strncmp (s, "HELIX ", 6) ||
731 !strncmp (s, "LINK ", 6) ||
732 !strncmp (s, "MTRIX1", 6) ||
733 !strncmp (s, "MTRIX2", 6) ||
734 !strncmp (s, "MTRIX3", 6) ||
735 !strncmp (s, "SHEET ", 6) ||
736 !strncmp (s, "CISPEP", 6) ||
738 !strncmp (s, "SEQADV", 6) ||
739 !strncmp (s, "SITE ", 5) ||
740 !strncmp (s, "FTNOTE", 6) ||
741 !strncmp (s, "MODEL ", 5) ||
742 !strncmp (s, "ENDMDL", 6) ||
743 !strncmp (s, "SPRSDE", 6) ||
744 !strncmp (s, "MODRES", 6) ||
746 !strncmp (s, "GENERATED BY", 12) ||
747 !strncmp (s, "TER ", 4) ||
748 !strncmp (s, "END ", 4) ||
749 !strncmp (s, "TER\n", 4) ||
750 !strncmp (s, "END\n", 4) ||
751 !strncmp (s, "\n", 1))
754 else if (!strncmp (s, "ATOM ", 7))
757 const char *end = strchr (s, '\n');
759 char *name = (char *) calloc (1, 4);
760 GLfloat x = -999, y = -999, z = -999;
762 if (1 != sscanf (s+7, " %d ", &id))
763 parse_error (filename, line, s);
765 /* Use the "atom name" field if that is all that is available. */
766 strncpy (name, s+12, 3);
768 /* But prefer the "element" field. */
769 if (L > 77 && !isspace(s[77])) {
770 /* fprintf(stderr, " \"%s\" -> ", name); */
774 /* fprintf(stderr, "\"%s\"\n", name); */
777 while (isspace(*name)) name++;
778 ss = name + strlen(name)-1;
779 while (isspace(*ss) && ss > name)
787 if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
788 parse_error (filename, line, s);
791 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
792 progname, filename, line,
795 push_atom (m, id, name, x, y, z);
797 else if (!strncmp (s, "HETATM ", 7))
800 char *name = (char *) calloc (1, 4);
801 GLfloat x = -999, y = -999, z = -999;
803 if (1 != sscanf (s+7, " %d ", &id))
804 parse_error (filename, line, s);
806 strncpy (name, s+12, 3);
807 while (isspace(*name)) name++;
808 ss = name + strlen(name)-1;
809 while (isspace(*ss) && ss > name)
811 if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
812 parse_error (filename, line, s);
814 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
815 progname, filename, line,
818 push_atom (m, id, name, x, y, z);
820 else if (!strncmp (s, "CONECT ", 7))
823 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
824 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
825 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
826 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
828 for (j = 1; j < i; j++)
832 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
833 progname, filename, line, atoms[0], atoms[j]);
835 push_bond (m, atoms[0], atoms[j]);
840 char *s1 = strdup (s);
841 for (ss = s1; *ss && *ss != '\n'; ss++)
844 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
845 progname, filename, line, s1);
848 while (*s && *s != '\n')
858 parse_pdb_file (molecule *m, const char *name)
861 int buf_size = 40960;
865 in = fopen(name, "r");
868 char *buf = (char *) malloc(1024 + strlen(name));
869 sprintf(buf, "%s: error reading \"%s\"", progname, name);
874 buf = (char *) malloc (buf_size);
876 while (fgets (buf, buf_size-1, in))
879 for (s = buf; *s; s++)
880 if (*s == '\r') *s = '\n';
881 parse_pdb_data (m, buf, name, line++);
889 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
894 if (!m->nbonds && do_bonds)
896 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
905 typedef struct { char *atom; int count; } atom_and_count;
907 /* When listing the components of a molecule, the convention is to put the
908 carbon atoms first, the hydrogen atoms second, and the other atom types
909 sorted alphabetically after that (although for some molecules, the usual
910 order is different: we special-case a few of those.)
913 cmp_atoms (const void *aa, const void *bb)
915 const atom_and_count *a = (atom_and_count *) aa;
916 const atom_and_count *b = (atom_and_count *) bb;
917 if (!a->atom) return 1;
918 if (!b->atom) return -1;
919 if (!strcmp(a->atom, "C")) return -1;
920 if (!strcmp(b->atom, "C")) return 1;
921 if (!strcmp(a->atom, "H")) return -1;
922 if (!strcmp(b->atom, "H")) return 1;
923 return strcmp (a->atom, b->atom);
926 static void special_case_formula (char *f);
929 generate_molecule_formula (molecule *m)
931 char *buf = (char *) malloc (m->natoms * 10);
934 atom_and_count counts[200];
935 memset (counts, 0, sizeof(counts));
937 for (i = 0; i < m->natoms; i++)
940 char *a = (char *) m->atoms[i].label;
942 while (!isalpha(*a)) a++;
944 for (e = a; isalpha(*e); e++);
946 while (counts[j].atom && !!strcmp(a, counts[j].atom))
956 while (counts[i].atom) i++;
957 qsort (counts, i, sizeof(*counts), cmp_atoms);
960 while (counts[i].atom)
962 strcat (s, counts[i].atom);
963 free (counts[i].atom);
965 if (counts[i].count > 1)
966 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
971 special_case_formula (buf);
973 if (!m->label) m->label = strdup("");
974 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
975 strcpy (s, m->label);
978 free ((char *) m->label);
983 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
985 special_case_formula (char *f)
987 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
988 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
989 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
990 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
991 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
992 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
993 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
994 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
999 insert_vertical_whitespace (char *string)
1003 if ((string[0] == ',' ||
1005 string[0] == ':') &&
1007 string[0] = ' ', string[1] = '\n';
1013 /* Construct the molecule data from either: the builtins; or from
1014 the (one) .pdb file specified with -molecule.
1017 load_molecules (ModeInfo *mi)
1019 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1020 int wire = MI_IS_WIREFRAME(mi);
1024 if (molecule_str && *molecule_str &&
1025 strcmp(molecule_str, "(default)")) /* try external PDB files */
1027 /* The -molecule option can point to a .pdb file, or to
1028 a directory of them.
1036 if (!stat (molecule_str, &st) &&
1037 S_ISDIR (st.st_mode))
1041 struct dirent *dentry;
1043 pdb_dir = opendir (molecule_str);
1046 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1052 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1056 files = (char **) calloc (sizeof(*files), list_size);
1058 while ((dentry = readdir (pdb_dir)))
1060 int L = strlen (dentry->d_name);
1061 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1064 if (nfiles >= list_size-1)
1066 list_size = (list_size + 10) * 1.2;
1068 realloc (files, list_size * sizeof(*files));
1072 fprintf (stderr, "%s: out of memory (%d files)\n",
1078 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1080 strcpy (fn, molecule_str);
1081 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1082 strcat (fn, dentry->d_name);
1083 files[nfiles++] = fn;
1085 fprintf (stderr, "%s: file %s\n", progname, fn);
1091 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1092 progname, molecule_str);
1096 files = (char **) malloc (sizeof (*files));
1098 files[0] = strdup (molecule_str);
1100 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1103 mc->nmolecules = nfiles;
1104 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1106 for (i = 0; i < mc->nmolecules; i++)
1109 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1110 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1112 if ((wire || !do_atoms) &&
1114 mc->molecules[molecule_ctr].nbonds == 0)
1116 /* If we're not drawing atoms (e.g., wireframe mode), and
1117 there is no bond info, then make sure labels are turned
1118 on, or we'll be looking at a black screen... */
1119 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1120 progname, files[i]);
1131 mc->nmolecules = molecule_ctr;
1134 if (mc->nmolecules == 0) /* do the builtins if no files */
1136 mc->nmolecules = countof(builtin_pdb_data);
1137 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1138 for (i = 0; i < mc->nmolecules; i++)
1141 sprintf (name, "<builtin-%d>", i);
1142 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1143 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1147 for (i = 0; i < mc->nmolecules; i++)
1149 generate_molecule_formula (&mc->molecules[i]);
1150 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1156 /* Window management, etc
1159 reshape_molecule (ModeInfo *mi, int width, int height)
1161 GLfloat h = (GLfloat) height / (GLfloat) width;
1163 glViewport (0, 0, (GLint) width, (GLint) height);
1165 glMatrixMode(GL_PROJECTION);
1167 gluPerspective (30.0, 1/h, 20.0, 100.0);
1169 glMatrixMode(GL_MODELVIEW);
1171 gluLookAt( 0.0, 0.0, 30.0,
1175 glClear(GL_COLOR_BUFFER_BIT);
1180 gl_init (ModeInfo *mi)
1182 static const GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1183 static const GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1184 static const GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1185 static const GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1186 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1187 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1188 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1189 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1194 startup_blurb (ModeInfo *mi)
1196 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1197 const char *s = "Constructing molecules...";
1198 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1199 mi->xgwa.width, mi->xgwa.height,
1200 10, mi->xgwa.height - 10,
1203 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1207 molecule_handle_event (ModeInfo *mi, XEvent *event)
1209 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1211 if (event->xany.type == ButtonPress &&
1212 event->xbutton.button == Button1)
1214 mc->button_down_p = True;
1215 gltrackball_start (mc->trackball,
1216 event->xbutton.x, event->xbutton.y,
1217 MI_WIDTH (mi), MI_HEIGHT (mi));
1220 else if (event->xany.type == ButtonRelease &&
1221 event->xbutton.button == Button1)
1223 mc->button_down_p = False;
1226 else if (event->xany.type == ButtonPress &&
1227 (event->xbutton.button == Button4 ||
1228 event->xbutton.button == Button5 ||
1229 event->xbutton.button == Button6 ||
1230 event->xbutton.button == Button7))
1232 gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1233 !!event->xbutton.state);
1236 else if (event->xany.type == KeyPress)
1240 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1242 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1244 GLfloat speed = 4.0;
1246 mc->mode_tick = 10 * speed;
1250 else if (event->xany.type == MotionNotify &&
1253 gltrackball_track (mc->trackball,
1254 event->xmotion.x, event->xmotion.y,
1255 MI_WIDTH (mi), MI_HEIGHT (mi));
1264 init_molecule (ModeInfo *mi)
1266 molecule_configuration *mc;
1270 mcs = (molecule_configuration *)
1271 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1273 fprintf(stderr, "%s: out of memory\n", progname);
1278 mc = &mcs[MI_SCREEN(mi)];
1280 if ((mc->glx_context = init_GL(mi)) != NULL) {
1282 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1288 wire = MI_IS_WIREFRAME(mi);
1291 Bool spinx=False, spiny=False, spinz=False;
1292 double spin_speed = 0.5;
1293 double spin_accel = 0.3;
1294 double wander_speed = 0.01;
1299 if (*s == 'x' || *s == 'X') spinx = True;
1300 else if (*s == 'y' || *s == 'Y') spiny = True;
1301 else if (*s == 'z' || *s == 'Z') spinz = True;
1302 else if (*s == '0') ;
1306 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1313 mc->rot = make_rotator (spinx ? spin_speed : 0,
1314 spiny ? spin_speed : 0,
1315 spinz ? spin_speed : 0,
1317 do_wander ? wander_speed : 0,
1318 (spinx && spiny && spinz));
1319 mc->trackball = gltrackball_init ();
1322 orig_do_labels = do_labels;
1323 orig_do_atoms = do_atoms;
1324 orig_do_bonds = do_bonds;
1325 orig_do_shells = do_shells;
1326 orig_wire = MI_IS_WIREFRAME(mi);
1328 mc->molecule_dlist = glGenLists(1);
1330 mc->shell_dlist = glGenLists(1);
1332 load_molecules (mi);
1333 mc->which = random() % mc->nmolecules;
1335 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1336 "NoLabelThreshold");
1337 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1338 "WireframeThreshold");
1346 /* Put the labels on the atoms.
1347 This can't be a part of the display list because of the games
1348 we play with the translation matrix.
1351 draw_labels (ModeInfo *mi)
1353 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1354 int wire = MI_IS_WIREFRAME(mi);
1355 molecule *m = &mc->molecules[mc->which];
1362 glDisable (GL_LIGHTING); /* don't light fonts */
1364 for (i = 0; i < m->natoms; i++)
1366 molecule_atom *a = &m->atoms[i];
1367 GLfloat size = atom_size (a);
1373 set_atom_color (mi, a, True, 1);
1375 /* First, we translate the origin to the center of the atom.
1377 Then we retrieve the prevailing modelview matrix (which
1378 includes any rotation, wandering, and user-trackball-rolling
1381 We set the top 3x3 cells of that matrix to be the identity
1382 matrix. This removes all rotation from the matrix, while
1383 leaving the translation alone. This has the effect of
1384 leaving the prevailing coordinate system perpendicular to
1385 the camera view: were we to draw a square face, it would
1386 be in the plane of the screen.
1388 Now we translate by `size' toward the viewer -- so that the
1389 origin is *just in front* of the ball.
1391 Then we draw the label text, allowing the depth buffer to
1392 do its work: that way, labels on atoms will be occluded
1393 properly when other atoms move in front of them.
1395 This technique (of neutralizing rotation relative to the
1396 observer, after both rotations and translations have been
1397 applied) is known as "billboarding".
1400 glTranslatef(a->x, a->y, a->z); /* get matrix */
1401 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1402 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1403 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1404 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1405 glLoadIdentity(); /* reset modelview */
1406 glMultMatrixf (&m[0][0]); /* replace with ours */
1408 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1410 glRasterPos3f (0, 0, 0); /* draw text here */
1412 /* Before drawing the string, shift the origin to center
1413 the text over the origin of the sphere. */
1414 glBitmap (0, 0, 0, 0,
1415 -string_width (mc->xfont1, a->label, 0) / 2,
1416 -mc->xfont1->descent,
1419 for (j = 0; j < strlen(a->label); j++)
1421 glCallList (mc->font1_dlist + (int)(a->label[j]));
1426 /* More efficient to always call glEnable() with correct values
1427 than to call glPushAttrib()/glPopAttrib(), since reading
1428 attributes from GL does a round-trip and stalls the pipeline.
1431 glEnable (GL_LIGHTING);
1436 pick_new_molecule (ModeInfo *mi, time_t last)
1438 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1440 if (mc->nmolecules == 1)
1442 if (last != 0) return;
1447 mc->which = random() % mc->nmolecules;
1452 while (n == mc->which)
1453 n = random() % mc->nmolecules;
1459 char *name = strdup (mc->molecules[mc->which].label);
1460 char *s = strpbrk (name, "\r\n");
1462 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1466 mc->polygon_count = 0;
1468 glNewList (mc->molecule_dlist, GL_COMPILE);
1469 ensure_bounding_box_visible (mi);
1471 do_labels = orig_do_labels;
1472 do_atoms = orig_do_atoms;
1473 do_bonds = orig_do_bonds;
1474 do_shells = orig_do_shells;
1475 MI_IS_WIREFRAME(mi) = orig_wire;
1477 if (mc->molecule_size > mc->no_label_threshold)
1479 if (mc->molecule_size > mc->wireframe_threshold)
1480 MI_IS_WIREFRAME(mi) = 1;
1482 if (MI_IS_WIREFRAME(mi))
1483 do_bonds = 1, do_shells = 0;
1488 if (! (do_bonds || do_atoms || do_labels))
1490 /* Make sure *something* shows up! */
1491 MI_IS_WIREFRAME(mi) = 1;
1495 build_molecule (mi, False);
1500 glNewList (mc->shell_dlist, GL_COMPILE);
1501 ensure_bounding_box_visible (mi);
1507 build_molecule (mi, True);
1510 do_bonds = orig_do_bonds;
1511 do_atoms = orig_do_atoms;
1512 do_labels = orig_do_labels;
1518 draw_molecule (ModeInfo *mi)
1520 time_t now = time ((time_t *) 0);
1521 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1523 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1524 Display *dpy = MI_DISPLAY(mi);
1525 Window window = MI_WINDOW(mi);
1527 if (!mc->glx_context)
1530 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1532 if (mc->draw_time == 0)
1534 pick_new_molecule (mi, mc->draw_time);
1535 mc->draw_time = now;
1537 else if (mc->mode == 0)
1539 if (mc->draw_tick++ > 10)
1541 time_t now = time((time_t *) 0);
1542 if (mc->draw_time == 0) mc->draw_time = now;
1545 if (!mc->button_down_p &&
1546 mc->nmolecules > 1 &&
1547 mc->draw_time + timeout <= now)
1549 /* randomize molecules every -timeout seconds */
1550 mc->mode = 1; /* go out */
1551 mc->mode_tick = 10 * speed;
1552 mc->draw_time = now;
1556 else if (mc->mode == 1) /* out */
1558 if (--mc->mode_tick <= 0)
1560 mc->mode_tick = 10 * speed;
1561 mc->mode = 2; /* go in */
1562 pick_new_molecule (mi, mc->draw_time);
1563 mc->draw_time = now;
1566 else if (mc->mode == 2) /* in */
1568 if (--mc->mode_tick <= 0)
1569 mc->mode = 0; /* normal */
1575 glScalef(1.1, 1.1, 1.1);
1579 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1580 glTranslatef((x - 0.5) * 9,
1584 gltrackball_rotate (mc->trackball);
1586 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1587 glRotatef (x * 360, 1.0, 0.0, 0.0);
1588 glRotatef (y * 360, 0.0, 1.0, 0.0);
1589 glRotatef (z * 360, 0.0, 0.0, 1.0);
1592 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1596 GLfloat s = (mc->mode == 1
1597 ? mc->mode_tick / (10 * speed)
1598 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1603 glCallList (mc->molecule_dlist);
1607 molecule *m = &mc->molecules[mc->which];
1611 /* This can't go in the display list, or the characters are spaced
1612 wrongly when the window is resized. */
1613 if (do_titles && m->label && *m->label)
1615 set_atom_color (mi, 0, True, 1);
1616 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1617 mi->xgwa.width, mi->xgwa.height,
1618 10, mi->xgwa.height - 10,
1626 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1628 glCallList (mc->shell_dlist);
1630 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1632 glDepthFunc (GL_EQUAL);
1633 glEnable (GL_BLEND);
1634 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1636 glCallList (mc->shell_dlist);
1638 glDepthFunc (GL_LESS);
1639 glDisable (GL_BLEND);
1644 mi->polygon_count = mc->polygon_count;
1646 if (mi->fps_p) do_fps (mi);
1649 glXSwapBuffers(dpy, window);
1652 XSCREENSAVER_MODULE ("Molecule", molecule)