|
|
1.1 root 1: // BoinkViewPart.m
2: //
3: // implements a bouncing ball screen saver view
4: //
5: // You may freely copy, distribute, and reuse the code in this example.
6: // NeXT disclaims any warranty of any kind, expressed or implied, as to its
7: // fitness for any particular use.
8:
9:
10: #import "BoinkViewPart.h"
11: #import "SpaceView.h"
12: #import "Thinker.h"
13: #import "BoinkWraps.h"
14: #import <appkit/NXImage.h>
15: #import <math.h>
16: #import <libc.h>
17: #import <dpsclient/wraps.h>
18:
19: @implementation BoinkView
20:
21: // assumed interval in milliseconds
22: #define ASSUMED_INTERVAL 35
23:
24: #define WIDTH 100
25: #define HEIGHT 100
26: #define GAP 4
27: #define COUNT 10
28: #define ACCEL (-2)
29: #define REBOUND (-1.3)
30:
31: // This screen height value is not critical, though it will be used
32: // to determine how high the ball can go
33: #define SCREEN_HEIGHT 832
34: #define LAUNCH_SPEED (sqrt(fabs(2*ACCEL*(SCREEN_HEIGHT - HEIGHT))))
35: #define REAL_LAUNCH_SPEEd (sqrt(fabs(2*accel*(viewHeight - HEIGHT))))
36:
37: #define MIN_X_SPEED (3)
38: #define MAX_X_SPEED (6)
39: #define ABS_MAX_X_SPEED (6)
40: #define MAX_Y_SPEED (LAUNCH_SPEED + 20)
41:
42: #define BUFFER_WIDTH (WIDTH + ABS_MAX_X_SPEED + 1)
43: #define BUFFER_HEIGHT (HEIGHT + MAX_Y_SPEED + 1)
44:
45:
46:
47: /* move the ball to its new bounce position */
48: - oneStep
49: {
50: NXRect black = {0,0,0,0};
51: NXRect ballRect;
52: BRECT new;
53: float scaledTime, calcYpos;
54:
55: then = now;
56: now = currentTimeInMs();
57:
58: /* calculate new ball x position */
59: xpos += [self timeCorrectedXSpeed];
60:
61: if (xpos < 0) /* ball hit left edge */
62: { xspeed = -xspeed;
63: if (viewWidth > WIDTH)
64: { spinDir = -spinDir;
65: }
66: xpos = 0;
67: }
68: else if (xpos > (viewWidth - WIDTH)) /* ball hit right edge */
69: { if (viewWidth > WIDTH)
70: {
71: xspeed = -[self getRandomXspeed];
72: [self checkXspeed:&xspeed];
73: xpos = (viewWidth - WIDTH);
74: }
75: else
76: { xspeed = xpos = 0;
77: }
78: }
79:
80:
81: scaledTime = ((float)(now - then) / ASSUMED_INTERVAL);
82: if (scaledTime > 1) scaledTime = 1;
83:
84: // calculate new ball vertical position
85: calcYpos = ypos + (scaledTime*yspeed) + ((accel * scaledTime * scaledTime)/2);
86:
87: // change vertical ball speed to simulate gravity
88: yspeed += (accel * scaledTime);
89:
90: if (calcYpos < (ypos - MAX_Y_SPEED)) calcYpos = ypos - MAX_Y_SPEED;
91: else if (calcYpos > (ypos + MAX_Y_SPEED)) calcYpos = ypos + MAX_Y_SPEED;
92:
93: ypos = calcYpos;
94:
95: if (yspeed < -MAX_Y_SPEED) yspeed = -MAX_Y_SPEED;
96:
97:
98: if (ypos <= 0) /* ball hit bottom of window */
99: {
100: ypos = 0;
101:
102: if (viewHeight > HEIGHT)
103: {
104: if (reboundMode == DECREASING)
105: {
106: yspeed = lastLaunchSpeed = lastLaunchSpeed + rebound;
107: }
108: else
109: {
110: yspeed = lastLaunchSpeed = lastLaunchSpeed - (2*rebound);
111: }
112:
113: if (yspeed <= 0)
114: {
115: yspeed = 0;
116: reboundMode = INCREASING; /* bounce height increases every bounce */
117: }
118: else if (yspeed > MAX_Y_SPEED) yspeed = MAX_Y_SPEED - (3*accel);
119: }
120: else yspeed = 0;
121:
122: }
123: else if (ypos >= (viewHeight - HEIGHT)) /* ball hit top of window */
124: { if (viewHeight > HEIGHT)
125: {
126: yspeed = accel;
127: ypos = (viewHeight - HEIGHT);
128: reboundMode = DECREASING; /* bounce height decreases every bounce */
129: spinDir = -spinDir;
130: }
131: else
132: { yspeed = ypos = 0;
133: }
134: }
135:
136:
137: /* rotate the ball by selecting a new ball image to blit */
138: /* we have an image of the ball in 10 different stages of rotation */
139:
140: [self incrementBallNumber];
141:
142: new.l = floor(xpos);
143: new.b = floor(ypos);
144: new.r = new.l + WIDTH;
145: new.t = new.b + HEIGHT;
146:
147: ballRect.origin.x = (WIDTH+GAP) * ballNum;
148: ballRect.origin.y = 0;
149: ballRect.size.width = WIDTH;
150: ballRect.size.height = HEIGHT;
151:
152: redrawTo.x = MIN(new.l, old.l);
153: redrawTo.y = MIN(new.b, old.b);
154:
155: redraw.origin.x = 0;
156: redraw.origin.y = 0;
157: redraw.size.width = (MAX(new.r, old.r)) - redrawTo.x + 1;
158: redraw.size.height = (MAX(new.t, old.t)) - redrawTo.y + 1;
159:
160: black.size= redraw.size;
161:
162: [self updateGrid];
163:
164: [buffer lockFocus];
165: PSsetgray(0);
166: NXRectFill(&black);
167:
168: ballTo.x = new.l - redrawTo.x;
169: ballTo.y = new.b - redrawTo.y;
170:
171: [self drawLinesInBuffer];
172:
173: [balls composite:NX_SOVER fromRect:&ballRect toPoint:&ballTo];
174: [buffer unlockFocus];
175:
176:
177: // Now bring it onto the screen
178:
179: [buffer composite:NX_COPY fromRect:&redraw toPoint:&redrawTo];
180:
181: old = new;
182:
183: return self;
184: }
185:
186:
187:
188: /* calculate a vertical launch speed which will get the ball almost to */
189: /* the top of the window before gravity pulls it back down. Little bit */
190: /* of physics lesson here... */
191:
192: - newSpeed
193: {
194: lastLaunchSpeed = yspeed = REAL_LAUNCH_SPEEd;
195: if (yspeed > MAX_Y_SPEED) yspeed = MAX_Y_SPEED;
196: xpos = 0;
197: ypos = 0;
198:
199: if (viewWidth <= WIDTH) xspeed = 0;
200: else xspeed = [self getRandomXspeed];
201:
202: [self checkXspeed:&xspeed];
203: rebound = REBOUND;
204: return self;
205: }
206:
207:
208: - initFrame:(const NXRect *)frameRect
209: {
210: NXRect black = {0, 0, BUFFER_WIDTH, BUFFER_HEIGHT };
211:
212: [super initFrame:frameRect];
213: [self allocateGState]; // For faster lock/unlockFocus
214: [self setClipping:NO]; // even faster...
215:
216: accel = ACCEL;
217: spinDir = 1;
218:
219: //in this case, I only need one buffer for several Views
220: if (!(buffer = [NXImage findImageNamed:"boinkBuffer"]))
221: {
222: buffer = [[NXImage alloc] initSize:&black.size];
223: [buffer setName:"boinkBuffer"];
224: }
225:
226: if ([buffer lockFocus])
227: {
228: PSsetgray(0);
229: NXRectFill(&black);
230: [buffer unlockFocus];
231: }
232:
233: balls = [NXImage findImageNamed:"balls"];
234:
235: [self newViewSize];
236:
237: return self;
238: }
239:
240: - setAccel:(float)val
241: {
242: accel = val;
243: return self;
244: }
245:
246: - sizeTo:(NXCoord)width :(NXCoord)height
247: {
248: [super sizeTo:width :height];
249: [self newViewSize];
250: return self;
251: }
252:
253: - drawSelf:(const NXRect *)rects :(int)rectCount
254: {
255: if (!rects || !rectCount) return self;
256:
257: //PSsetgray(0);
258: //NXRectFill(rects);
259:
260: NXRectClip(rects);
261: [self drawGrid];
262:
263: return self;
264: }
265:
266: - newViewSize
267: {
268: int i;
269: //this is called every time View size changes
270: NXRect black = {0, 0, BUFFER_WIDTH, BUFFER_HEIGHT };
271:
272: then = now = currentTimeInMs();
273:
274: if (oldSize.width == bounds.size.width &&
275: oldSize.height == bounds.size.height)
276: return self;
277: else
278: {
279: oldSize.width = bounds.size.width;
280: oldSize.height = bounds.size.height;
281: }
282:
283: old.l = old.r = old.b = old.t = ballTo.x = ballTo.y = 0;
284:
285: viewWidth = bounds.size.width;
286: viewHeight = bounds.size.height;
287: if (viewHeight > SCREEN_HEIGHT) viewHeight = SCREEN_HEIGHT;
288:
289: nvert = viewWidth/130;
290: if (nvert > NVERT) nvert = NVERT;
291: nhoriz = viewHeight/130;
292: if (nhoriz > NHORIZ) nhoriz = NHORIZ;
293:
294: if (viewWidth < WIDTH) nvert = 0;
295: if (viewHeight < HEIGHT) nhoriz= 0;
296: vcount = hcount = 0;
297:
298: for (i=0; i<nvert; i++)
299: {
300: vertLines[i].hue = i * 0.17;
301: while (vertLines[i].hue > 1) vertLines[i].hue -= 1;
302: vertLines[i].pos = floor(i * (viewWidth/nvert));
303: }
304:
305: for (i=0; i<nhoriz; i++)
306: {
307: horizLines[i].hue = i * 0.17 + 0.1;
308: while (horizLines[i].hue > 1) horizLines[i].hue -= 1;
309: horizLines[i].pos = i * floor((viewHeight/nhoriz)) + 1;
310: }
311:
312: if ([buffer lockFocus])
313: {
314: PSsetgray(0);
315: NXRectFill(&black);
316: [buffer unlockFocus];
317: }
318:
319: [self newSpeed];
320: return self;
321: }
322:
323: - incrementBallNumber
324: {
325: if (now > nextRotationTime)
326: {
327: ballNum += spinDir;
328:
329: if (ballNum >= COUNT) ballNum = 0;
330: else if (ballNum < 0) ballNum = COUNT-1;
331: nextRotationTime = now + 24;
332: }
333:
334: return self;
335: }
336:
337: - (float) getRandomXspeed
338: {
339: return randBetween(MIN_X_SPEED, MAX_X_SPEED);
340: }
341:
342: - (float) timeCorrectedXSpeed
343: {
344: float ret = xspeed * ((float)(now - then) / ASSUMED_INTERVAL);
345: [self checkXspeed:&ret];
346: return ret;
347: }
348:
349: - checkXspeed:(float *)speed
350: {
351: if (*speed > MAX_X_SPEED) *speed = MAX_X_SPEED;
352: else if (*speed < -MAX_X_SPEED) *speed = -MAX_X_SPEED;
353: return self;
354: }
355:
356: - (const char *)windowTitle
357: {
358: return "Boink!";
359: }
360:
361:
362: - drawGrid
363: {
364: int i;
365: float *fp;
366:
367: for (i=0; i<nvert; i++)
368: {
369: fp = &vertLines[i].pos;
370: colorLine(*fp, 0, *fp, viewHeight, vertLines[i].hue, 1);
371: }
372:
373: for (i=0; i<nhoriz; i++)
374: {
375: fp = &horizLines[i].pos;
376: colorLine(0, *fp, viewWidth, *fp, horizLines[i].hue, 1);
377: }
378:
379: return self;
380: }
381:
382: - updateGrid
383: {
384: NXRect avoid;
385: float oldPos;
386: float *fp;
387:
388: if (!nvert && !nhoriz) return self;
389:
390: if (now < nextLineDrawTime) return self;
391:
392: nextLineDrawTime = now + 3300;
393:
394: avoid.origin = redrawTo;
395: avoid.size = redraw.size;
396:
397: if (++toggle & 1)
398: {
399: //advance vertical line
400:
401: if (!nvert) return self;
402:
403: fp = &vertLines[vcount].pos;
404: oldPos = *fp;
405: *fp += 1;
406: if (*fp > viewWidth) *fp = 0;
407: vertLines[vcount].hue += 0.005;
408: if (vertLines[vcount].hue > 1) vertLines[vcount].hue -= 1;
409:
410:
411: verticalLineWithAvoidance(*fp, 0, *fp, viewHeight, vertLines[vcount].hue, 1, &avoid);
412: verticalLineWithAvoidance(oldPos, 0, oldPos, viewHeight, 0, 0, &avoid);
413:
414: if (++vcount >= nvert) vcount = 0;
415: }
416: else
417: {
418: //advance horiz line
419:
420: if (!nhoriz) return self;
421:
422: fp = &horizLines[hcount].pos;
423: oldPos = *fp;
424: *fp += 1;
425: if (*fp > viewHeight) *fp = 0;
426: horizLines[hcount].hue += 0.005;
427: if (horizLines[hcount].hue > 1) horizLines[hcount].hue -= 1;
428:
429:
430: horizLineWithAvoidance(0, *fp, viewWidth, *fp, horizLines[hcount].hue, 1, &avoid);
431: horizLineWithAvoidance(0, oldPos, viewWidth, oldPos, 0, 0, &avoid);
432:
433: if (++hcount >= nhoriz) hcount = 0;
434: }
435:
436: return self;
437: }
438:
439: void horizLineWithAvoidance(float x1, float y1, float x2,float y2,
440: float hue,float brightness, const NXRect *r)
441: {
442: if (y1 <= r->origin.y || y1 >= r->origin.y+r->size.height)
443: colorLine(x1, y1, x2, y2, hue, brightness);
444: else
445: {
446: colorLine(x1, y1, r->origin.x, y2, hue, brightness);
447: colorLine(r->origin.x+r->size.width, y1, x2, y2, hue, brightness);
448: }
449: }
450:
451: void verticalLineWithAvoidance(float x1, float y1, float x2,float y2,
452: float hue,float brightness, const NXRect *r)
453: {
454: if (x1 <= r->origin.x || x1 >= r->origin.x+r->size.width)
455: colorLine(x1, y1, x2, y2, hue, brightness);
456: else
457: {
458: colorLine(x1, y1, x2, r->origin.y, hue, brightness);
459: colorLine(x1, r->origin.y+r->size.height, x2, y2, hue, brightness);
460: }
461: }
462:
463: - drawLinesInBuffer
464: {
465: NXRect avoid;
466: int i;
467:
468: avoid.origin = redrawTo;
469: avoid.size = redraw.size;
470:
471: for (i=0; i<nvert; i++)
472: {
473: float x = vertLines[i].pos;
474:
475: if (x >= avoid.origin.x && x <= avoid.origin.x + avoid.size.width)
476: {
477: colorLine(x-redrawTo.x, 0, x-redrawTo.x, avoid.size.height, vertLines[i].hue, 1);
478: }
479: }
480:
481: for (i=0; i<nhoriz; i++)
482: {
483: float y = horizLines[i].pos;
484:
485: if (y >= avoid.origin.y && y <= avoid.origin.y + avoid.size.height)
486: {
487: colorLine(0, y-redrawTo.y, avoid.size.width, y-redrawTo.y, horizLines[i].hue, 1);
488: }
489: }
490:
491: return self;
492: }
493:
494: - inspector:sender
495: {
496: return [sender boinkInspector];
497: }
498:
499:
500:
501: @end
502:
503:
504:
505:
506:
507:
508:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.