diff --git a/pom.xml b/pom.xml
index b217204..a84d398 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
org.spigotmc
spigot
- 1.19.4-R0.1-SNAPSHOT
+ 1.21.5-R0.1-SNAPSHOT
provided
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/EdgeSet.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/EdgeSet.java
new file mode 100644
index 0000000..18ac793
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/EdgeSet.java
@@ -0,0 +1,51 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh;
+
+import java.util.HashSet;
+
+/**
+ * A set for easily checking if a set contains/does not contain a half-edge or its sym.
+ *
+ * @author BananaPuncher714
+ */
+public class EdgeSet extends HashSet< HalfEdge > {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 4042266887750818558L;
+
+ @Override
+ public boolean add( HalfEdge edge ) {
+ if ( !contains( edge ) ) {
+ return super.add( edge );
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean remove( Object object ) {
+ if ( object instanceof HalfEdge ) {
+ HalfEdge edge = ( HalfEdge ) object;
+ final boolean r1 = super.remove( edge );
+ final boolean r2 = super.remove( edge.getSym() );
+
+ if ( r1 && r2 ) {
+ throw new IllegalStateException( "Removed edge was added twice!" );
+ }
+
+ return r1 || r2;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean contains( Object object ) {
+ if ( object instanceof HalfEdge ) {
+ HalfEdge edge = ( HalfEdge ) object;
+ return super.contains( edge ) || super.contains( edge.getSym() );
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/HalfEdge.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/HalfEdge.java
new file mode 100644
index 0000000..3a81591
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/HalfEdge.java
@@ -0,0 +1,201 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh;
+
+/**
+ * A half-edge class used to represent a DCEL(doubly connected edge list): https://en.wikipedia.org/wiki/Doubly_connected_edge_list
+ *
+ * In this case, it is intended to be used for a 2d PSLG(planar straight-line graph), but can probably be repurposed
+ * for some other nefarious use case.
+ *
+ * An edge is a line segment connecting an origin to a destination. A half-edge is a partial line segment containing only
+ * the origin, but not the destination.
+ *
+ * Each half-edge structure contains:
+ * - A reference to the origin
+ * - A reference to the symmetrical, or opposite half-edge which is attached to the destination.
+ * - A reference to the next clockwise half-edge of the opposite half edge. This can be used to
+ * traverse counter-clockwise through the half-edges of a polygon.
+ * - A reference to the previous edge, which is the next counter-clockwise edge of this half edge.
+ * This can be used to traverse all the edges of a vertex, in counter clockwise order.
+ *
+ * For a given half-edge attached to two points that contain no other edges, the previous edge is
+ * itself, and the next edge is the same as the opposite edge.
+ *
+ * There are 2 main operations that are used when manipulating half-edges to attach/detach them from
+ * a vertex:
+ * - splicing
+ * - splitting
+ *
+ * Splicing is the most important of the 2 operations, by a large margin. Given two half-edges, A and B,
+ * swap the previous edges, and set the next edge for both of the previous edges to the opposite half edge.
+ * This has the desired effect of either detaching two edges if they are connected to the same vertex, or
+ * attaching two separate half-edges together to the same vertex.
+ *
+ * Splitting is adding an edge between the opposite of an edge and attaching it to the destination. It
+ * splits a single edge into two edges with a vertex in the middle.
+ *
+ * @author BananaPuncher714
+ */
+public class HalfEdge {
+ /**
+ * The origin
+ */
+ protected Vertex origin;
+
+ /**
+ * The symmetrical half-edge
+ */
+ protected HalfEdge sym;
+ /**
+ * The half-edge counter-clockwise on the same origin
+ */
+ protected HalfEdge prev;
+ /**
+ * The half-edge clockwise from the symmetrical half-edge
+ */
+ protected HalfEdge next;
+
+ // TODO Consider adding a constructor with a origin and destination
+
+ public HalfEdge() {
+ init();
+
+ // Whenever we create a new half-edge, we need to make
+ // sure that it has a symmetrical half-edge.
+ new HalfEdge( this );
+ }
+
+ private HalfEdge( HalfEdge o ) {
+ init();
+
+ sym = o;
+ o.sym = this;
+
+ next = o;
+ o.next = this;
+ }
+
+ private void init() {
+ origin = new Vertex( this );
+ prev = this;
+ }
+
+ public Vertex getOrigin() {
+ return origin;
+ }
+
+ /**
+ * Set the origin. Does not affect any additional
+ *
+ * @param vert
+ * @return
+ */
+ public HalfEdge setOrigin( Vertex vert ) {
+ origin = vert;
+ return this;
+ }
+
+ public HalfEdge getSym() {
+ return sym;
+ }
+
+ public HalfEdge getPrev() {
+ return prev;
+ }
+
+ /**
+ * Set the previous half-edge. Does not update additional properties.
+ *
+ * @param edge The half-edge to set as the previous half-edge.
+ * @return This half-edge
+ */
+ public HalfEdge setPrev( HalfEdge edge ) {
+ this.prev = edge;
+ return this;
+ }
+
+ public HalfEdge getNext() {
+ return next;
+ }
+
+ /**
+ * Set the next half-edge. Does not update additional properties.
+ *
+ * @param edge The half-edge to set as the next half-edge.
+ * @return This half-edge
+ */
+ public HalfEdge setNext( HalfEdge edge ) {
+ this.next = edge;
+ return this;
+ }
+
+ public Vertex getDest() {
+ return sym.origin;
+ }
+
+ /**
+ * Get the difference between the destination position and the origin position
+ *
+ * @return A vector representing the destination minus the origin
+ */
+ public Vector2d toVector2d() {
+ return getDest().getPosition().subtracted( getOrigin().getPosition() );
+ }
+
+ /**
+ * Check if the origin and destination are the same
+ *
+ * @return If the origin is equal to the destination
+ */
+ public boolean isZero() {
+ return getOrigin().equals( getDest() );
+ }
+
+ /**
+ * Split this half-edge in half and return the newly created
+ * half-edge, as the next half-edge of this half-edge.
+ *
+ * @return A new half-edge with a default initialized origin.
+ */
+ public HalfEdge split() {
+ final HalfEdge edge = new HalfEdge();
+
+ splice( edge, getNext() );
+
+ splice( getSym(), getNext() );
+ splice( getSym(), edge.getSym() );
+
+ edge.setOrigin( getDest() );
+ edge.getOrigin().setEdge( edge );
+
+ getSym().setOrigin( edge.getDest() );
+ getDest().setEdge( getSym() );
+
+ edge.getOrigin().setEdge( edge );
+
+ return edge.getSym();
+ }
+
+ /**
+ * Splice two half-edges together. A commutative operation.
+ *
+ * This does _not_ modify any vertices.
+ *
+ * @param a The first half-edge
+ * @param b The second half-edge
+ */
+ public static void splice( HalfEdge a, HalfEdge b ) {
+ final HalfEdge ap = a.prev;
+ final HalfEdge bp = b.prev;
+
+ a.prev = bp;
+ b.prev = ap;
+
+ ap.sym.next = b;
+ bp.sym.next = a;
+ }
+
+ @Override
+ public String toString() {
+ return String.format( "HalfEdge{orig=%1$s,dest=%2$s}", getOrigin(), getDest() );
+ }
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java
new file mode 100644
index 0000000..8ebc952
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java
@@ -0,0 +1,1324 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.PriorityQueue;
+import java.util.Queue;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Supplier;
+
+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
+ *
+ * @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 );
+
+ // All points in the graph
+ protected Collection< Vertex > vertices = new ArrayDeque< Vertex >();
+ // All rules associated with a given half edge
+ protected Map< HalfEdge, RegionRule< T > > rules = new HashMap< HalfEdge, RegionRule< T > >();
+ protected Set< HalfEdge > interiorEdges = new HashSet< HalfEdge >();
+
+ 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() );
+ }
+
+ public Mesh( final Supplier< T > defaultRegionSupplier ) {
+ this.defaultRegionSupplier = defaultRegionSupplier;
+ }
+
+ public void addPolygon( final Polygon poly, final RegionRule< T > rule ) {
+ HalfEdge edge = null;
+ for ( Point point : poly.getPoints() ) {
+ if ( edge == null ) {
+ edge = new HalfEdge();
+ HalfEdge.splice( edge, edge.getSym() );
+ edge.getOrigin().update();
+ } else {
+ edge = edge.split();
+ }
+
+ edge.getOrigin().setPosition( new Vector2d( point ) );
+
+ rules.put( edge, rule );
+ rules.put( edge.getSym(), rule.inverse() );
+
+ vertices.add( edge.getOrigin() );
+
+ state = MeshState.DIRTY;
+ }
+ }
+
+ public Collection< Vertex > getVertices() {
+ return vertices;
+ }
+
+ public int getRuleSize() {
+ return rules.size();
+ }
+
+ public void clear() {
+ vertices.clear();
+ rules.clear();
+ }
+
+ /**
+ * Do a check over all vertices to see if there are any within
+ * VERTEX_TOLERANCE of each other. If so, then merge them.
+ *
+ * The exact order of the vertices and their edges is not particularly important.
+ *
+ * In addition, remove vertices without any edges, and solve any
+ * intersections by splitting edges and/or adding new vertices as required.
+ */
+ public void simplify() {
+ // Create a queue and insert all vertices in O(nlogn) time.
+ final Queue< Vertex > queue = new PriorityQueue< Vertex >( Mesh::compare );
+ queue.addAll( vertices );
+
+ /*
+ * As we iterate through each vertex, there are certain operations
+ * that are concerned with any edges that we may be potentially sweeping though.
+ *
+ * This means we must keep track of all edges at and a little ahead of the current
+ * scan line. We don't want to iterate through each edge every time since that would
+ * make this method take polynomial time, so instead add edges when we advance the
+ * scanline and remove any edges from vertices that we have already passed.
+ */
+ final EdgeSet scannedEdges = new EdgeSet();
+ // This set contains the non-redundant vertices.
+ final Set< Vertex > scannedVertices = new HashSet< Vertex >();
+
+ Vertex vertex;
+ while ( ( vertex = queue.poll() ) != null ) {
+ final Vector2d pos = vertex.getPosition();
+ {
+ final List< Vertex > addBack = new LinkedList< Vertex >();
+ Vertex other;
+ // We must poll and add back later since priority queues are not
+ // sorted, so an iterator over its elements would not guarantee
+ // the correct ordering unless we poll it.
+ while ( ( other = queue.poll() ) != null ) {
+ final Vector2d otherPos = other.getPosition();
+
+ // Once the vertex scanned is past the tolerance in the X direction,
+ // we know there are no more vertices which can be merged with the
+ // current vertex.
+ if ( compareX( otherPos, pos ) > VERTEX_TOLERANCE ) {
+ addBack.add( other );
+ break;
+ }
+
+ // Check if the vertices are close enough that they can
+ // be considered "the same"
+ if ( isSimilar( pos, otherPos ) ) {
+ HalfEdge.splice( vertex.getEdge(), other.getEdge() );
+ } else {
+ addBack.add( other );
+ }
+ }
+ queue.addAll( addBack );
+ }
+
+ {
+ // Remove any zero-edges
+ HalfEdge validEdge = null;
+ for ( HalfEdge edge : vertex ) {
+ if ( edge.isZero() ) {
+ // At this point in time, if the edge is zero,
+ // then the origin and destination must be the same
+ if ( edge.getOrigin() != edge.getDest() ) {
+ throw new IllegalStateException( "Zero edge but the origin and destination are not the same!" );
+ }
+
+ // Un-splice the edge and its sym from the vertex
+ HalfEdge.splice( edge, edge.getSym().getNext() );
+ HalfEdge.splice( edge.getSym(), edge.getNext() );
+ } else {
+ validEdge = edge;
+ }
+ }
+
+ // This vertex does not contain any edges!!
+ if ( validEdge == null ) {
+ continue;
+ } else {
+ vertex.setEdge( validEdge );
+ }
+ }
+
+ // Add all edges from this vertex if they do not already exist in
+ // the set of scanned edges
+ for ( final HalfEdge edge : vertex ) {
+ scannedEdges.add( edge );
+ }
+
+ // Add edges that belong to vertices up to VERTEX_TOLERANCE away in the X direction
+ for ( final Iterator< Vertex > it = queue.iterator(); it.hasNext(); ) {
+ final Vertex other = it.next();
+ final Vector2d otherPos = other.getPosition();
+
+ if ( compareX( otherPos, pos ) > VERTEX_TOLERANCE ) {
+ break;
+ }
+
+ for ( final HalfEdge edge : other ) {
+ scannedEdges.add( edge );
+ }
+ }
+
+ // Update all edges this vertex is connected to for consistency
+ vertex.update();
+
+ // Merge any vertices with edges that they may be on
+ resolveVertexIntersections( scannedEdges, vertex );
+
+ for ( final HalfEdge edge : vertex ) {
+ // Resolve any intersections belonging to this edge which are still
+ // being scanned
+ if ( scannedEdges.contains( edge ) ) {
+ resolveEdgeIntersections( queue, scannedEdges, edge );
+ }
+ }
+
+ // Remove all edges from previously scanned vertices that are no longer useful
+ for ( final HalfEdge edge : vertex ) {
+ if ( scannedVertices.contains( edge.getDest() ) ) {
+ /*
+ * Remove the half edge and it's sym if the destination
+ * has already been removed, since it means that it is
+ * no longer relevant.
+ *
+ * However, only one half-edge should have been present.
+ * If both or none are there, then that is an issue.
+ */
+ if ( !scannedEdges.remove( edge ) ) {
+ throw new IllegalStateException( "Tried to remove an invalid edge!" );
+ }
+ }
+ }
+ scannedVertices.add( vertex );
+ }
+
+ mergeOverlappingEdges( scannedVertices );
+
+ // Update the list of vertices with our new updated list of vertices
+ vertices = scannedVertices;
+
+ state = MeshState.SIMPLIFIED;
+ }
+
+ /**
+ * Check for vertices which are directly on an edge.
+ *
+ * @param scannedEdges
+ * @param vertex
+ */
+ private void resolveVertexIntersections( final EdgeSet scannedEdges, final Vertex vertex ) {
+ // Split any edges passing through vertex, which do not include
+ // the vertex as the origin or destination
+ final Vector2d v = vertex.getPosition();
+ final Collection< HalfEdge > toInsert = new ArrayDeque< HalfEdge >();
+ for ( final HalfEdge edge : scannedEdges ) {
+ final Vector2d a = edge.getOrigin().getPosition();
+ final Vector2d b = edge.getDest().getPosition();
+
+ if ( v == a || v == b ) {
+ // Skip if this edge
+ continue;
+ } else if ( isSimilar( v, b ) || isSimilar( v, a ) ) {
+ // The vertices should have been merged already
+ throw new IllegalStateException( "Vertex not merged" );
+ } else if ( edge.isZero() ) {
+ throw new IllegalStateException( "Edge is zero!" );
+ } else {
+ // Equations taken from https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
+ final double edgeLenSq = a.distanceSquared( b );
+ final Vector2d toB = b.subtracted( a );
+ final double t = v.subtracted( a ).dot( toB ) / edgeLenSq;
+ if ( t >= 0 && t <= 1 ) {
+ // Get perpendicular distance from v to the line formed by a + b
+ final Vector2d projection = toB.multiplied( t ).add( a );
+
+ // Check if the distance between the vertex
+ // and the edge is within the tolerance
+ if ( isSimilar( v, projection ) ) {
+ final HalfEdge split = edge.split();
+
+ // Set the vertex to this vertex
+ split.setOrigin( vertex );
+ edge.getSym().setOrigin( vertex );
+
+ // Splice the newly split wing into the vertex
+ HalfEdge.splice( vertex.getEdge(), split );
+
+ // Don't forget to update the winding rules for the newly created edge!!
+ final RegionRule< T > rule = rules.get( edge );
+ rules.put( split, rule );
+ rules.put( split.getSym(), rule.inverse() );
+
+ toInsert.add( split );
+ }
+ }
+ }
+ }
+ scannedEdges.addAll( toInsert );
+ }
+
+ /**
+ * Given a set of edges within the scan region, check for
+ * intersections with a half edge.
+ *
+ * @param scannedEdges
+ * @param edge
+ */
+ private void resolveEdgeIntersections( final Collection< Vertex > vertices, final EdgeSet scannedEdges, final HalfEdge edge ) {
+ // Use the positive edge for finding the intersection
+ final HalfEdge positiveEdge = isPositive( edge ) ? edge : edge.getSym();
+ for ( HalfEdge other : scannedEdges ) {
+ if ( !isPositive( other ) ) {
+ other = other.getSym();
+ }
+ final Optional< Intersection > optInt = getIntersection( positiveEdge, other );
+ if ( optInt.isPresent() ) {
+ final Intersection intersection = optInt.get();
+ if ( intersection instanceof IntersectionEdgeToEdge ) {
+ final IntersectionEdgeToEdge intEdge = ( IntersectionEdgeToEdge ) intersection;
+
+ if ( positiveEdge.getDest() == other.getDest() ) {
+ throw new IllegalStateException( "Invalid intersection" );
+ }
+
+ final Vector2d point = intEdge.getPoint();
+ if ( isSimilar( point, other.getDest().getPosition() ) || isSimilar( point, positiveEdge.getDest().getPosition() ) ) {
+ throw new IllegalStateException( "Invalid intersection point" );
+ }
+
+ // Split the first edge
+ HalfEdge aSplit = edge.split();
+ RegionRule< T > aRule = rules.get( edge );
+ rules.put( aSplit, aRule );
+ rules.put( aSplit.getSym(), aRule.inverse() );
+
+ // Update the position
+ final Vertex origin = aSplit.getOrigin();
+ origin.setPosition( point );
+
+ // Split the other edge
+ HalfEdge bSplit = other.split();
+ RegionRule< T > bRule = rules.get( other );
+ rules.put( bSplit, bRule );
+ rules.put( bSplit.getSym(), bRule.inverse() );
+
+ // Splice the edges together into the same vertex
+ HalfEdge.splice( bSplit, aSplit );
+
+ origin.update();
+
+ vertices.add( origin );
+ } else if ( intersection instanceof IntersectionColinear ) {
+ final IntersectionColinear intCol = ( IntersectionColinear ) intersection;
+
+ final HalfEdge shorter = intCol.getShorterEdge();
+ HalfEdge longer = intCol.getLongerEdge();
+
+ // Use the original edge for splitting to preserve the order of
+ // edges around the current vertex
+ if ( longer == positiveEdge ) {
+ longer = edge;
+ }
+
+ // Update the winding rule for the split
+ final HalfEdge split = longer.split();
+ RegionRule< T > rule = rules.get( longer );
+ rules.put( split, rule );
+ rules.put( split.getSym(), rule.inverse() );
+
+ // Update the vertex for the split
+ final Vertex vert = shorter.getDest();
+ split.setOrigin( vert );
+ longer.getSym().setOrigin( vert );
+
+ // Splice the two together
+ HalfEdge.splice( shorter.getSym(), split );
+ } else {
+ throw new IllegalStateException( "Unknown intersection type" );
+ }
+ }
+ }
+ }
+
+ /**
+ * Calculate the intersection for two edges
+ *
+ * @param a
+ * @param b
+ * @return
+ */
+ private static Optional< Intersection > getIntersection( HalfEdge a, HalfEdge b ) {
+ // Assume a and b are both positive
+ // This assumes that if a and b are colinear, then they _must_ share
+ // the same origin, since we are traversing through the vertices
+ // starting with the smallest.
+ if ( a.getOrigin() == b.getOrigin() ) {
+ // Do not split edges that are pretty much equal, since they will get merged later
+ if ( !isSimilar( a.getDest(), b.getDest() ) ) {
+ final Vector2d r = a.toVector2d().normalize();
+ final Vector2d s = b.toVector2d().normalize();
+ final double angle = r.cross( s );
+
+ // Ignore edges that are not colinear
+ if ( Math.abs( angle ) <= ANGLE_TOLERANCE ) {
+
+ // Compare which edge is shorter
+ if ( r.lengthSquared() < s.lengthSquared() ) {
+ return Optional.of( new IntersectionColinear( a, b ) );
+ } else {
+ return Optional.of( new IntersectionColinear( b, a ) );
+ }
+ }
+ }
+ } else if ( isSimilar( a.getOrigin(), b.getOrigin() ) ) {
+ throw new IllegalArgumentException( "Vertex not merged!" );
+ } else if ( !isSimilar( a.getDest(), b.getDest() ) ) {
+ // Both edges may intersect somewhere in the middle
+ final Vector2d p = a.getOrigin().getPosition();
+ final Vector2d r = a.toVector2d();
+ final Vector2d q = b.getOrigin().getPosition();
+ final Vector2d s = b.toVector2d();
+
+ final Vector2d pq = q.subtracted( p );
+ final double pqr = pq.cross( r );
+ final double rs = r.cross( s );
+
+ // Check if parallel
+ if ( rs != 0 ) {
+ // Calculate t and u
+ final double t = pq.cross( s ) / rs;
+ final double u = pqr / rs;
+
+ final double tTol = VERTEX_TOLERANCE / r.length();
+ final double uTol = VERTEX_TOLERANCE / s.length();
+
+ // Do the line segments intersect
+ // They should NOT intersect at their endpoints
+ if ( t > tTol && t < ( 1 - tTol ) && u > uTol && u < ( 1 - uTol ) ) {
+ // Calculate the point of intersection
+ final Vector2d intersection = r.multiply( t ).add( p );
+
+ return Optional.of( new IntersectionEdgeToEdge( intersection ) );
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * The goal for this method is to sort vertices
+ * and to ensure that edges with the same endpoint and
+ * destination are combined.
+ */
+ private void mergeOverlappingEdges( final Collection< Vertex > vertices ) {
+ for ( final Vertex vertex : vertices ) {
+ sort( vertex );
+
+ // Keep track of which ranges of edges are attached to which
+ final Map< Vertex, Queue< HalfEdge > > ranges = new HashMap< Vertex, Queue< HalfEdge > >();
+
+ // Get a list of edges that have the same endpoint
+ for ( final HalfEdge edge : vertex ) {
+ // Only check positive edges
+ // Technically we can do both, but this will result in us
+ // having to check ~50% less edges
+ if ( isPositive( edge ) ) {
+ Queue< HalfEdge > edges;
+ if ( ranges.containsKey( edge.getDest() ) ) {
+ edges = ranges.get( edge.getDest() );
+ } else {
+ edges = new ArrayDeque< HalfEdge >();
+ ranges.put( edge.getDest(), edges );
+ }
+
+ edges.add( edge );
+ }
+ }
+
+ for ( final Entry< Vertex, Queue< HalfEdge > > entry : ranges.entrySet() ) {
+ final Queue< HalfEdge > edges = entry.getValue();
+
+ // Only merge the edges if there are more than 1 edges to merge
+ if ( edges.size() > 1 ) {
+ // Keep the first edge, and discard the rest
+ final HalfEdge first = edges.poll();
+ RegionRule< T > rule = rules.get( first );
+ while ( !edges.isEmpty() ) {
+ final HalfEdge edge = edges.poll();
+
+ // Update the rule
+ rule = rule.apply( rules.get( edge ) );
+
+ // Update the destination's edge
+ edge.getDest().setEdge( edge.getNext() );
+ // Don't need to worry about updating this vertex's edge
+ // because the edge is either negative, the first edge in a range,
+ // or because it was not touched.
+
+ // Now remove the edge
+ HalfEdge.splice( edge, edge.getSym().getNext() );
+ HalfEdge.splice( edge.getSym(), edge.getNext() );
+
+ // Manually remove the edge from the rule map
+ rules.remove( edge );
+ rules.remove( edge.getSym() );
+ }
+ rules.put( first, rule );
+ rules.put( first.getSym(), rule.inverse() );
+ }
+ }
+ }
+ }
+
+ /**
+ * The vertices must be simplified, and must not contain any self
+ * intersections or overlaps.
+ */
+ public void generateRegions() {
+ if ( state != MeshState.SIMPLIFIED ) {
+ throw new IllegalStateException( "Mesh is not simplified!" );
+ }
+
+ final Queue< Vertex > queue = new PriorityQueue< Vertex >( Mesh::compare );
+ queue.addAll( vertices );
+
+ // Keep track of what regions belong to what edge
+ final Map< HalfEdge, T > regions = new HashMap< HalfEdge, T >();
+
+ // Keep track of edges from down to up
+ final SortedEdgeCollection< HalfEdge > edges = new SortedEdgeCollection< HalfEdge >( Mesh::greaterThanOrEqualTo );
+
+ // Keep track of edges that face inside or outside
+ final Set< HalfEdge > interiorAboveEdges = new HashSet< HalfEdge >();
+ final Set< HalfEdge > interiorBelowEdges = new HashSet< HalfEdge >();
+
+ // Keep track of the edges that need to be removed,
+ // because they do not change if a region is interior/exterior
+ final Set< HalfEdge > toRemove = new HashSet< HalfEdge >();
+
+ // Assume the edges on each vertex is already sorted
+ Vertex vertex;
+ while ( ( vertex = queue.poll() ) != null ) {
+ // Insert all edges into the edge set
+ for ( final HalfEdge edge : vertex ) {
+ if ( !isPositive( edge ) ) {
+ if ( !edges.remove( edge.getSym() ) ) {
+ throw new IllegalStateException( "Negative edge was not added to the edge collection" );
+ }
+ } else {
+ edges.insert( edge );
+ }
+ }
+
+ T currentRegion = defaultRegionSupplier.get();
+ // Loop over the edges from bottom to top
+ for ( final HalfEdge edge : edges ) {
+ final RegionRule< T > rule = rules.get( edge );
+
+ // Apply the rule to the previous region
+ final boolean wasInterior = currentRegion.isInterior();
+ currentRegion = rule.apply( currentRegion );
+ if ( currentRegion.isInterior() == wasInterior ) {
+ toRemove.add( edge );
+ }
+
+ if ( regions.containsKey( edge ) ) {
+ final T old = regions.get( edge );
+
+ if ( !old.equals( currentRegion ) ) {
+ // See if the previously calculated region for this edge is consistent
+ throw new IllegalStateException( "Inconsistent winding rule!" );
+ } else if ( wasInterior ^ interiorBelowEdges.contains( edge ) ) {
+ // If the previous region's interior status does not match
+ // what was recorded previously
+ throw new IllegalStateException( "Inconsistent region status" );
+ }
+ } else {
+ regions.put( edge, currentRegion );
+
+ if ( currentRegion.isInterior() ) {
+ // Mark this edge as an above interior edge, that is,
+ // the region above this edge is considered "inside"
+ interiorAboveEdges.add( edge );
+ }
+
+ if ( wasInterior ) {
+ // Mark this edge as a below interior edge,
+ // where the region below this edge is considered "inside"
+ interiorBelowEdges.add( edge );
+ }
+ }
+ }
+ }
+
+ // Keep track of which vertices we want to keep
+ final Set< Vertex > keep = new HashSet< Vertex >( vertices );
+
+ /*
+ * Remove edges from the graph, because the two regions on
+ * either side of the edge are both interior/exterior.
+ *
+ * This _will_ cause issues if you want to preserve
+ * more information than just interior/exterior,
+ * so this method is technically a destructive
+ * method, and the resulting graph cannot be re-used.
+ *
+ * However, if the rule you are using is not more
+ * complex than a simple winding number, then you
+ * can probably re-use the vertices.
+ */
+ for ( final HalfEdge edge : toRemove ) {
+ // Update the edge of the vertex, or remove the vertex
+ // if this is the only edge on the vertex
+ if ( edge.getPrev() == edge ) {
+ keep.remove( edge.getOrigin() );
+ } else {
+ edge.getOrigin().setEdge( edge.getPrev() );
+ HalfEdge.splice( edge, edge.getSym().getNext() );
+ }
+
+ if ( edge.getNext() == edge.getSym() ) {
+ keep.remove( edge.getDest() );
+ } else {
+ edge.getDest().setEdge( edge.getNext() );
+ HalfEdge.splice( edge.getSym(), edge.getNext() );
+ }
+
+ // Manually remove the edge from the rule and region map
+ rules.remove( edge );
+ rules.remove( edge.getSym() );
+ regions.remove( edge );
+ interiorAboveEdges.remove( edge );
+ interiorBelowEdges.remove( edge );
+ }
+
+ // Merge any colinear edges before returning
+ mergeColinearEdges( keep, interiorAboveEdges );
+
+ interiorEdges.clear();
+ interiorEdges.addAll( interiorAboveEdges );
+
+ vertices = keep;
+
+ state = MeshState.TRIANGULATION_READY;
+ }
+
+ // Sort the edges around this vertex.
+ private static List< HalfEdge > sort( Vertex vertex ) {
+ final List< HalfEdge > edges = new ArrayList< HalfEdge >();
+
+ for ( final HalfEdge edge : vertex ) {
+ if ( edge.isZero() ) {
+ throw new IllegalStateException( "Edge has length of 0" );
+ }
+ edges.add( edge );
+ }
+
+ // If there are 2 or less edges, they will always be in order
+ if ( edges.size() > 2 ) {
+ // 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 ) -> {
+ // This assumes that no edge has length of 0
+ final boolean e1p = isPositive( e1 );
+ final boolean e2p = isPositive( e2 );
+
+ if ( e1p ^ e2p ) {
+ return e1p ? 1 : -1;
+ } else if ( e1.getDest() == e2.getDest() ) {
+ return 0;
+ } else {
+ final double cross = e1.toVector2d().cross( e2.toVector2d() );
+ return Double.compare( cross, 0 );
+ }
+ } );
+
+ 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() );
+
+ // Make sure that they are in order
+ if ( e1.getPrev() != e2 ) {
+ e1.setPrev( e2 );
+ e2.getSym().setNext( e1 );
+ }
+ }
+
+ vertex.setEdge( edges.get( 0 ) );
+ }
+
+ return edges;
+ }
+
+ private void mergeColinearEdges( final Collection< Vertex > vertices, final Collection< HalfEdge > internalEdges ) {
+ final Collection< HalfEdge > toRemove = new ArrayDeque< HalfEdge >();
+
+ final Set< HalfEdge > edges = new HashSet< HalfEdge >();
+ for ( final HalfEdge internalEdge : internalEdges ) {
+ // Skip any edges that we have already checked
+ if ( edges.contains( internalEdge ) ) {
+ continue;
+ }
+
+ HalfEdge edge = internalEdge;
+
+ // Fast forward to the start of a potential colinear chain, since this edge may be in the middle of one
+ while ( Math.abs( edge.toVector2d().normalize().cross( edge.getPrev().toVector2d().normalize() ) ) <= ANGLE_TOLERANCE ) {
+ edge = edge.getNext();
+ }
+
+ while ( !edges.contains( edge ) ) {
+ HalfEdge next = edge.getNext();
+
+ final Vector2d a = edge.toVector2d().normalize();
+ final Vector2d b = next.toVector2d().normalize();
+
+ if ( Math.abs( a.cross( b ) ) <= ANGLE_TOLERANCE ) {
+ // We can more or less unlink edges for free at this point
+ // since we can guarantee each edge borders one exterior
+ // and one interior region, and no two regions share an edge.
+ final HalfEdge after = next.getNext();
+
+ // Unlink the next edge from after
+ HalfEdge.splice( next.getSym(), after );
+
+ // Unlink this edge from the next edge
+ HalfEdge.splice( edge.getSym(), next );
+
+ // Since we removed an edge entirely, check if it's still connected
+ // to anything that's important, or if we should remove the
+ // vertex it belongs to
+ if ( next.getPrev() == next ) {
+ vertices.remove( next.getOrigin() );
+ } else {
+ // Set the next origin to something else
+ next.getOrigin().setEdge( next.getPrev() );
+
+ // Unlink it from its own origin
+ HalfEdge.splice( next, next.getSym().getNext() );
+ }
+
+ // Unlink the next edge from the current vertex
+ HalfEdge.splice( next, next.getSym().getNext() );
+
+ // Link this edge and the edge after next together
+ HalfEdge.splice( edge.getSym(), after );
+
+ // Set the destination vertex
+ edge.getSym().setOrigin( after.getOrigin() );
+ edge.getDest().update();
+ after.getOrigin().setEdge( after );
+
+ edges.add( next );
+
+ rules.remove( next );
+ rules.remove( next.getSym() );
+
+ toRemove.add( next );
+ } else {
+ edges.add( edge );
+ edge = edge.getNext();
+ }
+ }
+ }
+
+ internalEdges.removeAll( toRemove );
+ }
+
+ public Collection< EdgePolygon > mesh() {
+ if ( state != MeshState.TRIANGULATION_READY ) {
+ throw new IllegalStateException( "Mesh has not been split into regions!" );
+ }
+
+ // Keep track of all edges that we've seen
+ final Set< HalfEdge > scanned = new HashSet< HalfEdge >();
+ // Map each old vertex to a new vertex
+ final Map< Vertex, Vertex > newVertices = new HashMap< Vertex, Vertex >();
+
+ // Keep track of all newly added interior edges
+ final Collection< HalfEdge > edges = new HashSet< HalfEdge >();
+
+ // Get a collection of vertices that need to be sorted, again
+ final Collection< Vertex > toSort = new HashSet< Vertex >();
+
+ for ( final HalfEdge edge : interiorEdges ) {
+ if ( scanned.add( edge ) ) {
+ HalfEdge temp = edge;
+
+ HalfEdge newEdge = null;
+ do {
+ if ( newEdge == null ) {
+ newEdge = new HalfEdge();
+ HalfEdge.splice( newEdge, newEdge.getSym() );
+ } 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 );
+
+ HalfEdge.splice( newEdge.getPrev(), vert.getEdge() );
+
+ toSort.add( vert );
+ } else {
+ vert = newEdge.getOrigin();
+ newVertices.put( tempVert, vert );
+
+ vert.setPosition( tempVert.getPosition() );
+ }
+ vert.update();
+
+ edges.add( newEdge );
+ } while ( scanned.add( temp = temp.getNext() ) );
+ }
+ }
+
+ for ( final Vertex v : toSort ) {
+ 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;
+ }
+
+ private static Collection< EdgePolygon > partitionMonotone( final TreeSet< Vertex > vertices, final Collection< HalfEdge > interior ) {
+ // Helper class to keep track of the immediate upper and lower
+ // edges for a given vertex
+ class MarkedEdge {
+ final HalfEdge lower;
+ final HalfEdge upper;
+
+ MarkedEdge( final HalfEdge lower, final HalfEdge upper ) {
+ this.lower = lower;
+ this.upper = upper;
+ }
+
+ public boolean equals( MarkedEdge o ) {
+ return o != null && lower == o.lower && upper == o.upper;
+ }
+ }
+
+ // Keep track of edges from bottom top
+ final SortedEdgeCollection< HalfEdge > edgeCollection = new SortedEdgeCollection< HalfEdge >( Mesh::greaterThanOrEqualTo );
+
+ // 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 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 >();
+
+ for ( final HalfEdge edge : event ) {
+ if ( isPositive( edge ) ) {
+ rightGoing.add( edge );
+ } else {
+ leftGoing.add( edge );
+ if ( !edgeCollection.remove( edge.getSym() ) ) {
+ throw new IllegalStateException( "Edge not added to the edge collection!" );
+ }
+ }
+ }
+
+ // 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
+ // would mean it is a vertex with no edges
+ // If it has a left and right edge, then that means it's a normal
+ // vertex and does not break monotony
+ MarkedEdge eventMark = null;
+ if ( leftGoing.isEmpty() ^ rightGoing.isEmpty() ) {
+ HalfEdge edge = event.getEdge();
+ if ( !isPositive( edge ) ) {
+ edge = edge.getSym();
+ }
+ final HalfEdge lower = edgeCollection.searchLower( edge );
+ // Since we are dealing with simple polygons, we can guarantee that
+ // if the lower edge is an interior edge, then the upper edge is
+ // also an interior edge and this vertex is inside the polygon.
+ if ( interior.contains( lower ) ) {
+ // Get the upper edge
+ final HalfEdge upper = edgeCollection.upper( lower );
+
+ // The current vertex must be between the upper and the lower
+ // edge, and it cannot be part of either edge
+ if ( upper == null ) {
+ throw new IllegalStateException( "Lower does not have an upper edge!" );
+ } if ( !interior.contains( upper.getSym() ) ) {
+ throw new IllegalStateException( "Polygon is not simple!" );
+ } else if ( upper.getOrigin() == event || lower.getOrigin() == event ) {
+ throw new IllegalStateException( "Vertex is invalid!" );
+ }
+
+ // Save this edge since it must be resolved eventually
+ eventMark = new MarkedEdge( lower, upper );
+ }
+ } else if ( rightGoing.isEmpty() && leftGoing.isEmpty() ) {
+ throw new IllegalStateException( "Cannot have a vertex with no edges!" );
+ }
+
+ // 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();
+ final MarkedEdge marked = entry.getValue();
+
+ final HalfEdge lower = marked.lower;
+ final HalfEdge upper = marked.upper;
+
+ if ( upper.getDest() == event ) {
+ // Is this vertex the left end of an upper edge?
+ final HalfEdge edge = new HalfEdge();
+
+ edge.setOrigin( event );
+ edge.getSym().setOrigin( prev );
+
+ HalfEdge.splice( edge, upper.getSym() );
+ HalfEdge.splice( edge.getSym(), prev.getEdge() );
+
+ supportEdges.add( edge );
+ } else if ( lower.getDest() == event ) {
+ // Is this vertex the left end of a lower edge?
+ final HalfEdge edge = new HalfEdge();
+
+ edge.setOrigin( event );
+ edge.getSym().setOrigin( prev );
+
+ HalfEdge.splice( edge, lower.getNext() );
+ HalfEdge.splice( edge.getSym(), prev.getEdge() );
+
+ supportEdges.add( edge );
+ } else if ( marked.equals( eventMark ) ) {
+ // Are there two marked vertices between the same edges?
+ final HalfEdge edge = new HalfEdge();
+
+ edge.setOrigin( event );
+ edge.getSym().setOrigin( prev );
+
+ HalfEdge.splice( edge, event.getEdge() );
+ HalfEdge.splice( edge, prev.getEdge() );
+
+ leftGoing.add( edge );
+ supportEdges.add( edge );
+
+ toSort.add( event );
+ } else {
+ continue;
+ }
+
+ // Remove the right marked vertex since it no longer breaks monotony
+ it.remove();
+ }
+
+ if ( eventMark != null ) {
+ // Since the mark may have had left going edges added, it may be
+ // 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 );
+
+ // 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 );
+ }
+ }
+
+ // Add all right going edges to the edge collection,
+ // now that we have finally finished adding new edges.
+ for ( final HalfEdge edge : rightGoing ) {
+ if ( isPositive( edge ) ) {
+ edgeCollection.insert( edge );
+ } else {
+ throw new IllegalStateException( "Edge suddenly changed sign!" );
+ }
+ }
+ }
+
+ // All right marked vertices MUST be resolved by now.
+ if ( !rightMarked.isEmpty() ) {
+ 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( lower, edge );
+ 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( upper.getSym().getNext(), edge );
+ 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.getSym(), vertex.getEdge() );
+ HalfEdge.splice( edge, prev.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 ) );
+
+ // 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.
+ for ( final HalfEdge edge : supportEdges ) {
+ final HalfEdge copy = new HalfEdge();
+
+ copy.setOrigin( edge.getOrigin() );
+ copy.getSym().setOrigin( edge.getDest() );
+
+ HalfEdge.splice( copy, edge );
+ HalfEdge.splice( copy.getSym(), edge.getNext() );
+ }
+
+ // 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
+ Collection< EdgePolygon > newPolygons = new HashSet< 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 {
+ // 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 ) {
+ 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 ( scanned.add( temp = temp.getNext() ) );
+ newPolygons.add( newPoly );
+ }
+ }
+
+ return newPolygons;
+ }
+
+ private void triangulate() {
+
+ }
+
+ /*
+ * Assumes region a and b overlap somewhere on the x axis
+ * Assume both wings do not intersect, and are nonzero and positive
+ * Assume that a and b cannot both be vertical and not share the same origin
+ */
+ private static boolean greaterThanOrEqualTo( final HalfEdge a, final HalfEdge 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() );
+ if ( compared == 0 ) {
+ return b2.subtracted( b1 ).cross( a2.subtracted( a1 ) ) >= 0;
+ } else if ( compared < 0 ) {
+ return b1.subtracted( a1 ).cross( a2.subtracted( a1 ) ) >= 0;
+ } else {
+ return b2.subtracted( b1 ).cross( a1.subtracted( b1 ) ) >= 0;
+ }
+ }
+
+ /**
+ * Determines if the edge's origin is less than its destination
+ *
+ * @param edge
+ * @return
+ */
+ private static boolean isPositive( HalfEdge edge ) {
+ return compare( edge.getOrigin(), edge.getDest() ) <= 0;
+ }
+
+ private static int compare( final Vertex a, final Vertex b ) {
+ final Vector2d aPos = a.getPosition();
+ final Vector2d bPos = b.getPosition();
+
+ final double x = compareX( aPos, bPos );
+ if ( x == 0 ) {
+ return Double.compare( compareY( aPos, bPos ), 0 );
+ }
+ return Double.compare( x, 0 );
+ }
+
+ private static double compareX( final Vector2d a, final Vector2d b ) {
+ return a.getX() - b.getX();
+ }
+
+ private static double compareY( final Vector2d a, final Vector2d b ) {
+ return a.getY() - b.getY();
+ }
+
+ // Check if 2 vertices are 'close enough'
+ private static boolean isSimilar( Vertex a, Vertex b ) {
+ return isSimilar( a.getPosition(), b.getPosition() );
+ }
+
+ private static boolean isSimilar( Vector2d a, Vector2d b ) {
+ // If for some reason the sqrt is slow, then it is
+ // probably OK using an AABB for the tolerance test
+ return Math.abs( a.distance( b ) ) <= VERTEX_TOLERANCE;
+ }
+
+ /*
+ * Represents an intersection
+ */
+ private static abstract class Intersection {
+ }
+
+ /*
+ * When 2 edges both intersect somewhere in the middle
+ */
+ private static class IntersectionEdgeToEdge extends Intersection {
+ private final Vector2d point;
+
+ IntersectionEdgeToEdge( Vector2d point ) {
+ this.point = point;
+ }
+
+ Vector2d getPoint() {
+ return point;
+ }
+ }
+
+ /*
+ * When 2 edges are colinear, and do not share the same endpoint
+ */
+ private static class IntersectionColinear extends Intersection {
+ private final HalfEdge shorter;
+ private final HalfEdge longer;
+
+ IntersectionColinear( HalfEdge shorter, HalfEdge longer ) {
+ this.shorter = shorter;
+ this.longer = longer;
+ }
+
+ public HalfEdge getShorterEdge() {
+ return shorter;
+ }
+
+ public HalfEdge getLongerEdge() {
+ return longer;
+ }
+ }
+
+ public static class EdgePolygon {
+ private Set< HalfEdge > edges = new HashSet< HalfEdge >();
+ private TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare );
+
+ private void addEdge( HalfEdge edge ) {
+ edges.add( edge );
+ vertices.add( edge.getOrigin() );
+ }
+
+ public Set< HalfEdge > getEdges() {
+ return edges;
+ }
+
+ public TreeSet< Vertex > getVertices() {
+ return vertices;
+ }
+ }
+
+ protected enum MeshState {
+ DIRTY,
+ SIMPLIFIED,
+ TRIANGULATION_READY
+ }
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Point.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Point.java
similarity index 68%
rename from src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Point.java
rename to src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Point.java
index 71a4cb7..bdf8d2d 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Point.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Point.java
@@ -1,7 +1,12 @@
-package com.aaaaahhhhhhh.bananapuncher714.tess4j;
+package com.aaaaahhhhhhh.bananapuncher714.mesh;
+/**
+ * A 2d cartesian coordinate
+ *
+ * @author BananaPuncher714
+ */
public class Point {
- double x, y;
+ protected double x, y;
public Point( double x, double y ) {
this.x = x;
@@ -15,6 +20,16 @@ public class Point {
public double getY() {
return y;
}
+
+ public Point setX( double x ) {
+ this.x = x;
+ return this;
+ }
+
+ public Point setY( double y ) {
+ this.y = y;
+ return this;
+ }
@Override
public int hashCode() {
@@ -43,4 +58,9 @@ public class Point {
return false;
return true;
}
+
+ @Override
+ public String toString() {
+ return String.format( "Point{x=%f,y=%f}", x, y );
+ }
}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Polygon.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Polygon.java
new file mode 100644
index 0000000..8c3e782
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Polygon.java
@@ -0,0 +1,31 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh;
+
+import java.util.List;
+
+/**
+ * A list of points denoting the shape of the polygon. May contain self intersections,
+ * duplicate vertices, and overlapping edges.
+ *
+ * @author BananaPuncher714
+ */
+public class Polygon {
+ protected List< Point > points;
+
+ public Polygon( List< Point > points ) {
+ this.points = points;
+ }
+
+ public List< Point > getPoints() {
+ return points;
+ }
+
+ public double area() {
+ double area = 0;
+ for ( int i = 0; i < points.size(); ++i ) {
+ final Vector2d a = new Vector2d( points.get( i ) );
+ final Vector2d b = new Vector2d( points.get( ( i + 1 ) % points.size() ) );
+ area += a.cross( b );
+ }
+ return Math.abs( area ) / 2.0;
+ }
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/SortedEdgeCollection.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/SortedEdgeCollection.java
new file mode 100644
index 0000000..a43e773
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/SortedEdgeCollection.java
@@ -0,0 +1,141 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
+
+public class SortedEdgeCollection< T > implements Iterable< T > {
+ Map< T, Node > quickMap = new ConcurrentHashMap< T, Node >();
+ Node head = new Node( null );
+
+ BiFunction< T, T, Boolean > isGreaterThan;
+
+ public SortedEdgeCollection( BiFunction< T, T, Boolean > compare ) {
+ isGreaterThan = compare;
+ }
+
+ public T bottom() {
+ return head.upper == head ? null : head.upper.value;
+ }
+
+ public T upper( T val ) {
+ Node n = quickMap.get( val );
+ return n == null ? null : n.upper.value;
+ }
+
+ public T lower( T val ) {
+ Node n = quickMap.get( val );
+ return n == null ? null : n.lower.value;
+ }
+
+ public boolean remove( T val ) {
+ Node n = quickMap.remove( val );
+ if ( n != null ) {
+ n.lower.upper = n.upper;
+ n.upper.lower = n.lower;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean contains( T val ) {
+ return quickMap.containsKey( val );
+ }
+
+ public void insert( T val ) {
+ if ( !quickMap.containsKey( val ) ) {
+ Node insertBefore = head.upper;
+ // Find the first node that the value is NOT greater than
+ // Otherwise, break and insert before
+ while ( insertBefore.value != null && isGreaterThan.apply( val, insertBefore.value ) ) {
+ insertBefore = insertBefore.upper;
+ }
+
+ Node newNode = new Node( val );
+
+ newNode.lower = insertBefore.lower;
+ newNode.lower.upper = newNode;
+ newNode.upper = insertBefore;
+
+ insertBefore.lower = newNode;
+
+ quickMap.put( val, newNode );
+ } else {
+ // Should not really happen
+ throw new IllegalStateException( "Tried to insert value twice" );
+ }
+ }
+
+ // Find the first value that is greater than the one provided
+ public T searchUpper( T val ) {
+ Node n = quickMap.get( val );
+ if ( n == null ) {
+ Node greater = head.upper;
+ while ( greater.value != null && isGreaterThan.apply( val, greater.value ) ) {
+ greater = greater.upper;
+ }
+ return greater.value;
+ } else {
+ return n.upper.value;
+ }
+ }
+
+ // Find the highest value that is lower than the one provided
+ public T searchLower( T val ) {
+ Node n = quickMap.get( val );
+ if ( n == null ) {
+ Node lower = head.upper;
+ while ( lower.value != null && isGreaterThan.apply( val, lower.value ) ) {
+ lower = lower.upper;
+ }
+ return lower.lower.value;
+ } else {
+ return n.lower.value;
+ }
+ }
+
+ public void clear() {
+ head = new Node( null );
+ quickMap.clear();
+ }
+
+ protected class Node {
+ Node lower;
+ Node upper;
+ T value;
+
+ Node( T val ) {
+ lower = this;
+ upper = this;
+
+ this.value = val;
+ }
+ }
+
+ @Override
+ public Iterator< T > iterator() {
+ return new Iterator< T >() {
+ Node current = head;
+
+ @Override
+ public boolean hasNext() {
+ return current.upper != head;
+ }
+
+ @Override
+ public T next() {
+ current = current.upper;
+ return current.value;
+ }
+
+ @Override
+ public void remove() {
+ T val = current.value;
+ current = current.lower;
+ SortedEdgeCollection.this.remove( val );
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Test.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Test.java
new file mode 100644
index 0000000..dc6e157
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Test.java
@@ -0,0 +1,42 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh;
+
+public class Test {
+ public static void main( String[] args ) {
+ final Vector2d a = new Vector2d( -1, -1 );
+ final Vector2d b = new Vector2d( 10, 10 );
+ final Vector2d c = new Vector2d( 0, -1 );
+ final Vector2d d = new Vector2d( 10, 11 );
+
+ test( a, b, c, d );
+ }
+
+ public static void test( final Vector2d a, final Vector2d b, final Vector2d c, final Vector2d d ) {
+ final Vector2d ab = b.subtracted( a );
+ final Vector2d cd = d.subtracted( c );
+
+ final Vector2d ac = c.subtracted( a );
+
+ final double acab = ac.cross( ab );
+ final double abcd = ab.cross( cd );
+
+ System.out.println( "Parallel value: " + abcd );
+ if ( abcd != 0 ) {
+ final double s = ac.cross( cd );
+ final double t = s / abcd;
+ final double u = acab / abcd;
+
+ System.out.println( "S: " + s );
+ System.out.println( "T: " + t );
+ System.out.println( "U: " + u );
+
+ System.out.println( "AB Length: " + ( t * ab.length() ) );
+ System.out.println( "CD Length: " + ( u * cd.length() ) );
+
+ // Gives the intersection correctly
+ System.out.println( "AB int: " + ab.multiplied( t ).add( a ) );
+ System.out.println( "CD int: " + cd.multiplied( u ).add( c ) );
+ } else {
+
+ }
+ }
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2d.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Vector2d.java
similarity index 67%
rename from src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2d.java
rename to src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Vector2d.java
index 32bab8b..314e999 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2d.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Vector2d.java
@@ -1,41 +1,28 @@
-package com.aaaaahhhhhhh.bananapuncher714.tess4j;
+package com.aaaaahhhhhhh.bananapuncher714.mesh;
-public class Vector2d {
- double x;
- double y;
-
+/**
+ * A basic 2d vector implementation.
+ *
+ * @author BananaPuncher714
+ */
+public class Vector2d extends Point {
public Vector2d() {
this( 0, 0 );
}
public Vector2d( double x, double y ) {
- this.x = x;
- this.y = y;
+ super( x, y );
}
- public Vector2d( Vector2d o ) {
+ public Vector2d( Point o ) {
this( o.x, o.y );
}
- public double getX() {
- return x;
- }
-
- public double getY() {
- return y;
- }
-
- public Vector2d setX( double x ) {
- this.x = x;
- return this;
- }
-
- public Vector2d setY( double y ) {
- this.y = y;
- return this;
+ public double angle( Point o ) {
+ return Math.atan2( cross( o ), dot( o ) );
}
- public double cross( Vector2d o ) {
+ public double cross( Point o ) {
return ( x * o.y ) - ( y * o.x );
}
@@ -47,15 +34,15 @@ public class Vector2d {
return Math.sqrt( lengthSquared() );
}
- public double distanceSquared( Vector2d o ) {
+ public double distanceSquared( Point o ) {
return distanceSquared( this, o );
}
- public double distance( Vector2d o ) {
+ public double distance( Point o ) {
return distance( this, o );
}
- public double dot( Vector2d o ) {
+ public double dot( Point o ) {
return dot( this, o );
}
@@ -69,13 +56,13 @@ public class Vector2d {
return new Vector2d( x + v, y + v );
}
- public Vector2d add( Vector2d o ) {
+ public Vector2d add( Point o ) {
x += o.x;
y += o.y;
return this;
}
- public Vector2d added( Vector2d o ) {
+ public Vector2d added( Point o ) {
return add( this, o );
}
@@ -89,13 +76,13 @@ public class Vector2d {
return new Vector2d( x - v, y - v );
}
- public Vector2d subtract( Vector2d o ) {
+ public Vector2d subtract( Point o ) {
x -= o.x;
y -= o.y;
return this;
}
- public Vector2d subtracted( Vector2d o ) {
+ public Vector2d subtracted( Point o ) {
return new Vector2d( x - o.x, y - o.y );
}
@@ -109,13 +96,13 @@ public class Vector2d {
return new Vector2d( x * v, y * v );
}
- public Vector2d multiply( Vector2d o ) {
+ public Vector2d multiply( Point o ) {
x *= o.x;
y *= o.y;
return this;
}
- public Vector2d multiplied( Vector2d o ) {
+ public Vector2d multiplied( Point o ) {
return multiply( this, o );
}
@@ -129,13 +116,13 @@ public class Vector2d {
return new Vector2d( x / v, y / v );
}
- public Vector2d divide( Vector2d o ) {
+ public Vector2d divide( Point o ) {
x /= o.x;
y /= o.y;
return this;
}
- public Vector2d divided( Vector2d o ) {
+ public Vector2d divided( Point o ) {
return new Vector2d( x / o.x, y / o.y );
}
@@ -151,23 +138,23 @@ public class Vector2d {
return x == 0 && y == 0;
}
- public static double dot( Vector2d a, Vector2d b ) {
+ public static double dot( Point a, Point b ) {
return a.x * b.x + a.y * b.y;
}
- public static Vector2d add( Vector2d a, Vector2d b ) {
+ public static Vector2d add( Point a, Point b ) {
return new Vector2d( a.x + b.x, a.y + b.y );
}
- public static Vector2d multiply( Vector2d a, Vector2d b ) {
+ public static Vector2d multiply( Point a, Point b ) {
return new Vector2d( a.x * b.x, a.y * b.y );
}
- public static double distanceSquared( Vector2d a, Vector2d b ) {
+ public static double distanceSquared( Vector2d a, Point b ) {
return a.subtracted( b ).lengthSquared();
}
- public static double distance( Vector2d a, Vector2d b ) {
+ public static double distance( Vector2d a, Point b ) {
return Math.sqrt( distanceSquared( a, b ) );
}
@@ -201,6 +188,6 @@ public class Vector2d {
@Override
public String toString() {
- return "Vector2d{x=" + x + ",y=" + y + "}";
+ return String.format( "Vector2d{x=%f,y=%f}", x, y );
}
}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Vertex.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Vertex.java
new file mode 100644
index 0000000..5decf33
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Vertex.java
@@ -0,0 +1,121 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh;
+
+import java.util.Iterator;
+
+/**
+ * The vertex class is used to represent a point in space. For our use cases, it is going to be 2d.
+ *
+ * A vertex contains two pieces of information:
+ * - The position
+ * - A half-edge which is attached to this vertex
+ *
+ * To traverse over all the edges which are connected to this vertex,
+ * loop over the previous edge of the half edge until all of the edges
+ * have been reached at least once.
+ *
+ * Whenever performing a splicing operation on an edge, it is a good
+ * idea to update all edges attached to this vertex to point to this
+ * vertex, in case if they are pointing at a different one.
+ *
+ * @author BananaPuncher714
+ */
+public class Vertex implements Iterable< HalfEdge > {
+ protected HalfEdge edge;
+ protected Vector2d position;
+
+ public Vertex( final HalfEdge edge ) {
+ this( edge, new Vector2d() );
+ }
+
+ public Vertex( final HalfEdge edge, final Vector2d position ) {
+ setEdge( edge );
+ setPosition( position );
+ }
+
+ public HalfEdge getEdge() {
+ return edge;
+ }
+
+ public Vertex setEdge( HalfEdge edge ) {
+ this.edge = edge;
+ return this;
+ }
+
+ public Vector2d getPosition() {
+ return position;
+ }
+
+ public Vertex setPosition( Vector2d vec ) {
+ this.position = vec;
+ return this;
+ }
+
+ /**
+ * O(n)
+ */
+ public int size() {
+ int size = 0;
+ for ( final Iterator< HalfEdge > it = iterator(); it.hasNext(); it.next() ) {
+ ++size;
+ }
+ return size;
+ }
+
+ /**
+ * Update all attached half-edges to point to this vertex
+ * as the origin
+ */
+ public void update() {
+ HalfEdge edge = this.edge;
+ do {
+ edge.setOrigin( this );
+ } while ( ( edge = edge.getPrev() ) != this.edge );
+ }
+
+ @Override
+ public Iterator< HalfEdge > iterator() {
+ return new Iterator< HalfEdge >() {
+ private final HalfEdge start = edge;
+ private HalfEdge index = null;
+
+ @Override
+ public boolean hasNext() {
+ return index != start;
+ }
+
+ @Override
+ public HalfEdge next() {
+ if ( index == null ) {
+ index = start.getPrev();
+ return start;
+ } else {
+ final HalfEdge curr = index;
+ index = index.getPrev();
+ return curr;
+ }
+ }
+ };
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Vertex other = (Vertex) obj;
+ if (position == null) {
+ if (other.position != null)
+ return false;
+ } else if (!position.equals(other.position))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Vertex{x=" + position.getX() + ",y=" + position.getY() + "}";
+ }
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/CompositeRegionRule.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/CompositeRegionRule.java
new file mode 100644
index 0000000..18d7c3e
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/CompositeRegionRule.java
@@ -0,0 +1,57 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh.region;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+public class CompositeRegionRule< T extends Region > implements RegionRule< T > {
+ final protected List< RegionRule< T > > rules;
+
+ public CompositeRegionRule() {
+ this.rules = new ArrayList< RegionRule< T > >();
+ }
+
+ public CompositeRegionRule( final List< RegionRule< T > > rules ) {
+ this.rules = new ArrayList< RegionRule< T > >( rules );
+ }
+
+ public CompositeRegionRule< T > addRule( final RegionRule< T > rule ) {
+ rules.add( rule );
+ return this;
+ }
+
+ public List< RegionRule< T > > getRules() {
+ return rules;
+ }
+
+ @Override
+ public T apply( T region ) {
+ for ( final RegionRule< T > rule : rules ) {
+ region = rule.apply( region );
+ }
+ return region;
+ }
+
+ @Override
+ public RegionRule< T > inverse() {
+ CompositeRegionRule< T > rule = new CompositeRegionRule< T >();
+ for ( ListIterator< RegionRule< T > > it = rules.listIterator(); it.hasPrevious(); ) {
+ rule.rules.add( it.previous().inverse() );
+ }
+ return rule;
+ }
+
+ @Override
+ public RegionRule< T > apply( RegionRule< T > rule ) {
+ final CompositeRegionRule< T > copy = new CompositeRegionRule< T >( rules );
+ if ( rule instanceof CompositeRegionRule ) {
+ final CompositeRegionRule< T > other = ( CompositeRegionRule< T > ) rule;
+ for ( RegionRule< T > r : other.getRules() ) {
+ copy.addRule( r );
+ }
+ } else {
+ copy.addRule( rule );
+ }
+ return copy;
+ }
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/Region.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/Region.java
new file mode 100644
index 0000000..366d6b7
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/Region.java
@@ -0,0 +1,14 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh.region;
+
+/**
+ * A continuous portion in space that can be clearly defined as "inside" or "outside".
+ *
+ * The equality check is mainly used to guarantee winding rule consistency.
+ *
+ * @author BananaPuncher714
+ */
+public abstract class Region {
+ // Represents the area under a wing
+ public abstract boolean isInterior();
+ public abstract boolean equals( Object other );
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/RegionRule.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/RegionRule.java
new file mode 100644
index 0000000..40414a1
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/RegionRule.java
@@ -0,0 +1,28 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh.region;
+
+/**
+ * A region rule describes how a region is modified, particularly when crossing a border
+ * between a known region to an unknown region. The inverse region rule must provide a
+ * symmetric change such that applying the region rule then the inverse, the resulting region
+ * must be equivalent to the original region. Normally, the same rule is applied to all polygons
+ * that need to be tessellated, such as the odd number rule, but if your polygons conform to different or
+ * more complex interior/exterior critera, then you can easily set different rules per polygon.
+ * An example might be if you have two sets of polygons that you would like to tessellate
+ * together, and distinctly mark regions where they might overlap. You can use a combination
+ * of two odd number rules to properly mark the boundaries.
+ *
+ * For more information:
+ * - https://en.wikipedia.org/wiki/Nonzero-rule
+ *
+ * @author BananaPuncher714
+ *
+ * @param A region type that can be modified by this rule
+ */
+public interface RegionRule< T extends Region > {
+ // Apply this rule to the region, and return a new region
+ T apply( T region );
+ // Get the inverse of this rule
+ RegionRule< T > inverse();
+ // Apply this rule to the given rule, and return a new rule
+ RegionRule< T > apply( RegionRule< T > rule );
+}
\ No newline at end of file
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/simple/RegionRuleWinding.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/simple/RegionRuleWinding.java
new file mode 100644
index 0000000..cbc3eb8
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/simple/RegionRuleWinding.java
@@ -0,0 +1,61 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple;
+
+import com.aaaaahhhhhhh.bananapuncher714.mesh.region.CompositeRegionRule;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.region.RegionRule;
+
+/**
+ * A simple winding number rule.
+ *
+ * Changes the winding number for a given region.
+ *
+ * @author BananaPuncher714
+ *
+ */
+public class RegionRuleWinding implements RegionRule< RegionSimple > {
+ public static final RegionRuleWinding CLOCKWISE = new RegionRuleWinding( 1 );
+ public static final RegionRuleWinding COUNTER_CLOCKWISE = CLOCKWISE.inverse();
+
+ private final int amount;
+ private RegionRuleWinding inverse;
+
+ private RegionRuleWinding( final int amount ) {
+ this.amount = amount;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ @Override
+ public RegionSimple apply( final RegionSimple region ) {
+ RegionSimple copy = new RegionSimple( region );
+ copy.increment( amount );
+ return copy;
+ }
+
+ @Override
+ public RegionRuleWinding inverse() {
+ if ( inverse == null ) {
+ inverse = new RegionRuleWinding( -amount );
+ inverse.inverse = this;
+ }
+ return inverse;
+ }
+
+ @Override
+ public RegionRule< RegionSimple > apply( final RegionRule< RegionSimple > rule ) {
+ if ( rule instanceof RegionRuleWinding ) {
+ final RegionRuleWinding other = ( RegionRuleWinding ) rule;
+ return new RegionRuleWinding( amount + other.amount );
+ } else {
+ final CompositeRegionRule< RegionSimple > composite = new CompositeRegionRule< RegionSimple >();
+ composite.addRule( this ).addRule( rule );
+ return composite;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.format( "RegionRuleWinding{amount=%1$d}", amount );
+ }
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/simple/RegionSimple.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/simple/RegionSimple.java
new file mode 100644
index 0000000..b276102
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/region/simple/RegionSimple.java
@@ -0,0 +1,86 @@
+package com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple;
+
+import com.aaaaahhhhhhh.bananapuncher714.mesh.region.Region;
+
+/**
+ * A region with a winding number. The interior-ness of
+ * this region is determined by the GLU winding rule:
+ *
+ * https://www.songho.ca/opengl/gl_tessellation.html#winding
+ *
+ * @author BananaPuncher714
+ */
+public class RegionSimple extends Region {
+ protected int windingNumber;
+ protected GluWindingRule rule;
+
+ public RegionSimple( RegionSimple o ) {
+ windingNumber = o.windingNumber;
+ rule = o.rule;
+ }
+
+ public RegionSimple( GluWindingRule rule ) {
+ this.windingNumber = 0;
+ this.rule = rule;
+ }
+
+ @Override
+ public boolean isInterior() {
+ switch ( rule ) {
+ case ODD:
+ return ( windingNumber & 1 ) == 1;
+ case NONZERO:
+ return windingNumber != 0;
+ case POSITIVE:
+ return windingNumber > 0;
+ case NEGATIVE:
+ return windingNumber < 0;
+ case ABS_GEQ_TWO:
+ return Math.abs( windingNumber ) >= 2;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ RegionSimple other = (RegionSimple) obj;
+ if (rule != other.rule)
+ return false;
+ if (windingNumber != other.windingNumber)
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format( "Region{number=%1$d,rule=%2$s}", windingNumber, rule );
+ }
+
+ public int getWindingNumber() {
+ return windingNumber;
+ }
+
+ public RegionSimple setWindingNumber( int number ) {
+ this.windingNumber = number;
+ return this;
+ }
+
+ public int increment( final int amount ) {
+ return windingNumber += amount;
+ }
+
+ public enum GluWindingRule {
+ ODD,
+ NONZERO,
+ POSITIVE,
+ NEGATIVE,
+ ABS_GEQ_TWO
+ }
+}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest.java
index 3f5cf02..ff4ca7e 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest.java
@@ -8,7 +8,6 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import javax.swing.JFrame;
@@ -17,16 +16,14 @@ import javax.swing.SwingUtilities;
import org.bukkit.util.Vector;
-import com.aaaaahhhhhhh.bananapuncher714.minietest.MeshTest.AABB;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkLocation;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Facet;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.MeshBuilder;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Plane;
-import com.aaaaahhhhhhh.bananapuncher714.tess4j.Point;
-import com.aaaaahhhhhhh.bananapuncher714.tess4j.Polygon;
import com.aaaaahhhhhhh.bananapuncher714.tess4j.Tess4j;
-import com.aaaaahhhhhhh.bananapuncher714.tess4j.Vector2d;
-import com.aaaaahhhhhhh.bananapuncher714.tess4j.Vector2dComparator;
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple;
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple.GluWindingRule;
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.WindingRuleSimple;
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java
new file mode 100644
index 0000000..6c07a72
--- /dev/null
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest2.java
@@ -0,0 +1,485 @@
+package com.aaaaahhhhhhh.bananapuncher714.minietest;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import org.bukkit.util.Vector;
+
+import com.aaaaahhhhhhh.bananapuncher714.mesh.EdgeSet;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.HalfEdge;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh.EdgePolygon;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Vertex;
+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;
+import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkLocation;
+import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Facet;
+import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.MeshBuilder;
+import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Plane;
+
+public class MeshingTest2 extends JPanel {
+ private static final File BASE = new File( System.getProperty( "user.dir" ) );
+ private static final File CHUNK_DIR = new File( BASE, "chunks" );
+
+ JFrame f;
+
+ private int windowWidth = 1200;
+ private int windowHeight = 1000;
+
+// private int centerX = 600;
+// private int centerY = 400;
+
+ private int centerX = 400;
+ private int centerY = 1200;
+
+ private Graphics g;
+
+ private int scale = 8;
+
+ private Collection< Vertex > data;
+ private Collection< EdgePolygon > polygons;
+
+ public static void main( String[] args ) {
+ if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) {
+ System.out.println( "Found " + CHUNK_DIR.list().length + " files" );
+ for ( File file : CHUNK_DIR.listFiles() ) {
+ Collection< Plane > planes = mesh( file );
+
+ Plane draw = null;
+
+ System.out.println( "Planes: " + planes.size() );
+ for ( Plane plane : planes ) {
+ if ( plane.polygons.size() > 100 ) {
+ draw = plane;
+ break;
+ }
+ }
+
+// draw = planes.get( new Random().nextInt( planes.size() ) );
+
+// test( planes );
+
+ final Collection< Vertex > data = process( draw );
+ final Collection< EdgePolygon > polys = triangulate( draw );
+// final Collection< EdgePolygon > polys = test();
+
+ SwingUtilities.invokeLater( new Runnable() {
+ @Override
+ public void run() {
+ new MeshingTest2( data, polys );
+ }
+ } );
+ break;
+ }
+ } else {
+ System.err.println( "No such directory exists: " + CHUNK_DIR );
+ }
+ }
+
+ private static Collection< Plane > mesh( File file ) {
+ String name = file.getName();
+ String[] split = name.split( "," );
+ ChunkLocation location = new ChunkLocation( split[ 0 ], Integer.parseInt( split[ 1 ] ), Integer.parseInt( split[ 2 ] ) );
+
+ System.out.println( "Parsing chunk " + location );
+
+ // First parse the file to get a list of all bounding boxes that we can use
+ List< AABB > boxes = new ArrayList< AABB >();
+ try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) {
+ String line;
+ while ( ( line = reader.readLine() ) != null ) {
+ if ( !line.isEmpty() ) {
+ String[] values = line.split( "," );
+ double minX = Double.parseDouble( values[ 0 ] );
+ double minY = Double.parseDouble( values[ 1 ] );
+ double minZ = Double.parseDouble( values[ 2 ] );
+ double maxX = Double.parseDouble( values[ 3 ] );
+ double maxY = Double.parseDouble( values[ 4 ] );
+ double maxZ = Double.parseDouble( values[ 5 ] );
+ boxes.add( new AABB( minX, minY, minZ, maxX, maxY, maxZ ) );
+ }
+ }
+ } catch ( FileNotFoundException e ) {
+ e.printStackTrace();
+ return null;
+ } catch ( IOException e ) {
+ e.printStackTrace();
+ return null;
+ }
+
+ System.out.println( "Found " + boxes.size() + " boxes" );
+ // 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 );
+ }
+ }
+
+ return builder.planes;
+ }
+
+ 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 processAllEnd = System.currentTimeMillis();
+ System.out.println( "Took " + ( processAllEnd - processAllStart ) + "ms to process " + planes.size() + " planes" );
+ }
+
+ private static Collection< Vertex > process( final Plane plane ) {
+ if ( plane != null ) {
+ System.out.println( "Norm:\t" + plane.normal );
+ System.out.println( "Ref:\t" + plane.point );
+ System.out.println( "Size:\t" + plane.polygons.size() );
+
+ Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
+ for ( Polygon poly : plane.polygons ) {
+ mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE );
+ }
+
+ System.out.println( "Initial vertex count: " + mesh.getVertices().size() );
+ System.out.println( "Initial edge count: " + ( mesh.getRuleSize() / 2 ) );
+
+ long start = System.currentTimeMillis();
+ mesh.simplify();
+
+ System.out.println( "After simplify vertex count: " + mesh.getVertices().size() );
+ System.out.println( "After simplify edge count: " + ( mesh.getRuleSize() / 2 ) );
+
+ mesh.generateRegions();
+ long end = System.currentTimeMillis();
+
+ 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( "Took " + ( end - start ) + "ms" );
+ return mesh.getVertices();
+ } else {
+ System.out.println( "No data!" );
+ return Collections.emptySet();
+ }
+ }
+
+ private static Collection< EdgePolygon > triangulate( final Plane plane ) {
+ if ( plane != null ) {
+ System.out.println( "Norm:\t" + plane.normal );
+ System.out.println( "Ref:\t" + plane.point );
+ System.out.println( "Size:\t" + plane.polygons.size() );
+
+ Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
+ for ( Polygon poly : plane.polygons ) {
+ mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE );
+ }
+
+ System.out.println( "Initial vertex count: " + mesh.getVertices().size() );
+ System.out.println( "Initial edge count: " + ( mesh.getRuleSize() / 2 ) );
+
+ long start = System.currentTimeMillis();
+ mesh.simplify();
+
+ System.out.println( "After simplify vertex count: " + mesh.getVertices().size() );
+ System.out.println( "After simplify edge count: " + ( mesh.getRuleSize() / 2 ) );
+
+ mesh.generateRegions();
+
+ System.out.println( "After generating regions vertex count: " + mesh.getVertices().size() );
+ System.out.println( "After generating regions edge count: " + ( mesh.getRuleSize() / 2 ) );
+
+ final Collection< EdgePolygon > polys = mesh.mesh();
+ long end = System.currentTimeMillis();
+ System.out.println( "Took " + ( end - start ) + "ms" );
+
+ return polys;
+ } else {
+ System.out.println( "No data!" );
+ return Collections.emptySet();
+ }
+ }
+
+ 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, -1 ),
+ new Point( 1, -1 ),
+ new Point( 1, 1 ),
+ new Point( -1, 1 )
+ ) ), RegionRuleWinding.CLOCKWISE );
+
+ mesh.simplify();
+
+ mesh.generateRegions();
+
+ return mesh.mesh();
+ }
+
+ private static List< Facet > generateFacetsFor( AABB box ) {
+ List< Facet > facets = new ArrayList< Facet >();
+
+ Vector p1 = new Vector( box.xmin, box.ymin, box.zmin );
+ Vector p2 = new Vector( box.xmin, box.ymin, box.zmax );
+ Vector p3 = new Vector( box.xmin, box.ymax, box.zmin );
+ Vector p4 = new Vector( box.xmin, box.ymax, box.zmax );
+ Vector p5 = new Vector( box.xmax, box.ymin, box.zmin );
+ Vector p6 = new Vector( box.xmax, box.ymin, box.zmax );
+ Vector p7 = new Vector( box.xmax, box.ymax, box.zmin );
+ Vector p8 = new Vector( box.xmax, box.ymax, box.zmax );
+
+ {
+ Facet facet = new Facet();
+ facet.points.add( p1 );
+ facet.points.add( p2 );
+ facet.points.add( p4 );
+ facet.points.add( p3 );
+ facet.normal = new Vector( -1, 0, 0 );
+ facets.add( facet );
+ }
+
+ {
+ Facet facet = new Facet();
+ facet.points.add( p5 );
+ facet.points.add( p6 );
+ facet.points.add( p8 );
+ facet.points.add( p7 );
+ facet.normal = new Vector( 1, 0, 0 );
+ facets.add( facet );
+ }
+
+ {
+ Facet facet = new Facet();
+ facet.points.add( p1 );
+ facet.points.add( p2 );
+ facet.points.add( p6 );
+ facet.points.add( p5 );
+ facet.normal = new Vector( 0, -1, 0 );
+ facets.add( facet );
+ }
+
+ {
+ Facet facet = new Facet();
+ facet.points.add( p3 );
+ facet.points.add( p4 );
+ facet.points.add( p8 );
+ facet.points.add( p7 );
+ facet.normal = new Vector( 0, 1, 0 );
+ facets.add( facet );
+ }
+
+ {
+ Facet facet = new Facet();
+ facet.points.add( p1 );
+ facet.points.add( p3 );
+ facet.points.add( p7 );
+ facet.points.add( p5 );
+ facet.normal = new Vector( 0, 0, -1 );
+ facets.add( facet );
+ }
+
+ {
+ Facet facet = new Facet();
+ facet.points.add( p2 );
+ facet.points.add( p4 );
+ facet.points.add( p8 );
+ facet.points.add( p6 );
+ facet.normal = new Vector( 0, 0, 1 );
+ facets.add( facet );
+ }
+
+ return facets;
+ }
+
+ static class AABB {
+ double xmin, ymin, zmin;
+ double xmax, ymax, zmax;
+
+ AABB( double xmin, double ymin, double zmin, double xmax, double ymax, double zmax ) {
+ this.xmin = xmin;
+ this.ymin = ymin;
+ this.zmin = zmin;
+
+ this.xmax = xmax;
+ this.ymax = ymax;
+ this.zmax = zmax;
+ }
+
+ double getVolume() {
+ return ( xmax - xmin ) * ( ymax - ymin ) * ( zmax - zmin );
+ }
+
+ @Override
+ public String toString() {
+ return String.format( "(%f, %f, %f) -> (%f, %f, %f)", xmin, ymin, zmin, xmax, ymax, zmax );
+ }
+ }
+
+ public MeshingTest2( Collection< Vertex > polys, Collection< EdgePolygon > polygons ) {
+ data = polys;
+ this.polygons = polygons;
+
+ f = new JFrame( "Drawing Board" );
+ f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
+
+ f.add( this );
+
+ f.setSize( windowWidth, windowHeight );
+ f.setVisible( true );
+ f.setResizable( true );
+
+ 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 );
+ double ax = a.getX() * cos - a.getY() * sin;
+ double ay = a.getX() * sin + a.getY() * cos;
+ return new Vector2d( ax, ay );
+ }
+
+ @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.setColor( Color.BLACK );
+
+ if ( data != null ) {
+ Collection< Vertex > polygons = data;
+
+ EdgeSet edges = new EdgeSet();
+ for ( Vertex vert : polygons ) {
+ for ( HalfEdge edge : vert ) {
+ if ( edges.add( edge ) ) {
+ 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
+ );
+ }
+ }
+
+ final double diff = scale * 0.3;
+ final Point point = vert.getPosition();
+ g.drawRect( ( int ) ( point.getX() * scale ) + centerX - ( int ) diff, ( int ) ( point.getY() * scale ) + centerY - ( int ) diff, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) );
+ }
+ }
+
+ if ( polygons != null ) {
+ System.out.println( "Polygon count: " + polygons.size() );
+ g.setColor( Color.BLACK );
+ for ( final EdgePolygon p : polygons ) {
+ final Collection< HalfEdge > edges = p.getEdges();
+
+ for ( HalfEdge edge : edges ) {
+ Point p1 = edge.getOrigin().getPosition();
+ Point p2 = edge.getDest().getPosition();
+ g.drawLine(
+ ( int ) ( p1.getX() * scale ) + centerX + 400,
+ ( int ) ( p1.getY() * scale ) + centerY,
+ ( int ) ( p2.getX() * scale ) + centerX + 400,
+ ( int ) ( p2.getY() * scale ) + centerY
+ );
+ }
+
+// int size = edges.size();
+// int[] xPoints = new int[ size ];
+// int[] yPoints = new int[ size ];
+//
+// 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;
+// yPoints[ i ] = ( int ) ( point.getY() * scale ) + centerY;
+// }
+//
+// g.setColor( new Color( ( int ) ( Math.random() * 0xFFFFFF ) ) );
+// 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 );
+// }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/MeshBuilder.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/MeshBuilder.java
index 5939a31..dc7faa3 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/MeshBuilder.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/MeshBuilder.java
@@ -22,12 +22,6 @@ public class MeshBuilder {
Optional< Vector > ref = facet.points.stream().filter( v -> v.lengthSquared() > 0.001 && Math.abs( v.clone().normalize().dot( facet.normal ) ) < .999999 ).findAny();
if ( ref.isPresent() ) {
plane.point = ref.get();
- } else {
- System.out.println( "Normal " + facet.normal );
- for ( Vector point : facet.points ) {
- System.out.println( point );
- }
- System.exit( 1 );
}
plane.addShape( facet );
planes.add( plane );
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/Plane.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/Plane.java
index 578c584..191cbff 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/Plane.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/Plane.java
@@ -7,8 +7,8 @@ import org.bukkit.util.Vector;
import org.joml.Matrix3d;
import org.joml.Vector3d;
-import com.aaaaahhhhhhh.bananapuncher714.tess4j.Point;
-import com.aaaaahhhhhhh.bananapuncher714.tess4j.Polygon;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
public class Plane {
public Vector normal;
@@ -17,30 +17,35 @@ public class Plane {
public List< Polygon > polygons = new ArrayList< Polygon >();
public void addShape( Facet facet ) {
- // TODO Flatten the polygons into 2d points
- // Get the basis of this plane
- Vector basis1 = normal.clone().multiply( point.clone().multiply( -1 ).dot( normal ) ).add( point ).normalize();
- Vector3d b1 = new Vector3d( basis1.getX(), basis1.getY(), basis1.getZ() );
- Vector3d b3 = new Vector3d( normal.getX(), normal.getY(), normal.getZ() );
- Vector3d b2 = new Vector3d( b1 ).cross( b3 ).normalize();
- Matrix3d mat = new Matrix3d( b1, b2, b3 ).transpose();
+ Matrix3d mat = getProjectionMatrix();
List< Point > polygon = new ArrayList< Point >();
for ( Vector p : facet.points ) {
double t = point.clone().subtract( p ).dot( normal );
+ // TODO Subtract point or to not subtract point?!?
Vector i = normal.clone().multiply( t ).add( p ).subtract( point );
Vector3d v = new Vector3d( i.getX(), i.getY(), i.getZ() );
Vector3d r = v.mul( mat );
-
+
polygon.add( new Point( r.x(), r.y() ) );
}
-
+
polygons.add( new Polygon( polygon ) );
}
public boolean matches( Vector normal, Vector point ) {
return Math.abs( this.normal.dot( normal ) ) > 0.99 && Math.abs( this.point.clone().subtract( point ).dot( normal ) ) < 0.01;
}
+
+ private Matrix3d getProjectionMatrix() {
+ final Vector basis1 = normal.clone().multiply( point.clone().multiply( -1 ).dot( normal ) ).add( point ).normalize();
+ final Vector3d b1 = new Vector3d( basis1.getX(), basis1.getY(), basis1.getZ() );
+ final Vector3d b3 = new Vector3d( normal.getX(), normal.getY(), normal.getZ() );
+ final Vector3d b2 = new Vector3d( b1 ).cross( b3 ).normalize();
+ final Matrix3d mat = new Matrix3d( b1, b2, b3 ).transpose();
+
+ return mat;
+ }
}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/HalfWing.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/HalfWing.java
index 63d3b29..3dfc455 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/HalfWing.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/HalfWing.java
@@ -1,5 +1,7 @@
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d;
+
public class HalfWing {
protected HalfWing sym;
protected HalfWing prev;
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Mesh.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Mesh.java
deleted file mode 100644
index 9e460ff..0000000
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Mesh.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.aaaaahhhhhhh.bananapuncher714.tess4j;
-
-import java.util.List;
-
-public class Mesh {
- List< HalfEdge > edges;
-
-
-}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Polygon.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Polygon.java
deleted file mode 100644
index d502890..0000000
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Polygon.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.aaaaahhhhhhh.bananapuncher714.tess4j;
-
-import java.util.List;
-
-public class Polygon {
- List< Point > points;
-
- public Polygon( List< Point > points ) {
- this.points = points;
- }
-
- public List< Point > getPoints() {
- return points;
- }
-}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Scratch.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Scratch.java
index 3f13ddb..30a5e24 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Scratch.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Scratch.java
@@ -1,55 +1,104 @@
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d;
+
public class Scratch {
+ public static class Test {
+ int value;
+
+ Test( int v ) {
+ value = v;
+ }
+
+ @Override
+ public String toString() {
+ return "Test(" + value + ")";
+ }
+ }
+
public static void main( String[] args ) {
+ Map< Test, String > weakMap = new WeakHashMap< Test, String >();
{
- String t = "Vertex{x=4.25,y=0.5}\r\n" +
- "Vertex{x=2.75,y=-2.5}\r\n" +
- "Vertex{x=2.2045454545454546,y=-0.8636363636363638}\r\n" +
- "Vertex{x=1.75,y=0.5}\r\n" +
- "Vertex{x=1.25,y=-1.5}\r\n" +
- "Vertex{x=1.25,y=2.0}\r\n" +
- "Vertex{x=1.0,y=-1.0}\r\n" +
- "Vertex{x=1.0,y=0.0}\r\n" +
- "Vertex{x=1.0,y=0.5}\r\n" +
- "Vertex{x=1.0,y=1.0}\r\n" +
- "Vertex{x=1.0,y=1.25}\r\n" +
- "Vertex{x=1.0,y=3.0}\r\n" +
- "Vertex{x=0.9166666666666666,y=1.0}\r\n" +
- "Vertex{x=0.75,y=0.5}\r\n" +
- "Vertex{x=0.7222222222222222,y=0.41666666666666674}\r\n" +
- "Vertex{x=0.6666666666666667,y=0.5}\r\n" +
- "Vertex{x=0.5833333333333333,y=0.0}\r\n" +
- "Vertex{x=0.5,y=-1.0}\r\n" +
- "Vertex{x=0.33333333333333337,y=1.0}\r\n" +
- "Vertex{x=0.2954545454545454,y=-0.8636363636363636}\r\n" +
- "Vertex{x=0.24999999999999994,y=-1.0}\r\n" +
- "Vertex{x=0.0,y=-2.0}\r\n" +
- "Vertex{x=0.0,y=1.5}\r\n" +
- "Vertex{x=-0.050000000000000024,y=-1.9}\r\n" +
- "Vertex{x=-0.25,y=-2.5}\r\n" +
- "Vertex{x=-0.33333333333333337,y=1.0}\r\n" +
- "Vertex{x=-0.5,y=-1.0}\r\n" +
- "Vertex{x=-0.6666666666666667,y=0.5}\r\n" +
- "Vertex{x=-0.9999999999999999,y=-2.220446049250313E-16}\r\n" +
- "Vertex{x=-1.0,y=-1.0}\r\n" +
- "Vertex{x=-1.0,y=0.0}\r\n" +
- "Vertex{x=-1.0,y=0.5}\r\n" +
- "Vertex{x=-1.0,y=1.0}\r\n" +
- "Vertex{x=-1.0,y=2.0}\r\n" +
- "Vertex{x=-1.0,y=3.0}\r\n" +
- "Vertex{x=-1.75,y=0.5}\r\n" +
- "Vertex{x=-2.0,y=-1.0}\r\n" +
- "Vertex{x=-2.0,y=0.0}\r\n" +
- "Vertex{x=-2.0,y=1.0}\r\n" +
- "Vertex{x=-2.0,y=2.0}\r\n" +
- "Vertex{x=-5.0,y=0.0}\r\n" +
- "Vertex{x=-5.0,y=1.0}";
- String[] split = t.split( "\r\n" );
- for ( int i = split.length - 1; i >= 0; i-- ) {
- System.out.println( split[ i ] );
+ List< Test > values = new ArrayList< Test >();
+ {
+ for ( int i = 0; i < 10000; ++i ) {
+ Test test = new Test(i);
+ weakMap.put( test, test.toString() );
+ values.add( test );
+ }
+ }
+
+ System.out.println( "Size: " + weakMap.size() );
+ for ( int i = 45; i < 55; ++i ) {
+ values.set( i, null );
+ }
+ System.out.println( "Size: " + weakMap.size() );
+ for ( int i = 0; i < 100; i++ ) {
+ weakMap.size();
+ weakMap.isEmpty();
+ }
+ values = null;
+ try {
+ Thread.sleep( 5000 );
+ } catch ( InterruptedException e ) {
+ e.printStackTrace();
}
}
+ System.out.println( "Size: " + weakMap.size() );
+
+// {
+// String t = "Vertex{x=4.25,y=0.5}\r\n" +
+// "Vertex{x=2.75,y=-2.5}\r\n" +
+// "Vertex{x=2.2045454545454546,y=-0.8636363636363638}\r\n" +
+// "Vertex{x=1.75,y=0.5}\r\n" +
+// "Vertex{x=1.25,y=-1.5}\r\n" +
+// "Vertex{x=1.25,y=2.0}\r\n" +
+// "Vertex{x=1.0,y=-1.0}\r\n" +
+// "Vertex{x=1.0,y=0.0}\r\n" +
+// "Vertex{x=1.0,y=0.5}\r\n" +
+// "Vertex{x=1.0,y=1.0}\r\n" +
+// "Vertex{x=1.0,y=1.25}\r\n" +
+// "Vertex{x=1.0,y=3.0}\r\n" +
+// "Vertex{x=0.9166666666666666,y=1.0}\r\n" +
+// "Vertex{x=0.75,y=0.5}\r\n" +
+// "Vertex{x=0.7222222222222222,y=0.41666666666666674}\r\n" +
+// "Vertex{x=0.6666666666666667,y=0.5}\r\n" +
+// "Vertex{x=0.5833333333333333,y=0.0}\r\n" +
+// "Vertex{x=0.5,y=-1.0}\r\n" +
+// "Vertex{x=0.33333333333333337,y=1.0}\r\n" +
+// "Vertex{x=0.2954545454545454,y=-0.8636363636363636}\r\n" +
+// "Vertex{x=0.24999999999999994,y=-1.0}\r\n" +
+// "Vertex{x=0.0,y=-2.0}\r\n" +
+// "Vertex{x=0.0,y=1.5}\r\n" +
+// "Vertex{x=-0.050000000000000024,y=-1.9}\r\n" +
+// "Vertex{x=-0.25,y=-2.5}\r\n" +
+// "Vertex{x=-0.33333333333333337,y=1.0}\r\n" +
+// "Vertex{x=-0.5,y=-1.0}\r\n" +
+// "Vertex{x=-0.6666666666666667,y=0.5}\r\n" +
+// "Vertex{x=-0.9999999999999999,y=-2.220446049250313E-16}\r\n" +
+// "Vertex{x=-1.0,y=-1.0}\r\n" +
+// "Vertex{x=-1.0,y=0.0}\r\n" +
+// "Vertex{x=-1.0,y=0.5}\r\n" +
+// "Vertex{x=-1.0,y=1.0}\r\n" +
+// "Vertex{x=-1.0,y=2.0}\r\n" +
+// "Vertex{x=-1.0,y=3.0}\r\n" +
+// "Vertex{x=-1.75,y=0.5}\r\n" +
+// "Vertex{x=-2.0,y=-1.0}\r\n" +
+// "Vertex{x=-2.0,y=0.0}\r\n" +
+// "Vertex{x=-2.0,y=1.0}\r\n" +
+// "Vertex{x=-2.0,y=2.0}\r\n" +
+// "Vertex{x=-5.0,y=0.0}\r\n" +
+// "Vertex{x=-5.0,y=1.0}";
+// String[] split = t.split( "\r\n" );
+// for ( int i = split.length - 1; i >= 0; i-- ) {
+// System.out.println( split[ i ] );
+// }
+// }
// {
// System.out.println( new Vector2d( 1, 0 ).cross( new Vector2d( 1, 1 ) ) );
//
@@ -299,14 +348,14 @@ public class Scratch {
if ( a1.equals( b1 ) ) {
return b2.subtracted( b1 ).cross( a2.subtracted( a1 ) ) >= 0;
- } else if ( a1.x < b1.x ) {
+ } else if ( a1.getX() < b1.getX() ) {
System.out.println( "LESS A" );
return b1.subtracted( a1 ).cross( a2.subtracted( a1 ) ) >= 0;
- } else if ( a1.x > b1.x ) {
+ } else if ( a1.getX() > b1.getX() ) {
System.out.println( "MORAY" );
return b2.subtracted( b1 ).cross( a1.subtracted( b1 ) ) >= 0;
} else {
- return a1.y > b1.y;
+ return a1.getY() > b1.getY();
}
}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4j.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4j.java
index 3ab0258..611b9b5 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4j.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4j.java
@@ -19,6 +19,9 @@ import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.function.Supplier;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d;
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple;
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple.GluWindingRule;
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.WindingRuleSimple;
@@ -39,12 +42,12 @@ public class Tess4j< T extends Region > {
private Vector2dComparator comparator = new Vector2dComparator() {
@Override
public double compareX( Vector2d a, Vector2d b ) {
- return a.x - b.x;
+ return a.getX() - b.getX();
}
@Override
public double compareY( Vector2d a, Vector2d b ) {
- return a.y - b.y;
+ return a.getY() - b.getY();
}
};
@@ -69,7 +72,7 @@ public class Tess4j< T extends Region > {
// Populate the vertex queue
// TODO Ensure that the polygon has at least 3 points?
HalfWing wing = null;
- for ( Point point : polygon.points ) {
+ for ( Point point : polygon.getPoints() ) {
if ( wing == null ) {
// Make a self loop with one edge and one vertex
wing = new HalfWing();
@@ -81,7 +84,7 @@ public class Tess4j< T extends Region > {
}
// Set the origin
- wing.getOrigin().setPosition( new Vector2d( point.x, point.y ) );
+ wing.getOrigin().setPosition( new Vector2d( point ) );
// TODO Temporary angle check
// final double angle = 128;
@@ -100,11 +103,19 @@ public class Tess4j< T extends Region > {
}
public void tessellate() {
+ StagePreTess stage = tessStage.get();
+ Queue< Vertex > queue = stage.vertexQueue;
+ System.out.println( "Initial vertex count: " + queue.size() );
+ System.out.println( "Initial vertices: " + ( stage.windingMap.size() / 2 ) );
+
/*
* Given a heap of vertices and edges, remove any conflicting intersections and form interior regions
*/
generateRegions();
+ System.out.println( "After vertex count: " + queue.size() );
+ System.out.println( "After vertices: " + ( stage.windingMap.size() / 2 ) );
+
/*
* Merge all adjacent collinear edges
*/
@@ -1478,27 +1489,14 @@ public class Tess4j< T extends Region > {
Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); }, new Vector2dComparator() {
@Override
public double compareX( Vector2d a, Vector2d b ) {
- return a.x - b.x;
+ return a.getX() - b.getX();
}
@Override
public double compareY( Vector2d a, Vector2d b ) {
- return a.y - b.y;
+ return a.getY() - b.getY();
}
} );
- tess4j.addPolygon( new Polygon( Arrays.asList(
- new Point( -1, -1 ),
- new Point( 1, -1 ),
- new Point( 1, 1 ),
- new Point( -1, 1 )
- ) ), new WindingRuleSimple() );
- tess4j.addPolygon( new Polygon( Arrays.asList(
- new Point( -1, -1 ),
- new Point( 1, -1 ),
- new Point( 1, 1 ),
- new Point( -1, 1 )
- ) ), new WindingRuleSimple() );
-
// tess4j.addPolygon( new Polygon( Arrays.asList(
// new Point( -1, -1 ),
// new Point( 1, -1 ),
@@ -1506,42 +1504,55 @@ public class Tess4j< T extends Region > {
// new Point( -1, 1 )
// ) ), new WindingRuleSimple() );
// tess4j.addPolygon( new Polygon( Arrays.asList(
-// new Point( -1, 1 ),
-// new Point( 1, 1 ),
-// new Point( 1, 3 ),
-// new Point( -1, 3 )
-// ) ), new WindingRuleSimple() );
-// tess4j.addPolygon( new Polygon( Arrays.asList(
-// new Point( -2, -1 ),
// new Point( -1, -1 ),
-// new Point( -1, 2 ),
-// new Point( -2, 2 )
+// new Point( 1, -1 ),
+// new Point( 1, 1 ),
+// new Point( -1, 1 )
// ) ), new WindingRuleSimple() );
-// tess4j.addPolygon( new Polygon( Arrays.asList(
-// new Point( -4, -2 ),
-// new Point( -1, -2 ),
-// new Point( -1, 1.5 ),
-// new Point( -4, 1.5 )
-// ) ), new WindingRuleSimple() );
-// tess4j.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 )
-// ) ), new WindingRuleSimple() );
-// tess4j.addPolygon( new Polygon( Arrays.asList(
-// new Point( -1, 0 ),
-// new Point( 0, 1.5 ),
-// new Point( 1, 0 )
-// ) ), new WindingRuleSimple() );
-// tess4j.addPolygon( new Polygon( Arrays.asList(
-// new Point( 1, 0 ),
-// new Point( 0, -2 ),
-// new Point( -1, 0 )
-// ) ), new WindingRuleSimple() );
- tess4j.tessellate();
+ tess4j.addPolygon( new Polygon( Arrays.asList(
+ new Point( -1, -1 ),
+ new Point( 1, -1 ),
+ new Point( 1, 1 ),
+ new Point( -1, 1 )
+ ) ), new WindingRuleSimple() );
+ tess4j.addPolygon( new Polygon( Arrays.asList(
+ new Point( -1, 1 ),
+ new Point( 1, 1 ),
+ new Point( 1, 3 ),
+ new Point( -1, 3 )
+ ) ), new WindingRuleSimple() );
+ tess4j.addPolygon( new Polygon( Arrays.asList(
+ new Point( -2, -1 ),
+ new Point( -1, -1 ),
+ new Point( -1, 2 ),
+ new Point( -2, 2 )
+ ) ), new WindingRuleSimple() );
+ tess4j.addPolygon( new Polygon( Arrays.asList(
+ new Point( -4, -2 ),
+ new Point( -1, -2 ),
+ new Point( -1, 1.5 ),
+ new Point( -4, 1.5 )
+ ) ), new WindingRuleSimple() );
+ tess4j.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 )
+ ) ), new WindingRuleSimple() );
+ tess4j.addPolygon( new Polygon( Arrays.asList(
+ new Point( -1, 0 ),
+ new Point( 0, 1.5 ),
+ new Point( 1, 0 )
+ ) ), new WindingRuleSimple() );
+ tess4j.addPolygon( new Polygon( Arrays.asList(
+ new Point( 1, 0 ),
+ new Point( 0, -2 ),
+ new Point( -1, 0 )
+ ) ), new WindingRuleSimple() );
+
+ tess4j.tessellate();
}
/*
@@ -1763,7 +1774,7 @@ public class Tess4j< T extends Region > {
for ( HalfWing wing : poly ) {
Vector2d v = wing.getOrigin().getPosition();
- points.add( new Point( v.x, v.y ) );
+ points.add( v );
}
filledPolygons.add( new Polygon( points ) );
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4jTest.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4jTest.java
index 7c0bb9a..70e02a7 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4jTest.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4jTest.java
@@ -9,6 +9,9 @@ import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d;
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple;
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple.GluWindingRule;
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.WindingRuleSimple;
@@ -114,14 +117,14 @@ public class Tess4jTest extends JPanel {
public double compareX( Vector2d a, Vector2d b ) {
a = rotate( a, 128 );
b = rotate( b, 128 );
- return a.x - b.x;
+ return a.getX() - b.getX();
}
@Override
public double compareY(Vector2d a, Vector2d b ) {
a = rotate( a, 128 );
b = rotate( b, 128 );
- return b.y - a.y;
+ return b.getY() - a.getY();
}
} );
// tess4j.addPolygon( new Polygon( Arrays.asList(
@@ -195,21 +198,21 @@ public class Tess4jTest extends JPanel {
System.out.println( "Polygon count" );
System.out.println( polygons.size() );
for ( Polygon p : polygons ) {
- System.out.println( p.points.size() );
- int size = p.points.size();
+ System.out.println( p.getPoints().size() );
+ int size = p.getPoints().size();
int[] xPoints = new int[ size ];
int[] yPoints = new int[ size ];
- for ( int i = 0; i < p.points.size(); i++ ) {
- Point point = p.points.get( i );
+ for ( int i = 0; i < p.getPoints().size(); i++ ) {
+ Point point = p.getPoints().get( i );
- System.out.println( point.x + ", " + point.y );
- Vector2d pVec = new Vector2d( point.x, point.y );
+ System.out.println( point.getX() + ", " + point.getY() );
+ Vector2d pVec = new Vector2d( point.getX(), point.getY() );
pVec = rotate( pVec, 0 );
point = new Point( pVec.getX(), pVec.getY() );
- xPoints[ i ] = ( int ) ( point.x * scale ) + centerX;
- yPoints[ i ] = 100 - ( int ) ( point.y * scale ) + centerY;
+ xPoints[ i ] = ( int ) ( point.getX() * scale ) + centerX;
+ yPoints[ i ] = 100 - ( int ) ( point.getY() * scale ) + centerY;
}
g.setColor( new Color( ( int ) ( Math.random() * 0xFFFFFF ) ) );
@@ -218,13 +221,13 @@ public class Tess4jTest extends JPanel {
g.setColor( Color.BLACK );
for ( Polygon p : polygons ) {
- for ( Point point : p.points ) {
- Vector2d pVec = new Vector2d( point.x, point.y );
+ for ( Point point : p.getPoints() ) {
+ Vector2d pVec = new Vector2d( point.getX(), point.getY() );
pVec = rotate( pVec, 0 );
point = new Point( pVec.getX(), pVec.getY() );
double diff = scale * .05;
- g.drawRect( ( int ) ( point.x * scale ) + centerX - ( int ) diff, 100 - ( int ) ( point.y * scale ) + centerY - ( int ) diff, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) );
+ g.drawRect( ( int ) ( point.getX() * scale ) + centerX - ( int ) diff, 100 - ( int ) ( point.getY() * scale ) + centerY - ( int ) diff, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) );
}
}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tessellator.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tessellator.java
index 5edfc7a..bc6f83e 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tessellator.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tessellator.java
@@ -5,7 +5,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Optional;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
@@ -13,6 +12,9 @@ import java.util.WeakHashMap;
import org.joml.Math;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
+
public class Tessellator {
// TODO Insecure
private static VertexOld vertex;
@@ -43,7 +45,7 @@ public class Tessellator {
Set< VertexOld > vertexCollection = Collections.newSetFromMap( new WeakHashMap< VertexOld, Boolean >() );
for ( Polygon polygon : shapes ) {
HalfEdge edge = null;
- for ( Point point : polygon.points ) {
+ for ( Point point : polygon.getPoints() ) {
if ( edge == null ) {
// Make a self loop with one edge and one vertex
edge = HalfEdge.makeEdge();
@@ -53,17 +55,17 @@ public class Tessellator {
edge = edge.nextLeft;
}
- edge.origin.x = point.x;
- edge.origin.y = point.y;
+ edge.origin.x = point.getX();
+ edge.origin.y = point.getY();
// For now, assume reverse contours is false
edge.winding = 1;
edge.sym().winding = -1;
- minX = Math.min( minX, point.x );
- minY = Math.min( minY, point.y );
- maxX = Math.max( maxX, point.x );
- maxY = Math.max( maxY, point.y );
+ minX = Math.min( minX, point.getX() );
+ minY = Math.min( minY, point.getY() );
+ maxX = Math.max( maxX, point.getX() );
+ maxY = Math.max( maxY, point.getY() );
vertexCollection.add( edge.origin );
vertexCollection.add( edge.sym().origin );
@@ -450,7 +452,7 @@ public class Tessellator {
// Compare points
if ( !y1.lessThanOrEqualTo( x2 ) ) {
- point.x = ( y1.x + x2.x ) / 2;
+ point.setX( ( y1.x + x2.x ) / 2 );
} else if ( x2.lessThanOrEqualTo( y2 ) ) {
double z1 = y1.verticalDistance( x1, x2 );
double z2 = x2.verticalDistance( y1, y2 );
@@ -458,7 +460,7 @@ public class Tessellator {
z1 *= -1;
z2 *= -1;
}
- point.x = interpolate( z1, y1.x, z2, x2.x );
+ point.setX( interpolate( z1, y1.x, z2, x2.x ) );
} else {
double z1 = y1.compareTo( x1, x2 );
double z2 = - y2.compareTo( x1, x2 );
@@ -466,7 +468,7 @@ public class Tessellator {
z1 *= -1;
z2 *= -1;
}
- point.x = interpolate( z1, y1.x, z2, y2.x );
+ point.setX( interpolate( z1, y1.x, z2, y2.x ) );
}
return point;
}
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Util.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Util.java
index 195e80e..ece49cb 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Util.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Util.java
@@ -9,6 +9,8 @@ import java.util.Optional;
import org.joml.Math;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
+
public class Util {
protected static String toString( Object obj ) {
StringBuilder builder = new StringBuilder();
@@ -142,8 +144,8 @@ public class Util {
if ( qpr == 0 ) {
// Do not need to calculate the distance between the two parallel lines since it is 0
final Vector2dOld midpoint = r.multiply( midt ).add( p );
- point.x = midpoint.x;
- point.y = midpoint.y;
+ point.setX( midpoint.x );
+ point.setY( midpoint.y );
} else {
// Calculate the midpoint along pr
final Vector2dOld midp = r.multiply( midt ).add( p );
@@ -152,8 +154,8 @@ public class Util {
final Vector2dOld z = perp.multiply( qp.dot( perp ) / rr );
// Sum the components
final Vector2dOld midpoint = midp.add( z.divide( 2.0 ) );
- point.x = midpoint.x;
- point.y = midpoint.y;
+ point.setX( midpoint.x );
+ point.setY( midpoint.y );
}
} else {
// The lines aren't really anywhere close to each other, so just find the midpoint between the 2 closest points
@@ -162,8 +164,8 @@ public class Util {
// Same as the first edge but the second edge may be inverted
final Vector2dOld qv = ( inverted ^ t0 > 1 ) ? q : v;
final Vector2dOld midpoint = pu.add( qv ).divide( 2.0 );
- point.x = midpoint.x;
- point.y = midpoint.y;
+ point.setX( midpoint.x );
+ point.setY( midpoint.y );
}
} else {
// Calculate t and u
@@ -174,8 +176,8 @@ public class Util {
if ( t >= 0 && t <= 1 && u >= 0 && u <= 1 ) {
// Calculate the point of intersection
final Vector2dOld intersection = r.multiply( t ).add( p );
- point.x = intersection.x;
- point.y = intersection.y;
+ point.setX( intersection.x );
+ point.setY( intersection.y );
} else {
// No intersection, so calculate the closest point between the two segments
// We have t and u, which we can use to find the closest endpoints
@@ -185,8 +187,8 @@ public class Util {
final Vector2dOld nearP = r.multiply( nearT ).add( p );
final Vector2dOld nearQ = s.multiply( nearU ).add( q );
- point.x = ( nearP.x + nearQ.x ) / 2.0;
- point.y = ( nearP.y + nearQ.y ) / 2.0;
+ point.setX( ( nearP.x + nearQ.x ) / 2.0 );
+ point.setY( ( nearP.y + nearQ.y ) / 2.0 );
}
}
return point;
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dComparator.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dComparator.java
index b278563..7ba7c21 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dComparator.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dComparator.java
@@ -2,6 +2,8 @@ package com.aaaaahhhhhhh.bananapuncher714.tess4j;
import java.util.Comparator;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d;
+
public abstract class Vector2dComparator implements Comparator< Vector2d > {
public abstract double compareX( Vector2d a, Vector2d b );
public abstract double compareY( Vector2d a, Vector2d b );
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dOld.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dOld.java
index ee78593..a2da2b5 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dOld.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dOld.java
@@ -1,5 +1,7 @@
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
+
public class Vector2dOld {
final double x;
final double y;
diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vertex.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vertex.java
index b9c4afa..c3d7730 100644
--- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vertex.java
+++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vertex.java
@@ -1,5 +1,7 @@
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
+import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d;
+
public class Vertex {
private HalfWing wing;
private Vector2d position;