1 /* raverhoop, Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * Simulates an LED hula hoop in a dark room. Oontz oontz oontz.
14 #define DEFAULTS "*delay: 20000 \n" \
16 "*showFPS: False \n" \
17 "*wireframe: False \n" \
20 # define release_hoop 0
22 #define countof(x) (sizeof((x))/sizeof((*x)))
24 #include "xlockmore.h"
27 #include "gltrackball.h"
30 #ifdef USE_GL /* whole file */
33 #define DEF_SPIN "False"
34 #define DEF_WANDER "False"
35 #define DEF_LIGHTS "200"
36 #define DEF_SPEED "1.0"
37 #define DEF_LIGHT_SPEED "1.0"
38 #define DEF_SUSTAIN "1.0"
44 typedef struct afterimage afterimage;
53 int duty_cycle[10]; /* off, on, off, on... values add to 100 */
59 typedef struct oscillator oscillator;
61 GLfloat ratio, from, to, speed, *var;
68 GLXContext *glx_context;
70 trackball_state *trackball;
83 oscillator *oscillators;
87 static hoop_configuration *bps = NULL;
90 static Bool do_wander;
92 static GLfloat speed, light_speed, sustain;
94 static XrmOptionDescRec opts[] = {
95 { "-spin", ".spin", XrmoptionNoArg, "True" },
96 { "+spin", ".spin", XrmoptionNoArg, "False" },
97 { "-wander", ".wander", XrmoptionNoArg, "True" },
98 { "+wander", ".wander", XrmoptionNoArg, "False" },
99 { "-lights", ".lights", XrmoptionSepArg, 0 },
100 { "-speed", ".speed", XrmoptionSepArg, 0 },
101 { "-light-speed", ".lightSpeed", XrmoptionSepArg, 0 },
102 { "-sustain", ".sustain", XrmoptionSepArg, 0 },
105 static argtype vars[] = {
106 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
107 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
108 {&nlights, "lights", "Lights", DEF_LIGHTS, t_Int},
109 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
110 {&light_speed, "lightSpeed", "Speed", DEF_LIGHT_SPEED, t_Float},
111 {&sustain, "sustain", "Sustain", DEF_SUSTAIN, t_Float},
114 ENTRYPOINT ModeSpecOpt hoop_opts = {countof(opts), opts, countof(vars), vars, NULL};
118 decay_afterimages (ModeInfo *mi)
120 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
121 afterimage *prev = 0;
122 afterimage *a = bp->trail;
123 GLfloat tick = 0.05 / sustain;
127 afterimage *next = a->next;
144 add_afterimage (ModeInfo *mi, GLfloat x, GLfloat y, GLfloat z,
147 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
148 afterimage *a = (afterimage *) calloc (1, sizeof (*a));
153 memcpy (a->color, color, sizeof(a->color));
155 /* Put it at the end so that the older, dimmer ones are laid down on
156 the frame buffer before the newer, brighter ones. */
161 for (b = bp->trail; b->next; b = b->next)
169 tick_light (light *L)
174 L->ratio += 0.05 * light_speed;
178 for (i = 0; i < countof(L->duty_cycle); i++)
180 n += L->duty_cycle[i];
181 if (n > 100) abort();
182 if (n / 100.0 > L->ratio)
193 tick_hoop (ModeInfo *mi)
195 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
199 glGetFloatv (GL_MODELVIEW_MATRIX, m0);
201 for (i = 0; i < bp->nlights; i++)
203 light *L = &bp->lights[i];
205 GLfloat th = M_PI * 2 * i / bp->nlights;
206 GLfloat x = cos (th);
207 GLfloat y = sin (th);
216 glTranslatef (bp->midpoint.x, bp->midpoint.y, bp->midpoint.z);
217 glRotatef (bp->th * 180 / M_PI, 0, 0, 1);
218 glRotatef (bp->tilt, 0, 1, 0);
219 glRotatef (bp->spin, 1, 0, 0);
220 glTranslatef (x * bp->radius, y * bp->radius, 0);
221 glGetFloatv (GL_MODELVIEW_MATRIX, m1);
224 /* After all of our translations and rotations, figure out where
225 it actually ended up.
230 add_afterimage (mi, x, y, z, L->color);
236 draw_lights (ModeInfo *mi)
238 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
239 int wire = MI_IS_WIREFRAME(mi);
250 glVertex3f (0, 0, -bp->radius);
251 glVertex3f (0, 0, bp->radius);
254 glTranslatef (bp->midpoint.x, bp->midpoint.y, bp->midpoint.z);
255 glRotatef (bp->th * 180 / M_PI, 0, 0, 1);
256 glRotatef (bp->tilt, 0, 1, 0);
257 glRotatef (bp->spin, 1, 0, 0);
259 glBegin (GL_LINE_LOOP);
260 glVertex3f (0, 0, 0);
261 for (i = 0; i <= n; i++)
263 GLfloat th = i * M_PI * 2 / n;
264 glVertex3f (bp->radius * -cos(th),
265 bp->radius * -sin(th), 0);
267 for (i = 0; i <= n; i++)
269 GLfloat th = i * M_PI * 2 / n;
270 glVertex3f (bp->axial_radius * -cos(th),
271 bp->axial_radius * -sin(th), 0);
277 /* Billboard the lights to always face the camera */
278 glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);
279 m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
280 m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
281 m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
283 glMultMatrixf (&m[0][0]);
285 for (a = bp->trail; a; a = a->next)
289 glTranslatef (a->position.x, a->position.y, a->position.z);
294 c[0] = a->color[0] * a->color[3];
295 c[1] = a->color[1] * a->color[3];
296 c[2] = a->color[2] * a->color[3];
300 glColor4fv (a->color);
302 glRotatef (45, 0, 0, 1);
303 glScalef (0.15, 0.15, 0.15);
305 glTexCoord2f (0, 0); glVertex3f (-1, -1, 0);
306 glTexCoord2f (1, 0); glVertex3f ( 1, -1, 0);
307 glTexCoord2f (1, 1); glVertex3f ( 1, 1, 0);
308 glTexCoord2f (0, 1); glVertex3f (-1, 1, 0);
320 return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
325 ease_ratio (GLfloat r)
328 if (r <= 0) return 0;
329 else if (r >= 1) return 1;
330 else if (r <= ease) return ease * ease_fn (r / ease);
331 else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease);
337 tick_oscillators (ModeInfo *mi)
339 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
340 oscillator *prev = 0;
341 oscillator *a = bp->oscillators;
342 GLfloat tick = 0.1 / speed;
346 oscillator *next = a->next;
347 a->ratio += tick * a->speed;
351 *a->var = a->from + (a->to - a->from) * ease_ratio (a->ratio);
353 if (a->ratio < 1) /* mid cycle */
355 else if (--a->remaining <= 0) /* ended, and expired */
360 bp->oscillators = next;
363 else /* keep going the other way */
365 GLfloat swap = a->from;
378 calm_oscillators (ModeInfo *mi)
380 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
382 for (a = bp->oscillators; a && a->next; a = a->next)
388 add_oscillator (ModeInfo *mi, GLfloat *var, GLfloat speed, GLfloat to,
391 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
394 /* If an oscillator is already running on this variable, don't
396 for (a = bp->oscillators; a && a->next; a = a->next)
400 a = (oscillator *) calloc (1, sizeof (*a));
401 if (repeat <= 0) abort();
407 a->remaining = repeat;
408 a->next = bp->oscillators;
411 fprintf (stderr, "%s: %3d %6.2f -> %6.2f %s\n",
412 progname, repeat, *var, to,
413 (var == &bp->midpoint.z ? "z" :
414 var == &bp->tilt ? "tilt" :
415 var == &bp->axial_radius ? "r" :
416 var == &bp->speed ? "speed" : "?"));
422 add_random_oscillator (ModeInfo *mi)
424 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
425 int n = random() % 12;
427 case 0: case 1: case 2:
428 add_oscillator (mi, &bp->midpoint.z, 1,
429 bp->radius * (0.8 + frand(0.2))
430 * (random() & 1 ? 1 : -1),
431 (3 + (random() % 10)));
433 case 3: case 4: case 5:
434 add_oscillator (mi, &bp->tilt, 1,
435 -(GLfloat) (random() % 15),
436 3 + (random() % 20));
438 case 6: case 7: case 8:
439 add_oscillator (mi, &bp->axial_radius, 1,
440 0.1 + bp->radius * frand(1.4),
444 add_oscillator (mi, &bp->speed, 3,
445 (0.7 + frand(0.9)) * (random() & 1 ? 1 : -1),
448 : 2 + (random() % 5)));
451 add_oscillator (mi, &bp->spin, 0.1,
452 180 * (1 + (random() % 2)),
453 2 * (1 + (random() % 5)));
463 randomize_colors (ModeInfo *mi)
465 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
466 int ncolors = MI_NCOLORS(mi);
473 if (ncolors > bp->nlights)
474 ncolors = bp->nlights;
476 if (! (random() % 10))
479 colors = calloc (ncolors, 4 * sizeof(*colors));
480 for (i = 0; i < ncolors; i++)
482 GLfloat *c = &colors[i * 4];
483 # define QUANTIZE() (((random() % 16) << 4) | 0xF) / 255.0
490 switch (random() % 5) {
491 case 0: ncycles = 1; break;
492 case 2: ncycles = ncolors * (1 + (random() % 5)); break;
493 default: ncycles = ncolors; break;
496 for (i = 0; i < bp->nlights; i++)
498 light *L = &bp->lights[i];
499 int n = i * ncolors / bp->nlights;
500 int m = i * ncycles / bp->nlights;
501 if (n >= ncolors) abort();
502 if (m >= ncycles) abort();
503 memcpy (L->color, &colors[n], sizeof (L->color));
507 L->duty_cycle[0] = 0;
508 L->duty_cycle[1] = 100;
512 L->duty_cycle[0] = 50;
513 L->duty_cycle[1] = 50;
517 L->duty_cycle[0] = 0;
518 L->duty_cycle[1] = 50;
519 L->duty_cycle[2] = 50;
527 move_hoop (ModeInfo *mi)
529 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
531 bp->th += 0.2 * speed * bp->speed;
532 while (bp->th > M_PI*2)
537 bp->midpoint.x = bp->axial_radius * cos (bp->th);
538 bp->midpoint.y = bp->axial_radius * sin (bp->th);
540 tick_oscillators (mi);
542 if (! (random() % 80))
543 add_random_oscillator (mi);
545 if (! (random() % 120))
546 randomize_colors (mi);
551 build_texture (ModeInfo *mi)
557 unsigned char *data = malloc (bpl * size);
559 for (y = 0; y < size; y++)
561 for (x = 0; x < size; x++)
563 double dist = (sqrt (((s2 - x) * (s2 - x)) +
564 ((s2 - y) * (s2 - y)))
566 unsigned char *c = &data [y * bpl + x * 2];
568 c[1] = 0xFF * sin (dist > 1 ? 0 : (1 - dist));
572 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
573 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
574 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
575 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
576 glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
577 glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
578 check_gl_error ("texture param");
580 glTexImage2D (GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, size, size, 0,
581 GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);
582 check_gl_error ("light texture");
588 /* Window management, etc
591 reshape_hoop (ModeInfo *mi, int width, int height)
593 GLfloat h = (GLfloat) height / (GLfloat) width;
596 if (width > height * 5) { /* tiny window: show middle */
597 height = width * 9/16;
599 h = height / (GLfloat) width;
602 glViewport (0, y, (GLint) width, (GLint) height);
604 glMatrixMode(GL_PROJECTION);
606 gluPerspective (30.0, 1/h, 1.0, 100.0);
608 glMatrixMode(GL_MODELVIEW);
610 gluLookAt( 0.0, 0.0, 30.0,
614 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
616 int o = (int) current_device_rotation();
617 if (o != 0 && o != 180 && o != -180)
618 glScalef (1/h, 1/h, 1/h);
622 glClear(GL_COLOR_BUFFER_BIT);
627 hoop_handle_event (ModeInfo *mi, XEvent *event)
629 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
631 if (gltrackball_event_handler (event, bp->trackball,
632 MI_WIDTH (mi), MI_HEIGHT (mi),
635 else if (event->xany.type == KeyPress)
639 XLookupString (&event->xkey, &c, 1, &keysym, 0);
640 if (c == ' ' || c == '\t')
642 randomize_colors (mi);
643 calm_oscillators (mi);
644 add_random_oscillator (mi);
654 init_hoop (ModeInfo *mi)
656 hoop_configuration *bp;
657 int wire = MI_IS_WIREFRAME(mi);
661 bp = &bps[MI_SCREEN(mi)];
663 bp->glx_context = init_GL(mi);
665 reshape_hoop (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
667 glDisable (GL_LIGHTING);
668 glDisable (GL_DEPTH_TEST);
669 glEnable (GL_CULL_FACE);
670 glEnable (GL_NORMALIZE);
672 glBlendFunc (GL_SRC_ALPHA, GL_ONE);
673 glPolygonMode (GL_FRONT, GL_FILL);
674 glShadeModel (GL_FLAT);
679 glEnable (GL_TEXTURE_2D);
683 double spin_speed = 0.3;
684 double wander_speed = 0.005;
685 double spin_accel = 2.0;
687 bp->rot = make_rotator (do_spin ? spin_speed : 0,
688 do_spin ? spin_speed : 0,
689 do_spin ? spin_speed : 0,
691 do_wander ? wander_speed : 0,
693 bp->trackball = gltrackball_init (True);
697 bp->axial_radius = bp->radius * 0.3;
698 bp->tilt = - (GLfloat) (5 + (random() % 12));
699 bp->speed = (random() % 1 ? 1 : -1);
700 bp->nlights = nlights;
701 bp->lights = (light *) calloc (bp->nlights, sizeof (*bp->lights));
702 randomize_colors (mi);
704 add_random_oscillator (mi);
709 draw_hoop (ModeInfo *mi)
711 hoop_configuration *bp = &bps[MI_SCREEN(mi)];
712 Display *dpy = MI_DISPLAY(mi);
713 Window window = MI_WINDOW(mi);
715 if (!bp->glx_context)
718 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
720 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
726 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
727 glTranslatef((x - 0.5) * 7,
731 gltrackball_rotate (bp->trackball);
732 glRotatef (current_device_rotation(), 0, 0, 1);
734 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
735 glRotatef (x * 360, 1.0, 0.0, 0.0);
736 glRotatef (y * 360, 0.0, 1.0, 0.0);
737 glRotatef (z * 360, 0.0, 0.0, 1.0);
740 mi->polygon_count = 0;
742 glScalef (0.2, 0.2, 0.2);
745 glScalef (0.7, 0.7, 0.7);
748 glRotatef (70, 1, 0, 0);
750 if (! bp->button_down_p)
753 decay_afterimages (mi);
759 if (mi->fps_p) do_fps (mi);
762 glXSwapBuffers(dpy, window);
765 XSCREENSAVER_MODULE_2 ("RaverHoop", raverhoop, hoop)