mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-30 13:32:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1963 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1963 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Hyperbolic Rogue - Hypersian Rug mode
 | |
| // Copyright (C) 2011-2016 Zeno Rogue, see 'hyper.cpp' for details
 | |
| 
 | |
| /** \file rug.cpp
 | |
|  *  \brief Hypersian Rug mode
 | |
|  *
 | |
|  *  See also surface.cpp for constant curvature surfaces.
 | |
|  */
 | |
| 
 | |
| #include "hyper.h"
 | |
| namespace hr {
 | |
| 
 | |
| #if CAP_RUG
 | |
| 
 | |
| #define TEXTURESIZE (texturesize)
 | |
| #define HTEXTURESIZE (texturesize/2)
 | |
| 
 | |
| EX bool rug_failure = false;
 | |
| 
 | |
| EX namespace rug {
 | |
| 
 | |
| EX bool mouse_control_rug = false;
 | |
| 
 | |
| EX transmatrix rugView;
 | |
| 
 | |
| EX ld lwidth = 2;
 | |
| 
 | |
| EX bool in_crystal() { return surface::sh == surface::dsCrystal; }
 | |
| 
 | |
| bool computed = false;
 | |
| 
 | |
| #if HDR
 | |
|   struct edge {
 | |
|     struct rugpoint *target;
 | |
|     ld len;
 | |
|     };
 | |
|   
 | |
|   struct dexp_data {
 | |
|     hyperpoint params;
 | |
|     hyperpoint cont;
 | |
|     ld remaining_distance;
 | |
|     };
 | |
| 
 | |
|   struct rugpoint {
 | |
|     double x1, y1;
 | |
|     bool valid;
 | |
|     bool inqueue;
 | |
|     double dist;
 | |
|     shiftpoint h;      // point in the represented space
 | |
|     hyperpoint native; // point in the native space
 | |
|     hyperpoint precompute;
 | |
|     vector<edge> edges;
 | |
|     vector<edge> anticusp_edges;
 | |
|     // Find-Union algorithm
 | |
|     rugpoint *glue;
 | |
|     rugpoint *getglue() {
 | |
|       return glue ? (glue = glue->getglue()) : this;
 | |
|       }
 | |
|     hyperpoint& gluenative() {
 | |
|       return glue->native;
 | |
|       }
 | |
|     rugpoint() { glue = NULL; }
 | |
|     void glueto(rugpoint *x) {
 | |
|       x = x->getglue();
 | |
|       auto y = getglue();
 | |
|       if(x != y) y->glue = x;
 | |
|       }
 | |
|     int dexp_id;
 | |
|     dexp_data surface_point;
 | |
|     };
 | |
| 
 | |
|   struct triangle {
 | |
|     rugpoint *m[3];
 | |
|     triangle() { m[0] = m[1] = m[2] = nullptr; }
 | |
|     triangle(rugpoint *m1, rugpoint *m2, rugpoint *m3) {
 | |
|       m[0] = m1; m[1] = m2; m[2] = m3;
 | |
|       }
 | |
|     };
 | |
| #endif
 | |
| 
 | |
| EX vector<rugpoint*> points;
 | |
| EX vector<triangle> triangles;
 | |
| 
 | |
| int when_enabled;
 | |
| 
 | |
| struct rug_exception { };
 | |
| 
 | |
| EX bool fast_euclidean = true;
 | |
| EX bool good_shape;
 | |
| EX bool subdivide_first = false;
 | |
| EX bool spatial_rug = false;
 | |
| 
 | |
| EX bool subdivide_further();
 | |
| EX void subdivide();
 | |
| 
 | |
| EX ld modelscale = 1;
 | |
| EX ld model_distance = 4;
 | |
| 
 | |
| #if HDR
 | |
| constexpr eGeometry rgHyperbolic = gSpace534;
 | |
| constexpr eGeometry rgEuclid = gCubeTiling;
 | |
| constexpr eGeometry rgSphere = gCell120;
 | |
| constexpr eGeometry rgElliptic = gECell120;
 | |
| #endif
 | |
| 
 | |
| EX eGeometry gwhere = rgEuclid;
 | |
| 
 | |
| #if HDR
 | |
| #define USING_NATIVE_GEOMETRY_IN_RUG dynamicval<eGeometry> gw(geometry, rug::rugged ? hr::rug::gwhere : geometry)
 | |
| #define USING_NATIVE_GEOMETRY dynamicval<eGeometry> gw(geometry, hr::rug::gwhere)
 | |
| #endif
 | |
| 
 | |
| // hypersian rug datatypes and globals
 | |
| //-------------------------------------
 | |
| 
 | |
| EX bool rugged = false;
 | |
| bool genrug = false;
 | |
| 
 | |
| EX int vertex_limit = 20000;
 | |
| 
 | |
| EX bool renderonce  = false;
 | |
| EX int renderlate  = 0;
 | |
| EX bool rendernogl  = false;
 | |
| EX int  texturesize = 1024;
 | |
| EX ld scale = 1;
 | |
| 
 | |
| EX ld anticusp_factor = 1;
 | |
| EX ld anticusp_dist;
 | |
| 
 | |
| EX ld err_zero = 1e-3, err_zero_current, current_total_error;
 | |
| 
 | |
| EX int queueiter, qvalid, dt;
 | |
| 
 | |
| EX rugpoint *finger_center;
 | |
| EX ld finger_range = .1;
 | |
| EX ld finger_force = 1;
 | |
| 
 | |
| #define rconf (vid.rug_config)
 | |
| 
 | |
| EX bool perspective() { return models::is_perspective(rconf.model); }
 | |
| 
 | |
| void push_point(hyperpoint& h, int coord, ld val) {
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   if(fast_euclidean && euclid) 
 | |
|     h[coord] += val;
 | |
|   else if(!val) return;
 | |
|   else {
 | |
|     h = cpush(coord, val) * h;
 | |
|     }
 | |
|   }
 | |
|   
 | |
| EX void push_all_points(int coord, ld val) {
 | |
|   if(!val) return;
 | |
|   else for(int i=0; i<isize(points); i++)
 | |
|     push_point(points[i]->native, coord, val);
 | |
|   }
 | |
| 
 | |
| // construct the graph
 | |
| //---------------------
 | |
| 
 | |
| int hyprand;
 | |
| 
 | |
| bool rug_euclid() { USING_NATIVE_GEOMETRY; return euclid; }
 | |
| bool rug_hyperbolic() { USING_NATIVE_GEOMETRY; return hyperbolic; }
 | |
| bool rug_sphere() { USING_NATIVE_GEOMETRY; return sphere; }
 | |
| bool rug_elliptic() { USING_NATIVE_GEOMETRY; return elliptic; }
 | |
| 
 | |
| EX map<cell*, rugpoint*> rug_map;
 | |
| 
 | |
| EX rugpoint *addRugpoint(shiftpoint h, double dist) {
 | |
|   rugpoint *m = new rugpoint;
 | |
|   m->h = h;
 | |
|   
 | |
|   /*
 | |
|   ld tz = pconf.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] * pconf.scale) / 2;
 | |
|   m->y1 = (1 - onscreen[1] * pconf.scale) / 2;
 | |
|   m->valid = false;
 | |
| 
 | |
|   if(euclid && quotient && !closed_manifold) {
 | |
|     hyperpoint h1 = iso_inverse(models::euclidean_spin) * eumove(euc::eu.user_axes[1]) * 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];
 | |
| 
 | |
|     USING_NATIVE_GEOMETRY;
 | |
|     hyperpoint hpoint = ypush(modelscale) * xpush0(modelscale * d * TAU);
 | |
|     ld hpdist = hdist0(hpoint);
 | |
|     ld z = hypot_d(2, hpoint);
 | |
|     if(z==0) z = 1;
 | |
|     hpoint = hpoint * hpdist / z;
 | |
|     
 | |
|     m->native = point31(hpoint[0], hpoint[1] * sin(a*TAU), hpoint[1]*cos(a*TAU));
 | |
|     }
 | |
|   else if(sphere) {
 | |
|     m->valid = good_shape = true;
 | |
|     ld scale;
 | |
|     USING_NATIVE_GEOMETRY;
 | |
|     if(euclid) {
 | |
|       scale = modelscale;
 | |
|       }
 | |
|     else if(hyperbolic) {
 | |
|       // sinh(scale) = modelscale
 | |
|       scale = asinh(modelscale);
 | |
|       }
 | |
|     else if(sphere) {
 | |
|       if(modelscale >= 1) 
 | |
|         // do as good as we can...
 | |
|         scale = 90._deg - 1e-3, good_shape = false, m->valid = false;
 | |
|       else scale = asin(modelscale);
 | |
|       }
 | |
|     else
 | |
|       scale = 1;
 | |
|     m->native = unshift(h) * scale;
 | |
|     m->native = hpxy3(m->native[0], m->native[1], m->native[2]);
 | |
|     }
 | |
| 
 | |
|   else if(euclid && rug_euclid()) {
 | |
|     m->native = unshift(h) * modelscale;
 | |
|     m->native[2] = 0;
 | |
|     #if MAXMDIM >= 4
 | |
|     m->native[3] = 1;
 | |
|     #endif
 | |
|     m->valid = good_shape = true;
 | |
|     }
 | |
|   
 | |
|   else if(rug_hyperbolic() && euclid) {
 | |
|     m->valid = good_shape = true;
 | |
|     USING_NATIVE_GEOMETRY;
 | |
|     m->native = tC0(parabolic13(h[0]*modelscale, h[1]*modelscale));
 | |
|     }
 | |
| 
 | |
|   else if(rug_hyperbolic() && hyperbolic && modelscale >= 1) {
 | |
|     m->valid = good_shape = true;
 | |
| 
 | |
|     // radius of the equidistant
 | |
|     ld r = acosh(modelscale);
 | |
|     
 | |
|     h[3] = h[2]; h[2] = 0;
 | |
|     
 | |
|     USING_NATIVE_GEOMETRY;
 | |
|     
 | |
|     m->native = rgpushxto0(unshift(h)) * cpush0(2, r);
 | |
|     }
 | |
|   
 | |
|   else {
 | |
|     m->native = unshift(h);
 | |
|     ld hd = h[LDIM];
 | |
|     for(int d=GDIM; d<MAXMDIM-1; d++) {
 | |
|       m->native[d] = (hd - .99) * (rand() % 1000 - rand() % 1000) / 1000;
 | |
|       }
 | |
|     USING_NATIVE_GEOMETRY;
 | |
|     #if MAXMDIM >= 4
 | |
|     m->native[3] = 1;
 | |
|     m->native = normalize(m->native);
 | |
|     #endif
 | |
|     }
 | |
| 
 | |
|   m->inqueue = false;
 | |
|   m->dist = dist;
 | |
|   points.push_back(m);
 | |
|   return m;
 | |
|   }
 | |
| 
 | |
| EX rugpoint *findRugpoint(shiftpoint h) {
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   for(int i=0; i<isize(points); i++) 
 | |
|     if(geo_dist_q(points[i]->h.h, unshift(h, points[i]->h.shift)) < 1e-5) return points[i];
 | |
|   return NULL;
 | |
|   }
 | |
| 
 | |
| EX rugpoint *findOrAddRugpoint(shiftpoint 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);
 | |
|   }
 | |
| 
 | |
| EX 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);
 | |
|   }
 | |
| 
 | |
| EX void addTriangle(rugpoint *t1, rugpoint *t2, rugpoint *t3, ld len IS(1)) {
 | |
|   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<pair<rugpoint*, rugpoint*>, 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);
 | |
|   }
 | |
| 
 | |
| EX void sort_rug_points() {
 | |
|   sort(points.begin(), points.end(), psort);
 | |
|   }
 | |
| 
 | |
| void calcLengths() {
 | |
|   for(auto p: points) 
 | |
|     for(auto& edge: p->edges) 
 | |
|       edge.len = geo_dist_q(p->h.h, unshift(edge.target->h, p->h.shift)) * modelscale;
 | |
|   }
 | |
| 
 | |
| EX 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 * pconf.scale;
 | |
|   }
 | |
| 
 | |
| #if HDR
 | |
| struct clifford_torus {
 | |
|   transmatrix T;
 | |
|   transmatrix iT;
 | |
|   ld xfactor, yfactor;
 | |
|   bool flipped;
 | |
|   hyperpoint xh, yh;
 | |
|   hyperpoint torus_to_s4(hyperpoint t);  
 | |
|   hyperpoint actual_to_torus(const hyperpoint& a) {
 | |
|     return iT * a;
 | |
|     }
 | |
|   hyperpoint torus_to_actual(const hyperpoint& t) {
 | |
|     return T * t;
 | |
|     }
 | |
|   clifford_torus();
 | |
|   ld get_modelscale() {
 | |
|     return hypot_d(2, xh) * xfactor * TAU;
 | |
|     }
 | |
|   ld compute_mx();  
 | |
|   };
 | |
| #endif
 | |
| 
 | |
| struct hyperpoint clifford_torus::torus_to_s4(hyperpoint t) {
 | |
|   double alpha = -t[0] * TAU;
 | |
|   double beta = t[1] * TAU;
 | |
|   
 | |
|   ld ax = alpha + 1.124651, bx = beta + 1.214893;
 | |
|   return hyperpoint(
 | |
|     xfactor * sin(ax), 
 | |
|     xfactor * cos(ax), 
 | |
|     yfactor * sin(bx),
 | |
|     yfactor * cos(bx)
 | |
|     );
 | |
|   }
 | |
| 
 | |
| clifford_torus::clifford_torus() {
 | |
|   auto p1 = to_loc(euc::eu.user_axes[0]);
 | |
|   auto p2 = to_loc(euc::eu.user_axes[1]);
 | |
| 
 | |
|   bool f = embedded_plane;
 | |
|   if(f) geom3::light_flip(true);
 | |
|   xh = euc::eumove(p1)*C0-C0;
 | |
|   yh = euc::eumove(p2)*C0-C0;
 | |
|   if(f) geom3::light_flip(false);
 | |
|   xh[2] = xh[3] = yh[2] = yh[3] = 0;
 | |
|   dynamicval<eGeometry> g(geometry, gCubeTiling);
 | |
| 
 | |
|   if(nonorientable) yh *= 2;
 | |
|   
 | |
|   flipped = false; // sqhypot_d(2, xh) < sqhypot_d(2, yh);
 | |
|   
 | |
|   if(flipped) swap(xh, yh);
 | |
|   
 | |
|   ld factor2 = sqhypot_d(2, xh) / sqhypot_d(2, yh);
 | |
|   ld factor = sqrt(factor2);
 | |
| 
 | |
|   yfactor = sqrt(1/(1+factor2));
 | |
|   xfactor = factor * yfactor;
 | |
|                                     
 | |
|   T = build_matrix(xh, yh, C02, C03);
 | |
|   iT = inverse(T);
 | |
|   }
 | |
| 
 | |
| ld clifford_torus::compute_mx() {
 | |
|   ld mx = 0;
 | |
|   for(int i=0; i<1000; i++)
 | |
|     mx = max(mx, hypot(xfactor, yfactor * sin(i)) / (1-yfactor * cos(i)));    
 | |
|   println(hlog, "mx = ", mx);
 | |
|   return mx;
 | |
|   }
 | |
| 
 | |
| #if MAXMDIM >= 4
 | |
| EX void buildTorusRug() {
 | |
| 
 | |
|   calcparam_rug();
 | |
|   models::configure();
 | |
|   
 | |
|   clifford_torus ct;
 | |
| 
 | |
|   if(gwhere == gSphere) 
 | |
|     modelscale = ct.get_modelscale();
 | |
|   
 | |
|   cell *gs = currentmap->gamestart();  
 | |
|   map<pair<int, int>, rugpoint*> glues;
 | |
|   
 | |
|   ld mx = ct.compute_mx();
 | |
|     
 | |
|   auto addToruspoint = [&] (hyperpoint h) {
 | |
|     auto r = addRugpoint(shiftless(C0), 0);
 | |
|     hyperpoint onscreen;
 | |
|     shiftpoint h1 = gmatrix[gs] * ct.torus_to_actual(h);
 | |
|     applymodel(h1, onscreen);
 | |
|     r->x1 = onscreen[0];
 | |
|     r->y1 = onscreen[1];
 | |
|     
 | |
|     if(1) {
 | |
|       USING_NATIVE_GEOMETRY;
 | |
|       hyperpoint hp = ct.torus_to_s4(h);
 | |
|       
 | |
|       /* spherical coordinates are already good, otherwise... */
 | |
|       
 | |
|       if(!sphere) {
 | |
|         /* stereographic projection to get Euclidean conformal torus */
 | |
|         hp /= (hp[3]+1);
 | |
|         hp /= mx;
 | |
|         #if MAXMDIM >= 4
 | |
|         hp[3] = 1;
 | |
|         #endif
 | |
|         }
 | |
|       
 | |
|       /* ... in H^3, use inverse Poincare to get hyperbolic conformal torus */
 | |
| 
 | |
|       if(hyperbolic) 
 | |
|         hp = perspective_to_space(hp, 1, gcHyperbolic);
 | |
|       
 | |
|       r->native = hp;
 | |
|       }
 | |
|         
 | |
|     r->valid = true;
 | |
|     
 | |
|     static constexpr int X = 100003; // a prime
 | |
|     auto gluefun = [] (ld z) { return int(frac(z + .5/X) * X); };
 | |
|     auto p = make_pair(gluefun(h[0]), gluefun(h[1]));
 | |
|     auto& r2 = glues[p];
 | |
|     if(r2) r->glueto(r2); else r2 = r;
 | |
|     return r;
 | |
|     };
 | |
|     
 | |
|   int rugmax = (int) sqrt(vertex_limit / isize(currentmap->allcells()));
 | |
|   if(rugmax < 1) rugmax = 1;
 | |
|   if(rugmax > 16) rugmax = 16;
 | |
|   
 | |
|   ld rmd = rugmax;
 | |
|   
 | |
|   hyperpoint sx = ct.iT * (currentmap->adj(gs, 0)*C0-C0)/rmd;
 | |
|   hyperpoint sy = ct.iT * (currentmap->adj(gs, 1)*C0-C0)/rmd;
 | |
|   
 | |
|   for(int leaf=0; leaf<(nonorientable ? 2 : 1); leaf++)
 | |
|   for(cell *c: currentmap->allcells()) {
 | |
|         
 | |
|     hyperpoint h = ct.iT * calc_relative_matrix(c, gs, C0) * C0;
 | |
|     
 | |
|     if(leaf) h[ct.flipped ? 0 : 1] += .5;
 | |
|     
 | |
|     rugpoint *rugarr[32][32];
 | |
|     for(int yy=0; yy<=rugmax; yy++)
 | |
|     for(int xx=0; xx<=rugmax; xx++)
 | |
|       rugarr[yy][xx] = addToruspoint(h+xx*sx+yy*sy);
 | |
|     
 | |
|     for(int yy=0; yy<rugmax; yy++)
 | |
|     for(int xx=0; xx<rugmax; xx++)
 | |
|       addTriangle(rugarr[yy][xx], rugarr[yy+1][xx], rugarr[yy+1][xx+1], modelscale/rugmax),
 | |
|       addTriangle(rugarr[yy][xx+1], rugarr[yy][xx], rugarr[yy+1][xx+1], modelscale/rugmax);
 | |
|     }
 | |
|   
 | |
|   double maxz = 0;
 | |
|   
 | |
|   for(auto p: points)
 | |
|     maxz = max(maxz, max(abs(p->x1), abs(p->y1)));
 | |
|   
 | |
|   if(1) pconf.scale = 1 / maxz;
 | |
| 
 | |
|   if(1) for(auto p: points)
 | |
|     p->x1 = (1 + pconf.scale * p->x1)/2,
 | |
|     p->y1 = (1 - pconf.scale * p->y1)/2;
 | |
|   
 | |
|   qvalid = 0;
 | |
|   for(auto p: points) if(!p->glue) qvalid++;
 | |
|   println(hlog, "qvalid = ", qvalid);
 | |
| 
 | |
|   return;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
| EX void verify() {
 | |
|   vector<ld> ratios;
 | |
|   for(auto m: points)
 | |
|     for(auto& e: m->edges) {
 | |
|       auto m2 = e.target;
 | |
|       ld l = e.len;
 | |
|       
 | |
|       USING_NATIVE_GEOMETRY;
 | |
|       ld l0 = geo_dist_q(m->native, m2->native);
 | |
| 
 | |
|       ratios.push_back(l0 / l);
 | |
|       }
 | |
|     
 | |
|   println(hlog, "Length verification:");
 | |
|   sort(ratios.begin(), ratios.end());
 | |
|   for(int i=0; i<isize(ratios); i += isize(ratios) / 10)
 | |
|     println(hlog, ratios[i]);
 | |
|   println(hlog);
 | |
|   }
 | |
| 
 | |
| void comp(cell*& minimum, cell *next) {
 | |
|   int nc = next->cpdist, mc = minimum->cpdist;
 | |
|   if(tie(nc, next) < tie(mc, minimum))
 | |
|     minimum = next;
 | |
|   }
 | |
| 
 | |
| EX void buildRug() {
 | |
| 
 | |
|   need_mouseh = true;
 | |
|   good_shape = false;
 | |
|   #if MAXMDIM >= 4
 | |
|   if(euclid && closed_manifold) {
 | |
|     good_shape = true;
 | |
|     buildTorusRug();
 | |
|     return;
 | |
|     }
 | |
|   #endif
 | |
|   
 | |
|   celllister cl(centerover ? centerover : cwt.at, get_sightrange(), vertex_limit, NULL);
 | |
| 
 | |
|   for(int i=0; i<isize(cl.lst); i++)
 | |
|     rug_map[cl.lst[i]] = addRugpoint(ggmatrix(cl.lst[i])*C0, cl.dists[i]);
 | |
| 
 | |
|   for(auto& p: rug_map) {
 | |
|     cell *c = p.first;
 | |
|     rugpoint *v = p.second;
 | |
|     
 | |
|     if(arcm::in() || arb::in() || (euclid && quotient)) {
 | |
|       vector<rugpoint*> p(c->type+1);
 | |
|       for(int j=0; j<c->type; j++) p[j] = findOrAddRugpoint(ggmatrix(c) * get_corner_position(c, j), v->dist);
 | |
|       for(int j=0; j<c->type; j++) addTriangle(v, p[j], p[(j+1) % c->type]);
 | |
|       
 | |
|       if((euclid && quotient) && nonorientable) {
 | |
|         shiftmatrix T = ggmatrix(c) * eumove(euc::eu.user_axes[1]);
 | |
|         rugpoint *Tv = addRugpoint(T * C0, 0);
 | |
|         for(int j=0; j<c->type; j++) p[j] = findOrAddRugpoint(T * get_corner_position(c, j), v->dist);
 | |
|         for(int j=0; j<c->type; j++) addTriangle(Tv, p[j], p[(j+1) % c->type]);
 | |
|         }
 | |
|       }
 | |
|     
 | |
|     else for(int j=0; j<c->type; j++) try {
 | |
|       cell *c2 = c->move(j);
 | |
|       rugpoint *w = rug_map.at(c2);
 | |
|       // if(v<w) addEdge(v, w);
 | |
|       
 | |
|       cell *c3 = c->modmove(j+1);
 | |
|       rugpoint *w2 = rug_map.at(c3);
 | |
|       
 | |
|       cell *c4 = (cellwalker(c,j) + wstep - 1).cpeek();      
 | |
|       
 | |
|       if(c4 != c3) {
 | |
|         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(const std::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<rugpoint*> pqueue;
 | |
| 
 | |
| EX 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;
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   // double rd = geo_dist_q(m1.h, m2.h) * xd;
 | |
|   double t = sqhypot_d(3, m1.native - m2.native);
 | |
|   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; i<3; i++) {
 | |
|     double di = (m2.native[i] - m1.native[i]) * force;
 | |
|     m1.native[i] += di * d1;
 | |
|     m2.native[i] -= di * d2;
 | |
|     if(nonzero && d2>0) 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(rug_euclid() && fast_euclidean) {
 | |
|     return force_euclidean(m1, m2, rd, is_anticusp, d1, d2);
 | |
|     }
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   
 | |
|   ld t = geo_dist_q(m1.native, m2.native);
 | |
|   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 = iso_inverse(rgpushxto0(m1.native));
 | |
|   hyperpoint ie = inverse_exp(shiftless(T * m2.native));
 | |
| 
 | |
|   transmatrix iT = rgpushxto0(m1.native);
 | |
|   
 | |
|   for(int i=0; i<MXDIM; i++) if(std::isnan(m1.native[i])) { 
 | |
|     addMessage("Failed!");
 | |
|     println(hlog, "m1 = ", m1.native);
 | |
|     throw rug_exception();
 | |
|     }
 | |
| 
 | |
|   m1.native = iT * direct_exp(ie * (d1*forcev/t));
 | |
|   m2.native = iT * direct_exp(ie * ((t-d2*forcev)/t));
 | |
| 
 | |
|   if(nonzero && d2>0) enqueue(&m2);
 | |
|   return nonzero;
 | |
|   }
 | |
| 
 | |
| vector<pair<ld, rugpoint*> > preset_points;
 | |
| 
 | |
| EX void preset(rugpoint *m) {
 | |
|   if(GDIM == 3) return;
 | |
|   int q = 0;
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   hyperpoint h = Hypc;
 | |
|   
 | |
|   preset_points.clear();
 | |
|   
 | |
|   for(int j=0; j<isize(m->edges); j++)
 | |
|   for(int k=0; k<j; k++) {
 | |
|     rugpoint *a = m->edges[j].target;
 | |
|     rugpoint *b = m->edges[k].target;
 | |
|     if(!a->valid) continue;
 | |
|     if(!b->valid) continue;
 | |
|     double blen = -1;
 | |
|     for(int j2=0; j2<isize(a->edges); j2++) 
 | |
|       if(a->edges[j2].target == b) blen = a->edges[j2].len;
 | |
|     if(blen <= 0) continue;
 | |
|     for(int j2=0; j2<isize(a->edges); j2++) 
 | |
|     for(int k2=0; k2<isize(b->edges); 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->native - a->native - cz * (b->native-a->native)) / ch;
 | |
| 
 | |
|       // m->h = a->h + (b->h-a->h) * az - ah * ort
 | |
|       hyperpoint res = a->native + (b->native-a->native) * az - ah * ort;
 | |
|       
 | |
|       h += res;
 | |
|       
 | |
|       preset_points.emplace_back(hypot(blen * (ah+ch), blen * (az-cz)), c);
 | |
|       q++;
 | |
|       }
 | |
|     }
 | |
|     
 | |
|   #if MAXMDIM >= 4
 | |
|   if(q>0) m->native = normalize(h);
 | |
|   #else
 | |
|   if(q>0) m->native = h / q;
 | |
|   #endif
 | |
| 
 | |
|   if(std::isnan(m->native[0]) || std::isnan(m->native[1]) || std::isnan(m->native[2]))
 | |
|     throw rug_exception();
 | |
|   }
 | |
| 
 | |
| ld sse(const hyperpoint& h) {
 | |
|   ld sse = 0;
 | |
|   for(auto& p: preset_points) {
 | |
|     ld l = p.first;
 | |
|     USING_NATIVE_GEOMETRY;
 | |
|     ld l0 = geo_dist_q(h, p.second->native);
 | |
|     sse += (l0-l) * (l0-l);
 | |
|     }
 | |
|   
 | |
|   return sse;
 | |
|   }
 | |
| 
 | |
| EX 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(!rug_euclid()) {
 | |
|       ld cur = sse(m->native);
 | |
|       for(int it=0; it<500; it++) {
 | |
|         ld ex = exp(-it/60);
 | |
|         again:
 | |
|         hyperpoint last = m->native;
 | |
|         USING_NATIVE_GEOMETRY;
 | |
|         m->native = rgpushxto0(m->native) * cpush0((it/2)%3, (it&1)?ex:-ex);
 | |
|         ld now = sse(m->native);
 | |
|         if(now < cur) { cur = now; ex *= 1.2; goto again; }
 | |
|         else m->native = last;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   for(int it=0; it<50; it++) 
 | |
|     for(int j=0; j<isize(m->edges); j++)
 | |
|       force(*m, *m->edges[j].target, m->edges[j].len, false, 1, 0);
 | |
|   }
 | |
| 
 | |
| EX int divides;
 | |
| EX int precision_increases;
 | |
| bool stop = false;
 | |
| 
 | |
| EX bool subdivide_further() {
 | |
|   if(euclid && closed_manifold) return false;
 | |
|   if(GDIM == 3) return false;
 | |
|   return isize(points) * 4 < vertex_limit;
 | |
|   }
 | |
| 
 | |
| EX void subdivide() {
 | |
|   int N = isize(points);
 | |
|   // if(euclid && gwhere == gEuclid) return;
 | |
|   if(!subdivide_further()) {
 | |
|     if(euclid && !closed_manifold && gwhere == gEuclid) {
 | |
|       println(hlog, "Euclidean -- full precision");
 | |
|       stop = true; 
 | |
|       }
 | |
|     else {
 | |
|       precision_increases++;
 | |
|       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<triangle> otriangles = triangles;
 | |
|   triangles.clear();
 | |
|   
 | |
|   halves.clear();
 | |
| 
 | |
|   // subdivide edges
 | |
|   for(int i=0; i<N; i++) {
 | |
|     rugpoint *m = points[i];
 | |
|     for(int j=0; j<isize(m->edges); 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) {
 | |
|         #if MAXMDIM >= 4
 | |
|         USING_NATIVE_GEOMETRY;
 | |
|         mm->native = mid(m->native, m2->native);
 | |
|         #else
 | |
|         mm->native = (m->native + m2->native) / 2;
 | |
|         #endif
 | |
|         }
 | |
|       mm->valid = m->valid && m2->valid; 
 | |
|       if(mm->valid) qvalid++;
 | |
|       mm->inqueue = false; enqueue(mm);
 | |
|       }
 | |
|      m->edges.clear();
 | |
|      }
 | |
|     
 | |
|   for(int i=0; i<isize(otriangles); i++)
 | |
|     addTriangle1(otriangles[i].m[0], otriangles[i].m[1], otriangles[i].m[2]);
 | |
|     
 | |
|   calcLengths();
 | |
| 
 | |
|   println(hlog, "result ", make_tuple(isize(points), isize(triangles)));
 | |
|   }
 | |
| 
 | |
| EX ld modeldist(const hyperpoint& h1, const hyperpoint& h2) {
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   return geo_dist_q(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: case gcSol: case gcNIH: case gcSolN: case gcNil: case gcProduct: case gcSL2:
 | |
|       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: {
 | |
|       #if MAXMDIM >= 4
 | |
|       return acd_bin(h[0]) + acd_bin(h[1]) * sY + acd_bin(h[2]) * sZ + acd_bin(h[3]) * sT;
 | |
|       #else
 | |
|       return 0;
 | |
|       #endif
 | |
|       }
 | |
|     }
 | |
|   return 0;
 | |
|   }
 | |
| 
 | |
| void generate_deltas(vector<bincode>& 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->native, q->native) > 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<int, 3> stats = {{0,0,0}};
 | |
| 
 | |
|   map<bincode, vector<rugpoint*> > code_to_point;
 | |
|   for(auto p: points) if(p->valid)
 | |
|     code_to_point[get_bincode(p->native)].push_back(p);
 | |
|   
 | |
|   vector<bincode> 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(p<q) {
 | |
|     stats2[detect_cusp_at(p, q)]++;
 | |
|     }
 | |
| 
 | |
|   printf("cusp stats: %d/%d/%d | %d/%d/%d\n", stats[0], stats[1], stats[2], stats2[0], stats2[1], stats2[2]); */
 | |
| 
 | |
|   println(hlog, "cusp stats: ", stats);
 | |
|   return stats[2];
 | |
|   }
 | |
|   
 | |
| EX void addNewPoints() {
 | |
| 
 | |
|   if(anticusp_factor && detect_cusps())
 | |
|     return;
 | |
| 
 | |
|   if((euclid && quotient) || qvalid == isize(points)) {
 | |
|     subdivide();
 | |
|     return;
 | |
|     }
 | |
|   
 | |
|   ld dist = hdist0(points[qvalid]->h) + .1e-6;
 | |
|   
 | |
|   int oqvalid = qvalid;
 | |
| 
 | |
|   for(int i=0; i<isize(points); i++) {
 | |
|     rugpoint& m = *points[i];
 | |
|     bool wasvalid = m.valid;
 | |
|     m.valid = wasvalid || sphere || hdist0(m.h) <= dist;
 | |
|     if(m.valid && !wasvalid) {
 | |
|       qvalid++;
 | |
|       need_mouseh = true;  
 | |
|       
 | |
|       if(!good_shape) optimize(&m, i > 7);
 | |
| 
 | |
|       enqueue(&m);
 | |
|       }
 | |
|     }
 | |
|   if(qvalid != oqvalid) { println(hlog, "adding new points ", make_tuple(oqvalid, qvalid, isize(points), dist, dt, queueiter)); }
 | |
|   }
 | |
| 
 | |
| EX void physics() {
 | |
| 
 | |
|   #if CAP_CRYSTAL && MAXMDIM >= 4
 | |
|   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;
 | |
| 
 | |
| extern int besti;
 | |
| 
 | |
| #if CAP_ODS
 | |
| /* these functions are for the ODS projection, used in VR videos */
 | |
| 
 | |
| bool project_ods(hyperpoint h, hyperpoint& h1, hyperpoint& h2, bool eye) {
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   
 | |
|   return ods::project(h, h1, h2, eye);
 | |
| 
 | |
| //  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);
 | |
|   
 | |
|   
 | |
| //  printf("\n");
 | |
|   return true;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
| vector<glhr::ct_vertex> ct_array;
 | |
| 
 | |
| vector<glhr::ct_vertex> cp_array;
 | |
| 
 | |
| EX basic_textureinfo tinf;
 | |
| 
 | |
| void drawTriangle(triangle& t) {
 | |
|   for(int i=0; i<3; i++) {
 | |
|     if(!t.m[i]) return;
 | |
|     if(!t.m[i]->valid) return;
 | |
|     }
 | |
|   
 | |
|   ld col = 1;
 | |
|   if(true) {
 | |
|     hyperpoint hc = (t.m[1]->native - t.m[0]->native) ^ (t.m[2]->native - t.m[0]->native);  
 | |
|     double hch = hypot_d(3, hc);  
 | |
|     col = (2 + hc[0]/hch) / 3;
 | |
|     if(nonisotropic) col = (9+col) / 10;
 | |
|     }
 | |
| 
 | |
|   for(int i=0; i<3; i++)  {
 | |
|     curvepoint(t.m[i]->native);
 | |
|     tinf.tvertices.push_back(glhr::pointtogl(point3(t.m[i]->x1, t.m[i]->y1, col)));
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX struct renderbuffer *glbuf;
 | |
| 
 | |
| EX void prepareTexture() {
 | |
|   ensure_glbuf();
 | |
|   if(!glbuf) { rug::close(); return; }
 | |
|   resetbuffer rb;
 | |
|   
 | |
|   dynamicval<eStereo> d(vid.stereo_mode, sOFF);
 | |
|   dynamicval<ld> dl(levellines, 0);
 | |
|   #if CAP_VR
 | |
|   dynamicval<int> i(vrhr::state, 0);
 | |
|   #endif
 | |
|   calcparam_rug();
 | |
|   models::configure();
 | |
|   
 | |
|   glbuf->enable();
 | |
|   glbuf->clear(0);
 | |
| 
 | |
|   ptds.clear();
 | |
| 
 | |
|   #if CAP_QUEUE
 | |
|   draw_boundary(0);
 | |
|   draw_boundary(1);
 | |
|   
 | |
|   draw_model_elements();
 | |
|   #endif
 | |
| 
 | |
|   drawthemap();
 | |
|   if(finger_center) {
 | |
|     shiftmatrix V = rgpushxto0(finger_center->h);
 | |
|     queuestr(V, 0.5, "X", 0xFFFFFFFF, 2);
 | |
|     for(int i=0; i<72; i++)
 | |
|       queueline(V * xspinpush0(i*A_PI/32, finger_range), V * xspinpush0((i+1)*A_PI/32, finger_range), 0xFFFFFFFF, vid.linequality);
 | |
|     }
 | |
|   drawqueue();
 | |
|   calcparam();
 | |
|   rb.reset();
 | |
|   }
 | |
| 
 | |
| EX bool no_fog;
 | |
| 
 | |
| EX ld lowrug = 1e-2;
 | |
| EX ld hirug = 1e3;
 | |
| 
 | |
| EX GLuint alternate_texture;
 | |
| 
 | |
| EX bool rug_control() { return rug::rugged; }
 | |
| 
 | |
| #if HDR
 | |
| 
 | |
| struct using_rugview {
 | |
|   using_rugview() { if(rug_control()) swap(View, rugView), swap(geometry, gwhere); }
 | |
|   ~using_rugview() { if(rug_control()) swap(View, rugView), swap(geometry, gwhere); }
 | |
|   };
 | |
| 
 | |
| #endif
 | |
| 
 | |
| EX purehookset hooks_rugframe;
 | |
| 
 | |
| EX void drawRugScene() {
 | |
|   callhooks(hooks_rugframe);
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   tinf.texture_id = alternate_texture ? alternate_texture : glbuf->renderedTexture;
 | |
|   tinf.tvertices.clear();
 | |
|   
 | |
|   ptds.clear();
 | |
|   
 | |
|   for(auto t: triangles) drawTriangle(t);
 | |
|   
 | |
|   auto& rug = queuecurve(shiftless(Id), 0, 0xFFFFFFFF, PPR::LINE);
 | |
| 
 | |
|   dynamicval<transmatrix> tV(View, View);
 | |
|   View = Id; /* needed for vr */
 | |
| 
 | |
|   if(nonisotropic) {
 | |
|     transmatrix T2 = eupush( tC0(view_inverse(rugView)) );
 | |
|     NLP = rugView * T2;  
 | |
|     rug.V = shiftless(ortho_inverse(NLP) * rugView);
 | |
|     }
 | |
|   else {
 | |
|     rug.V = shiftless(rugView);
 | |
|     }
 | |
|    
 | |
|   rug.offset_texture = 0;
 | |
|   rug.tinf = &tinf;
 | |
|   if(levellines && disable_texture) rug.tinf = nullptr;
 | |
|   rug.flags = POLY_TRIANGLES | POLY_FAT | POLY_PRINTABLE | POLY_ALWAYS_IN | POLY_ISSIDE | POLY_SHADE_TEXTURE;
 | |
| 
 | |
|   dynamicval<projection_configuration> p(pconf, rconf);
 | |
|   calcparam();
 | |
|   
 | |
|   drawqueue();
 | |
|   }
 | |
| 
 | |
| // organization
 | |
| //--------------
 | |
| 
 | |
| EX transmatrix currentrot;
 | |
| 
 | |
| EX void close_glbuf() {
 | |
|   delete glbuf;
 | |
|   glbuf = nullptr;
 | |
|   }
 | |
| 
 | |
| EX void ensure_glbuf() {
 | |
|   if(glbuf) return;
 | |
|   glbuf = new renderbuffer(TEXTURESIZE, TEXTURESIZE, vid.usingGL && !rendernogl);
 | |
|   if(!glbuf->valid) {
 | |
|     addMessage(XLAT("Failed to enable"));
 | |
|     close_glbuf();
 | |
|     return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void reopen() {
 | |
|   if(rugged) return;
 | |
|   when_enabled = 0;
 | |
|   GLERR("before init");
 | |
|   ensure_glbuf();
 | |
|   if(!glbuf) { rugged = false; return; }
 | |
|   rugged = true;
 | |
|   if(renderonce && !disable_texture) prepareTexture();
 | |
|   if(!rugged) return;
 | |
|   }
 | |
| 
 | |
| EX bool display_warning = true;
 | |
| 
 | |
| EX void init_model() {
 | |
|   clear_model();
 | |
|   genrug = true;
 | |
|   drawthemap();
 | |
|   genrug = false;
 | |
|   
 | |
|   qvalid = 0; dt = 0; queueiter = 0;
 | |
|   err_zero_current = err_zero;
 | |
|   divides = 0;
 | |
|   precision_increases = 0;
 | |
|   
 | |
|   #if CAP_CRYSTAL
 | |
|   if(cryst && 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 && pconf.alpha > 1)
 | |
|       valid = false;
 | |
|     
 | |
|     if(display_warning && !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(const rug_exception&) {
 | |
|     close();
 | |
|     clear_model();
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void reset_view() {
 | |
|   rugView = Id;
 | |
|   if(perspective()) {
 | |
|     using_rugview urv;
 | |
|     shift_view(ztangent(model_distance));
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void init() {
 | |
|   if(dual::state) return;
 | |
|   reopen();
 | |
|   if(rugged) init_model();
 | |
|   reset_view();
 | |
|   }
 | |
| 
 | |
| EX void clear_model() {
 | |
|   triangles.clear();
 | |
|   for(int i=0; i<isize(points); i++) delete points[i];
 | |
|   rug_map.clear();
 | |
|   points.clear();
 | |
|   pqueue = queue<rugpoint*> ();
 | |
|   }
 | |
|   
 | |
| EX void close() {
 | |
|   if(!rugged) return;
 | |
|   rugged = false;
 | |
|   close_glbuf();
 | |
|   finger_center = NULL;
 | |
|   }
 | |
| 
 | |
| int lastticks;
 | |
| 
 | |
| ld protractor = 0;
 | |
| 
 | |
| #define CAP_HOLDKEYS (CAP_SDL && !ISWEB)
 | |
| 
 | |
| EX bool handlekeys(int sym, int uni) {
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   if(NUMBERKEY == '2') {
 | |
|     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 == '3') {
 | |
|     #if CAP_CRYSTAL
 | |
|     if(in_crystal())
 | |
|       crystal::switch_z_coordinate();
 | |
|     else
 | |
|     #endif
 | |
|       rotate_view(cspin180(0, 2));
 | |
|     return true;
 | |
|     }
 | |
|   else if(NUMBERKEY == '4') {
 | |
|     #if CAP_CRYSTAL
 | |
|     if(in_crystal())
 | |
|       crystal::flip_z();
 | |
|     else
 | |
|     #endif
 | |
|       rotate_view(cspin90(0, 2));
 | |
|     return true;
 | |
|     }
 | |
|   #if CAP_CRYSTAL
 | |
|   else if(sym == SDLK_HOME && in_crystal()) {
 | |
|     crystal::next_home_orientation();
 | |
|     return true;
 | |
|     }
 | |
|   #endif
 | |
|   else return false;
 | |
|   }
 | |
| 
 | |
| EX void finger_on(int coord, ld val) {
 | |
|   for(auto p: points) {
 | |
|     ld d = hdist(finger_center->h, p->getglue()->h);
 | |
|     push_point(p->native, coord, val * finger_force * exp( - sqr(d / finger_range)));
 | |
|     }
 | |
|   enqueue(finger_center), good_shape = false;
 | |
|   }
 | |
| 
 | |
| transmatrix last_orientation;
 | |
| 
 | |
| EX ld move_on_touch = 1;
 | |
| 
 | |
| EX void actDraw() {
 | |
|   try {
 | |
| 
 | |
|   if(!renderonce && !(levellines && disable_texture)) prepareTexture();
 | |
|   else if(renderlate) {
 | |
|     renderlate--;
 | |
|     prepareTexture();
 | |
|     }
 | |
|   // do not display button
 | |
|   else playerfound = true;
 | |
|   current_display->set_viewport(0);
 | |
|   physics();
 | |
|   drawRugScene();
 | |
|   
 | |
|   #if CAP_HOLDKEYS
 | |
|   double alpha = (ticks - lastticks) / 1000.0;
 | |
|   lastticks = ticks;
 | |
|   #endif
 | |
| 
 | |
|   #if CAP_HOLDKEYS
 | |
|   const sdl_keystate_type *keystate = SDL12_GetKeyState(NULL);
 | |
|   if(keystate[SDL12(SDLK_LALT, SDL_SCANCODE_LALT)]) alpha /= 10;
 | |
|   #endif
 | |
| 
 | |
|   #if CAP_HOLDKEYS
 | |
|   auto perform_finger = [=] () {
 | |
|     if(keystate[SDL12(SDLK_HOME, SDL_SCANCODE_HOME)]) finger_range /= exp(alpha);
 | |
|     if(keystate[SDL12(SDLK_END, SDL_SCANCODE_END)]) finger_range *= exp(alpha);
 | |
|     if(keystate[SDL12(SDLK_LEFT, SDL_SCANCODE_LEFT)]) finger_on(0, -alpha);
 | |
|     if(keystate[SDL12(SDLK_RIGHT, SDL_SCANCODE_RIGHT)]) finger_on(0, alpha);
 | |
|     if(keystate[SDL12(SDLK_UP, SDL_SCANCODE_UP)]) finger_on(1, alpha);
 | |
|     if(keystate[SDL12(SDLK_DOWN, SDL_SCANCODE_DOWN)]) finger_on(1, -alpha);
 | |
|     if(keystate[SDL12(SDLK_PAGEDOWN, SDL_SCANCODE_PAGEDOWN)]) finger_on(2, -alpha);
 | |
|     if(keystate[SDL12(SDLK_PAGEUP, SDL_SCANCODE_PAGEUP)]) finger_on(2, +alpha);
 | |
|     };
 | |
| 
 | |
|   if(finger_center)
 | |
|     perform_finger();    
 | |
|   #endif
 | |
|     }
 | |
|   catch(const rug_exception&) {
 | |
|     rug::close();
 | |
|     }
 | |
|   }
 | |
| 
 | |
| int besti;
 | |
| 
 | |
| static constexpr ld RADAR_INF = 1e12;
 | |
| ld radar_distance = RADAR_INF;
 | |
| 
 | |
| EX shiftpoint gethyper(ld x, ld y) {
 | |
|   
 | |
|   projection_configuration bak = pconf;
 | |
|   pconf = rconf;
 | |
|   calcparam(); 
 | |
| 
 | |
|   double mx = (x - current_display->xcenter)/current_display->radius;
 | |
|   double my = (y - current_display->ycenter)/current_display->radius/pconf.stretch;
 | |
| 
 | |
|   bool vr = vrhr::active() && which_pointer;
 | |
|   transmatrix U;
 | |
| 
 | |
|   if(1) {
 | |
|     USING_NATIVE_GEOMETRY;
 | |
|     U = ortho_inverse(NLP) * rugView;
 | |
|     }
 | |
|   
 | |
|   #if CAP_VR
 | |
|   transmatrix T = Id;
 | |
|   if(vr) {
 | |
|     mx = my = 0;
 | |
|     E4;
 | |
|     vrhr::gen_mv();
 | |
|     T = vrhr::model_to_controller(which_pointer);
 | |
|     }
 | |
|   #endif
 | |
|   
 | |
|   calcparam();
 | |
|   
 | |
|   radar_distance = RADAR_INF;
 | |
|   
 | |
|   double rx1=0, ry1=0;
 | |
|   
 | |
|   bool found = false;
 | |
| 
 | |
|   for(int i=0; i<isize(triangles); i++) {
 | |
|     auto r0 = triangles[i].m[0];
 | |
|     auto r1 = triangles[i].m[1];
 | |
|     auto r2 = triangles[i].m[2];
 | |
|     if(!r2) continue;
 | |
|     if(!r0->valid || !r1->valid || !r2->valid) continue;
 | |
|     hyperpoint p0, p1, p2;
 | |
|     bool error = false;
 | |
|     int spherepoints = 0;
 | |
|     if(1) {
 | |
|       USING_NATIVE_GEOMETRY;
 | |
|       // USING_RUG_PMODEL;
 | |
|       // dynamicval<eModel> m(pmodel, mdEquidistant);
 | |
| 
 | |
|       if(elliptic && pmodel == mdDisk) {
 | |
|         int sp = 
 | |
|           (r0->native[3] < 0) + (r1->native[3] < 0) + (r2->native[3] < 0);
 | |
|         if(sp == 1 || sp == 2) continue;
 | |
|         }
 | |
|       
 | |
| 
 | |
|       auto find = [&] (const hyperpoint &native, hyperpoint& res) {
 | |
|         if(!vr) {
 | |
|           applymodel(shiftless(U * native), res);
 | |
|           }
 | |
|         #if CAP_VR
 | |
|         else {
 | |
|           dynamicval<int> vi(vrhr::state, 2);
 | |
|           bool bad;
 | |
|           res = vrhr::model_location(shiftless(U * native), bad);
 | |
|           if(bad) error = true;
 | |
|           E4; res[3] = 1; res = T * res;
 | |
|           }
 | |
|         #endif
 | |
|         };
 | |
| 
 | |
|       find(r0->native, p0);
 | |
|       find(r1->native, p1);
 | |
|       find(r2->native, p2);
 | |
|       
 | |
|       }
 | |
| 
 | |
|     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(in_perspective() && (p0[2] < 0 || p1[2] < 0 || p2[2] < 0) && (p0[2] > 0 || p1[2] > 0 || p2[2] > 0)) continue;
 | |
| 
 | |
|     if(tx >= 0 && ty >= 0 && tx+ty <= 1) {
 | |
|       double rz1 = p0[2] * (1-tx-ty) + p1[2] * tx + p2[2] * ty;
 | |
|       if(vr) rz1 = -rz1;
 | |
|       if(in_perspective() && !vr && rz1 < 0) { /* behind the eye, ignore */ }
 | |
|       else if(vr && rz1 < 0) { /* behind the controller, ignore */ }
 | |
|       else 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;
 | |
|         #if CAP_VR
 | |
|         if(vr) vrhr::pointer_distance = radar_distance;
 | |
|         #endif
 | |
|         }
 | |
|       found = true;
 | |
|       }
 | |
|     }
 | |
|   
 | |
|   pconf = bak;
 | |
|   if(!found) return shiftless(Hypc);
 | |
|   
 | |
|   double px = rx1 * TEXTURESIZE, py = (1-ry1) * TEXTURESIZE;
 | |
| 
 | |
|   calcparam_rug();
 | |
|   models::configure();
 | |
|   shiftpoint h = hr::gethyper(px, py);
 | |
|   calcparam();
 | |
| 
 | |
|   return h;
 | |
|   }
 | |
| 
 | |
| EX 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" */
 | |
|      ;
 | |
|   }
 | |
| 
 | |
| void change_texturesize() {
 | |
|   if(rugged) {
 | |
|     close();
 | |
|     reopen();
 | |
|     }
 | |
|   }
 | |
| 
 | |
| ld old_distance;
 | |
| 
 | |
| EX void rug_geometry_choice() {
 | |
|   cmode = sm::SIDE | sm::MAYDARK;
 | |
|   gamescreen();
 | |
|   dialog::init(XLAT("hypersian rug mode"), iinf[itPalace].color, 150, 100);
 | |
|   
 | |
|   USING_NATIVE_GEOMETRY; 
 | |
|   
 | |
|   dialog::addBoolItem("Euclidean", euclid, 'a');
 | |
|   dialog::add_action([] { gwhere = rgEuclid; popScreen(); });
 | |
| 
 | |
|   dialog::addBoolItem("hyperbolic", hyperbolic, 'b');
 | |
|   dialog::add_action([] { gwhere = rgHyperbolic; popScreen(); });
 | |
| 
 | |
|   dialog::addBoolItem("spherical", sphere && !elliptic, 'c');
 | |
|   dialog::add_action([] { gwhere = rgSphere; popScreen(); });
 | |
| 
 | |
|   dialog::addBoolItem("elliptic", elliptic, 'd');
 | |
|   dialog::add_action([] { gwhere = rgElliptic; popScreen(); });
 | |
| 
 | |
|   dialog::addBoolItem("Nil", nil, 'e');
 | |
|   dialog::add_action([] { gwhere = gNil; popScreen(); });
 | |
| 
 | |
|   dialog::addBoolItem("Solv", sol, 'e');
 | |
|   dialog::add_action([] { gwhere = gSol; popScreen(); });
 | |
| 
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
|   }
 | |
| 
 | |
| EX void show() {
 | |
|   cmode = sm::SIDE | sm::MAYDARK | sm::PANNING;
 | |
|   gamescreen();
 | |
|   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');
 | |
|   if(rug::rugged) {
 | |
|     dialog::addSelItem(XLAT("projection"), models::get_model_name(rconf.model), 'p'); 
 | |
|     }
 | |
|   else dialog::addBreak(100);
 | |
|   if(!rug::rugged) {
 | |
|     dynamicval<eGeometry> g(geometry, gwhere);
 | |
|     dialog::addSelItem(XLAT("native geometry"), geometry_name(), 'n');
 | |
|     if(gwhere == rgElliptic) dialog::lastItem().value += " (e)";
 | |
|     }
 | |
|   else
 | |
|     dialog::addSelItem(XLAT("radar"), radar_distance == RADAR_INF ? "∞" : fts(radar_distance, 4), '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 / degree) + "°", 'f');
 | |
|   if(!good_shape) {
 | |
|     dialog::addSelItem(XLAT("maximum error"), fts(err_zero), 'e');
 | |
|     if(rug::rugged)
 | |
|       dialog::lastItem().value += " (" + fts(err_zero_current) + ")";
 | |
|     }
 | |
|   #if ISMOBILE
 | |
|   dialog::addSelItem(XLAT("move on touch"), fts(move_on_touch), 'G');
 | |
|   #endif
 | |
|   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; });
 | |
|   edit_levellines('L');
 | |
| 
 | |
| #if CAP_SURFACE  
 | |
|   if(hyperbolic)
 | |
|     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) {
 | |
|     handlePanning(sym, 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(move_on_touch, -1, 1, .1, 0, XLAT("move on touch"), "");
 | |
|       dialog::get_ne().extra_options = anims::rug_angle_options;
 | |
|       }
 | |
|     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::get_ne().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::get_ne().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::get_ne().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;
 | |
|             if(adjust_points) p->native *= 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') {
 | |
|       pushScreen(models::model_menu);
 | |
|       }
 | |
|     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::get_di().reaction = [] () {
 | |
|         if(rug::rugged && perspective()) {
 | |
|           using_rugview rv;
 | |
|           shift_view(ztangent(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::get_di().reaction = [] () { err_zero_current = err_zero; };
 | |
|       }
 | |
|     else if(uni == 'f') 
 | |
|       pushScreen(showStereo);
 | |
|     #if MAXMDIM >= 4
 | |
|     else if(uni == 'n' && !rug::rugged) 
 | |
|       pushScreen(rug_geometry_choice);
 | |
|     #endif
 | |
| #if CAP_SDL
 | |
|     else if(uni == 'g' && !rug::rugged)
 | |
|       rendernogl = !rendernogl;
 | |
| #endif
 | |
|     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();
 | |
|     };
 | |
|   }
 | |
| 
 | |
| EX void select() {
 | |
|   if(dual::state) return;
 | |
|   pushScreen(rug::show);
 | |
|   }
 | |
| 
 | |
| EX void rug_save(string fname) {
 | |
|   fhstream f(fname, "wb");
 | |
|   if(!f.f) {
 | |
|     addMessage(XLAT("Failed to save embedding to %1", fname));
 | |
|     return;
 | |
|     }
 | |
|   f.write(f.vernum);
 | |
|   f.write(gwhere);
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   int N = isize(points);
 | |
|   f.write(N);
 | |
|   map<rugpoint*, int> ids;
 | |
|   for(int i=0; i<isize(points); i++) ids[points[i]] = i;
 | |
|   f.write(surface::sh);
 | |
|   for(int i=0; i<4; i++) for(int j=0; j<4; j++) 
 | |
|     f.write(rugView[i][j]);
 | |
|   auto get_id = [&] (rugpoint *r) {
 | |
|     if(!r) return int(-1);
 | |
|     return ids[r];
 | |
|     };
 | |
|   for(auto p: points) {
 | |
|     f.write(p->valid);
 | |
|     f.write(p->x1);
 | |
|     f.write(p->y1);
 | |
|     f.write(p->native);
 | |
|     f.write(get_id(p->glue));
 | |
|     }
 | |
|   int M = isize(triangles);
 | |
|   f.write(M);
 | |
|   for(auto t: triangles) {
 | |
|     f.write(get_id(t.m[0]));
 | |
|     f.write(get_id(t.m[1]));
 | |
|     f.write(get_id(t.m[2]));
 | |
|     }
 | |
| 
 | |
|   int cp = isize(surface::coverage);
 | |
|   f.write(cp);
 | |
|   for(auto p: surface::coverage) f.write(p.first), f.write(p.second);
 | |
|   }
 | |
| 
 | |
| EX void rug_load(string fname) {
 | |
|   clear_model();
 | |
|   fhstream f(fname, "rb");
 | |
|   if(!f.f) {
 | |
|     addMessage(XLAT("Failed to load embedding from %1", fname));
 | |
|     return;
 | |
|     }
 | |
|   f.read(f.vernum);
 | |
|   f.read(gwhere);
 | |
|   USING_NATIVE_GEOMETRY;
 | |
|   int N = f.get<int>();
 | |
|   println(hlog, "N = ", N);
 | |
|   points.resize(N);
 | |
|   for(int i=0; i<N; i++) 
 | |
|     points[i] = new rugpoint;
 | |
|   f.read(surface::sh);
 | |
|   for(int i=0; i<4; i++) for(int j=0; j<4; j++) 
 | |
|     f.read(rugView[i][j]);
 | |
|   auto by_id = [&] (rugpoint *& p) {
 | |
|     int i = f.get<int>();
 | |
|     if(i == -1) p = nullptr;
 | |
|     else p = points[i];
 | |
|     };
 | |
|   for(auto p: points) {
 | |
|     f.read(p->valid);
 | |
|     f.read(p->x1);
 | |
|     f.read(p->y1);
 | |
|     f.read(p->native);
 | |
|     by_id(p->glue);
 | |
|     }
 | |
|   triangles.resize(f.get<int>());
 | |
|   for(auto& t: triangles) {
 | |
|     by_id(t.m[0]);
 | |
|     by_id(t.m[1]);
 | |
|     by_id(t.m[2]);
 | |
|     }
 | |
| 
 | |
|   surface::coverage.resize(f.get<int>());
 | |
|   for(auto p: surface::coverage) f.read(p.first), f.read(p.second);
 | |
|   good_shape = true;
 | |
|   }
 | |
| 
 | |
| #if CAP_COMMANDLINE
 | |
| int rugArgs() {
 | |
|   using namespace arg;
 | |
|            
 | |
|   if(0) ;
 | |
|   else if(argis("-rugmodelscale")) {
 | |
|     shift_arg_formula(modelscale);
 | |
|     }
 | |
| 
 | |
|   else if(argis("-ruggeo")) {
 | |
|     shift(); gwhere = readGeo(args());
 | |
|     if(gwhere == gEuclid) gwhere = rgEuclid;
 | |
|     if(gwhere == gSphere) gwhere = rgSphere;
 | |
|     if(gwhere == gNormal) gwhere = rgHyperbolic;
 | |
|     if(gwhere == gElliptic) gwhere = rgElliptic;
 | |
|     }
 | |
| 
 | |
|   else if(argis("-rugpers")) {
 | |
|     USING_NATIVE_GEOMETRY;
 | |
|     rconf.model = nonisotropic ? mdGeodesic : mdPerspective;
 | |
|     }
 | |
| 
 | |
|   else if(argis("-rugonce")) {
 | |
|     renderonce = true;
 | |
|     }
 | |
| 
 | |
|   else if(argis("-rugsave")) {
 | |
|     shift(); rug_save(args());
 | |
|     }
 | |
| 
 | |
|   else if(argis("-rugload")) {
 | |
|     PHASE(3); 
 | |
|     start_game();
 | |
|     calcparam();
 | |
|     rug::init();
 | |
|     shift(); rug_load(args());
 | |
|     }
 | |
| 
 | |
|   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("-rugorth")) {
 | |
|     rconf.model = mdEquidistant;
 | |
|     }
 | |
| 
 | |
|   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();
 | |
|     err_zero_current = err_zero;
 | |
|     }
 | |
| 
 | |
|   else if(argis("-rugon")) {
 | |
|     PHASE(3); 
 | |
|     start_game();
 | |
|     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
 | |
| 
 | |
| auto clear_rug_map = addHook(hooks_clearmemory, 40, [] () { rug_map.clear(); });
 | |
| 
 | |
| EX }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| #if !CAP_RUG
 | |
| 
 | |
| // fake for mobile
 | |
| EX namespace rug {
 | |
|     EX bool rugged = false;
 | |
|     EX bool renderonce = false;
 | |
|     EX bool rendernogl = true;
 | |
|     EX bool mouse_control_rug = false;
 | |
|     EX int texturesize = 512;
 | |
|     EX ld scale = 1.0f;
 | |
|     EX bool rug_control() { return false; }
 | |
|     EX bool in_crystal() { return false; }
 | |
|     EX void reset_view() { }
 | |
|     EX void close() { }
 | |
| #if HDR
 | |
|   struct using_rugview {};
 | |
| #endif
 | |
| EX }
 | |
| 
 | |
| #endif
 | |
| }
 | 
