1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* lisa --- animated full-loop lisajous 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 lisajous
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 # define UNIFORM_COLORS
61 # define reshape_lisa 0
62 # define lisa_handle_event 0
63 # include "xlockmore.h" /* in xscreensaver distribution */
64 #else /* STANDALONE */
65 #include "xlock.h" /* in xlockmore distribution */
67 #endif /* STANDALONE */
71 #define DEF_ADDITIVE "True"
75 static XrmOptionDescRec opts[] =
77 {"-additive", ".lisa.additive", XrmoptionNoArg, "True"},
78 {"+additive", ".lisa.additive", XrmoptionNoArg, "False"}
81 static argtype vars[] =
83 {&additive, "additive", "Additive", DEF_ADDITIVE, t_Bool}
86 static OptionStruct desc[] =
88 {"-/+additive", "turn on/off additive functions mode"}
91 ENTRYPOINT ModeSpecOpt lisa_opts =
92 {sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};
95 ModStruct lisa_description =
96 {"lisa", "init_lisa", "draw_lisa", "release_lisa",
97 "refresh_lisa", "change_lisa", (char *) NULL, &lisa_opts,
98 17000, 1, 768, -1, 64, 1.0, "",
99 "Shows animated lisajous figures", 0, NULL};
104 /* #define FOLLOW_FUNC_ORDER 1 */
106 #define XVMAX 10 /* Maximum velocities */
108 #define LISAMAXFUNCS 2
109 #define NUMSTDFUNCS 28
110 #define RAREFUNCMIN 25
111 #define RAREFUNCODDS 4 /* 1:n chance a rare function will be re-randomized */
115 #define STARTFUNC 24 /* if negative, is upper-bound on randomization */
116 #define LINEWIDTH -8 /* if negative, is upper-bound on randomization */
117 #define LINESTYLE LineSolid /* an insane man might have fun with this :) */
118 #define LINECAP CapNotLast /* anything else looks pretty crappy */
119 #define LINEJOIN JoinBevel /* this ought to be fastest */
120 #define SET_COLOR() \
121 if (MI_NPIXELS(mi) > 2) { \
122 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_PIXEL(mi, loop->color)); \
124 && pctr % loop->cstep == 0 \
125 && ++(loop->color) >= (unsigned) MI_NPIXELS(mi)) \
126 { loop->color=STARTCOLOR; } \
127 } else { XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi)); }
128 #define GET_RADIUS(context) \
129 ((context->width > context->height)?context->height:context->width) * 3 / 8
130 #define CHECK_RADIUS(loop, context) \
131 if ((context->height / 2 > MI_SIZE(mi)) && (context->width / 2 > MI_SIZE(mi))) \
132 loop->radius = MI_SIZE(mi); \
133 if ((loop->radius < 0) || \
134 (loop->radius > loop->center.x) || \
135 (loop->radius > loop->center.y)) loop->radius = GET_RADIUS(context)
136 #define PRINT_FUNC(funcptr) \
137 printf("new function -- #%d:\n\tx = sin(%gs) * sin(%gs)\n\ty = sin(%gt) * sin(%gt)\n", \
139 funcptr->xcoeff[0], funcptr->xcoeff[1], \
140 funcptr->ycoeff[0], funcptr->ycoeff[1])
143 typedef struct lisafunc_struct {
144 double xcoeff[2], ycoeff[2];
149 typedef struct lisa_struct {
151 int radius, dx, dy, nsteps, nfuncs, melting, cstep;
152 double pistep, phi, theta;
153 XPoint center, *lastpoint;
154 lisafuncs *function[LISAMAXFUNCS];
158 typedef struct lisacontext_struct {
160 int width, height, nlisajous, loopcount;
165 static lisacons *Lisa = (lisacons *) NULL;
167 static lisafuncs Function[NUMSTDFUNCS] =
171 {1.0, 2.0}, 2, 2, 0},
174 {1.0, 1.0}, 2, 2, 1},
177 {1.0, 2.0}, 2, 2, 2},
180 {1.0, 3.0}, 2, 2, 3},
183 {1.0, 2.0}, 2, 2, 4},
186 {1.0, 3.0}, 2, 2, 5},
189 {1.0, 4.0}, 2, 2, 6},
192 {1.0, 5.0}, 2, 2, 7},
195 {2.0, 5.0}, 2, 2, 8},
198 {2.0, 5.0}, 2, 2, 9},
201 {3.0, 5.0}, 2, 2, 10},
204 {2.0, 3.0}, 2, 2, 11},
207 {2.0, 3.0}, 2, 2, 12},
210 {1.0, 3.0}, 2, 2, 13},
213 {1.0, 3.0}, 2, 2, 14},
216 {2.0, 3.0}, 2, 2, 15},
219 {2.0, 3.0}, 2, 2, 16},
222 {2.0, 3.0}, 2, 2, 17},
225 {2.0, 3.0}, 2, 2, 18},
228 {2.0, 5.0}, 2, 2, 19},
231 {2.0, 7.0}, 2, 2, 20},
234 {5.0, 7.0}, 2, 2, 21},
237 {3.0, 7.0}, 2, 2, 22},
240 {5.0, 7.0}, 2, 2, 23},
243 {5.0, 7.0}, 2, 2, 24},
244 { /* functions past here are 'rare' and won't */
245 {2.0, 7.0}, /* show up as often. tweak the #defines above */
246 {1.0, 7.0}, 2, 2, 25}, /* to see them more frequently */
249 {1.0, 7.0}, 2, 2, 26},
252 {2.0, 9.0}, 2, 2, 27}
258 free_lisa(lisacons *lc)
260 while (lc->lisajous) {
263 for (lctr = 0; lctr < lc->nlisajous; lctr++) {
264 (void) free((void *) lc->lisajous[lctr].lastpoint);
266 (void) free((void *) lc->lisajous);
267 lc->lisajous = (lisas *) NULL;
272 drawlisa(ModeInfo * mi, lisas * loop)
275 XPoint *lp = loop->lastpoint;
276 lisacons *lc = &Lisa[MI_SCREEN(mi)];
277 lisafuncs **lf = loop->function;
278 int phase = lc->loopcount % loop->nsteps;
279 int pctr, fctr, xctr, yctr, extra_points;
280 double xprod, yprod, xsum, ysum;
282 /* why carry this around in the struct when we can calculate it on demand? */
283 extra_points = loop->cstep - (loop->nsteps % loop->cstep);
285 /* Allocate the np (new point) array (with padding) */
286 if ((np = (XPoint *) calloc(loop->nsteps+extra_points, sizeof (XPoint))) == NULL) {
291 /* Update the center */
292 loop->center.x += loop->dx;
293 loop->center.y += loop->dy;
294 CHECK_RADIUS(loop, lc);
296 /* check for overlaps -- where the figure might go off the screen */
298 if ((loop->center.x - loop->radius) <= 0) {
299 loop->center.x = loop->radius;
300 loop->dx = NRAND(XVMAX);
301 } else if ((loop->center.x + loop->radius) >= lc->width) {
302 loop->center.x = lc->width - loop->radius;
303 loop->dx = -NRAND(XVMAX);
305 if ((loop->center.y - loop->radius) <= 0) {
306 loop->center.y = loop->radius;
307 loop->dy = NRAND(YVMAX);
308 } else if ((loop->center.y + loop->radius) >= lc->height) {
309 loop->center.y = lc->height - loop->radius;
310 loop->dy = -NRAND(YVMAX);
313 /* Now draw the points, and erase the ones from the last cycle */
315 for (pctr = 0; pctr < loop->nsteps; pctr++) {
317 loop->phi = (double) (pctr - phase) * loop->pistep;
318 loop->theta = (double) (pctr + phase) * loop->pistep;
326 xprod += sin(lf[fctr]->xcoeff[xctr] * loop->theta);
328 yprod += sin(lf[fctr]->ycoeff[yctr] * loop->phi);
331 xsum += xprod * (double) (loop->nsteps - loop->melting) /
332 (double) loop->nsteps;
333 ysum += yprod * (double) (loop->nsteps - loop->melting) /
334 (double) loop->nsteps;
336 xsum += xprod * (double) loop->melting / (double) loop->nsteps;
337 ysum += yprod * (double) loop->melting / (double) loop->nsteps;
344 xsum = xsum * (double) loop->radius / (double) lf[fctr]->nx;
345 ysum = ysum * (double) loop->radius / (double) lf[fctr]->ny;
350 yprod = xprod = (double) loop->radius *
351 (double) (loop->nsteps - loop->melting) /
352 (double) (loop->nsteps);
354 yprod = xprod = (double) loop->radius *
355 (double) (loop->melting) / (double) (loop->nsteps);
358 xprod = yprod = (double) loop->radius;
361 xprod *= sin(lf[fctr]->xcoeff[xctr] * loop->theta);
363 yprod *= sin(lf[fctr]->ycoeff[yctr] * loop->phi);
368 if ((loop->nfuncs > 1) && (!loop->melting)) {
369 xsum /= (double) loop->nfuncs;
370 ysum /= (double) loop->nfuncs;
372 xsum += (double) loop->center.x;
373 ysum += (double) loop->center.y;
375 np[pctr].x = (int) ceil(xsum);
376 np[pctr].y = (int) ceil(ysum);
378 /* fill in extra points */
379 for (pctr=loop->nsteps; pctr < loop->nsteps+extra_points; pctr++) {
380 np[pctr].x = np[pctr - loop->nsteps].x;
381 np[pctr].y = np[pctr - loop->nsteps].y;
384 if (!--loop->melting) {
386 loop->function[0] = loop->function[1];
390 /* reset starting color each time to prevent ass-like appearance */
391 loop->color = STARTCOLOR;
393 if (loop->cstep < xMaxLines) {
394 /* printf("Drawing dashes\n"); */
395 for (pctr = 0; pctr < loop->nsteps; pctr+=loop->cstep) {
396 #if defined DRAWLINES
397 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), loop->linewidth,
398 LINESTYLE, LINECAP, LINEJOIN);
399 /* erase the last cycle's point */
400 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
401 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), &lp[pctr],
402 loop->cstep, CoordModeOrigin);
404 /* Set the new color */
407 /* plot this cycle's point */
408 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
409 &np[pctr], loop->cstep, CoordModeOrigin);
410 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 1,
411 LINESTYLE, LINECAP, LINEJOIN);
413 /* erase the last cycle's point */
414 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
415 XDrawPoints(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
416 &lp[pctr], loop->cstep, CoordModeOrigin);
418 /* Set the new color */
421 /* plot this cycle's point */
422 XDrawPoints(MI_DISPLAY(mi), MI_WINDOW(mi),
423 MI_GC(mi), &np[pctr], loop->cstep, CoordModeOrigin);
426 } else { /* on my system, cstep is larger than 65532/2 if we get here */
427 for (pctr = 0; pctr < loop->nsteps; pctr++) {
428 #if defined DRAWLINES
429 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), loop->linewidth,
430 LINESTYLE, LINECAP, LINEJOIN);
431 /* erase the last cycle's point */
432 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
433 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi),
434 MI_GC(mi), lp[pctr].x, lp[pctr].y,
435 lp[(pctr + 1) % loop->nsteps].x,
436 lp[(pctr + 1) % loop->nsteps].y);
438 /* Set the new color */
441 /* plot this cycle's point */
442 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi),
443 MI_GC(mi), np[pctr].x, np[pctr].y,
444 np[(pctr + 1) % loop->nsteps].x,
445 np[(pctr + 1) % loop->nsteps].y);
446 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 1,
447 LINESTYLE, LINECAP, LINEJOIN);
448 #else /* DRAWLINES */
449 /* erase the last cycle's point */
450 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
451 XDrawPoint(MI_DISPLAY(mi), MI_WINDOW(mi),
452 MI_GC(mi), lp[pctr].x, lp[pctr].y);
454 /* Set the new color */
457 /* plot this cycle's point */
458 XDrawPoint(MI_DISPLAY(mi), MI_WINDOW(mi),
459 MI_GC(mi), np[pctr].x, np[pctr].y);
460 #endif /* DRAWLINES */
463 (void) free((void *) lp);
464 loop->lastpoint = np;
469 initlisa(ModeInfo * mi, lisas * loop)
471 lisacons *lc = &Lisa[MI_SCREEN(mi)];
472 lisafuncs **lf = loop->function;
474 int phase, pctr, fctr, xctr, yctr, extra_points;
475 double xprod, yprod, xsum, ysum;
477 xMaxLines = (XMaxRequestSize(MI_DISPLAY(mi))-3)/2;
478 /* printf("Got xMaxLines = %d\n", xMaxLines); */
479 loop->nsteps = MI_CYCLES(mi);
480 if (loop->nsteps == 0)
482 if (MI_NPIXELS(mi) > 2) {
483 loop->color = STARTCOLOR;
484 loop->cstep = (loop->nsteps > MI_NPIXELS(mi)) ? loop->nsteps / MI_NPIXELS(mi) : 1;
486 loop->color = MI_WHITE_PIXEL(mi);
489 extra_points = loop->cstep - (loop->nsteps % loop->cstep);
490 lc->maxcycles = (MAXCYCLES * loop->nsteps) - 1;
491 loop->cstep = ( loop->nsteps > MI_NPIXELS(mi) ) ? loop->nsteps / MI_NPIXELS(mi) : 1;
492 /* printf("Got cstep = %d\n", loop->cstep); */
495 loop->pistep = 2.0 * M_PI / (double) loop->nsteps;
496 loop->center.x = lc->width / 2;
497 loop->center.y = lc->height / 2;
498 loop->radius = (int) MI_SIZE(mi);
499 CHECK_RADIUS(loop, lc);
500 loop->dx = NRAND(XVMAX);
501 loop->dy = NRAND(YVMAX);
504 #if defined STARTFUNC
505 lf[0] = &Function[STARTFUNC];
506 #else /* STARTFUNC */
507 lf[0] = &Function[NRAND(NUMSTDFUNCS)];
508 #endif /* STARTFUNC */
510 if ((lp = loop->lastpoint = (XPoint *)
511 calloc(loop->nsteps+extra_points, sizeof (XPoint))) == NULL) {
515 phase = lc->loopcount % loop->nsteps;
518 printf( "nsteps = %d\tcstep = %d\tmrs = %d\textra_points = %d\n",
519 loop->nsteps, loop->cstep, xMaxLines, extra_points );
523 for (pctr = 0; pctr < loop->nsteps; pctr++) {
524 loop->phi = (double) (pctr - phase) * loop->pistep;
525 loop->theta = (double) (pctr + phase) * loop->pistep;
529 xprod = yprod = (double) loop->radius;
533 xprod *= sin(lf[fctr]->xcoeff[xctr] * loop->theta);
535 yprod *= sin(lf[fctr]->ycoeff[yctr] * loop->phi);
539 if (loop->nfuncs > 1) {
543 xsum += (double) loop->center.x;
544 ysum += (double) loop->center.y;
546 lp[pctr].x = (int) ceil(xsum);
547 lp[pctr].y = (int) ceil(ysum);
549 /* this fills in the extra points, so we can use segment-drawing calls */
550 for (pctr = loop->nsteps; pctr < loop->nsteps + extra_points; pctr++) {
551 lp[pctr].x=lp[pctr - loop->nsteps].x;
552 lp[pctr].y=lp[pctr - loop->nsteps].y;
554 #if defined DRAWLINES
555 loop->linewidth = LINEWIDTH; /* #### make this a resource */
557 if (loop->linewidth == 0)
559 if (loop->linewidth < 0)
560 loop->linewidth = NRAND(-loop->linewidth) + 1;
561 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), loop->linewidth,
562 LINESTYLE, LINECAP, LINEJOIN);
563 #endif /* DRAWLINES */
565 if ( loop->cstep < xMaxLines ) {
566 /* we can send each color segment in a single request
567 * because the max request length is long enough
568 * and because we have padded out the array to have extra elements
569 * to support calls which would otherwise fall off the end*/
570 for (pctr = 0; pctr < loop->nsteps; pctr+=loop->cstep) {
574 #if defined DRAWLINES
575 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi),
576 MI_GC(mi), &lp[pctr], loop->cstep, CoordModeOrigin );
577 #else /* DRAWLINES */
578 XDrawPoints(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
579 &lp[pctr], loop->cstep, CoordModeOrigin );
580 #endif /* DRAWLINES */
582 } else { /* do it one by one as before */
583 for (pctr = 0; pctr < loop->nsteps; pctr++ ) {
586 #if defined DRAWLINES
587 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
588 lp[pctr].x, lp[pctr].y,
589 lp[pctr+1 % loop->nsteps].x, lp[pctr+1 % loop->nsteps].y);
590 #else /* DRAWLINES */
591 XDrawPoint(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
592 lp[pctr].x, lp[pctr].y);
593 #endif /* DRAWLINES */
597 #if defined DRAWLINES
598 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 1,
599 LINESTYLE, LINECAP, LINEJOIN);
600 #endif /* DRAWLINES */
605 refreshlisa(ModeInfo * mi)
607 lisacons *lc = &Lisa[MI_SCREEN(mi)];
610 for (lctr = 0; lctr < lc->nlisajous; lctr++) {
611 if (!drawlisa(mi, &lc->lisajous[lctr]))
617 refresh_lisa(ModeInfo * mi)
623 lc = &Lisa[MI_SCREEN(mi)];
624 if (lc->lisajous == NULL)
635 change_lisa(ModeInfo * mi)
643 lc = &Lisa[MI_SCREEN(mi)];
644 if (lc->lisajous == NULL)
648 for (lctr = 0; lctr < lc->nlisajous; lctr++) {
649 loop = &lc->lisajous[lctr]; /* count through the loops we're drawing */
650 newfunc = NRAND(NUMSTDFUNCS); /* choose a new function at random */
651 #if defined FOLLOW_FUNC_ORDER
653 &Function[(loop->function[0]->index + 1) % NUMSTDFUNCS];
654 #else /* FOLLOW_FUNC_ORDER */
655 if (newfunc == loop->function[0]->index) {
657 newfunc %= NUMSTDFUNCS; /* take the next if we got the one we have */
659 if (newfunc >= RAREFUNCMIN \
660 && !(random() % RAREFUNCODDS) \
661 && (newfunc = NRAND(NUMSTDFUNCS)) == loop->function[0]->index) {
663 newfunc %= NUMSTDFUNCS;
665 loop->function[1] = /* set 2nd function pointer on the loop */
666 &Function[newfunc]; /* to the new function we just chose */
667 #endif /* FOLLOW_FUNC_ORDER */
669 PRINT_FUNC(loop->function[1]);
671 loop->melting = loop->nsteps - 1; /* melt the two functions together */
672 loop->nfuncs = 2; /* simultaneously for a full cycle */
677 init_lisa (ModeInfo * mi)
683 if ((Lisa = (lisacons *) calloc(MI_NUM_SCREENS(mi),
684 sizeof (lisacons))) == NULL)
687 lc = &Lisa[MI_SCREEN(mi)];
688 lc->width = MI_WIDTH(mi);
689 lc->height = MI_HEIGHT(mi);
691 lc->nlisajous = MI_COUNT(mi);
692 if (lc->nlisajous <= 0)
697 if (lc->lisajous == NULL) {
698 if ((lc->lisajous = (lisas *) calloc(lc->nlisajous,
699 sizeof (lisas))) == NULL)
701 for (lctr = 0; lctr < lc->nlisajous; lctr++) {
702 if (!initlisa(mi, &lc->lisajous[lctr]))
712 draw_lisa (ModeInfo * mi)
718 lc = &Lisa[MI_SCREEN(mi)];
719 if (lc->lisajous == NULL)
722 #ifdef HAVE_COCOA /* Don't second-guess Quartz's double-buffering */
723 XClearWindow (MI_DISPLAY(mi), MI_WINDOW(mi));
726 MI_IS_DRAWN(mi) = True;
728 if (++lc->loopcount > lc->maxcycles) {
735 release_lisa (ModeInfo * mi)
740 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
741 free_lisa(&Lisa[screen]);
743 Lisa = (lisacons *) NULL;
747 XSCREENSAVER_MODULE ("Lisa", lisa)
749 #endif /* MODE_lisa */