Sperm, V2.1 (NOT a patch, a new posting)

Dan Wallach dwallach at soda.berkeley.edu
Sun Apr 28 16:31:12 AEST 1991


[I would have shipped a patch, but the length of the patch was almost
 twice as long as the file, itself.  So much for that bright idea...]

	Differences Between 2.1 and 2.0:

	* Added a colorbuffer mode (-C) for folks without all the bitplanes.
	* Background mode now has it's own control window, so no more
	  calls to foreground().
	* getgdesc() is called instead of using XMAXSCREEN and YMAXSCREEN.
	* Double buffering in background mode now really works.
	* Optimized the math, nearly doubling the speed.
	* Fixed bugs with chasing the mouse around.

I'd like to hear if anybody can get this running on an RS/6000.  I had a
friend of mine "run" a test program, and the compiler barfed on the
getgdesc()'s by not having any of the interesting things in
/usr/include/gl/gl.h ...

Enjoy, and tell me how you like it.  I pretty much consider this program
done, now.  Time to work on a successor!  Any ideas?

Dan Wallach
dwallach at soda.berkeley.edu
--------------------------- cut here --------------------------------
#define PROGRAM_TITLE "Sperm 2.1 by Drew Olbrich and Dan Wallach"
#define PROGRAM_NAME "Sperm"
/*
	sperm, Version 2.1

	Dan Wallach, March-April 1991    <dwallach at soda.berkeley.edu>
	   Added all the cool GL whizzies.

	Drew Olbrich, February 1991      <po0o+ at andrew.cmu.edu>
	   Wrote the original, including the sperm dynamics.

	Compile with:

	cc -O4 sperm.c -o sperm -lgl_s -lfastm -lm

	[the fastm and the O4 both help ALOT -- I tried doing the profile
	 and then cc -feedback ... -cord ... but cord dumped core.  Sigh!]

	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

	Differences Between 2.1 and 2.0:

	* Added a colorbuffer mode (-C) for folks without all the bitplanes.
	* Background mode now has it's own control window, so no more
	  calls to foreground().
	* getgdesc() is called instead of using XMAXSCREEN and YMAXSCREEN.
	* Double buffering in background mode now really works.
	* Optimized the math, nearly doubling the speed.
	* Fixed bugs with chasing the mouse around.

	Intereseting GL tidbits:  (by Dan)

	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 lines 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.

	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 at soda.berkeley.edu>
	or <po0o+ at andrew.cmu.edu>
*/

#include <stdio.h>
#include <math.h>

#include <gl/gl.h>
#include <gl/device.h>

/*
 *  these defines to change which floating point system we use --
 *  useful for MAJOR speedups (nearly 2x over using normal double-precision)
 */
#define FLOAT_TYPE	float
#define FLOAT_COS	fcos
#define FLOAT_SIN	fsin
#define FLOAT_SQRT	fsqrt
#define FLOAT_RAND	(FLOAT_TYPE)drand48

typedef FLOAT_TYPE POINT[2];
typedef FLOAT_TYPE 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 CMAP_EGGCOLOR		 49	/* the egg, of course */
#define CMAP_START		 50	/* first colormap color */
#define CMAP_NCOLORS		100	/* number of colors to use */

#define VEC_DOT(x, y) (x[0]*y[0] + x[1]*y[1])
#define VEC_LEN(x) (fsqrt(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) { FLOAT_TYPE l = VEC_LEN(x); VEC_DIV(x, l); }

/*
 *  window ID's... we choose -2 because it will NEVER be returned by
 *  winopen.  It gives > 0 for good windows, or -1 for an error
 */
int main_win = -2;
int tiny_win = -2;


int count = COUNT;
int line_width = LINEWIDTH;

int background_mode = FALSE, overlay_mode = FALSE, underlay_mode = FALSE, cmap_mode = FALSE,
 rgb_mode = FALSE, tiny_window = FALSE, mouse_follow = FALSE, use_dbuffer = FALSE;

short mousex, mousey;
long xmax, ymax;
long xorigin, yorigin;
FLOAT_TYPE old_eggx, old_eggy, eggx, eggy, eggdx, eggdy;

FLOAT_TYPE rad_scale = 3.5;
int index = 0;

typedef struct
{
    POINT x;
    VECTOR v;
    FLOAT_TYPE sine, cosine;
    FLOAT_TYPE vel;
    long color;
    int oldx1, oldy1, oldx2, oldy2;	/* used in singlebuffer mode to clear */
} SPERM;

SPERM *sperm;

void init_egg(void)
{
    eggx = FLOAT_RAND() * (xmax + 1);
    eggy = FLOAT_RAND() * (ymax + 1);

    old_eggx = eggx;
    old_eggy = eggy;

    eggdx = FLOAT_RAND() * EGG_VELOCITY - (EGG_VELOCITY / 2);
    eggdy = FLOAT_RAND() * 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 if (cmap_mode)
	color(CMAP_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 += FLOAT_RAND() * EGG_DELTA - (EGG_DELTA / 2);
    eggdy += FLOAT_RAND() * 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;
}

/*
 *  this function's purpose is to clear out that one line within the
 *  tiny window, which I couldn't seem to get rid of any other way.
 *
 *  Unfortunately, of course, it doesn't always work, particularly
 *  in overlay or underlay mode.  I tried fixing this and ended up
 *  crashing the window server.  Easier to let it live alone.
 */
clear_tiny_window()
{
    long orig_win = winget();

    winset(tiny_win);

    if (rgb_mode)
	cpack(0xff000000);
    else
	color(0);
    clear();

    winset(orig_win);
}

init_color_ramp()
{
    int i;

    mapcolor(CMAP_EGGCOLOR, 255, 255, 0);	/* yellow */

    for (i = 0; i < CMAP_NCOLORS; i++)
	mapcolor(i + CMAP_START,
		 0x50 + lrand48() % 0x1f,	/* red */
		 0x30,	     /* green */
		 0xa0 + (i * 0x60) / CMAP_NCOLORS);	/* blue */
}

/*
 *  this function is a gross hack, but that's what it takes
 *  to sometimes have one window and sometimes have two windows
 */
init_display()
{

   /*
    * what's going on here:
    * 
    * There are two windows that may or may not be opened.  One, the "main"
    * window, is used by everything but overlay and underlay mode to draw
    * into.  The other, the "tiny" window, is used as a source of a close
    * button in normally full screen modes.
    * 
    * We don't NEED two windows ever, except when we're drawing into the
    * background, however, I thought this would be slightly more elegant.
    * Who knows.
    * 
    * First, we'll try and open the "tiny" window.
    */

    if (tiny_window)
    {

       /*
        * note: we say where it will go, and try to make it zero height. we
        * end up getting exactly one pixel inside there...  Grrr... This
        * requires the calls to clear_tiny_window() here and there.
        */
	prefposition(7, 450, 0, 0);
	tiny_win = winopen(PROGRAM_NAME);
	if (tiny_win == -1)
	{
	    fprintf(stderr, "winopen: no more windows available\n");
	    exit(1);
	}
	winset(tiny_win);
	wintitle(PROGRAM_TITLE);
	qenter(REDRAW, tiny_win);	/* deal with this later... */
    }

   /*
    * now, we'll try and open the "main" window.
    */
    if (!(overlay_mode || underlay_mode))
    {
	if (background_mode)
	    imakebackground();

	main_win = winopen(PROGRAM_NAME);
	if (main_win == -1)
	{
	    fprintf(stderr, "winopen: no more windows available\n");
	    exit(1);
	}
	winset(main_win);
	wintitle(PROGRAM_TITLE);
	qenter(REDRAW, main_win);	/* deal with this later... */
    }
    else
	main_win = tiny_win;

   /*
    * okay.  We don't really care what windows we have on the screen anymore.
    * Now, we just DEAL with them...
    */
    winset(main_win);

    if (tiny_window)
    {

       /*
        * fullscrn() mode combined with imakebackground() is super funky.
        * You get RGB mode in some places and cmode in others and everything
        * is just really cool.  Unfortunately, it's just not what this
        * program's all about, i.e., drawing cool stuff without actually
        * messing anything up.  It ends up drawing over all the other
        * windows...
        */
	if (!background_mode)
	    fullscrn();

       /*
        * when we have a tiny window, that tends to imply that the rest of
        * the program wants to use the full screen.  We set our idea of the
        * boundaries to make the egg bounce off the walls, correctly.
        */
	xmax = getgdesc(GD_XPMAX);
	ymax = getgdesc(GD_YPMAX);
	xorigin = 0;
	yorigin = 0;
    }

   /*
    * so we know which way to do coloring...
    */
    if (rgb_mode)
	RGBmode();
    else if (cmap_mode)
	init_color_ramp();
    else
    {
	mapcolor(OVERLAY_EGGCOLOR, 255, 255, 0);	/* yellow */
	mapcolor(OVERLAY_SPERMCOLOR, 100, 100, 255);	/* purple */
    }

    if (use_dbuffer)
    {
	doublebuffer();
	frontbuffer(FALSE);
	backbuffer(TRUE);
    }
    if (overlay_mode)
    {
	drawmode(OVERDRAW);
	overlay(2);
    }
    else if (underlay_mode)
    {
	drawmode(UNDERDRAW);
	underlay(2);
    }
    linewidth(line_width);

    gconfig();
    qdevice(ESCKEY);
   /* qdevice(WINCLOSE);  ---- don't need this one... */
    qdevice(WINQUIT);
    qdevice(WINSHUT);

    if (mouse_follow)
    {
	qdevice(MOUSEX);
	qdevice(MOUSEY);
    }
}

init_sperm()
{
    int i;
    FLOAT_TYPE 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] = FLOAT_RAND() * (xmax + 1);
	sperm[i].x[1] = FLOAT_RAND() * (ymax + 1);
	VEC_SET(sperm[i].v, 0.0, 0.0);
	angle = FLOAT_RAND() * 10.0 + 5.0;
	sperm[i].sine = FLOAT_SIN(angle * M_PI / 180.0);
	sperm[i].cosine = FLOAT_COS(angle * M_PI / 180.0);
	sperm[i].vel = FLOAT_RAND() * 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 if (cmap_mode)
	    sperm[i].color = (lrand48() % CMAP_NCOLORS) + CMAP_START;

	else		     /* we're in OVERLAY mode */
	    sperm[i].color = OVERLAY_SPERMCOLOR;
    }
}

dynamics()
{
    int i;
    VECTOR w, old_w;
    POINT p;
    FLOAT_TYPE 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 (data == tiny_win)
		    clear_tiny_window();

	       /*
	        * it's possible for tiny_win to be main_win...
	        */
		if (data == main_win)
		{
		    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:

	       /*
	        * we need to clear the overlay planes because, otherwise,
	        * they'd stay around.  We don't want sperm lying all over the
	        * screen, now, do we?
	        */
		if (overlay_mode || underlay_mode)
		{
		    if (use_dbuffer)
			frontbuffer(TRUE);
		    color(OVERLAY_BACKGROUND);
		    clear();
		}
		exit(0);
	    }

       /*
        * back to the main loop.  If we're using double buffering, we need to
        * clear the screen...
        */
	if (use_dbuffer)
	{
	    if (rgb_mode)
		cpack(0xff000000);
	    else
		color(OVERLAY_BACKGROUND);

	    if (background_mode)
		rectfi(0, 0, xmax, ymax);	/* HACK */
	    else
		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;

    rgb_mode = TRUE;	     /* unless proved otherwise... */
    while ((c = getopt(argc, argv, "Cduhbomc:w:")) != -1)
	switch (c)
	{
	case 'C':
	    if (overlay_mode || underlay_mode)
	    {
		errflg++;
		fprintf(stderr,
		    "Can't use colormap mode with over/underlay planes.\n");
	    }
	    cmap_mode = TRUE;
	    rgb_mode = FALSE;
	    break;
	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 || cmap_mode)
		errflg++;
	    if (use_dbuffer)
	    {
		errflg++;
		fprintf(stderr,
		   "Can't use double buffering in over/underlay planes.\n");
	    }
	    overlay_mode = TRUE;
	    rgb_mode = FALSE;
	    break;
	case 'u':
	    if (background_mode || overlay_mode || cmap_mode)
		errflg++;
	    if (use_dbuffer)
	    {
		errflg++;
		fprintf(stderr,
		   "Can't use double buffering in over/underlay planes.\n");
	    }
	    underlay_mode = TRUE;
	    rgb_mode = FALSE;
	    break;
	case 'm':
	    mouse_follow = TRUE;
	    break;
	case '?':
	case 'h':
	    errflg = TRUE;
	}
    if (errflg)
    {
	fprintf(stderr, "Usage: %s [-C] [-d] [-m] [-w sperm_width] [-c sperm_count] [-b | -o | -u]\n", argv[0]);
	fprintf(stderr, "   -C   == use color map mode (not RGBmode)\n");
	fprintf(stderr, "   -d   == use double buffering\n");
	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, "\nNote: certain things just don't mix, especially with over/underlay modes\n");
	exit(1);
    }
    tiny_window = overlay_mode || underlay_mode || background_mode;

    srand48(time(0));

    init_display();

    init_sperm();
    if (!mouse_follow)
	init_egg();

    main_loop();
}



More information about the Comp.sys.sgi mailing list