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.rcsb.org/pdb/file_formats/pdb/pdbguide2.2/guide2.2_frame.html
19 Good source of PDB files:
20 http://www.sci.ouc.bc.ca/chem/molecule/molecule.html
21 http://www.umass.edu/microbio/rasmol/whereget.htm
22 http://www.wwpdb.org/docs.html
25 #define DEFAULTS "*delay: 10000 \n" \
26 "*showFPS: False \n" \
27 "*wireframe: False \n" \
28 "*atomFont: -*-helvetica-medium-r-normal-*-240-*\n" \
29 "*titleFont: -*-helvetica-medium-r-normal-*-180-*\n" \
30 "*noLabelThreshold: 30 \n" \
31 "*wireframeThreshold: 150 \n" \
33 # define refresh_molecule 0
34 # define release_molecule 0
36 #define countof(x) (sizeof((x))/sizeof((*x)))
38 #include "xlockmore.h"
44 #include "gltrackball.h"
46 #ifdef USE_GL /* whole file */
48 #include <sys/types.h>
53 #define DEF_TIMEOUT "20"
54 #define DEF_SPIN "XYZ"
55 #define DEF_WANDER "False"
56 #define DEF_LABELS "True"
57 #define DEF_TITLES "True"
58 #define DEF_ATOMS "True"
59 #define DEF_BONDS "True"
60 #define DEF_ESHELLS "True"
61 #define DEF_BBOX "False"
62 #define DEF_SHELL_ALPHA "0.3"
63 #define DEF_MOLECULE "(default)"
64 #define DEF_VERBOSE "False"
66 #define SPHERE_SLICES 24 /* how densely to render spheres */
67 #define SPHERE_STACKS 12
69 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
72 # define TUBE_FACES 12 /* how densely to render tubes */
77 #define SPHERE_SLICES_2 7
78 #define SPHERE_STACKS_2 4
79 #define TUBE_FACES_2 3
83 __extension__ /* don't warn about "string length is greater than the length
84 ISO C89 compilers are required to support" when includng
85 the following data file... */
87 const char * const builtin_pdb_data[] = {
88 # include "molecules.h"
96 const char *text_color;
101 /* These are the traditional colors used to render these atoms,
102 and their approximate size in angstroms.
104 static const atom_data all_atom_data[] = {
105 { "H", 1.17, 0.40, "#FFFFFF", "#B3B3B3", { 0, }},
106 { "C", 1.75, 0.58, "#999999", "#FFFFFF", { 0, }},
107 { "CA", 1.80, 0.60, "#0000FF", "#ADD8E6", { 0, }},
108 { "N", 1.55, 0.52, "#A2B5CD", "#836FFF", { 0, }},
109 { "O", 1.40, 0.47, "#FF0000", "#FFB6C1", { 0, }},
110 { "P", 1.28, 0.43, "#9370DB", "#DB7093", { 0, }},
111 { "S", 1.80, 0.60, "#8B8B00", "#FFFF00", { 0, }},
112 { "bond", 0, 0, "#B3B3B3", "#FFFF00", { 0, }},
113 { "*", 1.40, 0.47, "#008B00", "#90EE90", { 0, }}
118 int id; /* sequence number in the PDB file */
119 const char *label; /* The atom name */
120 GLfloat x, y, z; /* position in 3-space (angstroms) */
121 const atom_data *data; /* computed: which style of atom this is */
125 int from, to; /* atom sequence numbers */
126 int strength; /* how many bonds are between these two atoms */
131 const char *label; /* description of this compound */
132 int natoms, atoms_size;
133 int nbonds, bonds_size;
134 molecule_atom *atoms;
135 molecule_bond *bonds;
140 GLXContext *glx_context;
142 trackball_state *trackball;
145 GLfloat molecule_size; /* max dimension of molecule bounding box */
147 GLfloat no_label_threshold; /* Things happen when molecules are huge */
148 GLfloat wireframe_threshold;
150 int which; /* which of the molecules is being shown */
154 int mode; /* 0 = normal, 1 = out, 2 = in */
157 GLuint molecule_dlist;
160 XFontStruct *xfont1, *xfont2;
161 GLuint font1_dlist, font2_dlist;
169 } molecule_configuration;
172 static molecule_configuration *mcs = NULL;
175 static char *molecule_str;
176 static char *do_spin;
177 static Bool do_wander;
178 static Bool do_titles;
179 static Bool do_labels;
180 static Bool do_atoms;
181 static Bool do_bonds;
182 static Bool do_shells;
184 static Bool verbose_p;
185 static GLfloat shell_alpha;
188 static Bool orig_do_labels, orig_do_atoms, orig_do_bonds, orig_do_shells,
192 static XrmOptionDescRec opts[] = {
193 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
194 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
195 { "-spin", ".spin", XrmoptionSepArg, 0 },
196 { "+spin", ".spin", XrmoptionNoArg, "" },
197 { "-wander", ".wander", XrmoptionNoArg, "True" },
198 { "+wander", ".wander", XrmoptionNoArg, "False" },
199 { "-labels", ".labels", XrmoptionNoArg, "True" },
200 { "+labels", ".labels", XrmoptionNoArg, "False" },
201 { "-titles", ".titles", XrmoptionNoArg, "True" },
202 { "+titles", ".titles", XrmoptionNoArg, "False" },
203 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
204 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
205 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
206 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
207 { "-shells", ".eshells", XrmoptionNoArg, "True" },
208 { "+shells", ".eshells", XrmoptionNoArg, "False" },
209 { "-shell-alpha", ".shellAlpha", XrmoptionSepArg, 0 },
210 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
211 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
212 { "-verbose", ".verbose", XrmoptionNoArg, "True" },
215 static argtype vars[] = {
216 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE, t_String},
217 {&timeout, "timeout", "Seconds", DEF_TIMEOUT, t_Int},
218 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
219 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
220 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
221 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
222 {&do_shells, "eshells", "EShells", DEF_ESHELLS, t_Bool},
223 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
224 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
225 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
226 {&shell_alpha, "shellAlpha", "ShellAlpha", DEF_SHELL_ALPHA, t_Float},
227 {&verbose_p, "verbose", "Verbose", DEF_VERBOSE, t_Bool},
230 ENTRYPOINT ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
238 sphere (molecule_configuration *mc,
239 GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
241 int stacks = (mc->scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
242 int slices = (mc->scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
245 glTranslatef (x, y, z);
246 glScalef (diameter, diameter, diameter);
247 unit_sphere (stacks, slices, wire);
250 return stacks * slices;
255 load_fonts (ModeInfo *mi)
257 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
258 load_font (mi->dpy, "atomFont", &mc->xfont1, &mc->font1_dlist);
259 load_font (mi->dpy, "titleFont", &mc->xfont2, &mc->font2_dlist);
263 static const atom_data *
264 get_atom_data (const char *atom_name)
267 const atom_data *d = 0;
268 char *n = strdup (atom_name);
272 while (!isalpha(*n)) n++;
274 while (L > 0 && !isalpha(n[L-1]))
277 for (i = 0; i < countof(all_atom_data); i++)
279 d = &all_atom_data[i];
280 if (!strcasecmp (n, all_atom_data[i].name))
290 set_atom_color (ModeInfo *mi, const molecule_atom *a,
291 Bool font_p, GLfloat alpha)
299 d = get_atom_data ("bond");
303 gl_color[0] = d->gl_color[4];
304 gl_color[1] = d->gl_color[5];
305 gl_color[2] = d->gl_color[6];
306 gl_color[3] = d->gl_color[7];
310 gl_color[0] = d->gl_color[0];
311 gl_color[1] = d->gl_color[1];
312 gl_color[2] = d->gl_color[2];
313 gl_color[3] = d->gl_color[3];
316 if (gl_color[3] == 0)
318 const char *string = !font_p ? d->color : d->text_color;
320 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
322 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
323 (a ? a->label : d->name), string);
327 gl_color[0] = xcolor.red / 65536.0;
328 gl_color[1] = xcolor.green / 65536.0;
329 gl_color[2] = xcolor.blue / 65536.0;
335 glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
337 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
342 atom_size (const molecule_atom *a)
345 return a->data->size2;
347 return a->data->size;
351 static molecule_atom *
352 get_atom (molecule_atom *atoms, int natoms, int id)
356 /* quick short-circuit */
359 if (atoms[id].id == id)
361 if (id > 0 && atoms[id-1].id == id)
363 if (id < natoms-1 && atoms[id+1].id == id)
367 for (i = 0; i < natoms; i++)
368 if (id == atoms[i].id)
371 fprintf (stderr, "%s: no atom %d\n", progname, id);
377 molecule_bounding_box (ModeInfo *mi,
378 GLfloat *x1, GLfloat *y1, GLfloat *z1,
379 GLfloat *x2, GLfloat *y2, GLfloat *z2)
381 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
382 molecule *m = &mc->molecules[mc->which];
387 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
391 *x1 = *x2 = m->atoms[0].x;
392 *y1 = *y2 = m->atoms[0].y;
393 *z1 = *z2 = m->atoms[0].z;
396 for (i = 1; i < m->natoms; i++)
398 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
399 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
400 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
402 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
403 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
404 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
417 draw_bounding_box (ModeInfo *mi)
419 static const GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
420 static const GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
421 int wire = MI_IS_WIREFRAME(mi);
422 GLfloat x1, y1, z1, x2, y2, z2;
423 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
425 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
428 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
430 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
431 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
433 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
434 glNormal3f(0, -1, 0);
435 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
436 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
438 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
440 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
441 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
443 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
444 glNormal3f(0, 0, -1);
445 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
446 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
448 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
450 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
451 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
453 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
454 glNormal3f(-1, 0, 0);
455 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
456 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
459 glPushAttrib (GL_LIGHTING);
460 glDisable (GL_LIGHTING);
462 glColor3f (c2[0], c2[1], c2[2]);
464 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
465 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
466 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
467 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
468 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
469 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
476 /* Since PDB files don't always have the molecule centered around the
477 origin, and since some molecules are pretty large, scale and/or
478 translate so that the whole molecule is visible in the window.
481 ensure_bounding_box_visible (ModeInfo *mi)
483 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
485 GLfloat x1, y1, z1, x2, y2, z2;
488 GLfloat max_size = 10; /* don't bother scaling down if the molecule
489 is already smaller than this */
491 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
496 size = (w > h ? w : h);
497 size = (size > d ? size : d);
499 mc->molecule_size = size;
505 GLfloat scale = max_size / size;
506 glScalef (scale, scale, scale);
508 mc->scale_down = scale < 0.3;
511 glTranslatef (-(x1 + w/2),
518 /* Constructs the GL shapes of the current molecule
521 build_molecule (ModeInfo *mi, Bool transparent_p)
523 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
524 int wire = MI_IS_WIREFRAME(mi);
526 GLfloat alpha = transparent_p ? shell_alpha : 1.0;
529 molecule *m = &mc->molecules[mc->which];
533 glDisable(GL_CULL_FACE);
534 glDisable(GL_LIGHTING);
535 glDisable(GL_LIGHT0);
536 glDisable(GL_DEPTH_TEST);
537 glDisable(GL_NORMALIZE);
538 glDisable(GL_CULL_FACE);
542 glEnable(GL_CULL_FACE);
543 glEnable(GL_LIGHTING);
545 glEnable(GL_DEPTH_TEST);
546 glEnable(GL_NORMALIZE);
547 glEnable(GL_CULL_FACE);
551 set_atom_color (mi, 0, False, alpha);
554 for (i = 0; i < m->nbonds; i++)
556 const molecule_bond *b = &m->bonds[i];
557 const molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
558 const molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
563 glVertex3f(from->x, from->y, from->z);
564 glVertex3f(to->x, to->y, to->z);
570 int faces = (mc->scale_down ? TUBE_FACES_2 : TUBE_FACES);
576 GLfloat thickness = 0.07 * b->strength;
577 GLfloat cap_size = 0.03;
581 polys += tube (from->x, from->y, from->z,
584 faces, smooth, (!do_atoms || do_shells), wire);
588 if (!wire && do_atoms)
589 for (i = 0; i < m->natoms; i++)
591 const molecule_atom *a = &m->atoms[i];
592 GLfloat size = atom_size (a);
593 set_atom_color (mi, a, False, alpha);
594 polys += sphere (mc, a->x, a->y, a->z, size, wire);
597 if (do_bbox && !transparent_p)
599 draw_bounding_box (mi);
603 mc->polygon_count += polys;
611 push_atom (molecule *m,
612 int id, const char *label,
613 GLfloat x, GLfloat y, GLfloat z)
616 if (m->atoms_size < m->natoms)
619 m->atoms = (molecule_atom *) realloc (m->atoms,
620 m->atoms_size * sizeof(*m->atoms));
622 m->atoms[m->natoms-1].id = id;
623 m->atoms[m->natoms-1].label = label;
624 m->atoms[m->natoms-1].x = x;
625 m->atoms[m->natoms-1].y = y;
626 m->atoms[m->natoms-1].z = z;
627 m->atoms[m->natoms-1].data = get_atom_data (label);
632 push_bond (molecule *m, int from, int to)
636 for (i = 0; i < m->nbonds; i++)
637 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
638 (m->bonds[i].to == from && m->bonds[i].from == to))
640 m->bonds[i].strength++;
645 if (m->bonds_size < m->nbonds)
648 m->bonds = (molecule_bond *) realloc (m->bonds,
649 m->bonds_size * sizeof(*m->bonds));
651 m->bonds[m->nbonds-1].from = from;
652 m->bonds[m->nbonds-1].to = to;
653 m->bonds[m->nbonds-1].strength = 1;
658 parse_error (const char *file, int lineno, const char *line)
660 fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
661 progname, file, lineno, line);
666 /* This function is crap.
669 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
671 const char *s = data;
675 if ((!m->label || !*m->label) &&
676 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
678 char *name = calloc (1, 100);
685 while (isspace(*n2)) n2++;
687 ss = strchr (n2, '\n');
689 ss = strchr (n2, '\r');
692 ss = n2+strlen(n2)-1;
693 while (isspace(*ss) && ss > n2)
696 if (strlen (n2) > 4 &&
697 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
698 n2[strlen(n2)-4] = 0;
700 if (m->label) free ((char *) m->label);
701 m->label = strdup (n2);
704 else if (!strncmp (s, "TITLE ", 6) ||
705 !strncmp (s, "HEADER", 6) ||
706 !strncmp (s, "COMPND", 6) ||
707 !strncmp (s, "AUTHOR", 6) ||
708 !strncmp (s, "REVDAT", 6) ||
709 !strncmp (s, "SOURCE", 6) ||
710 !strncmp (s, "EXPDTA", 6) ||
711 !strncmp (s, "JRNL ", 6) ||
712 !strncmp (s, "REMARK", 6) ||
713 !strncmp (s, "SEQRES", 6) ||
714 !strncmp (s, "HET ", 6) ||
715 !strncmp (s, "FORMUL", 6) ||
716 !strncmp (s, "CRYST1", 6) ||
717 !strncmp (s, "ORIGX1", 6) ||
718 !strncmp (s, "ORIGX2", 6) ||
719 !strncmp (s, "ORIGX3", 6) ||
720 !strncmp (s, "SCALE1", 6) ||
721 !strncmp (s, "SCALE2", 6) ||
722 !strncmp (s, "SCALE3", 6) ||
723 !strncmp (s, "MASTER", 6) ||
724 !strncmp (s, "KEYWDS", 6) ||
725 !strncmp (s, "DBREF ", 6) ||
726 !strncmp (s, "HETNAM", 6) ||
727 !strncmp (s, "HETSYN", 6) ||
728 !strncmp (s, "HELIX ", 6) ||
729 !strncmp (s, "LINK ", 6) ||
730 !strncmp (s, "MTRIX1", 6) ||
731 !strncmp (s, "MTRIX2", 6) ||
732 !strncmp (s, "MTRIX3", 6) ||
733 !strncmp (s, "SHEET ", 6) ||
734 !strncmp (s, "CISPEP", 6) ||
736 !strncmp (s, "SEQADV", 6) ||
737 !strncmp (s, "SITE ", 5) ||
738 !strncmp (s, "FTNOTE", 6) ||
739 !strncmp (s, "MODEL ", 5) ||
740 !strncmp (s, "ENDMDL", 6) ||
741 !strncmp (s, "SPRSDE", 6) ||
742 !strncmp (s, "MODRES", 6) ||
744 !strncmp (s, "GENERATED BY", 12) ||
745 !strncmp (s, "TER ", 4) ||
746 !strncmp (s, "END ", 4) ||
747 !strncmp (s, "TER\n", 4) ||
748 !strncmp (s, "END\n", 4) ||
749 !strncmp (s, "\n", 1))
752 else if (!strncmp (s, "ATOM ", 7))
755 char *name = (char *) calloc (1, 4);
756 GLfloat x = -999, y = -999, z = -999;
758 if (1 != sscanf (s+7, " %d ", &id))
759 parse_error (filename, line, s);
761 strncpy (name, s+12, 3);
762 while (isspace(*name)) name++;
763 ss = name + strlen(name)-1;
764 while (isspace(*ss) && ss > name)
772 if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
773 parse_error (filename, line, s);
776 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
777 progname, filename, line,
780 push_atom (m, id, name, x, y, z);
782 else if (!strncmp (s, "HETATM ", 7))
785 char *name = (char *) calloc (1, 4);
786 GLfloat x = -999, y = -999, z = -999;
788 if (1 != sscanf (s+7, " %d ", &id))
789 parse_error (filename, line, s);
791 strncpy (name, s+12, 3);
792 while (isspace(*name)) name++;
793 ss = name + strlen(name)-1;
794 while (isspace(*ss) && ss > name)
796 if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
797 parse_error (filename, line, s);
799 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
800 progname, filename, line,
803 push_atom (m, id, name, x, y, z);
805 else if (!strncmp (s, "CONECT ", 7))
808 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
809 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
810 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
811 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
813 for (j = 1; j < i; j++)
817 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
818 progname, filename, line, atoms[0], atoms[j]);
820 push_bond (m, atoms[0], atoms[j]);
825 char *s1 = strdup (s);
826 for (ss = s1; *ss && *ss != '\n'; ss++)
829 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
830 progname, filename, line, s1);
833 while (*s && *s != '\n')
843 parse_pdb_file (molecule *m, const char *name)
846 int buf_size = 40960;
850 in = fopen(name, "r");
853 char *buf = (char *) malloc(1024 + strlen(name));
854 sprintf(buf, "%s: error reading \"%s\"", progname, name);
859 buf = (char *) malloc (buf_size);
861 while (fgets (buf, buf_size-1, in))
864 for (s = buf; *s; s++)
865 if (*s == '\r') *s = '\n';
866 parse_pdb_data (m, buf, name, line++);
874 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
879 if (!m->nbonds && do_bonds)
881 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
890 typedef struct { char *atom; int count; } atom_and_count;
892 /* When listing the components of a molecule, the convention is to put the
893 carbon atoms first, the hydrogen atoms second, and the other atom types
894 sorted alphabetically after that (although for some molecules, the usual
895 order is different: we special-case a few of those.)
898 cmp_atoms (const void *aa, const void *bb)
900 const atom_and_count *a = (atom_and_count *) aa;
901 const atom_and_count *b = (atom_and_count *) bb;
902 if (!a->atom) return 1;
903 if (!b->atom) return -1;
904 if (!strcmp(a->atom, "C")) return -1;
905 if (!strcmp(b->atom, "C")) return 1;
906 if (!strcmp(a->atom, "H")) return -1;
907 if (!strcmp(b->atom, "H")) return 1;
908 return strcmp (a->atom, b->atom);
911 static void special_case_formula (char *f);
914 generate_molecule_formula (molecule *m)
916 char *buf = (char *) malloc (m->natoms * 10);
919 atom_and_count counts[200];
920 memset (counts, 0, sizeof(counts));
922 for (i = 0; i < m->natoms; i++)
925 char *a = (char *) m->atoms[i].label;
927 while (!isalpha(*a)) a++;
929 for (e = a; isalpha(*e); e++);
931 while (counts[j].atom && !!strcmp(a, counts[j].atom))
941 while (counts[i].atom) i++;
942 qsort (counts, i, sizeof(*counts), cmp_atoms);
945 while (counts[i].atom)
947 strcat (s, counts[i].atom);
948 free (counts[i].atom);
950 if (counts[i].count > 1)
951 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
956 special_case_formula (buf);
958 if (!m->label) m->label = strdup("");
959 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
960 strcpy (s, m->label);
963 free ((char *) m->label);
968 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
970 special_case_formula (char *f)
972 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
973 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
974 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
975 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
976 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
977 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
978 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
979 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
984 insert_vertical_whitespace (char *string)
988 if ((string[0] == ',' ||
992 string[0] = ' ', string[1] = '\n';
998 /* Construct the molecule data from either: the builtins; or from
999 the (one) .pdb file specified with -molecule.
1002 load_molecules (ModeInfo *mi)
1004 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1005 int wire = MI_IS_WIREFRAME(mi);
1009 if (molecule_str && *molecule_str &&
1010 strcmp(molecule_str, "(default)")) /* try external PDB files */
1012 /* The -molecule option can point to a .pdb file, or to
1013 a directory of them.
1021 if (!stat (molecule_str, &st) &&
1022 S_ISDIR (st.st_mode))
1026 struct dirent *dentry;
1028 pdb_dir = opendir (molecule_str);
1031 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1037 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1041 files = (char **) calloc (sizeof(*files), list_size);
1043 while ((dentry = readdir (pdb_dir)))
1045 int L = strlen (dentry->d_name);
1046 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1049 if (nfiles >= list_size-1)
1051 list_size = (list_size + 10) * 1.2;
1053 realloc (files, list_size * sizeof(*files));
1057 fprintf (stderr, "%s: out of memory (%d files)\n",
1063 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1065 strcpy (fn, molecule_str);
1066 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1067 strcat (fn, dentry->d_name);
1068 files[nfiles++] = fn;
1070 fprintf (stderr, "%s: file %s\n", progname, fn);
1076 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1077 progname, molecule_str);
1081 files = (char **) malloc (sizeof (*files));
1083 files[0] = strdup (molecule_str);
1085 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1088 mc->nmolecules = nfiles;
1089 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1091 for (i = 0; i < mc->nmolecules; i++)
1094 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1095 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1097 if ((wire || !do_atoms) &&
1099 mc->molecules[molecule_ctr].nbonds == 0)
1101 /* If we're not drawing atoms (e.g., wireframe mode), and
1102 there is no bond info, then make sure labels are turned
1103 on, or we'll be looking at a black screen... */
1104 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1105 progname, files[i]);
1116 mc->nmolecules = molecule_ctr;
1119 if (mc->nmolecules == 0) /* do the builtins if no files */
1121 mc->nmolecules = countof(builtin_pdb_data);
1122 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1123 for (i = 0; i < mc->nmolecules; i++)
1126 sprintf (name, "<builtin-%d>", i);
1127 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1128 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1132 for (i = 0; i < mc->nmolecules; i++)
1134 generate_molecule_formula (&mc->molecules[i]);
1135 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1141 /* Window management, etc
1144 reshape_molecule (ModeInfo *mi, int width, int height)
1146 GLfloat h = (GLfloat) height / (GLfloat) width;
1148 glViewport (0, 0, (GLint) width, (GLint) height);
1150 glMatrixMode(GL_PROJECTION);
1152 gluPerspective (30.0, 1/h, 20.0, 100.0);
1154 glMatrixMode(GL_MODELVIEW);
1156 gluLookAt( 0.0, 0.0, 30.0,
1160 glClear(GL_COLOR_BUFFER_BIT);
1165 gl_init (ModeInfo *mi)
1167 static const GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1168 static const GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1169 static const GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1170 static const GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1171 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1172 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1173 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1174 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1179 startup_blurb (ModeInfo *mi)
1181 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1182 const char *s = "Constructing molecules...";
1183 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1184 mi->xgwa.width, mi->xgwa.height,
1185 10, mi->xgwa.height - 10,
1188 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1192 molecule_handle_event (ModeInfo *mi, XEvent *event)
1194 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1196 if (event->xany.type == ButtonPress &&
1197 event->xbutton.button == Button1)
1199 mc->button_down_p = True;
1200 gltrackball_start (mc->trackball,
1201 event->xbutton.x, event->xbutton.y,
1202 MI_WIDTH (mi), MI_HEIGHT (mi));
1205 else if (event->xany.type == ButtonRelease &&
1206 event->xbutton.button == Button1)
1208 mc->button_down_p = False;
1211 else if (event->xany.type == ButtonPress &&
1212 (event->xbutton.button == Button4 ||
1213 event->xbutton.button == Button5 ||
1214 event->xbutton.button == Button6 ||
1215 event->xbutton.button == Button7))
1217 gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1218 !!event->xbutton.state);
1221 else if (event->xany.type == KeyPress)
1225 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1227 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1229 GLfloat speed = 4.0;
1231 mc->mode_tick = 10 * speed;
1235 else if (event->xany.type == MotionNotify &&
1238 gltrackball_track (mc->trackball,
1239 event->xmotion.x, event->xmotion.y,
1240 MI_WIDTH (mi), MI_HEIGHT (mi));
1249 init_molecule (ModeInfo *mi)
1251 molecule_configuration *mc;
1255 mcs = (molecule_configuration *)
1256 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1258 fprintf(stderr, "%s: out of memory\n", progname);
1263 mc = &mcs[MI_SCREEN(mi)];
1265 if ((mc->glx_context = init_GL(mi)) != NULL) {
1267 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1273 wire = MI_IS_WIREFRAME(mi);
1276 Bool spinx=False, spiny=False, spinz=False;
1277 double spin_speed = 0.5;
1278 double spin_accel = 0.3;
1279 double wander_speed = 0.01;
1284 if (*s == 'x' || *s == 'X') spinx = True;
1285 else if (*s == 'y' || *s == 'Y') spiny = True;
1286 else if (*s == 'z' || *s == 'Z') spinz = True;
1287 else if (*s == '0') ;
1291 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1298 mc->rot = make_rotator (spinx ? spin_speed : 0,
1299 spiny ? spin_speed : 0,
1300 spinz ? spin_speed : 0,
1302 do_wander ? wander_speed : 0,
1303 (spinx && spiny && spinz));
1304 mc->trackball = gltrackball_init ();
1307 orig_do_labels = do_labels;
1308 orig_do_atoms = do_atoms;
1309 orig_do_bonds = do_bonds;
1310 orig_do_shells = do_shells;
1311 orig_wire = MI_IS_WIREFRAME(mi);
1313 mc->molecule_dlist = glGenLists(1);
1315 mc->shell_dlist = glGenLists(1);
1317 load_molecules (mi);
1318 mc->which = random() % mc->nmolecules;
1320 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1321 "NoLabelThreshold");
1322 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1323 "WireframeThreshold");
1331 /* Put the labels on the atoms.
1332 This can't be a part of the display list because of the games
1333 we play with the translation matrix.
1336 draw_labels (ModeInfo *mi)
1338 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1339 int wire = MI_IS_WIREFRAME(mi);
1340 molecule *m = &mc->molecules[mc->which];
1347 glDisable (GL_LIGHTING); /* don't light fonts */
1349 for (i = 0; i < m->natoms; i++)
1351 molecule_atom *a = &m->atoms[i];
1352 GLfloat size = atom_size (a);
1358 set_atom_color (mi, a, True, 1);
1360 /* First, we translate the origin to the center of the atom.
1362 Then we retrieve the prevailing modelview matrix (which
1363 includes any rotation, wandering, and user-trackball-rolling
1366 We set the top 3x3 cells of that matrix to be the identity
1367 matrix. This removes all rotation from the matrix, while
1368 leaving the translation alone. This has the effect of
1369 leaving the prevailing coordinate system perpendicular to
1370 the camera view: were we to draw a square face, it would
1371 be in the plane of the screen.
1373 Now we translate by `size' toward the viewer -- so that the
1374 origin is *just in front* of the ball.
1376 Then we draw the label text, allowing the depth buffer to
1377 do its work: that way, labels on atoms will be occluded
1378 properly when other atoms move in front of them.
1380 This technique (of neutralizing rotation relative to the
1381 observer, after both rotations and translations have been
1382 applied) is known as "billboarding".
1385 glTranslatef(a->x, a->y, a->z); /* get matrix */
1386 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1387 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1388 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1389 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1390 glLoadIdentity(); /* reset modelview */
1391 glMultMatrixf (&m[0][0]); /* replace with ours */
1393 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1395 glRasterPos3f (0, 0, 0); /* draw text here */
1397 /* Before drawing the string, shift the origin to center
1398 the text over the origin of the sphere. */
1399 glBitmap (0, 0, 0, 0,
1400 -string_width (mc->xfont1, a->label, 0) / 2,
1401 -mc->xfont1->descent,
1404 for (j = 0; j < strlen(a->label); j++)
1406 glCallList (mc->font1_dlist + (int)(a->label[j]));
1411 /* More efficient to always call glEnable() with correct values
1412 than to call glPushAttrib()/glPopAttrib(), since reading
1413 attributes from GL does a round-trip and stalls the pipeline.
1416 glEnable (GL_LIGHTING);
1421 pick_new_molecule (ModeInfo *mi, time_t last)
1423 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1425 if (mc->nmolecules == 1)
1427 if (last != 0) return;
1432 mc->which = random() % mc->nmolecules;
1437 while (n == mc->which)
1438 n = random() % mc->nmolecules;
1444 char *name = strdup (mc->molecules[mc->which].label);
1445 char *s = strpbrk (name, "\r\n");
1447 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1451 mc->polygon_count = 0;
1453 glNewList (mc->molecule_dlist, GL_COMPILE);
1454 ensure_bounding_box_visible (mi);
1456 do_labels = orig_do_labels;
1457 do_atoms = orig_do_atoms;
1458 do_bonds = orig_do_bonds;
1459 do_shells = orig_do_shells;
1460 MI_IS_WIREFRAME(mi) = orig_wire;
1462 if (mc->molecule_size > mc->no_label_threshold)
1464 if (mc->molecule_size > mc->wireframe_threshold)
1465 MI_IS_WIREFRAME(mi) = 1;
1467 if (MI_IS_WIREFRAME(mi))
1468 do_bonds = 1, do_shells = 0;
1473 if (! (do_bonds || do_atoms || do_labels))
1475 /* Make sure *something* shows up! */
1476 MI_IS_WIREFRAME(mi) = 1;
1480 build_molecule (mi, False);
1485 glNewList (mc->shell_dlist, GL_COMPILE);
1486 ensure_bounding_box_visible (mi);
1492 build_molecule (mi, True);
1495 do_bonds = orig_do_bonds;
1496 do_atoms = orig_do_atoms;
1497 do_labels = orig_do_labels;
1503 draw_molecule (ModeInfo *mi)
1505 time_t now = time ((time_t *) 0);
1506 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1508 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1509 Display *dpy = MI_DISPLAY(mi);
1510 Window window = MI_WINDOW(mi);
1512 if (!mc->glx_context)
1515 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1517 if (mc->draw_time == 0)
1519 pick_new_molecule (mi, mc->draw_time);
1520 mc->draw_time = now;
1522 else if (mc->mode == 0)
1524 if (mc->draw_tick++ > 10)
1526 time_t now = time((time_t *) 0);
1527 if (mc->draw_time == 0) mc->draw_time = now;
1530 if (!mc->button_down_p &&
1531 mc->nmolecules > 1 &&
1532 mc->draw_time + timeout <= now)
1534 /* randomize molecules every -timeout seconds */
1535 mc->mode = 1; /* go out */
1536 mc->mode_tick = 10 * speed;
1537 mc->draw_time = now;
1541 else if (mc->mode == 1) /* out */
1543 if (--mc->mode_tick <= 0)
1545 mc->mode_tick = 10 * speed;
1546 mc->mode = 2; /* go in */
1547 pick_new_molecule (mi, mc->draw_time);
1548 mc->draw_time = now;
1551 else if (mc->mode == 2) /* in */
1553 if (--mc->mode_tick <= 0)
1554 mc->mode = 0; /* normal */
1560 glScalef(1.1, 1.1, 1.1);
1564 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1565 glTranslatef((x - 0.5) * 9,
1569 gltrackball_rotate (mc->trackball);
1571 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1572 glRotatef (x * 360, 1.0, 0.0, 0.0);
1573 glRotatef (y * 360, 0.0, 1.0, 0.0);
1574 glRotatef (z * 360, 0.0, 0.0, 1.0);
1577 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1581 GLfloat s = (mc->mode == 1
1582 ? mc->mode_tick / (10 * speed)
1583 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1588 glCallList (mc->molecule_dlist);
1592 molecule *m = &mc->molecules[mc->which];
1596 /* This can't go in the display list, or the characters are spaced
1597 wrongly when the window is resized. */
1598 if (do_titles && m->label && *m->label)
1600 set_atom_color (mi, 0, True, 1);
1601 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1602 mi->xgwa.width, mi->xgwa.height,
1603 10, mi->xgwa.height - 10,
1611 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1613 glCallList (mc->shell_dlist);
1615 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1617 glDepthFunc (GL_EQUAL);
1618 glEnable (GL_BLEND);
1619 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1621 glCallList (mc->shell_dlist);
1623 glDepthFunc (GL_LESS);
1624 glDisable (GL_BLEND);
1629 mi->polygon_count = mc->polygon_count;
1631 if (mi->fps_p) do_fps (mi);
1634 glXSwapBuffers(dpy, window);
1637 XSCREENSAVER_MODULE ("Molecule", molecule)