From 702a4288b2634fcf56b630519448f6bbe4a1f522 Mon Sep 17 00:00:00 2001 From: BananaPuncher714 Date: Fri, 23 May 2025 05:36:00 -0400 Subject: [PATCH] Passing commit Check for chain destination vs origin comparison only before intersection Force move vertex horizontally if within VERTEX_TOLERANCE of another vertex Share chains across partitions that are being combined --- .../bananapuncher714/mesh/Mesh.java | 278 ++++++++++-------- .../minietest/MeshingTest2.java | 56 ++-- .../minietest/MeshingTest3.java | 36 +-- 3 files changed, 206 insertions(+), 164 deletions(-) diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java index 541828b..d575966 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java @@ -353,6 +353,11 @@ public class Mesh< T extends Region > { break; } + // Forcefully move any vertices that are within VERTEX_TOLERANCE + // horizontally onto the exact same x position as this vertex. + // It saves us from having fuzzy errors in the future. + otherPos.setX( pos.getX() ); + for ( final HalfEdge edge : other ) { scannedEdges.add( edge ); } @@ -991,7 +996,10 @@ public class Mesh< T extends Region > { } } - // Have a single set of edges + // Have a single set of edges. Use a fuzzy compare here for anything that might + // have made it past the simplification process where two vertices may be within + // VERTEX_TOLERANCE of each other horizontally. Realistically not necessary, and + // probably more of a false assurance having it here than not. final TreeSet< Vertex > fuzzyVertices = new TreeSet< Vertex >( Mesh::fuzzyCompare ); fuzzyVertices.addAll( newVertices.values() ); @@ -1053,15 +1061,31 @@ public class Mesh< T extends Region > { mergeChains( chains, edges ); -// return Collections.emptySet(); final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare ); vertices.addAll( fuzzyVertices ); - return Collections.emptySet(); -// return partitionMonotone( vertices, edges ).parallelStream() -// .map( Mesh::triangulate ) -// .flatMap( p -> p.parallelStream() ) -// .collect( Collectors.toSet() ); + final Collection< EdgePolygon > polys = partitionMonotone( vertices, edges ); + + if ( handlers != null ) { + final Collection< Polygon > polygons = new LinkedHashSet< Polygon >(); + + for ( EdgePolygon p : polys ) { + final List< Point > points = new ArrayList< Point >(); + final HalfEdge start = p.edges.iterator().next(); + HalfEdge t = start; + do { + points.add( new Point( t.getOrigin().getPosition() ) ); + } while ( ( t = t.getNext() ) != start ); + polygons.add( new Polygon( points ) ); + } + + handlers.forEach( h -> h.onPartitionEvent( polygons ) ); + } + + return polys.parallelStream() + .map( Mesh::triangulate ) + .flatMap( p -> p.parallelStream() ) + .collect( Collectors.toSet() ); } /* @@ -1122,6 +1146,13 @@ public class Mesh< T extends Region > { } } + /* + * Reference to a collection of links + */ + class ChainReference { + Collection< Link > links = new LinkedHashSet< Link >(); + } + /** * A partition represents a monotone section of a polygon at the current * scan line. At any given time, a vertical scan line which passes through @@ -1155,7 +1186,7 @@ public class Mesh< T extends Region > { // Keep track of all chains that were formed in this current partition. // For all new chains, see what previous chains it may intersect with - Collection< Link > chains = new LinkedHashSet< Link >(); + ChainReference chains = new ChainReference(); Partition( final HalfEdge lower, final HalfEdge upper ) { this.lower = lower; @@ -1432,7 +1463,7 @@ public class Mesh< T extends Region > { final Vertex chainMidpoint = chain.getMidpoint(); final Vertex chainDestination = chain.getDest(); - for ( final Link other : chains ) { + for ( final Link other : chains.links ) { if ( other.getOrigin() == chainOrigin && other.getDest() == chainDestination ) { // Already added this chain, so break out early return; @@ -1450,115 +1481,116 @@ public class Mesh< T extends Region > { final Vector2d p = chainOrigin.getPosition(); final Vector2d r = chainDestination.getPosition().subtracted( p ); - chains.parallelStream().forEach( other -> { + chains.links.parallelStream().forEach( other -> { if ( other.getMidpoint() == chainOrigin && other.getDest() == chainMidpoint ) { // The new chain is a continuation of this chain other.next = chain; chain.previous = other; - } else if ( chainMidpoint == other.getOrigin() && chainDestination == other.getMidpoint() ) { - System.out.println( "BUG" ); - other.previous = chain; - chain.next = other; +// } else if ( chainMidpoint == other.getOrigin() && chainDestination == other.getMidpoint() ) { + // You may think that this is a valid case, but it is not possible for this to occur since the + // new chain's destination must always be greater than or equal to the destination of all existing chains. +// other.previous = chain; +// chain.next = other; + } else if ( chainOrigin == other.getMidpoint() ) { + // In the case that the new chain's origin is another chain's midpoint, + // we need to check which direction the other chain is facing. + // This is because the new chain only counts as intersecting with the other + // chain if and only if the creation of this chain would prevent the other + // chain from being linked correctly. + + final Vector2d otherDirection = other.getDest().getPosition().subtracted( other.getOrigin().getPosition() ); + final double cross = otherDirection.cross( r ); + + boolean intersects = true; + final HalfEdge firstLink = other.links.get( 0 ); + final HalfEdge secondLink = other.links.get( 1 ); + if ( firstLink.getDest() == other.getMidpoint() ) { + // Is the first link a direct connection? + intersects = interior.contains( firstLink ) ^ cross < 0; + } else if ( secondLink.getDest() == other.getDest() ) { + // Is the second link a direct connection? + intersects = interior.contains( secondLink ) ^ cross < 0; + } else { + // The chain is comprised of only vertices... + intersects = otherDirection.cross( secondLink.toVector2d() ) < 0 ^ cross < 0; + } + + if ( intersects ) { + synchronized ( intersections ) { + intersections.add( other ); + } + other.intersections.add( chain ); + } +// } else if ( chainDestination == other.getMidpoint() ) { + // For the same reason above, this case is also impossible. +// final Vector2d otherDirection = other.getDest().getPosition().subtracted( other.getOrigin().getPosition() ); +// final double cross = r.cross( otherDirection ); +// +// boolean intersects = true; +// final HalfEdge firstLink = other.links.get( 0 ); +// final HalfEdge secondLink = other.links.get( 1 ); +// if ( firstLink.getDest() == other.getMidpoint() ) { +// // Is the first link a direct connection? +// intersects = interior.contains( firstLink ) ^ cross < 0; +// } else if ( secondLink.getDest() == other.getDest() ) { +// // Is the second link a direct connection? +// intersects = interior.contains( secondLink ) ^ cross < 0; +// } else { +// // The chain is comprised of only vertices... +// intersects = otherDirection.cross( secondLink.toVector2d() ) < 0 ^ cross < 0; +// } +// +// if ( intersects ) { +// synchronized ( intersections ) { +// intersections.add( other ); +// } +// other.intersections.add( chain ); +// } + } else if ( other.getDest() == chainMidpoint ) { + // Likewise, the new chain's midpoint may be another existing chain's + // destination. We need to check for that case too. + final Vector2d otherDirection = other.getOrigin().getPosition().subtracted( other.getDest().getPosition() ).normalize(); + final double cross = r.cross( otherDirection ); + + boolean intersects = true; + if ( firstEdge.getDest() == chainMidpoint ) { + intersects = interior.contains( firstEdge ) ^ cross < 0; + } else if ( secondEdge.getDest() == chainDestination ) { + intersects = interior.contains( secondEdge ) ^ cross < 0; + } else { + intersects = r.cross( secondEdgeVec ) < 0 ^ cross < 0; + } + + if ( intersects ) { + synchronized ( intersections ) { + intersections.add( other ); + } + other.intersections.add( chain ); + } + } else if ( other.getOrigin() == chainMidpoint ) { + // Third and last case + // destination. We need to check for that case too. + final Vector2d otherDirection = other.getDest().getPosition().subtracted( other.getOrigin().getPosition() ).normalize(); + final double cross = r.cross( otherDirection ); + + boolean intersects = true; + if ( firstEdge.getDest() == chainMidpoint ) { + intersects = interior.contains( firstEdge ) ^ cross < 0; + } else if ( secondEdge.getDest() == chainDestination ) { + intersects = interior.contains( secondEdge ) ^ cross < 0; + } else { + intersects = r.cross( secondEdgeVec ) < 0 ^ cross < 0; + } + + if ( intersects ) { + synchronized ( intersections ) { + intersections.add( other ); + } + other.intersections.add( chain ); + } } else if ( compare( other.getDest(), chainOrigin ) > 0 ) { // Do the chains even overlap? - if ( chainOrigin == other.getMidpoint() ) { - // In the case that the new chain's origin is another chain's midpoint, - // we need to check which direction the other chain is facing. - // This is because the new chain only counts as intersecting with the other - // chain if and only if the creation of this chain would prevent the other - // chain from being linked correctly. - - final Vector2d otherDirection = other.getDest().getPosition().subtracted( other.getOrigin().getPosition() ); - final double cross = otherDirection.cross( r ); - - boolean intersects = true; - final HalfEdge firstLink = other.links.get( 0 ); - final HalfEdge secondLink = other.links.get( 1 ); - if ( firstLink.getDest() == other.getMidpoint() ) { - // Is the first link a direct connection? - intersects = interior.contains( firstLink ) ^ cross < 0; - } else if ( secondLink.getDest() == other.getDest() ) { - // Is the second link a direct connection? - intersects = interior.contains( secondLink ) ^ cross < 0; - } else { - // The chain is comprised of only vertices... - intersects = otherDirection.cross( secondLink.toVector2d() ) < 0 ^ cross < 0; - } - - if ( intersects ) { - synchronized ( intersections ) { - intersections.add( other ); - } - other.intersections.add( chain ); - } - } else if ( chainDestination == other.getMidpoint() ) { - System.out.println( "BUG 2" ); - final Vector2d otherDirection = other.getDest().getPosition().subtracted( other.getOrigin().getPosition() ); - final double cross = r.cross( otherDirection ); - - boolean intersects = true; - final HalfEdge firstLink = other.links.get( 0 ); - final HalfEdge secondLink = other.links.get( 1 ); - if ( firstLink.getDest() == other.getMidpoint() ) { - // Is the first link a direct connection? - intersects = interior.contains( firstLink ) ^ cross < 0; - } else if ( secondLink.getDest() == other.getDest() ) { - // Is the second link a direct connection? - intersects = interior.contains( secondLink ) ^ cross < 0; - } else { - // The chain is comprised of only vertices... - intersects = otherDirection.cross( secondLink.toVector2d() ) < 0 ^ cross < 0; - } - - if ( intersects ) { - synchronized ( intersections ) { - intersections.add( other ); - } - other.intersections.add( chain ); - } - } else if ( other.getDest() == chainMidpoint ) { - // Likewise, the new chain's midpoint may be another existing chain's - // destination. We need to check for that case too. - final Vector2d otherDirection = other.getOrigin().getPosition().subtracted( other.getDest().getPosition() ).normalize(); - final double cross = r.cross( otherDirection ); - - boolean intersects = true; - if ( firstEdge.getDest() == chainMidpoint ) { - intersects = interior.contains( firstEdge ) ^ cross < 0; - } else if ( secondEdge.getDest() == chainDestination ) { - intersects = interior.contains( secondEdge ) ^ cross < 0; - } else { - intersects = r.cross( secondEdgeVec ) < 0 ^ cross < 0; - } - - if ( intersects ) { - synchronized ( intersections ) { - intersections.add( other ); - } - other.intersections.add( chain ); - } - } else if ( other.getOrigin() == chainMidpoint ) { - // Third and last case - // destination. We need to check for that case too. - final Vector2d otherDirection = other.getDest().getPosition().subtracted( other.getOrigin().getPosition() ).normalize(); - final double cross = r.cross( otherDirection ); - - boolean intersects = true; - if ( firstEdge.getDest() == chainMidpoint ) { - intersects = interior.contains( firstEdge ) ^ cross < 0; - } else if ( secondEdge.getDest() == chainDestination ) { - intersects = interior.contains( secondEdge ) ^ cross < 0; - } else { - intersects = r.cross( secondEdgeVec ) < 0 ^ cross < 0; - } - - if ( intersects ) { - synchronized ( intersections ) { - intersections.add( other ); - } - other.intersections.add( chain ); - } - } else if ( other.getOrigin() != chainOrigin && other.getDest() != chainDestination ) { + if ( other.getOrigin() != chainOrigin && other.getDest() != chainDestination ) { // Make sure the chains don't share the same origin or destination // Perform a basic check @@ -1593,7 +1625,7 @@ public class Mesh< T extends Region > { } } } ); - chains.add( chain ); + chains.links.add( chain ); } void add( final Vertex vertex, final VisibilityRegion region ) { @@ -1655,7 +1687,7 @@ public class Mesh< T extends Region > { final Map< HalfEdge, Partition > lowerEdgeMap = new HashMap< HalfEdge, Partition >(); // Keep track of all completed chain links that we have - final Collection< Link > chains = new LinkedHashSet< Link >(); + final Collection< Collection< Link > > chainLinks = new LinkedHashSet< Collection< Link > >(); // Keep track of edges from bottom top final SortedEdgeCollection< HalfEdge > edgeCollection = new SortedEdgeCollection< HalfEdge >( Mesh::greaterThanOrEqualTo ); @@ -1712,7 +1744,7 @@ public class Mesh< T extends Region > { partition.incrementUpper( partition.lower.getSym() ); // Gather all chains from the partition - chains.addAll( partition.chains ); + chainLinks.add( partition.chains.links ); // Remove this partition from the lower edge map as well lowerEdgeMap.remove( partition.lower ); @@ -1894,11 +1926,11 @@ public class Mesh< T extends Region > { top.incrementLower( topLeft ); bottom.incrementUpper( bottomLeft ); - // Problem... need to make bottom chains and top chains the same because merging regions must use the same chain map... - // TODO Do it... make a reverse region map for each chain... it only makes some sense... - // And then technically just combine all chain maps at the end because that also only makes sense - // Fixes the issues... - top.chains.addAll( bottom.chains ); + // Merge the refs + if ( top.chains.links != bottom.chains.links ) { + top.chains.links.addAll( bottom.chains.links ); + bottom.chains.links = top.chains.links; + } // Merge the two partitions top.add( bottom ); @@ -1985,7 +2017,10 @@ public class Mesh< T extends Region > { throw new IllegalStateException( "Dangling partitions!" ); } - return chains; + final Collection< Link > aggregateLinks = new LinkedHashSet< Link >(); + chainLinks.forEach( aggregateLinks::addAll ); + + return aggregateLinks; } private static Collection< Link > maximizeChains( final Collection< Link > chains ) { @@ -2420,7 +2455,6 @@ public class Mesh< T extends Region > { if ( upper == null ) { throw new IllegalStateException( "Lower does not have an upper edge!" ); } if ( !interior.contains( upper.getSym() ) ) { - System.out.println( upper ); throw new IllegalStateException( "Polygon is not simple!" ); } else if ( upper.getOrigin() == event || lower.getOrigin() == event ) { throw new IllegalStateException( "Vertex is invalid!" ); diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java index e465e6f..edbde8e 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java @@ -141,8 +141,8 @@ public class MeshingTest2 extends JPanel { // System.out.println( "Remaining: " + limit ); // } // } - final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-2,1" ) ); - masterPlanes.add( planes.get( 218 ) ); + final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-4,-6" ) ); + masterPlanes.add( planes.get( 177 ) ); // masterPlanes.add( getTestPlane() ); @@ -277,30 +277,38 @@ public class MeshingTest2 extends JPanel { public void onPreChainMergeEvent( Collection< Chain > chains ) { data.selectedChains = chains; - final List< Chain > sorted = new ArrayList< Chain >( chains ); - Collections.sort( sorted, ( a, b ) -> { - final double diff = a.getStart().getX() - b.getStart().getX(); - if ( Math.abs( diff ) < 1e-7 ) { - return Double.compare( a.getStart().getY(), b.getStart().getY() ); - } - return Double.compare( diff, 0 ); - } ); - for ( Chain chain : sorted ) { - System.out.println( "c: " + chain.getPoints() ); - } - - final Set< Point > points = new HashSet< Point >(); - for ( Chain chain : chains ) { - for ( int i = 1; i < chain.getPoints().size() - 1; ++i ) { - if ( !points.add( chain.getPoints().get( i ) ) ) { - System.out.println( "Intersection at " + chain.getPoints().get( i ) ); - } - } - } +// final List< Chain > sorted = new ArrayList< Chain >( chains ); +// Collections.sort( sorted, ( a, b ) -> { +// final double diff = a.getStart().getX() - b.getStart().getX(); +// if ( Math.abs( diff ) < 1e-7 ) { +// return Double.compare( a.getStart().getY(), b.getStart().getY() ); +// } +// return Double.compare( diff, 0 ); +// } ); +// for ( Chain chain : sorted ) { +// System.out.println( "c: " + chain.getPoints() ); +// } +// +// final Set< Point > points = new HashSet< Point >(); +// for ( Chain chain : chains ) { +// for ( int i = 1; i < chain.getPoints().size() - 1; ++i ) { +// if ( !points.add( chain.getPoints().get( i ) ) ) { +// System.out.println( "Intersection at " + chain.getPoints().get( i ) ); +// } +// } +// } + } + + @Override + public void onPartitionEvent( Collection< Polygon > polygons ) { + data.polygons = polygons; } } ); - - data.polygons = mesh.mesh(); + + final Collection< Polygon > triangles = mesh.mesh(); + if ( !triangles.isEmpty() ) { + data.polygons = triangles; + } // final List< Point > points = new ArrayList< Point >( data.vertices ); // Collections.sort( points, ( a, b ) -> { diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java index 1652dbd..01489a2 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java @@ -29,30 +29,30 @@ public class MeshingTest3 { public static void main( String[] args ) { if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) { System.out.println( "Found " + CHUNK_DIR.list().length + " files" ); -// for ( int i = 0; i < CHUNK_DIR.listFiles().length; ++i ) { -// final File file = CHUNK_DIR.listFiles()[ i ]; -// System.out.println( "Meshing " + file + "\t" + ( i + 1 ) + "/" + CHUNK_DIR.listFiles().length ); -// final List< Plane > planes = mesh( file ); -// planes.parallelStream().forEach( p -> { -// try { -// process( p ); -// } catch ( IllegalStateException e ) { -// e.printStackTrace(); -// -// semiProcess( p ); -// -// System.exit( 1 ); -// } -// } ); -// } + for ( int i = 0; i < CHUNK_DIR.listFiles().length; ++i ) { + final File file = CHUNK_DIR.listFiles()[ i ]; + System.out.println( "Meshing " + file + "\t" + ( i + 1 ) + "/" + CHUNK_DIR.listFiles().length ); + final List< Plane > planes = mesh( file ); + planes.parallelStream().forEach( p -> { + try { + process( p ); + } catch ( IllegalStateException e ) { + e.printStackTrace(); + + semiProcess( p ); + + System.exit( 1 ); + } + } ); + } - final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-2,1" ) ); +// final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-4,-6" ) ); // 123 // for ( int i = 0; i < planes.size(); ++i ) { // System.out.println( "Meshing plane " + i ); // process( planes.get( i ) ); // } - process( planes.get( 218 ) ); +// process( planes.get( 177 ) ); } else { System.err.println( "No such directory exists: " + CHUNK_DIR ); }