mirror of
git://f0xx.org/ac/ac-be-hub
synced 2026-08-09 20:59:23 +03:00
fix(globe3d=11): ortho hub land + affine flat wrap fallback
Restore sampleHub for reference N-S proportions; wrap via inverse inner-layer affine on mercator-flat with seamless U bilinear. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
* globe3d=8: hub SVG ellipse trace (breaks under rotation — deprecated).
|
||||
* 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 + mercator-flat land wrap (lon center 12°, inverse Mercator Y).
|
||||
* globe3d=11: v10 grid + ortho hub land + affine flat-plate wrap fallback.
|
||||
*/
|
||||
export const GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 322, rim: 330 };
|
||||
|
||||
@@ -20,8 +20,13 @@ 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;
|
||||
/** hub-logo-continents inner layer fit (continents-meta.json / extract_red_polygons.py). */
|
||||
const CONTINENT_FLAT_W = 800;
|
||||
const CONTINENT_FLAT_H = 519;
|
||||
const CONTINENT_INNER_TX = 223.66088631984587;
|
||||
const CONTINENT_INNER_TY = 238.0;
|
||||
const CONTINENT_INNER_SX = 1.2408477842003853;
|
||||
const CONTINENT_INNER_SY = 1.2408477842003853;
|
||||
const RASTER_SCALE = 2;
|
||||
const AXIS_STEPS = 160;
|
||||
const SPHERE_AXIS_STEPS = 180;
|
||||
@@ -135,6 +140,34 @@ function rasterizeImage(img, w, h, scale) {
|
||||
return { data: ctx.getImageData(0, 0, rw, rh).data, w: rw, h: rh };
|
||||
}
|
||||
|
||||
function sampleImageDataBilinearWrapU(imgData, w, h, fx, fy) {
|
||||
const y = Math.max(0, Math.min(h - 1.001, fy));
|
||||
let x = fx % w;
|
||||
if (x < 0) {
|
||||
x += w;
|
||||
}
|
||||
const x0 = x | 0;
|
||||
const tx = x - x0;
|
||||
const x1 = (x0 + 1) % w;
|
||||
const y0 = y | 0;
|
||||
const y1 = Math.min(h - 1, y0 + 1);
|
||||
const ty = y - y0;
|
||||
const i00 = (y0 * w + x0) * 4;
|
||||
const i10 = (y0 * w + x1) * 4;
|
||||
const i01 = (y1 * w + x0) * 4;
|
||||
const i11 = (y1 * w + x1) * 4;
|
||||
const out = [0, 0, 0, 0];
|
||||
for (let c = 0; c < 4; c++) {
|
||||
out[c] = Math.round(
|
||||
imgData[i00 + c] * (1 - tx) * (1 - ty) +
|
||||
imgData[i10 + c] * tx * (1 - ty) +
|
||||
imgData[i01 + c] * (1 - tx) * ty +
|
||||
imgData[i11 + c] * tx * ty
|
||||
);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function sampleImageDataBilinear(imgData, w, h, fx, fy) {
|
||||
const x = Math.max(0, Math.min(w - 1.001, fx));
|
||||
const y = Math.max(0, Math.min(h - 1.001, fy));
|
||||
@@ -900,13 +933,25 @@ function latToMercatorRow(lat, h) {
|
||||
function sampleMercFlat(mercFlat, lon, lat) {
|
||||
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);
|
||||
return sampleImageDataBilinearWrapU(mercFlat.data, mercFlat.w, mercFlat.h, u, v);
|
||||
}
|
||||
|
||||
/** Inverse hub-disk → flat plate (same affine as globe SVG), X wraps 360°. */
|
||||
function sampleFlatPlateFromHub(mercFlat, hpX, hpY) {
|
||||
const fx = (hpX - CONTINENT_INNER_TX) / CONTINENT_INNER_SX;
|
||||
const fy = (hpY - CONTINENT_INNER_TY) / CONTINENT_INNER_SY;
|
||||
if (fy < 0 || fy > CONTINENT_FLAT_H - 1) {
|
||||
return null;
|
||||
}
|
||||
const sx = (fx / CONTINENT_FLAT_W) * mercFlat.w;
|
||||
const sy = (fy / CONTINENT_FLAT_H) * mercFlat.h;
|
||||
return sampleImageDataBilinearWrapU(mercFlat.data, mercFlat.w, mercFlat.h, sx, sy);
|
||||
}
|
||||
|
||||
/** 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);
|
||||
const lon = wrapLon(ll.lon + (12 * Math.PI) / 180);
|
||||
return sampleMercFlat(mercFlat, lon, ll.lat);
|
||||
}
|
||||
|
||||
@@ -936,6 +981,24 @@ function hubContinentRgba(sample) {
|
||||
return [sample[0], sample[1], sample[2], 255];
|
||||
}
|
||||
|
||||
/** Solid fill from rasterized globe SVG — skip gradient fringe / drop-shadow. */
|
||||
function isSolidContinentPixel(r, g, b, a) {
|
||||
if (a < 72) {
|
||||
return false;
|
||||
}
|
||||
if (r > 168 && g > 198 && b > 218) {
|
||||
return false;
|
||||
}
|
||||
if (r < 58 && g < 68 && b < 95) {
|
||||
return false;
|
||||
}
|
||||
const lum = r * 0.299 + g * 0.587 + b * 0.114;
|
||||
if (lum < 102 || lum > 218) {
|
||||
return false;
|
||||
}
|
||||
return b >= 105 && g >= 100 && r >= 72;
|
||||
}
|
||||
|
||||
/** Light hub grid strokes baked into hub-logo-fragment-globe.svg (vector grid drawn separately). */
|
||||
function isGridPixel(r, g, b, a) {
|
||||
if (a < 12) {
|
||||
@@ -1000,13 +1063,19 @@ function sampleLandRgbaMercator(mercFlat, tex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** globe3d=11 — visible hemisphere (z > LIMB_Z); mercator-flat wrap (single source, no ortho ghost). */
|
||||
function sampleLandVisibleHemisphere(mercFlat, tex) {
|
||||
/** globe3d=11 — ortho hub (reference N–S) + wrapped flat-plate fallback. */
|
||||
function sampleLandVisibleHemisphere(continents, mercFlat, tex) {
|
||||
if (tex.z <= LIMB_Z) {
|
||||
return null;
|
||||
}
|
||||
const flat = sampleMercFlatFromTex(mercFlat, tex);
|
||||
if (isHubContinentPixel(flat[0], flat[1], flat[2], flat[3])) {
|
||||
const ortho = sampleHub(continents, tex);
|
||||
if (isSolidContinentPixel(ortho[0], ortho[1], ortho[2], ortho[3])) {
|
||||
return hubContinentRgba(ortho);
|
||||
}
|
||||
const hpX = GLOBE_VIEW.cx + tex.x * GLOBE_VIEW.r;
|
||||
const hpY = GLOBE_VIEW.cy - tex.y * GLOBE_VIEW.r;
|
||||
const flat = sampleFlatPlateFromHub(mercFlat, hpX, hpY);
|
||||
if (flat && isHubContinentPixel(flat[0], flat[1], flat[2], flat[3])) {
|
||||
return hubContinentRgba(flat);
|
||||
}
|
||||
return null;
|
||||
@@ -1222,7 +1291,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(continents, mercFlat, tex);
|
||||
return l || [0, 0, 0, 0];
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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 = '20260704globe15';
|
||||
var logoBuild = '20260704globe16';
|
||||
|
||||
function hubAsset(rel) {
|
||||
var link = document.querySelector('link[href*="hub.css"]');
|
||||
|
||||
Reference in New Issue
Block a user