1 /* molecule, Copyright (c) 2001-2016 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" \
34 "*suppressRotationAnimation: True\n" \
36 # define refresh_molecule 0
37 # define release_molecule 0
39 #define countof(x) (sizeof((x))/sizeof((*x)))
41 #include "xlockmore.h"
47 #include "gltrackball.h"
49 #ifdef USE_GL /* whole file */
51 #include <sys/types.h>
56 #define DEF_TIMEOUT "20"
57 #define DEF_SPIN "XYZ"
58 #define DEF_WANDER "False"
59 #define DEF_LABELS "True"
60 #define DEF_TITLES "True"
61 #define DEF_ATOMS "True"
62 #define DEF_BONDS "True"
63 #define DEF_ESHELLS "True"
64 #define DEF_BBOX "False"
65 #define DEF_SHELL_ALPHA "0.3"
66 #define DEF_MOLECULE "(default)"
67 #define DEF_VERBOSE "False"
69 #define SPHERE_SLICES 48 /* how densely to render spheres */
70 #define SPHERE_STACKS 24
72 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
75 # define TUBE_FACES 12 /* how densely to render tubes */
80 #define SPHERE_SLICES_2 14
81 #define SPHERE_STACKS_2 8
82 #define TUBE_FACES_2 6
86 __extension__ /* don't warn about "string length is greater than the length
87 ISO C89 compilers are required to support" when includng
88 the following data file... */
90 static const char * const builtin_pdb_data[] = {
91 # include "molecules.h"
104 const char *text_color;
109 /* These are the traditional colors used to render these atoms,
110 and their approximate size in angstroms.
112 static const atom_data all_atom_data[] = {
113 { "H", 1.17, 0.40, "#FFFFFF", "#000000", { 0, }},
114 { "C", 1.75, 0.58, "#999999", "#FFFFFF", { 0, }},
115 { "CA", 1.80, 0.60, "#0000FF", "#ADD8E6", { 0, }},
116 { "N", 1.55, 0.52, "#A2B5CD", "#EE99FF", { 0, }},
117 { "O", 1.40, 0.47, "#FF0000", "#FFB6C1", { 0, }},
118 { "P", 1.28, 0.43, "#9370DB", "#DB7093", { 0, }},
119 { "S", 1.80, 0.60, "#8B8B00", "#FFFF00", { 0, }},
120 { "bond", 0, 0, "#B3B3B3", "#FFFF00", { 0, }},
121 { "*", 1.40, 0.47, "#008B00", "#90EE90", { 0, }}
126 int id; /* sequence number in the PDB file */
127 const char *label; /* The atom name */
128 GLfloat x, y, z; /* position in 3-space (angstroms) */
129 const atom_data *data; /* computed: which style of atom this is */
133 int from, to; /* atom sequence numbers */
134 int strength; /* how many bonds are between these two atoms */
139 const char *label; /* description of this compound */
140 int natoms, atoms_size;
141 int nbonds, bonds_size;
142 molecule_atom *atoms;
143 molecule_bond *bonds;
148 GLXContext *glx_context;
150 trackball_state *trackball;
153 GLfloat molecule_size; /* max dimension of molecule bounding box */
155 GLfloat no_label_threshold; /* Things happen when molecules are huge */
156 GLfloat wireframe_threshold;
158 int which; /* which of the molecules is being shown */
162 int mode; /* 0 = normal, 1 = out, 2 = in */
164 int next; /* 0 = random, -1 = back, 1 = forward */
166 GLuint molecule_dlist;
169 texture_font_data *atom_font, *title_font;
176 GLfloat overall_scale;
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->low_rez_p ? SPHERE_STACKS_2 : SPHERE_STACKS);
252 int slices = (mc->low_rez_p ? 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->atom_font = load_texture_font (mi->dpy, "atomFont");
269 mc->title_font = load_texture_font (mi->dpy, "titleFont");
273 static const atom_data *
274 get_atom_data (const char *atom_name)
277 const atom_data *d = 0;
278 char *n = strdup (atom_name);
282 while (!isalpha(*n)) n++;
284 while (L > 0 && !isalpha(n[L-1]))
287 for (i = 0; i < countof(all_atom_data); i++)
289 d = &all_atom_data[i];
290 if (!strcasecmp (n, all_atom_data[i].name))
300 set_atom_color (ModeInfo *mi, const molecule_atom *a,
301 Bool font_p, GLfloat alpha)
309 d = get_atom_data ("bond");
313 gl_color[0] = d->gl_color[4];
314 gl_color[1] = d->gl_color[5];
315 gl_color[2] = d->gl_color[6];
316 gl_color[3] = d->gl_color[7];
320 gl_color[0] = d->gl_color[0];
321 gl_color[1] = d->gl_color[1];
322 gl_color[2] = d->gl_color[2];
323 gl_color[3] = d->gl_color[3];
326 if (gl_color[3] == 0)
328 const char *string = !font_p ? d->color : d->text_color;
330 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
332 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
333 (a ? a->label : d->name), string);
337 gl_color[0] = xcolor.red / 65536.0;
338 gl_color[1] = xcolor.green / 65536.0;
339 gl_color[2] = xcolor.blue / 65536.0;
344 /* If we're not drawing atoms, and the color is black, use white instead.
345 This is a kludge so that H can have black text over its white ball,
346 but the text still shows up if balls are off.
348 if (font_p && !do_atoms &&
349 gl_color[0] == 0 && gl_color[1] == 0 && gl_color[2] == 0)
351 gl_color[0] = gl_color[1] = gl_color[2] = 1;
355 glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
357 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
362 atom_size (const molecule_atom *a)
365 return a->data->size2;
367 return a->data->size;
371 static molecule_atom *
372 get_atom (molecule_atom *atoms, int natoms, int id)
376 /* quick short-circuit */
379 if (atoms[id].id == id)
381 if (id > 0 && atoms[id-1].id == id)
383 if (id < natoms-1 && atoms[id+1].id == id)
387 for (i = 0; i < natoms; i++)
388 if (id == atoms[i].id)
391 fprintf (stderr, "%s: no atom %d\n", progname, id);
397 molecule_bounding_box (ModeInfo *mi,
398 GLfloat *x1, GLfloat *y1, GLfloat *z1,
399 GLfloat *x2, GLfloat *y2, GLfloat *z2)
401 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
402 molecule *m = &mc->molecules[mc->which];
407 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
411 *x1 = *x2 = m->atoms[0].x;
412 *y1 = *y2 = m->atoms[0].y;
413 *z1 = *z2 = m->atoms[0].z;
416 for (i = 1; i < m->natoms; i++)
418 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
419 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
420 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
422 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
423 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
424 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
437 draw_bounding_box (ModeInfo *mi)
439 static const GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
440 static const GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
441 int wire = MI_IS_WIREFRAME(mi);
442 GLfloat x1, y1, z1, x2, y2, z2;
443 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
445 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
448 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
450 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
451 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
453 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
454 glNormal3f(0, -1, 0);
455 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
456 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
458 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
460 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
461 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
463 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
464 glNormal3f(0, 0, -1);
465 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
466 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
468 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
470 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
471 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
473 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
474 glNormal3f(-1, 0, 0);
475 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
476 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
479 glDisable (GL_LIGHTING);
481 glColor3f (c2[0], c2[1], c2[2]);
489 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
490 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
491 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
495 glEnable (GL_LIGHTING);
499 /* Since PDB files don't always have the molecule centered around the
500 origin, and since some molecules are pretty large, scale and/or
501 translate so that the whole molecule is visible in the window.
504 ensure_bounding_box_visible (ModeInfo *mi)
506 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
508 GLfloat x1, y1, z1, x2, y2, z2;
511 GLfloat max_size = 10; /* don't bother scaling down if the molecule
512 is already smaller than this */
514 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
519 size = (w > h ? w : h);
520 size = (size > d ? size : d);
522 mc->molecule_size = size;
525 mc->overall_scale = 1;
529 mc->overall_scale = max_size / size;
530 glScalef (mc->overall_scale, mc->overall_scale, mc->overall_scale);
532 mc->low_rez_p = mc->overall_scale < 0.3;
535 glTranslatef (-(x1 + w/2),
542 /* Constructs the GL shapes of the current molecule
545 build_molecule (ModeInfo *mi, Bool transparent_p)
547 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
548 int wire = MI_IS_WIREFRAME(mi);
550 GLfloat alpha = transparent_p ? shell_alpha : 1.0;
553 molecule *m = &mc->molecules[mc->which];
557 glDisable(GL_CULL_FACE);
558 glDisable(GL_LIGHTING);
559 glDisable(GL_LIGHT0);
560 glDisable(GL_DEPTH_TEST);
561 glDisable(GL_NORMALIZE);
562 glDisable(GL_CULL_FACE);
566 glEnable(GL_CULL_FACE);
567 glEnable(GL_LIGHTING);
569 glEnable(GL_DEPTH_TEST);
570 glEnable(GL_NORMALIZE);
571 glEnable(GL_CULL_FACE);
575 set_atom_color (mi, 0, False, alpha);
578 for (i = 0; i < m->nbonds; i++)
580 const molecule_bond *b = &m->bonds[i];
581 const molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
582 const molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
587 glVertex3f(from->x, from->y, from->z);
588 glVertex3f(to->x, to->y, to->z);
594 int faces = (mc->low_rez_p ? TUBE_FACES_2 : TUBE_FACES);
600 Bool cap_p = (!do_atoms || do_shells);
602 GLfloat thickness = base * b->strength;
603 GLfloat cap_size = (cap_p ? base / 2 : 0);
607 polys += tube (from->x, from->y, from->z,
610 faces, smooth, cap_p, wire);
614 if (!wire && do_atoms)
615 for (i = 0; i < m->natoms; i++)
617 const molecule_atom *a = &m->atoms[i];
618 GLfloat size = atom_size (a);
619 set_atom_color (mi, a, False, alpha);
620 polys += sphere (mc, a->x, a->y, a->z, size, wire);
623 if (do_bbox && !transparent_p)
625 draw_bounding_box (mi);
629 mc->polygon_count += polys;
637 push_atom (molecule *m,
638 int id, const char *label,
639 GLfloat x, GLfloat y, GLfloat z)
642 if (m->atoms_size < m->natoms)
645 m->atoms = (molecule_atom *) realloc (m->atoms,
646 m->atoms_size * sizeof(*m->atoms));
648 m->atoms[m->natoms-1].id = id;
649 m->atoms[m->natoms-1].label = label;
650 m->atoms[m->natoms-1].x = x;
651 m->atoms[m->natoms-1].y = y;
652 m->atoms[m->natoms-1].z = z;
653 m->atoms[m->natoms-1].data = get_atom_data (label);
658 push_bond (molecule *m, int from, int to)
662 for (i = 0; i < m->nbonds; i++)
663 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
664 (m->bonds[i].to == from && m->bonds[i].from == to))
666 m->bonds[i].strength++;
671 if (m->bonds_size < m->nbonds)
674 m->bonds = (molecule_bond *) realloc (m->bonds,
675 m->bonds_size * sizeof(*m->bonds));
677 m->bonds[m->nbonds-1].from = from;
678 m->bonds[m->nbonds-1].to = to;
679 m->bonds[m->nbonds-1].strength = 1;
684 parse_error (const char *file, int lineno, const char *line)
686 fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
687 progname, file, lineno, line);
692 /* This function is crap.
695 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
697 const char *s = data;
701 if ((!m->label || !*m->label) &&
702 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
704 char *name = calloc (1, 100);
711 while (isspace(*n2)) n2++;
713 ss = strchr (n2, '\n');
715 ss = strchr (n2, '\r');
718 ss = n2+strlen(n2)-1;
719 while (isspace(*ss) && ss > n2)
722 if (strlen (n2) > 4 &&
723 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
724 n2[strlen(n2)-4] = 0;
726 if (m->label) free ((char *) m->label);
727 m->label = strdup (n2);
730 else if (!strncmp (s, "TITLE ", 6) ||
731 !strncmp (s, "HEADER", 6) ||
732 !strncmp (s, "COMPND", 6) ||
733 !strncmp (s, "AUTHOR", 6) ||
734 !strncmp (s, "REVDAT", 6) ||
735 !strncmp (s, "SOURCE", 6) ||
736 !strncmp (s, "EXPDTA", 6) ||
737 !strncmp (s, "JRNL ", 6) ||
738 !strncmp (s, "REMARK", 6) ||
739 !strncmp (s, "SEQRES", 6) ||
740 !strncmp (s, "HET ", 6) ||
741 !strncmp (s, "FORMUL", 6) ||
742 !strncmp (s, "CRYST1", 6) ||
743 !strncmp (s, "ORIGX1", 6) ||
744 !strncmp (s, "ORIGX2", 6) ||
745 !strncmp (s, "ORIGX3", 6) ||
746 !strncmp (s, "SCALE1", 6) ||
747 !strncmp (s, "SCALE2", 6) ||
748 !strncmp (s, "SCALE3", 6) ||
749 !strncmp (s, "MASTER", 6) ||
750 !strncmp (s, "KEYWDS", 6) ||
751 !strncmp (s, "DBREF ", 6) ||
752 !strncmp (s, "HETNAM", 6) ||
753 !strncmp (s, "HETSYN", 6) ||
754 !strncmp (s, "HELIX ", 6) ||
755 !strncmp (s, "LINK ", 6) ||
756 !strncmp (s, "MTRIX1", 6) ||
757 !strncmp (s, "MTRIX2", 6) ||
758 !strncmp (s, "MTRIX3", 6) ||
759 !strncmp (s, "SHEET ", 6) ||
760 !strncmp (s, "CISPEP", 6) ||
762 !strncmp (s, "SEQADV", 6) ||
763 !strncmp (s, "SITE ", 5) ||
764 !strncmp (s, "FTNOTE", 6) ||
765 !strncmp (s, "MODEL ", 5) ||
766 !strncmp (s, "ENDMDL", 6) ||
767 !strncmp (s, "SPRSDE", 6) ||
768 !strncmp (s, "MODRES", 6) ||
770 !strncmp (s, "GENERATED BY", 12) ||
771 !strncmp (s, "TER ", 4) ||
772 !strncmp (s, "END ", 4) ||
773 !strncmp (s, "TER\n", 4) ||
774 !strncmp (s, "END\n", 4) ||
775 !strncmp (s, "\n", 1))
778 else if (!strncmp (s, "ATOM ", 7))
781 const char *end = strchr (s, '\n');
783 char *name = (char *) calloc (1, 4);
784 GLfloat x = -999, y = -999, z = -999;
786 if (1 != sscanf (s+7, " %d ", &id))
787 parse_error (filename, line, s);
789 /* Use the "atom name" field if that is all that is available. */
790 strncpy (name, s+12, 3);
792 /* But prefer the "element" field. */
793 if (L > 77 && !isspace(s[77])) {
794 /* fprintf(stderr, " \"%s\" -> ", name); */
798 /* fprintf(stderr, "\"%s\"\n", name); */
801 while (isspace(*name)) name++;
802 ss = name + strlen(name)-1;
803 while (isspace(*ss) && ss > name)
811 if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
812 parse_error (filename, line, s);
815 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
816 progname, filename, line,
819 push_atom (m, id, name, x, y, z);
821 else if (!strncmp (s, "HETATM ", 7))
824 char *name = (char *) calloc (1, 4);
825 GLfloat x = -999, y = -999, z = -999;
827 if (1 != sscanf (s+7, " %d ", &id))
828 parse_error (filename, line, s);
830 strncpy (name, s+12, 3);
831 while (isspace(*name)) name++;
832 ss = name + strlen(name)-1;
833 while (isspace(*ss) && ss > name)
835 if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
836 parse_error (filename, line, s);
838 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
839 progname, filename, line,
842 push_atom (m, id, name, x, y, z);
844 else if (!strncmp (s, "CONECT ", 7))
847 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
848 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
849 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
850 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
852 for (j = 1; j < i; j++)
856 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
857 progname, filename, line, atoms[0], atoms[j]);
859 push_bond (m, atoms[0], atoms[j]);
864 char *s1 = strdup (s);
865 for (ss = s1; *ss && *ss != '\n'; ss++)
868 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
869 progname, filename, line, s1);
872 while (*s && *s != '\n')
883 parse_pdb_file (molecule *m, const char *name)
886 int buf_size = 40960;
890 in = fopen(name, "r");
893 char *buf = (char *) malloc(1024 + strlen(name));
894 sprintf(buf, "%s: error reading \"%s\"", progname, name);
899 buf = (char *) malloc (buf_size);
901 while (fgets (buf, buf_size-1, in))
904 for (s = buf; *s; s++)
905 if (*s == '\r') *s = '\n';
906 parse_pdb_data (m, buf, name, line++);
914 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
919 if (!m->nbonds && do_bonds)
921 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
928 #endif /* LOAD_FILES */
931 typedef struct { char *atom; int count; } atom_and_count;
933 /* When listing the components of a molecule, the convention is to put the
934 carbon atoms first, the hydrogen atoms second, and the other atom types
935 sorted alphabetically after that (although for some molecules, the usual
936 order is different: we special-case a few of those.)
939 cmp_atoms (const void *aa, const void *bb)
941 const atom_and_count *a = (atom_and_count *) aa;
942 const atom_and_count *b = (atom_and_count *) bb;
943 if (!a->atom) return 1;
944 if (!b->atom) return -1;
945 if (!strcmp(a->atom, "C")) return -1;
946 if (!strcmp(b->atom, "C")) return 1;
947 if (!strcmp(a->atom, "H")) return -1;
948 if (!strcmp(b->atom, "H")) return 1;
949 return strcmp (a->atom, b->atom);
952 static void special_case_formula (char *f);
955 generate_molecule_formula (molecule *m)
957 char *buf = (char *) malloc (m->natoms * 10);
960 atom_and_count counts[200];
961 memset (counts, 0, sizeof(counts));
963 for (i = 0; i < m->natoms; i++)
966 char *a = (char *) m->atoms[i].label;
968 while (!isalpha(*a)) a++;
970 for (e = a; isalpha(*e); e++);
972 while (counts[j].atom && !!strcmp(a, counts[j].atom))
982 while (counts[i].atom) i++;
983 qsort (counts, i, sizeof(*counts), cmp_atoms);
986 while (counts[i].atom)
988 strcat (s, counts[i].atom);
989 free (counts[i].atom);
991 if (counts[i].count > 1)
992 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
997 special_case_formula (buf);
999 if (!m->label) m->label = strdup("");
1000 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1001 strcpy (s, m->label);
1004 free ((char *) m->label);
1009 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
1011 special_case_formula (char *f)
1013 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
1014 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
1015 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
1016 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
1017 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
1018 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
1019 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
1020 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
1025 insert_vertical_whitespace (char *string)
1029 if ((string[0] == ',' ||
1031 string[0] == ':') &&
1033 string[0] = ' ', string[1] = '\n';
1039 /* Construct the molecule data from either: the builtins; or from
1040 the (one) .pdb file specified with -molecule.
1043 load_molecules (ModeInfo *mi)
1045 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1050 if (molecule_str && *molecule_str &&
1051 strcmp(molecule_str, "(default)")) /* try external PDB files */
1053 /* The -molecule option can point to a .pdb file, or to
1054 a directory of them.
1056 int wire = MI_IS_WIREFRAME(mi);
1063 if (!stat (molecule_str, &st) &&
1064 S_ISDIR (st.st_mode))
1068 struct dirent *dentry;
1070 pdb_dir = opendir (molecule_str);
1073 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1079 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1083 files = (char **) calloc (sizeof(*files), list_size);
1085 while ((dentry = readdir (pdb_dir)))
1087 int L = strlen (dentry->d_name);
1088 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1091 if (nfiles >= list_size-1)
1093 list_size = (list_size + 10) * 1.2;
1095 realloc (files, list_size * sizeof(*files));
1099 fprintf (stderr, "%s: out of memory (%d files)\n",
1105 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1107 strcpy (fn, molecule_str);
1108 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1109 strcat (fn, dentry->d_name);
1110 files[nfiles++] = fn;
1112 fprintf (stderr, "%s: file %s\n", progname, fn);
1118 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1119 progname, molecule_str);
1123 files = (char **) malloc (sizeof (*files));
1125 files[0] = strdup (molecule_str);
1127 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1130 mc->nmolecules = nfiles;
1131 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1133 for (i = 0; i < mc->nmolecules; i++)
1136 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1137 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1139 if ((wire || !do_atoms) &&
1141 mc->molecules[molecule_ctr].nbonds == 0)
1143 /* If we're not drawing atoms (e.g., wireframe mode), and
1144 there is no bond info, then make sure labels are turned
1145 on, or we'll be looking at a black screen... */
1146 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1147 progname, files[i]);
1158 mc->nmolecules = molecule_ctr;
1160 # endif /* LOAD_FILES */
1162 if (mc->nmolecules == 0) /* do the builtins if no files */
1164 mc->nmolecules = countof(builtin_pdb_data);
1165 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1166 for (i = 0; i < mc->nmolecules; i++)
1169 sprintf (name, "<builtin-%d>", i);
1170 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1171 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1175 for (i = 0; i < mc->nmolecules; i++)
1177 generate_molecule_formula (&mc->molecules[i]);
1178 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1184 /* Window management, etc
1187 reshape_molecule (ModeInfo *mi, int width, int height)
1189 GLfloat h = (GLfloat) height / (GLfloat) width;
1191 glViewport (0, 0, (GLint) width, (GLint) height);
1193 glMatrixMode(GL_PROJECTION);
1195 gluPerspective (30.0, 1/h, 20.0, 100.0);
1197 glMatrixMode(GL_MODELVIEW);
1199 gluLookAt( 0.0, 0.0, 30.0,
1203 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
1205 int o = (int) current_device_rotation();
1206 if (o != 0 && o != 180 && o != -180)
1207 glScalef (1/h, 1/h, 1/h);
1211 glClear(GL_COLOR_BUFFER_BIT);
1216 gl_init (ModeInfo *mi)
1218 static const GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1219 static const GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1220 static const GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1221 static const GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1222 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1223 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1224 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1225 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1230 startup_blurb (ModeInfo *mi)
1232 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1233 const char *s = "Constructing molecules...";
1234 print_texture_label (mi->dpy, mc->title_font,
1235 mi->xgwa.width, mi->xgwa.height,
1238 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1242 molecule_handle_event (ModeInfo *mi, XEvent *event)
1244 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1246 if (gltrackball_event_handler (event, mc->trackball,
1247 MI_WIDTH (mi), MI_HEIGHT (mi),
1248 &mc->button_down_p))
1252 if (event->xany.type == KeyPress)
1256 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1257 if (c == '<' || c == ',' || c == '-' || c == '_' ||
1258 keysym == XK_Left || keysym == XK_Up || keysym == XK_Prior)
1263 else if (c == '>' || c == '.' || c == '=' || c == '+' ||
1264 keysym == XK_Right || keysym == XK_Down ||
1272 if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
1286 init_molecule (ModeInfo *mi)
1288 molecule_configuration *mc;
1291 MI_INIT (mi, mcs, NULL);
1293 mc = &mcs[MI_SCREEN(mi)];
1295 if ((mc->glx_context = init_GL(mi)) != NULL) {
1297 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1303 wire = MI_IS_WIREFRAME(mi);
1306 Bool spinx=False, spiny=False, spinz=False;
1307 double spin_speed = 0.5;
1308 double spin_accel = 0.3;
1309 double wander_speed = 0.01;
1314 if (*s == 'x' || *s == 'X') spinx = True;
1315 else if (*s == 'y' || *s == 'Y') spiny = True;
1316 else if (*s == 'z' || *s == 'Z') spinz = True;
1317 else if (*s == '0') ;
1321 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1328 mc->rot = make_rotator (spinx ? spin_speed : 0,
1329 spiny ? spin_speed : 0,
1330 spinz ? spin_speed : 0,
1332 do_wander ? wander_speed : 0,
1333 (spinx && spiny && spinz));
1334 mc->trackball = gltrackball_init (True);
1337 orig_do_labels = do_labels;
1338 orig_do_atoms = do_atoms;
1339 orig_do_bonds = do_bonds;
1340 orig_do_shells = do_shells;
1341 orig_wire = MI_IS_WIREFRAME(mi);
1343 mc->molecule_dlist = glGenLists(1);
1345 mc->shell_dlist = glGenLists(1);
1347 load_molecules (mi);
1348 mc->which = random() % mc->nmolecules;
1350 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1351 "NoLabelThreshold");
1352 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1353 "WireframeThreshold");
1361 /* Put the labels on the atoms.
1362 This can't be a part of the display list because of the games
1363 we play with the translation matrix.
1366 draw_labels (ModeInfo *mi)
1368 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1369 int wire = MI_IS_WIREFRAME(mi);
1370 molecule *m = &mc->molecules[mc->which];
1376 for (i = 0; i < m->natoms; i++)
1378 molecule_atom *a = &m->atoms[i];
1379 GLfloat size = atom_size (a);
1385 set_atom_color (mi, a, True, 1);
1387 /* First, we translate the origin to the center of the atom.
1389 Then we retrieve the prevailing modelview matrix, which
1390 includes any rotation, wandering, and user-trackball-rolling
1393 We set the top 3x3 cells of that matrix to be the identity
1394 matrix. This removes all rotation from the matrix, while
1395 leaving the translation alone. This has the effect of
1396 leaving the prevailing coordinate system perpendicular to
1397 the camera view: were we to draw a square face, it would
1398 be in the plane of the screen.
1400 Now we translate by `size' toward the viewer -- so that the
1401 origin is *just in front* of the ball.
1403 Then we draw the label text, allowing the depth buffer to
1404 do its work: that way, labels on atoms will be occluded
1405 properly when other atoms move in front of them.
1407 This technique (of neutralizing rotation relative to the
1408 observer, after both rotations and translations have been
1409 applied) is known as "billboarding".
1412 glTranslatef(a->x, a->y, a->z); /* get matrix */
1413 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1414 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1415 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1416 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1417 glLoadIdentity(); /* reset modelview */
1418 glMultMatrixf (&m[0][0]); /* replace with ours */
1420 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1422 glRotatef (current_device_rotation(), 0, 0, 1); /* right side up */
1429 texture_string_metrics (mc->atom_font, a->label, &e, 0, 0);
1431 h = e.ascent + e.descent;
1433 s = 1.0 / h; /* Scale to unit */
1434 s *= mc->overall_scale; /* Scale to size of atom */
1435 s *= 0.8; /* Shrink a bit */
1437 glTranslatef (-w/2, -h/2, 0);
1438 print_texture_string (mc->atom_font, a->label);
1447 pick_new_molecule (ModeInfo *mi, time_t last)
1449 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1451 if (mc->nmolecules == 1)
1453 if (last != 0) return;
1458 mc->which = random() % mc->nmolecules;
1460 else if (mc->next < 0)
1463 if (mc->which < 0) mc->which = mc->nmolecules-1;
1466 else if (mc->next > 0)
1469 if (mc->which >= mc->nmolecules) mc->which = 0;
1475 while (n == mc->which)
1476 n = random() % mc->nmolecules;
1482 char *name = strdup (mc->molecules[mc->which].label);
1483 char *s = strpbrk (name, "\r\n");
1485 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1489 mc->polygon_count = 0;
1491 glNewList (mc->molecule_dlist, GL_COMPILE);
1492 ensure_bounding_box_visible (mi);
1494 do_labels = orig_do_labels;
1495 do_atoms = orig_do_atoms;
1496 do_bonds = orig_do_bonds;
1497 do_shells = orig_do_shells;
1498 MI_IS_WIREFRAME(mi) = orig_wire;
1500 if (mc->molecule_size > mc->no_label_threshold)
1502 if (mc->molecule_size > mc->wireframe_threshold)
1503 MI_IS_WIREFRAME(mi) = 1;
1505 if (MI_IS_WIREFRAME(mi))
1506 do_bonds = 1, do_shells = 0;
1511 if (! (do_bonds || do_atoms || do_labels))
1513 /* Make sure *something* shows up! */
1514 MI_IS_WIREFRAME(mi) = 1;
1518 build_molecule (mi, False);
1523 glNewList (mc->shell_dlist, GL_COMPILE);
1524 ensure_bounding_box_visible (mi);
1530 build_molecule (mi, True);
1533 do_bonds = orig_do_bonds;
1534 do_atoms = orig_do_atoms;
1535 do_labels = orig_do_labels;
1541 draw_molecule (ModeInfo *mi)
1543 time_t now = time ((time_t *) 0);
1544 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1546 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1547 Display *dpy = MI_DISPLAY(mi);
1548 Window window = MI_WINDOW(mi);
1550 if (!mc->glx_context)
1553 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1555 if (mc->draw_time == 0)
1557 pick_new_molecule (mi, mc->draw_time);
1558 mc->draw_time = now;
1560 else if (mc->mode == 0)
1562 if (mc->draw_tick++ > 10)
1564 time_t now = time((time_t *) 0);
1565 if (mc->draw_time == 0) mc->draw_time = now;
1568 if (!mc->button_down_p &&
1569 mc->nmolecules > 1 &&
1570 mc->draw_time + timeout <= now)
1572 /* randomize molecules every -timeout seconds */
1573 mc->mode = 1; /* go out */
1574 mc->mode_tick = 80 / speed;
1575 mc->draw_time = now;
1579 else if (mc->mode == 1) /* out */
1581 if (--mc->mode_tick <= 0)
1583 mc->mode_tick = 80 / speed;
1584 mc->mode = 2; /* go in */
1585 pick_new_molecule (mi, mc->draw_time);
1588 else if (mc->mode == 2) /* in */
1590 if (--mc->mode_tick <= 0)
1591 mc->mode = 0; /* normal */
1597 glScalef(1.1, 1.1, 1.1);
1601 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1602 glTranslatef((x - 0.5) * 9,
1606 gltrackball_rotate (mc->trackball);
1608 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1609 glRotatef (x * 360, 1.0, 0.0, 0.0);
1610 glRotatef (y * 360, 0.0, 1.0, 0.0);
1611 glRotatef (z * 360, 0.0, 0.0, 1.0);
1614 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1618 GLfloat s = (mc->mode == 1
1619 ? mc->mode_tick / (80 / speed)
1620 : ((80 / speed) - mc->mode_tick + 1) / (80 / speed));
1625 glCallList (mc->molecule_dlist);
1629 molecule *m = &mc->molecules[mc->which];
1633 /* This can't go in the display list, or the characters are spaced
1634 wrongly when the window is resized. */
1635 if (do_titles && m->label && *m->label)
1637 set_atom_color (mi, 0, True, 1);
1638 print_texture_label (mi->dpy, mc->title_font,
1639 mi->xgwa.width, mi->xgwa.height,
1647 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1649 glCallList (mc->shell_dlist);
1651 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1653 glDepthFunc (GL_EQUAL);
1654 glEnable (GL_BLEND);
1655 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1657 glCallList (mc->shell_dlist);
1659 glDepthFunc (GL_LESS);
1660 glDisable (GL_BLEND);
1665 mi->polygon_count = mc->polygon_count;
1667 if (mi->fps_p) do_fps (mi);
1670 glXSwapBuffers(dpy, window);
1673 XSCREENSAVER_MODULE ("Molecule", molecule)