JavaCAD
Slice.java
Go to the documentation of this file.
1 package eu.mihosoft.vrl.v3d;
2 
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collections;
6 import java.util.List;
7 
8 import javafx.scene.image.WritableImage;
9 import java.util.HashMap;
10 import eu.mihosoft.vrl.v3d.CSG;
11 import javafx.application.Platform;
12 //import javafx.embed.swing.JFXPanel;
13 import javafx.geometry.Insets;
14 import javafx.scene.SnapshotParameters;
15 import javafx.scene.layout.AnchorPane;
16 import javafx.scene.layout.Background;
17 import javafx.scene.layout.BackgroundFill;
18 import javafx.scene.layout.CornerRadii;
19 import javafx.scene.layout.Pane;
20 import javafx.scene.paint.Color;
21 import javafx.scene.shape.MeshView;
22 import javafx.scene.transform.Scale;
23 import javafx.stage.Stage;
24 import javafx.scene.image.PixelReader;
25 
26 public class Slice {
27  private static int maxRes = 3000;
28  private static class DefaultSliceImp implements ISlice {
29  double sizeinPixelSpace = 1500;
30  HashMap<WritableImage, PixelReader> readers = new HashMap<>();
31  // pixelData=new HashMap<>();
32  ArrayList<int[]> usedPixels = new ArrayList<>();
33  int minRes = 1000;
34  private boolean done;
35 
36  Object[] toPixMap(CSG slicePart) {
37 
38  // BowlerStudioController.getBowlerStudio()
39  // .addObject((Object)slicePart.movez(1),(File)null)
40  // BowlerStudioController.getBowlerStudio()
41  // .addObject((Object)rawPolygons,(File)null)
42  double ratio = slicePart.getTotalY() / slicePart.getTotalX();
43  boolean ratioOrentation = slicePart.getTotalX() > slicePart.getTotalY();
44  if (ratioOrentation)
45  ratio = slicePart.getTotalX() / slicePart.getTotalY();
46  ratio = 1 / ratio;
47  ;
48  double mySize = slicePart.getTotalX() > slicePart.getTotalY() ? slicePart.getTotalX()
49  : slicePart.getTotalY();
50  List<Polygon> polys = slicePart.getPolygons();
51  double size = sizeinPixelSpace * (mySize / 200) * (polys.size() / 300);
52  if (size < minRes)
53  size = minRes;
54  if (size > getMaxRes())
55  size = getMaxRes();
56  // println "Vectorizing "+polys.size()+" polygons at pixel resolution: "+size
57 
58  double xPix = size * (ratioOrentation ? 1.0 : ratio);
59  double yPix = size * (!ratioOrentation ? 1.0 : ratio);
60  double xOffset = slicePart.getMinX();
61  double yOffset = slicePart.getMinY();
62  double scaleX = slicePart.getTotalX() / xPix;
63  double scaleY = slicePart.getTotalY() / yPix;
64 
65  // println "New Slicer Image x=" +xPix+" by y="+yPix+" at x="+xOffset+"
66  // y="+yOffset
67 
68  double imageOffset = 180.0;
69  double imageOffsetMotion = imageOffset * scaleX / 2;
70  int imgx = (int) (xPix + imageOffset);
71  int imgy = (int) (yPix + imageOffset);
72  WritableImage obj_img = new WritableImage(imgx, imgy);
73  // int snWidth = (int) 4096;
74  // int snHeight = (int) 4096;
75 
76  MeshView sliceMesh = slicePart.getMesh();
77  sliceMesh.getTransforms()
78  .add(javafx.scene.transform.Transform.translate(imageOffsetMotion, imageOffsetMotion));
79  AnchorPane anchor = new AnchorPane(sliceMesh);
80  AnchorPane.setBottomAnchor(sliceMesh, (double) 0);
81  AnchorPane.setTopAnchor(sliceMesh, (double) 0);
82  AnchorPane.setLeftAnchor(sliceMesh, (double) 0);
83  AnchorPane.setRightAnchor(sliceMesh, (double) 0);
84  Pane snapshotGroup = new Pane(anchor);
85  snapshotGroup.prefHeight((double) (yPix + imageOffset));
86  snapshotGroup.prefWidth((double) (xPix + imageOffset));
87  snapshotGroup
88  .setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)));
89 
90  SnapshotParameters snapshotParameters = new SnapshotParameters();
91  snapshotParameters.setTransform(new Scale(1 / scaleX, 1 / scaleY));
92  snapshotParameters.setDepthBuffer(true);
93  snapshotParameters.setFill(Color.TRANSPARENT);
94  done = false;
95  Runnable r = new Runnable() {
96  @Override
97  public void run() {
98  snapshotGroup.snapshot(snapshotParameters, obj_img);
99  done = true;
100  }
101  };
102  Platform.runLater(r);
103  while (done == false) {
104  try {
105  Thread.sleep(10);
106  } catch (InterruptedException e) {
107  // TODO Auto-generated catch block
108  e.printStackTrace();
109  }
110  }
111 
112  // println "Find boundries "
113 
114  return new Object[] { obj_img, scaleX, xOffset - imageOffsetMotion, scaleY, yOffset - imageOffsetMotion,
115  imageOffsetMotion, imageOffset };
116  }
117  int[] toPixels(double absX, double absY, double xOff, double yOff, double scaleX, double scaleY) {
118  return new int[] { (int) ((absX - xOff) / scaleX), (int) ((absY - yOff) / scaleY) };
119  }
120 
121  boolean pixelBlack(double absX, double absY, WritableImage obj_img) {
122  if (readers.get(obj_img) == null) {
123  readers.put(obj_img, obj_img.getPixelReader());
124  }
125  PixelReader pixelReader = readers.get(obj_img);
126  return pixelReader.getColor((int) absX, (int) absY).getOpacity() != 0;
127  }
128 
129  boolean pixelEdge(double absX, double absY, WritableImage obj_img) {
130  for (int i = -1; i < 2; i++) {
131  int x = (int) (absX + i);
132  for (int j = -1; j < 2; j++) {
133  int y = (int) (absY + j);
134  try {
135  if (!pixelBlack(x, y, obj_img)) {
136  return true;
137  }
138  } catch (Throwable t) {
139  // BowlerStudio.printStackTrace(t);
140  }
141  }
142  }
143  return false;
144  }
145 
158  public List<Polygon> slice(CSG incoming, Transform slicePlane, double normalInsetDistance) {
159  if (Thread.interrupted()) {
160  return null;
161  }
162  long startTime = System.currentTimeMillis();
163  // if(display)BowlerStudioController.getBowlerStudio().getJfx3dmanager().clearUserNode()
164  List<Polygon> rawPolygons = new ArrayList<>();
165  CSG finalPart = incoming.transformed(slicePlane.inverse()
166  ).toolOffset(normalInsetDistance);
167  // Actual slice plane
168  CSG planeCSG = finalPart.getBoundingBox().toZMin();
169  planeCSG = planeCSG
170  .intersect(planeCSG
171  .toZMax()
172  .movez(0.00001)
173  );
174  // Loop over each polygon in the slice of the incoming CSG
175  // Add the polygon to the final slice if it lies entirely in the z plane
176  // println "Preparing CSG slice"
177  CSG slicePart = finalPart
178 
179  .intersect(planeCSG);
180  for (Polygon p : slicePart.getPolygons()) {
181  if (Slice.isPolygonAtZero(p)) {
182  rawPolygons.add(p);
183  }
184  }
185 
186  Object[] parts = toPixMap(slicePart);
187  WritableImage obj_img = (WritableImage) parts[0];
188  double scaleX = (double) parts[1];
189  double xOffset = (double) parts[2];
190  double scaleY = (double) parts[3];
191  double yOffset = (double) parts[4];
192 
193  ArrayList<Vector3d> points = new ArrayList<>();
194  for (Polygon p : rawPolygons) {
195  for (Vertex v : p.vertices) {
196  points.add(v.pos);
197  }
198  }
199 
200  ArrayList<Polygon> polys = new ArrayList<>();
201  ArrayList<int[]> pixelVersionOfPoints = new ArrayList<>();
202  for (Vector3d it : points) {
203  int[] pix = toPixels(it.x, it.y, xOffset, yOffset, scaleX, scaleY);
204  if (pixelEdge(pix[0], pix[1], obj_img)) {
205  pixelVersionOfPoints.add(pix);
206  }
207  }
208 
209  ArrayList<int[]> pixelVersionOfPointsFiltered = new ArrayList<>();
210  for (int[] d : pixelVersionOfPoints) {
211  boolean testIt = false;
212  for (int[] x : pixelVersionOfPointsFiltered) {
213  if (withinAPix(x, d)) {
214  testIt = true;
215  }
216 
217  }
218  if (!testIt) {
219  pixelVersionOfPointsFiltered.add(d);
220  }
221  }
222  pixelVersionOfPoints = pixelVersionOfPointsFiltered;
223  // if(display)showPoints(pixelVersionOfPoints)
224  int[] pixStart = pixelVersionOfPoints.get(0);
225  pixelVersionOfPoints.remove(0);
226  int[] nextPoint = pixStart;
227  ArrayList<int[]> listOfPointsForThisPoly = new ArrayList<>();
228  listOfPointsForThisPoly.add(pixStart);
229 
230  // if(display)showPoints([nextPoint],20,javafx.scene.paint.Color.ORANGE)
231  int lastSearchIndex = 0;
232  while ((pixelVersionOfPoints.size() > 0 || listOfPointsForThisPoly.size() > 0) && !Thread.interrupted()) {
233 
234  Object[] results = searchNext(nextPoint, obj_img, lastSearchIndex);
235  // println "Searching "+results
236  if (results == null) {
237  listOfPointsForThisPoly.clear();
238  if (pixelVersionOfPoints.size() > 0) {
239  pixStart = pixelVersionOfPoints.remove(0);
240  nextPoint = pixStart;
241  listOfPointsForThisPoly.clear();
242  listOfPointsForThisPoly.add(nextPoint);
243  // if(display)showPoints([nextPoint],40,javafx.scene.paint.Color.BLACK)
244  } else
245  break;
246  continue;
247  }
248  nextPoint = (int[]) results[0];
249  lastSearchIndex = (int) results[1];
250  // if(display)showPoints([nextPoint],2,javafx.scene.paint.Color.YELLOW)
251  // Thread.sleep(10)
252  ArrayList<int[]> toRemove = new ArrayList<>();
253  for (int[] it : pixelVersionOfPoints) {
254  if (withinAPix(nextPoint, it)) {
255  toRemove.add(it);
256  }
257  }
258 
259  if (toRemove.size() > 0) {
260  // println "Found "+toRemove
261  for (int[] d : toRemove) {
262  // if(display)showPoints([d],30,javafx.scene.paint.Color.GREEN)
263  pixelVersionOfPoints.remove(d);
264  listOfPointsForThisPoly.add(d);
265  }
266 
267  } else {
268  if (listOfPointsForThisPoly.size() > 2) {
269  if (withinAPix(nextPoint, pixStart)) {
270  // if(display)println "Closed Polygon Found!"
271  // Thread.sleep(1000)
272  List<Vector3d> p = new ArrayList<>();
273  for (int[] it : listOfPointsForThisPoly) {
274  p.add(new Vector3d((it[0] * scaleX) + xOffset, (it[1] * scaleY) + yOffset, 0));
275  }
276 
277  Polygon polyNew = Polygon.fromPoints(p);
278  polys.add(polyNew);
279  listOfPointsForThisPoly.clear();
280  if (pixelVersionOfPoints.size() > 0) {
281  pixStart = pixelVersionOfPoints.remove(0);
282  nextPoint = pixStart;
283  listOfPointsForThisPoly.add(nextPoint);
284  }
285  // if(display)showPoints([nextPoint],20,javafx.scene.paint.Color.ORANGE)
286  }
287  }
288  }
289 
290  }
291  if (listOfPointsForThisPoly.size() > 0) {
292  // println "Spare Polygon Found!"
293  // Thread.sleep(1000)
294  List<Vector3d> p = new ArrayList<>();
295  for (int[] it : listOfPointsForThisPoly) {
296  p.add(new Vector3d((it[0] * scaleX) + xOffset, (it[1] * scaleY) + yOffset, 0));
297  }
298  polys.add(Polygon.fromPoints(p));
299  // if(display)BowlerStudioController.getBowlerStudio() .addObject(polys, new
300  // File("."))
301  }
302 
303  readers.clear();
304  // pixelData.clear();
305  usedPixels.clear();
306  // if(display)BowlerStudioController.getBowlerStudio().getJfx3dmanager().clearUserNode()
307  // BowlerStudioController.getBowlerStudio() .addObject(polys, new File("."));
308  System.out.println(
309  "Slice took: " + (((double) (System.currentTimeMillis() - startTime)) / 1000.0) + " seconds");
310  return polys;
311  }
312 
313  Object[] searchNext(int[] pixStart, WritableImage obj_img, int lastSearchIndex) {
314 
315  double index = 1;
316  Object[] ret = searchNextDepth(pixStart, obj_img, index, lastSearchIndex);
317 
318  while (ret == null && index < 10 && !Thread.interrupted()) {
319  index += 0.5;
320  ret = searchNextDepth(pixStart, obj_img, index, 0);
321  }
322  return ret;
323 
324  }
325 
326  Object[] searchNextDepth(int[] pixStart, WritableImage obj_img, double searchSize, int lastSearchIndex) {
327  ArrayList<int[]> locations = new ArrayList<>();
328  double inc = Math.toDegrees(Math.atan2(1, searchSize));
329  if (searchSize > 2) {
330  for (double i = 0; i < 360 + inc; i += inc) {
331  int x = (int) Math.round(Math.cos(Math.toRadians(i)) * searchSize);
332  int y = (int) Math.round(Math.sin(Math.toRadians(i)) * searchSize);
333  locations.add(new int[] { pixStart[0] + x, pixStart[1] + y });
334  }
335  } else {
336 
337  // arrange the pixels in the data array based on a CCW search
338  for (int i = (int) -searchSize; i < searchSize + 1; i++) {
339  locations.add(new int[] { (int) (pixStart[0] + searchSize), pixStart[1] + i });
340  }
341  // after the firat loop, leave off the first index to avoid duplicates
342  for (int i = (int) (searchSize - 1); i > -searchSize - 1; i--) {
343  locations.add(new int[] { pixStart[0] + i, (int) (pixStart[1] + searchSize) });
344  }
345  for (int i = (int) (searchSize - 1); i > -searchSize - 1; i--) {
346  locations.add(new int[] { (int) (pixStart[0] - searchSize), pixStart[1] + i });
347  }
348  for (int i = (int) (-searchSize + 1); i < searchSize + 1; i++) {
349  locations.add(new int[] { pixStart[0] + i, (int) (pixStart[1] - searchSize) });
350  }
351 
352  }
353  // println inc+" "+locations
354  // if(searchSize>2)println "\t\t "+searchSize
355  int searchArraySize = locations.size();
356  if (lastSearchIndex >= searchArraySize) {
357  lastSearchIndex = 0;
358  }
359  int end = lastSearchIndex - 1;
360  if (end < 0)
361  end = searchArraySize - 1;
362  // rotate throught he data looking for CCW edge
363  for (int i = lastSearchIndex; i != end
364  && !Thread.interrupted(); i = (i + 1 >= searchArraySize ? 0 : i + 1)) {
365  // println "\t\t "+i+" start = " +lastSearchIndex+" end = "+end+" array size =
366  // "+searchArraySize
367  int counterCW = i - 1;
368  if (counterCW < 0)
369  counterCW = searchArraySize - 1;
370  int[] ccw = locations.get(counterCW);
371  int[] self = locations.get(i);
372  boolean w = !pixelBlack(self[0], self[1], obj_img);
373  boolean b = pixelBlack(ccw[0], ccw[1], obj_img);
374 
375  boolean useMe = true;
376  for (int[] it : usedPixels) {
377  if (it[0] == self[0] && it[1] == self[1]) {
378  useMe = false;
379  break;
380  }
381  }
382  if (w && b && useMe) {
383  usedPixels.add(self);
384  // edge detected doing a ccw rotation search
385  return new Object[] { self, i };
386  } else {
387  // if(display)showPoints([self],1,javafx.scene.paint.Color.WHITE) ;
388  }
389  }
390  return null;
391  /*
392  * //println "From "+pixStart x= pixStart[0] y=pixStart[1] ul =
393  * pixelBlack(x+1,y+1,obj_img) uc = pixelBlack(x+1,y,obj_img) ur =
394  * pixelBlack(x+1,y-1,obj_img) l= pixelBlack(x,y+1,obj_img) r=
395  * pixelBlack(x,y-1,obj_img) bl = pixelBlack(x-1,y+1,obj_img) bc =
396  * pixelBlack(x-1,y,obj_img) br = pixelBlack(x-1,y-1,obj_img) me =
397  * pixelBlack(x,y,obj_img) println
398  * "Ul = "+ul+" uc "+uc+" ur "+ur+" \r\nl "+l+" c "+me+" r "+r+"\r\nbl "+bl+
399  * " bc "+bc+" br "+br
400  */
401 
402  }
403 
404  boolean withinAPix(int[] incoming, int[] out) {
405  int pixSize = 2;
406  for (int i = -pixSize; i < pixSize + 1; i++) {
407  int x = incoming[0] + i;
408  for (int j = -pixSize; j < pixSize + 1; j++) {
409  int y = incoming[1] + j;
410  if (x == out[0] && y == out[1]) {
411  return true;
412  }
413  }
414  }
415  return false;
416  }
417  };
418 
419  private static ISlice sliceEngine = new DefaultSliceImp();
420 
428  private static boolean isPolygonAtZero(Polygon polygon) {
429  // Return false if there is a vertex in this polygon which is not at
430  // zero
431  // Else, the polygon is at zero if every vertex in it is at zero
432  for (Vertex v : polygon.vertices)
433  if (!isVertexAtZero(v))
434  return false;
435 
436  return true;
437  }
438 
446  private static boolean isVertexAtZero(Vertex vertex) {
447  // The upper and lower bounds for checking the vertex z coordinate
448  // against
449  final double SLICE_UPPER_BOUND = 0.001, SLICE_LOWER_BOUND = -0.001;
450 
451  // The vertex is at zero if it is within tight bounds (to account for
452  // floating point error)
453  return vertex.getZ() < SLICE_UPPER_BOUND && vertex.getZ() > SLICE_LOWER_BOUND;
454  }
455 
456  public static List<Polygon> slice(CSG incoming, Transform slicePlane, double normalInsetDistance) {
457  try {
458  if(DefaultSliceImp.class.isInstance(sliceEngine)) {
459  // avoid concurrecy issues
460  try {
461  return sanatize(new DefaultSliceImp().slice(incoming, slicePlane, normalInsetDistance));
462  }catch(IllegalStateException e) {
464 
465  return sanatize(new DefaultSliceImp().slice(incoming, slicePlane, normalInsetDistance));
466  }
467  }
468  return sanatize(getSliceEngine().slice(incoming, slicePlane, normalInsetDistance));
469  }catch(Throwable e) {
470  return sanatize(incoming.getPolygons());
471  }
472  }
473 
474  private static List<Polygon> sanatize(List<Polygon> slice) {
475  for (int i = 0; i < slice.size(); i++) {
476  Polygon me = slice.get(i);
477  boolean bad = !Extrude.isCCW(me);
478  if (bad) {
479  // println "Bad polygon!"
480  List<Vector3d> points = me.getPoints();
481  ArrayList<Vector3d> result = new ArrayList<Vector3d>(points);
482  Collections.reverse(result);
483  me = Polygon.fromPoints(result);
484  }
485  slice.set(i, me);
486  }
487  return slice;
488  }
489 
490  public static List<Polygon> slice(CSG incoming) {
491  return slice(incoming, new Transform(),0);
492  }
493  public static List<Polygon> slice(CSG incoming, double normalInsetDistance) {
494  return slice(incoming, new Transform(),normalInsetDistance);
495  }
496  public static ISlice getSliceEngine() {
497  return sliceEngine;
498  }
499 
500  public static void setSliceEngine(ISlice sliceEngine) {
502  }
503 
504  public static int getMaxRes() {
505  return maxRes;
506  }
507 
508  public static void setNumFacesInOffset(int numFacesInOffset) {
509  CSG.setNumFacesInOffset( numFacesInOffset);
510  }
511  public static void setMaxRes(int mr) {
512  maxRes = mr;
513  }
514 }
MeshView getMesh()
Definition: CSG.java:252
CSG transformed(Transform transform)
Definition: CSG.java:1676
List< Polygon > getPolygons()
Definition: CSG.java:698
CSG intersect(CSG csg)
Definition: CSG.java:1275
CSG toZMin(CSG target)
Definition: CSG.java:298
CSG toolOffset(Number sn)
Definition: CSG.java:2064
static void setNumFacesInOffset(int numFacesInOffset)
Definition: CSG.java:2439
static boolean isCCW(Polygon polygon)
Definition: Extrude.java:258
final List< Vertex > vertices
Definition: Polygon.java:54
List< Vector3d > getPoints()
Definition: Polygon.java:547
static Polygon fromPoints(List< Vector3d > points, PropertyStorage shared)
Definition: Polygon.java:386
static ISlice sliceEngine
Definition: Slice.java:419
static List< Polygon > sanatize(List< Polygon > slice)
Definition: Slice.java:474
static void setSliceEngine(ISlice sliceEngine)
Definition: Slice.java:500
static List< Polygon > slice(CSG incoming, Transform slicePlane, double normalInsetDistance)
Definition: Slice.java:456
static ISlice getSliceEngine()
Definition: Slice.java:496
static List< Polygon > slice(CSG incoming)
Definition: Slice.java:490
static void setNumFacesInOffset(int numFacesInOffset)
Definition: Slice.java:508
static boolean isVertexAtZero(Vertex vertex)
Definition: Slice.java:446
static int getMaxRes()
Definition: Slice.java:504
static void setMaxRes(int mr)
Definition: Slice.java:511
static boolean isPolygonAtZero(Polygon polygon)
Definition: Slice.java:428
static List< Polygon > slice(CSG incoming, double normalInsetDistance)
Definition: Slice.java:493