mirror of
git://f0xx.org/android_cast
synced 2026-07-29 05:58:14 +03:00
sync BE / landing 3d
This commit is contained in:
@@ -1,15 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* Hub landing globe — orthographic Canvas2D sphere.
|
* Hub landing globe — orthographic Canvas2D (globe3d=1).
|
||||||
* Reference stack (globe3d=0): hub-logo-fragment-globe + continents-mercator-globe.
|
* Layer order matches globe3d=0 SVG stack: ocean → grid → continents.
|
||||||
* Body frame uses 180° flip around equator (X) so N/S matches the static hub.
|
* Land baked to equirectangular (ortho ref + mercator-flat wrap); grid under continents.
|
||||||
*/
|
*/
|
||||||
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 };
|
||||||
|
|
||||||
const AUTO_SPIN_RAD_S = 0.035;
|
const AUTO_SPIN_RAD_S = 0.035;
|
||||||
const DRAG_ROTATION_PER_PX = 0.004;
|
const DRAG_ROTATION_PER_PX = 0.004;
|
||||||
const OCEAN_FALLBACK = { r: 22, g: 38, b: 60 };
|
const OCEAN_FALLBACK = { r: 22, g: 38, b: 60 };
|
||||||
const VIEW_Z_EPS = 0.001;
|
const EQ_W = 1024;
|
||||||
const BODY_FRONT_Z = 0.001;
|
const EQ_H = 512;
|
||||||
|
const VIEW_Z_MIN = 0.06;
|
||||||
|
|
||||||
function hubAsset(rel, build) {
|
function hubAsset(rel, build) {
|
||||||
const link = document.querySelector('link[href*="hub.css"]');
|
const link = document.querySelector('link[href*="hub.css"]');
|
||||||
@@ -67,7 +68,6 @@ function sampleImageData(imgData, w, h, x, y) {
|
|||||||
return [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];
|
return [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 180° about equator (X): swap S↔N to match globe3d=0 hub orientation. */
|
|
||||||
function flipSouthNorth(p) {
|
function flipSouthNorth(p) {
|
||||||
return { x: p.x, y: -p.y, z: -p.z };
|
return { x: p.x, y: -p.y, z: -p.z };
|
||||||
}
|
}
|
||||||
@@ -106,6 +106,13 @@ function bodyToHubPixel(xb, yb) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lonLatFromBody(body) {
|
||||||
|
return {
|
||||||
|
lon: Math.atan2(body.x, body.z),
|
||||||
|
lat: Math.asin(Math.max(-1, Math.min(1, body.y))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function sampleMercatorFlat(merc, lon, lat) {
|
function sampleMercatorFlat(merc, lon, lat) {
|
||||||
const u = (lon + Math.PI) / (2 * Math.PI);
|
const u = (lon + Math.PI) / (2 * Math.PI);
|
||||||
const v = 0.5 - lat / Math.PI;
|
const v = 0.5 - lat / Math.PI;
|
||||||
@@ -114,47 +121,75 @@ function sampleMercatorFlat(merc, lon, lat) {
|
|||||||
return sampleImageData(merc.data, merc.w, merc.h, x, y);
|
return sampleImageData(merc.data, merc.w, merc.h, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLandPixel(r, g, b, a) {
|
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) {
|
||||||
return a > 12 && r > 100 && g < 140 && b < 140;
|
return a > 12 && r > 100 && g < 140 && b < 140;
|
||||||
}
|
}
|
||||||
|
|
||||||
function bodyLonLat(xb, yb, zb) {
|
/** Equirect land: ortho globe3d=0 colors on front body + mercator-flat elsewhere. */
|
||||||
return {
|
function bakeLandEquator(continents, mercFlat) {
|
||||||
lon: Math.atan2(xb, zb),
|
const out = new Uint8ClampedArray(EQ_W * EQ_H * 4);
|
||||||
lat: Math.asin(Math.max(-1, Math.min(1, yb))),
|
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) {
|
||||||
* globe3d=0 orthographic disk (primary) + mercator-flat lon/lat fill during rotation.
|
const hp = bodyToHubPixel(body.x, body.y);
|
||||||
*/
|
const ortho = sampleImageData(continents.data, continents.w, continents.h, hp.x, hp.y);
|
||||||
function sampleBodySurface(continents, globe, mercFlat, xb, yb, zb) {
|
|
||||||
const p = bodyToHubPixel(xb, yb);
|
|
||||||
const ocean = sampleImageData(globe.data, globe.w, globe.h, p.x, p.y);
|
|
||||||
const oceanRgb =
|
|
||||||
ocean[3] < 8
|
|
||||||
? [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b]
|
|
||||||
: [ocean[0], ocean[1], ocean[2]];
|
|
||||||
|
|
||||||
if (zb <= BODY_FRONT_Z) {
|
|
||||||
return oceanRgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ortho = sampleImageData(continents.data, continents.w, continents.h, p.x, p.y);
|
|
||||||
if (ortho[3] > 20) {
|
if (ortho[3] > 20) {
|
||||||
return [ortho[0], ortho[1], ortho[2]];
|
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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ll = bodyLonLat(xb, yb, zb);
|
out[p] = rgba[0];
|
||||||
const flat = sampleMercatorFlat(mercFlat, ll.lon, ll.lat);
|
out[p + 1] = rgba[1];
|
||||||
if (isLandPixel(flat[0], flat[1], flat[2], flat[3])) {
|
out[p + 2] = rgba[2];
|
||||||
return [flat[0], flat[1], flat[2]];
|
out[p + 3] = rgba[3];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return oceanRgb;
|
return { data: out, w: EQ_W, h: EQ_H };
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawGlobeDisk(ctx, cx, cy, r, rotY, continents, globe, mercFlat) {
|
function viewDirBody(rotY) {
|
||||||
|
return inverseRotateY(0, 0, 1, rotY);
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
return [o[0], o[1], o[2]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillDiskRegion(ctx, cx, cy, r, rotY, fn) {
|
||||||
const r2 = r * r;
|
const r2 = r * r;
|
||||||
const x0 = Math.max(0, Math.floor(cx - r));
|
const x0 = Math.max(0, Math.floor(cx - r));
|
||||||
const y0 = Math.max(0, Math.floor(cy - r));
|
const y0 = Math.max(0, Math.floor(cy - r));
|
||||||
@@ -162,7 +197,7 @@ function drawGlobeDisk(ctx, cx, cy, r, rotY, continents, globe, mercFlat) {
|
|||||||
const y1 = Math.min(ctx.canvas.height - 1, Math.ceil(cy + r));
|
const y1 = Math.min(ctx.canvas.height - 1, Math.ceil(cy + r));
|
||||||
const w = x1 - x0 + 1;
|
const w = x1 - x0 + 1;
|
||||||
const h = y1 - y0 + 1;
|
const h = y1 - y0 + 1;
|
||||||
if (w <= 0 || h <= 0) return;
|
if (w <= 0 || h <= 0) return null;
|
||||||
|
|
||||||
const out = ctx.createImageData(w, h);
|
const out = ctx.createImageData(w, h);
|
||||||
const px = out.data;
|
const px = out.data;
|
||||||
@@ -172,8 +207,7 @@ function drawGlobeDisk(ctx, cx, cy, r, rotY, continents, globe, mercFlat) {
|
|||||||
for (let dx = x0; dx <= x1; dx++) {
|
for (let dx = x0; dx <= x1; dx++) {
|
||||||
const ox = dx - cx;
|
const ox = dx - cx;
|
||||||
const oy = dy - cy;
|
const oy = dy - cy;
|
||||||
const d2 = ox * ox + oy * oy;
|
if (ox * ox + oy * oy > r2) {
|
||||||
if (d2 > r2) {
|
|
||||||
px[p++] = 0;
|
px[p++] = 0;
|
||||||
px[p++] = 0;
|
px[p++] = 0;
|
||||||
px[p++] = 0;
|
px[p++] = 0;
|
||||||
@@ -184,38 +218,60 @@ function drawGlobeDisk(ctx, cx, cy, r, rotY, continents, globe, mercFlat) {
|
|||||||
const ny = oy / r;
|
const ny = oy / r;
|
||||||
const nz = Math.sqrt(Math.max(0, 1 - nx * nx - ny * ny));
|
const nz = Math.sqrt(Math.max(0, 1 - nx * nx - ny * ny));
|
||||||
const body = inverseRotateY(nx, ny, nz, rotY);
|
const body = inverseRotateY(nx, ny, nz, rotY);
|
||||||
const rgb = sampleBodySurface(continents, globe, mercFlat, body.x, body.y, body.z);
|
const rgb = fn(body);
|
||||||
|
|
||||||
px[p++] = rgb[0];
|
px[p++] = rgb[0];
|
||||||
px[p++] = rgb[1];
|
px[p++] = rgb[1];
|
||||||
px[p++] = rgb[2];
|
px[p++] = rgb[2];
|
||||||
px[p++] = 255;
|
px[p++] = rgb[3] !== undefined ? rgb[3] : 255;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { out, x0, y0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
function blitLayer(ctx, cx, cy, r, layer) {
|
||||||
|
if (!layer) return;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
ctx.putImageData(out, x0, y0);
|
ctx.putImageData(layer.out, layer.x0, layer.y0);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Draw each contiguous visible arc of a 3D curve (latitude circles stay on the surface). */
|
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]);
|
||||||
|
});
|
||||||
|
blitLayer(ctx, cx, cy, r, layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawContinentsLayer(ctx, cx, cy, r, rotY, landEq) {
|
||||||
|
const layer = fillDiskRegion(ctx, cx, cy, r, rotY, function (body) {
|
||||||
|
if (!facingCamera(body, rotY)) {
|
||||||
|
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])) {
|
||||||
|
return [0, 0, 0, 0];
|
||||||
|
}
|
||||||
|
return [land[0], land[1], land[2], land[3]];
|
||||||
|
});
|
||||||
|
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) {
|
function strokeVisibleArcs3D(ctx, cx, cy, r, rotY, pointAt, styleAt, steps) {
|
||||||
const samples = [];
|
const samples = [];
|
||||||
for (let i = 0; i <= steps; i++) {
|
for (let i = 0; i <= steps; i++) {
|
||||||
const raw = pointAt(i / steps);
|
const raw = pointAt(i / steps);
|
||||||
const p = rotateYRaw(raw.x, raw.y, raw.z, rotY);
|
const p = rotateYRaw(raw.x, raw.y, raw.z, rotY);
|
||||||
if (p.z <= VIEW_Z_EPS) {
|
if (p.z <= VIEW_Z_MIN) {
|
||||||
samples.push(null);
|
samples.push(null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
samples.push({
|
samples.push({ sx: cx + p.x * r, sy: cy - p.y * r, z: p.z });
|
||||||
sx: cx + p.x * r,
|
|
||||||
sy: cy - p.y * r,
|
|
||||||
z: p.z,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let run = [];
|
let run = [];
|
||||||
@@ -224,37 +280,51 @@ function strokeVisibleArcs3D(ctx, cx, cy, r, rotY, pointAt, styleAt, steps) {
|
|||||||
run = [];
|
run = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = 1; i < run.length; i++) {
|
let zSum = 0;
|
||||||
const a = run[i - 1];
|
|
||||||
const b = run[i];
|
|
||||||
const style = styleAt((a.z + b.z) * 0.5);
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(a.sx, a.sy);
|
ctx.moveTo(run[0].sx, run[0].sy);
|
||||||
ctx.lineTo(b.sx, b.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.strokeStyle = style.color;
|
||||||
ctx.lineWidth = style.width;
|
ctx.lineWidth = style.width;
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
ctx.lineJoin = 'round';
|
ctx.lineJoin = 'round';
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
|
||||||
run = [];
|
run = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < samples.length; i++) {
|
for (let i = 0; i < samples.length; i++) {
|
||||||
if (!samples[i]) {
|
if (!samples[i]) flushRun();
|
||||||
flushRun();
|
else run.push(samples[i]);
|
||||||
} else {
|
|
||||||
run.push(samples[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
flushRun();
|
flushRun();
|
||||||
}
|
}
|
||||||
|
|
||||||
function depthStyle(baseWidth, centerAlpha, edgeAlpha, z) {
|
function meridianStyle(isPrime, scale, z) {
|
||||||
const t = Math.max(0, Math.min(1, z / 0.95));
|
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 {
|
return {
|
||||||
width: baseWidth * (0.3 + 0.7 * t),
|
width: bw * (0.25 + 0.75 * t * t),
|
||||||
color: 'rgba(238,245,255,' + (edgeAlpha + (centerAlpha - edgeAlpha) * t).toFixed(3) + ')',
|
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) + ')',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,18 +333,13 @@ function drawGridOverlay(ctx, rect, rotY) {
|
|||||||
const cy = rect.cy;
|
const cy = rect.cy;
|
||||||
const r = rect.r;
|
const r = rect.r;
|
||||||
const scale = rect.scale;
|
const scale = rect.scale;
|
||||||
const steps = 128;
|
const steps = 160;
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
|
|
||||||
const lwBase = Math.max(2.5, scale * 0.008);
|
|
||||||
const lwPrime = Math.max(3, scale * 0.0095);
|
|
||||||
const lwThin = Math.max(1.5, scale * 0.0055);
|
|
||||||
const lwEq = Math.max(2, scale * 0.007);
|
|
||||||
|
|
||||||
strokeVisibleArcs3D(
|
strokeVisibleArcs3D(
|
||||||
ctx,
|
ctx,
|
||||||
cx,
|
cx,
|
||||||
@@ -282,15 +347,10 @@ function drawGridOverlay(ctx, rect, rotY) {
|
|||||||
r,
|
r,
|
||||||
rotY,
|
rotY,
|
||||||
function (t) {
|
function (t) {
|
||||||
const lon = -Math.PI + t * 2 * Math.PI;
|
return sphereLatLon(-Math.PI + t * 2 * Math.PI, 0);
|
||||||
return sphereLatLon(lon, 0);
|
|
||||||
},
|
},
|
||||||
function (z) {
|
function (z) {
|
||||||
const t = Math.max(0, Math.min(1, z / 0.95));
|
return parallelStyle(scale, z, true);
|
||||||
return {
|
|
||||||
width: lwEq * (0.35 + 0.65 * t),
|
|
||||||
color: 'rgba(219,231,246,' + (0.42 + 0.46 * t).toFixed(3) + ')',
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
steps
|
steps
|
||||||
);
|
);
|
||||||
@@ -304,15 +364,10 @@ function drawGridOverlay(ctx, rect, rotY) {
|
|||||||
r,
|
r,
|
||||||
rotY,
|
rotY,
|
||||||
function (t) {
|
function (t) {
|
||||||
const lon = -Math.PI + t * 2 * Math.PI;
|
return sphereLatLon(-Math.PI + t * 2 * Math.PI, lat);
|
||||||
return sphereLatLon(lon, lat);
|
|
||||||
},
|
},
|
||||||
function (z) {
|
function (z) {
|
||||||
const t = Math.max(0, Math.min(1, z / 0.95));
|
return parallelStyle(scale, z, false);
|
||||||
return {
|
|
||||||
width: lwThin * (0.35 + 0.65 * t),
|
|
||||||
color: 'rgba(231,236,243,' + (0.18 + 0.54 * t).toFixed(3) + ')',
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
steps
|
steps
|
||||||
);
|
);
|
||||||
@@ -321,8 +376,6 @@ function drawGridOverlay(ctx, rect, rotY) {
|
|||||||
for (let lonDeg = -150; lonDeg <= 150; lonDeg += 30) {
|
for (let lonDeg = -150; lonDeg <= 150; lonDeg += 30) {
|
||||||
const lon = (lonDeg * Math.PI) / 180;
|
const lon = (lonDeg * Math.PI) / 180;
|
||||||
const isPrime = lonDeg === 0;
|
const isPrime = lonDeg === 0;
|
||||||
const bw = isPrime ? lwPrime : lwBase;
|
|
||||||
const ca = isPrime ? 0.98 : 0.78;
|
|
||||||
strokeVisibleArcs3D(
|
strokeVisibleArcs3D(
|
||||||
ctx,
|
ctx,
|
||||||
cx,
|
cx,
|
||||||
@@ -330,11 +383,10 @@ function drawGridOverlay(ctx, rect, rotY) {
|
|||||||
r,
|
r,
|
||||||
rotY,
|
rotY,
|
||||||
function (t) {
|
function (t) {
|
||||||
const lat = -Math.PI / 2 + t * Math.PI;
|
return sphereLatLon(lon, -Math.PI / 2 + t * Math.PI);
|
||||||
return sphereLatLon(lon, lat);
|
|
||||||
},
|
},
|
||||||
function (z) {
|
function (z) {
|
||||||
return depthStyle(bw, ca, 0.15, z);
|
return meridianStyle(isPrime, scale, z);
|
||||||
},
|
},
|
||||||
96
|
96
|
||||||
);
|
);
|
||||||
@@ -366,10 +418,6 @@ function drawRim(ctx, globeImg, cx, cy, r, rim, layoutScale) {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {HTMLElement} stage
|
|
||||||
* @param {{ build: string, reducedMotion?: boolean, initialRotationY?: number }} opts
|
|
||||||
*/
|
|
||||||
export async function mountHubGlobe(stage, opts) {
|
export async function mountHubGlobe(stage, opts) {
|
||||||
const build = opts.build || 'earth3d';
|
const build = opts.build || 'earth3d';
|
||||||
const reducedMotion = !!opts.reducedMotion;
|
const reducedMotion = !!opts.reducedMotion;
|
||||||
@@ -419,6 +467,7 @@ export async function mountHubGlobe(stage, opts) {
|
|||||||
mercatorFlatImg.naturalHeight || 519
|
mercatorFlatImg.naturalHeight || 519
|
||||||
).data,
|
).data,
|
||||||
};
|
};
|
||||||
|
const landEq = bakeLandEquator(continents, mercFlat);
|
||||||
|
|
||||||
let rotation = typeof opts.initialRotationY === 'number' ? opts.initialRotationY : 0;
|
let rotation = typeof opts.initialRotationY === 'number' ? opts.initialRotationY : 0;
|
||||||
let angularVelocity = reducedMotion ? 0 : AUTO_SPIN_RAD_S;
|
let angularVelocity = reducedMotion ? 0 : AUTO_SPIN_RAD_S;
|
||||||
@@ -457,9 +506,10 @@ export async function mountHubGlobe(stage, opts) {
|
|||||||
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);
|
ctx.clearRect(0, 0, px, px);
|
||||||
drawGlobeDisk(ctx, cx, cy, r, rotation, continents, globe, mercFlat);
|
drawOceanLayer(ctx, cx, cy, r, rotation, globe);
|
||||||
drawRim(ctx, globeImg, cx, cy, r, rim, rect.scale * dpr);
|
|
||||||
drawGridOverlay(ctx, drawRect, rotation);
|
drawGridOverlay(ctx, drawRect, rotation);
|
||||||
|
drawContinentsLayer(ctx, cx, cy, r, rotation, landEq);
|
||||||
|
drawRim(ctx, globeImg, cx, cy, r, rim, rect.scale * dpr);
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ unset($__platformDir);
|
|||||||
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 = '20260606globe3';
|
var logoBuild = '20260606globe4';
|
||||||
|
|
||||||
function hubAsset(rel) {
|
function hubAsset(rel) {
|
||||||
var link = document.querySelector('link[href*="hub.css"]');
|
var link = document.querySelector('link[href*="hub.css"]');
|
||||||
|
|||||||
Reference in New Issue
Block a user