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.