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))
1214 gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1215 !!event->xbutton.state);
1218 else if (event->xany.type == KeyPress)
1222 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1224 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1226 GLfloat speed = 4.0;
1228 mc->mode_tick = 10 * speed;
1232 else if (event->xany.type == MotionNotify &&
1235 gltrackball_track (mc->trackball,
1236 event->xmotion.x, event->xmotion.y,
1237 MI_WIDTH (mi), MI_HEIGHT (mi));
1246 init_molecule (ModeInfo *mi)
1248 molecule_configuration *mc;
1252 mcs = (molecule_configuration *)
1253 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1255 fprintf(stderr, "%s: out of memory\n", progname);
1260 mc = &mcs[MI_SCREEN(mi)];
1262 if ((mc->glx_context = init_GL(mi)) != NULL) {
1264 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1270 wire = MI_IS_WIREFRAME(mi);
1273 Bool spinx=False, spiny=False, spinz=False;
1274 double spin_speed = 0.5;
1275 double spin_accel = 0.3;
1276 double wander_speed = 0.01;
1281 if (*s == 'x' || *s == 'X') spinx = True;
1282 else if (*s == 'y' || *s == 'Y') spiny = True;
1283 else if (*s == 'z' || *s == 'Z') spinz = True;
1284 else if (*s == '0') ;
1288 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1295 mc->rot = make_rotator (spinx ? spin_speed : 0,
1296 spiny ? spin_speed : 0,
1297 spinz ? spin_speed : 0,
1299 do_wander ? wander_speed : 0,
1300 (spinx && spiny && spinz));
1301 mc->trackball = gltrackball_init ();
1304 orig_do_labels = do_labels;
1305 orig_do_atoms = do_atoms;
1306 orig_do_bonds = do_bonds;
1307 orig_do_shells = do_shells;
1308 orig_wire = MI_IS_WIREFRAME(mi);
1310 mc->molecule_dlist = glGenLists(1);
1312 mc->shell_dlist = glGenLists(1);
1314 load_molecules (mi);
1315 mc->which = random() % mc->nmolecules;
1317 mc->no_label_threshold = get_float_resource (mi->dpy, "noLabelThreshold",
1318 "NoLabelThreshold");
1319 mc->wireframe_threshold = get_float_resource (mi->dpy, "wireframeThreshold",
1320 "WireframeThreshold");
1328 /* Put the labels on the atoms.
1329 This can't be a part of the display list because of the games
1330 we play with the translation matrix.
1333 draw_labels (ModeInfo *mi)
1335 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1336 int wire = MI_IS_WIREFRAME(mi);
1337 molecule *m = &mc->molecules[mc->which];
1344 glDisable (GL_LIGHTING); /* don't light fonts */
1346 for (i = 0; i < m->natoms; i++)
1348 molecule_atom *a = &m->atoms[i];
1349 GLfloat size = atom_size (a);
1355 set_atom_color (mi, a, True, 1);
1357 /* First, we translate the origin to the center of the atom.
1359 Then we retrieve the prevailing modelview matrix (which
1360 includes any rotation, wandering, and user-trackball-rolling
1363 We set the top 3x3 cells of that matrix to be the identity
1364 matrix. This removes all rotation from the matrix, while
1365 leaving the translation alone. This has the effect of
1366 leaving the prevailing coordinate system perpendicular to
1367 the camera view: were we to draw a square face, it would
1368 be in the plane of the screen.
1370 Now we translate by `size' toward the viewer -- so that the
1371 origin is *just in front* of the ball.
1373 Then we draw the label text, allowing the depth buffer to
1374 do its work: that way, labels on atoms will be occluded
1375 properly when other atoms move in front of them.
1377 This technique (of neutralizing rotation relative to the
1378 observer, after both rotations and translations have been
1379 applied) is known as "billboarding".
1382 glTranslatef(a->x, a->y, a->z); /* get matrix */
1383 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1384 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1385 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1386 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1387 glLoadIdentity(); /* reset modelview */
1388 glMultMatrixf (&m[0][0]); /* replace with ours */
1390 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1392 glRasterPos3f (0, 0, 0); /* draw text here */
1394 /* Before drawing the string, shift the origin to center
1395 the text over the origin of the sphere. */
1396 glBitmap (0, 0, 0, 0,
1397 -string_width (mc->xfont1, a->label) / 2,
1398 -mc->xfont1->descent,
1401 for (j = 0; j < strlen(a->label); j++)
1403 glCallList (mc->font1_dlist + (int)(a->label[j]));
1408 /* More efficient to always call glEnable() with correct values
1409 than to call glPushAttrib()/glPopAttrib(), since reading
1410 attributes from GL does a round-trip and stalls the pipeline.
1413 glEnable (GL_LIGHTING);
1418 pick_new_molecule (ModeInfo *mi, time_t last)
1420 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1422 if (mc->nmolecules == 1)
1424 if (last != 0) return;
1429 mc->which = random() % mc->nmolecules;
1434 while (n == mc->which)
1435 n = random() % mc->nmolecules;
1441 char *name = strdup (mc->molecules[mc->which].label);
1442 char *s = strpbrk (name, "\r\n");
1444 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1448 mc->polygon_count = 0;
1450 glNewList (mc->molecule_dlist, GL_COMPILE);
1451 ensure_bounding_box_visible (mi);
1453 do_labels = orig_do_labels;
1454 do_atoms = orig_do_atoms;
1455 do_bonds = orig_do_bonds;
1456 do_shells = orig_do_shells;
1457 MI_IS_WIREFRAME(mi) = orig_wire;
1459 if (mc->molecule_size > mc->no_label_threshold)
1461 if (mc->molecule_size > mc->wireframe_threshold)
1462 MI_IS_WIREFRAME(mi) = 1;
1464 if (MI_IS_WIREFRAME(mi))
1465 do_bonds = 1, do_shells = 0;
1470 if (! (do_bonds || do_atoms || do_labels))
1472 /* Make sure *something* shows up! */
1473 MI_IS_WIREFRAME(mi) = 1;
1477 build_molecule (mi, False);
1482 glNewList (mc->shell_dlist, GL_COMPILE);
1483 ensure_bounding_box_visible (mi);
1489 build_molecule (mi, True);
1492 do_bonds = orig_do_bonds;
1493 do_atoms = orig_do_atoms;
1494 do_labels = orig_do_labels;
1500 draw_molecule (ModeInfo *mi)
1502 time_t now = time ((time_t *) 0);
1503 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1505 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1506 Display *dpy = MI_DISPLAY(mi);
1507 Window window = MI_WINDOW(mi);
1509 if (!mc->glx_context)
1512 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mc->glx_context));
1514 if (mc->draw_time == 0)
1516 pick_new_molecule (mi, mc->draw_time);
1517 mc->draw_time = now;
1519 else if (mc->mode == 0)
1521 if (mc->draw_tick++ > 10)
1523 time_t now = time((time_t *) 0);
1524 if (mc->draw_time == 0) mc->draw_time = now;
1527 if (!mc->button_down_p &&
1528 mc->nmolecules > 1 &&
1529 mc->draw_time + timeout <= now)
1531 /* randomize molecules every -timeout seconds */
1532 mc->mode = 1; /* go out */
1533 mc->mode_tick = 10 * speed;
1534 mc->draw_time = now;
1538 else if (mc->mode == 1) /* out */
1540 if (--mc->mode_tick <= 0)
1542 mc->mode_tick = 10 * speed;
1543 mc->mode = 2; /* go in */
1544 pick_new_molecule (mi, mc->draw_time);
1545 mc->draw_time = now;
1548 else if (mc->mode == 2) /* in */
1550 if (--mc->mode_tick <= 0)
1551 mc->mode = 0; /* normal */
1557 glScalef(1.1, 1.1, 1.1);
1561 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1562 glTranslatef((x - 0.5) * 9,
1566 gltrackball_rotate (mc->trackball);
1568 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1569 glRotatef (x * 360, 1.0, 0.0, 0.0);
1570 glRotatef (y * 360, 0.0, 1.0, 0.0);
1571 glRotatef (z * 360, 0.0, 0.0, 1.0);
1574 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1578 GLfloat s = (mc->mode == 1
1579 ? mc->mode_tick / (10 * speed)
1580 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1585 glCallList (mc->molecule_dlist);
1589 molecule *m = &mc->molecules[mc->which];
1593 /* This can't go in the display list, or the characters are spaced
1594 wrongly when the window is resized. */
1595 if (do_titles && m->label && *m->label)
1597 set_atom_color (mi, 0, True, 1);
1598 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1599 mi->xgwa.width, mi->xgwa.height,
1600 10, mi->xgwa.height - 10,
1608 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1610 glCallList (mc->shell_dlist);
1612 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1614 glDepthFunc (GL_EQUAL);
1615 glEnable (GL_BLEND);
1616 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1618 glCallList (mc->shell_dlist);
1620 glDepthFunc (GL_LESS);
1621 glDisable (GL_BLEND);
1626 mi->polygon_count = mc->polygon_count;
1628 if (mi->fps_p) do_fps (mi);
1631 glXSwapBuffers(dpy, window);
1634 XSCREENSAVER_MODULE ("Molecule", molecule)