1 /* molecule, Copyright (c) 2001-2004 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_BBOX "False"
43 #define DEF_MOLECULE "(default)"
44 #define DEF_VERBOSE "False"
46 #define DEFAULTS "*delay: 10000 \n" \
47 "*timeout: " DEF_TIMEOUT "\n" \
48 "*showFPS: False \n" \
49 "*wireframe: False \n" \
50 "*verbose: " DEF_VERBOSE "\n" \
51 "*molecule: " DEF_MOLECULE "\n" \
52 "*spin: " DEF_SPIN "\n" \
53 "*wander: " DEF_WANDER "\n" \
54 "*labels: " DEF_LABELS "\n" \
55 "*atoms: " DEF_ATOMS "\n" \
56 "*bonds: " DEF_BONDS "\n" \
57 "*bbox: " DEF_BBOX "\n" \
58 "*atomFont: -*-times-bold-r-normal-*-240-*\n" \
59 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
60 "*noLabelThreshold: 30 \n" \
61 "*wireframeThreshold: 150 \n" \
65 #define countof(x) (sizeof((x))/sizeof((*x)))
67 #include "xlockmore.h"
73 #include "gltrackball.h"
75 #ifdef USE_GL /* whole file */
83 #define SPHERE_SLICES 24 /* how densely to render spheres */
84 #define SPHERE_STACKS 12
86 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
89 # define TUBE_FACES 12 /* how densely to render tubes */
94 static int scale_down;
95 #define SPHERE_SLICES_2 7
96 #define SPHERE_STACKS_2 4
97 #define TUBE_FACES_2 3
101 __extension__ /* don't warn about "string length is greater than the length
102 ISO C89 compilers are required to support" when includng
103 the following data file... */
105 const char * const builtin_pdb_data[] = {
106 # include "molecules.h"
114 const char *text_color;
119 /* These are the traditional colors used to render these atoms,
120 and their approximate size in angstroms.
122 static atom_data all_atom_data[] = {
123 { "H", 1.17, 0, "White", "Grey70", { 0, }},
124 { "C", 1.75, 0, "Grey60", "White", { 0, }},
125 { "CA", 1.80, 0, "Blue", "LightBlue", { 0, }},
126 { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }},
127 { "O", 1.40, 0, "Red", "LightPink", { 0, }},
128 { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }},
129 { "S", 1.80, 0, "Yellow4", "Yellow1", { 0, }},
130 { "bond", 0, 0, "Grey70", "Yellow1", { 0, }},
131 { "*", 1.40, 0, "Green4", "LightGreen", { 0, }}
136 int id; /* sequence number in the PDB file */
137 const char *label; /* The atom name */
138 GLfloat x, y, z; /* position in 3-space (angstroms) */
139 atom_data *data; /* computed: which style of atom this is */
143 int from, to; /* atom sequence numbers */
144 int strength; /* how many bonds are between these two atoms */
149 const char *label; /* description of this compound */
150 int natoms, atoms_size;
151 int nbonds, bonds_size;
152 molecule_atom *atoms;
153 molecule_bond *bonds;
158 GLXContext *glx_context;
160 trackball_state *trackball;
163 GLfloat molecule_size; /* max dimension of molecule bounding box */
165 GLfloat no_label_threshold; /* Things happen when molecules are huge */
166 GLfloat wireframe_threshold;
168 int which; /* which of the molecules is being shown */
172 int mode; /* 0 = normal, 1 = out, 2 = in */
175 GLuint molecule_dlist;
177 XFontStruct *xfont1, *xfont2;
178 GLuint font1_dlist, font2_dlist;
180 } molecule_configuration;
183 static molecule_configuration *mcs = NULL;
186 static char *molecule_str;
187 static char *do_spin;
188 static Bool do_wander;
189 static Bool do_titles;
190 static Bool do_labels;
191 static Bool do_atoms;
192 static Bool do_bonds;
194 static Bool verbose_p;
196 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
199 static XrmOptionDescRec opts[] = {
200 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
201 { "-timeout",".timeout",XrmoptionSepArg, 0 },
202 { "-spin", ".spin", XrmoptionSepArg, 0 },
203 { "+spin", ".spin", XrmoptionNoArg, "" },
204 { "-wander", ".wander", XrmoptionNoArg, "True" },
205 { "+wander", ".wander", XrmoptionNoArg, "False" },
206 { "-labels", ".labels", XrmoptionNoArg, "True" },
207 { "+labels", ".labels", XrmoptionNoArg, "False" },
208 { "-titles", ".titles", XrmoptionNoArg, "True" },
209 { "+titles", ".titles", XrmoptionNoArg, "False" },
210 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
211 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
212 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
213 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
214 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
215 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
216 { "-verbose",".verbose",XrmoptionNoArg, "True" },
219 static argtype vars[] = {
220 {&molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
221 {&timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
222 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
223 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
224 {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
225 {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
226 {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
227 {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
228 {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
229 {&verbose_p, "verbose","Verbose",DEF_VERBOSE,t_Bool},
232 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
240 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
242 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
243 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
246 glTranslatef (x, y, z);
247 glScalef (diameter, diameter, diameter);
248 unit_sphere (stacks, slices, wire);
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);
263 get_atom_data (const char *atom_name)
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, molecule_atom *a, Bool font_p)
298 static atom_data *def_data = 0;
299 if (!def_data) def_data = get_atom_data ("bond");
303 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
305 if (gl_color[3] == 0)
307 const char *string = !font_p ? d->color : d->text_color;
309 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
311 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
312 (a ? a->label : d->name), string);
316 gl_color[0] = xcolor.red / 65536.0;
317 gl_color[1] = xcolor.green / 65536.0;
318 gl_color[2] = xcolor.blue / 65536.0;
323 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
325 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
330 atom_size (molecule_atom *a)
334 if (a->data->size2 == 0)
336 /* let the molecules have the same relative sizes, but scale
337 them to a smaller range, so that the bond-tubes are
344 GLfloat ratio = (a->data->size - min) / (max - min);
345 a->data->size2 = bot + (ratio * (top - bot));
347 return a->data->size2;
350 return a->data->size;
354 static molecule_atom *
355 get_atom (molecule_atom *atoms, int natoms, int id)
359 /* quick short-circuit */
362 if (atoms[id].id == id)
364 if (id > 0 && atoms[id-1].id == id)
366 if (id < natoms-1 && atoms[id+1].id == id)
370 for (i = 0; i < natoms; i++)
371 if (id == atoms[i].id)
374 fprintf (stderr, "%s: no atom %d\n", progname, id);
380 molecule_bounding_box (ModeInfo *mi,
381 GLfloat *x1, GLfloat *y1, GLfloat *z1,
382 GLfloat *x2, GLfloat *y2, GLfloat *z2)
384 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
385 molecule *m = &mc->molecules[mc->which];
390 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
394 *x1 = *x2 = m->atoms[0].x;
395 *y1 = *y2 = m->atoms[0].y;
396 *z1 = *z2 = m->atoms[0].z;
399 for (i = 1; i < m->natoms; i++)
401 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
402 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
403 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
405 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
406 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
407 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
420 draw_bounding_box (ModeInfo *mi)
422 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
423 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
424 int wire = MI_IS_WIREFRAME(mi);
425 GLfloat x1, y1, z1, x2, y2, z2;
426 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
428 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
431 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
433 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
434 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
436 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
437 glNormal3f(0, -1, 0);
438 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
439 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
441 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
443 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
444 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
446 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
447 glNormal3f(0, 0, -1);
448 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
449 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
451 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
453 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
454 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
456 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
457 glNormal3f(-1, 0, 0);
458 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
459 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
462 glPushAttrib (GL_LIGHTING);
463 glDisable (GL_LIGHTING);
465 glColor3f (c2[0], c2[1], c2[2]);
467 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
468 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
469 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
470 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
471 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
472 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
479 /* Since PDB files don't always have the molecule centered around the
480 origin, and since some molecules are pretty large, scale and/or
481 translate so that the whole molecule is visible in the window.
484 ensure_bounding_box_visible (ModeInfo *mi)
486 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
488 GLfloat x1, y1, z1, x2, y2, z2;
491 GLfloat max_size = 10; /* don't bother scaling down if the molecule
492 is already smaller than this */
494 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
499 size = (w > h ? w : h);
500 size = (size > d ? size : d);
502 mc->molecule_size = size;
508 GLfloat scale = max_size / size;
509 glScalef (scale, scale, scale);
511 scale_down = scale < 0.3;
514 glTranslatef (-(x1 + w/2),
521 /* Constructs the GL shapes of the current molecule
524 build_molecule (ModeInfo *mi)
526 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
527 int wire = MI_IS_WIREFRAME(mi);
530 molecule *m = &mc->molecules[mc->which];
534 glDisable(GL_CULL_FACE);
535 glDisable(GL_LIGHTING);
536 glDisable(GL_LIGHT0);
537 glDisable(GL_DEPTH_TEST);
538 glDisable(GL_NORMALIZE);
539 glDisable(GL_CULL_FACE);
543 glEnable(GL_CULL_FACE);
544 glEnable(GL_LIGHTING);
546 glEnable(GL_DEPTH_TEST);
547 glEnable(GL_NORMALIZE);
548 glEnable(GL_CULL_FACE);
552 set_atom_color (mi, 0, False);
555 for (i = 0; i < m->nbonds; i++)
557 molecule_bond *b = &m->bonds[i];
558 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
559 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
564 glVertex3f(from->x, from->y, from->z);
565 glVertex3f(to->x, to->y, to->z);
570 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
576 GLfloat thickness = 0.07 * b->strength;
577 GLfloat cap_size = 0.03;
581 tube (from->x, from->y, from->z,
584 faces, smooth, False, wire);
588 if (!wire && do_atoms)
589 for (i = 0; i < m->natoms; i++)
591 molecule_atom *a = &m->atoms[i];
592 GLfloat size = atom_size (a);
593 set_atom_color (mi, a, False);
594 sphere (a->x, a->y, a->z, size, wire);
598 draw_bounding_box (mi);
600 if (do_titles && m->label && *m->label)
602 set_atom_color (mi, 0, True);
603 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
604 mi->xgwa.width, mi->xgwa.height,
605 10, mi->xgwa.height - 10,
615 push_atom (molecule *m,
616 int id, const char *label,
617 GLfloat x, GLfloat y, GLfloat z)
620 if (m->atoms_size < m->natoms)
623 m->atoms = (molecule_atom *) realloc (m->atoms,
624 m->atoms_size * sizeof(*m->atoms));
626 m->atoms[m->natoms-1].id = id;
627 m->atoms[m->natoms-1].label = label;
628 m->atoms[m->natoms-1].x = x;
629 m->atoms[m->natoms-1].y = y;
630 m->atoms[m->natoms-1].z = z;
631 m->atoms[m->natoms-1].data = get_atom_data (label);
636 push_bond (molecule *m, int from, int to)
640 for (i = 0; i < m->nbonds; i++)
641 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
642 (m->bonds[i].to == from && m->bonds[i].from == to))
644 m->bonds[i].strength++;
649 if (m->bonds_size < m->nbonds)
652 m->bonds = (molecule_bond *) realloc (m->bonds,
653 m->bonds_size * sizeof(*m->bonds));
655 m->bonds[m->nbonds-1].from = from;
656 m->bonds[m->nbonds-1].to = to;
657 m->bonds[m->nbonds-1].strength = 1;
662 /* This function is crap.
665 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
667 const char *s = data;
671 if ((!m->label || !*m->label) &&
672 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
674 char *name = calloc (1, 100);
681 while (isspace(*n2)) n2++;
683 ss = strchr (n2, '\n');
685 ss = strchr (n2, '\r');
688 ss = n2+strlen(n2)-1;
689 while (isspace(*ss) && ss > n2)
692 if (strlen (n2) > 4 &&
693 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
694 n2[strlen(n2)-4] = 0;
696 if (m->label) free ((char *) m->label);
697 m->label = strdup (n2);
700 else if (!strncmp (s, "TITLE ", 6) ||
701 !strncmp (s, "HEADER", 6) ||
702 !strncmp (s, "COMPND", 6) ||
703 !strncmp (s, "AUTHOR", 6) ||
704 !strncmp (s, "REVDAT", 6) ||
705 !strncmp (s, "SOURCE", 6) ||
706 !strncmp (s, "EXPDTA", 6) ||
707 !strncmp (s, "JRNL ", 6) ||
708 !strncmp (s, "REMARK", 6) ||
709 !strncmp (s, "SEQRES", 6) ||
710 !strncmp (s, "HET ", 6) ||
711 !strncmp (s, "FORMUL", 6) ||
712 !strncmp (s, "CRYST1", 6) ||
713 !strncmp (s, "ORIGX1", 6) ||
714 !strncmp (s, "ORIGX2", 6) ||
715 !strncmp (s, "ORIGX3", 6) ||
716 !strncmp (s, "SCALE1", 6) ||
717 !strncmp (s, "SCALE2", 6) ||
718 !strncmp (s, "SCALE3", 6) ||
719 !strncmp (s, "MASTER", 6) ||
720 !strncmp (s, "KEYWDS", 6) ||
721 !strncmp (s, "DBREF ", 6) ||
722 !strncmp (s, "HETNAM", 6) ||
723 !strncmp (s, "HETSYN", 6) ||
724 !strncmp (s, "HELIX ", 6) ||
725 !strncmp (s, "LINK ", 6) ||
726 !strncmp (s, "MTRIX1", 6) ||
727 !strncmp (s, "MTRIX2", 6) ||
728 !strncmp (s, "MTRIX3", 6) ||
729 !strncmp (s, "SHEET ", 6) ||
730 !strncmp (s, "CISPEP", 6) ||
731 !strncmp (s, "GENERATED BY", 12) ||
732 !strncmp (s, "TER ", 4) ||
733 !strncmp (s, "END ", 4) ||
734 !strncmp (s, "TER\n", 4) ||
735 !strncmp (s, "END\n", 4) ||
736 !strncmp (s, "\n", 1))
739 else if (!strncmp (s, "ATOM ", 7))
742 char *name = (char *) calloc (1, 4);
743 GLfloat x = -999, y = -999, z = -999;
745 sscanf (s+7, " %d ", &id);
747 strncpy (name, s+12, 3);
748 while (isspace(*name)) name++;
749 ss = name + strlen(name)-1;
750 while (isspace(*ss) && ss > name)
758 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
760 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
761 progname, filename, line,
764 push_atom (m, id, name, x, y, z);
766 else if (!strncmp (s, "HETATM ", 7))
769 char *name = (char *) calloc (1, 4);
770 GLfloat x = -999, y = -999, z = -999;
772 sscanf (s+7, " %d ", &id);
774 strncpy (name, s+12, 3);
775 while (isspace(*name)) name++;
776 ss = name + strlen(name)-1;
777 while (isspace(*ss) && ss > name)
779 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
781 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
782 progname, filename, line,
785 push_atom (m, id, name, x, y, z);
787 else if (!strncmp (s, "CONECT ", 7))
790 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ",
791 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
792 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
793 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
795 for (j = 1; j < i; j++)
799 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
800 progname, filename, line, atoms[0], atoms[j]);
802 push_bond (m, atoms[0], atoms[j]);
807 char *s1 = strdup (s);
808 for (ss = s1; *ss && *ss != '\n'; ss++)
811 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
812 progname, filename, line, s1);
815 while (*s && *s != '\n')
825 parse_pdb_file (molecule *m, const char *name)
828 int buf_size = 40960;
832 in = fopen(name, "r");
835 char *buf = (char *) malloc(1024 + strlen(name));
836 sprintf(buf, "%s: error reading \"%s\"", progname, name);
841 buf = (char *) malloc (buf_size);
843 while (fgets (buf, buf_size-1, in))
846 for (s = buf; *s; s++)
847 if (*s == '\r') *s = '\n';
848 parse_pdb_data (m, buf, name, line++);
856 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
861 if (!m->nbonds && do_bonds)
863 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
872 typedef struct { char *atom; int count; } atom_and_count;
874 /* When listing the components of a molecule, the convention is to put the
875 carbon atoms first, the hydrogen atoms second, and the other atom types
876 sorted alphabetically after that (although for some molecules, the usual
877 order is different: we special-case a few of those.)
880 cmp_atoms (const void *aa, const void *bb)
882 const atom_and_count *a = (atom_and_count *) aa;
883 const atom_and_count *b = (atom_and_count *) bb;
884 if (!a->atom) return 1;
885 if (!b->atom) return -1;
886 if (!strcmp(a->atom, "C")) return -1;
887 if (!strcmp(b->atom, "C")) return 1;
888 if (!strcmp(a->atom, "H")) return -1;
889 if (!strcmp(b->atom, "H")) return 1;
890 return strcmp (a->atom, b->atom);
893 static void special_case_formula (char *f);
896 generate_molecule_formula (molecule *m)
898 char *buf = (char *) malloc (m->natoms * 10);
901 atom_and_count counts[200];
902 memset (counts, 0, sizeof(counts));
904 for (i = 0; i < m->natoms; i++)
907 char *a = (char *) m->atoms[i].label;
909 while (!isalpha(*a)) a++;
911 for (e = a; isalpha(*e); e++);
913 while (counts[j].atom && !!strcmp(a, counts[j].atom))
923 while (counts[i].atom) i++;
924 qsort (counts, i, sizeof(*counts), cmp_atoms);
927 while (counts[i].atom)
929 strcat (s, counts[i].atom);
930 free (counts[i].atom);
932 if (counts[i].count > 1)
933 sprintf (s, "[%d]", counts[i].count); /* use [] to get subscripts */
938 special_case_formula (buf);
940 if (!m->label) m->label = strdup("");
941 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
942 strcpy (s, m->label);
945 free ((char *) m->label);
950 /* thanks to Rene Uittenbogaard <ruittenb@wish.nl> */
952 special_case_formula (char *f)
954 if (!strcmp(f, "H(2)Be")) strcpy(f, "BeH(2)");
955 else if (!strcmp(f, "H(3)B")) strcpy(f, "BH(3)");
956 else if (!strcmp(f, "H(3)N")) strcpy(f, "NH(3)");
957 else if (!strcmp(f, "CHN")) strcpy(f, "HCN");
958 else if (!strcmp(f, "CKN")) strcpy(f, "KCN");
959 else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)");
960 else if (!strcmp(f, "Cl(3)P")) strcpy(f, "PCl(3)");
961 else if (!strcmp(f, "Cl(5)P")) strcpy(f, "PCl(5)");
966 insert_vertical_whitespace (char *string)
970 if ((string[0] == ',' ||
974 string[0] = ' ', string[1] = '\n';
980 /* Construct the molecule data from either: the builtins; or from
981 the (one) .pdb file specified with -molecule.
984 load_molecules (ModeInfo *mi)
986 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
987 int wire = MI_IS_WIREFRAME(mi);
991 if (molecule_str && *molecule_str &&
992 strcmp(molecule_str, "(default)")) /* try external PDB files */
994 /* The -molecule option can point to a .pdb file, or to
1003 if (!stat (molecule_str, &st) &&
1004 S_ISDIR (st.st_mode))
1008 struct dirent *dentry;
1010 pdb_dir = opendir (molecule_str);
1013 sprintf (buf, "%.100s: %.100s", progname, molecule_str);
1019 fprintf (stderr, "%s: directory %s\n", progname, molecule_str);
1023 files = (char **) calloc (sizeof(*files), list_size);
1025 while ((dentry = readdir (pdb_dir)))
1027 int L = strlen (dentry->d_name);
1028 if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb"))
1031 if (nfiles >= list_size-1)
1033 list_size = (list_size + 10) * 1.2;
1035 realloc (files, list_size * sizeof(*files));
1039 fprintf (stderr, "%s: out of memory (%d files)\n",
1045 fn = (char *) malloc (strlen (molecule_str) + L + 10);
1047 strcpy (fn, molecule_str);
1048 if (fn[strlen(fn)-1] != '/') strcat (fn, "/");
1049 strcat (fn, dentry->d_name);
1050 files[nfiles++] = fn;
1052 fprintf (stderr, "%s: file %s\n", progname, fn);
1058 fprintf (stderr, "%s: no .pdb files in directory %s\n",
1059 progname, molecule_str);
1063 files = (char **) malloc (sizeof (*files));
1065 files[0] = strdup (molecule_str);
1067 fprintf (stderr, "%s: file %s\n", progname, molecule_str);
1070 mc->nmolecules = nfiles;
1071 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1073 for (i = 0; i < mc->nmolecules; i++)
1076 fprintf (stderr, "%s: reading %s\n", progname, files[i]);
1077 if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i]))
1079 if ((wire || !do_atoms) &&
1081 mc->molecules[molecule_ctr].nbonds == 0)
1083 /* If we're not drawing atoms (e.g., wireframe mode), and
1084 there is no bond info, then make sure labels are turned
1085 on, or we'll be looking at a black screen... */
1086 fprintf (stderr, "%s: %s: no bonds: turning -label on.\n",
1087 progname, files[i]);
1098 mc->nmolecules = molecule_ctr;
1101 if (mc->nmolecules == 0) /* do the builtins if no files */
1103 mc->nmolecules = countof(builtin_pdb_data);
1104 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1105 for (i = 0; i < mc->nmolecules; i++)
1108 sprintf (name, "<builtin-%d>", i);
1109 if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name);
1110 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1114 for (i = 0; i < mc->nmolecules; i++)
1116 generate_molecule_formula (&mc->molecules[i]);
1117 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1123 /* Window management, etc
1126 reshape_molecule (ModeInfo *mi, int width, int height)
1128 GLfloat h = (GLfloat) height / (GLfloat) width;
1130 glViewport (0, 0, (GLint) width, (GLint) height);
1132 glMatrixMode(GL_PROJECTION);
1134 gluPerspective (30.0, 1/h, 20.0, 40.0);
1136 glMatrixMode(GL_MODELVIEW);
1138 gluLookAt( 0.0, 0.0, 30.0,
1142 glClear(GL_COLOR_BUFFER_BIT);
1147 gl_init (ModeInfo *mi)
1149 static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
1150 static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1151 static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
1152 static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
1153 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1154 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1155 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1156 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1158 orig_do_labels = do_labels;
1159 orig_do_bonds = do_bonds;
1160 orig_wire = MI_IS_WIREFRAME(mi);
1165 startup_blurb (ModeInfo *mi)
1167 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1168 const char *s = "Constructing molecules...";
1169 print_gl_string (mi->dpy, mc->xfont2, mc->font2_dlist,
1170 mi->xgwa.width, mi->xgwa.height,
1171 10, mi->xgwa.height - 10,
1174 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1178 molecule_handle_event (ModeInfo *mi, XEvent *event)
1180 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1182 if (event->xany.type == ButtonPress &&
1183 event->xbutton.button == Button1)
1185 mc->button_down_p = True;
1186 gltrackball_start (mc->trackball,
1187 event->xbutton.x, event->xbutton.y,
1188 MI_WIDTH (mi), MI_HEIGHT (mi));
1191 else if (event->xany.type == ButtonRelease &&
1192 event->xbutton.button == Button1)
1194 mc->button_down_p = False;
1197 else if (event->xany.type == ButtonPress &&
1198 (event->xbutton.button == Button4 ||
1199 event->xbutton.button == Button5))
1201 gltrackball_mousewheel (mc->trackball, event->xbutton.button, 10,
1202 !!event->xbutton.state);
1205 else if (event->xany.type == KeyPress)
1209 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1211 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1213 GLfloat speed = 4.0;
1215 mc->mode_tick = 10 * speed;
1219 else if (event->xany.type == MotionNotify &&
1222 gltrackball_track (mc->trackball,
1223 event->xmotion.x, event->xmotion.y,
1224 MI_WIDTH (mi), MI_HEIGHT (mi));
1233 init_molecule (ModeInfo *mi)
1235 molecule_configuration *mc;
1239 mcs = (molecule_configuration *)
1240 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1242 fprintf(stderr, "%s: out of memory\n", progname);
1247 mc = &mcs[MI_SCREEN(mi)];
1249 if ((mc->glx_context = init_GL(mi)) != NULL) {
1251 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1257 wire = MI_IS_WIREFRAME(mi);
1260 Bool spinx=False, spiny=False, spinz=False;
1261 double spin_speed = 0.5;
1262 double spin_accel = 0.3;
1263 double wander_speed = 0.01;
1268 if (*s == 'x' || *s == 'X') spinx = True;
1269 else if (*s == 'y' || *s == 'Y') spiny = True;
1270 else if (*s == 'z' || *s == 'Z') spinz = True;
1274 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1281 mc->rot = make_rotator (spinx ? spin_speed : 0,
1282 spiny ? spin_speed : 0,
1283 spinz ? spin_speed : 0,
1285 do_wander ? wander_speed : 0,
1286 (spinx && spiny && spinz));
1287 mc->trackball = gltrackball_init ();
1290 mc->molecule_dlist = glGenLists(1);
1292 load_molecules (mi);
1293 mc->which = random() % mc->nmolecules;
1295 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1296 "NoLabelThreshold");
1297 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1298 "WireframeThreshold");
1306 /* Put the labels on the atoms.
1307 This can't be a part of the display list because of the games
1308 we play with the translation matrix.
1311 draw_labels (ModeInfo *mi)
1313 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1314 int wire = MI_IS_WIREFRAME(mi);
1315 molecule *m = &mc->molecules[mc->which];
1322 glDisable (GL_LIGHTING); /* don't light fonts */
1324 for (i = 0; i < m->natoms; i++)
1326 molecule_atom *a = &m->atoms[i];
1327 GLfloat size = atom_size (a);
1333 set_atom_color (mi, a, True);
1335 /* First, we translate the origin to the center of the atom.
1337 Then we retrieve the prevailing modelview matrix (which
1338 includes any rotation, wandering, and user-trackball-rolling
1341 We set the top 3x3 cells of that matrix to be the identity
1342 matrix. This removes all rotation from the matrix, while
1343 leaving the translation alone. This has the effect of
1344 leaving the prevailing coordinate system perpendicular to
1345 the camera view: were we to draw a square face, it would
1346 be in the plane of the screen.
1348 Now we translate by `size' toward the viewer -- so that the
1349 origin is *just in front* of the ball.
1351 Then we draw the label text, allowing the depth buffer to
1352 do its work: that way, labels on atoms will be occluded
1353 properly when other atoms move in front of them.
1355 This technique (of neutralizing rotation relative to the
1356 observer, after both rotations and translations have been
1357 applied) is known as "billboarding".
1360 glTranslatef(a->x, a->y, a->z); /* get matrix */
1361 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
1362 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1363 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1364 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1365 glLoadIdentity(); /* reset modelview */
1366 glMultMatrixf (&m[0][0]); /* replace with ours */
1368 glTranslatef (0, 0, (size * 1.1)); /* move toward camera */
1370 glRasterPos3f (0, 0, 0); /* draw text here */
1372 /* Before drawing the string, shift the origin to center
1373 the text over the origin of the sphere. */
1374 glBitmap (0, 0, 0, 0,
1375 -string_width (mc->xfont1, a->label) / 2,
1376 -mc->xfont1->descent,
1379 for (j = 0; j < strlen(a->label); j++)
1380 glCallList (mc->font1_dlist + (int)(a->label[j]));
1385 /* More efficient to always call glEnable() with correct values
1386 than to call glPushAttrib()/glPopAttrib(), since reading
1387 attributes from GL does a round-trip and stalls the pipeline.
1390 glEnable (GL_LIGHTING);
1395 pick_new_molecule (ModeInfo *mi, time_t last)
1397 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1399 if (mc->nmolecules == 1)
1401 if (last != 0) return;
1406 mc->which = random() % mc->nmolecules;
1411 while (n == mc->which)
1412 n = random() % mc->nmolecules;
1418 char *name = strdup (mc->molecules[mc->which].label);
1419 char *s = strpbrk (name, "\r\n");
1421 fprintf (stderr, "%s: drawing %s (%d)\n", progname, name, mc->which);
1425 glNewList (mc->molecule_dlist, GL_COMPILE);
1426 ensure_bounding_box_visible (mi);
1428 do_labels = orig_do_labels;
1429 do_bonds = orig_do_bonds;
1430 MI_IS_WIREFRAME(mi) = orig_wire;
1432 if (mc->molecule_size > mc->no_label_threshold)
1434 if (mc->molecule_size > mc->wireframe_threshold)
1435 MI_IS_WIREFRAME(mi) = 1;
1437 if (MI_IS_WIREFRAME(mi))
1440 build_molecule (mi);
1446 draw_molecule (ModeInfo *mi)
1448 static time_t last = 0;
1449 time_t now = time ((time_t *) 0);
1450 GLfloat speed = 4.0; /* speed at which the zoom out/in happens */
1452 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1453 Display *dpy = MI_DISPLAY(mi);
1454 Window window = MI_WINDOW(mi);
1456 if (!mc->glx_context)
1461 pick_new_molecule (mi, last);
1464 else if (mc->mode == 0)
1466 static int tick = 0;
1469 time_t now = time((time_t *) 0);
1470 if (last == 0) last = now;
1473 if (!mc->button_down_p &&
1474 mc->nmolecules > 1 &&
1475 last + timeout <= now)
1477 /* randomize molecules every -timeout seconds */
1478 mc->mode = 1; /* go out */
1479 mc->mode_tick = 10 * speed;
1484 else if (mc->mode == 1) /* out */
1486 if (--mc->mode_tick <= 0)
1488 mc->mode_tick = 10 * speed;
1489 mc->mode = 2; /* go in */
1490 pick_new_molecule (mi, last);
1494 else if (mc->mode == 2) /* in */
1496 if (--mc->mode_tick <= 0)
1497 mc->mode = 0; /* normal */
1503 glScalef(1.1, 1.1, 1.1);
1507 get_position (mc->rot, &x, &y, &z, !mc->button_down_p);
1508 glTranslatef((x - 0.5) * 9,
1512 gltrackball_rotate (mc->trackball);
1514 get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p);
1515 glRotatef (x * 360, 1.0, 0.0, 0.0);
1516 glRotatef (y * 360, 0.0, 1.0, 0.0);
1517 glRotatef (z * 360, 0.0, 0.0, 1.0);
1520 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1524 GLfloat s = (mc->mode == 1
1525 ? mc->mode_tick / (10 * speed)
1526 : ((10 * speed) - mc->mode_tick + 1) / (10 * speed));
1530 glCallList (mc->molecule_dlist);
1537 if (mi->fps_p) do_fps (mi);
1540 glXSwapBuffers(dpy, window);