Fixed duplicate collinear edge bug

This commit is contained in:
2025-05-17 16:05:10 -04:00
parent 2ff86b88a4
commit 7e39c48d76
2 changed files with 118 additions and 38 deletions

View File

@@ -983,6 +983,9 @@ public class Mesh< T extends Region > {
// TODO Make not static // TODO Make not static
public static class Chain { public static class Chain {
List< HalfEdge > links; List< HalfEdge > links;
Set< Chain > intersections = new HashSet< Chain >();
Chain previous;
Chain next;
Chain( final Collection< HalfEdge > links ) { Chain( final Collection< HalfEdge > links ) {
this.links = new ArrayList< HalfEdge >( links ); this.links = new ArrayList< HalfEdge >( links );
@@ -992,6 +995,10 @@ public class Mesh< T extends Region > {
return links.get( 0 ).getOrigin(); return links.get( 0 ).getOrigin();
} }
public Vertex getMidpoint() {
return links.get( 1 ).getOrigin();
}
public Vertex getDest() { public Vertex getDest() {
return links.get( 2 ).getOrigin(); return links.get( 2 ).getOrigin();
} }
@@ -1089,6 +1096,9 @@ public class Mesh< T extends Region > {
// may be merged into a single partition, and each partition // may be merged into a single partition, and each partition
// may have a visibility region for the same vertex. // may have a visibility region for the same vertex.
Map< Vertex, Collection< VisibilityRegion > > regionMap = new HashMap< Vertex, Collection< VisibilityRegion > >(); Map< Vertex, Collection< VisibilityRegion > > regionMap = new HashMap< Vertex, Collection< VisibilityRegion > >();
// 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< Chain > chains = new HashSet< Chain >(); Collection< Chain > chains = new HashSet< Chain >();
Partition( final HalfEdge lower, final HalfEdge upper ) { Partition( final HalfEdge lower, final HalfEdge upper ) {
@@ -1131,10 +1141,8 @@ public class Mesh< T extends Region > {
// and we don't really care about what comes after // and we don't really care about what comes after
links.add( edge ); links.add( edge );
// TODO Check for intersections
final Chain chain = new Chain( links ); final Chain chain = new Chain( links );
chains.add( chain ); addChain( chain );
} }
} else if ( upperCross < region.upper ) { } else if ( upperCross < region.upper ) {
@@ -1148,15 +1156,17 @@ public class Mesh< T extends Region > {
// The event vertex is not collinear with the previous upper // The event vertex is not collinear with the previous upper
// vertex, but it _is_ collinear with the previous lower vertex! // vertex, but it _is_ collinear with the previous lower vertex!
// Remove the previous links // Remove the previous links, but only if the upper is not exactly equal to the lower
if ( region.upper != region.lower ) {
region.links.remove( region.upper ); region.links.remove( region.upper );
region.upper = region.lower;
}
// There should be exactly 1 link, the lower region's link // There should be exactly 1 link, the lower region's link
if ( region.links.size() != 1 ) { if ( region.links.size() != 1 ) {
throw new IllegalStateException( "Dangling link!" ); throw new IllegalStateException( "Dangling link!" );
} }
region.upper = region.lower;
final List< HalfEdge > links = region.links.get( region.upper ); final List< HalfEdge > links = region.links.get( region.upper );
@@ -1172,10 +1182,8 @@ public class Mesh< T extends Region > {
// and we don't really care about what comes after // and we don't really care about what comes after
links.add( edge ); links.add( edge );
// TODO Check for intersections
final Chain chain = new Chain( links ); final Chain chain = new Chain( links );
chains.add( chain ); addChain( chain );
// Remove this vertex since we know the upper and lower // Remove this vertex since we know the upper and lower
// bounds have already converged, and met at this collinear // bounds have already converged, and met at this collinear
@@ -1286,10 +1294,8 @@ public class Mesh< T extends Region > {
// and we don't really care about what comes after // and we don't really care about what comes after
links.add( edge ); links.add( edge );
// TODO Check for intersections
final Chain chain = new Chain( links ); final Chain chain = new Chain( links );
chains.add( chain ); addChain( chain );
} }
} else if ( lowerCross > region.lower ) { } else if ( lowerCross > region.lower ) {
// Is the new cross value greater than the previous cross value? // Is the new cross value greater than the previous cross value?
@@ -1302,16 +1308,17 @@ public class Mesh< T extends Region > {
// The event vertex is not collinear with the previous upper // The event vertex is not collinear with the previous upper
// vertex, but it _is_ collinear with the previous upper vertex! // vertex, but it _is_ collinear with the previous upper vertex!
// Remove the previous links // Remove the previous links, but only if the upper is not exactly equal to the lower
if ( region.upper != region.lower ) {
region.links.remove( region.lower ); region.links.remove( region.lower );
region.lower = region.upper;
}
// There should be exactly 1 link, the lower region's link // There should be exactly 1 link, the lower region's link
if ( region.links.size() != 1 ) { if ( region.links.size() != 1 ) {
throw new IllegalStateException( "Dangling link!" ); throw new IllegalStateException( "Dangling link!" );
} }
region.lower = region.upper;
final List< HalfEdge > links = region.links.get( region.lower ); final List< HalfEdge > links = region.links.get( region.lower );
if ( links == null ) { if ( links == null ) {
@@ -1326,10 +1333,8 @@ public class Mesh< T extends Region > {
// and we don't really care about what comes after // and we don't really care about what comes after
links.add( edge ); links.add( edge );
// TODO Check for intersections
final Chain chain = new Chain( links ); final Chain chain = new Chain( links );
chains.add( chain ); addChain( chain );
// Remove this vertex since we know the upper and lower // Remove this vertex since we know the upper and lower
// bounds have already converged, and met at this collinear // bounds have already converged, and met at this collinear
@@ -1384,6 +1389,58 @@ public class Mesh< T extends Region > {
} }
} }
/*
* Check all previous chains for intersections and stuff
*/
void addChain( final Chain chain ) {
final Vertex chainOrigin = chain.getOrigin();
final Vertex chainMidpoint = chain.getMidpoint();
final Vertex chainDestination = chain.getDest();
final Vector2d p = chainOrigin.getPosition();
final Vector2d r = chainDestination.getPosition().subtracted( p );
for ( final Chain other : chains ) {
// The new chain is a continuation of this chain
if ( other.getMidpoint() == chainOrigin && other.getDest() == chainMidpoint ) {
other.next = chain;
chain.previous = other;
} else if ( compare( other.getDest(), chainOrigin ) > 0 ) {
// Do the chains even overlap?
if ( other.getOrigin() != chainOrigin && other.getDest() != chainDestination ) {
// Make sure the chains don't share the same origin or destination
// Perform a basic check
// Both edges may intersect somewhere in the middle
final Vector2d q = other.getOrigin().getPosition();
final Vector2d s = other.getDest().getPosition().subtracted( q );
final Vector2d pq = q.subtracted( p );
final double pqr = pq.cross( r );
final double rs = r.cross( s );
// Check if parallel
if ( rs != 0 ) {
// Calculate t and u
final double t = pq.cross( s ) / rs;
final double u = pqr / rs;
final double uTol = VERTEX_TOLERANCE / s.length();
// Do the line segments intersect
// Allow for overlap
if ( t >= 0 && t <= 1 && u > -uTol && u < ( 1 + uTol ) ) {
// The chains intersect! We don't care about where.
chain.intersections.add( other );
other.intersections.add( chain );
}
}
}
}
}
chains.add( chain );
}
void add( final Vertex vertex, final VisibilityRegion region ) { void add( final Vertex vertex, final VisibilityRegion region ) {
Collection< VisibilityRegion > regions; Collection< VisibilityRegion > regions;
if ( regionMap.containsKey( vertex ) ) { if ( regionMap.containsKey( vertex ) ) {
@@ -1579,6 +1636,14 @@ public class Mesh< T extends Region > {
if ( region.lower != MIN_CROSS ) { if ( region.lower != MIN_CROSS ) {
copyRegion.lower = region.lower; copyRegion.lower = region.lower;
copyRegion.links.put( region.lower, region.links.remove( region.lower ) ); copyRegion.links.put( region.lower, region.links.remove( region.lower ) );
// Obviously, whenever we remove a region from the links, we need to check
// if the region even has any more links... otherwise it's clearly dead and
// needs to be removed entirely.
if ( region.links.isEmpty() ) {
regionIt.remove();
}
region.lower = MIN_CROSS; region.lower = MIN_CROSS;
} }
@@ -1646,11 +1711,11 @@ public class Mesh< T extends Region > {
throw new IllegalStateException( "Vertex has less than 2 left edges!" ); throw new IllegalStateException( "Vertex has less than 2 left edges!" );
} }
final HalfEdge topLeft = leftGoing.get( 0 ).getSym(); final HalfEdge topLeft = leftGoing.get( 0 );
final HalfEdge bottomLeft = leftGoing.get( leftGoing.size() - 1 ).getSym(); final HalfEdge bottomLeft = leftGoing.get( leftGoing.size() - 1 );
final Partition top = lowerEdgeMap.remove( topLeft ); final Partition top = lowerEdgeMap.remove( topLeft.getSym() );
final Partition bottom = upperEdgeMap.remove( bottomLeft ); final Partition bottom = upperEdgeMap.remove( bottomLeft.getSym() );
if ( top == null ) { if ( top == null ) {
throw new IllegalStateException( "Upper partition not found!" ); throw new IllegalStateException( "Upper partition not found!" );
@@ -1658,7 +1723,7 @@ public class Mesh< T extends Region > {
throw new IllegalStateException( "Lower partition not found!" ); throw new IllegalStateException( "Lower partition not found!" );
} }
chains.addAll( bottom.chains ); top.chains.addAll( bottom.chains );
lowerEdgeMap.remove( bottom.upper ); lowerEdgeMap.remove( bottom.upper );
// Merge the top and bottom partition // Merge the top and bottom partition
@@ -1668,14 +1733,14 @@ public class Mesh< T extends Region > {
lowerEdgeMap.put( top.lower, top ); lowerEdgeMap.put( top.lower, top );
// Update all visibility regions for the top and bottom partitions, separately // Update all visibility regions for the top and bottom partitions, separately
top.incrementLower( topLeft.getSym() ); top.incrementLower( topLeft );
bottom.incrementUpper( bottomLeft.getSym() ); bottom.incrementUpper( bottomLeft );
// Merge the two partitions // Merge the two partitions
top.add( bottom ); top.add( bottom );
// Add a new visibility region // Add a new visibility region
top.add( event, new VisibilityRegion( bottomLeft.getSym(), topLeft.getSym() ) ); top.add( event, new VisibilityRegion( bottomLeft, topLeft ) );
} }
// This vertex has both left and right going edges // This vertex has both left and right going edges
@@ -1688,13 +1753,13 @@ public class Mesh< T extends Region > {
if ( interior.contains( lower ) ) { if ( interior.contains( lower ) ) {
// There exists a partition below this vertex // There exists a partition below this vertex
final HalfEdge left = leftGoing.get( leftGoing.size() - 1 ).getSym(); final HalfEdge left = leftGoing.get( leftGoing.size() - 1 );
final HalfEdge right = rightGoing.get( 0 ); final HalfEdge right = rightGoing.get( 0 );
final Partition partition = lowerEdgeMap.get( lower ); final Partition partition = lowerEdgeMap.get( lower );
if ( partition != null ) { if ( partition != null ) {
if ( partition.upper != left ) { if ( partition.upper != left.getSym() ) {
throw new IllegalStateException( "Partition upper edge is inconsistent!" ); throw new IllegalStateException( "Partition upper edge is inconsistent!" );
} }
@@ -1708,7 +1773,7 @@ public class Mesh< T extends Region > {
partition.incrementUpper( right ); partition.incrementUpper( right );
// Add a new region for this vertex // Add a new region for this vertex
partition.add( event, new VisibilityRegion( left.getSym(), right ) ); partition.add( event, new VisibilityRegion( left, right ) );
} else { } else {
throw new IllegalStateException( "Interior edge does not have a partition!" ); throw new IllegalStateException( "Interior edge does not have a partition!" );
} }
@@ -1716,13 +1781,13 @@ public class Mesh< T extends Region > {
if ( upper != null && interior.contains( upper.getSym() ) ) { if ( upper != null && interior.contains( upper.getSym() ) ) {
// There exists a partition above this vertex // There exists a partition above this vertex
final HalfEdge left = leftGoing.get( 0 ).getSym(); final HalfEdge left = leftGoing.get( 0 );
final HalfEdge right = rightGoing.get( rightGoing.size() - 1 ); final HalfEdge right = rightGoing.get( rightGoing.size() - 1 );
final Partition partition = upperEdgeMap.get( upper ); final Partition partition = upperEdgeMap.get( upper );
if ( partition != null ) { if ( partition != null ) {
if ( partition.lower != left ) { if ( partition.lower != left.getSym() ) {
throw new IllegalStateException( "Partition lower edge is inconsistent!" ); throw new IllegalStateException( "Partition lower edge is inconsistent!" );
} }
@@ -1735,7 +1800,7 @@ public class Mesh< T extends Region > {
partition.incrementLower( right ); partition.incrementLower( right );
// Add a new region for this vertex // Add a new region for this vertex
partition.add( event, new VisibilityRegion( left.getSym(), right ) ); partition.add( event, new VisibilityRegion( right, left ) );
} else { } else {
throw new IllegalStateException( "Interior edge does not have a partition!" ); throw new IllegalStateException( "Interior edge does not have a partition!" );
} }
@@ -1759,7 +1824,17 @@ public class Mesh< T extends Region > {
System.out.println( "Found links: " + chains.size() ); System.out.println( "Found links: " + chains.size() );
Map< Vertex, Collection< Vertex > > mappings = new HashMap< Vertex, Collection< Vertex > >(); Map< Vertex, Collection< Vertex > > mappings = new HashMap< Vertex, Collection< Vertex > >();
for ( final Chain c : chains ) { for ( final Chain c : chains ) {
System.out.println( c.getOrigin() + "\t to \t" + c.getDest() ); System.out.println( c + ":\t" + c.links.get( 0 ) + "\t to \t" + c.links.get( 1 ) + "\t to \t" + c.links.get( 2 ) );
// System.out.println( "\tIntersects with: " + c.intersections.size() );
for ( final Chain i : c.intersections ) {
// System.out.println( "\t\t" + i + ":\t" + i.getOrigin() + "\t to \t" + i.getDest() );
}
if ( c.previous != null ) {
// System.out.println( "\tPrev: " + c.previous );
}
if ( c.next != null ) {
// System.out.println( "\tNext: " + c.next );
}
Collection< Vertex > destinations; Collection< Vertex > destinations;
if ( mappings.containsKey( c.getOrigin() ) ) { if ( mappings.containsKey( c.getOrigin() ) ) {

View File

@@ -279,17 +279,22 @@ public class MeshingTest2 extends JPanel {
Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
mesh.addPolygon( new Polygon( Arrays.asList( mesh.addPolygon( new Polygon( Arrays.asList(
new Point( -1, -1 ), new Point( -1, 0 ),
new Point( 1, -2 ),
new Point( 1, -1 ), new Point( 1, -1 ),
new Point( 1, 1 ), new Point( 2, -1 ),
new Point( -1, 1 ) new Point( 1, 0 ),
new Point( 1, 1 )
) ), RegionRuleWinding.CLOCKWISE ); ) ), RegionRuleWinding.CLOCKWISE );
mesh.simplify(); mesh.simplify();
mesh.generateRegions(); mesh.generateRegions();
return mesh.mesh(); final Collection< EdgePolygon > polys = mesh.mesh();
chains = mesh.chains;
return polys;
} }
private static List< Facet > generateFacetsFor( AABB box ) { private static List< Facet > generateFacetsFor( AABB box ) {