//============================================================================ // // Terence Welsh Screensaver - Flux // http://www.reallyslick.com/ // // Ported to KDE by Karl Robillard // /* * Copyright (C) 2002 Terence M. Welsh * * Flux is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * Flux 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ //============================================================================ /* TODO [ ] Regular and others are messed up after Sparkler. Insane seems to reset them. */ #include #include #include #include #include #include "Flux.h" #include "Flux.moc" #define NUMCONSTS 8 #define PIx2 6.28318530718f #define DEG2RAD 0.0174532925f 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)); } //---------------------------------------------------------------------------- // Flux context to allow many instances. static FluxWidget* _fc = 0; static int whichparticle; // This class is poorly named. It's actually a whole trail of particles. class particle { public: particle(); ~particle(); float update(float *c); private: float** vertices; short trails; short counter; float offset[3]; }; particle::particle() { // Offsets are somewhat like default positions for the head of each // particle trail. Offsets spread out the particle trails and keep // them from all overlapping. offset[0] = cos(PIx2 * float(whichparticle) / float(_fc->dParticles)); offset[1] = float(whichparticle) / float(_fc->dParticles) - 0.5f; offset[2] = sin(PIx2 * float(whichparticle) / float(_fc->dParticles)); whichparticle++; // Initialize memory and set initial positions out of view of the camera trails = _fc->dTrail; vertices = new float*[ trails ]; int i; for(i=0; idExpansion); static float blower = 0.001f * float(_fc->dWind); //static float otherxyz[3]; float depth = 0; // Record old position int oldc = counter; float oldpos[3]; oldpos[0] = vertices[oldc][0]; oldpos[1] = vertices[oldc][1]; oldpos[2] = vertices[oldc][2]; counter ++; if(counter >= _fc->dTrail) counter = 0; // Here's the iterative math for calculating new vertex positions // first calculate limiting terms which keep vertices from constantly // flying off to infinity cx = vertices[oldc][0] * (1.0f - 1.0f / (vertices[oldc][0] * vertices[oldc][0] + 1.0f)); cy = vertices[oldc][1] * (1.0f - 1.0f / (vertices[oldc][1] * vertices[oldc][1] + 1.0f)); cz = vertices[oldc][2] * (1.0f - 1.0f / (vertices[oldc][2] * vertices[oldc][2] + 1.0f)); // then calculate new positions vertices[counter][0] = vertices[oldc][0] + c[6] * offset[0] - cx + c[2] * vertices[oldc][1] + c[5] * vertices[oldc][2]; vertices[counter][1] = vertices[oldc][1] + c[6] * offset[1] - cy + c[1] * vertices[oldc][2] + c[4] * vertices[oldc][0]; vertices[counter][2] = vertices[oldc][2] + c[6] * offset[2] - cz + c[0] * vertices[oldc][0] + c[3] * vertices[oldc][1]; // Pick a hue vertices[counter][3] = cx * cx + cy * cy + cz * cz; if(vertices[counter][3] > 1.0f) vertices[counter][3] = 1.0f; vertices[counter][3] += c[7]; // Limit the hue (0 - 1) if(vertices[counter][3] > 1.0f) vertices[counter][3] -= 1.0f; if(vertices[counter][3] < 0.0f) vertices[counter][3] += 1.0f; // Pick a saturation vertices[counter][4] = c[0] + vertices[counter][3]; // Limit the saturation (0 - 1) if(vertices[counter][4] < 0.0f) vertices[counter][4] = -vertices[counter][4]; vertices[counter][4] -= float(int(vertices[counter][4])); vertices[counter][4] = 1.0f - (vertices[counter][4] * vertices[counter][4]); // Bring particles back if they escape if(!counter){ if((vertices[0][0] > 1000000000.0f) || (vertices[0][0] < -1000000000.0f) || (vertices[0][1] > 1000000000.0f) || (vertices[0][1] < -1000000000.0f) || (vertices[2][2] > 1000000000.0f) || (vertices[0][2] < -1000000000.0f)){ vertices[0][0] = myRandf(2.0f) - 1.0f; vertices[0][1] = myRandf(2.0f) - 1.0f; vertices[0][2] = myRandf(2.0f) - 1.0f; } } // Draw every vertex in particle trail p = counter; growth = 0; luminosity = _fc->lumdiff; for(i=0; i<_fc->dTrail; i++){ p ++; if(p >= _fc->dTrail) p = 0; growth++; // assign color to particle hsl2rgb(vertices[p][3], vertices[p][4], luminosity, rgb[0], rgb[1], rgb[2]); glColor3fv(rgb); glPushMatrix(); if(_fc->dGeometry == 1) // Spheres glTranslatef(vertices[p][0], vertices[p][1], vertices[p][2]); else{ // Points or lights depth = _fc->cosCameraAngle * vertices[p][2] - _fc->sinCameraAngle * vertices[p][0]; glTranslatef(_fc->cosCameraAngle * vertices[p][0] + _fc->sinCameraAngle * vertices[p][2], vertices[p][1], depth); } if(_fc->dGeometry){ // Spheres or lights switch(_fc->dTrail - growth){ case 0: glScalef(0.259f, 0.259f, 0.259f); break; case 1: glScalef(0.5f, 0.5f, 0.5f); break; case 2: glScalef(0.707f, 0.707f, 0.707f); break; case 3: glScalef(0.866f, 0.866f, 0.866f); break; case 4: glScalef(0.966f, 0.966f, 0.966f); } } switch(_fc->dGeometry){ case 0: // Points switch(_fc->dTrail - growth){ case 0: glPointSize(float(_fc->dSize * (depth + 200.0f) * 0.001036f)); break; case 1: glPointSize(float(_fc->dSize * (depth + 200.0f) * 0.002f)); break; case 2: glPointSize(float(_fc->dSize * (depth + 200.0f) * 0.002828f)); break; case 3: glPointSize(float(_fc->dSize * (depth + 200.0f) * 0.003464f)); break; case 4: glPointSize(float(_fc->dSize * (depth + 200.0f) * 0.003864f)); break; default: glPointSize(float(_fc->dSize * (depth + 200.0f) * 0.004f)); } glBegin(GL_POINTS); glVertex3f(0.0f,0.0f,0.0f); glEnd(); break; case 1: // Spheres case 2: // Lights glCallList(1); } glPopMatrix(); vertices[p][0] *= expander; vertices[p][1] *= expander; vertices[p][2] *= expander; vertices[p][2] += blower; luminosity += _fc->lumdiff; } // Find distance between new position and old position and return it oldpos[0] -= vertices[counter][0]; oldpos[1] -= vertices[counter][1]; oldpos[2] -= vertices[counter][2]; return(float(sqrt(oldpos[0] * oldpos[0] + oldpos[1] * oldpos[1] + oldpos[2] * oldpos[2]))); } // This class is a set of particle trails and constants that enter // into their equations of motion. class flux { public: flux(); ~flux(); void update(); particle *particles; int randomize; float c[NUMCONSTS]; // constants float cv[NUMCONSTS]; // constants' change velocities int currentSmartConstant; float oldDistance; }; flux::flux() { whichparticle = 0; particles = new particle[_fc->dParticles]; randomize = 1; float instability = _fc->dInstability; int i; for(i=0; idRandomize){ randomize --; if(randomize <= 0){ for(i=0; idRandomize; temp = temp * temp; randomize = temp + myRandi(temp); } } // update constants for(i=0; i= 1.0f){ c[i] = 1.0f; cv[i] = -cv[i]; } if(c[i] <= -1.0f){ c[i] = -1.0f; cv[i] = -cv[i]; } } // update all particles in this flux field float dist; for(i=0; i<_fc->dParticles; i++) dist = particles[i].update(c); // use dist from last particle to activate smart constants _fc->dSmart = 0; if(_fc->dSmart){ const float upper = 0.4f; const float lower = 0.2f; int beSmart = 0; if(dist > upper && dist > oldDistance) beSmart = 1; if(dist < lower && dist < oldDistance) beSmart = 1; if(beSmart){ cv[currentSmartConstant] = -cv[currentSmartConstant]; currentSmartConstant ++; if(currentSmartConstant >= _fc->dSmart) currentSmartConstant = 0; } oldDistance = dist; } } //---------------------------------------------------------------------------- FluxWidget::FluxWidget( QWidget* parent, const char* name ) : QGLWidget(parent, name), _fluxes(0) { setDefaults( Regular ); _frameTime = 1000 / 60; _timer = new QTimer( this ); connect( _timer, SIGNAL(timeout()), this, SLOT(nextFrame()) ); } FluxWidget::~FluxWidget() { // Free memory delete[] _fluxes; } void FluxWidget::paintGL() { // clear the screen glLoadIdentity(); if(dBlur) // partially { int viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); float viewRatio = float(viewport[2]) / float(viewport[3]); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); glColor4f(0.0f, 0.0f, 0.0f, 0.5f - (float(sqrt(sqrt(double(dBlur)))) * 0.15495f)); glBegin(GL_TRIANGLE_STRIP); glVertex3f(-3.0f * viewRatio, -3.0f, 0.0f); glVertex3f(3.0f * viewRatio, -3.0f, 0.0f); glVertex3f(-3.0f * viewRatio, 3.0f, 0.0f); glVertex3f(3.0f * viewRatio, 3.0f, 0.0f); glEnd(); } else // completely { glClear(GL_COLOR_BUFFER_BIT); } cameraAngle += 0.01f * float(dRotation); if(cameraAngle >= 360.0f) cameraAngle -= 360.0f; if(dGeometry == 1) // Only rotate for spheres glRotatef(cameraAngle, 0.0f, 1.0f, 0.0f); else { cosCameraAngle = cos(cameraAngle * DEG2RAD); sinCameraAngle = sin(cameraAngle * DEG2RAD); } // set up blend modes for rendering particles switch(dGeometry) { case 0: // Blending for points glBlendFunc(GL_SRC_ALPHA, GL_ONE); glEnable(GL_BLEND); glEnable(GL_POINT_SMOOTH); glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); break; case 1: // No blending for spheres, but we need z-buffering glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); glClear(GL_DEPTH_BUFFER_BIT); break; case 2: // Blending for lights glBlendFunc(GL_ONE, GL_ONE); glEnable(GL_BLEND); } // Update particles if( _fluxes ) { _fc = this; int i; for(i=0; istart( _frameTime, true ); } #ifdef UNIT_TEST void FluxWidget::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(); } } #endif void FluxWidget::nextFrame() { updateGL(); _timer->start( _frameTime, true ); } /** May be called at any time - makes no OpenGL calls. */ void FluxWidget::setDefaults( int which ) { switch(which) { case Hypnotic: dFluxes = 2; dParticles = 10; dTrail = 40; dGeometry = 2; dSize = 15; dRandomize = 80; dExpansion = 20; dRotation = 0; dWind = 40; dInstability = 10; dBlur = 30; break; case Insane: dFluxes = 4; dParticles = 30; dTrail = 8; dGeometry = 2; dSize = 25; dRandomize = 0; dExpansion = 80; dRotation = 60; dWind = 40; dInstability = 100; dBlur = 10; break; case Sparklers: dFluxes = 3; dParticles = 20; dTrail = 6; dGeometry = 1; dSize = 20; dComplexity = 3; dRandomize = 85; dExpansion = 60; dRotation = 30; dWind = 20; dInstability = 30; dBlur = 0; break; case Paradigm: dFluxes = 1; dParticles = 40; dTrail = 40; dGeometry = 2; dSize = 5; dRandomize = 90; dExpansion = 30; dRotation = 20; dWind = 10; dInstability = 5; dBlur = 10; break; case Galactic: dFluxes = 1; dParticles = 2; dTrail = 1500; dGeometry = 2; dSize = 10; dRandomize = 0; dExpansion = 5; dRotation = 25; dWind = 0; dInstability = 5; dBlur = 0; break; case Regular: default: dFluxes = 1; dParticles = 20; dTrail = 40; dGeometry = 2; dSize = 15; dRandomize = 0; dExpansion = 40; dRotation = 30; dWind = 20; dInstability = 20; dBlur = 0; break; } } /** Called after dGeometry, dTrail, or dFluxes is changed (such as with setDefaults). */ void FluxWidget::updateParameters() { int i, j; float x, y, temp; srand((unsigned)time(NULL)); rand(); rand(); rand(); rand(); rand(); cameraAngle = 0.0f; glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if(dGeometry == 0) { glDisable(GL_LIGHTING); glDisable(GL_COLOR_MATERIAL); glDisable(GL_TEXTURE_2D); glEnable(GL_POINT_SMOOTH); //glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); } else if(dGeometry == 1) // Spheres and their lighting { glNewList(1, GL_COMPILE); GLUquadricObj* qobj = gluNewQuadric(); gluSphere(qobj, 0.005f * dSize, dComplexity + 2, dComplexity + 1); gluDeleteQuadric(qobj); glEndList(); glDisable(GL_TEXTURE_2D); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); float ambient[4] = {0.0f, 0.0f, 0.0f, 0.0f}; float diffuse[4] = {1.0f, 1.0f, 1.0f, 0.0f}; float specular[4] = {1.0f, 1.0f, 1.0f, 0.0f}; float position[4] = {500.0f, 500.0f, 500.0f, 0.0f}; glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse); glLightfv(GL_LIGHT0, GL_SPECULAR, specular); glLightfv(GL_LIGHT0, GL_POSITION, position); glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); } else if(dGeometry == 2) // Init lights { for(i=0; i 1.0f) temp = 1.0f; if(temp < 0.0f) temp = 0.0f; lightTexture[i][j] = (unsigned char) (255.0f * temp * temp); } } glDisable(GL_LIGHTING); glDisable(GL_COLOR_MATERIAL); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 1); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, 1, LIGHTSIZE, LIGHTSIZE, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, lightTexture); temp = float(dSize) * 0.005f; glNewList(1, GL_COMPILE); glBindTexture(GL_TEXTURE_2D, 1); glBegin(GL_TRIANGLES); glTexCoord2f(0.0f, 0.0f); glVertex3f(-temp, -temp, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(temp, -temp, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(temp, temp, 0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-temp, -temp, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(temp, temp, 0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-temp, temp, 0.0f); glEnd(); glEndList(); } // Initialize luminosity difference lumdiff = 1.0f / float(dTrail); _fc = this; delete[] _fluxes; _fluxes = new flux[dFluxes]; } //---------------------------------------------------------------------------- #ifndef UNIT_TEST #include #include #include // libkscreensaver interface extern "C" { KDE_EXPORT const char* kss_applicationName = "kflux.kss"; KDE_EXPORT const char* kss_description = I18N_NOOP( "Flux" ); KDE_EXPORT const char* kss_version = "1.0"; KDE_EXPORT KScreenSaver* kss_create( WId id ) { return new KFluxScreenSaver( id ); } KDE_EXPORT QDialog* kss_setup() { return new KFluxSetup; } } //---------------------------------------------------------------------------- KFluxScreenSaver::KFluxScreenSaver( WId id ) : KScreenSaver( id ) { _flux = new FluxWidget; readSettings(); embed( _flux ); _flux->show(); } KFluxScreenSaver::~KFluxScreenSaver() { } static int filterRandom( int n ) { if( (n < 0) || (n >= FluxWidget::DefaultModes) ) { srand((unsigned)time(NULL)); n = rand() % FluxWidget::DefaultModes; } return n; } void KFluxScreenSaver::readSettings() { KConfig* config = KGlobal::config(); config->setGroup("Settings"); _mode = config->readNumEntry( "Mode", FluxWidget::Regular ); _flux->setDefaults( filterRandom(_mode) ); } /** Any invalid mode will select one at random. */ void KFluxScreenSaver::setMode( int id ) { _mode = id; _flux->setDefaults( filterRandom(id) ); _flux->updateParameters(); } //---------------------------------------------------------------------------- #include #include #include #include #include #include static const char* defaultText[] = { I18N_NOOP( "Regular" ), I18N_NOOP( "Hypnotic" ), I18N_NOOP( "Insane" ), I18N_NOOP( "Sparklers" ), I18N_NOOP( "Paradigm" ), I18N_NOOP( "Galactic" ), I18N_NOOP( "(Random)" ), 0 }; KFluxSetup::KFluxSetup( QWidget* parent, const char* name ) : KDialogBase( parent, name, true, i18n( "Setup Flux Screen Saver" ), Ok|Cancel|Help, Ok, true ) { setButtonText( Help, i18n( "A&bout" ) ); QWidget *main = makeMainWidget(); QHBoxLayout* top = new QHBoxLayout( main, 0, spacingHint() ); QVBoxLayout* leftCol = new QVBoxLayout; top->addLayout( leftCol ); // Parameters QLabel* label = new QLabel( i18n("Mode:"), main ); leftCol->addWidget( label ); modeW = new QComboBox( main ); int i = 0; while (defaultText[i]) modeW->insertItem( i18n(defaultText[i++]) ); leftCol->addWidget( modeW ); leftCol->addStretch(); // Preview QWidget* preview; preview = new QWidget( main ); preview->setFixedSize( 220, 165 ); preview->setBackgroundColor( black ); preview->show(); // otherwise saver does not get correct size _saver = new KFluxScreenSaver( preview->winId() ); top->addWidget(preview); // Now that we have _saver... modeW->setCurrentItem( _saver->mode() ); // set before we connect connect( modeW, SIGNAL(activated(int)), _saver, SLOT(setMode(int)) ); } KFluxSetup::~KFluxSetup() { delete _saver; } void KFluxSetup::slotHelp() { KMessageBox::about(this, i18n("

Flux 1.0

\n

Copyright (c) 2002 Terence M. Welsh
\nhttp://www.reallyslick.com/

\n\n

Ported to KDE by Karl Robillard

"), QString::null, KMessageBox::AllowLink); } /** Ok pressed - save settings and exit */ void KFluxSetup::slotOk() { KConfig* config = KGlobal::config(); config->setGroup("Settings"); QString val; val.setNum( modeW->currentItem() ); config->writeEntry("Mode", val ); config->sync(); accept(); } #endif //---------------------------------------------------------------------------- #ifdef UNIT_TEST // moc Flux.h -o Flux.moc // g++ -g -DUNIT_TEST Flux.cpp -I/usr/lib/qt3/include -lqt -L/usr/lib/qt3/lib -lGLU -lGL #include int main( int argc, char** argv ) { QApplication app( argc, argv ); FluxWidget w; w.setDefaults( FluxWidget::Sparklers ); app.setMainWidget( &w ); w.show(); return app.exec(); } #endif //EOF