Skip to content

Commit 2d16bc5

Browse files
committed
Adding the importer and exported methods to wrap manifold in JCSG
classes
1 parent 5d5be5b commit 2d16bc5

4 files changed

Lines changed: 249 additions & 2 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.neuronrobotics.manifold3d;
2+
3+
import com.cadoodlecad.manifold.ManifoldBindings;
4+
5+
public class CSGManifold3d {
6+
private final ManifoldBindings manifold;
7+
Manifold3dExporter exporter;
8+
Manifold3dImporter importer;
9+
public CSGManifold3d() throws Exception {
10+
this.manifold = new ManifoldBindings();
11+
exporter = new Manifold3dExporter(manifold);
12+
importer = new Manifold3dImporter(manifold);
13+
}
14+
15+
16+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package com.neuronrobotics.manifold3d;
2+
3+
import java.lang.foreign.MemorySegment;
4+
import java.util.ArrayList;
5+
import java.util.HashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
import com.cadoodlecad.manifold.ManifoldBindings;
10+
11+
import eu.mihosoft.vrl.v3d.CSG;
12+
import eu.mihosoft.vrl.v3d.Polygon;
13+
import eu.mihosoft.vrl.v3d.Vertex;
14+
15+
/**
16+
* Exports a JCSG {@link CSG} object into a native manifold3d manifold via the bridge API.
17+
*
18+
* <p>Usage:
19+
* <pre>{@code
20+
* Manifold3dBridge bridge = ...; // your wrapper holding the MethodHandle map
21+
* CSG csg = ...;
22+
*
23+
* MemorySegment manifoldSeg = new Manifold3dExporter(bridge).toManifold(csg);
24+
* // use manifoldSeg with bridge boolean operations, then clean up via bridge.delete(...)
25+
* }</pre>
26+
*
27+
* <p>JCSG polygons may have more than three vertices (the BSP representation preserves
28+
* quads and n-gons). This exporter triangulates each polygon using a simple fan from the
29+
* first vertex (safe for convex polygons, which is guaranteed by the JCSG BSP). The
30+
* resulting triangle soup is de-duplicated into an indexed mesh before being handed to
31+
* the bridge so that manifold3d's merge step has shared vertices to work with.
32+
*/
33+
public class Manifold3dExporter {
34+
35+
private final ManifoldBindings bridge;
36+
37+
public Manifold3dExporter(ManifoldBindings bridge) {
38+
if (bridge == null)
39+
throw new IllegalArgumentException("bridge must not be null");
40+
this.bridge = bridge;
41+
}
42+
43+
/**
44+
* Converts a JCSG {@link CSG} into a native manifold {@link MemorySegment}.
45+
*
46+
* <p>The caller is responsible for eventually freeing the returned segment via the
47+
* bridge's delete method (e.g. {@code manifold_delete_manifold}).
48+
*
49+
* @param csg the solid to export; must not be null
50+
* @return a native manifold segment ready for boolean operations
51+
* @throws Throwable if the native import call fails
52+
* @throws IllegalArgumentException if {@code csg} is null or has no polygons
53+
*/
54+
public MemorySegment toManifold(CSG csg) throws Throwable {
55+
if (csg == null)
56+
throw new IllegalArgumentException("csg must not be null");
57+
58+
List<Polygon> polygons = csg.getPolygons();
59+
if (polygons == null || polygons.isEmpty())
60+
throw new IllegalArgumentException("CSG has no polygons");
61+
62+
// Build an indexed triangle mesh.
63+
// Use a tolerance-free exact key so we don't merge numerically-close-but-distinct verts.
64+
Map<String, Integer> vertexIndex = new HashMap<>();
65+
List<double[]> vertexList = new ArrayList<>();
66+
List<Long> triList = new ArrayList<>();
67+
68+
for (Polygon poly : polygons) {
69+
List<Vertex> pverts = poly.getVertices();
70+
if (pverts == null || pverts.size() < 3)
71+
continue;
72+
73+
// Fan triangulation: (0,1,2), (0,2,3), (0,3,4), ...
74+
int i0 = intern(pverts.get(0), vertexIndex, vertexList);
75+
for (int i = 1; i < pverts.size() - 1; i++) {
76+
int i1 = intern(pverts.get(i), vertexIndex, vertexList);
77+
int i2 = intern(pverts.get(i + 1), vertexIndex, vertexList);
78+
79+
// Skip degenerate triangles (two or more identical indices).
80+
if (i0 == i1 || i1 == i2 || i0 == i2)
81+
continue;
82+
83+
triList.add((long) i0);
84+
triList.add((long) i1);
85+
triList.add((long) i2);
86+
}
87+
}
88+
89+
if (triList.isEmpty())
90+
throw new IllegalArgumentException("CSG produced no valid triangles after triangulation");
91+
92+
long nVerts = vertexList.size();
93+
long nTris = triList.size() / 3;
94+
95+
// Flatten vertex list into a primitive array.
96+
double[] vertices = new double[(int) (nVerts * 3)];
97+
for (int i = 0; i < nVerts; i++) {
98+
double[] v = vertexList.get(i);
99+
vertices[i * 3] = v[0];
100+
vertices[i * 3 + 1] = v[1];
101+
vertices[i * 3 + 2] = v[2];
102+
}
103+
104+
// Flatten triangle index list.
105+
long[] triangles = new long[triList.size()];
106+
for (int i = 0; i < triList.size(); i++) {
107+
triangles[i] = triList.get(i);
108+
}
109+
110+
return bridge.importMeshGL64(vertices, triangles, nVerts, nTris);
111+
}
112+
113+
// -------------------------------------------------------------------------
114+
// helpers
115+
116+
/**
117+
* Returns the index of {@code v} in {@code vertexList}, inserting it if not already present.
118+
* The key is an exact string representation of (x, y, z) using {@link Double#toHexString}
119+
* so that only bit-identical positions are merged, matching the BSP's behaviour.
120+
*/
121+
private static int intern(Vertex v, Map<String, Integer> index, List<double[]> list) {
122+
String key = Double.toHexString(v.pos.x) + "," + Double.toHexString(v.pos.y) + ","
123+
+ Double.toHexString(v.pos.z);
124+
125+
return index.computeIfAbsent(key, k -> {
126+
int idx = list.size();
127+
list.add(new double[] { v.pos.x, v.pos.y, v.pos.z });
128+
return idx;
129+
});
130+
}
131+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.neuronrobotics.manifold3d;
2+
3+
import eu.mihosoft.vrl.v3d.CSG;
4+
import eu.mihosoft.vrl.v3d.Polygon;
5+
import eu.mihosoft.vrl.v3d.Vector3d;
6+
import eu.mihosoft.vrl.v3d.Vertex;
7+
import eu.mihosoft.vrl.v3d.ext.org.poly2tri.PolygonUtil;
8+
9+
import java.lang.foreign.MemorySegment;
10+
import java.util.ArrayList;
11+
import java.util.Arrays;
12+
import java.util.List;
13+
14+
import com.cadoodlecad.manifold.ManifoldBindings;
15+
import com.cadoodlecad.manifold.ManifoldBindings.MeshData64;
16+
17+
/**
18+
* Imports a manifold3d mesh (via its native bridge API) into a JCSG {@link CSG} object.
19+
*
20+
* <p>Usage:
21+
* <pre>{@code
22+
* Manifold3dBridge bridge = ...; // your wrapper holding the MethodHandle map
23+
* MemorySegment manifoldSeg = bridge.importMeshGL64(vertices, triangles, nVerts, nTris);
24+
*
25+
* CSG csg = new Manifold3dImporter(bridge).fromManifold(manifoldSeg);
26+
* }</pre>
27+
*
28+
* <p>The bridge instance must expose {@code exportMeshGL64(MemorySegment)} returning a
29+
* {@code MeshData64} record with fields {@code double[] vertices}, {@code long[] triangles},
30+
* {@code int vertCount}, and {@code int triCount}.
31+
*/
32+
public class Manifold3dImporter {
33+
34+
35+
36+
private ManifoldBindings bridge;
37+
38+
public Manifold3dImporter(ManifoldBindings bridge) {
39+
if (bridge == null)
40+
throw new IllegalArgumentException("bridge must not be null");
41+
this.bridge = bridge;
42+
}
43+
44+
/**
45+
* Converts a native manifold {@link MemorySegment} to a JCSG {@link CSG}.
46+
*
47+
* <p>The manifold is exported as a triangle soup via the bridge's {@code exportMeshGL64}
48+
* method. Each triangle becomes one JCSG {@link Polygon} (three {@link Vertex} objects
49+
* with positions taken from the flat vertex array). Per-vertex normals are computed
50+
* as the face normal so that JCSG downstream tools (BSP, boolean ops) have valid planes.
51+
*
52+
* @param manifold native manifold segment returned by the bridge import call
53+
* @return a new {@link CSG} representing the same geometry
54+
* @throws Throwable if the native export call fails
55+
* @throws IllegalArgumentException if {@code manifold} is null
56+
*/
57+
public CSG fromManifold(MemorySegment manifold) throws Throwable {
58+
if (manifold == null)
59+
throw new IllegalArgumentException("manifold segment must not be null");
60+
61+
MeshData64 mesh = bridge.exportMeshGL64(manifold);
62+
63+
double[] verts = mesh.vertices(); // flat [x0,y0,z0, x1,y1,z1, ...]
64+
long[] tris = mesh.triangles(); // flat [i0,i1,i2, i3,i4,i5, ...]
65+
int triCount = mesh.triCount();
66+
67+
if (triCount == 0)
68+
return new CSG();
69+
70+
ArrayList<Polygon> polygons = new ArrayList<>(triCount);
71+
72+
for (int t = 0; t < triCount; t++) {
73+
int base = t * 3;
74+
75+
Vector3d p0 = vertexAt(verts, (int) tris[base]);
76+
Vector3d p1 = vertexAt(verts, (int) tris[base + 1]);
77+
Vector3d p2 = vertexAt(verts, (int) tris[base + 2]);
78+
79+
// Face normal (used as the vertex normal for all three corners).
80+
Vector3d edge1 = p1.minus(p0);
81+
Vector3d edge2 = p2.minus(p0);
82+
Vector3d normal = edge1.cross(edge2).normalized();
83+
84+
List<Vertex> vertices = Arrays.asList(new Vertex(p0), new Vertex(p1), new Vertex(p2));
85+
86+
polygons.add(new Polygon(vertices));
87+
}
88+
89+
return CSG.fromPolygons(polygons);
90+
}
91+
92+
// -------------------------------------------------------------------------
93+
// helpers
94+
95+
private static Vector3d vertexAt(double[] verts, int index) {
96+
int base = index * 3;
97+
return new Vector3d(verts[base], verts[base + 1], verts[base + 2]);
98+
}
99+
}

src/main/java/eu/mihosoft/vrl/v3d/CSG.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import com.aparapi.internal.kernel.KernelRunner;
6666
import com.cadoodlecad.manifold.ManifoldBindings;
6767
import com.neuronrobotics.interaction.CadInteractionEvent;
68+
import com.neuronrobotics.manifold3d.CSGManifold3d;
6869

6970
import javafx.scene.paint.Color;
7071
import javafx.scene.paint.PhongMaterial;
@@ -194,7 +195,7 @@ public void progressUpdate(int currentIndex, int finalIndex, String type, CSG in
194195

195196
private int pointsAdded;
196197
private String uniqueId = UUID.randomUUID().toString();
197-
private static ManifoldBindings manifold = null;
198+
private static CSGManifold3d manifold = null;
198199

199200
/**
200201
* Instantiates a new csg.
@@ -2805,7 +2806,7 @@ protected OptType getOptType() {
28052806
public static void setDefaultOptType(OptType optType) {
28062807
if (optType == OptType.Manifold3d) {
28072808
try {
2808-
manifold = new ManifoldBindings();
2809+
manifold = new CSGManifold3d();
28092810
Slice.setSliceEngine(new ISlice() {
28102811

28112812
@Override

0 commit comments

Comments
 (0)