// Hyperbolic Rogue -- paper model generator // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file netgen.cpp * \brief paper model generator */ #include "hyper.h" #if CAP_MODEL namespace hr { EX namespace netgen { // We need a two-dimensional vector class for this. // (actually we could just use hyperpoint but this is very old...) struct vec { double x, y; vec(double _x, double _y) : x(_x), y(_y) { } vec() : x(0), y(0) {} }; vec& operator += (vec& a, const vec b) { a.x += b.x; a.y += b.y; return a; } vec& operator -= (vec& a, const vec b) { a.x -= b.x; a.y -= b.y; return a; } // coordinatewise multiplication and division vec& operator *= (vec& a, const vec b) { a.x *= b.x; a.y *= b.y; return a; } vec& operator *= (vec& a, double scalar) { a.x *= scalar; a.y *= scalar; return a; } vec& operator /= (vec& a, const vec b) { a.x /= b.x; a.y /= b.y; return a; } vec& operator /= (vec& a, double scalar) { a.x /= scalar; a.y /= scalar; return a; } vec operator + (vec a, const vec b) { return a+=b; } vec operator - (vec a, const vec b) { return a-=b; } vec operator * (vec a, const vec b) { return a*=b; } vec operator / (vec a, const vec b) { return a/=b; } vec operator * (vec a, double scalar) { return a*=scalar; } vec operator * (double scalar, vec a) { return a*=scalar; } vec operator / (vec a, double scalar) { return a/=scalar; } vec operator / (double scalar, vec a) { return a/=scalar; } vec ang(double f) { return vec(cos(f), sin(f)); } double norm(vec v) { return v.x*v.x+v.y*v.y; } // the parameters. bool loaded; int nscale, PX, PY, BASE, SX, SY, CELLS, fontsize, created; double el; #define MAXCELLS 1000 // All the datatables stored in the net files. int ct[MAXCELLS]; double vx[MAXCELLS][16]; vec center[MAXCELLS]; double rot[MAXCELLS]; int glued[MAXCELLS]; vector<int> nei[MAXCELLS]; // auxiliary data double raylen[MAXCELLS]; double edgist[MAXCELLS]; vector<char> patek[MAXCELLS]; // data generated by HyperRogue vector<hyperpoint> hcenter[MAXCELLS]; // Functions handling the data. //============================== // Use HyperRogue to generate the data (ct, vx, nei). EX int mode = 0; EX void buildVertexInfo(cell *c, transmatrix V) { if(mode == 1) for(int ii=0; ii<CELLS; ii++) if(dcal[ii] == c) { hcenter[ii].resize(c->type+1); hcenter[ii][c->type] = V * C0; if(c->type == S7) { for(int i=0; i<c->type; i++) { transmatrix V2 = V * ddspin(c, i, M_PI/S7) * xpush(cgi.hexf); hcenter[ii][i] = V2 * C0; } } if(c->type == S6) { for(int i=0; i<c->type; i++) { transmatrix V2 = V * ddspin(c, i, 0) * xpush(cgi.crossf) * spin(M_PI+M_PI/S7) * xpush(cgi.hexf); hcenter[ii][i] = V2 * C0; } } } } void dataFromHR() { mode = 1; drawthemap(); mode = 0; for(int i=0; i<CELLS; i++) { ct[i] = dcal[i]->type; for(int k=0; k<=ct[i]; k++) vx[i][2*k] = hcenter[i][k][0], vx[i][2*k+1] = hcenter[i][k][1]; nei[i].clear(); nei[i].resize(ct[i], -1); for(int j=0; j<CELLS; j++) { cell *c1 = dcal[i]; cell *c2 = dcal[j]; for(int k=0; k<c1->type; k++) if(c1->move(k) == c2) nei[i][k] = j; } } for(int i=0; i<CELLS; i++) { center[i] = vec(SX/2, SY/2); rot[i] = 0; glued[i] = -1; for(int e=0; e<ct[i]; e++) if(nei[i][e] < i && nei[i][e] != -1 && (glued[i] == -1 || nei[i][e] < glued[i])) { glued[i] = nei[i][e]; } } } void loadData() { fhstream f("papermodeldata.txt", "rt"); if(!f.f) return; if(!scan(f, CELLS, SX, SY, PX, PY, nscale, BASE, el, created)) return; loaded = true; if(!created) return; for(int i=0; i<CELLS; i++) scan(f, ct[i]); for(int i=0; i<CELLS; i++) nei[i].clear(), nei[i].resize(ct[i], -1); for(int i=0; i<CELLS; i++) for(int j=0; j<16; j++) scan(f, vx[i][j]); while(true) { int a, b, c; scan(f, a, b, c); if(a < 0) break; else nei[a][c] = b; } for(int i=0; i<CELLS; i++) { double dx, dy, dr; int g; scan(f, dx, dy, dr, g); center[i] = vec(dx, dy); rot[i] = dr; glued[i] = g; } } void saveData() { // global parameters fhstream f("papermodeldata2.txt", "wt"); if(!f.f) { addMessage("Could not save the paper model data"); return; } println(f, spaced(CELLS, SX, SY, PX, PY, nscale, BASE, el, created), "\n"); // net parameters: cell types println(f, spaced_of(ct, CELLS)); // net parameters: hcenters for(int i=0; i<CELLS; i++) { println(f, spaced_of(vx[i], 16)); } println(f, "\n"); // create netgen for(int i=0; i<CELLS; i++) for(int j=0; j<CELLS; j++) { for(int k=0; k<ct[i]; k++) if(nei[i][k] == j) print(f, spaced(i, j, k), " "); } println(f, "-1 -1 -1"); // graphics for(int i=0; i<CELLS; i++) println(f, spaced(center[i].x, center[i].y, rot[i], glued[i])); } // Simple graphical functions //============================ color_t argb(color_t c) { return ((c & 0xFFFFFF) >> 8) | ((c & 0xFF) << 24); } void blackline(vec v1, vec v2, color_t col = 0x000000FF) { #if CAP_SDLGFX aalineColor(srend, int(v1.x), int(v1.y), int(v2.x), int(v2.y), align(col)); #elif CAP_SDL SDL_LockSurface(s); int len = abs(v1.x-v2.x) + abs(v1.y-v2.y); for(int i=0; i<=len; i++) qpixel(s, int(v1.x + (v2.x-v1.x)*i/len), int(v1.y + (v2.y-v1.y)*i/len)) = argb(col); SDL_UnlockSurface(s); #endif } void drawtriangle(vec v1, vec v2, vec v3, color_t col) { #if CAP_SDLGFX polyx[0] = int(v1.x); polyx[1] = int(v2.x); polyx[2] = int(v3.x); polyy[0] = int(v1.y); polyy[1] = int(v2.y); polyy[2] = int(v3.y); filledPolygonColorI(srend, polyx, polyy, 3, col); #elif CAP_SDL SDL_LockSurface(s); int len = abs(v1.x-v2.x) + abs(v1.y-v2.y); for(int i=0; i<=len; i++) for(int j=0; j<=len; j++) if(i+j <= len) qpixel(s, int(v3.x + (v2.x-v3.x)*i/len + (v1.x-v3.x)*j/len), int(v3.y + (v2.y-v3.y)*i/len + (v1.y-v3.y)*j/len)) = argb(col); SDL_UnlockSurface(s); #endif } void blackcircle(vec v, int r, color_t col = 0x000000FF) { #if CAP_SDLGFX aacircleColor(srend, int(v.x), int(v.y), r, col); #endif } void blacktext(vec v, char c) { char str[2]; str[0] = c; str[1] = 0; int tsize = int(el * 12/27); displaystr(int(v.x), int(v.y), 0, tsize, str, 0, 8); } hyperpoint hvec(int i, int e) { return hpxy(vx[i][2*e], vx[i][2*e+1]); } bool wellspread(double d1, double d2, double d3, int &co) { int id1 = int(d1); int id2 = int(d2); int id3 = int(d3); co = min(min(id1,id2),id3); return (id1 <= co+1 && id2 <= co+1 && id3 <= co+1); } SDL_Surface *net, *hqsurface; color_t& hqpixel(hyperpoint h) { int hx, hy, hs; getcoord0(shiftless(h), hx, hy, hs); return qpixel(hqsurface, hx, hy); } void copyhypertriangle( vec g1, vec g2, vec g3, hyperpoint h1, hyperpoint h2, hyperpoint h3) { int ix, iy; if(wellspread(g1.x,g2.x,g3.x,ix) && wellspread(g1.y,g2.y,g3.y,iy)) qpixel(net,ix,iy) = hqpixel(h1); else { vec g4 = (g2+g3)/2; vec g5 = (g3+g1)/2; vec g6 = (g1+g2)/2; hyperpoint h4 = mid(h2,h3); hyperpoint h5 = mid(h3,h1); hyperpoint h6 = mid(h1,h2); copyhypertriangle(g1,g5,g6, h1,h5,h6); copyhypertriangle(g5,g3,g4, h5,h3,h4); copyhypertriangle(g6,g4,g2, h6,h4,h2); copyhypertriangle(g4,g6,g5, h4,h6,h5); } } void setRaylen() { for(int i=0; i<CELLS; i++) { raylen[i] = el / sin(M_PI / ct[i]); edgist[i] = raylen[i] * cos(M_PI / ct[i]); } } // draw the model void createPapermodel() { loadData(); renderbuffer rbuf(2000, 2000, vid.usingGL); dynamicval<videopar> dv(vid, vid); vid.xres = vid.yres = 2000; pconf.scale = 0.99; if(1) { resetbuffer rb; calcparam(); models::configure(); mode = 2; darken = 0; rbuf.enable(); current_display->set_viewport(0); drawfullmap(); hqsurface = rbuf.render(); IMAGESAVE(hqsurface, "test.png"); rb.reset(); } vid.usingGL = false; mode = 0; /* for(int i=0; i<CELLS; i++) { int t = ct[i]; for(int e=0; e<t; e++) drawline(hvec(i,e), hvec(i,(e+1)%t), 0x80808080); for(int e=0; e<7; e++) drawline(hvec(i,e), hvec(i,7), 0x80808080); } */ dynamicval<SDL_Surface*> ds(s); s = net = SDL_CreateRGBSurface(SDL_SWSURFACE,SX*nscale,SY*nscale,32,0,0,0,0); #if CAP_SDL2 dynamicval<SDL_Renderer*> dr(srend); srend = SDL_CreateSoftwareRenderer(s); #endif SDL_FillRect(net, NULL, 0xFFFFFF); int pateks = 0; for(int i=0; i<CELLS; i++) patek[i].resize(ct[i]); int zeroi = nei[0][0]; int zeroe = 0; for(int e=0; e<6; e++) if(nei[zeroi][e] == 0) zeroe = e; el *= nscale; setRaylen(); for(int faza=0; faza<2; faza++) for(int i=0; i<CELLS; i++) { int t = ct[i]; printf("faza %d cell %d\n", faza, i); for(int e=0; e<t; e++) { vec v1 = center[i] * nscale + raylen[i] * ang(rot[i] + 2*M_PI*e/t); vec v2 = center[i] * nscale + raylen[i] * ang(rot[i] + 2*M_PI*(e+1)/t); vec v3 = (v1+v2)/2; if(faza == 1) blackline(v1, v2); int ofs = t == 7 ? 0 : 5; // 0,2,0 ~ 2,0,0 if(0) if((i==0 && e == 0) || (i == zeroi && e == zeroe)) { for(int ofs=0; ofs<t; ofs++) { println(hlog, "OFS ", ofs, hvec(i, (e+ofs)%t), hvec(i, (e+1+ofs)%t)); } } if(faza == 0) copyhypertriangle( center[i] * nscale, v1, v2, hvec(i,7), hvec(i, (e+ofs)%t), hvec(i, (e+1+ofs)%t) ); if(faza == 1) if(nei[i][e] != -1 && nei[i][e] != glued[i] && glued[nei[i][e]] != i) { vec vd = v2-v1; swap(vd.x, vd.y); vd.x = -vd.x; double factor = -sqrt(3)/6; vd.x *= factor; vd.y *= factor; vec v4 = v3 + vd; vec v5 = v3 + vd/2; if(!patek[i][e]) { int i2 = nei[i][e]; for(int e2=0; e2<ct[nei[i][e]]; e2++) if(nei[i2][e2] == i) patek[i][e] = patek[i2][e2] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" "!@#$%^&*+=~:;<>?/|\"., [{(\\]})" [(pateks++) % 85]; } color_t col = 0xFFFFFFFF; int p = patek[i][e]; col -= 0x8000 * (p&1); p /= 2; col -= 0x800000 * (p&1); p /= 2; col -= 0x80000000 * (p&1); p /= 2; col -= 0x4000 * (p&1); p /= 2; col -= 0x400000 * (p&1); p /= 2; col -= 0x40000000 * (p&1); p /= 2; col -= 0x2000 * (p&1); p /= 2; col -= 0x200000 * (p&1); p /= 2; col -= 0x20000000 * (p&1); p /= 2; drawtriangle(v1,v2,v4, col); blacktext(v5, patek[i][e]); blackline(v1, v4); blackline(v2, v4); } } } println(hlog, "pateks = ", pateks); IMAGESAVE(net, "papermodel-all" IMAGEEXT); IMAGESAVE(hqsurface, "papermodel-source" IMAGEEXT); int qx = SX*nscale/PX; int qy = SY*nscale/PY; SDL_Surface *quarter = SDL_CreateRGBSurface(SDL_SWSURFACE,qx,qy,32,0,0,0,0); for(int iy=0; iy<PY; iy++) for(int ix=0; ix<PX; ix++) { for(int y=0; y<qy; y++) for(int x=0; x<qx; x++) qpixel(quarter,x,y) = qpixel(net, x+qx*ix, y+qy*iy); char buf[64]; sprintf(buf, "papermodel-page%d%d" IMAGEEXT, iy, ix); IMAGESAVE(quarter, buf); } SDL_FreeSurface(net); SDL_FreeSurface(quarter); #if CAP_SDL2 SDL_DestroyRenderer(srend); #endif } vec mousepos, rel; int bei = 0, bee = 0, whichcell = 0; double cedist; bool dragging = false; int glueroot(int i) { if(glued[i] == -1) return i; return glueroot(glued[i]); } void clicked(int x, int y, int b) { mousepos = vec(x, y); if(b == 1) rel = center[glueroot(whichcell)] - mousepos, dragging = true; if(b == 17) dragging = false; if(b == 32 && dragging) center[glueroot(whichcell)] = rel + mousepos; } void applyGlue(int i) { int j = glued[i]; int it = ct[i]; int jt = ct[j]; int ie = 0, je = 0; for(int e=0; e<it; e++) if(nei[i][e] == j) ie = e; for(int e=0; e<jt; e++) if(nei[j][e] == i) je = e; rot[i] = rot[j] + 2*M_PI*(je+.5)/jt - 2*M_PI*(ie+.5)/it + M_PI; center[i] = center[j] + (edgist[i]+edgist[j]) * ang(rot[j] + 2*M_PI*(je+.5)/jt); } shiftpoint vec_to_p(vec v) { return shiftless(hyperpoint(v.x - current_display->xcenter, v.y - current_display->ycenter, 0, 1)); } void netline(vec a, vec b, color_t col) { if(vid.usingGL) queueline(vec_to_p(a), vec_to_p(b), col, 0); else blackline(a, b, col); } void netcircle(vec ctr, int rad, color_t col) { if(vid.usingGL) queuecircle(ctr.x, ctr.y, rad, col); else blackcircle(ctr, rad, col); } void displaynets() { if(!vid.usingGL) SDL_LockSurface(s); setRaylen(); if(vid.usingGL) { calcparam(); setGLProjection(); glhr::set_depthtest(false); current_display->set_all(0,0); } else { for(int uy=SY-1; uy>=0; uy--) for(int ux=SX-1; ux>=0; ux--) { qpixel(s, ux, uy) = 0; } initquickqueue(); } for(int y=1; y<PY; y++) netline(vec(0,SY*y/PY), vec(SX,SY*y/PY), 0x404080FF); for(int x=1; x<PX; x++) netline(vec(SX*x/PX,0), vec(SX*x/PX,SY), 0x404080FF); for(int i=0; i<CELLS; i++) { if(norm(center[i]-mousepos) < norm(center[whichcell]-mousepos)) whichcell = i; int t = ct[i]; if(i == whichcell) netcircle(center[i], 10, 0x40FF40FF); if(i == bei || i == nei[bei][bee]) netcircle(center[i], 5, 0x40FF40FF); if(glued[i] == -1) netcircle(center[i], 7, 0xFF4040FF); if(glued[i] != -1) applyGlue(i); for(int e=0; e<t; e++) { vec v1 = center[i] + raylen[i] * ang(rot[i] + 2*M_PI*e/t); vec v2 = center[i] + raylen[i] * ang(rot[i] + 2*M_PI*(e+1)/t); vec v3 = (v1+v2)/2; if(nei[i][e] >= 0 && !dragging) { if(norm(v3-mousepos) < cedist) bei = i, bee = e; if(i == bei && e == bee) cedist = norm(v3-mousepos); } color_t col = i == bei && e == bee ? 0x40FF40FF: i == nei[bei][bee] && nei[i][e] == bei ? 0x40FF40FF : nei[i][e] == glued[i] ? 0x303030FF : glued[nei[i][e]] == i ? 0x303030FF : nei[i][e] >= 0 ? 0xC0C0C0FF : 0x808080FF; netline(v1, v2, col); if(nei[i][e] != -1 && nei[i][e] != glued[i] && glued[nei[i][e]] != i) { vec vd = v2-v1; swap(vd.x, vd.y); vd.x = -vd.x; double factor = -sqrt(3)/6; vd.x *= factor; vd.y *= factor; vec v4 = v3 + vd; netline(v1, v4, 0xFFC0C0C0); netline(v2, v4, 0xFFC0C0C0); } } } if(!vid.usingGL) SDL_UnlockSurface(s); else quickqueue(); present_screen(); } double rs, rz; void addglue() { int i = bei; int j = nei[bei][bee]; if(glued[i] == j) glued[i] = -1; else if(glued[j] == i) glued[j] = -1; else if(glueroot(i) == glueroot(j)) ; else if(glued[j] == -1) glued[j] = i; } int nti; void smooth() { int ti = SDL_GetTicks(); rot[whichcell] += rs * (nti - ti) / 1000.0; el += rz * (nti - ti) / 1000.0; nti = ti; } void netgen_loop() { nti = SDL_GetTicks(); while(true) { smooth(); displaynets(); SDL_Event event; while(SDL_PollEvent(&event)) switch (event.type) { case SDL_QUIT: exit(1); return; case SDL_MOUSEBUTTONDOWN: { clicked(event.button.x, event.button.y, event.button.button); break; } case SDL_MOUSEBUTTONUP: { clicked(event.button.x, event.button.y, 16+event.button.button); break; } case SDL_MOUSEMOTION: { clicked(event.motion.x, event.motion.y, 32); break; } case SDL_KEYDOWN: { int key = event.key.keysym.sym; #if CAP_SDL2 int uni = key; #else int uni = event.key.keysym.unicode; #endif if(uni == 'q' || key == SDLK_ESCAPE || key == SDLK_F10) return; if(key == SDLK_PAGEUP) rs = 3; if(key == SDLK_PAGEDOWN) rs = -3; if(uni == 'z') rz = 1; if(uni == 'x') rz = -1; if(uni == 'g') addglue(); break; } case SDL_KEYUP: { rs = 0; rz = 0; break; } } } } void designNet() { if(1) { dynamicval<int> dwx(vid.window_x, SX); dynamicval<int> dwy(vid.window_y, SY); dynamicval<int> dfx(vid.fullscreen_x, SX); dynamicval<int> dfy(vid.fullscreen_y, SY); dynamicval<bool> dr(resizable, false); dynamicval<bool> dws(vid.relative_window_size, false); dynamicval<bool> dfs(vid.change_fullscr, true); dynamicval<bool> dcf(vid.want_fullscreen, false); dynamicval<eModel> m(pmodel, mdPixel); request_resolution_change = true; apply_screen_settings(); netgen_loop(); saveData(); setvideomode(); } apply_screen_settings(); } void show() { cmode = sm::SIDE; gamescreen(); if(true) { initquickqueue(); for(int i=0; i<CELLS; i++) { int t = ct[i]; int ofs = t == 7 ? 0 : 5; for(int e=0; e<t; e++) { color_t col = nei[i][e] == glued[i] && glued[i] >= 0 ? 0x303030 : nei[i][e] >= 0 && glued[nei[i][e]] == i ? 0x303030 : nei[i][e] >= 0 ? 0x808080 : 0xC0C0C0; queueline(shiftless(hvec(i, (e+ofs)%t)), shiftless(hvec(i, (e+1+ofs)%t)), (col << 8) + 0xFF, 3); } } quickqueue(); } if(mode != 2) { dialog::init("paper model creator"); dialog::addItem(XLAT("synchronize net and map"), 's'); dialog::addItem(XLAT("display the scope"), 't'); dialog::addItem(XLAT("create the model"), 'c'); dialog::addItem(XLAT("design the net"), 'd'); dialog::addBreak(50); dialog::addBack(); dialog::display(); } keyhandler = [] (int sym, int uni) { dialog::handleNavigation(sym, uni); if(!loaded) { loadData(); if(!loaded) { addMessage(XLAT("Failed to load the file 'papermodeldata.txt'")); popScreen(); return; } if(!created) { View = Id; playermoved = false; dataFromHR(); designNet(); created = 1; return; } } if(mode == 2 && uni != 0) { mode = 0; return; } if(uni == 's') { View = Id; if(ctof(centerover)) View = spin(2 * M_PI * (rand() % 7) / 7) * View; playermoved = false; } else if(uni == 'c') { createPapermodel(); addMessage(XLAT("The paper model created as papermodel-*.bmp")); } else if(uni == 'd') designNet(); else if(uni == 't') mode = 2; else if(doexiton(sym, uni)) popScreen(); }; } EX void run() { if(euclid) addMessage("Useless in Euclidean geometry."); else if(sphere) addMessage("Not implemented for spherical geometry. Please tell me if you really want this."); else pushScreen(show); } EX } } #endif