diff options
Diffstat (limited to 'hacks/glx/beats.c')
-rw-r--r-- | hacks/glx/beats.c | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/hacks/glx/beats.c b/hacks/glx/beats.c new file mode 100644 index 0000000..67a6eb8 --- /dev/null +++ b/hacks/glx/beats.c @@ -0,0 +1,439 @@ +/* beats, Copyright (c) 2020 David Eccles (gringer) <hacking@gringene.org> + * + * 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. + */ + +/* Beats changes the position of objects in time with a + * synchronisation signal (or more correctly, based on the time + * elapsed since the last synchronisation point). By default, the + * system clock is used for this signal, with synchronisation + * happening every minute. The location of objects is entirely + * dependant on this synchronisation signal; there is no multi-object + * state that needs to be stored, although there may be some styling + * state required. + */ + +#define DEFAULTS "*count: 30 \n" \ + "*delay: 30000 \n" \ + "*showFPS: False \n" \ + "*wireframe: False \n" \ + +# define release_beats 0 + +#include "xlockmore.h" +#include "colors.h" +#include "sphere.h" +#include "hsv.h" +#include <ctype.h> +#include <sys/time.h> + +#ifdef USE_GL /* whole file */ + +#define DEF_CYCLE "-1" +#define DEF_TICK "True" +#define DEF_BLUR "True" + +#define SPHERE_SLICES 16 /* how densely to render spheres */ +#define SPHERE_STACKS 16 + + +typedef struct { + GLXContext *glx_context; + Bool button_down_p; + + GLuint beats_list; + + GLfloat pos; + + int ball_count; /* Number of balls */ + int preset_cycle; /* Cycle to show (-1 for random) */ + Bool use_tick; /* Add tick for clockwise / galaxy */ + Bool use_blur; /* Motion blur */ + int ncolors; + XColor *colors; + int ccolor; + int color_shift; + +} beats_configuration; + +static beats_configuration *bps = NULL; + +static int cycle_arg; +static Bool tick_arg; +static Bool blur_arg; + +static XrmOptionDescRec opts[] = { + { "-cycle", ".cycle", XrmoptionSepArg, 0 }, + { "-count", ".count", XrmoptionSepArg, 0 }, + { "-tick", ".tick", XrmoptionNoArg, "on" }, + { "+tick", ".tick", XrmoptionNoArg, "off" }, + { "-blur", ".blur", XrmoptionNoArg, "on" }, + { "+blur", ".blur", XrmoptionNoArg, "off" } +}; + +static argtype vars[] = { + {&cycle_arg, "cycle", "Cycle", DEF_CYCLE, t_Int}, + {&tick_arg, "tick", "Tick", DEF_TICK, t_Bool}, + {&blur_arg, "blur", "Blur", DEF_BLUR, t_Bool} +}; + +static OptionStruct desc[] = { + {"-count num", "number of balls"}, + {"-cycle num", "cycle / pattern type"}, + {"-/+tick", "enable/disable tick for clockwise and galaxy"}, + {"-/+blur", "enable/disable motion blur"} +}; + +ENTRYPOINT ModeSpecOpt beats_opts = + {countof(opts), opts, countof(vars), vars, desc}; + +/* Window management, etc + */ +ENTRYPOINT void +reshape_beats (ModeInfo *mi, int width, int height) +{ + GLfloat h = (GLfloat) height / (GLfloat) width; + int y = 0; + + if (width > height * 5) { /* tiny window: show middle */ + height = width * 9/16; + y = -height/2; + h = height / (GLfloat) width; + } + + glViewport (0, y, (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); + + glClear(GL_COLOR_BUFFER_BIT); +} + + +ENTRYPOINT Bool +beats_handle_event (ModeInfo *mi, XEvent *event) +{ + return True; +} + +static Bool getFracColour (GLfloat* retVal, float posFrac, float s){ + /* top: red, right: yellow, bottom: [dark] green, left: blue */ + /* note: fixed point; align to 0.1 degree increments */ + int theta, h, v; + unsigned short r,g,b; + theta = ((int)(posFrac * 3600) % 3600 + 3600) % 3600; + v = 100; + if ((theta >= 0) && (theta < 900)) { + h = (theta * 600) / 900; + } else if ((theta >= 900) && (theta < 1800)) { + h = ((theta - 900) * 600) / 900 + 600; + v = 100 - ((theta - 900) / 18); + } else if ((theta >= 1800) && (theta < 2700)) { + h = ((theta - 1800) * 1200) / 900 + 1200; + v = ((theta - 1800) / 18) + 50; + } else /* if ((theta >= 2700) && (theta < 3600))*/ { + h = ((theta - 2700) * 1200) / 900 + 2400; + } + hsv_to_rgb((int)h / 10.0, s, v / 100.0, &r, &g, &b); + retVal[0] = r / 65535.0; + retVal[1] = g / 65535.0; + retVal[2] = b / 65535.0; + return True; +} + + +ENTRYPOINT void +init_beats (ModeInfo *mi) +{ + beats_configuration *bp; + int wire = MI_IS_WIREFRAME(mi); + + MI_INIT (mi, bps); + bp = &bps[MI_SCREEN(mi)]; + + bp->glx_context = init_GL(mi); + + reshape_beats (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); + + if (!wire) + { + GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0}; + GLfloat amb[4] = {0.02, 0.02, 0.02, 1.0}; + GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0}; + GLfloat spc[4] = {0.2, 0.2, 0.2, 0.2}; + + glEnable(GL_LIGHTING); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + + glLightfv(GL_LIGHT0, GL_POSITION, pos); + glLightfv(GL_LIGHT0, GL_AMBIENT, amb); + glLightfv(GL_LIGHT0, GL_DIFFUSE, dif); + glLightfv(GL_LIGHT0, GL_SPECULAR, spc); + + glEnable(GL_LIGHT0); + } + + if (cycle_arg > 3) cycle_arg = -1; + + bp->ball_count = MI_COUNT(mi); + if (bp->ball_count < 2) bp->ball_count = 2; + + bp->preset_cycle = cycle_arg; + bp->use_tick = tick_arg; + bp->use_blur = blur_arg; + +# ifdef HAVE_ANDROID + bp->use_blur = False; /* Works on iOS but not Android */ +# endif + + bp->ncolors = 128; + bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor)); + make_smooth_colormap (0, 0, 0, + bp->colors, &bp->ncolors, + False, 0, False); + + bp->beats_list = glGenLists(1); + + glNewList (bp->beats_list, GL_COMPILE); + glScalef(0.71, 0.71, 0.71); + unit_sphere (SPHERE_STACKS, SPHERE_SLICES, wire); + glEndList (); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + + +ENTRYPOINT void +draw_beats (ModeInfo *mi) +{ + beats_configuration *bp = &bps[MI_SCREEN(mi)]; + Display *dpy = MI_DISPLAY(mi); + Window window = MI_WINDOW(mi); + unsigned num_objects = bp->ball_count, oi; + struct timeval tv, tvOrig; + struct tm *now; + Bool sineWaveTick = bp->use_tick; + Bool motionBlur = bp->use_blur; + size_t cycle, dist; + unsigned int tmS, tmM, tmH, tmD; + unsigned int timeSeed; + int timeDelta = 0; + size_t blurOffset = 10; /* offset per blur frame, in milliseconds */ + size_t framesPerBlur = 20; /* number of sub-frames to blur */ + size_t deltaLimit = (motionBlur) ? (blurOffset * framesPerBlur) : 1; + float ballAlpha; + float secFrac, minFrac, minProp, hourProp, halfDayProp, + z, op, mp, m2m, + theta, delta, blurFrac, oFP, pathLength; + + static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0}; + static const GLfloat bshiny = 92.0; + + GLfloat bcolor[4] = {0.85, 0.75, 0.75, 1.0}; + + if (!bp->glx_context) + return; + gettimeofday (&tvOrig, NULL); + + glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); + + glShadeModel(GL_SMOOTH); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_ALPHA_TEST); + glEnable(GL_NORMALIZE); + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glPushMatrix (); + glRotatef(current_device_rotation(), 0, 0, 1); + + { + GLfloat s = (MI_WIDTH(mi) < MI_HEIGHT(mi) + ? MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi) + : 1); + glScalef (s, s, s); + } + + /* timeDelta is in milliseconds */ + for(timeDelta = 0; timeDelta <= deltaLimit; timeDelta += blurOffset){ + if(timeDelta < blurOffset){ + /* glEnable(GL_DEPTH_TEST); */ + ballAlpha = 1.0; + } else { + /* glDisable(GL_DEPTH_TEST); */ + ballAlpha = 1.0 / framesPerBlur; + } + blurFrac = sin((1 - (float) timeDelta / deltaLimit) * M_PI_2) * ballAlpha; + tv = tvOrig; + now = localtime (&tv.tv_sec); /* This seems to be needed for seconds */ + tmS = now->tm_sec; + tmM = now->tm_min; + tmH = now->tm_hour; + tmD = now->tm_yday; + secFrac = ((tv.tv_usec % 1000000) - (timeDelta * 1000)) / (1e6); + if(secFrac < 0){ + secFrac += 1; + tmS--; + if(tmS < 0){ + tmS += 60; + tmM--; + } + if(tmM < 0){ + tmM += 60; + tmH--; + } + if(tmH < 0){ + tmH += 24; + tmD--; + } + if(tmD < 0){ + /* note: this won't be accurate for leap years, but the rare + event logic is complex enough */ + tmD += 365; + } + } + /* pseudo-random generator based on current minute */ + timeSeed = (((tmM+1) * (tmM+1) * ((tmH+1) * 37) * + ((tmD+1) * 1151) * 1233599) % 653); + cycle = timeSeed % 4; + if(bp->preset_cycle != -1){ + cycle = bp->preset_cycle; + } + if(sineWaveTick && (cycle == 0 || cycle == 3)){ + Bool doTick = (timeSeed % 2 == 0); + if(doTick){ /* choose to tick randomly */ + /* sine-wave 'tick' motion, converts linear 0..1 to + pause/fast/pause 0..1 */ + secFrac = (1.0 - sin((0.5-secFrac) * M_PI))/2.0; + } + } + minFrac = tmS / 60.0; + /* now we have enough information to calculate our goal statistic, + minProp: the position in the synchronisation cycle of one + minute */ + minProp = (minFrac - trunc(minFrac)) + (secFrac / 60); + m2m = minProp * 2 * M_PI; + + /* change colour based on the minute and hour */ + hourProp = tmM / 60.0 + minProp / 60.0; + hourProp = hourProp - trunc(hourProp); + + halfDayProp = tmH / 12.0 + hourProp / 12.0; + halfDayProp = halfDayProp - trunc(halfDayProp); + + mi->polygon_count = 0; + + for(oi = 0; oi < num_objects; oi++){ + glPushMatrix (); + glScalef(1.1, 1.1, 1.1); + + /* Object Fraction Position - 0..1 depending on native Z order */ + oFP = oi * 1.0 / (num_objects - 1); + + /* set Z distance between [-3.5 .. 0.5] (common to all cycles) */ + z = (oFP) * 4.0 - 3.5; + + /* set colour (common to all cycles) */ + if(oFP < (1 / 3.0)){ /* "second" objects */ + getFracColour(bcolor, minProp, 1.0); + } else if(oFP < (2 / 3.0)) { /* "minute" objects */ + getFracColour(bcolor, hourProp, 1.0); + } else { /* "hour" objects */ + getFracColour(bcolor, halfDayProp, 1.0); + } + + /* set x/y location */ + if(cycle == 0){ + /* clockwise */ + glRotatef(-minProp * 360 * (oi + 1), 0, 0, 1); + glTranslatef(0, 5, 0); + } else if(cycle == 1){ + /* rain dance */ + float y = 10 * cos(m2m * (oi + 1.0))/2; + /* rotate around Y axis */ + glTranslatef(0, 0, -20); + glRotatef(minProp * 360, 0, 1, 0); + glTranslatef(0, y, 20); + } else if(cycle == 2){ + /* metronome */ + theta = sin(-m2m * (oi + 1.0)) * 90; + /* rotate around z axis at (-5, 0, 0) */ + glTranslatef(0, -5, 0); + glRotatef(theta, 0, 0, 1); + glTranslatef(0, 10, 0); + } else if (cycle == 3){ + /* galaxy */ + mp = (num_objects - 1.0) / 2; + op = mp - oi; + dist = (int)(fabs(op)+0.5); /* dist from centre */ + /* make sure each object travels an integer number of loops in + a path through one cycle */ + pathLength = (int)((60.0 / dist) + 0.5) * 720.0; + delta = pathLength / 2; + theta = -minProp * delta - 180; + /* rotate around X axis after translating (0,-5,0) */ + glTranslatef(0, 0, -20); + glRotatef(minProp * 360 - 180, 1, 0, 0); + glTranslatef(0, 0, 20); + glTranslatef(0, -5, 0); + /* rotate around Y axis */ + glTranslatef(0, 0, -20); + glRotatef(theta, 0, 1, 0); + glTranslatef(0, 0, 20); + } + + /* spread out based on Z position */ + glTranslatef(0, 0, (z - 0.5) * 10); + + /* set up colours */ + glMaterialfv (GL_FRONT, GL_SPECULAR, bspec); + glMateriali (GL_FRONT, GL_SHININESS, bshiny); + if(motionBlur){ + bcolor[3] = (timeDelta == 0) ? 1.0 : blurFrac; /* was ballAlpha */ + } + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, bcolor); + glCallList (bp->beats_list); /* draw sphere */ + mi->polygon_count += (SPHERE_SLICES * SPHERE_STACKS); + + glPopMatrix(); + } + } + glPopMatrix(); + if (mi->fps_p) do_fps (mi); + glFinish(); + glXSwapBuffers(dpy, window); +} + + +ENTRYPOINT void +free_beats (ModeInfo *mi) +{ + beats_configuration *bp = &bps[MI_SCREEN(mi)]; + if (!bp->glx_context) return; + glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); + if (bp->colors) free (bp->colors); + if (glIsList(bp->beats_list)) glDeleteLists(bp->beats_list, 1); +} + +XSCREENSAVER_MODULE ("Beats", beats) + +#endif /* USE_GL */ |