From d82bd4d78ea94625cac1b19170f9b18ab177e861 Mon Sep 17 00:00:00 2001 From: Anton Afanasyeu Date: Wed, 5 Aug 2026 17:00:15 +0200 Subject: [PATCH] Hub globe3d=10: v4 latitude arcs with screen-Y strip depth. Hub 2D ellipses (v8) are not latitude circles and fail when rotated. Use v4 sphere arcs plus hub ry vertical thickness clipped at limb. Co-authored-by: Cursor --- assets/hub-globe.js | 98 ++++++++++++++++++++++++++++++++++++++++++++- index.php | 2 +- 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/assets/hub-globe.js b/assets/hub-globe.js index 0284c46..946ae61 100644 --- a/assets/hub-globe.js +++ b/assets/hub-globe.js @@ -8,8 +8,9 @@ * globe3d=5: hub ellipse trace (deprecated — breaks under rotation). * globe3d=6: v4 math + latitude band fill (continuous arc paths). * globe3d=7: v4 math + wide centerline stroke bands (no fill quads). - * globe3d=8: hub SVG ellipse → sphere trace, wide stroke, gap-safe (ellipses at rest). - * globe3d=9: v8 parallels + v4 sphere meridians (split best-of). + * globe3d=8: hub SVG ellipse trace (breaks under rotation — deprecated). + * globe3d=9: reserved / same as 10. + * globe3d=10: v4 latitude arcs + screen-Y strip thickness (hub ry), limb-clipped. */ export const GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 322, rim: 330 }; @@ -549,6 +550,99 @@ function drawHubEllipseGridRotated(ctx, cx, cy, r, rotY, layoutScale) { ctx.restore(); } +/** v4 latitude arc with hub ry as vertical screen thickness — rotates correctly. */ +function fillParallelScreenStrip(ctx, cx, cy, r, rotY, par) { + const halfPx = (par.bright ? HUB_EW_PARALLELS[0].ry : HUB_EW_PARALLELS[1].ry) * (r / GLOBE_VIEW.r); + const ring = parallelBodyRingAtLat(par.lat, SPHERE_AXIS_STEPS); + const rgb = par.bright ? '219,231,246' : '231,236,243'; + let run = []; + + function flushFill() { + if (run.length < 2) { + run = []; + return; + } + let zSum = 0; + for (let k = 0; k < run.length; k++) { + zSum += run[k].z; + } + const zAvg = zSum / run.length; + const t = Math.max(0, Math.min(1, (zAvg - LIMB_Z) / (0.95 - LIMB_Z))); + if (t <= 0) { + run = []; + return; + } + const alpha = (par.bright ? 0.16 + 0.48 * t : 0.09 + 0.34 * t).toFixed(3); + ctx.beginPath(); + for (let k = 0; k < run.length; k++) { + ctx.lineTo(run[k].sx, run[k].sy - halfPx); + } + for (let k = run.length - 1; k >= 0; k--) { + ctx.lineTo(run[k].sx, run[k].sy + halfPx); + } + ctx.closePath(); + ctx.fillStyle = 'rgba(' + rgb + ',' + alpha + ')'; + ctx.fill(); + run = []; + } + + for (let i = 0; i < ring.length; i++) { + const view = bodyToView(ring[i], rotY); + if (view.z <= LIMB_Z) { + flushFill(); + continue; + } + run.push({ + sx: cx + view.x * r, + sy: cy - view.y * r, + z: view.z, + }); + } + flushFill(); + + strokeBodyRing( + ctx, + cx, + cy, + r, + rotY, + ring, + function (z) { + return parallelStyle(par.bright, 1, z, LIMB_Z); + }, + LIMB_Z + ); +} + +/** globe3d=10 — v4 E–W arcs, screen-vertical band; v4 meridians. */ +function drawSphereGridScreenStrip(ctx, cx, cy, r, rotY, layoutScale) { + ctx.save(); + ctx.beginPath(); + ctx.arc(cx, cy, r, 0, Math.PI * 2); + ctx.clip(); + + SPHERE_PARALLELS.forEach(function (par) { + fillParallelScreenStrip(ctx, cx, cy, r, rotY, par); + }); + + 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(); +} + /** globe3d=7 — v4 sphere model, wide stroke bands (no path fill). */ function drawSphereGridWideStroke(ctx, cx, cy, r, rotY, layoutScale, opts) { const parallelsOnly = !!(opts && opts.parallelsOnly); diff --git a/index.php b/index.php index 686f990..f0ba7b5 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 = '20260704globe8'; + var logoBuild = '20260704globe10'; function hubAsset(rel) { var link = document.querySelector('link[href*="hub.css"]');