1 // Hyperbolic Rogue -- texture mode
2 // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
3
4 /** \file textures.cpp
5 * \brief texture mode, also the basic definitions of textures, used in 3D mode
6 */
7
8 #include "hyper.h"
9 #if CAP_TEXTURE
10 namespace hr {
11
12 #if HDR
13 struct texture_triangle {
14 array<hyperpoint, 3> v;
15 array<shiftpoint, 3> tv;
texture_trianglehr::texture_triangle16 texture_triangle(array<hyperpoint, 3> _v, array<shiftpoint, 3> _tv) : v(_v), tv(_tv) {}
17 };
18
19 struct textureinfo : basic_textureinfo {
20 shiftmatrix M;
21 vector<texture_triangle> triangles;
22 vector<glvertex> vertices;
23 cell *c;
24 vector<shiftmatrix> matrices;
25
26 // these are required to adjust to geometry changes
27 int current_type, symmetries;
28 };
29 #endif
30
31 EX namespace texture {
32
33 #if HDR
34 enum eTextureState {
35 tsOff, tsAdjusting, tsActive
36 };
37
38 struct texture_data {
39 GLuint textureid;
40
41 int twidth, theight;
42 bool stretched, original;
43 int tx, ty, origdim;
44
45 int strx, stry, base_x, base_y;
46
texture_datahr::texture::texture_data47 texture_data() { textureid = 0; twidth = 2048; theight = 0; stretched = false; original = false; }
48
49 vector<color_t> texture_pixels;
50
get_texture_pixelhr::texture::texture_data51 color_t& get_texture_pixel(int x, int y) {
52 return texture_pixels[(y&(theight-1))*twidth+(x&(twidth-1))];
53 }
54
55 vector<pair<color_t*, color_t>> undos;
56 vector<tuple<cell*, shiftpoint, int> > pixels_to_draw;
57
58 bool loadTextureGL();
59 bool whitetexture();
60 bool readtexture(string tn);
61 void saveRawTexture(string tn);
62
63 void undo();
64 void undoLock();
65 void update();
66 };
67
68 struct texture_config {
69 string texturename;
70 string configname;
71 eTextureState tstate;
72 eTextureState tstate_max;
73
74 transmatrix itt;
75
76 color_t grid_color;
77 color_t mesh_color;
78 color_t master_color;
79 color_t slave_color;
80
81 int color_alpha;
82
83 int gsplits;
84
85 int recolor(color_t col);
86
87 typedef tuple<eGeometry, eVariation, char, int, eModel, ld, ld> texture_parameters;
88 texture_parameters orig_texture_parameters;
89
90 map<int, textureinfo> texture_map, texture_map_orig;
91 set<cell*> models;
92
93 basic_textureinfo tinf3;
94
95 bool texture_tuned;
96 string texture_tuner;
97 vector<shiftpoint*> tuned_vertices;
98
99 bool apply(cell *c, const shiftmatrix &V, color_t col);
100 void mark_triangles();
101
102 void clear_texture_map();
103 void perform_mapping();
104 void mapTextureTriangle(textureinfo &mi, const array<hyperpoint, 3>& v, const array<shiftpoint, 3>& tv, int splits);
mapTextureTrianglehr::texture::texture_config105 void mapTextureTriangle(textureinfo &mi, const array<hyperpoint, 3>& v, const array<shiftpoint, 3>& tv) { mapTextureTriangle(mi, v, tv, gsplits); }
106 void mapTexture2(textureinfo& mi);
107 void finish_mapping();
108 void true_remap();
109 void remap();
110 bool correctly_mapped;
111 hyperpoint texture_coordinates(shiftpoint);
112
113 void drawRawTexture();
114 void saveFullTexture(string tn);
115
116 bool save();
117 bool load();
118
119 texture_data data;
120
texture_confighr::texture::texture_config121 texture_config() {
122 // argh, no member initialization in some of my compilers
123 texturename = "textures/hyperrogue-texture.png";
124 configname = "textures/hyperrogue.txc";
125 itt = Id;
126 grid_color = 0;
127 mesh_color = 0;
128 master_color = 0xFFFFFF30;
129 slave_color = 0xFF000008;
130 color_alpha = 128;
131 gsplits = 1;
132 texture_tuned = false;
133 }
134
135 };
136 #endif
137
138 EX cpatterntype cgroup;
139
140 #if CAP_PNG
convertSurface(SDL_Surface * s)141 SDL_Surface *convertSurface(SDL_Surface* s) {
142 SDL_PixelFormat fmt;
143 // fmt.format = SDL_PIXELFORMAT_BGRA8888;
144 fmt.BitsPerPixel = 32;
145 fmt.BytesPerPixel = 4;
146
147 fmt.Ashift=24;
148 fmt.Rshift=16;
149 fmt.Gshift=8;
150 fmt.Bshift=0;
151 fmt.Amask=0xff<<24;
152 fmt.Rmask=0xff<<16;
153 fmt.Gmask=0xff<<8;
154 fmt.Bmask=0xff;
155 fmt.Aloss = fmt.Rloss = fmt.Gloss = fmt.Bloss = 0;
156 fmt.palette = NULL;
157
158 #if !CAP_SDL2
159 fmt.alpha = 0;
160 fmt.colorkey = 0x1ffffff;
161 #endif
162
163 return SDL_ConvertSurface(s, &fmt, SDL_SWSURFACE);
164 }
165 #endif
166
167 struct undo {
168 unsigned* pix;
169 unsigned last;
170 };
171
172 EX texture_config config;
173
174 EX bool saving = false;
175
scale_colorarray(int origdim,int targetdim,const T & src,const U & dest)176 template<class T, class U> void scale_colorarray(int origdim, int targetdim, const T& src, const U& dest) {
177 int ox = 0, tx = 0, partials[4];
178 int omissing = targetdim, tmissing = origdim;
179 for(int p=0; p<4; p++) partials[p] = 0;
180
181 while(tx < targetdim) {
182 int fv = min(omissing, tmissing);
183 color_t c = src(ox);
184 for(int p=0; p<4; p++)
185 partials[p] += part(c, p) * fv;
186 omissing -= fv; tmissing -= fv;
187 if(omissing == 0) {
188 ox++; omissing = targetdim;
189 }
190 if(tmissing == 0) {
191 color_t target;
192 for(int p=0; p<4; p++) {
193 part(target, p) = partials[p] / origdim;
194 partials[p] = 0;
195 }
196 dest(tx++, target);
197 tmissing = origdim;
198 }
199 }
200 }
201
loadTextureGL()202 bool texture_data::loadTextureGL() {
203
204 if(textureid == 0) glGenTextures(1, &textureid);
205
206 glBindTexture( GL_TEXTURE_2D, textureid);
207 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
208 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
209
210 // BGRA may be not supported in the web version
211 if(ISWEB) for(auto& p: texture_pixels) swap(part(p, 0), part(p, 2));
212
213 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, twidth, theight, 0,
214 ISWEB ? GL_RGBA : GL_BGRA, GL_UNSIGNED_BYTE,
215 &texture_pixels[0] );
216
217 if(ISWEB) for(auto& p: texture_pixels) swap(part(p, 0), part(p, 2));
218
219 return true;
220 }
221
whitetexture()222 bool texture_data::whitetexture() {
223 undos.clear();
224 texture_pixels.resize(0);
225 if(theight == 0) theight = twidth;
226 texture_pixels.resize(twidth * theight, 0xFFFFFFFF);
227 pixels_to_draw.clear();
228 return true;
229 }
230
readtexture(string tn)231 bool texture_data::readtexture(string tn) {
232
233 #if CAP_SDL_IMG || CAP_PNG
234 undos.clear();
235
236 #if CAP_SDL_IMG
237 SDL_Surface *txt = IMG_Load(tn.c_str());
238 if(!txt) {
239 addMessage(XLAT("Failed to load %1", texturename));
240 return false;
241 }
242 auto txt2 = convertSurface(txt);
243 SDL_FreeSurface(txt);
244
245 tx = txt2->w, ty = txt2->h;
246
247 auto pix = [&] (int x, int y) { return qpixel(txt2, x, y); };
248
249 #elif CAP_PNG
250
251 FILE *f = fopen(tn.c_str(), "rb");
252 if(!f) { printf("failed to open file\n"); return false; }
253 png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
254 if(!png) { printf("failed to create_read_struct\n"); return false; }
255 if(setjmp(png_jmpbuf(png))) {
256 printf("failed to read\n");
257 return false;
258 }
259 png_init_io(png, f);
260
261 // set the expected format
262 png_infop info = png_create_info_struct(png);
263 png_read_info(png, info);
264 tx = png_get_image_width(png, info);
265 ty = png_get_image_height(png, info);
266 png_byte color_type = png_get_color_type(png, info);
267 png_byte bit_depth = png_get_bit_depth(png, info);
268 if(bit_depth == 16) png_set_strip_16(png);
269 if(color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png);
270 if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png);
271 if(png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png);
272 if(color_type == PNG_COLOR_TYPE_RGB ||
273 color_type == PNG_COLOR_TYPE_GRAY ||
274 color_type == PNG_COLOR_TYPE_PALETTE)
275 png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
276
277 if(color_type == PNG_COLOR_TYPE_GRAY ||
278 color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
279 png_set_gray_to_rgb(png);
280 png_read_update_info(png, info);
281
282 // read png
283 vector<png_bytep> row_pointers(ty);
284
285 vector<color_t> origpixels(ty * tx);
286
287 for(int y = 0; y < ty; y++)
288 row_pointers[y] = (png_bytep) & origpixels[y * tx];
289
290 png_read_image(png, &row_pointers[0]);
291 fclose(f);
292
293 for(int i=0; i<ty*tx; i++)
294 swap(part(origpixels[i], 0), part(origpixels[i], 2));
295
296 auto pix = [&] (int x, int y) {
297 if(x<0 || y<0 || x >= tx || y >= ty) return (color_t) 0;
298 return origpixels[y*tx + x];
299 };
300
301 printf("texture read OK\n");
302
303 #endif
304
305 if(twidth == 0)
306 twidth = next_p2(tx);
307 if(theight == 0) theight = (stretched || original) ? next_p2(ty) : twidth;
308
309 texture_pixels.resize(twidth * theight);
310
311 if(stretched) {
312 int i = 0;
313 println(hlog, tx, " -> " , twidth, " / " , ty, " -> ", theight);
314 for(int y=0; y<theight; y++)
315 for(int x=0; x<twidth; x++)
316 texture_pixels[i++] = pix(x * tx / twidth, y * ty / theight);
317 strx = twidth; stry = theight; base_x = base_y = 0;
318 }
319
320 else if(tx == twidth && ty == theight) {
321 int i = 0;
322 for(int y=0; y<ty; y++)
323 for(int x=0; x<tx; x++)
324 texture_pixels[i++] = pix(x, y);
325 strx = twidth; stry = theight; base_x = base_y = 0;
326 }
327
328 else if(original) {
329 base_x = 0;
330 base_y = 0;
331 strx = tx; stry = ty;
332 for(int y=0; y<ty; y++)
333 for(int x=0; x<tx; x++)
334 get_texture_pixel(x, y) = pix(x,y);
335 }
336
337 else {
338
339 origdim = max(tx, ty);
340 base_x = origdim/2 - tx/2;
341 base_y = origdim/2 - ty/2;
342
343 strx = twidth * tx / origdim;
344 stry = theight * ty / origdim;
345
346 qpixel_pixel_outside = 0; // outside is black
347
348 vector<int> half_expanded(twidth * ty);
349 for(int y=0; y<ty; y++)
350 scale_colorarray(origdim, twidth,
351 [&] (int x) { return pix(x - base_x,y); },
352 [&] (int x, int v) { half_expanded[twidth * y + x] = v; }
353 );
354
355 for(int x=0; x<twidth; x++)
356 scale_colorarray(origdim, twidth,
357 [&] (int y) { return y-base_y < 0 || y-base_y >= ty ? 0 : half_expanded[x + (y-base_y) * twidth]; },
358 [&] (int y, int v) { get_texture_pixel(x, y) = v; }
359 );
360
361 base_x = base_x * twidth / origdim;
362 base_y = base_y * theight / origdim;
363 }
364
365 #if CAP_SDL_IMG
366 SDL_FreeSurface(txt2);
367 #endif
368
369 return true;
370 #else
371 return false;
372 #endif
373 }
374
saveRawTexture(string tn)375 void texture_data::saveRawTexture(string tn) {
376 SDL_Surface *sraw = SDL_CreateRGBSurface(SDL_SWSURFACE,twidth,twidth,32,0,0,0,0);
377 for(int y=0; y<twidth; y++)
378 for(int x=0; x<twidth; x++)
379 qpixel(sraw,x,y) = get_texture_pixel(x, y);
380 IMAGESAVE(sraw, tn.c_str());
381 SDL_FreeSurface(sraw);
382 addMessage(XLAT("Saved the raw texture to %1", tn));
383 }
384
texture_coordinates(shiftpoint h)385 hyperpoint texture_config::texture_coordinates(shiftpoint h) {
386 hyperpoint inmodel;
387 applymodel(h, inmodel);
388 inmodel[0] *= current_display->radius * 1. / current_display->scrsize;
389 inmodel[1] *= current_display->radius * pconf.stretch / current_display->scrsize;
390 inmodel[2] = 1;
391 inmodel = itt * inmodel;
392 inmodel[0] = (inmodel[0] + 1) / 2;
393 inmodel[1] = (inmodel[1] + 1) / 2;
394 return inmodel;
395 }
396
mapTextureTriangle(textureinfo & mi,const array<hyperpoint,3> & v,const array<shiftpoint,3> & tv,int splits)397 void texture_config::mapTextureTriangle(textureinfo &mi, const array<hyperpoint, 3>& v, const array<shiftpoint, 3>& tv, int splits) {
398
399 if(splits) {
400 array<hyperpoint, 3> v2 = make_array( mid(v[1], v[2]), mid(v[2], v[0]), mid(v[0], v[1]) );
401 array<shiftpoint, 3> tv2 = make_array( mid(tv[1], tv[2]), mid(tv[2], tv[0]), mid(tv[0], tv[1]) );
402 mapTextureTriangle(mi, make_array(v[0], v2[2], v2[1]), make_array(tv[0], tv2[2], tv2[1]), splits-1);
403 mapTextureTriangle(mi, make_array(v[1], v2[0], v2[2]), make_array(tv[1], tv2[0], tv2[2]), splits-1);
404 mapTextureTriangle(mi, make_array(v[2], v2[1], v2[0]), make_array(tv[2], tv2[1], tv2[0]), splits-1);
405 mapTextureTriangle(mi, make_array(v2[0], v2[1], v2[2]), make_array(tv2[0], tv2[1], tv2[2]), splits-1);
406 return;
407 }
408
409 for(int i=0; i<3; i++) {
410 mi.vertices.push_back(glhr::pointtogl(v[i]));
411 hyperpoint inmodel;
412 mi.tvertices.push_back(glhr::pointtogl(texture_coordinates(tv[i])));
413 }
414
415 // when reading a spherical band in a cylindrical projection,
416 // take care that texture vertices are mapped not around the sphere
417 if(sphere && mdBandAny()) {
418 int s = isize(mi.tvertices)-3;
419 ld xmin = min(mi.tvertices[s][0], min(mi.tvertices[s+1][0], mi.tvertices[s+2][0]));
420 ld xmax = max(mi.tvertices[s][0], max(mi.tvertices[s+1][0], mi.tvertices[s+2][0]));
421 if(xmax - xmin > .5) {
422 for(int ss=s; ss<s+3; ss++)
423 if(mi.tvertices[ss][0] < .5) mi.tvertices[ss][0] += 1;
424 }
425 }
426 }
427
428 texture_triangle *edited_triangle;
429 textureinfo *edited_tinfo;
430
celltriangles(cell * c)431 int celltriangles(cell *c) {
432 return c->type;
433 }
434
findTextureTriangle(cell * c,patterns::patterninfo & si,int i)435 array<shiftpoint, 3> findTextureTriangle(cell *c, patterns::patterninfo& si, int i) {
436 shiftmatrix M = ggmatrix(c) * applyPatterndir(c, si);
437 return make_array(M * C0, M * get_corner_position(c, i), M * get_corner_position(c, i+1));
438 }
439
440 // using: mouseh, mouseouver
getTriangleID(cell * c,patterns::patterninfo & si,shiftpoint h)441 int getTriangleID(cell *c, patterns::patterninfo& si, shiftpoint h) {
442 // auto si = getpatterninfo0(c);
443 ld quality = 1e10;
444 int best = 0;
445 for(int i=0; i<celltriangles(c); i++) {
446 auto t = findTextureTriangle(c, si, i);
447 ld q = hdist(t[1], h) + hdist(t[2], h);
448 if(q < quality) quality = q, best = i;
449 }
450 return best;
451 }
452
mapTexture(cell * c,textureinfo & mi,patterns::patterninfo & si,const shiftmatrix & T,int shift=0)453 void mapTexture(cell *c, textureinfo& mi, patterns::patterninfo &si, const shiftmatrix& T, int shift = 0) {
454 mi.c = c;
455 mi.symmetries = si.symmetries;
456 mi.current_type = celltriangles(c);
457
458 mi.M = T * applyPatterndir(c, si);
459 mi.triangles.clear();
460
461 transmatrix iv = inverse(applyPatterndir(c, si));
462
463 int sd = si.dir;
464 if((NONSTDVAR) || bt::in()) sd = 0;
465
466 for(int i=0; i<c->type; i++) {
467 hyperpoint h1 = iv * get_corner_position(c, (i + sd + shift) % c->type);
468 hyperpoint h2 = iv * get_corner_position(c, (i + sd + shift + 1) % c->type);
469 mi.triangles.emplace_back(make_array(C0, h1, h2), make_array(mi.M*C0, mi.M*h1, mi.M*h2));
470 }
471 }
472
mapTexture2(textureinfo & mi)473 void texture_config::mapTexture2(textureinfo& mi) {
474 mi.vertices.clear();
475 mi.tvertices.clear();
476 for(auto& t: mi.triangles)
477 mapTextureTriangle(mi, t.v, t.tv);
478 }
479
recolor(color_t col)480 int texture_config::recolor(color_t col) {
481 if(color_alpha == 0) return col;
482 for(int i=1; i<4; i++)
483 part(col, i) = color_alpha + ((255-color_alpha) * part(col,i) + 127) / 255;
484 return col;
485 }
486
487 EX bool texture_aura;
488
using_aura()489 EX bool using_aura() {
490 return texture_aura && config.tstate == texture::tsActive;
491 }
492
apply(cell * c,const shiftmatrix & V,color_t col)493 bool texture_config::apply(cell *c, const shiftmatrix &V, color_t col) {
494 if(config.tstate == tsOff || !correctly_mapped) return false;
495
496 using namespace patterns;
497 auto si = getpatterninfo0(c);
498
499 if(config.tstate == tsAdjusting) {
500 dynamicval<color_t> d(poly_outline, slave_color);
501 draw_floorshape(c, V, cgi.shFullFloor, 0, PPR::LINE);
502
503 curvepoint(C0);
504 for(int i=0; i<c->type; i++)
505 curvepoint(get_corner_position(c, i)), curvepoint(C0);
506 queuecurve(V, slave_color, 0, PPR::LINE);
507
508 return false;
509 }
510 try {
511 auto& mi = texture_map.at(si.id);
512
513 set_floor(cgi.shFullFloor);
514 qfi.tinf = &mi;
515 qfi.spin = applyPatterndir(c, si);
516
517 if(grid_color) {
518 dynamicval<color_t> d(poly_outline, grid_color);
519 draw_floorshape(c, V, cgi.shFullFloor, 0, PPR::FLOOR);
520 }
521
522 if(using_aura()) {
523 for(int i=0; i<isize(mi.tvertices); i += 3) {
524 ld p[3];
525 if(inHighQual)
526 while(true) {
527 p[0] = hrandf();
528 p[1] = hrandf();
529 p[2] = 1 - p[0] - p[1];
530 if(p[2] >= 0) break;
531 }
532 else p[0] = p[1] = p[2] = 1/3.;
533 ld v[2] = {0,0};
534 for(int j=0; j<2; j++) for(int k=0; k<3; k++)
535 v[j] += mi.tvertices[i+k][j] * p[k];
536
537 int vi[2] = {int(v[0] * config.data.twidth), int(v[1] * config.data.twidth)};
538
539 col = config.data.get_texture_pixel(vi[0], vi[1]);
540 hyperpoint h = glhr::gltopoint(mi.vertices[i]);
541 addaura(V*h, col, 0);
542 }
543 }
544
545 return true;
546 }
547 catch(out_of_range&) {
548 // printf("Ignoring tile #%d / %08x: not mapped\n", si.id, patterns::subcode(c, si));
549 return false;
550 }
551 }
552
mark_triangles()553 void texture_config::mark_triangles() {
554 if(config.tstate == tsAdjusting)
555 for(auto& mi: texture_map) {
556 for(auto& t: mi.second.triangles) {
557 vector<hyperpoint> t2;
558 for(int i=0; i<3; i++)
559 t2.push_back(unshift(t.tv[i]));
560 prettypoly(t2, master_color, master_color, gsplits);
561 }
562 }
563 }
564
565 static const auto current_texture_parameters = tie(geometry, variation, patterns::whichPattern, patterns::subpattern_flags, pmodel, pconf.scale, pconf.alpha);
566
clear_texture_map()567 void texture_config::clear_texture_map() {
568 texture_map.clear();
569 edited_triangle = nullptr;
570 edited_tinfo = nullptr;
571 tuned_vertices.clear();
572 models.clear();
573 texture_tuned = false;
574 texture_tuner = "";
575 }
576
perform_mapping()577 void texture_config::perform_mapping() {
578 if(gsplits < 0) gsplits = 0;
579 if(gsplits > 4) gsplits = 4;
580 using namespace patterns;
581
582 clear_texture_map();
583
584 for(auto& p: gmatrix) {
585 cell *c = p.first;
586 auto si = getpatterninfo0(c);
587 bool replace = false;
588
589 // int sgn = sphere ? -1 : 1;
590
591 if(!texture_map.count(si.id))
592 replace = true;
593 else if(hdist0(p.second*sphereflip * C0) < hdist0(texture_map[si.id].M * sphereflip * C0))
594 replace = true;
595
596 if(replace) {
597 auto& mi = texture_map[si.id];
598 mapTexture(c, mi, si, p.second);
599 mi.texture_id = config.data.textureid;
600 }
601 }
602
603 for(auto& t: texture_map) models.insert(t.second.c);
604
605 for(auto& p: gmatrix) {
606 cell *c = p.first;
607 bool nearmodel = models.count(c);
608 forCellEx(c2, c)
609 if(models.count(c2))
610 nearmodel = true;
611 if(nearmodel) {
612 auto si = getpatterninfo0(c);
613 texture_map[si.id].matrices.push_back(p.second * applyPatterndir(c, si));
614 }
615 }
616
617 }
618
619 set<int> missing_cells_known;
620
finish_mapping()621 void texture_config::finish_mapping() {
622 tinf3.tvertices.clear();
623 tinf3.texture_id = config.data.textureid;
624 if(isize(texture_map) && isize(texture_map.begin()->second.triangles)) {
625 auto& tris = texture_map.begin()->second.triangles;
626
627 for(int a=0; a<8; a++) {
628 auto& tri = tris[a % isize(tris)];
629 shiftpoint center = tri.tv[0];
630 hyperpoint v1 = unshift(tri.tv[1], center.shift) - center.h;
631 hyperpoint v2 = unshift(tri.tv[2], center.shift) - center.h;
632 texture_order([&] (ld x, ld y) {
633 shiftpoint h = shiftless(normalize(center.h + v1 * x + v2 * y), center.shift);
634 tinf3.tvertices.push_back(glhr::pointtogl(texture_coordinates(h)));
635 });
636 }
637 }
638
639 if(config.tstate == tsActive) {
640 for(auto& mi: texture_map)
641 mapTexture2(mi.second);
642 correctly_mapped = true;
643 missing_cells_known.clear();
644 }
645
646 patterns::computeCgroup();
647 texture::cgroup = patterns::cgroup;
648 texture_map_orig = texture_map;
649 orig_texture_parameters = current_texture_parameters;
650 // printf("texture_map has %d elements (S%d)\n", isize(texture_map), config.tstate);
651 }
652
653 #if CAP_SHOT
saveFullTexture(string tn)654 void texture_config::saveFullTexture(string tn) {
655 addMessage(XLAT("Saving full texture to %1...", tn));
656 dynamicval<color_t> dd(grid_color, 0);
657 dynamicval<color_t> dm(mesh_color, 0);
658 dynamicval<ld> dx(pconf.xposition, 0);
659 dynamicval<ld> dy(pconf.yposition, 0);
660 dynamicval<ld> dvs(pconf.scale, (pmodel == mdDisk && !euclid) ? 1 : pconf.scale);
661 dynamicval<bool> dro(rug::rugged, false);
662 dynamicval<bool> dnh(nohud, true);
663 texture::saving = true;
664 drawscreen();
665
666 dynamicval<int> dvx(shot::shotx, data.twidth);
667 dynamicval<int> dvy(shot::shoty, data.twidth);
668 dynamicval<int> dvf(shot::shotformat, -1);
669 shot::take(tn.c_str());
670 texture::saving = false;
671
672 drawscreen();
673 if(data.readtexture(tn) && data.loadTextureGL()) {
674 itt = Id; // xyscale(Id, current_display->scrsize * 1. / current_display->radius);
675 perform_mapping();
676 finish_mapping();
677 }
678 }
679 #endif
680
681 bool newmove = false;
682
683 vector<glhr::textured_vertex> rtver(4);
684
drawRawTexture()685 void texture_config::drawRawTexture() {
686 glflush();
687 current_display->next_shader_flags = GF_TEXTURE;
688 dynamicval<eModel> m(pmodel, mdPixel);
689 current_display->set_all(0, 0);
690 glhr::color2(0xFFFFFF20);
691 glBindTexture(GL_TEXTURE_2D, config.data.textureid);
692 for(int i=0; i<4; i++) {
693 int cx[4] = {2, -2, -2, 2};
694 int cy[4] = {2, 2, -2, -2};
695 int x = cx[i];
696 int y = cy[i];
697 hyperpoint inmodel = hpxyz(x, y, 1);
698 inmodel = itt * inmodel;
699 rtver[i].texture[0] = (inmodel[0]+1)/2;
700 rtver[i].texture[1] = (inmodel[1]+1)/2;
701 rtver[i].coords[0] = x * current_display->scrsize;
702 rtver[i].coords[1] = y * current_display->scrsize;
703 rtver[i].coords[2] = 0;
704 rtver[i].coords[3] = 1;
705 }
706 glhr::id_modelview();
707 glhr::prepare(rtver);
708 glhr::set_depthtest(false);
709 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
710 }
711
712 struct magicmapper_point {
713 cell *c;
714 hyperpoint cell_relative;
715 hyperpoint texture_coords;
716 };
717
718 vector<magicmapper_point> amp;
719
720 enum eMagicParameter {
721 mpScale,
722 mpProjection,
723 mpMove,
724 mpRotate,
725 mpSlant,
726 mpStretch,
727 mpTexPosX,
728 mpTexPosY,
729 mpMAX
730 };
731
732 EX vector<string> mpnames = {
733 "affect model scale",
734 "affect model projection",
735 "affect model central point",
736 "affect model rotation",
737 "affect texture slanting",
738 "affect texture stretching",
739 "affect texture position X",
740 "affect texture position Y"
741 };
742
743 flagtype current_magic = 15;
744
have_mp(eMagicParameter i)745 bool have_mp(eMagicParameter i) { return (current_magic >> i) & 1; }
746
747 struct magic_param {
748 bool do_spin;
749 int texmode;
750 ld spinangle, scale, proj, moveangle, shift, slant, stretch, tx, ty;
751
shufflehr::texture::magic_param752 void shuffle() {
753 do_spin = hrand(2);
754 spinangle = hrandf() - hrandf();
755 moveangle = hrandf() * 2 * M_PI;
756 shift = hrandf() - hrandf();
757 scale = hrandf() - hrandf();
758 proj = hrandf() - hrandf();
759 texmode = hrand(3);
760 slant = have_mp(mpSlant) ? hrandf() - hrandf() : 0;
761 stretch = have_mp(mpStretch) ? hrandf() - hrandf() : 0;
762 tx = have_mp(mpTexPosX) ? hrandf() - hrandf() : 0;
763 ty = have_mp(mpTexPosY) ? hrandf() - hrandf() : 0;
764 }
765
affect_itthr::texture::magic_param766 void affect_itt(const transmatrix& T) {
767 transmatrix Ti = inverse(T);
768 for(auto& p: amp)
769 p.texture_coords = Ti * p.texture_coords;
770 config.itt = config.itt * T;
771 }
772
applyhr::texture::magic_param773 void apply(ld delta) {
774 if(have_mp(mpProjection))
775 pconf.alpha *= exp(delta * proj);
776 if(have_mp(mpScale))
777 pconf.scale *= exp(delta * scale);
778
779 if(do_spin) {
780 if(have_mp(mpRotate))
781 View = spin(delta * spinangle) * View;
782 }
783 else {
784 if(have_mp(mpMove))
785 View = spin(moveangle) * xpush(delta*shift) * spin(-moveangle) * View;
786 }
787
788 if(texmode == 0 && have_mp(mpStretch))
789 affect_itt(euaffine(hpxyz(0, delta * stretch, 0)));
790
791 if(texmode == 1 && have_mp(mpSlant))
792 affect_itt(euaffine(hpxyz(delta * slant, 0, 0)));
793
794 if(texmode == 2 && (have_mp(mpTexPosX) || have_mp(mpTexPosY)))
795 affect_itt(eupush(delta * tx, delta * ty));
796
797 fixmatrix(View);
798 }
799 };
800
magic_quality()801 ld magic_quality() {
802 gmatrix.clear();
803 calcparam();
804
805 ld q = 0;
806 for(auto& p: amp) {
807 hyperpoint inmodel;
808 applymodel(ggmatrix(p.c) * p.cell_relative, inmodel);
809 inmodel[0] *= current_display->radius * 1. / current_display->scrsize;
810 inmodel[1] *= current_display->radius * 1. / current_display->scrsize;
811 q += sqhypot_d(2, inmodel - p.texture_coords);
812 }
813 return q;
814 }
815
applyMagic()816 void applyMagic() {
817 ld cq = magic_quality();
818
819 int last_success = 0;
820
821 for(int s=0; s<50000 && s<last_success + 1000; s++) {
822 magic_param p;
823 p.shuffle();
824
825 bool failed = false;
826
827 for(ld delta = 1; abs(delta) > 1e-9; delta *= (failed ? -.7 : 1.2)) {
828 p.apply(delta);
829 ld nq = magic_quality();
830 if(nq < cq) {
831 cq = nq;
832 last_success = s;
833 }
834 else {
835 p.apply(-delta);
836 failed = true;
837 }
838 }
839 }
840 config.perform_mapping();
841 }
842
843 enum eTexturePanstate {tpsModel, tpsMove, tpsScale, tpsAffine, tpsZoom, tpsProjection, tpsCell, tpsTriangle, tpsTune};
844 eTexturePanstate panstate;
845
mousemovement()846 void mousemovement() {
847 static hyperpoint lastmouse;
848
849 hyperpoint mouseeu = hpxyz((mousex - current_display->xcenter + .0) / current_display->scrsize, (mousey - current_display->ycenter + .0) / current_display->scrsize, 1);
850 bool nonzero = mouseeu[0] || mouseeu[1];
851
852 switch(panstate) {
853 case tpsModel:
854 if(!newmove && mouseh[2] < 50 && lastmouse[2] < 50) {
855 panning(shiftless(lastmouse), mouseh);
856 config.perform_mapping();
857 }
858 lastmouse = unshift(mouseh); newmove = false;
859 break;
860
861 case tpsMove: {
862 if(!newmove)
863 config.itt = config.itt * inverse(eupush(mouseeu)) * eupush(lastmouse);
864 lastmouse = mouseeu; newmove = false;
865 break;
866 }
867
868 case tpsScale: {
869 if(nonzero && !newmove)
870 config.itt = config.itt * inverse(euscalezoom(mouseeu)) * euscalezoom(lastmouse);
871 if(nonzero) lastmouse = mouseeu;
872 newmove = false;
873 break;
874 }
875
876 case tpsAffine: {
877 if(!newmove)
878 config.itt = config.itt * inverse(euaffine(mouseeu)) * euaffine(lastmouse);
879 lastmouse = mouseeu; newmove = false;
880 break;
881 }
882
883 case tpsZoom: {
884 // do not zoom in portrait!
885 if(nonzero && !newmove) {
886 View = inverse(spintox(mouseeu)) * spintox(lastmouse) * View;
887 pconf.scale = pconf.scale * sqhypot_d(2, mouseeu) / sqhypot_d(2, lastmouse);
888 config.perform_mapping();
889 }
890 if(nonzero) lastmouse = mouseeu;
891 newmove = false;
892 break;
893 }
894
895 case tpsProjection: {
896 if(nonzero && !newmove) {
897 pconf.alpha = pconf.alpha * sqhypot_d(2, mouseeu) / sqhypot_d(2, lastmouse);
898 }
899 if(nonzero) lastmouse = mouseeu;
900 newmove = false;
901 break;
902 }
903
904 case tpsCell: {
905 cell *c = mouseover;
906 if(!c) break;
907 auto si = patterns::getpatterninfo0(c);
908 if(newmove) {
909 edited_tinfo = NULL;
910 if(config.texture_map.count(si.id)) {
911 edited_tinfo = &config.texture_map[si.id];
912 newmove = false;
913 }
914 }
915 if(edited_tinfo && isize(edited_tinfo->triangles) == celltriangles(c)) {
916 for(int i=0; i<celltriangles(c); i++)
917 edited_tinfo->triangles[i].tv = findTextureTriangle(c, si, i);
918 config.texture_tuned = true;
919 }
920 break;
921 }
922
923 case tpsTriangle: {
924 cell *c = mouseover;
925 if(!c) break;
926 auto si = patterns::getpatterninfo0(c);
927 int i = getTriangleID(c, si, mouseh);
928 if(newmove) {
929 edited_triangle = NULL;
930 if(config.texture_map.count(si.id)) {
931 edited_triangle = &config.texture_map[si.id].triangles[i];
932 newmove = false;
933 }
934 }
935 if(edited_triangle) {
936 edited_triangle->tv = findTextureTriangle(c, si, i);
937 config.texture_tuned = true;
938 }
939 break;
940 }
941
942 case tpsTune: {
943 ld tdist = 1e20;
944 if(newmove) {
945 config.tuned_vertices.clear();
946 for(auto& a: config.texture_map)
947 for(auto& t: a.second.triangles)
948 for(auto& v: t.tv)
949 if(hdist(v, mouseh) < tdist)
950 tdist = hdist(v, mouseh);
951 for(auto& a: config.texture_map)
952 for(auto& t: a.second.triangles)
953 for(auto& v: t.tv)
954 if(hdist(v, mouseh) < tdist * (1.000001))
955 config.tuned_vertices.push_back(&v);
956 newmove = false;
957 }
958 for(auto v: config.tuned_vertices) {
959 *v = mouseh;
960 config.texture_tuned = true;
961 }
962 break;
963 }
964
965 default: break;
966 }
967 }
968
969 patterns::patterninfo si_save;
970
971 saverlist texturesavers;
972
973 eVariation targetvariation;
974 eGeometry targetgeometry;
975
976 string csymbol;
977
978 string tes;
979
init_textureconfig()980 void init_textureconfig() {
981 #if CAP_CONFIG
982 texturesavers = move(savers);
983 for(int i=0; i<3; i++)
984 for(int j=0; j<3; j++)
985 addsaver(config.itt[i][j], "texturematrix_" + its(i) + its(j), i==j ? 1 : 0);
986
987 for(int i=0; i<3; i++)
988 for(int j=0; j<3; j++)
989 addsaver(View[i][j], "viewmatrix_" + its(i) + its(j), i==j ? 1 : 0);
990
991 addsaverenum(targetgeometry, "geometry", gNormal);
992 addsaver(tes, "tes", "");
993 addsaverenum(pmodel, "used model", mdDisk);
994 addsaver(vid.yshift, "Y shift", 0);
995 addsaver(pconf.yposition, "Y position", 0);
996 addsaver(pconf.xposition, "X position", 0);
997 addsaver(pconf.camera_angle, "camera angle", 0);
998 addsaverenum(targetvariation, "bitruncated", eVariation::bitruncated);
999 // ... geometry parameters
1000
1001 addsaverenum(patterns::whichPattern, "pattern", patterns::PAT_TYPES);
1002 addsaver(patterns::subpattern_flags, "pattern flags", 0);
1003
1004 addsaver(si_save.id, "center type", 1);
1005 addsaver(si_save.dir, "center direction", 0);
1006 addsaver(si_save.reflect, "center reflection", false);
1007 addsaver(config.data.twidth, "texture resolution", 2048);
1008 addsaver(config.gsplits, "precision", 1);
1009
1010 addsaver(config.grid_color, "grid color", 0);
1011 addsaver(config.color_alpha, "alpha color", 0);
1012 addsaver(config.mesh_color, "mesh color", 0);
1013
1014 addsaver(pconf.alpha, "projection", 1);
1015 addsaver(pconf.scale, "scale", 1);
1016 addsaver(pconf.stretch, "stretch", 1);
1017 addsaver(vid.binary_width, "binary-tiling-width", 1);
1018
1019 addsaver(config.texturename, "texture filename", "");
1020 addsaver(config.texture_tuner, "texture tuning", "");
1021
1022 addsaver(csymbol, "symbol", "");
1023
1024 swap(texturesavers, savers);
1025 #endif
1026 }
1027
save()1028 bool texture_config::save() {
1029 #if CAP_CONFIG
1030 init_textureconfig();
1031 FILE *f = fopen(configname.c_str(), "wt");
1032 if(!f) return false;
1033
1034 if(texture_tuned) {
1035 texture_tuner = "";
1036 for(auto& a: config.texture_map)
1037 for(auto& t: a.second.triangles)
1038 for(auto& v: t.tv)
1039 for(int i=0; i<3; i++) {
1040 texture_tuner += fts(v[i]);
1041 texture_tuner += ';';
1042 }
1043 }
1044
1045 targetgeometry = geometry;
1046 targetvariation = variation;
1047
1048 cell *ctr = centerover;
1049 si_save = patterns::getpatterninfo0(ctr);
1050
1051 if(arb::in()) tes = arb::current.filename;
1052
1053 if(arcm::in()) csymbol = arcm::current.symbol;
1054 else csymbol = "";
1055
1056 for(auto s: texturesavers) if(s->dosave())
1057 fprintf(f, "%s=%s\n", s->name.c_str(), s->save().c_str());
1058
1059 fclose(f);
1060 #endif
1061 return true;
1062 }
1063
load()1064 bool texture_config::load() {
1065 #if CAP_CONFIG
1066 init_textureconfig();
1067
1068 FILE *f = fopen(configname.c_str(), "rt");
1069 if(!f) return false;
1070 swap(texturesavers, savers);
1071 for(auto s: savers) s->reset();
1072 loadNewConfig(f);
1073 swap(texturesavers, savers);
1074 fclose(f);
1075 polygonal::solve();
1076
1077 if(1) {
1078 dynamicval<patterns::ePattern> d1(patterns::whichPattern, patterns::whichPattern);
1079 dynamicval<int> d2(patterns::subpattern_flags, patterns::subpattern_flags);
1080
1081 if(targetgeometry != geometry) {
1082 stop_game();
1083 if(targetgeometry == gArchimedean) {
1084 arcm::current.symbol = csymbol;
1085 arcm::current.parse();
1086 if(arcm::current.errors) {
1087 printf("Error while reading Archimedean texture: %s\n", arcm::current.errormsg.c_str());
1088 addMessage("Error: " + arcm::current.errormsg);
1089 return false;
1090 }
1091 }
1092 if(targetgeometry == gArbitrary) {
1093 arb::run(tes);
1094 stop_game();
1095 }
1096 set_geometry(targetgeometry);
1097 start_game();
1098 return config.load();
1099 }
1100
1101 if(variation != targetvariation) {
1102 set_variation(targetvariation);
1103 start_game();
1104 return config.load();
1105 }
1106 }
1107
1108
1109 if(true) {
1110 celllister cl(currentmap->gamestart(), 20, 10000, NULL);
1111 bool found = false;
1112 for(cell *c: cl.lst) if(euclid || ctof(c)) {
1113 auto si_here = patterns::getpatterninfo0(c);
1114 if(si_here.id == si_save.id && si_here.reflect == si_save.reflect && si_here.dir == si_save.dir) {
1115 centerover = c;
1116 found = true;
1117 break;
1118 }
1119 }
1120 if(!found)
1121 addMessage(XLAT("warning: unable to find the center"));
1122 }
1123
1124 if(!data.readtexture(texturename)) return false;
1125 if(!data.loadTextureGL()) return false;
1126 calcparam();
1127 models::configure();
1128 drawthemap();
1129 config.tstate = config.tstate_max = tsActive;
1130 string s = move(texture_tuner);
1131 perform_mapping();
1132
1133 texture_tuner = move(s);
1134
1135 if(texture_tuner != "") {
1136 texture_tuned = true;
1137 vector<ld*> coords;
1138 for(auto& a: config.texture_map)
1139 for(auto& t: a.second.triangles)
1140 for(auto& v: t.tv)
1141 for(int i=0; i<3; i++)
1142 coords.push_back(&v[i]);
1143 int semicounter = 0;
1144 for(char c: texture_tuner) if(c == ';') semicounter++;
1145 if(semicounter != isize(coords))
1146 addMessage("Tuning error: wrong number");
1147 else {
1148 string cur = "";
1149 int index = 0;
1150 for(char c: texture_tuner)
1151 if(c == ';') {
1152 *(coords[index++]) = atof(cur.c_str());
1153 cur = "";
1154 }
1155 else cur += c;
1156 printf("index = %d semi = %d sc = %d\n", index, semicounter, isize(coords));
1157 }
1158 }
1159
1160 finish_mapping();
1161 #endif
1162 return true;
1163 }
1164
showMagicMenu()1165 void showMagicMenu() {
1166 cmode = sm::SIDE | sm::MAYDARK | sm::DIALOG_STRICT_X;
1167 gamescreen(0);
1168
1169 dialog::init(XLAT("texture auto-adjustment"));
1170
1171 dialog::addInfo(XLAT("drag from the model to the texture"));
1172
1173 for(int i=0; i<mpMAX; i++)
1174 dialog::addBoolItem(XLAT(mpnames[i]), have_mp(eMagicParameter(i)), 'a'+i);
1175
1176 dialog::addSelItem(XLAT("delete markers"), its(isize(amp)), 'D');
1177 dialog::addItem(XLAT("perform auto-adjustment"), 'R');
1178 dialog::addBack();
1179
1180 getcstat = '-';
1181
1182 dialog::display();
1183
1184 if(holdmouse) {
1185 hyperpoint mouseeu = hpxyz((mousex - current_display->xcenter + .0) / current_display->scrsize, (mousey - current_display->ycenter + .0) / current_display->scrsize, 1);
1186 if(newmove) {
1187 magicmapper_point newpoint;
1188 newpoint.c = mouseover;
1189 newpoint.cell_relative = inverse_shift(gmatrix[mouseover], mouseh);
1190 amp.push_back(newpoint);
1191 newmove = false;
1192 }
1193 amp.back().texture_coords = mouseeu;
1194 }
1195
1196 if(config.tstate == tsAdjusting) {
1197 initquickqueue();
1198 char letter = 'A';
1199 for(auto& am: amp) {
1200 shiftpoint h = ggmatrix(am.c) * am.cell_relative;
1201 queuestr(h, vid.fsize, s0+letter, 0xC00000, 1);
1202
1203 /*
1204 hyperpoint inmodel;
1205 applymodel(h, inmodel);
1206 inmodel[0] *= current_display->radius * 1. / current_display->scrsize;
1207 inmodel[1] *= current_display->radius * 1. / current_display->scrsize;
1208 */
1209
1210 queuestr(
1211 current_display->xcenter + current_display->scrsize * am.texture_coords[0],
1212 current_display->ycenter + current_display->scrsize * am.texture_coords[1],
1213 0, vid.fsize, s0+letter, 0x00C000, 1);
1214
1215 letter++;
1216 }
1217 quickqueue();
1218 }
1219
1220 keyhandler = [] (int sym, int uni) {
1221 // handlePanning(sym, uni);
1222 dialog::handleNavigation(sym, uni);
1223
1224 if(uni == '-' && config.tstate == tsAdjusting) {
1225 if(!holdmouse) {
1226 holdmouse = true;
1227 newmove = true;
1228 }
1229 }
1230 else if(uni >= 'a' && uni < 'a' + mpMAX)
1231 current_magic ^= 1<<(uni - 'a');
1232
1233 else if(uni == 'D') amp.clear();
1234 else if(uni == 'R') applyMagic();
1235 else if(doexiton(sym, uni))
1236 popScreen();
1237 };
1238 }
1239
1240 string texturehelp =
1241 "This mode lets you change the floor tesselation easily -- "
1242 "select 'paint a new texture' and draw like in a Paint program. "
1243 "The obtained pattern can then be easily changed to another geometry, "
1244 "or saved.\n\n"
1245 "Instead of drawing, it is also possible to use an arbitrary image "
1246 "as a texture. "
1247 "Works best with spherical/Euclidean/hyperbolic tesselations "
1248 "(e.g., a photo of a soccerball, or one of the tesselations by M. C. "
1249 "Escher), but it can be also used on arbitrary photos to make them periodic "
1250 "(these probably work best with the 'large picture' setting in geometry selection). "
1251 "Again, tesselations can have their geometry changed.\n\n";
1252
1253 #if CAP_EDIT
start_editor()1254 EX void start_editor() {
1255 if(config.data.whitetexture() && config.data.loadTextureGL()) {
1256 config.tstate = config.tstate_max = tsActive;
1257 config.perform_mapping();
1258 config.finish_mapping();
1259 mapeditor::initdraw(cwt.at);
1260 mapeditor::intexture = true;
1261 mapeditor::drawing_tool = false;
1262 pushScreen(mapeditor::showDrawEditor);
1263 }
1264 }
1265 #endif
1266
showMenu()1267 EX void showMenu() {
1268 cmode = sm::SIDE | sm::MAYDARK | sm::DIALOG_STRICT_X;
1269 gamescreen(0);
1270 if(config.tstate == tsAdjusting) {
1271 ptds.clear();
1272 config.mark_triangles();
1273 drawqueue();
1274 }
1275
1276 if(config.tstate == tsOff) {
1277 dialog::init(XLAT("texture mode (off)"));
1278 dialog::addItem(XLAT("select geometry/pattern"), 'r');
1279 dialog::add_action(patterns::pushChangeablePatterns);
1280 if(config.tstate_max == tsAdjusting || config.tstate_max == tsActive) {
1281 dialog::addItem(XLAT("reactivate the texture"), 't');
1282 dialog::add_action([] {
1283 config.tstate = config.tstate_max;
1284 });
1285 }
1286 if(GDIM == 2 && !rug::rugged) {
1287 dialog::addItem(XLAT("open PNG as texture"), 'o');
1288 dialog::add_action([] {
1289 dialog::openFileDialog(config.texturename, XLAT("open PNG as texture"), ".png",
1290 [] () {
1291 if(config.data.readtexture(config.texturename) && config.data.loadTextureGL()) {
1292 if(config.tstate_max == tsOff) config.tstate_max = tsAdjusting;
1293 config.tstate = config.tstate_max;
1294 config.perform_mapping();
1295 config.finish_mapping();
1296 return true;
1297 }
1298 else return false;
1299 });
1300 });
1301 }
1302 dialog::addItem(XLAT("load texture config"), 'l');
1303 dialog::add_action([] {
1304 dialog::openFileDialog(config.configname, XLAT("load texture config"), ".txc",
1305 [] () {
1306 return config.load();
1307 });
1308 });
1309 dialog::addSelItem(XLAT("texture size"), its(config.data.twidth), 'w');
1310 dialog::add_action([] {
1311 config.data.twidth *= 2;
1312 if(config.data.twidth > 9000) config.data.twidth = 256;
1313 config.tstate_max = tsOff;
1314 });
1315 #if CAP_EDIT
1316 if(GDIM == 2) {
1317 dialog::addItem(XLAT("paint a new texture"), 'n');
1318 dialog::add_action(start_editor);
1319 }
1320 #endif
1321
1322 dialog::addBoolItem(XLATN("Canvas"), specialland == laCanvas, 'X');
1323 dialog::add_action([] () {
1324 bool inwhite = specialland == laCanvas && patterns::whichCanvas == 'g' && patterns::canvasback == 0xFFFFFF;
1325 if(inwhite)
1326 pushScreen(patterns::showPrePattern);
1327 else {
1328 stop_game();
1329 enable_canvas();
1330 patterns::whichCanvas = 'g';
1331 patterns::canvasback = 0xFFFFFF;
1332 start_game();
1333 }
1334 });
1335 }
1336
1337 if(config.tstate == tsAdjusting) {
1338 dialog::init(XLAT("texture mode (overlay)"));
1339 dialog::addItem(XLAT("select the texture's pattern"), 'r');
1340 dialog::add_action(patterns::pushChangeablePatterns);
1341 dialog::addItem(XLAT("enable the texture"), 't');
1342 dialog::add_action([] {
1343 config.tstate = config.tstate_max = tsActive;
1344 config.finish_mapping();
1345 });
1346 dialog::addItem(XLAT("cancel the texture"), 'T');
1347 dialog::add_action([] {
1348 config.tstate = tsOff;
1349 config.tstate_max = tsOff;
1350 });
1351 dialog::addBoolItem_choice(XLAT("move the model"), panstate, tpsModel, 'm');
1352 dialog::addBoolItem_choice(XLAT("move the texture"), panstate, tpsMove, 'a');
1353 dialog::addBoolItem_choice(XLAT("scale/rotate the texture"), panstate, tpsScale, 'x');
1354 dialog::addBoolItem_choice(XLAT("scale/rotate the model"), panstate, tpsZoom, 'z');
1355 dialog::addBoolItem_choice(XLAT("projection"), panstate, tpsProjection, 'p');
1356 dialog::addBoolItem_choice(XLAT("affine transformations"), panstate, tpsAffine, 'y');
1357 dialog::addBoolItem(XLAT("magic"), false, 'A');
1358 dialog::add_action_push(showMagicMenu);
1359
1360 dialog::addBreak(50);
1361
1362 dialog::addBoolItem_choice(XLAT("select master cells"), panstate, tpsCell, 'C');
1363 dialog::addBoolItem_choice(XLAT("select master triangles"), panstate, tpsTriangle, 'X');
1364 dialog::addBoolItem_choice(XLAT("fine tune vertices"), panstate, tpsTune, 'F');
1365
1366 dialog::addColorItem(XLAT("grid color (master)"), config.master_color, 'M');
1367 dialog::add_action([] { dialog::openColorDialog(config.master_color, NULL); });
1368 dialog::addColorItem(XLAT("grid color (copy)"), config.slave_color, 'K');
1369 dialog::add_action([] { dialog::openColorDialog(config.slave_color, NULL); });
1370
1371 dialog::addBreak(50);
1372
1373 #if CAP_SHOT
1374 dialog::addItem(XLAT("save the raw texture"), 'S');
1375 dialog::add_action([] {
1376 dialog::openFileDialog(config.texturename, XLAT("save the raw texture"), ".png",
1377 [] () {
1378 config.data.saveRawTexture(config.texturename); return true;
1379 });
1380 });
1381 #endif
1382 }
1383
1384 if(config.tstate == tsActive) {
1385 dialog::init(XLAT("texture mode (active)"));
1386 dialog::addItem(XLAT("deactivate the texture"), 't');
1387 dialog::add_action([] { config.tstate = tsOff; });
1388 dialog::addItem(XLAT("back to overlay mode"), 'T');
1389 dialog::add_action([] {config.tstate = tsAdjusting; });
1390 dialog::addItem(XLAT("change the geometry"), 'r');
1391 dialog::add_action(patterns::pushChangeablePatterns);
1392 dialog::addColorItem(XLAT("grid color"), config.grid_color, 'g');
1393 dialog::add_action([] { dialog::openColorDialog(config.grid_color, NULL); });
1394 dialog::addColorItem(XLAT("mesh color"), config.mesh_color, 'm');
1395 dialog::add_action([] { dialog::openColorDialog(config.mesh_color, NULL); });
1396 dialog::addSelItem(XLAT("color alpha"), its(config.color_alpha), 'c');
1397 dialog::add_action([] {
1398 dialog::editNumber(config.color_alpha, 0, 255, 15, 0, XLAT("color alpha"),
1399 XLAT("The higher the value, the less important the color of underlying terrain is."));
1400 });
1401 dialog::addBoolItem_action(XLAT("aura from texture"), texture_aura, 'a');
1402 #if CAP_EDIT
1403 if(GDIM == 2) {
1404 dialog::addItem(XLAT("edit the texture"), 'e');
1405 dialog::add_action([] {
1406 mapeditor::intexture = true;
1407 mapeditor::drawing_tool = false;
1408 mapeditor::initdraw(cwt.at);
1409 pushScreen(mapeditor::showDrawEditor);
1410 });
1411 }
1412 #endif
1413 #if CAP_SHOT
1414 dialog::addItem(XLAT("save the full texture image"), 'S');
1415 dialog::add_action([] {
1416 dialog::openFileDialog(config.texturename, XLAT("save the full texture image"), ".png",
1417 [] () {
1418 config.saveFullTexture(config.texturename);
1419 return true;
1420 });
1421 });
1422 #endif
1423 dialog::addItem(XLAT("save texture config"), 's');
1424 dialog::add_action([] {
1425 dialog::openFileDialog(config.configname, XLAT("save texture config"), ".txc",
1426 [] () {
1427 return config.save();
1428 });
1429 });
1430 }
1431
1432 dialog::addSelItem(XLAT("precision"), its(config.gsplits), 'P');
1433 dialog::add_action([] {
1434 dialog::editNumber(config.gsplits, 0, 4, 1, 1, XLAT("precision"),
1435 XLAT("precision"));
1436 if(config.tstate == tsActive) dialog::reaction = [] () { config.finish_mapping();
1437 };
1438 });
1439 dialog::addHelp();
1440 dialog::addBack();
1441
1442 getcstat = '-';
1443
1444 dialog::display();
1445
1446 if(holdmouse) mousemovement();
1447
1448 keyhandler = [] (int sym, int uni) {
1449 // handlePanning(sym, uni);
1450 dialog::handleNavigation(sym, uni);
1451 if(uni == '-' && config.tstate == tsAdjusting) {
1452 if(!holdmouse) {
1453 holdmouse = true;
1454 newmove = true;
1455 }
1456 }
1457 else if(uni == SDLK_F1)
1458 gotoHelp(texturehelp);
1459 else if(doexiton(sym, uni))
1460 popScreen();
1461 };
1462 }
1463
1464 typedef pair<int,int> point;
1465
ptc(shiftpoint h)1466 point ptc(shiftpoint h) {
1467 hyperpoint inmodel = config.texture_coordinates(h);
1468 return make_pair(int(inmodel[0] * config.data.twidth), int(inmodel[1] * config.data.twidth));
1469 }
1470
ptc(const array<shiftpoint,3> & h)1471 array<point, 3> ptc(const array<shiftpoint, 3>& h) {
1472 return make_array(ptc(h[0]), ptc(h[1]), ptc(h[2]));
1473 }
1474
texture_distance(pair<int,int> p1,pair<int,int> p2)1475 int texture_distance(pair<int, int> p1, pair<int, int> p2) {
1476 return max(abs(p1.first-p2.first), abs(p1.second - p2.second));
1477 }
1478
fillpixel(int x,int y,unsigned col)1479 void fillpixel(int x, int y, unsigned col) {
1480 if(x<0 || y<0 || x >= config.data.twidth || y >= config.data.twidth) return;
1481 auto& pix = config.data.get_texture_pixel(x, y);
1482 if(pix != col) {
1483 config.data.undos.emplace_back(&pix, pix);
1484 pix = col;
1485 }
1486 }
1487
undo()1488 void texture_data::undo() {
1489 texture::config.data.pixels_to_draw.clear();
1490 while(!undos.empty()) {
1491 auto p = undos.back();
1492 undos.pop_back();
1493 if(!p.first) {
1494 loadTextureGL();
1495 return;
1496 }
1497 *p.first = p.second;
1498 }
1499 }
1500
undoLock()1501 void texture_data::undoLock() {
1502 printf("undos size = %d\n", isize(undos));
1503 if(isize(undos) > 2000000) {
1504 // limit undo memory
1505 int moveto = 0;
1506 for(int i=0; i < isize(undos) - 1000000; i++)
1507 if(!undos[i].first) moveto = i;
1508 if(moveto) {
1509 for(int i=0; i+moveto < isize(undos); i++)
1510 undos[i] = undos[i+moveto];
1511 undos.resize(isize(undos) - moveto);
1512 printf("undos sized to = %d\n", isize(undos));
1513 }
1514 }
1515
1516 undos.emplace_back(nullptr, 1);
1517 }
1518
filltriangle(const array<shiftpoint,3> & v,const array<point,3> & p,color_t col,int lev)1519 void filltriangle(const array<shiftpoint, 3>& v, const array<point, 3>& p, color_t col, int lev) {
1520
1521 int d2 = texture_distance(p[0], p[1]), d1 = texture_distance(p[0], p[2]), d0 = texture_distance(p[1], p[2]);
1522
1523 int a, b, c;
1524
1525 if((d0 <= 1 && d1 <= 1 && d2 <= 1) || lev >= 20) {
1526 for(int i=0; i<3; i++)
1527 fillpixel(p[i].first, p[i].second, col);
1528 return;
1529 }
1530 else if(d1 >= d0 && d1 >= d2)
1531 a = 0, b = 2, c = 1;
1532 else if(d2 >= d0 && d2 >= d1)
1533 a = 0, b = 1, c = 2;
1534 else
1535 a = 1, b = 2, c = 0;
1536
1537 shiftpoint v3 = mid(v[a], v[b]);
1538 point p3 = ptc(v3);
1539 filltriangle(make_array(v[c], v[a], v3), make_array(p[c], p[a], p3), col, lev+1);
1540 filltriangle(make_array(v[c], v[b], v3), make_array(p[c], p[b], p3), col, lev+1);
1541 }
1542
splitseg(const shiftmatrix & A,const array<ld,2> & angles,const array<shiftpoint,2> & h,const array<point,2> & p,color_t col,int lev)1543 void splitseg(const shiftmatrix& A, const array<ld, 2>& angles, const array<shiftpoint, 2>& h, const array<point, 2>& p, color_t col, int lev) {
1544 ld newangle = (angles[0] + angles[1]) / 2;
1545 shiftpoint nh = A * xspinpush0(newangle, mapeditor::dtwidth);
1546 auto np = ptc(nh);
1547
1548 filltriangle(make_array(h[0],h[1],nh), make_array(p[0],p[1],np), col, lev);
1549 if(lev < 10) {
1550 if(texture_distance(p[0],np) > 1)
1551 splitseg(A, make_array(angles[0], newangle), make_array(h[0], nh), make_array(p[0], np), col, lev+1);
1552 if(texture_distance(np,p[1]) > 1)
1553 splitseg(A, make_array(newangle, angles[1]), make_array(nh, h[1]), make_array(np, p[1]), col, lev+1);
1554 }
1555 }
1556
fillcircle(shiftpoint h,color_t col)1557 void fillcircle(shiftpoint h, color_t col) {
1558 shiftmatrix A = rgpushxto0(h);
1559
1560 ld step = M_PI * 2/3;
1561
1562 array<shiftpoint, 3> mh = make_array(A * xpush0(mapeditor::dtwidth), A * xspinpush0(step, mapeditor::dtwidth), A * xspinpush0(-step, mapeditor::dtwidth));
1563 auto mp = ptc(mh);
1564
1565 filltriangle(mh, mp, col, 0);
1566
1567 for(int i=0; i<3; i++) {
1568 int j = (i+1) % 3;
1569 if(texture_distance(mp[i], mp[j]) > 1)
1570 splitseg(A, make_array(step*i, step*(i+1)), make_array(mh[i], mh[j]), make_array(mp[i], mp[j]), col, 1);
1571 }
1572 }
1573
1574 EX bool texturesym = false;
1575
actDrawPixel(cell * c,shiftpoint h,color_t col)1576 void actDrawPixel(cell *c, shiftpoint h, color_t col) {
1577 try {
1578 shiftmatrix M = gmatrix.at(c);
1579 auto si = patterns::getpatterninfo0(c);
1580 hyperpoint h1 = inverse_shift(M * applyPatterndir(c, si), h);
1581 auto& tinf = config.texture_map[si.id];
1582 for(auto& M2: tinf.matrices) for(int i = 0; i<c->type; i += si.symmetries) {
1583 fillcircle(M2 * spin(2 * M_PI * i / c->type) * h1, col);
1584 if(texturesym)
1585 fillcircle(M2 * spin(2 * M_PI * i / c->type) * Mirror * h1, col);
1586 }
1587 }
1588 catch(out_of_range&) {}
1589 }
1590
drawPixel(cell * c,shiftpoint h,color_t col)1591 EX void drawPixel(cell *c, shiftpoint h, color_t col) {
1592 config.data.pixels_to_draw.emplace_back(c, h, col);
1593 }
1594
1595 EX cell *where;
1596
drawPixel(shiftpoint h,color_t col)1597 EX void drawPixel(shiftpoint h, color_t col) {
1598 try {
1599 again:
1600 shiftmatrix g0 = gmatrix[where];
1601 ld cdist0 = hdist(tC0(g0), h);
1602
1603 forCellEx(c, where)
1604 try {
1605 shiftmatrix g = gmatrix[c];
1606 ld cdist = hdist(tC0(g), h);
1607 if(cdist < cdist0) {
1608 cdist0 = cdist;
1609 where = c; g0 = g;
1610 goto again;
1611 }
1612 }
1613 catch(out_of_range&) {}
1614 drawPixel(where, h, col);
1615 }
1616 catch(out_of_range&) {}
1617 }
1618
1619 EX void drawLine(shiftpoint h1, shiftpoint h2, color_t col, int steps IS(10)) {
1620 if(steps > 0 && hdist(h1, h2) > mapeditor::dtwidth / 3) {
1621 shiftpoint h3 = mid(h1, h2);
1622 drawLine(h1, h3, col, steps-1);
1623 drawLine(h3, h2, col, steps-1);
1624 }
1625 else
1626 drawPixel(h2, col);
1627 }
1628
true_remap()1629 void texture_config::true_remap() {
1630 models::configure();
1631 drawthemap();
1632 if(GDIM == 3) return;
1633 texture_map.clear();
1634 missing_cells_known.clear();
1635 for(cell *c: dcal) {
1636 auto si = patterns::getpatterninfo0(c);
1637 int oldid = patterns::getpatterninfo(c, patterns::whichPattern, patterns::subpattern_flags | patterns::SPF_NO_SUBCODES).id;
1638
1639 if(texture_map.count(si.id)) continue;
1640
1641 int pshift = 0;
1642 if(texture::cgroup == cpSingle) oldid = 0;
1643 if(texture::cgroup == cpFootball && patterns::cgroup == cpThree) {
1644 if(si.id == 4) pshift = 1;
1645 oldid = !si.id;
1646 }
1647
1648 try {
1649
1650 auto& mi = texture_map_orig.at(oldid);
1651 int ncurr = isize(mi.tvertices);
1652 int ntarget = ncurr * celltriangles(c) / mi.current_type;
1653 vector<glvertex> new_tvertices = mi.tvertices;
1654 new_tvertices.resize(ntarget);
1655 for(int i=ncurr; i<ntarget; i++) {
1656 new_tvertices[i] = new_tvertices[i - ncurr];
1657 }
1658
1659 auto& mi2 = texture_map[si.id];
1660 mi2 = mi;
1661 if(GOLDBERG || IRREGULAR) pshift += si.dir;
1662 mapTexture(c, mi2, si, ggmatrix(c), pshift);
1663 mapTexture2(mi2);
1664 mi2.tvertices = move(new_tvertices);
1665 // printf("%08x remapping %d vertices to %d vertices\n", si.id, isize(mi.tvertices), isize(mi2.tvertices));
1666 }
1667 catch(out_of_range&) {
1668 if(missing_cells_known.count(si.id) == 0) {
1669 missing_cells_known.insert(si.id);
1670 printf("Unexpected missing cell #%d/%d\n", si.id, oldid);
1671 addMessage(XLAT("Unexpected missing cell #%1/%1", its(si.id), its(oldid)));
1672 }
1673 // config.tstate_max = config.tstate = tsAdjusting;
1674 return;
1675 }
1676 }
1677 }
1678
remap()1679 void texture_config::remap() {
1680 if(tstate == tsActive) {
1681 if(geometry == gFake) {
1682 /* always correct */
1683 true_remap();
1684 return;
1685 }
1686 patterns::computeCgroup();
1687 correctly_mapped = patterns::compatible(texture::cgroup, patterns::cgroup);
1688 if(!correctly_mapped)
1689 correctly_mapped =
1690 current_texture_parameters == config.orig_texture_parameters;
1691 if(correctly_mapped) true_remap();
1692 else addMessage(XLAT("Pattern incompatible."));
1693 }
1694 else if(tstate == tsAdjusting) {
1695 printf("perform_mapping %d/%d\n", config.tstate, config.tstate_max);
1696 calcparam();
1697 models::configure();
1698 drawthemap();
1699 perform_mapping();
1700 finish_mapping();
1701 printf("texture_map size = %d\n", isize(texture_map));
1702 }
1703 }
1704
1705 #if CAP_TEXTURE
1706 texture_data txp;
1707
get_txp(ld x,ld y,int p)1708 EX double get_txp(ld x, ld y, int p) {
1709 if(txp.texture_pixels.empty()) return 0.5;
1710 color_t col = txp.get_texture_pixel(txp.twidth * x, txp.twidth * y);
1711 return part(col, p) / 255.;
1712 }
1713 #endif
1714
textureArgs()1715 int textureArgs() {
1716 using namespace arg;
1717
1718 if(0) ;
1719 else if(argis("-txpic")) {
1720 shift(); config.texturename = args();
1721 }
1722
1723 else if(argis("-txp")) {
1724 shift(); config.gsplits = argi();
1725 }
1726
1727 else if(argis("-txc")) {
1728 shift(); config.configname = args();
1729 }
1730
1731 else if(argis("-txc")) {
1732 shift(); config.configname = args();
1733 }
1734
1735 else if(argis("-txpsize")) {
1736 shift(); txp.twidth = argi();
1737 }
1738
1739 else if(argis("-txpf")) {
1740 shift(); txp.readtexture(args());
1741 }
1742
1743 else if(argis("-txcl")) {
1744 PHASE(3); drawscreen();
1745 config.load();
1746 }
1747
1748 else return 1;
1749 return 0;
1750 }
1751
1752 auto texture_hook =
1753 addHook(hooks_args, 100, textureArgs)
__anon288753e12302() 1754 + addHook(hooks_clearmemory, 100, [] () { config.data.pixels_to_draw.clear(); });
1755
1756 int lastupdate;
1757
update()1758 void texture_data::update() {
1759 if(!pixels_to_draw.empty()) {
1760 auto t = SDL_GetTicks();
1761 while(SDL_GetTicks() < t + 75 && !pixels_to_draw.empty()) {
1762 auto p = pixels_to_draw.back();
1763 actDrawPixel(get<0>(p), get<1>(p), get<2>(p));
1764 pixels_to_draw.pop_back();
1765 }
1766 loadTextureGL();
1767 }
1768 }
1769
1770 EX }
1771 }
1772 #endif
1773