From 20cf9e9ec577a028d66b500d83bc605ac6323d3d Mon Sep 17 00:00:00 2001 From: BananaPuncher714 Date: Fri, 23 May 2025 22:15:40 -0400 Subject: [PATCH] Update comments --- .../bananapuncher714/mesh/Chain.java | 4 + .../bananapuncher714/mesh/Mesh.java | 354 +++++++++++------- .../bananapuncher714/mesh/Point.java | 2 +- .../bananapuncher714/mesh/Segment.java | 3 + .../bananapuncher714/mesh/Test.java | 42 --- .../minietest/MeshingTest2.java | 8 +- 6 files changed, 223 insertions(+), 190 deletions(-) delete mode 100644 src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Test.java diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Chain.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Chain.java index c863b16..54b99c6 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Chain.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Chain.java @@ -4,6 +4,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +/** + * A chain represents a series of points which can be connected to reduce + * the amount of vertices overall within a given mesh. + */ public class Chain { protected List< Point > points = new ArrayList< Point >(); protected List< Chain > intersections = new ArrayList< Chain >(); diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java index d575966..be6763e 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java @@ -36,6 +36,7 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.region.RegionRule; * The entire algorithm from polygons to triangles can be summarized as follows: * 1. Simplify the polygons into one massive DCEL/PSLG. This includes: * - Merging vertices that are within VERTEX_TOLERANCE of each other + * - Shifting vertices that are within VERTEX_TOLERANCE horizontally * - Merging vertices on edges that are at most VERTEX_TOLERANCE away * - Merging collinear edges * - Creating new vertices where two or more edges intersect @@ -54,22 +55,24 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.region.RegionRule; * This step in the algorithm is destructive in the sense that it removes edges, but it * does not move any vertices. This step also removes merges collinear edges where possible. * - * 3. Meshing and triangulating the resulting polygons. Firstly, a copy of the PSLG is generated - * since this step adds edges and vertices to the PSLG, which may be undesirable for reusability. - * Then, we remove collinear edges which may have been generated as a result of the monotone partitioning. + * 3. Meshing and triangulating the resulting polygons. Firstly, the mesh is duplicated so that + * the original mesh can be reused for future modifications, since this step adds edges and + * vertices to the PSLG, which may be undesirable for reusability. Then, if the user decides to, + * (which is enabled by default), chain links can be calculated and merged to optimally minimize + * the total amount of triangles used to triangluate this mesh. + * * Following a general O(nlogn) algorithm, the PSLG is split into polygons strictly monotone with * respect to Y. The PSLG is not guaranteed to be comprised of simple polygons, and may contain * holes. * * Once complete, the simple polygons are separated into their own PSLG(that is, they do not share - * any vertices or edge POJOs), and can be triangulated in parallel. The triangulation method used + * any vertices or edge objects), and can be triangulated in parallel. The triangulation method used * is the algorithm described in Computation Geometry Algorithms and Applications 3rd Ed., which * is a linear-time algorithm. * * The time complexity for the entire algorithm should be roughly O(n^2logn). * * TODO Remove guaranteed checks or add some compile time thing to remove - * TODO Update this description * * @author BananaPuncher714 */ @@ -91,10 +94,18 @@ public class Mesh< T extends Region > { protected Collection< MeshEventHandler > handlers = new LinkedHashSet< MeshEventHandler >(); + protected boolean mergeChains = true; + public Mesh( final Supplier< T > defaultRegionSupplier ) { this.defaultRegionSupplier = defaultRegionSupplier; } + /** + * Add a polygon with a rule for the edges of the polygon to this mesh. + * + * @param poly + * @param rule + */ public void addPolygon( final Polygon poly, final RegionRule< T > rule ) { // Is it at least a triangle? if ( poly.getPoints().size() < 3 ) { @@ -135,7 +146,7 @@ public class Mesh< T extends Region > { } public Set< Point > getVertices() { - return vertices.parallelStream().map( v -> { return new Point( v.getPosition() ); } ).collect( Collectors.toSet() ); + return vertices.parallelStream().map( v -> new Point( v.getPosition() ) ).collect( Collectors.toSet() ); } public Set< Segment > getEdges() { @@ -155,6 +166,14 @@ public class Mesh< T extends Region > { return rules.size(); } + public boolean isMergeChains() { + return mergeChains; + } + + public void setMergeChains( boolean merge ) { + mergeChains = merge; + } + public void clear() { state = MeshState.TRIANGULATION_READY; vertices.clear(); @@ -232,7 +251,7 @@ public class Mesh< T extends Region > { } /** - * + * Process this mesh and triangulate it * * @return A collection of triangles */ @@ -319,6 +338,7 @@ public class Mesh< T extends Region > { // At this point in time, if the edge is zero, // then the origin and destination must be the same if ( edge.getOrigin() != edge.getDest() ) { + // TODO Remove this throw new IllegalStateException( "Zero edge but the origin and destination are not the same!" ); } @@ -389,6 +409,7 @@ public class Mesh< T extends Region > { * If both or none are there, then that is an issue. */ if ( !scannedEdges.remove( edge ) ) { + // TODO Remove this throw new IllegalStateException( "Tried to remove an invalid edge!" ); } } @@ -401,6 +422,21 @@ public class Mesh< T extends Region > { // Update the list of vertices with our new updated list of vertices vertices = scannedVertices; + /* + * At this point, each vertex should be merged and moved to the + * correct position, and all edges should be connected to the + * correct vertex, but not necessarily in the right order. + * + * Invariants: + * - All vertices are at least VERTEX_TOLERANCE away from each other + * - All vertices are at least VERTEX_TOLERANCE away from each other horizontally + * - All vertices will not be moved after this + * - No vertices have only 1 edge + * - No vertices are within VERTEX_TOLERANCE of another edge which they are not part of + * - No edges have length of 0 + * - No edges intersect or overlap + */ + state = MeshState.SIMPLIFIED; } @@ -424,14 +460,17 @@ public class Mesh< T extends Region > { continue; } else if ( isSimilar( v, b ) || isSimilar( v, a ) ) { // The vertices should have been merged already + // TODO Remove this throw new IllegalStateException( "Vertex not merged" ); } else if ( edge.isZero() ) { + // TODO Remove this throw new IllegalStateException( "Edge is zero!" ); } else { // Equations taken from https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment final double edgeLenSq = a.distanceSquared( b ); final Vector2d toB = b.subtracted( a ); final double t = v.subtracted( a ).dot( toB ) / edgeLenSq; + // TODO Maybe check for tolerance here? if ( t >= 0 && t <= 1 ) { // Get perpendicular distance from v to the line formed by a + b final Vector2d projection = toB.multiplied( t ).add( a ); @@ -482,11 +521,13 @@ public class Mesh< T extends Region > { final IntersectionEdgeToEdge intEdge = ( IntersectionEdgeToEdge ) intersection; if ( positiveEdge.getDest() == other.getDest() ) { + // TODO Remove this throw new IllegalStateException( "Invalid intersection" ); } final Vector2d point = intEdge.getPoint(); if ( isSimilar( point, other.getDest().getPosition() ) || isSimilar( point, positiveEdge.getDest().getPosition() ) ) { + // TODO Remove this throw new IllegalStateException( "Invalid intersection point" ); } @@ -538,6 +579,7 @@ public class Mesh< T extends Region > { // Splice the two together HalfEdge.splice( shorter.getSym(), split ); } else { + // TODO Remove this throw new IllegalStateException( "Unknown intersection type" ); } } @@ -575,6 +617,7 @@ public class Mesh< T extends Region > { } } } else if ( isSimilar( a.getOrigin(), b.getOrigin() ) ) { + // TODO Remove this throw new IllegalArgumentException( "Vertex not merged!" ); } else if ( !isSimilar( a.getDest(), b.getDest() ) ) { // Both edges may intersect somewhere in the middle @@ -710,6 +753,7 @@ public class Mesh< T extends Region > { for ( final HalfEdge edge : vertex ) { if ( !isPositive( edge ) ) { if ( !edges.remove( edge.getSym() ) ) { + // TODO Remove this throw new IllegalStateException( "Negative edge was not added to the edge collection" ); } } else { @@ -810,16 +854,33 @@ public class Mesh< T extends Region > { vertices = keep; + /* + * Invariants: + * - All vertices have an even number of edges + * - All vertices are in the correct order + * - There are no consecutive collinear edges part of the same polygon + * - Exactly one side of every edge is an interior edge + */ + state = MeshState.TRIANGULATION_READY; } - // Sort the edges around this vertex. + /** + * Sort this vertex such that the edges on this vertex are in counter + * clockwise order, then set the vertex's edge to the least edge. + * + * Multiple vertices can be sorted in parallel without affecting the other. + * + * @param vertex + * @return + */ private static List< HalfEdge > sort( Vertex vertex ) { final Vector2d UP = new Vector2d( 0, 1 ); final List< HalfEdge > edges = new ArrayList< HalfEdge >(); for ( final HalfEdge edge : vertex ) { if ( edge.isZero() ) { + // TODO Remove this throw new IllegalStateException( "Edge has length of 0" ); } edges.add( edge ); @@ -877,6 +938,7 @@ public class Mesh< T extends Region > { HalfEdge edge = internalEdge; if ( edge.getPrev() == edge ) { + // TODO Remove this throw new IllegalStateException( "Double sided interior edges detected" ); } @@ -996,74 +1058,71 @@ public class Mesh< T extends Region > { } } - // 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() ); + // At this point each vertex should be connected to the correct edges + // but needs to be sorted first to ensure that the order is correct. + final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare ); + vertices.addAll( newVertices.values() ); // Sort each vertex so that the edge they are pointing to is the least edge - fuzzyVertices.parallelStream().forEach( v -> sort( v ) ); + vertices.parallelStream().forEach( v -> sort( v ) ); - final Collection< Link > allChains = findChains( fuzzyVertices, edges ); - final Collection< Link > chains = maximizeChains( allChains ); - if ( !handlers.isEmpty() ) { - final Collection< Chain > newChains = new HashSet< Chain >(); - final Collection< Link > seen = new HashSet< Link >(); - - for ( final Link link : allChains ) { - if ( seen.add( link ) ) { - Link head = link; - while ( head.previous != null ) { - head = head.previous; + if ( mergeChains ) { + final Collection< Link > allChains = findChains( vertices, edges ); + final Collection< Link > chains = maximizeChains( allChains ); + if ( !handlers.isEmpty() ) { + final Collection< Chain > newChains = new HashSet< Chain >(); + final Collection< Link > seen = new HashSet< Link >(); + + for ( final Link link : allChains ) { + if ( seen.add( link ) ) { + Link head = link; + while ( head.previous != null ) { + head = head.previous; + } + + final Chain chain = new Chain(); + Link previous; + do { + chain.points.add( new Point( head.getOrigin().getPosition() ) ); + seen.add( head ); + previous = head; + } while ( ( head = head.next ) != null ); + chain.points.add( new Point( previous.getMidpoint().getPosition() ) ); + chain.points.add( new Point( previous.getDest().getPosition() ) ); + newChains.add( chain ); } - - final Chain chain = new Chain(); - Link previous; - do { - chain.points.add( new Point( head.getOrigin().getPosition() ) ); - seen.add( head ); - previous = head; - } while ( ( head = head.next ) != null ); - chain.points.add( new Point( previous.getMidpoint().getPosition() ) ); - chain.points.add( new Point( previous.getDest().getPosition() ) ); - newChains.add( chain ); } + + handlers.forEach( h -> h.onChainGenerationEvent( newChains ) ); + + final Collection< Link > selectedSeen = new HashSet< Link >(); + final Collection< Chain > selectedChains = new HashSet< Chain >(); + for ( final Link link : chains ) { + if ( selectedSeen.add( link ) ) { + Link head = link; + while ( chains.contains( head.previous ) ) { + head = head.previous; + } + + final Chain chain = new Chain(); + Link previous; + do { + chain.points.add( new Point( head.getOrigin().getPosition() ) ); + selectedSeen.add( head ); + previous = head; + } while ( ( head = head.next ) != null && chains.contains( head ) ); + chain.points.add( new Point( previous.getMidpoint().getPosition() ) ); + chain.points.add( new Point( previous.getDest().getPosition() ) ); + selectedChains.add( chain ); + } + } + + handlers.forEach( h -> h.onPreChainMergeEvent( selectedChains ) ); } - handlers.forEach( h -> h.onChainGenerationEvent( newChains ) ); - - final Collection< Link > selectedSeen = new HashSet< Link >(); - final Collection< Chain > selectedChains = new HashSet< Chain >(); - for ( final Link link : chains ) { - if ( selectedSeen.add( link ) ) { - Link head = link; - while ( chains.contains( head.previous ) ) { - head = head.previous; - } - - final Chain chain = new Chain(); - Link previous; - do { - chain.points.add( new Point( head.getOrigin().getPosition() ) ); - selectedSeen.add( head ); - previous = head; - } while ( ( head = head.next ) != null && chains.contains( head ) ); - chain.points.add( new Point( previous.getMidpoint().getPosition() ) ); - chain.points.add( new Point( previous.getDest().getPosition() ) ); - selectedChains.add( chain ); - } - } - - handlers.forEach( h -> h.onPreChainMergeEvent( selectedChains ) ); + mergeChains( chains, edges ); } - mergeChains( chains, edges ); - - final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare ); - vertices.addAll( fuzzyVertices ); - final Collection< EdgePolygon > polys = partitionMonotone( vertices, edges ); if ( handlers != null ) { @@ -1088,19 +1147,16 @@ public class Mesh< T extends Region > { .collect( Collectors.toSet() ); } - /* - * This algorithm revolves around first generating chains, then sorting which chains and chain links to keep. + /** + * Find all chain links that occur within the provided mesh. Mark which ones intersect, and which links are connected. * - * A chain is a collection of chain links. - * A chain link is 3 collinear vertices. - * 2 chain links are considered linked if the first link's 2 last vertices are the same as the second link's 2 first vertices. - * For example, given collinear vertices A, B, C and D, chain links u and v are linked together if - * chain link u has vertices A, B, C, and if chain link v has vertices B, C and D. No more than 2 chain links - * can be linked together at one end. Two chains are considered crossing if a link from both chains intersects each other. - * At most 3 chain links can intersect another chain link, since an intersection only occurs if the segment formed by 2 - * vertices from one chain link intersects another segment formed by 2 vertices from a different chain link. + * Each link represents 3 directly collinear vertices. * - * The complexity comes from analyzing which combinations of chain links provides the greatest reduction of vertices. + * Time complexity: O(n^2logn) + * + * @param vertices + * @param interior + * @return */ private static Collection< Link > findChains( final TreeSet< Vertex > vertices, final Collection< HalfEdge > interior ) { final Vector2d CROSS = new Vector2d( 1, 0 ); @@ -1214,8 +1270,10 @@ public class Mesh< T extends Region > { final List< HalfEdge > links = region.links.get( region.upper ); if ( links == null ) { + // TODO Remove this throw new IllegalStateException( "No links found for scanned vertex!" ); } else if ( links.size() < 2 ) { + // TODO Remove this throw new IllegalStateException( "Missing links!" ); } @@ -1248,17 +1306,13 @@ public class Mesh< T extends Region > { region.upper = region.lower; } - // There should be exactly 1 link, the lower region's link - if ( region.links.size() != 1 ) { - throw new IllegalStateException( "Dangling link!" ); - } - - final List< HalfEdge > links = region.links.get( region.upper ); if ( links == null ) { + // TODO Remove this throw new IllegalStateException( "No links found for scanned vertex!" ); } else if ( links.size() < 2 ) { + // TODO Remove this throw new IllegalStateException( "Missing links!" ); } @@ -1284,6 +1338,7 @@ public class Mesh< T extends Region > { if ( region.upper != MAX_CROSS ) { final List< HalfEdge > prevLinks = region.links.get( region.upper ); if ( prevLinks.size() < 2 ) { + // TODO Remove this throw new IllegalStateException( "Missing links!" ); } @@ -1295,6 +1350,7 @@ public class Mesh< T extends Region > { // As of right now there should only be 1 or 0 links if ( region.links.size() > 1 ) { + // TODO Remove this throw new IllegalStateException( "Too many links!" ); } } @@ -1309,6 +1365,7 @@ public class Mesh< T extends Region > { links.add( region.upperEdge ); links.add( edge ); if ( region.links.put( region.upper, links ) != null ) { + // TODO Remove this throw new IllegalStateException( "Cross value already used!" ); } } @@ -1344,8 +1401,10 @@ public class Mesh< T extends Region > { final List< HalfEdge > links = region.links.get( region.lower ); if ( links == null ) { + // TODO Remove this throw new IllegalStateException( "No links found for scanned vertex!" ); } else if ( links.size() < 2 ) { + // TODO Remove this throw new IllegalStateException( "Missing links!" ); } @@ -1377,16 +1436,13 @@ public class Mesh< T extends Region > { region.lower = region.upper; } - // There should be exactly 1 link, the lower region's link - if ( region.links.size() != 1 ) { - throw new IllegalStateException( "Dangling link!" ); - } - final List< HalfEdge > links = region.links.get( region.lower ); if ( links == null ) { + // TODO Remove this throw new IllegalStateException( "No links found for scanned vertex!" ); } else if ( links.size() < 2 ) { + // TODO Remove this throw new IllegalStateException( "Missing links!" ); } @@ -1412,6 +1468,7 @@ public class Mesh< T extends Region > { if ( region.lower != MIN_CROSS ) { final List< HalfEdge > prevLinks = region.links.get( region.lower ); if ( prevLinks.size() < 2 ) { + // TODO Remove this throw new IllegalStateException( "Missing links!" ); } @@ -1423,6 +1480,7 @@ public class Mesh< T extends Region > { // As of right now there should only be 1 or 0 links if ( region.links.size() > 1 ) { + // TODO Remove this throw new IllegalStateException( "Too many links!" ); } } @@ -1437,6 +1495,7 @@ public class Mesh< T extends Region > { links.add( region.lowerEdge ); links.add( edge ); if ( region.links.put( region.lower, links ) != null ) { + // TODO Remove this throw new IllegalStateException( "Cross value already used!" ); } } @@ -1481,16 +1540,20 @@ public class Mesh< T extends Region > { final Vector2d p = chainOrigin.getPosition(); final Vector2d r = chainDestination.getPosition().subtracted( p ); + + /* + * There are two cases where it might seem possible, but are not: + * - Where this link is the previous link for an existing link + * - Where the destination of this link is another link's midpoint + * + * This is because it depends on the new link's destination being less than + * any other vertex in all the existing links, which should never be possible. + */ 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() ) { - // 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. @@ -1521,31 +1584,6 @@ public class Mesh< T extends Region > { } 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. @@ -1702,6 +1740,7 @@ public class Mesh< T extends Region > { } else { leftGoing.add( edge ); if ( !edgeCollection.remove( edge.getSym() ) ) { + // TODO Remove this throw new IllegalStateException( "Edge not added to the edge collection!" ); } } @@ -1761,6 +1800,7 @@ public class Mesh< T extends Region > { if ( toSplit != null ) { if ( rightGoing.size() < 2 ) { + // TODO Remove this throw new IllegalStateException( "Vertex has less than 2 right edges!" ); } @@ -1861,6 +1901,7 @@ public class Mesh< T extends Region > { newPartitions.add( copy ); } else { + // TODO Remove this throw new IllegalStateException( "A partition with an interior edge does not exist!" ); } } @@ -1899,6 +1940,7 @@ public class Mesh< T extends Region > { // into a single partition. if ( leftGoing.size() < 2 ) { + // TODO Remove this throw new IllegalStateException( "Vertex has less than 2 left edges!" ); } @@ -1909,8 +1951,10 @@ public class Mesh< T extends Region > { final Partition bottom = upperEdgeMap.remove( bottomLeft.getSym() ); if ( top == null ) { + // TODO Remove this throw new IllegalStateException( "Upper partition not found!" ); } else if ( bottom == null ) { + // TODO Remove this throw new IllegalStateException( "Lower partition not found!" ); } @@ -1956,6 +2000,7 @@ public class Mesh< T extends Region > { if ( partition != null ) { if ( partition.upper != left.getSym() ) { + // TODO Remove this throw new IllegalStateException( "Partition upper edge is inconsistent!" ); } @@ -1971,6 +2016,7 @@ public class Mesh< T extends Region > { // Add a new region for this vertex partition.add( event, new VisibilityRegion( left, right ) ); } else { + // TODO Remove this throw new IllegalStateException( "Interior edge does not have a partition!" ); } } @@ -1984,6 +2030,7 @@ public class Mesh< T extends Region > { if ( partition != null ) { if ( partition.lower != left.getSym() ) { + // TODO Remove this throw new IllegalStateException( "Partition lower edge is inconsistent!" ); } @@ -1998,6 +2045,7 @@ public class Mesh< T extends Region > { // Add a new region for this vertex partition.add( event, new VisibilityRegion( right, left ) ); } else { + // TODO Remove this throw new IllegalStateException( "Interior edge does not have a partition!" ); } } @@ -2014,6 +2062,7 @@ public class Mesh< T extends Region > { } if ( !upperEdgeMap.isEmpty() || !lowerEdgeMap.isEmpty() ) { + // TODO Remove this throw new IllegalStateException( "Dangling partitions!" ); } @@ -2023,6 +2072,16 @@ public class Mesh< T extends Region > { return aggregateLinks; } + /** + * Given a collection of chain links, maximize the amount of non intersecting + * links. Use a greedy method to select whichever links have the least amount + * of intersections, until there are no more chain links left to be selected. + * + * Time complexity: O(n^2) worst case + * + * @param chains + * @return + */ private static Collection< Link > maximizeChains( final Collection< Link > chains ) { final Collection< Link > remainingChains = new LinkedHashSet< Link >( chains ); final Collection< Link > selectedChains = new HashSet< Link >(); @@ -2060,6 +2119,14 @@ public class Mesh< T extends Region > { return selectedChains; } + /** + * Given a collection of chain links, merge them into the resulting mesh. + * + * Time complexity: O(n) + * + * @param selected + * @param interior + */ private static void mergeChains( final Collection< Link > selected, final Collection< HalfEdge > interior ) { final Collection< Link > chains = new HashSet< Link >( selected ); while ( !chains.isEmpty() ) { @@ -2106,6 +2173,7 @@ public class Mesh< T extends Region > { if ( interior.contains( head ) ) { // Left handed if ( right != null ) { + // TODO Remove this throw new IllegalStateException( "Unterminated left edge!" ); } right = new HalfEdge(); @@ -2144,6 +2212,7 @@ public class Mesh< T extends Region > { } else { // Right handed if ( left != null ) { + // TODO Remove this throw new IllegalStateException( "Unterminated right edge!" ); } left = new HalfEdge(); @@ -2328,7 +2397,7 @@ public class Mesh< T extends Region > { edge = edge.getPrev(); if ( --size < 0 ) { - throw new IllegalStateException( "Not good vertex! With only " + vertex.size() ); + throw new IllegalStateException( "Checked too many edges!" ); } } } @@ -2359,9 +2428,11 @@ public class Mesh< T extends Region > { final SortedEdgeCollection< HalfEdge > edgeCollection = new SortedEdgeCollection< HalfEdge >( ( a, b ) -> { // Assume each region is marked by an upper edge going from right to left, up to down if ( a.isZero() || b.isZero() ) { + // TODO Remove this throw new IllegalStateException( "Zero length edge detected" ); } if ( !( isPositive( a ) && isPositive( b ) ) ) { + // TODO Remove this throw new IllegalStateException( "Negative edge detected" ); } @@ -2419,6 +2490,7 @@ public class Mesh< T extends Region > { } else { leftGoing.add( edge ); if ( !edgeCollection.remove( edge.getSym() ) ) { + // TODO Remove this throw new IllegalStateException( "Edge not added to the edge collection!" ); } } @@ -2453,10 +2525,13 @@ public class Mesh< T extends Region > { // The current vertex must be between the upper and the lower // edge, and it cannot be part of either edge if ( upper == null ) { + // TODO Remove this throw new IllegalStateException( "Lower does not have an upper edge!" ); } if ( !interior.contains( upper.getSym() ) ) { + // TODO Remove this throw new IllegalStateException( "Polygon is not simple!" ); } else if ( upper.getOrigin() == event || lower.getOrigin() == event ) { + // TODO Remove this throw new IllegalStateException( "Vertex is invalid!" ); } @@ -2464,6 +2539,7 @@ public class Mesh< T extends Region > { eventMark = new MarkedEdge( lower, upper ); } } else if ( rightGoing.isEmpty() && leftGoing.isEmpty() ) { + // TODO Remove this throw new IllegalStateException( "Cannot have a vertex with no edges!" ); } @@ -2503,6 +2579,7 @@ public class Mesh< T extends Region > { // upper/lower region as this vertex, then // that must be because this vertex is left marked if ( !leftGoing.isEmpty() ) { + // TODO Remove this throw new IllegalStateException( "Vertex is not left-marked!" ); } @@ -2583,6 +2660,7 @@ public class Mesh< T extends Region > { if ( isPositive( edge ) ) { edgeCollection.insert( edge ); } else { + // TODO Remove this throw new IllegalStateException( "Edge suddenly changed sign!" ); } } @@ -2590,6 +2668,7 @@ public class Mesh< T extends Region > { // All right marked vertices MUST be resolved by now. if ( !rightMarked.isEmpty() ) { + // TODO Remove this throw new IllegalStateException( "Unresolved right marked vertices!" ); } @@ -2648,6 +2727,14 @@ public class Mesh< T extends Region > { return newPolygons; } + /** + * Generate triangles as described in Computational Geometry Algorithms and Applications 3rd Edition. + * + * A linear time algorithm. + * + * @param polygon + * @return + */ private static Collection< Polygon > triangulate( final EdgePolygon polygon ) { // Go down each monotone chain and connect the vertices where possible. // Implements the O(n) triangulation of a polygon as described in @@ -2757,8 +2844,10 @@ public class Mesh< T extends Region > { // TODO Remove this NOW toSort.parallelStream().forEach( v -> sort( v ) ); - Map< Vertex, Point > pointMap = polygon.vertices.parallelStream().collect( Collectors.toMap( Function.identity(), v -> { return new Point( v.getPosition() ); } ) ); + // Convert each vertex to a point + final Map< Vertex, Point > pointMap = polygon.vertices.parallelStream().collect( Collectors.toMap( Function.identity(), v -> new Point( v.getPosition() ) ) ); + // Convert each triangle to a polygon final Collection< Polygon > polygons = new ArrayDeque< Polygon >(); final Set< HalfEdge > scanned = new HashSet< HalfEdge >(); for ( final HalfEdge edge : polygon.getEdges() ) { @@ -2771,6 +2860,7 @@ public class Mesh< T extends Region > { } while ( scanned.add( temp = temp.getNext() ) ); if ( points.size() != 3 ) { + // TODO Remove this throw new IllegalStateException( "Not a triangle: " + points.size() ); } polygons.add( new Polygon( points ) ); @@ -2821,28 +2911,6 @@ public class Mesh< T extends Region > { return compare( edge.getOrigin(), edge.getDest() ) <= 0; } - /* - * Since sometimes vertices will be off by a few floating point errors, - * it will cause problems because we have checks for tolerance, but if - * the sorting for vertices does not, it causes issues. - */ - private static int fuzzyCompare( final Vertex a, final Vertex b ) { - if ( a == b ) { - return 0; - } - - - final Vector2d aPos = a.getPosition(); - final Vector2d bPos = b.getPosition(); - - final double xDiff = aPos.getX() - bPos.getX(); - if ( Math.abs( xDiff ) < VERTEX_TOLERANCE ) { - return Double.compare( aPos.getY(), bPos.getY() ); - } else { - return Double.compare( aPos.getX(), bPos.getX() ); - } - } - private static int compare( final Vertex a, final Vertex b ) { if ( a == b ) { return 0; diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Point.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Point.java index c3f88b1..321057e 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Point.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Point.java @@ -1,7 +1,7 @@ package com.aaaaahhhhhhh.bananapuncher714.mesh; /** - * A 2d cartesian coordinate + * A 2d Cartesian coordinate * * @author BananaPuncher714 */ diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Segment.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Segment.java index 7c483f9..d63cf3a 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Segment.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Segment.java @@ -1,5 +1,8 @@ package com.aaaaahhhhhhh.bananapuncher714.mesh; +/** + * A line segment + */ public class Segment { private final Point start; private final Point end; diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Test.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Test.java deleted file mode 100644 index dc6e157..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Test.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.mesh; - -public class Test { - public static void main( String[] args ) { - final Vector2d a = new Vector2d( -1, -1 ); - final Vector2d b = new Vector2d( 10, 10 ); - final Vector2d c = new Vector2d( 0, -1 ); - final Vector2d d = new Vector2d( 10, 11 ); - - test( a, b, c, d ); - } - - public static void test( final Vector2d a, final Vector2d b, final Vector2d c, final Vector2d d ) { - final Vector2d ab = b.subtracted( a ); - final Vector2d cd = d.subtracted( c ); - - final Vector2d ac = c.subtracted( a ); - - final double acab = ac.cross( ab ); - final double abcd = ab.cross( cd ); - - System.out.println( "Parallel value: " + abcd ); - if ( abcd != 0 ) { - final double s = ac.cross( cd ); - final double t = s / abcd; - final double u = acab / abcd; - - System.out.println( "S: " + s ); - System.out.println( "T: " + t ); - System.out.println( "U: " + u ); - - System.out.println( "AB Length: " + ( t * ab.length() ) ); - System.out.println( "CD Length: " + ( u * cd.length() ) ); - - // Gives the intersection correctly - System.out.println( "AB int: " + ab.multiplied( t ).add( a ) ); - System.out.println( "CD int: " + cd.multiplied( u ).add( c ) ); - } else { - - } - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java index edbde8e..c26f06e 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; -import java.util.Set; import javax.swing.JButton; import javax.swing.JComboBox; @@ -142,7 +141,8 @@ public class MeshingTest2 extends JPanel { // } // } final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-4,-6" ) ); - masterPlanes.add( planes.get( 177 ) ); +// masterPlanes.add( planes.get( 177 ) ); + masterPlanes.addAll( planes ); // masterPlanes.add( getTestPlane() ); @@ -969,9 +969,9 @@ public class MeshingTest2 extends JPanel { g.setColor( Color.MAGENTA ); drawOnBoard( g, "Triangle count: " + data.polygons.size(), 0, -10 ); g.setColor( Color.RED ); - drawOnBoard( g, "Total Chain count: " + data.chains.size(), 0, -15 ); + drawOnBoard( g, "Total chain count: " + data.chains.size(), 0, -15 ); g.setColor( Color.BLUE ); - drawOnBoard( g, "Possible chain count: " + data.selectedChains.size(), 0, -20 ); + drawOnBoard( g, "Optimal chain count: " + data.selectedChains.size(), 0, -20 ); } java.awt.Point p = MouseInfo.getPointerInfo().getLocation();