Skip to content

Commit 278fca5

Browse files
committed
adding a basic set of operations
1 parent 2d16bc5 commit 278fca5

2 files changed

Lines changed: 270 additions & 5 deletions

File tree

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ dependencies {
104104
implementation 'org.bouncycastle:bcpkix-jdk18on:1.80'
105105

106106
//manifold 3d
107-
implementation("com.github.madhephaestus:manifold3d:v3.4.1-1")
107+
implementation("com.github.madhephaestus:manifold3d:v3.4.1-2")
108108

109109
}
110110

Lines changed: 269 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,281 @@
11
package com.neuronrobotics.manifold3d;
22

3+
import java.io.File;
4+
import java.lang.foreign.MemorySegment;
5+
36
import com.cadoodlecad.manifold.ManifoldBindings;
47

8+
import eu.mihosoft.vrl.v3d.CSG;
9+
import eu.mihosoft.vrl.v3d.Polygon;
10+
import eu.mihosoft.vrl.v3d.Vertex;
11+
512
public class CSGManifold3d {
613
private final ManifoldBindings manifold;
7-
Manifold3dExporter exporter;
8-
Manifold3dImporter importer;
14+
private final Manifold3dExporter exporter;
15+
private final Manifold3dImporter importer;
16+
917
public CSGManifold3d() throws Exception {
1018
this.manifold = new ManifoldBindings();
1119
exporter = new Manifold3dExporter(manifold);
1220
importer = new Manifold3dImporter(manifold);
1321
}
14-
15-
22+
23+
/**
24+
* Converts a JCSG {@link CSG} into a native manifold {@link MemorySegment}.
25+
*
26+
* <p>The caller is responsible for eventually freeing the returned segment via the
27+
* bridge's delete method (e.g. {@code manifold_delete_manifold}).
28+
*
29+
* @param csg the solid to export; must not be null
30+
* @return a native manifold segment ready for boolean operations
31+
* @throws Throwable if the native import call fails
32+
* @throws IllegalArgumentException if {@code csg} is null or has no polygons
33+
*/
34+
private MemorySegment toManifold(CSG csg) throws Throwable {
35+
return exporter.toManifold(csg);
36+
}
37+
38+
/**
39+
* Converts a native manifold {@link MemorySegment} to a JCSG {@link CSG}.
40+
*
41+
* <p>The manifold is exported as a triangle soup via the bridge's {@code exportMeshGL64}
42+
* method. Each triangle becomes one JCSG {@link Polygon} (three {@link Vertex} objects
43+
* with positions taken from the flat vertex array). Per-vertex normals are computed
44+
* as the face normal so that JCSG downstream tools (BSP, boolean ops) have valid planes.
45+
*
46+
* @param manifold native manifold segment returned by the bridge import call
47+
* @return a new {@link CSG} representing the same geometry
48+
* @throws Throwable if the native export call fails
49+
* @throws IllegalArgumentException if {@code manifold} is null
50+
*/
51+
private CSG fromManifold(MemorySegment manifold) throws Throwable {
52+
return importer.fromManifold(manifold);
53+
}
54+
55+
// -------------------------------------------------------------------------
56+
// Boolean operations
57+
// -------------------------------------------------------------------------
58+
59+
/**
60+
* Returns the union of two CSG solids.
61+
* Uses {@code manifold.union(a, b)} directly (wrapper around
62+
* {@code manifold_union} in the C library).
63+
*/
64+
public CSG union(CSG a, CSG b) throws Throwable {
65+
MemorySegment ma = toManifold(a);
66+
MemorySegment mb = toManifold(b);
67+
try {
68+
MemorySegment result = manifold.union(ma, mb);
69+
return fromManifold(result);
70+
} finally {
71+
manifold.deleteMeshGL64(ma);
72+
manifold.deleteMeshGL64(mb);
73+
}
74+
}
75+
76+
/**
77+
* Returns the difference of two CSG solids (a minus b).
78+
* Uses {@code manifold.difference(a, b)}.
79+
*/
80+
public CSG difference(CSG a, CSG b) throws Throwable {
81+
MemorySegment ma = toManifold(a);
82+
MemorySegment mb = toManifold(b);
83+
try {
84+
MemorySegment result = manifold.difference(ma, mb);
85+
return fromManifold(result);
86+
} finally {
87+
manifold.deleteMeshGL64(ma);
88+
manifold.deleteMeshGL64(mb);
89+
}
90+
}
91+
92+
/**
93+
* Returns the intersection of two CSG solids.
94+
* Uses {@code manifold.intersection(a, b)}.
95+
*/
96+
public CSG intersection(CSG a, CSG b) throws Throwable {
97+
MemorySegment ma = toManifold(a);
98+
MemorySegment mb = toManifold(b);
99+
try {
100+
MemorySegment result = manifold.intersection(ma, mb);
101+
return fromManifold(result);
102+
} finally {
103+
manifold.deleteMeshGL64(ma);
104+
manifold.deleteMeshGL64(mb);
105+
}
106+
}
107+
108+
// -------------------------------------------------------------------------
109+
// Convex hull
110+
// -------------------------------------------------------------------------
111+
112+
/**
113+
* Returns the convex hull of two CSG solids combined.
114+
* <p>
115+
* Manifold's {@code manifold_hull} operates on a single manifold, so we
116+
* first union the two inputs to combine their point sets, then hull the
117+
* result via {@code manifold.hull(MemorySegment)}.
118+
*/
119+
public CSG hull(CSG a) throws Throwable {
120+
MemorySegment ma = toManifold(a);
121+
try {
122+
MemorySegment result = manifold.hull(ma);
123+
return fromManifold(result);
124+
} finally {
125+
manifold.deleteMeshGL64(ma);
126+
}
127+
}
128+
129+
/**
130+
* Convenience overload: convex hull over an arbitrary number of CSG solids.
131+
* Uses {@code manifold.batchHull(MemorySegment[])} which maps to
132+
* {@code manifold_batch_hull}.
133+
*/
134+
public CSG hull(CSG... solids) throws Throwable {
135+
MemorySegment[] segs = new MemorySegment[solids.length];
136+
for (int i = 0; i < solids.length; i++)
137+
segs[i] = toManifold(solids[i]);
138+
try {
139+
MemorySegment result = manifold.batchHull(segs);
140+
return fromManifold(result);
141+
} finally {
142+
for (MemorySegment seg : segs)
143+
manifold.deleteMeshGL64(seg);
144+
}
145+
}
146+
147+
// -------------------------------------------------------------------------
148+
// Slice at a plane → List<Polygon>
149+
// -------------------------------------------------------------------------
150+
151+
// /**
152+
// * Slices a CSG solid at a horizontal plane (Z = height) and returns the
153+
// * resulting cross-section contours as JCSG {@link Polygon}s.
154+
// * <p>
155+
// * Manifold's {@code manifold_slice(mem, m, height)} always cuts perpendicular
156+
// * to the Z axis and returns a {@code ManifoldPolygons*}. Each contour ring
157+
// * is reconstructed here as a JCSG {@link Polygon} lying in the XY plane at
158+
// * {@code z = height}.
159+
// * <p>
160+
// * To cut along an arbitrary plane, rotate the solid so that the desired
161+
// * normal aligns with +Z, call this method, then reverse-rotate the polygons.
162+
// *
163+
// * @param csg the solid to slice
164+
// * @param height Z coordinate of the cutting plane
165+
// * @return cross-section polygons (may be empty for solids that don't reach
166+
// * that height)
167+
// * @throws Throwable if the native call fails
168+
// */
169+
// public List<Polygon> sliceAtZ(CSG csg, double height) throws Throwable {
170+
// MemorySegment m = toManifold(csg);
171+
// try {
172+
// // manifold.slice(MemorySegment m, double height) →
173+
// // ManifoldPolygons* manifold_slice(void* mem, ManifoldManifold* m, double height)
174+
// MemorySegment polygonsSeg = manifold.slice(m, height);
175+
// try {
176+
// return importer.fromManifoldPolygons(polygonsSeg, height);
177+
// } finally {
178+
// manifold.deletePolygons(polygonsSeg);
179+
// }
180+
// } finally {
181+
// manifold.deleteMeshGL64(m);
182+
// }
183+
// }
184+
185+
// -------------------------------------------------------------------------
186+
// STL import / export
187+
// -------------------------------------------------------------------------
188+
189+
// /**
190+
// * Exports a CSG solid to an STL file via Manifold's mesh pipeline.
191+
// * <p>
192+
// * The geometry is round-tripped through Manifold's MeshGL64 representation
193+
// * and written by the {@link Manifold3dSTLExporter} helper.
194+
// * <p>
195+
// * <b>Note:</b> STL is lossy – it has no topology encoding. Prefer 3MF for
196+
// * any round-trip use-case.
197+
// *
198+
// * @param csg the solid to export
199+
// * @param file destination STL file (created or overwritten)
200+
// * @throws Throwable if export or file-write fails
201+
// */
202+
// public void exportSTL(CSG csg, File file) throws Throwable {
203+
// MemorySegment m = toManifold(csg);
204+
// try {
205+
// ManifoldBindings.MeshData64 mesh = manifold.exportMeshGL64(m);
206+
// Manifold3dSTLExporter.write(mesh, file);
207+
// } finally {
208+
// manifold.deleteMeshGL64(m);
209+
// }
210+
// }
211+
//
212+
// /**
213+
// * Imports an STL file and returns its geometry as a CSG solid.
214+
// * <p>
215+
// * The file is parsed by {@link Manifold3dSTLImporter} into raw vertex/triangle
216+
// * arrays that are fed into {@code manifold.importMeshGL64(...)}, which merges
217+
// * duplicate vertices and validates manifoldness before handing back a native
218+
// * manifold segment that is then converted to CSG.
219+
// *
220+
// * @param file the STL file to read
221+
// * @return a new {@link CSG} representing the imported geometry
222+
// * @throws Throwable if parsing or the native import fails
223+
// * @throws IllegalArgumentException if the file does not exist
224+
// */
225+
// public CSG importSTL(File file) throws Throwable {
226+
// if (!file.exists())
227+
// throw new IllegalArgumentException("STL file not found: " + file.getAbsolutePath());
228+
// Manifold3dSTLImporter.RawMesh raw = Manifold3dSTLImporter.read(file);
229+
// MemorySegment m = manifold.importMeshGL64(raw.vertices(), raw.triangles(), raw.vertCount(), raw.triCount());
230+
// try {
231+
// return fromManifold(m);
232+
// } finally {
233+
// manifold.deleteMeshGL64(m);
234+
// }
235+
// }
236+
//
237+
// // -------------------------------------------------------------------------
238+
// // 3MF import / export
239+
// // -------------------------------------------------------------------------
240+
//
241+
// /**
242+
// * Exports a CSG solid to a 3MF file.
243+
// * <p>
244+
// * 3MF preserves topology and is lossless – recommended over STL for any
245+
// * workflow that re-imports the file.
246+
// *
247+
// * @param csg the solid to export
248+
// * @param file destination 3MF file (created or overwritten)
249+
// * @throws Throwable if export or file-write fails
250+
// */
251+
// public void export3MF(CSG csg, File file) throws Throwable {
252+
// MemorySegment m = toManifold(csg);
253+
// try {
254+
// ManifoldBindings.MeshData64 mesh = manifold.exportMeshGL64(m);
255+
// Manifold3d3MFExporter.write(mesh, file);
256+
// } finally {
257+
// manifold.deleteMeshGL64(m);
258+
// }
259+
// }
260+
//
261+
// /**
262+
// * Imports a 3MF file and returns the first body as a CSG solid.
263+
// *
264+
// * @param file the 3MF file to read
265+
// * @return a new {@link CSG} representing the imported geometry
266+
// * @throws Throwable if parsing or the native import fails
267+
// * @throws IllegalArgumentException if the file does not exist
268+
// */
269+
// public CSG import3MF(File file) throws Throwable {
270+
// if (!file.exists())
271+
// throw new IllegalArgumentException("3MF file not found: " + file.getAbsolutePath());
272+
// Manifold3dSTLImporter.RawMesh raw = Manifold3d3MFImporter.read(file);
273+
// MemorySegment m = manifold.importMeshGL64(raw.vertices(), raw.triangles(), raw.vertCount(), raw.triCount());
274+
// try {
275+
// return fromManifold(m);
276+
// } finally {
277+
// manifold.deleteMeshGL64(m);
278+
// }
279+
// }
280+
16281
}

0 commit comments

Comments
 (0)