From d45d644b32a081b62d975d5e87f4db530867f25d Mon Sep 17 00:00:00 2001 From: BananaPuncher714 Date: Mon, 26 May 2025 16:38:55 -0400 Subject: [PATCH] Change to use facet format for saving and processing chunks Fixed bug where merging and splitting connected regions may result in a chain desync Added spawn size to minie spawn command Added live chunk generation Use a smarter more conservative facet generation method for each chunk to reduce initial mesh simplification costs --- .gitignore | 3 +- .../bananapuncher714/mesh/Mesh.java | 31 +- .../minietest/MeshingTest3.java | 60 +- .../minietest/MeshingTest4.java | 124 ++- .../minietest/MeshingTest5.java | 167 +++ .../minietest/MeshingTest6.java | 985 ++++++++++++++++++ .../minietest/MiniePlugin.java | 389 +++++-- .../validator/InputValidatorDouble.java | 39 + .../minietest/objects/mesh/MeshBuilder.java | 10 +- 9 files changed, 1660 insertions(+), 148 deletions(-) create mode 100644 src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest5.java create mode 100644 src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest6.java create mode 100644 src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/command/validator/InputValidatorDouble.java diff --git a/.gitignore b/.gitignore index d3f0f49..359475f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ images chunk_models chunk_models_new chunks_test -chunk_models_test \ No newline at end of file +chunk_models_test +facets \ No newline at end of file diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java index 2dcb53a..7c4ff05 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/Mesh.java @@ -1248,13 +1248,6 @@ public class Mesh< T extends Region > { } } - /* - * Reference to a collection of links - */ - class ChainReference { - Collection< Link > links = new LinkedHashSet< Link >(); - } - /** * A partition represents a monotone section of a polygon at the current * scan line. At any given time, a vertical scan line which passes through @@ -1288,7 +1281,7 @@ public class Mesh< T extends Region > { // Keep track of all chains that were formed in this current partition. // For all new chains, see what previous chains it may intersect with - ChainReference chains = new ChainReference(); + Collection< Link > chains = new LinkedHashSet< Link >(); Partition( final HalfEdge lower, final HalfEdge upper ) { this.lower = lower; @@ -1568,7 +1561,7 @@ public class Mesh< T extends Region > { final Vertex chainMidpoint = chain.getMidpoint(); final Vertex chainDestination = chain.getDest(); - for ( final Link other : chains.links ) { + for ( final Link other : chains ) { if ( other.getOrigin() == chainOrigin && other.getDest() == chainDestination ) { // Already added this chain, so break out early return; @@ -1595,7 +1588,7 @@ public class Mesh< T extends Region > { * This is because it depends on the new link's destination being less than * any other vertex in all the existing links, which should never be possible. */ - chains.links.parallelStream().forEach( other -> { + chains.parallelStream().forEach( other -> { if ( other.getMidpoint() == chainOrigin && other.getDest() == chainMidpoint ) { // The new chain is a continuation of this chain other.next = chain; @@ -1709,7 +1702,7 @@ public class Mesh< T extends Region > { } } } ); - chains.links.add( chain ); + chains.add( chain ); } void add( final Vertex vertex, final VisibilityRegion region ) { @@ -1829,7 +1822,7 @@ public class Mesh< T extends Region > { partition.incrementUpper( partition.lower.getSym() ); // Gather all chains from the partition - chainLinks.add( partition.chains.links ); + chainLinks.add( partition.chains ); // Remove this partition from the lower edge map as well lowerEdgeMap.remove( partition.lower ); @@ -2017,9 +2010,17 @@ public class Mesh< T extends Region > { bottom.incrementUpper( bottomLeft ); // Merge the refs - if ( top.chains.links != bottom.chains.links ) { - top.chains.links.addAll( bottom.chains.links ); - bottom.chains.links = top.chains.links; + if ( top.chains != bottom.chains ) { + top.chains.addAll( bottom.chains ); + + // Find all other partitions which share the same chain reference + // and set those to the new total collection + for ( final Partition partition : lowerEdgeMap.values() ) { + if ( partition.chains == bottom.chains ) { + partition.chains = top.chains; + } + } + bottom.chains = top.chains; } // Merge the two partitions diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java index 22d0f96..3293a92 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest3.java @@ -6,7 +6,10 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import org.bukkit.util.Vector; @@ -28,11 +31,18 @@ public class MeshingTest3 { public static void main( String[] args ) { if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) { System.out.println( "Found " + CHUNK_DIR.list().length + " files" ); - for ( int i = 0; i < CHUNK_DIR.listFiles().length; ++i ) { - final File file = CHUNK_DIR.listFiles()[ i ]; - System.out.println( ( i + 1 ) + "/" + CHUNK_DIR.listFiles().length + ":\tMeshing " + file ); - long start = System.currentTimeMillis(); - final List< Plane > planes = mesh( file ); + + final long allStart = System.currentTimeMillis(); + final AtomicInteger index = new AtomicInteger( 0 ); + final Collection< File > files = Arrays.asList( CHUNK_DIR.listFiles() ); + files.parallelStream().forEach( f -> { + final StringBuilder builder = new StringBuilder(); + + builder.append( "Meshing file: " + f + "\n" ); + + final long start = System.currentTimeMillis(); + final List< Plane > planes = mesh( f, builder ); + planes.parallelStream().forEach( p -> { try { process( p ); @@ -45,8 +55,32 @@ public class MeshingTest3 { long time = System.currentTimeMillis() - start; - System.out.println( "\tTook " + time + "ms" ); - } + builder.append( "\tTook " + time + "ms\n" ); + builder.append( "\tCompleted " + index.incrementAndGet() + "/" + files.size() ); + + System.out.println( builder.toString() ); + } ); + System.out.println( "Took a total of " + ( System.currentTimeMillis() - allStart ) + "ms" ); + +// for ( int i = 0; i < CHUNK_DIR.listFiles().length; ++i ) { +// final File file = CHUNK_DIR.listFiles()[ i ]; +// System.out.println( ( i + 1 ) + "/" + CHUNK_DIR.listFiles().length + ":\tMeshing " + file ); +// long start = System.currentTimeMillis(); +// final List< Plane > planes = mesh( file, null ); +// planes.parallelStream().forEach( p -> { +// try { +// process( p ); +// } catch ( IllegalStateException e ) { +// e.printStackTrace(); +// +// System.exit( 1 ); +// } +// } ); +// +// long time = System.currentTimeMillis() - start; +// +// System.out.println( "\tTook " + time + "ms" ); +// } // final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-10,-10" ) ); // 25 // for ( int i = 0; i < planes.size(); ++i ) { @@ -73,13 +107,11 @@ public class MeshingTest3 { } } - private static List< Plane > mesh( File file ) { + private static List< Plane > mesh( File file, final StringBuilder stringBuilder ) { 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( "\tParsing chunk " + location ); - // First parse the file to get a list of all bounding boxes that we can use List< AABB > boxes = new ArrayList< AABB >(); try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) { @@ -104,7 +136,13 @@ public class MeshingTest3 { return null; } - System.out.println( "\tFound " + boxes.size() + " boxes" ); + if ( stringBuilder != null ) { + stringBuilder.append( "\tParsing chunk " + location + "\n" ); + stringBuilder.append( "\tFound " + boxes.size() + " boxes\n" ); + } else { + System.out.println( "\tParsing chunk " + location ); + System.out.println( "\tFound " + boxes.size() + " boxes" ); + } // Now that we have a bunch of bounding boxes, do whatever MeshBuilder builder = new MeshBuilder(); diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest4.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest4.java index e06b43b..222e8fd 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest4.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest4.java @@ -7,6 +7,7 @@ import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -14,7 +15,9 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.bukkit.util.Vector; @@ -37,16 +40,41 @@ public class MeshingTest4 { public static void main( String[] args ) { if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) { System.out.println( "Found " + CHUNK_DIR.list().length + " files" ); - for ( int i = 0; i < CHUNK_DIR.listFiles().length; ++i ) { - final File file = CHUNK_DIR.listFiles()[ i ]; - System.out.println( ( i + 1 ) + "/" + CHUNK_DIR.listFiles().length + ":\tMeshing " + file ); - long start = System.currentTimeMillis(); - final List< Plane > planes = mesh( file ); - savePly2( new File( MODEL_DIR, file.getName() + ".ply" ), planes.parallelStream().flatMap( p -> process( p ).parallelStream().map( p::convert ) ).collect( Collectors.toSet() ) ); - long time = System.currentTimeMillis() - start; + + // Omega mesh time +// omegaMesh( CHUNK_DIR, new File( MODEL_DIR, "omega.ply" ) ); + + final long allStart = System.currentTimeMillis(); + final AtomicInteger index = new AtomicInteger( 0 ); + final Collection< File > files = Arrays.asList( CHUNK_DIR.listFiles() ); + files.parallelStream().forEach( f -> { + final StringBuilder builder = new StringBuilder(); + + builder.append( "Meshing file: " + f + "\n" ); - System.out.println( "\tTook " + time + "ms" ); - } + final long start = System.currentTimeMillis(); + final List< Plane > planes = mesh( f, builder ); + savePly2( new File( MODEL_DIR, f.getName() + ".ply" ), planes.parallelStream().flatMap( p -> process( p ).parallelStream().map( p::convert ) ).collect( Collectors.toSet() ) ); + + final long time = System.currentTimeMillis() - start; + + builder.append( "\tTook " + time + "ms\n" ); + builder.append( "\tCompleted " + index.incrementAndGet() + "/" + files.size() ); + + System.out.println( builder.toString() ); + } ); + System.out.println( "Took a total of " + ( System.currentTimeMillis() - allStart ) ); + +// for ( int i = 0; i < CHUNK_DIR.listFiles().length; ++i ) { +// final File file = CHUNK_DIR.listFiles()[ i ]; +// System.out.println( ( i + 1 ) + "/" + CHUNK_DIR.listFiles().length + ":\tMeshing " + file ); +// final long start = System.currentTimeMillis(); +// final List< Plane > planes = mesh( file ); +// savePly2( new File( MODEL_DIR, file.getName() + ".ply" ), planes.parallelStream().flatMap( p -> process( p ).parallelStream().map( p::convert ) ).collect( Collectors.toSet() ) ); +// long time = System.currentTimeMillis() - start; +// +// System.out.println( "\tTook " + time + "ms" ); +// } } else { System.err.println( "No such directory exists: " + CHUNK_DIR ); } @@ -156,6 +184,35 @@ public class MeshingTest4 { } } + private static void omegaMesh( final File directory, final File output ) { + final List< AABB > allBoxes = new ArrayList< AABB >(); + for ( int i = 0; i < directory.listFiles().length; ++i ) { + final File file = directory.listFiles()[ i ]; + System.out.println( ( i + 1 ) + "/" + directory.listFiles().length + ": Getting meshes for: " + file ); + allBoxes.addAll( getBoxes( file ) ); + } + System.out.println( "Gathered " + allBoxes.size() + " boxes" ); + final MeshBuilder builder = new MeshBuilder(); + int boxCount = 0; + for ( final AABB box : allBoxes ) { + for ( final Facet facet : generateFacetsFor( box ) ) { + builder.addFacet( facet ); + } + if ( ++boxCount % 100_000 == 0 ) { + System.out.println( "Added box " + boxCount + "/" + allBoxes.size() + "\t" + ( boxCount / ( double ) allBoxes.size() ) + "%" ); + } + } + System.out.println( "Starting to mesh " + builder.planes.size() + " planes" ); + final long start = System.currentTimeMillis(); + final AtomicInteger counter = new AtomicInteger(); + savePly2( output, builder.planes.parallelStream().flatMap( p -> { + final Stream< Facet > facets = process( p ).parallelStream().map( p::convert ); + System.out.println( "Finished " + counter.incrementAndGet() + "/" + builder.planes.size() ); + return facets; + } ).collect( Collectors.toSet() ) ); + System.out.println( "\tTook " + ( System.currentTimeMillis() - start ) + "ms" ); + } + private static Collection< Polygon > process( final Plane plane ) { if ( plane != null ) { Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); @@ -170,13 +227,11 @@ public class MeshingTest4 { } } - private static List< Plane > mesh( File file ) { + private static List< Plane > mesh( File file, final StringBuilder stringBuilder ) { 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( "\tParsing chunk " + location ); - // First parse the file to get a list of all bounding boxes that we can use List< AABB > boxes = new ArrayList< AABB >(); try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) { @@ -209,12 +264,53 @@ public class MeshingTest4 { builder.addFacet( facet ); } } - - System.out.println( "\tFound " + boxes.size() + " boxes and " + builder.planes.size() + " planes" ); + if ( stringBuilder != null ) { + stringBuilder.append( "\tParsing chunk " + location + "\n" ); + stringBuilder.append( "\tFound " + boxes.size() + " boxes and " + builder.planes.size() + " planes\n" ); + } else { + System.out.println( "\tParsing chunk " + location ); + System.out.println( "\tFound " + boxes.size() + " boxes and " + builder.planes.size() + " planes" ); + } + return builder.planes; } + private static List< AABB > getBoxes( final File file ) { + final String name = file.getName(); + final String[] split = name.split( "," ); + final int chunkX = Integer.parseInt( split[ 1 ] ); + final int chunkZ = Integer.parseInt( split[ 2 ] ); + final int blockX = chunkX << 4; + final int blockZ = chunkZ << 4; + + // First parse the file to get a list of all bounding boxes that we can use + final 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 + blockX, minY, minZ + blockZ, maxX + blockX, maxY, maxZ + blockZ ) ); + } + } + } catch ( FileNotFoundException e ) { + e.printStackTrace(); + return Collections.emptyList(); + } catch ( IOException e ) { + e.printStackTrace(); + return Collections.emptyList(); + } + + return boxes; + } + private static List< Facet > generateFacetsFor( AABB box ) { List< Facet > facets = new ArrayList< Facet >(); diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest5.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest5.java new file mode 100644 index 0000000..90c4194 --- /dev/null +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest5.java @@ -0,0 +1,167 @@ +package com.aaaaahhhhhhh.bananapuncher714.minietest; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.bukkit.util.Vector; + +import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh; +import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon; +import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionRuleWinding; +import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple; +import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple.GluWindingRule; +import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkLocation; +import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Facet; +import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.MeshBuilder; +import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Plane; + +public class MeshingTest5 { + private static final File BASE = new File( System.getProperty( "user.dir" ) ); + private static final File FACET_DIR = new File( BASE, "facets" ); + + public static void main( String[] args ) { + if ( FACET_DIR.exists() && FACET_DIR.isDirectory() ) { + System.out.println( "Found " + FACET_DIR.list().length + " files" ); + + final long allStart = System.currentTimeMillis(); + final AtomicInteger index = new AtomicInteger( 0 ); + final Collection< File > files = Arrays.asList( FACET_DIR.listFiles() ); + files.parallelStream().forEach( f -> { + final StringBuilder builder = new StringBuilder(); + + builder.append( "Meshing file: " + f + "\n" ); + + final long start = System.currentTimeMillis(); + final List< Plane > planes = mesh( f, builder ); + + planes.parallelStream().forEach( p -> { + try { + process( p ); + } catch ( IllegalStateException e ) { + e.printStackTrace(); + + System.exit( 1 ); + } + } ); + + long time = System.currentTimeMillis() - start; + + builder.append( "\tTook " + time + "ms\n" ); + builder.append( "\tCompleted " + index.incrementAndGet() + "/" + files.size() ); + + System.out.println( builder.toString() ); + } ); + System.out.println( "Took a total of " + ( System.currentTimeMillis() - allStart ) + "ms" ); + +// for ( int i = 0; i < FACET_DIR.listFiles().length; ++i ) { +// final File file = FACET_DIR.listFiles()[ i ]; +// System.out.println( ( i + 1 ) + "/" + FACET_DIR.listFiles().length + ":\tMeshing " + file ); +// long start = System.currentTimeMillis(); +// final List< Plane > planes = mesh( file, null ); +// planes.parallelStream().forEach( p -> { +// try { +// process( p ); +// } catch ( IllegalStateException e ) { +// e.printStackTrace(); +// +// System.exit( 1 ); +// } +// } ); +// +// long time = System.currentTimeMillis() - start; +// +// System.out.println( "\tTook " + time + "ms" ); +// } + +// final List< Plane > planes = mesh( new File( FACET_DIR, "world,-1,2.facet" ), null ); // 25 +// for ( int i = 0; i < planes.size(); ++i ) { +// System.out.println( "Meshing plane " + i ); +// process( planes.get( i ) ); +// } + +// process( planes.get( 73 ) ); + } else { + System.err.println( "No such directory exists: " + FACET_DIR ); + } + } + + private static void process( final Plane plane ) { + if ( plane != null ) { + Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); + for ( Polygon poly : plane.polygons ) { + mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE ); + } + + mesh.meshify(); + } else { + System.out.println( "No data!" ); + } + } + + private static List< Plane > mesh( File file, final StringBuilder stringBuilder ) { + final String name = file.getName().substring( 0, file.getName().indexOf( '.' ) ); + final String[] split = name.split( "," ); + final ChunkLocation location = new ChunkLocation( split[ 0 ], Integer.parseInt( split[ 1 ] ), Integer.parseInt( split[ 2 ] ) ); + + // First parse the file to get a list of all facets + final List< Facet > facets = new ArrayList< Facet >(); + try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) { + String line; + while ( ( line = reader.readLine() ) != null ) { + if ( !line.isEmpty() ) { + String[] values = line.split( " " ); + final int facetCount = Integer.parseInt( values[ 0 ] ); + final double nx = Double.parseDouble( values[ 1 ] ); + final double ny = Double.parseDouble( values[ 2 ] ); + final double nz = Double.parseDouble( values[ 3 ] ); + + final Facet facet = new Facet(); + facet.normal = new Vector( nx, ny, nz ); + + int point = 0; + while ( point < facetCount && ( line = reader.readLine() ) != null ) { + if ( !line.isEmpty() ) { + String[] values2 = line.split( " " ); + final double px = Double.parseDouble( values2[ 0 ] ); + final double py = Double.parseDouble( values2[ 1 ] ); + final double pz = Double.parseDouble( values2[ 2 ] ); + facet.points.add( new Vector( px, py, pz ) ); + ++point; + } + } + facets.add( facet ); + } + } + } catch ( FileNotFoundException e ) { + e.printStackTrace(); + return null; + } catch ( IOException e ) { + e.printStackTrace(); + return null; + } + + if ( stringBuilder != null ) { + stringBuilder.append( "\tParsing chunk " + location + "\n" ); + stringBuilder.append( "\tFound " + facets.size() + " facets\n" ); + } else { + System.out.println( "\tParsing chunk " + location ); + System.out.println( "\tFound " + facets.size() + " facets" ); + } + // Now that we have a bunch of bounding boxes, do whatever + + MeshBuilder builder = new MeshBuilder(); + for ( Facet facet : facets ) { + builder.addFacet( facet ); + } + + return builder.planes; + } +} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest6.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest6.java new file mode 100644 index 0000000..44c6100 --- /dev/null +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MeshingTest6.java @@ -0,0 +1,985 @@ +package com.aaaaahhhhhhh.bananapuncher714.minietest; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.MouseInfo; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; + +import org.bukkit.util.Vector; + +import com.aaaaahhhhhhh.bananapuncher714.mesh.Chain; +import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh; +import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh.MeshEventHandler; +import com.aaaaahhhhhhh.bananapuncher714.mesh.Point; +import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon; +import com.aaaaahhhhhhh.bananapuncher714.mesh.Segment; +import com.aaaaahhhhhhh.bananapuncher714.mesh.Vector2d; +import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionRuleWinding; +import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple; +import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple.GluWindingRule; +import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkLocation; +import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Facet; +import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.MeshBuilder; +import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Plane; + +public class MeshingTest6 extends JPanel { + private static final File BASE = new File( System.getProperty( "user.dir" ) ); + private static final File FACET_DIR = new File( BASE, "facets" ); + + JFrame f; + +// private int windowWidth = 1200; +// private int windowHeight = 1000; + + private int windowWidth = 1200; + private int windowHeight = 800; + +// private int centerX = 600; +// private int centerY = 400; + + private int offsetX = windowWidth >> 1; + private int offsetY = windowHeight >> 1; + + private double centerX = 0; + private double centerY = 0; + + private double scale = 8; + + private int stringBoardX = -100; + private int stringBoardY = 20; + + final private List< Plane > planes; + final private PlaneData[] data; + int index = -1; + + private JLabel statusBar; + private JComboBox< String > selectionBox; + + private ReentrantLock lock = new ReentrantLock(); + + public static void main( String[] args ) { + if ( FACET_DIR.exists() && FACET_DIR.isDirectory() ) { + System.out.println( "Found " + FACET_DIR.list().length + " files" ); +// for ( File file : FACET_DIR.listFiles() ) { +// List< Plane > planes = mesh( file ); + +// Plane draw = null; + +// System.out.println( "Planes: " + planes.size() ); +// int planeIndex = 0; +// for ( planeIndex = 0; planeIndex < planes.size(); ++planeIndex ) { +// if ( planes.get( planeIndex ).polygons.size() > 100 ) { +// break; +// } +// } + +// Collections.sort( planes, ( a, b ) -> Integer.compare( b.polygons.size(), a.polygons.size() ) ); +// draw = planes.get( 4 ); + + // Select a random plane to draw +// draw = planes.get( new Random().nextInt( planes.size() ) ); + +// System.out.println( "Draw is " + draw ); +// Attempt to mesh all planes +// try { +// test( planes ); +// } catch ( PolygonException e ) { +// draw = e.getPlane(); +// System.out.println( "Draw is now " + draw ); +// } + +// final PlaneData data = process( draw ); +// final Collection< Polygon > polys = triangulate( draw ); +// final Collection< Polygon > polys = test(); +// System.out.println( "Polygon count: " + data.polygons.size() ); + +// final int startingIndex = planeIndex; +// SwingUtilities.invokeLater( new Runnable() { +// @Override +// public void run() { +// new MeshingTest2( planes ).setIndex( startingIndex ).start();; +// } +// } ); +// break; +// } + + final List< Plane > masterPlanes = new ArrayList< Plane >(); + System.out.println( "Facet files: " + FACET_DIR.listFiles().length ); + int limit = 1; + for ( File file : FACET_DIR.listFiles() ) { + masterPlanes.addAll( mesh( file ) ); + if ( --limit <= 0 ) { + break; + } else { + System.out.println( "Remaining: " + limit ); + } + } + +// final List< Plane > planes = mesh( new File( FACET_DIR, "world,-1,2.facet" ) ); +// masterPlanes.add( planes.get( 73 ) ); +// masterPlanes.addAll( planes ); + + System.out.println( "Adding test plane" ); + masterPlanes.add( getTestPlane() ); + + SwingUtilities.invokeLater( new Runnable() { + @Override + public void run() { + new MeshingTest6( masterPlanes ).start(); + } + } ); + } else { + System.err.println( "No such directory exists: " + FACET_DIR ); + } + } + + private static List< Plane > mesh( File file ) { + final String name = file.getName().substring( 0, file.getName().indexOf( '.' ) ); + final String[] split = name.split( "," ); + final 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 facets + final List< Facet > facets = new ArrayList< Facet >(); + try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) { + String line; + while ( ( line = reader.readLine() ) != null ) { + if ( !line.isEmpty() ) { + String[] values = line.split( " " ); + final int facetCount = Integer.parseInt( values[ 0 ] ); + final double nx = Double.parseDouble( values[ 1 ] ); + final double ny = Double.parseDouble( values[ 2 ] ); + final double nz = Double.parseDouble( values[ 3 ] ); + + final Facet facet = new Facet(); + facet.normal = new Vector( nx, ny, nz ); + + int point = 0; + while ( point < facetCount && ( line = reader.readLine() ) != null ) { + if ( !line.isEmpty() ) { + String[] values2 = line.split( " " ); + final double px = Double.parseDouble( values2[ 0 ] ); + final double py = Double.parseDouble( values2[ 1 ] ); + final double pz = Double.parseDouble( values2[ 2 ] ); + facet.points.add( new Vector( px, py, pz ) ); + ++point; + } + } + facets.add( facet ); + } + } + } catch ( FileNotFoundException e ) { + e.printStackTrace(); + return null; + } catch ( IOException e ) { + e.printStackTrace(); + return null; + } + + // Now that we have a bunch of facets, do whatever + System.out.println( "Found " + facets.size() + " facets" ); + + MeshBuilder builder = new MeshBuilder(); + for ( Facet facet : facets ) { + builder.addFacet( facet ); + } + + return builder.planes; + } + + public static class PolygonException extends RuntimeException { + Plane plane; + + PolygonException( Plane plane ) { + this.plane = plane; + } + + Plane getPlane() { + return plane; + } + } + + private static void test( final Collection< Plane > planes ) { + final long processAllStart = System.currentTimeMillis(); + planes.parallelStream().forEach( plane -> { + try { + Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); + + for ( Polygon poly : plane.polygons ) { + mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE ); + } + long start = System.currentTimeMillis(); + mesh.simplify(); + mesh.generateRegions(); + final Collection< Polygon > polys = mesh.copyOf().mesh(); + long end = System.currentTimeMillis(); + System.out.println( plane.polygons.size() + " to " + polys.size() + ":\t " + ( end - start ) + "ms" ); + } catch ( IllegalStateException e ) { + e.printStackTrace(); + throw new PolygonException( plane ); + } + } ); + final long processAllEnd = System.currentTimeMillis(); + System.out.println( "Took " + ( processAllEnd - processAllStart ) + "ms to process " + planes.size() + " planes" ); + } + + private static PlaneData process( final Plane plane ) { + if ( plane != null ) { + final PlaneData data = new PlaneData(); + System.out.println( "Norm:\t" + plane.normal ); + System.out.println( "Ref:\t" + plane.point ); + System.out.println( "Size:\t" + plane.polygons.size() ); + + Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); + for ( Polygon poly : plane.polygons ) { + mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE ); + } + + System.out.println( "Initial vertex count: " + mesh.getVertices().size() ); + System.out.println( "Initial edge count: " + ( mesh.getEdgeCount() / 2 ) ); + + long start = System.currentTimeMillis(); + mesh.simplify(); + + System.out.println( "After simplify vertex count: " + mesh.getVertices().size() ); + System.out.println( "After simplify edge count: " + ( mesh.getEdgeCount() / 2 ) ); + + data.vertices = mesh.getVertices(); + data.edges = mesh.getEdges(); + + try { + mesh.generateRegions(); + + data.vertices = mesh.getVertices(); + data.edges = mesh.getEdges(); + } catch ( Exception e ) { + e.printStackTrace(); + + return data; + } + long end = System.currentTimeMillis(); + + System.out.println( "After generating regions vertex count: " + mesh.getVertices().size() ); + System.out.println( "After generating regions edge count: " + ( mesh.getEdgeCount() / 2 ) ); + + mesh.addHandler( new MeshEventHandler() { + @Override + public void onChainGenerationEvent( Collection< Chain > chains ) { + data.chains = chains; + } + + @Override + public void onPreChainMergeEvent( Collection< Chain > chains ) { + data.selectedChains = chains; + + final List< Chain > sorted = new ArrayList< Chain >( chains ); + Collections.sort( sorted, ( a, b ) -> { + final double diff = a.getStart().getX() - b.getStart().getX(); + if ( Math.abs( diff ) < 1e-7 ) { + return Double.compare( a.getStart().getY(), b.getStart().getY() ); + } + return Double.compare( diff, 0 ); + } ); + for ( Chain chain : sorted ) { + System.out.println( "c: " + chain.getPoints() ); + } + + final Collection< Point > points = new HashSet< Point >(); + for ( Chain chain : chains ) { + for ( int i = 1; i < chain.getPoints().size() - 1; ++i ) { + if ( !points.add( chain.getPoints().get( i ) ) ) { + System.out.println( "Intersection at " + chain.getPoints().get( i ) ); + } + } + } + } + + @Override + public void onPartitionEvent( Collection< Polygon > polygons ) { + data.polygons = polygons; + } + } ); + + try { + // If the chains are the issue, set this to false and see if it still meshes + mesh.setMergeChains( true ); + + final Collection< Polygon > triangles = mesh.mesh(); + if ( !triangles.isEmpty() ) { + data.polygons = triangles; + } + } catch ( IllegalStateException e ) { + e.printStackTrace(); + } + +// final List< Point > points = new ArrayList< Point >( data.vertices ); +// Collections.sort( points, ( a, b ) -> { +// final int x = Double.compare( a.getX(), b.getX() ); +// if ( x == 0 ) { +// return Double.compare( a.getY(), b.getY() ); +// } +// return x; +// } ); +// for ( Point v : points ) { +// System.out.println( v.getX() + ", " + v.getY() ); +// } + + System.out.println( "Took " + ( end - start ) + "ms" ); + return data; + } else { + System.out.println( "No data!" ); + return null; + } + } + + private static Collection< Polygon > triangulate( final Plane plane ) { + if ( plane != null ) { + System.out.println( "Norm:\t" + plane.normal ); + System.out.println( "Ref:\t" + plane.point ); + System.out.println( "Size:\t" + plane.polygons.size() ); + + Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); + for ( Polygon poly : plane.polygons ) { + mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE ); + } + + System.out.println( "Initial vertex count: " + mesh.getVertices().size() ); + System.out.println( "Initial edge count: " + ( mesh.getEdgeCount() / 2 ) ); + + long start = System.currentTimeMillis(); + mesh.simplify(); + + System.out.println( "After simplify vertex count: " + mesh.getVertices().size() ); + System.out.println( "After simplify edge count: " + ( mesh.getEdgeCount() / 2 ) ); + + mesh.generateRegions(); + + System.out.println( "After generating regions vertex count: " + mesh.getVertices().size() ); + System.out.println( "After generating regions edge count: " + ( mesh.getEdgeCount() / 2 ) ); + + // Make sure this works + mesh = mesh.copyOf(); + + Collection< Polygon > polys = new HashSet< Polygon >(); + + try { + polys = mesh.mesh(); + } catch ( IllegalStateException e ) { + e.printStackTrace(); + } + + long end = System.currentTimeMillis(); + System.out.println( "Took " + ( end - start ) + "ms" ); + + return polys; + } else { + System.out.println( "No data!" ); + return Collections.emptySet(); + } + } + + private static PlaneData test() { + final PlaneData data = new PlaneData(); + final Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } ); + mesh.addHandler( new MeshEventHandler() { + @Override + public void onChainGenerationEvent( Collection< Chain > chains ) { + data.chains = chains; + } + + @Override + public void onPreChainMergeEvent( Collection< Chain > chains ) { + data.selectedChains = chains; + + for ( Chain chain : chains ) { + System.out.println( "c: " + chain.getPoints() ); + } + } + } ); + +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( -1, 0 ), +// new Point( 1, -2 ), +// new Point( 1, -1 ), +// new Point( 2, -1 ), +// new Point( 1, 0 ), +// new Point( 1, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 5, 0 ), +// new Point( 5, 1 ), +// new Point( 4, 1 ), +// new Point( 4, 2 ), +// new Point( 3, 2 ), +// new Point( 3, 3 ), +// new Point( 2, 3 ), +// new Point( 2, 4 ), +// new Point( 0, 4 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 2, 0 ), +// new Point( 2, 1 ), +// new Point( 1, 1 ), +// new Point( 1, 2 ), +// new Point( 2, 2 ), +// new Point( 2, 4 ), +// new Point( 1, 4 ), +// new Point( 1, 3 ), +// new Point( 0, 3 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 3, 0 ), +// new Point( 3, 1 ), +// new Point( 4, 1 ), +// new Point( 4, 2 ), +// new Point( 2, 2 ), +// new Point( 2, 1 ), +// new Point( 1, 1 ), +// new Point( 0, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); + +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 2, 0 ), +// new Point( 2, 1 ), +// new Point( 3, 1 ), +// new Point( 3, 2 ), +// new Point( 2, 2 ), +// new Point( 2, 5 ), +// new Point( 1, 5 ), +// new Point( 1, 7 ), +// new Point( 0, 7 ), +// new Point( 0, 4 ), +// new Point( 1, 4 ), +// new Point( 1, 1 ), +// new Point( 0, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 1, 0 ), +// new Point( 6, 0 ), +// new Point( 6, 1 ), +// new Point( 4, 1 ), +// new Point( 4, 2 ), +// new Point( 2, 2 ), +// new Point( 2, 3 ), +// new Point( 0, 3 ), +// new Point( 0, 1 ), +// new Point( 1, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 3, 1 ), +// new Point( 3, 0 ), +// new Point( 10, 0 ), +// new Point( 10, 6 ), +// new Point( 9, 6 ), +// new Point( 9, 4 ), +// new Point( 8, 4 ), +// new Point( 8, 3 ), +// new Point( 5, 3 ), +// new Point( 5, 2 ), +// new Point( 0, 2 ), +// new Point( 0, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 7, 2 ), +// new Point( 9, 2 ), +// new Point( 9, 3 ), +// new Point( 7, 3 ) +// ) ), RegionRuleWinding.CLOCKWISE ); + +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 6, 1 ), +// new Point( 7, 2 ), +// new Point( 1, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 2, 2 ), +// new Point( 8, 3 ), +// new Point( 9, 4 ), +// new Point( 3, 3 ) +// ) ), RegionRuleWinding.CLOCKWISE ); + +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 2.816104467407629, -3.7472939407853763 ), +// new Point( 2.815383313894849, -2.9972942874937156 ), +// new Point( 2.0028836894955497, -2.9980755371325607 ), +// new Point( 2.0036048430083304, -3.7480751904242213 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 2.8144217758778085, -1.9972947497715015 ), +// new Point( 2.0019221514785093, -1.998075999410347 ), +// new Point( 2.00264330499129, -2.748075652702007 ), +// new Point( 2.8151429293905887, -2.747294403063162 ) +// ) ), RegionRuleWinding.CLOCKWISE ); + +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 0 ), +// new Point( 1, 0 ), +// new Point( 1, 1 ), +// new Point( 0, 1 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( 0, 2 ), +// new Point( 1, 2 ), +// new Point( 1, 3 ), +// new Point( 0, 3 ) +// ) ), RegionRuleWinding.CLOCKWISE ); +// mesh.addPolygon( new Polygon( Arrays.asList( +// new Point( -1, -1 ), +// new Point( 2, -1 ), +// new Point( 2, 4 ), +// new Point( -1, 4 ) +// ) ), RegionRuleWinding.CLOCKWISE ); + + data.polygons = mesh.mesh(); + data.vertices = mesh.getVertices(); + data.edges = mesh.getEdges(); + + return data; + } + + private static Plane getTestPlane() { + final Plane plane = new Plane(); + + plane.normal = new Vector( 0, 0, 1 ); + plane.point = new Vector( 1, 0, 0 ); + +// plane.polygons.add( new Polygon( Arrays.asList( +// new Point( 1, 0 ), +// new Point( 2, 1 ), +// new Point( 3, 0 ), +// new Point( 4, 1 ), +// new Point( 3, 2 ), +// new Point( 4, 3 ), +// new Point( 3, 4 ), +// new Point( 2, 3 ), +// new Point( 1, 4 ), +// new Point( 0, 3 ), +// new Point( 1, 2 ), +// new Point( 0, 1 ) +// plane.polygons.add( new Polygon( Arrays.asList( +// new Point( 1, 0 ), +// new Point( 2, 1 ), +// new Point( 3, 0 ), +// new Point( 4, 1 ), +// new Point( 3, 2 ), +// new Point( 4, 3 ), +// new Point( 3, 4 ), +// new Point( 2, 3 ), +// new Point( 1, 4 ), +// new Point( 0, 3 ), +// new Point( 1, 2 ), +// new Point( 0, 1 ) +// ) ) ); + + plane.polygons.add( new Polygon( Arrays.asList( + new Point( -1, -1 ), + new Point( 1, -1 ), + new Point( 1, 1 ), + new Point( -1, 1 ) + ) ) ); + plane.polygons.add( new Polygon( Arrays.asList( + new Point( -1, -1 ), + new Point( 1, -1 ), + new Point( 1, 1 ), + new Point( -1, 1 ) + ) ) ); + + plane.polygons.add( new Polygon( Arrays.asList( + new Point( -1, -1 ), + new Point( 1, -1 ), + new Point( 1, 1 ), + new Point( -1, 1 ) + ) ) ); + plane.polygons.add( new Polygon( Arrays.asList( + new Point( -1, 1 ), + new Point( 1, 1 ), + new Point( 1, 3 ), + new Point( -1, 3 ) + ) ) ); + plane.polygons.add( new Polygon( Arrays.asList( + new Point( -2, -1 ), + new Point( -1, -1 ), + new Point( -1, 2 ), + new Point( -2, 2 ) + ) ) ); + plane.polygons.add( new Polygon( Arrays.asList( + new Point( -4, -2 ), + new Point( -1, -2 ), + new Point( -1, 1.5 ), + new Point( -4, 1.5 ) + ) ) ); + plane.polygons.add( 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 ) + ) ) ); + plane.polygons.add( new Polygon( Arrays.asList( + new Point( -1, 0 ), + new Point( 0, 1.5 ), + new Point( 1, 0 ) + ) ) ); + plane.polygons.add( new Polygon( Arrays.asList( + new Point( 1, 0 ), + new Point( 0, -2 ), + new Point( -1, 0 ) + ) ) ); + + return plane; + } + + public MeshingTest6( List< Plane > data ) { + this.planes = data; + this.data = new PlaneData[ data.size() ]; + } + + private MeshingTest6 setIndex( final int index ) { + final int planeCount = planes.size(); + final int newIndex = ( ( ( index % planeCount ) + planeCount ) % planeCount ); + lock.lock(); + if ( this.index != newIndex ) { + this.index = newIndex; + + if ( selectionBox != null ) { + selectionBox.setSelectedIndex( newIndex ); + } + + new Thread( () -> { getData( this.index, this::repaint ); } ).start(); + } + lock.unlock(); + + return this; + } + + private PlaneData getData( final int index, final Runnable callback ) { + if ( data[ index ] != null ) { + callback.run(); + return data[ index ]; + } else { + setStatus( "Loading..." ); + final PlaneData data = process( planes.set( index, null ) ); + setStatus( "Done!" ); + this.data[ index ] = data; + callback.run(); + return data; + } + } + + private void setStatus( final String status ) { + if ( statusBar != null ) { + statusBar.setText( status ); + } + } + + private MeshingTest6 preMesh() { + for ( int i = 0; i < planes.size(); ++i ) { + this.data[ i ] = process( planes.set( i, null ) ); + } + return this; + } + + private void start() { + if ( index == -1 ) { + setIndex( 0 ); + } + + f = new JFrame( "Drawing Board" ); + f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + + final MouseAdapter adapter = new MouseAdapter() { + private boolean dragging = false; + private double initX, initY; + private int dragX, dragY; + private double scroll = Math.log( scale ); + + @Override + public void mousePressed( MouseEvent e ) { + dragging = true; + + initX = centerX; + initY = centerY; + + dragX = e.getXOnScreen(); + dragY = e.getYOnScreen(); + } + + @Override + public void mouseReleased( MouseEvent e ) { + dragging = false; + } + + @Override + public void mouseDragged( MouseEvent e ) { + if ( dragging ) { + centerX = initX + ( ( e.getXOnScreen() - dragX ) / scale ); + centerY = initY + ( ( e.getYOnScreen() - dragY ) / scale ); + + repaint(); + } + } + + @Override + public void mouseWheelMoved( MouseWheelEvent e ) { + if ( e.getWheelRotation() < 0 ) { + scroll += e.isControlDown() ? 0.1 : 0.5; + } else { + scroll -= e.isControlDown() ? 0.1 : 0.5; + } + scale = Math.exp( scroll ); + + repaint(); + } + + @Override + public void mouseMoved( MouseEvent e ) { + super.mouseMoved( e ); + + repaint(); + } + }; + + addMouseListener( adapter ); + addMouseMotionListener( adapter ); + addMouseWheelListener( adapter ); + + f.addComponentListener( new ComponentAdapter() { + public void componentResized( ComponentEvent e ) { + offsetX = getWidth() >> 1; + offsetY = getHeight() >> 1; + }; + } ); + + final JPanel taskbar = new JPanel(); + taskbar.setLayout( new FlowLayout() ); + + { + final JComboBox< String > data = new JComboBox< String >(); + for ( int i = 0; i < planes.size(); ++i ) { + data.addItem( "Plane " + i ); + } + data.addActionListener( e -> { + setIndex( data.getSelectedIndex() ); + } ); + taskbar.add( data ); + + selectionBox = data; + } + + { + final JLabel label = new JLabel(); + label.setPreferredSize( new Dimension( 100, 16 ) ); + label.setHorizontalAlignment( SwingConstants.LEFT ); + taskbar.add( label ); + + statusBar = label; + } + + { + final JButton prev = new JButton( "Previous" ); + prev.addActionListener( e -> { setIndex( index - 1 ); } ); + taskbar.add( prev ); + } + + + { + final JButton prev = new JButton( "Next" ); + prev.addActionListener( e -> { setIndex( index + 1 ); } ); + taskbar.add( prev ); + } + + f.add( taskbar, BorderLayout.NORTH ); + + f.add( this ); + + f.setSize( windowWidth, windowHeight ); + f.setVisible( true ); + f.setResizable( true ); + + f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + } + + 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 ); + } + + private void drawOnBoard( Graphics g, String str, int x, int y ) { + g.setFont( new Font( Font.MONOSPACED, Font.BOLD, ( int ) scale * 5 ) ); + g.drawString( str, ( int ) ( ( centerX + x + stringBoardX ) * scale ) + offsetX, ( int ) ( ( centerY - y + stringBoardY ) * scale ) + offsetY ); + } + + @Override + public void paintComponent( Graphics g ) { + super.paintComponent( g ); + + final PlaneData data = this.data[ index ]; + + g.setColor( new Color( 200, 200, 200 ) ); + g.drawLine( ( int ) ( scale * centerX ) + offsetX, 0, ( int ) ( scale * centerX ) + offsetX, getHeight() ); + g.drawLine( 0, ( int ) ( scale * centerY ) + offsetY, getWidth(), ( int ) ( scale * centerY ) + offsetY ); + g.setColor( Color.BLACK ); + + if ( data != null ) { + int highestX = 40; + if ( !data.edges.isEmpty() ) { + Collection< Point > points = data.vertices; + + if ( !data.polygons.isEmpty() ) { + highestX = ( int ) ( points.parallelStream() + .max( ( a, b ) -> { return Double.compare( a.getX(), b.getX() ); } ).get().getX() + 40.5 ); + } else { + highestX = 0; + } + + g.setColor( Color.BLACK ); + for ( final Segment segment : data.edges ) { + Point p1 = segment.getStart(); + Point p2 = segment.getEnd(); + g.drawLine( + ( int ) ( ( centerX + p1.getX() + highestX ) * scale ) + offsetX, + ( int ) ( ( centerY - p1.getY() ) * scale ) + offsetY, + ( int ) ( ( centerX + p2.getX() + highestX ) * scale ) + offsetX, + ( int ) ( ( centerY - p2.getY() ) * scale ) + offsetY + ); + } + + for ( final Point point : points ) { + final double diff = scale * 0.15; + g.drawRect( ( int ) ( ( centerX + point.getX() + highestX ) * scale ) - ( int ) diff + offsetX, ( int ) ( ( centerY - point.getY() ) * scale ) - ( int ) diff + offsetY, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) ); + } + } + + { + int count = 2; + Random random = new Random( hashCode() ); + + final int spacing = 25; + + for ( final Polygon p : data.polygons ) { + final List< Point > points = p.getPoints(); + final int[] x = new int[ points.size() ]; + final int[] y = new int[ points.size() ]; + final int[] xOffset = new int[ points.size() ]; + final int[] yOffset = new int[ points.size() ]; + final int[] filledX = new int[ points.size() ]; + final int[] filledY = new int[ points.size() ]; + for ( int i = 0; i < points.size(); ++i ) { + final Point point = points.get( i ); + x[ i ] = ( int ) ( ( centerX + point.getX() ) * scale ) + offsetX; + y[ i ] = ( int ) ( ( centerY - point.getY() - 150 ) * scale ) + offsetY; + xOffset[ i ] = ( int ) ( ( centerX + point.getX() + count * spacing ) * scale ) + offsetX; + yOffset[ i ] = ( int ) ( ( centerY - point.getY() - 150 ) * scale ) + offsetY; + filledX[ i ] = ( int ) ( ( centerX + point.getX() ) * scale ) + offsetX; + filledY[ i ] = ( int ) ( ( centerY - point.getY() ) * scale ) + offsetY; + } + + g.setColor( Color.BLACK ); + g.drawPolygon( x, y, points.size() ); + g.drawPolygon( xOffset, yOffset, points.size() ); + g.setColor( new Color( random.nextInt( 0x1000000 ) ) ); + g.fillPolygon( filledX, filledY, points.size() ); + ++count; + } + } + + { + g.setColor( Color.RED ); + for ( final Chain chain : data.chains ) { + final Point p1 = chain.getStart(); + final Point p2 = chain.getEnd(); + + g.drawLine( + ( int ) ( ( centerX + p1.getX() + highestX ) * scale ) + offsetX, + ( int ) ( ( centerY - p1.getY() ) * scale ) + offsetY, + ( int ) ( ( centerX + p2.getX() + highestX ) * scale ) + offsetX, + ( int ) ( ( centerY - p2.getY() ) * scale ) + offsetY + ); + } + + } + + { + g.setColor( Color.BLUE ); + for ( final Chain chain : data.selectedChains ) { + final Point p1 = chain.getStart(); + final Point p2 = chain.getEnd(); + + g.drawLine( + ( int ) ( ( centerX + p1.getX() + highestX ) * scale ) + offsetX, + ( int ) ( ( centerY - p1.getY() ) * scale ) + offsetY, + ( int ) ( ( centerX + p2.getX() + highestX ) * scale ) + offsetX, + ( int ) ( ( centerY - p2.getY() ) * scale ) + offsetY + ); + } + } + + g.setColor( Color.BLACK ); + drawOnBoard( g, "Plane index: " + index, 0, 10 ); + g.setColor( Color.CYAN ); + drawOnBoard( g, "Edge count: " + data.edges.size(), 0, 0 ); + g.setColor( Color.GREEN ); + drawOnBoard( g, "Vertex count: " + data.vertices.size(), 0, -5 ); + g.setColor( Color.MAGENTA ); + drawOnBoard( g, "Triangle count: " + data.polygons.size(), 0, -10 ); + g.setColor( Color.RED ); + drawOnBoard( g, "Total chain count: " + data.chains.size(), 0, -15 ); + g.setColor( Color.BLUE ); + drawOnBoard( g, "Optimal chain count: " + data.selectedChains.size(), 0, -20 ); + } + + java.awt.Point p = MouseInfo.getPointerInfo().getLocation(); + java.awt.Point componentLocation = getLocationOnScreen(); + p = new java.awt.Point( p.x - componentLocation.x, p.y - componentLocation.y ); + g.setColor( Color.RED ); + g.setFont( new Font( Font.MONOSPACED, Font.BOLD, 30 ) ); + g.drawString( "X: " + ( ( p.x - offsetX ) / scale - centerX ), 10, 30 ); + g.drawString( "Y: " + ( - ( ( p.y - offsetY ) / scale - centerY ) ), 10, 60 ); + } + + private static class PlaneData { + Collection< Point > vertices = new HashSet< Point >(); + Collection< Segment > edges = new HashSet< Segment >(); + Collection< Chain > chains = new HashSet< Chain >(); + Collection< Chain > selectedChains = new HashSet< Chain >(); + Collection< Polygon > polygons = new HashSet< Polygon >(); + } +} \ No newline at end of file diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MiniePlugin.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MiniePlugin.java index ce1b1a8..ba95417 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MiniePlugin.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/MiniePlugin.java @@ -25,6 +25,7 @@ import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; +import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.v1_21_R4.CraftWorld; import org.bukkit.craftbukkit.v1_21_R4.block.data.CraftBlockData; import org.bukkit.entity.BlockDisplay; @@ -47,8 +48,11 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon; import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionRuleWinding; import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple; import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple.GluWindingRule; +import com.aaaaahhhhhhh.bananapuncher714.minietest.MeshingTest2.AABB; +import com.aaaaahhhhhhh.bananapuncher714.minietest.command.CommandParameters; import com.aaaaahhhhhhh.bananapuncher714.minietest.command.SubCommand; import com.aaaaahhhhhhh.bananapuncher714.minietest.command.executor.CommandExecutableMessage; +import com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.InputValidatorDouble; import com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.InputValidatorInt; import com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.sender.SenderValidatorPlayer; import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkLocation; @@ -158,9 +162,9 @@ public class MiniePlugin extends JavaPlugin { @EventHandler private void onChunkLoad( ChunkLoadEvent event ) { if ( event.getWorld().getName().equalsIgnoreCase( "world" ) ) { -// getLogger().info( "Loading chunk..." ); -// scanAndGenerate( event.getChunk() ); -// saveChunk( event.getChunk() ); + getLogger().info( "Loading chunk " + event.getChunk().getX() + ", " + event.getChunk().getZ() ); + scanAndGenerate( event.getChunk() ); + saveFacets( event.getChunk() ); } } @@ -177,9 +181,12 @@ public class MiniePlugin extends JavaPlugin { }, this ); -// getLogger().info( "There are " + Bukkit.getWorld( "world" ).getLoadedChunks().length + " chunks" ); + getLogger().info( "There are " + Bukkit.getWorld( "world" ).getLoadedChunks().length + " chunks" ); // getLogger().info( "Converted " + saveAllChunks( Bukkit.getWorld( "world" ) ) ); + scanAndGenerate( Bukkit.getWorld( "world" ) ); + saveAllFacets( Bukkit.getWorld( "world" ) ); + Bukkit.getScheduler().runTaskTimer( this, () -> { if ( !paused ) { // Update the real world with the physics space objects @@ -284,69 +291,9 @@ public class MiniePlugin extends JavaPlugin { new SubCommand( "minie" ) .addSenderValidator( new SenderValidatorPlayer() ) .add( new SubCommand( "spawn" ) - .defaultTo( ( sender, args, params ) -> { - Player player = ( Player ) sender; - Location loc = player.getLocation(); - loc.setPitch( 0 ); - loc.setDirection( new Vector( 0, 0, 0 ) ); - - BlockData displayData; - ItemStack item = player.getInventory().getItemInMainHand(); - if ( item != null && item.getType() != Material.AIR && item.getType().isBlock() ) { - displayData = item.getType().createBlockData(); - } else { - displayData = Material.TNT.createBlockData(); - } - - BoundingBox[] boxes = convertFrom( getShape( displayData, null, BlockShapeType.SHAPE ) ); - Vector blockCenter = calculateCenter( boxes ); - - CollisionShape box; - if ( boxes.length == 0 ) { - player.sendMessage( "No bounding box detected! Wrong method?" ); - return; - } - - if ( boxes.length == 1 ) { - Vector min = boxes[ 0 ].getMin(); - Vector center = boxes[ 0 ].getCenter().subtract( min ); - box = new BoxCollisionShape( ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() ); - } else { - CompoundCollisionShape compound = new CompoundCollisionShape(); - for ( BoundingBox aabb : boxes ) { - Vector min = aabb.getMin(); - Vector center = aabb.getCenter(); - Vector half = center.clone().subtract( min ); - center.subtract( blockCenter ); - CollisionShape subShape = new BoxCollisionShape( ( float ) half.getX(), ( float ) half.getY(), ( float ) half.getZ() ); - compound.addChildShape( subShape, ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() ); - } - box = compound; - } - - PhysicsRigidBody rigid = new PhysicsRigidBody( box, 1f ); - - // Since the center of the rigid body and the box shape are different, we need to offset the location - rigid.setPhysicsLocation( new Vector3f( ( float ) ( loc.getX() + blockCenter.getX() ), ( float ) ( loc.getY() + blockCenter.getY() ), ( float ) ( loc.getZ() + blockCenter.getZ() ) ) ); - - // Convert the display direction vector to a quaternion - rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) ); - - space.addCollisionObject( rigid ); - - // Create the display - BlockDisplay display = loc.getWorld().spawn( loc, BlockDisplay.class, d -> { - d.setBlock( displayData ); - } ); - - linkedDisplays.put( rigid, new AuxiliaryDisplayStruct( display, blockCenter ) ); - - if ( displayData.getMaterial() == Material.TNT ) { - isTnt.add( rigid ); - } - - player.sendMessage( "Created display" ); - } ) ) + .add( new SubCommand( new InputValidatorDouble( 0.01, 100 ) ) + .defaultTo( this::spawnBlock ) ) + .defaultTo( this::spawnBlock ) ) .add( new SubCommand( "constraint" ) .defaultTo( ( sender, args, params ) -> { Player player = ( Player ) sender; @@ -436,6 +383,24 @@ public class MiniePlugin extends JavaPlugin { player.sendMessage( "Registering chunk..." ); scanAndGenerate( player.getLocation().getChunk() ); } ) ) + .add( new SubCommand( "area" ) + .defaultTo( ( sender, args, params ) -> { + Player player = ( Player ) sender; + + player.sendMessage( "Registering chunk..." ); + final World world = player.getWorld(); + final Chunk chunk = player.getLocation().getChunk(); + final int x = chunk.getX(); + final int z = chunk.getZ(); + + int radius = 5; + for ( int i = -radius; i < radius + 1; ++i ) { + for ( int j = -radius; j < radius + 1; ++j ) { + getLogger().info( "Queueing chunk scan at (" + ( x + i ) + ", " + ( z + j ) + ")" ); + scanAndGenerate( world.getChunkAt( x + i, z + j ) ); + } + } + } ) ) .add( new SubCommand( "plane" ) .defaultTo( ( sender, args, params ) -> { Player player = ( Player ) sender; @@ -511,6 +476,86 @@ public class MiniePlugin extends JavaPlugin { } ); } + private void spawnBlock( final CommandSender sender, final String[] args, final CommandParameters params ) { + Player player = ( Player ) sender; + Location loc = player.getLocation(); + + // Optional scale + double scale = 1; + if ( params.size() > 2 ) { + scale = params.getFirst( double.class ); + } + + loc.setPitch( 0 ); + loc.setDirection( new Vector( 0, 0, 0 ) ); + + BlockData displayData; + ItemStack item = player.getInventory().getItemInMainHand(); + if ( item != null && item.getType() != Material.AIR && item.getType().isBlock() ) { + displayData = item.getType().createBlockData(); + } else { + displayData = Material.TNT.createBlockData(); + } + + BoundingBox[] boxes = convertFrom( getShape( displayData, null, BlockShapeType.SHAPE ) ); + Vector blockCenter = calculateCenter( boxes ); + + CollisionShape box; + if ( boxes.length == 0 ) { + player.sendMessage( "No bounding box detected! Wrong method?" ); + return; + } + + if ( boxes.length == 1 ) { + Vector min = boxes[ 0 ].getMin(); + Vector center = boxes[ 0 ].getCenter().subtract( min ); + box = new BoxCollisionShape( ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() ); + } else { + CompoundCollisionShape compound = new CompoundCollisionShape(); + for ( BoundingBox aabb : boxes ) { + Vector min = aabb.getMin(); + Vector center = aabb.getCenter(); + Vector half = center.clone().subtract( min ); + center.subtract( blockCenter ); + CollisionShape subShape = new BoxCollisionShape( ( float ) half.getX(), ( float ) half.getY(), ( float ) half.getZ() ); + compound.addChildShape( subShape, ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() ); + } + box = compound; + } + + box.setScale( ( float ) scale ); + + PhysicsRigidBody rigid = new PhysicsRigidBody( box, 1f ); + + // Since the center of the rigid body and the box shape are different, we need to offset the location + rigid.setPhysicsLocation( new Vector3f( ( float ) ( loc.getX() + blockCenter.getX() ), ( float ) ( loc.getY() + blockCenter.getY() ), ( float ) ( loc.getZ() + blockCenter.getZ() ) ) ); + + // Convert the display direction vector to a quaternion + rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) ); + + rigid.setMass( rigid.getMass() * ( float ) ( scale * scale ) ); + + space.addCollisionObject( rigid ); + + // Create the display + BlockDisplay display = loc.getWorld().spawn( loc, BlockDisplay.class, d -> { + d.setBlock( displayData ); + d.setViewRange( 1000 ); + } ); + + linkedDisplays.put( rigid, new AuxiliaryDisplayStruct( display, blockCenter.multiply( scale ) ).setScale( new Vector( scale, scale, scale ) ) ); + + if ( displayData.getMaterial() == Material.TNT ) { + isTnt.add( rigid ); + } + + player.sendMessage( "Created display" ); + } + + /* + * Replaced with saveAllFacets + */ + @Deprecated private int saveAllChunks( World world ) { int total = 0; for ( Chunk chunk : world.getLoadedChunks() ) { @@ -519,6 +564,10 @@ public class MiniePlugin extends JavaPlugin { return total; } + /* + * Replaced with saveFacets + */ + @Deprecated private int saveChunk( Chunk chunk ) { File saveFile = new File( getDataFolder() + "/chunks", chunk.getWorld().getName() + "," + chunk.getX() + "," + chunk.getZ() ); saveFile.getParentFile().mkdirs(); @@ -560,6 +609,140 @@ public class MiniePlugin extends JavaPlugin { return count; } + private void saveAllFacets( final World world ) { + for ( final Chunk chunk : world.getLoadedChunks() ) { + saveFacets( chunk ); + } + } + + private void saveFacets( final Chunk chunk ) { + final Collection< Facet > facets = getFacetsConservative( chunk ); + saveFacets( new File( getDataFolder() + "/facets", chunk.getWorld().getName() + "," + chunk.getX() + "," + chunk.getZ() + ".facet" ), facets ); + } + + private Collection< Facet > getFacetsConservative( Chunk chunk ) { + final World world = chunk.getWorld(); + final int worldMinHeight = world.getMinHeight(); + final int worldMaxHeight = world.getMaxHeight(); + final int worldHeight = worldMaxHeight - worldMinHeight; + + final Collection< Facet > facets = new HashSet< Facet >(); + + BlockVoxelType[] types = new BlockVoxelType[ worldHeight << 8 ]; + + for ( int y = worldMinHeight; y < worldMaxHeight; ++y ) { + final int yIndex = ( y - worldMinHeight ) << 8; + for ( int z = 0; z < 16; ++z ) { + final int zIndex = yIndex + ( z << 4 ); + for ( int x = 0; x < 16; ++x ) { + final int index = zIndex + x; + final Block block = chunk.getBlock( x, y, z ); + if ( !( block.isEmpty() && block.isLiquid() ) ) { + + final BlockData data = block.getBlockData(); + final Location loc = block.getLocation(); + + // Start the box at 0, 0, 0 + final BoundingBox[] boundingBoxes = convertFrom( getShape( data, loc, BlockShapeType.COLLISION_SHAPE ), new Vector( x, y - worldMinHeight, z ) ); + + if ( boundingBoxes.length == 0 ) { + // No bounding box + types[ index ] = BlockVoxelType.NONE; + } else if ( boundingBoxes.length == 1 && boundingBoxes[ 0 ].getVolume() == 1 ) { + // Solid + types[ index ] = BlockVoxelType.SOLID; + } else { + types[ index ] = BlockVoxelType.TRANSPARENT; + // Complex bounding shape, and transparent + + // Convert the bounding box to facets + for ( final BoundingBox box : boundingBoxes ) { + facets.addAll( generateFacetsFor( box ) ); + } + } + } else { + types[ index ] = BlockVoxelType.NONE; + } + } + } + } + + // Now generate facets for each solid block based on if the side they are on is visible + for ( int y = 0; y < worldHeight; ++y ) { + final int yIndex = y << 8; + for ( int z = 0; z < 16; ++z ) { + final int zIndex = z << 4; + for ( int x = 0; x < 16; ++x ) { + final int index = yIndex + zIndex + x; + if ( types[ index ] == BlockVoxelType.SOLID ) { + // Check each face + if ( y == 0 || types[ index - 256 ] != BlockVoxelType.SOLID ) { + Facet facet = new Facet(); + facet.points.add( new Vector( x, y, z ) ); + facet.points.add( new Vector( x, y, z + 1 ) ); + facet.points.add( new Vector( x + 1, y, z + 1 ) ); + facet.points.add( new Vector( x + 1, y, z ) ); + facet.normal = new Vector( 0, -1, 0 ); + facets.add( facet ); + } + + if ( y == worldHeight - 1 || types[ index + 256 ] != BlockVoxelType.SOLID ) { + Facet facet = new Facet(); + facet.points.add( new Vector( x, y + 1, z ) ); + facet.points.add( new Vector( x, y + 1, z + 1 ) ); + facet.points.add( new Vector( x + 1, y + 1, z + 1 ) ); + facet.points.add( new Vector( x + 1, y + 1, z ) ); + facet.normal = new Vector( 0, 1, 0 ); + facets.add( facet ); + } + + if ( z == 0 || types[ index - 16 ] != BlockVoxelType.SOLID ) { + Facet facet = new Facet(); + facet.points.add( new Vector( x, y, z ) ); + facet.points.add( new Vector( x, y + 1, z ) ); + facet.points.add( new Vector( x + 1, y + 1, z ) ); + facet.points.add( new Vector( x + 1, y, z ) ); + facet.normal = new Vector( 0, 0, -1 ); + facets.add( facet ); + } + + if ( z == 15 || types[ index + 16 ] != BlockVoxelType.SOLID ) { + Facet facet = new Facet(); + facet.points.add( new Vector( x, y, z + 1 ) ); + facet.points.add( new Vector( x, y + 1, z + 1 ) ); + facet.points.add( new Vector( x + 1, y + 1, z + 1 ) ); + facet.points.add( new Vector( x + 1, y, z + 1 ) ); + facet.normal = new Vector( 0, 0, 1 ); + facets.add( facet ); + } + + if ( x == 0 || types[ index - 1 ] != BlockVoxelType.SOLID ) { + Facet facet = new Facet(); + facet.points.add( new Vector( x, y, z ) ); + facet.points.add( new Vector( x, y, z + 1 ) ); + facet.points.add( new Vector( x, y + 1, z + 1 ) ); + facet.points.add( new Vector( x, y + 1, z ) ); + facet.normal = new Vector( -1, 0, 0 ); + facets.add( facet ); + } + + if ( x == 15 || types[ index + 1 ] != BlockVoxelType.SOLID ) { + Facet facet = new Facet(); + facet.points.add( new Vector( x + 1, y, z ) ); + facet.points.add( new Vector( x + 1, y, z + 1 ) ); + facet.points.add( new Vector( x + 1, y + 1, z + 1 ) ); + facet.points.add( new Vector( x + 1, y + 1, z ) ); + facet.normal = new Vector( 1, 0, 0 ); + facets.add( facet ); + } + } + } + } + } + + return facets; + } + private void scanAndGenerate( World world ) { for ( Chunk chunk : world.getLoadedChunks() ) { scanAndGenerate( chunk ); @@ -569,40 +752,19 @@ public class MiniePlugin extends JavaPlugin { private void scanAndGenerate( final Chunk chunk ) { final long start = System.currentTimeMillis(); +// getLogger().info( "Gathering bounding boxes..." ); final World world = chunk.getWorld(); - final int worldMinHeight = world.getMinHeight(); - final int worldMaxHeight = world.getMaxHeight(); - final Collection< BoundingBox > boxes = new ArrayDeque< BoundingBox >(); - getLogger().info( "Gathering bounding boxes..." ); - for ( int y = worldMinHeight; y < worldMaxHeight; ++y ) { - for ( int z = 0; z < 16; ++z ) { - for ( int x = 0; x < 16; ++x ) { - Block block = chunk.getBlock( x, y, z ); - if ( !( block.isEmpty() && block.isLiquid() ) ) { - final BlockData data = block.getBlockData(); - final Location loc = block.getLocation(); - - // Start the box at 0, 0, 0 - final BoundingBox[] boundingBoxes = convertFrom( getShape( data, loc, BlockShapeType.COLLISION_SHAPE ), new Vector( x, y - worldMinHeight, z ) ); - - for ( BoundingBox box : boundingBoxes ) { - boxes.add( box ); - } - } - } - } - } + + final Collection< Facet > originalFacets = getFacetsConservative( chunk ); Bukkit.getScheduler().runTaskAsynchronously( this, () -> { - getLogger().info( "Building planes..." ); +// getLogger().info( "Building planes..." ); final MeshBuilder builder = new MeshBuilder(); - for ( final BoundingBox box : boxes ) { - for ( final Facet facet : generateFacetsFor( box ) ) { - builder.addFacet( facet ); - } + for ( final Facet facet : originalFacets ) { + builder.addFacet( facet ); } - getLogger().info( "Meshing planes..." ); +// getLogger().info( "Meshing planes..." ); Collection< Facet > facets = builder.planes.parallelStream().flatMap( p -> process( p ).parallelStream().map( p::convert ) ).collect( Collectors.toSet() ); final class VectorRef { @@ -616,7 +778,7 @@ public class MiniePlugin extends JavaPlugin { final Map< Vector, VectorRef > vertices = new HashMap< Vector, VectorRef >(); final List< Triangle > triangles = new ArrayList< Triangle >(); - getLogger().info( "Generating triangles..." ); +// getLogger().info( "Generating triangles..." ); // Convert each facet to a triangle for ( final Facet facet : facets ) { if ( facet.points.size() != 3 ) { @@ -664,7 +826,7 @@ public class MiniePlugin extends JavaPlugin { } } ); - getLogger().info( "Creating buffers..." ); +// getLogger().info( "Creating buffers..." ); final Vector3f[] positionArray = new Vector3f[ sorted.size() ]; for ( int i = 0; i < sorted.size(); ++i ) { final Entry< Vector, VectorRef > entry = sorted.get( i ); @@ -688,12 +850,12 @@ public class MiniePlugin extends JavaPlugin { indexArray[ indexArrayStart + 2 ] = triangle.refs.get( 2 ).index; } - getLogger().info( "Adding mesh collision shape..." ); +// getLogger().info( "Adding mesh collision shape..." ); final IndexedMesh nativeMesh = new IndexedMesh( positionArray, indexArray ); // Enable quantized AABB compression... hopefully that doesn't mess with the accuracy or anything... final MeshCollisionShape mesh = new MeshCollisionShape( true, nativeMesh ); - getLogger().info( "Mesh has " + mesh.countMeshVertices() + " vertices and " + mesh.countMeshTriangles() + " triangles" ); +// getLogger().info( "Mesh has " + mesh.countMeshVertices() + " vertices and " + mesh.countMeshTriangles() + " triangles" ); // Create a rigid body PhysicsRigidBody rigid = new PhysicsRigidBody( mesh, PhysicsBody.massForStatic ); @@ -709,13 +871,31 @@ public class MiniePlugin extends JavaPlugin { getLogger().warning( "Old rigid body found!" ); space.remove( old ); } - getLogger().info( "Done!" ); +// getLogger().info( "Done!" ); final long time = System.currentTimeMillis() - start; - getLogger().info( "Took " + time + "ms" ); + getLogger().info( "Took " + time + "ms to mesh chunk " + chunk.getX() + ", " + chunk.getZ() + " into " + mesh.countMeshVertices() + " vertices and " + mesh.countMeshTriangles() + " triangles from " + facets.size() + " facets" ); } ); } ); } + private void saveFacets( final File file, final Collection< Facet > facets ) { + file.getParentFile().mkdirs(); + file.delete(); + + try ( FileWriter writer = new FileWriter( file ) ) { + for ( final Facet facet : facets ) { + final Vector normal = facet.normal; + + writer.write( facet.points.size() + " " + normal.getX() + " " + normal.getY() + " " + normal.getZ() + "\n" ); + for ( final Vector point : facet.points ) { + writer.write( point.getX() + " " + point.getY() + " " + point.getZ() + "\n" ); + } + } + } catch ( IOException e ) { + e.printStackTrace(); + } + } + private boolean convert( Block block ) { BlockState state = block.getState(); BlockData data = state.getBlockData(); @@ -960,6 +1140,11 @@ public class MiniePlugin extends JavaPlugin { VISUAL_SHAPE, INTERACTION_SHAPE, BLOCK_SUPPORT_SHAPE - + } + + private enum BlockVoxelType { + NONE, + TRANSPARENT, + SOLID } } diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/command/validator/InputValidatorDouble.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/command/validator/InputValidatorDouble.java new file mode 100644 index 0000000..de3541f --- /dev/null +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/command/validator/InputValidatorDouble.java @@ -0,0 +1,39 @@ +package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator; + +import java.util.Collection; + +import org.bukkit.command.CommandSender; + +public class InputValidatorDouble implements InputValidator< Double > { + protected double min = Integer.MIN_VALUE; + protected double max = Integer.MAX_VALUE; + + public InputValidatorDouble() { + } + + public InputValidatorDouble( double min, double max ) { + this.min = min; + this.max = max; + } + + @Override + public Collection< String > getTabCompletes( CommandSender sender, String[] input ) { + return null; + } + + @Override + public boolean isValid( CommandSender sender, String[] input, String[] args ) { + try { + double i = Double.valueOf( input[ 0 ] ); + return i >= min && i <= max; + } catch ( Exception exception ) { + return false; + } + } + + @Override + public Double get( CommandSender sender, String[] input ) { + return Double.parseDouble( input[ 0 ] ); + } + +} diff --git a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/MeshBuilder.java b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/MeshBuilder.java index dc7faa3..4084684 100644 --- a/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/MeshBuilder.java +++ b/src/main/java/com/aaaaahhhhhhh/bananapuncher714/minietest/objects/mesh/MeshBuilder.java @@ -10,11 +10,11 @@ public class MeshBuilder { public List< Plane > planes = new ArrayList< Plane >(); public Plane addFacet( Facet facet ) { - for ( Plane plane : planes ) { - if ( plane.matches( facet.normal, facet.points.get( 0 ) ) ) { - plane.addShape( facet ); - return plane; - } + final Optional< Plane > opt = planes.stream().filter( p -> p.matches( facet.normal, facet.points.get( 0 ) ) ).findAny(); + if ( opt.isPresent() ) { + final Plane plane = opt.get(); + plane.addShape( facet ); + return plane; } Plane plane = new Plane();