X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fglx%2Fraverhoop.c;fp=hacks%2Fglx%2Fraverhoop.c;h=99310676120630f47f48afe7177c083bdd22be01;hp=0000000000000000000000000000000000000000;hb=aa75c7476aeaa84cf3abc192b376a8b03c325213;hpb=88cfe534a698a0562e81345957a50714af1453bc diff --git a/hacks/glx/raverhoop.c b/hacks/glx/raverhoop.c new file mode 100644 index 00000000..99310676 --- /dev/null +++ b/hacks/glx/raverhoop.c @@ -0,0 +1,767 @@ +/* raverhoop, Copyright (c) 2016 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + * + * Simulates an LED hula hoop in a dark room. Oontz oontz oontz. + */ + +#define DEFAULTS "*delay: 20000 \n" \ + "*ncolors: 12 \n" \ + "*showFPS: False \n" \ + "*wireframe: False \n" \ + +# define refresh_hoop 0 +# define release_hoop 0 +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) + +#include "xlockmore.h" +#include "colors.h" +#include "rotator.h" +#include "gltrackball.h" +#include + +#ifdef USE_GL /* whole file */ + + +#define DEF_SPIN "False" +#define DEF_WANDER "False" +#define DEF_LIGHTS "200" +#define DEF_SPEED "1.0" +#define DEF_LIGHT_SPEED "1.0" +#define DEF_SUSTAIN "1.0" + +typedef struct { + GLfloat x,y,z; +} XYZ; + +typedef struct afterimage afterimage; +struct afterimage { + GLfloat color[4]; + XYZ position; + afterimage *next; +}; + +typedef struct { + GLfloat color[4]; + int duty_cycle[10]; /* off, on, off, on... values add to 100 */ + GLfloat ratio; + Bool on; +} light; + + +typedef struct oscillator oscillator; +struct oscillator { + GLfloat ratio, from, to, speed, *var; + int remaining; + oscillator *next; +}; + + +typedef struct { + GLXContext *glx_context; + rotator *rot; + trackball_state *trackball; + Bool button_down_p; + + int nlights; + light *lights; + GLfloat radius; + GLfloat axial_radius; + XYZ midpoint; + GLfloat tilt; + GLfloat spin; + GLfloat th; + GLfloat speed; + afterimage *trail; + oscillator *oscillators; + +} hoop_configuration; + +static hoop_configuration *bps = NULL; + +static Bool do_spin; +static Bool do_wander; +static int nlights; +static GLfloat speed, light_speed, sustain; + +static XrmOptionDescRec opts[] = { + { "-spin", ".spin", XrmoptionNoArg, "True" }, + { "+spin", ".spin", XrmoptionNoArg, "False" }, + { "-wander", ".wander", XrmoptionNoArg, "True" }, + { "+wander", ".wander", XrmoptionNoArg, "False" }, + { "-lights", ".lights", XrmoptionSepArg, 0 }, + { "-speed", ".speed", XrmoptionSepArg, 0 }, + { "-light-speed", ".lightSpeed", XrmoptionSepArg, 0 }, + { "-sustain", ".sustain", XrmoptionSepArg, 0 }, +}; + +static argtype vars[] = { + {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool}, + {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool}, + {&nlights, "lights", "Lights", DEF_LIGHTS, t_Int}, + {&speed, "speed", "Speed", DEF_SPEED, t_Float}, + {&light_speed, "lightSpeed", "Speed", DEF_LIGHT_SPEED, t_Float}, + {&sustain, "sustain", "Sustain", DEF_SUSTAIN, t_Float}, +}; + +ENTRYPOINT ModeSpecOpt hoop_opts = {countof(opts), opts, countof(vars), vars, NULL}; + + +static void +decay_afterimages (ModeInfo *mi) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + afterimage *prev = 0; + afterimage *a = bp->trail; + GLfloat tick = 0.05 / sustain; + + while (a) + { + afterimage *next = a->next; + a->color[3] -= tick; + if (a->color[3] < 0) + { + if (prev) + prev->next = next; + else + bp->trail = next; + free (a); + } + else + prev = a; + a = next; + } +} + +static void +add_afterimage (ModeInfo *mi, GLfloat x, GLfloat y, GLfloat z, + GLfloat color[4]) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + afterimage *a = (afterimage *) calloc (1, sizeof (*a)); + afterimage *b; + a->position.x = x; + a->position.y = y; + a->position.z = z; + memcpy (a->color, color, sizeof(a->color)); + + /* Put it at the end so that the older, dimmer ones are laid down on + the frame buffer before the newer, brighter ones. */ + if (!bp->trail) + bp->trail = a; + else + { + for (b = bp->trail; b->next; b = b->next) + ; + b->next = a; + } +} + + +static void +tick_light (light *L) +{ + int i; + int n = 0; + + L->ratio += 0.05 * light_speed; + while (L->ratio > 1) + L->ratio -= 1; + + for (i = 0; i < countof(L->duty_cycle); i++) + { + n += L->duty_cycle[i]; + if (n > 100) abort(); + if (n / 100.0 > L->ratio) + { + L->on = (i & 1); + break; + } + } +} + + + +static void +tick_hoop (ModeInfo *mi) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + GLfloat m0[16]; + int i; + + glGetFloatv (GL_MODELVIEW_MATRIX, m0); + + for (i = 0; i < bp->nlights; i++) + { + light *L = &bp->lights[i]; + GLfloat m1[16]; + GLfloat th = M_PI * 2 * i / bp->nlights; + GLfloat x = cos (th); + GLfloat y = sin (th); + GLfloat z; + + tick_light (L); + if (! L->on) + continue; + + glPushMatrix(); + + glTranslatef (bp->midpoint.x, bp->midpoint.y, bp->midpoint.z); + glRotatef (bp->th * 180 / M_PI, 0, 0, 1); + glRotatef (bp->tilt, 0, 1, 0); + glRotatef (bp->spin, 1, 0, 0); + glTranslatef (x * bp->radius, y * bp->radius, 0); + glGetFloatv (GL_MODELVIEW_MATRIX, m1); + glPopMatrix(); + + /* After all of our translations and rotations, figure out where + it actually ended up. + */ + x = m1[12] - m0[12]; + y = m1[13] - m0[13]; + z = m1[14] - m0[14]; + add_afterimage (mi, x, y, z, L->color); + } +} + + +static void +draw_lights (ModeInfo *mi) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + int wire = MI_IS_WIREFRAME(mi); + afterimage *a; + GLfloat m[4][4]; + + if (wire) + { + int n = 360; + int i; + glPushMatrix(); + + glBegin (GL_LINES); + glVertex3f (0, 0, -bp->radius); + glVertex3f (0, 0, bp->radius); + glEnd(); + + glTranslatef (bp->midpoint.x, bp->midpoint.y, bp->midpoint.z); + glRotatef (bp->th * 180 / M_PI, 0, 0, 1); + glRotatef (bp->tilt, 0, 1, 0); + glRotatef (bp->spin, 1, 0, 0); + + glBegin (GL_LINE_LOOP); + glVertex3f (0, 0, 0); + for (i = 0; i <= n; i++) + { + GLfloat th = i * M_PI * 2 / n; + glVertex3f (bp->radius * -cos(th), + bp->radius * -sin(th), 0); + } + for (i = 0; i <= n; i++) + { + GLfloat th = i * M_PI * 2 / n; + glVertex3f (bp->axial_radius * -cos(th), + bp->axial_radius * -sin(th), 0); + } + glEnd(); + glPopMatrix(); + } + + /* Billboard the lights to always face the camera */ + glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); + m[0][0] = 1; m[1][0] = 0; m[2][0] = 0; + m[0][1] = 0; m[1][1] = 1; m[2][1] = 0; + m[0][2] = 0; m[1][2] = 0; m[2][2] = 1; + glLoadIdentity(); + glMultMatrixf (&m[0][0]); + + for (a = bp->trail; a; a = a->next) + { + glPushMatrix(); + + glTranslatef (a->position.x, a->position.y, a->position.z); + + if (wire) + { + GLfloat c[3]; + c[0] = a->color[0] * a->color[3]; + c[1] = a->color[1] * a->color[3]; + c[2] = a->color[2] * a->color[3]; + glColor3fv (c); + } + else + glColor4fv (a->color); + + glRotatef (45, 0, 0, 1); + glScalef (0.15, 0.15, 0.15); + glBegin (GL_QUADS); + glTexCoord2f (0, 0); glVertex3f (-1, -1, 0); + glTexCoord2f (1, 0); glVertex3f ( 1, -1, 0); + glTexCoord2f (1, 1); glVertex3f ( 1, 1, 0); + glTexCoord2f (0, 1); glVertex3f (-1, 1, 0); + glEnd(); + mi->polygon_count++; + + glPopMatrix(); + } +} + + +static GLfloat +ease_fn (GLfloat r) +{ + return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */ +} + + +static GLfloat +ease_ratio (GLfloat r) +{ + GLfloat ease = 0.35; + if (r <= 0) return 0; + else if (r >= 1) return 1; + else if (r <= ease) return ease * ease_fn (r / ease); + else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease); + else return r; +} + + +static void +tick_oscillators (ModeInfo *mi) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + oscillator *prev = 0; + oscillator *a = bp->oscillators; + GLfloat tick = 0.1 / speed; + + while (a) + { + oscillator *next = a->next; + a->ratio += tick * a->speed; + if (a->ratio > 1) + a->ratio = 1; + + *a->var = a->from + (a->to - a->from) * ease_ratio (a->ratio); + + if (a->ratio < 1) /* mid cycle */ + prev = a; + else if (--a->remaining <= 0) /* ended, and expired */ + { + if (prev) + prev->next = next; + else + bp->oscillators = next; + free (a); + } + else /* keep going the other way */ + { + GLfloat swap = a->from; + a->from = a->to; + a->to = swap; + a->ratio = 0; + prev = a; + } + + a = next; + } +} + + +static void +calm_oscillators (ModeInfo *mi) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + oscillator *a; + for (a = bp->oscillators; a && a->next; a = a->next) + a->remaining = 1; +} + + +static void +add_oscillator (ModeInfo *mi, GLfloat *var, GLfloat speed, GLfloat to, + int repeat) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + oscillator *a; + + /* If an oscillator is already running on this variable, don't + add another. */ + for (a = bp->oscillators; a && a->next; a = a->next) + if (a->var == var) + return; + + a = (oscillator *) calloc (1, sizeof (*a)); + if (repeat <= 0) abort(); + a->ratio = 0; + a->from = *var; + a->to = to; + a->speed = speed; + a->var = var; + a->remaining = repeat; + a->next = bp->oscillators; + bp->oscillators = a; +# if 0 + fprintf (stderr, "%s: %3d %6.2f -> %6.2f %s\n", + progname, repeat, *var, to, + (var == &bp->midpoint.z ? "z" : + var == &bp->tilt ? "tilt" : + var == &bp->axial_radius ? "r" : + var == &bp->speed ? "speed" : "?")); +# endif +} + + +static void +add_random_oscillator (ModeInfo *mi) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + int n = random() % 12; + switch (n) { + case 0: case 1: case 2: + add_oscillator (mi, &bp->midpoint.z, 1, + bp->radius * (0.8 + frand(0.2)) + * (random() & 1 ? 1 : -1), + (3 + (random() % 10))); + break; + case 3: case 4: case 5: + add_oscillator (mi, &bp->tilt, 1, + -(GLfloat) (random() % 15), + 3 + (random() % 20)); + break; + case 6: case 7: case 8: + add_oscillator (mi, &bp->axial_radius, 1, + 0.1 + bp->radius * frand(1.4), + 1 + (random() % 4)); + break; + case 9: case 10: + add_oscillator (mi, &bp->speed, 3, + (0.7 + frand(0.9)) * (random() & 1 ? 1 : -1), + ((random() % 5) + ? 1 + : 2 + (random() % 5))); + break; + case 11: + add_oscillator (mi, &bp->spin, 0.1, + 180 * (1 + (random() % 2)), + 2 * (1 + (random() % 5))); + break; + default: + abort(); + break; + } +} + + +static void +randomize_colors (ModeInfo *mi) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + int ncolors = MI_NCOLORS(mi); + GLfloat *colors; + int ncycles; + int i; + + if (ncolors < 1) + ncolors = 1; + if (ncolors > bp->nlights) + ncolors = bp->nlights; + + if (! (random() % 10)) + ncolors = 1; + + colors = calloc (ncolors, 4 * sizeof(*colors)); + for (i = 0; i < ncolors; i++) + { + GLfloat *c = &colors[i * 4]; +# define QUANTIZE() (((random() % 16) << 4) | 0xF) / 255.0 + c[0] = QUANTIZE(); + c[1] = QUANTIZE(); + c[2] = QUANTIZE(); + c[3] = 1; + } + + switch (random() % 5) { + case 0: ncycles = 1; break; + case 2: ncycles = ncolors * (1 + (random() % 5)); break; + default: ncycles = ncolors; break; + } + + for (i = 0; i < bp->nlights; i++) + { + light *L = &bp->lights[i]; + int n = i * ncolors / bp->nlights; + int m = i * ncycles / bp->nlights; + if (n >= ncolors) abort(); + if (m >= ncycles) abort(); + memcpy (L->color, &colors[n], sizeof (L->color)); + + if (ncycles <= 1) + { + L->duty_cycle[0] = 0; + L->duty_cycle[1] = 100; + } + else if (m & 1) + { + L->duty_cycle[0] = 50; + L->duty_cycle[1] = 50; + } + else + { + L->duty_cycle[0] = 0; + L->duty_cycle[1] = 50; + L->duty_cycle[2] = 50; + } + } + free (colors); +} + + +static void +move_hoop (ModeInfo *mi) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + + bp->th += 0.2 * speed * bp->speed; + while (bp->th > M_PI*2) + bp->th -= M_PI*2; + while (bp->th < 0) + bp->th += M_PI*2; + + bp->midpoint.x = bp->axial_radius * cos (bp->th); + bp->midpoint.y = bp->axial_radius * sin (bp->th); + + tick_oscillators (mi); + + if (! (random() % 80)) + add_random_oscillator (mi); + + if (! (random() % 120)) + randomize_colors (mi); +} + + +static void +build_texture (ModeInfo *mi) +{ + int x, y; + int size = 128; + int s2 = size / 2; + int bpl = size * 2; + unsigned char *data = malloc (bpl * size); + + for (y = 0; y < size; y++) + { + for (x = 0; x < size; x++) + { + double dist = (sqrt (((s2 - x) * (s2 - x)) + + ((s2 - y) * (s2 - y))) + / s2); + unsigned char *c = &data [y * bpl + x * 2]; + c[0] = 0xFF; + c[1] = 0xFF * sin (dist > 1 ? 0 : (1 - dist)); + } + } + + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glPixelStorei (GL_UNPACK_ALIGNMENT, 1); + check_gl_error ("texture param"); + + glTexImage2D (GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, size, size, 0, + GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data); + check_gl_error ("light texture"); + free (data); +} + + + +/* Window management, etc + */ +ENTRYPOINT void +reshape_hoop (ModeInfo *mi, int width, int height) +{ + GLfloat h = (GLfloat) height / (GLfloat) width; + + glViewport (0, 0, (GLint) width, (GLint) height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective (30.0, 1/h, 1.0, 100.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt( 0.0, 0.0, 30.0, + 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0); + +# ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */ + { + int o = (int) current_device_rotation(); + if (o != 0 && o != 180 && o != -180) + glScalef (1/h, 1/h, 1/h); + } +# endif + + glClear(GL_COLOR_BUFFER_BIT); +} + + +ENTRYPOINT Bool +hoop_handle_event (ModeInfo *mi, XEvent *event) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + + if (gltrackball_event_handler (event, bp->trackball, + MI_WIDTH (mi), MI_HEIGHT (mi), + &bp->button_down_p)) + return True; + else if (event->xany.type == KeyPress) + { + KeySym keysym; + char c = 0; + XLookupString (&event->xkey, &c, 1, &keysym, 0); + if (c == ' ' || c == '\t') + { + randomize_colors (mi); + calm_oscillators (mi); + add_random_oscillator (mi); + return True; + } + } + + return False; +} + + +ENTRYPOINT void +init_hoop (ModeInfo *mi) +{ + hoop_configuration *bp; + int wire = MI_IS_WIREFRAME(mi); + + if (!bps) { + bps = (hoop_configuration *) + calloc (MI_NUM_SCREENS(mi), sizeof (hoop_configuration)); + if (!bps) { + fprintf(stderr, "%s: out of memory\n", progname); + exit(1); + } + } + + bp = &bps[MI_SCREEN(mi)]; + + bp->glx_context = init_GL(mi); + + reshape_hoop (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); + + glDisable (GL_LIGHTING); + glDisable (GL_DEPTH_TEST); + glEnable (GL_CULL_FACE); + glEnable (GL_NORMALIZE); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE); + glPolygonMode (GL_FRONT, GL_FILL); + glShadeModel (GL_FLAT); + + if (! wire) + { + build_texture (mi); + glEnable (GL_TEXTURE_2D); + } + + { + double spin_speed = 0.3; + double wander_speed = 0.005; + double spin_accel = 2.0; + + bp->rot = make_rotator (do_spin ? spin_speed : 0, + do_spin ? spin_speed : 0, + do_spin ? spin_speed : 0, + spin_accel, + do_wander ? wander_speed : 0, + False); + bp->trackball = gltrackball_init (True); + } + + bp->radius = 30; + bp->axial_radius = bp->radius * 0.3; + bp->tilt = - (GLfloat) (5 + (random() % 12)); + bp->speed = (random() % 1 ? 1 : -1); + bp->nlights = nlights; + bp->lights = (light *) calloc (bp->nlights, sizeof (*bp->lights)); + randomize_colors (mi); + move_hoop (mi); + add_random_oscillator (mi); +} + + +ENTRYPOINT void +draw_hoop (ModeInfo *mi) +{ + hoop_configuration *bp = &bps[MI_SCREEN(mi)]; + Display *dpy = MI_DISPLAY(mi); + Window window = MI_WINDOW(mi); + + if (!bp->glx_context) + return; + + glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context)); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glPushMatrix (); + + { + double x, y, z; + get_position (bp->rot, &x, &y, &z, !bp->button_down_p); + glTranslatef((x - 0.5) * 7, + (y - 0.5) * 0.5, + (z - 0.5) * 15); + + gltrackball_rotate (bp->trackball); + glRotatef (current_device_rotation(), 0, 0, 1); + + get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p); + glRotatef (x * 360, 1.0, 0.0, 0.0); + glRotatef (y * 360, 0.0, 1.0, 0.0); + glRotatef (z * 360, 0.0, 0.0, 1.0); + } + + mi->polygon_count = 0; + + glScalef (0.2, 0.2, 0.2); + +# ifdef HAVE_MOBILE + glScalef (0.7, 0.7, 0.7); +# endif + + glRotatef (70, 1, 0, 0); + + if (! bp->button_down_p) + move_hoop (mi); + + decay_afterimages (mi); + tick_hoop (mi); + draw_lights (mi); + + glPopMatrix (); + + if (mi->fps_p) do_fps (mi); + glFinish(); + + glXSwapBuffers(dpy, window); +} + +XSCREENSAVER_MODULE_2 ("RaverHoop", raverhoop, hoop) + +#endif /* USE_GL */