/** * ResearchIcons — procedural tech glyphs for the tree nodes and the detail * readout (128×128 canvas textures, neon stroke + soft glow, transparent * background). Names come from each node's `icon` key; unknown names fall * back to `diamond`. Tintable at use (Phaser tint multiplies). */ import { toColor } from '../utils/Color.js'; export const ICON_NAMES = ['tether', 'tether2', 'anchor', 'signal', 'diamond']; export function iconKey(name, color = 0x00e5ff) { const n = ICON_NAMES.includes(name) ? name : 'diamond'; const c = toColor(color); return `__research_icon_${n}_${c.toString(16).padStart(6, '0')}`; } export function ensureIcon(scene, name, color = 0x00e5ff) { const key = iconKey(name, color); if (scene.textures.exists(key)) return key; const S = 128; const canvas = document.createElement('canvas'); canvas.width = S; canvas.height = S; const ctx = canvas.getContext('2d'); const hex = `#${toColor(color).toString(16).padStart(6, '0')}`; const cx = S / 2; const cy = S / 2; ctx.clearRect(0, 0, S, S); ctx.strokeStyle = hex; ctx.fillStyle = hex; ctx.shadowColor = hex; ctx.shadowBlur = 9; ctx.lineWidth = 5; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; const diamond = (x, y, r) => { ctx.beginPath(); ctx.moveTo(x, y - r); ctx.lineTo(x + r, y); ctx.lineTo(x, y + r); ctx.lineTo(x - r, y); ctx.closePath(); ctx.fill(); }; const n = ICON_NAMES.includes(name) ? name : 'diamond'; if (n === 'tether') { // Concentric tether rings + core + tick marks + a satellite on the mid ring. const rings = [ [52, 0.5], [38, 0.75], [24, 1.0], ]; for (const [r, a] of rings) { ctx.globalAlpha = a; ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.stroke(); } ctx.globalAlpha = 1; ctx.beginPath(); ctx.arc(cx, cy, 7, 0, Math.PI * 2); ctx.fill(); for (const a of [0, Math.PI / 2, Math.PI, (3 * Math.PI) / 2]) { ctx.beginPath(); ctx.moveTo(cx + Math.cos(a) * 55, cy + Math.sin(a) * 55); ctx.lineTo(cx + Math.cos(a) * 61, cy + Math.sin(a) * 61); ctx.stroke(); } diamond(cx + 38, cy, 8); } else if (n === 'tether2') { // Level-2 tether: two bright concentric level rings + core + tick // marks + TWO satellites riding the outer ring (L1 shows one). const rings = [ [52, 1.0], [34, 0.85], ]; for (const [r, a] of rings) { ctx.globalAlpha = a; ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.stroke(); } ctx.globalAlpha = 1; ctx.beginPath(); ctx.arc(cx, cy, 8, 0, Math.PI * 2); ctx.fill(); for (const a of [0, Math.PI / 2, Math.PI, (3 * Math.PI) / 2]) { ctx.beginPath(); ctx.moveTo(cx + Math.cos(a) * 55, cy + Math.sin(a) * 55); ctx.lineTo(cx + Math.cos(a) * 61, cy + Math.sin(a) * 61); ctx.stroke(); } diamond(cx + 52 * Math.cos(-Math.PI / 3), cy + 52 * Math.sin(-Math.PI / 3), 8); diamond(cx + 52 * Math.cos(Math.PI / 3), cy + 52 * Math.sin(Math.PI / 3), 8); } else if (n === 'anchor') { // Anchor ring + stock + crossbar + flukes (the relay bolted to a body). ctx.globalAlpha = 0.9; ctx.beginPath(); ctx.arc(cx, cy, 46, 0, Math.PI * 2); ctx.stroke(); ctx.globalAlpha = 1; ctx.beginPath(); ctx.moveTo(cx, cy - 24); ctx.lineTo(cx, cy + 20); ctx.stroke(); ctx.beginPath(); ctx.arc(cx, cy - 24, 7, 0, Math.PI * 2); ctx.stroke(); ctx.beginPath(); ctx.moveTo(cx - 15, cy - 8); ctx.lineTo(cx + 15, cy - 8); ctx.stroke(); ctx.beginPath(); ctx.arc(cx, cy + 4, 20, Math.PI * 0.12, Math.PI * 0.88); ctx.stroke(); diamond(cx - 19, cy + 15, 6); diamond(cx + 19, cy + 15, 6); } else if (n === 'signal') { // Long-range array: source dot + rising arcs. ctx.beginPath(); ctx.arc(cx, cy + 22, 7, 0, Math.PI * 2); ctx.fill(); const arcs = [ [19, 0.95], [33, 0.7], [47, 0.45], ]; for (const [r, a] of arcs) { ctx.globalAlpha = a; ctx.beginPath(); ctx.arc(cx, cy + 22, r, -Math.PI * 0.82, -Math.PI * 0.18); ctx.stroke(); } ctx.globalAlpha = 1; } else { // diamond — the default glyph ctx.beginPath(); ctx.moveTo(cx, cy - 42); ctx.lineTo(cx + 42, cy); ctx.lineTo(cx, cy + 42); ctx.lineTo(cx - 42, cy); ctx.closePath(); ctx.stroke(); ctx.globalAlpha = 0.55; ctx.beginPath(); ctx.moveTo(cx, cy - 22); ctx.lineTo(cx + 22, cy); ctx.lineTo(cx, cy + 22); ctx.lineTo(cx - 22, cy); ctx.closePath(); ctx.stroke(); ctx.globalAlpha = 1; } scene.textures.addCanvas(key, canvas); return key; }