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 VARF(blendpaintmode, 0, 0, 5,
358 {
359 if(!blendpaintmode) stoppaintblendmap();
360 });
361
blitblendmap(uchar & type,BlendMapNode & node,int bmx,int bmy,int bmsize,uchar * src,int sx,int sy,int sw,int sh,int smode)362 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)
363 {
364 if(type==BM_BRANCH)
365 {
366 bmsize /= 2;
367 if(sy < bmy + bmsize)
368 {
369 if(sx < bmx + bmsize) blitblendmap(node.branch->type[0], node.branch->children[0], bmx, bmy, bmsize, src, sx, sy, sw, sh, smode);
370 if(sx + sw > bmx + bmsize) blitblendmap(node.branch->type[1], node.branch->children[1], bmx+bmsize, bmy, bmsize, src, sx, sy, sw, sh, smode);
371 }
372 if(sy + sh > bmy + bmsize)
373 {
374 if(sx < bmx + bmsize) blitblendmap(node.branch->type[2], node.branch->children[2], bmx, bmy+bmsize, bmsize, src, sx, sy, sw, sh, smode);
375 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);
376 }
377 return;
378 }
379 if(type==BM_SOLID)
380 {
381 uchar val = node.solid->val;
382 if(bmsize > BM_IMAGE_SIZE)
383 {
384 node.splitsolid(type, val);
385 blitblendmap(type, node, bmx, bmy, bmsize, src, sx, sy, sw, sh, smode);
386 return;
387 }
388
389 type = BM_IMAGE;
390 node.image = new BlendMapImage;
391 memset(node.image->data, val, sizeof(node.image->data));
392 }
393
394 int x1 = clamp(sx - bmx, 0, bmsize), y1 = clamp(sy - bmy, 0, bmsize),
395 x2 = clamp(sx+sw - bmx, 0, bmsize), y2 = clamp(sy+sh - bmy, 0, bmsize);
396 uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1];
397 src += max(bmy - sy, 0)*sw + max(bmx - sx, 0);
398 loopi(y2-y1)
399 {
400 switch(smode)
401 {
402 case 1:
403 memcpy(dst, src, x2 - x1);
404 break;
405
406 case 2:
407 loopi(x2 - x1) dst[i] = min(dst[i], src[i]);
408 break;
409
410 case 3:
411 loopi(x2 - x1) dst[i] = max(dst[i], src[i]);
412 break;
413
414 case 4:
415 loopi(x2 - x1) dst[i] = min(dst[i], uchar(0xFF - src[i]));
416 break;
417
418 case 5:
419 loopi(x2 - x1) dst[i] = max(dst[i], uchar(0xFF - src[i]));
420 break;
421 }
422 dst += BM_IMAGE_SIZE;
423 src += sw;
424 }
425 }
426
blitblendmap(uchar * src,int sx,int sy,int sw,int sh,int smode)427 void blitblendmap(uchar *src, int sx, int sy, int sw, int sh, int smode)
428 {
429 int bmsize = worldsize>>BM_SCALE;
430 if(max(sx, sy) >= bmsize || min(sx+sw, sy+sh) <= 0 || min(sw, sh) <= 0) return;
431 blitblendmap(blendmap.type, blendmap, 0, 0, bmsize, src, sx, sy, sw, sh, smode);
432 }
433
resetblendmap()434 void resetblendmap()
435 {
436 blendmap.cleanup();
437 blendmap.type = BM_SOLID;
438 blendmap.solid = &bmsolids[0xFF];
439 }
440
enlargeblendmap()441 void enlargeblendmap()
442 {
443 if(blendmap.type == BM_SOLID) return;
444 BlendMapBranch *branch = new BlendMapBranch;
445 branch->type[0] = blendmap.type;
446 branch->children[0] = blendmap;
447 loopi(3)
448 {
449 branch->type[i+1] = BM_SOLID;
450 branch->children[i+1].solid = &bmsolids[0xFF];
451 }
452 blendmap.type = BM_BRANCH;
453 blendmap.branch = branch;
454 }
455
shrinkblendmap(int octant)456 void shrinkblendmap(int octant)
457 {
458 blendmap.shrink(octant&3);
459 }
460
moveblendmap(uchar type,BlendMapNode & node,int size,int x,int y,int dx,int dy)461 void moveblendmap(uchar type, BlendMapNode &node, int size, int x, int y, int dx, int dy)
462 {
463 if(type == BM_BRANCH)
464 {
465 size /= 2;
466 moveblendmap(node.branch->type[0], node.branch->children[0], size, x, y, dx, dy);
467 moveblendmap(node.branch->type[1], node.branch->children[1], size, x + size, y, dx, dy);
468 moveblendmap(node.branch->type[2], node.branch->children[2], size, x, y + size, dx, dy);
469 moveblendmap(node.branch->type[3], node.branch->children[3], size, x + size, y + size, dx, dy);
470 return;
471 }
472 else if(type == BM_SOLID)
473 {
474 fillblendmap(x+dx, y+dy, size, size, node.solid->val);
475 }
476 else if(type == BM_IMAGE)
477 {
478 blitblendmap(node.image->data, x+dx, y+dy, size, size, 1);
479 }
480 }
481
moveblendmap(int dx,int dy)482 void moveblendmap(int dx, int dy)
483 {
484 BlendMapRoot old = blendmap;
485 blendmap.type = BM_SOLID;
486 blendmap.solid = &bmsolids[0xFF];
487 moveblendmap(old.type, old, worldsize>>BM_SCALE, 0, 0, dx, dy);
488 old.cleanup();
489 }
490
491 struct BlendBrush
492 {
493 char *name;
494 int w, h;
495 uchar *data;
496 GLuint tex;
497
BlendBrushBlendBrush498 BlendBrush(const char *name, int w, int h) :
499 name(newstring(name)), w(w), h(h), data(new uchar[w*h]), tex(0)
500 {}
501
~BlendBrushBlendBrush502 ~BlendBrush()
503 {
504 cleanup();
505 delete[] name;
506 if(data) delete[] data;
507 }
508
cleanupBlendBrush509 void cleanup()
510 {
511 if(tex) { glDeleteTextures(1, &tex); tex = 0; }
512 }
513
gentexBlendBrush514 void gentex()
515 {
516 if(!tex) glGenTextures(1, &tex);
517 uchar *buf = new uchar[2*w*h];
518 uchar *dst = buf, *src = data;
519 loopi(h)
520 {
521 loopj(w) *dst++ = 255 - *src++;
522 }
523 createtexture(tex, w, h, buf, 3, 1, hasTRG ? GL_R8 : GL_LUMINANCE8);
524 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
525 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
526 GLfloat border[4] = { 0, 0, 0, 0 };
527 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border);
528 delete[] buf;
529 }
530
reorientBlendBrush531 void reorient(bool flipx, bool flipy, bool swapxy)
532 {
533 uchar *rdata = new uchar[w*h];
534 int stridex = 1, stridey = 1;
535 if(swapxy) stridex *= h; else stridey *= w;
536 uchar *src = data, *dst = rdata;
537 if(flipx) { dst += (w-1)*stridex; stridex = -stridex; }
538 if(flipy) { dst += (h-1)*stridey; stridey = -stridey; }
539 loopi(h)
540 {
541 uchar *curdst = dst;
542 loopj(w)
543 {
544 *curdst = *src++;
545 curdst += stridex;
546 }
547 dst += stridey;
548 }
549 if(swapxy) swap(w, h);
550 delete[] data;
551 data = rdata;
552 if(tex) gentex();
553 }
554 };
555
556 static vector<BlendBrush *> brushes;
557 static int curbrush = -1;
558
cleanupblendmap()559 void cleanupblendmap()
560 {
561 loopv(brushes) brushes[i]->cleanup();
562 }
563
clearblendbrushes()564 void clearblendbrushes()
565 {
566 while(brushes.length()) delete brushes.pop();
567 curbrush = -1;
568 }
569
delblendbrush(const char * name)570 void delblendbrush(const char *name)
571 {
572 loopv(brushes) if(!strcmp(brushes[i]->name, name))
573 {
574 delete brushes[i];
575 brushes.remove(i--);
576 }
577 curbrush = brushes.empty() ? -1 : clamp(curbrush, 0, brushes.length()-1);
578 }
579
addblendbrush(const char * name,const char * imgname)580 void addblendbrush(const char *name, const char *imgname)
581 {
582 delblendbrush(name);
583
584 ImageData s;
585 if(!loadimage(imgname, s)) { conoutf(CON_ERROR, "could not load blend brush image %s", imgname); return; }
586 if(max(s.w, s.h) > (1<<12))
587 {
588 conoutf(CON_ERROR, "blend brush image size exceeded %dx%d pixels: %s", 1<<12, 1<<12, imgname);
589 return;
590 }
591
592 BlendBrush *brush = new BlendBrush(name, s.w, s.h);
593
594 uchar *dst = brush->data, *srcrow = s.data;
595 loopi(s.h)
596 {
597 for(uchar *src = srcrow, *end = &srcrow[s.w*s.bpp]; src < end; src += s.bpp)
598 *dst++ = src[0];
599 srcrow += s.pitch;
600 }
601
602 brushes.add(brush);
603 if(curbrush < 0) curbrush = 0;
604 else if(curbrush >= brushes.length()) curbrush = brushes.length()-1;
605
606 }
607
nextblendbrush(int * dir)608 void nextblendbrush(int *dir)
609 {
610 curbrush += *dir < 0 ? -1 : 1;
611 if(brushes.empty()) curbrush = -1;
612 else if(!brushes.inrange(curbrush)) curbrush = *dir < 0 ? brushes.length()-1 : 0;
613 }
614
setblendbrush(const char * name)615 void setblendbrush(const char *name)
616 {
617 loopv(brushes) if(!strcmp(brushes[i]->name, name)) { curbrush = i; break; }
618 }
619
getblendbrushname(int * n)620 void getblendbrushname(int *n)
621 {
622 result(brushes.inrange(*n) ? brushes[*n]->name : "");
623 }
624
curblendbrush()625 void curblendbrush()
626 {
627 intret(curbrush);
628 }
629
630 COMMAND(clearblendbrushes, "");
631 COMMAND(delblendbrush, "s");
632 COMMAND(addblendbrush, "ss");
633 COMMAND(nextblendbrush, "i");
634 COMMAND(setblendbrush, "s");
635 COMMAND(getblendbrushname, "i");
636 COMMAND(curblendbrush, "");
637
638 extern int nompedit;
639
canpaintblendmap(bool brush=true,bool sel=false,bool msg=true)640 bool canpaintblendmap(bool brush = true, bool sel = false, bool msg = true)
641 {
642 if(noedit(!sel, msg) || (nompedit && multiplayer())) return false;
643 if(!blendpaintmode)
644 {
645 if(msg) conoutf(CON_ERROR, "operation only allowed in blend paint mode");
646 return false;
647 }
648 if(brush && !brushes.inrange(curbrush))
649 {
650 if(msg) conoutf(CON_ERROR, "no blend brush selected");
651 return false;
652 }
653 return true;
654 }
655
rotateblendbrush(int * val)656 void rotateblendbrush(int *val)
657 {
658 if(!canpaintblendmap()) return;
659 BlendBrush *brush = brushes[curbrush];
660 const texrotation &r = texrotations[*val < 0 ? 3 : clamp(*val, 1, 7)];
661 brush->reorient(r.flipx, r.flipy, r.swapxy);
662 }
663
664 COMMAND(rotateblendbrush, "i");
665
paintblendmap(bool msg)666 void paintblendmap(bool msg)
667 {
668 if(!canpaintblendmap(true, false, msg)) return;
669
670 BlendBrush *brush = brushes[curbrush];
671 int x = (int)floor(clamp(worldpos.x, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*brush->w),
672 y = (int)floor(clamp(worldpos.y, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*brush->h);
673 blitblendmap(brush->data, x, y, brush->w, brush->h, blendpaintmode);
674 previewblends(ivec((x-1)<<BM_SCALE, (y-1)<<BM_SCALE, 0),
675 ivec((x+brush->w+1)<<BM_SCALE, (y+brush->h+1)<<BM_SCALE, worldsize));
676 }
677
678 VAR(paintblendmapdelay, 1, 500, 3000);
679 VAR(paintblendmapinterval, 1, 30, 3000);
680
681 int paintingblendmap = 0, lastpaintblendmap = 0;
682
stoppaintblendmap()683 void stoppaintblendmap()
684 {
685 paintingblendmap = 0;
686 lastpaintblendmap = 0;
687 }
688
trypaintblendmap()689 void trypaintblendmap()
690 {
691 if(!paintingblendmap || totalmillis - paintingblendmap < paintblendmapdelay) return;
692 if(lastpaintblendmap)
693 {
694 int diff = totalmillis - lastpaintblendmap;
695 if(diff < paintblendmapinterval) return;
696 lastpaintblendmap = (diff - diff%paintblendmapinterval) + lastpaintblendmap;
697 }
698 else lastpaintblendmap = totalmillis;
699 paintblendmap(false);
700 }
701
702 ICOMMAND(paintblendmap, "D", (int *isdown),
703 {
704 if(*isdown)
705 {
706 if(!paintingblendmap) { paintblendmap(true); paintingblendmap = totalmillis; }
707 }
708 else stoppaintblendmap();
709 });
710
clearblendmapsel()711 void clearblendmapsel()
712 {
713 if(noedit(false) || (nompedit && multiplayer())) return;
714 extern selinfo sel;
715 int x1 = sel.o.x>>BM_SCALE, y1 = sel.o.y>>BM_SCALE,
716 x2 = (sel.o.x+sel.s.x*sel.grid+(1<<BM_SCALE)-1)>>BM_SCALE,
717 y2 = (sel.o.y+sel.s.y*sel.grid+(1<<BM_SCALE)-1)>>BM_SCALE;
718 fillblendmap(x1, y1, x2-x1, y2-y1, 0xFF);
719 previewblends(ivec(x1<<BM_SCALE, y1<<BM_SCALE, 0),
720 ivec(x2<<BM_SCALE, y2<<BM_SCALE, worldsize));
721 }
722
723 COMMAND(clearblendmapsel, "");
724
invertblendmapsel()725 void invertblendmapsel()
726 {
727 if(noedit(false) || (nompedit && multiplayer())) return;
728 extern selinfo sel;
729 int x1 = sel.o.x>>BM_SCALE, y1 = sel.o.y>>BM_SCALE,
730 x2 = (sel.o.x+sel.s.x*sel.grid+(1<<BM_SCALE)-1)>>BM_SCALE,
731 y2 = (sel.o.y+sel.s.y*sel.grid+(1<<BM_SCALE)-1)>>BM_SCALE;
732 invertblendmap(x1, y1, x2-x1, y2-y1);
733 previewblends(ivec(x1<<BM_SCALE, y1<<BM_SCALE, 0),
734 ivec(x2<<BM_SCALE, y2<<BM_SCALE, worldsize));
735 }
736
737 COMMAND(invertblendmapsel, "");
738
invertblendmap()739 void invertblendmap()
740 {
741 if(noedit(false) || (nompedit && multiplayer())) return;
742 invertblendmap(0, 0, worldsize>>BM_SCALE, worldsize>>BM_SCALE);
743 previewblends(ivec(0, 0, 0), ivec(worldsize, worldsize, worldsize));
744 }
745
746 COMMAND(invertblendmap, "");
747
showblendmap()748 void showblendmap()
749 {
750 if(noedit(true) || (nompedit && multiplayer())) return;
751 previewblends(ivec(0, 0, 0), ivec(worldsize, worldsize, worldsize));
752 }
753
754 COMMAND(showblendmap, "");
755 COMMAND(optimizeblendmap, "");
756 ICOMMAND(clearblendmap, "", (),
757 {
758 if(noedit(true) || (nompedit && multiplayer())) return;
759 resetblendmap();
760 showblendmap();
761 });
762
763 ICOMMAND(moveblendmap, "ii", (int *dx, int *dy),
764 {
765 if(noedit(true) || (nompedit && multiplayer())) return;
766 if(*dx%(BM_IMAGE_SIZE<<BM_SCALE) || *dy%(BM_IMAGE_SIZE<<BM_SCALE))
767 {
768 conoutf(CON_ERROR, "blendmap movement must be in multiples of %d", BM_IMAGE_SIZE<<BM_SCALE);
769 return;
770 }
771 if(*dx <= -worldsize || *dx >= worldsize || *dy <= -worldsize || *dy >= worldsize)
772 resetblendmap();
773 else
774 moveblendmap(*dx>>BM_SCALE, *dy>>BM_SCALE);
775 showblendmap();
776 });
777
renderblendbrush()778 void renderblendbrush()
779 {
780 if(!blendpaintmode || !brushes.inrange(curbrush)) return;
781
782 BlendBrush *brush = brushes[curbrush];
783 int x1 = (int)floor(clamp(worldpos.x, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*brush->w) << BM_SCALE,
784 y1 = (int)floor(clamp(worldpos.y, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*brush->h) << BM_SCALE,
785 x2 = x1 + (brush->w << BM_SCALE),
786 y2 = y1 + (brush->h << BM_SCALE);
787
788 if(max(x1, y1) >= worldsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return;
789
790 if(!brush->tex) brush->gentex();
791 renderblendbrush(brush->tex, x1, y1, x2 - x1, y2 - y1);
792 }
793
loadblendmap(stream * f,uchar & type,BlendMapNode & node)794 bool loadblendmap(stream *f, uchar &type, BlendMapNode &node)
795 {
796 type = f->getchar();
797 switch(type)
798 {
799 case BM_SOLID:
800 {
801 int val = f->getchar();
802 if(val<0 || val>0xFF) return false;
803 node.solid = &bmsolids[val];
804 break;
805 }
806
807 case BM_IMAGE:
808 node.image = new BlendMapImage;
809 if(f->read(node.image->data, sizeof(node.image->data)) != sizeof(node.image->data))
810 return false;
811 break;
812
813 case BM_BRANCH:
814 node.branch = new BlendMapBranch;
815 loopi(4) { node.branch->type[i] = BM_SOLID; node.branch->children[i].solid = &bmsolids[0xFF]; }
816 loopi(4) if(!loadblendmap(f, node.branch->type[i], node.branch->children[i]))
817 return false;
818 break;
819
820 default:
821 type = BM_SOLID;
822 node.solid = &bmsolids[0xFF];
823 return false;
824 }
825 return true;
826 }
827
loadblendmap(stream * f,int info)828 bool loadblendmap(stream *f, int info)
829 {
830 resetblendmap();
831 return loadblendmap(f, blendmap.type, blendmap);
832 }
833
saveblendmap(stream * f,uchar type,BlendMapNode & node)834 void saveblendmap(stream *f, uchar type, BlendMapNode &node)
835 {
836 f->putchar(type);
837 switch(type)
838 {
839 case BM_SOLID:
840 f->putchar(node.solid->val);
841 break;
842
843 case BM_IMAGE:
844 f->write(node.image->data, sizeof(node.image->data));
845 break;
846
847 case BM_BRANCH:
848 loopi(4) saveblendmap(f, node.branch->type[i], node.branch->children[i]);
849 break;
850 }
851 }
852
saveblendmap(stream * f)853 void saveblendmap(stream *f)
854 {
855 saveblendmap(f, blendmap.type, blendmap);
856 }
857
shouldsaveblendmap()858 uchar shouldsaveblendmap()
859 {
860 return blendmap.solid!=&bmsolids[0xFF] ? 1 : 0;
861 }
862
863