/*
 *  Terence Welsh Screensaver - Euphoria
 *  http://www.reallyslick.com/
 *
 *  Ported to KDE by Karl Robillard
 *  Copyright (C) 2002  Terence M. Welsh
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of 
 *  the License, or (at your option) any later version.
 *   
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <qtimer.h>

#include "Euphoria.h"
#include "Euphoria.moc"
#include "EuphoriaTexture.h"
#include <GL/glu.h>


#define NUMCONSTS 9
#define PIx2 6.28318530718f


//----------------------------------------------------------------------------


#include <sys/time.h>
#include <unistd.h>


// Returns the system time, in seconds.
double timeGetTime()
{
    struct timeval tp;
    gettimeofday( &tp, 0 );
    return (double)tp.tv_sec + (double)tp.tv_usec / 1000000;
}


//----------------------------------------------------------------------------


class rsVec
{
public:

    float v[3];

    rsVec() {}
    rsVec(float xx, float yy, float zz);

    void set(float xx, float yy, float zz);
    float normalize();
    float dot(rsVec);
    void cross(rsVec, rsVec);

    float & operator [] (int i) {return v[i];}
    const float & operator [] (int i) const {return v[i];}
    rsVec & operator = (const rsVec &vec)
        {v[0]=vec[0];v[1]=vec[1];v[2]=vec[2];return *this;}
    rsVec operator + (const rsVec &vec)
        {return(rsVec(v[0]+vec[0], v[1]+vec[1], v[2]+vec[2]));}
    rsVec operator - (const rsVec &vec)
        {return(rsVec(v[0]-vec[0], v[1]-vec[1], v[2]-vec[2]));}
    rsVec operator * (const float &mul)
        {return(rsVec(v[0]*mul, v[1]*mul, v[2]*mul));}
    rsVec operator / (const float &div)
        {float rec = 1.0f/div; return(rsVec(v[0]*rec, v[1]*rec, v[2]*rec));}
    rsVec & operator += (const rsVec &vec)
        {v[0]+=vec[0];v[1]+=vec[1];v[2]+=vec[2];return *this;}
    rsVec & operator -= (const rsVec &vec)
        {v[0]-=vec[0];v[1]-=vec[1];v[2]-=vec[2];return *this;}
    rsVec & operator *= (const rsVec &vec)
        {v[0]*=vec[0];v[1]*=vec[1];v[2]*=vec[2];return *this;}
    rsVec & operator *= (const float &mul)
        {v[0]*=mul;v[1]*=mul;v[2]*=mul;return *this;}
};


rsVec::rsVec(float xx, float yy, float zz){
    v[0] = xx;
    v[1] = yy;
    v[2] = zz;
}


void rsVec::set(float xx, float yy, float zz){
    v[0] = xx;
    v[1] = yy;
    v[2] = zz;
}


float rsVec::normalize(){
    float length = float(sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
    if(length == 0.0f){
        v[1] = 1.0f;
        return(0.0f);
    }
    float reciprocal = 1.0f / length;
    v[0] *= reciprocal;
    v[1] *= reciprocal;
    v[2] *= reciprocal;
    // Really freakin' stupid compiler bug fix for VC++ 5.0
    /*v[0] /= length;
    v[1] /= length;
    v[2] /= length;*/
    return(length);
}


float rsVec::dot(rsVec vec1){
    return(v[0] * vec1[0] + v[1] * vec1[1] + v[2] * vec1[2]);
}


void rsVec::cross(rsVec vec1, rsVec vec2){
    v[0] = vec1[1] * vec2[2] - vec2[1] * vec1[2];
    v[1] = vec1[2] * vec2[0] - vec2[2] * vec1[0];
    v[2] = vec1[0] * vec2[1] - vec2[0] * vec1[1];
}


//----------------------------------------------------------------------------


void hsl2rgb(float h, float s, float l, float &r, float &g, float &b)
{
    // hue influence
    if(h < 0.166667){  // full red, some green
        r = 1.0;
        g = h * 6.0f;
        b = 0.0;
    }
    else {
        if(h < 0.5){  // full green
            g = 1.0;
            if(h < 0.333333){  // some red
                r = 1.0f - ((h - 0.166667f) * 6.0f);
                b = 0.0;
            }
            else{  // some blue
                b = (h - 0.333333f) * 6.0f;
                r = 0.0;
            }
        }
        else{
            if(h < 0.833333){  // full blue
                b = 1.0;
                if(h < 0.666667){  // some green
                    g = 1.0f - ((h - 0.5f) * 6.0f);
                    r = 0.0;
                }
                else{  // some red
                    r = (h - 0.666667f) * 6.0f;
                    g = 0.0;
                }
            }
            else{  // full red, some blue
                r = 1.0;
                b = 1.0f - ((h - 0.833333f) * 6.0f);
                g = 0.0;
            }
        }
    }

    // saturation influence
    r = 1.0f - (s * (1.0f - r));
    g = 1.0f - (s * (1.0f - g));
    b = 1.0f - (s * (1.0f - b));

    // luminosity influence
    r *= l;
    g *= l;
    b *= l;
}


// Useful random number macros
// Don't forget to initialize with srand()
inline int myRandi(int x){
    return((rand() * x) / RAND_MAX);
}
inline float myRandf(float x){
    return(float(rand() * x) / float(RAND_MAX));
}


//----------------------------------------------------------------------------


// Context pointer to allow many instances.
static EuphoriaWidget* _ec = 0;


class wisp
{
public:

	wisp();
	~wisp();
	void update();
	void draw();
	void drawAsBackground();


    int density;
	float*** vertices;
	float c[NUMCONSTS];     // constants
	float cr[NUMCONSTS];    // constants' radial position
	float cv[NUMCONSTS];    // constants' change velocities
	float hsl[3];
	float rgb[3];
	float hueSpeed;
	float saturationSpeed;
};


wisp::wisp()
{
	int i, j;
	float recHalfDens = 1.0f / (float(_ec->dDensity) * 0.5f);

    density = _ec->dDensity;
	vertices = new float**[density+1];
	for(i=0; i<=density; i++)
    {
		vertices[i] = new float*[density+1];
		for(j=0; j<=density; j++)
        {
			vertices[i][j] = new float[7];
			vertices[i][j][3] = float(i) * recHalfDens - 1.0f;  // x position on grid
			vertices[i][j][4] = float(j) * recHalfDens - 1.0f;  // y position on grid
			// distance squared from the center
			vertices[i][j][5] = vertices[i][j][3] * vertices[i][j][3]
				+ vertices[i][j][4] * vertices[i][j][4];
			vertices[i][j][6] = 0.0f;  // intensity
		}
	}

	// initialize constants
	for(i=0; i<NUMCONSTS; i++)
    {
		c[i] = myRandf(2.0f) - 1.0f;
		cr[i] = myRandf(PIx2);
		cv[i] = myRandf(_ec->dSpeed * 0.03f) + (_ec->dSpeed * 0.001f);
	}

	// pick color
	hsl[0] = myRandf(1.0f);
	hsl[1] = 0.1f + myRandf(0.9f);
	hsl[2] = 1.0f;
	hueSpeed = myRandf(0.1f) - 0.05f;
	saturationSpeed = myRandf(0.04f) + 0.001f;
}


wisp::~wisp()
{
	int i, j;

	for(i=0; i<=density; i++)
    {
		for(j=0; j<=density; j++)
        {
			delete[] vertices[i][j];
		}
		delete[] vertices[i];
	}
	delete[] vertices;
}


void wisp::update()
{
	int i, j;
	rsVec up, right, crossvec;
	// visibility constants
	static float viscon1 = float(_ec->dVisibility) * 0.01f;
	static float viscon2 = 1.0f / viscon1;

	// update constants
	for(i=0; i<NUMCONSTS; i++){
		cr[i] += cv[i] * _ec->elapsedTime;
		if(cr[i] > PIx2)
			cr[i] -= PIx2;
		c[i] = cos(cr[i]);
	}

	// update vertex positions
	for(i=0; i<=density; i++){
		for(j=0; j<=density; j++){
			vertices[i][j][0] = vertices[i][j][3] * vertices[i][j][3] * vertices[i][j][4] * c[0]
				+ vertices[i][j][5] * c[1] + 0.5f * c[2];
			vertices[i][j][1] = vertices[i][j][4] * vertices[i][j][4] * vertices[i][j][5] * c[3]
				+ vertices[i][j][3] * c[4] + 0.5f * c[5];
			vertices[i][j][2] = vertices[i][j][5] * vertices[i][j][5] * vertices[i][j][3] * c[6]
				+ vertices[i][j][4] * c[7] + c[8];
		}
	}

	// update vertex normals for most of mesh
	for(i=1; i<density; i++){
		for(j=1; j<density; j++){
			up.set(vertices[i][j+1][0] - vertices[i][j-1][0],
				vertices[i][j+1][1] - vertices[i][j-1][1],
				vertices[i][j+1][2] - vertices[i][j-1][2]);
			right.set(vertices[i+1][j][0] - vertices[i-1][j][0],
				vertices[i+1][j][1] - vertices[i-1][j][1],
				vertices[i+1][j][2] - vertices[i-1][j][2]);
			up.normalize();
			right.normalize();
			crossvec.cross(right, up);
			// Use depth component of normal to compute intensity
			// This way only edges of wisp are bright
			if(crossvec[2] < 0.0f)
				crossvec[2] *= -1.0f;
			vertices[i][j][6] = viscon2 * (viscon1 - crossvec[2]);
			if(vertices[i][j][6] > 1.0f)
				vertices[i][j][6] = 1.0f;
			if(vertices[i][j][6] < 0.0f)
				vertices[i][j][6] = 0.0f;
		}
	}

	// update color
	hsl[0] += hueSpeed * _ec->elapsedTime;
	if(hsl[0] < 0.0f)
		hsl[0] += 1.0f;
	if(hsl[0] > 1.0f)
		hsl[0] -= 1.0f;
	hsl[1] += saturationSpeed * _ec->elapsedTime;
	if(hsl[1] <= 0.1f){
		hsl[1] = 0.1f;
		saturationSpeed = -saturationSpeed;
	}
	if(hsl[1] >= 1.0f){
		hsl[1] = 1.0f;
		saturationSpeed = -saturationSpeed;
	}
	hsl2rgb(hsl[0], hsl[1], hsl[2], rgb[0], rgb[1], rgb[2]);
}


void wisp::draw()
{
	int i, j;

	glPushMatrix();

	if(_ec->dWireframe)
    {
		for(i=1; i<density; i++){
			glBegin(GL_LINE_STRIP);
			for(j=0; j<=density; j++){
				glColor3f(rgb[0] + vertices[i][j][6] - 1.0f, rgb[1] + vertices[i][j][6] - 1.0f, rgb[2] + vertices[i][j][6] - 1.0f);
				glTexCoord2d(vertices[i][j][3] - vertices[i][j][0], vertices[i][j][4] - vertices[i][j][1]);
				glVertex3fv(vertices[i][j]);
			}
			glEnd();
		}
		for(j=1; j<density; j++){
			glBegin(GL_LINE_STRIP);
			for(i=0; i<=density; i++){
				glColor3f(rgb[0] + vertices[i][j][6] - 1.0f, rgb[1] + vertices[i][j][6] - 1.0f, rgb[2] + vertices[i][j][6] - 1.0f);
				glTexCoord2d(vertices[i][j][3] - vertices[i][j][0], vertices[i][j][4] - vertices[i][j][1]);
				glVertex3fv(vertices[i][j]);
			}
			glEnd();
		}
	}
	else
    {
		for(i=0; i<density; i++){
			glBegin(GL_TRIANGLE_STRIP);
				for(j=0; j<=density; j++){
					glColor3f(rgb[0] + vertices[i+1][j][6] - 1.0f, rgb[1] + vertices[i+1][j][6] - 1.0f, rgb[2] + vertices[i+1][j][6] - 1.0f);
					glTexCoord2d(vertices[i+1][j][3] - vertices[i+1][j][0], vertices[i+1][j][4] - vertices[i+1][j][1]);
					glVertex3fv(vertices[i+1][j]);
					glColor3f(rgb[0] + vertices[i][j][6] - 1.0f, rgb[1] + vertices[i][j][6] - 1.0f, rgb[2] + vertices[i][j][6] - 1.0f);
					glTexCoord2d(vertices[i][j][3] - vertices[i][j][0], vertices[i][j][4] - vertices[i][j][1]);
					glVertex3fv(vertices[i][j]);
				}
			glEnd();
		}
	}

	glPopMatrix();
}


void wisp::drawAsBackground()
{
	int i, j;

	glPushMatrix();
	glTranslatef(c[0] * 0.2f, c[1] * 0.2f, 1.6f);

	if(_ec->dWireframe)
    {
		for(i=1; i<density; i++){
			glBegin(GL_LINE_STRIP);
			for(j=0; j<=density; j++){
				glColor3f(rgb[0] + vertices[i][j][6] - 1.0f, rgb[1] + vertices[i][j][6] - 1.0f, rgb[2] + vertices[i][j][6] - 1.0f);
				glTexCoord2d(vertices[i][j][3] - vertices[i][j][0], vertices[i][j][4] - vertices[i][j][1]);
				glVertex3f(vertices[i][j][3], vertices[i][j][4], vertices[i][j][6]);
			}
			glEnd();
		}
		for(j=1; j<density; j++){
			glBegin(GL_LINE_STRIP);
			for(i=0; i<=density; i++){
				glColor3f(rgb[0] + vertices[i][j][6] - 1.0f, rgb[1] + vertices[i][j][6] - 1.0f, rgb[2] + vertices[i][j][6] - 1.0f);
				glTexCoord2d(vertices[i][j][3] - vertices[i][j][0], vertices[i][j][4] - vertices[i][j][1]);
				glVertex3f(vertices[i][j][3], vertices[i][j][4], vertices[i][j][6]);
			}
			glEnd();
		}
	}
	else
    {
		for(i=0; i<density; i++){
			glBegin(GL_TRIANGLE_STRIP);
				for(j=0; j<=density; j++){
					glColor3f(rgb[0] + vertices[i+1][j][6] - 1.0f, rgb[1] + vertices[i+1][j][6] - 1.0f, rgb[2] + vertices[i+1][j][6] - 1.0f);
					glTexCoord2d(vertices[i+1][j][3] - vertices[i+1][j][0], vertices[i+1][j][4] - vertices[i+1][j][1]);
					glVertex3f(vertices[i+1][j][3], vertices[i+1][j][4], vertices[i+1][j][6]);
					glColor3f(rgb[0] + vertices[i][j][6] - 1.0f, rgb[1] + vertices[i][j][6] - 1.0f, rgb[2] + vertices[i][j][6] - 1.0f);
					glTexCoord2d(vertices[i][j][3] - vertices[i][j][0], vertices[i][j][4] - vertices[i][j][1]);
					glVertex3f(vertices[i][j][3], vertices[i][j][4], vertices[i][j][6]);
				}
			glEnd();
		}
	}

	glPopMatrix();
}


//----------------------------------------------------------------------------


EuphoriaWidget::EuphoriaWidget( QWidget* parent )
              : QGLWidget(parent), texName(0), _wisps(0), _backwisps(0),
	      feedbackmap(0), feedbacktex(0)
{
    setDefaults( Regular );

    _frameTime = 1000 / 60;
    _timer = new QTimer( this );
    _timer->setSingleShot( true );
    connect( _timer, SIGNAL(timeout()), this, SLOT(nextFrame()) );
}


EuphoriaWidget::~EuphoriaWidget()
{
	// Free memory
	if ( texName )
		glDeleteTextures( 1, &texName );
	if ( feedbacktex )
		glDeleteTextures( 1, &feedbacktex );
	delete[] _wisps;
	delete[] _backwisps;
}


void EuphoriaWidget::paintGL()
{
	int i;
	static double lastTime = timeGetTime();

	// update time
	elapsedTime = timeGetTime() - lastTime;
	lastTime += elapsedTime;

    _ec = this;

	// Update wisps
	for(i=0; i<dWisps; i++)
		_wisps[i].update();
	for(i=0; i<dBackground; i++)
		_backwisps[i].update();


	if(dFeedback)
    {
		float feedbackIntensity = float(dFeedback) / 101.0f;

		// update feedback variables
		for(i=0; i<4; i++)
        {
			fr[i] += elapsedTime * fv[i];
			if(fr[i] > PIx2)
				fr[i] -= PIx2;
		}
		f[0] = 30.0f * cos(fr[0]);
		f[1] = 0.2f * cos(fr[1]);
		f[2] = 0.2f * cos(fr[2]);
		f[3] = 0.8f * cos(fr[3]);
		for(i=0; i<3; i++)
        {
			lr[i] += elapsedTime * lv[i];
			if(lr[i] > PIx2)
				lr[i] -= PIx2;
			l[i] = cos(lr[i]);
			l[i] = l[i] * l[i];
		}

		// Create drawing area for feedback texture
		glViewport(0, 0, feedbacktexsize, feedbacktexsize);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(30.0, aspectRatio, 0.01f, 20.0f);
		glMatrixMode(GL_MODELVIEW);

		// Draw
		glClear(GL_COLOR_BUFFER_BIT);
		glColor3f(feedbackIntensity, feedbackIntensity, feedbackIntensity);
		glBindTexture(GL_TEXTURE_2D, feedbacktex);
		glPushMatrix();
		glTranslatef(f[1] * l[1], f[2] * l[1], f[3] * l[2]);
		glRotatef(f[0] * l[0], 0, 0, 1);
		glBegin(GL_TRIANGLE_STRIP);
			glTexCoord2f(-0.5f, -0.5f);
			glVertex3f(-aspectRatio*2.0f, -2.0f, 1.25f);
			glTexCoord2f(1.5f, -0.5f);
			glVertex3f(aspectRatio*2.0f, -2.0f, 1.25f);
			glTexCoord2f(-0.5f, 1.5f);
			glVertex3f(-aspectRatio*2.0f, 2.0f, 1.25f);
			glTexCoord2f(1.5f, 1.5f);
			glVertex3f(aspectRatio*2.0f, 2.0f, 1.25f);
		glEnd();
		glPopMatrix();
		glBindTexture(GL_TEXTURE_2D, texName);
		for(i=0; i<dBackground; i++)
			_backwisps[i].drawAsBackground();
		for(i=0; i<dWisps; i++)
			_wisps[i].draw();

		// readback feedback texture
		glReadBuffer(GL_BACK);
		glPixelStorei(GL_UNPACK_ROW_LENGTH, feedbacktexsize);
		glBindTexture(GL_TEXTURE_2D, feedbacktex);
		glReadPixels(0, 0, feedbacktexsize, feedbacktexsize, GL_RGB, GL_UNSIGNED_BYTE, feedbackmap);
		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, feedbacktexsize, feedbacktexsize, GL_RGB, GL_UNSIGNED_BYTE, feedbackmap);

		// create regular drawing area
		glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(20.0, aspectRatio, 0.01f, 20.0f);
		glMatrixMode(GL_MODELVIEW);

		// Draw again
		glClear(GL_COLOR_BUFFER_BIT);
		glColor3f(feedbackIntensity, feedbackIntensity, feedbackIntensity);
		glPushMatrix();
		glTranslatef(f[1] * l[1], f[2] * l[1], f[3] * l[2]);
		glRotatef(f[0] * l[0], 0, 0, 1);
		glBegin(GL_TRIANGLE_STRIP);
			glTexCoord2f(-0.5f, -0.5f);
			glVertex3f(-aspectRatio*2.0f, -2.0f, 1.25f);
			glTexCoord2f(1.5f, -0.5f);
			glVertex3f(aspectRatio*2.0f, -2.0f, 1.25f);
			glTexCoord2f(-0.5f, 1.5f);
			glVertex3f(-aspectRatio*2.0f, 2.0f, 1.25f);
			glTexCoord2f(1.5f, 1.5f);
			glVertex3f(aspectRatio*2.0f, 2.0f, 1.25f);
		glEnd();
		glPopMatrix();

		glBindTexture(GL_TEXTURE_2D, texName);
	}
	else
		glClear(GL_COLOR_BUFFER_BIT);

	//
	for(i=0; i<dBackground; i++)
		_backwisps[i].drawAsBackground();
	for(i=0; i<dWisps; i++)
		_wisps[i].draw();

	glFlush();
}


void EuphoriaWidget::resizeGL( int w, int h )
{
    glViewport(0, 0, w, h );

	viewport[0] = 0;
	viewport[1] = 0;
	viewport[2] = w;
	viewport[3] = h;

	aspectRatio = (float) w / (float) h;

	// setup regular drawing area just in case feedback isn't used
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
    gluPerspective(20.0, aspectRatio, 0.01, 20);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.0, 0.0, -5.0);
}


// Window initialization
void EuphoriaWidget::initializeGL()
{
    // Need to call this to setup viewport[] parameters used in
    // the next updateParameters() call
    resizeGL( width(), height() );

    updateParameters();

    _timer->start( _frameTime );
}


#ifdef UNIT_TEST
void EuphoriaWidget::keyPressEvent( QKeyEvent* e )
{
    if( e->key() == Qt::Key_0 ) { setDefaults( 0 ); updateParameters(); }
    if( e->key() == Qt::Key_1 ) { setDefaults( 1 ); updateParameters(); }
    if( e->key() == Qt::Key_2 ) { setDefaults( 2 ); updateParameters(); }
    if( e->key() == Qt::Key_3 ) { setDefaults( 3 ); updateParameters(); }
    if( e->key() == Qt::Key_4 ) { setDefaults( 4 ); updateParameters(); }
    if( e->key() == Qt::Key_5 ) { setDefaults( 5 ); updateParameters(); }
    if( e->key() == Qt::Key_6 ) { setDefaults( 6 ); updateParameters(); }
    if( e->key() == Qt::Key_7 ) { setDefaults( 7 ); updateParameters(); }
    if( e->key() == Qt::Key_8 ) { setDefaults( 8 ); updateParameters(); }
}
#endif


void EuphoriaWidget::nextFrame()
{
    updateGL();
    _timer->start( _frameTime );
}


void EuphoriaWidget::updateParameters()
{
	srand((unsigned)time(NULL));
	rand(); rand(); rand(); rand(); rand();

    elapsedTime = 0.0f;

    fr[0] = 0.0f;
    fr[1] = 0.0f;
    fr[2] = 0.0f;
    fr[3] = 0.0f;

    lr[0] = 0.0f;
    lr[1] = 0.0f;
    lr[2] = 0.0f;

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE, GL_ONE);
	glLineWidth(2.0f);

	// Commented out because smooth lines and textures don't mix on my TNT.
	// It's like it rendering in software mode
	glEnable(GL_LINE_SMOOTH);
	//glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

	if(dTexture)
    {
		int whichtex = dTexture;
		if(whichtex == 4)  // random texture
			whichtex = myRandi(3) + 1;
		glEnable(GL_TEXTURE_2D);
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		// Initialize texture
		glGenTextures(1, &texName);
		glBindTexture(GL_TEXTURE_2D, texName);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		switch(whichtex){
		case 1:
			gluBuild2DMipmaps(GL_TEXTURE_2D, 1, TEXSIZE, TEXSIZE, GL_LUMINANCE, GL_UNSIGNED_BYTE, plasmamap);
			break;
		case 2:
			gluBuild2DMipmaps(GL_TEXTURE_2D, 1, TEXSIZE, TEXSIZE, GL_LUMINANCE, GL_UNSIGNED_BYTE, stringymap);
			break;
		case 3:
			gluBuild2DMipmaps(GL_TEXTURE_2D, 1, TEXSIZE, TEXSIZE, GL_LUMINANCE, GL_UNSIGNED_BYTE, linesmap);
		}
	} else if ( texName ) {
		glDeleteTextures( 1, &texName );
		texName = 0;
	}

	if(dFeedback)
    {
		feedbacktexsize = int(pow(2.0, dFeedbacksize));
		while(feedbacktexsize > viewport[2] || feedbacktexsize > viewport[3]){
			dFeedbacksize -= 1;
			feedbacktexsize = int(pow(2.0, dFeedbacksize));
		}

		// feedback texture setup
		glEnable(GL_TEXTURE_2D);
		delete [] feedbackmap;
		feedbackmap = new unsigned char[feedbacktexsize*feedbacktexsize*3];
		glGenTextures(1, &feedbacktex);
		glBindTexture(GL_TEXTURE_2D, feedbacktex);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexImage2D(GL_TEXTURE_2D, 0, 3, feedbacktexsize, feedbacktexsize, 0, GL_RGB, GL_UNSIGNED_BYTE, feedbackmap);

		// feedback velocity variable setup
		fv[0] = float(dFeedbackspeed) * (myRandf(0.025f) + 0.025f);
		fv[1] = float(dFeedbackspeed) * (myRandf(0.05f) + 0.05f);
		fv[2] = float(dFeedbackspeed) * (myRandf(0.05f) + 0.05f);
		fv[3] = float(dFeedbackspeed) * (myRandf(0.1f) + 0.1f);
		lv[0] = float(dFeedbackspeed) * (myRandf(0.0025f) + 0.0025f);
		lv[1] = float(dFeedbackspeed) * (myRandf(0.0025f) + 0.0025f);
		lv[2] = float(dFeedbackspeed) * (myRandf(0.0025f) + 0.0025f);
	} else if ( feedbacktex ) {
		glDeleteTextures( 1, &feedbacktex );
		feedbacktex = 0;
	}

	// Initialize wisps
    _ec = this;
    delete[] _wisps;
    delete[] _backwisps;
	_wisps     = new wisp[dWisps];
	_backwisps = new wisp[dBackground];
}


/**
  May be called at any time - makes no OpenGL calls.
*/
void EuphoriaWidget::setDefaults(int which)
{
	switch(which)
    {
	case Grid:
		dWisps = 4;
		dBackground = 1;
		dDensity = 25;
		dVisibility = 70;
		dSpeed = 15;
		dFeedback = 0;
		dFeedbackspeed = 1;
		dFeedbacksize = 8;
		dWireframe = 1;
		dTexture = 0;
		break;

    case Cubism:
		dWisps = 15;
		dBackground = 0;
		dDensity = 4;
		dVisibility = 15;
		dSpeed = 10;
		dFeedback = 0;
		dFeedbackspeed = 1;
		dFeedbacksize = 8;
		dWireframe = 0;
		dTexture = 0;
		break;

    case BadMath:
		dWisps = 2;
		dBackground = 2;
		dDensity = 20;
		dVisibility = 40;
		dSpeed = 30;
		dFeedback = 40;
		dFeedbackspeed = 5;
		dFeedbacksize = 8;
		dWireframe = 1;
		dTexture = 2;
		break;

    case MTheory:
		dWisps = 3;
		dBackground = 0;
		dDensity = 25;
		dVisibility = 15;
		dSpeed = 20;
		dFeedback = 40;
		dFeedbackspeed = 20;
		dFeedbacksize = 8;
		dWireframe = 0;
		dTexture = 0;
		break;

	case UHFTEM:
		dWisps = 0;
		dBackground = 3;
		dDensity = 35;
		dVisibility = 5;
		dSpeed = 50;
		dFeedback = 0;
		dFeedbackspeed = 1;
		dFeedbacksize = 8;
		dWireframe = 0;
		dTexture = 0;
		break;

    case Nowhere:
		dWisps = 0;
		dBackground = 3;
		dDensity = 30;
		dVisibility = 40;
		dSpeed = 20;
		dFeedback = 80;
		dFeedbackspeed = 10;
		dFeedbacksize = 8;
		dWireframe = 1;
		dTexture = 3;
		break;

    case Echo:
		dWisps = 3;
		dBackground = 0;
		dDensity = 25;
		dVisibility = 30;
		dSpeed = 20;
		dFeedback = 85;
		dFeedbackspeed = 30;
		dFeedbacksize = 8;
		dWireframe = 0;
		dTexture = 1;
		break;

    case Kaleidoscope:
		dWisps = 3;
		dBackground = 0;
		dDensity = 25;
		dVisibility = 40;
		dSpeed = 15;
		dFeedback = 90;
		dFeedbackspeed = 3;
		dFeedbacksize = 8;
		dWireframe = 0;
		dTexture = 0;
		break;

    case Regular:
    default:
		dWisps = 5;
		dBackground = 0;
		dDensity = 25;
		dVisibility = 35;
		dSpeed = 15;
		dFeedback = 0;
		dFeedbackspeed = 1;
		dFeedbacksize = 8;
		dWireframe = 0;
		dTexture = 2;
		break;
	}
}


//----------------------------------------------------------------------------


#ifndef UNIT_TEST
#include <klocale.h>
#include <kglobal.h>
#include <kconfig.h>


// libkscreensaver interface
class KEuphoriaSaverInterface : public KScreenSaverInterface
{


public:
    virtual KAboutData* aboutData() {
        return new KAboutData( "keuphoria.kss", "klock", ki18n( "Euphoria" ), "1.0", ki18n( "Euphoria" ) );
    }


    virtual KScreenSaver* create( WId id )
    {
        return new KEuphoriaScreenSaver( id );
    }

    virtual QDialog* setup()
    {
        return new KEuphoriaSetup;
    }
};

int main( int argc, char *argv[] )
{
    KEuphoriaSaverInterface kss;
    return kScreenSaverMain( argc, argv, kss );
}


//----------------------------------------------------------------------------


KEuphoriaScreenSaver::KEuphoriaScreenSaver( WId id ) : KScreenSaver( id )
{
    _effect = new EuphoriaWidget;

    readSettings();

    embed( _effect );
    _effect->show();
}


KEuphoriaScreenSaver::~KEuphoriaScreenSaver()
{
}


static int filterRandom( int n )
{
    if( (n < 0) || (n >= EuphoriaWidget::DefaultModes) )
    {
        srand((unsigned)time(NULL));
        n = rand() % EuphoriaWidget::DefaultModes;
    }
    return n;
}


void KEuphoriaScreenSaver::readSettings()
{
    KConfigGroup config(KGlobal::config(), "Settings");

    _mode = config.readEntry( "Mode", (int)EuphoriaWidget::Regular );
    _effect->setDefaults( filterRandom(_mode) );
}


/**
  Any invalid mode will select one at random.
*/
void KEuphoriaScreenSaver::setMode( int id )
{
    _mode = id;
    _effect->setDefaults( filterRandom(id) );
    _effect->updateParameters();
}


//----------------------------------------------------------------------------


#include <qlayout.h>
#include <qlabel.h>
#include <qcombobox.h>
#include <kmessagebox.h>


static const char* defaultText[] =
{
    I18N_NOOP( "Regular" ),
    I18N_NOOP( "Grid" ),
    I18N_NOOP( "Cubism" ),
    I18N_NOOP( "Bad Math" ),
    I18N_NOOP( "M-Theory" ),
    I18N_NOOP( "UHFTEM" ), //"ultra high frequency tunneling electron microscope",
    I18N_NOOP( "Nowhere" ),
    I18N_NOOP( "Echo" ),
    I18N_NOOP( "Kaleidoscope" ),
    I18N_NOOP( "(Random)" ),
    0
};


KEuphoriaSetup::KEuphoriaSetup( QWidget* parent )
        : KDialog( parent)
{
    setCaption(i18n("Setup Euphoria Screen Saver"));
	setButtons(Ok|Cancel|Help);
	setDefaultButton(Ok);
	setModal(true);
	showButtonSeparator(true);
    setButtonText( Help, i18n( "A&bout" ) );

    QWidget *main = new QWidget(this);
	setMainWidget(main);

    QHBoxLayout* top = new QHBoxLayout( main );
    top->setSpacing( spacingHint() );
    QVBoxLayout* leftCol = new QVBoxLayout;
    top->addLayout( leftCol );

    QLabel* label = new QLabel( i18n("Mode:"), main );
    leftCol->addWidget( label );

    modeW = new QComboBox( main );
    int i = 0;
    while (defaultText[i])
        modeW->addItem( i18n(defaultText[i++]) );
    leftCol->addWidget( modeW );

    leftCol->addStretch();

    // Preview
    QWidget* preview;
    preview = new QWidget( main );
    preview->setFixedSize( 220, 170 );
    {
        QPalette palette;
        palette.setColor( preview->backgroundRole(), Qt::black );
        preview->setPalette( palette );
	preview->setAutoFillBackground(true);
    }
    preview->show();    // otherwise saver does not get correct size
    _saver = new KEuphoriaScreenSaver( preview->winId() );
    top->addWidget(preview);

    // Now that we have _saver...
    modeW->setCurrentIndex( _saver->mode() );    // set before we connect
    connect( modeW, SIGNAL(activated(int)), _saver, SLOT(setMode(int)) );
	connect(this,SIGNAL(okClicked()),SLOT(slotOk()));
	connect(this,SIGNAL(helpClicked()),SLOT(slotHelp()));

    setMinimumSize( sizeHint() );
}


KEuphoriaSetup::~KEuphoriaSetup()
{
    delete _saver;
}


void KEuphoriaSetup::slotHelp()
{
    KMessageBox::about(this,
        i18n("<h3>Euphoria 1.0</h3>\n<p>Copyright (c) 2002 Terence M. Welsh<br>\n<a href=\"http://www.reallyslick.com/\">http://www.reallyslick.com/</a></p>\n\n<p>Ported to KDE by Karl Robillard</p>"),
        QString(), KMessageBox::AllowLink);
}


/**
  Ok pressed - save settings and exit
*/
void KEuphoriaSetup::slotOk()
{
    KConfigGroup config( KGlobal::config(), "Settings");

    config.writeEntry("Mode", QString::number( modeW->currentIndex() ) );

    config.sync();
    accept();
}
#endif
//----------------------------------------------------------------------------


#ifdef UNIT_TEST
// moc Euphoria.h -o Euphoria.moc
// g++ -g -DUNIT_TEST Euphoria.cpp -I/usr/lib/qt3/include -lqt -L/usr/lib/qt3/lib -lGLU -lGL

#include <qapplication.h>

int main( int argc, char** argv )
{
    QApplication app( argc, argv );

    EuphoriaWidget w;
    w.setDefaults( EuphoriaWidget::UHFTEM );
    app.setMainWidget( &w );
    w.show();

    return app.exec();
}
#endif


//EOF
