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 https://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];
1371 for (i = 0; i < m->natoms; i++)
1373 molecule_atom *a = &m->atoms[i];
1374 GLfloat size = atom_size (a);
1380 set_atom_color (mi, a, True, 1);
1382 /* First, we translate the origin to the center of the atom.
1384 Then we retrieve the prevailing modelview matrix, which
1385 includes any rotation, wandering, and user-trackball-rolling
1388 We set the top 3x3 cells of that matrix to be the identity
1389 matrix. This removes all rotation from the matrix, while
1390 leaving the translation alone. This has the effect of
1391 leaving the prevailing coordinate system perpendicular to
1392 the camera view: were we to draw a square face, it would
1393 be in the plane of the screen.
1395 Now we translate by `size' toward the viewer -- so that the
1396 origin is *just in front* of the ball.
1398 Then we draw the label text, allowing the depth buffer to
1399 do its work: that way, labels on atoms will be occluded
1400 properly when other atoms move in front of them.
1402 This technique (of neutralizing rotation relative to the
1403 observer, after both rotations and translations have been
1404 applied) is known as "billboarding".
1407 glTranslatef(a->x, a->y, a->z); /* get matrix */
1408 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1409 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1410 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1411 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1412 glLoadIdentity(); /* reset modelview */
1413 glMultMatrixf (&m[0][0]); /* replace with ours */
1415 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1417 glRotatef (current_device_rotation(), 0, 0, 1); /* right side up */
1422 texture_string_metrics (mc->atom_font, a->label, &e, 0, 0);
1424 h = e.ascent + e.descent;
1426 GLfloat s = 1.0 / h; /* Scale to unit */
1427 s *= mc->overall_scale; /* Scale to size of atom */
1428 s *= 0.8; /* Shrink a bit */
1430 glTranslatef (-w/2, -h/2, 0);
1431 print_texture_string (mc->atom_font, a->label);
1440 pick_new_molecule (ModeInfo *mi, time_t last)
1442 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1444 if (mc->nmolecules == 1)
1446 if (last != 0) return;
1451 mc->which = random() % mc->nmolecules;
1453 else if (mc->next < 0)
1456 if (mc->which < 0) mc->which = mc->nmolecules-1;
1459 else if (mc->next > 0)
1462 if (mc->which >= mc->nmolecules) mc->which = 0;
1468 while (n == mc->which)
1469 n = random() % mc->nmolecules;
1475 char *name = strdup (mc->molecules[mc->which].label);
1476 char *s = strpbrk (name, "\r\n");
1478 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1482 mc->polygon_count = 0;
1484 glNewList (mc->molecule_dlist, GL_COMPILE);
1485 ensure_bounding_box_visible (mi);
1487 do_labels = orig_do_labels;
1488 do_atoms = orig_do_atoms;
1489 do_bonds = orig_do_bonds;
1490 do_shells = orig_do_shells;
1491 MI_IS_WIREFRAME(mi) = orig_wire;
1493 if (mc->molecule_size > mc->no_label_threshold)
1495 if (mc->molecule_size > mc->wireframe_threshold)
1496 MI_IS_WIREFRAME(mi) = 1;
1498 if (MI_IS_WIREFRAME(mi))
1499 do_bonds = 1, do_shells = 0;
1504 if (! (do_bonds || do_atoms || do_labels))
1506 /* Make sure *something* shows up! */
1507 MI_IS_WIREFRAME(mi) = 1;
1511 build_molecule (mi, False);
1516 glNewList (mc->shell_dlist, GL_COMPILE);
1517 ensure_bounding_box_visible (mi);
1523 build_molecule (mi, True);
1526 do_bonds = orig_do_bonds;
1527 do_atoms = orig_do_atoms;
1528 do_labels = orig_do_labels;
1534 draw_molecule (ModeInfo *mi)
1536 time_t now = time ((time_t *) 0);
1537 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1539 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1540 Display *dpy = MI_DISPLAY(mi);
1541 Window window = MI_WINDOW(mi);
1543 if (!mc->glx_context)
1546 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1548 if (mc->draw_time == 0)
1550 pick_new_molecule (mi, mc->draw_time);
1551 mc->draw_time = now;
1553 else if (mc->mode == 0)
1555 if (mc->draw_tick++ > 10)
1557 time_t now = time((time_t *) 0);
1558 if (mc->draw_time == 0) mc->draw_time = now;
1561 if (!mc->button_down_p &&
1562 mc->nmolecules > 1 &&
1563 mc->draw_time + timeout <= now)
1565 /* randomize molecules every -timeout seconds */
1566 mc->mode = 1; /* go out */
1567 mc->mode_tick = 80 / speed;
1568 mc->draw_time = now;
1572 else if (mc->mode == 1) /* out */
1574 if (--mc->mode_tick <= 0)
1576 mc->mode_tick = 80 / speed;
1577 mc->mode = 2; /* go in */
1578 pick_new_molecule (mi, mc->draw_time);
1581 else if (mc->mode == 2) /* in */
1583 if (--mc->mode_tick <= 0)
1584 mc->mode = 0; /* normal */
1590 glScalef(1.1, 1.1, 1.1);
1594 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1595 glTranslatef((x - 0.5) * 9,
1599 gltrackball_rotate (mc->trackball);
1601 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1602 glRotatef (x * 360, 1.0, 0.0, 0.0);
1603 glRotatef (y * 360, 0.0, 1.0, 0.0);
1604 glRotatef (z * 360, 0.0, 0.0, 1.0);
1607 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1611 GLfloat s = (mc->mode == 1
1612 ? mc->mode_tick / (80 / speed)
1613 : ((80 / speed) - mc->mode_tick + 1) / (80 / speed));
1618 glCallList (mc->molecule_dlist);
1622 molecule *m = &mc->molecules[mc->which];
1626 /* This can't go in the display list, or the characters are spaced
1627 wrongly when the window is resized. */
1628 if (do_titles && m->label && *m->label)
1630 set_atom_color (mi, 0, True, 1);
1631 print_texture_label (mi->dpy, mc->title_font,
1632 mi->xgwa.width, mi->xgwa.height,
1640 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1642 glCallList (mc->shell_dlist);
1644 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1646 glDepthFunc (GL_EQUAL);
1647 glEnable (GL_BLEND);
1648 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1650 glCallList (mc->shell_dlist);
1652 glDepthFunc (GL_LESS);
1653 glDisable (GL_BLEND);
1658 mi->polygon_count = mc->polygon_count;
1660 if (mi->fps_p) do_fps (mi);
1663 glXSwapBuffers(dpy, window);
1666 XSCREENSAVER_MODULE ("Molecule", molecule)