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