ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-4.22.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 static GC draw_gc, erase_gc;
42 static unsigned int default_fg_pixel;
43
44 /* Maximum number of points, maximum tail length, and the number of forcefields/effects (hard-coded) */
45 #define maxps 1000
46 #define maxts 50
47 #define fs 16
48 /* TODO: change ps and ts arrays into pointers, for dynamic allocation at runtime. */
49
50 /* Screen width and height */
51 static int scrwid,scrhei;
52 static int starsize;
53
54 /* Current x,y of stars in realspace */
55 static float cx[maxps];
56 static float cy[maxps];
57 /* Previous x,y plots in pixelspace for removal later */
58 static int tx[maxps*maxts];
59 static int ty[maxps*maxts];
60 /* The force fields and their parameters */
61 static char *name[fs];
62 static int fon[fs];     /* Is field on or off? */
63 static float var[fs];   /* Current parameter */
64 static float op[fs];    /* Optimum (central/mean) value */
65 /* These have now become standardised across all forcefields:
66  static float damp[fs];  / * Dampening (how much drawn between current and optimal) * /
67  static float force[fs]; / * Amount of change per moment * /
68 */
69 static float acc[fs];
70 static float vel[fs];
71
72 /* Number of points and tail length */
73 static int ps=500;
74 static int ts=5;
75
76 /* Show meters or not? */
77 static Bool meters;
78
79 static Bool init_whirlwindwarp(Display *dpy, Window window)
80 {
81   XGCValues gcv;
82   Colormap cmap;
83   XWindowAttributes xgwa;
84   XGetWindowAttributes (dpy, window, &xgwa);
85   cmap = xgwa.colormap;
86   gcv.foreground = default_fg_pixel = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
87   draw_gc = XCreateGC (dpy, window, GCForeground, &gcv);
88   gcv.foreground = get_pixel_resource ("background", "Background", dpy, cmap);
89   erase_gc = XCreateGC (dpy, window, GCForeground, &gcv);
90
91   ps = get_integer_resource ("points", "Integer");
92   ts = get_integer_resource ("tails", "Integer");
93   meters = get_boolean_resource ("meters", "Show meters");
94   if (ps>maxps || ts>maxts)
95     return 0;
96   return 1;
97 }
98
99 static float myrnd(void)
100 { /* between -1.0 (inclusive) and +1.0 (exclusive) */
101   return 2.0*((float)((random()%10000000)/10000000.0)-0.5);
102 }
103
104 float mysgn(float x)
105 {
106   return ( x < 0 ? -1 :
107            x > 0 ? +1 :
108                     0 );
109 }
110
111 void stars_newp(int p)
112 {
113   cx[p]=myrnd();
114   cy[p]=myrnd();
115 }
116
117 /* Adjust a variable var about optimum op,
118      with damp = dampening about op
119          force = force of random perturbation */
120 /* float stars_perturb(float var,float op,float damp,float force) {
121    return op+damp*(var-op)+force*myrnd()/4.0;
122  }*/
123 #define stars_perturb(var,op,damp,force) \
124   ( (op) + (damp)*((var)-(op)) + (force)*myrnd()/4.0 )
125
126 /* Get pixel coordinates of a star */
127 int stars_scrpos_x(int p)
128 {
129   return scrwid*(cx[p]+1.0)/2.0;
130 }
131
132 int stars_scrpos_y(int p)
133 {
134   return scrhei*(cy[p]+1.0)/2.0;
135 }
136
137 /* Draw a meter of a forcefield's parameter */
138 void stars_draw_meter(Display *dpy,Window window,GC draw_gc,int f)
139 {
140   int x,y,w,h;
141   x=scrwid/2;
142   y=f*10;
143   w=(var[f]-op[f])*scrwid*4;
144   h=7;
145   if (w<0) {
146     w=-w;
147     x=x-w;
148   }
149   if (fon[f])
150     XFillRectangle(dpy,window,draw_gc,x,y,w,h);
151   /* else
152      XDrawRectangle(dpy,window,draw_gc,x,y,w,h); */
153 }
154
155 /* Move a star according to acting forcefields */
156 void stars_move(int p)
157 {
158   float nx,ny;
159   float x=cx[p];
160   float y=cy[p];
161
162   /* In theory all these if checks are unneccessary,
163      since each forcefield effect should do nothing when its var = op.
164      But the if's are good for efficiency because this function
165      is called once for every point.
166
167     Squirge towards edges (makes a leaf shape, previously split the screen in 4 but now only 1 :)
168     These ones must go first, to avoid x+1.0 < 0
169    */
170   if (fon[6]) {
171     /* x = mysgn(x) * pow(fabs(x),var[6]);
172        y = mysgn(y) * pow(fabs(y),var[6]);*/
173     x = -1.0 + 2.0*pow((x + 1.0)/2.0,var[6]);
174   }
175   if (fon[7]) {
176     y = -1.0 + 2.0*pow((y + 1.0)/2.0,var[7]);
177   }
178
179   /* Warping in/out */
180   if (fon[1]) {
181     x = x * var[1]; y = y * var[1];
182   }
183
184   /* Rotation */
185   if (fon[2]) {
186     nx=x*cos(1.1*var[2])+y*sin(1.1*var[2]);
187     ny=-x*sin(1.1*var[2])+y*cos(1.1*var[2]);
188     x=nx;
189     y=ny;
190   }
191
192   /* Asymptotes (looks like a plane with a horizon; equivalent to 1D warp) */
193   if (fon[3]) { /* Horizontal asymptote */
194     y=y*var[3];
195   }
196   if (fon[4]) { /* Vertical asymptote */
197     x=x+var[4]*x; /* this is the same maths as the last, but with op=0 */
198   }
199   if (fon[5]) { /* Vertical asymptote at right of screen */
200     x=(x-1.0)*var[5]+1.0;
201   }
202
203   /* Splitting (whirlwind effect): */
204   #define num_splits ( 2 + (int) (fabs(var[0]) * 1000) )
205   /* #define thru ( (float)(p%num_splits)/(float)(num_splits-1) ) */
206   #define thru ( (float)((int)(num_splits*(float)(p)/(float)(ps)))/(float)(num_splits-1) )
207   if (fon[8]) {
208     x=x+0.5*var[8]*(-1.0+2.0*thru);
209   }
210   if (fon[9]) {
211     y=y+0.5*var[9]*(-1.0+2.0*thru);
212   }
213
214   /* Waves */
215   if (fon[10]) {
216     y = y + 0.4*var[10]*sin(300.0*var[12]*x + 600.0*var[11]);
217   }
218   if (fon[13]) {
219     x = x + 0.4*var[13]*sin(300.0*var[15]*y + 600.0*var[14]);
220   }
221
222   cx[p]=x;
223   cy[p]=y;
224 }
225
226 /* Turns a forcefield on, and ensures its vars are suitable. */
227 void turn_on_field(int f)
228 {
229   if (!fon[f]) {
230     /* acc[f]=0.0; */
231     acc[f]=0.02 * myrnd();
232     vel[f]=0.0;
233     var[f]=op[f];
234   }
235   fon[f] = 1;
236   if (f == 10) {
237     turn_on_field(11);
238     turn_on_field(12);
239   }
240   if (f == 13) {
241     turn_on_field(14);
242     turn_on_field(15);
243   }
244 }
245
246 static void do_whirlwindwarp(Display *dpy, Window window)
247 {
248   Colormap cmap;
249   XWindowAttributes xgwa;
250   int got_color = 0;
251   XColor color[maxps]; /* The colour assigned to each star */
252   XColor bgcolor;
253   int p,f,nt, sx,sy, resets,lastresets,cnt;
254   int colsavailable;
255   int hue;
256
257   /* time_t lastframe = time((time_t) 0); */
258   struct timeval lastframe;
259
260   XClearWindow (dpy, window);
261   XGetWindowAttributes (dpy, window, &xgwa);
262   cmap = xgwa.colormap;
263   scrwid = xgwa.width;
264   scrhei = xgwa.height;
265
266   starsize=scrhei/480;
267   if (starsize<=0)
268     starsize=1;
269
270   /* Setup colours */
271   hsv_to_rgb (0.0, 0.0, 0.0, &bgcolor.red, &bgcolor.green, &bgcolor.blue);
272   got_color = XAllocColor (dpy, cmap, &bgcolor);
273   colsavailable=0;
274   for (p=0;p<ps;p++) {
275         if (!mono_p)
276           hsv_to_rgb (random()%360, .6+.4*myrnd(), .6+.4*myrnd(), &color[p].red, &color[p].green, &color[p].blue);
277           /* hsv_to_rgb (random()%360, 1.0, 1.0, &color[p].red, &color[p].green, &color[p].blue);   for stronger colours! */
278         if ((!mono_p) && (got_color = XAllocColor (dpy, cmap, &color[p]))) {
279           colsavailable=p;
280         } else {
281           if (colsavailable>0) /* assign colours from those already allocated */
282             color[p]=color[ p % colsavailable ];
283           else
284             color[p].pixel=default_fg_pixel;
285         }
286   }
287
288   /* Set up central (optimal) points for each different forcefield */
289   op[1] = 1; name[1] = "Warp";
290   op[2] = 0; name[2] = "Rotation";
291   op[3] = 1; name[3] = "Horizontal asymptote";
292   op[4] = 0; name[4] = "Vertical asymptote";
293   op[5] = 1; name[5] = "Vertical asymptote right";
294   op[6] = 1; name[6] = "Squirge x";
295   op[7] = 1; name[7] = "Squirge y";
296   op[0] = 0; name[0] = "Split number (inactive)";
297   op[8] = 0; name[8] = "Split velocity x";
298   op[9] = 0; name[9] = "Split velocity y";
299   op[10] = 0; name[10] = "Horizontal wave amplitude";
300   op[11] = myrnd()*3.141; name[11] = "Horizontal wave phase (inactive)";
301   op[12] = 0.01; name[12] = "Horizontal wave frequency (inactive)";
302   op[13] = 0; name[13] = "Vertical wave amplitude";
303   op[14] = myrnd()*3.141; name[14] = "Vertical wave phase (inactive)";
304   op[15] = 0.01; name[15] = "Vertical wave frequency (inactive)";
305
306   /* Initialise parameters to optimum, all off */
307   for (f=0;f<fs;f++) {
308     var[f]=op[f];
309     fon[f]=( myrnd()>0.5 ? 1 : 0 );
310     acc[f]=0.02 * myrnd();
311     vel[f]=0;
312   }
313
314   /* Initialise stars */
315   for (p=0;p<ps;p++)
316     stars_newp(p);
317
318   /* tx[nt],ty[nt] remember earlier screen plots (tails of stars)
319      which are deleted when nt comes round again */
320   nt = 0;
321   resets = 0;
322
323   hue = 180 + 180*myrnd();
324
325   gettimeofday(&lastframe, NULL);
326
327   while (1) {
328
329     if (myrnd()>0.75) {
330       /* Change one of the allocated colours to something near the current hue. */
331       /* By changing a random colour, we sometimes get a tight colour spread, sometime a diverse one. */
332       int p = colsavailable * (0.5+myrnd()/2);
333       hsv_to_rgb (hue, .6+.4*myrnd(), .6+.4*myrnd(), &color[p].red, &color[p].green, &color[p].blue);
334       if ((!mono_p) && (got_color = XAllocColor (dpy, cmap, &color[p]))) {
335       }
336       hue = hue + 0.5 + myrnd()*9.0;
337       if (hue<0) hue+=360;
338       if (hue>=360) hue-=360;
339     }
340
341       /* Move current points */
342       lastresets=resets;
343       resets=0;
344       for (p=0;p<ps;p++) {
345         /* Erase old */
346         XSetForeground (dpy, draw_gc, bgcolor.pixel);
347         /* XDrawPoint(dpy,window,draw_gc,tx[nt],ty[nt]); */
348         XFillRectangle(dpy,window,draw_gc,tx[nt],ty[nt],starsize,starsize);
349
350         /* Move */
351         stars_move(p);
352         /* If moved off screen, create a new one */
353         if (cx[p]<=-0.9999 || cx[p]>=+0.9999 ||
354             cy[p]<=-0.9999 || cy[p]>=+0.9999 ||
355             fabs(cx[p])<.0001 || fabs(cy[p])<.0001) {
356           stars_newp(p);
357           resets++;
358         } else if (myrnd()>0.99) /* Reset at random */
359           stars_newp(p);
360
361         /* Draw point */
362         sx=stars_scrpos_x(p);
363         sy=stars_scrpos_y(p);
364         XSetForeground (dpy, draw_gc, color[p].pixel);
365         /* XDrawPoint(dpy,window,draw_gc,sx,sy); */
366         XFillRectangle(dpy,window,draw_gc,sx,sy,starsize,starsize);
367
368         /* Remember it for removal later */
369         tx[nt]=sx;
370         ty[nt]=sy;
371         nt=(nt+1)%(ps*ts);
372       }
373
374       /* Adjust force fields */
375       cnt=0;
376       for (f=0;f<fs;f++) {
377
378         if (meters) { /* Remove meter from display */
379           XSetForeground(dpy, draw_gc, bgcolor.pixel);
380           stars_draw_meter(dpy,window,draw_gc,f);
381         }
382
383         /* Adjust forcefield's parameter */
384         if (fon[f]) {
385           /* This configuration produces var[f]s usually below 0.01 */
386           acc[f]=stars_perturb(acc[f],0,0.98,0.005);
387           vel[f]=stars_perturb(vel[f]+0.03*acc[f],0,0.995,0.0);
388           var[f]=op[f]+(var[f]-op[f])*0.9995+0.001*vel[f];
389         }
390         /* fprintf(stderr,"f=%i fon=%i acc=%f vel=%f var=%f\n",f,fon[f],acc[f],vel[f],var[f]); */
391
392         /* Decide whether to turn this forcefield on or off. */
393         /* prob_on makes the "splitting" effects less likely than the rest */
394         #define prob_on ( f==8 || f==9 ? 0.999975 : 0.9999 )
395         if ( fon[f]==0 && myrnd()>prob_on ) {
396           turn_on_field(f);
397         } else if ( fon[f]!=0 && myrnd()>0.99 && fabs(var[f]-op[f])<0.0005 && fabs(vel[f])<0.005 /* && fabs(acc[f])<0.01 */ ) {
398           /* We only turn it off if it has gently returned to its optimal (as opposed to rapidly passing through it). */
399           fon[f] = 0;
400         }
401
402         if (meters) { /* Redraw the meter */
403           XSetForeground(dpy, draw_gc, color[f].pixel);
404           stars_draw_meter(dpy,window,draw_gc,f);
405         }
406
407         if (fon[f])
408           cnt++;
409       }
410
411       /* Ensure at least three forcefields are on.
412        * BUG: Picking randomly might not be enough since 0,11,12,14 and 15 do nothing!
413        * But then what's wrong with a rare gentle twinkle?!
414       */
415       if (cnt<3) {
416         f=random() % fs;
417         turn_on_field(f);
418       }
419
420       if (meters) {
421         XSetForeground(dpy, draw_gc, bgcolor.pixel);
422         XDrawRectangle(dpy,window,draw_gc,0,0,lastresets*5,3);
423         XSetForeground(dpy, draw_gc, default_fg_pixel);
424         XDrawRectangle(dpy,window,draw_gc,0,0,resets*5,3);
425       }
426
427       /* Cap frames per second; do not go above specified fps: */
428       {
429         int maxfps = 200;
430         long utimeperframe = 1000000/maxfps;
431         struct timeval now;
432         long timediff;
433         gettimeofday(&now, NULL);
434         timediff = now.tv_sec*1000000 + now.tv_usec - lastframe.tv_sec*1000000 - lastframe.tv_usec;
435         if (timediff < utimeperframe) {
436           /* fprintf(stderr,"sleeping for %i\n",utimeperframe-timediff); */
437           usleep(utimeperframe-timediff);
438         }
439         lastframe = now;
440
441         XSync (dpy, False);
442         screenhack_handle_events (dpy);
443       }
444   }
445
446 }
447
448
449 char *progclass = "WhirlwindWarp";
450
451 char *defaults [] = {
452   ".background: black",
453   ".foreground: white",
454   "*points:     400",
455   "*tails:      8",
456   "*meters:     false",
457   0
458 };
459
460 XrmOptionDescRec options [] = {
461   { "-points",  ".points",      XrmoptionSepArg, 0 },
462   { "-tails",   ".tails",       XrmoptionSepArg, 0 },
463   { "-meters",  ".meters",      XrmoptionNoArg, "true" },
464   { 0, 0, 0, 0 }
465 };
466
467 void screenhack(Display *dpy, Window window)
468 {
469   if (init_whirlwindwarp(dpy, window))
470     do_whirlwindwarp(dpy, window);
471 }