Feat: Dynamic tile-based globe texture (like Radio Garden)
Replace static earth texture with tile-based rendering from CDN. Tiles load progressively (zoom 1→2→3 base), then dynamically load zoom 4-6 as user zooms in. Mercator→equirectangular reprojection via strip-based canvas compositing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1e4ccfb1f1
commit
ed7e07a9ba
2 changed files with 211 additions and 3 deletions
|
|
@ -1,5 +1,7 @@
|
|||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import Globe from 'globe.gl';
|
||||
import { CanvasTexture, LinearFilter } from 'three';
|
||||
import { TileTextureManager } from './TileTextureManager';
|
||||
|
||||
// ── Types ──
|
||||
interface RadioPlace {
|
||||
|
|
@ -59,6 +61,8 @@ export default function RadioTab({ data }: { data: any }) {
|
|||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const globeRef = useRef<any>(null);
|
||||
const rotationResumeRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||
const tileManagerRef = useRef<TileTextureManager | null>(null);
|
||||
const tileTextureRef = useRef<CanvasTexture | null>(null);
|
||||
|
||||
const [theme, setTheme] = useState(() => localStorage.getItem('radio-theme') || 'default');
|
||||
const [places, setPlaces] = useState<RadioPlace[]>([]);
|
||||
|
|
@ -179,8 +183,13 @@ export default function RadioTab({ data }: { data: any }) {
|
|||
const initRgb = initStyle.getPropertyValue('--accent-rgb').trim() || '230, 126, 34';
|
||||
const initAccent = initStyle.getPropertyValue('--accent').trim() || '#e67e22';
|
||||
|
||||
// ── Tile-based texture (like Radio Garden) ──
|
||||
const tileMgr = new TileTextureManager(4096, 2048, () => {
|
||||
if (tileTextureRef.current) tileTextureRef.current.needsUpdate = true;
|
||||
});
|
||||
tileManagerRef.current = tileMgr;
|
||||
|
||||
const globe = new Globe(containerRef.current)
|
||||
.globeImageUrl('/earth-night.jpg')
|
||||
.backgroundColor('rgba(0,0,0,0)')
|
||||
.atmosphereColor(`rgba(${initRgb}, 0.25)`)
|
||||
.atmosphereAltitude(0.12)
|
||||
|
|
@ -200,6 +209,18 @@ export default function RadioTab({ data }: { data: any }) {
|
|||
.width(containerRef.current.clientWidth)
|
||||
.height(containerRef.current.clientHeight);
|
||||
|
||||
// Apply tile canvas as globe texture
|
||||
const texture = new CanvasTexture(tileMgr.canvas);
|
||||
texture.minFilter = LinearFilter;
|
||||
texture.generateMipmaps = false;
|
||||
tileTextureRef.current = texture;
|
||||
const mat = globe.globeMaterial() as any;
|
||||
mat.map = texture;
|
||||
mat.needsUpdate = true;
|
||||
|
||||
// Start loading tiles (progressive: zoom 1 → 2 → 3)
|
||||
tileMgr.init();
|
||||
|
||||
// Sharp rendering on HiDPI/Retina displays
|
||||
globe.renderer().setPixelRatio(window.devicePixelRatio);
|
||||
|
||||
|
|
@ -213,11 +234,12 @@ export default function RadioTab({ data }: { data: any }) {
|
|||
controls.autoRotateSpeed = 0.3;
|
||||
}
|
||||
|
||||
// Scale point radius only when zoom (altitude) changes, not during rotation
|
||||
// Scale points + load detail tiles on zoom change
|
||||
let lastAlt = 2.0;
|
||||
const BASE_ALT = 2.0;
|
||||
const onControlsChange = () => {
|
||||
const alt = globe.pointOfView().altitude;
|
||||
const pov = globe.pointOfView();
|
||||
const alt = pov.altitude;
|
||||
// Only recalc when altitude changed significantly (>5%)
|
||||
if (Math.abs(alt - lastAlt) / lastAlt < 0.05) return;
|
||||
lastAlt = alt;
|
||||
|
|
@ -226,6 +248,8 @@ export default function RadioTab({ data }: { data: any }) {
|
|||
const base = Math.max(0.12, Math.min(0.45, 0.06 + (d.size ?? 1) * 0.005));
|
||||
return base * scale;
|
||||
});
|
||||
// Load higher-res tiles for visible area
|
||||
tileManagerRef.current?.loadViewport(pov.lat, pov.lng, alt);
|
||||
};
|
||||
controls.addEventListener('change', onControlsChange);
|
||||
|
||||
|
|
@ -253,6 +277,7 @@ export default function RadioTab({ data }: { data: any }) {
|
|||
el.removeEventListener('touchstart', onInteract);
|
||||
el.removeEventListener('wheel', onInteract);
|
||||
window.removeEventListener('resize', onResize);
|
||||
tileManagerRef.current?.destroy();
|
||||
};
|
||||
}, [places, pauseRotation]);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue