1 /* molecule, Copyright (c) 2001-2006 Jamie Zawinski <jwz@jwz.org>
2 * Draws molecules, based on coordinates from PDB (Protein Data Base) files.
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
14 /* Documentation on the PDB file format:
15 http://www.rcsb.org/pdb/file_formats/pdb/pdbguide2.2/guide2.2_frame.html
17 Good source of PDB files:
18 http://www.sci.ouc.bc.ca/chem/molecule/molecule.html
21 #define DEFAULTS "*delay: 10000 \n" \
22 "*showFPS: False \n" \
23 "*wireframe: False \n" \
24 "*atomFont: -*-times-bold-r-normal-*-240-*\n" \
25 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
26 "*noLabelThreshold: 30 \n" \
27 "*wireframeThreshold: 150 \n" \
29 # define refresh_molecule 0
30 # define release_molecule 0
32 #define countof(x) (sizeof((x))/sizeof((*x)))
34 #include "xlockmore.h"
40 #include "gltrackball.h"
42 #ifdef USE_GL /* whole file */
44 #include <sys/types.h>
49 #define DEF_TIMEOUT "20"
50 #define DEF_SPIN "XYZ"
51 #define DEF_WANDER "False"
52 #define DEF_LABELS "True"
53 #define DEF_TITLES "True"
54 #define DEF_ATOMS "True"
55 #define DEF_BONDS "True"
56 #define DEF_SHELLS "True"
57 #define DEF_BBOX "False"
58 #define DEF_SHELL_ALPHA "0.3"
59 #define DEF_MOLECULE "(default)"
60 #define DEF_VERBOSE "False"
62 #define SPHERE_SLICES 24 /* how densely to render spheres */
63 #define SPHERE_STACKS 12
65 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
68 # define TUBE_FACES 12 /* how densely to render tubes */
73 #define SPHERE_SLICES_2 7
74 #define SPHERE_STACKS_2 4
75 #define TUBE_FACES_2 3
79 __extension__ /* don't warn about "string length is greater than the length
80 ISO C89 compilers are required to support" when includng
81 the following data file... */
83 const char * const builtin_pdb_data[] = {
84 # include "molecules.h"
92 const char *text_color;
97 /* These are the traditional colors used to render these atoms,
98 and their approximate size in angstroms.
100 static const atom_data all_atom_data[] = {
101 { "H", 1.17, 0.40, "#FFFFFF", "#B3B3B3", { 0, }},
102 { "C", 1.75, 0.58, "#999999", "#FFFFFF", { 0, }},
103 { "CA", 1.80, 0.60, "#0000FF", "#ADD8E6", { 0, }},
104 { "N", 1.55, 0.52, "#A2B5CD", "#836FFF", { 0, }},
105 { "O", 1.40, 0.47, "#FF0000", "#FFB6C1", { 0, }},
106 { "P", 1.28, 0.43, "#9370DB", "#DB7093", { 0, }},
107 { "S", 1.80, 0.60, "#8B8B00", "#FFFF00", { 0, }},
108 { "bond", 0, 0, "#B3B3B3", "#FFFF00", { 0, }},
109 { "*", 1.40, 0.47, "#008B00", "#90EE90", { 0, }}
114 int id; /* sequence number in the PDB file */
115 const char *label; /* The atom name */
116 GLfloat x, y, z; /* position in 3-space (angstroms) */
117 const atom_data *data; /* computed: which style of atom this is */
121 int from, to; /* atom sequence numbers */
122 int strength; /* how many bonds are between these two atoms */
127 const char *label; /* description of this compound */
128 int natoms, atoms_size;
129 int nbonds, bonds_size;
130 molecule_atom *atoms;
131 molecule_bond *bonds;
136 GLXContext *glx_context;
138 trackball_state *trackball;
141 GLfloat molecule_size; /* max dimension of molecule bounding box */
143 GLfloat no_label_threshold; /* Things happen when molecules are huge */
144 GLfloat wireframe_threshold;
146 int which; /* which of the molecules is being shown */
150 int mode; /* 0 = normal, 1 = out, 2 = in */
153 GLuint molecule_dlist;
156 XFontStruct *xfont1, *xfont2;
157 GLuint font1_dlist, font2_dlist;
165 } molecule_configuration;
168 static molecule_configuration *mcs = NULL;
171 static char *molecule_str;
172 static char *do_spin;
173 static Bool do_wander;
174 static Bool do_titles;
175 static Bool do_labels;
176 static Bool do_atoms;
177 static Bool do_bonds;
178 static Bool do_shells;
180 static Bool verbose_p;
181 static GLfloat shell_alpha;
184 static Bool orig_do_labels, orig_do_atoms, orig_do_bonds, orig_do_shells,
188 static XrmOptionDescRec opts[] = {
189 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
190 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
191 { "-spin", ".spin", XrmoptionSepArg, 0 },
192 { "+spin", ".spin", XrmoptionNoArg, "" },
193 { "-wander", ".wander", XrmoptionNoArg, "True" },
194 { "+wander", ".wander", XrmoptionNoArg, "False" },
195 { "-labels", ".labels", XrmoptionNoArg, "True" },
196 { "+labels", ".labels", XrmoptionNoArg, "False" },
197 { "-titles", ".titles", XrmoptionNoArg, "True" },
198 { "+titles", ".titles", XrmoptionNoArg, "False" },
199 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
200 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
201 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
202 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
203 { "-shells", ".eshells", XrmoptionNoArg, "True" },
204 { "+shells", ".eshells", XrmoptionNoArg, "False" },
205 { "-shell-alpha", ".shellAlpha", XrmoptionSepArg, 0 },
206 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
207 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
208 { "-verbose", ".verbose", XrmoptionNoArg, "True" },
211 static argtype vars[] = {
212 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE, t_String},
213 {&timeout, "timeout", "Seconds", DEF_TIMEOUT, t_Int},
214 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
215 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
216 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
217 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
218 {&do_shells, "eshells", "EShells", DEF_SHELLS, t_Bool},
219 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
220 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
221 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
222 {&shell_alpha, "shellAlpha", "ShellAlpha", DEF_SHELL_ALPHA, t_Float},
223 {&verbose_p, "verbose", "Verbose", DEF_VERBOSE, t_Bool},
226 ENTRYPOINT ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
234 sphere (molecule_configuration *mc,
235 GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
237 int stacks = (mc->scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
238 int slices = (mc->scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
241 glTranslatef (x, y, z);
242 glScalef (diameter, diameter, diameter);
243 unit_sphere (stacks, slices, wire);
246 return stacks * slices;
251 load_fonts (ModeInfo *mi)
253 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
254 load_font (mi->dpy, "atomFont", &mc->xfont1, &mc->font1_dlist);
255 load_font (mi->dpy, "titleFont", &mc->xfont2, &mc->font2_dlist);
259 static const atom_data *
260 get_atom_data (const char *atom_name)
263 const atom_data *d = 0;
264 char *n = strdup (atom_name);
268 while (!isalpha(*n)) n++;
270 while (L > 0 && !isalpha(n[L-1]))
273 for (i = 0; i < countof(all_atom_data); i++)
275 d = &all_atom_data[i];
276 if (!strcasecmp (n, all_atom_data[i].name))
286 set_atom_color (ModeInfo *mi, const molecule_atom *a,
287 Bool font_p, GLfloat alpha)
295 d = get_atom_data ("bond");
299 gl_color[0] = d->gl_color[4];
300 gl_color[1] = d->gl_color[5];
301 gl_color[2] = d->gl_color[6];
302 gl_color[3] = d->gl_color[7];
306 gl_color[0] = d->gl_color[0];
307 gl_color[1] = d->gl_color[1];
308 gl_color[2] = d->gl_color[2];
309 gl_color[3] = d->gl_color[3];
312 if (gl_color[3] == 0)
314 const char *string = !font_p ? d->color : d->text_color;
316 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
318 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
319 (a ? a->label : d->name), string);
323 gl_color[0] = xcolor.red / 65536.0;
324 gl_color[1] = xcolor.green / 65536.0;
325 gl_color[2] = xcolor.blue / 65536.0;
331 glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
333 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
338 atom_size (const molecule_atom *a)
341 return a->data->size2;
343 return a->data->size;
347 static molecule_atom *
348 get_atom (molecule_atom *atoms, int natoms, int id)
352 /* quick short-circuit */
355 if (atoms[id].id == id)
357 if (id > 0 && atoms[id-1].id == id)
359 if (id < natoms-1 && atoms[id+1].id == id)
363 for (i = 0; i < natoms; i++)
364 if (id == atoms[i].id)
367 fprintf (stderr, "%s: no atom %d\n", progname, id);
373 molecule_bounding_box (ModeInfo *mi,
374 GLfloat *x1, GLfloat *y1, GLfloat *z1,
375 GLfloat *x2, GLfloat *y2, GLfloat *z2)
377 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
378 molecule *m = &mc->molecules[mc->which];
383 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
387 *x1 = *x2 = m->atoms[0].x;
388 *y1 = *y2 = m->atoms[0].y;
389 *z1 = *z2 = m->atoms[0].z;
392 for (i = 1; i < m->natoms; i++)
394 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
395 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
396 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
398 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
399 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
400 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
413 draw_bounding_box (ModeInfo *mi)
415 static const GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
416 static const GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
417 int wire = MI_IS_WIREFRAME(mi);
418 GLfloat x1, y1, z1, x2, y2, z2;
419 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
421 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
424 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
426 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
427 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
429 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
430 glNormal3f(0, -1, 0);
431 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
432 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
434 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
436 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
437 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
439 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
440 glNormal3f(0, 0, -1);
441 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
442 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
444 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
446 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
447 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
449 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
450 glNormal3f(-1, 0, 0);
451 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
452 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
455 glPushAttrib (GL_LIGHTING);
456 glDisable (GL_LIGHTING);
458 glColor3f (c2[0], c2[1], c2[2]);
460 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
461 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
462 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
463 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
464 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
465 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
472 /* Since PDB files don't always have the molecule centered around the
473 origin, and since some molecules are pretty large, scale and/or
474 translate so that the whole molecule is visible in the window.
477 ensure_bounding_box_visible (ModeInfo *mi)
479 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
481 GLfloat x1, y1, z1, x2, y2, z2;
484 GLfloat max_size = 10; /* don't bother scaling down if the molecule
485 is already smaller than this */
487 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
492 size = (w > h ? w : h);
493 size = (size > d ? size : d);
495 mc->molecule_size = size;
501 GLfloat scale = max_size / size;
502 glScalef (scale, scale, scale);
504 mc->scale_down = scale < 0.3;
507 glTranslatef (-(x1 + w/2),
514 /* Constructs the GL shapes of the current molecule
517 build_molecule (ModeInfo *mi, Bool transparent_p)
519 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
520 int wire = MI_IS_WIREFRAME(mi);
522 GLfloat alpha = transparent_p ? shell_alpha : 1.0;
525 molecule *m = &mc->molecules[mc->which];
529 glDisable(GL_CULL_FACE);
530 glDisable(GL_LIGHTING);
531 glDisable(GL_LIGHT0);
532 glDisable(GL_DEPTH_TEST);
533 glDisable(GL_NORMALIZE);
534 glDisable(GL_CULL_FACE);
538 glEnable(GL_CULL_FACE);
539 glEnable(GL_LIGHTING);
541 glEnable(GL_DEPTH_TEST);
542 glEnable(GL_NORMALIZE);
543 glEnable(GL_CULL_FACE);
547 set_atom_color (mi, 0, False, alpha);
550 for (i = 0; i < m->nbonds; i++)
552 const molecule_bond *b = &m->bonds[i];
553 const molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
554 const molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
559 glVertex3f(from->x, from->y, from->z);
560 glVertex3f(to->x, to->y, to->z);
566 int faces = (mc->scale_down ? TUBE_FACES_2 : TUBE_FACES);
572 GLfloat thickness = 0.07 * b->strength;
573 GLfloat cap_size = 0.03;
577 tube (from->x, from->y, from->z,
580 faces, smooth, (!do_atoms || do_shells), wire);
585 if (!wire && do_atoms)
586 for (i = 0; i < m->natoms; i++)
588 const molecule_atom *a = &m->atoms[i];
589 GLfloat size = atom_size (a);
590 set_atom_color (mi, a, False, alpha);
591 polys += sphere (mc, a->x, a->y, a->z, size, wire);
594 if (do_bbox && !transparent_p)
596 draw_bounding_box (mi);
600 mc->polygon_count += polys;
608 push_atom (molecule *m,
609 int id, const char *label,
610 GLfloat x, GLfloat y, GLfloat z)
613 if (m->atoms_size < m->natoms)
616 m->atoms = (molecule_atom *) realloc (m->atoms,
617 m->atoms_size * sizeof(*m->atoms));
619 m->atoms[m->natoms-1].id = id;
620 m->atoms[m->natoms-1].label = label;
621 m->atoms[m->natoms-1].x = x;
622 m->atoms[m->natoms-1].y = y;
623 m->atoms[m->natoms-1].z = z;
624 m->atoms[m->natoms-1].data = get_atom_data (label);
629 push_bond (molecule *m, int from, int to)
633 for (i = 0; i < m->nbonds; i++)
634 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
635 (m->bonds[i].to == from && m->bonds[i].from == to))
637 m->bonds[i].strength++;
642 if (m->bonds_size < m->nbonds)
645 m->bonds = (molecule_bond *) realloc (m->bonds,
646 m->bonds_size * sizeof(*m->bonds));
648 m->bonds[m->nbonds-1].from = from;
649 m->bonds[m->nbonds-1].to = to;
650 m->bonds[m->nbonds-1].strength = 1;
655 parse_error (const char *file, int lineno, const char *line)
657 fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
658 progname, file, lineno, line);
663 /* This function is crap.
666 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
668 const char *s = data;
672 if ((!m->label || !*m->label) &&
673 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
675 char *name = calloc (1, 100);
682 while (isspace(*n2)) n2++;
684 ss = strchr (n2, '\n');
686 ss = strchr (n2, '\r');
689 ss = n2+strlen(n2)-1;
690 while (isspace(*ss) && ss > n2)
693 if (strlen (n2) > 4 &&
694 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
695 n2[strlen(n2)-4] = 0;
697 if (m->label) free ((char *) m->label);
698 m->label = strdup (n2);
701 else if (!strncmp (s, "TITLE ", 6) ||
702 !strncmp (s, "HEADER", 6) ||
703 !strncmp (s, "COMPND", 6) ||
704 !strncmp (s, "AUTHOR", 6) ||
705 !strncmp (s, "REVDAT", 6) ||
706 !strncmp (s, "SOURCE", 6) ||
707 !strncmp (s, "EXPDTA", 6) ||
708 !strncmp (s, "JRNL ", 6) ||
709 !strncmp (s, "REMARK", 6) ||
710 !strncmp (s, "SEQRES", 6) ||
711 !strncmp (s, "HET ", 6) ||
712 !strncmp (s, "FORMUL", 6) ||
713 !strncmp (s, "CRYST1", 6) ||
714 !strncmp (s, "ORIGX1", 6) ||
715 !strncmp (s, "ORIGX2", 6) ||
716 !strncmp (s, "ORIGX3", 6) ||
717 !strncmp (s, "SCALE1", 6) ||
718 !strncmp (s, "SCALE2", 6) ||
719 !strncmp (s, "SCALE3", 6) ||
720 !strncmp (s, "MASTER", 6) ||
721 !strncmp (s, "KEYWDS", 6) ||
722 !strncmp (s, "DBREF ", 6) ||
723 !strncmp (s, "HETNAM", 6) ||
724 !strncmp (s, "HETSYN", 6) ||
725 !strncmp (s, "HELIX ", 6) ||
726 !strncmp (s, "LINK ", 6) ||
727 !strncmp (s, "MTRIX1", 6) ||
728 !strncmp (s, "MTRIX2", 6) ||
729 !strncmp (s, "MTRIX3", 6) ||
730 !strncmp (s, "SHEET ", 6) ||
731 !strncmp (s, "CISPEP", 6) ||
733 !strncmp (s, "SEQADV", 6) ||
734 !strncmp (s, "SITE ", 5) ||
735 !strncmp (s, "FTNOTE", 6) ||
736 !strncmp (s, "MODEL ", 5) ||
737 !strncmp (s, "ENDMDL", 6) ||
738 !strncmp (s, "SPRSDE", 6) ||
739 !strncmp (s, "MODRES", 6) ||
741 !strncmp (s, "GENERATED BY", 12) ||
742 !strncmp (s, "TER ", 4) ||
743 !strncmp (s, "END ", 4) ||
744 !strncmp (s, "TER\n", 4) ||
745 !strncmp (s, "END\n", 4) ||
746 !strncmp (s, "\n", 1))
749 else if (!strncmp (s, "ATOM ", 7))
752 char *name = (char *) calloc (1, 4);
753 GLfloat x = -999, y = -999, z = -999;
755 if (1 != sscanf (s+7, " %d ", &id))
756 parse_error (filename, line, s);
758 strncpy (name, s+12, 3);
759 while (isspace(*name)) name++;
760 ss = name + strlen(name)-1;
761 while (isspace(*ss) && ss > name)
769 if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
770 parse_error (filename, line, s);
773 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
774 progname, filename, line,
777 push_atom (m, id, name, x, y, z);
779 else if (!strncmp (s, "HETATM ", 7))
782 char *name = (char *) calloc (1, 4);
783 GLfloat x = -999, y = -999, z = -999;
785 if (1 != sscanf (s+7, " %d ", &id))
786 parse_error (filename, line, s);
788 strncpy (name, s+12, 3);
789 while (isspace(*name)) name++;
790 ss = name + strlen(name)-1;
791 while (isspace(*ss) && ss > name)
793 if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
794 parse_error (filename, line, s);
796 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
797 progname, filename, line,
800 push_atom (m, id, name, x, y, z);
802 else if (!strncmp (s, "CONECT ", 7))
805 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
806 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
807 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
808 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
810 for (j = 1; j < i; j++)
814 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
815 progname, filename, line, atoms[0], atoms[j]);
817 push_bond (m, atoms[0], atoms[j]);
822 char *s1 = strdup (s);
823 for (ss = s1; *ss && *ss != '\n'; ss++)
826 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
827 progname, filename, line, s1);
830 while (*s && *s != '\n')
840 parse_pdb_file (molecule *m, const char *name)
843 int buf_size = 40960;
847 in = fopen(name, "r");
850 char *buf = (char *) malloc(1024 + strlen(name));
851 sprintf(buf, "%s: error reading \"%s\"", progname, name);
856 buf = (char *) malloc (buf_size);
858 while (fgets (buf, buf_size-1, in))
861 for (s = buf; *s; s++)
862 if (*s == '\r') *s = '\n';
863 parse_pdb_data (m, buf, name, line++);
871 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
876 if (!m->nbonds && do_bonds)
878 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
887 typedef struct { char *atom; int count; } atom_and_count;
889 /* When listing the components of a molecule, the convention is to put the
890 carbon atoms first, the hydrogen atoms second, and the other atom types
891 sorted alphabetically after that (although for some molecules, the usual
892 order is different: we special-case a few of those.)
895 cmp_atoms (const void *aa, const void *bb)
897 const atom_and_count *a = (atom_and_count *) aa;
898 const atom_and_count *b = (atom_and_count *) bb;
899 if (!a->atom) return 1;
900 if (!b->atom) return -1;
901 if (!strcmp(a->atom, "C")) return -1;
902 if (!strcmp(b->atom, "C")) return 1;
903 if (!strcmp(a->atom, "H")) return -1;
904 if (!strcmp(b->atom, "H")) return 1;
905 return strcmp (a->atom, b->atom);
908 static void special_case_formula (char *f);
911 generate_molecule_formula (molecule *m)
913 char *buf = (char *) malloc (m->natoms * 10);
916 atom_and_count counts[200];
917 memset (counts, 0, sizeof(counts));
919 for (i = 0; i < m->natoms; i++)
922 char *a = (char *) m->atoms[i].label;
924 while (!isalpha(*a)) a++;
926 for (e = a; isalpha(*e); e++);
928 while (counts[j].atom && !!strcmp(a, counts[j].atom))
938 while (counts[i].atom) i++;
939 qsort (counts, i, sizeof(*counts), cmp_atoms);
942 while (counts[i].atom)
944 strcat (s, counts[i].atom);
945 free (counts[i].atom);
947 if (counts[i].count > 1)
948 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
953 special_case_formula (buf);
955 if (!m->label) m->label = strdup("");
956 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
957 strcpy (s, m->label);
960 free ((char *) m->label);
965 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
967 special_case_formula (char *f)
969 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
970 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
971 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
972 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
973 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
974 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
975 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
976 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
981 insert_vertical_whitespace (char *string)
985 if ((string[0] == ',' ||
989 string[0] = ' ', string[1] = '\n';
995 /* Construct the molecule data from either: the builtins; or from
996 the (one) .pdb file specified with -molecule.
999 load_molecules (ModeInfo *mi)
1001 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1002 int wire = MI_IS_WIREFRAME(mi);
1006 if (molecule_str && *molecule_str &&
1007 strcmp(molecule_str, "(default)")) /* try external PDB files */
1009 /* The -molecule option can point to a .pdb file, or to
1010 a directory of them.
1018 if (!stat (molecule_str, &st) &&
1019 S_ISDIR (st.st_mode))
1023 struct dirent *dentry;
1025 pdb_dir = opendir (molecule_str);
1028 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1034 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1038 files = (char **) calloc (sizeof(*files), list_size);
1040 while ((dentry = readdir (pdb_dir)))
1042 int L = strlen (dentry->d_name);
1043 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1046 if (nfiles >= list_size-1)
1048 list_size = (list_size + 10) * 1.2;
1050 realloc (files, list_size * sizeof(*files));
1054 fprintf (stderr, "%s: out of memory (%d files)\n",
1060 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1062 strcpy (fn, molecule_str);
1063 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1064 strcat (fn, dentry->d_name);
1065 files[nfiles++] = fn;
1067 fprintf (stderr, "%s: file %s\n", progname, fn);
1073 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1074 progname, molecule_str);
1078 files = (char **) malloc (sizeof (*files));
1080 files[0] = strdup (molecule_str);
1082 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1085 mc->nmolecules = nfiles;
1086 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1088 for (i = 0; i < mc->nmolecules; i++)
1091 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1092 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1094 if ((wire || !do_atoms) &&
1096 mc->molecules[molecule_ctr].nbonds == 0)
1098 /* If we're not drawing atoms (e.g., wireframe mode), and
1099 there is no bond info, then make sure labels are turned
1100 on, or we'll be looking at a black screen... */
1101 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1102 progname, files[i]);
1113 mc->nmolecules = molecule_ctr;
1116 if (mc->nmolecules == 0) /* do the builtins if no files */
1118 mc->nmolecules = countof(builtin_pdb_data);
1119 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1120 for (i = 0; i < mc->nmolecules; i++)
1123 sprintf (name, "<builtin-%d>", i);
1124 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1125 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1129 for (i = 0; i < mc->nmolecules; i++)
1131 generate_molecule_formula (&mc->molecules[i]);
1132 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1138 /* Window management, etc
1141 reshape_molecule (ModeInfo *mi, int width, int height)
1143 GLfloat h = (GLfloat) height / (GLfloat) width;
1145 glViewport (0, 0, (GLint) width, (GLint) height);
1147 glMatrixMode(GL_PROJECTION);
1149 gluPerspective (30.0, 1/h, 20.0, 100.0);
1151 glMatrixMode(GL_MODELVIEW);
1153 gluLookAt( 0.0, 0.0, 30.0,
1157 glClear(GL_COLOR_BUFFER_BIT);
1162 gl_init (ModeInfo *mi)
1164 static const GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1165 static const GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1166 static const GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1167 static const GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1168 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1169 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1170 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1171 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1176 startup_blurb (ModeInfo *mi)
1178 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1179 const char *s = "Constructing molecules...";
1180 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1181 mi->xgwa.width, mi->xgwa.height,
1182 10, mi->xgwa.height - 10,
1185 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1189 molecule_handle_event (ModeInfo *mi, XEvent *event)
1191 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1193 if (event->xany.type == ButtonPress &&
1194 event->xbutton.button == Button1)
1196 mc->button_down_p = True;
1197 gltrackball_start (mc->trackball,
1198 event->xbutton.x, event->xbutton.y,
1199 MI_WIDTH (mi), MI_HEIGHT (mi));
1202 else if (event->xany.type == ButtonRelease &&
1203 event->xbutton.button == Button1)
1205 mc->button_down_p = False;
1208 else if (event->xany.type == ButtonPress &&
1209 (event->xbutton.button == Button4 ||
1210 event->xbutton.button == Button5))
1212 gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1213 !!event->xbutton.state);
1216 else if (event->xany.type == KeyPress)
1220 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1222 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1224 GLfloat speed = 4.0;
1226 mc->mode_tick = 10 * speed;
1230 else if (event->xany.type == MotionNotify &&
1233 gltrackball_track (mc->trackball,
1234 event->xmotion.x, event->xmotion.y,
1235 MI_WIDTH (mi), MI_HEIGHT (mi));
1244 init_molecule (ModeInfo *mi)
1246 molecule_configuration *mc;
1250 mcs = (molecule_configuration *)
1251 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1253 fprintf(stderr, "%s: out of memory\n", progname);
1258 mc = &mcs[MI_SCREEN(mi)];
1260 if ((mc->glx_context = init_GL(mi)) != NULL) {
1262 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1268 wire = MI_IS_WIREFRAME(mi);
1271 Bool spinx=False, spiny=False, spinz=False;
1272 double spin_speed = 0.5;
1273 double spin_accel = 0.3;
1274 double wander_speed = 0.01;
1279 if (*s == 'x' || *s == 'X') spinx = True;
1280 else if (*s == 'y' || *s == 'Y') spiny = True;
1281 else if (*s == 'z' || *s == 'Z') spinz = True;
1282 else if (*s == '0') ;
1286 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1293 mc->rot = make_rotator (spinx ? spin_speed : 0,
1294 spiny ? spin_speed : 0,
1295 spinz ? spin_speed : 0,
1297 do_wander ? wander_speed : 0,
1298 (spinx && spiny && spinz));
1299 mc->trackball = gltrackball_init ();
1302 orig_do_labels = do_labels;
1303 orig_do_atoms = do_atoms;
1304 orig_do_bonds = do_bonds;
1305 orig_do_shells = do_shells;
1306 orig_wire = MI_IS_WIREFRAME(mi);
1308 mc->molecule_dlist = glGenLists(1);
1310 mc->shell_dlist = glGenLists(1);
1312 load_molecules (mi);
1313 mc->which = random() % mc->nmolecules;
1315 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1316 "NoLabelThreshold");
1317 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1318 "WireframeThreshold");
1326 /* Put the labels on the atoms.
1327 This can't be a part of the display list because of the games
1328 we play with the translation matrix.
1331 draw_labels (ModeInfo *mi)
1333 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1334 int wire = MI_IS_WIREFRAME(mi);
1335 molecule *m = &mc->molecules[mc->which];
1342 glDisable (GL_LIGHTING); /* don't light fonts */
1344 for (i = 0; i < m->natoms; i++)
1346 molecule_atom *a = &m->atoms[i];
1347 GLfloat size = atom_size (a);
1353 set_atom_color (mi, a, True, 1);
1355 /* First, we translate the origin to the center of the atom.
1357 Then we retrieve the prevailing modelview matrix (which
1358 includes any rotation, wandering, and user-trackball-rolling
1361 We set the top 3x3 cells of that matrix to be the identity
1362 matrix. This removes all rotation from the matrix, while
1363 leaving the translation alone. This has the effect of
1364 leaving the prevailing coordinate system perpendicular to
1365 the camera view: were we to draw a square face, it would
1366 be in the plane of the screen.
1368 Now we translate by `size' toward the viewer -- so that the
1369 origin is *just in front* of the ball.
1371 Then we draw the label text, allowing the depth buffer to
1372 do its work: that way, labels on atoms will be occluded
1373 properly when other atoms move in front of them.
1375 This technique (of neutralizing rotation relative to the
1376 observer, after both rotations and translations have been
1377 applied) is known as "billboarding".
1380 glTranslatef(a->x, a->y, a->z); /* get matrix */
1381 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1382 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1383 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1384 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1385 glLoadIdentity(); /* reset modelview */
1386 glMultMatrixf (&m[0][0]); /* replace with ours */
1388 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1390 glRasterPos3f (0, 0, 0); /* draw text here */
1392 /* Before drawing the string, shift the origin to center
1393 the text over the origin of the sphere. */
1394 glBitmap (0, 0, 0, 0,
1395 -string_width (mc->xfont1, a->label) / 2,
1396 -mc->xfont1->descent,
1399 for (j = 0; j < strlen(a->label); j++)
1401 glCallList (mc->font1_dlist + (int)(a->label[j]));
1406 /* More efficient to always call glEnable() with correct values
1407 than to call glPushAttrib()/glPopAttrib(), since reading
1408 attributes from GL does a round-trip and stalls the pipeline.
1411 glEnable (GL_LIGHTING);
1416 pick_new_molecule (ModeInfo *mi, time_t last)
1418 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1420 if (mc->nmolecules == 1)
1422 if (last != 0) return;
1427 mc->which = random() % mc->nmolecules;
1432 while (n == mc->which)
1433 n = random() % mc->nmolecules;
1439 char *name = strdup (mc->molecules[mc->which].label);
1440 char *s = strpbrk (name, "\r\n");
1442 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1446 mc->polygon_count = 0;
1448 glNewList (mc->molecule_dlist, GL_COMPILE);
1449 ensure_bounding_box_visible (mi);
1451 do_labels = orig_do_labels;
1452 do_atoms = orig_do_atoms;
1453 do_bonds = orig_do_bonds;
1454 do_shells = orig_do_shells;
1455 MI_IS_WIREFRAME(mi) = orig_wire;
1457 if (mc->molecule_size > mc->no_label_threshold)
1459 if (mc->molecule_size > mc->wireframe_threshold)
1460 MI_IS_WIREFRAME(mi) = 1;
1462 if (MI_IS_WIREFRAME(mi))
1463 do_bonds = 1, do_shells = 0;
1468 if (! (do_bonds || do_atoms || do_labels))
1470 /* Make sure *something* shows up! */
1471 MI_IS_WIREFRAME(mi) = 1;
1475 build_molecule (mi, False);
1480 glNewList (mc->shell_dlist, GL_COMPILE);
1481 ensure_bounding_box_visible (mi);
1487 build_molecule (mi, True);
1490 do_bonds = orig_do_bonds;
1491 do_atoms = orig_do_atoms;
1492 do_labels = orig_do_labels;
1498 draw_molecule (ModeInfo *mi)
1500 time_t now = time ((time_t *) 0);
1501 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1503 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1504 Display *dpy = MI_DISPLAY(mi);
1505 Window window = MI_WINDOW(mi);
1507 if (!mc->glx_context)
1510 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1512 if (mc->draw_time == 0)
1514 pick_new_molecule (mi, mc->draw_time);
1515 mc->draw_time = now;
1517 else if (mc->mode == 0)
1519 if (mc->draw_tick++ > 10)
1521 time_t now = time((time_t *) 0);
1522 if (mc->draw_time == 0) mc->draw_time = now;
1525 if (!mc->button_down_p &&
1526 mc->nmolecules > 1 &&
1527 mc->draw_time + timeout <= now)
1529 /* randomize molecules every -timeout seconds */
1530 mc->mode = 1; /* go out */
1531 mc->mode_tick = 10 * speed;
1532 mc->draw_time = now;
1536 else if (mc->mode == 1) /* out */
1538 if (--mc->mode_tick <= 0)
1540 mc->mode_tick = 10 * speed;
1541 mc->mode = 2; /* go in */
1542 pick_new_molecule (mi, mc->draw_time);
1543 mc->draw_time = now;
1546 else if (mc->mode == 2) /* in */
1548 if (--mc->mode_tick <= 0)
1549 mc->mode = 0; /* normal */
1555 glScalef(1.1, 1.1, 1.1);
1559 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1560 glTranslatef((x - 0.5) * 9,
1564 gltrackball_rotate (mc->trackball);
1566 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1567 glRotatef (x * 360, 1.0, 0.0, 0.0);
1568 glRotatef (y * 360, 0.0, 1.0, 0.0);
1569 glRotatef (z * 360, 0.0, 0.0, 1.0);
1572 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1576 GLfloat s = (mc->mode == 1
1577 ? mc->mode_tick / (10 * speed)
1578 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1583 glCallList (mc->molecule_dlist);
1587 molecule *m = &mc->molecules[mc->which];
1591 /* This can't go in the display list, or the characters are spaced
1592 wrongly when the window is resized. */
1593 if (do_titles && m->label && *m->label)
1595 set_atom_color (mi, 0, True, 1);
1596 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1597 mi->xgwa.width, mi->xgwa.height,
1598 10, mi->xgwa.height - 10,
1606 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1608 glCallList (mc->shell_dlist);
1610 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1612 glDepthFunc (GL_EQUAL);
1613 glEnable (GL_BLEND);
1614 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1616 glCallList (mc->shell_dlist);
1618 glDepthFunc (GL_LESS);
1619 glDisable (GL_BLEND);
1624 mi->polygon_count = mc->polygon_count;
1626 if (mi->fps_p) do_fps (mi);
1629 glXSwapBuffers(dpy, window);
1632 XSCREENSAVER_MODULE ("Molecule", molecule)