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
This commit is contained in:
2025-05-06 00:08:52 -04:00
parent 510fcc7b7d
commit 0f430e7a31
2 changed files with 456 additions and 213 deletions

View File

@@ -16,23 +16,63 @@ import java.util.Optional;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.Stack;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
import com.aaaaahhhhhhh.bananapuncher714.mesh.region.Region; import com.aaaaahhhhhhh.bananapuncher714.mesh.region.Region;
import com.aaaaahhhhhhh.bananapuncher714.mesh.region.RegionRule; 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 * @author BananaPuncher714
*/ */
public class Mesh< T extends Region > { public class Mesh< T extends Region > {
public static final double VERTEX_TOLERANCE = 1e-7; 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 // All points in the graph
protected Collection< Vertex > vertices = new ArrayDeque< Vertex >(); protected Collection< Vertex > vertices = new ArrayDeque< Vertex >();
@@ -42,88 +82,24 @@ public class Mesh< T extends Region > {
protected final Supplier< T > defaultRegionSupplier; protected final Supplier< T > defaultRegionSupplier;
protected MeshState state = MeshState.SIMPLIFIED; protected MeshState state = MeshState.TRIANGULATION_READY;
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() );
}
public Mesh( final Supplier< T > defaultRegionSupplier ) { public Mesh( final Supplier< T > defaultRegionSupplier ) {
this.defaultRegionSupplier = defaultRegionSupplier; this.defaultRegionSupplier = defaultRegionSupplier;
} }
public void addPolygon( final Polygon poly, final RegionRule< T > rule ) { 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; HalfEdge edge = null;
for ( Point point : poly.getPoints() ) { for ( Point point : poly.getPoints() ) {
if ( edge == null ) { if ( edge == null ) {
edge = new HalfEdge(); edge = new HalfEdge();
HalfEdge.splice( edge, edge.getSym() ); HalfEdge.splice( edge, edge.getSym() );
edge.getOrigin().update(); edge.getSym().setOrigin( edge.getOrigin() );
} else { } else {
edge = edge.split(); edge = edge.split();
} }
@@ -148,8 +124,77 @@ public class Mesh< T extends Region > {
} }
public void clear() { public void clear() {
state = MeshState.TRIANGULATION_READY;
vertices.clear(); vertices.clear();
rules.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 * In addition, remove vertices without any edges, and solve any
* intersections by splitting edges and/or adding new vertices as required. * 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() { public void simplify() {
// Create a queue and insert all vertices in O(nlogn) time. // Create a queue and insert all vertices in O(nlogn) time.
@@ -573,7 +621,10 @@ public class Mesh< T extends Region > {
* intersections or overlaps. * intersections or overlaps.
*/ */
public void generateRegions() { 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!" ); throw new IllegalStateException( "Mesh is not simplified!" );
} }
@@ -691,7 +742,10 @@ public class Mesh< T extends Region > {
} }
// Merge any colinear edges before returning // Merge any colinear edges before returning
mergeColinearEdges( keep, interiorAboveEdges ); mergeColinearEdges( keep, interiorAboveEdges ).forEach( e -> {
rules.remove( e );
rules.remove( e.getSym() );
} );
interiorEdges.clear(); interiorEdges.clear();
interiorEdges.addAll( interiorAboveEdges ); 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 // Get this edge and the next edge
final HalfEdge e1 = edges.get( i ); final HalfEdge e1 = edges.get( i );
final HalfEdge e2 = edges.get( ( i + 1 ) % edges.size() ); final HalfEdge e2 = edges.get( ( i + 1 ) % edges.size() );
@@ -749,7 +803,7 @@ public class Mesh< T extends Region > {
return edges; 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 Collection< HalfEdge > toRemove = new ArrayDeque< HalfEdge >();
final Set< HalfEdge > edges = new HashSet< HalfEdge >(); final Set< HalfEdge > edges = new HashSet< HalfEdge >();
@@ -814,9 +868,6 @@ public class Mesh< T extends Region > {
edges.add( next ); edges.add( next );
rules.remove( next );
rules.remove( next.getSym() );
toRemove.add( next ); toRemove.add( next );
} else { } else {
edges.add( edge ); edges.add( edge );
@@ -826,6 +877,8 @@ public class Mesh< T extends Region > {
} }
internalEdges.removeAll( toRemove ); internalEdges.removeAll( toRemove );
return toRemove;
} }
public Collection< EdgePolygon > mesh() { public Collection< EdgePolygon > mesh() {
@@ -844,6 +897,10 @@ public class Mesh< T extends Region > {
// Get a collection of vertices that need to be sorted, again // Get a collection of vertices that need to be sorted, again
final Collection< Vertex > toSort = new HashSet< Vertex >(); 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 ) { for ( final HalfEdge edge : interiorEdges ) {
if ( scanned.add( edge ) ) { if ( scanned.add( edge ) ) {
HalfEdge temp = edge; HalfEdge temp = edge;
@@ -853,6 +910,7 @@ public class Mesh< T extends Region > {
if ( newEdge == null ) { if ( newEdge == null ) {
newEdge = new HalfEdge(); newEdge = new HalfEdge();
HalfEdge.splice( newEdge, newEdge.getSym() ); HalfEdge.splice( newEdge, newEdge.getSym() );
newEdge.getSym().setOrigin( newEdge.getOrigin() );
} else { } else {
newEdge = newEdge.split(); newEdge = newEdge.split();
} }
@@ -864,6 +922,9 @@ public class Mesh< T extends Region > {
if ( newVertices.containsKey( tempVert ) ) { if ( newVertices.containsKey( tempVert ) ) {
vert = newVertices.get( tempVert ); vert = newVertices.get( tempVert );
newEdge.setOrigin( vert );
newEdge.getPrev().setOrigin( vert );
HalfEdge.splice( newEdge.getPrev(), vert.getEdge() ); HalfEdge.splice( newEdge.getPrev(), vert.getEdge() );
toSort.add( vert ); toSort.add( vert );
@@ -873,24 +934,27 @@ public class Mesh< T extends Region > {
vert.setPosition( tempVert.getPosition() ); vert.setPosition( tempVert.getPosition() );
} }
vert.update();
edges.add( newEdge ); edges.add( newEdge );
} while ( scanned.add( temp = temp.getNext() ) ); } while ( scanned.add( temp = temp.getNext() ) );
} }
} }
for ( final Vertex v : toSort ) { toSort.parallelStream().forEach( v -> sort( v ) );
sort( v );
}
// Have a single set of edges // Have a single set of edges
final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare ); final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare );
vertices.addAll( newVertices.values() ); vertices.addAll( newVertices.values() );
final Collection< EdgePolygon > polygons = partitionMonotone( vertices, edges ); return partitionMonotone( vertices, edges ).parallelStream()
.map( p -> {
mergeColinearEdges( p.getVertices(), p.getEdges() ).forEach( e -> p.getVertices().remove( e.getOrigin() ) );
return polygons; 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 ) { 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.setOrigin( prev );
edge.getSym().setOrigin( vertex ); edge.getSym().setOrigin( vertex );
HalfEdge.splice( lower, edge ); HalfEdge.splice( edge, lower );
HalfEdge.splice( edge.getSym(), vertex.getEdge() ); HalfEdge.splice( edge.getSym(), vertex.getEdge() );
supportEdges.add( edge ); supportEdges.add( edge );
@@ -1121,7 +1185,7 @@ public class Mesh< T extends Region > {
edge.setOrigin( prev ); edge.setOrigin( prev );
edge.getSym().setOrigin( vertex ); edge.getSym().setOrigin( vertex );
HalfEdge.splice( upper.getSym().getNext(), edge ); HalfEdge.splice( edge, upper.getSym().getNext() );
HalfEdge.splice( edge.getSym(), vertex.getEdge() ); HalfEdge.splice( edge.getSym(), vertex.getEdge() );
supportEdges.add( edge ); 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 // Now that we've split the polygon into monotone regions, we must
// also return a collection of the newly created polygon, since each // also return a collection of the newly created polygon, since each
// monotone region is its own separate polygon // 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 >(); final Collection< EdgePolygon > newPolygons = new ArrayDeque< EdgePolygon >();
// Keep track of all edges that we've seen // Keep track of all edges that we've seen
final Set< HalfEdge > scanned = new HashSet< HalfEdge >(); final Set< HalfEdge > scanned = new HashSet< HalfEdge >();
for ( final HalfEdge edge : interior ) { for ( final HalfEdge edge : interior ) {
if ( scanned.add( edge ) ) { if ( scanned.add( edge ) ) {
// We have not looked at this edge yet, so loop over it // We have not looked at this edge yet, so loop over it
@@ -1188,6 +1253,8 @@ public class Mesh< T extends Region > {
// As we iterate over each edge, if the origin has more than // As we iterate over each edge, if the origin has more than
// 2 edges, then we need to split the vertex. // 2 edges, then we need to split the vertex.
if ( temp.getPrev().getPrev() != temp ) { if ( temp.getPrev().getPrev() != temp ) {
temp.getOrigin().setEdge( temp.getPrev().getPrev() );
HalfEdge.splice( temp.getPrev(), temp.getSym().getNext() ); HalfEdge.splice( temp.getPrev(), temp.getSym().getNext() );
// Set their origins to a new vertex // Set their origins to a new vertex
@@ -1205,8 +1272,136 @@ public class Mesh< T extends Region > {
return newPolygons; return newPolygons;
} }
private void triangulate() { private static Collection< EdgePolygon > triangulate( final EdgePolygon polygon ) {
// TODO // 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 { public static class EdgePolygon {
private Set< HalfEdge > edges = new HashSet< HalfEdge >(); 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 ) { private void addEdge( HalfEdge edge ) {
edges.add( edge ); edges.add( edge );
@@ -1339,7 +1534,7 @@ public class Mesh< T extends Region > {
return edges; return edges;
} }
public TreeSet< Vertex > getVertices() { public Set< Vertex > getVertices() {
return vertices; return vertices;
} }
} }

View File

@@ -1,7 +1,16 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest; package com.aaaaahhhhhhh.bananapuncher714.minietest;
import java.awt.Color; import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics; 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.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -11,13 +20,13 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Random;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.Timer;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
@@ -52,12 +61,13 @@ public class MeshingTest2 extends JPanel {
// private int centerX = 600; // private int centerX = 600;
// private int centerY = 400; // private int centerY = 400;
private int centerX = 400; private int offsetX = windowWidth >> 1;
private int centerY = 100; private int offsetY = windowHeight >> 1;
private Graphics g; private double centerX = 0;
private double centerY = 0;
private int scale = 8; private double scale = 8;
private Collection< Vertex > data; private Collection< Vertex > data;
private Collection< EdgePolygon > polygons; 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() ) ); // 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< Vertex > data = process( draw );
final Collection< EdgePolygon > polys = triangulate( draw ); final Collection< EdgePolygon > polys = triangulate( draw );
// final Collection< EdgePolygon > polys = test(); // final Collection< EdgePolygon > polys = test();
System.out.println( "Polygon count: " + polys.size() );
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -134,7 +154,6 @@ public class MeshingTest2 extends JPanel {
// Now that we have a bunch of bounding boxes, do whatever // Now that we have a bunch of bounding boxes, do whatever
MeshBuilder builder = new MeshBuilder(); MeshBuilder builder = new MeshBuilder();
Plane draw = null;
for ( AABB box : boxes ) { for ( AABB box : boxes ) {
for ( Facet facet : generateFacetsFor( box ) ) { for ( Facet facet : generateFacetsFor( box ) ) {
builder.addFacet( facet ); builder.addFacet( facet );
@@ -144,26 +163,40 @@ public class MeshingTest2 extends JPanel {
return builder.planes; return builder.planes;
} }
private static void test( final Collection< Plane > planes ) { public static class PolygonException extends RuntimeException {
final long processAllStart = System.currentTimeMillis(); Plane plane;
planes.parallelStream().forEach( plane -> {
try {
Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
for ( Polygon poly : plane.polygons ) { PolygonException( Plane plane ) {
mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE ); this.plane = plane;
} }
long start = System.currentTimeMillis();
mesh.simplify(); Plane getPlane() {
mesh.generateRegions(); return plane;
long end = System.currentTimeMillis(); }
System.out.println( plane.polygons.size() + ":\t " + ( end - start ) + "ms" ); }
} catch ( IllegalStateException e ) {
e.printStackTrace(); 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();
final long processAllEnd = System.currentTimeMillis(); mesh.simplify();
System.out.println( "Took " + ( processAllEnd - processAllStart ) + "ms to process " + planes.size() + " planes" ); 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 ) { 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 vertex count: " + mesh.getVertices().size() );
System.out.println( "After generating regions edge count: " + ( mesh.getRuleSize() / 2 ) ); System.out.println( "After generating regions edge count: " + ( mesh.getRuleSize() / 2 ) );
// Make sure this works
mesh = mesh.copyOf();
final Collection< EdgePolygon > polys = mesh.mesh(); final Collection< EdgePolygon > polys = mesh.mesh();
long end = System.currentTimeMillis(); long end = System.currentTimeMillis();
System.out.println( "Took " + ( end - start ) + "ms" ); System.out.println( "Took " + ( end - start ) + "ms" );
@@ -360,6 +396,69 @@ public class MeshingTest2 extends JPanel {
f = new JFrame( "Drawing Board" ); f = new JFrame( "Drawing Board" );
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 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.add( this );
f.setSize( windowWidth, windowHeight ); f.setSize( windowWidth, windowHeight );
@@ -369,10 +468,6 @@ public class MeshingTest2 extends JPanel {
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 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 ) { private static Vector2d rotate( Vector2d a, double angle ) {
double cos = Math.cos( angle ); double cos = Math.cos( angle );
double sin = Math.sin( angle ); double sin = Math.sin( angle );
@@ -384,30 +479,15 @@ public class MeshingTest2 extends JPanel {
@Override @Override
public void paintComponent( Graphics g ) { public void paintComponent( Graphics g ) {
super.paintComponent( g ); super.paintComponent( g );
this.g = g;
g.setColor( new Color( 200, 200, 200 ) ); g.setColor( new Color( 200, 200, 200 ) );
g.drawLine( centerX, 0, centerX, 2000 ); g.drawLine( ( int ) ( scale * centerX ) + offsetX, 0, ( int ) ( scale * centerX ) + offsetX, getHeight() );
g.drawLine( 0, centerY, 2000, centerY ); g.drawLine( 0, ( int ) ( scale * centerY ) + offsetY, getWidth(), ( int ) ( scale * centerY ) + offsetY );
g.setColor( Color.BLACK ); g.setColor( Color.BLACK );
if ( data != null ) { if ( data != null ) {
Collection< Vertex > polygons = data; 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(); EdgeSet edges = new EdgeSet();
for ( Vertex vert : polygons ) { for ( Vertex vert : polygons ) {
for ( HalfEdge edge : vert ) { for ( HalfEdge edge : vert ) {
@@ -415,10 +495,10 @@ public class MeshingTest2 extends JPanel {
Point p1 = edge.getOrigin().getPosition(); Point p1 = edge.getOrigin().getPosition();
Point p2 = edge.getDest().getPosition(); Point p2 = edge.getDest().getPosition();
g.drawLine( g.drawLine(
( int ) ( p1.getX() * scale ) + centerX, ( int ) ( ( centerX + p1.getX() + 40 ) * scale ) + offsetX,
( int ) ( - p1.getY() * scale ) + centerY, ( int ) ( ( centerY - p1.getY() ) * scale ) + offsetY,
( int ) ( p2.getX() * scale ) + centerX, ( int ) ( ( centerX + p2.getX() + 40 ) * scale ) + offsetX,
( int ) ( - p2.getY() * scale ) + centerY ( int ) ( ( centerY - p2.getY() ) * scale ) + offsetY
); );
} }
} }
@@ -426,36 +506,36 @@ public class MeshingTest2 extends JPanel {
final double diff = scale * 0.15; final double diff = scale * 0.15;
final Point point = vert.getPosition(); final Point point = vert.getPosition();
g.setColor( Color.BLACK ); 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 ) { if ( polygons != null ) {
// System.out.println( "Polygon count: " + polygons.size() );
g.setColor( Color.BLACK ); g.setColor( Color.BLACK );
int count = 2; int count = 2;
Random random = new Random( hashCode() );
for ( final EdgePolygon p : polygons ) { for ( final EdgePolygon p : polygons ) {
final Collection< HalfEdge > edges = p.getEdges(); final Collection< HalfEdge > edges = p.getEdges();
// g.setColor( Color.BLACK ); g.setColor( Color.BLACK );
// for ( HalfEdge edge : edges ) { for ( HalfEdge edge : edges ) {
// Point p1 = edge.getOrigin().getPosition(); Point p1 = edge.getOrigin().getPosition();
// Point p2 = edge.getDest().getPosition(); Point p2 = edge.getDest().getPosition();
// g.drawLine( g.drawLine(
// ( int ) ( p1.getX() * scale ) + centerX + ( 170 * count ), ( int ) ( ( centerX + p1.getX() + count * 25 ) * scale ) + offsetX,
// ( int ) ( - p1.getY() * scale ) + centerY, ( int ) ( ( centerY - p1.getY() - 150 ) * scale ) + offsetY,
// ( int ) ( p2.getX() * scale ) + centerX + ( 170 * count ), ( int ) ( ( centerX + p2.getX() + count * 25 ) * scale ) + offsetX,
// ( int ) ( - p2.getY() * scale ) + centerY ( int ) ( ( centerY - p2.getY() - 150 ) * scale ) + offsetY
// ); );
//
// g.drawLine( g.drawLine(
// ( int ) ( p1.getX() * scale ) + centerX + 170, ( int ) ( ( centerX + p1.getX() ) * scale ) + offsetX,
// ( int ) ( - p1.getY() * scale ) + centerY, ( int ) ( ( centerY - p1.getY() - 150 ) * scale ) + offsetY,
// ( int ) ( p2.getX() * scale ) + centerX + 170, ( int ) ( ( centerX + p2.getX() ) * scale ) + offsetX,
// ( int ) ( - p2.getY() * scale ) + centerY ( int ) ( ( centerY - p2.getY() - 150 ) * scale ) + offsetY
// ); );
// } }
// count++; count++;
int size = edges.size(); int size = edges.size();
int[] xPoints = new int[ size ]; int[] xPoints = new int[ size ];
@@ -464,53 +544,21 @@ public class MeshingTest2 extends JPanel {
HalfEdge edge = edges.iterator().next(); HalfEdge edge = edges.iterator().next();
for ( int i = 0; i < edges.size(); ++i ) { for ( int i = 0; i < edges.size(); ++i ) {
final Point point = edge.getOrigin().getPosition(); final Point point = edge.getOrigin().getPosition();
xPoints[ i ] = ( int ) ( point.getX() * scale ) + centerX + 400; xPoints[ i ] = ( int ) ( ( centerX + point.getX() ) * scale ) + offsetX;
yPoints[ i ] = ( int ) ( - point.getY() * scale ) + centerY; yPoints[ i ] = ( int ) ( ( centerY - point.getY() ) * scale ) + offsetY;
edge = edge.getNext(); edge = edge.getNext();
} }
g.setColor( new Color( ( int ) ( Math.random() * 0xFFFFFF ) ) ); g.setColor( new Color( random.nextInt( 0x1000000 ) ) );
g.fillPolygon( xPoints, yPoints, size ); 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 = { java.awt.Point p = MouseInfo.getPointerInfo().getLocation();
// new Point( 0, -2 ), p = new java.awt.Point( p.x - getLocation().x, p.y - getLocation().y );
// new Point( 1.5, -3.0 ), g.setColor( Color.RED );
// new Point( 0.0, -2.0 ), g.setFont( new Font( Font.MONOSPACED, Font.BOLD, 30 ) );
// new Point( 0.9545454545454546, -1.3636363636363638 ), g.drawString( "X: " + ( ( p.x - offsetX ) / scale - centerX ), 10, 30 );
// new Point( -0.8333333333333334, -1.0 ), g.drawString( "Y: " + ( ( p.y - offsetY ) / scale - centerY ), 10, 60 );
// 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 );
// }
} }
} }