3838import java .util .stream .Collectors ;
3939import java .util .stream .Stream ;
4040
41+ import eu .mihosoft .vrl .v3d .ext .org .poly2tri .PolygonUtil ;
42+
4143// Auto-generated Javadoc
4244/**
4345 * Holds a node in a BSP tree. A BSP tree is built from a collection of polygons
4749 * no distinction between internal and leaf nodes.
4850 */
4951public final class Node {
50-
52+ private static final int COPLANAR = 0 ;
53+ private static final int FRONT = 1 ;
54+ private static final int BACK = 2 ;
55+ private static final int SPANNING = 3 ; // == some in the FRONT + some in the BACK
5156 /**
5257 * Polygons.
5358 */
54- private List <Polygon > polygons ;
59+ private ArrayList <Polygon > polygons ;
5560 /**
5661 * Plane used for BSP.
5762 */
@@ -64,7 +69,7 @@ public final class Node {
6469 * Polygons in back of the plane.
6570 */
6671 private Node back ;
67-
72+
6873 private long maxDepth = -1 ;
6974
7075 /**
@@ -74,7 +79,7 @@ public final class Node {
7479 *
7580 * @param polygons polygons
7681 */
77- public Node (List <Polygon > polygons ) {
82+ public Node (ArrayList <Polygon > polygons ) {
7883 this .polygons = new ArrayList <>();
7984 if (polygons != null ) {
8085 this .build (polygons );
@@ -106,12 +111,12 @@ public Node clone() {
106111
107112 Stream <Polygon > polygonStream ;
108113
109- if (polygons .size () > 200 ) {
110- polygonStream = polygons .parallelStream ();
111- } else
112- polygonStream = polygons .stream ();
114+ if (polygons .size () > 200 ) {
115+ polygonStream = polygons .parallelStream ();
116+ } else
117+ polygonStream = polygons .stream ();
113118
114- node .polygons = polygonStream .map (p -> p .clone ()).collect (Collectors .toList ( ));
119+ node .polygons = polygonStream .map (p -> p .clone ()).collect (Collectors .toCollection ( ArrayList :: new ));
115120
116121 return node ;
117122 }
@@ -123,10 +128,10 @@ public void invert() {
123128
124129 Stream <Polygon > polygonStream ;
125130
126- if (polygons .size () > 200 ) {
127- polygonStream = polygons .parallelStream ();
128- } else
129- polygonStream = polygons .stream ();
131+ if (polygons .size () > 200 ) {
132+ polygonStream = polygons .parallelStream ();
133+ } else
134+ polygonStream = polygons .stream ();
130135
131136 polygonStream .forEach ((polygon ) -> {
132137 polygon .flip ();
@@ -136,8 +141,9 @@ public void invert() {
136141 this .setPlane (polygons .get (0 ).getPlane ().clone ());
137142 } else if (this .getPlane () == null && polygons .isEmpty ()) {
138143
139- //com.neuronrobotics.sdk.common.Log.error("Please fix me! I don't know what to do?");
140- throw new RuntimeException ("Please fix me! Plane = " +plane +" and polygons are empty" );
144+ // com.neuronrobotics.sdk.common.Log.error("Please fix me! I don't know what to
145+ // do?");
146+ throw new RuntimeException ("Please fix me! Plane = " + plane + " and polygons are empty" );
141147 // return;
142148 }
143149
@@ -164,18 +170,17 @@ public void invert() {
164170 *
165171 * @return the cliped list of polygons
166172 */
167- private List <Polygon > clipPolygons (List <Polygon > polygons ) {
173+ private ArrayList <Polygon > clipPolygons (ArrayList <Polygon > polygons ) {
168174
169175 if (this .getPlane () == null ) {
170176 return new ArrayList <>(polygons );
171177 }
172178
173- List <Polygon > frontP = new ArrayList <>();
174- List <Polygon > backP = new ArrayList <>();
179+ ArrayList <Polygon > frontP = new ArrayList <>();
180+ ArrayList <Polygon > backP = new ArrayList <>();
181+
182+ splitPolygon (polygons , frontP , backP , frontP , backP );
175183
176- for (Polygon polygon : polygons ) {
177- this .getPlane ().splitPolygon (polygon , frontP , backP , frontP , backP );
178- }
179184 if (this .front != null ) {
180185 frontP = this .front .clipPolygons (frontP );
181186 }
@@ -189,6 +194,138 @@ private List<Polygon> clipPolygons(List<Polygon> polygons) {
189194 return frontP ;
190195 }
191196
197+ /**
198+ * Splits a {@link Polygon} by this plane if needed. After that it puts the
199+ * polygons or the polygon fragments in the appropriate lists ({@code front},
200+ * {@code back}). Coplanar polygons go into either {@code coplanarFront},
201+ * {@code coplanarBack} depending on their orientation with respect to this
202+ * plane. Polygons in front or back of this plane go into either {@code front}
203+ * or {@code back}.
204+ *
205+ * @param polygon polygon to split
206+ * @param coplanarFront "coplanar front" polygons
207+ * @param coplanarBack "coplanar back" polygons
208+ * @param front front polygons
209+ * @param back back polgons
210+ */
211+ public void splitPolygon (ArrayList <Polygon > polygons , List <Polygon > coplanarFront , List <Polygon > coplanarBack ,
212+ List <Polygon > front , List <Polygon > back ) {
213+ for (int k = 0 ; k < polygons .size (); k ++) {
214+ Polygon polygon = polygons .get (k );
215+
216+
217+ // search for the epsilon values of the incoming plane
218+ double negEpsilon = -Plane .getEPSILON ();
219+ double posEpsilon = Plane .getEPSILON ();
220+ for (int i = 0 ; i < polygon .getVertices ().size (); i ++) {
221+ double t = polygon .getPlane ().getNormal ().dot (polygon .getVertices ().get (i ).pos )
222+ - polygon .getPlane ().getDist ();
223+ if (t > posEpsilon ) {
224+ // com.neuronrobotics.sdk.common.Log.error("Non flat polygon, increasing
225+ // positive epsilon "+t);
226+ posEpsilon = t + Plane .getEPSILON ();
227+ }
228+ if (t < negEpsilon ) {
229+ // com.neuronrobotics.sdk.common.Log.error("Non flat polygon, decreasing
230+ // negative epsilon "+t);
231+ negEpsilon = t - Plane .getEPSILON ();
232+ }
233+ }
234+ int polygonType = 0 ;
235+ List <Integer > types = new ArrayList <>();
236+ boolean somePointsInfront = false ;
237+ boolean somePointsInBack = false ;
238+ for (int i = 0 ; i < polygon .getVertices ().size (); i ++) {
239+ double t = this .getPlane ().getNormal ().dot (polygon .getVertices ().get (i ).pos )
240+ - this .getPlane ().getDist ();
241+ int type = (t < negEpsilon ) ? BACK : (t > posEpsilon ) ? FRONT : COPLANAR ;
242+ if (type == BACK )
243+ somePointsInBack = true ;
244+ if (type == FRONT )
245+ somePointsInfront = true ;
246+ types .add (type );
247+ }
248+ if (somePointsInBack && somePointsInfront )
249+ polygonType = SPANNING ;
250+ else if (somePointsInBack ) {
251+ polygonType = BACK ;
252+ } else if (somePointsInfront )
253+ polygonType = FRONT ;
254+
255+ // Put the polygon in the correct list, splitting it when necessary.
256+ switch (polygonType ) {
257+ case COPLANAR :
258+ (this .getPlane ().getNormal ().dot (polygon .getPlane ().getNormal ()) > 0 ? coplanarFront : coplanarBack )
259+ .add (polygon );
260+ break ;
261+ case FRONT :
262+ front .add (polygon );
263+ break ;
264+ case BACK :
265+ back .add (polygon );
266+ break ;
267+ case SPANNING :
268+ List <Vertex > f = new ArrayList <>();
269+ List <Vertex > b = new ArrayList <>();
270+ for (int i = 0 ; i < polygon .getVertices ().size (); i ++) {
271+ int j = (i + 1 ) % polygon .getVertices ().size ();
272+ int ti = types .get (i );
273+ int tj = types .get (j );
274+ Vertex vi = polygon .getVertices ().get (i );
275+ Vertex vj = polygon .getVertices ().get (j );
276+ if (ti != BACK ) {
277+ f .add (vi );
278+ }
279+ if (ti != FRONT ) {
280+ b .add (ti != BACK ? vi .clone () : vi );
281+ }
282+ if ((ti | tj ) == SPANNING ) {
283+ double t = (this .getPlane ().getDist () - this .getPlane ().getNormal ().dot (vi .pos ))
284+ / this .getPlane ().getNormal ().dot (vj .pos .minus (vi .pos ));
285+ Vertex v = vi .interpolate (vj , t );
286+ f .add (v );
287+ b .add (v .clone ());
288+ }
289+ }
290+ if (f .size () >= 3 ) {
291+ try {
292+ Polygon fpoly = new Polygon (f , polygon .getStorage (), false , polygon .getPlane ())
293+ .setColor (polygon .getColor ());
294+ add (front , fpoly );
295+ } catch (Exception ex ) {
296+ System .err .println ("Pruning bad polygon Plane::splitPolygon" );
297+ // skip adding broken polygon here
298+ }
299+ } else {
300+ // com.neuronrobotics.sdk.common.Log.error("Front Clip Fault!");
301+ }
302+ if (b .size () >= 3 ) {
303+ try {
304+ Polygon bpoly = new Polygon (b , polygon .getStorage (), false , polygon .getPlane ())
305+ .setColor (polygon .getColor ());
306+ add (back , bpoly );
307+ } catch (Exception ex ) {
308+ // ex.printStackTrace();
309+ System .err .println ("Pruning bad polygon Plane::splitPolygon" );
310+ }
311+ } else {
312+ // com.neuronrobotics.sdk.common.Log.error("Back Clip Fault!");
313+ }
314+ break ;
315+ }
316+ }
317+ }
318+
319+ private static void add (List <Polygon > l , Polygon p ) {
320+ try {
321+ // test triangulation of new polygon before adding
322+ PolygonUtil .concaveToConvex (p );
323+ l .add (p );
324+ } catch (Exception ex ) {
325+ ex .printStackTrace ();
326+ }
327+ }
328+
192329 // Remove all polygons in this BSP tree that are inside the other BSP tree
193330 // `bsp`.
194331 /**
@@ -214,8 +351,8 @@ public void clipTo(Node bsp) {
214351 *
215352 * @return a list of all polygons in this BSP tree
216353 */
217- public List <Polygon > allPolygons () {
218- List <Polygon > localPolygons = new ArrayList <>(this .polygons );
354+ public ArrayList <Polygon > allPolygons () {
355+ ArrayList <Polygon > localPolygons = new ArrayList <>(this .polygons );
219356 if (this .front != null ) {
220357 localPolygons .addAll (this .front .allPolygons ());
221358// polygons = Utils.concat(polygons, this.front.allPolygons());
@@ -236,8 +373,8 @@ public List<Polygon> allPolygons() {
236373 *
237374 * @param polygons polygons used to build the BSP
238375 */
239- public final void build (List <Polygon > polygons ) {
240- build (polygons , 0 ,polygons .size () );
376+ public final void build (ArrayList <Polygon > polygons ) {
377+ build (polygons , 0 , polygons .size ());
241378 }
242379
243380 /**
@@ -248,7 +385,7 @@ public final void build(List<Polygon> polygons) {
248385 *
249386 * @param polygons polygons used to build the BSP
250387 */
251- public final void build (List <Polygon > polygons , long depth , long maxDepth ) {
388+ public final void build (ArrayList <Polygon > polygons , long depth , long maxDepth ) {
252389// if (depth > maxDepth) {
253390// throw new RuntimeException("Impossible Node depth " + depth + " with " + polygons.size() + " remaining max = "+maxDepth );
254391// }
@@ -267,26 +404,26 @@ public final void build(List<Polygon> polygons, long depth, long maxDepth) {
267404 if (this .getPlane () == null ) {
268405 this .setPlane (polygons .get (0 ).getPlane ().clone ());
269406 }
270- //this.polygons.add(polygons.get(0));
407+ // this.polygons.add(polygons.get(0));
271408
272- List <Polygon > frontP = new ArrayList <>();
273- List <Polygon > backP = new ArrayList <>();
409+ ArrayList <Polygon > frontP = new ArrayList <>();
410+ ArrayList <Polygon > backP = new ArrayList <>();
274411
275412 // parellel version does not work here
276- for ( int i = 0 ; i < polygons . size (); i ++) {
277- this . getPlane (). splitPolygon (polygons . get ( i ) , this .polygons , this .polygons , frontP , backP );
278- }
413+
414+ splitPolygon (polygons , this .polygons , this .polygons , frontP , backP );
415+
279416 if (frontP .size () > 0 ) {
280417 if (this .front == null ) {
281418 this .front = new Node ();
282419 }
283- this .front .build (frontP , depth + 1 ,maxDepth );
420+ this .front .build (frontP , depth + 1 , maxDepth );
284421 }
285422 if (backP .size () > 0 ) {
286423 if (this .back == null ) {
287424 this .back = new Node ();
288425 }
289- this .back .build (backP , depth + 1 ,maxDepth );
426+ this .back .build (backP , depth + 1 , maxDepth );
290427 }
291428 }
292429
@@ -295,7 +432,7 @@ public Plane getPlane() {
295432 }
296433
297434 public void setPlane (Plane plane ) {
298- if (plane == null )
435+ if (plane == null )
299436 throw new RuntimeException ("Plane can not be null!" );
300437 this .plane = plane ;
301438 }
0 commit comments