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 ATOM_FONT "-*-helvetica-medium-r-normal-*-240-*"
29 #define DEFAULTS "*delay: 10000 \n" \
30 "*showFPS: False \n" \
31 "*wireframe: False \n" \
32 "*atomFont: " ATOM_FONT "\n" \
33 "*atomFont2: -*-helvetica-bold-r-normal-*-80-*\n" \
34 "*titleFont: -*-helvetica-medium-r-normal-*-180-*\n" \
35 "*noLabelThreshold: 30 \n" \
36 "*wireframeThreshold: 150 \n" \
38 # define refresh_molecule 0
39 # define release_molecule 0
41 #define countof(x) (sizeof((x))/sizeof((*x)))
43 #include "xlockmore.h"
49 #include "gltrackball.h"
51 #ifdef USE_GL /* whole file */
53 #include <sys/types.h>
58 #define DEF_TIMEOUT "20"
59 #define DEF_SPIN "XYZ"
60 #define DEF_WANDER "False"
61 #define DEF_LABELS "True"
62 #define DEF_TITLES "True"
63 #define DEF_ATOMS "True"
64 #define DEF_BONDS "True"
65 #define DEF_ESHELLS "True"
66 #define DEF_BBOX "False"
67 #define DEF_SHELL_ALPHA "0.3"
68 #define DEF_MOLECULE "(default)"
69 #define DEF_VERBOSE "False"
71 #define SPHERE_SLICES 48 /* how densely to render spheres */
72 #define SPHERE_STACKS 24
74 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
77 # define TUBE_FACES 12 /* how densely to render tubes */
82 #define SPHERE_SLICES_2 14
83 #define SPHERE_STACKS_2 8
84 #define TUBE_FACES_2 6
88 __extension__ /* don't warn about "string length is greater than the length
89 ISO C89 compilers are required to support" when includng
90 the following data file... */
92 static const char * const builtin_pdb_data[] = {
93 # include "molecules.h"
106 const char *text_color;
111 /* These are the traditional colors used to render these atoms,
112 and their approximate size in angstroms.
114 static const atom_data all_atom_data[] = {
115 { "H", 1.17, 0.40, "#FFFFFF", "#000000", { 0, }},
116 { "C", 1.75, 0.58, "#999999", "#FFFFFF", { 0, }},
117 { "CA", 1.80, 0.60, "#0000FF", "#ADD8E6", { 0, }},
118 { "N", 1.55, 0.52, "#A2B5CD", "#EE99FF", { 0, }},
119 { "O", 1.40, 0.47, "#FF0000", "#FFB6C1", { 0, }},
120 { "P", 1.28, 0.43, "#9370DB", "#DB7093", { 0, }},
121 { "S", 1.80, 0.60, "#8B8B00", "#FFFF00", { 0, }},
122 { "bond", 0, 0, "#B3B3B3", "#FFFF00", { 0, }},
123 { "*", 1.40, 0.47, "#008B00", "#90EE90", { 0, }}
128 int id; /* sequence number in the PDB file */
129 const char *label; /* The atom name */
130 GLfloat x, y, z; /* position in 3-space (angstroms) */
131 const atom_data *data; /* computed: which style of atom this is */
135 int from, to; /* atom sequence numbers */
136 int strength; /* how many bonds are between these two atoms */
141 const char *label; /* description of this compound */
142 int natoms, atoms_size;
143 int nbonds, bonds_size;
144 molecule_atom *atoms;
145 molecule_bond *bonds;
150 GLXContext *glx_context;
152 trackball_state *trackball;
155 GLfloat molecule_size; /* max dimension of molecule bounding box */
157 GLfloat no_label_threshold; /* Things happen when molecules are huge */
158 GLfloat wireframe_threshold;
160 int which; /* which of the molecules is being shown */
164 int mode; /* 0 = normal, 1 = out, 2 = in */
167 GLuint molecule_dlist;
170 texture_font_data *font1_data, *font2_data, *font3_data;
179 } molecule_configuration;
182 static molecule_configuration *mcs = NULL;
185 static char *molecule_str;
186 static char *do_spin;
187 static Bool do_wander;
188 static Bool do_titles;
189 static Bool do_labels;
190 static Bool do_atoms;
191 static Bool do_bonds;
192 static Bool do_shells;
194 static Bool verbose_p;
195 static GLfloat shell_alpha;
198 static Bool orig_do_labels, orig_do_atoms, orig_do_bonds, orig_do_shells,
202 static XrmOptionDescRec opts[] = {
203 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
204 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
205 { "-spin", ".spin", XrmoptionSepArg, 0 },
206 { "+spin", ".spin", XrmoptionNoArg, "" },
207 { "-wander", ".wander", XrmoptionNoArg, "True" },
208 { "+wander", ".wander", XrmoptionNoArg, "False" },
209 { "-labels", ".labels", XrmoptionNoArg, "True" },
210 { "+labels", ".labels", XrmoptionNoArg, "False" },
211 { "-titles", ".titles", XrmoptionNoArg, "True" },
212 { "+titles", ".titles", XrmoptionNoArg, "False" },
213 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
214 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
215 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
216 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
217 { "-shells", ".eshells", XrmoptionNoArg, "True" },
218 { "+shells", ".eshells", XrmoptionNoArg, "False" },
219 { "-shell-alpha", ".shellAlpha", XrmoptionSepArg, 0 },
220 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
221 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
222 { "-verbose", ".verbose", XrmoptionNoArg, "True" },
225 static argtype vars[] = {
226 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE, t_String},
227 {&timeout, "timeout", "Seconds", DEF_TIMEOUT, t_Int},
228 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
229 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
230 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
231 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
232 {&do_shells, "eshells", "EShells", DEF_ESHELLS, t_Bool},
233 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
234 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
235 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
236 {&shell_alpha, "shellAlpha", "ShellAlpha", DEF_SHELL_ALPHA, t_Float},
237 {&verbose_p, "verbose", "Verbose", DEF_VERBOSE, t_Bool},
240 ENTRYPOINT ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
248 sphere (molecule_configuration *mc,
249 GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
251 int stacks = (mc->scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
252 int slices = (mc->scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
255 glTranslatef (x, y, z);
256 glScalef (diameter, diameter, diameter);
257 unit_sphere (stacks, slices, wire);
260 return stacks * slices;
265 load_fonts (ModeInfo *mi)
267 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
268 mc->font1_data = load_texture_font (mi->dpy, "atomFont");
269 mc->font2_data = load_texture_font (mi->dpy, "atomFont2");
270 mc->font3_data = load_texture_font (mi->dpy, "titleFont");
274 static const atom_data *
275 get_atom_data (const char *atom_name)
278 const atom_data *d = 0;
279 char *n = strdup (atom_name);
283 while (!isalpha(*n)) n++;
285 while (L > 0 && !isalpha(n[L-1]))
288 for (i = 0; i < countof(all_atom_data); i++)
290 d = &all_atom_data[i];
291 if (!strcasecmp (n, all_atom_data[i].name))
301 set_atom_color (ModeInfo *mi, const molecule_atom *a,
302 Bool font_p, GLfloat alpha)
310 d = get_atom_data ("bond");
314 gl_color[0] = d->gl_color[4];
315 gl_color[1] = d->gl_color[5];
316 gl_color[2] = d->gl_color[6];
317 gl_color[3] = d->gl_color[7];
321 gl_color[0] = d->gl_color[0];
322 gl_color[1] = d->gl_color[1];
323 gl_color[2] = d->gl_color[2];
324 gl_color[3] = d->gl_color[3];
327 if (gl_color[3] == 0)
329 const char *string = !font_p ? d->color : d->text_color;
331 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
333 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
334 (a ? a->label : d->name), string);
338 gl_color[0] = xcolor.red / 65536.0;
339 gl_color[1] = xcolor.green / 65536.0;
340 gl_color[2] = xcolor.blue / 65536.0;
345 /* If we're not drawing atoms, and the color is black, use white instead.
346 This is a kludge so that H can have black text over its white ball,
347 but the text still shows up if balls are off.
349 if (font_p && !do_atoms &&
350 gl_color[0] == 0 && gl_color[1] == 0 && gl_color[2] == 0)
352 gl_color[0] = gl_color[1] = gl_color[2] = 1;
356 glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
358 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
363 atom_size (const molecule_atom *a)
366 return a->data->size2;
368 return a->data->size;
372 static molecule_atom *
373 get_atom (molecule_atom *atoms, int natoms, int id)
377 /* quick short-circuit */
380 if (atoms[id].id == id)
382 if (id > 0 && atoms[id-1].id == id)
384 if (id < natoms-1 && atoms[id+1].id == id)
388 for (i = 0; i < natoms; i++)
389 if (id == atoms[i].id)
392 fprintf (stderr, "%s: no atom %d\n", progname, id);
398 molecule_bounding_box (ModeInfo *mi,
399 GLfloat *x1, GLfloat *y1, GLfloat *z1,
400 GLfloat *x2, GLfloat *y2, GLfloat *z2)
402 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
403 molecule *m = &mc->molecules[mc->which];
408 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
412 *x1 = *x2 = m->atoms[0].x;
413 *y1 = *y2 = m->atoms[0].y;
414 *z1 = *z2 = m->atoms[0].z;
417 for (i = 1; i < m->natoms; i++)
419 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
420 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
421 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
423 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
424 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
425 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
438 draw_bounding_box (ModeInfo *mi)
440 static const GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
441 static const GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
442 int wire = MI_IS_WIREFRAME(mi);
443 GLfloat x1, y1, z1, x2, y2, z2;
444 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
446 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
449 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
451 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
452 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
454 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
455 glNormal3f(0, -1, 0);
456 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
457 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
459 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
461 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
462 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
464 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
465 glNormal3f(0, 0, -1);
466 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
467 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
469 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
471 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
472 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
474 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
475 glNormal3f(-1, 0, 0);
476 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
477 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
480 glDisable (GL_LIGHTING);
482 glColor3f (c2[0], c2[1], c2[2]);
484 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
485 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
486 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
487 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
488 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
489 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
493 glEnable (GL_LIGHTING);
497 /* Since PDB files don't always have the molecule centered around the
498 origin, and since some molecules are pretty large, scale and/or
499 translate so that the whole molecule is visible in the window.
502 ensure_bounding_box_visible (ModeInfo *mi)
504 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
506 GLfloat x1, y1, z1, x2, y2, z2;
509 GLfloat max_size = 10; /* don't bother scaling down if the molecule
510 is already smaller than this */
512 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
517 size = (w > h ? w : h);
518 size = (size > d ? size : d);
520 mc->molecule_size = size;
526 GLfloat scale = max_size / size;
527 glScalef (scale, scale, scale);
529 mc->scale_down = scale < 0.3;
532 glTranslatef (-(x1 + w/2),
539 /* Constructs the GL shapes of the current molecule
542 build_molecule (ModeInfo *mi, Bool transparent_p)
544 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
545 int wire = MI_IS_WIREFRAME(mi);
547 GLfloat alpha = transparent_p ? shell_alpha : 1.0;
550 molecule *m = &mc->molecules[mc->which];
554 glDisable(GL_CULL_FACE);
555 glDisable(GL_LIGHTING);
556 glDisable(GL_LIGHT0);
557 glDisable(GL_DEPTH_TEST);
558 glDisable(GL_NORMALIZE);
559 glDisable(GL_CULL_FACE);
563 glEnable(GL_CULL_FACE);
564 glEnable(GL_LIGHTING);
566 glEnable(GL_DEPTH_TEST);
567 glEnable(GL_NORMALIZE);
568 glEnable(GL_CULL_FACE);
572 set_atom_color (mi, 0, False, alpha);
575 for (i = 0; i < m->nbonds; i++)
577 const molecule_bond *b = &m->bonds[i];
578 const molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
579 const molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
584 glVertex3f(from->x, from->y, from->z);
585 glVertex3f(to->x, to->y, to->z);
591 int faces = (mc->scale_down ? TUBE_FACES_2 : TUBE_FACES);
597 GLfloat thickness = 0.07 * b->strength;
598 GLfloat cap_size = 0.03;
602 polys += tube (from->x, from->y, from->z,
605 faces, smooth, (!do_atoms || do_shells), wire);
609 if (!wire && do_atoms)
610 for (i = 0; i < m->natoms; i++)
612 const molecule_atom *a = &m->atoms[i];
613 GLfloat size = atom_size (a);
614 set_atom_color (mi, a, False, alpha);
615 polys += sphere (mc, a->x, a->y, a->z, size, wire);
618 if (do_bbox && !transparent_p)
620 draw_bounding_box (mi);
624 mc->polygon_count += polys;
632 push_atom (molecule *m,
633 int id, const char *label,
634 GLfloat x, GLfloat y, GLfloat z)
637 if (m->atoms_size < m->natoms)
640 m->atoms = (molecule_atom *) realloc (m->atoms,
641 m->atoms_size * sizeof(*m->atoms));
643 m->atoms[m->natoms-1].id = id;
644 m->atoms[m->natoms-1].label = label;
645 m->atoms[m->natoms-1].x = x;
646 m->atoms[m->natoms-1].y = y;
647 m->atoms[m->natoms-1].z = z;
648 m->atoms[m->natoms-1].data = get_atom_data (label);
653 push_bond (molecule *m, int from, int to)
657 for (i = 0; i < m->nbonds; i++)
658 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
659 (m->bonds[i].to == from && m->bonds[i].from == to))
661 m->bonds[i].strength++;
666 if (m->bonds_size < m->nbonds)
669 m->bonds = (molecule_bond *) realloc (m->bonds,
670 m->bonds_size * sizeof(*m->bonds));
672 m->bonds[m->nbonds-1].from = from;
673 m->bonds[m->nbonds-1].to = to;
674 m->bonds[m->nbonds-1].strength = 1;
679 parse_error (const char *file, int lineno, const char *line)
681 fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
682 progname, file, lineno, line);
687 /* This function is crap.
690 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
692 const char *s = data;
696 if ((!m->label || !*m->label) &&
697 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
699 char *name = calloc (1, 100);
706 while (isspace(*n2)) n2++;
708 ss = strchr (n2, '\n');
710 ss = strchr (n2, '\r');
713 ss = n2+strlen(n2)-1;
714 while (isspace(*ss) && ss > n2)
717 if (strlen (n2) > 4 &&
718 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
719 n2[strlen(n2)-4] = 0;
721 if (m->label) free ((char *) m->label);
722 m->label = strdup (n2);
725 else if (!strncmp (s, "TITLE ", 6) ||
726 !strncmp (s, "HEADER", 6) ||
727 !strncmp (s, "COMPND", 6) ||
728 !strncmp (s, "AUTHOR", 6) ||
729 !strncmp (s, "REVDAT", 6) ||
730 !strncmp (s, "SOURCE", 6) ||
731 !strncmp (s, "EXPDTA", 6) ||
732 !strncmp (s, "JRNL ", 6) ||
733 !strncmp (s, "REMARK", 6) ||
734 !strncmp (s, "SEQRES", 6) ||
735 !strncmp (s, "HET ", 6) ||
736 !strncmp (s, "FORMUL", 6) ||
737 !strncmp (s, "CRYST1", 6) ||
738 !strncmp (s, "ORIGX1", 6) ||
739 !strncmp (s, "ORIGX2", 6) ||
740 !strncmp (s, "ORIGX3", 6) ||
741 !strncmp (s, "SCALE1", 6) ||
742 !strncmp (s, "SCALE2", 6) ||
743 !strncmp (s, "SCALE3", 6) ||
744 !strncmp (s, "MASTER", 6) ||
745 !strncmp (s, "KEYWDS", 6) ||
746 !strncmp (s, "DBREF ", 6) ||
747 !strncmp (s, "HETNAM", 6) ||
748 !strncmp (s, "HETSYN", 6) ||
749 !strncmp (s, "HELIX ", 6) ||
750 !strncmp (s, "LINK ", 6) ||
751 !strncmp (s, "MTRIX1", 6) ||
752 !strncmp (s, "MTRIX2", 6) ||
753 !strncmp (s, "MTRIX3", 6) ||
754 !strncmp (s, "SHEET ", 6) ||
755 !strncmp (s, "CISPEP", 6) ||
757 !strncmp (s, "SEQADV", 6) ||
758 !strncmp (s, "SITE ", 5) ||
759 !strncmp (s, "FTNOTE", 6) ||
760 !strncmp (s, "MODEL ", 5) ||
761 !strncmp (s, "ENDMDL", 6) ||
762 !strncmp (s, "SPRSDE", 6) ||
763 !strncmp (s, "MODRES", 6) ||
765 !strncmp (s, "GENERATED BY", 12) ||
766 !strncmp (s, "TER ", 4) ||
767 !strncmp (s, "END ", 4) ||
768 !strncmp (s, "TER\n", 4) ||
769 !strncmp (s, "END\n", 4) ||
770 !strncmp (s, "\n", 1))
773 else if (!strncmp (s, "ATOM ", 7))
776 const char *end = strchr (s, '\n');
778 char *name = (char *) calloc (1, 4);
779 GLfloat x = -999, y = -999, z = -999;
781 if (1 != sscanf (s+7, " %d ", &id))
782 parse_error (filename, line, s);
784 /* Use the "atom name" field if that is all that is available. */
785 strncpy (name, s+12, 3);
787 /* But prefer the "element" field. */
788 if (L > 77 && !isspace(s[77])) {
789 /* fprintf(stderr, " \"%s\" -> ", name); */
793 /* fprintf(stderr, "\"%s\"\n", name); */
796 while (isspace(*name)) name++;
797 ss = name + strlen(name)-1;
798 while (isspace(*ss) && ss > name)
806 if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
807 parse_error (filename, line, s);
810 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
811 progname, filename, line,
814 push_atom (m, id, name, x, y, z);
816 else if (!strncmp (s, "HETATM ", 7))
819 char *name = (char *) calloc (1, 4);
820 GLfloat x = -999, y = -999, z = -999;
822 if (1 != sscanf (s+7, " %d ", &id))
823 parse_error (filename, line, s);
825 strncpy (name, s+12, 3);
826 while (isspace(*name)) name++;
827 ss = name + strlen(name)-1;
828 while (isspace(*ss) && ss > name)
830 if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
831 parse_error (filename, line, s);
833 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
834 progname, filename, line,
837 push_atom (m, id, name, x, y, z);
839 else if (!strncmp (s, "CONECT ", 7))
842 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
843 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
844 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
845 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
847 for (j = 1; j < i; j++)
851 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
852 progname, filename, line, atoms[0], atoms[j]);
854 push_bond (m, atoms[0], atoms[j]);
859 char *s1 = strdup (s);
860 for (ss = s1; *ss && *ss != '\n'; ss++)
863 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
864 progname, filename, line, s1);
867 while (*s && *s != '\n')
878 parse_pdb_file (molecule *m, const char *name)
881 int buf_size = 40960;
885 in = fopen(name, "r");
888 char *buf = (char *) malloc(1024 + strlen(name));
889 sprintf(buf, "%s: error reading \"%s\"", progname, name);
894 buf = (char *) malloc (buf_size);
896 while (fgets (buf, buf_size-1, in))
899 for (s = buf; *s; s++)
900 if (*s == '\r') *s = '\n';
901 parse_pdb_data (m, buf, name, line++);
909 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
914 if (!m->nbonds && do_bonds)
916 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
923 #endif /* LOAD_FILES */
926 typedef struct { char *atom; int count; } atom_and_count;
928 /* When listing the components of a molecule, the convention is to put the
929 carbon atoms first, the hydrogen atoms second, and the other atom types
930 sorted alphabetically after that (although for some molecules, the usual
931 order is different: we special-case a few of those.)
934 cmp_atoms (const void *aa, const void *bb)
936 const atom_and_count *a = (atom_and_count *) aa;
937 const atom_and_count *b = (atom_and_count *) bb;
938 if (!a->atom) return 1;
939 if (!b->atom) return -1;
940 if (!strcmp(a->atom, "C")) return -1;
941 if (!strcmp(b->atom, "C")) return 1;
942 if (!strcmp(a->atom, "H")) return -1;
943 if (!strcmp(b->atom, "H")) return 1;
944 return strcmp (a->atom, b->atom);
947 static void special_case_formula (char *f);
950 generate_molecule_formula (molecule *m)
952 char *buf = (char *) malloc (m->natoms * 10);
955 atom_and_count counts[200];
956 memset (counts, 0, sizeof(counts));
958 for (i = 0; i < m->natoms; i++)
961 char *a = (char *) m->atoms[i].label;
963 while (!isalpha(*a)) a++;
965 for (e = a; isalpha(*e); e++);
967 while (counts[j].atom && !!strcmp(a, counts[j].atom))
977 while (counts[i].atom) i++;
978 qsort (counts, i, sizeof(*counts), cmp_atoms);
981 while (counts[i].atom)
983 strcat (s, counts[i].atom);
984 free (counts[i].atom);
986 if (counts[i].count > 1)
987 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
992 special_case_formula (buf);
994 if (!m->label) m->label = strdup("");
995 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
996 strcpy (s, m->label);
999 free ((char *) m->label);
1004 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1006 special_case_formula (char *f)
1008 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
1009 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
1010 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
1011 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1012 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1013 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
1014 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
1015 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
1020 insert_vertical_whitespace (char *string)
1024 if ((string[0] == ',' ||
1026 string[0] == ':') &&
1028 string[0] = ' ', string[1] = '\n';
1034 /* Construct the molecule data from either: the builtins; or from
1035 the (one) .pdb file specified with -molecule.
1038 load_molecules (ModeInfo *mi)
1040 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1045 if (molecule_str && *molecule_str &&
1046 strcmp(molecule_str, "(default)")) /* try external PDB files */
1048 /* The -molecule option can point to a .pdb file, or to
1049 a directory of them.
1051 int wire = MI_IS_WIREFRAME(mi);
1058 if (!stat (molecule_str, &st) &&
1059 S_ISDIR (st.st_mode))
1063 struct dirent *dentry;
1065 pdb_dir = opendir (molecule_str);
1068 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1074 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1078 files = (char **) calloc (sizeof(*files), list_size);
1080 while ((dentry = readdir (pdb_dir)))
1082 int L = strlen (dentry->d_name);
1083 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1086 if (nfiles >= list_size-1)
1088 list_size = (list_size + 10) * 1.2;
1090 realloc (files, list_size * sizeof(*files));
1094 fprintf (stderr, "%s: out of memory (%d files)\n",
1100 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1102 strcpy (fn, molecule_str);
1103 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1104 strcat (fn, dentry->d_name);
1105 files[nfiles++] = fn;
1107 fprintf (stderr, "%s: file %s\n", progname, fn);
1113 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1114 progname, molecule_str);
1118 files = (char **) malloc (sizeof (*files));
1120 files[0] = strdup (molecule_str);
1122 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1125 mc->nmolecules = nfiles;
1126 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1128 for (i = 0; i < mc->nmolecules; i++)
1131 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1132 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1134 if ((wire || !do_atoms) &&
1136 mc->molecules[molecule_ctr].nbonds == 0)
1138 /* If we're not drawing atoms (e.g., wireframe mode), and
1139 there is no bond info, then make sure labels are turned
1140 on, or we'll be looking at a black screen... */
1141 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1142 progname, files[i]);
1153 mc->nmolecules = molecule_ctr;
1155 # endif /* LOAD_FILES */
1157 if (mc->nmolecules == 0) /* do the builtins if no files */
1159 mc->nmolecules = countof(builtin_pdb_data);
1160 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1161 for (i = 0; i < mc->nmolecules; i++)
1164 sprintf (name, "<builtin-%d>", i);
1165 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1166 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1170 for (i = 0; i < mc->nmolecules; i++)
1172 generate_molecule_formula (&mc->molecules[i]);
1173 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1179 /* Window management, etc
1182 reshape_molecule (ModeInfo *mi, int width, int height)
1184 GLfloat h = (GLfloat) height / (GLfloat) width;
1186 glViewport (0, 0, (GLint) width, (GLint) height);
1188 glMatrixMode(GL_PROJECTION);
1190 gluPerspective (30.0, 1/h, 20.0, 100.0);
1192 glMatrixMode(GL_MODELVIEW);
1194 gluLookAt( 0.0, 0.0, 30.0,
1198 glClear(GL_COLOR_BUFFER_BIT);
1203 gl_init (ModeInfo *mi)
1205 static const GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1206 static const GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1207 static const GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1208 static const GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1209 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1210 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1211 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1212 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1217 startup_blurb (ModeInfo *mi)
1219 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1220 const char *s = "Constructing molecules...";
1221 print_gl_string (mi->dpy, mc->font3_data,
1222 mi->xgwa.width, mi->xgwa.height,
1223 10, mi->xgwa.height - 10,
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))
1238 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
1240 GLfloat speed = 4.0;
1242 mc->mode_tick = 10 * speed;
1251 init_molecule (ModeInfo *mi)
1253 molecule_configuration *mc;
1257 mcs = (molecule_configuration *)
1258 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1260 fprintf(stderr, "%s: out of memory\n", progname);
1265 mc = &mcs[MI_SCREEN(mi)];
1267 if ((mc->glx_context = init_GL(mi)) != NULL) {
1269 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1275 wire = MI_IS_WIREFRAME(mi);
1278 Bool spinx=False, spiny=False, spinz=False;
1279 double spin_speed = 0.5;
1280 double spin_accel = 0.3;
1281 double wander_speed = 0.01;
1286 if (*s == 'x' || *s == 'X') spinx = True;
1287 else if (*s == 'y' || *s == 'Y') spiny = True;
1288 else if (*s == 'z' || *s == 'Z') spinz = True;
1289 else if (*s == '0') ;
1293 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1300 mc->rot = make_rotator (spinx ? spin_speed : 0,
1301 spiny ? spin_speed : 0,
1302 spinz ? spin_speed : 0,
1304 do_wander ? wander_speed : 0,
1305 (spinx && spiny && spinz));
1306 mc->trackball = gltrackball_init (True);
1309 orig_do_labels = do_labels;
1310 orig_do_atoms = do_atoms;
1311 orig_do_bonds = do_bonds;
1312 orig_do_shells = do_shells;
1313 orig_wire = MI_IS_WIREFRAME(mi);
1315 mc->molecule_dlist = glGenLists(1);
1317 mc->shell_dlist = glGenLists(1);
1319 load_molecules (mi);
1320 mc->which = random() % mc->nmolecules;
1322 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1323 "NoLabelThreshold");
1324 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1325 "WireframeThreshold");
1333 /* Put the labels on the atoms.
1334 This can't be a part of the display list because of the games
1335 we play with the translation matrix.
1338 draw_labels (ModeInfo *mi)
1340 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1341 int wire = MI_IS_WIREFRAME(mi);
1342 molecule *m = &mc->molecules[mc->which];
1349 glDisable (GL_LIGHTING); /* don't light fonts */
1351 for (i = 0; i < m->natoms; i++)
1353 molecule_atom *a = &m->atoms[i];
1354 GLfloat size = atom_size (a);
1360 set_atom_color (mi, a, True, 1);
1362 /* First, we translate the origin to the center of the atom.
1364 Then we retrieve the prevailing modelview matrix (which
1365 includes any rotation, wandering, and user-trackball-rolling
1368 We set the top 3x3 cells of that matrix to be the identity
1369 matrix. This removes all rotation from the matrix, while
1370 leaving the translation alone. This has the effect of
1371 leaving the prevailing coordinate system perpendicular to
1372 the camera view: were we to draw a square face, it would
1373 be in the plane of the screen.
1375 Now we translate by `size' toward the viewer -- so that the
1376 origin is *just in front* of the ball.
1378 Then we draw the label text, allowing the depth buffer to
1379 do its work: that way, labels on atoms will be occluded
1380 properly when other atoms move in front of them.
1382 This technique (of neutralizing rotation relative to the
1383 observer, after both rotations and translations have been
1384 applied) is known as "billboarding".
1387 glTranslatef(a->x, a->y, a->z); /* get matrix */
1388 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1389 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1390 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1391 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1392 glLoadIdentity(); /* reset modelview */
1393 glMultMatrixf (&m[0][0]); /* replace with ours */
1395 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1397 glRotatef (current_device_rotation(), 0, 0, 1); /* right side up */
1401 int w = texture_string_width (mc->font1_data, a->label, &h);
1402 GLfloat s = 1.0 / h;
1403 GLfloat max = 18; /* max point size to avoid pixellated text */
1404 if (h > max) s *= max/h;
1406 glTranslatef (-w/2, h*2/3, 0);
1407 print_gl_string (mi->dpy, mc->font1_data,
1415 /* More efficient to always call glEnable() with correct values
1416 than to call glPushAttrib()/glPopAttrib(), since reading
1417 attributes from GL does a round-trip and stalls the pipeline.
1420 glEnable (GL_LIGHTING);
1425 pick_new_molecule (ModeInfo *mi, time_t last)
1427 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1429 if (mc->nmolecules == 1)
1431 if (last != 0) return;
1436 mc->which = random() % mc->nmolecules;
1441 while (n == mc->which)
1442 n = random() % mc->nmolecules;
1448 char *name = strdup (mc->molecules[mc->which].label);
1449 char *s = strpbrk (name, "\r\n");
1451 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1455 mc->polygon_count = 0;
1457 glNewList (mc->molecule_dlist, GL_COMPILE);
1458 ensure_bounding_box_visible (mi);
1460 do_labels = orig_do_labels;
1461 do_atoms = orig_do_atoms;
1462 do_bonds = orig_do_bonds;
1463 do_shells = orig_do_shells;
1464 MI_IS_WIREFRAME(mi) = orig_wire;
1466 if (mc->molecule_size > mc->no_label_threshold)
1468 if (mc->molecule_size > mc->wireframe_threshold)
1469 MI_IS_WIREFRAME(mi) = 1;
1471 if (MI_IS_WIREFRAME(mi))
1472 do_bonds = 1, do_shells = 0;
1477 if (! (do_bonds || do_atoms || do_labels))
1479 /* Make sure *something* shows up! */
1480 MI_IS_WIREFRAME(mi) = 1;
1484 build_molecule (mi, False);
1489 glNewList (mc->shell_dlist, GL_COMPILE);
1490 ensure_bounding_box_visible (mi);
1496 build_molecule (mi, True);
1499 do_bonds = orig_do_bonds;
1500 do_atoms = orig_do_atoms;
1501 do_labels = orig_do_labels;
1507 draw_molecule (ModeInfo *mi)
1509 time_t now = time ((time_t *) 0);
1510 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1512 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1513 Display *dpy = MI_DISPLAY(mi);
1514 Window window = MI_WINDOW(mi);
1516 if (!mc->glx_context)
1519 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1521 if (mc->draw_time == 0)
1523 pick_new_molecule (mi, mc->draw_time);
1524 mc->draw_time = now;
1526 else if (mc->mode == 0)
1528 if (mc->draw_tick++ > 10)
1530 time_t now = time((time_t *) 0);
1531 if (mc->draw_time == 0) mc->draw_time = now;
1534 if (!mc->button_down_p &&
1535 mc->nmolecules > 1 &&
1536 mc->draw_time + timeout <= now)
1538 /* randomize molecules every -timeout seconds */
1539 mc->mode = 1; /* go out */
1540 mc->mode_tick = 10 * speed;
1541 mc->draw_time = now;
1545 else if (mc->mode == 1) /* out */
1547 if (--mc->mode_tick <= 0)
1549 mc->mode_tick = 10 * speed;
1550 mc->mode = 2; /* go in */
1551 pick_new_molecule (mi, mc->draw_time);
1552 mc->draw_time = now;
1555 else if (mc->mode == 2) /* in */
1557 if (--mc->mode_tick <= 0)
1558 mc->mode = 0; /* normal */
1564 glScalef(1.1, 1.1, 1.1);
1568 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1569 glTranslatef((x - 0.5) * 9,
1573 gltrackball_rotate (mc->trackball);
1575 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1576 glRotatef (x * 360, 1.0, 0.0, 0.0);
1577 glRotatef (y * 360, 0.0, 1.0, 0.0);
1578 glRotatef (z * 360, 0.0, 0.0, 1.0);
1581 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1585 GLfloat s = (mc->mode == 1
1586 ? mc->mode_tick / (10 * speed)
1587 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1592 glCallList (mc->molecule_dlist);
1596 molecule *m = &mc->molecules[mc->which];
1600 /* This can't go in the display list, or the characters are spaced
1601 wrongly when the window is resized. */
1602 if (do_titles && m->label && *m->label)
1604 set_atom_color (mi, 0, True, 1);
1605 print_gl_string (mi->dpy, mc->font3_data,
1606 mi->xgwa.width, mi->xgwa.height,
1607 10, mi->xgwa.height - 10,
1615 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1617 glCallList (mc->shell_dlist);
1619 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1621 glDepthFunc (GL_EQUAL);
1622 glEnable (GL_BLEND);
1623 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1625 glCallList (mc->shell_dlist);
1627 glDepthFunc (GL_LESS);
1628 glDisable (GL_BLEND);
1633 mi->polygon_count = mc->polygon_count;
1635 if (mi->fps_p) do_fps (mi);
1638 glXSwapBuffers(dpy, window);
1641 XSCREENSAVER_MODULE ("Molecule", molecule)