From 5eaad41405d7384c1af4936ea10a570acc53d582 Mon Sep 17 00:00:00 2001 From: Anton Afanasyeu Date: Sat, 6 Jun 2026 17:31:42 +0200 Subject: [PATCH] sync landing --- examples/app_hub/README.md | 2 +- examples/app_hub/assets/hub-globe.js | 135 +++++++++++---------------- examples/app_hub/index.php | 2 +- 3 files changed, 57 insertions(+), 82 deletions(-) diff --git a/examples/app_hub/README.md b/examples/app_hub/README.md index 7b2e3b1..6024580 100644 --- a/examples/app_hub/README.md +++ b/examples/app_hub/README.md @@ -13,7 +13,7 @@ Review one layer: `?logoPart=space|globe|full` Compare Earth grids: `?logoPart=globe&grid=3` (default) or `?logoPart=full&grid=4` -**3D globe background** (default): Canvas2D orthographic sphere — ocean from `hub-logo-fragment-globe.svg`, continents from `hub-logo-continents-mercator-flat.svg` (front hemisphere only), Mercator grid projected in 3D with depth-weighted lines. Slow Y-axis spin (~0.035 rad/s); drag horizontally to rotate. Disable: `?globe3d=0` (static SVG stack). Yaw override: `?globeYawDeg=24`. Deploy `assets/hub-globe.js` with `index.php` (see `DEPLOY.md`). +**3D globe background** (default): Canvas2D orthographic sphere — same assets as `?globe3d=0`: ocean from `hub-logo-fragment-globe.svg`, continents from `hub-logo-continents-mercator-globe.svg` (body-fixed disk, front face only). Mercator grid is 3D-projected with depth-weighted lines. Disable 3D: `?globe3d=0`. Yaw: `?globeYawDeg=90`. **BE only.** FE `apps.f0xx.org` sends `location /` → `http://artc0.intra.raptor.org:80` (not :8089). diff --git a/examples/app_hub/assets/hub-globe.js b/examples/app_hub/assets/hub-globe.js index 1c1b78c..5428895 100644 --- a/examples/app_hub/assets/hub-globe.js +++ b/examples/app_hub/assets/hub-globe.js @@ -1,7 +1,8 @@ /** * Hub landing globe — orthographic Canvas2D sphere. - * Ocean shading from hub-logo-fragment-globe.svg; continents from mercator-flat.svg. - * Y-axis spin with front-hemisphere-only land; grid lines are 3D-projected with depth weighting. + * Matches globe3d=0 reference: ocean from hub-logo-fragment-globe.svg, + * continents from hub-logo-continents-mercator-globe.svg (body-fixed orthographic disk). + * Y-axis spin; land only on front body face (z > 0); grid projected in 3D with depth weighting. */ export const GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 322, rim: 330 }; @@ -63,36 +64,10 @@ function sampleImageData(imgData, w, h, x, y) { const ix = Math.max(0, Math.min(w - 1, x | 0)); const iy = Math.max(0, Math.min(h - 1, y | 0)); const i = (iy * w + ix) * 4; - return [ - imgData[i], - imgData[i + 1], - imgData[i + 2], - imgData[i + 3], - ]; -} - -function isLandPixel(r, g, b, a) { - return a > 8 && r > 120 && g < 130 && b < 130; -} - -function sampleMercator(merc, lon, lat) { - const u = (lon + Math.PI) / (2 * Math.PI); - const v = 0.5 - lat / Math.PI; - const x = Math.round(u * (merc.w - 1)); - const y = Math.round(v * (merc.h - 1)); - return sampleImageData(merc.data, merc.w, merc.h, x, y); -} - -function sampleGlobeOcean(globe, xw, yw) { - const sx = Math.round(GLOBE_VIEW.cx + xw * GLOBE_VIEW.r); - const sy = Math.round(GLOBE_VIEW.cy - yw * GLOBE_VIEW.r); - const rgba = sampleImageData(globe.data, globe.w, globe.h, sx, sy); - if (rgba[3] < 8) { - return [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b, 255]; - } - return rgba; + return [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]]; } +/** View unit sphere (nx,ny,nz) → body-fixed unit sphere before Y spin. */ function inverseRotateY(nx, ny, nz, rotY) { const cosR = Math.cos(rotY); const sinR = Math.sin(rotY); @@ -111,10 +86,38 @@ function rotateY(x, y, z, rotY) { }; } +/** Orthographic hub disk coords for body point on unit sphere (same as static SVG stack). */ +function bodyToHubPixel(xb, yb) { + return { + x: Math.round(GLOBE_VIEW.cx + xb * GLOBE_VIEW.r), + y: Math.round(GLOBE_VIEW.cy - yb * GLOBE_VIEW.r), + }; +} + /** - * Orthographic disk → world lon/lat → mercator land + globe ocean (front face only). + * Sample globe3d=0 continent + ocean stack in body-fixed frame. + * Continents layer is authoritative where opaque; else globe ocean shading. */ -function drawGlobeDisk(ctx, cx, cy, r, rotY, merc, globe) { +function sampleBodySurface(continents, globe, xb, yb, zb) { + if (zb <= FRONT_Z) { + return [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b]; + } + const p = bodyToHubPixel(xb, yb); + const land = sampleImageData(continents.data, continents.w, continents.h, p.x, p.y); + if (land[3] > 24) { + return [land[0], land[1], land[2]]; + } + const ocean = sampleImageData(globe.data, globe.w, globe.h, p.x, p.y); + if (ocean[3] < 8) { + return [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b]; + } + return [ocean[0], ocean[1], ocean[2]]; +} + +/** + * Visible orthographic disk → inverse Y-rotation → body-fixed texture (globe3d=0 reference). + */ +function drawGlobeDisk(ctx, cx, cy, r, rotY, continents, globe) { const r2 = r * r; const x0 = Math.max(0, Math.floor(cx - r)); const y0 = Math.max(0, Math.floor(cy - r)); @@ -143,34 +146,12 @@ function drawGlobeDisk(ctx, cx, cy, r, rotY, merc, globe) { const nx = ox / r; const ny = oy / r; const nz = Math.sqrt(Math.max(0, 1 - nx * nx - ny * ny)); - const wpos = inverseRotateY(nx, ny, nz, rotY); - const lon = Math.atan2(wpos.x, wpos.z); - const lat = Math.asin(Math.max(-1, Math.min(1, wpos.y))); + const body = inverseRotateY(nx, ny, nz, rotY); + const rgb = sampleBodySurface(continents, globe, body.x, body.y, body.z); - let r8; - let g8; - let b8; - if (wpos.z > FRONT_Z) { - const land = sampleMercator(merc, lon, lat); - if (isLandPixel(land[0], land[1], land[2], land[3])) { - r8 = land[0]; - g8 = land[1]; - b8 = land[2]; - } else { - const ocean = sampleGlobeOcean(globe, wpos.x, wpos.y); - r8 = ocean[0]; - g8 = ocean[1]; - b8 = ocean[2]; - } - } else { - r8 = OCEAN_FALLBACK.r; - g8 = OCEAN_FALLBACK.g; - b8 = OCEAN_FALLBACK.b; - } - - px[p++] = r8; - px[p++] = g8; - px[p++] = b8; + px[p++] = rgb[0]; + px[p++] = rgb[1]; + px[p++] = rgb[2]; px[p++] = 255; } } @@ -260,7 +241,6 @@ function drawGridOverlay(ctx, rect, rotY) { const latRad = latFrac * Math.PI; const cosLat = Math.cos(latRad); const sinLat = Math.sin(latRad); - const styleFn = depthStyle(lwThin, 0.55, 0.2); strokeCurve3D( ctx, cx, @@ -272,8 +252,10 @@ function drawGridOverlay(ctx, rect, rotY) { return { x: cosLat * Math.sin(lon), y: sinLat, z: cosLat * Math.cos(lon) }; }, function (z) { - const s = styleFn(z); - return { width: s.width, color: 'rgba(231,236,243,' + (0.2 + 0.52 * Math.max(0, (z - FRONT_Z) / (1 - FRONT_Z))).toFixed(3) + ')' }; + return { + width: lwThin * (0.35 + 0.65 * Math.max(0, (z - FRONT_Z) / (1 - FRONT_Z))), + color: 'rgba(231,236,243,' + (0.2 + 0.52 * Math.max(0, (z - FRONT_Z) / (1 - FRONT_Z))).toFixed(3) + ')', + }; } ); }); @@ -294,8 +276,7 @@ function drawGridOverlay(ctx, rect, rotY) { return { x: cosLat * Math.sin(lonRad), y: Math.sin(lat), z: cosLat * Math.cos(lonRad) }; }, function (z) { - const s = styleFn(z); - return s; + return styleFn(z); } ); } @@ -341,15 +322,15 @@ export async function mountHubGlobe(stage, opts) { const urls = { globe: hubAsset('assets/hub-logo-fragment-globe.svg', build), - mercatorFlat: hubAsset('assets/hub-logo-continents-mercator-flat.svg', build), + continents: hubAsset('assets/hub-logo-continents-mercator-globe.svg', build), }; let globeImg; - let mercatorFlatImg; + let continentsImg; try { - [globeImg, mercatorFlatImg] = await Promise.all([ + [globeImg, continentsImg] = await Promise.all([ loadImage(urls.globe), - loadImage(urls.mercatorFlat), + loadImage(urls.continents), ]); } catch (e) { console.warn('HubGlobe: asset load failed', e); @@ -362,10 +343,10 @@ export async function mountHubGlobe(stage, opts) { h: GLOBE_VIEW.imgH, data: rasterizeImage(globeImg, GLOBE_VIEW.imgW, GLOBE_VIEW.imgH).data, }; - const merc = { - w: mercatorFlatImg.naturalWidth || 800, - h: mercatorFlatImg.naturalHeight || 519, - data: rasterizeImage(mercatorFlatImg, mercatorFlatImg.naturalWidth || 800, mercatorFlatImg.naturalHeight || 519).data, + const continents = { + w: GLOBE_VIEW.imgW, + h: GLOBE_VIEW.imgH, + data: rasterizeImage(continentsImg, GLOBE_VIEW.imgW, GLOBE_VIEW.imgH).data, }; let rotation = typeof opts.initialRotationY === 'number' ? opts.initialRotationY : 0; @@ -402,16 +383,10 @@ export async function mountHubGlobe(stage, opts) { const cy = (rect.cy - rect.top) * dpr; const r = rect.r * dpr; const rim = rect.rim * dpr; - const drawRect = { - cx, - cy, - r, - rim, - scale: rect.scale * dpr, - }; + const drawRect = { cx, cy, r, rim, scale: rect.scale * dpr }; ctx.clearRect(0, 0, px, px); - drawGlobeDisk(ctx, cx, cy, r, rotation, merc, globe); + drawGlobeDisk(ctx, cx, cy, r, rotation, continents, globe); drawRim(ctx, globeImg, cx, cy, r, rim, rect.scale * dpr); drawGridOverlay(ctx, drawRect, rotation); ctx.save(); diff --git a/examples/app_hub/index.php b/examples/app_hub/index.php index 7fb9af2..f73a767 100644 --- a/examples/app_hub/index.php +++ b/examples/app_hub/index.php @@ -180,7 +180,7 @@ unset($__platformDir); var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover')); var logoEnabled = !!(stage && supportsSvg && supportsLayout); - var logoBuild = '20260606globe'; + var logoBuild = '20260606globe2'; function hubAsset(rel) { var link = document.querySelector('link[href*="hub.css"]');