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