Sperm demo, Version 2.0

Dan Wallach dwallach at soda.berkeley.edu
Wed Apr 17 17:19:00 AEST 1991


(after you cut this, the file should be 578 lines long, and have 13339 chars)
Enjoy!

Dan Wallach
dwallach at soda.berkeley.edu
================ cut here ======= but don't scratch the monitor =============
/*
	sperm, Version 2.0

	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 -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 at soda.berkeley.edu>
	or <po0o+ at 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();
}



More information about the Comp.sys.sgi mailing list