/*-
  
 ** XSwarm
 ** ======
 **
 ** Purpose:	Display a swarm of bees chasing a wasp.
 **
 ** Features:	uses only integer math
 **		has no redeeming social value
 **
 ** Comments:	Most of this program is greatly rewritten by me, but the
 **		initial math and update stuff is left over from the older
 **		program.  Much of the NeXT related stuff is from LizardSaver.
 **
 ** Created:	by Jeff Butterworth on 7/11/90
 **		butterwo@cs.unc.edu
 **
 ** NeXTPoRT:    Kurt Werle
 **              frsvnsvn!kurt@crash.cts.com
 **
 ** Updated:	by Kurt Werle on 10/28/91
 
 Mod Dec 16, 1991, Scott Byer - Changed the way drawing was done to use wraps
 and encoded user paths.  Separated variable initialization code from
 variable allocation code.  Added 'stages' at beginning of oneStep.
 
 */

/*
  Standard Includes							 */
#import "SwarmView.h"
#import "SwarmWraps.c"

#import <appkit/NXImage.h>
#import <appkit/Panel.h>                // for NXRunAlertPanel()
#import <appkit/Control.h>

#import <dpsclient/wraps.h>
#import <dpsclient/dpsclient.h>

#import <libc.h>
#import <stdlib.h>


@implementation SwarmView
    
    
    /* Single step the wasp  and the bees.   Called continuously by BackSpace
       as long as the animation is needed.				   */
    
- oneStep
  {
    register int	xCd, yCd, beeNum, xVel, yVel;
    int		curWX, curWY;
    
    /* Wasp position and velocity.					   */
    
    /* Clear the BBOX before all position calculations.			   */
    RESET_BBOX;	  
    
    /* If the wasp  is the cursor, set it.   Otherwise, figure out the change
       in velocity, and then add the velocity into the current position.   */
    if (cursorwasp) {
	float	mouseX, mouseY;
	
	PScurrentmouse([window windowNum], &mouseX, &mouseY);
	
	/* Age the old position and check against the bounding box.	   */
	xCd = OWX(0);
	yCd = OWY(0);
	
	BUMP_BBOX(xCd, yCd);
	
	WX(1) = xCd;
	WY(1) = yCd;
	
	/* Get the new position.					   */
	xCd = (int) mouseX;
	yCd = (int) mouseY;
	
	BUMP_BBOX(xCd, yCd);
	
	WX(0) = xCd;
	WY(0) = yCd;
      }
    else {
	
	/* Accellerate the wasp and check against the wasp speed limits.   */
	xVel = WXV + RAND(WASPACC);
	if (xVel > WASPVEL)
	    xVel = WASPVEL;
	else if (xVel < -WASPVEL)
	    xVel = -WASPVEL;
	
	yVel = WYV + RAND(WASPACC);
	if (yVel > WASPVEL)
	    yVel = WASPVEL;
	else if (yVel < -WASPVEL)
	    yVel = -WASPVEL;
	
	/* Move the wasp, aging the array at the  same time, and doing bounds
	   checking.							   */
	xCd = OWX(0); 
	yCd = OWY(0); 
	
	WX(1) = xCd;
	WY(1) = yCd;
	
	BUMP_BBOX(xCd, yCd);
	
	xCd += xVel;
	if (xCd < BORDER)
	    xVel = abs(xVel);
	else if (xCd > VIEW_WIDTH - BORDER)
	    xVel = -abs(xVel);
	
	yCd += yVel;
	if (yCd < BORDER)
	    yVel = abs(yVel);
	else if (yCd > VIEW_HEIGHT - BORDER)
	    yVel = -abs(yVel);
	
	/* Dump the positions and velocities out into their proper places. */
	WX(0) = xCd;
	WY(0) = yCd;
	
	WXV = xVel;
	WYV = yVel;
	
	BUMP_BBOX(xCd, yCd);
      }
    
    /* Store the wasp position in simple variables as loop invariants.	   */
    curWX = WX(1);
    curWY = WY(1);
    
    /* Bee position and velocity.					   */
    
    /* Blip the velocity of some random bees, to keep things interesting.  By
       doing the blipping  here,  their velocities still get  adjusted later,
       making the 'blipping' appear natural and smooth.			   */
    for ( beeNum = 0 ; beeNum < (bees / 20) ; beeNum++ ) {
	xv[random() % bees] += RAND(BEEVEL);
	yv[random() % bees] += RAND(BEEVEL);
      }
    
    /* Each bee must have it's velocity and position  adjusted.  Loop through
       the bees, doing the processing for each.				   */
    for ( beeNum = 1 ; beeNum < bees ; beeNum++ ) {
	int		dist, distX, distY;
	
	/* Grab the  current bee's   position   -  used for  calculating  new
	   velocity, new position, and aging the current position.	   */
	xCd = OX(0, beeNum);
	yCd = OY(0, beeNum);
	
	/* Age the array right away.					   */
	X(1, beeNum) = xCd;
	Y(1, beeNum) = yCd;
	
	BUMP_BBOX(xCd, yCd);
	
	/* Calculate the acceleration  of  each  be  based  on it's  relative
	   position to the wasp.  Approximate the  distance of this  bee from
	   the previous position of the wasp, and use  that to accelerate the
	   bee in the right direction at it's meximum velocity.		   */
	distX = curWX - xCd;
	distY = curWY - yCd;
	dist = abs(distX) + abs(distY);
	
	if (dist == 0) dist = 1;
	
	/* Calculate the new velocities and do velocity checking.	   */
	xVel = xv[beeNum] + (distX * BEEACC) / dist;
	if (xVel > BEEVEL)
	    xVel = BEEVEL;
	if (xVel < -BEEVEL)
	    xVel = -BEEVEL;
	
	yVel = yv[beeNum] + (distY * BEEACC) / dist;
	if (yVel > BEEVEL)
	    yVel = BEEVEL;
	if (yVel < -BEEVEL)
	    yVel = -BEEVEL;
	
	/* Move the bee, aging it at the same time - no bounds checking... */
	xCd += xVel;
	X(0, beeNum) = xCd;
	
	yCd += yVel;
	Y(0, beeNum) = yCd;
	
	BUMP_BBOX(xCd, yCd);
	
	/* Dump the velocities into their arrays			   */
	xv[beeNum] = xVel;
	yv[beeNum] = yVel;
      }
    
    
    /* Display.								   */
    
    /* If you  thought that the structure that  the   calculations were being
       done in was wierd, here is why.  For the type of drawing we are doing,
       encoded user  paths  are the fastest way  to  get  it done.  Well, the
       arrangement  of the coordinates  is set up so  I can just hand off the
       calculation array  and bounding  box  and operand array  straight off.
       The   operand array   is never   touched  after initialization  -  the
       coordinates are always to be eaten by a  moveto followed  by a lineto.
       NOTE: If you  ever think  about going to   floating point calculations
       here, don't.  Go to  16.16 fixed point instead  -  the user path stuff
       will eat that up faster than integers, even.			   */
    PSsetgray(1.0);
    DPSDoUserPath(DRAW_BASE, (4 * bees), dps_long, ops, (2 * bees),
		  &drawBBox[0], dps_ustroke);
    PSsetgray(0.0);
    DPSDoUserPath(ERASE_BASE, (4 * bees), dps_long, ops, (2 * bees),
		  &eraseBBox[0], dps_ustroke);
    
    /* Copy the coordinates  just used for  drawing into the ones to  be used
       next time for erasing.						   */
    COPY_CDS;
    COPY_BBOX;				  
    
    return self;
    
  }

- initFrame:(const NXRect *)frameRect
  {	
    [super initFrame:frameRect];
    [self initSwarmView];
    [inspectorPanel display];

    return self;
  }

- drawSelf:(const NXRect *)rects :(int)rectCount
  {
    if (!rects || !rectCount) 
      {
	return self;
      }
    
    PSsetgray(0.0);
    NXRectFill(rects);
    
    return self;
  }

- initSwarmView
  {
    register int	time;
    
    /* Get the random number generator ready.				   */
    srandom(getpid());
    
    bees = BEES;			/* Initial number of bees.	   */
    cursorwasp = NO;		/* Use the cursor for the wasp position.   */
    
    /* Initialize the pointers - done once.				   */
    drawCds = &spaceOne;
    eraseCds = &spaceTwo;
    
    drawBBox = &bboxOne;
    eraseBBox = &bboxTwo;
    
    /* Initialize the operator array - done only once.			   */
    for ( time = 0 ; time < COORDS * 2 ;  )    
      {
	ops[time++] = dps_moveto;
	ops[time++] = dps_lineto;
      }
    
    /* Initialize the values.						   */
    [self initValues];
    
    return self;
  }

- initValues
  {
    register int	beeNum, xVal, yVal;
    
    RESET_BBOX;
    
    /* Initialize the wasp location and velocity.			   */
    xVal = BORDER + random() % (int) (VIEW_WIDTH - 2*BORDER);
    WX(0) = xVal;	WX(1) = xVal;
    
    yVal = BORDER + random() % (int) (VIEW_HEIGHT - 2*BORDER);
    WY(0) = yVal;	WY(1) = yVal;
    
    BUMP_BBOX(xVal, yVal);
    
    WXV = 0;
    WYV = 0;
    
    /* Initialize the bee locations and velocities.			   */
    for ( beeNum = 1 ; beeNum <= MAXBEES ; beeNum++ ) 
      {
	xVal = random() % VIEW_WIDTH;
	X(0,beeNum) = xVal;
	X(1,beeNum) = xVal;
	
	yVal = random() % VIEW_HEIGHT;
	Y(0,beeNum) = yVal;
	Y(1,beeNum) = yVal;
	
	BUMP_BBOX(xVal, yVal);		
	
	xv[beeNum] = RAND(BEEVEL);
	yv[beeNum] = RAND(BEEVEL);
      }
    
    /* Copy the draw coordinates to the erase coordinates for real.	   */
      {
	register int	*from = (int *)drawCds, *to = (int *)eraseCds, cnt;
	
	for (cnt = 0 ; cnt < COORDS_SIZE ; cnt++)
	    *to++ = *from++;
	
	from = (int *)drawBBox;
	to = (int *)eraseBBox;
	for (cnt = 0 ; cnt < BBOX_SIZE ; cnt++ )
	    *to++ = *from++;
      }
    
    
    return self;
  }

  /* Preferences panel methods.						   */
- setNumberBees: sender
  {
    bees = [sender intValue];
    [slider setIntValue:bees];
    [beeText setIntValue:bees];
    
    /* Bump   it one  -  location   zero is  the   wasp,  so  go  from 1   to
       <request>+1							   */
    bees++;
    
    [self initValues];
    
    [self lockFocus];
    PSsetgray(0.0);
    NXRectFill(&bounds);
    [self unlockFocus];
    return self;
  }

- setFollow: sender
  {	
    cursorwasp = [sender intValue];
    return self;
  }

- inspector:sender
{
    char buf[MAXPATHLEN];
	
    if (!inspectorPanel)
	{
	  sprintf(buf,"%s/Swarm.nib",[sender moduleDirectory:"Swarm"]);
	  [NXApp loadNibFile:buf owner:self withNames:NO];
    }
    return inspectorPanel;
}

- (BOOL)ignoreMouseMovement
{
  return cursorwasp;
}

- (const char *)windowTitle
{	
  return "Swarm";
}

@end
    
