2 * lockward.c: First attempt at an Xscreensaver.
4 * Leo L. Schwab 2007.08.17
6 * Copyright (c) 2007 Leo L. Schwab
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to permit
13 * persons to whom the Software is furnished to do so, subject to the
14 * following conditions:
16 * The above copyright notice and this permission notice shall be included
17 * in all copies or substantial portions of the Software.
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
22 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
23 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
24 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
25 * USE OR OTHER DEALINGS IN THE SOFTWARE.
30 #include "xlockmore.h"
34 /***************************************************************************
37 #ifdef USE_GL /* whole file */
39 #define DEFAULTS "*delay: 20000 \n"\
42 #define refresh_lockward 0
45 #define NUMOF(x) (sizeof ((x)) / sizeof ((*x)))
50 #define COLORIDX_SHF 4
53 /***************************************************************************
54 * Structure definitions.
56 struct lockward_context; /* Forward declaration. */
61 #define uint8_t unsigned char
62 #define uint16_t unsigned short
63 #define uint32_t unsigned int
65 typedef struct bladestate {
66 uint8_t outer, inner; /* Radii */
69 typedef struct spinnerstate {
70 GLfloat rot; /* Terminal rotation after count expires */
71 GLfloat rotinc; /* Per-frame increment to rot. */
74 int ncolors; /* n.4 fixed-point */
75 int ccolor; /* n.4 fixed-point */
76 int colorinc; /* n.4 fixed-point */
81 typedef struct blinkstate {
82 int (*drawfunc) (struct lockward_context *ctx,
83 struct blinkstate *bs);
84 uint32_t *noise; /* For draw_blink_segment_scatter() */
87 int16_t dwell; /* <0: sharp >0: decay */
96 BTYPE_RADIAL_SINGLE = 0,
99 BTYPE_RADIAL_DOUBLESEQ,
100 BTYPE_SEGMENT_SINGLE,
101 BTYPE_SEGMENT_RANDOM,
102 BTYPE_CONCENTRIC_SINGLE,
103 BTYPE_CONCENTRIC_RANDOM,
104 BTYPE_CONCENTRIC_SEQ,
105 BTYPE_SEGMENT_SCATTER,
109 typedef struct lockward_context {
110 GLXContext *glx_context;
112 spinnerstate spinners[NSPINNERS];
115 GLuint blades_outer, blades_inner;
123 /***************************************************************************
126 static void free_lockward (lockward_context *ctx);
129 /***************************************************************************
132 static lockward_context *g_ctx = NULL;
133 static Bool g_blink_p = True;
134 static int g_blades = NBLADES;
135 static int g_rotateidle_min,
137 static int g_blinkidle_min,
139 static int g_blinkdwell_min,
142 static XrmOptionDescRec opts[] = {
143 { "-blink", ".blink", XrmoptionNoArg, "on" },
144 { "+blink", ".blink", XrmoptionNoArg, "off" },
145 { "-rotateidle-min", ".rotateidle-min", XrmoptionSepArg, 0 },
146 { "-rotateidle-max", ".rotateidle-max", XrmoptionSepArg, 0 },
147 { "-blinkidle-min", ".blinkidle-min", XrmoptionSepArg, 0 },
148 { "-blinkidle-max", ".blinkidle-max", XrmoptionSepArg, 0 },
149 { "-blinkdwell-min", ".blinkdwell-min", XrmoptionSepArg, 0 },
150 { "-blinkdwell-max", ".blinkdwell-max", XrmoptionSepArg, 0 },
153 static argtype vars[] = {
154 { &g_blink_p, "blink", "Blink", "True", t_Bool },
155 { &g_rotateidle_min, "rotateidle-min", "Rotateidle-min", "1000", t_Int },
156 { &g_rotateidle_max, "rotateidle-max", "Rotateidle-max", "6000", t_Int },
157 { &g_blinkidle_min, "blinkidle-min", "Blinkidle-min", "1000", t_Int },
158 { &g_blinkidle_max, "blinkidle-max", "Blinkidle-max", "9000", t_Int },
159 { &g_blinkdwell_min, "blinkdwell-min", "Blinkdwell-min", "100", t_Int },
160 { &g_blinkdwell_max, "blinkdwell-max", "Blinkdwell-max", "600", t_Int },
163 static OptionStruct desc[] = {
164 { "-/+blink", "Turn on/off blinking effects." },
165 { "-rotateidle-min", "Minimum idle time for rotators, in milliseconds." },
166 { "-rotateidle-max", "Maximum idle time for rotators, in milliseconds." },
167 { "-blinkidle-min", "Minimum idle time between blink effects, in milliseconds." },
168 { "-blinkidle-max", "Maximum idle time between blink effects, in milliseconds." },
169 { "-blinkdwell-min", "Minimum dwell time for blink effects, in milliseconds." },
170 { "-blinkdwell-max", "Maximum dwell time for blink effects, in milliseconds." },
173 ENTRYPOINT ModeSpecOpt lockward_opts = {
174 NUMOF(opts), opts, NUMOF(vars), vars, desc
178 /***************************************************************************
182 reshape_lockward (ModeInfo *mi, int width, int height)
184 GLfloat h = (GLfloat) height / (GLfloat) width;
186 glViewport (0, 0, (GLint) width, (GLint) height);
188 glMatrixMode (GL_PROJECTION);
191 gluOrtho2D (-8.0, 8.0, -8.0 * h, 8.0 * h);
193 gluOrtho2D (-8.0 / h, 8.0 / h, -8.0, 8.0);
195 glMatrixMode (GL_MODELVIEW);
199 lockward_handle_event (ModeInfo *mi, XEvent *event)
201 lockward_context *ctx = &g_ctx[MI_SCREEN (mi)];
203 if (event->xany.type == KeyPress) {
207 XLookupString (&event->xkey, &c, 1, &keysym, 0);
218 /***************************************************************************
222 random_blade_rot (lockward_context *ctx, struct spinnerstate *ss)
225 * The circle is divided up in to g_blades divisions. The idea here
226 * is to rotate to an exact division point.
228 * The target rotation is computed via random numbers.
230 * The time it takes to get there is a maximum of six seconds per
231 * division, and a minimum of one second (no matter how far away it
232 * is), and is selected by random numbers.
234 * The time value is converted into frames, and a per-frame rotation
237 * During rendering, we approach the target rotation by subtracting
238 * from it the per-frame rotation times the number of outstanding
239 * ticks. Doing it this way means we'll hit the target rotation
240 * exactly, without low-order errors creeping in to the values (at
241 * least not nearly as quickly).
246 dist = random() % g_blades + 1;
248 ss->rotcount = random() % (6 * dist * ctx->fps - ctx->fps)
253 d = dist * 360.0 / (GLfloat) g_blades;
255 ss->rotinc = d / (GLfloat) ss->rotcount;
260 * A "blade" is pie-wedge shaped flat thing that is rotated around where the
261 * apex is/would be. Initially envisioned as 1/12th of a circle, but that
262 * could be configurable. The inner and outer edges are rounded off using
263 * six subdivisions so that, when multiple blades are assembled, it looks
264 * more like a circle and less like a polygon.
266 * The blade is assembled as a tri-fan. It is oriented centered at 3
267 * o'clock. The blade is composed of two display lists -- arcs, essentially
268 * -- the outer and the inner one. The outer one *must* be called before
269 * the inner one, or the blade clockwise-ness will be wrong, and become
270 * invisible. Arcs of various radii are compiled.
275 gen_blade_arcs (lockward_context *ctx)
277 GLfloat here, there, step;
281 there = M_PI * 2.0 / g_blades;
282 step = there / SUBDIV;
283 here -= SUBDIV * step / 2.0;
284 there -= SUBDIV * step / 2.0;
287 * Build outer blade arcs.
288 * Start at left side of outer radius. Strike all its vertices.
290 for (n = 0; n < NRADII; ++n) {
291 glNewList (ctx->blades_outer + n, GL_COMPILE);
292 for (i = SUBDIV; i >= 0; --i)
293 glVertex3f (cos (here + step * i) * (n + 1.0),
294 sin (here + step * i) * (n + 1.0), 0);
299 * Build inner blade arcs.
300 * Move to inner radius, strike all vertices in opposite order.
302 for (n = 0; n < NRADII; ++n) {
303 glNewList (ctx->blades_inner + n, GL_COMPILE);
304 for (i = 0; i <= SUBDIV; ++i)
305 glVertex3f (cos (here + step * i) * (n + 1.0),
306 sin (here + step * i) * (n + 1.0), 0);
312 gen_rings (lockward_context *ctx)
317 step = M_PI * 2.0 / (g_blades * SUBDIV);
319 for (n = 0; n < NRADII - 1; ++n) {
320 glNewList (ctx->rings + n, GL_COMPILE);
321 glBegin (GL_TRIANGLE_STRIP);
322 for (i = g_blades * SUBDIV; i >= 0; --i) {
323 glVertex3f (cos (step * i) * (n + 1.0),
324 sin (step * i) * (n + 1.0), 0);
325 glVertex3f (cos (step * i) * (n + 2.0),
326 sin (step * i) * (n + 2.0), 0);
334 /***************************************************************************
338 calc_interval_frames (lockward_context *ctx, int min, int max)
341 * Compute random interval between min and max milliseconds.
342 * Returned value is in frames.
348 i += random() % (max - min);
350 return i * ctx->fps / 1000;
354 set_alpha_by_dwell (struct blinkstate *bs)
357 bs->color[3] = (GLfloat) bs->dwellcnt / (GLfloat) bs->dwell;
359 bs->color[3] = bs->dwellcnt > (-bs->dwell >> 2) ? 1.0 : 0.0;
363 draw_blink_blade (lockward_context *ctx, int inner, int outer)
365 glBegin (GL_TRIANGLE_FAN);
366 glCallList (ctx->blades_outer + outer);
367 glCallList (ctx->blades_inner + inner);
372 draw_blink_radial_random (lockward_context *ctx, struct blinkstate *bs)
377 * There is no sense of direction in a random sweep, so re-use the
378 * 'direction' field to hold the current blade we're messing with.
380 if (bs->dwellcnt < 0) {
381 if (bs->counter <= 0) {
387 * Find available blade. Potentially very slow, depending on
388 * how unlucky we are.
391 i = random() % g_blades;
392 } while (bs->val & (1 << i));
393 bs->val |= (1 << i); /* Mark as used. */
395 if ((bs->dwellcnt = bs->dwell) < 0)
396 bs->dwellcnt = -bs->dwellcnt;
398 if ( bs->type == BTYPE_SEGMENT_SINGLE
399 || bs->type == BTYPE_SEGMENT_RANDOM)
400 bs->radius = random() % (NRADII - 1);
405 set_alpha_by_dwell (bs);
406 glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
407 glColor4fv (bs->color);
408 glRotatef (bs->direction * 360.0 / (GLfloat) g_blades, 0, 0, 1);
410 draw_blink_blade (ctx, bs->radius, bs->radius + 1);
412 draw_blink_blade (ctx, 0, NRADII - 1);
416 return SUBDIV + SUBDIV;
420 draw_blink_radial_sequential (lockward_context *ctx, struct blinkstate *bs)
422 if (bs->dwellcnt < 0) {
423 if (bs->counter <= 0) {
427 if ((bs->dwellcnt = bs->dwell) < 0)
428 bs->dwellcnt = -bs->dwellcnt;
432 set_alpha_by_dwell (bs);
433 glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
434 glColor4fv (bs->color);
435 glRotatef ((bs->counter * bs->direction + (int) bs->val)
436 * 360.0 / (GLfloat) g_blades,
438 draw_blink_blade (ctx, 0, NRADII - 1);
442 return SUBDIV + SUBDIV;
446 draw_blink_radial_doubleseq (lockward_context *ctx, struct blinkstate *bs)
450 if (bs->dwellcnt < 0) {
451 if (bs->counter <= 0) {
455 if ((bs->dwellcnt = bs->dwell) < 0)
456 bs->dwellcnt = -bs->dwellcnt;
460 set_alpha_by_dwell (bs);
461 glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
462 glColor4fv (bs->color);
465 glRotatef (((int) bs->val + bs->counter) * 360.0 / (GLfloat) g_blades,
467 draw_blink_blade (ctx, 0, NRADII - 1);
469 polys = SUBDIV + SUBDIV;
471 if (bs->counter && bs->counter < g_blades / 2) {
472 glRotatef (((int) bs->val - bs->counter)
473 * 360.0 / (GLfloat) g_blades,
475 draw_blink_blade (ctx, 0, NRADII - 1);
476 polys += SUBDIV + SUBDIV;
485 draw_blink_concentric_random (lockward_context *ctx, struct blinkstate *bs)
489 if (bs->dwellcnt < 0) {
490 if (bs->counter <= 0) {
496 i = random() % (NRADII - 1);
497 } while (bs->val & (1 << i));
500 if ((bs->dwellcnt = bs->dwell) < 0)
501 bs->dwellcnt = -bs->dwellcnt;
506 set_alpha_by_dwell (bs);
507 glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
508 glColor4fv (bs->color);
509 glCallList (ctx->rings + bs->direction);
513 return g_blades * SUBDIV * 2;
517 draw_blink_concentric_sequential (lockward_context *ctx, struct blinkstate *bs)
519 if (bs->dwellcnt < 0) {
520 if (bs->counter <= 0) {
524 if ((bs->dwellcnt = bs->dwell) < 0)
525 bs->dwellcnt = -bs->dwellcnt;
529 set_alpha_by_dwell (bs);
530 glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
531 glColor4fv (bs->color);
532 if (bs->direction > 0)
533 glCallList (ctx->rings + (NRADII - 2) - bs->counter);
535 glCallList (ctx->rings + bs->counter);
539 return g_blades * SUBDIV * 2;
543 draw_blink_segment_scatter (lockward_context *ctx, struct blinkstate *bs)
547 if (bs->dwellcnt < 0) {
548 if (bs->counter <= 0) {
554 * Init random noise array. On average, 1/4 of the bits will
555 * be set, which should look nice. (1/2 looks too busy.)
557 for (i = g_blades; --i >= 0; )
558 bs->noise[i] = random() & random()
559 & ((1 << (NRADII - 1)) - 1);
561 if ((bs->dwellcnt = bs->dwell) < 0)
562 bs->dwellcnt = -bs->dwellcnt;
566 set_alpha_by_dwell (bs);
567 glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
568 glColor4fv (bs->color);
570 for (i = g_blades; --i >= 0; ) {
571 register uint32_t bits;
575 * Find consecutive runs of 1 bits. Keep going until we run
578 for (bits = bs->noise[i]; bits; ) {
579 inner = ffs (bits) - 1;
580 bits = ~bits & ~((1 << inner) - 1);
581 outer = ffs (bits) - 1;
582 bits = ~bits & ~((1 << outer) - 1);
585 glRotatef (i * 360.0 / (GLfloat) g_blades, 0, 0, 1);
586 draw_blink_blade (ctx, inner, outer);
589 polys += SUBDIV + SUBDIV;
599 random_blink (lockward_context *ctx, struct blinkstate *bs)
607 bs->dwell = calc_interval_frames
608 (ctx, g_blinkdwell_min, g_blinkdwell_max);
610 bs->dwell = -bs->dwell;
612 bs->type = random() % MAX_BTYPE;
615 case BTYPE_RADIAL_SINGLE:
616 case BTYPE_SEGMENT_SINGLE:
617 bs->drawfunc = draw_blink_radial_random;
621 case BTYPE_RADIAL_RANDOM:
622 case BTYPE_SEGMENT_RANDOM:
623 bs->drawfunc = draw_blink_radial_random;
625 bs->counter = g_blades;
627 case BTYPE_RADIAL_SEQ:
628 bs->drawfunc = draw_blink_radial_sequential;
629 bs->val = random() % g_blades; /* Initial offset */
630 bs->direction = random() & 8 ? 1 : -1;
631 bs->counter = g_blades;
633 case BTYPE_RADIAL_DOUBLESEQ:
634 bs->drawfunc = draw_blink_radial_doubleseq;
635 bs->val = random() % g_blades; /* Initial offset */
636 bs->counter = g_blades / 2 + 1;
638 case BTYPE_CONCENTRIC_SINGLE:
639 bs->drawfunc = draw_blink_concentric_random;
643 case BTYPE_CONCENTRIC_RANDOM:
644 bs->drawfunc = draw_blink_concentric_random;
646 bs->counter = NRADII - 1;
648 case BTYPE_CONCENTRIC_SEQ:
649 bs->drawfunc = draw_blink_concentric_sequential;
650 bs->direction = random() & 8 ? 1 : -1;
651 bs->counter = NRADII - 1;
653 case BTYPE_SEGMENT_SCATTER:
654 bs->drawfunc = draw_blink_segment_scatter;
655 bs->counter = random() % (g_blades / 2) + (g_blades / 2) + 1;
661 /***************************************************************************
662 * Main rendering routine.
665 draw_lockward (ModeInfo *mi)
667 lockward_context *ctx = &g_ctx[MI_SCREEN (mi)];
669 Display *dpy = MI_DISPLAY(mi);
670 Window window = MI_WINDOW(mi);
673 GLfloat scolor[4] = {0.0, 0.0, 0.0, 0.5};
675 if (!ctx->glx_context)
678 glXMakeCurrent (MI_DISPLAY (mi), MI_WINDOW (mi), *(ctx->glx_context));
681 glClear (GL_COLOR_BUFFER_BIT);
684 glBlendFunc (GL_ONE, GL_ONE);
686 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
691 mi->polygon_count = 0;
693 for (n = NSPINNERS; --n >= 0; ) {
694 ss = &ctx->spinners[n];
697 i = ss->ccolor >> COLORIDX_SHF;
698 scolor[0] = ss->colors[i].red / 65535.0;
699 scolor[1] = ss->colors[i].green / 65535.0;
700 scolor[2] = ss->colors[i].blue / 65535.0;
704 glRotatef (ss->rot - ss->rotcount * ss->rotinc, 0, 0, 1);
705 for (i = ss->nblades; --i >= 0; ) {
707 glRotatef (360.0 * i / ss->nblades, 0, 0, 1);
709 glBegin (GL_TRIANGLE_FAN);
710 glCallList (ctx->blades_outer + ss->bladeidx[i].outer);
711 glCallList (ctx->blades_inner + ss->bladeidx[i].inner);
715 mi->polygon_count += SUBDIV + SUBDIV;
719 /* Advance rotation. */
721 if (ss->rotcount > 0)
724 if (ss->rotinc == 0.0)
725 random_blade_rot (ctx, ss);
727 /* Compute # of ticks to sit idle. */
730 calc_interval_frames (ctx,
736 /* Advance colors. */
737 if ((ss->ccolor += ss->colorinc) >= ss->ncolors)
738 ss->ccolor -= ss->ncolors;
739 else if (ss->ccolor < 0)
740 ss->ccolor += ss->ncolors;
744 if (ctx->blink.drawfunc) {
746 ctx->blink.drawfunc (ctx, &ctx->blink);
748 if (ctx->nextblink > 0)
751 /* Compute # of frames for blink idle time. */
753 calc_interval_frames (ctx,
756 random_blink (ctx, &ctx->blink);
762 if (MI_IS_FPS (mi)) do_fps (mi);
765 glXSwapBuffers (dpy, window);
769 /***************************************************************************
770 * Initialization/teardown.
773 init_lockward (ModeInfo *mi)
775 lockward_context *ctx;
779 g_ctx = (lockward_context *) calloc (MI_NUM_SCREENS (mi),
780 sizeof (lockward_context));
782 fprintf (stderr, "%s: can't allocate context.\n",
787 ctx = &g_ctx[MI_SCREEN (mi)];
789 ctx->glx_context = init_GL (mi);
791 reshape_lockward (mi, MI_WIDTH (mi), MI_HEIGHT (mi));
793 glEnable (GL_CULL_FACE);
795 glDisable (GL_DEPTH_TEST);
797 glShadeModel (GL_FLAT);
801 glClearColor (0.0, 0.0, 0.0, 1.0);
803 ctx->blades_outer = glGenLists (NRADII);
804 ctx->blades_inner = glGenLists (NRADII);
805 ctx->rings = glGenLists (NRADII - 1);
807 ctx->fps = 1000000 / MI_DELAY (mi);
808 ctx->nextblink = calc_interval_frames
809 (ctx, g_blinkidle_min, g_blinkidle_max);
810 ctx->blink.drawfunc = NULL;
811 ctx->blink.noise = malloc (sizeof (uint32_t) * g_blades);
812 if (!ctx->blink.noise) {
813 fprintf (stderr, "Can't allocate noise array.\n");
817 gen_blade_arcs (ctx);
820 for (i = NSPINNERS; --i >= 0; ) {
821 spinnerstate *ss = &ctx->spinners[i];
826 /* Establish rotation */
827 random_blade_rot (ctx, ss);
830 * Establish color cycling path and rate. Rate avoids zero.
833 ss->colorinc = (random() & ((2 << COLORIDX_SHF) - 1))
834 - (1 << COLORIDX_SHF);
835 if (ss->colorinc >= 0)
838 ss->colors = (XColor *) calloc (ss->ncolors, sizeof (XColor));
841 "Can't allocate XColors for spinner %d.\n",
845 make_smooth_colormap (0, 0, 0,
846 ss->colors, &ss->ncolors,
848 ss->ncolors <<= COLORIDX_SHF;
853 ss->nblades = g_blades;
854 ss->bladeidx = malloc (sizeof (bladestate) * g_blades);
856 fprintf (stderr, "Can't allocate blades.\n");
859 for (n = g_blades; --n >= 0; ) {
861 * Establish blade radii. Can't be equal. Ensure
865 ss->bladeidx[n].outer = random() & 7;
866 ss->bladeidx[n].inner = random() & 7;
867 } while (ss->bladeidx[n].outer ==
868 ss->bladeidx[n].inner);
870 if (ss->bladeidx[n].outer < ss->bladeidx[n].inner) {
873 tmp = ss->bladeidx[n].outer;
874 ss->bladeidx[n].outer = ss->bladeidx[n].inner;
875 ss->bladeidx[n].inner = tmp;
882 free_lockward (lockward_context *ctx)
886 if (ctx->blink.noise)
887 free (ctx->blink.noise);
888 if (glIsList (ctx->rings))
889 glDeleteLists (ctx->rings, NRADII - 1);
890 if (glIsList (ctx->blades_outer))
891 glDeleteLists (ctx->blades_outer, NRADII);
892 if (glIsList (ctx->blades_inner))
893 glDeleteLists (ctx->blades_inner, NRADII);
895 for (i = NSPINNERS; --i >= 0; ) {
896 spinnerstate *ss = &ctx->spinners[i];
906 release_lockward (ModeInfo *mi)
913 for (i = MI_NUM_SCREENS (mi); --i >= 0; ) {
914 if (g_ctx[i].glx_context)
915 glXMakeCurrent (MI_DISPLAY (mi), MI_WINDOW (mi),
916 *(g_ctx[i].glx_context));
917 free_lockward (&g_ctx[i]);
921 free (g_ctx); g_ctx = NULL;
925 XSCREENSAVER_MODULE ("Lockward", lockward)
929 /* vim:se ts=8 sts=8 sw=8: */