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+ }
0 commit comments