package workbench; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Sym² manifold — pure geometry + heat diffusion. * * Given m seed points, maps every pair (u,v) to a 3D vertex: * x,y = midpoint of p_u and p_v (centered) * z = distance between p_u and p_v * 8 * * This is the symmetric product Sym²(curve). The seam (u==v diagonal) * re-embeds the original curve at z=0. * * No Swing dependencies — pure data structure. */ public class Manifold { public static final int M = 32; // grid resolution public static final float DECAY = 0.96f; // heat decay per frame // flat float arrays: vertex i → positions[i*3 .. i*3+2] public float[] positions; // current positions (displaced by heat + wobble) public float[] originalPositions;// rest positions (no displacement) public int[] indices; // triangle index list // seam: diagonal u==v re-embeds original curve at z=0 public float[] seamPositions; // M points, flat x,y,z // adjacency[i] = list of vertex indices adjacent to i public List> adjacency; public float[] heat; // heat per vertex public int vertexCount; public boolean built = false; // ── Sym² computation ────────────────────────────────────────────────────── /** * Pure math: compute M*M vertex positions from seed points. * Returns flat float array: [x0,y0,z0, x1,y1,z1, ...] */ public static float[] computePositions(float[] seedX, float[] seedY, int n) { // sample M evenly-spaced seeds float[] sx = new float[M], sy = new float[M]; for (int i = 0; i < M; i++) { int k = (int)(i * n / (double)M); sx[i] = seedX[k]; sy[i] = seedY[k]; } // center float cx = 0, cy = 0; for (int i = 0; i < M; i++) { cx += sx[i]; cy += sy[i]; } cx /= M; cy /= M; float[] pos = new float[M * M * 3]; for (int u = 0; u < M; u++) { for (int v = 0; v < M; v++) { int base = (u * M + v) * 3; float midX = (sx[u] + sx[v]) / 2f - cx; float midY = (sy[u] + sy[v]) / 2f - cy; float dx = sx[u] - sx[v]; float dy = sy[u] - sy[v]; pos[base] = midX * 12f; pos[base + 1] = -midY * 12f; pos[base + 2] = (float)Math.sqrt(dx*dx + dy*dy) * 8f; } } return pos; } /** * Build manifold from seed points. Call when seeds change. * Resets heat to zero. */ public void build(float[] seedX, float[] seedY, int n) { if (n < 5) return; vertexCount = M * M; positions = computePositions(seedX, seedY, n); originalPositions = Arrays.copyOf(positions, positions.length); // triangle indices: quad (u,v) split into 2 triangles int quadCount = (M - 1) * (M - 1); indices = new int[quadCount * 6]; int idx = 0; for (int u = 0; u < M - 1; u++) { for (int v = 0; v < M - 1; v++) { int a = u*M+v, b = (u+1)*M+v, c = u*M+(v+1), d = (u+1)*M+(v+1); indices[idx++] = a; indices[idx++] = b; indices[idx++] = c; indices[idx++] = b; indices[idx++] = d; indices[idx++] = c; } } // adjacency from triangle index list adjacency = new ArrayList<>(vertexCount); for (int i = 0; i < vertexCount; i++) adjacency.add(new ArrayList<>()); for (int i = 0; i < indices.length; i += 3) { int a = indices[i], b = indices[i+1], c = indices[i+2]; adjacency.get(a).add(b); adjacency.get(b).add(a); adjacency.get(b).add(c); adjacency.get(c).add(b); adjacency.get(a).add(c); adjacency.get(c).add(a); } // deduplicate for (int i = 0; i < vertexCount; i++) { Set seen = new HashSet<>(adjacency.get(i)); adjacency.set(i, new ArrayList<>(seen)); } // seam: diagonal u==v seamPositions = new float[M * 3]; for (int u = 0; u < M; u++) { int base = (u * M + u) * 3; seamPositions[u*3] = positions[base]; seamPositions[u*3 + 1] = positions[base + 1]; seamPositions[u*3 + 2] = positions[base + 2]; } heat = new float[vertexCount]; built = true; } // ── Heat diffusion (graph Laplacian, one step) ──────────────────────────── public void diffuseHeat() { if (!built) return; float[] next = new float[heat.length]; for (int i = 0; i < heat.length; i++) { List nb = adjacency.get(i); float sum = heat[i]; for (int j : nb) sum += heat[j]; next[i] = (sum / (nb.size() + 1)) * DECAY; } heat = next; } // ── Bounding box for viewport scaling ──────────────────────────────────── public float[] bounds() { if (!built) return new float[]{-1,-1,-1,1,1,1}; float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE, minZ = Float.MAX_VALUE; float maxX = -Float.MAX_VALUE, maxY = -Float.MAX_VALUE, maxZ = -Float.MAX_VALUE; for (int i = 0; i < vertexCount; i++) { float x = originalPositions[i*3], y = originalPositions[i*3+1], z = originalPositions[i*3+2]; if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y; if (z < minZ) minZ = z; if (z > maxZ) maxZ = z; } return new float[]{minX, minY, minZ, maxX, maxY, maxZ}; } }