This is BoinkViewPart.m in view mode; [Download] [Up]
// BoinkViewPart.m // // implements a bouncing ball screen saver view // // You may freely copy, distribute, and reuse the code in this example. // NeXT disclaims any warranty of any kind, expressed or implied, as to its // fitness for any particular use. #import "BoinkViewPart.h" #import "SpaceView.h" #import "Thinker.h" #import "BoinkWraps.h" #import <appkit/NXImage.h> #import <math.h> #import <libc.h> #import <dpsclient/wraps.h> @implementation BoinkView // assumed interval in milliseconds #define ASSUMED_INTERVAL 35 #define WIDTH 100 #define HEIGHT 100 #define GAP 4 #define COUNT 10 #define ACCEL (-2) #define REBOUND (-1.3) // This screen height value is not critical, though it will be used // to determine how high the ball can go #define SCREEN_HEIGHT 832 #define LAUNCH_SPEED (sqrt(fabs(2*ACCEL*(SCREEN_HEIGHT - HEIGHT)))) #define REAL_LAUNCH_SPEEd (sqrt(fabs(2*accel*(viewHeight - HEIGHT)))) #define MIN_X_SPEED (3) #define MAX_X_SPEED (6) #define ABS_MAX_X_SPEED (6) #define MAX_Y_SPEED (LAUNCH_SPEED + 20) #define BUFFER_WIDTH (WIDTH + ABS_MAX_X_SPEED + 1) #define BUFFER_HEIGHT (HEIGHT + MAX_Y_SPEED + 1) /* move the ball to its new bounce position */ - oneStep { NXRect black = {0,0,0,0}; NXRect ballRect; BRECT new; float scaledTime, calcYpos; then = now; now = currentTimeInMs(); /* calculate new ball x position */ xpos += [self timeCorrectedXSpeed]; if (xpos < 0) /* ball hit left edge */ { xspeed = -xspeed; if (viewWidth > WIDTH) { spinDir = -spinDir; } xpos = 0; } else if (xpos > (viewWidth - WIDTH)) /* ball hit right edge */ { if (viewWidth > WIDTH) { xspeed = -[self getRandomXspeed]; [self checkXspeed:&xspeed]; xpos = (viewWidth - WIDTH); } else { xspeed = xpos = 0; } } scaledTime = ((float)(now - then) / ASSUMED_INTERVAL); if (scaledTime > 1) scaledTime = 1; // calculate new ball vertical position calcYpos = ypos + (scaledTime*yspeed) + ((accel * scaledTime * scaledTime)/2); // change vertical ball speed to simulate gravity yspeed += (accel * scaledTime); if (calcYpos < (ypos - MAX_Y_SPEED)) calcYpos = ypos - MAX_Y_SPEED; else if (calcYpos > (ypos + MAX_Y_SPEED)) calcYpos = ypos + MAX_Y_SPEED; ypos = calcYpos; if (yspeed < -MAX_Y_SPEED) yspeed = -MAX_Y_SPEED; if (ypos <= 0) /* ball hit bottom of window */ { ypos = 0; if (viewHeight > HEIGHT) { if (reboundMode == DECREASING) { yspeed = lastLaunchSpeed = lastLaunchSpeed + rebound; } else { yspeed = lastLaunchSpeed = lastLaunchSpeed - (2*rebound); } if (yspeed <= 0) { yspeed = 0; reboundMode = INCREASING; /* bounce height increases every bounce */ } else if (yspeed > MAX_Y_SPEED) yspeed = MAX_Y_SPEED - (3*accel); } else yspeed = 0; } else if (ypos >= (viewHeight - HEIGHT)) /* ball hit top of window */ { if (viewHeight > HEIGHT) { yspeed = accel; ypos = (viewHeight - HEIGHT); reboundMode = DECREASING; /* bounce height decreases every bounce */ spinDir = -spinDir; } else { yspeed = ypos = 0; } } /* rotate the ball by selecting a new ball image to blit */ /* we have an image of the ball in 10 different stages of rotation */ [self incrementBallNumber]; new.l = floor(xpos); new.b = floor(ypos); new.r = new.l + WIDTH; new.t = new.b + HEIGHT; ballRect.origin.x = (WIDTH+GAP) * ballNum; ballRect.origin.y = 0; ballRect.size.width = WIDTH; ballRect.size.height = HEIGHT; redrawTo.x = MIN(new.l, old.l); redrawTo.y = MIN(new.b, old.b); redraw.origin.x = 0; redraw.origin.y = 0; redraw.size.width = (MAX(new.r, old.r)) - redrawTo.x + 1; redraw.size.height = (MAX(new.t, old.t)) - redrawTo.y + 1; black.size= redraw.size; [self updateGrid]; [buffer lockFocus]; PSsetgray(0); NXRectFill(&black); ballTo.x = new.l - redrawTo.x; ballTo.y = new.b - redrawTo.y; [self drawLinesInBuffer]; [balls composite:NX_SOVER fromRect:&ballRect toPoint:&ballTo]; [buffer unlockFocus]; // Now bring it onto the screen [buffer composite:NX_COPY fromRect:&redraw toPoint:&redrawTo]; old = new; return self; } /* calculate a vertical launch speed which will get the ball almost to */ /* the top of the window before gravity pulls it back down. Little bit */ /* of physics lesson here... */ - newSpeed { lastLaunchSpeed = yspeed = REAL_LAUNCH_SPEEd; if (yspeed > MAX_Y_SPEED) yspeed = MAX_Y_SPEED; xpos = 0; ypos = 0; if (viewWidth <= WIDTH) xspeed = 0; else xspeed = [self getRandomXspeed]; [self checkXspeed:&xspeed]; rebound = REBOUND; return self; } - initFrame:(const NXRect *)frameRect { NXRect black = {0, 0, BUFFER_WIDTH, BUFFER_HEIGHT }; [super initFrame:frameRect]; [self allocateGState]; // For faster lock/unlockFocus [self setClipping:NO]; // even faster... accel = ACCEL; spinDir = 1; //in this case, I only need one buffer for several Views if (!(buffer = [NXImage findImageNamed:"boinkBuffer"])) { buffer = [[NXImage alloc] initSize:&black.size]; [buffer setName:"boinkBuffer"]; } if ([buffer lockFocus]) { PSsetgray(0); NXRectFill(&black); [buffer unlockFocus]; } balls = [NXImage findImageNamed:"balls"]; [self newViewSize]; return self; } - setAccel:(float)val { accel = val; return self; } - sizeTo:(NXCoord)width :(NXCoord)height { [super sizeTo:width :height]; [self newViewSize]; return self; } - drawSelf:(const NXRect *)rects :(int)rectCount { if (!rects || !rectCount) return self; //PSsetgray(0); //NXRectFill(rects); NXRectClip(rects); [self drawGrid]; return self; } - newViewSize { int i; //this is called every time View size changes NXRect black = {0, 0, BUFFER_WIDTH, BUFFER_HEIGHT }; then = now = currentTimeInMs(); if (oldSize.width == bounds.size.width && oldSize.height == bounds.size.height) return self; else { oldSize.width = bounds.size.width; oldSize.height = bounds.size.height; } old.l = old.r = old.b = old.t = ballTo.x = ballTo.y = 0; viewWidth = bounds.size.width; viewHeight = bounds.size.height; if (viewHeight > SCREEN_HEIGHT) viewHeight = SCREEN_HEIGHT; nvert = viewWidth/130; if (nvert > NVERT) nvert = NVERT; nhoriz = viewHeight/130; if (nhoriz > NHORIZ) nhoriz = NHORIZ; if (viewWidth < WIDTH) nvert = 0; if (viewHeight < HEIGHT) nhoriz= 0; vcount = hcount = 0; for (i=0; i<nvert; i++) { vertLines[i].hue = i * 0.17; while (vertLines[i].hue > 1) vertLines[i].hue -= 1; vertLines[i].pos = floor(i * (viewWidth/nvert)); } for (i=0; i<nhoriz; i++) { horizLines[i].hue = i * 0.17 + 0.1; while (horizLines[i].hue > 1) horizLines[i].hue -= 1; horizLines[i].pos = i * floor((viewHeight/nhoriz)) + 1; } if ([buffer lockFocus]) { PSsetgray(0); NXRectFill(&black); [buffer unlockFocus]; } [self newSpeed]; return self; } - incrementBallNumber { if (now > nextRotationTime) { ballNum += spinDir; if (ballNum >= COUNT) ballNum = 0; else if (ballNum < 0) ballNum = COUNT-1; nextRotationTime = now + 24; } return self; } - (float) getRandomXspeed { return randBetween(MIN_X_SPEED, MAX_X_SPEED); } - (float) timeCorrectedXSpeed { float ret = xspeed * ((float)(now - then) / ASSUMED_INTERVAL); [self checkXspeed:&ret]; return ret; } - checkXspeed:(float *)speed { if (*speed > MAX_X_SPEED) *speed = MAX_X_SPEED; else if (*speed < -MAX_X_SPEED) *speed = -MAX_X_SPEED; return self; } - (const char *)windowTitle { return "Boink!"; } - drawGrid { int i; float *fp; for (i=0; i<nvert; i++) { fp = &vertLines[i].pos; colorLine(*fp, 0, *fp, viewHeight, vertLines[i].hue, 1); } for (i=0; i<nhoriz; i++) { fp = &horizLines[i].pos; colorLine(0, *fp, viewWidth, *fp, horizLines[i].hue, 1); } return self; } - updateGrid { NXRect avoid; float oldPos; float *fp; if (!nvert && !nhoriz) return self; if (now < nextLineDrawTime) return self; nextLineDrawTime = now + 3300; avoid.origin = redrawTo; avoid.size = redraw.size; if (++toggle & 1) { //advance vertical line if (!nvert) return self; fp = &vertLines[vcount].pos; oldPos = *fp; *fp += 1; if (*fp > viewWidth) *fp = 0; vertLines[vcount].hue += 0.005; if (vertLines[vcount].hue > 1) vertLines[vcount].hue -= 1; verticalLineWithAvoidance(*fp, 0, *fp, viewHeight, vertLines[vcount].hue, 1, &avoid); verticalLineWithAvoidance(oldPos, 0, oldPos, viewHeight, 0, 0, &avoid); if (++vcount >= nvert) vcount = 0; } else { //advance horiz line if (!nhoriz) return self; fp = &horizLines[hcount].pos; oldPos = *fp; *fp += 1; if (*fp > viewHeight) *fp = 0; horizLines[hcount].hue += 0.005; if (horizLines[hcount].hue > 1) horizLines[hcount].hue -= 1; horizLineWithAvoidance(0, *fp, viewWidth, *fp, horizLines[hcount].hue, 1, &avoid); horizLineWithAvoidance(0, oldPos, viewWidth, oldPos, 0, 0, &avoid); if (++hcount >= nhoriz) hcount = 0; } return self; } void horizLineWithAvoidance(float x1, float y1, float x2,float y2, float hue,float brightness, const NXRect *r) { if (y1 <= r->origin.y || y1 >= r->origin.y+r->size.height) colorLine(x1, y1, x2, y2, hue, brightness); else { colorLine(x1, y1, r->origin.x, y2, hue, brightness); colorLine(r->origin.x+r->size.width, y1, x2, y2, hue, brightness); } } void verticalLineWithAvoidance(float x1, float y1, float x2,float y2, float hue,float brightness, const NXRect *r) { if (x1 <= r->origin.x || x1 >= r->origin.x+r->size.width) colorLine(x1, y1, x2, y2, hue, brightness); else { colorLine(x1, y1, x2, r->origin.y, hue, brightness); colorLine(x1, r->origin.y+r->size.height, x2, y2, hue, brightness); } } - drawLinesInBuffer { NXRect avoid; int i; avoid.origin = redrawTo; avoid.size = redraw.size; for (i=0; i<nvert; i++) { float x = vertLines[i].pos; if (x >= avoid.origin.x && x <= avoid.origin.x + avoid.size.width) { colorLine(x-redrawTo.x, 0, x-redrawTo.x, avoid.size.height, vertLines[i].hue, 1); } } for (i=0; i<nhoriz; i++) { float y = horizLines[i].pos; if (y >= avoid.origin.y && y <= avoid.origin.y + avoid.size.height) { colorLine(0, y-redrawTo.y, avoid.size.width, y-redrawTo.y, horizLines[i].hue, 1); } } return self; } - inspector:sender { return [sender boinkInspector]; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.