1 /* molecule, Copyright (c) 2001-2005 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/docs/format/pdbguide2.2/guide2.2_frame.html
17 Good source of PDB files:
18 http://www.sci.ouc.bc.ca/chem/molecule/molecule.html
21 #include <sys/types.h>
25 #include <X11/Intrinsic.h>
27 #define PROGCLASS "Molecule"
28 #define HACK_INIT init_molecule
29 #define HACK_DRAW draw_molecule
30 #define HACK_RESHAPE reshape_molecule
31 #define HACK_HANDLE_EVENT molecule_handle_event
32 #define EVENT_MASK PointerMotionMask
33 #define molecule_opts xlockmore_opts
35 #define DEF_TIMEOUT "20"
36 #define DEF_SPIN "XYZ"
37 #define DEF_WANDER "False"
38 #define DEF_LABELS "True"
39 #define DEF_TITLES "True"
40 #define DEF_ATOMS "True"
41 #define DEF_BONDS "True"
42 #define DEF_SHELLS "False"
43 #define DEF_BBOX "False"
44 #define DEF_SHELL_ALPHA "0.3"
45 #define DEF_MOLECULE "(default)"
46 #define DEF_VERBOSE "False"
48 #define DEFAULTS "*delay: 10000 \n" \
49 "*showFPS: False \n" \
50 "*wireframe: False \n" \
51 "*atomFont: -*-times-bold-r-normal-*-240-*\n" \
52 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
53 "*noLabelThreshold: 30 \n" \
54 "*wireframeThreshold: 150 \n" \
58 #define countof(x) (sizeof((x))/sizeof((*x)))
60 #include "xlockmore.h"
66 #include "gltrackball.h"
68 #ifdef USE_GL /* whole file */
76 #define SPHERE_SLICES 24 /* how densely to render spheres */
77 #define SPHERE_STACKS 12
79 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
82 # define TUBE_FACES 12 /* how densely to render tubes */
87 static int scale_down;
88 #define SPHERE_SLICES_2 7
89 #define SPHERE_STACKS_2 4
90 #define TUBE_FACES_2 3
94 __extension__ /* don't warn about "string length is greater than the length
95 ISO C89 compilers are required to support" when includng
96 the following data file... */
98 const char * const builtin_pdb_data[] = {
99 # include "molecules.h"
107 const char *text_color;
112 /* These are the traditional colors used to render these atoms,
113 and their approximate size in angstroms.
115 static atom_data all_atom_data[] = {
116 { "H", 1.17, 0, "White", "Grey70", { 0, }},
117 { "C", 1.75, 0, "Grey60", "White", { 0, }},
118 { "CA", 1.80, 0, "Blue", "LightBlue", { 0, }},
119 { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }},
120 { "O", 1.40, 0, "Red", "LightPink", { 0, }},
121 { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }},
122 { "S", 1.80, 0, "Yellow4", "Yellow1", { 0, }},
123 { "bond", 0, 0, "Grey70", "Yellow1", { 0, }},
124 { "*", 1.40, 0, "Green4", "LightGreen", { 0, }}
129 int id; /* sequence number in the PDB file */
130 const char *label; /* The atom name */
131 GLfloat x, y, z; /* position in 3-space (angstroms) */
132 atom_data *data; /* computed: which style of atom this is */
136 int from, to; /* atom sequence numbers */
137 int strength; /* how many bonds are between these two atoms */
142 const char *label; /* description of this compound */
143 int natoms, atoms_size;
144 int nbonds, bonds_size;
145 molecule_atom *atoms;
146 molecule_bond *bonds;
151 GLXContext *glx_context;
153 trackball_state *trackball;
156 GLfloat molecule_size; /* max dimension of molecule bounding box */
158 GLfloat no_label_threshold; /* Things happen when molecules are huge */
159 GLfloat wireframe_threshold;
161 int which; /* which of the molecules is being shown */
165 int mode; /* 0 = normal, 1 = out, 2 = in */
168 GLuint molecule_dlist;
171 XFontStruct *xfont1, *xfont2;
172 GLuint font1_dlist, font2_dlist;
175 } molecule_configuration;
178 static molecule_configuration *mcs = NULL;
181 static char *molecule_str;
182 static char *do_spin;
183 static Bool do_wander;
184 static Bool do_titles;
185 static Bool do_labels;
186 static Bool do_atoms;
187 static Bool do_bonds;
188 static Bool do_shells;
190 static Bool verbose_p;
191 static GLfloat shell_alpha;
194 static Bool orig_do_labels, orig_do_atoms, orig_do_bonds, orig_do_shells,
198 static XrmOptionDescRec opts[] = {
199 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
200 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
201 { "-spin", ".spin", XrmoptionSepArg, 0 },
202 { "+spin", ".spin", XrmoptionNoArg, "" },
203 { "-wander", ".wander", XrmoptionNoArg, "True" },
204 { "+wander", ".wander", XrmoptionNoArg, "False" },
205 { "-labels", ".labels", XrmoptionNoArg, "True" },
206 { "+labels", ".labels", XrmoptionNoArg, "False" },
207 { "-titles", ".titles", XrmoptionNoArg, "True" },
208 { "+titles", ".titles", XrmoptionNoArg, "False" },
209 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
210 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
211 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
212 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
213 { "-shells", ".eshells", XrmoptionNoArg, "True" },
214 { "+shells", ".eshells", XrmoptionNoArg, "False" },
215 { "-shell-alpha", ".shellAlpha", XrmoptionSepArg, 0 },
216 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
217 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
218 { "-verbose", ".verbose", XrmoptionNoArg, "True" },
221 static argtype vars[] = {
222 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE, t_String},
223 {&timeout, "timeout", "Seconds", DEF_TIMEOUT, t_Int},
224 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
225 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
226 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
227 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
228 {&do_shells, "eshells", "EShells", DEF_SHELLS, t_Bool},
229 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
230 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
231 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
232 {&shell_alpha, "shellAlpha", "ShellAlpha", DEF_SHELL_ALPHA, t_Float},
233 {&verbose_p, "verbose", "Verbose", DEF_VERBOSE, t_Bool},
236 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
244 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
246 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
247 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
250 glTranslatef (x, y, z);
251 glScalef (diameter, diameter, diameter);
252 unit_sphere (stacks, slices, wire);
255 return stacks * slices;
260 load_fonts (ModeInfo *mi)
262 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
263 load_font (mi->dpy, "atomFont", &mc->xfont1, &mc->font1_dlist);
264 load_font (mi->dpy, "titleFont", &mc->xfont2, &mc->font2_dlist);
269 get_atom_data (const char *atom_name)
273 char *n = strdup (atom_name);
277 while (!isalpha(*n)) n++;
279 while (L > 0 && !isalpha(n[L-1]))
282 for (i = 0; i < countof(all_atom_data); i++)
284 d = &all_atom_data[i];
285 if (!strcasecmp (n, all_atom_data[i].name))
295 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p, GLfloat alpha)
304 static atom_data *def_data = 0;
305 if (!def_data) def_data = get_atom_data ("bond");
309 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
311 if (gl_color[3] == 0)
313 const char *string = !font_p ? d->color : d->text_color;
315 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
317 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
318 (a ? a->label : d->name), string);
322 gl_color[0] = xcolor.red / 65536.0;
323 gl_color[1] = xcolor.green / 65536.0;
324 gl_color[2] = xcolor.blue / 65536.0;
330 glColor4f (gl_color[0], gl_color[1], gl_color[2], gl_color[3]);
332 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
337 atom_size (molecule_atom *a)
341 if (a->data->size2 == 0)
343 /* let the molecules have the same relative sizes, but scale
344 them to a smaller range, so that the bond-tubes are
351 GLfloat ratio = (a->data->size - min) / (max - min);
352 a->data->size2 = bot + (ratio * (top - bot));
354 return a->data->size2;
357 return a->data->size;
361 static molecule_atom *
362 get_atom (molecule_atom *atoms, int natoms, int id)
366 /* quick short-circuit */
369 if (atoms[id].id == id)
371 if (id > 0 && atoms[id-1].id == id)
373 if (id < natoms-1 && atoms[id+1].id == id)
377 for (i = 0; i < natoms; i++)
378 if (id == atoms[i].id)
381 fprintf (stderr, "%s: no atom %d\n", progname, id);
387 molecule_bounding_box (ModeInfo *mi,
388 GLfloat *x1, GLfloat *y1, GLfloat *z1,
389 GLfloat *x2, GLfloat *y2, GLfloat *z2)
391 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
392 molecule *m = &mc->molecules[mc->which];
397 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
401 *x1 = *x2 = m->atoms[0].x;
402 *y1 = *y2 = m->atoms[0].y;
403 *z1 = *z2 = m->atoms[0].z;
406 for (i = 1; i < m->natoms; i++)
408 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
409 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
410 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
412 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
413 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
414 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
427 draw_bounding_box (ModeInfo *mi)
429 static GLfloat c1[4] = { 0.2, 0.2, 0.4, 1.0 };
430 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
431 int wire = MI_IS_WIREFRAME(mi);
432 GLfloat x1, y1, z1, x2, y2, z2;
433 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
435 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
438 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
440 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
441 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
443 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
444 glNormal3f(0, -1, 0);
445 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
446 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
448 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
450 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
451 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
453 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
454 glNormal3f(0, 0, -1);
455 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
456 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
458 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
460 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
461 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
463 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
464 glNormal3f(-1, 0, 0);
465 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
466 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
469 glPushAttrib (GL_LIGHTING);
470 glDisable (GL_LIGHTING);
472 glColor3f (c2[0], c2[1], c2[2]);
474 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
475 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
476 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
477 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
478 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
479 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
486 /* Since PDB files don't always have the molecule centered around the
487 origin, and since some molecules are pretty large, scale and/or
488 translate so that the whole molecule is visible in the window.
491 ensure_bounding_box_visible (ModeInfo *mi)
493 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
495 GLfloat x1, y1, z1, x2, y2, z2;
498 GLfloat max_size = 10; /* don't bother scaling down if the molecule
499 is already smaller than this */
501 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
506 size = (w > h ? w : h);
507 size = (size > d ? size : d);
509 mc->molecule_size = size;
515 GLfloat scale = max_size / size;
516 glScalef (scale, scale, scale);
518 scale_down = scale < 0.3;
521 glTranslatef (-(x1 + w/2),
528 /* Constructs the GL shapes of the current molecule
531 build_molecule (ModeInfo *mi, Bool transparent_p)
533 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
534 int wire = MI_IS_WIREFRAME(mi);
536 GLfloat alpha = transparent_p ? shell_alpha : 1.0;
539 molecule *m = &mc->molecules[mc->which];
543 glDisable(GL_CULL_FACE);
544 glDisable(GL_LIGHTING);
545 glDisable(GL_LIGHT0);
546 glDisable(GL_DEPTH_TEST);
547 glDisable(GL_NORMALIZE);
548 glDisable(GL_CULL_FACE);
552 glEnable(GL_CULL_FACE);
553 glEnable(GL_LIGHTING);
555 glEnable(GL_DEPTH_TEST);
556 glEnable(GL_NORMALIZE);
557 glEnable(GL_CULL_FACE);
561 set_atom_color (mi, 0, False, alpha);
564 for (i = 0; i < m->nbonds; i++)
566 molecule_bond *b = &m->bonds[i];
567 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
568 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
573 glVertex3f(from->x, from->y, from->z);
574 glVertex3f(to->x, to->y, to->z);
580 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
586 GLfloat thickness = 0.07 * b->strength;
587 GLfloat cap_size = 0.03;
591 tube (from->x, from->y, from->z,
594 faces, smooth, (!do_atoms || do_shells), wire);
599 if (!wire && do_atoms)
600 for (i = 0; i < m->natoms; i++)
602 molecule_atom *a = &m->atoms[i];
603 GLfloat size = atom_size (a);
604 set_atom_color (mi, a, False, alpha);
605 polys += sphere (a->x, a->y, a->z, size, wire);
608 if (do_bbox && !transparent_p)
610 draw_bounding_box (mi);
614 mc->polygon_count += polys;
622 push_atom (molecule *m,
623 int id, const char *label,
624 GLfloat x, GLfloat y, GLfloat z)
627 if (m->atoms_size < m->natoms)
630 m->atoms = (molecule_atom *) realloc (m->atoms,
631 m->atoms_size * sizeof(*m->atoms));
633 m->atoms[m->natoms-1].id = id;
634 m->atoms[m->natoms-1].label = label;
635 m->atoms[m->natoms-1].x = x;
636 m->atoms[m->natoms-1].y = y;
637 m->atoms[m->natoms-1].z = z;
638 m->atoms[m->natoms-1].data = get_atom_data (label);
643 push_bond (molecule *m, int from, int to)
647 for (i = 0; i < m->nbonds; i++)
648 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
649 (m->bonds[i].to == from && m->bonds[i].from == to))
651 m->bonds[i].strength++;
656 if (m->bonds_size < m->nbonds)
659 m->bonds = (molecule_bond *) realloc (m->bonds,
660 m->bonds_size * sizeof(*m->bonds));
662 m->bonds[m->nbonds-1].from = from;
663 m->bonds[m->nbonds-1].to = to;
664 m->bonds[m->nbonds-1].strength = 1;
669 parse_error (const char *file, int lineno, const char *line)
671 fprintf (stderr, "%s: %s: parse error, line %d: %s\n",
672 progname, file, lineno, line);
677 /* This function is crap.
680 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
682 const char *s = data;
686 if ((!m->label || !*m->label) &&
687 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
689 char *name = calloc (1, 100);
696 while (isspace(*n2)) n2++;
698 ss = strchr (n2, '\n');
700 ss = strchr (n2, '\r');
703 ss = n2+strlen(n2)-1;
704 while (isspace(*ss) && ss > n2)
707 if (strlen (n2) > 4 &&
708 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
709 n2[strlen(n2)-4] = 0;
711 if (m->label) free ((char *) m->label);
712 m->label = strdup (n2);
715 else if (!strncmp (s, "TITLE ", 6) ||
716 !strncmp (s, "HEADER", 6) ||
717 !strncmp (s, "COMPND", 6) ||
718 !strncmp (s, "AUTHOR", 6) ||
719 !strncmp (s, "REVDAT", 6) ||
720 !strncmp (s, "SOURCE", 6) ||
721 !strncmp (s, "EXPDTA", 6) ||
722 !strncmp (s, "JRNL ", 6) ||
723 !strncmp (s, "REMARK", 6) ||
724 !strncmp (s, "SEQRES", 6) ||
725 !strncmp (s, "HET ", 6) ||
726 !strncmp (s, "FORMUL", 6) ||
727 !strncmp (s, "CRYST1", 6) ||
728 !strncmp (s, "ORIGX1", 6) ||
729 !strncmp (s, "ORIGX2", 6) ||
730 !strncmp (s, "ORIGX3", 6) ||
731 !strncmp (s, "SCALE1", 6) ||
732 !strncmp (s, "SCALE2", 6) ||
733 !strncmp (s, "SCALE3", 6) ||
734 !strncmp (s, "MASTER", 6) ||
735 !strncmp (s, "KEYWDS", 6) ||
736 !strncmp (s, "DBREF ", 6) ||
737 !strncmp (s, "HETNAM", 6) ||
738 !strncmp (s, "HETSYN", 6) ||
739 !strncmp (s, "HELIX ", 6) ||
740 !strncmp (s, "LINK ", 6) ||
741 !strncmp (s, "MTRIX1", 6) ||
742 !strncmp (s, "MTRIX2", 6) ||
743 !strncmp (s, "MTRIX3", 6) ||
744 !strncmp (s, "SHEET ", 6) ||
745 !strncmp (s, "CISPEP", 6) ||
746 !strncmp (s, "GENERATED BY", 12) ||
747 !strncmp (s, "TER ", 4) ||
748 !strncmp (s, "END ", 4) ||
749 !strncmp (s, "TER\n", 4) ||
750 !strncmp (s, "END\n", 4) ||
751 !strncmp (s, "\n", 1))
754 else if (!strncmp (s, "ATOM ", 7))
757 char *name = (char *) calloc (1, 4);
758 GLfloat x = -999, y = -999, z = -999;
760 if (1 != sscanf (s+7, " %d ", &id))
761 parse_error (filename, line, s);
763 strncpy (name, s+12, 3);
764 while (isspace(*name)) name++;
765 ss = name + strlen(name)-1;
766 while (isspace(*ss) && ss > name)
774 if (3 != sscanf (s + 32, " %f %f %f ", &x, &y, &z))
775 parse_error (filename, line, s);
778 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
779 progname, filename, line,
782 push_atom (m, id, name, x, y, z);
784 else if (!strncmp (s, "HETATM ", 7))
787 char *name = (char *) calloc (1, 4);
788 GLfloat x = -999, y = -999, z = -999;
790 if (1 != sscanf (s+7, " %d ", &id))
791 parse_error (filename, line, s);
793 strncpy (name, s+12, 3);
794 while (isspace(*name)) name++;
795 ss = name + strlen(name)-1;
796 while (isspace(*ss) && ss > name)
798 if (3 != sscanf (s + 30, " %f %f %f ", &x, &y, &z))
799 parse_error (filename, line, s);
801 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
802 progname, filename, line,
805 push_atom (m, id, name, x, y, z);
807 else if (!strncmp (s, "CONECT ", 7))
810 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
811 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
812 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
813 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
815 for (j = 1; j < i; j++)
819 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
820 progname, filename, line, atoms[0], atoms[j]);
822 push_bond (m, atoms[0], atoms[j]);
827 char *s1 = strdup (s);
828 for (ss = s1; *ss && *ss != '\n'; ss++)
831 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
832 progname, filename, line, s1);
835 while (*s && *s != '\n')
845 parse_pdb_file (molecule *m, const char *name)
848 int buf_size = 40960;
852 in = fopen(name, "r");
855 char *buf = (char *) malloc(1024 + strlen(name));
856 sprintf(buf, "%s: error reading \"%s\"", progname, name);
861 buf = (char *) malloc (buf_size);
863 while (fgets (buf, buf_size-1, in))
866 for (s = buf; *s; s++)
867 if (*s == '\r') *s = '\n';
868 parse_pdb_data (m, buf, name, line++);
876 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
881 if (!m->nbonds && do_bonds)
883 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
892 typedef struct { char *atom; int count; } atom_and_count;
894 /* When listing the components of a molecule, the convention is to put the
895 carbon atoms first, the hydrogen atoms second, and the other atom types
896 sorted alphabetically after that (although for some molecules, the usual
897 order is different: we special-case a few of those.)
900 cmp_atoms (const void *aa, const void *bb)
902 const atom_and_count *a = (atom_and_count *) aa;
903 const atom_and_count *b = (atom_and_count *) bb;
904 if (!a->atom) return 1;
905 if (!b->atom) return -1;
906 if (!strcmp(a->atom, "C")) return -1;
907 if (!strcmp(b->atom, "C")) return 1;
908 if (!strcmp(a->atom, "H")) return -1;
909 if (!strcmp(b->atom, "H")) return 1;
910 return strcmp (a->atom, b->atom);
913 static void special_case_formula (char *f);
916 generate_molecule_formula (molecule *m)
918 char *buf = (char *) malloc (m->natoms * 10);
921 atom_and_count counts[200];
922 memset (counts, 0, sizeof(counts));
924 for (i = 0; i < m->natoms; i++)
927 char *a = (char *) m->atoms[i].label;
929 while (!isalpha(*a)) a++;
931 for (e = a; isalpha(*e); e++);
933 while (counts[j].atom && !!strcmp(a, counts[j].atom))
943 while (counts[i].atom) i++;
944 qsort (counts, i, sizeof(*counts), cmp_atoms);
947 while (counts[i].atom)
949 strcat (s, counts[i].atom);
950 free (counts[i].atom);
952 if (counts[i].count > 1)
953 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
958 special_case_formula (buf);
960 if (!m->label) m->label = strdup("");
961 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
962 strcpy (s, m->label);
965 free ((char *) m->label);
970 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
972 special_case_formula (char *f)
974 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
975 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
976 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
977 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
978 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
979 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
980 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
981 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
986 insert_vertical_whitespace (char *string)
990 if ((string[0] == ',' ||
994 string[0] = ' ', string[1] = '\n';
1000 /* Construct the molecule data from either: the builtins; or from
1001 the (one) .pdb file specified with -molecule.
1004 load_molecules (ModeInfo *mi)
1006 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1007 int wire = MI_IS_WIREFRAME(mi);
1011 if (molecule_str && *molecule_str &&
1012 strcmp(molecule_str, "(default)")) /* try external PDB files */
1014 /* The -molecule option can point to a .pdb file, or to
1015 a directory of them.
1023 if (!stat (molecule_str, &st) &&
1024 S_ISDIR (st.st_mode))
1028 struct dirent *dentry;
1030 pdb_dir = opendir (molecule_str);
1033 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1039 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1043 files = (char **) calloc (sizeof(*files), list_size);
1045 while ((dentry = readdir (pdb_dir)))
1047 int L = strlen (dentry->d_name);
1048 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1051 if (nfiles >= list_size-1)
1053 list_size = (list_size + 10) * 1.2;
1055 realloc (files, list_size * sizeof(*files));
1059 fprintf (stderr, "%s: out of memory (%d files)\n",
1065 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1067 strcpy (fn, molecule_str);
1068 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1069 strcat (fn, dentry->d_name);
1070 files[nfiles++] = fn;
1072 fprintf (stderr, "%s: file %s\n", progname, fn);
1078 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1079 progname, molecule_str);
1083 files = (char **) malloc (sizeof (*files));
1085 files[0] = strdup (molecule_str);
1087 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1090 mc->nmolecules = nfiles;
1091 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1093 for (i = 0; i < mc->nmolecules; i++)
1096 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1097 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1099 if ((wire || !do_atoms) &&
1101 mc->molecules[molecule_ctr].nbonds == 0)
1103 /* If we're not drawing atoms (e.g., wireframe mode), and
1104 there is no bond info, then make sure labels are turned
1105 on, or we'll be looking at a black screen... */
1106 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1107 progname, files[i]);
1118 mc->nmolecules = molecule_ctr;
1121 if (mc->nmolecules == 0) /* do the builtins if no files */
1123 mc->nmolecules = countof(builtin_pdb_data);
1124 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1125 for (i = 0; i < mc->nmolecules; i++)
1128 sprintf (name, "<builtin-%d>", i);
1129 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1130 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1134 for (i = 0; i < mc->nmolecules; i++)
1136 generate_molecule_formula (&mc->molecules[i]);
1137 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1143 /* Window management, etc
1146 reshape_molecule (ModeInfo *mi, int width, int height)
1148 GLfloat h = (GLfloat) height / (GLfloat) width;
1150 glViewport (0, 0, (GLint) width, (GLint) height);
1152 glMatrixMode(GL_PROJECTION);
1154 gluPerspective (30.0, 1/h, 20.0, 100.0);
1156 glMatrixMode(GL_MODELVIEW);
1158 gluLookAt( 0.0, 0.0, 30.0,
1162 glClear(GL_COLOR_BUFFER_BIT);
1167 gl_init (ModeInfo *mi)
1169 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1170 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1171 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1172 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1173 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1174 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1175 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1176 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1181 startup_blurb (ModeInfo *mi)
1183 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1184 const char *s = "Constructing molecules...";
1185 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1186 mi->xgwa.width, mi->xgwa.height,
1187 10, mi->xgwa.height - 10,
1190 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1194 molecule_handle_event (ModeInfo *mi, XEvent *event)
1196 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1198 if (event->xany.type == ButtonPress &&
1199 event->xbutton.button == Button1)
1201 mc->button_down_p = True;
1202 gltrackball_start (mc->trackball,
1203 event->xbutton.x, event->xbutton.y,
1204 MI_WIDTH (mi), MI_HEIGHT (mi));
1207 else if (event->xany.type == ButtonRelease &&
1208 event->xbutton.button == Button1)
1210 mc->button_down_p = False;
1213 else if (event->xany.type == ButtonPress &&
1214 (event->xbutton.button == Button4 ||
1215 event->xbutton.button == Button5))
1217 gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1218 !!event->xbutton.state);
1221 else if (event->xany.type == KeyPress)
1225 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1227 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1229 GLfloat speed = 4.0;
1231 mc->mode_tick = 10 * speed;
1235 else if (event->xany.type == MotionNotify &&
1238 gltrackball_track (mc->trackball,
1239 event->xmotion.x, event->xmotion.y,
1240 MI_WIDTH (mi), MI_HEIGHT (mi));
1249 init_molecule (ModeInfo *mi)
1251 molecule_configuration *mc;
1255 mcs = (molecule_configuration *)
1256 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1258 fprintf(stderr, "%s: out of memory\n", progname);
1263 mc = &mcs[MI_SCREEN(mi)];
1265 if ((mc->glx_context = init_GL(mi)) != NULL) {
1267 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1273 wire = MI_IS_WIREFRAME(mi);
1276 Bool spinx=False, spiny=False, spinz=False;
1277 double spin_speed = 0.5;
1278 double spin_accel = 0.3;
1279 double wander_speed = 0.01;
1284 if (*s == 'x' || *s == 'X') spinx = True;
1285 else if (*s == 'y' || *s == 'Y') spiny = True;
1286 else if (*s == 'z' || *s == 'Z') spinz = True;
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 ("noLabelThreshold",
1320 "NoLabelThreshold");
1321 mc->wireframe_threshold = get_float_resource ("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++)
1404 glCallList (mc->font1_dlist + (int)(a->label[j]));
1409 /* More efficient to always call glEnable() with correct values
1410 than to call glPushAttrib()/glPopAttrib(), since reading
1411 attributes from GL does a round-trip and stalls the pipeline.
1414 glEnable (GL_LIGHTING);
1419 pick_new_molecule (ModeInfo *mi, time_t last)
1421 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1423 if (mc->nmolecules == 1)
1425 if (last != 0) return;
1430 mc->which = random() % mc->nmolecules;
1435 while (n == mc->which)
1436 n = random() % mc->nmolecules;
1442 char *name = strdup (mc->molecules[mc->which].label);
1443 char *s = strpbrk (name, "\r\n");
1445 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1449 mc->polygon_count = 0;
1451 glNewList (mc->molecule_dlist, GL_COMPILE);
1452 ensure_bounding_box_visible (mi);
1454 do_labels = orig_do_labels;
1455 do_atoms = orig_do_atoms;
1456 do_bonds = orig_do_bonds;
1457 do_shells = orig_do_shells;
1458 MI_IS_WIREFRAME(mi) = orig_wire;
1460 if (mc->molecule_size > mc->no_label_threshold)
1462 if (mc->molecule_size > mc->wireframe_threshold)
1463 MI_IS_WIREFRAME(mi) = 1;
1465 if (MI_IS_WIREFRAME(mi))
1466 do_bonds = 1, do_shells = 0;
1471 if (! (do_bonds || do_atoms || do_labels))
1473 /* Make sure *something* shows up! */
1474 MI_IS_WIREFRAME(mi) = 1;
1478 build_molecule (mi, False);
1483 glNewList (mc->shell_dlist, GL_COMPILE);
1484 ensure_bounding_box_visible (mi);
1490 build_molecule (mi, True);
1493 do_bonds = orig_do_bonds;
1494 do_atoms = orig_do_atoms;
1495 do_labels = orig_do_labels;
1501 draw_molecule (ModeInfo *mi)
1503 static time_t last = 0;
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)
1516 pick_new_molecule (mi, last);
1519 else if (mc->mode == 0)
1521 static int tick = 0;
1524 time_t now = time((time_t *) 0);
1525 if (last == 0) last = now;
1528 if (!mc->button_down_p &&
1529 mc->nmolecules > 1 &&
1530 last + timeout <= now)
1532 /* randomize molecules every -timeout seconds */
1533 mc->mode = 1; /* go out */
1534 mc->mode_tick = 10 * speed;
1539 else if (mc->mode == 1) /* out */
1541 if (--mc->mode_tick <= 0)
1543 mc->mode_tick = 10 * speed;
1544 mc->mode = 2; /* go in */
1545 pick_new_molecule (mi, last);
1549 else if (mc->mode == 2) /* in */
1551 if (--mc->mode_tick <= 0)
1552 mc->mode = 0; /* normal */
1558 glScalef(1.1, 1.1, 1.1);
1562 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1563 glTranslatef((x - 0.5) * 9,
1567 gltrackball_rotate (mc->trackball);
1569 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1570 glRotatef (x * 360, 1.0, 0.0, 0.0);
1571 glRotatef (y * 360, 0.0, 1.0, 0.0);
1572 glRotatef (z * 360, 0.0, 0.0, 1.0);
1575 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1579 GLfloat s = (mc->mode == 1
1580 ? mc->mode_tick / (10 * speed)
1581 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1586 glCallList (mc->molecule_dlist);
1590 molecule *m = &mc->molecules[mc->which];
1594 /* This can't go in the display list, or the characters are spaced
1595 wrongly when the window is resized. */
1596 if (do_titles && m->label && *m->label)
1598 set_atom_color (mi, 0, True, 1);
1599 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1600 mi->xgwa.width, mi->xgwa.height,
1601 10, mi->xgwa.height - 10,
1609 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1611 glCallList (mc->shell_dlist);
1613 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1615 glDepthFunc (GL_EQUAL);
1616 glEnable (GL_BLEND);
1617 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1619 glCallList (mc->shell_dlist);
1621 glDepthFunc (GL_LESS);
1622 glDisable (GL_BLEND);
1627 mi->polygon_count = mc->polygon_count;
1629 if (mi->fps_p) do_fps (mi);
1632 glXSwapBuffers(dpy, window);