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.wwpdb.org/docs.html
16 http://www.rcsb.org/pdb/file_formats/pdb/pdbguide2.2/guide2.2_frame.html
18 Good source of PDB files:
19 http://www.sci.ouc.bc.ca/chem/molecule/molecule.html
20 http://www.umass.edu/microbio/rasmol/whereget.htm
21 http://www.wwpdb.org/docs.html
24 #define DEFAULTS "*delay: 10000 \n" \
25 "*showFPS: False \n" \
26 "*wireframe: False \n" \
27 "*atomFont: -*-times-bold-r-normal-*-240-*\n" \
28 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
29 "*noLabelThreshold: 30 \n" \
30 "*wireframeThreshold: 150 \n" \
32 # define refresh_molecule 0
33 # define release_molecule 0
35 #define countof(x) (sizeof((x))/sizeof((*x)))
37 #include "xlockmore.h"
43 #include "gltrackball.h"
45 #ifdef USE_GL /* whole file */
47 #include <sys/types.h>
52 #define DEF_TIMEOUT "20"
53 #define DEF_SPIN "XYZ"
54 #define DEF_WANDER "False"
55 #define DEF_LABELS "True"
56 #define DEF_TITLES "True"
57 #define DEF_ATOMS "True"
58 #define DEF_BONDS "True"
59 #define DEF_SHELLS "True"
60 #define DEF_BBOX "False"
61 #define DEF_SHELL_ALPHA "0.3"
62 #define DEF_MOLECULE "(default)"
63 #define DEF_VERBOSE "False"
65 #define SPHERE_SLICES 24 /* how densely to render spheres */
66 #define SPHERE_STACKS 12
68 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
71 # define TUBE_FACES 12 /* how densely to render tubes */
76 #define SPHERE_SLICES_2 7
77 #define SPHERE_STACKS_2 4
78 #define TUBE_FACES_2 3
82 __extension__ /* don't warn about "string length is greater than the length
83 ISO C89 compilers are required to support" when includng
84 the following data file... */
86 const char * const builtin_pdb_data[] = {
87 # include "molecules.h"
95 const char *text_color;
100 /* These are the traditional colors used to render these atoms,
101 and their approximate size in angstroms.
103 static const atom_data all_atom_data[] = {
104 { "H", 1.17, 0.40, "#FFFFFF", "#B3B3B3", { 0, }},
105 { "C", 1.75, 0.58, "#999999", "#FFFFFF", { 0, }},
106 { "CA", 1.80, 0.60, "#0000FF", "#ADD8E6", { 0, }},
107 { "N", 1.55, 0.52, "#A2B5CD", "#836FFF", { 0, }},
108 { "O", 1.40, 0.47, "#FF0000", "#FFB6C1", { 0, }},
109 { "P", 1.28, 0.43, "#9370DB", "#DB7093", { 0, }},
110 { "S", 1.80, 0.60, "#8B8B00", "#FFFF00", { 0, }},
111 { "bond", 0, 0, "#B3B3B3", "#FFFF00", { 0, }},
112 { "*", 1.40, 0.47, "#008B00", "#90EE90", { 0, }}
117 int id; /* sequence number in the PDB file */
118 const char *label; /* The atom name */
119 GLfloat x, y, z; /* position in 3-space (angstroms) */
120 const atom_data *data; /* computed: which style of atom this is */
124 int from, to; /* atom sequence numbers */
125 int strength; /* how many bonds are between these two atoms */
130 const char *label; /* description of this compound */
131 int natoms, atoms_size;
132 int nbonds, bonds_size;
133 molecule_atom *atoms;
134 molecule_bond *bonds;
139 GLXContext *glx_context;
141 trackball_state *trackball;
144 GLfloat molecule_size; /* max dimension of molecule bounding box */
146 GLfloat no_label_threshold; /* Things happen when molecules are huge */
147 GLfloat wireframe_threshold;
149 int which; /* which of the molecules is being shown */
153 int mode; /* 0 = normal, 1 = out, 2 = in */
156 GLuint molecule_dlist;
159 XFontStruct *xfont1, *xfont2;
160 GLuint font1_dlist, font2_dlist;
168 } molecule_configuration;
171 static molecule_configuration *mcs = NULL;
174 static char *molecule_str;
175 static char *do_spin;
176 static Bool do_wander;
177 static Bool do_titles;
178 static Bool do_labels;
179 static Bool do_atoms;
180 static Bool do_bonds;
181 static Bool do_shells;
183 static Bool verbose_p;
184 static GLfloat shell_alpha;
187 static Bool orig_do_labels, orig_do_atoms, orig_do_bonds, orig_do_shells,
191 static XrmOptionDescRec opts[] = {
192 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
193 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
194 { "-spin", ".spin", XrmoptionSepArg, 0 },
195 { "+spin", ".spin", XrmoptionNoArg, "" },
196 { "-wander", ".wander", XrmoptionNoArg, "True" },
197 { "+wander", ".wander", XrmoptionNoArg, "False" },
198 { "-labels", ".labels", XrmoptionNoArg, "True" },
199 { "+labels", ".labels", XrmoptionNoArg, "False" },
200 { "-titles", ".titles", XrmoptionNoArg, "True" },
201 { "+titles", ".titles", XrmoptionNoArg, "False" },
202 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
203 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
204 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
205 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
206 { "-shells", ".eshells", XrmoptionNoArg, "True" },
207 { "+shells", ".eshells", XrmoptionNoArg, "False" },
208 { "-shell-alpha", ".shellAlpha", XrmoptionSepArg, 0 },
209 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
210 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
211 { "-verbose", ".verbose", XrmoptionNoArg, "True" },
214 static argtype vars[] = {
215 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE, t_String},
216 {&timeout, "timeout", "Seconds", DEF_TIMEOUT, t_Int},
217 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
218 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
219 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
220 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
221 {&do_shells, "eshells", "EShells", DEF_SHELLS, t_Bool},
222 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
223 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
224 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
225 {&shell_alpha, "shellAlpha", "ShellAlpha", DEF_SHELL_ALPHA, t_Float},
226 {&verbose_p, "verbose", "Verbose", DEF_VERBOSE, t_Bool},
229 ENTRYPOINT ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
237 sphere (molecule_configuration *mc,
238 GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
240 int stacks = (mc->scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
241 int slices = (mc->scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
244 glTranslatef (x, y, z);
245 glScalef (diameter, diameter, diameter);
246 unit_sphere (stacks, slices, wire);
249 return stacks * slices;
254 load_fonts (ModeInfo *mi)
256 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
257 load_font (mi->dpy, "atomFont", &mc->xfont1, &mc->font1_dlist);
258 load_font (mi->dpy, "titleFont", &mc->xfont2, &mc->font2_dlist);
262 static const atom_data *
263 get_atom_data (const char *atom_name)
266 const atom_data *d = 0;
267 char *n = strdup (atom_name);
271 while (!isalpha(*n)) n++;
273 while (L > 0 && !isalpha(n[L-1]))
276 for (i = 0; i < countof(all_atom_data); i++)
278 d = &all_atom_data[i];
279 if (!strcasecmp (n, all_atom_data[i].name))
289 set_atom_color (ModeInfo *mi, const molecule_atom *a,
290 Bool font_p, GLfloat alpha)
298 d = get_atom_data ("bond");
302 gl_color[0] = d->gl_color[4];
303 gl_color[1] = d->gl_color[5];
304 gl_color[2] = d->gl_color[6];
305 gl_color[3] = d->gl_color[7];
309 gl_color[0] = d->gl_color[0];
310 gl_color[1] = d->gl_color[1];
311 gl_color[2] = d->gl_color[2];
312 gl_color[3] = d->gl_color[3];
315 if (gl_color[3] == 0)
317 const char *string = !font_p ? d->color : d->text_color;
319 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
321 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
322 (a ? a->label : d->name), string);
326 gl_color[0] = xcolor.red / 65536.0;
327 gl_color[1] = xcolor.green / 65536.0;
328 gl_color[2] = xcolor.blue / 65536.0;
334 glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
336 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
341 atom_size (const molecule_atom *a)
344 return a->data->size2;
346 return a->data->size;
350 static molecule_atom *
351 get_atom (molecule_atom *atoms, int natoms, int id)
355 /* quick short-circuit */
358 if (atoms[id].id == id)
360 if (id > 0 && atoms[id-1].id == id)
362 if (id < natoms-1 && atoms[id+1].id == id)
366 for (i = 0; i < natoms; i++)
367 if (id == atoms[i].id)
370 fprintf (stderr, "%s: no atom %d\n", progname, id);
376 molecule_bounding_box (ModeInfo *mi,
377 GLfloat *x1, GLfloat *y1, GLfloat *z1,
378 GLfloat *x2, GLfloat *y2, GLfloat *z2)
380 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
381 molecule *m = &mc->molecules[mc->which];
386 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
390 *x1 = *x2 = m->atoms[0].x;
391 *y1 = *y2 = m->atoms[0].y;
392 *z1 = *z2 = m->atoms[0].z;
395 for (i = 1; i < m->natoms; i++)
397 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
398 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
399 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
401 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
402 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
403 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
416 draw_bounding_box (ModeInfo *mi)
418 static const GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
419 static const GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
420 int wire = MI_IS_WIREFRAME(mi);
421 GLfloat x1, y1, z1, x2, y2, z2;
422 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
424 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
427 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
429 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
430 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
432 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
433 glNormal3f(0, -1, 0);
434 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
435 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
437 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
439 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
440 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
442 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
443 glNormal3f(0, 0, -1);
444 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
445 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
447 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
449 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
450 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
452 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
453 glNormal3f(-1, 0, 0);
454 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
455 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
458 glPushAttrib (GL_LIGHTING);
459 glDisable (GL_LIGHTING);
461 glColor3f (c2[0], c2[1], c2[2]);
463 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
464 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
465 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
466 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
467 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
468 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
475 /* Since PDB files don't always have the molecule centered around the
476 origin, and since some molecules are pretty large, scale and/or
477 translate so that the whole molecule is visible in the window.
480 ensure_bounding_box_visible (ModeInfo *mi)
482 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
484 GLfloat x1, y1, z1, x2, y2, z2;
487 GLfloat max_size = 10; /* don't bother scaling down if the molecule
488 is already smaller than this */
490 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
495 size = (w > h ? w : h);
496 size = (size > d ? size : d);
498 mc->molecule_size = size;
504 GLfloat scale = max_size / size;
505 glScalef (scale, scale, scale);
507 mc->scale_down = scale < 0.3;
510 glTranslatef (-(x1 + w/2),
517 /* Constructs the GL shapes of the current molecule
520 build_molecule (ModeInfo *mi, Bool transparent_p)
522 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
523 int wire = MI_IS_WIREFRAME(mi);
525 GLfloat alpha = transparent_p ? shell_alpha : 1.0;
528 molecule *m = &mc->molecules[mc->which];
532 glDisable(GL_CULL_FACE);
533 glDisable(GL_LIGHTING);
534 glDisable(GL_LIGHT0);
535 glDisable(GL_DEPTH_TEST);
536 glDisable(GL_NORMALIZE);
537 glDisable(GL_CULL_FACE);
541 glEnable(GL_CULL_FACE);
542 glEnable(GL_LIGHTING);
544 glEnable(GL_DEPTH_TEST);
545 glEnable(GL_NORMALIZE);
546 glEnable(GL_CULL_FACE);
550 set_atom_color (mi, 0, False, alpha);
553 for (i = 0; i < m->nbonds; i++)
555 const molecule_bond *b = &m->bonds[i];
556 const molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
557 const molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
562 glVertex3f(from->x, from->y, from->z);
563 glVertex3f(to->x, to->y, to->z);
569 int faces = (mc->scale_down ? TUBE_FACES_2 : TUBE_FACES);
575 GLfloat thickness = 0.07 * b->strength;
576 GLfloat cap_size = 0.03;
580 polys += tube (from->x, from->y, from->z,
583 faces, smooth, (!do_atoms || do_shells), wire);
587 if (!wire && do_atoms)
588 for (i = 0; i < m->natoms; i++)
590 const molecule_atom *a = &m->atoms[i];
591 GLfloat size = atom_size (a);
592 set_atom_color (mi, a, False, alpha);
593 polys += sphere (mc, a->x, a->y, a->z, size, wire);
596 if (do_bbox && !transparent_p)
598 draw_bounding_box (mi);
602 mc->polygon_count += polys;
610 push_atom (molecule *m,
611 int id, const char *label,
612 GLfloat x, GLfloat y, GLfloat z)
615 if (m->atoms_size < m->natoms)
618 m->atoms = (molecule_atom *) realloc (m->atoms,
619 m->atoms_size * sizeof(*m->atoms));
621 m->atoms[m->natoms-1].id = id;
622 m->atoms[m->natoms-1].label = label;
623 m->atoms[m->natoms-1].x = x;
624 m->atoms[m->natoms-1].y = y;
625 m->atoms[m->natoms-1].z = z;
626 m->atoms[m->natoms-1].data = get_atom_data (label);
631 push_bond (molecule *m, int from, int to)
635 for (i = 0; i < m->nbonds; i++)
636 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
637 (m->bonds[i].to == from && m->bonds[i].from == to))
639 m->bonds[i].strength++;
644 if (m->bonds_size < m->nbonds)
647 m->bonds = (molecule_bond *) realloc (m->bonds,
648 m->bonds_size * sizeof(*m->bonds));
650 m->bonds[m->nbonds-1].from = from;
651 m->bonds[m->nbonds-1].to = to;
652 m->bonds[m->nbonds-1].strength = 1;
657 parse_error (const char *file, int lineno, const char *line)
659 fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
660 progname, file, lineno, line);
665 /* This function is crap.
668 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
670 const char *s = data;
674 if ((!m->label || !*m->label) &&
675 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
677 char *name = calloc (1, 100);
684 while (isspace(*n2)) n2++;
686 ss = strchr (n2, '\n');
688 ss = strchr (n2, '\r');
691 ss = n2+strlen(n2)-1;
692 while (isspace(*ss) && ss > n2)
695 if (strlen (n2) > 4 &&
696 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
697 n2[strlen(n2)-4] = 0;
699 if (m->label) free ((char *) m->label);
700 m->label = strdup (n2);
703 else if (!strncmp (s, "TITLE ", 6) ||
704 !strncmp (s, "HEADER", 6) ||
705 !strncmp (s, "COMPND", 6) ||
706 !strncmp (s, "AUTHOR", 6) ||
707 !strncmp (s, "REVDAT", 6) ||
708 !strncmp (s, "SOURCE", 6) ||
709 !strncmp (s, "EXPDTA", 6) ||
710 !strncmp (s, "JRNL ", 6) ||
711 !strncmp (s, "REMARK", 6) ||
712 !strncmp (s, "SEQRES", 6) ||
713 !strncmp (s, "HET ", 6) ||
714 !strncmp (s, "FORMUL", 6) ||
715 !strncmp (s, "CRYST1", 6) ||
716 !strncmp (s, "ORIGX1", 6) ||
717 !strncmp (s, "ORIGX2", 6) ||
718 !strncmp (s, "ORIGX3", 6) ||
719 !strncmp (s, "SCALE1", 6) ||
720 !strncmp (s, "SCALE2", 6) ||
721 !strncmp (s, "SCALE3", 6) ||
722 !strncmp (s, "MASTER", 6) ||
723 !strncmp (s, "KEYWDS", 6) ||
724 !strncmp (s, "DBREF ", 6) ||
725 !strncmp (s, "HETNAM", 6) ||
726 !strncmp (s, "HETSYN", 6) ||
727 !strncmp (s, "HELIX ", 6) ||
728 !strncmp (s, "LINK ", 6) ||
729 !strncmp (s, "MTRIX1", 6) ||
730 !strncmp (s, "MTRIX2", 6) ||
731 !strncmp (s, "MTRIX3", 6) ||
732 !strncmp (s, "SHEET ", 6) ||
733 !strncmp (s, "CISPEP", 6) ||
735 !strncmp (s, "SEQADV", 6) ||
736 !strncmp (s, "SITE ", 5) ||
737 !strncmp (s, "FTNOTE", 6) ||
738 !strncmp (s, "MODEL ", 5) ||
739 !strncmp (s, "ENDMDL", 6) ||
740 !strncmp (s, "SPRSDE", 6) ||
741 !strncmp (s, "MODRES", 6) ||
743 !strncmp (s, "GENERATED BY", 12) ||
744 !strncmp (s, "TER ", 4) ||
745 !strncmp (s, "END ", 4) ||
746 !strncmp (s, "TER\n", 4) ||
747 !strncmp (s, "END\n", 4) ||
748 !strncmp (s, "\n", 1))
751 else if (!strncmp (s, "ATOM ", 7))
754 char *name = (char *) calloc (1, 4);
755 GLfloat x = -999, y = -999, z = -999;
757 if (1 != sscanf (s+7, " %d ", &id))
758 parse_error (filename, line, s);
760 strncpy (name, s+12, 3);
761 while (isspace(*name)) name++;
762 ss = name + strlen(name)-1;
763 while (isspace(*ss) && ss > name)
771 if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
772 parse_error (filename, line, s);
775 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
776 progname, filename, line,
779 push_atom (m, id, name, x, y, z);
781 else if (!strncmp (s, "HETATM ", 7))
784 char *name = (char *) calloc (1, 4);
785 GLfloat x = -999, y = -999, z = -999;
787 if (1 != sscanf (s+7, " %d ", &id))
788 parse_error (filename, line, s);
790 strncpy (name, s+12, 3);
791 while (isspace(*name)) name++;
792 ss = name + strlen(name)-1;
793 while (isspace(*ss) && ss > name)
795 if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
796 parse_error (filename, line, s);
798 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
799 progname, filename, line,
802 push_atom (m, id, name, x, y, z);
804 else if (!strncmp (s, "CONECT ", 7))
807 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
808 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
809 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
810 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
812 for (j = 1; j < i; j++)
816 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
817 progname, filename, line, atoms[0], atoms[j]);
819 push_bond (m, atoms[0], atoms[j]);
824 char *s1 = strdup (s);
825 for (ss = s1; *ss && *ss != '\n'; ss++)
828 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
829 progname, filename, line, s1);
832 while (*s && *s != '\n')
842 parse_pdb_file (molecule *m, const char *name)
845 int buf_size = 40960;
849 in = fopen(name, "r");
852 char *buf = (char *) malloc(1024 + strlen(name));
853 sprintf(buf, "%s: error reading \"%s\"", progname, name);
858 buf = (char *) malloc (buf_size);
860 while (fgets (buf, buf_size-1, in))
863 for (s = buf; *s; s++)
864 if (*s == '\r') *s = '\n';
865 parse_pdb_data (m, buf, name, line++);
873 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
878 if (!m->nbonds && do_bonds)
880 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
889 typedef struct { char *atom; int count; } atom_and_count;
891 /* When listing the components of a molecule, the convention is to put the
892 carbon atoms first, the hydrogen atoms second, and the other atom types
893 sorted alphabetically after that (although for some molecules, the usual
894 order is different: we special-case a few of those.)
897 cmp_atoms (const void *aa, const void *bb)
899 const atom_and_count *a = (atom_and_count *) aa;
900 const atom_and_count *b = (atom_and_count *) bb;
901 if (!a->atom) return 1;
902 if (!b->atom) return -1;
903 if (!strcmp(a->atom, "C")) return -1;
904 if (!strcmp(b->atom, "C")) return 1;
905 if (!strcmp(a->atom, "H")) return -1;
906 if (!strcmp(b->atom, "H")) return 1;
907 return strcmp (a->atom, b->atom);
910 static void special_case_formula (char *f);
913 generate_molecule_formula (molecule *m)
915 char *buf = (char *) malloc (m->natoms * 10);
918 atom_and_count counts[200];
919 memset (counts, 0, sizeof(counts));
921 for (i = 0; i < m->natoms; i++)
924 char *a = (char *) m->atoms[i].label;
926 while (!isalpha(*a)) a++;
928 for (e = a; isalpha(*e); e++);
930 while (counts[j].atom && !!strcmp(a, counts[j].atom))
940 while (counts[i].atom) i++;
941 qsort (counts, i, sizeof(*counts), cmp_atoms);
944 while (counts[i].atom)
946 strcat (s, counts[i].atom);
947 free (counts[i].atom);
949 if (counts[i].count > 1)
950 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
955 special_case_formula (buf);
957 if (!m->label) m->label = strdup("");
958 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
959 strcpy (s, m->label);
962 free ((char *) m->label);
967 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
969 special_case_formula (char *f)
971 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
972 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
973 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
974 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
975 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
976 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
977 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
978 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
983 insert_vertical_whitespace (char *string)
987 if ((string[0] == ',' ||
991 string[0] = ' ', string[1] = '\n';
997 /* Construct the molecule data from either: the builtins; or from
998 the (one) .pdb file specified with -molecule.
1001 load_molecules (ModeInfo *mi)
1003 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1004 int wire = MI_IS_WIREFRAME(mi);
1008 if (molecule_str && *molecule_str &&
1009 strcmp(molecule_str, "(default)")) /* try external PDB files */
1011 /* The -molecule option can point to a .pdb file, or to
1012 a directory of them.
1020 if (!stat (molecule_str, &st) &&
1021 S_ISDIR (st.st_mode))
1025 struct dirent *dentry;
1027 pdb_dir = opendir (molecule_str);
1030 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1036 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1040 files = (char **) calloc (sizeof(*files), list_size);
1042 while ((dentry = readdir (pdb_dir)))
1044 int L = strlen (dentry->d_name);
1045 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1048 if (nfiles >= list_size-1)
1050 list_size = (list_size + 10) * 1.2;
1052 realloc (files, list_size * sizeof(*files));
1056 fprintf (stderr, "%s: out of memory (%d files)\n",
1062 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1064 strcpy (fn, molecule_str);
1065 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1066 strcat (fn, dentry->d_name);
1067 files[nfiles++] = fn;
1069 fprintf (stderr, "%s: file %s\n", progname, fn);
1075 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1076 progname, molecule_str);
1080 files = (char **) malloc (sizeof (*files));
1082 files[0] = strdup (molecule_str);
1084 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1087 mc->nmolecules = nfiles;
1088 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1090 for (i = 0; i < mc->nmolecules; i++)
1093 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1094 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1096 if ((wire || !do_atoms) &&
1098 mc->molecules[molecule_ctr].nbonds == 0)
1100 /* If we're not drawing atoms (e.g., wireframe mode), and
1101 there is no bond info, then make sure labels are turned
1102 on, or we'll be looking at a black screen... */
1103 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1104 progname, files[i]);
1115 mc->nmolecules = molecule_ctr;
1118 if (mc->nmolecules == 0) /* do the builtins if no files */
1120 mc->nmolecules = countof(builtin_pdb_data);
1121 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1122 for (i = 0; i < mc->nmolecules; i++)
1125 sprintf (name, "<builtin-%d>", i);
1126 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1127 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1131 for (i = 0; i < mc->nmolecules; i++)
1133 generate_molecule_formula (&mc->molecules[i]);
1134 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1140 /* Window management, etc
1143 reshape_molecule (ModeInfo *mi, int width, int height)
1145 GLfloat h = (GLfloat) height / (GLfloat) width;
1147 glViewport (0, 0, (GLint) width, (GLint) height);
1149 glMatrixMode(GL_PROJECTION);
1151 gluPerspective (30.0, 1/h, 20.0, 100.0);
1153 glMatrixMode(GL_MODELVIEW);
1155 gluLookAt( 0.0, 0.0, 30.0,
1159 glClear(GL_COLOR_BUFFER_BIT);
1164 gl_init (ModeInfo *mi)
1166 static const GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1167 static const GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1168 static const GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1169 static const GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1170 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1171 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1172 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1173 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1178 startup_blurb (ModeInfo *mi)
1180 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1181 const char *s = "Constructing molecules...";
1182 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1183 mi->xgwa.width, mi->xgwa.height,
1184 10, mi->xgwa.height - 10,
1187 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1191 molecule_handle_event (ModeInfo *mi, XEvent *event)
1193 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1195 if (event->xany.type == ButtonPress &&
1196 event->xbutton.button == Button1)
1198 mc->button_down_p = True;
1199 gltrackball_start (mc->trackball,
1200 event->xbutton.x, event->xbutton.y,
1201 MI_WIDTH (mi), MI_HEIGHT (mi));
1204 else if (event->xany.type == ButtonRelease &&
1205 event->xbutton.button == Button1)
1207 mc->button_down_p = False;
1210 else if (event->xany.type == ButtonPress &&
1211 (event->xbutton.button == Button4 ||
1212 event->xbutton.button == Button5 ||
1213 event->xbutton.button == Button6 ||
1214 event->xbutton.button == Button7))
1216 gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1217 !!event->xbutton.state);
1220 else if (event->xany.type == KeyPress)
1224 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1226 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1228 GLfloat speed = 4.0;
1230 mc->mode_tick = 10 * speed;
1234 else if (event->xany.type == MotionNotify &&
1237 gltrackball_track (mc->trackball,
1238 event->xmotion.x, event->xmotion.y,
1239 MI_WIDTH (mi), MI_HEIGHT (mi));
1248 init_molecule (ModeInfo *mi)
1250 molecule_configuration *mc;
1254 mcs = (molecule_configuration *)
1255 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1257 fprintf(stderr, "%s: out of memory\n", progname);
1262 mc = &mcs[MI_SCREEN(mi)];
1264 if ((mc->glx_context = init_GL(mi)) != NULL) {
1266 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1272 wire = MI_IS_WIREFRAME(mi);
1275 Bool spinx=False, spiny=False, spinz=False;
1276 double spin_speed = 0.5;
1277 double spin_accel = 0.3;
1278 double wander_speed = 0.01;
1283 if (*s == 'x' || *s == 'X') spinx = True;
1284 else if (*s == 'y' || *s == 'Y') spiny = True;
1285 else if (*s == 'z' || *s == 'Z') spinz = True;
1286 else if (*s == '0') ;
1290 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1297 mc->rot = make_rotator (spinx ? spin_speed : 0,
1298 spiny ? spin_speed : 0,
1299 spinz ? spin_speed : 0,
1301 do_wander ? wander_speed : 0,
1302 (spinx && spiny && spinz));
1303 mc->trackball = gltrackball_init ();
1306 orig_do_labels = do_labels;
1307 orig_do_atoms = do_atoms;
1308 orig_do_bonds = do_bonds;
1309 orig_do_shells = do_shells;
1310 orig_wire = MI_IS_WIREFRAME(mi);
1312 mc->molecule_dlist = glGenLists(1);
1314 mc->shell_dlist = glGenLists(1);
1316 load_molecules (mi);
1317 mc->which = random() % mc->nmolecules;
1319 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1320 "NoLabelThreshold");
1321 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1322 "WireframeThreshold");
1330 /* Put the labels on the atoms.
1331 This can't be a part of the display list because of the games
1332 we play with the translation matrix.
1335 draw_labels (ModeInfo *mi)
1337 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1338 int wire = MI_IS_WIREFRAME(mi);
1339 molecule *m = &mc->molecules[mc->which];
1346 glDisable (GL_LIGHTING); /* don't light fonts */
1348 for (i = 0; i < m->natoms; i++)
1350 molecule_atom *a = &m->atoms[i];
1351 GLfloat size = atom_size (a);
1357 set_atom_color (mi, a, True, 1);
1359 /* First, we translate the origin to the center of the atom.
1361 Then we retrieve the prevailing modelview matrix (which
1362 includes any rotation, wandering, and user-trackball-rolling
1365 We set the top 3x3 cells of that matrix to be the identity
1366 matrix. This removes all rotation from the matrix, while
1367 leaving the translation alone. This has the effect of
1368 leaving the prevailing coordinate system perpendicular to
1369 the camera view: were we to draw a square face, it would
1370 be in the plane of the screen.
1372 Now we translate by `size' toward the viewer -- so that the
1373 origin is *just in front* of the ball.
1375 Then we draw the label text, allowing the depth buffer to
1376 do its work: that way, labels on atoms will be occluded
1377 properly when other atoms move in front of them.
1379 This technique (of neutralizing rotation relative to the
1380 observer, after both rotations and translations have been
1381 applied) is known as "billboarding".
1384 glTranslatef(a->x, a->y, a->z); /* get matrix */
1385 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1386 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1387 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1388 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1389 glLoadIdentity(); /* reset modelview */
1390 glMultMatrixf (&m[0][0]); /* replace with ours */
1392 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1394 glRasterPos3f (0, 0, 0); /* draw text here */
1396 /* Before drawing the string, shift the origin to center
1397 the text over the origin of the sphere. */
1398 glBitmap (0, 0, 0, 0,
1399 -string_width (mc->xfont1, a->label) / 2,
1400 -mc->xfont1->descent,
1403 for (j = 0; j < strlen(a->label); j++)
1405 glCallList (mc->font1_dlist + (int)(a->label[j]));
1410 /* More efficient to always call glEnable() with correct values
1411 than to call glPushAttrib()/glPopAttrib(), since reading
1412 attributes from GL does a round-trip and stalls the pipeline.
1415 glEnable (GL_LIGHTING);
1420 pick_new_molecule (ModeInfo *mi, time_t last)
1422 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1424 if (mc->nmolecules == 1)
1426 if (last != 0) return;
1431 mc->which = random() % mc->nmolecules;
1436 while (n == mc->which)
1437 n = random() % mc->nmolecules;
1443 char *name = strdup (mc->molecules[mc->which].label);
1444 char *s = strpbrk (name, "\r\n");
1446 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1450 mc->polygon_count = 0;
1452 glNewList (mc->molecule_dlist, GL_COMPILE);
1453 ensure_bounding_box_visible (mi);
1455 do_labels = orig_do_labels;
1456 do_atoms = orig_do_atoms;
1457 do_bonds = orig_do_bonds;
1458 do_shells = orig_do_shells;
1459 MI_IS_WIREFRAME(mi) = orig_wire;
1461 if (mc->molecule_size > mc->no_label_threshold)
1463 if (mc->molecule_size > mc->wireframe_threshold)
1464 MI_IS_WIREFRAME(mi) = 1;
1466 if (MI_IS_WIREFRAME(mi))
1467 do_bonds = 1, do_shells = 0;
1472 if (! (do_bonds || do_atoms || do_labels))
1474 /* Make sure *something* shows up! */
1475 MI_IS_WIREFRAME(mi) = 1;
1479 build_molecule (mi, False);
1484 glNewList (mc->shell_dlist, GL_COMPILE);
1485 ensure_bounding_box_visible (mi);
1491 build_molecule (mi, True);
1494 do_bonds = orig_do_bonds;
1495 do_atoms = orig_do_atoms;
1496 do_labels = orig_do_labels;
1502 draw_molecule (ModeInfo *mi)
1504 time_t now = time ((time_t *) 0);
1505 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1507 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1508 Display *dpy = MI_DISPLAY(mi);
1509 Window window = MI_WINDOW(mi);
1511 if (!mc->glx_context)
1514 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1516 if (mc->draw_time == 0)
1518 pick_new_molecule (mi, mc->draw_time);
1519 mc->draw_time = now;
1521 else if (mc->mode == 0)
1523 if (mc->draw_tick++ > 10)
1525 time_t now = time((time_t *) 0);
1526 if (mc->draw_time == 0) mc->draw_time = now;
1529 if (!mc->button_down_p &&
1530 mc->nmolecules > 1 &&
1531 mc->draw_time + timeout <= now)
1533 /* randomize molecules every -timeout seconds */
1534 mc->mode = 1; /* go out */
1535 mc->mode_tick = 10 * speed;
1536 mc->draw_time = now;
1540 else if (mc->mode == 1) /* out */
1542 if (--mc->mode_tick <= 0)
1544 mc->mode_tick = 10 * speed;
1545 mc->mode = 2; /* go in */
1546 pick_new_molecule (mi, mc->draw_time);
1547 mc->draw_time = now;
1550 else if (mc->mode == 2) /* in */
1552 if (--mc->mode_tick <= 0)
1553 mc->mode = 0; /* normal */
1559 glScalef(1.1, 1.1, 1.1);
1563 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1564 glTranslatef((x - 0.5) * 9,
1568 gltrackball_rotate (mc->trackball);
1570 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1571 glRotatef (x * 360, 1.0, 0.0, 0.0);
1572 glRotatef (y * 360, 0.0, 1.0, 0.0);
1573 glRotatef (z * 360, 0.0, 0.0, 1.0);
1576 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1580 GLfloat s = (mc->mode == 1
1581 ? mc->mode_tick / (10 * speed)
1582 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1587 glCallList (mc->molecule_dlist);
1591 molecule *m = &mc->molecules[mc->which];
1595 /* This can't go in the display list, or the characters are spaced
1596 wrongly when the window is resized. */
1597 if (do_titles && m->label && *m->label)
1599 set_atom_color (mi, 0, True, 1);
1600 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1601 mi->xgwa.width, mi->xgwa.height,
1602 10, mi->xgwa.height - 10,
1610 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1612 glCallList (mc->shell_dlist);
1614 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1616 glDepthFunc (GL_EQUAL);
1617 glEnable (GL_BLEND);
1618 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1620 glCallList (mc->shell_dlist);
1622 glDepthFunc (GL_LESS);
1623 glDisable (GL_BLEND);
1628 mi->polygon_count = mc->polygon_count;
1630 if (mi->fps_p) do_fps (mi);
1633 glXSwapBuffers(dpy, window);
1636 XSCREENSAVER_MODULE ("Molecule", molecule)