1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-11-27 14:37:16 +00:00
hyperrogue/rug.cpp
2019-09-12 22:50:08 +02:00

2129 lines
56 KiB
C++

// 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<rugpoint*> points;
vector<triangle> 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<eGeometry> 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; j<i; j++) vec[i] -= vec[j] * (vec[i] | vec[j]);
if(zero_d(3, vec[i])) {
// 'random' direction
vec[i] = hpxyz(1.12, 1.512+i, 1.12904+i);
for(int j=0; j<i; j++) vec[i] -= vec[j] * (vec[i] | vec[j]);
}
vec[i] /= hypot_d(3, vec[i]);
}
transmatrix M;
for(int i=0; i<3; i++) for(int j=0; j<3; j++)
M[i][j] = vec[j][i];
return M;
}
hyperpoint azeq_to_hyperboloid(hyperpoint h) {
if(abs(h[2])>1e-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<eGeometry> 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; i<isize(points); i++)
push_point(points[i]->flat, 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; d<rugdim; d++)
m->flat[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; i<isize(points); i++)
if(sqhypot_d(rugdim, points[i]->h - 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<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);
}
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<toruspoint> zeropoints;
vector<toruspoint> tps(qty);
auto& mode = tmodes[torus_mode];
bool single = mode.flags & TF_SINGLE;
bool klein = mode.flags & TF_KLEIN;
pair<toruspoint, toruspoint> solution;
if(single) {
for(int ax=-qty; ax<qty; ax++)
for(int ay=-qty; ay<qty; ay++) {
int v = (ax*dx + ay*dy) % qty;
if(v<0) v += qty;
toruspoint tp(ax, ay);
if(tps[v].d2() > 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 / cgi.crossf;
}
map<pair<int, int>, 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<getqty(); i++) {
int x, y;
if(single) {
x = tps[i].x;
y = tps[i].y;
}
else {
x = i % sdx;
y = i / sdx;
if(x > 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; 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)));
// 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<ld> 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; 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;
}
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<cell*, rugpoint *> vptr;
for(int i=0; i<isize(cl.lst); i++)
vptr[cl.lst[i]] = addRugpoint(ggmatrix(cl.lst[i])*C0, cl.dists[i]);
for(auto& p: vptr) {
cell *c = p.first;
rugpoint *v = p.second;
if(archimedean || euwrap) {
rugpoint *p[MAX_EDGE+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(euwrap && nonorientable) {
transmatrix T = ggmatrix(c) * eumove(torusconfig::sdx, torusconfig::sdy);
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 = vptr.at(c2);
// if(v<w) addEdge(v, w);
cell *c3 = c->modmove(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<rugpoint*> 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; i<rugdim; i++) {
double di = (m2.flat[i] - m1.flat[i]) * force;
m1.flat[i] += di * d1;
m2.flat[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(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<pair<ld, rugpoint*> > 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; 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->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; j<isize(m->edges); 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<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) {
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<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)));
}
ld slow_modeldist(const hyperpoint& h1, const hyperpoint& h2) {
normalizer n(h1, h2);
hyperpoint f1 = n(h1);
hyperpoint f2 = n(h2);
return hdist(f1, f2);
}
typedef array<ld, 4> hyperpoint4;
hyperpoint4 azeq_to_4(const hyperpoint& h) {
array<ld, 4> 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<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->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<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->flat)].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];
}
void addNewPoints() {
if(anticusp_factor && detect_cusps())
return;
if(euwrap || 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)); }
}
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<glhr::ct_vertex> ct_array;
vector<glhr::ct_vertex> cp_array;
void drawTriangle(triangle& t) {
int num = t.m[2] ? 3 : 2;
using namespace hyperpoint_vec;
for(int i=0; i<num; i++) {
if(!t.m[i]->valid) 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; i<num; i++)
pts[i] = t.m[i]->getglue()->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<hyperpoint, 6> 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<hyperpoint,3> h;
for(int i=0; i<num; i++) getco(t.m[i], h[i], spherepoints);
if(spherepoints == 1 || spherepoints == 2) return;
ld col = 1;
if(num == 3) {
hyperpoint hc = (h[1] - h[0]) ^ (h[2] - h[0]);
double hch = hypot_d(3, hc);
col = (2 + hc[0]/hch) / 3;
}
for(int i=0; i<num; i++)
(num==3?ct_array:cp_array).emplace_back(h[i], t.m[i]->x1, t.m[i]->y1, col);
}
renderbuffer *glbuf;
void prepareTexture() {
resetbuffer rb;
dynamicval<eStereo> 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; i<numplayers(); i++) if(multi::playerActive(i))
queueline(tC0(ggmatrix(playerpos(i))), mouseh, 0xFF00FF, 8 + vid.linequality);
}
if(finger_center) {
transmatrix V = rgpushxto0(finger_center->h);
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);
glhr::set_depthwrite(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<eModel> 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,
darkena(backcolor, 0, 0xFF)
);
for(int t=0; t<isize(triangles); t++)
drawTriangle(triangles[t]);
glhr::id_modelview();
if(isize(ct_array) > 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() {
if(dual::state) return;
reopen();
if(rugged) init_model();
}
void clear_model() {
triangles.clear();
for(int i=0; i<isize(points); i++) delete points[i];
points.clear();
pqueue = queue<rugpoint*> ();
}
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(next_orientation) * last_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; 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;
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 ? "" : 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 * 180 / M_PI) + "°", 'f');
if(!good_shape) {
dialog::addSelItem(XLAT("maximum error"), fts(err_zero), 'e');
if(rug::rugged)
dialog::lastItem().value += " (" + fts(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() {
if(dual::state) return;
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
}