orbit/js/research/ResearchIcons.js

205 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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', 'map', 'gate'];
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 if (n === 'map') {
// The SYSTEM category's chart glyph — a folded map sheet with a plotted
// route between two NAV points (the per-system '{System} Map' tech).
const w = 62,
h = 50,
x = cx - w / 2,
y = cy - h / 2;
ctx.beginPath();
ctx.roundRect(x, y, w, h, 7);
ctx.stroke();
// fold line
ctx.beginPath();
ctx.moveTo(cx, y);
ctx.lineTo(cx, y + h);
ctx.stroke();
// route: two plotted points + the line between them
ctx.beginPath();
ctx.moveTo(x + w * 0.22, y + h * 0.68);
ctx.lineTo(x + w * 0.78, y + h * 0.32);
ctx.stroke();
diamond(x + w * 0.22, y + h * 0.68, 5);
diamond(x + w * 0.78, y + h * 0.32, 5);
} else if (n === 'gate') {
// The SYSTEM category's jumpgate glyph — the ring + its inner aperture
// (+ the four aperture seams), like the world's gate entity minus field.
ctx.globalAlpha = 0.95;
ctx.beginPath();
ctx.arc(cx, cy, 44, 0, Math.PI * 2);
ctx.stroke();
ctx.globalAlpha = 1;
ctx.lineWidth = 3.5;
ctx.beginPath();
ctx.arc(cx, cy, 19, 0, Math.PI * 2);
ctx.stroke();
ctx.lineWidth = 5;
ctx.globalAlpha = 0.6;
for (const a of [0, Math.PI / 2, Math.PI, (3 * Math.PI) / 2]) {
ctx.beginPath();
ctx.moveTo(cx + Math.cos(a) * 28, cy + Math.sin(a) * 28);
ctx.lineTo(cx + Math.cos(a) * 37, cy + Math.sin(a) * 37);
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;
}