//----------------------------------------------------------------------------- // // Lorenz - Lorenz Attractor screen saver // Nicolas Brodu, brodu@kde.org, 2000 // // Portions of code from kblankscrn and khop. // See authors there. // // I release my code as GPL, but see the other headers and the README #include #include #include #include #include #include #include #include #include #include #include #include #include "lorenz.h" #include "lorenz.moc" // libkscreensaver interface extern "C" { KDE_EXPORT const char *kss_applicationName = "klorenz.kss"; KDE_EXPORT const char *kss_description = I18N_NOOP( "KLorenz" ); KDE_EXPORT const char *kss_version = "2.2.0"; KDE_EXPORT KScreenSaver *kss_create( WId id ) { return new KLorenzSaver( id ); } KDE_EXPORT TQDialog *kss_setup() { return new KLorenzSetup(); } } #define MINSPEED 1 #define MAXSPEED 1500 #define DEFSPEED 150 #define MINZROT -180 #define MAXZROT 180 #define DEFZROT 104 //100 #define MINYROT -180 #define MAXYROT 180 #define DEFYROT -19 //80 #define MINXROT -180 #define MAXXROT 180 #define DEFXROT 25 //20 #define MINEPOCH 1 #define MAXEPOCH 30000 #define DEFEPOCH 5800 #define MINCOLOR 1 #define MAXCOLOR 100 #define DEFCOLOR 20 //----------------------------------------------------------------------------- // dialog to setup screen saver parameters // KLorenzSetup::KLorenzSetup( TQWidget *parent, const char *name ) : KDialogBase( parent, name, true, i18n( "Setup Lorenz Attractor" ), Ok|Cancel|Default|Help, Ok, true ) { readSettings(); setButtonText( Help, i18n( "A&bout" ) ); TQWidget *main = makeMainWidget(); TQHBoxLayout *tl = new TQHBoxLayout( main, 0, spacingHint() ); TQVBoxLayout *tl1 = new TQVBoxLayout; tl->addLayout(tl1); TQLabel *label = new TQLabel( i18n("Speed:"), main ); tl1->addWidget(label); sps = new TQSlider(MINSPEED, MAXSPEED, 10, speed, Qt::Horizontal, main); sps->setMinimumSize( 120, 20 ); sps->setTickmarks(TQSlider::Below); sps->setTickInterval(150); connect( sps, TQT_SIGNAL( valueChanged( int ) ), TQT_SLOT( slotSpeed( int ) ) ); tl1->addWidget(sps); label = new TQLabel( i18n("Epoch:"), main ); tl1->addWidget(label); eps = new TQSlider(MINEPOCH, MAXEPOCH, 100, epoch, Qt::Horizontal, main); eps->setMinimumSize( 120, 20 ); eps->setTickmarks(TQSlider::Below); eps->setTickInterval(3000); connect( eps, TQT_SIGNAL( valueChanged( int ) ), TQT_SLOT( slotEpoch( int ) ) ); tl1->addWidget(eps); label = new TQLabel( i18n("Color rate:"), main ); tl1->addWidget(label); crs = new TQSlider(MINCOLOR, MAXCOLOR, 5, crate, Qt::Horizontal, main); crs->setMinimumSize( 120, 20 ); crs->setTickmarks(TQSlider::Below); crs->setTickInterval(10); connect( crs, TQT_SIGNAL( valueChanged( int ) ), TQT_SLOT( slotCRate( int ) ) ); tl1->addWidget(crs); label = new TQLabel( i18n("Rotation Z:"), main ); tl1->addWidget(label); zrs = new TQSlider(MINZROT, MAXZROT, 18, zrot, Qt::Horizontal, main); zrs->setMinimumSize( 120, 20 ); zrs->setTickmarks(TQSlider::Below); zrs->setTickInterval(36); connect( zrs, TQT_SIGNAL( valueChanged( int ) ), TQT_SLOT( slotZRot( int ) ) ); tl1->addWidget(zrs); label = new TQLabel( i18n("Rotation Y:"), main ); tl1->addWidget(label); yrs = new TQSlider(MINYROT, MAXYROT, 18, yrot, Qt::Horizontal, main); yrs->setMinimumSize( 120, 20 ); yrs->setTickmarks(TQSlider::Below); yrs->setTickInterval(36); connect( yrs, TQT_SIGNAL( valueChanged( int ) ), TQT_SLOT( slotYRot( int ) ) ); tl1->addWidget(yrs); label = new TQLabel( i18n("Rotation X:"), main ); tl1->addWidget(label); xrs = new TQSlider(MINXROT, MAXXROT, 18, xrot, Qt::Horizontal, main); xrs->setMinimumSize( 120, 20 ); xrs->setTickmarks(TQSlider::Below); xrs->setTickInterval(36); connect( xrs, TQT_SIGNAL( valueChanged( int ) ), TQT_SLOT( slotXRot( int ) ) ); tl1->addWidget(xrs); preview = new TQWidget( main ); preview->setFixedSize( 220, 165 ); preview->setBackgroundColor( black ); preview->show(); // otherwise saver does not get correct size saver = new KLorenzSaver( preview->winId() ); tl->addWidget(preview); } KLorenzSetup::~KLorenzSetup() { delete saver; } // read settings from config file void KLorenzSetup::readSettings() { TDEConfig *config = TDEGlobal::config(); config->setGroup( "Settings" ); speed = config->readNumEntry( "Speed", DEFSPEED ); epoch = config->readNumEntry( "Epoch", DEFEPOCH ); crate = config->readNumEntry( "Color Rate", DEFCOLOR ); zrot = config->readNumEntry( "ZRot", DEFZROT ); yrot = config->readNumEntry( "YRot", DEFZROT ); xrot = config->readNumEntry( "XRot", DEFZROT ); } void KLorenzSetup::slotSpeed(int num) { speed = num; if (saver) saver->setSpeed(speed); } void KLorenzSetup::slotEpoch(int num) { epoch = num; if (saver) saver->setEpoch(epoch); } void KLorenzSetup::slotCRate(int num) { crate = num; if (saver) saver->setCRate(crate); } void KLorenzSetup::slotZRot(int num) { zrot = num; if (saver) { saver->setZRot(zrot); saver->updateMatrix(); saver->newEpoch(); } } void KLorenzSetup::slotYRot(int num) { yrot = num; if (saver) { saver->setYRot(yrot); saver->updateMatrix(); saver->newEpoch(); } } void KLorenzSetup::slotXRot(int num) { xrot = num; if (saver) { saver->setXRot(xrot); saver->updateMatrix(); saver->newEpoch(); } } void KLorenzSetup::slotHelp() { KMessageBox::about(this,i18n("Lorenz Attractor screen saver for TDE\n\nCopyright (c) 2000 Nicolas Brodu")); } // Ok pressed - save settings and exit void KLorenzSetup::slotOk() { TDEConfig *config = TDEGlobal::config(); config->setGroup( "Settings" ); config->writeEntry( "Speed", speed ); config->writeEntry( "Epoch", epoch ); config->writeEntry( "Color Rate", crate ); config->writeEntry( "ZRot", zrot ); config->writeEntry( "YRot", yrot ); config->writeEntry( "XRot", xrot ); config->sync(); accept(); } void KLorenzSetup::slotDefault() { speed = DEFSPEED; epoch = DEFEPOCH; crate = DEFCOLOR; zrot = DEFZROT; yrot = DEFYROT; xrot = DEFXROT; if (saver) { saver->setSpeed(speed); saver->setEpoch(epoch); saver->setCRate(crate); saver->setZRot(zrot); saver->setYRot(yrot); saver->setXRot(xrot); saver->updateMatrix(); saver->newEpoch(); } sps->setValue(speed); eps->setValue(epoch); crs->setValue(crate); zrs->setValue(zrot); yrs->setValue(yrot); xrs->setValue(xrot); /* // User can cancel, or save defaults? TDEConfig *config = TDEGlobal::config(); config->setGroup( "Settings" ); config->writeEntry( "Speed", speed ); config->writeEntry( "Epoch", epoch ); config->writeEntry( "Color Rate", crate ); config->writeEntry( "ZRot", zrot ); config->writeEntry( "YRot", yrot ); config->writeEntry( "XRot", xrot ); config->sync(); */ } //----------------------------------------------------------------------------- #ifndef M_PI #define M_PI 3.14159265358979323846 #endif const double pi = M_PI; // Homogeneous coordinate transform matrix // I initially wrote it for a Java applet, it is inspired from a // Matrix class in the JDK. // Nicolas Brodu, 1998-2000 class Matrix3D { // All coefficients double xx, xy, xz, xo; double yx, yy, yz, yo; double zx, zy, zz, zo; // 0, 0, 0, 1 are implicit public: void unit() { xx=1.0; xy=0.0; xz=0.0; xo=0.0; yx=0.0; yy=1.0; yz=0.0; yo=0.0; zx=0.0; zy=0.0; zz=1.0; zo=0.0; } Matrix3D () { unit(); } // Translation void translate(double x, double y, double z) { xo += x; yo += y; zo += z; } // Rotation, in degrees, around the Y axis void rotY(double theta) { theta *= pi / 180; double ct = cos(theta); double st = sin(theta); double Nxx = xx * ct + zx * st; double Nxy = xy * ct + zy * st; double Nxz = xz * ct + zz * st; double Nxo = xo * ct + zo * st; double Nzx = zx * ct - xx * st; double Nzy = zy * ct - xy * st; double Nzz = zz * ct - xz * st; double Nzo = zo * ct - xo * st; xo = Nxo; xx = Nxx; xy = Nxy; xz = Nxz; zo = Nzo; zx = Nzx; zy = Nzy; zz = Nzz; } // Rotation, in degrees, around the X axis void rotX(double theta) { theta *= pi / 180; double ct = cos(theta); double st = sin(theta); double Nyx = yx * ct + zx * st; double Nyy = yy * ct + zy * st; double Nyz = yz * ct + zz * st; double Nyo = yo * ct + zo * st; double Nzx = zx * ct - yx * st; double Nzy = zy * ct - yy * st; double Nzz = zz * ct - yz * st; double Nzo = zo * ct - yo * st; yo = Nyo; yx = Nyx; yy = Nyy; yz = Nyz; zo = Nzo; zx = Nzx; zy = Nzy; zz = Nzz; } // Rotation, in degrees, around the Z axis void rotZ(double theta) { theta *= pi / 180; double ct = cos(theta); double st = sin(theta); double Nyx = yx * ct + xx * st; double Nyy = yy * ct + xy * st; double Nyz = yz * ct + xz * st; double Nyo = yo * ct + xo * st; double Nxx = xx * ct - yx * st; double Nxy = xy * ct - yy * st; double Nxz = xz * ct - yz * st; double Nxo = xo * ct - yo * st; yo = Nyo; yx = Nyx; yy = Nyy; yz = Nyz; xo = Nxo; xx = Nxx; xy = Nxy; xz = Nxz; } // Multiply by a projection matrix, with camera f // f 0 0 0 x f*x // 0 f 0 0 * y = f*y // 0 0 1 f z z+f // 0 0 0 1 1 1 // So, it it easy to find the 2D coordinates after the transform // u = f*x / (z+f) // v = f*y / (z+f) void proj(double f) { xx*=f; xy*=f; xz*=f; xo*=f; yx*=f; yy*=f; yz*=f; yo*=f; zo+=f; } // Apply the transformation 3D => 2D void transform(double x, double y, double z, double &u, double& v, double& w) { u = x * xx + y * xy + z * xz + xo; v = x * yx + y * yy + z * yz + yo; w = x * zx + y * zy + z * zz + zo; } }; KLorenzSaver::KLorenzSaver( WId id ) : KScreenSaver( id ) { readSettings(); // Create a transform matrix with the parameters mat = new Matrix3D(); updateMatrix(); colorContext = TQColor::enterAllocContext(); setBackgroundColor( black ); newEpoch(); timer.start( 10 ); connect( &timer, TQT_SIGNAL( timeout() ), TQT_SLOT( drawOnce() ) ); } KLorenzSaver::~KLorenzSaver() { delete mat; mat=0; timer.stop(); TQColor::leaveAllocContext(); TQColor::destroyAllocContext( colorContext ); } // read configuration settings from config file void KLorenzSaver::readSettings() { TDEConfig *config = TDEGlobal::config(); config->setGroup( "Settings" ); speed = config->readNumEntry( "Speed", DEFSPEED ); epoch = config->readNumEntry( "Epoch", DEFEPOCH ); zrot = config->readNumEntry( "ZRot", DEFZROT ); yrot = config->readNumEntry( "YRot", DEFZROT ); xrot = config->readNumEntry( "XRot", DEFZROT ); int crate_num = config->readNumEntry( "Color Rate", DEFCOLOR ); crate = (double)crate_num / (double)MAXCOLOR; } void KLorenzSaver::setSpeed(int num) { speed = num; } void KLorenzSaver::setEpoch(int num) { epoch = num; } void KLorenzSaver::setZRot(int num) { zrot = num; } void KLorenzSaver::setYRot(int num) { yrot = num; } void KLorenzSaver::setXRot(int num) { xrot = num; } void KLorenzSaver::setCRate(int num) { crate = (double)num / (double)MAXCOLOR; } void KLorenzSaver::updateMatrix() { // reset matrix mat->unit(); // Remove the mean before the rotations... mat->translate(-0.95413, -0.96740, -23.60065); mat->rotZ(zrot); mat->rotY(yrot); mat->rotX(xrot); mat->translate(0, 0, 100); mat->proj(1); } void KLorenzSaver::newEpoch() { // Start at a random position, somewhere around the mean x = 0.95-25.0+50.0*kapp->random() / (RAND_MAX+1.0); y = 0.97-25.0+50.0*kapp->random() / (RAND_MAX+1.0); z = 23.6-25.0+50.0*kapp->random() / (RAND_MAX+1.0); // start at some random 'time' as well to have different colors t = 10000.0*kapp->random() / (RAND_MAX+1.0); erase(); e=0; // reset epoch counter } // Computes the derivatives using Lorenz equations static void lorenz(double x, double y, double z, double& dx, double& dy, double& dz) { dx = 10*(y-x); dy = 28*x - y - x*z; dz = x*y - z*8.0/3.0; } // Use a simple Runge-Kutta formula to draw a few points // No need to go beyond 2nd order for a screensaver! void KLorenzSaver::drawOnce() { double kx, ky, kz, dx, dy, dz; const double h = 0.0001; const double tqh = h * 3.0 / 4.0; TQPainter p(this); for (int i=0; itransform(x,y,z,kx,ky,kz); // Choose a color p.setPen( TQColor((int)(sin(t*crate/pi)*127+128), (int)(sin(t*crate/(pi-1))*127+128), (int)(sin(t*crate/(pi-2))*127+128)).pixel() ); // Draw a point p.drawPoint( (int)(kx*width()*1.5/kz)+(int)(width()/2), (int)(ky*height()*1.5/kz)+(int)(height()/2)); t+=h; } if (++e>=epoch) newEpoch(); }