1 /* rubikblocks, Copyright (c) 2009 Vasek Potocek <vasek.potocek@post.cz>
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 /* RubikBlocks - a Rubik's Mirror Blocks puzzle introduced in 2008.
13 * No mirrors in this version, though, hence the altered name.
17 * add reflection to the faces
20 #define DEFAULTS "*delay: 20000 \n" \
21 "*showFPS: False \n" \
22 "*wireframe: False \n"
24 # define refresh_rubikblocks 0
25 #include "xlockmore.h"
27 #include "gltrackball.h"
31 #define DEF_SPIN "True"
32 #define DEF_WANDER "True"
33 #define DEF_TEXTURE "True"
34 #define DEF_RANDOMIZE "False"
35 #define DEF_SPINSPEED "0.1"
36 #define DEF_ROTSPEED "3.0"
37 #define DEF_WANDERSPEED "0.005"
38 #define DEF_WAIT "40.0"
39 #define DEF_CUBESIZE "1.0"
46 #define BORDER2 (BORDER*BORDER)
49 #define countof(x) (sizeof((x))/sizeof((*x)))
51 #define rnd01() ((int)(random()%2))
53 /*************************************************************************/
55 static Bool spin, wander, rndstart, tex;
56 static float spinspeed, tspeed, wspeed, twait, size;
58 static argtype vars[] = {
59 { &spin, "spin", "Spin", DEF_SPIN, t_Bool},
60 { &wander, "wander", "Wander", DEF_WANDER, t_Bool},
61 { &rndstart, "randomize", "Randomize", DEF_RANDOMIZE, t_Bool},
62 { &tex, "texture", "Texture", DEF_TEXTURE, t_Bool},
63 { &spinspeed, "spinspeed", "SpinSpeed", DEF_SPINSPEED, t_Float},
64 { &tspeed, "rotspeed", "RotSpeed", DEF_ROTSPEED, t_Float},
65 { &wspeed, "wanderspeed", "WanderSpeed", DEF_WANDERSPEED, t_Float},
66 { &twait, "wait", "Wait", DEF_WAIT, t_Float},
67 { &size, "cubesize", "CubeSize", DEF_CUBESIZE, t_Float},
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 { "-randomize", ".randomize", XrmoptionNoArg, "True" },
76 { "+randomize", ".randomize", XrmoptionNoArg, "False" },
77 { "-texture", ".texture", XrmoptionNoArg, "True" },
78 { "+texture", ".texture", XrmoptionNoArg, "False" },
79 { "-spinspeed", ".spinspeed", XrmoptionSepArg, 0 },
80 { "-wanderspeed", ".wanderspeed", XrmoptionSepArg, 0 },
81 { "-rotspeed", ".rotspeed", XrmoptionSepArg, 0 },
82 { "-wait", ".wait", XrmoptionSepArg, 0 },
83 { "-cubesize", ".cubesize", XrmoptionSepArg, 0 },
86 ENTRYPOINT ModeSpecOpt rubikblocks_opts = {countof(opts), opts, countof(vars), vars, NULL};
89 ModStruct rubikblocks_description =
90 { "rubikblocks", "init_rubikblocks", "draw_rubikblocks", "release_rubikblocks",
91 "draw_rubikblocks", "change_rubikblocks", NULL, &rubikblocks_opts,
92 25000, 1, 1, 1, 1.0, 4, "",
93 "Shows randomly shuffling Rubik's Mirror Blocks puzzle", 0, NULL
98 float pos[3]; /* _original_ position */
99 float qr[4]; /* quaternion of rotation */
100 Bool act; /* flag if it is undergoing the current rotation */
104 GLXContext *glx_context;
106 trackball_state *trackball;
110 Bool pause; /* pause between two rotations */
111 float qfram[4]; /* quaternion describing the rotation in one anim. frame */
112 GLfloat t, tmax; /* rotation clock */
113 piece_t pieces[27]; /* type and tilt of all the pieces */
115 unsigned char texture[TEX_HEIGHT][TEX_WIDTH];
120 static rubikblocks_conf *rubikblocks = NULL;
122 static const GLfloat shininess = 20.0;
123 static const GLfloat ambient[] = {0.0, 0.0, 0.0, 1.0};
124 static const GLfloat diffuse[] = {1.0, 1.0, 1.0, 1.0};
125 static const GLfloat position0[] = {1.0, 1.0, 1.0, 0.0};
126 static const GLfloat position1[] = {-1.0, -1.0, 1.0, 0.0};
127 static const GLfloat lmodel_ambient[] = {0.1, 0.1, 0.1, 1.0};
128 static const GLfloat material_ambient[] = {0.7, 0.7, 0.7, 1.0};
129 static const GLfloat material_diffuse[] = {0.7, 0.7, 0.7, 1.0};
130 static const GLfloat material_specular[] = {0.2, 0.2, 0.2, 1.0};
131 static const GLfloat zpos = -18.0;
133 /*************************************************************************/
135 /* Multiplies two quaternions, src*dest, and stores the result in dest. */
137 mult_quat(float src[4], float dest[4])
140 r = src[0]*dest[0] - src[1]*dest[1] - src[2]*dest[2] - src[3]*dest[3];
141 i = src[0]*dest[1] + src[1]*dest[0] + src[2]*dest[3] - src[3]*dest[2];
142 j = src[0]*dest[2] + src[2]*dest[0] + src[3]*dest[1] - src[1]*dest[3];
143 k = src[0]*dest[3] + src[3]*dest[0] + src[1]*dest[2] - src[2]*dest[1];
150 /* Sets the 'act' flag for pieces which will undergo the rotation. */
152 flag_pieces(piece_t pieces[27], int axis, int side)
156 for(i = 0; i < 27; i++)
159 q[1] = pieces[i].pos[0];
160 q[2] = pieces[i].pos[1];
161 q[3] = pieces[i].pos[2];
162 mult_quat(pieces[i].qr, q);
163 for(j = 1; j < 4; j++)
165 mult_quat(pieces[i].qr, q);
166 for(j = 1; j < 4; j++)
168 if(fabs(q[axis] - side) < 0.1)
169 pieces[i].act = True;
171 pieces[i].act = False;
175 /* "Rounds" the value to the nearest from the set {0, +-1/2, +-1/sqrt(2), +-1}.
176 * It is guaranteed to be pretty close to one when this function is called. */
178 settle_value(float v)
180 if(v > 0.9) return 1;
181 else if(v < -0.9) return -1;
182 else if(v > 0.6) return M_SQRT1_2;
183 else if(v < -0.6) return -M_SQRT1_2;
184 else if(v > 0.4) return 0.5;
185 else if(v < -0.4) return -0.5;
190 randomize(rubikblocks_conf *cp)
194 for(i = 0; i < SHUFFLE; i++)
196 axis = (random()%3)+1;
198 flag_pieces(cp->pieces, axis, side);
199 for(j = 1; j < 4; j++)
201 cp->qfram[0] = M_SQRT1_2;
202 cp->qfram[axis] = M_SQRT1_2;
203 for(j = 0; j < 27; j++)
205 if(cp->pieces[j].act)
206 mult_quat(cp->qfram, cp->pieces[j].qr);
212 finish(rubikblocks_conf *cp)
232 flag_pieces(cp->pieces, axis, side);
234 cp->tmax = 90.0*angle;
235 for(i = 1; i < 4; i++)
237 cp->qfram[0] = cos(tspeed*M_PI/360);
238 cp->qfram[axis] = sin((rnd01()*2-1)*tspeed*M_PI/360);
242 for(i = 0; i < 27; i++)
244 for(j = 0; j < 4; j++)
246 cp->pieces[i].qr[j] = settle_value(cp->pieces[i].qr[j]);
256 draw_main(ModeInfo *mi, rubikblocks_conf *cp)
261 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
263 get_position(cp->rot, &x, &y, &z, !cp->button_down);
264 glTranslatef((x-0.5)*6, (y-0.5)*6, -20);
266 /* Do it twice because we don't track the device's orientation. */
267 glRotatef( current_device_rotation(), 0, 0, 1);
268 gltrackball_rotate(cp->trackball);
269 glRotatef(-current_device_rotation(), 0, 0, 1);
271 get_rotation(cp->rot, &x, &y, &z, !cp->button_down);
272 glRotatef(x*360, 1, 0, 0);
273 glRotatef(y*360, 0, 1, 0);
274 glRotatef(z*360, 0, 0, 1);
275 glScalef(size, size, size);
277 if(cp->wire) glColor3f(0.7, 0.7, 0.7);
279 for(i = 0; i < 27; i++)
280 if(cp->pieces[i].act)
281 mult_quat(cp->qfram, cp->pieces[i].qr);
282 for(i = 0; i < 27; i++)
285 if(fabs(cp->pieces[i].qr[0]) < 1)
286 glRotatef(360/M_PI*acos(cp->pieces[i].qr[0]),
287 cp->pieces[i].qr[1], cp->pieces[i].qr[2], cp->pieces[i].qr[3]);
288 glCallList(cp->list_base + i);
291 if((cp->t += tspeed) > cp->tmax) finish(cp);
296 draw_horz_line(rubikblocks_conf *cp, int x1, int x2, int y)
299 if(y < BORDER) y = -y;
301 for(; y < BORDER; y++) {
302 if(y0+y >= TEX_HEIGHT) break;
304 for(x = x1; x <= x2; x++)
305 if(cp->texture[y0+y][x]>w) cp->texture[y0+y][x] = w;
310 draw_vert_line(rubikblocks_conf *cp, int x, int y1, int y2)
315 for(; x < BORDER; x++) {
316 if(x0+x >= TEX_WIDTH) break;
318 for(y = y1; y <= y2; y++)
319 if(cp->texture[y][x0+x]>w) cp->texture[y][x0+x] = w;
324 make_texture(rubikblocks_conf *cp)
327 for(y = 0; y < TEX_HEIGHT; y++)
328 for(x = 0; x < TEX_WIDTH; x++)
329 cp->texture[y][x] = 255;
330 draw_horz_line(cp, 0, TEX_WIDTH-1, 0);
331 draw_horz_line(cp, 0, TEX_WIDTH-1, TEX_HEIGHT-1);
332 draw_vert_line(cp, 0, 0, TEX_HEIGHT-1);
333 draw_vert_line(cp, TEX_WIDTH-1, 0, TEX_HEIGHT-1);
336 /* These simple transforms make the actual shape of the pieces. The parameters
337 * A, B and C affect the excentricity of the pieces in each direction. */
342 if(x > 1.4) return 1.5 - A;
343 else if(x < -1.4) return -1.5 - A;
350 const float B = 0.25;
351 if(y > 1.4) return 1.5 - B;
352 else if(y < -1.4) return -1.5 - B;
360 if(z > 1.4) return 1.5 - C;
361 else if(z < -1.4) return -1.5 - C;
366 init_lists(rubikblocks_conf *cp)
371 base = cp->list_base = glGenLists(27);
372 for(i = 0; i < 27; i++)
374 x = cp->pieces[i].pos[0];
375 y = cp->pieces[i].pos[1];
376 z = cp->pieces[i].pos[2];
377 glNewList(base+i, GL_COMPILE);
378 glBegin(GL_QUAD_STRIP);
381 glVertex3f(fx(x+0.5), fy(y-0.5), fz(z-0.5));
383 glVertex3f(fx(x+0.5), fy(y+0.5), fz(z-0.5));
385 glVertex3f(fx(x+0.5), fy(y-0.5), fz(z+0.5));
387 glVertex3f(fx(x+0.5), fy(y+0.5), fz(z+0.5));
390 glVertex3f(fx(x-0.5), fy(y-0.5), fz(z+0.5));
392 glVertex3f(fx(x-0.5), fy(y+0.5), fz(z+0.5));
393 glNormal3f(-1, 0, 0);
395 glVertex3f(fx(x-0.5), fy(y-0.5), fz(z-0.5));
397 glVertex3f(fx(x-0.5), fy(y+0.5), fz(z-0.5));
398 glNormal3f(0, 0, -1);
400 glVertex3f(fx(x+0.5), fy(y-0.5), fz(z-0.5));
402 glVertex3f(fx(x+0.5), fy(y+0.5), fz(z-0.5));
407 glVertex3f(fx(x+0.5), fy(y+0.5), fz(z+0.5));
409 glVertex3f(fx(x+0.5), fy(y+0.5), fz(z-0.5));
411 glVertex3f(fx(x-0.5), fy(y+0.5), fz(z-0.5));
413 glVertex3f(fx(x-0.5), fy(y+0.5), fz(z+0.5));
414 glNormal3f(0, -1, 0);
416 glVertex3f(fx(x+0.5), fy(y-0.5), fz(z-0.5));
418 glVertex3f(fx(x+0.5), fy(y-0.5), fz(z+0.5));
420 glVertex3f(fx(x-0.5), fy(y-0.5), fz(z+0.5));
422 glVertex3f(fx(x-0.5), fy(y-0.5), fz(z-0.5));
428 /* It looks terrible... FIXME: any other ideas, maybe some anisotropic filtering? */
432 init_gl(ModeInfo *mi)
434 rubikblocks_conf *cp = &rubikblocks[MI_SCREEN(mi)];
438 cp->wire = MI_IS_WIREFRAME(mi);
440 # ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
447 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
452 glDrawBuffer(GL_BACK);
453 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
454 glShadeModel(GL_FLAT);
455 glDepthFunc(GL_LESS);
456 glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
457 glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
458 glLightfv(GL_LIGHT0, GL_POSITION, position0);
459 glLightfv(GL_LIGHT1, GL_AMBIENT, ambient);
460 glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse);
461 glLightfv(GL_LIGHT1, GL_POSITION, position1);
462 glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
463 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
464 glEnable(GL_DEPTH_TEST);
467 glEnable(GL_LIGHTING);
468 glEnable(GL_NORMALIZE);
469 glEnable(GL_COLOR_MATERIAL);
470 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_ambient);
471 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse);
472 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_specular);
473 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
475 glEnable(GL_TEXTURE_2D);
478 status = gluBuild2DMipmaps(GL_TEXTURE_2D, 1, TEX_WIDTH, TEX_HEIGHT,
479 GL_LUMINANCE, GL_UNSIGNED_BYTE, cp->texture);
481 const char *s = (char *)gluErrorString(status);
482 fprintf (stderr, "%s: error mipmapping texture: %s\n", progname, (s?s:"(unknown)"));
485 check_gl_error("mipmapping");
487 glTexImage2D(GL_TEXTURE_2D, 0, 1, TEX_WIDTH, TEX_HEIGHT,
488 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, cp->texture);
490 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
491 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
492 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
493 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
495 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
497 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
502 init_cp(rubikblocks_conf *cp)
510 for(i = -1, m = 0; i <= 1; i++)
511 for(j = -1; j <= 1; j++)
512 for(k = -1; k <= 1; k++)
514 cp->pieces[m].pos[0] = k;
515 cp->pieces[m].pos[1] = j;
516 cp->pieces[m].pos[2] = i;
517 cp->pieces[m].qr[0] = 1;
518 cp->pieces[m].qr[1] = 0;
519 cp->pieces[m].qr[2] = 0;
520 cp->pieces[m].qr[3] = 0;
524 cp->rot = make_rotator(spin?spinspeed:0, spin?spinspeed:0, spin?spinspeed:0,
525 0.1, wander?wspeed:0, True);
526 cp->trackball = gltrackball_init();
528 if(rndstart) randomize(cp);
531 /*************************************************************************/
534 reshape_rubikblocks(ModeInfo *mi, int width, int height)
536 rubikblocks_conf *cp = &rubikblocks[MI_SCREEN(mi)];
537 if(!height) height = 1;
538 cp->ratio = (GLfloat)width/(GLfloat)height;
539 glViewport(0, 0, (GLint) width, (GLint) height);
540 glMatrixMode(GL_PROJECTION);
542 gluPerspective(30.0, cp->ratio, 1.0, 100.0);
543 glMatrixMode(GL_MODELVIEW);
544 glClear(GL_COLOR_BUFFER_BIT);
548 release_rubikblocks(ModeInfo *mi)
550 if (rubikblocks != NULL)
553 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
555 rubikblocks_conf *cp = &rubikblocks[screen];
556 if (cp->glx_context) {
557 cp->glx_context = NULL;
560 free((void *)rubikblocks);
567 init_rubikblocks(ModeInfo *mi)
569 rubikblocks_conf *cp;
572 rubikblocks = (rubikblocks_conf *)calloc(MI_NUM_SCREENS(mi), sizeof(rubikblocks_conf));
573 if(!rubikblocks) return;
575 cp = &rubikblocks[MI_SCREEN(mi)];
580 if ((cp->glx_context = init_GL(mi)) != NULL)
585 reshape_rubikblocks(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
594 draw_rubikblocks(ModeInfo * mi)
596 Display *display = MI_DISPLAY(mi);
597 Window window = MI_WINDOW(mi);
598 rubikblocks_conf *cp;
599 if (!rubikblocks) return;
600 cp = &rubikblocks[MI_SCREEN(mi)];
601 MI_IS_DRAWN(mi) = True;
602 if (!cp->glx_context) return;
603 mi->polygon_count = 0;
604 glXMakeCurrent(display, window, *(cp->glx_context));
605 if (!draw_main(mi, cp))
607 release_rubikblocks(mi);
610 if (MI_IS_FPS(mi)) do_fps (mi);
612 glXSwapBuffers(display, window);
617 change_rubikblocks(ModeInfo * mi)
619 rubikblocks_conf *cp = &rubikblocks[MI_SCREEN(mi)];
620 if (!cp->glx_context) return;
621 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(cp->glx_context));
624 #endif /* !STANDALONE */
627 rubikblocks_handle_event (ModeInfo *mi, XEvent *event)
629 rubikblocks_conf *cp = &rubikblocks[MI_SCREEN(mi)];
630 if(event->xany.type == ButtonPress && event->xbutton.button == Button1)
632 cp->button_down = True;
633 gltrackball_start(cp->trackball, event->xbutton.x, event->xbutton.y,
634 MI_WIDTH(mi), MI_HEIGHT(mi));
637 else if(event->xany.type == ButtonRelease && event->xbutton.button == Button1)
639 cp->button_down = False;
642 else if(event->xany.type == ButtonPress &&
643 (event->xbutton.button == Button4 || event->xbutton.button == Button5 ||
644 event->xbutton.button == Button6 || event->xbutton.button == Button7))
646 gltrackball_mousewheel(cp->trackball,
647 event->xbutton.button, 5, !!event->xbutton.state);
650 else if(event->xany.type == MotionNotify && cp->button_down)
652 gltrackball_track(cp->trackball, event->xmotion.x, event->xmotion.y,
653 MI_WIDTH (mi), MI_HEIGHT (mi));
660 XSCREENSAVER_MODULE ("RubikBlocks", rubikblocks)