// Hyperbolic Rogue -- basic computations in non-Euclidean geometry // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file hyperpoint.cpp * \brief basic computations in non-Euclidean geometry * * This implements hyperpoint (a point in non-Euclidean space), transmatrix (a transformation matrix), * and various basic routines related to them: rotations, translations, inverses and determinants, etc. * For nonisotropic geometries, it rather refers to nonisotropic.cpp. */ #include "hyper.h" namespace hr { #if HDR static const ld full_circle = 2 * M_PI; static const ld quarter_circle = M_PI / 2; static const ld degree = M_PI / 180; static const ld golden_phi = (sqrt(5)+1)/2; static const ld log_golden_phi = log(golden_phi); #endif eGeometry geometry; eVariation variation; #if HDR /** \brief A point in our continuous space * * Originally used for representing points in the hyperbolic plane. * Currently used for all kinds of supported spaces, as well as * for all vector spaces (up to 4 dimensions). We are using * the normalized homogeneous coordinates, which allows us to work with most * geometries in HyperRogue in a uniform way. * In the hyperbolic plane, this is the Minkowski hyperboloid model: * (x,y,z) such that x*x+y*y-z*z == -1 and z > 0. * * In spherical geometry, we have x*x+y*y+z*z == 1. * * In Euclidean geometry, we have z = 1. * * In isotropic 3D geometries an extra coordinate is added. * * In nonisotropic coordinates h[3] == 1. * * In product geometries the 'z' coordinate is modelled by multiplying all * three coordinates with exp(z). * */ struct hyperpoint : array { hyperpoint() {} #if MAXMDIM == 4 constexpr hyperpoint(ld x, ld y, ld z, ld w) : array {{x,y,z,w}} {} #else constexpr hyperpoint(ld x, ld y, ld z, ld w) : array {{x,y,z}} {} #endif inline hyperpoint& operator *= (ld d) { for(int i=0; i1 ? M_PI/2 : x<-1 ? -M_PI/2 : std::isnan(x) ? 0 : asin(x); } EX ld asin_auto_clamp(ld x) { switch(cgclass) { case gcEuclid: return x; case gcHyperbolic: return asinh(x); case gcSphere: return asin_clamp(x); default: return x; } } EX ld acos_auto_clamp(ld x) { switch(cgclass) { case gcHyperbolic: return x < 1 ? 0 : acosh(x); case gcSphere: return x > 1 ? 0 : x < -1 ? M_PI : acos(x); case gcProduct: return PIU(acos_auto_clamp(x)); default: return x; } } EX ld cos_auto(ld x) { switch(cgclass) { case gcEuclid: return 1; case gcHyperbolic: return cosh(x); case gcSphere: return cos(x); case gcProduct: return PIU(cos_auto(x)); default: return 1; } } EX ld tan_auto(ld x) { switch(cgclass) { case gcEuclid: return x; case gcHyperbolic: return tanh(x); case gcSphere: return tan(x); case gcProduct: return PIU(tan_auto(x)); default: return 1; } } EX ld atan_auto(ld x) { switch(cgclass) { case gcEuclid: return x; case gcHyperbolic: return atanh(x); case gcSphere: return atan(x); case gcProduct: return PIU(atan_auto(x)); default: return x; } } EX ld atan2_auto(ld y, ld x) { switch(cgclass) { case gcEuclid: return y/x; case gcHyperbolic: return atanh(y/x); case gcSphere: return atan2(y, x); case gcProduct: return PIU(atan2_auto(y, x)); default: return y/x; } } /** This function returns the length of the edge opposite the angle alpha in * a triangle with angles alpha, beta, gamma. This is called the cosine rule, * and of course works only in non-Euclidean geometry. */ EX ld edge_of_triangle_with_angles(ld alpha, ld beta, ld gamma) { return acos_auto((cos(alpha) + cos(beta) * cos(gamma)) / (sin(beta) * sin(gamma))); } EX hyperpoint hpxy(ld x, ld y) { return PIU(hpxyz(x,y, sl2 ? sqrt(1+x*x+y*y) : translatable ? 1 : sphere ? sqrt(1-x*x-y*y) : sqrt(1+x*x+y*y))); } EX hyperpoint hpxy3(ld x, ld y, ld z) { return hpxyz3(x,y,z, sl2 ? sqrt(1+x*x+y*y-z*z) :translatable ? 1 : sphere ? sqrt(1-x*x-y*y-z*z) : sqrt(1+x*x+y*y+z*z)); } #if HDR // a point (I hope this number needs no comments ;) ) constexpr hyperpoint Cx12 = hyperpoint(1,0,1.41421356237,0); constexpr hyperpoint Cx13 = hyperpoint(1,0,0,1.41421356237); #define Cx1 (GDIM==2?Cx12:Cx13) #endif EX bool zero_d(int d, hyperpoint h) { for(int i=0; i eps) return false; return true; } #if MAXMDIM >= 4 // in the 3D space, move the point h orthogonally to the (x,y) plane by z units EX hyperpoint orthogonal_move(const hyperpoint& h, ld z) { if(prod) return zshift(h, z); if(sl2) return slr::translate(h) * cpush0(2, z); if(!hyperbolic) return rgpushxto0(h) * cpush(2, z) * C0; if(nil) return nisot::translate(h) * cpush0(2, z); if(translatable) return hpxy3(h[0], h[1], h[2] + z); ld u = 1; if(h[2]) z += asin_auto(h[2]), u /= acos_auto(z); u *= cos_auto(z); return hpxy3(h[0] * u, h[1] * u, sinh(z)); } #endif // push alpha units vertically EX transmatrix ypush(ld alpha) { return cpush(1, alpha); } EX transmatrix zpush(ld z) { return cpush(2, z); } EX transmatrix matrix3(ld a, ld b, ld c, ld d, ld e, ld f, ld g, ld h, ld i) { #if MAXMDIM==3 return transmatrix {{{a,b,c},{d,e,f},{g,h,i}}}; #else if(GDIM == 2) return transmatrix {{{a,b,c,0},{d,e,f,0},{g,h,i,0},{0,0,0,1}}}; else return transmatrix {{{a,b,0,c},{d,e,0,f},{0,0,1,0},{g,h,0,i}}}; #endif } EX transmatrix matrix4(ld a, ld b, ld c, ld d, ld e, ld f, ld g, ld h, ld i, ld j, ld k, ld l, ld m, ld n, ld o, ld p) { #if MAXMDIM==3 return transmatrix {{{a,b,d},{e,f,h},{m,n,p}}}; #else return transmatrix {{{a,b,c,d},{e,f,g,h},{i,j,k,l},{m,n,o,p}}}; #endif } #if MAXMDIM >= 4 EX void swapmatrix(transmatrix& T) { for(int i=0; i<4; i++) swap(T[i][2], T[i][3]); for(int i=0; i<4; i++) swap(T[2][i], T[3][i]); if(GDIM == 3) { for(int i=0; i<4; i++) T[i][2] = T[2][i] = 0; T[2][2] = 1; } fixmatrix(T); for(int i=0; i<4; i++) for(int j=0; j<4; j++) if(isnan(T[i][j])) T = Id; } EX void swapmatrix(hyperpoint& h) { swap(h[2], h[3]); } #endif EX transmatrix parabolic1(ld u) { if(euclid) return ypush(u); else { ld diag = u*u/2; return matrix3( -diag+1, u, diag, -u, 1, u, -diag, u, diag+1 ); } } EX transmatrix parabolic13(ld u, ld v) { if(euclid) return ypush(u); else { ld diag = (u*u+v*v)/2; return matrix4( -diag+1, u, v, diag, -u, 1, 0, u, -v, 0, 1, v, -diag, u, v, diag+1 ); } } EX hyperpoint parabolic10(hyperpoint h) { if(euclid) { h[MDIM] = 1; return h; } else if(MDIM == 4) return hyperpoint(sinh(h[0]), h[1]/exp(h[0]), h[2]/exp(h[0]), cosh(h[0])); else return hyperpoint(sinh(h[0]), h[1]/exp(h[0]), cosh(h[0]), 0); } EX hyperpoint deparabolic10(const hyperpoint h) { if(euclid) return h; ld x = -log(h[LDIM] - h[0]); if(MDIM == 3) return hyperpoint(x, h[1] * exp(x), 1, 0); return point31(x, h[1] * exp(x), h[2] * exp(x)); } EX transmatrix spintoc(const hyperpoint& H, int t, int f) { transmatrix T = Id; ld R = hypot(H[f], H[t]); if(R >= 1e-12) { T[t][t] = +H[t]/R; T[t][f] = +H[f]/R; T[f][t] = -H[f]/R; T[f][f] = +H[t]/R; } return T; } /** an Euclidean rotation in the axes (t,f) which rotates * the point H to the positive 't' axis */ EX transmatrix rspintoc(const hyperpoint& H, int t, int f) { transmatrix T = Id; ld R = hypot(H[f], H[t]); if(R >= 1e-12) { T[t][t] = +H[t]/R; T[t][f] = -H[f]/R; T[f][t] = +H[f]/R; T[f][f] = +H[t]/R; } return T; } /** an isometry which takes the point H to the positive X axis * \see rspintox */ EX transmatrix spintox(const hyperpoint& H) { if(GDIM == 2 || prod) return spintoc(H, 0, 1); transmatrix T1 = spintoc(H, 0, 1); return spintoc(T1*H, 0, 2) * T1; } /** inverse of hr::spintox */ EX transmatrix rspintox(const hyperpoint& H) { if(GDIM == 2 || prod) return rspintoc(H, 0, 1); transmatrix T1 = spintoc(H, 0, 1); return rspintoc(H, 0, 1) * rspintoc(T1*H, 0, 2); } /** for H on the X axis, this matrix pushes H to C0 * \see gpushxto0 */ EX transmatrix pushxto0(const hyperpoint& H) { transmatrix T = Id; T[0][0] = +H[LDIM]; T[0][LDIM] = -H[0]; T[LDIM][0] = curvature() * H[0]; T[LDIM][LDIM] = +H[LDIM]; return T; } /** set the i-th column of T to H */ EX void set_column(transmatrix& T, int i, const hyperpoint& H) { for(int j=0; j abs(T1[best][a])) best = b; int b = best; if(b != a) for(int c=0; c=0; a--) { for(int b=0; b product_decompose(hyperpoint h) { ld z = zlevel(h); return make_pair(z, mscale(h, -z)); } /** distance from mh and 0 */ EX ld hdist0(const hyperpoint& mh) { switch(cgclass) { case gcHyperbolic: if(mh[LDIM] < 1) return 0; return acosh(mh[LDIM]); case gcEuclid: { return hypot_d(GDIM, mh); } case gcSphere: { ld res = mh[LDIM] >= 1 ? 0 : mh[LDIM] <= -1 ? M_PI : acos(mh[LDIM]); return res; } case gcProduct: { auto d1 = product_decompose(mh); return hypot(PIU(hdist0(d1.second)), d1.first); } case gcSL2: { auto cosh_r = hypot(mh[2], mh[3]); auto phi = atan2(mh[2], mh[3]); return hypot(cosh_r < 1 ? 0 : acosh(cosh_r), phi); } case gcNil: { ld bz = mh[0] * mh[1] / 2; return hypot(mh[0], mh[1]) + abs(mh[2] - bz); } default: return hypot_d(GDIM, mh); } } /** length of a circle of radius r */ EX ld circlelength(ld r) { switch(cgclass) { case gcEuclid: return 2 * M_PI * r; case gcHyperbolic: return 2 * M_PI * sinh(r); case gcSphere: return 2 * M_PI * sin(r); default: return 2 * M_PI * r; } } /* distance between h1 and h2 */ EX ld hdist(const hyperpoint& h1, const hyperpoint& h2) { ld iv = intval(h1, h2); switch(cgclass) { case gcEuclid: if(iv < 0) return 0; return sqrt(iv); case gcHyperbolic: if(iv < 0) return 0; return 2 * asinh(sqrt(iv) / 2); case gcSphere: return 2 * asin_auto_clamp(sqrt(iv) / 2); case gcProduct: { auto d1 = product_decompose(h1); auto d2 = product_decompose(h2); return hypot(PIU(hdist(d1.second, d2.second)), d1.first - d2.first); } case gcSL2: return hdist0(inverse(slr::translate(h1)) * h2); default: if(iv < 0) return 0; return sqrt(iv); } } EX hyperpoint mscale(const hyperpoint& t, double fac) { if(GDIM == 3 && !prod) return cpush(2, fac) * t; if(prod) fac = exp(fac); hyperpoint res; for(int i=0; i0?1:0; } EX bool asign(ld y1, ld y2) { return signum(y1) != signum(y2); } EX ld xcross(ld x1, ld y1, ld x2, ld y2) { return x1 + (x2 - x1) * y1 / (y1 - y2); } EX transmatrix parallel_transport(const transmatrix Position, const transmatrix& ori, const hyperpoint direction) { if(nonisotropic) return nisot::parallel_transport(Position, direction); else if(prod) { hyperpoint h = product::direct_exp(ori * direction); return Position * rgpushxto0(h); } else return Position * rgpushxto0(direct_exp(direction)); } EX void apply_parallel_transport(transmatrix& Position, const transmatrix orientation, const hyperpoint direction) { Position = parallel_transport(Position, orientation, direction); } EX void rotate_object(transmatrix& Position, transmatrix& orientation, transmatrix R) { if(prod) orientation = orientation * R; else Position = Position * R; } EX transmatrix spin_towards(const transmatrix Position, transmatrix& ori, const hyperpoint goal, int dir, int back) { transmatrix T; ld alpha = 0; if(nonisotropic && nisot::geodesic_movement) T = nisot::spin_towards(Position, goal); else { hyperpoint U = inverse(Position) * goal; if(prod) { hyperpoint h = product::inverse_exp(U); alpha = asin_clamp(h[2] / hypot_d(3, h)); U = product_decompose(U).second; } T = rspintox(U); } if(back < 0) T = T * spin(M_PI), alpha = -alpha; if(prod) { if(dir == 0) ori = cspin(2, 0, alpha); if(dir == 2) ori = cspin(2, 0, alpha - M_PI/2), dir = 0; } if(dir) T = T * cspin(dir, 0, -M_PI/2); T = Position * T; return T; } EX ld ortho_error(transmatrix T) { ld err = 0; for(int x=0; x<3; x++) for(int y=0; y<3; y++) { ld s = 0; for(int z=0; z<3; z++) s += T[z][x] * T[z][y]; s -= (x==y); err += s*s; } return err; } EX transmatrix transpose(transmatrix T) { transmatrix result; for(int i=0; i 0) for(int i=0; i b + M_PI) a -= 2 * M_PI; while(a < b - M_PI) a += 2 * M_PI; } EX 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; } EX unsigned bucketer(ld x) { return unsigned((long long)(x * 10000 + 100000.5) - 100000); } EX unsigned bucketer(hyperpoint h) { unsigned dx = 0; if(prod) { auto d = product_decompose(h); h = d.second; dx += bucketer(d.first) * 50; } dx += bucketer(h[0]) + 1000 * bucketer(h[1]) + 1000000 * bucketer(h[2]); if(MDIM == 4) dx += bucketer(h[3]) * 1000000001; return dx; } EX hyperpoint lerp(hyperpoint a0, hyperpoint a1, ld x) { return a0 + (a1-a0) * x; } }