X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=hacks%2Fglx%2Fmolecule.c;h=02b8362461a5285bf26fd8661f602fb715da9085;hb=a94197e76a5dea5cb60542840809d6c20d0abbf3;hp=a65e918ccc5171e6126611f5f081bfded2b93f10;hpb=a445bdd3e3ba4abbee441844b6665b4c3c13d48c;p=xscreensaver diff --git a/hacks/glx/molecule.c b/hacks/glx/molecule.c index a65e918c..02b83624 100644 --- a/hacks/glx/molecule.c +++ b/hacks/glx/molecule.c @@ -22,6 +22,49 @@ - I'm not sure the text labels are being done in the best way; they are sometimes, but not always, occluded by spheres that pass in front of them. + + GENERAL OPENGL NAIVETY: + + I don't understand the *right* way to place text in front of the + atoms. What I'm doing now is close, but has glitches. I think I + understand glPolygonOffset(), but I think it doesn't help me. + + Here's how I'd phrase the problem I'm trying to solve: + + - I have a bunch of spherical objects of various sizes + - I want a piece of text in the scene, between each object + and the observer + - the position of this text should be apparently tangential + to the surface of the sphere, so that: + - it is never inside the sphere; + - but can be occluded by other objects in the scene. + + So I was trying to use glPolygonOffset() to say "pretend all + polygons are N units deeper than they actually are" where N was + somewhere around the maximal radius of the objects. Which wasn't a + perfect solution, but was close. But it turns out that can't work, + because the second arg to glPolygonOffset() is multiplied by some + minimal depth quantum which is not revealed, so I can't pass it an + offset in scene units -- only in multiples of the quantum. So I + don't know how many quanta in radius my spheres are. + + I think I need to position and render the text with glRasterPos3f() + so that the text is influenced by the depth buffer. If I used 2f, + or an explicit constant Z value, then the text would always be in + front of each sphere, and text would be visible for spheres that + were fully occluded, which isn't what I want. + + So my only guess at this point is that I need to position the text + exactly where I want it, tangential to the spheres -- but that + means I need to be able to compute that XYZ position, which is + dependent on the position of the observer! Which means two things: + first, while generating my scene, I need to take into account the + position of the observer, and I don't have a clue how to do that; + and second, it means I can't put my whole molecule in a display + list, because the XYZ position of the text in the scene changes at + every frame, as the molecule rotates. + + This just *can't* be as hard as it seems! */ #include @@ -64,17 +107,18 @@ #include "xlockmore.h" #include "colors.h" +#include "sphere.h" +#include "tube.h" #ifdef USE_GL /* whole file */ +#include #include #include - #define SPHERE_SLICES 16 /* how densely to render spheres */ #define SPHERE_STACKS 10 - #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */ #ifdef SMOOTH_TUBE @@ -186,11 +230,13 @@ static XrmOptionDescRec opts[] = { { "-molecule", ".molecule", XrmoptionSepArg, 0 }, { "-timeout",".timeout",XrmoptionSepArg, 0 }, { "-spin", ".spin", XrmoptionSepArg, 0 }, - { "+spin", ".spin", XrmoptionNoArg, "" }, + { "+spin", ".spin", XrmoptionNoArg, "False" }, { "-wander", ".wander", XrmoptionNoArg, "True" }, { "+wander", ".wander", XrmoptionNoArg, "False" }, { "-labels", ".labels", XrmoptionNoArg, "True" }, { "+labels", ".labels", XrmoptionNoArg, "False" }, + { "-titles", ".titles", XrmoptionNoArg, "True" }, + { "+titles", ".titles", XrmoptionNoArg, "False" }, { "-atoms", ".atoms", XrmoptionNoArg, "True" }, { "+atoms", ".atoms", XrmoptionNoArg, "False" }, { "-bonds", ".bonds", XrmoptionNoArg, "True" }, @@ -219,192 +265,15 @@ ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL}; /* shapes */ static void -unit_tube (Bool wire) -{ - int i; - int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES); - GLfloat step = M_PI * 2 / faces; - GLfloat th; - int z = 0; - - /* side walls - */ - glFrontFace(GL_CCW); - -# ifdef SMOOTH_TUBE - glBegin(wire ? GL_LINES : GL_QUAD_STRIP); -# else - glBegin(wire ? GL_LINES : GL_QUADS); -# endif - - for (i = 0, th = 0; i <= faces; i++) - { - GLfloat x = cos (th); - GLfloat y = sin (th); - glNormal3f(x, 0, y); - glVertex3f(x, 0.0, y); - glVertex3f(x, 1.0, y); - th += step; - -# ifndef SMOOTH_TUBE - x = cos (th); - y = sin (th); - glVertex3f(x, 1.0, y); - glVertex3f(x, 0.0, y); -# endif - } - glEnd(); - - /* End caps - */ - for (z = 0; z <= 1; z++) - { - glFrontFace(z == 0 ? GL_CCW : GL_CW); - glNormal3f(0, (z == 0 ? -1 : 1), 0); - glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN); - if (! wire) glVertex3f(0, z, 0); - for (i = 0, th = 0; i <= faces; i++) - { - GLfloat x = cos (th); - GLfloat y = sin (th); - glVertex3f(x, z, y); - th += step; - } - glEnd(); - } -} - - -static void -tube (GLfloat x1, GLfloat y1, GLfloat z1, - GLfloat x2, GLfloat y2, GLfloat z2, - GLfloat diameter, GLfloat cap_size, - Bool wire) -{ - GLfloat length, angle, a, b, c; - - if (diameter <= 0) abort(); - - a = (x2 - x1); - b = (y2 - y1); - c = (z2 - z1); - - length = sqrt (a*a + b*b + c*c); - angle = acos (a / length); - - glPushMatrix(); - glTranslatef(x1, y1, z1); - glScalef (length, length, length); - - if (c == 0 && b == 0) - glRotatef (angle / (M_PI / 180), 0, 1, 0); - else - glRotatef (angle / (M_PI / 180), 0, -c, b); - - glRotatef (-90, 0, 0, 1); - glScalef (diameter/length, 1, diameter/length); - - /* extend the endpoints of the tube by the cap size in both directions */ - if (cap_size != 0) - { - GLfloat c = cap_size/length; - glTranslatef (0, -c, 0); - glScalef (1, 1+c+c, 1); - } - - unit_tube (wire); - glPopMatrix(); -} - - -/* lifted from glplanet */ -/* Function for determining points on the surface of the sphere */ -static void -parametric_sphere (float theta, float rho, GLfloat *vector) -{ - vector[0] = -sin(theta) * sin(rho); - vector[1] = cos(theta) * sin(rho); - vector[2] = cos(rho); -} - -/* lifted from glplanet */ -static void -unit_sphere (Bool wire) +sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire) { int stacks = (scale_down ? SPHERE_STACKS_2 : SPHERE_STACKS); int slices = (scale_down ? SPHERE_SLICES_2 : SPHERE_SLICES); - int i, j; - float drho, dtheta; - float rho, theta; - GLfloat vector[3]; - GLfloat ds, dt, t, s; - - if (!do_bonds && !scale_down) /* if balls are bigger, be smoother... */ - slices *= 2, stacks *= 2; - - if (!wire) - glShadeModel(GL_SMOOTH); - - /* Generate a sphere with quadrilaterals. - * Quad vertices are determined using a parametric sphere function. - * For fun, you could generate practically any parameteric surface and - * map an image onto it. - */ - drho = M_PI / stacks; - dtheta = 2.0 * M_PI / slices; - ds = 1.0 / slices; - dt = 1.0 / stacks; - - glFrontFace(GL_CCW); - glBegin( wire ? GL_LINE_LOOP : GL_QUADS ); - - t = 0.0; - for (i=0; i < stacks; i++) { - rho = i * drho; - s = 0.0; - for (j=0; j < slices; j++) { - theta = j * dtheta; - - glTexCoord2f (s,t); - parametric_sphere (theta, rho, vector); - glNormal3fv (vector); - parametric_sphere (theta, rho, vector); - glVertex3f (vector[0], vector[1], vector[2]); - - glTexCoord2f (s,t+dt); - parametric_sphere (theta, rho+drho, vector); - glNormal3fv (vector); - parametric_sphere (theta, rho+drho, vector); - glVertex3f (vector[0], vector[1], vector[2]); - - glTexCoord2f (s+ds,t+dt); - parametric_sphere (theta + dtheta, rho+drho, vector); - glNormal3fv (vector); - parametric_sphere (theta + dtheta, rho+drho, vector); - glVertex3f (vector[0], vector[1], vector[2]); - - glTexCoord2f (s+ds, t); - parametric_sphere (theta + dtheta, rho, vector); - glNormal3fv (vector); - parametric_sphere (theta + dtheta, rho, vector); - glVertex3f (vector[0], vector[1], vector[2]); - - s = s + ds; - } - t = t + dt; - } - glEnd(); -} - - -static void -sphere (GLfloat x, GLfloat y, GLfloat z, GLfloat diameter, Bool wire) -{ glPushMatrix (); glTranslatef (x, y, z); glScalef (diameter, diameter, diameter); - unit_sphere (wire); + unit_sphere (stacks, slices, wire); glPopMatrix (); } @@ -727,7 +596,8 @@ print_title_string (ModeInfo *mi, const char *string, y -= line_height; - glPushAttrib (GL_LIGHTING | GL_DEPTH_TEST); + glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */ + GL_ENABLE_BIT); /* for various glDisable calls */ glDisable (GL_LIGHTING); glDisable (GL_DEPTH_TEST); { @@ -797,6 +667,21 @@ build_molecule (ModeInfo *mi) glEnable(GL_CULL_FACE); } +#if 0 + if (do_labels && !wire) + { + /* This is so all polygons are drawn slightly farther back in the depth + buffer, so that when we render text directly on top of the spheres, + it still shows up. */ + glEnable (GL_POLYGON_OFFSET_FILL); + glPolygonOffset (1.0, (do_bonds ? 10.0 : 35.0)); + } + else + { + glDisable (GL_POLYGON_OFFSET_FILL); + } +#endif + if (!wire) set_atom_color (mi, 0, False); @@ -816,65 +701,76 @@ build_molecule (ModeInfo *mi) } else { + int faces = (scale_down ? TUBE_FACES_2 : TUBE_FACES); +# ifdef SMOOTH_TUBE + int smooth = True; +# else + int smooth = False; +# endif GLfloat thickness = 0.07 * b->strength; GLfloat cap_size = 0.03; if (thickness > 0.3) thickness = 0.3; + tube (from->x, from->y, from->z, to->x, to->y, to->z, - thickness, cap_size, wire); + thickness, cap_size, + faces, smooth, wire); } } - for (i = 0; i < m->natoms; i++) - { - molecule_atom *a = &m->atoms[i]; - int i; + if (!wire && do_atoms) + for (i = 0; i < m->natoms; i++) + { + molecule_atom *a = &m->atoms[i]; + GLfloat size = atom_size (a); + set_atom_color (mi, a, False); + sphere (a->x, a->y, a->z, size, wire); + } - if (!wire && do_atoms) - { - GLfloat size = atom_size (a); - set_atom_color (mi, a, False); - sphere (a->x, a->y, a->z, size, wire); - } + /* Second pass to draw labels, after all atoms and bonds are in place + */ + if (do_labels) + for (i = 0; i < m->natoms; i++) + { + molecule_atom *a = &m->atoms[i]; + int j; - if (do_labels) - { - glPushAttrib (GL_LIGHTING | GL_DEPTH_TEST); - glDisable (GL_LIGHTING); - glDisable (GL_DEPTH_TEST); + if (!wire) + { + glDisable (GL_LIGHTING); +#if 1 + glDisable (GL_DEPTH_TEST); +#endif + } - if (!wire) - set_atom_color (mi, a, True); + if (!wire) + set_atom_color (mi, a, True); - glRasterPos3f (a->x, a->y, a->z); + glRasterPos3f (a->x, a->y, a->z); - { - GLdouble mm[17], pm[17]; - GLint vp[5]; - GLdouble wx=-1, wy=-1, wz=-1; - glGetDoublev (GL_MODELVIEW_MATRIX, mm); - glGetDoublev (GL_PROJECTION_MATRIX, pm); - glGetIntegerv (GL_VIEWPORT, vp); - - /* Convert 3D coordinates to window coordinates */ - gluProject (a->x, a->y, a->z, mm, pm, vp, &wx, &wy, &wz); - - /* Fudge the window coordinates to center the string */ - wx -= string_width (mc->xfont1, a->label) / 2; - wy -= mc->xfont1->descent; - - /* Convert new window coordinates back to 3D coordinates */ - gluUnProject (wx, wy, wz, mm, pm, vp, &wx, &wy, &wz); - glRasterPos3f (wx, wy, wz); - } + /* Before drawing the string, shift the origin to center + the text over the origin of the sphere. */ + glBitmap (0, 0, 0, 0, + -string_width (mc->xfont1, a->label) / 2, + -mc->xfont1->descent, + NULL); - for (i = 0; i < strlen(a->label); i++) - glCallList (mc->font1_dlist + (int)(a->label[i])); + for (j = 0; j < strlen(a->label); j++) + glCallList (mc->font1_dlist + (int)(a->label[j])); - glPopAttrib(); - } - } + /* More efficient to always call glEnable() with correct values + than to call glPushAttrib()/glPopAttrib(), since reading + attributes from GL does a round-trip and stalls the pipeline. + */ + if (!wire) + { + glEnable(GL_LIGHTING); +#if 1 + glEnable(GL_DEPTH_TEST); +#endif + } + } if (do_bbox) draw_bounding_box (mi); @@ -1139,13 +1035,34 @@ parse_pdb_file (molecule *m, const char *name) } +typedef struct { char *atom; int count; } atom_and_count; + +/* When listing the components of a molecule, the convention is to put the + carbon atoms first, the hydrogen atoms second, and the other atom types + sorted alphabetically after that (although for some molecules, the usual + order is different, like for NH(3), but we don't special-case those.) + */ +static int +cmp_atoms (const void *aa, const void *bb) +{ + const atom_and_count *a = (atom_and_count *) aa; + const atom_and_count *b = (atom_and_count *) bb; + if (!a->atom) return 1; + if (!b->atom) return -1; + if (!strcmp(a->atom, "C")) return -1; + if (!strcmp(b->atom, "C")) return 1; + if (!strcmp(a->atom, "H")) return -1; + if (!strcmp(b->atom, "H")) return 1; + return strcmp (a->atom, b->atom); +} + static void generate_molecule_formula (molecule *m) { char *buf = (char *) malloc (m->natoms * 10); char *s = buf; int i; - struct { char *atom; int count; } counts[200]; + atom_and_count counts[200]; memset (counts, 0, sizeof(counts)); *s = 0; for (i = 0; i < m->natoms; i++) @@ -1166,6 +1083,10 @@ generate_molecule_formula (molecule *m) counts[j].count++; } + i = 0; + while (counts[i].atom) i++; + qsort (counts, i, sizeof(*counts), cmp_atoms); + i = 0; while (counts[i].atom) { @@ -1263,10 +1184,11 @@ reshape_molecule (ModeInfo *mi, int width, int height) glMatrixMode(GL_PROJECTION); glLoadIdentity(); - gluPerspective( 30.0, 1/h, 1.0, 100.0 ); + gluPerspective( 30.0, 1/h, 20.0, 40.0 ); gluLookAt( 0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); + glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.0, 0.0, -15.0);