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