/* bouncingcow, Copyright (c) 2003-2018 Jamie Zawinski <jwz@jwz.org>
*
* 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.
*
* Boing, boing, boing. Cow, cow, cow.
*/
#define DEFAULTS "*delay: 30000 \n" \
"*count: 1 \n" \
"*showFPS: False \n" \
"*wireframe: False \n" \
# define free_cow 0
# define release_cow 0
#define DEF_SPEED "1.0"
#define DEF_TEXTURE "(none)"
#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))
#undef BELLRAND
#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
#undef RANDSIGN
#define RANDSIGN() ((random() & 1) ? 1 : -1)
#include "xlockmore.h"
#include "rotator.h"
#include "gltrackball.h"
#include "ximage-loader.h"
#include <ctype.h>
#ifdef USE_GL /* whole file */
#include "gllist.h"
extern struct gllist
*cow_face, *cow_hide, *cow_hoofs, *cow_horns, *cow_tail, *cow_udder;
static struct gllist **all_objs[] = {
&cow_face, &cow_hide, &cow_hoofs, &cow_horns, &cow_tail, &cow_udder
};
#define FACE 0
#define HIDE 1
#define HOOFS 2
#define HORNS 3
#define TAIL 4
#define UDDER 5
typedef struct {
GLfloat x, y, z;
GLfloat ix, iy, iz;
GLfloat dx, dy, dz;
GLfloat ddx, ddy, ddz;
rotator *rot;
Bool spinner_p;
} floater;
typedef struct {
GLXContext *glx_context;
trackball_state *trackball;
Bool button_down_p;
GLuint *dlists;
GLuint texture;
int nfloaters;
floater *floaters;
} cow_configuration;
static cow_configuration *bps = NULL;
static GLfloat speed;
static const char *do_texture;
static XrmOptionDescRec opts[] = {
{ "-speed", ".speed", XrmoptionSepArg, 0 },
{"-texture", ".texture", XrmoptionSepArg, 0 },
{"+texture", ".texture", XrmoptionNoArg, "(none)" },
};
static argtype vars[] = {
{&speed, "speed", "Speed", DEF_SPEED, t_Float},
{&do_texture, "texture", "Texture", DEF_TEXTURE, t_String},
};
ENTRYPOINT ModeSpecOpt cow_opts = {countof(opts), opts, countof(vars), vars, NULL};
#define BOTTOM 28.0
static void
reset_floater (ModeInfo *mi, floater *f)
{
cow_configuration *bp = &bps[MI_SCREEN(mi)];
f->y = -BOTTOM;
f->x = f->ix;
f->z = f->iz;
/* Yes, I know I'm varying the force of gravity instead of varying the
launch velocity. That's intentional: empirical studies indicate
that it's way, way funnier that way. */
f->dy = 5.0;
f->dx = 0;
f->dz = 0;
/* -0.18 max -0.3 top -0.4 middle -0.6 bottom */
f->ddy = speed * (-0.6 + BELLRAND(0.45));
f->ddx = 0;
f->ddz = 0;
f->spinner_p = !(random() % (12 * bp->nfloaters));
if (! (random() % (30 * bp->nfloaters)))
{
f->dx = BELLRAND(1.8) * RANDSIGN();
f->dz = BELLRAND(1.8) * RANDSIGN();
}
}
static void
tick_floater (ModeInfo *mi, floater *f)
{
cow_configuration *bp = &bps[MI_SCREEN(mi)];
if (bp->button_down_p) return;
f->dx += f->ddx;
f->dy += f->ddy;
f->dz += f->ddz;
f->x += f->dx * speed;
f->y += f->dy * speed;
f->z += f->dz * speed;
if (f->y < -BOTTOM ||
f->x < -BOTTOM*8 || f->x > BOTTOM*8 ||
f->z < -BOTTOM*8 || f->z > BOTTOM*8)
reset_floater (mi, f);
}
/* Window management, etc
*/
ENTRYPOINT void
reshape_cow (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.0, 1/h, 1.0, 100);
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
cow_handle_event (ModeInfo *mi, XEvent *event)
{
cow_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;
}
/* Textures
*/
static Bool
load_texture (ModeInfo *mi, const char *filename)
{
Display *dpy = mi->dpy;
Visual *visual = mi->xgwa.visual;
char buf[1024];
XImage *image;
if (MI_IS_WIREFRAME(mi))
return False;
if (!filename ||
!*filename ||
!strcasecmp (filename, "(none)"))
{
glDisable (GL_TEXTURE_2D);
return False;
}
image = file_to_ximage (dpy, visual, filename);
if (!image) return False;
clear_gl_error();
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
image->width, image->height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, image->data);
sprintf (buf, "texture: %.100s (%dx%d)",
filename, image->width, image->height);
check_gl_error(buf);
glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
return True;
}
ENTRYPOINT void
init_cow (ModeInfo *mi)
{
cow_configuration *bp;
int wire = MI_IS_WIREFRAME(mi);
int i;
Bool tex_p = False;
MI_INIT (mi, bps);
bp = &bps[MI_SCREEN(mi)];
bp->glx_context = init_GL(mi);
reshape_cow (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);
}
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);
tex_p = load_texture (mi, do_texture);
if (tex_p)
glBindTexture (GL_TEXTURE_2D, bp->texture);
for (i = 0; i < countof(all_objs); i++)
{
GLfloat black[4] = {0, 0, 0, 1};
const struct gllist *gll = *all_objs[i];
glNewList (bp->dlists[i], GL_COMPILE);
glDisable (GL_TEXTURE_2D);
if (i == HIDE)
{
GLfloat color[4] = {0.63, 0.43, 0.36, 1.00};
if (tex_p)
{
/* if we have a texture, make the base color be white. */
color[0] = color[1] = color[2] = 1.0;
glTexGeni (GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeni (GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_2D);
/* approximately line it up with ../images/earth.png */
glMatrixMode (GL_TEXTURE);
glLoadIdentity();
glTranslatef (0.45, 0.58, 0);
glScalef (0.08, 0.16, 1);
glRotatef (-5, 0, 0, 1);
glMatrixMode (GL_MODELVIEW);
}
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, black);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 128);
}
else if (i == TAIL)
{
GLfloat color[4] = {0.63, 0.43, 0.36, 1.00};
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, black);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 128);
}
else if (i == UDDER)
{
GLfloat color[4] = {1.00, 0.53, 0.53, 1.00};
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, black);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 128);
}
else if (i == HOOFS || i == HORNS)
{
GLfloat color[4] = {0.20, 0.20, 0.20, 1.00};
GLfloat spec[4] = {0.30, 0.30, 0.30, 1.00};
GLfloat shiny = 8.0;
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
}
else if (i == FACE)
{
GLfloat color[4] = {0.10, 0.10, 0.10, 1.00};
GLfloat spec[4] = {0.10, 0.10, 0.10, 1.00};
GLfloat shiny = 8.0;
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
}
else
{
GLfloat color[4] = {1.00, 1.00, 1.00, 1.00};
GLfloat spec[4] = {1.00, 1.00, 1.00, 1.00};
GLfloat shiny = 128.0;
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
}
renderList (gll, wire);
glEndList ();
}
bp->nfloaters = MI_COUNT (mi);
bp->floaters = (floater *) calloc (bp->nfloaters, sizeof (floater));
for (i = 0; i < bp->nfloaters; i++)
{
floater *f = &bp->floaters[i];
f->rot = make_rotator (10.0, 0, 0,
4, 0.05 * speed,
True);
if (bp->nfloaters == 2)
{
f->x = (i ? 6 : -6);
}
else if (i != 0)
{
double th = (i - 1) * M_PI*2 / (bp->nfloaters-1);
double r = 10;
f->x = r * cos(th);
f->z = r * sin(th);
}
f->ix = f->x;
f->iy = f->y;
f->iz = f->z;
reset_floater (mi, f);
}
}
static void
draw_floater (ModeInfo *mi, floater *f)
{
cow_configuration *bp = &bps[MI_SCREEN(mi)];
GLfloat n;
double x, y, z;
get_position (f->rot, &x, &y, &z, !bp->button_down_p);
glPushMatrix();
glTranslatef (f->x, f->y, f->z);
gltrackball_rotate (bp->trackball);
glRotatef (y * 360, 0.0, 1.0, 0.0);
if (f->spinner_p)
{
glRotatef (x * 360, 1.0, 0.0, 0.0);
glRotatef (z * 360, 0.0, 0.0, 1.0);
}
n = 1.5;
if (bp->nfloaters > 99) n *= 0.05;
else if (bp->nfloaters > 25) n *= 0.18;
else if (bp->nfloaters > 9) n *= 0.3;
else if (bp->nfloaters > 1) n *= 0.7;
glScalef(n, n, n);
glCallList (bp->dlists[FACE]);
mi->polygon_count += (*all_objs[FACE])->points / 3;
glCallList (bp->dlists[HIDE]);
mi->polygon_count += (*all_objs[HIDE])->points / 3;
glCallList (bp->dlists[HOOFS]);
mi->polygon_count += (*all_objs[HOOFS])->points / 3;
glCallList (bp->dlists[HORNS]);
mi->polygon_count += (*all_objs[HORNS])->points / 3;
glCallList (bp->dlists[TAIL]);
mi->polygon_count += (*all_objs[TAIL])->points / 3;
glCallList (bp->dlists[UDDER]);
mi->polygon_count += (*all_objs[UDDER])->points / 3;
glPopMatrix();
}
ENTRYPOINT void
draw_cow (ModeInfo *mi)
{
cow_configuration *bp = &bps[MI_SCREEN(mi)];
Display *dpy = MI_DISPLAY(mi);
Window window = MI_WINDOW(mi);
int i;
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 ();
# 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);
glRotatef(o, 0, 0, 1);
}
# endif
glScalef (0.5, 0.5, 0.5);
mi->polygon_count = 0;
# if 0
{
floater F;
F.x = F.y = F.z = 0;
F.dx = F.dy = F.dz = 0;
F.ddx = F.ddy = F.ddz = 0;
F.rot = make_rotator (0, 0, 0, 1, 0, False);
glScalef(2,2,2);
draw_floater (mi, &F);
}
# else
for (i = 0; i < bp->nfloaters; i++)
{
/* "Don't kid yourself, Jimmy. If a cow ever got the chance,
he'd eat you and everyone you care about!"
-- Troy McClure in "Meat and You: Partners in Freedom"
*/
floater *f = &bp->floaters[i];
draw_floater (mi, f);
tick_floater (mi, f);
}
# endif
glPopMatrix ();
if (mi->fps_p) do_fps (mi);
glFinish();
glXSwapBuffers(dpy, window);
}
XSCREENSAVER_MODULE_2 ("BouncingCow", bouncingcow, cow)
#endif /* USE_GL */