X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=hacks%2Fglx%2Fmolecule.c;h=992ff7ca1417f4bdf198ef6fa0fb77e53729d84b;hb=9c9d475ff889ed8be02e8ce8c17da28b93278fca;hp=02b8362461a5285bf26fd8661f602fb715da9085;hpb=a94197e76a5dea5cb60542840809d6c20d0abbf3;p=xscreensaver diff --git a/hacks/glx/molecule.c b/hacks/glx/molecule.c index 02b83624..992ff7ca 100644 --- a/hacks/glx/molecule.c +++ b/hacks/glx/molecule.c @@ -1,4 +1,4 @@ -/* molecule, Copyright (c) 2001 Jamie Zawinski +/* molecule, Copyright (c) 2001-2004 Jamie Zawinski * Draws molecules, based on coordinates from PDB (Protein Data Base) files. * * Permission to use, copy, modify, distribute, and sell this software and its @@ -16,63 +16,20 @@ Good source of PDB files: http://www.sci.ouc.bc.ca/chem/molecule/molecule.html - - TO DO: - - - 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 +#include +#include +#include #include #define PROGCLASS "Molecule" #define HACK_INIT init_molecule #define HACK_DRAW draw_molecule #define HACK_RESHAPE reshape_molecule +#define HACK_HANDLE_EVENT molecule_handle_event +#define EVENT_MASK PointerMotionMask #define molecule_opts xlockmore_opts #define DEF_TIMEOUT "20" @@ -109,11 +66,15 @@ #include "colors.h" #include "sphere.h" #include "tube.h" +#include "rotator.h" +#include "gltrackball.h" #ifdef USE_GL /* whole file */ #include #include +#include +#include #include #define SPHERE_SLICES 16 /* how densely to render spheres */ @@ -133,6 +94,11 @@ static int scale_down; #define TUBE_FACES_2 3 +# ifdef __GNUC__ + __extension__ /* don't warn about "string length is greater than the length + ISO C89 compilers are required to support" when includng + the following data file... */ +# endif const char * const builtin_pdb_data[] = { # include "molecules.h" }; @@ -153,6 +119,7 @@ typedef struct { static atom_data all_atom_data[] = { { "H", 1.17, 0, "White", "Grey70", { 0, }}, { "C", 1.75, 0, "Grey60", "White", { 0, }}, + { "CA", 1.80, 0, "Blue", "LightBlue", { 0, }}, { "N", 1.55, 0, "LightSteelBlue3", "SlateBlue1", { 0, }}, { "O", 1.40, 0, "Red", "LightPink", { 0, }}, { "P", 1.28, 0, "MediumPurple", "PaleVioletRed", { 0, }}, @@ -186,13 +153,9 @@ typedef struct { typedef struct { GLXContext *glx_context; - - GLfloat rotx, roty, rotz; /* current object rotation */ - GLfloat dx, dy, dz; /* current rotational velocity */ - GLfloat ddx, ddy, ddz; /* current rotational acceleration */ - GLfloat d_max; /* max velocity */ - - Bool spin_x, spin_y, spin_z; + rotator *rot; + trackball_state *trackball; + Bool button_down_p; GLfloat molecule_size; /* max dimension of molecule bounding box */ @@ -230,7 +193,7 @@ static XrmOptionDescRec opts[] = { { "-molecule", ".molecule", XrmoptionSepArg, 0 }, { "-timeout",".timeout",XrmoptionSepArg, 0 }, { "-spin", ".spin", XrmoptionSepArg, 0 }, - { "+spin", ".spin", XrmoptionNoArg, "False" }, + { "+spin", ".spin", XrmoptionNoArg, "" }, { "-wander", ".wander", XrmoptionNoArg, "True" }, { "+wander", ".wander", XrmoptionNoArg, "False" }, { "-labels", ".labels", XrmoptionNoArg, "True" }, @@ -246,15 +209,15 @@ static XrmOptionDescRec opts[] = { }; static argtype vars[] = { - {(caddr_t *) &molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String}, - {(caddr_t *) &timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int}, - {(caddr_t *) &do_spin, "spin", "Spin", DEF_SPIN, t_String}, - {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool}, - {(caddr_t *) &do_labels, "labels", "Labels", DEF_LABELS, t_Bool}, - {(caddr_t *) &do_titles, "titles", "Titles", DEF_TITLES, t_Bool}, - {(caddr_t *) &do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool}, - {(caddr_t *) &do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool}, - {(caddr_t *) &do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool}, + {&molecule_str, "molecule", "Molecule", DEF_MOLECULE,t_String}, + {&timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int}, + {&do_spin, "spin", "Spin", DEF_SPIN, t_String}, + {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool}, + {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool}, + {&do_titles, "titles", "Titles", DEF_TITLES, t_Bool}, + {&do_atoms, "atoms", "Atoms", DEF_ATOMS, t_Bool}, + {&do_bonds, "bonds", "Bonds", DEF_BONDS, t_Bool}, + {&do_bbox, "bbox", "BBox", DEF_BBOX, t_Bool}, }; ModeSpecOpt molecule_opts = {countof(opts), opts, countof(vars), vars, NULL}; @@ -347,7 +310,7 @@ get_atom_data (const char *atom_name) for (i = 0; i < countof(all_atom_data); i++) { d = &all_atom_data[i]; - if (!strcmp (n, all_atom_data[i].name)) + if (!strcasecmp (n, all_atom_data[i].name)) break; } @@ -590,10 +553,12 @@ ensure_bounding_box_visible (ModeInfo *mi) static void print_title_string (ModeInfo *mi, const char *string, - GLfloat x, GLfloat y, GLfloat line_height) + GLfloat x, GLfloat y, XFontStruct *font) { molecule_configuration *mc = &mcs[MI_SCREEN(mi)]; - + GLfloat line_height = font->ascent + font->descent; + GLfloat sub_shift = (line_height * 0.3); + y -= line_height; glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */ @@ -610,6 +575,8 @@ print_title_string (ModeInfo *mi, const char *string, glPushMatrix(); { int i; + int x2 = x; + Bool sub_p = False; glLoadIdentity(); gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height); @@ -621,9 +588,27 @@ print_title_string (ModeInfo *mi, const char *string, { char c = string[i]; if (c == '\n') - glRasterPos2f (x, (y -= line_height)); + { + glRasterPos2f (x, (y -= line_height)); + x2 = x; + } + else if (c == '(' && (isdigit (string[i+1]))) + { + sub_p = True; + glRasterPos2f (x2, (y -= sub_shift)); + } + else if (c == ')' && sub_p) + { + sub_p = False; + glRasterPos2f (x2, (y += sub_shift)); + } else - glCallList (mc->font2_dlist + (int)(c)); + { + glCallList (mc->font2_dlist + (int)(c)); + x2 += (font->per_char + ? font->per_char[c - font->min_char_or_byte2].width + : font->min_bounds.width); + } } } glPopMatrix(); @@ -667,21 +652,6 @@ 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); @@ -715,7 +685,7 @@ build_molecule (ModeInfo *mi) tube (from->x, from->y, from->z, to->x, to->y, to->z, thickness, cap_size, - faces, smooth, wire); + faces, smooth, False, wire); } } @@ -728,57 +698,13 @@ build_molecule (ModeInfo *mi) 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 (!wire) - { - glDisable (GL_LIGHTING); -#if 1 - glDisable (GL_DEPTH_TEST); -#endif - } - - if (!wire) - set_atom_color (mi, a, True); - - glRasterPos3f (a->x, a->y, a->z); - - /* 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 (j = 0; j < strlen(a->label); j++) - glCallList (mc->font1_dlist + (int)(a->label[j])); - - /* 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); if (do_titles && m->label && *m->label) print_title_string (mi, m->label, 10, mi->xgwa.height - 10, - mc->xfont2->ascent + mc->xfont2->descent); + mc->xfont2); } @@ -923,6 +849,12 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line) ss = name + strlen(name)-1; while (isspace(*ss) && ss > name) *ss-- = 0; + ss = name + 1; + while(*ss) + { + *ss = tolower(*ss); + ss++; + } sscanf (s + 32, " %f %f %f ", &x, &y, &z); /* fprintf (stderr, "%s: %s: %d: atom: %d \"%s\" %9.4f %9.4f %9.4f\n", @@ -955,7 +887,7 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line) else if (!strncmp (s, "CONECT ", 7)) { int atoms[11]; - int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d ", + int i = sscanf (s + 8, " %d %d %d %d %d %d %d %d %d %d %d %d ", &atoms[0], &atoms[1], &atoms[2], &atoms[3], &atoms[4], &atoms[5], &atoms[6], &atoms[7], &atoms[8], &atoms[9], &atoms[10], &atoms[11]); @@ -989,7 +921,7 @@ parse_pdb_data (molecule *m, const char *data, const char *filename, int line) } -static void +static int parse_pdb_file (molecule *m, const char *name) { FILE *in; @@ -1003,7 +935,7 @@ parse_pdb_file (molecule *m, const char *name) char *buf = (char *) malloc(1024 + strlen(name)); sprintf(buf, "%s: error reading \"%s\"", progname, name); perror(buf); - exit (1); + return -1; } buf = (char *) malloc (buf_size); @@ -1023,7 +955,7 @@ parse_pdb_file (molecule *m, const char *name) { fprintf (stderr, "%s: file %s contains no atomic coordinates!\n", progname, name); - exit (1); + return -1; } if (!m->nbonds && do_bonds) @@ -1032,6 +964,8 @@ parse_pdb_file (molecule *m, const char *name) progname, name); do_bonds = 0; } + + return 0; } @@ -1040,7 +974,7 @@ 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.) + order is different: we special-case a few of those.) */ static int cmp_atoms (const void *aa, const void *bb) @@ -1056,6 +990,8 @@ cmp_atoms (const void *aa, const void *bb) return strcmp (a->atom, b->atom); } +static void special_case_formula (char *f); + static void generate_molecule_formula (molecule *m) { @@ -1099,6 +1035,8 @@ generate_molecule_formula (molecule *m) i++; } + special_case_formula (buf); + if (!m->label) m->label = strdup(""); s = (char *) malloc (strlen (m->label) + strlen (buf) + 2); strcpy (s, m->label); @@ -1109,6 +1047,21 @@ generate_molecule_formula (molecule *m) m->label = s; } +/* thanks to Rene Uittenbogaard */ +static void +special_case_formula (char *f) +{ + if (!strcmp(f, "H(2)Be")) strcpy(f, "BeH(2)"); + else if (!strcmp(f, "H(3)B")) strcpy(f, "BH(3)"); + else if (!strcmp(f, "H(3)N")) strcpy(f, "NH(3)"); + else if (!strcmp(f, "CHN")) strcpy(f, "HCN"); + else if (!strcmp(f, "CKN")) strcpy(f, "KCN"); + else if (!strcmp(f, "H(4)N(2)")) strcpy(f, "N(2)H(4)"); + else if (!strcmp(f, "Cl(3)P")) strcpy(f, "PCl(3)"); + else if (!strcmp(f, "Cl(5)P")) strcpy(f, "PCl(5)"); +} + + static void insert_vertical_whitespace (char *string) { @@ -1132,41 +1085,137 @@ load_molecules (ModeInfo *mi) { molecule_configuration *mc = &mcs[MI_SCREEN(mi)]; int wire = MI_IS_WIREFRAME(mi); + Bool verbose_p = False; + int i; + + mc->nmolecules = 0; + if (molecule_str && *molecule_str && + strcmp(molecule_str, "(default)")) /* try external PDB files */ + { + /* The -molecule option can point to a .pdb file, or to + a directory of them. + */ + struct stat st; + int nfiles = 0; + int list_size = 0; + char **files = 0; + int molecule_ctr; + + if (!stat (molecule_str, &st) && + S_ISDIR (st.st_mode)) + { + char buf [255]; + DIR *pdb_dir; + struct dirent *dentry; + + pdb_dir = opendir (molecule_str); + if (! pdb_dir) + { + sprintf (buf, "%.100s: %.100s", progname, molecule_str); + perror (buf); + exit (1); + } + + if (verbose_p) + fprintf (stderr, "%s: directory %s\n", progname, molecule_str); + + nfiles = 0; + list_size = 100; + files = (char **) calloc (sizeof(*files), list_size); + + while ((dentry = readdir (pdb_dir))) + { + int L = strlen (dentry->d_name); + if (L > 4 && !strcasecmp (dentry->d_name + L - 4, ".pdb")) + { + char *fn; + if (nfiles >= list_size-1) + { + list_size = (list_size + 10) * 1.2; + files = (char **) + realloc (files, list_size * sizeof(*files)); + if (!files) + { + OOM: + fprintf (stderr, "%s: out of memory (%d files)\n", + progname, nfiles); + exit (1); + } + } + + fn = (char *) malloc (strlen (molecule_str) + L + 10); + if (!fn) goto OOM; + strcpy (fn, molecule_str); + if (fn[strlen(fn)-1] != '/') strcat (fn, "/"); + strcat (fn, dentry->d_name); + files[nfiles++] = fn; + if (verbose_p) + fprintf (stderr, "%s: file %s\n", progname, fn); + } + } + closedir (pdb_dir); + + if (nfiles == 0) + fprintf (stderr, "%s: no .pdb files in directory %s\n", + progname, molecule_str); + } + else + { + files = (char **) malloc (sizeof (*files)); + nfiles = 1; + files[0] = strdup (molecule_str); + if (verbose_p) + fprintf (stderr, "%s: file %s\n", progname, molecule_str); + } + + mc->nmolecules = nfiles; + mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules); + molecule_ctr = 0; + for (i = 0; i < mc->nmolecules; i++) + { + if (verbose_p) + fprintf (stderr, "%s: reading %s\n", progname, files[i]); + if (!parse_pdb_file (&mc->molecules[molecule_ctr], files[i])) + { + if ((wire || !do_atoms) && + !do_labels && + mc->molecules[molecule_ctr].nbonds == 0) + { + /* If we're not drawing atoms (e.g., wireframe mode), and + there is no bond info, then make sure labels are turned + on, or we'll be looking at a black screen... */ + fprintf (stderr, "%s: %s: no bonds: turning -label on.\n", + progname, files[i]); + do_labels = 1; + } + free (files[i]); + files[i] = 0; + molecule_ctr++; + } + } + + free (files); + files = 0; + mc->nmolecules = molecule_ctr; + } - if (!molecule_str || !*molecule_str || - !strcmp(molecule_str, "(default)")) /* do the builtins */ + if (mc->nmolecules == 0) /* do the builtins if no files */ { - int i; mc->nmolecules = countof(builtin_pdb_data); mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules); for (i = 0; i < mc->nmolecules; i++) { char name[100]; sprintf (name, "", i); + if (verbose_p) fprintf (stderr, "%s: reading %s\n", progname, name); parse_pdb_data (&mc->molecules[i], builtin_pdb_data[i], name, 1); - generate_molecule_formula (&mc->molecules[i]); - insert_vertical_whitespace ((char *) mc->molecules[i].label); } } - else /* Load a file */ + + for (i = 0; i < mc->nmolecules; i++) { - int i = 0; - mc->nmolecules = 1; - mc->molecules = (molecule *) calloc (sizeof (molecule), mc->nmolecules); - parse_pdb_file (&mc->molecules[i], molecule_str); generate_molecule_formula (&mc->molecules[i]); insert_vertical_whitespace ((char *) mc->molecules[i].label); - - if ((wire || !do_atoms) && - !do_labels && - mc->molecules[i].nbonds == 0) - { - /* If we're not drawing atoms (e.g., wireframe mode), and - there is no bond info, then make sure labels are turned on, - or we'll be looking at a black screen... */ - fprintf (stderr, "%s: no bonds: turning -label on.\n", progname); - do_labels = 1; - } } } @@ -1183,15 +1232,13 @@ reshape_molecule (ModeInfo *mi, int width, int height) glMatrixMode(GL_PROJECTION); glLoadIdentity(); - - 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); + gluPerspective (30.0, 1/h, 20.0, 40.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - glTranslatef(0.0, 0.0, -15.0); + gluLookAt( 0.0, 0.0, 30.0, + 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); } @@ -1200,8 +1247,14 @@ reshape_molecule (ModeInfo *mi, int width, int height) 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; @@ -1209,90 +1262,52 @@ gl_init (ModeInfo *mi) } -/* lifted from lament.c */ -#define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n)))) -#define RANDSIGN() ((random() & 1) ? 1 : -1) - static void -rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v) +startup_blurb (ModeInfo *mi) { - double ppos = *pos; - - /* tick position */ - if (ppos < 0) - ppos = -(ppos + *v); - else - ppos += *v; - - if (ppos > 1.0) - ppos -= 1.0; - else if (ppos < 0) - ppos += 1.0; - - if (ppos < 0) abort(); - if (ppos > 1.0) abort(); - *pos = (*pos > 0 ? ppos : -ppos); + molecule_configuration *mc = &mcs[MI_SCREEN(mi)]; + const char *s = "Constructing molecules..."; + print_title_string (mi, s, + mi->xgwa.width - (string_width (mc->xfont2, s) + 40), + 10 + mc->xfont2->ascent + mc->xfont2->descent, + mc->xfont2); + glFinish(); + glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi)); +} - /* accelerate */ - *v += *dv; +Bool +molecule_handle_event (ModeInfo *mi, XEvent *event) +{ + molecule_configuration *mc = &mcs[MI_SCREEN(mi)]; - /* clamp velocity */ - if (*v > max_v || *v < -max_v) + if (event->xany.type == ButtonPress && + event->xbutton.button & Button1) { - *dv = -*dv; + mc->button_down_p = True; + gltrackball_start (mc->trackball, + event->xbutton.x, event->xbutton.y, + MI_WIDTH (mi), MI_HEIGHT (mi)); + return True; } - /* If it stops, start it going in the other direction. */ - else if (*v < 0) + else if (event->xany.type == ButtonRelease && + event->xbutton.button & Button1) { - if (random() % 4) - { - *v = 0; - - /* keep going in the same direction */ - if (random() % 2) - *dv = 0; - else if (*dv < 0) - *dv = -*dv; - } - else - { - /* reverse gears */ - *v = -*v; - *dv = -*dv; - *pos = -*pos; - } + mc->button_down_p = False; + return True; } - - /* Alter direction of rotational acceleration randomly. */ - if (! (random() % 120)) - *dv = -*dv; - - /* Change acceleration very occasionally. */ - if (! (random() % 200)) + else if (event->xany.type == MotionNotify && + mc->button_down_p) { - if (*dv == 0) - *dv = 0.00001; - else if (random() & 1) - *dv *= 1.2; - else - *dv *= 0.8; + gltrackball_track (mc->trackball, + event->xmotion.x, event->xmotion.y, + MI_WIDTH (mi), MI_HEIGHT (mi)); + return True; } -} - -static void -startup_blurb (ModeInfo *mi) -{ - molecule_configuration *mc = &mcs[MI_SCREEN(mi)]; - const char *s = "Constructing molecules..."; - print_title_string (mi, s, - mi->xgwa.width - (string_width (mc->xfont2, s) + 40), - 10 + mc->xfont2->ascent + mc->xfont2->descent, - mc->xfont2->ascent + mc->xfont2->descent); - glFinish(); - glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi)); + return False; } + void init_molecule (ModeInfo *mi) { @@ -1320,28 +1335,17 @@ init_molecule (ModeInfo *mi) wire = MI_IS_WIREFRAME(mi); - mc->rotx = frand(1.0) * RANDSIGN(); - mc->roty = frand(1.0) * RANDSIGN(); - mc->rotz = frand(1.0) * RANDSIGN(); - - /* bell curve from 0-6 degrees, avg 3 */ - mc->dx = (frand(1) + frand(1) + frand(1)) / (360/2); - mc->dy = (frand(1) + frand(1) + frand(1)) / (360/2); - mc->dz = (frand(1) + frand(1) + frand(1)) / (360/2); - - mc->d_max = mc->dx * 2; - - mc->ddx = 0.00006 + frand(0.00003); - mc->ddy = 0.00006 + frand(0.00003); - mc->ddz = 0.00006 + frand(0.00003); - { + Bool spinx=False, spiny=False, spinz=False; + double spin_speed = 2.0; + double wander_speed = 0.03; + char *s = do_spin; while (*s) { - if (*s == 'x' || *s == 'X') mc->spin_x = 1; - else if (*s == 'y' || *s == 'Y') mc->spin_y = 1; - else if (*s == 'z' || *s == 'Z') mc->spin_z = 1; + if (*s == 'x' || *s == 'X') spinx = True; + else if (*s == 'y' || *s == 'Y') spiny = True; + else if (*s == 'z' || *s == 'Z') spinz = True; else { fprintf (stderr, @@ -1351,6 +1355,14 @@ init_molecule (ModeInfo *mi) } s++; } + + mc->rot = make_rotator (spinx ? spin_speed : 0, + spiny ? spin_speed : 0, + spinz ? spin_speed : 0, + 1.0, + do_wander ? wander_speed : 0, + (spinx && spiny && spinz)); + mc->trackball = gltrackball_init (); } mc->molecule_dlist = glGenLists(1); @@ -1368,6 +1380,94 @@ init_molecule (ModeInfo *mi) } +/* Put the labels on the atoms. + This can't be a part of the display list because of the games + we play with the translation matrix. + */ +void +draw_labels (ModeInfo *mi) +{ + molecule_configuration *mc = &mcs[MI_SCREEN(mi)]; + int wire = MI_IS_WIREFRAME(mi); + molecule *m = &mc->molecules[mc->which]; + int i, j; + + if (!do_labels) + return; + + if (!wire) + glDisable (GL_LIGHTING); /* don't light fonts */ + + for (i = 0; i < m->natoms; i++) + { + molecule_atom *a = &m->atoms[i]; + GLfloat size = atom_size (a); + GLfloat m[4][4]; + + glPushMatrix(); + + if (!wire) + set_atom_color (mi, a, True); + + /* First, we translate the origin to the center of the atom. + + Then we retrieve the prevailing modelview matrix (which + includes any rotation, wandering, and user-trackball-rolling + of the scene. + + We set the top 3x3 cells of that matrix to be the identity + matrix. This removes all rotation from the matrix, while + leaving the translation alone. This has the effect of + leaving the prevailing coordinate system perpendicular to + the camera view: were we to draw a square face, it would + be in the plane of the screen. + + Now we translate by `size' toward the viewer -- so that the + origin is *just in front* of the ball. + + Then we draw the label text, allowing the depth buffer to + do its work: that way, labels on atoms will be occluded + properly when other atoms move in front of them. + + This technique (of neutralizing rotation relative to the + observer, after both rotations and translations have been + applied) is known as "billboarding". + */ + + glTranslatef(a->x, a->y, a->z); /* get matrix */ + glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */ + m[0][0] = 1; m[1][0] = 0; m[2][0] = 0; + m[0][1] = 0; m[1][1] = 1; m[2][1] = 0; + m[0][2] = 0; m[1][2] = 0; m[2][2] = 1; + glLoadIdentity(); /* reset modelview */ + glMultMatrixf (&m[0][0]); /* replace with ours */ + + glTranslatef (0, 0, (size * 1.1)); /* move toward camera */ + + glRasterPos3f (0, 0, 0); /* draw text here */ + + /* 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 (j = 0; j < strlen(a->label); j++) + glCallList (mc->font1_dlist + (int)(a->label[j])); + + glPopMatrix(); + } + + /* 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); +} + + void draw_molecule (ModeInfo *mi) { @@ -1381,7 +1481,8 @@ draw_molecule (ModeInfo *mi) if (!mc->glx_context) return; - if (last + timeout <= now) /* randomize molecules every -timeout seconds */ + if (last + timeout <= now && /* randomize molecules every -timeout seconds */ + !mc->button_down_p) { if (mc->nmolecules == 1) { @@ -1427,43 +1528,24 @@ draw_molecule (ModeInfo *mi) glScalef(1.1, 1.1, 1.1); { - GLfloat x, y, z; - - if (do_wander) - { - static int frame = 0; - -# define SINOID(SCALE,SIZE) \ - ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2) - - x = SINOID(0.031, 9.0); - y = SINOID(0.023, 9.0); - z = SINOID(0.017, 9.0); - frame++; - glTranslatef(x, y, z); - } - - if (mc->spin_x || mc->spin_y || mc->spin_z) - { - x = mc->rotx; - y = mc->roty; - z = mc->rotz; - if (x < 0) x = 1 - (x + 1); - if (y < 0) y = 1 - (y + 1); - if (z < 0) z = 1 - (z + 1); - - if (mc->spin_x) glRotatef(x * 360, 1.0, 0.0, 0.0); - if (mc->spin_y) glRotatef(y * 360, 0.0, 1.0, 0.0); - if (mc->spin_z) glRotatef(z * 360, 0.0, 0.0, 1.0); - - rotate(&mc->rotx, &mc->dx, &mc->ddx, mc->d_max); - rotate(&mc->roty, &mc->dy, &mc->ddy, mc->d_max); - rotate(&mc->rotz, &mc->dz, &mc->ddz, mc->d_max); - } + double x, y, z; + get_position (mc->rot, &x, &y, &z, !mc->button_down_p); + glTranslatef((x - 0.5) * 9, + (y - 0.5) * 9, + (z - 0.5) * 9); + + gltrackball_rotate (mc->trackball); + + get_rotation (mc->rot, &x, &y, &z, !mc->button_down_p); + glRotatef (x * 360, 1.0, 0.0, 0.0); + glRotatef (y * 360, 0.0, 1.0, 0.0); + glRotatef (z * 360, 0.0, 0.0, 1.0); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glCallList (mc->molecule_dlist); + draw_labels (mi); + glPopMatrix (); if (mi->fps_p) do_fps (mi);