@@ -912,6 +912,18 @@ namespace ntt {
912912 const int H = g_renderer.height ();
913913 const bool mirror = g_renderer.mirror ();
914914
915+ // fulldome fisheye ("dome master"). Cartesian slices are a flat plane, so
916+ // the kernel warps each pixel radially (fisheye). Curvilinear slices
917+ // (spherical / GR Kerr-Schild) are ALREADY a meridional disk, so dome mode
918+ // there is only a framing change: mirror to a full disk (the `mirror`
919+ // default) and fit that disk to the frame's inscribed circle (the pad skip
920+ // below), while the kernel keeps its native (X, Z) meridional map. Reported
921+ // back so the (metric-agnostic) compositor keeps the frame a clean square.
922+ // All ranks take the same branch (M is fixed per run), so it stays seamless
923+ // across tiles.
924+ const out::DomeMap dome = g_renderer.dome ();
925+ g_renderer.setDomeActive (dome.enabled );
926+
915927 // global slice-plane world window (shared by all ranks -> seamless),
916928 // taken from the optional render region (== full extent when uncropped).
917929 // gext (the full extent) is kept for the field-line coarse grid below.
@@ -973,15 +985,19 @@ namespace ntt {
973985 }
974986 // spherical slices get a background border so the round outline and its
975987 // R/theta labels are not clipped at the frame edges (Cartesian fills the
976- // frame and draws its ticks in dedicated margins, so it needs none).
988+ // frame and draws its ticks in dedicated margins, so it needs none). A dome
989+ // master skips it: the disk must reach the frame's inscribed circle (which
990+ // the projector maps to the dome horizon), and it draws no axes.
977991 if constexpr (M::CoordType != Coord::type::Cartesian) {
978- const real_t pad = static_cast <real_t >(1.12 );
979- const real_t cu = HALF * (umin + umax), hu = HALF * (umax - umin) * pad;
980- const real_t cv = HALF * (vmin + vmax), hv = HALF * (vmax - vmin) * pad;
981- umin = cu - hu;
982- umax = cu + hu;
983- vmin = cv - hv;
984- vmax = cv + hv;
992+ if (not dome.enabled ) {
993+ const real_t pad = static_cast <real_t >(1.12 );
994+ const real_t cu = HALF * (umin + umax), hu = HALF * (umax - umin) * pad;
995+ const real_t cv = HALF * (vmin + vmax), hv = HALF * (vmax - vmin) * pad;
996+ umin = cu - hu;
997+ umax = cu + hu;
998+ vmin = cv - hv;
999+ vmax = cv + hv;
1000+ }
9851001 }
9861002
9871003 // hand the world window + axis names to the (host) axes overlay. Default
@@ -1015,8 +1031,40 @@ namespace ntt {
10151031 // boundary; an arc for spherical, a box for Cartesian)
10161032 const auto le = local_domain->mesh .extent ();
10171033 auto toPix = [&](real_t u, real_t v, real_t & px, real_t & py) {
1018- px = (u - umin) / (umax - umin) * static_cast <real_t >(W) - HALF ;
1019- py = (vmax - v) / (vmax - vmin) * static_cast <real_t >(H) - HALF ;
1034+ if (dome.enabled and M::CoordType == Coord::type::Cartesian) {
1035+ // forward fisheye projection (inverse of the kernel's radial map),
1036+ // used to bound this domain's footprint on the dome disk. Cartesian
1037+ // only -- curvilinear dome uses the linear (X, Z) map below, matching
1038+ // the kernel's native meridional projection.
1039+ const real_t cxp = HALF * static_cast <real_t >(W);
1040+ const real_t cyp = HALF * static_cast <real_t >(H);
1041+ const real_t Rpx = HALF * static_cast <real_t >(std::min (W, H));
1042+ const real_t dx = u - dome.cx , dy = v - dome.cy ;
1043+ const real_t rw = std::sqrt (dx * dx + dy * dy);
1044+ real_t fr = (dome.R > ZERO ) ? (rw / dome.R ) : ZERO ;
1045+ if (fr > ONE ) {
1046+ fr = ONE ; // clamp onto the rim (conservative for the bbox)
1047+ }
1048+ real_t theta;
1049+ if (dome.law == out::DomeMap::Gnomonic) {
1050+ theta = std::atan (fr * std::tan (dome.theta_max ));
1051+ } else if (dome.law == out::DomeMap::Stereographic) {
1052+ theta = static_cast <real_t >(2 ) *
1053+ std::atan (fr * std::tan (HALF * dome.theta_max ));
1054+ } else if (dome.law == out::DomeMap::Orthographic) {
1055+ theta = std::asin (fr * std::sin (dome.theta_max ));
1056+ } else {
1057+ theta = fr * dome.theta_max ;
1058+ }
1059+ const real_t rho = (dome.theta_max > ZERO ) ? (theta / dome.theta_max )
1060+ : fr;
1061+ const real_t phi = std::atan2 (dy, dx);
1062+ px = cxp + rho * Rpx * std::cos (phi) - HALF ;
1063+ py = cyp - rho * Rpx * std::sin (phi) - HALF ;
1064+ } else {
1065+ px = (u - umin) / (umax - umin) * static_cast <real_t >(W) - HALF ;
1066+ py = (vmax - v) / (vmax - vmin) * static_cast <real_t >(H) - HALF ;
1067+ }
10201068 };
10211069 real_t minx = static_cast <real_t >(1e30 ), miny = static_cast <real_t >(1e30 );
10221070 real_t maxx = static_cast <real_t >(-1e30 ), maxy = static_cast <real_t >(-1e30 );
@@ -1029,10 +1077,28 @@ namespace ntt {
10291077 maxy = std::max (maxy, py);
10301078 };
10311079 if constexpr (M::CoordType == Coord::type::Cartesian) {
1032- acc (le[0 ].first , le[1 ].first );
1033- acc (le[0 ].second , le[1 ].first );
1034- acc (le[0 ].first , le[1 ].second );
1035- acc (le[0 ].second , le[1 ].second );
1080+ if (dome.enabled ) {
1081+ // the fisheye map is nonlinear (and a domain straddling the center
1082+ // wraps around the image center), so bound the footprint by sampling
1083+ // the whole domain-rectangle boundary, not just the 4 corners
1084+ const int NB = 65 ;
1085+ const real_t x0 = le[0 ].first , x1 = le[0 ].second ;
1086+ const real_t y0 = le[1 ].first , y1 = le[1 ].second ;
1087+ for (int k = 0 ; k < NB ; ++k) {
1088+ const real_t t = static_cast <real_t >(k) / static_cast <real_t >(NB - 1 );
1089+ const real_t xx = x0 + (x1 - x0) * t;
1090+ const real_t yy = y0 + (y1 - y0) * t;
1091+ acc (xx, y0);
1092+ acc (xx, y1);
1093+ acc (x0, yy);
1094+ acc (x1, yy);
1095+ }
1096+ } else {
1097+ acc (le[0 ].first , le[1 ].first );
1098+ acc (le[0 ].second , le[1 ].first );
1099+ acc (le[0 ].first , le[1 ].second );
1100+ acc (le[0 ].second , le[1 ].second );
1101+ }
10361102 } else {
10371103 const int NB = 33 ;
10381104 const real_t r0 = le[0 ].first , r1 = le[0 ].second ;
@@ -1213,6 +1279,7 @@ namespace ntt {
12131279 by0,
12141280 bw,
12151281 mirror,
1282+ dome,
12161283 x1lo,
12171284 x1hi,
12181285 x2lo,
0 commit comments