summaryrefslogtreecommitdiffstats
path: root/hacks/glx/beats.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/glx/beats.c')
-rw-r--r--hacks/glx/beats.c439
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 */