/* headroom, Copyright (c) 2020 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. * * Well, it's supposed to be Max Headroom, but I have so far been unable to * find or commission a decent 3D model of Max. So it's a skull instead. * This will have to do for now. * * Created 29-Nov-2020. */ #define DEFAULTS "*delay: 20000 \n" \ "*showFPS: False \n" \ "*skullColor: #777777" "\n" \ "*teethColor: #FFFFFF" "\n" \ "*torsoColor: #447744" "\n" \ "*torsoCapColor:#222222" "\n" \ "*gridColor1: #AA0000" "\n" \ "*gridColor2: #00FF00" "\n" \ "*gridColor3: #6666FF" "\n" \ "*wireframe: False \n" # define release_headroom 0 #define DEF_SPEED "1.0" #define DEF_SPIN "XYZ" #define DEF_WANDER "False" #include "xlockmore.h" #include #ifdef USE_GL /* whole file */ #include "gltrackball.h" #include "rotator.h" #include "gllist.h" extern const struct gllist *headroom_model_skull_half, *headroom_model_jaw_half, *headroom_model_teeth_upper_half, *headroom_model_teeth_lower_half, *headroom_model_torso_half, *headroom_model_torso_cap_half; static const struct gllist * const *all_objs[] = { &headroom_model_skull_half, &headroom_model_jaw_half, &headroom_model_teeth_upper_half, &headroom_model_teeth_lower_half, &headroom_model_torso_half, &headroom_model_torso_cap_half, }; enum { SKULL_HALF, JAW_HALF, TEETH_UPPER_HALF, TEETH_LOWER_HALF, TORSO_HALF, TORSO_CAP_HALF }; typedef struct { GLfloat x, y, z; } XYZ; typedef struct { GLXContext *glx_context; rotator *rot, *rot2, *rot3; trackball_state *trackball; Bool button_down_p; Bool spinx, spiny, spinz; XYZ head_pos; GLfloat jaw_pos; GLfloat grid_colors[3][4]; GLuint *dlists; GLfloat component_colors[countof(all_objs)][4]; } headroom_configuration; static headroom_configuration *bps = NULL; static GLfloat speed; static char *do_spin; static Bool do_wander; static XrmOptionDescRec opts[] = { { "-speed", ".speed", XrmoptionSepArg, 0 }, { "-spin", ".spin", XrmoptionSepArg, 0 }, { "+spin", ".spin", XrmoptionNoArg, "" }, { "-wander", ".wander", XrmoptionNoArg, "True" }, { "+wander", ".wander", XrmoptionNoArg, "False" }, }; static argtype vars[] = { {&speed, "speed", "Speed", DEF_SPEED, t_Float}, {&do_spin, "spin", "Spin", DEF_SPIN, t_String}, {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool}, }; ENTRYPOINT ModeSpecOpt headroom_opts = { countof(opts), opts, countof(vars), vars, NULL}; /* Window management, etc */ ENTRYPOINT void reshape_headroom (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, 1/h, 1, 500); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0, 0, 30, 0, 0, 0, 0, 1, 0); { GLfloat s = (MI_WIDTH(mi) < MI_HEIGHT(mi) ? (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi)) : 1); glScalef (s, s, s); } glClear(GL_COLOR_BUFFER_BIT); } ENTRYPOINT Bool headroom_handle_event (ModeInfo *mi, XEvent *event) { headroom_configuration *bp = &bps[MI_SCREEN(mi)]; if (gltrackball_event_handler (event, bp->trackball, MI_WIDTH (mi), MI_HEIGHT (mi), &bp->button_down_p)) return True; return False; } 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; } ENTRYPOINT void init_headroom (ModeInfo *mi) { headroom_configuration *bp; int wire = MI_IS_WIREFRAME(mi); int i; MI_INIT (mi, bps); bp = &bps[MI_SCREEN(mi)]; bp->glx_context = init_GL(mi); reshape_headroom (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); glEnable(GL_NORMALIZE); glEnable(GL_CULL_FACE); if (!wire) { GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0}; /* GLfloat amb[4] = {0.0, 0.0, 0.0, 1.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); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } { double spin_speed = 0.5; double wander_speed = 0.002 * speed; double tilt_speed = 0.005 * speed; double spin_accel = 0.5; double spin_speed_2 = 0.2 * speed; double spin_accel_2 = 0.2; double wander_speed_2 = 0.01 * speed; 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 = make_rotator (0, 0, 0, 0, tilt_speed, True); bp->rot3 = make_rotator (spin_speed_2, spin_speed_2, spin_speed_2, spin_accel_2, wander_speed_2, True); bp->trackball = gltrackball_init (False); } 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]; char *key = 0; GLfloat spec[4] = {0.4, 0.4, 0.4, 1.0}; GLfloat shiny = 80; /* 0-128 */ glNewList (bp->dlists[i], GL_COMPILE); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glMatrixMode(GL_TEXTURE); glPushMatrix(); glMatrixMode(GL_MODELVIEW); glRotatef (-90, 1, 0, 0); glBindTexture (GL_TEXTURE_2D, 0); switch (i) { case SKULL_HALF: case JAW_HALF: key = "skullColor"; break; case TEETH_UPPER_HALF: case TEETH_LOWER_HALF: key = "teethColor"; break; case TORSO_HALF: key = "torsoColor"; break; case TORSO_CAP_HALF: key = "torsoCapColor"; break; default: abort(); } parse_color (mi, key, bp->component_colors[i]); glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec); glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny); renderList (gll, wire); glMatrixMode(GL_TEXTURE); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glEndList (); } parse_color (mi, "gridColor1", bp->grid_colors[0]); parse_color (mi, "gridColor2", bp->grid_colors[1]); parse_color (mi, "gridColor3", bp->grid_colors[2]); reshape_headroom (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); } static int draw_unit_panel (ModeInfo *mi, GLfloat *color1, GLfloat *color2) { int wire = MI_IS_WIREFRAME(mi); int polys = 0; int rows = 36; GLfloat spacing = 1.0 / rows; GLfloat thickness = spacing / 8; int i; glFrontFace (GL_CCW); glBegin (wire ? GL_LINES : GL_QUADS); glNormal3f (0, 0, 1); for (i = 0; i < rows; i++) { GLfloat y = i / (GLfloat) rows + spacing/2; glColor4fv (color2); glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color2); glVertex3f (1, y, 0); glColor4fv (color1); glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color1); glVertex3f (0, y, 0); glVertex3f (0, y + thickness, 0); glColor4fv (color2); glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color2); glVertex3f (1, y + thickness, 0); polys++; } glEnd(); return polys; } static int draw_box (ModeInfo *mi) { headroom_configuration *bp = &bps[MI_SCREEN(mi)]; int polys = 0; GLfloat *c0 = bp->grid_colors[0]; GLfloat *c1 = bp->grid_colors[1]; GLfloat *c2 = bp->grid_colors[2]; glPushMatrix(); glTranslatef (-0.5, -0.5, 0.5); polys += draw_unit_panel (mi, c0, c1); glRotatef (-90, 0, 1, 0); glTranslatef (-1, 0, 0); polys += draw_unit_panel (mi, c1, c0); glRotatef (-90, 0, 1, 0); glTranslatef (-1, 0, 0); polys += draw_unit_panel (mi, c0, c1); glRotatef (-90, 0, 1, 0); glTranslatef (-1, 0, 0); polys += draw_unit_panel (mi, c1, c0); glRotatef (-90, 1, 0, 0); glTranslatef (0, 0, 1); polys += draw_unit_panel (mi, c2, c1); glRotatef (-180, 1, 0, 0); glTranslatef (0, -1, 1); polys += draw_unit_panel (mi, c1, c2); glPopMatrix(); return polys; } static int draw_component (ModeInfo *mi, int i) { headroom_configuration *bp = &bps[MI_SCREEN(mi)]; glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bp->component_colors[i]); glFrontFace (GL_CCW); glCallList (bp->dlists[i]); glPushMatrix(); glScalef (-1, 1, 1); glFrontFace (GL_CW); glCallList (bp->dlists[i]); glPopMatrix(); return 2 * (*all_objs[i])->points / 3; } ENTRYPOINT void draw_headroom (ModeInfo *mi) { headroom_configuration *bp = &bps[MI_SCREEN(mi)]; Display *dpy = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); int wire = MI_IS_WIREFRAME(mi); GLfloat s; if (!bp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix (); glRotatef(current_device_rotation(), 0, 0, 1); mi->polygon_count = 0; { double x, y, z; get_rotation (bp->rot3, &x, &y, &z, !bp->button_down_p); glPushMatrix(); glRotatef (x * 360, 1, 0, 0); glRotatef (y * 360, 0, 1, 0); glRotatef (z * 360, 0, 0, 1); get_position (bp->rot3, &x, &y, &z, !bp->button_down_p); s = 3; glScalef (1+x*s, 1+y*s, 1+z*s); s = 60; glScalef (s, s, s); if (!wire) glDisable(GL_LIGHTING); mi->polygon_count += draw_box (mi); if (!wire) glEnable(GL_LIGHTING); glPopMatrix(); glClear(GL_DEPTH_BUFFER_BIT); } { double x, y, z; get_position (bp->rot, &x, &y, &z, !bp->button_down_p); glTranslatef((x - 0.5) * 8, (y - 0.5) * 6, (z - 0.5) * 10); gltrackball_rotate (bp->trackball); { double maxx = 40; double maxy = 20; double maxz = 2; get_position (bp->rot2, &x, &y, &z, !bp->button_down_p); if (bp->spinx) glRotatef (maxy/2 - x*maxy, 1, 0, 0); if (bp->spiny) glRotatef (maxx/2 - y*maxx, 0, 1, 0); if (bp->spinz) glRotatef (maxz/2 - z*maxz, 0, 0, 1); } } glTranslatef (0, -6, 0); s = 0.03; glScalef (s, s, s); { /* head x, nod: -50 to +30 head y, turn: +- 70 head z, tilt: +- 30 jaw, open: 0 - 22 */ const XYZ head_base = { 0, 200, 0 }; const XYZ jaw_base = { 0, 270, 40 }; mi->polygon_count += draw_component (mi, TORSO_HALF); mi->polygon_count += draw_component (mi, TORSO_CAP_HALF); glTranslatef (head_base.x, head_base.y, head_base.z); glRotatef (bp->head_pos.x, 1, 0, 0); glRotatef (bp->head_pos.y, 0, 1, 0); glRotatef (bp->head_pos.z, 0, 0, 1); glTranslatef (-head_base.x, -head_base.y, -head_base.z); mi->polygon_count += draw_component (mi, SKULL_HALF); mi->polygon_count += draw_component (mi, TEETH_UPPER_HALF); glTranslatef (jaw_base.x, jaw_base.y, jaw_base.z); glRotatef (bp->jaw_pos, 1, 0, 0); glTranslatef (-jaw_base.x, -jaw_base.y, -jaw_base.z); mi->polygon_count += draw_component (mi, JAW_HALF); mi->polygon_count += draw_component (mi, TEETH_LOWER_HALF); glFrontFace (GL_CCW); } if (! bp->button_down_p) { int twitch = 200 / speed; int untwitch = 50 / speed; if (twitch < 10) twitch = 10; if (untwitch < 5) untwitch = 5; if (! (random() % twitch)) bp->head_pos.x = -20.0 + (random() % (20 + 30)); if (! (random() % twitch)) bp->head_pos.y = -50.0 + (random() % (50 * 2)); if (! (random() % twitch)) bp->head_pos.z = -30.0 + (random() % (30 * 2)); if (! (random() % twitch)) bp->jaw_pos = (random() % 22); if (! (random() % untwitch)) { bp->head_pos.x = 0; bp->head_pos.y = 0; bp->head_pos.z = 0; bp->jaw_pos = 0; } } glPopMatrix (); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers(dpy, window); } ENTRYPOINT void free_headroom (ModeInfo *mi) { headroom_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->trackball) gltrackball_free (bp->trackball); if (bp->rot) free_rotator (bp->rot); if (bp->rot2) free_rotator (bp->rot2); if (bp->rot3) free_rotator (bp->rot3); 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 ("Headroom", headroom) #endif /* USE_GL */