1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* ball --- bouncing balls with random drawing functions that leave a trail */
3
4 #if 0
5 static const char sccsid[] = "@(#)ball.c 5.00 2000/11/01 xlockmore";
6
7 #endif
8
9 /*-
10 * Copyright (c) 1995 by Heath Rice <rice@asl.dl.nec.com>.
11 *
12 * Permission to use, copy, modify, and distribute this software and its
13 * documentation for any purpose and without fee is hereby granted,
14 * provided that the above copyright notice appear in all copies and that
15 * both that copyright notice and this permission notice appear in
16 * supporting documentation.
17 *
18 * This file is provided AS IS with no warranties of any kind. The author
19 * shall have no liability with respect to the infringement of copyrights,
20 * trade secrets or any patents by this file or any part thereof. In no
21 * event will the author be liable for any lost revenue or profits or
22 * other special, indirect and consequential damages.
23 *
24 * Revision History:
25 * 01-Nov-2000: Allocation checks
26 * 10-May-1997: Compatible with xscreensaver
27 */
28
29 #ifdef STANDALONE
30 # define MODE_ball
31 # define DEFAULTS "*delay: 10000 \n" \
32 "*count: 10 \n" \
33 "*cycles: 20 \n" \
34 "*size: -100 \n" \
35 "*ncolors: 200 \n" \
36
37 # define reshape_ball 0
38 # define ball_handle_event 0
39 # include "xlockmore.h" /* in xscreensaver distribution */
40 #else /* STANDALONE */
41 # include "xlock.h" /* in xlockmore distribution */
42 #endif /* STANDALONE */
43
44 #ifdef MODE_ball
45
46 ENTRYPOINT ModeSpecOpt ball_opts =
47 {0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};
48
49 #ifdef USE_MODULES
50 const ModStruct ball_description =
51 {"ball", "init_ball", "draw_ball", "release_ball",
52 "refresh_ball", "init_ball", "free_ball", &ball_opts,
53 10000, 10, 20, -100, 64, 1.0, "",
54 "Shows bouncing balls", 0, NULL};
55
56 #endif
57
58 #define NONE 0 /* not in a window */
59 #define V 1 /* vertical */
60 #define H 2 /* horizontal */
61 #define B 3 /* ball */
62
63 #define MINBALLS 1
64 #define MINSIZE 2
65 #define MINGRIDSIZE 4
66
67 #define DEFNO 6
68 #define SPEED 156
69 #define SQLIMIT (SPEED*SPEED/(30*30)) /* square of lower speed limit */
70 #define RATE 600
71
72 typedef struct {
73 int x, y; /* x and y coords */
74 int dx, dy; /* x and y velocity */
75 int rad;
76 int bounce;
77 int dyold;
78 int started;
79 int def;
80 GC GcF, GcB;
81 } balltype;
82
83 typedef struct {
84 Bool painted;
85 balltype *bt;
86 int rad;
87 int size;
88 int width, height;
89 int bounce;
90 int nballs;
91 int dispx, dispy;
92 } ballstruct;
93
94 static ballstruct *balls = (ballstruct *) NULL;
95
96 static void
collided(ModeInfo * mi,int i,int n,int * dx,int * dy,int td)97 collided(ModeInfo * mi, int i, int n, int *dx, int *dy, int td)
98 {
99 ballstruct *bp = &balls[MI_SCREEN(mi)];
100 balltype *bti = &bp->bt[i];
101 balltype *btn = &bp->bt[n];
102 int rx1, ry1, rx2, ry2;
103 int Vx1, Vy1, Vx2, Vy2;
104 int NVx1, NVy1, NVx2, NVy2;
105
106 float Ux1, Uy1, Ux2, Uy2;
107 float mag1, mag2, imp;
108
109 rx1 = bti->x;
110 ry1 = bti->y;
111 Vx1 = bti->dx;
112 Vy1 = bti->dy;
113
114 rx2 = btn->x;
115 ry2 = btn->y;
116 Vx2 = btn->dx;
117 Vy2 = btn->dy;
118
119 Ux1 = rx1 - rx2;
120 Uy1 = ry1 - ry2;
121 mag1 = sqrt(((Ux1 * Ux1) + (Uy1 * Uy1)));
122 Ux1 = Ux1 / mag1;
123 Uy1 = Uy1 / mag1;
124
125 Ux2 = rx2 - rx1;
126 Uy2 = ry2 - ry1;
127 mag2 = sqrt(((Ux2 * Ux2) + (Uy2 * Uy2)));
128 Ux2 = Ux2 / mag2;
129 Uy2 = Uy2 / mag2;
130
131 imp = ((Vx1 * Ux2) + (Vy1 * Uy2)) + ((Vx2 * Ux1) + (Vy2 * Uy1));
132
133 NVx1 = Vx1 + (int) (imp * Ux1);
134 NVy1 = Vy1 + (int) (imp * Uy1);
135
136 NVx2 = Vx2 + (int) (imp * Ux2);
137 NVy2 = Vy2 + (int) (imp * Uy2);
138
139 bti->dx = NVx1;
140 bti->dy = NVy1;
141
142 btn->dx = NVx2;
143 btn->dy = NVy2;
144
145 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), btn->GcB,
146 btn->x - (btn->rad / 2), btn->y - (btn->rad / 2),
147 btn->rad, btn->rad, 0, 360 * 64);
148 if (bp->dispx > 100) {
149 *dx = (td * btn->dx) / RATE;
150 *dy = (td * btn->dy) / RATE;
151 } else {
152 *dx = (td * btn->dx) / 150;
153 *dy = (td * btn->dy) / 150;
154 }
155
156 btn->x += (*dx / 2);
157 btn->y += (*dy / 2);
158 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), btn->GcF,
159 btn->x - (btn->rad / 2), btn->y - (btn->rad / 2),
160 btn->rad, btn->rad, 0, 360 * 64);
161
162 if (bp->dispx > 100) {
163 *dx = (td * bti->dx) / RATE;
164 *dy = (td * bti->dy) / RATE;
165 } else {
166 *dx = (td * bti->dx) / 150;
167 *dy = (td * bti->dy) / 150;
168 }
169
170 bti->x += (*dx / 2);
171 bti->y += (*dy / 2);
172 }
173
174 static int
inwin(ballstruct * bp,int x,int y,int * n,int rad)175 inwin(ballstruct * bp, int x, int y, int *n, int rad)
176 {
177 int i, diffx, diffy;
178
179 if ((x < 0) || (x > bp->dispx)) {
180 return (V);
181 }
182 if ((y < 0) || (y > bp->dispy)) {
183 return (H);
184 }
185 if (bp->dispx > 100) {
186 for (i = 0; i < bp->nballs; i++) {
187 if ((i == (*n)) || (!bp->bt[i].def))
188 continue;
189 diffx = (bp->bt[i].x - x);
190 diffy = (bp->bt[i].y - y);
191 if ((diffx * diffx + diffy * diffy) <
192 (((rad / 2) * (rad / 2) * 2) +
193 ((bp->bt[i].rad / 2) * (bp->bt[i].rad / 2) * 2))) {
194 (*n) = i;
195 return (B);
196 }
197 }
198 }
199 return (NONE);
200 }
201
202 static void
randomball(ModeInfo * mi,int i)203 randomball(ModeInfo * mi, int i)
204 {
205 ballstruct *bp = &balls[MI_SCREEN(mi)];
206 balltype *bti = &bp->bt[i];
207 Display *display = MI_DISPLAY(mi);
208 int x, y, bn;
209 int dummy;
210 int attempts;
211 unsigned long randbg;
212
213 attempts = 0;
214 if (bp->bounce == -2)
215 bn = 30 + NRAND(69L);
216 else
217 bn = bp->bounce;
218
219 if (bn > 100)
220 bn = 100;
221 if (bn < 0)
222 bn = 0;
223 bn = (0 - bn);
224
225 if (bp->dispx > 100) {
226 bti->dx = NRAND(2L * SPEED) + SPEED;
227 bti->dy = NRAND(2L * SPEED) + (SPEED / 2);
228 } else {
229 bti->dx = NRAND(4L * SPEED) + (SPEED / 20);
230 bti->dy = NRAND(2L * SPEED) + (SPEED / 40);
231 }
232
233 switch (NRAND(9L) % 2) {
234 case 0:
235 break;
236 case 1:
237 bti->dx = (0 - bti->dx);
238 break;
239 }
240
241 bti->bounce = bn;
242 bti->dyold = 0;
243 bti->rad = bp->rad; /* Pretty lame... should be different sizes */
244
245 do {
246 dummy = i;
247 x = NRAND((long) bp->dispx);
248 y = 0;
249 attempts++;
250 if (attempts > 5) {
251 bti->def = 0;
252 return;
253 }
254 } while ((inwin(bp, x, y, &dummy, bti->rad) != NONE) ||
255 (inwin(bp, bti->dx + x, bti->dy + y, &dummy, bti->rad) != NONE));
256
257 bti->def = 1;
258 bti->x = x;
259 bti->y = y;
260
261 /* set background color for ball */
262
263 if (MI_NPIXELS(mi) > 2) {
264 randbg = MI_PIXEL(mi, NRAND(MI_NPIXELS(mi)));
265 } else {
266 randbg = MI_BLACK_PIXEL(mi);
267 }
268 XSetForeground(display, bti->GcB, randbg);
269
270 /* set foreground color for ball */
271
272 if (MI_NPIXELS(mi) > 2) {
273 randbg = MI_PIXEL(mi, NRAND(MI_NPIXELS(mi)));
274 } else {
275 randbg = MI_WHITE_PIXEL(mi);
276 }
277 XSetForeground(display, bti->GcF, randbg);
278
279 XFillArc(display, MI_WINDOW(mi),
280 bti->GcB, bti->x - (bti->rad / 2), bti->y - (bti->rad / 2),
281 bti->rad, bti->rad, 0, 360 * 64);
282 }
283
284 static void
free_ball_screen(Display * display,ballstruct * bp)285 free_ball_screen(Display *display, ballstruct *bp)
286 {
287 if (bp == NULL) {
288 return;
289 }
290 if (bp->bt != NULL) {
291 int i;
292
293 for (i = 0; i < bp->nballs; i++) {
294 if (bp->bt[i].GcF != None) {
295 XFreeGC(display, bp->bt[i].GcF);
296 bp->bt[i].GcF = None;
297 }
298 if (bp->bt[i].GcB != None) {
299 XFreeGC(display, bp->bt[i].GcB);
300 bp->bt[i].GcB = None;
301 }
302 }
303 free(bp->bt);
304 bp->bt = (balltype *) NULL;
305 }
306 bp = NULL;
307 }
308
309 ENTRYPOINT void
free_ball(ModeInfo * mi)310 free_ball(ModeInfo * mi)
311 {
312 free_ball_screen(MI_DISPLAY(mi), &balls[MI_SCREEN(mi)]);
313 }
314
315 ENTRYPOINT void
init_ball(ModeInfo * mi)316 init_ball(ModeInfo * mi)
317 {
318 Display *display = MI_DISPLAY(mi);
319 Window window = MI_WINDOW(mi);
320 int GcLp, i;
321 int size = MI_SIZE(mi);
322 ballstruct *bp;
323
324 MI_INIT(mi, balls);
325 bp = &balls[MI_SCREEN(mi)];
326
327 bp->bounce = 85;
328 bp->width = MI_WIDTH(mi);
329 bp->height = MI_HEIGHT(mi);
330
331 bp->nballs = MI_COUNT(mi);
332 if (bp->nballs < -MINBALLS) {
333 /* if bp->nballs is random ... the size can change */
334 if (bp->bt != NULL) {
335 free(bp->bt);
336 bp->bt = (balltype *) NULL;
337 }
338 bp->nballs = NRAND(-bp->nballs - MINBALLS + 1) + MINBALLS;
339 } else if (bp->nballs < MINBALLS)
340 bp->nballs = MINBALLS;
341 if (bp->bt == NULL) {
342 if ((bp->bt = (balltype *) calloc(bp->nballs, sizeof (balltype))) ==
343 NULL) {
344 free_ball_screen(display, bp);
345 return;
346 }
347 }
348 if (size == 0 ||
349 MINGRIDSIZE * size > bp->width || MINGRIDSIZE * size > bp->height) {
350 bp->rad = MAX(MINSIZE, MIN(bp->width, bp->height) / MINGRIDSIZE);
351 } else {
352 if (size < -MINSIZE)
353 bp->rad = NRAND(MIN(-size, MAX(MINSIZE, MIN(bp->width, bp->height) /
354 MINGRIDSIZE)) - MINSIZE + 1) + MINSIZE;
355 else if (size < MINSIZE)
356 bp->rad = MINSIZE;
357 else
358 bp->rad = MIN(size, MAX(MINSIZE, MIN(bp->width, bp->height) /
359 MINGRIDSIZE));
360 }
361
362 /* clearballs */
363 MI_CLEARWINDOW(mi);
364 bp->painted = False;
365 XFlush(display);
366 if (bp->nballs <= 0)
367 bp->nballs = 1;
368 if (!bp->bt[0].GcB) {
369 XGCValues gcv;
370
371 gcv.foreground = MI_WHITE_PIXEL(mi);
372 gcv.background = MI_BLACK_PIXEL(mi);
373 for (GcLp = 0; GcLp < bp->nballs; GcLp++) {
374 if ((bp->bt[GcLp].GcB = XCreateGC(display, window,
375 GCForeground | GCBackground, &gcv)) == None) {
376 free_ball_screen(display, bp);
377 return;
378 }
379 if ((bp->bt[GcLp].GcF = XCreateGC(display, window,
380 GCForeground | GCBackground, &gcv)) == None) {
381 free_ball_screen(display, bp);
382 return;
383 }
384 }
385 }
386 for (GcLp = 0; GcLp < bp->nballs; GcLp++) {
387 if (MI_NPIXELS(mi) > 2) {
388 XSetFunction(display, bp->bt[GcLp].GcB, NRAND(16L));
389 XSetFunction(display, bp->bt[GcLp].GcF, NRAND(16L));
390 } else {
391 XSetFunction(display, bp->bt[GcLp].GcB, NRAND(8L));
392 XSetFunction(display, bp->bt[GcLp].GcF, NRAND(8L));
393 }
394 }
395
396 bp->dispx = MI_WIDTH(mi);
397 bp->dispy = MI_HEIGHT(mi);
398
399 XFlush(display);
400 for (i = 0; i < bp->nballs; i++) {
401 randomball(mi, i);
402 }
403 }
404
405 ENTRYPOINT void
draw_ball(ModeInfo * mi)406 draw_ball(ModeInfo * mi)
407 {
408 Display *display = MI_DISPLAY(mi);
409 Window window = MI_WINDOW(mi);
410 int i, n;
411 int td;
412 int dx, dy;
413 int redo;
414 ballstruct *bp;
415
416 if (balls == NULL)
417 return;
418 bp = &balls[MI_SCREEN(mi)];
419 if (bp->bt == NULL)
420 return;
421
422 MI_IS_DRAWN(mi) = True;
423 td = 10;
424 bp->painted = True;
425 for (i = 0; i < bp->nballs; i++) {
426 if (!bp->bt[i].def)
427 randomball(mi, i);
428 }
429
430 for (i = 0; i < bp->nballs; i++) {
431 if (!bp->bt[i].def) {
432 continue;
433 }
434 XFillArc(display, window, bp->bt[i].GcB,
435 bp->bt[i].x - (bp->bt[i].rad / 2), bp->bt[i].y - (bp->bt[i].rad / 2),
436 bp->bt[i].rad, bp->bt[i].rad, 0, 360 * 64);
437 redo = 0;
438 if (((bp->bt[i].dx * bp->bt[i].dx + bp->bt[i].dy * bp->bt[i].dy) <
439 SQLIMIT) && (bp->bt[i].y >= (bp->dispy - 3))) {
440 redo = 25;
441 }
442 do {
443 if (bp->dispx > 100) {
444 dx = (td * bp->bt[i].dx) / RATE;
445 dy = (td * bp->bt[i].dy) / RATE;
446 } else {
447 dx = (td * bp->bt[i].dx) / 150;
448 dy = (td * bp->bt[i].dy) / 150;
449 }
450
451 if (redo > 5) {
452 redo = 0;
453 randomball(mi, i);
454 if (!bp->bt[i].def)
455 continue;
456 XFillArc(display, window, bp->bt[i].GcF,
457 bp->bt[i].x - (bp->bt[i].rad / 2),
458 bp->bt[i].y - (bp->bt[i].rad / 2),
459 bp->bt[i].rad, bp->bt[i].rad, 0, 360 * 64);
460 }
461 n = i;
462 switch (inwin(bp, dx + bp->bt[i].x, dy + bp->bt[i].y, &n,
463 bp->bt[i].rad)) {
464 case NONE:
465 bp->bt[i].x += dx;
466 bp->bt[i].y += dy;
467 redo = 0;
468 break;
469 case V:
470 bp->bt[i].dx = (int) (((float) bp->bt[i].bounce *
471 (float) bp->bt[i].dx) / (float) 100);
472 redo++;
473 break;
474 case H:
475 bp->bt[i].dy = (int) (((float) bp->bt[i].bounce *
476 (float) bp->bt[i].dy) / (float) 100);
477 if (bp->bt[i].bounce != 100) {
478 if ((bp->bt[i].y >= (bp->dispy - 3)) && (bp->bt[i].dy > -250) &&
479 (bp->bt[i].dy < 0)) {
480 redo = 15;
481 }
482 if ((bp->bt[i].y >= (bp->dispy - 3)) &&
483 (bp->bt[i].dy == bp->bt[i].dyold)) {
484 redo = 10;
485 }
486 bp->bt[i].dyold = bp->bt[i].dy;
487 }
488 redo++;
489 break;
490 case B:
491 if (redo > 5) {
492 if (bp->bt[i].y >= (bp->dispy - 3)) {
493 randomball(mi, i);
494 redo = 0;
495 } else if (bp->bt[n].y >= (bp->dispy - 3)) {
496 randomball(mi, n);
497 redo = 0;
498 } else
499 redo = 0;
500 } else {
501 collided(mi, i, n, &dx, &dy, td);
502 redo = 0;
503 }
504 break;
505 }
506 }
507 while (redo);
508 bp->bt[i].dy += td;
509
510 if (bp->bt[i].def)
511 XFillArc(display, window, bp->bt[i].GcF,
512 bp->bt[i].x - (bp->bt[i].rad / 2), bp->bt[i].y - (bp->bt[i].rad / 2),
513 bp->bt[i].rad, bp->bt[i].rad, 0, 360 * 64);
514 }
515
516 XFlush(display);
517 }
518
519 ENTRYPOINT void
release_ball(ModeInfo * mi)520 release_ball(ModeInfo * mi)
521 {
522 if (balls != NULL) {
523 int screen;
524
525 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
526 free_ball_screen(MI_DISPLAY(mi), &balls[screen]);
527 free(balls);
528 balls = (ballstruct *) NULL;
529 }
530 }
531
532 #ifndef STANDALONE
533 ENTRYPOINT void
refresh_ball(ModeInfo * mi)534 refresh_ball(ModeInfo * mi)
535 {
536 ballstruct *bp;
537
538 if (balls == NULL)
539 return;
540 bp = &balls[MI_SCREEN(mi)];
541
542 if (bp->painted)
543 MI_CLEARWINDOW(mi);
544 }
545 #endif
546
547 XSCREENSAVER_MODULE ("Ball", ball)
548
549 #endif /* MODE_ball */
550