1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-12-22 16:20:26 +00:00
hyperrogue/textures.cpp

1777 lines
52 KiB
C++
Raw Normal View History

// Hyperbolic Rogue -- texture mode
// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
/** \file textures.cpp
* \brief texture mode, also the basic definitions of textures, used in 3D mode
*/
#include "hyper.h"
#if CAP_TEXTURE
2019-08-09 20:07:03 +00:00
namespace hr {
2017-12-14 01:53:29 +00:00
2019-08-09 23:56:00 +00:00
#if HDR
struct texture_triangle {
array<hyperpoint, 3> v;
array<shiftpoint, 3> tv;
texture_triangle(array<hyperpoint, 3> _v, array<shiftpoint, 3> _tv) : v(_v), tv(_tv) {}
2019-08-09 23:56:00 +00:00
};
struct textureinfo : basic_textureinfo {
shiftmatrix M;
2019-08-09 23:56:00 +00:00
vector<texture_triangle> triangles;
vector<glvertex> vertices;
cell *c;
vector<shiftmatrix> matrices;
2019-08-09 23:56:00 +00:00
// these are required to adjust to geometry changes
int current_type, symmetries;
};
#endif
2019-08-09 20:07:03 +00:00
EX namespace texture {
#if HDR
enum eTextureState {
tsOff, tsAdjusting, tsActive
};
struct texture_data {
GLuint textureid;
2020-09-11 09:17:48 +00:00
int twidth, theight;
2021-04-12 11:57:28 +00:00
bool stretched, original;
2019-08-09 20:07:03 +00:00
int tx, ty, origdim;
2020-09-11 09:17:48 +00:00
int strx, stry, base_x, base_y;
2021-04-12 11:57:28 +00:00
texture_data() { textureid = 0; twidth = 2048; theight = 0; stretched = false; original = false; }
2019-08-09 20:07:03 +00:00
vector<color_t> texture_pixels;
color_t& get_texture_pixel(int x, int y) {
2020-09-11 09:17:48 +00:00
return texture_pixels[(y&(theight-1))*twidth+(x&(twidth-1))];
2019-08-09 20:07:03 +00:00
}
vector<pair<color_t*, color_t>> undos;
vector<tuple<cell*, shiftpoint, int> > pixels_to_draw;
2019-08-09 20:07:03 +00:00
bool loadTextureGL();
bool whitetexture();
bool readtexture(string tn);
void saveRawTexture(string tn);
void undo();
void undoLock();
void update();
};
struct texture_config {
string texturename;
string configname;
eTextureState tstate;
eTextureState tstate_max;
transmatrix itt;
color_t grid_color;
color_t mesh_color;
color_t master_color;
color_t slave_color;
int color_alpha;
int gsplits;
int recolor(color_t col);
typedef tuple<eGeometry, eVariation, char, int, eModel, ld, ld> texture_parameters;
texture_parameters orig_texture_parameters;
map<int, textureinfo> texture_map, texture_map_orig;
set<cell*> models;
basic_textureinfo tinf3;
bool texture_tuned;
string texture_tuner;
vector<shiftpoint*> tuned_vertices;
2019-08-09 20:07:03 +00:00
bool apply(cell *c, const shiftmatrix &V, color_t col);
2019-08-09 20:07:03 +00:00
void mark_triangles();
void clear_texture_map();
void perform_mapping();
void mapTextureTriangle(textureinfo &mi, const array<hyperpoint, 3>& v, const array<shiftpoint, 3>& tv, int splits);
void mapTextureTriangle(textureinfo &mi, const array<hyperpoint, 3>& v, const array<shiftpoint, 3>& tv) { mapTextureTriangle(mi, v, tv, gsplits); }
2019-08-09 20:07:03 +00:00
void mapTexture2(textureinfo& mi);
void finish_mapping();
void true_remap();
void remap();
bool correctly_mapped;
hyperpoint texture_coordinates(shiftpoint);
2019-08-09 20:07:03 +00:00
void drawRawTexture();
void saveFullTexture(string tn);
bool save();
bool load();
texture_data data;
texture_config() {
// argh, no member initialization in some of my compilers
texturename = "textures/hyperrogue-texture.png";
configname = "textures/hyperrogue.txc";
itt = Id;
grid_color = 0;
mesh_color = 0;
master_color = 0xFFFFFF30;
slave_color = 0xFF000008;
color_alpha = 128;
gsplits = 1;
texture_tuned = false;
}
};
#endif
EX cpatterntype cgroup;
2017-12-19 13:35:34 +00:00
#if CAP_PNG
2017-12-09 01:20:10 +00:00
SDL_Surface *convertSurface(SDL_Surface* s) {
SDL_PixelFormat fmt;
// fmt.format = SDL_PIXELFORMAT_BGRA8888;
fmt.BitsPerPixel = 32;
fmt.BytesPerPixel = 4;
fmt.Ashift=24;
fmt.Rshift=16;
fmt.Gshift=8;
fmt.Bshift=0;
fmt.Amask=0xff<<24;
fmt.Rmask=0xff<<16;
fmt.Gmask=0xff<<8;
fmt.Bmask=0xff;
fmt.Aloss = fmt.Rloss = fmt.Gloss = fmt.Bloss = 0;
fmt.palette = NULL;
#if !CAP_SDL2
2017-12-09 01:20:10 +00:00
fmt.alpha = 0;
fmt.colorkey = 0x1ffffff;
#endif
return SDL_ConvertSurface(s, &fmt, SDL_SWSURFACE);
}
#endif
2017-12-09 01:20:10 +00:00
2017-12-22 19:59:04 +00:00
struct undo {
unsigned* pix;
unsigned last;
};
2019-08-09 20:07:03 +00:00
EX texture_config config;
2018-03-17 20:12:46 +00:00
2019-08-09 20:07:03 +00:00
EX bool saving = false;
2017-12-22 19:59:04 +00:00
2018-03-17 20:12:46 +00:00
template<class T, class U> void scale_colorarray(int origdim, int targetdim, const T& src, const U& dest) {
2017-12-14 01:53:29 +00:00
int ox = 0, tx = 0, partials[4];
2018-03-17 20:12:46 +00:00
int omissing = targetdim, tmissing = origdim;
2017-12-14 01:53:29 +00:00
for(int p=0; p<4; p++) partials[p] = 0;
2017-12-09 01:20:10 +00:00
2018-03-17 20:12:46 +00:00
while(tx < targetdim) {
2017-12-14 01:53:29 +00:00
int fv = min(omissing, tmissing);
color_t c = src(ox);
2017-12-14 01:53:29 +00:00
for(int p=0; p<4; p++)
partials[p] += part(c, p) * fv;
omissing -= fv; tmissing -= fv;
if(omissing == 0) {
2018-03-17 20:12:46 +00:00
ox++; omissing = targetdim;
2017-12-14 01:53:29 +00:00
}
if(tmissing == 0) {
color_t target;
2017-12-14 01:53:29 +00:00
for(int p=0; p<4; p++) {
part(target, p) = partials[p] / origdim;
partials[p] = 0;
}
dest(tx++, target);
tmissing = origdim;
}
2017-12-09 01:20:10 +00:00
}
2017-12-14 01:53:29 +00:00
}
2018-03-17 20:12:46 +00:00
bool texture_data::loadTextureGL() {
2017-12-17 23:24:56 +00:00
2018-03-17 20:12:46 +00:00
if(textureid == 0) glGenTextures(1, &textureid);
2017-12-09 01:20:10 +00:00
2018-05-18 15:34:12 +00:00
glBindTexture( GL_TEXTURE_2D, textureid);
2017-12-17 23:24:56 +00:00
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
// BGRA may be not supported in the web version
if(ISWEB) for(auto& p: texture_pixels) swap(part(p, 0), part(p, 2));
2020-09-11 09:17:48 +00:00
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, twidth, theight, 0,
ISWEB ? GL_RGBA : GL_BGRA, GL_UNSIGNED_BYTE,
2017-12-18 23:39:11 +00:00
&texture_pixels[0] );
if(ISWEB) for(auto& p: texture_pixels) swap(part(p, 0), part(p, 2));
2017-12-17 23:24:56 +00:00
return true;
}
2018-03-17 20:12:46 +00:00
bool texture_data::whitetexture() {
2017-12-22 19:59:04 +00:00
undos.clear();
2017-12-18 23:39:11 +00:00
texture_pixels.resize(0);
2020-09-11 09:17:48 +00:00
if(theight == 0) theight = twidth;
texture_pixels.resize(twidth * theight, 0xFFFFFFFF);
2017-12-30 16:01:53 +00:00
pixels_to_draw.clear();
2017-12-17 23:24:56 +00:00
return true;
}
2018-03-17 20:12:46 +00:00
bool texture_data::readtexture(string tn) {
#if CAP_SDL_IMG || CAP_PNG
2017-12-22 19:59:04 +00:00
undos.clear();
2017-12-21 13:05:07 +00:00
#if CAP_SDL_IMG
2018-03-17 20:12:46 +00:00
SDL_Surface *txt = IMG_Load(tn.c_str());
if(!txt) {
addMessage(XLAT("Failed to load %1", texturename));
return false;
}
2017-12-14 01:53:29 +00:00
auto txt2 = convertSurface(txt);
SDL_FreeSurface(txt);
tx = txt2->w, ty = txt2->h;
2017-12-14 01:53:29 +00:00
auto pix = [&] (int x, int y) { return qpixel(txt2, x, y); };
#elif CAP_PNG
2017-12-14 01:53:29 +00:00
2018-03-17 20:12:46 +00:00
FILE *f = fopen(tn.c_str(), "rb");
2018-02-20 21:12:54 +00:00
if(!f) { printf("failed to open file\n"); return false; }
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
2018-02-20 21:12:54 +00:00
if(!png) { printf("failed to create_read_struct\n"); return false; }
2018-01-05 18:05:41 +00:00
if(setjmp(png_jmpbuf(png))) {
printf("failed to read\n");
return false;
}
png_init_io(png, f);
// set the expected format
png_infop info = png_create_info_struct(png);
png_read_info(png, info);
tx = png_get_image_width(png, info);
ty = png_get_image_height(png, info);
png_byte color_type = png_get_color_type(png, info);
png_byte bit_depth = png_get_bit_depth(png, info);
if(bit_depth == 16) png_set_strip_16(png);
if(color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png);
if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png);
if(png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png);
if(color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
if(color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
png_read_update_info(png, info);
// read png
vector<png_bytep> row_pointers(ty);
vector<color_t> origpixels(ty * tx);
for(int y = 0; y < ty; y++)
row_pointers[y] = (png_bytep) & origpixels[y * tx];
png_read_image(png, &row_pointers[0]);
fclose(f);
for(int i=0; i<ty*tx; i++)
swap(part(origpixels[i], 0), part(origpixels[i], 2));
auto pix = [&] (int x, int y) {
if(x<0 || y<0 || x >= tx || y >= ty) return (color_t) 0;
return origpixels[y*tx + x];
};
2018-02-20 21:12:54 +00:00
printf("texture read OK\n");
2017-12-14 01:53:29 +00:00
#endif
2020-09-11 09:17:48 +00:00
if(twidth == 0)
twidth = next_p2(tx);
2021-04-12 11:57:28 +00:00
if(theight == 0) theight = (stretched || original) ? next_p2(ty) : twidth;
2020-09-11 09:17:48 +00:00
texture_pixels.resize(twidth * theight);
if(stretched) {
int i = 0;
println(hlog, tx, " -> " , twidth, " / " , ty, " -> ", theight);
for(int y=0; y<theight; y++)
for(int x=0; x<twidth; x++)
texture_pixels[i++] = pix(x * tx / twidth, y * ty / theight);
strx = twidth; stry = theight; base_x = base_y = 0;
}
2017-12-14 01:53:29 +00:00
2020-09-11 09:17:48 +00:00
else if(tx == twidth && ty == theight) {
int i = 0;
for(int y=0; y<ty; y++)
for(int x=0; x<tx; x++)
texture_pixels[i++] = pix(x, y);
2020-09-11 09:17:48 +00:00
strx = twidth; stry = theight; base_x = base_y = 0;
}
2021-04-12 11:57:28 +00:00
else if(original) {
base_x = 0;
base_y = 0;
strx = tx; stry = ty;
for(int y=0; y<ty; y++)
for(int x=0; x<tx; x++)
get_texture_pixel(x, y) = pix(x,y);
}
else {
origdim = max(tx, ty);
2020-09-11 09:17:48 +00:00
base_x = origdim/2 - tx/2;
base_y = origdim/2 - ty/2;
strx = twidth * tx / origdim;
stry = theight * ty / origdim;
2017-12-22 20:24:08 +00:00
qpixel_pixel_outside = 0; // outside is black
2017-12-17 23:24:56 +00:00
vector<int> half_expanded(twidth * ty);
for(int y=0; y<ty; y++)
2018-03-17 20:12:46 +00:00
scale_colorarray(origdim, twidth,
2020-09-11 09:17:48 +00:00
[&] (int x) { return pix(x - base_x,y); },
[&] (int x, int v) { half_expanded[twidth * y + x] = v; }
);
for(int x=0; x<twidth; x++)
2018-03-17 20:12:46 +00:00
scale_colorarray(origdim, twidth,
2020-09-11 09:17:48 +00:00
[&] (int y) { return y-base_y < 0 || y-base_y >= ty ? 0 : half_expanded[x + (y-base_y) * twidth]; },
2017-12-22 19:59:04 +00:00
[&] (int y, int v) { get_texture_pixel(x, y) = v; }
);
2020-09-15 18:01:41 +00:00
base_x = base_x * twidth / origdim;
base_y = base_y * theight / origdim;
}
2017-12-14 01:53:29 +00:00
#if CAP_SDL_IMG
2017-12-14 01:53:29 +00:00
SDL_FreeSurface(txt2);
#endif
2017-12-09 01:20:10 +00:00
return true;
#else
return false;
#endif
2017-12-09 01:20:10 +00:00
}
2018-03-17 20:12:46 +00:00
void texture_data::saveRawTexture(string tn) {
2017-12-21 11:58:36 +00:00
SDL_Surface *sraw = SDL_CreateRGBSurface(SDL_SWSURFACE,twidth,twidth,32,0,0,0,0);
for(int y=0; y<twidth; y++)
for(int x=0; x<twidth; x++)
2017-12-22 19:59:04 +00:00
qpixel(sraw,x,y) = get_texture_pixel(x, y);
2018-03-17 20:12:46 +00:00
IMAGESAVE(sraw, tn.c_str());
2017-12-21 11:58:36 +00:00
SDL_FreeSurface(sraw);
2018-03-17 20:12:46 +00:00
addMessage(XLAT("Saved the raw texture to %1", tn));
2017-12-21 11:58:36 +00:00
}
hyperpoint texture_config::texture_coordinates(shiftpoint h) {
hyperpoint inmodel;
applymodel(h, inmodel);
inmodel[0] *= current_display->radius * 1. / current_display->scrsize;
inmodel[1] *= current_display->radius * pconf.stretch / current_display->scrsize;
inmodel[2] = 1;
inmodel = itt * inmodel;
inmodel[0] = (inmodel[0] + 1) / 2;
inmodel[1] = (inmodel[1] + 1) / 2;
return inmodel;
}
void texture_config::mapTextureTriangle(textureinfo &mi, const array<hyperpoint, 3>& v, const array<shiftpoint, 3>& tv, int splits) {
2017-12-09 01:20:10 +00:00
2017-12-14 01:53:29 +00:00
if(splits) {
2018-01-05 18:05:41 +00:00
array<hyperpoint, 3> v2 = make_array( mid(v[1], v[2]), mid(v[2], v[0]), mid(v[0], v[1]) );
array<shiftpoint, 3> tv2 = make_array( mid(tv[1], tv[2]), mid(tv[2], tv[0]), mid(tv[0], tv[1]) );
mapTextureTriangle(mi, make_array(v[0], v2[2], v2[1]), make_array(tv[0], tv2[2], tv2[1]), splits-1);
mapTextureTriangle(mi, make_array(v[1], v2[0], v2[2]), make_array(tv[1], tv2[0], tv2[2]), splits-1);
mapTextureTriangle(mi, make_array(v[2], v2[1], v2[0]), make_array(tv[2], tv2[1], tv2[0]), splits-1);
mapTextureTriangle(mi, make_array(v2[0], v2[1], v2[2]), make_array(tv2[0], tv2[1], tv2[2]), splits-1);
2017-12-14 01:53:29 +00:00
return;
}
2017-12-09 01:20:10 +00:00
for(int i=0; i<3; i++) {
2018-02-11 18:08:17 +00:00
mi.vertices.push_back(glhr::pointtogl(v[i]));
2017-12-09 01:20:10 +00:00
hyperpoint inmodel;
mi.tvertices.push_back(glhr::pointtogl(texture_coordinates(tv[i])));
}
// when reading a spherical band in a cylindrical projection,
// take care that texture vertices are mapped not around the sphere
if(sphere && mdBandAny()) {
2018-06-22 12:47:24 +00:00
int s = isize(mi.tvertices)-3;
ld xmin = min(mi.tvertices[s][0], min(mi.tvertices[s+1][0], mi.tvertices[s+2][0]));
ld xmax = max(mi.tvertices[s][0], max(mi.tvertices[s+1][0], mi.tvertices[s+2][0]));
if(xmax - xmin > .5) {
for(int ss=s; ss<s+3; ss++)
if(mi.tvertices[ss][0] < .5) mi.tvertices[ss][0] += 1;
}
}
2017-12-09 01:20:10 +00:00
}
texture_triangle *edited_triangle;
textureinfo *edited_tinfo;
int celltriangles(cell *c) {
return c->type;
}
array<shiftpoint, 3> findTextureTriangle(cell *c, patterns::patterninfo& si, int i) {
shiftmatrix M = ggmatrix(c) * applyPatterndir(c, si);
return make_array(M * C0, M * get_corner_position(c, i), M * get_corner_position(c, i+1));
}
// using: mouseh, mouseouver
int getTriangleID(cell *c, patterns::patterninfo& si, shiftpoint h) {
// auto si = getpatterninfo0(c);
ld quality = 1e10;
int best = 0;
for(int i=0; i<celltriangles(c); i++) {
auto t = findTextureTriangle(c, si, i);
ld q = hdist(t[1], h) + hdist(t[2], h);
if(q < quality) quality = q, best = i;
}
return best;
}
void mapTexture(cell *c, textureinfo& mi, patterns::patterninfo &si, const shiftmatrix& T, int shift = 0) {
2017-12-14 01:53:29 +00:00
mi.c = c;
mi.symmetries = si.symmetries;
2018-08-14 08:18:18 +00:00
mi.current_type = celltriangles(c);
2017-12-14 01:53:29 +00:00
2018-08-19 13:15:47 +00:00
mi.M = T * applyPatterndir(c, si);
mi.triangles.clear();
transmatrix iv = inverse(applyPatterndir(c, si));
int sd = si.dir;
if((NONSTDVAR) || bt::in()) sd = 0;
for(int i=0; i<c->type; i++) {
hyperpoint h1 = iv * get_corner_position(c, (i + sd + shift) % c->type);
hyperpoint h2 = iv * get_corner_position(c, (i + sd + shift + 1) % c->type);
mi.triangles.emplace_back(make_array(C0, h1, h2), make_array(mi.M*C0, mi.M*h1, mi.M*h2));
}
2017-12-14 01:53:29 +00:00
}
2018-03-17 20:12:46 +00:00
void texture_config::mapTexture2(textureinfo& mi) {
mi.vertices.clear();
mi.tvertices.clear();
for(auto& t: mi.triangles)
mapTextureTriangle(mi, t.v, t.tv);
}
int texture_config::recolor(color_t col) {
2017-12-14 01:53:29 +00:00
if(color_alpha == 0) return col;
for(int i=1; i<4; i++)
part(col, i) = color_alpha + ((255-color_alpha) * part(col,i) + 127) / 255;
return col;
}
2019-08-09 20:07:03 +00:00
EX bool texture_aura;
2018-08-03 08:47:20 +00:00
2019-08-09 20:07:03 +00:00
EX bool using_aura() {
2018-08-03 08:47:20 +00:00
return texture_aura && config.tstate == texture::tsActive;
}
bool texture_config::apply(cell *c, const shiftmatrix &V, color_t col) {
2018-08-20 00:04:49 +00:00
if(config.tstate == tsOff || !correctly_mapped) return false;
2017-12-09 01:20:10 +00:00
using namespace patterns;
auto si = getpatterninfo0(c);
2017-12-14 01:53:29 +00:00
2018-03-17 20:12:46 +00:00
if(config.tstate == tsAdjusting) {
dynamicval<color_t> d(poly_outline, slave_color);
draw_floorshape(c, V, cgi.shFullFloor, 0, PPR::LINE);
curvepoint(C0);
for(int i=0; i<c->type; i++)
curvepoint(get_corner_position(c, i)), curvepoint(C0);
queuecurve(V, slave_color, 0, PPR::LINE);
2017-12-14 01:53:29 +00:00
return false;
}
2017-12-09 01:20:10 +00:00
try {
auto& mi = texture_map.at(si.id);
2017-12-09 01:20:10 +00:00
set_floor(cgi.shFullFloor);
2017-12-09 01:20:10 +00:00
qfi.tinf = &mi;
2018-08-19 13:15:47 +00:00
qfi.spin = applyPatterndir(c, si);
2017-12-09 01:20:10 +00:00
2017-12-17 23:24:56 +00:00
if(grid_color) {
dynamicval<color_t> d(poly_outline, grid_color);
draw_floorshape(c, V, cgi.shFullFloor, 0, PPR::FLOOR);
2017-12-14 01:53:29 +00:00
}
2017-12-21 13:05:07 +00:00
2018-08-03 08:47:20 +00:00
if(using_aura()) {
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(mi.tvertices); i += 3) {
2017-12-21 13:05:07 +00:00
ld p[3];
2019-03-11 17:48:31 +00:00
if(inHighQual)
2017-12-21 13:05:07 +00:00
while(true) {
p[0] = hrandf();
p[1] = hrandf();
p[2] = 1 - p[0] - p[1];
if(p[2] >= 0) break;
}
2019-03-11 17:48:31 +00:00
else p[0] = p[1] = p[2] = 1/3.;
2017-12-21 13:05:07 +00:00
ld v[2] = {0,0};
for(int j=0; j<2; j++) for(int k=0; k<3; k++)
2018-02-11 18:08:17 +00:00
v[j] += mi.tvertices[i+k][j] * p[k];
2017-12-21 13:05:07 +00:00
2018-03-17 20:12:46 +00:00
int vi[2] = {int(v[0] * config.data.twidth), int(v[1] * config.data.twidth)};
2017-12-21 13:05:07 +00:00
2018-03-17 20:12:46 +00:00
col = config.data.get_texture_pixel(vi[0], vi[1]);
2018-02-11 18:08:17 +00:00
hyperpoint h = glhr::gltopoint(mi.vertices[i]);
2017-12-21 13:05:07 +00:00
addaura(V*h, col, 0);
}
}
2017-12-09 01:20:10 +00:00
return true;
}
catch(out_of_range&) {
// printf("Ignoring tile #%d / %08x: not mapped\n", si.id, patterns::subcode(c, si));
2017-12-09 01:20:10 +00:00
return false;
}
}
2018-03-17 20:12:46 +00:00
void texture_config::mark_triangles() {
if(config.tstate == tsAdjusting)
for(auto& mi: texture_map) {
for(auto& t: mi.second.triangles) {
vector<hyperpoint> t2;
for(int i=0; i<3; i++)
t2.push_back(unshift(t.tv[i]));
prettypoly(t2, master_color, master_color, gsplits);
}
}
}
static const auto current_texture_parameters = tie(geometry, variation, patterns::whichPattern, patterns::subpattern_flags, pmodel, pconf.scale, pconf.alpha);
2018-03-17 20:12:46 +00:00
void texture_config::clear_texture_map() {
texture_map.clear();
edited_triangle = nullptr;
edited_tinfo = nullptr;
tuned_vertices.clear();
models.clear();
texture_tuned = false;
texture_tuner = "";
}
2018-03-17 20:12:46 +00:00
void texture_config::perform_mapping() {
2017-12-14 01:53:29 +00:00
if(gsplits < 0) gsplits = 0;
if(gsplits > 4) gsplits = 4;
using namespace patterns;
clear_texture_map();
2017-12-18 22:42:08 +00:00
for(auto& p: gmatrix) {
2017-12-09 01:20:10 +00:00
cell *c = p.first;
auto si = getpatterninfo0(c);
2017-12-09 01:20:10 +00:00
bool replace = false;
2017-12-09 19:01:50 +00:00
2017-12-14 01:53:29 +00:00
// int sgn = sphere ? -1 : 1;
if(!texture_map.count(si.id))
2017-12-09 01:20:10 +00:00
replace = true;
2017-12-16 08:03:50 +00:00
else if(hdist0(p.second*sphereflip * C0) < hdist0(texture_map[si.id].M * sphereflip * C0))
2017-12-09 01:20:10 +00:00
replace = true;
if(replace) {
auto& mi = texture_map[si.id];
2017-12-14 01:53:29 +00:00
mapTexture(c, mi, si, p.second);
2018-03-17 20:12:46 +00:00
mi.texture_id = config.data.textureid;
2017-12-17 23:24:56 +00:00
}
2017-12-09 01:20:10 +00:00
}
2017-12-14 01:53:29 +00:00
for(auto& t: texture_map) models.insert(t.second.c);
2017-12-18 22:42:08 +00:00
for(auto& p: gmatrix) {
cell *c = p.first;
bool nearmodel = models.count(c);
forCellEx(c2, c)
if(models.count(c2))
nearmodel = true;
if(nearmodel) {
auto si = getpatterninfo0(c);
texture_map[si.id].matrices.push_back(p.second * applyPatterndir(c, si));
}
}
2017-12-19 13:35:34 +00:00
}
2018-08-20 00:04:49 +00:00
set<int> missing_cells_known;
2018-03-17 20:12:46 +00:00
void texture_config::finish_mapping() {
tinf3.tvertices.clear();
tinf3.texture_id = config.data.textureid;
if(isize(texture_map) && isize(texture_map.begin()->second.triangles)) {
2019-04-13 11:04:41 +00:00
auto& tris = texture_map.begin()->second.triangles;
for(int a=0; a<8; a++) {
auto& tri = tris[a % isize(tris)];
shiftpoint center = tri.tv[0];
hyperpoint v1 = unshift(tri.tv[1], center.shift) - center.h;
hyperpoint v2 = unshift(tri.tv[2], center.shift) - center.h;
texture_order([&] (ld x, ld y) {
shiftpoint h = shiftless(normalize(center.h + v1 * x + v2 * y), center.shift);
tinf3.tvertices.push_back(glhr::pointtogl(texture_coordinates(h)));
});
}
}
2018-08-20 00:04:49 +00:00
if(config.tstate == tsActive) {
for(auto& mi: texture_map)
mapTexture2(mi.second);
2018-08-20 00:04:49 +00:00
correctly_mapped = true;
missing_cells_known.clear();
}
patterns::computeCgroup();
texture::cgroup = patterns::cgroup;
texture_map_orig = texture_map;
orig_texture_parameters = current_texture_parameters;
2018-06-22 12:47:24 +00:00
// printf("texture_map has %d elements (S%d)\n", isize(texture_map), config.tstate);
2017-12-09 01:20:10 +00:00
}
#if CAP_SHOT
2018-03-17 20:12:46 +00:00
void texture_config::saveFullTexture(string tn) {
addMessage(XLAT("Saving full texture to %1...", tn));
dynamicval<color_t> dd(grid_color, 0);
dynamicval<color_t> dm(mesh_color, 0);
dynamicval<ld> dx(pconf.xposition, 0);
dynamicval<ld> dy(pconf.yposition, 0);
dynamicval<ld> dvs(pconf.scale, (pmodel == mdDisk && !euclid) ? 1 : pconf.scale);
dynamicval<bool> dro(rug::rugged, false);
2018-08-03 08:48:40 +00:00
dynamicval<bool> dnh(nohud, true);
2017-12-21 13:05:07 +00:00
texture::saving = true;
drawscreen();
2017-12-09 01:20:10 +00:00
2018-12-13 12:33:08 +00:00
dynamicval<int> dvx(shot::shotx, data.twidth);
dynamicval<int> dvy(shot::shoty, data.twidth);
dynamicval<int> dvf(shot::shotformat, -1);
shot::take(tn.c_str());
2017-12-21 13:05:07 +00:00
texture::saving = false;
drawscreen();
2018-03-17 20:12:46 +00:00
if(data.readtexture(tn) && data.loadTextureGL()) {
itt = Id; // xyscale(Id, current_display->scrsize * 1. / current_display->radius);
2018-02-20 21:12:54 +00:00
perform_mapping();
finish_mapping();
}
2017-12-09 01:20:10 +00:00
}
#endif
2017-12-09 01:20:10 +00:00
2017-12-14 01:53:29 +00:00
bool newmove = false;
2018-02-11 18:08:17 +00:00
vector<glhr::textured_vertex> rtver(4);
2018-03-17 20:12:46 +00:00
void texture_config::drawRawTexture() {
2019-11-03 10:00:56 +00:00
glflush();
current_display->next_shader_flags = GF_TEXTURE;
dynamicval<eModel> m(pmodel, mdPixel);
current_display->set_all(0, 0);
2018-02-09 00:46:14 +00:00
glhr::color2(0xFFFFFF20);
2018-03-17 20:12:46 +00:00
glBindTexture(GL_TEXTURE_2D, config.data.textureid);
2017-12-14 01:53:29 +00:00
for(int i=0; i<4; i++) {
2017-12-25 09:26:50 +00:00
int cx[4] = {2, -2, -2, 2};
int cy[4] = {2, 2, -2, -2};
2017-12-14 01:53:29 +00:00
int x = cx[i];
int y = cy[i];
hyperpoint inmodel = hpxyz(x, y, 1);
inmodel = itt * inmodel;
2018-02-11 18:08:17 +00:00
rtver[i].texture[0] = (inmodel[0]+1)/2;
rtver[i].texture[1] = (inmodel[1]+1)/2;
rtver[i].coords[0] = x * current_display->scrsize;
rtver[i].coords[1] = y * current_display->scrsize;
2019-03-12 01:43:00 +00:00
rtver[i].coords[2] = 0;
rtver[i].coords[3] = 1;
2017-12-14 01:53:29 +00:00
}
2019-10-21 20:34:20 +00:00
glhr::id_modelview();
2018-02-11 18:08:17 +00:00
glhr::prepare(rtver);
glhr::set_depthtest(false);
2017-12-14 01:53:29 +00:00
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
2017-12-09 01:20:10 +00:00
2017-12-16 08:03:50 +00:00
struct magicmapper_point {
cell *c;
hyperpoint cell_relative;
hyperpoint texture_coords;
};
vector<magicmapper_point> amp;
2018-01-03 00:05:03 +00:00
enum eMagicParameter {
mpScale,
mpProjection,
mpMove,
mpRotate,
mpSlant,
mpStretch,
mpTexPosX,
mpTexPosY,
2018-01-03 00:05:03 +00:00
mpMAX
};
EX vector<string> mpnames = {
2018-01-03 00:05:03 +00:00
"affect model scale",
"affect model projection",
"affect model central point",
2018-01-03 00:05:03 +00:00
"affect model rotation",
"affect texture slanting",
"affect texture stretching",
"affect texture position X",
"affect texture position Y"
2018-01-03 00:05:03 +00:00
};
flagtype current_magic = 15;
bool have_mp(eMagicParameter i) { return (current_magic >> i) & 1; }
2017-12-16 08:03:50 +00:00
struct magic_param {
bool do_spin;
int texmode;
ld spinangle, scale, proj, moveangle, shift, slant, stretch, tx, ty;
2017-12-16 08:03:50 +00:00
void shuffle() {
do_spin = hrand(2);
spinangle = hrandf() - hrandf();
moveangle = hrandf() * 2 * M_PI;
shift = hrandf() - hrandf();
scale = hrandf() - hrandf();
proj = hrandf() - hrandf();
texmode = hrand(3);
2018-01-03 00:05:03 +00:00
slant = have_mp(mpSlant) ? hrandf() - hrandf() : 0;
stretch = have_mp(mpStretch) ? hrandf() - hrandf() : 0;
tx = have_mp(mpTexPosX) ? hrandf() - hrandf() : 0;
ty = have_mp(mpTexPosY) ? hrandf() - hrandf() : 0;
2018-01-03 00:05:03 +00:00
}
void affect_itt(const transmatrix& T) {
transmatrix Ti = inverse(T);
for(auto& p: amp)
p.texture_coords = Ti * p.texture_coords;
2018-03-17 20:12:46 +00:00
config.itt = config.itt * T;
2017-12-16 08:03:50 +00:00
}
void apply(ld delta) {
2018-01-03 00:05:03 +00:00
if(have_mp(mpProjection))
pconf.alpha *= exp(delta * proj);
2018-01-03 00:05:03 +00:00
if(have_mp(mpScale))
pconf.scale *= exp(delta * scale);
2018-01-03 00:05:03 +00:00
if(do_spin) {
if(have_mp(mpRotate))
View = spin(delta * spinangle) * View;
}
else {
if(have_mp(mpMove))
View = spin(moveangle) * xpush(delta*shift) * spin(-moveangle) * View;
}
if(texmode == 0 && have_mp(mpStretch))
2018-01-03 00:05:03 +00:00
affect_itt(euaffine(hpxyz(0, delta * stretch, 0)));
2017-12-16 08:03:50 +00:00
if(texmode == 1 && have_mp(mpSlant))
2018-01-03 00:05:03 +00:00
affect_itt(euaffine(hpxyz(delta * slant, 0, 0)));
2017-12-16 08:03:50 +00:00
if(texmode == 2 && (have_mp(mpTexPosX) || have_mp(mpTexPosY)))
affect_itt(eupush(delta * tx, delta * ty));
2017-12-16 08:03:50 +00:00
fixmatrix(View);
}
};
ld magic_quality() {
gmatrix.clear();
calcparam();
ld q = 0;
for(auto& p: amp) {
hyperpoint inmodel;
applymodel(ggmatrix(p.c) * p.cell_relative, inmodel);
inmodel[0] *= current_display->radius * 1. / current_display->scrsize;
inmodel[1] *= current_display->radius * 1. / current_display->scrsize;
2019-02-27 22:41:45 +00:00
q += sqhypot_d(2, inmodel - p.texture_coords);
2017-12-16 08:03:50 +00:00
}
return q;
}
void applyMagic() {
ld cq = magic_quality();
int last_success = 0;
for(int s=0; s<50000 && s<last_success + 1000; s++) {
magic_param p;
p.shuffle();
bool failed = false;
for(ld delta = 1; abs(delta) > 1e-9; delta *= (failed ? -.7 : 1.2)) {
2017-12-16 08:03:50 +00:00
p.apply(delta);
ld nq = magic_quality();
if(nq < cq) {
cq = nq;
last_success = s;
}
else {
p.apply(-delta);
failed = true;
}
}
}
config.perform_mapping();
2017-12-16 08:03:50 +00:00
}
enum eTexturePanstate {tpsModel, tpsMove, tpsScale, tpsAffine, tpsZoom, tpsProjection, tpsCell, tpsTriangle, tpsTune};
2017-12-14 01:53:29 +00:00
eTexturePanstate panstate;
2017-12-09 01:20:10 +00:00
void mousemovement() {
static hyperpoint lastmouse;
hyperpoint mouseeu = hpxyz((mousex - current_display->xcenter + .0) / current_display->scrsize, (mousey - current_display->ycenter + .0) / current_display->scrsize, 1);
bool nonzero = mouseeu[0] || mouseeu[1];
switch(panstate) {
case tpsModel:
if(!newmove && mouseh[2] < 50 && lastmouse[2] < 50) {
panning(shiftless(lastmouse), mouseh);
2018-03-17 20:12:46 +00:00
config.perform_mapping();
}
lastmouse = unshift(mouseh); newmove = false;
break;
case tpsMove: {
if(!newmove)
2018-03-17 20:12:46 +00:00
config.itt = config.itt * inverse(eupush(mouseeu)) * eupush(lastmouse);
lastmouse = mouseeu; newmove = false;
break;
}
case tpsScale: {
if(nonzero && !newmove)
2018-03-17 20:12:46 +00:00
config.itt = config.itt * inverse(euscalezoom(mouseeu)) * euscalezoom(lastmouse);
if(nonzero) lastmouse = mouseeu;
newmove = false;
break;
}
case tpsAffine: {
if(!newmove)
2018-03-17 20:12:46 +00:00
config.itt = config.itt * inverse(euaffine(mouseeu)) * euaffine(lastmouse);
lastmouse = mouseeu; newmove = false;
break;
}
case tpsZoom: {
// do not zoom in portrait!
if(nonzero && !newmove) {
2017-12-16 08:03:50 +00:00
View = inverse(spintox(mouseeu)) * spintox(lastmouse) * View;
pconf.scale = pconf.scale * sqhypot_d(2, mouseeu) / sqhypot_d(2, lastmouse);
config.perform_mapping();
}
if(nonzero) lastmouse = mouseeu;
newmove = false;
break;
}
case tpsProjection: {
if(nonzero && !newmove) {
pconf.alpha = pconf.alpha * sqhypot_d(2, mouseeu) / sqhypot_d(2, lastmouse);
}
if(nonzero) lastmouse = mouseeu;
newmove = false;
break;
}
case tpsCell: {
cell *c = mouseover;
if(!c) break;
auto si = patterns::getpatterninfo0(c);
if(newmove) {
edited_tinfo = NULL;
2018-03-17 20:12:46 +00:00
if(config.texture_map.count(si.id)) {
edited_tinfo = &config.texture_map[si.id];
newmove = false;
}
}
if(edited_tinfo && isize(edited_tinfo->triangles) == celltriangles(c)) {
for(int i=0; i<celltriangles(c); i++)
edited_tinfo->triangles[i].tv = findTextureTriangle(c, si, i);
2018-03-17 20:12:46 +00:00
config.texture_tuned = true;
}
break;
}
case tpsTriangle: {
cell *c = mouseover;
if(!c) break;
auto si = patterns::getpatterninfo0(c);
int i = getTriangleID(c, si, mouseh);
if(newmove) {
edited_triangle = NULL;
2018-03-17 20:12:46 +00:00
if(config.texture_map.count(si.id)) {
edited_triangle = &config.texture_map[si.id].triangles[i];
newmove = false;
}
}
if(edited_triangle) {
edited_triangle->tv = findTextureTriangle(c, si, i);
2018-03-17 20:12:46 +00:00
config.texture_tuned = true;
}
break;
}
case tpsTune: {
ld tdist = 1e20;
if(newmove) {
2018-03-17 20:12:46 +00:00
config.tuned_vertices.clear();
for(auto& a: config.texture_map)
for(auto& t: a.second.triangles)
for(auto& v: t.tv)
if(hdist(v, mouseh) < tdist)
tdist = hdist(v, mouseh);
2018-03-17 20:12:46 +00:00
for(auto& a: config.texture_map)
for(auto& t: a.second.triangles)
for(auto& v: t.tv)
if(hdist(v, mouseh) < tdist * (1.000001))
2018-03-17 20:12:46 +00:00
config.tuned_vertices.push_back(&v);
newmove = false;
}
2018-03-17 20:12:46 +00:00
for(auto v: config.tuned_vertices) {
*v = mouseh;
2018-03-17 20:12:46 +00:00
config.texture_tuned = true;
}
break;
}
default: break;
}
}
patterns::patterninfo si_save;
saverlist texturesavers;
eVariation targetvariation;
eGeometry targetgeometry;
string csymbol;
2020-05-30 18:07:50 +00:00
string tes;
void init_textureconfig() {
#if CAP_CONFIG
texturesavers = std::move(savers);
for(int i=0; i<3; i++)
for(int j=0; j<3; j++)
2018-03-17 20:12:46 +00:00
addsaver(config.itt[i][j], "texturematrix_" + its(i) + its(j), i==j ? 1 : 0);
for(int i=0; i<3; i++)
for(int j=0; j<3; j++)
addsaver(View[i][j], "viewmatrix_" + its(i) + its(j), i==j ? 1 : 0);
addsaverenum(targetgeometry, "geometry", gNormal);
2020-05-30 18:07:50 +00:00
addsaver(tes, "tes", "");
2017-12-25 09:26:50 +00:00
addsaverenum(pmodel, "used model", mdDisk);
addsaver(vid.yshift, "Y shift", 0);
addsaver(pconf.yposition, "Y position", 0);
addsaver(pconf.xposition, "X position", 0);
addsaver(pconf.camera_angle, "camera angle", 0);
addsaverenum(targetvariation, "bitruncated", eVariation::bitruncated);
// ... geometry parameters
addsaverenum(patterns::whichPattern, "pattern", patterns::PAT_TYPES);
addsaver(patterns::subpattern_flags, "pattern flags", 0);
addsaver(si_save.id, "center type", 1);
addsaver(si_save.dir, "center direction", 0);
addsaver(si_save.reflect, "center reflection", false);
2018-03-17 20:12:46 +00:00
addsaver(config.data.twidth, "texture resolution", 2048);
addsaver(config.gsplits, "precision", 1);
2018-03-17 20:12:46 +00:00
addsaver(config.grid_color, "grid color", 0);
addsaver(config.color_alpha, "alpha color", 0);
addsaver(config.mesh_color, "mesh color", 0);
addsaver(pconf.alpha, "projection", 1);
addsaver(pconf.scale, "scale", 1);
addsaver(pconf.stretch, "stretch", 1);
2018-08-14 08:18:18 +00:00
addsaver(vid.binary_width, "binary-tiling-width", 1);
2018-03-17 20:12:46 +00:00
addsaver(config.texturename, "texture filename", "");
addsaver(config.texture_tuner, "texture tuning", "");
addsaver(csymbol, "symbol", "");
swap(texturesavers, savers);
#endif
}
2018-03-17 20:12:46 +00:00
bool texture_config::save() {
#if CAP_CONFIG
init_textureconfig();
FILE *f = fopen(configname.c_str(), "wt");
if(!f) return false;
if(texture_tuned) {
texture_tuner = "";
2018-03-17 20:12:46 +00:00
for(auto& a: config.texture_map)
for(auto& t: a.second.triangles)
for(auto& v: t.tv)
for(int i=0; i<3; i++) {
texture_tuner += fts(v[i]);
texture_tuner += ';';
}
}
targetgeometry = geometry;
targetvariation = variation;
cell *ctr = centerover;
2018-07-27 20:14:46 +00:00
si_save = patterns::getpatterninfo0(ctr);
2020-05-30 18:07:50 +00:00
if(arb::in()) tes = arb::current.filename;
2018-07-27 20:14:46 +00:00
2022-05-06 10:40:48 +00:00
csymbol = "";
#if CAP_ARCM
if(arcm::in()) csymbol = arcm::current.symbol;
2022-05-06 10:40:48 +00:00
#endif
for(auto s: texturesavers) if(s->dosave())
fprintf(f, "%s=%s\n", s->name.c_str(), s->save().c_str());
fclose(f);
#endif
return true;
}
2018-03-17 20:12:46 +00:00
bool texture_config::load() {
#if CAP_CONFIG
init_textureconfig();
FILE *f = fopen(configname.c_str(), "rt");
if(!f) return false;
swap(texturesavers, savers);
for(auto s: savers) s->reset();
loadNewConfig(f);
swap(texturesavers, savers);
fclose(f);
polygonal::solve();
if(1) {
dynamicval<patterns::ePattern> d1(patterns::whichPattern, patterns::whichPattern);
dynamicval<int> d2(patterns::subpattern_flags, patterns::subpattern_flags);
if(targetgeometry != geometry) {
stop_game();
2022-05-06 10:40:48 +00:00
#if CAP_ARCM
if(targetgeometry == gArchimedean) {
arcm::current.symbol = csymbol;
arcm::current.parse();
if(arcm::current.errors) {
printf("Error while reading Archimedean texture: %s\n", arcm::current.errormsg.c_str());
addMessage("Error: " + arcm::current.errormsg);
return false;
}
}
2022-05-06 10:40:48 +00:00
#endif
2020-05-30 18:07:50 +00:00
if(targetgeometry == gArbitrary) {
arb::run(tes);
stop_game();
}
set_geometry(targetgeometry);
start_game();
2018-03-17 20:12:46 +00:00
return config.load();
}
if(variation != targetvariation) {
set_variation(targetvariation);
start_game();
return config.load();
}
}
2018-01-03 00:21:38 +00:00
if(true) {
celllister cl(currentmap->gamestart(), 20, 10000, NULL);
bool found = false;
for(cell *c: cl.lst) if(euclid || ctof(c)) {
auto si_here = patterns::getpatterninfo0(c);
if(si_here.id == si_save.id && si_here.reflect == si_save.reflect && si_here.dir == si_save.dir) {
centerover = c;
2018-01-03 00:21:38 +00:00
found = true;
break;
}
}
if(!found)
addMessage(XLAT("warning: unable to find the center"));
}
2018-03-17 20:12:46 +00:00
if(!data.readtexture(texturename)) return false;
if(!data.loadTextureGL()) return false;
2017-12-16 08:03:50 +00:00
calcparam();
models::configure();
drawthemap();
2018-03-17 20:12:46 +00:00
config.tstate = config.tstate_max = tsActive;
string s = std::move(texture_tuner);
perform_mapping();
texture_tuner = std::move(s);
if(texture_tuner != "") {
texture_tuned = true;
vector<ld*> coords;
2018-03-17 20:12:46 +00:00
for(auto& a: config.texture_map)
for(auto& t: a.second.triangles)
for(auto& v: t.tv)
for(int i=0; i<3; i++)
coords.push_back(&v[i]);
int semicounter = 0;
for(char c: texture_tuner) if(c == ';') semicounter++;
2018-06-22 12:47:24 +00:00
if(semicounter != isize(coords))
addMessage("Tuning error: wrong number");
else {
string cur = "";
int index = 0;
for(char c: texture_tuner)
if(c == ';') {
*(coords[index++]) = atof(cur.c_str());
cur = "";
}
else cur += c;
2018-06-22 12:47:24 +00:00
printf("index = %d semi = %d sc = %d\n", index, semicounter, isize(coords));
}
}
finish_mapping();
#endif
return true;
}
2018-01-03 00:05:03 +00:00
void showMagicMenu() {
cmode = sm::SIDE | sm::MAYDARK | sm::DIALOG_STRICT_X;
gamescreen();
2018-01-03 00:05:03 +00:00
dialog::init(XLAT("texture auto-adjustment"));
dialog::addInfo(XLAT("drag from the model to the texture"));
for(int i=0; i<mpMAX; i++)
dialog::addBoolItem(XLAT(mpnames[i]), have_mp(eMagicParameter(i)), 'a'+i);
2018-06-22 12:47:24 +00:00
dialog::addSelItem(XLAT("delete markers"), its(isize(amp)), 'D');
2018-01-03 00:05:03 +00:00
dialog::addItem(XLAT("perform auto-adjustment"), 'R');
dialog::addBack();
2018-01-03 00:05:03 +00:00
getcstat = '-';
dialog::display();
if(holdmouse) {
hyperpoint mouseeu = hpxyz((mousex - current_display->xcenter + .0) / current_display->scrsize, (mousey - current_display->ycenter + .0) / current_display->scrsize, 1);
2018-01-03 00:05:03 +00:00
if(newmove) {
magicmapper_point newpoint;
newpoint.c = mouseover;
newpoint.cell_relative = inverse_shift(gmatrix[mouseover], mouseh);
2018-01-03 00:05:03 +00:00
amp.push_back(newpoint);
newmove = false;
}
amp.back().texture_coords = mouseeu;
}
2018-03-17 20:12:46 +00:00
if(config.tstate == tsAdjusting) {
2018-01-03 00:05:03 +00:00
initquickqueue();
char letter = 'A';
for(auto& am: amp) {
shiftpoint h = ggmatrix(am.c) * am.cell_relative;
queuestr(h, vid.fsize, s0+letter, 0xC00000, 1);
2018-01-03 00:05:03 +00:00
/*
2018-01-03 00:05:03 +00:00
hyperpoint inmodel;
applymodel(h, inmodel);
inmodel[0] *= current_display->radius * 1. / current_display->scrsize;
inmodel[1] *= current_display->radius * 1. / current_display->scrsize;
*/
2018-01-03 00:05:03 +00:00
queuestr(
current_display->xcenter + current_display->scrsize * am.texture_coords[0],
current_display->ycenter + current_display->scrsize * am.texture_coords[1],
0, vid.fsize, s0+letter, 0x00C000, 1);
2018-01-03 00:05:03 +00:00
letter++;
}
quickqueue();
}
keyhandler = [] (int sym, int uni) {
// handlePanning(sym, uni);
dialog::handleNavigation(sym, uni);
2018-03-17 20:12:46 +00:00
if(uni == '-' && config.tstate == tsAdjusting) {
2018-01-03 00:05:03 +00:00
if(!holdmouse) {
holdmouse = true;
newmove = true;
}
}
else if(uni >= 'a' && uni < 'a' + mpMAX)
current_magic ^= 1<<(uni - 'a');
else if(uni == 'D') amp.clear();
else if(uni == 'R') applyMagic();
else if(doexiton(sym, uni))
popScreen();
};
}
string texturehelp =
2021-02-04 14:02:14 +00:00
"This mode lets you change the floor tesselation easily -- "
"select 'paint a new texture' and draw like in a Paint program. "
"The obtained pattern can then be easily changed to another geometry, "
"or saved.\n\n"
"Instead of drawing, it is also possible to use an arbitrary image "
"as a texture. "
"Works best with spherical/Euclidean/hyperbolic tesselations "
"(e.g., a photo of a soccerball, or one of the tesselations by M. C. "
"Escher), but it can be also used on arbitrary photos to make them periodic "
"(these probably work best with the 'large picture' setting in geometry selection). "
"Again, tesselations can have their geometry changed.\n\n";
#if CAP_EDIT
2019-08-09 20:07:03 +00:00
EX void start_editor() {
if(config.data.whitetexture() && config.data.loadTextureGL()) {
config.tstate = config.tstate_max = tsActive;
config.perform_mapping();
config.finish_mapping();
mapeditor::initdraw(cwt.at);
mapeditor::intexture = true;
mapeditor::drawing_tool = false;
pushScreen(mapeditor::showDrawEditor);
}
}
#endif
2019-08-09 20:07:03 +00:00
EX void showMenu() {
2017-12-14 01:53:29 +00:00
cmode = sm::SIDE | sm::MAYDARK | sm::DIALOG_STRICT_X;
gamescreen();
2018-03-17 20:12:46 +00:00
if(config.tstate == tsAdjusting) {
ptds.clear();
2018-03-17 20:12:46 +00:00
config.mark_triangles();
drawqueue();
}
2017-12-09 01:20:10 +00:00
2018-03-17 20:12:46 +00:00
if(config.tstate == tsOff) {
dialog::init(XLAT("texture mode (off)"));
2017-12-21 11:02:15 +00:00
dialog::addItem(XLAT("select geometry/pattern"), 'r');
dialog::add_action(patterns::pushChangeablePatterns);
if(config.tstate_max == tsAdjusting || config.tstate_max == tsActive) {
2017-12-19 13:35:34 +00:00
dialog::addItem(XLAT("reactivate the texture"), 't');
dialog::add_action([] {
config.tstate = config.tstate_max;
});
}
if(GDIM == 2 && !rug::rugged) {
dialog::addItem(XLAT("open PNG as texture"), 'o');
dialog::add_action([] {
dialog::openFileDialog(config.texturename, XLAT("open PNG as texture"), ".png",
[] () {
if(config.data.readtexture(config.texturename) && config.data.loadTextureGL()) {
if(config.tstate_max == tsOff) config.tstate_max = tsAdjusting;
config.tstate = config.tstate_max;
config.perform_mapping();
config.finish_mapping();
return true;
}
else return false;
});
});
}
2017-12-19 13:35:34 +00:00
dialog::addItem(XLAT("load texture config"), 'l');
dialog::add_action([] {
dialog::openFileDialog(config.configname, XLAT("load texture config"), ".txc",
[] () {
return config.load();
});
});
2018-03-17 20:12:46 +00:00
dialog::addSelItem(XLAT("texture size"), its(config.data.twidth), 'w');
dialog::add_action([] {
config.data.twidth *= 2;
if(config.data.twidth > 9000) config.data.twidth = 256;
config.tstate_max = tsOff;
});
#if CAP_EDIT
if(GDIM == 2) {
dialog::addItem(XLAT("paint a new texture"), 'n');
dialog::add_action(start_editor);
}
#endif
2018-08-27 17:38:02 +00:00
dialog::addBoolItem(XLATN("Canvas"), specialland == laCanvas, 'X');
dialog::add_action([] () {
bool inwhite = specialland == laCanvas && patterns::whichCanvas == 'g' && patterns::canvasback == 0xFFFFFF;
if(inwhite)
pushScreen(patterns::showPrePattern);
else {
stop_game();
enable_canvas();
patterns::whichCanvas = 'g';
patterns::canvasback = 0xFFFFFF;
start_game();
}
});
}
2017-12-14 01:53:29 +00:00
2018-03-17 20:12:46 +00:00
if(config.tstate == tsAdjusting) {
dialog::init(XLAT("texture mode (overlay)"));
2017-12-19 13:35:34 +00:00
dialog::addItem(XLAT("select the texture's pattern"), 'r');
dialog::add_action(patterns::pushChangeablePatterns);
2017-12-19 13:35:34 +00:00
dialog::addItem(XLAT("enable the texture"), 't');
dialog::add_action([] {
config.tstate = config.tstate_max = tsActive;
config.finish_mapping();
});
2017-12-19 13:35:34 +00:00
dialog::addItem(XLAT("cancel the texture"), 'T');
dialog::add_action([] {
config.tstate = tsOff;
config.tstate_max = tsOff;
});
dialog::addBoolItem_choice(XLAT("move the model"), panstate, tpsModel, 'm');
dialog::addBoolItem_choice(XLAT("move the texture"), panstate, tpsMove, 'a');
dialog::addBoolItem_choice(XLAT("scale/rotate the texture"), panstate, tpsScale, 'x');
dialog::addBoolItem_choice(XLAT("scale/rotate the model"), panstate, tpsZoom, 'z');
dialog::addBoolItem_choice(XLAT("projection"), panstate, tpsProjection, 'p');
dialog::addBoolItem_choice(XLAT("affine transformations"), panstate, tpsAffine, 'y');
2018-01-03 00:05:03 +00:00
dialog::addBoolItem(XLAT("magic"), false, 'A');
dialog::add_action_push(showMagicMenu);
dialog::addBreak(50);
dialog::addBoolItem_choice(XLAT("select master cells"), panstate, tpsCell, 'C');
dialog::addBoolItem_choice(XLAT("select master triangles"), panstate, tpsTriangle, 'X');
dialog::addBoolItem_choice(XLAT("fine tune vertices"), panstate, tpsTune, 'F');
2017-12-17 23:24:56 +00:00
2018-03-17 20:12:46 +00:00
dialog::addColorItem(XLAT("grid color (master)"), config.master_color, 'M');
dialog::add_action([] { dialog::openColorDialog(config.master_color, NULL); });
dialog::addColorItem(XLAT("grid color (copy)"), config.slave_color, 'K');
dialog::add_action([] { dialog::openColorDialog(config.slave_color, NULL); });
dialog::addBreak(50);
#if CAP_SHOT
2017-12-21 11:58:36 +00:00
dialog::addItem(XLAT("save the raw texture"), 'S');
dialog::add_action([] {
dialog::openFileDialog(config.texturename, XLAT("save the raw texture"), ".png",
[] () {
config.data.saveRawTexture(config.texturename); return true;
});
});
#endif
2017-12-14 01:53:29 +00:00
}
2017-12-09 01:20:10 +00:00
2018-03-17 20:12:46 +00:00
if(config.tstate == tsActive) {
dialog::init(XLAT("texture mode (active)"));
2017-12-19 13:35:34 +00:00
dialog::addItem(XLAT("deactivate the texture"), 't');
dialog::add_action([] { config.tstate = tsOff; });
dialog::addItem(XLAT("back to overlay mode"), 'T');
dialog::add_action([] {config.tstate = tsAdjusting; });
2017-12-19 13:35:34 +00:00
dialog::addItem(XLAT("change the geometry"), 'r');
dialog::add_action(patterns::pushChangeablePatterns);
2018-03-17 20:12:46 +00:00
dialog::addColorItem(XLAT("grid color"), config.grid_color, 'g');
dialog::add_action([] { dialog::openColorDialog(config.grid_color, NULL); });
2018-03-17 20:12:46 +00:00
dialog::addColorItem(XLAT("mesh color"), config.mesh_color, 'm');
dialog::add_action([] { dialog::openColorDialog(config.mesh_color, NULL); });
2018-03-17 20:12:46 +00:00
dialog::addSelItem(XLAT("color alpha"), its(config.color_alpha), 'c');
dialog::add_action([] {
dialog::editNumber(config.color_alpha, 0, 255, 15, 0, XLAT("color alpha"),
XLAT("The higher the value, the less important the color of underlying terrain is."));
});
dialog::addBoolItem_action(XLAT("aura from texture"), texture_aura, 'a');
#if CAP_EDIT
if(GDIM == 2) {
dialog::addItem(XLAT("edit the texture"), 'e');
dialog::add_action([] {
mapeditor::intexture = true;
mapeditor::drawing_tool = false;
mapeditor::initdraw(cwt.at);
pushScreen(mapeditor::showDrawEditor);
});
}
#endif
#if CAP_SHOT
2017-12-21 13:05:07 +00:00
dialog::addItem(XLAT("save the full texture image"), 'S');
dialog::add_action([] {
dialog::openFileDialog(config.texturename, XLAT("save the full texture image"), ".png",
[] () {
config.saveFullTexture(config.texturename);
return true;
});
});
#endif
2018-01-04 19:05:34 +00:00
dialog::addItem(XLAT("save texture config"), 's');
dialog::add_action([] {
dialog::openFileDialog(config.configname, XLAT("save texture config"), ".txc",
[] () {
return config.save();
});
});
2017-12-09 01:20:10 +00:00
}
dialog::addSelItem(XLAT("precision"), its(config.gsplits), 'P');
dialog::add_action([] {
dialog::editNumber(config.gsplits, 0, 4, 1, 1, XLAT("precision"),
XLAT("precision"));
if(config.tstate == tsActive) dialog::reaction = [] () { config.finish_mapping();
};
});
dialog::addHelp();
dialog::addBack();
2017-12-09 01:20:10 +00:00
2017-12-14 01:53:29 +00:00
getcstat = '-';
2017-12-09 01:20:10 +00:00
dialog::display();
if(holdmouse) mousemovement();
2017-12-14 01:53:29 +00:00
2017-12-09 01:20:10 +00:00
keyhandler = [] (int sym, int uni) {
2017-12-14 01:53:29 +00:00
// handlePanning(sym, uni);
dialog::handleNavigation(sym, uni);
2018-03-17 20:12:46 +00:00
if(uni == '-' && config.tstate == tsAdjusting) {
2017-12-14 01:53:29 +00:00
if(!holdmouse) {
holdmouse = true;
newmove = true;
}
}
else if(uni == SDLK_F1)
gotoHelp(texturehelp);
2017-12-09 01:20:10 +00:00
else if(doexiton(sym, uni))
popScreen();
};
}
2017-12-18 22:42:08 +00:00
typedef pair<int,int> point;
point ptc(shiftpoint h) {
hyperpoint inmodel = config.texture_coordinates(h);
return make_pair(int(inmodel[0] * config.data.twidth), int(inmodel[1] * config.data.twidth));
2017-12-17 23:24:56 +00:00
}
array<point, 3> ptc(const array<shiftpoint, 3>& h) {
return make_array(ptc(h[0]), ptc(h[1]), ptc(h[2]));
2017-12-18 22:42:08 +00:00
}
int texture_distance(pair<int, int> p1, pair<int, int> p2) {
2017-12-17 23:24:56 +00:00
return max(abs(p1.first-p2.first), abs(p1.second - p2.second));
}
2017-12-22 19:59:04 +00:00
void fillpixel(int x, int y, unsigned col) {
2018-03-17 20:12:46 +00:00
if(x<0 || y<0 || x >= config.data.twidth || y >= config.data.twidth) return;
auto& pix = config.data.get_texture_pixel(x, y);
2017-12-22 19:59:04 +00:00
if(pix != col) {
2018-03-17 20:12:46 +00:00
config.data.undos.emplace_back(&pix, pix);
2017-12-22 19:59:04 +00:00
pix = col;
}
}
2018-03-17 20:12:46 +00:00
void texture_data::undo() {
texture::config.data.pixels_to_draw.clear();
2017-12-22 19:59:04 +00:00
while(!undos.empty()) {
auto p = undos.back();
undos.pop_back();
if(!p.first) {
loadTextureGL();
return;
}
*p.first = p.second;
}
}
2018-03-17 20:12:46 +00:00
void texture_data::undoLock() {
2018-06-22 12:47:24 +00:00
printf("undos size = %d\n", isize(undos));
if(isize(undos) > 2000000) {
2017-12-22 19:59:04 +00:00
// limit undo memory
int moveto = 0;
2018-06-22 12:47:24 +00:00
for(int i=0; i < isize(undos) - 1000000; i++)
2017-12-22 19:59:04 +00:00
if(!undos[i].first) moveto = i;
if(moveto) {
2018-06-22 12:47:24 +00:00
for(int i=0; i+moveto < isize(undos); i++)
2017-12-22 19:59:04 +00:00
undos[i] = undos[i+moveto];
2018-06-22 12:47:24 +00:00
undos.resize(isize(undos) - moveto);
printf("undos sized to = %d\n", isize(undos));
2017-12-22 19:59:04 +00:00
}
}
undos.emplace_back(nullptr, 1);
}
void filltriangle(const array<shiftpoint, 3>& v, const array<point, 3>& p, color_t col, int lev) {
2017-12-17 23:24:56 +00:00
int d2 = texture_distance(p[0], p[1]), d1 = texture_distance(p[0], p[2]), d0 = texture_distance(p[1], p[2]);
2017-12-17 23:24:56 +00:00
2017-12-18 22:42:08 +00:00
int a, b, c;
if((d0 <= 1 && d1 <= 1 && d2 <= 1) || lev >= 20) {
2017-12-17 23:24:56 +00:00
for(int i=0; i<3; i++)
2017-12-22 19:59:04 +00:00
fillpixel(p[i].first, p[i].second, col);
2017-12-17 23:24:56 +00:00
return;
}
else if(d1 >= d0 && d1 >= d2)
2017-12-18 22:42:08 +00:00
a = 0, b = 2, c = 1;
2017-12-17 23:24:56 +00:00
else if(d2 >= d0 && d2 >= d1)
2017-12-18 22:42:08 +00:00
a = 0, b = 1, c = 2;
else
a = 1, b = 2, c = 0;
shiftpoint v3 = mid(v[a], v[b]);
2017-12-18 22:42:08 +00:00
point p3 = ptc(v3);
filltriangle(make_array(v[c], v[a], v3), make_array(p[c], p[a], p3), col, lev+1);
filltriangle(make_array(v[c], v[b], v3), make_array(p[c], p[b], p3), col, lev+1);
2017-12-18 22:42:08 +00:00
}
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) {
2017-12-18 22:42:08 +00:00
ld newangle = (angles[0] + angles[1]) / 2;
shiftpoint nh = A * xspinpush0(newangle, mapeditor::dtwidth);
2017-12-18 22:42:08 +00:00
auto np = ptc(nh);
filltriangle(make_array(h[0],h[1],nh), make_array(p[0],p[1],np), col, lev);
2017-12-18 22:42:08 +00:00
if(lev < 10) {
if(texture_distance(p[0],np) > 1)
splitseg(A, make_array(angles[0], newangle), make_array(h[0], nh), make_array(p[0], np), col, lev+1);
if(texture_distance(np,p[1]) > 1)
splitseg(A, make_array(newangle, angles[1]), make_array(nh, h[1]), make_array(np, p[1]), col, lev+1);
2017-12-18 22:42:08 +00:00
}
2017-12-17 23:24:56 +00:00
}
void fillcircle(shiftpoint h, color_t col) {
shiftmatrix A = rgpushxto0(h);
2017-12-18 22:42:08 +00:00
ld step = M_PI * 2/3;
array<shiftpoint, 3> mh = make_array(A * xpush0(mapeditor::dtwidth), A * xspinpush0(step, mapeditor::dtwidth), A * xspinpush0(-step, mapeditor::dtwidth));
2017-12-18 22:42:08 +00:00
auto mp = ptc(mh);
2017-12-17 23:24:56 +00:00
2017-12-18 22:42:08 +00:00
filltriangle(mh, mp, col, 0);
for(int i=0; i<3; i++) {
int j = (i+1) % 3;
if(texture_distance(mp[i], mp[j]) > 1)
splitseg(A, make_array(step*i, step*(i+1)), make_array(mh[i], mh[j]), make_array(mp[i], mp[j]), col, 1);
2017-12-18 22:42:08 +00:00
}
2017-12-17 23:24:56 +00:00
}
2019-08-09 20:07:03 +00:00
EX bool texturesym = false;
2017-12-22 20:57:55 +00:00
void actDrawPixel(cell *c, shiftpoint h, color_t col) {
2017-12-17 23:24:56 +00:00
try {
shiftmatrix M = gmatrix.at(c);
2017-12-17 23:24:56 +00:00
auto si = patterns::getpatterninfo0(c);
hyperpoint h1 = inverse_shift(M * applyPatterndir(c, si), h);
2018-03-17 20:12:46 +00:00
auto& tinf = config.texture_map[si.id];
2017-12-17 23:24:56 +00:00
for(auto& M2: tinf.matrices) for(int i = 0; i<c->type; i += si.symmetries) {
fillcircle(M2 * spin(2 * M_PI * i / c->type) * h1, col);
2017-12-22 20:57:55 +00:00
if(texturesym)
fillcircle(M2 * spin(2 * M_PI * i / c->type) * Mirror * h1, col);
2017-12-17 23:24:56 +00:00
}
}
catch(out_of_range&) {}
2017-12-17 23:24:56 +00:00
}
2017-12-30 16:01:53 +00:00
EX void drawPixel(cell *c, shiftpoint h, color_t col) {
2018-03-17 20:12:46 +00:00
config.data.pixels_to_draw.emplace_back(c, h, col);
2017-12-30 16:01:53 +00:00
}
2019-08-09 20:07:03 +00:00
EX cell *where;
EX void drawPixel(shiftpoint h, color_t col) {
try {
again:
shiftmatrix g0 = gmatrix[where];
ld cdist0 = hdist(tC0(g0), h);
forCellEx(c, where)
try {
shiftmatrix g = gmatrix[c];
ld cdist = hdist(tC0(g), h);
if(cdist < cdist0) {
cdist0 = cdist;
where = c; g0 = g;
goto again;
}
}
catch(out_of_range&) {}
drawPixel(where, h, col);
}
catch(out_of_range&) {}
}
EX void drawLine(shiftpoint h1, shiftpoint h2, color_t col, int steps IS(10)) {
if(steps > 0 && hdist(h1, h2) > mapeditor::dtwidth / 3) {
shiftpoint h3 = mid(h1, h2);
drawLine(h1, h3, col, steps-1);
drawLine(h3, h2, col, steps-1);
}
else
drawPixel(h2, col);
}
2018-08-20 00:04:49 +00:00
void texture_config::true_remap() {
models::configure();
drawthemap();
2019-08-15 13:05:43 +00:00
if(GDIM == 3) return;
texture_map.clear();
2018-08-20 00:04:49 +00:00
missing_cells_known.clear();
for(cell *c: dcal) {
auto si = patterns::getpatterninfo0(c);
int oldid = patterns::getpatterninfo(c, patterns::whichPattern, patterns::subpattern_flags | patterns::SPF_NO_SUBCODES).id;
2018-08-20 00:04:49 +00:00
if(texture_map.count(si.id)) continue;
int pshift = 0;
if(texture::cgroup == cpSingle) oldid = 0;
if(texture::cgroup == cpFootball && patterns::cgroup == cpThree) {
if(si.id == 4) pshift = 1;
oldid = !si.id;
}
2018-08-20 00:04:49 +00:00
try {
2018-08-20 00:04:49 +00:00
auto& mi = texture_map_orig.at(oldid);
int ncurr = isize(mi.tvertices);
int ntarget = ncurr * celltriangles(c) / mi.current_type;
vector<glvertex> new_tvertices = mi.tvertices;
new_tvertices.resize(ntarget);
for(int i=ncurr; i<ntarget; i++) {
new_tvertices[i] = new_tvertices[i - ncurr];
}
2018-08-20 00:04:49 +00:00
auto& mi2 = texture_map[si.id];
mi2 = mi;
if(GOLDBERG || IRREGULAR) pshift += si.dir;
2018-08-20 00:04:49 +00:00
mapTexture(c, mi2, si, ggmatrix(c), pshift);
mapTexture2(mi2);
mi2.tvertices = std::move(new_tvertices);
2018-08-20 00:04:49 +00:00
// printf("%08x remapping %d vertices to %d vertices\n", si.id, isize(mi.tvertices), isize(mi2.tvertices));
}
catch(out_of_range&) {
if(missing_cells_known.count(si.id) == 0) {
missing_cells_known.insert(si.id);
2018-08-02 20:25:08 +00:00
printf("Unexpected missing cell #%d/%d\n", si.id, oldid);
2018-08-20 00:04:49 +00:00
addMessage(XLAT("Unexpected missing cell #%1/%1", its(si.id), its(oldid)));
}
2018-08-20 00:04:49 +00:00
// config.tstate_max = config.tstate = tsAdjusting;
return;
}
}
2018-08-20 00:04:49 +00:00
}
void texture_config::remap() {
if(tstate == tsActive) {
if(geometry == gFake) {
/* always correct */
true_remap();
return;
}
2018-08-20 00:04:49 +00:00
patterns::computeCgroup();
correctly_mapped = patterns::compatible(texture::cgroup, patterns::cgroup);
if(!correctly_mapped)
correctly_mapped =
current_texture_parameters == config.orig_texture_parameters;
2018-08-20 00:04:49 +00:00
if(correctly_mapped) true_remap();
else addMessage(XLAT("Pattern incompatible."));
}
else if(tstate == tsAdjusting) {
2018-03-17 20:12:46 +00:00
printf("perform_mapping %d/%d\n", config.tstate, config.tstate_max);
calcparam();
models::configure();
drawthemap();
perform_mapping();
finish_mapping();
2018-06-22 12:47:24 +00:00
printf("texture_map size = %d\n", isize(texture_map));
}
}
2019-11-15 12:37:57 +00:00
#if CAP_TEXTURE
texture_data txp;
EX double get_txp(ld x, ld y, int p) {
if(txp.texture_pixels.empty()) return 0.5;
color_t col = txp.get_texture_pixel(txp.twidth * x, txp.twidth * y);
return part(col, p) / 255.;
}
#endif
2017-12-21 13:05:07 +00:00
int textureArgs() {
using namespace arg;
if(0) ;
else if(argis("-txpic")) {
2018-03-17 20:12:46 +00:00
shift(); config.texturename = args();
2017-12-21 13:05:07 +00:00
}
else if(argis("-txp")) {
shift(); config.gsplits = argi();
2017-12-21 13:05:07 +00:00
}
else if(argis("-txc")) {
2018-03-17 20:12:46 +00:00
shift(); config.configname = args();
2017-12-21 13:05:07 +00:00
}
else if(argis("-txc")) {
2018-03-17 20:12:46 +00:00
shift(); config.configname = args();
2017-12-21 13:05:07 +00:00
}
2019-11-15 12:37:57 +00:00
else if(argis("-txpsize")) {
shift(); txp.twidth = argi();
}
else if(argis("-txpf")) {
shift(); txp.readtexture(args());
}
2017-12-21 13:05:07 +00:00
else if(argis("-txcl")) {
PHASE(3); drawscreen();
2018-03-17 20:12:46 +00:00
config.load();
2017-12-21 13:05:07 +00:00
}
else return 1;
return 0;
}
auto texture_hook =
2017-12-30 16:01:53 +00:00
addHook(hooks_args, 100, textureArgs)
+ addHook(hooks_clearmemory, 100, [] () { config.data.pixels_to_draw.clear(); });
2017-12-21 13:05:07 +00:00
2017-12-30 16:01:53 +00:00
int lastupdate;
2018-03-17 20:12:46 +00:00
void texture_data::update() {
2017-12-30 16:01:53 +00:00
if(!pixels_to_draw.empty()) {
auto t = SDL_GetTicks();
while(SDL_GetTicks() < t + 75 && !pixels_to_draw.empty()) {
auto p = pixels_to_draw.back();
actDrawPixel(get<0>(p), get<1>(p), get<2>(p));
pixels_to_draw.pop_back();
}
loadTextureGL();
}
}
2017-12-21 13:05:07 +00:00
EX }
}
#endif