diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java index be6763e..30420ca 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java @@ -885,7 +885,7 @@ public class Mesh< T extends Region > { } edges.add( edge ); } - + // 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 ) -> { @@ -900,10 +900,10 @@ public class Mesh< T extends Region > { if ( cross2 < 0 ) { cross2 += 10; } - + return Double.compare( cross2, cross1 ); } ); - + // Only need to re-organize the edges if there are 3 or more if ( edges.size() > 2 ) { for ( int i = 0; i < edges.size(); ++i ) { @@ -1141,10 +1141,22 @@ public class Mesh< T extends Region > { handlers.forEach( h -> h.onPartitionEvent( polygons ) ); } - return polys.parallelStream() - .map( Mesh::triangulate ) - .flatMap( p -> p.parallelStream() ) - .collect( Collectors.toSet() ); + if ( mergeChains ) { + return polys.parallelStream() + .map( Mesh::triangulate ) + .flatMap( p -> p.parallelStream() ) + .collect( Collectors.toSet() ); + } else { + // If do not merge chains, then at least try to merge some adjacent collinear edges + // which may have resulted from partitioning into monotone polygons, since the triangles + // probably don't play too nicely with those(0 area triangles). + return polys.stream() + .peek( p -> mergeAdjacentCollinearEdges( p.getVertices(), p.getEdges() ).forEach( e -> p.getVertices().remove( e.getOrigin() ) ) ) + .map( Mesh::triangulate ) + .flatMap( p -> p.parallelStream() ) + .collect( Collectors.toSet() ); + } + } /** @@ -2739,18 +2751,20 @@ public class Mesh< T extends Region > { // Go down each monotone chain and connect the vertices where possible. // Implements the O(n) triangulation of a polygon as described in // Computation Geometry Algorithms and Applications 3rd Ed. - final Collection< Vertex > toSort = new HashSet< Vertex >(); 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( new Polygon( polygon.getVertices().stream().map( v -> new Point( v.getPosition() ) ).toList() ) ); + return Arrays.asList( new Polygon( polygon.getVertices().stream().map( v -> new Point( v.getPosition() ) ).collect( Collectors.toList() ) ) ); } + // Keep track of which edge to insert new edges for the given vertex + final Map< Vertex, HalfEdge > insertEdges = new HashMap< Vertex, HalfEdge >(); final Queue< HalfEdge > edges = new PriorityQueue< HalfEdge >( ( a, b ) -> { return compare( a.getOrigin(), b.getOrigin() ); } ); edges.addAll( polygon.getEdges() ); + edges.forEach( e -> insertEdges.put( e.getOrigin(), e ) ); final Stack< HalfEdge > stack = new Stack< HalfEdge >(); // Add the first two vertices/edges @@ -2761,49 +2775,72 @@ public class Mesh< T extends Region > { while ( edges.size() > 1 ) { final HalfEdge edge = edges.poll(); - if ( isPositive( edge ) ^ isPositive( stack.peek() ) ) { + final boolean isEdgePositive = isPositive( edge ); + if ( isEdgePositive ^ isPositive( stack.peek() ) ) { // Insert an edge from the current event to each vertex in the stack HalfEdge current = stack.pop(); + // Keep track of the latest edge that we can splice to + HalfEdge insertEdge = edge; while ( !stack.isEmpty() ) { final HalfEdge newEdge = new HalfEdge(); newEdge.setOrigin( edge.getOrigin() ); newEdge.getSym().setOrigin( current.getOrigin() ); - HalfEdge.splice( newEdge, edge ); - HalfEdge.splice( newEdge.getSym(), current ); + // Find the correct edge to insert onto for the edge in the stack + // If the current edge is positive, then the stack edge must be negative, + // and vice versa. In addition, the edge in the stack may already have + // some edges added to it previously. + if ( isEdgePositive ) { + HalfEdge.splice( newEdge, insertEdge ); + HalfEdge.splice( newEdge.getSym(), insertEdges.get( current.getOrigin() ) ); + + insertEdges.put( current.getOrigin(), newEdge.getSym() ); + } else { + HalfEdge.splice( newEdge, edge ); + HalfEdge.splice( newEdge.getSym(), insertEdges.get( current.getOrigin() ) ); + } + insertEdge = newEdge; polygon.addEdge( newEdge ); - toSort.add( edge.getOrigin() ); - toSort.add( current.getOrigin() ); - current = stack.pop(); } + if ( !isEdgePositive ) { + insertEdges.put( edge.getOrigin(), edge.getSym().getNext().getSym().getNext() ); + } + stack.push( prev ); } else { HalfEdge current = stack.pop(); - final boolean isPositive = isPositive( edge ); - HalfEdge leftEdge = isPositive ? edge.getPrev() : edge; + HalfEdge leftEdge = isEdgePositive ? edge.getPrev() : edge; + HalfEdge insertEdge = edge; while ( !stack.isEmpty() ) { final HalfEdge next = stack.peek(); - final Vector2d diagonal = next.getOrigin().getPosition().subtracted( edge.getOrigin().getPosition() ); - final double cross = diagonal.cross( leftEdge.toVector2d() ); + final Vector2d diagonal = next.getOrigin().getPosition().subtracted( edge.getOrigin().getPosition() ).normalize(); + final double cross = diagonal.cross( leftEdge.toVector2d().normalize() ); - // Check if the diagonal is inside the polygon - final boolean isInside = isPositive ? cross > 0 : cross < 0; + // Check if the diagonal is inside the polygon... Use some tolerance + final boolean isInside = isEdgePositive ? cross > CROSS_TOLERANCE : cross < -CROSS_TOLERANCE; if ( isInside ) { final HalfEdge newEdge = new HalfEdge(); newEdge.setOrigin( edge.getOrigin() ); newEdge.getSym().setOrigin( next.getOrigin() ); - HalfEdge.splice( newEdge, edge ); - HalfEdge.splice( newEdge.getSym(), next ); - - toSort.add( edge.getOrigin() ); - toSort.add( next.getOrigin() ); + if ( isEdgePositive ) { + HalfEdge.splice( newEdge, edge ); + HalfEdge.splice( newEdge.getSym(), insertEdges.get( next.getOrigin() ) ); + + insertEdges.put( next.getOrigin(), newEdge.getSym() ); + + polygon.addEdge( newEdge ); + } else { + HalfEdge.splice( newEdge, insertEdge ); + HalfEdge.splice( newEdge.getSym(), insertEdges.get( next.getOrigin() ) ); + } + insertEdge = newEdge; polygon.addEdge( newEdge ); @@ -2814,6 +2851,10 @@ public class Mesh< T extends Region > { break; } } + if ( !isEdgePositive ) { + insertEdges.put( edge.getOrigin(), insertEdge ); + } + stack.push( current ); } stack.push( edge ); @@ -2821,29 +2862,32 @@ public class Mesh< T extends Region > { prev = edge; } + // The last edge must be a negative edge, since + // the last vertex must terminate the polygon and + // therefore have no right-going edges. final HalfEdge last = edges.poll(); stack.pop(); + final boolean isPositive = isPositive( stack.peek() ); + HalfEdge insertEdge = last; while ( stack.size() > 1 ) { final HalfEdge popped = stack.pop(); final HalfEdge newEdge = new HalfEdge(); newEdge.setOrigin( last.getOrigin() ); newEdge.getSym().setOrigin( popped.getOrigin() ); + + if ( isPositive ) { + HalfEdge.splice( newEdge, last ); + } else { + HalfEdge.splice( newEdge, insertEdge ); + } + HalfEdge.splice( newEdge.getSym(), insertEdges.get( popped.getOrigin() ) ); - HalfEdge.splice( newEdge, last ); - HalfEdge.splice( newEdge.getSym(), popped ); + insertEdge = newEdge; polygon.addEdge( newEdge ); - - toSort.add( last.getOrigin() ); - toSort.add( popped.getOrigin() ); } - // Lazy solution, just sort the vertices - // Eventually we will remove this - // TODO Remove this NOW - toSort.parallelStream().forEach( v -> sort( v ) ); - // Convert each vertex to a point final Map< Vertex, Point > pointMap = polygon.vertices.parallelStream().collect( Collectors.toMap( Function.identity(), v -> new Point( v.getPosition() ) ) ); diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java index 73d7e5e..ac35f8e 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; import javax.swing.JButton; import javax.swing.JComboBox; @@ -83,6 +84,8 @@ public class MeshingTest2 extends JPanel { private JLabel statusBar; private JComboBox< String > selectionBox; + private ReentrantLock lock = new ReentrantLock(); + public static void main( String[] args ) { if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) { System.out.println( "Found " + CHUNK_DIR.list().length + " files" ); @@ -129,7 +132,7 @@ public class MeshingTest2 extends JPanel { // break; // } - List< Plane > masterPlanes = new ArrayList< Plane >(); + final List< Plane > masterPlanes = new ArrayList< Plane >(); System.out.println( "Chunk files: " + CHUNK_DIR.listFiles().length ); int limit = 1; for ( File file : CHUNK_DIR.listFiles() ) { @@ -140,8 +143,9 @@ public class MeshingTest2 extends JPanel { System.out.println( "Remaining: " + limit ); } } -// final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-4,-6" ) ); -// masterPlanes.add( planes.get( 177 ) ); + +// final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-1,-1" ) ); +// masterPlanes.add( planes.get( 20 ) ); // masterPlanes.addAll( planes ); // masterPlanes.add( getTestPlane() ); @@ -679,23 +683,24 @@ public class MeshingTest2 extends JPanel { } private MeshingTest2 setIndex( final int index ) { - // TODO Synchronize this final int planeCount = planes.size(); final int newIndex = ( ( ( index % planeCount ) + planeCount ) % planeCount ); + lock.lock(); if ( this.index != newIndex ) { this.index = newIndex; - new Thread( () -> { getData( this::repaint ); } ).start(); - if ( selectionBox != null ) { selectionBox.setSelectedIndex( newIndex ); } + + new Thread( () -> { getData( this.index, this::repaint ); } ).start(); } + lock.unlock(); return this; } - private PlaneData getData( final Runnable callback ) { + private PlaneData getData( final int index, final Runnable callback ) { if ( data[ index ] != null ) { callback.run(); return data[ index ]; diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java index 100f446..f444c55 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java @@ -11,7 +11,6 @@ import java.util.List; import org.bukkit.util.Vector; import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh; -import com.aaaaahhhhhhh.bananapuncher714.mesh.Point; import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon; import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionRuleWinding; import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple; @@ -31,7 +30,8 @@ public class MeshingTest3 { System.out.println( "Found " + CHUNK_DIR.list().length + " files" ); for ( int i = 0; i < CHUNK_DIR.listFiles().length; ++i ) { final File file = CHUNK_DIR.listFiles()[ i ]; - System.out.println( "Meshing " + file + "\t" + ( i + 1 ) + "/" + CHUNK_DIR.listFiles().length ); + System.out.println( ( i + 1 ) + "/" + CHUNK_DIR.listFiles().length + ":\tMeshing " + file ); + long start = System.currentTimeMillis(); final List< Plane > planes = mesh( file ); planes.parallelStream().forEach( p -> { try { @@ -42,15 +42,19 @@ public class MeshingTest3 { System.exit( 1 ); } } ); + + long time = System.currentTimeMillis() - start; + + System.out.println( "\tTook " + time + "ms" ); } -// final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-4,-6" ) ); // 123 +// final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-1,-1" ) ); // 123 // for ( int i = 0; i < planes.size(); ++i ) { // System.out.println( "Meshing plane " + i ); // process( planes.get( i ) ); // } -// process( planes.get( 177 ) ); +// process( planes.get( 20 ) ); } else { System.err.println( "No such directory exists: " + CHUNK_DIR ); } @@ -74,7 +78,7 @@ public class MeshingTest3 { String[] split = name.split( "," ); ChunkLocation location = new ChunkLocation( split[ 0 ], Integer.parseInt( split[ 1 ] ), Integer.parseInt( split[ 2 ] ) ); - System.out.println( "Parsing chunk " + location ); + System.out.println( "\tParsing chunk " + location ); // First parse the file to get a list of all bounding boxes that we can use List< AABB > boxes = new ArrayList< AABB >(); @@ -100,7 +104,7 @@ public class MeshingTest3 { return null; } - System.out.println( "Found " + boxes.size() + " boxes" ); + System.out.println( "\tFound " + boxes.size() + " boxes" ); // Now that we have a bunch of bounding boxes, do whatever MeshBuilder builder = new MeshBuilder(); diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MiniePlugin.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MiniePlugin.java index db681da..d1a7437 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MiniePlugin.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MiniePlugin.java @@ -298,7 +298,7 @@ public class MiniePlugin extends JavaPlugin { displayData = Material.TNT.createBlockData(); } - BoundingBox[] boxes = convertFrom( getShape( displayData, null, BlockShapeType.VISUAL_SHAPE ) ); + BoundingBox[] boxes = convertFrom( getShape( displayData, null, BlockShapeType.SHAPE ) ); Vector blockCenter = calculateCenter( boxes ); CollisionShape box;