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

feat(globe3d=11): latf/lngf/zy/zx land plate zoom URL params

Default latf=1.25; linear multiply on lon/lat plus optional non-linear
bias via zy/zx toward equator and prime meridian.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Anton Afanasyeu
2026-08-05 19:34:26 +02:00
parent ae83a64042
commit a4e582d212
2 changed files with 62 additions and 24 deletions

View File

@@ -12,7 +12,7 @@
* globe3d=9: v4 sphere grid only (thin EW + NS) — proper rotation baseline. * globe3d=9: v4 sphere grid only (thin EW + NS) — proper rotation baseline.
* globe3d=10: v4 visible-arc → screen ellipse (hub ry), disk-clipped. * globe3d=10: v4 visible-arc → screen ellipse (hub ry), disk-clipped.
* globe3d=11: v10 grid + geographic mercator-flat land (360° U-wrap). * globe3d=11: v10 grid + geographic mercator-flat land (360° U-wrap).
* ?f=0.66 — NS latitude factor (default 0.66; lower = more squeeze). * ?latf=1.25&lngf=1&zy=0&zx=0 — land plate zoom (linear + non-linear).
*/ */
export const GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 322, rim: 330 }; export const GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 322, rim: 330 };
@@ -30,14 +30,39 @@ const CONTINENT_INNER_SX = 1.2408477842003853;
const CONTINENT_INNER_SY = 1.2408477842003853; const CONTINENT_INNER_SY = 1.2408477842003853;
/** Map plate center longitude (extract_red_polygons.py CENTER_LON). */ /** Map plate center longitude (extract_red_polygons.py CENTER_LON). */
const MERCATOR_LON_CENTER = (12 * Math.PI) / 180; const MERCATOR_LON_CENTER = (12 * Math.PI) / 180;
/** Default NS latitude factor (override with ?f= on globe3d=11). */ /** Default land-plate zoom (?latf / ?lngf / ?zy / ?zx on globe3d=11). */
const CONTINENT_LAT_FACTOR_DEFAULT = 0.66; const LAND_ZOOM_DEFAULT = { latf: 1.25, lngf: 1, zy: 0, zx: 0 };
function resolveContinentLatFactor(raw) { function clampZoomNum(v, min, max, fallback) {
if (typeof raw !== 'number' || !isFinite(raw)) { if (typeof v !== 'number' || !isFinite(v)) {
return CONTINENT_LAT_FACTOR_DEFAULT; return fallback;
} }
return Math.max(0.05, Math.min(2, raw)); return Math.max(min, Math.min(max, v));
}
function resolveLandZoom(raw) {
const r = raw && typeof raw === 'object' ? raw : {};
return {
latf: clampZoomNum(r.latf, 0.05, 4, LAND_ZOOM_DEFAULT.latf),
lngf: clampZoomNum(r.lngf, 0.05, 4, LAND_ZOOM_DEFAULT.lngf),
zy: clampZoomNum(r.zy, -4, 4, LAND_ZOOM_DEFAULT.zy),
zx: clampZoomNum(r.zx, -4, 4, LAND_ZOOM_DEFAULT.zx),
};
}
/**
* Linear latf/lngf on sphere lon/lat; zy/zx bias zoom toward equator/ prime meridian:
* mul = factor × (1 + z×(1t²)), t = |coord|/pole (1 at pole, 0 at center line).
*/
function landZoomLonLat(lon, lat, zoom) {
const latT = Math.min(1, Math.abs(lat) / (Math.PI / 2));
const lonT = Math.min(1, Math.abs(lon) / Math.PI);
const latMul = zoom.latf * (1 + zoom.zy * (1 - latT * latT));
const lonMul = zoom.lngf * (1 + zoom.zx * (1 - lonT * lonT));
return {
lon: wrapLon(lon * lonMul + MERCATOR_LON_CENTER),
lat: Math.max(-1.52, Math.min(1.52, lat * latMul)),
};
} }
const RASTER_SCALE = 2; const RASTER_SCALE = 2;
const AXIS_STEPS = 160; const AXIS_STEPS = 160;
@@ -961,11 +986,10 @@ function sampleFlatPlateFromHub(mercFlat, hpX, hpY) {
} }
/** Sphere tex → mercator-flat plate (lon center + inverse Mercator Y). */ /** Sphere tex → mercator-flat plate (lon center + inverse Mercator Y). */
function sampleMercFlatFromTex(mercFlat, tex, latFactor) { function sampleMercFlatFromTex(mercFlat, tex, landZoom) {
const ll = texLonLat(tex); const ll = texLonLat(tex);
const lon = wrapLon(ll.lon + MERCATOR_LON_CENTER); const mapped = landZoomLonLat(ll.lon, ll.lat, landZoom);
const lat = Math.max(-1.52, Math.min(1.52, ll.lat * latFactor)); return sampleMercFlat(mercFlat, mapped.lon, mapped.lat);
return sampleMercFlat(mercFlat, lon, lat);
} }
function isLandPixel(r, g, b, a) { function isLandPixel(r, g, b, a) {
@@ -1077,11 +1101,11 @@ function sampleLandRgbaMercator(mercFlat, tex) {
} }
/** globe3d=11 — geographic mercator wrap (360°); z > LIMB_Z only. */ /** globe3d=11 — geographic mercator wrap (360°); z > LIMB_Z only. */
function sampleLandVisibleHemisphere(mercFlat, tex, latFactor) { function sampleLandVisibleHemisphere(mercFlat, tex, landZoom) {
if (tex.z <= LIMB_Z) { if (tex.z <= LIMB_Z) {
return null; return null;
} }
const flat = sampleMercFlatFromTex(mercFlat, tex, latFactor); const flat = sampleMercFlatFromTex(mercFlat, tex, landZoom);
if (isHubContinentPixel(flat[0], flat[1], flat[2], flat[3])) { if (isHubContinentPixel(flat[0], flat[1], flat[2], flat[3])) {
return hubContinentRgba(flat); return hubContinentRgba(flat);
} }
@@ -1187,7 +1211,7 @@ export async function mountHubGlobe(stage, opts) {
const build = opts.build || 'earth3d'; const build = opts.build || 'earth3d';
const reducedMotion = !!opts.reducedMotion; const reducedMotion = !!opts.reducedMotion;
const renderVersion = Math.max(1, parseInt(opts.renderVersion, 10) || 1); const renderVersion = Math.max(1, parseInt(opts.renderVersion, 10) || 1);
const continentLatFactor = resolveContinentLatFactor(opts.continentLatFactor); const landZoom = resolveLandZoom(opts.landZoom);
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.className = 'hub-globe-canvas'; canvas.className = 'hub-globe-canvas';
@@ -1299,7 +1323,7 @@ export async function mountHubGlobe(stage, opts) {
cy, cy,
r, r,
fillDiskRegion(ctx, cx, cy, r, rotation, function (tex) { fillDiskRegion(ctx, cx, cy, r, rotation, function (tex) {
const l = sampleLandVisibleHemisphere(mercFlat, tex, continentLatFactor); const l = sampleLandVisibleHemisphere(mercFlat, tex, landZoom);
return l || [0, 0, 0, 0]; return l || [0, 0, 0, 0];
}) })
); );
@@ -1375,7 +1399,10 @@ export async function mountHubGlobe(stage, opts) {
raf = requestAnimationFrame(tick); raf = requestAnimationFrame(tick);
stage.dataset.globe3d = String(renderVersion); stage.dataset.globe3d = String(renderVersion);
if (renderVersion >= 11) { if (renderVersion >= 11) {
stage.dataset.globeLatFactor = String(continentLatFactor); stage.dataset.globeLatf = String(landZoom.latf);
stage.dataset.globeLngf = String(landZoom.lngf);
stage.dataset.globeZy = String(landZoom.zy);
stage.dataset.globeZx = String(landZoom.zx);
} }
stage.dataset.globeAxis = stage.dataset.globeAxis =
renderVersion === 11 renderVersion === 11
@@ -1405,7 +1432,10 @@ export async function mountHubGlobe(stage, opts) {
canvas.remove(); canvas.remove();
delete stage.dataset.globe3d; delete stage.dataset.globe3d;
delete stage.dataset.globeAxis; delete stage.dataset.globeAxis;
delete stage.dataset.globeLatFactor; delete stage.dataset.globeLatf;
delete stage.dataset.globeLngf;
delete stage.dataset.globeZy;
delete stage.dataset.globeZx;
}, },
}; };
} }

View File

@@ -187,7 +187,7 @@ function hub_h(string $s): string {
var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover')); var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover'));
var logoEnabled = !!(stage && supportsSvg && supportsLayout); var logoEnabled = !!(stage && supportsSvg && supportsLayout);
var logoBuild = '20260704globe25'; var logoBuild = '20260704globe26';
function hubAsset(rel) { function hubAsset(rel) {
var link = document.querySelector('link[href*="hub.css"]'); var link = document.querySelector('link[href*="hub.css"]');
@@ -256,10 +256,18 @@ function hub_h(string $s): string {
var globe3dRaw = params.get('globe3d'); var globe3dRaw = params.get('globe3d');
var globe3dVersion = globe3dRaw === null || globe3dRaw === '' ? 1 : parseInt(globe3dRaw, 10); var globe3dVersion = globe3dRaw === null || globe3dRaw === '' ? 1 : parseInt(globe3dRaw, 10);
if (!isFinite(globe3dVersion)) globe3dVersion = 1; if (!isFinite(globe3dVersion)) globe3dVersion = 1;
var globeLatFactorRaw = params.get('f'); var globeLandZoom = {};
var globeLatFactor = globeLatFactorRaw === null || globeLatFactorRaw === '' ['latf', 'lngf', 'zy', 'zx'].forEach(function (key) {
? NaN var raw = params.get(key);
: parseFloat(globeLatFactorRaw); if (raw === null || raw === '') return;
var n = parseFloat(raw);
if (isFinite(n)) globeLandZoom[key] = n;
});
var globeLatfLegacy = params.get('f');
if (globeLatfLegacy !== null && globeLatfLegacy !== '' && !isFinite(globeLandZoom.latf)) {
var fLegacy = parseFloat(globeLatfLegacy);
if (isFinite(fLegacy)) globeLandZoom.latf = fLegacy;
}
var useGlobe3d = logoEnabled && globe3dVersion !== 0; var useGlobe3d = logoEnabled && globe3dVersion !== 0;
var globe3dLayers = { globe: true, continents: true, gridOverlay: true }; var globe3dLayers = { globe: true, continents: true, gridOverlay: true };
@@ -304,8 +312,8 @@ function hub_h(string $s): string {
if (isFinite(globeYawDeg)) { if (isFinite(globeYawDeg)) {
globeOpts.initialRotationY = globeYawDeg * Math.PI / 180; globeOpts.initialRotationY = globeYawDeg * Math.PI / 180;
} }
if (isFinite(globeLatFactor)) { if (Object.keys(globeLandZoom).length > 0) {
globeOpts.continentLatFactor = globeLatFactor; globeOpts.landZoom = globeLandZoom;
} }
var globeModuleUrl = new URL('assets/hub-globe.js', window.location.href); var globeModuleUrl = new URL('assets/hub-globe.js', window.location.href);
globeModuleUrl.searchParams.set('v', logoBuild); globeModuleUrl.searchParams.set('v', logoBuild);