From ae83a6404222bc6cc626dd5c64a62daadd382683 Mon Sep 17 00:00:00 2001 From: Anton Afanasyeu Date: Wed, 5 Aug 2026 19:27:56 +0200 Subject: [PATCH] feat(globe3d=11): ?f= URL param for N-S latitude factor tuning Default remains 0.66; exposed on #hub-logo-stage as data-globe-lat-factor. Co-authored-by: Cursor --- assets/hub-globe.js | 27 ++++++++++++++++++++------- index.php | 9 ++++++++- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/assets/hub-globe.js b/assets/hub-globe.js index a018014..448eb5a 100644 --- a/assets/hub-globe.js +++ b/assets/hub-globe.js @@ -12,6 +12,7 @@ * globe3d=9: v4 sphere grid only (thin E–W + N–S) — proper rotation baseline. * globe3d=10: v4 visible-arc → screen ellipse (hub ry), disk-clipped. * globe3d=11: v10 grid + geographic mercator-flat land (360° U-wrap). + * ?f=0.66 — N–S latitude factor (default 0.66; lower = more squeeze). */ export const GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 322, rim: 330 }; @@ -29,8 +30,15 @@ const CONTINENT_INNER_SX = 1.2408477842003853; const CONTINENT_INNER_SY = 1.2408477842003853; /** Map plate center longitude (extract_red_polygons.py CENTER_LON). */ const MERCATOR_LON_CENTER = (12 * Math.PI) / 180; -/** N–S latitude factor on mercator sample (0.75 = lat × 0.75). */ -const CONTINENT_LAT_FACTOR = 0.66; +/** Default N–S latitude factor (override with ?f= on globe3d=11). */ +const CONTINENT_LAT_FACTOR_DEFAULT = 0.66; + +function resolveContinentLatFactor(raw) { + if (typeof raw !== 'number' || !isFinite(raw)) { + return CONTINENT_LAT_FACTOR_DEFAULT; + } + return Math.max(0.05, Math.min(2, raw)); +} const RASTER_SCALE = 2; const AXIS_STEPS = 160; const SPHERE_AXIS_STEPS = 180; @@ -953,10 +961,10 @@ function sampleFlatPlateFromHub(mercFlat, hpX, hpY) { } /** Sphere tex → mercator-flat plate (lon center + inverse Mercator Y). */ -function sampleMercFlatFromTex(mercFlat, tex) { +function sampleMercFlatFromTex(mercFlat, tex, latFactor) { const ll = texLonLat(tex); const lon = wrapLon(ll.lon + MERCATOR_LON_CENTER); - const lat = Math.max(-1.52, Math.min(1.52, ll.lat * CONTINENT_LAT_FACTOR)); + const lat = Math.max(-1.52, Math.min(1.52, ll.lat * latFactor)); return sampleMercFlat(mercFlat, lon, lat); } @@ -1069,11 +1077,11 @@ function sampleLandRgbaMercator(mercFlat, tex) { } /** globe3d=11 — geographic mercator wrap (360°); z > LIMB_Z only. */ -function sampleLandVisibleHemisphere(mercFlat, tex) { +function sampleLandVisibleHemisphere(mercFlat, tex, latFactor) { if (tex.z <= LIMB_Z) { return null; } - const flat = sampleMercFlatFromTex(mercFlat, tex); + const flat = sampleMercFlatFromTex(mercFlat, tex, latFactor); if (isHubContinentPixel(flat[0], flat[1], flat[2], flat[3])) { return hubContinentRgba(flat); } @@ -1179,6 +1187,7 @@ export async function mountHubGlobe(stage, opts) { const build = opts.build || 'earth3d'; const reducedMotion = !!opts.reducedMotion; const renderVersion = Math.max(1, parseInt(opts.renderVersion, 10) || 1); + const continentLatFactor = resolveContinentLatFactor(opts.continentLatFactor); const canvas = document.createElement('canvas'); canvas.className = 'hub-globe-canvas'; @@ -1290,7 +1299,7 @@ export async function mountHubGlobe(stage, opts) { cy, r, fillDiskRegion(ctx, cx, cy, r, rotation, function (tex) { - const l = sampleLandVisibleHemisphere(mercFlat, tex); + const l = sampleLandVisibleHemisphere(mercFlat, tex, continentLatFactor); return l || [0, 0, 0, 0]; }) ); @@ -1365,6 +1374,9 @@ export async function mountHubGlobe(stage, opts) { raf = requestAnimationFrame(tick); stage.dataset.globe3d = String(renderVersion); + if (renderVersion >= 11) { + stage.dataset.globeLatFactor = String(continentLatFactor); + } stage.dataset.globeAxis = renderVersion === 11 ? 'ew-sphere-ellipse-land' @@ -1393,6 +1405,7 @@ export async function mountHubGlobe(stage, opts) { canvas.remove(); delete stage.dataset.globe3d; delete stage.dataset.globeAxis; + delete stage.dataset.globeLatFactor; }, }; } diff --git a/index.php b/index.php index 700481d..1056c59 100644 --- a/index.php +++ b/index.php @@ -187,7 +187,7 @@ function hub_h(string $s): string { var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover')); var logoEnabled = !!(stage && supportsSvg && supportsLayout); - var logoBuild = '20260704globe24'; + var logoBuild = '20260704globe25'; function hubAsset(rel) { var link = document.querySelector('link[href*="hub.css"]'); @@ -256,6 +256,10 @@ function hub_h(string $s): string { var globe3dRaw = params.get('globe3d'); var globe3dVersion = globe3dRaw === null || globe3dRaw === '' ? 1 : parseInt(globe3dRaw, 10); if (!isFinite(globe3dVersion)) globe3dVersion = 1; + var globeLatFactorRaw = params.get('f'); + var globeLatFactor = globeLatFactorRaw === null || globeLatFactorRaw === '' + ? NaN + : parseFloat(globeLatFactorRaw); var useGlobe3d = logoEnabled && globe3dVersion !== 0; var globe3dLayers = { globe: true, continents: true, gridOverlay: true }; @@ -300,6 +304,9 @@ function hub_h(string $s): string { if (isFinite(globeYawDeg)) { globeOpts.initialRotationY = globeYawDeg * Math.PI / 180; } + if (isFinite(globeLatFactor)) { + globeOpts.continentLatFactor = globeLatFactor; + } var globeModuleUrl = new URL('assets/hub-globe.js', window.location.href); globeModuleUrl.searchParams.set('v', logoBuild); globeModuleUrl.searchParams.set('globe3d', String(globe3dVersion));