Initial cleanup and refactor

This commit is contained in:
2026-03-14 23:03:06 -04:00
parent 793d1e846a
commit ce8165417b
77 changed files with 492 additions and 94 deletions

View File

@@ -0,0 +1,253 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>mc-mesh-master</artifactId>
<groupId>com.aaaaahhhhhhh.bananapuncher714</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mc-mesh-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.aaaaahhhhhhh.bananapuncher714.minietest.MeshingTest6</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.21.10-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>spigot-api</artifactId>
<groupId>org.spigotmc</groupId>
</exclusion>
<exclusion>
<artifactId>jline</artifactId>
<groupId>org.jline</groupId>
</exclusion>
<exclusion>
<artifactId>log4j-iostreams</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
<exclusion>
<artifactId>asm-commons</artifactId>
<groupId>org.ow2.asm</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-annotations</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-core</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-databind</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>oshi-core</artifactId>
<groupId>com.github.oshi</groupId>
</exclusion>
<exclusion>
<artifactId>jcip-annotations</artifactId>
<groupId>com.github.stephenc.jcip</groupId>
</exclusion>
<exclusion>
<artifactId>msal4j</artifactId>
<groupId>com.microsoft.azure</groupId>
</exclusion>
<exclusion>
<artifactId>authlib</artifactId>
<groupId>com.mojang</groupId>
</exclusion>
<exclusion>
<artifactId>brigadier</artifactId>
<groupId>com.mojang</groupId>
</exclusion>
<exclusion>
<artifactId>datafixerupper</artifactId>
<groupId>com.mojang</groupId>
</exclusion>
<exclusion>
<artifactId>jtracy</artifactId>
<groupId>com.mojang</groupId>
</exclusion>
<exclusion>
<artifactId>logging</artifactId>
<groupId>com.mojang</groupId>
</exclusion>
<exclusion>
<artifactId>content-type</artifactId>
<groupId>com.nimbusds</groupId>
</exclusion>
<exclusion>
<artifactId>lang-tag</artifactId>
<groupId>com.nimbusds</groupId>
</exclusion>
<exclusion>
<artifactId>nimbus-jose-jwt</artifactId>
<groupId>com.nimbusds</groupId>
</exclusion>
<exclusion>
<artifactId>oauth2-oidc-sdk</artifactId>
<groupId>com.nimbusds</groupId>
</exclusion>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
<exclusion>
<artifactId>netty-buffer</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>netty-codec</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>netty-codec-http</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>netty-common</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>netty-handler</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>netty-resolver</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>netty-transport</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>netty-transport-classes-epoll</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>netty-transport-native-epoll</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>netty-transport-native-epoll</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>netty-transport-native-unix-common</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>fastutil</artifactId>
<groupId>it.unimi.dsi</groupId>
</exclusion>
<exclusion>
<artifactId>jna</artifactId>
<groupId>net.java.dev.jna</groupId>
</exclusion>
<exclusion>
<artifactId>jna-platform</artifactId>
<groupId>net.java.dev.jna</groupId>
</exclusion>
<exclusion>
<artifactId>accessors-smart</artifactId>
<groupId>net.minidev</groupId>
</exclusion>
<exclusion>
<artifactId>json-smart</artifactId>
<groupId>net.minidev</groupId>
</exclusion>
<exclusion>
<artifactId>jopt-simple</artifactId>
<groupId>net.sf.jopt-simple</groupId>
</exclusion>
<exclusion>
<artifactId>commons-lang3</artifactId>
<groupId>org.apache.commons</groupId>
</exclusion>
<exclusion>
<artifactId>log4j-api</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
<exclusion>
<artifactId>log4j-core</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
<exclusion>
<artifactId>log4j-slf4j2-impl</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
<exclusion>
<artifactId>lz4-java</artifactId>
<groupId>org.lz4</groupId>
</exclusion>
<exclusion>
<artifactId>commons-lang</artifactId>
<groupId>commons-lang</groupId>
</exclusion>
<exclusion>
<artifactId>json-simple</artifactId>
<groupId>com.googlecode.json-simple</groupId>
</exclusion>
<exclusion>
<artifactId>sqlite-jdbc</artifactId>
<groupId>org.xerial</groupId>
</exclusion>
<exclusion>
<artifactId>mysql-connector-j</artifactId>
<groupId>com.mysql</groupId>
</exclusion>
<exclusion>
<artifactId>maven-resolver-provider</artifactId>
<groupId>org.apache.maven</groupId>
</exclusion>
<exclusion>
<artifactId>maven-resolver-connector-basic</artifactId>
<groupId>org.apache.maven.resolver</groupId>
</exclusion>
<exclusion>
<artifactId>maven-resolver-transport-http</artifactId>
<groupId>org.apache.maven.resolver</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

78
plugin/pom.xml Normal file
View File

@@ -0,0 +1,78 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.aaaaahhhhhhh.bananapuncher714</groupId>
<artifactId>mc-mesh-master</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>mc-mesh-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.21.10-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.stephengold</groupId>
<artifactId>Minie</artifactId>
<version>9.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.scijava</groupId>
<artifactId>native-lib-loader</artifactId>
<version>2.4.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.aaaaahhhhhhh.bananapuncher714</groupId>
<artifactId>mc-mesh-core</artifactId>
<version>0.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.aaaaahhhhhhh.bananapuncher714.minietest.MeshingTest6</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,51 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.ReentrantLock;
import org.bukkit.Chunk;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkLocation;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkMesh;
public class ChunkMeshCache {
final File directory;
Map< ChunkLocation, ChunkMesh > meshes = new TreeMap< ChunkLocation, ChunkMesh >();
Collection< ChunkLocation > shouldRemove = new HashSet< ChunkLocation >();
ReentrantLock lock = new ReentrantLock();
public ChunkMeshCache( final File directory ) {
this.directory = directory;
}
public void onChunkLoad( final Chunk chunk ) {
final ChunkLocation location = new ChunkLocation( chunk );
// Unqueue this chunk for removal if it's queued
shouldRemove.remove( location );
// TODO Add a ticket
chunk.addPluginChunkTicket( null );
// Does the mesh map contain the current location?
{
// If not, then load or mesh the chunk
// Emit a chunk mesh complete event
}
}
public void saveAndRemove( final ChunkLocation location ) {
lock.lock();
meshes.remove( location );
lock.unlock();
}
public void saveAndRemoveAll() {
lock.unlock();
}
}

View File

@@ -0,0 +1,194 @@
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.List;
import org.bukkit.util.Vector;
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 MeshTest {
private static final File BASE = new File( System.getProperty( "user.dir" ) );
private static final File CHUNK_DIR = new File( BASE, "chunks" );
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() ) {
doStuff( file );
break;
}
} else {
System.err.println( "No such directory exists: " + CHUNK_DIR );
}
}
private static void doStuff( 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;
} catch ( IOException e ) {
e.printStackTrace();
return;
}
System.out.println( "Found " + boxes.size() + " boxes" );
// Now that we have a bunch of bounding boxes, do whatever
MeshBuilder builder = new MeshBuilder();
int i = 0;
for ( AABB box : boxes ) {
for ( Facet facet : generateFacetsFor( box ) ) {
builder.addFacet( facet );
}
}
System.out.println( "Planes: " + builder.planes.size() );
for ( Plane plane : builder.planes ) {
System.out.println( "Plane" );
System.out.println( "Norm:\t" + plane.normal );
System.out.println( "Ref:\t" + plane.point );
System.out.println( "Size:\t" + plane.polygons.size() );
}
/*
* The general plan is as follows:
* - Create a new mesh object
* - Insert faces one by one until we have all the shapes we want
* - In this case, it happens to be perfect AABB
* - Process the faces
* - Sort them into their correct plane of existence, along with their normal
* - In this case, their unique plane can be defined by their normal, and a point
* - Use the first point of the polygon to compare
* - Use the smallest point we can find as the reference point
* - O(1) operation per face, I suppose
* - Need to check point and normal with tolerance which is why
* - Now that we have faces each in their assorted group, we can start to tessellate each one
* - Generate even/odd regions and remove any colinear edges
* - Save this as the pre-mesh
* - Now trianglate each group and put that into a mesh collision shape or gimpact shape or whatnot
* - Use an IndexedMesh with the two array constructor
* - Put into game and test
*/
}
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.ymax = ymin;
this.zmin = zmin;
this.xmax = xmax;
this.ymax = ymax;
this.zmax = zmax;
}
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,235 @@
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.MeshingTest2.AABB;
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;
/**
* Mesh speed test
*/
public class MeshingTest3 {
private static final File BASE = new File( System.getProperty( "user.dir" ) );
private static final File CHUNK_DIR = new File( BASE, "chunks" );
public static void main( String[] args ) {
if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) {
System.out.println( "Found " + CHUNK_DIR.list().length + " files" );
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 );
} 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 < 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 ) {
// System.out.println( "Meshing plane " + i );
// process( planes.get( i ) );
// }
// process( planes.get( 27 ) );
} else {
System.err.println( "No such directory exists: " + CHUNK_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 ) {
String name = file.getName();
String[] split = name.split( "," );
ChunkLocation location = new ChunkLocation( split[ 0 ], Integer.parseInt( split[ 1 ] ), Integer.parseInt( split[ 2 ] ) );
// 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;
}
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();
for ( AABB box : boxes ) {
for ( Facet facet : generateFacetsFor( box ) ) {
builder.addFacet( facet );
}
}
return builder.planes;
}
private static List< Facet > generateFacetsFor( AABB box ) {
List< Facet > facets = new ArrayList< Facet >();
Vector p1 = new Vector( box.xmin, box.ymin, box.zmin );
Vector p2 = new Vector( box.xmin, box.ymin, box.zmax );
Vector p3 = new Vector( box.xmin, box.ymax, box.zmin );
Vector p4 = new Vector( box.xmin, box.ymax, box.zmax );
Vector p5 = new Vector( box.xmax, box.ymin, box.zmin );
Vector p6 = new Vector( box.xmax, box.ymin, box.zmax );
Vector p7 = new Vector( box.xmax, box.ymax, box.zmin );
Vector p8 = new Vector( box.xmax, box.ymax, box.zmax );
{
Facet facet = new Facet();
facet.points.add( p1 );
facet.points.add( p2 );
facet.points.add( p4 );
facet.points.add( p3 );
facet.normal = new Vector( -1, 0, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p5 );
facet.points.add( p6 );
facet.points.add( p8 );
facet.points.add( p7 );
facet.normal = new Vector( 1, 0, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p1 );
facet.points.add( p2 );
facet.points.add( p6 );
facet.points.add( p5 );
facet.normal = new Vector( 0, -1, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p3 );
facet.points.add( p4 );
facet.points.add( p8 );
facet.points.add( p7 );
facet.normal = new Vector( 0, 1, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p1 );
facet.points.add( p3 );
facet.points.add( p7 );
facet.points.add( p5 );
facet.normal = new Vector( 0, 0, -1 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p2 );
facet.points.add( p4 );
facet.points.add( p8 );
facet.points.add( p6 );
facet.normal = new Vector( 0, 0, 1 );
facets.add( facet );
}
return facets;
}
}

View File

@@ -0,0 +1,392 @@
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.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.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;
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.MeshingTest2.AABB;
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;
/**
* Mesh chunk data to ply2 format
*/
public class MeshingTest4 {
private static final File BASE = new File( System.getProperty( "user.dir" ) );
private static final File CHUNK_DIR = new File( BASE, "chunks" );
private static final File MODEL_DIR = new File( BASE, "chunk_models" );
public static void main( String[] args ) {
if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) {
System.out.println( "Found " + CHUNK_DIR.list().length + " files" );
// 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" );
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 );
}
}
private static void savePly2( final File file, Collection< Facet > facets ) {
file.getParentFile().mkdirs();
if ( file.exists() ) {
file.delete();
}
class VectorRef {
int index;
}
class Triangle {
List< VectorRef > refs = new ArrayList< VectorRef >();
}
Map< Vector, VectorRef > vertices = new HashMap< Vector, VectorRef >();
Collection< Triangle > triangles = new HashSet< Triangle >();
// Convert each facet to a triangle
for ( final Facet facet : facets ) {
if ( facet.points.size() != 3 ) {
System.out.println( "Warning: Facet is not a triangle!" );
}
final Triangle triangle = new Triangle();
for ( final Vector vec : facet.points ) {
VectorRef ref = null;
for ( final Entry< Vector, VectorRef > entry : vertices.entrySet() ) {
final Vector existingVec = entry.getKey();
if ( existingVec.distanceSquared( vec ) < 1e-8 ) {
ref = entry.getValue();
break;
}
}
if ( ref == null ) {
ref = new VectorRef();
vertices.put( vec, ref );
}
triangle.refs.add( ref );
}
triangles.add( triangle );
}
// Sort the vertices
final List< Entry< Vector, VectorRef > > sorted = new ArrayList< Entry< Vector, VectorRef > >( vertices.entrySet() );
Collections.sort( sorted, ( aEntry, bEntry ) -> {
final Vector a = aEntry.getKey();
final Vector b = bEntry.getKey();
final double xDiff = a.getX() - b.getX();
if ( xDiff == 0 ) {
final double yDiff = a.getY() - b.getY();
if ( yDiff == 0 ) {
return Double.compare( a.getZ(), b.getZ() );
} else {
return Double.compare( yDiff, 0 );
}
} else {
return Double.compare( xDiff, 0 );
}
} );
try ( PrintWriter writer = new PrintWriter( file ) ) {
writer.println( "ply" );
writer.println( "format ascii 1.0" );
writer.println( "comment Created by java tess" );
writer.println( "element vertex " + sorted.size() );
writer.println( "property float x" );
writer.println( "property float y" );
writer.println( "property float z" );
writer.println( "element face " + triangles.size() );
writer.println( "element property list uchar uint vertex_indices" );
writer.println( "end_header" );
// Write vertices
for ( int i = 0; i < sorted.size(); ++i ) {
final Entry< Vector, VectorRef > entry = sorted.get( i );
entry.getValue().index = i;
final Vector vector = entry.getKey();
writer.println( String.format( "%f %f %f", vector.getX(), vector.getY(), vector.getZ() ) );
}
for ( final Triangle triangle : triangles ) {
final StringBuilder builder = new StringBuilder();
builder.append( triangle.refs.size() );
for ( final VectorRef ref : triangle.refs ) {
builder.append( " " );
builder.append( ref.index );
}
writer.println( builder.toString() );
}
System.out.println( "\tWriting " + triangles.size() + " triangles and " + sorted.size() + " vertices to " + file );
} catch ( FileNotFoundException e ) {
e.printStackTrace();
}
}
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 ); } );
for ( Polygon poly : plane.polygons ) {
mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE );
}
return mesh.meshify();
} else {
System.out.println( "No data!" );
return Collections.emptySet();
}
}
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 ] ) );
// 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;
}
// Now that we have a bunch of bounding boxes, do whatever
MeshBuilder builder = new MeshBuilder();
for ( AABB box : boxes ) {
for ( Facet facet : generateFacetsFor( box ) ) {
builder.addFacet( facet );
}
}
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 >();
Vector p1 = new Vector( box.xmin, box.ymin, box.zmin );
Vector p2 = new Vector( box.xmin, box.ymin, box.zmax );
Vector p3 = new Vector( box.xmin, box.ymax, box.zmin );
Vector p4 = new Vector( box.xmin, box.ymax, box.zmax );
Vector p5 = new Vector( box.xmax, box.ymin, box.zmin );
Vector p6 = new Vector( box.xmax, box.ymin, box.zmax );
Vector p7 = new Vector( box.xmax, box.ymax, box.zmin );
Vector p8 = new Vector( box.xmax, box.ymax, box.zmax );
{
Facet facet = new Facet();
facet.points.add( p1 );
facet.points.add( p2 );
facet.points.add( p4 );
facet.points.add( p3 );
facet.normal = new Vector( -1, 0, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p5 );
facet.points.add( p6 );
facet.points.add( p8 );
facet.points.add( p7 );
facet.normal = new Vector( 1, 0, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p1 );
facet.points.add( p2 );
facet.points.add( p6 );
facet.points.add( p5 );
facet.normal = new Vector( 0, -1, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p3 );
facet.points.add( p4 );
facet.points.add( p8 );
facet.points.add( p7 );
facet.normal = new Vector( 0, 1, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p1 );
facet.points.add( p3 );
facet.points.add( p7 );
facet.points.add( p5 );
facet.normal = new Vector( 0, 0, -1 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p2 );
facet.points.add( p4 );
facet.points.add( p8 );
facet.points.add( p6 );
facet.normal = new Vector( 0, 0, 1 );
facets.add( facet );
}
return facets;
}
}

View File

@@ -0,0 +1,178 @@
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.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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;
/**
* Mesh speed test for facets
*/
public class MeshingTest5 {
private static final File BASE = new File( System.getProperty( "user.dir" ) );
private static final File FACET_DIR = new File( BASE, "facets" );
private static Collection< Facet > facets = Collections.synchronizedCollection( new LinkedHashSet< Facet >() );
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 );
}
synchronized( facets ) {
facets.addAll( plane.convert( 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;
}
}

View File

@@ -0,0 +1,991 @@
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.MeshChainEventHandler;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh.MeshPartitionEventHandler;
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;
/**
* Facet meshing visualizer
*/
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.addChainHandler( new MeshChainEventHandler() {
@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 ) );
}
}
}
}
} );
mesh.addPartitionHandler( new MeshPartitionEventHandler() {
@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.addChainHandler( new MeshChainEventHandler() {
@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 >();
}
}

View File

@@ -0,0 +1,98 @@
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 com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
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;
/**
* Mesh speed test for polygons
*/
public class MeshingTest7 {
private static final File BASE = new File( System.getProperty( "user.dir" ) );
private static final File POLYGON_DIR = new File( BASE, "polygons" );
public static void main( String[] args ) {
if ( POLYGON_DIR.exists() && POLYGON_DIR.isDirectory() ) {
System.out.println( "Found " + POLYGON_DIR.list().length + " files" );
final long allStart = System.currentTimeMillis();
final AtomicInteger index = new AtomicInteger( 0 );
final Collection< File > files = Arrays.asList( POLYGON_DIR.listFiles() );
files.parallelStream().forEach( f -> {
final StringBuilder builder = new StringBuilder();
builder.append( "Meshing file: " + f + "\n" );
final long start = System.currentTimeMillis();
final List< Polygon > polygons = mesh( f, builder );
Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
for ( Polygon poly : polygons ) {
mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE );
}
mesh.meshify();
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" );
} else {
System.err.println( "No such directory exists: " + POLYGON_DIR );
}
}
private static List< Polygon > mesh( File file, final StringBuilder stringBuilder ) {
// First parse the file to get a list of all polygons
final List< Polygon > polys = new ArrayList< Polygon >();
try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) {
String line;
while ( ( line = reader.readLine() ) != null ) {
if ( !line.isEmpty() ) {
final int pointCount = Integer.parseInt( line );
List< Point > points = new ArrayList< Point >( pointCount );
int point = 0;
while ( point < pointCount && ( 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 ] );
points.add( new Point( px, py ) );
++point;
}
}
polys.add( new Polygon( points ) ) ;
}
}
} catch ( FileNotFoundException e ) {
e.printStackTrace();
return null;
} catch ( IOException e ) {
e.printStackTrace();
return null;
}
return polys;
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsSpace.BroadphaseType;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.PlaneCollisionShape;
import com.jme3.bullet.objects.PhysicsBody;
import com.jme3.bullet.objects.PhysicsRigidBody;
import com.jme3.math.Plane;
import com.jme3.math.Vector3f;
public class MinieTest {
public static void main( String[] args ) {
System.loadLibrary( "bulletjme" );
System.out.println( "Testing a simple PhysicsSpace" );
PhysicsSpace space = new PhysicsSpace( BroadphaseType.DBVT );
PlaneCollisionShape plane = new PlaneCollisionShape( new Plane( new Vector3f( 0, 1, 0 ), 0 ) );
PhysicsRigidBody planeBody = new PhysicsRigidBody( plane, PhysicsBody.massForStatic );
planeBody.setPhysicsLocation( new Vector3f( 0, 0, 0 ) );
BoxCollisionShape box = new BoxCollisionShape( 0.5f );
PhysicsRigidBody rigid = new PhysicsRigidBody( box, 1f );
rigid.setPhysicsLocation( new Vector3f( 0f, 100f, 0f ) );
space.addCollisionObject( rigid );
space.addCollisionObject( planeBody );
for ( float i = 0; i < 9.6; i += 1 / 256.0 ) {
space.update( i );
Vector3f loc = rigid.getPhysicsLocation();
System.out.println( i + "\t: " + loc.getX() + ", " + loc.getY() + ", " + loc.getZ() );
}
}
}

View File

@@ -0,0 +1,58 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.command.PluginCommand;
import org.bukkit.permissions.Permission;
import com.aaaaahhhhhhh.bananapuncher714.minietest.util.BukkitUtil;
public class CommandBase {
protected PluginCommand command;
public CommandBase( String command ) {
this.command = BukkitUtil.constructCommand( command );
}
public CommandBase setPermission( String permission ) {
command.setPermission( permission );
return this;
}
public CommandBase setDescription( String description ) {
command.setDescription( description );
return this;
}
public CommandBase addAliases( String... aliases ) {
Set< String > aliasSet = new HashSet< String >( command.getAliases() );
for ( String alias : aliases ) {
aliasSet.add( alias );
}
command.setAliases( new ArrayList< String >( aliasSet ) );
return this;
}
public CommandBase addAliases( Collection< String > aliases ) {
Set< String > aliasSet = new HashSet< String >( command.getAliases() );
aliasSet.addAll( aliases );
command.setAliases( new ArrayList< String >( aliasSet ) );
return this;
}
public CommandBase setPermission( Permission permission ) {
return setPermission( permission.toString() );
}
public CommandBase setSubCommand( SubCommand subCommand ) {
subCommand.applyTo( command );
return this;
}
public PluginCommand build() {
return command;
}
}

View File

@@ -0,0 +1,25 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command;
import org.bukkit.command.CommandSender;
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.executor.CommandExecutable;
public class CommandOption {
protected CommandExecutable exe;
protected String[] args;
protected CommandParameters parameter;
public CommandOption( CommandExecutable exe, String[] args, CommandParameters parameter ) {
this.exe = exe;
this.args = args;
this.parameter = parameter;
}
public int getArgumentSize() {
return args.length;
}
public void execute( CommandSender sender ) {
exe.execute( sender, args, parameter );
}
}

View File

@@ -0,0 +1,65 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.google.common.primitives.Primitives;
public class CommandParameters {
protected List< Object > parameters = new ArrayList< Object >();
public CommandParameters() {
}
public CommandParameters( CommandParameters copy ) {
parameters.addAll( copy.parameters );
}
public void add( Object object ) {
parameters.add( object );
}
public int size() {
return parameters.size();
}
public < T > T get( Class < T > clazz, int index ) {
clazz = Primitives.wrap( clazz );
if ( index < 0 || index >= parameters.size() ) {
return null;
}
Object obj = parameters.get( index );
if ( obj != null && clazz.isInstance( obj ) ) {
return clazz.cast( obj );
}
return null;
}
public < T > T getLast( Class< T > clazz ) {
clazz = Primitives.wrap( clazz );
for ( int i = parameters.size() - 1; i >= 0; i-- ) {
Object obj = parameters.get( i );
if ( obj != null && clazz.isInstance( obj ) ) {
return clazz.cast( obj );
}
}
return null;
}
public < T > T getFirst( Class< T > clazz ) {
clazz = Primitives.wrap( clazz );
for ( int i = 0; i < parameters.size(); i++ ) {
Object obj = parameters.get( i );
if ( obj != null && clazz.isInstance( obj ) ) {
return clazz.cast( obj );
}
}
return null;
}
@Override
public String toString() {
return "CommandParameters[" + parameters.stream().map( Object::toString ).collect( Collectors.joining( ", " ) ) + "]";
}
}

View File

@@ -0,0 +1,39 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.bukkit.command.CommandSender;
public class CommandResult {
protected List< CommandOption > options = new ArrayList< CommandOption >();
public void add( CommandOption option ) {
options.add( option );
}
public void addAll( Collection< CommandOption > options ) {
this.options.addAll( options );
}
public void add( CommandResult result ) {
this.options.addAll( result.getOptions() );
}
public List< CommandOption > getOptions() {
return options;
}
public void execute( CommandSender sender ) {
CommandOption lowest = null;
for ( CommandOption option : options ) {
if ( lowest == null || option.getArgumentSize() < lowest.getArgumentSize() ) {
lowest = option;
}
}
if ( lowest != null ) {
lowest.execute( sender );
}
}
}

View File

@@ -0,0 +1,27 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command;
public class SplitCommand {
protected String[] input;
protected String[] arguments;
public SplitCommand( String[] input, String[] arguments ) {
this.input = input;
this.arguments = arguments;
}
public String[] getInput() {
return input;
}
public void setInput( String[] input ) {
this.input = input;
}
public String[] getArguments() {
return arguments;
}
public void setArguments( String[] arguments ) {
this.arguments = arguments;
}
}

View File

@@ -0,0 +1,231 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.util.StringUtil;
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.executor.CommandExecutable;
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.InputValidator;
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.InputValidatorString;
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.sender.SenderValidator;
/**
* A build and run command framework for automatic tab completions and easy branching.
*
* @author BananaPuncher714
*/
public class SubCommand {
protected List< SubCommand > subCommands = new ArrayList< SubCommand >();
protected InputValidator< ? > validator;
protected Set< SenderValidator > senderValidators = new HashSet< SenderValidator >();
protected CommandExecutable whenUnknown;
protected CommandExecutable whenNone;
/**
* Accept anything as a valid input
*/
public SubCommand() {
}
// Helped constructor, really belongs in a factory.
/**
* Create a SubCommand with the string as the input validator.
*
* @param command
* The subcommand value.
*/
public SubCommand( String command ) {
this( new InputValidatorString( command ) );
}
/**
* Create a SubCommand with the input validator provided.
*
* @param validator
* Can be null.
*/
public SubCommand( InputValidator< ? > validator ) {
this.validator = validator;
}
public SubCommand add( SubCommand builder ) {
subCommands.add( builder );
return this;
}
public SubCommand addSenderValidator( SenderValidator validator ) {
senderValidators.add( validator );
return this;
}
/**
* Ran when the arguments provided don't match any SubCommands registered.
*
* @param executable
* An executable where the arguments will start with the unknown subcommand.
* @return
* Builder pattern return.
*/
public SubCommand whenUnknown( CommandExecutable executable ) {
this.whenUnknown = executable;
return this;
}
// Naming by jetp250
/**
* Ran when there are no arguments provided, or if the executable for when unknown is not set.
*
* @param executable
* If null, nothing will happen.
* @return
* Builder pattern return.
*/
public SubCommand defaultTo( CommandExecutable executable ) {
this.whenNone = executable;
return this;
}
public boolean matches( CommandSender sender ) {
for ( SenderValidator validator : senderValidators ) {
if ( !validator.isValid( sender ) ) {
return false;
}
}
return true;
}
public boolean matches( CommandSender sender, String input[], String[] args ) {
return ( validator == null ? true : validator.isValid( sender, input, args ) ) && matches( sender );
}
public Collection< String > getTabCompletes( CommandSender sender, String[] input ) {
return validator == null ? null : validator.getTabCompletes( sender, input );
}
public List< SubCommand > getSubCommands() {
return subCommands;
}
public InputValidator< ? > getInputValidator() {
return validator;
}
public Set< SenderValidator > getSenderValidators() {
return senderValidators;
}
public CommandResult submit( CommandSender sender, String[] command, String[] args, CommandParameters parameter ) {
CommandResult result = new CommandResult();
parameter = new CommandParameters( parameter );
if ( validator != null ) {
parameter.add( validator.get( sender, command ) );
} else {
parameter.add( null );
}
if ( args.length > 0 ) {
boolean found = false;
for ( SubCommand subCommand : subCommands ) {
SplitCommand split = split( args, subCommand.getInputValidator().getArgumentCount() );
String[] input = split.getInput();
String[] newArgs = split.getArguments();
if ( subCommand.matches( sender, input, newArgs ) ) {
found = true;
result.add( subCommand.submit( sender, input, newArgs, parameter ) );
}
}
if ( !found ) {
if ( whenUnknown != null ) {
result.add( new CommandOption( whenUnknown, args, parameter ) );
} else if ( whenNone != null ) {
result.add( new CommandOption( whenNone, args, parameter ) );
}
}
} else {
if ( whenNone != null ) {
result.add( new CommandOption( whenNone, args, parameter ) );
}
}
return result;
}
public Collection< String > getTabCompletions( CommandSender sender, String[] args ) {
Set< String > tabs = new HashSet< String >();
if ( args.length > 0 ) {
for ( SubCommand subCommand : subCommands ) {
if ( subCommand.matches( sender ) ) {
SplitCommand split = split( args, subCommand.getInputValidator().getArgumentCount() );
String[] input = split.getInput();
String[] newArgs = split.getArguments();
// Check if the subcommand matches the argument, and if it has subcommands of its own
if ( subCommand.matches( sender, input, newArgs ) && !subCommand.getSubCommands().isEmpty() ) {
tabs.addAll( subCommand.getTabCompletions( sender, newArgs ) );
} else if ( newArgs.length == 0 ) {
// If not, and this is the last argument, add all possible tab completes
// This allows for "recommendations"
Collection< String > completions = subCommand.getTabCompletes( sender, input );
if ( completions != null ) {
tabs.addAll( completions );
}
}
}
}
}
return tabs;
}
public SubCommand applyTo( PluginCommand command ) {
command.setExecutor( this::onCommand );
command.setTabCompleter( this::onTabComplete );
return this;
}
private boolean onCommand( CommandSender sender, Command command, String arg2, String[] args ) {
CommandParameters parameters = new CommandParameters();
if ( matches( sender ) ) {
String[] commandArr = new String[ args.length + 1 ];
commandArr[ 0 ] = command.getName();
for ( int i = 1; i < commandArr.length; i++ ) {
commandArr[ i ] = args[ i - 1 ];
}
SplitCommand split = split( commandArr, validator.getArgumentCount() );
submit( sender, split.getInput(), split.getArguments(), parameters ).execute( sender );
}
return false;
}
private List< String > onTabComplete( CommandSender sender, Command command, String arg2, String[] args ) {
List< String > completions = new ArrayList< String >();
String[] commandArr = new String[ args.length + 1 ];
commandArr[ 0 ] = command.getName();
for ( int i = 1; i < commandArr.length; i++ ) {
commandArr[ i ] = args[ i - 1 ];
}
SplitCommand split = split( commandArr, validator.getArgumentCount() );
StringUtil.copyPartialMatches( args[ args.length - 1 ], getTabCompletions( sender, split.getArguments() ), completions );
Collections.sort( completions );
return completions;
}
protected static SplitCommand split( String[] command, int inputSize ) {
String[] input = command.length > 0 ? Arrays.copyOfRange( command, 0, Math.min( inputSize, command.length ) ) : new String[ 0 ];
String[] args = command.length > inputSize ? Arrays.copyOfRange( command, inputSize, command.length ) : new String[ 0 ];
return new SplitCommand( input, args );
}
}

View File

@@ -0,0 +1,9 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.executor;
import org.bukkit.command.CommandSender;
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.CommandParameters;
public interface CommandExecutable {
void execute( CommandSender sender, String[] args, CommandParameters params );
}

View File

@@ -0,0 +1,18 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.executor;
import org.bukkit.command.CommandSender;
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.CommandParameters;
public class CommandExecutableMessage implements CommandExecutable {
protected String message;
public CommandExecutableMessage( String message ) {
this.message = message;
}
@Override
public void execute( CommandSender sender, String[] args, CommandParameters params ) {
sender.sendMessage( message );
}
}

View File

@@ -0,0 +1,8 @@
/**
*
*/
/**
* @author BananaPuncher714
*
*/
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.executor;

View File

@@ -0,0 +1,6 @@
/**
* Classes for building complex command trees.
*
* @author BananaPuncher714
*/
package com.aaaaahhhhhhh.bananapuncher714.minietest.command;

View File

@@ -0,0 +1,14 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator;
import java.util.Collection;
import org.bukkit.command.CommandSender;
public interface InputValidator< T > {
Collection< String > getTabCompletes( CommandSender sender, String[] input );
boolean isValid( CommandSender sender, String[] input, String[] args );
T get( CommandSender sender, String[] input );
default int getArgumentCount() {
return 1;
}
}

View File

@@ -0,0 +1,37 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator;
import java.util.Collection;
import org.bukkit.command.CommandSender;
public class InputValidatorArguments implements InputValidator< Void > {
protected int min;
protected int max;
public InputValidatorArguments( int amount ) {
this.min = amount;
this.max = amount;
}
public InputValidatorArguments( int min, int 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 ) {
int len = args.length;
return len >= min && len <= max;
}
@Override
public Void get( CommandSender sender, String input[] ) {
return null;
}
}

View File

@@ -0,0 +1,47 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.command.CommandSender;
public class InputValidatorBoolean implements InputValidator< Boolean > {
protected Set< String > trueVals = new HashSet< String >();
protected Set< String > falseVals = new HashSet< String >();
public InputValidatorBoolean( String[] trueVal, String[] falseVal ) {
for ( String str : trueVal ) {
trueVals.add( str.toLowerCase() );
}
for ( String str : falseVal ) {
falseVals.add( str.toLowerCase() );
}
}
public InputValidatorBoolean() {
trueVals.add( "true" );
falseVals.add( "false" );
}
@Override
public Collection< String > getTabCompletes( CommandSender sender, String[] input ) {
Set< String > combined = new HashSet< String >( trueVals );
combined.addAll( falseVals );
return combined;
}
@Override
public boolean isValid( CommandSender sender, String input[], String[] args ) {
String lowercase = input[ 0 ].toLowerCase();
return trueVals.contains( lowercase ) || falseVals.contains( lowercase );
}
@Override
public Boolean get( CommandSender sender, String input[] ) {
String lowercase = input[ 0 ].toLowerCase();
return trueVals.contains( lowercase );
}
}

View File

@@ -0,0 +1,44 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.bukkit.command.CommandSender;
public class InputValidatorChain< T > implements InputValidator< T > {
protected InputValidator< T > primaryValidator;
protected List< InputValidator< ? > > validators = new ArrayList< InputValidator< ? > >();
public InputValidatorChain( InputValidator< T > primaryValidator ) {
this.primaryValidator = primaryValidator;
}
public InputValidatorChain< T > addValidator( InputValidator< ? > validator ) {
validators.add( validator );
return this;
}
@Override
public Collection< String > getTabCompletes( CommandSender sender, String[] input ) {
return primaryValidator.getTabCompletes( sender, input );
}
@Override
public boolean isValid( CommandSender sender, String input[], String[] args ) {
if ( !primaryValidator.isValid( sender, input, args ) ) {
return false;
}
for ( InputValidator< ? > validator : validators ) {
if ( !validator.isValid( sender, input, args ) ) {
return false;
}
}
return true;
}
@Override
public T get( CommandSender sender, String input[] ) {
return primaryValidator.get( sender, input );
}
}

View File

@@ -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 ] );
}
}

View File

@@ -0,0 +1,38 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator;
import java.util.Collection;
import org.bukkit.command.CommandSender;
public class InputValidatorInt implements InputValidator< Integer > {
protected int min = Integer.MIN_VALUE;
protected int max = Integer.MAX_VALUE;
public InputValidatorInt() {
}
public InputValidatorInt( int min, int 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 {
int i = Integer.valueOf( input[ 0 ] );
return i >= min && i <= max;
} catch ( Exception exception ) {
return false;
}
}
@Override
public Integer get( CommandSender sender, String input[] ) {
return Integer.parseInt( input[ 0 ] );
}
}

View File

@@ -0,0 +1,28 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator;
import java.util.Collection;
import org.bukkit.command.CommandSender;
public class InputValidatorPattern implements InputValidator< String > {
protected String pattern;
public InputValidatorPattern( String pattern ) {
this.pattern = pattern;
}
@Override
public Collection< String > getTabCompletes( CommandSender sender, String[] input ) {
return null;
}
@Override
public boolean isValid( CommandSender sender, String input[], String[] args ) {
return input[ 0 ].matches( pattern );
}
@Override
public String get( CommandSender sender, String input[] ) {
return input[ 0 ];
}
}

View File

@@ -0,0 +1,31 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class InputValidatorPlayer implements InputValidator< Player > {
@Override
public Collection< String > getTabCompletes( CommandSender sender, String[] input ) {
Set< String > playerNames = new HashSet< String >();
for ( Player player : Bukkit.getOnlinePlayers() ) {
playerNames.add( player.getName() );
}
return playerNames;
}
@Override
public boolean isValid( CommandSender sender, String input[], String[] args ) {
return Bukkit.getPlayerExact( input[ 0 ] ) != null;
}
@Override
public Player get( CommandSender sender, String input[] ) {
return Bukkit.getPlayerExact( input[ 0 ] );
}
}

View File

@@ -0,0 +1,38 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang.Validate;
import org.bukkit.command.CommandSender;
public class InputValidatorString implements InputValidator< String > {
protected Set< String > values = new HashSet< String >();
public InputValidatorString( String value ) {
values.add( value.toLowerCase() );
}
public InputValidatorString( String... values ) {
Validate.isTrue( values.length > 0, "Must provide at least 1 argument!" );
for ( String str : values ) {
this.values.add( str.toLowerCase() );
}
}
@Override
public Collection< String > getTabCompletes( CommandSender sender, String[] input ) {
return values;
}
@Override
public boolean isValid( CommandSender sender, String input[], String[] args ) {
return values.contains( input[ 0 ].toLowerCase() );
}
@Override
public String get( CommandSender sender, String input[] ) {
return input[ 0 ];
}
}

View File

@@ -0,0 +1,8 @@
/**
*
*/
/**
* @author BananaPuncher714
*
*/
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator;

View File

@@ -0,0 +1,7 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.sender;
import org.bukkit.command.CommandSender;
public interface SenderValidator {
boolean isValid( CommandSender sender );
}

View File

@@ -0,0 +1,11 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.sender;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class SenderValidatorNotPlayer implements SenderValidator {
@Override
public boolean isValid( CommandSender sender ) {
return !( sender instanceof Player );
}
}

View File

@@ -0,0 +1,27 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.sender;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.command.CommandSender;
public class SenderValidatorPermission implements SenderValidator {
protected Set< String > permissions = new HashSet< String >();
public SenderValidatorPermission( String... permissions ) {
for ( String permission : permissions ) {
this.permissions.add( permission );
}
}
@Override
public boolean isValid( CommandSender sender ) {
for ( String permission : permissions ) {
if ( sender.hasPermission( permission ) ) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,11 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.sender;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class SenderValidatorPlayer implements SenderValidator {
@Override
public boolean isValid( CommandSender sender ) {
return sender instanceof Player;
}
}

View File

@@ -0,0 +1,8 @@
/**
*
*/
/**
* @author BananaPuncher714
*
*/
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.sender;

View File

@@ -0,0 +1,50 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.io.Serializable;
import org.bukkit.util.BoundingBox;
public class Aabb implements Serializable {
private static final long serialVersionUID = 6518512405568765913L;
public final int minX;
public final int minY;
public final int minZ;
public final int maxX;
public final int maxY;
public final int maxZ;
public final int width;
public final int depth;
public final int height;
public Aabb( final int minX, final int minY, final int minZ, final int maxX, final int maxY, final int maxZ ) {
this.minX = minX;
this.minY = minY;
this.minZ = minZ;
this.maxX = maxX;
this.maxY = maxY;
this.maxZ = maxZ;
this.width = maxX - minX;
this.depth = maxZ - minZ;
this.height = maxY - minY;
}
public Aabb( final BoundingBox box ) {
this.minX = ( int ) box.getMinX();
this.maxX = ( int ) box.getMaxX();
this.minY = ( int ) box.getMinY();
this.maxY = ( int ) box.getMaxY();
this.minZ = ( int ) box.getMinZ();
this.maxZ = ( int ) box.getMaxZ();
this.width = maxX - minX;
this.depth = maxZ - minZ;
this.height = maxY - minY;
}
public int getVolume() {
return width * depth * height;
}
}

View File

@@ -0,0 +1,5 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
public enum BlockDataType {
NONE, COMPLEX, SOLID
}

View File

@@ -0,0 +1,55 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.util.BitSet;
import com.aaaaahhhhhhh.bananapuncher714.minietest.util.BinaryWriter;
public class BlockDataTypeSet {
private static final int BLOCK_SIZE;
private static final BitSet[] SETS;
static {
final int length = BlockDataType.values().length;
BLOCK_SIZE = 32 - Integer.numberOfLeadingZeros( length );
SETS = new BitSet[ length ];
// Perhaps not the fastest, since it sets each bit in N time
for ( int i = 0; i < length; ++i ) {
final BitSet set = new BitSet();
for ( int j = 0; j < BLOCK_SIZE; ++j ) {
set.set( j, ( ( i >> j ) & 1 ) == 1 );
}
SETS[ i ] = set;
}
}
protected BitSet buffer;
public BlockDataTypeSet( final byte[] arr ) {
buffer = BitSet.valueOf( arr );
}
public BlockDataTypeSet( final int size ) {
buffer = new BitSet( size * BLOCK_SIZE );
}
public void set( final int index, final BlockDataType type ) {
final BitSet set = SETS[ type.ordinal() ];
for ( int i = 0; i < BLOCK_SIZE; ++i ) {
buffer.set( ( index * BLOCK_SIZE ) + i, set.get( i ) );
}
}
public BlockDataType get( final int index ) {
final int bufferIndex = index * BLOCK_SIZE;
int ord = 0;
for ( int i = buffer.nextSetBit( bufferIndex ); i >= bufferIndex && i < bufferIndex + BLOCK_SIZE; i = buffer.nextSetBit( i + 1 ) ) {
ord |= 1 << ( i - bufferIndex );
}
return BlockDataType.values()[ ord ];
}
public byte[] toByteArray() {
return buffer.toByteArray();
}
}

View File

@@ -0,0 +1,298 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
import org.bukkit.World;
/**
* Represents a Bukkit chunk's location.
*
* @author BananaPuncher714
*/
public class ChunkLocation implements Comparable< ChunkLocation > {
private int x, z;
private String worldName;
private World world;
/**
* Construct a ChunkLocation from a Location.
*
* @param location
* Cannot be null.
*/
public ChunkLocation( Location location ) {
Validate.notNull( location );
world = location.getWorld();
worldName = world.getName();
x = location.getBlockX() >> 4;
z = location.getBlockZ() >> 4;
}
/**
* Copy a ChunkLocation.
*
* @param location
* Cannot be null.
*/
public ChunkLocation( ChunkLocation location ) {
Validate.notNull( location );
x = location.x;
z = location.z;
worldName = location.worldName;
world = location.world;
}
/**
* Create a ChunkLocation from chunk coordinates.
*
* @param world
* Cannot be null.
* @param x
* Chunk coordinate x value.
* @param z
* Chunk coordinate z value.
*/
public ChunkLocation( World world, int x, int z ) {
Validate.notNull( world );
this.x = x;
this.z = z;
this.worldName = world.getName();
this.world = world;
}
public ChunkLocation( String world, int x, int z ) {
this.x = x;
this.z = z;
this.worldName = world;
}
/**
* Create a ChunkLocation from a Bukkit chunk.
*
* @param chunk
* Cannot be null.
*/
public ChunkLocation( Chunk chunk ) {
Validate.notNull( chunk );
world = chunk.getWorld();
worldName = world.getName();
x = chunk.getX();
z = chunk.getZ();
}
/**
* Create a ChunkLocation from a ChunkSnapshot.
*
* @param snapshot
* Cannot be null. The world will not be cached, only the world name.
*/
public ChunkLocation( ChunkSnapshot snapshot ) {
Validate.notNull( snapshot );
worldName = snapshot.getWorldName();
x = snapshot.getX();
z = snapshot.getZ();
}
/**
* Get the x coordinate.
*
* @return
* Chunk coordinate x value.
*/
public int getX() {
return x;
}
/**
* Set the x coordinate.
*
* @param x
* Chunk coordinate x value.
*/
public ChunkLocation setX( int x ) {
this.x = x;
return this;
}
/**
* Get the z coordinate.
*
* @return
* Chunk coordinate z value.
*/
public int getZ() {
return z;
}
/**
* Set the z coordinate.
*
* @param z
* Chunk coordinate z value.
*/
public ChunkLocation setZ( int z ) {
this.z = z;
return this;
}
/**
* Add coordinates from current coordinates.
*
* @param x
* Chunk x coordinate value.
* @param z
* Chunk z coordinate value.
* @return
* This location.
*/
public ChunkLocation add( int x, int z ) {
this.x += x;
this.z += z;
return this;
}
/**
* Subtract coordinates from current coordinates.
*
* @param x
* Chunk x coordinate value.
* @param z
* Chunk z coordinate value.
* @return
* This location.
*/
public ChunkLocation subtract( int x, int z ) {
this.x -= x;
this.z -= z;
return this;
}
public String getWorldName() {
return worldName;
}
/**
* Get the world.
*
* @return
* A non-null world, will fetch and cache the world if not cached already.
*/
public World getWorld() {
if ( world == null ) {
world = Bukkit.getWorld( worldName );
}
return world;
}
/**
* Set the world.
*
* @param world
* Cannot be null.
*/
public void setWorld( World world ) {
Validate.notNull( world );
this.world = world;
this.worldName = world.getName();
}
/**
* Get the chunk this location represents.
*
* @return
* May load the chunk.
*/
public Chunk getChunk() {
return world.getChunkAt( x, z );
}
/**
* Check if the chunk that this location represents is loaded.
*
* @return
* If the chunk is loaded. Does not load the chunk.
*/
public boolean isLoaded() {
return getWorld().isChunkLoaded( x, z );
}
/**
* Load the chunk that this location represents.
*/
public void load() {
getWorld().loadChunk( x, z );
}
/**
* Check if this chunk exists.
*
* @return
* Checks if the chunk exists, does not have to be loaded.
*/
public boolean exists() {
return getWorld().loadChunk( x, z, false );
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( worldName == null ) ? 0 : worldName.hashCode() );
result = prime * result + x;
result = prime * result + z;
return result;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
ChunkLocation other = ( ChunkLocation ) obj;
if ( worldName == null ) {
if ( other.worldName != null ) {
return false;
}
} else if ( !worldName.equals( other.worldName ) ) {
return false;
}
if ( x != other.x ) {
return false;
}
if ( z != other.z ) {
return false;
}
return true;
}
@Override
public String toString() {
return "ChunkLocation{x:" + x + ",z:" + z + ",world:" + worldName + "}";
}
@Override
public int compareTo( ChunkLocation o ) {
final int worldCompare = worldName.compareTo( o.worldName );
if ( worldCompare == 0 ) {
if ( x == o.x ) {
return Integer.compare( z, o.z );
}
return Integer.compare( x, o.x );
}
return worldCompare;
}
}

View File

@@ -0,0 +1,285 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.TreeSet;
import org.bukkit.block.data.BlockData;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.MinecraftPlane.PlaneAxis;
import com.aaaaahhhhhhh.bananapuncher714.minietest.util.BinaryWriter;
import com.jme3.bullet.collision.shapes.infos.IndexedMesh;
import com.jme3.math.Vector3f;
public class ChunkMesh {
// TODO Public for now while I figure out how to organize the stuff properly
// Hashcode of the current block's blockdata so we can check if it changed
private int[] data;
// The bounding boxes for each complex block, null if air or a cube
private BoundingBox[][] boxes;
// Stores which blocks are complex/simple/empty
private BlockDataTypeSet blockTypes;
// References to every point used by at least one planemesh
public TreeSet< VectorReference > pointReferences = new TreeSet< VectorReference >();
// Map of meshes to each plane
// TODO Allow for non axis-aligned planes
public Map< MinecraftPlane, PlaneMesh > planes = new TreeMap< MinecraftPlane, PlaneMesh >();
// Bounding box of this mesh
private Aabb box;
public ChunkMesh( BoundingBox box ) {
this.box = new Aabb( box );
final int volume = this.box.getVolume();
data = new int[ volume ];
boxes = new BoundingBox[ volume ][];
blockTypes = new BlockDataTypeSet( volume );
}
public ChunkMesh( Aabb box ) {
this.box = box;
final int volume = this.box.getVolume();
data = new int[ volume ];
boxes = new BoundingBox[ volume ][];
blockTypes = new BlockDataTypeSet( volume );
}
public void setType( final int index, final BlockDataType type ) {
blockTypes.set( index, type );
}
public BlockDataType getType( final int index ) {
return blockTypes.get( index );
}
public void setData( final int index, final BlockData data ) {
this.data[ index ] = data.getAsString().hashCode();
}
public void setBoxes( final int index, final BoundingBox[] boxes ) {
this.boxes[ index ] = boxes;
}
public boolean isBlockAt( final int index, final BlockData data ) {
return blockTypes.get( index ) == BlockDataType.COMPLEX && this.data[ index ] == data.getAsString().hashCode();
}
public BoundingBox[] getBoxesAt( final int index ) {
return boxes[ index ];
}
public PlaneMesh getOrAdd( final MinecraftPlane plane ) {
PlaneMesh results = planes.get( plane );
if ( results == null ) {
results = new PlaneMesh();
planes.put( plane, results );
}
return results;
}
public IndexedMesh toIndexedMesh() {
final Vector3f[] positionArray = new Vector3f[ pointReferences.size() ];
int vertexIndex = 0;
for ( final VectorReference ref : pointReferences ) {
final Vector vector = ref.vector;
positionArray[ vertexIndex ] = new Vector3f( ( float ) vector.getX(), ( float ) vector.getY(), ( float ) vector.getZ() );
ref.index = vertexIndex;
++vertexIndex;
}
final int totalTriangleCount = planes.values().stream().mapToInt( m -> m.completedPolygons.size() ).sum();
int triangleIndex = 0;
final int[] indexArray = new int[ totalTriangleCount * 3 ];
for ( final PlaneMesh mesh : planes.values() ) {
for ( final IndexedPolygon triangle : mesh.completedPolygons ) {
if ( triangle.points.size() != 3 ) {
throw new IllegalStateException( "Triangle does not have exactly 3 vertices!" );
}
final int indexArrayStart = triangleIndex * 3;
indexArray[ indexArrayStart ] = triangle.points.get( 0 ).index;
indexArray[ indexArrayStart + 1 ] = triangle.points.get( 1 ).index;
indexArray[ indexArrayStart + 2 ] = triangle.points.get( 2 ).index;
++triangleIndex;
}
}
return new IndexedMesh( positionArray, indexArray );
}
public void writeTo( final BinaryWriter writer ) {
// Gather all vectors and map them to indices
// Write the bounding box
writer.write( box.minX );
writer.write( box.minY );
writer.write( box.minZ );
writer.write( box.maxX );
writer.write( box.maxY );
writer.write( box.maxZ );
// Write our data blocks
for ( final int hash : data ) {
writer.write( hash );
}
// Write our bounding boxes
for ( final BoundingBox[] boxArr : boxes ) {
// Write how many boxes
if ( boxArr == null ) {
writer.write( ( int ) 0 );
} else {
writer.write( boxArr.length );
for ( final BoundingBox box : boxArr ) {
writer.write( box.getMinX() );
writer.write( box.getMinY() );
writer.write( box.getMinZ() );
writer.write( box.getMaxX() );
writer.write( box.getMaxY() );
writer.write( box.getMaxZ() );
}
}
}
// Write our block types
final byte[] blockTypeBuf = blockTypes.toByteArray();
writer.write( blockTypeBuf.length );
writer.write( blockTypeBuf );
// Write our point references
writer.write( pointReferences.size() );
for ( final VectorReference ref : pointReferences ) {
writer.write( ref.vector.getX() );
writer.write( ref.vector.getY() );
writer.write( ref.vector.getZ() );
writer.write( ref.index );
writer.write( ref.referenceCount );
}
// Write our planes
writer.write( planes.size() );
for ( final Entry< MinecraftPlane, PlaneMesh > entry : planes.entrySet() ) {
final MinecraftPlane plane = entry.getKey();
writer.write( plane.normal.ordinal() );
writer.write( plane.offset );
final PlaneMesh mesh = entry.getValue();
// Write each region
writer.write( mesh.generatedRegions.size() );
for ( final Polygon poly : mesh.generatedRegions ) {
writer.write( poly.getPoints().size() );
for ( final Point p : poly.getPoints() ) {
writer.write( p.getX() );
writer.write( p.getY() );
}
}
// Write each triangle
writer.write( mesh.completedPolygons.size() );
for ( final IndexedPolygon poly : mesh.completedPolygons ) {
writer.write( poly.points.size() );
for ( final VectorReference ref : poly.points ) {
writer.write( ref.index );
}
}
}
}
public static ChunkMesh readFrom( final ByteBuffer buffer ) {
final Aabb box = new Aabb( buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt() );
final ChunkMesh mesh = new ChunkMesh( box );
final int size = box.getVolume();
buffer.asIntBuffer().get( mesh.data );
buffer.position( buffer.position() + ( mesh.data.length << 2 ) );
// Read our boxes
for ( int i = 0; i < size; ++i ) {
final int boxCount = buffer.getInt();
if ( boxCount > 0 ) {
final BoundingBox[] boxes = new BoundingBox[ boxCount ];
for ( int j = 0; j < boxCount; ++j ) {
boxes[ j ] = new BoundingBox( buffer.getDouble(), buffer.getDouble(), buffer.getDouble(), buffer.getDouble(), buffer.getDouble(), buffer.getDouble() );
}
mesh.boxes[ i ] = boxes;
}
}
// Read the block types
final int blockTypeBufSize = buffer.getInt();
final byte[] bitSet = new byte[ blockTypeBufSize ];
buffer.get( bitSet );
mesh.blockTypes = new BlockDataTypeSet( bitSet );
// Read the plane references
final int pointCount = buffer.getInt();
final VectorReference[] references = new VectorReference[ pointCount ];
for ( int i = 0; i < pointCount; ++i ) {
final VectorReference reference = new VectorReference( new Vector( buffer.getDouble(), buffer.getDouble(), buffer.getDouble() ) );
reference.index = buffer.getInt();
reference.referenceCount = buffer.getInt();
references[ reference.index ] = reference;
mesh.pointReferences.add( reference );
}
// Get the planes
final int planeCount = buffer.getInt();
for ( int i = 0; i < planeCount; ++i ) {
final MinecraftPlane plane = new MinecraftPlane( PlaneAxis.values()[ buffer.getInt() ], buffer.getDouble() );
final PlaneMesh planeMesh = new PlaneMesh();
planeMesh.generatedRegions = new ArrayDeque< Polygon >();
planeMesh.completedPolygons = new ArrayDeque< IndexedPolygon >();
final int regionCount = buffer.getInt();
for ( int j = 0; j < regionCount; ++j ) {
final int vertexCount = buffer.getInt();
final List< Point > points = new ArrayList< Point >();
for ( int k = 0; k < vertexCount; ++k ) {
points.add( new Point( buffer.getDouble(), buffer.getDouble() ) );
}
planeMesh.generatedRegions.add( new Polygon( points ) );
}
final int triangleCount = buffer.getInt();
for ( int j = 0; j < triangleCount; ++j ) {
final int vertexCount = buffer.getInt();
final IndexedPolygon triangle = new IndexedPolygon();
for ( int k = 0; k < vertexCount; ++k ) {
triangle.points.add( references[ buffer.getInt() ] );
}
planeMesh.completedPolygons.add( triangle );
}
mesh.planes.put( plane, planeMesh );
}
return mesh;
}
}

View File

@@ -0,0 +1,8 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.util.ArrayList;
import java.util.List;
public class IndexedPolygon {
public List< VectorReference > points = new ArrayList< VectorReference >();
}

View File

@@ -0,0 +1,74 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.io.Serializable;
import org.bukkit.util.Vector;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
public class MinecraftPlane implements Comparable< MinecraftPlane > {
public final PlaneAxis normal;
public final double offset;
public MinecraftPlane( final PlaneAxis axis, final double offset ) {
this.normal = axis;
this.offset = offset;
}
public Vector convert( final Point point ) {
switch ( normal ) {
case X:
return new Vector( offset, point.getX(), point.getY() );
case Y:
return new Vector( point.getX(), offset, point.getY() );
case Z:
return new Vector( point.getX(), point.getY(), offset );
}
throw new IllegalStateException( "No normal set!" );
}
@Override
public int compareTo( final MinecraftPlane o ) {
if ( normal == o.normal ) {
return Double.compare( offset, o.offset );
}
return normal.compareTo( o.normal );
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((normal == null) ? 0 : normal.hashCode());
long temp;
temp = Double.doubleToLongBits(offset);
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;
MinecraftPlane other = (MinecraftPlane) obj;
if (normal != other.normal)
return false;
if (Double.doubleToLongBits(offset) != Double.doubleToLongBits(other.offset))
return false;
return true;
}
@Override
public String toString() {
return "MinecraftPlane{axis=" + normal + ",offset=" + offset + "}";
}
public static enum PlaneAxis {
X, Y, Z
}
}

View File

@@ -0,0 +1,10 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.util.Collection;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
public class PlaneMesh {
public Collection< Polygon > generatedRegions;
public Collection< IndexedPolygon > completedPolygons;
}

View File

@@ -0,0 +1,29 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import org.bukkit.util.Vector;
public class VectorReference implements Comparable< VectorReference > {
public Vector vector;
public int referenceCount = 0;
public int index = 0;
public VectorReference( final Vector vector ) {
this.vector = vector;
}
@Override
public int compareTo( final VectorReference o ) {
final Vector oVec = o.vector;
if ( vector.getX() == oVec.getX() ) {
if ( vector.getY() == oVec.getY() ) {
if ( vector.getZ() == oVec.getZ() ) {
return 0;
}
return Double.compare( vector.getZ(), oVec.getZ() );
}
return Double.compare( vector.getY(), oVec.getY() );
}
return Double.compare( vector.getX(), oVec.getX() );
}
}

View File

@@ -0,0 +1,11 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.util.Vector;
public class Facet {
public Vector normal;
public List< Vector > points = new ArrayList< Vector >();
}

View File

@@ -0,0 +1,45 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh;
import org.bukkit.util.Vector;
public class LineSegment {
public Vector point1, point2;
public LineSegment( Vector point1, Vector point2 ) {
if ( point1.getX() < point2.getX() ) {
this.point1 = point1;
this.point2 = point2;
} else if ( point1.getX() > point2.getX() ) {
this.point1 = point2;
this.point2 = point1;
} else if ( point1.getY() < point2.getY() ) {
this.point1 = point1;
this.point2 = point2;
} else if ( point1.getY() > point2.getY() ) {
this.point1 = point2;
this.point2 = point1;
} else if ( point1.getZ() < point2.getZ() ) {
this.point1 = point1;
this.point2 = point2;
} else if ( point1.getZ() > point2.getZ() ) {
this.point1 = point2;
this.point2 = point1;
} else {
// point 1 and 2 are the same, I guess...
this.point1 = point1;
this.point2 = point2;
}
}
public boolean isPoint() {
return point1.equals( point2 );
}
public double length() {
return point1.distance( point2 );
}
public Vector getVector() {
return point2.clone().subtract( point1 );
}
}

View File

@@ -0,0 +1,30 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.bukkit.util.Vector;
public class MeshBuilder {
public List< Plane > planes = new ArrayList< Plane >();
public Plane addFacet( Facet facet ) {
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();
plane.normal = facet.normal;
Optional< Vector > ref = facet.points.stream().filter( v -> v.lengthSquared() > 0.001 && Math.abs( v.clone().normalize().dot( facet.normal ) ) < .999999 ).findAny();
if ( ref.isPresent() ) {
plane.point = ref.get();
}
plane.addShape( facet );
planes.add( plane );
return plane;
}
}

View File

@@ -0,0 +1,98 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.bukkit.util.Vector;
import org.joml.Matrix3d;
import org.joml.Vector3d;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
public class Plane {
public Vector normal;
public Vector point;
public double distance;
private boolean set = false;
public List< Polygon > polygons = new ArrayList< Polygon >();
private Matrix3d mat;
private Matrix3d transpose;
public void addShape( Facet facet ) {
final Matrix3d mat = getProjectionMatrix();
List< Point > polygon = new ArrayList< Point >();
for ( Vector p : facet.points ) {
double t = point.clone().subtract( p ).dot( normal );
Vector i = normal.clone().multiply( t ).add( p );
Vector3d v = new Vector3d( i.getX(), i.getY(), i.getZ() );
Vector3d r = v.mul( mat );
polygon.add( new Point( r.x(), r.y() ) );
if ( !set ) {
distance = r.z();
set = true;
} else if ( Math.abs( r.z() - distance ) > 1e-7 ) {
throw new IllegalStateException( "Bad point in facet!" );
}
}
polygons.add( new Polygon( polygon ) );
}
public Vector convert( final Point p ) {
final Vector3d plane = new Vector3d( p.getX(), p.getY(), distance );
final Vector3d res = plane.mul( getInverseProjectionMatrix() );
return new Vector( res.x(), res.y(), res.z() );
}
public Facet convert( final Polygon polygon ) {
final Facet facet = new Facet();
facet.normal = normal;
for ( final Point p : polygon.getPoints() ) {
facet.points.add( convert( p ) );
}
return facet;
}
public Collection< Facet > convert( final Collection< Polygon > polygons ) {
return polygons.parallelStream().map( this::convert ).collect( Collectors.toSet() );
}
public boolean matches( Vector normal, Vector point ) {
return Math.abs( this.normal.dot( normal ) ) > 0.99 && Math.abs( this.point.clone().subtract( point ).dot( normal ) ) < 0.01;
}
private Matrix3d getProjectionMatrix() {
if ( mat == null ) {
final Vector basis1 = normal.clone().multiply( point.clone().multiply( -1 ).dot( normal ) ).add( point ).normalize();
final Vector3d b1 = new Vector3d( basis1.getX(), basis1.getY(), basis1.getZ() );
final Vector3d b3 = new Vector3d( normal.getX(), normal.getY(), normal.getZ() );
final Vector3d b2 = new Vector3d( b1 ).cross( b3 ).normalize();
mat = new Matrix3d( b1, b2, b3 ).transpose();
}
return mat;
}
private Matrix3d getInverseProjectionMatrix() {
if ( transpose == null ) {
transpose = getProjectionMatrix().transpose();
}
return transpose;
}
}

View File

@@ -0,0 +1,74 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.util;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* Big/little endian binary writer
*/
public class BinaryWriter {
protected final ByteArrayOutputStream buffer;
public BinaryWriter() {
buffer = new ByteArrayOutputStream();
}
public BinaryWriter( final int startSize ) {
buffer = new ByteArrayOutputStream( startSize );
}
public void write( final long val, final int length ) {
for ( int i = length - 1; i >= 0; i-- ) {
write( ( byte ) ( ( val >> ( i << 3 ) ) & 0xFFL ) );
}
}
public void write( final int val ) {
write( val, 4 );
}
public void write( final short val ) {
write( val, 2 );
}
public void write( final double val ) {
final byte[] bytes = new byte[ 8 ];
ByteBuffer.wrap( bytes ).putDouble( val );
write( bytes );
}
public void writeLittle( final long val, final int length ) {
for ( int i = 0; i < length; i++ ) {
write( ( byte ) ( ( val >> ( i << 3 ) ) & 0xFFL ) );
}
}
public void write( final long[] a, final int length ) {
for ( long l : a ) {
write( l, length );
}
}
public void write( final byte b ) {
buffer.write( b );
}
public void write( String str, final int length ) {
final byte[] bytes = str.getBytes();
final byte[] res = new byte[ length ];
for ( int i = 0; i < bytes.length && i < length; ++i ) {
res[ i ] = bytes[ i ];
}
write( res );
}
public void write( final byte[] a ) {
buffer.write( a, 0, a.length );
}
public byte[] toByteArray() {
return buffer.toByteArray();
}
}

View File

@@ -0,0 +1,183 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.regex.Pattern;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.event.Event;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoader;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.SimplePluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader;
import com.aaaaahhhhhhh.bananapuncher714.minietest.MiniePlugin;
public class BukkitUtil {
private static Constructor< PluginCommand > PLUGINCOMMAND_CONSTRUCTOR;
private static Field SIMPLEPLUGINMANAGER_FILEASSOCIATIONS;
private static Method JAVAPLUGINLOADER_GETCLASS_1;
private static Method JAVAPLUGINLOADER_GETCLASS_2;
private static Method JAVAPLUGINLOADER_SETCLASS;
private static Method JAVAPLUGINLOADER_REMOVECLASS_1;
private static Method JAVAPLUGINLOADER_REMOVECLASS_2;
static {
try {
PLUGINCOMMAND_CONSTRUCTOR = PluginCommand.class.getDeclaredConstructor( String.class, Plugin.class );
PLUGINCOMMAND_CONSTRUCTOR.setAccessible( true );
SIMPLEPLUGINMANAGER_FILEASSOCIATIONS = SimplePluginManager.class.getDeclaredField( "fileAssociations" );
SIMPLEPLUGINMANAGER_FILEASSOCIATIONS.setAccessible( true );
JAVAPLUGINLOADER_SETCLASS = JavaPluginLoader.class.getDeclaredMethod( "setClass", String.class, Class.class );
JAVAPLUGINLOADER_SETCLASS.setAccessible( true );
} catch ( NoSuchMethodException | SecurityException | NoSuchFieldException e ) {
e.printStackTrace();
}
try {
JAVAPLUGINLOADER_GETCLASS_1 = JavaPluginLoader.class.getDeclaredMethod( "getClassByName", String.class );
JAVAPLUGINLOADER_GETCLASS_1.setAccessible( true );
} catch ( NoSuchMethodException | SecurityException e ) {
try {
JAVAPLUGINLOADER_GETCLASS_2 = JavaPluginLoader.class.getDeclaredMethod( "getClassByName", String.class, boolean.class, PluginDescriptionFile.class );
JAVAPLUGINLOADER_GETCLASS_2.setAccessible( true );
} catch ( NoSuchMethodException | SecurityException e1 ) {
e1.printStackTrace();
}
}
try {
JAVAPLUGINLOADER_REMOVECLASS_1 = JavaPluginLoader.class.getDeclaredMethod( "removeClass", String.class );
JAVAPLUGINLOADER_REMOVECLASS_1.setAccessible( true );
} catch ( NoSuchMethodException | SecurityException e ) {
try {
JAVAPLUGINLOADER_REMOVECLASS_2 = JavaPluginLoader.class.getDeclaredMethod( "removeClass", Class.class );
JAVAPLUGINLOADER_REMOVECLASS_2.setAccessible( true );
} catch ( NoSuchMethodException | SecurityException e1 ) {
e1.printStackTrace();
}
}
}
public static PluginCommand constructCommand( String id ) {
PluginCommand command = null;
try {
command = PLUGINCOMMAND_CONSTRUCTOR.newInstance( id, JavaPlugin.getPlugin( MiniePlugin.class ) );
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
return null;
}
return command;
}
/**
* Call an event synchronously, on the main thread
*/
public static void callEventSync( Event event ) {
if ( Bukkit.getServer().isPrimaryThread() ) {
Bukkit.getPluginManager().callEvent( event );
} else {
Bukkit.getScheduler().scheduleSyncDelayedTask( JavaPlugin.getPlugin( MiniePlugin.class ), () -> { callEventSync( event ); } );
}
}
/**
* Check if the plugin given is loaded
*
* @param id
* case sensitive plugin id
* @return
* If the plugin is loaded and enabled
*/
public static boolean isPluginLoaded( String id ) {
return Bukkit.getPluginManager().isPluginEnabled( id );
}
public static Class< ? > getClassByName( String name ) {
PluginManager manager = Bukkit.getPluginManager();
if ( manager instanceof SimplePluginManager ) {
SimplePluginManager sManager = ( SimplePluginManager ) manager;
try {
Map< Pattern, PluginLoader > associations = ( Map< Pattern, PluginLoader > ) SIMPLEPLUGINMANAGER_FILEASSOCIATIONS.get( sManager );
for ( PluginLoader loader : associations.values() ) {
if ( loader instanceof JavaPluginLoader ) {
if ( JAVAPLUGINLOADER_GETCLASS_1 != null ) {
return ( Class< ? > ) JAVAPLUGINLOADER_GETCLASS_1.invoke( loader, name );
} else {
return ( Class< ? > ) JAVAPLUGINLOADER_GETCLASS_2.invoke( loader, name, true, JavaPlugin.getPlugin( MiniePlugin.class ).getDescription() );
}
}
}
} catch ( IllegalArgumentException | IllegalAccessException | InvocationTargetException e ) {
e.printStackTrace();
}
}
return null;
}
public static void setClassToJavaPluginLoader( String name, Class< ? > clazz ) {
PluginManager manager = Bukkit.getPluginManager();
if ( manager instanceof SimplePluginManager ) {
SimplePluginManager sManager = ( SimplePluginManager ) manager;
try {
Map< Pattern, PluginLoader > associations = ( Map< Pattern, PluginLoader > ) SIMPLEPLUGINMANAGER_FILEASSOCIATIONS.get( sManager );
for ( PluginLoader loader : associations.values() ) {
if ( loader instanceof JavaPluginLoader ) {
JAVAPLUGINLOADER_SETCLASS.invoke( loader, name, clazz );
}
}
} catch ( IllegalArgumentException | IllegalAccessException | InvocationTargetException e ) {
e.printStackTrace();
}
}
}
public static boolean removeClassFromJavaPluginLoader( Class clazz ) {
if ( JAVAPLUGINLOADER_REMOVECLASS_2 != null ) {
PluginManager manager = Bukkit.getPluginManager();
if ( manager instanceof SimplePluginManager ) {
SimplePluginManager sManager = ( SimplePluginManager ) manager;
try {
Map< Pattern, PluginLoader > associations = ( Map< Pattern, PluginLoader > ) SIMPLEPLUGINMANAGER_FILEASSOCIATIONS.get( sManager );
for ( PluginLoader loader : associations.values() ) {
if ( loader instanceof JavaPluginLoader ) {
JAVAPLUGINLOADER_REMOVECLASS_2.invoke( loader, clazz );
}
}
} catch ( IllegalArgumentException | IllegalAccessException | InvocationTargetException e ) {
e.printStackTrace();
}
}
return true;
}
return false;
}
public static boolean removeClassFromJavaPluginLoader( String name ) {
if ( JAVAPLUGINLOADER_REMOVECLASS_1 != null ) {
PluginManager manager = Bukkit.getPluginManager();
if ( manager instanceof SimplePluginManager ) {
SimplePluginManager sManager = ( SimplePluginManager ) manager;
try {
Map< Pattern, PluginLoader > associations = ( Map< Pattern, PluginLoader > ) SIMPLEPLUGINMANAGER_FILEASSOCIATIONS.get( sManager );
for ( PluginLoader loader : associations.values() ) {
if ( loader instanceof JavaPluginLoader ) {
JAVAPLUGINLOADER_REMOVECLASS_1.invoke( loader, name );
}
}
} catch ( IllegalArgumentException | IllegalAccessException | InvocationTargetException e ) {
e.printStackTrace();
}
}
return true;
}
return false;
}
}

View File

@@ -0,0 +1,41 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.util;
import java.nio.ByteBuffer;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkMesh;
public class ChunkMeshUtil {
public static byte[] serialize( final ChunkMesh mesh ) {
final BinaryWriter writer = new BinaryWriter();
// Header
writer.write( "cmesh1", 16 );
// How many chunk meshes are we providing
writer.write( 1, 4 );
mesh.writeTo( writer );
return writer.toByteArray();
}
public static ChunkMesh deserialize( final byte[] data ) {
final ByteBuffer reader = ByteBuffer.wrap( data );
final byte[] headerArr = new byte[ 16 ];
reader.get( headerArr );
final String header = new String( headerArr ).trim();
if ( header.equals( "cmesh1" ) ) {
// This is how many chunk meshes this data contains
final int amount = reader.getInt();
if ( amount > 1 ) {
// Some data is getting discarded...
}
return ChunkMesh.readFrom( reader );
}
throw new IllegalStateException( "Cannot read invalid header: '" + header + "'" );
}
}

View File

@@ -0,0 +1,150 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
/**
* Simple file management including YAML updaters
*
* @author BananaPuncher714
*/
public final class FileUtil {
public static void saveAndUpdate( InputStream stream, File output, boolean trim ) {
if ( !output.exists() ) {
saveToFile( stream, output, false );
}
updateConfigFromFile( output, stream, trim );
}
public static void saveToFile( InputStream stream, File output, boolean force ) {
if ( force && output.exists() ) {
output.delete();
}
if ( !output.exists() ) {
output.getParentFile().mkdirs();
try ( OutputStream outStream = new FileOutputStream( output ) ) {
byte[] buffer = new byte[ stream.available() ];
int len;
while ( ( len = stream.read( buffer ) ) > 0) {
outStream.write( buffer, 0, len );
}
stream.close();
} catch ( FileNotFoundException e ) {
e.printStackTrace();
} catch ( IOException e ) {
e.printStackTrace();
}
}
}
public static void updateConfigFromFile( File toUpdate, InputStream toCopy ) {
updateConfigFromFile( toUpdate, toCopy, false );
}
public static void updateConfigFromFile( File toUpdate, InputStream toCopy, boolean trim ) {
FileConfiguration config = YamlConfiguration.loadConfiguration( new InputStreamReader( toCopy ) );
FileConfiguration old = YamlConfiguration.loadConfiguration( toUpdate );
for ( String key : config.getKeys( true ) ) {
if ( !old.contains( key ) ) {
old.set( key, config.get( key ) );
}
}
if ( trim ) {
for ( String key : old.getKeys( true ) ) {
if ( !config.contains( key ) ) {
old.set( key, null );
}
}
}
try {
old.save( toUpdate );
} catch ( Exception exception ) {
exception.printStackTrace();
}
}
public static boolean move( File original, File dest, boolean force ) {
if ( dest.exists() ) {
if ( !force ) {
return false;
} else {
recursiveDelete( dest );
}
}
dest.getParentFile().mkdirs();
original.renameTo( dest );
recursiveDelete( original );
return true;
}
public static void recursiveDelete( File file ) {
if ( !file.exists() ) {
return;
}
if ( file.isDirectory() ) {
for ( File lower : file.listFiles() ) {
recursiveDelete( lower );
}
}
file.delete();
}
public static < T extends Serializable > T readObject( File file ) throws IOException, ClassNotFoundException {
if ( !file.exists() ) {
return null;
}
T head = null;
FileInputStream fis = new FileInputStream( file );
ObjectInputStream ois = new ObjectInputStream( fis );
head = ( T ) ois.readObject();
ois.close();
fis.close();
return head;
}
public static void writeObject( Serializable object, File file ) {
file.getParentFile().mkdirs();
try {
FileOutputStream fos = new FileOutputStream( file );
ObjectOutputStream oos = new ObjectOutputStream( fos );
oos.writeObject( object );
oos.close();
fos.close();
} catch ( Exception exception ) {
exception.printStackTrace();
}
}
public static File getImageFile( File dir, String prefix ) {
File image = new File( dir + "/" + prefix + ".png" );
for ( File file : dir.listFiles() ) {
String fileName = file.getName();
if ( fileName.equalsIgnoreCase( prefix + ".gif" ) ) {
return file;
} else if ( fileName.equalsIgnoreCase( prefix + ".png" ) || fileName.equalsIgnoreCase( prefix + ".jpg" ) || fileName.equalsIgnoreCase( prefix + ".jpeg" ) || fileName.equalsIgnoreCase( prefix + ".bmp" ) ) {
image = file;
}
}
return image;
}
}

View File

@@ -0,0 +1,67 @@
# Tess4j(name subject to change)
## About
Tess4j(name subject to change) is an all purpose 2d polygon tessellator inspired by the GLUTesselator and libtess2. This tessellator focuses around
user defined rules for interior/exterior regions, and aims to be a robust algorithm with a focus on reducing triangle
count while preserving the original shapes.
Since it _is_ written in java, speed is not the main priority. Rather, this aims to serve as an easy to understand
and easy to modify library for anyone interested in adding improvements.
## Restrictions
Tess4j(name subject to change) accepts one or more polygons. Polygons are defined as a collection of one or more 2d points
that form a loop. Steiner points are not (currently)supported. There may be some rounding errors for especially for very large
and small points, but this library aims for consistency, with a small tolerance for adjustments.
## Algorithm
This library utilizes a very basic single sweep monotone partitioning algorithm, then greedily creates triangle fans and strips.
Additional monotone regions may be broken down into smaller regions if doing so would decrease the overall amount of triangles.
### Invariants
Tess4j(name subject to change) assumes no responsibility or liability for any errors or omissions in the execution of this program.
The output returned by this program is provided on an "as is" basis with no guarantees of completeness, accuracy, usefulness or timeliness.
That said, here are a list of invariants that _should_ be upheld:
- Any vertex created during the tessellation will be greater than the current event vertex
### The actual algorithm
Tess4j(name subject to change) begins by accepting a list of one or more polygons. The polygons are split into collections of connected
half-edges and vertices. The vertices are then added to a priority queue where they are sorted from left to right(-x to x),
bottom to top(-y to y) order.
Once all polygons have been inserted and processed, sweeping of the vertices begins. The lowest unique vertex is extracted from
the priority queue. Any subsequent vertices with the same coordinates are merged together, and any new vertices created during the
sweep event are guaranteed to be greater than the current vertex.
Sweep over each vertex and mark which ones are an interior non-monotone vertex. Process all left going edges, and mark the faces attached
as interior/exterior. Register all right going edges. Split any intersections into new vertices and edges, and add the vertices to the
event queue.
**TODO**
Now that each face has been fully completed... reduce the amount of vertices before adding in additional edges for monotone regions.
To do this, get all colinear edges that share the same face, and does not intersect with any other edges???
Loop through each edge and keep it in the list until it would intersect with another edge, and mark which edges/vertices it is
colinear with, as well as any potential new edges it might collide with. Solve which combination of edges would reduce
vertex count by the greatest amount. 2 vertices if connecting to an edge, 1 vertex if connecting to a vertex. Traveling salesman problem?
When checking for intersections with other support edges, only need to check all active potential aux edges... Consolidate colinears
For each edge of a face, advance around the face until it intersects with an edge or vertex. Advance backwards as well, checking ONLY vertex intersections.
This will capture all possible conlinear reductions as new edges.
How to determine which combination can reduce the total amount of vertices? Build clusters of intersecting vertices,
After all vertex reduction edges have been added, get all faces and calculate the upper and lower sweep edge of
each vertex of each face for monotone partitioning.
When processing the marked vertices to form monotone regions, some data must be stored for each point so
that a valid point with which to connect can be located afterwards:
- The immediate upper and lower edge of the vertex when it is being swept.
This allows the forward/backward monotone connecting to consistently find a valid point. When checking for valid points:
- The point must be less/greater than the current point if iterating forwards/backwards, respectively.
- The point must fulfill at least one of the following conditions:
- It shares the same upper edge
- It is part of the upper edge
- It shares the same lower edge
- It is part of the lower edge
Once a marked vertex has been connected, either by finding another valid vertex, or by being connected to from a marked
vertex, it can be unmarked and removed from the queue of marked vertices.
Now that each face is a monotone region, split it into triangle fans and strips.

View File

@@ -0,0 +1,14 @@
name: MinieTest
main: com.aaaaahhhhhhh.bananapuncher714.minietest.MiniePlugin
version: 0.0.1
description: Minecraft with Minie
author: BananaPuncher714
api-version: 1.13
#libraries:
#- com.github.stephengold:Minie:7.5.0
commands:
minie:
description: Main Minie command
aliases: []
permission: minie