1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* lisa --- animated full-loop lissajous figures */
5 static const char sccsid[] = "@(#)lisa.c 5.00 2000/11/01 xlockmore";
9 * Copyright (c) 1997, 2006 by Caleb Cullen.
11 * Permission to use, copy, modify, and distribute this software and its
12 * documentation for any purpose and without fee is hereby granted,
13 * provided that the above copyright notice appear in all copies and that
14 * both that copyright notice and this permission notice appear in
15 * supporting documentation.
17 * This file is provided AS IS with no warranties of any kind. The author
18 * shall have no liability with respect to the infringement of copyrights,
19 * trade secrets or any patents by this file or any part thereof. In no
20 * event will the author be liable for any lost revenue or profits or
21 * other special, indirect and consequential damages.
24 * 23-Feb-2006: fixed color-cycling issues
25 * 01-Nov-2000: Allocation checks
26 * 10-May-1997: Compatible with xscreensaver
28 * The inspiration for this program, Lasp, was written by Adam B. Roach
29 * in 1990, assisted by me, Caleb Cullen. It was written first in C, then
30 * in assembly, and used pre-calculated data tables to graph lissajous
31 * figures on 386 machines and lower. This version bears only superficial
32 * resemblances to the original Lasp.
34 * The `lissie' module's source code was studied as an example of how
35 * to incorporate a new module into xlock. Resemblances to it are
36 * expected, but not intended to be plaigiaristic.
38 * February, 2006: 21st Century Update for Lisa
39 * + fixed color-mapping: the 'beginning' of the loop always uses the
40 * same (starting) pixel value, causing the loop's coloration to
41 * appear solid rather than flickering as in the previous version
42 * + all lines/points in a single color are drawn at once using XDrawLines()
43 * or XDrawPoints(); the artifacting evident in the previous version
44 * has been masked by the use of CapNotLast to separate individual drawn
45 * areas with intentional "whitespace" (typically black)
46 * + added many new elements to the Function[] array
47 * + randomized selection of next function
48 * + introduced concept of "rarely-chosen" functions
49 * + cleaned up code somewhat, standardized capitalization, commented all
50 * #directives with block labels
55 # define DEFAULTS "*delay: 17000 \n" \
60 "*fpsSolid: true \n" \
62 # define UNIFORM_COLORS
63 # define reshape_lisa 0
64 # define lisa_handle_event 0
65 # include "xlockmore.h" /* in xscreensaver distribution */
66 #else /* STANDALONE */
67 #include "xlock.h" /* in xlockmore distribution */
69 #endif /* STANDALONE */
73 #define DEF_ADDITIVE "True"
77 static XrmOptionDescRec opts[] =
79 {"-additive", ".lisa.additive", XrmoptionNoArg, "True"},
80 {"+additive", ".lisa.additive", XrmoptionNoArg, "False"}
83 static argtype vars[] =
85 {&additive, "additive", "Additive", DEF_ADDITIVE, t_Bool}
88 static OptionStruct desc[] =
90 {"-/+additive", "turn on/off additive functions mode"}
93 ENTRYPOINT ModeSpecOpt lisa_opts =
94 {sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};
97 ModStruct lisa_description =
98 {"lisa", "init_lisa", "draw_lisa", "release_lisa",
99 "refresh_lisa", "change_lisa", (char *) NULL, &lisa_opts,
100 17000, 1, 768, -1, 64, 1.0, "",
101 "Shows animated lissajous figures", 0, NULL};
106 /* #define FOLLOW_FUNC_ORDER 1 */
108 #define XVMAX 10 /* Maximum velocities */
110 #define LISAMAXFUNCS 2
111 #define NUMSTDFUNCS 28
112 #define RAREFUNCMIN 25
113 #define RAREFUNCODDS 4 /* 1:n chance a rare function will be re-randomized */
117 #define STARTFUNC 24 /* if negative, is upper-bound on randomization */
118 #define LINEWIDTH -8 /* if negative, is upper-bound on randomization */
119 #define LINESTYLE LineSolid /* an insane man might have fun with this :) */
120 #define LINECAP CapNotLast /* anything else looks pretty crappy */
121 #define LINEJOIN JoinBevel /* this ought to be fastest */
122 #define SET_COLOR() \
123 if (MI_NPIXELS(mi) > 2) { \
124 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_PIXEL(mi, loop->color)); \
126 && pctr % loop->cstep == 0 \
127 && ++(loop->color) >= (unsigned) MI_NPIXELS(mi)) \
128 { loop->color=STARTCOLOR; } \
129 } else { XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi)); }
130 #define GET_RADIUS(context) \
131 ((context->width > context->height)?context->height:context->width) * 3 / 8
132 #define CHECK_RADIUS(loop, context) \
133 if ((context->height / 2 > MI_SIZE(mi)) && (context->width / 2 > MI_SIZE(mi))) \
134 loop->radius = MI_SIZE(mi); \
135 if ((loop->radius < 0) || \
136 (loop->radius > loop->center.x) || \
137 (loop->radius > loop->center.y)) loop->radius = GET_RADIUS(context)
138 #define PRINT_FUNC(funcptr) \
139 printf("new function -- #%d:\n\tx = sin(%gs) * sin(%gs)\n\ty = sin(%gt) * sin(%gt)\n", \
141 funcptr->xcoeff[0], funcptr->xcoeff[1], \
142 funcptr->ycoeff[0], funcptr->ycoeff[1])
145 typedef struct lisafunc_struct {
146 double xcoeff[2], ycoeff[2];
151 typedef struct lisa_struct {
153 int radius, dx, dy, nsteps, nfuncs, melting, cstep;
154 double pistep, phi, theta;
155 XPoint center, *lastpoint;
156 lisafuncs *function[LISAMAXFUNCS];
160 typedef struct lisacontext_struct {
162 int width, height, nlissajous, loopcount;
167 static lisacons *Lisa = (lisacons *) NULL;
169 static lisafuncs Function[NUMSTDFUNCS] =
173 {1.0, 2.0}, 2, 2, 0},
176 {1.0, 1.0}, 2, 2, 1},
179 {1.0, 2.0}, 2, 2, 2},
182 {1.0, 3.0}, 2, 2, 3},
185 {1.0, 2.0}, 2, 2, 4},
188 {1.0, 3.0}, 2, 2, 5},
191 {1.0, 4.0}, 2, 2, 6},
194 {1.0, 5.0}, 2, 2, 7},
197 {2.0, 5.0}, 2, 2, 8},
200 {2.0, 5.0}, 2, 2, 9},
203 {3.0, 5.0}, 2, 2, 10},
206 {2.0, 3.0}, 2, 2, 11},
209 {2.0, 3.0}, 2, 2, 12},
212 {1.0, 3.0}, 2, 2, 13},
215 {1.0, 3.0}, 2, 2, 14},
218 {2.0, 3.0}, 2, 2, 15},
221 {2.0, 3.0}, 2, 2, 16},
224 {2.0, 3.0}, 2, 2, 17},
227 {2.0, 3.0}, 2, 2, 18},
230 {2.0, 5.0}, 2, 2, 19},
233 {2.0, 7.0}, 2, 2, 20},
236 {5.0, 7.0}, 2, 2, 21},
239 {3.0, 7.0}, 2, 2, 22},
242 {5.0, 7.0}, 2, 2, 23},
245 {5.0, 7.0}, 2, 2, 24},
246 { /* functions past here are 'rare' and won't */
247 {2.0, 7.0}, /* show up as often. tweak the #defines above */
248 {1.0, 7.0}, 2, 2, 25}, /* to see them more frequently */
251 {1.0, 7.0}, 2, 2, 26},
254 {2.0, 9.0}, 2, 2, 27}
260 free_lisa(lisacons *lc)
262 while (lc->lissajous) {
265 for (lctr = 0; lctr < lc->nlissajous; lctr++) {
266 (void) free((void *) lc->lissajous[lctr].lastpoint);
268 (void) free((void *) lc->lissajous);
269 lc->lissajous = (lisas *) NULL;
274 drawlisa(ModeInfo * mi, lisas * loop)
277 XPoint *lp = loop->lastpoint;
278 lisacons *lc = &Lisa[MI_SCREEN(mi)];
279 lisafuncs **lf = loop->function;
280 int phase = lc->loopcount % loop->nsteps;
281 int pctr, fctr, xctr, yctr, extra_points;
282 double xprod, yprod, xsum, ysum;
284 /* why carry this around in the struct when we can calculate it on demand? */
285 extra_points = loop->cstep - (loop->nsteps % loop->cstep);
287 /* Allocate the np (new point) array (with padding) */
288 if ((np = (XPoint *) calloc(loop->nsteps+extra_points, sizeof (XPoint))) == NULL) {
293 /* Update the center */
294 loop->center.x += loop->dx;
295 loop->center.y += loop->dy;
296 CHECK_RADIUS(loop, lc);
298 /* check for overlaps -- where the figure might go off the screen */
300 if ((loop->center.x - loop->radius) <= 0) {
301 loop->center.x = loop->radius;
302 loop->dx = NRAND(XVMAX);
303 } else if ((loop->center.x + loop->radius) >= lc->width) {
304 loop->center.x = lc->width - loop->radius;
305 loop->dx = -NRAND(XVMAX);
307 if ((loop->center.y - loop->radius) <= 0) {
308 loop->center.y = loop->radius;
309 loop->dy = NRAND(YVMAX);
310 } else if ((loop->center.y + loop->radius) >= lc->height) {
311 loop->center.y = lc->height - loop->radius;
312 loop->dy = -NRAND(YVMAX);
315 /* Now draw the points, and erase the ones from the last cycle */
317 for (pctr = 0; pctr < loop->nsteps; pctr++) {
319 loop->phi = (double) (pctr - phase) * loop->pistep;
320 loop->theta = (double) (pctr + phase) * loop->pistep;
328 xprod += sin(lf[fctr]->xcoeff[xctr] * loop->theta);
330 yprod += sin(lf[fctr]->ycoeff[yctr] * loop->phi);
333 xsum += xprod * (double) (loop->nsteps - loop->melting) /
334 (double) loop->nsteps;
335 ysum += yprod * (double) (loop->nsteps - loop->melting) /
336 (double) loop->nsteps;
338 xsum += xprod * (double) loop->melting / (double) loop->nsteps;
339 ysum += yprod * (double) loop->melting / (double) loop->nsteps;
346 xsum = xsum * (double) loop->radius / (double) lf[fctr]->nx;
347 ysum = ysum * (double) loop->radius / (double) lf[fctr]->ny;
352 yprod = xprod = (double) loop->radius *
353 (double) (loop->nsteps - loop->melting) /
354 (double) (loop->nsteps);
356 yprod = xprod = (double) loop->radius *
357 (double) (loop->melting) / (double) (loop->nsteps);
360 xprod = yprod = (double) loop->radius;
363 xprod *= sin(lf[fctr]->xcoeff[xctr] * loop->theta);
365 yprod *= sin(lf[fctr]->ycoeff[yctr] * loop->phi);
370 if ((loop->nfuncs > 1) && (!loop->melting)) {
371 xsum /= (double) loop->nfuncs;
372 ysum /= (double) loop->nfuncs;
374 xsum += (double) loop->center.x;
375 ysum += (double) loop->center.y;
377 np[pctr].x = (int) ceil(xsum);
378 np[pctr].y = (int) ceil(ysum);
380 /* fill in extra points */
381 for (pctr=loop->nsteps; pctr < loop->nsteps+extra_points; pctr++) {
382 np[pctr].x = np[pctr - loop->nsteps].x;
383 np[pctr].y = np[pctr - loop->nsteps].y;
386 if (!--loop->melting) {
388 loop->function[0] = loop->function[1];
392 /* reset starting color each time to prevent ass-like appearance */
393 loop->color = STARTCOLOR;
395 if (loop->cstep < xMaxLines) {
396 /* printf("Drawing dashes\n"); */
397 for (pctr = 0; pctr < loop->nsteps; pctr+=loop->cstep) {
398 #if defined DRAWLINES
399 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), loop->linewidth,
400 LINESTYLE, LINECAP, LINEJOIN);
401 /* erase the last cycle's point */
402 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
403 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), &lp[pctr],
404 loop->cstep, CoordModeOrigin);
406 /* Set the new color */
409 /* plot this cycle's point */
410 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
411 &np[pctr], loop->cstep, CoordModeOrigin);
412 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 1,
413 LINESTYLE, LINECAP, LINEJOIN);
415 /* erase the last cycle's point */
416 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
417 XDrawPoints(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
418 &lp[pctr], loop->cstep, CoordModeOrigin);
420 /* Set the new color */
423 /* plot this cycle's point */
424 XDrawPoints(MI_DISPLAY(mi), MI_WINDOW(mi),
425 MI_GC(mi), &np[pctr], loop->cstep, CoordModeOrigin);
428 } else { /* on my system, cstep is larger than 65532/2 if we get here */
429 for (pctr = 0; pctr < loop->nsteps; pctr++) {
430 #if defined DRAWLINES
431 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), loop->linewidth,
432 LINESTYLE, LINECAP, LINEJOIN);
433 /* erase the last cycle's point */
434 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
435 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi),
436 MI_GC(mi), lp[pctr].x, lp[pctr].y,
437 lp[(pctr + 1) % loop->nsteps].x,
438 lp[(pctr + 1) % loop->nsteps].y);
440 /* Set the new color */
443 /* plot this cycle's point */
444 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi),
445 MI_GC(mi), np[pctr].x, np[pctr].y,
446 np[(pctr + 1) % loop->nsteps].x,
447 np[(pctr + 1) % loop->nsteps].y);
448 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 1,
449 LINESTYLE, LINECAP, LINEJOIN);
450 #else /* DRAWLINES */
451 /* erase the last cycle's point */
452 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
453 XDrawPoint(MI_DISPLAY(mi), MI_WINDOW(mi),
454 MI_GC(mi), lp[pctr].x, lp[pctr].y);
456 /* Set the new color */
459 /* plot this cycle's point */
460 XDrawPoint(MI_DISPLAY(mi), MI_WINDOW(mi),
461 MI_GC(mi), np[pctr].x, np[pctr].y);
462 #endif /* DRAWLINES */
465 (void) free((void *) lp);
466 loop->lastpoint = np;
471 initlisa(ModeInfo * mi, lisas * loop)
473 lisacons *lc = &Lisa[MI_SCREEN(mi)];
474 lisafuncs **lf = loop->function;
476 int phase, pctr, fctr, xctr, yctr, extra_points;
477 double xprod, yprod, xsum, ysum;
479 xMaxLines = (XMaxRequestSize(MI_DISPLAY(mi))-3)/2;
480 /* printf("Got xMaxLines = %d\n", xMaxLines); */
481 loop->nsteps = MI_CYCLES(mi);
482 if (loop->nsteps == 0)
484 if (MI_NPIXELS(mi) > 2) {
485 loop->color = STARTCOLOR;
486 loop->cstep = (loop->nsteps > MI_NPIXELS(mi)) ? loop->nsteps / MI_NPIXELS(mi) : 1;
488 loop->color = MI_WHITE_PIXEL(mi);
491 extra_points = loop->cstep - (loop->nsteps % loop->cstep);
492 lc->maxcycles = (MAXCYCLES * loop->nsteps) - 1;
493 loop->cstep = ( loop->nsteps > MI_NPIXELS(mi) ) ? loop->nsteps / MI_NPIXELS(mi) : 1;
494 /* printf("Got cstep = %d\n", loop->cstep); */
497 loop->pistep = 2.0 * M_PI / (double) loop->nsteps;
498 loop->center.x = lc->width / 2;
499 loop->center.y = lc->height / 2;
500 loop->radius = (int) MI_SIZE(mi);
501 CHECK_RADIUS(loop, lc);
502 loop->dx = NRAND(XVMAX);
503 loop->dy = NRAND(YVMAX);
506 #if defined STARTFUNC
507 lf[0] = &Function[STARTFUNC];
508 #else /* STARTFUNC */
509 lf[0] = &Function[NRAND(NUMSTDFUNCS)];
510 #endif /* STARTFUNC */
512 if ((lp = loop->lastpoint = (XPoint *)
513 calloc(loop->nsteps+extra_points, sizeof (XPoint))) == NULL) {
517 phase = lc->loopcount % loop->nsteps;
520 printf( "nsteps = %d\tcstep = %d\tmrs = %d\textra_points = %d\n",
521 loop->nsteps, loop->cstep, xMaxLines, extra_points );
525 for (pctr = 0; pctr < loop->nsteps; pctr++) {
526 loop->phi = (double) (pctr - phase) * loop->pistep;
527 loop->theta = (double) (pctr + phase) * loop->pistep;
531 xprod = yprod = (double) loop->radius;
535 xprod *= sin(lf[fctr]->xcoeff[xctr] * loop->theta);
537 yprod *= sin(lf[fctr]->ycoeff[yctr] * loop->phi);
541 if (loop->nfuncs > 1) {
545 xsum += (double) loop->center.x;
546 ysum += (double) loop->center.y;
548 lp[pctr].x = (int) ceil(xsum);
549 lp[pctr].y = (int) ceil(ysum);
551 /* this fills in the extra points, so we can use segment-drawing calls */
552 for (pctr = loop->nsteps; pctr < loop->nsteps + extra_points; pctr++) {
553 lp[pctr].x=lp[pctr - loop->nsteps].x;
554 lp[pctr].y=lp[pctr - loop->nsteps].y;
556 #if defined DRAWLINES
557 loop->linewidth = LINEWIDTH; /* #### make this a resource */
559 if (loop->linewidth == 0)
561 if (loop->linewidth < 0)
562 loop->linewidth = NRAND(-loop->linewidth) + 1;
563 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), loop->linewidth,
564 LINESTYLE, LINECAP, LINEJOIN);
565 #endif /* DRAWLINES */
567 if ( loop->cstep < xMaxLines ) {
568 /* we can send each color segment in a single request
569 * because the max request length is long enough
570 * and because we have padded out the array to have extra elements
571 * to support calls which would otherwise fall off the end*/
572 for (pctr = 0; pctr < loop->nsteps; pctr+=loop->cstep) {
576 #if defined DRAWLINES
577 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi),
578 MI_GC(mi), &lp[pctr], loop->cstep, CoordModeOrigin );
579 #else /* DRAWLINES */
580 XDrawPoints(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
581 &lp[pctr], loop->cstep, CoordModeOrigin );
582 #endif /* DRAWLINES */
584 } else { /* do it one by one as before */
585 for (pctr = 0; pctr < loop->nsteps; pctr++ ) {
588 #if defined DRAWLINES
589 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
590 lp[pctr].x, lp[pctr].y,
591 lp[pctr+1 % loop->nsteps].x, lp[pctr+1 % loop->nsteps].y);
592 #else /* DRAWLINES */
593 XDrawPoint(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
594 lp[pctr].x, lp[pctr].y);
595 #endif /* DRAWLINES */
599 #if defined DRAWLINES
600 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 1,
601 LINESTYLE, LINECAP, LINEJOIN);
602 #endif /* DRAWLINES */
607 refreshlisa(ModeInfo * mi)
609 lisacons *lc = &Lisa[MI_SCREEN(mi)];
612 for (lctr = 0; lctr < lc->nlissajous; lctr++) {
613 if (!drawlisa(mi, &lc->lissajous[lctr]))
619 refresh_lisa(ModeInfo * mi)
625 lc = &Lisa[MI_SCREEN(mi)];
626 if (lc->lissajous == NULL)
637 change_lisa(ModeInfo * mi)
645 lc = &Lisa[MI_SCREEN(mi)];
646 if (lc->lissajous == NULL)
650 for (lctr = 0; lctr < lc->nlissajous; lctr++) {
651 loop = &lc->lissajous[lctr]; /* count through the loops we're drawing */
652 newfunc = NRAND(NUMSTDFUNCS); /* choose a new function at random */
653 #if defined FOLLOW_FUNC_ORDER
655 &Function[(loop->function[0]->index + 1) % NUMSTDFUNCS];
656 #else /* FOLLOW_FUNC_ORDER */
657 if (newfunc == loop->function[0]->index) {
659 newfunc %= NUMSTDFUNCS; /* take the next if we got the one we have */
661 if (newfunc >= RAREFUNCMIN \
662 && !(random() % RAREFUNCODDS) \
663 && (newfunc = NRAND(NUMSTDFUNCS)) == loop->function[0]->index) {
665 newfunc %= NUMSTDFUNCS;
667 loop->function[1] = /* set 2nd function pointer on the loop */
668 &Function[newfunc]; /* to the new function we just chose */
669 #endif /* FOLLOW_FUNC_ORDER */
671 PRINT_FUNC(loop->function[1]);
673 loop->melting = loop->nsteps - 1; /* melt the two functions together */
674 loop->nfuncs = 2; /* simultaneously for a full cycle */
679 init_lisa (ModeInfo * mi)
685 if ((Lisa = (lisacons *) calloc(MI_NUM_SCREENS(mi),
686 sizeof (lisacons))) == NULL)
689 lc = &Lisa[MI_SCREEN(mi)];
690 lc->width = MI_WIDTH(mi);
691 lc->height = MI_HEIGHT(mi);
693 lc->nlissajous = MI_COUNT(mi);
694 if (lc->nlissajous <= 0)
699 if (lc->lissajous == NULL) {
700 if ((lc->lissajous = (lisas *) calloc(lc->nlissajous,
701 sizeof (lisas))) == NULL)
703 for (lctr = 0; lctr < lc->nlissajous; lctr++) {
704 if (!initlisa(mi, &lc->lissajous[lctr]))
714 draw_lisa (ModeInfo * mi)
720 lc = &Lisa[MI_SCREEN(mi)];
721 if (lc->lissajous == NULL)
724 #ifdef HAVE_COCOA /* Don't second-guess Quartz's double-buffering */
725 XClearWindow (MI_DISPLAY(mi), MI_WINDOW(mi));
728 MI_IS_DRAWN(mi) = True;
730 if (++lc->loopcount > lc->maxcycles) {
737 release_lisa (ModeInfo * mi)
742 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
743 free_lisa(&Lisa[screen]);
745 Lisa = (lisacons *) NULL;
749 XSCREENSAVER_MODULE ("Lisa", lisa)
751 #endif /* MODE_lisa */