Fix radio marker sprites and size scaling
This commit is contained in:
parent
cf113f65ca
commit
3d59eda3da
1 changed files with 44 additions and 21 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import Globe from 'globe.gl';
|
import Globe from 'globe.gl';
|
||||||
|
import { CanvasTexture, Sprite, SpriteMaterial } from 'three';
|
||||||
|
|
||||||
// ── Types ──
|
// ── Types ──
|
||||||
interface RadioPlace {
|
interface RadioPlace {
|
||||||
|
|
@ -62,13 +63,13 @@ const THEMES = [
|
||||||
{ id: 'cherry', color: '#e74c6f', label: 'Cherry' },
|
{ id: 'cherry', color: '#e74c6f', label: 'Cherry' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function createParticleTexture(color: string): string {
|
function createMarkerTexture(color: string): CanvasTexture | null {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = 64;
|
canvas.width = 64;
|
||||||
canvas.height = 64;
|
canvas.height = 64;
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) return '';
|
if (!ctx) return null;
|
||||||
|
|
||||||
const gradient = ctx.createRadialGradient(32, 32, 3, 32, 32, 32);
|
const gradient = ctx.createRadialGradient(32, 32, 3, 32, 32, 32);
|
||||||
gradient.addColorStop(0, 'rgba(255,255,255,1)');
|
gradient.addColorStop(0, 'rgba(255,255,255,1)');
|
||||||
|
|
@ -79,7 +80,11 @@ function createParticleTexture(color: string): string {
|
||||||
ctx.fillStyle = gradient;
|
ctx.fillStyle = gradient;
|
||||||
ctx.fillRect(0, 0, 64, 64);
|
ctx.fillRect(0, 0, 64, 64);
|
||||||
|
|
||||||
return canvas.toDataURL('image/png');
|
return new CanvasTexture(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
function markerScale(size: number): number {
|
||||||
|
return Math.max(0.55, Math.min(1.75, 0.42 + size * 0.035));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Component ──
|
// ── Component ──
|
||||||
|
|
@ -174,11 +179,21 @@ export default function RadioTab({ data }: { data: any }) {
|
||||||
if (globeRef.current && containerRef.current) {
|
if (globeRef.current && containerRef.current) {
|
||||||
const style = getComputedStyle(containerRef.current.parentElement!);
|
const style = getComputedStyle(containerRef.current.parentElement!);
|
||||||
const accentRgb = style.getPropertyValue('--accent-rgb').trim();
|
const accentRgb = style.getPropertyValue('--accent-rgb').trim();
|
||||||
const particleTexture = createParticleTexture(`rgb(${accentRgb})`);
|
const spriteTexture = createMarkerTexture(`rgb(${accentRgb})`);
|
||||||
globeRef.current
|
globeRef.current.atmosphereColor(`rgb(${accentRgb})`);
|
||||||
.particlesColor(`rgb(${accentRgb})`)
|
globeRef.current.customLayerData(places);
|
||||||
.particlesTexture(particleTexture)
|
globeRef.current.customThreeObject((d: any) => {
|
||||||
.atmosphereColor(`rgb(${accentRgb})`);
|
const material = new SpriteMaterial({
|
||||||
|
map: spriteTexture ?? undefined,
|
||||||
|
color: `rgb(${accentRgb})`,
|
||||||
|
transparent: true,
|
||||||
|
depthWrite: false,
|
||||||
|
});
|
||||||
|
const sprite = new Sprite(material);
|
||||||
|
const scale = markerScale(d.size ?? 1);
|
||||||
|
sprite.scale.set(scale, scale, 1);
|
||||||
|
return sprite;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
|
@ -216,33 +231,41 @@ export default function RadioTab({ data }: { data: any }) {
|
||||||
if (!containerRef.current || places.length === 0) return;
|
if (!containerRef.current || places.length === 0) return;
|
||||||
|
|
||||||
if (globeRef.current) {
|
if (globeRef.current) {
|
||||||
globeRef.current.particlesData([places]);
|
globeRef.current.customLayerData(places);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read accent color from theme
|
// Read accent color from theme
|
||||||
const initStyle = getComputedStyle(containerRef.current.parentElement!);
|
const initStyle = getComputedStyle(containerRef.current.parentElement!);
|
||||||
const initRgb = initStyle.getPropertyValue('--accent-rgb').trim() || '230, 126, 34';
|
const initRgb = initStyle.getPropertyValue('--accent-rgb').trim() || '230, 126, 34';
|
||||||
const particleTexture = createParticleTexture(`rgb(${initRgb})`);
|
const markerTexture = createMarkerTexture(`rgb(${initRgb})`);
|
||||||
const globe = new Globe(containerRef.current)
|
const globe = new Globe(containerRef.current)
|
||||||
.backgroundColor('rgba(0,0,0,0)')
|
.backgroundColor('rgba(0,0,0,0)')
|
||||||
.atmosphereColor(`rgb(${initRgb})`)
|
.atmosphereColor(`rgb(${initRgb})`)
|
||||||
.atmosphereAltitude(0.12)
|
.atmosphereAltitude(0.12)
|
||||||
.globeImageUrl('/nasa-blue-marble.jpg')
|
.globeImageUrl('/nasa-blue-marble.jpg')
|
||||||
.particlesData([places] as any)
|
.customLayerData(places)
|
||||||
.particlesList((d: any) => d)
|
.customLayerLabel((d: any) =>
|
||||||
.particleLat((d: any) => d.geo[1])
|
|
||||||
.particleLng((d: any) => d.geo[0])
|
|
||||||
.particleAltitude(0.0008)
|
|
||||||
.particlesSize(6)
|
|
||||||
.particlesSizeAttenuation(false)
|
|
||||||
.particlesColor(`rgb(${initRgb})`)
|
|
||||||
.particlesTexture(particleTexture)
|
|
||||||
.particleLabel((d: any) =>
|
|
||||||
`<div style="font-family:system-ui;font-size:13px;color:#fff;background:rgba(30,31,34,0.92);padding:6px 10px;border-radius:6px;border:1px solid rgba(${initRgb},0.3);pointer-events:none">` +
|
`<div style="font-family:system-ui;font-size:13px;color:#fff;background:rgba(30,31,34,0.92);padding:6px 10px;border-radius:6px;border:1px solid rgba(${initRgb},0.3);pointer-events:none">` +
|
||||||
`<b>${d.title}</b><br/><span style="color:#949ba4;font-size:11px">${d.country}</span></div>`
|
`<b>${d.title}</b><br/><span style="color:#949ba4;font-size:11px">${d.country}</span></div>`
|
||||||
)
|
)
|
||||||
.onParticleClick((d: any) => handlePointClickRef.current?.(d))
|
.customThreeObject((d: any) => {
|
||||||
|
const material = new SpriteMaterial({
|
||||||
|
map: markerTexture ?? undefined,
|
||||||
|
color: `rgb(${initRgb})`,
|
||||||
|
transparent: true,
|
||||||
|
depthWrite: false,
|
||||||
|
});
|
||||||
|
const sprite = new Sprite(material);
|
||||||
|
const scale = markerScale(d.size ?? 1);
|
||||||
|
sprite.scale.set(scale, scale, 1);
|
||||||
|
return sprite;
|
||||||
|
})
|
||||||
|
.customThreeObjectUpdate((obj: any, d: any) => {
|
||||||
|
const scale = markerScale(d.size ?? 1);
|
||||||
|
obj.scale.set(scale, scale, 1);
|
||||||
|
})
|
||||||
|
.onCustomLayerClick((d: any) => handlePointClickRef.current?.(d))
|
||||||
.width(containerRef.current.clientWidth)
|
.width(containerRef.current.clientWidth)
|
||||||
.height(containerRef.current.clientHeight);
|
.height(containerRef.current.clientHeight);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue