#pragma once // Copyright (c) 2024-2026 Tarik Moussa. // SPDX-License-Identifier: MIT // Port of the static helper // HyperIdealVisualizationPlugin.getEuclideanCircleFromHyperbolic() // from de.varylab.discreteconformal.plugin. // // Converts a hyperbolic circle (center + radius in the hyperboloid model) // to its Euclidean representation (cx, cy, r) in the Poincaré disk model. // // Mathematical background // ----------------------- // Hyperboloid model: points (x,y,z,w) with w²-x²-y²-z²=1, w>0. // Metric signature: g = diag(+1,+1,+1,−1) (spatial-first, time-last). // // Hyperbolic translation from the origin e₄=(0,0,0,1) to p=(a,b,c,d): // T = [ I₃ + p'·p'ᵀ/(d+1) p' ] p' = (a,b,c) // [ p'ᵀ d ] // This is the standard Lorentz boost; it is in O(3,1) and maps e₄ → p. // // Poincaré disk projection (jReality convention): // (x,y,z,w) → (x,y) / (w+1) // // The three reference points on the unit hyperbolic circle (at origin) are // p1 = (sinh r, 0, 0, cosh r) // p2 = (0, sinh r, 0, cosh r) // p3 = (-sinh r, 0, 0, cosh r) // After translation and projection to the Poincaré disk their circumcircle // equals the image of the original hyperbolic circle. #include // downgraded from : this header only // uses Matrix/Vector primitives, no decompositions. #include #include namespace conformallab { // --------------------------------------------------------------------------- // Circumcenter of three 2-D points // --------------------------------------------------------------------------- inline Eigen::Vector2d circumcenter2d( const Eigen::Vector2d& a, const Eigen::Vector2d& b, const Eigen::Vector2d& c) { double ax = a.x(), ay = a.y(); double bx = b.x(), by = b.y(); double cx = c.x(), cy = c.y(); double D = 2.0 * (ax*(by - cy) + bx*(cy - ay) + cx*(ay - by)); double a2 = ax*ax + ay*ay; double b2 = bx*bx + by*by; double c2 = cx*cx + cy*cy; double ux = (a2*(by - cy) + b2*(cy - ay) + c2*(ay - by)) / D; double uy = (a2*(cx - bx) + b2*(ax - cx) + c2*(bx - ax)) / D; return {ux, uy}; } // --------------------------------------------------------------------------- // 4×4 Lorentz boost: maps the hyperboloid origin e₄=(0,0,0,1) to `center`. // `center` must lie on the hyperboloid: center[3]² - ‖center.head<3>()‖² = 1. // --------------------------------------------------------------------------- inline Eigen::Matrix4d hyperboloidTranslation(const Eigen::Vector4d& center) { Eigen::Vector3d p = center.head<3>(); double d = center(3); Eigen::Matrix4d T = Eigen::Matrix4d::Identity(); // Upper-left 3×3 block: I + p'·p'ᵀ / (d+1) T.block<3,3>(0,0) += p * p.transpose() / (d + 1.0); // Right column and bottom row T.block<3,1>(0,3) = p; T.block<1,3>(3,0) = p.transpose(); T(3,3) = d; return T; } // --------------------------------------------------------------------------- // Project a hyperboloid point to the Poincaré disk (jReality convention: // add 1 to the w-coordinate, then dehomogenize the spatial part). // --------------------------------------------------------------------------- inline Eigen::Vector2d toPoincareDisk(const Eigen::Vector4d& x) { double w = x(3) + 1.0; return {x(0) / w, x(1) / w}; } // --------------------------------------------------------------------------- // getEuclideanCircleFromHyperbolic // // Inputs // center – point on the hyperboloid, e.g. (0,0,0,1) for the origin // radius – hyperbolic radius (real number > 0) // // Output // { euclidean_cx, euclidean_cy, euclidean_radius } // describing the circle in the Poincaré disk that corresponds to the // given hyperbolic circle. // // Port of HyperIdealVisualizationPlugin.getEuclideanCircleFromHyperbolic() // --------------------------------------------------------------------------- inline std::array getEuclideanCircleFromHyperbolic( const Eigen::Vector4d& center, double radius) { const double s = std::sinh(radius); const double ch = std::cosh(radius); // Three points on the hyperbolic circle centered at the origin Eigen::Vector4d p1( s, 0.0, 0.0, ch); Eigen::Vector4d p2(0.0, s, 0.0, ch); Eigen::Vector4d p3(-s, 0.0, 0.0, ch); // Apply the hyperbolic translation to the target center const Eigen::Matrix4d T = hyperboloidTranslation(center); p1 = T * p1; p2 = T * p2; p3 = T * p3; // Project to the Poincaré disk const Eigen::Vector2d q1 = toPoincareDisk(p1); const Eigen::Vector2d q2 = toPoincareDisk(p2); const Eigen::Vector2d q3 = toPoincareDisk(p3); // Euclidean circumcircle of the three projected points const Eigen::Vector2d ec = circumcenter2d(q1, q2, q3); const double r = (ec - q1).norm(); return {ec.x(), ec.y(), r}; } } // namespace conformallab