
/*
   ====
   ep.c
   ====

   OpenGL program to swirl rectangles around the
   screen.  Intended to resemble SGI electropaint;
   I've no idea how electropaint works, and it's a
   few years since I've even seen it in action, so
   don't expect this to really compare.

   There's far too much code here; much of it is
   setting up different levels of colours and stuff.

   If used as an xscreensaver hack, the batchcount
   will indicate the detail level (0 to 3).  This is
   set through the -count command-line variable.

   To build, get the xscreensaver distribution from
   http://www.jwz.org/xscreensaver/, stick this in
   the hacks/glx/ subdirectory, and then edit the
   Makefile.in in that directory.  Add ep.c to the
   SRCS line, ep.o to the OBJS; then duplicate the
   two lines further down that build, say, "cage"
   (starting "cage: cage.o") and change "cage" to "ep"
   throughout one copy.  If you see what I mean.
   Then configure and make xscreensaver as normal.

   This should build on NT as well, as a standalone
   program (not a screensaver); but you'll need to
   worry about makefiles and things yourself.

   Chris Cannam, October 1998
*/

/* ==========================================
   Change these things according to your whim
   ========================================== */

#define NUM_LEAVES 20
#define SMOOTHNESS 12
#define LEAF_SIZE 0.2

/* 0 to 3 */
#define DEFAULT_DETAIL_LEVEL 3

#define MAX_FRAME_RATE 35 /* Unix only, and likely to make it rather jerky */

/* ===========================
   The rest *should* just work
   =========================== */

#ifdef WINDOWS
#define STANDALONE
#define USE_GL
#endif

#ifdef STANDALONE
#ifdef USE_GL

#ifdef WINDOWS

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#else

#include <X11/Intrinsic.h>
#define CALLBACK

#endif

#define PROGCLASS	"ep"
#define HACK_INIT	init_ep
#define HACK_DRAW	draw_ep
#define ep_opts	xlockmore_opts
#define DEFAULTS	"*count:	3	\n"

#ifndef WINDOWS
#include "xlockmore.h"
#endif

#ifndef WINDOWS
ModeSpecOpt ep_opts = { 0, NULL, 0, NULL, NULL };
#endif

#include <math.h>
#include <time.h>

#ifndef M_PI
#define M_PI 3.1415926536
#endif

#ifndef WINDOWS
#include <sys/time.h>
#include <sys/types.h>
#include <sys/times.h>
#include <unistd.h>
#endif

static double thetaOffsets[NUM_LEAVES];
static double idealOffsets[NUM_LEAVES];
static double radii[NUM_LEAVES];
static double heights[NUM_LEAVES];

static int lightsource = 0;
static int doBorders = 0;
static int doTexture = 0;

#ifndef WINDOWS
static GLXContext *epGLXcontext = 0;
#endif

static double epClock;
static int epOrder;
static int epLevel = DEFAULT_DETAIL_LEVEL;

#ifdef WINDOWS
typedef void ModeInfo;
void init_ep(ModeInfo *mi);
GLvoid CALLBACK draw_ep(GLvoid);

int level;

void _CRTAPI1 main(int argc, char **argv)
{
    if (argc > 2) {
	if (!strcmp(argv[1], "-count")) {
	    epLevel = atoi(argv[2]);
	}
    }

    srand(time(0));
    init_ep(0);
    auxMainLoop(draw_ep);
}
#endif

GLvoid CALLBACK resize(GLsizei width, GLsizei height);

void init_ep(ModeInfo *mi)
{
    int i;

#ifndef WINDOWS
    epLevel = MI_BATCHCOUNT(mi);
#endif

    if (epLevel < 0) epLevel = 0;
    if (epLevel > 3) epLevel = 3;

    switch (epLevel) {
    case 0:
	lightsource = doBorders = doTexture = 0; break;
    case 1:
	lightsource = doBorders = 1; doTexture = 0; break;
    case 2:
	lightsource = doBorders = 0; doTexture = 1; break;
    case 3:
	lightsource = doBorders = doTexture = 1; break;
    }

    for (i = 0; i < NUM_LEAVES; ++i) {
	thetaOffsets[i] = 0;
	radii[i] = 1;
    }

    srand(time(NULL));
    epClock = (double)(rand() % 1000);
    epOrder = rand() % (NUM_LEAVES/2) + 1;

#ifdef WINDOWS
    auxInitPosition(256, 192, 512, 384);
    auxInitDisplayMode(AUX_RGB | AUX_DOUBLE);
    auxInitWindow("ep");
    auxIdleFunc(draw_ep);
    auxReshapeFunc(resize);
    {
#else
    if ((epGLXcontext = init_GL(mi)) != NULL) {
#endif

	GLfloat ambientProperties[] = { 0.7, 0.7, 0.7, 1.0 };
	GLfloat diffuseProperties[] = { 0.8, 0.8, 0.8, 1.0 };
	GLfloat specularProperties[] = { 1.0, 1.0, 1.0, 1.0 };

	if (doTexture) {
	    ambientProperties[0] = 1.0;
	    ambientProperties[1] = 1.0;
	    ambientProperties[2] = 1.0;
	    diffuseProperties[0] = 1.0;
	    diffuseProperties[1] = 1.0;
	    diffuseProperties[2] = 1.0;
	}

	glEnable(GL_LIGHTING);
	glDisable(GL_DITHER);
	glDisable(GL_DEPTH_TEST);
	glDepthFunc(GL_NEVER);
    
	if (lightsource) {

	    glShadeModel(GL_SMOOTH);

	    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientProperties);
	    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseProperties);
	    glLightfv(GL_LIGHT0, GL_SPECULAR, specularProperties);
    
	    glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1.0);
	
	    glEnable(GL_LIGHT0);
	    
	} else {

	    GLfloat sLight[] = { 1, 1, 1, 1 };

	    glShadeModel(GL_FLAT);

	    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientProperties);
	    glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, 0.0);

	    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, sLight);
	    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 100.0);

	    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT);
	    glEnable(GL_COLOR_MATERIAL);

	    glEnable(GL_LIGHT0);
	}

	if (doTexture) {

	    int i;
	    GLfloat params[] = { 1.0, 0.0, 0.0, 0.0 };
	    GLubyte tex[6] = { 255, 0, 0, 0, 0, 255 };

	    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	    glTexImage1D(GL_TEXTURE_1D, 0, 3, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, tex);
	    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
	    glTexGenfv(GL_S, GL_OBJECT_PLANE, params);

	    glEnable(GL_TEXTURE_GEN_S);
	    glEnable(GL_TEXTURE_1D);
	}

#ifdef WINDOWS
	resize(1024, 768);
#else
	resize(MI_WIDTH(mi), MI_HEIGHT(mi));
#endif

	glNewList(1, GL_COMPILE);
	glRectf(0, 0, LEAF_SIZE, LEAF_SIZE);
	/* Texture could go here */
	glEndList();
	
#ifdef WINDOWS
    }
#else
    } else {
	MI_CLEARWINDOW(mi);
    }
#endif
}

GLvoid CALLBACK resize(GLsizei width, GLsizei height)
{
    GLfloat h = (GLfloat)height / (GLfloat)width;

    glViewport(0, 0, (GLint)width, (GLint)height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-1, 1, -0.7, 1.2, 15, 60);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0, 0, -40);
    glClear(GL_COLOR_BUFFER_BIT);
}    

double sinGenerator(double t, unsigned long period, unsigned long seed,
		    double min, double max)
{
    double arg, centre, range;

    t += seed;
    arg = t * 2.0*M_PI / (double)period;
    centre = (min + max) / 2.0;
    range = (max - min) / 2.0;
    return centre + range * sin(arg);
}

double peakGenerator(double t, unsigned long period, unsigned long seed,
		    double min, double max)
{
    double arg, s;

    t += seed;

    if ((long)t % period > period/2) {
	t = (long)t % period;
	if (t < 98*period/100) {
	    return min;
	} else {
	    double td;
	    t -= 98*period/100;
	    td = t / ((double)period / 50.0);
	    return min + (max-min) * td * td;
	}
    }
    
    arg = t * M_PI / (double)period;
    s = 1 - fabs(sin(arg));
    s = s*s*s;
    return min + (max - min) * s;
}

#ifndef WINDOWS
void stabiliseFrameRate()
{
    clock_t tStart, t;
    static clock_t prevT = 0;
    static fd_set r, w, e;
    static struct timeval delay;
    struct tms spare;
    static int frames = 0;

    if (prevT == 0) {
        prevT = times(&spare);
	FD_ZERO(&r);
	FD_ZERO(&w);
	FD_ZERO(&e);
    }

    tStart = t = times(&spare);

    if (t % 100 < prevT % 100) {
      /*	printf("%d frames per second\n", frames);*/
	frames = 0;
    }

    while (t > prevT && t < prevT + CLK_TCK/MAX_FRAME_RATE) {
      delay.tv_sec = 0;
      delay.tv_usec = 10;
      select(0, &r, &w, &e, &delay);
      t = times(&spare);
    }

    prevT = tStart;
    ++frames;
}
#endif

#ifdef WINDOWS
GLvoid CALLBACK draw_ep(GLvoid)
#else
void draw_ep(ModeInfo *mi)
#endif
{
    GLfloat aLight[] = {0.1, 0.3, 0.1, 1.0};
    GLfloat dLight[] = {0.1, 0.3, 0.1, 1.0};
    GLfloat sLight[] = {1.0, 1.0, 1.0, 1.0};
    GLfloat borderLight[] = { 0.0, 0.0, 0.0, 0.0 };
    static GLfloat lightPosition0[] = { -1.0, -2.0, 4.0, 1.0 };

    double r;

    double speed;
    static double speedClock = 0;
    double accel;

    static double iClock = 0;

    static int rCopy = 1;

    static double theta = 0;
    double height;

    double idealOffset, diff, thetaWas;
    double leafTilt;
    int i;

#ifndef WINDOWS
    if (!epGLXcontext) return;

    stabiliseFrameRate();
#endif

    glDrawBuffer(GL_BACK);

#ifndef WINDOWS
    glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *epGLXcontext);
#endif

    glPushMatrix();

    glRotated(-62, 1.0, 0.0, 0.0);    

    glClear(GL_COLOR_BUFFER_BIT);

    if (lightsource) {
	glLightfv(GL_LIGHT0, GL_POSITION, lightPosition0);
    }

    accel = sinGenerator(epClock, 530, 0, -15.0/SMOOTHNESS, 15.0/SMOOTHNESS);
    speedClock += accel * 10;
    speed = sinGenerator(speedClock, 2700, 400, -0.1, 0.1);
    theta += speed;
    while (theta > 200*M_PI) theta -= 200*M_PI;

    thetaWas = theta;

    aLight[0] = peakGenerator(epClock, 1610, 1720, 0.1, 1);
    aLight[1] = peakGenerator(epClock, 1450, 550, 0.6, 1) -
	peakGenerator(epClock, 2110, 550, 0, 0.6);
    aLight[2] = peakGenerator(epClock, 1920, 1210, 0.1, 1);

    if (doBorders) {
	if (doTexture) {
	    borderLight[0] = peakGenerator(epClock, 1430, 20, 0, 0.6);
	    borderLight[1] = peakGenerator(epClock, 1460, 400, 0, 0.6);
	    borderLight[2] = peakGenerator(epClock, 1490, 810, 0, 0.6);
	} else {
	    borderLight[0] = 0.6 - peakGenerator(epClock, 1430, 20, 0, 0.6);
	    borderLight[1] = 0.6 - peakGenerator(epClock, 1460, 400, 0, 0.6);
	    borderLight[2] = peakGenerator(epClock, 1490, 810, 0, 0.6);
	}
    }

    if (lightsource) {

	dLight[0] = peakGenerator(epClock, 320, 0, 0.2, 1);
	dLight[1] = sinGenerator(epClock, 3170, 1050, 0.2, 1);
	dLight[2] = sinGenerator(epClock, 910, 70, 0.2, 1);
    }

    if (rand() % (int)(80 - peakGenerator(epClock, 710, 240, 0, 50)) == 1) {
	epOrder = rand()%(NUM_LEAVES/2) + 1;
	for (i = 0; i < NUM_LEAVES; ++i) {
	    idealOffset = (2*M_PI / epOrder) * i;
	    if (rand()%10 < 7) {
		while (idealOffset > 2*M_PI) idealOffset -= 2*M_PI;
	    } else {
		while (idealOffset > 4*M_PI) idealOffset -= 2*M_PI;
	    }
	    idealOffsets[i] = idealOffset;
	}
    }
    if (epOrder == NUM_LEAVES/2) epOrder = NUM_LEAVES;

    for (i = 0; i < NUM_LEAVES; ++i) {
	radii[i] = 1 + peakGenerator(epClock, 590, 42 + i, 0, 0.7)
	    - peakGenerator(epClock, 1310, 541 + i, 0, 1);
    }

    for (i = 0; i < NUM_LEAVES; ++i) {
	heights[i] = peakGenerator(epClock, 600, i*5, 0, 0.7) *
	    sinGenerator(epClock, 127, i, 0, 1);
	heights[i] -= peakGenerator(epClock, 999, i, 0, 1);
    }

    if (lightsource) {

	glPushAttrib(GL_LIGHTING_BIT);
    
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, aLight);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, dLight);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, sLight);
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 100.0);
	
    } else {
    
	glColor3fv(aLight);
    }

    leafTilt =
      peakGenerator(epClock, 1730, 410, 0, 90) +
      sinGenerator(epClock, 750, 300, 5, 30);

    for (i = 0; i < NUM_LEAVES; ++i) {

        /* Any use of epClock inside this loop may be an inefficiency */

	glPushMatrix();

	r = radii[i] + sinGenerator(speedClock, 381, 0, 0, speed);

	diff = (idealOffsets[i] - thetaOffsets[i]) /
	    sinGenerator(speedClock, 890, 0, 5, 50);
	thetaOffsets[i] += diff;
	theta = thetaWas + thetaOffsets[i];

	theta *= pow(sinGenerator(iClock, 10304, 122, 1, 1.015), i);

	height = (double)i/15 + heights[i];

	glTranslatef(r * cos(theta), r * sin(theta), height);
	glRotatef(theta * 180.0 / M_PI, 0, 0, 1);
	glRotatef(leafTilt, 0, 1, 0);

	if (doBorders) {

	    glPushAttrib(GL_LIGHTING_BIT);
	    
	    glMaterialfv(GL_FRONT, GL_AMBIENT, borderLight);
	    glMaterialfv(GL_FRONT, GL_DIFFUSE, borderLight);
	    glMaterialfv(GL_FRONT, GL_SPECULAR, borderLight);

	    glRectf(-0.01, -0.01, LEAF_SIZE + 0.01, LEAF_SIZE + 0.01);

	    glPopAttrib();
	}

	glCallList(1);
	glPopMatrix();

	iClock += 10.0 / (double)SMOOTHNESS;
    }

    if (lightsource) glPopAttrib();

    theta = thetaWas;

    glPopMatrix();
    glFinish();

#ifdef WINDOWS
    auxSwapBuffers();
#else
    glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
#endif

    epClock += 10.0 / (double)SMOOTHNESS;
}

#endif
#endif

