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
This commit is contained in:
2025-05-23 05:36:00 -04:00
parent 586fb04c02
commit 702a4288b2
3 changed files with 206 additions and 164 deletions

View File

@@ -353,6 +353,11 @@ public class Mesh< T extends Region > {
break; 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 ) { for ( final HalfEdge edge : other ) {
scannedEdges.add( edge ); 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 ); final TreeSet< Vertex > fuzzyVertices = new TreeSet< Vertex >( Mesh::fuzzyCompare );
fuzzyVertices.addAll( newVertices.values() ); fuzzyVertices.addAll( newVertices.values() );
@@ -1053,15 +1061,31 @@ public class Mesh< T extends Region > {
mergeChains( chains, edges ); mergeChains( chains, edges );
// return Collections.emptySet();
final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare ); final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare );
vertices.addAll( fuzzyVertices ); vertices.addAll( fuzzyVertices );
return Collections.emptySet(); final Collection< EdgePolygon > polys = partitionMonotone( vertices, edges );
// return partitionMonotone( vertices, edges ).parallelStream()
// .map( Mesh::triangulate ) if ( handlers != null ) {
// .flatMap( p -> p.parallelStream() ) final Collection< Polygon > polygons = new LinkedHashSet< Polygon >();
// .collect( Collectors.toSet() );
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 * 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 * 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. // Keep track of all chains that were formed in this current partition.
// For all new chains, see what previous chains it may intersect with // 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 ) { Partition( final HalfEdge lower, final HalfEdge upper ) {
this.lower = lower; this.lower = lower;
@@ -1432,7 +1463,7 @@ public class Mesh< T extends Region > {
final Vertex chainMidpoint = chain.getMidpoint(); final Vertex chainMidpoint = chain.getMidpoint();
final Vertex chainDestination = chain.getDest(); final Vertex chainDestination = chain.getDest();
for ( final Link other : chains ) { for ( final Link other : chains.links ) {
if ( other.getOrigin() == chainOrigin && other.getDest() == chainDestination ) { if ( other.getOrigin() == chainOrigin && other.getDest() == chainDestination ) {
// Already added this chain, so break out early // Already added this chain, so break out early
return; return;
@@ -1450,115 +1481,116 @@ public class Mesh< T extends Region > {
final Vector2d p = chainOrigin.getPosition(); final Vector2d p = chainOrigin.getPosition();
final Vector2d r = chainDestination.getPosition().subtracted( p ); final Vector2d r = chainDestination.getPosition().subtracted( p );
chains.parallelStream().forEach( other -> { chains.links.parallelStream().forEach( other -> {
if ( other.getMidpoint() == chainOrigin && other.getDest() == chainMidpoint ) { if ( other.getMidpoint() == chainOrigin && other.getDest() == chainMidpoint ) {
// The new chain is a continuation of this chain // The new chain is a continuation of this chain
other.next = chain; other.next = chain;
chain.previous = other; chain.previous = other;
} else if ( chainMidpoint == other.getOrigin() && chainDestination == other.getMidpoint() ) { // } else if ( chainMidpoint == other.getOrigin() && chainDestination == other.getMidpoint() ) {
System.out.println( "BUG" ); // You may think that this is a valid case, but it is not possible for this to occur since the
other.previous = chain; // new chain's destination must always be greater than or equal to the destination of all existing chains.
chain.next = other; // 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 ) { } else if ( compare( other.getDest(), chainOrigin ) > 0 ) {
// Do the chains even overlap? // Do the chains even overlap?
if ( chainOrigin == other.getMidpoint() ) { if ( other.getOrigin() != chainOrigin && other.getDest() != chainDestination ) {
// 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 ) {
// Make sure the chains don't share the same origin or destination // Make sure the chains don't share the same origin or destination
// Perform a basic check // 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 ) { 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 >(); final Map< HalfEdge, Partition > lowerEdgeMap = new HashMap< HalfEdge, Partition >();
// Keep track of all completed chain links that we have // 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 // Keep track of edges from bottom top
final SortedEdgeCollection< HalfEdge > edgeCollection = new SortedEdgeCollection< HalfEdge >( Mesh::greaterThanOrEqualTo ); final SortedEdgeCollection< HalfEdge > edgeCollection = new SortedEdgeCollection< HalfEdge >( Mesh::greaterThanOrEqualTo );
@@ -1712,7 +1744,7 @@ public class Mesh< T extends Region > {
partition.incrementUpper( partition.lower.getSym() ); partition.incrementUpper( partition.lower.getSym() );
// Gather all chains from the partition // 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 // Remove this partition from the lower edge map as well
lowerEdgeMap.remove( partition.lower ); lowerEdgeMap.remove( partition.lower );
@@ -1894,11 +1926,11 @@ public class Mesh< T extends Region > {
top.incrementLower( topLeft ); top.incrementLower( topLeft );
bottom.incrementUpper( bottomLeft ); bottom.incrementUpper( bottomLeft );
// Problem... need to make bottom chains and top chains the same because merging regions must use the same chain map... // Merge the refs
// TODO Do it... make a reverse region map for each chain... it only makes some sense... if ( top.chains.links != bottom.chains.links ) {
// And then technically just combine all chain maps at the end because that also only makes sense top.chains.links.addAll( bottom.chains.links );
// Fixes the issues... bottom.chains.links = top.chains.links;
top.chains.addAll( bottom.chains ); }
// Merge the two partitions // Merge the two partitions
top.add( bottom ); top.add( bottom );
@@ -1985,7 +2017,10 @@ public class Mesh< T extends Region > {
throw new IllegalStateException( "Dangling partitions!" ); 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 ) { private static Collection< Link > maximizeChains( final Collection< Link > chains ) {
@@ -2420,7 +2455,6 @@ public class Mesh< T extends Region > {
if ( upper == null ) { if ( upper == null ) {
throw new IllegalStateException( "Lower does not have an upper edge!" ); throw new IllegalStateException( "Lower does not have an upper edge!" );
} if ( !interior.contains( upper.getSym() ) ) { } if ( !interior.contains( upper.getSym() ) ) {
System.out.println( upper );
throw new IllegalStateException( "Polygon is not simple!" ); throw new IllegalStateException( "Polygon is not simple!" );
} else if ( upper.getOrigin() == event || lower.getOrigin() == event ) { } else if ( upper.getOrigin() == event || lower.getOrigin() == event ) {
throw new IllegalStateException( "Vertex is invalid!" ); throw new IllegalStateException( "Vertex is invalid!" );

View File

@@ -141,8 +141,8 @@ public class MeshingTest2 extends JPanel {
// System.out.println( "Remaining: " + limit ); // System.out.println( "Remaining: " + limit );
// } // }
// } // }
final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-2,1" ) ); final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-4,-6" ) );
masterPlanes.add( planes.get( 218 ) ); masterPlanes.add( planes.get( 177 ) );
// masterPlanes.add( getTestPlane() ); // masterPlanes.add( getTestPlane() );
@@ -277,30 +277,38 @@ public class MeshingTest2 extends JPanel {
public void onPreChainMergeEvent( Collection< Chain > chains ) { public void onPreChainMergeEvent( Collection< Chain > chains ) {
data.selectedChains = chains; data.selectedChains = chains;
final List< Chain > sorted = new ArrayList< Chain >( chains ); // final List< Chain > sorted = new ArrayList< Chain >( chains );
Collections.sort( sorted, ( a, b ) -> { // Collections.sort( sorted, ( a, b ) -> {
final double diff = a.getStart().getX() - b.getStart().getX(); // final double diff = a.getStart().getX() - b.getStart().getX();
if ( Math.abs( diff ) < 1e-7 ) { // if ( Math.abs( diff ) < 1e-7 ) {
return Double.compare( a.getStart().getY(), b.getStart().getY() ); // return Double.compare( a.getStart().getY(), b.getStart().getY() );
} // }
return Double.compare( diff, 0 ); // return Double.compare( diff, 0 );
} ); // } );
for ( Chain chain : sorted ) { // for ( Chain chain : sorted ) {
System.out.println( "c: " + chain.getPoints() ); // System.out.println( "c: " + chain.getPoints() );
} // }
//
final Set< Point > points = new HashSet< Point >(); // final Set< Point > points = new HashSet< Point >();
for ( Chain chain : chains ) { // for ( Chain chain : chains ) {
for ( int i = 1; i < chain.getPoints().size() - 1; ++i ) { // for ( int i = 1; i < chain.getPoints().size() - 1; ++i ) {
if ( !points.add( chain.getPoints().get( i ) ) ) { // if ( !points.add( chain.getPoints().get( i ) ) ) {
System.out.println( "Intersection at " + 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 ); // final List< Point > points = new ArrayList< Point >( data.vertices );
// Collections.sort( points, ( a, b ) -> { // Collections.sort( points, ( a, b ) -> {

View File

@@ -29,30 +29,30 @@ public class MeshingTest3 {
public static void main( String[] args ) { public static void main( String[] args ) {
if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) { if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) {
System.out.println( "Found " + CHUNK_DIR.list().length + " files" ); System.out.println( "Found " + CHUNK_DIR.list().length + " files" );
// for ( int i = 0; i < CHUNK_DIR.listFiles().length; ++i ) { for ( int i = 0; i < CHUNK_DIR.listFiles().length; ++i ) {
// final File file = CHUNK_DIR.listFiles()[ i ]; final File file = CHUNK_DIR.listFiles()[ i ];
// System.out.println( "Meshing " + file + "\t" + ( i + 1 ) + "/" + CHUNK_DIR.listFiles().length ); System.out.println( "Meshing " + file + "\t" + ( i + 1 ) + "/" + CHUNK_DIR.listFiles().length );
// final List< Plane > planes = mesh( file ); final List< Plane > planes = mesh( file );
// planes.parallelStream().forEach( p -> { planes.parallelStream().forEach( p -> {
// try { try {
// process( p ); process( p );
// } catch ( IllegalStateException e ) { } catch ( IllegalStateException e ) {
// e.printStackTrace(); e.printStackTrace();
//
// semiProcess( p ); semiProcess( p );
//
// System.exit( 1 ); 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 ) { // for ( int i = 0; i < planes.size(); ++i ) {
// System.out.println( "Meshing plane " + i ); // System.out.println( "Meshing plane " + i );
// process( planes.get( i ) ); // process( planes.get( i ) );
// } // }
process( planes.get( 218 ) ); // process( planes.get( 177 ) );
} else { } else {
System.err.println( "No such directory exists: " + CHUNK_DIR ); System.err.println( "No such directory exists: " + CHUNK_DIR );
} }