1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* goop --- goop from a lava lamp */
3 
4 #if 0
5 static const char sccsid[] = "@(#)goop.c	5.00 2000/11/01 xlockmore";
6 
7 #endif
8 
9 /*-
10  * Copyright (c) 1997 by Jamie Zawinski
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  * 24-Mar-1998: xlock version David Bagley <bagleyd AT verizon.net>
27  * 1997: xscreensaver version Jamie Zawinski <jwz AT jwz.org>
28  */
29 
30 /*-
31  * original copyright
32  * xscreensaver, Copyright (c) 1997 Jamie Zawinski <jwz AT jwz.org>
33  *
34  * Permission to use, copy, modify, distribute, and sell this software and its
35  * documentation for any purpose is hereby granted without fee, provided that
36  * the above copyright notice appear in all copies and that both that
37  * copyright notice and this permission notice appear in supporting
38  * documentation.  No representations are made about the suitability of this
39  * software for any purpose.  It is provided "as is" without express or
40  * implied warranty.
41  */
42 
43 /*-
44  * This is pretty compute-intensive, probably due to the large number of
45  * polygon fills.  I tried introducing a scaling factor to make the spline
46  * code emit fewer line segments, but that made the edges very rough.
47  * However, tuning *maxVelocity, *elasticity and *delay can result in much
48  * smoother looking animation.  I tuned these for a 1280x1024 Indy display,
49  * but I don't know whether these values will be reasonable for a slower
50  * machine...
51  *
52  * The more planes the better -- SGIs have a 12-bit pseudocolor display
53  * (4096 colormap cells) which is mostly useless, except for this program,
54  * where it means you can have 11 or 12 mutually-transparent objects instead
55  * of only 7 or 8.  But, if you are using the 12-bit visual, you should crank
56  * down the velocity and elasticity, or server slowness will cause the
57  * animation to look jerky (yes, it's sad but true, SGI's X server is
58  * perceptibly slower when using plane masks on a 12-bit visual than on an
59  * 8-bit visual.)  Using -max-velocity 0.5 -elasticity 0.9 seems to work ok
60  * on my Indy R5k with visual 0x27 and the bottom-of-the-line 24-bit graphics
61  * board.
62  *
63  * It might look better if each blob had an outline, which was a *slightly*
64  * darker color than the center, to give them a bit more definition -- but
65  * that would mean using two planes per blob.  (Or maybe allocating the
66  * outline colors outside of the plane-space?  Then the outlines wouldn't be
67  * transparent, but maybe that wouldn't be so noticeable?)
68  *
69  * Oh, for an alpha channel... maybe I should rewrite this in GL.  Then the
70  * blobs could have thickness, and curved edges with specular reflections...
71  */
72 
73 #ifdef STANDALONE
74 #define MODE_goop
75 #define DEFAULTS "*delay: 40000 \n" \
76 	"*count: 100 \n" \
77 
78 /*-  Come back to this.
79   "*delay:		12000",
80 "*transparent:	true",
81 "*additive:		true",
82 "*xor:		false",
83 "*count:		0",
84 "*planes:		0",
85 "*thickness:		5",
86 "*torque:		0.0075",
87 "*elasticity:		1.8",
88 "*maxVelocity:	1.2",
89  */
90 # define free_goop 0
91 # define reshape_goop 0
92 # define goop_handle_event 0
93 #include "xlockmore.h"		/* in xscreensaver distribution */
94 #else /* STANDALONE */
95 #include "xlock.h"		/* in xlockmore distribution */
96 
97 #endif /* STANDALONE */
98 #include <spline.h>
99 
100 #ifdef MODE_goop
101 
102 ENTRYPOINT ModeSpecOpt goop_opts =
103 {0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};
104 
105 #ifdef USE_MODULES
106 ModStruct   goop_description =
107 {"goop", "init_goop", "draw_goop", "release_goop",
108  "init_goop", "init_goop", (char *) NULL, &goop_opts,
109  10000, -12, 1, 1, 64, 1.0, "",
110  "Shows goop from a lava lamp", 0, NULL};
111 
112 #endif
113 
114 
115 #define SCALE       10000	/* fixed-point math, for sub-pixel motion */
116 #define DEF_COUNT   12		/* When planes and count are 0, how many blobs. */
117 
118 #ifndef RANDSIGN
119 #define RANDSIGN() ((LRAND() & 1) ? 1 : -1)
120 #endif
121 
122 typedef struct {
123 	long        x, y;	/* position of midpoint */
124 	long        dx, dy;	/* velocity and direction */
125 	double      torque;	/* rotational speed */
126 	double      th;		/* angle of rotation */
127 	long        elasticity;	/* how fast they deform */
128 	long        max_velocity;	/* speed limit */
129 	long        min_r, max_r;	/* radius range */
130 	int         npoints;	/* control points */
131 	long       *r;		/* radii */
132 	spline     *splines;
133 } blob;
134 
135 typedef struct {
136 	int         nblobs;	/* number of blops per plane */
137 	blob       *blobs;
138 	Pixmap      pixmap;
139 	unsigned long pixel;
140 	GC          gc;
141 } layer;
142 
143 enum goop_mode {
144 	transparent,
145 	opaque,
146 	xored,
147 	outline
148 };
149 
150 typedef struct {
151 	enum goop_mode mode;
152 	int         width, height;
153 	int         nlayers;
154 	layer      *layers;
155 	unsigned long background;
156 	Pixmap      pixmap;
157 	GC          pixmap_gc;
158 } goopstruct;
159 
160 static goopstruct *goops = (goopstruct *) NULL;
161 
162 static Bool
make_blob(blob * b,int maxx,int maxy,int size)163 make_blob(blob * b, int maxx, int maxy, int size)
164 {
165 	int         i;
166 	long        mid;
167 
168 	maxx *= SCALE;
169 	maxy *= SCALE;
170 	size *= SCALE;
171 
172 	b->max_r = size / 2;
173 	b->min_r = size / 10;
174 
175 	if (b->min_r < (5 * SCALE))
176 		b->min_r = (5 * SCALE);
177 	mid = ((b->min_r + b->max_r) / 2);
178 
179 	b->torque = 0.0075;	/* torque init */
180 	b->elasticity = (long) (SCALE * 1.8);	/* elasticity init */
181 	b->max_velocity = (long) (SCALE * 1.2);		/* max_velocity init */
182 
183 	b->x = NRAND(maxx);
184 	b->y = NRAND(maxy);
185 
186 	b->dx = NRAND(b->max_velocity) * RANDSIGN();
187 	b->dy = NRAND(b->max_velocity) * RANDSIGN();
188 	b->th = (2.0 * M_PI) * LRAND() / MAXRAND * RANDSIGN();
189 	b->npoints = (int) (LRAND() % 5) + 5;
190 
191 	b->splines = make_spline(b->npoints);
192 	if ((b->r = (long *) malloc(sizeof (*b->r) * b->npoints)) == NULL)
193 		return False;
194 	for (i = 0; i < b->npoints; i++)
195 		b->r[i] = ((LRAND() % mid) + (mid / 2)) * RANDSIGN();
196 	return True;
197 }
198 
199 static void
throb_blob(blob * b)200 throb_blob(blob * b)
201 {
202 	int         i;
203 	double      frac = ((M_PI + M_PI) / b->npoints);
204 
205 	for (i = 0; i < b->npoints; i++) {
206 		long        r = b->r[i];
207 		long        ra = (r > 0 ? r : -r);
208 		double      th = (b->th > 0 ? b->th : -b->th);
209 		long        x, y;
210 
211 		/* place control points evenly around perimiter, shifted by theta */
212 		x = b->x + (long) (ra * cos(i * frac + th));
213 		y = b->y + (long) (ra * sin(i * frac + th));
214 
215 		b->splines->control_x[i] = x / SCALE;
216 		b->splines->control_y[i] = y / SCALE;
217 
218 		/* alter the radius by a random amount, in the direction in which
219 		   it had been going (the sign of the radius indicates direction.) */
220 		ra += (NRAND(b->elasticity) * (r > 0 ? 1 : -1));
221 		r = ra * (r >= 0 ? 1 : -1);
222 
223 		/* If we've reached the end (too long or too short) reverse direction. */
224 		if ((ra > b->max_r && r >= 0) ||
225 		    (ra < b->min_r && r < 0))
226 			r = -r;
227 		/* And reverse direction in mid-course once every 50 times. */
228 		else if (!(LRAND() % 50))
229 			r = -r;
230 
231 		b->r[i] = r;
232 	}
233 }
234 
235 static void
move_blob(blob * b,int maxx,int maxy)236 move_blob(blob * b, int maxx, int maxy)
237 {
238 	maxx *= SCALE;
239 	maxy *= SCALE;
240 
241 	b->x += b->dx;
242 	b->y += b->dy;
243 
244 	/* If we've reached the edge of the box, reverse direction. */
245 	if ((b->x > maxx && b->dx >= 0) ||
246 	    (b->x < 0 && b->dx < 0)) {
247 		b->dx = -b->dx;
248 	}
249 	if ((b->y > maxy && b->dy >= 0) ||
250 	    (b->y < 0 && b->dy < 0)) {
251 		b->dy = -b->dy;
252 	}
253 	/* Alter velocity randomly. */
254 	if (!(LRAND() % 10)) {
255 		b->dx += (NRAND(b->max_velocity / 2) * RANDSIGN());
256 		b->dy += (NRAND(b->max_velocity / 2) * RANDSIGN());
257 
258 		/* Throttle velocity */
259 		if (b->dx > b->max_velocity || b->dx < -b->max_velocity)
260 			b->dx /= 2;
261 		if (b->dy > b->max_velocity || b->dy < -b->max_velocity)
262 			b->dy /= 2;
263 	} {
264 		double      th = b->th;
265 		double      d = (b->torque == 0 ? 0 : (b->torque) * LRAND() / MAXRAND);
266 
267 		if (th < 0)
268 			th = -(th + d);
269 		else
270 			th += d;
271 
272 		if (th > (M_PI + M_PI))
273 			th -= (M_PI + M_PI);
274 		else if (th < 0)
275 			th += (M_PI + M_PI);
276 
277 		b->th = (b->th > 0 ? th : -th);
278 	}
279 
280 	/* Alter direction of rotation randomly. */
281 	if (!(LRAND() % 100))
282 		b->th *= -1;
283 }
284 
285 
286 static void
draw_blob(Display * display,Drawable drawable,GC gc,blob * b,Bool fill_p)287 draw_blob(Display * display, Drawable drawable, GC gc, blob * b,
288 	  Bool fill_p)
289 {
290 	compute_closed_spline(b->splines);
291 #ifdef DEBUG
292 	{
293 		int         i;
294 
295 		for (i = 0; i < b->npoints; i++)
296 			XDrawLine(display, drawable, gc, b->x / SCALE, b->y / SCALE,
297 			 b->splines->control_x[i], b->splines->control_y[i]);
298 	}
299 #else
300 	if (fill_p)
301 		XFillPolygon(display, drawable, gc, b->splines->points, b->splines->n_points,
302 			     Nonconvex, CoordModeOrigin);
303 	else
304 #endif
305 		XDrawLines(display, drawable, gc, b->splines->points, b->splines->n_points,
306 			   CoordModeOrigin);
307 }
308 
309 static Bool
make_layer(ModeInfo * mi,layer * l,int nblobs)310 make_layer(ModeInfo * mi, layer * l, int nblobs)
311 {
312 	int         i;
313 	int         blob_min, blob_max;
314 	XGCValues   gcv;
315 	int         width = MI_WIDTH(mi), height = MI_HEIGHT(mi);
316 
317 	l->nblobs = nblobs;
318 
319 	if ((l->blobs = (blob *) calloc(l->nblobs, sizeof (blob))) == NULL)
320 		return False;
321 
322 	blob_max = (width < height ? width : height) / 2;
323 	blob_min = (blob_max * 2) / 3;
324 	for (i = 0; i < l->nblobs; i++)
325 		if (!make_blob(&(l->blobs[i]), width, height, (int) (LRAND() %
326 				(blob_max - blob_min + 1)) + blob_min))
327 			return False;
328 
329 	if ((l->pixmap = XCreatePixmap(MI_DISPLAY(mi), MI_WINDOW(mi),
330 			width, height, 1)) == None)
331 		return False;
332 	if ((l->gc = XCreateGC(MI_DISPLAY(mi), l->pixmap, 0, &gcv)) == None)
333 		return False;
334 	return True;
335 }
336 
337 static void
draw_layer_plane(Display * display,layer * layer_plane,int width,int height)338 draw_layer_plane(Display * display, layer * layer_plane, int width, int height)
339 {
340 	int         i;
341 
342 	XSetForeground(display, layer_plane->gc, 1L);
343 	XFillRectangle(display, layer_plane->pixmap, layer_plane->gc,
344      0, 0, width, height);
345 	XSetForeground(display, layer_plane->gc, 0L);
346 	for (i = 0; i < layer_plane->nblobs; i++) {
347 		throb_blob(&(layer_plane->blobs[i]));
348 		move_blob(&(layer_plane->blobs[i]), width, height);
349 		draw_blob(display, layer_plane->pixmap, layer_plane->gc,
350        &(layer_plane->blobs[i]), True);
351 	}
352 }
353 
354 
355 static void
draw_layer_blobs(Display * display,Drawable drawable,GC gc,layer * layer_plane,int width,int height,Bool fill_p)356 draw_layer_blobs(Display * display, Drawable drawable, GC gc,
357 		 layer * layer_plane, int width, int height,
358 		 Bool fill_p)
359 {
360 	int         i;
361 
362 	for (i = 0; i < layer_plane->nblobs; i++) {
363 		throb_blob(&(layer_plane->blobs[i]));
364 		move_blob(&(layer_plane->blobs[i]), width, height);
365 		draw_blob(display, drawable, gc, &(layer_plane->blobs[i]), fill_p);
366 	}
367 }
368 
369 static void
free_goop_screen(Display * display,goopstruct * gp)370 free_goop_screen(Display * display, goopstruct * gp)
371 {
372 	int         l;
373 
374 	if (gp == NULL) {
375 		return;
376 	}
377 	if (gp->layers != NULL) {
378 		for (l = 0; l < gp->nlayers; l++) {
379 			if (gp->layers[l].blobs != NULL) {
380 				int         b;
381 
382 				for (b = 0; b < gp->layers[l].nblobs; b++) {
383 					if (gp->layers[l].blobs[b].r != NULL)
384 						free(gp->layers[l].blobs[b].r);
385 					free_spline(gp->layers[l].blobs[b].splines);
386 				}
387 				free(gp->layers[l].blobs);
388 			}
389 			if (gp->layers[l].gc != None)
390 				XFreeGC(display, gp->layers[l].gc);
391 			if (gp->layers[l].pixmap != None)
392 				XFreePixmap(display, gp->layers[l].pixmap);
393 		}
394 		free(gp->layers);
395 		gp->layers = (layer *) NULL;
396 	}
397 	if (gp->pixmap_gc != None) {
398 		XFreeGC(display, gp->pixmap_gc);
399 		gp->pixmap_gc = None;
400 	}
401 	if (gp->pixmap != None) {
402 		XFreePixmap(display, gp->pixmap);
403 		gp->pixmap = None;
404 	}
405 	gp = NULL;
406 }
407 
408 ENTRYPOINT void
init_goop(ModeInfo * mi)409 init_goop(ModeInfo * mi)
410 {
411 	Display    *display = MI_DISPLAY(mi);
412 	Window      window = MI_WINDOW(mi);
413 	int         i;
414 	XGCValues   gcv;
415 	int         nblobs;
416 	unsigned long *plane_masks = NULL;
417 	unsigned long base_pixel = 0;
418 	goopstruct *gp;
419 
420 	MI_INIT(mi, goops);
421 	gp = &goops[MI_SCREEN(mi)];
422 
423 	gp->mode = (False /* xor init */ ? xored
424 		    : (True /* transparent init */ ? transparent : opaque));
425 
426 	gp->width = MI_WIDTH(mi);
427 	gp->height = MI_HEIGHT(mi);
428 
429 	free_goop_screen(display, gp);
430 
431 	gp->nlayers = 0;	/* planes init */
432 	if (gp->nlayers <= 0)
433 		gp->nlayers = (int) (LRAND() % (MI_DEPTH(mi) - 2)) + 2;
434 	if ((gp->layers = (layer *) calloc(gp->nlayers, sizeof (layer))) == NULL) {
435 		return; /* free_goop_screen just ran */
436 	}
437 
438 	if ((MI_NPIXELS(mi) < 2) && gp->mode == transparent)
439 		gp->mode = opaque;
440 
441 	/* Try to allocate some color planes before committing to nlayers.
442 	 */
443 #if 0
444 	if (gp->mode == transparent) {
445 		Bool        additive_p = True;	/* additive init */
446 		int         nplanes = gp->nlayers;
447 
448 		/* allocate_alpha_colors (display, MI_COLORMAP(mi), &nplanes, additive_p, &plane_masks,
449 		   &base_pixel); *//* COME BACK */
450 		if (nplanes > 1)
451 			gp->nlayers = nplanes;
452 		else {
453 			(void) fprintf(stderr,
454 				       "could not allocate any color planes; turning transparency off.\n");
455 			gp->mode = opaque;
456 		}
457 	}
458 #else
459 	if (gp->mode == transparent)
460 		gp->mode = opaque;
461 #endif
462 
463 	nblobs = MI_COUNT(mi);
464 	if (nblobs < 0) {
465 		nblobs = NRAND(-nblobs) + 1;	/* Add 1 so its not too boring */
466 	} {
467 		int        *lblobs;
468 		int         total = DEF_COUNT;
469 
470 		if ((lblobs = (int *) calloc(gp->nlayers,
471 				sizeof (int))) == NULL) {
472 			free_goop_screen(display, gp);
473 			return;
474 		}
475 		if (nblobs <= 0)
476 			while (total)
477 				for (i = 0; total && i < gp->nlayers; i++)
478 					lblobs[i]++, total--;
479 		for (i = 0; i < gp->nlayers; i++)
480 			if (!make_layer(mi, &(gp->layers[i]),
481 				   (nblobs > 0 ? nblobs : lblobs[i])))
482 				free_goop_screen(display, gp);
483 		free(lblobs);
484 	}
485 
486 	if (gp->mode == transparent && plane_masks) {
487 		for (i = 0; i < gp->nlayers; i++)
488 			gp->layers[i].pixel = base_pixel | plane_masks[i];
489 		gp->background = base_pixel;
490 	}
491 	if (plane_masks != NULL)
492 		free(plane_masks);
493 
494 	if (gp->mode != transparent) {
495 		gp->background = 0;	/* init */
496 
497 		for (i = 0; i < gp->nlayers; i++) {
498 			if (MI_NPIXELS(mi) > 2)
499 				gp->layers[i].pixel = MI_PIXEL(mi, NRAND(MI_NPIXELS(mi)));
500 			else
501 				gp->layers[i].pixel = MI_WHITE_PIXEL(mi);
502 		}
503 	}
504 	if ((gp->pixmap = XCreatePixmap(display, window,
505 			MI_WIDTH(mi), MI_HEIGHT(mi),
506 			(gp->mode == xored ? 1 : MI_DEPTH(mi)))) == None) {
507 		free_goop_screen(display, gp);
508 		return;
509 	}
510 
511 	gcv.background = gp->background;
512 	gcv.foreground = 255;	/* init */
513 	gcv.line_width = 5;	/* thickness init */
514 	if ((gp->pixmap_gc = XCreateGC(display, gp->pixmap, GCLineWidth,
515 			 &gcv)) == None) {
516 		free_goop_screen(display, gp);
517 		return;
518 	}
519 	MI_CLEARWINDOW(mi);
520 }
521 
522 ENTRYPOINT void
draw_goop(ModeInfo * mi)523 draw_goop(ModeInfo * mi)
524 {
525 	Display    *display = MI_DISPLAY(mi);
526 	Window      window = MI_WINDOW(mi);
527 	int         i;
528 	goopstruct *gp;
529 
530 	if (goops == NULL)
531 		return;
532 	gp = &goops[MI_SCREEN(mi)];
533 	if (gp->layers == NULL)
534 		return;
535 
536 	MI_IS_DRAWN(mi) = True;
537 	switch (gp->mode) {
538 		case transparent:
539 
540 			for (i = 0; i < gp->nlayers; i++)
541 				draw_layer_plane(display, &(gp->layers[i]), gp->width, gp->height);
542 
543 			XSetForeground(display, gp->pixmap_gc, gp->background);
544 			XSetPlaneMask(display, gp->pixmap_gc, AllPlanes);
545 			XFillRectangle(display, gp->pixmap, gp->pixmap_gc, 0, 0,
546 				       gp->width, gp->height);
547 			XSetForeground(display, gp->pixmap_gc, ~0L);
548 			for (i = 0; i < gp->nlayers; i++) {
549 				XSetPlaneMask(display, gp->pixmap_gc, gp->layers[i].pixel);
550 
551 #if 0
552 			XSetForeground (display, gp->pixmap_gc, ~0L);
553 			XFillRectangle (display, gp->pixmap, gp->pixmap_gc, 0, 0,
554 				gp->width, gp->height);
555 			XSetForeground (display, gp->pixmap_gc, 0L);
556 #endif
557 				draw_layer_blobs(display, gp->pixmap, gp->pixmap_gc,
558 				     &(gp->layers[i]), gp->width, gp->height,
559 						 True);
560 			}
561 			XCopyArea(display, gp->pixmap, window, MI_GC(mi), 0, 0,
562 				  gp->width, gp->height, 0, 0);
563 			break;
564 
565 		case xored:
566 			XSetFunction(display, gp->pixmap_gc, GXcopy);
567 			XSetForeground(display, gp->pixmap_gc, 0);
568 			XFillRectangle(display, gp->pixmap, gp->pixmap_gc, 0, 0,
569 				       gp->width, gp->height);
570 			XSetFunction(display, gp->pixmap_gc, GXxor);
571 			XSetForeground(display, gp->pixmap_gc, 1);
572 			for (i = 0; i < gp->nlayers; i++)
573 				draw_layer_blobs(display, gp->pixmap, gp->pixmap_gc,
574 				     &(gp->layers[i]), gp->width, gp->height,
575 						 (gp->mode != outline));
576 			XCopyPlane(display, gp->pixmap, window, MI_GC(mi), 0, 0,
577 				   gp->width, gp->height, 0, 0, 1L);
578 			break;
579 
580 		case opaque:
581 		case outline:
582 			XSetForeground(display, gp->pixmap_gc, MI_BLACK_PIXEL(mi));
583 			XFillRectangle(display, gp->pixmap, gp->pixmap_gc, 0, 0,
584 				       gp->width, gp->height);
585 			for (i = 0; i < gp->nlayers; i++) {
586 				XSetForeground(display, gp->pixmap_gc, gp->layers[i].pixel);
587 				draw_layer_blobs(display, gp->pixmap, gp->pixmap_gc,
588 				     &(gp->layers[i]), gp->width, gp->height,
589 						 (gp->mode != outline));
590 			}
591 			XCopyArea(display, gp->pixmap, window, MI_GC(mi), 0, 0,
592 				  gp->width, gp->height, 0, 0);
593 			break;
594 
595 		default:
596 			if (MI_IS_VERBOSE(mi)) {
597 				(void) fprintf(stderr,
598 					"Weirdness in draw_goop()\n");
599 				(void) fprintf(stderr,
600 					"gp->mode = %d\n", gp->mode);
601 			}
602 			break;
603 	}
604 }
605 
606 ENTRYPOINT void
release_goop(ModeInfo * mi)607 release_goop(ModeInfo * mi)
608 {
609 	if (goops != NULL) {
610 		int         screen;
611 
612 		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
613 			free_goop_screen(MI_DISPLAY(mi), &goops[screen]);
614 		free(goops);
615 		goops = (goopstruct *) NULL;
616 	}
617 }
618 
619 XSCREENSAVER_MODULE ("Goop", goop)
620 
621 #endif /* MODE_goop */
622