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 /* This function is crap.
672 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
674 const char *s = data;
678 if ((!m->label || !*m->label) &&
679 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
681 char *name = calloc (1, 100);
688 while (isspace(*n2)) n2++;
690 ss = strchr (n2, '\n');
692 ss = strchr (n2, '\r');
695 ss = n2+strlen(n2)-1;
696 while (isspace(*ss) && ss > n2)
699 if (strlen (n2) > 4 &&
700 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
701 n2[strlen(n2)-4] = 0;
703 if (m->label) free ((char *) m->label);
704 m->label = strdup (n2);
707 else if (!strncmp (s, "TITLE ", 6) ||
708 !strncmp (s, "HEADER", 6) ||
709 !strncmp (s, "COMPND", 6) ||
710 !strncmp (s, "AUTHOR", 6) ||
711 !strncmp (s, "REVDAT", 6) ||
712 !strncmp (s, "SOURCE", 6) ||
713 !strncmp (s, "EXPDTA", 6) ||
714 !strncmp (s, "JRNL ", 6) ||
715 !strncmp (s, "REMARK", 6) ||
716 !strncmp (s, "SEQRES", 6) ||
717 !strncmp (s, "HET ", 6) ||
718 !strncmp (s, "FORMUL", 6) ||
719 !strncmp (s, "CRYST1", 6) ||
720 !strncmp (s, "ORIGX1", 6) ||
721 !strncmp (s, "ORIGX2", 6) ||
722 !strncmp (s, "ORIGX3", 6) ||
723 !strncmp (s, "SCALE1", 6) ||
724 !strncmp (s, "SCALE2", 6) ||
725 !strncmp (s, "SCALE3", 6) ||
726 !strncmp (s, "MASTER", 6) ||
727 !strncmp (s, "KEYWDS", 6) ||
728 !strncmp (s, "DBREF ", 6) ||
729 !strncmp (s, "HETNAM", 6) ||
730 !strncmp (s, "HETSYN", 6) ||
731 !strncmp (s, "HELIX ", 6) ||
732 !strncmp (s, "LINK ", 6) ||
733 !strncmp (s, "MTRIX1", 6) ||
734 !strncmp (s, "MTRIX2", 6) ||
735 !strncmp (s, "MTRIX3", 6) ||
736 !strncmp (s, "SHEET ", 6) ||
737 !strncmp (s, "CISPEP", 6) ||
738 !strncmp (s, "GENERATED BY", 12) ||
739 !strncmp (s, "TER ", 4) ||
740 !strncmp (s, "END ", 4) ||
741 !strncmp (s, "TER\n", 4) ||
742 !strncmp (s, "END\n", 4) ||
743 !strncmp (s, "\n", 1))
746 else if (!strncmp (s, "ATOM ", 7))
749 char *name = (char *) calloc (1, 4);
750 GLfloat x = -999, y = -999, z = -999;
752 sscanf (s+7, " %d ", &id);
754 strncpy (name, s+12, 3);
755 while (isspace(*name)) name++;
756 ss = name + strlen(name)-1;
757 while (isspace(*ss) && ss > name)
765 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
767 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
768 progname, filename, line,
771 push_atom (m, id, name, x, y, z);
773 else if (!strncmp (s, "HETATM ", 7))
776 char *name = (char *) calloc (1, 4);
777 GLfloat x = -999, y = -999, z = -999;
779 sscanf (s+7, " %d ", &id);
781 strncpy (name, s+12, 3);
782 while (isspace(*name)) name++;
783 ss = name + strlen(name)-1;
784 while (isspace(*ss) && ss > name)
786 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
788 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
789 progname, filename, line,
792 push_atom (m, id, name, x, y, z);
794 else if (!strncmp (s, "CONECT ", 7))
797 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
798 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
799 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
800 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
802 for (j = 1; j < i; j++)
806 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
807 progname, filename, line, atoms[0], atoms[j]);
809 push_bond (m, atoms[0], atoms[j]);
814 char *s1 = strdup (s);
815 for (ss = s1; *ss && *ss != '\n'; ss++)
818 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
819 progname, filename, line, s1);
822 while (*s && *s != '\n')
832 parse_pdb_file (molecule *m, const char *name)
835 int buf_size = 40960;
839 in = fopen(name, "r");
842 char *buf = (char *) malloc(1024 + strlen(name));
843 sprintf(buf, "%s: error reading \"%s\"", progname, name);
848 buf = (char *) malloc (buf_size);
850 while (fgets (buf, buf_size-1, in))
853 for (s = buf; *s; s++)
854 if (*s == '\r') *s = '\n';
855 parse_pdb_data (m, buf, name, line++);
863 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
868 if (!m->nbonds && do_bonds)
870 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
879 typedef struct { char *atom; int count; } atom_and_count;
881 /* When listing the components of a molecule, the convention is to put the
882 carbon atoms first, the hydrogen atoms second, and the other atom types
883 sorted alphabetically after that (although for some molecules, the usual
884 order is different: we special-case a few of those.)
887 cmp_atoms (const void *aa, const void *bb)
889 const atom_and_count *a = (atom_and_count *) aa;
890 const atom_and_count *b = (atom_and_count *) bb;
891 if (!a->atom) return 1;
892 if (!b->atom) return -1;
893 if (!strcmp(a->atom, "C")) return -1;
894 if (!strcmp(b->atom, "C")) return 1;
895 if (!strcmp(a->atom, "H")) return -1;
896 if (!strcmp(b->atom, "H")) return 1;
897 return strcmp (a->atom, b->atom);
900 static void special_case_formula (char *f);
903 generate_molecule_formula (molecule *m)
905 char *buf = (char *) malloc (m->natoms * 10);
908 atom_and_count counts[200];
909 memset (counts, 0, sizeof(counts));
911 for (i = 0; i < m->natoms; i++)
914 char *a = (char *) m->atoms[i].label;
916 while (!isalpha(*a)) a++;
918 for (e = a; isalpha(*e); e++);
920 while (counts[j].atom && !!strcmp(a, counts[j].atom))
930 while (counts[i].atom) i++;
931 qsort (counts, i, sizeof(*counts), cmp_atoms);
934 while (counts[i].atom)
936 strcat (s, counts[i].atom);
937 free (counts[i].atom);
939 if (counts[i].count > 1)
940 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
945 special_case_formula (buf);
947 if (!m->label) m->label = strdup("");
948 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
949 strcpy (s, m->label);
952 free ((char *) m->label);
957 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
959 special_case_formula (char *f)
961 if (!strcmp(f, "H[2]Be")) strcpy(f, "BeH[2]");
962 else if (!strcmp(f, "H[3]B")) strcpy(f, "BH[3]");
963 else if (!strcmp(f, "H[3]N")) strcpy(f, "NH[3]");
964 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
965 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
966 else if (!strcmp(f, "H[4]N[2]")) strcpy(f, "N[2]H[4]");
967 else if (!strcmp(f, "Cl[3]P")) strcpy(f, "PCl[3]");
968 else if (!strcmp(f, "Cl[5]P")) strcpy(f, "PCl[5]");
973 insert_vertical_whitespace (char *string)
977 if ((string[0] == ',' ||
981 string[0] = ' ', string[1] = '\n';
987 /* Construct the molecule data from either: the builtins; or from
988 the (one) .pdb file specified with -molecule.
991 load_molecules (ModeInfo *mi)
993 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
994 int wire = MI_IS_WIREFRAME(mi);
998 if (molecule_str && *molecule_str &&
999 strcmp(molecule_str, "(default)")) /* try external PDB files */
1001 /* The -molecule option can point to a .pdb file, or to
1002 a directory of them.
1010 if (!stat (molecule_str, &st) &&
1011 S_ISDIR (st.st_mode))
1015 struct dirent *dentry;
1017 pdb_dir = opendir (molecule_str);
1020 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1026 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1030 files = (char **) calloc (sizeof(*files), list_size);
1032 while ((dentry = readdir (pdb_dir)))
1034 int L = strlen (dentry->d_name);
1035 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1038 if (nfiles >= list_size-1)
1040 list_size = (list_size + 10) * 1.2;
1042 realloc (files, list_size * sizeof(*files));
1046 fprintf (stderr, "%s: out of memory (%d files)\n",
1052 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1054 strcpy (fn, molecule_str);
1055 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1056 strcat (fn, dentry->d_name);
1057 files[nfiles++] = fn;
1059 fprintf (stderr, "%s: file %s\n", progname, fn);
1065 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1066 progname, molecule_str);
1070 files = (char **) malloc (sizeof (*files));
1072 files[0] = strdup (molecule_str);
1074 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1077 mc->nmolecules = nfiles;
1078 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1080 for (i = 0; i < mc->nmolecules; i++)
1083 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1084 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1086 if ((wire || !do_atoms) &&
1088 mc->molecules[molecule_ctr].nbonds == 0)
1090 /* If we're not drawing atoms (e.g., wireframe mode), and
1091 there is no bond info, then make sure labels are turned
1092 on, or we'll be looking at a black screen... */
1093 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1094 progname, files[i]);
1105 mc->nmolecules = molecule_ctr;
1108 if (mc->nmolecules == 0) /* do the builtins if no files */
1110 mc->nmolecules = countof(builtin_pdb_data);
1111 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1112 for (i = 0; i < mc->nmolecules; i++)
1115 sprintf (name, "<builtin-%d>", i);
1116 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1117 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1121 for (i = 0; i < mc->nmolecules; i++)
1123 generate_molecule_formula (&mc->molecules[i]);
1124 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1130 /* Window management, etc
1133 reshape_molecule (ModeInfo *mi, int width, int height)
1135 GLfloat h = (GLfloat) height / (GLfloat) width;
1137 glViewport (0, 0, (GLint) width, (GLint) height);
1139 glMatrixMode(GL_PROJECTION);
1141 gluPerspective (30.0, 1/h, 20.0, 100.0);
1143 glMatrixMode(GL_MODELVIEW);
1145 gluLookAt( 0.0, 0.0, 30.0,
1149 glClear(GL_COLOR_BUFFER_BIT);
1154 gl_init (ModeInfo *mi)
1156 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1157 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1158 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1159 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1160 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1161 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1162 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1163 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1168 startup_blurb (ModeInfo *mi)
1170 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1171 const char *s = "Constructing molecules...";
1172 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1173 mi->xgwa.width, mi->xgwa.height,
1174 10, mi->xgwa.height - 10,
1177 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1181 molecule_handle_event (ModeInfo *mi, XEvent *event)
1183 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1185 if (event->xany.type == ButtonPress &&
1186 event->xbutton.button == Button1)
1188 mc->button_down_p = True;
1189 gltrackball_start (mc->trackball,
1190 event->xbutton.x, event->xbutton.y,
1191 MI_WIDTH (mi), MI_HEIGHT (mi));
1194 else if (event->xany.type == ButtonRelease &&
1195 event->xbutton.button == Button1)
1197 mc->button_down_p = False;
1200 else if (event->xany.type == ButtonPress &&
1201 (event->xbutton.button == Button4 ||
1202 event->xbutton.button == Button5))
1204 gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1205 !!event->xbutton.state);
1208 else if (event->xany.type == KeyPress)
1212 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1214 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1216 GLfloat speed = 4.0;
1218 mc->mode_tick = 10 * speed;
1222 else if (event->xany.type == MotionNotify &&
1225 gltrackball_track (mc->trackball,
1226 event->xmotion.x, event->xmotion.y,
1227 MI_WIDTH (mi), MI_HEIGHT (mi));
1236 init_molecule (ModeInfo *mi)
1238 molecule_configuration *mc;
1242 mcs = (molecule_configuration *)
1243 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1245 fprintf(stderr, "%s: out of memory\n", progname);
1250 mc = &mcs[MI_SCREEN(mi)];
1252 if ((mc->glx_context = init_GL(mi)) != NULL) {
1254 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1260 wire = MI_IS_WIREFRAME(mi);
1263 Bool spinx=False, spiny=False, spinz=False;
1264 double spin_speed = 0.5;
1265 double spin_accel = 0.3;
1266 double wander_speed = 0.01;
1271 if (*s == 'x' || *s == 'X') spinx = True;
1272 else if (*s == 'y' || *s == 'Y') spiny = True;
1273 else if (*s == 'z' || *s == 'Z') spinz = True;
1277 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1284 mc->rot = make_rotator (spinx ? spin_speed : 0,
1285 spiny ? spin_speed : 0,
1286 spinz ? spin_speed : 0,
1288 do_wander ? wander_speed : 0,
1289 (spinx && spiny && spinz));
1290 mc->trackball = gltrackball_init ();
1293 orig_do_labels = do_labels;
1294 orig_do_atoms = do_atoms;
1295 orig_do_bonds = do_bonds;
1296 orig_do_shells = do_shells;
1297 orig_wire = MI_IS_WIREFRAME(mi);
1299 mc->molecule_dlist = glGenLists(1);
1301 mc->shell_dlist = glGenLists(1);
1303 load_molecules (mi);
1304 mc->which = random() % mc->nmolecules;
1306 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1307 "NoLabelThreshold");
1308 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1309 "WireframeThreshold");
1317 /* Put the labels on the atoms.
1318 This can't be a part of the display list because of the games
1319 we play with the translation matrix.
1322 draw_labels (ModeInfo *mi)
1324 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1325 int wire = MI_IS_WIREFRAME(mi);
1326 molecule *m = &mc->molecules[mc->which];
1333 glDisable (GL_LIGHTING); /* don't light fonts */
1335 for (i = 0; i < m->natoms; i++)
1337 molecule_atom *a = &m->atoms[i];
1338 GLfloat size = atom_size (a);
1344 set_atom_color (mi, a, True, 1);
1346 /* First, we translate the origin to the center of the atom.
1348 Then we retrieve the prevailing modelview matrix (which
1349 includes any rotation, wandering, and user-trackball-rolling
1352 We set the top 3x3 cells of that matrix to be the identity
1353 matrix. This removes all rotation from the matrix, while
1354 leaving the translation alone. This has the effect of
1355 leaving the prevailing coordinate system perpendicular to
1356 the camera view: were we to draw a square face, it would
1357 be in the plane of the screen.
1359 Now we translate by `size' toward the viewer -- so that the
1360 origin is *just in front* of the ball.
1362 Then we draw the label text, allowing the depth buffer to
1363 do its work: that way, labels on atoms will be occluded
1364 properly when other atoms move in front of them.
1366 This technique (of neutralizing rotation relative to the
1367 observer, after both rotations and translations have been
1368 applied) is known as "billboarding".
1371 glTranslatef(a->x, a->y, a->z); /* get matrix */
1372 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1373 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1374 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1375 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1376 glLoadIdentity(); /* reset modelview */
1377 glMultMatrixf (&m[0][0]); /* replace with ours */
1379 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1381 glRasterPos3f (0, 0, 0); /* draw text here */
1383 /* Before drawing the string, shift the origin to center
1384 the text over the origin of the sphere. */
1385 glBitmap (0, 0, 0, 0,
1386 -string_width (mc->xfont1, a->label) / 2,
1387 -mc->xfont1->descent,
1390 for (j = 0; j < strlen(a->label); j++)
1391 glCallList (mc->font1_dlist + (int)(a->label[j]));
1396 /* More efficient to always call glEnable() with correct values
1397 than to call glPushAttrib()/glPopAttrib(), since reading
1398 attributes from GL does a round-trip and stalls the pipeline.
1401 glEnable (GL_LIGHTING);
1406 pick_new_molecule (ModeInfo *mi, time_t last)
1408 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1410 if (mc->nmolecules == 1)
1412 if (last != 0) return;
1417 mc->which = random() % mc->nmolecules;
1422 while (n == mc->which)
1423 n = random() % mc->nmolecules;
1429 char *name = strdup (mc->molecules[mc->which].label);
1430 char *s = strpbrk (name, "\r\n");
1432 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1436 mc->polygon_count = 0;
1438 glNewList (mc->molecule_dlist, GL_COMPILE);
1439 ensure_bounding_box_visible (mi);
1441 do_labels = orig_do_labels;
1442 do_atoms = orig_do_atoms;
1443 do_bonds = orig_do_bonds;
1444 do_shells = orig_do_shells;
1445 MI_IS_WIREFRAME(mi) = orig_wire;
1447 if (mc->molecule_size > mc->no_label_threshold)
1449 if (mc->molecule_size > mc->wireframe_threshold)
1450 MI_IS_WIREFRAME(mi) = 1;
1452 if (MI_IS_WIREFRAME(mi))
1453 do_bonds = 1, do_shells = 0;
1458 if (! (do_bonds || do_atoms || do_labels))
1460 /* Make sure *something* shows up! */
1461 MI_IS_WIREFRAME(mi) = 1;
1465 build_molecule (mi, False);
1470 glNewList (mc->shell_dlist, GL_COMPILE);
1471 ensure_bounding_box_visible (mi);
1477 build_molecule (mi, True);
1480 do_bonds = orig_do_bonds;
1481 do_atoms = orig_do_atoms;
1482 do_labels = orig_do_labels;
1488 draw_molecule (ModeInfo *mi)
1490 static time_t last = 0;
1491 time_t now = time ((time_t *) 0);
1492 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1494 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1495 Display *dpy = MI_DISPLAY(mi);
1496 Window window = MI_WINDOW(mi);
1498 if (!mc->glx_context)
1503 pick_new_molecule (mi, last);
1506 else if (mc->mode == 0)
1508 static int tick = 0;
1511 time_t now = time((time_t *) 0);
1512 if (last == 0) last = now;
1515 if (!mc->button_down_p &&
1516 mc->nmolecules > 1 &&
1517 last + timeout <= now)
1519 /* randomize molecules every -timeout seconds */
1520 mc->mode = 1; /* go out */
1521 mc->mode_tick = 10 * speed;
1526 else if (mc->mode == 1) /* out */
1528 if (--mc->mode_tick <= 0)
1530 mc->mode_tick = 10 * speed;
1531 mc->mode = 2; /* go in */
1532 pick_new_molecule (mi, last);
1536 else if (mc->mode == 2) /* in */
1538 if (--mc->mode_tick <= 0)
1539 mc->mode = 0; /* normal */
1545 glScalef(1.1, 1.1, 1.1);
1549 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1550 glTranslatef((x - 0.5) * 9,
1554 gltrackball_rotate (mc->trackball);
1556 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1557 glRotatef (x * 360, 1.0, 0.0, 0.0);
1558 glRotatef (y * 360, 0.0, 1.0, 0.0);
1559 glRotatef (z * 360, 0.0, 0.0, 1.0);
1562 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1566 GLfloat s = (mc->mode == 1
1567 ? mc->mode_tick / (10 * speed)
1568 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1573 glCallList (mc->molecule_dlist);
1577 molecule *m = &mc->molecules[mc->which];
1581 /* This can't go in the display list, or the characters are spaced
1582 wrongly when the window is resized. */
1583 if (do_titles && m->label && *m->label)
1585 set_atom_color (mi, 0, True, 1);
1586 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1587 mi->xgwa.width, mi->xgwa.height,
1588 10, mi->xgwa.height - 10,
1596 glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1598 glCallList (mc->shell_dlist);
1600 glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1602 glDepthFunc (GL_EQUAL);
1603 glEnable (GL_BLEND);
1604 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1606 glCallList (mc->shell_dlist);
1608 glDepthFunc (GL_LESS);
1609 glDisable (GL_BLEND);
1614 mi->polygon_count = mc->polygon_count;
1616 if (mi->fps_p) do_fps (mi);
1619 glXSwapBuffers(dpy, window);