diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest.java deleted file mode 100644 index ff4ca7e..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest.java +++ /dev/null @@ -1,418 +0,0 @@ -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.List; - -import javax.swing.JFrame; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; - -import org.bukkit.util.Vector; - -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.Tess4j; -import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple; -import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple.GluWindingRule; -import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.WindingRuleSimple; - -public class MeshingTest 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 = 400; // 600 - private int centerY = 1000; // 400 - - private Graphics g; - - private int scale = 10; - - private List< Polygon > data; - - 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() ) { - List< Polygon > polys = mesh( file ); - SwingUtilities.invokeLater( new Runnable() { - @Override - public void run() { - new MeshingTest( polys ); - } - } ); - break; - } - } else { - System.err.println( "No such directory exists: " + CHUNK_DIR ); - } - } - - private static List< Polygon > 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 ); - } - } - - System.out.println( "Planes: " + builder.planes.size() ); - for ( Plane plane : builder.planes ) { - if ( plane.polygons.size() > 100 ) { - draw = plane; - break; - } - } - - for ( Plane plane : builder.planes ) { - try { - Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); - for ( Polygon poly : plane.polygons ) { - tess4j.addPolygon( poly, new WindingRuleSimple() ); - } - tess4j.tessellate(); - } catch ( IllegalStateException e ) { - e.printStackTrace(); - - draw = plane; -// Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); -// tess4j.setDebug( true ); -// System.out.println( "Polygon count: " + plane.polygons.size() ); -// for ( Polygon poly : plane.polygons ) { -// tess4j.addPolygon( poly, new WindingRuleSimple() ); -// } -// tess4j.tessellate(); - } - } - - if ( draw != null ) { - System.out.println( "Norm:\t" + draw.normal ); - System.out.println( "Ref:\t" + draw.point ); - System.out.println( "Size:\t" + draw.polygons.size() ); - - Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); - tess4j.setDebug( true ); - for ( int i = 0; i < draw.polygons.size(); i++ ) { - if ( i >= 4 ) { - break; - } - - Polygon poly = draw.polygons.get( i ); - - System.out.println( "Adding polygon " + poly.getPoints().size() ); - for ( Point point : poly.getPoints() ) { - System.out.println( point.getX() + ", " + point.getY() ); - } - - tess4j.addPolygon( poly, new WindingRuleSimple() ); - } - - long start = System.currentTimeMillis(); - tess4j.tessellate(); - long end = System.currentTimeMillis(); - System.out.println( "Took " + ( end - start ) + "ms" ); - - return tess4j.getPolygons(); - } else { - System.out.println( "No data!" ); - } - - return new ArrayList< Polygon >(); - } - - 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( p7 ); - facet.points.add( p8 ); - 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 MeshingTest( List< Polygon > polys) { - data = polys; - - 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; - -// Vertex v = null; -// for ( int i = 0; i < 100; i++ ) { -// double deg = Math.random() * 360; -// double dist = Math.random() * 100 + 100; -// double x = dist * Math.cos( Math.toRadians( deg ) ); -// double y = dist * Math.sin( Math.toRadians( deg ) ); -// HalfWing edge = new HalfWing(); -// edge.getDest().setPosition( new Vector2d( x, y ) ); -// if ( v == null ) { -// v = edge.getOrigin(); -// } else { -// HalfWing.splice( edge, v.getWing() ); -// } -// } -// v.update(); -// -// HalfWing edge = v.getWing(); -// int i = 0; -// double ic = 0; -// do { -// Vector2d dest = edge.getDest().getPosition(); -// g.drawLine( centerX, centerY, ( int ) dest.getX() + centerX, ( int ) dest.getY() + centerY ); -// dest = dest.normalized(); -// g.drawString( i++ + "", ( int ) ( dest.x * 200 * ( ic + 1.1 ) ) + centerX, ( int ) ( dest.y * 200 * ( ic + 1.1 ) ) + centerY ); -// ic = ( ic + 0.07 ) % .7; -// edge = edge.getPrev(); -// } while ( edge != v.getWing() ); -// -// v.sort(); -// -// g.drawLine( centerX + 700, centerY, centerX + 700, centerY - 400 ); -// edge = v.getWing(); -// i = 0; -// ic = 0; -// do { -// Vector2d dest = edge.getDest().getPosition(); -// g.drawLine( centerX + 700, centerY, ( int ) dest.getX() + centerX + 700, ( int ) dest.y + centerY ); -// dest = dest.normalized(); -// g.drawString( i++ + "", ( int ) ( dest.x * 200 * ( ic + 1.1 ) ) + centerX + 700, ( int ) ( dest.y * 200 * ( ic + 1.1 ) ) + centerY ); -// ic = ( ic + 0.07 ) % .7; -// edge = edge.getPrev(); -// } while ( edge != v.getWing() ); - - List< Polygon > polygons = data; - - System.out.println( "Polygon count: " + polygons.size() ); - System.out.println( polygons.size() ); - for ( Polygon p : polygons ) { - List< Point > points = p.getPoints(); -// System.out.println( points.size() ); - int size = points.size(); - int[] xPoints = new int[ size ]; - int[] yPoints = new int[ size ]; - - for ( int i = 0; i < points.size(); i++ ) { - Point point = points.get( i ); - -// 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.getX() * scale ) + centerX; - yPoints[ i ] = 100 - ( int ) ( point.getY() * scale ) + centerY; - } - - g.setColor( new Color( ( int ) ( Math.random() * 0xFFFFFF ) ) ); - g.fillPolygon( xPoints, yPoints, size ); - } - - g.setColor( Color.BLACK ); - for ( Polygon p : polygons ) { - 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.getX() * scale ) + centerX - ( int ) diff, 100 - ( int ) ( point.getY() * scale ) + centerY - ( int ) diff, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) ); - } - } - - g.drawLine( centerX, 0, centerX, 2000 ); - g.drawLine( 0, centerY + 100, 2000, centerY + 100 ); - -// 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/tess4j/EdgeCollection.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/EdgeCollection.java deleted file mode 100644 index 193b33f..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/EdgeCollection.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiFunction; - -/* - * A primitive sorted linked list of edges - * - O(n) insert, search - * - O(1) upper, lower, remove - */ -public class EdgeCollection< T > implements Iterable< T > { - Map< T, Node > quickMap = new ConcurrentHashMap< T, Node >(); - Node head = new Node( null ); - - BiFunction< T, T, Boolean > isGreaterThan; - - public EdgeCollection( BiFunction< T, T, Boolean > compare ) { - isGreaterThan = compare; - } - - 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; - } - - // Not necessary, for now -// @Override -// public void remove() { -// T val = current.value; -// current = current.lower; -// EdgeCollection.this.remove( val ); -// } - }; - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/EdgeDict.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/EdgeDict.java deleted file mode 100644 index b84347f..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/EdgeDict.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Supplier; - -/** - * This edge dictionary is a loosely sorted collection of regions that are organized by the _current_ event vertex, - * and its relationship with the upper edge of each region. Regions are sorted on insertion, and stored in a doubly - * linked list thereafter. Searching for a region entails moving backwards through the list starting at the tail - * until a region which matches the sorting method is found. - * - * There are no checks in place to prevent undefined behavior of any kind. - * - * Regions are sorted in ascending order. - * - * Searching for a region is done from head to tail, until a region that is greater than or equal has been found. - */ -public class EdgeDict { - Map< RegionOld, Node > quickMap = new HashMap< RegionOld, Node >(); - /* - * The comparator function is responsible for determining whether a region is less than or equal to another - * - * Given o1 and o2, return true if o1 is less than or equal to o2 - */ - BiFunction< RegionOld, RegionOld, Boolean > comparator; - Node head; - - public EdgeDict( Supplier< VertexOld > supplier ) { - head = new Node( null ); - - head.after = head; - head.before = head; - - comparator = ( o1, o2 ) -> { - VertexOld vert = supplier.get(); - HalfEdge e1 = o1.upperEdge; - HalfEdge e2 = o2.upperEdge; - - // Check if the left vertex is the current event - if ( e1.sym().origin == vert ) { - if ( e2.sym().origin == vert ) { - // Compare right vertices - // Contrived, why not check slope instead? - // Edge case is if slope is infinity - if ( e1.origin.lessThanOrEqualTo( e2.origin ) ) { - // Check if the right vertex is below the edge - return e1.origin.compareTo( e2.sym().origin, e2.origin ) <= 0; - } - return e2.origin.compareTo( e1.sym().origin, e1.origin ) >= 0; - } - return vert.compareTo( e2.sym().origin, e2.origin ) <= 0; - } - - if ( e2.sym().origin == vert ) { - return vert.compareTo( e1.sym().origin, e1.origin ) >= 0; - } - - // Measure the distance and see if one is shorter - double d1 = vert.verticalDistance( e1.sym().origin, e1.origin ); - double d2 = vert.verticalDistance( e2.sym().origin, e2.origin ); - - return d1 >= d2; - }; - } - - public RegionOld above( RegionOld region ) { - return quickMap.get( region ).after.region; - } - - public RegionOld below( RegionOld region ) { - return quickMap.get( region ).before.region; - } - - public void insert( RegionOld region ) { - // Insert from the back - insertBefore( null, region ); - } - - public void remove( RegionOld region ) { - Node node = quickMap.remove( region ); - if ( node != null ) { - node.before.after = node.after.before; - } - region.upperEdge.region = null; - } - - public void insertBefore( RegionOld startRegion, RegionOld region ) { - Node node = new Node( region ); - quickMap.put( region, node ); - - // Start at the tail if null - Node temp = startRegion == null ? head : quickMap.get( startRegion ); - // Look for a region that is less than or equal to region, and insert after - - do { - temp = temp.before; - } while ( temp.region != null && !comparator.apply( temp.region, region ) ); - - node.after = temp.after; - node.after.before = node; - temp.after = node; - node.before = temp; - } - - // Similar to above - public RegionOld search( RegionOld region ) { - Node temp = head; - - // Find the first region that is greater than or equal to region - do { - temp = temp.after; - } while ( temp.region != null && !comparator.apply( region, temp.region ) ); - - return temp.region; - } - - protected class Node { - Node before; - Node after; - RegionOld region; - - Node( RegionOld region ) { - this.region = region; - } - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Face.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Face.java deleted file mode 100644 index 7a9c353..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Face.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -public class Face { - // An edge that is attached to this face - HalfEdge edge; - boolean inside; - - public Face( HalfEdge edge ) { - this.edge = edge; - } - - public void assign() { - HalfEdge temp = edge; - do { - temp.face = this; - temp = temp.nextLeft; - } while ( temp != edge ); - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/HalfEdge.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/HalfEdge.java deleted file mode 100644 index dd66224..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/HalfEdge.java +++ /dev/null @@ -1,249 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -public class HalfEdge { - // The half edge opposite of this one - HalfEdge opposite; - - // The vertex that this half edge extends from - VertexOld origin; - // The face to the left of this edge - Face face; - - // The next edge CCW around the origin - // Keeps the same origin - HalfEdge nextOrigin; - // The next edge CCW around the left face - // The destination becomes the origin - HalfEdge nextLeft; - - RegionOld region; - int winding; - - private HalfEdge() { - origin = new VertexOld( this ); - nextOrigin = this; - } - - // The opposite, or symmetrical half edge to this one - public HalfEdge sym() { - return opposite; - } - - public HalfEdge prevOrigin() { - return sym().nextLeft; - } - - public HalfEdge prevR() { - return sym().nextOrigin; - } - - public HalfEdge nextD() { - return prevR().sym(); - } - - // Positive means left to right, aka the destination > origin - public boolean isPositiveEdge() { - return origin.lessThanOrEqualTo( opposite.origin ); - } - - @Override - public String toString() { - return "HalfEdge{origin=" + origin + ",opposite=" + opposite.origin + ",nextOrigin=" + nextOrigin.origin + ",nextLeft=" + nextLeft.origin + "}"; - } - - public static HalfEdge makeEdge() { - // TODO Half edges are always made in pairs, should really simplify this and make it cleaner - HalfEdge main = new HalfEdge(); - HalfEdge opposite = new HalfEdge(); - - main.opposite = opposite; - main.nextLeft = opposite; - opposite.opposite = main; - opposite.nextLeft = main; - - Face face = new Face( main ); - main.face = face; - opposite.face = face; - - return main; - } - - public static void swapLinks( HalfEdge e1, HalfEdge e2 ) { - HalfEdge e1Next = e1.nextOrigin; - HalfEdge e2Next = e2.nextOrigin; - - e1Next.sym().nextLeft = e2; - e2Next.sym().nextLeft = e1; - e1.nextOrigin = e2Next; - e2.nextOrigin = e1Next; - } - - /** - * The basic operation for changing the mesh connectivity and topology. It changes the mesh so that - * original.nextOrigin = OLD( destination.nextOrigin ) - * destination.nextOrigin = OLD( original.nextOrigin ) - * where OLD( ... ) means the value before the splice operation - * - * This can have two effects on the vertex structure: - * if original.face == destination.face, then the face is split into two - * if original.face != destination.face, then the two faces are combined into one - * In both cases, destination.face is changed, but original.face is not. - */ - public static void splice( HalfEdge original, HalfEdge destination ) { - boolean mergeVertices = false; - boolean mergeFaces = false; - - // Merging vertices - if ( original.origin != destination.origin ) { - mergeVertices = true; - HalfEdge prev = destination.origin.edge; - HalfEdge dest = prev; - do { - dest.origin = original.origin; - dest = dest.nextOrigin; - } while ( dest != prev ); - } - - // Merging faces - if ( original.face != destination.face ) { - mergeFaces = true; - HalfEdge prev = destination.face.edge; - HalfEdge dest = prev; - do { - dest.face = original.face; - dest = dest.nextLeft; - } while ( dest != prev ); - } - - swapLinks( original, destination ); - - if ( !mergeVertices ) { - // Splitting a vertex into two, so create - // a new vertex with destination as the edge, - // update all edges in the destination loop, - // and set the old vertex edge to this - - // TODO Vertices always need a half edge, constructor? - new VertexOld( destination ).assign(); - original.origin.edge = original; - } - - if ( !mergeFaces ) { - // Splitting a face/loop into two - // Similar to splitting a vertex - new Face( destination ).assign();; - original.face.edge = original; - } - } - - /** - * Delete the edge. There are several cases: - * if edge.leftFace != edge.rightFace, then two faces/loops are joined into one - * else, one loop is being split into two, and the new loop will contain the opposite vertex - * This function could be implemented as two calls to splice, but it would be less efficient - */ - public static void delete( HalfEdge edge ) { - boolean mergeFaces = false; - HalfEdge sym = edge.sym(); - - if ( edge.face != sym.face ) { - mergeFaces = true; - HalfEdge original = edge.face.edge; - HalfEdge dest = original; - do { - dest.face = sym.face; - dest = dest.nextLeft; - } while ( dest != original ); - } - - if ( edge.nextOrigin == edge ) { - HalfEdge original = edge.origin.edge; - HalfEdge dest = original; - do { - dest.origin = null; - dest = dest.nextOrigin; - } while ( dest != original ); - } else { - sym.face.edge = edge.prevOrigin(); - edge.origin.edge = edge.nextOrigin; - - splice( edge, edge.prevOrigin() ); - if ( !mergeFaces ) { - // Creating a new face - new Face( edge ).assign(); - } - } - - if ( sym.nextOrigin == sym ) { - sym.face.assign(); - sym.origin.assign(); - } else { - edge.face.edge = sym.prevOrigin(); - sym.origin.edge = sym.nextOrigin; - splice( sym, sym.prevOrigin() ); - } - } - - public static HalfEdge addEdgeVertex( HalfEdge orig ) { - HalfEdge edge = makeEdge(); - HalfEdge sym = edge.sym(); - - swapLinks( edge, orig.nextLeft ); - - edge.origin = orig.sym().origin; - - VertexOld vert = new VertexOld( sym ); - HalfEdge dest = sym; - do { - dest.origin = vert; - dest = dest.nextOrigin; - } while ( dest != sym ); - sym.face = orig.face; - edge.face = orig.face; - - return edge; - } - - public static HalfEdge splitEdge( HalfEdge edge ) { - HalfEdge tempEdge = addEdgeVertex( edge ); - HalfEdge sym = tempEdge.sym(); - - swapLinks( edge.sym(), edge.sym().prevOrigin() ); - swapLinks( edge.sym(), sym ); - - edge.sym().origin = sym.origin; - sym.sym().origin.edge = sym.sym(); - sym.sym().face = edge.sym().face; - - return sym; - } - - public static HalfEdge connect( HalfEdge e1, HalfEdge e2 ) { - boolean mergeFaces = false; - HalfEdge edge = makeEdge(); - - HalfEdge sym = edge.sym(); - - if ( e1.face != e2.face ) { - mergeFaces = true; - e2.face.edge = e1; - e2.face.assign(); - } - - swapLinks( edge, e1.nextLeft ); - swapLinks( sym, e2 ); - - edge.origin = e1.sym().origin; - sym.origin = e2.origin; - edge.face = e1.face; - sym.face = e1.face; - - e1.face.edge = sym; - - if ( !mergeFaces ) { - new Face( edge ).assign(); - } - - return edge; - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/HalfWing.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/HalfWing.java deleted file mode 100644 index 3dfc455..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/HalfWing.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d; - -public class HalfWing { - protected HalfWing sym; - protected HalfWing prev; - protected HalfWing next; - - protected Vertex origin; - - public HalfWing() { - init(); - - new HalfWing( this ); - } - - private HalfWing( HalfWing 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; - } - - public HalfWing setOrigin( Vertex vert ) { - origin = vert; - return this; - } - - public HalfWing getSym() { - return sym; - } - - public HalfWing getPrev() { - return prev; - } - - public HalfWing setPrev( HalfWing wing ) { - this.prev = wing; - return this; - } - - public HalfWing getNext() { - return next; - } - - public HalfWing setNext( HalfWing wing ) { - this.next = wing; - return this; - } - - public Vertex getDest() { - return sym.origin; - } - - public Vector2d toVector2d() { - return getDest().getPosition().subtracted( getOrigin().getPosition() ); - } - - public boolean isZero() { - return getOrigin().equals( getDest() ); - } - - // Split this edge in half, and return the new edge - public HalfWing split() { - HalfWing wing = new HalfWing(); - - splice( wing, getNext() ); - - splice( getSym(), getNext() ); - splice( getSym(), wing.getSym() ); - - wing.setOrigin( getDest() ); - wing.getOrigin().setWing( wing ); - - getSym().setOrigin( wing.getDest() ); - getDest().setWing( getSym() ); - - wing.getOrigin().update(); - wing.getDest().update(); - - return wing.getSym(); - } - - public static void splice( HalfWing a, HalfWing b ) { - HalfWing ap = a.prev; - HalfWing bp = b.prev; - - a.prev = bp; - b.prev = ap; - - ap.sym.next = b; - bp.sym.next = a; - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Region.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Region.java deleted file mode 100644 index de3bba6..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Region.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -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/tess4j/RegionOld.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/RegionOld.java deleted file mode 100644 index 448c0d5..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/RegionOld.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -public class RegionOld { - HalfEdge upperEdge; - int windingNumber = 0; - - boolean inside = false; - boolean fixUpperEdge = false; - boolean sentinel = false; - boolean dirty = false; - - public void fixUpperEdge( HalfEdge edge ) { - HalfEdge.delete( upperEdge ); - fixUpperEdge = false; - upperEdge = edge; - edge.region = this; - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Scratch.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Scratch.java deleted file mode 100644 index 30a5e24..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Scratch.java +++ /dev/null @@ -1,416 +0,0 @@ -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 >(); - { - 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 ) ) ); -// -// HalfWing toInsert = new HalfWing(); -// HalfWing first = new HalfWing(); -// -// toInsert.getOrigin().setPosition( new Vector2d( -.95454545454, -1.36363636 ) ); -// toInsert.getDest().setPosition( new Vector2d( 0, -2 ) ); -// first.getOrigin().setPosition( new Vector2d( -1.5, -3 ) ); -// first.getDest().setPosition( new Vector2d( 0, -2 ) ); -// -// toInsert.getOrigin().setPosition( new Vector2d( 0, 0 ) ); -// toInsert.getDest().setPosition( new Vector2d( 2, 0 ) ); -// first.getOrigin().setPosition( new Vector2d( 1, 1 ) ); -// first.getDest().setPosition( new Vector2d( 2, 3 ) ); -// -// System.out.println( greaterThanOrEqualTo( toInsert, first ) ); -// } -// { -// Point[] points = { -// new Point( 1, -1 ), -// new Point( 1, 1 ), -// new Point( -1, 1 ), -// new Point( 0, 1 ), -// new Point( 1, 1 ), -// new Point( 0, -1 ) -// }; -// -// Vertex v = null; -// for ( Point point : points ) { -// HalfWing edge = new HalfWing(); -// edge.getDest().setPosition( new Vector2d( point.x, point.y ) ); -// if ( v == null ) { -// v = edge.getOrigin(); -// } else { -// HalfWing.splice( edge, v.getWing() ); -// } -// } -// v.update(); -// -// System.out.println( "Before sort" ); -// HalfWing edge = v.getWing(); -// do { -// System.out.println( edge.getDest() ); -// edge = edge.getPrev(); -// } while ( edge != v.getWing() ); -// -// v.sort(); -// -// System.out.println( "After sort" ); -// edge = v.getWing(); -// do { -// System.out.println( edge.getDest() ); -// edge = edge.getPrev(); -// } while ( edge != v.getWing() ); -// } -// { -// HalfWing a = new HalfWing(); -// HalfWing b = new HalfWing(); -// -// b.getSym().setOrigin( a.getDest() ); -// -// a.getOrigin().setPosition( new Vector2d( -1, 1 ) ); -// a.getSym().getOrigin().setPosition( new Vector2d( 1, 1 ) ); -// b.getOrigin().setPosition( new Vector2d( 1, 0 ) ); -// -// System.out.println( greaterThanOrEqualTo( b, a ) ); -// } -// { -// Vertex v = null; -// for ( int i = 0; i < 100; i++ ) { -// double deg = Math.random() * 360; -// double dist = Math.random() * 100 + 100; -// double x = dist * Math.cos( Math.toRadians( deg ) ); -// double y = dist * Math.sin( Math.toRadians( deg ) ); -// HalfWing edge = new HalfWing(); -// edge.getDest().setPosition( new Vector2d( x, y ) ); -// if ( v == null ) { -// v = edge.getOrigin(); -// } else { -// HalfWing.splice( edge, v.getWing() ); -// } -// } -// v.update(); -// -// System.out.println( "Before sort" ); -// HalfWing edge = v.getWing(); -// do { -// System.out.println( edge.getDest() ); -// edge = edge.getPrev(); -// } while ( edge != v.getWing() ); -// -// v.sort(); -// -// System.out.println( "After sort" ); -// edge = v.getWing(); -// do { -// System.out.println( edge.getDest() ); -// edge = edge.getPrev(); -// } while ( edge != v.getWing() ); -// } - -// { -// Vector2d a1 = new Vector2d( 0, 0 ); -// Vector2d a2 = new Vector2d( 0, 5 ); -// Vector2d b1 = new Vector2d( 0, 0 ); -// Vector2d b2 = new Vector2d( 0, 5 ); -// System.out.println( isGreaterThan( a1, a2, b1, b2 ) ); -// } -// HalfEdge upper = HalfEdge.makeEdge(); -// HalfEdge lower = HalfEdge.makeEdge(); -// -// upper.sym().origin = lower.sym().origin; -// upper.sym().origin.x = -1; -// upper.sym().origin.y = -1; -// -// upper.origin.x = 1; -// upper.origin.y = 1; -// lower.origin.x = 2; -// lower.origin.y = 2; -// -// System.out.println( "Right spliced? " + checkForRightSplice( upper, lower ) ); -// System.out.println( upper ); -// System.out.println( lower ); -// System.out.println( upper.prevOrigin() ); - -// { -// Vertex v1 = new Vertex( null ); -// v1.x = 0; -// v1.y = 0; -// Vertex v2 = new Vertex( null ); -// v2.x = 5; -// v2.y = 0; -// Vertex v3 = new Vertex( null ); -// v3.x = 1; -// v3.y = 1; -// Vertex v4 = new Vertex( null ); -// v4.x = 6; -// v4.y = -4; -// -// System.out.println( "Colinear and overlapping" ); -// System.out.println( Tessellator.intersection( v1, v2, v3, v4 ).x ); -// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).x ); -// } -// { -// Vertex v1 = new Vertex( null ); -// v1.x = 0; -// v1.y = 0; -// Vertex v2 = new Vertex( null ); -// v2.x = 5; -// v2.y = 0; -// Vertex v3 = new Vertex( null ); -// v3.x = 6; -// v3.y = 0; -// Vertex v4 = new Vertex( null ); -// v4.x = 10; -// v4.y = 0; -// -// System.out.println( "Colinear but not overlapping" ); -// System.out.println( Tessellator.intersection( v1, v2, v3, v4 ).x ); -// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).x ); -// } -// { -// Vertex v1 = new Vertex( null ); -// v1.x = 0; -// v1.y = 3; -// Vertex v2 = new Vertex( null ); -// v2.x = 5; -// v2.y = 3; -// Vertex v3 = new Vertex( null ); -// v3.x = -6; -// v3.y = 6; -// Vertex v4 = new Vertex( null ); -// v4.x = 2.5; -// v4.y = 6; -// -// System.out.println( "Parallel and overlapping but not colinear" ); -// System.out.println( Tessellator.intersection( v1, v2, v3, v4 ).x ); -// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).x ); -// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).y ); -// } -// { -// Vertex v1 = new Vertex( null ); -// v1.x = 0; -// v1.y = 3; -// Vertex v2 = new Vertex( null ); -// v2.x = 5; -// v2.y = 3; -// Vertex v3 = new Vertex( null ); -// v3.x = -100; -// v3.y = 6; -// Vertex v4 = new Vertex( null ); -// v4.x = -1; -// v4.y = 6; -// -// System.out.println( "Parallel but not overlapping nor colinear" ); -// System.out.println( Tessellator.intersection( v1, v2, v3, v4 ).x ); -// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).x ); -// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).y ); -// } - -// { -// HalfEdge root = HalfEdge.makeEdge(); -// root.origin.y = -1; -// HalfEdge next = HalfEdge.makeEdge(); -// next.sym().origin.x = 1; -// HalfEdge.swapLinks( root.sym(), next ); -// HalfEdge newEdge = HalfEdge.addEdgeVertex( root ); -// newEdge.sym().origin.x = -1; -// System.out.println( root ); -// System.out.println( root.nextLeft ); -// System.out.println( root.nextLeft.sym() ); -// System.out.println( root.nextLeft.nextLeft.nextLeft.sym() ); -// } -// -// System.out.println( "---" ); -// { -// HalfEdge root = HalfEdge.makeEdge(); -// root.origin.y = -1; -// HalfEdge next = HalfEdge.makeEdge(); -// next.sym().origin.x = 1; -// HalfEdge.swapLinks( root.sym(), next ); -// HalfEdge newEdge = HalfEdge.splitEdge( root ); -// newEdge.origin.x = -1; -// System.out.println( root ); -// System.out.println( root.nextLeft ); -// System.out.println( root.nextLeft.nextLeft ); -// System.out.println( root.nextLeft.nextLeft.nextLeft ); -// } - } - - private static boolean greaterThanOrEqualTo( HalfWing a, HalfWing 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 ( !( a.isPositive() && b.isPositive() ) ) { -// throw new IllegalStateException( "Negative edge detected" ); -// } - - // a1 < a2 - Vector2d a1 = a.getOrigin().getPosition(); - Vector2d a2 = a.getDest().getPosition(); - // b1 < b2 - Vector2d b1 = b.getOrigin().getPosition(); - Vector2d b2 = b.getDest().getPosition(); - - if ( a1.equals( b1 ) ) { - return b2.subtracted( b1 ).cross( a2.subtracted( a1 ) ) >= 0; - } else if ( a1.getX() < b1.getX() ) { - System.out.println( "LESS A" ); - return b1.subtracted( a1 ).cross( a2.subtracted( a1 ) ) >= 0; - } else if ( a1.getX() > b1.getX() ) { - System.out.println( "MORAY" ); - return b2.subtracted( b1 ).cross( a1.subtracted( b1 ) ) >= 0; - } else { - return a1.getY() > b1.getY(); - } - } - - public static boolean isGreaterThan( Vector2dOld a1, Vector2dOld a2, Vector2dOld b1, Vector2dOld b2 ) { - // Invariants: - // a1 < a2 - // b1 < b2 - // a.length > 0 - // b.length > 0 - // a and b do not intersect - // a and b do not overlap - - if ( a1.equals( b1 ) ) { - return b2.subtract( b1 ).cross( a2.subtract( a1 ) ) >= 0; - } else if ( a1.x < b1.x ) { - return b1.subtract( a1 ).cross( a2.subtract( a1 ) ) >= 0; - } else if ( a1.x > b1.x ) { - return a1.subtract( b1 ).cross( b2.subtract( b1 ) ) >= 0; - } else { - return a1.y > b1.y; - } - } - - public static boolean checkForRightSplice( HalfEdge upEdge, HalfEdge downEdge) { - // Check if the right vertex of the upper edge is to the left or below the lower edge's right vertex - if ( upEdge.origin.lessThanOrEqualTo( downEdge.origin ) ) { - // Check if the right vertex is above the lower edge - if ( upEdge.origin.compareTo( downEdge.sym().origin, downEdge.origin ) > 0 ) { - // If so, no splicing is necessary - return false; - } - - // If the right vertex of the upper edge is not equal to the right vertex of the lower edge - if ( !upEdge.origin.equals( downEdge.origin ) ) { - // Split the lower edge in two, with the new edge on the right - HalfEdge.splitEdge( downEdge.sym() ); - // Move the right vertex of the lower edge to the - HalfEdge.splice( upEdge, downEdge.prevOrigin() ); - - } else if ( upEdge.origin != downEdge.origin ) { - // Merge the two vertices if they are not already the same - HalfEdge.splice( downEdge.prevOrigin(), upEdge ); - } - } else { - // Check if the lower edge's right vertex is on or below the upper edge - if ( downEdge.origin.compareTo( upEdge.sym().origin, upEdge.origin ) <= 0 ) { - return false; - } - - // Split the upper edge - HalfEdge.splitEdge( upEdge.sym() ); - // Move the upper edge to the lower edge's vertex - HalfEdge.splice( downEdge.prevOrigin(), upEdge ); - - } - return true; - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4j.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4j.java deleted file mode 100644 index 611b9b5..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4j.java +++ /dev/null @@ -1,1853 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -import java.util.ArrayList; -import java.util.Arrays; -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.Stack; -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; - -// Tess4j(name subject to change) -public class Tess4j< T extends Region > { - // Change at your own peril - public static final double TOLERANCE = 0; - public static final double COLLINEAR_TOLERANCE = 1e-7; - public static final double VERTEX_TOLERANCE = 1e-7; - - private boolean debug = false; - - /* - * Represents the current stage that this tessellator is currently in - */ - private Tess4jStage tessStage; - private Vector2dComparator comparator = new Vector2dComparator() { - @Override - public double compareX( Vector2d a, Vector2d b ) { - return a.getX() - b.getX(); - } - - @Override - public double compareY( Vector2d a, Vector2d b ) { - return a.getY() - b.getY(); - } - }; - - /* - * Use a custom sorting direction, of sorts. Not sure if it works - */ - public Tess4j( Supplier< T > voidSupplier, Vector2dComparator customSort ) { - this( voidSupplier ); - - this.comparator = customSort; - } - - public Tess4j( Supplier< T > voidSupplier ) { - tessStage = new StagePreTess( voidSupplier ); - } - - public void addPolygon( Polygon polygon, WindingRule< T > rule ) { - StagePreTess stage = tessStage.get(); - Queue< Vertex > queue = stage.vertexQueue; - Map< HalfWing, WindingRule< T > > windingMap = stage.windingMap; - - // Populate the vertex queue - // TODO Ensure that the polygon has at least 3 points? - HalfWing wing = null; - for ( Point point : polygon.getPoints() ) { - if ( wing == null ) { - // Make a self loop with one edge and one vertex - wing = new HalfWing(); - HalfWing.splice( wing, wing.getSym() ); - wing.getOrigin().update(); - } else { - // Split the previous edge in 2 and set the new edge as the current edge - wing = wing.split(); - } - - // Set the origin - wing.getOrigin().setPosition( new Vector2d( point ) ); - - // TODO Temporary angle check -// final double angle = 128; -// double cos = Math.cos( angle ); -// double sin = Math.sin( angle ); -// double x = point.x * cos - point.y * sin; -// double y = point.x * sin + point.y * cos; -// wing.getOrigin().setPosition( new Vector2d( x, y ) ); - - // Update the edge winding rule - windingMap.put( wing, rule ); - windingMap.put( wing.getSym(), rule.inverse() ); - - queue.add( wing.getOrigin() ); - } - } - - 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 - */ - consolidateAdjacentCollinear(); - - /* - * Attempt to reduce the total amount of triangles by merging edges and vertices on the same line - * - * Since the minimum amount of triangles required to triangulate a polygon is n - 2, where n is the amount of vertices, - * we want to remove as many vertices as possible. A polygon may even be split into multiple polygons, if doing so would reduce - * the overall amount of triangles, as given by the formula: total = summation( polygon.vertices - 2 ) - */ -// reduceVertices(); - - /* - * TODO - * For now, we can skip reducing the vertice count and assume that it's just going to be kind of greedy... This is clearly - * suboptimal in terms of triangles generated, but oh well. - */ - - /* - * Given a list of simple polygons in the form of half wings, further divide them into monotone polygons if they are not already. - * Should take approximately O(nlogn) time - */ -// partitionMonotone(); - - /* - * Triangulate a list of monotone regions in linear time - */ -// triangulate(); - -// advance(); - } - - private void generateRegions() { - StageRegionGeneration stage = advance(); - Queue< Vertex > queue = stage.getQueue(); - EdgeCollection< HalfWing > edgeCollection = stage.getCollection(); - Map< HalfWing, WindingRule< T > > windingMap = stage.getWindingMap(); - Map< HalfWing, T > regionMap = stage.regionMap; - Set< HalfWing > interiorWings = stage.interior; - Set< HalfWing > inverseInteriorWings = stage.inverseInterior; - - /* - * The queue holds a collection of vertices, sorted by the comparator - * There may be vertices that must be merged together, so do a quick loop - * over all the vertices and merge any that are close enough together. - * - * Remember, vertices must be sorted by x, then y, as specified by the comparator - */ - Vertex event; - while ( ( event = queue.poll() ) != null ) { - // Check for any vertices that are nearby, in x and y direction and merge if similar - Vector2d vec = event.getPosition(); - for ( Iterator< Vertex > it = queue.iterator(); it.hasNext(); ) { - Vertex v = it.next(); - - Vector2d vPos = v.getPosition(); - // Once we go past a certain difference in X value, we know that any further vertices - // cannot be similar to the current event vertex, so break - if ( comparator.compareX( vPos, vec ) > VERTEX_TOLERANCE ) { - break; - } - - // This assumes that the vertices are NOT directly connected - if ( isSimilar( vec, vPos ) ) { - // Merge vertices that are the same as the event vertex - HalfWing.splice( event.getWing(), v.getWing() ); - - it.remove(); - } - } - - // Update all attached edges - event.update(); - - // Resolve any colinear intersections with this vertex first - resolveVertexIntersections( event ); - - // Vertices should start with an even number of edges, since they come in pairs - // After sorting them this one time, they should not need to be mass-sorted again - // Any precise modifications should keep the order - List< HalfWing > sorted = sort( event ); - if ( sorted.size() % 2 == 1 ) { - throw new IllegalStateException( "Invalid amount of edges for vertex" ); - } - - // Remove any negative edges from the edge collection before doing anything else - // This helps with proper ordering when inserting new edges and prevents inconsistencies - List< HalfWing > wings = new LinkedList< HalfWing >(); - for ( HalfWing wing : sorted ) { - if ( !isPositive( wing ) ) { - if ( !edgeCollection.remove( wing.getSym() ) ) { - throw new IllegalStateException( "Left going edge was not added to the edge collection" ); - } - } else { - if ( debug ) { - System.out.println( wing.hashCode() ); - System.out.println( "Adding " + wing.getOrigin().getPosition() + " to " + wing.getDest().getPosition() ); - } - wings.add( wing ); - } - } - - // All edges should be positive edges at this point - for ( HalfWing wing : wings ) { - // Resolve any intersections before inserting them into the edge collection - // The only intersections possible at this moment are: - // - An intersection in the middle of 2 edges - // - Colinear edges with the same left endpoint - resolveEdgeIntersections( wing ); - - // The issue with resolving edge intersections is that if the edge is - // barely positive, it can actually make it negative - - if ( !isPositive( wing ) ) { - System.out.println( wing.getOrigin().getPosition() + " to " + wing.getDest().getPosition() ); - throw new IllegalStateException( "Edge must be positive!" ); - } - - // Add the edge to the edge collection if it is a right-going edge - // Insert it as a positive edge - // Useful since if we split the edge, it does not have to be re-inserted in to the edge collection - edgeCollection.insert( wing ); - } - - // Get the default outer region - T region = stage.getDefaultRegion(); - - // Start marking interior/exterior regions as we work our way up through the edge collection - // This is in preparation for the next stage - // Keep track of whether the upper or lower region is an interior/exterior region - for ( HalfWing wing : edgeCollection ) { - WindingRule< T > rule = windingMap.get( wing ); - - boolean preInterior = region.isInterior(); - region = rule.apply( region ); - if ( regionMap.containsKey( wing ) ) { - T old = regionMap.get( wing ); - - if ( !old.equals( region ) ) { - // If this edge for some reason has calculated a different region - throw new IllegalStateException( "Inconsistent winding rules" ); - } else if ( preInterior ^ inverseInteriorWings.contains( wing ) ) { - // If the previous region's interior status does not match what was recorded previously - throw new IllegalStateException( "Inconsistent lower winding rules" ); - } - } else { - regionMap.put( wing, region ); - if ( region.isInterior() ) { - interiorWings.add( wing ); - } - - if ( preInterior ) { - inverseInteriorWings.add( wing ); - } - } - } - - // Now that all edges have been inserted into the edge collection, and all regions have been properly marked, - // we can add an extra step to remove any edges that have the same endpoints... - // Optional step - removeEqualEdges( event ); - - // Any new vertices must not be less than or equal to the current vertex - Vertex next = queue.peek(); - if ( next != null && compare( next, event ) < 1 ) { - throw new IllegalStateException( "Vertex less than event vertex detected!" ); - } - } - } - - private void consolidateAdjacentCollinear() { - StageColinearConsolidation stage = advance(); - Set< HalfWing > polygons = stage.polygons; - Set< List< HalfWing > > newPolygons = stage.newPolygons; - /* - * A short stage to combine colinear edges - */ - - // First join any adjacent colinear edges - for ( HalfWing wing : polygons ) { - List< HalfWing > wings = new LinkedList< HalfWing >(); - Set< HalfWing > checked = new HashSet< HalfWing >(); - - // Fast forward to the start of a colinear chain, rather than the middle of one - while ( Math.abs( wing.toVector2d().normalized().cross( wing.getPrev().getSym().toVector2d().normalized() ) ) < COLLINEAR_TOLERANCE ) { - wing = wing.getNext(); - } - - int count = 0; - int reduced = 0; - - while ( !checked.contains( wing ) ) { - count++; - HalfWing next = wing.getNext(); - - Vector2d a = wing.toVector2d().normalized(); - Vector2d b = next.toVector2d().normalized(); - - // Colinear - if ( Math.abs( a.cross( b ) ) <= COLLINEAR_TOLERANCE ) { - HalfWing after = next.getNext(); - - if ( wing.getOrigin().equals( after.getOrigin() ) ) { - throw new IllegalStateException( "Invalid edge with length of 0" ); - } - - // Unlink next from its destination - HalfWing.splice( next.getSym(), after ); - - // Unlink wing from next - HalfWing.splice( wing.getSym(), next ); - - // Set next origin to something else - next.getOrigin().setWing( next.getPrev() ); - - // Unlink next from the current vertex - HalfWing.splice( next, next.getSym().getNext() ); - - // Link wing and the wing after next together - HalfWing.splice( wing.getSym(), after ); - - // Set the destination vertex - wing.getSym().setOrigin( after.getOrigin() ); - wing.getDest().update(); - after.getOrigin().setWing( after ); - - reduced++; - } else { - wings.add( wing ); - checked.add( wing ); - wing = wing.getNext(); - } - } - - newPolygons.add( wings ); - } - } - - private void reduceVertices() { - StageVertexReduction stage = advance(); - Set< List< HalfWing > > polygons = stage.polygons; - /* - * The next step is to remove as many vertices as possible, before partitioning into monotone polygons - * All polygons should be simple interior region polygons at this point - * - * Reducing vertex count can happen in a variety of ways: - * - Joining non intersecting colinear edges - * - Joining an edge and a vertex - * - Joining 3 vertices in a row - * Each polygon should be independent of any other polygon, aside from sharing a common vertex - * If parallelizing, then each vertex should be made independent, or something like that - * - * This is a polynomial time operation, but can be skipped if undesired... - * - * For each edge, scan ahead to locate the first edge that it intersects with or is colinear, as well as any vertices - * Stop if it hits an edge, continue if it hits a vertex but does not hit a wall - * Scan backwards for only vertices as well - * - * How to resolve if a vertex and edge are colinear? - */ - - /* - * Given a vertex, find all other vertices that it intersects with - * Check if it can actually intersect with that vertex, or if it is blocked - * Simple solution: - * For each vertex - * Scan each other vertex - * See if it intersects with that vertex - * If so, then add it to a list for that vector - * After scanning each vertex, remove any lists with only 1 intersection - * At the end, a list of vertices with potential lines - * For each line in the list, get a list of lines that it intersects with - * For each group of lines that all intersect, calculate the - * O(n2) minimum? Since there can be at most O(n2) colinear lines - */ - - class IntersectionPair { - HalfWing start; - HalfWing end; - HalfWing otherStart; - HalfWing otherEnd; - - public IntersectionPair( HalfWing start, HalfWing end, HalfWing otherStart, HalfWing otherEnd ) { - this.start = start; - this.end = end; - this.otherStart = otherStart; - this.otherEnd = otherEnd; - } - } - - class VertexIntersection { - List< HalfWing > colinear = new ArrayList< HalfWing >(); - List< IntersectionPair > pairs = new LinkedList< IntersectionPair >(); - - public VertexIntersection( HalfWing wing ) { - colinear.add( wing ); - } - - public void addIntersection( IntersectionPair pair ) { - pairs.add( pair ); - } - } - - class PathingVertex { - HalfWing origin; - Vector2d right; - - Map< Vector2d, VertexIntersection > colinear = new HashMap< Vector2d, VertexIntersection >(); - - PathingVertex( HalfWing origin ) { - this.origin = origin; - right = origin.toVector2d().normalize(); - } - - Vector2d getPosition() { - return origin.getOrigin().getPosition(); - } - - void addWing( HalfWing wing ) { - for ( Entry< Vector2d, VertexIntersection > entry : colinear.entrySet() ) { - Vector2d vec = entry.getKey(); - if ( Math.abs( right.cross( vec ) ) <= COLLINEAR_TOLERANCE ) { - entry.getValue().colinear.add( wing ); - return; - } - } - colinear.put( right, new VertexIntersection( wing ) ); - } - } - - /* - * Create a list of PathingVertices as we loop around the polygon - * If we reach the same vertex again and it is being checked, remove it - * Otherwise, create a new one and add it to the checklist - * For each checked vertex, see if it can see the current vertex - * If it can, then add it to a list for that vertex and vector - * It should be sorted by distance from start - * Do this until we reach the last vertex - * This should prevent opposite redundant vertex lines - * remove any with 1, since that is a regular line - * Should now have a bunch of lines from one vertex to multiple other vertices - * There are duplicates, so reduce - * Now that we have a full list, determine which ones intersect? Or can that be done in a different step... - * Technically that is polynomial time... - * Iterate backwards from the vertex, until we reach the intersection vert - * For each vertex that is not the start or the end - * Check each intersection on that vertex - * If it intersects with a point before the start vertex - * Then it must intersect with this line - * - * A double loop is unavoidable unless the vector can be reversed, such that a colinear line - * How to make it such that exactly opposite vertices are equal, and in constant time? - */ - for ( List< HalfWing > wings : polygons ) { - System.out.println( "Starting at " + wings.get( 0 ).getOrigin() ); - Map< HalfWing, PathingVertex > vertices = new HashMap< HalfWing, PathingVertex >(); - // Keep track of the index of each vertex for constant time access - Map< HalfWing, Integer > indices = new HashMap< HalfWing, Integer >(); - List< PathingVertex > nodes = new ArrayList< PathingVertex >(); - - for ( HalfWing wing : wings ) { - List< Integer > recentlyLinked = new LinkedList< Integer >(); - PathingVertex pathing = vertices.computeIfAbsent( wing, PathingVertex::new ); - - List< Vector2d > added = new LinkedList< Vector2d >(); - int linkedIndex = -1; - for ( PathingVertex v : nodes ) { - linkedIndex++; - // Check if this vertex intersects with the node - Vector2d toWing = wing.getOrigin().getPosition().subtracted( v.getPosition() ).normalize(); - - // TODO How to prevent intersections with unprocessed wings? - // -2 2 1 3 - double cross = v.right.cross( toWing ); - if ( cross < - COLLINEAR_TOLERANCE ) { - continue; - } else if ( cross >= COLLINEAR_TOLERANCE ) { - // Not colinear, and need to update the angle - v.right = toWing; - } else { - // Colinear - } - - addWing: { - // Only add the vertex once per line that passes through it - // That way there are no redundant sub-lines being formed - for ( Vector2d vec : added ) { - if ( Math.abs( v.right.cross( vec ) ) <= COLLINEAR_TOLERANCE ) { - break addWing; - } - } - - // For each vertex, add once per vector - v.addWing( wing ); - added.add( v.right ); - recentlyLinked.add( linkedIndex ); - } - } - - if ( indices.containsKey( wing ) ) { - throw new IllegalStateException( "Already added edge" ); - } - - indices.put( wing, nodes.size() ); - nodes.add( pathing ); - - /* - * Ideally, it should only check for an intersection once, and not have multiple with the same - */ - if ( !recentlyLinked.isEmpty() ) { - for ( int i = 0; i <= recentlyLinked.get( 0 ); i++ ) { - PathingVertex vertex = nodes.get( i ); - - for ( VertexIntersection linked : vertex.colinear.values() ) { - // Check each sub-segment of the linked list - // Each sub-segment is guaranteed to be a unique segment - for ( int j = -1; j < linked.colinear.size() - 1; j++ ) { - HalfWing start = j == -1 ? vertex.origin : linked.colinear.get( j ); - HalfWing end = linked.colinear.get( j + 1 ); - - int startIndex = indices.get( start ); - int endIndex = indices.get( end ); - if ( end != wing ) { - for ( int index : recentlyLinked ) { - if ( startIndex <= index && index < endIndex ) { - linked.pairs.add( new IntersectionPair( start, end, nodes.get( index ).origin, wing ) ); - } - } - } - } - } - } - } - - // Now that the wing has been computed, scan backwards until we reach the lowest vertex that intersects with this node - // This is to map out any intersections, and straighten out the vertices and put them in order - /* - * What exactly is being checked in this case? - * As we iterate over each vertex, for each previous vertex, if a connection is possible then a link is added - * However, it might be the case that between two previous vertices, a connection is made that interferes with the - * connection we just made. So, loop backwards to the most previous connection, and check if there are any that intersect - * along the way. This is O(n2) - * - */ - } - - for ( PathingVertex v : nodes ) { - v.colinear.entrySet().removeIf( e -> e.getValue().colinear.size() == 1 ); - - if ( !v.colinear.isEmpty() ) { - System.out.println( "Checking " + v.getPosition() ); - for ( Entry< Vector2d, VertexIntersection > entry : v.colinear.entrySet() ) { - System.out.println( "\t" + entry.getKey() ); - VertexIntersection intersection = entry.getValue(); - for ( HalfWing w : intersection.colinear ) { - System.out.println( "\t\t" + w.getOrigin() ); - } - System.out.println( "\tOther colinear intersections" ); - for ( IntersectionPair pair : intersection.pairs ) { - System.out.println( "\t" + pair.start.getOrigin() + "\t" + pair.end.getOrigin() + "\tto\t" + pair.otherStart.getOrigin() + "\t" + pair.otherEnd.getOrigin() ); - } - } - } - - // The lists of HalfWings in each vertex contains a unique line segmet comprising of 2 or more vertices - // that intersect with the vertex. There is no particular order of the vertices, therefore care must - // be taken when determining if any sub-segments are valid, vs the entire line as a whole - } - } - } - - private void greedySplitter() { - /* - * Given a partition, split it into as many smaller regions while there are 3 or more colinear points that can be connected - * - * Steps: - * - Locate all colinear points that are not obstructed - * - Remove sets with less than 3 points - * - Get the set of points which has the greatest amount - * - In the case of a tie, select the one with the least amount of other obstructions - * - Split the partition - * - For all colinear sets, split them into one or the other polygon - * - Remove sets with less than 3 points - * - Repeat the procedure until there are no more colinear sets remaining - */ - } - - private void partitionMonotone() { - StageMonotonePartition stage = advance(); - TreeSet< Vertex > vertices = stage.vertices; - Set< HalfWing > interior = stage.interior; - EdgeCollection< HalfWing > edgeCollection = new EdgeCollection< HalfWing >( this::greaterThanOrEqualTo ); - - /* - * Now that vertices have been reduced as much as possible, partition each polygon into a monotone region, and triangulate it, but slowly - */ - - // Helper class to keep track of the immediate upper and lower edges for a given vertex - class MarkedWing { - HalfWing upper; - HalfWing lower; - - public MarkedWing( HalfWing upper, HalfWing lower ) { - this.upper = upper; - this.lower = lower; - } - - public boolean equals( MarkedWing o ) { - return o != null && lower == o.lower && upper == o.upper; - } - } - - // Keep track of all vertices that need to be linked with a left-going edge - Map< Vertex, MarkedWing > leftMarked = new HashMap< Vertex, MarkedWing >(); - // Likewise, keep track of all vertices that need to be linked with a right-going edge - Map< Vertex, MarkedWing > rightMarked = new HashMap< Vertex, MarkedWing >(); - - Set< Vertex > toSort = new HashSet< Vertex >(); - // Sweep each vertex and mark the upper and lower regions - for ( Vertex event : vertices ) { - // Get the left and right wings - List< HalfWing > rightGoing = new LinkedList< HalfWing >(); - List< HalfWing > leftGoing = new LinkedList< HalfWing >(); - { - HalfWing wing = event.getWing(); - do { - if ( isPositive( wing ) ) { - rightGoing.add( wing ); - } else { - leftGoing.add( wing ); - if ( !edgeCollection.remove( wing.getSym() ) ) { - throw new IllegalStateException( "Edge not added to the edge collection" ); - } - } - } while ( ( wing = wing.getPrev() ) != event.getWing() ); - } - - // Create a mark if the vertex has no left xor no right going edges - // AKA, if it prevents the polygon from being monotone - // It should never have no left and no right edges, since - // that means it should have never been added to the queue in the first place - // If there are both left and right edges, then it's a normal edge and does not break monotony - MarkedWing eventMark = null; - if ( rightGoing.isEmpty() ^ leftGoing.isEmpty() ) { - HalfWing tempWing = event.getWing(); - if ( !isPositive( tempWing ) ) { - tempWing = tempWing.getSym(); - } - HalfWing lower = edgeCollection.searchLower( tempWing ); - // Since we are dealing with simple polygons, we can guarantee that - // if the lower edge is an interior edge, then the upper edge must be - // part of the same polygon, and that this point is inside - if ( interior.contains( lower ) ) { - // Get the upper edge of the same polygon - HalfWing upper = edgeCollection.upper( lower ); - - // The current vertex must be between upper and lower, - // and cannot be part of either edge - - // Save this information, for now - eventMark = new MarkedWing( upper, lower ); - } - } 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 ( Iterator< Entry< Vertex, MarkedWing > > it = rightMarked.entrySet().iterator(); it.hasNext(); ) { - Entry< Vertex, MarkedWing > entry = it.next(); - Vertex prev = entry.getKey(); - MarkedWing marked = entry.getValue(); - - HalfWing upper = marked.upper; - HalfWing lower = marked.lower; - - if ( upper.getDest() == event ) { - HalfWing newWing = new HalfWing(); - - newWing.setOrigin( event ); - newWing.getSym().setOrigin( prev ); - - HalfWing.splice( newWing, upper.getSym() ); - HalfWing.splice( newWing.getSym(), prev.getWing() ); - } else if ( lower.getDest() == event ) { - HalfWing newWing = new HalfWing(); - - newWing.setOrigin( event ); - newWing.getSym().setOrigin( prev ); - - HalfWing.splice( newWing, lower.getNext() ); - HalfWing.splice( newWing.getSym(), prev.getWing() ); - } else if ( marked.equals( eventMark ) ) { - // Need to sort this vertex, since we can't guarantee it's being inserted in the correct position - HalfWing newWing = new HalfWing(); - - newWing.setOrigin( event ); - newWing.getSym().setOrigin( prev ); - - HalfWing.splice( newWing, event.getWing() ); - HalfWing.splice( newWing.getSym(), prev.getWing() ); - - leftGoing.add( newWing ); - toSort.add( event ); - } else { - continue; - } - - // Remove the right marked vertex since it no longer breaks monotony - it.remove(); - } - - if ( eventMark != null ) { - // Since the mark might have had left going edges added, - // it may not need additional edges. Only add if it still - // does not have any left or right going edges - if ( leftGoing.isEmpty() ) { - leftMarked.put( event, eventMark ); - } else if ( rightGoing.isEmpty() ) { - rightMarked.put( event, eventMark ); - - toSort.add( event ); - } - } - - for ( HalfWing right : rightGoing ) { - if ( isPositive( right ) ) { - edgeCollection.insert( right ); - } else { - throw new IllegalStateException( "Attempted to insert negative edge" ); - } - } - } - - // Reverse sweep the vertices and connect any left marked vertices - for ( Entry< Vertex, MarkedWing > entry : leftMarked.entrySet() ) { - Vertex event = entry.getKey(); - MarkedWing mark = entry.getValue(); - // Only interested in marked vertices - if ( mark != null ) { - HalfWing upper = mark.upper; - HalfWing lower = mark.lower; - - Vertex lesser = upper.getOrigin(); - if ( compare( lesser, lower.getOrigin() ) > 0 ) { - lesser = lower.getOrigin(); - } - // Get the largest range of vertices that we need to check - Set< Vertex > checkSet = vertices.descendingSet().tailSet( event, 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 ( Vertex prev : checkSet ) { - // Do a similar check as with the right marked vertices - if ( lower.getOrigin() == prev ) { - HalfWing wing = new HalfWing(); - - wing.setOrigin( prev ); - wing.getSym().setOrigin( event ); - - HalfWing.splice( lower, wing ); - HalfWing.splice( wing.getSym(), event.getWing() ); - } else if ( upper.getOrigin() == prev ) { - HalfWing wing = new HalfWing(); - - wing.setOrigin( prev ); - wing.getSym().setOrigin( event ); - - HalfWing.splice( upper.getSym().getNext(), wing ); - HalfWing.splice( wing.getSym(), event.getWing() ); - } else if ( mark.equals( leftMarked.get( prev ) ) ) { - // Need to sort prev, since we can't guarantee it's being inserted in the correct position - HalfWing wing = new HalfWing(); - - wing.setOrigin( prev ); - wing.getSym().setOrigin( event ); - - HalfWing.splice( wing.getSym(), event.getWing() ); - HalfWing.splice( wing, prev.getWing() ); - - toSort.add( prev ); - } else { - // Not this vertex - continue; - } - - // Only need to merge with the first vertex to maintain monotony - break; - } - - toSort.add( event ); - } - } - - // When connecting vertices, the edges may have been inserted out of order - for ( Vertex vert : toSort ) { - sort( vert ); - } - - if ( debug ) { - Set< HalfWing > wings = new HashSet< HalfWing >( interior ); - int polyCount = 0; - while ( !wings.isEmpty() ) { - polyCount++; - final HalfWing start = wings.iterator().next(); - - Set< Vertex > checkSet = new HashSet< Vertex >(); - - boolean failed = false; - int count = 0; - HalfWing wing = start; - do { - count++; - wings.remove( wing ); - - if ( !checkSet.add( wing.getOrigin() ) ) { - failed = true; - } - } while ( ( wing = wing.getNext() ) != start ); - - if ( failed ) { - System.out.println( "Amount of edges: " + count ); - HalfWing temp = start; - do { - System.out.println( temp.getOrigin().getPosition().getX() + ", " + temp.getOrigin().getPosition().getY() ); - } while ( ( temp = temp.getNext() ) != start ); - } - } - System.out.println( "Amount of polygons: " + polyCount ); - } - } - - private void triangulate() { - StageTriangulation stage = advance(); - Set< HalfWing > interior = stage.interior; - Set< HalfWing > wings = stage.wings; - - // For each polygon, go down each monotone chain and connect the vertices where possible - // Implements the O(n) triangulation of a polygon as described in Computation Geometry Algorithms and Applications 3rd Ed - for ( HalfWing wing : wings ) { - Set< Vertex > toSort = new HashSet< Vertex >(); - Queue< HalfWing > vertices = new PriorityQueue< HalfWing >( ( a, b ) -> { - return compare( a.getOrigin(), b.getOrigin() ); - } ); - - HalfWing temp = wing; - do { - interior.add( temp ); - vertices.add( temp ); - } while ( ( temp = temp.getNext() ) != wing ); - - Stack< HalfWing > stack = new Stack< HalfWing >(); - // Add the first two vertices - stack.add( vertices.poll() ); - stack.add( vertices.poll() ); - - HalfWing prev = stack.peek(); - while ( vertices.size() > 1 ) { - HalfWing w = vertices.poll(); - if ( isPositive( w ) ^ isPositive( stack.peek() ) ) { - HalfWing current = stack.pop(); - while ( !stack.isEmpty() ) { - // Insert an edge from the current event to each vertex in the stack - HalfWing newWing = new HalfWing(); - - newWing.setOrigin( w.getOrigin() ); - newWing.getSym().setOrigin( current.getOrigin() ); - - HalfWing.splice( newWing, w ); - HalfWing.splice( newWing.getSym(), current ); - - toSort.add( w.getOrigin() ); - toSort.add( current.getOrigin() ); - - interior.add( newWing ); - - current = stack.pop(); - } - stack.push( prev ); - } else { - HalfWing lastPopped = stack.pop(); - boolean positive = isPositive( w ); - HalfWing leftEdge = positive ? w.getPrev() : w; - while ( !stack.isEmpty() ) { - HalfWing next = stack.peek(); - - HalfWing newWing = new HalfWing(); - - newWing.setOrigin( w.getOrigin() ); - newWing.getSym().setOrigin( next.getOrigin() ); - - // Check if the diagonal is inside the polygon - double cross = newWing.toVector2d().cross( leftEdge.toVector2d() ); - // Use a tolerance to avoid parallel lines with a non-zero cross product - boolean isInside = positive ? cross > COLLINEAR_TOLERANCE : cross < - COLLINEAR_TOLERANCE; - - if ( isInside ) { - HalfWing.splice( newWing, w ); - HalfWing.splice( newWing.getSym(), next ); - - toSort.add( w.getOrigin() ); - toSort.add( next.getOrigin() ); - - interior.add( newWing ); - - leftEdge = newWing; - - lastPopped = stack.pop(); - } else { - break; - } - } - stack.push( lastPopped ); - } - stack.push( w ); - - prev = w; - } - - HalfWing last = vertices.poll(); - stack.pop(); - while ( stack.size() > 1 ) { - HalfWing popped = stack.pop(); - - HalfWing newWing = new HalfWing(); - - newWing.setOrigin( last.getOrigin() ); - newWing.getSym().setOrigin( popped.getOrigin() ); - - HalfWing.splice( newWing, last ); - HalfWing.splice( newWing.getSym(), popped ); - - interior.add( newWing ); - - toSort.add( last.getOrigin() ); - toSort.add( popped.getOrigin() ); - } - - // Lazy solution, just sort everything - for ( Vertex v : toSort ) { - sort( v ); - } - } - } - - /* - * We can guarantee that the only left-going colinear edges are ones that have the same endpoints - * The next logical step is to find all edges that share the same endpoints(since there can be multiple) - * and either: remove all of them, or leave one if the lower or upper regions are not both interior or exterior - */ - private void removeEqualEdges( Vertex vert ) { - StageRegionGeneration stage = tessStage.get(); - Set< HalfWing > interiorWings = stage.interior; - Set< HalfWing > inverseInteriorWings = stage.inverseInterior; - - List< HalfWing > wings = new LinkedList< HalfWing >(); - { - final HalfWing start = vert.getWing(); - HalfWing wing = start; - do { - wings.add( wing ); - } while ( ( wing = wing.getPrev() ) != start ); - } - - class WingRange { - HalfWing start; - HalfWing end; - int winding = 0; - - public WingRange( HalfWing wing ) { - start = wing; - } - } - - // Form sets of similar edges - Map< Vertex, WingRange > ranges = new HashMap< Vertex, WingRange >(); - for ( HalfWing wing : wings ) { - // Only add edges that go from right to left - if ( !isPositive( wing ) ) { - Vertex v = wing.getDest(); - - WingRange range = ranges.computeIfAbsent( v, ( dir ) -> { return new WingRange( wing ); } ); - - if ( range.end != null && range.end.getPrev() != wing ) { - throw new IllegalStateException( "Vertex not sorted" ); - } - - range.end = wing; - - // Basic winding rules - // Add 1 if the upper region is in, -1 if the lower region is in, and 0 if both regions are in or out - boolean upperIn = interiorWings.contains( wing.getSym() ); - boolean lowerIn = inverseInteriorWings.contains( wing.getSym() ); - if ( upperIn ^ lowerIn ) { - range.winding += upperIn ? 1 : -1; - } else { - throw new IllegalStateException( "Wing is both an interior and exterior region!" ); - } - } - } - - for ( Entry< Vertex, WingRange > entry : ranges.entrySet() ) { - WingRange range = entry.getValue(); - HalfWing start = range.start; - HalfWing end = range.end; - - if ( range.winding == 0 ) { - // If there is no change in the interior status, then we want to remove this range of edges entirely - start = start.getSym().getNext(); - } else if ( start != end ) { - // Keep one edge and update the interior edge sets accordingly - if ( range.winding == 1 ) { - interiorWings.add( start.getSym() ); - inverseInteriorWings.remove( start.getSym() ); - } else if ( range.winding == -1 ) { - interiorWings.remove( start.getSym() ); - inverseInteriorWings.add( start.getSym() ); - } else { - throw new IllegalStateException( "Invalid winding order" ); - } - } else { - // Do nothing, since it is a normal edge - continue; - } - - if ( start == end ) { - // We want to remove ALL of the wings on this point... - // Essentially, remove this vertex from existence - HalfWing temp = start; - do { - interiorWings.remove( temp.getSym() ); - inverseInteriorWings.remove( temp.getSym() ); - - HalfWing prev = temp.getNext(); - HalfWing.splice( temp.getSym(), prev ); - prev.getOrigin().setWing( prev ); - - temp = temp.getPrev(); - } while ( temp != start ); - } else { - // Remove each edge in the range from their destination vertex - // Should be the destination vertex, but the order is not known - HalfWing temp = start; - while ( temp != end ) { - temp = temp.getPrev(); - - // Remove this edge from the interior wing set since it should no longer exist - interiorWings.remove( temp.getSym() ); - inverseInteriorWings.remove( temp.getSym() ); - - HalfWing prev = temp.getNext(); - HalfWing.splice( temp.getSym(), prev ); - prev.getOrigin().setWing( prev ); - } - - // Remove the range of edges from this vertex - HalfWing.splice( start, end ); - start.getOrigin().setWing( start ); - } - } - } - - /* - * As we sweep over each vertex, there is a chance that an edge that is still being processed may go through the current vertex - * We need to catch this and connect it to this vertex, to ensure no intersecting edges and for simpler processing later on - * - * There is also a chance that there are edges which have not been processed, but are close enough that they "pass" through this vertex - * Those need to be merged with this vertex BEFORE it gets processed - */ - private void resolveVertexIntersections( Vertex vertex ) { - StageRegionGeneration stage = tessStage.get(); - Queue< Vertex > queue = stage.getQueue(); - EdgeCollection< HalfWing > edgeCollection = stage.getCollection(); - Map< HalfWing, WindingRule< T > > windingMap = stage.getWindingMap(); - - // Split any edges passing through vertex, which do not end at vertex - Vector2d v = vertex.getPosition(); - for ( HalfWing wing : edgeCollection ) { - Vector2d a = wing.getOrigin().getPosition(); - Vector2d b = wing.getDest().getPosition(); - - if ( v == b ) { - // Skip if this wing ends at this vertex - continue; - } else if ( isSimilar( v, b ) || isSimilar( v, a ) ) { - // The vertices should have been merged already - throw new IllegalStateException( "Vertex not merged" ); - } else if ( compare( vertex, wing.getOrigin() ) < 1 || compare( vertex, wing.getDest() ) > -1 ) { - // Must be true: a < vertex < b - throw new IllegalStateException( "Vertex must be between edge" ); - } else { - Vector2d toB = b.subtracted( a ); - Vector2d toV = v.subtracted( a ); - - // Get perpendicular distance from v to the line formed by a + b - double t = toB.dot( toV ) / toB.length(); - Vector2d intersection = toB.multiplied( t ).add( a ); - // Or, if vertex is directly on the line formed by ab, then check the cross product - if ( Math.abs( toB.cross( toV ) ) <= COLLINEAR_TOLERANCE || isSimilar( v, intersection ) ) { - HalfWing split = wing.split(); - - // Set the vertex to this vertex - split.setOrigin( vertex ); - wing.getSym().setOrigin( vertex ); - - // Splice the newly split wing into the vertex - HalfWing.splice( vertex.getWing(), split ); - - // Don't forget to update the winding rules for the newly created edge!! - WindingRule< T > rule = windingMap.get( wing ); - windingMap.put( split, rule ); - windingMap.put( split.getSym(), rule.inverse() ); - } - } - } - - // Check for edges that may be close, but not necessarily be in the edge collection - // For example, if there is a positive horizontal edge from this vertex, and a - // vertical edge directly adjacent to this vertex, then the intersection formed by - // the two edges may be almost on top of this vertex, but greater. If that is the case, - // then there is a chance that the vertices that form the edges have not yet been processed - // and therefore the vertical edge is not in the edge collection. - // It is a pain to go back and modify a vertex once it has been processed, so pre-process - // it now, and split the not-yet processed edge into this vertex. - for ( Vertex queued : queue ) { - // Only check the lower vertices of edges, and only to a certain extent - // since we only need to worry about positive edges. - Vector2d queuedPos = queued.getPosition(); - if ( comparator.compareX( queuedPos, v ) > VERTEX_TOLERANCE ) { - break; - } else if ( comparator.compareY( queuedPos, v ) < VERTEX_TOLERANCE ) { - // Check any positive edges attached to the vertex to see if it should be merged into this vertex - HalfWing wing = queued.getWing(); - do { - if ( isPositive( wing ) ) { - Vector2d a = wing.getOrigin().getPosition(); - Vector2d b = wing.getDest().getPosition(); - - if ( v == b ) { - // Skip, will process this later - continue; - } else if ( isSimilar( v, b ) || isSimilar( v, a ) ) { - // The vertices should have been merged already - throw new IllegalStateException( "Vertex not merged!" ); - } else { - Vector2d toB = b.subtracted( a ); - Vector2d toV = v.subtracted( a ); - - // Get perpendicular distance from v to the line formed by a + b - double t = toB.dot( toV ) / toB.length(); - Vector2d intersection = toB.multiplied( t ).add( a ); - if ( isSimilar( v, intersection ) ) { - HalfWing split = wing.split(); - - // Set the vertex - split.setOrigin( vertex ); - wing.getSym().setOrigin( vertex ); - - // Splice the newly split wing into the vertex - HalfWing.splice( vertex.getWing(), split ); - - // Don't forget to update the winding rules for the newly created edge!! - WindingRule< T > rule = windingMap.get( wing ); - windingMap.put( split, rule ); - windingMap.put( split.getSym(), rule.inverse() ); - } - } - } - } while ( ( wing = wing.getPrev() ) != queued.getWing() ); - } - } - } - - /* - * Loop over each edge and try to split if they intersect. - * It may not capture all intersections, but the edge will - * be guaranteed to be intersection free - */ - private void resolveEdgeIntersections( HalfWing wing ) { - StageRegionGeneration stage = tessStage.get(); - Queue< Vertex > queue = stage.getQueue(); - EdgeCollection< HalfWing > edgeCollection = stage.getCollection(); - Map< HalfWing, WindingRule< T > > windingMap = stage.getWindingMap(); - - for ( HalfWing check : edgeCollection ) { - Optional< Intersection > optionalIntersection = getIntersection( wing, check ); - if ( optionalIntersection.isPresent() ) { - Intersection intersection = optionalIntersection.get(); - if ( intersection instanceof IntersectionEdgeToEdge ) { - IntersectionEdgeToEdge intEdge = ( IntersectionEdgeToEdge ) intersection; - - // The intersection should not intersect at the endpoints - if ( check.getDest() == wing.getDest() ) { - throw new IllegalStateException( "Invalid intersection" ); - } - - Vector2d point = intEdge.getPoint(); - // Check if point would have been merged with either of the destination vertices - // If it would have merged, then move the point of the destination to the intersection position - // This is to prevent creating edges between 2 vertices that are going to be merged, - // resulting in a 0 length edge. - Vertex origin; - // Flag for if a new vertex is created, and needs to be added to the event queue - boolean newPoint = true; - if ( isSimilar( point, wing.getDest().getPosition() ) ) { - // Use the current wing's destination as the intersection point - // Do not split - origin = wing.getDest(); - newPoint = false; - } else { - // Update the winding rules for both new edges - HalfWing aSplit = wing.split(); - WindingRule< T > aRule = windingMap.get( wing ); - windingMap.put( aSplit, aRule ); - windingMap.put( aSplit.getSym(), aRule.inverse() ); - - // Update the position - origin = aSplit.getOrigin(); - origin.setPosition( point ); - } - - if ( isSimilar( point, check.getDest().getPosition() ) ) { - // Use the intersected edge's endpoint as the intersection point - // Do not split - HalfWing.splice( check.getDest().getWing(), origin.getWing() ); - - origin = check.getDest(); - - newPoint = false; - } else { - HalfWing bSplit = check.split(); - WindingRule< T > bRule = windingMap.get( check ); - windingMap.put( bSplit, bRule ); - windingMap.put( bSplit.getSym(), bRule.inverse() ); - - // Splice the edges together into the same vertex - HalfWing.splice( bSplit, origin.getWing() ); - } - - // Update all vertices around the origin - origin.update(); - - // Don't re-insert the origin if it is an existing vertex - if ( newPoint ) { - queue.add( origin ); - } - } else if ( intersection instanceof IntersectionColinear ) { - IntersectionColinear intCol = ( IntersectionColinear ) intersection; - - HalfWing shorter = intCol.getShorterEdge(); - HalfWing longer = intCol.getLongerEdge(); - - // Update the winding rule for the split - HalfWing split = longer.split(); - WindingRule< T > rule = windingMap.get( longer ); - windingMap.put( split, rule ); - windingMap.put( split.getSym(), rule.inverse() ); - - // Update the vertex for the split - Vertex vert = shorter.getDest(); - split.setOrigin( vert ); - longer.getSym().setOrigin( vert ); - - // Splice the two together - HalfWing.splice( shorter.getSym(), split ); - } - } - } - } - - /* - * This makes some assumptions about a and b: - * - a and b are positive edges - * - a and b do not intersect at their rightmost endpoint - * - a and b share the same leftmost endpoint if they are colinear - */ - private Optional< Intersection > getIntersection( HalfWing a, HalfWing b ) { - 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(); - final Vector2d s = b.toVector2d(); - - // Ignore edges that are not colinear - if ( Math.abs( r.cross( s ) ) <= TOLERANCE ) { - - // Compare which destination is less - if ( compare( a.getDest(), b.getDest() ) <= 0 ) { - 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() ) ) { - // a.getDest().equals( 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 qp = q.subtracted( p ); - final double qpr = qp.cross( r ); - final double rs = r.cross( s ); - - // Check if parallel - if ( rs != 0 ) { - // Calculate t and u - final double t = qp.cross( s ) / rs; - final double u = qpr / rs; - - // Do the line segments intersect - // They should NOT intersect at their endpoints - if ( t > 0 && t < 1 && u > 0 && u < 1 ) { - // Calculate the point of intersection - final Vector2d intersection = r.multiply( t ).add( p ); - - return Optional.of( new IntersectionEdgeToEdge( intersection ) ); - } - } - } - return Optional.empty(); - } - - // Sort the edges around this vertex. - private List< HalfWing > sort( Vertex vertex ) { - List< HalfWing > edges = new ArrayList< HalfWing >(); - - final HalfWing vertWing = vertex.getWing(); - HalfWing edge = vertWing; - do { - if ( edge.isZero() ) { - // Invariant: Edges must have a length > 0 - throw new IllegalStateException( "Edge has length of 0" ); - } - edges.add( edge ); - } while ( ( edge = edge.getPrev() ) != vertWing ); - - // 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 lowest, and 12:00 is the highest - Collections.sort( edges, ( e1, e2 ) -> { - // This assumes that no edge has length of 0 - boolean e1p = isPositive( e1 ); - boolean e2p = isPositive( e2 ); - - if ( e1p ^ e2p ) { - return e1p ? 1 : -1; - } else { - Vector2d a1 = e1.getOrigin().getPosition(); - Vector2d a2 = e1.getDest().getPosition(); - Vector2d b1 = e2.getOrigin().getPosition(); - Vector2d b2 = e2.getDest().getPosition(); - - double cross = b2.subtracted( b1 ).normalized().cross( a2.subtracted( a1 ).normalized() ); - if ( Math.abs( cross ) <= TOLERANCE ) { - return 0; - } else { - return cross < 0 ? -1 : 1; - } - } - } ); - - for ( int i = 0; i < edges.size(); i++ ) { - // Get this edge and the next edge - HalfWing e1 = edges.get( i ); - HalfWing 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.setWing( edges.get( 0 ) ); - } - - return edges; - } - - /* - * 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 boolean greaterThanOrEqualTo( HalfWing a, HalfWing 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 - Vector2d a1 = a.getOrigin().getPosition(); - Vector2d a2 = a.getDest().getPosition(); - // b1 < b2 - Vector2d b1 = b.getOrigin().getPosition(); - Vector2d b2 = b.getDest().getPosition(); - - 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; - } - } - - private boolean isPositive( HalfWing wing ) { - return compare( wing.getOrigin(), wing.getDest() ) <= 0; - } - - private int compare( Vertex a, Vertex b ) { - return comparator.compare( a.getPosition(), b.getPosition() ); - } - - // Check if 2 vertices are 'close enough' - private boolean isSimilar( Vertex a, Vertex b ) { - return isSimilar( a.getPosition(), b.getPosition() ); - } - - private boolean isSimilar( Vector2d a, Vector2d b ) { - return Math.abs( a.distance( b ) ) <= VERTEX_TOLERANCE; - } - - < C extends Tess4jStage > C advance() { - tessStage = tessStage.advance(); - return tessStage.get(); - } - - public List< Polygon > getPolygons() { - if ( tessStage instanceof Tess4j.StageComplete ) { - StageComplete stage = tessStage.get(); - return stage.filledPolygons; - - } else if ( tessStage instanceof Tess4j.StageColinearConsolidation ) { - StageColinearConsolidation stage = tessStage.get(); - List< Polygon > polygons = new ArrayList< Polygon >(); - for ( List< HalfWing > wings : stage.newPolygons ) { - List< Point > points = new ArrayList< Point >(); - - HalfWing start = wings.get( 0 ); - HalfWing wing = start; - do { - Vector2d pos = wing.getOrigin().getPosition(); - points.add( new Point( pos.getX(), pos.getY() ) ); - } while ( ( wing = wing.next ) != start ); - - polygons.add( new Polygon( points ) ); - } - return polygons; - } - - return new ArrayList< Polygon >(); - } - - public void setDebug( boolean debug ) { - this.debug = debug; - } - - public static void main( String[] args ) { - Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); }, new Vector2dComparator() { - @Override - public double compareX( Vector2d a, Vector2d b ) { - return a.getX() - b.getX(); - } - - @Override - public double compareY( Vector2d a, Vector2d b ) { - 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 ), - 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(); - } - - /* - * Represents the stage that this tessellator is currently in. - * Used to help guarantee which class variables can be access during each stage - * and helps prevent calling certain methods at the wrong stage. - */ - private abstract class Tess4jStage { - @SuppressWarnings( "unchecked" ) - < C extends Tess4jStage > C get() { - try { - return ( C ) this; - } catch ( ClassCastException e ) { - throw new IllegalArgumentException( "Invalid stage class: " + getClass() ); - } - } - - abstract Tess4jStage advance(); - } - - /* - * Represents the stage while the user adds various polygons, before tessellation starts - */ - private class StagePreTess extends Tess4jStage { - Queue< Vertex > vertexQueue = new PriorityQueue< Vertex >( Tess4j.this::compare ); - EdgeCollection< HalfWing > edgeCollection = new EdgeCollection< HalfWing >( Tess4j.this::greaterThanOrEqualTo ); - /* - * Use a weak map for storing the winding rule per halfwing - * This also means we need to keep meticulous order of half wings when creating them, such as HalfWing.split - * Used while resolving intersections and calculating whether a region is interior/exterior - */ - Map< HalfWing, WindingRule< T > > windingMap = new WeakHashMap< HalfWing, WindingRule< T > >(); - /* - * A supplier for fetching a new region. The region must represent the space outside of all polygons - */ - Supplier< T > defaultRegionSupplier; - - StagePreTess( Supplier< T > defaultRegionSupplier ) { - this.defaultRegionSupplier = defaultRegionSupplier; - } - - @Override - Tess4jStage advance() { - return new StageRegionGeneration( this ); - } - } - - /* - * Represents the first stage of tessellation - * - Similar vertices are merged - * - Intersecting edges are split - * - Regions are partitioned into inner/outer - */ - private class StageRegionGeneration extends Tess4jStage { - private StagePreTess preStage; - - /* - * Contains the region for each HalfWing object - * Used while resolving intersections and calculating whether a region is interior/exterior - */ - Map< HalfWing, T > regionMap = new HashMap< HalfWing, T >(); - - /* - * Contains wings who's left face is interior - */ - Set< HalfWing > interior = new HashSet< HalfWing >(); - /* - * Contains wings who's right face is interior - */ - Set< HalfWing > inverseInterior = new HashSet< HalfWing >(); - - StageRegionGeneration( StagePreTess preTess ) { - this.preStage = preTess; - } - - @Override - Tess4jStage advance() { - return new StageColinearConsolidation( interior ); - } - - Queue< Vertex > getQueue() { - return preStage.vertexQueue; - } - - EdgeCollection< HalfWing > getCollection() { - return preStage.edgeCollection; - } - - Map< HalfWing, WindingRule< T > > getWindingMap() { - return preStage.windingMap; - } - - T getDefaultRegion() { - return preStage.defaultRegionSupplier.get(); - } - } - - /* - * Merge all adjacent colinear edges that are part of the same polygon - * Then provide a list of all the edges for each polygon - * Doesn't really have to be a list though, but it's convenient - */ - private class StageColinearConsolidation extends Tess4jStage { - Set< HalfWing > polygons = new HashSet< HalfWing >(); - Set< List< HalfWing > > newPolygons = new HashSet< List< HalfWing > >(); - - StageColinearConsolidation( Set< HalfWing > interior ) { - while ( !interior.isEmpty() ) { - final HalfWing start = interior.iterator().next(); - - polygons.add( start ); - - HalfWing wing = start; - do { - interior.remove( wing ); - } while ( ( wing = wing.getNext() ) != start ); - } - } - - @Override - Tess4jStage advance() { -// return new StageVertexReduction( newPolygons ); - return new StageMonotonePartition( newPolygons ); -// return new StageComplete( newPolygons ); - } - } - - /* - * Perform more aggressive vertex reduction by connecting edges and vertices on - * the same line, while also splitting into multiple polygons if required - * - * Ideally this would take in a set of monotone partitions, and for each - * one, divide it into multiple partitions(if applicable) which require - * a smaller amount of triangles to tessellate overall. - */ - private class StageVertexReduction extends Tess4jStage { - Set< List< HalfWing > > polygons; - - StageVertexReduction( Set< List< HalfWing > > polygons ) { - this.polygons = polygons; - } - - @Override - Tess4jStage advance() { -// return new StageMonotonePartition( polygons ); - return new StageComplete( polygons ); - } - - } - - private class StageMonotonePartition extends Tess4jStage { - TreeSet< Vertex > vertices = new TreeSet< Vertex >( Tess4j.this::compare ); - Set< HalfWing > interior = new HashSet< HalfWing >(); - - StageMonotonePartition( Set< List< HalfWing > > polygons ) { - Set< Vertex > vertices = new HashSet< Vertex >(); - if ( debug ) { - System.out.println( "Amount of polygons: " + polygons.size() ); - } - for ( List< HalfWing > list : polygons ) { - if ( debug ) { - System.out.println( "Amount of edges in the polygon: " + list.size() ); - } - for ( HalfWing w : list ) { - if ( isPositive( w ) ) { - interior.add( w ); - } - - vertices.add( w.getOrigin() ); - } - } - this.vertices.addAll( vertices ); - } - - @Override - Tess4jStage advance() { - return new StageTriangulation( interior ); -// return new StageComplete( interior, true ); - } - } - - private class StageTriangulation extends Tess4jStage { - Set< HalfWing > wings = new HashSet< HalfWing >(); - Set< HalfWing > interior = new HashSet< HalfWing >(); - - StageTriangulation( Set< HalfWing > wings ) { - // Get the minimum interior wing of each polygon - while ( !wings.isEmpty() ) { - final HalfWing start = wings.iterator().next(); - - this.wings.add( start ); - - Set< Vertex > checkSet = new HashSet< Vertex >(); - - HalfWing wing = start; - do { - wings.remove( wing ); - - if ( !checkSet.add( wing.getOrigin() ) ) { - throw new IllegalStateException( "Contains the same point more than once" ); - } - } while ( ( wing = wing.getNext() ) != start ); - } - } - - @Override - Tess4jStage advance() { - return new StageComplete( interior, false ); - } - } - - // TODO Provide each polygon with a region - private class StageComplete extends Tess4jStage { - List< Polygon > filledPolygons = new ArrayList< Polygon >(); - - StageComplete( Set< List< HalfWing > > wings ) { - for ( List< HalfWing > poly : wings ) { - List< Point > points = new ArrayList< Point >(); - - for ( HalfWing wing : poly ) { - Vector2d v = wing.getOrigin().getPosition(); - points.add( v ); - } - - filledPolygons.add( new Polygon( points ) ); - } - } - - StageComplete( Set< HalfWing > wings, boolean other ) { - // Convert the edges to polygons - while ( !wings.isEmpty() ) { - final HalfWing start = wings.iterator().next(); - - List< Point > points = new ArrayList< Point >(); - Set< Point > checkSet = new HashSet< Point >(); - HalfWing wing = start; - do { - Vector2d vec = wing.getOrigin().getPosition(); - Point point = new Point( vec.getX(), vec.getY() ); - points.add( point ); - if ( !checkSet.add( point ) ) { - throw new IllegalStateException( "Polygon is not simple" ); - } - wings.remove( wing ); - } while ( ( wing = wing.getNext() ) != start ); - filledPolygons.add( new Polygon( points ) ); - } - } - - @Override - Tess4jStage advance() { - throw new IllegalStateException( "Already at the last stage" ); - } - } - - /* - * 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 HalfWing shorter; - private final HalfWing longer; - - IntersectionColinear( HalfWing shorter, HalfWing longer ) { - this.shorter = shorter; - this.longer = longer; - } - - public HalfWing getShorterEdge() { - return shorter; - } - - public HalfWing getLongerEdge() { - return longer; - } - } -} - diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4jTest.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4jTest.java deleted file mode 100644 index 70e02a7..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4jTest.java +++ /dev/null @@ -1,266 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -import java.awt.Color; -import java.awt.Graphics; -import java.util.Arrays; -import java.util.List; - -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; - -public class Tess4jTest extends JPanel { - JFrame f; - - private int windowWidth = 1200; - private int windowHeight = 1000; - - private int centerX = 600; - private int centerY = 400; - - private Graphics g; - - private int scale = 100; - - public static void main( String[] args ) { - SwingUtilities.invokeLater( new Runnable() { - @Override - public void run() { - new Tess4jTest(); - } - } ); - } - - public Tess4jTest() { - 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; - -// Vertex v = null; -// for ( int i = 0; i < 100; i++ ) { -// double deg = Math.random() * 360; -// double dist = Math.random() * 100 + 100; -// double x = dist * Math.cos( Math.toRadians( deg ) ); -// double y = dist * Math.sin( Math.toRadians( deg ) ); -// HalfWing edge = new HalfWing(); -// edge.getDest().setPosition( new Vector2d( x, y ) ); -// if ( v == null ) { -// v = edge.getOrigin(); -// } else { -// HalfWing.splice( edge, v.getWing() ); -// } -// } -// v.update(); -// -// HalfWing edge = v.getWing(); -// int i = 0; -// double ic = 0; -// do { -// Vector2d dest = edge.getDest().getPosition(); -// g.drawLine( centerX, centerY, ( int ) dest.getX() + centerX, ( int ) dest.getY() + centerY ); -// dest = dest.normalized(); -// g.drawString( i++ + "", ( int ) ( dest.x * 200 * ( ic + 1.1 ) ) + centerX, ( int ) ( dest.y * 200 * ( ic + 1.1 ) ) + centerY ); -// ic = ( ic + 0.07 ) % .7; -// edge = edge.getPrev(); -// } while ( edge != v.getWing() ); -// -// v.sort(); -// -// g.drawLine( centerX + 700, centerY, centerX + 700, centerY - 400 ); -// edge = v.getWing(); -// i = 0; -// ic = 0; -// do { -// Vector2d dest = edge.getDest().getPosition(); -// g.drawLine( centerX + 700, centerY, ( int ) dest.getX() + centerX + 700, ( int ) dest.y + centerY ); -// dest = dest.normalized(); -// g.drawString( i++ + "", ( int ) ( dest.x * 200 * ( ic + 1.1 ) ) + centerX + 700, ( int ) ( dest.y * 200 * ( ic + 1.1 ) ) + centerY ); -// ic = ( ic + 0.07 ) % .7; -// edge = edge.getPrev(); -// } while ( edge != v.getWing() ); - -// Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); - Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); }, new Vector2dComparator() { - @Override - public double compareX( Vector2d a, Vector2d b ) { - a = rotate( a, 128 ); - b = rotate( b, 128 ); - return a.getX() - b.getX(); - } - - @Override - public double compareY(Vector2d a, Vector2d b ) { - a = rotate( a, 128 ); - b = rotate( b, 128 ); - return b.getY() - a.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, 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( -5, 0 ), -// new Point( -2, 0 ), -// new Point( -2, 1 ), -// new Point( -5, 1 ) -// ) ), new WindingRuleSimple() ); -// tess4j.addPolygon( new Polygon( Arrays.asList( -// new Point( -1.75, .5 ), -// new Point( 4.25, .5 ), -// new Point( -0.25, -2.5 ), -// new Point( 1.25, 2 ), -// new Point( 2.75, -2.5 ) -// ) ), 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( -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() ); - for ( int i = 0; i < 4; i++ ) { - for ( int j = 0; j < 4; j++ ) { - tess4j.addPolygon( new Polygon( Arrays.asList( - new Point( i, j ), - new Point( i, j + 1 ), - new Point( i + 1, j + 1 ), - new Point( i + 1, j ) - ) ), new WindingRuleSimple() ); -// tess4j.addPolygon( new Polygon( Arrays.asList( -// new Point( i, j ), -// new Point( i, j + 1 ), -// new Point( i + 1, j ), -// new Point( i + 1, j + 1 ) -// ) ), new WindingRuleSimple() ); - } - } - - tess4j.tessellate(); - - List< Polygon > polygons = tess4j.getPolygons(); - - System.out.println( "Polygon count" ); - System.out.println( polygons.size() ); - for ( Polygon p : polygons ) { - 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.getPoints().size(); i++ ) { - Point point = p.getPoints().get( i ); - - 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.getX() * scale ) + centerX; - yPoints[ i ] = 100 - ( int ) ( point.getY() * scale ) + centerY; - } - - g.setColor( new Color( ( int ) ( Math.random() * 0xFFFFFF ) ) ); - g.fillPolygon( xPoints, yPoints, size ); - } - - g.setColor( Color.BLACK ); - for ( Polygon p : polygons ) { - 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.getX() * scale ) + centerX - ( int ) diff, 100 - ( int ) ( point.getY() * scale ) + centerY - ( int ) diff, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) ); - } - } - - g.drawLine( centerX, 0, centerX, 2000 ); - g.drawLine( 0, centerY + 100, 2000, centerY + 100 ); - -// 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/tess4j/Tessellator.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tessellator.java deleted file mode 100644 index bc6f83e..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tessellator.java +++ /dev/null @@ -1,508 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.PriorityQueue; -import java.util.Queue; -import java.util.Set; -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; - private static EdgeDict dict; - private static Queue< VertexOld > eventQueue; - - public static void main( String[] args ) { - List< Polygon > polygons = new ArrayList< Polygon >(); - polygons.add( new Polygon( Arrays.asList( - new Point( -1, -1 ), - new Point( 1, -1 ), - new Point( 1, 1 ), - new Point( -1, 1 ) - ) ) ); - tessellate( polygons ); - } - - public static void tessellate( Collection< Polygon > shapes ) { - eventQueue = new PriorityQueue< VertexOld >(); - dict = new EdgeDict( () -> { return vertex; } ); - - double minX = Double.MAX_VALUE; - double minY = Double.MAX_VALUE; - double maxX = Double.MIN_VALUE; - double maxY = Double.MIN_VALUE; - - // Get all unique vertices that are in use - Set< VertexOld > vertexCollection = Collections.newSetFromMap( new WeakHashMap< VertexOld, Boolean >() ); - for ( Polygon polygon : shapes ) { - HalfEdge edge = null; - for ( Point point : polygon.getPoints() ) { - if ( edge == null ) { - // Make a self loop with one edge and one vertex - edge = HalfEdge.makeEdge(); - HalfEdge.splice( edge, edge.sym() ); - } else { - HalfEdge.splitEdge( edge ); - edge = edge.nextLeft; - } - - 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.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 ); - } - } - eventQueue.addAll( vertexCollection ); - - // Add sentinel regions so that all polygons are between two edges/regions - addSentinel( minX, minY, maxX, maxY ); - - // Sweep over each vertex, from left to right, bottom to top - VertexOld current; - while ( !eventQueue.isEmpty() ) { - current = eventQueue.poll(); - while ( true ) { - VertexOld next = eventQueue.peek(); - // Get all vertices that overlap and merge them together - if ( next == null || !current.equals( next ) ) { - break; - } - next = eventQueue.poll(); - HalfEdge.splice( current.edge, next.edge ); - } - System.out.println( "SCANNING " + current ); - sweepEvent( current ); - } - } - - public static void addSentinel( double minX, double minY, double maxX, double maxY ) { - double width = ( maxX - minX ) + 0.01; - double height = ( maxY - minY ) + 0.01; - - double lowerX = minX - width; - double lowerY = minY - height; - double upperX = maxX + width; - double upperY = maxY + height; - - addSentinelRegion( lowerX, upperX, lowerY ); - addSentinelRegion( lowerX, upperX, upperY ); - } - - private static void addSentinelRegion( double startX, double endX, double y ) { - HalfEdge edge = HalfEdge.makeEdge(); - - edge.origin.x = endX; - edge.origin.y = y; - edge.sym().origin.x = startX; - edge.sym().origin.y = y; - - vertex = edge.sym().origin; - - RegionOld region = new RegionOld(); - region.upperEdge = edge; - region.inside = false; - region.sentinel = true; - dict.insert( region ); - } - - public static void sweepEvent( VertexOld vert ) { - vertex = vert; - - // Find the region that this vertex belongs to - HalfEdge edge = vert.edge; - while ( edge.region == null ) { - edge = edge.nextOrigin; - if ( edge == vert.edge ) { - // This is a new vertex which is not associated with any region - // Connect it to something - connectLeftVertex( vert ); - return; - } - } - } - - public static void connectLeftVertex( VertexOld vert ) { - RegionOld tmpRegion = new RegionOld(); - tmpRegion.upperEdge = vert.edge.sym(); - - // Find the regions immediately above and below this vertex - RegionOld up = dict.search( tmpRegion ); - RegionOld lower = dict.below( up ); - if ( lower == null ) { - // Coplanar polygon - return; - } - // Get the edges associated with both regions - HalfEdge upperEdge = up.upperEdge; - HalfEdge lowerEdge = lower.upperEdge; - - // If the vertex lies on the upper edge... ??? - if ( vert.compareTo( upperEdge.sym().origin, upperEdge.origin ) == 0 ) { - connectLeftDegenerate( up, vert ); - return; - } - - // Get the higher region - RegionOld region = lowerEdge.sym().origin.lessThanOrEqualTo( upperEdge.sym().origin ) ? up : lower; - if ( region.inside || region.fixUpperEdge ) { - HalfEdge edge; - if ( region == up ) { - edge = HalfEdge.connect( vert.edge.sym(), upperEdge.nextLeft ); - } else { - edge = HalfEdge.connect( lowerEdge.nextD(), vert.edge ).sym(); - } - - if ( region.fixUpperEdge ) { - region.fixUpperEdge( edge ); - } else { - computeWinding( addRegionBelow( up, edge ) ); - } - } else { - addRightEdges( up, vertex.edge, vertex.edge, null, true ); - } - } - - public static void addRightEdges( RegionOld region, HalfEdge first, HalfEdge last, HalfEdge topLeft, boolean clean ) { - // Add all edges from first to last - // The last edge to be inserted is the edge CCW of last - - System.out.println( "Region is " + region.upperEdge ); - HalfEdge edge = first; - do { - // Create regions for all edges starting from first, before last, in CCW fashion - System.out.println( "Inserting region for edge " + edge ); - // The region defining edge is always going to be right to left - addRegionBelow( region, edge.sym() ); - edge = edge.nextOrigin; - } while ( edge != last ); - - // Get the most CCW edge that is to the right of the sweep line - if ( topLeft == null ) { - topLeft = dict.below( region ).upperEdge.prevR(); - System.out.println( "Top left region is " + topLeft ); - } - RegionOld previousRegion = region; - HalfEdge previousEdge = topLeft; - - // Update winding and fix any problems with ordering - boolean firstTime = true; - while ( true ) { - // Iterate over each region from top to bottom - RegionOld tempRegion = dict.below( previousRegion ); - // Get the left to right, bottom to top edge - HalfEdge tempEdge = tempRegion.upperEdge.sym(); - System.out.println( "Looping for edge " + tempEdge ); - - // Break if the vertex changes - if ( tempEdge.origin != previousEdge.origin ) { - break; - } - - // Check if the previous edge is equal to the previous edge - // Otherwise there's a discrepancy and it needs to be relinked accordingly - if ( tempEdge.nextOrigin != previousEdge ) { - // Unlink the edge from its current position, and relink below the previous edge - HalfEdge.splice( tempEdge.nextOrigin, tempEdge ); - HalfEdge.splice( previousEdge.prevOrigin(), tempEdge ); - } - - // Calculate the winding number and if it is inside - tempRegion.windingNumber = previousRegion.windingNumber - tempEdge.winding; - tempRegion.inside = tempRegion.windingNumber % 2 == 1; - - previousRegion.dirty = true; - - if ( !firstTime && checkForRightSplice( previousRegion ) ) { - tempEdge.winding += previousEdge.winding; - - HalfEdge.delete( previousEdge ); - } - - firstTime = false; - previousRegion = tempRegion; - previousEdge = tempEdge; - } - - if ( clean ) { - walkDirtyRegions( previousRegion ); - } - } - - public static boolean checkForLeftSplice( RegionOld region ) { - // Check the upper and lower edge of the region for any inconsistencies - // Simiar to checkForRightSplice, but the left vertices - RegionOld below = dict.below( region ); - HalfEdge upEdge = region.upperEdge; - HalfEdge downEdge = below.upperEdge; - - if ( upEdge.sym().origin.lessThanOrEqualTo( downEdge.sym().origin ) ) { - if ( downEdge.sym().origin.compareTo( upEdge.sym().origin, upEdge.origin ) < 0 ) { - return false; - } - - // The lower edge is above the upper edge, so splice - dict.above( region ).dirty = true; - region.dirty = true; - - HalfEdge edge = HalfEdge.splitEdge( upEdge ); - HalfEdge.splice( downEdge.sym(), edge ); - edge.face.inside = region.inside; - } else { - if ( upEdge.sym().origin.compareTo( downEdge.sym().origin, downEdge.origin ) > 0 ) { - return false; - } - - region.dirty = true; - below.dirty = true; - HalfEdge edge = HalfEdge.splitEdge( downEdge ); - HalfEdge.splice( upEdge.nextLeft, downEdge.sym() ); - edge.sym().face.inside = region.inside; - } - return true; - } - - public static boolean checkForRightSplice( RegionOld region ) { - RegionOld below = dict.below( region ); - HalfEdge upEdge = region.upperEdge; - HalfEdge downEdge = below.upperEdge; - - // Check if the right vertex of the upper edge is to the left or below the lower edge's right vertex - if ( upEdge.origin.lessThanOrEqualTo( downEdge.origin ) ) { - // Check if the right vertex is above the lower edge - if ( upEdge.origin.compareTo( downEdge.sym().origin, downEdge.origin ) > 0 ) { - // If so, no splicing is necessary - return false; - } - - // If the right vertex of the upper edge is not equal to the right vertex of the lower edge - if ( !upEdge.origin.equals( downEdge.origin ) ) { - // Split the lower edge in two, with the new edge on the right - HalfEdge.splitEdge( downEdge.sym() ); - // Move the right vertex of the lower edge to the - HalfEdge.splice( upEdge, downEdge.prevOrigin() ); - - region.dirty = true; - below.dirty = true; - } else if ( upEdge.origin != downEdge.origin ) { - // Merge the two vertices if they are not already the same - eventQueue.remove( upEdge.origin ); - HalfEdge.splice( downEdge.prevOrigin(), upEdge ); - } - } else { - // Check if the lower edge's right vertex is on or below the upper edge - if ( downEdge.origin.compareTo( upEdge.sym().origin, upEdge.origin ) <= 0 ) { - return false; - } - - dict.above( region ).dirty = true; - region.dirty = true; - - // Split the upper edge - HalfEdge.splitEdge( upEdge.sym() ); - // Move the upper edge to the lower edge's vertex - HalfEdge.splice( downEdge.prevOrigin(), upEdge ); - - } - return true; - } - - public static void walkDirtyRegions( RegionOld region ) { - RegionOld lower = dict.below( region ); - while ( true ) { - // Find the lowest dirty region - while ( lower.dirty ) { - region = lower; - lower = dict.below( lower ); - } - - if ( !region.dirty ) { - lower = region; - region = dict.above( region ); - if ( region == null || !region.dirty ) { - // No dirty regions left - return; - } - } - - region.dirty = false; - HalfEdge upperEdge = region.upperEdge; - HalfEdge lowerEdge = lower.upperEdge; - - // If the start of the region edge is not the same as the start of the lower region edge - if ( upperEdge.sym().origin != lowerEdge.sym().origin ) { - // Consistency check - if ( checkForLeftSplice( region ) ) { - if ( lower.fixUpperEdge ) { - dict.remove( lower ); - HalfEdge.delete( lowerEdge ); - lower = dict.below( region ); - lowerEdge = lower.upperEdge; - } else if ( region.fixUpperEdge ) { - dict.remove( region ); - HalfEdge.delete( upperEdge ); - region = dict.above( lower ); - upperEdge = region.upperEdge; - } - } - } - - // If the end of the region edge is not the same as the end of the lower region edge - if ( upperEdge.origin != lowerEdge.origin ) { - if ( upperEdge.sym().origin != lowerEdge.sym().origin - && !region.fixUpperEdge - && !lower.fixUpperEdge - && ( upperEdge.sym().origin == vertex || lowerEdge.sym().origin == vertex ) ) { - if ( checkForIntersect( region ) ) { - return; - } - } else { - checkForRightSplice( region ); - } - } - - // Matching edges - if ( upperEdge.origin == lowerEdge.origin && upperEdge.sym().origin == lowerEdge.sym().origin ) { - // Degenerate loop - lowerEdge.winding += upperEdge.winding; - dict.remove( region ); - HalfEdge.delete( upperEdge ); - region = dict.above( lower ); - } - } - } - - public static boolean checkForIntersect( RegionOld upper ) { - // Check the upper and lower edges of this region to see if they intersect - - RegionOld lower = dict.below( upper ); - HalfEdge upperEdge = upper.upperEdge; - HalfEdge lowerEdge = lower.upperEdge; - VertexOld upLeft = upperEdge.sym().origin; - VertexOld upRight = upperEdge.origin; - VertexOld downLeft = lowerEdge.sym().origin; - VertexOld downRight = lowerEdge.origin; - - // Endpoints are the same, so no intersection - if ( upRight == downRight ) { - return false; - } - - double upY = Math.min( upRight.y, upLeft.y ); - double downY = Math.max( downRight.y, downLeft.y ); - // The edges do not overlap vertically - if ( upY > downY ) { - return false; - } - - // Check for any vertical intersection of the right point - if ( upRight.lessThanOrEqualTo( downRight ) ) { - if ( upRight.compareTo( downLeft, downRight ) > 0 ) { - return false; - } - } else { - if ( downRight.compareTo( upLeft, upRight ) < 0 ) { - return false; - } - } - - // TODO Compute intersection - - return false; - } - - public static Point intersection( VertexOld x1, VertexOld x2, VertexOld y1, VertexOld y2 ) { - Point point = new Point( 0, 0 ); - if ( !x1.lessThanOrEqualTo( x2 ) ) { - VertexOld temp = x1; - x1 = x2; - x2 = temp; - } - if ( !y1.lessThanOrEqualTo( y2 ) ) { - VertexOld temp = y1; - y1 = y2; - y2 = temp; - } - if ( !x1.lessThanOrEqualTo( y1 ) ) { - VertexOld temp = x1; - x1 = y1; - y1 = temp; - VertexOld temp2 = x2; - x2 = y2; - y2 = temp2; - } - - // Compare points - if ( !y1.lessThanOrEqualTo( x2 ) ) { - point.setX( ( y1.x + x2.x ) / 2 ); - } else if ( x2.lessThanOrEqualTo( y2 ) ) { - double z1 = y1.verticalDistance( x1, x2 ); - double z2 = x2.verticalDistance( y1, y2 ); - if ( z1 + z1 < 0 ) { - z1 *= -1; - z2 *= -1; - } - point.setX( interpolate( z1, y1.x, z2, x2.x ) ); - } else { - double z1 = y1.compareTo( x1, x2 ); - double z2 = - y2.compareTo( x1, x2 ); - if ( z1 + z2 < 0 ) { - z1 *= -1; - z2 *= -1; - } - point.setX( interpolate( z1, y1.x, z2, y2.x ) ); - } - return point; - } - - public static double interpolate( double a, double x, double b, double y ) { - a = Math.max( a, 0 ); - b = Math.max( b, 0 ); - if ( a > b ) { - return y - ( ( x - y ) * ( b / ( a + b ) ) ); - } - if ( b != 0 ) { - return x + ( ( y - x ) * ( a / ( a + b ) ) ); - } - return ( x + y ) / 2; - } - - public static void computeWinding( RegionOld region ) { - region.windingNumber = dict.above( region ).windingNumber + region.upperEdge.winding; - // TODO Using odd for now - region.inside = region.windingNumber % 2 == 1; - } - - public static void connectLeftDegenerate( RegionOld region, VertexOld vert ) { - throw new IllegalArgumentException( "connectLeftDegenerate is unimplemented!" ); - } - - public static RegionOld addRegionBelow( RegionOld above, HalfEdge edge ) { - RegionOld region = new RegionOld(); - region.upperEdge = edge; - dict.insertBefore( above, region ); - region.fixUpperEdge = false; - region.sentinel = false; - - edge.region = region; - return region; - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Util.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Util.java deleted file mode 100644 index ece49cb..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Util.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -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(); - - builder.append( obj.getClass().getSimpleName() ); - builder.append( '{' ); - List< Field > fields = new ArrayList< Field >(); - fields.addAll( Arrays.asList( obj.getClass().getDeclaredFields() ) ); - fields.addAll( Arrays.asList( obj.getClass().getFields() ) ); - - for ( Iterator< Field > it = fields.iterator(); it.hasNext(); builder.append( "," ) ) { - Field field = it.next(); - builder.append( field.getName() ); - builder.append( '=' ); - try { - builder.append( field.get( obj ) ); - } catch ( IllegalArgumentException | IllegalAccessException e ) { - builder.append( "?" ); - } - } - - builder.append( '}' ); - - return builder.toString(); - } - - public static Optional< Point > intersection( final VertexOld x1, final VertexOld x2, final VertexOld x3, final VertexOld x4 ) { - Point point = null; - - final Vector2dOld p = x1.toVector(); - final Vector2dOld r = x2.subtract( x1 ); - final Vector2dOld q = x3.toVector(); - final Vector2dOld s = x4.subtract( x3 ); - - final Vector2dOld qp = q.subtract( p ); - final double qpr = qp.cross( r ); - final double rs = r.cross( s ); - - // Are the two lines parallel - if ( rs == 0 ) { - final Vector2dOld u = x2.toVector(); - final Vector2dOld v = x4.toVector(); - - // For normalizing against pr - final double rr = r.dot( r ); - - // t0 and t1 represent the distance of the second edge endpoints relative to the first edge normalized by r - // t0 = ( ( q - p ) . r ) / rr; - double t0 = qp.dot( r ) / rr; - // t1 = ( ( q + s - p ) . r ) / rr - double t1 = v.subtract( p ).dot( r ) / rr; - - // If the vectors are pointing in the opposite directions, then swap t0 and t1 - final boolean inverted = s.dot( r ) < 0; - if ( inverted ) { - double temp = t0; - t0 = t1; - t1 = temp; - } - - if ( qpr == 0 ) { - if ( t0 < 1 & t1 > 0 ) { - final double midt = Math.max( t0, 0 ) + Math.min( t1, 1 ) / 2.0; - final Vector2dOld midpoint = r.multiply( midt ).add( p ); - point = midpoint.toPoint(); - } else { - final Vector2dOld pu = t0 > 1 ? u : p; - // 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 = midpoint.toPoint(); - } - } - } else { - // Calculate t and u - final double t = qp.cross( s ) / rs; - final double u = qpr / rs; - - // Do the line segments intersect - if ( t >= 0 && t <= 1 && u >= 0 && u <= 1 ) { - // Calculate the point of intersection - final Vector2dOld intersection = r.multiply( t ).add( p ); - point = intersection.toPoint(); - } - } - return Optional.ofNullable( point ); - } - - // https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect - // Vector based closest intersection method - public static Point closestPoint( final VertexOld x1, final VertexOld x2, final VertexOld x3, final VertexOld x4 ) { - Point point = new Point( 0, 0 ); - - final Vector2dOld p = x1.toVector(); - final Vector2dOld r = x2.subtract( x1 ); - final Vector2dOld q = x3.toVector(); - final Vector2dOld s = x4.subtract( x3 ); - - final Vector2dOld qp = q.subtract( p ); - final double qpr = qp.cross( r ); - final double rs = r.cross( s ); - - // Are the two lines parallel - if ( rs == 0 ) { - final Vector2dOld u = x2.toVector(); - final Vector2dOld v = x4.toVector(); - - // For normalizing against pr - final double rr = r.dot( r ); - - // t0 and t1 represent the distance of the second edge endpoints relative to the first edge normalized by r - // t0 = ( ( q - p ) . r ) / rr; - double t0 = qp.dot( r ) / rr; - // t1 = ( ( q + s - p ) . r ) / rr - double t1 = v.subtract( p ).dot( r ) / rr; - - // If the vectors are pointing in the opposite directions, then swap t0 and t1 - final boolean inverted = s.dot( r ) < 0; - if ( inverted ) { - double temp = t0; - t0 = t1; - t1 = temp; - } - - // Do the lines overlap - if ( t0 < 1 && t1 > 0 ) { - // The midpoint is an arbitrary point that just happens to look good as an intersection - // Find the midpoint between the overlapping region - final double midt = Math.max( t0, 0 ) + Math.min( t1, 1 ) / 2.0; - // If colinear - 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.setX( midpoint.x ); - point.setY( midpoint.y ); - } else { - // Calculate the midpoint along pr - final Vector2dOld midp = r.multiply( midt ).add( p ); - // Calculate the perpendicular distance between both lines - final Vector2dOld perp = r.perpendicular(); - final Vector2dOld z = perp.multiply( qp.dot( perp ) / rr ); - // Sum the components - final Vector2dOld midpoint = midp.add( z.divide( 2.0 ) ); - 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 - // If t0 > 1, then the second edge must lie to the right of the first edge - final Vector2dOld pu = t0 > 1 ? u : p; - // 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.setX( midpoint.x ); - point.setY( midpoint.y ); - } - } else { - // Calculate t and u - final double t = qp.cross( s ) / rs; - final double u = qpr / rs; - - // Do the line segments intersect - if ( t >= 0 && t <= 1 && u >= 0 && u <= 1 ) { - // Calculate the point of intersection - final Vector2dOld intersection = r.multiply( t ).add( p ); - 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 - final double nearT = Math.max( Math.min( t, 1 ), 0 ); - final double nearU = Math.max( Math.min( u, 1 ), 0 ); - - final Vector2dOld nearP = r.multiply( nearT ).add( p ); - final Vector2dOld nearQ = s.multiply( nearU ).add( q ); - - 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 deleted file mode 100644 index 7ba7c21..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dComparator.java +++ /dev/null @@ -1,19 +0,0 @@ -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 ); - - // Sorts by x, then y - public final int compare( Vector2d a, Vector2d b ) { - double x = compareX( a, b ); - if ( x == 0 ) { - return Double.compare( compareY( a, b ), 0 ); - } - return Double.compare( x, 0 ); - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dOld.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dOld.java deleted file mode 100644 index a2da2b5..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vector2dOld.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -import com.aaaaahhhhhhh.bananapuncher714.mesh.Point; - -public class Vector2dOld { - final double x; - final double y; - - Vector2dOld( double x, double y ) { - this.x = x; - this.y = y; - } - - public double cross( Vector2dOld other ) { - return ( x * other.y ) - ( y * other.x ); - } - - public double dot( Vector2dOld other ) { - return x * other.x + y * other.y; - } - - public Vector2dOld subtract( Vector2dOld other ) { - return new Vector2dOld( x - other.x , y - other.y ); - } - - public Vector2dOld add( Vector2dOld other ) { - return new Vector2dOld( x + other.x, y + other.y ); - } - - public Vector2dOld perpendicular() { - return new Vector2dOld( -y, x ); - } - - public Vector2dOld multiply( double v ) { - return new Vector2dOld( x * v, y * v ); - } - - public Vector2dOld divide( double v ) { - return new Vector2dOld( x / v, y / v ); - } - - public Vector2dOld normalize() { - return divide( Math.sqrt( dot( this ) ) ); - } - - public Point toPoint() { - return new Point( x, y ); - } - - public double length() { - return Math.sqrt( dot( this ) ); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - long temp; - temp = Double.doubleToLongBits(x); - result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(y); - result = prime * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Vector2dOld other = (Vector2dOld) obj; - if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) - return false; - if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) - return false; - return true; - } - - @Override - public String toString() { - return "Vector2d{x=" + x + ",y=" + y + "}"; - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vertex.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vertex.java deleted file mode 100644 index c3d7730..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Vertex.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d; - -public class Vertex { - private HalfWing wing; - private Vector2d position; - - public Vertex( HalfWing wing ) { - setWing( wing ); - setPosition( new Vector2d() ); - } - - public HalfWing getWing() { - return wing; - } - - public Vertex setWing( HalfWing wing ) { - this.wing = wing; - return this; - } - - public Vector2d getPosition() { - return position; - } - - public Vertex setPosition( Vector2d vec ) { - this.position = vec; - return this; - } - - public void update() { - HalfWing wing = this.wing; - do { - wing.setOrigin( this ); - } while ( ( wing = wing.getPrev() ) != this.wing ); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((position == null) ? 0 : position.hashCode()); - return result; - } - - @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/tess4j/VertexOld.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/VertexOld.java deleted file mode 100644 index af33a19..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/VertexOld.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -public class VertexOld implements Comparable< VertexOld > { - double x; - double y; - - // An edge that is attached to this vertex - HalfEdge edge; - - public VertexOld( HalfEdge edge ) { - this.edge = edge; - } - - public void assign() { - HalfEdge temp = edge; - do { - temp.origin = this; - temp = temp.nextOrigin; - } while ( temp != edge ); - } - - public boolean isEqualTo( VertexOld o ) { - return x == o.x && y == o.y; - } - - public boolean lessThanOrEqualTo( VertexOld o ) { - return ( x < o.x ) || ( x == o.x && y <= o.y ); - } - - public boolean lessThanOrEqualTo2( VertexOld o ) { - return ( y < o.y ) || ( y == o.y && x <= o.x ); - } - - public double compareTo( VertexOld v1, VertexOld v2 ) { - double x1 = x - v1.x; - double x2 = v2.x - x; - - if ( x1 + x2 > 0 ) { - return ( y - v2.y ) * x1 + ( y - v1.y ) * x2; - } - return 0; - } - - public Vector2dOld subtract( VertexOld other ) { - return new Vector2dOld( x - other.x, y - other.y ); - } - - public Vector2dOld toVector() { - return new Vector2dOld( x, y ); - } - - /* - * Get the vertical distance from this point to the line segment represented by v1 and v2 - * - * This is assuming the following is true: - * - v1 <= this <= v2 - * If v1v2 is vertical (and thus passes through this), the result is 0 - */ - public double verticalDistance( VertexOld v1, VertexOld v2 ) { - // x1 >= 0 - double x1 = x - v1.x; - // x2 >= 0 - double x2 = v2.x - x; - - // Check if it is not vertical - if ( x1 + x2 > 0 ) { - // Find the slope - // Use smaller x value for better double precision, I suppose - if ( x1 < x2 ) { - return ( y - v1.y ) + ( ( v1.y - v2.y ) * ( x1 / ( x1 + x2 ) ) ); - } else { - return ( y - v2.y ) + ( ( v2.y - v1.y ) * ( x2 / ( x1 + x2 ) ) ); - } - } - return 0; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - VertexOld other = (VertexOld) obj; - if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) - return false; - if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) - return false; - return true; - } - - @Override - public String toString() { - return "Vertex{x=" + x + ",y=" + y + "}"; - } - - @Override - public int compareTo( final VertexOld o ) { - final int sortX = Double.compare( x, o.x ); - return sortX == 0 ? Double.compare( y, o.y ) : sortX; - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/WindingRule.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/WindingRule.java deleted file mode 100644 index 21f4b0f..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/WindingRule.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j; - -/** - * A winding rule describes how a region is modified, particularly when crossing a border - * between a known region to an unknown region. The inverse winding rule must provide a - * symmetric change such that regions can be converted consistently. Normally, the same - * winding 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. - * - * @author BananaPuncher714 - * - * @param A region type that can be modified by this rule - */ -public interface WindingRule< T extends Region > { - // Apply this rule to the region, and return a new region - T apply( T region ); - // Get the inverse of this rule - WindingRule< T > inverse(); -} \ No newline at end of file diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/region/RegionSimple.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/region/RegionSimple.java deleted file mode 100644 index f705c6e..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/region/RegionSimple.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j.region; - -import com.aaaaahhhhhhh.bananapuncher714.tess4j.Region; - -public class RegionSimple extends Region { - private int windingNumber; - private 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 "Region: " + windingNumber + ", " + rule; - } - - public int getWindingNumber() { - return windingNumber; - } - - public RegionSimple setWindingNumber( int number ) { - this.windingNumber = number; - return this; - } - - public int increment() { - return ++windingNumber; - } - - public int decrement() { - return --windingNumber; - } - - public enum GluWindingRule { - ODD, - NONZERO, - POSITIVE, - NEGATIVE, - ABS_GEQ_TWO - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/region/WindingRuleSimple.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/region/WindingRuleSimple.java deleted file mode 100644 index 2445b6f..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/region/WindingRuleSimple.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.aaaaahhhhhhh.bananapuncher714.tess4j.region; - -import com.aaaaahhhhhhh.bananapuncher714.tess4j.WindingRule; - -public class WindingRuleSimple implements WindingRule< RegionSimple > { - private final boolean inverse; - private WindingRuleSimple opposite; - - public WindingRuleSimple() { - this( false ); - } - - public WindingRuleSimple( boolean inverse ) { - this.inverse = inverse; - } - - @Override - public RegionSimple apply( RegionSimple region ) { - RegionSimple copy = new RegionSimple( region ); - if ( inverse ) { - copy.decrement(); - } else { - copy.increment(); - } - return copy; - } - - @Override - public WindingRuleSimple inverse() { - if ( opposite == null ) { - opposite = new WindingRuleSimple( !inverse ); - opposite.opposite = this; - } - return opposite; - } -} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/vertex_reduction.txt b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/vertex_reduction.txt deleted file mode 100644 index 00755e7..0000000 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/vertex_reduction.txt +++ /dev/null @@ -1,41 +0,0 @@ -The task of triangulation can be completed in logarithmic time, based on the amount of regions and how many nodes each region contains. However, it is not -uncommon for regions to contain many segments that lie on the same line. When this is the case, often times the region can be split into multiple smaller -regions. The benefit of doing so is that there are less triangles that are generated, and each region can be triangulated in parallel, for a bonus speed -increase. The downside is that splitting a region in the most optimal method can be quite costly. It would take polynomial time based on the number of -nodes in order to find the combination of regions that would decrease triangle count the most. However, it can be argued that for certain region shapes -which may occur frequently such as the topology of connected Minecraft blocks, it may be worth the investment if time is less of an issue, and memory -usage matters more. - -Ideal time: O(n) or O(nlog(n)) - -For each point: -- Find all intersecting edges - - Easy check, see which side both ends of each segment are on - - If different sides, then it intersects with the line -- Find all other segments on the same line - - Easy check, find all lines with the same slope and intersection point or something - - Need to check if segment can be connected to - - This means checking all intersecting edges - - Also need to check for individual points - - A segment and a point results in 1 less triangle - - Two segments results in 2 less triangles -- Find all intersections with potential segments - - For each potential segment, check for intersection with each other - - Count how many it intersects with - - Greedy method... Add lines with least amount of intersections first? - - According to the rule of mutual crosses, the total amount of intersections is always even - -The overall algorithm can be broken down into 3 parts: -- Finding all possible segments between 3 or more points -- Removing all connections that intersect with the polygon edges -- Determining how many other possible segments intersect with each other -The first two are both O(n^2) time, which is unfortunate. It is unknown if it could -be made into O(logn) or better. -The last part is O(logn) - -Obviously, one can determine which points are visible from one point by iterating -over every edge and building a depth field. Then, all potential connecting segments -can be found by checking each visible point. -Alternatively, one can check each point to see if it lies on the same segment? -A logical structure to hold a segment would be the two endpoints, and any points that are -contained by the...