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;
}
// 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!" );