1 /* molecule, Copyright (c) 2001 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
22 - I'm not sure the text labels are being done in the best way;
23 they are sometimes, but not always, occluded by spheres that
24 pass in front of them.
27 #include <X11/Intrinsic.h>
29 #define PROGCLASS "Molecule"
30 #define HACK_INIT init_molecule
31 #define HACK_DRAW draw_molecule
32 #define HACK_RESHAPE reshape_molecule
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)"
45 #define DEFAULTS "*delay: 10000 \n" \
46 "*timeout: " DEF_TIMEOUT "\n" \
47 "*showFPS: False \n" \
48 "*wireframe: False \n" \
49 "*molecule: " DEF_MOLECULE "\n" \
50 "*spin: " DEF_SPIN "\n" \
51 "*wander: " DEF_WANDER "\n" \
52 "*labels: " DEF_LABELS "\n" \
53 "*atoms: " DEF_ATOMS "\n" \
54 "*bonds: " DEF_BONDS "\n" \
55 "*bbox: " DEF_BBOX "\n" \
56 "*atomFont: -*-times-bold-r-normal-*-240-*\n" \
57 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
58 "*noLabelThreshold: 30 \n" \
59 "*wireframeThreshold: 150 \n" \
63 #define countof(x) (sizeof((x))/sizeof((*x)))
65 #include "xlockmore.h"
70 #ifdef USE_GL /* whole file */
75 #define SPHERE_SLICES 16 /* how densely to render spheres */
76 #define SPHERE_STACKS 10
78 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
81 # define TUBE_FACES 12 /* how densely to render tubes */
86 static int scale_down;
87 #define SPHERE_SLICES_2 7
88 #define SPHERE_STACKS_2 4
89 #define TUBE_FACES_2 3
92 const char * const builtin_pdb_data[] = {
93 # include "molecules.h"
101 const char *text_color;
106 /* These are the traditional colors used to render these atoms,
107 and their approximate size in angstroms.
109 static atom_data all_atom_data[] = {
110 { "H", 1.17, 0, "White", "Grey70", { 0, }},
111 { "C", 1.75, 0, "Grey60", "White", { 0, }},
112 { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }},
113 { "O", 1.40, 0, "Red", "LightPink", { 0, }},
114 { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }},
115 { "S", 1.80, 0, "Yellow4", "Yellow1", { 0, }},
116 { "bond", 0, 0, "Grey70", "Yellow1", { 0, }},
117 { "*", 1.40, 0, "Green4", "LightGreen", { 0, }}
122 int id; /* sequence number in the PDB file */
123 const char *label; /* The atom name */
124 GLfloat x, y, z; /* position in 3-space (angstroms) */
125 atom_data *data; /* computed: which style of atom this is */
129 int from, to; /* atom sequence numbers */
130 int strength; /* how many bonds are between these two atoms */
135 const char *label; /* description of this compound */
136 int natoms, atoms_size;
137 int nbonds, bonds_size;
138 molecule_atom *atoms;
139 molecule_bond *bonds;
144 GLXContext *glx_context;
146 GLfloat rotx, roty, rotz; /* current object rotation */
147 GLfloat dx, dy, dz; /* current rotational velocity */
148 GLfloat ddx, ddy, ddz; /* current rotational acceleration */
149 GLfloat d_max; /* max velocity */
151 Bool spin_x, spin_y, spin_z;
153 GLfloat molecule_size; /* max dimension of molecule bounding box */
155 GLfloat no_label_threshold; /* Things happen when molecules are huge */
156 GLfloat wireframe_threshold;
158 int which; /* which of the molecules is being shown */
162 GLuint molecule_dlist;
164 XFontStruct *xfont1, *xfont2;
165 GLuint font1_dlist, font2_dlist;
167 } molecule_configuration;
170 static molecule_configuration *mcs = NULL;
173 static char *molecule_str;
174 static char *do_spin;
175 static Bool do_wander;
176 static Bool do_titles;
177 static Bool do_labels;
178 static Bool do_atoms;
179 static Bool do_bonds;
182 static Bool orig_do_labels, orig_do_bonds, orig_wire; /* saved to reset */
185 static XrmOptionDescRec opts[] = {
186 { "-molecule", ".molecule", XrmoptionSepArg, 0 },
187 { "-timeout",".timeout",XrmoptionSepArg, 0 },
188 { "-spin", ".spin", XrmoptionSepArg, 0 },
189 { "+spin", ".spin", XrmoptionNoArg, "" },
190 { "-wander", ".wander", XrmoptionNoArg, "True" },
191 { "+wander", ".wander", XrmoptionNoArg, "False" },
192 { "-labels", ".labels", XrmoptionNoArg, "True" },
193 { "+labels", ".labels", XrmoptionNoArg, "False" },
194 { "-titles", ".titles", XrmoptionNoArg, "True" },
195 { "+titles", ".titles", XrmoptionNoArg, "False" },
196 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
197 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
198 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
199 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
200 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
201 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
204 static argtype vars[] = {
205 {(caddr_t *) &molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
206 {(caddr_t *) &timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
207 {(caddr_t *) &do_spin, "spin", "Spin", DEF_SPIN, t_String},
208 {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
209 {(caddr_t *) &do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
210 {(caddr_t *) &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
211 {(caddr_t *) &do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
212 {(caddr_t *) &do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
213 {(caddr_t *) &do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
216 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
224 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
226 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
227 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
230 glTranslatef (x, y, z);
231 glScalef (diameter, diameter, diameter);
232 unit_sphere (stacks, slices, wire);
238 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
240 const char *font = get_string_resource (res, "Font");
245 if (!font) font = "-*-times-bold-r-normal-*-180-*";
247 f = XLoadQueryFont(mi->dpy, font);
248 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
251 first = f->min_char_or_byte2;
252 last = f->max_char_or_byte2;
255 *dlistP = glGenLists ((GLuint) last+1);
256 check_gl_error ("glGenLists");
257 glXUseXFont(id, first, last-first+1, *dlistP + first);
258 check_gl_error ("glXUseXFont");
265 load_fonts (ModeInfo *mi)
267 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
268 load_font (mi, "atomFont", &mc->xfont1, &mc->font1_dlist);
269 load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
274 string_width (XFontStruct *f, const char *c)
279 int cc = *((unsigned char *) c);
281 ? f->per_char[cc-f->min_char_or_byte2].rbearing
282 : f->min_bounds.rbearing);
290 get_atom_data (const char *atom_name)
294 char *n = strdup (atom_name);
298 while (!isalpha(*n)) n++;
300 while (L > 0 && !isalpha(n[L-1]))
303 for (i = 0; i < countof(all_atom_data); i++)
305 d = &all_atom_data[i];
306 if (!strcmp (n, all_atom_data[i].name))
316 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
325 static atom_data *def_data = 0;
326 if (!def_data) def_data = get_atom_data ("bond");
330 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
332 if (gl_color[3] == 0)
334 const char *string = !font_p ? d->color : d->text_color;
336 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
338 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
339 (a ? a->label : d->name), string);
343 gl_color[0] = xcolor.red / 65536.0;
344 gl_color[1] = xcolor.green / 65536.0;
345 gl_color[2] = xcolor.blue / 65536.0;
350 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
352 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
357 atom_size (molecule_atom *a)
361 if (a->data->size2 == 0)
363 /* let the molecules have the same relative sizes, but scale
364 them to a smaller range, so that the bond-tubes are
371 GLfloat ratio = (a->data->size - min) / (max - min);
372 a->data->size2 = bot + (ratio * (top - bot));
374 return a->data->size2;
377 return a->data->size;
381 static molecule_atom *
382 get_atom (molecule_atom *atoms, int natoms, int id)
386 /* quick short-circuit */
389 if (atoms[id].id == id)
391 if (id > 0 && atoms[id-1].id == id)
393 if (id < natoms-1 && atoms[id+1].id == id)
397 for (i = 0; i < natoms; i++)
398 if (id == atoms[i].id)
401 fprintf (stderr, "%s: no atom %d\n", progname, id);
407 molecule_bounding_box (ModeInfo *mi,
408 GLfloat *x1, GLfloat *y1, GLfloat *z1,
409 GLfloat *x2, GLfloat *y2, GLfloat *z2)
411 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
412 molecule *m = &mc->molecules[mc->which];
417 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
421 *x1 = *x2 = m->atoms[0].x;
422 *y1 = *y2 = m->atoms[0].y;
423 *z1 = *z2 = m->atoms[0].z;
426 for (i = 1; i < m->natoms; i++)
428 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
429 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
430 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
432 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
433 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
434 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
447 draw_bounding_box (ModeInfo *mi)
449 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
450 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
451 int wire = MI_IS_WIREFRAME(mi);
452 GLfloat x1, y1, z1, x2, y2, z2;
453 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
455 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
458 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
460 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
461 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
463 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
464 glNormal3f(0, -1, 0);
465 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
466 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
468 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
470 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
471 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
473 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
474 glNormal3f(0, 0, -1);
475 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
476 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
478 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
480 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
481 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
483 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
484 glNormal3f(-1, 0, 0);
485 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
486 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
489 glPushAttrib (GL_LIGHTING);
490 glDisable (GL_LIGHTING);
492 glColor3f (c2[0], c2[1], c2[2]);
494 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
495 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
496 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
497 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
498 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
499 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
506 /* Since PDB files don't always have the molecule centered around the
507 origin, and since some molecules are pretty large, scale and/or
508 translate so that the whole molecule is visible in the window.
511 ensure_bounding_box_visible (ModeInfo *mi)
513 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
515 GLfloat x1, y1, z1, x2, y2, z2;
518 GLfloat max_size = 10; /* don't bother scaling down if the molecule
519 is already smaller than this */
521 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
526 size = (w > h ? w : h);
527 size = (size > d ? size : d);
529 mc->molecule_size = size;
535 GLfloat scale = max_size / size;
536 glScalef (scale, scale, scale);
538 scale_down = scale < 0.3;
541 glTranslatef (-(x1 + w/2),
548 print_title_string (ModeInfo *mi, const char *string,
549 GLfloat x, GLfloat y, GLfloat line_height)
551 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
555 glPushAttrib (GL_LIGHTING | GL_DEPTH_TEST);
556 glDisable (GL_LIGHTING);
557 glDisable (GL_DEPTH_TEST);
559 glMatrixMode(GL_PROJECTION);
564 glMatrixMode(GL_MODELVIEW);
570 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
572 set_atom_color (mi, 0, True);
574 glRasterPos2f (x, y);
575 for (i = 0; i < strlen(string); i++)
579 glRasterPos2f (x, (y -= line_height));
581 glCallList (mc->font2_dlist + (int)(c));
586 glMatrixMode(GL_PROJECTION);
591 glMatrixMode(GL_MODELVIEW);
595 /* Constructs the GL shapes of the current molecule
598 build_molecule (ModeInfo *mi)
600 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
601 int wire = MI_IS_WIREFRAME(mi);
604 molecule *m = &mc->molecules[mc->which];
608 glDisable(GL_CULL_FACE);
609 glDisable(GL_LIGHTING);
610 glDisable(GL_LIGHT0);
611 glDisable(GL_DEPTH_TEST);
612 glDisable(GL_NORMALIZE);
613 glDisable(GL_CULL_FACE);
617 glEnable(GL_CULL_FACE);
618 glEnable(GL_LIGHTING);
620 glEnable(GL_DEPTH_TEST);
621 glEnable(GL_NORMALIZE);
622 glEnable(GL_CULL_FACE);
625 if (do_labels && !wire)
627 /* This is so all polygons are drawn slightly farther back in the depth
628 buffer, so that when we render text directly on top of the spheres,
629 it still shows up. */
630 glEnable (GL_POLYGON_OFFSET_FILL);
631 glPolygonOffset (1.0, (do_bonds ? 10.0 : 35.0));
635 glDisable (GL_POLYGON_OFFSET_FILL);
639 set_atom_color (mi, 0, False);
642 for (i = 0; i < m->nbonds; i++)
644 molecule_bond *b = &m->bonds[i];
645 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
646 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
651 glVertex3f(from->x, from->y, from->z);
652 glVertex3f(to->x, to->y, to->z);
657 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
663 GLfloat thickness = 0.07 * b->strength;
664 GLfloat cap_size = 0.03;
668 tube (from->x, from->y, from->z,
671 faces, smooth, wire);
675 if (!wire && do_atoms)
676 for (i = 0; i < m->natoms; i++)
678 molecule_atom *a = &m->atoms[i];
679 GLfloat size = atom_size (a);
680 set_atom_color (mi, a, False);
681 sphere (a->x, a->y, a->z, size, wire);
684 /* Second pass to draw labels, after all atoms and bonds are in place
687 for (i = 0; i < m->natoms; i++)
689 molecule_atom *a = &m->atoms[i];
692 glPushAttrib (GL_LIGHTING | GL_DEPTH_TEST);
693 glDisable (GL_LIGHTING);
694 /* glDisable (GL_DEPTH_TEST);*/
697 set_atom_color (mi, a, True);
699 glRasterPos3f (a->x, a->y, a->z);
701 /* Before drawing the string, shift the origin to center
702 the text over the origin of the sphere. */
703 glBitmap (0, 0, 0, 0,
704 -string_width (mc->xfont1, a->label) / 2,
705 -mc->xfont1->descent,
708 for (j = 0; j < strlen(a->label); j++)
709 glCallList (mc->font1_dlist + (int)(a->label[j]));
715 draw_bounding_box (mi);
717 if (do_titles && m->label && *m->label)
718 print_title_string (mi, m->label,
719 10, mi->xgwa.height - 10,
720 mc->xfont2->ascent + mc->xfont2->descent);
728 push_atom (molecule *m,
729 int id, const char *label,
730 GLfloat x, GLfloat y, GLfloat z)
733 if (m->atoms_size < m->natoms)
736 m->atoms = (molecule_atom *) realloc (m->atoms,
737 m->atoms_size * sizeof(*m->atoms));
739 m->atoms[m->natoms-1].id = id;
740 m->atoms[m->natoms-1].label = label;
741 m->atoms[m->natoms-1].x = x;
742 m->atoms[m->natoms-1].y = y;
743 m->atoms[m->natoms-1].z = z;
744 m->atoms[m->natoms-1].data = get_atom_data (label);
749 push_bond (molecule *m, int from, int to)
753 for (i = 0; i < m->nbonds; i++)
754 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
755 (m->bonds[i].to == from && m->bonds[i].from == to))
757 m->bonds[i].strength++;
762 if (m->bonds_size < m->nbonds)
765 m->bonds = (molecule_bond *) realloc (m->bonds,
766 m->bonds_size * sizeof(*m->bonds));
768 m->bonds[m->nbonds-1].from = from;
769 m->bonds[m->nbonds-1].to = to;
770 m->bonds[m->nbonds-1].strength = 1;
775 /* This function is crap.
778 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
780 const char *s = data;
784 if ((!m->label || !*m->label) &&
785 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
787 char *name = calloc (1, 100);
794 while (isspace(*n2)) n2++;
796 ss = strchr (n2, '\n');
798 ss = strchr (n2, '\r');
801 ss = n2+strlen(n2)-1;
802 while (isspace(*ss) && ss > n2)
805 if (strlen (n2) > 4 &&
806 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
807 n2[strlen(n2)-4] = 0;
809 if (m->label) free ((char *) m->label);
810 m->label = strdup (n2);
813 else if (!strncmp (s, "TITLE ", 6) ||
814 !strncmp (s, "HEADER", 6) ||
815 !strncmp (s, "COMPND", 6) ||
816 !strncmp (s, "AUTHOR", 6) ||
817 !strncmp (s, "REVDAT", 6) ||
818 !strncmp (s, "SOURCE", 6) ||
819 !strncmp (s, "EXPDTA", 6) ||
820 !strncmp (s, "JRNL ", 6) ||
821 !strncmp (s, "REMARK", 6) ||
822 !strncmp (s, "SEQRES", 6) ||
823 !strncmp (s, "HET ", 6) ||
824 !strncmp (s, "FORMUL", 6) ||
825 !strncmp (s, "CRYST1", 6) ||
826 !strncmp (s, "ORIGX1", 6) ||
827 !strncmp (s, "ORIGX2", 6) ||
828 !strncmp (s, "ORIGX3", 6) ||
829 !strncmp (s, "SCALE1", 6) ||
830 !strncmp (s, "SCALE2", 6) ||
831 !strncmp (s, "SCALE3", 6) ||
832 !strncmp (s, "MASTER", 6) ||
833 !strncmp (s, "KEYWDS", 6) ||
834 !strncmp (s, "DBREF ", 6) ||
835 !strncmp (s, "HETNAM", 6) ||
836 !strncmp (s, "HETSYN", 6) ||
837 !strncmp (s, "HELIX ", 6) ||
838 !strncmp (s, "LINK ", 6) ||
839 !strncmp (s, "MTRIX1", 6) ||
840 !strncmp (s, "MTRIX2", 6) ||
841 !strncmp (s, "MTRIX3", 6) ||
842 !strncmp (s, "SHEET ", 6) ||
843 !strncmp (s, "CISPEP", 6) ||
844 !strncmp (s, "GENERATED BY", 12) ||
845 !strncmp (s, "TER ", 4) ||
846 !strncmp (s, "END ", 4) ||
847 !strncmp (s, "TER\n", 4) ||
848 !strncmp (s, "END\n", 4) ||
849 !strncmp (s, "\n", 1))
852 else if (!strncmp (s, "ATOM ", 7))
855 char *name = (char *) calloc (1, 4);
856 GLfloat x = -999, y = -999, z = -999;
858 sscanf (s+7, " %d ", &id);
860 strncpy (name, s+12, 3);
861 while (isspace(*name)) name++;
862 ss = name + strlen(name)-1;
863 while (isspace(*ss) && ss > name)
865 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
867 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
868 progname, filename, line,
871 push_atom (m, id, name, x, y, z);
873 else if (!strncmp (s, "HETATM ", 7))
876 char *name = (char *) calloc (1, 4);
877 GLfloat x = -999, y = -999, z = -999;
879 sscanf (s+7, " %d ", &id);
881 strncpy (name, s+12, 3);
882 while (isspace(*name)) name++;
883 ss = name + strlen(name)-1;
884 while (isspace(*ss) && ss > name)
886 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
888 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
889 progname, filename, line,
892 push_atom (m, id, name, x, y, z);
894 else if (!strncmp (s, "CONECT ", 7))
897 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d ",
898 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
899 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
900 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
902 for (j = 1; j < i; j++)
906 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
907 progname, filename, line, atoms[0], atoms[j]);
909 push_bond (m, atoms[0], atoms[j]);
914 char *s1 = strdup (s);
915 for (ss = s1; *ss && *ss != '\n'; ss++)
918 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
919 progname, filename, line, s1);
922 while (*s && *s != '\n')
932 parse_pdb_file (molecule *m, const char *name)
935 int buf_size = 40960;
939 in = fopen(name, "r");
942 char *buf = (char *) malloc(1024 + strlen(name));
943 sprintf(buf, "%s: error reading \"%s\"", progname, name);
948 buf = (char *) malloc (buf_size);
950 while (fgets (buf, buf_size-1, in))
953 for (s = buf; *s; s++)
954 if (*s == '\r') *s = '\n';
955 parse_pdb_data (m, buf, name, line++);
963 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
968 if (!m->nbonds && do_bonds)
970 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
978 generate_molecule_formula (molecule *m)
980 char *buf = (char *) malloc (m->natoms * 10);
983 struct { char *atom; int count; } counts[200];
984 memset (counts, 0, sizeof(counts));
986 for (i = 0; i < m->natoms; i++)
989 char *a = (char *) m->atoms[i].label;
991 while (!isalpha(*a)) a++;
993 for (e = a; isalpha(*e); e++);
995 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1005 while (counts[i].atom)
1007 strcat (s, counts[i].atom);
1008 free (counts[i].atom);
1010 if (counts[i].count > 1)
1011 sprintf (s, "(%d)", counts[i].count);
1016 if (!m->label) m->label = strdup("");
1017 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1018 strcpy (s, m->label);
1021 free ((char *) m->label);
1027 insert_vertical_whitespace (char *string)
1031 if ((string[0] == ',' ||
1033 string[0] == ':') &&
1035 string[0] = ' ', string[1] = '\n';
1041 /* Construct the molecule data from either: the builtins; or from
1042 the (one) .pdb file specified with -molecule.
1045 load_molecules (ModeInfo *mi)
1047 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1048 int wire = MI_IS_WIREFRAME(mi);
1050 if (!molecule_str || !*molecule_str ||
1051 !strcmp(molecule_str, "(default)")) /* do the builtins */
1054 mc->nmolecules = countof(builtin_pdb_data);
1055 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1056 for (i = 0; i < mc->nmolecules; i++)
1059 sprintf (name, "<builtin-%d>", i);
1060 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1061 generate_molecule_formula (&mc->molecules[i]);
1062 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1065 else /* Load a file */
1069 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1070 parse_pdb_file (&mc->molecules[i], molecule_str);
1071 generate_molecule_formula (&mc->molecules[i]);
1072 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1074 if ((wire || !do_atoms) &&
1076 mc->molecules[i].nbonds == 0)
1078 /* If we're not drawing atoms (e.g., wireframe mode), and
1079 there is no bond info, then make sure labels are turned on,
1080 or we'll be looking at a black screen... */
1081 fprintf (stderr, "%s: no bonds: turning -label on.\n", progname);
1089 /* Window management, etc
1092 reshape_molecule (ModeInfo *mi, int width, int height)
1094 GLfloat h = (GLfloat) height / (GLfloat) width;
1096 glViewport (0, 0, (GLint) width, (GLint) height);
1098 glMatrixMode(GL_PROJECTION);
1101 gluPerspective( 30.0, 1/h, 1.0, 100.0 );
1102 gluLookAt( 0.0, 0.0, 15.0,
1106 glMatrixMode(GL_MODELVIEW);
1108 glTranslatef(0.0, 0.0, -15.0);
1110 glClear(GL_COLOR_BUFFER_BIT);
1115 gl_init (ModeInfo *mi)
1117 static GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
1118 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1120 orig_do_labels = do_labels;
1121 orig_do_bonds = do_bonds;
1122 orig_wire = MI_IS_WIREFRAME(mi);
1126 /* lifted from lament.c */
1127 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
1128 #define RANDSIGN() ((random() & 1) ? 1 : -1)
1131 rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v)
1137 ppos = -(ppos + *v);
1146 if (ppos < 0) abort();
1147 if (ppos > 1.0) abort();
1148 *pos = (*pos > 0 ? ppos : -ppos);
1153 /* clamp velocity */
1154 if (*v > max_v || *v < -max_v)
1158 /* If it stops, start it going in the other direction. */
1165 /* keep going in the same direction */
1180 /* Alter direction of rotational acceleration randomly. */
1181 if (! (random() % 120))
1184 /* Change acceleration very occasionally. */
1185 if (! (random() % 200))
1189 else if (random() & 1)
1198 startup_blurb (ModeInfo *mi)
1200 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1201 const char *s = "Constructing molecules...";
1202 print_title_string (mi, s,
1203 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1204 10 + mc->xfont2->ascent + mc->xfont2->descent,
1205 mc->xfont2->ascent + mc->xfont2->descent);
1207 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1211 init_molecule (ModeInfo *mi)
1213 molecule_configuration *mc;
1217 mcs = (molecule_configuration *)
1218 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1220 fprintf(stderr, "%s: out of memory\n", progname);
1225 mc = &mcs[MI_SCREEN(mi)];
1227 if ((mc->glx_context = init_GL(mi)) != NULL) {
1229 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1235 wire = MI_IS_WIREFRAME(mi);
1237 mc->rotx = frand(1.0) * RANDSIGN();
1238 mc->roty = frand(1.0) * RANDSIGN();
1239 mc->rotz = frand(1.0) * RANDSIGN();
1241 /* bell curve from 0-6 degrees, avg 3 */
1242 mc->dx = (frand(1) + frand(1) + frand(1)) / (360/2);
1243 mc->dy = (frand(1) + frand(1) + frand(1)) / (360/2);
1244 mc->dz = (frand(1) + frand(1) + frand(1)) / (360/2);
1246 mc->d_max = mc->dx * 2;
1248 mc->ddx = 0.00006 + frand(0.00003);
1249 mc->ddy = 0.00006 + frand(0.00003);
1250 mc->ddz = 0.00006 + frand(0.00003);
1256 if (*s == 'x' || *s == 'X') mc->spin_x = 1;
1257 else if (*s == 'y' || *s == 'Y') mc->spin_y = 1;
1258 else if (*s == 'z' || *s == 'Z') mc->spin_z = 1;
1262 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1270 mc->molecule_dlist = glGenLists(1);
1272 load_molecules (mi);
1273 mc->which = random() % mc->nmolecules;
1275 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1276 "NoLabelThreshold");
1277 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1278 "WireframeThreshold");
1286 draw_molecule (ModeInfo *mi)
1288 static time_t last = 0;
1289 time_t now = time ((time_t *) 0);
1291 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1292 Display *dpy = MI_DISPLAY(mi);
1293 Window window = MI_WINDOW(mi);
1295 if (!mc->glx_context)
1298 if (last + timeout <= now) /* randomize molecules every -timeout seconds */
1300 if (mc->nmolecules == 1)
1302 if (last != 0) goto SKIP;
1307 mc->which = random() % mc->nmolecules;
1312 while (n == mc->which)
1313 n = random() % mc->nmolecules;
1320 glNewList (mc->molecule_dlist, GL_COMPILE);
1321 ensure_bounding_box_visible (mi);
1323 do_labels = orig_do_labels;
1324 do_bonds = orig_do_bonds;
1325 MI_IS_WIREFRAME(mi) = orig_wire;
1327 if (mc->molecule_size > mc->no_label_threshold)
1329 if (mc->molecule_size > mc->wireframe_threshold)
1330 MI_IS_WIREFRAME(mi) = 1;
1332 if (MI_IS_WIREFRAME(mi))
1335 build_molecule (mi);
1341 glScalef(1.1, 1.1, 1.1);
1348 static int frame = 0;
1350 # define SINOID(SCALE,SIZE) \
1351 ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2)
1353 x = SINOID(0.031, 9.0);
1354 y = SINOID(0.023, 9.0);
1355 z = SINOID(0.017, 9.0);
1357 glTranslatef(x, y, z);
1360 if (mc->spin_x || mc->spin_y || mc->spin_z)
1365 if (x < 0) x = 1 - (x + 1);
1366 if (y < 0) y = 1 - (y + 1);
1367 if (z < 0) z = 1 - (z + 1);
1369 if (mc->spin_x) glRotatef(x * 360, 1.0, 0.0, 0.0);
1370 if (mc->spin_y) glRotatef(y * 360, 0.0, 1.0, 0.0);
1371 if (mc->spin_z) glRotatef(z * 360, 0.0, 0.0, 1.0);
1373 rotate(&mc->rotx, &mc->dx, &mc->ddx, mc->d_max);
1374 rotate(&mc->roty, &mc->dy, &mc->ddy, mc->d_max);
1375 rotate(&mc->rotz, &mc->dz, &mc->ddz, mc->d_max);
1379 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1380 glCallList (mc->molecule_dlist);
1383 if (mi->fps_p) do_fps (mi);
1386 glXSwapBuffers(dpy, window);