diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 1721770f..582eb55f 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -42,9 +42,6 @@ jobs: - name: Pull a JavaFX JDK run: wget https://cdn.azul.com/zulu/bin/zulu21.46.19-ca-fx-jdk21.0.9-linux_aarch64.tar.gz - - name: After JDK download, list directory contents - run: pwd; ls -la - - name: Set Java uses: actions/setup-java@v1 with: @@ -119,9 +116,6 @@ jobs: - name: Pull a JavaFX JDK run: wget https://cdn.azul.com/zulu/bin/zulu21.46.19-ca-fx-jdk21.0.9-linux_x64.tar.gz - - name: After JDK download, list directory contnts - run: pwd; ls -la - - name: Set Java uses: actions/setup-java@v1 with: diff --git a/box.svg b/box.svg index cbd7f4bd..58c19fa8 100644 --- a/box.svg +++ b/box.svg @@ -1,85 +1,85 @@ - - - -ABox - - -Box - ABox -2024-10-27 21:50:41 -https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en&render=1 -https://boxes.hackerspace-bamberg.de/ABox -A simple Box - -This box is kept simple on purpose. If you need more features have a look at the UniversalBox. - -Created with Boxes.py (https://boxes.hackerspace-bamberg.de/) -Command line: boxes ABox --FingerJoint_style=rectangular --FingerJoint_surroundingspaces=2.0 --FingerJoint_bottom_lip=0.0 --FingerJoint_edge_width=1.0 --FingerJoint_extra_length=0.0 --FingerJoint_finger=2.0 --FingerJoint_play=0.0 --FingerJoint_space=2.0 --FingerJoint_width=1.0 --Lid_handle=none --Lid_style=none --Lid_handle_height=8.0 --Lid_height=4.0 --Lid_play=0.1 --x=100.0 --y=100.0 --h=100.0 --outside=0 --outside=1 --bottom_edge=h --thickness=3.0 --format=svg --tabs=0.0 --qr_code=0 --debug=0 --labels=0 --labels=1 --reference=100.0 --inner_corners=loop --burn=0.1 -Command line short: boxes ABox -Url: https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en&render=1 -Url short: https://boxes.hackerspace-bamberg.de/ABox -SettingsUrl: https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en -SettingsUrl short: https://boxes.hackerspace-bamberg.de/ABox - - - - - 100.0mm, burn:0.10mm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + +ABox + + +Box - ABox +2024-10-27 21:50:41 +https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en&render=1 +https://boxes.hackerspace-bamberg.de/ABox +A simple Box + +This box is kept simple on purpose. If you need more features have a look at the UniversalBox. + +Created with Boxes.py (https://boxes.hackerspace-bamberg.de/) +Command line: boxes ABox --FingerJoint_style=rectangular --FingerJoint_surroundingspaces=2.0 --FingerJoint_bottom_lip=0.0 --FingerJoint_edge_width=1.0 --FingerJoint_extra_length=0.0 --FingerJoint_finger=2.0 --FingerJoint_play=0.0 --FingerJoint_space=2.0 --FingerJoint_width=1.0 --Lid_handle=none --Lid_style=none --Lid_handle_height=8.0 --Lid_height=4.0 --Lid_play=0.1 --x=100.0 --y=100.0 --h=100.0 --outside=0 --outside=1 --bottom_edge=h --thickness=3.0 --format=svg --tabs=0.0 --qr_code=0 --debug=0 --labels=0 --labels=1 --reference=100.0 --inner_corners=loop --burn=0.1 +Command line short: boxes ABox +Url: https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en&render=1 +Url short: https://boxes.hackerspace-bamberg.de/ABox +SettingsUrl: https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en +SettingsUrl short: https://boxes.hackerspace-bamberg.de/ABox + + + + + 100.0mm, burn:0.10mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 13518b8e..683cca9a 100644 --- a/build.gradle +++ b/build.gradle @@ -10,14 +10,17 @@ spotless { java { lineEndings = com.diffplug.spotless.LineEnding.UNIX // Eclipse formatter with your config file - eclipse('4.26') // Uses Eclipse's built-in default profile — no XML needed! + eclipse('4.26') // Uses Eclipse's built-in default profile — no XML needed! // Optional but recommended additions: removeUnusedImports() trimTrailingWhitespace() endWithNewline() } } - +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} File buildDir = file("."); Properties props = new Properties() props.load(new FileInputStream(buildDir.getAbsolutePath()+"/src/main/resources/com/neuronrobotics/javacad/build.properties")) @@ -25,41 +28,7 @@ group = "com.neuronrobotics" archivesBaseName = "JavaCad" version = props."app.version" -// BEGIN AI SLOP - -//nexusStaging { -// serverUrl = "https://oss.sonatype.org/service/local/" -// username = System.getenv("MAVEN_USERNAME") -// password = System.getenv("MAVEN_PASSWORD") -// packageGroup = "com.neuronrobotics" // Replace with your actual package group -//} - -//task closeAndReleaseSeparately { -// dependsOn tasks.releaseRepository -//} - -//tasks.releaseRepository.dependsOn tasks.closeRepository -//tasks.closeRepository.dependsOn tasks.getStagingProfile - -// Optional: Add this if you want to see more information during the execution -//tasks.getStagingProfile.logging.level = LogLevel.INFO -//tasks.closeRepository.logging.level = LogLevel.INFO -//tasks.releaseRepository.logging.level = LogLevel.INFO - -//tasks.getStagingProfile.doFirst { -// println "Executing getStagingProfile task" -//} - -//tasks.closeRepository.doFirst { -// println "Executing closeRepository task" -//} -// -//tasks.releaseRepository.doFirst { -// println "Executing releaseRepository task" -//} - -// END AI SLOP -sourceCompatibility = '1.8' + [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' //apply from: 'http://gradle-plugins.mihosoft.eu/latest/vlicenseheader.gradle' @@ -74,6 +43,7 @@ task sourcesJar(type: Jar) { repositories { mavenCentral() mavenLocal() + maven { url "https://clojars.org/repo" } } // javadoc is way too strict for my taste. @@ -130,8 +100,11 @@ dependencies { implementation 'com.aparapi:aparapi:3.0.2' //SSL for server -implementation 'org.bouncycastle:bcprov-jdk18on:1.80' -implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' + implementation 'org.bouncycastle:bcprov-jdk18on:1.80' + implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' + + //manifold 3d + implementation("com.github.madhephaestus:manifold3d:v3.4.1-13") } @@ -141,9 +114,19 @@ ext { buildTime = new java.text.SimpleDateFormat('HH:mm:ss.SSSZ').format(buildTimeAndDate) } +tasks.withType(JavaCompile).configureEach { + options.compilerArgs += ['--enable-preview'] +} + +tasks.withType(Test).configureEach { + jvmArgs '--enable-preview' +} +tasks.withType(JavaExec).configureEach { + jvmArgs '--enable-preview' +} test { - dependsOn 'spotlessCheck' + dependsOn 'spotlessApply' testLogging { // Show which test is running events "passed", "skipped", "failed", "standardOut", "standardError" diff --git a/gradlew.bat b/gradlew.bat index 9b42019c..9d21a218 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,94 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java new file mode 100644 index 00000000..700199fe --- /dev/null +++ b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java @@ -0,0 +1,293 @@ +package com.neuronrobotics.manifold3d; + +import java.lang.foreign.MemorySegment; +import java.util.ArrayList; +import java.util.List; + +import com.cadoodlecad.manifold.ManifoldBindings; +import com.cadoodlecad.manifold.ManifoldBindings.ManifoldError; +import com.cadoodlecad.manifold.ManifoldBindings.MeshData64; + +import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.Polygon; +import eu.mihosoft.vrl.v3d.Transform; +import eu.mihosoft.vrl.v3d.Vector3d; +import eu.mihosoft.vrl.v3d.Vertex; +import javafx.scene.paint.Color; + +public class CSGManifold3d { + private final ManifoldBindings manifold; + // private final Manifold3dExporter exporter; + // private final Manifold3dImporter importer; + + public CSGManifold3d() throws Exception { + this.manifold = new ManifoldBindings(); + // exporter = new Manifold3dExporter(manifold); + // importer = new Manifold3dImporter(manifold); + } + + /** + * Converts a JCSG {@link CSG} into a native manifold {@link MemorySegment}. + * + *

+ * The caller is responsible for eventually freeing the returned segment via the + * bridge's delete method (e.g. {@code manifold_delete_manifold}). + * + * @param csg + * the solid to export; must not be null + * @return a native manifold segment ready for boolean operations + * @throws Throwable + * if the native import call fails + * @throws IllegalArgumentException + * if {@code csg} is null or has no polygons + */ + public MemorySegment toManifold(CSG csg) throws Throwable { + if (csg == null) + throw new IllegalArgumentException("csg must not be null"); + + double[] vertices = csg.getVertices(); + + long[] triangles = csg.getTriangles(); + MemorySegment ms = manifold.importMeshGL64(vertices, triangles, vertices.length, triangles.length); + checkResult(ms); + return ms; + } + + /** + * Converts a native manifold {@link MemorySegment} to a JCSG {@link CSG}. + * + *

+ * The manifold is exported as a triangle soup via the bridge's + * {@code exportMeshGL64} method. Each triangle becomes one JCSG {@link Polygon} + * (three {@link Vertex} objects with positions taken from the flat vertex + * array). Per-vertex normals are computed as the face normal so that JCSG + * downstream tools (BSP, boolean ops) have valid planes. + * + * @param ms + * native manifold segment returned by the bridge import call + * @return a new {@link CSG} representing the same geometry + * @throws Throwable + * if the native export call fails + * @throws IllegalArgumentException + * if {@code manifold} is null + */ + public CSG fromManifold(MemorySegment ms, Color c) throws Throwable { + if (ms == null) + throw new IllegalArgumentException("manifold segment must not be null"); + MeshData64 mesh = this.manifold.exportMeshGL64(ms); + + double[] verts = mesh.vertices(); // flat [x0,y0,z0, x1,y1,z1, ...] + long[] tris = mesh.triangles(); // flat [i0,i1,i2, i3,i4,i5, ...] + int triCount = mesh.triCount(); + + if (triCount == 0) + return new CSG(); + + return new CSG(verts, tris, c); + } + + /** + * Slices the given CSG at Z=0 and returns the resulting cross-section as a list + * of JCSG {@link Polygon} objects. + * + *

+ * Each polygon is a flat contour in the Z=0 plane. Outer contours are wound + * counter-clockwise; holes are wound clockwise — matching the winding order + * that manifold_slice produces. + * + *

+ * Contours with fewer than 3 vertices are skipped because they cannot form a + * valid polygon. + * + * @param csg + * the solid to slice + * @return closed polygon contours of the cross-section at Z=0, never + * {@code null}, may be empty if the plane misses the solid + * @throws Throwable + * @throws RuntimeException + * wrapping any native call failure + */ + public ArrayList sliceAtZero(CSG incoming, Transform slicePlane) throws Throwable { + CSG csg = incoming.transformed(slicePlane.inverse()); + MemorySegment csgm = null; + try { + csgm = toManifold(csg); + checkResult(csgm); + List contours = manifold.slice(csgm, 0.0); + + ArrayList result = new ArrayList<>(contours.size()); + + for (double[][] contour : contours) { + // Need at least 3 vertices to define a plane and a valid polygon. + if (contour.length < 3) { + continue; + } + + ArrayList points = new ArrayList<>(contour.length); + for (double[] xy : contour) { + // Z=0 because this is a cross-section at height 0. + points.add(Vector3d.xyz(xy[0], xy[1], 0.0)); + } + + result.add(Polygon.fromPoints(points)); + } + + return result; + + } catch (Throwable e) { + if (csgm != null) + manifold.delete(csgm); + throw new RuntimeException("Failed to slice CSG at Z=0", e); + } + } + + // ------------------------------------------------------------------------- + // Boolean operations + // ------------------------------------------------------------------------- + + /** + * Returns the union of two CSG solids. Uses {@code manifold.union(a, b)} + * directly (wrapper around {@code manifold_union} in the C library). + */ + public CSG union(CSG a, CSG b) throws Throwable { + MemorySegment ma = toManifold(a); + MemorySegment mb = toManifold(b); + try { + MemorySegment result = manifold.union(ma, mb); + checkResult(result); + CSG fromManifold = fromManifold(result, b.getColor()); + manifold.delete(result); + return fromManifold; + } finally { + manifold.delete(ma); + manifold.delete(mb); + } + } + + /** + * Returns the difference of two CSG solids (a minus b). Uses + * {@code manifold.difference(a, b)}. + */ + public CSG difference(CSG a, CSG b) throws Throwable { + MemorySegment ma = toManifold(a); + MemorySegment mb = toManifold(b); + try { + MemorySegment result = manifold.difference(ma, mb); + checkResult(result); + CSG fromManifold = fromManifold(result, a.getColor()); + manifold.delete(result); + return fromManifold; + } finally { + manifold.delete(ma); + manifold.delete(mb); + } + } + + /** + * Returns the intersection of two CSG solids. Uses + * {@code manifold.intersection(a, b)}. + */ + public CSG intersection(CSG a, CSG b) throws Throwable { + MemorySegment ma = toManifold(a); + MemorySegment mb = toManifold(b); + try { + MemorySegment result = manifold.intersection(ma, mb); + checkResult(result); + CSG fromManifold = fromManifold(result, a.getColor()); + manifold.delete(result); + return fromManifold; + } finally { + manifold.delete(ma); + manifold.delete(mb); + } + } + + // ------------------------------------------------------------------------- + // Convex hull + // ------------------------------------------------------------------------- + + /** + * Returns the convex hull of two CSG solids combined. + *

+ * Manifold's {@code manifold_hull} operates on a single manifold, so we first + * union the two inputs to combine their point sets, then hull the result via + * {@code manifold.hull(MemorySegment)}. + */ + public CSG hull(CSG a) throws Throwable { + MemorySegment ma = toManifold(a); + checkResult(ma); + try { + MemorySegment result = manifold.hull(ma); + checkResult(result); + CSG fromManifold = fromManifold(result, a.getColor()); + manifold.delete(result); + return fromManifold; + } finally { + manifold.delete(ma); + } + } + + /** + * Convenience overload: convex hull over an arbitrary number of CSG solids. + * Uses {@code manifold.batchHull(MemorySegment[])} which maps to + * {@code manifold_batch_hull}. + */ + public CSG hull(CSG... solids) throws Throwable { + MemorySegment[] segs = new MemorySegment[solids.length]; + for (int i = 0; i < solids.length; i++) { + segs[i] = toManifold(solids[i]); + } + try { + MemorySegment result = manifold.batchHull(segs); + checkResult(result); + return fromManifold(result, solids[0].getColor()); + } finally { + for (MemorySegment seg : segs) + manifold.delete(seg); + } + } + + private void checkResult(MemorySegment... memorySegments) throws Throwable { + for (int i = 0; i < memorySegments.length; i++) { + MemorySegment ms = memorySegments[i]; + ManifoldError err = manifold.status(ms); + // System.out.println("Status of Manifold Op is "+result); + if (err != ManifoldError.NO_ERROR) { + System.out.println("Status: " + err); + System.out.println("Verts: " + manifold.numVert(ms)); + System.out.println("Tris: " + manifold.numTri(ms)); + System.out.println("Genus: " + manifold.genus(ms)); + throw new NonManifoldShapeError("Error was " + err); + } else { + // System.out.println("Manifold check ok!"); + } + } + } + + public CSG hull(List points) throws Throwable { + ArrayList pts = new ArrayList(); + for (int i = 0; i < points.size(); i++) { + Vector3d v = points.get(i); + double[] p = new double[]{v.x, v.y, v.z}; + pts.add(p); + } + MemorySegment mem = null; + try { + mem = manifold.hull(pts); + checkResult(mem); + + CSG fromManifold = fromManifold(mem, CSG.getDefaultColor()); + manifold.delete(mem); + mem = null; + return fromManifold; + } finally { + if (mem != null) + manifold.delete(mem); + } + } + + public void delete(MemorySegment back) throws Throwable { + manifold.delete(back); + } + +} diff --git a/src/main/java/com/neuronrobotics/manifold3d/NonManifoldShapeError.java b/src/main/java/com/neuronrobotics/manifold3d/NonManifoldShapeError.java new file mode 100644 index 00000000..802df317 --- /dev/null +++ b/src/main/java/com/neuronrobotics/manifold3d/NonManifoldShapeError.java @@ -0,0 +1,11 @@ +package com.neuronrobotics.manifold3d; + +public class NonManifoldShapeError extends Exception { + + private static final long serialVersionUID = 9171271069943603762L; + + public NonManifoldShapeError(String string) { + super(string); + } + +} diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index 0dd00f90..5cf475d8 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -41,7 +41,9 @@ import eu.mihosoft.vrl.v3d.parametrics.LengthParameter; import eu.mihosoft.vrl.v3d.parametrics.Parameter; +import java.io.File; import java.io.Serializable; +import java.lang.foreign.MemorySegment; import java.lang.reflect.Field; import java.time.Duration; import java.util.ArrayList; @@ -50,7 +52,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; -import java.util.Objects; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -63,6 +65,8 @@ import com.aparapi.Range; import com.aparapi.internal.kernel.KernelRunner; import com.neuronrobotics.interaction.CadInteractionEvent; +import com.neuronrobotics.manifold3d.CSGManifold3d; +import com.neuronrobotics.manifold3d.NonManifoldShapeError; import javafx.scene.paint.Color; import javafx.scene.paint.PhongMaterial; @@ -141,6 +145,20 @@ public class CSG implements IuserAPI, Serializable { transient private static HashMap regenerate = new HashMap(); transient private static HashMap manipulator = new HashMap(); + /** + * The Enum OptType. + */ + public static enum OptType { + + /** The csg bound. */ + CSG_BOUND, + + Manifold3d, + + /** The none. */ + NONE + } + transient private static OptType defaultOptType = OptType.CSG_BOUND; transient private static String defaultcolor = "#007956"; // private boolean triangulated; @@ -159,13 +177,10 @@ public void progressUpdate(int currentIndex, int finalIndex, String type, CSG in transient private static ForkJoinPool poolGlobal = null; /** The polygons. */ - private ArrayList polygons; + // private ArrayList polygons; /** The default opt type. */ - /** The opt type. */ - private OptType optType = null; - /** The storage. */ private PropertyStorage str; private PropertyStorage assembly; @@ -192,6 +207,10 @@ public void progressUpdate(int currentIndex, int finalIndex, String type, CSG in private int pointsAdded; private String uniqueId = UUID.randomUUID().toString(); + private static CSGManifold3d manifold = null; + + private double[] vertices; + private long[] triangles; /** * Instantiates a new csg. @@ -205,6 +224,167 @@ public CSG() { } } + public CSG(double[] vertices, long[] triangles, Color c) { + this(); + this.vertices = vertices; + this.triangles = triangles; + setColor(c); + } + + public CSG(ArrayList polygons) throws ColinearPointsException { + this(); + processPolygonsToTriangles(polygons); + + } + + public ArrayList generatePolygonsFromMesh() throws ColinearPointsException { + if (getTriCount() == 0) + return new ArrayList<>(); + + ArrayList polygons = new ArrayList(); + + for (long t = 0; t < getTriCount(); t++) { + try { + polygons.add(getPolygonByIndex((int) t)); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + return polygons; + } + + private static Vector3d vertexAt(double[] verts, long index) { + long base = index * 3; + return new Vector3d(verts[(int) base], verts[(int) (base + 1)], verts[(int) (base + 2)]); + } + + public Vector3d vertexAt(long i) { + return vertexAt(getVertices(), i); + } + + public List getPoints() { + List points = new ArrayList(); + for (int i = 0; i < getVertCount(); i++) + points.add(vertexAt(i)); + return points; + } + + public Polygon getPolygonByIndex(int faceIndex) throws ColinearPointsException { + List points = new ArrayList(); + points.add(new Vertex(vertexAt(getTriangles()[faceIndex * 3]))); + points.add(new Vertex(vertexAt(getTriangles()[faceIndex * 3 + 1]))); + points.add(new Vertex(vertexAt(getTriangles()[faceIndex * 3 + 2]))); + Polygon polygon = new Polygon(points); + polygon.setColor(getColor()); + return polygon; + } + + public CSG processPolygonsToTriangles(ArrayList polygons) throws ColinearPointsException { + // Build an indexed triangle mesh. + // Use a tolerance-free exact key so we don't merge + // numerically-close-but-distinct verts. + Map vertexIndex = new HashMap<>(); + List vertexList = new ArrayList<>(); + List triList = new ArrayList<>(); + + for (Polygon incoming : polygons) { + for (Polygon poly : PolygonUtil.triangulatePolygon(incoming)) { + List pverts = poly.getVertices(); + if (pverts == null || pverts.size() != 3) + continue; + int i0 = intern(pverts.get(0), vertexIndex, vertexList); + int i1 = intern(pverts.get(1), vertexIndex, vertexList); + int i2 = intern(pverts.get(2), vertexIndex, vertexList); + + // Skip degenerate triangles (two or more identical indices). + if (i0 == i1 || i1 == i2 || i0 == i2) + continue; + + triList.add((long) i0); + triList.add((long) i1); + triList.add((long) i2); + + } + } + + if (triList.isEmpty()) { + vertices = new double[0]; + triangles = new long[0]; + + return this; + } + + // Flatten vertex list into a primitive array. + vertices = new double[(int) (vertexList.size() * 3)]; + for (int i = 0; i < getVertCount(); i++) { + Vector3d v = vertexList.get(i); + getVertices()[i * 3] = v.x; + getVertices()[i * 3 + 1] = v.y; + getVertices()[i * 3 + 2] = v.z; + } + + // Flatten triangle index list. + triangles = new long[triList.size()]; + for (int i = 0; i < getTriangles().length; i++) { + getTriangles()[i] = triList.get(i); + } + return this; + } + + /** + * Returns the index of {@code v} in {@code vertexList}, inserting it if not + * already present. The key is an exact string representation of (x, y, z) using + * {@link Double#toHexString} so that only bit-identical positions are merged, + * matching the BSP's behavior. + */ + private static int intern(Vertex v, Map index, List list) { + + double precision = 1.0d / POINTS_CONTACT_DISTANCE;// 0.1d/Plane.getEPSILON(); + + long x = Math.round(v.pos.x * precision); + long y = Math.round(v.pos.y * precision); + long z = Math.round(v.pos.z * precision); + + String key = x + "_" + y + "_" + z; + + return index.computeIfAbsent(key, k -> { + int idx = list.size(); + list.add(v.pos.clone()); + return idx; + }); + } + + /** + * Gets the polygons. + * + * @return the polygons of this CSG + */ + public ArrayList getPolygons() { + try { + return generatePolygonsFromMesh(); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return new ArrayList<>(); + } + } + + /** + * Sets the polygons. + * + * @param polygons + * the new polygons + * @throws ColinearPointsException + */ + public CSG setPolygons(ArrayList polygons) throws ColinearPointsException { + processPolygonsToTriangles(polygons); + return this; + } + + public long getNumberOfTriangles() { + return getTriCount(); + } + public CSG setID(CSG dying) { uniqueId = dying.uniqueId; return this; @@ -270,8 +450,8 @@ public CSG setColor(javafx.scene.paint.Color color) { g = color.getGreen(); b = color.getBlue(); o = color.getOpacity(); - for (Polygon p : polygons) - p.setColor(color); + // for (Polygon p : polygons) + // p.setColor(color); return this; } @@ -318,6 +498,7 @@ public CSG setManipulator(javafx.scene.transform.Affine m) { * Gets the mesh. * * @return the mesh + * @throws ColinearPointsException */ public MeshView getMesh() { if (getCurrentMeshView() != null) @@ -330,6 +511,7 @@ public MeshView getMesh() { * Gets the mesh. * * @return the mesh + * @throws ColinearPointsException */ public MeshView newMesh() { @@ -337,7 +519,8 @@ public MeshView newMesh() { MeshView current = meshContainer.getAsMeshViews().get(0); - PhongMaterial m = new PhongMaterial(getColor()); + Color color = getColor(); + PhongMaterial m = new PhongMaterial(color); current.setMaterial(m); boolean hasManipulator = hasManipulator(); @@ -735,65 +918,67 @@ public CSG scale(Number scaleValue) { return this.transformed(new Transform().scale(scaleValue.doubleValue())); } - /** - * Constructs a CSG from a list of {@link Polygon} instances. - * - * @param polygons - * polygons - * @return a CSG instance - */ - public static CSG fromPolygons(ArrayList polygons) { - - CSG csg = new CSG(); - csg.setPolygons(polygons); - return csg; - } - - /** - * Constructs a CSG from the specified {@link Polygon} instances. - * - * @param polygons - * polygons - * @return a CSG instance - */ - public static CSG fromPolygons(Polygon... polygons) { - return fromPolygons(new ArrayList<>(Arrays.asList(polygons))); - } - - /** - * Constructs a CSG from a list of {@link Polygon} instances. - * - * @param storage - * shared storage - * @param polygons - * polygons - * @return a CSG instance - */ - public static CSG fromPolygons(PropertyStorage storage, ArrayList polygons) { - - CSG csg = new CSG(); - csg.setPolygons(polygons); - - csg.setStorage(storage); - - for (Polygon polygon : polygons) { - polygon.setStorage(storage); - } - return csg; - } - - /** - * Constructs a CSG from the specified {@link Polygon} instances. - * - * @param storage - * shared storage - * @param polygons - * polygons - * @return a CSG instance - */ - public static CSG fromPolygons(PropertyStorage storage, Polygon... polygons) { - return fromPolygons(storage, new ArrayList<>(Arrays.asList(polygons))); - } + // /** + // * Constructs a CSG from a list of {@link Polygon} instances. + // * + // * @param polygons + // * polygons + // * @return a CSG instance + // */ + // public static CSG fromPolygons(ArrayList polygons) { + // + // CSG csg = new CSG(); + // csg.setPolygons(polygons); + // return csg; + // } + // + // /** + // * Constructs a CSG from the specified {@link Polygon} instances. + // * + // * @param polygons + // * polygons + // * @return a CSG instance + // */ + // public static CSG fromPolygons(Polygon... polygons) { + // return fromPolygons(new ArrayList<>(Arrays.asList(polygons))); + // } + // + // /** + // * Constructs a CSG from a list of {@link Polygon} instances. + // * + // * @param storage + // * shared storage + // * @param polygons + // * polygons + // * @return a CSG instance + // */ + // public static CSG fromPolygons(PropertyStorage storage, ArrayList + // polygons) { + // + // CSG csg = new CSG(); + // csg.setPolygons(polygons); + // + // csg.setStorage(storage); + // + // for (Polygon polygon : polygons) { + // polygon.setStorage(storage); + // } + // return csg; + // } + // + // /** + // * Constructs a CSG from the specified {@link Polygon} instances. + // * + // * @param storage + // * shared storage + // * @param polygons + // * polygons + // * @return a CSG instance + // */ + // public static CSG fromPolygons(PropertyStorage storage, Polygon... polygons) + // { + // return fromPolygons(storage, new ArrayList<>(Arrays.asList(polygons))); + // } /* * (non-Javadoc) @@ -808,40 +993,7 @@ public CSG clone() { } public CSG cloneShallow() { - ArrayList collect = new ArrayList(); - for (Polygon p : polygons) { - if (p == null) - continue; - try { - Polygon my = p.clone(); - collect.add(my); - } catch (Exception ex) { - - ex.printStackTrace(); - } - } - return CSG.fromPolygons(collect); - } - - /** - * Gets the polygons. - * - * @return the polygons of this CSG - */ - public ArrayList getPolygons() { - return polygons; - } - - /** - * Defines the CSg optimization type. - * - * @param type - * optimization type - * @return this CSG - */ - public CSG optimization(OptType type) { - this.setOptType(type); - return this; + return new CSG(getVertices().clone(), getTriangles().clone(), getColor()); } /** @@ -873,7 +1025,8 @@ public CSG optimization(OptType type) { * @return union of this csg and the specified csg */ public CSG union(CSG csg) { - if (this.polygons.size() > getMinPolygonsForOffloading() || csg.polygons.size() > getMinPolygonsForOffloading()) + if (this.getNumberOfTriangles() > getMinPolygonsForOffloading() + || csg.getNumberOfTriangles() > getMinPolygonsForOffloading()) if (CSGClient.isRunning()) { ArrayList go = new ArrayList(Arrays.asList(this, csg)); try { @@ -886,13 +1039,21 @@ public CSG union(CSG csg) { // triangulate(); // csg.triangulate(); switch (getOptType()) { + case Manifold3d : + try { + return getManifold().union(this, csg); + } catch (Throwable e) { + System.err.println("ERROR failing over to Java Union " + e.getMessage()); + e.printStackTrace(); + } case CSG_BOUND : return _unionCSGBoundsOpt(csg).historySync(this).historySync(csg); - case POLYGON_BOUND : - return _unionPolygonBoundsOpt(csg).historySync(this).historySync(csg); + // case POLYGON_BOUND: + // return _unionPolygonBoundsOpt(csg).historySync(this).historySync(csg); default : // return _unionIntersectOpt(csg); return _unionNoOpt(csg).historySync(this).historySync(csg); + } } @@ -909,16 +1070,26 @@ public CSG union(CSG csg) { * csg * * @return a csg consisting of the polygons of this csg and the specified csg + * @throws ColinearPointsException */ public CSG dumbUnion(CSG csg) { // boolean tri = triangulated && csg.triangulated; CSG result = this.clone(); CSG other = csg.clone(); - result.getPolygons().addAll(other.getPolygons()); - bounds = null; - // result.triangulated = tri; - return result.historySync(other); + ArrayList polygonsFromMesh; + try { + polygonsFromMesh = result.generatePolygonsFromMesh(); + polygonsFromMesh.addAll(other.generatePolygonsFromMesh()); + bounds = null; + // result.triangulated = tri; + return new CSG(polygonsFromMesh).historySync(csg).historySync(this); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return this; + } + } /** @@ -1079,7 +1250,7 @@ public static CSG unionAll(List csgs) { if (CSGClient.isRunning()) { boolean offload = false; for (int i = 0; i < csgs.size(); i++) - if (csgs.get(i).polygons.size() > getMinPolygonsForOffloading()) { + if (csgs.get(i).getNumberOfTriangles() > getMinPolygonsForOffloading()) { offload = true; break; } @@ -1115,28 +1286,15 @@ public static CSG hullAll(List csgs) { * @return the convex hull of this csg and the specified csgs */ public CSG hull(List csgs) { + ArrayList points = new ArrayList(); - CSG csgsUnion = new CSG(); - // csgsUnion.setStorage(storage); - csgsUnion.optType = optType; - csgsUnion.setPolygons(this.clone().getPolygons()); - - csgs.stream().forEach((csg) -> { - csgsUnion.getPolygons().addAll(csg.clone().getPolygons()); - csgsUnion.historySync(csg); - }); - - csgsUnion.getPolygons().forEach(p -> p.setStorage(getStorage())); - bounds = null; - return csgsUnion.hull(); + for (CSG c : csgs) { + for (int i = 0; i < c.getVertCount(); i++) { + points.add(vertexAt(c.getVertices(), i)); + } + } - // CSG csgsUnion = this; - // - // for (CSG csg : csgs) { - // csgsUnion = csgsUnion.union(csg); - // } - // - // return csgsUnion.hull(); + return HullUtil.hull(points, str); } /** @@ -1161,7 +1319,13 @@ public CSG hull(CSG... csgs) { private CSG _unionCSGBoundsOpt(CSG csg) { // com.neuronrobotics.sdk.common.Log.error("WARNING: using " + CSG.OptType.NONE // + " since other optimization types missing for union operation."); - return _unionIntersectOpt(csg); + try { + return _unionIntersectOpt(csg); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return this; + } } /** @@ -1171,38 +1335,38 @@ private CSG _unionCSGBoundsOpt(CSG csg) { * the csg * @return the csg */ - private CSG _unionPolygonBoundsOpt(CSG csg) { - ArrayList inner = new ArrayList<>(); - ArrayList outer = new ArrayList<>(); - - Bounds b = csg.getBounds(); - - this.getPolygons().stream().forEach((p) -> { - if (b.intersects(p.getBounds())) { - inner.add(p); - } else { - outer.add(p); - } - }); - - ArrayList allPolygons = new ArrayList<>(); - - if (!inner.isEmpty()) { - CSG innerCSG = CSG.fromPolygons(inner); - - allPolygons.addAll(outer); - allPolygons.addAll(innerCSG._unionNoOpt(csg).getPolygons()); - } else { - allPolygons.addAll(this.getPolygons()); - allPolygons.addAll(csg.getPolygons()); - } - bounds = null; - CSG back = CSG.fromPolygons(allPolygons).optimization(getOptType()); - if (getName().length() != 0 && csg.getName().length() != 0) { - back.setName(name); - } - return back; - } + // private CSG _unionPolygonBoundsOpt(CSG csg) { + // ArrayList inner = new ArrayList<>(); + // ArrayList outer = new ArrayList<>(); + // + // Bounds b = csg.getBounds(); + // + // this.getPolygons().stream().forEach((p) -> { + // if (b.intersects(p.getBounds())) { + // inner.add(p); + // } else { + // outer.add(p); + // } + // }); + // + // ArrayList allPolygons = new ArrayList<>(); + // + // if (!inner.isEmpty()) { + // CSG innerCSG = CSG.fromPolygons(inner); + // + // allPolygons.addAll(outer); + // allPolygons.addAll(innerCSG._unionNoOpt(csg).getPolygons()); + // } else { + // allPolygons.addAll(this.getPolygons()); + // allPolygons.addAll(csg.getPolygons()); + // } + // bounds = null; + // CSG back = CSG.fromPolygons(allPolygons).optimization(getOptType()); + // if (getName().length() != 0 && csg.getName().length() != 0) { + // back.setName(name); + // } + // return back; + // } /** * Optimizes for intersection. If csgs do not intersect create a new csg that @@ -1212,32 +1376,26 @@ private CSG _unionPolygonBoundsOpt(CSG csg) { * @param csg * csg * @return the union of this csg and the specified csg + * @throws ColinearPointsException */ - private CSG _unionIntersectOpt(CSG csg) { + private CSG _unionIntersectOpt(CSG csg) throws ColinearPointsException { boolean intersects = false; Bounds bounds = csg.getBounds(); - for (Polygon p : getPolygons()) { + for (Polygon p : generatePolygonsFromMesh()) { if (bounds.intersects(p.getBounds())) { intersects = true; break; } } - ArrayList allPolygons = new ArrayList<>(); - if (intersects) { return _unionNoOpt(csg); } else { - allPolygons.addAll(this.getPolygons()); - allPolygons.addAll(csg.getPolygons()); - } - CSG back = CSG.fromPolygons(allPolygons).optimization(getOptType()); - if (getName().length() != 0 && csg.getName().length() != 0) { - back.setName(name); + return dumbUnion(csg); } - return back; + } /** @@ -1249,21 +1407,24 @@ private CSG _unionIntersectOpt(CSG csg) { * @throws Exception */ private CSG _unionNoOpt(CSG csg) { - if (this.getPolygons().size() == 0) + if (this.getNumberOfTriangles() == 0) return csg.clone(); - if (csg.getPolygons().size() == 0) + if (csg.getNumberOfTriangles() == 0) return this.clone(); Node a; try { - a = new Node(this.clone().getPolygons(), this.getPolygons().get(0).getPlane()); - Node b = new Node(csg.clone().getPolygons(), csg.getPolygons().get(0).getPlane()); + ArrayList thisPoly = generatePolygonsFromMesh(); + ArrayList otherPoly = csg.generatePolygonsFromMesh(); + + a = new Node(thisPoly, thisPoly.get(0).getPlane()); + Node b = new Node(otherPoly, otherPoly.get(0).getPlane()); a.clipTo(b); b.clipTo(a); b.invert(); b.clipTo(a); b.invert(); a.build(b.allPolygons()); - CSG back = CSG.fromPolygons(a.allPolygons()).optimization(getOptType()); + CSG back = new CSG(a.allPolygons()); if (getName().length() != 0 && csg.getName().length() != 0) { back.setName(name); } @@ -1391,7 +1552,8 @@ public CSG difference(CSG... csgs) { * @return difference of this csg and the specified csg */ public CSG difference(CSG csg) { - if (this.polygons.size() > getMinPolygonsForOffloading() || csg.polygons.size() > getMinPolygonsForOffloading()) + if (this.getNumberOfTriangles() > getMinPolygonsForOffloading() + || csg.getNumberOfTriangles() > getMinPolygonsForOffloading()) if (CSGClient.isRunning()) { ArrayList go = new ArrayList(Arrays.asList(this, csg)); try { @@ -1407,42 +1569,26 @@ public CSG difference(CSG csg) { // Check to see if a CSG operation is attempting to difference with // no // polygons - if (this.getPolygons().size() > 0 && csg.getPolygons().size() > 0) { + if (this.getNumberOfTriangles() > 0 && csg.getNumberOfTriangles() > 0) { switch (getOptType()) { + case Manifold3d : + try { + return getManifold().difference(this, csg); + } catch (Throwable e) { + System.err.println("ERROR failing over to Java Difference " + e.getMessage()); + e.printStackTrace(); + } case CSG_BOUND : return _differenceCSGBoundsOpt(csg).historySync(this).historySync(csg); - case POLYGON_BOUND : - return _differencePolygonBoundsOpt(csg).historySync(this).historySync(csg); default : return _differenceNoOpt(csg).historySync(this).historySync(csg); + } } else return this; } catch (Exception ex) { - // ex.printStackTrace(); - try { - // com.neuronrobotics.sdk.common.Log.error("CSG difference failed, performing - // workaround"); - // ex.printStackTrace(); - CSG intersectingParts = csg.intersect(this); - - if (intersectingParts.getPolygons().size() > 0) { - switch (getOptType()) { - case CSG_BOUND : - return _differenceCSGBoundsOpt(intersectingParts).historySync(this) - .historySync(intersectingParts); - case POLYGON_BOUND : - return _differencePolygonBoundsOpt(intersectingParts).historySync(this) - .historySync(intersectingParts); - default : - return _differenceNoOpt(intersectingParts).historySync(this).historySync(intersectingParts); - } - } else - return this; - } catch (Exception e) { - e.printStackTrace(); - return this; - } + ex.printStackTrace(); + return this; } } @@ -1459,8 +1605,14 @@ private CSG _differenceCSGBoundsOpt(CSG csg) { CSG a2 = this.intersect(csg.getBounds().toCSG()); CSG result = null; - if (a2.getPolygons().size() > 0) - result = a2._differenceNoOpt(csg)._unionIntersectOpt(a1).optimization(getOptType()); + if (a2.getNumberOfTriangles() > 0) + try { + result = a2._differenceNoOpt(csg)._unionIntersectOpt(a1); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + result = this; + } else result = a1; if (getName().length() != 0 && csg.getName().length() != 0) { @@ -1477,31 +1629,31 @@ private CSG _differenceCSGBoundsOpt(CSG csg) { * the csg * @return the csg */ - private CSG _differencePolygonBoundsOpt(CSG csg) { - ArrayList inner = new ArrayList<>(); - ArrayList outer = new ArrayList<>(); - - Bounds bounds = csg.getBounds(); - - this.getPolygons().stream().forEach((p) -> { - if (bounds.intersects(p.getBounds())) { - inner.add(p); - } else { - outer.add(p); - } - }); - - CSG innerCSG = CSG.fromPolygons(inner); - - ArrayList allPolygons = new ArrayList<>(); - allPolygons.addAll(outer); - allPolygons.addAll(innerCSG._differenceNoOpt(csg).getPolygons()); - CSG BACK = CSG.fromPolygons(allPolygons).optimization(getOptType()); - if (getName().length() != 0 && csg.getName().length() != 0) { - BACK.setName(name); - } - return BACK; - } + // private CSG _differencePolygonBoundsOpt(CSG csg) { + // ArrayList inner = new ArrayList<>(); + // ArrayList outer = new ArrayList<>(); + // + // Bounds bounds = csg.getBounds(); + // + // this.getPolygons().stream().forEach((p) -> { + // if (bounds.intersects(p.getBounds())) { + // inner.add(p); + // } else { + // outer.add(p); + // } + // }); + // + // CSG innerCSG = CSG.fromPolygons(inner); + // + // ArrayList allPolygons = new ArrayList<>(); + // allPolygons.addAll(outer); + // allPolygons.addAll(innerCSG._differenceNoOpt(csg).getPolygons()); + // CSG BACK = CSG.fromPolygons(allPolygons).optimization(getOptType()); + // if (getName().length() != 0 && csg.getName().length() != 0) { + // BACK.setName(name); + // } + // return BACK; + // } /** * _difference no opt. @@ -1514,8 +1666,10 @@ private CSG _differenceNoOpt(CSG csg) { Node a; try { - a = new Node(this.clone().getPolygons(), this.getPolygons().get(0).getPlane()); - Node b = new Node(csg.clone().getPolygons(), csg.getPolygons().get(0).getPlane()); + ArrayList thisPoly = generatePolygonsFromMesh(); + ArrayList otherPoly = csg.generatePolygonsFromMesh(); + a = new Node(thisPoly, thisPoly.get(0).getPlane()); + Node b = new Node(otherPoly, otherPoly.get(0).getPlane()); a.invert(); a.clipTo(b); @@ -1526,7 +1680,7 @@ private CSG _differenceNoOpt(CSG csg) { a.build(b.allPolygons()); a.invert(); - CSG csgA = CSG.fromPolygons(a.allPolygons()).optimization(getOptType()); + CSG csgA = new CSG(a.allPolygons()); if (getName().length() != 0 && csg.getName().length() != 0) { csgA.setName(name); } @@ -1567,7 +1721,8 @@ private CSG _differenceNoOpt(CSG csg) { * @return intersection of this csg and the specified csg */ public CSG intersect(CSG csg) { - if (this.polygons.size() > getMinPolygonsForOffloading() || csg.polygons.size() > getMinPolygonsForOffloading()) + if (this.getNumberOfTriangles() > getMinPolygonsForOffloading() + || csg.getNumberOfTriangles() > getMinPolygonsForOffloading()) if (CSGClient.isRunning()) { ArrayList go = new ArrayList(Arrays.asList(this, csg)); try { @@ -1577,17 +1732,28 @@ public CSG intersect(CSG csg) { e.printStackTrace(); } } - // triangulate(); - // csg.triangulate(); - if (getPolygons().size() == 0 || csg.getPolygons().size() == 0) { + + if (getNumberOfTriangles() == 0 || csg.getNumberOfTriangles() == 0) { Exception ex = new Exception("Error! Intersection is invalid when one CSG has no polygons!"); ex.printStackTrace(); - return CSG.fromPolygons(new ArrayList()).historySync(this).historySync(csg); + return new CSG().historySync(this).historySync(csg); + } + if (defaultOptType == OptType.Manifold3d) { + try { + return getManifold().intersection(this, csg); + } catch (Throwable e) { + System.err.println("ERROR failing over to Java Intersect " + e.getMessage()); + e.printStackTrace(); + } } + Node a; try { - a = new Node(this.clone().getPolygons(), this.getPolygons().get(0).getPlane()); - Node b = new Node(csg.clone().getPolygons(), csg.getPolygons().get(0).getPlane()); + + ArrayList thisPoly = generatePolygonsFromMesh(); + ArrayList otherPoly = csg.generatePolygonsFromMesh(); + a = new Node(thisPoly, thisPoly.get(0).getPlane()); + Node b = new Node(otherPoly, otherPoly.get(0).getPlane()); a.invert(); b.clipTo(a); b.invert(); @@ -1595,7 +1761,7 @@ public CSG intersect(CSG csg) { b.clipTo(a); a.build(b.allPolygons()); a.invert(); - CSG back = CSG.fromPolygons(a.allPolygons()).optimization(getOptType()).historySync(csg).historySync(this); + CSG back = new CSG(a.allPolygons()).historySync(csg).historySync(this); if (getName().length() != 0 && csg.getName().length() != 0) { back.setName(name); } @@ -1699,12 +1865,51 @@ public CSG intersect(CSG... csgs) { * Returns this csg in STL string format. * * @return this csg in STL string format + * @throws NonManifoldShapeError + * @throws ColinearPointsException + */ + public String toStlString(boolean repair) throws ColinearPointsException, NonManifoldShapeError { + StringBuilder sb = new StringBuilder(); + toStlString(sb, repair); + return sb.toString(); + } + /** + * Returns this csg in STL string format. + * + * @return this csg in STL string format + * @throws NonManifoldShapeError + * @throws ColinearPointsException */ public String toStlString() { StringBuilder sb = new StringBuilder(); - toStlString(sb); + try { + toStlString(sb, true); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NonManifoldShapeError e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } return sb.toString(); } + public CSG to3mf(File target) { + if (defaultOptType == OptType.Manifold3d) { + new RuntimeException("Manifold3d 3mf export not implemented yet").printStackTrace(); + } else { + throw new RuntimeException("Non-Manifold3d 3mf export not implemented yet"); + } + return this; + } + + public static CSG loadFrom3mf(File target) { + if (defaultOptType == OptType.Manifold3d) { + new RuntimeException("Manifold3d 3mf export not implemented yet").printStackTrace(); + } else { + throw new RuntimeException("Non-Manifold3d 3mf export not implemented yet"); + } + return null; + } /** * Returns this csg in STL string format. @@ -1713,50 +1918,59 @@ public String toStlString() { * string builder * * @return the specified string builder + * @throws NonManifoldShapeError + * @throws ColinearPointsException */ - public StringBuilder toStlString(StringBuilder sb) { - triangulate(false); - try { - sb.append("solid v3d.csg\n"); - for (Polygon p : getPolygons()) { - try { - Plane.createFromPoints(p.getVertices(), null); - p.toStlString(sb); - } catch (Exception ex) { - System.out.println("Prune Polygon on export"); - } + public StringBuilder toStlString(StringBuilder sb, boolean repair) + throws ColinearPointsException, NonManifoldShapeError { + + makeManifold(repair); + sb.append("solid v3d.csg\n"); + for (Polygon p : generatePolygonsFromMesh()) { + try { + Plane.createFromPoints(p.getVertices(), null); + p.toStlString(sb); + } catch (Exception ex) { + System.out.println("Prune Polygon on export"); } - sb.append("endsolid v3d.csg\n"); - return sb; - } catch (Throwable t) { - t.printStackTrace(); - throw new RuntimeException(t); } + sb.append("endsolid v3d.csg\n"); + return sb; } - public CSG triangulate() { - return triangulate(false); - } - - public CSG triangulate(boolean fix) { - return triangulate(fix, false); - } - - public CSG snapPoints() { - return triangulate(false, true); - } + // public CSG snapPoints() throws ColinearPointsException { + // return triangulate(false, true); + // } - public CSG triangulate(boolean fix, boolean justSnap) { - // if (fix && needsDegeneratesPruned) - // triangulated = false; - // if (triangulated) - // return this; - if (this.polygons.size() > getMinPolygonsForOffloading() && preventNonManifoldTriangles) + public CSG makeManifold(boolean repair) throws ColinearPointsException, NonManifoldShapeError { + if (getNumberOfTriangles() < 4) + return this; + if (getOptType() == OptType.Manifold3d) { + try { + MemorySegment back = manifold.toManifold(this); + CSG mcsg = manifold.fromManifold(back, this.getColor()); + manifold.delete(back); + vertices = mcsg.vertices; + triangles = mcsg.triangles; + return this; + } catch (NonManifoldShapeError e) { + if (repair) + System.err.println("Shape can not be loaded as manifold, correcting"); + else + throw e; + } catch (Throwable e) { + if (repair) + e.printStackTrace(); + else + throw new RuntimeException(e); + } + } + if (this.getNumberOfTriangles() > getMinPolygonsForOffloading() && preventNonManifoldTriangles) if (CSGClient.isRunning()) { ArrayList go = new ArrayList(Arrays.asList(this)); try { CSG csg = CSGClient.getClient().triangulate(go).get(0); - setPolygons(csg.getPolygons()); + setData(csg); // triangulated = true; return csg; } catch (Exception e) { @@ -1773,22 +1987,21 @@ public CSG triangulate(boolean fix, boolean justSnap) { int added = 0; int itr = 1; if (preventNonManifoldTriangles) { + + ArrayList polygons = generatePolygonsFromMesh(); do { - long np = 0; - int numberOfPolygons = polygons.size(); + long numberOfPolygons = getNumberOfTriangles(); + long np = numberOfPolygons * 3; - for (int i = 0; i < numberOfPolygons; i++) { - np += (polygons.get(i).getVertices().size()); - } int extraSpace = ExtraSpace; long longLength = 1 + np + ((numberOfPolygons + 1) * extraSpace); - if (longLength * 4 > Integer.MAX_VALUE) + if (longLength > Integer.MAX_VALUE) new RuntimeException("Mesh too large to process with integers!").printStackTrace(); else { System.err.println("Processing Mesh Manifold with " + longLength * 4 + " byte buffer"); added = 0; try { - added = runGPUMakeManifold(itr, (int) np, (int) longLength, numberOfPolygons, justSnap); + added = runGPUMakeManifold(itr, np, (int) longLength, numberOfPolygons, polygons); } catch (Exception ex) { ex.printStackTrace(); } @@ -1800,10 +2013,15 @@ public CSG triangulate(boolean fix, boolean justSnap) { } } } while (added < 0 && itr++ < 51); + try { + processPolygonsToTriangles(polygons); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } // } - if (!justSnap) - performTriangulation(); + // System.out.println("Complete Triangulation \n\n"); // now all polygons are definantly triangles // triangulated = true; @@ -1811,25 +2029,19 @@ public CSG triangulate(boolean fix, boolean justSnap) { return this; } - private void performTriangulation() { - ArrayList toAdd = new ArrayList(); - for (int i = 0; i < polygons.size(); i++) { - Polygon p = polygons.get(i); - updatePolygons(toAdd, p); - } - if (toAdd.size() > 0) { - setPolygons(toAdd); - } + private void setData(CSG csg) { + vertices = csg.getVertices().clone(); + triangles = csg.getTriangles().clone(); } - private int runGPUMakeManifold(int iteration, int np, int longLength, int numPoly, boolean justSnap) { + private int runGPUMakeManifold(int iteration, long np, int longLength, long numPoly, ArrayList polygons) { if (iteration < 0 || np <= 0 || longLength <= 0 || numPoly <= 0) throw new RuntimeException("Error none of the dataa lengths can be negative nor 0"); // Flattened approach - more Aparapi-friendly - int numberOfPolygons = numPoly; + int numberOfPolygons = (int) numPoly; int extraSpace = ExtraSpace; int numberOfPointsWithExtra = longLength; - int numberOfPoints = np; + int numberOfPoints = (int) np; // Flattened arrays instead of objects float[] pointDataX = new float[numberOfPoints]; float[] pointDataY = new float[numberOfPoints]; @@ -1916,15 +2128,16 @@ public void run() { } }; - gpuRun(numberOfPoints, snapPointsToDistance, done, "Snap Points Itr(" + iteration + ")", () -> { - tp[0] += snapChunk; - tp[1] += snapChunk; - if (tp[1] > polySizes.length) - tp[1] = polySizes.length; - if (tp[0] < polySizes.length) - return true; - return false; - }, iteration, polySizes.length / snapChunk); + // gpuRun(numberOfPoints, snapPointsToDistance, done, "Snap Points Itr(" + + // iteration + ")", () -> { + // tp[0] += snapChunk; + // tp[1] += snapChunk; + // if (tp[1] > polySizes.length) + // tp[1] = polySizes.length; + // if (tp[0] < polySizes.length) + // return true; + // return false; + // }, iteration, polySizes.length / snapChunk); tp[0] = 0; tp[1] = testPointChunk; HashSet unique = new HashSet(); @@ -2108,41 +2321,41 @@ public void run() { }// Run method }; pointsAdded = 0; - if (!justSnap) { - gpuRun(numberOfPolygons, findNonManifoldPoints, done, "Manifold Itr(" + iteration + ")", () -> { - // for (int tp = 0; tp < uniquePoints.length; tp++) - // Iterate through each of the test points in host thread - tp[0] += testPointChunk; - tp[1] += testPointChunk; - if (tp[1] > uniquePoints.length) - tp[1] = uniquePoints.length; - if (tp[0] < uniquePoints.length) - return true; - pointsAdded = 0; - String out = "points added report ["; - for (int x = 0; x < added.length; x++) { - if (added[x] < 0) { - progressMoniter.progressUpdate(1, 1, - "\n\nManifold failed after " + x + " of " + numberOfPolygons + " polygons ", this); - pointsAdded = -1; - break; - } else { - pointsAdded += added[x]; - if (added[x] > 0 && out.length() < 300) - out += " to " + x + " added " + (added[x]) + " size " + polySizes[x] + " , "; - } - } - out += "]"; - out = "Total added " + pointsAdded + " " + out; - if (pointsAdded > 0) { - progressMoniter.progressUpdate(1, 1, out, this); - // return true; + + gpuRun(numberOfPolygons, findNonManifoldPoints, done, "Manifold Itr(" + iteration + ")", () -> { + // for (int tp = 0; tp < uniquePoints.length; tp++) + // Iterate through each of the test points in host thread + tp[0] += testPointChunk; + tp[1] += testPointChunk; + if (tp[1] > uniquePoints.length) + tp[1] = uniquePoints.length; + if (tp[0] < uniquePoints.length) + return true; + pointsAdded = 0; + String out = "points added report ["; + for (int x = 0; x < added.length; x++) { + if (added[x] < 0) { + progressMoniter.progressUpdate(1, 1, + "\n\nManifold failed after " + x + " of " + numberOfPolygons + " polygons ", this); + pointsAdded = -1; + break; + } else { + pointsAdded += added[x]; + if (added[x] > 0 && out.length() < 300) + out += " to " + x + " added " + (added[x]) + " size " + polySizes[x] + " , "; } - return false; - }, iteration, uniquePoints.length / testPointChunk); - } + } + out += "]"; + out = "Total added " + pointsAdded + " " + out; + if (pointsAdded > 0) { + progressMoniter.progressUpdate(1, 1, out, this); + // return true; + } + return false; + }, iteration, uniquePoints.length / testPointChunk); + ArrayList newPoly = new ArrayList<>(); - for (int i = 0; i < polygons.size(); i++) { + for (int i = 0; i < getNumberOfTriangles(); i++) { Polygon polygon = polygons.get(i); Plane pl = polygon.plane; ArrayList points = new ArrayList(); @@ -2179,7 +2392,7 @@ public void run() { polygon.getPoints().clear(); } polygons.clear(); - polygons = newPoly; + polygons.addAll(newPoly); return pointsAdded; } @@ -2409,9 +2622,14 @@ public CSG color(Color c) { * @param sb * string builder * @return the specified string builder + * @throws NonManifoldShapeError + * @throws ColinearPointsException */ - public StringBuilder toObjString(StringBuilder sb) { - triangulate(true); + public StringBuilder toObjString(StringBuilder sb, boolean repair) + throws ColinearPointsException, NonManifoldShapeError { + + makeManifold(repair); + sb.append("# Group").append("\n"); sb.append("g v3d.csg\n"); sb.append("o " + (name == null || name.length() == 0 ? "CSG Export" : getName()) + "\n"); @@ -2433,20 +2651,25 @@ public PolygonStruct(PropertyStorage storage, List indices, String mate sb.append("\n# Vertices\n"); - for (Polygon p : getPolygons()) { - List polyIndices = new ArrayList<>(); - - p.getVertices().stream().forEach((v) -> { - if (!vertices.contains(v)) { - vertices.add(v); - v.toObjString(sb); - polyIndices.add(vertices.size()); - } else { - polyIndices.add(vertices.indexOf(v) + 1); - } - }); - indices.add(new PolygonStruct(getStorage(), polyIndices, " ")); + try { + for (Polygon p : generatePolygonsFromMesh()) { + List polyIndices = new ArrayList<>(); + + p.getVertices().stream().forEach((v) -> { + if (!vertices.contains(v)) { + vertices.add(v); + v.toObjString(sb); + polyIndices.add(vertices.size()); + } else { + polyIndices.add(vertices.indexOf(v) + 1); + } + }); + indices.add(new PolygonStruct(getStorage(), polyIndices, " ")); + } + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } HashMap mapping = new HashMap(); HashMap mappingTF = new HashMap<>(); @@ -2496,21 +2719,44 @@ public PolygonStruct(PropertyStorage storage, List indices, String mate * Returns this csg in OBJ string format. * * @return this csg in OBJ string format + * @throws NonManifoldShapeError + * @throws ColinearPointsException + */ + public String toObjString(boolean repair) throws ColinearPointsException, NonManifoldShapeError { + StringBuilder sb = new StringBuilder(); + return toObjString(sb, repair).toString(); + } + /** + * Returns this csg in OBJ string format. + * + * @return this csg in OBJ string format + * @throws NonManifoldShapeError + * @throws ColinearPointsException */ public String toObjString() { StringBuilder sb = new StringBuilder(); - return toObjString(sb).toString(); + try { + return toObjString(sb, true).toString(); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NonManifoldShapeError e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return sb.toString(); } /** - * Weighted. - * - * @param f - * the f - * @return the csg + * Reverse the winding order of all the triangles */ - public CSG weighted(WeightFunction f) { - return new Modifier(f).modified(this); + private void flip() { + for (int i = 0; i < getTriCount(); i++) { + long a = triangles[i * 3 + 1]; + long b = triangles[i * 3 + 2]; + triangles[i * 3 + 1] = b; + triangles[i * 3 + 2] = a; + } } /** @@ -2524,23 +2770,40 @@ public CSG weighted(WeightFunction f) { public CSG transformed(Transform transform) { // if( isMotionLock()) // return this.clone(); - if (getPolygons().isEmpty()) { + if (getNumberOfTriangles() == 0) { return clone(); } - - ArrayList newpolygons = this.getPolygons().stream().map(p -> { - try { - return p.transformed(transform); - } catch (Exception e) { - // e.printStackTrace(); - System.err.println("Removing Polygon during transform " + p); - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toCollection(ArrayList::new)); - - CSG csg = CSG.fromPolygons(newpolygons).optimization(getOptType()); - - // csg.setStorage(storage); + CSG csg = clone(); + + for (int i = 0; i < csg.getVertCount(); i++) { + double vectx = csg.getVertices()[i * 3]; + double vecty = csg.getVertices()[i * 3 + 1]; + double vectz = csg.getVertices()[i * 3 + 2]; + double prevX = vectx; + double prevY = vecty; + double prevZ = vectz; + + final double x, y; + x = transform.getInternalMatrix().m00 * vectx + transform.getInternalMatrix().m01 * vecty + + transform.getInternalMatrix().m02 * vectz + transform.getInternalMatrix().m03; + y = transform.getInternalMatrix().m10 * vectx + transform.getInternalMatrix().m11 * vecty + + transform.getInternalMatrix().m12 * vectz + transform.getInternalMatrix().m13; + vectz = transform.getInternalMatrix().m20 * vectx + transform.getInternalMatrix().m21 * vecty + + transform.getInternalMatrix().m22 * vectz + transform.getInternalMatrix().m23; + vectx = x; + vecty = y; + + double diffX = vectx - prevX; + double diffY = vecty - prevY; + double diffZ = vectz - prevZ; + + csg.getVertices()[i * 3] = prevX + (diffX); + csg.getVertices()[i * 3 + 1] = prevY + (diffY); + csg.getVertices()[i * 3 + 2] = prevZ + (diffZ); + } + if (transform.isMirror()) { + csg.flip(); + } if (getName().length() != 0) { csg.setName(name); @@ -2555,6 +2818,7 @@ public CSG transformed(Transform transform) { * @param interact * the interact * @return the mesh container + * @throws ColinearPointsException */ // TODO finish experiment (20.7.2014) public MeshContainer toJavaFXMesh(CadInteractionEvent interact) { @@ -2581,10 +2845,17 @@ public MeshContainer toJavaFXMesh(CadInteractionEvent interact) { * @param interact * the interact * @return the CSG as JavaFX triangle mesh + * @throws ColinearPointsException */ public MeshContainer toJavaFXMeshSimple(CadInteractionEvent interact) { - return CSGtoJavafx.meshFromPolygon(getPolygons()); + try { + return CSGtoJavafx.meshFromPolygon(generatePolygonsFromMesh()); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return CSGtoJavafx.meshFromPolygon(new ArrayList<>()); + } } /** @@ -2596,10 +2867,6 @@ public MeshContainer toJavaFXMeshSimple(CadInteractionEvent interact) { public Bounds getBounds() { if (bounds != null) return bounds; - if (getPolygons().isEmpty()) { - bounds = new Bounds(Vector3d.ZERO, Vector3d.ZERO); - return bounds; - } double minX = Double.POSITIVE_INFINITY; double minY = Double.POSITIVE_INFINITY; @@ -2609,35 +2876,35 @@ public Bounds getBounds() { double maxY = Double.NEGATIVE_INFINITY; double maxZ = Double.NEGATIVE_INFINITY; - for (Polygon p : getPolygons()) { + // for (Polygon p : getPolygons()) { - for (int i = 0; i < p.getVertices().size(); i++) { + for (int i = 0; i < getVertCount(); i++) { - Vertex vert = p.getVertices().get(i); + Vector3d vert = vertexAt(getVertices(), i); - if (vert.pos.x < minX) { - minX = vert.pos.x; - } - if (vert.pos.y < minY) { - minY = vert.pos.y; - } - if (vert.pos.z < minZ) { - minZ = vert.pos.z; - } + if (vert.x < minX) { + minX = vert.x; + } + if (vert.y < minY) { + minY = vert.y; + } + if (vert.z < minZ) { + minZ = vert.z; + } - if (vert.pos.x > maxX) { - maxX = vert.pos.x; - } - if (vert.pos.y > maxY) { - maxY = vert.pos.y; - } - if (vert.pos.z > maxZ) { - maxZ = vert.pos.z; - } + if (vert.x > maxX) { + maxX = vert.x; + } + if (vert.y > maxY) { + maxY = vert.y; + } + if (vert.z > maxZ) { + maxZ = vert.z; + } - } // end for vertices + } // end for vertices - } // end for polygon + // } // end for polygon bounds = new Bounds(new Vector3d(minX, minY, minZ), new Vector3d(maxX, maxY, maxZ)); return bounds; @@ -2761,7 +3028,7 @@ public double getTotalZ() { * @return the optType */ protected OptType getOptType() { - return optType != null ? optType : defaultOptType; + return defaultOptType; } /** @@ -2771,56 +3038,44 @@ protected OptType getOptType() { * the optType to set */ public static void setDefaultOptType(OptType optType) { + if (optType == OptType.Manifold3d) { + try { + manifold = new CSGManifold3d(); + Slice.setSliceEngine(new ISlice() { + @Override + public List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) + throws ColinearPointsException { + try { + return getManifold().sliceAtZero(incoming, slicePlane); + } catch (Throwable e) { + System.err.println("Slice failed on manifold, using legacy slice"); + e.printStackTrace(); + Slice.setSliceEngine(null); + Slice.getSliceEngine();// set the default when the engine is null + return Slice.getSliceEngine().slice(incoming, slicePlane, normalInsetDistance); + } + } + }); + } catch (Exception e) { + e.printStackTrace(); + optType = defaultOptType; + } + } else { + Slice.setSliceEngine(null); + Slice.getSliceEngine();// set the default when the engine is null + } defaultOptType = optType; } - /** - * Sets the opt type. - * - * @param optType - * the optType to set - */ - public CSG setOptType(OptType optType) { - this.optType = optType; - return this; - } - - /** - * Sets the polygons. - * - * @param polygons - * the new polygons - */ - public CSG setPolygons(ArrayList polygons) { - bounds = null; - // triangulated = false; - this.polygons = polygons; - return this; - } - - /** - * The Enum OptType. - */ - public static enum OptType { - - /** The csg bound. */ - CSG_BOUND, - - /** The polygon bound. */ - POLYGON_BOUND, - - /** The none. */ - NONE - } - /** * Hail Zeon! In case you forget the name of minkowski and are a Gundam fan * * @param travelingShape * @return + * @throws ColinearPointsException */ @Deprecated - public ArrayList minovsky(CSG travelingShape) { + public ArrayList minovsky(CSG travelingShape) throws ColinearPointsException { // com.neuronrobotics.sdk.common.Log.error("Hail Zeon!"); return minkowski(travelingShape); } @@ -2830,8 +3085,9 @@ public ArrayList minovsky(CSG travelingShape) { * * @param travelingShape * @return + * @throws ColinearPointsException */ - public ArrayList mink(CSG travelingShape) { + public ArrayList mink(CSG travelingShape) throws ColinearPointsException { return minkowski(travelingShape); } @@ -2845,8 +3101,9 @@ public ArrayList mink(CSG travelingShape) { * @param travelingShape * a shape to sweep around * @return + * @throws ColinearPointsException */ - public ArrayList minkowskiHullShape(CSG travelingShape) { + public ArrayList minkowskiHullShape(CSG travelingShape) throws ColinearPointsException { if (CSGClient.isRunning()) { ArrayList go = new ArrayList(Arrays.asList(this, travelingShape)); try { @@ -2857,7 +3114,7 @@ public ArrayList minkowskiHullShape(CSG travelingShape) { } } ArrayList bits = new ArrayList<>(); - List polygons2 = this.getPolygons(); + List polygons2 = this.generatePolygonsFromMesh(); int size3 = polygons2.size(); for (int i = 0; i < size3; i++) { Polygon p = polygons2.get(i); @@ -2867,7 +3124,7 @@ public ArrayList minkowskiHullShape(CSG travelingShape) { for (int j = 0; j < size2; j++) { Vertex v = vertices.get(j); CSG newSHape = travelingShape.move(v); - List polygons3 = newSHape.getPolygons(); + List polygons3 = newSHape.generatePolygonsFromMesh(); int size1 = polygons3.size(); for (int k = 0; k < size1; k++) { Polygon np = polygons3.get(k); @@ -2894,10 +3151,11 @@ public ArrayList minkowskiHullShape(CSG travelingShape) { * @param travelingShape * a shape to sweep around * @return + * @throws ColinearPointsException */ - public ArrayList minkowski(CSG travelingShape) { + public ArrayList minkowski(CSG travelingShape) throws ColinearPointsException { HashMap map = new HashMap<>(); - for (Polygon p : travelingShape.getPolygons()) { + for (Polygon p : travelingShape.generatePolygonsFromMesh()) { for (Vertex v : p.getVertices()) { if (map.get(v) == null)// use hashmap to avoid duplicate locations map.put(v, this.move(v)); @@ -2918,8 +3176,9 @@ public ArrayList minkowski(CSG travelingShape) { * @param minkowskiObject * the object to represent the offset * @return + * @throws ColinearPointsException */ - public CSG minkowskiDifference(CSG itemToDifference, CSG minkowskiObject) { + public CSG minkowskiDifference(CSG itemToDifference, CSG minkowskiObject) throws ColinearPointsException { CSG intersection = this.intersect(itemToDifference); ArrayList csgDiff = intersection.minkowskiHullShape(minkowskiObject); @@ -2943,15 +3202,16 @@ public CSG minkowskiDifference(CSG itemToDifference, CSG minkowskiObject) { * @param tolerance * the tolerance distance * @return + * @throws ColinearPointsException */ - public CSG minkowskiDifference(CSG itemToDifference, double tolerance) { + public CSG minkowskiDifference(CSG itemToDifference, double tolerance) throws ColinearPointsException { double shellThickness = Math.abs(tolerance); if (shellThickness < 0.001) return this; return minkowskiDifference(itemToDifference, new Sphere(shellThickness / 2.0, 8, 4).toCSG()); } - public CSG toolOffset(Number sn) { + public CSG toolOffset(Number sn) throws ColinearPointsException { double shellThickness = sn.doubleValue(); boolean cut = shellThickness < 0; shellThickness = Math.abs(shellThickness); @@ -3273,7 +3533,7 @@ public boolean touching(CSG incoming) { if (isBoundsTouching(incoming)) { // Run a full intersection CSG inter = this.intersect(incoming); - if (inter.getPolygons().size() > 0) { + if (inter.getNumberOfTriangles() > 0) { // intersection success return true; } @@ -4285,12 +4545,32 @@ public String getUniqueId() { return uniqueId; } - public List generatePolygonsFromMesh() throws ColinearPointsException { - return polygons; + public static OptType getDefaultOptionType() { + return defaultOptType; + } + + public static CSGManifold3d getManifold() { + return manifold; + } + + public double[] getVertices() { + return vertices; } - public int getNumberOfTriangles() { - return polygons.size(); + public long[] getTriangles() { + return triangles; + } + + public long getVertCount() { + if (vertices == null) + return 0; + return vertices.length / 3; + } + + public long getTriCount() { + if (triangles == null) + return 0; + return triangles.length / 3; } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSGClient.java b/src/main/java/eu/mihosoft/vrl/v3d/CSGClient.java index df0daa5f..30a44788 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSGClient.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSGClient.java @@ -171,14 +171,13 @@ private ArrayList performOperation(List csgList, CSGRemoteOperation op ArrayList toSend = new ArrayList(); for (CSG c : csgList) { - List polygons = c.getPolygons(); - if (polygons.size() == 0) { + + if (c.getNumberOfTriangles() == 0) { Exception ex = new Exception("No Polygons In Incoming geometry here!"); ex.printStackTrace(); throw ex; } - CSG tmp = CSG.fromPolygons(new ArrayList<>(polygons)); - tmp.setOptType(c.getOptType()); + CSG tmp = c.cloneShallow(); toSend.add(tmp); } @@ -210,14 +209,14 @@ private ArrayList performOperation(List csgList, CSGRemoteOperation op // Return results as ArrayList back = new ArrayList(); for (CSG c : response.getCsgList()) { - if (c.getPolygons().size() == 0) { + if (c.getNumberOfTriangles() == 0) { System.out.println("Running Operation on server: " + hostname + " " + operation); RuntimeException runtimeException = new RuntimeException( "Network CSG op resulted in no polygons here "); runtimeException.printStackTrace(); throw runtimeException; } - CSG historySync = CSG.fromPolygons(c.getPolygons()); + CSG historySync = c.cloneShallow(); back.add(historySync); for (CSG s : csgList) { historySync.historySync(s); @@ -283,7 +282,6 @@ public static void main(String[] args) { CSG c = new Cube(10, 10, 10).toCSG(); CSG u = CSG.unionAll(a, b, c); CSG d = a.difference(b); - CSG t = d.triangulate(true); ArrayList m = a.minkowskiHullShape(b); CSGClient.close(); } catch (Exception e) { diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSGServerHandler.java b/src/main/java/eu/mihosoft/vrl/v3d/CSGServerHandler.java index 02020543..568dc495 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSGServerHandler.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSGServerHandler.java @@ -124,7 +124,7 @@ private CSGResponse processCSGRequest(CSGRequest request) { case TRIANGULATE : CSG.setPreventNonManifoldTriangles(true); for (CSG c : csgList) - back.add(c.triangulate(true)); + back.add(c.makeManifold(true)); break; case UNION : try { @@ -153,7 +153,7 @@ private CSGResponse processCSGRequest(CSGRequest request) { } } catch (Throwable t) { CSGClient.setServerCall(false); - throw t; + throw new RuntimeException(t); } CSGClient.setServerCall(false); return new CSGResponse(back, request.getOperation()); diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ChamferedCube.java b/src/main/java/eu/mihosoft/vrl/v3d/ChamferedCube.java index ed97a6a6..75c9c9db 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ChamferedCube.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ChamferedCube.java @@ -1,7 +1,5 @@ package eu.mihosoft.vrl.v3d; -import java.util.List; - public class ChamferedCube extends Primitive { double w, h, d, chamferHeight; @@ -36,11 +34,11 @@ public ChamferedCube(double w, double h, double d, double chamferHeight) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { CSG cube1 = new Cube(w - chamferHeight * 2, h, d - chamferHeight * 2).toCSG(); CSG cube2 = new Cube(w, h - chamferHeight * 2, d - chamferHeight * 2).toCSG(); CSG cube3 = new Cube(w - chamferHeight * 2, h - chamferHeight * 2, d).toCSG(); - return cube1.union(cube2).union(cube3).hull().getPolygons(); + return cube1.union(cube2).union(cube3).hull(); } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ChamferedCylinder.java b/src/main/java/eu/mihosoft/vrl/v3d/ChamferedCylinder.java index 2a260e66..af028f20 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ChamferedCylinder.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ChamferedCylinder.java @@ -1,7 +1,5 @@ package eu.mihosoft.vrl.v3d; -import java.util.List; - public class ChamferedCylinder extends Primitive { double r, h, chamferHeight; int sides = -1; @@ -54,9 +52,9 @@ public ChamferedCylinder(double r, double h, double chamferHeight) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { CSG cube1 = new Cylinder(r - chamferHeight, r - chamferHeight, h, sides).toCSG(); CSG cube2 = new Cylinder(r, r, h - chamferHeight * 2, sides).toCSG().movez(chamferHeight); - return cube1.union(cube2).hull().getPolygons(); + return cube1.union(cube2).hull(); } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Cube.java b/src/main/java/eu/mihosoft/vrl/v3d/Cube.java index 0aa68d23..b2f6474a 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Cube.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Cube.java @@ -36,6 +36,8 @@ import java.util.ArrayList; import java.util.List; +import eu.mihosoft.vrl.v3d.ext.quickhull3d.HullUtil; + // Auto-generated Javadoc /** * An axis-aligned solid cuboid defined by {@code center} and @@ -122,7 +124,7 @@ public Cube(double w, double h, double d) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { if (dimensions.x <= 0) throw new NumberFormatException("X can not be negative"); if (dimensions.y <= 0) @@ -133,39 +135,27 @@ public List toPolygons() { // position // normal {{0, 4, 6, 2}, {-1, 0, 0}}, {{1, 3, 7, 5}, {+1, 0, 0}}, {{0, 1, 5, 4}, {0, -1, 0}}, {{2, 6, 7, 3}, {0, +1, 0}}, {{0, 2, 3, 1}, {0, 0, -1}}, {{4, 5, 7, 6}, {0, 0, +1}}}; - List polygons = new ArrayList<>(); + // List polygons = new ArrayList<>(); + List vertices = new ArrayList<>(); + for (int[][] info : a) { - List vertices = new ArrayList<>(); for (int i : info[0]) { Vector3d pos = new Vector3d(center.x + dimensions.x * (1 * Math.min(1, i & 1) - 0.5), center.y + dimensions.y * (1 * Math.min(1, i & 2) - 0.5), center.z + dimensions.z * (1 * Math.min(1, i & 4) - 0.5)); - vertices.add(new Vertex(pos)); - } - try { - polygons.add(new Polygon(vertices, properties)); - } catch (ColinearPointsException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + vertices.add(pos); } } + CSG hull = HullUtil.hull(vertices, properties); if (!centered) { Transform centerTransform = Transform.unity().translate(dimensions.x / 2.0, dimensions.y / 2.0, dimensions.z / 2.0); - - for (Polygon p : polygons) { - try { - p.transform(centerTransform); - } catch (ColinearPointsException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } + hull = hull.transformed(centerTransform); } - return polygons; + return hull; } /** diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Cylinder.java b/src/main/java/eu/mihosoft/vrl/v3d/Cylinder.java index 17daae48..6dc7d9fe 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Cylinder.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Cylinder.java @@ -35,7 +35,8 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.List; + +import eu.mihosoft.vrl.v3d.ext.quickhull3d.HullUtil; // Auto-generated Javadoc /** @@ -227,7 +228,7 @@ public Cylinder(double startRadius, double endRadius, double height) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { if (startRadius <= 0) throw new NumberFormatException("startRadius can not be negative"); if (endRadius <= 0) @@ -245,39 +246,23 @@ public List toPolygons() { boolean isY = (Math.abs(axisZ.y) > 0.5); final Vector3d axisX = new Vector3d(isY ? 1 : 0, !isY ? 1 : 0, 0).cross(axisZ).normalized(); final Vector3d axisY = axisX.cross(axisZ).normalized(); - Vertex startV = new Vertex(s); - Vertex endV = new Vertex(e); - List polygons = new ArrayList<>(); - + Vector3d startV = s; + Vector3d endV = e; + // List polygons = new ArrayList<>(); + ArrayList points = new ArrayList(); for (int i = 0; i < numSlices; i++) { double t0 = i / (double) numSlices, t1 = (i + 1) / (double) numSlices; - try { - polygons.add( - new Polygon(Arrays.asList(startV, cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t0, -1), - cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t1, -1)), properties)); - } catch (ColinearPointsException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - try { - polygons.add(new Polygon(Arrays.asList(cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t1, 0), - cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t0, 0), - cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t0, 0), - cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t1, 0)), properties)); - } catch (ColinearPointsException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - try { - polygons.add(new Polygon(Arrays.asList(endV, cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t1, 1), - cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t0, 1)), properties)); - } catch (ColinearPointsException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } + points.addAll(Arrays.asList(startV, cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t0, -1), + cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t1, -1))); + points.addAll(Arrays.asList(cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t1, 0), + cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t0, 0), + cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t0, 0), + cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t1, 0))); + points.addAll(Arrays.asList(endV, cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t1, 1), + cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t0, 1))); } - return polygons; + return HullUtil.hull(points, properties); } /** @@ -303,13 +288,12 @@ public List toPolygons() { * the normal blend * @return the vertex */ - private Vertex cylPoint(Vector3d axisX, Vector3d axisY, Vector3d axisZ, Vector3d ray, Vector3d s, double r, + private Vector3d cylPoint(Vector3d axisX, Vector3d axisY, Vector3d axisZ, Vector3d ray, Vector3d s, double r, double stack, double slice, double normalBlend) { double angle = slice * Math.PI * 2; Vector3d out = axisX.times(Math.cos(angle)).plus(axisY.times(Math.sin(angle))); Vector3d pos = s.plus(ray.times(stack)).plus(out.times(r)); - Vector3d normal = out.times(1.0 - Math.abs(normalBlend)).plus(axisZ.times(normalBlend)); - return new Vertex(pos); + return pos; } /** diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Dodecahedron.java b/src/main/java/eu/mihosoft/vrl/v3d/Dodecahedron.java index a5ad3bb8..63692693 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Dodecahedron.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Dodecahedron.java @@ -64,7 +64,7 @@ public Dodecahedron(Vector3d center, double size) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { if (radius <= 0) throw new NumberFormatException("radius can not be negative"); @@ -92,9 +92,7 @@ public List toPolygons() { points.add(new Vector3d(-1 / phi, -phi, 0)); points.add(new Vector3d(0, -1 / phi, -phi)); - List polygons = HullUtil.hull(points).scale(radius * (phi - 1)).getPolygons(); - - return polygons; + return HullUtil.hull(points).scale(radius * (phi - 1)); } /** diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Edge.java b/src/main/java/eu/mihosoft/vrl/v3d/Edge.java index 6e4af856..781056f5 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Edge.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Edge.java @@ -835,15 +835,16 @@ public Optional getCrossingPoint(Edge e) { * @return the list * @throws ColinearPointsException */ - public static List boundaryPolygons(CSG csg) throws ColinearPointsException { - List result = new ArrayList<>(); - - for (List polygonGroup : searchPlaneGroups(csg.getPolygons())) { - result.addAll(boundaryPolygonsOfPlaneGroup(polygonGroup)); - } - - return result; - } + // public static List boundaryPolygons(CSG csg) throws + // ColinearPointsException { + // List result = new ArrayList<>(); + // + // for (List polygonGroup : searchPlaneGroups(csg.getPolygons())) { + // result.addAll(boundaryPolygonsOfPlaneGroup(polygonGroup)); + // } + // + // return result; + // } /** * Boundary edges of plane group. diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java b/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java index 826d0034..f9cb947f 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java @@ -109,7 +109,7 @@ private CSG monotoneExtrude(Vector3d dir, Polygon polygon1) throws ColinearPoint // ArrayList topPolygons = PolygonUtil.triangulatePolygon(polygon2); - extrude = CSG.fromPolygons(newPolygons); + extrude = new CSG(newPolygons); return extrude; } @@ -598,7 +598,7 @@ public static CSG sweep(Polygon p, Transform increment, Transform offset, int st List topPolygons = PolygonUtil.triangulatePolygon(polygon2.flipped()); newPolygons.addAll(topPolygons); - return CSG.fromPolygons(newPolygons); + return new CSG(newPolygons); } public static CSG sweep(Polygon p, double angle, double z, double radius, int steps) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Fillet.java b/src/main/java/eu/mihosoft/vrl/v3d/Fillet.java index 3cb8ba5f..ef592bd8 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Fillet.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Fillet.java @@ -83,10 +83,10 @@ public static CSG outerFillet(List polys, double rad) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { CSG simpleSyntax = new Cylinder(w, h + 1).toCSG() // a one line Cylinder .rotx(90).toXMin().toZMin().movey(-0.5); CSG cubeSection = new Cube(w - 0.1, h, w - 0.1).toCSG().toXMin().toZMin().toYMin(); - return cubeSection.difference(simpleSyntax).getPolygons(); + return cubeSection.difference(simpleSyntax); } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Hexagon.java b/src/main/java/eu/mihosoft/vrl/v3d/Hexagon.java index 3c7d43d2..630eabda 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Hexagon.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Hexagon.java @@ -3,8 +3,6 @@ */ package eu.mihosoft.vrl.v3d; -import java.util.List; - public class Hexagon extends Primitive { /** @@ -42,9 +40,9 @@ public Hexagon(double flatToFlatDIstance, double height) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { head = new Cylinder(nunRad, nunRad, height, (int) 6).toCSG(); - return head.getPolygons(); + return head; } /** diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Icosahedron.java b/src/main/java/eu/mihosoft/vrl/v3d/Icosahedron.java index b6bc4fe5..5e2eb1ac 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Icosahedron.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Icosahedron.java @@ -64,7 +64,7 @@ public Icosahedron(Vector3d center, double size) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { if (radius <= 0) throw new NumberFormatException("radius can not be negative"); double phi = (Math.sqrt(5) + 1) / 2; @@ -83,9 +83,8 @@ public List toPolygons() { points.add(new Vector3d(-1, -phi, 0)); points.add(new Vector3d(0, -1, -phi)); - List polygons = HullUtil.hull(points).scale(radius / (Math.sqrt(1 + Math.pow(phi, 2)))).getPolygons(); + return HullUtil.hull(points).scale(radius / (Math.sqrt(1 + Math.pow(phi, 2)))); - return polygons; } /** diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Isosceles.java b/src/main/java/eu/mihosoft/vrl/v3d/Isosceles.java index 9b71449b..303e96cd 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Isosceles.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Isosceles.java @@ -1,7 +1,5 @@ package eu.mihosoft.vrl.v3d; -import java.util.List; - public class Isosceles extends Primitive { double w, h, d; @@ -35,7 +33,7 @@ public Isosceles(double w, double h, double d) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { if (w <= 0) throw new NumberFormatException("w can not be negative"); if (h <= 0) @@ -54,7 +52,7 @@ public List toPolygons() { // TODO Auto-generated catch block e.printStackTrace(); } - return polygon.getPolygons(); + return polygon; } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Modifier.java b/src/main/java/eu/mihosoft/vrl/v3d/Modifier.java deleted file mode 100644 index ab150529..00000000 --- a/src/main/java/eu/mihosoft/vrl/v3d/Modifier.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ - -package eu.mihosoft.vrl.v3d; - -// Auto-generated Javadoc -/** - * The Class Modifier. - * - * @author Michael Hoffer <info@michaelhoffer.de> - */ -final class Modifier { - - /** The function. */ - private final WeightFunction function; - - /** - * Instantiates a new modifier. - * - * @param function - * the function - */ - public Modifier(WeightFunction function) { - this.function = function; - } - - /** - * Modify. - * - * @param csg - * the csg - */ - void modify(CSG csg) { - for (Polygon p : csg.getPolygons()) { - for (Vertex v : p.getVertices()) { - v.setWeight(function.eval(v.pos, csg)); - } - } - } - - /** - * Modified. - * - * @param csg - * the csg - * @return the csg - */ - CSG modified(CSG csg) { - CSG result = csg.clone(); - modify(result); - return result; - } -} diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Octahedron.java b/src/main/java/eu/mihosoft/vrl/v3d/Octahedron.java index 35f6d856..b330a04f 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Octahedron.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Octahedron.java @@ -64,7 +64,7 @@ public Octahedron(Vector3d center, double size) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { if (radius <= 0) throw new NumberFormatException("radius can not be negative"); double sqrt2_2 = Math.sqrt(2) / 2; @@ -77,9 +77,8 @@ public List toPolygons() { points.add(new Vector3d(+sqrt2_2, -sqrt2_2, 0)); points.add(new Vector3d(+sqrt2_2, +sqrt2_2, 0)); - List polygons = HullUtil.hull(points).scale(radius).getPolygons(); + return HullUtil.hull(points).scale(radius); - return polygons; } /** diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Polyhedron.java b/src/main/java/eu/mihosoft/vrl/v3d/Polyhedron.java deleted file mode 100644 index c89fed12..00000000 --- a/src/main/java/eu/mihosoft/vrl/v3d/Polyhedron.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package eu.mihosoft.vrl.v3d; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -// Auto-generated Javadoc -/** - * Polyhedron. - * - * @author Michael Hoffer <info@michaelhoffer.de> - */ -public class Polyhedron extends Primitive { - - /** The properties. */ - private final PropertyStorage properties = new PropertyStorage(); - - /** The points. */ - private final List points = new ArrayList<>(); - - /** The faces. */ - private final List> faces = new ArrayList<>(); - - /** - * Constructor. Creates a polyhedron defined by a list of points and a list of - * faces. - * - * @param points - * points ({@link Vector3d} list) - * @param faces - * list of faces (list of point index lists) - */ - public Polyhedron(List points, List> faces) { - this.points.addAll(points); - this.faces.addAll(faces); - } - - /** - * Constructor. Creates a polyhedron defined by a list of points and a list of - * faces. - * - * @param points - * points ({@link Vector3d} array) - * @param faces - * list of faces (array of point index arrays) - */ - public Polyhedron(Vector3d[] points, Integer[][] faces) { - this.points.addAll(Arrays.asList(points)); - - for (Integer[] list : faces) { - this.faces.add(Arrays.asList(list)); - } - - } - - /* - * (non-Javadoc) - * - * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() - */ - @Override - public List toPolygons() { - - Function indexToPoint = (Integer i) -> { - return points.get(i).clone(); - }; - - Function, Polygon> faceListToPolygon = (List faceList) -> { - try { - return Polygon.fromPoints(faceList.stream().map(indexToPoint).collect(Collectors.toList()), properties); - } catch (ColinearPointsException e) { - throw new RuntimeException(e); - } - }; - - return faces.stream().map(faceListToPolygon).collect(Collectors.toList()); - } - - /* - * (non-Javadoc) - * - * @see eu.mihosoft.vrl.v3d.Primitive#getProperties() - */ - @Override - public PropertyStorage getProperties() { - return properties; - } - -} diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Primitive.java b/src/main/java/eu/mihosoft/vrl/v3d/Primitive.java index 1815b3dd..bda422ea 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Primitive.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Primitive.java @@ -30,9 +30,6 @@ package eu.mihosoft.vrl.v3d; -import java.util.ArrayList; -import java.util.List; - // Auto-generated Javadoc /** * A primitive geometry. @@ -42,29 +39,23 @@ public abstract class Primitive implements ItoCSG { // ArrayList parametrics=new ArrayList<>(); - /** - * Returns the polygons that define this primitive. - * - * Note: this method computes the polygons each time this method is called. The - * polygons can be cached inside a {@link CSG} object. - * - * @return a list of polygons that define this primitive - */ - public abstract List toPolygons(); + // /** + // * Returns the polygons that define this primitive. + // * + // * Note: this method computes the polygons each time this method is called. + // The + // * polygons can be cached inside a {@link CSG} object. + // * + // * @return a list of polygons that define this primitive + // */ + // public abstract List toPolygons(); /** * Returns this primitive as {@link CSG}. * * @return this primitive as {@link CSG} */ - public CSG toCSG() { - CSG tmp = CSG.fromPolygons(getProperties(), new ArrayList<>(toPolygons())); - // if(parametrics!=null) - // for(Parameter p:parametrics) - // tmp.setParameter(p); - // tmp.triangulate(); - return tmp; - } + public abstract CSG toCSG(); /** * Returns the property storage of this primitive. diff --git a/src/main/java/eu/mihosoft/vrl/v3d/RoundedCube.java b/src/main/java/eu/mihosoft/vrl/v3d/RoundedCube.java index 2bbf8e02..c0a551cb 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/RoundedCube.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/RoundedCube.java @@ -6,7 +6,6 @@ package eu.mihosoft.vrl.v3d; import static eu.mihosoft.vrl.v3d.Transform.unity; -import java.util.List; // Auto-generated Javadoc /** @@ -102,7 +101,7 @@ public RoundedCube(double w, double h, double d) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { CSG spherePrototype = new Sphere(getCornerRadius(), getResolution() * 2, getResolution()).toCSG(); double x = dimensions.x / 2.0 - getCornerRadius(); @@ -119,24 +118,17 @@ public List toPolygons() { CSG sphere7 = spherePrototype.transformed(unity().translate(x, y, z)); CSG sphere8 = spherePrototype.transformed(unity().translate(-x, y, z)); - List result = CSG.hullAll(sphere1, sphere2, sphere3, sphere4, sphere5, sphere6, sphere7, sphere8) - .getPolygons(); + CSG back = CSG.hullAll(sphere1, sphere2, sphere3, sphere4, sphere5, sphere6, sphere7, sphere8); if (!centered) { Transform centerTransform = Transform.unity().translate(dimensions.x / 2.0, dimensions.y / 2.0, dimensions.z / 2.0); - for (Polygon p : result) { - try { - p.transform(centerTransform); - } catch (ColinearPointsException e) { - throw new RuntimeException(e); - } - } + back = back.transformed(centerTransform); } - return result; + return back; } /* diff --git a/src/main/java/eu/mihosoft/vrl/v3d/RoundedCylinder.java b/src/main/java/eu/mihosoft/vrl/v3d/RoundedCylinder.java index 38d0b857..79a613bd 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/RoundedCylinder.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/RoundedCylinder.java @@ -6,7 +6,6 @@ package eu.mihosoft.vrl.v3d; import java.util.ArrayList; -import java.util.List; import eu.mihosoft.vrl.v3d.ext.quickhull3d.HullUtil; @@ -81,7 +80,7 @@ public RoundedCylinder(double startRadius, double height) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { double minHeight = height - cornerRadius * 2; ArrayList cylParts = new ArrayList<>(); @@ -98,7 +97,7 @@ public List toPolygons() { (int) resolution // resolution ).toCSG().movez(-heightInc)); } - return HullUtil.hull(cylParts).toZMin().getPolygons(); + return HullUtil.hull(cylParts).toZMin(); } /* diff --git a/src/main/java/eu/mihosoft/vrl/v3d/STL.java b/src/main/java/eu/mihosoft/vrl/v3d/STL.java index e8c82801..4410320d 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/STL.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/STL.java @@ -31,6 +31,7 @@ package eu.mihosoft.vrl.v3d; +import eu.mihosoft.vrl.v3d.CSG.OptType; import eu.mihosoft.vrl.v3d.ext.imagej.STLLoader; import java.io.IOException; @@ -45,6 +46,8 @@ import java.util.HashMap; import java.util.Map; +import com.neuronrobotics.manifold3d.NonManifoldShapeError; + // Auto-generated Javadoc /** * Loads a CSG from stl. @@ -63,14 +66,16 @@ public class STL { * if loading failed * @throws URISyntaxException * the URI syntax exception + * @throws ColinearPointsException */ - public static CSG file(URL path) throws IOException, URISyntaxException { + public static CSG file(URL path, boolean repair) + throws IOException, URISyntaxException, ColinearPointsException, NonManifoldShapeError { final URI uri = path.toURI(); Map env = new HashMap<>(); env.put("create", "true"); FileSystem zipfs = FileSystems.newFileSystem(uri, env); Path myFolderPath = Paths.get(uri); - return file(myFolderPath); + return file(myFolderPath, repair); } /** @@ -81,13 +86,48 @@ public static CSG file(URL path) throws IOException, URISyntaxException { * @return CSG * @throws IOException * if loading failed + * @throws NonManifoldShapeError + * @throws ColinearPointsException */ - public static CSG file(Path path) throws IOException { + public static CSG file(Path path, boolean repair) + throws IOException, NonManifoldShapeError, ColinearPointsException { STLLoader loader = new STLLoader(); ArrayList polygons = loader.parse(path.toFile()); - CSG fromPolygons = CSG.fromPolygons(new PropertyStorage(), polygons); + CSG fromPolygons; + + fromPolygons = new CSG(polygons); + if (CSG.getDefaultOptionType() == OptType.Manifold3d) { + fromPolygons.makeManifold(repair); + } + return fromPolygons; } + /** + * Loads a CSG from stl. + * + * @param path + * file path + * @return CSG + * @throws IOException + * if loading failed + * @throws NonManifoldShapeError + * @throws ColinearPointsException + */ + public static CSG file(Path path) { + try { + return file(path, true); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NonManifoldShapeError e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return new CSG(); + } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Slice.java b/src/main/java/eu/mihosoft/vrl/v3d/Slice.java index 0452923e..e9f29c42 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Slice.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Slice.java @@ -30,7 +30,7 @@ private static class DefaultSliceImp implements ISlice { int minRes = 1000; private boolean done; - Object[] toPixMap(CSG slicePart) { + Object[] toPixMap(CSG slicePart) throws ColinearPointsException { // BowlerStudioController.getBowlerStudio() // .addObject((Object)slicePart.movez(1),(File)null) @@ -44,7 +44,7 @@ Object[] toPixMap(CSG slicePart) { double mySize = slicePart.getTotalX() > slicePart.getTotalY() ? slicePart.getTotalX() : slicePart.getTotalY(); - List polys = slicePart.getPolygons(); + List polys = slicePart.generatePolygonsFromMesh(); double size = sizeinPixelSpace * (mySize / 200) * (polys.size() / 300); if (size < minRes) size = minRes; @@ -180,7 +180,7 @@ public List slice(CSG incoming, Transform slicePlane, double normalInse CSG slicePart = finalPart .intersect(planeCSG); - for (Polygon p : slicePart.getPolygons()) { + for (Polygon p : slicePart.generatePolygonsFromMesh()) { if (Slice.isPolygonAtZero(p)) { rawPolygons.add(p); } @@ -419,7 +419,7 @@ boolean withinAPix(int[] incoming, int[] out) { } }; - private static ISlice sliceEngine = new DefaultSliceImp(); + private static ISlice sliceEngine; /** * Returns true if this polygon lies entirely in the z plane @@ -471,7 +471,8 @@ public static List slice(CSG incoming, Transform slicePlane, double nor } return sanatize(getSliceEngine().slice(incoming, slicePlane, normalInsetDistance)); } catch (Throwable e) { - return sanatize(incoming.getPolygons()); + e.printStackTrace(); + return sanatize(incoming.generatePolygonsFromMesh()); } } @@ -498,6 +499,8 @@ public static List slice(CSG incoming, double normalInsetDistance) thro return slice(incoming, new Transform(), normalInsetDistance); } public static ISlice getSliceEngine() { + if (sliceEngine == null) + sliceEngine = new DefaultSliceImp(); return sliceEngine; } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java b/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java index 971a8484..49aa1800 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java @@ -36,6 +36,10 @@ import java.util.ArrayList; import java.util.List; +import eu.mihosoft.vrl.v3d.ext.quickhull3d.HullUtil; + +import eu.mihosoft.vrl.v3d.ext.quickhull3d.HullUtil; + // Auto-generated Javadoc /** * A solid sphere. @@ -86,6 +90,7 @@ public Sphere(double radius) { init(); this.radius = radius; } + // public Cube(LengthParameter w, LengthParameter h, LengthParameter d) { // this(Vector3d.ZERO, new Vector3d(w.getMM(), h.getMM(), d.getMM())); // @@ -162,6 +167,13 @@ private void init() { private Vertex sphereVertex(Vector3d c, double r, double theta, double phi) { theta *= Math.PI * 2; phi *= Math.PI; + + // Clamp poles to exact values to avoid sin(PI) floating point error + if (phi <= 0) + return new Vertex(c.plus(new Vector3d(0, r, 0))); + if (phi >= Math.PI) + return new Vertex(c.plus(new Vector3d(0, -r, 0))); + Vector3d dir = new Vector3d(Math.cos(theta) * Math.sin(phi), Math.cos(phi), Math.sin(theta) * Math.sin(phi)); return new Vertex(c.plus(dir.times(r))); } @@ -172,35 +184,37 @@ private Vertex sphereVertex(Vector3d c, double r, double theta, double phi) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { if (radius <= 0) throw new NumberFormatException("radius can not be negative"); - List polygons = new ArrayList<>(); - - for (int i = 0; i < getNumSlices(); i++) { - for (int j = 0; j < getNumStacks(); j++) { - final List vertices = new ArrayList<>(); - - vertices.add(sphereVertex(center, radius, i / (double) getNumSlices(), j / (double) getNumStacks())); - if (j > 0) { - vertices.add(sphereVertex(center, radius, (i + 1) / (double) getNumSlices(), - j / (double) getNumStacks())); - } - if (j < getNumStacks() - 1) { - vertices.add(sphereVertex(center, radius, (i + 1) / (double) getNumSlices(), - (j + 1) / (double) getNumStacks())); - } - vertices.add( - sphereVertex(center, radius, i / (double) getNumSlices(), (j + 1) / (double) getNumStacks())); - try { - polygons.add(new Polygon(vertices, getProperties())); - } catch (ColinearPointsException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + List points = new ArrayList<>(); + + // 1. Add the Poles explicitly (avoiding the loop to ensure they are clean) + points.add(new Vector3d(center.x, center.y + radius, center.z)); // North + points.add(new Vector3d(center.x, center.y - radius, center.z)); // South + // 2. Generate the rings (excluding the pole stacks) + for (int j = 1; j < numStacks; j++) { + double phi = Math.PI * j / numStacks; + double y = Math.cos(phi); + double rRing = Math.sin(phi); + + for (int i = 0; i < numSlices; i++) { + double theta = 2.0 * Math.PI * i / numSlices; + + double x = Math.cos(theta) * rRing; + double z = Math.sin(theta) * rRing; + + // Micro-snapping to zero for stability + if (Math.abs(x) < 1e-12) + x = 0; + if (Math.abs(z) < 1e-12) + z = 0; + + points.add(new Vector3d(center.x + x * radius, center.y + y * radius, center.z + z * radius)); } } - return polygons; + + return HullUtil.hull(points, getProperties()); } /** @@ -259,8 +273,8 @@ public int getNumSlices() { * the numSlices to set */ public Sphere setNumSlices(int numSlices) { - if (numSlices > (NUM_SLICES * 4)) - System.out.println("Very large sphere! this may crash!"); + // if (numSlices > (NUM_SLICES * 4)) + // System.out.println("Very large sphere! this may crash!"); this.numSlices = numSlices; return this; } @@ -281,8 +295,8 @@ public int getNumStacks() { * the numStacks to set */ public Sphere setNumStacks(int numStacks) { - if (numStacks > (NUM_STACKS * 4)) - System.out.println("Very large sphere! this may crash!"); + // if (numStacks > (NUM_STACKS * 4)) + // System.out.println("Very large sphere! this may crash!"); this.numStacks = numStacks; return this; } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Tetrahedron.java b/src/main/java/eu/mihosoft/vrl/v3d/Tetrahedron.java index af9a6e96..23f83f4f 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Tetrahedron.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Tetrahedron.java @@ -64,7 +64,7 @@ public Tetrahedron(Vector3d center, double size) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { if (radius <= 0) throw new NumberFormatException("radius can not be negative"); double _1_sqrt2 = 1 / Math.sqrt(2); @@ -75,9 +75,7 @@ public List toPolygons() { points.add(new Vector3d(0, -1, +_1_sqrt2)); points.add(new Vector3d(0, +1, +_1_sqrt2)); - List polygons = HullUtil.hull(points).scale(radius / Math.sqrt(3)).getPolygons(); - - return polygons; + return HullUtil.hull(points).scale(radius / Math.sqrt(3)); } /** diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Text3d.java b/src/main/java/eu/mihosoft/vrl/v3d/Text3d.java index fec20dfa..fc1c5d64 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Text3d.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Text3d.java @@ -34,7 +34,6 @@ package eu.mihosoft.vrl.v3d; import java.util.ArrayList; -import java.util.List; import javafx.scene.text.Font; /** @@ -99,12 +98,8 @@ public Text3d(String text, String fontName, double fontSize, double depth) { } @Override - public List toPolygons() { - List poly = new ArrayList(); - for (CSG c : letters) { - poly.addAll(c.getPolygons()); - } - return poly; + public CSG toCSG() { + return CSG.unionAll(letters); } @Override diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Toroid.java b/src/main/java/eu/mihosoft/vrl/v3d/Toroid.java index 43bcfc1c..66a1fea6 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Toroid.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Toroid.java @@ -49,7 +49,7 @@ public class Toroid extends Primitive { /** The properties. */ private final PropertyStorage properties = new PropertyStorage(); - List polys; + ArrayList polys; public Toroid(double innerRadius, double OuterRadius) { this(innerRadius, OuterRadius, 20, 16); } @@ -89,7 +89,7 @@ public Toroid(double innerRadius, double OuterRadius, int numSlices, int facets) e.printStackTrace(); } } - List newPolygons = new ArrayList<>(); + ArrayList newPolygons = new ArrayList<>(); for (int j = 0; j < slices.size(); j++) { int next = j + 1; if (next == slices.size()) @@ -134,8 +134,14 @@ public Toroid(double innerRadius, double OuterRadius, int numSlices, int facets) * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { - return polys; + public CSG toCSG() { + try { + return new CSG(polys); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return new CSG(); + } } /* diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java b/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java index 5fb7d99c..1fcbe19e 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java @@ -81,6 +81,10 @@ private Vertex(Vector3d pos, double weight) { this.weight = weight; } + public Vertex(double x, double y, double z) { + pos = new Vector3d(x, y, z); + } + /* * (non-Javadoc) * diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Wedge.java b/src/main/java/eu/mihosoft/vrl/v3d/Wedge.java index 2ddbe097..8ee40d03 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Wedge.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Wedge.java @@ -1,7 +1,5 @@ package eu.mihosoft.vrl.v3d; -import java.util.List; - public class Wedge extends Primitive { double w, h, d; @@ -41,7 +39,7 @@ public Wedge(double w, double h, double d) { * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() */ @Override - public List toPolygons() { + public CSG toCSG() { CSG polygon = null; try { polygon = Extrude.points(new Vector3d(0, 0, h), // This is the extrusion depth @@ -53,6 +51,6 @@ public List toPolygons() { // TODO Auto-generated catch block e.printStackTrace(); } - return polygon.getPolygons(); + return polygon; } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java b/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java index 3bae1749..1b6cd3d5 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java @@ -47,6 +47,10 @@ public STLLoader() { * Signals that an I/O exception has occurred. */ public ArrayList parse(File f) throws IOException { + // if (CSG.getDefaultOptionType() == OptType.Manifold3d) { + // new RuntimeException("Manifold3d STL import not implemented + // yet").printStackTrace(); + // } ArrayList polygons = new ArrayList<>(); // determine if this is a binary or ASCII STL diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ext/org/poly2tri/PolygonUtil.java b/src/main/java/eu/mihosoft/vrl/v3d/ext/org/poly2tri/PolygonUtil.java index 45ffacfb..7def95c0 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ext/org/poly2tri/PolygonUtil.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ext/org/poly2tri/PolygonUtil.java @@ -629,7 +629,7 @@ public static ArrayList triangulatePolygon(Polygon incoming) throws Col // } try { makeTriangles(concave, cw, result, zplane, normalOfPlane, debug, orientationInv, reorient, - incoming.getColor()); + incoming.getColor()); } catch (java.lang.IllegalStateException ex) { Polygon repaired = repairOverlappingEdges(concave); diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java b/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java index 3542d529..2db4a84f 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java @@ -6,6 +6,7 @@ package eu.mihosoft.vrl.v3d.ext.quickhull3d; import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.CSG.OptType; import eu.mihosoft.vrl.v3d.CSGClient; import eu.mihosoft.vrl.v3d.ColinearPointsException; import eu.mihosoft.vrl.v3d.Vector3d; @@ -34,6 +35,7 @@ private HullUtil() { * @param points * the points * @return the csg + * @throws ColinearPointsException */ public static CSG hull(List points) { List plist = new ArrayList<>(); @@ -42,9 +44,10 @@ public static CSG hull(List points) { return hull(plist, new PropertyStorage()); } if (CSG.class.isInstance(points.get(0))) { - for (Object csg : points) - ((CSG) csg).getPolygons().forEach((p) -> p.getVertices().forEach((v) -> plist.add(v.pos))); - + for (Object c : points) { + CSG csg = (CSG) c; + plist.addAll(csg.getPoints()); + } return hull(plist, new PropertyStorage()); } throw new RuntimeException("Objects in list are of unknown type: " + points.get(0).getClass().getName() @@ -59,6 +62,7 @@ public static CSG hull(List points) { * @param storage * the storage * @return the csg + * @throws ColinearPointsException */ public static CSG hull(List points, PropertyStorage storage) { if (CSGClient.isRunning()) { @@ -71,6 +75,14 @@ public static CSG hull(List points, PropertyStorage storage) { e.printStackTrace(); } } + if (CSG.getDefaultOptionType() == OptType.Manifold3d) { + try { + return CSG.getManifold().hull(points); + } catch (Throwable e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } Point3d[] hullPoints = points.stream().map((vec) -> new Point3d(vec.x, vec.y, vec.z)).toArray(Point3d[]::new); QuickHull3D hull = new QuickHull3D(); @@ -99,7 +111,13 @@ public static CSG hull(List points, PropertyStorage storage) { vertices.clear(); } - return CSG.fromPolygons(polygons); + try { + return new CSG(polygons); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return new CSG(); + } } /** @@ -113,9 +131,7 @@ public static CSG hull(List points, PropertyStorage storage) { */ public static CSG hull(CSG csg, PropertyStorage storage) { - List points = new ArrayList<>(csg.getPolygons().size() * 3); - - csg.getPolygons().forEach((p) -> p.getVertices().forEach((v) -> points.add(v.pos))); + List points = csg.getPoints(); return hull(points, storage); } @@ -131,8 +147,7 @@ public static CSG hull(CSG... csgList) { List points = new ArrayList<>(); for (CSG csg : csgList) - csg.getPolygons().forEach((p) -> p.getVertices().forEach((v) -> points.add(v.pos))); - + points.addAll(csg.getPoints()); return hull(points, new PropertyStorage()); } } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/CSGTest.java b/src/test/java/eu/mihosoft/vrl/v3d/CSGTest.java index 34d10c2c..6ddd7e28 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/CSGTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/CSGTest.java @@ -11,9 +11,10 @@ public void setColor_ShouldSetColorToAllPolygons() { CSG cube = new Cube().toCSG().setColor(Color.BLUE); assertEquals(Color.BLUE, cube.getColor()); - cube.getPolygons().forEach(polygon -> { - assertEquals("Expected the polygon to get the same color as the CSG", Color.BLUE, polygon.getColor()); - }); + // cube.getPolygons().forEach(polygon -> { + // assertEquals("Expected the polygon to get the same color as the CSG", + // Color.BLUE, polygon.getColor()); + // }); } @Test @@ -71,9 +72,10 @@ public void setColor_OnCSGShouldChangeColorsOfAllPolygons() { cube.setColor(Color.RED); - cube.getPolygons().forEach(polygon -> { - assertEquals("Expected the cube polygons to be another color", Color.RED, polygon.getColor()); - }); + // cube.getPolygons().forEach(polygon -> { + // assertEquals("Expected the cube polygons to be another color", Color.RED, + // polygon.getColor()); + // }); } @Test diff --git a/src/test/java/eu/mihosoft/vrl/v3d/ConcavePolygonExtrusionTest.java b/src/test/java/eu/mihosoft/vrl/v3d/ConcavePolygonExtrusionTest.java index b2327585..07810c19 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/ConcavePolygonExtrusionTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/ConcavePolygonExtrusionTest.java @@ -2,11 +2,6 @@ import static org.junit.Assert.*; -import java.util.ArrayList; -import java.util.Arrays; - -import org.junit.Test; - public class ConcavePolygonExtrusionTest { Vector3d[] helvetica_H = {new Vector3d(2.9375, -21.875, 0.0), new Vector3d(5.90625, -21.875, 0.0), new Vector3d(5.90625, -12.90625, 0.0), new Vector3d(16.65625, -12.90625, 0.0), @@ -222,29 +217,4 @@ public class ConcavePolygonExtrusionTest { new Vector3d(39.39875030517578, -9.593905448913574, 0.0), new Vector3d(39.41316604614258, -9.238319396972656, 0.0)}; - @Test - public void test() throws ColinearPointsException { - ArrayList H_points = new ArrayList(Arrays.asList(helvetica_H)); - ArrayList e_points = new ArrayList(Arrays.asList(helvetica_e)); - - CSG HLetter = Extrude.points(new Vector3d(0, 0, 10), H_points); - CSG eLetter = Extrude.points(new Vector3d(0, 0, 10), e_points); - - // System.out.println("H number of polygons: " + HLetter.getPolygons().size()); - // System.out.println("e number of polygons: " + eLetter.getPolygons().size()); - - CSG.setDefaultOptType(CSG.OptType.CSG_BOUND); - - CSG simpleUnionOfNonIntersectingBodies = eLetter.union(HLetter); - - int numPolysExpected = HLetter.getPolygons().size() + eLetter.getPolygons().size(); - - // System.out.println("Both number of polygons: " + - // simpleUnionOfNonIntersectingBodies.getPolygons().size()); - - // assumption only valid if optimization is enabled! (see above) - // assumption is wrong for CSG.setDefaultOptType(CSG.OptType.NONE); - assertTrue(numPolysExpected == simpleUnionOfNonIntersectingBodies.getPolygons().size()); - } - } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/DodecahedronTest.java b/src/test/java/eu/mihosoft/vrl/v3d/DodecahedronTest.java index 11c99841..08aec24b 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/DodecahedronTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/DodecahedronTest.java @@ -4,23 +4,25 @@ import org.junit.Test; +import com.neuronrobotics.manifold3d.NonManifoldShapeError; + import java.io.IOException; import java.nio.file.Paths; public class DodecahedronTest { @Test - public void test() throws IOException { + public void test() throws IOException, ColinearPointsException, NonManifoldShapeError { double radius = 10; CSG dodecahedron = new Dodecahedron(radius).toCSG(); CSG box = new Cube(3 * radius).toCSG().difference(new Cube(2 * radius).toCSG()); CSG insphere = new Sphere(0.794654472292 * radius).toCSG(); - assertTrue(dodecahedron.intersect(box).getPolygons().size() == 0); - assertTrue(insphere.difference(dodecahedron).getPolygons().size() == 0); + assertTrue(dodecahedron.intersect(box).getNumberOfTriangles() == 0); + assertTrue(insphere.difference(dodecahedron).getNumberOfTriangles() == 0); - FileUtil.write(Paths.get("dodecahedron.stl"), dodecahedron.toStlString()); + FileUtil.write(Paths.get("dodecahedron.stl"), dodecahedron.toStlString(true)); } } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/IcosahedronTest.java b/src/test/java/eu/mihosoft/vrl/v3d/IcosahedronTest.java index cef9c1e2..48e22974 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/IcosahedronTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/IcosahedronTest.java @@ -4,13 +4,15 @@ import org.junit.Test; +import com.neuronrobotics.manifold3d.NonManifoldShapeError; + import java.io.IOException; import java.nio.file.Paths; public class IcosahedronTest { @Test - public void test() throws IOException { + public void test() throws IOException, ColinearPointsException, NonManifoldShapeError { double radius = 10; CSG icosahedron = new Icosahedron(radius).toCSG(); @@ -20,7 +22,7 @@ public void test() throws IOException { // assertTrue(icosahedron.intersect(box).getPolygons().size() == 0); // assertTrue(insphere.difference(icosahedron).getPolygons().size() == 0); // - FileUtil.write(Paths.get("icosahedron.stl"), icosahedron.toStlString()); + FileUtil.write(Paths.get("icosahedron.stl"), icosahedron.toStlString(true)); } } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java new file mode 100644 index 00000000..d56b123a --- /dev/null +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -0,0 +1,43 @@ +package eu.mihosoft.vrl.v3d; + +import java.io.File; +import java.nio.file.Paths; +import java.util.List; + +import org.junit.Test; + +import eu.mihosoft.vrl.v3d.CSG.OptType; +import eu.mihosoft.vrl.v3d.svg.SVGExporter; + +public class Manifold3d_test { + @Test + public void loadTest() throws Throwable { + OptType og = CSG.getDefaultOptionType(); + + try { + CSG.setDefaultOptType(OptType.Manifold3d); + CSG cube = new Cube(50, 50, 50).toCSG(); + CSG sphere = new Sphere(30, 10, 10).toCSG(); + FileUtil.write(Paths.get("Manifole-sphere.stl"), sphere.toStlString()); + List polygons = Slice.slice(sphere, new Transform(), 0); + SVGExporter.export(polygons, new File("Manifold-SVGExportTest.svg"), false); + CSG difference = cube.difference(sphere); + CSG intersect = cube.intersect(sphere); + CSG union = cube.union(sphere); + CSG mirrored = union.mirrorx(); + FileUtil.write(Paths.get("Manifole-mirror.stl"), mirrored.toStlString()); + FileUtil.write(Paths.get("Manifole-union.stl"), union.toStlString()); + FileUtil.write(Paths.get("Manifole-difference.stl"), difference.toStlString()); + FileUtil.write(Paths.get("Manifole-intersect.stl"), intersect.toStlString()); + CSG hull = union.hull(); + FileUtil.write(Paths.get("Manifole-hull.stl"), hull.toStlString()); + } catch (Throwable t) { + t.printStackTrace(); + // Set back to default to complete test and not disrupt other tests + CSG.setDefaultOptType(og); + throw t; + } + // Set back to default to complete test and not disrupt other tests + CSG.setDefaultOptType(og); + } +} diff --git a/src/test/java/eu/mihosoft/vrl/v3d/OctahedronTest.java b/src/test/java/eu/mihosoft/vrl/v3d/OctahedronTest.java index 9c30383b..329c2df5 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/OctahedronTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/OctahedronTest.java @@ -17,8 +17,8 @@ public void test() throws IOException { CSG box = new Cube(3 * radius).toCSG().difference(new Cube(2 * radius).toCSG()); CSG insphere = new Sphere(Math.sqrt(6) / 6 * radius).toCSG(); - assertTrue(octahedron.intersect(box).getPolygons().size() == 0); - assertTrue(insphere.difference(octahedron).getPolygons().size() == 0); + assertTrue(octahedron.intersect(box).getNumberOfTriangles() == 0); + assertTrue(insphere.difference(octahedron).getNumberOfTriangles() == 0); FileUtil.write(Paths.get("octahedron.stl"), octahedron.toStlString()); } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/ServerClientTest.java b/src/test/java/eu/mihosoft/vrl/v3d/ServerClientTest.java index a3280dbd..eb80e640 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/ServerClientTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/ServerClientTest.java @@ -60,13 +60,13 @@ public CSG prep(CSG incoming) { CSG dif = new Cube(100, 100, 1).toCSG(); dif.getBounds(); - int apoly1 = a.getPolygons().size(); - int bpoly1 = b.getPolygons().size(); + long apoly1 = a.getNumberOfTriangles(); + long bpoly1 = b.getNumberOfTriangles(); CSG u1 = a.union(b, c); CSG i1 = c.intersect(b); CSG d1 = a.difference(b, dif); - CSG t1 = d1.clone().triangulate(true); + CSG t1 = d1.clone().makeManifold(true); ArrayList m1 = a.minkowskiHullShape(b); CSG h1 = u1.hull(); @@ -75,8 +75,8 @@ public CSG prep(CSG incoming) { CSG.setMinPolygonsForOffloading(4); // Connect to server System.out.println("Client info: " + CSGClient.getClient().getServerInfo()); - int apoly = a.getPolygons().size(); - int bpoly = b.getPolygons().size(); + long apoly = a.getNumberOfTriangles(); + long bpoly = b.getNumberOfTriangles(); CSG u = a.union(b, c); if (testPoly(u1, u)) fail(); @@ -85,8 +85,9 @@ public CSG prep(CSG incoming) { fail(); CSG d = a.difference(b, dif); if (testPoly(d1, d)) - fail("Difference Step fail , expected " + d1.getPolygons().size() + " got " + d.getPolygons().size()); - CSG t = d.clone().triangulate(true); + fail("Difference Step fail , expected " + d1.getNumberOfTriangles() + " got " + + d.getNumberOfTriangles()); + CSG t = d.clone().makeManifold(true); if (testPoly(t1, t)) fail(); ArrayList m = a.minkowskiHullShape(b); @@ -114,16 +115,19 @@ public CSG prep(CSG incoming) { System.out.println("\nClient example completed."); } - boolean testPoly(CSG p1, CSG p2) { - int size1 = p1.getPolygons().size(); - int size2 = p2.getPolygons().size(); + boolean testPoly(CSG p1, CSG p2) throws ColinearPointsException { + long size1 = p1.getNumberOfTriangles(); + long size2 = p2.getNumberOfTriangles(); if (size1 != size2) { System.err.println("Mismatched number of polygons expected " + size1 + " but got " + size2); return true; } + ArrayList p1p = p1.generatePolygonsFromMesh(); + ArrayList p2p = p2.generatePolygonsFromMesh(); + for (int i = 0; i < size1; i++) { - Polygon poly1 = p1.getPolygons().get(i); - Polygon poly2 = p2.getPolygons().get(i); + Polygon poly1 = p1p.get(i); + Polygon poly2 = p2p.get(i); int size = poly1.getPoints().size(); if (size != poly2.getPoints().size()) { System.err.println("Number of Points mismatch "); diff --git a/src/test/java/eu/mihosoft/vrl/v3d/SliceTest.java b/src/test/java/eu/mihosoft/vrl/v3d/SliceTest.java index 948e2a86..a81f35cf 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/SliceTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/SliceTest.java @@ -22,7 +22,7 @@ public void slice() throws Exception { List polygons = Slice.slice(carrot, new Transform(), 0); // Construct a CSG from that Polygon List - CSG finished = CSG.fromPolygons(new ArrayList<>(polygons)); + CSG finished = new CSG(new ArrayList<>(polygons)); // System.out.println(finished.toStlString()); } @@ -36,7 +36,7 @@ public void sliceWithHole() throws Exception { List polygons = Slice.slice(carrot, new Transform(), 0); // Construct a CSG from that Polygon List - CSG finished = CSG.fromPolygons(new ArrayList<>(polygons)); + CSG finished = new CSG(new ArrayList<>(polygons)); // System.out.println(finished.toStlString()); } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/StlExportTest.java b/src/test/java/eu/mihosoft/vrl/v3d/StlExportTest.java index 82026af9..96f1dde9 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/StlExportTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/StlExportTest.java @@ -8,12 +8,14 @@ import org.junit.Test; +import eu.mihosoft.vrl.v3d.CSG.OptType; + public class StlExportTest { @Test public void makeBadSTL() throws IOException { - + CSG.setDefaultOptType(OptType.Manifold3d); // Vector3d.setEXPORTEPSILON(1.0e-10); CSG.setUseGPU(true); CSG.setPreventNonManifoldTriangles(true); diff --git a/src/test/java/eu/mihosoft/vrl/v3d/StlLoadTest.java b/src/test/java/eu/mihosoft/vrl/v3d/StlLoadTest.java index 4a288e5a..ff739d29 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/StlLoadTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/StlLoadTest.java @@ -42,7 +42,7 @@ public void tower() throws IOException { // Auto-generated catch block e.printStackTrace(); } - if (loaded.getPolygons().size() / 2 > diff.getPolygons().size()) { + if (loaded.getNumberOfTriangles() / 2 > diff.getNumberOfTriangles()) { fail("Failed perform difference without losing information!"); } } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/SvgExportTest.java b/src/test/java/eu/mihosoft/vrl/v3d/SvgExportTest.java index ce82fdcc..4cffb273 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/SvgExportTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/SvgExportTest.java @@ -8,12 +8,19 @@ import java.util.Arrays; import java.util.List; +import org.junit.BeforeClass; import org.junit.Test; +import eu.mihosoft.vrl.v3d.CSG.OptType; import eu.mihosoft.vrl.v3d.svg.SVGExporter; import eu.mihosoft.vrl.v3d.svg.SVGLoad; public class SvgExportTest { + @BeforeClass + public static void init() { + JavaFXInitializer.go(); + CSG.setDefaultOptType(OptType.Manifold3d); + } @Test public void slicetest() throws IOException, ColinearPointsException { @@ -35,7 +42,8 @@ public void slicetest() throws IOException, ColinearPointsException { CSG incoming = main.difference(cut).intersect(new Cube(400, 400, 2).toCSG()); List polygons = Slice.slice(incoming, slicePlane, normalInsetDistance); - + if (polygons.size() == 0) + fail(); SVGExporter.export(polygons, new File("SVGExportTest.svg"), false); } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/TestFlatData.java b/src/test/java/eu/mihosoft/vrl/v3d/TestFlatData.java new file mode 100644 index 00000000..138cab06 --- /dev/null +++ b/src/test/java/eu/mihosoft/vrl/v3d/TestFlatData.java @@ -0,0 +1,20 @@ +package eu.mihosoft.vrl.v3d; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; + +import org.junit.Test; + +public class TestFlatData { + + @Test + public void testFlatData() throws ColinearPointsException, IOException { + CSG cube = new Cube(20).toCSG(); + FileUtil.write(Paths.get("FlatData-cube.stl"), cube.toStlString()); + ArrayList polygons = cube.generatePolygonsFromMesh(); + CSG fromPoly = new CSG(polygons); + FileUtil.write(Paths.get("FlatData-loaded.stl"), fromPoly.toStlString()); + + } +} diff --git a/src/test/java/eu/mihosoft/vrl/v3d/TetrahedronTest.java b/src/test/java/eu/mihosoft/vrl/v3d/TetrahedronTest.java index 19351da8..f42f9372 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/TetrahedronTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/TetrahedronTest.java @@ -17,8 +17,8 @@ public void test() throws IOException { CSG box = new Cube(3 * radius).toCSG().difference(new Cube(2 * radius).toCSG()); CSG insphere = new Sphere(1.0 / 3.0).toCSG(); - assertTrue(tetrahedron.intersect(box).getPolygons().size() == 0); - assertTrue(insphere.difference(tetrahedron).getPolygons().size() == 0); + assertTrue(tetrahedron.intersect(box).getNumberOfTriangles() == 0); + assertTrue(insphere.difference(tetrahedron).getNumberOfTriangles() == 0); FileUtil.write(Paths.get("tetrahedron.stl"), tetrahedron.toStlString()); }