9e2f2b65a5f3da7b80cc00a46c75096d707dcfbf
[xscreensaver] / hacks / glx / lockward.c
1 /*
2  * lockward.c:  First attempt at an Xscreensaver.
3  *
4  * Leo L. Schwab                                        2007.08.17
5  ****
6  * Copyright (c) 2007 Leo L. Schwab
7  *
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:
15  *
16  * The above copyright notice and this permission notice shall be included
17  * in all copies or substantial portions of the Software.
18  * 
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.
26  */
27 #include <ctype.h>
28 #include <strings.h>
29
30 #include "xlockmore.h"
31 #include "colors.h"
32
33
34 /***************************************************************************
35  * #defines
36  */
37 #ifdef USE_GL /* whole file */
38
39 #define DEFAULTS        "*delay:        20000   \n"\
40                         "*showFPS:      False   \n"
41
42 #define refresh_lockward        0
43
44
45 #define NUMOF(x)        (sizeof ((x)) / sizeof ((*x)))
46
47 #define NBLADES         12
48 #define NSPINNERS       4
49 #define NRADII          8
50 #define COLORIDX_SHF    4
51
52
53 /***************************************************************************
54  * Structure definitions.
55  */
56 struct lockward_context;                        /*  Forward declaration.  */
57
58 typedef struct bladestate {
59         uint8_t         outer, inner;   /*  Radii  */
60 } bladestate;
61
62 typedef struct spinnerstate {
63         GLfloat         rot;    /*  Terminal rotation after count expires */
64         GLfloat         rotinc; /*  Per-frame increment to rot.  */
65         XColor          *colors;
66         bladestate      *bladeidx;
67         int             ncolors;        /*  n.4 fixed-point  */
68         int             ccolor;         /*  n.4 fixed-point  */
69         int             colorinc;       /*  n.4 fixed-point  */
70         int             rotcount;
71         uint8_t         nblades;
72 } spinnerstate;
73
74 typedef struct blinkstate {
75         int             (*drawfunc) (struct lockward_context *ctx,
76                                      struct blinkstate *bs);
77         uint32_t        *noise; /*  For draw_blink_segment_scatter()  */
78         GLfloat         color[4];
79         uint32_t        val;
80         int16_t         dwell;  /*  <0: sharp  >0: decay  */
81         int16_t         dwellcnt;
82         uint8_t         type;
83         int8_t          counter;
84         int8_t          direction;
85         int8_t          radius;
86 } blinkstate;
87
88 enum blinktype {
89         BTYPE_RADIAL_SINGLE = 0,
90         BTYPE_RADIAL_RANDOM,
91         BTYPE_RADIAL_SEQ,
92         BTYPE_RADIAL_DOUBLESEQ,
93         BTYPE_SEGMENT_SINGLE,
94         BTYPE_SEGMENT_RANDOM,
95         BTYPE_CONCENTRIC_SINGLE,
96         BTYPE_CONCENTRIC_RANDOM,
97         BTYPE_CONCENTRIC_SEQ,
98         BTYPE_SEGMENT_SCATTER,
99         MAX_BTYPE
100 };
101
102 typedef struct lockward_context {
103         GLXContext      *glx_context;
104
105         spinnerstate    spinners[NSPINNERS];
106         blinkstate      blink;
107
108         GLuint          blades_outer, blades_inner;
109         GLuint          rings;
110         Bool            blendmode;
111         int             nextblink;
112         int             fps;
113 } lockward_context;
114
115
116 /***************************************************************************
117  * Prototypes.
118  */
119 static void free_lockward (lockward_context *ctx);
120
121
122 /***************************************************************************
123  * Global variables.
124  */
125 static lockward_context *g_ctx = NULL;
126 static Bool             g_blink_p = True;
127 static int              g_blades = NBLADES;
128 static int              g_rotateidle_min,
129                         g_rotateidle_max;
130 static int              g_blinkidle_min,
131                         g_blinkidle_max;
132 static int              g_blinkdwell_min,
133                         g_blinkdwell_max;
134
135 static XrmOptionDescRec opts[] = {
136         { "-blink",     ".blink",  XrmoptionNoArg, "on" },
137         { "+blink",     ".blink",  XrmoptionNoArg, "off" },
138         { "-rotateidle-min",    ".rotateidle-min",      XrmoptionSepArg,        0 },
139         { "-rotateidle-max",    ".rotateidle-max",      XrmoptionSepArg,        0 },
140         { "-blinkidle-min",     ".blinkidle-min",       XrmoptionSepArg,        0 },
141         { "-blinkidle-max",     ".blinkidle-max",       XrmoptionSepArg,        0 },
142         { "-blinkdwell-min",    ".blinkdwell-min",      XrmoptionSepArg,        0 },
143         { "-blinkdwell-max",    ".blinkdwell-max",      XrmoptionSepArg,        0 },
144 };
145
146 static argtype vars[] = {
147         { &g_blink_p, "blink",  "Blink", "True", t_Bool },
148         { &g_rotateidle_min, "rotateidle-min",  "Rotateidle-min", "1000", t_Int },
149         { &g_rotateidle_max, "rotateidle-max",  "Rotateidle-max", "6000", t_Int },
150         { &g_blinkidle_min, "blinkidle-min", "Blinkidle-min", "1000", t_Int },
151         { &g_blinkidle_max, "blinkidle-max", "Blinkidle-max", "9000", t_Int },
152         { &g_blinkdwell_min, "blinkdwell-min", "Blinkdwell-min", "100", t_Int },
153         { &g_blinkdwell_max, "blinkdwell-max", "Blinkdwell-max", "600", t_Int },
154 };
155
156 static OptionStruct desc[] = {
157         { "-/+blink", "Turn on/off blinking effects." },
158         { "-rotateidle-min", "Minimum idle time for rotators, in milliseconds." },
159         { "-rotateidle-max", "Maximum idle time for rotators, in milliseconds." },
160         { "-blinkidle-min", "Minimum idle time between blink effects, in milliseconds." },
161         { "-blinkidle-max", "Maximum idle time between blink effects, in milliseconds." },
162         { "-blinkdwell-min", "Minimum dwell time for blink effects, in milliseconds." },
163         { "-blinkdwell-max", "Maximum dwell time for blink effects, in milliseconds." },
164 };
165
166 ENTRYPOINT ModeSpecOpt lockward_opts = {
167         NUMOF(opts), opts, NUMOF(vars), vars, desc
168 };
169
170
171 /***************************************************************************
172  * Window management.
173  */
174 ENTRYPOINT void
175 reshape_lockward (ModeInfo *mi, int width, int height)
176 {
177         GLfloat h = (GLfloat) height / (GLfloat) width;
178
179         glViewport (0, 0, (GLint) width, (GLint) height);
180
181         glMatrixMode (GL_PROJECTION);
182         glLoadIdentity ();
183         if (height > width)
184                 gluOrtho2D (-8.0, 8.0, -8.0 * h, 8.0 * h);
185         else
186                 gluOrtho2D (-8.0 / h, 8.0 / h, -8.0, 8.0);
187
188         glMatrixMode (GL_MODELVIEW);
189 }
190
191 ENTRYPOINT Bool
192 lockward_handle_event (ModeInfo *mi, XEvent *event)
193 {
194         lockward_context *ctx = &g_ctx[MI_SCREEN (mi)];
195
196         if (event->xany.type == KeyPress) {
197                 KeySym  keysym;
198                 char    c = 0;
199
200                 XLookupString (&event->xkey, &c, 1, &keysym, 0);
201                 if (c == 'b') {
202                         ctx->blendmode ^= 1;
203                         return True;
204                 }
205         }
206
207         return False;
208 }
209
210
211 /***************************************************************************
212  * "Blade" routines.
213  */
214 static void
215 random_blade_rot (lockward_context *ctx, struct spinnerstate *ss)
216 {
217         /*
218          * The circle is divided up in to g_blades divisions.  The idea here
219          * is to rotate to an exact division point.
220          *
221          * The target rotation is computed via random numbers.
222          *
223          * The time it takes to get there is a maximum of six seconds per
224          * division, and a minimum of one second (no matter how far away it
225          * is), and is selected by random numbers.
226          *
227          * The time value is converted into frames, and a per-frame rotation
228          * is computed.
229          *
230          * During rendering, we approach the target rotation by subtracting
231          * from it the per-frame rotation times the number of outstanding
232          * ticks.  Doing it this way means we'll hit the target rotation
233          * exactly, without low-order errors creeping in to the values (at
234          * least not nearly as quickly).
235          */
236         GLfloat d;
237         int     dist;
238
239         dist = random() % g_blades + 1;
240
241         ss->rotcount = random() % (6 * dist * ctx->fps - ctx->fps)
242                      + ctx->fps;
243
244         if (random() & 4)
245                 dist = -dist;
246         d = dist * 360.0 / (GLfloat) g_blades;
247         ss->rot += d;
248         ss->rotinc = d / (GLfloat) ss->rotcount;
249 }
250
251
252 /*
253  * A "blade" is pie-wedge shaped flat thing that is rotated around where the
254  * apex is/would be.  Initially envisioned as 1/12th of a circle, but that
255  * could be configurable.  The inner and outer edges are rounded off using
256  * six subdivisions so that, when multiple blades are assembled, it looks
257  * more like a circle and less like a polygon.
258  *
259  * The blade is assembled as a tri-fan.  It is oriented centered at 3
260  * o'clock.  The blade is composed of two display lists -- arcs, essentially
261  * -- the outer and the inner one.  The outer one *must* be called before
262  * the inner one, or the blade clockwise-ness will be wrong, and become
263  * invisible.  Arcs of various radii are compiled.
264  */
265 #define SUBDIV          6
266
267 static void
268 gen_blade_arcs (lockward_context *ctx)
269 {
270         GLfloat here, there, step;
271         int     i, n;
272
273         here = 0;
274         there = M_PI * 2.0 / g_blades;
275         step = there / SUBDIV;
276         here -= SUBDIV * step / 2.0;
277         there -= SUBDIV * step / 2.0;
278
279         /*
280          * Build outer blade arcs.
281          * Start at left side of outer radius.  Strike all its vertices.
282          */
283         for (n = 0;  n < NRADII;  ++n) {
284                 glNewList (ctx->blades_outer + n, GL_COMPILE);
285                 for (i = SUBDIV;  i >= 0;  --i)
286                         glVertex3f (cos (here + step * i) * (n + 1.0),
287                                     sin (here + step * i) * (n + 1.0), 0);
288                 glEndList ();
289         }
290
291         /*
292          * Build inner blade arcs.
293          * Move to inner radius, strike all vertices in opposite order.
294          */
295         for (n = 0;  n < NRADII;  ++n) {
296                 glNewList (ctx->blades_inner + n, GL_COMPILE);
297                 for (i = 0;  i <= SUBDIV;  ++i)
298                         glVertex3f (cos (here + step * i) * (n + 1.0),
299                                     sin (here + step * i) * (n + 1.0), 0);
300                 glEndList ();
301         }
302 }
303
304 static void
305 gen_rings (lockward_context *ctx)
306 {
307         GLfloat step;
308         int     i, n;
309
310         step = M_PI * 2.0 / (g_blades * SUBDIV);
311
312         for (n = 0;  n < NRADII - 1;  ++n) {
313                 glNewList (ctx->rings + n, GL_COMPILE);
314                 glBegin (GL_TRIANGLE_STRIP);
315                 for (i = g_blades * SUBDIV;  i >= 0;  --i) {
316                         glVertex3f (cos (step * i) * (n + 1.0),
317                                     sin (step * i) * (n + 1.0), 0);
318                         glVertex3f (cos (step * i) * (n + 2.0),
319                                     sin (step * i) * (n + 2.0), 0);
320                 }
321                 glEnd();
322                 glEndList ();
323         }
324 }
325
326
327 /***************************************************************************
328  * "Blink" routines.
329  */
330 static int
331 calc_interval_frames (lockward_context *ctx, int min, int max)
332 {
333         /*
334          * Compute random interval between min and max milliseconds.
335          * Returned value is in frames.
336          */
337         register int i;
338
339         i = min;
340         if (max > min)
341                 i += random() % (max - min);
342
343         return i * ctx->fps / 1000;
344 }
345
346 static void
347 set_alpha_by_dwell (struct blinkstate *bs)
348 {
349         if (bs->dwell > 0)
350                 bs->color[3] = (GLfloat) bs->dwellcnt / (GLfloat) bs->dwell;
351         else
352                 bs->color[3] = bs->dwellcnt > (-bs->dwell >> 2)  ?  1.0  : 0.0;
353 }
354
355 static void
356 draw_blink_blade (lockward_context *ctx, int inner, int outer)
357 {
358         glBegin (GL_TRIANGLE_FAN);
359         glCallList (ctx->blades_outer + outer);
360         glCallList (ctx->blades_inner + inner);
361         glEnd ();
362 }
363
364 static int
365 draw_blink_radial_random (lockward_context *ctx, struct blinkstate *bs)
366 {
367         int i;
368
369         /*
370          * There is no sense of direction in a random sweep, so re-use the
371          * 'direction' field to hold the current blade we're messing with.
372          */
373         if (bs->dwellcnt < 0) {
374                 if (bs->counter <= 0) {
375                         bs->drawfunc = NULL;
376                         return 0;
377                 }
378
379                 /*
380                  * Find available blade.  Potentially very slow, depending on
381                  * how unlucky we are.
382                  */
383                 do {
384                         i = random() % g_blades;
385                 } while (bs->val & (1 << i));
386                 bs->val |= (1 << i);    /*  Mark as used.  */
387                 bs->direction = i;
388                 if ((bs->dwellcnt = bs->dwell) < 0)
389                         bs->dwellcnt = -bs->dwellcnt;
390                 
391                 if (    bs->type == BTYPE_SEGMENT_SINGLE
392                     ||  bs->type == BTYPE_SEGMENT_RANDOM)
393                         bs->radius = random() % (NRADII - 1);
394
395                 --bs->counter;
396         }
397
398         set_alpha_by_dwell (bs);
399         glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
400         glColor4fv (bs->color);
401         glRotatef (bs->direction * 360.0 / (GLfloat) g_blades, 0, 0, 1);
402         if (bs->radius >= 0)
403                 draw_blink_blade (ctx, bs->radius, bs->radius + 1);
404         else
405                 draw_blink_blade (ctx, 0, NRADII - 1);
406
407         --bs->dwellcnt;
408
409         return SUBDIV + SUBDIV;
410 }
411
412 static int
413 draw_blink_radial_sequential (lockward_context *ctx, struct blinkstate *bs)
414 {
415         if (bs->dwellcnt < 0) {
416                 if (bs->counter <= 0) {
417                         bs->drawfunc = NULL;
418                         return 0;
419                 }
420                 if ((bs->dwellcnt = bs->dwell) < 0)
421                         bs->dwellcnt = -bs->dwellcnt;
422                 --bs->counter;
423         }
424
425         set_alpha_by_dwell (bs);
426         glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
427         glColor4fv (bs->color);
428         glRotatef ((bs->counter * bs->direction + (int) bs->val)
429                     * 360.0 / (GLfloat) g_blades,
430                    0, 0, 1);
431         draw_blink_blade (ctx, 0, NRADII - 1);
432
433         --bs->dwellcnt;
434
435         return SUBDIV + SUBDIV;
436 }
437
438 static int
439 draw_blink_radial_doubleseq (lockward_context *ctx, struct blinkstate *bs)
440 {
441         int polys;
442
443         if (bs->dwellcnt < 0) {
444                 if (bs->counter <= 0) {
445                         bs->drawfunc = NULL;
446                         return 0;
447                 }
448                 if ((bs->dwellcnt = bs->dwell) < 0)
449                         bs->dwellcnt = -bs->dwellcnt;
450                 --bs->counter;
451         }
452
453         set_alpha_by_dwell (bs);
454         glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
455         glColor4fv (bs->color);
456
457         glPushMatrix ();
458         glRotatef (((int) bs->val + bs->counter) * 360.0 / (GLfloat) g_blades,
459                    0, 0, 1);
460         draw_blink_blade (ctx, 0, NRADII - 1);
461         glPopMatrix ();
462         polys = SUBDIV + SUBDIV;
463
464         if (bs->counter  &&  bs->counter < g_blades / 2) {
465                 glRotatef (((int) bs->val - bs->counter)
466                             * 360.0 / (GLfloat) g_blades,
467                            0, 0, 1);
468                 draw_blink_blade (ctx, 0, NRADII - 1);
469                 polys += SUBDIV + SUBDIV;
470         }
471
472         --bs->dwellcnt;
473
474         return polys;
475 }
476
477 static int
478 draw_blink_concentric_random (lockward_context *ctx, struct blinkstate *bs)
479 {
480         int i;
481
482         if (bs->dwellcnt < 0) {
483                 if (bs->counter <= 0) {
484                         bs->drawfunc = NULL;
485                         return 0;
486                 }
487
488                 do {
489                         i = random() % (NRADII - 1);
490                 } while (bs->val & (1 << i));
491                 bs->val |= (1 << i);
492                 bs->direction = i;
493                 if ((bs->dwellcnt = bs->dwell) < 0)
494                         bs->dwellcnt = -bs->dwellcnt;
495
496                 --bs->counter;
497         }
498
499         set_alpha_by_dwell (bs);
500         glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
501         glColor4fv (bs->color);
502         glCallList (ctx->rings + bs->direction);
503
504         --bs->dwellcnt;
505
506         return g_blades * SUBDIV * 2;
507 }
508
509 static int
510 draw_blink_concentric_sequential (lockward_context *ctx, struct blinkstate *bs)
511 {
512         if (bs->dwellcnt < 0) {
513                 if (bs->counter <= 0) {
514                         bs->drawfunc = NULL;
515                         return 0;
516                 }
517                 if ((bs->dwellcnt = bs->dwell) < 0)
518                         bs->dwellcnt = -bs->dwellcnt;
519                 --bs->counter;
520         }
521
522         set_alpha_by_dwell (bs);
523         glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
524         glColor4fv (bs->color);
525         if (bs->direction > 0)
526                 glCallList (ctx->rings + (NRADII - 2) - bs->counter);
527         else
528                 glCallList (ctx->rings + bs->counter);
529
530         --bs->dwellcnt;
531
532         return g_blades * SUBDIV * 2;
533 }
534
535 static int
536 draw_blink_segment_scatter (lockward_context *ctx, struct blinkstate *bs)
537 {
538         int i, polys = 0;
539
540         if (bs->dwellcnt < 0) {
541                 if (bs->counter <= 0) {
542                         bs->drawfunc = NULL;
543                         return 0;
544                 }
545
546                 /*
547                  * Init random noise array.  On average, 1/4 of the bits will
548                  * be set, which should look nice.  (1/2 looks too busy.)
549                  */
550                 for (i = g_blades;  --i >= 0; )
551                         bs->noise[i] = random() & random()
552                                      & ((1 << (NRADII - 1)) - 1);
553
554                 if ((bs->dwellcnt = bs->dwell) < 0)
555                         bs->dwellcnt = -bs->dwellcnt;
556                 --bs->counter;
557         }
558
559         set_alpha_by_dwell (bs);
560         glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
561         glColor4fv (bs->color);
562
563         for (i = g_blades;  --i >= 0; ) {
564                 register uint32_t       bits;
565                 int                     inner, outer;
566
567                 /*
568                  * Find consecutive runs of 1 bits.  Keep going until we run
569                  * out of them.
570                  */
571                 for (bits = bs->noise[i];  bits; ) {
572                         inner = ffs (bits) - 1;
573                         bits = ~bits & ~((1 << inner) - 1);
574                         outer = ffs (bits) - 1;
575                         bits = ~bits & ~((1 << outer) - 1);
576
577                         glPushMatrix ();
578                         glRotatef (i * 360.0 / (GLfloat) g_blades, 0, 0, 1);
579                         draw_blink_blade (ctx, inner, outer);
580                         glPopMatrix ();
581
582                         polys += SUBDIV + SUBDIV;
583                 }
584         }
585
586         --bs->dwellcnt;
587
588         return polys;
589 }
590
591 static void
592 random_blink (lockward_context *ctx, struct blinkstate *bs)
593 {
594         bs->color[0]    =
595         bs->color[1]    =
596         bs->color[2]    =
597         bs->color[3]    = 1.0;
598         bs->dwellcnt    = -1;
599         bs->radius      = -1;
600         bs->dwell       = calc_interval_frames
601                           (ctx, g_blinkdwell_min, g_blinkdwell_max);
602         if (random() & 2)
603                 bs->dwell = -bs->dwell;
604
605         bs->type = random() % MAX_BTYPE;
606
607         switch (bs->type) {
608         case BTYPE_RADIAL_SINGLE:
609         case BTYPE_SEGMENT_SINGLE:
610                 bs->drawfunc = draw_blink_radial_random;
611                 bs->val = 0;
612                 bs->counter = 1;
613                 break;
614         case BTYPE_RADIAL_RANDOM:
615         case BTYPE_SEGMENT_RANDOM:
616                 bs->drawfunc = draw_blink_radial_random;
617                 bs->val = 0;
618                 bs->counter = g_blades;
619                 break;
620         case BTYPE_RADIAL_SEQ:
621                 bs->drawfunc = draw_blink_radial_sequential;
622                 bs->val = random() % g_blades;  /*  Initial offset  */
623                 bs->direction = random() & 8  ?  1 :  -1;
624                 bs->counter = g_blades;
625                 break;
626         case BTYPE_RADIAL_DOUBLESEQ:
627                 bs->drawfunc = draw_blink_radial_doubleseq;
628                 bs->val = random() % g_blades;  /*  Initial offset  */
629                 bs->counter = g_blades / 2 + 1;
630                 break;
631         case BTYPE_CONCENTRIC_SINGLE:
632                 bs->drawfunc = draw_blink_concentric_random;
633                 bs->val = 0;
634                 bs->counter = 1;
635                 break;
636         case BTYPE_CONCENTRIC_RANDOM:
637                 bs->drawfunc = draw_blink_concentric_random;
638                 bs->val = 0;
639                 bs->counter = NRADII - 1;
640                 break;
641         case BTYPE_CONCENTRIC_SEQ:
642                 bs->drawfunc = draw_blink_concentric_sequential;
643                 bs->direction = random() & 8  ?  1 :  -1;
644                 bs->counter = NRADII - 1;
645                 break;
646         case BTYPE_SEGMENT_SCATTER:
647                 bs->drawfunc = draw_blink_segment_scatter;
648                 bs->counter = random() % (g_blades / 2) + (g_blades / 2) + 1;
649                 break;
650         }
651 }
652
653
654 /***************************************************************************
655  * Main rendering routine.
656  */
657 ENTRYPOINT void
658 draw_lockward (ModeInfo *mi)
659 {
660         lockward_context        *ctx = &g_ctx[MI_SCREEN (mi)];
661         spinnerstate    *ss;
662         Display         *dpy = MI_DISPLAY(mi);
663         Window          window = MI_WINDOW(mi);
664         int             i, n;
665
666         GLfloat scolor[4] = {0.0, 0.0, 0.0, 0.5};
667
668         if (!ctx->glx_context)
669                 return;
670
671         glXMakeCurrent (MI_DISPLAY (mi), MI_WINDOW (mi), *(ctx->glx_context));
672
673
674         glClear (GL_COLOR_BUFFER_BIT);
675
676         if (ctx->blendmode)
677                 glBlendFunc (GL_ONE, GL_ONE);
678         else
679                 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
680
681         glPushMatrix ();
682         glLoadIdentity ();
683
684         mi->polygon_count = 0;
685
686         for (n = NSPINNERS;  --n >= 0; ) {
687                 ss = &ctx->spinners[n];
688
689                 /*  Set color.  */
690                 i = ss->ccolor >> COLORIDX_SHF;
691                 scolor[0] = ss->colors[i].red   / 65535.0;
692                 scolor[1] = ss->colors[i].green / 65535.0;
693                 scolor[2] = ss->colors[i].blue  / 65535.0;
694                 glColor4fv (scolor);
695
696                 glPushMatrix ();
697                 glRotatef (ss->rot - ss->rotcount * ss->rotinc, 0, 0, 1);
698                 for (i = ss->nblades;  --i >= 0; ) {
699                         glPushMatrix ();
700                         glRotatef (360.0 * i / ss->nblades, 0, 0, 1);
701
702                         glBegin (GL_TRIANGLE_FAN);
703                         glCallList (ctx->blades_outer + ss->bladeidx[i].outer);
704                         glCallList (ctx->blades_inner + ss->bladeidx[i].inner);
705                         glEnd ();
706
707                         glPopMatrix ();
708                         mi->polygon_count += SUBDIV + SUBDIV;
709                 }
710                 glPopMatrix ();
711
712                 /*  Advance rotation.  */
713                 if (ss->rotcount) {
714                         if (ss->rotcount > 0)
715                                 --ss->rotcount;
716                 } else {
717                         if (ss->rotinc == 0.0)
718                                 random_blade_rot (ctx, ss);
719                         else {
720                                 /*  Compute # of ticks to sit idle.  */
721                                 ss->rotinc = 0.0;
722                                 ss->rotcount =
723                                  calc_interval_frames (ctx,
724                                                        g_rotateidle_min,
725                                                        g_rotateidle_max);
726                         }
727                 }
728
729                 /*  Advance colors.  */
730                 if ((ss->ccolor += ss->colorinc) >= ss->ncolors)
731                         ss->ccolor -= ss->ncolors;
732                 else if (ss->ccolor < 0)
733                         ss->ccolor += ss->ncolors;
734         }
735
736         if (g_blink_p) {
737                 if (ctx->blink.drawfunc) {
738                         mi->polygon_count +=
739                          ctx->blink.drawfunc (ctx, &ctx->blink);
740                 } else {
741                         if (ctx->nextblink > 0)
742                                 --ctx->nextblink;
743                         else {
744                                 /* Compute # of frames for blink idle time. */
745                                 ctx->nextblink =
746                                  calc_interval_frames (ctx,
747                                                        g_blinkidle_min,
748                                                        g_blinkidle_max);
749                                 random_blink (ctx, &ctx->blink);
750                         }
751                 }
752         }
753         glPopMatrix ();
754
755         if (MI_IS_FPS (mi)) do_fps (mi);
756         glFinish();
757
758         glXSwapBuffers (dpy, window);
759 }
760
761
762 /***************************************************************************
763  * Initialization/teardown.
764  */
765 ENTRYPOINT void 
766 init_lockward (ModeInfo *mi)
767 {
768         lockward_context        *ctx;
769         int             i, n;
770
771         if (!g_ctx) {
772                 g_ctx = (lockward_context *) calloc (MI_NUM_SCREENS (mi),
773                                                    sizeof (lockward_context));
774                 if (!g_ctx) {
775                         fprintf (stderr, "%s: can't allocate context.\n",
776                                  progname);
777                         exit (1);
778                 }
779         }
780         ctx = &g_ctx[MI_SCREEN (mi)];
781
782         ctx->glx_context = init_GL (mi);
783
784         reshape_lockward (mi, MI_WIDTH (mi), MI_HEIGHT (mi));
785
786         glEnable (GL_CULL_FACE);
787         glEnable (GL_BLEND);
788         glDisable (GL_DEPTH_TEST);
789
790         glShadeModel (GL_FLAT);
791         glFrontFace (GL_CW);
792
793
794         glClearColor (0.0, 0.0, 0.0, 1.0);
795
796         ctx->blades_outer       = glGenLists (NRADII);
797         ctx->blades_inner       = glGenLists (NRADII);
798         ctx->rings              = glGenLists (NRADII - 1);
799         ctx->blendmode          = 0;
800         ctx->fps                = 1000000 / MI_DELAY (mi);
801         ctx->nextblink          = calc_interval_frames
802                                    (ctx, g_blinkidle_min, g_blinkidle_max);
803         ctx->blink.drawfunc     = NULL;
804         ctx->blink.noise        = malloc (sizeof (uint32_t) * g_blades);
805         if (!ctx->blink.noise) {
806                 fprintf (stderr, "Can't allocate noise array.\n");
807                 exit (1);
808         }
809
810         gen_blade_arcs (ctx);
811         gen_rings (ctx);
812
813         for (i = NSPINNERS;  --i >= 0; ) {
814                 spinnerstate *ss = &ctx->spinners[i];
815
816                 ss->rot         = 0.0;
817                 ss->rotcount    = -1;
818
819                 /*  Establish rotation  */
820                 random_blade_rot (ctx, ss);
821
822                 /*
823                  * Establish color cycling path and rate.  Rate avoids zero.
824                  */
825                 ss->ncolors = 128;
826                 ss->colorinc = (random() & ((2 << COLORIDX_SHF) - 1))
827                              - (1 << COLORIDX_SHF);
828                 if (ss->colorinc >= 0)
829                         ++ss->colorinc;
830
831                 ss->colors = (XColor *) calloc (ss->ncolors, sizeof (XColor));
832                 if (!ss->colors) {
833                         fprintf (stderr,
834                                  "Can't allocate XColors for spinner %d.\n",
835                                  i);
836                         exit (1);
837                 }
838                 make_smooth_colormap (0, 0, 0,
839                                       ss->colors, &ss->ncolors,
840                                       False, 0, False);
841                 ss->ncolors <<= COLORIDX_SHF;
842
843                 /*
844                  * Create blades.
845                  */
846                 ss->nblades     = g_blades;
847                 ss->bladeidx    = malloc (sizeof (bladestate) * g_blades);
848                 if (!ss->bladeidx) {
849                         fprintf (stderr, "Can't allocate blades.\n");
850                         exit (1);
851                 }
852                 for (n = g_blades;  --n >= 0; ) {
853                         /*
854                          * Establish blade radii.  Can't be equal.  Ensure
855                          * outer > inner.
856                          */
857                         do {
858                                 ss->bladeidx[n].outer = random() & 7;
859                                 ss->bladeidx[n].inner = random() & 7;
860                         } while (ss->bladeidx[n].outer ==
861                                  ss->bladeidx[n].inner);
862
863                         if (ss->bladeidx[n].outer < ss->bladeidx[n].inner) {
864                                 uint8_t tmp;
865
866                                 tmp = ss->bladeidx[n].outer;
867                                 ss->bladeidx[n].outer = ss->bladeidx[n].inner;
868                                 ss->bladeidx[n].inner = tmp;
869                         }
870                 }
871         }
872 }
873
874 static void
875 free_lockward (lockward_context *ctx)
876 {
877         int i;
878
879         if (ctx->blink.noise)
880                 free (ctx->blink.noise);
881         if (glIsList (ctx->rings))
882                 glDeleteLists (ctx->rings, NRADII - 1);
883         if (glIsList (ctx->blades_outer))
884                 glDeleteLists (ctx->blades_outer, NRADII);
885         if (glIsList (ctx->blades_inner))
886                 glDeleteLists (ctx->blades_inner, NRADII);
887
888         for (i = NSPINNERS;  --i >= 0; ) {
889                 spinnerstate *ss = &ctx->spinners[i];
890
891                 if (ss->colors)
892                         free (ss->colors);
893                 if (ss->bladeidx)
894                         free (ss->bladeidx);
895         }
896 }
897
898 ENTRYPOINT void
899 release_lockward (ModeInfo *mi)
900 {
901         int i;
902
903         if (!g_ctx)
904                 return;
905
906         for (i = MI_NUM_SCREENS (mi);  --i >= 0; ) {
907                 if (g_ctx[i].glx_context)
908                         glXMakeCurrent (MI_DISPLAY (mi), MI_WINDOW (mi),
909                                         *(g_ctx[i].glx_context));
910                 free_lockward (&g_ctx[i]);
911         }
912
913         FreeAllGL (mi);
914         free (g_ctx);  g_ctx = NULL;
915 }
916
917
918 XSCREENSAVER_MODULE ("Lockward", lockward)
919
920 #endif /* USE_GL */
921
922 /*  vim:se ts=8 sts=8 sw=8:  */