[comp.sources.bugs] xball: patch to add realism

tif@doorstop.austin.ibm.com (Paul Chamberlain) (09/21/90)

Below are the changes I made to xball to add realistic bouncing and
collisions.  I have amazed myself that it accurately predicts real
world bounces the same way my intuition says it should.  The changes
were actually derived from conservation of energy and conservation of
momentum equations.  A few other bouncing changes were made to add to
the realism.

On my 16 color X-windows machine, I kept getting invisible balls when
I used the last color so I made it not use the last one.  Since I have
a white background I also made the random colors inherently dark.  And
changed the random's to use lrand48 and friends.  I changed the external
declaration of malloc() to an include of malloc.h.

I have to post this now 'cause I've wasted too much time watching it
already.  I still see some funny looking bounces occasionally but
never have been able to reproduce them when I start debugging.  So
I give up.

I would be pleased to here from you if you enjoy the changes or add
further enhancements.  I have been running this on an IBM Risc System
6000 using an RT for the X display.  A DELAY of 200 looks good.
My apologies for a 14K patch file to a 35K source file.

Paul Chamberlain | I do NOT represent IBM         tif@doorstop, sc30661@ausvm6
512/838-7008     | ...!cs.utexas.edu!ibmaus!auschs!doorstop.austin.ibm.com!tif


#!/bin/sh
# This is a shell archive (shar 3.21)
# made 09/20/1990 20:43 UTC by tif@golddragon
# Source directory /usr/local/src/x/xball
#
# existing files WILL be overwritten
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#  14813 -rw-r--r-- xball.pat
#
if touch 2>&1 | fgrep '[-amc]' > /dev/null
 then TOUCH=touch
 else TOUCH=true
fi
# ============= xball.pat ==============
echo "x - extracting xball.pat (Text)"
sed 's/^X//' << 'SHAR_EOF' > xball.pat &&
X*** xball.org	Thu Sep 13 16:54:54 1990
X--- xball.c	Thu Sep 20 15:21:05 1990
X***************
X*** 14,19 ****
X--- 14,21 ----
X  /* It is provided "as is" without express or implied warranty.
X  */
X  
X+ #include <malloc.h>
X+ #include <math.h>
X  #include <string.h>
X  #include <X11/Intrinsic.h>
X  #include <X11/StringDefs.h>
X***************
X*** 68,73 ****
X--- 70,76 ----
X  #define ITEM_HEIGHT 10
X  #define WIN_HEIGHT 400		/* Default window's size */
X  #define WIN_WIDTH  500
X+ #define R2X4		(MULI(ITEM_WIDTH)*MULI(ITEM_HEIGHT))	/* radius^2 * 4 */
X  
X  #ifdef X11_R3
X  typedef char *XtPointer;
X***************
X*** 94,99 ****
X--- 97,103 ----
X    char	valid;		/* TRUE if item is valid */
X    int	rebounded;	/* Used to determine if item collision */
X                          /* had already been calculated for this item */
X+   int	rebounded_last;	/* prevent wierd double bounces -- still necessary? */
X    int	p_index;	/* Index of pixel map to use for drawing item */
X  };
X  
X***************
X*** 140,153 ****
X    Widget   canvas_widget;	/* Where balls are drawn */
X    Arg      wargs[10];		/* Used to set widget resources */
X    int      n;
X  
X  
X    datap.item_index = 0;			/* No items yet */
X    datap.winfo_ptr  = &win_info;
X!   datap.right_wall = MULI(WIN_WIDTH);	/* Locations of room structures */
X!   datap.left_wall  = 0;
X!   datap.ceiling    = 0;
X!   datap.floor      = MULI(WIN_HEIGHT);
X    datap.gravity	   = GRAVITY;
X    datap.elasticity = ELASTICITY;
X    datap.button1_pressed = FALSE;
X--- 144,159 ----
X    Widget   canvas_widget;	/* Where balls are drawn */
X    Arg      wargs[10];		/* Used to set widget resources */
X    int      n;
X+   long	time();
X  
X  
X+   srand48(time((long *) 0) + getpid());
X    datap.item_index = 0;			/* No items yet */
X    datap.winfo_ptr  = &win_info;
X!   datap.right_wall = MULI(WIN_WIDTH)-MULI(ITEM_WIDTH)/2;	/* Locations of room structures */
X!   datap.left_wall  = MULI(ITEM_WIDTH)/2;
X!   datap.ceiling    = MULI(ITEM_HEIGHT)/2;
X!   datap.floor      = MULI(WIN_HEIGHT)-MULI(ITEM_HEIGHT)/2;
X    datap.gravity	   = GRAVITY;
X    datap.elasticity = ELASTICITY;
X    datap.button1_pressed = FALSE;
X***************
X*** 203,211 ****
X  			    0, cells,1))
X  	break; /* Can't allocate any more colors */
X  
X!       color.red   = random()%65535;
X!       color.green = random()%65535;
X!       color.blue  = random()%65535;
X        color.pixel = cells[0];
X        XStoreColor( XtDisplay(toplevel), win_info.colormap, &color);
X  
X--- 209,217 ----
X  			    0, cells,1))
X  	break; /* Can't allocate any more colors */
X  
X!       color.red   = lrand48()%20000;
X!       color.green = lrand48()%20000;
X!       color.blue  = lrand48()%20000;
X        color.pixel = cells[0];
X        XStoreColor( XtDisplay(toplevel), win_info.colormap, &color);
X  
X***************
X*** 226,231 ****
X--- 232,238 ----
X        XFillArc(XtDisplay(toplevel), datap.pixmaps[datap.max_color],
X  	       win_info.color_gc, 0, 0, ITEM_WIDTH-1,ITEM_HEIGHT-1,0,360*64);
X      }
X+     datap.max_color--;
X    }
X  
X    XSetFunction( XtDisplay(toplevel), win_info.set_gc, GXxor);
X***************
X*** 408,415 ****
X    XtSetArg(wargs[n], XtNheight, &height); n++;
X    XtGetValues(w, wargs, n);
X  
X!   datap_ptr->right_wall = MULI(width);
X!   datap_ptr->floor      = MULI(height);
X  }
X  
X  
X--- 415,422 ----
X    XtSetArg(wargs[n], XtNheight, &height); n++;
X    XtGetValues(w, wargs, n);
X  
X!   datap_ptr->right_wall = MULI(width)-MULI(ITEM_WIDTH)/2;
X!   datap_ptr->floor      = MULI(height)-MULI(ITEM_HEIGHT)/2;
X  }
X  
X  
X***************
X*** 424,429 ****
X--- 431,441 ----
X    XFlush(XtDisplay(datap_ptr->winfo_ptr->w));
X    delay(20 - datap_ptr->item_index); /* Slow down the first 20 balls */
X  
X+ 	for (x = 0; x < datap_ptr->item_index; x++) {
X+ 		datap_ptr->item_list[x].rebounded_last = datap_ptr->item_list[x].rebounded;
X+ 		datap_ptr->item_list[x].rebounded = FALSE;
X+ 	}
X+ 
X    /* Move each item on the screen */
X    for (x = 0; x < datap_ptr->item_index; x++)
X    {
X***************
X*** 459,470 ****
X  	datap_ptr->item_list[x].y; /* Bounce back */
X  
X        datap_ptr->item_list[x].y_velocity = 
X! 	(-datap_ptr->item_list[x].y_velocity) + /* Rev vel */
X! 	  DIVI(datap_ptr->gravity);
X  
X!       if (ABS(datap_ptr->item_list[x].y_velocity) < datap_ptr->elasticity)
X  	datap_ptr->item_list[x].y_velocity = 0;
X!       else
X  	/* Remove some inertia */
X  	datap_ptr->item_list[x].y_velocity += datap_ptr->elasticity;
X      }
X--- 471,482 ----
X  	datap_ptr->item_list[x].y; /* Bounce back */
X  
X        datap_ptr->item_list[x].y_velocity = 
X! 	(-datap_ptr->item_list[x].y_velocity); /* Rev vel */
X  
X!       if (ABS(datap_ptr->item_list[x].y_velocity) < datap_ptr->elasticity) {
X  	datap_ptr->item_list[x].y_velocity = 0;
X! 	datap_ptr->item_list[x].y = datap_ptr->floor;
X!       } else
X  	/* Remove some inertia */
X  	datap_ptr->item_list[x].y_velocity += datap_ptr->elasticity;
X      }
X***************
X*** 476,481 ****
X--- 488,498 ----
X  	datap_ptr->item_list[x].y; /* Bounce off */
X        datap_ptr->item_list[x].y_velocity = 
X  	-datap_ptr->item_list[x].y_velocity; /* Rev dir */
X+       if (ABS(datap_ptr->item_list[x].y_velocity) < datap_ptr->elasticity)
X+ 	datap_ptr->item_list[x].y_velocity = 0;
X+       else
X+ 	/* Remove some inertia */
X+ 	datap_ptr->item_list[x].y_velocity -= datap_ptr->elasticity;
X      }
X  
X      /* Calculate new x position */
X***************
X*** 489,494 ****
X--- 506,516 ----
X  	datap_ptr->item_list[x].x; /* Bounce off */
X        datap_ptr->item_list[x].x_velocity = 
X  	-datap_ptr->item_list[x].x_velocity; /* Rev dir */
X+       if (ABS(datap_ptr->item_list[x].x_velocity) < datap_ptr->elasticity)
X+ 	datap_ptr->item_list[x].x_velocity = 0;
X+       else
X+ 	/* Remove some inertia */
X+ 	datap_ptr->item_list[x].x_velocity += datap_ptr->elasticity;
X      }
X      else
X      if (datap_ptr->item_list[x].x < datap_ptr->left_wall)
X***************
X*** 498,511 ****
X  	datap_ptr->item_list[x].x; /* Bounce off */
X        datap_ptr->item_list[x].x_velocity = 
X  	-datap_ptr->item_list[x].x_velocity; /* Rev dir */
X      }
X  
X      /* See if collided with another item */
X!     for (y = 0; y < datap_ptr->item_index &&
X! 	        datap_ptr->item_list[x].rebounded == FALSE; y++)
X!       if (x != y)
X! 	rebound_item( &datap_ptr->item_list[x], &datap_ptr->item_list[y]);
X!     datap_ptr->item_list[x].rebounded = FALSE;
X  
X      /* Don't redraw if item hasn't moved */
X      if (oldx == DIVI(datap_ptr->item_list[x].x) &&
X--- 520,537 ----
X  	datap_ptr->item_list[x].x; /* Bounce off */
X        datap_ptr->item_list[x].x_velocity = 
X  	-datap_ptr->item_list[x].x_velocity; /* Rev dir */
X+       if (ABS(datap_ptr->item_list[x].x_velocity) < datap_ptr->elasticity)
X+ 	datap_ptr->item_list[x].x_velocity = 0;
X+       else
X+ 	/* Remove some inertia */
X+ 	datap_ptr->item_list[x].x_velocity -= datap_ptr->elasticity;
X      }
X  
X      /* See if collided with another item */
X!     for (y = 0; y < datap_ptr->item_index; y++)
X! 	if (x != y)
X! 		rebound_item( datap_ptr, x, y);
X!     /*  datap_ptr->item_list[x].rebounded = FALSE; */
X  
X      /* Don't redraw if item hasn't moved */
X      if (oldx == DIVI(datap_ptr->item_list[x].x) &&
X***************
X*** 521,533 ****
X  	       oldy - ITEM_HEIGHT/2);
X  
X      /* See if item has come to a peaceful rest */
X!     if (ABS(datap_ptr->item_list[x].y - datap_ptr->floor) <
X! 	  datap_ptr->gravity/4 &&  /* on floor */
X! 	ABS(datap_ptr->item_list[x].y_velocity) <= 
X  	  datap_ptr->elasticity*2) /* Not bouncing */
X      {
X!        if (ABS(datap_ptr->item_list[x].x_velocity) < 
X! 	   datap_ptr->gravity/10) /* No roll */
X         {
X  	 datap_ptr->item_list[x].valid = FALSE;
X  	 continue; /* Don't draw item */
X--- 547,557 ----
X  	       oldy - ITEM_HEIGHT/2);
X  
X      /* See if item has come to a peaceful rest */
X!     if (ABS(datap_ptr->item_list[x].y == datap_ptr->floor) &&
X! 	  ABS(datap_ptr->item_list[x].y_velocity) <= 
X  	  datap_ptr->elasticity*2) /* Not bouncing */
X      {
X!        if (ABS(datap_ptr->item_list[x].x_velocity) < 2) /* No roll */
X         {
X  	 datap_ptr->item_list[x].valid = FALSE;
X  	 continue; /* Don't draw item */
X***************
X*** 535,543 ****
X  
X         /* Slow down velocity once rolling */
X         if (datap_ptr->item_list[x].x_velocity > 0)
X! 	 datap_ptr->item_list[x].x_velocity -= DIVI(datap_ptr->gravity);
X         else
X! 	 datap_ptr->item_list[x].x_velocity += DIVI(datap_ptr->gravity);
X      }
X  
X      /* Draw new item */
X--- 559,567 ----
X  
X         /* Slow down velocity once rolling */
X         if (datap_ptr->item_list[x].x_velocity > 0)
X! 	 datap_ptr->item_list[x].x_velocity--;
X         else
X! 	 datap_ptr->item_list[x].x_velocity++;
X      }
X  
X      /* Draw new item */
X***************
X*** 649,656 ****
X        break;
X  
X      case RANDOM_MODE:
X!       *x_vel_ptr = (random() & 0xff) - 128;  /* Random x velocity */
X!       *y_vel_ptr = (random() & 0x3f) - 128;  /* Random, but up, y vel */
X        break;
X  
X      case STILL_MODE:
X--- 673,680 ----
X        break;
X  
X      case RANDOM_MODE:
X!       *x_vel_ptr = (lrand48() & 0xff) - 128;  /* Random x velocity */
X!       *y_vel_ptr = (lrand48() & 0x3f) - 128;  /* Random, but up, y vel */
X        break;
X  
X      case STILL_MODE:
X***************
X*** 663,696 ****
X  
X  /* See if items have hit and rebound them if they have */
X  /* Itema is assumed to have just been moved */
X! rebound_item( itema_ptr, itemb_ptr)
X! struct item_struct *itema_ptr, *itemb_ptr;
X  {
X!   int xdiff,ydiff;
X  
X!   xdiff = DIVI(itema_ptr->x - itemb_ptr->x);
X!   ydiff = DIVI(itema_ptr->y - itemb_ptr->y);
X!   if (ABS(xdiff) < ITEM_WIDTH && ABS(ydiff) < ITEM_HEIGHT)
X!   {
X!     itema_ptr->rebounded = TRUE; /* Mark as rebound */
X!     itemb_ptr->rebounded = TRUE; /* Mark as rebound */
X  
X!     SWAP( itema_ptr->x_velocity, itemb_ptr->x_velocity, int);
X!     SWAP( itema_ptr->y_velocity, itemb_ptr->y_velocity, int);
X  
X!     if (itema_ptr->y_velocity <= ITEM_WIDTH &&
X! 	itemb_ptr->y_velocity <= ITEM_WIDTH)
X!     {
X!       itema_ptr->y_velocity += ydiff;
X!       itemb_ptr->y_velocity -= ydiff;
X!     }
X!     if (itema_ptr->x_velocity <= ITEM_WIDTH &&
X! 	itemb_ptr->x_velocity <= ITEM_WIDTH)
X!     {
X!       itema_ptr->x_velocity += xdiff;
X!       itemb_ptr->x_velocity -= xdiff;
X!     }
X!   }
X  }
X  
X  
X--- 687,804 ----
X  
X  /* See if items have hit and rebound them if they have */
X  /* Itema is assumed to have just been moved */
X! rebound_item( datap_ptr, a, b)
X! datap_type *datap_ptr;
X! int a, b;
X  {
X! 	struct item_struct *itema_ptr, *itemb_ptr;
X! 	int xdiff,ydiff;
X! 	double slope;
X! 	int vx, vy;			/* velocity of a relative to b */
X! 	double delta_ax;
X! 	double qa, qb, qc, qr, qt;	/* quadratic components */
X  
X! 	itema_ptr = &datap_ptr->item_list[a];
X! 	itemb_ptr = &datap_ptr->item_list[b];
X  
X! 	xdiff = itema_ptr->x - itemb_ptr->x;
X! 	ydiff = itema_ptr->y - itemb_ptr->y;
X  
X! 	if (ABS(xdiff) > MULI(ITEM_WIDTH) || ABS(ydiff) > MULI(ITEM_HEIGHT))
X! 		return;		/* not even close */
X! 
X! /* printf("Ball %d and %d are %d,%d apart, vx=%d, vy=%d\n", a, b, xdiff, ydiff,
X!     itema_ptr->x_velocity, itema_ptr->y_velocity);
X! sleep(1);
X! /* */
X!   
X! 	if (itema_ptr->rebounded == TRUE && itemb_ptr->rebounded == TRUE) {
X! /* printf("%d already hit %d\n", a, b); */
X! 		return;
X! 	}
X! 
X! 	vy = itema_ptr->y_velocity;
X! 	vx = itema_ptr->x_velocity;
X! 
X! 	if (vy == 0 && vx == 0) {
X! 		return;
X! 	}
X! 
X! 	qa = (double) vx * vx + (double) vy * vy;
X! 	qb = 2 * ((double) xdiff * vx + (double) ydiff * vy);
X! 	qc = (double) xdiff * xdiff + (double) ydiff * ydiff - R2X4;
X! 
X! 	qr = qb * qb - 4 * qa * qc;
X! 	if (qr < 0) {
X! /*		printf("close but no hit\n"); /* */
X! 		return;
X! 	}
X! 
X! 	/*
X! 	 * We are looking for the earliest collision
X! 	 * so we use the -sqr() to get a negative time.
X! 	 */
X! 	qt = (-(qb) - sqrt(qr)) * 0.5 / qa;
X! 	if (qt > 0 || qt < -1) {
X! /*		printf("not yet %f\n", qt); /* */
X! 		return;
X! 	}
X! 
X! 	itema_ptr->rebounded = TRUE; /* Mark as rebound */
X! 	itemb_ptr->rebounded = TRUE; /* Mark as rebound */
X! 
X! 	if (itema_ptr->rebounded_last == TRUE && itemb_ptr->rebounded_last == TRUE) {
X! /* printf("%d still hitting %d\n", a, b); */
X! /* sleep(3); */
X! 		return;
X! 	}
X! 
X! /* printf("diff used to be %d,%d, qt+=%f\n", xdiff, ydiff, */
X! /*	(-(qb) + sqrt(qr)) * 0.5 / qa); */
X! 	/*
X! 	 * Should be safe to scoot the ball back a fraction of a unit
X! 	 * of time.  Okay, it's not perfect, cause I only affect one ball.
X! 	 */
X! 	itema_ptr->x += qt * vx + 0.5;
X! 	itema_ptr->y += qt * vy + 0.5;
X! 
X! 	/* update these things since we scooted back */
X! 	xdiff = itema_ptr->x - itemb_ptr->x;
X! 	ydiff = itema_ptr->y - itemb_ptr->y;
X! 
X! 	/* Use relative velocities for collision equations */
X! 	vy = itema_ptr->y_velocity - itemb_ptr->y_velocity;
X! 	vx = itema_ptr->x_velocity - itemb_ptr->x_velocity;
X! 
X! /* printf("Balls %d, %d hit %f ago, qa=%f, ", a, b, qt, qa); */
X! /* printf("v=%d,%d, diff=%d,%d, ", vx, vy, xdiff, ydiff); */
X! 
X! 	if (xdiff == 0) { /* special case */
X! 		if (ydiff != 0) {
X! 			itema_ptr->y_velocity = 0;
X! 			itemb_ptr->y_velocity += vy;
X! /* printf("vert-hit with vy=%d\n", vy); */
X! 		} else {
X! /* printf("Whoa, balls are in the same place!\n"); */
X! 			return;
X! 		}
X! 	} else {
X! 		slope = (double) ydiff/xdiff;
X! 		delta_ax = (vx + slope * vy) / (slope * slope + 1);
X! 		itema_ptr->x_velocity -= delta_ax + 0.5;
X! 		itema_ptr->y_velocity -= delta_ax * slope + 0.5;
X! 		itemb_ptr->x_velocity += delta_ax + 0.5;
X! 		itemb_ptr->y_velocity += delta_ax * slope + 0.5;
X! /* printf("good hit, dax=%f, slope=%f\n", delta_ax, slope); */
X! /* sleep(3); */
X! 	}
X! 
X! 	/*
X! 	 * Just for grins, add back that slice of time after
X! 	 * the bounce took place.
X! 	 */
X! 	itema_ptr->x += qt * itema_ptr->x_velocity;
X! 	itema_ptr->y += qt * itema_ptr->y_velocity;
X  }
X  
X  
X***************
X*** 885,892 ****
X    if (datap_ptr->elasticity > MAX_ELASTICITY)
X      datap_ptr->elasticity = MAX_ELASTICITY;
X    else
X!   if (datap_ptr->elasticity < 1)
X!     datap_ptr->elasticity = 1;
X  
X    XawScrollbarSetThumb(w,1.0 -(float)datap_ptr->elasticity/MAX_ELASTICITY,0.1);
X  }
X--- 993,1000 ----
X    if (datap_ptr->elasticity > MAX_ELASTICITY)
X      datap_ptr->elasticity = MAX_ELASTICITY;
X    else
X!   if (datap_ptr->elasticity < 0)
X!     datap_ptr->elasticity = 0;
X  
X    XawScrollbarSetThumb(w,1.0 -(float)datap_ptr->elasticity/MAX_ELASTICITY,0.1);
X  }
X***************
X*** 900,910 ****
X  {
X    float percent = *(float *)percent_ptr;
X  
X!   /* ... + 1 is because 0 elasticity reveales algotithm deficiencies */
X!   datap_ptr->elasticity = MAX_ELASTICITY * (1.0 - percent) + 1;
X  }
X  
X- char *malloc();
X  char *concat_words(n, words)
X  int   n;
X  char *words[];
X--- 1008,1016 ----
X  {
X    float percent = *(float *)percent_ptr;
X  
X!   datap_ptr->elasticity = MAX_ELASTICITY * (1.0 - percent);
X  }
X  
X  char *concat_words(n, words)
X  int   n;
X  char *words[];
SHAR_EOF
$TOUCH -am 0920153490 xball.pat &&
chmod 0644 xball.pat ||
echo "restore of xball.pat failed"
set `wc -c xball.pat`;Wc_c=$1
if test "$Wc_c" != "14813"; then
	echo original size 14813, current size $Wc_c
fi
exit 0