- 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 <X11/Intrinsic.h>
#include "xlockmore.h"
#include "colors.h"
+#include "sphere.h"
+#include "tube.h"
#ifdef USE_GL /* whole file */
+#include <stdlib.h>
#include <ctype.h>
#include <GL/glu.h>
-
#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
{ "-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" },
/* 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 ();
}
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);
{
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);
}
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);
}
+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++)
counts[j].count++;
}
+ i = 0;
+ while (counts[i].atom) i++;
+ qsort (counts, i, sizeof(*counts), cmp_atoms);
+
i = 0;
while (counts[i].atom)
{
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);
static void
gl_init (ModeInfo *mi)
{
- static GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
+ static GLfloat pos[4] = {1.0, 0.4, 0.9, 0.0};
+ static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
+ static GLfloat dif[4] = {0.8, 0.8, 0.8, 1.0};
+ static GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
glLightfv(GL_LIGHT0, GL_POSITION, pos);
+ glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
+ glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
+ glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
orig_do_labels = do_labels;
orig_do_bonds = do_bonds;