/* xscreensaver, Copyright (c) 2000 Paul "Joey" Clark * * 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. * * 19971004: Johannes Keukelaar : Use helix screen * eraser. */ /* WhirlwindWarp: moving stars. Ported from QBasic by Joey. Version 1.3. Smooth with pretty colours. This code adapted from original program by jwz/jk above. Freely distrubtable. Please keep this tag with this code, and add your own if you contribute. I would be delighted to hear if have made use of this code. If you find this code useful or have any queries, please contact me: pclark@cs.bris.ac.uk / joeyclark@usa.net Paul "Joey" Clark, hacking for humanity, Feb 99 www.cs.bris.ac.uk/~pclark | www.changetheworld.org.uk */ /* 15/May/05: Added colour rotation, limit on max FPS, scaling size dots, and smoother drivers. 4/Mar/01: Star colours are cycled when new colour can not be allocated. 4/Mar/01: Stars are plotted as squares with size relative to screen. 28/Nov/00: Submitted to xscreensaver as "whirlwindwarp". 10/Oct/00: Ported to xscreensaver as "twinkle". 19/Feb/98: Meters and interaction added for Ivor's birthday "stars11f". 11/Aug/97: Original QBasic program. */ #include #include "screenhack.h" #include "erase.h" #include "hsv.h" /* Maximum number of points, maximum tail length, and the number of forcefields/effects (hard-coded) */ #define maxps 1000 #define maxts 50 #define fs 16 /* TODO: change ps and ts arrays into pointers, for dynamic allocation at runtime. */ struct state { Display *dpy; Window window; GC draw_gc, erase_gc; unsigned int default_fg_pixel; int scrwid,scrhei; int starsize; float cx[maxps]; /* Current x,y of stars in realspace */ float cy[maxps]; int tx[maxps*maxts]; /* Previous x,y plots in pixelspace for removal later */ int ty[maxps*maxts]; char *name[fs]; /* The force fields and their parameters */ int fon[fs]; /* Is field on or off? */ float var[fs]; /* Current parameter */ float op[fs]; /* Optimum (central/mean) value */ float acc[fs]; float vel[fs]; int ps; /* Number of points and tail length */ int ts; Bool meters; int initted; XWindowAttributes xgwa; int got_color; XColor color[maxps]; /* The colour assigned to each star */ XColor bgcolor; int p,f,nt, sx,sy, resets,lastresets,cnt; int colsavailable; int hue; struct timeval lastframe; }; static void * whirlwindwarp_init (Display *dpy, Window window) { struct state *st = (struct state *) calloc (1, sizeof(*st)); XGCValues gcv; Colormap cmap; st->dpy = dpy; st->window = window; st->ps=500; st->ts=5; XGetWindowAttributes (st->dpy, st->window, &st->xgwa); cmap = st->xgwa.colormap; gcv.foreground = st->default_fg_pixel = get_pixel_resource (st->dpy, cmap, "foreground", "Foreground"); st->draw_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv); gcv.foreground = get_pixel_resource (st->dpy, cmap, "background", "Background"); st->erase_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv); st->ps = get_integer_resource (st->dpy, "points", "Integer"); st->ts = get_integer_resource (st->dpy, "tails", "Integer"); st->meters = get_boolean_resource (st->dpy, "meters", "Show meters"); if (st->ps > maxps) st->ps = maxps; if (st->ts > maxts) st->ts = maxts; return st; } static float myrnd(void) { /* between -1.0 (inclusive) and +1.0 (exclusive) */ return 2.0*((float)((random()%10000000)/10000000.0)-0.5); } #if 0 static float mysgn(float x) { return ( x < 0 ? -1 : x > 0 ? +1 : 0 ); } #endif static void stars_newp(struct state *st, int pp) { st->cx[pp]=myrnd(); st->cy[pp]=myrnd(); } /* Adjust a variable var about optimum op, with damp = dampening about op force = force of random perturbation */ /* float stars_perturb(float var,float op,float damp,float force) { return op+damp*(var-op)+force*myrnd()/4.0; }*/ #define stars_perturb(var,op,damp,force) \ ( (op) + (damp)*((var)-(op)) + (force)*myrnd()/4.0 ) /* Get pixel coordinates of a star */ static int stars_scrpos_x(struct state *st, int pp) { return st->scrwid*(st->cx[pp]+1.0)/2.0; } static int stars_scrpos_y(struct state *st, int pp) { return st->scrhei*(st->cy[pp]+1.0)/2.0; } /* Draw a meter of a forcefield's parameter */ static void stars_draw_meter(struct state *st, int ff) { int x,y,w,h; x=st->scrwid/2; y=ff*10; w=(st->var[ff]-st->op[ff])*st->scrwid*4; h=7; if (w<0) { w=-w; x=x-w; } if (st->fon[ff]) XFillRectangle(st->dpy,st->window,st->draw_gc,x,y,w,h); /* else XDrawRectangle(dpy,window,draw_gc,x,y,w,h); */ } /* Move a star according to acting forcefields */ static void stars_move(struct state *st, int pp) { float nx,ny; float x=st->cx[pp]; float y=st->cy[pp]; /* In theory all these if checks are unneccessary, since each forcefield effect should do nothing when its var = op. But the if's are good for efficiency because this function is called once for every point. Squirge towards edges (makes a leaf shape, previously split the screen in 4 but now only 1 :) These ones must go first, to avoid x+1.0 < 0 */ if (st->fon[6]) { /* x = mysgn(x) * pow(fabs(x),var[6]); y = mysgn(y) * pow(fabs(y),var[6]);*/ x = -1.0 + 2.0*pow((x + 1.0)/2.0,st->var[6]); } if (st->fon[7]) { y = -1.0 + 2.0*pow((y + 1.0)/2.0,st->var[7]); } /* Warping in/out */ if (st->fon[1]) { x = x * st->var[1]; y = y * st->var[1]; } /* Rotation */ if (st->fon[2]) { nx=x*cos(1.1*st->var[2])+y*sin(1.1*st->var[2]); ny=-x*sin(1.1*st->var[2])+y*cos(1.1*st->var[2]); x=nx; y=ny; } /* Asymptotes (looks like a plane with a horizon; equivalent to 1D warp) */ if (st->fon[3]) { /* Horizontal asymptote */ y=y*st->var[3]; } if (st->fon[4]) { /* Vertical asymptote */ x=x+st->var[4]*x; /* this is the same maths as the last, but with op=0 */ } if (st->fon[5]) { /* Vertical asymptote at right of screen */ x=(x-1.0)*st->var[5]+1.0; } /* Splitting (whirlwind effect): */ #define num_splits ( 2 + (int) (fabs(st->var[0]) * 1000) ) /* #define thru ( (float)(pp%num_splits)/(float)(num_splits-1) ) */ #define thru ( (float)((int)(num_splits*(float)(pp)/(float)(st->ps)))/(float)(num_splits-1) ) if (st->fon[8]) { x=x+0.5*st->var[8]*(-1.0+2.0*thru); } if (st->fon[9]) { y=y+0.5*st->var[9]*(-1.0+2.0*thru); } /* Waves */ if (st->fon[10]) { y = y + 0.4*st->var[10]*sin(300.0*st->var[12]*x + 600.0*st->var[11]); } if (st->fon[13]) { x = x + 0.4*st->var[13]*sin(300.0*st->var[15]*y + 600.0*st->var[14]); } st->cx[pp]=x; st->cy[pp]=y; } /* Turns a forcefield on, and ensures its vars are suitable. */ static void turn_on_field(struct state *st, int ff) { if (!st->fon[ff]) { /* acc[ff]=0.0; */ st->acc[ff]=0.02 * myrnd(); st->vel[ff]=0.0; st->var[ff]=st->op[ff]; } st->fon[ff] = 1; if (ff == 10) { turn_on_field(st, 11); turn_on_field(st, 12); } if (ff == 13) { turn_on_field(st, 14); turn_on_field(st, 15); } } static unsigned long whirlwindwarp_draw (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; /* time_t lastframe = time((time_t) 0); */ if (!st->initted) { st->initted = 1; XClearWindow (st->dpy, st->window); XGetWindowAttributes (st->dpy, st->window, &st->xgwa); st->scrwid = st->xgwa.width; st->scrhei = st->xgwa.height; st->starsize=st->scrhei/480; if (st->starsize<=0) st->starsize=1; /* Setup colours */ hsv_to_rgb (0.0, 0.0, 0.0, &st->bgcolor.red, &st->bgcolor.green, &st->bgcolor.blue); st->got_color = XAllocColor (st->dpy, st->xgwa.colormap, &st->bgcolor); st->colsavailable=0; for (st->p=0;st->pps;st->p++) { if (!mono_p) hsv_to_rgb (random()%360, .6+.4*myrnd(), .6+.4*myrnd(), &st->color[st->p].red, &st->color[st->p].green, &st->color[st->p].blue); /* hsv_to_rgb (random()%360, 1.0, 1.0, &color[p].red, &color[p].green, &color[p].blue); for stronger colours! */ if ((!mono_p) && (st->got_color = XAllocColor (st->dpy, st->xgwa.colormap, &st->color[st->p]))) { st->colsavailable=st->p; } else { if (st->colsavailable>0) /* assign colours from those already allocated */ st->color[st->p]=st->color[ st->p % st->colsavailable ]; else st->color[st->p].pixel=st->default_fg_pixel; } } /* Set up central (optimal) points for each different forcefield */ st->op[1] = 1; st->name[1] = "Warp"; st->op[2] = 0; st->name[2] = "Rotation"; st->op[3] = 1; st->name[3] = "Horizontal asymptote"; st->op[4] = 0; st->name[4] = "Vertical asymptote"; st->op[5] = 1; st->name[5] = "Vertical asymptote right"; st->op[6] = 1; st->name[6] = "Squirge x"; st->op[7] = 1; st->name[7] = "Squirge y"; st->op[0] = 0; st->name[0] = "Split number (inactive)"; st->op[8] = 0; st->name[8] = "Split velocity x"; st->op[9] = 0; st->name[9] = "Split velocity y"; st->op[10] = 0; st->name[10] = "Horizontal wave amplitude"; st->op[11] = myrnd()*3.141; st->name[11] = "Horizontal wave phase (inactive)"; st->op[12] = 0.01; st->name[12] = "Horizontal wave frequency (inactive)"; st->op[13] = 0; st->name[13] = "Vertical wave amplitude"; st->op[14] = myrnd()*3.141; st->name[14] = "Vertical wave phase (inactive)"; st->op[15] = 0.01; st->name[15] = "Vertical wave frequency (inactive)"; /* Initialise parameters to optimum, all off */ for (st->f=0;st->ff++) { st->var[st->f]=st->op[st->f]; st->fon[st->f]=( myrnd()>0.5 ? 1 : 0 ); st->acc[st->f]=0.02 * myrnd(); st->vel[st->f]=0; } /* Initialise stars */ for (st->p=0;st->pps;st->p++) stars_newp(st, st->p); /* tx[nt],ty[nt] remember earlier screen plots (tails of stars) which are deleted when nt comes round again */ st->nt = 0; st->resets = 0; st->hue = 180 + 180*myrnd(); gettimeofday(&st->lastframe, NULL); } if (myrnd()>0.75) { /* Change one of the allocated colours to something near the current hue. */ /* By changing a random colour, we sometimes get a tight colour spread, sometime a diverse one. */ int pp = st->colsavailable * (0.5+myrnd()/2); hsv_to_rgb (st->hue, .6+.4*myrnd(), .6+.4*myrnd(), &st->color[pp].red, &st->color[pp].green, &st->color[pp].blue); if ((!mono_p) && (st->got_color = XAllocColor (st->dpy, st->xgwa.colormap, &st->color[pp]))) { } st->hue = st->hue + 0.5 + myrnd()*9.0; if (st->hue<0) st->hue+=360; if (st->hue>=360) st->hue-=360; } /* Move current points */ st->lastresets=st->resets; st->resets=0; for (st->p=0;st->pps;st->p++) { /* Erase old */ XSetForeground (st->dpy, st->draw_gc, st->bgcolor.pixel); /* XDrawPoint(dpy,window,draw_gc,tx[nt],ty[nt]); */ XFillRectangle(st->dpy,st->window,st->draw_gc,st->tx[st->nt],st->ty[st->nt],st->starsize,st->starsize); /* Move */ stars_move(st, st->p); /* If moved off screen, create a new one */ if (st->cx[st->p]<=-0.9999 || st->cx[st->p]>=+0.9999 || st->cy[st->p]<=-0.9999 || st->cy[st->p]>=+0.9999 || fabs(st->cx[st->p])<.0001 || fabs(st->cy[st->p])<.0001) { stars_newp(st, st->p); st->resets++; } else if (myrnd()>0.99) /* Reset at random */ stars_newp(st, st->p); /* Draw point */ st->sx=stars_scrpos_x(st, st->p); st->sy=stars_scrpos_y(st, st->p); XSetForeground (st->dpy, st->draw_gc, st->color[st->p].pixel); /* XDrawPoint(dpy,window,draw_gc,sx,sy); */ XFillRectangle(st->dpy,st->window,st->draw_gc,st->sx,st->sy,st->starsize,st->starsize); /* Remember it for removal later */ st->tx[st->nt]=st->sx; st->ty[st->nt]=st->sy; st->nt=(st->nt+1)%(st->ps*st->ts); } /* Adjust force fields */ st->cnt=0; for (st->f=0;st->ff++) { if (st->meters) { /* Remove meter from display */ XSetForeground(st->dpy, st->draw_gc, st->bgcolor.pixel); stars_draw_meter(st,st->f); } /* Adjust forcefield's parameter */ if (st->fon[st->f]) { /* This configuration produces var[f]s usually below 0.01 */ st->acc[st->f]=stars_perturb(st->acc[st->f],0,0.98,0.005); st->vel[st->f]=stars_perturb(st->vel[st->f]+0.03*st->acc[st->f],0,0.995,0.0); st->var[st->f]=st->op[st->f]+(st->var[st->f]-st->op[st->f])*0.9995+0.001*st->vel[st->f]; } /* fprintf(stderr,"f=%i fon=%i acc=%f vel=%f var=%f\n",f,fon[f],acc[f],vel[f],var[f]); */ /* Decide whether to turn this forcefield on or off. */ /* prob_on makes the "splitting" effects less likely than the rest */ #define prob_on ( st->f==8 || st->f==9 ? 0.999975 : 0.9999 ) if ( st->fon[st->f]==0 && myrnd()>prob_on ) { turn_on_field(st, st->f); } else if ( st->fon[st->f]!=0 && myrnd()>0.99 && fabs(st->var[st->f]-st->op[st->f])<0.0005 && fabs(st->vel[st->f])<0.005 /* && fabs(acc[f])<0.01 */ ) { /* We only turn it off if it has gently returned to its optimal (as opposed to rapidly passing through it). */ st->fon[st->f] = 0; } if (st->meters) { /* Redraw the meter */ XSetForeground(st->dpy, st->draw_gc, st->color[st->f].pixel); stars_draw_meter(st,st->f); } if (st->fon[st->f]) st->cnt++; } /* Ensure at least three forcefields are on. * BUG: Picking randomly might not be enough since 0,11,12,14 and 15 do nothing! * But then what's wrong with a rare gentle twinkle?! */ if (st->cnt<3) { st->f=random() % fs; turn_on_field(st, st->f); } if (st->meters) { XSetForeground(st->dpy, st->draw_gc, st->bgcolor.pixel); XDrawRectangle(st->dpy,st->window,st->draw_gc,0,0,st->lastresets*5,3); XSetForeground(st->dpy, st->draw_gc, st->default_fg_pixel); XDrawRectangle(st->dpy,st->window,st->draw_gc,0,0,st->resets*5,3); } /* Cap frames per second; do not go above specified fps: */ { unsigned long this_delay = 0; int maxfps = 200; long utimeperframe = 1000000/maxfps; struct timeval now; long timediff; gettimeofday(&now, NULL); /* timediff = now.tv_sec*1000000 + now.tv_usec - st->lastframe.tv_sec*1000000 - st->lastframe.tv_usec; */ timediff = (now.tv_sec - st->lastframe.tv_sec) * 1000000 + now.tv_usec - st->lastframe.tv_usec; if (timediff < utimeperframe) { /* fprintf(stderr,"sleeping for %i\n",utimeperframe-timediff); */ this_delay = (utimeperframe-timediff); } st->lastframe = now; return this_delay; } } static void whirlwindwarp_reshape (Display *dpy, Window window, void *closure, unsigned int w, unsigned int h) { struct state *st = (struct state *) closure; st->scrwid = w; st->scrhei = h; } static Bool whirlwindwarp_event (Display *dpy, Window window, void *closure, XEvent *event) { return False; } static void whirlwindwarp_free (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; XFreeGC (dpy, st->draw_gc); XFreeGC (dpy, st->erase_gc); free (st); } static const char *whirlwindwarp_defaults [] = { ".background: black", ".foreground: white", "*fpsSolid: true", "*points: 400", "*tails: 8", "*meters: false", #ifdef HAVE_MOBILE "*ignoreRotation: True", #endif 0 }; static XrmOptionDescRec whirlwindwarp_options [] = { { "-points", ".points", XrmoptionSepArg, 0 }, { "-tails", ".tails", XrmoptionSepArg, 0 }, { "-meters", ".meters", XrmoptionNoArg, "true" }, { 0, 0, 0, 0 } }; XSCREENSAVER_MODULE ("WhirlWindWarp", whirlwindwarp)