/* boing, Copyright (c) 2005-2014 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. * * A clone of the Amiga 1000 "Boing" demo. This was the first graphics demo * for the Amiga, written by Dale Luck and RJ Mical during a break at the 1984 * Consumer Electronics Show (or so the legend goes.) The boing ball was * briefly the official logo of Amiga Inc., until they were bought by * Commodore later that year. * * With no arguments, this program looks a lot like the original Amiga demo. * With "-smooth -lighting", it looks... less old. * * The amiga version made noise when the ball hit the walls. This version * does not, obviously. */ #define DEFAULTS "*delay: 30000 \n" \ "*showFPS: False \n" \ "*wireframe: False \n" \ # define release_boing 0 #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #include "xlockmore.h" #include "gltrackball.h" #include #ifdef USE_GL /* whole file */ #define DEF_SPIN "True" #define DEF_LIGHTING "False" #define DEF_SMOOTH "False" #define DEF_SCANLINES "True" #define DEF_SPEED "1.0" #define DEF_BALL_SIZE "0.5" #define DEF_ANGLE "15" #define DEF_MERIDIANS "16" #define DEF_PARALLELS "8" #define DEF_TILES "12" #define DEF_THICKNESS "0.05" #define DEF_BALL_COLOR1 "#CC1919" #define DEF_BALL_COLOR2 "#F2F2F2" #define DEF_GRID_COLOR "#991999" #define DEF_SHADOW_COLOR "#303030" #define DEF_BACKGROUND "#8C8C8C" typedef struct { GLfloat x, y, z; } XYZ; typedef struct { GLXContext *glx_context; trackball_state *trackball; Bool button_down_p; GLfloat speed; GLuint ball_list; double ball_x, ball_y, ball_z, ball_th; double ball_dx, ball_dy, ball_dz, ball_dth; double ball_ddx, ball_ddy, ball_ddz; GLfloat ball_color1[4], ball_color2[4], grid_color[4]; GLfloat bg_color[4], shadow_color[4]; GLfloat lightpos[4]; } boing_configuration; static boing_configuration *bps = NULL; static Bool spin; static Bool lighting_p; static Bool smooth_p; static Bool scanlines_p; static GLfloat speed; static int angle; static GLfloat ball_size; static unsigned int meridians; static unsigned int parallels; static unsigned int tiles; static GLfloat thickness; static char *ball_color1_str, *ball_color2_str, *grid_color_str, *shadow_str, *bg_str; static XrmOptionDescRec opts[] = { { "-spin", ".spin", XrmoptionNoArg, "True" }, { "+spin", ".spin", XrmoptionNoArg, "False" }, { "-lighting", ".lighting", XrmoptionNoArg, "True" }, { "+lighting", ".lighting", XrmoptionNoArg, "False" }, { "-smooth", ".smooth", XrmoptionNoArg, "True" }, { "+smooth", ".smooth", XrmoptionNoArg, "False" }, { "-scanlines", ".scanlines", XrmoptionNoArg, "True" }, { "+scanlines", ".scanlines", XrmoptionNoArg, "False" }, { "-speed", ".speed", XrmoptionSepArg, 0 }, { "-angle", ".angle", XrmoptionSepArg, 0 }, { "-size", ".ballSize", XrmoptionSepArg, 0 }, { "-meridians", ".meridians", XrmoptionSepArg, 0 }, { "-parallels", ".parallels", XrmoptionSepArg, 0 }, { "-tiles", ".tiles", XrmoptionSepArg, 0 }, { "-thickness", ".thickness", XrmoptionSepArg, 0 }, { "-ball-color1",".ballColor1",XrmoptionSepArg, 0 }, { "-ball-color2",".ballColor2",XrmoptionSepArg, 0 }, { "-grid-color", ".gridColor", XrmoptionSepArg, 0 }, { "-shadow-color",".shadowColor",XrmoptionSepArg, 0 }, { "-background", ".boingBackground",XrmoptionSepArg, 0 }, { "-bg", ".boingBackground",XrmoptionSepArg, 0 }, }; static argtype vars[] = { {&spin, "spin", "Spin", DEF_SPIN, t_Bool}, {&lighting_p,"lighting", "Lighting", DEF_LIGHTING, t_Bool}, {&smooth_p, "smooth", "Smooth", DEF_SMOOTH, t_Bool}, {&scanlines_p,"scanlines","Scanlines", DEF_SCANLINES, t_Bool}, {&speed, "speed", "Speed", DEF_SPEED, t_Float}, {&angle, "angle", "Angle", DEF_ANGLE, t_Int}, {&ball_size, "ballSize", "BallSize", DEF_BALL_SIZE, t_Float}, {&meridians, "meridians", "meridians", DEF_MERIDIANS, t_Int}, {¶llels, "parallels", "parallels", DEF_PARALLELS, t_Int}, {&tiles, "tiles", "Tiles", DEF_TILES, t_Int}, {&thickness, "thickness", "Thickness", DEF_THICKNESS, t_Float}, {&ball_color1_str, "ballColor1", "BallColor1", DEF_BALL_COLOR1, t_String}, {&ball_color2_str, "ballColor2", "BallColor2", DEF_BALL_COLOR2, t_String}, {&grid_color_str, "gridColor", "GridColor", DEF_GRID_COLOR, t_String}, {&shadow_str, "shadowColor","ShadowColor",DEF_SHADOW_COLOR,t_String}, /* dammit, -background is too magic... */ {&bg_str, "boingBackground", "Background", DEF_BACKGROUND, t_String}, }; ENTRYPOINT ModeSpecOpt boing_opts = {countof(opts), opts, countof(vars), vars, NULL}; static void parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a) { XColor c; a[3] = 1.0; /* alpha */ if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c)) { fprintf (stderr, "%s: can't parse %s color %s", progname, name, s); exit (1); } a[0] = c.red / 65536.0; a[1] = c.green / 65536.0; a[2] = c.blue / 65536.0; } static void draw_grid (ModeInfo *mi) { boing_configuration *bp = &bps[MI_SCREEN(mi)]; int x, y; GLfloat t2 = (GLfloat) tiles / 2; GLfloat s = 1.0 / (tiles + thickness); GLfloat z = 0; GLfloat lw = MI_HEIGHT(mi) * 0.06 * thickness; glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, bp->grid_color); glColor3fv (bp->grid_color); glPushMatrix(); glScalef(s, s, s); glTranslatef (-t2, -t2, 0); glLineWidth (lw); glBegin (GL_LINES); for (y = 0; y <= tiles; y++) { glVertex3f (0, y, z); glVertex3f (tiles, y, z); /*mi->polygon_count++;*/ } for (x = 0; x <= tiles; x++) { glVertex3f (x, tiles, z); glVertex3f (x, 0, z); /*mi->polygon_count++;*/ } glEnd(); glPopMatrix(); } static void draw_box (ModeInfo *mi) { /* boing_configuration *bp = &bps[MI_SCREEN(mi)]; */ glPushMatrix(); glTranslatef (0, 0, -0.5); /* glFrontFace (GL_CCW);*/ draw_grid (mi); glPopMatrix(); glPushMatrix(); glRotatef (90, 1, 0, 0); glTranslatef (0, 0, 0.5); /* glFrontFace (GL_CW);*/ draw_grid (mi); glPopMatrix(); } static void draw_ball (ModeInfo *mi) { boing_configuration *bp = &bps[MI_SCREEN(mi)]; int wire = MI_IS_WIREFRAME(mi); int x, y; int xx = meridians; int yy = parallels; int scale = (smooth_p ? 5 : 1); if (lighting_p && !wire) glEnable (GL_LIGHTING); if (parallels < 3) scale *= 2; xx *= scale; yy *= scale; glFrontFace (GL_CW); glPushMatrix(); glTranslatef (bp->ball_x, bp->ball_y, bp->ball_z); glScalef (ball_size, ball_size, ball_size); glRotatef (-angle, 0, 0, 1); glRotatef (bp->ball_th, 0, 1, 0); for (y = 0; y < yy; y++) { GLfloat thy0 = y * (M_PI * 2) / (yy * 2) + M_PI_2; GLfloat thy1 = (y+1) * (M_PI * 2) / (yy * 2) + M_PI_2; for (x = 0; x < xx; x++) { GLfloat thx0 = x * (M_PI * 2) / xx; GLfloat thx1 = (x+1) * (M_PI * 2) / xx; XYZ p; Bool bgp = ((x/scale) & 1) ^ ((y/scale) & 1); if (wire && bgp) continue; glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, (bgp ? bp->ball_color2 : bp->ball_color1)); glColor3fv (bgp ? bp->ball_color2 : bp->ball_color1); glBegin (wire ? GL_LINE_LOOP : GL_QUADS); if (!smooth_p) { p.x = cos((thy0+thy1)/2) * cos((thx0+thx1)/2); p.y = sin((thy0+thy1)/2); p.z = cos((thy0+thy1)/2) * sin((thx0+thx1)/2); glNormal3f (-p.x, -p.y, -p.z); } p.x = cos(thy0) * cos(thx0) / 2; p.y = sin(thy0) / 2; p.z = cos(thy0) * sin(thx0) / 2; if (smooth_p) glNormal3f (-p.x, -p.y, -p.z); glVertex3f (p.x, p.y, p.z); p.x = cos(thy1) * cos(thx0) / 2; p.y = sin(thy1) / 2; p.z = cos(thy1) * sin(thx0) / 2; if (smooth_p) glNormal3f (-p.x, -p.y, -p.z); glVertex3f (p.x, p.y, p.z); p.x = cos(thy1) * cos(thx1) / 2; p.y = sin(thy1) / 2; p.z = cos(thy1) * sin(thx1) / 2; if (smooth_p) glNormal3f (-p.x, -p.y, -p.z); glVertex3f (p.x, p.y, p.z); p.x = cos(thy0) * cos(thx1) / 2; p.y = sin(thy0) / 2; p.z = cos(thy0) * sin(thx1) / 2; if (smooth_p) glNormal3f (-p.x, -p.y, -p.z); glVertex3f (p.x, p.y, p.z); glEnd (); mi->polygon_count++; } } glPopMatrix(); if (lighting_p && !wire) glDisable(GL_LIGHTING); } static void draw_shadow (ModeInfo *mi) { boing_configuration *bp = &bps[MI_SCREEN(mi)]; int wire = MI_IS_WIREFRAME(mi); GLfloat xoff = 0.14; GLfloat yoff = 0.07; int y; int yy = parallels; int scale = (smooth_p ? 5 : 1); if (lighting_p && !wire) glEnable (GL_BLEND); if (parallels < 3) scale *= 2; yy *= scale; glPushMatrix(); glTranslatef (bp->ball_x + xoff, bp->ball_y + yoff, -0.49); glScalef (ball_size, ball_size, ball_size); glRotatef (-angle, 0, 0, 1); glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, bp->shadow_color); glColor4fv (bp->shadow_color); glFrontFace (GL_CCW); glNormal3f (0, 0, 1); glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN); if (!wire) glVertex3f (0, 0, 0); for (y = 0; y < yy*2+1; y++) { GLfloat thy0 = y * (M_PI * 2) / (yy * 2) + M_PI_2; glVertex3f (cos(thy0) / 2, sin(thy0) / 2, 0); mi->polygon_count++; } glEnd (); glPopMatrix(); if (lighting_p && !wire) glDisable (GL_BLEND); } static void draw_scanlines (ModeInfo *mi) { /* boing_configuration *bp = &bps[MI_SCREEN(mi)]; */ int wire = MI_IS_WIREFRAME(mi); int w = MI_WIDTH(mi); int h = MI_HEIGHT(mi); if (h <= 300) return; if (!wire) { glEnable (GL_BLEND); glDisable (GL_DEPTH_TEST); } glMatrixMode(GL_PROJECTION); glPushMatrix(); { glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); { int lh, ls; int y; glLoadIdentity(); glOrtho (0, w, 0, h, -1, 1); if (h > 500) lh = 4, ls = 4; else if (h > 300) lh = 2, ls = 1; else lh = 1, ls = 1; if (lh == 1) glDisable (GL_BLEND); glLineWidth (lh); glColor4f (0, 0, 0, 0.3); glBegin(GL_LINES); for (y = 0; y < h; y += lh + ls) { glVertex3f (0, y, 0); glVertex3f (w, y, 0); } glEnd(); } glPopMatrix(); } glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); if (!wire) { glDisable (GL_BLEND); glEnable (GL_DEPTH_TEST); } } static void tick_physics (ModeInfo *mi) { boing_configuration *bp = &bps[MI_SCREEN(mi)]; GLfloat s2 = ball_size / 2; GLfloat max = 0.5 - s2; GLfloat min = -max; bp->ball_th += bp->ball_dth; while (bp->ball_th > 360) bp->ball_th -= 360; while (bp->ball_th < 0) bp->ball_th += 360; bp->ball_dx += bp->ball_ddx; bp->ball_x += bp->ball_dx; if (bp->ball_x < min) bp->ball_x = min, bp->ball_dx = -bp->ball_dx, bp->ball_dth = -bp->ball_dth, bp->ball_dx += (frand(bp->speed/2) - bp->speed); else if (bp->ball_x > max) bp->ball_x = max, bp->ball_dx = -bp->ball_dx, bp->ball_dth = -bp->ball_dth, bp->ball_dx += (frand(bp->speed/2) - bp->speed); bp->ball_dy += bp->ball_ddy; bp->ball_y += bp->ball_dy; if (bp->ball_y < min) bp->ball_y = min, bp->ball_dy = -bp->ball_dy; else if (bp->ball_y > max) bp->ball_y = max, bp->ball_dy = -bp->ball_dy; bp->ball_dz += bp->ball_ddz; bp->ball_z += bp->ball_dz; if (bp->ball_z < min) bp->ball_z = min, bp->ball_dz = -bp->ball_dz; else if (bp->ball_z > max) bp->ball_z = max, bp->ball_dz = -bp->ball_dz; } /* Window management, etc */ ENTRYPOINT void reshape_boing (ModeInfo *mi, int width, int height) { GLfloat h = (GLfloat) height / (GLfloat) width; int y = 0; h *= 4.0 / 3.0; /* Back in the caveman days we couldn't even afford square pixels! */ if (width > height * 5) { /* tiny window: show middle */ height = width * 3/4; y = -height/2; h = height / (GLfloat) width; } glViewport (0, y, (GLint) width, (GLint) height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (height > width) { GLfloat s = width / (GLfloat) height; glScalef (s, s, s); } gluPerspective (8.0, 1/h, 1.0, 10.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt (0.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); } ENTRYPOINT Bool boing_handle_event (ModeInfo *mi, XEvent *event) { boing_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; } ENTRYPOINT void init_boing (ModeInfo *mi) { boing_configuration *bp; int wire = MI_IS_WIREFRAME(mi); MI_INIT (mi, bps); bp = &bps[MI_SCREEN(mi)]; bp->glx_context = init_GL(mi); if (tiles < 1) tiles = 1; if (smooth_p) { if (meridians < 1) meridians = 1; if (parallels < 1) parallels = 1; } else { if (meridians < 3) meridians = 3; if (parallels < 2) parallels = 2; } if (meridians > 1 && meridians & 1) meridians++; /* odd numbers look bad */ if (thickness <= 0) thickness = 0.001; else if (thickness > 1) thickness = 1; reshape_boing (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); parse_color (mi, "ballColor1", ball_color1_str, bp->ball_color1); parse_color (mi, "ballColor2", ball_color2_str, bp->ball_color2); parse_color (mi, "gridColor", grid_color_str, bp->grid_color); parse_color (mi, "shadowColor", shadow_str, bp->shadow_color); parse_color (mi, "background", bg_str, bp->bg_color); bp->shadow_color[3] = 0.9; glClearColor (bp->bg_color[0], bp->bg_color[1], bp->bg_color[2], 1); if (!wire) { glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); } bp->lightpos[0] = 0.5; bp->lightpos[1] = 0.5; bp->lightpos[2] = -1; bp->lightpos[3] = 0; if (lighting_p && !wire) { GLfloat amb[4] = {0, 0, 0, 1}; GLfloat dif[4] = {1, 1, 1, 1}; GLfloat spc[4] = {1, 1, 1, 1}; glEnable(GL_LIGHT0); 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); bp->speed = speed / 800.0; bp->ball_dth = (spin ? -bp->speed * 7 * 360 : 0); bp->ball_x = 0.5 - ((ball_size/2) + frand(1-ball_size)); bp->ball_y = 0.2; bp->ball_dx = bp->speed * 6 + frand(bp->speed); bp->ball_ddy = -bp->speed; bp->ball_dz = bp->speed * 6 + frand(bp->speed); bp->trackball = gltrackball_init (False); } ENTRYPOINT void draw_boing (ModeInfo *mi) { boing_configuration *bp = &bps[MI_SCREEN(mi)]; Display *dpy = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); if (!bp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); mi->polygon_count = 0; glShadeModel(GL_SMOOTH); glEnable(GL_NORMALIZE); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (! bp->button_down_p) tick_physics (mi); glPushMatrix (); { double rot = current_device_rotation(); glRotatef(rot, 0, 0, 1); /* if ((rot > 45 && rot < 135) || (rot < -45 && rot > -135)) { GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi); glScalef (1/s, s, 1); } */ } gltrackball_rotate (bp->trackball); glLightfv (GL_LIGHT0, GL_POSITION, bp->lightpos); glDisable (GL_CULL_FACE); glDisable (GL_DEPTH_TEST); glEnable (GL_LINE_SMOOTH); glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable (GL_BLEND); draw_box (mi); draw_shadow (mi); glEnable (GL_CULL_FACE); glEnable (GL_DEPTH_TEST); draw_ball (mi); if (scanlines_p) draw_scanlines (mi); glPopMatrix (); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers(dpy, window); } ENTRYPOINT void free_boing (ModeInfo *mi) { boing_configuration *bp = &bps[MI_SCREEN(mi)]; if (!bp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); if (bp->trackball) gltrackball_free (bp->trackball); } XSCREENSAVER_MODULE ("Boing", boing) #endif /* USE_GL */