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

Hub globe3d=3..5: fix E-W grid rotation iterations.

Strip baked SVG grid from rotating ocean (solid fill v3+).
v3 sphere parallels only; v4 +meridians; v5 hub ellipse trace.
Continents skipped v3+ until wrap is revisited.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Anton Afanasyeu
2026-08-05 16:10:44 +02:00
parent 07bdf85220
commit 54c3ac4966
2 changed files with 140 additions and 50 deletions

View File

@@ -1,8 +1,11 @@
/**
* Hub landing globe — orthographic Canvas2D (?globe3d=N).
* globe3d=0: flat SVG layers (index.php).
* globe3d=1: legacy hybrid ortho front + mercator back.
* globe3d=2+: mercator wrap on visible hemisphere + full-sphere grid rings.
* globe3d=1: legacy hybrid ortho front + mercator back + hub SVG grid trace.
* globe3d=2: mercator land attempt + full-sphere grid (superseded).
* globe3d=3: solid ocean, EW parallels only (true latitude rings) — no baked grid bleed.
* globe3d=4: solid ocean, full sphere grid (parallels + meridians).
* globe3d=5: solid ocean, EW via hub ellipse → sphere trace (matches globe3d=0 art at rest).
*/
export const GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 322, rim: 330 };
@@ -15,11 +18,16 @@ const RASTER_SCALE = 2;
const AXIS_STEPS = 160;
const SPHERE_AXIS_STEPS = 180;
/** Latitudes derived from hub-logo-fragment-globe-grid-overlay.svg EW ellipses. */
/** Latitudes from hub grid overlay ellipse centers (meridian x = cx). */
function hubParallelLat(hubCy) {
const texY = (GLOBE_VIEW.cy - hubCy) / GLOBE_VIEW.r;
return Math.asin(Math.max(-1, Math.min(1, texY)));
}
const SPHERE_PARALLELS = [
{ lat: 0, bright: true },
{ lat: Math.asin((560 - 392) / 322), bright: false },
{ lat: Math.asin((560 - 728) / 322), bright: false },
{ lat: hubParallelLat(392), bright: false },
{ lat: hubParallelLat(728), bright: false },
];
/** Longitudes at equator from hub NS meridian artwork (x=620 / x=820). */
@@ -311,8 +319,9 @@ function meridianBodyRingAtLon(lon, steps) {
return out;
}
/** Full-sphere grid rings (globe3d>=2) — visible segments only, no front-hemisphere SVG trace. */
function drawSphereGrid(ctx, cx, cy, r, rotY, layoutScale) {
/** Full-sphere grid rings (globe3d>=4). */
function drawSphereGrid(ctx, cx, cy, r, rotY, layoutScale, opts) {
const parallelsOnly = !!(opts && opts.parallelsOnly);
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
@@ -333,26 +342,45 @@ function drawSphereGrid(ctx, cx, cy, r, rotY, layoutScale) {
);
});
SPHERE_MERIDIANS.forEach(function (mer) {
strokeBodyRing(
ctx,
cx,
cy,
r,
rotY,
meridianBodyRingAtLon(mer.lon, SPHERE_AXIS_STEPS),
function (z) {
return meridianStyle(mer.prime, layoutScale, z, LIMB_Z);
},
LIMB_Z
);
if (!parallelsOnly) {
SPHERE_MERIDIANS.forEach(function (mer) {
strokeBodyRing(
ctx,
cx,
cy,
r,
rotY,
meridianBodyRingAtLon(mer.lon, SPHERE_AXIS_STEPS),
function (z) {
return meridianStyle(mer.prime, layoutScale, z, LIMB_Z);
},
LIMB_Z
);
});
}
ctx.restore();
}
/** Hub SVG ellipses traced on sphere (globe3d=5) — matches static art at yaw 0. */
function drawHubParallelGrid(ctx, cx, cy, r, rotY, layoutScale) {
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.clip();
HUB_EW_PARALLELS.forEach(function (ellipse) {
strokeBodyRing(ctx, cx, cy, r, rotY, ellipseBodyRing(ellipse, AXIS_STEPS), function (z) {
return parallelStyle(ellipse.bright, layoutScale, z, LIMB_Z);
}, LIMB_Z);
});
ctx.restore();
}
/** Hub SVG grid: EW ellipses + NS meridian paths (matches globe3d=0 artwork geometry). */
function drawHubGrid(ctx, cx, cy, r, rotY, layoutScale) {
/** Hub SVG grid: EW ellipses + NS meridian paths (globe3d=1). */
function drawHubGrid(ctx, cx, cy, r, rotY, layoutScale, opts) {
const parallelsOnly = !!(opts && opts.parallelsOnly);
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
@@ -364,15 +392,55 @@ function drawHubGrid(ctx, cx, cy, r, rotY, layoutScale) {
});
});
HUB_NS_MERIDIANS.forEach(function (mer) {
strokeBodyRing(ctx, cx, cy, r, rotY, hubPointsToBodyRing(mer.points), function (z) {
return meridianStyle(mer.prime, layoutScale, z);
if (!parallelsOnly) {
HUB_NS_MERIDIANS.forEach(function (mer) {
strokeBodyRing(ctx, cx, cy, r, rotY, hubPointsToBodyRing(mer.points), function (z) {
return meridianStyle(mer.prime, layoutScale, z);
});
});
});
}
ctx.restore();
}
function drawGlobeGrid(ctx, cx, cy, r, rotY, layoutScale, renderVersion) {
if (renderVersion === 3) {
drawSphereGrid(ctx, cx, cy, r, rotY, layoutScale, { parallelsOnly: true });
return;
}
if (renderVersion === 4) {
drawSphereGrid(ctx, cx, cy, r, rotY, layoutScale, { parallelsOnly: false });
return;
}
if (renderVersion === 5) {
drawHubParallelGrid(ctx, cx, cy, r, rotY, layoutScale);
return;
}
if (renderVersion >= 2) {
drawSphereGrid(ctx, cx, cy, r, rotY, layoutScale, { parallelsOnly: false });
return;
}
drawHubGrid(ctx, cx, cy, r, rotY, layoutScale, { parallelsOnly: false });
}
function drawOceanRim(ctx, globeImg, cx, cy, r, rim, layoutScale, renderVersion) {
if (renderVersion >= 3) {
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, rim, 0, Math.PI * 2);
ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
ctx.clip();
const g = ctx.createRadialGradient(cx, cy, r, cx, cy, rim);
g.addColorStop(0, 'rgba(28,48,74,0.98)');
g.addColorStop(1, 'rgba(14,24,38,0.92)');
ctx.fillStyle = g;
ctx.fill();
ctx.restore();
return;
}
drawRim(ctx, globeImg, cx, cy, r, rim, layoutScale);
}
function sampleHub(layer, tex) {
const hp = {
x: GLOBE_VIEW.cx + tex.x * GLOBE_VIEW.r,
@@ -415,15 +483,21 @@ function isGridPixel(r, g, b, a) {
return Math.max(r, g, b) - Math.min(r, g, b) < 85;
}
function sampleOceanRgba(globe, tex) {
const OCEAN_SOLID = [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b, 255];
function sampleOceanSolid() {
return OCEAN_SOLID;
}
function sampleOceanClean(globe, tex) {
if (tex.z <= FRONT_Z) {
return [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b, 255];
return OCEAN_SOLID;
}
const s = sampleHub(globe, tex);
if (s[3] > 8 && !isGridPixel(s[0], s[1], s[2], s[3])) {
return [s[0], s[1], s[2], 255];
}
return [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b, 255];
return OCEAN_SOLID;
}
function sampleLandRgbaLegacy(continents, mercFlat, tex) {
@@ -633,28 +707,37 @@ export async function mountHubGlobe(stage, opts) {
cy,
r,
fillDiskRegion(ctx, cx, cy, r, rotation, function (tex) {
return sampleOceanRgba(globe, tex);
if (renderVersion >= 3) {
return sampleOceanSolid();
}
return sampleOceanClean(globe, tex);
})
);
blitLayer(
ctx,
cx,
cy,
r,
fillDiskRegion(ctx, cx, cy, r, rotation, function (tex) {
const l =
renderVersion >= 2
? sampleLandRgbaMercator(mercFlat, tex)
: sampleLandRgbaLegacy(continents, mercFlat, tex);
return l || [0, 0, 0, 0];
})
);
if (renderVersion >= 2) {
drawSphereGrid(ctx, cx, cy, r, rotation, layoutScale);
} else {
drawHubGrid(ctx, cx, cy, r, rotation, layoutScale);
if (renderVersion === 1) {
blitLayer(
ctx,
cx,
cy,
r,
fillDiskRegion(ctx, cx, cy, r, rotation, function (tex) {
const l = sampleLandRgbaLegacy(continents, mercFlat, tex);
return l || [0, 0, 0, 0];
})
);
} else if (renderVersion === 2) {
blitLayer(
ctx,
cx,
cy,
r,
fillDiskRegion(ctx, cx, cy, r, rotation, function (tex) {
const l = sampleLandRgbaMercator(mercFlat, tex);
return l || [0, 0, 0, 0];
})
);
}
drawRim(ctx, globeImg, cx, cy, r, rim, layoutScale);
drawGlobeGrid(ctx, cx, cy, r, rotation, layoutScale, renderVersion);
drawOceanRim(ctx, globeImg, cx, cy, r, rim, layoutScale, renderVersion);
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
@@ -723,7 +806,14 @@ export async function mountHubGlobe(stage, opts) {
raf = requestAnimationFrame(tick);
stage.dataset.globe3d = String(renderVersion);
stage.dataset.globeAxis = renderVersion >= 2 ? 'ew-ns-sphere' : 'ew-ns-grid';
stage.dataset.globeAxis =
renderVersion >= 5
? 'ew-hub-ellipse'
: renderVersion >= 3
? 'ew-sphere'
: renderVersion >= 2
? 'ew-ns-sphere'
: 'ew-ns-grid';
return {
destroy() {