From c101c2e1dd5ff9366903746ffd06717de6945925 Mon Sep 17 00:00:00 2001 From: BananaPuncher714 Date: Wed, 21 May 2025 02:40:24 -0400 Subject: [PATCH] Added single chain link resolution and many debug prints and commented out blocks of code --- .../bananapuncher714/mesh/Mesh.java | 929 ++++++++++++++---- .../minietest/MeshingTest2.java | 194 +++- 2 files changed, 928 insertions(+), 195 deletions(-) diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java index a304fc4..501794e 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java @@ -72,7 +72,7 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.region.RegionRule; */ public class Mesh< T extends Region > { public static final double VERTEX_TOLERANCE = 1e-7; - public static final double ANGLE_TOLERANCE = Math.sin( Math.toRadians( 0.0001 ) ); + public static final double ANGLE_TOLERANCE = Math.sin( Math.toRadians( 1e-10 ) ); // All points in the graph protected Collection< Vertex > vertices = new ArrayDeque< Vertex >(); @@ -772,6 +772,8 @@ public class Mesh< T extends Region > { edges.add( edge ); } + // TODO Can really just figure this out based on the absolute angle? + // relative to the (0, 1) vector... // Sort the edges in a counter clockwise direction, // where 11:59 is the least, and 12:00 is the greatest Collections.sort( edges, ( e1, e2 ) -> { @@ -890,7 +892,7 @@ public class Mesh< T extends Region > { // TODO Provide triangles // and preferrably a simple polygon - public Collection< EdgePolygon > mesh() { + public Collection< EdgePolygon > mesh( boolean b ) { if ( state != MeshState.TRIANGULATION_READY ) { throw new IllegalStateException( "Mesh has not been split into regions!" ); } @@ -943,7 +945,7 @@ public class Mesh< T extends Region > { } while ( scanned.add( temp = temp.getNext() ) ); } } - + // Have a single set of edges final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare ); vertices.addAll( newVertices.values() ); @@ -952,7 +954,7 @@ public class Mesh< T extends Region > { vertices.parallelStream().forEach( v -> sort( v ) ); try { - final Collection< Chain > chains = reduceCollinear2( vertices, edges ); + final Collection< Chain > chains = reduceCollinear2( vertices, edges, b ); this.chains = new ArrayDeque< Chain >(); for ( Chain chain : chains ) { List< HalfEdge > newEdges = new ArrayList< HalfEdge >(); @@ -967,14 +969,56 @@ public class Mesh< T extends Region > { } } catch ( IllegalStateException e ) { e.printStackTrace(); + throw e; } - return partitionMonotone( vertices, edges ).parallelStream() - .map( p -> { - mergeAdjacentCollinearEdges( p.getVertices(), p.getEdges() ).forEach( e -> p.getVertices().remove( e.getOrigin() ) ); - - return p; - } ) +// boolean a = true; + if ( b ) { + final Collection< EdgePolygon > newPolygons = new ArrayDeque< EdgePolygon >(); + + // Keep track of all edges that we've seen + final Set< HalfEdge > scanned2 = new HashSet< HalfEdge >(); + for ( final HalfEdge edge : edges ) { + if ( scanned2.add( edge ) ) { + // We have not looked at this edge yet, so loop over it + // and create a new polygon from it + final EdgePolygon newPoly = new EdgePolygon(); + HalfEdge temp = edge; + + do { + // As we iterate over each edge, if the origin has more than + // 2 edges, then we need to split the vertex. + if ( temp.getPrev().getPrev() != temp ) { + temp.getOrigin().setEdge( temp.getPrev().getPrev() ); + + HalfEdge.splice( temp.getPrev(), temp.getSym().getNext() ); + + // Set their origins to a new vertex + final Vertex vertex = new Vertex( temp, temp.getOrigin().getPosition() ); + + temp.setOrigin( vertex ); + temp.getPrev().setOrigin( vertex ); + } + newPoly.addEdge( temp ); + } while ( scanned2.add( temp = temp.getNext() ) ); + newPolygons.add( newPoly ); + } + } + + return newPolygons; + } + +// boolean b = true; + if ( b ) { + return partitionMonotone( vertices, edges, b ); + } + + return partitionMonotone( vertices, edges, b ).parallelStream() +// .map( p -> { +// mergeAdjacentCollinearEdges( p.getVertices(), p.getEdges() ).forEach( e -> p.getVertices().remove( e.getOrigin() ) ); +// +// return p; +// } ) .map( Mesh::triangulate ) .flatMap( p -> p.parallelStream() ) .collect( Collectors.toSet() ); @@ -1022,7 +1066,7 @@ public class Mesh< T extends Region > { * * The complexity comes from analyzing which combinations of chain links provides the greatest reduction of vertices. */ - private static Collection< Chain > reduceCollinear2( final TreeSet< Vertex > vertices, final Collection< HalfEdge > interior ) { + private static Collection< Chain > reduceCollinear2( final TreeSet< Vertex > vertices, final Collection< HalfEdge > interior, boolean b ) { final Vector2d CROSS = new Vector2d( 1, 0 ); // Set maximum and minimum cross values that won't occur naturally final double MAX_CROSS = 1.1; @@ -1115,7 +1159,7 @@ public class Mesh< T extends Region > { // Get the cross value for this vertex to the current event vertex final Vector2d toEvent = event.getPosition().subtracted( vert.getPosition() ).normalize(); - final double upperCross = CROSS.cross( toEvent ); + final double upperCross = clamp( CROSS.cross( toEvent ), -1, 1 ); final double upperAngle = Math.asin( upperCross ); // Check over each visibility region and update the upper cross value @@ -1184,14 +1228,12 @@ public class Mesh< T extends Region > { final Chain chain = new Chain( links ); addChain( chain ); - - // Remove this vertex since we know the upper and lower - // bounds have already converged, and met at this collinear - // vertex. - regionIt.remove(); - } else { - throw new IllegalStateException( "Region was not previously removed!" ); } + + // Remove this vertex since we know the upper and lower + // bounds have already converged, and met at this collinear + // vertex. + regionIt.remove(); } else if ( angleDifference > ANGLE_TOLERANCE ) { // If the upper angle is less than the lower angle, then we know // that the region is no longer visible to this vertex @@ -1228,26 +1270,6 @@ public class Mesh< T extends Region > { throw new IllegalStateException( "Cross value already used!" ); } } - - /* - * For each vertex, we only care about collinear chains of exactly - * 3 vertices. We must also know which collinear links intersect - * with our given 3-link. To detect collinear chains is quite - * simple: if the upper or lower cross is within ANGLE_TOLERANCE of - * the previous one. If so, then we should mark it as COLLINEAR, and - * take note of the 3 vertices. Then, once we have found all vertices - * for which the given vertex forms a 3-chain, mark the upper edge - * of this partition with the intersections. - * - * Upon encountering another intersection, we must perform the - * actual intersection determination check. But before that, - * look through all the intersection marks. Two intersections - * have the potential to be intersections if and only if: - * - The earlier intersection's destination be greater than - * the new intersection's origin - * - * - */ } } @@ -1268,7 +1290,7 @@ public class Mesh< T extends Region > { // Get the cross value for this vertex to the current event vertex final Vector2d toEvent = event.getPosition().subtracted( vert.getPosition() ).normalize(); - final double lowerCross = CROSS.cross( toEvent ); + final double lowerCross = clamp( CROSS.cross( toEvent ), -1, 1 ); final double lowerAngle = Math.asin( lowerCross ); // Check over each visibility region and update the upper cross value @@ -1335,14 +1357,12 @@ public class Mesh< T extends Region > { final Chain chain = new Chain( links ); addChain( chain ); - - // Remove this vertex since we know the upper and lower - // bounds have already converged, and met at this collinear - // vertex. - regionIt.remove(); - } else { - throw new IllegalStateException( "Region was not previously removed!" ); } + + // Remove this vertex since we know the upper and lower + // bounds have already converged, and met at this collinear + // vertex. + regionIt.remove(); } else if ( angleDifference > ANGLE_TOLERANCE ) { // If the upper angle is less than the lower angle, then we know // that the region is no longer visible to this vertex @@ -1403,16 +1423,26 @@ public class Mesh< T extends Region > { final Vertex chainMidpoint = chain.getMidpoint(); final Vertex chainDestination = chain.getDest(); + for ( final Chain other : chains ) { + if ( other.getOrigin() == chainOrigin && other.getDest() == chainDestination ) { + // Already added this chain, so break out early + return; + } + } + 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 ) { + // The new chain is a continuation of this chain 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 ) { + if ( chainOrigin == other.getMidpoint() ) { + chain.intersections.add( other ); + other.intersections.add( chain ); + } else if ( other.getOrigin() != chainOrigin && other.getDest() != chainDestination ) { // Make sure the chains don't share the same origin or destination // Perform a basic check @@ -1638,7 +1668,7 @@ public class Mesh< T extends Region > { } else { // Create a new visibility region final VisibilityRegion copyRegion = new VisibilityRegion( region.lowerEdge, region.upperEdge ); - + // Update the new region's lower links if ( region.lower != MIN_CROSS ) { copyRegion.lower = region.lower; @@ -1654,6 +1684,16 @@ public class Mesh< T extends Region > { region.lower = MIN_CROSS; } + // Inherit the upper region's links + // This may cause duplication of chains, but + // we should be able to check them when we add them + // TODO Can this be simplified? + if ( region.upper != MAX_CROSS ) { + copyRegion.upper = region.upper; + + copyRegion.links.put( region.upper, region.links.get( region.upper ) ); + } + copyRegions.add( copyRegion ); } } @@ -1672,7 +1712,7 @@ public class Mesh< T extends Region > { copy.incrementUpper( newUpper ); // Add the current vertex with a visibility region to the top and bottom partitions - toSplit.add( event, new VisibilityRegion( newUpper, newLower ) ); + toSplit.add( event, new VisibilityRegion( newLower, newUpper ) ); copy.add( event, new VisibilityRegion( newLower, newUpper ) ); newPartitions.add( copy ); @@ -1832,17 +1872,17 @@ public class Mesh< T extends Region > { System.out.println( "Found links: " + chains.size() ); Map< Vertex, Collection< Vertex > > mappings = new HashMap< Vertex, Collection< Vertex > >(); for ( final Chain c : chains ) { - 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( 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 ) { +// for ( final Chain i : c.intersections ) { // System.out.println( "\t\t" + i + ":\t" + i.getOrigin() + "\t to \t" + i.getDest() ); - } - if ( c.previous != null ) { +// } +// if ( c.previous != null ) { // System.out.println( "\tPrev: " + c.previous ); - } - if ( c.next != null ) { +// } +// if ( c.next != null ) { // System.out.println( "\tNext: " + c.next ); - } +// } Collection< Vertex > destinations; if ( mappings.containsKey( c.getOrigin() ) ) { @@ -1851,10 +1891,6 @@ public class Mesh< T extends Region > { destinations = new HashSet< Vertex >(); mappings.put( c.getOrigin(), destinations ); } - - if ( !destinations.add( c.getDest() ) ) { - System.out.println( "DUPLICATE!" ); - } } // Given a chain link, select the links which intersect the least, and remove any that it intersects with from the @@ -1877,6 +1913,7 @@ public class Mesh< T extends Region > { } } + // Chain links while ( !remainingChains.isEmpty() ) { // Find the chain with the least amount of intersections final Chain least = remainingChains.parallelStream().min( ( c1, c2 ) -> { @@ -1895,14 +1932,455 @@ public class Mesh< T extends Region > { selectedChains.add( least ); } +// if ( b ) { +// return selectedChains; +// } + + final Collection< Chain > cs = new HashSet< Chain >( selectedChains ); + System.out.println( "After reducing the amount of intersecting chains: " + selectedChains.size() ); - // TODO Now resolve the chains into the collection!!! + final EdgeSet added = new EdgeSet(); - return selectedChains; + + int i = 0; + while ( !selectedChains.isEmpty() ) { + System.out.println( "Iteration " + ++i ); + // Get a chain + Chain chain = selectedChains.iterator().next(); + + // Get the head of the chain + while ( selectedChains.contains( chain.previous ) ) { + chain = chain.previous; + } + + // Keep track of a left and right edge as we progress + HalfEdge left = null; + HalfEdge right = null; + + // Get the direction of this chain + final Vector2d chainDirection = chain.getDest().getPosition().subtracted( chain.getOrigin().getPosition() ); + + System.out.println( chain + ":\t" + chain.links.get( 0 ) + "\t to \t" + chain.links.get( 1 ) + "\t to \t" + chain.links.get( 2 ) ); + System.out.println( "Linking chain" ); + // Setup the left and right edges if the first link is not a direct edge from the origin to the midpoint + if ( chain.links.get( 0 ).getDest() != chain.getMidpoint() ) { + System.out.println( "Not!" ); + System.out.println( chain.links.get( 0 ) ); + final HalfEdge attach = findPrevious( chainDirection, chain.getOrigin() ); + left = new HalfEdge(); + HalfEdge.splice( left, attach ); + left.setOrigin( chain.getOrigin() ); + interior.add( left ); + + right = new HalfEdge(); + HalfEdge.splice( right, attach ); + right.setOrigin( chain.getOrigin() ); + interior.add( right.getSym() ); + + added.add( left ); + added.add( right ); + } + + System.out.println( "Starting chain " + chain.getOrigin() ); + + + + Chain previousChain; + do { + selectedChains.remove( chain ); + +// { +// HalfEdge e1 = chain.links.get( 0 ); +// HalfEdge e2 = chain.links.get( 1 ); +// +// if ( e1.getDest() == e2.getOrigin() && e2.getDest() == chain.getDest() ) { +// throw new IllegalStateException( "Double collinear lines!" ); +// } +// } + + final HalfEdge head = chain.links.get( 0 ); + final Vertex origin = chain.getOrigin(); + final Vertex midpoint = chain.getMidpoint(); + final Vertex destination = chain.getDest(); + + if ( head.getDest() == midpoint ) { + System.out.println( "Edge" ); + // Extend one, and start a new chain + if ( interior.contains( head ) ) { + System.out.println( "Left" ); + // Left handed + if ( right != null ) { + throw new IllegalStateException( "Unterminated left edge!" ); + } + right = new HalfEdge(); + + // Attach the new edge to the midpoint + HalfEdge.splice( head.getNext(), right ); + + right.setOrigin( midpoint ); + midpoint.setEdge( right ); + + interior.add( right.getSym() ); + + added.add( right ); + + // The left edge may not be null + // If it is not, then discard head + if ( left != null ) { + origin.setEdge( head.getPrev() ); + + // Remove the head from the origin + HalfEdge.splice( head, head.getSym().getNext() ); + + interior.remove( head ); + } else { + // Copy the head edge + left = new HalfEdge(); + + left.setOrigin( head.getOrigin() ); + + head.getOrigin().setEdge( left ); + + HalfEdge.splice( left, head.getSym().getNext() ); + HalfEdge.splice( left, head ); + + interior.add( left ); + interior.remove( head ); + + added.add( left ); + } + } else { +// System.out.println( "Right" ); + // Right handed + if ( left != null ) { + throw new IllegalStateException( "Unterminated right edge!" ); + } + left = new HalfEdge(); +// System.out.println( "4 \t" + chain.getMidpoint() ); + + // Attach the new edge to the midpoint + HalfEdge.splice( head.getNext(), left ); + + left.setOrigin( midpoint ); + midpoint.setEdge( left ); + + interior.add( left ); + + added.add( left ); + +// System.out.println( "3 \t" + chain.getMidpoint() ); + if ( right != null ) { + origin.setEdge( head.getPrev() ); + + // Remove the head from the origin + HalfEdge.splice( head, head.getSym().getNext() ); + + interior.remove( head.getSym() ); + } else { + // Copy the head edge + right = new HalfEdge(); + + right.setOrigin( head.getOrigin() ); + + head.getOrigin().setEdge( right ); + + HalfEdge.splice( right, head.getSym().getNext() ); + HalfEdge.splice( right, head ); + + interior.add( right.getSym() ); + interior.remove( head.getSym() ); + + added.add( right ); + } +// System.out.println( "2 \t" + chain.getMidpoint() ); + } + + // Detach the head from the midpoint + HalfEdge.splice( head.getSym(), head.getNext() ); +// System.out.println( "1 \t" + chain.getMidpoint() ); + } else { + // Attach one edge to the midpoint +// System.out.println( "Vertex" ); +// boolean test = true; +// if ( test ) { +// break; +// } + + final HalfEdge tail = chain.links.get( 1 ); + if ( tail.getDest() == destination ) { +// System.out.println( "Ends in solid" ); + if ( interior.contains( tail ) ) { + // Attach and unset the right edge + HalfEdge.splice( right.getSym(), tail ); + + right.getSym().setOrigin( midpoint ); + + midpoint.setEdge( right.getSym() ); + + right = null; + } else { + // Attach and unset the left edge + HalfEdge.splice( left.getSym(), tail.getSym().getNext() ); + + left.getSym().setOrigin( midpoint ); + + midpoint.setEdge( left.getSym() ); + + left = null; + } + } else { +// System.out.println( "Ends in vertex" ); + // Get which edge to splice with to attach to the midpoint + final HalfEdge midEdge = findPrevious( chainDirection, chain.getMidpoint() ); + + // Find which left/right edge to attach to the midpoint + if ( chainDirection.cross( midEdge.toVector2d() ) < 0 ) { + HalfEdge.splice( right.getSym(), midEdge ); + + right.getSym().setOrigin( midpoint ); + + // Create a new edge as the right edge + final HalfEdge newRight = new HalfEdge(); + + HalfEdge.splice( newRight, midEdge ); + + newRight.setOrigin( midpoint ); + + interior.add( newRight.getSym() ); + + added.add( newRight ); + + right = newRight; + } else { + HalfEdge.splice( left.getSym(), midEdge ); + + left.getSym().setOrigin( midpoint ); + + // Create a new edge as the left edge + final HalfEdge newLeft = new HalfEdge(); + + HalfEdge.splice( newLeft, left.getSym() ); + + newLeft.setOrigin( midpoint ); + + interior.add( newLeft ); + + added.add( newLeft ); + + left = newLeft; + } + } + } + + previousChain = chain; +// selectedChains.remove( chain.next ); +// cs.remove( chain.next ); +// break; + } while ( ( chain = chain.next ) != null ); + + // TODO Make sure the edges are fully attached/detached! Dangling edges are detected! + + // Terminate the chain + final Vertex destination = previousChain.getDest(); + final HalfEdge tail = previousChain.links.get( 1 ); + if ( tail.getDest() == destination ) { + tail.getOrigin().setEdge( tail.getPrev() ); + if ( interior.contains( tail ) ) { + tail.getDest().setEdge( left.getSym() ); + + // Remove the tail edge, and connect the left edge + HalfEdge.splice( left.getSym(), tail.getNext() ); + HalfEdge.splice( tail, tail.getSym().getNext() ); + HalfEdge.splice( left.getSym(), tail.getSym() ); + + left.getSym().setOrigin( destination ); + + interior.remove( tail ); + } else { + tail.getDest().setEdge( right.getSym() ); + + // Remove the tail edge, and connect it to the right edge + HalfEdge.splice( right.getSym(), tail.getNext() ); + HalfEdge.splice( tail, tail.getSym().getNext() ); + HalfEdge.splice( right.getSym(), tail.getSym() ); + + right.getSym().setOrigin( destination ); + + interior.remove( tail.getSym() ); + } + } else { +// System.out.println( "Linking the ends to a vertex" ); +// System.out.println( "0 \t" + previousChain.getMidpoint() ); +// +// System.out.println( "Dest size: " + destination.size() ); + if ( destination.size() == 1 ) { + throw new IllegalStateException( "ONE EDGE!" ); + } + + // Get which edge to splice with to attach to the destination + final HalfEdge destEdge = findPrevious( chainDirection.multiply( -1 ), destination ); +// System.out.println( "Dest edge is " + destEdge ); + + // Attach both the right and left edges + HalfEdge.splice( right.getSym(), destEdge ); + HalfEdge.splice( left.getSym(), destEdge ); + + right.getSym().setOrigin( destination ); + left.getSym().setOrigin( destination ); + +// System.out.println( "-1 \t" + previousChain.getMidpoint() ); + } + +// System.out.println( "Origin: " + previousChain.getOrigin() ); +// for ( HalfEdge e : previousChain.getOrigin() ) { +// System.out.println( e + "\t" + e.toVector2d() ); +// } +// +// System.out.println( "Mid: " + previousChain.getMidpoint() ); +// for ( HalfEdge e : previousChain.getMidpoint() ) { +// System.out.println( e + "\t" + e.toVector2d() ); +// } +// +// System.out.println( "Dest " + previousChain.getDest() ); +// for ( HalfEdge e : previousChain.getDest() ) { +// System.out.println( e + "\t" + e.toVector2d() ); +// } + } + +// boolean b = true; +// if ( b ) { +// return cs; +// } + + // Scan for consistency + for ( Vertex vert : vertices ) { + for ( HalfEdge edge : vert ) { + if ( edge.getOrigin() != vert ) { + throw new IllegalStateException( "Inconsistent origin!" ); + } else if ( !vertices.contains( edge.getDest() ) ) { + throw new IllegalStateException( "Missing destination!" ); + } + boolean found = false; + for ( HalfEdge e : edge.getDest() ) { + if ( e == edge.getSym() ) { + found = true; + break; + } + } + if ( !found ) { + if ( added.contains( edge ) ) { + throw new IllegalStateException( "Was a newly created edge!" ); + } + throw new IllegalStateException( "Bad edge/vertex!" ); + } else if ( edge.isZero() ) { + throw new IllegalStateException( "Zero edge!" ); + } + } + } + + { + Set< HalfEdge > scanned = new HashSet< HalfEdge >(); + for ( final HalfEdge edge : interior ) { + if ( !scanned.add( edge ) ) { + HalfEdge temp = edge; + do { + if ( scanned.contains( temp.getSym() ) ) { + if ( added.contains( temp ) ) { + throw new IllegalStateException( "Double interior chain!" ); + } + throw new IllegalStateException( "Double interior edge!!" ); + } + + if ( !interior.contains( temp ) ) { + if ( added.contains( temp ) ) { + throw new IllegalStateException( "Unregistered chain!" ); + } + throw new IllegalStateException( "Unregistered interior edge!" ); + } + } while ( scanned.add( temp = temp.getNext() ) ); + } + } + } + + { + final Set< HalfEdge > scanned = new HashSet< HalfEdge >(); + for ( final HalfEdge edge : interior ) { + if ( scanned.add( edge ) ) { + final Set< Vertex > verts = new HashSet< Vertex >(); + HalfEdge e = edge; + do { +// if ( !verts.add( e.getOrigin() ) ) { +// System.out.println( "Poly" ); +// HalfEdge t = edge; +// do { +// System.out.println( t ); +// } while ( ( t = t.getNext() ) != edge ); +// +// throw new IllegalStateException( "Intersecting polygon!" ); +// } + if ( !scanned.add( e.getSym() ) ) { + throw new IllegalStateException( "Mobius polygon!" ); + } + scanned.add( e ); + } while ( scanned.add( e = e.getNext() ) ); + } + } + } + + return cs; } - private static Collection< EdgePolygon > partitionMonotone( final TreeSet< Vertex > vertices, final Collection< HalfEdge > interior ) { + // Use the vertex's edge as the hint, basically because we have no better edge to start from + private static HalfEdge findPrevious( final Vector2d vector, final Vertex vertex ) { + return findPrevious( vector, vertex, vertex.getEdge() ); + } + + /* + * Find the half edge immediately preceding the provided vector, but allow the user to provide a hint edge, which may be closer + * to the actual edge that we want to use. + */ + private static HalfEdge findPrevious( final Vector2d vector, final Vertex vertex, final HalfEdge hint ) { + if ( hint.getOrigin() != vertex ) { + throw new IllegalStateException( "Hint vertex does not match provided vertex!" ); + } + + // The lowest angle we've seen so far + // Set to 10 because it's larger than 2 pi + double lowest = 12; + + final Vector2d normalized = vector.normalized(); + + // TODO Remove this check + double size = vertex.size(); + + // This is a fairly simple use case, so compare absolute angles + HalfEdge edge = hint; + while ( true ) { + double angle = edge.toVector2d().normalized().angle( normalized ); + if ( angle < 0 ) { + angle += Math.PI * 2; + } + if ( lowest == 10 ) { + lowest = angle; + } else if ( angle > lowest + ANGLE_TOLERANCE ) { + // We've looped back around, so return the previous edge + return edge.getSym().getNext(); + } else { + lowest = angle; + } + edge = edge.getPrev(); + + if ( --size < 0 ) { + throw new IllegalStateException( "Infinite loop! Vertex size: " + vertex.size() ); + } + } + } + + /* + * Partitions all polygons into simple monotone polygons. The vertices must be sorted, and the interiors must contain all interior edges. + */ + private static Collection< EdgePolygon > partitionMonotone( final TreeSet< Vertex > vertices, final Collection< HalfEdge > interior, boolean deb ) { // Helper class to keep track of the immediate upper and lower // edges for a given vertex class MarkedEdge { @@ -1920,24 +2398,54 @@ public class Mesh< T extends Region > { } // 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 ); + // Use a custom comparator that sorts by if the edges are interior or exterior... interior edges are greater than exterior. + // Only applicable in this situation where only two edges maximum of opposite sides may overlap. + 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() ) { + throw new IllegalStateException( "Zero length edge detected" ); + } + if ( !( isPositive( a ) && isPositive( b ) ) ) { + throw new IllegalStateException( "Negative edge detected" ); + } + + // a1 < a2 + final Vector2d a1 = a.getOrigin().getPosition(); + final Vector2d a2 = a.getDest().getPosition(); + // b1 < b2 + final Vector2d b1 = b.getOrigin().getPosition(); + final Vector2d b2 = b.getDest().getPosition(); + + final int compared = compare( a.getOrigin(), b.getOrigin() ); + double cross; + if ( compared == 0 ) { + cross = b2.subtracted( b1 ).normalized().cross( a2.subtracted( a1 ).normalized() ); + } else if ( compared < 0 ) { + cross = b1.subtracted( a1 ).normalized().cross( a2.subtracted( a1 ).normalized() ); + } else { + cross = b2.subtracted( b1 ).normalized().cross( a1.subtracted( b1 ).normalized() ); + } + + if ( Math.asin( Math.min( 1, Math.abs( cross ) ) ) > ANGLE_TOLERANCE ) { + return cross > 0; + } else { + // Collinear + return !( !interior.contains( a ) && interior.contains( b ) ); + } + } ); - // Keep track of all vertices that need to be linked with a left-going edge - final Map< Vertex, MarkedEdge > leftMarked = new HashMap< Vertex, MarkedEdge >(); - // Likewise, keep track of all vertices that need to be linked with a right-going edge - final Map< Vertex, MarkedEdge > rightMarked = new HashMap< Vertex, MarkedEdge >(); + // Keep track of all vertices that need to be linked with a right-going edge + final Map< HalfEdge, MarkedEdge > rightMarked = new HashMap< HalfEdge, MarkedEdge >(); // Keep track of all edges that we added so we can do stuff with them later final EdgeSet supportEdges = new EdgeSet(); - // Keep track of which vertices were used as a pylon so that we - // can sort them later, since we added at least one extra edge - final Set< Vertex > toSort = new HashSet< Vertex >(); // Sweep each vertex and mark the upper and lower regions for ( final Vertex event : vertices ) { // Get the left and right wings - final Collection< HalfEdge > rightGoing = new ArrayDeque< HalfEdge >(); - final Collection< HalfEdge > leftGoing = new ArrayDeque< HalfEdge >(); + final Collection< HalfEdge > rightGoing = new HashSet< HalfEdge >(); + final Collection< HalfEdge > leftGoing = new HashSet< HalfEdge >(); for ( final HalfEdge edge : event ) { if ( isPositive( edge ) ) { @@ -1950,6 +2458,12 @@ public class Mesh< T extends Region > { } } + // Get the uppermost right edge + HalfEdge upperRight = null; + if ( !rightGoing.isEmpty() ) { + upperRight = findPrevious( new Vector2d( 0, 1 ), event ); + } + // Create a mark if the vertex has no left xor no right going edges // AKA, if it prevents a polygon from being monotone. // It should never have no left and no right edges, since that @@ -1973,6 +2487,11 @@ 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 ) { + System.out.println( "Event: " + event ); + System.out.println( "Edges:" ); + for ( HalfEdge e : edgeCollection ) { + System.out.println( interior.contains( e ) + "\t" + e + "\t" + e.hashCode() ); + } throw new IllegalStateException( "Lower does not have an upper edge!" ); } if ( !interior.contains( upper.getSym() ) ) { throw new IllegalStateException( "Polygon is not simple!" ); @@ -1988,9 +2507,9 @@ public class Mesh< T extends Region > { } // Scan all right marked vertices to see if it can be connected to this one - for ( final Iterator< Entry< Vertex, MarkedEdge > > it = rightMarked.entrySet().iterator(); it.hasNext(); ) { - final Entry< Vertex, MarkedEdge > entry = it.next(); - final Vertex prev = entry.getKey(); + for ( final Iterator< Entry< HalfEdge, MarkedEdge > > it = rightMarked.entrySet().iterator(); it.hasNext(); ) { + final Entry< HalfEdge, MarkedEdge > entry = it.next(); + final HalfEdge prev = entry.getKey(); final MarkedEdge marked = entry.getValue(); final HalfEdge lower = marked.lower; @@ -2001,10 +2520,10 @@ public class Mesh< T extends Region > { final HalfEdge edge = new HalfEdge(); edge.setOrigin( event ); - edge.getSym().setOrigin( prev ); + edge.getSym().setOrigin( prev.getOrigin() ); HalfEdge.splice( edge, upper.getSym() ); - HalfEdge.splice( edge.getSym(), prev.getEdge() ); + HalfEdge.splice( edge.getSym(), prev ); supportEdges.add( edge ); } else if ( lower.getDest() == event ) { @@ -2012,10 +2531,10 @@ public class Mesh< T extends Region > { final HalfEdge edge = new HalfEdge(); edge.setOrigin( event ); - edge.getSym().setOrigin( prev ); + edge.getSym().setOrigin( prev.getOrigin() ); HalfEdge.splice( edge, lower.getNext() ); - HalfEdge.splice( edge.getSym(), prev.getEdge() ); + HalfEdge.splice( edge.getSym(), prev ); supportEdges.add( edge ); } else if ( marked.equals( eventMark ) ) { @@ -2030,20 +2549,16 @@ public class Mesh< T extends Region > { final HalfEdge edge = new HalfEdge(); edge.setOrigin( event ); - edge.getSym().setOrigin( prev ); + edge.getSym().setOrigin( prev.getOrigin() ); - HalfEdge.splice( edge, event.getEdge() ); - HalfEdge.splice( edge.getSym(), prev.getEdge() ); + HalfEdge.splice( edge, upperRight ); + HalfEdge.splice( edge.getSym(), prev ); // Add the edge to the left-going for this vertex // so it can be considered "fixed" leftGoing.add( edge ); supportEdges.add( edge ); - // Technically both vertices need to be sorted - toSort.add( event ); - toSort.add( prev ); - // Can break out of this loop early, since this vertex must have // been a left-marked vertex, and therefore cannot fulfill any of the // conditions above anymore. @@ -2062,15 +2577,66 @@ public class Mesh< T extends Region > { // resolved. Only add it if it does not have any left or right going // edges. if ( leftGoing.isEmpty() ) { - // Process this after we are done processing all right-marked vertices - leftMarked.put( event, eventMark ); - } else if ( rightGoing.isEmpty() ) { - rightMarked.put( event, eventMark ); + final HalfEdge lower = eventMark.lower; + final HalfEdge upper = eventMark.upper; - // We are queuing the event for sorting now - // which is why we didn't queue it for sorting in the earlier - // for loop. It would have been queued already anyways. - toSort.add( event ); + final HalfEdge edge = new HalfEdge(); + + edge.setOrigin( event ); + + supportEdges.add( edge ); + if ( compare( lower.getOrigin(), upper.getOrigin() ) > 0 ) { +// for ( Vertex v : vertices ) { +// System.out.println( v ); +// } + // Lower comes before upper +// System.out.println( "At " + event ); +// System.out.println( "lower " + lower ); +// System.out.println( "upper " + upper ); +// System.out.println( "Before:" ); +// for ( HalfEdge e : lower.getOrigin() ) { +// System.out.println( interior.contains( e ) + "\t" + e ); +// } +// System.out.println( "And" ); +// for ( HalfEdge e : event ) { +// System.out.println( interior.contains( e ) + "\t" + e ); +// } + edge.getSym().setOrigin( lower.getOrigin() ); + + final Vector2d toLower = lower.getOrigin().getPosition().subtracted( event.getPosition() ); + + final HalfEdge vertexPrevious = findPrevious( toLower, event, upperRight ); + final HalfEdge lowerPrevious = findPrevious( toLower.multiply( -1 ), lower.getOrigin(), lower ); + + + HalfEdge.splice( edge, vertexPrevious ); + HalfEdge.splice( edge.getSym(), lowerPrevious ); + +// System.out.println( "Edge: " + lowerPrevious ); +// System.out.println( "After:" ); +// for ( HalfEdge e : lower.getOrigin() ) { +// System.out.println( interior.contains( e ) + "\t" + e ); +// } +// System.out.println( "And" ); +// for ( HalfEdge e : event ) { +// System.out.println( interior.contains( e ) + "\t" + e ); +// } + } else { + // Upper comes before lower + edge.getSym().setOrigin( upper.getOrigin() ); + + final Vector2d toUpper = upper.getOrigin().getPosition().subtracted( event.getPosition() ); + + + final HalfEdge vertexPrevious = findPrevious( toUpper, event, upperRight ); + final HalfEdge upperPrevious = findPrevious( toUpper.multiply( -1 ), upper.getOrigin(), upper.getSym().getNext() ); + + HalfEdge.splice( edge, vertexPrevious ); + HalfEdge.splice( edge.getSym(), upperPrevious ); + } + } else if ( rightGoing.isEmpty() ) { + final HalfEdge lowerLeft = findPrevious( new Vector2d( 0, -1 ), event ); + rightMarked.put( lowerLeft, eventMark ); } } @@ -2090,78 +2656,29 @@ public class Mesh< T extends Region > { throw new IllegalStateException( "Unresolved right marked vertices!" ); } - // Reverse sweep the vertices and connect any left marked vertices - for ( final Entry< Vertex, MarkedEdge > entry : leftMarked.entrySet() ) { - final Vertex vertex = entry.getKey(); - final MarkedEdge mark = entry.getValue(); - - final HalfEdge lower = mark.lower; - final HalfEdge upper = mark.upper; - - // Get whichever vertex is further to the left/down - Vertex lesser = upper.getOrigin(); - if ( compare( lesser, lower.getOrigin() ) > 0 ) { - lesser = lower.getOrigin(); - } - - // Get the largest range of vertices that we need to check - final Set< Vertex > checkSet = vertices.descendingSet() - .tailSet( vertex, false ) - .headSet( lesser, true ); - - // Find the next previous vertex that: - // - Is a marked vertex with the same upper and lower edges - // - Is the origin of the upper or lower edge - for ( final Vertex prev : checkSet ) { - // Do a similar check as with the right marked vertices - if ( lower.getOrigin() == prev ) { - final HalfEdge edge = new HalfEdge(); - - edge.setOrigin( prev ); - edge.getSym().setOrigin( vertex ); - - HalfEdge.splice( edge, lower ); - HalfEdge.splice( edge.getSym(), vertex.getEdge() ); - - supportEdges.add( edge ); - } else if ( upper.getOrigin() == prev ) { - HalfEdge edge = new HalfEdge(); - - edge.setOrigin( prev ); - edge.getSym().setOrigin( vertex ); - - HalfEdge.splice( edge, upper.getSym().getNext() ); - HalfEdge.splice( edge.getSym(), vertex.getEdge() ); - - supportEdges.add( edge ); - } else if ( mark.equals( leftMarked.get( prev ) ) ) { - // Need to sort prev, since we can't guarantee it's being - // inserted in the correct position - final HalfEdge edge = new HalfEdge(); - - edge.setOrigin( prev ); - edge.getSym().setOrigin( vertex ); - - HalfEdge.splice( edge, prev.getEdge() ); - HalfEdge.splice( edge.getSym(), vertex.getEdge() ); - - supportEdges.add( edge ); - - toSort.add( prev ); - } else { - continue; - } - - // Only need to merge with the first vertex found to maintain monotony - break; - } - - toSort.add( vertex ); - } - - // Sort the vertices we have to sort, now that we're done - toSort.parallelStream().forEach( v -> sort( v ) ); - +// boolean b = true; +// if ( b ) { +// final Collection< EdgePolygon > newPolygons = new ArrayDeque< EdgePolygon >(); +// +// // Keep track of all edges that we've seen +// final Set< HalfEdge > scanned = new HashSet< HalfEdge >(); +// for ( final HalfEdge edge : interior ) { +// if ( scanned.add( edge ) ) { +// // We have not looked at this edge yet, so loop over it +// // and create a new polygon from it +// final EdgePolygon newPoly = new EdgePolygon(); +// HalfEdge temp = edge; +// +// do { +// newPoly.addEdge( temp ); +// } while ( scanned.add( temp = temp.getNext() ) ); +// newPolygons.add( newPoly ); +// } +// } +// +// return newPolygons; +// } + // In preparation for the generation of new regions, // we need to duplicate each support edge so that the // upper and lower polygons can have their edge. @@ -2214,6 +2731,42 @@ public class Mesh< T extends Region > { } } + { + final Set< Vertex > verts = new HashSet< Vertex >(); + for ( EdgePolygon poly : newPolygons ) { + final int size = verts.size(); + if ( verts.addAll( poly.getVertices() ) ) { + if ( size + poly.getVertices().size() != verts.size() ) { + throw new IllegalStateException( "Shared vertex!" ); + } + } + } + } + + if ( !deb ) { + for ( EdgePolygon poly : newPolygons ) { +// System.out.println( "New Poly" ); + final Set< Vertex > verts = new HashSet< Vertex >(); + final Set< HalfEdge > scanned2 = new HashSet< HalfEdge >(); + HalfEdge e = poly.getEdges().iterator().next(); + scanned2.add( e ); + do { +// System.out.println( e ); +// if ( !verts.add( e.getOrigin() ) ) { +// System.out.println( "Around " + e.getOrigin() ); +// for ( HalfEdge edge : e.getOrigin() ) { +// System.out.println( edge ); +// } +// +// throw new IllegalStateException( "Intersecting polygon!" ); +// } + if ( !scanned2.add( e.getSym() ) ) { + throw new IllegalStateException( "Mobius polygon!" ); + } + } while ( scanned2.add( e = e.getNext() ) ); + } + } + return newPolygons; } @@ -2223,7 +2776,9 @@ public class Mesh< T extends Region > { // Computation Geometry Algorithms and Applications 3rd Ed. final Collection< Vertex > toSort = new HashSet< Vertex >(); - if ( polygon.getEdges().size() == 3 ) { + if ( polygon.getEdges().size() < 3 || polygon.getVertices().size() < 3 ) { + throw new IllegalStateException( "Invalid amount of verts/edges!" ); + } else if ( polygon.getEdges().size() == 3 ) { return Arrays.asList( polygon ); } @@ -2255,6 +2810,10 @@ public class Mesh< T extends Region > { polygon.addEdge( newEdge ); + if ( newEdge.isZero() ) { + throw new IllegalStateException( "Zero during initial pop!" ); + } + toSort.add( edge.getOrigin() ); toSort.add( current.getOrigin() ); @@ -2287,6 +2846,10 @@ public class Mesh< T extends Region > { polygon.addEdge( newEdge ); + if ( newEdge.isZero() ) { + throw new IllegalStateException( "Zero during second pop!" ); + } + leftEdge = newEdge; current = stack.pop(); @@ -2315,6 +2878,10 @@ public class Mesh< T extends Region > { polygon.addEdge( newEdge ); + if ( newEdge.isZero() ) { + throw new IllegalStateException( "Zero during last pop!" ); + } + toSort.add( last.getOrigin() ); toSort.add( popped.getOrigin() ); } @@ -2336,7 +2903,7 @@ public class Mesh< T extends Region > { } while ( scanned.add( temp = temp.getNext() ) ); if ( poly.getVertices().size() != poly.getEdges().size() ) { - throw new IllegalStateException( "Polygon has inconsistent edges/vertices!" ); + throw new IllegalStateException( "Polygon has inconsistent edges/vertices! " + poly.getVertices().size() + "/" + poly.getEdges().size() ); } if ( poly.getVertices().size() != 3 ) { @@ -2395,12 +2962,13 @@ public class Mesh< T extends Region > { return 0; } - final Vector2d aPos = a.getPosition(); - final Vector2d bPos = b.getPosition(); - - final double x = compareX( aPos, bPos ); + return compare( a.getPosition(), b.getPosition() ); + } + + private static int compare( final Vector2d a, final Vector2d b ) { + final double x = compareX( a, b ); if ( x == 0 ) { - return Double.compare( compareY( aPos, bPos ), 0 ); + return Double.compare( compareY( a, b ), 0 ); } return Double.compare( x, 0 ); } @@ -2424,6 +2992,15 @@ public class Mesh< T extends Region > { return Math.abs( a.distance( b ) ) <= VERTEX_TOLERANCE; } + private static double clamp( final double v, final double a, final double b ) { + if ( v < a ) { + return a; + } else if ( v > b ) { + return b; + } + return v; + } + /* * Represents an intersection */ diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java index b48d792..e557382 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java @@ -18,8 +18,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Random; +import java.util.Set; import javax.swing.JFrame; import javax.swing.JPanel; @@ -178,7 +180,7 @@ public class MeshingTest2 extends JPanel { private static void test( final Collection< Plane > planes ) { final long processAllStart = System.currentTimeMillis(); - planes.parallelStream().forEach( plane -> { + planes.stream().forEach( plane -> { try { Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); @@ -188,7 +190,7 @@ public class MeshingTest2 extends JPanel { long start = System.currentTimeMillis(); mesh.simplify(); mesh.generateRegions(); - final Collection< EdgePolygon > polys = mesh.copyOf().mesh(); + final Collection< EdgePolygon > polys = mesh.copyOf().mesh( false ); long end = System.currentTimeMillis(); System.out.println( plane.polygons.size() + " to " + polys.size() + ":\t " + ( end - start ) + "ms" ); } catch ( IllegalStateException e ) { @@ -263,7 +265,7 @@ public class MeshingTest2 extends JPanel { // Make sure this works mesh = mesh.copyOf(); - final Collection< EdgePolygon > polys = mesh.mesh(); + final Collection< EdgePolygon > polys = mesh.mesh( false ); chains = mesh.chains; long end = System.currentTimeMillis(); System.out.println( "Took " + ( end - start ) + "ms" ); @@ -278,20 +280,138 @@ public class MeshingTest2 extends JPanel { private static Collection< EdgePolygon > test() { Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); - mesh.addPolygon( new Polygon( Arrays.asList( - new Point( -1, 0 ), - new Point( 1, -2 ), - new Point( 1, -1 ), - new Point( 2, -1 ), - new Point( 1, 0 ), - new Point( 1, 1 ) - ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( -1, 0 ), +// new Point( 1, -2 ), +// new Point( 1, -1 ), +// new Point( 2, -1 ), +// new Point( 1, 0 ), +// new Point( 1, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 5, 0 ), +// new Point( 5, 1 ), +// new Point( 4, 1 ), +// new Point( 4, 2 ), +// new Point( 3, 2 ), +// new Point( 3, 3 ), +// new Point( 2, 3 ), +// new Point( 2, 4 ), +// new Point( 0, 4 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 2, 0 ), +// new Point( 2, 1 ), +// new Point( 1, 1 ), +// new Point( 1, 2 ), +// new Point( 2, 2 ), +// new Point( 2, 4 ), +// new Point( 1, 4 ), +// new Point( 1, 3 ), +// new Point( 0, 3 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 3, 0 ), +// new Point( 3, 1 ), +// new Point( 4, 1 ), +// new Point( 4, 2 ), +// new Point( 2, 2 ), +// new Point( 2, 1 ), +// new Point( 1, 1 ), +// new Point( 0, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); + +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 2, 0 ), +// new Point( 2, 1 ), +// new Point( 3, 1 ), +// new Point( 3, 2 ), +// new Point( 2, 2 ), +// new Point( 2, 5 ), +// new Point( 1, 5 ), +// new Point( 1, 7 ), +// new Point( 0, 7 ), +// new Point( 0, 4 ), +// new Point( 1, 4 ), +// new Point( 1, 1 ), +// new Point( 0, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 1, 0 ), +// new Point( 6, 0 ), +// new Point( 6, 1 ), +// new Point( 4, 1 ), +// new Point( 4, 2 ), +// new Point( 2, 2 ), +// new Point( 2, 3 ), +// new Point( 0, 3 ), +// new Point( 0, 1 ), +// new Point( 1, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 3, 1 ), +// new Point( 3, 0 ), +// new Point( 10, 0 ), +// new Point( 10, 6 ), +// new Point( 9, 6 ), +// new Point( 9, 4 ), +// new Point( 8, 4 ), +// new Point( 8, 3 ), +// new Point( 5, 3 ), +// new Point( 5, 2 ), +// new Point( 0, 2 ), +// new Point( 0, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 7, 2 ), +// new Point( 9, 2 ), +// new Point( 9, 3 ), +// new Point( 7, 3 ) +// ) ), RegionRuleWinding.CLOCKWISE ); + +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 6, 1 ), +// new Point( 7, 2 ), +// new Point( 1, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 2, 2 ), +// new Point( 8, 3 ), +// new Point( 9, 4 ), +// new Point( 3, 3 ) +// ) ), RegionRuleWinding.CLOCKWISE ); + +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 2.816104467407629, -3.7472939407853763 ), +// new Point( 2.815383313894849, -2.9972942874937156 ), +// new Point( 2.0028836894955497, -2.9980755371325607 ), +// new Point( 2.0036048430083304, -3.7480751904242213 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 2.8144217758778085, -1.9972947497715015 ), +// new Point( 2.0019221514785093, -1.998075999410347 ), +// new Point( 2.00264330499129, -2.748075652702007 ), +// new Point( 2.8151429293905887, -2.747294403063162 ) +// ) ), RegionRuleWinding.CLOCKWISE ); mesh.simplify(); mesh.generateRegions(); - final Collection< EdgePolygon > polys = mesh.mesh(); + try { + final Collection< EdgePolygon > polys = mesh.mesh( false ); + chains = mesh.chains; + return polys; + } catch ( Exception e ) { + e.printStackTrace(); + } + + final Collection< EdgePolygon > polys = mesh.mesh( true ); chains = mesh.chains; return polys; @@ -492,9 +612,13 @@ public class MeshingTest2 extends JPanel { g.drawLine( 0, ( int ) ( scale * centerY ) + offsetY, getWidth(), ( int ) ( scale * centerY ) + offsetY ); g.setColor( Color.BLACK ); + int highestX = 40; if ( data != null ) { Collection< Vertex > polygons = data; + highestX = ( int ) ( polygons.parallelStream() + .max( ( a, b ) -> { return Double.compare( a.getPosition().getX(), b.getPosition().getX() ); } ).get().getPosition().getX() + 40.5 ); + EdgeSet edges = new EdgeSet(); for ( Vertex vert : polygons ) { for ( HalfEdge edge : vert ) { @@ -502,9 +626,9 @@ public class MeshingTest2 extends JPanel { Point p1 = edge.getOrigin().getPosition(); Point p2 = edge.getDest().getPosition(); g.drawLine( - ( int ) ( ( centerX + p1.getX() + 40 ) * scale ) + offsetX, + ( int ) ( ( centerX + p1.getX() + highestX ) * scale ) + offsetX, ( int ) ( ( centerY - p1.getY() ) * scale ) + offsetY, - ( int ) ( ( centerX + p2.getX() + 40 ) * scale ) + offsetX, + ( int ) ( ( centerX + p2.getX() + highestX ) * scale ) + offsetX, ( int ) ( ( centerY - p2.getY() ) * scale ) + offsetY ); } @@ -513,7 +637,7 @@ public class MeshingTest2 extends JPanel { final double diff = scale * 0.15; final Point point = vert.getPosition(); g.setColor( Color.BLACK ); - g.drawRect( ( int ) ( ( centerX + point.getX() + 40 ) * scale ) - ( int ) diff + offsetX, ( int ) ( ( centerY - point.getY() ) * scale ) - ( int ) diff + offsetY, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) ); + g.drawRect( ( int ) ( ( centerX + point.getX() + highestX ) * scale ) - ( int ) diff + offsetX, ( int ) ( ( centerY - point.getY() ) * scale ) - ( int ) diff + offsetY, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) ); } } @@ -521,9 +645,11 @@ public class MeshingTest2 extends JPanel { g.setColor( Color.BLACK ); int count = 2; Random random = new Random( hashCode() ); + for ( final EdgePolygon p : polygons ) { final Collection< HalfEdge > edges = p.getEdges(); + final Set< Vertex > verts = new HashSet< Vertex >(); g.setColor( Color.BLACK ); for ( HalfEdge edge : edges ) { Point p1 = edge.getOrigin().getPosition(); @@ -541,6 +667,11 @@ public class MeshingTest2 extends JPanel { ( int ) ( ( centerX + p2.getX() ) * scale ) + offsetX, ( int ) ( ( centerY - p2.getY() - 150 ) * scale ) + offsetY ); + + final double diff = scale * 0.15; + if ( !verts.add( edge.getOrigin() ) ) { + g.drawRect( ( int ) ( ( centerX + p1.getX() + count * 25 ) * scale ) - ( int ) diff + offsetX, ( int ) ( ( centerY - p1.getY() - 150 ) * scale ) - ( int ) diff + offsetY, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) ); + } } count++; @@ -548,11 +679,22 @@ public class MeshingTest2 extends JPanel { int[] xPoints = new int[ size ]; int[] yPoints = new int[ size ]; + final Set< Vertex > verts2 = new HashSet< Vertex >(); + HalfEdge edge = edges.iterator().next(); for ( int i = 0; i < edges.size(); ++i ) { final Point point = edge.getOrigin().getPosition(); xPoints[ i ] = ( int ) ( ( centerX + point.getX() ) * scale ) + offsetX; yPoints[ i ] = ( int ) ( ( centerY - point.getY() ) * scale ) + offsetY; + + final double diff = scale * 0.15; + if ( !verts2.add( edge.getOrigin() ) ) { + g.setColor( Color.ORANGE ); + } else { + g.setColor( Color.BLACK ); + } + g.drawRect( ( int ) ( ( centerX + point.getX() + highestX ) * scale ) - ( int ) diff + offsetX, ( int ) ( ( centerY - point.getY() ) * scale ) - ( int ) diff + offsetY, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) ); + edge = edge.getNext(); } @@ -562,16 +704,30 @@ public class MeshingTest2 extends JPanel { } if ( chains != null ) { + int i = 0; for ( Chain chain : chains ) { Point p1 = chain.getOrigin().getPosition(); Point p2 = chain.getDest().getPosition(); g.setColor( Color.RED ); g.drawLine( - ( int ) ( ( centerX + p1.getX() + 40 ) * scale ) + offsetX, - ( int ) ( ( centerY - p1.getY() ) * scale ) + offsetY, - ( int ) ( ( centerX + p2.getX() + 40 ) * scale ) + offsetX, - ( int ) ( ( centerY - p2.getY() ) * scale ) + offsetY + ( int ) ( ( centerX + p1.getX() + highestX ) * scale ) + offsetX + i, + ( int ) ( ( centerY - p1.getY() ) * scale ) + offsetY + i, + ( int ) ( ( centerX + p2.getX() + highestX ) * scale ) + offsetX + i, + ( int ) ( ( centerY - p2.getY() ) * scale ) + offsetY + i ); + +// for ( HalfEdge e : chain.getLinks() ) { +// Point e1 = e.getOrigin().getPosition(); +// Point e2 = e.getSym().getOrigin().getPosition(); +// g.setColor( Color.BLUE ); +// g.drawLine( +// ( int ) ( ( centerX + e1.getX() + 40 ) * scale ) + offsetX + i + 1, +// ( int ) ( ( centerY - e1.getY() ) * scale ) + offsetY + i + 1, +// ( int ) ( ( centerX + e2.getX() + 40 ) * scale ) + offsetX + i + 1, +// ( int ) ( ( centerY - e2.getY() ) * scale ) + offsetY + i + 1 +// ); +// } +// i += 2; } }