/* rubikblocks, Copyright (c) 2009 Vasek Potocek * * 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. */ /* RubikBlocks - a Rubik's Mirror Blocks puzzle introduced in 2008. * No mirrors in this version, though, hence the altered name. */ /* TODO: * add reflection to the faces */ #define DEFAULTS "*delay: 20000 \n" \ "*showFPS: False \n" \ "*wireframe: False \n" \ "*suppressRotationAnimation: True\n" \ # define release_rubikblocks 0 #include "xlockmore.h" #include "rotator.h" #include "gltrackball.h" #ifdef USE_GL #define DEF_SPIN "True" #define DEF_WANDER "True" #define DEF_TEXTURE "True" #define DEF_RANDOMIZE "False" #define DEF_SPINSPEED "0.1" #define DEF_ROTSPEED "3.0" #define DEF_WANDERSPEED "0.005" #define DEF_WAIT "40.0" #define DEF_CUBESIZE "1.0" #define SHUFFLE 100 #define TEX_WIDTH 64 #define TEX_HEIGHT 64 #define BORDER 5 #define BORDER2 (BORDER*BORDER) #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #define rnd01() ((int)(random()%2)) /*************************************************************************/ static Bool spin, wander, rndstart, tex; static float spinspeed, tspeed, wspeed, twait, size; static argtype vars[] = { { &spin, "spin", "Spin", DEF_SPIN, t_Bool}, { &wander, "wander", "Wander", DEF_WANDER, t_Bool}, { &rndstart, "randomize", "Randomize", DEF_RANDOMIZE, t_Bool}, { &tex, "texture", "Texture", DEF_TEXTURE, t_Bool}, { &spinspeed, "spinspeed", "SpinSpeed", DEF_SPINSPEED, t_Float}, { &tspeed, "rotspeed", "RotSpeed", DEF_ROTSPEED, t_Float}, { &wspeed, "wanderspeed", "WanderSpeed", DEF_WANDERSPEED, t_Float}, { &twait, "wait", "Wait", DEF_WAIT, t_Float}, { &size, "cubesize", "CubeSize", DEF_CUBESIZE, t_Float}, }; static XrmOptionDescRec opts[] = { { "-spin", ".spin", XrmoptionNoArg, "True" }, { "+spin", ".spin", XrmoptionNoArg, "False" }, { "-wander", ".wander", XrmoptionNoArg, "True" }, { "+wander", ".wander", XrmoptionNoArg, "False" }, { "-randomize", ".randomize", XrmoptionNoArg, "True" }, { "+randomize", ".randomize", XrmoptionNoArg, "False" }, { "-texture", ".texture", XrmoptionNoArg, "True" }, { "+texture", ".texture", XrmoptionNoArg, "False" }, { "-spinspeed", ".spinspeed", XrmoptionSepArg, 0 }, { "-wanderspeed", ".wanderspeed", XrmoptionSepArg, 0 }, { "-rotspeed", ".rotspeed", XrmoptionSepArg, 0 }, { "-wait", ".wait", XrmoptionSepArg, 0 }, { "-cubesize", ".cubesize", XrmoptionSepArg, 0 }, }; ENTRYPOINT ModeSpecOpt rubikblocks_opts = {countof(opts), opts, countof(vars), vars, NULL}; #ifdef USE_MODULES ModStruct rubikblocks_description = { "rubikblocks", "init_rubikblocks", "draw_rubikblocks", NULL, "draw_rubikblocks", "change_rubikblocks", NULL, &rubikblocks_opts, 25000, 1, 1, 1, 1.0, 4, "", "Shows randomly shuffling Rubik's Mirror Blocks puzzle", 0, NULL }; #endif typedef struct { float pos[3]; /* _original_ position */ float qr[4]; /* quaternion of rotation */ Bool act; /* flag if it is undergoing the current rotation */ } piece_t; typedef struct { GLXContext *glx_context; rotator *rot; trackball_state *trackball; GLfloat ratio; Bool button_down; Bool pause; /* pause between two rotations */ float qfram[4]; /* quaternion describing the rotation in one anim. frame */ GLfloat t, tmax; /* rotation clock */ piece_t pieces[27]; /* type and tilt of all the pieces */ unsigned char texture[TEX_HEIGHT][TEX_WIDTH]; GLuint list_base; Bool wire; } rubikblocks_conf; static rubikblocks_conf *rubikblocks = NULL; static const GLfloat shininess = 20.0; static const GLfloat ambient[] = {0.0, 0.0, 0.0, 1.0}; static const GLfloat diffuse[] = {1.0, 1.0, 1.0, 1.0}; static const GLfloat position0[] = {1.0, 1.0, 1.0, 0.0}; static const GLfloat position1[] = {-1.0, -1.0, 1.0, 0.0}; static const GLfloat lmodel_ambient[] = {0.1, 0.1, 0.1, 1.0}; static const GLfloat material_ambient[] = {0.7, 0.7, 0.7, 1.0}; static const GLfloat material_diffuse[] = {0.7, 0.7, 0.7, 1.0}; static const GLfloat material_specular[] = {0.2, 0.2, 0.2, 1.0}; /*************************************************************************/ /* Multiplies two quaternions, src*dest, and stores the result in dest. */ static void mult_quat(float src[4], float dest[4]) { float r, i, j, k; r = src[0]*dest[0] - src[1]*dest[1] - src[2]*dest[2] - src[3]*dest[3]; i = src[0]*dest[1] + src[1]*dest[0] + src[2]*dest[3] - src[3]*dest[2]; j = src[0]*dest[2] + src[2]*dest[0] + src[3]*dest[1] - src[1]*dest[3]; k = src[0]*dest[3] + src[3]*dest[0] + src[1]*dest[2] - src[2]*dest[1]; dest[0] = r; dest[1] = i; dest[2] = j; dest[3] = k; } /* Sets the 'act' flag for pieces which will undergo the rotation. */ static void flag_pieces(piece_t pieces[27], int axis, int side) { int i, j; float q[4]; for(i = 0; i < 27; i++) { q[0] = 0; q[1] = pieces[i].pos[0]; q[2] = pieces[i].pos[1]; q[3] = pieces[i].pos[2]; mult_quat(pieces[i].qr, q); for(j = 1; j < 4; j++) q[j] = -q[j]; mult_quat(pieces[i].qr, q); for(j = 1; j < 4; j++) q[j] = -q[j]; if(fabs(q[axis] - side) < 0.1) pieces[i].act = True; else pieces[i].act = False; } } /* "Rounds" the value to the nearest from the set {0, +-1/2, +-1/sqrt(2), +-1}. * It is guaranteed to be pretty close to one when this function is called. */ static float settle_value(float v) { if(v > 0.9) return 1; else if(v < -0.9) return -1; else if(v > 0.6) return M_SQRT1_2; else if(v < -0.6) return -M_SQRT1_2; else if(v > 0.4) return 0.5; else if(v < -0.4) return -0.5; else return 0; } static void randomize(rubikblocks_conf *cp) { int axis, side; int i, j; for(i = 0; i < SHUFFLE; i++) { axis = (random()%3)+1; side = rnd01()*2-1; flag_pieces(cp->pieces, axis, side); for(j = 1; j < 4; j++) cp->qfram[j] = 0; cp->qfram[0] = M_SQRT1_2; cp->qfram[axis] = M_SQRT1_2; for(j = 0; j < 27; j++) { if(cp->pieces[j].act) mult_quat(cp->qfram, cp->pieces[j].qr); } } } static void finish(rubikblocks_conf *cp) { static int axis = 1; int side, angle; int i, j; if(cp->pause) { switch(axis) { case 1: axis = rnd01()+2; break; case 2: axis = 2*rnd01()+1; break; default: axis = rnd01()+1; } side = rnd01()*2-1; angle = rnd01()+1; flag_pieces(cp->pieces, axis, side); cp->pause = False; cp->tmax = 90.0*angle; for(i = 1; i < 4; i++) cp->qfram[i] = 0; cp->qfram[0] = cos(tspeed*M_PI/360); cp->qfram[axis] = sin((rnd01()*2-1)*tspeed*M_PI/360); } else { for(i = 0; i < 27; i++) { for(j = 0; j < 4; j++) { cp->pieces[i].qr[j] = settle_value(cp->pieces[i].qr[j]); } } cp->pause = True; cp->tmax = twait; } cp->t = 0; } static Bool draw_main(ModeInfo *mi, rubikblocks_conf *cp) { int i; double x, y, z; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); get_position(cp->rot, &x, &y, &z, !cp->button_down); glTranslatef((x-0.5)*6, (y-0.5)*6, -20); gltrackball_rotate(cp->trackball); get_rotation(cp->rot, &x, &y, &z, !cp->button_down); glRotatef(x*360, 1, 0, 0); glRotatef(y*360, 0, 1, 0); glRotatef(z*360, 0, 0, 1); glScalef(size, size, size); # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */ { GLfloat h = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi); int o = (int) current_device_rotation(); if (o != 0 && o != 180 && o != -180) glScalef (1/h, 1/h, 1/h); } # endif if(cp->wire) glColor3f(0.7, 0.7, 0.7); if(!cp->pause) for(i = 0; i < 27; i++) if(cp->pieces[i].act) mult_quat(cp->qfram, cp->pieces[i].qr); for(i = 0; i < 27; i++) { glPushMatrix(); if(fabs(cp->pieces[i].qr[0]) < 1) glRotatef(360/M_PI*acos(cp->pieces[i].qr[0]), cp->pieces[i].qr[1], cp->pieces[i].qr[2], cp->pieces[i].qr[3]); glCallList(cp->list_base + i); glPopMatrix(); } if((cp->t += tspeed) > cp->tmax) finish(cp); return True; } static void draw_horz_line(rubikblocks_conf *cp, int x1, int x2, int y) { int x, y0 = y, w; if(y < BORDER) y = -y; else y = -BORDER; for(; y < BORDER; y++) { if(y0+y >= TEX_HEIGHT) break; w = y*y*255/BORDER2; for(x = x1; x <= x2; x++) if(cp->texture[y0+y][x]>w) cp->texture[y0+y][x] = w; } } static void draw_vert_line(rubikblocks_conf *cp, int x, int y1, int y2) { int x0 = x, y, w; if(x= TEX_WIDTH) break; w = x*x*255/BORDER2; for(y = y1; y <= y2; y++) if(cp->texture[y][x0+x]>w) cp->texture[y][x0+x] = w; } } static void make_texture(rubikblocks_conf *cp) { int x, y; for(y = 0; y < TEX_HEIGHT; y++) for(x = 0; x < TEX_WIDTH; x++) cp->texture[y][x] = 255; draw_horz_line(cp, 0, TEX_WIDTH-1, 0); draw_horz_line(cp, 0, TEX_WIDTH-1, TEX_HEIGHT-1); draw_vert_line(cp, 0, 0, TEX_HEIGHT-1); draw_vert_line(cp, TEX_WIDTH-1, 0, TEX_HEIGHT-1); } /* These simple transforms make the actual shape of the pieces. The parameters * A, B and C affect the excentricity of the pieces in each direction. */ static float fx(float x) { const float A = 0.5; if(x > 1.4) return 1.5 - A; else if(x < -1.4) return -1.5 - A; else return x; } static float fy(float y) { const float B = 0.25; if(y > 1.4) return 1.5 - B; else if(y < -1.4) return -1.5 - B; else return y; } static float fz(float z) { const float C = 0.0; if(z > 1.4) return 1.5 - C; else if(z < -1.4) return -1.5 - C; else return z; } static void init_lists(rubikblocks_conf *cp) { GLuint base; int i; float x, y, z; base = cp->list_base = glGenLists(27); for(i = 0; i < 27; i++) { x = cp->pieces[i].pos[0]; y = cp->pieces[i].pos[1]; z = cp->pieces[i].pos[2]; glNewList(base+i, GL_COMPILE); glBegin(GL_QUAD_STRIP); glNormal3f(1, 0, 0); glTexCoord2f(0, 0); glVertex3f(fx(x+0.5), fy(y-0.5), fz(z-0.5)); glTexCoord2f(0, 1); glVertex3f(fx(x+0.5), fy(y+0.5), fz(z-0.5)); glTexCoord2f(1, 0); glVertex3f(fx(x+0.5), fy(y-0.5), fz(z+0.5)); glTexCoord2f(1, 1); glVertex3f(fx(x+0.5), fy(y+0.5), fz(z+0.5)); glNormal3f(0, 0, 1); glTexCoord2f(0, 0); glVertex3f(fx(x-0.5), fy(y-0.5), fz(z+0.5)); glTexCoord2f(0, 1); glVertex3f(fx(x-0.5), fy(y+0.5), fz(z+0.5)); glNormal3f(-1, 0, 0); glTexCoord2f(1, 0); glVertex3f(fx(x-0.5), fy(y-0.5), fz(z-0.5)); glTexCoord2f(1, 1); glVertex3f(fx(x-0.5), fy(y+0.5), fz(z-0.5)); glNormal3f(0, 0, -1); glTexCoord2f(0, 0); glVertex3f(fx(x+0.5), fy(y-0.5), fz(z-0.5)); glTexCoord2f(0, 1); glVertex3f(fx(x+0.5), fy(y+0.5), fz(z-0.5)); glEnd(); glBegin(GL_QUADS); glNormal3f(0, 1, 0); glTexCoord2f(0, 0); glVertex3f(fx(x+0.5), fy(y+0.5), fz(z+0.5)); glTexCoord2f(0, 1); glVertex3f(fx(x+0.5), fy(y+0.5), fz(z-0.5)); glTexCoord2f(1, 1); glVertex3f(fx(x-0.5), fy(y+0.5), fz(z-0.5)); glTexCoord2f(1, 0); glVertex3f(fx(x-0.5), fy(y+0.5), fz(z+0.5)); glNormal3f(0, -1, 0); glTexCoord2f(0, 0); glVertex3f(fx(x+0.5), fy(y-0.5), fz(z-0.5)); glTexCoord2f(0, 1); glVertex3f(fx(x+0.5), fy(y-0.5), fz(z+0.5)); glTexCoord2f(1, 1); glVertex3f(fx(x-0.5), fy(y-0.5), fz(z+0.5)); glTexCoord2f(1, 0); glVertex3f(fx(x-0.5), fy(y-0.5), fz(z-0.5)); glEnd(); glEndList(); } } /* It looks terrible... FIXME: any other ideas, maybe some anisotropic filtering? */ /*#define MIPMAP*/ static void init_gl(ModeInfo *mi) { rubikblocks_conf *cp = &rubikblocks[MI_SCREEN(mi)]; #ifdef MIPMAP int status; #endif cp->wire = MI_IS_WIREFRAME(mi); # ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */ cp->wire = 0; # endif if(MI_IS_MONO(mi)) tex = False; if(cp->wire) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); return; } glClearDepth(1.0); glDrawBuffer(GL_BACK); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glShadeModel(GL_FLAT); glDepthFunc(GL_LESS); glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse); glLightfv(GL_LIGHT0, GL_POSITION, position0); glLightfv(GL_LIGHT1, GL_AMBIENT, ambient); glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse); glLightfv(GL_LIGHT1, GL_POSITION, position1); glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHT0); glEnable(GL_LIGHT1); glEnable(GL_LIGHTING); glEnable(GL_NORMALIZE); glEnable(GL_COLOR_MATERIAL); glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_ambient); glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_specular); glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess); if (!tex) return; glEnable(GL_TEXTURE_2D); #ifdef MIPMAP clear_gl_error(); status = gluBuild2DMipmaps(GL_TEXTURE_2D, 1, TEX_WIDTH, TEX_HEIGHT, GL_LUMINANCE, GL_UNSIGNED_BYTE, cp->texture); if (status) { const char *s = (char *)gluErrorString(status); fprintf (stderr, "%s: error mipmapping texture: %s\n", progname, (s?s:"(unknown)")); exit (1); } check_gl_error("mipmapping"); #else glTexImage2D(GL_TEXTURE_2D, 0, 1, TEX_WIDTH, TEX_HEIGHT, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, cp->texture); #endif glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); #ifdef MIPMAP glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); #else glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); #endif } static void init_cp(rubikblocks_conf *cp) { int i, j, k, m; cp->pause = True; cp->t = 0.0; cp->tmax = twait; for(i = -1, m = 0; i <= 1; i++) for(j = -1; j <= 1; j++) for(k = -1; k <= 1; k++) { cp->pieces[m].pos[0] = k; cp->pieces[m].pos[1] = j; cp->pieces[m].pos[2] = i; cp->pieces[m].qr[0] = 1; cp->pieces[m].qr[1] = 0; cp->pieces[m].qr[2] = 0; cp->pieces[m].qr[3] = 0; m++; } cp->rot = make_rotator(spin?spinspeed:0, spin?spinspeed:0, spin?spinspeed:0, 0.1, wander?wspeed:0, True); cp->trackball = gltrackball_init(True); if(rndstart) randomize(cp); } /*************************************************************************/ ENTRYPOINT void reshape_rubikblocks(ModeInfo *mi, int width, int height) { rubikblocks_conf *cp = &rubikblocks[MI_SCREEN(mi)]; int y = 0; if (width > height * 5) { /* tiny window: show middle */ height = width; y = -height/2; } if(!height) height = 1; cp->ratio = (GLfloat)width/(GLfloat)height; glViewport(0, y, (GLint) width, (GLint) height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(30.0, cp->ratio, 1.0, 100.0); glMatrixMode(GL_MODELVIEW); glClear(GL_COLOR_BUFFER_BIT); } ENTRYPOINT void init_rubikblocks(ModeInfo *mi) { rubikblocks_conf *cp; MI_INIT(mi, rubikblocks); cp = &rubikblocks[MI_SCREEN(mi)]; if(tex) make_texture(cp); if ((cp->glx_context = init_GL(mi)) != NULL) { init_gl(mi); init_cp(cp); init_lists(cp); reshape_rubikblocks(mi, MI_WIDTH(mi), MI_HEIGHT(mi)); } else { MI_CLEARWINDOW(mi); } } ENTRYPOINT void draw_rubikblocks(ModeInfo * mi) { Display *display = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); rubikblocks_conf *cp; if (!rubikblocks) return; cp = &rubikblocks[MI_SCREEN(mi)]; MI_IS_DRAWN(mi) = True; if (!cp->glx_context) return; mi->polygon_count = 0; glXMakeCurrent(display, window, *cp->glx_context); if (!draw_main(mi, cp)) { MI_ABORT(mi); return; } if (MI_IS_FPS(mi)) do_fps (mi); glFlush(); glXSwapBuffers(display, window); } #ifndef STANDALONE ENTRYPOINT void change_rubikblocks(ModeInfo * mi) { rubikblocks_conf *cp = &rubikblocks[MI_SCREEN(mi)]; if (!cp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *cp->glx_context); init_gl(mi); } #endif /* !STANDALONE */ ENTRYPOINT Bool rubikblocks_handle_event (ModeInfo *mi, XEvent *event) { rubikblocks_conf *cp = &rubikblocks[MI_SCREEN(mi)]; if (gltrackball_event_handler (event, cp->trackball, MI_WIDTH (mi), MI_HEIGHT (mi), &cp->button_down)) return True; return False; } ENTRYPOINT void free_rubikblocks (ModeInfo *mi) { rubikblocks_conf *cp = &rubikblocks[MI_SCREEN(mi)]; if (!cp->glx_context) return; glXMakeCurrent (MI_DISPLAY(mi), MI_WINDOW(mi), *cp->glx_context); if (cp->trackball) gltrackball_free (cp->trackball); glDeleteLists (cp->list_base, 27); } XSCREENSAVER_MODULE ("RubikBlocks", rubikblocks) #endif