From 0f430e7a31f92db0b22af0a70f2a18e7b933edef Mon Sep 17 00:00:00 2001 From: BananaPuncher714 Date: Tue, 6 May 2025 00:08:52 -0400 Subject: [PATCH] Fixed monotone partioning and finished triangulation algorithm Removed a lot of debug code Added comments Fixed partition monotone not setting the vertex after unlinking edges Added some better java awt stuff Added a method to copy meshes --- .../bananapuncher714/mesh/Mesh.java | 393 +++++++++++++----- .../minietest/MeshingTest2.java | 276 +++++++----- 2 files changed, 456 insertions(+), 213 deletions(-) diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java index f208f95..288dcff 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java @@ -16,23 +16,63 @@ import java.util.Optional; import java.util.PriorityQueue; import java.util.Queue; import java.util.Set; +import java.util.Stack; import java.util.TreeSet; import java.util.function.Supplier; +import java.util.stream.Collectors; import com.aaaaahhhhhhh.bananapuncher714.mesh.region.Region; import com.aaaaahhhhhhh.bananapuncher714.mesh.region.RegionRule; -import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionRuleWinding; -import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple; -import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple.GluWindingRule; /** - * A general mesh class containing polygons + * A general mesh class containing polygons. Inspiration taken from: https://github.com/memononen/libtess2 + * + * This general algorithm loosely follows the libtess2 source code, but is more or less re-done + * from scratch to be simpler and easier to follow and comprehend. In addition, there is support + * for custom region winding. + * + * 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 + * - Merging vertices on edges that are at most VERTEX_TOLERANCE away + * - Merging colinear edges + * - Creating new vertices where two or more edges intersect + * This makes the resulting graph much easier to traverse and process. Unlike libtess2, + * we do the simplification separate from step 2, so that we don't run into cases where + * modifying/fixing a vertex will change some property of a previous vertex, thus invalidating + * previously processed sections. + * + * In addition to the simplification, this also merges overlapping edges, which is performed + * before generating regions. All RegionRules for each polygon _must_ be able to be merged. + * That is, given rule A, B and C, and region u and v, v = B * A * u, then C = B * A and v = C * u. + * + * 2. Generating regions based on the PSLG and RegionRules. Given the default region outside + * the PSLG, determine which regions enclosed by edges are interior or exterior regions. + * Any edges between two interior/exterior regions is a no-op edge, and can be removed. + * 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 colinear 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 colinear edges which may have been generated as a result of the monotone partitioning. + * 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 + * 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(nlogn). + * + * TODO Remove guaranteed checks or add some compile time thing to remove * * @author BananaPuncher714 */ public class Mesh< T extends Region > { public static final double VERTEX_TOLERANCE = 1e-7; - public static final double ANGLE_TOLERANCE = Math.toRadians( 0.01 ); + public static final double ANGLE_TOLERANCE = Math.toRadians( 0.0001 ); // All points in the graph protected Collection< Vertex > vertices = new ArrayDeque< Vertex >(); @@ -42,88 +82,24 @@ public class Mesh< T extends Region > { protected final Supplier< T > defaultRegionSupplier; - protected MeshState state = MeshState.SIMPLIFIED; - - public static void main( String[] args ) { - Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); - - mesh.addPolygon( new Polygon( Arrays.asList( - new Point( -1, -1 ), - new Point( 1, -1 ), - new Point( 1, 1 ), - new Point( -1, 1 ) - ) ), RegionRuleWinding.CLOCKWISE ); - mesh.addPolygon( new Polygon( Arrays.asList( - new Point( 1, -1 ), - new Point( 3, -1 ), - new Point( 3, 1 ), - new Point( 1, 1 ) - ) ), RegionRuleWinding.CLOCKWISE ); - -// mesh.addPolygon( new Polygon( Arrays.asList( -// new Point( -1, -1 ), -// new Point( 1, -1 ), -// new Point( 1, 1 ), -// new Point( -1, 1 ) -// ) ), RegionRuleWinding.CLOCKWISE ); -// mesh.addPolygon( new Polygon( Arrays.asList( -// new Point( -1, 1 ), -// new Point( 1, 1 ), -// new Point( 1, 3 ), -// new Point( -1, 3 ) -// ) ), RegionRuleWinding.CLOCKWISE ); -// mesh.addPolygon( new Polygon( Arrays.asList( -// new Point( -2, -1 ), -// new Point( -1, -1 ), -// new Point( -1, 2 ), -// new Point( -2, 2 ) -// ) ), RegionRuleWinding.CLOCKWISE ); -// mesh.addPolygon( new Polygon( Arrays.asList( -// new Point( -4, -2 ), -// new Point( -1, -2 ), -// new Point( -1, 1.5 ), -// new Point( -4, 1.5 ) -// ) ), RegionRuleWinding.CLOCKWISE ); -// mesh.addPolygon( new Polygon( Arrays.asList( -// new Point( -3, 0 ), -// new Point( 3, 0 ), -// new Point( -1.5, -3 ), -// new Point( 0, 1.5 ), -// new Point( 1.5, -3 ) -// ) ), RegionRuleWinding.CLOCKWISE ); -// mesh.addPolygon( new Polygon( Arrays.asList( -// new Point( -1, 0 ), -// new Point( 0, 1.5 ), -// new Point( 1, 0 ) -// ) ), RegionRuleWinding.CLOCKWISE ); -// mesh.addPolygon( new Polygon( Arrays.asList( -// new Point( 1, 0 ), -// new Point( 0, -2 ), -// new Point( -1, 0 ) -// ) ), RegionRuleWinding.CLOCKWISE ); - - System.out.println( "Vertices: " + mesh.vertices.size() ); - System.out.println( "Edges: " + ( mesh.rules.size() / 2 ) ); - mesh.simplify(); - System.out.println( "Vertices: " + mesh.vertices.size() ); - System.out.println( "Edges: " + ( mesh.rules.size() / 2 ) ); - mesh.generateRegions(); - System.out.println( "Vertices: " + mesh.vertices.size() ); - System.out.println( "Edges: " + ( mesh.rules.size() / 2 ) ); - System.out.println( "Partitions: " + mesh.mesh().size() ); - } + protected MeshState state = MeshState.TRIANGULATION_READY; public Mesh( final Supplier< T > defaultRegionSupplier ) { this.defaultRegionSupplier = defaultRegionSupplier; } public void addPolygon( final Polygon poly, final RegionRule< T > rule ) { + // Is it at least a triangle? + if ( poly.getPoints().size() < 3 ) { + return; + } + HalfEdge edge = null; for ( Point point : poly.getPoints() ) { if ( edge == null ) { edge = new HalfEdge(); HalfEdge.splice( edge, edge.getSym() ); - edge.getOrigin().update(); + edge.getSym().setOrigin( edge.getOrigin() ); } else { edge = edge.split(); } @@ -148,8 +124,77 @@ public class Mesh< T extends Region > { } public void clear() { + state = MeshState.TRIANGULATION_READY; vertices.clear(); rules.clear(); + interiorEdges.clear(); + } + + public Mesh< T > copyOf() { + final Mesh< T > copy = new Mesh< T >( defaultRegionSupplier ); + + // Keep track of all edges that we've seen + final EdgeSet scanned = new EdgeSet(); + // Map each old vertex to a new vertex + final Map< Vertex, Vertex > newVertices = new HashMap< Vertex, Vertex >(); + + /* + * We now want to create a completely separate PSLG so that we don't modify + * the original one. + */ + for ( Vertex vertex : vertices ) { + for ( final HalfEdge edge : vertex ) { + if ( scanned.add( edge ) ) { + HalfEdge temp = edge; + + HalfEdge newEdge = null; + do { + if ( newEdge == null ) { + newEdge = new HalfEdge(); + HalfEdge.splice( newEdge, newEdge.getSym() ); + newEdge.getSym().setOrigin( newEdge.getOrigin() ); + } else { + newEdge = newEdge.split(); + } + + final Vertex tempVert = temp.getOrigin(); + + // Re-use a previously created vertex if there is one + Vertex vert; + if ( newVertices.containsKey( tempVert ) ) { + vert = newVertices.get( tempVert ); + + newEdge.setOrigin( vert ); + newEdge.getPrev().setOrigin( vert ); + + HalfEdge.splice( newEdge.getPrev(), vert.getEdge() ); + } else { + vert = newEdge.getOrigin(); + newVertices.put( tempVert, vert ); + + vert.setPosition( tempVert.getPosition() ); + + copy.vertices.add( vert ); + } + + if ( interiorEdges.contains( temp ) ) { + copy.interiorEdges.add( temp ); + } + if ( interiorEdges.contains( temp.getSym() ) ) { + copy.interiorEdges.add( temp.getSym() ); + } + + copy.rules.put( newEdge, rules.get( temp ) ); + copy.rules.put( newEdge.getSym(), rules.get( temp.getSym() ) ); + } while ( scanned.add( temp = temp.getNext() ) ); + } + } + } + + copy.vertices.parallelStream().forEach( v -> sort( v ) ); + copy.state = state; + + return copy; } /** @@ -160,6 +205,9 @@ public class Mesh< T extends Region > { * * In addition, remove vertices without any edges, and solve any * intersections by splitting edges and/or adding new vertices as required. + * + * This method only removes intersections. It does NOT guarantee that polygons + * will be simple, since there can be holes. */ public void simplify() { // Create a queue and insert all vertices in O(nlogn) time. @@ -573,7 +621,10 @@ public class Mesh< T extends Region > { * intersections or overlaps. */ public void generateRegions() { - if ( state != MeshState.SIMPLIFIED ) { + if ( state == MeshState.TRIANGULATION_READY ) { + // The regions have already been generated + return; + } else if ( state != MeshState.SIMPLIFIED ) { throw new IllegalStateException( "Mesh is not simplified!" ); } @@ -691,7 +742,10 @@ public class Mesh< T extends Region > { } // Merge any colinear edges before returning - mergeColinearEdges( keep, interiorAboveEdges ); + mergeColinearEdges( keep, interiorAboveEdges ).forEach( e -> { + rules.remove( e ); + rules.remove( e.getSym() ); + } ); interiorEdges.clear(); interiorEdges.addAll( interiorAboveEdges ); @@ -731,7 +785,7 @@ public class Mesh< T extends Region > { } } ); - for ( int i = 0; i < edges.size(); i++ ) { + for ( int i = 0; i < edges.size(); ++i ) { // Get this edge and the next edge final HalfEdge e1 = edges.get( i ); final HalfEdge e2 = edges.get( ( i + 1 ) % edges.size() ); @@ -749,7 +803,7 @@ public class Mesh< T extends Region > { return edges; } - private void mergeColinearEdges( final Collection< Vertex > vertices, final Collection< HalfEdge > internalEdges ) { + private static Collection< HalfEdge > mergeColinearEdges( final Collection< Vertex > vertices, final Collection< HalfEdge > internalEdges ) { final Collection< HalfEdge > toRemove = new ArrayDeque< HalfEdge >(); final Set< HalfEdge > edges = new HashSet< HalfEdge >(); @@ -814,9 +868,6 @@ public class Mesh< T extends Region > { edges.add( next ); - rules.remove( next ); - rules.remove( next.getSym() ); - toRemove.add( next ); } else { edges.add( edge ); @@ -826,8 +877,10 @@ public class Mesh< T extends Region > { } internalEdges.removeAll( toRemove ); + + return toRemove; } - + public Collection< EdgePolygon > mesh() { if ( state != MeshState.TRIANGULATION_READY ) { throw new IllegalStateException( "Mesh has not been split into regions!" ); @@ -844,6 +897,10 @@ public class Mesh< T extends Region > { // Get a collection of vertices that need to be sorted, again final Collection< Vertex > toSort = new HashSet< Vertex >(); + /* + * We now want to create a completely separate PSLG so that we don't modify + * the original one. + */ for ( final HalfEdge edge : interiorEdges ) { if ( scanned.add( edge ) ) { HalfEdge temp = edge; @@ -853,6 +910,7 @@ public class Mesh< T extends Region > { if ( newEdge == null ) { newEdge = new HalfEdge(); HalfEdge.splice( newEdge, newEdge.getSym() ); + newEdge.getSym().setOrigin( newEdge.getOrigin() ); } else { newEdge = newEdge.split(); } @@ -864,6 +922,9 @@ public class Mesh< T extends Region > { if ( newVertices.containsKey( tempVert ) ) { vert = newVertices.get( tempVert ); + newEdge.setOrigin( vert ); + newEdge.getPrev().setOrigin( vert ); + HalfEdge.splice( newEdge.getPrev(), vert.getEdge() ); toSort.add( vert ); @@ -873,24 +934,27 @@ public class Mesh< T extends Region > { vert.setPosition( tempVert.getPosition() ); } - vert.update(); edges.add( newEdge ); } while ( scanned.add( temp = temp.getNext() ) ); } } - for ( final Vertex v : toSort ) { - sort( v ); - } + toSort.parallelStream().forEach( v -> sort( v ) ); // Have a single set of edges final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare ); vertices.addAll( newVertices.values() ); - final Collection< EdgePolygon > polygons = partitionMonotone( vertices, edges ); - - return polygons; + return partitionMonotone( vertices, edges ).parallelStream() + .map( p -> { + mergeColinearEdges( p.getVertices(), p.getEdges() ).forEach( e -> p.getVertices().remove( e.getOrigin() ) ); + + return p; + } ) + .map( Mesh::triangulate ) + .flatMap( p -> p.parallelStream() ) + .collect( Collectors.toSet() ); } private static Collection< EdgePolygon > partitionMonotone( final TreeSet< Vertex > vertices, final Collection< HalfEdge > interior ) { @@ -1111,7 +1175,7 @@ public class Mesh< T extends Region > { edge.setOrigin( prev ); edge.getSym().setOrigin( vertex ); - HalfEdge.splice( lower, edge ); + HalfEdge.splice( edge, lower ); HalfEdge.splice( edge.getSym(), vertex.getEdge() ); supportEdges.add( edge ); @@ -1121,7 +1185,7 @@ public class Mesh< T extends Region > { edge.setOrigin( prev ); edge.getSym().setOrigin( vertex ); - HalfEdge.splice( upper.getSym().getNext(), edge ); + HalfEdge.splice( edge, upper.getSym().getNext() ); HalfEdge.splice( edge.getSym(), vertex.getEdge() ); supportEdges.add( edge ); @@ -1172,11 +1236,12 @@ public class Mesh< T extends Region > { // Now that we've split the polygon into monotone regions, we must // also return a collection of the newly created polygon, since each // monotone region is its own separate polygon + // That means each polygon should not share any vertices or edges, + // which is perfect for us so we can triangulate each polygon in parallel 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 @@ -1188,11 +1253,13 @@ public class Mesh< T extends Region > { // 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 ); } @@ -1205,8 +1272,136 @@ public class Mesh< T extends Region > { return newPolygons; } - private void triangulate() { - // TODO + private static Collection< EdgePolygon > 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 + // Computation Geometry Algorithms and Applications 3rd Ed. + final Collection< Vertex > toSort = new HashSet< Vertex >(); + + if ( polygon.getEdges().size() == 3 ) { + return Arrays.asList( polygon ); + } + + final Queue< HalfEdge > edges = new PriorityQueue< HalfEdge >( ( a, b ) -> { + return compare( a.getOrigin(), b.getOrigin() ); + } ); + edges.addAll( polygon.getEdges() ); + + final Stack< HalfEdge > stack = new Stack< HalfEdge >(); + // Add the first two vertices/edges + stack.add( edges.poll() ); + stack.add( edges.poll() ); + + HalfEdge prev = stack.peek(); + while ( edges.size() > 1 ) { + final HalfEdge edge = edges.poll(); + + if ( isPositive( edge ) ^ isPositive( stack.peek() ) ) { + // Insert an edge from the current event to each vertex in the stack + HalfEdge current = stack.pop(); + 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 ); + + polygon.addEdge( newEdge ); + + toSort.add( edge.getOrigin() ); + toSort.add( current.getOrigin() ); + + current = stack.pop(); + } + stack.push( prev ); + } else { + HalfEdge current = stack.pop(); + final boolean isPositive = isPositive( edge ); + HalfEdge leftEdge = isPositive ? edge.getPrev() : edge; + while ( !stack.isEmpty() ) { + final HalfEdge next = stack.peek(); + + final Vector2d diagonal = next.getOrigin().getPosition().subtracted( edge.getOrigin().getPosition() ); + final double cross = diagonal.normalize().cross( leftEdge.toVector2d().normalize() ); + + // Check if the diagonal is inside the polygon + final boolean isInside = isPositive ? cross > ANGLE_TOLERANCE : cross < - ANGLE_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() ); + + polygon.addEdge( newEdge ); + + leftEdge = newEdge; + + current = stack.pop(); + } else { + break; + } + } + stack.push( current ); + } + stack.push( edge ); + + prev = edge; + } + + final HalfEdge last = edges.poll(); + stack.pop(); + while ( stack.size() > 1 ) { + final HalfEdge popped = stack.pop(); + final HalfEdge newEdge = new HalfEdge(); + + newEdge.setOrigin( last.getOrigin() ); + newEdge.getSym().setOrigin( popped.getOrigin() ); + + HalfEdge.splice( newEdge, last ); + HalfEdge.splice( newEdge.getSym(), popped ); + + polygon.addEdge( newEdge ); + + toSort.add( last.getOrigin() ); + toSort.add( popped.getOrigin() ); + } + + // Lazy solution, just sort the vertices + // Eventually we will remove this + toSort.parallelStream().forEach( v -> sort( v ) ); + + // TODO Convert to triangles or something? + final Collection< EdgePolygon > polygons = new ArrayDeque< EdgePolygon >(); + final Set< HalfEdge > scanned = new HashSet< HalfEdge >(); + for ( final HalfEdge edge : polygon.getEdges() ) { + if ( scanned.add( edge ) ) { + HalfEdge temp = edge; + EdgePolygon poly = new EdgePolygon(); + + do { + poly.addEdge( temp ); + } while ( scanned.add( temp = temp.getNext() ) ); + + if ( poly.getVertices().size() != poly.getEdges().size() ) { + throw new IllegalStateException( "Polygon has inconsistent edges/vertices!" ); + } + + if ( poly.getVertices().size() != 3 ) { + throw new IllegalStateException( "Not a triangle! " + poly.getVertices().size() ); + } + polygons.add( poly ); + } + } + + return polygons; } /* @@ -1328,7 +1523,7 @@ public class Mesh< T extends Region > { public static class EdgePolygon { private Set< HalfEdge > edges = new HashSet< HalfEdge >(); - private TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare ); + private Set< Vertex > vertices = new HashSet< Vertex >(); private void addEdge( HalfEdge edge ) { edges.add( edge ); @@ -1339,7 +1534,7 @@ public class Mesh< T extends Region > { return edges; } - public TreeSet< Vertex > getVertices() { + public Set< Vertex > getVertices() { return vertices; } } diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java index 3bab805..4d3a9bc 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java @@ -1,7 +1,16 @@ package com.aaaaahhhhhhh.bananapuncher714.minietest; import java.awt.Color; +import java.awt.Font; import java.awt.Graphics; +import java.awt.MouseInfo; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; @@ -11,13 +20,13 @@ 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.Set; +import java.util.Random; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; +import javax.swing.Timer; import org.bukkit.util.Vector; @@ -52,12 +61,13 @@ public class MeshingTest2 extends JPanel { // private int centerX = 600; // private int centerY = 400; - private int centerX = 400; - private int centerY = 100; + private int offsetX = windowWidth >> 1; + private int offsetY = windowHeight >> 1; + + private double centerX = 0; + private double centerY = 0; - private Graphics g; - - private int scale = 8; + private double scale = 8; private Collection< Vertex > data; private Collection< EdgePolygon > polygons; @@ -78,14 +88,24 @@ public class MeshingTest2 extends JPanel { } } + // Select a random plane to draw // draw = planes.get( new Random().nextInt( planes.size() ) ); -// test( planes ); + System.out.println( "Draw is " + draw ); + // Attempt to mesh all planes +// try { +// test( planes ); +// } catch ( PolygonException e ) { +// draw = e.getPlane(); +// System.out.println( "Draw is now " + draw ); +// } final Collection< Vertex > data = process( draw ); final Collection< EdgePolygon > polys = triangulate( draw ); // final Collection< EdgePolygon > polys = test(); + System.out.println( "Polygon count: " + polys.size() ); + SwingUtilities.invokeLater( new Runnable() { @Override public void run() { @@ -134,7 +154,6 @@ public class MeshingTest2 extends JPanel { // Now that we have a bunch of bounding boxes, do whatever MeshBuilder builder = new MeshBuilder(); - Plane draw = null; for ( AABB box : boxes ) { for ( Facet facet : generateFacetsFor( box ) ) { builder.addFacet( facet ); @@ -144,26 +163,40 @@ public class MeshingTest2 extends JPanel { return builder.planes; } + public static class PolygonException extends RuntimeException { + Plane plane; + + PolygonException( Plane plane ) { + this.plane = plane; + } + + Plane getPlane() { + return plane; + } + } + private static void test( final Collection< Plane > planes ) { - final long processAllStart = System.currentTimeMillis(); - planes.parallelStream().forEach( plane -> { - try { - Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); - - for ( Polygon poly : plane.polygons ) { - mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE ); - } - long start = System.currentTimeMillis(); - mesh.simplify(); - mesh.generateRegions(); - long end = System.currentTimeMillis(); - System.out.println( plane.polygons.size() + ":\t " + ( end - start ) + "ms" ); - } catch ( IllegalStateException e ) { - e.printStackTrace(); + final long processAllStart = System.currentTimeMillis(); + planes.parallelStream().forEach( plane -> { + try { + Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); + + for ( Polygon poly : plane.polygons ) { + mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE ); } - } ); - final long processAllEnd = System.currentTimeMillis(); - System.out.println( "Took " + ( processAllEnd - processAllStart ) + "ms to process " + planes.size() + " planes" ); + long start = System.currentTimeMillis(); + mesh.simplify(); + mesh.generateRegions(); + final Collection< EdgePolygon > polys = mesh.mesh(); + long end = System.currentTimeMillis(); + System.out.println( plane.polygons.size() + " to " + polys.size() + ":\t " + ( end - start ) + "ms" ); + } catch ( IllegalStateException e ) { + e.printStackTrace(); + throw new PolygonException( plane ); + } + } ); + final long processAllEnd = System.currentTimeMillis(); + System.out.println( "Took " + ( processAllEnd - processAllStart ) + "ms to process " + planes.size() + " planes" ); } private static Collection< Vertex > process( final Plane plane ) { @@ -226,6 +259,9 @@ public class MeshingTest2 extends JPanel { System.out.println( "After generating regions vertex count: " + mesh.getVertices().size() ); System.out.println( "After generating regions edge count: " + ( mesh.getRuleSize() / 2 ) ); + // Make sure this works + mesh = mesh.copyOf(); + final Collection< EdgePolygon > polys = mesh.mesh(); long end = System.currentTimeMillis(); System.out.println( "Took " + ( end - start ) + "ms" ); @@ -360,6 +396,69 @@ public class MeshingTest2 extends JPanel { f = new JFrame( "Drawing Board" ); f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + final MouseAdapter adapter = new MouseAdapter() { + private boolean dragging = false; + private double initX, initY; + private int dragX, dragY; + private double scroll = Math.log( scale ); + + @Override + public void mousePressed( MouseEvent e ) { + dragging = true; + + initX = centerX; + initY = centerY; + + dragX = e.getXOnScreen(); + dragY = e.getYOnScreen(); + } + + @Override + public void mouseReleased( MouseEvent e ) { + dragging = false; + } + + @Override + public void mouseDragged( MouseEvent e ) { + if ( dragging ) { + centerX = initX + ( ( e.getXOnScreen() - dragX ) / scale ); + centerY = initY + ( ( e.getYOnScreen() - dragY ) / scale ); + + repaint(); + } + } + + @Override + public void mouseWheelMoved( MouseWheelEvent e ) { + if ( e.getWheelRotation() < 0 ) { + scroll += 0.5; + } else { + scroll -= 0.5; + } + scale = Math.exp( scroll ); + + repaint(); + } + + @Override + public void mouseMoved( MouseEvent e ) { + super.mouseMoved( e ); + + repaint(); + } + }; + + addMouseListener( adapter ); + addMouseMotionListener( adapter ); + addMouseWheelListener( adapter ); + + f.addComponentListener( new ComponentAdapter() { + public void componentResized( ComponentEvent e ) { + offsetX = getWidth() >> 1; + offsetY = getHeight() >> 1; + }; + } ); + f.add( this ); f.setSize( windowWidth, windowHeight ); @@ -369,10 +468,6 @@ public class MeshingTest2 extends JPanel { f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); } - public void drawPoint( double x, double y ) { - g.fillRect( ( int ) x * scale, ( int ) y * scale, scale, scale ); - } - private static Vector2d rotate( Vector2d a, double angle ) { double cos = Math.cos( angle ); double sin = Math.sin( angle ); @@ -384,30 +479,15 @@ public class MeshingTest2 extends JPanel { @Override public void paintComponent( Graphics g ) { super.paintComponent( g ); - this.g = g; g.setColor( new Color( 200, 200, 200 ) ); - g.drawLine( centerX, 0, centerX, 2000 ); - g.drawLine( 0, centerY, 2000, centerY ); + g.drawLine( ( int ) ( scale * centerX ) + offsetX, 0, ( int ) ( scale * centerX ) + offsetX, getHeight() ); + g.drawLine( 0, ( int ) ( scale * centerY ) + offsetY, getWidth(), ( int ) ( scale * centerY ) + offsetY ); g.setColor( Color.BLACK ); if ( data != null ) { Collection< Vertex > polygons = data; -// Set< HalfEdge > positiveEdges = new HashSet< HalfEdge >(); -// Set< HalfEdge > negativeEdges = new HashSet< HalfEdge >(); -// -// for ( Vertex v : polygons ) { -// for ( HalfEdge e : v ) { -// if ( Mesh.isPositive( e ) ) { -// positiveEdges.add( e ); -// } else { -// negativeEdges.add( e ); -// } -// } -// } - - EdgeSet edges = new EdgeSet(); for ( Vertex vert : polygons ) { for ( HalfEdge edge : vert ) { @@ -415,10 +495,10 @@ public class MeshingTest2 extends JPanel { Point p1 = edge.getOrigin().getPosition(); Point p2 = edge.getDest().getPosition(); g.drawLine( - ( int ) ( p1.getX() * scale ) + centerX, - ( int ) ( - p1.getY() * scale ) + centerY, - ( int ) ( p2.getX() * scale ) + centerX, - ( int ) ( - p2.getY() * scale ) + centerY + ( 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 ); } } @@ -426,36 +506,36 @@ public class MeshingTest2 extends JPanel { final double diff = scale * 0.15; final Point point = vert.getPosition(); g.setColor( Color.BLACK ); - g.drawRect( ( int ) ( point.getX() * scale ) + centerX - ( int ) diff, ( int ) ( - point.getY() * scale ) + centerY - ( int ) diff, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) ); + 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 ) ); } } if ( polygons != null ) { -// System.out.println( "Polygon count: " + polygons.size() ); g.setColor( Color.BLACK ); int count = 2; + Random random = new Random( hashCode() ); for ( final EdgePolygon p : polygons ) { final Collection< HalfEdge > edges = p.getEdges(); -// g.setColor( Color.BLACK ); -// for ( HalfEdge edge : edges ) { -// Point p1 = edge.getOrigin().getPosition(); -// Point p2 = edge.getDest().getPosition(); -// g.drawLine( -// ( int ) ( p1.getX() * scale ) + centerX + ( 170 * count ), -// ( int ) ( - p1.getY() * scale ) + centerY, -// ( int ) ( p2.getX() * scale ) + centerX + ( 170 * count ), -// ( int ) ( - p2.getY() * scale ) + centerY -// ); -// -// g.drawLine( -// ( int ) ( p1.getX() * scale ) + centerX + 170, -// ( int ) ( - p1.getY() * scale ) + centerY, -// ( int ) ( p2.getX() * scale ) + centerX + 170, -// ( int ) ( - p2.getY() * scale ) + centerY -// ); -// } -// count++; + g.setColor( Color.BLACK ); + for ( HalfEdge edge : edges ) { + Point p1 = edge.getOrigin().getPosition(); + Point p2 = edge.getDest().getPosition(); + g.drawLine( + ( int ) ( ( centerX + p1.getX() + count * 25 ) * scale ) + offsetX, + ( int ) ( ( centerY - p1.getY() - 150 ) * scale ) + offsetY, + ( int ) ( ( centerX + p2.getX() + count * 25 ) * scale ) + offsetX, + ( int ) ( ( centerY - p2.getY() - 150 ) * scale ) + offsetY + ); + + g.drawLine( + ( int ) ( ( centerX + p1.getX() ) * scale ) + offsetX, + ( int ) ( ( centerY - p1.getY() - 150 ) * scale ) + offsetY, + ( int ) ( ( centerX + p2.getX() ) * scale ) + offsetX, + ( int ) ( ( centerY - p2.getY() - 150 ) * scale ) + offsetY + ); + } + count++; int size = edges.size(); int[] xPoints = new int[ size ]; @@ -464,53 +544,21 @@ public class MeshingTest2 extends JPanel { HalfEdge edge = edges.iterator().next(); for ( int i = 0; i < edges.size(); ++i ) { final Point point = edge.getOrigin().getPosition(); - xPoints[ i ] = ( int ) ( point.getX() * scale ) + centerX + 400; - yPoints[ i ] = ( int ) ( - point.getY() * scale ) + centerY; + xPoints[ i ] = ( int ) ( ( centerX + point.getX() ) * scale ) + offsetX; + yPoints[ i ] = ( int ) ( ( centerY - point.getY() ) * scale ) + offsetY; edge = edge.getNext(); } - g.setColor( new Color( ( int ) ( Math.random() * 0xFFFFFF ) ) ); + g.setColor( new Color( random.nextInt( 0x1000000 ) ) ); g.fillPolygon( xPoints, yPoints, size ); } - -// g.setColor( Color.BLACK ); -// for ( final EdgePolygon p : polygons ) { -// for ( final Vertex v : p.getVertices() ) { -// final Point point = v.getPosition(); -// -// double diff = scale * .3; -// g.drawRect( 400 + ( int ) ( point.getX() * scale ) + centerX - ( int ) diff, ( int ) ( point.getY() * scale ) + centerY - ( int ) diff, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) ); -// } -// } } -// Point[] points = { -// new Point( 0, -2 ), -// new Point( 1.5, -3.0 ), -// new Point( 0.0, -2.0 ), -// new Point( 0.9545454545454546, -1.3636363636363638 ), -// new Point( -0.8333333333333334, -1.0 ), -// new Point( 0.8333333333333334, -1.0 ), -// new Point( -0.5, 0.0 ), -// new Point( 0.5, 0.0 ), -// new Point( -0.16666666666666669, 1.0 ), -// new Point( 0.16666666666666669, 1.0 ), -// new Point( -0.16666666666666669, 1.0 ), -// new Point( 1, 1 ), -// new Point( -0.16666666666666669, 1.0 ), -// new Point( 0, 1.5 ), -// new Point( 0, 1.5 ), -// new Point( 0.16666666666666669, 1.0 ), -// new Point( -1, 2 ), -// new Point( 1, 2 ), -// }; -// -// int size = points.length >> 1; -// for ( int i = 0; i < size;i++ ) { -// Point a = points[ i << 1 ]; -// Point b = points[ ( i << 1 ) + 1 ]; -// -// g.drawLine( ( int ) ( a.x * scale ) + centerX, ( int ) ( a.y * scale ) + centerY, ( int ) ( b.x * scale ) + centerX, ( int ) ( b.y * scale ) + centerY ); -// } + java.awt.Point p = MouseInfo.getPointerInfo().getLocation(); + p = new java.awt.Point( p.x - getLocation().x, p.y - getLocation().y ); + g.setColor( Color.RED ); + g.setFont( new Font( Font.MONOSPACED, Font.BOLD, 30 ) ); + g.drawString( "X: " + ( ( p.x - offsetX ) / scale - centerX ), 10, 30 ); + g.drawString( "Y: " + ( ( p.y - offsetY ) / scale - centerY ), 10, 60 ); } } \ No newline at end of file