dwallach@soda.berkeley.edu (Dan Wallach) (04/17/91)
(after you cut this, the file should be 578 lines long, and have 13339 chars) Enjoy! Dan Wallach dwallach@soda.berkeley.edu ================ cut here ======= but don't scratch the monitor ============= /* sperm, Version 2.0 Dan Wallach, March-April 1991 <dwallach@soda.berkeley.edu> Added all the cool GL whizzies. Drew Olbrich, February 1991 <po0o+@andrew.cmu.edu> Wrote the original, including the sperm dynamics. Compile with: cc -O3 sperm.c -o sperm -lgl_s -lm To get a complete list of options, run sperm -h Typical fun options % sperm -o uses the overlay planes % sperm -o -m chases the mouse around % sperm -c 3000 get lots of sperm % sperm -b they go into the screen background Intereseting GL tidbits: (by Dan) When you're using the background (root window) there's no way to also have up a normal window with borders. In this case I just foreground()'ed the program, where normally you'd get a prompt back. I found this easier in terms of killing the program later. Also, for some stupid reason, clear() doesn't work on the background of my Personal Iris when a window is moved/iconified. I had to resort to a rectangle fill. Slower, but it works. Somebody may wish to hack the line where I do the rectfi() to just call clear() on their machine and see if it's just me. Turns out, you can't do double buffering on the overlay planes. I tried, but it basically got ignored. Thus, I've disabled it in this program. It may well behave differently on a GTX or VGX machine. To allow -o and -d together, you'll have to make a quick change in main() after the getopt() call. Turns out, the underlay() stuff looks exactly the same as the overlay() stuff. I have no idea why. Initially, I did everything double buffered. When I changed to single buffering, the performance enhancement was amazing. It appears that clearing a region is extremely painful. I suppose I could profile the code to try to squeeze more speed out of it, but it's pretty fast enough, already. If you have less than all the normal bitplanes, I don't guarantee what will happen on your machine. Also, if you have a 14" screen, I make assumptions about the resolution, i.e., not bothering to call getgdesc() but using XMAXSCREEN from <gl/gl.h>... You've been warned. If you have one of those monstrous 4D/340VGX or larger machines, I'd like to know the speed difference between single and double buffering. You could likely parallelize the calculation of where the sperm go next, and speed things up further. Send any more comments/suggestions to <dwallach@soda.berkeley.edu> or <po0o+@andrew.cmu.edu> */ #include <stdio.h> #include <math.h> #include <gl/gl.h> #include <gl/device.h> typedef double POINT[2]; typedef double VECTOR[2]; #define COUNT 200 /* default count */ #define LINEWIDTH 3 #define OVERLAY_BACKGROUND 0 #define OVERLAY_EGGCOLOR 1 #define OVERLAY_SPERMCOLOR 2 #define RGB_EGGCOLOR 0xff00ffff /* yellow */ #define EGG_VELOCITY 6.5 /* max velocity */ #define EGG_DELTA 2.0 /* max change in velocity */ #define VEC_DOT(x, y) (x[0]*y[0] + x[1]*y[1]) #define VEC_LEN(x) (sqrt(x[0]*x[0] + x[1]*x[1])) #define VEC_SET(x, a, b) x[0] = a, x[1] = b #define VEC_COPY(y, x) y[0] = x[0], y[1] = x[1] #define VEC_NEG(x) x[0] = -x[0], x[1] = -x[1] #define VEC_ADD(z, x, y) z[0] = x[0] + y[0], z[1] = x[1] + y[1] #define VEC_SUB(z, x, y) z[0] = x[0] - y[0], z[1] = x[1] - y[1] #define VEC_MULT(x, a) x[0] *= a, x[1] *= a #define VEC_DIV(x, a) x[0] /= a, x[1] /= a #define VEC_ADDS(z, x, a, y) z[0] = x[0] + (a)*y[0], z[1] = x[1] + (a)*y[1] #define VEC_NORM(x) { double l = VEC_LEN(x); VEC_DIV(x, l); } int count = COUNT; int line_width = LINEWIDTH; int background_mode = FALSE, overlay_mode = FALSE, underlay_mode = FALSE, rgb_mode = FALSE, tiny_window = FALSE, mouse_follow = FALSE, use_dbuffer = FALSE; short mousex, mousey; long xmax, ymax; long xorigin, yorigin; double old_eggx, old_eggy, eggx, eggy, eggdx, eggdy; double rad_scale = 3.5; int index = 0; typedef struct { POINT x; VECTOR v; double sine, cosine; double vel; long color; int oldx1, oldy1, oldx2, oldy2; /* used in singlebuffer mode to clear */ } SPERM; SPERM *sperm; void init_egg(void) { eggx = drand48() * (xmax+1); eggy = drand48() * (ymax+1); old_eggx = eggx; old_eggy = eggy; eggdx = drand48() * EGG_VELOCITY - (EGG_VELOCITY / 2); eggdy = drand48() * EGG_VELOCITY - (EGG_VELOCITY / 2); } void move_egg(void) { int new_eggx, new_eggy; new_eggx = eggx + eggdx; new_eggy = eggy + eggdy; if(new_eggx > xmax) { new_eggx = xmax; eggdx *= -1; } if(new_eggy > ymax) { new_eggy = ymax; eggdy *= -1; } if(new_eggx < 0) { new_eggx = 0; eggdx *= -1; } if(new_eggy < 0) { new_eggy = 0; eggdy *= -1; } if(!use_dbuffer) /* erase the old egg */ { if(rgb_mode) cpack(0xff000000); /* black */ else color(OVERLAY_BACKGROUND); move2(old_eggx, old_eggy); draw2(eggx, eggy); } if(rgb_mode) cpack(RGB_EGGCOLOR); else color(OVERLAY_EGGCOLOR); move2(eggx, eggy); draw2(new_eggx, new_eggy); old_eggx = eggx; old_eggy = eggy; eggx = new_eggx; eggy = new_eggy; eggdx += drand48() * EGG_DELTA - (EGG_DELTA / 2); eggdy += drand48() * EGG_DELTA - (EGG_DELTA / 2); /* keep some kind of bounds on the egg velocity */ if(eggdx > EGG_VELOCITY) eggdx = EGG_VELOCITY; if(eggdy > EGG_VELOCITY) eggdy = EGG_VELOCITY; if(eggdx < -EGG_VELOCITY) eggdx = -EGG_VELOCITY; if(eggdy < -EGG_VELOCITY) eggdy = -EGG_VELOCITY; } init_display() { if(background_mode) { /* * due to idiocy of imakebackground(), we can't get a window * up at the same time, at least, without resorting to NeWS * I call foreground() so the user can kill the process without * doing a ps and kill... */ imakebackground(); foreground(); /* no other way to keep track of the program */ } if(tiny_window) prefposition(10,450,10,10); winopen("Sperm"); wintitle("Sperm 2.0 by Drew Olbrich and Dan Wallach"); /* * we want to get the buffering right beforehand so we can * clear what's in the tiny_window BEFORE we set up the * funky display modes -- if we don't do this, the tiny * window is likely to have crap in it */ if(use_dbuffer) { doublebuffer(); gconfig(); frontbuffer(TRUE); backbuffer(TRUE); } else { singlebuffer(); gconfig(); } if(rgb_mode) cpack(0xff000000); else color(OVERLAY_BACKGROUND); clear(); if(use_dbuffer) frontbuffer(FALSE); if(tiny_window) { fullscrn(); xmax = XMAXSCREEN; ymax = YMAXSCREEN; xorigin = 0; yorigin = 0; } if(overlay_mode) { drawmode(OVERDRAW); overlay(2); } else if (underlay_mode) { drawmode(UNDERDRAW); underlay(2); } else { rgb_mode = TRUE; RGBmode(); } linewidth(line_width); gconfig(); qreset(); qdevice(ESCKEY); qdevice(WINCLOSE); qdevice(WINQUIT); qdevice(WINSHUT); if(mouse_follow) { qdevice(MOUSEX); qdevice(MOUSEY); } if(!rgb_mode) { mapcolor(OVERLAY_EGGCOLOR, 255, 255, 0); /* yellow */ mapcolor(OVERLAY_SPERMCOLOR, 100, 100, 255); /* purple */ } qenter(REDRAW, 0); /* force some things into a sane state */ } init_sperm() { int i; double angle; if((sperm = (SPERM *)malloc(sizeof (SPERM) * count)) == NULL) { perror("malloc"); fprintf(stderr, "Not enough memory to hold all your sperm, hmmm.....\n"); exit(1); } for (i = 0; i < count; i++) { sperm[i].oldx1 = sperm[i].oldy1 = sperm[i].oldx2 = sperm[i].oldy2 = 0; sperm[i].x[0] = drand48()*(xmax+1); sperm[i].x[1] = drand48()*(ymax+1); VEC_SET(sperm[i].v, 0.0, 0.0); angle = drand48()*10.0 + 5.0; sperm[i].sine = sin(angle*M_PI/180.0); sperm[i].cosine = cos(angle*M_PI/180.0); sperm[i].vel = drand48()*4.0 + 4.0; if(rgb_mode) /* a shade of blue from half-intensity to max, with red tint */ sperm[i].color = 0xffa03050 + ((lrand48() % 0x60L) << 16) /* blue */ + (lrand48() % 0x1fL); /* red */ else /* we're in OVERLAY mode */ sperm[i].color = OVERLAY_SPERMCOLOR; } } dynamics() { int i; VECTOR w, old_w; POINT p; double r, dot, temp; VECTOR mouse; int x1, y1, x2, y2; VECTOR target; if(mouse_follow) { mouse[0] = mousex; mouse[1] = mousey; } else { mouse[0] = eggx; mouse[1] = eggy; } for (i=0 ; i < count; i++) { x1 = (int) sperm[i].x[0]; y1 = (int) sperm[i].x[1]; VEC_COPY(target, mouse); VEC_SUB(w, sperm[i].x, target); VEC_NORM(w); VEC_COPY(old_w, w); w[0] = old_w[0]*sperm[i].cosine - old_w[1]*sperm[i].sine; w[1] = old_w[1]*sperm[i].cosine + old_w[0]*sperm[i].sine; VEC_ADDS(p, target, rad_scale*(160.0 - sperm[i].vel*20.0), w); VEC_SUB(w, p, sperm[i].x); VEC_NORM(w); VEC_ADDS(sperm[i].v, sperm[i].v, 1.0, w); VEC_NORM(sperm[i].v); VEC_MULT(sperm[i].v, sperm[i].vel); VEC_ADD(sperm[i].x, sperm[i].x, sperm[i].v); x2 = (int) sperm[i].x[0]; y2 = (int) sperm[i].x[1]; if(!use_dbuffer) { if(rgb_mode) cpack(0xff000000); else color(OVERLAY_BACKGROUND); move2i(sperm[i].oldx1, sperm[i].oldy1); draw2i(sperm[i].oldx2, sperm[i].oldy2); } if(rgb_mode) cpack(sperm[i].color); else color(sperm[i].color); move2i(x1, y1); draw2i(x2, y2); if(!use_dbuffer) { sperm[i].oldx1 = x1; sperm[i].oldx2 = x2; sperm[i].oldy1 = y1; sperm[i].oldy2 = y2; } } /* * deal with the egg last, so it always gets drawn on top of the * screen full of sperm */ if(!mouse_follow) move_egg(); } main_loop() { int dev; short data; for(;;) { while(qtest()) switch(dev = qread(&data)) { case MOUSEX: mousex = data - (short)xorigin; break; case MOUSEY: mousey = data - (short)yorigin; break; case REDRAW: if(!tiny_window) { getsize(&xmax, &ymax); getorigin(&xorigin, &yorigin); reshapeviewport(); ortho2(0, xmax, 0, ymax); } if(rgb_mode) cpack(0xff000000); else color(OVERLAY_BACKGROUND); if(use_dbuffer) { frontbuffer(TRUE); backbuffer(TRUE); } /* * this works around a bug on the Personal Iris -- * for some reason, clear() doesn't work in the * background... Oh, well. */ if(background_mode) rectfi(0, 0, xmax, ymax); /* HACK */ else clear(); if(use_dbuffer) frontbuffer(FALSE); break; default: /* we don't care */ break; case ESCKEY: case WINCLOSE: case WINQUIT: case WINSHUT: if(tiny_window) { if(use_dbuffer) frontbuffer(TRUE); color(OVERLAY_BACKGROUND); clear(); } exit(0); } if(use_dbuffer) { if(rgb_mode) cpack(0xff000000); else color(OVERLAY_BACKGROUND); clear(); } /* * when we have the "tiny" window, we don't normally get any * data about where the mouse is, because it isn't often in * the window. In this case, we need to get the mouse by hand. */ if(tiny_window && mouse_follow) { mousex = getvaluator(MOUSEX); mousey = getvaluator(MOUSEY); } dynamics(); /* does everything... */ if(use_dbuffer) swapbuffers(); } } main(int argc, char **argv) { int c, errflg = FALSE; extern char *optarg; extern int optind; while ((c = getopt(argc, argv, "duhbomc:w:")) != -1) switch(c) { case 'd': if(overlay_mode || underlay_mode) { errflg++; fprintf(stderr, "Can't use double buffering in over/underlay planes.\n"); } use_dbuffer = TRUE; break; case 'w': line_width = atoi(optarg); break; case 'c': count = atoi(optarg); break; case 'b': if(overlay_mode || underlay_mode) errflg++; background_mode = TRUE; break; case 'o': if(background_mode || underlay_mode) errflg++; if(use_dbuffer) { errflg++; fprintf(stderr, "Can't use double buffering in over/underlay planes.\n"); } overlay_mode = TRUE; break; case 'u': if(background_mode || overlay_mode) errflg++; if(use_dbuffer) { errflg++; fprintf(stderr, "Can't use double buffering in over/underlay planes.\n"); } underlay_mode = TRUE; break; case 'm': mouse_follow = TRUE; break; case '?': case 'h': errflg = TRUE; } if(errflg) { fprintf(stderr, "Usage: %s [-d] [-m] [-w sperm_width] [-c sperm_count] [-b | -o | -u]\n", argv[0]); fprintf(stderr, " -m == follow the mouse\n"); fprintf(stderr, " -w # == pixel width of sperm (default = %d)\n", LINEWIDTH); fprintf(stderr, " -c # == number of sperm (default = %d)\n", COUNT); fprintf(stderr, " -b == screen background\n"); fprintf(stderr, " -o == overlay mode\n"); fprintf(stderr, " -u == underlay mode\n"); fprintf(stderr, " -d == use double buffering\n"); exit(1); } tiny_window = overlay_mode || underlay_mode; srand48(time(0)); init_display(); init_sperm(); if(! mouse_follow) init_egg(); main_loop(); }