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 #define DEFAULTTREETYPE 2
23 
24 #define NOTACTIVE \
25    (Flags.BirdsOnly || !WorkspaceActive())
26 
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30 #include <stdio.h>
31 #include <gtk/gtk.h>
32 #include <stdlib.h>
33 #include <X11/Intrinsic.h>
34 #include <X11/xpm.h>
35 #include "debug.h"
36 #include "scenery.h"
37 #include "windows.h"
38 #include "utils.h"
39 #include "ixpm.h"
40 #include "flags.h"
41 #include "pixmaps.h"
42 #include "fallensnow.h"
43 #include "csvpos.h"
44 #include "treesnow.h"
45 
46 static int              do_initbaum(void *);
47 static void             ReInitTree0(void);
48 static void             InitTreePixmaps(void);
49 static void             RedrawTrees(void);
50 static cairo_surface_t *tree_surface(int flip, const char **xpm, float scale);
51 static void             create_tree_surfaces(void);
52 static void             create_tree_dimensions(int tt);
53 static int              compartrees(const void *a, const void *b);
54 static void             setTreeScale(void);
55 
56 static int         NtreeTypes = 0;
57 static int         TreeRead = 0;
58 static char      **TreeXpm = NULL;
59 static Pixmap      TreePixmap[MAXTREETYPE+1][2];
60 static Pixmap      TreeMaskPixmap[MAXTREETYPE+1][2];
61 static int         TreeWidth[MAXTREETYPE+1], TreeHeight[MAXTREETYPE+1];
62 static int        *TreeType = NULL;
63 static int         NTrees     = 0;  // actual number of trees
64 static int         Newtrees   = 1;  // switch to determine if trees are to be repositioned
65 static Treeinfo  **Trees = NULL;
66 
67 static float       treeScale  = 1.0;
68 static const float LocalScale = 0.7;   // correction scale: if scenery is always too smnall, enlarge this and vice versa
69 static float       MinScale   = 0.6;   // scale for items with low y-coordinate
70 
71 
scenery_init()72 void scenery_init()
73 {
74    {
75       P("scenery_init\n");
76       // sanitize Flags.TreeType
77       int *a;
78       int n;
79       csvpos(Flags.TreeType,&a,&n);
80       int i;
81       int *b = (int *)malloc(sizeof(int)*n);
82       int m = 0;
83       for (i=0; i<n; i++)
84       {
85 	 if(a[i] >=0 && a[i] <= MAXTREETYPE)
86 	 {
87 	    b[m] = a[i];
88 	    m++;
89 	 }
90       }
91       free(Flags.TreeType);
92       vsc(&Flags.TreeType,b,m);
93       WriteFlags();
94       free(a);
95       free(b);
96    }
97    P("treecolor: %s\n",Flags.TreeColor);
98    setTreeScale();
99    P("treeScale: %f\n",treeScale);
100    //global.TreeRegion = XCreateRegion();
101    global.TreeRegion = cairo_region_create();
102    InitTreePixmaps();
103    add_to_mainloop(PRIORITY_DEFAULT, time_initbaum, do_initbaum);
104 }
105 
setTreeScale()106 void setTreeScale()
107 {
108    treeScale         = LocalScale*0.01*Flags.Scale*global.WindowScale;
109 }
110 
compartrees(const void * a,const void * b)111 int compartrees(const void *a, const void *b)
112 {
113    Treeinfo *ta = *(Treeinfo **)a;
114    Treeinfo *tb = *(Treeinfo **)b;
115    P("compartrees %d %d %d %d\n",ta->y, tb->y, ta->h, tb->h);
116    return ta->y + ta->h*ta->scale - tb->y - tb->h*tb->scale;
117 }
118 
scenery_draw(cairo_t * cr)119 int scenery_draw(cairo_t *cr)
120 {
121    int i;
122 
123    if(Flags.NoTrees)
124       return TRUE;
125    for (i=0; i<NTrees; i++)
126    {
127       Treeinfo *tree = Trees[i];
128       P("scenery: %d\n",tree->y+tree->h);
129       cairo_set_source_surface (cr, tree->surface, tree->x, tree->y);
130       my_cairo_paint_with_alpha(cr,ALPHA);
131    }
132    return TRUE;
133 }
134 
scenery_ui()135 void scenery_ui()
136 {
137    UIDOS(TreeType               , RedrawTrees(););
138    UIDO (DesiredNumberOfTrees   , RedrawTrees(););
139    UIDO (TreeFill               , RedrawTrees(););
140    UIDO (NoTrees                , if(!global.IsDouble) RedrawTrees(););
141    UIDOS(TreeColor              , ReInitTree0(););
142    UIDO (Overlap                , RedrawTrees(););
143 
144    static int prev = 100;
145    if(ScaleChanged(&prev))
146    {
147       setTreeScale();
148       RedrawTrees();
149    }
150 
151 }
152 
RedrawTrees()153 void RedrawTrees()
154 {
155    Newtrees = 1;     // this signals initbaum to recreate the trees
156    reinit_treesnow_region();
157    ClearScreen();
158 }
159 
EraseTrees()160 void EraseTrees()
161 {
162    RedrawTrees();
163 }
164 
tree_surface(int flip,const char ** xpm,float scale)165 cairo_surface_t *tree_surface(int flip, const char **xpm, float scale)
166 {
167    P("xpm[2]: %s\n",xpm[2]);
168    GdkPixbuf *pixbuf, *pixbuf1;
169    pixbuf1 = gdk_pixbuf_new_from_xpm_data((const char **)xpm);
170    if (flip)
171    {
172       pixbuf = gdk_pixbuf_flip(pixbuf1,1);
173       g_clear_object(&pixbuf1);
174    }
175    else
176       pixbuf = pixbuf1;
177 
178    int w,h;
179    sscanf(xpm[0],"%d %d",&w,&h);
180    P("tree_surface: %d %d %f\n",w,h,scale);
181    w *= scale;
182    h *= scale;
183    if (w < 1) w = 1;
184    if (h < 1) h = 1;
185    if (w == 1 && h == 1) h = 2;
186    GdkPixbuf *pixbufscaled  = gdk_pixbuf_scale_simple(pixbuf,w,h,GDK_INTERP_HYPER);
187    cairo_surface_t *surface = gdk_cairo_surface_create_from_pixbuf (pixbufscaled, 0, NULL);
188    g_clear_object(&pixbuf);
189    g_clear_object(&pixbufscaled);
190    return surface;
191 }
192 
create_tree_dimensions(int tt)193 void create_tree_dimensions(int tt)
194 {
195    sscanf(xpmtrees[tt][0],"%d %d",&TreeWidth[tt],&TreeHeight[tt]);
196 }
197 
198 // fallen snow and trees must have been initialized
199 // tree coordinates and so are recalculated here, in anticipation
200 // of a changed window size
201 // The function returns immediately if Newtrees==0, otherwize an attempt
202 // is done to place the DesiredNumberOfTrees
do_initbaum(void * d)203 int do_initbaum(void *d)
204 {
205    (void)d;
206    if (Flags.Done)
207       return FALSE;
208    P("%d initbaum %d %d\n",global.counter++,Newtrees, Flags.NoTrees);
209 
210    static int count = 0;
211 
212    if(global.RemoveFluff && count++ > 2)
213       global.RemoveFluff = 0;
214 
215    if (Flags.NoTrees || Newtrees == 0)
216       return TRUE;
217 
218    P("%d initbaum really...\n",global.counter++);
219    ClearScreen();
220    Newtrees = 0;
221 
222    int i,h,w;
223 
224    for (i=0; i<NTrees; i++)
225       free(Trees[i]);
226    free(Trees);
227    Trees = NULL;
228 
229    global.RemoveFluff = 1;
230    count              = 0;
231 
232 
233    cairo_region_destroy(global.gSnowOnTreesRegion);
234    cairo_region_destroy(global.TreeRegion);
235 
236    global.gSnowOnTreesRegion = cairo_region_create();
237    global.TreeRegion         = cairo_region_create();
238 
239    // determine which trees are to be used
240    //
241    int *tmptreetype, ntemp;
242    if(TreeRead)
243    {
244       TreeType = (int *)realloc(TreeType,1*sizeof(*TreeType));
245       TreeType[0] = 0;
246    }
247    else
248    {
249       if (!strcmp("all",Flags.TreeType))
250 	 // user wants all treetypes
251       {
252 	 ntemp = 1+MAXTREETYPE;
253 	 tmptreetype = (int *)malloc(sizeof(*tmptreetype)*ntemp);
254 	 int i;
255 	 for (i=0; i<ntemp; i++)
256 	    tmptreetype[i] = i;
257       }
258       else if (strlen(Flags.TreeType) == 0)
259 	 // default: use 1..MAXTREETYPE
260       {
261 	 ntemp = MAXTREETYPE;
262 	 tmptreetype = (int *)malloc(sizeof(*tmptreetype)*ntemp);
263 	 int i;
264 	 for (i=0; i<ntemp; i++)
265 	    tmptreetype[i] = i+1;
266       }
267       else
268       {
269 	 // decode string like "1,1,3,2,4"
270 	 csvpos(Flags.TreeType,&tmptreetype,&ntemp);
271       }
272 
273       NtreeTypes = 0;
274       for (i=0; i<ntemp; i++)
275       {
276 	 if (tmptreetype[i] >=0 && tmptreetype[i]<=MAXTREETYPE)
277 	 {
278 	    int j;
279 	    int unique = 1;
280 	    // investigate if this is already contained in TreeType.
281 	    // if so, do not use it. Note that this algorithm is not
282 	    // good scalable, when ntemp is large (100 ...) one should
283 	    // consider an algorithm involving qsort()
284 	    //
285 	    for (j=0; j<NtreeTypes; j++)
286 	       if (tmptreetype[i] == TreeType[j])
287 	       {
288 		  unique = 0;
289 		  break;
290 	       }
291 	    if (unique)
292 	    {
293 	       TreeType = (int *)realloc(TreeType,(NtreeTypes+1)*sizeof(*TreeType));
294 	       TreeType[NtreeTypes] = tmptreetype[i];
295 	       NtreeTypes++;
296 	    }
297 	 }
298       }
299       if(NtreeTypes == 0)
300       {
301 	 TreeType = (int *)realloc(TreeType,sizeof(*TreeType));
302 	 TreeType[0] = DEFAULTTREETYPE;
303 	 NtreeTypes++;
304       }
305       free(tmptreetype);
306    }
307 
308    // determine placement of trees and NTrees:
309 
310    NTrees    = 0;
311    for (i=0; i< 4*Flags.DesiredNumberOfTrees; i++) // no overlap permitted in certain cases
312    {
313       if (NTrees >= Flags.DesiredNumberOfTrees)
314 	 break;
315 
316       int tt = TreeType[randint(NtreeTypes)];
317       w = TreeWidth[tt];
318       h = TreeHeight[tt];
319 
320       int y1 = global.SnowWinHeight - global.MaxScrSnowDepth - h*treeScale;
321       int y2 = global.SnowWinHeight*(1.0 - 0.01*Flags.TreeFill);
322       if (y2>y1) y1=y2+1;
323 
324       int x = randint(global.SnowWinWidth-w*treeScale);
325       int y = y1 - randint(y1-y2);
326 
327       float myScale = (1-MinScale)*(y - y2)/(y1 - y2) + MinScale;
328       P("%d myScale: %d %d %d %f\n",global.counter++,y,y1,y2,myScale);
329       myScale *=treeScale;
330       cairo_rectangle_int_t grect = {x-1,y-1,(int)(myScale*w+2),(int)(myScale*h+2)};
331       cairo_region_overlap_t in   = cairo_region_contains_rectangle(global.TreeRegion,&grect);
332 
333       // no overlap considerations if:
334 
335       if(!global.IsDouble || !Flags.Overlap)
336 	 if (in == CAIRO_REGION_OVERLAP_IN || in == CAIRO_REGION_OVERLAP_PART)
337 	 {
338 	    P("skiptree\n");
339 	    continue;
340 	 }
341 
342       int flop = (drand48()>0.5);
343 
344       Treeinfo *tree = (Treeinfo *)malloc(sizeof(Treeinfo));
345       tree->x     = x;
346       tree->y     = y;
347       tree->w     = w;
348       tree->h     = h;
349       tree->type  = tt;
350       tree->rev   = flop;
351       tree->scale = myScale;
352       tree->surface = NULL;
353       P("tree: %d %d %d %d %d %p\n",tree->x, tree->y, tree->type, tree->rev, NTrees,(void *)tree);
354 
355       cairo_region_t *r;
356 
357       switch(tt)
358       {
359 	 case -SOMENUMBER:
360 	    r = gregionfromxpm((const char **)TreeXpm,tree->rev,tree->scale);
361 	    break;
362 	 default:
363 	    r = gregionfromxpm(xpmtrees[tt],tree->rev,tree->scale);
364 	    break;
365       }
366       cairo_region_translate(r,x,y);
367       cairo_region_union(global.TreeRegion,r);
368       cairo_region_destroy(r);
369 
370       NTrees++;
371       Trees = (Treeinfo **)realloc(Trees,NTrees*sizeof(Treeinfo*));
372       Trees[NTrees-1] = tree;
373    }
374 
375    // sort using y+h values of trees, so that higher trees are painted first
376    P("%d qsort: %d %ld\n",counter++,NTrees,sizeof(*Trees));
377    qsort(Trees,NTrees,sizeof(*Trees),compartrees);
378    create_tree_surfaces();
379    ReInitTree0();
380 
381    global.OnTrees = 0;
382    return TRUE;
383 }
384 
create_tree_surfaces()385 void create_tree_surfaces()
386 {
387    int i;
388    for (i=0; i<NTrees; i++)
389    {
390       Treeinfo *tree = Trees[i];
391       if(tree->surface)
392 	 cairo_surface_destroy(tree->surface);
393       if(TreeRead)
394 	 tree->surface = tree_surface(tree->rev, (const char **)TreeXpm,tree->scale);
395       else
396 	 tree->surface = tree_surface(tree->rev, (const char **)xpmtrees[tree->type],tree->scale);
397    }
398 }
399 
InitTreePixmaps()400 void InitTreePixmaps()
401 {
402    XpmAttributes attributes;
403    attributes.valuemask = XpmDepth;
404    attributes.depth     = global.SnowWinDepth;
405    char *path = NULL;
406    FILE *f = HomeOpen("xsnow/pixmaps/tree.xpm","r",&path);
407    if (f)
408    {
409       // there seems to be a local definition of tree
410       // set TreeType to some number, so we can respond accordingly
411       TreeType = (int *)realloc(TreeType,sizeof(*TreeType));
412       NtreeTypes = 1;
413       TreeRead = 1;
414       int rc = XpmReadFileToData(path,&TreeXpm);
415       if(rc == XpmSuccess)
416       {
417 	 int i;
418 	 for(i=0; i<2; i++)
419 	 {
420 	    iXpmCreatePixmapFromData(global.display, global.SnowWin, (const char **)TreeXpm,
421 		  &TreePixmap[0][i], &TreeMaskPixmap[0][i], &attributes,i);
422 	    create_tree_dimensions(0);
423 	 }
424 	 sscanf(*TreeXpm,"%d %d", &TreeWidth[0],&TreeHeight[0]);
425 	 P("treexpm %d %d\n",TreeWidth[0],TreeHeight[0]);
426 	 printf("using external tree: %s\n",path);
427 	 if (!Flags.NoMenu)
428 	    printf("Disabling menu.\n");
429 	 Flags.NoMenu = 1;
430       }
431       else
432       {
433 	 printf("Invalid external xpm for tree given: %s\n",path);
434 	 exit(1);
435       }
436       fclose(f);
437    }
438    else
439    {
440       int i;
441       for(i=0; i<2; i++)
442       {
443 	 int tt;
444 	 for (tt=0; tt<=MAXTREETYPE; tt++)
445 	 {
446 	    iXpmCreatePixmapFromData(global.display, global.SnowWin, xpmtrees[tt],
447 		  &TreePixmap[tt][i],&TreeMaskPixmap[tt][i],&attributes,i);
448 	    sscanf(xpmtrees[tt][0],"%d %d",&TreeWidth[tt],&TreeHeight[tt]);
449 	    create_tree_dimensions(tt);
450 	 }
451       }
452    }
453    if(path)
454       free(path);
455    global.OnTrees = 0;
456 }
457 
458 
459 //
460 // apply TreeColor to xpmtree[0] and xpmtree[1]
ReInitTree0()461 void ReInitTree0()
462 {
463    P("Reinittree0 %s\n",Flags.TreeColor);
464    XpmAttributes attributes;
465    attributes.valuemask = XpmDepth;
466    attributes.depth     = global.SnowWinDepth;
467    int i;
468    int n = TreeHeight[0]+3;
469    char **xpmtmp = (char **)malloc(n*sizeof(char *));
470    int j;
471    for (j=0; j<2; j++)
472       xpmtmp[j] = strdup(xpmtrees[0][j]);
473    xpmtmp[2] = strdup(". c ");
474    xpmtmp[2] = (char *)realloc(xpmtmp[2],strlen(xpmtmp[2])+strlen(Flags.TreeColor)+1);
475    strcat(xpmtmp[2],Flags.TreeColor);
476    for(j=3; j<n; j++)
477       xpmtmp[j] = strdup(xpmtrees[0][j]);
478    //P("xpmtmp:\n");xpm_print(xpmtmp);
479    for(i=0; i<2; i++)
480    {
481       XFreePixmap(global.display,TreePixmap[0][i]);
482       iXpmCreatePixmapFromData(global.display, global.SnowWin, (const char **)xpmtmp,
483 	    &TreePixmap[0][i],&TreeMaskPixmap[0][i],&attributes,i);
484       create_tree_dimensions(0);
485    }
486    for (i=0; i<NTrees; i++)
487    {
488       Treeinfo *tree = Trees[i];
489       P("tree->type %d\n",tree->type);
490       if (tree->type == 0)
491       {
492 	 tree->surface = tree_surface(tree->rev,(const char **)xpmtmp,tree->scale);
493       }
494    }
495 
496    for (j=0; j<n; j++)
497       free(xpmtmp[j]);
498    free(xpmtmp);
499 }
500 
501