// Hyperbolic Rogue // Copyright (C) 2011-2016 Zeno Rogue, see 'hyper.cpp' for details // implementation of the Hypersian Rug mode namespace hr { #if CAP_RUG #define TEXTURESIZE (texturesize) #define HTEXTURESIZE (texturesize/2) bool rug_failure = false; namespace rug { ld lwidth = 2; bool in_crystal() { return surface::sh == surface::dsCrystal; } bool computed = false; vector points; vector triangles; int when_enabled; struct rug_exception { }; bool fast_euclidean = true; bool good_shape; bool subdivide_first = false; bool spatial_rug = false; bool subdivide_further(); void subdivide(); ld modelscale = 1; ld model_distance = 4; eGeometry gwhere = gEuclid; #define USING_NATIVE_GEOMETRY dynamicval gw(geometry, gwhere == gElliptic ? gSphere : gwhere) // hypersian rug datatypes and globals //------------------------------------- bool rugged = false; bool genrug = false; int vertex_limit = 20000; bool renderonce = false; int renderlate = 0; bool rendernogl = false; int texturesize = 1024; ld scale = 1; ld ruggo = 0; ld anticusp_factor = 1; ld anticusp_dist; ld err_zero = 1e-3, err_zero_current, current_total_error; int queueiter, qvalid, dt; rugpoint *finger_center; ld finger_range = .1; ld finger_force = 1; int rugdim; bool rug_perspective = ISANDROID; // extra geometry functions //-------------------------- // returns a matrix M // such that inverse(M) * h1 = ( |h1|, 0, 0) and inverse(M) * h2 = ( .., .., 0) transmatrix orthonormalize(hyperpoint h1, hyperpoint h2) { using namespace hyperpoint_vec; hyperpoint vec[3] = {h1, h2, h1 ^ h2}; for(int i=0; i<3; i++) { for(int j=0; j1e-4) { println(hlog, "Error: h[2] = ", h[2]); rug_failure = true; } if(euclid) { h[2] = 1; return h; } ld d = hypot(h[0], h[1]); if(d == 0) { h[2] = 1; return h; } if(sphere) { ld d0 = d ? d : 1; h[0] = sin(d) * h[0]/d0; h[1] = sin(d) * h[1]/d0; h[2] = cos(d); } else { ld d0 = d ? d : 1; h[0] = sinh(d) * h[0]/d0; h[1] = sinh(d) * h[1]/d0; h[2] = cosh(d); } return h; } hyperpoint hyperboloid_to_azeq(hyperpoint h) { if(euclid) { h[2] = 0; return h; } else { ld d = hdist0(h); if(d == 0) { h[2] = 0; return h; } ld d2 = hypot_d(2, h); if(d2 == 0) { h[2] = 0; return h; } h[0] = d * h[0] / d2; h[1] = d * h[1] / d2; h[2] = 0; return h; } } struct normalizer { transmatrix M, Mi; dynamicval gw; normalizer (hyperpoint h1, hyperpoint h2) : gw(geometry, gwhere == gElliptic ? gSphere : gwhere) { M = orthonormalize(h1, h2); Mi = inverse(M); } hyperpoint operator() (hyperpoint h) { return azeq_to_hyperboloid(Mi*h); } hyperpoint operator[] (hyperpoint h) { return M*hyperboloid_to_azeq(h); } }; void push_point(hyperpoint& h, int coord, ld val) { if(fast_euclidean && gwhere == gEuclid) h[coord] += val; else if(!val) return; else { // if(zero_d(3, h)) { h[0] = 1e-9; h[1] = 1e-10; h[2] = 1e-11; } normalizer n(hpxyz(coord==0,coord==1,coord==2), h); hyperpoint f = n(h); h = n[xpush(val) * f]; } } void push_all_points(int coord, ld val) { if(!val) return; else for(int i=0; iflat, coord, val); } // construct the graph //--------------------- int hyprand; rugpoint *addRugpoint(hyperpoint h, double dist) { rugpoint *m = new rugpoint; m->h = h; /* ld tz = vid.alpha+h[2]; m->x1 = (1 + h[0] / tz) / 2; m->y1 = (1 + h[1] / tz) / 2; */ hyperpoint onscreen; applymodel(m->h, onscreen); m->x1 = (1 + onscreen[0] * vid.scale) / 2; m->y1 = (1 - onscreen[1] * vid.scale) / 2; m->valid = false; using namespace hyperpoint_vec; if(euwrap && !bounded) { hyperpoint h1 = eumove(torusconfig::sdx, torusconfig::sdy) * C0; h1 /= sqhypot_d(2, h1); if(nonorientable) h1 /= 2; m->valid = good_shape = true; ld d = h1[0] * h[1] - h1[1] * h[0]; ld a = h[0] * h1[0] + h[1] * h1[1]; // m->flat = modelscale * hpxyz(d * 2 * M_PI, sin(a * 2 * M_PI), cos(a * 2 * M_PI)); USING_NATIVE_GEOMETRY; hyperpoint hpoint = ypush(modelscale) * xpush0(modelscale * d * 2 * M_PI); ld hpdist = hdist0(hpoint); ld z = hypot_d(2, hpoint); if(z==0) z = 1; hpoint = hpoint * hpdist / z; m->flat = hpxyz(hpoint[0], hpoint[1] * sin(a*2*M_PI), hpoint[1]*cos(a*2*M_PI)); } else if(sphere) { m->valid = good_shape = true; ld scale; if(gwhere == gEuclid) { scale = modelscale; } else if(gwhere == gNormal) { // sinh(scale) = modelscale scale = asinh(modelscale); } else /* sphere/elliptic*/ { if(modelscale >= 1) // do as good as we can... scale = M_PI / 2 - 1e-3, good_shape = false, m->valid = false; else scale = asin(modelscale); } m->flat = h * scale; } else if(euclid && gwhere == gEuclid) { m->flat = h * modelscale; m->valid = good_shape = true; } else if(gwhere == gNormal && (euclid || (hyperbolic && modelscale >= 1))) { m->valid = good_shape = true; ld d = hdist0(h); ld d0 = hypot_d(2, h); if(!d0) d0 = 1; hyperpoint hpoint; bool orig_euclid = euclid; USING_NATIVE_GEOMETRY; if(orig_euclid) { d *= modelscale; // point on a horocycle going through C0, in distance d along the horocycle hpoint = hpxy(d*d/2, d); } else { // radius of the equidistant ld r = acosh(modelscale); // point on an equdistant going through C0 in distance d along the guiding line // hpoint = hpxy(cosh(r) * sinh(r) * (cosh(d) - 1), sinh(d) * cosh(r)); hpoint = xpush(r) * ypush(d) * xpush0(-r); hpoint[0] = -hpoint[0]; } ld hpdist = hdist0(hpoint); ld z = hypot_d(2, hpoint); if(z==0) z = 1; m->flat = hpxyz(hpdist * h[0]/d0 * hpoint[1] / z, hpdist * h[1]/d0 * hpoint[1] / z, -hpdist * hpoint[0] / z); } else { m->flat = h; ld hd = h[DIM]; for(int d=DIM; dflat[d] = (hd - .99) * (rand() % 1000 - rand() % 1000) / 1000; } if(rug_perspective) push_point(m->flat, 2, -model_distance); // if(rug_perspective && gwhere == gEuclid) m->flat[2] -= 3; m->inqueue = false; m->dist = dist; points.push_back(m); return m; } rugpoint *findRugpoint(hyperpoint h) { using namespace hyperpoint_vec; for(int i=0; ih - h) < 1e-5) return points[i]; return NULL; } rugpoint *findOrAddRugpoint(hyperpoint h, double dist) { rugpoint *r = findRugpoint(h); return r ? r : addRugpoint(h, dist); } void addNewEdge(rugpoint *e1, rugpoint *e2, ld len = 1) { edge e; e.len = len; e.target = e2; e1->edges.push_back(e); e.target = e1; e2->edges.push_back(e); } bool edge_exists(rugpoint *e1, rugpoint *e2) { for(auto& e: e1->edges) if(e.target == e2) return true; return false; } void addEdge(rugpoint *e1, rugpoint *e2, ld len = 1) { if(!edge_exists(e1, e2)) addNewEdge(e1, e2, len); } void add_anticusp_edge(rugpoint *e1, rugpoint *e2, ld len = 1) { for(auto& e: e1->anticusp_edges) if(e.target == e2) return; edge e; e.len = len; e.target = e2; e1->anticusp_edges.push_back(e); e.target = e1; e2->anticusp_edges.push_back(e); } void addTriangle(rugpoint *t1, rugpoint *t2, rugpoint *t3, ld len) { addEdge(t1->getglue(), t2->getglue(), len); addEdge(t2->getglue(), t3->getglue(), len); addEdge(t3->getglue(), t1->getglue(), len); triangles.push_back(triangle(t1,t2,t3)); } map, rugpoint*> halves; rugpoint* findhalf(rugpoint *r1, rugpoint *r2) { if(r1 > r2) swap(r1, r2); return halves[make_pair(r1,r2)]; } void addTriangle1(rugpoint *t1, rugpoint *t2, rugpoint *t3) { rugpoint *t12 = findhalf(t1, t2); rugpoint *t23 = findhalf(t2, t3); rugpoint *t31 = findhalf(t3, t1); addTriangle(t1, t12, t31); addTriangle(t12, t2, t23); addTriangle(t23, t3, t31); addTriangle(t23, t31, t12); } bool psort(rugpoint *a, rugpoint *b) { return hdist0(a->h) < hdist0(b->h); } void sort_rug_points() { sort(points.begin(), points.end(), psort); } void calcLengths() { for(auto p: points) for(auto& edge: p->edges) edge.len = hdist(p->h, edge.target->h) * modelscale; } void calcparam_rug() { auto cd = current_display; cd->xtop = cd->ytop = 0; cd->xsize = cd->ysize = TEXTURESIZE; cd->xcenter = cd->ycenter = cd->scrsize = HTEXTURESIZE; cd->radius = cd->scrsize * vid.scale; } void buildTorusRug() { using namespace torusconfig; calcparam_rug(); conformal::configure(); struct toruspoint { int x,y; toruspoint() { x=y=getqty(); } toruspoint(int _x, int _y) : x(_x), y(_y) {} int d2() { return x*x+(euclid6?x*y:0)+y*y; } }; vector zeropoints; vector tps(qty); auto& mode = tmodes[torus_mode]; bool single = mode.flags & TF_SINGLE; bool klein = mode.flags & TF_KLEIN; pair solution; if(single) { for(int ax=-qty; ax tp.d2()) tps[v] = tp; if(v == 0) zeropoints.emplace_back(ax, ay); } ld bestsol = 1e12; for(auto p1: zeropoints) for(auto p2: zeropoints) { int det = p1.x * p2.y - p2.x * p1.y; if(det < 0) continue; if(det != qty && det != -qty) continue; ld quality = ld(p1.d2()) * p2.d2(); if(quality < bestsol * 3) if(quality < bestsol) bestsol = quality, solution.first = p1, solution.second = p2; } if(solution.first.d2() > solution.second.d2()) swap(solution.first, solution.second); } else { if(klein) solution.first = toruspoint(2*sdx, 0); else solution.first = toruspoint(sdx, 0); if(mode.flags & TF_WEIRD) solution.second = toruspoint(sdy/2, sdy); else solution.second = toruspoint(0, sdy); if(solution.first.d2() > solution.second.d2()) swap(solution.first, solution.second); } ld factor = sqrt(ld(solution.second.d2()) / solution.first.d2()); ld xfactor = 0, yfactor = 0; println(hlog, "factor = ", factor); if(factor <= 2.05) factor = 2.2; factor -= 1; // 22,1 // 7,-17 // transmatrix z1 = {{{22,7,0}, {1,-17,0}, {0,0,1}}}; transmatrix z1 = {{{(ld)solution.first.x,(ld)solution.second.x,0}, {(ld)solution.first.y,(ld)solution.second.y,0}, {0,0,1}}}; transmatrix z2 = inverse(z1); if(gwhere == gSphere) { hyperpoint xh = z2 * hpxyz(1, 0, 0); hyperpoint yh = z2 * hpxyz(0, 1, 0); // hypot(xh[0], factor * xh[1]) == hypot(yh[0], factor * yh[1]) // xh[0]*xh[0] - yh[0] * yh[0] = factor * factor * (yh[1] * yh[1] - (xh[1] * xh[1]) ld factor2 = (xh[0]*xh[0] - yh[0] * yh[0]) / (yh[1] * yh[1] - xh[1] * xh[1]); ld factor = sqrt(factor2); xfactor = sqrt(1/(1+factor2)); yfactor = xfactor * factor; ld xscale = hypot(xfactor * xh[0] * 2 * M_PI, yfactor * xh[1] * 2 * M_PI); ld yscale = hypot(xfactor * yh[0] * 2 * M_PI, yfactor * yh[1] * 2 * M_PI); println(hlog, "xh = ", xh); println(hlog, "yh = ", yh); println(hlog, "factor = ", make_tuple(xfactor, yfactor, factor)); println(hlog, "scales = ", make_tuple(xscale, yscale)); modelscale = xscale / crossf; } map, rugpoint*> glues; auto addToruspoint = [&] (ld x, ld y) { auto r = addRugpoint(C0, 0); hyperpoint onscreen; applymodel(tC0(eumove(x, y)), onscreen); // take point (1,0) // apply eumove(1,0) // multiply by current_display->radius (= HTEXTURESIZE * rugzoom) // add 1, divide by texturesize r->x1 = onscreen[0]; r->y1 = onscreen[1]; hyperpoint h1 = hpxyz(x, y, 0); hyperpoint h2 = z2 * h1; double alpha = -h2[0] * 2 * M_PI; double beta = -h2[1] * 2 * M_PI; // r->flat = {alpha, beta, 0}; double sc = (factor+1)/4; if(gwhere == gSphere) { ld ax = alpha + 1.124651, bx = beta + 1.214893; ld x = xfactor * sin(ax), y = xfactor * cos(ax), z = yfactor * sin(bx), t = yfactor * cos(bx); ld d = acos(t) / sqrt(x*x+y*y+z*z); r->flat = r->h = hpxyz(x * d, y * d, z * d); } else r->flat = r->h = hpxyz((factor+cos(alpha)) * cos(beta) * sc, (factor+cos(alpha)) * sin(beta) * sc, -sin(alpha) * sc); r->valid = true; static const int X = 100003; // a prime auto gluefun = [] (ld z) { return int(frac(z + .5/X) * X); }; auto p = make_pair(gluefun(h2[0]), gluefun(h2[1])); auto& r2 = glues[p]; if(r2) r->glueto(r2); else r2 = r; return r; }; int rugmax = (int) sqrt(vertex_limit / qty); if(rugmax < 1) rugmax = 1; if(rugmax > 16) rugmax = 16; ld rmd = rugmax; for(int leaf=0; leaf<(klein ? 2 : 1); leaf++) for(int i=0; i sdx/2) x -= sdx; if(y > sdy/2) y -= sdy; if(leaf) { x += sdx; if(x > sdx) x -= 2 * sdx; } } rugpoint *rugarr[32][32]; for(int yy=0; yy<=rugmax; yy++) for(int xx=0; xx<=rugmax; xx++) rugarr[yy][xx] = addToruspoint(x+xx/rmd, y+(yy-xx)/rmd); for(int yy=0; yyx1), abs(p->y1))); // maxz * rugzoom * current_display->radius == current_display->radius vid.scale = 1 / maxz; for(auto p: points) p->x1 = (current_display->xcenter + current_display->radius * vid.scale * p->x1)/ vid.xres, p->y1 = (current_display->ycenter - current_display->radius * vid.scale * p->y1)/ vid.yres; qvalid = 0; for(auto p: points) if(!p->glue) qvalid++; println(hlog, "qvalid = ", qvalid); if(rug_perspective) push_all_points(2, -model_distance); return; } void verify() { vector ratios; for(auto m: points) for(auto& e: m->edges) { auto m2 = e.target; ld l = e.len; ld l0; if(fast_euclidean) l0 = sqdhypot_d(rugdim, m->flat, m2->flat); else { normalizer n(m->flat, m2->flat); hyperpoint h1 = n(m->flat); hyperpoint h2 = n(m2->flat); l0 = hdist(h1, h2); } ratios.push_back(l0 / l); } println(hlog, "Length verification:"); sort(ratios.begin(), ratios.end()); for(int i=0; icpdist, mc = minimum->cpdist; if(tie(nc, next) < tie(mc, minimum)) minimum = next; } void buildRug() { need_mouseh = true; good_shape = false; if(fulltorus) { good_shape = true; buildTorusRug(); return; } celllister cl(centerover.at ? centerover.at : cwt.at, get_sightrange(), vertex_limit, NULL); map vptr; for(int i=0; itype; j++) p[j] = findOrAddRugpoint(ggmatrix(c) * get_corner_position(c, j), v->dist); for(int j=0; jtype; j++) addTriangle(v, p[j], p[(j+1) % c->type]); if(euwrap && nonorientable) { transmatrix T = ggmatrix(c) * eumove(torusconfig::sdx, torusconfig::sdy); rugpoint *Tv = addRugpoint(T * C0, 0); for(int j=0; jtype; j++) p[j] = findOrAddRugpoint(T * get_corner_position(c, j), v->dist); for(int j=0; jtype; j++) addTriangle(Tv, p[j], p[(j+1) % c->type]); } } else for(int j=0; jtype; j++) try { cell *c2 = c->move(j); rugpoint *w = vptr.at(c2); // if(vmodmove(j+1); rugpoint *w2 = vptr.at(c3); if(a4) { cell *c4 = (cellwalker(c,j) + wstep - 1).cpeek(); cell *cm = c; comp(cm, c); comp(cm, c2); comp(cm, c3); comp(cm, c4); if(cm == c || cm == c4) addTriangle(v, w, w2); } else if(v > w && v > w2) addTriangle(v, w, w2); } catch(out_of_range&) {} } println(hlog, "vertices = ", isize(points), " triangles= ", isize(triangles)); if(subdivide_first) for(int i=0; i<20 && subdivide_further(); i++) subdivide(); sort_rug_points(); calcLengths(); verify(); for(auto p: points) if(p->valid) qvalid++; } // rug physics queue pqueue; void enqueue(rugpoint *m) { if(m->inqueue) return; pqueue.push(m); m->inqueue = true; } bool force_euclidean(rugpoint& m1, rugpoint& m2, double rd, bool is_anticusp = false, double d1=1, double d2=1) { if(!m1.valid || !m2.valid) return false; // double rd = hdist(m1.h, m2.h) * xd; double t = sqdhypot_d(rugdim, m1.flat, m2.flat); if(is_anticusp && t > rd*rd) return false; t = sqrt(t); current_total_error += (t-rd) * (t-rd); bool nonzero = abs(t-rd) > err_zero_current; double force = (t - rd) / t / 2; // 20.0; for(int i=0; i0) enqueue(&m2); } return nonzero; } bool force(rugpoint& m1, rugpoint& m2, double rd, bool is_anticusp=false, double d1=1, double d2=1) { if(!m1.valid || !m2.valid) return false; if(gwhere == gEuclid && fast_euclidean) { return force_euclidean(m1, m2, rd, is_anticusp, d1, d2); } using namespace hyperpoint_vec; normalizer n(m1.flat, m2.flat); hyperpoint f1 = n(m1.flat); hyperpoint f2 = n(m2.flat); ld t = hdist(f1, f2); if(is_anticusp && t > rd) return false; current_total_error += (t-rd) * (t-rd); bool nonzero = abs(t-rd) > err_zero_current; double forcev = (t - rd) / 2; // 20.0; transmatrix T = gpushxto0(f1); transmatrix T1 = spintox(T * f2) * T; transmatrix iT1 = inverse(T1); for(int i=0; i<3; i++) if(std::isnan(m1.flat[i])) { addMessage("Failed!"); throw rug_exception(); } f1 = iT1 * xpush0(d1*forcev); f2 = iT1 * xpush0(t-d2*forcev); m1.flat = n[f1]; m2.flat = n[f2]; if(nonzero && d2>0) enqueue(&m2); return nonzero; } vector > preset_points; void preset(rugpoint *m) { if(DIM == 3) return; int q = 0; hyperpoint h; for(int i=0; i<3; i++) h[i] = 0; using namespace hyperpoint_vec; preset_points.clear(); for(int j=0; jedges); j++) for(int k=0; kedges[j].target; rugpoint *b = m->edges[k].target; if(!a->valid) continue; if(!b->valid) continue; double blen = -1; for(int j2=0; j2edges); j2++) if(a->edges[j2].target == b) blen = a->edges[j2].len; if(blen <= 0) continue; for(int j2=0; j2edges); j2++) for(int k2=0; k2edges); k2++) if(a->edges[j2].target == b->edges[k2].target && a->edges[j2].target != m) { rugpoint *c = a->edges[j2].target; if(!c->valid) continue; double a1 = m->edges[j].len/blen; double a2 = m->edges[k].len/blen; double c1 = a->edges[j2].len/blen; double c2 = b->edges[k2].len/blen; double cz = (c1*c1-c2*c2+1) / 2; double ch = sqrt(c1*c1 - cz*cz + 1e-10); double az = (a1*a1-a2*a2+1) / 2; double ah = sqrt(a1*a1 - az*az + 1e-10); // c->h = a->h + (b->h-a->h) * cz + ch * ort hyperpoint ort = (c->flat - a->flat - cz * (b->flat-a->flat)) / ch; // m->h = a->h + (b->h-a->h) * az - ah * ort hyperpoint res = a->flat + (b->flat-a->flat) * az - ah * ort; h += res; preset_points.emplace_back(hypot(blen * (ah+ch), blen * (az-cz)), c); q++; } } if(q>0) m->flat = h/q; // printf("preset (%d) -> %s\n", q, display(m->flat)); if(std::isnan(m->flat[0]) || std::isnan(m->flat[1]) || std::isnan(m->flat[2])) throw rug_exception(); } ld sse(const hyperpoint& h) { ld sse = 0; for(auto& p: preset_points) { ld l = p.first; ld l0; if(fast_euclidean) l0 = dhypot_d(rugdim, h, p.second->flat); else { normalizer n(h, p.second->flat); hyperpoint h1 = n(h); hyperpoint h2 = n(p.second->flat); l0 = hdist(h1, h2); } sse += (l0-l) * (l0-l); } return sse; } void optimize(rugpoint *m, bool do_preset) { if(do_preset) { preset(m); // int ed0 = isize(preset_points); for(auto& e: m->edges) if(e.target->valid) preset_points.emplace_back(e.len, e.target); if(gwhere >= gSphere || DIM == 3) { ld cur = sse(m->flat); for(int it=0; it<500; it++) { ld ex = exp(-it/60); again: hyperpoint last = m->flat; m->flat[(it/2)%rugdim] += ((it&1)?1:-1) * ex; ld now = sse(m->flat); if(now < cur) { cur = now; ex *= 1.2; goto again; } else m->flat = last; } } } for(int it=0; it<50; it++) for(int j=0; jedges); j++) force(*m, *m->edges[j].target, m->edges[j].len, false, 1, 0); } int divides = 0; bool stop = false; bool subdivide_further() { if(fulltorus) return false; if(DIM == 3) return false; return isize(points) * 4 < vertex_limit; } void subdivide() { int N = isize(points); // if(euclid && gwhere == gEuclid) return; if(!subdivide_further()) { if(euclid && !bounded && gwhere == gEuclid) { println(hlog, "Euclidean -- full precision"); stop = true; } else { err_zero_current /= 2; println(hlog, "increasing precision to ", err_zero_current); for(auto p: points) enqueue(p); } return; } println(hlog, "subdivide ", make_pair(N, isize(triangles))); need_mouseh = true; divides++; vector otriangles = triangles; triangles.clear(); halves.clear(); // subdivide edges for(int i=0; iedges); j++) { rugpoint *m2 = m->edges[j].target; if(m2 < m) continue; rugpoint *mm = addRugpoint(mid(m->h, m2->h), (m->dist+m2->dist)/2); halves[make_pair(m, m2)] = mm; if(!good_shape) { using namespace hyperpoint_vec; normalizer n(m->flat, m2->flat); hyperpoint h1 = n(m->flat); hyperpoint h2 = n(m2->flat); mm->flat = n[mid(h1, h2)]; } mm->valid = m->valid && m2->valid; if(mm->valid) qvalid++; mm->inqueue = false; enqueue(mm); } m->edges.clear(); } for(int i=0; i hyperpoint4; hyperpoint4 azeq_to_4(const hyperpoint& h) { array res; ld rad = hypot_d(3, h); res[3] = cos(rad); ld sr = sin(rad) / rad; for(int j=0; j<3; j++) res[j] = h[j] * sr; return res; } ld modeldist(const hyperpoint& h1, const hyperpoint& h2) { if(gwhere == gSphere) { hyperpoint4 coord[2] = { azeq_to_4(h1), azeq_to_4(h2) }; ld edist = 0; for(int j=0; j<4; j++) edist += sqr(coord[0][j] - coord[1][j]); return 2 * asin(sqrt(edist) / 2); } return slow_modeldist(h1, h2); } typedef long long bincode; const bincode sY = (1<<16); const bincode sZ = sY * sY; const bincode sT = sY * sY * sY; bincode acd_bin(ld x) { return (int) floor(x / anticusp_dist + .5); } bincode get_bincode(hyperpoint h) { switch(ginf[gwhere].cclass) { case gcEuclid: return acd_bin(h[0]) + acd_bin(h[1]) * sY + acd_bin(h[2]) * sZ; case gcHyperbolic: return acd_bin(hypot_d(3, h)); case gcSphere: { auto p = azeq_to_4(h); return acd_bin(p[0]) + acd_bin(p[1]) * sY + acd_bin(p[2]) * sZ + acd_bin(p[3]) * sT; } } return 0; } void generate_deltas(vector& target, int dim, bincode offset) { if(dim == 0) { if(offset > 0) target.push_back(offset); } else { generate_deltas(target, dim-1, offset * sY); generate_deltas(target, dim-1, offset * sY + 1); generate_deltas(target, dim-1, offset * sY - 1); } } int detect_cusp_at(rugpoint *p, rugpoint *q) { if(hdist(p->h, q->h) * modelscale <= anticusp_dist) return 0; else if(modeldist(p->flat, q->flat) > anticusp_dist - err_zero_current) return 1; else { add_anticusp_edge(p, q); enqueue(p); enqueue(q); return 2; } } int detect_cusps() { ld max_edge_length = 0; for(auto p: points) for(auto e: p->edges) max_edge_length = max(max_edge_length, e.len); anticusp_dist = anticusp_factor * max_edge_length; array stats = {{0,0,0}}; map > code_to_point; for(auto p: points) if(p->valid) code_to_point[get_bincode(p->flat)].push_back(p); vector deltas; generate_deltas(deltas, gwhere == gEuclid ? 3 : gwhere == gNormal ? 1 : 4, 0); for(auto b: code_to_point) { bincode at = b.first; for(auto p: b.second) for(auto q: b.second) if(p < q) stats[detect_cusp_at(p, q)]++; for(bincode bc: deltas) if(code_to_point.count(at + bc)) for(auto p: b.second) for(auto q: code_to_point[at+bc]) stats[detect_cusp_at(p, q)]++; } /* printf("testing\n"); int stats2[3] = {0,0,0}; for(auto p: points) if(p->valid) for(auto q: points) if(q->valid) if(ph) + .1e-6; int oqvalid = qvalid; for(int i=0; i 7); enqueue(&m); } } if(qvalid != oqvalid) { println(hlog, "adding new points ", make_tuple(oqvalid, qvalid, isize(points), dist, dt, queueiter)); } } void physics() { #if CAP_CRYSTAL if(in_crystal()) { crystal::build_rugdata(); return; } #endif if(good_shape) return; auto t = SDL_GetTicks(); current_total_error = 0; while(SDL_GetTicks() < t + 5 && !stop) for(int it=0; it<50 && !stop; it++) if(pqueue.empty()) addNewPoints(); else { queueiter++; rugpoint *m = pqueue.front(); pqueue.pop(); m->inqueue = false; bool moved = false; for(auto& e: m->edges) moved = force(*m, *e.target, e.len) || moved; for(auto& e: m->anticusp_edges) moved = force(*m, *e.target, anticusp_dist, true) || moved; if(moved) enqueue(m), need_mouseh = true; } } // drawing the Rug //----------------- bool use_precompute; void getco(rugpoint *m, hyperpoint& h, int &spherepoints) { using namespace hyperpoint_vec; h = use_precompute ? m->getglue()->precompute : m->getglue()->flat; if(rug_perspective && gwhere >= gSphere) { if(h[2] > 0) { ld rad = hypot_d(3, h); // turn M_PI to -M_PI // the only difference between sphere and elliptic is here: // in elliptic, we subtract PI from the distance ld rad_to = (gwhere == gSphere ? M_PI + M_PI : M_PI) - rad; ld r = -rad_to / rad; h *= r; spherepoints++; } } } extern int besti; #if CAP_ODS /* these functions are for the ODS projection, used in VR videos */ void cyclefix(ld& a, ld b) { if(a > b + M_PI) a -= 2 * M_PI; if(a < b - M_PI) a += 2 * M_PI; } ld raddif(ld a, ld b) { ld d = a-b; if(d < 0) d = -d; if(d > 2*M_PI) d -= 2*M_PI; if(d > M_PI) d = 2 * M_PI-d; return d; } bool project_ods(hyperpoint azeq, hyperpoint& h1, hyperpoint& h2, bool eye) { USING_NATIVE_GEOMETRY; ld tanalpha = tan_auto(vid.ipd/2); if(eye) tanalpha = -tanalpha; if(!sphere) tanalpha = -tanalpha; using namespace hyperpoint_vec; ld d = hypot_d(3, azeq); ld sindbd = sin_auto(d)/d, cosd = cos_auto(d); ld x = azeq[0] * sindbd; ld y = azeq[2] * sindbd; ld z = azeq[1] * sindbd; ld t = cosd; // printf("%10.5lf %10.5lf %10.5lf ", azeq[0], azeq[1], azeq[2]); // printf(" => %10.5lf %10.5lf %10.5lf %10.5lf", x, y, z, t); ld y02 = (x*x + y*y - tanalpha*tanalpha*t*t); if(y02 < 0) return false; ld y0 = sqrt(y02); ld theta = atan(z / y0); for(int i=0; i<2; i++) { hyperpoint& h = (i ? h1 : h2); if(i == 1) theta = -theta, y0 = -y0; ld x0 = t * tanalpha; ld phi = atan2(y, x) - atan2(y0, x0) + M_PI; ld delta = euclid ? hypot(y0,z) : atan2_auto(z / sin(theta), t / cos_auto(vid.ipd/2)); if(euclid || hyperbolic) phi -= M_PI; if(hyperbolic) delta = -delta; h[0] = phi; h[1] = theta; h[2] = delta; if(euclid || hyperbolic) h[1] = -theta; // printf(" => %10.5lf %10.5lf %10.5lf", phi, theta, delta); } // printf("\n"); return true; } #endif vector ct_array; vector cp_array; void drawTriangle(triangle& t) { int num = t.m[2] ? 3 : 2; using namespace hyperpoint_vec; for(int i=0; ivalid) return; // if(t.m[i]->dist >= get_sightrange()+.51) return; } dt++; #if CAP_ODS if(vid.stereo_mode == current_display->sODS) { hyperpoint pts[3]; // not implemented if(num == 2) return; for(int i=0; igetglue()->flat; hyperpoint hc = (pts[1] - pts[0]) ^ (pts[2] - pts[0]); double hch = hypot_d(3, hc); ld col = (2 + hc[0]/hch) / 3; bool natsph = among(gwhere, gSphere, gElliptic); bool ok = true; array h; for(int eye=0; eye<2; eye++) { if(true) { for(int i=0; i<3; i++) ok = ok && project_ods(pts[i], h[i], h[i+3], eye); if(!ok) return; for(int i=0; i<6; i++) { // let Delta be from 0 to 2PI if(h[i][2]<0) h[i][2] += 2 * M_PI; // Theta is from -PI/2 to PI/2. Let it be from 0 to PI h[i][1] += (eye?-1:1) * M_PI/2; } } else { for(int i=0; i<6; i++) h[i][0] = -h[i][0], h[i][1] = -h[i][1], h[i][2] = 2*M_PI-h[i][2]; } if(natsph) { if(raddif(h[4][0], h[0][0]) < raddif(h[1][0], h[0][0])) swap(h[1], h[4]); if(raddif(h[5][0], h[0][0]) < raddif(h[2][0], h[0][0])) swap(h[5], h[2]); } else { if(h[0][2] < 0) swap(h[0], h[3]); if(h[1][2] < 0) swap(h[1], h[4]); if(h[2][2] < 0) swap(h[2], h[5]); } if(abs(h[1][1] - h[0][1]) > M_PI/2) return; if(abs(h[2][1] - h[0][1]) > M_PI/2) return; cyclefix(h[1][0], h[0][0]); cyclefix(h[2][0], h[0][0]); cyclefix(h[4][0], h[3][0]); cyclefix(h[5][0], h[3][0]); for(int s: {0, 3}) { int fst = 0, lst = 0; if(h[s+1][0] < -M_PI || h[s+2][0] < -M_PI) lst++; if(h[s+1][0] > +M_PI || h[s+2][0] > +M_PI) fst--; for(int x=fst; x<=lst; x++) for(int i=0; i<3; i++) { ct_array.emplace_back( hpxyz(h[s+i][0] + 2*M_PI*x, h[s+i][1], h[s+i][2]), t.m[i]->x1, t.m[i]->y1, col); } if(!natsph) break; } } return; } #endif int spherepoints = 0; array h; for(int i=0; ix1, t.m[i]->y1, col); } renderbuffer *glbuf; void prepareTexture() { resetbuffer rb; dynamicval d(vid.stereo_mode, sOFF); calcparam_rug(); conformal::configure(); glbuf->enable(); glbuf->clear(0); ptds.clear(); #if CAP_QUEUE draw_boundary(0); draw_boundary(1); draw_model_elements(); #endif drawthemap(); if(mousing && !renderonce) { for(int i=0; ih); queuechr(V, 0.5, 'X', 0xFFFFFFFF, 2); for(int i=0; i<72; i++) queueline(V * xspinpush0(i*M_PI/32, finger_range), V * xspinpush0((i+1)*M_PI/32, finger_range), 0xFFFFFFFF, vid.linequality); } drawqueue(); calcparam(); rb.reset(); } double xview, yview; bool no_fog; ld lowrug = 1e-2, hirug = 1e3; GLuint alternate_texture; bool invert_depth; void drawRugScene() { glbuf->use_as_texture(); if(alternate_texture) glBindTexture( GL_TEXTURE_2D, alternate_texture); if(backcolor == 0) glClearColor(0.05f,0.05f,0.05f,1.0f); else glhr::colorClear(backcolor << 8 | 0xFF); #ifdef GLES_ONLY glClearDepthf(invert_depth ? -1.0f : 1.0f); #else glClearDepth(invert_depth ? -1.0f : 1.0f); #endif glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_BLEND); glhr::switch_mode(glhr::gmLightFog, glhr::shader_projection::standard); glhr::set_depthtest(true); glDepthFunc(invert_depth ? GL_GREATER : GL_LESS); for(int ed=current_display->stereo_active() && vid.stereo_mode != sODS ? -1 : 0; ed < 2; ed += 2) { use_precompute = false; ct_array.clear(); cp_array.clear(); if(ed == 1 && vid.stereo_mode == sAnaglyph) glClear(GL_DEPTH_BUFFER_BIT); dynamicval p(pmodel, mdRug); current_display->set_all(ed); eyewidth_translate(ed); if(vid.stereo_mode == sODS) { glhr::projection_multiply(glhr::ortho(M_PI, M_PI, 100)); // 2*M_PI)); } else if(rug_perspective || current_display->stereo_active()) { xview = current_display->tanfov; yview = current_display->tanfov * vid.yres / vid.xres; glhr::projection_multiply(glhr::frustum(xview, yview, lowrug, hirug)); xview = -xview; yview = -yview; if(!rug_perspective) glhr::projection_multiply(glhr::translate(0, 0, -model_distance)); if(ed) { if(gwhere == gEuclid) glhr::projection_multiply(glhr::translate(vid.ipd*ed/2, 0, 0)); else { use_precompute = true; for(auto p: points) { p->precompute = p->flat; push_point(p->precompute, 0, vid.ipd*ed/2); } } } } else { xview = current_display->tanfov * model_distance; yview = current_display->tanfov * model_distance * vid.yres / vid.xres; // glOrtho(-xview, xview, yview, -yview, -1000, 1000); glhr::projection_multiply(glhr::ortho(xview, yview, -1000)); } glhr::color2(0xFFFFFFFF); glhr::fog_max( no_fog ? 1000 : gwhere == gSphere && rug_perspective ? 10 : gwhere == gElliptic && rug_perspective ? 4 : 100 ); for(int t=0; t 0) { glhr::prepare(ct_array); glDrawArrays(GL_TRIANGLES, 0, isize(ct_array)); } if(isize(cp_array) > 0) { glhr::prepare(cp_array); glLineWidth(lwidth); glDrawArrays(GL_LINES, 0, isize(cp_array)); } current_display->set_mask(0); } glEnable(GL_BLEND); if(rug_failure) { rug::close(); rug::clear_model(); rug::init(); } } // organization //-------------- transmatrix currentrot; void reopen() { if(rugged) return; rugdim = 2 * DIM - 1; when_enabled = ticks; GLERR("before init"); glbuf = new renderbuffer(TEXTURESIZE, TEXTURESIZE, vid.usingGL && !rendernogl); if(!glbuf->valid) { addMessage(XLAT("Failed to enable")); delete glbuf; return; } rugged = true; if(renderonce) prepareTexture(); if(!rugged) return; } void init_model() { clear_model(); genrug = true; drawthemap(); genrug = false; qvalid = 0; dt = 0; queueiter = 0; err_zero_current = err_zero; #if CAP_CRYSTAL if(geometry == gCrystal && surface::sh == surface::dsNone) { surface::sh = surface::dsCrystal; crystal::init_rotation(); good_shape = true; return; } #endif try { buildRug(); while(good_shape && subdivide_further()) subdivide(); currentrot = Id; bool valid = true; for(rugpoint *r: points) if(r->x1<0 || r->x1>1 || r->y1<0 || r->y1 > 1) valid = false; if(sphere && pmodel == mdDisk && vid.alpha > 1) valid = false; if(!valid) gotoHelp( "Note: this mode is based on what you see on the screen -- but re-rendered in another way. " "If not everything is shown on the screen (e.g., too zoomed in), the results will be incorrect " "(though possibly interesting). " "Use a different projection to fix this." ); } catch(rug_exception) { close(); clear_model(); } } void init() { reopen(); if(rugged) init_model(); } void clear_model() { triangles.clear(); for(int i=0; i (); } void close() { if(!rugged) return; rugged = false; delete glbuf; finger_center = NULL; } int lastticks; ld protractor = 0; void apply_rotation(const transmatrix& t) { if(!rug_perspective) currentrot = t * currentrot; #if CAP_CRYSTAL if(in_crystal()) crystal::apply_rotation(t); else #endif for(auto p: points) p->flat = t * p->flat; } void move_forward(ld distance) { if(rug_perspective) push_all_points(2, distance); else model_distance /= exp(distance); } #define CAP_HOLDKEYS CAP_SDL // && !ISWEB) bool handlekeys(int sym, int uni) { if(NUMBERKEY == '1') { ld bdist = 1e12; if(finger_center) finger_center = NULL; else { for(auto p: points) { ld cdist = hdist(p->getglue()->h, mouseh); if(cdist < bdist) bdist = cdist, finger_center = p->getglue(); } } if(renderonce) renderlate+=10; return true; } else if(NUMBERKEY == '2') { #if CAP_CRYSTAL if(in_crystal()) crystal::switch_z_coordinate(); else #endif apply_rotation(rotmatrix(M_PI, 0, 2)); return true; } else if(NUMBERKEY == '3') { #if CAP_CRYSTAL if(in_crystal()) crystal::flip_z(); else #endif apply_rotation(rotmatrix(M_PI/2, 0, 2)); return true; } #if CAP_CRYSTAL else if(sym == SDLK_HOME && in_crystal()) { crystal::next_home_orientation(); return true; } #endif #if !CAP_HOLDKEYS else if(sym == SDLK_PAGEUP || uni == '[') { move_forward(.1); return true; } else if(sym == SDLK_PAGEDOWN || uni == ']') { move_forward(-.1); return true; } else if(sym == SDLK_HOME) { apply_rotation(rotmatrix(.1, 0, 1)); return true; } else if(sym == SDLK_END) { apply_rotation(rotmatrix(.1, 1, 0)); return true; } else if(sym == SDLK_DOWN) { apply_rotation(rotmatrix(.1, 2, 1)); return true; } else if(sym == SDLK_UP) { apply_rotation(rotmatrix(.1, 1, 2)); return true; } else if(sym == SDLK_LEFT) { apply_rotation(rotmatrix(.1, 2, 0)); return true; } else if(sym == SDLK_RIGHT) { apply_rotation(rotmatrix(.1, 0, 2)); return true; } #endif else return false; } void finger_on(int coord, ld val) { for(auto p: points) { ld d = hdist(finger_center->h, p->getglue()->h); push_point(p->flat, coord, val * finger_force * exp( - sqr(d / finger_range))); } enqueue(finger_center), good_shape = false; } transmatrix last_orientation; ld ruggospeed = 1; void actDraw() { try { if(!renderonce) prepareTexture(); else if(renderlate) { renderlate--; prepareTexture(); } // do not display button else playerfound = true; current_display->set_viewport(0); physics(); drawRugScene(); #if CAP_ORIENTATION if(ticks < when_enabled + 500) last_orientation = getOrientation(); else { transmatrix next_orientation = getOrientation(); apply_rotation(inverse(last_orientation) * next_orientation); last_orientation = next_orientation; } #endif int qm = 0; double alpha = (ticks - lastticks) / 1000.0; lastticks = ticks; if(ruggo) move_forward(ruggo * alpha); #if CAP_HOLDKEYS Uint8 *keystate = SDL_GetKeyState(NULL); if(keystate[SDLK_LALT]) alpha /= 10; transmatrix t = Id; auto perform_finger = [=] () { if(keystate[SDLK_HOME]) finger_range /= exp(alpha); if(keystate[SDLK_END]) finger_range *= exp(alpha); if(keystate[SDLK_LEFT]) finger_on(0, -alpha); if(keystate[SDLK_RIGHT]) finger_on(0, alpha); if(keystate[SDLK_UP]) finger_on(1, alpha); if(keystate[SDLK_DOWN]) finger_on(1, -alpha); if(keystate[SDLK_PAGEDOWN]) finger_on(2, -alpha); if(keystate[SDLK_PAGEUP]) finger_on(2, +alpha); }; if(cmode & sm::NUMBER) { } else if(rug_perspective) { ld strafex = 0, strafey = 0, push = 0; if(finger_center) perform_finger(); else { if(keystate[SDLK_HOME]) qm++, t = t * rotmatrix(alpha, 0, 1), protractor += alpha; if(keystate[SDLK_END]) qm++, t = t * rotmatrix(alpha, 1, 0), protractor -= alpha; if(!keystate[SDLK_LSHIFT]) { if(keystate[SDLK_DOWN]) qm++, t = t * rotmatrix(alpha, 2, 1), protractor += alpha; if(keystate[SDLK_UP]) qm++, t = t * rotmatrix(alpha, 1, 2), protractor -= alpha; if(keystate[SDLK_LEFT]) qm++, t = t * rotmatrix(alpha, 2, 0), protractor += alpha; if(keystate[SDLK_RIGHT]) qm++, t = t * rotmatrix(alpha, 0, 2), protractor -= alpha; } if(keystate[SDLK_PAGEDOWN]) push -= alpha; if(keystate[SDLK_PAGEUP]) push += alpha; if(keystate[SDLK_LSHIFT]) { if(keystate[SDLK_LEFT]) strafex += alpha; if(keystate[SDLK_RIGHT]) strafex -= alpha; if(keystate[SDLK_UP]) strafey -= alpha; if(keystate[SDLK_DOWN]) strafey += alpha; } } if(qm) { if(keystate[SDLK_LCTRL]) push_all_points(2, +model_distance); apply_rotation(t); if(keystate[SDLK_LCTRL]) push_all_points(2, -model_distance); } model_distance -= push; push_all_points(2, push * ruggospeed); push_all_points(0, strafex * ruggospeed); push_all_points(1, strafey * ruggospeed); } else { if(finger_center) perform_finger(); else { if(keystate[SDLK_HOME] && !in_crystal()) qm++, t = inverse(currentrot); if(keystate[SDLK_END]) { qm++; if(in_crystal()) t = t * rotmatrix(alpha, 0, 1); else t = currentrot * rotmatrix(alpha, 0, 1) * inverse(currentrot); } if(keystate[SDLK_DOWN]) qm++, t = t * rotmatrix(alpha, 1, 2); if(keystate[SDLK_UP]) qm++, t = t * rotmatrix(alpha, 2, 1); if(keystate[SDLK_LEFT]) qm++, t = t * rotmatrix(alpha, 0, 2); if(keystate[SDLK_RIGHT]) qm++, t = t * rotmatrix(alpha, 2, 0); if(keystate[SDLK_PAGEUP]) model_distance /= exp(alpha * ruggospeed); if(keystate[SDLK_PAGEDOWN]) model_distance *= exp(alpha * ruggospeed); } if(qm) { apply_rotation(t); } } #endif } catch(rug_exception) { rug::close(); } } int besti; void getco_pers(rugpoint *r, hyperpoint& p, int& spherepoints, bool& error) { getco(r, p, spherepoints); if(rug_perspective) { if(p[2] >= 0) error = true; else { p[0] /= p[2]; p[1] /= p[2]; } } } static const ld RADAR_INF = 1e12; ld radar_distance = RADAR_INF; hyperpoint gethyper(ld x, ld y) { double mx = (x - current_display->xcenter)/vid.xres * 2 * xview; double my = (current_display->ycenter - y)/vid.yres * 2 * yview; radar_distance = RADAR_INF; double rx1=0, ry1=0; bool found = false; for(int i=0; ivalid || !r1->valid || !r2->valid) continue; hyperpoint p0, p1, p2; bool error = false; int spherepoints = 0; getco_pers(r0, p0, spherepoints, error); getco_pers(r1, p1, spherepoints, error); getco_pers(r2, p2, spherepoints, error); if(error || spherepoints == 1 || spherepoints == 2) continue; double dx1 = p1[0] - p0[0]; double dy1 = p1[1] - p0[1]; double dx2 = p2[0] - p0[0]; double dy2 = p2[1] - p0[1]; double dxm = mx - p0[0]; double dym = my - p0[1]; // A (dx1,dy1) = (1,0) // B (dx2,dy2) = (0,1) double det = dx1*dy2 - dy1*dx2; double tx = dxm * dy2 - dym * dx2; double ty = -(dxm * dy1 - dym * dx1); tx /= det; ty /= det; if(tx >= 0 && ty >= 0 && tx+ty <= 1) { double rz1 = p0[2] * (1-tx-ty) + p1[2] * tx + p2[2] * ty; rz1 = -rz1; if(!rug_perspective) rz1 += model_distance; if(rz1 < radar_distance) { radar_distance = rz1; rx1 = r0->x1 + (r1->x1 - r0->x1) * tx + (r2->x1 - r0->x1) * ty; ry1 = r0->y1 + (r1->y1 - r0->y1) * tx + (r2->y1 - r0->y1) * ty; } found = true; } } if(!found) return Hypc; double px = rx1 * TEXTURESIZE, py = (1-ry1) * TEXTURESIZE; calcparam_rug(); conformal::configure(); hyperpoint h = hr::gethyper(px, py); calcparam(); return h; } string makehelp() { return XLAT( "In this mode, HyperRogue is played on a 3D model of a part of the hyperbolic plane, " "similar to one you get from the 'paper model creator' or by hyperbolic crocheting.\n\n") /* "This requires some OpenGL extensions and may crash or not work correctly -- enabling " "the 'render texture without OpenGL' options may be helpful in this case. Also the 'render once' option " "will make the rendering faster, but the surface will be rendered only once, so " "you won't be able to play a game on it.\n\n" */ #if !ISMOBILE + XLAT("Use arrow keys to rotate, Page Up/Down to zoom.") + "\n\n" + XLAT("In the perspective projection, you can use arrows to rotate the camera, Page Up/Down to go forward/backward, Shift+arrows to strafe, and Ctrl+arrows to rotate the model.") #endif ; } string geometry_name(eGeometry g) { switch(g) { case gNormal: return XLAT("hyperbolic"); case gEuclid: return XLAT("Euclidean"); case gSphere: return XLAT("spherical"); case gElliptic: return XLAT("elliptic"); default: return XLAT("unknown"); } } void change_texturesize() { if(rugged) { close(); reopen(); } } ld old_distance; void show() { cmode = sm::SIDE | sm::MAYDARK; gamescreen(0); dialog::init(XLAT("hypersian rug mode"), iinf[itPalace].color, 150, 100); dialog::addBoolItem(XLAT("enable the Hypersian Rug mode"), rug::rugged, 'u'); dialog::addBoolItem(XLAT("render the texture only once"), (renderonce), 'o'); #if CAP_SDL dialog::addBoolItem(XLAT("render texture without OpenGL"), (rendernogl), 'g'); #else rendernogl = false; #endif dialog::addSelItem(XLAT("texture size"), its(texturesize)+"x"+its(texturesize), 's'); dialog::addSelItem(XLAT("vertex limit"), its(vertex_limit), 'v'); if(rug::rugged) dialog::lastItem().value += " (" + its(qvalid) + ")"; dialog::addSelItem(XLAT("model distance"), fts(model_distance), 'd'); dialog::addBoolItem(XLAT("projection"), rug_perspective, 'p'); dialog::lastItem().value = XLAT(rug_perspective ? "perspective" : gwhere == gEuclid ? "orthogonal" : "azimuthal equidistant"); if(!rug::rugged) dialog::addSelItem(XLAT("native geometry"), geometry_name(gwhere), 'n'); else dialog::addSelItem(XLAT("radar"), radar_distance == RADAR_INF ? "∞" : fts4(radar_distance), 'r'); dialog::addSelItem(XLAT("model scale factor"), fts(modelscale), 'm'); if(rug::rugged) dialog::addSelItem(XLAT("model iterations"), its(queueiter), 0); dialog::addItem(XLAT("stereo vision config"), 'f'); // dialog::addSelItem(XLAT("protractor"), fts(protractor * 180 / M_PI) + "°", 'f'); if(!good_shape) { dialog::addSelItem(XLAT("maximum error"), ftsg(err_zero), 'e'); if(rug::rugged) dialog::lastItem().value += " (" + ftsg(err_zero_current) + ")"; } dialog::addSelItem(XLAT("automatic move speed"), fts(ruggo), 'G'); dialog::addSelItem(XLAT("anti-crossing"), fts(anticusp_factor), 'A'); dialog::addBoolItem(XLAT("3D monsters/walls on the surface"), spatial_rug, 'S'); dialog::add_action([] () { spatial_rug = !spatial_rug; }); #if CAP_SURFACE if(hyperbolic) { if(gwhere == gEuclid) dialog::addItem(XLAT("smooth surfaces"), 'c'); else dialog::addBreak(100); } #endif dialog::addBreak(50); dialog::addHelp(); dialog::addBack(); dialog::display(); keyhandler = [] (int sym, int uni) { dialog::handleNavigation(sym, uni); if(uni == 'h' || uni == SDLK_F1) gotoHelp(makehelp()); else if(uni == 'u') { if(rug::rugged) rug::close(); else { #if CAP_SURFACE surface::sh = surface::dsNone; #endif rug::init(); } } else if(uni == 'R') dialog::editNumber(finger_range, 0, 1, .01, .1, XLAT("finger range"), XLAT("Press 1 to enable the finger mode.") ); else if(uni == 'F') dialog::editNumber(finger_force, 0, 1, .01, .1, XLAT("finger force"), XLAT("Press 1 to enable the finger force.") ); else if(uni == 'o') renderonce = !renderonce; else if(uni == 'G') { dialog::editNumber(ruggo, -1, 1, .1, 0, XLAT("automatic move speed"), XLAT("Move automatically without pressing any keys.") ); } else if(uni == 'A') { dialog::editNumber(anticusp_factor, 0, 1.5, .1, 0, XLAT("anti-crossing"), XLAT("The anti-crossing algorithm prevents the model from crossing itself, " "by preventing points which should not be close from being close. " "The bigger number, the more sensitive it is, but the embedding is slower. Set 0 to disable.") ); } else if(uni == 'v') { dialog::editNumber(vertex_limit, 0, 50000, 500, 3000, ("vertex limit"), XLAT("The more vertices, the more accurate the Hypersian Rug model is. " "However, a number too high might make the model slow to compute and render.") ); dialog::reaction = [] () { err_zero_current = err_zero; }; } else if(uni == 'r') addMessage(XLAT("This just shows the 'z' coordinate of the selected point.")); else if(uni == 'm') { dialog::editNumber(modelscale, 0.1, 10, rugged ? .01 : .1, 1, XLAT("model scale factor"), XLAT("This is relevant when the native geometry is not Euclidean. " "For example, if the native geometry is spherical, and scale < 1, a 2d sphere will be rendered as a subsphere; " "if the native geometry is hyperbolic, and scale > 1, a hyperbolic plane will be rendered as an equidistant surface. ") ); dialog::scaleLog(); if(rug::rugged) { static bool adjust_points = true; static bool camera_center = false; static bool adjust_edges = true; static bool adjust_distance = true; static ld last; last = modelscale; dialog::extra_options = [] () { dialog::addBoolItem_action(XLAT("adjust points"), adjust_points, 'P'); if(adjust_points) dialog::addBoolItem_action(XLAT("center on camera"), camera_center, 'C'); else dialog::addBreak(100); dialog::addBoolItem_action(XLAT("adjust edges"), adjust_edges, 'E'); dialog::addBoolItem_action(XLAT("adjust distance"), adjust_distance, 'D'); }; dialog::reaction = [] () { if(!last || !modelscale) return; if(!camera_center) push_all_points(2, model_distance); for(auto p:points) { if(adjust_edges) for(auto& e: p->edges) e.len *= modelscale / last; using namespace hyperpoint_vec; if(adjust_points) p->flat *= modelscale / last; enqueue(p); } if(adjust_distance) model_distance = model_distance * modelscale / last; last = modelscale; good_shape = false; if(!camera_center) push_all_points(2, -model_distance); }; } } else if(uni == 'p') { rug_perspective = !rug_perspective; if(rugged) { if(rug_perspective) push_all_points(2, -model_distance); else push_all_points(2, +model_distance); } } else if(uni == 'd') { dialog::editNumber(model_distance, -10, 10, .1, 1, XLAT("model distance"), XLAT("In the perspective projection, this sets the distance from the camera to the center of the model. " "In the orthogonal projection this just controls the scale.") ); old_distance = model_distance; dialog::reaction = [] () { if(rug::rugged && rug_perspective) { push_all_points(2, old_distance - model_distance); } old_distance = model_distance; }; } else if(uni == 'e') { dialog::editNumber(err_zero, 1e-9, 1, .1, 1e-3, XLAT("maximum error"), XLAT("New points are added when the current error in the model is smaller than this value.") ); dialog::scaleLog(); dialog::reaction = [] () { err_zero_current = err_zero; }; } else if(uni == 'f') pushScreen(showStereo); else if(uni == 'n' && !rug::rugged) gwhere = eGeometry((gwhere+1) % 4); else if(uni == 'g' && !rug::rugged && CAP_SDL) rendernogl = !rendernogl; else if(uni == 's') { texturesize *= 2; if(texturesize == 8192) texturesize = 64; change_texturesize(); } #if CAP_SURFACE else if(uni == 'c') pushScreen(surface::show_surfaces); #endif else if(handlekeys(sym, uni)) ; else if(doexiton(sym, uni)) popScreen(); }; } void select() { pushScreen(rug::show); } #if CAP_COMMANDLINE int rugArgs() { using namespace arg; if(0) ; else if(argis("-rugmodelscale")) { shift_arg_formula(modelscale); } else if(argis("-ruggeo")) { shift(); gwhere = (eGeometry) argi(); } else if(argis("-rugpers")) { rug_perspective = true; } else if(argis("-rugonce")) { renderonce = true; } else if(argis("-rugdist")) { shift_arg_formula(model_distance); } else if(argis("-ruglate")) { renderonce = true; renderlate += 10; } else if(argis("-rugmany")) { renderonce = false; } else if(argis("-ruglwidth")) { shift_arg_formula(lwidth); } else if(argis("-rugauto")) { shift_arg_formula(ruggo); } else if(argis("-rugorth")) { rug_perspective = false; } else if(argis("-rugerr")) { shift_arg_formula(err_zero); } else if(argis("-rugtsize")) { shift(); rug::texturesize = argi(); change_texturesize(); } else if(argis("-rugv")) { shift(); vertex_limit = argi(); } else if(argis("-rugon")) { PHASE(3); calcparam(); rug::init(); } else if(argis("-sdfoff")) { subdivide_first = false; } else if(argis("-sdfon")) { subdivide_first = true; } else if(argis("-anticusp")) { shift_arg_formula(anticusp_factor); } else if(argis("-d:rug")) launch_dialog(show); else return 1; return 0; } auto rug_hook = addHook(hooks_args, 100, rugArgs); #endif } #else // fake for mobile namespace rug { bool rugged = false; bool renderonce = false; bool rendernogl = true; int texturesize = 512; ld scale = 1.0f; } #endif }