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"
68 #ifdef USE_GL /* whole file */
74 #define SPHERE_SLICES 16 /* how densely to render spheres */
75 #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 { "-atoms", ".atoms", XrmoptionNoArg, "True" },
195 { "+atoms", ".atoms", XrmoptionNoArg, "False" },
196 { "-bonds", ".bonds", XrmoptionNoArg, "True" },
197 { "+bonds", ".bonds", XrmoptionNoArg, "False" },
198 { "-bbox", ".bbox", XrmoptionNoArg, "True" },
199 { "+bbox", ".bbox", XrmoptionNoArg, "False" },
202 static argtype vars[] = {
203 {(caddr_t *) &molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String},
204 {(caddr_t *) &timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int},
205 {(caddr_t *) &do_spin, "spin", "Spin", DEF_SPIN, t_String},
206 {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
207 {(caddr_t *) &do_labels, "labels", "Labels", DEF_LABELS, t_Bool},
208 {(caddr_t *) &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
209 {(caddr_t *) &do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool},
210 {(caddr_t *) &do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool},
211 {(caddr_t *) &do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool},
214 ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL};
222 unit_tube (Bool wire)
225 int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES);
226 GLfloat step = M_PI * 2 / faces;
235 glBegin(wire ? GL_LINES : GL_QUAD_STRIP);
237 glBegin(wire ? GL_LINES : GL_QUADS);
240 for (i = 0, th = 0; i <= faces; i++)
242 GLfloat x = cos (th);
243 GLfloat y = sin (th);
245 glVertex3f(x, 0.0, y);
246 glVertex3f(x, 1.0, y);
252 glVertex3f(x, 1.0, y);
253 glVertex3f(x, 0.0, y);
260 for (z = 0; z <= 1; z++)
262 glFrontFace(z == 0 ? GL_CCW : GL_CW);
263 glNormal3f(0, (z == 0 ? -1 : 1), 0);
264 glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
265 if (! wire) glVertex3f(0, z, 0);
266 for (i = 0, th = 0; i <= faces; i++)
268 GLfloat x = cos (th);
269 GLfloat y = sin (th);
279 tube (GLfloat x1, GLfloat y1, GLfloat z1,
280 GLfloat x2, GLfloat y2, GLfloat z2,
281 GLfloat diameter, GLfloat cap_size,
284 GLfloat length, angle, a, b, c;
286 if (diameter <= 0) abort();
292 length = sqrt (a*a + b*b + c*c);
293 angle = acos (a / length);
296 glTranslatef(x1, y1, z1);
297 glScalef (length, length, length);
299 if (c == 0 && b == 0)
300 glRotatef (angle / (M_PI / 180), 0, 1, 0);
302 glRotatef (angle / (M_PI / 180), 0, -c, b);
304 glRotatef (-90, 0, 0, 1);
305 glScalef (diameter/length, 1, diameter/length);
307 /* extend the endpoints of the tube by the cap size in both directions */
310 GLfloat c = cap_size/length;
311 glTranslatef (0, -c, 0);
312 glScalef (1, 1+c+c, 1);
320 /* lifted from glplanet */
321 /* Function for determining points on the surface of the sphere */
323 parametric_sphere (float theta, float rho, GLfloat *vector)
325 vector[0] = -sin(theta) * sin(rho);
326 vector[1] = cos(theta) * sin(rho);
327 vector[2] = cos(rho);
330 /* lifted from glplanet */
332 unit_sphere (Bool wire)
334 int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS);
335 int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES);
341 GLfloat ds, dt, t, s;
343 if (!do_bonds && !scale_down) /* if balls are bigger, be smoother... */
344 slices *= 2, stacks *= 2;
347 glShadeModel(GL_SMOOTH);
349 /* Generate a sphere with quadrilaterals.
350 * Quad vertices are determined using a parametric sphere function.
351 * For fun, you could generate practically any parameteric surface and
352 * map an image onto it.
354 drho = M_PI / stacks;
355 dtheta = 2.0 * M_PI / slices;
360 glBegin( wire ? GL_LINE_LOOP : GL_QUADS );
363 for (i=0; i < stacks; i++) {
366 for (j=0; j < slices; j++) {
370 parametric_sphere (theta, rho, vector);
371 glNormal3fv (vector);
372 parametric_sphere (theta, rho, vector);
373 glVertex3f (vector[0], vector[1], vector[2]);
375 glTexCoord2f (s,t+dt);
376 parametric_sphere (theta, rho+drho, vector);
377 glNormal3fv (vector);
378 parametric_sphere (theta, rho+drho, vector);
379 glVertex3f (vector[0], vector[1], vector[2]);
381 glTexCoord2f (s+ds,t+dt);
382 parametric_sphere (theta + dtheta, rho+drho, vector);
383 glNormal3fv (vector);
384 parametric_sphere (theta + dtheta, rho+drho, vector);
385 glVertex3f (vector[0], vector[1], vector[2]);
387 glTexCoord2f (s+ds, t);
388 parametric_sphere (theta + dtheta, rho, vector);
389 glNormal3fv (vector);
390 parametric_sphere (theta + dtheta, rho, vector);
391 glVertex3f (vector[0], vector[1], vector[2]);
402 sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire)
405 glTranslatef (x, y, z);
406 glScalef (diameter, diameter, diameter);
413 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
415 const char *font = get_string_resource (res, "Font");
420 if (!font) font = "-*-times-bold-r-normal-*-180-*";
422 f = XLoadQueryFont(mi->dpy, font);
423 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
426 first = f->min_char_or_byte2;
427 last = f->max_char_or_byte2;
430 *dlistP = glGenLists ((GLuint) last+1);
431 check_gl_error ("glGenLists");
432 glXUseXFont(id, first, last-first+1, *dlistP + first);
433 check_gl_error ("glXUseXFont");
440 load_fonts (ModeInfo *mi)
442 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
443 load_font (mi, "atomFont", &mc->xfont1, &mc->font1_dlist);
444 load_font (mi, "titleFont", &mc->xfont2, &mc->font2_dlist);
449 string_width (XFontStruct *f, const char *c)
454 int cc = *((unsigned char *) c);
456 ? f->per_char[cc-f->min_char_or_byte2].rbearing
457 : f->min_bounds.rbearing);
465 get_atom_data (const char *atom_name)
469 char *n = strdup (atom_name);
473 while (!isalpha(*n)) n++;
475 while (L > 0 && !isalpha(n[L-1]))
478 for (i = 0; i < countof(all_atom_data); i++)
480 d = &all_atom_data[i];
481 if (!strcmp (n, all_atom_data[i].name))
491 set_atom_color (ModeInfo *mi, molecule_atom *a, Bool font_p)
500 static atom_data *def_data = 0;
501 if (!def_data) def_data = get_atom_data ("bond");
505 gl_color = (!font_p ? d->gl_color : (d->gl_color + 4));
507 if (gl_color[3] == 0)
509 const char *string = !font_p ? d->color : d->text_color;
511 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
513 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
514 (a ? a->label : d->name), string);
518 gl_color[0] = xcolor.red / 65536.0;
519 gl_color[1] = xcolor.green / 65536.0;
520 gl_color[2] = xcolor.blue / 65536.0;
525 glColor3f (gl_color[0], gl_color[1], gl_color[2]);
527 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gl_color);
532 atom_size (molecule_atom *a)
536 if (a->data->size2 == 0)
538 /* let the molecules have the same relative sizes, but scale
539 them to a smaller range, so that the bond-tubes are
546 GLfloat ratio = (a->data->size - min) / (max - min);
547 a->data->size2 = bot + (ratio * (top - bot));
549 return a->data->size2;
552 return a->data->size;
556 static molecule_atom *
557 get_atom (molecule_atom *atoms, int natoms, int id)
561 /* quick short-circuit */
564 if (atoms[id].id == id)
566 if (id > 0 && atoms[id-1].id == id)
568 if (id < natoms-1 && atoms[id+1].id == id)
572 for (i = 0; i < natoms; i++)
573 if (id == atoms[i].id)
576 fprintf (stderr, "%s: no atom %d\n", progname, id);
582 molecule_bounding_box (ModeInfo *mi,
583 GLfloat *x1, GLfloat *y1, GLfloat *z1,
584 GLfloat *x2, GLfloat *y2, GLfloat *z2)
586 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
587 molecule *m = &mc->molecules[mc->which];
592 *x1 = *y1 = *z1 = *x2 = *y2 = *z2 = 0;
596 *x1 = *x2 = m->atoms[0].x;
597 *y1 = *y2 = m->atoms[0].y;
598 *z1 = *z2 = m->atoms[0].z;
601 for (i = 1; i < m->natoms; i++)
603 if (m->atoms[i].x < *x1) *x1 = m->atoms[i].x;
604 if (m->atoms[i].y < *y1) *y1 = m->atoms[i].y;
605 if (m->atoms[i].z < *z1) *z1 = m->atoms[i].z;
607 if (m->atoms[i].x > *x2) *x2 = m->atoms[i].x;
608 if (m->atoms[i].y > *y2) *y2 = m->atoms[i].y;
609 if (m->atoms[i].z > *z2) *z2 = m->atoms[i].z;
622 draw_bounding_box (ModeInfo *mi)
624 static GLfloat c1[4] = { 0.2, 0.2, 0.6, 1.0 };
625 static GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0 };
626 int wire = MI_IS_WIREFRAME(mi);
627 GLfloat x1, y1, z1, x2, y2, z2;
628 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
630 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c1);
633 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
635 glVertex3f(x1, y1, z1); glVertex3f(x1, y1, z2);
636 glVertex3f(x2, y1, z2); glVertex3f(x2, y1, z1);
638 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
639 glNormal3f(0, -1, 0);
640 glVertex3f(x2, y2, z1); glVertex3f(x2, y2, z2);
641 glVertex3f(x1, y2, z2); glVertex3f(x1, y2, z1);
643 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
645 glVertex3f(x1, y1, z1); glVertex3f(x2, y1, z1);
646 glVertex3f(x2, y2, z1); glVertex3f(x1, y2, z1);
648 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
649 glNormal3f(0, 0, -1);
650 glVertex3f(x1, y2, z2); glVertex3f(x2, y2, z2);
651 glVertex3f(x2, y1, z2); glVertex3f(x1, y1, z2);
653 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
655 glVertex3f(x1, y2, z1); glVertex3f(x1, y2, z2);
656 glVertex3f(x1, y1, z2); glVertex3f(x1, y1, z1);
658 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
659 glNormal3f(-1, 0, 0);
660 glVertex3f(x2, y1, z1); glVertex3f(x2, y1, z2);
661 glVertex3f(x2, y2, z2); glVertex3f(x2, y2, z1);
664 glPushAttrib (GL_LIGHTING);
665 glDisable (GL_LIGHTING);
667 glColor3f (c2[0], c2[1], c2[2]);
669 if (x1 > 0) x1 = 0; if (x2 < 0) x2 = 0;
670 if (y1 > 0) y1 = 0; if (y2 < 0) y2 = 0;
671 if (z1 > 0) z1 = 0; if (z2 < 0) z2 = 0;
672 glVertex3f(x1, 0, 0); glVertex3f(x2, 0, 0);
673 glVertex3f(0 , y1, 0); glVertex3f(0, y2, 0);
674 glVertex3f(0, 0, z1); glVertex3f(0, 0, z2);
681 /* Since PDB files don't always have the molecule centered around the
682 origin, and since some molecules are pretty large, scale and/or
683 translate so that the whole molecule is visible in the window.
686 ensure_bounding_box_visible (ModeInfo *mi)
688 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
690 GLfloat x1, y1, z1, x2, y2, z2;
693 GLfloat max_size = 10; /* don't bother scaling down if the molecule
694 is already smaller than this */
696 molecule_bounding_box (mi, &x1, &y1, &z1, &x2, &y2, &z2);
701 size = (w > h ? w : h);
702 size = (size > d ? size : d);
704 mc->molecule_size = size;
710 GLfloat scale = max_size / size;
711 glScalef (scale, scale, scale);
713 scale_down = scale < 0.3;
716 glTranslatef (-(x1 + w/2),
723 print_title_string (ModeInfo *mi, const char *string,
724 GLfloat x, GLfloat y, GLfloat line_height)
726 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
730 glPushAttrib (GL_LIGHTING | GL_DEPTH_TEST);
731 glDisable (GL_LIGHTING);
732 glDisable (GL_DEPTH_TEST);
734 glMatrixMode(GL_PROJECTION);
739 glMatrixMode(GL_MODELVIEW);
745 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
747 set_atom_color (mi, 0, True);
749 glRasterPos2f (x, y);
750 for (i = 0; i < strlen(string); i++)
754 glRasterPos2f (x, (y -= line_height));
756 glCallList (mc->font2_dlist + (int)(c));
761 glMatrixMode(GL_PROJECTION);
766 glMatrixMode(GL_MODELVIEW);
770 /* Constructs the GL shapes of the current molecule
773 build_molecule (ModeInfo *mi)
775 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
776 int wire = MI_IS_WIREFRAME(mi);
779 molecule *m = &mc->molecules[mc->which];
783 glDisable(GL_CULL_FACE);
784 glDisable(GL_LIGHTING);
785 glDisable(GL_LIGHT0);
786 glDisable(GL_DEPTH_TEST);
787 glDisable(GL_NORMALIZE);
788 glDisable(GL_CULL_FACE);
792 glEnable(GL_CULL_FACE);
793 glEnable(GL_LIGHTING);
795 glEnable(GL_DEPTH_TEST);
796 glEnable(GL_NORMALIZE);
797 glEnable(GL_CULL_FACE);
801 set_atom_color (mi, 0, False);
804 for (i = 0; i < m->nbonds; i++)
806 molecule_bond *b = &m->bonds[i];
807 molecule_atom *from = get_atom (m->atoms, m->natoms, b->from);
808 molecule_atom *to = get_atom (m->atoms, m->natoms, b->to);
813 glVertex3f(from->x, from->y, from->z);
814 glVertex3f(to->x, to->y, to->z);
819 GLfloat thickness = 0.07 * b->strength;
820 GLfloat cap_size = 0.03;
823 tube (from->x, from->y, from->z,
825 thickness, cap_size, wire);
829 for (i = 0; i < m->natoms; i++)
831 molecule_atom *a = &m->atoms[i];
834 if (!wire && do_atoms)
836 GLfloat size = atom_size (a);
837 set_atom_color (mi, a, False);
838 sphere (a->x, a->y, a->z, size, wire);
843 glPushAttrib (GL_LIGHTING | GL_DEPTH_TEST);
844 glDisable (GL_LIGHTING);
845 glDisable (GL_DEPTH_TEST);
848 set_atom_color (mi, a, True);
850 glRasterPos3f (a->x, a->y, a->z);
853 GLdouble mm[17], pm[17];
855 GLdouble wx=-1, wy=-1, wz=-1;
856 glGetDoublev (GL_MODELVIEW_MATRIX, mm);
857 glGetDoublev (GL_PROJECTION_MATRIX, pm);
858 glGetIntegerv (GL_VIEWPORT, vp);
860 /* Convert 3D coordinates to window coordinates */
861 gluProject (a->x, a->y, a->z, mm, pm, vp, &wx, &wy, &wz);
863 /* Fudge the window coordinates to center the string */
864 wx -= string_width (mc->xfont1, a->label) / 2;
865 wy -= mc->xfont1->descent;
867 /* Convert new window coordinates back to 3D coordinates */
868 gluUnProject (wx, wy, wz, mm, pm, vp, &wx, &wy, &wz);
869 glRasterPos3f (wx, wy, wz);
872 for (i = 0; i < strlen(a->label); i++)
873 glCallList (mc->font1_dlist + (int)(a->label[i]));
880 draw_bounding_box (mi);
882 if (do_titles && m->label && *m->label)
883 print_title_string (mi, m->label,
884 10, mi->xgwa.height - 10,
885 mc->xfont2->ascent + mc->xfont2->descent);
893 push_atom (molecule *m,
894 int id, const char *label,
895 GLfloat x, GLfloat y, GLfloat z)
898 if (m->atoms_size < m->natoms)
901 m->atoms = (molecule_atom *) realloc (m->atoms,
902 m->atoms_size * sizeof(*m->atoms));
904 m->atoms[m->natoms-1].id = id;
905 m->atoms[m->natoms-1].label = label;
906 m->atoms[m->natoms-1].x = x;
907 m->atoms[m->natoms-1].y = y;
908 m->atoms[m->natoms-1].z = z;
909 m->atoms[m->natoms-1].data = get_atom_data (label);
914 push_bond (molecule *m, int from, int to)
918 for (i = 0; i < m->nbonds; i++)
919 if ((m->bonds[i].from == from && m->bonds[i].to == to) ||
920 (m->bonds[i].to == from && m->bonds[i].from == to))
922 m->bonds[i].strength++;
927 if (m->bonds_size < m->nbonds)
930 m->bonds = (molecule_bond *) realloc (m->bonds,
931 m->bonds_size * sizeof(*m->bonds));
933 m->bonds[m->nbonds-1].from = from;
934 m->bonds[m->nbonds-1].to = to;
935 m->bonds[m->nbonds-1].strength = 1;
940 /* This function is crap.
943 parse_pdb_data (molecule *m, const char *data, const char *filename, int line)
945 const char *s = data;
949 if ((!m->label || !*m->label) &&
950 (!strncmp (s, "HEADER", 6) || !strncmp (s, "COMPND", 6)))
952 char *name = calloc (1, 100);
959 while (isspace(*n2)) n2++;
961 ss = strchr (n2, '\n');
963 ss = strchr (n2, '\r');
966 ss = n2+strlen(n2)-1;
967 while (isspace(*ss) && ss > n2)
970 if (strlen (n2) > 4 &&
971 !strcmp (n2 + strlen(n2) - 4, ".pdb"))
972 n2[strlen(n2)-4] = 0;
974 if (m->label) free ((char *) m->label);
975 m->label = strdup (n2);
978 else if (!strncmp (s, "TITLE ", 6) ||
979 !strncmp (s, "HEADER", 6) ||
980 !strncmp (s, "COMPND", 6) ||
981 !strncmp (s, "AUTHOR", 6) ||
982 !strncmp (s, "REVDAT", 6) ||
983 !strncmp (s, "SOURCE", 6) ||
984 !strncmp (s, "EXPDTA", 6) ||
985 !strncmp (s, "JRNL ", 6) ||
986 !strncmp (s, "REMARK", 6) ||
987 !strncmp (s, "SEQRES", 6) ||
988 !strncmp (s, "HET ", 6) ||
989 !strncmp (s, "FORMUL", 6) ||
990 !strncmp (s, "CRYST1", 6) ||
991 !strncmp (s, "ORIGX1", 6) ||
992 !strncmp (s, "ORIGX2", 6) ||
993 !strncmp (s, "ORIGX3", 6) ||
994 !strncmp (s, "SCALE1", 6) ||
995 !strncmp (s, "SCALE2", 6) ||
996 !strncmp (s, "SCALE3", 6) ||
997 !strncmp (s, "MASTER", 6) ||
998 !strncmp (s, "KEYWDS", 6) ||
999 !strncmp (s, "DBREF ", 6) ||
1000 !strncmp (s, "HETNAM", 6) ||
1001 !strncmp (s, "HETSYN", 6) ||
1002 !strncmp (s, "HELIX ", 6) ||
1003 !strncmp (s, "LINK ", 6) ||
1004 !strncmp (s, "MTRIX1", 6) ||
1005 !strncmp (s, "MTRIX2", 6) ||
1006 !strncmp (s, "MTRIX3", 6) ||
1007 !strncmp (s, "SHEET ", 6) ||
1008 !strncmp (s, "CISPEP", 6) ||
1009 !strncmp (s, "GENERATED BY", 12) ||
1010 !strncmp (s, "TER ", 4) ||
1011 !strncmp (s, "END ", 4) ||
1012 !strncmp (s, "TER\n", 4) ||
1013 !strncmp (s, "END\n", 4) ||
1014 !strncmp (s, "\n", 1))
1017 else if (!strncmp (s, "ATOM ", 7))
1020 char *name = (char *) calloc (1, 4);
1021 GLfloat x = -999, y = -999, z = -999;
1023 sscanf (s+7, " %d ", &id);
1025 strncpy (name, s+12, 3);
1026 while (isspace(*name)) name++;
1027 ss = name + strlen(name)-1;
1028 while (isspace(*ss) && ss > name)
1030 sscanf (s + 32, " %f %f %f ", &x, &y, &z);
1032 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
1033 progname, filename, line,
1036 push_atom (m, id, name, x, y, z);
1038 else if (!strncmp (s, "HETATM ", 7))
1041 char *name = (char *) calloc (1, 4);
1042 GLfloat x = -999, y = -999, z = -999;
1044 sscanf (s+7, " %d ", &id);
1046 strncpy (name, s+12, 3);
1047 while (isspace(*name)) name++;
1048 ss = name + strlen(name)-1;
1049 while (isspace(*ss) && ss > name)
1051 sscanf (s + 30, " %f %f %f ", &x, &y, &z);
1053 fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n",
1054 progname, filename, line,
1057 push_atom (m, id, name, x, y, z);
1059 else if (!strncmp (s, "CONECT ", 7))
1062 int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d ",
1063 &atoms[0], &atoms[1], &atoms[2], &atoms[3],
1064 &atoms[4], &atoms[5], &atoms[6], &atoms[7],
1065 &atoms[8], &atoms[9], &atoms[10], &atoms[11]);
1067 for (j = 1; j < i; j++)
1071 fprintf (stderr, "%s: %s: %d: bond: %d %d\n",
1072 progname, filename, line, atoms[0], atoms[j]);
1074 push_bond (m, atoms[0], atoms[j]);
1079 char *s1 = strdup (s);
1080 for (ss = s1; *ss && *ss != '\n'; ss++)
1083 fprintf (stderr, "%s: %s: %d: unrecognised line: %s\n",
1084 progname, filename, line, s1);
1087 while (*s && *s != '\n')
1097 parse_pdb_file (molecule *m, const char *name)
1100 int buf_size = 40960;
1104 in = fopen(name, "r");
1107 char *buf = (char *) malloc(1024 + strlen(name));
1108 sprintf(buf, "%s: error reading \"%s\"", progname, name);
1113 buf = (char *) malloc (buf_size);
1115 while (fgets (buf, buf_size-1, in))
1118 for (s = buf; *s; s++)
1119 if (*s == '\r') *s = '\n';
1120 parse_pdb_data (m, buf, name, line++);
1128 fprintf (stderr, "%s: file %s contains no atomic coordinates!\n",
1133 if (!m->nbonds && do_bonds)
1135 fprintf (stderr, "%s: warning: file %s contains no atomic bond info.\n",
1143 generate_molecule_formula (molecule *m)
1145 char *buf = (char *) malloc (m->natoms * 10);
1148 struct { char *atom; int count; } counts[200];
1149 memset (counts, 0, sizeof(counts));
1151 for (i = 0; i < m->natoms; i++)
1154 char *a = (char *) m->atoms[i].label;
1156 while (!isalpha(*a)) a++;
1158 for (e = a; isalpha(*e); e++);
1160 while (counts[j].atom && !!strcmp(a, counts[j].atom))
1170 while (counts[i].atom)
1172 strcat (s, counts[i].atom);
1173 free (counts[i].atom);
1175 if (counts[i].count > 1)
1176 sprintf (s, "(%d)", counts[i].count);
1181 if (!m->label) m->label = strdup("");
1182 s = (char *) malloc (strlen (m->label) + strlen (buf) + 2);
1183 strcpy (s, m->label);
1186 free ((char *) m->label);
1192 insert_vertical_whitespace (char *string)
1196 if ((string[0] == ',' ||
1198 string[0] == ':') &&
1200 string[0] = ' ', string[1] = '\n';
1206 /* Construct the molecule data from either: the builtins; or from
1207 the (one) .pdb file specified with -molecule.
1210 load_molecules (ModeInfo *mi)
1212 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1213 int wire = MI_IS_WIREFRAME(mi);
1215 if (!molecule_str || !*molecule_str ||
1216 !strcmp(molecule_str, "(default)")) /* do the builtins */
1219 mc->nmolecules = countof(builtin_pdb_data);
1220 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1221 for (i = 0; i < mc->nmolecules; i++)
1224 sprintf (name, "<builtin-%d>", i);
1225 parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1);
1226 generate_molecule_formula (&mc->molecules[i]);
1227 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1230 else /* Load a file */
1234 mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules);
1235 parse_pdb_file (&mc->molecules[i], molecule_str);
1236 generate_molecule_formula (&mc->molecules[i]);
1237 insert_vertical_whitespace ((char *) mc->molecules[i].label);
1239 if ((wire || !do_atoms) &&
1241 mc->molecules[i].nbonds == 0)
1243 /* If we're not drawing atoms (e.g., wireframe mode), and
1244 there is no bond info, then make sure labels are turned on,
1245 or we'll be looking at a black screen... */
1246 fprintf (stderr, "%s: no bonds: turning -label on.\n", progname);
1254 /* Window management, etc
1257 reshape_molecule (ModeInfo *mi, int width, int height)
1259 GLfloat h = (GLfloat) height / (GLfloat) width;
1261 glViewport (0, 0, (GLint) width, (GLint) height);
1263 glMatrixMode(GL_PROJECTION);
1266 gluPerspective( 30.0, 1/h, 1.0, 100.0 );
1267 gluLookAt( 0.0, 0.0, 15.0,
1270 glMatrixMode(GL_MODELVIEW);
1272 glTranslatef(0.0, 0.0, -15.0);
1274 glClear(GL_COLOR_BUFFER_BIT);
1279 gl_init (ModeInfo *mi)
1281 static GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
1282 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1284 orig_do_labels = do_labels;
1285 orig_do_bonds = do_bonds;
1286 orig_wire = MI_IS_WIREFRAME(mi);
1290 /* lifted from lament.c */
1291 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
1292 #define RANDSIGN() ((random() & 1) ? 1 : -1)
1295 rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v)
1301 ppos = -(ppos + *v);
1310 if (ppos < 0) abort();
1311 if (ppos > 1.0) abort();
1312 *pos = (*pos > 0 ? ppos : -ppos);
1317 /* clamp velocity */
1318 if (*v > max_v || *v < -max_v)
1322 /* If it stops, start it going in the other direction. */
1329 /* keep going in the same direction */
1344 /* Alter direction of rotational acceleration randomly. */
1345 if (! (random() % 120))
1348 /* Change acceleration very occasionally. */
1349 if (! (random() % 200))
1353 else if (random() & 1)
1362 startup_blurb (ModeInfo *mi)
1364 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1365 const char *s = "Constructing molecules...";
1366 print_title_string (mi, s,
1367 mi->xgwa.width - (string_width (mc->xfont2, s) + 40),
1368 10 + mc->xfont2->ascent + mc->xfont2->descent,
1369 mc->xfont2->ascent + mc->xfont2->descent);
1371 glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1375 init_molecule (ModeInfo *mi)
1377 molecule_configuration *mc;
1381 mcs = (molecule_configuration *)
1382 calloc (MI_NUM_SCREENS(mi), sizeof (molecule_configuration));
1384 fprintf(stderr, "%s: out of memory\n", progname);
1389 mc = &mcs[MI_SCREEN(mi)];
1391 if ((mc->glx_context = init_GL(mi)) != NULL) {
1393 reshape_molecule (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1399 wire = MI_IS_WIREFRAME(mi);
1401 mc->rotx = frand(1.0) * RANDSIGN();
1402 mc->roty = frand(1.0) * RANDSIGN();
1403 mc->rotz = frand(1.0) * RANDSIGN();
1405 /* bell curve from 0-6 degrees, avg 3 */
1406 mc->dx = (frand(1) + frand(1) + frand(1)) / (360/2);
1407 mc->dy = (frand(1) + frand(1) + frand(1)) / (360/2);
1408 mc->dz = (frand(1) + frand(1) + frand(1)) / (360/2);
1410 mc->d_max = mc->dx * 2;
1412 mc->ddx = 0.00006 + frand(0.00003);
1413 mc->ddy = 0.00006 + frand(0.00003);
1414 mc->ddz = 0.00006 + frand(0.00003);
1420 if (*s == 'x' || *s == 'X') mc->spin_x = 1;
1421 else if (*s == 'y' || *s == 'Y') mc->spin_y = 1;
1422 else if (*s == 'z' || *s == 'Z') mc->spin_z = 1;
1426 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1434 mc->molecule_dlist = glGenLists(1);
1436 load_molecules (mi);
1437 mc->which = random() % mc->nmolecules;
1439 mc->no_label_threshold = get_float_resource ("noLabelThreshold",
1440 "NoLabelThreshold");
1441 mc->wireframe_threshold = get_float_resource ("wireframeThreshold",
1442 "WireframeThreshold");
1450 draw_molecule (ModeInfo *mi)
1452 static time_t last = 0;
1453 time_t now = time ((time_t *) 0);
1455 molecule_configuration *mc = &mcs[MI_SCREEN(mi)];
1456 Display *dpy = MI_DISPLAY(mi);
1457 Window window = MI_WINDOW(mi);
1459 if (!mc->glx_context)
1462 if (last + timeout <= now) /* randomize molecules every -timeout seconds */
1464 if (mc->nmolecules == 1)
1466 if (last != 0) goto SKIP;
1471 mc->which = random() % mc->nmolecules;
1476 while (n == mc->which)
1477 n = random() % mc->nmolecules;
1484 glNewList (mc->molecule_dlist, GL_COMPILE);
1485 ensure_bounding_box_visible (mi);
1487 do_labels = orig_do_labels;
1488 do_bonds = orig_do_bonds;
1489 MI_IS_WIREFRAME(mi) = orig_wire;
1491 if (mc->molecule_size > mc->no_label_threshold)
1493 if (mc->molecule_size > mc->wireframe_threshold)
1494 MI_IS_WIREFRAME(mi) = 1;
1496 if (MI_IS_WIREFRAME(mi))
1499 build_molecule (mi);
1505 glScalef(1.1, 1.1, 1.1);
1512 static int frame = 0;
1514 # define SINOID(SCALE,SIZE) \
1515 ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2)
1517 x = SINOID(0.031, 9.0);
1518 y = SINOID(0.023, 9.0);
1519 z = SINOID(0.017, 9.0);
1521 glTranslatef(x, y, z);
1524 if (mc->spin_x || mc->spin_y || mc->spin_z)
1529 if (x < 0) x = 1 - (x + 1);
1530 if (y < 0) y = 1 - (y + 1);
1531 if (z < 0) z = 1 - (z + 1);
1533 if (mc->spin_x) glRotatef(x * 360, 1.0, 0.0, 0.0);
1534 if (mc->spin_y) glRotatef(y * 360, 0.0, 1.0, 0.0);
1535 if (mc->spin_z) glRotatef(z * 360, 0.0, 0.0, 1.0);
1537 rotate(&mc->rotx, &mc->dx, &mc->ddx, mc->d_max);
1538 rotate(&mc->roty, &mc->dy, &mc->ddy, mc->d_max);
1539 rotate(&mc->rotz, &mc->dz, &mc->ddz, mc->d_max);
1543 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1544 glCallList (mc->molecule_dlist);
1547 if (mi->fps_p) do_fps (mi);
1550 glXSwapBuffers(dpy, window);