1
0
mirror of git://f0xx.org/ac/ac-be-hub synced 2026-08-09 20:59:23 +03:00

fix(globe3d=11): mercator-only land wrap with correct projection

Drop ortho hub layer (ghost/double shadow). Sample flat plate with
12° lon center and inverse Mercator Y matching extract_red_polygons.py.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Anton Afanasyeu
2026-08-05 18:17:10 +02:00
parent 0f03e1759a
commit 7b10062604
2 changed files with 41 additions and 15 deletions

View File

@@ -11,7 +11,7 @@
* globe3d=8: hub SVG ellipse trace (breaks under rotation — deprecated).
* globe3d=9: v4 sphere grid only (thin EW + NS) — proper rotation baseline.
* globe3d=10: v4 visible-arc → screen ellipse (hub ry), disk-clipped.
* globe3d=11: v10 grid + visible-hemisphere continent wrap (ortho hub + mercator flat).
* globe3d=11: v10 grid + mercator-flat land wrap (lon center 12°, inverse Mercator Y).
*/
export const GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 322, rim: 330 };
@@ -20,6 +20,8 @@ const DRAG_ROTATION_PER_PX = 0.004;
const OCEAN_FALLBACK = { r: 22, g: 38, b: 60 };
const FRONT_Z = 0.04;
const LIMB_Z = 0.002;
/** hub-logo-continents ortho / flat map center (extract_red_polygons.py). */
const MERCATOR_LON_CENTER = (12 * Math.PI) / 180;
const RASTER_SCALE = 2;
const AXIS_STEPS = 160;
const SPHERE_AXIS_STEPS = 180;
@@ -877,19 +879,44 @@ function texLonLat(tex) {
};
}
function wrapLon(lon) {
let l = lon;
while (l > Math.PI) {
l -= Math.PI * 2;
}
while (l < -Math.PI) {
l += Math.PI * 2;
}
return l;
}
/** Inverse Mercator row — matches extract_red_polygons.py mercator_y_to_lat. */
function latToMercatorRow(lat, h) {
const clamped = Math.max(-1.52, Math.min(1.52, lat));
const t = 0.5 + Math.asinh(Math.tan(clamped)) / (2 * Math.PI);
return Math.max(0, Math.min(h - 1, (1 - t) * (h - 1)));
}
function sampleMercFlat(mercFlat, lon, lat) {
const u = ((lon + Math.PI) / (2 * Math.PI)) * (mercFlat.w - 1);
const v = (0.5 - lat / Math.PI) * (mercFlat.h - 1);
const u = ((wrapLon(lon) + Math.PI) / (2 * Math.PI)) * (mercFlat.w - 1);
const v = latToMercatorRow(lat, mercFlat.h);
return sampleImageDataBilinear(mercFlat.data, mercFlat.w, mercFlat.h, u, v);
}
/** Sphere tex → mercator-flat plate (lon offset + inverse Mercator Y). */
function sampleMercFlatFromTex(mercFlat, tex) {
const ll = texLonLat(tex);
const lon = wrapLon(ll.lon + MERCATOR_LON_CENTER);
return sampleMercFlat(mercFlat, lon, ll.lat);
}
function isLandPixel(r, g, b, a) {
return a > 10 && r > 100 && g < 140 && b < 140;
}
/** hub-logo-continents-mercator-{globe,flat}.svg grey-blue fills (not red JPG extract). */
function isHubContinentPixel(r, g, b, a) {
if (a < 12) {
if (a < 8) {
return false;
}
if (r > 168 && g > 198 && b > 218) {
@@ -905,6 +932,10 @@ function isHubContinentPixel(r, g, b, a) {
return b >= 100 && g >= 95 && r >= 68;
}
function hubContinentRgba(sample) {
return [sample[0], sample[1], sample[2], 255];
}
/** Light hub grid strokes baked into hub-logo-fragment-globe.svg (vector grid drawn separately). */
function isGridPixel(r, g, b, a) {
if (a < 12) {
@@ -969,19 +1000,14 @@ function sampleLandRgbaMercator(mercFlat, tex) {
return null;
}
/** globe3d=11 — same visible hemisphere as v10 grid (z > LIMB_Z); ortho hub then mercator wrap. */
function sampleLandVisibleHemisphere(continents, mercFlat, tex) {
/** globe3d=11 — visible hemisphere (z > LIMB_Z); mercator-flat wrap (single source, no ortho ghost). */
function sampleLandVisibleHemisphere(mercFlat, tex) {
if (tex.z <= LIMB_Z) {
return null;
}
const ortho = sampleHub(continents, tex);
if (isHubContinentPixel(ortho[0], ortho[1], ortho[2], ortho[3])) {
return [ortho[0], ortho[1], ortho[2], Math.min(255, ortho[3])];
}
const ll = texLonLat(tex);
const flat = sampleMercFlat(mercFlat, ll.lon, ll.lat);
const flat = sampleMercFlatFromTex(mercFlat, tex);
if (isHubContinentPixel(flat[0], flat[1], flat[2], flat[3])) {
return [flat[0], flat[1], flat[2], Math.min(255, flat[3])];
return hubContinentRgba(flat);
}
return null;
}
@@ -1196,7 +1222,7 @@ export async function mountHubGlobe(stage, opts) {
cy,
r,
fillDiskRegion(ctx, cx, cy, r, rotation, function (tex) {
const l = sampleLandVisibleHemisphere(continents, mercFlat, tex);
const l = sampleLandVisibleHemisphere(mercFlat, tex);
return l || [0, 0, 0, 0];
})
);