1 #include "engine.h"
2 
3 enum
4 {
5     BM_BRANCH = 0,
6     BM_SOLID,
7     BM_IMAGE
8 };
9 
10 struct BlendMapBranch;
11 struct BlendMapSolid;
12 struct BlendMapImage;
13 
14 struct BlendMapNode
15 {
16     union
17     {
18         BlendMapBranch *branch;
19         BlendMapSolid *solid;
20         BlendMapImage *image;
21     };
22 
23     void cleanup(int type);
24     void splitsolid(uchar &type, uchar val);
25 };
26 
27 struct BlendMapBranch
28 {
29     uchar type[4];
30     BlendMapNode children[4];
31 
~BlendMapBranchBlendMapBranch32     ~BlendMapBranch()
33     {
34         loopi(4) children[i].cleanup(type[i]);
35     }
36 
37     uchar shrink(BlendMapNode &child, int quadrant);
38 };
39 
40 struct BlendMapSolid
41 {
42     uchar val;
43 
BlendMapSolidBlendMapSolid44     BlendMapSolid(uchar val) : val(val) {}
45 };
46 
47 #define BM_SCALE 1
48 #define BM_IMAGE_SIZE 64
49 
50 struct BlendMapImage
51 {
52     uchar data[BM_IMAGE_SIZE*BM_IMAGE_SIZE];
53 };
54 
cleanup(int type)55 void BlendMapNode::cleanup(int type)
56 {
57     switch(type)
58     {
59         case BM_BRANCH: delete branch; break;
60         case BM_IMAGE: delete image; break;
61     }
62 }
63 
64 #define DEFBMSOLIDS(n) n, n+1, n+2, n+3, n+4, n+5, n+6, n+7, n+8, n+9, n+10, n+11, n+12, n+13, n+14, n+15
65 
66 static BlendMapSolid bmsolids[256] =
67 {
68     DEFBMSOLIDS(0x00), DEFBMSOLIDS(0x10), DEFBMSOLIDS(0x20), DEFBMSOLIDS(0x30),
69     DEFBMSOLIDS(0x40), DEFBMSOLIDS(0x50), DEFBMSOLIDS(0x60), DEFBMSOLIDS(0x70),
70     DEFBMSOLIDS(0x80), DEFBMSOLIDS(0x90), DEFBMSOLIDS(0xA0), DEFBMSOLIDS(0xB0),
71     DEFBMSOLIDS(0xC0), DEFBMSOLIDS(0xD0), DEFBMSOLIDS(0xE0), DEFBMSOLIDS(0xF0),
72 };
73 
splitsolid(uchar & type,uchar val)74 void BlendMapNode::splitsolid(uchar &type, uchar val)
75 {
76     cleanup(type);
77     type = BM_BRANCH;
78     branch = new BlendMapBranch;
79     loopi(4)
80     {
81         branch->type[i] = BM_SOLID;
82         branch->children[i].solid = &bmsolids[val];
83     }
84 }
85 
shrink(BlendMapNode & child,int quadrant)86 uchar BlendMapBranch::shrink(BlendMapNode &child, int quadrant)
87 {
88     uchar childtype = type[quadrant];
89     child = children[quadrant];
90     type[quadrant] = BM_SOLID;
91     children[quadrant].solid = &bmsolids[0];
92     return childtype;
93 }
94 
95 struct BlendMapRoot : BlendMapNode
96 {
97     uchar type;
98 
BlendMapRootBlendMapRoot99     BlendMapRoot() : type(BM_SOLID) { solid = &bmsolids[0xFF]; }
BlendMapRootBlendMapRoot100     BlendMapRoot(uchar type, const BlendMapNode &node) : BlendMapNode(node), type(type) {}
101 
cleanupBlendMapRoot102     void cleanup() { BlendMapNode::cleanup(type); }
103 
shrinkBlendMapRoot104     void shrink(int quadrant)
105     {
106         if(type == BM_BRANCH)
107         {
108             BlendMapRoot oldroot = *this;
109             type = branch->shrink(*this, quadrant);
110             oldroot.cleanup();
111         }
112     }
113 };
114 
115 static BlendMapRoot blendmap;
116 
117 struct BlendMapCache
118 {
119     BlendMapRoot node;
120     int scale;
121     ivec2 origin;
122 };
123 
newblendmapcache()124 BlendMapCache *newblendmapcache() { return new BlendMapCache; }
125 
freeblendmapcache(BlendMapCache * & cache)126 void freeblendmapcache(BlendMapCache *&cache) { delete cache; cache = NULL; }
127 
setblendmaporigin(BlendMapCache * cache,const ivec & o,int size)128 bool setblendmaporigin(BlendMapCache *cache, const ivec &o, int size)
129 {
130     if(blendmap.type!=BM_BRANCH)
131     {
132         cache->node = blendmap;
133         cache->scale = worldscale-BM_SCALE;
134         cache->origin = ivec2(0, 0);
135         return cache->node.solid!=&bmsolids[0xFF];
136     }
137 
138     BlendMapBranch *bm = blendmap.branch;
139     int bmscale = worldscale-BM_SCALE, bmsize = 1<<bmscale,
140         x = o.x>>BM_SCALE, y = o.y>>BM_SCALE,
141         x1 = max(x-1, 0), y1 = max(y-1, 0),
142         x2 = min(((o.x + size + (1<<BM_SCALE)-1)>>BM_SCALE) + 1, bmsize),
143         y2 = min(((o.y + size + (1<<BM_SCALE)-1)>>BM_SCALE) + 1, bmsize),
144         diff = (x1^x2)|(y1^y2);
145     if(diff < bmsize) while(!(diff&(1<<(bmscale-1))))
146     {
147         bmscale--;
148         int n = (((y1>>bmscale)&1)<<1) | ((x1>>bmscale)&1);
149         if(bm->type[n]!=BM_BRANCH)
150         {
151             cache->node = BlendMapRoot(bm->type[n], bm->children[n]);
152             cache->scale = bmscale;
153             cache->origin = ivec2(x1&(~0U<<bmscale), y1&(~0U<<bmscale));
154             return cache->node.solid!=&bmsolids[0xFF];
155         }
156         bm = bm->children[n].branch;
157     }
158 
159     cache->node.type = BM_BRANCH;
160     cache->node.branch = bm;
161     cache->scale = bmscale;
162     cache->origin = ivec2(x1&(~0U<<bmscale), y1&(~0U<<bmscale));
163     return true;
164 }
165 
hasblendmap(BlendMapCache * cache)166 bool hasblendmap(BlendMapCache *cache)
167 {
168     return cache->node.solid!=&bmsolids[0xFF];
169 }
170 
lookupblendmap(int x,int y,BlendMapBranch * bm,int bmscale)171 static uchar lookupblendmap(int x, int y, BlendMapBranch *bm, int bmscale)
172 {
173     for(;;)
174     {
175         bmscale--;
176         int n = (((y>>bmscale)&1)<<1) | ((x>>bmscale)&1);
177         switch(bm->type[n])
178         {
179             case BM_SOLID: return bm->children[n].solid->val;
180             case BM_IMAGE: return bm->children[n].image->data[(y&((1<<bmscale)-1))*BM_IMAGE_SIZE + (x&((1<<bmscale)-1))];
181         }
182         bm = bm->children[n].branch;
183     }
184 }
185 
lookupblendmap(BlendMapCache * cache,const vec & pos)186 uchar lookupblendmap(BlendMapCache *cache, const vec &pos)
187 {
188     if(cache->node.type==BM_SOLID) return cache->node.solid->val;
189 
190     uchar vals[4], *val = vals;
191     float bx = pos.x/(1<<BM_SCALE) - 0.5f, by = pos.y/(1<<BM_SCALE) - 0.5f;
192     int ix = (int)floor(bx), iy = (int)floor(by),
193         rx = ix-cache->origin.x, ry = iy-cache->origin.y;
194     loop(vy, 2) loop(vx, 2)
195     {
196         int cx = clamp(rx+vx, 0, (1<<cache->scale)-1), cy = clamp(ry+vy, 0, (1<<cache->scale)-1);
197         if(cache->node.type==BM_IMAGE)
198             *val++ = cache->node.image->data[cy*BM_IMAGE_SIZE + cx];
199         else *val++ = lookupblendmap(cx, cy, cache->node.branch, cache->scale);
200     }
201     float fx = bx - ix, fy = by - iy;
202     return uchar((1-fy)*((1-fx)*vals[0] + fx*vals[1]) +
203                  fy*((1-fx)*vals[2] + fx*vals[3]));
204 }
205 
fillblendmap(uchar & type,BlendMapNode & node,int size,uchar val,int x1,int y1,int x2,int y2)206 static void fillblendmap(uchar &type, BlendMapNode &node, int size, uchar val, int x1, int y1, int x2, int y2)
207 {
208     if(max(x1, y1) <= 0 && min(x2, y2) >= size)
209     {
210         node.cleanup(type);
211         type = BM_SOLID;
212         node.solid = &bmsolids[val];
213         return;
214     }
215 
216     if(type==BM_BRANCH)
217     {
218         size /= 2;
219         if(y1 < size)
220         {
221             if(x1 < size) fillblendmap(node.branch->type[0], node.branch->children[0], size, val,
222                                         x1, y1, min(x2, size), min(y2, size));
223             if(x2 > size) fillblendmap(node.branch->type[1], node.branch->children[1], size, val,
224                                         max(x1-size, 0), y1, x2-size, min(y2, size));
225         }
226         if(y2 > size)
227         {
228             if(x1 < size) fillblendmap(node.branch->type[2], node.branch->children[2], size, val,
229                                         x1, max(y1-size, 0), min(x2, size), y2-size);
230             if(x2 > size) fillblendmap(node.branch->type[3], node.branch->children[3], size, val,
231                                         max(x1-size, 0), max(y1-size, 0), x2-size, y2-size);
232         }
233         loopi(4) if(node.branch->type[i]!=BM_SOLID || node.branch->children[i].solid->val!=val) return;
234         node.cleanup(type);
235         type = BM_SOLID;
236         node.solid = &bmsolids[val];
237         return;
238     }
239     else if(type==BM_SOLID)
240     {
241         uchar oldval = node.solid->val;
242         if(oldval==val) return;
243 
244         if(size > BM_IMAGE_SIZE)
245         {
246             node.splitsolid(type, oldval);
247             fillblendmap(type, node, size, val, x1, y1, x2, y2);
248             return;
249         }
250 
251         type = BM_IMAGE;
252         node.image = new BlendMapImage;
253         memset(node.image->data, oldval, sizeof(node.image->data));
254     }
255 
256     uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1];
257     loopi(y2-y1)
258     {
259         memset(dst, val, x2-x1);
260         dst += BM_IMAGE_SIZE;
261     }
262 }
263 
fillblendmap(int x,int y,int w,int h,uchar val)264 void fillblendmap(int x, int y, int w, int h, uchar val)
265 {
266     int bmsize = worldsize>>BM_SCALE,
267         x1 = clamp(x, 0, bmsize),
268         y1 = clamp(y, 0, bmsize),
269         x2 = clamp(x+w, 0, bmsize),
270         y2 = clamp(y+h, 0, bmsize);
271     if(max(x1, y1) >= bmsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return;
272     fillblendmap(blendmap.type, blendmap, bmsize, val, x1, y1, x2, y2);
273 }
274 
invertblendmap(uchar & type,BlendMapNode & node,int size,int x1,int y1,int x2,int y2)275 static void invertblendmap(uchar &type, BlendMapNode &node, int size, int x1, int y1, int x2, int y2)
276 {
277     if(type==BM_BRANCH)
278     {
279         size /= 2;
280         if(y1 < size)
281         {
282             if(x1 < size) invertblendmap(node.branch->type[0], node.branch->children[0], size,
283                                         x1, y1, min(x2, size), min(y2, size));
284             if(x2 > size) invertblendmap(node.branch->type[1], node.branch->children[1], size,
285                                         max(x1-size, 0), y1, x2-size, min(y2, size));
286         }
287         if(y2 > size)
288         {
289             if(x1 < size) invertblendmap(node.branch->type[2], node.branch->children[2], size,
290                                         x1, max(y1-size, 0), min(x2, size), y2-size);
291             if(x2 > size) invertblendmap(node.branch->type[3], node.branch->children[3], size,
292                                         max(x1-size, 0), max(y1-size, 0), x2-size, y2-size);
293         }
294         return;
295     }
296     else if(type==BM_SOLID)
297     {
298         fillblendmap(type, node, size, 255-node.solid->val, x1, y1, x2, y2);
299     }
300     else if(type==BM_IMAGE)
301     {
302         uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1];
303         loopi(y2-y1)
304         {
305             loopj(x2-x1) dst[j] = 255-dst[j];
306             dst += BM_IMAGE_SIZE;
307         }
308     }
309 }
310 
invertblendmap(int x,int y,int w,int h)311 void invertblendmap(int x, int y, int w, int h)
312 {
313     int bmsize = worldsize>>BM_SCALE,
314         x1 = clamp(x, 0, bmsize),
315         y1 = clamp(y, 0, bmsize),
316         x2 = clamp(x+w, 0, bmsize),
317         y2 = clamp(y+h, 0, bmsize);
318     if(max(x1, y1) >= bmsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return;
319     invertblendmap(blendmap.type, blendmap, bmsize, x1, y1, x2, y2);
320 }
321 
optimizeblendmap(uchar & type,BlendMapNode & node)322 static void optimizeblendmap(uchar &type, BlendMapNode &node)
323 {
324     switch(type)
325     {
326         case BM_IMAGE:
327         {
328             uint val = node.image->data[0];
329             val |= val<<8;
330             val |= val<<16;
331             for(uint *data = (uint *)node.image->data, *end = &data[sizeof(node.image->data)/sizeof(uint)]; data < end; data++)
332                 if(*data != val) return;
333             node.cleanup(type);
334             type = BM_SOLID;
335             node.solid = &bmsolids[val&0xFF];
336             break;
337         }
338         case BM_BRANCH:
339         {
340             loopi(4) optimizeblendmap(node.branch->type[i], node.branch->children[i]);
341             if(node.branch->type[3]!=BM_SOLID) return;
342             uint val = node.branch->children[3].solid->val;
343             loopi(3) if(node.branch->type[i]!=BM_SOLID || node.branch->children[i].solid->val != val) return;
344             node.cleanup(type);
345             type = BM_SOLID;
346             node.solid = &bmsolids[val];
347             break;
348         }
349     }
350 }
351 
optimizeblendmap()352 void optimizeblendmap()
353 {
354     optimizeblendmap(blendmap.type, blendmap);
355 }
356 
357 ICOMMAND(optimizeblendmap, "", (), optimizeblendmap());
358 
359 VARF(blendpaintmode, 0, 0, 5,
360 {
361     if(!blendpaintmode) stoppaintblendmap();
362 });
363 
blitblendmap(uchar & type,BlendMapNode & node,int bmx,int bmy,int bmsize,uchar * src,int sx,int sy,int sw,int sh,int smode)364 static void blitblendmap(uchar &type, BlendMapNode &node, int bmx, int bmy, int bmsize, uchar *src, int sx, int sy, int sw, int sh, int smode)
365 {
366     if(type==BM_BRANCH)
367     {
368         bmsize /= 2;
369         if(sy < bmy + bmsize)
370         {
371             if(sx < bmx + bmsize) blitblendmap(node.branch->type[0], node.branch->children[0], bmx, bmy, bmsize, src, sx, sy, sw, sh, smode);
372             if(sx + sw > bmx + bmsize) blitblendmap(node.branch->type[1], node.branch->children[1], bmx+bmsize, bmy, bmsize, src, sx, sy, sw, sh, smode);
373         }
374         if(sy + sh > bmy + bmsize)
375         {
376             if(sx < bmx + bmsize) blitblendmap(node.branch->type[2], node.branch->children[2], bmx, bmy+bmsize, bmsize, src, sx, sy, sw, sh, smode);
377             if(sx + sw > bmx + bmsize) blitblendmap(node.branch->type[3], node.branch->children[3], bmx+bmsize, bmy+bmsize, bmsize, src, sx, sy, sw, sh, smode);
378         }
379         return;
380     }
381     if(type==BM_SOLID)
382     {
383         uchar val = node.solid->val;
384         if(bmsize > BM_IMAGE_SIZE)
385         {
386             node.splitsolid(type, val);
387             blitblendmap(type, node, bmx, bmy, bmsize, src, sx, sy, sw, sh, smode);
388             return;
389         }
390 
391         type = BM_IMAGE;
392         node.image = new BlendMapImage;
393         memset(node.image->data, val, sizeof(node.image->data));
394     }
395 
396     int x1 = clamp(sx - bmx, 0, bmsize), y1 = clamp(sy - bmy, 0, bmsize),
397         x2 = clamp(sx+sw - bmx, 0, bmsize), y2 = clamp(sy+sh - bmy, 0, bmsize);
398     uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1];
399     src += max(bmy - sy, 0)*sw + max(bmx - sx, 0);
400     loopi(y2-y1)
401     {
402         switch(smode)
403         {
404             case 1:
405                 memcpy(dst, src, x2 - x1);
406                 break;
407 
408             case 2:
409                 loopi(x2 - x1) dst[i] = min(dst[i], src[i]);
410                 break;
411 
412             case 3:
413                 loopi(x2 - x1) dst[i] = max(dst[i], src[i]);
414                 break;
415 
416             case 4:
417                 loopi(x2 - x1) dst[i] = min(dst[i], uchar(0xFF - src[i]));
418                 break;
419 
420             case 5:
421                 loopi(x2 - x1) dst[i] = max(dst[i], uchar(0xFF - src[i]));
422                 break;
423         }
424         dst += BM_IMAGE_SIZE;
425         src += sw;
426     }
427 }
428 
blitblendmap(uchar * src,int sx,int sy,int sw,int sh,int smode)429 void blitblendmap(uchar *src, int sx, int sy, int sw, int sh, int smode)
430 {
431     int bmsize = worldsize>>BM_SCALE;
432     if(max(sx, sy) >= bmsize || min(sx+sw, sy+sh) <= 0 || min(sw, sh) <= 0) return;
433     blitblendmap(blendmap.type, blendmap, 0, 0, bmsize, src, sx, sy, sw, sh, smode);
434 }
435 
resetblendmap()436 void resetblendmap()
437 {
438     clearblendtextures();
439     blendmap.cleanup();
440     blendmap.type = BM_SOLID;
441     blendmap.solid = &bmsolids[0xFF];
442 }
443 
enlargeblendmap()444 void enlargeblendmap()
445 {
446     if(blendmap.type == BM_SOLID) return;
447     BlendMapBranch *branch = new BlendMapBranch;
448     branch->type[0] = blendmap.type;
449     branch->children[0] = blendmap;
450     loopi(3)
451     {
452         branch->type[i+1] = BM_SOLID;
453         branch->children[i+1].solid = &bmsolids[0xFF];
454     }
455     blendmap.type = BM_BRANCH;
456     blendmap.branch = branch;
457 }
458 
shrinkblendmap(int octant)459 void shrinkblendmap(int octant)
460 {
461     blendmap.shrink(octant&3);
462 }
463 
calcblendlayer(uchar & type,BlendMapNode & node,int bmx,int bmy,int bmsize,int cx,int cy,int cw,int ch)464 static int calcblendlayer(uchar &type, BlendMapNode &node, int bmx, int bmy, int bmsize, int cx, int cy, int cw, int ch)
465 {
466     if(type==BM_BRANCH)
467     {
468         bmsize /= 2;
469         int layer = -1;
470         if(cy < bmy + bmsize)
471         {
472             if(cx < bmx + bmsize)
473             {
474                 int clayer = calcblendlayer(node.branch->type[0], node.branch->children[0], bmx, bmy, bmsize, cx, cy, cw, ch);
475                 if(layer < 0) layer = clayer; else if(clayer != layer) return LAYER_BLEND;
476             }
477             if(cx + cw > bmx + bmsize)
478             {
479                 int clayer = calcblendlayer(node.branch->type[1], node.branch->children[1], bmx+bmsize, bmy, bmsize, cx, cy, cw, ch);
480                 if(layer < 0) layer = clayer; else if(clayer != layer) return LAYER_BLEND;
481             }
482         }
483         if(cy + ch > bmy + bmsize)
484         {
485             if(cx < bmx + bmsize)
486             {
487                 int clayer = calcblendlayer(node.branch->type[2], node.branch->children[2], bmx, bmy+bmsize, bmsize, cx, cy, cw, ch);
488                 if(layer < 0) layer = clayer; else if(clayer != layer) return LAYER_BLEND;
489             }
490             if(cx + cw > bmx + bmsize)
491             {
492                 int clayer = calcblendlayer(node.branch->type[3], node.branch->children[3], bmx+bmsize, bmy+bmsize, bmsize, cx, cy, cw, ch);
493                 if(layer < 0) layer = clayer; else if(clayer != layer) return LAYER_BLEND;
494             }
495         }
496         return layer >= 0 ? layer : LAYER_TOP;
497     }
498     uchar val;
499     if(type == BM_SOLID) val = node.solid->val;
500     else
501     {
502         int x1 = clamp(cx - bmx, 0, bmsize), y1 = clamp(cy - bmy, 0, bmsize),
503             x2 = clamp(cx+cw - bmx, 0, bmsize), y2 = clamp(cy+ch - bmy, 0, bmsize);
504         uchar *src = &node.image->data[y1*BM_IMAGE_SIZE + x1];
505         val = src[0];
506         loopi(y2-y1)
507         {
508             loopj(x2-x1) if(src[j] != val) return LAYER_BLEND;
509             src += BM_IMAGE_SIZE;
510         }
511     }
512     switch(val)
513     {
514         case 0xFF: return LAYER_TOP;
515         case 0: return LAYER_BOTTOM;
516         default: return LAYER_BLEND;
517     }
518 }
519 
calcblendlayer(int x1,int y1,int x2,int y2)520 int calcblendlayer(int x1, int y1, int x2, int y2)
521 {
522     int bmsize = worldsize>>BM_SCALE,
523         ux1 = max(x1, 0) >> BM_SCALE,
524         ux2 = (min(x2, worldsize) + (1<<BM_SCALE)-1) >> BM_SCALE,
525         uy1 = max(y1, 0) >> BM_SCALE,
526         uy2 = (min(y2, worldsize) + (1<<BM_SCALE)-1) >> BM_SCALE;
527     if(ux1 >= ux2 || uy1 >= uy2) return LAYER_TOP;
528     return calcblendlayer(blendmap.type, blendmap, 0, 0, bmsize, ux1, uy1, ux2-ux1, uy2-uy1);
529 }
530 
moveblendmap(uchar type,BlendMapNode & node,int size,int x,int y,int dx,int dy)531 void moveblendmap(uchar type, BlendMapNode &node, int size, int x, int y, int dx, int dy)
532 {
533     if(type == BM_BRANCH)
534     {
535         size /= 2;
536         moveblendmap(node.branch->type[0], node.branch->children[0], size, x, y, dx, dy);
537         moveblendmap(node.branch->type[1], node.branch->children[1], size, x + size, y, dx, dy);
538         moveblendmap(node.branch->type[2], node.branch->children[2], size, x, y + size, dx, dy);
539         moveblendmap(node.branch->type[3], node.branch->children[3], size, x + size, y + size, dx, dy);
540         return;
541     }
542     else if(type == BM_SOLID)
543     {
544         fillblendmap(x+dx, y+dy, size, size, node.solid->val);
545     }
546     else if(type == BM_IMAGE)
547     {
548         blitblendmap(node.image->data, x+dx, y+dy, size, size, 1);
549     }
550 }
551 
moveblendmap(int dx,int dy)552 void moveblendmap(int dx, int dy)
553 {
554     BlendMapRoot old = blendmap;
555     blendmap.type = BM_SOLID;
556     blendmap.solid = &bmsolids[0xFF];
557     moveblendmap(old.type, old, worldsize>>BM_SCALE, 0, 0, dx, dy);
558     old.cleanup();
559 }
560 
561 struct BlendBrush
562 {
563     char *name;
564     int w, h;
565     uchar *data;
566     GLuint tex;
567 
BlendBrushBlendBrush568     BlendBrush(const char *name, int w, int h) :
569       name(newstring(name)), w(w), h(h), data(new uchar[w*h]), tex(0)
570     {}
571 
~BlendBrushBlendBrush572     ~BlendBrush()
573     {
574         cleanup();
575         delete[] name;
576         if(data) delete[] data;
577     }
578 
cleanupBlendBrush579     void cleanup()
580     {
581         if(tex) { glDeleteTextures(1, &tex); tex = 0; }
582     }
583 
gentexBlendBrush584     void gentex()
585     {
586         if(!tex) glGenTextures(1, &tex);
587         uchar *buf = new uchar[w*h];
588         uchar *dst = buf, *src = data;
589         loopi(h)
590         {
591             loopj(w) *dst++ = 255 - *src++;
592         }
593         createtexture(tex, w, h, buf, 3, 1, hasTRG ? GL_R8 : GL_LUMINANCE8);
594         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
595         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
596         GLfloat border[4] = { 0, 0, 0, 0 };
597         glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border);
598         delete[] buf;
599     }
600 
reorientBlendBrush601     void reorient(bool flipx, bool flipy, bool swapxy)
602     {
603         uchar *rdata = new uchar[w*h];
604         int stridex = 1, stridey = 1;
605         if(swapxy) stridex *= h; else stridey *= w;
606         uchar *src = data, *dst = rdata;
607         if(flipx) { dst += (w-1)*stridex; stridex = -stridex; }
608         if(flipy) { dst += (h-1)*stridey; stridey = -stridey; }
609         loopi(h)
610         {
611             uchar *curdst = dst;
612             loopj(w)
613             {
614                 *curdst = *src++;
615                 curdst += stridex;
616             }
617             dst += stridey;
618         }
619         if(swapxy) swap(w, h);
620         delete[] data;
621         data = rdata;
622         if(tex) gentex();
623     }
624 };
625 
626 static vector<BlendBrush *> brushes;
627 static int curbrush = -1;
628 
629 VARFP(blendtexsize, 0, 9, 12-BM_SCALE, { clearblendtextures(); updateblendtextures(); });
630 
631 struct BlendTexture
632 {
633     int x, y, size;
634     uchar *data;
635     GLuint tex;
636     GLenum format;
637     bool valid;
638 
BlendTextureBlendTexture639     BlendTexture() : x(0), y(0), size(0), data(NULL), tex(0), format(GL_FALSE), valid(false)
640     {}
641 
~BlendTextureBlendTexture642     ~BlendTexture()
643     {
644         cleanup();
645     }
646 
setupBlendTexture647     bool setup(int sz)
648     {
649         if(!tex) glGenTextures(1, &tex);
650         sz = min(sz, maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize);
651         while(sz&(sz-1)) sz &= sz-1;
652         if(sz == size) return false;
653         size = sz;
654         if(data) delete[] data;
655         data = new uchar[size*size];
656         format = hasTRG ? GL_RED : GL_LUMINANCE;
657         createtexture(tex, size, size, NULL, 3, 1, hasTRG ? GL_R8 : GL_LUMINANCE8);
658         valid = false;
659         return true;
660     }
661 
cleanupBlendTexture662     void cleanup()
663     {
664         if(tex) { glDeleteTextures(1, &tex); tex = 0; }
665         if(data) { delete[] data; data = NULL; }
666         size = 0;
667         valid = false;
668     }
669 
containsBlendTexture670     bool contains(int px, int py) const
671     {
672         return px >= x && py >= y && px < x + 0x1000 && py < y + 0x1000;
673     }
674 
containsBlendTexture675     bool contains(const ivec &p) const { return contains(p.x, p.y); }
676 };
677 
678 static vector<BlendTexture> blendtexs;
679 
dumpblendtexs()680 void dumpblendtexs()
681 {
682     loopv(blendtexs)
683     {
684         BlendTexture &bt = blendtexs[i];
685         if(!bt.size || !bt.valid) continue;
686         ImageData temp(bt.size, bt.size, 1, blendtexs[i].data);
687         const char *map = game::getclientmap(), *name = strrchr(map, '/');
688         defformatstring(buf, "blendtex_%s_%d.png", name ? name+1 : map, i);
689         savepng(buf, temp, true);
690     }
691 }
692 
693 COMMAND(dumpblendtexs, "");
694 
renderblendtexture(uchar & type,BlendMapNode & node,int bmx,int bmy,int bmsize,uchar * dst,int dsize,int dx,int dy,int dw,int dh)695 static void renderblendtexture(uchar &type, BlendMapNode &node, int bmx, int bmy, int bmsize, uchar *dst, int dsize, int dx, int dy, int dw, int dh)
696 {
697     if(type==BM_BRANCH)
698     {
699         bmsize /= 2;
700         if(dy < bmy + bmsize)
701         {
702             if(dx < bmx + bmsize) renderblendtexture(node.branch->type[0], node.branch->children[0], bmx, bmy, bmsize, dst, dsize, dx, dy, dw, dh);
703             if(dx + dw > bmx + bmsize) renderblendtexture(node.branch->type[1], node.branch->children[1], bmx+bmsize, bmy, bmsize, dst, dsize, dx, dy, dw, dh);
704         }
705         if(dy + dh > bmy + bmsize)
706         {
707             if(dx < bmx + bmsize) renderblendtexture(node.branch->type[2], node.branch->children[2], bmx, bmy+bmsize, bmsize, dst, dsize, dx, dy, dw, dh);
708             if(dx + dw > bmx + bmsize) renderblendtexture(node.branch->type[3], node.branch->children[3], bmx+bmsize, bmy+bmsize, bmsize, dst, dsize, dx, dy, dw, dh);
709         }
710         return;
711     }
712 
713     int x1 = clamp(dx - bmx, 0, bmsize), y1 = clamp(dy - bmy, 0, bmsize),
714         x2 = clamp(dx+dw - bmx, 0, bmsize), y2 = clamp(dy+dh - bmy, 0, bmsize),
715         tsize = 1<<(min(worldscale, 12)-BM_SCALE),
716         step = tsize/dsize, stepw = (x2 - x1)/step, steph = (y2 - y1)/step;
717     dst += max(bmy - dy, 0)/step*dsize + max(bmx - dx, 0)/step;
718     if(type == BM_SOLID) loopi(steph)
719     {
720         memset(dst, node.solid->val, stepw);
721         dst += dsize;
722     }
723     else
724     {
725         uchar *src = &node.image->data[y1*BM_IMAGE_SIZE + x1];
726         loopi(steph)
727         {
728             if(step <= 1) memcpy(dst, src, stepw);
729             else for(int j = 0, k = 0; j < stepw; j++, k += step) dst[j] = src[k];
730             src += step*BM_IMAGE_SIZE;
731             dst += dsize;
732         }
733     }
734 }
735 
renderblendtexture(uchar * dst,int dsize,int dx,int dy,int dw,int dh)736 void renderblendtexture(uchar *dst, int dsize, int dx, int dy, int dw, int dh)
737 {
738     int bmsize = worldsize>>BM_SCALE;
739     if(max(dx, dy) >= bmsize || min(dx+dw, dy+dh) <= 0 || min(dw, dh) <= 0) return;
740     renderblendtexture(blendmap.type, blendmap, 0, 0, bmsize, dst, dsize, dx, dy, dw, dh);
741 }
742 
usesblendmap(uchar & type,BlendMapNode & node,int bmx,int bmy,int bmsize,int ux,int uy,int uw,int uh)743 static bool usesblendmap(uchar &type, BlendMapNode &node, int bmx, int bmy, int bmsize, int ux, int uy, int uw, int uh)
744 {
745     if(type==BM_BRANCH)
746     {
747         bmsize /= 2;
748         if(uy < bmy + bmsize)
749         {
750             if(ux < bmx + bmsize && usesblendmap(node.branch->type[0], node.branch->children[0], bmx, bmy, bmsize, ux, uy, uw, uh))
751                 return true;
752             if(ux + uw > bmx + bmsize && usesblendmap(node.branch->type[1], node.branch->children[1], bmx+bmsize, bmy, bmsize, ux, uy, uw, uh))
753                 return true;
754         }
755         if(uy + uh > bmy + bmsize)
756         {
757             if(ux < bmx + bmsize && usesblendmap(node.branch->type[2], node.branch->children[2], bmx, bmy+bmsize, bmsize, ux, uy, uw, uh))
758                 return true;
759             if(ux + uw > bmx + bmsize && usesblendmap(node.branch->type[3], node.branch->children[3], bmx+bmsize, bmy+bmsize, bmsize, ux, uy, uw, uh))
760                 return true;
761         }
762         return false;
763     }
764     return type!=BM_SOLID || node.solid->val != 0xFF;
765 }
766 
usesblendmap(int x1,int y1,int x2,int y2)767 bool usesblendmap(int x1, int y1, int x2, int y2)
768 {
769     int bmsize = worldsize>>BM_SCALE,
770         ux1 = max(x1, 0) >> BM_SCALE,
771         ux2 = (min(x2, worldsize) + (1<<BM_SCALE)-1) >> BM_SCALE,
772         uy1 = max(y1, 0) >> BM_SCALE,
773         uy2 = (min(y2, worldsize) + (1<<BM_SCALE)-1) >> BM_SCALE;
774     if(ux1 >= ux2 || uy1 >= uy2) return false;
775     return usesblendmap(blendmap.type, blendmap, 0, 0, bmsize, ux1, uy1, ux2-ux1, uy2-uy1);
776 }
777 
bindblendtexture(const ivec & p)778 void bindblendtexture(const ivec &p)
779 {
780     loopv(blendtexs) if(blendtexs[i].contains(p))
781     {
782         BlendTexture &bt = blendtexs[i];
783         int tsize = 1<<min(worldscale, 12);
784         GLOBALPARAMF(blendmapparams, bt.x, bt.y, 1.0f/tsize, 1.0f/tsize);
785         glBindTexture(GL_TEXTURE_2D, bt.tex);
786         break;
787     }
788 }
789 
updateblendtextures(uchar & type,BlendMapNode & node,int bmx,int bmy,int bmsize,int ux,int uy,int uw,int uh)790 static void updateblendtextures(uchar &type, BlendMapNode &node, int bmx, int bmy, int bmsize, int ux, int uy, int uw, int uh)
791 {
792     if(type==BM_BRANCH)
793     {
794         if(bmsize > 0x1000>>BM_SCALE)
795         {
796             bmsize /= 2;
797             if(uy < bmy + bmsize)
798             {
799                 if(ux < bmx + bmsize) updateblendtextures(node.branch->type[0], node.branch->children[0], bmx, bmy, bmsize, ux, uy, uw, uh);
800                 if(ux + uw > bmx + bmsize) updateblendtextures(node.branch->type[1], node.branch->children[1], bmx+bmsize, bmy, bmsize, ux, uy, uw, uh);
801             }
802             if(uy + uh > bmy + bmsize)
803             {
804                 if(ux < bmx + bmsize) updateblendtextures(node.branch->type[2], node.branch->children[2], bmx, bmy+bmsize, bmsize, ux, uy, uw, uh);
805                 if(ux + uw > bmx + bmsize) updateblendtextures(node.branch->type[3], node.branch->children[3], bmx+bmsize, bmy+bmsize, bmsize, ux, uy, uw, uh);
806             }
807             return;
808         }
809         if(!usesblendmap(type, node, bmx, bmy, bmsize, ux, uy, uw, uh)) return;
810     }
811     else if(type==BM_SOLID && node.solid->val == 0xFF) return;
812 
813     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
814 
815     int tx1 = max(bmx, ux)&~((0x1000>>BM_SCALE)-1),
816         tx2 = (min(bmx+bmsize, ux+uw) + (0x1000>>BM_SCALE)-1)&~((0x1000>>BM_SCALE)-1),
817         ty1 = max(bmy, uy)&~((0x1000>>BM_SCALE)-1),
818         ty2 = (min(bmy+bmsize, uy+uh) + (0x1000>>BM_SCALE)-1)&~((0x1000>>BM_SCALE)-1);
819     for(int ty = ty1; ty < ty2; ty += 0x1000>>BM_SCALE) for(int tx = tx1; tx < tx2; tx += 0x1000>>BM_SCALE)
820     {
821         BlendTexture *bt = NULL;
822         loopv(blendtexs) if(blendtexs[i].contains(tx<<BM_SCALE, ty<<BM_SCALE)) { bt = &blendtexs[i]; break; }
823         if(!bt)
824         {
825             bt = &blendtexs.add();
826             bt->x = tx<<BM_SCALE;
827             bt->y = ty<<BM_SCALE;
828         }
829         bt->setup(1<<min(worldscale-BM_SCALE, blendtexsize));
830         int tsize = 1<<(min(worldscale, 12)-BM_SCALE),
831             ux1 = tx, ux2 = tx + tsize, uy1 = ty, uy2 = ty + tsize,
832             step = tsize/bt->size;
833         if(!bt->valid)
834         {
835             ux1 = max(ux1, ux&~(step-1));
836             ux2 = min(ux2, (ux+uw+step-1)&~(step-1));
837             uy1 = max(uy1, uy&~(step-1));
838             uy2 = min(uy2, (uy+uh+step-1)&~(step-1));
839             bt->valid = true;
840         }
841         uchar *data = bt->data + (uy1-ty)/step*bt->size + (ux1-tx)/step;
842         renderblendtexture(type, node, bmx, bmy, bmsize, data, bt->size, ux1, uy1, ux2-ux1, uy2-uy1);
843         glPixelStorei(GL_UNPACK_ROW_LENGTH, bt->size);
844         glBindTexture(GL_TEXTURE_2D, bt->tex);
845         glTexSubImage2D(GL_TEXTURE_2D, 0, (ux1-tx)/step, (uy1-ty)/step, (ux2-ux1)/step, (uy2-uy1)/step, bt->format, GL_UNSIGNED_BYTE, data);
846     }
847 
848     glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
849 }
850 
updateblendtextures(int x1,int y1,int x2,int y2)851 void updateblendtextures(int x1, int y1, int x2, int y2)
852 {
853     int bmsize = worldsize>>BM_SCALE,
854         ux1 = max(x1, 0) >> BM_SCALE,
855         ux2 = (min(x2, worldsize) + (1<<BM_SCALE)-1) >> BM_SCALE,
856         uy1 = max(y1, 0) >> BM_SCALE,
857         uy2 = (min(y2, worldsize) + (1<<BM_SCALE)-1) >> BM_SCALE;
858     if(ux1 >= ux2 || uy1 >= uy2) return;
859     updateblendtextures(blendmap.type, blendmap, 0, 0, bmsize, ux1, uy1, ux2-ux1, uy2-uy1);
860 }
861 
clearblendtextures()862 void clearblendtextures()
863 {
864     loopv(blendtexs) blendtexs[i].cleanup();
865     blendtexs.shrink(0);
866 }
867 
cleanupblendmap()868 void cleanupblendmap()
869 {
870     loopv(brushes) brushes[i]->cleanup();
871     loopv(blendtexs) blendtexs[i].cleanup();
872 }
873 
874 ICOMMAND(clearblendbrushes, "", (),
875 {
876     while(brushes.length()) delete brushes.pop();
877     curbrush = -1;
878 });
879 
delblendbrush(const char * name)880 void delblendbrush(const char *name)
881 {
882     loopv(brushes) if(!strcmp(brushes[i]->name, name))
883     {
884         delete brushes[i];
885         brushes.remove(i--);
886     }
887     curbrush = brushes.empty() ? -1 : clamp(curbrush, 0, brushes.length()-1);
888 }
889 
890 COMMAND(delblendbrush, "s");
891 
addblendbrush(const char * name,const char * imgname)892 void addblendbrush(const char *name, const char *imgname)
893 {
894     delblendbrush(name);
895 
896     ImageData s;
897     if(!loadimage(imgname, s)) { conoutf(CON_ERROR, "could not load blend brush image %s", imgname); return; }
898     if(max(s.w, s.h) > (1<<12))
899     {
900         conoutf(CON_ERROR, "blend brush image size exceeded %dx%d pixels: %s", 1<<12, 1<<12, imgname);
901         return;
902     }
903 
904     BlendBrush *brush = new BlendBrush(name, s.w, s.h);
905 
906     uchar *dst = brush->data, *srcrow = s.data;
907     loopi(s.h)
908     {
909         for(uchar *src = srcrow, *end = &srcrow[s.w*s.bpp]; src < end; src += s.bpp)
910             *dst++ = src[0];
911         srcrow += s.pitch;
912     }
913 
914     brushes.add(brush);
915     if(curbrush < 0) curbrush = 0;
916     else if(curbrush >= brushes.length()) curbrush = brushes.length()-1;
917 
918 }
919 
920 COMMAND(addblendbrush, "ss");
921 
922 ICOMMAND(nextblendbrush, "i", (int *dir),
923 {
924     curbrush += *dir < 0 ? -1 : 1;
925     if(brushes.empty()) curbrush = -1;
926     else if(!brushes.inrange(curbrush)) curbrush = *dir < 0 ? brushes.length()-1 : 0;
927 });
928 
929 ICOMMAND(setblendbrush, "s", (char *name),
930 {
931     loopv(brushes) if(!strcmp(brushes[i]->name, name)) { curbrush = i; break; }
932 });
933 
934 ICOMMAND(getblendbrushname, "i", (int *n),
935 {
936     result(brushes.inrange(*n) ? brushes[*n]->name : "");
937 });
938 
939 ICOMMAND(curblendbrush, "", (), intret(curbrush));
940 
941 extern int nompedit;
942 
canpaintblendmap(bool brush=true,bool sel=false,bool msg=true)943 bool canpaintblendmap(bool brush = true, bool sel = false, bool msg = true)
944 {
945     if(noedit(!sel, msg) || (nompedit && multiplayer())) return false;
946     if(!blendpaintmode)
947     {
948         if(msg) conoutf(CON_ERROR, "operation only allowed in blend paint mode");
949         return false;
950     }
951     if(brush && !brushes.inrange(curbrush))
952     {
953         if(msg) conoutf(CON_ERROR, "no blend brush selected");
954         return false;
955     }
956     return true;
957 }
958 
959 ICOMMAND(rotateblendbrush, "i", (int *val),
960 {
961     if(!canpaintblendmap()) return;
962 
963     int numrots = *val < 0 ? 3 : clamp(*val, 1, 5);
964     BlendBrush *brush = brushes[curbrush];
965     brush->reorient(numrots>=2 && numrots<=4, numrots<=2 || numrots==5, (numrots&5)==1);
966 });
967 
paintblendmap(bool msg)968 void paintblendmap(bool msg)
969 {
970     if(!canpaintblendmap(true, false, msg)) return;
971 
972     BlendBrush *brush = brushes[curbrush];
973     int x = (int)floor(clamp(worldpos.x, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*brush->w),
974         y = (int)floor(clamp(worldpos.y, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*brush->h);
975     blitblendmap(brush->data, x, y, brush->w, brush->h, blendpaintmode);
976     previewblends(ivec((x-1)<<BM_SCALE, (y-1)<<BM_SCALE, 0),
977                   ivec((x+brush->w+1)<<BM_SCALE, (y+brush->h+1)<<BM_SCALE, worldsize));
978 }
979 
980 VAR(paintblendmapdelay, 1, 500, 3000);
981 VAR(paintblendmapinterval, 1, 30, 3000);
982 
983 int paintingblendmap = 0, lastpaintblendmap = 0;
984 
stoppaintblendmap()985 void stoppaintblendmap()
986 {
987     paintingblendmap = 0;
988     lastpaintblendmap = 0;
989 }
990 
trypaintblendmap()991 void trypaintblendmap()
992 {
993     if(!paintingblendmap || totalmillis - paintingblendmap < paintblendmapdelay) return;
994     if(lastpaintblendmap)
995     {
996         int diff = totalmillis - lastpaintblendmap;
997         if(diff < paintblendmapinterval) return;
998         lastpaintblendmap = (diff - diff%paintblendmapinterval) + lastpaintblendmap;
999     }
1000     else lastpaintblendmap = totalmillis;
1001     paintblendmap(false);
1002 }
1003 
1004 ICOMMAND(paintblendmap, "D", (int *isdown),
1005 {
1006     if(*isdown)
1007     {
1008         if(!paintingblendmap) { paintblendmap(true); paintingblendmap = totalmillis; }
1009     }
1010     else stoppaintblendmap();
1011 });
1012 
clearblendmapsel()1013 void clearblendmapsel()
1014 {
1015     if(noedit(false) || (nompedit && multiplayer())) return;
1016     extern selinfo sel;
1017     int x1 = sel.o.x>>BM_SCALE, y1 = sel.o.y>>BM_SCALE,
1018         x2 = (sel.o.x+sel.s.x*sel.grid+(1<<BM_SCALE)-1)>>BM_SCALE,
1019         y2 = (sel.o.y+sel.s.y*sel.grid+(1<<BM_SCALE)-1)>>BM_SCALE;
1020     fillblendmap(x1, y1, x2-x1, y2-y1, 0xFF);
1021     previewblends(ivec(x1<<BM_SCALE, y1<<BM_SCALE, 0),
1022                   ivec(x2<<BM_SCALE, y2<<BM_SCALE, worldsize));
1023 }
1024 
1025 COMMAND(clearblendmapsel, "");
1026 
invertblendmapsel()1027 void invertblendmapsel()
1028 {
1029     if(noedit(false) || (nompedit && multiplayer())) return;
1030     extern selinfo sel;
1031     int x1 = sel.o.x>>BM_SCALE, y1 = sel.o.y>>BM_SCALE,
1032         x2 = (sel.o.x+sel.s.x*sel.grid+(1<<BM_SCALE)-1)>>BM_SCALE,
1033         y2 = (sel.o.y+sel.s.y*sel.grid+(1<<BM_SCALE)-1)>>BM_SCALE;
1034     invertblendmap(x1, y1, x2-x1, y2-y1);
1035     previewblends(ivec(x1<<BM_SCALE, y1<<BM_SCALE, 0),
1036                   ivec(x2<<BM_SCALE, y2<<BM_SCALE, worldsize));
1037 }
1038 
1039 COMMAND(invertblendmapsel, "");
1040 
1041 ICOMMAND(invertblendmap, "", (),
1042 {
1043     if(noedit(false) || (nompedit && multiplayer())) return;
1044     invertblendmap(0, 0, worldsize>>BM_SCALE, worldsize>>BM_SCALE);
1045     previewblends(ivec(0, 0, 0), ivec(worldsize, worldsize, worldsize));
1046 });
1047 
showblendmap()1048 void showblendmap()
1049 {
1050     if(noedit(true) || (nompedit && multiplayer())) return;
1051     previewblends(ivec(0, 0, 0), ivec(worldsize, worldsize, worldsize));
1052 }
1053 
1054 COMMAND(showblendmap, "");
1055 ICOMMAND(clearblendmap, "", (),
1056 {
1057     if(noedit(true) || (nompedit && multiplayer())) return;
1058     resetblendmap();
1059     showblendmap();
1060 });
1061 
1062 ICOMMAND(moveblendmap, "ii", (int *dx, int *dy),
1063 {
1064     if(noedit(true) || (nompedit && multiplayer())) return;
1065     if(*dx%(BM_IMAGE_SIZE<<BM_SCALE) || *dy%(BM_IMAGE_SIZE<<BM_SCALE))
1066     {
1067         conoutf(CON_ERROR, "blendmap movement must be in multiples of %d", BM_IMAGE_SIZE<<BM_SCALE);
1068         return;
1069     }
1070     if(*dx <= -worldsize || *dx >= worldsize || *dy <= -worldsize || *dy >= worldsize)
1071         resetblendmap();
1072     else
1073         moveblendmap(*dx>>BM_SCALE, *dy>>BM_SCALE);
1074     showblendmap();
1075 });
1076 
renderblendbrush()1077 void renderblendbrush()
1078 {
1079     if(!blendpaintmode || !brushes.inrange(curbrush)) return;
1080 
1081     BlendBrush *brush = brushes[curbrush];
1082     int x1 = (int)floor(clamp(worldpos.x, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*brush->w) << BM_SCALE,
1083         y1 = (int)floor(clamp(worldpos.y, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*brush->h) << BM_SCALE,
1084         x2 = x1 + (brush->w << BM_SCALE),
1085         y2 = y1 + (brush->h << BM_SCALE);
1086 
1087     if(max(x1, y1) >= worldsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return;
1088 
1089     if(!brush->tex) brush->gentex();
1090     renderblendbrush(brush->tex, x1, y1, x2 - x1, y2 - y1);
1091 }
1092 
loadblendmap(stream * f,uchar & type,BlendMapNode & node)1093 bool loadblendmap(stream *f, uchar &type, BlendMapNode &node)
1094 {
1095     type = f->getchar();
1096     switch(type)
1097     {
1098         case BM_SOLID:
1099         {
1100             int val = f->getchar();
1101             if(val<0 || val>0xFF) return false;
1102             node.solid = &bmsolids[val];
1103             break;
1104         }
1105 
1106         case BM_IMAGE:
1107             node.image = new BlendMapImage;
1108             if(f->read(node.image->data, sizeof(node.image->data)) != sizeof(node.image->data))
1109                 return false;
1110             break;
1111 
1112         case BM_BRANCH:
1113             node.branch = new BlendMapBranch;
1114             loopi(4) { node.branch->type[i] = BM_SOLID; node.branch->children[i].solid = &bmsolids[0xFF]; }
1115             loopi(4) if(!loadblendmap(f, node.branch->type[i], node.branch->children[i]))
1116                 return false;
1117             break;
1118 
1119         default:
1120             type = BM_SOLID;
1121             node.solid = &bmsolids[0xFF];
1122             return false;
1123     }
1124     return true;
1125 }
1126 
loadblendmap(stream * f,int info)1127 bool loadblendmap(stream *f, int info)
1128 {
1129     resetblendmap();
1130     return loadblendmap(f, blendmap.type, blendmap);
1131 }
1132 
saveblendmap(stream * f,uchar type,BlendMapNode & node)1133 void saveblendmap(stream *f, uchar type, BlendMapNode &node)
1134 {
1135     f->putchar(type);
1136     switch(type)
1137     {
1138         case BM_SOLID:
1139             f->putchar(node.solid->val);
1140             break;
1141 
1142         case BM_IMAGE:
1143             f->write(node.image->data, sizeof(node.image->data));
1144             break;
1145 
1146         case BM_BRANCH:
1147             loopi(4) saveblendmap(f, node.branch->type[i], node.branch->children[i]);
1148             break;
1149     }
1150 }
1151 
saveblendmap(stream * f)1152 void saveblendmap(stream *f)
1153 {
1154     saveblendmap(f, blendmap.type, blendmap);
1155 }
1156 
shouldsaveblendmap()1157 uchar shouldsaveblendmap()
1158 {
1159     return blendmap.solid!=&bmsolids[0xFF] ? 1 : 0;
1160 }
1161 
1162