Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
746 lines
30 KiB
Java
746 lines
30 KiB
Java
package workbench;
|
||
|
||
import javax.script.ScriptEngine;
|
||
import javax.script.ScriptEngineManager;
|
||
import javax.script.SimpleBindings;
|
||
import javax.swing.*;
|
||
import java.awt.*;
|
||
import java.awt.event.*;
|
||
import java.awt.geom.*;
|
||
import java.util.ArrayList;
|
||
import java.util.List;
|
||
import java.util.Random;
|
||
|
||
/**
|
||
* Symmetric Embedding Workbench — Java port of kernel.js + kernel.html.
|
||
*
|
||
* Renders the Sym² manifold with heat diffusion and friend agents.
|
||
* No external dependencies. Compile with the patched javac and run with java.
|
||
*
|
||
* Compile (from tests/):
|
||
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
||
* workbench/Manifold.java workbench/Friend.java workbench/Workbench.java
|
||
*
|
||
* Run:
|
||
* java -cp . workbench.Workbench
|
||
*/
|
||
public class Workbench {
|
||
|
||
// ── Palette ───────────────────────────────────────────────────────────────
|
||
private static final Color BG_PANEL = new Color(0x0d, 0x0d, 0x16);
|
||
private static final Color BG_SCENE = Color.WHITE;
|
||
private static final Color FG_TEXT = Color.WHITE;
|
||
private static final Color PCB_GOLD = new Color(0xff, 0xd7, 0x00);
|
||
private static final Color PCB_GREEN = new Color(0x68, 0xff, 0x9a);
|
||
private static final Color BORDER_COL = new Color(0x33, 0x33, 0x33);
|
||
private static final Color SEAM_COL = new Color(0xff, 0x32, 0x32);
|
||
private static final Color WIRE_COL = new Color(0x00, 0x00, 0x00, 128);
|
||
|
||
// Friend render colors (matching JS palette)
|
||
private static final Color[] FRIEND_COLORS = {
|
||
new Color(0x68, 0xff, 0x9a), // G — green
|
||
new Color(0xff, 0x32, 0x32), // R — red
|
||
new Color(0x00, 0xee, 0xff), // C — cyan
|
||
new Color(0xff, 0xd7, 0x00), // Y — gold
|
||
new Color(0x94, 0x00, 0xd3) // V — violet
|
||
};
|
||
|
||
// ── State ─────────────────────────────────────────────────────────────────
|
||
private final Manifold manifold = new Manifold();
|
||
private final List<Friend> friends = new ArrayList<>();
|
||
private final List<float[]> seedPoints = new ArrayList<>(); // {x, y}
|
||
|
||
private float flickerRate = 0.05f;
|
||
private float wobbleBase = 2.0f;
|
||
private int tick = 0;
|
||
|
||
private String oracleExpr = "Math.sin(t * 0.05) * 5";
|
||
private ScriptEngine scriptEngine;
|
||
|
||
// ── Swing components ──────────────────────────────────────────────────────
|
||
private ViewportPanel viewport;
|
||
private DrawOverlay drawOverlay;
|
||
private JTextArea pcbSource;
|
||
private JTextArea pcbOutput;
|
||
private JTextField oracleField;
|
||
private JPanel matrixBody;
|
||
private JButton friendBtn;
|
||
private JButton solveBtn;
|
||
private JButton inscribeBtn;
|
||
|
||
// ── Entry point ───────────────────────────────────────────────────────────
|
||
|
||
public static void main(String[] args) {
|
||
SwingUtilities.invokeLater(() -> new Workbench().start());
|
||
}
|
||
|
||
private void start() {
|
||
// Optional: try to load Nashorn for oracle eval
|
||
try {
|
||
ScriptEngineManager mgr = new ScriptEngineManager();
|
||
scriptEngine = mgr.getEngineByName("javascript");
|
||
} catch (Exception e) {
|
||
scriptEngine = null;
|
||
}
|
||
|
||
PatchVerifier.Status patch = PatchVerifier.check();
|
||
|
||
JFrame frame = new JFrame("ARK_ID: 1.0.2.10 // JAVA_PORT" +
|
||
(patch.patched() ? " [PATCHED]" : " [UNPATCHED]"));
|
||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||
frame.setLayout(new BorderLayout());
|
||
|
||
// Header
|
||
JPanel header = new JPanel(new BorderLayout());
|
||
header.setBackground(BG_PANEL);
|
||
header.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, BORDER_COL));
|
||
header.setPreferredSize(new Dimension(0, 50));
|
||
JLabel title = monoLabel("ARK_ID: 1.0.2.10 // JAVA_PORT", 9, PCB_GOLD);
|
||
title.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0));
|
||
header.add(title, BorderLayout.WEST);
|
||
frame.add(header, BorderLayout.NORTH);
|
||
|
||
// Main area
|
||
JPanel main = new JPanel(new BorderLayout());
|
||
viewport = new ViewportPanel();
|
||
viewport.setBackground(BG_SCENE);
|
||
|
||
// Draw overlay (200×200, positioned top-left of viewport area)
|
||
JLayeredPane layered = new JLayeredPane();
|
||
layered.setLayout(null);
|
||
viewport.setBounds(0, 0, 1000, 800); // will be updated on resize
|
||
drawOverlay = new DrawOverlay();
|
||
drawOverlay.setBounds(10, 10, 200, 200);
|
||
layered.add(viewport, JLayeredPane.DEFAULT_LAYER);
|
||
layered.add(drawOverlay, JLayeredPane.PALETTE_LAYER);
|
||
layered.addComponentListener(new ComponentAdapter() {
|
||
@Override public void componentResized(ComponentEvent e) {
|
||
Dimension d = layered.getSize();
|
||
viewport.setBounds(0, 0, d.width, d.height);
|
||
drawOverlay.setBounds(10, 10, 200, 200);
|
||
}
|
||
});
|
||
|
||
main.add(layered, BorderLayout.CENTER);
|
||
main.add(buildSidebar(), BorderLayout.EAST);
|
||
frame.add(main, BorderLayout.CENTER);
|
||
|
||
frame.setSize(1200, 800);
|
||
frame.setLocationRelativeTo(null);
|
||
frame.setVisible(true);
|
||
|
||
// Startup banner in PCB output
|
||
pcbLog("JAVA: " + System.getProperty("java.version"));
|
||
pcbLog("PATCH: " + patch);
|
||
pcbLog(scriptEngine != null ? "ORACLE: js engine ready" : "ORACLE: fallback sin(t*0.05)*5");
|
||
|
||
// Kick off render loop
|
||
Timer timer = new Timer(16, e -> tick());
|
||
timer.start();
|
||
}
|
||
|
||
// ── Tick (one frame) ──────────────────────────────────────────────────────
|
||
|
||
private void tick() {
|
||
tick++;
|
||
if (manifold.built) {
|
||
manifold.diffuseHeat();
|
||
applyOracle();
|
||
for (Friend f : friends) f.compute();
|
||
}
|
||
if (tick % 20 == 0) updateMatrix();
|
||
viewport.repaint();
|
||
}
|
||
|
||
private void applyOracle() {
|
||
String expr = oracleField != null ? oracleField.getText().trim() : oracleExpr;
|
||
if (expr.isEmpty()) expr = "1";
|
||
|
||
Random rng = viewport.rng;
|
||
float wobble = wobbleBase;
|
||
|
||
for (int i = 0; i < manifold.vertexCount; i++) {
|
||
float ox = manifold.originalPositions[i*3];
|
||
float oy = manifold.originalPositions[i*3+1];
|
||
float oz = manifold.originalPositions[i*3+2];
|
||
float cx = ox, cy = oy;
|
||
float d = (float)Math.sqrt(cx*cx + cy*cy) * 0.001f;
|
||
float t = tick;
|
||
float h = manifold.heat[i];
|
||
|
||
double w = evalOracle(expr, t, d, i, h);
|
||
|
||
manifold.positions[i*3] = ox + (rng.nextFloat() - 0.5f) * wobble;
|
||
manifold.positions[i*3 + 1] = oy + (rng.nextFloat() - 0.5f) * wobble;
|
||
manifold.positions[i*3 + 2] = oz + h * (float)w;
|
||
}
|
||
// also update seam positions
|
||
for (int u = 0; u < Manifold.M; u++) {
|
||
int base = (u * Manifold.M + u) * 3;
|
||
manifold.seamPositions[u*3] = manifold.positions[base];
|
||
manifold.seamPositions[u*3 + 1] = manifold.positions[base + 1];
|
||
manifold.seamPositions[u*3 + 2] = manifold.positions[base + 2];
|
||
}
|
||
}
|
||
|
||
private double evalOracle(String expr, float t, float d, int i, float heat) {
|
||
if (scriptEngine != null) {
|
||
try {
|
||
SimpleBindings b = new SimpleBindings();
|
||
b.put("t", (double)t);
|
||
b.put("d", (double)d);
|
||
b.put("i", (double)i);
|
||
b.put("heat", (double)heat);
|
||
Object result = scriptEngine.eval(expr, b);
|
||
if (result instanceof Number) return ((Number)result).doubleValue();
|
||
} catch (Exception e) { /* fall through */ }
|
||
}
|
||
// Built-in fallback: sin(t*0.05)*5
|
||
return Math.sin(t * 0.05) * 5.0;
|
||
}
|
||
|
||
// ── PCB Console ───────────────────────────────────────────────────────────
|
||
|
||
private void pcbRun(String input) {
|
||
pcbLog("PURGING_DRAWPAD...");
|
||
drawOverlay.clear();
|
||
seedPoints.clear();
|
||
input = input.trim();
|
||
|
||
if (input.equals("sys.verify")) {
|
||
PatchVerifier.Status s = PatchVerifier.check();
|
||
pcbLog("PATCH_STATUS: " + s);
|
||
return;
|
||
}
|
||
|
||
if (input.startsWith("sys.connect")) {
|
||
pcbLog("FRONTIER: Liminal channel open. Speak. (Java port — no API)");
|
||
return;
|
||
}
|
||
|
||
// Modulate flicker/wobble from input hash
|
||
int sum = 0;
|
||
for (char c : input.toCharArray()) sum += c;
|
||
flickerRate = (sum % 127) / 1000f + 0.02f;
|
||
wobbleBase = (input.isEmpty() ? 1f : (float)sum / input.length()) / 32f;
|
||
|
||
if (input.equals("E>.<3")) {
|
||
injectHeart();
|
||
} else if (input.startsWith("manifold.(")) {
|
||
java.util.regex.Matcher m = java.util.regex.Pattern
|
||
.compile("manifold\\.\\(\\[(.*)\\]\\)").matcher(input);
|
||
if (m.find()) {
|
||
String[] parts = m.group(1).split(",");
|
||
float[] coords = new float[parts.length];
|
||
for (int i = 0; i < parts.length; i++) {
|
||
try { coords[i] = Float.parseFloat(parts[i].trim()); }
|
||
catch (NumberFormatException e) { coords[i] = 0; }
|
||
}
|
||
injectCoords(coords);
|
||
}
|
||
} else {
|
||
interpretASCII(input);
|
||
}
|
||
}
|
||
|
||
private void injectCoords(float[] coords) {
|
||
for (int i = 0; i + 1 < coords.length; i += 2) {
|
||
float x = coords[i], y = coords[i+1];
|
||
seedPoints.add(new float[]{x, y});
|
||
drawOverlay.addDot(x, y);
|
||
}
|
||
buildManifoldFromSeeds();
|
||
pcbLog("MANIFOLD: " + seedPoints.size() + " seeds injected");
|
||
}
|
||
|
||
private void interpretASCII(String str) {
|
||
float[] coords = new float[str.length() * 2];
|
||
for (int i = 0; i < str.length(); i++) {
|
||
int val = str.charAt(i);
|
||
coords[i*2] = 100 + (float)Math.cos(i * 0.4) * val * 0.7f;
|
||
coords[i*2 + 1] = 100 + (float)Math.sin(i * 0.4) * val * 0.7f;
|
||
}
|
||
injectCoords(coords);
|
||
}
|
||
|
||
private void injectHeart() {
|
||
List<float[]> pts = new ArrayList<>();
|
||
for (double t = 0; t < Math.PI * 2; t += 0.15) {
|
||
float x = (float)(16 * Math.pow(Math.sin(t), 3) * 4 + 100);
|
||
float y = (float)(-(13*Math.cos(t) - 5*Math.cos(2*t) - 2*Math.cos(3*t) - Math.cos(4*t)) * 4 + 100);
|
||
pts.add(new float[]{x, y});
|
||
}
|
||
float[] coords = new float[pts.size() * 2];
|
||
for (int i = 0; i < pts.size(); i++) {
|
||
coords[i*2] = pts.get(i)[0];
|
||
coords[i*2 + 1] = pts.get(i)[1];
|
||
}
|
||
injectCoords(coords);
|
||
}
|
||
|
||
private void buildManifoldFromSeeds() {
|
||
int n = seedPoints.size();
|
||
float[] sx = new float[n], sy = new float[n];
|
||
for (int i = 0; i < n; i++) { sx[i] = seedPoints.get(i)[0]; sy[i] = seedPoints.get(i)[1]; }
|
||
manifold.build(sx, sy, n);
|
||
// relocate existing friends
|
||
for (Friend f : friends) f.relocate();
|
||
if (friendBtn != null) friendBtn.setEnabled(true);
|
||
// reset viewport scale so it re-fits on next draw
|
||
viewport.resetScale();
|
||
pcbLog("MANIFOLD: built " + manifold.vertexCount + " vertices");
|
||
}
|
||
|
||
private void pcbLog(String msg) {
|
||
if (pcbOutput == null) return;
|
||
SwingUtilities.invokeLater(() -> {
|
||
pcbOutput.append("\n> " + msg);
|
||
pcbOutput.setCaretPosition(pcbOutput.getDocument().getLength());
|
||
});
|
||
}
|
||
|
||
private void deployFriends() {
|
||
if (!manifold.built) return;
|
||
for (int i = 0; i < 5; i++) {
|
||
Friend f = new Friend(i, manifold);
|
||
int idx = i;
|
||
f.onRespawn = () -> pcbLog("AGENT_" + Friend.IDS[idx] + ": RESPAWN at v" + f.vIdx);
|
||
f.onBloom = () -> pcbLog("AGENT_" + Friend.IDS[idx] + ": AUTO_MACRO: manifold.patch()");
|
||
friends.add(f);
|
||
}
|
||
if (friendBtn != null) friendBtn.setEnabled(false);
|
||
}
|
||
|
||
private void updateMatrix() {
|
||
if (matrixBody == null) return;
|
||
SwingUtilities.invokeLater(() -> {
|
||
matrixBody.removeAll();
|
||
for (Friend f : friends) {
|
||
JPanel row = new JPanel(new GridLayout(1, 5, 2, 0));
|
||
row.setBackground(BG_PANEL);
|
||
row.add(monoLabel(f.id, 8, FRIEND_COLORS[f.idx]));
|
||
row.add(monoLabel(String.format("%.0f", f.life), 8, PCB_GREEN));
|
||
row.add(monoLabel(String.format("%.1f", f.sap), 8, PCB_GREEN));
|
||
row.add(monoLabel(String.valueOf(f.blooms), 8, PCB_GREEN));
|
||
row.add(monoLabel("AUTO", 8, PCB_GREEN));
|
||
matrixBody.add(row);
|
||
}
|
||
matrixBody.revalidate();
|
||
matrixBody.repaint();
|
||
});
|
||
}
|
||
|
||
// ── Sidebar ───────────────────────────────────────────────────────────────
|
||
|
||
private JPanel buildSidebar() {
|
||
JPanel sidebar = new JPanel();
|
||
sidebar.setBackground(BG_PANEL);
|
||
sidebar.setPreferredSize(new Dimension(420, 0));
|
||
sidebar.setLayout(new BoxLayout(sidebar, BoxLayout.Y_AXIS));
|
||
sidebar.setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, BORDER_COL));
|
||
|
||
sidebar.add(buildPCBModule());
|
||
sidebar.add(Box.createVerticalStrut(8));
|
||
sidebar.add(buildOracleModule());
|
||
sidebar.add(Box.createVerticalStrut(8));
|
||
sidebar.add(buildMatrixModule());
|
||
sidebar.add(Box.createVerticalStrut(8));
|
||
sidebar.add(buildReifyModule());
|
||
sidebar.add(Box.createVerticalGlue());
|
||
|
||
JButton purgeBtn = new JButton("PURGE_SESSION");
|
||
purgeBtn.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 8));
|
||
purgeBtn.setBackground(Color.BLACK);
|
||
purgeBtn.setForeground(new Color(0xff, 0x32, 0x32));
|
||
purgeBtn.setBorder(BorderFactory.createLineBorder(new Color(0xff, 0x32, 0x32)));
|
||
purgeBtn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30));
|
||
purgeBtn.addActionListener(e -> purgeSession());
|
||
sidebar.add(purgeBtn);
|
||
sidebar.add(Box.createVerticalStrut(10));
|
||
|
||
return sidebar;
|
||
}
|
||
|
||
private JPanel buildPCBModule() {
|
||
JPanel p = module("POCKET_CUP_BOX_CONSOLE");
|
||
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
|
||
p.setBorder(BorderFactory.createCompoundBorder(
|
||
BorderFactory.createLineBorder(PCB_GOLD, 2),
|
||
BorderFactory.createEmptyBorder(8, 8, 8, 8)));
|
||
|
||
pcbSource = new JTextArea(4, 30);
|
||
pcbSource.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 9));
|
||
pcbSource.setBackground(Color.BLACK);
|
||
pcbSource.setForeground(PCB_GOLD);
|
||
pcbSource.setCaretColor(PCB_GOLD);
|
||
pcbSource.setBorder(BorderFactory.createLineBorder(BORDER_COL));
|
||
pcbSource.setText("sys.connect('frontier')");
|
||
|
||
JButton runBtn = new JButton("RUN_ADMIN_CMD");
|
||
runBtn.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 8));
|
||
runBtn.setBackground(Color.BLACK);
|
||
runBtn.setForeground(PCB_GOLD);
|
||
runBtn.setBorder(BorderFactory.createLineBorder(PCB_GOLD));
|
||
runBtn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 25));
|
||
runBtn.addActionListener(e -> pcbRun(pcbSource.getText()));
|
||
// Enter key in source also runs
|
||
pcbSource.addKeyListener(new KeyAdapter() {
|
||
@Override public void keyPressed(KeyEvent e) {
|
||
if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_ENTER)
|
||
pcbRun(pcbSource.getText());
|
||
}
|
||
});
|
||
|
||
pcbOutput = new JTextArea(3, 30);
|
||
pcbOutput.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 8));
|
||
pcbOutput.setBackground(new Color(5, 5, 5));
|
||
pcbOutput.setForeground(new Color(0x88, 0x88, 0x88));
|
||
pcbOutput.setEditable(false);
|
||
pcbOutput.setText("PCB_STATUS: IDLE.");
|
||
JScrollPane scroll = new JScrollPane(pcbOutput);
|
||
scroll.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, PCB_GOLD));
|
||
scroll.setMaximumSize(new Dimension(Integer.MAX_VALUE, 60));
|
||
|
||
p.add(new JScrollPane(pcbSource));
|
||
p.add(Box.createVerticalStrut(4));
|
||
p.add(runBtn);
|
||
p.add(scroll);
|
||
return p;
|
||
}
|
||
|
||
private JPanel buildOracleModule() {
|
||
JPanel p = module("ORACLE_HEURISTICS");
|
||
oracleField = new JTextField(oracleExpr);
|
||
oracleField.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 9));
|
||
oracleField.setBackground(Color.BLACK);
|
||
oracleField.setForeground(PCB_GREEN);
|
||
oracleField.setCaretColor(PCB_GREEN);
|
||
oracleField.setBorder(BorderFactory.createLineBorder(new Color(0x94, 0x00, 0xd3)));
|
||
p.add(oracleField);
|
||
return p;
|
||
}
|
||
|
||
private JPanel buildMatrixModule() {
|
||
JPanel p = module("AGENT_LABS_MATRIX");
|
||
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
|
||
|
||
// header row
|
||
JPanel hdr = new JPanel(new GridLayout(1, 5, 2, 0));
|
||
hdr.setBackground(BG_PANEL);
|
||
for (String h : new String[]{"ID","LIFE","SAP","BLM","MODE"})
|
||
hdr.add(monoLabel(h, 7, new Color(0x44,0x44,0x44)));
|
||
p.add(hdr);
|
||
|
||
matrixBody = new JPanel();
|
||
matrixBody.setBackground(BG_PANEL);
|
||
matrixBody.setLayout(new BoxLayout(matrixBody, BoxLayout.Y_AXIS));
|
||
p.add(matrixBody);
|
||
return p;
|
||
}
|
||
|
||
private JPanel buildReifyModule() {
|
||
JPanel p = module("REIFY TOOLS");
|
||
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
|
||
|
||
inscribeBtn = sideBtn("INSCRIBE_DRAW_TO_PCB", PCB_GOLD);
|
||
inscribeBtn.addActionListener(e -> inscribe());
|
||
p.add(inscribeBtn);
|
||
p.add(Box.createVerticalStrut(4));
|
||
|
||
friendBtn = sideBtn("DEPLOY_FRIENDS", Color.WHITE);
|
||
friendBtn.setEnabled(false);
|
||
friendBtn.addActionListener(e -> deployFriends());
|
||
p.add(friendBtn);
|
||
p.add(Box.createVerticalStrut(4));
|
||
|
||
solveBtn = sideBtn("REIFY_MANIFOLD", Color.WHITE);
|
||
solveBtn.addActionListener(e -> buildManifoldFromSeeds());
|
||
p.add(solveBtn);
|
||
return p;
|
||
}
|
||
|
||
private void inscribe() {
|
||
if (seedPoints.isEmpty()) return;
|
||
int step = Math.max(1, seedPoints.size() / 20);
|
||
StringBuilder sb = new StringBuilder("manifold.([");
|
||
for (int i = 0; i < seedPoints.size(); i += step) {
|
||
if (i > 0) sb.append(',');
|
||
sb.append(Math.round(seedPoints.get(i)[0])).append(',')
|
||
.append(Math.round(seedPoints.get(i)[1]));
|
||
}
|
||
sb.append("])");
|
||
if (pcbSource != null) pcbSource.setText(sb.toString());
|
||
}
|
||
|
||
private void purgeSession() {
|
||
seedPoints.clear();
|
||
friends.clear();
|
||
manifold.built = false;
|
||
drawOverlay.clear();
|
||
viewport.resetScale();
|
||
if (pcbOutput != null) pcbOutput.setText("PCB_STATUS: IDLE.");
|
||
viewport.repaint();
|
||
}
|
||
|
||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||
|
||
private JPanel module(String title) {
|
||
JPanel p = new JPanel(new BorderLayout());
|
||
p.setBackground(BG_PANEL);
|
||
p.setBorder(BorderFactory.createCompoundBorder(
|
||
BorderFactory.createLineBorder(BORDER_COL),
|
||
BorderFactory.createEmptyBorder(6, 6, 6, 6)));
|
||
JLabel lbl = monoLabel(title, 9, FG_TEXT);
|
||
lbl.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, BORDER_COL));
|
||
p.add(lbl, BorderLayout.NORTH);
|
||
return p;
|
||
}
|
||
|
||
private JButton sideBtn(String text, Color fgColor) {
|
||
JButton btn = new JButton(text);
|
||
btn.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 8));
|
||
btn.setBackground(new Color(0x11, 0x11, 0x11));
|
||
btn.setForeground(fgColor);
|
||
btn.setBorder(BorderFactory.createLineBorder(BORDER_COL));
|
||
btn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 28));
|
||
return btn;
|
||
}
|
||
|
||
private static JLabel monoLabel(String text, int size, Color fg) {
|
||
JLabel l = new JLabel(text);
|
||
l.setFont(new Font(Font.MONOSPACED, Font.PLAIN, size));
|
||
l.setForeground(fg);
|
||
return l;
|
||
}
|
||
|
||
// ── Viewport panel — 3D wireframe renderer ────────────────────────────────
|
||
|
||
class ViewportPanel extends JPanel {
|
||
|
||
final Random rng = new Random();
|
||
|
||
// Rotation matrix (3×3, row-major)
|
||
private float[] rot = {1,0,0, 0,1,0, 0,0,1};
|
||
|
||
// Mouse state for drag-to-rotate
|
||
private int lastMX, lastMY;
|
||
private boolean dragging;
|
||
|
||
// Auto-computed scale (fits manifold to panel)
|
||
private float scale = 0f; // 0 = needs recompute
|
||
private float camDist = 3000f;
|
||
|
||
// Pre-allocated per-frame buffers — avoids GC pressure during spin
|
||
private float[] rv = new float[3]; // rotation scratch
|
||
private float[] sxBuf = new float[0]; // projected x
|
||
private float[] syBuf = new float[0]; // projected y
|
||
private float[] szBuf = new float[0]; // projected z (depth)
|
||
|
||
// Pre-allocated strokes
|
||
private final BasicStroke wireStroke = new BasicStroke(0.5f);
|
||
private final BasicStroke seamStroke = new BasicStroke(1.5f);
|
||
|
||
ViewportPanel() {
|
||
setBackground(BG_SCENE);
|
||
MouseAdapter ma = new MouseAdapter() {
|
||
@Override public void mousePressed(MouseEvent e) {
|
||
lastMX = e.getX(); lastMY = e.getY(); dragging = true;
|
||
}
|
||
@Override public void mouseReleased(MouseEvent e) { dragging = false; }
|
||
@Override public void mouseDragged(MouseEvent e) {
|
||
if (!dragging) return;
|
||
float dx = (e.getX() - lastMX) * 0.008f;
|
||
float dy = (e.getY() - lastMY) * 0.008f;
|
||
lastMX = e.getX(); lastMY = e.getY();
|
||
rotateX(dy);
|
||
rotateY(dx);
|
||
repaint();
|
||
}
|
||
};
|
||
addMouseListener(ma);
|
||
addMouseMotionListener(ma);
|
||
}
|
||
|
||
void resetScale() { scale = 0f; }
|
||
|
||
@Override
|
||
protected void paintComponent(Graphics g0) {
|
||
super.paintComponent(g0);
|
||
Graphics2D g = (Graphics2D)g0;
|
||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||
|
||
if (!manifold.built) {
|
||
g.setColor(new Color(0x33, 0x33, 0x33));
|
||
g.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 10));
|
||
g.drawString("AWAITING_SEEDS — draw on overlay or type in PCB", 20, getHeight()/2);
|
||
return;
|
||
}
|
||
|
||
int W = getWidth(), H = getHeight();
|
||
float cx = W / 2f, cy = H / 2f;
|
||
|
||
// auto-scale once per build
|
||
if (scale == 0f) {
|
||
float[] bb = manifold.bounds();
|
||
float span = Math.max(
|
||
Math.max(bb[3]-bb[0], bb[4]-bb[1]),
|
||
bb[5]-bb[2]
|
||
);
|
||
scale = Math.min(W, H) * 0.35f / Math.max(span, 1f);
|
||
camDist = span * 3f;
|
||
}
|
||
|
||
int M = Manifold.M;
|
||
|
||
// Resize projection buffers only when vertex count changes
|
||
if (sxBuf.length < manifold.vertexCount) {
|
||
sxBuf = new float[manifold.vertexCount];
|
||
syBuf = new float[manifold.vertexCount];
|
||
szBuf = new float[manifold.vertexCount];
|
||
}
|
||
|
||
// Project all vertices (no allocation inside loop)
|
||
for (int i = 0; i < manifold.vertexCount; i++) {
|
||
rotateVecInto(
|
||
manifold.positions[i*3],
|
||
manifold.positions[i*3+1],
|
||
manifold.positions[i*3+2],
|
||
rv
|
||
);
|
||
szBuf[i] = rv[2];
|
||
float w = camDist / (rv[2] + camDist);
|
||
sxBuf[i] = cx + rv[0] * scale * w;
|
||
syBuf[i] = cy - rv[1] * scale * w;
|
||
}
|
||
|
||
// Draw wireframe grid (u-lines and v-lines)
|
||
g.setColor(WIRE_COL);
|
||
g.setStroke(wireStroke);
|
||
|
||
// u-lines: for each u, draw edges along v
|
||
for (int u = 0; u < M; u++) {
|
||
for (int v = 0; v < M - 1; v++) {
|
||
int a = u*M+v, b = u*M+(v+1);
|
||
g.drawLine(Math.round(sxBuf[a]), Math.round(syBuf[a]),
|
||
Math.round(sxBuf[b]), Math.round(syBuf[b]));
|
||
}
|
||
}
|
||
// v-lines: for each v, draw edges along u
|
||
for (int v = 0; v < M; v++) {
|
||
for (int u = 0; u < M - 1; u++) {
|
||
int a = u*M+v, b = (u+1)*M+v;
|
||
g.drawLine(Math.round(sxBuf[a]), Math.round(syBuf[a]),
|
||
Math.round(sxBuf[b]), Math.round(syBuf[b]));
|
||
}
|
||
}
|
||
|
||
// Draw seam (diagonal u==v) in red
|
||
g.setColor(SEAM_COL);
|
||
g.setStroke(seamStroke);
|
||
for (int u = 0; u < M - 1; u++) {
|
||
int a = u*M+u, b = (u+1)*M+(u+1);
|
||
g.drawLine(Math.round(sxBuf[a]), Math.round(syBuf[a]),
|
||
Math.round(sxBuf[b]), Math.round(syBuf[b]));
|
||
}
|
||
|
||
// Draw friends
|
||
for (Friend f : friends) {
|
||
rotateVecInto(f.x, f.y, f.z, rv);
|
||
float w = camDist / (rv[2] + camDist);
|
||
int fx = Math.round(cx + rv[0] * scale * w);
|
||
int fy = Math.round(cy - rv[1] * scale * w);
|
||
g.setColor(FRIEND_COLORS[f.idx]);
|
||
g.fillOval(fx - 6, fy - 6, 12, 12);
|
||
}
|
||
|
||
// Flicker overlay on opacity — draw subtle sine pulse as bottom label
|
||
float alpha = 0.3f + (float)Math.sin(tick * flickerRate) * 0.2f;
|
||
g.setColor(new Color(0f, 0f, 0f, Math.max(0f, Math.min(1f, alpha))));
|
||
g.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 8));
|
||
g.setColor(new Color(0x33, 0x33, 0x33));
|
||
g.drawString(String.format("tick=%d seeds=%d heat_max=%.2f",
|
||
tick, seedPoints.size(),
|
||
manifold.heat.length > 0 ? maxHeat() : 0f), 8, H - 8);
|
||
}
|
||
|
||
private float maxHeat() {
|
||
float m = 0;
|
||
for (float v : manifold.heat) if (v > m) m = v;
|
||
return m;
|
||
}
|
||
|
||
// Apply rotation matrix into a pre-allocated float[3] — no allocation
|
||
private void rotateVecInto(float x, float y, float z, float[] out) {
|
||
out[0] = rot[0]*x + rot[1]*y + rot[2]*z;
|
||
out[1] = rot[3]*x + rot[4]*y + rot[5]*z;
|
||
out[2] = rot[6]*x + rot[7]*y + rot[8]*z;
|
||
}
|
||
|
||
// Rotate around X axis by angle (radians)
|
||
private void rotateX(float angle) {
|
||
float c = (float)Math.cos(angle), s = (float)Math.sin(angle);
|
||
float[] rx = {1,0,0, 0,c,-s, 0,s,c};
|
||
rot = mulMat(rx, rot);
|
||
}
|
||
|
||
// Rotate around Y axis by angle (radians)
|
||
private void rotateY(float angle) {
|
||
float c = (float)Math.cos(angle), s = (float)Math.sin(angle);
|
||
float[] ry = {c,0,s, 0,1,0, -s,0,c};
|
||
rot = mulMat(ry, rot);
|
||
}
|
||
|
||
private float[] mulMat(float[] a, float[] b) {
|
||
float[] r = new float[9];
|
||
for (int row = 0; row < 3; row++)
|
||
for (int col = 0; col < 3; col++)
|
||
for (int k = 0; k < 3; k++)
|
||
r[row*3+col] += a[row*3+k] * b[k*3+col];
|
||
return r;
|
||
}
|
||
}
|
||
|
||
// ── Draw overlay — seed point canvas ─────────────────────────────────────
|
||
|
||
class DrawOverlay extends JPanel {
|
||
|
||
private final List<float[]> dots = new ArrayList<>();
|
||
|
||
DrawOverlay() {
|
||
setOpaque(true);
|
||
setBackground(new Color(255, 255, 255, 180));
|
||
setBorder(BorderFactory.createLineBorder(BORDER_COL));
|
||
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
|
||
|
||
MouseAdapter ma = new MouseAdapter() {
|
||
@Override public void mousePressed(MouseEvent e) { dot(e); }
|
||
@Override public void mouseDragged(MouseEvent e) { dot(e); }
|
||
private void dot(MouseEvent e) {
|
||
float x = e.getX(), y = e.getY();
|
||
dots.add(new float[]{x, y});
|
||
seedPoints.add(new float[]{x, y});
|
||
repaint();
|
||
}
|
||
};
|
||
addMouseListener(ma);
|
||
addMouseMotionListener(ma);
|
||
}
|
||
|
||
void addDot(float x, float y) {
|
||
dots.add(new float[]{x, y});
|
||
repaint();
|
||
}
|
||
|
||
void clear() {
|
||
dots.clear();
|
||
repaint();
|
||
}
|
||
|
||
@Override
|
||
protected void paintComponent(Graphics g) {
|
||
super.paintComponent(g);
|
||
g.setColor(Color.BLACK);
|
||
for (float[] d : dots) g.fillRect((int)d[0], (int)d[1], 2, 2);
|
||
}
|
||
}
|
||
}
|