diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java index 32bd4a1..8dbc615 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java @@ -980,6 +980,7 @@ public class Mesh< T extends Region > { .collect( Collectors.toSet() ); } + // TODO Make not static public static class Chain { 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 * 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 have a visibility region for the same vertex. Map< Vertex, Collection< VisibilityRegion > > regionMap = new HashMap< Vertex, Collection< VisibilityRegion > >(); + Collection< Chain > chains = new HashSet< Chain >(); Partition( final HalfEdge lower, final HalfEdge upper ) { this.lower = lower; @@ -1434,11 +1433,17 @@ public class Mesh< T extends Region > { } } - // TODO Use a map for faster searching - // TODO Can keep two maps, a lower and upper map mapping edges to - // a particular partition, which will let us keep constant time - // for partition related operations. - final Collection< Partition > partitions = new HashSet< Partition >(); + // We use two maps to keep track of which partition is associated with which upper/lower edge + // so that we can find and confirm that a partition exists in constant time. Naturally, + // they must be kept up to date at all times, but that's not too difficult. + // Since each partition has exactly one upper and lower edge, both maps should have the + // 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 final SortedEdgeCollection< HalfEdge > edgeCollection = new SortedEdgeCollection< HalfEdge >( Mesh::greaterThanOrEqualTo ); @@ -1484,8 +1489,9 @@ public class Mesh< T extends Region > { if ( !leftGoing.isEmpty() ) { // Terminate any partitions - for ( final Iterator< Partition > it = partitions.iterator(); it.hasNext(); ) { - final Partition partition = it.next(); + for ( final Iterator< Entry< HalfEdge, Partition > > it = upperEdgeMap.entrySet().iterator(); it.hasNext(); ) { + 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? 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. 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(); } } @@ -1501,10 +1512,9 @@ public class Mesh< T extends Region > { // and is inside a partition. That means // the partition containing this vertex must // be split into two smaller partitions. - - final Optional< Partition > opt = partitions.parallelStream().filter( p -> p.lower == lower ).findAny(); - if ( opt.isPresent() ) { - final Partition toSplit = opt.get(); + + final Partition toSplit = lowerEdgeMap.remove( lower ); + if ( toSplit != null ) { if ( rightGoing.size() < 2 ) { throw new IllegalStateException( "Vertex has less than 2 right edges!" ); @@ -1514,7 +1524,15 @@ public class Mesh< T extends Region > { final HalfEdge newLower = rightGoing.get( rightGoing.size() - 1 ); 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; + + lowerEdgeMap.put( newLower, toSplit ); + lowerEdgeMap.put( lower, copy ); + upperEdgeMap.put( newUpper, copy ); /* * A simple method for splitting and updating the visibility regions @@ -1623,29 +1641,16 @@ public class Mesh< T extends Region > { // means there exists 2 partitions which // are converging and need to be merged // into a single partition. - Partition top = null; - Partition bottom = null; - + if ( leftGoing.size() < 2 ) { throw new IllegalStateException( "Vertex has less than 2 left edges!" ); } final HalfEdge topLeft = leftGoing.get( 0 ).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 - if ( partition.upper == 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; - } - } + + final Partition top = lowerEdgeMap.remove( topLeft ); + final Partition bottom = upperEdgeMap.remove( bottomLeft ); if ( top == null ) { throw new IllegalStateException( "Upper partition not found!" ); @@ -1653,9 +1658,15 @@ public class Mesh< T extends Region > { throw new IllegalStateException( "Lower partition not found!" ); } + chains.addAll( bottom.chains ); + lowerEdgeMap.remove( bottom.upper ); + // Merge the top and bottom partition 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 top.incrementLower( topLeft.getSym() ); bottom.incrementUpper( bottomLeft.getSym() ); @@ -1680,22 +1691,22 @@ public class Mesh< T extends Region > { final HalfEdge left = leftGoing.get( leftGoing.size() - 1 ).getSym(); final HalfEdge right = rightGoing.get( 0 ); - // TODO Benefit from a constant time lower partition mapping - final Optional< Partition > opt = partitions.parallelStream().filter( p -> p.lower == lower ).findAny(); - - if ( opt.isPresent() ) { - final Partition partition = opt.get(); + final Partition partition = lowerEdgeMap.get( lower ); + if ( partition != null ) { if ( partition.upper != left ) { 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 partition.upper = right; // Update all visibility regions partition.incrementUpper( right ); - + // Add a new region for this vertex partition.add( event, new VisibilityRegion( left.getSym(), right ) ); } else { @@ -1708,16 +1719,16 @@ public class Mesh< T extends Region > { final HalfEdge left = leftGoing.get( 0 ).getSym(); final HalfEdge right = rightGoing.get( rightGoing.size() - 1 ); - // TODO Benefit from an upper partition mapping - final Optional< Partition > opt = partitions.parallelStream().filter( p -> p.upper == upper ).findAny(); + final Partition partition = upperEdgeMap.get( upper ); + if ( partition != null ) { - if ( opt.isPresent() ) { - final Partition partition = opt.get(); - if ( partition.lower != left ) { throw new IllegalStateException( "Partition lower edge is inconsistent!" ); } + lowerEdgeMap.remove( partition.lower ); + lowerEdgeMap.put( right, partition ); + partition.lower = right; // 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 ) { edgeCollection.insert( edge ); } } - if ( !partitions.isEmpty() ) { + if ( !upperEdgeMap.isEmpty() || !lowerEdgeMap.isEmpty() ) { throw new IllegalStateException( "Dangling partitions!" ); }