java-topology/tests/workbench/Manifold.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
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.
2026-03-26 17:11:57 -04:00

156 lines
5.9 KiB
Java

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<List<Integer>> 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<Integer> 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<Integer> 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};
}
}