mirror of
git://f0xx.org/android_cast
synced 2026-07-29 03:57:50 +03:00
test landing
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
/**
|
||||
* Hub landing globe — orthographic Canvas2D (globe3d=1).
|
||||
* Layer order matches globe3d=0 SVG stack: ocean → grid → continents.
|
||||
* Land baked to equirectangular (ortho ref + mercator-flat wrap); grid under continents.
|
||||
* Matches globe3d=0 stack: globe (ocean+base grid) → grid overlay → continents.
|
||||
* Grid/land sampled from hub SVG disks in body-fixed frame (Mercator ortho, not geodesic lines).
|
||||
*/
|
||||
export const GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 322, rim: 330 };
|
||||
|
||||
const AUTO_SPIN_RAD_S = 0.035;
|
||||
const DRAG_ROTATION_PER_PX = 0.004;
|
||||
const OCEAN_FALLBACK = { r: 22, g: 38, b: 60 };
|
||||
const EQ_W = 1024;
|
||||
const EQ_H = 512;
|
||||
const VIEW_Z_MIN = 0.06;
|
||||
const FRONT_Z = 0.02;
|
||||
const RASTER_SCALE = 2;
|
||||
|
||||
function hubAsset(rel, build) {
|
||||
const link = document.querySelector('link[href*="hub.css"]');
|
||||
@@ -52,141 +51,115 @@ function loadImage(url) {
|
||||
});
|
||||
}
|
||||
|
||||
function rasterizeImage(img, w, h) {
|
||||
function rasterizeImage(img, w, h, scale) {
|
||||
const s = scale || 1;
|
||||
const rw = Math.max(1, Math.round(w * s));
|
||||
const rh = Math.max(1, Math.round(h * s));
|
||||
const c = document.createElement('canvas');
|
||||
c.width = w;
|
||||
c.height = h;
|
||||
c.width = rw;
|
||||
c.height = rh;
|
||||
const ctx = c.getContext('2d', { willReadFrequently: true });
|
||||
ctx.drawImage(img, 0, 0, w, h);
|
||||
return ctx.getImageData(0, 0, w, h);
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
ctx.drawImage(img, 0, 0, rw, rh);
|
||||
return { data: ctx.getImageData(0, 0, rw, rh).data, w: rw, h: rh };
|
||||
}
|
||||
|
||||
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 flipSouthNorth(p) {
|
||||
return { x: p.x, y: -p.y, z: -p.z };
|
||||
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));
|
||||
const x0 = x | 0;
|
||||
const y0 = y | 0;
|
||||
const x1 = Math.min(w - 1, x0 + 1);
|
||||
const y1 = Math.min(h - 1, y0 + 1);
|
||||
const tx = x - x0;
|
||||
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 inverseRotateY(nx, ny, nz, rotY) {
|
||||
const cosR = Math.cos(rotY);
|
||||
const sinR = Math.sin(rotY);
|
||||
return flipSouthNorth({
|
||||
return {
|
||||
x: nx * cosR - nz * sinR,
|
||||
y: ny,
|
||||
z: nx * sinR + nz * cosR,
|
||||
});
|
||||
}
|
||||
|
||||
function rotateYRaw(x, y, z, rotY) {
|
||||
return {
|
||||
x: x * Math.cos(rotY) + z * Math.sin(rotY),
|
||||
y,
|
||||
z: -x * Math.sin(rotY) + z * Math.cos(rotY),
|
||||
};
|
||||
}
|
||||
|
||||
function sphereLatLon(lon, lat) {
|
||||
const cosLat = Math.cos(lat);
|
||||
return flipSouthNorth({
|
||||
x: cosLat * Math.sin(lon),
|
||||
y: Math.sin(lat),
|
||||
z: cosLat * Math.cos(lon),
|
||||
});
|
||||
/** Hub SVG disk is N-up; negate Y for texture lookup only. */
|
||||
function toTextureBody(body) {
|
||||
return { x: body.x, y: -body.y, z: body.z };
|
||||
}
|
||||
|
||||
function bodyToHubPixel(xb, yb) {
|
||||
function hubPixelF(tex) {
|
||||
return {
|
||||
x: Math.round(GLOBE_VIEW.cx + xb * GLOBE_VIEW.r),
|
||||
y: Math.round(GLOBE_VIEW.cy - yb * GLOBE_VIEW.r),
|
||||
x: GLOBE_VIEW.cx + tex.x * GLOBE_VIEW.r,
|
||||
y: GLOBE_VIEW.cy - tex.y * GLOBE_VIEW.r,
|
||||
};
|
||||
}
|
||||
|
||||
function lonLatFromBody(body) {
|
||||
function mercatorFlatUV(lon, lat, w, h) {
|
||||
return {
|
||||
lon: Math.atan2(body.x, body.z),
|
||||
lat: Math.asin(Math.max(-1, Math.min(1, body.y))),
|
||||
x: ((lon + Math.PI) / (2 * Math.PI)) * (w - 1),
|
||||
y: (0.5 - lat / Math.PI) * (h - 1),
|
||||
};
|
||||
}
|
||||
|
||||
function sampleMercatorFlat(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 sampleEquatorLand(landEq, lon, lat) {
|
||||
const u = (lon + Math.PI) / (2 * Math.PI);
|
||||
const v = 0.5 - lat / Math.PI;
|
||||
const x = Math.round(u * (EQ_W - 1));
|
||||
const y = Math.round(v * (EQ_H - 1));
|
||||
return sampleImageData(landEq.data, EQ_W, EQ_H, x, y);
|
||||
}
|
||||
|
||||
function isLandMask(r, g, b, a) {
|
||||
return a > 16;
|
||||
}
|
||||
|
||||
function isFlatLand(r, g, b, a) {
|
||||
function isLandPixel(r, g, b, a) {
|
||||
return a > 12 && r > 100 && g < 140 && b < 140;
|
||||
}
|
||||
|
||||
/** Equirect land: ortho globe3d=0 colors on front body + mercator-flat elsewhere. */
|
||||
function bakeLandEquator(continents, mercFlat) {
|
||||
const out = new Uint8ClampedArray(EQ_W * EQ_H * 4);
|
||||
for (let j = 0; j < EQ_H; j++) {
|
||||
for (let i = 0; i < EQ_W; i++) {
|
||||
const lon = (i / EQ_W) * 2 * Math.PI - Math.PI;
|
||||
const lat = Math.PI * 0.5 - (j / EQ_H) * Math.PI;
|
||||
const body = sphereLatLon(lon, lat);
|
||||
const p = (j * EQ_W + i) * 4;
|
||||
let rgba = [0, 0, 0, 0];
|
||||
|
||||
if (body.z > 0) {
|
||||
const hp = bodyToHubPixel(body.x, body.y);
|
||||
const ortho = sampleImageData(continents.data, continents.w, continents.h, hp.x, hp.y);
|
||||
if (ortho[3] > 20) {
|
||||
rgba = [ortho[0], ortho[1], ortho[2], 255];
|
||||
}
|
||||
}
|
||||
if (rgba[3] < 16) {
|
||||
const flat = sampleMercatorFlat(mercFlat, lon, lat);
|
||||
if (isFlatLand(flat[0], flat[1], flat[2], flat[3])) {
|
||||
rgba = [flat[0], flat[1], flat[2], 220];
|
||||
}
|
||||
}
|
||||
|
||||
out[p] = rgba[0];
|
||||
out[p + 1] = rgba[1];
|
||||
out[p + 2] = rgba[2];
|
||||
out[p + 3] = rgba[3];
|
||||
}
|
||||
function sampleOceanRgba(globe, tex) {
|
||||
const hp = hubPixelF(tex);
|
||||
const sx = (hp.x / GLOBE_VIEW.imgW) * (globe.w - 1);
|
||||
const sy = (hp.y / GLOBE_VIEW.imgH) * (globe.h - 1);
|
||||
const ocean = sampleImageDataBilinear(globe.data, globe.w, globe.h, sx, sy);
|
||||
if (ocean[3] < 8) {
|
||||
return [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b, 255];
|
||||
}
|
||||
return { data: out, w: EQ_W, h: EQ_H };
|
||||
return ocean;
|
||||
}
|
||||
|
||||
function viewDirBody(rotY) {
|
||||
return inverseRotateY(0, 0, 1, rotY);
|
||||
function sampleOverlayRgba(layer, tex) {
|
||||
const hp = hubPixelF(tex);
|
||||
const sx = (hp.x / GLOBE_VIEW.imgW) * (layer.w - 1);
|
||||
const sy = (hp.y / GLOBE_VIEW.imgH) * (layer.h - 1);
|
||||
return sampleImageDataBilinear(layer.data, layer.w, layer.h, sx, sy);
|
||||
}
|
||||
|
||||
function facingCamera(body, rotY) {
|
||||
const vd = viewDirBody(rotY);
|
||||
return body.x * vd.x + body.y * vd.y + body.z * vd.z > 0.002;
|
||||
}
|
||||
|
||||
function sampleOcean(globe, xb, yb) {
|
||||
const hp = bodyToHubPixel(xb, yb);
|
||||
const o = sampleImageData(globe.data, globe.w, globe.h, hp.x, hp.y);
|
||||
if (o[3] < 8) {
|
||||
return [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b];
|
||||
function sampleLandRgba(continents, mercFlat, body) {
|
||||
const tex = toTextureBody(body);
|
||||
const hp = hubPixelF(tex);
|
||||
const sx = (hp.x / GLOBE_VIEW.imgW) * (continents.w - 1);
|
||||
const sy = (hp.y / GLOBE_VIEW.imgH) * (continents.h - 1);
|
||||
const ortho = sampleImageDataBilinear(continents.data, continents.w, continents.h, sx, sy);
|
||||
if (ortho[3] > 6) {
|
||||
return ortho;
|
||||
}
|
||||
return [o[0], o[1], o[2]];
|
||||
|
||||
const lon = Math.atan2(tex.x, tex.z);
|
||||
const lat = Math.asin(Math.max(-1, Math.min(1, tex.y)));
|
||||
const uv = mercatorFlatUV(lon, lat, mercFlat.w, mercFlat.h);
|
||||
const flat = sampleImageDataBilinear(mercFlat.data, mercFlat.w, mercFlat.h, uv.x, uv.y);
|
||||
if (isLandPixel(flat[0], flat[1], flat[2], flat[3])) {
|
||||
return [flat[0], flat[1], flat[2], Math.min(255, flat[3])];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function fillDiskRegion(ctx, cx, cy, r, rotY, fn) {
|
||||
@@ -218,11 +191,11 @@ function fillDiskRegion(ctx, cx, cy, r, rotY, fn) {
|
||||
const ny = oy / r;
|
||||
const nz = Math.sqrt(Math.max(0, 1 - nx * nx - ny * ny));
|
||||
const body = inverseRotateY(nx, ny, nz, rotY);
|
||||
const rgb = fn(body);
|
||||
px[p++] = rgb[0];
|
||||
px[p++] = rgb[1];
|
||||
px[p++] = rgb[2];
|
||||
px[p++] = rgb[3] !== undefined ? rgb[3] : 255;
|
||||
const rgba = fn(body);
|
||||
px[p++] = rgba[0];
|
||||
px[p++] = rgba[1];
|
||||
px[p++] = rgba[2];
|
||||
px[p++] = rgba[3] !== undefined ? rgba[3] : 255;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,166 +214,40 @@ function blitLayer(ctx, cx, cy, r, layer) {
|
||||
|
||||
function drawOceanLayer(ctx, cx, cy, r, rotY, globe) {
|
||||
const layer = fillDiskRegion(ctx, cx, cy, r, rotY, function (body) {
|
||||
return sampleOcean(globe, body.x, body.y).concat([255]);
|
||||
if (body.z <= FRONT_Z) {
|
||||
return [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b, 255];
|
||||
}
|
||||
return sampleOceanRgba(globe, toTextureBody(body));
|
||||
});
|
||||
blitLayer(ctx, cx, cy, r, layer);
|
||||
}
|
||||
|
||||
function drawContinentsLayer(ctx, cx, cy, r, rotY, landEq) {
|
||||
function drawGridLayer(ctx, cx, cy, r, rotY, gridOverlay) {
|
||||
const layer = fillDiskRegion(ctx, cx, cy, r, rotY, function (body) {
|
||||
if (!facingCamera(body, rotY)) {
|
||||
if (body.z <= FRONT_Z) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
const ll = lonLatFromBody(body);
|
||||
const land = sampleEquatorLand(landEq, ll.lon, ll.lat);
|
||||
if (!isLandMask(land[0], land[1], land[2], land[3])) {
|
||||
const px = sampleOverlayRgba(gridOverlay, toTextureBody(body));
|
||||
if (px[3] < 10) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
return [land[0], land[1], land[2], land[3]];
|
||||
return px;
|
||||
});
|
||||
blitLayer(ctx, cx, cy, r, layer);
|
||||
}
|
||||
|
||||
/** One continuous path per visible arc — parallels stay on the sphere. */
|
||||
function strokeVisibleArcs3D(ctx, cx, cy, r, rotY, pointAt, styleAt, steps) {
|
||||
const samples = [];
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const raw = pointAt(i / steps);
|
||||
const p = rotateYRaw(raw.x, raw.y, raw.z, rotY);
|
||||
if (p.z <= VIEW_Z_MIN) {
|
||||
samples.push(null);
|
||||
continue;
|
||||
function drawLandLayer(ctx, cx, cy, r, rotY, continents, mercFlat) {
|
||||
const layer = fillDiskRegion(ctx, cx, cy, r, rotY, function (body) {
|
||||
if (body.z <= FRONT_Z) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
samples.push({ sx: cx + p.x * r, sy: cy - p.y * r, z: p.z });
|
||||
}
|
||||
|
||||
let run = [];
|
||||
function flushRun() {
|
||||
if (run.length < 2) {
|
||||
run = [];
|
||||
return;
|
||||
const rgba = sampleLandRgba(continents, mercFlat, body);
|
||||
if (!rgba) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
let zSum = 0;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(run[0].sx, run[0].sy);
|
||||
for (let i = 1; i < run.length; i++) {
|
||||
ctx.lineTo(run[i].sx, run[i].sy);
|
||||
zSum += run[i].z;
|
||||
}
|
||||
const style = styleAt(zSum / run.length);
|
||||
ctx.strokeStyle = style.color;
|
||||
ctx.lineWidth = style.width;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.stroke();
|
||||
run = [];
|
||||
}
|
||||
|
||||
for (let i = 0; i < samples.length; i++) {
|
||||
if (!samples[i]) flushRun();
|
||||
else run.push(samples[i]);
|
||||
}
|
||||
flushRun();
|
||||
}
|
||||
|
||||
function meridianStyle(isPrime, scale, z) {
|
||||
const t = Math.max(0, Math.min(1, (z - VIEW_Z_MIN) / (0.92 - VIEW_Z_MIN)));
|
||||
if (t <= 0) return { width: 0, color: 'rgba(0,0,0,0)' };
|
||||
const bw = isPrime ? Math.max(3, scale * 0.0095) : Math.max(2.5, scale * 0.008);
|
||||
const ca = isPrime ? 0.98 : 0.72;
|
||||
const ea = 0.02;
|
||||
return {
|
||||
width: bw * (0.25 + 0.75 * t * t),
|
||||
color: 'rgba(238,245,255,' + (ea + (ca - ea) * t * t).toFixed(3) + ')',
|
||||
};
|
||||
}
|
||||
|
||||
function parallelStyle(scale, z, bright) {
|
||||
const t = Math.max(0, Math.min(1, (z - VIEW_Z_MIN) / (0.92 - VIEW_Z_MIN)));
|
||||
if (t <= 0) return { width: 0, color: 'rgba(0,0,0,0)' };
|
||||
const bw = bright ? Math.max(2, scale * 0.007) : Math.max(1.5, scale * 0.0055);
|
||||
const ca = bright ? 0.88 : 0.55;
|
||||
return {
|
||||
width: bw * (0.3 + 0.7 * t),
|
||||
color: bright
|
||||
? 'rgba(219,231,246,' + (0.35 + 0.53 * t).toFixed(3) + ')'
|
||||
: 'rgba(231,236,243,' + (0.15 + 0.45 * t).toFixed(3) + ')',
|
||||
};
|
||||
}
|
||||
|
||||
function drawGridOverlay(ctx, rect, rotY) {
|
||||
const cx = rect.cx;
|
||||
const cy = rect.cy;
|
||||
const r = rect.r;
|
||||
const scale = rect.scale;
|
||||
const steps = 160;
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||
ctx.clip();
|
||||
|
||||
strokeVisibleArcs3D(
|
||||
ctx,
|
||||
cx,
|
||||
cy,
|
||||
r,
|
||||
rotY,
|
||||
function (t) {
|
||||
return sphereLatLon(-Math.PI + t * 2 * Math.PI, 0);
|
||||
},
|
||||
function (z) {
|
||||
return parallelStyle(scale, z, true);
|
||||
},
|
||||
steps
|
||||
);
|
||||
|
||||
[-0.25, 0.25].forEach(function (latFrac) {
|
||||
const lat = latFrac * Math.PI;
|
||||
strokeVisibleArcs3D(
|
||||
ctx,
|
||||
cx,
|
||||
cy,
|
||||
r,
|
||||
rotY,
|
||||
function (t) {
|
||||
return sphereLatLon(-Math.PI + t * 2 * Math.PI, lat);
|
||||
},
|
||||
function (z) {
|
||||
return parallelStyle(scale, z, false);
|
||||
},
|
||||
steps
|
||||
);
|
||||
return rgba;
|
||||
});
|
||||
|
||||
for (let lonDeg = -150; lonDeg <= 150; lonDeg += 30) {
|
||||
const lon = (lonDeg * Math.PI) / 180;
|
||||
const isPrime = lonDeg === 0;
|
||||
strokeVisibleArcs3D(
|
||||
ctx,
|
||||
cx,
|
||||
cy,
|
||||
r,
|
||||
rotY,
|
||||
function (t) {
|
||||
return sphereLatLon(lon, -Math.PI / 2 + t * Math.PI);
|
||||
},
|
||||
function (z) {
|
||||
return meridianStyle(isPrime, scale, z);
|
||||
},
|
||||
96
|
||||
);
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, Math.max(3, scale * 0.01), 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#5eead4';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#0a0e14';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
blitLayer(ctx, cx, cy, r, layer);
|
||||
}
|
||||
|
||||
function drawRim(ctx, globeImg, cx, cy, r, rim, layoutScale) {
|
||||
@@ -430,16 +277,19 @@ export async function mountHubGlobe(stage, opts) {
|
||||
const urls = {
|
||||
globe: hubAsset('assets/hub-logo-fragment-globe.svg', build),
|
||||
continents: hubAsset('assets/hub-logo-continents-mercator-globe.svg', build),
|
||||
gridOverlay: hubAsset('assets/hub-logo-fragment-globe-grid-overlay.svg', build),
|
||||
mercatorFlat: hubAsset('assets/hub-logo-continents-mercator-flat.svg', build),
|
||||
};
|
||||
|
||||
let globeImg;
|
||||
let continentsImg;
|
||||
let gridOverlayImg;
|
||||
let mercatorFlatImg;
|
||||
try {
|
||||
[globeImg, continentsImg, mercatorFlatImg] = await Promise.all([
|
||||
[globeImg, continentsImg, gridOverlayImg, mercatorFlatImg] = await Promise.all([
|
||||
loadImage(urls.globe),
|
||||
loadImage(urls.continents),
|
||||
loadImage(urls.gridOverlay),
|
||||
loadImage(urls.mercatorFlat),
|
||||
]);
|
||||
} catch (e) {
|
||||
@@ -448,26 +298,12 @@ export async function mountHubGlobe(stage, opts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const globe = {
|
||||
w: GLOBE_VIEW.imgW,
|
||||
h: GLOBE_VIEW.imgH,
|
||||
data: rasterizeImage(globeImg, GLOBE_VIEW.imgW, GLOBE_VIEW.imgH).data,
|
||||
};
|
||||
const continents = {
|
||||
w: GLOBE_VIEW.imgW,
|
||||
h: GLOBE_VIEW.imgH,
|
||||
data: rasterizeImage(continentsImg, GLOBE_VIEW.imgW, GLOBE_VIEW.imgH).data,
|
||||
};
|
||||
const mercFlat = {
|
||||
w: mercatorFlatImg.naturalWidth || 800,
|
||||
h: mercatorFlatImg.naturalHeight || 519,
|
||||
data: rasterizeImage(
|
||||
mercatorFlatImg,
|
||||
mercatorFlatImg.naturalWidth || 800,
|
||||
mercatorFlatImg.naturalHeight || 519
|
||||
).data,
|
||||
};
|
||||
const landEq = bakeLandEquator(continents, mercFlat);
|
||||
const globe = rasterizeImage(globeImg, GLOBE_VIEW.imgW, GLOBE_VIEW.imgH, RASTER_SCALE);
|
||||
const continents = rasterizeImage(continentsImg, GLOBE_VIEW.imgW, GLOBE_VIEW.imgH, RASTER_SCALE);
|
||||
const gridOverlay = rasterizeImage(gridOverlayImg, GLOBE_VIEW.imgW, GLOBE_VIEW.imgH, RASTER_SCALE);
|
||||
const mercW = (mercatorFlatImg.naturalWidth || 800) * 2;
|
||||
const mercH = (mercatorFlatImg.naturalHeight || 519) * 2;
|
||||
const mercFlat = rasterizeImage(mercatorFlatImg, mercW, mercH, 1);
|
||||
|
||||
let rotation = typeof opts.initialRotationY === 'number' ? opts.initialRotationY : 0;
|
||||
let angularVelocity = reducedMotion ? 0 : AUTO_SPIN_RAD_S;
|
||||
@@ -503,12 +339,11 @@ 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 };
|
||||
|
||||
ctx.clearRect(0, 0, px, px);
|
||||
drawOceanLayer(ctx, cx, cy, r, rotation, globe);
|
||||
drawGridOverlay(ctx, drawRect, rotation);
|
||||
drawContinentsLayer(ctx, cx, cy, r, rotation, landEq);
|
||||
drawGridLayer(ctx, cx, cy, r, rotation, gridOverlay);
|
||||
drawLandLayer(ctx, cx, cy, r, rotation, continents, mercFlat);
|
||||
drawRim(ctx, globeImg, cx, cy, r, rim, rect.scale * dpr);
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
|
||||
@@ -180,7 +180,7 @@ unset($__platformDir);
|
||||
var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover'));
|
||||
var logoEnabled = !!(stage && supportsSvg && supportsLayout);
|
||||
|
||||
var logoBuild = '20260606globe4';
|
||||
var logoBuild = '20260606globe6';
|
||||
|
||||
function hubAsset(rel) {
|
||||
var link = document.querySelector('link[href*="hub.css"]');
|
||||
|
||||
Reference in New Issue
Block a user