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