.
*/
package eu.mihosoft.jcsg;
+import static eu.mihosoft.jcsg.STL.file;
import eu.mihosoft.vvecmath.Vector3d;
import eu.mihosoft.vvecmath.Transform;
import eu.mihosoft.jcsg.ext.quickhull3d.HullUtil;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.scene.paint.Color;
@@ -53,18 +53,15 @@
* This implementation is a Java port of
* https://github.com/evanw/csg.js/
- * with some additional features like polygon extrude, transformations etc.
- * Thanks to the author for creating the CSG.js library.
+ * with some additional features like polygon extrude, transformations etc. Thanks to the author for creating the CSG.js
+ * library.
*
* Implementation Details
*
- * All CSG operations are implemented in terms of two functions,
- * {@link Node#clipTo(Node)} and {@link Node#invert()},
- * which remove parts of a BSP tree inside another BSP tree and swap solid and
- * empty space, respectively. To find the union of {@code a} and {@code b}, we
- * want to remove everything in {@code a} inside {@code b} and everything in
- * {@code b} inside {@code a}, then combine polygons from {@code a} and
- * {@code b} into one solid:
+ * All CSG operations are implemented in terms of two functions, {@link Node#clipTo(Node)} and {@link Node#invert()},
+ * which remove parts of a BSP tree inside another BSP tree and swap solid and empty space, respectively. To find the
+ * union of {@code a} and {@code b}, we want to remove everything in {@code a} inside {@code b} and everything in
+ * {@code b} inside {@code a}, then combine polygons from {@code a} and {@code b} into one solid:
*
*
* a.clipTo(b);
@@ -72,11 +69,9 @@
* a.build(b.allPolygons());
*
*
- * The only tricky part is handling overlapping coplanar polygons in both trees.
- * The code above keeps both copies, but we need to keep them in one tree and
- * remove them in the other tree. To remove them from {@code b} we can clip the
- * inverse of {@code b} against {@code a}. The code for union now looks like
- * this:
+ * The only tricky part is handling overlapping coplanar polygons in both trees. The code above keeps both copies, but
+ * we need to keep them in one tree and remove them in the other tree. To remove them from {@code b} we can clip the
+ * inverse of {@code b} against {@code a}. The code for union now looks like this:
*
*
* a.clipTo(b);
@@ -87,9 +82,8 @@
* a.build(b.allPolygons());
*
*
- * Subtraction and intersection naturally follow from set operations. If union
- * is {@code A | B}, differenceion is {@code A - B = ~(~A | B)} and intersection
- * is {@code A & B =
+ * Subtraction and intersection naturally follow from set operations. If union is {@code A | B}, differenceion is
+ * {@code A - B = ~(~A | B)} and intersection is {@code A & B =
* ~(~A | ~B)} where {@code ~} is the complement operator.
*/
public class CSG {
@@ -204,8 +198,7 @@ public CSG optimization(OptType type) {
}
/**
- * Return a new CSG solid representing the union of this csg and the
- * specified csg.
+ * Return a new CSG solid representing the union of this csg and the specified csg.
*
* Note: Neither this csg nor the specified csg are weighted.
*
@@ -239,33 +232,32 @@ public CSG union(CSG csg) {
return _unionNoOpt(csg);
}
}
-
+
/**
* Returns a csg consisting of the polygons of this csg and the specified csg.
- *
- * The purpose of this method is to allow fast union operations for objects
- * that do not intersect.
- *
- * WARNING: this method does not apply the csg algorithms. Therefore,
- * please ensure that this csg and the specified csg do not intersect.
- *
+ *
+ * The purpose of this method is to allow fast union operations for objects that do not intersect.
+ *
+ *
+ * WARNING: this method does not apply the csg algorithms. Therefore, please ensure that this csg and the
+ * specified csg do not intersect.
+ *
* @param csg csg
- *
+ *
* @return a csg consisting of the polygons of this csg and the specified csg
*/
public CSG dumbUnion(CSG csg) {
-
+
CSG result = this.clone();
CSG other = csg.clone();
-
+
result.polygons.addAll(other.polygons);
-
+
return result;
}
/**
- * Return a new CSG solid representing the union of this csg and the
- * specified csgs.
+ * Return a new CSG solid representing the union of this csg and the specified csgs.
*
* Note: Neither this csg nor the specified csg are weighted.
*
@@ -299,8 +291,7 @@ public CSG union(List csgs) {
}
/**
- * Return a new CSG solid representing the union of this csg and the
- * specified csgs.
+ * Return a new CSG solid representing the union of this csg and the specified csgs.
*
* Note: Neither this csg nor the specified csg are weighted.
*
@@ -412,9 +403,8 @@ private CSG _unionPolygonBoundsOpt(CSG csg) {
}
/**
- * Optimizes for intersection. If csgs do not intersect create a new csg
- * that consists of the polygon lists of this csg and the specified csg. In
- * this case no further space partitioning is performed.
+ * Optimizes for intersection. If csgs do not intersect create a new csg that consists of the polygon lists of this
+ * csg and the specified csg. In this case no further space partitioning is performed.
*
* @param csg csg
* @return the union of this csg and the specified csg
@@ -456,8 +446,7 @@ private CSG _unionNoOpt(CSG csg) {
}
/**
- * Return a new CSG solid representing the difference of this csg and the
- * specified csgs.
+ * Return a new CSG solid representing the difference of this csg and the specified csgs.
*
* Note: Neither this csg nor the specified csgs are weighted.
*
@@ -493,8 +482,7 @@ public CSG difference(List csgs) {
}
/**
- * Return a new CSG solid representing the difference of this csg and the
- * specified csgs.
+ * Return a new CSG solid representing the difference of this csg and the specified csgs.
*
* Note: Neither this csg nor the specified csgs are weighted.
*
@@ -520,8 +508,7 @@ public CSG difference(CSG... csgs) {
}
/**
- * Return a new CSG solid representing the difference of this csg and the
- * specified csg.
+ * Return a new CSG solid representing the difference of this csg and the specified csg.
*
* Note: Neither this csg nor the specified csg are weighted.
*
@@ -604,8 +591,7 @@ private CSG _differenceNoOpt(CSG csg) {
}
/**
- * Return a new CSG solid representing the intersection of this csg and the
- * specified csg.
+ * Return a new CSG solid representing the intersection of this csg and the specified csg.
*
* Note: Neither this csg nor the specified csg are weighted.
*
@@ -641,8 +627,7 @@ public CSG intersect(CSG csg) {
}
/**
- * Return a new CSG solid representing the intersection of this csg and the
- * specified csgs.
+ * Return a new CSG solid representing the intersection of this csg and the specified csgs.
*
* Note: Neither this csg nor the specified csgs are weighted.
*
@@ -679,8 +664,7 @@ public CSG intersect(List csgs) {
}
/**
- * Return a new CSG solid representing the intersection of this csg and the
- * specified csgs.
+ * Return a new CSG solid representing the intersection of this csg and the specified csgs.
*
* Note: Neither this csg nor the specified csgs are weighted.
*
@@ -717,6 +701,7 @@ public String toStlString() {
return sb.toString();
}
+
/**
* Returns this csg in STL string format.
*
@@ -747,6 +732,17 @@ public CSG color(Color c) {
}
public ObjFile toObj() {
+ // we triangulate the polygon to ensure
+ // compatibility with 3d printer software
+ return toObj(3);
+ }
+
+ public ObjFile toObj(int maxNumberOfVerts) {
+
+ if (maxNumberOfVerts != 3) {
+ throw new UnsupportedOperationException(
+ "maxNumberOfVerts > 3 not supported yet");
+ }
StringBuilder objSb = new StringBuilder();
@@ -822,6 +818,12 @@ public PolygonStruct(PropertyStorage storage, List indices, String mate
append(index2).append(" ").
append(index3).append("\n");
}
+//
+// objSb.append("f ");
+// for (int i = 0; i < pVerts.size(); i++) {
+// objSb.append(pVerts.get(i)).append(" ");
+// }
+ objSb.append("\n");
}
objSb.append("\n# End Group v3d.csg").append("\n");
@@ -941,7 +943,6 @@ public CSG transformed(Transform transform) {
return result;
}
-
// TODO finish experiment (20.7.2014)
public MeshContainer toJavaFXMesh() {
@@ -1109,13 +1110,15 @@ public Bounds getBounds() {
return new Bounds(Vector3d.ZERO, Vector3d.ZERO);
}
- double minX = Double.POSITIVE_INFINITY;
- double minY = Double.POSITIVE_INFINITY;
- double minZ = Double.POSITIVE_INFINITY;
+ Vector3d initial = polygons.get(0).vertices.get(0).pos;
- double maxX = Double.NEGATIVE_INFINITY;
- double maxY = Double.NEGATIVE_INFINITY;
- double maxZ = Double.NEGATIVE_INFINITY;
+ double minX = initial.x();
+ double minY = initial.y();
+ double minZ = initial.z();
+
+ double maxX = initial.x();
+ double maxY = initial.y();
+ double maxZ = initial.z();
for (Polygon p : getPolygons()) {
@@ -1180,4 +1183,46 @@ public static enum OptType {
NONE
}
+ /**
+ * Computes and returns the volume of this CSG based on a triangulated version
+ * of the internal mesh.
+ * @return volume of this csg
+ */
+ public double computeVolume() {
+ if(getPolygons().isEmpty()) return 0;
+
+ // triangulate polygons (parallel for larger meshes)
+ Stream polyStream;
+ if(getPolygons().size()>200) {
+ polyStream = getPolygons().parallelStream();
+ } else {
+ polyStream = getPolygons().stream();
+ }
+ List triangles = polyStream.
+ flatMap(poly->poly.toTriangles().stream()).
+ collect(Collectors.toList());
+
+ // compute sum over signed volumes of triangles
+ // we use parallel streams for larger meshes
+ // see http://chenlab.ece.cornell.edu/Publication/Cha/icip01_Cha.pdf
+ Stream triangleStream;
+ if(triangles.size()>200) {
+ triangleStream = triangles.parallelStream();
+ } else {
+ triangleStream = triangles.stream();
+ }
+
+ double volume = triangleStream.mapToDouble(tri-> {
+ Vector3d p1 = tri.vertices.get(0).pos;
+ Vector3d p2 = tri.vertices.get(1).pos;
+ Vector3d p3 = tri.vertices.get(2).pos;
+
+ return p1.dot(p2.crossed(p3)) / 6.0;
+ }).sum();
+
+ volume = Math.abs(volume);
+
+ return volume;
+ }
+
}
diff --git a/src/main/java/eu/mihosoft/jcsg/Cube.java b/src/main/java/eu/mihosoft/jcsg/Cube.java
index 3d314df4..53e755a1 100644
--- a/src/main/java/eu/mihosoft/jcsg/Cube.java
+++ b/src/main/java/eu/mihosoft/jcsg/Cube.java
@@ -36,6 +36,7 @@
import eu.mihosoft.vvecmath.Vector3d;
import eu.mihosoft.vvecmath.Transform;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -103,7 +104,66 @@ public Cube(double w, double h, double d) {
this(Vector3d.ZERO, Vector3d.xyz(w, h, d));
}
- @Override
+// public List toPolygons() {
+// List result = new ArrayList<>(6);
+//
+// Vector3d centerOffset = dimensions.times(0.5);
+//
+// result.addAll(Arrays.asList(new Polygon[]{
+// Polygon.fromPoints(
+// centerOffset.times(-1, -1, -1),
+// centerOffset.times(1, -1, -1),
+// centerOffset.times(1, -1, 1),
+// centerOffset.times(-1, -1, 1)
+// ),
+// Polygon.fromPoints(
+// centerOffset.times(1, -1, -1),
+// centerOffset.times(1, 1, -1),
+// centerOffset.times(1, 1, 1),
+// centerOffset.times(1, -1, 1)
+// ),
+// Polygon.fromPoints(
+// centerOffset.times(1, 1, -1),
+// centerOffset.times(-1, 1, -1),
+// centerOffset.times(-1, 1, 1),
+// centerOffset.times(1, 1, 1)
+// ),
+// Polygon.fromPoints(
+// centerOffset.times(1, 1, 1),
+// centerOffset.times(-1, 1, 1),
+// centerOffset.times(-1, -1, 1),
+// centerOffset.times(1, -1, 1)
+// ),
+// Polygon.fromPoints(
+// centerOffset.times(-1, 1, 1),
+// centerOffset.times(-1, 1, -1),
+// centerOffset.times(-1, -1, -1),
+// centerOffset.times(-1, -1, 1)
+// ),
+// Polygon.fromPoints(
+// centerOffset.times(-1, 1, -1),
+// centerOffset.times(1, 1, -1),
+// centerOffset.times(1, -1, -1),
+// centerOffset.times(-1, -1, -1)
+// )
+// }
+// ));
+//
+// if(!centered) {
+// Transform centerTransform = Transform.unity().
+// translate(dimensions.x() / 2.0,
+// dimensions.y() / 2.0,
+// dimensions.z() / 2.0);
+//
+// for (Polygon p : result) {
+// p.transform(centerTransform);
+// }
+// }
+//
+// return result;
+// }
+
+
public List toPolygons() {
int[][][] a = {
@@ -183,6 +243,7 @@ public PropertyStorage getProperties() {
/**
* Defines that this cube will not be centered.
+ *
* @return this cube
*/
public Cube noCenter() {
diff --git a/src/main/java/eu/mihosoft/jcsg/Cylinder.java b/src/main/java/eu/mihosoft/jcsg/Cylinder.java
index 80ebfad7..122a12da 100644
--- a/src/main/java/eu/mihosoft/jcsg/Cylinder.java
+++ b/src/main/java/eu/mihosoft/jcsg/Cylinder.java
@@ -150,8 +150,8 @@ public List toPolygons() {
final Vector3d axisZ = ray.normalized();
boolean isY = (Math.abs(axisZ.y()) > 0.5);
final Vector3d axisX = Vector3d.xyz(isY ? 1 : 0, !isY ? 1 : 0, 0).
- cross(axisZ).normalized();
- final Vector3d axisY = axisX.cross(axisZ).normalized();
+ crossed(axisZ).normalized();
+ final Vector3d axisY = axisX.crossed(axisZ).normalized();
Vertex startV = new Vertex(s, axisZ.negated());
Vertex endV = new Vertex(e, axisZ.normalized());
List polygons = new ArrayList<>();
diff --git a/src/main/java/eu/mihosoft/jcsg/Edge.java b/src/main/java/eu/mihosoft/jcsg/Edge.java
index ac3d72ad..86dd106a 100644
--- a/src/main/java/eu/mihosoft/jcsg/Edge.java
+++ b/src/main/java/eu/mihosoft/jcsg/Edge.java
@@ -599,11 +599,11 @@ private static List boundaryEdgesOfPlaneGroup(List planeGroup) {
Stream pStream;
- if (planeGroup.size() > 200) {
- pStream = planeGroup.parallelStream();
- } else {
+ // if (planeGroup.size() > 200) {
+ // pStream = planeGroup.parallelStream();
+ // } else {
pStream = planeGroup.stream();
- }
+ // }
pStream.map((p) -> Edge.fromPolygon(p)).forEach((pEdges) -> {
edges.addAll(pEdges);
@@ -611,11 +611,11 @@ private static List boundaryEdgesOfPlaneGroup(List planeGroup) {
Stream edgeStream;
- if (edges.size() > 200) {
- edgeStream = edges.parallelStream();
- } else {
+ // if (edges.size() > 200) {
+ // edgeStream = edges.parallelStream();
+ // } else {
edgeStream = edges.stream();
- }
+ // }
// find potential boundary edges, i.e., edges that occur once (freq=1)
List potentialBoundaryEdges = new ArrayList<>();
@@ -716,8 +716,8 @@ private static List> searchPlaneGroups(List polygons) {
continue;
}
- Vector3d nOuter = pOuter.plane.normal;
- Vector3d nInner = pInner.plane.normal;
+ Vector3d nOuter = pOuter._csg_plane.normal;
+ Vector3d nInner = pInner._csg_plane.normal;
// TODO do we need radians or degrees?
double angle = nOuter.angle(nInner);
diff --git a/src/main/java/eu/mihosoft/jcsg/Extrude.java b/src/main/java/eu/mihosoft/jcsg/Extrude.java
index 1b0a32ba..c0072b03 100644
--- a/src/main/java/eu/mihosoft/jcsg/Extrude.java
+++ b/src/main/java/eu/mihosoft/jcsg/Extrude.java
@@ -1,7 +1,7 @@
/**
* Extrude.java
*
- * Copyright 2014-2014 Michael Hoffer . All rights
+ * Copyright 2014-2017 Michael Hoffer . All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -33,6 +33,7 @@
*/
package eu.mihosoft.jcsg;
+import eu.mihosoft.vvecmath.Transform;
import eu.mihosoft.vvecmath.Vector3d;
import eu.mihosoft.jcsg.ext.org.poly2tri.PolygonUtil;
import java.util.ArrayList;
@@ -82,6 +83,105 @@ public static CSG points(Vector3d dir, List points) {
return extrude(dir, Polygon.fromPoints(toCCW(newList)));
}
+
+ /**
+ * Extrudes the specified path (convex or concave polygon without holes or
+ * intersections, specified in CCW) into the specified direction.
+ *
+ * @param dir direction
+ * @param points path (convex or concave polygon without holes or
+ * intersections)
+ *
+ * @return a list containing the extruded polygon
+ */
+ public static List points(Vector3d dir, boolean top, boolean bottom, Vector3d... points) {
+
+ return extrude(dir, Polygon.fromPoints(toCCW(Arrays.asList(points))), top, bottom);
+ }
+
+ /**
+ * Extrudes the specified path (convex or concave polygon without holes or
+ * intersections, specified in CCW) into the specified direction.
+ *
+ * @param dir direction
+ * @param points1 path (convex or concave polygon without holes or
+ * intersections)
+ * @param points1 path (convex or concave polygon without holes or
+ * intersections)
+ *
+ * @return a list containing the extruded polygon
+ */
+ public static List points(Vector3d dir, boolean top, boolean bottom, List points1) {
+
+ List newList1 = new ArrayList<>(points1);
+
+ return extrude(dir, Polygon.fromPoints(toCCW(newList1)), top, bottom);
+ }
+
+ /**
+ * Combines two polygons into one CSG object. Polygons p1 and p2 are treated as top and
+ * bottom of a tube segment with p1 and p2 as the profile. Note: both polygons must have the
+ * same number of vertices. This method does not guarantee intersection-free CSGs. It is in the
+ * responsibility of the caller to ensure that the orientation of p1 and p2 allow for
+ * intersection-free combination of both.
+ *
+ * @param p1 first polygon
+ * @param p2 second polygon
+ * @return List of polygons
+ */
+ public static CSG combine(Polygon p1, Polygon p2) {
+ return CSG.fromPolygons(combine(p1,p2,true,true));
+ }
+
+ /**
+ * Combines two polygons into one CSG object. Polygons p1 and p2 are treated as top and
+ * bottom of a tube segment with p1 and p2 as the profile. Note: both polygons must have the
+ * same number of vertices. This method does not guarantee intersection-free CSGs. It is in the
+ * responsibility of the caller to ensure that the orientation of p1 and p2 allow for
+ * intersection-free combination of both.
+ *
+ * @param p1 first polygon
+ * @param p2 second polygon
+ * @param bottom defines whether to close the bottom of the tube
+ * @param top defines whether to close the top of the tube
+ * @return List of polygons
+ */
+ public static List combine(Polygon p1, Polygon p2, boolean bottom, boolean top) {
+ List newPolygons = new ArrayList<>();
+
+ if (p1.vertices.size() != p2.vertices.size()) {
+ throw new RuntimeException("Polygons must have the same number of vertices");
+ }
+
+ int numVertices = p1.vertices.size();
+
+ if (bottom) {
+ newPolygons.add(p1.flipped());
+ }
+
+ for (int i = 0; i < numVertices; i++) {
+
+ int nexti = (i + 1) % numVertices;
+
+ Vector3d bottomV1 = p1.vertices.get(i).pos;
+ Vector3d topV1 = p2.vertices.get(i).pos;
+ Vector3d bottomV2 = p1.vertices.get(nexti).pos;
+ Vector3d topV2 = p2.vertices.get(nexti).pos;
+
+ List pPoints;
+
+ pPoints = Arrays.asList(bottomV2, topV2, topV1);
+ newPolygons.add(Polygon.fromPoints(pPoints, p1.getStorage()));
+ pPoints = Arrays.asList(bottomV2, topV1, bottomV1);
+ newPolygons.add(Polygon.fromPoints(pPoints, p1.getStorage()));
+ }
+
+ if (top) {
+ newPolygons.add(p2);
+ }
+
+ return newPolygons;
+ }
private static CSG extrude(Vector3d dir, Polygon polygon1) {
List newPolygons = new ArrayList<>();
@@ -118,6 +218,78 @@ private static CSG extrude(Vector3d dir, Polygon polygon1) {
}
+
+ private static List extrude(Vector3d dir, Polygon polygon1, boolean top, boolean bottom) {
+ List newPolygons = new ArrayList<>();
+
+
+ if (bottom) {
+ newPolygons.addAll(PolygonUtil.concaveToConvex(polygon1));
+ }
+
+ Polygon polygon2 = polygon1.translated(dir);
+
+ Transform rot = Transform.unity();
+
+ Vector3d a = polygon2.getPlane().getNormal().normalized();
+ Vector3d b = dir.normalized();
+
+ Vector3d c = a.crossed(b);
+
+ double l = c.magnitude(); // sine of angle
+
+ if (l > 1e-9) {
+
+ Vector3d axis = c.times(1.0 / l);
+ double angle = a.angle(b);
+
+ double sx = 0;
+ double sy = 0;
+ double sz = 0;
+
+ int n = polygon2.vertices.size();
+
+ for (Vertex v : polygon2.vertices) {
+ sx += v.pos.x();
+ sy += v.pos.y();
+ sz += v.pos.z();
+ }
+
+ Vector3d center = Vector3d.xyz(sx / n, sy / n, sz / n);
+
+ rot = rot.rot(center, axis, angle * Math.PI / 180.0);
+
+ for (Vertex v : polygon2.vertices) {
+ v.pos = rot.transform(v.pos);
+ }
+ }
+
+ int numvertices = polygon1.vertices.size();
+ for (int i = 0; i < numvertices; i++) {
+
+ int nexti = (i + 1) % numvertices;
+
+ Vector3d bottomV1 = polygon1.vertices.get(i).pos;
+ Vector3d topV1 = polygon2.vertices.get(i).pos;
+ Vector3d bottomV2 = polygon1.vertices.get(nexti).pos;
+ Vector3d topV2 = polygon2.vertices.get(nexti).pos;
+
+ List pPoints = Arrays.asList(bottomV2, topV2, topV1, bottomV1);
+
+ newPolygons.add(Polygon.fromPoints(pPoints, polygon1.getStorage()));
+ }
+
+ polygon2 = polygon2.flipped();
+ List topPolygons = PolygonUtil.concaveToConvex(polygon2);
+ if (top) {
+ newPolygons.addAll(topPolygons);
+ }
+
+ return newPolygons;
+
+ }
+
+
static List toCCW(List points) {
List result = new ArrayList<>(points);
@@ -140,6 +312,12 @@ static List toCW(List points) {
return result;
}
+ /**
+ * Indicates whether the specified polygon is defined counter-clockwise.
+ * @param polygon polygon
+ * @return {@code true} if the specified polygon is defined counter-clockwise;
+ * {@code false} otherwise
+ */
public static boolean isCCW(Polygon polygon) {
// thanks to Sepp Reiter for explaining me the algorithm!
@@ -205,7 +383,7 @@ public static boolean isCCW(Polygon polygon) {
private static double normalizedX(Vector3d v1, Vector3d v2) {
Vector3d v2MinusV1 = v2.minus(v1);
- return v2MinusV1.dividedBy(v2MinusV1.magnitude()).times(Vector3d.X_ONE).x();
+ return v2MinusV1.divided(v2MinusV1.magnitude()).times(Vector3d.X_ONE).x();
}
// public static void main(String[] args) {
diff --git a/src/main/java/eu/mihosoft/jcsg/FileUtil.java b/src/main/java/eu/mihosoft/jcsg/FileUtil.java
index 44c180d9..e3c00490 100644
--- a/src/main/java/eu/mihosoft/jcsg/FileUtil.java
+++ b/src/main/java/eu/mihosoft/jcsg/FileUtil.java
@@ -36,6 +36,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* File util class.
@@ -74,4 +76,30 @@ public static void write(Path p, String s) throws IOException {
public static String read(Path p) throws IOException {
return new String(Files.readAllBytes(p), Charset.forName("UTF-8"));
}
+
+
+ /**
+ * Saves the specified csg using STL ASCII format.
+ *
+ * @param path destination path
+ * @param csg csg to save
+ * @throws java.io.IOException
+ */
+ public static void toStlFile(Path path, CSG csg) throws IOException {
+ try (BufferedWriter out = Files.newBufferedWriter(path, Charset.forName("UTF-8"),
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+
+ out.append("solid v3d.csg\n");
+ csg.getPolygons().stream().forEach(
+ (Polygon p) -> {
+ try {
+ out.append(p.toStlString());
+ } catch (IOException ex) {
+ Logger.getLogger(CSG.class.getName()).log(Level.SEVERE, null, ex);
+ throw new RuntimeException(ex);
+ }
+ });
+ out.append("endsolid v3d.csg\n");
+ }
+ }
}
diff --git a/src/main/java/eu/mihosoft/jcsg/Node.java b/src/main/java/eu/mihosoft/jcsg/Node.java
index c88f2862..c5d91cb1 100644
--- a/src/main/java/eu/mihosoft/jcsg/Node.java
+++ b/src/main/java/eu/mihosoft/jcsg/Node.java
@@ -128,7 +128,7 @@ public void invert() {
});
if (this.plane == null && !polygons.isEmpty()) {
- this.plane = polygons.get(0).plane.clone();
+ this.plane = polygons.get(0)._csg_plane.clone();
} else if (this.plane == null && polygons.isEmpty()) {
System.err.println("Please fix me! I don't know what to do?");
@@ -238,7 +238,7 @@ public final void build(List polygons) {
if (polygons.isEmpty()) return;
if (this.plane == null) {
- this.plane = polygons.get(0).plane.clone();
+ this.plane = polygons.get(0)._csg_plane.clone();
}
polygons = polygons.stream().filter(p->p.isValid()).distinct().collect(Collectors.toList());
diff --git a/src/main/java/eu/mihosoft/jcsg/Plane.java b/src/main/java/eu/mihosoft/jcsg/Plane.java
index 3ef82165..37f1407c 100644
--- a/src/main/java/eu/mihosoft/jcsg/Plane.java
+++ b/src/main/java/eu/mihosoft/jcsg/Plane.java
@@ -94,7 +94,7 @@ public Plane(Vector3d normal, double dist) {
* @return a plane
*/
public static Plane createFromPoints(Vector3d a, Vector3d b, Vector3d c) {
- Vector3d n = b.minus(a).cross(c.minus(a)).normalized();
+ Vector3d n = b.minus(a).crossed(c.minus(a)).normalized();
return new Plane(n, n.dot(a));
}
@@ -152,7 +152,7 @@ public void splitPolygon(
switch (polygonType) {
case COPLANAR:
//System.out.println(" -> coplanar");
- (this.normal.dot(polygon.plane.normal) > 0 ? coplanarFront : coplanarBack).add(polygon);
+ (this.normal.dot(polygon._csg_plane.normal) > 0 ? coplanarFront : coplanarBack).add(polygon);
break;
case FRONT:
//System.out.println(" -> front");
diff --git a/src/main/java/eu/mihosoft/jcsg/Polygon.java b/src/main/java/eu/mihosoft/jcsg/Polygon.java
index e820a0db..faf8f56c 100644
--- a/src/main/java/eu/mihosoft/jcsg/Polygon.java
+++ b/src/main/java/eu/mihosoft/jcsg/Polygon.java
@@ -64,7 +64,17 @@ public final class Polygon {
*
* Note: uses first three vertices to define the plane.
*/
- public final Plane plane;
+ public final Plane _csg_plane;
+ private eu.mihosoft.vvecmath.Plane plane;
+
+ /**
+ * Returns the plane defined by this triangle.
+ *
+ * @return plane
+ */
+ public eu.mihosoft.vvecmath.Plane getPlane() {
+ return plane;
+ }
void setStorage(PropertyStorage storage) {
this.shared = storage;
@@ -95,15 +105,16 @@ public static List fromConcavePoints(List points) {
}
/**
- * Indicates whether this polyon is valid, i.e., if it
- * @return
+ * Indicates whether this polyon is valid, i.e., if it
+ *
+ * @return
*/
public boolean isValid() {
return valid;
}
-
+
private boolean valid = true;
-
+
/**
* Constructor. Creates a new polygon that consists of the specified
* vertices.
@@ -117,30 +128,32 @@ public boolean isValid() {
public Polygon(List vertices, PropertyStorage shared) {
this.vertices = vertices;
this.shared = shared;
- this.plane = Plane.createFromPoints(
+ this._csg_plane = Plane.createFromPoints(
vertices.get(0).pos,
vertices.get(1).pos,
vertices.get(2).pos);
-
+ this.plane = eu.mihosoft.vvecmath.Plane.
+ fromPointAndNormal(centroid(), _csg_plane.normal);
+
validateAndInit(vertices);
}
private void validateAndInit(List vertices1) {
for (Vertex v : vertices1) {
- v.normal = plane.normal;
+ v.normal = _csg_plane.normal;
}
- if (Vector3d.ZERO.equals(plane.normal)) {
+ if (Vector3d.ZERO.equals(_csg_plane.normal)) {
valid = false;
System.err.println(
- "Normal is zero! Probably, duplicate points have been specified!\n\n"+toStlString());
+ "Normal is zero! Probably, duplicate points have been specified!\n\n" + toStlString());
// throw new RuntimeException(
// "Normal is zero! Probably, duplicate points have been specified!\n\n"+toStlString());
- }
-
- if(vertices.size()<3) {
+ }
+
+ if (vertices.size() < 3) {
throw new RuntimeException(
"Invalid polygon: at least 3 vertices expected, got: "
- + vertices.size());
+ + vertices.size());
}
}
@@ -155,11 +168,14 @@ private void validateAndInit(List vertices1) {
*/
public Polygon(List vertices) {
this.vertices = vertices;
- this.plane = Plane.createFromPoints(
+ this._csg_plane = Plane.createFromPoints(
vertices.get(0).pos,
vertices.get(1).pos,
vertices.get(2).pos);
-
+
+ this.plane = eu.mihosoft.vvecmath.Plane.
+ fromPointAndNormal(centroid(), _csg_plane.normal);
+
validateAndInit(vertices);
}
@@ -197,7 +213,8 @@ public Polygon flip() {
});
Collections.reverse(vertices);
- plane.flip();
+ _csg_plane.flip();
+ this.plane = plane.flipped();
return this;
}
@@ -241,8 +258,7 @@ public StringBuilder toStlString(StringBuilder sb) {
String firstVertexStl = this.vertices.get(0).toStlString();
for (int i = 0; i < this.vertices.size() - 2; i++) {
sb.
- append(" facet normal ").append(
- this.plane.normal.toStlString()).append("\n").
+ append(" facet normal ").append(this._csg_plane.normal.toStlString()).append("\n").
append(" outer loop\n").
append(" ").append(firstVertexStl).append("\n").
append(" ");
@@ -257,6 +273,38 @@ public StringBuilder toStlString(StringBuilder sb) {
return sb;
}
+ /**
+ * Returns a triangulated version of this polygon.
+ *
+ * @return triangles
+ */
+ public List toTriangles() {
+
+ List result = new ArrayList<>();
+
+ if (this.vertices.size() >= 3) {
+
+ // TODO: improve the triangulation?
+ //
+ // If our polygon has more vertices, create
+ // multiple triangles:
+ Vertex firstVertexStl = this.vertices.get(0);
+ for (int i = 0; i < this.vertices.size() - 2; i++) {
+
+ // create triangle
+ Polygon polygon = Polygon.fromPoints(
+ firstVertexStl.pos,
+ this.vertices.get(i + 1).pos,
+ this.vertices.get(i + 2).pos
+ );
+
+ result.add(polygon);
+ }
+ }
+
+ return result;
+ }
+
/**
* Translates this polygon.
*
@@ -272,7 +320,11 @@ public Polygon translate(Vector3d v) {
Vector3d b = this.vertices.get(1).pos;
Vector3d c = this.vertices.get(2).pos;
- this.plane.normal = b.minus(a).cross(c.minus(a));
+ // TODO plane update correct?
+ this._csg_plane.normal = b.minus(a).crossed(c.minus(a));
+
+ this.plane = eu.mihosoft.vvecmath.Plane.
+ fromPointAndNormal(centroid(), _csg_plane.normal);
return this;
}
@@ -312,8 +364,15 @@ public Polygon transform(Transform transform) {
Vector3d b = this.vertices.get(1).pos;
Vector3d c = this.vertices.get(2).pos;
- this.plane.normal = b.minus(a).cross(c.minus(a)).normalized();
- this.plane.dist = this.plane.normal.dot(a);
+ this._csg_plane.normal = b.minus(a).crossed(c.minus(a)).normalized();
+ this._csg_plane.dist = this._csg_plane.normal.dot(a);
+
+ this.plane = eu.mihosoft.vvecmath.Plane.
+ fromPointAndNormal(centroid(), _csg_plane.normal);
+
+ vertices.forEach((vertex) -> {
+ vertex.normal = plane.getNormal();
+ });
if (transform.isMirror()) {
// the transformation includes mirroring. flip polygon
@@ -447,27 +506,119 @@ public Bounds getBounds() {
Vector3d.xyz(maxX, maxY, maxZ));
}
+ public Vector3d centroid() {
+ Vector3d sum = Vector3d.zero();
+
+ for (Vertex v : vertices) {
+ sum = sum.plus(v.pos);
+ }
+
+ return sum.times(1.0 / vertices.size());
+ }
+
+ /**
+ * Indicates whether the specified point is contained within this polygon.
+ *
+ * @param p point
+ * @return {@code true} if the point is inside the polygon or on one of the
+ * edges; {@code false} otherwise
+ */
public boolean contains(Vector3d p) {
- // taken from http://www.java-gaming.org/index.php?topic=26013.0
- // and http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
- double px = p.x();
- double py = p.y();
+
+ // P not on the plane
+ if (plane.distance(p) > Plane.EPSILON) {
+ return false;
+ }
+
+ // if P is on one of the vertices, return true
+ for (int i = 0; i < vertices.size() - 1; i++) {
+ if (p.minus(vertices.get(i).pos).magnitude() < Plane.EPSILON) {
+ return true;
+ }
+ }
+
+ // if P is on the plane, we proceed with projection to XY plane
+ //
+ // P1--P------P2
+ // ^
+ // |
+ // P is on the segment if( dist(P1,P) + dist(P2,P) - dist(P1,P2) < TOL)
+ for (int i = 0; i < vertices.size() - 1; i++) {
+
+ Vector3d p1 = vertices.get(i).pos;
+ Vector3d p2 = vertices.get(i + 1).pos;
+
+ boolean onASegment = p1.minus(p).magnitude() + p2.minus(p).magnitude()
+ - p1.minus(p2).magnitude() < Plane.EPSILON;
+
+ if (onASegment) {
+ return true;
+ }
+ }
+
+ // find projection plane
+ // we start with XY plane
+ int coordIndex1 = 0;
+ int coordIndex2 = 1;
+
+ boolean orthogonalToXY = Math.abs(eu.mihosoft.vvecmath.Plane.XY_PLANE.getNormal()
+ .dot(plane.getNormal())) < Plane.EPSILON;
+
+ boolean foundProjectionPlane = false;
+ if (!orthogonalToXY && !foundProjectionPlane) {
+ coordIndex1 = 0;
+ coordIndex2 = 1;
+ foundProjectionPlane = true;
+ }
+
+ boolean orthogonalToXZ = Math.abs(eu.mihosoft.vvecmath.Plane.XZ_PLANE.getNormal()
+ .dot(plane.getNormal())) < Plane.EPSILON;
+
+ if (!orthogonalToXZ && !foundProjectionPlane) {
+ coordIndex1 = 0;
+ coordIndex2 = 2;
+ foundProjectionPlane = true;
+ }
+
+ boolean orthogonalToYZ = Math.abs(eu.mihosoft.vvecmath.Plane.YZ_PLANE.getNormal()
+ .dot(plane.getNormal())) < Plane.EPSILON;
+
+ if (!orthogonalToYZ && !foundProjectionPlane) {
+ coordIndex1 = 1;
+ coordIndex2 = 2;
+ foundProjectionPlane = true;
+ }
+
+ // see from http://www.java-gaming.org/index.php?topic=26013.0
+ // see http://alienryderflex.com/polygon/
+ // see http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
+ int i, j = vertices.size() - 1;
boolean oddNodes = false;
- double x2 = vertices.get(vertices.size() - 1).pos.x();
- double y2 = vertices.get(vertices.size() - 1).pos.y();
- double x1, y1;
- for (int i = 0; i < vertices.size(); x2 = x1, y2 = y1, ++i) {
- x1 = vertices.get(i).pos.x();
- y1 = vertices.get(i).pos.y();
- if (((y1 < py) && (y2 >= py))
- || (y1 >= py) && (y2 < py)) {
- if ((py - y1) / (y2 - y1)
- * (x2 - x1) < (px - x1)) {
- oddNodes = !oddNodes;
- }
+ double x = p.get(coordIndex1);
+ double y = p.get(coordIndex2);
+ for (i = 0; i < vertices.size(); i++) {
+ double xi = vertices.get(i).pos.get(coordIndex1);
+ double yi = vertices.get(i).pos.get(coordIndex2);
+ double xj = vertices.get(j).pos.get(coordIndex1);
+ double yj = vertices.get(j).pos.get(coordIndex2);
+ if ((yi < y && yj >= y
+ || yj < y && yi >= y)
+ && (xi <= x || xj <= x)) {
+ oddNodes ^= (xi + (y - yi) / (yj - yi) * (xj - xi) < x);
}
+ j = i;
}
return oddNodes;
+
+ }
+
+ @Deprecated
+ public boolean intersects(Polygon p) {
+ if (!getBounds().intersects(p.getBounds())) {
+ return false;
+ }
+
+ throw new UnsupportedOperationException("Not implemented");
}
public boolean contains(Polygon p) {
diff --git a/src/main/java/eu/mihosoft/jcsg/ext/openjfx/importers/SmoothingGroups.java b/src/main/java/eu/mihosoft/jcsg/ext/openjfx/importers/SmoothingGroups.java
index 827227da..a0a0dbf6 100644
--- a/src/main/java/eu/mihosoft/jcsg/ext/openjfx/importers/SmoothingGroups.java
+++ b/src/main/java/eu/mihosoft/jcsg/ext/openjfx/importers/SmoothingGroups.java
@@ -40,7 +40,6 @@
import java.util.List;
import java.util.Map;
import java.util.Queue;
-import com.sun.javafx.geom.Vec3f;
import javafx.scene.shape.TriangleMesh;
/** Util for converting Normals to Smoothing Groups */
diff --git a/src/main/java/eu/mihosoft/jcsg/ext/openjfx/importers/Vec3f.java b/src/main/java/eu/mihosoft/jcsg/ext/openjfx/importers/Vec3f.java
new file mode 100644
index 00000000..8de9acb9
--- /dev/null
+++ b/src/main/java/eu/mihosoft/jcsg/ext/openjfx/importers/Vec3f.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ * - Neither the name of Oracle Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package eu.mihosoft.jcsg.ext.openjfx.importers;
+
+/**
+ * A 3-dimensional, single-precision, floating-point vector.
+ *
+ */
+public class Vec3f {
+ /**
+ * The x coordinate.
+ */
+ public float x;
+
+ /**
+ * The y coordinate.
+ */
+ public float y;
+
+ /**
+ * The z coordinate.
+ */
+ public float z;
+
+ public Vec3f() { }
+
+ public Vec3f(float x, float y, float z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public Vec3f(Vec3f v) {
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+ }
+
+ public void set(Vec3f v) {
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+ }
+
+ public void set(float x, float y, float z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public final void mul(float s) {
+ this.x *= s;
+ this.y *= s;
+ this.z *= s;
+ }
+
+ /**
+ * Sets the value of this vector to the difference
+ * of vectors t1 and t2 (this = t1 - t2).
+ * @param t1 the first vector
+ * @param t2 the second vector
+ */
+ public void sub(Vec3f t1, Vec3f t2) {
+ this.x = t1.x - t2.x;
+ this.y = t1.y - t2.y;
+ this.z = t1.z - t2.z;
+ }
+
+ /**
+ * Sets the value of this vector to the difference of
+ * itself and vector t1 (this = this - t1) .
+ * @param t1 the other vector
+ */
+ public void sub(Vec3f t1) {
+ this.x -= t1.x;
+ this.y -= t1.y;
+ this.z -= t1.z;
+ }
+
+ /**
+ * Sets the value of this vector to the sum
+ * of vectors t1 and t2 (this = t1 + t2).
+ * @param t1 the first vector
+ * @param t2 the second vector
+ */
+ public void add(Vec3f t1, Vec3f t2) {
+ this.x = t1.x + t2.x;
+ this.y = t1.y + t2.y;
+ this.z = t1.z + t2.z;
+ }
+
+ /**
+ * Sets the value of this vector to the sum of
+ * itself and vector t1 (this = this + t1) .
+ * @param t1 the other vector
+ */
+ public void add(Vec3f t1) {
+ this.x += t1.x;
+ this.y += t1.y;
+ this.z += t1.z;
+ }
+
+ /**
+ * Returns the length of this vector.
+ * @return the length of this vector
+ */
+ public float length() {
+ return (float) Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z);
+ }
+
+ /**
+ * Normalize this vector.
+ */
+ public void normalize() {
+ float norm = 1.0f / length();
+ this.x = this.x * norm;
+ this.y = this.y * norm;
+ this.z = this.z * norm;
+ }
+
+ /**
+ * Sets this vector to be the vector cross product of vectors v1 and v2.
+ * @param v1 the first vector
+ * @param v2 the second vector
+ */
+ public void cross(Vec3f v1, Vec3f v2) {
+ float tmpX;
+ float tmpY;
+
+ tmpX = v1.y * v2.z - v1.z * v2.y;
+ tmpY = v2.x * v1.z - v2.z * v1.x;
+ this.z = v1.x * v2.y - v1.y * v2.x;
+ this.x = tmpX;
+ this.y = tmpY;
+ }
+
+ /**
+ * Computes the dot product of this vector and vector v1.
+ * @param v1 the other vector
+ * @return the dot product of this vector and v1
+ */
+ public float dot(Vec3f v1) {
+ return this.x * v1.x + this.y * v1.y + this.z * v1.z;
+ }
+
+ /**
+ * Returns the hashcode for this Vec3f
.
+ * @return a hash code for this Vec3f
.
+ */
+ @Override
+ public int hashCode() {
+ int bits = 7;
+ bits = 31 * bits + Float.floatToIntBits(x);
+ bits = 31 * bits + Float.floatToIntBits(y);
+ bits = 31 * bits + Float.floatToIntBits(z);
+ return bits;
+ }
+
+ /**
+ * Determines whether or not two 3D points or vectors are equal.
+ * Two instances of Vec3f
are equal if the values of their
+ * x
, y
and z
member fields,
+ * representing their position in the coordinate space, are the same.
+ * @param obj an object to be compared with this Vec3f
+ * @return true
if the object to be compared is
+ * an instance of Vec3f
and has
+ * the same values; false
otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof Vec3f) {
+ Vec3f v = (Vec3f) obj;
+ return (x == v.x) && (y == v.y) && (z == v.z);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a String
that represents the value
+ * of this Vec3f
.
+ * @return a string representation of this Vec3f
.
+ */
+ @Override
+ public String toString() {
+ return "Vec3f[" + x + ", " + y + ", " + z + "]";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/mihosoft/jcsg/playground/Main.java b/src/main/java/eu/mihosoft/jcsg/playground/Main.java
new file mode 100644
index 00000000..f67b4e5f
--- /dev/null
+++ b/src/main/java/eu/mihosoft/jcsg/playground/Main.java
@@ -0,0 +1,736 @@
+/*
+ * 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.jcsg.playground;
+
+import eu.mihosoft.jcsg.Bounds;
+import eu.mihosoft.jcsg.CSG;
+import eu.mihosoft.jcsg.Cube;
+import eu.mihosoft.jcsg.ObjFile;
+import eu.mihosoft.jcsg.Polygon;
+import eu.mihosoft.jcsg.STL;
+import eu.mihosoft.jcsg.Sphere;
+import eu.mihosoft.jcsg.Vertex;
+import eu.mihosoft.vvecmath.ModifiableVector3d;
+import eu.mihosoft.vvecmath.Plane;
+import eu.mihosoft.vvecmath.Transform;
+import eu.mihosoft.vvecmath.Vector3d;
+
+import javax.lang.model.type.IntersectionType;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author Michael Hoffer (info@michaelhoffer.de)
+ */
+public class Main {
+
+ public static final double EPS = 1e-8;
+
+ public static void main(String[] args) throws IOException {
+
+ testCut();
+
+ CSG c1 = new Cube(Vector3d.zero(), Vector3d.xyz(1, 1, 1)).toCSG();
+
+ CSG c2 = new Cube(Vector3d.xyz(1, 1, 1), Vector3d.xyz(2, 2, 2)).toCSG()
+ .transformed(Transform.unity().rot(Vector3d.ZERO, Vector3d.UNITY, 78));
+
+// Files.write(Paths.get("c1.stl"), c1.toStlString().getBytes());
+// Files.write(Paths.get("c2.stl"), c2.toStlString().getBytes());
+// c1 = STL.file(Paths.get("c1.stl"));
+// c2 = STL.file(Paths.get("c2.stl"));
+// c1 = new Sphere(Vector3d.x(0.), 0.5, 16, 16).toCSG();
+// c2 = new Sphere(Vector3d.x(0.6), 0.5, 16, 16).toCSG();
+ c2 = new Sphere(Vector3d.x(0.0), 0.65, 16, 16).toCSG();
+ List result1 = splitPolygons(
+ c1.getPolygons(), c2.getPolygons(),
+ c1.getBounds(), c2.getBounds()
+ );
+
+ List result2 = splitPolygons(
+ c2.getPolygons(), c1.getPolygons(),
+ c2.getBounds(), c1.getBounds()
+ );
+
+ /* result1 = splitPolygons(
+ result2, c2.getPolygons(),
+ c1.getBounds(), c2.getBounds());*/
+
+ List splitted = new ArrayList<>();
+ splitted.addAll(result1);
+ splitted.addAll(result2);
+
+// CSG.fromPolygons(splitted).toObj(100).toFiles(Paths.get("test-split1.obj"));
+//
+ Files.write(Paths.get("test-split1.stl"),
+ CSG.fromPolygons(splitted).toStlString().getBytes());
+ List inC2 = new ArrayList<>();
+ List outC2 = new ArrayList<>();
+ List sameC2 = new ArrayList<>();
+ List oppositeC2 = new ArrayList<>();
+
+ List unknownOfC1 = new ArrayList<>();
+
+ for (Polygon p : result2) {
+ PolygonType pT = classifyPolygon(p, c2.getPolygons(), c2.getBounds());
+
+ if (pT == PolygonType.INSIDE) {
+ inC2.add(p);
+ }
+
+ if (pT == PolygonType.SAME) {
+ sameC2.add(p);
+ }
+
+ if (pT == PolygonType.OPPOSITE) {
+ oppositeC2.add(p);
+ }
+
+ if (pT == PolygonType.OUTSIDE) {
+ outC2.add(p);
+ }
+
+ if (pT == PolygonType.UNKNOWN) {
+ unknownOfC1.add(p);
+ }
+ }
+
+ List inC1 = new ArrayList<>();
+ List outC1 = new ArrayList<>();
+ List sameC1 = new ArrayList<>();
+ List oppositeC1 = new ArrayList<>();
+
+ List unknownOfC2 = new ArrayList<>();
+
+ for (Polygon p : result1) {
+ PolygonType pT = classifyPolygon(p, c1.getPolygons(), c1.getBounds());
+
+ if (pT == PolygonType.INSIDE) {
+ inC1.add(p);
+ }
+
+ if (pT == PolygonType.OUTSIDE) {
+ outC1.add(p);
+ }
+
+ if (pT == PolygonType.SAME) {
+ sameC1.add(p);
+ }
+
+ if (pT == PolygonType.OPPOSITE) {
+ oppositeC1.add(p);
+ }
+
+ if (pT == PolygonType.UNKNOWN) {
+ unknownOfC2.add(p);
+ }
+ }
+
+ List difference = new ArrayList<>();
+ difference.addAll(outC2);
+ difference.addAll(oppositeC2);
+ for (Polygon p : inC1) {
+ p.flip();
+ }
+ for (Polygon p : inC2) {
+ p.flip();
+ }
+
+ difference.addAll(inC1);
+
+ System.err.println(">> creating CSG");
+
+ CSG result = CSG.fromPolygons(difference);
+
+ System.err.println(">> unknown polygons in C1: " + unknownOfC1.size());
+ System.err.println(">> unknown polygons in C2: " + unknownOfC2.size());
+ System.err.println(">> opposite polygons in C1: " + oppositeC1.size());
+ System.err.println(">> opposite polygons in C2: " + oppositeC2.size());
+ System.err.println(">> inside polygons in C1: " + inC1.size());
+ System.err.println(">> inside polygons in C2: " + inC2.size());
+
+ Files.write(Paths.get("test.stl"), result.toStlString().getBytes());
+
+ }
+
+ public static PolygonType classifyPolygon(Polygon p1, List polygons, Bounds b) {
+
+ double TOL = 1e-10;
+
+ // we are definitely outside if bounding boxes don't intersect
+ if (!p1.getBounds().intersects(b)) {
+ return PolygonType.OUTSIDE;
+ }
+
+ Vector3d rayCenter = p1.centroid();
+ Vector3d rayDirection = p1.getPlane().getNormal();
+
+ List intersections = getPolygonsThatIntersectWithRay(
+ rayCenter, rayDirection, polygons, TOL);
+
+ if (intersections.isEmpty()) {
+ return PolygonType.OUTSIDE;
+ }
+
+ // find the closest polygon to the centroid of p1 which intersects the
+ // ray
+ RayIntersection min = null; //intersections.get(0);
+ double dist = 0;
+ double prevDist = Double.MAX_VALUE; // min.polygon.centroid().minus(rayCenter).magnitude();
+ int i = 0;
+ for (RayIntersection ri : intersections) {
+
+ int frontOrBack = p1.getPlane().compare(ri.intersectionPoint, TOL);
+
+ if (frontOrBack < 0) {
+ // System.out.println(" -> skipping intersection behind ray " + i);
+ continue;
+ }
+
+ //try {
+ // ObjFile objF = CSG.fromPolygons(ri.polygon).toObj(3);
+ // objF.toFiles(Paths.get("test-intersection-" + i + ".obj"));
+ //} catch (IOException ex) {
+ // Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ //}
+
+ dist = ri.polygon.centroid().minus(rayCenter).magnitude();
+
+ //System.out.println("dist-"+i+": " + dist);
+
+ if (dist < TOL && ri.polygon.getPlane().getNormal().dot(rayDirection) < TOL) {
+ // System.out.println(" -> skipping intersection " + i);
+ continue;
+ }
+
+ if (dist < prevDist) {
+ prevDist = dist;
+ min = ri;
+ }
+
+ i++;
+ }
+
+ if (min == null) {
+ return PolygonType.OUTSIDE;
+ }
+
+ // try {
+ // ObjFile objF = CSG.fromPolygons(min.polygon).toObj();
+ // objF.toFiles(Paths.get("test-intersection-min.obj"));
+ //} catch (IOException ex) {
+ // Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ //}
+
+ int frontOrBack = p1.getPlane().compare(min.intersectionPoint, TOL);
+
+ Vector3d planePoint = p1.getPlane().getAnchor();
+
+ int sameOrOpposite = p1.getPlane().compare(
+ planePoint.plus(min.polygon.getPlane().getNormal()), TOL
+ );
+
+ if (frontOrBack > 0 && sameOrOpposite > 0) {
+ return PolygonType.INSIDE;
+ }
+
+ if (frontOrBack > 0 && sameOrOpposite < 0) {
+ return PolygonType.OUTSIDE;
+ }
+
+ if (frontOrBack < 0 && sameOrOpposite < 0) {
+ return PolygonType.INSIDE;
+ }
+
+ if (frontOrBack < 0 && sameOrOpposite > 0) {
+ return PolygonType.OUTSIDE;
+ }
+
+ if (frontOrBack == 0 && sameOrOpposite > 0) {
+ return PolygonType.SAME;
+ }
+
+ if (frontOrBack == 0 && sameOrOpposite < 0) {
+ return PolygonType.OPPOSITE;
+ }
+
+ System.err.println("I need help (2) !");
+
+ return PolygonType.UNKNOWN;
+ }
+
+ public static final class PlaneIntersection {
+
+ public final IntersectionType type;
+ public Optional point;
+
+ public PlaneIntersection(
+ IntersectionType type, Optional point) {
+ this.type = type;
+ this.point = point;
+ }
+
+ public static enum IntersectionType {
+ ON,
+ PARALLEL,
+ NON_PARALLEL
+ }
+ }
+
+ public static final class RayIntersection {
+
+ public final Vector3d intersectionPoint;
+ public final Polygon polygon;
+ public final PlaneIntersection.IntersectionType type;
+
+ public RayIntersection(Vector3d intersectionPoint,
+ Polygon polygon, PlaneIntersection.IntersectionType type) {
+ this.intersectionPoint = intersectionPoint;
+ this.polygon = polygon;
+ this.type = type;
+ }
+
+
+ @Override
+ public String toString() {
+ return ""
+ + "[\n"
+ + " -> point: " + intersectionPoint + "\n"
+ + " -> polygon-normal: " + polygon.getPlane().getNormal() + "\n"
+ + " -> type: " + type + "\n"
+ + "]";
+ }
+
+ }
+
+ public static List getPolygonsThatIntersectWithRay(
+ Vector3d point, Vector3d direction, List polygons, double TOL) {
+ List intersection = new ArrayList<>();
+ for (Polygon p : polygons) {
+ PlaneIntersection res = computePlaneIntersection(p.getPlane(), point, direction, TOL);
+ if (res.point.isPresent()) {
+ if (p.contains(res.point.get())) {
+ intersection.add(new RayIntersection(res.point.get(), p, res.type));
+ }
+ }
+ }
+
+ return intersection;
+ }
+
+ public static PlaneIntersection computePlaneIntersection(
+ Plane plane, Vector3d point, Vector3d direction, double TOL) {
+
+ //Ax + By + Cz + D = 0
+ //x = x0 + t(x1 x0)
+ //y = y0 + t(y1 y0)
+ //z = z0 + t(z1 z0)
+ //(x1 - x0) = dx, (y1 - y0) = dy, (z1 - z0) = dz
+ //t = -(A*x0 + B*y0 + C*z0 )/(A*dx + B*dy + C*dz)
+ Vector3d normal = plane.getNormal();
+ Vector3d planePoint = plane.getAnchor();
+
+ double A = normal.x();
+ double B = normal.y();
+ double C = normal.z();
+ double D = -(normal.x() * planePoint.x() + normal.y() * planePoint.y() + normal.z() * planePoint.z());
+
+ double numerator = A * point.x() + B * point.y() + C * point.z() + D;
+ double denominator = A * direction.x() + B * direction.y() + C * direction.z();
+
+ //if line is paralel to the plane...
+ if (Math.abs(denominator) < TOL) {
+ //if line is contained in the plane...
+ if (Math.abs(numerator) < TOL) {
+ return new PlaneIntersection(
+ PlaneIntersection.IntersectionType.ON,
+ Optional.of(point));
+ } else {
+ return new PlaneIntersection(
+ PlaneIntersection.IntersectionType.PARALLEL,
+ Optional.empty());
+ }
+ } //if line intercepts the plane...
+ else {
+ double t = -numerator / denominator;
+ Vector3d resultPoint = Vector3d.xyz(
+ point.x() + t * direction.x(),
+ point.y() + t * direction.y(),
+ point.z() + t * direction.z());
+
+ return new PlaneIntersection(
+ PlaneIntersection.IntersectionType.NON_PARALLEL,
+ Optional.of(resultPoint));
+ }
+ }
+
+ /**
+ * Splits polygons ps2 with planes from polygons ps1.
+ *
+ * @param ps1
+ * @param ps2
+ * @param b1
+ * @param b2
+ * @return
+ */
+ public static List splitPolygons(
+ List ps1,
+ List ps2,
+ Bounds b1, Bounds b2) {
+
+ System.out.println("#ps1: " + ps1.size() + ", #ps2: " + ps2.size());
+
+ if (ps1.isEmpty() || ps2.isEmpty()) return Collections.EMPTY_LIST;
+
+ List ps2WithCuts = new ArrayList<>(ps2);
+
+ for (Polygon p1 : ps1) {
+
+ // return early if polygon bounds do not intersect object bounds
+ if (!p1.getBounds().intersects(b2)) {
+ continue;
+ }
+
+ List cutsWithP1 = new ArrayList<>();
+ List p2ToDelete = new ArrayList<>();
+ for (Polygon p2 : ps2WithCuts) {
+
+ // return early if polygon bounds do not intersect other polygon bound
+ if (!p1.getBounds().intersects(p2.getBounds())) {
+ continue;
+ }
+
+ List cutsOfP2WithP1 = cutPolygonWithPlaneIf(p2, p1.getPlane(),
+ (Predicate>) segments -> {
+
+ //if(true)return true;
+ if(segments.size()!=2) return true;
+
+ Vector3d s1 = segments.get(0);
+ Vector3d s2 = segments.get(1);
+
+ int numIntersectionsPoly1 = 0;
+ for(int i = 0; i< p1.vertices.size()-1;i++) {
+ //System.out.println("i,j : " + i + ", " + (i+1%p1.vertices.size()));
+ Vector3d e1 = p1.vertices.get(i).pos;
+ Vector3d e2 = p1.vertices.get(i+1%p1.vertices.size()).pos;
+ LineIntersectionResult iRes = calculateLineLineIntersection(e1,e2,s1,s2);
+ if(iRes.type == LineIntersectionResult.IntersectionType.INTERSECTING &&
+ p1.contains(iRes.segmentPoint1.get())) {
+ numIntersectionsPoly1++;
+ }
+ }
+
+ int numIntersectionsPoly2 = 0;
+ for(int i = 0; i< p2.vertices.size()-1;i++) {
+ Vector3d e1 = p2.vertices.get(i).pos;
+ Vector3d e2 = p2.vertices.get(i+1%p2.vertices.size()).pos;
+ LineIntersectionResult iRes = calculateLineLineIntersection(e1,e2,s1,s2);
+ if(iRes.type == LineIntersectionResult.IntersectionType.INTERSECTING &&
+ p2.contains(iRes.segmentPoint1.get())) {
+ numIntersectionsPoly2++;
+ }
+ }
+
+ return numIntersectionsPoly1 > 0 && numIntersectionsPoly2 > 0;
+ });
+
+ if (!cutsOfP2WithP1.isEmpty()) {
+ cutsWithP1.addAll(cutsOfP2WithP1);
+ p2ToDelete.add(p2);
+ }
+ }
+ ps2WithCuts.addAll(cutsWithP1);
+ ps2WithCuts.removeAll(p2ToDelete);
+ }
+
+ return ps2WithCuts;
+ }
+
+
+ private static void cutPolygonWithPlaneAndTypes(Polygon polygon, Plane cutPlane,
+ int[] vertexTypes, List frontPolygon,
+ List backPolygon, List onPlane) {
+
+// System.out.println("polygon: \n" + polygon.toStlString());
+// System.out.println("--------------------");
+// System.out.println("plane: \n -> p: " + cutPlane.getAnchor() + "\n -> n: " + cutPlane.getNormal());
+// System.out.println("--------------------");
+ for (int i = 0; i < polygon.vertices.size(); i++) {
+ int j = (i + 1) % polygon.vertices.size();
+ int ti = vertexTypes[i];
+ int tj = vertexTypes[j];
+ Vertex vi = polygon.vertices.get(i);
+ Vertex vj = polygon.vertices.get(j);
+ if (ti == 1 /*front*/) {
+ frontPolygon.add(vi.pos);
+ }
+ if (ti == -1 /*back*/) {
+ backPolygon.add(vi.pos);
+ }
+
+ if (ti == 0) {
+ frontPolygon.add(vi.pos);
+ backPolygon.add(vi.pos);
+// segmentPoints.add(vi.pos);
+ }
+
+ if (ti != tj && (ti != 0 && tj != 0)/*spanning*/) {
+ PlaneIntersection pI = computePlaneIntersection(cutPlane, vi.pos, vj.pos.minus(vi.pos), EPS);
+
+ if (pI.type != PlaneIntersection.IntersectionType.NON_PARALLEL) {
+ throw new RuntimeException("I need help (3)!");
+ }
+
+ Vector3d intersectionPoint = pI.point.get();
+
+ frontPolygon.add(intersectionPoint);
+ backPolygon.add(intersectionPoint);
+ onPlane.add(intersectionPoint);
+ }
+ }
+ }
+
+ public static void testCut() {
+
+ Polygon p = Polygon.fromPoints(
+ Vector3d.xyz(0, 0, 0),
+ Vector3d.xyz(1, 0, 0),
+ Vector3d.xyz(1, 0, 1),
+ Vector3d.xyz(0, 0, 1)
+ );
+
+ try {
+ CSG pCSG = STL.file(Paths.get("sphere-test-01.stl"));
+
+ p = pCSG.getPolygons().get(0);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+
+ CSG cube = new Cube(Vector3d.xyz(1, 1, 1), Vector3d.xyz(2, 2, 2)).toCSG()
+ .transformed(Transform.unity().rot(Vector3d.ZERO, Vector3d.UNITY, 17));
+
+ cube = new Sphere(Vector3d.x(0.), 0.5, 16, 16).toCSG();
+
+// CSG cube = new Cube(1).toCSG().transformed(
+// Transform.unity().translate(0.5,-0.55,0.5).rot(Vector3d.ZERO, Vector3d.UNITY, 0)
+// );
+
+ int cubePolyFrom = 0;
+ int cubePolyTo = 6;
+
+ List cubePolys = cube.getPolygons();//.subList(cubePolyFrom, cubePolyTo);
+
+ System.out.println("p: " + p.toStlString());
+ System.out.println("p-centroid: " + p.centroid());
+
+ List intersections =
+ getPolygonsThatIntersectWithRay(
+ p.centroid(),
+ p.getPlane().getNormal(),
+ cubePolys, EPS);
+
+ System.out.println("my normal: " + p.getPlane().getNormal());
+
+ System.out.println("#intersections: " + intersections.size());
+ for (RayIntersection ri : intersections) {
+ System.out.println(ri);
+ }
+
+ PolygonType pType = classifyPolygon(p, cubePolys, cube.getBounds());
+
+ System.out.println("#pType:");
+ System.out.println(" -> " + pType);
+
+ List cutsWithCube = splitPolygons(cubePolys,
+ Arrays.asList(p), p.getBounds(), cube.getBounds());
+
+ cutsWithCube.addAll(cube.getPolygons()/*.subList(cubePolyFrom, cubePolyTo)*/);
+
+ try {
+ ObjFile objF = CSG.fromPolygons(cutsWithCube).toObj(3);
+ objF.toFiles(Paths.get("test-split1.obj"));
+// Files.write(Paths.get("test-split1.stl"),
+// CSG.fromPolygons(cutsWithP1).toStlString().getBytes());
+ } catch (IOException ex) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ ModifiableVector3d segmentP1 = Vector3d.zero().asModifiable();
+ ModifiableVector3d segmentP2 = Vector3d.zero().asModifiable();
+ LineIntersectionResult lineRes = calculateLineLineIntersection(
+ Vector3d.xyz(-1, 0, 0), Vector3d.xyz(1, 0, 0),
+ Vector3d.xyz(0, -1, 0), Vector3d.xyz(0, 1, 0));
+
+ System.out.println("l1 intersect l2: ");
+ System.out.println(lineRes);
+
+ // System.exit(0);
+ }
+
+ private static List cutPolygonWithPlaneIf(Polygon p, Plane plane, Predicate> check) {
+
+ boolean typesEqual = true;
+ int types[] = new int[p.vertices.size()];
+ for (int i = 0; i < p.vertices.size(); i++) {
+ types[i] = plane.compare(p.vertices.get(i).pos, EPS);
+// System.out.println("type " + i + ": " + types[i]);
+
+ if (i > 0 && typesEqual) {
+ typesEqual = typesEqual && (types[i] == types[i - 1]);
+ }
+ }
+
+ // planes are parallel, thus polygons do not intersect
+ if (typesEqual) {
+ return Collections.EMPTY_LIST;
+ }
+
+ List front = new ArrayList<>();
+ List back = new ArrayList<>();
+ List on = new ArrayList<>();
+ cutPolygonWithPlaneAndTypes(p, plane, types, front, back, on);
+
+ boolean checkResult = check == null;
+
+ if (check != null) {
+ checkResult = check.test(on);
+ }
+
+ if (!checkResult) return Collections.EMPTY_LIST;
+
+ List cutsWithP1 = new ArrayList<>();
+ if (front.size() > 2) {
+ Polygon frontCut = Polygon.fromPoints(
+ front);
+ if (frontCut.isValid()) {
+ cutsWithP1.add(frontCut);
+ }
+ }
+ if (back.size() > 2) {
+ Polygon backCut = Polygon.fromPoints(
+ back);
+ if (backCut.isValid()) {
+ cutsWithP1.add(backCut);
+ }
+ }
+ return cutsWithP1;
+ }
+
+ enum PolygonType {
+ UNKNOWN,
+ INSIDE,
+ OUTSIDE,
+ OPPOSITE,
+ SAME
+ }
+
+
+ static class LineIntersectionResult {
+
+ public final IntersectionType type;
+
+ public final Optional segmentPoint1;
+ public final Optional segmentPoint2;
+
+ LineIntersectionResult(IntersectionType type, Vector3d segmentPoint1, Vector3d segmentPoint2) {
+ this.type = type;
+ this.segmentPoint1 = Optional.ofNullable(segmentPoint1);
+ this.segmentPoint2 = Optional.ofNullable(segmentPoint2);
+ }
+
+ static enum IntersectionType {
+ PARALLEL,
+ NON_PARALLEL,
+ INTERSECTING
+ }
+
+ static final LineIntersectionResult PARALLEL =
+ new LineIntersectionResult(IntersectionType.PARALLEL, null, null);
+
+ @Override
+ public String toString() {
+ return "[\n -> type: " + type
+ + "\n -> segmentP1: " + (segmentPoint1.isPresent() ? segmentPoint1.get() : "none")
+ + "\n -> segmentP2: " + (segmentPoint2.isPresent() ? segmentPoint2.get() : "none")
+ + "\n]";
+ }
+ }
+
+ /**
+ * Calculates the intersection line segment between two lines.
+ *
+ * @param line1Point1
+ * @param line1Point2
+ * @param line2Point1
+ * @param line2Point2
+ * @return {@code true} if the intersection line segment exists; {@code false} otherwise
+ */
+ public static LineIntersectionResult calculateLineLineIntersection(Vector3d line1Point1, Vector3d line1Point2,
+ Vector3d line2Point1, Vector3d line2Point2) {
+ // Algorithm is ported from the C algorithm of
+ // Paul Bourke at http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/
+
+ Vector3d p1 = line1Point1;
+ Vector3d p2 = line1Point2;
+ Vector3d p3 = line2Point1;
+ Vector3d p4 = line2Point2;
+ Vector3d p13 = p1.minus(p3);
+ Vector3d p43 = p4.minus(p3);
+
+ if (p43.magnitudeSq() < EPS) {
+ return LineIntersectionResult.PARALLEL;
+ }
+ Vector3d p21 = p2.minus(p1);
+ if (p21.magnitudeSq() < EPS) {
+ return LineIntersectionResult.PARALLEL;
+ }
+
+ double d1343 = p13.x() * (double) p43.x() + (double) p13.y() * p43.y() + (double) p13.z() * p43.z();
+ double d4321 = p43.x() * (double) p21.x() + (double) p43.y() * p21.y() + (double) p43.z() * p21.z();
+ double d1321 = p13.x() * (double) p21.x() + (double) p13.y() * p21.y() + (double) p13.z() * p21.z();
+ double d4343 = p43.x() * (double) p43.x() + (double) p43.y() * p43.y() + (double) p43.z() * p43.z();
+ double d2121 = p21.x() * (double) p21.x() + (double) p21.y() * p21.y() + (double) p21.z() * p21.z();
+
+ double denom = d2121 * d4343 - d4321 * d4321;
+ if (Math.abs(denom) < EPS) {
+ return LineIntersectionResult.PARALLEL;
+ }
+ double numer = d1343 * d4321 - d1321 * d4343;
+
+ double mua = numer / denom;
+ double mub = (d1343 + d4321 * (mua)) / d4343;
+
+ ModifiableVector3d resultSegmentPoint1 = Vector3d.zero().asModifiable();
+ ModifiableVector3d resultSegmentPoint2 = Vector3d.zero().asModifiable();
+
+ resultSegmentPoint1.setX(p1.x() + mua * p21.x());
+ resultSegmentPoint1.setY(p1.y() + mua * p21.y());
+ resultSegmentPoint1.setZ(p1.z() + mua * p21.z());
+ resultSegmentPoint2.setX(p3.x() + mub * p43.x());
+ resultSegmentPoint2.setY(p3.y() + mub * p43.y());
+ resultSegmentPoint2.setZ(p3.z() + mub * p43.z());
+
+ if (resultSegmentPoint1.equals(resultSegmentPoint2)) {
+ return new LineIntersectionResult(LineIntersectionResult.IntersectionType.INTERSECTING,
+ resultSegmentPoint1, resultSegmentPoint2);
+ } else {
+ return new LineIntersectionResult(LineIntersectionResult.IntersectionType.NON_PARALLEL,
+ resultSegmentPoint1, resultSegmentPoint2);
+ }
+ }
+
+}
diff --git a/src/main/java/eu/mihosoft/jcsg/playground/Main2.java b/src/main/java/eu/mihosoft/jcsg/playground/Main2.java
new file mode 100644
index 00000000..a65958b6
--- /dev/null
+++ b/src/main/java/eu/mihosoft/jcsg/playground/Main2.java
@@ -0,0 +1,723 @@
+/*
+ * 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.jcsg.playground;
+
+import eu.mihosoft.jcsg.*;
+import eu.mihosoft.vvecmath.ModifiableVector3d;
+import eu.mihosoft.vvecmath.Plane;
+import eu.mihosoft.vvecmath.Transform;
+import eu.mihosoft.vvecmath.Vector3d;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * @author Michael Hoffer (info@michaelhoffer.de)
+ */
+public class Main2 {
+
+ public static final double EPS = 1e-8;
+ // s3 = s1 - s2
+
+ // # Step 1:
+ //
+ // Take all polygons of s2 that have vertices inside the bounding box b 1of s1
+ // (polygons with only some vertices inside of b are cut along the planes of b1)
+ // All polygons outside of b1 are ignored from now on.
+ //
+ // Remark: collinear polygons are considered as being outside of b1.
+ //
+ // # Step 2:
+ //
+ // Cut all remaining polygons of s2 with the polygons of s1. Only keep the polygons with all vertices
+ // inside of b1.
+ //
+ // # Step 3:
+ //
+ // a) For each remaining polygon p of s2: cast an orthogonal ray from the center of p (normal) and
+ // count the number of intersecting polygons of s1. If the ray hits a vertex of p or cuts the
+ // boundary, it counts as being intersected by the ray.
+ //
+ // b) Classify the resulting polygon by whether the number of intersections is even or uneven number
+ // of intersections. An uneven number of intersections indicates that the polygon is inside of s1;
+ // An even number of intersections indicates that the polygon is outside of s2.
+ //
+ //
+ // # Step 5:
+ //
+ // Repeat these steps (1-4) with s1 and s2 reversed.
+ //
+ // # Step 6:
+ //
+ // s3 consists of all polygons of s1 classified as being outside of s2 and all polygons of s2 being
+ // classified as being inside of s1.
+ //
+ // _________
+ // / /*\ \
+ // | |*| | * = intersection
+ // \___\*/___/
+ //
+ // _________ ____
+ // / / \ \ / /
+ // | | | | === | |
+ // \___\ /___/ \___\
+ public static void main(String[] args) throws IOException {
+
+ CSG s1 = new Cube(2).toCSG();
+ CSG s2 = new Sphere(Vector3d.x(0.0), 1.25, 32, 32).toCSG();
+
+ Files.write(Paths.get("diff-orig.stl"), s2.difference(s1).toStlString().getBytes());
+
+ Classification classification = classify(s1,s2);
+
+ List polygons = new ArrayList<>();
+
+ polygons.addAll(classification.outsideS1);
+// polygons.addAll(classification.outsideS2);
+// polygons.addAll(classification.insideS1);
+ polygons.addAll(classification.insideS2);
+
+ CSG difference = CSG.fromPolygons(polygons);
+
+ Files.write(Paths.get("diff.stl"), difference.toStlString().getBytes());
+ }
+
+ static class Classification {
+ public List insideS1;
+ public List outsideS1;
+ public List insideS2;
+ public List outsideS2;
+ }
+
+ static class Classification1 {
+ public List inside;
+ public List outside;
+ }
+
+ public static Classification classify(CSG s1, CSG s2) {
+ Classification result = new Classification();
+
+ Classification1 r1 = classify1(s1,s2);
+ Classification1 r2 = classify1(s2,s1);
+
+ result.insideS1 = r1.inside;
+ result.outsideS1 = r1.outside;
+ result.insideS2 = r2.inside;
+ result.outsideS2 = r2.outside;
+
+ return result;
+ }
+
+ public static Classification1 classify1(CSG s1, CSG s2) {
+
+ // step 1
+
+ // get polygons inside of b
+ Bounds b1 = s1.getBounds();
+ Bounds b2 = s2.getBounds();
+ List ps1 = s1.getPolygons();
+ List ps2 = s2.getPolygons();//.stream().filter(p->b1.intersects(p)).collect(Collectors.toList());
+
+ // step 2
+
+ // cut polygons
+ ps2 = splitPolygons(ps1, ps2, b1, b2);//.stream().filter(p->p.vertices.stream().filter(v->b1.contains(v)).count()==p.vertices.size()).collect(Collectors.toList());
+
+ // step 3
+
+ double TOL = 1e-10;
+
+ Map> polygons = ps2.parallelStream().collect(Collectors.partitioningBy(p-> {
+ return classifyPolygon(p, ps1, b1) == PolygonType.OUTSIDE;
+ }));
+
+ List inside = polygons.get(false);
+ List outside = polygons.get(true);
+
+ Classification1 result = new Classification1();
+ result.inside = inside;
+ result.outside = outside;
+
+ return result;
+ }
+
+
+
+ public static PolygonType classifyPolygon(Polygon p1, List polygons, Bounds b) {
+
+ double TOL = 1e-10;
+
+ // we are definitely outside if bounding boxes don't intersect
+ if (!p1.getBounds().intersects(b)) {
+ return PolygonType.OUTSIDE;
+ }
+
+ Vector3d rayCenter = p1.centroid();
+ Vector3d rayDirection = p1.getPlane().getNormal();
+
+ List intersections = getPolygonsThatIntersectWithRay(
+ rayCenter, rayDirection, polygons, TOL);
+
+ if (intersections.isEmpty()) {
+ return PolygonType.OUTSIDE;
+ }
+
+ // find the closest polygon to the centroid of p1 which intersects the
+ // ray
+ RayIntersection min = null; //intersections.get(0);
+ double dist = 0;
+ double prevDist = Double.MAX_VALUE; // min.polygon.centroid().minus(rayCenter).magnitude();
+ int i = 0;
+ for (RayIntersection ri : intersections) {
+
+ int frontOrBack = p1.getPlane().compare(ri.intersectionPoint, TOL);
+
+ if (frontOrBack < 0) {
+ // System.out.println(" -> skipping intersection behind ray " + i);
+ continue;
+ }
+
+ //try {
+ // ObjFile objF = CSG.fromPolygons(ri.polygon).toObj(3);
+ // objF.toFiles(Paths.get("test-intersection-" + i + ".obj"));
+ //} catch (IOException ex) {
+ // Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ //}
+
+ dist = ri.polygon.centroid().minus(rayCenter).magnitude();
+
+ //System.out.println("dist-"+i+": " + dist);
+
+ if (dist < TOL && ri.polygon.getPlane().getNormal().dot(rayDirection) < TOL) {
+ // System.out.println(" -> skipping intersection " + i);
+ continue;
+ }
+
+ if (dist < prevDist) {
+ prevDist = dist;
+ min = ri;
+ }
+
+ i++;
+ }
+
+ if (min == null) {
+ return PolygonType.OUTSIDE;
+ }
+
+ // try {
+ // ObjFile objF = CSG.fromPolygons(min.polygon).toObj();
+ // objF.toFiles(Paths.get("test-intersection-min.obj"));
+ //} catch (IOException ex) {
+ // Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+ //}
+
+ int frontOrBack = p1.getPlane().compare(min.intersectionPoint, TOL);
+
+ Vector3d planePoint = p1.getPlane().getAnchor();
+
+ int sameOrOpposite = p1.getPlane().compare(
+ planePoint.plus(min.polygon.getPlane().getNormal()), TOL
+ );
+
+ if (frontOrBack > 0 && sameOrOpposite > 0) {
+ return PolygonType.INSIDE;
+ }
+
+ if (frontOrBack > 0 && sameOrOpposite < 0) {
+ return PolygonType.OUTSIDE;
+ }
+
+ if (frontOrBack < 0 && sameOrOpposite < 0) {
+ return PolygonType.INSIDE;
+ }
+
+ if (frontOrBack < 0 && sameOrOpposite > 0) {
+ return PolygonType.OUTSIDE;
+ }
+
+ if (frontOrBack == 0 && sameOrOpposite > 0) {
+ return PolygonType.SAME;
+ }
+
+ if (frontOrBack == 0 && sameOrOpposite < 0) {
+ return PolygonType.OPPOSITE;
+ }
+
+ System.err.println("I need help (2) !");
+
+ return PolygonType.UNKNOWN;
+ }
+
+ public static final class PlaneIntersection {
+
+ public final IntersectionType type;
+ public Optional point;
+
+ public PlaneIntersection(
+ IntersectionType type, Optional point) {
+ this.type = type;
+ this.point = point;
+ }
+
+ public static enum IntersectionType {
+ ON,
+ PARALLEL,
+ NON_PARALLEL
+ }
+ }
+
+ public static final class RayIntersection {
+
+ public final Vector3d intersectionPoint;
+ public final Polygon polygon;
+ public final PlaneIntersection.IntersectionType type;
+
+ public RayIntersection(Vector3d intersectionPoint,
+ Polygon polygon, PlaneIntersection.IntersectionType type) {
+ this.intersectionPoint = intersectionPoint;
+ this.polygon = polygon;
+ this.type = type;
+ }
+
+
+ @Override
+ public String toString() {
+ return ""
+ + "[\n"
+ + " -> point: " + intersectionPoint + "\n"
+ + " -> polygon-normal: " + polygon.getPlane().getNormal() + "\n"
+ + " -> type: " + type + "\n"
+ + "]";
+ }
+
+ }
+
+ public static List getPolygonsThatIntersectWithRay(
+ Vector3d point, Vector3d direction, List polygons, double TOL) {
+ List intersection = new ArrayList<>();
+ for (Polygon p : polygons) {
+ PlaneIntersection res = computePlaneIntersection(p.getPlane(), point, direction, TOL);
+ if (res.point.isPresent()) {
+ if (p.contains(res.point.get())) {
+ intersection.add(new RayIntersection(res.point.get(), p, res.type));
+ }
+ }
+ }
+
+ return intersection;
+ }
+
+ public static PlaneIntersection computePlaneIntersection(
+ Plane plane, Vector3d point, Vector3d direction, double TOL) {
+
+ //Ax + By + Cz + D = 0
+ //x = x0 + t(x1 x0)
+ //y = y0 + t(y1 y0)
+ //z = z0 + t(z1 z0)
+ //(x1 - x0) = dx, (y1 - y0) = dy, (z1 - z0) = dz
+ //t = -(A*x0 + B*y0 + C*z0 )/(A*dx + B*dy + C*dz)
+ Vector3d normal = plane.getNormal();
+ Vector3d planePoint = plane.getAnchor();
+
+ double A = normal.x();
+ double B = normal.y();
+ double C = normal.z();
+ double D = -(normal.x() * planePoint.x() + normal.y() * planePoint.y() + normal.z() * planePoint.z());
+
+ double numerator = A * point.x() + B * point.y() + C * point.z() + D;
+ double denominator = A * direction.x() + B * direction.y() + C * direction.z();
+
+ //if line is parallel to the plane...
+ if (Math.abs(denominator) < TOL) {
+ //if line is contained in the plane...
+ if (Math.abs(numerator) < TOL) {
+ return new PlaneIntersection(
+ PlaneIntersection.IntersectionType.ON,
+ Optional.of(point));
+ } else {
+ return new PlaneIntersection(
+ PlaneIntersection.IntersectionType.PARALLEL,
+ Optional.empty());
+ }
+ } //if line intercepts the plane...
+ else {
+ double t = -numerator / denominator;
+ Vector3d resultPoint = Vector3d.xyz(
+ point.x() + t * direction.x(),
+ point.y() + t * direction.y(),
+ point.z() + t * direction.z());
+
+ return new PlaneIntersection(
+ PlaneIntersection.IntersectionType.NON_PARALLEL,
+ Optional.of(resultPoint));
+ }
+ }
+
+ /**
+ * Splits polygons ps2 with planes from polygons ps1.
+ *
+ * @param ps1
+ * @param ps2
+ * @param b1
+ * @param b2
+ * @return
+ */
+ public static List splitPolygons(
+ List ps1,
+ List ps2,
+ Bounds b1, Bounds b2) {
+
+ System.out.println("#ps1: " + ps1.size() + ", #ps2: " + ps2.size());
+
+ if (ps1.isEmpty() || ps2.isEmpty()) return Collections.EMPTY_LIST;
+
+ List ps2WithCuts = new ArrayList<>(ps2);
+
+ for (Polygon p1 : ps1) {
+
+ // return early if polygon bounds do not intersect object bounds
+ if (!p1.getBounds().intersects(b2)) {
+ continue;
+ }
+
+ List cutsWithP1 = new ArrayList<>();
+ List p2ToDelete = new ArrayList<>();
+ for (Polygon p2 : ps2WithCuts) {
+
+ // return early if polygon bounds do not intersect other polygon bound
+ if (!p1.getBounds().intersects(p2.getBounds())) {
+ continue;
+ }
+
+ List cutsOfP2WithP1 = cutPolygonWithPlaneIf(p2, p1.getPlane(),
+ (Predicate>) segments -> {
+
+ //if(true)return true;
+ if(segments.size()!=2) return true;
+
+ Vector3d s1 = segments.get(0);
+ Vector3d s2 = segments.get(1);
+
+ int numIntersectionsPoly1 = 0;
+ for(int i = 0; i< p1.vertices.size()-1;i++) {
+ //System.out.println("i,j : " + i + ", " + (i+1%p1.vertices.size()));
+ Vector3d e1 = p1.vertices.get(i).pos;
+ Vector3d e2 = p1.vertices.get(i+1%p1.vertices.size()).pos;
+ LineIntersectionResult iRes = calculateLineLineIntersection(e1,e2,s1,s2);
+ if(iRes.type == LineIntersectionResult.IntersectionType.INTERSECTING &&
+ p1.contains(iRes.segmentPoint1.get())) {
+ numIntersectionsPoly1++;
+ }
+ }
+
+ int numIntersectionsPoly2 = 0;
+ for(int i = 0; i< p2.vertices.size()-1;i++) {
+ Vector3d e1 = p2.vertices.get(i).pos;
+ Vector3d e2 = p2.vertices.get(i+1%p2.vertices.size()).pos;
+ LineIntersectionResult iRes = calculateLineLineIntersection(e1,e2,s1,s2);
+ if(iRes.type == LineIntersectionResult.IntersectionType.INTERSECTING &&
+ p2.contains(iRes.segmentPoint1.get())) {
+ numIntersectionsPoly2++;
+ }
+ }
+
+ return numIntersectionsPoly1 > 0 && numIntersectionsPoly2 > 0;
+ });
+
+ if (!cutsOfP2WithP1.isEmpty()) {
+ cutsWithP1.addAll(cutsOfP2WithP1);
+ p2ToDelete.add(p2);
+ }
+ }
+ ps2WithCuts.addAll(cutsWithP1);
+ ps2WithCuts.removeAll(p2ToDelete);
+ }
+
+ return ps2WithCuts;
+ }
+
+
+ private static void cutPolygonWithPlaneAndTypes(Polygon polygon, Plane cutPlane,
+ int[] vertexTypes, List frontPolygon,
+ List backPolygon, List onPlane) {
+
+// System.out.println("polygon: \n" + polygon.toStlString());
+// System.out.println("--------------------");
+// System.out.println("plane: \n -> p: " + cutPlane.getAnchor() + "\n -> n: " + cutPlane.getNormal());
+// System.out.println("--------------------");
+ for (int i = 0; i < polygon.vertices.size(); i++) {
+ int j = (i + 1) % polygon.vertices.size();
+ int ti = vertexTypes[i];
+ int tj = vertexTypes[j];
+ Vertex vi = polygon.vertices.get(i);
+ Vertex vj = polygon.vertices.get(j);
+ if (ti == 1 /*front*/) {
+ frontPolygon.add(vi.pos);
+ }
+ if (ti == -1 /*back*/) {
+ backPolygon.add(vi.pos);
+ }
+
+ if (ti == 0) {
+ frontPolygon.add(vi.pos);
+ backPolygon.add(vi.pos);
+// segmentPoints.add(vi.pos);
+ }
+
+ if (ti != tj && (ti != 0 && tj != 0)/*spanning*/) {
+ PlaneIntersection pI = computePlaneIntersection(cutPlane, vi.pos, vj.pos.minus(vi.pos), EPS);
+
+ if (pI.type != PlaneIntersection.IntersectionType.NON_PARALLEL) {
+ throw new RuntimeException("I need help (3)!");
+ }
+
+ Vector3d intersectionPoint = pI.point.get();
+
+ frontPolygon.add(intersectionPoint);
+ backPolygon.add(intersectionPoint);
+ onPlane.add(intersectionPoint);
+ }
+ }
+ }
+
+ public static void testCut() {
+
+ Polygon p = Polygon.fromPoints(
+ Vector3d.xyz(0, 0, 0),
+ Vector3d.xyz(1, 0, 0),
+ Vector3d.xyz(1, 0, 1),
+ Vector3d.xyz(0, 0, 1)
+ );
+
+ try {
+ CSG pCSG = STL.file(Paths.get("sphere-test-01.stl"));
+
+ p = pCSG.getPolygons().get(0);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+
+ CSG cube = new Cube(Vector3d.xyz(1, 1, 1), Vector3d.xyz(2, 2, 2)).toCSG()
+ .transformed(Transform.unity().rot(Vector3d.ZERO, Vector3d.UNITY, 17));
+
+ cube = new Sphere(Vector3d.x(0.), 0.5, 16, 16).toCSG();
+
+// CSG cube = new Cube(1).toCSG().transformed(
+// Transform.unity().translate(0.5,-0.55,0.5).rot(Vector3d.ZERO, Vector3d.UNITY, 0)
+// );
+
+ int cubePolyFrom = 0;
+ int cubePolyTo = 6;
+
+ List cubePolys = cube.getPolygons();//.subList(cubePolyFrom, cubePolyTo);
+
+ System.out.println("p: " + p.toStlString());
+ System.out.println("p-centroid: " + p.centroid());
+
+ List intersections =
+ getPolygonsThatIntersectWithRay(
+ p.centroid(),
+ p.getPlane().getNormal(),
+ cubePolys, EPS);
+
+ System.out.println("my normal: " + p.getPlane().getNormal());
+
+ System.out.println("#intersections: " + intersections.size());
+ for (RayIntersection ri : intersections) {
+ System.out.println(ri);
+ }
+
+ PolygonType pType = classifyPolygon(p, cubePolys, cube.getBounds());
+
+ System.out.println("#pType:");
+ System.out.println(" -> " + pType);
+
+ List cutsWithCube = splitPolygons(cubePolys,
+ Arrays.asList(p), p.getBounds(), cube.getBounds());
+
+ cutsWithCube.addAll(cube.getPolygons()/*.subList(cubePolyFrom, cubePolyTo)*/);
+
+ try {
+ ObjFile objF = CSG.fromPolygons(cutsWithCube).toObj(3);
+ objF.toFiles(Paths.get("test-split1.obj"));
+// Files.write(Paths.get("test-split1.stl"),
+// CSG.fromPolygons(cutsWithP1).toStlString().getBytes());
+ } catch (IOException ex) {
+ Logger.getLogger(Main2.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ ModifiableVector3d segmentP1 = Vector3d.zero().asModifiable();
+ ModifiableVector3d segmentP2 = Vector3d.zero().asModifiable();
+ LineIntersectionResult lineRes = calculateLineLineIntersection(
+ Vector3d.xyz(-1, 0, 0), Vector3d.xyz(1, 0, 0),
+ Vector3d.xyz(0, -1, 0), Vector3d.xyz(0, 1, 0));
+
+ System.out.println("l1 intersect l2: ");
+ System.out.println(lineRes);
+
+ // System.exit(0);
+ }
+
+ private static List cutPolygonWithPlaneIf(Polygon p, Plane plane, Predicate> check) {
+
+ boolean typesEqual = true;
+ int types[] = new int[p.vertices.size()];
+ for (int i = 0; i < p.vertices.size(); i++) {
+ types[i] = plane.compare(p.vertices.get(i).pos, EPS);
+// System.out.println("type " + i + ": " + types[i]);
+
+ if (i > 0 && typesEqual) {
+ typesEqual = typesEqual && (types[i] == types[i - 1]);
+ }
+ }
+
+ // planes are parallel, thus polygons do not intersect
+ if (typesEqual) {
+ return Collections.EMPTY_LIST;
+ }
+
+ List front = new ArrayList<>();
+ List back = new ArrayList<>();
+ List on = new ArrayList<>();
+ cutPolygonWithPlaneAndTypes(p, plane, types, front, back, on);
+
+ boolean checkResult = check == null;
+
+ if (check != null) {
+ checkResult = check.test(on);
+ }
+
+ if (!checkResult) return Collections.EMPTY_LIST;
+
+ List cutsWithP1 = new ArrayList<>();
+ if (front.size() > 2) {
+ Polygon frontCut = Polygon.fromPoints(
+ front);
+ if (frontCut.isValid()) {
+ cutsWithP1.add(frontCut);
+ }
+ }
+ if (back.size() > 2) {
+ Polygon backCut = Polygon.fromPoints(
+ back);
+ if (backCut.isValid()) {
+ cutsWithP1.add(backCut);
+ }
+ }
+ return cutsWithP1;
+ }
+
+ enum PolygonType {
+ UNKNOWN,
+ INSIDE,
+ OUTSIDE,
+ OPPOSITE,
+ SAME
+ }
+
+
+ static class LineIntersectionResult {
+
+ public final IntersectionType type;
+
+ public final Optional segmentPoint1;
+ public final Optional segmentPoint2;
+
+ LineIntersectionResult(IntersectionType type, Vector3d segmentPoint1, Vector3d segmentPoint2) {
+ this.type = type;
+ this.segmentPoint1 = Optional.ofNullable(segmentPoint1);
+ this.segmentPoint2 = Optional.ofNullable(segmentPoint2);
+ }
+
+ static enum IntersectionType {
+ PARALLEL,
+ NON_PARALLEL,
+ INTERSECTING
+ }
+
+ static final LineIntersectionResult PARALLEL =
+ new LineIntersectionResult(IntersectionType.PARALLEL, null, null);
+
+ @Override
+ public String toString() {
+ return "[\n -> type: " + type
+ + "\n -> segmentP1: " + (segmentPoint1.isPresent() ? segmentPoint1.get() : "none")
+ + "\n -> segmentP2: " + (segmentPoint2.isPresent() ? segmentPoint2.get() : "none")
+ + "\n]";
+ }
+ }
+
+ /**
+ * Calculates the intersection line segment between two lines.
+ *
+ * @param line1Point1
+ * @param line1Point2
+ * @param line2Point1
+ * @param line2Point2
+ * @return {@code true} if the intersection line segment exists; {@code false} otherwise
+ */
+ public static LineIntersectionResult calculateLineLineIntersection(Vector3d line1Point1, Vector3d line1Point2,
+ Vector3d line2Point1, Vector3d line2Point2) {
+ // Algorithm is ported from the C algorithm of
+ // Paul Bourke at http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/
+
+ Vector3d p1 = line1Point1;
+ Vector3d p2 = line1Point2;
+ Vector3d p3 = line2Point1;
+ Vector3d p4 = line2Point2;
+ Vector3d p13 = p1.minus(p3);
+ Vector3d p43 = p4.minus(p3);
+
+ if (p43.magnitudeSq() < EPS) {
+ return LineIntersectionResult.PARALLEL;
+ }
+ Vector3d p21 = p2.minus(p1);
+ if (p21.magnitudeSq() < EPS) {
+ return LineIntersectionResult.PARALLEL;
+ }
+
+ double d1343 = p13.x() * (double) p43.x() + (double) p13.y() * p43.y() + (double) p13.z() * p43.z();
+ double d4321 = p43.x() * (double) p21.x() + (double) p43.y() * p21.y() + (double) p43.z() * p21.z();
+ double d1321 = p13.x() * (double) p21.x() + (double) p13.y() * p21.y() + (double) p13.z() * p21.z();
+ double d4343 = p43.x() * (double) p43.x() + (double) p43.y() * p43.y() + (double) p43.z() * p43.z();
+ double d2121 = p21.x() * (double) p21.x() + (double) p21.y() * p21.y() + (double) p21.z() * p21.z();
+
+ double denom = d2121 * d4343 - d4321 * d4321;
+ if (Math.abs(denom) < EPS) {
+ return LineIntersectionResult.PARALLEL;
+ }
+ double numer = d1343 * d4321 - d1321 * d4343;
+
+ double mua = numer / denom;
+ double mub = (d1343 + d4321 * (mua)) / d4343;
+
+ ModifiableVector3d resultSegmentPoint1 = Vector3d.zero().asModifiable();
+ ModifiableVector3d resultSegmentPoint2 = Vector3d.zero().asModifiable();
+
+ resultSegmentPoint1.setX(p1.x() + mua * p21.x());
+ resultSegmentPoint1.setY(p1.y() + mua * p21.y());
+ resultSegmentPoint1.setZ(p1.z() + mua * p21.z());
+ resultSegmentPoint2.setX(p3.x() + mub * p43.x());
+ resultSegmentPoint2.setY(p3.y() + mub * p43.y());
+ resultSegmentPoint2.setZ(p3.z() + mub * p43.z());
+
+ if (resultSegmentPoint1.equals(resultSegmentPoint2)) {
+ return new LineIntersectionResult(LineIntersectionResult.IntersectionType.INTERSECTING,
+ resultSegmentPoint1, resultSegmentPoint2);
+ } else {
+ return new LineIntersectionResult(LineIntersectionResult.IntersectionType.NON_PARALLEL,
+ resultSegmentPoint1, resultSegmentPoint2);
+ }
+ }
+
+}
diff --git a/src/main/java/eu/mihosoft/jcsg/samples/FractalStructure.java b/src/main/java/eu/mihosoft/jcsg/samples/FractalStructure.java
index 6191837c..21fb9d28 100644
--- a/src/main/java/eu/mihosoft/jcsg/samples/FractalStructure.java
+++ b/src/main/java/eu/mihosoft/jcsg/samples/FractalStructure.java
@@ -148,10 +148,10 @@ public FractalStructure(Vector3d groundCenter, Vector3d topCenter,
&& Math.abs(orthoVecToRotAxis2.dot(rotationAxis)) < orthoThreshhold) {
this.orthoVecToRotAxis2 = orthoVecToRotAxis2.normalized();
} else {
- this.orthoVecToRotAxis2 = rotationAxis.cross(this.orthoVecToRotAxis1).normalized();
+ this.orthoVecToRotAxis2 = rotationAxis.crossed(this.orthoVecToRotAxis1).normalized();
}
} else {
- this.orthoVecToRotAxis2 = rotationAxis.cross(this.orthoVecToRotAxis1).normalized();
+ this.orthoVecToRotAxis2 = rotationAxis.crossed(this.orthoVecToRotAxis1).normalized();
}
// x, y, z
@@ -445,7 +445,7 @@ private ArrayList createSubStructures() {
helpCenterPoint = connectionLineVectorNormalized.times(j).plus(connectionLineVectorNormalized.times(stepSizeOnConnectionLineHalf)).plus(centerGroundPoint).plus(correctionInRotationAxisDirection);
if (secondOrthoVec == null) {
- secondOrthoVec = connectionLineVectorNormalized.cross(helpCenterPoint.minus(helpEdgePoint));
+ secondOrthoVec = connectionLineVectorNormalized.crossed(helpCenterPoint.minus(helpEdgePoint));
}
// prevent that the last cross connactions from bottom left to top right has a to above end point in the top plane
diff --git a/src/test/java/eu/mihosoft/jcsg/PolygonFlipTest.java b/src/test/java/eu/mihosoft/jcsg/PolygonFlipTest.java
new file mode 100644
index 00000000..67d83838
--- /dev/null
+++ b/src/test/java/eu/mihosoft/jcsg/PolygonFlipTest.java
@@ -0,0 +1,28 @@
+package eu.mihosoft.jcsg;
+
+import eu.mihosoft.vvecmath.Vector3d;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PolygonFlipTest {
+
+ private static final double EPSILON = 1e-8;
+
+ @Test
+ public void flipPolygonTest() {
+ Polygon polygon = Polygon.fromPoints(
+ Vector3d.xy(1, 1),
+ Vector3d.xy(2, 1),
+ Vector3d.xy(1, 2)
+ );
+ assertEquals(Vector3d.z(1), polygon.getPlane().getNormal());
+ polygon.flip();
+ assertEquals(Vector3d.z(-1), polygon.getPlane().getNormal());
+ }
+
+ private void assertEquals(final Vector3d expected, final Vector3d actual) {
+ Assert.assertEquals(expected.getX(), actual.getX(), EPSILON);
+ Assert.assertEquals(expected.getY(), actual.getY(), EPSILON);
+ Assert.assertEquals(expected.getZ(), actual.getZ(), EPSILON);
+ }
+}
diff --git a/src/test/java/eu/mihosoft/jcsg/VolumeTest.java b/src/test/java/eu/mihosoft/jcsg/VolumeTest.java
new file mode 100644
index 00000000..c1343d1e
--- /dev/null
+++ b/src/test/java/eu/mihosoft/jcsg/VolumeTest.java
@@ -0,0 +1,61 @@
+package eu.mihosoft.jcsg;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.nio.file.Paths;
+
+public class VolumeTest {
+
+ @Test
+ public void vlumeTest() {
+
+ {
+ // volume of empty CSG object is 0
+ double emptyVolume = CSG.fromPolygons(new Polygon[0]).computeVolume();
+ assertEquals(emptyVolume, 0, 1e-16);
+ }
+
+ {
+ // volume of unit cube is 1 unit^3
+ double volumeUnitCube = new Cube(1.0).toCSG().computeVolume();
+ assertEquals(1.0, volumeUnitCube, 1e-16);
+ }
+
+ {
+ // volume of cube is w*h*d unit^3
+ double w = 30.65;
+ double h = 24.17;
+ double d = 75.3;
+ double volumeBox = new Cube(w,h,d).toCSG().computeVolume();
+ assertEquals(w*h*d, volumeBox, 1e-16);
+ }
+
+ {
+ // volume of sphere is (4*PI*r^3)/3.0 unit^3
+ double r = 3.4;
+
+ // bad approximation
+ double volumeSphere1 = new Sphere(r, 32,16).toCSG().computeVolume();
+ assertEquals((4.0*Math.PI*r*r*r)/3.0, volumeSphere1, 10.0);
+
+ // better approximation
+ double volumeSphere2 = new Sphere(r, 1024, 512).toCSG().computeVolume();
+ assertEquals((4.0*Math.PI*r*r*r)/3.0, volumeSphere2, 1e-2);
+ }
+
+ {
+ // volume of cylinder is PI*r^2*h unit^3
+ double r = 5.9;
+ double h = 2.1;
+
+ // bad approximation
+ double volumeCylinder1 = new Cylinder(r, h, 16).toCSG().computeVolume();
+ assertEquals(Math.PI*r*r*h, volumeCylinder1, 10);
+
+ // better approximation
+ double volumeCylinder2 = new Cylinder(r, h, 1024).toCSG().computeVolume();
+ assertEquals(Math.PI*r*r*h, volumeCylinder2, 1e-2);
+ }
+
+ }
+}
\ No newline at end of file