From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / whirlwindwarp.c
1 /* xscreensaver, Copyright (c) 2000 Paul "Joey" Clark <pclark@bris.ac.uk>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or
9  * implied warranty.
10  *
11  * 19971004: Johannes Keukelaar <johannes@nada.kth.se>: Use helix screen
12  *           eraser.
13  */
14
15 /* WhirlwindWarp: moving stars.  Ported from QBasic by Joey.
16    Version 1.3.  Smooth with pretty colours.
17
18    This code adapted from original program by jwz/jk above.
19    Freely distrubtable.  Please keep this tag with
20    this code, and add your own if you contribute.
21    I would be delighted to hear if have made use of this code.
22    If you find this code useful or have any queries, please
23    contact me: pclark@cs.bris.ac.uk / joeyclark@usa.net
24    Paul "Joey" Clark, hacking for humanity, Feb 99
25    www.cs.bris.ac.uk/~pclark | www.changetheworld.org.uk */
26
27 /* 15/May/05: Added colour rotation, limit on max FPS, scaling size dots, and smoother drivers.
28     4/Mar/01: Star colours are cycled when new colour can not be allocated.
29     4/Mar/01: Stars are plotted as squares with size relative to screen.
30    28/Nov/00: Submitted to xscreensaver as "whirlwindwarp".
31    10/Oct/00: Ported to xscreensaver as "twinkle".
32    19/Feb/98: Meters and interaction added for Ivor's birthday "stars11f".
33    11/Aug/97: Original QBasic program. */
34
35 #include <math.h>
36
37 #include "screenhack.h"
38 #include "erase.h"
39 #include "hsv.h"
40
41 /* Maximum number of points, maximum tail length, and the number of forcefields/effects (hard-coded) */
42 #define maxps 1000
43 #define maxts 50
44 #define fs 16
45 /* TODO: change ps and ts arrays into pointers, for dynamic allocation at runtime. */
46
47 struct state {
48   Display *dpy;
49   Window window;
50
51    GC draw_gc, erase_gc;
52    unsigned int default_fg_pixel;
53
54    int scrwid,scrhei;
55    int starsize;
56
57    float cx[maxps];     /* Current x,y of stars in realspace */
58    float cy[maxps];
59    int tx[maxps*maxts]; /* Previous x,y plots in pixelspace for removal later */
60    int ty[maxps*maxts];
61    char *name[fs];      /* The force fields and their parameters */
62
63    int fon[fs];     /* Is field on or off? */
64    float var[fs];   /* Current parameter */
65    float op[fs];    /* Optimum (central/mean) value */
66    float acc[fs];
67    float vel[fs];
68
69    int ps;      /* Number of points and tail length */
70    int ts;
71
72    Bool meters;
73
74    int initted;
75    XWindowAttributes xgwa;
76    int got_color;
77    XColor color[maxps]; /* The colour assigned to each star */
78    XColor bgcolor;
79    int p,f,nt, sx,sy, resets,lastresets,cnt;
80    int colsavailable;
81    int hue;
82
83   struct timeval lastframe;
84 };
85
86
87 static void *
88 whirlwindwarp_init (Display *dpy, Window window)
89 {
90   struct state *st = (struct state *) calloc (1, sizeof(*st));
91   XGCValues gcv;
92   Colormap cmap;
93
94   st->dpy = dpy;
95   st->window = window;
96
97   st->ps=500;
98   st->ts=5;
99
100   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
101   cmap = st->xgwa.colormap;
102   gcv.foreground = st->default_fg_pixel = get_pixel_resource (st->dpy, cmap, "foreground", "Foreground");
103   st->draw_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
104   gcv.foreground = get_pixel_resource (st->dpy, cmap, "background", "Background");
105   st->erase_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
106
107   st->ps = get_integer_resource (st->dpy, "points", "Integer");
108   st->ts = get_integer_resource (st->dpy, "tails", "Integer");
109   st->meters = get_boolean_resource (st->dpy, "meters", "Show meters");
110   if (st->ps > maxps) st->ps = maxps;
111   if (st->ts > maxts) st->ts = maxts;
112
113   return st;
114 }
115
116 static float myrnd(void)
117 { /* between -1.0 (inclusive) and +1.0 (exclusive) */
118   return 2.0*((float)((random()%10000000)/10000000.0)-0.5);
119 }
120
121 #if 0
122 static float mysgn(float x)
123 {
124   return ( x < 0 ? -1 :
125            x > 0 ? +1 :
126                     0 );
127 }
128 #endif
129
130 static void stars_newp(struct state *st, int pp)
131 {
132   st->cx[pp]=myrnd();
133   st->cy[pp]=myrnd();
134 }
135
136 /* Adjust a variable var about optimum op,
137      with damp = dampening about op
138          force = force of random perturbation */
139 /* float stars_perturb(float var,float op,float damp,float force) {
140    return op+damp*(var-op)+force*myrnd()/4.0;
141  }*/
142 #define stars_perturb(var,op,damp,force) \
143   ( (op) + (damp)*((var)-(op)) + (force)*myrnd()/4.0 )
144
145 /* Get pixel coordinates of a star */
146 static int stars_scrpos_x(struct state *st, int pp)
147 {
148   return st->scrwid*(st->cx[pp]+1.0)/2.0;
149 }
150
151 static int stars_scrpos_y(struct state *st, int pp)
152 {
153   return st->scrhei*(st->cy[pp]+1.0)/2.0;
154 }
155
156 /* Draw a meter of a forcefield's parameter */
157 static void stars_draw_meter(struct state *st, int ff)
158 {
159   int x,y,w,h;
160   x=st->scrwid/2;
161   y=ff*10;
162   w=(st->var[ff]-st->op[ff])*st->scrwid*4;
163   h=7;
164   if (w<0) {
165     w=-w;
166     x=x-w;
167   }
168   if (st->fon[ff])
169     XFillRectangle(st->dpy,st->window,st->draw_gc,x,y,w,h);
170   /* else
171      XDrawRectangle(dpy,window,draw_gc,x,y,w,h); */
172 }
173
174 /* Move a star according to acting forcefields */
175 static void stars_move(struct state *st, int pp)
176 {
177   float nx,ny;
178   float x=st->cx[pp];
179   float y=st->cy[pp];
180
181   /* In theory all these if checks are unneccessary,
182      since each forcefield effect should do nothing when its var = op.
183      But the if's are good for efficiency because this function
184      is called once for every point.
185
186     Squirge towards edges (makes a leaf shape, previously split the screen in 4 but now only 1 :)
187     These ones must go first, to avoid x+1.0 < 0
188    */
189   if (st->fon[6]) {
190     /* x = mysgn(x) * pow(fabs(x),var[6]);
191        y = mysgn(y) * pow(fabs(y),var[6]);*/
192     x = -1.0 + 2.0*pow((x + 1.0)/2.0,st->var[6]);
193   }
194   if (st->fon[7]) {
195     y = -1.0 + 2.0*pow((y + 1.0)/2.0,st->var[7]);
196   }
197
198   /* Warping in/out */
199   if (st->fon[1]) {
200     x = x * st->var[1]; y = y * st->var[1];
201   }
202
203   /* Rotation */
204   if (st->fon[2]) {
205     nx=x*cos(1.1*st->var[2])+y*sin(1.1*st->var[2]);
206     ny=-x*sin(1.1*st->var[2])+y*cos(1.1*st->var[2]);
207     x=nx;
208     y=ny;
209   }
210
211   /* Asymptotes (looks like a plane with a horizon; equivalent to 1D warp) */
212   if (st->fon[3]) { /* Horizontal asymptote */
213     y=y*st->var[3];
214   }
215   if (st->fon[4]) { /* Vertical asymptote */
216     x=x+st->var[4]*x; /* this is the same maths as the last, but with op=0 */
217   }
218   if (st->fon[5]) { /* Vertical asymptote at right of screen */
219     x=(x-1.0)*st->var[5]+1.0;
220   }
221
222   /* Splitting (whirlwind effect): */
223   #define num_splits ( 2 + (int) (fabs(st->var[0]) * 1000) )
224   /* #define thru ( (float)(pp%num_splits)/(float)(num_splits-1) ) */
225   #define thru ( (float)((int)(num_splits*(float)(pp)/(float)(st->ps)))/(float)(num_splits-1) )
226   if (st->fon[8]) {
227     x=x+0.5*st->var[8]*(-1.0+2.0*thru);
228   }
229   if (st->fon[9]) {
230     y=y+0.5*st->var[9]*(-1.0+2.0*thru);
231   }
232
233   /* Waves */
234   if (st->fon[10]) {
235     y = y + 0.4*st->var[10]*sin(300.0*st->var[12]*x + 600.0*st->var[11]);
236   }
237   if (st->fon[13]) {
238     x = x + 0.4*st->var[13]*sin(300.0*st->var[15]*y + 600.0*st->var[14]);
239   }
240
241   st->cx[pp]=x;
242   st->cy[pp]=y;
243 }
244
245 /* Turns a forcefield on, and ensures its vars are suitable. */
246 static void turn_on_field(struct state *st, int ff)
247 {
248   if (!st->fon[ff]) {
249     /* acc[ff]=0.0; */
250     st->acc[ff]=0.02 * myrnd();
251     st->vel[ff]=0.0;
252     st->var[ff]=st->op[ff];
253   }
254   st->fon[ff] = 1;
255   if (ff == 10) {
256     turn_on_field(st, 11);
257     turn_on_field(st, 12);
258   }
259   if (ff == 13) {
260     turn_on_field(st, 14);
261     turn_on_field(st, 15);
262   }
263 }
264
265 static unsigned long
266 whirlwindwarp_draw (Display *dpy, Window window, void *closure)
267 {
268   struct state *st = (struct state *) closure;
269
270   /* time_t lastframe = time((time_t) 0); */
271
272   if (!st->initted) {
273     st->initted = 1;
274
275     XClearWindow (st->dpy, st->window);
276     XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
277     st->scrwid = st->xgwa.width;
278     st->scrhei = st->xgwa.height;
279
280     st->starsize=st->scrhei/480;
281     if (st->starsize<=0)
282       st->starsize=1;
283
284     /* Setup colours */
285     hsv_to_rgb (0.0, 0.0, 0.0, &st->bgcolor.red, &st->bgcolor.green, &st->bgcolor.blue);
286     st->got_color = XAllocColor (st->dpy, st->xgwa.colormap, &st->bgcolor);
287     st->colsavailable=0;
288     for (st->p=0;st->p<st->ps;st->p++) {
289       if (!mono_p)
290         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);
291       /* hsv_to_rgb (random()%360, 1.0, 1.0, &color[p].red, &color[p].green, &color[p].blue);   for stronger colours! */
292       if ((!mono_p) && (st->got_color = XAllocColor (st->dpy, st->xgwa.colormap, &st->color[st->p]))) {
293         st->colsavailable=st->p;
294       } else {
295         if (st->colsavailable>0) /* assign colours from those already allocated */
296           st->color[st->p]=st->color[ st->p % st->colsavailable ];
297         else
298           st->color[st->p].pixel=st->default_fg_pixel;
299       }
300     }
301
302   /* Set up central (optimal) points for each different forcefield */
303   st->op[1] = 1; st->name[1] = "Warp";
304   st->op[2] = 0; st->name[2] = "Rotation";
305   st->op[3] = 1; st->name[3] = "Horizontal asymptote";
306   st->op[4] = 0; st->name[4] = "Vertical asymptote";
307   st->op[5] = 1; st->name[5] = "Vertical asymptote right";
308   st->op[6] = 1; st->name[6] = "Squirge x";
309   st->op[7] = 1; st->name[7] = "Squirge y";
310   st->op[0] = 0; st->name[0] = "Split number (inactive)";
311   st->op[8] = 0; st->name[8] = "Split velocity x";
312   st->op[9] = 0; st->name[9] = "Split velocity y";
313   st->op[10] = 0; st->name[10] = "Horizontal wave amplitude";
314   st->op[11] = myrnd()*3.141; st->name[11] = "Horizontal wave phase (inactive)";
315   st->op[12] = 0.01; st->name[12] = "Horizontal wave frequency (inactive)";
316   st->op[13] = 0; st->name[13] = "Vertical wave amplitude";
317   st->op[14] = myrnd()*3.141; st->name[14] = "Vertical wave phase (inactive)";
318   st->op[15] = 0.01; st->name[15] = "Vertical wave frequency (inactive)";
319
320   /* Initialise parameters to optimum, all off */
321   for (st->f=0;st->f<fs;st->f++) {
322     st->var[st->f]=st->op[st->f];
323     st->fon[st->f]=( myrnd()>0.5 ? 1 : 0 );
324     st->acc[st->f]=0.02 * myrnd();
325     st->vel[st->f]=0;
326   }
327
328   /* Initialise stars */
329   for (st->p=0;st->p<st->ps;st->p++)
330     stars_newp(st, st->p);
331
332   /* tx[nt],ty[nt] remember earlier screen plots (tails of stars)
333      which are deleted when nt comes round again */
334   st->nt = 0;
335   st->resets = 0;
336
337   st->hue = 180 + 180*myrnd();
338
339   gettimeofday(&st->lastframe, NULL);
340
341   }
342
343
344     if (myrnd()>0.75) {
345       /* Change one of the allocated colours to something near the current hue. */
346       /* By changing a random colour, we sometimes get a tight colour spread, sometime a diverse one. */
347       int pp = st->colsavailable * (0.5+myrnd()/2);
348       hsv_to_rgb (st->hue, .6+.4*myrnd(), .6+.4*myrnd(), &st->color[pp].red, &st->color[pp].green, &st->color[pp].blue);
349       if ((!mono_p) && (st->got_color = XAllocColor (st->dpy, st->xgwa.colormap, &st->color[pp]))) {
350       }
351       st->hue = st->hue + 0.5 + myrnd()*9.0;
352       if (st->hue<0) st->hue+=360;
353       if (st->hue>=360) st->hue-=360;
354     }
355
356       /* Move current points */
357       st->lastresets=st->resets;
358       st->resets=0;
359       for (st->p=0;st->p<st->ps;st->p++) {
360         /* Erase old */
361         XSetForeground (st->dpy, st->draw_gc, st->bgcolor.pixel);
362         /* XDrawPoint(dpy,window,draw_gc,tx[nt],ty[nt]); */
363         XFillRectangle(st->dpy,st->window,st->draw_gc,st->tx[st->nt],st->ty[st->nt],st->starsize,st->starsize);
364
365         /* Move */
366         stars_move(st, st->p);
367         /* If moved off screen, create a new one */
368         if (st->cx[st->p]<=-0.9999 || st->cx[st->p]>=+0.9999 ||
369             st->cy[st->p]<=-0.9999 || st->cy[st->p]>=+0.9999 ||
370             fabs(st->cx[st->p])<.0001 || fabs(st->cy[st->p])<.0001) {
371           stars_newp(st, st->p);
372           st->resets++;
373         } else if (myrnd()>0.99) /* Reset at random */
374           stars_newp(st, st->p);
375
376         /* Draw point */
377         st->sx=stars_scrpos_x(st, st->p);
378         st->sy=stars_scrpos_y(st, st->p);
379         XSetForeground (st->dpy, st->draw_gc, st->color[st->p].pixel);
380         /* XDrawPoint(dpy,window,draw_gc,sx,sy); */
381         XFillRectangle(st->dpy,st->window,st->draw_gc,st->sx,st->sy,st->starsize,st->starsize);
382
383         /* Remember it for removal later */
384         st->tx[st->nt]=st->sx;
385         st->ty[st->nt]=st->sy;
386         st->nt=(st->nt+1)%(st->ps*st->ts);
387       }
388
389       /* Adjust force fields */
390       st->cnt=0;
391       for (st->f=0;st->f<fs;st->f++) {
392
393         if (st->meters) { /* Remove meter from display */
394           XSetForeground(st->dpy, st->draw_gc, st->bgcolor.pixel);
395           stars_draw_meter(st,st->f);
396         }
397
398         /* Adjust forcefield's parameter */
399         if (st->fon[st->f]) {
400           /* This configuration produces var[f]s usually below 0.01 */
401           st->acc[st->f]=stars_perturb(st->acc[st->f],0,0.98,0.005);
402           st->vel[st->f]=stars_perturb(st->vel[st->f]+0.03*st->acc[st->f],0,0.995,0.0);
403           st->var[st->f]=st->op[st->f]+(st->var[st->f]-st->op[st->f])*0.9995+0.001*st->vel[st->f];
404         }
405         /* fprintf(stderr,"f=%i fon=%i acc=%f vel=%f var=%f\n",f,fon[f],acc[f],vel[f],var[f]); */
406
407         /* Decide whether to turn this forcefield on or off. */
408         /* prob_on makes the "splitting" effects less likely than the rest */
409         #define prob_on ( st->f==8 || st->f==9 ? 0.999975 : 0.9999 )
410         if ( st->fon[st->f]==0 && myrnd()>prob_on ) {
411           turn_on_field(st, st->f);
412         } 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 */ ) {
413           /* We only turn it off if it has gently returned to its optimal (as opposed to rapidly passing through it). */
414           st->fon[st->f] = 0;
415         }
416
417         if (st->meters) { /* Redraw the meter */
418           XSetForeground(st->dpy, st->draw_gc, st->color[st->f].pixel);
419           stars_draw_meter(st,st->f);
420         }
421
422         if (st->fon[st->f])
423           st->cnt++;
424       }
425
426       /* Ensure at least three forcefields are on.
427        * BUG: Picking randomly might not be enough since 0,11,12,14 and 15 do nothing!
428        * But then what's wrong with a rare gentle twinkle?!
429       */
430       if (st->cnt<3) {
431         st->f=random() % fs;
432         turn_on_field(st, st->f);
433       }
434
435       if (st->meters) {
436         XSetForeground(st->dpy, st->draw_gc, st->bgcolor.pixel);
437         XDrawRectangle(st->dpy,st->window,st->draw_gc,0,0,st->lastresets*5,3);
438         XSetForeground(st->dpy, st->draw_gc, st->default_fg_pixel);
439         XDrawRectangle(st->dpy,st->window,st->draw_gc,0,0,st->resets*5,3);
440       }
441
442       /* Cap frames per second; do not go above specified fps: */
443       {
444         unsigned long this_delay = 0;
445         int maxfps = 200;
446         long utimeperframe = 1000000/maxfps;
447         struct timeval now;
448         long timediff;
449         gettimeofday(&now, NULL);
450         /* timediff = now.tv_sec*1000000 + now.tv_usec - st->lastframe.tv_sec*1000000 - st->lastframe.tv_usec; */
451         timediff = (now.tv_sec - st->lastframe.tv_sec) * 1000000 + now.tv_usec - st->lastframe.tv_usec;
452         if (timediff < utimeperframe) {
453           /* fprintf(stderr,"sleeping for %i\n",utimeperframe-timediff); */
454           this_delay = (utimeperframe-timediff);
455         }
456         st->lastframe = now;
457
458         return this_delay;
459       }
460 }
461
462
463 static void
464 whirlwindwarp_reshape (Display *dpy, Window window, void *closure, 
465                  unsigned int w, unsigned int h)
466 {
467   struct state *st = (struct state *) closure;
468   st->scrwid = w;
469   st->scrhei = h;
470 }
471
472 static Bool
473 whirlwindwarp_event (Display *dpy, Window window, void *closure, XEvent *event)
474 {
475   return False;
476 }
477
478 static void
479 whirlwindwarp_free (Display *dpy, Window window, void *closure)
480 {
481   struct state *st = (struct state *) closure;
482   free (st);
483 }
484
485
486 static const char *whirlwindwarp_defaults [] = {
487   ".background: black",
488   ".foreground: white",
489   "*fpsSolid:   true",
490   "*points:     400",
491   "*tails:      8",
492   "*meters:     false",
493 #ifdef HAVE_MOBILE
494   "*ignoreRotation: True",
495 #endif
496   0
497 };
498
499 static XrmOptionDescRec whirlwindwarp_options [] = {
500   { "-points",  ".points",      XrmoptionSepArg, 0 },
501   { "-tails",   ".tails",       XrmoptionSepArg, 0 },
502   { "-meters",  ".meters",      XrmoptionNoArg, "true" },
503   { 0, 0, 0, 0 }
504 };
505
506 XSCREENSAVER_MODULE ("WhirlWindWarp", whirlwindwarp)