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;
1292 mcs = (molecule_configuration *)
1293 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1295 fprintf(stderr, "%s: out of memory\n", progname);
1300 mc = &mcs[MI_SCREEN(mi)];
1302 if ((mc->glx_context = init_GL(mi)) != NULL) {
1304 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1310 wire = MI_IS_WIREFRAME(mi);
1313 Bool spinx=False, spiny=False, spinz=False;
1314 double spin_speed = 0.5;
1315 double spin_accel = 0.3;
1316 double wander_speed = 0.01;
1321 if (*s == 'x' || *s == 'X') spinx = True;
1322 else if (*s == 'y' || *s == 'Y') spiny = True;
1323 else if (*s == 'z' || *s == 'Z') spinz = True;
1324 else if (*s == '0') ;
1328 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1335 mc->rot = make_rotator (spinx ? spin_speed : 0,
1336 spiny ? spin_speed : 0,
1337 spinz ? spin_speed : 0,
1339 do_wander ? wander_speed : 0,
1340 (spinx && spiny && spinz));
1341 mc->trackball = gltrackball_init (True);
1344 orig_do_labels = do_labels;
1345 orig_do_atoms = do_atoms;
1346 orig_do_bonds = do_bonds;
1347 orig_do_shells = do_shells;
1348 orig_wire = MI_IS_WIREFRAME(mi);
1350 mc->molecule_dlist = glGenLists(1);
1352 mc->shell_dlist = glGenLists(1);
1354 load_molecules (mi);
1355 mc->which = random() % mc->nmolecules;
1357 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1358 "NoLabelThreshold");
1359 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1360 "WireframeThreshold");
1368 /* Put the labels on the atoms.
1369 This can't be a part of the display list because of the games
1370 we play with the translation matrix.
1373 draw_labels (ModeInfo *mi)
1375 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1376 int wire = MI_IS_WIREFRAME(mi);
1377 molecule *m = &mc->molecules[mc->which];
1383 for (i = 0; i < m->natoms; i++)
1385 molecule_atom *a = &m->atoms[i];
1386 GLfloat size = atom_size (a);
1392 set_atom_color (mi, a, True, 1);
1394 /* First, we translate the origin to the center of the atom.
1396 Then we retrieve the prevailing modelview matrix, which
1397 includes any rotation, wandering, and user-trackball-rolling
1400 We set the top 3x3 cells of that matrix to be the identity
1401 matrix. This removes all rotation from the matrix, while
1402 leaving the translation alone. This has the effect of
1403 leaving the prevailing coordinate system perpendicular to
1404 the camera view: were we to draw a square face, it would
1405 be in the plane of the screen.
1407 Now we translate by `size' toward the viewer -- so that the
1408 origin is *just in front* of the ball.
1410 Then we draw the label text, allowing the depth buffer to
1411 do its work: that way, labels on atoms will be occluded
1412 properly when other atoms move in front of them.
1414 This technique (of neutralizing rotation relative to the
1415 observer, after both rotations and translations have been
1416 applied) is known as "billboarding".
1419 glTranslatef(a->x, a->y, a->z); /* get matrix */
1420 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1421 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1422 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1423 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1424 glLoadIdentity(); /* reset modelview */
1425 glMultMatrixf (&m[0][0]); /* replace with ours */
1427 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1429 glRotatef (current_device_rotation(), 0, 0, 1); /* right side up */
1436 texture_string_metrics (mc->atom_font, a->label, &e, 0, 0);
1438 h = e.ascent + e.descent;
1440 s = 1.0 / h; /* Scale to unit */
1441 s *= mc->overall_scale; /* Scale to size of atom */
1442 s *= 0.8; /* Shrink a bit */
1444 glTranslatef (-w/2, -h/2, 0);
1445 print_texture_string (mc->atom_font, a->label);
1454 pick_new_molecule (ModeInfo *mi, time_t last)
1456 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1458 if (mc->nmolecules == 1)
1460 if (last != 0) return;
1465 mc->which = random() % mc->nmolecules;
1467 else if (mc->next < 0)
1470 if (mc->which < 0) mc->which = mc->nmolecules-1;
1473 else if (mc->next > 0)
1476 if (mc->which >= mc->nmolecules) mc->which = 0;
1482 while (n == mc->which)
1483 n = random() % mc->nmolecules;
1489 char *name = strdup (mc->molecules[mc->which].label);
1490 char *s = strpbrk (name, "\r\n");
1492 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1496 mc->polygon_count = 0;
1498 glNewList (mc->molecule_dlist, GL_COMPILE);
1499 ensure_bounding_box_visible (mi);
1501 do_labels = orig_do_labels;
1502 do_atoms = orig_do_atoms;
1503 do_bonds = orig_do_bonds;
1504 do_shells = orig_do_shells;
1505 MI_IS_WIREFRAME(mi) = orig_wire;
1507 if (mc->molecule_size > mc->no_label_threshold)
1509 if (mc->molecule_size > mc->wireframe_threshold)
1510 MI_IS_WIREFRAME(mi) = 1;
1512 if (MI_IS_WIREFRAME(mi))
1513 do_bonds = 1, do_shells = 0;
1518 if (! (do_bonds || do_atoms || do_labels))
1520 /* Make sure *something* shows up! */
1521 MI_IS_WIREFRAME(mi) = 1;
1525 build_molecule (mi, False);
1530 glNewList (mc->shell_dlist, GL_COMPILE);
1531 ensure_bounding_box_visible (mi);
1537 build_molecule (mi, True);
1540 do_bonds = orig_do_bonds;
1541 do_atoms = orig_do_atoms;
1542 do_labels = orig_do_labels;
1548 draw_molecule (ModeInfo *mi)
1550 time_t now = time ((time_t *) 0);
1551 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1553 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1554 Display *dpy = MI_DISPLAY(mi);
1555 Window window = MI_WINDOW(mi);
1557 if (!mc->glx_context)
1560 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1562 if (mc->draw_time == 0)
1564 pick_new_molecule (mi, mc->draw_time);
1565 mc->draw_time = now;
1567 else if (mc->mode == 0)
1569 if (mc->draw_tick++ > 10)
1571 time_t now = time((time_t *) 0);
1572 if (mc->draw_time == 0) mc->draw_time = now;
1575 if (!mc->button_down_p &&
1576 mc->nmolecules > 1 &&
1577 mc->draw_time + timeout <= now)
1579 /* randomize molecules every -timeout seconds */
1580 mc->mode = 1; /* go out */
1581 mc->mode_tick = 80 / speed;
1582 mc->draw_time = now;
1586 else if (mc->mode == 1) /* out */
1588 if (--mc->mode_tick <= 0)
1590 mc->mode_tick = 80 / speed;
1591 mc->mode = 2; /* go in */
1592 pick_new_molecule (mi, mc->draw_time);
1595 else if (mc->mode == 2) /* in */
1597 if (--mc->mode_tick <= 0)
1598 mc->mode = 0; /* normal */
1604 glScalef(1.1, 1.1, 1.1);
1608 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1609 glTranslatef((x - 0.5) * 9,
1613 gltrackball_rotate (mc->trackball);
1615 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1616 glRotatef (x * 360, 1.0, 0.0, 0.0);
1617 glRotatef (y * 360, 0.0, 1.0, 0.0);
1618 glRotatef (z * 360, 0.0, 0.0, 1.0);
1621 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1625 GLfloat s = (mc->mode == 1
1626 ? mc->mode_tick / (80 / speed)
1627 : ((80 / speed) - mc->mode_tick + 1) / (80 / speed));
1632 glCallList (mc->molecule_dlist);
1636 molecule *m = &mc->molecules[mc->which];
1640 /* This can't go in the display list, or the characters are spaced
1641 wrongly when the window is resized. */
1642 if (do_titles && m->label && *m->label)
1644 set_atom_color (mi, 0, True, 1);
1645 print_texture_label (mi->dpy, mc->title_font,
1646 mi->xgwa.width, mi->xgwa.height,
1654 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1656 glCallList (mc->shell_dlist);
1658 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1660 glDepthFunc (GL_EQUAL);
1661 glEnable (GL_BLEND);
1662 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1664 glCallList (mc->shell_dlist);
1666 glDepthFunc (GL_LESS);
1667 glDisable (GL_BLEND);
1672 mi->polygon_count = mc->polygon_count;
1674 if (mi->fps_p) do_fps (mi);
1677 glXSwapBuffers(dpy, window);
1680 XSCREENSAVER_MODULE ("Molecule", molecule)