This is Roach.m in view mode; [Download] [Up]
/* RoachesView is Copyright 1994 by Brian Hill <bhill@physics.ucla.edu>. */
#import "Roach.h"
#import "RoachesView.h"
#import <dpsclient/dpswraps.h>
/* #import "RoachWraps.h" */
#import "point.h"
#define NOT_STUCK 0
#define STUCK_TURNING_CCW 1
#define STUCK_TURNING_CW -1
#define TURN_SLOWER 0.6f
#define TURN_FASTER_WHEN_STUCK 12.0f
#define ONCE_IN_A_WHILE 0.0003f
#define ROACHSIZE (NXCoord)14.0f
/* ROACHSIZE is the radius of a Roach. A Roach should not come within
* ROACHSIZE of a View's bounds, nor within 2 * ROACHSIZE of another Roach.
* This latter restriction is not currently imposed. */
#define RETRIES 20
#define PI 3.14159265f
/* A couple of useful functions for discrete angles. */
static float radiansConversion = 2.0f * PI / (float)RESOLUTION;
#define ANGLE2RADIANS(angle) ((float)angle * radiansConversion)
void fixUpAngle(int *angle)
{
while (*angle <= 0) *angle += RESOLUTION;
while (*angle > RESOLUTION) *angle -= RESOLUTION;
return;
}
int radians2angle(float angleInRadians)
{
/* Caller's problem if angleInRadians doesn't sensibly round to an int
* Good place to raise an exception. In any case, this function would
* have to be completely rewritten to handle large angles well. */
int angle;
angleInRadians /= radiansConversion;
angle = (int)floor((double)angleInRadians + 0.5);
fixUpAngle(&angle);
return angle;
}
/* Stuff for user paths that draw Roaches. */
typedef struct {
float x, y;
char op;
} userPathEntry;
#define BODY_PATH_LENGTH 14
#define LEGS_PATH_LENGTH 6
#define MAX_PATH_LENGTH 14 /* The larger of the preceding lengths. */
/* Encloses roach. */
static float bbox[4] = {
0.0f,
0.0f,
2.0f * (float)ROACHSIZE,
2.0f * (float)ROACHSIZE
};
static userPathEntry bodyPathEntries[BODY_PATH_LENGTH] = {
{ 1.0, 0.0, dps_moveto},
{ 0.95, 0.15, dps_lineto},
{ 0.90, 0.17, dps_lineto},
{ 0.45, 0.25, dps_lineto},
{ 0.25, 0.36, dps_lineto},
{-0.36, 0.36, dps_lineto},
{-0.72, 0.22, dps_lineto},
{-0.95, 0.0, dps_lineto},
{-0.72, -0.22, dps_lineto},
{-0.36, -0.36, dps_lineto},
{ 0.25, -0.36, dps_lineto},
{ 0.45, -0.25, dps_lineto},
{ 0.90, -0.17, dps_lineto},
{ 0.95, -0.15, dps_lineto}
};
static userPathEntry legsPathEntries[LEGS_PATH_LENGTH] = {
{-0.70, -0.70, dps_moveto},
{ 0.70, 0.70, dps_lineto},
{ 0.0, -0.85, dps_moveto},
{ 0.0, 0.85, dps_lineto},
{ 0.70, -0.70, dps_moveto},
{-0.70, 0.70, dps_lineto}
};
float randomf(void);
@implementation Roach:Object
- initRoachesView:view
{
roachesView = view;
return [super init];
}
- setColor:(NXColor)roachesColor
{
color = roachesColor;
return self;
}
- (BOOL)reposition
{
unsigned count = 0;
/* A Roach must be repositioned if it finds itself outside of a resized
* window. Going from a smaller window to a larger window, one will find
* the Roaches initially clustered around the lower left part of the new
* window. A Roach must also must be positioned if it has never been
* positioned (a case which is flagged by zero angle). Zero angle is
* also used as a flag in the drawSelf method. */
angle[DRAW] = radians2angle(2.0f * PI * randomf());
/* An unitialized position will be zero and okPosition:: will return NO. */
while (![roachesView okPosition:&position[DRAW] :ROACHSIZE]) {
NXRect bounds;
if (++count > RETRIES) return NO; /* RoachesView is too crowded. */
[roachesView getBounds:&bounds];
position[DRAW].x = ROACHSIZE +
(NXCoord)randomf() * (bounds.size.width - (NXCoord)2.0f * ROACHSIZE);
position[DRAW].y = ROACHSIZE +
(NXCoord)randomf() * (bounds.size.height - (NXCoord)2.0f * ROACHSIZE);
/* Due to nonzero ROACHSIZE (and the extremely slim possibility that
* randomf() could return 0.0f), assignment can fail to be inside the
* bounds. Hence the while statement rather than an if statement. */
}
return YES;
}
- (BOOL)okPosition:(const NXPoint *)positionToCheck :(NXCoord)size
{
int i;
for (i = 0; i < 2; ++i) {
if (angle[i] != 0) {
if (squareDistance(positionToCheck, &position[i]) < size + ROACHSIZE) {
return NO;
}
}
}
return YES;
}
- (const NXPoint *)position
{
return &position[DRAW];
}
- oneStep
{
/* If necessary, Roaches will have been repositioned and sizeInvalid */
/* will be NO by the time this method is reached. */
NXPoint translation;
NXPoint saveOldPosition; /* In case I want it back. Why overwrite
position[DRAW] before I know I need it? Because
okPosition:: is expecting exactly one Roach to
be at the same position. */
int exposed, newExposed;
float deltaAngle;
unitVector(&translation, ANGLE2RADIANS(angle[DRAW]));
scale(&translation, (NXCoord)[roachesView scaledSpeed]);
set(&saveOldPosition, &position[DRAW]);
translate(&position[DRAW], &translation);
deltaAngle = TURN_SLOWER * (2.0f * randomf() - 1.0f);
/* deltaAngle is in units imposed by RESOLUTION.
* The formula for deltaAngle must give a value larger in absolute value
* than 0.5 at least some of the time, or a Roach that is not stuck will
* never turn. See comment where RESOLUTION is defined in RoachesView.h. */
/* See if new position is ok. What I do with deltaAngle depends on this. */
exposed = [roachesView positionExposed:&saveOldPosition :ROACHSIZE];
newExposed = [roachesView positionExposed:&position[DRAW] :ROACHSIZE];
if (newExposed == YES) {
/* Once in a while a roach goes out despite exposure. */
if (randomf() < ONCE_IN_A_WHILE) newExposed = NO;
}
if ((exposed == EXPOSED || newExposed == HIDDEN) &&
[roachesView okPosition:&position[DRAW] :ROACHSIZE]) {
stuck = NOT_STUCK;
} else {
/* Roach can't go this direction. Scrap new position, and only
* change angle. So that Roach will
* be a little more intelligent about getting unstuck, the stuck instance
* variable records
* whether it is turning cc or ccw to get unstuck. If it is set, it
* continues in that direction. */
set(&position[DRAW], &saveOldPosition);
if (stuck == NOT_STUCK) {
stuck = deltaAngle >= 0.0f ? STUCK_TURNING_CCW : STUCK_TURNING_CW;
}
deltaAngle = stuck * fabs(deltaAngle) * TURN_FASTER_WHEN_STUCK;
}
{
float temp;
temp = (float)angle[DRAW] + deltaAngle + 0.5f;
angle[DRAW] = (int)floor((double)temp);
fixUpAngle(&angle[DRAW]);
}
return self;
}
- prepareToDraw
{
NXImage *drawBuffer, **sharedImages;
int i;
NXColor backgroundColor;
const NXSize imageSize = {
(NXCoord)(2.0f * ROACHSIZE),
(NXCoord)(2.0f * ROACHSIZE)
};
sharedImages = [roachesView sharedImages];
if ([roachesView imageClean][angle[DRAW]] || angle[DRAW] == 0) {
/* Already prepared to draw. */
} else {
float pts[2 * MAX_PATH_LENGTH];
char ops[MAX_PATH_LENGTH];
[roachesView imageClean][angle[DRAW] % RESOLUTION] = YES;
/* Lazy instantiation of sharedImages. */
drawBuffer = sharedImages[angle[DRAW] % RESOLUTION];
if (!drawBuffer) {
drawBuffer = [NXImage allocFromZone:[self zone]];
[drawBuffer initSize:&imageSize];
sharedImages[angle[DRAW] % RESOLUTION] = drawBuffer;
}
[drawBuffer lockFocus];
PSsetlinewidth(0.0f);
/* The following choice of background colors is important when a device
* uses patterns to represents colors outside of it's color space. */
backgroundColor = NX_COLORCLEAR;
/* PSsetrgbcolor(NXRedComponent(backgroundColor),
NXGreenComponent(backgroundColor),
NXBlueComponent(backgroundColor)); */
PSsetalpha(0.0f);
PSrectfill(0.0f, 0.0f, 2.0f * (float)ROACHSIZE, 2.0f * (float)ROACHSIZE);
PSsetrgbcolor(NXRedComponent(color),
NXGreenComponent(color),
NXBlueComponent(color));
PSsetalpha(1.0f);
for (i = 0; i < BODY_PATH_LENGTH; ++i) {
NXPoint pathPoint, translation = {(NXCoord)1.0f, (NXCoord)1.0f};
pathPoint.x = (NXCoord)bodyPathEntries[i].x;
pathPoint.y = (NXCoord)bodyPathEntries[i].y;
rotate(&pathPoint, ANGLE2RADIANS(angle[DRAW]));
translate(&pathPoint, &translation);
scale(&pathPoint, ROACHSIZE);
pts[2 * i] = pathPoint.x;
pts[2 * i + 1] = pathPoint.y;
ops[i] = bodyPathEntries[i].op;
}
DPSDoUserPath((void *)pts, 2 * BODY_PATH_LENGTH, dps_float, ops,
BODY_PATH_LENGTH, (void *)bbox, dps_ufill);
/* Should eliminate duplication between legs path code and body path
* code */
for (i = 0; i < LEGS_PATH_LENGTH; ++i) {
NXPoint pathPoint, translation = {(NXCoord)1.0f, (NXCoord)1.0f};
pathPoint.x = (NXCoord)legsPathEntries[i].x;
pathPoint.y = (NXCoord)legsPathEntries[i].y;
rotate(&pathPoint, ANGLE2RADIANS(angle[DRAW]));
translate(&pathPoint, &translation);
scale(&pathPoint, ROACHSIZE);
pts[2 * i] = pathPoint.x;
pts[2 * i + 1] = pathPoint.y;
ops[i] = legsPathEntries[i].op;
}
DPSDoUserPath((void *)pts, 2 * LEGS_PATH_LENGTH, dps_float, ops,
LEGS_PATH_LENGTH, (void *)bbox, dps_ustroke);
[drawBuffer unlockFocus];
}
return self;
}
- erase
{
if (angle[ERASE] == 0) return self;
PSrectfill((float)(position[ERASE].x - ROACHSIZE),
(float)(position[ERASE].y - ROACHSIZE),
(float)(2.0f * ROACHSIZE),
(float)(2.0f * ROACHSIZE));
return self;
}
- compositeDraw
{
NXPoint compositePoint;
/* I could choose to not to draw the Roach if rects[0] (or rects[1] and
* rects[2]) do not intersect it. In that case though, animation speed
* would depend on the number of Roaches that are visible. */
set(&position[ERASE], &position[DRAW]);
angle[ERASE] = angle[DRAW];
if (angle[DRAW] == 0) return self;
compositePoint.x = (NXCoord)(position[DRAW].x - ROACHSIZE);
compositePoint.y = (NXCoord)(position[DRAW].y - ROACHSIZE);
[[roachesView sharedImages][angle[DRAW] % RESOLUTION] composite:NX_SOVER toPoint:&compositePoint];
return self;
}
@end
static const float factor=(1.0f/(float)LONG_MAX);
float randomf(void)
{
return random() * factor;
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.