This is BoinkViewPart.m in view mode; [Download] [Up]
#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:(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;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.