Use maps for keeping track of partitions

This commit is contained in:
2025-05-17 03:10:02 -04:00
parent 9e010d3fe1
commit 2ff86b88a4

View File

@@ -980,6 +980,7 @@ public class Mesh< T extends Region > {
.collect( Collectors.toSet() ); .collect( Collectors.toSet() );
} }
// TODO Make not static
public static class Chain { public static class Chain {
List< HalfEdge > links; List< HalfEdge > links;
@@ -1058,9 +1059,6 @@ public class Mesh< T extends Region > {
} }
} }
// Keep track of all completed chain links that we have
final Collection< Chain > chains = new HashSet< Chain >();
/** /**
* 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
@@ -1091,6 +1089,7 @@ 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 > >();
Collection< Chain > chains = new HashSet< Chain >();
Partition( final HalfEdge lower, final HalfEdge upper ) { Partition( final HalfEdge lower, final HalfEdge upper ) {
this.lower = lower; this.lower = lower;
@@ -1434,11 +1433,17 @@ public class Mesh< T extends Region > {
} }
} }
// TODO Use a map for faster searching // We use two maps to keep track of which partition is associated with which upper/lower edge
// TODO Can keep two maps, a lower and upper map mapping edges to // so that we can find and confirm that a partition exists in constant time. Naturally,
// a particular partition, which will let us keep constant time // they must be kept up to date at all times, but that's not too difficult.
// for partition related operations. // Since each partition has exactly one upper and lower edge, both maps should have the
final Collection< Partition > partitions = new HashSet< Partition >(); // same size, along with the same partitions, which means we can use either to loop through
// all existing partitions at any given moment
final Map< HalfEdge, Partition > upperEdgeMap = new HashMap< HalfEdge, Partition >();
final Map< HalfEdge, Partition > lowerEdgeMap = new HashMap< HalfEdge, Partition >();
// Keep track of all completed chain links that we have
final Collection< Chain > chains = new HashSet< Chain >();
// 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 );
@@ -1484,8 +1489,9 @@ public class Mesh< T extends Region > {
if ( !leftGoing.isEmpty() ) { if ( !leftGoing.isEmpty() ) {
// Terminate any partitions // Terminate any partitions
for ( final Iterator< Partition > it = partitions.iterator(); it.hasNext(); ) { for ( final Iterator< Entry< HalfEdge, Partition > > it = upperEdgeMap.entrySet().iterator(); it.hasNext(); ) {
final Partition partition = it.next(); final Entry< HalfEdge, Partition > entry = it.next();
final Partition partition = entry.getValue();
// Does both the upper and lower region of this partition end at this vertex? // Does both the upper and lower region of this partition end at this vertex?
if ( partition.upper.getDest() == event && partition.lower.getDest() == event ) { if ( partition.upper.getDest() == event && partition.lower.getDest() == event ) {
@@ -1493,6 +1499,11 @@ public class Mesh< T extends Region > {
// a difference which one we do since they all converge at the same location. // a difference which one we do since they all converge at the same location.
partition.incrementUpper( partition.lower.getSym() ); partition.incrementUpper( partition.lower.getSym() );
// Gather all chains from the partition
chains.addAll( partition.chains );
// Remove this partition from the lower edge map as well
lowerEdgeMap.remove( partition.lower );
it.remove(); it.remove();
} }
} }
@@ -1502,9 +1513,8 @@ public class Mesh< T extends Region > {
// the partition containing this vertex must // the partition containing this vertex must
// be split into two smaller partitions. // be split into two smaller partitions.
final Optional< Partition > opt = partitions.parallelStream().filter( p -> p.lower == lower ).findAny(); final Partition toSplit = lowerEdgeMap.remove( lower );
if ( opt.isPresent() ) { if ( toSplit != null ) {
final Partition toSplit = opt.get();
if ( rightGoing.size() < 2 ) { if ( rightGoing.size() < 2 ) {
throw new IllegalStateException( "Vertex has less than 2 right edges!" ); throw new IllegalStateException( "Vertex has less than 2 right edges!" );
@@ -1514,8 +1524,16 @@ public class Mesh< T extends Region > {
final HalfEdge newLower = rightGoing.get( rightGoing.size() - 1 ); final HalfEdge newLower = rightGoing.get( rightGoing.size() - 1 );
final Partition copy = new Partition( lower, newUpper ); final Partition copy = new Partition( lower, newUpper );
// Copy all chains from the previous region in case of intersection
copy.chains.addAll( toSplit.chains );
toSplit.lower = newLower; toSplit.lower = newLower;
lowerEdgeMap.put( newLower, toSplit );
lowerEdgeMap.put( lower, copy );
upperEdgeMap.put( newUpper, copy );
/* /*
* A simple method for splitting and updating the visibility regions * A simple method for splitting and updating the visibility regions
* is to add all the visibility regions from toSplit to the copy, then * is to add all the visibility regions from toSplit to the copy, then
@@ -1623,8 +1641,6 @@ public class Mesh< T extends Region > {
// means there exists 2 partitions which // means there exists 2 partitions which
// are converging and need to be merged // are converging and need to be merged
// into a single partition. // into a single partition.
Partition top = null;
Partition bottom = null;
if ( leftGoing.size() < 2 ) { if ( leftGoing.size() < 2 ) {
throw new IllegalStateException( "Vertex has less than 2 left edges!" ); throw new IllegalStateException( "Vertex has less than 2 left edges!" );
@@ -1632,20 +1648,9 @@ public class Mesh< T extends Region > {
final HalfEdge topLeft = leftGoing.get( 0 ).getSym(); final HalfEdge topLeft = leftGoing.get( 0 ).getSym();
final HalfEdge bottomLeft = leftGoing.get( leftGoing.size() - 1 ).getSym(); final HalfEdge bottomLeft = leftGoing.get( leftGoing.size() - 1 ).getSym();
for ( final Iterator< Partition > it = partitions.iterator(); it.hasNext() && ( top == null || bottom == null ); ) {
final Partition partition = it.next();
// TODO Benefit from upper and lower partition mappings final Partition top = lowerEdgeMap.remove( topLeft );
if ( partition.upper == bottomLeft ) { final Partition bottom = upperEdgeMap.remove( bottomLeft );
bottom = partition;
// Delete the bottom partition
// No particular reason for why the bottom over the top
it.remove();
} else if ( partition.lower == topLeft ) {
top = partition;
}
}
if ( top == null ) { if ( top == null ) {
throw new IllegalStateException( "Upper partition not found!" ); throw new IllegalStateException( "Upper partition not found!" );
@@ -1653,9 +1658,15 @@ public class Mesh< T extends Region > {
throw new IllegalStateException( "Lower partition not found!" ); throw new IllegalStateException( "Lower partition not found!" );
} }
chains.addAll( bottom.chains );
lowerEdgeMap.remove( bottom.upper );
// Merge the top and bottom partition // Merge the top and bottom partition
top.lower = bottom.lower; top.lower = bottom.lower;
// Update the lower edge map
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.getSym() );
bottom.incrementUpper( bottomLeft.getSym() ); bottom.incrementUpper( bottomLeft.getSym() );
@@ -1680,16 +1691,16 @@ public class Mesh< T extends Region > {
final HalfEdge left = leftGoing.get( leftGoing.size() - 1 ).getSym(); final HalfEdge left = leftGoing.get( leftGoing.size() - 1 ).getSym();
final HalfEdge right = rightGoing.get( 0 ); final HalfEdge right = rightGoing.get( 0 );
// TODO Benefit from a constant time lower partition mapping final Partition partition = lowerEdgeMap.get( lower );
final Optional< Partition > opt = partitions.parallelStream().filter( p -> p.lower == lower ).findAny(); if ( partition != null ) {
if ( opt.isPresent() ) {
final Partition partition = opt.get();
if ( partition.upper != left ) { if ( partition.upper != left ) {
throw new IllegalStateException( "Partition upper edge is inconsistent!" ); throw new IllegalStateException( "Partition upper edge is inconsistent!" );
} }
upperEdgeMap.remove( partition.upper );
upperEdgeMap.put( right, partition );
// Update the partition's upper edge to the new right going edge // Update the partition's upper edge to the new right going edge
partition.upper = right; partition.upper = right;
@@ -1708,16 +1719,16 @@ public class Mesh< T extends Region > {
final HalfEdge left = leftGoing.get( 0 ).getSym(); final HalfEdge left = leftGoing.get( 0 ).getSym();
final HalfEdge right = rightGoing.get( rightGoing.size() - 1 ); final HalfEdge right = rightGoing.get( rightGoing.size() - 1 );
// TODO Benefit from an upper partition mapping final Partition partition = upperEdgeMap.get( upper );
final Optional< Partition > opt = partitions.parallelStream().filter( p -> p.upper == upper ).findAny(); if ( partition != null ) {
if ( opt.isPresent() ) {
final Partition partition = opt.get();
if ( partition.lower != left ) { if ( partition.lower != left ) {
throw new IllegalStateException( "Partition lower edge is inconsistent!" ); throw new IllegalStateException( "Partition lower edge is inconsistent!" );
} }
lowerEdgeMap.remove( partition.lower );
lowerEdgeMap.put( right, partition );
partition.lower = right; partition.lower = right;
// Update all visibility regions // Update all visibility regions
@@ -1731,14 +1742,17 @@ public class Mesh< T extends Region > {
} }
} }
partitions.addAll( newPartitions ); for ( final Partition newPartition : newPartitions ) {
upperEdgeMap.put( newPartition.upper, newPartition );
lowerEdgeMap.put( newPartition.lower, newPartition );
}
for ( final HalfEdge edge : rightGoing ) { for ( final HalfEdge edge : rightGoing ) {
edgeCollection.insert( edge ); edgeCollection.insert( edge );
} }
} }
if ( !partitions.isEmpty() ) { if ( !upperEdgeMap.isEmpty() || !lowerEdgeMap.isEmpty() ) {
throw new IllegalStateException( "Dangling partitions!" ); throw new IllegalStateException( "Dangling partitions!" );
} }