diff --git a/.gitignore b/.gitignore index c8b7c802..a80040b0 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,7 @@ html /file.txt /file2.txt /public.pgp +/HoleCut.stl.png +/Bolt.stl.png +/hs_err_*.log +/vows.svg.png diff --git a/flame.svg b/flame.svg new file mode 100644 index 00000000..30638c10 --- /dev/null +++ b/flame.svg @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/src/main/java/com/piro/bezier/BezierPath.java b/src/main/java/com/piro/bezier/BezierPath.java index fb44b453..47924fb5 100644 --- a/src/main/java/com/piro/bezier/BezierPath.java +++ b/src/main/java/com/piro/bezier/BezierPath.java @@ -7,26 +7,25 @@ import java.util.regex.Pattern; import eu.mihosoft.vrl.v3d.Edge; +import eu.mihosoft.vrl.v3d.Extrude; import eu.mihosoft.vrl.v3d.Plane; import eu.mihosoft.vrl.v3d.Vector3d; import eu.mihosoft.vrl.v3d.Vertex; public class BezierPath { + private static final double MaximumInterpolationStep = 0.5; + static final Matcher matchPoint = Pattern.compile("\\s*(\\d+)[^\\d]+(\\d+)\\s*").matcher(""); BezierListProducer path; private ArrayList plInternal = new ArrayList(); - double resolution = 0.075; + private final int resolutionPoints; /** Creates a new instance of Animate */ - public BezierPath() { - } - - /** Creates a new instance of Animate */ - public BezierPath(String path) { - parsePathString(path); + public BezierPath(int resolution) { + this.resolutionPoints = resolution; } public void parsePathString(String d) { @@ -57,7 +56,7 @@ protected void parsePathList(String list) { } else { tokens.addFirst(curToken); } - double x, y; + double x, y; switch (curCmd) { case 'M': x = nextFloat(tokens); @@ -108,53 +107,37 @@ protected void parsePathList(String list) { break; case 'Q': path.curvetoQuadraticAbs(nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens)); - for (double i = resolution; i < 1; i += resolution) { - addingPoint(i); - } + expandPath(); break; case 'q': path.curvetoQuadraticAbs(nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens)); - for (double i = resolution; i < 1; i += resolution) { - addingPoint(i); - } + expandPath(); break; case 'T': path.curvetoQuadraticSmoothAbs(nextFloat(tokens), nextFloat(tokens)); - for (double i = resolution; i < 1; i += resolution) { - addingPoint(i); - } + expandPath(); break; case 't': path.curvetoQuadraticSmoothRel(nextFloat(tokens), nextFloat(tokens)); - for (double i = resolution; i < 1; i += resolution) { - addingPoint(i); - } + expandPath(); 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) { - addingPoint(i); - } + expandPath(); 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) { - addingPoint(i); - } + expandPath(); break; case 'S': path.curvetoCubicSmoothAbs(nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens)); - for (double i = resolution; i < 1; i += resolution) { - addingPoint(i); - } + expandPath(); break; case 's': path.curvetoCubicSmoothRel(nextFloat(tokens), nextFloat(tokens), nextFloat(tokens), nextFloat(tokens)); - for (double i = resolution; i < 1; i += resolution) { - addingPoint(i); - } + expandPath(); break; case 'Z': case 'z': @@ -170,22 +153,49 @@ protected void parsePathList(String list) { } } + private void expandPath() { + double resolution = getResolution(); + for (double i = 0; i < 1; i += resolution) { + addingPoint(i); + } + addingPoint(1); + } + + private double getResolution() { + Vector3d start = path.bezierSegs.get(path.bezierSegs.size() - 1).eval(0); + Vector3d end = path.bezierSegs.get(path.bezierSegs.size() - 1).eval(1); + double magnitude = start.minus(end).magnitude(); + if (magnitude < Plane.getEPSILON()) + return 1; + double dpoints = magnitude /0.75; + if (dpoints < 1) + dpoints = 1; + double increment = 1.0 / dpoints; + double min = 1.0 / ((double) resolutionPoints); + if (increment < min) + increment = min; + if (increment > MaximumInterpolationStep) + increment = MaximumInterpolationStep; +// System.out.println("Path with inc "+points); + return increment; + } + private boolean addingPoint(double i) { Vector3d eval = path.bezierSegs.get(path.bezierSegs.size() - 1).eval(i); return setThePoint(eval); } private boolean setThePoint(Vector3d eval) { - int end = plInternal.size()-1; - - for(Vector3d v:plInternal) { - if(Math.abs(v.minus(eval).magnitude())<0.001) { - return false; + int end = plInternal.size() - 1; + for (int i = 0; i < plInternal.size(); i++) + if (end > 0) { + if (Math.abs(plInternal.get(i).minus(eval).magnitude()) < Extrude.getMinimumDIstance()) { + return false; + } } - } - if(plInternal.size()>1) { - Edge e = new Edge(new Vertex(plInternal.get(end-1),null), new Vertex(plInternal.get(end),null)); - if(e.colinear(eval)) { + if (plInternal.size() > 1) { + Edge e = new Edge(new Vertex(plInternal.get(end - 1)), new Vertex(plInternal.get(end))); + if (e.colinear(eval)) { plInternal.set(end, eval); return true; } @@ -193,7 +203,7 @@ private boolean setThePoint(Vector3d eval) { return plInternal.add(eval); } - static protected double nextFloat(LinkedList l) { + static protected double nextFloat(LinkedList l) { String s = l.removeFirst(); return Float.parseFloat(s); } @@ -202,12 +212,12 @@ static protected double nextFloat(LinkedList l) { * Evaluates this animation element for the passed interpolation time. Interp * must be on [0..1]. */ - public Vector3d eval(double interp) { + public Vector3d eval(double interp) { Vector3d point = new Vector3d(0, 0);// = new Vector3d(); - if (interp < 0.001) - interp = (double ) 0.001; - if (interp > 0.9999) - interp = (double ) 0.9999; +// if (interp < 0.001) +// interp = (double ) 0.001; +// if (interp > 0.9999) +// interp = (double ) 0.9999; double curLength = path.curveLength * interp; for (Iterator it = path.bezierSegs.iterator(); it.hasNext();) { diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Bounds.java b/src/main/java/eu/mihosoft/vrl/v3d/Bounds.java index 05dc80f2..a1ae02ca 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Bounds.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Bounds.java @@ -162,19 +162,7 @@ public boolean contains(Polygon p) { return p.getVertices().stream().allMatch(v -> contains(v)); } - /** - * Indicates whether the specified polygon intersects with this bounding box - * (check includes box boundary). - * - * @param p polygon to check - * @return {@code true} if the polygon intersects this bounding box; - * {@code false} otherwise - * @deprecated not implemented yet - */ - @Deprecated - public boolean intersects(Polygon p) { - throw new UnsupportedOperationException("Implementation missing!"); - } + /** * Indicates whether the specified bounding box intersects with this diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index 72afe5bb..cfcd0e49 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -43,6 +43,7 @@ import java.io.IOException; import java.io.Serializable; +import java.lang.reflect.Field; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; @@ -52,6 +53,9 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -61,6 +65,7 @@ import com.aparapi.Range; import com.aparapi.device.Device; import com.aparapi.internal.kernel.KernelManager; +import com.aparapi.internal.kernel.KernelRunner; import com.neuronrobotics.interaction.CadInteractionEvent; import javafx.scene.paint.Color; @@ -128,6 +133,7 @@ @SuppressWarnings("restriction") public class CSG implements IuserAPI, Serializable { + private static final double POINTS_CONTACT_DISTANCE = 0.00001; private static int MinPolygonsForOffloading = 200; private static final long serialVersionUID = 4071874097772427063L; private static IDebug3dProvider providerOf3d = null; @@ -187,6 +193,7 @@ public void progressUpdate(int currentIndex, int finalIndex, String type, CSG in System.err.println(type + " cur:" + currentIndex + " of " + finalIndex); } }; + private static ForkJoinPool poolGlobal=null; /** * Instantiates a new csg. @@ -882,27 +889,82 @@ public CSG dumbUnion(CSG csg) { * * @return union of this csg and the specified csgs */ - public CSG union(List csgs) { + public CSG union(List incoming) { if (CSGClient.isRunning()) { ArrayList go = new ArrayList(); go.add(this); - go.addAll(csgs); + go.addAll(incoming); try { return CSGClient.getClient().union(go).get(0); } catch (Exception e) { throw new RuntimeException(e); } } - CSG result = this; + CSG solid = this.isHole()?null:this; + CSG hole = this.isHole()?this:null; + ArrayList csgs=new ArrayList(); + CSG dumb = null; + if(incoming.size()<10) { + csgs.addAll(incoming); + }else { + for( int i=0;i()).historySync(this).historySync(csg); } - Node a = new Node(this.clone().getPolygons()); - Node b = new Node(csg.clone().getPolygons()); - a.invert(); - b.clipTo(a); - b.invert(); - a.clipTo(b); - b.clipTo(a); - a.build(b.allPolygons()); - a.invert(); - CSG back = CSG.fromPolygons(a.allPolygons()).optimization(getOptType()).historySync(csg).historySync(this); - if (getName().length() != 0 && csg.getName().length() != 0) { - back.setName(name); + Node a; + try { + a = new Node(this.clone().getPolygons(),this.getPolygons().get(0).getPlane()); + Node b = new Node(csg.clone().getPolygons(),csg.getPolygons().get(0).getPlane()); + a.invert(); + b.clipTo(a); + b.invert(); + a.clipTo(b); + b.clipTo(a); + a.build(b.allPolygons()); + a.invert(); + CSG back = CSG.fromPolygons(a.allPolygons()).optimization(getOptType()).historySync(csg).historySync(this); + if (getName().length() != 0 && csg.getName().length() != 0) { + back.setName(name); + } + return back; + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); } - return back; + return this; } /** @@ -1559,7 +1643,7 @@ public StringBuilder toStlString(StringBuilder sb) { sb.append("solid v3d.csg\n"); for (Polygon p : getPolygons()) { try { - Plane.computeNormal(p.getVertices()); + Plane.createFromPoints(p.getVertices(),null); p.toStlString(sb); } catch (Exception ex) { System.out.println("Prune Polygon on export"); @@ -1694,11 +1778,11 @@ private int runGPUMakeManifold(int iteration, int np, int longLength, int numPol } // System.out.println("Data loaded!"); - float eps = 0.00001f; + float eps = (float)POINTS_CONTACT_DISTANCE; float epsSq = (float) (eps * eps); int[] added = new int[numberOfPolygons]; - int testPointChunk = 50; - int snapChunk = 500; + int testPointChunk = 100; + int snapChunk = 1000; int[] tp = new int[] { 0, snapChunk }; // Aparapi-compatible kernel with flattened data @@ -1986,84 +2070,181 @@ public void run() { // } // pointIndexSet.add(pointIndex); Vector3d thispoint = orderedPoints[pointIndex]; - points.add(new Vertex(thispoint, pl.getNormal())); + points.add(new Vertex(thispoint)); } if (points.size() < 3) { System.out.println("ERR polygon " + i + " pruned because of too few points"); continue; } - Polygon p = new Polygon(points, polygon.getStorage(), true, pl); - newPoly.add(p); + Polygon p; + try { + p = new Polygon(points, polygon.getStorage(), true, pl); + newPoly.add(p); + } catch (ColinearPointsException e) { + System.out.println("Prining "+points+" "+e); + } polygon.getPoints().clear(); } polygons.clear(); polygons = newPoly; return pointsAdded; } + public static List getForkJoinWorkers(ForkJoinPool pool) { + return Thread.getAllStackTraces().keySet().stream() + .filter(thread -> thread instanceof ForkJoinWorkerThread) + .map(thread -> (ForkJoinWorkerThread) thread) + .collect(Collectors.toList()); + } + public static void setPrivateThreadPool(KernelRunner kernelRunner, ForkJoinPool newThreadPool) throws Exception { + // Get the class of the object + Class clazz = kernelRunner.getClass(); + // Get the private field + Field threadPoolField = clazz.getDeclaredField("threadPool"); + + // Make it accessible + threadPoolField.setAccessible(true); + + // Set the new value + threadPoolField.set(kernelRunner, newThreadPool); + } + + public static ForkJoinPool getPrivateThreadPool(KernelRunner kernelRunner) throws Exception { + Class clazz = kernelRunner.getClass(); + Field threadPoolField = clazz.getDeclaredField("threadPool"); + threadPoolField.setAccessible(true); + return (ForkJoinPool) threadPoolField.get(kernelRunner); + } public static void gpuRun(int numberOfPoints, Kernel kernel, float[] done, String type, BooleanSupplier test, int itr, int expectedIterations) { - progressMoniter.progressUpdate(0, 100, "Start " + typOfCPU(kernel) + type, null); + // progressMoniter.progressUpdate(0, 100, "Start " + typOfCPU(kernel) + type, + // null); + String valueOf = String.valueOf(Runtime.getRuntime().availableProcessors() ); + System.setProperty("com.aparapi.threadPoolSize", valueOf); if (!useGPU) { - String valueOf = String.valueOf(Runtime.getRuntime().availableProcessors() * 4); progressMoniter.progressUpdate(0, 100, "CPU mode " + valueOf, null); - System.setProperty("com.aparapi.threadPoolSize", valueOf); kernel.setExecutionMode(Kernel.EXECUTION_MODE.JTP); // Java Thread Pool } int[] iteration = new int[] { 0 }; - Thread thread = new Thread(() -> { - long begin = System.currentTimeMillis(); + long begin = System.currentTimeMillis(); + boolean print = false; + long timeSinceLastPrint = 0; + long printLimit = 800; + String typOfCPU = typOfCPU(kernel); +// ForkJoinPool commonPool = ForkJoinPool.commonPool(); +// System.out.println("Common ForkJoinPool Status:"); +// System.out.println(" Pool Size: " + commonPool.getPoolSize()); +// System.out.println(" Active Thread Count: " + commonPool.getActiveThreadCount()); +// System.out.println(" Running Thread Count: " + commonPool.getRunningThreadCount()); +// System.out.println(" Queued Task Count: " + commonPool.getQueuedTaskCount()); +// System.out.println(" Queued Submission Count: " + commonPool.getQueuedSubmissionCount()); +// System.out.println(" Steal Count: " + commonPool.getStealCount()); +// System.out.println(" Parallelism: " + commonPool.getParallelism()); +// System.out.println(" Is Shutdown: " + commonPool.isShutdown()); +// System.out.println(" Is Terminated: " + commonPool.isTerminated()); +// List workersInitial=null; +// try { +// +// workersInitial = getForkJoinWorkers(commonPool); +// +// if (workersInitial != null) { +// System.out.println("Worker threads in pool:"); +// for (int i = 0; i < workersInitial.size(); i++) { +// ForkJoinWorkerThread worker = workersInitial.get(i); +// if (worker != null) { +// System.out.println(" Worker[" + i + "]: " + worker.getName() + +// " | State: " + worker.getState() + +// " | Pool Index: " + worker.getPoolIndex() + +// " | ID: " + worker.getId()); +// } +// } +// } +// }catch(Exception ex) { +// ex.printStackTrace(); +// } + try { do { - kernel.execute(numberOfPoints); + KernelRunner kernelRunner = new KernelRunner(kernel); + if(poolGlobal==null) + poolGlobal=getPrivateThreadPool(kernelRunner); + else + setPrivateThreadPool(kernelRunner, poolGlobal); + + kernelRunner.execute("run", Range.create(null,numberOfPoints), 1); + typOfCPU = typOfCPU(kernel); iteration[0] += 1; - long sinceStart = System.currentTimeMillis() - begin; + long now = System.currentTimeMillis(); + long sinceStart = now - begin; long took = sinceStart / iteration[0]; long expected = took * (expectedIterations + 2); - long remaining = expected - sinceStart; - for (int i = 0; i < done.length; i++) { - done[i] = 0; + print = expected > printLimit || print; + if (print) { + if (now - timeSinceLastPrint > printLimit) { + timeSinceLastPrint = now; + long remaining = expected - sinceStart; + if (done != null) + for (int i = 0; i < done.length; i++) { + done[i] = 0; + } + if (remaining < 0) + remaining = 0; + String dur = makeTimestamp(expected); + String rem = makeTimestamp(remaining); + progressMoniter.progressUpdate(iteration[0], expectedIterations, "Rem->" + rem + " " + type + + typOfCPU + " \nTot->" + dur, null); + } } - if (remaining < 0) - remaining = 0; - String dur = makeTimestamp(expected); - String rem = makeTimestamp(remaining); - progressMoniter.progressUpdate(iteration[0], expectedIterations, "Rem->" + rem + " " + type - + typOfCPU(kernel) + "(" + iteration[0] + ") Estimated Total: " + dur, null); + } while (test.getAsBoolean()); - }); - thread.start(); - do { - if (kernel.getTargetDevice().getType().toString().contains("JTP")) { - useGPU = false; - } - if (!useGPU) { - float doneness = 0; - for (int i = 0; i < done.length; i++) { - doneness += done[i]; - } - float percent = doneness / ((float) done.length) * 100.0f; - // int currentPass = kernel.getCurrentPass(); - // percent=(float)currentPass/(float)numberOfPoints; - if (expectedIterations == 1) - progressMoniter.progressUpdate((int) (percent), 100, - typOfCPU(kernel) + type + "(" + iteration[0] + ")", null); - } - try { - Thread.sleep(useGPU ? 1 : 100); - } catch (InterruptedException e) { - // Auto-generated catch block - e.printStackTrace(); - } - } while (kernel.isExecuting() && thread.isAlive()); - try { - thread.join(); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + } catch (Exception ex) { + ex.printStackTrace(); } - progressMoniter.progressUpdate(100, 100, "Finished on " + typOfCPU(kernel), null); +// commonPool.awaitQuiescence(10, TimeUnit.MILLISECONDS); +// List workers=null; +// List workersInit=workersInitial; +// try { +// workers = getForkJoinWorkers(commonPool).stream() +// .filter(afterThread -> workersInit.stream() +// .noneMatch(beforeThread -> beforeThread.getId() == afterThread.getId())) +// .collect(Collectors.toList());; +// +// if (workers != null) { +// System.out.println("After Worker threads in pool:"); +// for (int i = 0; i < workers.size(); i++) { +// ForkJoinWorkerThread worker = workers.get(i); +// if (worker != null) { +// System.out.println(" Worker[" + i + "]: " + worker.getName() + +// " | State: " + worker.getState() + +// " | Pool Index: " + worker.getPoolIndex() + +// " | ID: " + worker.getId()); +// worker.interrupt(); +// } +// } +// } +// }catch(Exception ex) { +// ex.printStackTrace(); +// } +// System.out.println("\n2 Common ForkJoinPool Status:"); +// System.out.println(" 2 Pool Size: " + commonPool.getPoolSize()); +// System.out.println(" 2 Active Thread Count: " + commonPool.getActiveThreadCount()); +// System.out.println(" 2 Running Thread Count: " + commonPool.getRunningThreadCount()); +// System.out.println(" 2 Queued Task Count: " + commonPool.getQueuedTaskCount()); +// System.out.println(" 2 Queued Submission Count: " + commonPool.getQueuedSubmissionCount()); +// System.out.println(" 2 Steal Count: " + commonPool.getStealCount()); +// System.out.println(" 2 Parallelism: " + commonPool.getParallelism()); +// System.out.println(" 2 Is Shutdown: " + commonPool.isShutdown()); +// System.out.println(" 2 Is Terminated: " + commonPool.isTerminated()); + boolean executing ; + do { + executing=kernel.isExecuting(); + }while(executing ); + long sinceStart = System.currentTimeMillis() - begin; + if (print) + progressMoniter.progressUpdate(100, 100, + "Took " + makeTimestamp(sinceStart) + " Finished " + type + " on " + typOfCPU, null); + } private static String typOfCPU(Kernel kernel) { @@ -2076,8 +2257,15 @@ private static String makeTimestamp(long expected) { long hours = duration.toHours(); long minutes = duration.toMinutes() % 60; long seconds = duration.getSeconds() % 60; - - String dur = String.format("%02d:%02d:%02d", hours, minutes, seconds); + long ms = duration.getNano() / 1000000; + if (hours > 0) { + return String.format("h%02d:m%02d", hours, minutes); + } + if (minutes > 0) + return String.format("m%02d:s%02d", minutes, seconds); + if (seconds > 0) + return String.format("s%02d:ms%03d", seconds, ms); + String dur = String.format("ms%03d", ms); return dur; } @@ -2087,22 +2275,17 @@ private void updatePolygons(ArrayList toAdd, Polygon p) { toAdd.add(p); } else { - try { - if (!p.areAllPointsCollinear()) { - List triangles = PolygonUtil.concaveToConvex(p); + + List triangles; + try { + triangles = PolygonUtil.triangulatePolygon(p); for (Polygon poly : triangles) { toAdd.add(poly); } - } else { - System.err.println("Polygon is colinear, removing " + p); - return; + } catch (ColinearPointsException e) { + System.out.println(e.getMessage()+" Polygon pruned "+p); } - } catch (Throwable ex) { -// System.err.println("Failed to triangulate "+p); - ex.printStackTrace(); - progressMoniter.progressUpdate(1, 1, "Pruning bad polygon CSG::updatePolygons " + p, null); - return; - } + } return; @@ -2170,8 +2353,8 @@ public PolygonStruct(PropertyStorage storage, List indices, String mate int startingIndex = vertices.size() + 1; sb.append("\n# Reference Datum").append("\n"); for (Transform t : datumReferences) { - Vertex v = new Vertex(new Vector3d(0, 0, 0), new Vector3d(0, 0, 1)).transform(t); - Vertex v1 = new Vertex(new Vector3d(0, 0, 1), new Vector3d(0, 0, 1)).transform(t); + Vertex v = new Vertex(new Vector3d(0, 0, 0)).transform(t); + Vertex v1 = new Vertex(new Vector3d(0, 0, 1)).transform(t); mapping.put(v, startingIndex++); mapping.put(v1, startingIndex++); mappingTF.put(t, v); @@ -2246,7 +2429,7 @@ public CSG transformed(Transform transform) { return p.transformed(transform); } catch (Exception e) { // e.printStackTrace(); - System.err.println("Removing Polygon during transform"); + System.err.println("Removing Polygon during transform " + p); return null; } }).filter(Objects::nonNull).collect(Collectors.toCollection(ArrayList::new)); @@ -3748,7 +3931,7 @@ public static boolean isPreventNonManifoldTriangles() { public static void setPreventNonManifoldTriangles(boolean preventNonManifoldTriangles) { if (!preventNonManifoldTriangles) { - if(!warned) + if (!warned) System.err.println( "WARNING:This will make STL's incompatible with low quality slicing engines like Slice3r and PrusaSlicer"); warned=true; @@ -3765,6 +3948,8 @@ public static void setUseGPU(boolean useGPU) { } public boolean isBoundsTouching(CSG incoming) { + if(incoming==null) + return false; return getBounds().isBoundsTouching(incoming.getBounds()); } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ColinearPointsException.java b/src/main/java/eu/mihosoft/vrl/v3d/ColinearPointsException.java new file mode 100644 index 00000000..2d888a4e --- /dev/null +++ b/src/main/java/eu/mihosoft/vrl/v3d/ColinearPointsException.java @@ -0,0 +1,11 @@ +package eu.mihosoft.vrl.v3d; + +public class ColinearPointsException extends Exception { + + public ColinearPointsException(String string) { + super(string); + } + + private static final long serialVersionUID = -4762994948568812906L; + +} diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Cube.java b/src/main/java/eu/mihosoft/vrl/v3d/Cube.java index 760078d2..96d1c52f 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Cube.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Cube.java @@ -143,13 +143,14 @@ public List toPolygons() { center.y + dimensions.y * (1 * Math.min(1, i & 2) - 0.5), center.z + dimensions.z * (1 * Math.min(1, i & 4) - 0.5) ); - vertices.add(new Vertex(pos, new Vector3d( - (double) info[1][0], - (double) info[1][1], - (double) info[1][2] - ))); + vertices.add(new Vertex(pos)); } - polygons.add(new Polygon(vertices, properties)); + try { + polygons.add(new Polygon(vertices, properties)); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } if (!centered) { @@ -157,7 +158,12 @@ public List toPolygons() { Transform centerTransform = Transform.unity().translate(dimensions.x / 2.0, dimensions.y / 2.0, dimensions.z / 2.0); for (Polygon p : polygons) { - p.transform(centerTransform); + try { + p.transform(centerTransform); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Cylinder.java b/src/main/java/eu/mihosoft/vrl/v3d/Cylinder.java index 2816c696..77a9ed47 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Cylinder.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Cylinder.java @@ -227,32 +227,47 @@ public List toPolygons() { final Vector3d axisX = new Vector3d(isY ? 1 : 0, !isY ? 1 : 0, 0). cross(axisZ).normalized(); final Vector3d axisY = axisX.cross(axisZ).normalized(); - Vertex startV = new Vertex(s, axisZ.negated()); - Vertex endV = new Vertex(e, axisZ.normalized()); + Vertex startV = new Vertex(s); + Vertex endV = new Vertex(e); List polygons = new ArrayList<>(); for (int i = 0; i < numSlices; i++) { double t0 = i / (double) numSlices, t1 = (i + 1) / (double) numSlices; - polygons.add(new Polygon(Arrays.asList( - startV, - cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t0, -1), - cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t1, -1)), - properties - )); - polygons.add(new Polygon(Arrays.asList( - cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t1, 0), - cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t0, 0), - cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t0, 0), - cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t1, 0)), - properties - )); - polygons.add(new Polygon( - Arrays.asList( - endV, - cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t1, 1), - cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t0, 1)), - properties - )); + try { + polygons.add(new Polygon(Arrays.asList( + startV, + cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t0, -1), + cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t1, -1)), + properties + )); + } catch (ColinearPointsException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + try { + polygons.add(new Polygon(Arrays.asList( + cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t1, 0), + cylPoint(axisX, axisY, axisZ, ray, s, startRadius, 0, t0, 0), + cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t0, 0), + cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t1, 0)), + properties + )); + } catch (ColinearPointsException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + try { + polygons.add(new Polygon( + Arrays.asList( + endV, + cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t1, 1), + cylPoint(axisX, axisY, axisZ, ray, s, endRadius, 1, t0, 1)), + properties + )); + } catch (ColinearPointsException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } } return polygons; @@ -279,7 +294,7 @@ private Vertex cylPoint( Vector3d out = axisX.times(Math.cos(angle)).plus(axisY.times(Math.sin(angle))); Vector3d pos = s.plus(ray.times(stack)).plus(out.times(r)); Vector3d normal = out.times(1.0 - Math.abs(normalBlend)).plus(axisZ.times(normalBlend)); - return new Vertex(pos, normal); + return new Vertex(pos); } /** diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Edge.java b/src/main/java/eu/mihosoft/vrl/v3d/Edge.java index d2fb55c4..922c3e6c 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Edge.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Edge.java @@ -118,16 +118,12 @@ public static List toPoints(List edges) { * @param plane the plane * @return the polygon */ - public static Polygon toPolygon(List points, Plane plane) { + public static Polygon toPolygon(List points, Plane plane) throws ColinearPointsException{ // List points = edges.stream().().map(e -> e.p1.pos). // collect(Collectors.toList()); Polygon p = Polygon.fromPoints(points); - p.getVertices().stream().forEachOrdered((vertex) -> { - vertex.normal = plane.getNormal().clone(); - }); - // // we try to detect wrong orientation by comparing normals // if (p.plane.normal.angle(plane.normal) > 0.1) { // p.flip(); @@ -141,8 +137,9 @@ public static Polygon toPolygon(List points, Plane plane) { * @param boundaryEdges the boundary edges * @param plane the plane * @return the list + * @throws ColinearPointsException */ - public static List toPolygons(List boundaryEdges, Plane plane) { + public static List toPolygons(List boundaryEdges, Plane plane) throws ColinearPointsException { List boundaryPath = new ArrayList<>(); @@ -406,7 +403,7 @@ public static List boundaryPathsWithHoles(List boundaryPaths) * @param boundaryEdges boundary edges (all paths must be closed) * @return the list */ - public static List boundaryPaths(List boundaryEdges) { + public static List boundaryPaths(List boundaryEdges) throws ColinearPointsException{ List result = new ArrayList<>(); boolean[] used = new boolean[boundaryEdges.size()]; @@ -489,8 +486,9 @@ private static int nextUnused(boolean[] usage) { * @param boundaryEdges the boundary edges * @param plane the plane * @return the list + * @throws ColinearPointsException */ - public static List _toPolygons(List boundaryEdges, Plane plane) { + public static List _toPolygons(List boundaryEdges, Plane plane) throws ColinearPointsException { List boundaryPath = new ArrayList<>(); @@ -533,11 +531,11 @@ public static List _toPolygons(List boundaryEdges, Plane plane) { * false otherwise */ public boolean colinear(Vector3d p) { - return colinear(p, Plane.EPSILON_Point); + return colinear(p, Plane.getEPSILON_Point()); } public boolean colinear(Edge p) { - return colinear(p.getP1().pos, Plane.EPSILON_Point) && colinear(p.getP2().pos, Plane.EPSILON_Point); + return colinear(p.getP1().pos, Plane.getEPSILON_Point()) && colinear(p.getP2().pos, Plane.getEPSILON_Point()); } @@ -637,7 +635,7 @@ public boolean contains(Vector3d p, double TOL) { double t = dotProduct / edgeLengthSq; // If 0 ≤ t ≤ 1, the point is within the bounds of the edge - return t >= 0 && t <= 1; + return t > 0 && t < 1; } /** @@ -678,12 +676,12 @@ public boolean equals(Object obj) { return false; } final Edge other = (Edge) obj; - if (this.p1.pos.test(other.p1.pos, Plane.EPSILON_Point) - && this.p2.pos.test(other.p2.pos, Plane.EPSILON_Point)) { + if (this.p1.pos.test(other.p1.pos, Plane.getEPSILON_Point()) + && this.p2.pos.test(other.p2.pos, Plane.getEPSILON_Point())) { return true; } - if (this.p1.pos.test(other.p2.pos, Plane.EPSILON_Point) - && this.p2.pos.test(other.p1.pos, Plane.EPSILON_Point)) { + if (this.p1.pos.test(other.p2.pos, Plane.getEPSILON_Point()) + && this.p2.pos.test(other.p1.pos, Plane.getEPSILON_Point())) { return true; } if (!(Objects.equals(this.p1, other.p1) || Objects.equals(this.p2, other.p1))) { @@ -781,7 +779,7 @@ public Optional getIntersection(Edge e) { Vector3d closestP = closestPOpt.get(); - if (e.contains(closestP)) { + if (e.contains(closestP, Plane.getEPSILON())) { return closestPOpt; } else { // intersection point outside of segment @@ -812,8 +810,9 @@ public Optional getCrossingPoint(Edge e) { * * @param csg the csg * @return the list + * @throws ColinearPointsException */ - public static List boundaryPolygons(CSG csg) { + public static List boundaryPolygons(CSG csg) throws ColinearPointsException { List result = new ArrayList<>(); for (List polygonGroup : searchPlaneGroups(csg.getPolygons())) { @@ -874,7 +873,7 @@ public static List boundaryEdgesOfPlaneGroup(List planeGroup) { } List realBndEdges = bndEdgeStream - .filter(be -> edges.stream().filter(e -> falseBoundaryEdgeSharedWithOtherEdge(be, e)).count() == 0) + .filter(be -> edges.stream().filter(e -> falseBoundaryEdgeSharedWithOtherEdge(be, e)!=null).count() == 0) .collect(Collectors.toList()); // @@ -889,8 +888,9 @@ public static List boundaryEdgesOfPlaneGroup(List planeGroup) { * * @param planeGroup the plane group * @return the list + * @throws ColinearPointsException */ - private static List boundaryPolygonsOfPlaneGroup(List planeGroup) { + private static List boundaryPolygonsOfPlaneGroup(List planeGroup) throws ColinearPointsException { List polygons = boundaryPathsWithHoles(boundaryPaths(boundaryEdgesOfPlaneGroup(planeGroup))); @@ -905,42 +905,48 @@ private static List boundaryPolygonsOfPlaneGroup(List planeGro if (!holesOfPresult.isPresent()) { result.add(p); } else { - result.addAll(PolygonUtil.concaveToConvex(p)); + result.addAll(PolygonUtil.triangulatePolygon(p)); } } return result; } - /** - * False boundary edge shared with other edge. - * - * @param fbe the fbe - * @param e the e - * @return true, if successful - */ - public static boolean falseBoundaryEdgeSharedWithOtherEdge(Edge fbe, Edge e) { + public static Vertex falseBoundaryEdgeSharedWithOtherEdge(Edge fbe, Edge e) { // we don't consider edges with shared end-points since we are only // interested in "false-boundary-edge"-cases - boolean sharedEndPointsp1 = e.getP1().pos.test(fbe.getP1().pos) || e.getP1().pos.test(fbe.getP2().pos); + boolean test1 = e.getP1().pos.test(fbe.getP1().pos); + boolean test3 = e.getP1().pos.test(fbe.getP2().pos); + boolean sharedEndPointsp1 = test1 || test3; - boolean sharedP2= e.getP2().pos.test(fbe.getP1().pos) || e.getP2().pos.test(fbe.getP2().pos); + boolean test = e.getP2().pos.test(fbe.getP1().pos); + boolean test2 = e.getP2().pos.test(fbe.getP2().pos); + boolean sharedP2= test || test2; boolean containsP2 = fbe.contains(e.getP2().pos); boolean containsP1 = fbe.contains(e.getP1().pos); - if(containsP2||containsP1) { + if(sharedEndPointsp1 && sharedP2) { //System.out.println("Edge Contains point!"); } - if ((!sharedP2) && containsP2) { - return true; + if ((sharedP2) && containsP1) { + return e.getP2(); } - if ((!sharedEndPointsp1) && containsP1) { - return true; + if ((sharedEndPointsp1) && containsP2) { + return e.getP1(); } - return false;//fbe.contains(e.getP1().pos) || fbe.contains(e.getP2().pos); + return null; } +// +// /** Distance from point r to the infinite line through a → b */ +// private static double distancePointToLine(Vector3d r, Vector3d a, Vector3d b) { +// Vector3d ab = b.minus(a); +// Vector3d ar = r.minus(a); +// Vector3d cross = ab.cross(ar); +// return cross.length() / ab.length(); +// } + /** * Search plane groups. diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java b/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java index a84dff94..2cb9f53c 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Extrude.java @@ -37,6 +37,8 @@ import com.piro.bezier.BezierPath; import eu.mihosoft.vrl.v3d.Transform; import eu.mihosoft.vrl.v3d.svg.*; +import javafx.scene.paint.Color; + import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -53,6 +55,7 @@ * @author Michael Hoffer <info@michaelhoffer.de> */ public class Extrude { + private static double MINIMUM_DISTANCE = 0.005; private static IExtrusion extrusionEngine = new IExtrusion() { /** * Extrudes the specified path (convex or concave polygon without holes or @@ -62,12 +65,13 @@ public class Extrude { * @param points path (convex or concave polygon without holes or intersections) * * @return a CSG object that consists of the extruded polygon + * @throws ColinearPointsException */ - public CSG points(Vector3d dir, List points) { + public CSG points(Vector3d dir, List points) throws ColinearPointsException { List newList = new ArrayList<>(points); - - return extrude(dir, Polygon.fromPoints(toCCW(newList))); + Polygon fromPoints = Polygon.fromPoints(toCCW(newList)); + return extrude(dir, fromPoints); } /** @@ -79,56 +83,42 @@ public CSG points(Vector3d dir, List points) { */ public CSG extrude(Vector3d dir, Polygon polygon1) { - return monotoneExtrude(dir, polygon1); + try { + return monotoneExtrude(dir, polygon1); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return new Cube(10).toCSG().setColor(Color.PINK); } - private CSG monotoneExtrude(Vector3d dir, Polygon polygon1) { + + private CSG monotoneExtrude(Vector3d dir, Polygon polygon1) throws ColinearPointsException { ArrayList newPolygons = new ArrayList<>(); CSG extrude; - //polygon1=polygon1.flipped(); -// List newVertices = new ArrayList<>(); -// polygon1.getVertices().forEach((vertex) -> { -// newVertices.add(vertex.clone()); -// }); -// Polygon top= new Polygon(newVertices, polygon1.getStorage(),true,polygon1.getPlane().clone()).setColor(polygon1.getColor()); - Polygon top=polygon1.flipped(); - newPolygons.addAll(PolygonUtil.concaveToConvex(top)); - Polygon polygon2 = polygon1.transformed(new Transform().move(dir)); - - int numvertices = polygon1.getVertices().size(); - //com.neuronrobotics.sdk.common.Log.error("Building Polygon "+polygon1.getPoints().size()); - for (int i = 0; i < numvertices; i++) { - - int nexti = (i + 1) % numvertices; - - Vector3d bottomV1 = polygon1.getVertices().get(i).pos; - Vector3d topV1 = polygon2.getVertices().get(i).pos; - Vector3d bottomV2 = polygon1.getVertices().get(nexti).pos; - Vector3d topV2 = polygon2.getVertices().get(nexti).pos; - double distance = bottomV1.minus(bottomV2).magnitude(); - if(Math.abs(distance)<0.001) { - //com.neuronrobotics.sdk.common.Log.error("Skipping invalid polygon "+i+" to "+nexti); - continue; - } - try { - newPolygons.add(Polygon.fromPoints(Arrays.asList(bottomV2, topV2, topV1), polygon1.getStorage())); - newPolygons.add(Polygon.fromPoints(Arrays.asList(bottomV2, topV1, bottomV1), polygon1.getStorage())); - }catch(Exception ex) { - //com.neuronrobotics.sdk.common.Log.error("Polygon has problems: "); - ex.printStackTrace(); - } + ArrayList triangulatePolygon = PolygonUtil.triangulatePolygon(polygon1); + for(Polygon p:triangulatePolygon) { + newPolygons.add(p.flipped()); + newPolygons.add(p.transformed(new Transform().move(dir))); } + Polygon polygon2 = polygon1.transformed(new Transform().move(dir)); + List parts = Extrude.monotoneExtrude(polygon2, polygon1); + newPolygons.addAll(parts); - ArrayList topPolygons = PolygonUtil.concaveToConvex(polygon2); - - newPolygons.addAll(topPolygons); + //ArrayList topPolygons = PolygonUtil.triangulatePolygon(polygon2); + extrude = CSG.fromPolygons(newPolygons); - return extrude; } @Override public CSG extrude(Vector3d dir, List points) { - return points(dir, points); + try { + return points(dir, points); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return new Cube(10).toCSG().setColor(Color.PINK); } }; @@ -139,76 +129,9 @@ private Extrude() { throw new AssertionError("Don't instantiate me!", null); } - public static CSG polygons(Polygon polygon1, Number zDistance) { - return polygons(polygon1, polygon1.transformed(new Transform().movez(zDistance))); - } - - public static CSG polygons(Polygon polygon1, Polygon polygon2) { - // if(!isCCW(polygon1)) { - // polygon1=Polygon.fromPoints(toCCW(polygon1.getPoints())); - // } - // if(!isCCW(polygon2)) { - // polygon2=Polygon.fromPoints(toCCW(polygon2.getPoints())); - // } - - ArrayList newPolygons = new ArrayList<>(); - CSG extrude; - newPolygons.addAll(PolygonUtil.concaveToConvex(polygon1.flipped())); - if (polygon1.getVertices().size() != polygon2.getVertices().size()) { - throw new RuntimeException("These polygons do not match"); - } - int numvertices = polygon1.getVertices().size(); - for (int i = 0; i < numvertices; i++) { - int nexti = (i + 1) % numvertices; - - Vector3d bottomV1 = polygon1.getVertices().get(i).pos; - Vector3d topV1 = polygon2.getVertices().get(i).pos; - Vector3d bottomV2 = polygon1.getVertices().get(nexti).pos; - Vector3d topV2 = polygon2.getVertices().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.flipped()); - - newPolygons.addAll(topPolygons); - extrude = CSG.fromPolygons(newPolygons); - - return extrude; - } - - public static ArrayList polygons(eu.mihosoft.vrl.v3d.Polygon polygon1, ArrayList transforms) { - if (transforms.size() == 1) - transforms.add(0, new Transform()); - polygon1 = Polygon.fromPoints(toCCW(polygon1.getPoints())); - if (transforms.size() < 2) { - transforms.add(0, new Transform()); - } - ArrayList parts = new ArrayList<>(); - Transform transform = new Transform(); - // transform.rotY(90); - for (int i = 0; i < transforms.size() - 1; i++) { - CSG tmp = polygons(polygon1.transformed(transform).transformed(transforms.get(i)), - polygon1.transformed(transform).transformed(transforms.get(i + 1))); - parts.add(tmp); - } - return parts; - - } - - public static ArrayList polygons(eu.mihosoft.vrl.v3d.Polygon polygon1, Transform... transformparts) { - - return polygons(polygon1, (ArrayList) Arrays.asList(transformparts)); - - } - - public static CSG points(Vector3d dir, List points) { + public static CSG points(Vector3d dir, List points) throws ColinearPointsException { return getExtrusionEngine().extrude(dir, points); } @@ -221,8 +144,9 @@ public static CSG points(Vector3d dir, List points) { * @param points path (convex or concave polygon without holes or intersections) * * @return a CSG object that consists of the extruded polygon + * @throws ColinearPointsException */ - public static CSG points(Vector3d dir, Vector3d... points) { + public static CSG points(Vector3d dir, Vector3d... points) throws ColinearPointsException { return points(dir, Arrays.asList(points)); } @@ -232,12 +156,13 @@ public static CSG points(Vector3d dir, Vector3d... points) { * * @param points the points * @return the list + * @throws ColinearPointsException */ - public static List toCCW(List points) { + public static List toCCW(List points) throws ColinearPointsException { List result = new ArrayList<>(points); - if (!isCCW(Polygon.fromPoints(result))) { + if (!isCCWv3d(result)) { Collections.reverse(result); } @@ -249,12 +174,13 @@ public static List toCCW(List points) { * * @param points the points * @return the list + * @throws ColinearPointsException */ - static List toCW(List points) { + static List toCW(List points) throws ColinearPointsException { List result = new ArrayList<>(points); - if (isCCW(Polygon.fromPoints(result))) { + if (isCCWv3d(result)) { Collections.reverse(result); } @@ -266,65 +192,108 @@ static List toCW(List points) { * * @param polygon the polygon * @return true, if is ccw + * @throws ColinearPointsException */ - public static boolean isCCW(Polygon polygon) { - - // thanks to Sepp Reiter for explaining me the algorithm! - - if (polygon.getVertices().size() < 3) { - throw new IllegalArgumentException("Only polygons with at least 3 vertices are supported!"); - } - - // search highest left vertex - int highestLeftVertexIndex = 0; - Vertex highestLeftVertex = polygon.getVertices().get(0); - for (int i = 0; i < polygon.getVertices().size(); i++) { - Vertex v = polygon.getVertices().get(i); - - if (v.pos.y > highestLeftVertex.pos.y) { - highestLeftVertex = v; - highestLeftVertexIndex = i; - } else if (v.pos.y == highestLeftVertex.pos.y && v.pos.x < highestLeftVertex.pos.x) { - highestLeftVertex = v; - highestLeftVertexIndex = i; - } - } - - // determine next and previous vertex indices - int nextVertexIndex = (highestLeftVertexIndex + 1) % polygon.getVertices().size(); - int prevVertexIndex = highestLeftVertexIndex - 1; - if (prevVertexIndex < 0) { - prevVertexIndex = polygon.getVertices().size() - 1; - } - Vertex nextVertex = polygon.getVertices().get(nextVertexIndex); - Vertex prevVertex = polygon.getVertices().get(prevVertexIndex); - - // edge 1 - double a1 = normalizedX(highestLeftVertex.pos, nextVertex.pos); - - // edge 2 - double a2 = normalizedX(highestLeftVertex.pos, prevVertex.pos); - - // select vertex with lowest x value - int selectedVIndex; - - if (a2 > a1) { - selectedVIndex = nextVertexIndex; - } else { - selectedVIndex = prevVertexIndex; - } - - if (selectedVIndex == 0 && highestLeftVertexIndex == polygon.getVertices().size() - 1) { - selectedVIndex = polygon.getVertices().size(); - } - - if (highestLeftVertexIndex == 0 && selectedVIndex == polygon.getVertices().size() - 1) { - highestLeftVertexIndex = polygon.getVertices().size(); - } + public static boolean isCCW(Polygon polygon) throws ColinearPointsException { + return isCCWv3d(polygon.getPoints()); + } + /** + * Checks if is ccw. + * + * @param polygon the polygon + * @return true, if is ccw + * @throws ColinearPointsException + */ + public static boolean isCCW(List vertices) throws ColinearPointsException { + return isCCW(vertices, new Vector3d(0, 0,1)); + } + /** + * Checks if is ccw. + * + * @param polygon the polygon + * @param normal the normal to check the CCW against. + * @return true, if is ccw + * @throws ColinearPointsException + */ + public static boolean isCCW(List vertices, Vector3d normal) throws ColinearPointsException { + Plane p = Plane.createFromPoints(vertices); + + double dot = normal.dot(p.getNormal()); + return dot > (1.0 - Plane.getEPSILON()); + } - // indicates whether edge points from highestLeftVertexIndex towards - // the sel index (ccw) - return selectedVIndex > highestLeftVertexIndex; + /** + * Checks if is ccw. + * + * @param polygon the polygon + * @return true, if is ccw + */ + public static boolean isCCWv3d(List vertices) throws ColinearPointsException { + ArrayList v = new ArrayList(); + for(Vector3d vc:vertices) + v.add(new Vertex(vc)); + return isCCW(v); + +// // thanks to Sepp Reiter for explaining me the algorithm! +// if (vertices.size() < 3) { +// throw new IllegalArgumentException("Only polygons with at least 3 vertices are supported!"); +// } +// +// // search highest left vertex +// int highestLeftVertexIndex = 0; +// Vector3d highestLeftVertex = vertices.get(0); +// double zSet = highestLeftVertex.z; +// for (int i = 0; i < vertices.size(); i++) { +// +// Vector3d v = vertices.get(i); +// double abs = Math.abs(zSet - v.z); +// if (abs > Plane.getEPSILON()) { +// throw new RuntimeException("isCCW can only be performed on the X Y plane "+abs); +// } +// if (v.y > highestLeftVertex.y) { +// highestLeftVertex = v; +// highestLeftVertexIndex = i; +// } else if (v.y == highestLeftVertex.y && v.x < highestLeftVertex.x) { +// highestLeftVertex = v; +// highestLeftVertexIndex = i; +// } +// } +// +// // determine next and previous vertex indices +// int nextVertexIndex = (highestLeftVertexIndex + 1) % vertices.size(); +// int prevVertexIndex = highestLeftVertexIndex - 1; +// if (prevVertexIndex < 0) { +// prevVertexIndex = vertices.size() - 1; +// } +// Vector3d nextVertex = vertices.get(nextVertexIndex); +// Vector3d prevVertex = vertices.get(prevVertexIndex); +// +// // edge 1 +// double a1 = normalizedX(highestLeftVertex, nextVertex); +// +// // edge 2 +// double a2 = normalizedX(highestLeftVertex, prevVertex); +// +// // select vertex with lowest x value +// int selectedVIndex; +// +// if (a2 > a1) { +// selectedVIndex = nextVertexIndex; +// } else { +// selectedVIndex = prevVertexIndex; +// } +// +// if (selectedVIndex == 0 && highestLeftVertexIndex == vertices.size() - 1) { +// selectedVIndex = vertices.size(); +// } +// +// if (highestLeftVertexIndex == 0 && selectedVIndex == vertices.size() - 1) { +// highestLeftVertexIndex = vertices.size(); +// } +// +// // indicates whether edge points from highestLeftVertexIndex towards +// // the sel index (ccw) +// return selectedVIndex > highestLeftVertexIndex; } @@ -349,13 +318,13 @@ public static void setExtrusionEngine(IExtrusion extrusionEngine) { Extrude.extrusionEngine = extrusionEngine; } - public static CSG byPath(List> points, double height) { + public static CSG byPath(List> points, double height) throws ColinearPointsException { return byPath(points, height, 200); } - public static CSG byPath(List> points, double height, int resolution) { + public static CSG byPath(List> points, double height, int resolution) throws ColinearPointsException { ArrayList trPath = pathToTransforms(points, resolution); ArrayList finalPath = new ArrayList<>(); for (Transform tr : trPath) { @@ -415,9 +384,9 @@ public static ArrayList pathToTransforms(List> points, } // println "A string = " +pathStringA // println "B String = " +pathStringB - BezierPath path = new BezierPath(); + BezierPath path = new BezierPath(10); path.parsePathString(pathStringA); - BezierPath path2 = new BezierPath(); + BezierPath path2 = new BezierPath(10); path2.parsePathString(pathStringB); return bezierToTransforms(path, path2, resolution, null, null); @@ -430,10 +399,10 @@ public static ArrayList moveAlongProfile(CSG object, List> p public static ArrayList bezierToTransforms(Vector3d controlA, Vector3d controlB, Vector3d endPoint, int iterations) { - BezierPath path = new BezierPath(); + BezierPath path = new BezierPath(10); path.parsePathString("C " + controlA.x + "," + controlA.y + " " + controlB.x + "," + controlB.y + " " + endPoint.x + "," + endPoint.y); - BezierPath path2 = new BezierPath(); + BezierPath path2 = new BezierPath(10); path2.parsePathString("C " + controlA.x + "," + controlA.z + " " + controlB.x + "," + controlB.z + " " + endPoint.x + "," + endPoint.z); @@ -465,7 +434,7 @@ public static ArrayList bezierToTransforms(BezierPath pathA, BezierPa Vector3d pointBStart = pathB.eval(0); double x = pointAStart.x, y = pointAStart.y, z = pointBStart.y; double lastx = x, lasty = y, lastz = z; - // double min = (double ) 0.0001; + // double min = (double ) 0.0001; int startIndex = 0; if (controlA != null) { startIndex = 1; @@ -475,7 +444,8 @@ public static ArrayList bezierToTransforms(BezierPath pathA, BezierPa double rise = zdiff; double run = Math.sqrt((ydiff * ydiff) + (xdiff * xdiff)); double rotz = 90 - Math.toDegrees(Math.atan2(xdiff, ydiff)); - // //com.neuronrobotics.sdk.common.Log.error("Rot z = "+rotz+" x="+xdiff+" y="+ydiff); + // //com.neuronrobotics.sdk.common.Log.error("Rot z = "+rotz+" x="+xdiff+" + // y="+ydiff); double roty = Math.toDegrees(Math.atan2(rise, run)); Transform t = new Transform(); t.translateX(x); @@ -494,7 +464,7 @@ public static ArrayList bezierToTransforms(BezierPath pathA, BezierPa double rotz; double roty; for (int i = startIndex; i < iterations - 1; i++) { - double pathFunction = (double ) (((double ) i) / ((double ) (iterations - 1))); + double pathFunction = (double) (((double) i) / ((double) (iterations - 1))); Vector3d pointA = pathA.eval(pathFunction); Vector3d pointB = pathB.eval(pathFunction); @@ -508,8 +478,8 @@ public static ArrayList bezierToTransforms(BezierPath pathA, BezierPa t.translateY(y); t.translateZ(z); - Vector3d pointAEst = pathA.eval((double ) (pathFunction + d)); - Vector3d pointBEst = pathB.eval((double ) (pathFunction + d)); + Vector3d pointAEst = pathA.eval((double) (pathFunction + d)); + Vector3d pointBEst = pathB.eval((double) (pathFunction + d)); double xest = pointAEst.x; double yest = pointAEst.y; double zest = pointBEst.y; @@ -521,13 +491,15 @@ public static ArrayList bezierToTransforms(BezierPath pathA, BezierPa rise = zdiff; run = Math.sqrt((ydiff * ydiff) + (xdiff * xdiff)); rotz = 90 - Math.toDegrees(Math.atan2(xdiff, ydiff)); - // //com.neuronrobotics.sdk.common.Log.error("Rot z = "+rotz+" x="+xdiff+" y="+ydiff); + // //com.neuronrobotics.sdk.common.Log.error("Rot z = "+rotz+" x="+xdiff+" + // y="+ydiff); roty = Math.toDegrees(Math.atan2(rise, run)); t.rotZ(-rotz); t.rotY(roty); // if(i==0) - // //com.neuronrobotics.sdk.common.Log.error( " Tr = "+x+" "+y+" "+z+" path = "+pathFunction); + // //com.neuronrobotics.sdk.common.Log.error( " Tr = "+x+" "+y+" "+z+" path = + // "+pathFunction); // println "z = "+rotz+" y = "+roty p.add(t); lastx = x; @@ -535,8 +507,8 @@ public static ArrayList bezierToTransforms(BezierPath pathA, BezierPa lastz = z; } - Vector3d pointA = pathA.eval((double ) 1); - Vector3d pointB = pathB.eval((double ) 1); + Vector3d pointA = pathA.eval((double) 1); + Vector3d pointB = pathB.eval((double) 1); x = pointA.x; y = pointA.y; @@ -574,42 +546,50 @@ public static ArrayList bezierToTransforms(Vector3d start, Vector3d c String b = "M " + start.x + "," + start.z + "\n" + "C " + controlA.x + "," + controlA.z + " " + controlB.x + "," + controlB.z + " " + endPoint.x + "," + endPoint.z; // println "Start = "+startString - BezierPath path = new BezierPath(); + BezierPath path = new BezierPath(10); path.parsePathString(startString); - BezierPath path2 = new BezierPath(); + BezierPath path2 = new BezierPath(10); path2.parsePathString(b); // newParts.remove(parts.size()-1) // newParts.remove(0) - // //com.neuronrobotics.sdk.common.Log.error("Parsing "+startString+" \nand\n"+b); + // //com.neuronrobotics.sdk.common.Log.error("Parsing "+startString+" + // \nand\n"+b); return bezierToTransforms(path, path2, iterations, controlA, controlB); } - public static CSG sweep(Polygon p, Transform increment, Transform offset, int steps) { - return sweep(p,increment,offset,steps, (u,d)->{return new Transform();}); + + public static CSG sweep(Polygon p, Transform increment, Transform offset, int steps) + throws ColinearPointsException { + return sweep(p, increment, offset, steps, (u, d) -> { + return new Transform(); + }); } - public static CSG sweep(Polygon p, Transform increment, Transform offset, int steps,ITransformProvider provider) { + + public static CSG sweep(Polygon p, Transform increment, Transform offset, int steps, ITransformProvider provider) + throws ColinearPointsException { Polygon offsetP = p.transformed(offset); ArrayList newPolygons = new ArrayList<>(); - newPolygons.addAll(PolygonUtil.concaveToConvex(offsetP)); + newPolygons.addAll(PolygonUtil.triangulatePolygon(offsetP)); Transform running = new Transform(); Polygon prev = offsetP.transformed(provider.get(0, steps)); for (int i = 0; i < steps; i++) { running.apply(increment); - double unit = ((double)i)/((double)steps); + double unit = ((double) i) / ((double) steps); Polygon step = offsetP.transformed(provider.get(unit, steps)).transformed(running); List parts = monotoneExtrude(prev, step); prev = step; newPolygons.addAll(parts); } Polygon polygon2 = prev.clone(); - List topPolygons = PolygonUtil.concaveToConvex(polygon2.flipped()); + List topPolygons = PolygonUtil.triangulatePolygon(polygon2.flipped()); newPolygons.addAll(topPolygons); return CSG.fromPolygons(newPolygons); } - public static CSG sweep(Polygon p, double angle, double z, double radius, int steps) { + public static CSG sweep(Polygon p, double angle, double z, double radius, int steps) throws ColinearPointsException { return sweep(p, new Transform().rotX(angle).movex(z), new Transform().movey(radius), steps); } + public static List monotoneExtrude(Polygon polygon2, Polygon polygon1) { List newPolygons = new ArrayList<>(); @@ -624,43 +604,44 @@ public static List monotoneExtrude(Polygon polygon2, Polygon polygon1) Vector3d bottomV2 = polygon1.getVertices().get(nexti).pos; Vector3d topV2 = polygon2.getVertices().get(nexti).pos; double distance = bottomV1.minus(bottomV2).magnitude(); - if (Math.abs(distance) < Plane.getEPSILON()) { - continue; + double z1Dist = topV1.minus(bottomV1).magnitude(); + if (Math.abs(distance) > Plane.getEPSILON() && Math.abs(z1Dist) > Plane.getEPSILON()) { + List asList = Arrays.asList(bottomV2.clone(), topV1.clone(), bottomV1.clone()); + try { + newPolygons.add(Polygon.fromPoints(asList, polygon1.getStorage())); + } catch (ColinearPointsException ex) { + System.out.println(ex.getMessage()+" Pruning from extrude"); + } } - try { - newPolygons.add(Polygon.fromPoints(Arrays.asList(bottomV2, topV2, topV1), polygon1.getStorage())); - newPolygons.add(Polygon.fromPoints(Arrays.asList(bottomV2, topV1, bottomV1), polygon1.getStorage())); - } catch (Exception ex) { - ex.printStackTrace(); + double distance2 = topV2.minus(topV1).magnitude(); + double z1Dist2 = topV2.minus(bottomV2).magnitude(); + if (Math.abs(distance2) > Plane.getEPSILON() && Math.abs(z1Dist2) > Plane.getEPSILON()) { + List asList2 = Arrays.asList(bottomV2.clone(), topV2.clone(), topV1.clone()); + try { + newPolygons.add(Polygon.fromPoints(asList2, polygon1.getStorage())); + } catch (ColinearPointsException ex) { + System.out.println(ex.getMessage()+" Pruning from extrude"); + } } } return newPolygons; } + public static ArrayList revolve(CSG slice, double radius, int numSlices) { return revolve(slice, radius, 360.0, null, numSlices); } - public static ArrayList revolve(Polygon poly, int numSlices) { + public static ArrayList revolve(Polygon poly, int numSlices) throws ColinearPointsException { return revolve(poly, 0, numSlices); } - public static ArrayList revolve(Polygon poly, double radius, int numSlices) { - ArrayList parts = new ArrayList(); - ArrayList slices = new ArrayList(); - - for (int i = 0; i < numSlices; i++) { - double angle = 360.0 / ((double) numSlices) * ((double) i); - slices.add(poly.transformed(new Transform().movex(radius).roty(angle))); - } - for (int i = 0; i < slices.size(); i++) { - int next = i + 1; - if (next == slices.size()) - next = 0; - // println "Extruding "+i+" to "+next - parts.add(Extrude.polygons(slices.get(i), slices.get(next))); - } - - return parts; + public static ArrayList revolve(Polygon poly, double radius, int numSlices) throws ColinearPointsException { + Polygon p=poly; + double angle=360; + double z=0; + int steps=numSlices; + CSG result=sweep( p, angle, z, radius, steps); + return new ArrayList(Arrays.asList(result)); } public static ArrayList revolve(CSG slice, double radius, double archLen, int numSlices) { @@ -782,9 +763,9 @@ private static Vector3d fromDouble(ArrayList controlA) { } public static ArrayList moveBezier(CSG slice, BezierPath pathA, int numSlices) { - Vector3d pointA = pathA.eval((double ) 1.0); + Vector3d pointA = pathA.eval((double) 1.0); String zpath = "C 0,0 " + pointA.x + "," + pointA.y + " " + pointA.x + "," + pointA.y; - BezierPath pathB = new BezierPath(); + BezierPath pathB = new BezierPath(10); pathB.parsePathString(zpath); return moveBezier(slice, pathA, pathB, numSlices); @@ -798,19 +779,22 @@ public static ArrayList moveBezier(CSG slice, BezierPath pathA, BezierPath } - public static Polygon toCCW(Polygon concave) { - if (!isCCW(concave)) { -// List points = concave.getPoints(); -// List result = new ArrayList<>(points); -// Collections.reverse(result); -// return Polygon.fromPoints(result); - List points = concave.getVertices(); - List result = new ArrayList<>(points); - Collections.reverse(result); - Plane p = concave.getPlane().clone(); - p.flip(); - return new Polygon(result, concave.getStorage(), true, p); - } - return concave; +// public static Polygon toCCW(Polygon concave) throws ColinearPointsException { +// if (!isCCW(concave)) { +//// List points = concave.getPoints(); +//// List result = new ArrayList<>(points); +//// Collections.reverse(result); +//// return Polygon.fromPoints(result); +// return concave.flipped(); +// } +// return concave; +// } + + public static double getMinimumDIstance() { + return MINIMUM_DISTANCE; + } + + public static void setMinimumDIstance(double mINIMUM_DISTANCE) { + MINIMUM_DISTANCE = mINIMUM_DISTANCE; } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Fillet.java b/src/main/java/eu/mihosoft/vrl/v3d/Fillet.java index f7314e8e..1205c76b 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Fillet.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Fillet.java @@ -33,7 +33,7 @@ public static CSG corner(double rad, double angle) { // .rotz(180) } - public static CSG outerFillet(CSG base, double rad) { + public static CSG outerFillet(CSG base, double rad) throws ColinearPointsException { List polys = Slice.slice(base); return base.union(outerFillet(polys, rad)); } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/IExtrusion.java b/src/main/java/eu/mihosoft/vrl/v3d/IExtrusion.java index 2df69b49..7d14e817 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/IExtrusion.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/IExtrusion.java @@ -9,7 +9,7 @@ public interface IExtrusion { * @param points * @return */ - CSG extrude(Vector3d dir, List points) ; + CSG extrude(Vector3d dir, List points) throws ColinearPointsException; /** * Extrude. * @@ -19,5 +19,5 @@ public interface IExtrusion { * the polygon1 * @return the csg */ - CSG extrude(Vector3d dir, Polygon polygon1); + CSG extrude(Vector3d dir, Polygon polygon1)throws ColinearPointsException; } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/IPolygonRepairTool.java b/src/main/java/eu/mihosoft/vrl/v3d/IPolygonRepairTool.java index d09ee286..aec99b2a 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/IPolygonRepairTool.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/IPolygonRepairTool.java @@ -1,5 +1,5 @@ package eu.mihosoft.vrl.v3d; public interface IPolygonRepairTool { - Polygon repairOverlappingEdges(Polygon concave); + Polygon repairOverlappingEdges(Polygon concave) throws ColinearPointsException; } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ISlice.java b/src/main/java/eu/mihosoft/vrl/v3d/ISlice.java index 02e43bf3..3ac51ea5 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ISlice.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ISlice.java @@ -10,5 +10,5 @@ public interface ISlice { * @param normalInsetDistance Inset for sliced output * @return A set of polygons defining the sliced shape */ - List slice(CSG incoming, Transform slicePlane, double normalInsetDistance); + List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) throws ColinearPointsException; } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Isosceles.java b/src/main/java/eu/mihosoft/vrl/v3d/Isosceles.java index 5a1bc861..1e08834b 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Isosceles.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Isosceles.java @@ -40,13 +40,19 @@ public List toPolygons() { throw new NumberFormatException("h can not be negative"); if(d<=0) throw new NumberFormatException("d can not be negative"); - CSG polygon = Extrude.points(new Vector3d(0, 0, w),// This is the extrusion depth - new Vector3d(0,0),// All values after this are the points in the polygon - new Vector3d(0,-h/2),// upper right corner - new Vector3d(d,0),// Bottom right corner - new Vector3d(0,h/2)// upper right corner - ).roty(90) - .rotz(180); + CSG polygon=null; + try { + polygon = Extrude.points(new Vector3d(0, 0, w),// This is the extrusion depth + new Vector3d(0,0),// All values after this are the points in the polygon + new Vector3d(0,-h/2),// upper right corner + new Vector3d(d,0),// Bottom right corner + new Vector3d(0,h/2)// upper right corner + ).roty(90) + .rotz(180); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } return polygon.getPolygons(); } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Node.java b/src/main/java/eu/mihosoft/vrl/v3d/Node.java index 72837de2..5b55fb88 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Node.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Node.java @@ -33,12 +33,20 @@ */ package eu.mihosoft.vrl.v3d; +import java.math.BigInteger; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.aparapi.Kernel; +import com.aparapi.device.Device; +import com.aparapi.device.OpenCLDevice; +import com.aparapi.internal.kernel.KernelManager; + import eu.mihosoft.vrl.v3d.ext.org.poly2tri.PolygonUtil; +import javafx.scene.paint.Color; // Auto-generated Javadoc /** @@ -49,10 +57,12 @@ * no distinction between internal and leaf nodes. */ public final class Node { - private static final int COPLANAR = 0; - private static final int FRONT = 1; - private static final int BACK = 2; - private static final int SPANNING = 3; // == some in the FRONT + some in the BACK + private static final int LIMIT_FOR_GPU = 5000; + public static final int COPLANAR = 0; + public static final int FRONT = 1; + public static final int BACK = 2; + public static final int SPANNING = 3; // == some in the FRONT + some in the BACK + /** * Polygons. */ @@ -60,7 +70,7 @@ public final class Node { /** * Plane used for BSP. */ - private Plane plane; + private final Plane myNodePlane; /** * Polygons in front of the plane. */ @@ -72,14 +82,19 @@ public final class Node { private long maxDepth = -1; + private int count = 1; + private static boolean GPUTest = false;; + /** * Constructor. * - * Creates a BSP node consisting of the specified polygons. + * Creates a Binary Space Partition (BSP) node consisting of the specified polygons. * * @param polygons polygons + * @throws Exception */ - public Node(ArrayList polygons) { + public Node(ArrayList polygons,Plane p) throws Exception { + myNodePlane=p.clone(); this.polygons = new ArrayList<>(); if (polygons != null) { this.build(polygons); @@ -89,8 +104,8 @@ public Node(ArrayList polygons) { // /** // * Constructor. Creates a node without polygons. // */ - private Node() { - this(null); + private Node(Plane p) throws Exception { + this(null,p); } /* @@ -100,25 +115,32 @@ private Node() { */ @Override public Node clone() { - Node node = new Node(); - node.setPlane(this.getPlane() == null ? null : this.getPlane().clone()); - node.front = this.front == null ? null : this.front.clone(); - node.back = this.back == null ? null : this.back.clone(); -// node.polygons = new ArrayList<>(); -// polygons.parallelStream().forEach((Polygon p) -> { -// node.polygons.add(p.clone()); -// }); + Node node; + try { + node = new Node(this.getThisNodePlane().clone()); +// node.setPlane(this.getPlane() == null ? null : this.getPlane().clone()); + node.front = this.front == null ? null : this.front.clone(); + node.back = this.back == null ? null : this.back.clone(); +// node.polygons = new ArrayList<>(); +// polygons.parallelStream().forEach((Polygon p) -> { +// node.polygons.add(p.clone()); +// }); - Stream polygonStream; + Stream polygonStream; - if (polygons.size() > 200) { - polygonStream = polygons.parallelStream(); - } else - polygonStream = polygons.stream(); + if (polygons.size() > 200) { + polygonStream = polygons.parallelStream(); + } else + polygonStream = polygons.stream(); - node.polygons = polygonStream.map(p -> p.clone()).collect(Collectors.toCollection(ArrayList::new)); + node.polygons = polygonStream.map(p -> p.clone()).collect(Collectors.toCollection(ArrayList::new)); - return node; + return node; + } catch (Exception e) { + // Auto-generated catch block + e.printStackTrace(); + } + throw new RuntimeException("Failed to clone"); } /** @@ -137,17 +159,17 @@ public void invert() { polygon.flip(); }); - if (this.getPlane() == null && !polygons.isEmpty()) { - this.setPlane(polygons.get(0).getPlane().clone()); - } else if (this.getPlane() == null && polygons.isEmpty()) { - - // com.neuronrobotics.sdk.common.Log.error("Please fix me! I don't know what to - // do?"); - throw new RuntimeException("Please fix me! Plane = " + plane + " and polygons are empty"); - // return; - } +// if (this.getPlane() == null && !polygons.isEmpty()) { +// this.setPlane(polygons.get(0).getPlane().clone()); +// } else if (this.getPlane() == null && polygons.isEmpty()) { +// +// // com.neuronrobotics.sdk.common.Log.error("Please fix me! I don't know what to +// // do?"); +// throw new RuntimeException("Please fix me! Plane = " + getPlane() + " and polygons are empty"); +// // return; +// } - this.getPlane().flip(); + this.getThisNodePlane().flip(); if (this.front != null) { this.front.invert(); @@ -159,7 +181,51 @@ public void invert() { this.front = this.back; this.back = temp; } - +// public void invert() { +// // Use ArrayList as a stack to track nodes to process +// ArrayList stack = new ArrayList<>(); +// stack.add(this); +// +// while (!stack.isEmpty()) { +// // Pop the last node from our stack +// Node current = stack.remove(stack.size() - 1); +// +// // Process polygons for current node +// Stream polygonStream; +// if (current.polygons.size() > 200) { +// polygonStream = current.polygons.parallelStream(); +// } else { +// polygonStream = current.polygons.stream(); +// } +// +// polygonStream.forEach((polygon) -> { +// polygon.flip(); +// }); +// +// // Handle plane logic +// if (current.getPlane() == null && !current.polygons.isEmpty()) { +// current.setPlane(current.polygons.get(0).getPlane().clone()); +// } else if (current.getPlane() == null && current.polygons.isEmpty()) { +// throw new RuntimeException("Please fix me! Plane = " + current.plane + " and polygons are empty"); +// } +// +// current.getPlane().flip(); +// +// // Add child nodes to stack for processing (if they exist) +// // Note: We add them in reverse order so they're processed in the same order as the recursive version +// if (current.back != null) { +// stack.add(current.back); +// } +// if (current.front != null) { +// stack.add(current.front); +// } +// +// // Swap front and back +// Node temp = current.front; +// current.front = current.back; +// current.back = temp; +// } +// } /** * Recursively removes all polygons in the {@link polygons} list that are * contained within this BSP tree. @@ -169,31 +235,658 @@ public void invert() { * @param polygons the polygons to clip * * @return the cliped list of polygons + * @throws Exception */ - private ArrayList clipPolygons(ArrayList polygons) { + private ArrayList clipPolygons(ArrayList polygons) throws Exception { - if (this.getPlane() == null) { - return new ArrayList<>(polygons); + if (this.getThisNodePlane() == null) { + throw new RuntimeException("Plane can not be null"); } - - ArrayList frontP = new ArrayList<>(); - ArrayList backP = new ArrayList<>(); + // preallocate the lists so they do not use dynamic memory in split + ArrayList frontP = new ArrayList<>(polygons.size()); + ArrayList backP = new ArrayList<>(polygons.size()); splitPolygon(polygons, frontP, backP, frontP, backP); - if (this.front != null) { frontP = this.front.clipPolygons(frontP); } if (this.back != null) { backP = this.back.clipPolygons(backP); } else { - backP = new ArrayList<>(0); + backP=new ArrayList(0); } - frontP.addAll(backP); return frontP; } + private static int add(List l, int polygonIndex, int[] polygonStartIndex, int[] polygonSize, + ArrayList orderedPoints, double[] polygonPointX, double[] polygonPointY, double[] polygonPointZ, + Polygon polygon, boolean[] isCopy) { + int polygonBase = polygonStartIndex[polygonIndex]; + int size = polygonSize[polygonIndex]; + if (polygonBase < 0) + return 0; + if (isCopy[polygonIndex]) { + l.add(polygon); + return 1; + } + try { + testAddPolygon(l, orderedPoints, polygonPointX, polygonPointY, polygonPointZ, polygon, polygonBase, size, + false); + return 1; + } catch (Exception ex) { + ex.printStackTrace(); + System.err.println("Pruning bad polygon Node::splitPolygon::add " + l.size()); + } + return 0; + } + + private static void testAddPolygon(List l, ArrayList orderedPoints, double[] polygonPointX, + double[] polygonPointY, double[] polygonPointZ, Polygon polygon, int polygonBase, int size, boolean test) { + List f = new ArrayList<>(); + for (int i = polygonBase; i < polygonBase + size; i++) { + if (i < orderedPoints.size()) { + f.add(orderedPoints.get(i).clone()); + } else { + double x = (polygonPointX[i]); + double y = (polygonPointY[i]); + double z = (polygonPointZ[i]); + Vertex v = new Vertex(new Vector3d(x, y, z)); + addPoint(f, v); + } + } + + add(l, f, polygon, test); + } + + private static boolean addPoint(List f, Vertex v) { + if (f.size() > 0) { +// if (Math.abs(v.pos.distance(f.get(0).pos)) < Plane.getEPSILON()) { +// return false; +// } +// if (Math.abs(v.pos.distance(f.get(f.size() - 1).pos)) < Plane.getEPSILON()) { +// return false; +// } + } + return f.add(v); + } + + private static void add(List l, List f, Polygon polygon) { + add(l, f, polygon, false); + } + + private static void add(List l, List f, Polygon polygon, boolean test) { + if (f.size() < 3) + return; + try { + if(!Extrude.isCCW(f, polygon.getPlane().getNormal())) { + Collections.reverse(f); + } + Polygon fpoly = new Polygon(f, polygon.getStorage(), true, polygon.getPlane()) + .setColor(polygon.getColor()); + if (!test) + l.add(fpoly); + }catch(ColinearPointsException ex) { + System.err.println(ex.getMessage()+" Pruned Colinear polygon "+f ); + } + } + + /** + * 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 + * @throws Exception + */ + public void splitPolygon(ArrayList polygons, List cf, List cb, + List f, List b) throws Exception { + if (polygons.size() > LIMIT_FOR_GPU) { + splitPolygonGPU(polygons, cf, cb, f, b); + return; + } + splitPolygonOriginal(polygons, cf, cb, f, b); + +// List cf1 = new ArrayList(); +// List cb1= new ArrayList(); +// List f1= new ArrayList(); +// List b1= new ArrayList(); +// List cf2= new ArrayList(); +// List cb2= new ArrayList(); +// List f2= new ArrayList(); +// List b2= new ArrayList(); +// splitPolygonOriginal(polygons, cf2, cb2, f2, b2); +// splitPolygonGPU(polygons, cf1, cb1, f1, b1); +// +// if(cf1.size()!=cf2.size() +// ||cb1.size()!=cb2.size() +// || f1.size()!=f2.size() +// ||b1.size()!=b2.size()) { +// throw new RuntimeException("Node split mismathch"); +// } +// for (int i = 0; i < cf1.size(); i++) { +// Polygon p1 = cf1.get(i); +// Polygon p2 = cf2.get(i); +// if(p1.size()!=p2.size()) +// throw new RuntimeException("Node slit mismathch, polygon size mismatch"); +// +// } +// for (int i = 0; i < cb1.size(); i++) { +// Polygon p1 = cb1.get(i); +// Polygon p2 = cb2.get(i); +// if(p1.size()!=p2.size()) +// throw new RuntimeException("Node slit mismathch, polygon size mismatch"); +// +// } +// for (int i = 0; i < f1.size(); i++) { +// Polygon p1 = f1.get(i); +// Polygon p2 = f2.get(i); +// if(p1.size()!=p2.size()) +// throw new RuntimeException("Node slit mismathch, polygon size mismatch"); +// +// } +// for (int i = 0; i < b1.size(); i++) { +// Polygon p1 = b1.get(i); +// Polygon p2 = b2.get(i); +// if(p1.size()!=p2.size()) +// throw new RuntimeException("Node slit mismathch, polygon size mismatch"); +// +// } +// f.addAll(f1); +// b.addAll(b1); +// cb.addAll(cb1); +// cf.addAll(cf1); + } + + /** + * An attempt to make part of the CSG stack GPu accelerated + * + * this is worth exploring in the future + * + * @param polygons + * @param coplanarFront + * @param coplanarBack + * @param front + * @param back + * @throws Exception + */ + @SuppressWarnings("deprecation") + public void splitPolygonGPU(ArrayList polygons, List coplanarFront, List coplanarBack, + List front, List back) throws Exception { + + // Main conversion of your original code + int numberOfPointsTmp = 0; + int polygonNumber = polygons.size(); + int max = 0; + + ArrayList orderedPoints = new ArrayList(); + int[] polygonStartIndex = new int[polygonNumber]; + int[] polygonSize = new int[polygonNumber]; + int[] newPointStartIndex = new int[polygonNumber]; + + // Convert to fixed point arrays + double[] normalPolygonX = new double[polygonNumber]; + double[] normalPolygonY = new double[polygonNumber]; + double[] normalPolygonZ = new double[polygonNumber]; + double[] normalPolygonDistance = new double[polygonNumber]; + + for (int k = 0; k < polygonNumber; k++) { + Polygon polygon = polygons.get(k); + List vertices = polygon.getVertices(); + int size = vertices.size(); + if (size > max) + max = size; + numberOfPointsTmp += size; + polygonStartIndex[k] = orderedPoints.size(); + polygonSize[k] = size; + + // Convert to fixed point + normalPolygonX[k] = (polygon.getPlane().getNormal().x); + normalPolygonY[k] = (polygon.getPlane().getNormal().y); + normalPolygonZ[k] = (polygon.getPlane().getNormal().z); + normalPolygonDistance[k] = (polygon.getPlane().getDist()); + orderedPoints.addAll(vertices); + } + int ExtraSpace = max * 2 + 2; + int[] coplanarFrontStartIndex = new int[polygonNumber]; + int[] coplanarFrontSize = new int[polygonNumber]; + int[] coplanarBackStartIndex = new int[polygonNumber]; + int[] coplanarBackSize = new int[polygonNumber]; + int[] frontStartIndex = new int[polygonNumber]; + int[] frontspace = new int[polygonNumber]; + int[] frontSize = new int[polygonNumber]; + int[] backStartIndex = new int[polygonNumber]; + int[] backspace = new int[polygonNumber]; + int[] backSize = new int[polygonNumber]; + + int pointsNumber = numberOfPointsTmp + 1 + ((ExtraSpace) * (polygonNumber + 1) * 2); + boolean[] memoryError = new boolean[polygonNumber]; + boolean[] isCopy = new boolean[polygonNumber]; + + for (int k = 0; k < polygonNumber; k++) { + newPointStartIndex[k] = numberOfPointsTmp + (k * ExtraSpace * 2); + coplanarFrontStartIndex[k] = -1; + coplanarBackStartIndex[k] = -1; + frontStartIndex[k] = -1; + backStartIndex[k] = -1; + memoryError[k] = false; + isCopy[k] = false; + frontspace[k] = ExtraSpace; + backspace[k] = ExtraSpace; + } + + int pointsEmptyIndex = 0; + int[] types = new int[max * (polygonNumber + 1)]; + int maxPolygonSize = max; + + // Convert point arrays to fixed point + double[] polygonPointX = new double[pointsNumber]; + double[] polygonPointY = new double[pointsNumber]; + double[] polygonPointZ = new double[pointsNumber]; + + for (; pointsEmptyIndex < orderedPoints.size(); pointsEmptyIndex++) { + Vertex vertex = orderedPoints.get(pointsEmptyIndex); + polygonPointX[pointsEmptyIndex] = (vertex.getX()); + polygonPointY[pointsEmptyIndex] = (vertex.getY()); + polygonPointZ[pointsEmptyIndex] = (vertex.getZ()); + } + for (int i = orderedPoints.size(); i < polygonPointX.length; i++) { + polygonPointX[i] = -1; + polygonPointY[i] = -1; + polygonPointZ[i] = -1; + } + // Convert plane normal to fixed point + double planeNormalX = (this.getThisNodePlane().getNormal().x); + double planeNormalY = (this.getThisNodePlane().getNormal().y); + double planeNormalZ = (this.getThisNodePlane().getNormal().z); + double planeNormalDistance = (this.getThisNodePlane().getDist()); + + double epsilon = Plane.getEPSILON(); + + int chunkSize =5000; + int loops = polygonNumber / chunkSize; + if (loops < 0) + loops = 1; + + //System.out.println("\n\nStarting Kernel "+polygonNumber+" polygons in "+loops+" loops "); + Kernel splitPolygonsKernel = new Kernel() { + + int size(int polygonIndex, int[] mypolygonSize) { + return mypolygonSize[polygonIndex]; + } + + int addPolygon(int polygonIndex, int size, int[] mypolygonStartIndex, int[] mypolygonSize, int[] space) { + int w = polygonIndex; + mypolygonStartIndex[w] = newPointStartIndex[w]; + newPointStartIndex[w] += size; + mypolygonSize[w] = 0; + space[w] -= size; + double fx = polygonPointX[mypolygonStartIndex[w]]; + double fy = polygonPointY[mypolygonStartIndex[w]]; + double fz = polygonPointZ[mypolygonStartIndex[w]]; + if (Math.abs(fx + 1) > epsilon || Math.abs(fy + 1) > epsilon || Math.abs(fz + 1) > epsilon) { + memoryError[polygonIndex] = true; + return -1; + } + return space[w]; + } + + void copy(int polygonIndex, int[] mypolygonStartIndex, int[] mypolygonSize) { + mypolygonStartIndex[polygonIndex] = polygonStartIndex[polygonIndex]; + mypolygonSize[polygonIndex] = polygonSize[polygonIndex]; + } + + void clear(int polygonIndex, int[] mypolygonSize) { + mypolygonSize[polygonIndex] = 0; + } + + void incrementSize(int polygonIndex, int[] mypolygonStartIndex, int[] mypolygonSize) { + mypolygonSize[polygonIndex]++; + int ni = mypolygonSize[polygonIndex]; + if (polygonIndex == polygonNumber - 1) { + if (ni >= pointsNumber) { + memoryError[polygonIndex] = true; + } + } else { + if (ni + mypolygonStartIndex[polygonIndex] == mypolygonStartIndex[polygonIndex + 1]) { + memoryError[polygonIndex] = true; + } + } + } + + int writeIncrementPoint(int polygonIndex, int source, int[] mypolygonStartIndex, int[] mypolygonSize) { + int pointInPolygon = mypolygonSize[polygonIndex];// get the end of the current list + incrementSize(polygonIndex, mypolygonStartIndex, mypolygonSize); + double x = polygonPointX[source]; + double y = polygonPointY[source]; + double z = polygonPointZ[source]; + return writePoint(polygonIndex, pointInPolygon, x, y, z, mypolygonStartIndex); + } + + int interpolate(int polygonIndex, int vi, int vj, int[] mypolygonStartIndex, int[] mypolygonSize) { + + int globalVi = polygonStartIndex[polygonIndex] + vi; + int globalVj = polygonStartIndex[polygonIndex] + vj; + double planeDot = dotProductFixed(planeNormalX, planeNormalY, planeNormalZ, polygonPointX[globalVi], + polygonPointY[globalVi], polygonPointZ[globalVi]); + + double d = (planeNormalDistance - planeDot); + // Get fixed point coordinates + double xvi = polygonPointX[globalVi]; + double yvi = polygonPointY[globalVi]; + double zvi = polygonPointZ[globalVi]; + double diff_x = (polygonPointX[globalVj] - xvi); + double diff_y = (polygonPointY[globalVj] - yvi); + double diff_z = (polygonPointZ[globalVj] - zvi); + + double dotMinus = dotProductFixed(planeNormalX, planeNormalY, planeNormalZ, diff_x, diff_y, diff_z); + // Paralell case where one point is slightly infront by the same amount that the + // other is slightly behind. when summed, they make a point that is exactly on + // the plane + // therefor the intersection point is halfway between i and j + double t = 0.5; + if (dotMinus != 0) + t = (d / dotMinus); + else + return -1; + if(t<0||t>1) + return -1; + + // Fixed point interpolation: lerp = vi + (vj - vi) * t + double sx = diff_x * t; + double sy = diff_y * t; + double sz = diff_z * t; + + double lerp_x = (xvi + sx); + double lerp_y = (yvi + sy); + double lerp_z = (zvi + sz); + +// new Vertex(new Vector3d(lerp_x , lerp_y , lerp_z ), polygons.get(polygonIndex).plane.getNormal()); + + int pointInPolygon = mypolygonSize[polygonIndex]; + incrementSize(polygonIndex, mypolygonStartIndex, mypolygonSize); + + int ret = writePoint(polygonIndex, pointInPolygon, lerp_x, lerp_y, lerp_z, mypolygonStartIndex); + + return ret; + } + + double dotProductFixed(double ax, double ay, double az, double bx, double by, double bz) { + return (az * bz) + (ay * by) + (ax * bx); + } + + int writePoint(int polygonIndex, int pointInPolygon, double x, double y, double z, + int[] mypolygonStartIndex) { + int pointIndex = mypolygonStartIndex[polygonIndex] + pointInPolygon; + double fx = polygonPointX[pointIndex]; + double fy = polygonPointY[pointIndex]; + double fz = polygonPointZ[pointIndex]; + if (Math.abs(fx + 1) > epsilon || Math.abs(fy + 1) > epsilon || Math.abs(fz + 1) > epsilon) { + memoryError[polygonIndex] = true; + return -1; + } + polygonPointX[pointIndex] = x; + polygonPointY[pointIndex] = y; + polygonPointZ[pointIndex] = z; + return pointIndex; + } + + double polygonPointDistance(int polygonIndex, int pointIndex) { + int globalIndex = polygonStartIndex[polygonIndex] + pointIndex; + double dotResult = dotProductFixed(normalPolygonX[polygonIndex], normalPolygonY[polygonIndex], + normalPolygonZ[polygonIndex], polygonPointX[globalIndex], polygonPointY[globalIndex], + polygonPointZ[globalIndex]); + + double result = (dotResult - normalPolygonDistance[polygonIndex]); + return (result); + } + + double planePointDistance(int polygonIndex, int pointIndex) { + int globalIndex = polygonStartIndex[polygonIndex] + pointIndex; + double dotResult = dotProductFixed(planeNormalX, planeNormalY, planeNormalZ, polygonPointX[globalIndex], + polygonPointY[globalIndex], polygonPointZ[globalIndex]); + + double result = (dotResult - planeNormalDistance); + return (result); + } + + double planeDotPolygonNormal(int polygonIndex) { + double result = dotProductFixed(planeNormalX, planeNormalY, planeNormalZ, normalPolygonX[polygonIndex], + normalPolygonY[polygonIndex], normalPolygonZ[polygonIndex]); + return (result); + } + void runOnePolygon(int polygonIndex) { + // search for the epsilon values of the incoming plane + double negEpsilon = -epsilon; + double posEpsilon = epsilon; +// for (int i = 0; i < size(polygonIndex, polygonSize); i++) { +// double t = polygonPointDistance(polygonIndex, i); +// if (t > posEpsilon) { +// posEpsilon = (float) (t + epsilon); +// } +// if (t < negEpsilon) { +// negEpsilon = (float) (t - epsilon); +// } +// } + int polygonType = COPLANAR;; +// boolean someF =false; +// boolean someB=false; + for (int i = 0; i < size(polygonIndex, polygonSize); i++) { + double t = planePointDistance(polygonIndex, i); + int type = (t < negEpsilon) ? BACK : (t > posEpsilon) ? FRONT : COPLANAR; + types[i + polygonIndex * maxPolygonSize] = type; + polygonType |= type; +// if(type==BACK) +// someB=true; +// if(type==FRONT) +// someF=true; + } +// polygonType=COPLANAR; +// if(someF && (!someB) ) { +// polygonType=FRONT; +// } +// if((!someF) && (someB) ) { +// polygonType=BACK; +// } +// if((someF) && (someB) ) { +// polygonType=SPANNING; +// } + +// List cf2= new ArrayList(); +// List cb2= new ArrayList(); +// List f2= new ArrayList(); +// List b2= new ArrayList(); +// splitSinglePolygon(polygons.get(polygonIndex), cf2, cb2, f2, b2); + if (polygonType == COPLANAR) { + isCopy[polygonIndex] = true; + if (planeDotPolygonNormal(polygonIndex) > 0) { +// if(cf2.size()!=1) { +// memoryError[polygonIndex] = true; +// return; +// } + copy(polygonIndex, coplanarFrontStartIndex, coplanarFrontSize); + } else { +// if(cb2.size()!=1) { +// memoryError[polygonIndex] = true; +// return; +// } + copy(polygonIndex, coplanarBackStartIndex, coplanarBackSize); + } + } else if (polygonType == FRONT) { +// if(f2.size()!=1) { +// memoryError[polygonIndex] = true; +// return; +// } + isCopy[polygonIndex] = true; + copy(polygonIndex, frontStartIndex, frontSize); + } else if (polygonType == BACK) { +// if(b2.size()!=1) { +// memoryError[polygonIndex] = true; +// return; +// } + isCopy[polygonIndex] = true; + copy(polygonIndex, backStartIndex, backSize); + } else if (polygonType == SPANNING) { +// if(b2.size()!=1 && f2.size()!=1) { +// memoryError[polygonIndex] = true; +// return; +// } + isCopy[polygonIndex] = false; + + int size = size(polygonIndex, polygonSize); + int polygonMax = size * 2; + int retF = addPolygon(polygonIndex, polygonMax, frontStartIndex, frontSize, frontspace); + if (retF < 0) { + memoryError[polygonIndex] = true; + return; + } + int retB = addPolygon(polygonIndex, polygonMax, backStartIndex, backSize, backspace); + if (retB < 0) { + memoryError[polygonIndex] = true; + return; + } + clear(polygonIndex, frontSize); + clear(polygonIndex, backSize); + + for (int i = 0; i < size; i++) { + int j = (i + 1) % size; + int ti = types[i + polygonIndex * maxPolygonSize]; + int tj = types[j + polygonIndex * maxPolygonSize]; + int sourctPointIndex = polygonStartIndex[polygonIndex] + i; + if (ti != BACK) { + writeIncrementPoint(polygonIndex, sourctPointIndex, frontStartIndex, frontSize); + } + if (ti != FRONT) { + writeIncrementPoint(polygonIndex, sourctPointIndex, backStartIndex, backSize); + } + if ((ti|tj)==SPANNING) { + int newPointIndex = interpolate(polygonIndex, i, j, frontStartIndex, frontSize); + if (newPointIndex > 0) { + writeIncrementPoint(polygonIndex, newPointIndex, backStartIndex, backSize); + } + } + if (memoryError[polygonIndex]) + return; + int fsize = frontSize[polygonIndex]; + int bsize = backSize[polygonIndex]; + if (fsize > (polygonMax) || bsize > (polygonMax)) { + memoryError[polygonIndex] = true; + return; + } + } +// ArrayList testF=new ArrayList(); +// ArrayList testB=new ArrayList(); +// +// int size23 = frontSize[polygonIndex]; +// int polygonBase = frontStartIndex[polygonIndex]; +// testAddPolygon(testF, orderedPoints, polygonPointX, polygonPointY, polygonPointZ, polygons.get(polygonIndex), polygonBase, size23, +// false); +// int size22 = backSize[polygonIndex]; +// int polygonBase2 = backStartIndex[polygonIndex]; +// +// testAddPolygon(testB, orderedPoints, polygonPointX, polygonPointY, polygonPointZ, polygons.get(polygonIndex), polygonBase2, size22, +// false); +// if(testF.size()!=f2.size()||testB.size()!=b2.size()) { +// memoryError[polygonIndex] = true; +// return; +// } +// if(testB.size()>0) +// if(testB.get(0).size()!=b2.get(0).size()) { +// memoryError[polygonIndex] = true; +// return; +// } +// if(testF.size()>0) +// if(testF.get(0).size()!=f2.get(0).size()) { +// memoryError[polygonIndex] = true; +// return; +// } + } else { + memoryError[polygonIndex] = true; + return; + } + int polygonBase = frontStartIndex[polygonIndex]; + int polygonBase22 = backStartIndex[polygonIndex]; + if (coplanarBackStartIndex[polygonIndex] < 0 && coplanarFrontStartIndex[polygonIndex] < 0 + && polygonBase < 0 && polygonBase22 < 0) { + memoryError[polygonIndex] = true; + return; + } + + } + @Override + public void run() { + int pi = getGlobalId() * chunkSize; + int end = pi + chunkSize; + if(end>polygonNumber) + end=polygonNumber; + //System.out.println("#"+getGlobalId()+" Start "+pi+" to "+end); + for (int polygonIndex = pi; (polygonIndex < end); polygonIndex++) { + if (memoryError[polygonIndex]) + return; + runOnePolygon(polygonIndex); + } // outer for loop of all polygons + }// run + }; + if(!GPUTest) { + try { + splitPolygonsKernel.compile(splitPolygonsKernel.getTargetDevice()); + }catch(Exception ex) { + System.err.println("GPU missing feature "+ex.getMessage()); + GPUTest=true; + } + } + if(GPUTest) + splitPolygonsKernel.setExecutionMode(Kernel.EXECUTION_MODE.JTP); // Java Thread Pool + + CSG.gpuRun(loops+3, splitPolygonsKernel, null, "split ", () -> { + return false; + }, 1, 1); + + for (int k = 0; k < polygonNumber; k++) + if (memoryError[k]) + throw new RuntimeException("Memory error here!"); + + // Collect the polygon data into the return structures + //int copies = 0; + for (int k = 0; k < polygonNumber; k++) { + copyDataIntoPolygon(polygons, coplanarFront, coplanarBack, front, back, orderedPoints, + coplanarFrontStartIndex, coplanarFrontSize, coplanarBackStartIndex, coplanarBackSize, + frontStartIndex, frontSize, backStartIndex, backSize, isCopy, polygonPointX, polygonPointY, + polygonPointZ, k); + + } + + } + + private void copyDataIntoPolygon(ArrayList polygons, List coplanarFront, + List coplanarBack, List front, List back, ArrayList orderedPoints, + int[] coplanarFrontStartIndex, int[] coplanarFrontSize, int[] coplanarBackStartIndex, + int[] coplanarBackSize, int[] frontStartIndex, int[] frontSize, int[] backStartIndex, int[] backSize, + boolean[] isCopy, double[] polygonPointX, double[] polygonPointY, double[] polygonPointZ, int k) { + int copies; + Polygon polygon = polygons.get(k); + copies = 0; + + copies += add(coplanarFront, k, coplanarFrontStartIndex, coplanarFrontSize, orderedPoints, polygonPointX, + polygonPointY, polygonPointZ, polygon, isCopy); + copies += add(coplanarBack, k, coplanarBackStartIndex, coplanarBackSize, orderedPoints, polygonPointX, + polygonPointY, polygonPointZ, polygon, isCopy); + copies += add(front, k, frontStartIndex, frontSize, orderedPoints, polygonPointX, polygonPointY, polygonPointZ, + polygon, isCopy); + copies += add(back, k, backStartIndex, backSize, orderedPoints, polygonPointX, polygonPointY, polygonPointZ, + polygon, isCopy); + if (copies != 1 && copies != 2) { + throw new RuntimeException("Failed to load all polygons??"); + } + } + /** * 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}, @@ -208,121 +901,171 @@ private ArrayList clipPolygons(ArrayList polygons) { * @param front front polygons * @param back back polgons */ - public void splitPolygon(ArrayList polygons, List coplanarFront, List coplanarBack, + public void splitPolygonOriginal(List polygons, List coplanarFront, List coplanarBack, List front, List back) { + for (int k = 0; k < polygons.size(); k++) { Polygon polygon = polygons.get(k); + splitSinglePolygon(polygon,coplanarFront, coplanarBack, front, back); + } + if(Debug3dProvider.isProviderAvailible()) { +// Debug3dProvider.clearScreen(); +// Debug3dProvider.addObject(polygons.get(0).getVertices().get(0)); +// Debug3dProvider.addObject(front.stream().map(polygon -> polygon.setColor(Color.RED)).collect(Collectors.toList())); +// List collect = back.stream().map(polygon -> polygon.setColor(Color.WHITE)).collect(Collectors.toList()); +// Debug3dProvider.addObject(collect); +// Debug3dProvider.addObject(coplanarBack.stream().map(polygon -> polygon.setColor(Color.YELLOW)).collect(Collectors.toList())); +// Debug3dProvider.addObject(coplanarFront.stream().map(polygon -> polygon.setColor(Color.GREEN)).collect(Collectors.toList())); +// Debug3dProvider.clearScreen(); + } + } - - // search for the epsilon values of the incoming plane - double negEpsilon = -Plane.getEPSILON(); - double posEpsilon = Plane.getEPSILON(); - for (int i = 0; i < polygon.getVertices().size(); i++) { - double t = polygon.getPlane().getNormal().dot(polygon.getVertices().get(i).pos) + private void splitSinglePolygon(Polygon polygon,List coplanarFront, List coplanarBack, List front, + List back) { + // search for the epsilon values of the incoming plane + double negEpsilon = -Plane.getEPSILON(); + double posEpsilon = Plane.getEPSILON(); + int size = polygon.getVertices().size(); + Vector3d normal = polygon.getPlane().getNormal(); + for (int i = 0; i < size; i++) { + Vector3d pos = polygon.getVertices().get(i).pos; + double dot = normal.dot(pos); + double t = dot - polygon.getPlane().getDist(); + if(Math.abs(t)>0.01) { + throw new RuntimeException("A plane epsilon of "+t+" is impossible"); + } if (t > posEpsilon) { // com.neuronrobotics.sdk.common.Log.error("Non flat polygon, increasing // positive epsilon "+t); - posEpsilon = t + Plane.getEPSILON(); + posEpsilon = t; } if (t < negEpsilon) { // com.neuronrobotics.sdk.common.Log.error("Non flat polygon, decreasing // negative epsilon "+t); - negEpsilon = t - Plane.getEPSILON(); + negEpsilon = t; } } - int polygonType = 0; - List types = new ArrayList<>(); - boolean somePointsInfront = false; - boolean somePointsInBack = false; - for (int i = 0; i < polygon.getVertices().size(); i++) { - double t = this.getPlane().getNormal().dot(polygon.getVertices().get(i).pos) - - this.getPlane().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.getPlane().getNormal().dot(polygon.getPlane().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.getVertices().size(); i++) { - int j = (i + 1) % polygon.getVertices().size(); - int ti = types.get(i); - int tj = types.get(j); - Vertex vi = polygon.getVertices().get(i); - Vertex vj = polygon.getVertices().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.getPlane().getDist() - this.getPlane().getNormal().dot(vi.pos)) - / this.getPlane().getNormal().dot(vj.pos.minus(vi.pos)); - Vertex v = vi.interpolate(vj, t); - f.add(v); - b.add(v.clone()); - } + int polygonType = 0; + List types = new ArrayList<>(); +// boolean someF =false; +// boolean someB=false; + + //double distP = polygon.getPlane().getDist(); + for (int i = 0; i < size; i++) { + Vector3d pos = polygon.getVertices().get(i).pos; +// double dot = normal.dot(pos); +// double ep = Math.abs( dot-distP);// this is this points distance from its plane + double t = getThisNodePlane().getNormal().dot(pos) - getThisNodePlane().getDist(); + int type = (t < negEpsilon) ? BACK : (t > posEpsilon) ? FRONT : COPLANAR; + types.add(type); + polygonType = polygonType|type; + +// if(type==BACK) +// someB=true; +// if(type==FRONT) +// someF=true; + } +// polygonType=COPLANAR; +// if(someF && (!someB) ) { +// polygonType=FRONT; +// } +// if((!someF) && (someB) ) { +// polygonType=BACK; +// } +// if((someF) && (someB) ) { +// polygonType=SPANNING; +// } + // Put the polygon in the correct list, splitting it when necessary. + switch (polygonType) { + case COPLANAR: + double cp = getThisNodePlane().getNormal().dot(normal); + (cp > 0 ? coplanarFront : coplanarBack).add(polygon); + break; + case FRONT: + front.add(polygon); + break; + case BACK: + back.add(polygon); + break; + case SPANNING: + List f = new ArrayList<>(size); + List b = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + int j = (i + 1) % size; + int ti = types.get(i); + int tj = types.get(j); + Vertex vi = polygon.getVertices().get(i); + Vertex vj = polygon.getVertices().get(j); + if (ti != BACK) { + addPoint(f, vi); + // f.add(vi); } - if (f.size() >= 3) { - try { - Polygon fpoly = new Polygon(f, polygon.getStorage(), false, polygon.getPlane()) - .setColor(polygon.getColor()); - add(front, fpoly); - } catch (Exception ex) { - System.err.println("Pruning bad polygon Plane::splitPolygon"); - // skip adding broken polygon here - } - } else { - // com.neuronrobotics.sdk.common.Log.error("Front Clip Fault!"); + if (ti != FRONT) { + addPoint(b, (ti != BACK ? vi.clone() : vi)); } - if (b.size() >= 3) { - try { - Polygon bpoly = new Polygon(b, polygon.getStorage(), false, polygon.getPlane()) - .setColor(polygon.getColor()); - add(back, bpoly); - } catch (Exception ex) { - // ex.printStackTrace(); - System.err.println("Pruning bad polygon Plane::splitPolygon"); + if ((ti|tj) == SPANNING) { + double planeDot = this.getThisNodePlane().getNormal().dot(vi.pos); + double planeNormalDistance = this.getThisNodePlane().getDist(); + + double d = planeNormalDistance - planeDot; + + // Extract the vector components + double xvi = vi.pos.x; + double yvi = vi.pos.y; + double zvi = vi.pos.z; + + double xvj = vj.pos.x; + double yvj = vj.pos.y; + double zvj = vj.pos.z; + + // Compute the difference vector (vj - vi) + double diff_x = xvj - xvi; + double diff_y = yvj - yvi; + double diff_z = zvj - zvi; + + // Assuming plane.getNormal() returns a Vector3d or similar with x, y, z fields + double planeNormalX = getThisNodePlane().getNormal().x; + double planeNormalY = getThisNodePlane().getNormal().y; + double planeNormalZ = getThisNodePlane().getNormal().z; + + // Compute dot product + double dotMinus = (planeNormalX * diff_x) + (planeNormalY * diff_y) + (planeNormalZ * diff_z); + + // Compute scalar t + // Paralell case where one point is slightly infront by the same amount that the + // other is slightly behind. when summed, they make a point that is exactly on + // the plane + // therefor the intersection point is halfway between i and j + double t = (d / dotMinus); + if (!Double.isFinite(t) || t < 0 || t > 1.0) { + continue; } - } else { - // com.neuronrobotics.sdk.common.Log.error("Back Clip Fault!"); + + // Scale difference vector by tOld + double sx = diff_x * t; + double sy = diff_y * t; + double sz = diff_z * t; + + // Compute interpolated point intrp = vi + scaled vector + double lerp_x = xvi + sx; + double lerp_y = yvi + sy; + double lerp_z = zvi + sz; + Vector3d intrp = new Vector3d(lerp_x, lerp_y, lerp_z); +// double distPoly = polygon.getPlane().getDist(); +// double dotNP = normal.dot(intrp); +// double tnp = dotNP- distPoly; +// if(Math.abs(tnp)>Plane.getEPSILON()) { +// throw new RuntimeException("New point doesnt lie on the plane of the split polygon!"); +// }else { + addPoint(f, new Vertex(intrp)); + addPoint(b, new Vertex(intrp.clone())); + //} } - break; } - } - } - - private static void add(List l, Polygon p) { - try { - // test triangulation of new polygon before adding - PolygonUtil.concaveToConvex(p); - l.add(p); - } catch (Exception ex) { - ex.printStackTrace(); + add(front, f, polygon); + add(back, b, polygon); + break; } } @@ -332,11 +1075,12 @@ private static void add(List l, Polygon p) { * Removes all polygons in this BSP tree that are inside the specified BSP tree * ({@code bsp}). * - * Note: polygons are splitted if necessary. + * Note: polygons are split if necessary. * * @param bsp bsp that shall be used for clipping + * @throws Exception */ - public void clipTo(Node bsp) { + public void clipTo(Node bsp) throws Exception { this.polygons = bsp.clipPolygons(this.polygons); if (this.front != null) { this.front.clipTo(bsp); @@ -372,9 +1116,11 @@ public ArrayList allPolygons() { * heuristic is used to pick a good split). * * @param polygons polygons used to build the BSP + * @throws Exception */ - public final void build(ArrayList polygons) { - build(polygons, 0, polygons.size()); + public final void build(ArrayList polygons) throws Exception { + int size = count; + build(polygons, 0, polygons.size()*size); } /** @@ -384,56 +1130,57 @@ public final void build(ArrayList polygons) { * heuristic is used to pick a good split). * * @param polygons polygons used to build the BSP + * @throws Exception */ - public final void build(ArrayList polygons, long depth, long maxDepth) { -// if (depth > maxDepth) { -// throw new RuntimeException("Impossible Node depth " + depth + " with " + polygons.size() + " remaining max = "+maxDepth ); -// } -// if (depth > 200) { -// com.neuronrobotics.sdk.common.Log.error("Node depth " + depth + " with " + polygons.size() + " remaining "); -// Plane.setUseDebugger(true); -// } else { -// Plane.setUseDebugger(false); -// } + public final int build(ArrayList polygons, long depth, long maxDepth) throws Exception { + if (depth > maxDepth) { + new RuntimeException("Impossible Node depth " + depth + " with " + polygons.size() + " remaining max = "+maxDepth ).printStackTrace(); + } + if (polygons.isEmpty()) { - return; + return 0; } - if (this.getPlane() == null) { - this.setPlane(polygons.get(0).getPlane().clone()); - } +// if (this.getPlane() == null) { +// this.setPlane(polygons.get(0).getPlane()); +// } // this.polygons.add(polygons.get(0)); ArrayList frontP = new ArrayList<>(); ArrayList backP = new ArrayList<>(); // parellel version does not work here - - splitPolygon(polygons, this.polygons, this.polygons, frontP, backP); + List coplanarFront=this.polygons; + List coplanarBack=this.polygons; + splitPolygon(polygons, coplanarFront, coplanarBack, frontP, backP); +// if(this.polygons.size()==0) { +// throw new RuntimeException("Binary Spacial Partitioning Tree step failed!"); +// } if (frontP.size() > 0) { if (this.front == null) { - this.front = new Node(); + this.front = new Node(frontP.get(0).getPlane()); } - this.front.build(frontP, depth + 1, maxDepth); + count+=this.front.build(frontP, depth + 1, maxDepth); } if (backP.size() > 0) { if (this.back == null) { - this.back = new Node(); + this.back = new Node(backP.get(0).getPlane()); } - this.back.build(backP, depth + 1, maxDepth); + count+=this.back.build(backP, depth + 1, maxDepth); } + return count; } - public Plane getPlane() { - return plane; + public Plane getThisNodePlane() { + return myNodePlane; } - public void setPlane(Plane plane) { - if (plane == null) - throw new RuntimeException("Plane can not be null!"); - this.plane = plane; - } +// public void setPlane(Plane plane) { +// if (plane == null) +// throw new RuntimeException("Plane can not be null!"); +// this.myNodePlane = plane.clone(); +// } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Parabola.java b/src/main/java/eu/mihosoft/vrl/v3d/Parabola.java index f3837ebc..dbb997a4 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Parabola.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Parabola.java @@ -111,7 +111,7 @@ public static CSG coneByFocalLength(double Radius, double FocalLength) { return eu.mihosoft.vrl.v3d.ext.quickhull3d.HullUtil.hull(pointsOut); } - public static CSG extrudeByEquation(double Radius, double a, double b, double thickness) { + public static CSG extrudeByEquation(double Radius, double a, double b, double thickness) throws ColinearPointsException { return Extrude.points(new Vector3d(0, 0, thickness), // This is the extrusion depth new Parabola().fromEquation(Radius, a, b).getpoints()// upper right corner ); diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Plane.java b/src/main/java/eu/mihosoft/vrl/v3d/Plane.java index c9492377..67d46c23 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Plane.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Plane.java @@ -56,9 +56,9 @@ public class Plane implements Serializable { * 0.00000001; */ - public static double EPSILON = 1.0e-7; + private static double EPSILON = 1.0e-8; public static double EPSILON_Point = getEPSILON(); - //public static double EPSILON_duplicate = 1.0e-4; + // public static double EPSILON_duplicate = 1.0e-4; /** * XY plane. */ @@ -80,6 +80,7 @@ public class Plane implements Serializable { * Distance to origin. */ private double dist; + private double lengthSquared; /** * Constructor. Creates a new plane defined by its normal vector and the @@ -92,6 +93,21 @@ public Plane(Vector3d normal, double dist) { this.setNormal(normal.normalized()); this.setDist(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 + * @throws ColinearPointsException + */ + public Plane(List vertices, Vector3d testNorm) throws ColinearPointsException { + Vector3d a = vertices.get(0).pos; + Vector3d n = computeNormal(vertices, testNorm); + this.setNormal(n); + this.setDist(n.dot(a)); + } + /** * Constructor. Creates a new plane defined by its normal vector and the * distance to the origin. @@ -103,161 +119,199 @@ public Plane(Vector3d normal, List vertices) { this.setNormal(normal.normalized()); this.setDist(normal.dot(vertices.get(0).pos)); } + /** + * 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, Vector3d vertice) { + this.setNormal(normal.normalized()); + this.setDist(normal.dot(vertice)); + } + /** + * Creates a plane defined by the the specified points. + * + * @param vector3d + * + * @param a first point + * @param b second point + * @param c third point + * @return a plane + */ + public static Plane createFromPoints(List vertices) throws ColinearPointsException { + return createFromPoints(vertices, null); + } /** * Creates a plane defined by the the specified points. + * + * @param vector3d * - * @param a first point - * @param b second point - * @param c third point + * @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 Plane createFromPoints(List vertices, Vector3d testNorm) throws ColinearPointsException { + return new Plane(vertices, testNorm); } - public static Vector3d computeNormal(List vertices) { + public Vector3d computeNormal(List vertices) throws ColinearPointsException{ + return computeNormal(vertices, null); + } + + public Vector3d computeNormal(List vertices, Vector3d testNorm) throws ColinearPointsException { + if (vertices == null || vertices.size() < 3) { - return new Vector3d(0, 0, 1); // Default normal for degenerate cases + throw new ColinearPointsException("Can not find normal without at least 3 points "+vertices); } - // First attempt: Newell's method Vector3d normal = new Vector3d(0, 0, 0); int n = vertices.size(); - Vector3d lastValid = null; for (int i = 0; i < n; i++) { - Vector3d current = vertices.get(i).pos; - Vector3d next = vertices.get((i + 1) % n).pos; - - // Correct Newell's Method formulas - normal.x += (current.y - next.y) * (current.z + next.z); // (y1-y2)(z1+z2) - normal.y += (current.z - next.z) * (current.x + next.x); // (z1-z2)(x1+x2) - normal.z += (current.x - next.x) * (current.y + next.y); - if (n >= 3) { - Vector3d normalized = normal.normalized(); - if (isValidNormal(normalized, getEPSILON() / 10)) { - lastValid = normalized; - } - } + Vector3d current = vertices.get(i).pos; + Vector3d next = vertices.get((i + 1) % n).pos; + + double d = (current.y - next.y) * (current.z + next.z); + double e = (current.z - next.z) * (current.x + next.x); + double e2 = (current.x - next.x) * (current.y + next.y); + normal.x += d; + normal.y += e; + normal.z += e2; } - if (isValidNormal(lastValid, getEPSILON() / 10)) { - return lastValid; + if (isValidNormal(normal)) { + return normal.normalized(); } - throw new RuntimeException("Mesh has problems, can not work around it"); -// // Second attempt: Find three non-colinear points -// -// normal = findNormalFromNonColinearPoints(vertices); -// if (normal != null) { -// System.err.println("findNormalFromNonColinearPoints "); -// return normal; -// } -// // Third attempt: Find principal direction -// normal = findPrincipalDirection(vertices); -// if (normal != null) { -// System.err.println("findPrincipalDirection "); -// -// return normal; -// } -// System.err.println("determineStatisticalNormal "); -// // Final fallback: Use statistical approach -// return determineStatisticalNormal(vertices); + throw new ColinearPointsException("Failed to compute the normal! "+vertices); } - - private static boolean isValidNormal(Vector3d normal, double epsilon) { - if (Double.isFinite(normal.x) && Double.isFinite(normal.y) && Double.isFinite(normal.z)) { - double lengthSquared = Math.abs(normal.length()); - return lengthSquared >= epsilon; - } - return false; + private boolean isValidNormal(Vector3d normal) { + if (Double.isFinite(normal.x) && Double.isFinite(normal.y) && Double.isFinite(normal.z)) { + setLengthSquared(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z); + return getLengthSquared() > 0; // Compare squared values + } + return false; } - private static Vector3d findNormalFromNonColinearPoints(List vertices) { - int n = vertices.size(); - Vector3d firstPoint = vertices.get(0).pos; - - // Try to find two vectors that aren't parallel - for (int i = 1; i < n; i++) { - Vector3d v1 = vertices.get(i).pos.minus(firstPoint); - for (int j = i + 1; j < n; j++) { - Vector3d v2 = vertices.get(j).pos.minus(firstPoint); - Vector3d cross = v1.cross(v2); - if (isValidNormal(cross, getEPSILON())) { - return cross.normalized(); + public boolean checkNormal(ArrayList vertex) { + Plane p = null; + try { + p = Plane.createFromPoints(vertex, getNormal()); + } catch (Exception ex) { + //ex.printStackTrace(); + } + if (p != null) { + Vector3d normal = p.getNormal(); + Vector3d normal2 = getNormal(); + // check for actual misallignment + double d = 0.0001; + if ((normal.x) - (normal2.x) > d + || (normal.y) - (normal2.y) > d + || (normal.z) - (normal2.z) > d) { + if (Math.abs(normal.x) - Math.abs(normal2.x) > d + || Math.abs(normal.y) - Math.abs(normal2.y) > d + || Math.abs(normal.z) - Math.abs(normal2.z) > d) { + return false; } + return false; } } - return null; + return true; } + + public static Vector3d computeNormalCrossProduct(List verts) { + int n = verts.size(); + if (n < 3) + return new Vector3d(0, 0, 1); - private static Vector3d findPrincipalDirection(List vertices) { - // Find the direction with maximum spread - double maxX = Double.NEGATIVE_INFINITY; - double minX = Double.POSITIVE_INFINITY; - double maxY = Double.NEGATIVE_INFINITY; - double minY = Double.POSITIVE_INFINITY; - double maxZ = Double.NEGATIVE_INFINITY; - double minZ = Double.POSITIVE_INFINITY; - - for (Vertex vertex : vertices) { - Vector3d pos = vertex.pos; - maxX = Math.max(maxX, pos.x); - minX = Math.min(minX, pos.x); - maxY = Math.max(maxY, pos.y); - minY = Math.min(minY, pos.y); - maxZ = Math.max(maxZ, pos.z); - minZ = Math.min(minZ, pos.z); - } - - double rangeX = maxX - minX; - double rangeY = maxY - minY; - double rangeZ = maxZ - minZ; + // 1. Build all edge vectors + List edges = new ArrayList<>(); - // Use the axis with minimum spread as normal direction - if (rangeX <= rangeY && rangeX <= rangeZ) { - return new Vector3d(1, 0, 0); - } else if (rangeY <= rangeX && rangeY <= rangeZ) { - return new Vector3d(0, 1, 0); - } else { - return new Vector3d(0, 0, 1); + for (int j = 0; j < n; j++) { + Vector3d e = verts.get((j+1)%n).pos.minus(verts.get(j).pos); + edges.add(e); + + } + + + // 2. Find pair with smallest |dot| / (|e1||e2|) + double bestScore = Double.POSITIVE_INFINITY; + Vector3d bestE1 = null, bestE2 = null; + for (int i = 0; i < edges.size(); i++) { + Vector3d e1 = edges.get(i); + double len1 = e1.length(); + for (int j = i + 1; j < edges.size(); j++) { + Vector3d e2 = edges.get(j); + double len2 = e2.length(); + double score = Math.abs(e1.dot(e2)) / (len1 * len2); + if (score < bestScore) { + bestScore = score; + bestE1 = e1; + bestE2 = e2; + } + } } - } - private static Vector3d determineStatisticalNormal(List vertices) { - // Calculate center of mass - Vector3d center = new Vector3d(0, 0, 0); - for (Vertex vertex : vertices) { - center = center.plus(vertex.pos); + // 3. Fallback: use first three vertices if no good pair found + if (bestE1 == null) { + Vector3d v0 = verts.get(0).pos; + bestE1 = verts.get(1).pos.minus(v0); + bestE2 = verts.get(2).pos.minus(v0); } - center = center.times(1.0 / vertices.size()); - - // Calculate covariance matrix - double[][] covariance = new double[3][3]; - for (Vertex vertex : vertices) { - Vector3d diff = vertex.pos.minus(center); - covariance[0][0] += diff.x * diff.x; - covariance[0][1] += diff.x * diff.y; - covariance[0][2] += diff.x * diff.z; - covariance[1][1] += diff.y * diff.y; - covariance[1][2] += diff.y * diff.z; - covariance[2][2] += diff.z * diff.z; + + // 4. Compute normal + Vector3d normal = bestE1.cross(bestE2); + if (normal.magnitude() < Plane.getEPSILON()) { + throw new RuntimeException("Fail! Normal can not be computed"); } - covariance[1][0] = covariance[0][1]; - covariance[2][0] = covariance[0][2]; - covariance[2][1] = covariance[1][2]; - - // Use the eigenvector corresponding to the smallest eigenvalue - // For simplicity, we'll use power iteration to find it - Vector3d normal = new Vector3d(1, 1, 1); - for (int i = 0; i < 10; i++) { - normal = multiplyMatrixVector(covariance, normal); - normal = normal.normalized(); + normal.normalize(); + Vector3d v0 = verts.get(0).pos; + Vector3d std = verts.get(1).pos.minus(v0).cross(verts.get(2).pos.minus(v0)).normalized(); + if (normal.dot(std) < 0) { + normal = normal.negated(); } - return normal; } + + +// public static Vector3d computeNormal(List vertices) { +// if (vertices == null || vertices.size() < 3) { +// return new Vector3d(0, 0, 1); // Default normal for degenerate cases +// } +// +// // First attempt: Newell's method +// Vector3d normal = new Vector3d(0, 0, 0); +// int n = vertices.size(); +// Vector3d lastValid = null; +// Vector3d corner = vertices.get(0).pos; +// for (int i = 0; i < n; i++) { +// Vector3d current = corner.minus( vertices.get(i).pos).times(PolygonUtil.triangleScale); +// Vector3d next = corner.minus( vertices.get((i + 1) % n).pos).times(PolygonUtil.triangleScale); +// +// // Correct Newell's Method formulas +// normal.x += (current.y - next.y) * (current.z + next.z); // (y1-y2)(z1+z2) +// normal.y += (current.z - next.z) * (current.x + next.x); // (z1-z2)(x1+x2) +// normal.z += (current.x - next.x) * (current.y + next.y); +// if (i >= 2) { +// if(Math.abs(normal.magnitude())>0) { +// Vector3d normalized = normal.normalized(); +// if (isValidNormal(normalized, 1-Plane.getEPSILON())) { +// lastValid = normalized; +// } +// } +// } +// } +// if(lastValid!=null) +// if (isValidNormal(lastValid, 1-Plane.getEPSILON())) { +// return lastValid.normalized(); +// } +// throw new RuntimeException("Mesh has problems, can not work around it "+lastValid); +// } + + + private static Vector3d multiplyMatrixVector(double[][] matrix, Vector3d vector) { return new Vector3d(matrix[0][0] * vector.x + matrix[0][1] * vector.y + matrix[0][2] * vector.z, matrix[1][0] * vector.x + matrix[1][1] * vector.y + matrix[1][2] * vector.z, @@ -326,7 +380,6 @@ public void flip() { setDist(-getDist()); } - public static IPolygonDebugger getDebugger() { return debugger; } @@ -348,9 +401,13 @@ public Vector3d getNormal() { } public void setNormal(Vector3d normal) { - if (Double.isFinite(normal.x) && Double.isFinite(normal.y) && Double.isFinite(normal.z)) - this.normal = normal; - else { + if (Double.isFinite(normal.x) && Double.isFinite(normal.y) && Double.isFinite(normal.z)) { + if (Vector3d.ZERO.equals(normal)) { + throw new NumberFormatException( + "Normal is zero!"); + } + this.normal = normal.normalized(); + }else { NumberFormatException numberFormatException = new NumberFormatException(); // numberFormatException.printStackTrace(); @@ -377,20 +434,28 @@ public static double getEPSILON() { return EPSILON; } - public static void setEPSILON(double ePSILON) { + public static void setEpsilon(double ePSILON) { EPSILON = ePSILON; } + @Override + public String toString() { + return "Normal"+normal+" distance "+dist; + } + + public static double getEPSILON_Point() { + return EPSILON_Point; + } - public void transformPlane(Transform transform_in, Vector3d a) { - Transform trans_rot = transform_in.copy()// .inverse() - .setToOrigin(); -// Transform trans_dist = new Transform().movex(transform_in.getX()) -// .movey(transform_in.getY()) -// .movez(transform_in.getZ()); - Vector3d newNormal = this.normal.transformed(trans_rot); - newNormal = newNormal.negated(); - this.setNormal(newNormal.normalized()); - this.setDist(this.normal.dot(a)); + public static void setEPSILON_Point(double ePSILON_Point) { + EPSILON_Point = ePSILON_Point; + } + public double getLengthSquared() { + return lengthSquared; } + + public void setLengthSquared(double lengthSquared) { + this.lengthSquared = lengthSquared; + } + } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java b/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java index f2c7d3f1..2174f5aa 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java @@ -50,120 +50,118 @@ * polygon. This can be used to define per-polygon properties (such as surface * color). */ -public final class Polygon implements Serializable{ +public final class Polygon implements Serializable { - private static final long serialVersionUID = 2090322224114633535L; + private static final long serialVersionUID = 2090322224114633535L; /** Polygon vertices. */ - private ArrayList vertices; - /** - * Shared property (can be used for shared color etc.). - */ - private PropertyStorage shared; - /** - * Plane defined by this polygon. - * - * Note: uses first three vertices to define the plane. - */ - public Plane plane=null; - private boolean isHole = false; - private double r= CSG.getDefaultColor().getRed(); - private double g=CSG.getDefaultColor().getGreen(); - private double b= CSG.getDefaultColor().getBlue(); - private double o=CSG.getDefaultColor().getOpacity(); - private boolean valid = true; - private boolean degenerate=false; - /** - * Sets the storage. - * - * @param storage the new storage - */ - void setStorage(PropertyStorage storage) { - this.shared = storage; - } - - /** - * Decomposes the specified concave polygon into convex polygons. - * - * @param points the points that define the polygon - * @return the decomposed concave polygon (list of convex polygons) - */ - public static List fromConcavePoints(Vector3d... points) { - Polygon p = fromPoints(points); - - return PolygonUtil.concaveToConvex(p); - } - - /** - * Decomposes the specified concave polygon into convex polygons. - * - * @param points the points that define the polygon - * @return the decomposed concave polygon (list of convex polygons) - */ - public static List fromConcavePoints(List points) { - Polygon p = fromPoints(points); - - return PolygonUtil.concaveToConvex(p); - } - - /** - * Constructor. Creates a new polygon that consists of the specified - * vertices. - * - * Note: the vertices used to initialize a polygon must be coplanar - * and form a convex loop. - * - * @param vertices polygon vertices - * @param shared shared property - */ - public Polygon(List vertices, PropertyStorage shared, boolean allowDegenerate, Plane p) { - this.vertices = pruneDuplicatePoints(vertices); - this.shared = shared; - if(p!=null) - setPlane(p.clone()); - - validateAndInit(allowDegenerate); - } - /** - * Constructor. Creates a new polygon that consists of the specified - * vertices. - * - * Note: the vertices used to initialize a polygon must be coplanar - * and form a convex loop. - * - * @param vertices polygon vertices - * @param shared shared property - */ - public Polygon(List vertices, PropertyStorage shared) { - this(vertices,shared,true,null); - } - /** - * Constructor. Creates a new polygon that consists of the specified - * vertices. - * - * Note: the vertices used to initialize a polygon must be coplanar - * and form a convex loop. - * - * @param vertices polygon vertices - */ - public Polygon(List vertices) { - this(vertices,new PropertyStorage(),true,null); - } - public static ArrayList pruneDuplicatePoints(List incoming) { - //return incoming; + private ArrayList vertices; + /** + * Shared property (can be used for shared color etc.). + */ + private PropertyStorage shared; + /** + * Plane defined by this polygon. + * + * Note: uses first three vertices to define the plane. + */ + public Plane plane = null; + private boolean isHole = false; + private double r = CSG.getDefaultColor().getRed(); + private double g = CSG.getDefaultColor().getGreen(); + private double b = CSG.getDefaultColor().getBlue(); + private double o = CSG.getDefaultColor().getOpacity(); + private boolean valid = true; + private boolean degenerate = false; + + /** + * Sets the storage. + * + * @param storage the new storage + */ + void setStorage(PropertyStorage storage) { + this.shared = storage; + } + + /** + * Decomposes the specified concave polygon into convex polygons. + * + * @param points the points that define the polygon + * @return the decomposed concave polygon (list of convex polygons) + */ + public static List fromConcavePoints(Vector3d... points) throws ColinearPointsException { + Polygon p = fromPoints(points); + + return PolygonUtil.triangulatePolygon(p); + } + + /** + * Decomposes the specified concave polygon into convex polygons. + * + * @param points the points that define the polygon + * @return the decomposed concave polygon (list of convex polygons) + */ + public static List fromConcavePoints(List points)throws ColinearPointsException { + Polygon p = fromPoints(points); + + return PolygonUtil.triangulatePolygon(p); + } + + /** + * Constructor. Creates a new polygon that consists of the specified vertices. + * + * Note: the vertices used to initialize a polygon must be coplanar and form a + * convex loop. + * + * @param vertices polygon vertices + * @param shared shared property + */ + public Polygon(List vertices, PropertyStorage shared, boolean allowDegenerate, Plane p) throws ColinearPointsException { + this.setVertices(pruneDuplicatePoints(vertices)); + this.shared = shared; + if (p != null) + setPlane(p.clone()); + + validateAndInit(allowDegenerate); + } + + /** + * Constructor. Creates a new polygon that consists of the specified vertices. + * + * Note: the vertices used to initialize a polygon must be coplanar and form a + * convex loop. + * + * @param vertices polygon vertices + * @param shared shared property + */ + public Polygon(List vertices, PropertyStorage shared) throws ColinearPointsException { + this(vertices, shared, true, null); + } + + /** + * Constructor. Creates a new polygon that consists of the specified vertices. + * + * Note: the vertices used to initialize a polygon must be coplanar and form a + * convex loop. + * + * @param vertices polygon vertices + */ + public Polygon(List vertices) throws ColinearPointsException { + this(vertices, new PropertyStorage(), true, null); + } + + public static ArrayList pruneDuplicatePoints(List incoming) { + // return incoming; ArrayList newPoints = new ArrayList(); for (int i = 0; i < incoming.size(); i++) { Vertex v = incoming.get(i); - //v.pos.roundToEpsilon(Plane.getEPSILON()); - boolean duplicate = false; - for (Vertex vx : newPoints) { - if (vx.pos.test(v.pos, Plane.getEPSILON())) { - //duplicate = true; + for(Vertex vt:newPoints) { + if(vt.pos.test(v.pos)) { + v=null; + break; } } - if (!duplicate) { + if(v!=null) newPoints.add(v); - } - } try { return newPoints; @@ -171,139 +169,158 @@ public static ArrayList pruneDuplicatePoints(List incoming) { return null; } } - private void validateAndInit( boolean allowDegenerate) { - vertices=pruneDuplicatePoints(vertices); - if(getPlane()==null) - this.setPlane(Plane.createFromPoints( - vertices)); - for (Vertex v : getVertices()) { - v.normal = getPlane().getNormal(); - //v.pos.roundToEpsilon(); + + private void validateAndInit(boolean fixInversions) throws ColinearPointsException { + ArrayList vertices = pruneDuplicatePoints(this.vertices); + Plane p = Plane.createFromPoints(vertices); + if (getPlane() == null) { + setPlane(p); } - setDegenerate(true); - if (Vector3d.ZERO.equals(getPlane().getNormal())) { - valid = false; - throw new RuntimeException( - "Normal is zero! Probably, duplicate points have been specified!\n\n" + toStlString()); + + + //if(fixInversions) { + Vector3d minus = getPlane().getNormal().minus(p.getNormal()); + double magnitude = minus.magnitude(); + if (Math.abs( magnitude)>2-(Plane.getEPSILON()*2) ) { + Collections.reverse(vertices); + } + //} + if (!getPlane().checkNormal(vertices)) { + if(p.getLengthSquared()>Plane.getEPSILON()) + setPlane(p); + else + if (getPlane() == null) + throw new ColinearPointsException("Failed! the normal provided mismatched to calculated normal"); } + this.vertices=vertices; + if (getVertices().size() < 3) { - throw new RuntimeException("Invalid polygon: at least 3 vertices expected, got: " + getVertices().size()); + throw new ColinearPointsException("Invalid polygon: at least 3 vertices expected, got: " + getVertices().size()); } - Edge e = new Edge(getVertices().get(0), getVertices().get(1)); - for (int i = 2; i < getVertices().size(); i++) { - if (!e.colinear(getVertices().get(i).pos)) { - setDegenerate(false); - return; - } + if( !areAllPointsCollinear()) + return; + new ColinearPointsException("This polygon is colinear"); + + } + + public void rotatePoints() { + Vertex b = getVertices().remove(0); + getVertices().add(b); + } + + /** + * Constructor. Creates a new polygon that consists of the specified vertices. + * + * Note: the vertices used to initialize a polygon must be coplanar and form a + * convex loop. + * + * @param vertices polygon vertices + * + */ + public Polygon(Vertex... vertices) throws ColinearPointsException { + this(Arrays.asList(vertices)); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#clone() + */ + @Override + public Polygon clone() { + List newVertices = new ArrayList<>(); + this.getVertices().forEach((vertex) -> { + newVertices.add(vertex.clone()); + }); + // TODO figure out why this isnt working + try { + //return new Polygon(newVertices, getStorage(), true, plane.clone()).setColor(getColor()); + return new Polygon(newVertices, getStorage(),false,null).setColor(getColor()); + }catch(Exception ex) { + throw new RuntimeException(ex); } - if (!allowDegenerate) { - // throw runtimeException; - new RuntimeException("This polygon is colinear").printStackTrace(); + } + + /** + * Flips this polygon. + * + * @return this polygon + */ + public Polygon flip() { + + Collections.reverse(getVertices()); + plane.flip(); +// try { +// plane=Plane.createFromPoints(vertices); +// } catch (ColinearPointsException e) { +// plane.flip(); +// } + if (!getPlane().checkNormal(vertices)) { + // getPlane().checkNormal(vertices); + new RuntimeException("Failed! the normal provided mismatched to calculated normal").printStackTrace(); } + + return this; } - /** - * Constructor. Creates a new polygon that consists of the specified - * vertices. - * - * Note: the vertices used to initialize a polygon must be coplanar - * and form a convex loop. - * - * @param vertices polygon vertices - * - */ - public Polygon(Vertex... vertices) { - this(Arrays.asList(vertices)); - } - - /* (non-Javadoc) - * @see java.lang.Object#clone() - */ - @Override - public Polygon clone() { - List newVertices = new ArrayList<>(); - this.getVertices().forEach((vertex) -> { - newVertices.add(vertex.clone()); - }); - // TODO figure out why this isnt working - //return new Polygon(newVertices, getStorage(),true,plane.clone()).setColor(getColor()); - return new Polygon(newVertices, getStorage(),true,null).setColor(getColor()); - } - - /** - * Flips this polygon. - * - * @return this polygon - */ - public Polygon flip() { - getVertices().forEach((vertex) -> { - vertex.flip(); - }); - Collections.reverse(getVertices()); - - getPlane().flip(); - - return this; - } - - /** - * Returns a flipped copy of this polygon. - * - * Note: this polygon is not modified. - * - * @return a flipped copy of this polygon - */ - public Polygon flipped() { - return clone().flip(); - } - - /** - * Returns this polygon in STL string format. - * - * @return this polygon in STL string format - */ - public String toStlString() { - return toStlString(new StringBuilder()).toString(); - } - - /** - * Returns this polygon in STL string format. - * - * @param sb string builder - * - * @return the specified string builder - */ - public StringBuilder toStlString(StringBuilder sb) { - - if (this.getVertices().size() == 3) { - - // TODO: improve the triangulation? - // - // STL requires triangular polygons. - // If our polygon has more vertices, create - // multiple triangles: + + /** + * Returns a flipped copy of this polygon. + * + * Note: this polygon is not modified. + * + * @return a flipped copy of this polygon + */ + public Polygon flipped() { + return clone().flip(); + } + + /** + * Returns this polygon in STL string format. + * + * @return this polygon in STL string format + */ + public String toStlString() { + return toStlString(new StringBuilder()).toString(); + } + + /** + * Returns this polygon in STL string format. + * + * @param sb string builder + * + * @return the specified string builder + */ + public StringBuilder toStlString(StringBuilder sb) { + + if (this.getVertices().size() == 3) { + + // TODO: improve the triangulation? + // + // STL requires triangular polygons. + // If our polygon has more vertices, create + // multiple triangles: String firstVertexStl = this.getVertices().get(0).toStlString(); sb.append(" facet normal ").append(this.getPlane().getNormal().toStlString()).append("\n") .append(" outer loop\n").append(" ").append(firstVertexStl).append("\n").append(" "); - this.getVertices().get( 1).toStlString(sb).append("\n").append(" "); - this.getVertices().get(2).toStlString(sb).append("\n") - .append(" endloop\n").append(" endfacet\n"); - - }else { - throw new RuntimeException("Polygon must be a triangle before STL can be made "+getVertices().size()); - } - - return sb; - } - - /** - * Translates this polygon. - * - * @param v the vector that defines the translation - * @return this polygon - */ + this.getVertices().get(1).toStlString(sb).append("\n").append(" "); + this.getVertices().get(2).toStlString(sb).append("\n").append(" endloop\n").append(" endfacet\n"); + + } else { + throw new RuntimeException("Polygon must be a triangle before STL can be made " + getVertices().size()); + } + + return sb; + } + + /** + * Translates this polygon. + * + * @param v the vector that defines the translation + * @return this polygon + */ // public Polygon translate(Vector3d v) { // getVertices().forEach((vertex) -> { // vertex.pos = vertex.pos.plus(v); @@ -331,37 +348,40 @@ public StringBuilder toStlString(StringBuilder sb) { // return clone().translate(v); // } - /** - * Applies the specified transformation to this polygon. - * - * Note: if the applied transformation performs a mirror operation - * the vertex order of this polygon is reversed. - * - * @param transform the transformation to apply - * - * @return this polygon - */ - public Polygon transform(Transform transform) { - - this.getVertices().stream().forEach( - (v) -> { - v.transform(transform); - } - ); - - Vector3d a = this.getVertices().get(0).pos; - -// this.plane.setNormal(Plane.computeNormal(this.getVertices())); -// this.plane.setDist(this.plane.getNormal().dot(a)); - getPlane().transformPlane(transform, a); - - if (transform.isMirror()) { - // the transformation includes mirroring. flip polygon - flip(); - - } - return this; - } + /** + * Applies the specified transformation to this polygon. + * + * Note: if the applied transformation performs a mirror operation the vertex + * order of this polygon is reversed. + * + * @param transform the transformation to apply + * + * @return this polygon + * @throws ColinearPointsException + */ + public Polygon transform(Transform transform) throws ColinearPointsException { + + this.getVertices().stream().forEach((v) -> { + v.transform(transform); + }); + + Vector3d a = this.getVertices().get(0).pos; + + // Given how the relative locations of the points can change in a scale operations + // it is nessissary to reacalculated the normal on operation + this.plane=Plane.createFromPoints(getVertices()); +// + if (transform.isMirror()) { + // the transformation includes mirroring. flip polygon + flip(); + } +// if (!getPlane().checkNormal(vertices)) { +// new RuntimeException("Failed! the normal provided mismatched to calculated normal").printStackTrace(); +// ; +// } + return this; + } + // public Vector3d computeNormal(List vertices) { // Vector3d normal = new Vector3d(0, 0, 0); // int n = vertices.size(); @@ -377,393 +397,387 @@ public Polygon transform(Transform transform) { // // return normal.normalized(); // } - /** - * Returns a transformed copy of this polygon. - * - * Note: if the applied transformation performs a mirror operation - * the vertex order of this polygon is reversed. - * - * Note: this polygon is not modified - * - * @param transform the transformation to apply - * @return a transformed copy of this polygon - */ - public Polygon transformed(Transform transform) { - return clone().transform(transform); - } - - /** - * Creates a polygon from the specified point list. - * - * @param points the points that define the polygon - * @param shared shared property storage - * @return a polygon defined by the specified point list - */ - public static Polygon fromPoints(List points, - PropertyStorage shared) { - return fromPoints(points, shared, null,true); - } - - /** - * Creates a polygon from the specified point list. - * - * @param points the points that define the polygon - * @return a polygon defined by the specified point list - */ - public static Polygon fromPoints(List points) { - return fromPoints(points, new PropertyStorage(), null,true); - } - - /** - * Creates a polygon from the specified points. - * - * @param points the points that define the polygon - * @return a polygon defined by the specified point list - */ - public static Polygon fromPoints(Vector3d... points) { - return fromPoints(Arrays.asList(points), new PropertyStorage(), null,true); - } - public static Polygon fromPointsAllowDegenerate(List vertices2) { + /** + * Returns a transformed copy of this polygon. + * + * Note: if the applied transformation performs a mirror operation the vertex + * order of this polygon is reversed. + * + * Note: this polygon is not modified + * + * @param transform the transformation to apply + * @return a transformed copy of this polygon + * @throws ColinearPointsException + */ + public Polygon transformed(Transform transform) throws ColinearPointsException { + return clone().transform(transform); + } + + /** + * Creates a polygon from the specified point list. + * + * @param points the points that define the polygon + * @param shared shared property storage + * @return a polygon defined by the specified point list + */ + public static Polygon fromPoints(List points, PropertyStorage shared) throws ColinearPointsException { + return fromPoints(points, shared, null, true); + } + + /** + * Creates a polygon from the specified point list. + * + * @param points the points that define the polygon + * @return a polygon defined by the specified point list + */ + public static Polygon fromPoints(List points)throws ColinearPointsException { + return fromPoints(points, new PropertyStorage(), null, true); + } + + /** + * Creates a polygon from the specified points. + * + * @param points the points that define the polygon + * @return a polygon defined by the specified point list + */ + public static Polygon fromPoints(Vector3d... points)throws ColinearPointsException { + return fromPoints(Arrays.asList(points), new PropertyStorage(), null, true); + } + + public static Polygon fromPointsAllowDegenerate(List vertices2) throws ColinearPointsException { return fromPoints(vertices2, new PropertyStorage(), null, true); } - /** - * Creates a polygon from the specified point list. - * - * @param points the points that define the polygon - * @param shared the shared - * @param plane may be null - * @return a polygon defined by the specified point list - */ - private static Polygon fromPoints( - List points, PropertyStorage shared, Plane plane, boolean allowDegenerate) { - - Vector3d normal - = (plane != null) ? plane.getNormal().clone() : new Vector3d(0, 0, 0); - - List vertices = new ArrayList<>(); - - for (Vector3d p : points) { - Vector3d vec = p.clone(); - Vertex vertex = new Vertex(vec, normal); - vertices.add(vertex); - } - - return new Polygon(vertices, shared,allowDegenerate,null); - } - - /** - * Returns the bounds of this polygon. - * - * @return bouds of this polygon - */ - public Bounds getBounds() { - double minX = Double.POSITIVE_INFINITY; - double minY = Double.POSITIVE_INFINITY; - double minZ = Double.POSITIVE_INFINITY; - - double maxX = Double.NEGATIVE_INFINITY; - double maxY = Double.NEGATIVE_INFINITY; - double maxZ = Double.NEGATIVE_INFINITY; - - for (int i = 0; i < getVertices().size(); i++) { - - Vertex vert = getVertices().get(i); - - if (vert.pos.x < minX) { - minX = vert.pos.x; - } - if (vert.pos.y < minY) { - minY = vert.pos.y; - } - if (vert.pos.z < minZ) { - minZ = vert.pos.z; - } - - if (vert.pos.x > maxX) { - maxX = vert.pos.x; - } - if (vert.pos.y > maxY) { - maxY = vert.pos.y; - } - if (vert.pos.z > maxZ) { - maxZ = vert.pos.z; - } - - } // end for vertices - - return new Bounds( - new Vector3d(minX, minY, minZ), - new Vector3d(maxX, maxY, maxZ)); - } - - /** - * Contains. - * - * @param p the p - * @return true, if successful - */ - 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; - boolean oddNodes = false; - double x2 = getVertices().get(getVertices().size() - 1).pos.x; - double y2 = getVertices().get(getVertices().size() - 1).pos.y; - double x1, y1; - for (int i = 0; i < getVertices().size(); x2 = x1, y2 = y1, ++i) { - x1 = getVertices().get(i).pos.x; - y1 = getVertices().get(i).pos.y; - if (((y1 < py) && (y2 >= py)) - || (y1 >= py) && (y2 < py)) { - if ((py - y1) / (y2 - y1) - * (x2 - x1) < (px - x1)) { - oddNodes = !oddNodes; - } - } - } - return oddNodes; - } - - /** - * Contains. - * - * @param p the p - * @return true, if successful - */ - public boolean contains(Polygon p) { - - for (Vertex v : p.getVertices()) { - if (!contains(v.pos)) { - return false; - } - } - - return true; - } - - /** - * Gets the storage. - * - * @return the shared - */ - public PropertyStorage getStorage() { - if (shared == null) { - shared = new PropertyStorage(); - } + /** + * Creates a polygon from the specified point list. + * + * @param points the points that define the polygon + * @param shared the shared + * @param plane may be null + * @return a polygon defined by the specified point list + */ + public static Polygon fromPoints(List points, PropertyStorage shared, Plane plane, + boolean allowDegenerate)throws ColinearPointsException { + List vertices = new ArrayList<>(); + for (Vector3d p : points) { + Vector3d vec = p.clone(); + Vertex vertex = new Vertex(vec); + vertices.add(vertex); + } + + return new Polygon(vertices, shared, allowDegenerate, plane); + } + + /** + * Returns the bounds of this polygon. + * + * @return bouds of this polygon + */ + public Bounds getBounds() { + double minX = Double.POSITIVE_INFINITY; + double minY = Double.POSITIVE_INFINITY; + double minZ = Double.POSITIVE_INFINITY; + + double maxX = Double.NEGATIVE_INFINITY; + double maxY = Double.NEGATIVE_INFINITY; + double maxZ = Double.NEGATIVE_INFINITY; + + for (int i = 0; i < getVertices().size(); i++) { - return shared; - } + Vertex vert = getVertices().get(i); + + if (vert.pos.x < minX) { + minX = vert.pos.x; + } + if (vert.pos.y < minY) { + minY = vert.pos.y; + } + if (vert.pos.z < minZ) { + minZ = vert.pos.z; + } + + if (vert.pos.x > maxX) { + maxX = vert.pos.x; + } + if (vert.pos.y > maxY) { + maxY = vert.pos.y; + } + if (vert.pos.z > maxZ) { + maxZ = vert.pos.z; + } + + } // end for vertices + + return new Bounds(new Vector3d(minX, minY, minZ), new Vector3d(maxX, maxY, maxZ)); + } + + /** + * Contains. + * + * @param p the p + * @return true, if successful + */ + 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; + boolean oddNodes = false; + double x2 = getVertices().get(getVertices().size() - 1).pos.x; + double y2 = getVertices().get(getVertices().size() - 1).pos.y; + double x1, y1; + for (int i = 0; i < getVertices().size(); x2 = x1, y2 = y1, ++i) { + x1 = getVertices().get(i).pos.x; + y1 = getVertices().get(i).pos.y; + if (((y1 < py) && (y2 >= py)) || (y1 >= py) && (y2 < py)) { + if ((py - y1) / (y2 - y1) * (x2 - x1) < (px - x1)) { + oddNodes = !oddNodes; + } + } + } + return oddNodes; + } + + /** + * Contains. + * + * @param p the p + * @return true, if successful + */ + public boolean contains(Polygon p) { + + for (Vertex v : p.getVertices()) { + if (!contains(v.pos)) { + return false; + } + } + + return true; + } + + /** + * Gets the storage. + * + * @return the shared + */ + public PropertyStorage getStorage() { + + if (shared == null) { + shared = new PropertyStorage(); + } + + return shared; + } // public Exception getCreationEventStackTrace() { // return creationEventStackTrace; // } public List getPoints() { - ArrayList p =new ArrayList<>(); - for(Vertex v:getVertices()) { - p.add(v.pos); + ArrayList p = new ArrayList<>(); + for (Vertex v : getVertices()) { + p.add(v.pos.clone()); } return p; } + /** * Movey. * - * @param howFarToMove - * the how far to move + * @param howFarToMove the how far to move * @return the csg + * @throws ColinearPointsException */ // Helper/wrapper functions for movement - public Polygon movey(Number howFarToMove) { + public Polygon movey(Number howFarToMove) throws ColinearPointsException { return this.transformed(Transform.unity().translateY(howFarToMove.doubleValue())); } /** * Movez. * - * @param howFarToMove - * the how far to move + * @param howFarToMove the how far to move * @return the csg + * @throws ColinearPointsException */ - public Polygon movez(Number howFarToMove) { + public Polygon movez(Number howFarToMove) throws ColinearPointsException { return this.transformed(Transform.unity().translateZ(howFarToMove.doubleValue())); } /** * Movex. * - * @param howFarToMove - * the how far to move + * @param howFarToMove the how far to move * @return the csg + * @throws ColinearPointsException */ - public Polygon movex(Number howFarToMove) { + public Polygon movex(Number howFarToMove) throws ColinearPointsException { return this.transformed(Transform.unity().translateX(howFarToMove.doubleValue())); } /** * Rotz. * - * @param degreesToRotate - * the degrees to rotate + * @param degreesToRotate the degrees to rotate * @return the csg + * @throws ColinearPointsException */ // Rotation function, rotates the object - public Polygon rotz(Number degreesToRotate) { + public Polygon rotz(Number degreesToRotate) throws ColinearPointsException { return this.transformed(new Transform().rotZ(degreesToRotate.doubleValue())); } /** * Roty. * - * @param degreesToRotate - * the degrees to rotate + * @param degreesToRotate the degrees to rotate * @return the csg + * @throws ColinearPointsException */ - public Polygon roty(Number degreesToRotate) { + public Polygon roty(Number degreesToRotate) throws ColinearPointsException { return this.transformed(new Transform().rotY(degreesToRotate.doubleValue())); } /** * Rotx. * - * @param degreesToRotate - * the degrees to rotate + * @param degreesToRotate the degrees to rotate * @return the csg + * @throws ColinearPointsException */ - public Polygon rotx(Number degreesToRotate) { + public Polygon rotx(Number degreesToRotate) throws ColinearPointsException { return this.transformed(new Transform().rotX(degreesToRotate.doubleValue())); } /** * Scalez. * - * @param scaleValue - * the scale value + * @param scaleValue the scale value * @return the csg + * @throws ColinearPointsException */ // Scale function, scales the object - public Polygon scalez(Number scaleValue) { + public Polygon scalez(Number scaleValue) throws ColinearPointsException { return this.transformed(new Transform().scaleZ(scaleValue.doubleValue())); } /** * Scaley. * - * @param scaleValue - * the scale value + * @param scaleValue the scale value * @return the csg + * @throws ColinearPointsException */ - public Polygon scaley(Number scaleValue) { + public Polygon scaley(Number scaleValue) throws ColinearPointsException { return this.transformed(new Transform().scaleY(scaleValue.doubleValue())); } /** * Scalex. * - * @param scaleValue - * the scale value + * @param scaleValue the scale value * @return the csg + * @throws ColinearPointsException */ - public Polygon scalex(Number scaleValue) { + public Polygon scalex(Number scaleValue) throws ColinearPointsException { return this.transformed(new Transform().scaleX(scaleValue.doubleValue())); } /** * Scale. * - * @param scaleValue - * the scale value + * @param scaleValue the scale value * @return the csg + * @throws ColinearPointsException */ - public Polygon scale(Number scaleValue) { + public Polygon scale(Number scaleValue) throws ColinearPointsException { return this.transformed(new Transform().scale(scaleValue.doubleValue())); } - /** - * Indicates whether this polyon is valid, i.e., if it - * - * @return - */ - public boolean isValid() { - return valid; - } - - - + /** + * Indicates whether this polyon is valid, i.e., if it + * + * @return + */ + public boolean isValid() { + return valid; + } public void setDegenerate(boolean degenerate) { this.degenerate = degenerate; } + public boolean isDegenerate() { - + return degenerate; } - + public ArrayList getDegeneratePoints() { ArrayList back = new ArrayList(); - if(!isDegenerate()) + if (!isDegenerate()) return back; Edge longEdge = getLongEdge(); - for(int i=0;i e =edges(); - Edge longEdge =e.get(0); - for(int i=1;i e = edges(); + Edge longEdge = e.get(0); + for (int i = 1; i < e.size(); i++) { Edge edge = e.get(i); - if(edge.length()>longEdge.length()) { - longEdge=edge; + if (edge.length() > longEdge.length()) { + longEdge = edge; } } return longEdge; } public ArrayList edges() { - ArrayList e=new ArrayList(); - for(int i=0;i e = new ArrayList(); + for (int i = 0; i < getVertices().size(); i++) { int i1 = i; - int i2=i+1; - if(i2==getVertices().size()) { - i2=0; + int i2 = i + 1; + if (i2 == getVertices().size()) { + i2 = 0; } - e.add(new Edge(getVertices().get(i1),getVertices().get(i2))); + e.add(new Edge(getVertices().get(i1), getVertices().get(i2))); } return e; } public Polygon setColor(Color color) { - if(color!=null) { - r=color.getRed(); - g=color.getGreen(); - b=color.getBlue(); - o=color.getOpacity(); - }else { + if (color != null) { + r = color.getRed(); + g = color.getGreen(); + b = color.getBlue(); + o = color.getOpacity(); + } else { setColor(CSG.getDefaultColor()); } return this; } + public Color getColor() { return new Color(r, g, b, o); } + @Override public String toString() { - String ret="# points="+getVertices().size()+" normal="+getPlane().getNormal().toStlString()+" [ "; - for(Vertex v:getVertices()) { - ret+=" "+v.pos.toStlString()+" , "; + String ret = "# points=" + getVertices().size() + " normal=" + getPlane().getNormal().toStlString() + " [ "; + for (Vertex v : getVertices()) { + ret += "\n\t" + v.pos.toStlString() + " , "; } - return ret+" ] "; + return ret + " ] "; } public boolean isHole() { @@ -777,28 +791,28 @@ public void setHole(boolean isHole) { /** * @return the vertices */ - public List getVertices() { + public ArrayList getVertices() { return vertices; } - + public Polygon add(int index, Vertex v) { // for(Vertex vr:vertices) // if(vr.pos.test(v.pos, Plane.getEPSILON())) // return this; - vertices.add(index,v); - + getVertices().add(index, v); + return this; } - public boolean areAllPointsCollinear() { + private boolean areAllPointsCollinear() { // If we have 2 or fewer points, they're always collinear - if (vertices.size() <= 2) { + if (getVertices().size() <= 2) { return true; } // Get first two points to establish a direction vector - Vertex p1 = vertices.get(0); - Vertex p2 = vertices.get(1); + Vertex p1 = getVertices().get(0); + Vertex p2 = getVertices().get(1); // Calculate the direction vector between first two points Vector3d direction = p1.pos.minus(p2.pos); @@ -811,8 +825,8 @@ public boolean areAllPointsCollinear() { direction.normalize(); // Check each subsequent point - for (int i = 2; i < vertices.size(); i++) { - Vertex p = vertices.get(i); + for (int i = 2; i < getVertices().size(); i++) { + Vertex p = getVertices().get(i); // Calculate cross product Vector3d cross = direction.cross(p1.pos.minus(p.pos)); @@ -833,8 +847,19 @@ public Plane getPlane() { } public void setPlane(Plane plane) { - if(plane==null) + if (plane == null) throw new RuntimeException("Plane can not be null!"); + this.plane = plane; } + + public int size() { + + return getVertices().size(); + } + + public void setVertices(ArrayList vertices) throws ColinearPointsException { + plane = Plane.createFromPoints(vertices); + this.vertices = vertices; + } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Polyhedron.java b/src/main/java/eu/mihosoft/vrl/v3d/Polyhedron.java index 2bf44e7f..cdaacaea 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Polyhedron.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Polyhedron.java @@ -68,8 +68,12 @@ public List toPolygons() { Function, Polygon> faceListToPolygon = (List faceList) -> { - return Polygon.fromPoints(faceList.stream().map(indexToPoint). - collect(Collectors.toList()), properties); + try { + return Polygon.fromPoints(faceList.stream().map(indexToPoint). + collect(Collectors.toList()), properties); + } catch (ColinearPointsException e) { + throw new RuntimeException(e); + } }; return faces.stream().map(faceListToPolygon). diff --git a/src/main/java/eu/mihosoft/vrl/v3d/RoundedCube.java b/src/main/java/eu/mihosoft/vrl/v3d/RoundedCube.java index 70a3f254..f9c311f3 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/RoundedCube.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/RoundedCube.java @@ -116,16 +116,20 @@ public List toPolygons() { CSG sphere7 = spherePrototype.transformed(unity().translate(x, y, z)); CSG sphere8 = spherePrototype.transformed(unity().translate(-x, y, z)); - List result = sphere1.union( + List result = CSG.hullAll(sphere1, sphere2, sphere3, sphere4, - sphere5, sphere6, sphere7, sphere8).hull().getPolygons(); + sphere5, sphere6, sphere7, sphere8).getPolygons(); 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); + try { + p.transform(centerTransform); + } catch (ColinearPointsException e) { + throw new RuntimeException(e); + } } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/STL.java b/src/main/java/eu/mihosoft/vrl/v3d/STL.java index d6551b78..9a1f5570 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/STL.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/STL.java @@ -83,28 +83,9 @@ public static CSG file(URL path) throws IOException, URISyntaxException { public static CSG file(Path path) throws IOException { STLLoader loader = new STLLoader(); - ArrayList polygons = new ArrayList<>(); - List vertices = new ArrayList<>(); - for (Vertex p : loader.parse(path.toFile())) { - vertices.add(p); - if (vertices.size() == 3) { - try { - - Plane pl = new Plane(vertices.get(0).normal, vertices); - polygons.add(new Polygon(vertices, null, false, pl)); - } catch (RuntimeException ex) { - //ex.printStackTrace(); - System.err.println("Pruning polygon loading STL::file"); - } - vertices = new ArrayList<>(); - } - } + ArrayList polygons = loader.parse(path.toFile()); CSG fromPolygons = CSG.fromPolygons(new PropertyStorage(), polygons); - boolean b = CSG.isPreventNonManifoldTriangles(); - CSG.setPreventNonManifoldTriangles(false); - fromPolygons.triangulate(); - CSG.setPreventNonManifoldTriangles(b); return fromPolygons; } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Slice.java b/src/main/java/eu/mihosoft/vrl/v3d/Slice.java index b4bd5d51..51197a8e 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Slice.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Slice.java @@ -154,8 +154,9 @@ boolean pixelEdge(double absX, double absY, WritableImage obj_img) { * @param normalInsetDistance * Inset for sliced output * @return A set of polygons ining the sliced shape + * @throws ColinearPointsException */ - public List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) { + public List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) throws ColinearPointsException { if (Thread.interrupted()) { return null; } @@ -461,7 +462,7 @@ private static boolean isVertexAtZero(Vertex vertex) { return vertex.getZ() < SLICE_UPPER_BOUND && vertex.getZ() > SLICE_LOWER_BOUND; } - public static List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) { + public static List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) throws ColinearPointsException { try { if(DefaultSliceImp.class.isInstance(sliceEngine)) { // avoid concurrecy issues @@ -479,7 +480,7 @@ public static List slice(CSG incoming, Transform slicePlane, double nor } } - private static List sanatize(List slice) { + private static List sanatize(List slice) throws ColinearPointsException { for (int i = 0; i < slice.size(); i++) { Polygon me = slice.get(i); boolean bad = !Extrude.isCCW(me); @@ -495,10 +496,10 @@ private static List sanatize(List slice) { return slice; } - public static List slice(CSG incoming) { + public static List slice(CSG incoming) throws ColinearPointsException { return slice(incoming, new Transform(),0); } - public static List slice(CSG incoming, double normalInsetDistance) { + public static List slice(CSG incoming, double normalInsetDistance) throws ColinearPointsException { return slice(incoming, new Transform(),normalInsetDistance); } public static ISlice getSliceEngine() { diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java b/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java index 9c27e93d..44556e20 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java @@ -157,7 +157,7 @@ private Vertex sphereVertex(Vector3d c, double r, double theta, double phi) { Math.cos(phi), Math.sin(theta) * Math.sin(phi) ); - return new Vertex(c.plus(dir.times(r)), dir); + return new Vertex(c.plus(dir.times(r))); } /* (non-Javadoc) @@ -193,7 +193,12 @@ public List toPolygons() { sphereVertex(center, radius, i / (double) getNumSlices(), (j + 1) / (double) getNumStacks()) ); - polygons.add(new Polygon(vertices, getProperties())); + try { + polygons.add(new Polygon(vertices, getProperties())); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } } return polygons; diff --git a/src/main/java/eu/mihosoft/vrl/v3d/TextExtrude.java b/src/main/java/eu/mihosoft/vrl/v3d/TextExtrude.java index 92aa2f06..311abf44 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/TextExtrude.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/TextExtrude.java @@ -17,6 +17,9 @@ import javafx.scene.text.Text; import java.util.stream.Collectors; import java.util.stream.IntStream; + +import com.piro.bezier.BezierPath; + import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.scene.paint.Color; @@ -140,20 +143,26 @@ private TextExtrude(String text, Font font, double dir) { // Convert Text to Path Path subtract = (Path) (Shape.subtract(textNode, new Rectangle(0, 0))); - List> outlines = extractOutlines(subtract); + List> outlines = extractOutlines(subtract,font.getSize()); double zOff = 0; - boolean b = CSG.isPreventNonManifoldTriangles(); - CSG.setPreventNonManifoldTriangles(false); +// boolean b = CSG.isPreventNonManifoldTriangles(); +// CSG.setPreventNonManifoldTriangles(false); for (List points : outlines) { - boolean hole = Extrude.isCCW(Polygon.fromPoints(points)); - CSG newLetter = Extrude.points(new Vector3d(0, 0, dir), points).movez(zOff); - newLetter.triangulate(); - if (!hole) - sections.add(newLetter); - else - holes.add(newLetter); + try { + boolean hole = Extrude.isCCWv3d(points); + CSG newLetter = Extrude.points(new Vector3d(0, 0, dir), points).movez(zOff); + //newLetter.triangulate(); + if (!hole) + sections.add(newLetter); + else { + newLetter.setIsHole(true); + holes.add(newLetter); + } + }catch(ColinearPointsException e) { + e.printStackTrace(); + } } - CSG.setPreventNonManifoldTriangles(b); +// CSG.setPreventNonManifoldTriangles(b); // // Convert Path elements into lists of points defining the perimeter // // (exterior or interior) // subtract.getElements().forEach(this::getPoints); @@ -207,19 +216,20 @@ public List getOffset() { /** * Converts a JavaFX Text object into a list of cleaned vector lists * representing the outlines + * @param fontSize */ - public static List> extractOutlines(Path text) { + public static List> extractOutlines(Path text, double fontSize) { List> rawOutlines = extractRawOutlines(text); - List> cleanedOutlines = new ArrayList<>(); - - for (List outline : rawOutlines) { - List cleaned = cleanOutline(outline); - if (cleaned.size() >= 3) { // Only keep outlines with at least 3 points - cleanedOutlines.add(cleaned); - } - } - - return cleanedOutlines; +// List> cleanedOutlines = new ArrayList<>(); +// +// for (List outline : rawOutlines) { +// List cleaned = cleanOutline(outline,fontSize); +// if (cleaned.size() >= 3) { // Only keep outlines with at least 3 points +// cleanedOutlines.add(cleaned); +// } +// } + + return rawOutlines; } /** @@ -228,123 +238,55 @@ public static List> extractOutlines(Path text) { private static List> extractRawOutlines(Path textPath) { List> allOutlines = new ArrayList<>(); - List currentPath = new ArrayList<>(); - Vector3d lastPoint = null; + StringBuilder pathBuilder = new StringBuilder(); + //List currentPath = new ArrayList<>(); for (PathElement element : textPath.getElements()) { - if (element instanceof MoveTo) { - if (!currentPath.isEmpty()) { - allOutlines.add(new ArrayList<>(currentPath)); - currentPath.clear(); - } - MoveTo move = (MoveTo) element; - lastPoint = Vector3d.xyz(move.getX(), move.getY(), 0); - currentPath.add(lastPoint); - } else if (element instanceof LineTo) { - LineTo line = (LineTo) element; - lastPoint = Vector3d.xyz(line.getX(), line.getY(), 0); - currentPath.add(lastPoint); - } else if (element instanceof CubicCurveTo) { - CubicCurveTo curve = (CubicCurveTo) element; - List curvePoints = approximateCubicCurve(lastPoint, - Vector3d.xyz(curve.getControlX1(), curve.getControlY1(), 0), - Vector3d.xyz(curve.getControlX2(), curve.getControlY2(), 0), - Vector3d.xyz(curve.getX(), curve.getY(), 0)); - currentPath.addAll(curvePoints); - lastPoint = curvePoints.get(curvePoints.size() - 1); - } else if (element instanceof QuadCurveTo) { - QuadCurveTo curve = (QuadCurveTo) element; - List curvePoints = approximateQuadCurve(lastPoint, - Vector3d.xyz(curve.getControlX(), curve.getControlY(), 0), - Vector3d.xyz(curve.getX(), curve.getY(), 0)); - currentPath.addAll(curvePoints); - lastPoint = curvePoints.get(curvePoints.size() - 1); - } + if (element instanceof MoveTo) { + // If we have a current path, process it with BezierPath + if (pathBuilder.length() > 0) { + BezierPath bezierPath = new BezierPath(5); + bezierPath.parsePathString(pathBuilder.toString()); + List pathPoints = bezierPath.evaluate(); + if (!pathPoints.isEmpty()) { + allOutlines.add(new ArrayList<>(pathPoints)); + } + pathBuilder = new StringBuilder(); + } + + MoveTo move = (MoveTo) element; + pathBuilder.append("M").append(move.getX()).append(" ").append(move.getY()); + + } else if (element instanceof LineTo) { + LineTo line = (LineTo) element; + pathBuilder.append("L").append(line.getX()).append(" ").append(line.getY()); + + } else if (element instanceof CubicCurveTo) { + CubicCurveTo curve = (CubicCurveTo) element; + pathBuilder.append("C") + .append(curve.getControlX1()).append(" ").append(curve.getControlY1()).append(" ") + .append(curve.getControlX2()).append(" ").append(curve.getControlY2()).append(" ") + .append(curve.getX()).append(" ").append(curve.getY()); + + } else if (element instanceof QuadCurveTo) { + QuadCurveTo curve = (QuadCurveTo) element; + pathBuilder.append("Q") + .append(curve.getControlX()).append(" ").append(curve.getControlY()).append(" ") + .append(curve.getX()).append(" ").append(curve.getY()); + } } - if (!currentPath.isEmpty()) { - allOutlines.add(currentPath); + // Process the final path if it exists + if (pathBuilder.length() > 0) { + BezierPath bezierPath = new BezierPath(5); + bezierPath.parsePathString(pathBuilder.toString()); + List pathPoints = bezierPath.evaluate(); + if (!pathPoints.isEmpty()) { + allOutlines.add(pathPoints); + } } return allOutlines; } - /** - * Clean an outline by removing duplicate points and ensuring proper closure - */ - private static List cleanOutline(List outline) { - if (outline.isEmpty()) - return outline; - - List cleaned = new ArrayList<>(); - for (int i = 0; i < outline.size(); i++) { - Vector3d point = outline.get(i); - boolean touching=false; - for(Vector3d v:cleaned) { - if(v.test(point, 0.001)) - touching=true; - } - if(!touching) - cleaned.add(point); - } - // Remove redundant points that form zero-area triangles - return cleaned; - } - - /** - * Remove points that form zero-area triangles with their neighbors - */ - private static List removeRedundantPoints(List points) { - if (points.size() < 3) - return points; - - List result = new ArrayList<>(); - int size = points.size(); - - for (int i = 0; i < size; i++) { - Vector3d curr = points.get(i); - result.add(curr); - } - - return result; - } - -// private static boolean isNearlyEqual(Vector3d v1, Vector3d v2) { -// return v1.minus(v2).length() < POINT_EPSILON; -// } - - // Bezier curve methods remain the same - private static List approximateCubicCurve(Vector3d start, Vector3d control1, Vector3d control2, - Vector3d end) { - List points = new ArrayList<>(); - for (double i = 1; i <= CURVE_SEGMENTS; i+=1) { - double t = (double) i / CURVE_SEGMENTS; - double x = cubicBezier(start.x, control1.x, control2.x, end.x, t); - double y = cubicBezier(start.y, control1.y, control2.y, end.y, t); - points.add(Vector3d.xyz(x, y, 0)); - } - return points; - } - - private static List approximateQuadCurve(Vector3d start, Vector3d control, Vector3d end) { - List points = new ArrayList<>(); - for (double i = 1; i <= CURVE_SEGMENTS; i+=1) { - double t = (double) i / CURVE_SEGMENTS; - double x = quadBezier(start.x, control.x, end.x, t); - double y = quadBezier(start.y, control.y, end.y, t); - points.add(Vector3d.xyz(x, y, 0)); - } - return points; - } - - private static double cubicBezier(double p0, double p1, double p2, double p3, double t) { - double mt = 1 - t; - return p0 * mt * mt * mt + 3 * p1 * mt * mt * t + 3 * p2 * mt * t * t + p3 * t * t * t; - } - - private static double quadBezier(double p0, double p1, double p2, double t) { - double mt = 1 - t; - return p0 * mt * mt + 2 * p1 * mt * t + p2 * t * t; - } - } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Toroid.java b/src/main/java/eu/mihosoft/vrl/v3d/Toroid.java index a001e23d..0224ade6 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Toroid.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Toroid.java @@ -70,14 +70,24 @@ public Toroid(double innerRadius, double OuterRadius, int numSlices, int facets) double rad = index / f * 2 * Math.PI; double a = Math.cos(rad) * crossSecRad; double b = Math.sin(rad) * crossSecRad; - vertices.add(new Vertex(new Vector3d(a,b), new Vector3d(-1, 0,0))); + vertices.add(new Vertex(new Vector3d(a,b))); + } + Polygon poly; + try { + poly = new Polygon(vertices, properties); + } catch (ColinearPointsException e) { + throw new RuntimeException(e); } - Polygon poly = new Polygon(vertices, properties); ArrayList slices = new ArrayList(); for (int i = 0; i < numSlices; i++) { double angle = 360.0 / ((double) numSlices) * ((double) i); - slices.add(poly.transformed(new Transform().movex(innerRadius+crossSecRad).roty(angle))); + try { + slices.add(poly.transformed(new Transform().movex(innerRadius+crossSecRad).roty(angle))); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } List newPolygons = new ArrayList<>(); for (int j = 0; j < slices.size(); j++) { @@ -103,7 +113,12 @@ public Toroid(double innerRadius, double OuterRadius, int numSlices, int facets) List pPoints = Arrays.asList(bottomV2, topV2, topV1, bottomV1); - newPolygons.add(Polygon.fromPoints(pPoints, polygon1.getStorage())); + try { + newPolygons.add(Polygon.fromPoints(pPoints, polygon1.getStorage())); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Vector3d.java b/src/main/java/eu/mihosoft/vrl/v3d/Vector3d.java index 165556cd..396ad0d4 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Vector3d.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Vector3d.java @@ -85,15 +85,16 @@ public class Vector3d extends javax.vecmath.Vector3d { * @param z z value */ public Vector3d(double x, double y, double z) { +// if(!Double.isFinite(x)||!Double.isFinite(y)||!Double.isFinite(z)) { +// throw new NumberFormatException("Vectors must be real "+x+" "+y+" "+z); +// } this.x = x; this.y = y; this.z = z; } public Vector3d(Number x, Number y, Number z) { - this.x = x.doubleValue(); - this.y = y.doubleValue(); - this.z = z.doubleValue(); + this(x.doubleValue(),y.doubleValue(),z.doubleValue()); } @@ -105,16 +106,11 @@ public Vector3d(Number x, Number y, Number z) { * @param y y value */ public Vector3d(double x, double y) { - - this.x = x; - this.y = y; - this.z = 0; + this(x, y, (double) 0); } public Vector3d(Number x, Number y) { - this(x, y, (double) 0); - } @@ -458,7 +454,7 @@ public boolean test(Vector3d obj, double epsilon) { final Vector3d other = (Vector3d) obj; double distance =distance(other); double abs = Math.abs(distance); - if(abs>epsilon*1000) + if(abs>epsilon) return false; return true; } @@ -516,159 +512,11 @@ public static Vector3d z(double z) { return new Vector3d(0, 0, z); } - /** - * Creates a new vector which is orthogonal to this. - * - * this_i , this_j , this_k greater than or equal to i,j,k € {1,2,3} permutation - * - * looking for orthogonal vector o to vector this: this_i * o_i + this_j * - * o_j + this_k * o_k = 0 - * - * @return a new vector which is orthogonal to this - */ - public Vector3d orthogonal() { - -// if ((this.x == Double.NaN) || (this.y == Double.NaN) || (this.z == Double.NaN)) { -// throw new IllegalStateException("NaN is not a valid entry for a vector."); -// } - double o1 = 0.0; - double o2 = 0.0; - double o3 = 0.0; - - Random r = new Random(); - - int numberOfZeroEntries = 0; - - if (this.x == 0) { - numberOfZeroEntries++; - o1 = r.nextDouble(); - } - - if (this.y == 0) { - numberOfZeroEntries++; - o2 = r.nextDouble(); - } - - if (this.z == 0) { - numberOfZeroEntries++; - o3 = r.nextDouble(); - } - - switch (numberOfZeroEntries) { - - case 0: - // all this_i != 0 - // - //we do not want o3 to be zero - while (o3 == 0) { - o3 = r.nextDouble(); - } - - //we do not want o2 to be zero - while (o2 == 0) { - o2 = r.nextDouble(); - } - // calculate or choose randomly ?? -// o2 = -this.z * o3 / this.y; - - o1 = (-this.y * o2 - this.z * o3) / this.x; - - break; - - case 1: - // this_i = 0 , i € {1,2,3} - // this_j != 0 != this_k , j,k € {1,2,3}\{i} - // - // choose one none zero randomly and calculate the other one - - if (this.x == 0) { - //we do not want o3 to be zero - while (o3 == 0) { - o3 = r.nextDouble(); - } - - o2 = -this.z * o3 / this.y; - - } else if (this.y == 0) { - - //we do not want o3 to be zero - while (o3 == 0) { - o3 = r.nextDouble(); - } - - o1 = -this.z * o3 / this.x; - - } else if (this.z == 0) { - - //we do not want o1 to be zero - while (o1 == 0) { - o1 = r.nextDouble(); - } - - o2 = -this.z * o1 / this.y; - } - - break; - - case 2: - // if two parts of this are 0 we can achieve orthogonality - // via setting the corressponding part of the orthogonal vector - // to zero this is ALREADY DONE in the init (o_i = 0.0) - // NO CODE NEEDED -// if (this.x == 0) { -// o1 = 0; -// } else if (this.y == 0) { -// o2 = 0; -// } else if (this.z == 0) { -// o3 = 0; -// } - break; - - case 3: - //com.neuronrobotics.sdk.common.Log.error("This vector is equal to (0,0,0). "); - - default: - //com.neuronrobotics.sdk.common.Log.error("The orthogonal one is set randomly."); - - o1 = r.nextDouble(); - o2 = r.nextDouble(); - o3 = r.nextDouble(); - } - - Vector3d result = new Vector3d(o1, o2, o3); - -// if ((this.x ==Double.NaN) || (this.y == Double.NaN) || (this.z == Double.NaN)) { -// throw new IllegalStateException("NaN is not a valid entry for a vector."); -// } -// //com.neuronrobotics.sdk.common.Log.error(" this : "+ this); -// //com.neuronrobotics.sdk.common.Log.error(" result : "+ result); - // check if the created vector is really orthogonal to this - // if not try one more time - while (this.dot(result) != 0.0) { - result = this.orthogonal(); - } - - return result; - - } -// public double getX() { -// // Auto-generated method stub -// return x; -// } -// public double getY() { -// // Auto-generated method stub -// return y; -// } -// public double getZ() { -// // Auto-generated method stub -// return z; -// } - public static String getExportString() { return exportString; } - public static void setExportString(String exportString) { + static void setExportString(String exportString) { Vector3d.exportString = exportString; } @@ -676,7 +524,7 @@ public static double getEXPORTEPSILON() { return EXPORTEPSILON; } - public static void setEXPORTEPSILON(double eXPORTEPSILON) { + static void setEXPORTEPSILON(double eXPORTEPSILON) { if(eXPORTEPSILON<1.0e-5) EXPORTEPSILON = eXPORTEPSILON; } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java b/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java index d0681afc..6ed15e57 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java @@ -50,11 +50,6 @@ public class Vertex implements Serializable{ * Vertex position. */ public Vector3d pos; - - /** - * Normal. - */ - public Vector3d normal; /** The weight. */ private double weight = 1.0; @@ -65,9 +60,8 @@ public class Vertex implements Serializable{ * @param pos position * @param normal normal */ - public Vertex(Vector3d pos, Vector3d normal) { + public Vertex(Vector3d pos) { this.pos = pos; - this.normal = normal; } @@ -78,9 +72,8 @@ public Vertex(Vector3d pos, Vector3d normal) { * @param normal normal * @param weight weight */ - private Vertex(Vector3d pos, Vector3d normal, double weight) { + private Vertex(Vector3d pos, double weight) { this.pos = pos; - this.normal = normal; this.weight = weight; } @@ -89,14 +82,7 @@ private Vertex(Vector3d pos, Vector3d normal, double weight) { */ @Override public Vertex clone() { - return new Vertex(pos.clone(), normal.clone(), weight); - } - - /** - * Inverts all orientation-specific data. (e.g. vertex normal). - */ - public void flip() { - normal = normal.negated(); + return new Vertex(pos.clone(), weight); } /** @@ -108,8 +94,7 @@ public void flip() { * @return a new vertex between this and the specified vertex */ public Vertex interpolate(Vertex other, double t) { - return new Vertex(pos.lerp(other.pos, t), - normal.lerp(other.normal, t)); + return new Vertex(pos.lerp(other.pos, t)); } /** diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Wedge.java b/src/main/java/eu/mihosoft/vrl/v3d/Wedge.java index 5a936048..06437fdc 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Wedge.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Wedge.java @@ -40,11 +40,17 @@ public Wedge(double w, double h, double d) { */ @Override public List toPolygons() { - CSG polygon = Extrude.points(new Vector3d(0, 0, h), // This is the extrusion depth - new Vector3d(0, 0), // All values after this are the points in the polygon - new Vector3d(d, 0), // Bottom right corner - new Vector3d(0, w)// upper right corner - ).roty(90).rotz(90); + CSG polygon=null; + try { + polygon = Extrude.points(new Vector3d(0, 0, h), // This is the extrusion depth + new Vector3d(0, 0), // All values after this are the points in the polygon + new Vector3d(d, 0), // Bottom right corner + new Vector3d(0, w)// upper right corner + ).roty(90).rotz(90); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } return polygon.getPolygons(); } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java b/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java index e1760912..182596e9 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java @@ -17,7 +17,11 @@ import java.text.DecimalFormat; import java.text.ParseException; import java.util.ArrayList; +import java.util.List; +import eu.mihosoft.vrl.v3d.ColinearPointsException; +import eu.mihosoft.vrl.v3d.Plane; +import eu.mihosoft.vrl.v3d.Polygon; import eu.mihosoft.vrl.v3d.Vector3d; import eu.mihosoft.vrl.v3d.Vertex; @@ -30,48 +34,13 @@ */ public class STLLoader { -// /** -// * Load the specified stl file and returns the result as a hash map, mapping -// * the object names to the corresponding CustomMesh objects. -// */ -// public static Map load(String stlfile) -// throws IOException { -// STLLoader sl = new STLLoader(); -// try { -// sl.parse(stlfile); -// } catch (RuntimeException e) { -// ////System.out.println("error reading " + sl.name); -// throw e; -// } -// return sl.meshes; -// } -// /** * Instantiates a new STL loader. */ -// private HashMap meshes; public STLLoader() { } - /** The line. */ - String line; - /** The in. */ - BufferedReader in; - - /** The vertices. */ - // attributes of the currently read mesh - private ArrayList vertices = new ArrayList<>(); - - /** The normal. */ - //private Vector3d normal = new Vector3d(0.0f, 0.0f, 0.0f); // to be used for file checking - - /** The fis. */ - private FileInputStream fis; - - /** The triangles. */ - private int triangles; -// private DecimalFormat decimalFormat = new DecimalFormat("0.0E0"); /** * Parses the. @@ -80,8 +49,8 @@ public STLLoader() { * @return the array list * @throws IOException Signals that an I/O exception has occurred. */ - public ArrayList parse(File f) throws IOException { - vertices.clear(); + public ArrayList parse(File f) throws IOException { + ArrayList polygons = new ArrayList<>(); // determine if this is a binary or ASCII STL // and send to the appropriate parsing method @@ -92,9 +61,9 @@ public ArrayList parse(File f) throws IOException { String[] words = line.trim().split("\\s+"); if (line.indexOf('\0') < 0 && words[0].equalsIgnoreCase("solid")) { ////// System.out.println("Looks like an ASCII STL"); - parseAscii(f); + parseAscii(f,polygons); br.close(); - return vertices; + return polygons; } br.close(); } catch (java.lang.NullPointerException ex) { @@ -108,16 +77,16 @@ public ArrayList parse(File f) throws IOException { byte[] buffer = new byte[84]; fs.read(buffer, 0, 84); fs.close(); - triangles = (int) (((buffer[83] & 0xff) << 24) | ((buffer[82] & 0xff) << 16) | ((buffer[81] & 0xff) << 8) + int triangles = (int) (((buffer[83] & 0xff) << 24) | ((buffer[82] & 0xff) << 16) | ((buffer[81] & 0xff) << 8) | (buffer[80] & 0xff)); if (((f.length() - 84) / 50) == triangles) { ////// System.out.println("Looks like a binary STL"); - parseBinary(f); - return vertices; + parseBinary(f,polygons, triangles); + return polygons; } // System.out.println("File is not a valid STL"); - return vertices; + return polygons; } /** @@ -125,13 +94,16 @@ public ArrayList parse(File f) throws IOException { * * @param f the f */ - private void parseAscii(File f) { + private void parseAscii(File f,ArrayList polygons) { + BufferedReader in=null; + String line=""; try { in = new BufferedReader(new FileReader(f)); } catch (FileNotFoundException e) { e.printStackTrace(); + return; } - vertices = new ArrayList<>(); + ArrayList vertices = new ArrayList<>(); try { Vector3d normal = new Vector3d(0, 0,0); while ((line = in.readLine()) != null) { @@ -141,8 +113,18 @@ private void parseAscii(File f) { double y = parseDouble(numbers[2]); double z = parseDouble(numbers[3]); Vector3d vertex = new Vector3d(x, y, z); - vertices.add(new Vertex(vertex, normal)); + vertices.add(new Vertex(vertex)); + if(vertices.size()==3) { + Plane pl = new Plane(normal, vertices); + try { + polygons.add(new Polygon(vertices, null, true, pl)); + } catch (ColinearPointsException e) { + System.out.println(e.getMessage()+ " STL Load Pruned "+vertices); + } + vertices.clear(); + } } else if (numbers[0].equals("facet") && numbers[1].equals("normal")) { + normal = new Vector3d(0, 0,0); normal.x = parseDouble(numbers[2]); normal.y = parseDouble(numbers[3]); normal.z = parseDouble(numbers[4]); @@ -160,11 +142,11 @@ private void parseAscii(File f) { * Parses the binary. * * @param f the f + * @param polygons2 */ - private void parseBinary(File f) { - vertices = new ArrayList(); + private void parseBinary(File f, ArrayList polygons,int triangles) { try { - fis = new FileInputStream(f); + FileInputStream fis = new FileInputStream(f); for (int h = 0; h < 84; h++) { fis.read();// skip the header bytes } @@ -173,6 +155,7 @@ private void parseBinary(File f) { for (int tb = 0; tb < 50; tb++) { tri[tb] = (byte) fis.read(); } + ArrayList vertices = new ArrayList(); Vector3d normal = new Vector3d(0, 0,0); normal.x = leBytesToFloat(tri[0], tri[1], tri[2], tri[3]); normal.y = leBytesToFloat(tri[4], tri[5], tri[6], tri[7]); @@ -183,13 +166,23 @@ private void parseBinary(File f) { double py = leBytesToFloat(tri[j + 4], tri[j + 5], tri[j + 6], tri[j + 7]); double pz = leBytesToFloat(tri[j + 8], tri[j + 9], tri[j + 10], tri[j + 11]); Vector3d p = new Vector3d(px, py, pz); - vertices.add(new Vertex(p, normal)); + vertices.add(new Vertex(p)); + if(vertices.size()==3) { + Plane pl = new Plane(normal, vertices); + try { + polygons.add(new Polygon(vertices, null, true, pl)); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + vertices.clear(); + } } } fis.close(); } catch (FileNotFoundException e) { e.printStackTrace(); - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); } } 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 5785b0b4..a2fdde27 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 @@ -34,6 +34,7 @@ package eu.mihosoft.vrl.v3d.ext.org.poly2tri; import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.ColinearPointsException; import eu.mihosoft.vrl.v3d.Debug3dProvider; import eu.mihosoft.vrl.v3d.Edge; import eu.mihosoft.vrl.v3d.Extrude; @@ -43,13 +44,19 @@ import eu.mihosoft.vrl.v3d.Transform; import eu.mihosoft.vrl.v3d.Vector3d; import eu.mihosoft.vrl.v3d.Vertex; +import javafx.collections.ModifiableObservableListBase; import javafx.scene.paint.Color; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Optional; +import javax.vecmath.Matrix4d; +import javax.vecmath.Quat4d; + import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; @@ -63,18 +70,31 @@ * @author Michael Hoffer <info@michaelhoffer.de> */ public class PolygonUtil { - private static final double triangleScale = 1.0/Plane.getEPSILON()*10; + public static final double triangleScale = 1; private static IPolygonRepairTool repair = concave1 -> { ArrayList edges = new ArrayList(); ArrayList toRemove = new ArrayList(); - List v = concave1.getVertices(); + HashMap replace = new HashMap<>(); + List v1 = concave1.getVertices(); ArrayList modifiable = new ArrayList(); - modifiable.addAll(v); - for (int i = 0; i < v.size(); i++) { - Edge e = new Edge(v.get(i), v.get((i + 1) % v.size())); + + for (int i = 0; i < v1.size(); i++) { + boolean match = false; + for (int j = 0; j < modifiable.size(); j++) { + if (v1.get(i).pos.test(modifiable.get(j).pos, Plane.getEPSILON())) { + match = true; + } + } + if (!match) + modifiable.add(v1.get(i)); + else + System.err.println("Duplicate Point! " + v1.get(i)); + } + for (int i = 0; i < modifiable.size(); i++) { + Edge e = new Edge(modifiable.get(i), modifiable.get((i + 1) % modifiable.size())); double l = e.length(); - if (l < 0.0001) + if (l < 0.001) System.out.println("Length of edge is " + l); // System.out.println(e); edges.add(e); @@ -98,8 +118,7 @@ public class PolygonUtil { } } if (start == 0) { - CSG.getProgressMoniter().progressUpdate((i * (edges.size() / perThread)), - edges.size(), + CSG.getProgressMoniter().progressUpdate((i * (edges.size() / perThread)), edges.size(), " Repairing intersecting polygon (Hint in Inkscape->Path->Simplify): found # " + toRemove.size(), null); @@ -110,25 +129,25 @@ public class PolygonUtil { if (i == j) continue; Edge test2 = edges.get(j); - boolean b = Edge.falseBoundaryEdgeSharedWithOtherEdge(test, test2); + Vertex v = Edge.falseBoundaryEdgeSharedWithOtherEdge(test, test2); Optional cross = test.getCrossingPoint(test2); boolean c = cross.isPresent() && i < j; if (c) { - int x; - for (x = i + 1; x < j; x++) { - toRemove.add(v.get(x)); + int x = i + 1; + replace.put(modifiable.get(x), new Vertex(cross.get())); + for (; x < j; x++) { + toRemove.add(modifiable.get(x)); } // System.out // .println("Edges cross! " + cross.get() + // " pruned "+(x-i)); } - if (b) { - System.out.println("\n\nFalse Boundary " + test + " \n " + test2); + if (v != null) { + // System.out.println("\n\nFalse Boundary " + test + " \n " + test2); try { - Vertex vBad = test.getCommonPoint(test2); - toRemove.add(vBad); + // toRemove.add(v); } catch (Exception e) { - //throw new RuntimeException(e); + // throw new RuntimeException(e); return; } } @@ -139,16 +158,37 @@ public class PolygonUtil { threads.add(t); t.start(); } - for (Thread t : threads) + for (int i = 0; i < threads.size(); i++) { + Thread t = threads.get(i); try { t.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } + System.out.println("Reapir thread finished " + (i + 1) + " of " + threads.size()); + } + for (int j = 0; j < modifiable.size(); j++) { + Vertex key = modifiable.get(j); + + Vertex vertex = replace.get(key); + if (vertex != null) { + toRemove.remove(key); + modifiable.set(j, vertex); + } + } for (Vertex vr : toRemove) modifiable.remove(vr); - return new Polygon(modifiable, concave1.getStorage(), false, concave1.getPlane()); + if (modifiable.size() > 2) { + try { + Polygon polygon = new Polygon(modifiable, concave1.getStorage(), false, concave1.getPlane()); + return polygon; + } catch (ColinearPointsException e) { + System.out.println(" Pruning polygon in repair " + concave1 + " to " + modifiable); + + } + } + throw new ColinearPointsException("Fix failed!"); }; @@ -320,7 +360,7 @@ private static boolean doLineSegmentsIntersect(Vector3d p1, Vector3d p2, Vector3 private static int orientation(double[] p, double[] q, double[] r) { double val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]); - if (Math.abs(val) < 1e-10) + if (Math.abs(val) < Plane.getEPSILON()) return 0; // collinear return (val > 0) ? 1 : 2; // clockwise or counterclockwise } @@ -400,7 +440,7 @@ private static boolean doesLineIntersectPolygon(Vector3d lineStart, Vector3d lin double denominatorTerm = normal.dot(lineDirection); // If the line is parallel to the plane, no intersection - if (Math.abs(denominatorTerm) < 1e-10) { + if (Math.abs(denominatorTerm) < Plane.getEPSILON()) { return false; } @@ -462,14 +502,42 @@ private PolygonUtil() { // // return result; // } + /** - * Concave to convex. - * - * @param incoming the concave - * @return the list + * Calculates a quaternion-based transform that rotates `from` vector to align + * with (0,0,1). + * + * @throws ColinearPointsException */ - public static ArrayList concaveToConvex(Polygon incoming) { - return concaveToConvex(incoming, true); + private static Transform calculateQuaternionTransform(Polygon concave) throws ColinearPointsException { + // Normalize inputs + Vector3d u = concave.getPlane().getNormal(); + Vector3d v = new Vector3d(0, 0, 1); + + double dot = u.dot(v); + // If u ≈ v → identity + if (dot > 1.0 - Plane.getEPSILON()) { + return new Transform(); + } + if (dot < -1.0 + Plane.getEPSILON()) { + return new Transform().rotX(180); + } + double aboutZ = Math.toDegrees(Math.atan2(u.y, u.x)); + + Transform transform1 = new Transform().rotZ(aboutZ); + Vector3d u2 = u.transformed(transform1); + double aboutY = Math.toDegrees(Math.atan2(u2.x, u2.z)); + Transform transform = new Transform().rotY(aboutY).apply(transform1); + Vector3d u3 = u.transformed(transform).normalized(); + + Polygon test = concave.transformed(transform); + double abs = Math.abs(test.plane.getNormal().z); + if (1 - abs > 0.1) { + System.out.println("Error with " + test); + // Plane p = Plane.createFromPoints(test.getVertices()); + new ColinearPointsException("Failed to reorent the polygon for processing! z off by "+abs+" "+test.plane.getNormal()).printStackTrace(); + } + return transform; } /** @@ -477,191 +545,229 @@ public static ArrayList concaveToConvex(Polygon incoming) { * * @param incoming the concave * @return the list + * @throws ColinearPointsException */ - public static ArrayList concaveToConvex(Polygon incoming, boolean toCCW) { + public static ArrayList triangulatePolygon(Polygon incoming) throws ColinearPointsException { ArrayList result = new ArrayList<>(); if (incoming == null) return result; if (incoming.getVertices().size() < 3) return result; - Polygon concave = incoming; - Vector3d normalOfPlane = incoming.getPlane().getNormal(); - boolean reorent = normalOfPlane.z < 1.0 - Plane.getEPSILON(); - Transform orentationInv = null; + Polygon tmp = incoming; + Vector3d normalOfPlane = incoming.getPlane().getNormal().clone(); + normalOfPlane.normalize(); + boolean reorient = Math.abs(normalOfPlane.z - 1.0) > Plane.getEPSILON(); + Transform orientationInv = null; boolean debug = false; - Vector3d normal = concave.getPlane().getNormal().clone(); - - if (reorent) { - - double degreesToRotate = Math.toDegrees(Math.atan2(normalOfPlane.x, normalOfPlane.z)); - Transform orentation = new Transform().roty(degreesToRotate); - - Polygon tmp = incoming.transformed(orentation); - - Vector3d tmpnorm = tmp.getPlane().getNormal(); - double degreesToRotate2 = 90 + Math.toDegrees(Math.atan2(tmpnorm.z, tmpnorm.y)); - Transform orentation2 = orentation.rotx(degreesToRotate2);// th triangulation function needs - // the polygon on the xy plane - if (debug) { - Debug3dProvider.clearScreen(); - Debug3dProvider.addObject(incoming); - } - concave = incoming.transformed(orentation2); - orentationInv = orentation2.inverse(); - if (concave.getPlane().getNormal().z < 0) { - Transform orentation3 = orentation2.rotx(180); - concave = incoming.transformed(orentation3); - orentationInv = orentation3.inverse(); - } - // System.err.println("Re-orenting polygon " + concave); - Polygon transformed = concave.transformed(orentationInv); - // System.err.println("corrected-orenting polygon " + transformed); - checkForValidPolyOrentation(normal, transformed); + if (reorient) { + Transform orientation = calculateQuaternionTransform(incoming); + tmp = incoming.transformed(orientation); + orientationInv = orientation.inverse(); } - boolean cw = !Extrude.isCCW(concave); - if (cw && toCCW) - concave = Extrude.toCCW(concave); - if (debug) { - Debug3dProvider.clearScreen(); - Debug3dProvider.addObject(concave); - // Debug3dProvider.clearScreen(); - } + boolean cw = false; +// if(!Extrude.isCCW(tmp)) { +// ArrayList v =new ArrayList(tmp.getVertices()); +// Collections.reverse(v); +// tmp = new Polygon(v, tmp.getStorage(), false, null); +// } + Polygon concave = tmp; double zplane = concave.getVertices().get(0).pos.z; - +// for (Vector3d v : concave.getPoints()) { +// double abs = Math.abs(zplane - v.z); +// if (abs > 0.1) { +// new RuntimeException("Failed to triangulate, points must be coplainer, delta: " + abs) +// .printStackTrace(); +// } +// } try { - makeTriangles(concave, cw, result, zplane, normal, debug, orentationInv, reorent, incoming.getColor()); + if (concave.size() == 3) { + result.add(concave); + } else + makeTriangles(concave, cw, result, zplane, normalOfPlane, debug, orientationInv, reorient, + incoming.getColor()); } catch (java.lang.IllegalStateException ex) { -// ex.printStackTrace(); -// throw new RuntimeException(ex); - int start = concave.getVertices().size(); - concave = repairOverlappingEdges(concave); - int end = concave.getVertices().size(); - makeTriangles(concave, cw, result, zplane, normal, debug, orentationInv, reorent, incoming.getColor()); - System.out.println("Repaired the polygon! pruned " + (start - end)); + + Polygon repaired = repairOverlappingEdges(concave); + int end = repaired.getVertices().size(); + if (end == 3) { + result.add(repaired); + } else { + try { + makeTriangles(repaired, cw, result, zplane, normalOfPlane, debug, orientationInv, reorient, + incoming.getColor()); + } catch (Exception e) { + makeTrianglesInternal(repaired, cw, result, zplane, normalOfPlane, debug, orientationInv, reorient, + incoming.getColor()); + } + } + + if (reorient) { + repaired = repaired.transform(orientationInv); + } + incoming.setVertices(repaired.getVertices()); + } return result; } - private static Polygon repairOverlappingEdges(Polygon concave) { + private static Polygon repairOverlappingEdges(Polygon concave) throws ColinearPointsException { return getRepair().repairOverlappingEdges(concave); } -// private static void makeTrianglespoly2triMethod(List result, Polygon concave, boolean cw, boolean reorent, -// Transform orentationInv, Vector3d normal) { -// ArrayList points = new ArrayList(); -// for (Vector3d v : concave.getPoints()) { -// points.add(new PolygonPoint(v.x, v.y, v.z)); -// -// } -// org.poly2tri.geometry.polygon.Polygon poly2tri = new org.poly2tri.geometry.polygon.Polygon(points); -// try { -// Poly2Tri.triangulate(poly2tri); -// List triangles = poly2tri.getTriangles(); -// } catch (Exception e) { -// e.printStackTrace(); -// throw new RuntimeException(e); -// } -// -// } - -// private static void makeTrianglespoly2triMethod(Polygon incoming, List result, Polygon concave, boolean cw) { -// // System.err.println("Failed to triangulate "+concave); -// Polygon p = incoming; -// int size = p.getVertices().size(); -// int sizeOfVect = 3; -// double[] points = new double[size * sizeOfVect]; -// +// private static void fourPointSpecialCase(Polygon concave, boolean cw, List result, double zplane, +// Vector3d normal, boolean debug, Transform orentationInv, boolean reorent, Color color) { +// List points = concave.getPoints(); +// int size = points.size(); // for (int i = 0; i < size; i++) { -// Vector3d v = p.getVertices().get(i).pos; -// points[i * sizeOfVect + 0] = v.x; -// points[i * sizeOfVect + 1] = v.y; -// points[i * sizeOfVect + 2] = v.z; -// } -// List triangles = Earcut.earcut(points, null, sizeOfVect); -// int numTri = triangles.size() / 3; +// // Get first two points to establish a direction vector +// Vector3d p1 = points.get(i); +// Vector3d p2 = points.get((i + 1) % size); // -// for (int i = 0; i < numTri; i++) { -// int p1 = triangles.get(i * 3 + 0); -// int p2 = triangles.get(i * 3 + 1); -// int p3 = triangles.get(i * 3 + 2); -// ArrayList tripoints = new ArrayList(); -// tripoints.add(p.getVertices().get(p1)); -// tripoints.add(p.getVertices().get(p2)); -// tripoints.add(p.getVertices().get(p3)); +// // Calculate the direction vector between first two points +// Vector3d direction = p1.minus(p2); +// // Normalize the direction vector +// double length = direction.length(); +// double ep = Plane.getEPSILON(); +// if (length < ep) { // If points are effectively identical +// continue; +// } +// direction.normalize(); +// Vector3d p3 = points.get((i + 2) % size); +// +// // Calculate cross product +// Vector3d cross = direction.cross(p1.minus(p3)); +// // Calculate magnitude of cross product +// double magnitude = Math.abs(cross.length()); // -// if (tripoints.size() == 3) { +// // If magnitude is not close to zero, points are not collinear +// if (magnitude > ep) { // -// Polygon poly = new Polygon(tripoints, p.getStorage(), false, p.getPlane()); -// boolean b = !Extrude.isCCW(poly); -// if (cw != b) { -// // System.err.println("Triangle not matching incoming"); -// Collections.reverse(tripoints); -// poly = new Polygon(tripoints, p.getStorage(), true, p.getPlane()); -// b = !Extrude.isCCW(poly); -// if (cw != b) { -// // com.neuronrobotics.sdk.common.Log.error("Error, polygon is reversed!"); +// Plane normal2 = concave.plane; +// try { +// Polygon one = new Polygon( +// new ArrayList(Arrays.asList(new Vertex(p1), new Vertex(p2), new Vertex(p3))), +// concave.getStorage(), true, normal2); +// if (reorent) { +// one = one.transform(orentationInv); // } +// one.setColor(color); +// result.add(one); +// } catch (ColinearPointsException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); // } -//// if (reorent) { -//// poly = poly.transform(orentationInv); -//// -//// poly = checkForValidPolyOrentation(normal, poly); -//// } -// result.add(poly); +// try { +// Polygon two = new Polygon( +// new ArrayList(Arrays.asList(new Vertex(points.get((i + 3) % size)), +// new Vertex(points.get((i + 4) % size)), new Vertex(points.get((i + 5) % size)))), +// concave.getStorage(), true, normal2); +// if (reorent) { +// two = two.transform(orentationInv); +// } +// two.setColor(color); +// result.add(two); +// } catch (ColinearPointsException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// +// +// return; // } // } -// // check result for overlapping polygons -//// int startSize = result.size(); -//// ArrayList aP = new ArrayList<>(); -//// ArrayList bP= new ArrayList<>(); -//// aP.addAll(result); -//// bP.addAll(result); -//// Node a = new Node(aP); -//// Node b = new Node(bP); -//// a.clipTo(b); -//// b.clipTo(a); -//// result = new ArrayList<>();; -//// result.addAll(a.allPolygons()); -// // -// return;// no overlapping polygons to remove! -// // } - private static Polygon checkForValidPolyOrentation(Vector3d normal, Polygon poly) { - Vector3d normal2 = poly.getPlane().getNormal(); - Vector3d minus = normal.minus(normal2); - double length = minus.length(); - double d = 1.0e-3; - if (length > d * 10 && length < (2 - d)) { - List points = poly.getPoints(); - List r = new ArrayList<>(points); - Collections.reverse(r); - Polygon fromPoints = Polygon.fromPoints(r); - double l = normal.minus(fromPoints.getPlane().getNormal()).length(); - if (l > d * 10 && l < (2 - d)) { -// throw new RuntimeException( -// "Error, the reorentation of the polygon resulted in a different normal than the triangles produced from it"); - } else { - poly = fromPoints; + private static void makeTrianglesInternal(Polygon concave, boolean cw, List result, double zplane, + Vector3d normal, boolean debug, Transform orentationInv, boolean reorent, Color color) + throws ColinearPointsException { + ArrayList points = new ArrayList<>(concave.getPoints()); + double z = concave.getVertices().get(0).pos.z; +// for (Vector3d v : points) { +// if (Math.abs(z - v.z) > 0.1) { +// throw new ColinearPointsException("Failed to triangulate, points must be coplainer"); +// } +// } + while (points.size() > 0) { + int size = points.size(); + for (int i = 0; i < size; i++) { + // Get first two points to establish a direction vector + Vector3d p1 = points.get(i); + Vector3d p2 = points.get((i + 1) % size); +// if (Math.abs(z - p1.z) > 0.1) { +// throw new ColinearPointsException("Failed to triangulate, points must be coplainer"); +// } +// if (Math.abs(z - p2.z) > 0.1) { +// throw new ColinearPointsException("Failed to triangulate, points must be coplainer"); +// } + // Calculate the direction vector between first two points + Vector3d direction = p1.minus(p2); + // Normalize the direction vector + double length = direction.length(); + double ep = Plane.getEPSILON(); + if (length < ep) { // If points are effectively identical + continue; + } + direction.normalize(); + Vector3d p3 = points.get((i + 2) % size); + double abs = Math.abs(z - p3.z); + if (abs > 0.1) { + new RuntimeException("Failed to triangulate, points must be coplainer").printStackTrace(); + } + // Calculate cross product + Vector3d cross = direction.cross(p1.minus(p3)); + // Calculate magnitude of cross product + double magnitude = Math.abs(cross.length()); + + // If magnitude is not close to zero, points are not collinear + if (magnitude > ep) { + try { + Plane normal2 = concave.plane; + points.remove(p2); + ArrayList vertices = new ArrayList( + Arrays.asList(new Vertex(p1.clone()), new Vertex(p2.clone()), new Vertex(p3.clone()))); + if (!Extrude.isCCW(vertices)) { + Collections.reverse(vertices); + } + Polygon one = new Polygon(vertices, concave.getStorage(), true, normal2.clone()); + if (reorent) { + one = one.transform(orentationInv); + } + one.setColor(color); + result.add(one); + } catch (ColinearPointsException ex) { + System.out.println(ex.getMessage() + " Triangulation Pruned point " + p2); + } + if (points.size() == 2) { + points.clear(); + return; + } + break; + } else { + if (points.size() == 3) + return;// skip a colinear final segment + } + } + if (size == points.size()) { + if (result.size() == 0) + throw new ColinearPointsException("Triangulation Internal Error! All remaining points are colinear!"); + return; } } - return poly; + } - private static Geometry makeTriangles(Polygon concave, boolean cw, List result, double zplane, - Vector3d normal, boolean debug, Transform orentationInv, boolean reorent, Color color) { - Geometry triangles; + private static void makeTriangles(Polygon concave, boolean cw, List result, double zplane, Vector3d normal, + boolean debug, Transform orentationInv, boolean reorent, Color color) { + Polygon toTri = concave; -// if(cw) { -// toTri=Extrude.toCCW(concave); -// } + Coordinate[] coordinates = new Coordinate[toTri.getVertices().size() + 1]; for (int i = 0; i < toTri.getVertices().size(); i++) { Vector3d v = toTri.getVertices().get(i).pos; @@ -672,9 +778,9 @@ private static Geometry makeTriangles(Polygon concave, boolean cw, List v.z * triangleScale); // use the default factory, which gives full double-precision Geometry geom = new GeometryFactory().createPolygon(coordinates); - triangles = ConstrainedDelaunayTriangulator.triangulate(geom); + Geometry triangles = ConstrainedDelaunayTriangulator.triangulate(geom); ArrayList triPoints = new ArrayList<>(); - + Plane p1 = concave.getPlane().clone(); for (int i = 0; i < triangles.getNumGeometries(); i++) { Geometry tri = triangles.getGeometryN(i); Coordinate[] coords = tri.getCoordinates(); @@ -684,39 +790,51 @@ private static Geometry makeTriangles(Polygon concave, boolean cw, List for (int j = 0; j < 3; j++) { Coordinate tp = coords[j]; Vector3d pos = new Vector3d(tp.getX() / triangleScale, tp.getY() / triangleScale, zplane); - triPoints.add(new Vertex(pos, normal)); + Vertex e = null;// new Vertex(pos); + for (int x = 0; x < toTri.getVertices().size(); x++) { + Vector3d test = toTri.getVertices().get(x).pos; + double diffX = Math.abs( test.x-pos.x); + double diffY = Math.abs(test.y-pos.y); + if (diffY(); } else { @@ -724,8 +842,6 @@ private static Geometry makeTriangles(Polygon concave, boolean cw, List } } } - - return triangles; } public static IPolygonRepairTool getRepair() { diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java b/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java index 6d21f085..5d886cf6 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java @@ -7,6 +7,7 @@ import eu.mihosoft.vrl.v3d.CSG; import eu.mihosoft.vrl.v3d.CSGClient; +import eu.mihosoft.vrl.v3d.ColinearPointsException; import eu.mihosoft.vrl.v3d.Vector3d; import eu.mihosoft.vrl.v3d.Polygon; import eu.mihosoft.vrl.v3d.PropertyStorage; @@ -88,7 +89,12 @@ public static CSG hull(List points, PropertyStorage storage) { vertices.add(points.get(hull.getVertexPointIndices()[i])); } - polygons.add(Polygon.fromPoints(vertices, storage)); + try { + polygons.add(Polygon.fromPoints(vertices, storage)); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } vertices.clear(); } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGExporter.java b/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGExporter.java index 2dc93345..d2609ca4 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGExporter.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGExporter.java @@ -8,6 +8,7 @@ import java.util.Arrays; import java.util.List; import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.ColinearPointsException; import eu.mihosoft.vrl.v3d.Polygon; import eu.mihosoft.vrl.v3d.Slice; import eu.mihosoft.vrl.v3d.Transform; @@ -170,12 +171,12 @@ private static void write(String output, File defaultDir) throws IOException{ bw.write(output); bw.close(); } - public static void export(CSG currentCsg, File defaultDir) throws IOException { + public static void export(CSG currentCsg, File defaultDir) throws IOException, ColinearPointsException { SVGExporter svg = new SVGExporter(); addCsg(currentCsg,svg); write(svg.make(), defaultDir); } - public static void export(List currentCsg, File defaultDir) throws IOException { + public static void export(List currentCsg, File defaultDir) throws IOException, ColinearPointsException { try { eu.mihosoft.vrl.v3d.JavaFXInitializer.go(); } catch (Throwable t) { @@ -198,7 +199,7 @@ public static void export(List currentCsg, File defaultDir) throws IOExcept //System.out.println("ERROR No UI engine availible, SVG slicing is GPU accelerated and will not work"); } } - private static void addCsg(CSG currentCsg, SVGExporter svg) throws IOException { + private static void addCsg(CSG currentCsg, SVGExporter svg) throws IOException, ColinearPointsException { svg.setName(currentCsg.getName()); for(Transform slicePlane:currentCsg.getSlicePlanes()){ List polygons = Slice.slice(currentCsg.prepMfg(), slicePlane, 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 2e86ee37..3f909517 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGLoad.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGLoad.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.net.URI; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -25,6 +26,8 @@ import org.apache.batik.dom.svg.SVGItem; import org.apache.batik.util.XMLResourceDescriptor; import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.svg.SVGImageElement; @@ -33,11 +36,15 @@ import com.piro.bezier.BezierPath; import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.ColinearPointsException; import eu.mihosoft.vrl.v3d.Edge; import eu.mihosoft.vrl.v3d.Extrude; +import eu.mihosoft.vrl.v3d.Plane; import eu.mihosoft.vrl.v3d.Polygon; +import eu.mihosoft.vrl.v3d.PropertyStorage; import eu.mihosoft.vrl.v3d.Transform; import eu.mihosoft.vrl.v3d.Vector3d; +import eu.mihosoft.vrl.v3d.ext.org.poly2tri.PolygonUtil; import javafx.scene.paint.Color; // CSG.setDefaultOptType(CSG.OptType.CSG_BOUND); @@ -86,7 +93,13 @@ private double toPx(String value) { return Double.parseDouble(split[0]) / units.get(key); } } - return Double.parseDouble(value); + try { + return Double.parseDouble(value); + } catch (NumberFormatException ex) { + // if the size is not defined, then the scale can not be computed, using + // internal data + throw ex; + } } private void setScale(double value) { @@ -229,12 +242,6 @@ public SVGLoad(String data) throws IOException { tmpsvg.deleteOnExit(); } - public ArrayList extrude(double thickness) throws IOException { - - return extrude(thickness, 0.005); - - } - public static ArrayList extrude(File f, double thickness) throws IOException { return new SVGLoad(f.toURI()).extrude(thickness); @@ -253,10 +260,10 @@ public static HashMap> toPolygons(File f) throws IOExcepti } - public HashMap> toPolygons(double resolution) { + public HashMap> toPolygons() { if (polygonByLayers == null) try { - loadAllGroups(resolution, new Transform()); + loadAllGroups(new Transform()); } catch (Exception e) { // Auto-generated catch block e.printStackTrace(); @@ -265,13 +272,9 @@ public HashMap> toPolygons(double resolution) { return getPolygonByLayers(); } - public HashMap> toPolygons() { - return toPolygons(0.001); - } - - public static ArrayList extrude(File f, double thickness, double resolution) throws IOException { - return new SVGLoad(f.toURI()).extrude(thickness, resolution); - } +// public static ArrayList extrude(File f, double thickness) throws IOException { +// return new SVGLoad(f.toURI()).extrude(thickness); +// } public static ArrayList extrude(URI uri, double thickness) throws IOException { @@ -279,27 +282,37 @@ public static ArrayList extrude(URI uri, double thickness) throws IOExcepti } - public static ArrayList extrude(URI uri, double thickness, double resolution) throws IOException { - return new SVGLoad(uri).extrude(thickness, resolution); - } - - private void loadAllGroups(double resolution, Transform startingFrame) { + private void loadAllGroups(Transform startingFrame) { - NodeList pn = getSVGDocument().getDocumentElement().getChildNodes();// .getElementsByTagName("g"); + Element documentElement = getSVGDocument().getDocumentElement(); + NodeList pn = documentElement.getChildNodes();// .getElementsByTagName("g"); + String viewbox = documentElement.getAttribute("viewBox"); try { - String hval = getSVGDocument().getDocumentElement().getAttribute("height"); - String wval = getSVGDocument().getDocumentElement().getAttribute("width"); - String viewbox = getSVGDocument().getDocumentElement().getAttribute("viewBox"); double viewW = Double.parseDouble(viewbox.split(" ")[2]); - setScale(1);// use to compute bounds - height = toMM(hval); - width = toMM(wval); - double value = viewW / width; - //// com.neuronrobotics.sdk.common.Log.error("Page size height = "+height+" - //// width ="+width+" with scale "+(int)(value*25.4)+" DPI "); - setScale(value); - } catch (Throwable t) { - t.printStackTrace(); + double viewH = Double.parseDouble(viewbox.split(" ")[3]); + try { + NamedNodeMap all = documentElement.getAttributes(); +// for(int i=0;i 1) { // println "Seperated complex: " - loadSingle(sectionedPart, resolution, startingFrame, encapsulatingLayer, c); + loadSingle(sectionedPart, startingFrame, encapsulatingLayer, c); } } } @@ -621,12 +632,11 @@ public static boolean isCCW(Polygon polygon) { return runningTotal < 0; } - private void loadSingle(String code, double resolution, Transform startingFrame, String encapsulatingLayer, - Color c) { - if(encapsulatingLayer==null) + private void loadSingle(String code, Transform startingFrame, String encapsulatingLayer, Color c) { + if (encapsulatingLayer == null) throw new RuntimeException("Layer Name can not be null"); // println code - BezierPath path = new BezierPath(); + BezierPath path = new BezierPath(5); path.parsePathString(code); ArrayList p = path.evaluate(); @@ -637,23 +647,34 @@ private void loadSingle(String code, double resolution, Transform startingFrame, point.transform(new Transform().rotZ(-180)); point.transform(new Transform().rotY(180)); } - + // //com.neuronrobotics.sdk.common.Log.error(" Path " + code); - Polygon poly = Polygon.fromPoints(p); - boolean hole = !Extrude.isCCW(poly); - if (getPolygonByLayers() == null) - setPolygonByLayers(new HashMap>()); - if (getPolygonByLayers().get(encapsulatingLayer) == null) - getPolygonByLayers().put(encapsulatingLayer, new ArrayList()); - List list = getPolygonByLayers().get(encapsulatingLayer); - - poly = Polygon.fromPoints(Extrude.toCCW(poly.getPoints())); - poly.setHole(hole); - if (c != null) { - colors.put(poly, c); - poly.setColor(c); + // Polygon poly = Polygon.fromPoints(p); + if (p.size() > 2) { + if (getPolygonByLayers() == null) + setPolygonByLayers(new HashMap>()); + if (getPolygonByLayers().get(encapsulatingLayer) == null) + getPolygonByLayers().put(encapsulatingLayer, new ArrayList()); + try { + boolean hole = !Extrude.isCCWv3d(p); + if (hole) + Collections.reverse(p); + List list = getPolygonByLayers().get(encapsulatingLayer); + // Polygon poly = Polygon.fromPoints(p , new PropertyStorage(), new Plane(new + // Vector3d(0, 0, 1),p.get(0)) , true); + Polygon poly = Polygon.fromPoints(p); + PolygonUtil.triangulatePolygon(poly); + poly.setHole(hole); + if (c != null) { + colors.put(poly, c); + poly.setColor(c); + } + list.add(poly); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } - list.add(poly); } @@ -668,21 +689,21 @@ public List getLayers() { } public CSG extrudeLayerToCSG(double t, String layer) { - CSG unionAll = CSG.unionAll(extrudeLayers(t, 0.01, layer).get(layer)); + CSG unionAll = CSG.unionAll(extrudeLayers(t, layer).get(layer)); unionAll.setName(layer); return unionAll; } public ArrayList extrudeLayer(double t, String layer) { - return extrudeLayers(t, 0.01, layer).get(layer); + return extrudeLayers(t, layer).get(layer); } public HashMap> extrudeLayers(double t) { - return extrudeLayers(t, 0.01, null); + return extrudeLayers(t, null); } - public HashMap> extrudeLayers(double t, double resolution, String targetLayer) { + public HashMap> extrudeLayers(double t, String targetLayer) { this.thickness = t; if (thickness < 0) { @@ -692,7 +713,7 @@ public HashMap> extrudeLayers(double t, double resolution negativeThickness = false; } - toPolygons(0.001); + toPolygons(); for (String key : getPolygonByLayers().keySet()) { if (targetLayer != null) @@ -703,42 +724,47 @@ public HashMap> extrudeLayers(double t, double resolution } ArrayList parts = csgByLayers.get(key); parts.clear(); - boolean b = CSG.isPreventNonManifoldTriangles(); - CSG.setPreventNonManifoldTriangles(false); + for (Polygon p : getPolygonByLayers().get(key)) { - boolean isHole =p.isHole(); - CSG newbit; try { - newbit = Extrude.getExtrusionEngine().extrude(new Vector3d(0, 0, thickness), p); - if (negativeThickness) { - newbit = newbit.toZMax(); - } - if (colors.get(p) != null) { - newbit.setColor(colors.get(p)); - } - if(isHole) { - //newbit=newbit.movez(negativeThickness?0.5:-0.5); - newbit.setIsHole(true); + PolygonUtil.triangulatePolygon(p); + boolean isHole = p.isHole(); + CSG newbit; + try { + newbit = Extrude.getExtrusionEngine().extrude(new Vector3d(0, 0, thickness + (isHole ? 1 : 0)), + p); + if (negativeThickness) { + newbit = newbit.toZMax(); + } + if (colors.get(p) != null) { + newbit.setColor(colors.get(p)); + } + if (isHole) { + // newbit=newbit.movez(negativeThickness?0.5:-0.5); + newbit.setIsHole(true); + } + parts.add(newbit); + } catch (Exception ex) { + ex.printStackTrace(); } - newbit.triangulate(); - parts.add(newbit); - } catch (Exception ex) { - ex.printStackTrace(); + } catch (ColinearPointsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } + } - CSG.setPreventNonManifoldTriangles(b); } return csgByLayers; } - + public Color getColor(Polygon p) { return colors.get(p); } - public ArrayList extrude(double t, double resolution) throws IOException { + public ArrayList extrude(double t) throws IOException { - HashMap> layers = extrudeLayers(t, resolution, null); + HashMap> layers = extrudeLayers(t, null); ArrayList all = new ArrayList(); for (String key : layers.keySet()) { all.addAll(layers.get(key)); diff --git a/src/main/java/eu/mihosoft/vrl/v3d/thumbnail/ThumbnailImage.java b/src/main/java/eu/mihosoft/vrl/v3d/thumbnail/ThumbnailImage.java index da6b278d..08f133dd 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/thumbnail/ThumbnailImage.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/thumbnail/ThumbnailImage.java @@ -30,6 +30,7 @@ public class ThumbnailImage { private static CullFace cullFaceValue = CullFace.BACK; + private static int ImageSize=1000; public static Bounds getSellectedBounds(List incoming) { Vector3d min = null; @@ -129,18 +130,8 @@ public static WritableImage get(List c) { 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 scene = new Scene(root, getImageSize(), getImageSize(), true, SceneAntialiasing.BALANCED); scene.setFill(Color.TRANSPARENT); scene.setCamera(camera); @@ -155,7 +146,7 @@ public static WritableImage get(List c) { camera.setFarClip(9000.0); // Set the far clip plane // Create the WritableImage first - WritableImage snapshot = new WritableImage(i, i); + WritableImage snapshot = new WritableImage(getImageSize(), getImageSize()); root.snapshot(params, snapshot); @@ -168,7 +159,7 @@ public static Thread writeImage(CSG incoming, File toPNG) { return writeImage(bits, toPNG); } - public static Thread writeImage(ArrayList incoming, File toPNG) { + public static Thread writeImage(List incoming, File toPNG) { Thread t = new Thread(new Runnable() { WritableImage img = null; @@ -209,4 +200,12 @@ public static CullFace getCullFaceValue() { public static void setCullFaceValue(CullFace cullFaceValue) { ThumbnailImage.cullFaceValue = cullFaceValue; } + + public static int getImageSize() { + return ImageSize; + } + + public static void setImageSize(int imageSize) { + ImageSize = imageSize; + } } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/ConcavePolygonExtrusionTest.java b/src/test/java/eu/mihosoft/vrl/v3d/ConcavePolygonExtrusionTest.java index cbcf8fc7..1fc32c5d 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/ConcavePolygonExtrusionTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/ConcavePolygonExtrusionTest.java @@ -227,7 +227,7 @@ public class ConcavePolygonExtrusionTest { new Vector3d(39.41316604614258, -9.238319396972656, 0.0) }; @Test - public void test() { + public void test() throws ColinearPointsException { ArrayList H_points = new ArrayList(Arrays.asList(helvetica_H)); ArrayList e_points = new ArrayList(Arrays.asList(helvetica_e)); diff --git a/src/test/java/eu/mihosoft/vrl/v3d/EdgeIntersectionTest.java b/src/test/java/eu/mihosoft/vrl/v3d/EdgeIntersectionTest.java index 9cb1c3de..47fd1888 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/EdgeIntersectionTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/EdgeIntersectionTest.java @@ -98,15 +98,15 @@ private static void createIntersectionTest( Vector3d expectedPoint) { Edge e1 = new Edge( new Vertex( - e1p1, Vector3d.Z_ONE), + e1p1), new Vertex( - e1p2, Vector3d.Z_ONE)); + e1p2)); Edge e2 = new Edge( new Vertex( - e2p1, Vector3d.Z_ONE), + e2p1), new Vertex( - e2p2, Vector3d.Z_ONE)); + e2p2)); Optional closestPointResult = e1.getIntersection(e2); @@ -139,15 +139,15 @@ private static void createClosestPointTest( Vector3d expectedPoint) { Edge e1 = new Edge( new Vertex( - e1p1, Vector3d.Z_ONE), + e1p1), new Vertex( - e1p2, Vector3d.Z_ONE)); + e1p2)); Edge e2 = new Edge( new Vertex( - e2p1, Vector3d.Z_ONE), + e2p1), new Vertex( - e2p2, Vector3d.Z_ONE)); + e2p2)); Optional closestPointResult = e1.getClosestPoint(e2); diff --git a/src/test/java/eu/mihosoft/vrl/v3d/HoleDetectionTest.java b/src/test/java/eu/mihosoft/vrl/v3d/HoleDetectionTest.java index 5e872355..1dce702a 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/HoleDetectionTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/HoleDetectionTest.java @@ -21,9 +21,10 @@ public class HoleDetectionTest { /** * Hole detection test. + * @throws ColinearPointsException */ @Test - public void holeDetectionTest() { + public void holeDetectionTest() throws ColinearPointsException { // one polygon with one hole Polygon p1 = Polygon.fromPoints( diff --git a/src/test/java/eu/mihosoft/vrl/v3d/SVGLoadTest.java b/src/test/java/eu/mihosoft/vrl/v3d/SVGLoadTest.java index c1192551..d9ea4d37 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/SVGLoadTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/SVGLoadTest.java @@ -4,10 +4,13 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; +import org.junit.Before; import org.junit.Test; import eu.mihosoft.vrl.v3d.svg.SVGLoad; @@ -16,20 +19,59 @@ import javafx.scene.shape.CullFace; public class SVGLoadTest { + @Before + public void setup() { + CSG.setPreventNonManifoldTriangles(true); + } + @Test + public void vows() throws IOException { + +// JavaFXInitializer.go(); +// File svg = new File("vows.svg"); +// if (!svg.exists()) +// throw new RuntimeException("Test file missing!" + svg.getAbsolutePath()); +// SVGLoad s = new SVGLoad(svg.toURI()); +// +// ArrayList parts = new ArrayList(run(s)); +// for(int i=0;ip =run(s); + CSG nub = new Cylinder(10, 15).toCSG(); + + CSG flame =CSG.unionAll(run(s)).moveToCenter().moveToCenterY().toZMin().union(nub.movez(-2)); ArrayList parts = new ArrayList(); - parts.addAll(p); - for(CSG c:p) { - parts.add(c.rotx(180).rotz(5).toZMin().difference(c).movez(30).setColor(Color.YELLOW)); - } + parts.add(flame); + CSG cut = flame.difference(nub).movey(flame.getTotalY()); + parts.add(cut); + parts.add(flame.roty(180).movex(flame.getTotalX())); + parts.add(cut.roty(180).movex(flame.getTotalX())); + + System.out.println("Difference complete"); if(parts.size()==0) throw new RuntimeException("Failed to load"); try { @@ -39,6 +81,41 @@ public void Alexes_Bad() throws IOException { // Auto-generated catch block e.printStackTrace(); } + for(int i=0;ip =run(s); +// ArrayList parts = new ArrayList(); +// parts.addAll(p); +// +// for (int i = 0; i < p.size(); i++) { +// CSG c = p.get(i); +// System.out.println("Perform difference "+i+" of "+p.size()); +// parts.add(c.rotx(180).rotz(5).toZMin().difference(c).movez(30).setColor(Color.YELLOW)); +// } +// System.out.println("Difference complete"); +// if(parts.size()==0) +// throw new RuntimeException("Failed to load"); +// try { +// ThumbnailImage.setCullFaceValue(CullFace.NONE); +// ThumbnailImage.writeImage(parts,new File(svg.getAbsolutePath()+".png")).join(); +// } catch (InterruptedException e) { +// // Auto-generated catch block +// e.printStackTrace(); +// } +// for(int i=0;iparts =run(s); + ArrayListparts =new ArrayList<>(Arrays.asList(CSG.unionAll(run(s)))); try { ThumbnailImage.setCullFaceValue(CullFace.NONE); ThumbnailImage.writeImage(parts,new File(svg.getAbsolutePath()+".png")).join(); @@ -71,6 +151,9 @@ public void inside() throws IOException { // Auto-generated catch block e.printStackTrace(); } + for(int i=0;i run(SVGLoad s) { double depth = 5 + (layers.size() * 5); for (int i = 0; i < layers.size(); i++) { String layerName = layers.get(i); - HashMap> extrudeLayerToCSG = s.extrudeLayers(depth,0.1, layerName); + HashMap> extrudeLayerToCSG = s.extrudeLayers(depth, layerName); // extrudeLayerToCSG.setColor(Color.web(SVGExporter.colorNames.get(i))); for(String key:extrudeLayerToCSG.keySet()) { //System.out.println("Adding layer: "+key); diff --git a/src/test/java/eu/mihosoft/vrl/v3d/StlExportTest.java b/src/test/java/eu/mihosoft/vrl/v3d/StlExportTest.java index a32c398b..c7b549d7 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/StlExportTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/StlExportTest.java @@ -13,7 +13,6 @@ public class StlExportTest { @Test public void makeBadSTL() throws IOException { long start = System.currentTimeMillis(); - Plane.setEPSILON(1.0e-7); // Vector3d.setEXPORTEPSILON(1.0e-10); CSG.setUseGPU(true); CSG.setPreventNonManifoldTriangles(true); diff --git a/src/test/java/eu/mihosoft/vrl/v3d/SvgExportTest.java b/src/test/java/eu/mihosoft/vrl/v3d/SvgExportTest.java index b52a4c46..a4f35374 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/SvgExportTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/SvgExportTest.java @@ -18,7 +18,7 @@ public class SvgExportTest { @Test - public void slicetest() throws IOException { + public void slicetest() throws IOException, ColinearPointsException { double normalInsetDistance = 0; Transform slicePlane = new Transform(); @@ -44,21 +44,21 @@ public void slicetest() throws IOException { } @Test - public void test() throws IOException { + public void test() throws IOException, ColinearPointsException { List polygons = new ArrayList(); List vertices = new ArrayList(); - vertices.add(new Vertex(new Vector3d(-30, 0), new Vector3d(0, 0))); - vertices.add(new Vertex(new Vector3d(100, 0), new Vector3d(0, 0))); - vertices.add(new Vertex(new Vector3d(100, 100), new Vector3d(0, 0))); - vertices.add(new Vertex(new Vector3d(-30, 100), new Vector3d(0, 0))); + vertices.add(new Vertex(new Vector3d(-30, 0))); + vertices.add(new Vertex(new Vector3d(100, 0))); + vertices.add(new Vertex(new Vector3d(100, 100))); + vertices.add(new Vertex(new Vector3d(-30, 100))); List vertices2 = new ArrayList(); - vertices2.add(new Vertex(new Vector3d(50, 50), new Vector3d(0, 0))); - vertices2.add(new Vertex(new Vector3d(75, 50), new Vector3d(0, 0))); - vertices2.add(new Vertex(new Vector3d(75, 75), new Vector3d(0, 0))); - vertices2.add(new Vertex(new Vector3d(50, 75), new Vector3d(0, 0))); + vertices2.add(new Vertex(new Vector3d(50, 50))); + vertices2.add(new Vertex(new Vector3d(75, 50))); + vertices2.add(new Vertex(new Vector3d(75, 75))); + vertices2.add(new Vertex(new Vector3d(50, 75))); Polygon outline2 = new Polygon(vertices2); Polygon outline = new Polygon(vertices); @@ -70,21 +70,21 @@ public void test() throws IOException { } @Test - public void testSlices() throws IOException { + public void testSlices() throws IOException, ColinearPointsException { List polygons = new ArrayList(); List vertices = new ArrayList(); - vertices.add(new Vertex(new Vector3d(-30, 0), new Vector3d(0, 0))); - vertices.add(new Vertex(new Vector3d(100, 0), new Vector3d(0, 0))); - vertices.add(new Vertex(new Vector3d(100, 100), new Vector3d(0, 0))); - vertices.add(new Vertex(new Vector3d(-30, 100), new Vector3d(0, 0))); + vertices.add(new Vertex(new Vector3d(-30, 0))); + vertices.add(new Vertex(new Vector3d(100, 0))); + vertices.add(new Vertex(new Vector3d(100, 100))); + vertices.add(new Vertex(new Vector3d(-30, 100))); List vertices2 = new ArrayList(); - vertices2.add(new Vertex(new Vector3d(50, 50), new Vector3d(0, 0))); - vertices2.add(new Vertex(new Vector3d(75, 50), new Vector3d(0, 0))); - vertices2.add(new Vertex(new Vector3d(75, 75), new Vector3d(0, 0))); - vertices2.add(new Vertex(new Vector3d(50, 75), new Vector3d(0, 0))); + vertices2.add(new Vertex(new Vector3d(50, 50))); + vertices2.add(new Vertex(new Vector3d(75, 50))); + vertices2.add(new Vertex(new Vector3d(75, 75))); + vertices2.add(new Vertex(new Vector3d(50, 75))); Polygon outline2 = new Polygon(vertices2); Polygon outline = new Polygon(vertices); @@ -96,7 +96,7 @@ public void testSlices() throws IOException { } @Test - public void testCSGSlices() throws IOException { + public void testCSGSlices() throws IOException, ColinearPointsException { CSG carrot = new Cube(10, 10, 10).toCSG() // .toXMin() @@ -120,12 +120,12 @@ public void svgLoadSlices() throws IOException { File defaultDir = new File("svg/SVGExportTest6.svg"); SVGLoad s = new SVGLoad(defaultDir.toURI()); - ArrayListgear = s.extrude(10,0.001); + ArrayListgear = s.extrude(10); //System.out.println("SVG Elements ="+gear); } @Test - public void testManyCSGSlices() throws IOException { + public void testManyCSGSlices() throws IOException, ColinearPointsException { // Create a CSG to slice CSG pin = new Cylinder(10, 100) .toCSG(); @@ -153,18 +153,22 @@ public void testManyCSGSlices() throws IOException { carrot.addSlicePlane(new Transform()); carrot.addSlicePlane(slicePlane); - CSG sphere = new Sphere(10,80,80) + CSG sphere = new Sphere(10,40,40) .setCenter(new Vector3d(30, 30)) .toCSG(); for(int i=0;i<10;i++){ Transform sp = new Transform(); - sp.translateZ(0.9*i); + sp.translateZ(0.4*i); sphere.addSlicePlane(sp); } + System.out.println("Done slicing"); carrot.setName("Square Sections"); sphere.setName("Circle Sections"); File f = new File("SVGExportTest5.svg"); + System.out.println("Exporting the polygons..."); SVGExporter.export(Arrays.asList(carrot,sphere),f); + System.out.println("Loading generated polygons"); SVGLoad. toPolygons( f); + System.out.println("testManyCSGSlices complete"); } } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/sweepTest.java b/src/test/java/eu/mihosoft/vrl/v3d/sweepTest.java index d9b595f5..05f1e152 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/sweepTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/sweepTest.java @@ -16,7 +16,7 @@ public class sweepTest { @Test - public void test() throws IOException { + public void test() throws IOException, ColinearPointsException { File svg = new File("Test.SVG");