1 /* -copyright-
2 #-#
3 #-# xsnow: let it snow on your desktop
4 #-# Copyright (C) 1984,1988,1990,1993-1995,2000-2001 Rick Jansen
5 #-# 	      2019,2020,2021 Willem Vermin
6 #-#
7 #-# This program is free software: you can redistribute it and/or modify
8 #-# it under the terms of the GNU General Public License as published by
9 #-# the Free Software Foundation, either version 3 of the License, or
10 #-# (at your option) any later version.
11 #-#
12 #-# This program is distributed in the hope that it will be useful,
13 #-# but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #-# GNU General Public License for more details.
16 #-#
17 #-# You should have received a copy of the GNU General Public License
18 #-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #-#
20 */
21 
22 #include <stdio.h>
23 #include <gtk/gtk.h>
24 #include <stdlib.h>
25 #include <math.h>
26 #include <assert.h>
27 #include "debug.h"
28 #include "xsnow.h"
29 #include "pixmaps.h"
30 #include "windows.h"
31 #include "hashtable.h"
32 #include "flags.h"
33 #include "ixpm.h"
34 #include "snow.h"
35 #include "utils.h"
36 #include "clocks.h"
37 #include "wind.h"
38 #include "fallensnow.h"
39 #include "scenery.h"
40 #include "ui.h"
41 #include "blowoff.h"
42 #include "treesnow.h"
43 
44 #define NOTACTIVE \
45    (Flags.BirdsOnly || !WorkspaceActive() || Flags.NoSnowFlakes)
46 
47 #define EXTRA_FLAKES 300
48 
49 #define add_flake_to_mainloop(f) add_to_mainloop1(PRIORITY_HIGH,time_snowflakes,(GSourceFunc)do_UpdateSnowFlake,f)
50 
51 //static cairo_surface_t **snow_surfaces;
52 static float      FlakesPerSecond;
53 static int        KillFlakes = 0;  // 1: signal to flakes to kill themselves, and do not generate flakes
54 static float      SnowSpeedFactor;
55 
56 static SnowMap   *snowPix;
57 static char    ***xsnow_xpm = NULL;
58 static int        NFlakeTypesVintage;
59 static int        MaxFlakeTypes;
60 
61 static int        do_genflakes(void *);
62 static void       InitFlake(Snow *flake);
63 static void       InitFlakesPerSecond(void);
64 static void       InitSnowColor(void);
65 static void       InitSnowSpeedFactor(void);
66 static int        do_show_flakecount(void *);
67 static void       init_snow_pix(void);
68 static void       EraseSnowFlake1(Snow *flake);
69 static void       DelFlake(Snow *flake);
70 static void       genxpmflake(char ***xpm, int w, int h);
71 static void       add_random_flakes(int n);
72 static void       SetSnowSize(void);
73 static int        do_UpdateSnowFlake(Snow *flake);
74 static int        do_SwitchFlakes(void *);
75 
76 static const float  LocalScale = 0.8;
77 
78 
snow_init()79 void snow_init()
80 {
81    int i;
82 
83    MaxFlakeTypes = 0;
84    while(snow_xpm[MaxFlakeTypes])
85       MaxFlakeTypes++;
86    NFlakeTypesVintage = MaxFlakeTypes;
87 
88    add_random_flakes(EXTRA_FLAKES);   // will change MaxFlakeTypes
89    //                            and create xsnow_xpm, containing
90    //                            vintage and new flakes
91 
92 
93    snowPix       = (SnowMap          *)malloc(MaxFlakeTypes*sizeof(SnowMap));
94 
95    P("MaxFlakeTypes: %d\n",MaxFlakeTypes);
96 
97    for (i=0; i<MaxFlakeTypes; i++)
98    {
99       //snow_surfaces[i] = NULL;
100       snowPix[i].surface = NULL;
101    }
102 
103    //init_snow_surfaces();
104    init_snow_pix();
105    InitSnowSpeedFactor();
106    InitFlakesPerSecond();
107    InitSnowColor();
108    InitSnowSpeedFactor();
109    add_to_mainloop(PRIORITY_DEFAULT, time_genflakes,      do_genflakes       );
110    add_to_mainloop(PRIORITY_DEFAULT, time_flakecount,     do_show_flakecount );
111    add_to_mainloop(PRIORITY_DEFAULT, time_switchflakes,   do_SwitchFlakes    );
112 
113    // now we would like to be able to get rid of the snow xpms:
114    /*
115       for (i=0; i<MaxFlakeTypes; i++)
116       xpm_destroy(xsnow_xpm[i]);
117       free(xsnow_xpm);
118       */
119    // but we cannot: they are needed if user changes color
120 }
121 
SetSnowSize()122 void SetSnowSize()
123 {
124    add_random_flakes(EXTRA_FLAKES);
125    //init_snow_surfaces();
126    init_snow_pix();
127    if (!global.IsDouble)
128       ClearScreen();
129 }
130 
snow_ui()131 void snow_ui()
132 {
133    UIDO (NoSnowFlakes,
134 	 if(Flags.NoSnowFlakes)
135 	 ClearScreen();                                            );
136    UIDO (SnowFlakesFactor               , InitFlakesPerSecond();   );
137    UIDOS(SnowColor                      ,
138 	 InitSnowColor();
139          InitFallenSnow();
140 	 ClearScreen();                                            );
141    UIDO (SnowSpeedFactor                , InitSnowSpeedFactor();   );
142    UIDO (FlakeCountMax                  ,                          );
143    UIDO (SnowSize                       ,
144 	 SetSnowSize();
145 	 Flags.VintageFlakes = 0;                                  );
146    P("%d snow_ui\n",global.counter++);
147 
148    static int prev=100;
149    if(ScaleChanged(&prev))
150       init_snow_pix();
151 }
152 
init_snow_pix()153 void init_snow_pix()
154 {
155    (void)LocalScale;
156    int flake;
157    P("%d init_snow_pix\n",counter++);
158    for (flake=0; flake<MaxFlakeTypes; flake++)
159    {
160       SnowMap *rp = &snowPix[flake];
161       int w,h;
162       sscanf(xsnow_xpm[flake][0],"%d %d",&w,&h);
163       w *= 0.01*Flags.Scale*LocalScale*global.WindowScale;
164       h *= 0.01*Flags.Scale*LocalScale*global.WindowScale;
165       rp->width  = w;
166       rp->height = h;
167       char **x;
168       int lines;
169       xpm_set_color(xsnow_xpm[flake], &x, &lines, Flags.SnowColor);
170       GdkPixbuf *pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)x);
171       if (w<1) w=1;
172       if (h<1) h=1;
173       if (w == 1 && h == 1) h = 2;
174       GdkPixbuf *pixbufscaled  = gdk_pixbuf_scale_simple(pixbuf,w,h,GDK_INTERP_HYPER);
175       xpm_destroy(x);
176       if (rp->surface)
177 	 cairo_surface_destroy(rp->surface);
178       rp->surface = gdk_cairo_surface_create_from_pixbuf (pixbufscaled, 0, NULL);
179       g_clear_object(&pixbuf);
180       g_clear_object(&pixbufscaled);
181    }
182    global.fluffpix = &snowPix[MaxFlakeTypes-1];
183 }
184 
snow_draw(cairo_t * cr)185 int snow_draw(cairo_t *cr)
186 {
187    if (Flags.NoSnowFlakes)
188       return TRUE;
189    P("snow_draw %d\n",counter++);
190 
191    set_begin();
192    Snow *flake;
193    while( (flake = (Snow *)set_next()) )
194    {
195       P("snow_draw %d %f\n",counter++,ALPHA);
196       //cairo_set_source_surface (cr, snow_surfaces[flake->whatFlake], flake->rx, flake->ry);
197       cairo_set_source_surface (cr, snowPix[flake->whatFlake].surface, flake->rx, flake->ry);
198       double alpha = ALPHA;
199       if (flake->fluff)
200 	 alpha *= (1-flake->flufftimer/flake->flufftime);
201       if (alpha < 0)
202 	 alpha = 0;
203       if (global.IsDouble || !(flake->freeze || flake->fluff))
204 	 my_cairo_paint_with_alpha(cr,alpha);
205       flake->ix = lrint(flake->rx);
206       flake->iy = lrint(flake->ry);
207    }
208    return TRUE;
209 }
210 
snow_erase(int force)211 int snow_erase(int force)
212 {
213    if (!force && Flags.NoSnowFlakes)
214       return TRUE;
215    set_begin();
216    Snow *flake;
217    int n = 0;
218    while( (flake = (Snow *)set_next()) )
219    {
220       EraseSnowFlake1(flake);
221       n++;
222    }
223    P("snow_erase %d %d\n",counter++,n);
224    return TRUE;
225 }
226 
do_genflakes(void * d)227 int do_genflakes(void *d)
228 {
229    if (Flags.Done)
230       return FALSE;
231 
232 #define RETURN do {Prevtime = TNow; return TRUE;} while (0)
233 
234    static double Prevtime;
235    static double sumdt;
236    static int    first_run = 1;
237    double TNow = wallclock();
238 
239    if (KillFlakes)
240       RETURN;
241 
242    if (NOTACTIVE)
243       RETURN;
244 
245    if (first_run)
246    {
247       first_run = 0;
248       Prevtime = wallclock();
249       sumdt    = 0;
250    }
251 
252    double dt = TNow - Prevtime;
253 
254    // after suspend or sleep dt could have a strange value
255    if (dt < 0 || dt > 10*time_genflakes)
256       RETURN;
257    int desflakes = lrint((dt+sumdt)*FlakesPerSecond);
258    P("desflakes: %lf %lf %d %lf %d\n",dt,sumdt,desflakes,FlakesPerSecond,global.FlakeCount);
259    if(desflakes == 0)  // save dt for use next time: happens with low snowfall rate
260       sumdt += dt;
261    else
262       sumdt = 0;
263 
264    int i;
265    for(i=0; i<desflakes; i++)
266    {
267       MakeFlake(-1);
268    }
269    RETURN;
270    (void)d;
271 #undef RETURN
272 }
273 
do_UpdateSnowFlake(Snow * flake)274 int do_UpdateSnowFlake(Snow *flake)
275 {
276    if(NOTACTIVE)
277       return TRUE;
278    //P(" ");printflake(flake);
279 
280    if ((flake->freeze || flake->fluff)  && global.RemoveFluff)
281    {
282       P("removefluff\n");
283       DelFlake(flake);
284       EraseSnowFlake1(flake);
285       return FALSE;
286    }
287    double FlakesDT = time_snowflakes;
288    float NewX = flake->rx + (flake->vx*FlakesDT)*SnowSpeedFactor;
289    float NewY = flake->ry + (flake->vy*FlakesDT)*SnowSpeedFactor;
290 
291    // handle fluff and KillFlakes
292    if (KillFlakes || (flake->fluff && flake->flufftimer > flake->flufftime))
293    {
294       DelFlake(flake);
295       EraseSnowFlake1(flake);
296       return FALSE;
297    }
298    if (flake->fluff)
299    {
300       if (!flake->freeze)
301       {
302 	 flake->rx = NewX;
303 	 flake->ry = NewY;
304       }
305       flake->flufftimer += FlakesDT;
306       return TRUE;
307    }
308 
309    int fckill = global.FlakeCount - global.FluffCount >= Flags.FlakeCountMax;
310    if (
311 	 (fckill && !flake->cyclic && drand48() > 0.3) ||  // high probability to remove blown-off flake
312 	 (fckill && drand48() > 0.9)                       // low probability to remove other flakes
313       )
314    {
315       fluffify(flake,0.51);
316       return TRUE;
317    }
318 
319    //
320    // update speed in x Direction
321    //
322    if (!Flags.NoWind)
323    {
324       flake->vx += FlakesDT*flake->wsens*(global.NewWind - flake->vx)/flake->m;
325       static float speedxmaxes[] = {100.0, 300.0, 600.0,};
326       float speedxmax = speedxmaxes[global.Wind];
327       if(flake->vx > speedxmax) flake->vx = speedxmax;
328       if(flake->vx < -speedxmax) flake->vx = -speedxmax;
329    }
330 
331    flake->vy += INITIALYSPEED * (drand48()-0.4)*0.1 ;
332    if (flake->vy > flake->ivy*1.5) flake->vy = flake->ivy*1.5;
333 
334    if (flake->freeze)
335    {
336       return TRUE;
337    }
338 
339    int flakew = snowPix[flake->whatFlake].width;
340    int flakeh = snowPix[flake->whatFlake].height;
341 
342    if(flake->cyclic)
343    {
344       if (NewX < -flakew)       NewX += global.SnowWinWidth-1;
345       if (NewX >= global.SnowWinWidth) NewX -= global.SnowWinWidth;
346    }
347    else if (NewX < 0 || NewX >= global.SnowWinWidth)
348    {
349       // not-cyclic flakes die when going left or right out of the window
350       DelFlake(flake);
351       return FALSE;
352    }
353 
354    // remove flake if it falls below bottom of screen:
355    if (NewY >= global.SnowWinHeight)
356    {
357       DelFlake(flake);
358       return FALSE;
359    }
360 
361    int nx = lrintf(NewX);
362    int ny = lrintf(NewY);
363 
364    if (!flake->fluff)
365    {
366       // determine if non-fluffy-flake touches the fallen snow,
367       // if so: make the flake inactive.
368       // the bottom pixels of the snowflake are at y = NewY + (height of flake)
369       // the bottompixels are at x values NewX .. NewX+(width of flake)-1
370 
371       FallenSnow *fsnow = global.FsnowFirst;
372       int found = 0;
373       // investigate if flake is in a not-hidden fallensnowarea on current workspace
374       while(fsnow && !found)
375       {
376 	 if(!fsnow->win.hidden)
377 	    if(fsnow->win.id == 0 ||(fsnow->win.ws == global.CWorkSpace || fsnow->win.sticky))
378 	    {
379 	       if (nx >= fsnow->x && nx <= fsnow->x + fsnow->w &&
380 		     ny < fsnow->y+2)
381 	       {
382 		  int i;
383 		  int istart = nx     - fsnow->x;
384 		  int imax   = istart + flakew;
385 		  if (istart < 0) istart = 0;
386 		  if (imax > fsnow->w) imax = fsnow->w;
387 		  for (i = istart; i < imax; i++)
388 		     if (ny > fsnow->y - fsnow->acth[i] - 1)
389 		     {
390 			if(fsnow->acth[i] < fsnow->h/2)
391 			   UpdateFallenSnowPartial(fsnow,nx - fsnow->x, flakew);
392 			if(HandleFallenSnow(fsnow))
393 			{
394 			   // always erase flake, but repaint it on top of
395 			   // the correct position on fsnow (if !NoFluffy))
396 			   if (Flags.NoFluffy)
397 			   {
398 			   }
399 			   else
400 			   {
401 			      // x-value: NewX;
402 			      // y-value of top of fallen snow: fsnow->y - fsnow->acth[i]
403 			      //flake->rx = NewX;
404 			      //flake->ry = fsnow->y - 0.5*fsnow->acth[i] - 0.8*drand48()*flakeh ;
405 			      fluffify(flake,0.1);
406 			   }
407 			   if (flake->fluff)
408 			      return TRUE;
409 			   else
410 			   {
411 			      DelFlake(flake);
412 			      return FALSE;
413 			   }
414 			}
415 			found = 1;
416 			break;
417 		     }
418 	       }
419 	    }
420 	 fsnow = fsnow->next;
421       }
422    }
423 
424    int x  = lrintf(flake->rx);
425    int y  = lrintf(flake->ry);
426 
427    if(global.Wind !=2  && !Flags.NoKeepSnowOnTrees && !Flags.NoTrees)
428    {
429       // check if flake is touching or in gSnowOnTreesRegion
430       // if so: remove it
431 
432       cairo_rectangle_int_t grec = {x,y,flakew,flakeh};
433       cairo_region_overlap_t in = cairo_region_contains_rectangle(global.gSnowOnTreesRegion,&grec);
434 
435       if (in == CAIRO_REGION_OVERLAP_PART || in ==  CAIRO_REGION_OVERLAP_IN)
436       {
437 	 P("part or in\n");
438 	 if (Flags.NoFluffy)
439 	 {
440 	    DelFlake(flake);
441 	    return FALSE;
442 	 }
443 	 else
444 	 {
445 	    fluffify(flake,0.4);
446 	    flake->freeze=1;
447 	    return TRUE;
448 	 }
449       }
450 
451       // check if flake is touching TreeRegion. If so: add snow to
452       // gSnowOnTreesRegion.
453       cairo_rectangle_int_t grect = {x,y,flakew,flakeh};
454       in = cairo_region_contains_rectangle(global.TreeRegion,&grect);
455       if (in == CAIRO_REGION_OVERLAP_PART)
456       {
457 	 // so, part of the flake is in TreeRegion.
458 	 // For each bottom pixel of the flake:
459 	 //   find out if bottompixel is in TreeRegion
460 	 //   if so:
461 	 //     move upwards until pixel is not in TreeRegion
462 	 //     That pixel will be designated as snow-on-tree
463 	 // Only one snow-on-tree pixel has to be found.
464 	 int i;
465 	 int found = 0;
466 	 int xfound,yfound;
467 	 for(i=0; i<flakew; i++)
468 	 {
469 	    if(found) break;
470 	    int ybot = y+flakeh;
471 	    int xbot = x+i;
472 	    //int in = XRectInRegion(global.TreeRegion,xbot,ybot,1,1);
473 	    cairo_rectangle_int_t grect = {xbot,ybot,1,1};
474 	    cairo_region_overlap_t in = cairo_region_contains_rectangle(global.TreeRegion,&grect);
475 	    //if (in != RectangleIn) // if bottom pixel not in TreeRegion, skip
476 	    if (in != CAIRO_REGION_OVERLAP_IN) // if bottom pixel not in TreeRegion, skip
477 	       continue;
478 	    // move upwards, until pixel is not in TreeRegion
479 	    int j;
480 	    for (j=ybot-1; j >= y; j--)
481 	    {
482 	       //int in = XRectInRegion(global.TreeRegion,xbot,j,1,1);
483 	       cairo_rectangle_int_t grect = {xbot,j,1,1};
484 	       cairo_region_overlap_t in = cairo_region_contains_rectangle(global.TreeRegion,&grect);
485 	       //if (in != RectangleIn)
486 	       if (in != CAIRO_REGION_OVERLAP_IN)
487 	       {
488 		  // pixel (xbot,j) is snow-on-tree
489 		  found = 1;
490 		  cairo_rectangle_int_t grec;
491 		  grec.x = xbot;
492 		  int p = 1+drand48()*3;
493 		  grec.y = j-p+1;
494 		  grec.width = p;
495 		  grec.height = p;
496 		  cairo_region_union_rectangle(global.gSnowOnTreesRegion,&grec);
497 
498 		  if(Flags.BlowSnow && global.OnTrees < Flags.MaxOnTrees)
499 		  {
500 		     global.SnowOnTrees[global.OnTrees].x = grec.x;
501 		     global.SnowOnTrees[global.OnTrees].y = grec.y;
502 		     global.OnTrees++;
503 		     //P("%d %d %d\n",global.OnTrees,rec.x,rec.y);
504 		  }
505 		  xfound = grec.x;
506 		  yfound = grec.y;
507 		  break;
508 	       }
509 	    }
510 	 }
511 	 // do not erase: this gives bad effects in fvwm-like desktops
512 	 if(found)
513 	 {
514 	    flake->freeze = 1;
515 	    fluffify(flake,0.6);
516 
517 	    Snow *newflake;
518 	    if(Flags.VintageFlakes)
519 	       newflake = MakeFlake(0);
520 	    else
521 	       newflake = MakeFlake(-1);
522 	    newflake->freeze = 1;
523 	    newflake->rx = xfound;
524 	    newflake->ry = yfound-snowPix[1].height*0.3f;
525 	    fluffify(newflake,8);
526 	    return TRUE;
527 	 }
528       }
529    }
530 
531    flake->rx = NewX;
532    flake->ry = NewY;
533    return TRUE;
534 }
535 
536 // creates snowflake from type (0<type<=SNOWFLAKEMAXTYPE)
537 // if <0, create random type
MakeFlake(int type)538 Snow *MakeFlake(int type)
539 {
540    Snow *flake = (Snow *)malloc(sizeof(Snow));
541    global.FlakeCount++;
542    if (type < 0)
543    {
544       if (Flags.VintageFlakes)
545 	 type = drand48()*NFlakeTypesVintage;
546       else
547 	 type = NFlakeTypesVintage + drand48()*(MaxFlakeTypes - NFlakeTypesVintage);
548    }
549    flake -> whatFlake = type;
550    InitFlake(flake);
551    add_flake_to_mainloop(flake);
552    return flake;
553 }
554 
555 
EraseSnowFlake1(Snow * flake)556 void EraseSnowFlake1(Snow *flake)
557 {
558    P("Erasesnowflake1\n");
559    if(global.IsDouble)
560       return;
561    int x = flake->ix-1;
562    int y = flake->iy-1;
563    int flakew = snowPix[flake->whatFlake].width+2;
564    int flakeh = snowPix[flake->whatFlake].height+2;
565    myXClearArea(global.display, global.SnowWin,
566 	 x, y,
567 	 flakew, flakeh,
568 	 global.xxposures);
569 }
570 
571 // a call to this function must be followed by 'return FALSE' to remove this
572 // flake from the g_timeout callback
DelFlake(Snow * flake)573 void DelFlake(Snow *flake)
574 {
575    if (flake->fluff)
576       global.FluffCount--;
577    set_erase(flake);
578    free(flake);
579    global.FlakeCount--;
580 }
581 
InitFlake(Snow * flake)582 void InitFlake(Snow *flake)
583 {
584    int flakew        = snowPix[flake->whatFlake].width;
585    int flakeh        = snowPix[flake->whatFlake].height;
586    flake->rx         = randint(global.SnowWinWidth - flakew);
587    flake->ry         = -randint(global.SnowWinHeight/10)-flakeh;
588    flake->cyclic     = 1;
589    flake->fluff      = 0;
590    flake->flufftimer = 0;
591    flake->flufftime  = 0;
592    flake->m          = drand48()+0.1;
593    if(Flags.NoWind)
594       flake->vx      = 0;
595    else
596       flake->vx      = randint(global.NewWind)/2;
597    flake->ivy        = INITIALYSPEED * sqrt(flake->m);
598    flake->vy         = flake->ivy;
599    flake->wsens      = drand48()*MAXWSENS;
600    flake->testing    = 0;
601    flake->freeze     = 0;
602    set_insert(flake); // will be picked up by snow_draw()
603    P("wsens: %f\n",flake->wsens);
604 }
605 
InitFlakesPerSecond()606 void InitFlakesPerSecond()
607 {
608    FlakesPerSecond = global.SnowWinWidth*0.0015*Flags.SnowFlakesFactor*
609       0.001*FLAKES_PER_SEC_PER_PIXEL*SnowSpeedFactor;
610    P("snowflakesfactor: %d %f %f\n",Flags.SnowFlakesFactor,FlakesPerSecond,SnowSpeedFactor);
611 }
612 
InitSnowColor()613 void InitSnowColor()
614 {
615    init_snow_pix();
616 }
617 
InitSnowSpeedFactor()618 void InitSnowSpeedFactor()
619 {
620    if (Flags.SnowSpeedFactor < 10)
621       SnowSpeedFactor = 0.01*10;
622    else
623       SnowSpeedFactor = 0.01*Flags.SnowSpeedFactor;
624    SnowSpeedFactor *= SNOWSPEED;
625 }
626 
627 
do_initsnow(void * d)628 int do_initsnow(void *d)
629 {
630    P("initsnow %d %d\n",global.FlakeCount,counter++);
631    if (Flags.Done)
632       return FALSE;
633    // first, kill all snowflakes
634    KillFlakes = 1;
635 
636    // if FlakeCount != 0, there are still some flakes
637    if (global.FlakeCount > 0)
638       return TRUE;
639 
640    // signal that flakes may be generated
641    KillFlakes = 0;
642 
643    return FALSE;  // stop callback
644    (void)d;
645 }
646 
do_show_flakecount(void * d)647 int do_show_flakecount(void *d)
648 {
649    if (Flags.Done)
650       return FALSE;
651    ui_show_nflakes(global.FlakeCount);
652    return TRUE;
653    (void)d;
654 }
655 
656 // generate random xpm for flake with dimensions wxh
657 // the flake will be rotated, so the w and h of the resulting xpm will
658 // be different from the input w and h.
genxpmflake(char *** xpm,int w,int h)659 void genxpmflake(char ***xpm, int w, int h)
660 {
661    P("%d genxpmflake %d %d\n",counter++,w,h);
662    const char c='.'; // imposed by xpm_set_color
663    int nmax = w*h;
664    float *x, *y;
665 
666    x = (float *)malloc(nmax*sizeof(float));
667    y = (float *)malloc(nmax*sizeof(float));
668 
669    int i,j;
670    float w2 = 0.5*w;
671    float h2 = 0.5*h;
672 
673    y[0] = 0;
674    x[0] = 0;    // to have at least one pixel in the centre
675    int n = 1;
676    for (i=0; i<h; i++)
677    {
678       float yy = i;
679       if (yy > h2)
680 	 yy = h - yy;
681       float py = 2*yy/h;
682       for (j=0; j<w; j++)
683       {
684 	 float xx = j;
685 	 if (xx > w2)
686 	    xx = w - xx;
687 	 float px = 2*xx/w;
688 	 float p = 1.1-(px*py);
689 	 //printf("%d %d %f %f %f %f %f\n",j,i,y,x,px,py,p);
690 	 if (drand48() > p )
691 	 {
692 	    if (n<nmax)
693 	    {
694 	       y[n] = i - w2;
695 	       x[n] = j - h2;
696 	       n++;
697 	    }
698 	 }
699       }
700    }
701    // rotate points with a random angle 0 .. pi
702    float a = drand48()*355.0/113.0;
703    float *xa, *ya;
704    xa = (float *)malloc(n*sizeof(float));
705    ya = (float *)malloc(n*sizeof(float));
706 
707 
708    for (i=0; i<n; i++)
709    {
710       xa[i] = x[i]*cosf(a)-y[i]*sinf(a);
711       ya[i] = x[i]*sinf(a)+y[i]*cosf(a);
712    }
713 
714    float xmin = xa[0];
715    float xmax = xa[0];
716    float ymin = ya[0];
717    float ymax = ya[0];
718 
719    for (i=0; i<n; i++)
720    {
721       // smallest xa:
722       if (xa[i] < xmin)
723 	 xmin = xa[i];
724       // etc ..
725       if (xa[i] > xmax)
726 	 xmax = xa[i];
727       if (ya[i] < ymin)
728 	 ymin = ya[i];
729       if (ya[i] > ymax)
730 	 ymax = ya[i];
731    }
732 
733    int nw = ceilf(xmax - xmin + 1);
734    int nh = ceilf(ymax - ymin + 1);
735 
736    // for some reason, drawing of cairo_surfaces derived from 1x1 xpm slow down
737    // the x server terribly. So, to be sure, I demand that none of
738    // the dimensions is 1, and that the width is a multiple of 8.
739    // Btw: genxpmflake rotates and compresses the original wxh xpm,
740    // and sometimes that results in an xpm with both dimensions one.
741 
742    // Now, suddenly, nw doesn't seem to matter any more?
743    //nw = ((nw-1)/8+1)*8;
744    //nw = ((nw-1)/32+1)*32;
745    P("%d nw nh: %d %d\n",counter++,nw,nh);
746    // Ah! nh should be 2 at least ...
747    // nw is not important
748    //
749    // Ok, I tried some things, and it seems that if both nw and nh are 1,
750    // then we have trouble.
751    // Some pages on the www point in the same direction.
752 
753    if (nw == 1 && nh == 1)
754       nh = 2;
755 
756    assert(nh>0);
757 
758    P("allocating %d\n",(nh+3)*sizeof(char*));
759    *xpm = (char **)malloc((nh+3)*sizeof(char*));
760    char **X = *xpm;
761 
762    X[0] = (char *)malloc(80*sizeof(char));
763    snprintf(X[0],80,"%d %d 2 1",nw,nh);
764 
765    X[1] = strdup("  c None");
766    X[2] = (char *)malloc(80*sizeof(char));
767    snprintf(X[2],80,"%c c black",c);
768 
769    int offset = 3;
770    P("allocating %d\n",nw+1);
771    assert(nw>=0);
772    for (i=0; i<nh; i++)
773       X[i+offset] = (char *) malloc((nw+1)*sizeof(char));
774 
775    for (i=0; i<nh; i++)
776    {
777       for(j=0; j<nw; j++)
778 	 X[i+offset][j] = ' ';
779       X[i+offset][nw] = 0;
780    }
781 
782    P("max: %d %f %f %f %f\n",n,ymin,ymax,xmin,xmax);
783    for (i=0; i<n; i++)
784    {
785       X[offset + (int)(ya[i]-ymin)] [(int)(xa[i]-xmin)] = c;
786       P("%f %f\n",ya[i]-ymin,xa[i]-xmin);
787    }
788    free(x);
789    free(y);
790    free(xa);
791    free(ya);
792 }
793 
add_random_flakes(int n)794 void add_random_flakes(int n)
795 {
796    int i;
797    // create a new array with snow-xpm's:
798    if (xsnow_xpm)
799    {
800       for (i=0; i<MaxFlakeTypes; i++)
801 	 xpm_destroy(xsnow_xpm[i]);
802       free(xsnow_xpm);
803    }
804    if(n < 1)
805       n = 1;
806    char ***x;
807    x = (char ***)malloc((n+NFlakeTypesVintage+1)*sizeof(char **));
808    int lines;
809    // copy Rick's vintage flakes:
810    for (i=0; i<NFlakeTypesVintage; i++)
811    {
812       xpm_set_color((char **)snow_xpm[i],&x[i],&lines,"snow");
813       //xpm_print((char**)snow_xpm[i]);
814    }
815    // add n flakes:
816    for (i=0; i<n; i++)
817    {
818       int w,h;
819       int m = Flags.SnowSize;
820       w = m+m*drand48();
821       h = m+m*drand48();
822       genxpmflake(&x[i+NFlakeTypesVintage],w,h);
823    }
824    MaxFlakeTypes   = n + NFlakeTypesVintage;
825    x[MaxFlakeTypes] = NULL;
826    xsnow_xpm = x;
827 }
828 
fluffify(Snow * flake,float t)829 void fluffify(Snow *flake,float t)
830 {
831    if (flake->fluff)
832       return;
833    flake->fluff      = 1;
834    flake->flufftimer = 0;
835    if (t > 0.01)
836       flake->flufftime  = t;
837    else
838       flake->flufftime = 0.01;
839    global.FluffCount ++;
840 }
841 
do_SwitchFlakes(void * d)842 int do_SwitchFlakes(void *d)
843 {
844    (void)d;
845    static int prev = 0;
846    if (Flags.VintageFlakes != prev)
847    {
848       P("SwitchFlakes\n");
849       set_begin();
850       Snow *flake;
851       while( (flake = (Snow *)set_next()) )
852       {
853 	 if (Flags.VintageFlakes)
854 	 {
855 	    flake->whatFlake = drand48()*NFlakeTypesVintage;
856 	 }
857 	 else
858 	 {
859 	    flake->whatFlake = NFlakeTypesVintage + drand48()*(MaxFlakeTypes - NFlakeTypesVintage);
860 	 }
861       }
862       prev = Flags.VintageFlakes;
863    }
864    return TRUE;
865 }
866 
printflake(Snow * flake)867 void printflake(Snow *flake)
868 {
869    printf("flake: %p rx: %6.0f ry: %6.0f fluff: %d freeze: %d ftr: %8.3f ft: %8.3f\n",
870 	 (void *)flake,flake->rx,flake->ry,flake->fluff,flake->freeze,flake->flufftimer,flake->flufftime);
871 }
872