/* handsy, Copyright (c) 2018 Jamie Zawinski * * 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. */ #define DEFAULTS "*delay: 30000 \n" \ "*count: 2 \n" \ ".foreground: #8888CC" "\n" \ "*groundColor: #0000FF" "\n" \ "*showFPS: False \n" \ "*wireframe: False \n" # define release_hands 0 #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #include "xlockmore.h" #include "sphere.h" #include "tube.h" #include "rotator.h" #include "gltrackball.h" #include "gllist.h" #include #ifdef USE_GL /* whole file */ #define DEF_SPEED "1.0" #define DEF_SPIN "XY" #define DEF_WANDER "True" #define DEF_FACE_FRONT "True" #define DEF_DEBUG "False" extern const struct gllist *handsy_model_finger_distal, *handsy_model_finger_intermediate, *handsy_model_finger_proximal, *handsy_model_finger_metacarpal, *handsy_model_thumb_distal, *handsy_model_thumb_proximal, *handsy_model_thumb_metacarpal, *handsy_model_palm; static struct gllist *ground = 0; static const struct gllist * const *all_objs[] = { &handsy_model_finger_distal, &handsy_model_finger_intermediate, &handsy_model_finger_proximal, &handsy_model_finger_metacarpal, &handsy_model_thumb_distal, &handsy_model_thumb_proximal, &handsy_model_thumb_metacarpal, &handsy_model_palm, (const struct gllist * const *) &ground }; #define FINGER_DISTAL 0 #define FINGER_INTERMEDIATE 1 #define FINGER_PROXIMAL 2 #define FINGER_METACARPAL 3 #define THUMB_DISTAL 4 #define THUMB_PROXIMAL 5 #define THUMB_METACARPAL 6 #define PALM 7 #define GROUND 8 /* 'hand_geom' describes the position and extent of the various joints. 'hand' describes the current flexion of the joints in that model. 'hand_anim' is a list of positions and timings describing an animation. 'hands_configuration' is the usual global state structure. */ typedef struct { double min, max; /* +- pi */ } joint; typedef struct { joint bones[4]; joint base; } finger; typedef struct { finger fingers[5]; joint palm; joint wrist1; joint wrist2; } hand_geom; static const hand_geom human_hand = { {{{{ 0.0, 1.6 }, /* thumb distal */ { 0.0, 1.6 }, /* thumb proximal */ { 0.0, 1.6 }, /* thumb metacarpal */ { 0.0, 0.0 }}, /* none */ { -1.70, 0.00 }}, {{{ -0.2, 1.6 }, /* index distal */ { -0.2, 1.6 }, /* index intermediate */ { -0.2, 1.6 }, /* index proximal */ { 0.0, 0.0 }}, /* index metacarpal */ { -0.25, 0.25 }}, {{{ -0.2, 1.6 }, /* middle distal */ { -0.2, 1.6 }, /* middle intermediate */ { -0.2, 1.6 }, /* middle proximal */ { 0.0, 0.0 }}, /* middle metacarpal */ { -0.25, 0.25 }}, {{{ -0.2, 1.6 }, /* ring distal */ { -0.2, 1.6 }, /* ring intermediate */ { -0.2, 1.6 }, /* ring proximal */ { 0.0, 0.0 }}, /* ring metacarpal */ { -0.25, 0.25 }}, {{{ -0.2, 1.6 }, /* pinky distal */ { -0.2, 1.6 }, /* pinky intermediate */ { -0.2, 1.6 }, /* pinky proximal */ { 0.0, 0.0 }}, /* pinky metacarpal */ { -0.25, 0.25 }}}, { -0.7, 1.5 }, /* palm (wrist up/down) */ { -M_PI, M_PI }, /* wrist left/right */ { -M_PI, M_PI }, /* wrist rotate */ }; typedef struct { double joint[countof(human_hand.fingers)] /* +- pi */ [countof(human_hand.fingers[0].bones)]; double base[countof(human_hand.fingers)]; double wrist[3]; /* up/down, left/right, rotate */ double pos[3]; /* XYZ */ Bool sinister; double alpha; } hand; typedef struct { const hand * const dest; double duration, pause; double pos[3], rot[3]; /* XYZ */ } hand_anim; typedef struct { const hand_anim * const pair[2]; /* L/R */ double delay; /* Delay added to R */ } hand_anim_pair; typedef struct { GLXContext *glx_context; rotator *rot, *rot2; trackball_state *trackball; Bool spinx, spiny, spinz; Bool button_down_p; const hand_geom *geom; GLuint *dlists; int nhands; struct { const hand_anim *anim; int anim_hands; /* frames in animation */ int anim_hand; /* pos in anim, L/R */ double anim_start, anim_time; double tick; double delay; } pair[2]; struct { hand from, to, current; } *hands; GLfloat color[4]; Bool ringp; } hands_configuration; #include "handsy_anim.h" static hands_configuration *bps = NULL; static GLfloat speed; static char *do_spin; static Bool do_wander; static Bool face_front_p; static Bool debug_p; static XrmOptionDescRec opts[] = { { "-speed", ".speed", XrmoptionSepArg, 0 }, { "-spin", ".spin", XrmoptionSepArg, 0 }, { "+spin", ".spin", XrmoptionNoArg, "" }, { "-wander", ".wander", XrmoptionNoArg, "True" }, { "+wander", ".wander", XrmoptionNoArg, "False" }, { "-front", ".faceFront", XrmoptionNoArg, "True" }, { "+front", ".faceFront", XrmoptionNoArg, "False" }, { "-debug", ".debug", XrmoptionNoArg, "True" }, { "+debug", ".debug", XrmoptionNoArg, "False" }, }; static argtype vars[] = { {&do_spin, "spin", "Spin", DEF_SPIN, t_String}, {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool}, {&face_front_p, "faceFront", "FaceFront", DEF_FACE_FRONT, t_Bool}, {&speed, "speed", "Speed", DEF_SPEED, t_Float}, {&debug_p, "debug", "Debug", DEF_DEBUG, t_Bool}, }; ENTRYPOINT ModeSpecOpt hands_opts = {countof(opts), opts, countof(vars), vars, NULL}; /* Returns the current time in seconds as a double. */ static double double_time (void) { struct timeval now; # ifdef GETTIMEOFDAY_TWO_ARGS struct timezone tzp; gettimeofday(&now, &tzp); # else gettimeofday(&now); # endif return (now.tv_sec + ((double) now.tv_usec * 0.000001)); } static double constrain_joint (double v, double min, double max) { if (v < min) v = min; else if (v > max) v = max; return v; } static void draw_hand (ModeInfo *mi, hand *h) { hands_configuration *bp = &bps[MI_SCREEN(mi)]; int wire = MI_IS_WIREFRAME(mi); int finger; int off = h->sinister ? -1 : 1; int nfingers = countof (bp->geom->fingers); int nbones = countof (bp->geom->fingers[0].bones); glLineWidth (1); glPushMatrix(); glTranslatef (off * h->pos[0], h->pos[1], h->pos[2]); glRotatef (h->wrist[1] * 180 / M_PI * -off, 0, 1, 0); glRotatef (h->wrist[2] * 180 / M_PI * -off, 0, 0, 1); glRotatef (h->wrist[0] * 180 / M_PI, 1, 0, 0); bp->color[3] = h->alpha; glColor4fv (bp->color); glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bp->color); if (!wire) glEnable (GL_BLEND); glPushMatrix(); if (h->sinister) { glScalef (-1, 1, 1); glFrontFace (GL_CW); } else glFrontFace (GL_CCW); glCallList (bp->dlists[PALM]); glPopMatrix(); for (finger = 0; finger < nfingers; finger++) { int bone = nbones - 2; glPushMatrix(); if (finger == 0) /* thumb */ { glTranslatef (off * 0.113, -0.033, 0.093); glRotatef (off * 45, 0, 1, 0); if (h->sinister) glRotatef (180, 0, 0, 1); glRotatef (off * h->base[finger] * -180 / M_PI, 1, 0, 0); bone--; glFrontFace (GL_CCW); glCallList (bp->dlists[THUMB_METACARPAL]); glPushMatrix(); glScalef (1, -1, 1); glFrontFace (GL_CW); glCallList (bp->dlists[THUMB_METACARPAL]); glPopMatrix(); glTranslatef (0, 0, 0.1497); glRotatef (h->joint[finger][bone] * -180 / M_PI, 0, 1, 0); bone--; glFrontFace (GL_CCW); glCallList (bp->dlists[THUMB_PROXIMAL]); glPushMatrix(); glScalef (1, -1, 1); glFrontFace (GL_CW); glCallList (bp->dlists[THUMB_PROXIMAL]); glPopMatrix(); glTranslatef (0, 0, 0.1212); glRotatef (h->joint[finger][bone] * -180 / M_PI, 0, 1, 0); bone--; glFrontFace (GL_CCW); glCallList (bp->dlists[THUMB_DISTAL]); glPushMatrix(); glScalef (1, -1, 1); glFrontFace (GL_CW); glCallList (bp->dlists[THUMB_DISTAL]); glPopMatrix(); } else { switch (finger) { case 1: /* index */ glTranslatef (off * 0.135, 0.004, 0.26835); glRotatef (off * 4, 0, 1, 0); break; case 2: /* middle */ glTranslatef (off * 0.046, 0.004, 0.27152); glRotatef (off * 1, 0, 1, 0); break; case 3: /* ring */ glTranslatef (off * -0.046, 0.004, 0.25577); glRotatef (off * -1, 0, 1, 0); break; case 4: /* pinky */ glTranslatef (off * -0.135, 0.004, 0.22204); glRotatef (off * -4, 0, 1, 0); break; default: abort(); break; } glRotatef (90, 0, 0, 1); glFrontFace (GL_CCW); glCallList (bp->dlists[FINGER_METACARPAL]); glPushMatrix(); glScalef (1, -1, 1); glFrontFace (GL_CW); glCallList (bp->dlists[FINGER_METACARPAL]); glPopMatrix(); glTranslatef (0, 0, 0.1155); glRotatef (off * h->base[finger] * -180 / M_PI, 1, 0, 0); glRotatef (h->joint[finger][bone] * -180 / M_PI, 0, 1, 0); bone--; glFrontFace (GL_CCW); glCallList (bp->dlists[FINGER_PROXIMAL]); glPushMatrix(); glScalef (1, -1, 1); glFrontFace (GL_CW); glCallList (bp->dlists[FINGER_PROXIMAL]); glPopMatrix(); glTranslatef (0, 0, 0.1815); glRotatef (h->joint[finger][bone] * -180 / M_PI, 0, 1, 0); bone--; glFrontFace (GL_CCW); glCallList (bp->dlists[FINGER_INTERMEDIATE]); glPushMatrix(); glScalef (1, -1, 1); glFrontFace (GL_CW); glCallList (bp->dlists[FINGER_INTERMEDIATE]); glPopMatrix(); glTranslatef (0, 0, 0.1003); glRotatef (h->joint[finger][bone] * -180 / M_PI, 0, 1, 0); bone--; glFrontFace (GL_CCW); glCallList (bp->dlists[FINGER_DISTAL]); glPushMatrix(); glScalef (1, -1, 1); glFrontFace (GL_CW); glCallList (bp->dlists[FINGER_DISTAL]); glPopMatrix(); } glPopMatrix(); } glPopMatrix(); if (h->sinister && bp->ringp) { GLfloat color[] = { 1.0, 0.4, 0.4, 1 }; GLfloat center = 0.4; GLfloat th; GLfloat r = center - h->pos[0] + 0.1; GLfloat min = 0.22; if (r < min) r = min; glPushMatrix(); glTranslatef (-center, -0.28, 0.5); glRotatef (h->wrist[2] * 180 / M_PI * -off, 0, 0, 1); glRotatef (h->wrist[0] * 180 / M_PI, 1, 0, 0); glColor4fv (color); glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color); glDisable (GL_LIGHTING); glLineWidth (8); glBegin (GL_LINE_LOOP); for (th = 0; th < M_PI * 2; th += M_PI / 180) glVertex3f (r * cos(th), r * sin(th), 0); glEnd(); if (! wire) glEnable (GL_LIGHTING); glPopMatrix(); } glDisable (GL_BLEND); } static void parse_color (ModeInfo *mi, char *key, GLfloat color[4]) { XColor xcolor; char *string = get_string_resource (mi->dpy, key, "Color"); if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor)) { fprintf (stderr, "%s: unparsable color in %s: %s\n", progname, key, string); exit (1); } free (string); color[0] = xcolor.red / 65536.0; color[1] = xcolor.green / 65536.0; color[2] = xcolor.blue / 65536.0; color[3] = 1; } static int draw_ground (ModeInfo *mi) { int wire = MI_IS_WIREFRAME(mi); GLfloat i, j, k; /* When using fog, iOS apparently doesn't like lines or quads that are really long, and extend very far outside of the scene. Maybe? If the length of the line (cells * cell_size) is greater than 25 or so, lines that are oriented steeply away from the viewer tend to disappear (whether implemented as GL_LINES or as GL_QUADS). So we do a bunch of smaller grids instead of one big one. */ int cells = 30; GLfloat cell_size = 0.8; int points = 0; int grids = 12; GLfloat color[4]; if (wire) glLineWidth (1); parse_color (mi, "groundColor", color); glPushMatrix(); glScalef (0.2, 0.2, 0.2); glRotatef (frand(90), 0, 0, 1); if (!wire) { GLfloat fog_color[4] = { 0, 0, 0, 1 }; glLineWidth (4); glEnable (GL_LINE_SMOOTH); glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable (GL_BLEND); glFogi (GL_FOG_MODE, GL_EXP2); glFogfv (GL_FOG_COLOR, fog_color); glFogf (GL_FOG_DENSITY, 0.015); glFogf (GL_FOG_START, -cells/2 * cell_size * grids); glEnable (GL_FOG); } glColor4fv (color); glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color); glTranslatef (-cells * grids * cell_size / 2, -cells * grids * cell_size / 2, 0); for (j = 0; j < grids; j++) { glPushMatrix(); for (k = 0; k < grids; k++) { glBegin (GL_LINES); for (i = -cells/2; i < cells/2; i++) { GLfloat a = i * cell_size; GLfloat b = cells/2 * cell_size; glVertex3f (a, -b, 0); glVertex3f (a, b, 0); points++; glVertex3f (-b, a, 0); glVertex3f (b, a, 0); points++; } glEnd(); glTranslatef (cells * cell_size, 0, 0); } glPopMatrix(); glTranslatef (0, cells * cell_size, 0); } if (!wire) { glDisable (GL_LINE_SMOOTH); glDisable (GL_BLEND); glDisable (GL_FOG); } glPopMatrix(); return points; } ENTRYPOINT void reshape_hands (ModeInfo *mi, int width, int height) { GLfloat h = (GLfloat) height / (GLfloat) width; int y = 0; 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 hands_handle_event (ModeInfo *mi, XEvent *event) { hands_configuration *bp = &bps[MI_SCREEN(mi)]; Bool ok = False; if (gltrackball_event_handler (event, bp->trackball, MI_WIDTH (mi), MI_HEIGHT (mi), &bp->button_down_p)) ok = True; else if (debug_p && event->type == KeyPress) { int nfingers = countof (bp->geom->fingers); int nbones = countof (bp->geom->fingers[0].bones); KeySym keysym; char c = 0; Bool tiltp = False; double delta = 0.02; double sign = 1; int i, j, k; XLookupString (&event->xkey, &c, 1, &keysym, 0); for (i = 0; i < bp->nhands; i++) { hand *h = &bp->hands[i].current; Bool modp = !!event->xkey.state; switch (keysym) { case XK_Up: ok = True; h->pos[(modp ? 1 : 2)] += delta; break; case XK_Down: ok = True; h->pos[(modp ? 1 : 2)] -= delta; break; case XK_Right: ok = True; h->pos[0] += delta; break; case XK_Left: ok = True; h->pos[0] -= delta; break; default: break; } switch (c) { case '?': case '/': ok = True; fprintf (stderr, "\n" " Open fingers: 12345 (1 = pinky, 5 = thumb)\n" " Close fingers: QWERT\n" " Tilt left: ASDFG\n" " Tilt right: ZXCVB\n" " Bend wrist: UJ (up/down)\n" " Bend wrist: IK (left/right)\n" " Rotate wrist: OL\n" " Move origin: arrow keys: XZ plane; shift: XY plane\n" " Tab: print current state to stdout\n" " 0: Reset\n" " ?: This\n\n"); return ok; break; case '1': case '!': j = 4; sign = -1; goto FINGER; case '2': case '@': j = 3; sign = -1; goto FINGER; case '3': case '#': j = 2; sign = -1; goto FINGER; case '4': case '$': j = 1; sign = -1; goto FINGER; case '5': case '%': j = 0; sign = -1; goto FINGER; case 'q': case 'Q': j = 4; sign = 1; goto FINGER; case 'w': case 'W': j = 3; sign = 1; goto FINGER; case 'e': case 'E': j = 2; sign = 1; goto FINGER; case 'r': case 'R': j = 1; sign = 1; goto FINGER; case 't': case 'T': j = 0; sign = 1; goto FINGER; case 'a': case 'A': tiltp = True; j = 4; sign = 1; goto FINGER; case 's': case 'S': tiltp = True; j = 3; sign = 1; goto FINGER; case 'd': case 'D': tiltp = True; j = 2; sign = 1; goto FINGER; case 'f': case 'F': tiltp = True; j = 1; sign = 1; goto FINGER; case 'g': case 'G': tiltp = True; j = 0; sign = 1; goto FINGER; case 'z': case 'Z': tiltp = True; j = 4; sign = -1; goto FINGER; case 'x': case 'X': tiltp = True; j = 3; sign = -1; goto FINGER; case 'c': case 'C': tiltp = True; j = 2; sign = -1; goto FINGER; case 'v': case 'V': tiltp = True; j = 1; sign = -1; goto FINGER; case 'b': case 'B': tiltp = True; j = 0; sign = -1; goto FINGER; FINGER: ok = True; if (tiltp) h->base[j] = constrain_joint (h->base[j] + sign * delta, bp->geom->fingers[j].base.min, bp->geom->fingers[j].base.max); else for (k = 0; k < nbones; k++) h->joint[j][k] = constrain_joint (h->joint[j][k] + sign * delta, bp->geom->fingers[j].bones[k].min, bp->geom->fingers[j].bones[k].max); break; case 'u': case 'U': ok = True; h->wrist[0] -= delta; break; case 'j': case 'J': ok = True; h->wrist[0] += delta; break; case 'i': case 'I': ok = True; h->wrist[1] += delta; break; case 'k': case 'K': ok = True; h->wrist[1] -= delta; break; case 'o': case 'O': ok = True; h->wrist[2] -= delta; break; case 'l': case 'L': ok = True; h->wrist[2] += delta; break; case '0': case ')': ok = True; for (j = 0; j < nfingers; j++) { h->base[j] = 0; for (k = 0; k < nbones; k++) h->joint[j][k] = 0; } for (j = 0; j < 3; j++) h->wrist[j] = 0; for (j = 0; j < 3; j++) h->pos[j] = 0; break; case '\t': ok = True; fprintf (stdout, "\nstatic const hand H = {\n {"); for (i = 0; i < nfingers; i++) { if (i > 0) fprintf (stdout, " "); fprintf (stdout, "{"); for (j = 0; j < nbones; j++) { double v = h->joint[i][j]; if (i == 0 && j == 3) v = 0; /* no thumb intermediate */ if (j == 0) fprintf (stdout, " "); fprintf (stdout, "%.2f", v); if (j < nbones-1) fprintf (stdout, ", "); } fprintf (stdout, " }"); if (i < nfingers-1) fprintf (stdout, ",\n"); } fprintf (stdout, "},\n { "); for (i = 0; i < nfingers; i++) { fprintf (stdout, "%.2f", h->base[i]); if (i < nfingers-1) fprintf (stdout, ", "); } fprintf (stdout, " },\n"); fprintf (stdout, " { %.2f, %.2f, %.2f },\n", h->wrist[0], h->wrist[1], h->wrist[2]); fprintf (stdout, " { %.2f, %.2f, %.2f },\n", h->pos[0], h->pos[1], h->pos[2]); fprintf (stdout, " True\n};\n\n"); fflush (stdout); return ok; break; default: break; } } } return ok; } ENTRYPOINT void init_hands (ModeInfo *mi) { hands_configuration *bp; int wire = MI_IS_WIREFRAME(mi); hand def[2]; int i; MI_INIT (mi, bps); bp = &bps[MI_SCREEN(mi)]; bp->glx_context = init_GL(mi); reshape_hands (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); bp->pair[0].tick = bp->pair[1].tick = 1.0; bp->geom = &human_hand; bp->nhands = MI_COUNT(mi); if (bp->nhands <= 0) bp->nhands = 1; if (bp->nhands & 1) bp->nhands++; /* Even number */ if (debug_p) { bp->nhands = 1; do_spin = ""; do_wander = False; } bp->hands = calloc (bp->nhands, sizeof(*bp->hands)); { double spin_speed = 0.5 * speed; double wander_speed = 0.005 * speed; double tilt_speed = 0.001 * speed; double spin_accel = 0.5; char *s = do_spin; while (*s) { if (*s == 'x' || *s == 'X') bp->spinx = True; else if (*s == 'y' || *s == 'Y') bp->spiny = True; else if (*s == 'z' || *s == 'Z') bp->spinz = True; else if (*s == '0') ; else { fprintf (stderr, "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n", progname, do_spin); exit (1); } s++; } bp->rot = make_rotator (bp->spinx ? spin_speed : 0, bp->spiny ? spin_speed : 0, bp->spinz ? spin_speed : 0, spin_accel, do_wander ? wander_speed : 0, False); bp->rot2 = (face_front_p ? make_rotator (0, 0, 0, 0, tilt_speed, True) : 0); bp->trackball = gltrackball_init (False); } /* Set default hand to the last hand in the animation list. */ for (i = 0; i <= 1; i++) { int j; for (j = 0; ; j++) if (!all_hand_anims[j+1].pair[i]) { if (! all_hand_anims[j].pair[i]) abort(); def[i] = *all_hand_anims[j].pair[i]->dest; if (debug_p) def[i].alpha = 1; else { def[i].pos[1] = 5; /* off screen */ def[i].pos[2] = 5; } break; } } for (i = 0; i < bp->nhands; i++) { int sinister = (i & 1); bp->hands[i].to = def[sinister]; bp->hands[i].to.sinister = sinister; bp->hands[i].from = bp->hands[i].to; bp->hands[i].current = bp->hands[i].to; } glFrontFace(GL_CW); bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint)); for (i = 0; i < countof(all_objs); i++) bp->dlists[i] = glGenLists (1); for (i = 0; i < countof(all_objs); i++) { const struct gllist *gll = *all_objs[i]; GLfloat s = 0.1; glNewList (bp->dlists[i], GL_COMPILE); switch (i) { case GROUND: if (! ground) ground = (struct gllist *) calloc (1, sizeof(*ground)); ground->points = draw_ground (mi); break; default: glPushMatrix(); glScalef (s, s, s); renderList (gll, wire); glPopMatrix(); break; } glEndList (); } if (!wire) { GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0}; GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0}; GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0}; GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0}; glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); 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); } parse_color (mi, "foreground", bp->color); } static void tick_hands (ModeInfo *mi) { hands_configuration *bp = &bps[MI_SCREEN(mi)]; int i, j, k, nfingers, nbones, sinister; double now = double_time(); if (debug_p) return; if (!bp->pair[0].anim && /* Both hands finished. */ !bp->pair[1].anim) /* Pick a new animation. */ { int nanims = 0; while (all_hand_anims[nanims].pair[0] || all_hand_anims[nanims].pair[1]) nanims++; i = random() % nanims; for (sinister = 0; sinister <= 1; sinister++) { bp->pair[sinister].anim = all_hand_anims[i].pair[sinister]; bp->pair[sinister].anim_hand = 0; bp->pair[sinister].anim_hands = 0; while (bp->pair[sinister].anim[bp->pair[sinister].anim_hands].dest) bp->pair[sinister].anim_hands++; bp->pair[sinister].anim_start = now; bp->pair[sinister].tick = 0; if (sinister == 1) bp->pair[sinister].delay = all_hand_anims[i].delay; } bp->ringp = (all_hand_anims[i].pair[0] == goatse_anim); for (i = 0; i < bp->nhands; i++) { sinister = bp->hands[i].from.sinister; bp->hands[i].from = bp->hands[i].current; bp->hands[i].to = *bp->pair[sinister].anim->dest; bp->hands[i].to.sinister = sinister; j = bp->pair[sinister].anim_hand; bp->hands[i].to.alpha = (bp->pair[sinister].anim == hidden_anim ? 0 : 1); /* Anim keyframes can adjust position and rotation */ for (k = 0; k < 3; k++) { bp->hands[i].to.wrist[k] += bp->pair[sinister].anim[j].rot[k]; bp->hands[i].to.pos[k] += bp->pair[sinister].anim[j].pos[k]; } } } for (sinister = 0; sinister <= 1; sinister++) { const hand_anim *h; double elapsed, duration, duration2; if (! bp->pair[sinister].anim) /* Done with this hand, not the other. */ continue; h = &bp->pair[sinister].anim[bp->pair[sinister].anim_hand]; elapsed = now - bp->pair[sinister].anim_start; duration = h->duration / speed; duration2 = duration + (bp->pair[sinister].delay + h->pause) / speed; if (elapsed > duration2 && /* Done animating and pausing this hand. */ bp->pair[sinister].tick >= 1) /* ...and painted final frame. */ { bp->pair[sinister].anim_hand++; bp->pair[sinister].tick = 1; if (bp->pair[sinister].anim_hand >= bp->pair[sinister].anim_hands) { /* Done with all steps of this hand's animation. */ bp->pair[sinister].anim = 0; for (i = 0; i < bp->nhands; i++) if (bp->hands[i].from.sinister == sinister) bp->hands[i].from = bp->hands[i].to = bp->hands[i].current; } else { /* Move to next step of animation. */ for (i = 0; i < bp->nhands; i++) { if (sinister != bp->hands[i].current.sinister) continue; j = bp->pair[sinister].anim_hand; bp->hands[i].from = bp->hands[i].current; bp->hands[i].to = *bp->pair[sinister].anim[j].dest; bp->hands[i].to.alpha = (bp->pair[sinister].anim == hidden_anim ? 0 : 1); /* Anim keyframes can adjust position and rotation */ for (k = 0; k < 3; k++) { bp->hands[i].to.wrist[k] += bp->pair[sinister].anim[j].rot[k]; bp->hands[i].to.pos[k] += bp->pair[sinister].anim[j].pos[k]; } } bp->pair[sinister].anim_start = now; bp->pair[sinister].tick = 0; bp->pair[sinister].delay = 0; } } else if (elapsed > duration) /* Done animating, still pausing. */ bp->pair[sinister].tick = 1; else /* Still animating. */ bp->pair[sinister].tick = elapsed / duration; if (bp->pair[sinister].tick > 1) bp->pair[sinister].tick = 1; /* Move the joints into position: compute 'current' between 'from' and 'to' by ratio 'tick'. */ nfingers = countof (bp->geom->fingers); nbones = countof (bp->geom->fingers[0].bones); for (i = 0; i < bp->nhands; i++) { if (bp->hands[i].current.sinister != sinister) continue; for (j = 0; j < nfingers; j++) { for (k = 0; k < nbones; k++) bp->hands[i].current.joint[j][k] = constrain_joint (bp->hands[i].from.joint[j][k] + bp->pair[sinister].tick * (bp->hands[i].to.joint[j][k] - bp->hands[i].from.joint[j][k]), bp->geom->fingers[j].bones[k].min, bp->geom->fingers[j].bones[k].max); bp->hands[i].current.base[j] = constrain_joint (bp->hands[i].from.base[j] + bp->pair[sinister].tick * (bp->hands[i].to.base[j] - bp->hands[i].from.base[j]), bp->geom->fingers[j].base.min, bp->geom->fingers[j].base.max); } j = 0; bp->hands[i].current.wrist[j] = constrain_joint (bp->hands[i].from.wrist[j] + bp->pair[sinister].tick * (bp->hands[i].to.wrist[j] - bp->hands[i].from.wrist[j]), bp->geom->palm.min, bp->geom->palm.max); j = 1; bp->hands[i].current.wrist[j] = constrain_joint (bp->hands[i].from.wrist[j] + bp->pair[sinister].tick * (bp->hands[i].to.wrist[j] - bp->hands[i].from.wrist[j]), bp->geom->wrist1.min, bp->geom->wrist1.max); j = 2; bp->hands[i].current.wrist[j] = constrain_joint (bp->hands[i].from.wrist[j] + bp->pair[sinister].tick * (bp->hands[i].to.wrist[j] - bp->hands[i].from.wrist[j]), bp->geom->wrist2.min, bp->geom->wrist2.max); for (j = 0; j < 3; j++) bp->hands[i].current.pos[j] = constrain_joint (bp->hands[i].from.pos[j] + bp->pair[sinister].tick * (bp->hands[i].to.pos[j] - bp->hands[i].from.pos[j]), -999, 999); bp->hands[i].current.alpha = bp->hands[i].from.alpha + bp->pair[sinister].tick * (bp->hands[i].to.alpha - bp->hands[i].from.alpha); } } } ENTRYPOINT void draw_hands (ModeInfo *mi) { hands_configuration *bp = &bps[MI_SCREEN(mi)]; Display *dpy = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); GLfloat s; int i; static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0}; static const GLfloat bshiny = 128.0; GLfloat bcolor[4] = { 0.7, 0.7, 1.0, 1.0 }; if (!bp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); glEnable(GL_NORMALIZE); glEnable(GL_CULL_FACE); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix (); glRotatef(current_device_rotation(), 0, 0, 1); s = 10; glScalef (s, s, s); { double x, y, z; gltrackball_rotate (bp->trackball); if (face_front_p) { double maxx = 120 / 10.0; double maxy = 55 / 10.0; double maxz = 40 / 10.0; get_position (bp->rot2, &x, &y, &z, !bp->button_down_p); if (bp->spinx) glRotatef (maxx/2 - x*maxx, 0, 1, 0); if (bp->spiny) glRotatef (maxy/2 - y*maxy, 1, 0, 0); if (bp->spinz) glRotatef (maxz/2 - z*maxz, 0, 0, 1); } else { get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p); glRotatef (x * 360, 1, 0, 0); glRotatef (y * 360, 0, 1, 0); glRotatef (z * 360, 0, 0, 1); } glRotatef (-70, 1, 0, 0); glTranslatef (0, 0, -0.5); glPushMatrix(); glRotatef ((bp->spiny ? y : bp->spinx ? x : z) * 90, 0, 0, 1); glCallList (bp->dlists[GROUND]); glPopMatrix(); get_position (bp->rot, &x, &y, &z, !bp->button_down_p); z += 1; /* Origin of hands is 1.0 above floor. */ glTranslatef((x - 0.5) * 0.8, (y - 0.5) * 1.1, (z - 0.5) * 0.2); } glMaterialfv (GL_FRONT, GL_SPECULAR, bspec); glMateriali (GL_FRONT, GL_SHININESS, bshiny); glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, bcolor); { /* Lay the pairs out in a square-ish grid, keeping pairs together. */ int rows = sqrt (bp->nhands / 2); int cols; int x, y; cols = ceil (bp->nhands / 2.0 / rows); if (bp->nhands <= 2) rows = cols = 1; if (MI_WIDTH(mi) < MI_HEIGHT(mi)) /* Portrait */ { s = 0.5; glScalef (s, s, s); } if (bp->nhands == 1) glScalef (2, 2, 2); if (cols > 1) { s = 1.0 / rows; glScalef (s, s, s); } if (bp->nhands > 1) { s = 0.8; glTranslatef (-s * rows * 1.5, -s * cols, 0); glTranslatef (s, s, 0); } i = 0; for (y = 0; y < cols; y++) for (x = 0; x < rows; x++) { glPushMatrix(); glTranslatef (x * s * 3, y * s * 2, y * s); if (i < bp->nhands) draw_hand (mi, &bp->hands[i++].current); glTranslatef (s, 0, 0); if (i < bp->nhands) draw_hand (mi, &bp->hands[i++].current); glPopMatrix(); } } glPopMatrix(); tick_hands (mi); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers(dpy, window); } ENTRYPOINT void free_hands (ModeInfo *mi) { hands_configuration *bp = &bps[MI_SCREEN(mi)]; int i; if (!bp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); if (bp->rot) free_rotator (bp->rot); if (bp->trackball) gltrackball_free (bp->trackball); if (bp->dlists) { for (i = 0; i < countof(all_objs); i++) if (glIsList(bp->dlists[i])) glDeleteLists(bp->dlists[i], 1); free (bp->dlists); } } XSCREENSAVER_MODULE_2 ("Handsy", handsy, hands) #endif /* USE_GL */