diff --git a/.gitignore b/.gitignore index e4913633..b6fdc661 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ /testSpline.svg /CSGdatabase.json /*.stl -/*.svg +/Test.svg out /*.mtl @@ -26,3 +26,4 @@ html /handmade.step /test-export-2.step /test-export.step +/Part-Num-0.svg.png diff --git a/Part-Num-0.svg b/Part-Num-0.svg new file mode 100644 index 00000000..fc988110 --- /dev/null +++ b/Part-Num-0.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/piro/bezier/BezierPath.java b/src/main/java/com/piro/bezier/BezierPath.java index 82fa752f..c65be77d 100644 --- a/src/main/java/com/piro/bezier/BezierPath.java +++ b/src/main/java/com/piro/bezier/BezierPath.java @@ -6,6 +6,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import eu.mihosoft.vrl.v3d.Plane; import eu.mihosoft.vrl.v3d.Vector3d; public class BezierPath { @@ -14,7 +15,7 @@ public class BezierPath { BezierListProducer path; - private ArrayList pointList = new ArrayList(); + private ArrayList plInternal = new ArrayList(); double resolution = 0.075; /** Creates a new instance of Animate */ @@ -60,45 +61,45 @@ protected void parsePathList(String list) { x = nextFloat(tokens); y = nextFloat(tokens); path.movetoAbs(x, y); - pointList.add(new Vector3d(x, y, 0)); + setThePoint(new Vector3d(x, y, 0)); curCmd = 'L'; break; case 'm': x = nextFloat(tokens); y = nextFloat(tokens); path.movetoRel(x, y); - pointList.add(new Vector3d(x, y, 0)); + setThePoint(new Vector3d(x, y, 0)); curCmd = 'l'; break; case 'L': path.linetoAbs(nextFloat(tokens), nextFloat(tokens)); - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); + setThePoint(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); break; case 'l': path.linetoRel(nextFloat(tokens), nextFloat(tokens)); - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); + setThePoint(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); break; case 'H': path.linetoHorizontalAbs(nextFloat(tokens)); - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); + setThePoint(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); break; case 'h': path.linetoHorizontalRel(nextFloat(tokens)); - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); + setThePoint(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); break; case 'V': path.linetoVerticalAbs(nextFloat(tokens)); - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); + setThePoint(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); break; case 'v': path.linetoVerticalAbs(nextFloat(tokens)); - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); + setThePoint(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1)); break; case 'A': case 'a': @@ -106,51 +107,51 @@ protected void parsePathList(String list) { case 'Q': path.curvetoQuadraticAbs(nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens)); for (double i = resolution; i < 1; i += resolution) { - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(i)); + addingPoint(i); } break; case 'q': path.curvetoQuadraticAbs(nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens)); for (double i = resolution; i < 1; i += resolution) { - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(i)); + addingPoint(i); } break; case 'T': path.curvetoQuadraticSmoothAbs(nextFloat(tokens), nextFloat(tokens)); for (double i = resolution; i < 1; i += resolution) { - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(i)); + addingPoint(i); } break; case 't': path.curvetoQuadraticSmoothRel(nextFloat(tokens), nextFloat(tokens)); for (double i = resolution; i < 1; i += resolution) { - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(i)); + addingPoint(i); } break; case 'C': path.curvetoCubicAbs(nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens)); for (double i = resolution; i < 1; i += resolution) { - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(i)); + addingPoint(i); } break; case 'c': path.curvetoCubicRel(nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens)); for (double i = resolution; i < 1; i += resolution) { - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(i)); + addingPoint(i); } break; case 'S': path.curvetoCubicSmoothAbs(nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens)); for (double i = resolution; i < 1; i += resolution) { - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(i)); + addingPoint(i); } break; case 's': path.curvetoCubicSmoothRel(nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens)); for (double i = resolution; i < 1; i += resolution) { - pointList.add(path.bezierSegs.get(path.bezierSegs.size() - 1).eval(i)); + addingPoint(i); } break; case 'Z': @@ -167,6 +168,19 @@ protected void parsePathList(String list) { } } + private boolean addingPoint(double i) { + Vector3d eval = path.bezierSegs.get(path.bezierSegs.size() - 1).eval(i); + return setThePoint(eval); + } + + private boolean setThePoint(Vector3d eval) { + for(Vector3d v:plInternal) { + if(Math.abs(v.minus(eval).magnitude()) l) { String s = l.removeFirst(); return Float.parseFloat(s); @@ -206,7 +220,7 @@ public Vector3d eval(float interp) { */ public ArrayList evaluate() { - return pointList; + return plInternal; } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Edge.java b/src/main/java/eu/mihosoft/vrl/v3d/Edge.java index 5a7d068c..3d17c6c9 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Edge.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Edge.java @@ -125,7 +125,7 @@ public static Polygon toPolygon(List points, Plane plane) { Polygon p = Polygon.fromPoints(points); p.vertices.stream().forEachOrdered((vertex) -> { - vertex.normal = plane.normal.clone(); + vertex.normal = plane.getNormal().clone(); }); // // we try to detect wrong orientation by comparing normals @@ -897,8 +897,8 @@ private static List> searchPlaneGroups(List polygons) { continue; } - Vector3d nOuter = pOuter.plane.normal; - Vector3d nInner = pInner.plane.normal; + Vector3d nOuter = pOuter.plane.getNormal(); + Vector3d nInner = pInner.plane.getNormal(); double angle = nOuter.angle(nInner); diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java b/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java index afd7736d..50909875 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java @@ -90,6 +90,7 @@ private CSG monotoneExtrude(Vector3d dir, Polygon polygon1) { Polygon polygon2 = polygon1.translated(dir); int numvertices = polygon1.vertices.size(); + System.out.println("Building Polygon "+polygon1.getPoints().size()); for (int i = 0; i < numvertices; i++) { int nexti = (i + 1) % numvertices; @@ -98,11 +99,18 @@ private CSG monotoneExtrude(Vector3d dir, Polygon polygon1) { Vector3d topV1 = polygon2.vertices.get(i).pos; Vector3d bottomV2 = polygon1.vertices.get(nexti).pos; Vector3d topV2 = polygon2.vertices.get(nexti).pos; - + double distance = bottomV1.minus(bottomV2).magnitude(); + if(Math.abs(distance) pPoints = Arrays.asList(bottomV2, topV2, topV1, bottomV1); - - newPolygons.add(Polygon.fromPoints(pPoints, polygon1.getStorage())); - + try { + newPolygons.add(Polygon.fromPoints(pPoints, polygon1.getStorage())); + }catch(Exception ex) { + System.out.println("Polygon has problems: "); + ex.printStackTrace(); + } } polygon2 = polygon2.flipped(); diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Plane.java b/src/main/java/eu/mihosoft/vrl/v3d/Plane.java index 89b71680..d6934d7d 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Plane.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Plane.java @@ -45,190 +45,245 @@ public class Plane { private static IPolygonDebugger debugger = null; private static boolean useDebugger = false; - /** - * EPSILON is the tolerance used by {@link #splitPolygon(eu.mihosoft.vrl.v3d.Polygon, java.util.List, java.util.List, java.util.List, java.util.List) - * } to decide if a point is on the plane. - * public static final double EPSILON = 0.00000001; - */ - - public static final double EPSILON = 1.0e-9; - public static final double EPSILON_Point = EPSILON; - public static final double EPSILON_duplicate = 1.0e-4; - /** - * XY plane. - */ - public static final Plane XY_PLANE = new Plane(Vector3d.Z_ONE, 1); - /** - * XZ plane. - */ - public static final Plane XZ_PLANE = new Plane(Vector3d.Y_ONE, 1); - /** - * YZ plane. - */ - public static final Plane YZ_PLANE = new Plane(Vector3d.X_ONE, 1); - - /** - * Normal vector. - */ - public Vector3d normal; - /** - * Distance to origin. - */ - public double dist; - - /** - * Constructor. Creates a new plane defined by its normal vector and the - * distance to the origin. - * - * @param normal plane normal - * @param dist distance from origin - */ - public Plane(Vector3d normal, double dist) { - this.normal = normal.normalized(); - this.dist = dist; - } - - /** - * Creates a plane defined by the the specified points. - * - * @param a first point - * @param b second point - * @param c third point - * @return a plane - */ - public static Plane createFromPoints(Vector3d a, Vector3d b, Vector3d c) { - Vector3d n = b.minus(a).cross(c.minus(a)).normalized(); - return new Plane(n, n.dot(a)); - } - - /* (non-Javadoc) - * @see java.lang.Object#clone() - */ - @Override - public Plane clone() { - return new Plane(normal.clone(), dist); - } - - /** - * Flips this plane. - */ - public void flip() { - normal = normal.negated(); - dist = -dist; - } - - /** - * Splits a {@link Polygon} by this plane if needed. After that it puts the - * polygons or the polygon fragments in the appropriate lists - * ({@code front}, {@code back}). Coplanar polygons go into either - * {@code coplanarFront}, {@code coplanarBack} depending on their - * orientation with respect to this plane. Polygons in front or back of this - * plane go into either {@code front} or {@code back}. - * - * @param polygon polygon to split - * @param coplanarFront "coplanar front" polygons - * @param coplanarBack "coplanar back" polygons - * @param front front polygons - * @param back back polgons - */ - public void splitPolygon( - Polygon polygon, - List coplanarFront, - List coplanarBack, - List front, - List back) { - final int COPLANAR = 0; - final int FRONT = 1; - final int BACK = 2; - final int SPANNING = 3; // == some in the FRONT + some in the BACK - if(debugger!=null && useDebugger) { + /** + * EPSILON is the tolerance used by + * {@link #splitPolygon(eu.mihosoft.vrl.v3d.Polygon, java.util.List, java.util.List, java.util.List, java.util.List) } + * to decide if a point is on the plane. public static final double EPSILON = + * 0.00000001; + */ + + public static final double EPSILON = 1.0e-9; + public static final double EPSILON_Point = EPSILON; + public static final double EPSILON_duplicate = 1.0e-4; + /** + * XY plane. + */ + public static final Plane XY_PLANE = new Plane(Vector3d.Z_ONE, 1); + /** + * XZ plane. + */ + public static final Plane XZ_PLANE = new Plane(Vector3d.Y_ONE, 1); + /** + * YZ plane. + */ + public static final Plane YZ_PLANE = new Plane(Vector3d.X_ONE, 1); + + /** + * Normal vector. + */ + private Vector3d normal; + /** + * Distance to origin. + */ + private double dist; + + /** + * Constructor. Creates a new plane defined by its normal vector and the + * distance to the origin. + * + * @param normal plane normal + * @param dist distance from origin + */ + public Plane(Vector3d normal, double dist) { + this.setNormal(normal.normalized()); + this.setDist(dist); + } + + /** + * Creates a plane defined by the the specified points. + * + * @param a first point + * @param b second point + * @param c third point + * @return a plane + */ + public static Plane createFromPoints(List vertices) { + Vector3d a = vertices.get(0).pos; + Vector3d n = computeNormal(vertices); + return new Plane(n, n.dot(a)); + } + + public static Vector3d computeNormal(List vertices) { + Vector3d normal = new Vector3d(0, 0, 0); + int n = vertices.size(); + + for (int i = 0; i < n; i++) { + Vector3d current = vertices.get(i).pos; + Vector3d next = vertices.get((i + 1) % n).pos; + + normal.x += (current.y - next.y) * (current.z + next.z); + normal.y += (current.z - next.z) * (current.x + next.x); + normal.z += (current.x - next.x) * (current.y + next.y); + } + + Vector3d normalized = normal.normalized(); + // If Newell's method fails, try finding three non-collinear points + double lengthSquared = normal.lengthSquared(); + double d = EPSILON * EPSILON; + if (lengthSquared < d) { // Adjust this epsilon as needed + for (int i = 0; i < n - 2; i++) { + Vector3d a = vertices.get(i).pos; + for (int j = i + 1; j < n - 1; j++) { + Vector3d b = vertices.get(j).pos; + for (int k = j + 1; k < n; k++) { + Vector3d c = vertices.get(k).pos; + normal = b.minus(a).cross(c.minus(a)); + lengthSquared = normal.lengthSquared(); + if (lengthSquared > d) { // Non-zero normal found + return normal.normalized(); + } + } + } + } + } + + // If all else fails, return a default normal (e.g., in the z direction) + lengthSquared = normal.lengthSquared(); + + if (lengthSquared < Double.MIN_VALUE*10) { + throw new NumberFormatException("This set of points is not a valid polygon"); + } + if(normalized.lengthSquared() coplanarFront, List coplanarBack, + List front, List back) { + final int COPLANAR = 0; + final int FRONT = 1; + final int BACK = 2; + final int SPANNING = 3; // == some in the FRONT + some in the BACK + if (debugger != null && useDebugger) { // debugger.display(polygon); // debugger.display(coplanarFront); // debugger.display(coplanarBack); // debugger.display(front); // debugger.display(back); - } - // search for the epsilon values of the incoming plane - double negEpsilon = -Plane.EPSILON; - double posEpsilon = Plane.EPSILON; - for (int i = 0; i < polygon.vertices.size(); i++) { - double t = polygon.plane.normal.dot(polygon.vertices.get(i).pos) - polygon.plane.dist; - if(t>posEpsilon) { - //System.err.println("Non flat polygon, increasing positive epsilon "+t); - posEpsilon=t+Plane.EPSILON; - } - if(t types = new ArrayList<>(); - boolean somePointsInfront = false; - boolean somePointsInBack = false; - for (int i = 0; i < polygon.vertices.size(); i++) { - double t = this.normal.dot(polygon.vertices.get(i).pos) - this.dist; - int type = (t < negEpsilon) ? BACK : (t > posEpsilon) ? FRONT : COPLANAR; - if(type==BACK) - somePointsInBack=true; - if(type==FRONT) - somePointsInfront = true; - types.add(type); - } - if(somePointsInBack && somePointsInfront) - polygonType=SPANNING; - else if(somePointsInBack) { - polygonType=BACK; - }else if(somePointsInfront) - polygonType=FRONT; - - // Put the polygon in the correct list, splitting it when necessary. - switch (polygonType) { - case COPLANAR: - (this.normal.dot(polygon.plane.normal) > 0 ? coplanarFront : coplanarBack).add(polygon); - break; - case FRONT: - front.add(polygon); - break; - case BACK: - back.add(polygon); - break; - case SPANNING: - List f = new ArrayList<>(); - List b = new ArrayList<>(); - for (int i = 0; i < polygon.vertices.size(); i++) { - int j = (i + 1) % polygon.vertices.size(); - int ti = types.get(i); - int tj = types.get(j); - Vertex vi = polygon.vertices.get(i); - Vertex vj = polygon.vertices.get(j); - if (ti != BACK) { - f.add(vi); - } - if (ti != FRONT) { - b.add(ti != BACK ? vi.clone() : vi); - } - if ((ti | tj) == SPANNING) { - double t = (this.dist - this.normal.dot(vi.pos)) - / this.normal.dot(vj.pos.minus(vi.pos)); - Vertex v = vi.interpolate(vj, t); - f.add(v); - b.add(v.clone()); - } - } - if (f.size() >= 3) { - front.add(new Polygon(f, polygon.getStorage()).setColor(polygon.getColor())); - } else { - System.out.println("Front Clip Fault!"); - } - if (b.size() >= 3) { - back.add(new Polygon(b, polygon.getStorage()).setColor(polygon.getColor())); - } else { - System.out.println("Back Clip Fault!"); - } - break; - } - } + } + // search for the epsilon values of the incoming plane + double negEpsilon = -Plane.EPSILON; + double posEpsilon = Plane.EPSILON; + for (int i = 0; i < polygon.vertices.size(); i++) { + double t = polygon.plane.getNormal().dot(polygon.vertices.get(i).pos) - polygon.plane.getDist(); + if (t > posEpsilon) { + // System.err.println("Non flat polygon, increasing positive epsilon "+t); + posEpsilon = t + Plane.EPSILON; + } + if (t < negEpsilon) { + // System.err.println("Non flat polygon, decreasing negative epsilon "+t); + negEpsilon = t - Plane.EPSILON; + } + } + int polygonType = 0; + List types = new ArrayList<>(); + boolean somePointsInfront = false; + boolean somePointsInBack = false; + for (int i = 0; i < polygon.vertices.size(); i++) { + double t = this.getNormal().dot(polygon.vertices.get(i).pos) - this.getDist(); + int type = (t < negEpsilon) ? BACK : (t > posEpsilon) ? FRONT : COPLANAR; + if (type == BACK) + somePointsInBack = true; + if (type == FRONT) + somePointsInfront = true; + types.add(type); + } + if (somePointsInBack && somePointsInfront) + polygonType = SPANNING; + else if (somePointsInBack) { + polygonType = BACK; + } else if (somePointsInfront) + polygonType = FRONT; + + // Put the polygon in the correct list, splitting it when necessary. + switch (polygonType) { + case COPLANAR: + (this.getNormal().dot(polygon.plane.getNormal()) > 0 ? coplanarFront : coplanarBack).add(polygon); + break; + case FRONT: + front.add(polygon); + break; + case BACK: + back.add(polygon); + break; + case SPANNING: + List f = new ArrayList<>(); + List b = new ArrayList<>(); + for (int i = 0; i < polygon.vertices.size(); i++) { + int j = (i + 1) % polygon.vertices.size(); + int ti = types.get(i); + int tj = types.get(j); + Vertex vi = polygon.vertices.get(i); + Vertex vj = polygon.vertices.get(j); + if (ti != BACK) { + f.add(vi); + } + if (ti != FRONT) { + b.add(ti != BACK ? vi.clone() : vi); + } + if ((ti | tj) == SPANNING) { + double t = (this.getDist() - this.getNormal().dot(vi.pos)) + / this.getNormal().dot(vj.pos.minus(vi.pos)); + Vertex v = vi.interpolate(vj, t); + f.add(v); + b.add(v.clone()); + } + } + if (f.size() >= 3) { + try { + front.add(new Polygon(f, polygon.getStorage()).setColor(polygon.getColor())); + } catch (NumberFormatException ex) { + //ex.printStackTrace(); + // skip adding broken polygon here + } + } else { + System.out.println("Front Clip Fault!"); + } + if (b.size() >= 3) { + try { + back.add(new Polygon(b, polygon.getStorage()).setColor(polygon.getColor())); + } catch (NumberFormatException ex) { + //ex.printStackTrace(); + // skip adding broken polygon here + } + } else { + System.out.println("Back Clip Fault!"); + } + break; + } + } public static IPolygonDebugger getDebugger() { return debugger; @@ -245,4 +300,34 @@ public static boolean isUseDebugger() { public static void setUseDebugger(boolean useDebugger) { Plane.useDebugger = useDebugger; } + + public Vector3d getNormal() { + return normal; + } + + public void setNormal(Vector3d normal) { + if (Double.isFinite(normal.x) && Double.isFinite(normal.y) && Double.isFinite(normal.z)) + this.normal = normal; + else { + + NumberFormatException numberFormatException = new NumberFormatException(); + // numberFormatException.printStackTrace(); + throw numberFormatException; + } + } + + public double getDist() { + return dist; + } + + public void setDist(double dist) { + if (Double.isFinite(dist)) + this.dist = dist; + else { + + NumberFormatException numberFormatException = new NumberFormatException(); + // numberFormatException.printStackTrace(); + throw numberFormatException; + } + } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java b/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java index 5fc18f7a..12e0a838 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java @@ -62,7 +62,7 @@ public final class Polygon { * * Note: uses first three vertices to define the plane. */ - public final Plane plane; + public Plane plane; /** @@ -111,10 +111,15 @@ public static List fromConcavePoints(List points) { public Polygon(List vertices, PropertyStorage shared, boolean allowDegenerate) { this.vertices = pruneDuplicatePoints(vertices); this.shared = shared; - this.plane = Plane.createFromPoints( - vertices.get(0).pos, - vertices.get(1).pos, - vertices.get(2).pos); +// try { + this.plane = Plane.createFromPoints( + vertices); +// }catch(java.lang.NumberFormatException nf) { +// System.out.println("plane failed to load"); +// nf.printStackTrace(); +// this.plane = Plane.createFromPoints( +// vertices); +// } validateAndInit(allowDegenerate); } /** @@ -142,9 +147,7 @@ public Polygon(List vertices, PropertyStorage shared) { public Polygon(List vertices) { this.vertices = pruneDuplicatePoints(vertices); this.plane = Plane.createFromPoints( - vertices.get(0).pos, - vertices.get(1).pos, - vertices.get(2).pos); + vertices); validateAndInit(true); } public static List pruneDuplicatePoints(List incoming) { @@ -171,10 +174,10 @@ public static List pruneDuplicatePoints(List incoming) { } private void validateAndInit( boolean allowDegenerate) { for (Vertex v : vertices) { - v.normal = plane.normal; + v.normal = plane.getNormal(); } setDegenerate(true); - if (Vector3d.ZERO.equals(plane.normal)) { + if (Vector3d.ZERO.equals(plane.getNormal())) { valid = false; throw new RuntimeException( "Normal is zero! Probably, duplicate points have been specified!\n\n" + toStlString()); @@ -279,7 +282,7 @@ public StringBuilder toStlString(StringBuilder sb) { for (int i = 0; i < this.vertices.size() - 2; i++) { sb. append(" facet normal ").append( - this.plane.normal.toStlString()).append("\n"). + this.plane.getNormal().toStlString()).append("\n"). append(" outer loop\n"). append(" ").append(firstVertexStl).append("\n"). append(" "); @@ -311,7 +314,7 @@ 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)); + this.plane.setNormal(b.minus(a).cross(c.minus(a))); return this; } @@ -348,11 +351,9 @@ public Polygon transform(Transform transform) { ); Vector3d a = this.vertices.get(0).pos; - 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.plane.setNormal(Plane.computeNormal(this.vertices)); + this.plane.setDist(this.plane.getNormal().dot(a)); if (transform.isMirror()) { // the transformation includes mirroring. flip polygon @@ -361,7 +362,21 @@ public Polygon transform(Transform transform) { } return this; } - +// public Vector3d computeNormal(List vertices) { +// Vector3d normal = new Vector3d(0, 0, 0); +// int n = vertices.size(); +// +// for (int i = 0; i < n; i++) { +// Vector3d current = vertices.get(i).pos; +// Vector3d next = vertices.get((i + 1) % n).pos; +// +// normal.x += (current.y - next.y) * (current.z + next.z); +// normal.y += (current.z - next.z) * (current.x + next.x); +// normal.z += (current.x - next.x) * (current.y + next.y); +// } +// +// return normal.normalized(); +// } /** * Returns a transformed copy of this polygon. * @@ -423,7 +438,7 @@ private static Polygon fromPoints( List points, PropertyStorage shared, Plane plane, boolean allowDegenerate) { Vector3d normal - = (plane != null) ? plane.normal.clone() : new Vector3d(0, 0, 0); + = (plane != null) ? plane.getNormal().clone() : new Vector3d(0, 0, 0); List vertices = new ArrayList<>(); diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Transform.java b/src/main/java/eu/mihosoft/vrl/v3d/Transform.java index eeb31902..01c873e2 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Transform.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Transform.java @@ -328,10 +328,10 @@ public Transform mirror(Plane plane) { System.err.println("WARNING: I'm too dumb to implement the mirror() operation correctly. Please fix me!"); - double nx = plane.normal.x; - double ny = plane.normal.y; - double nz = plane.normal.z; - double w = plane.dist; + double nx = plane.getNormal().x; + double ny = plane.getNormal().y; + double nz = plane.getNormal().z; + double w = plane.getDist(); double elemenents[] = { (1.0 - 2.0 * nx * nx), (-2.0 * ny * nx), (-2.0 * nz * nx), 0, (-2.0 * nx * ny), (1.0 - 2.0 * ny * ny), (-2.0 * nz * ny), 0, (-2.0 * nx * nz), (-2.0 * ny * nz), (1.0 - 2.0 * nz * nz), 0, (-2.0 * nx * w), (-2.0 * ny * w), (-2.0 * nz * w), 1 }; 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 297de03a..0f131b9d 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 @@ -108,7 +108,7 @@ public static List concaveToConvex(Polygon incoming) { if (incoming.vertices.size() < 3) return result; Polygon concave = incoming; - Vector3d normalOfPlane = incoming.plane.normal; + Vector3d normalOfPlane = incoming.plane.getNormal(); boolean reorent = normalOfPlane.z < 1.0-Plane.EPSILON; Transform orentationInv = null; boolean debug = false; @@ -118,7 +118,7 @@ public static List concaveToConvex(Polygon incoming) { Polygon tmp = incoming.transformed(orentation); - Vector3d normal = tmp.plane.normal; + Vector3d normal = tmp.plane.getNormal(); double degreesToRotate2 =90+Math.toDegrees(Math.atan2(normal.z,normal.y)); Transform orentation2 = orentation.rotx(degreesToRotate2);// th triangulation function needs // the polygon on the xy plane @@ -128,7 +128,7 @@ public static List concaveToConvex(Polygon incoming) { } concave = incoming.transformed(orentation2); orentationInv = orentation2.inverse(); - if(concave.plane.normal.z <0) { + if(concave.plane.getNormal().z <0) { Transform orentation3 = orentation2.rotx(180); concave = incoming.transformed(orentation3); orentationInv = orentation3.inverse(); @@ -136,7 +136,7 @@ public static List concaveToConvex(Polygon incoming) { } - Vector3d normal = concave.plane.normal.clone(); + Vector3d normal = concave.plane.getNormal().clone(); boolean cw = !Extrude.isCCW(concave); //concave = Extrude.toCCW(concave); @@ -183,7 +183,7 @@ public static List concaveToConvex(Polygon incoming) { } Polygon poly = new Polygon(triPoints, concave.getStorage(), true); //poly = Extrude.toCCW(poly); - poly.plane.normal = concave.plane.normal; + poly.plane.setNormal(concave.plane.getNormal()); boolean b = !Extrude.isCCW(poly); if (cw != b) { // System.out.println("Triangle not matching incoming"); @@ -203,7 +203,7 @@ public static List concaveToConvex(Polygon incoming) { if (reorent) { poly = poly.transform(orentationInv); } - poly.plane.normal = normalOfPlane; + poly.plane.setNormal(normalOfPlane); poly.setColor(incoming.getColor()); result.add(poly); counter = 0; diff --git a/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGLoad.java b/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGLoad.java index 34da0ff3..1c6d6da9 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGLoad.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGLoad.java @@ -584,11 +584,13 @@ private void loadComposite(String code, double resolution, Transform startingFra // println "Single path found" // setHolePolarity(true); - try { +// try { loadSingle(code, resolution, startingFrame,encapsulatingLayer, c); - } catch (Exception ex) { - // BowlerStudio.printStackTrace(ex); - } +// } catch (Exception ex) { +// System.out.println("Polygon failed to load!"); +// ex.printStackTrace(); +// // BowlerStudio.printStackTrace(ex); +// } } else { // setHolePolarity(false); diff --git a/src/main/java/eu/mihosoft/vrl/v3d/thumbnail/ThumbnailImage.java b/src/main/java/eu/mihosoft/vrl/v3d/thumbnail/ThumbnailImage.java new file mode 100644 index 00000000..dc716c82 --- /dev/null +++ b/src/main/java/eu/mihosoft/vrl/v3d/thumbnail/ThumbnailImage.java @@ -0,0 +1,209 @@ +package eu.mihosoft.vrl.v3d.thumbnail; + + + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.imageio.ImageIO; + +import eu.mihosoft.vrl.v3d.Bounds; +import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.Vector3d; +import javafx.scene.Group; +import javafx.scene.Scene; +import javafx.scene.SceneAntialiasing; +import javafx.scene.SnapshotParameters; +import javafx.scene.image.WritableImage; +import javafx.scene.paint.Color; +import javafx.scene.paint.PhongMaterial; +import javafx.scene.shape.CullFace; +import javafx.scene.shape.MeshView; +import javafx.scene.transform.Transform; +import javafx.scene.PerspectiveCamera; +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.transform.Affine; +import javafx.scene.transform.Transform; +import javafx.scene.transform.Rotate; +import javafx.geometry.Rectangle2D; + +public class ThumbnailImage { + private static CullFace cullFaceValue = CullFace.BACK; + + public static Bounds getSellectedBounds(List incoming) { + Vector3d min = null; + Vector3d max = null; + for (CSG c : incoming) { + if(c.isHide()) + continue; + if(c.isInGroup()) + continue; + Vector3d min2 = c.getBounds().getMin().clone(); + Vector3d max2 = c.getBounds().getMax().clone(); + if (min == null) + min = min2; + if (max == null) + max = max2; + if (min2.x < min.x) + min.x = min2.x; + if (min2.y < min.y) + min.y = min2.y; + if (min2.z < min.z) + min.z = min2.z; + if (max.x < max2.x) + max.x = max2.x; + if (max.y < max2.y) + max.y = max2.y; + if (max.z < max2.z) + max.z = max2.z; + } + if(max==null) + max=new Vector3d(0,0,0); + if(min==null) + min=new Vector3d(0,0,0); + return new Bounds(min, max); + } + + public static WritableImage get(List c) { + ArrayList csgList=new ArrayList() ; + for(CSG cs:c) { + if(cs.getManipulator()!=null) { + csgList.add(cs.transformed(TransformConverter.fromAffine(cs.getManipulator())).syncProperties(cs)); + }else + csgList.add(cs); + } + // Create a group to hold all the meshes + Group root = new Group(); + + // Add all meshes to the group + Bounds b = getSellectedBounds(csgList); + + double yOffset = (b.getMax().y-b.getMin().y)/2; + double xOffset =(b.getMax().x -b.getMin().x)/2; + double zCenter = (b.getMax().z -b.getMin().z)/2; + for (CSG csg : csgList) { + if(csg.isHide()) + continue; + if(csg.isInGroup()) + continue; + MeshView meshView = csg.movez(-zCenter).getMesh(); + if (csg.isHole()) { + PhongMaterial material = new PhongMaterial(); + material.setDiffuseColor(new Color(0.25, 0.25, 0.25, 0.75)); + material.setSpecularColor(javafx.scene.paint.Color.WHITE); + meshView.setMaterial(material); + meshView.setOpacity(0.25); + } + meshView.setCullFace(getCullFaceValue()); + root.getChildren().add(meshView); + } + + // Calculate the bounds of all CSGs combined + double totalz = b.getMax().z - b.getMin().z; + double totaly = b.getMax().y - b.getMin().y; + double totalx = b.getMax().x - b.getMin().x; + + // Create a perspective camera + PerspectiveCamera camera = new PerspectiveCamera(true); + + // Calculate camera position to fit all objects in view + double maxDimension = Math.max(totalx, Math.max(totaly, totalz)); + double cameraDistance = (maxDimension / Math.tan(Math.toRadians(camera.getFieldOfView() / 2)))*0.8 ; + +// TransformNR camoffset = new TransformNR(xOffset, yOffset, 0); +// TransformNR camDist = new TransformNR(0, 0, -cameraDistance); +// TransformNR rot = new TransformNR(new RotationNR(-150, 45, 0)); +// +// Affine af = TransformFactory.nrToAffine(camoffset.times(rot.times(camDist))); + Affine camDist = new Affine(); + camDist.setTz(-cameraDistance); + Rotate rot1 =new Rotate(45, Rotate.Z_AXIS); + Rotate rot2 =new Rotate(-150, Rotate.Y_AXIS); + Affine camoffset = new Affine(); + camoffset.setTx(xOffset); + camoffset.setTy(yOffset); + camera.getTransforms().add(camoffset ); + camera.getTransforms().add(rot2 ); + camera.getTransforms().add(rot1 ); + camera.getTransforms().add(camDist ); + // + + + // Position the camera +// camera.setTranslateX(); +// camera.setTranslateY(); +// camera.setTranslateZ(); +// // Apply rotations to the root group instead of the camera +// root.getTransforms().addAll( +// new Rotate(-5, Rotate.Y_AXIS), +// new Rotate(-45, Rotate.X_AXIS) +// ); + // Create a scene with the group and camera + int i = 1000; + Scene scene = new Scene(root, i, i, true, SceneAntialiasing.BALANCED); + scene.setFill(Color.TRANSPARENT); + scene.setCamera(camera); + + // Set up snapshot parameters + SnapshotParameters params = new SnapshotParameters(); + params.setFill(Color.TRANSPARENT); + params.setCamera(camera); + params.setDepthBuffer(true); + params.setTransform(Transform.scale(1, 1)); + // Set the near and far clip + camera.setNearClip(0.1); // Set the near clip plane + camera.setFarClip(9000.0); // Set the far clip plane + + + // Create the WritableImage first + WritableImage snapshot = new WritableImage(i, i); + + root.snapshot(params, snapshot); + + return snapshot; + } + + public static Thread writeImage(ArrayList incoming, File toPNG) { + Thread t= new Thread(new Runnable() { + WritableImage img=null; + @Override + public void run() { + File image = toPNG; + javafx.application.Platform.runLater(() -> img=ThumbnailImage.get(incoming)); + while (img == null) + try { + Thread.sleep(16); + // System.out.println("Waiting for image to write"); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + break; + } + BufferedImage bufferedImage = SwingFXUtils.fromFXImage(img, null); + + try { + ImageIO.write(bufferedImage, "png", image); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + t.setUncaughtExceptionHandler((thread, throwable) -> { + throwable.printStackTrace(); + thread.interrupt(); + }); + t.start(); + return t; + } + + public static CullFace getCullFaceValue() { + return cullFaceValue; + } + + public static void setCullFaceValue(CullFace cullFaceValue) { + ThumbnailImage.cullFaceValue = cullFaceValue; + } +} diff --git a/src/main/java/eu/mihosoft/vrl/v3d/thumbnail/TransformConverter.java b/src/main/java/eu/mihosoft/vrl/v3d/thumbnail/TransformConverter.java new file mode 100644 index 00000000..33349046 --- /dev/null +++ b/src/main/java/eu/mihosoft/vrl/v3d/thumbnail/TransformConverter.java @@ -0,0 +1,34 @@ +package eu.mihosoft.vrl.v3d.thumbnail; + +import javax.vecmath.Matrix4d; + +import eu.mihosoft.vrl.v3d.Transform; + +public class TransformConverter { + public static Transform fromAffine(javafx.scene.transform.Affine rotations) { + double[] v = new double[]{ + rotations.getMxx(), + rotations.getMxy(), + rotations.getMxz(), + 0, + rotations.getMyx(), + rotations.getMyy(), + rotations.getMyz(), + 0, + rotations.getMzx(), + rotations.getMzy(), + rotations.getMzz(), + 0, + rotations.getTx(), + rotations.getTy(), + rotations.getTz(), + 1 + }; + Matrix4d rotation = new Matrix4d(v ); + //eu.mihosoft.vrl.v3d.Transform transform = new eu.mihosoft.vrl.v3d.Transform(rotation); + eu.mihosoft.vrl.v3d.Transform transform = new eu.mihosoft.vrl.v3d.Transform(); + System.out.println("Incoming "+rotations); + System.out.println("Converted to "+transform); + return transform; + } +} diff --git a/src/test/java/eu/mihosoft/vrl/v3d/SVGLoadTest.java b/src/test/java/eu/mihosoft/vrl/v3d/SVGLoadTest.java index e859540c..89383220 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/SVGLoadTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/SVGLoadTest.java @@ -11,38 +11,65 @@ import org.junit.Test; import eu.mihosoft.vrl.v3d.svg.SVGLoad; +import eu.mihosoft.vrl.v3d.thumbnail.ThumbnailImage; +import javafx.scene.shape.CullFace; public class SVGLoadTest { + @Test + public void adversarial() throws IOException { + JavaFXInitializer.go(); + File svg = new File("Part-Num-0.svg"); + if (!svg.exists()) + throw new RuntimeException("Test file missing!" + svg.getAbsolutePath()); + SVGLoad s = new SVGLoad(svg.toURI()); + ArrayListparts =run(s); + try { + ThumbnailImage.setCullFaceValue(CullFace.NONE); + ThumbnailImage.writeImage(parts,new File(svg.getAbsolutePath()+".png")).join(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + // fail("Not yet implemented"); + } @Test public void test() throws IOException { File svg = new File("Test.SVG"); - if(!svg.exists()) - throw new RuntimeException("Test file missing!"+svg.getAbsolutePath()); + if (!svg.exists()) + throw new RuntimeException("Test file missing!" + svg.getAbsolutePath()); SVGLoad s = new SVGLoad(svg.toURI()); run(s); - //fail("Not yet implemented"); + // fail("Not yet implemented"); } - private ArrayList run(SVGLoad s) { - - ArrayList polys= new ArrayList<>(); + + private ArrayList run(SVGLoad s) { + + ArrayList p = new ArrayList<>(); HashMap> polygons = s.toPolygons(); - for(String key:polygons.keySet()) { - for(Polygon P:polygons.get(key)) { - polys.add(P); + for (String key : polygons.keySet()) { + for (Polygon P : polygons.get(key)) { + p.add(P); } } - + ArrayList polys = new ArrayList(); List layers = s.getLayers(); - double depth =5+(layers.size()*5); - for(int i=0;i> extrudeLayerToCSG = s.extrudeLayers(depth,0.1, layerName); + // extrudeLayerToCSG.setColor(Color.web(SVGExporter.colorNames.get(i))); + for(String key:extrudeLayerToCSG.keySet()) { + System.out.println("Adding layer: "+key); + polys.add(CSG.unionAll(extrudeLayerToCSG.get(key))); +// for(CSG c:extrudeLayerToCSG.get(key)) { +// polys.add(c); +// } + } + depth -= 5; } - + return polys; }