1 /* cubetwist, Copyright (c) 2016-2017 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
12 #define DEFAULTS "*delay: 30000 \n" \
13 "*showFPS: False \n" \
14 "*wireframe: False \n" \
15 "*suppressRotationAnimation: True\n" \
17 # define release_cube 0
19 #define countof(x) (sizeof((x))/sizeof((*x)))
21 #include "xlockmore.h"
24 #include "gltrackball.h"
27 #ifdef USE_GL /* whole file */
30 #define DEF_SPIN "True"
31 #define DEF_WANDER "True"
32 #define DEF_SPEED "1.0"
33 #define DEF_FLAT "True"
34 #define DEF_THICKNESS "0.0"
35 #define DEF_DISPLACEMENT "0.0"
37 typedef struct cube cube;
39 GLfloat size, thickness;
45 typedef struct oscillator oscillator;
47 double ratio, from, to, speed, *var;
53 GLXContext *glx_context;
55 trackball_state *trackball;
58 oscillator *oscillators;
61 static cube_configuration *bps = NULL;
66 static GLfloat thickness;
67 static GLfloat displacement;
68 static Bool do_wander;
70 static XrmOptionDescRec opts[] = {
71 { "-spin", ".spin", XrmoptionNoArg, "True" },
72 { "+spin", ".spin", XrmoptionNoArg, "False" },
73 { "-wander", ".wander", XrmoptionNoArg, "True" },
74 { "+wander", ".wander", XrmoptionNoArg, "False" },
75 { "-flat", ".flat", XrmoptionNoArg, "True" },
76 { "+flat", ".flat", XrmoptionNoArg, "False" },
77 { "-speed", ".speed", XrmoptionSepArg, 0 },
78 { "-thickness", ".thickness", XrmoptionSepArg, 0 },
79 { "-displacement", ".displacement", XrmoptionSepArg, 0 },
82 static argtype vars[] = {
83 {&do_flat, "flat", "flat", DEF_FLAT, t_Bool},
84 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
85 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
86 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
87 {&thickness, "thickness", "Thickness", DEF_THICKNESS, t_Float},
88 {&displacement, "displacement", "Displacement", DEF_DISPLACEMENT, t_Float},
91 ENTRYPOINT ModeSpecOpt cube_opts = {countof(opts), opts, countof(vars), vars, NULL};
95 draw_strut (ModeInfo *mi, cube *c)
97 int wire = MI_IS_WIREFRAME(mi);
102 glNormal3f (0, 0, -1);
103 glTranslatef (-c->size/2, -c->size/2, -c->size/2);
105 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
106 glVertex3f (0, 0, 0);
107 glVertex3f (c->size, 0, 0);
108 glVertex3f (c->size - c->thickness, c->thickness, 0);
109 glVertex3f (c->thickness, c->thickness, 0);
113 glNormal3f (0, 1, 0);
114 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
115 glVertex3f (c->thickness, c->thickness, 0);
116 glVertex3f (c->size - c->thickness, c->thickness, 0);
117 glVertex3f (c->size - c->thickness, c->thickness, c->thickness);
118 glVertex3f (c->thickness, c->thickness, c->thickness);
128 draw_cubes (ModeInfo *mi, cube *c)
133 glColor4fv (c->color);
134 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c->color);
137 for (j = 0; j < 6; j++)
139 for (i = 0; i < 4; i++)
141 polys += draw_strut (mi, c);
142 glRotatef (90, 0, 0, 1);
145 glRotatef (90, 0, 0, 1);
147 glRotatef (90, 0, 1, 0);
149 glRotatef (180, 1, 0, 0);
155 /* This leaves rotations on the prevailing matrix stack, but since
156 this is a tail-call, that's fine. Don't blow the matrix stack. */
157 glRotatef (c->rot.x, 1, 0, 0);
158 glRotatef (c->rot.y, 0, 1, 0);
159 glRotatef (c->rot.z, 0, 0, 1);
160 glTranslatef (c->pos.x, c->pos.y, c->pos.z);
161 c->next->pos = c->pos;
162 c->next->rot = c->rot;
163 polys += draw_cubes (mi, c->next);
166 check_gl_error("cubetwist");
172 make_cubes (ModeInfo *mi)
174 cube_configuration *bp = &bps[MI_SCREEN(mi)];
175 GLfloat step = 2 * (thickness + displacement);
178 GLfloat cc[4], cstep;
182 cc[0] = 0.3 + frand(0.7);
183 cc[1] = 0.3 + frand(0.7);
184 cc[2] = 0.3 + frand(0.7);
187 if (bp->cubes) abort();
190 cube *c = (cube *) calloc (1, sizeof (*c));
192 c->thickness = thickness;
206 for (c = bp->cubes; c; c = c->next)
208 memcpy (c->color, cc, sizeof(cc));
219 return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
224 ease_ratio (GLfloat r)
227 if (r <= 0) return 0;
228 else if (r >= 1) return 1;
229 else if (r <= ease) return ease * ease_fn (r / ease);
230 else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease);
236 tick_oscillators (ModeInfo *mi)
238 cube_configuration *bp = &bps[MI_SCREEN(mi)];
239 oscillator *prev = 0;
240 oscillator *a = bp->oscillators;
241 GLfloat tick = 0.1 / speed;
245 oscillator *next = a->next;
246 a->ratio += tick * a->speed;
250 *a->var = a->from + (a->to - a->from) * ease_ratio (a->ratio);
252 if (a->ratio < 1) /* mid cycle */
254 else if (--a->remaining <= 0) /* ended, and expired */
259 bp->oscillators = next;
262 else /* keep going the other way */
264 GLfloat swap = a->from;
277 add_oscillator (ModeInfo *mi, double *var, GLfloat speed, GLfloat to,
280 cube_configuration *bp = &bps[MI_SCREEN(mi)];
283 /* If an oscillator is already running on this variable, don't
285 for (a = bp->oscillators; a && a->next; a = a->next)
289 a = (oscillator *) calloc (1, sizeof (*a));
290 if (repeat <= 0) abort();
296 a->remaining = repeat;
297 a->next = bp->oscillators;
300 fprintf (stderr, "%s: %3d %6.2f -> %6.2f %s\n",
301 progname, repeat, *var, to,
302 (var == &bp->midpoint.z ? "z" :
303 var == &bp->tilt ? "tilt" :
304 var == &bp->axial_radius ? "r" :
305 var == &bp->speed ? "speed" : "?"));
311 #define RANDSIGN() ((random() & 1) ? 1 : -1)
314 add_random_oscillator (ModeInfo *mi)
316 cube_configuration *bp = &bps[MI_SCREEN(mi)];
318 double s1 = speed * 0.07;
319 double s2 = speed * 0.3;
320 double disp = (thickness + displacement);
321 int c1 = 1 + ((random() % 4) ? 0 : (random() % 3));
323 int n = random() % 6;
326 case 0: add_oscillator (mi, &c->rot.x, s1, 90 * RANDSIGN(), c1); break;
327 case 1: add_oscillator (mi, &c->rot.y, s1, 90 * RANDSIGN(), c1); break;
328 case 2: add_oscillator (mi, &c->rot.z, s1, 90 * RANDSIGN(), c1); break;
329 case 3: add_oscillator (mi, &c->pos.x, s2, disp * RANDSIGN(), c2); break;
330 case 4: add_oscillator (mi, &c->pos.y, s2, disp * RANDSIGN(), c2); break;
331 case 5: add_oscillator (mi, &c->pos.z, s2, disp * RANDSIGN(), c2); break;
332 default: abort(); break;
337 /* Window management, etc
340 reshape_cube (ModeInfo *mi, int width, int height)
342 GLfloat h = (GLfloat) height / (GLfloat) width;
345 if (width > height * 5) { /* tiny window: show middle */
348 h = height / (GLfloat) width;
351 glViewport (0, y, (GLint) width, (GLint) height);
353 glMatrixMode(GL_PROJECTION);
355 gluPerspective (30.0, 1/h, 1.0, 100.0);
357 glMatrixMode(GL_MODELVIEW);
359 gluLookAt( 0.0, 0.0, 30.0,
363 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
365 int o = (int) current_device_rotation();
366 if (o != 0 && o != 180 && o != -180)
367 glScalef (1/h, 1/h, 1/h);
371 glClear(GL_COLOR_BUFFER_BIT);
376 cube_handle_event (ModeInfo *mi, XEvent *event)
378 cube_configuration *bp = &bps[MI_SCREEN(mi)];
380 if (gltrackball_event_handler (event, bp->trackball,
381 MI_WIDTH (mi), MI_HEIGHT (mi),
384 else if (event->xany.type == KeyPress)
388 XLookupString (&event->xkey, &c, 1, &keysym, 0);
389 if (c == ' ' || c == '\t')
393 cube *c = bp->cubes->next;
398 while (bp->oscillators)
400 oscillator *o = bp->oscillators->next;
401 free (bp->oscillators);
407 thickness = 0.03 + frand(0.02);
408 displacement = (random() & 1) ? 0 : (thickness / 3);
412 thickness = 0.001 + frand(0.02);
427 init_cube (ModeInfo *mi)
429 cube_configuration *bp;
430 int wire = MI_IS_WIREFRAME(mi);
434 bp = &bps[MI_SCREEN(mi)];
436 bp->glx_context = init_GL(mi);
438 reshape_cube (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
440 if (!wire && !do_flat)
442 GLfloat color[4] = {1, 1, 1, 1};
443 GLfloat cspec[4] = {1, 1, 0, 1};
444 static const GLfloat shiny = 30;
446 static GLfloat pos0[4] = { 0.5, -1, -0.5, 0};
447 static GLfloat pos1[4] = {-0.75, -1, 0, 0};
448 static GLfloat amb[4] = {0, 0, 0, 1};
449 static GLfloat dif[4] = {1, 1, 1, 1};
450 static GLfloat spc[4] = {1, 1, 1, 1};
452 glEnable(GL_LIGHTING);
455 glEnable(GL_DEPTH_TEST);
456 glEnable(GL_CULL_FACE);
458 glLightfv(GL_LIGHT0, GL_POSITION, pos0);
459 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
460 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
461 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
463 glLightfv(GL_LIGHT1, GL_POSITION, pos1);
464 glLightfv(GL_LIGHT1, GL_AMBIENT, amb);
465 glLightfv(GL_LIGHT1, GL_DIFFUSE, dif);
466 glLightfv(GL_LIGHT1, GL_SPECULAR, spc);
468 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
469 glMaterialfv (GL_FRONT, GL_SPECULAR, cspec);
470 glMateriali (GL_FRONT, GL_SHININESS, shiny);
474 double spin_speed = 0.05;
475 double wander_speed = 0.005;
476 double spin_accel = 1.0;
478 bp->rot = make_rotator (do_spin ? spin_speed : 0,
479 do_spin ? spin_speed : 0,
480 do_spin ? spin_speed : 0,
482 do_wander ? wander_speed : 0,
484 bp->trackball = gltrackball_init (True);
489 if (displacement > 0.5)
492 if (thickness <= 0.0001)
496 thickness = 0.03 + frand(0.02);
497 displacement = (random() & 1) ? 0 : (thickness / 3);
501 thickness = 0.001 + frand(0.02);
511 draw_cube (ModeInfo *mi)
513 cube_configuration *bp = &bps[MI_SCREEN(mi)];
514 Display *dpy = MI_DISPLAY(mi);
515 Window window = MI_WINDOW(mi);
517 if (!bp->glx_context)
520 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
522 glShadeModel(GL_SMOOTH);
523 glEnable(GL_DEPTH_TEST);
524 glEnable(GL_NORMALIZE);
525 glEnable(GL_CULL_FACE);
527 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
531 glScalef(1.1, 1.1, 1.1);
535 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
536 glTranslatef((x - 0.5) * 4,
540 gltrackball_rotate (bp->trackball);
542 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
543 glRotatef (x * 360, 1.0, 0.0, 0.0);
544 glRotatef (y * 360, 0.0, 1.0, 0.0);
545 glRotatef (z * 360, 0.0, 0.0, 1.0);
548 mi->polygon_count = 0;
552 mi->polygon_count = draw_cubes (mi, bp->cubes);
555 if (!bp->button_down_p)
556 tick_oscillators (mi);
558 if (! bp->oscillators &&
559 !bp->button_down_p &&
562 bp->cubes->pos.x = bp->cubes->pos.y = bp->cubes->pos.z = 0;
563 bp->cubes->rot.x = bp->cubes->rot.y = bp->cubes->rot.z = 0;
564 add_random_oscillator (mi);
567 if (mi->fps_p) do_fps (mi);
570 glXSwapBuffers(dpy, window);
574 free_cube (ModeInfo *mi)
576 cube_configuration *bp = &bps[MI_SCREEN(mi)];
579 cube *c = bp->cubes->next;
584 while (bp->oscillators)
586 oscillator *o = bp->oscillators->next;
587 free (bp->oscillators);
593 XSCREENSAVER_MODULE_2 ("CubeTwist", cubetwist, cube)