1 /* molecule, Copyright (c) 2001-2014 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: 150 \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 48 /* how densely to render spheres */
69 #define SPHERE_STACKS 24
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 14
80 #define SPHERE_STACKS_2 8
81 #define TUBE_FACES_2 6
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 static const char * const builtin_pdb_data[] = {
90 # include "molecules.h"
103 const char *text_color;
108 /* These are the traditional colors used to render these atoms,
109 and their approximate size in angstroms.
111 static const atom_data all_atom_data[] = {
112 { "H", 1.17, 0.40, "#FFFFFF", "#000000", { 0, }},
113 { "C", 1.75, 0.58, "#999999", "#FFFFFF", { 0, }},
114 { "CA", 1.80, 0.60, "#0000FF", "#ADD8E6", { 0, }},
115 { "N", 1.55, 0.52, "#A2B5CD", "#EE99FF", { 0, }},
116 { "O", 1.40, 0.47, "#FF0000", "#FFB6C1", { 0, }},
117 { "P", 1.28, 0.43, "#9370DB", "#DB7093", { 0, }},
118 { "S", 1.80, 0.60, "#8B8B00", "#FFFF00", { 0, }},
119 { "bond", 0, 0, "#B3B3B3", "#FFFF00", { 0, }},
120 { "*", 1.40, 0.47, "#008B00", "#90EE90", { 0, }}
125 int id; /* sequence number in the PDB file */
126 const char *label; /* The atom name */
127 GLfloat x, y, z; /* position in 3-space (angstroms) */
128 const atom_data *data; /* computed: which style of atom this is */
132 int from, to; /* atom sequence numbers */
133 int strength; /* how many bonds are between these two atoms */
138 const char *label; /* description of this compound */
139 int natoms, atoms_size;
140 int nbonds, bonds_size;
141 molecule_atom *atoms;
142 molecule_bond *bonds;
147 GLXContext *glx_context;
149 trackball_state *trackball;
152 GLfloat molecule_size; /* max dimension of molecule bounding box */
154 GLfloat no_label_threshold; /* Things happen when molecules are huge */
155 GLfloat wireframe_threshold;
157 int which; /* which of the molecules is being shown */
161 int mode; /* 0 = normal, 1 = out, 2 = in */
163 int next; /* 0 = random, -1 = back, 1 = forward */
165 GLuint molecule_dlist;
168 texture_font_data *atom_font, *title_font;
175 GLfloat overall_scale;
178 } molecule_configuration;
181 static molecule_configuration *mcs = NULL;
184 static char *molecule_str;
185 static char *do_spin;
186 static Bool do_wander;
187 static Bool do_titles;
188 static Bool do_labels;
189 static Bool do_atoms;
190 static Bool do_bonds;
191 static Bool do_shells;
193 static Bool verbose_p;
194 static GLfloat shell_alpha;
197 static Bool orig_do_labels, orig_do_atoms, orig_do_bonds, orig_do_shells,
201 static XrmOptionDescRec opts[] = {
202 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
203 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
204 { "-spin", ".spin", XrmoptionSepArg, 0 },
205 { "+spin", ".spin", XrmoptionNoArg, "" },
206 { "-wander", ".wander", XrmoptionNoArg, "True" },
207 { "+wander", ".wander", XrmoptionNoArg, "False" },
208 { "-labels", ".labels", XrmoptionNoArg, "True" },
209 { "+labels", ".labels", XrmoptionNoArg, "False" },
210 { "-titles", ".titles", XrmoptionNoArg, "True" },
211 { "+titles", ".titles", XrmoptionNoArg, "False" },
212 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
213 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
214 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
215 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
216 { "-shells", ".eshells", XrmoptionNoArg, "True" },
217 { "+shells", ".eshells", XrmoptionNoArg, "False" },
218 { "-shell-alpha", ".shellAlpha", XrmoptionSepArg, 0 },
219 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
220 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
221 { "-verbose", ".verbose", XrmoptionNoArg, "True" },
224 static argtype vars[] = {
225 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE, t_String},
226 {&timeout, "timeout", "Seconds", DEF_TIMEOUT, t_Int},
227 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
228 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
229 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
230 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
231 {&do_shells, "eshells", "EShells", DEF_ESHELLS, t_Bool},
232 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
233 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
234 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
235 {&shell_alpha, "shellAlpha", "ShellAlpha", DEF_SHELL_ALPHA, t_Float},
236 {&verbose_p, "verbose", "Verbose", DEF_VERBOSE, t_Bool},
239 ENTRYPOINT ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
247 sphere (molecule_configuration *mc,
248 GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
250 int stacks = (mc->low_rez_p ? SPHERE_STACKS_2 : SPHERE_STACKS);
251 int slices = (mc->low_rez_p ? SPHERE_SLICES_2 : SPHERE_SLICES);
254 glTranslatef (x, y, z);
255 glScalef (diameter, diameter, diameter);
256 unit_sphere (stacks, slices, wire);
259 return stacks * slices;
264 load_fonts (ModeInfo *mi)
266 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
267 mc->atom_font = load_texture_font (mi->dpy, "atomFont");
268 mc->title_font = load_texture_font (mi->dpy, "titleFont");
272 static const atom_data *
273 get_atom_data (const char *atom_name)
276 const atom_data *d = 0;
277 char *n = strdup (atom_name);
281 while (!isalpha(*n)) n++;
283 while (L > 0 && !isalpha(n[L-1]))
286 for (i = 0; i < countof(all_atom_data); i++)
288 d = &all_atom_data[i];
289 if (!strcasecmp (n, all_atom_data[i].name))
299 set_atom_color (ModeInfo *mi, const molecule_atom *a,
300 Bool font_p, GLfloat alpha)
308 d = get_atom_data ("bond");
312 gl_color[0] = d->gl_color[4];
313 gl_color[1] = d->gl_color[5];
314 gl_color[2] = d->gl_color[6];
315 gl_color[3] = d->gl_color[7];
319 gl_color[0] = d->gl_color[0];
320 gl_color[1] = d->gl_color[1];
321 gl_color[2] = d->gl_color[2];
322 gl_color[3] = d->gl_color[3];
325 if (gl_color[3] == 0)
327 const char *string = !font_p ? d->color : d->text_color;
329 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
331 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
332 (a ? a->label : d->name), string);
336 gl_color[0] = xcolor.red / 65536.0;
337 gl_color[1] = xcolor.green / 65536.0;
338 gl_color[2] = xcolor.blue / 65536.0;
343 /* If we're not drawing atoms, and the color is black, use white instead.
344 This is a kludge so that H can have black text over its white ball,
345 but the text still shows up if balls are off.
347 if (font_p && !do_atoms &&
348 gl_color[0] == 0 && gl_color[1] == 0 && gl_color[2] == 0)
350 gl_color[0] = gl_color[1] = gl_color[2] = 1;
354 glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
356 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
361 atom_size (const molecule_atom *a)
364 return a->data->size2;
366 return a->data->size;
370 static molecule_atom *
371 get_atom (molecule_atom *atoms, int natoms, int id)
375 /* quick short-circuit */
378 if (atoms[id].id == id)
380 if (id > 0 && atoms[id-1].id == id)
382 if (id < natoms-1 && atoms[id+1].id == id)
386 for (i = 0; i < natoms; i++)
387 if (id == atoms[i].id)
390 fprintf (stderr, "%s: no atom %d\n", progname, id);
396 molecule_bounding_box (ModeInfo *mi,
397 GLfloat *x1, GLfloat *y1, GLfloat *z1,
398 GLfloat *x2, GLfloat *y2, GLfloat *z2)
400 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
401 molecule *m = &mc->molecules[mc->which];
406 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
410 *x1 = *x2 = m->atoms[0].x;
411 *y1 = *y2 = m->atoms[0].y;
412 *z1 = *z2 = m->atoms[0].z;
415 for (i = 1; i < m->natoms; i++)
417 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
418 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
419 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
421 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
422 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
423 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
436 draw_bounding_box (ModeInfo *mi)
438 static const GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
439 static const GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
440 int wire = MI_IS_WIREFRAME(mi);
441 GLfloat x1, y1, z1, x2, y2, z2;
442 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
444 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
447 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
449 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
450 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
452 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
453 glNormal3f(0, -1, 0);
454 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
455 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
457 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
459 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
460 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
462 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
463 glNormal3f(0, 0, -1);
464 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
465 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
467 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
469 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
470 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
472 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
473 glNormal3f(-1, 0, 0);
474 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
475 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
478 glDisable (GL_LIGHTING);
480 glColor3f (c2[0], c2[1], c2[2]);
482 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
483 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
484 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
485 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
486 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
487 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
491 glEnable (GL_LIGHTING);
495 /* Since PDB files don't always have the molecule centered around the
496 origin, and since some molecules are pretty large, scale and/or
497 translate so that the whole molecule is visible in the window.
500 ensure_bounding_box_visible (ModeInfo *mi)
502 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
504 GLfloat x1, y1, z1, x2, y2, z2;
507 GLfloat max_size = 10; /* don't bother scaling down if the molecule
508 is already smaller than this */
510 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
515 size = (w > h ? w : h);
516 size = (size > d ? size : d);
518 mc->molecule_size = size;
521 mc->overall_scale = 1;
525 mc->overall_scale = max_size / size;
526 glScalef (mc->overall_scale, mc->overall_scale, mc->overall_scale);
528 mc->low_rez_p = mc->overall_scale < 0.3;
531 glTranslatef (-(x1 + w/2),
538 /* Constructs the GL shapes of the current molecule
541 build_molecule (ModeInfo *mi, Bool transparent_p)
543 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
544 int wire = MI_IS_WIREFRAME(mi);
546 GLfloat alpha = transparent_p ? shell_alpha : 1.0;
549 molecule *m = &mc->molecules[mc->which];
553 glDisable(GL_CULL_FACE);
554 glDisable(GL_LIGHTING);
555 glDisable(GL_LIGHT0);
556 glDisable(GL_DEPTH_TEST);
557 glDisable(GL_NORMALIZE);
558 glDisable(GL_CULL_FACE);
562 glEnable(GL_CULL_FACE);
563 glEnable(GL_LIGHTING);
565 glEnable(GL_DEPTH_TEST);
566 glEnable(GL_NORMALIZE);
567 glEnable(GL_CULL_FACE);
571 set_atom_color (mi, 0, False, alpha);
574 for (i = 0; i < m->nbonds; i++)
576 const molecule_bond *b = &m->bonds[i];
577 const molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
578 const molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
583 glVertex3f(from->x, from->y, from->z);
584 glVertex3f(to->x, to->y, to->z);
590 int faces = (mc->low_rez_p ? TUBE_FACES_2 : TUBE_FACES);
596 Bool cap_p = (!do_atoms || do_shells);
598 GLfloat thickness = base * b->strength;
599 GLfloat cap_size = (cap_p ? base / 2 : 0);
603 polys += tube (from->x, from->y, from->z,
606 faces, smooth, cap_p, wire);
610 if (!wire && do_atoms)
611 for (i = 0; i < m->natoms; i++)
613 const molecule_atom *a = &m->atoms[i];
614 GLfloat size = atom_size (a);
615 set_atom_color (mi, a, False, alpha);
616 polys += sphere (mc, a->x, a->y, a->z, size, wire);
619 if (do_bbox && !transparent_p)
621 draw_bounding_box (mi);
625 mc->polygon_count += polys;
633 push_atom (molecule *m,
634 int id, const char *label,
635 GLfloat x, GLfloat y, GLfloat z)
638 if (m->atoms_size < m->natoms)
641 m->atoms = (molecule_atom *) realloc (m->atoms,
642 m->atoms_size * sizeof(*m->atoms));
644 m->atoms[m->natoms-1].id = id;
645 m->atoms[m->natoms-1].label = label;
646 m->atoms[m->natoms-1].x = x;
647 m->atoms[m->natoms-1].y = y;
648 m->atoms[m->natoms-1].z = z;
649 m->atoms[m->natoms-1].data = get_atom_data (label);
654 push_bond (molecule *m, int from, int to)
658 for (i = 0; i < m->nbonds; i++)
659 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
660 (m->bonds[i].to == from && m->bonds[i].from == to))
662 m->bonds[i].strength++;
667 if (m->bonds_size < m->nbonds)
670 m->bonds = (molecule_bond *) realloc (m->bonds,
671 m->bonds_size * sizeof(*m->bonds));
673 m->bonds[m->nbonds-1].from = from;
674 m->bonds[m->nbonds-1].to = to;
675 m->bonds[m->nbonds-1].strength = 1;
680 parse_error (const char *file, int lineno, const char *line)
682 fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
683 progname, file, lineno, line);
688 /* This function is crap.
691 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
693 const char *s = data;
697 if ((!m->label || !*m->label) &&
698 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
700 char *name = calloc (1, 100);
707 while (isspace(*n2)) n2++;
709 ss = strchr (n2, '\n');
711 ss = strchr (n2, '\r');
714 ss = n2+strlen(n2)-1;
715 while (isspace(*ss) && ss > n2)
718 if (strlen (n2) > 4 &&
719 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
720 n2[strlen(n2)-4] = 0;
722 if (m->label) free ((char *) m->label);
723 m->label = strdup (n2);
726 else if (!strncmp (s, "TITLE ", 6) ||
727 !strncmp (s, "HEADER", 6) ||
728 !strncmp (s, "COMPND", 6) ||
729 !strncmp (s, "AUTHOR", 6) ||
730 !strncmp (s, "REVDAT", 6) ||
731 !strncmp (s, "SOURCE", 6) ||
732 !strncmp (s, "EXPDTA", 6) ||
733 !strncmp (s, "JRNL ", 6) ||
734 !strncmp (s, "REMARK", 6) ||
735 !strncmp (s, "SEQRES", 6) ||
736 !strncmp (s, "HET ", 6) ||
737 !strncmp (s, "FORMUL", 6) ||
738 !strncmp (s, "CRYST1", 6) ||
739 !strncmp (s, "ORIGX1", 6) ||
740 !strncmp (s, "ORIGX2", 6) ||
741 !strncmp (s, "ORIGX3", 6) ||
742 !strncmp (s, "SCALE1", 6) ||
743 !strncmp (s, "SCALE2", 6) ||
744 !strncmp (s, "SCALE3", 6) ||
745 !strncmp (s, "MASTER", 6) ||
746 !strncmp (s, "KEYWDS", 6) ||
747 !strncmp (s, "DBREF ", 6) ||
748 !strncmp (s, "HETNAM", 6) ||
749 !strncmp (s, "HETSYN", 6) ||
750 !strncmp (s, "HELIX ", 6) ||
751 !strncmp (s, "LINK ", 6) ||
752 !strncmp (s, "MTRIX1", 6) ||
753 !strncmp (s, "MTRIX2", 6) ||
754 !strncmp (s, "MTRIX3", 6) ||
755 !strncmp (s, "SHEET ", 6) ||
756 !strncmp (s, "CISPEP", 6) ||
758 !strncmp (s, "SEQADV", 6) ||
759 !strncmp (s, "SITE ", 5) ||
760 !strncmp (s, "FTNOTE", 6) ||
761 !strncmp (s, "MODEL ", 5) ||
762 !strncmp (s, "ENDMDL", 6) ||
763 !strncmp (s, "SPRSDE", 6) ||
764 !strncmp (s, "MODRES", 6) ||
766 !strncmp (s, "GENERATED BY", 12) ||
767 !strncmp (s, "TER ", 4) ||
768 !strncmp (s, "END ", 4) ||
769 !strncmp (s, "TER\n", 4) ||
770 !strncmp (s, "END\n", 4) ||
771 !strncmp (s, "\n", 1))
774 else if (!strncmp (s, "ATOM ", 7))
777 const char *end = strchr (s, '\n');
779 char *name = (char *) calloc (1, 4);
780 GLfloat x = -999, y = -999, z = -999;
782 if (1 != sscanf (s+7, " %d ", &id))
783 parse_error (filename, line, s);
785 /* Use the "atom name" field if that is all that is available. */
786 strncpy (name, s+12, 3);
788 /* But prefer the "element" field. */
789 if (L > 77 && !isspace(s[77])) {
790 /* fprintf(stderr, " \"%s\" -> ", name); */
794 /* fprintf(stderr, "\"%s\"\n", name); */
797 while (isspace(*name)) name++;
798 ss = name + strlen(name)-1;
799 while (isspace(*ss) && ss > name)
807 if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
808 parse_error (filename, line, s);
811 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
812 progname, filename, line,
815 push_atom (m, id, name, x, y, z);
817 else if (!strncmp (s, "HETATM ", 7))
820 char *name = (char *) calloc (1, 4);
821 GLfloat x = -999, y = -999, z = -999;
823 if (1 != sscanf (s+7, " %d ", &id))
824 parse_error (filename, line, s);
826 strncpy (name, s+12, 3);
827 while (isspace(*name)) name++;
828 ss = name + strlen(name)-1;
829 while (isspace(*ss) && ss > name)
831 if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
832 parse_error (filename, line, s);
834 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
835 progname, filename, line,
838 push_atom (m, id, name, x, y, z);
840 else if (!strncmp (s, "CONECT ", 7))
843 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
844 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
845 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
846 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
848 for (j = 1; j < i; j++)
852 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
853 progname, filename, line, atoms[0], atoms[j]);
855 push_bond (m, atoms[0], atoms[j]);
860 char *s1 = strdup (s);
861 for (ss = s1; *ss && *ss != '\n'; ss++)
864 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
865 progname, filename, line, s1);
868 while (*s && *s != '\n')
879 parse_pdb_file (molecule *m, const char *name)
882 int buf_size = 40960;
886 in = fopen(name, "r");
889 char *buf = (char *) malloc(1024 + strlen(name));
890 sprintf(buf, "%s: error reading \"%s\"", progname, name);
895 buf = (char *) malloc (buf_size);
897 while (fgets (buf, buf_size-1, in))
900 for (s = buf; *s; s++)
901 if (*s == '\r') *s = '\n';
902 parse_pdb_data (m, buf, name, line++);
910 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
915 if (!m->nbonds && do_bonds)
917 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
924 #endif /* LOAD_FILES */
927 typedef struct { char *atom; int count; } atom_and_count;
929 /* When listing the components of a molecule, the convention is to put the
930 carbon atoms first, the hydrogen atoms second, and the other atom types
931 sorted alphabetically after that (although for some molecules, the usual
932 order is different: we special-case a few of those.)
935 cmp_atoms (const void *aa, const void *bb)
937 const atom_and_count *a = (atom_and_count *) aa;
938 const atom_and_count *b = (atom_and_count *) bb;
939 if (!a->atom) return 1;
940 if (!b->atom) return -1;
941 if (!strcmp(a->atom, "C")) return -1;
942 if (!strcmp(b->atom, "C")) return 1;
943 if (!strcmp(a->atom, "H")) return -1;
944 if (!strcmp(b->atom, "H")) return 1;
945 return strcmp (a->atom, b->atom);
948 static void special_case_formula (char *f);
951 generate_molecule_formula (molecule *m)
953 char *buf = (char *) malloc (m->natoms * 10);
956 atom_and_count counts[200];
957 memset (counts, 0, sizeof(counts));
959 for (i = 0; i < m->natoms; i++)
962 char *a = (char *) m->atoms[i].label;
964 while (!isalpha(*a)) a++;
966 for (e = a; isalpha(*e); e++);
968 while (counts[j].atom && !!strcmp(a, counts[j].atom))
978 while (counts[i].atom) i++;
979 qsort (counts, i, sizeof(*counts), cmp_atoms);
982 while (counts[i].atom)
984 strcat (s, counts[i].atom);
985 free (counts[i].atom);
987 if (counts[i].count > 1)
988 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
993 special_case_formula (buf);
995 if (!m->label) m->label = strdup("");
996 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
997 strcpy (s, m->label);
1000 free ((char *) m->label);
1005 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1007 special_case_formula (char *f)
1009 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
1010 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
1011 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
1012 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1013 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1014 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
1015 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
1016 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
1021 insert_vertical_whitespace (char *string)
1025 if ((string[0] == ',' ||
1027 string[0] == ':') &&
1029 string[0] = ' ', string[1] = '\n';
1035 /* Construct the molecule data from either: the builtins; or from
1036 the (one) .pdb file specified with -molecule.
1039 load_molecules (ModeInfo *mi)
1041 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1046 if (molecule_str && *molecule_str &&
1047 strcmp(molecule_str, "(default)")) /* try external PDB files */
1049 /* The -molecule option can point to a .pdb file, or to
1050 a directory of them.
1052 int wire = MI_IS_WIREFRAME(mi);
1059 if (!stat (molecule_str, &st) &&
1060 S_ISDIR (st.st_mode))
1064 struct dirent *dentry;
1066 pdb_dir = opendir (molecule_str);
1069 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1075 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1079 files = (char **) calloc (sizeof(*files), list_size);
1081 while ((dentry = readdir (pdb_dir)))
1083 int L = strlen (dentry->d_name);
1084 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1087 if (nfiles >= list_size-1)
1089 list_size = (list_size + 10) * 1.2;
1091 realloc (files, list_size * sizeof(*files));
1095 fprintf (stderr, "%s: out of memory (%d files)\n",
1101 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1103 strcpy (fn, molecule_str);
1104 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1105 strcat (fn, dentry->d_name);
1106 files[nfiles++] = fn;
1108 fprintf (stderr, "%s: file %s\n", progname, fn);
1114 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1115 progname, molecule_str);
1119 files = (char **) malloc (sizeof (*files));
1121 files[0] = strdup (molecule_str);
1123 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1126 mc->nmolecules = nfiles;
1127 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1129 for (i = 0; i < mc->nmolecules; i++)
1132 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1133 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1135 if ((wire || !do_atoms) &&
1137 mc->molecules[molecule_ctr].nbonds == 0)
1139 /* If we're not drawing atoms (e.g., wireframe mode), and
1140 there is no bond info, then make sure labels are turned
1141 on, or we'll be looking at a black screen... */
1142 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1143 progname, files[i]);
1154 mc->nmolecules = molecule_ctr;
1156 # endif /* LOAD_FILES */
1158 if (mc->nmolecules == 0) /* do the builtins if no files */
1160 mc->nmolecules = countof(builtin_pdb_data);
1161 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1162 for (i = 0; i < mc->nmolecules; i++)
1165 sprintf (name, "<builtin-%d>", i);
1166 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1167 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1171 for (i = 0; i < mc->nmolecules; i++)
1173 generate_molecule_formula (&mc->molecules[i]);
1174 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1180 /* Window management, etc
1183 reshape_molecule (ModeInfo *mi, int width, int height)
1185 GLfloat h = (GLfloat) height / (GLfloat) width;
1187 glViewport (0, 0, (GLint) width, (GLint) height);
1189 glMatrixMode(GL_PROJECTION);
1191 gluPerspective (30.0, 1/h, 20.0, 100.0);
1193 glMatrixMode(GL_MODELVIEW);
1195 gluLookAt( 0.0, 0.0, 30.0,
1199 glClear(GL_COLOR_BUFFER_BIT);
1204 gl_init (ModeInfo *mi)
1206 static const GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1207 static const GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1208 static const GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1209 static const GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1210 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1211 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1212 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1213 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1218 startup_blurb (ModeInfo *mi)
1220 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1221 const char *s = "Constructing molecules...";
1222 print_texture_label (mi->dpy, mc->title_font,
1223 mi->xgwa.width, mi->xgwa.height,
1226 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1230 molecule_handle_event (ModeInfo *mi, XEvent *event)
1232 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1234 if (gltrackball_event_handler (event, mc->trackball,
1235 MI_WIDTH (mi), MI_HEIGHT (mi),
1236 &mc->button_down_p))
1240 if (event->xany.type == KeyPress)
1244 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1245 if (c == '<' || c == ',' || c == '-' || c == '_' ||
1246 keysym == XK_Left || keysym == XK_Up || keysym == XK_Prior)
1251 else if (c == '>' || c == '.' || c == '=' || c == '+' ||
1252 keysym == XK_Right || keysym == XK_Down ||
1260 if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
1274 init_molecule (ModeInfo *mi)
1276 molecule_configuration *mc;
1280 mcs = (molecule_configuration *)
1281 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1283 fprintf(stderr, "%s: out of memory\n", progname);
1288 mc = &mcs[MI_SCREEN(mi)];
1290 if ((mc->glx_context = init_GL(mi)) != NULL) {
1292 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1298 wire = MI_IS_WIREFRAME(mi);
1301 Bool spinx=False, spiny=False, spinz=False;
1302 double spin_speed = 0.5;
1303 double spin_accel = 0.3;
1304 double wander_speed = 0.01;
1309 if (*s == 'x' || *s == 'X') spinx = True;
1310 else if (*s == 'y' || *s == 'Y') spiny = True;
1311 else if (*s == 'z' || *s == 'Z') spinz = True;
1312 else if (*s == '0') ;
1316 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1323 mc->rot = make_rotator (spinx ? spin_speed : 0,
1324 spiny ? spin_speed : 0,
1325 spinz ? spin_speed : 0,
1327 do_wander ? wander_speed : 0,
1328 (spinx && spiny && spinz));
1329 mc->trackball = gltrackball_init (True);
1332 orig_do_labels = do_labels;
1333 orig_do_atoms = do_atoms;
1334 orig_do_bonds = do_bonds;
1335 orig_do_shells = do_shells;
1336 orig_wire = MI_IS_WIREFRAME(mi);
1338 mc->molecule_dlist = glGenLists(1);
1340 mc->shell_dlist = glGenLists(1);
1342 load_molecules (mi);
1343 mc->which = random() % mc->nmolecules;
1345 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1346 "NoLabelThreshold");
1347 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1348 "WireframeThreshold");
1356 /* Put the labels on the atoms.
1357 This can't be a part of the display list because of the games
1358 we play with the translation matrix.
1361 draw_labels (ModeInfo *mi)
1363 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1364 int wire = MI_IS_WIREFRAME(mi);
1365 molecule *m = &mc->molecules[mc->which];
1372 glDisable (GL_LIGHTING); /* don't light fonts */
1374 for (i = 0; i < m->natoms; i++)
1376 molecule_atom *a = &m->atoms[i];
1377 GLfloat size = atom_size (a);
1383 set_atom_color (mi, a, True, 1);
1385 /* First, we translate the origin to the center of the atom.
1387 Then we retrieve the prevailing modelview matrix (which
1388 includes any rotation, wandering, and user-trackball-rolling
1391 We set the top 3x3 cells of that matrix to be the identity
1392 matrix. This removes all rotation from the matrix, while
1393 leaving the translation alone. This has the effect of
1394 leaving the prevailing coordinate system perpendicular to
1395 the camera view: were we to draw a square face, it would
1396 be in the plane of the screen.
1398 Now we translate by `size' toward the viewer -- so that the
1399 origin is *just in front* of the ball.
1401 Then we draw the label text, allowing the depth buffer to
1402 do its work: that way, labels on atoms will be occluded
1403 properly when other atoms move in front of them.
1405 This technique (of neutralizing rotation relative to the
1406 observer, after both rotations and translations have been
1407 applied) is known as "billboarding".
1410 glTranslatef(a->x, a->y, a->z); /* get matrix */
1411 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1412 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1413 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1414 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1415 glLoadIdentity(); /* reset modelview */
1416 glMultMatrixf (&m[0][0]); /* replace with ours */
1418 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1420 glRotatef (current_device_rotation(), 0, 0, 1); /* right side up */
1424 int w = texture_string_width (mc->atom_font, a->label, &h);
1425 GLfloat s = 1.0 / h; /* Scale to unit */
1426 s *= mc->overall_scale; /* Scale to size of atom */
1427 s *= 0.8; /* Shrink a bit */
1429 glTranslatef (-w * 0.5, h * 0.3 - h, 0);
1430 print_texture_string (mc->atom_font, a->label);
1436 /* More efficient to always call glEnable() with correct values
1437 than to call glPushAttrib()/glPopAttrib(), since reading
1438 attributes from GL does a round-trip and stalls the pipeline.
1441 glEnable (GL_LIGHTING);
1446 pick_new_molecule (ModeInfo *mi, time_t last)
1448 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1450 if (mc->nmolecules == 1)
1452 if (last != 0) return;
1457 mc->which = random() % mc->nmolecules;
1459 else if (mc->next < 0)
1462 if (mc->which < 0) mc->which = mc->nmolecules-1;
1465 else if (mc->next > 0)
1468 if (mc->which >= mc->nmolecules) mc->which = 0;
1474 while (n == mc->which)
1475 n = random() % mc->nmolecules;
1481 char *name = strdup (mc->molecules[mc->which].label);
1482 char *s = strpbrk (name, "\r\n");
1484 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1488 mc->polygon_count = 0;
1490 glNewList (mc->molecule_dlist, GL_COMPILE);
1491 ensure_bounding_box_visible (mi);
1493 do_labels = orig_do_labels;
1494 do_atoms = orig_do_atoms;
1495 do_bonds = orig_do_bonds;
1496 do_shells = orig_do_shells;
1497 MI_IS_WIREFRAME(mi) = orig_wire;
1499 if (mc->molecule_size > mc->no_label_threshold)
1501 if (mc->molecule_size > mc->wireframe_threshold)
1502 MI_IS_WIREFRAME(mi) = 1;
1504 if (MI_IS_WIREFRAME(mi))
1505 do_bonds = 1, do_shells = 0;
1510 if (! (do_bonds || do_atoms || do_labels))
1512 /* Make sure *something* shows up! */
1513 MI_IS_WIREFRAME(mi) = 1;
1517 build_molecule (mi, False);
1522 glNewList (mc->shell_dlist, GL_COMPILE);
1523 ensure_bounding_box_visible (mi);
1529 build_molecule (mi, True);
1532 do_bonds = orig_do_bonds;
1533 do_atoms = orig_do_atoms;
1534 do_labels = orig_do_labels;
1540 draw_molecule (ModeInfo *mi)
1542 time_t now = time ((time_t *) 0);
1543 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1545 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1546 Display *dpy = MI_DISPLAY(mi);
1547 Window window = MI_WINDOW(mi);
1549 if (!mc->glx_context)
1552 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1554 if (mc->draw_time == 0)
1556 pick_new_molecule (mi, mc->draw_time);
1557 mc->draw_time = now;
1559 else if (mc->mode == 0)
1561 if (mc->draw_tick++ > 10)
1563 time_t now = time((time_t *) 0);
1564 if (mc->draw_time == 0) mc->draw_time = now;
1567 if (!mc->button_down_p &&
1568 mc->nmolecules > 1 &&
1569 mc->draw_time + timeout <= now)
1571 /* randomize molecules every -timeout seconds */
1572 mc->mode = 1; /* go out */
1573 mc->mode_tick = 80 / speed;
1574 mc->draw_time = now;
1578 else if (mc->mode == 1) /* out */
1580 if (--mc->mode_tick <= 0)
1582 mc->mode_tick = 80 / speed;
1583 mc->mode = 2; /* go in */
1584 pick_new_molecule (mi, mc->draw_time);
1587 else if (mc->mode == 2) /* in */
1589 if (--mc->mode_tick <= 0)
1590 mc->mode = 0; /* normal */
1596 glScalef(1.1, 1.1, 1.1);
1600 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1601 glTranslatef((x - 0.5) * 9,
1605 gltrackball_rotate (mc->trackball);
1607 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1608 glRotatef (x * 360, 1.0, 0.0, 0.0);
1609 glRotatef (y * 360, 0.0, 1.0, 0.0);
1610 glRotatef (z * 360, 0.0, 0.0, 1.0);
1613 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1617 GLfloat s = (mc->mode == 1
1618 ? mc->mode_tick / (80 / speed)
1619 : ((80 / speed) - mc->mode_tick + 1) / (80 / speed));
1624 glCallList (mc->molecule_dlist);
1628 molecule *m = &mc->molecules[mc->which];
1632 /* This can't go in the display list, or the characters are spaced
1633 wrongly when the window is resized. */
1634 if (do_titles && m->label && *m->label)
1636 set_atom_color (mi, 0, True, 1);
1637 print_texture_label (mi->dpy, mc->title_font,
1638 mi->xgwa.width, mi->xgwa.height,
1646 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1648 glCallList (mc->shell_dlist);
1650 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1652 glDepthFunc (GL_EQUAL);
1653 glEnable (GL_BLEND);
1654 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1656 glCallList (mc->shell_dlist);
1658 glDepthFunc (GL_LESS);
1659 glDisable (GL_BLEND);
1664 mi->polygon_count = mc->polygon_count;
1666 if (mi->fps_p) do_fps (mi);
1669 glXSwapBuffers(dpy, window);
1672 XSCREENSAVER_MODULE ("Molecule", molecule)