refactor(RegionMap.tsx): rename meanInboundAngle to meanNeighborAngle and update logic to compute mean angle to neighbors

This commit is contained in:
2025-08-09 21:33:13 +02:00
parent e2f804bac7
commit c10f4b43cb

View File

@@ -96,7 +96,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
type OffIndicator = { from: string; toRegion: string; count: number; color: string; angle: number; sampleTo?: string }; type OffIndicator = { from: string; toRegion: string; count: number; color: string; angle: number; sampleTo?: string };
const [offRegionIndicators, setOffRegionIndicators] = useState<OffIndicator[]>([]); const [offRegionIndicators, setOffRegionIndicators] = useState<OffIndicator[]>([]);
const [meanInboundAngle, setMeanInboundAngle] = useState<Record<string, number>>({}); const [meanNeighborAngle, setMeanNeighborAngle] = useState<Record<string, number>>({});
const [charLocs, setCharLocs] = useState<Array<{ character_id: number; character_name: string; solar_system_name: string }>>([]); const [charLocs, setCharLocs] = useState<Array<{ character_id: number; character_name: string; solar_system_name: string }>>([]);
useEffect(() => { useEffect(() => {
@@ -133,7 +133,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
setPositions(positions); setPositions(positions);
const connections = computeNodeConnections(systems); const connections = computeNodeConnections(systems);
setConnections(connections); setConnections(connections);
// Compute per-system mean outbound BEARING (0=north, clockwise positive) to in-region neighbors // Compute per-system mean outbound angle in screen coords (atan2(dy,dx)) to in-region neighbors
const angleMap: Record<string, number> = {}; const angleMap: Record<string, number> = {};
systems.forEach((sys, name) => { systems.forEach((sys, name) => {
const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean); const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean);
@@ -142,17 +142,17 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
const neighbor = systems.get(n); const neighbor = systems.get(n);
if (!neighbor) continue; if (!neighbor) continue;
const dx = neighbor.x - sys.x; const dx = neighbor.x - sys.x;
const dy = neighbor.y - sys.y; // screen coords (y down) const dy = neighbor.y - sys.y; // y-down screen
const bearing = Math.atan2(dx, -dy); // bearing relative to north const a = Math.atan2(dy, dx);
sumSin += Math.sin(bearing); sumSin += Math.sin(a);
sumCos += Math.cos(bearing); sumCos += Math.cos(a);
count++; count++;
} }
if (count > 0) { if (count > 0) {
angleMap[name] = Math.atan2(sumSin, sumCos); // average bearing angleMap[name] = Math.atan2(sumSin, sumCos); // average angle
} }
}); });
setMeanInboundAngle(angleMap); setMeanNeighborAngle(angleMap);
}, [systems]); }, [systems]);
// Poll character locations every 7s and store those in this region // Poll character locations every 7s and store those in this region
@@ -208,58 +208,49 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
} }
await ensureUniversePositions(); await ensureUniversePositions();
// Build indicators: group by from+toRegion // Build indicators: group by from+toRegion; angle from local geometry only (meanNeighborAngle + PI)
const grouped: Map<string, OffIndicator> = new Map(); type Agg = { from: string; toRegion: string; count: number; sumRemoteSec: number; sampleTo?: string };
const grouped: Map<string, Agg> = new Map();
for (const [fromName, sys] of systems.entries()) { for (const [fromName, sys] of systems.entries()) {
const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean); const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean);
for (const n of neighbors) { for (const n of neighbors) {
if (systems.has(n)) continue; if (systems.has(n)) continue;
const toRegion = nameToRegion[n]; const toRegion = nameToRegion[n];
if (!toRegion || toRegion === regionName) continue; if (!toRegion || toRegion === regionName) continue;
// compute color
const remote = regionSystemsCache.get(toRegion)?.get(n); const remote = regionSystemsCache.get(toRegion)?.get(n);
const avgSec = ((sys.security || 0) + (remote?.security || 0)) / 2; const gkey = `${fromName}__${toRegion}`;
const color = getSecurityColor(avgSec); const agg = grouped.get(gkey) || { from: fromName, toRegion, count: 0, sumRemoteSec: 0, sampleTo: n };
agg.count += 1;
// compute angle via universe region centroids if (remote) agg.sumRemoteSec += (remote.security || 0);
let angle: number | undefined = undefined; grouped.set(gkey, agg);
const inbound = meanInboundAngle[fromName];
if (inbound !== undefined) {
angle = inbound; // mean outbound bearing already computed
} else {
const curPos = universeRegionPosCache.get(regionName);
const toPos = universeRegionPosCache.get(toRegion);
if (curPos && toPos) {
const dxr = toPos.x - curPos.x;
const dyr = toPos.y - curPos.y;
angle = Math.atan2(dxr, -dyr); // bearing to remote region
} }
} }
const out: OffIndicator[] = [];
for (const [, agg] of grouped) {
if (agg.count === 0) continue;
// Angle: point away from existing connections = meanNeighborAngle + PI
let angle = meanNeighborAngle[agg.from];
if (angle === undefined) { if (angle === undefined) {
// final fallback deterministic angle // fallback: away from region centroid
let h = 0; const key = `${fromName}->${toRegion}`; // compute centroid of current region nodes
for (let i = 0; i < key.length; i++) h = (h * 31 + key.charCodeAt(i)) >>> 0; let cx = 0, cy = 0, c = 0;
angle = (h % 360) * (Math.PI / 180); systems.forEach(s => { cx += s.x; cy += s.y; c++; });
if (c > 0) { cx /= c; cy /= c; }
const sys = systems.get(agg.from)!;
angle = Math.atan2(sys.y - cy, sys.x - cx);
} }
// Flip 180° so indicator points away from existing connections
angle = angle + Math.PI; angle = angle + Math.PI;
const gkey = `${fromName}__${toRegion}`; // Color from avg of local system sec and avg remote sec; local from this system
const prev = grouped.get(gkey); const localSec = (systems.get(agg.from)?.security || 0);
if (prev) { const remoteAvg = agg.count > 0 ? (agg.sumRemoteSec / agg.count) : 0;
prev.count += 1; const color = getSecurityColor((localSec + remoteAvg) / 2);
if (!prev.sampleTo) prev.sampleTo = n; out.push({ from: agg.from, toRegion: agg.toRegion, count: agg.count, color, angle, sampleTo: agg.sampleTo });
} else {
grouped.set(gkey, { from: fromName, toRegion, count: 1, color, angle, sampleTo: n });
} }
} setOffRegionIndicators(out);
}
setOffRegionIndicators(Array.from(grouped.values()));
}; };
computeOffRegion(); computeOffRegion();
}, [systems, regionName]); }, [systems, regionName, meanNeighborAngle]);
useEffect(() => { useEffect(() => {
if (isWormholeRegion) { if (isWormholeRegion) {
@@ -686,8 +677,8 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
if (!pos) return null; if (!pos) return null;
const len = 26; const len = 26;
const r0 = 10; // start just outside node const r0 = 10; // start just outside node
const dx = Math.sin(ind.angle); const dx = Math.cos(ind.angle);
const dy = -Math.cos(ind.angle); const dy = Math.sin(ind.angle);
const x1 = pos.x + dx * r0; const x1 = pos.x + dx * r0;
const y1 = pos.y + dy * r0; const y1 = pos.y + dy * r0;
const x2 = x1 + dx * len; const x2 = x1 + dx * len;