// 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 #ifndef M_PI #define M_PI 3.14159265358979 #endif static constexpr ld A_PI = M_PI; static const ld TAU = 2 * A_PI; static const ld degree = A_PI / 180; static const ld golden_phi = (sqrt(5)+1)/2; static const ld log_golden_phi = log(golden_phi); constexpr ld operator"" _deg(long double deg) { return deg * A_PI / 180; } #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 ? 90._deg : x<-1 ? -90._deg : std::isnan(x) ? 0 : asin(x); } EX ld acos_clamp(ld x) { return x>1 ? 0 : x<-1 ? M_PI : std::isnan(x) ? 0 : acos(x); } EX ld asin_auto_clamp(ld x) { switch(cgclass) { case gcEuclid: return x; case gcHyperbolic: return asinh(x); case gcSL2: return asinh(x); case gcSphere: return asin_clamp(x); case gcProduct: return PIU(asin_auto_clamp(x)); default: return x; } } EX ld acos_auto_clamp(ld x) { switch(cgclass) { case gcHyperbolic: return x < 1 ? 0 : acosh(x); case gcSL2: return x < 1 ? 0 : acosh(x); case gcSphere: return acos_clamp(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 gcSL2: 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)); case gcSL2: return tanh(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)); case gcSL2: return atanh(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 gcSL2: 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) { if(embedded_plane) { geom3::light_flip(true); hyperpoint h = hpxy(x, y); geom3::light_flip(false); swapmatrix(h); return h; } if(sl2) return hyperpoint(x, y, 0, sqrt(1+x*x+y*y)); if(rotspace) return hyperpoint(x, y, 0, sqrt(1-x*x-y*y)); return PIU(hpxyz(x,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 1e-6) return 1; if(x < -1e-6) return -1; return 0; } return 1; } EX ld ideal_limit = 10; EX ld ideal_each = degree; EX hyperpoint safe_approximation_of_ideal(hyperpoint h) { return towards_inf(C0, h, ideal_limit); } /** the point on the line ab which is closest to zero. Might not be normalized. Works even if a and b are (ultra)ideal */ EX hyperpoint closest_to_zero(hyperpoint a, hyperpoint b) { if(sqhypot_d(MDIM, a-b) < 1e-9) return a; if(isnan(a[0])) return a; a /= a[LDIM]; b /= b[LDIM]; ld mul_a = 0, mul_b = 0; for(int i=0; i 0 ? 1 : -1); ld x = -asinh(S); h = lorentz(0, 3, -x) * lorentz(1, 2, x) * h; ld y = h[3]*h[3] > h[2]*h[2] ? atanh(h[1] / h[3]) : atanh(h[0] / h[2]); h = lorentz(0, 2, -y) * lorentz(1, 3, -y) * h; ld z = atan2(h[2], h[3]); return hyperpoint(x, y, z, 0); } /** normalize, and in product geometry, also flatten */ EX hyperpoint normalize_flat(hyperpoint h) { if(gproduct) { if(geom3::euc_in_product()) { ld bz = zlevel(h); auto h1 = h / exp(bz); ld bx = atan_auto(h1[0] / h1[2]); return zpush(bz) * xpush(bx) * C0; } return product_decompose(h).second; } if(geom3::euc_in_nil()) h[1] = 0; if(geom3::euc_in_sl2()) { hyperpoint h1 = esl2_ati(h); h1[1] = 0; return esl2_ita0(h1); } else if(sl2) h = slr::translate(h) * zpush0(-atan2(h[2], h[3])); if(geom3::euc_in_solnih()) h[2] = 0; if(geom3::hyp_in_solnih()) h[0] = 0; if(geom3::euc_in_sph()) { ld tx = hypot(h[0], h[2]); ld ty = hypot(h[1], h[3]); h[0] = h[0] / tx * sin(1); h[1] = h[1] / ty * cos(1); h[2] = h[2] / tx * sin(1); h[3] = h[3] / ty * cos(1); return h; } if(geom3::euc_in_hyp()) { h = normalize(h); auto h1 = deparabolic13(h); h1[2] = 0; return parabolic13(h1); } if(geom3::sph_in_euc()) { ld z = hypot_d(3, h); if(z > 0) h[0] /= z, h[1] /= z, h[2] /= z; h[3] = 1; return h; } if(geom3::sph_in_hyp()) { ld z = hypot_d(3, h); z = sinh(1) / z; if(z > 0) h[0] *= z, h[1] *= z, h[2] *= z; h[3] = cosh(1); return h; } return normalize(h); } /** get the center of the line segment from H1 to H2 */ EX hyperpoint mid(const hyperpoint& H1, const hyperpoint& H2) { if(gproduct) { auto d1 = product_decompose(H1); auto d2 = product_decompose(H2); hyperpoint res1 = PIU( mid(d1.second, d2.second) ); hyperpoint res = res1 * exp((d1.first + d2.first) / 2); return res; } return normalize(H1 + H2); } EX shiftpoint mid(const shiftpoint& H1, const shiftpoint& H2) { return shiftless(mid(H1.h, H2.h), (H1.shift + H2.shift)/2); } /** like mid, but take 3D into account */ EX hyperpoint midz(const hyperpoint& H1, const hyperpoint& H2) { if(gproduct) return mid(H1, H2); hyperpoint H3 = H1 + H2; ld Z = 2; if(!euclid) Z = zlevel(H3) * 2 / (zlevel(H1) + zlevel(H2)); for(int c=0; c 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(geom3::euc_in_hyp()) { hyperpoint hf = deparabolic13(h); hf[2] += z; return parabolic13(hf); } if(geom3::euc_in_nil()) { return nisot::translate(h) * cpush0(1, z); } if(geom3::euc_in_solnih()) { return nisot::translate(h) * cpush0(2, z); } if(geom3::sph_in_euc()) { ld z0 = hypot_d(3, h); ld f = ((z0 + z) / z0); hyperpoint hf; for(int i=0; i<3; i++) hf[i] = h[i] * f; hf[3] = 1; return hf; } if(geom3::hyp_in_solnih()) { return nisot::translate(h) * cpush0(0, z); } if(geom3::sph_in_hyp()) { ld z0 = acosh(h[3]); ld f = sinh(z0 + z) / sinh(z0); hyperpoint hf; for(int i=0; i<3; i++) hf[i] = h[i] * f; hf[3] = cosh(z0 + z); return hf; } if(geom3::euc_in_sph()) { // cspin(0,2,x) * cspin(1,3,y) * cspin0(2, 3, z) ld tx = hypot(h[0], h[2]); ld ty = hypot(h[1], h[3]); ld z0 = atan2(ty, tx); z0 -= z; hyperpoint hf; hf[0] = h[0] / tx * cos(z0); hf[1] = h[1] / ty * sin(z0); hf[2] = h[2] / tx * cos(z0); hf[3] = h[3] / ty * sin(z0); return hf; } if(geom3::euc_in_product()) { ld bz = zlevel(h); auto h1 = h / exp(bz); ld by = asin_auto(h1[1]); ld bx = atan_auto(h1[0] / h1[2]); by += z; return zpush(bz) * xpush(bx) * ypush(by) * C0; } if(geom3::euc_in_sl2()) { hyperpoint h1 = esl2_ati(h); h1[1] += z; return esl2_ita0(h1); } if(GDIM == 2) return scale_point(h, geom3::scale_at_lev(z)); if(gproduct) return scale_point(h, exp(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 /= cos_auto(asin_auto(h[2])); u *= cos_auto(z); return hpxy3(h[0] * u, h[1] * u, sinh(z)); } EX ld get_logical_z(hyperpoint h) { if(geom3::euc_in_nil()) return h[1]; if(geom3::euc_in_solnih()) return h[2]; if(geom3::hyp_in_solnih()) return h[0]; if(geom3::euc_in_sl2()) return esl2_ati(h)[1]; if(geom3::euc_in_product()) { ld bz = zlevel(h); auto h1 = h / exp(bz); return asin_auto(h1[1]); } if(gproduct) return log(h[2]); return asin_auto(h[2]) - (moved_center() ? 1 : 0); } #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 || MDIM == 3) 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 /** Transform a matrix between the 'embedded_plane' and underlying representation. Switches to the current variant. */ EX void swapmatrix(transmatrix& T) { if(geom3::euc_in_hyp() && !geom3::flipped) { geom3::light_flip(true); hyperpoint mov = T * C02; transmatrix U = gpushxto0(mov) * T; geom3::light_flip(false); for(int i=0; i<4; i++) U[i][3] = U[3][i] = i == 3; T = parabolic13(mov[0], mov[1]) * U; } else if(geom3::hyp_in_solnih()) { // rotations are illegal anyway... hyperpoint h = get_column(T, 2); swapmatrix(h); T = rgpushxto0(h); return; } else if(geom3::sph_in_euc() || geom3::sph_in_hyp()) { if(!geom3::flipped) { for(int i=0; i<4; i++) T[i][3] = T[3][i] = i == 3; } } else if(geom3::euc_in_nil()) { if(!geom3::flipped) { hyperpoint h1 = cgi.logical_to_intermediate * get_column(T, 2); // rotations are illegal anyway... T = eupush(hyperpoint(h1[0], 0, h1[2], 1)); return; } } else if(geom3::euc_in_solnih()) { if(!geom3::flipped) { hyperpoint h1 = cgi.logical_to_intermediate * get_column(T, 2); // rotations are illegal anyway... T = eupush(hyperpoint(h1[0], h1[1], 0, 1)); return; } } else if(geom3::euc_in_product()) { hyperpoint h1 = cgi.logical_to_intermediate * get_column(T, 2); T = xpush(h1[0]) * zpush(h1[2]); return; } else if(geom3::euc_in_sl2() && !geom3::flipped) { hyperpoint h1 = cgi.logical_to_intermediate * get_column(T, 2); h1[1] = 0; T = esl2_ita(h1); return; } else if(geom3::in_product()) { /* just do nothing */ } else if(geom3::euc_in_sph()) { hyperpoint h1 = cgi.logical_to_intermediate * get_column(T, 2); T = cspin(0, 2, h1[0]) * cspin(1, 3, h1[1]); } else { 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= 1e-15) { 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-15) { 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 || gproduct) 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 || gproduct) return rspintoc(H, 0, 1); transmatrix T1 = spintoc(H, 0, 1); return rspintoc(H, 0, 1) * rspintoc(T1*H, 0, 2); } EX transmatrix lspintox(const hyperpoint& H) { if(geom3::euc_in_product()) return Id; if(geom3::euc_vertical()) return spintoc(H, 0, 2); if(geom3::hyp_in_solnih()) return spintoc(H, 1, 2); if(WDIM == 2 || gproduct) return spintoc(H, 0, 1); transmatrix T1 = spintoc(H, 0, 1); return spintoc(T1*H, 0, 2) * T1; } EX transmatrix lrspintox(const hyperpoint& H) { if(geom3::euc_in_product()) return Id; if(geom3::euc_vertical()) return rspintoc(H, 0, 2); if(geom3::hyp_in_solnih()) return rspintoc(H, 1, 2); if(WDIM == 2 || gproduct) 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 g(geometry, gSphere); fixmatrix(rot); for(int i=0; i<3; i++) rot[i][3] = rot[3][i] = 0; rot[3][3] = 1; } /** determinant 2x2 */ EX ld det2(const transmatrix& T) { return T[0][0] * T[1][1] - T[0][1] * T[1][0]; } /** determinant 3x3 */ EX ld det3(const transmatrix& T) { ld det = 0; for(int i=0; i<3; i++) det += T[0][i] * T[1][(i+1)%3] * T[2][(i+2)%3]; for(int i=0; i<3; i++) det -= T[0][i] * T[1][(i+2)%3] * T[2][(i+1)%3]; return det; } /** determinant */ EX ld det(const transmatrix& T) { if(MDIM == 3) return det3(T); else { ld det = 1; transmatrix M = T; for(int a=0; a abs(M[max_at][a])) max_at = b; if(max_at != a) for(int c=a; c 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, scale_point(h, exp(-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); } #if MAXMDIM >= 4 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); } #endif default: return hypot_d(GDIM, mh); } } EX ld hdist0(const shiftpoint& mh) { return hdist0(unshift(mh)); } /** length of a circle of radius r */ EX ld circlelength(ld r) { switch(cgclass) { case gcEuclid: return TAU * r; case gcHyperbolic: return TAU * sinh(r); case gcSphere: return TAU * sin(r); default: return TAU * 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(stretch::itranslate(h1) * h2); default: if(iv < 0) return 0; return sqrt(iv); } } EX ld hdist(const shiftpoint& h1, const shiftpoint& h2) { return hdist(h1.h, unshift(h2, h1.shift)); } /** like orthogonal_move but fol may be factor (in 2D graphics) or level (elsewhere) */ EX hyperpoint orthogonal_move_fol(const hyperpoint& h, double fol) { if(GDIM == 2) return scale_point(h, fol); else return orthogonal_move(h, fol); } /** like orthogonal_move but fol may be factor (in 2D graphics) or level (elsewhere) */ EX transmatrix orthogonal_move_fol(const transmatrix& T, double fol) { if(GDIM == 2) return scale_matrix(T, fol); else return orthogonal_move(T, fol); } /** like orthogonal_move but fol may be factor (in 2D graphics) or level (elsewhere) */ EX shiftmatrix orthogonal_move_fol(const shiftmatrix& T, double fol) { if(GDIM == 2) return scale_matrix(T, fol); else return orthogonal_move(T, fol); } /** the scaling matrix (Euclidean homogeneous scaling; also shift by log(scale) in product space */ EX transmatrix scale_matrix(const transmatrix& t, ld scale_factor) { transmatrix 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); } #if HDR enum eShiftMethod { smProduct, smIsotropic, smEmbedded, smLie, smGeodesic, smESL2 }; enum eEmbeddedShiftMethodChoice { smcNone, smcBoth, smcAuto }; enum eShiftMethodApplication { smaManualCamera, smaAutocenter, smaObject, smaWallRadar }; #endif EX eEmbeddedShiftMethodChoice embedded_shift_method_choice = smcBoth; EX bool use_embedded_shift(eShiftMethodApplication sma) { switch(sma) { case smaAutocenter: return embedded_shift_method_choice != smcNone; case smaManualCamera: return embedded_shift_method_choice == smcBoth; case smaObject: return true; case smaWallRadar: return among(pmodel, mdLiePerspective, mdLieOrthogonal); default: throw hr_exception("unknown sma"); } } EX eShiftMethod shift_method(eShiftMethodApplication sma) { if(gproduct) return smProduct; if(embedded_plane && sma == smaObject) return geom3::same_in_same() ? smIsotropic : smEmbedded; if(embedded_plane && use_embedded_shift(sma)) return sl2 ? smESL2 : nonisotropic ? smLie : smEmbedded; if(!nonisotropic && !stretch::in() && !(!nisot::geodesic_movement && hyperbolic && bt::in())) return smIsotropic; if(!nisot::geodesic_movement && !embedded_plane) return smLie; return smGeodesic; } EX transmatrix shift_object(const transmatrix Position, const transmatrix& ori, const hyperpoint direction, eShiftMethod sm IS(shift_method(smaObject))) { switch(sm) { case smGeodesic: return nisot::parallel_transport(Position, direction); case smLie: return nisot::lie_transport(Position, direction); case smProduct: { hyperpoint h = product::direct_exp(ori * direction); return Position * rgpushxto0(h); } case smIsotropic: { return Position * rgpushxto0(direct_exp(direction)); } case smEmbedded: { if(geom3::euc_in_hyp() || geom3::sph_in_low()) { geom3::light_flip(true); transmatrix T = rgpushxto0(direct_exp(direction)); geom3::light_flip(false); swapmatrix(T); return Position * T; } transmatrix rot = inverse(map_relative_push(Position * C0)) * Position; transmatrix urot = unswap_spin(rot); geom3::light_flip(true); transmatrix T = rgpushxto0(direct_exp(urot * direction)); geom3::light_flip(false); swapmatrix(T); return Position * inverse(rot) * T * rot; } default: throw hr_exception("unknown shift method in shift_object"); } } EX void apply_shift_object(transmatrix& Position, const transmatrix orientation, const hyperpoint direction, eShiftMethod sm IS(shift_method(smaObject))) { Position = shift_object(Position, orientation, direction, sm); } EX void rotate_object(transmatrix& Position, transmatrix& orientation, transmatrix R) { if(gproduct && WDIM == 3) 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(gproduct) { 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 * spin180(), alpha = -alpha; if(gproduct) { if(dir == 0) ori = cspin(2, 0, alpha); if(dir == 2) ori = cspin(2, 0, alpha - 90._deg), dir = 0; } if(dir) T = T * cspin(dir, 0, -90._deg); T = Position * T; return T; } EX shiftmatrix spin_towards(const shiftmatrix Position, transmatrix& ori, const shiftpoint goal, int dir, int back) { return shiftless(spin_towards(Position.T, ori, unshift(goal, Position.shift), dir, back), Position.shift); } 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= 4 if(nil) return nilv::formula_exp(v); if(sl2 || stretch::in()) return stretch::mstretch ? nisot::numerical_exp(v) : rots::formula_exp(v); #endif if(gproduct) return product::direct_exp(v); ld d = hypot_d(GDIM, v); if(d > 0) for(int i=0; i 90._deg) return M_PI - d; return d; } EX hyperpoint lp_iapply(const hyperpoint h) { return nisot::local_perspective_used ? inverse(NLP) * h : h; } EX hyperpoint lp_apply(const hyperpoint h) { return nisot::local_perspective_used ? NLP * h : h; } EX hyperpoint smalltangent() { return xtangent(.1); } EX void cyclefix(ld& a, ld b) { while(a > b + M_PI) a -= TAU; while(a < b - M_PI) a += TAU; } EX ld raddif(ld a, ld b) { ld d = a-b; if(d < 0) d = -d; if(d > TAU) d -= TAU; if(d > M_PI) d = TAU-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(gproduct) { auto d = product_decompose(h); h = d.second; dx += bucketer(d.first) * 50; if(geom3::euc_in_product() && in_h2xe()) h /= h[2]; } dx += bucketer(h[0]) + 1000 * bucketer(h[1]) + 1000000 * bucketer(h[2]); if(MDIM == 4) dx += bucketer(h[3]) * 1000000001; if(elliptic) dx = min(dx, -dx); return dx; } #if MAXMDIM >= 4 /** @brief project the origin to the triangle [h1,h2,h3] */ EX hyperpoint project_on_triangle(hyperpoint h1, hyperpoint h2, hyperpoint h3) { h1 /= h1[3]; h2 /= h2[3]; h3 /= h3[3]; transmatrix T; T[0] = h1; T[1] = h2; T[2] = h3; T[3] = C0; ld det_orig = det3(T); hyperpoint orthogonal = (h2 - h1) ^ (h3 - h1); T[0] = orthogonal; T[1] = h2-h1; T[2] = h3-h1; ld det_orth = det3(T); hyperpoint result = orthogonal * (det_orig / det_orth); result[3] = 1; return normalize(result); } #endif EX hyperpoint lerp(hyperpoint a0, hyperpoint a1, ld x) { return a0 + (a1-a0) * x; } EX hyperpoint linecross(hyperpoint a, hyperpoint b, hyperpoint c, hyperpoint d) { a /= a[LDIM]; b /= b[LDIM]; c /= c[LDIM]; d /= d[LDIM]; ld bax = b[0] - a[0]; ld dcx = d[0] - c[0]; ld cax = c[0] - a[0]; ld bay = b[1] - a[1]; ld dcy = d[1] - c[1]; ld cay = c[1] - a[1]; hyperpoint res; res[0] = (cay * dcx * bax + a[0] * bay * dcx - c[0] * dcy * bax) / (bay * dcx - dcy * bax); res[1] = (cax * dcy * bay + a[1] * bax * dcy - c[1] * dcx * bay) / (bax * dcy - dcx * bay); res[2] = 0; res[3] = 0; res[GDIM] = 1; return normalize(res); } EX ld inner2(hyperpoint h1, hyperpoint h2) { return hyperbolic ? h1[LDIM] * h2[LDIM] - h1[0] * h2[0] - h1[1] * h2[1] : sphere ? h1[LDIM] * h2[LDIM] + h1[0] * h2[0] + h1[1] * h2[1] : h1[0] * h2[0] + h1[1] * h2[1]; } EX hyperpoint circumscribe(hyperpoint a, hyperpoint b, hyperpoint c) { hyperpoint h = C0; b = b - a; c = c - a; if(euclid) { ld b2 = inner2(b, b)/2; ld c2 = inner2(c, c)/2; ld det = c[1]*b[0] - b[1]*c[0]; h = a; h[1] += (c2*b[0] - b2 * c[0]) / det; h[0] += (c2*b[1] - b2 * c[1]) / -det; return h; } if(inner2(b,b) < 0) { b = b / sqrt(-inner2(b, b)); c = c + b * inner2(c, b); h = h + b * inner2(h, b); } else { b = b / sqrt(inner2(b, b)); c = c - b * inner2(c, b); h = h - b * inner2(h, b); } if(inner2(c,c) < 0) { c = c / sqrt(-inner2(c, c)); h = h + c * inner2(h, c); } else { c = c / sqrt(inner2(c, c)); h = h - c * inner2(h, c); } if(h[LDIM] < 0) h[0] = -h[0], h[1] = -h[1], h[LDIM] = -h[LDIM]; ld i = inner2(h, h); if(i > 0) h /= sqrt(i); else h /= -sqrt(-i); return h; } EX ld inner3(hyperpoint h1, hyperpoint h2) { return hyperbolic ? h1[LDIM] * h2[LDIM] - h1[0] * h2[0] - h1[1] * h2[1] - h1[2]*h2[2]: sphere ? h1[LDIM] * h2[LDIM] + h1[0] * h2[0] + h1[1] * h2[1] + h1[2]*h2[2]: h1[0] * h2[0] + h1[1] * h2[1]; } /** circumscribe for H3 and S3 (not for E3 yet!) */ EX hyperpoint circumscribe(hyperpoint a, hyperpoint b, hyperpoint c, hyperpoint d) { array ds = { b-a, c-a, d-a, C0 }; for(int i=0; i<3; i++) { if(inner3(ds[i],ds[i]) < 0) { ds[i] = ds[i] / sqrt(-inner3(ds[i], ds[i])); for(int j=i+1; j<4; j++) ds[j] = ds[j] + ds[i] * inner3(ds[i], ds[j]); } else { ds[i] = ds[i] / sqrt(inner3(ds[i], ds[i])); for(int j=i+1; j<4; j++) ds[j] = ds[j] - ds[i] * inner3(ds[i], ds[j]); } } hyperpoint& h = ds[3]; if(h[3] < 0) h = -h; ld i = inner3(h, h); if(i > 0) h /= sqrt(i); else h /= -sqrt(-i); return h; } /** the point in distance dist from 'material' to 'dir' (usually an (ultra)ideal point) */ EX hyperpoint towards_inf(hyperpoint material, hyperpoint dir, ld dist IS(1)) { transmatrix T = gpushxto0(material); hyperpoint id = T * dir; return rgpushxto0(material) * rspintox(id) * xpush0(dist); } EX bool clockwise(hyperpoint h1, hyperpoint h2) { return h1[0] * h2[1] > h1[1] * h2[0]; } EX ld worst_precision_error; #if HDR struct hr_precision_error : hr_exception { hr_precision_error() : hr_exception("precision error") {} }; #endif /** check if a and b are the same, testing for equality. Throw an exception or warning if not sure */ EX bool same_point_may_warn(hyperpoint a, hyperpoint b) { ld d = hdist(a, b); if(d > 1e-2) return false; if(d > 1e-3) throw hr_precision_error(); if(d > 1e-6 && worst_precision_error <= 1e-6) addMessage("warning: precision errors are building up!"); if(d > worst_precision_error) worst_precision_error = d; return true; } }