Initial commit for historical purposes
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
target
|
||||||
|
.settings
|
||||||
|
chunks
|
||||||
|
new_chunks
|
||||||
73
pom.xml
Normal file
73
pom.xml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<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>
|
||||||
|
<groupId>com.aaaaahhhhhhh.bananapuncher714</groupId>
|
||||||
|
<artifactId>MinieTest</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.spigotmc</groupId>
|
||||||
|
<artifactId>spigot</artifactId>
|
||||||
|
<version>1.19.4-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>io.github.earcut4j</groupId>
|
||||||
|
<artifactId>earcut4j</artifactId>
|
||||||
|
<version>2.2.2</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>2.3.2</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>
|
||||||
|
com.aaaaahhhhhhh.bananapuncher714.minietest.MinieTest
|
||||||
|
</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.minietest;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.minietest.MeshTest.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;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.Point;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.Polygon;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.Tess4j;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.Vector2d;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.Vector2dComparator;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple.GluWindingRule;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.WindingRuleSimple;
|
||||||
|
|
||||||
|
public class MeshingTest extends JPanel {
|
||||||
|
private static final File BASE = new File( System.getProperty( "user.dir" ) );
|
||||||
|
private static final File CHUNK_DIR = new File( BASE, "chunks" );
|
||||||
|
|
||||||
|
JFrame f;
|
||||||
|
|
||||||
|
private int windowWidth = 1200;
|
||||||
|
private int windowHeight = 1000;
|
||||||
|
|
||||||
|
private int centerX = 400; // 600
|
||||||
|
private int centerY = 1000; // 400
|
||||||
|
|
||||||
|
private Graphics g;
|
||||||
|
|
||||||
|
private int scale = 10;
|
||||||
|
|
||||||
|
private List< Polygon > data;
|
||||||
|
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) {
|
||||||
|
System.out.println( "Found " + CHUNK_DIR.list().length + " files" );
|
||||||
|
for ( File file : CHUNK_DIR.listFiles() ) {
|
||||||
|
List< Polygon > polys = mesh( file );
|
||||||
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
new MeshingTest( polys );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.err.println( "No such directory exists: " + CHUNK_DIR );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List< Polygon > mesh( File file ) {
|
||||||
|
String name = file.getName();
|
||||||
|
String[] split = name.split( "," );
|
||||||
|
ChunkLocation location = new ChunkLocation( split[ 0 ], Integer.parseInt( split[ 1 ] ), Integer.parseInt( split[ 2 ] ) );
|
||||||
|
|
||||||
|
System.out.println( "Parsing chunk " + location );
|
||||||
|
|
||||||
|
// First parse the file to get a list of all bounding boxes that we can use
|
||||||
|
List< AABB > boxes = new ArrayList< AABB >();
|
||||||
|
try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) {
|
||||||
|
String line;
|
||||||
|
while ( ( line = reader.readLine() ) != null ) {
|
||||||
|
if ( !line.isEmpty() ) {
|
||||||
|
String[] values = line.split( "," );
|
||||||
|
double minX = Double.parseDouble( values[ 0 ] );
|
||||||
|
double minY = Double.parseDouble( values[ 1 ] );
|
||||||
|
double minZ = Double.parseDouble( values[ 2 ] );
|
||||||
|
double maxX = Double.parseDouble( values[ 3 ] );
|
||||||
|
double maxY = Double.parseDouble( values[ 4 ] );
|
||||||
|
double maxZ = Double.parseDouble( values[ 5 ] );
|
||||||
|
boxes.add( new AABB( minX, minY, minZ, maxX, maxY, maxZ ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch ( FileNotFoundException e ) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} catch ( IOException e ) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println( "Found " + boxes.size() + " boxes" );
|
||||||
|
// Now that we have a bunch of bounding boxes, do whatever
|
||||||
|
|
||||||
|
MeshBuilder builder = new MeshBuilder();
|
||||||
|
Plane draw = null;
|
||||||
|
for ( AABB box : boxes ) {
|
||||||
|
for ( Facet facet : generateFacetsFor( box ) ) {
|
||||||
|
builder.addFacet( facet );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println( "Planes: " + builder.planes.size() );
|
||||||
|
for ( Plane plane : builder.planes ) {
|
||||||
|
if ( plane.polygons.size() > 100 ) {
|
||||||
|
draw = plane;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( Plane plane : builder.planes ) {
|
||||||
|
try {
|
||||||
|
Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
|
||||||
|
for ( Polygon poly : plane.polygons ) {
|
||||||
|
tess4j.addPolygon( poly, new WindingRuleSimple() );
|
||||||
|
}
|
||||||
|
tess4j.tessellate();
|
||||||
|
} catch ( IllegalStateException e ) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
draw = plane;
|
||||||
|
// Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
|
||||||
|
// tess4j.setDebug( true );
|
||||||
|
// System.out.println( "Polygon count: " + plane.polygons.size() );
|
||||||
|
// for ( Polygon poly : plane.polygons ) {
|
||||||
|
// tess4j.addPolygon( poly, new WindingRuleSimple() );
|
||||||
|
// }
|
||||||
|
// tess4j.tessellate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( draw != null ) {
|
||||||
|
System.out.println( "Norm:\t" + draw.normal );
|
||||||
|
System.out.println( "Ref:\t" + draw.point );
|
||||||
|
System.out.println( "Size:\t" + draw.polygons.size() );
|
||||||
|
|
||||||
|
Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
|
||||||
|
tess4j.setDebug( true );
|
||||||
|
for ( int i = 0; i < draw.polygons.size(); i++ ) {
|
||||||
|
if ( i >= 4 ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Polygon poly = draw.polygons.get( i );
|
||||||
|
|
||||||
|
System.out.println( "Adding polygon " + poly.getPoints().size() );
|
||||||
|
for ( Point point : poly.getPoints() ) {
|
||||||
|
System.out.println( point.getX() + ", " + point.getY() );
|
||||||
|
}
|
||||||
|
|
||||||
|
tess4j.addPolygon( poly, new WindingRuleSimple() );
|
||||||
|
}
|
||||||
|
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
tess4j.tessellate();
|
||||||
|
long end = System.currentTimeMillis();
|
||||||
|
System.out.println( "Took " + ( end - start ) + "ms" );
|
||||||
|
|
||||||
|
return tess4j.getPolygons();
|
||||||
|
} else {
|
||||||
|
System.out.println( "No data!" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList< Polygon >();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List< Facet > generateFacetsFor( AABB box ) {
|
||||||
|
List< Facet > facets = new ArrayList< Facet >();
|
||||||
|
|
||||||
|
Vector p1 = new Vector( box.xmin, box.ymin, box.zmin );
|
||||||
|
Vector p2 = new Vector( box.xmin, box.ymin, box.zmax );
|
||||||
|
Vector p3 = new Vector( box.xmin, box.ymax, box.zmin );
|
||||||
|
Vector p4 = new Vector( box.xmin, box.ymax, box.zmax );
|
||||||
|
Vector p5 = new Vector( box.xmax, box.ymin, box.zmin );
|
||||||
|
Vector p6 = new Vector( box.xmax, box.ymin, box.zmax );
|
||||||
|
Vector p7 = new Vector( box.xmax, box.ymax, box.zmin );
|
||||||
|
Vector p8 = new Vector( box.xmax, box.ymax, box.zmax );
|
||||||
|
|
||||||
|
{
|
||||||
|
Facet facet = new Facet();
|
||||||
|
facet.points.add( p1 );
|
||||||
|
facet.points.add( p2 );
|
||||||
|
facet.points.add( p4 );
|
||||||
|
facet.points.add( p3 );
|
||||||
|
facet.normal = new Vector( -1, 0, 0 );
|
||||||
|
facets.add( facet );
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Facet facet = new Facet();
|
||||||
|
facet.points.add( p5 );
|
||||||
|
facet.points.add( p6 );
|
||||||
|
facet.points.add( p8 );
|
||||||
|
facet.points.add( p7 );
|
||||||
|
facet.normal = new Vector( 1, 0, 0 );
|
||||||
|
facets.add( facet );
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Facet facet = new Facet();
|
||||||
|
facet.points.add( p1 );
|
||||||
|
facet.points.add( p2 );
|
||||||
|
facet.points.add( p6 );
|
||||||
|
facet.points.add( p5 );
|
||||||
|
facet.normal = new Vector( 0, -1, 0 );
|
||||||
|
facets.add( facet );
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Facet facet = new Facet();
|
||||||
|
facet.points.add( p3 );
|
||||||
|
facet.points.add( p4 );
|
||||||
|
facet.points.add( p7 );
|
||||||
|
facet.points.add( p8 );
|
||||||
|
facet.normal = new Vector( 0, 1, 0 );
|
||||||
|
facets.add( facet );
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Facet facet = new Facet();
|
||||||
|
facet.points.add( p1 );
|
||||||
|
facet.points.add( p3 );
|
||||||
|
facet.points.add( p7 );
|
||||||
|
facet.points.add( p5 );
|
||||||
|
facet.normal = new Vector( 0, 0, -1 );
|
||||||
|
facets.add( facet );
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Facet facet = new Facet();
|
||||||
|
facet.points.add( p2 );
|
||||||
|
facet.points.add( p4 );
|
||||||
|
facet.points.add( p8 );
|
||||||
|
facet.points.add( p6 );
|
||||||
|
facet.normal = new Vector( 0, 0, 1 );
|
||||||
|
facets.add( facet );
|
||||||
|
}
|
||||||
|
|
||||||
|
return facets;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AABB {
|
||||||
|
double xmin, ymin, zmin;
|
||||||
|
double xmax, ymax, zmax;
|
||||||
|
|
||||||
|
AABB( double xmin, double ymin, double zmin, double xmax, double ymax, double zmax ) {
|
||||||
|
this.xmin = xmin;
|
||||||
|
this.ymin = ymin;
|
||||||
|
this.zmin = zmin;
|
||||||
|
|
||||||
|
this.xmax = xmax;
|
||||||
|
this.ymax = ymax;
|
||||||
|
this.zmax = zmax;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getVolume() {
|
||||||
|
return ( xmax - xmin ) * ( ymax - ymin ) * ( zmax - zmin );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format( "(%f, %f, %f) -> (%f, %f, %f)", xmin, ymin, zmin, xmax, ymax, zmax );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MeshingTest( List< Polygon > polys) {
|
||||||
|
data = polys;
|
||||||
|
|
||||||
|
f = new JFrame( "Drawing Board" );
|
||||||
|
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
|
||||||
|
|
||||||
|
f.add( this );
|
||||||
|
|
||||||
|
f.setSize( windowWidth, windowHeight );
|
||||||
|
f.setVisible( true );
|
||||||
|
f.setResizable( true );
|
||||||
|
|
||||||
|
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawPoint( double x, double y ) {
|
||||||
|
g.fillRect( ( int ) x * scale, ( int ) y * scale, scale, scale );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector2d rotate( Vector2d a, double angle ) {
|
||||||
|
double cos = Math.cos( angle );
|
||||||
|
double sin = Math.sin( angle );
|
||||||
|
double ax = a.getX() * cos - a.getY() * sin;
|
||||||
|
double ay = a.getX() * sin + a.getY() * cos;
|
||||||
|
return new Vector2d( ax, ay );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paintComponent( Graphics g ) {
|
||||||
|
super.paintComponent( g );
|
||||||
|
this.g = g;
|
||||||
|
|
||||||
|
// Vertex v = null;
|
||||||
|
// for ( int i = 0; i < 100; i++ ) {
|
||||||
|
// double deg = Math.random() * 360;
|
||||||
|
// double dist = Math.random() * 100 + 100;
|
||||||
|
// double x = dist * Math.cos( Math.toRadians( deg ) );
|
||||||
|
// double y = dist * Math.sin( Math.toRadians( deg ) );
|
||||||
|
// HalfWing edge = new HalfWing();
|
||||||
|
// edge.getDest().setPosition( new Vector2d( x, y ) );
|
||||||
|
// if ( v == null ) {
|
||||||
|
// v = edge.getOrigin();
|
||||||
|
// } else {
|
||||||
|
// HalfWing.splice( edge, v.getWing() );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// v.update();
|
||||||
|
//
|
||||||
|
// HalfWing edge = v.getWing();
|
||||||
|
// int i = 0;
|
||||||
|
// double ic = 0;
|
||||||
|
// do {
|
||||||
|
// Vector2d dest = edge.getDest().getPosition();
|
||||||
|
// g.drawLine( centerX, centerY, ( int ) dest.getX() + centerX, ( int ) dest.getY() + centerY );
|
||||||
|
// dest = dest.normalized();
|
||||||
|
// g.drawString( i++ + "", ( int ) ( dest.x * 200 * ( ic + 1.1 ) ) + centerX, ( int ) ( dest.y * 200 * ( ic + 1.1 ) ) + centerY );
|
||||||
|
// ic = ( ic + 0.07 ) % .7;
|
||||||
|
// edge = edge.getPrev();
|
||||||
|
// } while ( edge != v.getWing() );
|
||||||
|
//
|
||||||
|
// v.sort();
|
||||||
|
//
|
||||||
|
// g.drawLine( centerX + 700, centerY, centerX + 700, centerY - 400 );
|
||||||
|
// edge = v.getWing();
|
||||||
|
// i = 0;
|
||||||
|
// ic = 0;
|
||||||
|
// do {
|
||||||
|
// Vector2d dest = edge.getDest().getPosition();
|
||||||
|
// g.drawLine( centerX + 700, centerY, ( int ) dest.getX() + centerX + 700, ( int ) dest.y + centerY );
|
||||||
|
// dest = dest.normalized();
|
||||||
|
// g.drawString( i++ + "", ( int ) ( dest.x * 200 * ( ic + 1.1 ) ) + centerX + 700, ( int ) ( dest.y * 200 * ( ic + 1.1 ) ) + centerY );
|
||||||
|
// ic = ( ic + 0.07 ) % .7;
|
||||||
|
// edge = edge.getPrev();
|
||||||
|
// } while ( edge != v.getWing() );
|
||||||
|
|
||||||
|
List< Polygon > polygons = data;
|
||||||
|
|
||||||
|
System.out.println( "Polygon count: " + polygons.size() );
|
||||||
|
System.out.println( polygons.size() );
|
||||||
|
for ( Polygon p : polygons ) {
|
||||||
|
List< Point > points = p.getPoints();
|
||||||
|
// System.out.println( points.size() );
|
||||||
|
int size = points.size();
|
||||||
|
int[] xPoints = new int[ size ];
|
||||||
|
int[] yPoints = new int[ size ];
|
||||||
|
|
||||||
|
for ( int i = 0; i < points.size(); i++ ) {
|
||||||
|
Point point = points.get( i );
|
||||||
|
|
||||||
|
// System.out.println( point.getX() + ", " + point.getY() );
|
||||||
|
Vector2d pVec = new Vector2d( point.getX(), point.getY() );
|
||||||
|
pVec = rotate( pVec, 0 );
|
||||||
|
point = new Point( pVec.getX(), pVec.getY() );
|
||||||
|
|
||||||
|
xPoints[ i ] = ( int ) ( point.getX() * scale ) + centerX;
|
||||||
|
yPoints[ i ] = 100 - ( int ) ( point.getY() * scale ) + centerY;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setColor( new Color( ( int ) ( Math.random() * 0xFFFFFF ) ) );
|
||||||
|
g.fillPolygon( xPoints, yPoints, size );
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setColor( Color.BLACK );
|
||||||
|
for ( Polygon p : polygons ) {
|
||||||
|
for ( Point point : p.getPoints() ) {
|
||||||
|
Vector2d pVec = new Vector2d( point.getX(), point.getY() );
|
||||||
|
pVec = rotate( pVec, 0 );
|
||||||
|
point = new Point( pVec.getX(), pVec.getY() );
|
||||||
|
|
||||||
|
double diff = scale * .05;
|
||||||
|
g.drawRect( ( int ) ( point.getX() * scale ) + centerX - ( int ) diff, 100 - ( int ) ( point.getY() * scale ) + centerY - ( int ) diff, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.drawLine( centerX, 0, centerX, 2000 );
|
||||||
|
g.drawLine( 0, centerY + 100, 2000, centerY + 100 );
|
||||||
|
|
||||||
|
// Point[] points = {
|
||||||
|
// new Point( 0, -2 ),
|
||||||
|
// new Point( 1.5, -3.0 ),
|
||||||
|
// new Point( 0.0, -2.0 ),
|
||||||
|
// new Point( 0.9545454545454546, -1.3636363636363638 ),
|
||||||
|
// new Point( -0.8333333333333334, -1.0 ),
|
||||||
|
// new Point( 0.8333333333333334, -1.0 ),
|
||||||
|
// new Point( -0.5, 0.0 ),
|
||||||
|
// new Point( 0.5, 0.0 ),
|
||||||
|
// new Point( -0.16666666666666669, 1.0 ),
|
||||||
|
// new Point( 0.16666666666666669, 1.0 ),
|
||||||
|
// new Point( -0.16666666666666669, 1.0 ),
|
||||||
|
// new Point( 1, 1 ),
|
||||||
|
// new Point( -0.16666666666666669, 1.0 ),
|
||||||
|
// new Point( 0, 1.5 ),
|
||||||
|
// new Point( 0, 1.5 ),
|
||||||
|
// new Point( 0.16666666666666669, 1.0 ),
|
||||||
|
// new Point( -1, 2 ),
|
||||||
|
// new Point( 1, 2 ),
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// int size = points.length >> 1;
|
||||||
|
// for ( int i = 0; i < size;i++ ) {
|
||||||
|
// Point a = points[ i << 1 ];
|
||||||
|
// Point b = points[ ( i << 1 ) + 1 ];
|
||||||
|
//
|
||||||
|
// g.drawLine( ( int ) ( a.x * scale ) + centerX, ( int ) ( a.y * scale ) + centerY, ( int ) ( b.x * scale ) + centerX, ( int ) ( b.y * scale ) + centerY );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,768 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.minietest;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.SystemUtils;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R4.CraftWorld;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R4.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.entity.BlockDisplay;
|
||||||
|
import org.bukkit.entity.Display;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.world.ChunkLoadEvent;
|
||||||
|
import org.bukkit.event.world.ChunkUnloadEvent;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.bukkit.util.BoundingBox;
|
||||||
|
import org.bukkit.util.Transformation;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.SubCommand;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.executor.CommandExecutableMessage;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.InputValidatorInt;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.sender.SenderValidatorPlayer;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkLocation;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.minietest.util.FileUtil;
|
||||||
|
import com.jme3.bullet.PhysicsSpace;
|
||||||
|
import com.jme3.bullet.PhysicsSpace.BroadphaseType;
|
||||||
|
import com.jme3.bullet.RotationOrder;
|
||||||
|
import com.jme3.bullet.collision.ContactListener;
|
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionEvent;
|
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionListener;
|
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionObject;
|
||||||
|
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.PlaneCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
|
||||||
|
import com.jme3.bullet.joints.New6Dof;
|
||||||
|
import com.jme3.bullet.joints.PhysicsJoint;
|
||||||
|
import com.jme3.bullet.joints.motors.MotorParam;
|
||||||
|
import com.jme3.bullet.objects.PhysicsBody;
|
||||||
|
import com.jme3.bullet.objects.PhysicsRigidBody;
|
||||||
|
import com.jme3.math.Matrix3f;
|
||||||
|
import com.jme3.math.Plane;
|
||||||
|
import com.jme3.math.Quaternion;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPosition;
|
||||||
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
|
|
||||||
|
public class MiniePlugin extends JavaPlugin {
|
||||||
|
private static final int TIME_STEPS = 5;
|
||||||
|
private static final float SIMULATION_SPEED = 1f;
|
||||||
|
|
||||||
|
private PhysicsSpace space;
|
||||||
|
private boolean paused = false;
|
||||||
|
private boolean teleportOnDeactivate = false;
|
||||||
|
private float tick = 0;
|
||||||
|
private Map< PhysicsCollisionObject, AuxiliaryDisplayStruct > linkedDisplays = new HashMap< PhysicsCollisionObject, AuxiliaryDisplayStruct >();
|
||||||
|
|
||||||
|
private Set< PhysicsCollisionObject > active = new HashSet< PhysicsCollisionObject >();
|
||||||
|
private Set< PhysicsCollisionObject > isTnt = new HashSet< PhysicsCollisionObject >();
|
||||||
|
|
||||||
|
private Map< PhysicsJoint, JointStruct > joints = new HashMap< PhysicsJoint, JointStruct >();
|
||||||
|
|
||||||
|
private Map< ChunkLocation, PhysicsRigidBody > chunks = new HashMap< ChunkLocation, PhysicsRigidBody >();
|
||||||
|
|
||||||
|
private Map< Vector3f, CollisionShape > collisionShapes = new HashMap< Vector3f, CollisionShape >();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
FileUtil.saveToFile( getResource( "native/windows/x86_64/bulletjme.dll" ), new File( getDataFolder() + "/lib", "bulletjme.dll" ), false );
|
||||||
|
FileUtil.saveToFile( getResource( "native/osx/x86_64/libbulletjme.dylib" ), new File( getDataFolder() + "/lib", "libbulletjme.dylib" ), false );
|
||||||
|
FileUtil.saveToFile( getResource( "native/linux/x86_64/libbulletjme.so" ), new File( getDataFolder() + "/lib", "libbulletjme.so" ), false );
|
||||||
|
if ( !loadNativeLibraries() ) {
|
||||||
|
getLogger().severe( "Unable to load Bullet JME native libraries! Disabling plugin" );
|
||||||
|
Bukkit.getPluginManager().disablePlugin( this );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCommands();
|
||||||
|
|
||||||
|
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, 128, 0 ) );
|
||||||
|
space.addCollisionObject( planeBody );
|
||||||
|
space.setAccuracy( 1f / ( TIME_STEPS * 20f ) );
|
||||||
|
|
||||||
|
space.addContactListener( new ContactListener() {
|
||||||
|
@Override
|
||||||
|
public void onContactEnded( long manifoldId ) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContactProcessed( PhysicsCollisionObject objA, PhysicsCollisionObject objB, long manifoldPointId ) {
|
||||||
|
if ( isTnt.remove( objA ) ) {
|
||||||
|
impulse( objA.getPhysicsLocation(), 30f );
|
||||||
|
if ( linkedDisplays.containsKey( objA ) ) {
|
||||||
|
linkedDisplays.get( objA ).display.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isTnt.remove( objB ) ) {
|
||||||
|
impulse( objB.getPhysicsLocation(), 30f );
|
||||||
|
if ( linkedDisplays.containsKey( objB ) ) {
|
||||||
|
linkedDisplays.get( objB ).display.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContactStarted( long manifoldId ) {
|
||||||
|
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
Bukkit.getPluginManager().registerEvents( new Listener() {
|
||||||
|
@EventHandler
|
||||||
|
private void onChunkLoad( ChunkLoadEvent event ) {
|
||||||
|
if ( event.getWorld().getName().equalsIgnoreCase( "world" ) ) {
|
||||||
|
getLogger().info( "Loading chunk..." );
|
||||||
|
// scanAndGenerate( event.getChunk() );
|
||||||
|
saveChunk( event.getChunk() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
private void onChunkUnload( ChunkUnloadEvent event ) {
|
||||||
|
if ( event.getWorld().getName().equalsIgnoreCase( "world" ) ) {
|
||||||
|
ChunkLocation loc = new ChunkLocation( event.getChunk() );
|
||||||
|
if ( chunks.containsKey( loc ) ) {
|
||||||
|
space.removeCollisionObject( chunks.get( loc ) );
|
||||||
|
chunks.remove( loc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this );
|
||||||
|
|
||||||
|
|
||||||
|
getLogger().info( "There are " + Bukkit.getWorld( "world" ).getLoadedChunks().length + " chunks" );
|
||||||
|
getLogger().info( "Converted " + saveAllChunks( Bukkit.getWorld( "world" ) ) );
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskTimer( this, () -> {
|
||||||
|
if ( !paused ) {
|
||||||
|
// Update the real world with the physics space objects
|
||||||
|
space.distributeEvents();
|
||||||
|
space.update( tick, TIME_STEPS );
|
||||||
|
|
||||||
|
for ( Iterator< Entry< PhysicsJoint, JointStruct > > it = joints.entrySet().iterator(); it.hasNext(); ) {
|
||||||
|
Entry< PhysicsJoint, JointStruct > entry = it.next();
|
||||||
|
PhysicsJoint joint = entry.getKey();
|
||||||
|
JointStruct struct = entry.getValue();
|
||||||
|
BlockState state = struct.state;
|
||||||
|
|
||||||
|
if ( state.getBlock().getType() != state.getType() || !state.getChunk().isLoaded() ) {
|
||||||
|
space.removeJoint( joint );
|
||||||
|
space.remove( struct.object );
|
||||||
|
it.remove();
|
||||||
|
} else if ( !joint.isEnabled() ) {
|
||||||
|
Location loc = state.getLocation();
|
||||||
|
BlockData displayData = state.getBlockData();
|
||||||
|
|
||||||
|
// Create the display
|
||||||
|
BlockDisplay display = loc.getWorld().spawn( loc, BlockDisplay.class, d -> {
|
||||||
|
d.setBlock( displayData );
|
||||||
|
} );
|
||||||
|
|
||||||
|
linkedDisplays.put( struct.object, new AuxiliaryDisplayStruct( display, struct.offset ) );
|
||||||
|
|
||||||
|
state.getBlock().setType( Material.AIR );
|
||||||
|
|
||||||
|
struct.object.setProtectGravity( false );
|
||||||
|
Vector3f grav = new Vector3f();
|
||||||
|
space.setGravity( grav );
|
||||||
|
struct.object.setGravity( grav );
|
||||||
|
|
||||||
|
space.removeJoint( joint );
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( Iterator< Entry< PhysicsCollisionObject, AuxiliaryDisplayStruct > > it = linkedDisplays.entrySet().iterator(); it.hasNext(); ) {
|
||||||
|
Entry< PhysicsCollisionObject, AuxiliaryDisplayStruct > entry = it.next();
|
||||||
|
PhysicsCollisionObject obj = entry.getKey();
|
||||||
|
AuxiliaryDisplayStruct disp = entry.getValue();
|
||||||
|
|
||||||
|
if ( !disp.display.isValid() ) {
|
||||||
|
it.remove();
|
||||||
|
space.removeCollisionObject( obj );
|
||||||
|
active.remove( obj );
|
||||||
|
isTnt.remove( obj );
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location displayLocation = disp.display.getLocation();
|
||||||
|
|
||||||
|
// Do not calculate for this object if it is inactive
|
||||||
|
if ( !obj.isActive() ) {
|
||||||
|
if ( teleportOnDeactivate && active.remove( obj ) ) {
|
||||||
|
// Newly deactivated object, teleport the display to the actual position to prevent the translation from getting too large
|
||||||
|
|
||||||
|
Transformation currentTransformation = disp.display.getTransformation();
|
||||||
|
org.joml.Vector3f trans = currentTransformation.getTranslation();
|
||||||
|
|
||||||
|
displayLocation.add( trans.x(), trans.y(), trans.z() );
|
||||||
|
disp.display.teleport( displayLocation );
|
||||||
|
|
||||||
|
Transformation newTrans = new Transformation( new org.joml.Vector3f( 0, 0, 0 ), currentTransformation.getLeftRotation(), currentTransformation.getScale(), currentTransformation.getRightRotation() );
|
||||||
|
disp.display.setInterpolationDelay( 0 );
|
||||||
|
disp.display.setInterpolationDuration( 0 );
|
||||||
|
disp.display.setTransformation( newTrans );
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
active.add( obj );
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f location = obj.getPhysicsLocation();
|
||||||
|
Quaternion rotation = new Quaternion();
|
||||||
|
obj.getPhysicsRotation( rotation );
|
||||||
|
|
||||||
|
Vector offsetVec = disp.offset;
|
||||||
|
Vector scale = disp.scale;
|
||||||
|
Matrix4f transformRot = new Matrix4f();
|
||||||
|
transformRot.set( new Quaternionf( rotation.getX(), rotation.getY(), rotation.getZ(), rotation.getW() ) );
|
||||||
|
|
||||||
|
transformRot.mul( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, - ( float ) offsetVec.getX(), - ( float ) offsetVec.getY(), - ( float ) offsetVec.getZ(), 1 );
|
||||||
|
transformRot.mul( ( float ) scale.getX(), 0, 0, 0, 0, ( float ) scale.getY(), 0, 0, 0, 0, ( float ) scale.getZ(), 0, 0, 0, 0, 1 );
|
||||||
|
Matrix4f transMat = new Matrix4f( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ( float ) ( location.getX() - displayLocation.getX() ), ( float ) ( location.getY() - displayLocation.getY() ), ( float ) ( location.getZ() - displayLocation.getZ() ), 1 );
|
||||||
|
|
||||||
|
disp.display.setInterpolationDelay( 0 );
|
||||||
|
disp.display.setInterpolationDuration( 2 );
|
||||||
|
disp.display.setTransformationMatrix( transMat.mul( transformRot ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
tick += SIMULATION_SPEED / 20f;
|
||||||
|
}
|
||||||
|
}, 0, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerCommands() {
|
||||||
|
new SubCommand( "minie" )
|
||||||
|
.addSenderValidator( new SenderValidatorPlayer() )
|
||||||
|
.add( new SubCommand( "spawn" )
|
||||||
|
.defaultTo( ( sender, args, params ) -> {
|
||||||
|
Player player = ( Player ) sender;
|
||||||
|
Location loc = player.getLocation();
|
||||||
|
loc.setPitch( 0 );
|
||||||
|
loc.setDirection( new Vector( 0, 0, 0 ) );
|
||||||
|
|
||||||
|
BlockData displayData;
|
||||||
|
ItemStack item = player.getInventory().getItemInMainHand();
|
||||||
|
if ( item != null && item.getType() != Material.AIR && item.getType().isBlock() ) {
|
||||||
|
displayData = item.getType().createBlockData();
|
||||||
|
} else {
|
||||||
|
displayData = Material.TNT.createBlockData();
|
||||||
|
}
|
||||||
|
|
||||||
|
CraftBlockData data = ( CraftBlockData ) displayData;
|
||||||
|
|
||||||
|
BoundingBox[] boxes = convertFrom( data.getState().f( null, null ) );
|
||||||
|
Vector blockCenter = calculateCenter( boxes );
|
||||||
|
|
||||||
|
CollisionShape box;
|
||||||
|
if ( boxes.length == 0 ) {
|
||||||
|
player.sendMessage( "No bounding box detected! Wrong method?" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( boxes.length == 1 ) {
|
||||||
|
Vector min = boxes[ 0 ].getMin();
|
||||||
|
Vector center = boxes[ 0 ].getCenter().subtract( min );
|
||||||
|
box = new BoxCollisionShape( ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() );
|
||||||
|
} else {
|
||||||
|
CompoundCollisionShape compound = new CompoundCollisionShape();
|
||||||
|
for ( BoundingBox aabb : boxes ) {
|
||||||
|
Vector min = aabb.getMin();
|
||||||
|
Vector center = aabb.getCenter();
|
||||||
|
Vector half = center.clone().subtract( min );
|
||||||
|
center.subtract( blockCenter );
|
||||||
|
CollisionShape subShape = new BoxCollisionShape( ( float ) half.getX(), ( float ) half.getY(), ( float ) half.getZ() );
|
||||||
|
compound.addChildShape( subShape, ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() );
|
||||||
|
}
|
||||||
|
box = compound;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsRigidBody rigid = new PhysicsRigidBody( box, 1f );
|
||||||
|
|
||||||
|
// Since the center of the rigid body and the box shape are different, we need to offset the location
|
||||||
|
rigid.setPhysicsLocation( new Vector3f( ( float ) ( loc.getX() + blockCenter.getX() ), ( float ) ( loc.getY() + blockCenter.getY() ), ( float ) ( loc.getZ() + blockCenter.getZ() ) ) );
|
||||||
|
|
||||||
|
// Convert the display direction vector to a quaternion
|
||||||
|
rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) );
|
||||||
|
|
||||||
|
space.addCollisionObject( rigid );
|
||||||
|
|
||||||
|
// Create the display
|
||||||
|
BlockDisplay display = loc.getWorld().spawn( loc, BlockDisplay.class, d -> {
|
||||||
|
d.setBlock( displayData );
|
||||||
|
} );
|
||||||
|
|
||||||
|
linkedDisplays.put( rigid, new AuxiliaryDisplayStruct( display, blockCenter ) );
|
||||||
|
|
||||||
|
if ( displayData.getMaterial() == Material.TNT ) {
|
||||||
|
isTnt.add( rigid );
|
||||||
|
}
|
||||||
|
|
||||||
|
player.sendMessage( "Created display" );
|
||||||
|
} ) )
|
||||||
|
.add( new SubCommand( "constraint" )
|
||||||
|
.defaultTo( ( sender, args, params ) -> {
|
||||||
|
Player player = ( Player ) sender;
|
||||||
|
Location loc = player.getLocation();
|
||||||
|
loc.setPitch( 0 );
|
||||||
|
loc.setDirection( new Vector( 0, 0, 0 ) );
|
||||||
|
|
||||||
|
BlockData displayData = Material.IRON_TRAPDOOR.createBlockData();
|
||||||
|
CraftBlockData data = ( CraftBlockData ) displayData;
|
||||||
|
|
||||||
|
final Vector scale = new Vector( 5, 2, 5 );
|
||||||
|
|
||||||
|
BlockPosition pos = new BlockPosition( loc.getBlockX(), loc.getBlockY(), loc.getBlockZ() );
|
||||||
|
BoundingBox[] boxes = convertFrom( data.getState().f( ( ( CraftWorld ) loc.getWorld() ).getHandle(), pos ) );
|
||||||
|
for ( BoundingBox box : boxes ) {
|
||||||
|
Vector min = box.getMin().multiply( scale );
|
||||||
|
Vector max = box.getMax().multiply( scale );
|
||||||
|
box.resize( min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ() );
|
||||||
|
}
|
||||||
|
Vector blockCenter = calculateCenter( boxes );
|
||||||
|
|
||||||
|
CollisionShape box;
|
||||||
|
if ( boxes.length == 0 ) {
|
||||||
|
player.sendMessage( "No bounding box detected! Wrong method?" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( boxes.length == 1 ) {
|
||||||
|
Vector min = boxes[ 0 ].getMin();
|
||||||
|
Vector center = boxes[ 0 ].getCenter().subtract( min );
|
||||||
|
box = new BoxCollisionShape( ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() );
|
||||||
|
} else {
|
||||||
|
CompoundCollisionShape compound = new CompoundCollisionShape();
|
||||||
|
for ( BoundingBox aabb : boxes ) {
|
||||||
|
Vector min = aabb.getMin();
|
||||||
|
Vector center = aabb.getCenter();
|
||||||
|
Vector half = center.clone().subtract( min );
|
||||||
|
center.subtract( blockCenter );
|
||||||
|
CollisionShape subShape = new BoxCollisionShape( ( float ) half.getX(), ( float ) half.getY(), ( float ) half.getZ() );
|
||||||
|
compound.addChildShape( subShape, ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() );
|
||||||
|
}
|
||||||
|
box = compound;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsRigidBody rigid = new PhysicsRigidBody( box, 5f );
|
||||||
|
|
||||||
|
// Since the center of the rigid body and the box shape are different, we need to offset the location
|
||||||
|
rigid.setPhysicsLocation( new Vector3f( ( float ) ( loc.getX() + blockCenter.getX() ), ( float ) ( loc.getY() + blockCenter.getY() ), ( float ) ( loc.getZ() + blockCenter.getZ() ) ) );
|
||||||
|
|
||||||
|
// Convert the display direction vector to a quaternion
|
||||||
|
rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) );
|
||||||
|
|
||||||
|
space.addCollisionObject( rigid );
|
||||||
|
|
||||||
|
New6Dof constraint = new New6Dof( rigid, new Vector3f( 0, 0, 0 ), rigid.getPhysicsLocation(), Matrix3f.IDENTITY, Matrix3f.IDENTITY, RotationOrder.XYZ );
|
||||||
|
|
||||||
|
constraint.set( MotorParam.UpperLimit, 3, ( float ) Math.PI / 6 );
|
||||||
|
constraint.set( MotorParam.LowerLimit, 3, ( float ) - Math.PI / 6 );
|
||||||
|
constraint.set( MotorParam.UpperLimit, 4, 0 );
|
||||||
|
constraint.set( MotorParam.LowerLimit, 4, 0 );
|
||||||
|
constraint.set( MotorParam.UpperLimit, 5, 0 );
|
||||||
|
constraint.set( MotorParam.LowerLimit, 5, 0 );
|
||||||
|
|
||||||
|
space.addJoint( constraint );
|
||||||
|
|
||||||
|
player.sendMessage( "Location: " + rigid.getPhysicsLocation() );
|
||||||
|
|
||||||
|
// Create the display
|
||||||
|
BlockDisplay display = loc.getWorld().spawn( loc, BlockDisplay.class, d -> {
|
||||||
|
d.setBlock( displayData );
|
||||||
|
} );
|
||||||
|
|
||||||
|
linkedDisplays.put( rigid, new AuxiliaryDisplayStruct( display, blockCenter ).setScale( scale ) );
|
||||||
|
|
||||||
|
player.sendMessage( "Created constraint" );
|
||||||
|
}) )
|
||||||
|
.add( new SubCommand( "fixed" )
|
||||||
|
.defaultTo( ( sender, args, params ) -> {
|
||||||
|
Player player = ( Player ) sender;
|
||||||
|
Block block = player.getTargetBlockExact( 20 );
|
||||||
|
convert( block );
|
||||||
|
|
||||||
|
player.sendMessage( "Fixed" );
|
||||||
|
} ) )
|
||||||
|
.add( new SubCommand( "chunk" )
|
||||||
|
.defaultTo( ( sender, args, params ) -> {
|
||||||
|
Player player = ( Player ) sender;
|
||||||
|
|
||||||
|
player.sendMessage( "Registering chunk..." );
|
||||||
|
player.sendMessage( "Registered " + scanAndGenerate( player.getLocation().getChunk() ) );
|
||||||
|
} ) )
|
||||||
|
.add( new SubCommand( "toggle" )
|
||||||
|
.defaultTo( ( sender, args, params ) -> {
|
||||||
|
paused = !paused;
|
||||||
|
} ) )
|
||||||
|
.add( new SubCommand( "teleport" )
|
||||||
|
.defaultTo( ( sender, args, params ) -> {
|
||||||
|
teleportOnDeactivate = !teleportOnDeactivate;
|
||||||
|
} ) )
|
||||||
|
.add( new SubCommand( "impulse" )
|
||||||
|
.add( new SubCommand( new InputValidatorInt() )
|
||||||
|
.defaultTo( ( sender, args, params ) -> {
|
||||||
|
Player player = ( Player ) sender;
|
||||||
|
Location loc = player.getLocation();
|
||||||
|
|
||||||
|
impulse( new Vector3f( ( float ) loc.getX(), ( float ) loc.getY(), ( float ) loc.getZ() ), params.getLast( int.class ) );
|
||||||
|
} ) )
|
||||||
|
.defaultTo( ( sender, args, params ) -> {
|
||||||
|
Player player = ( Player ) sender;
|
||||||
|
Location loc = player.getLocation();
|
||||||
|
|
||||||
|
impulse( new Vector3f( ( float ) loc.getX(), ( float ) loc.getY(), ( float ) loc.getZ() ), 15f );
|
||||||
|
} ) )
|
||||||
|
.defaultTo( new CommandExecutableMessage( "An argument must be provided" ) )
|
||||||
|
.whenUnknown( new CommandExecutableMessage( "Unknown argument" ) )
|
||||||
|
.applyTo( getCommand( "minie" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void impulse( Vector3f location, float power ) {
|
||||||
|
PhysicsRigidBody rigid = new PhysicsRigidBody( new SphereCollisionShape( 20 ), 1f );
|
||||||
|
rigid.setPhysicsLocation( location );
|
||||||
|
space.contactTest( rigid, new PhysicsCollisionListener() {
|
||||||
|
@Override
|
||||||
|
public void collision( PhysicsCollisionEvent event ) {
|
||||||
|
if ( event.getObjectB() instanceof PhysicsRigidBody ) {
|
||||||
|
PhysicsRigidBody objB = ( PhysicsRigidBody ) event.getObjectB();
|
||||||
|
|
||||||
|
Vector3f rLoc = objB.getPhysicsLocation();
|
||||||
|
Vector3f to = rLoc.subtract( ( float ) location.getX(), ( float ) location.getY(), ( float ) location.getZ() );
|
||||||
|
|
||||||
|
float distSq = to.lengthSquared();
|
||||||
|
|
||||||
|
if ( distSq != 0 ) {
|
||||||
|
if ( power / distSq > 1 ) {
|
||||||
|
objB.applyCentralImpulse( to.normalize().mult( power / distSq ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
private int saveAllChunks( World world ) {
|
||||||
|
int total = 0;
|
||||||
|
for ( Chunk chunk : world.getLoadedChunks() ) {
|
||||||
|
total += saveChunk( chunk );
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int saveChunk( Chunk chunk ) {
|
||||||
|
File saveFile = new File( getDataFolder() + "/chunks", chunk.getWorld().getName() + "," + chunk.getX() + "," + chunk.getZ() );
|
||||||
|
saveFile.getParentFile().mkdirs();
|
||||||
|
saveFile.delete();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
try ( FileWriter writer = new FileWriter( saveFile ) ) {
|
||||||
|
World world = chunk.getWorld();
|
||||||
|
for ( int y = world.getMinHeight(); y < world.getMaxHeight(); y++ ) {
|
||||||
|
for ( int z = 0; z < 16; z++ ) {
|
||||||
|
for ( int x = 0; x < 16; x++ ) {
|
||||||
|
Block block = chunk.getBlock( x, y, z );
|
||||||
|
if ( !( block.isEmpty() && block.isLiquid() ) ) {
|
||||||
|
BlockState state = block.getState();
|
||||||
|
BlockData displayData = state.getBlockData();
|
||||||
|
Location loc = block.getLocation();
|
||||||
|
CraftBlockData data = ( CraftBlockData ) displayData;
|
||||||
|
|
||||||
|
BlockPosition pos = new BlockPosition( loc.getBlockX(), loc.getBlockY(), loc.getBlockZ() );
|
||||||
|
BoundingBox[] boxes = convertFrom( data.getState().f( ( ( CraftWorld ) world ).getHandle(), pos ) );
|
||||||
|
|
||||||
|
if ( boxes.length > 0 ) {
|
||||||
|
// Save this block
|
||||||
|
for ( BoundingBox box : boxes ) {
|
||||||
|
writer.write( ( box.getMinX() + x ) + "," );
|
||||||
|
writer.write( ( box.getMinY() + y ) + "," );
|
||||||
|
writer.write( ( box.getMinZ() + z ) + "," );
|
||||||
|
writer.write( ( box.getMaxX() + x ) + "," );
|
||||||
|
writer.write( ( box.getMaxY() + y ) + "," );
|
||||||
|
writer.write( ( box.getMaxZ() + z ) + "\n" );
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int scanAndGenerate( World world ) {
|
||||||
|
int total = 0;
|
||||||
|
for ( Chunk chunk : world.getLoadedChunks() ) {
|
||||||
|
total += scanAndGenerate( chunk );
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int scanAndGenerate( Chunk chunk ) {
|
||||||
|
World world = chunk.getWorld();
|
||||||
|
CompoundCollisionShape compound = new CompoundCollisionShape();
|
||||||
|
int worldHeight = world.getMaxHeight() - world.getMinHeight();
|
||||||
|
boolean[][][] isSolid = new boolean[ worldHeight ][ 16 ][ 16 ];
|
||||||
|
for ( int y = 0; y < worldHeight; y++ ) {
|
||||||
|
for ( int z = 0; z < 16; z++ ) {
|
||||||
|
for ( int x = 0; x < 16; x++ ) {
|
||||||
|
Block block = chunk.getBlock( x, y + world.getMinHeight(), z );
|
||||||
|
if ( !( block.isEmpty() && block.isLiquid() ) ) {
|
||||||
|
BlockState state = block.getState();
|
||||||
|
BlockData displayData = state.getBlockData();
|
||||||
|
Location loc = block.getLocation();
|
||||||
|
CraftBlockData data = ( CraftBlockData ) displayData;
|
||||||
|
|
||||||
|
BlockPosition pos = new BlockPosition( loc.getBlockX(), loc.getBlockY(), loc.getBlockZ() );
|
||||||
|
BoundingBox[] boxes = convertFrom( data.getState().f( ( ( CraftWorld ) world ).getHandle(), pos ) );
|
||||||
|
|
||||||
|
if ( boxes.length == 1 && boxes[ 0 ].getVolume() == 1 ) {
|
||||||
|
isSolid[ y ][ z ][ x ] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( int y = 0; y < worldHeight; y++ ) {
|
||||||
|
for ( int z = 0; z < 16; z++ ) {
|
||||||
|
for ( int x = 0; x < 16; x++ ) {
|
||||||
|
if ( x > 0 && x < 15 && z > 0 && z < 15 && y > 0 && y < worldHeight - 1 ) {
|
||||||
|
// Figure out if this block is surrounded by solid blocks to avoid having to add this block
|
||||||
|
if ( isSolid[ y - 1 ][ z ][ x ]
|
||||||
|
&& isSolid[ y + 1 ][ z ][ x ]
|
||||||
|
&& isSolid[ y ][ z - 1 ][ x ]
|
||||||
|
&& isSolid[ y ][ z + 1 ][ x ]
|
||||||
|
&& isSolid[ y ][ z ][ x - 1 ]
|
||||||
|
&& isSolid[ y ][ z ][ x + 1 ] ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Block block = chunk.getBlock( x, y + world.getMinHeight(), z );
|
||||||
|
|
||||||
|
if ( !( block.isEmpty() && block.isLiquid() ) ) {
|
||||||
|
BlockState state = block.getState();
|
||||||
|
BlockData displayData = state.getBlockData();
|
||||||
|
Location loc = block.getLocation();
|
||||||
|
CraftBlockData data = ( CraftBlockData ) displayData;
|
||||||
|
|
||||||
|
BlockPosition pos = new BlockPosition( loc.getBlockX(), loc.getBlockY(), loc.getBlockZ() );
|
||||||
|
BoundingBox[] boxes = convertFrom( data.getState().f( ( ( CraftWorld ) world ).getHandle(), pos ) );
|
||||||
|
|
||||||
|
if ( boxes.length == 0 ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( boxes.length == 1 ) {
|
||||||
|
Vector min = boxes[ 0 ].getMin();
|
||||||
|
Vector center = boxes[ 0 ].getCenter().subtract( min );
|
||||||
|
Vector3f halfExtents = new Vector3f( ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() );
|
||||||
|
|
||||||
|
CollisionShape shape = collisionShapes.get( halfExtents );
|
||||||
|
if ( shape == null ) {
|
||||||
|
shape = new BoxCollisionShape( halfExtents );
|
||||||
|
collisionShapes.put( halfExtents, shape );
|
||||||
|
}
|
||||||
|
Vector3f shapeCenter = new Vector3f( ( float ) boxes[ 0 ].getCenterX() + x, ( float ) boxes[ 0 ].getCenterY() + y, ( float ) boxes[ 0 ].getCenterZ() + z );
|
||||||
|
compound.addChildShape( shape, shapeCenter );
|
||||||
|
} else {
|
||||||
|
for ( BoundingBox aabb : boxes ) {
|
||||||
|
Vector min = aabb.getMin();
|
||||||
|
Vector center = aabb.getCenter();
|
||||||
|
Vector half = center.clone().subtract( min );
|
||||||
|
Vector3f halfExtents = new Vector3f( ( float ) half.getX(), ( float ) half.getY(), ( float ) half.getZ() );
|
||||||
|
|
||||||
|
CollisionShape shape = collisionShapes.get( halfExtents );
|
||||||
|
if ( shape == null ) {
|
||||||
|
shape = new BoxCollisionShape( halfExtents );
|
||||||
|
collisionShapes.put( halfExtents, shape );
|
||||||
|
}
|
||||||
|
compound.addChildShape( shape, ( float ) center.getX() + x, ( float ) center.getY() + y, ( float ) center.getZ() + z );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogger().info( "Chunk has " + compound.countChildren() );
|
||||||
|
|
||||||
|
// Create a rigid body
|
||||||
|
PhysicsRigidBody rigid = new PhysicsRigidBody( compound, PhysicsBody.massForStatic );
|
||||||
|
rigid.setPhysicsLocation( new Vector3f( chunk.getX() << 4, world.getMinHeight(), chunk.getZ() << 4 ) );
|
||||||
|
rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) );
|
||||||
|
|
||||||
|
space.addCollisionObject( rigid );
|
||||||
|
|
||||||
|
PhysicsRigidBody old = chunks.put( new ChunkLocation( chunk ), rigid );
|
||||||
|
if ( old != null ) {
|
||||||
|
getLogger().warning( "Old rigid body found!" );
|
||||||
|
space.remove( old );
|
||||||
|
}
|
||||||
|
|
||||||
|
return compound.countChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean convert( Block block ) {
|
||||||
|
BlockState state = block.getState();
|
||||||
|
BlockData displayData = state.getBlockData();
|
||||||
|
Location loc = block.getLocation();
|
||||||
|
|
||||||
|
CraftBlockData data = ( CraftBlockData ) displayData;
|
||||||
|
|
||||||
|
BlockPosition pos = new BlockPosition( loc.getBlockX(), loc.getBlockY(), loc.getBlockZ() );
|
||||||
|
BoundingBox[] boxes = convertFrom( data.getState().f( ( ( CraftWorld ) loc.getWorld() ).getHandle(), pos ) );
|
||||||
|
Vector blockCenter = calculateCenter( boxes );
|
||||||
|
|
||||||
|
CollisionShape box;
|
||||||
|
if ( boxes.length == 0 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( boxes.length == 1 ) {
|
||||||
|
Vector min = boxes[ 0 ].getMin();
|
||||||
|
Vector center = boxes[ 0 ].getCenter().subtract( min );
|
||||||
|
box = new BoxCollisionShape( ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() );
|
||||||
|
} else {
|
||||||
|
CompoundCollisionShape compound = new CompoundCollisionShape();
|
||||||
|
for ( BoundingBox aabb : boxes ) {
|
||||||
|
Vector min = aabb.getMin();
|
||||||
|
Vector center = aabb.getCenter();
|
||||||
|
Vector half = center.clone().subtract( min );
|
||||||
|
center.subtract( blockCenter );
|
||||||
|
CollisionShape subShape = new BoxCollisionShape( ( float ) half.getX(), ( float ) half.getY(), ( float ) half.getZ() );
|
||||||
|
compound.addChildShape( subShape, ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() );
|
||||||
|
}
|
||||||
|
box = compound;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsRigidBody rigid = new PhysicsRigidBody( box, 1f );
|
||||||
|
rigid.setProtectGravity( true );
|
||||||
|
rigid.setGravity( new Vector3f( 0, 0, 0 ) );
|
||||||
|
|
||||||
|
// Since the center of the rigid body and the box shape are different, we need to offset the location
|
||||||
|
rigid.setPhysicsLocation( new Vector3f( ( float ) ( loc.getX() + blockCenter.getX() ), ( float ) ( loc.getY() + blockCenter.getY() ), ( float ) ( loc.getZ() + blockCenter.getZ() ) ) );
|
||||||
|
|
||||||
|
// Convert the display direction vector to a quaternion
|
||||||
|
rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) );
|
||||||
|
|
||||||
|
space.addCollisionObject( rigid );
|
||||||
|
|
||||||
|
New6Dof constraint = new New6Dof( rigid, new Vector3f( 0, 0, 0 ), rigid.getPhysicsLocation(), Matrix3f.IDENTITY, Matrix3f.IDENTITY, RotationOrder.XYZ );
|
||||||
|
|
||||||
|
constraint.setBreakingImpulseThreshold( 5 );
|
||||||
|
constraint.set( MotorParam.UpperLimit, 3, 0 );
|
||||||
|
constraint.set( MotorParam.LowerLimit, 3, 0 );
|
||||||
|
constraint.set( MotorParam.UpperLimit, 4, 0 );
|
||||||
|
constraint.set( MotorParam.LowerLimit, 4, 0 );
|
||||||
|
constraint.set( MotorParam.UpperLimit, 5, 0 );
|
||||||
|
constraint.set( MotorParam.LowerLimit, 5, 0 );
|
||||||
|
|
||||||
|
space.addJoint( constraint );
|
||||||
|
|
||||||
|
joints.put( constraint, new JointStruct( state, rigid, blockCenter ) );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean loadNativeLibraries() {
|
||||||
|
if ( SystemUtils.IS_OS_MAC ) {
|
||||||
|
System.load( new File( getDataFolder() + "/lib", "libbulletjme.dylib" ).getAbsolutePath() );
|
||||||
|
} else if ( SystemUtils.IS_OS_LINUX ) {
|
||||||
|
System.load( new File( getDataFolder() + "/lib", "libbulletjme.so" ).getAbsolutePath() );
|
||||||
|
} else if ( SystemUtils.IS_OS_WINDOWS ) {
|
||||||
|
System.load( new File( getDataFolder() + "/lib", "bulletjme.dll" ).getAbsolutePath() );
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector calculateCenter( BoundingBox[] boxes ) {
|
||||||
|
Vector center = new Vector( 0, 0, 0 );
|
||||||
|
// TODO Throw error, probably
|
||||||
|
if ( boxes.length == 0 ) {
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
|
||||||
|
double totalVolume = 0;
|
||||||
|
for ( BoundingBox box : boxes ) {
|
||||||
|
Vector mid = box.getCenter();
|
||||||
|
center.add( mid.multiply( box.getVolume() ) );
|
||||||
|
totalVolume += box.getVolume();
|
||||||
|
}
|
||||||
|
return center.multiply( 1 / totalVolume );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BoundingBox[] convertFrom( VoxelShape shape ) {
|
||||||
|
return shape.e().stream()
|
||||||
|
.map( aabb -> { return new BoundingBox( aabb.a, aabb.b, aabb.c, aabb.d, aabb.e, aabb.f ); } )
|
||||||
|
.toArray( BoundingBox[]::new );
|
||||||
|
}
|
||||||
|
|
||||||
|
private class JointStruct {
|
||||||
|
BlockState state;
|
||||||
|
PhysicsRigidBody object;
|
||||||
|
Vector offset;
|
||||||
|
|
||||||
|
JointStruct( BlockState state, PhysicsRigidBody body, Vector offset ) {
|
||||||
|
this.state = state;
|
||||||
|
this.object = body;
|
||||||
|
this.offset = offset.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AuxiliaryDisplayStruct {
|
||||||
|
AuxiliaryDisplayStruct( Display display, Vector offset ) {
|
||||||
|
this.display = display;
|
||||||
|
this.offset = offset.clone();
|
||||||
|
this.scale = new Vector( 1, 1, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
AuxiliaryDisplayStruct setScale( Vector scale ) {
|
||||||
|
this.scale = scale.clone();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Display display;
|
||||||
|
Vector offset;
|
||||||
|
Vector scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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( ", " ) ) + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 );
|
||||||
|
}
|
||||||
@@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @author BananaPuncher714
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.executor;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Classes for building complex command trees.
|
||||||
|
*
|
||||||
|
* @author BananaPuncher714
|
||||||
|
*/
|
||||||
|
package com.aaaaahhhhhhh.bananapuncher714.minietest.command;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 ];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 ];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @author BananaPuncher714
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.sender;
|
||||||
|
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
public interface SenderValidator {
|
||||||
|
boolean isValid( CommandSender sender );
|
||||||
|
}
|
||||||
@@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @author BananaPuncher714
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.aaaaahhhhhhh.bananapuncher714.minietest.command.validator.sender;
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
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 {
|
||||||
|
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 + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class Component {
|
||||||
|
|
||||||
|
|
||||||
|
public Collection< Component > getComponents() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 >();
|
||||||
|
}
|
||||||
@@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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 ) {
|
||||||
|
for ( Plane plane : planes ) {
|
||||||
|
if ( plane.matches( facet.normal, facet.points.get( 0 ) ) ) {
|
||||||
|
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();
|
||||||
|
} else {
|
||||||
|
System.out.println( "Normal " + facet.normal );
|
||||||
|
for ( Vector point : facet.points ) {
|
||||||
|
System.out.println( point );
|
||||||
|
}
|
||||||
|
System.exit( 1 );
|
||||||
|
}
|
||||||
|
plane.addShape( facet );
|
||||||
|
planes.add( plane );
|
||||||
|
return plane;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
import org.joml.Matrix3d;
|
||||||
|
import org.joml.Vector3d;
|
||||||
|
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.Point;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.Polygon;
|
||||||
|
|
||||||
|
public class Plane {
|
||||||
|
public Vector normal;
|
||||||
|
public Vector point;
|
||||||
|
|
||||||
|
public List< Polygon > polygons = new ArrayList< Polygon >();
|
||||||
|
|
||||||
|
public void addShape( Facet facet ) {
|
||||||
|
// TODO Flatten the polygons into 2d points
|
||||||
|
// Get the basis of this plane
|
||||||
|
Vector basis1 = normal.clone().multiply( point.clone().multiply( -1 ).dot( normal ) ).add( point ).normalize();
|
||||||
|
Vector3d b1 = new Vector3d( basis1.getX(), basis1.getY(), basis1.getZ() );
|
||||||
|
Vector3d b3 = new Vector3d( normal.getX(), normal.getY(), normal.getZ() );
|
||||||
|
Vector3d b2 = new Vector3d( b1 ).cross( b3 ).normalize();
|
||||||
|
Matrix3d mat = new Matrix3d( b1, b2, b3 ).transpose();
|
||||||
|
|
||||||
|
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 ).subtract( point );
|
||||||
|
|
||||||
|
Vector3d v = new Vector3d( i.getX(), i.getY(), i.getZ() );
|
||||||
|
|
||||||
|
Vector3d r = v.mul( mat );
|
||||||
|
|
||||||
|
polygon.add( new Point( r.x(), r.y() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
polygons.add( new Polygon( polygon ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches( Vector normal, Vector point ) {
|
||||||
|
return Math.abs( this.normal.dot( normal ) ) > 0.99 && Math.abs( this.point.clone().subtract( point ).dot( normal ) ) < 0.01;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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( Class< T > clazz, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A primitive sorted linked list of edges
|
||||||
|
* - O(n) insert, search
|
||||||
|
* - O(1) upper, lower, remove
|
||||||
|
*/
|
||||||
|
public class EdgeCollection< T > implements Iterable< T > {
|
||||||
|
Map< T, Node > quickMap = new ConcurrentHashMap< T, Node >();
|
||||||
|
Node head = new Node( null );
|
||||||
|
|
||||||
|
BiFunction< T, T, Boolean > isGreaterThan;
|
||||||
|
|
||||||
|
public EdgeCollection( BiFunction< T, T, Boolean > compare ) {
|
||||||
|
isGreaterThan = compare;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T upper( T val ) {
|
||||||
|
Node n = quickMap.get( val );
|
||||||
|
return n == null ? null : n.upper.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T lower( T val ) {
|
||||||
|
Node n = quickMap.get( val );
|
||||||
|
return n == null ? null : n.lower.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean remove( T val ) {
|
||||||
|
Node n = quickMap.remove( val );
|
||||||
|
if ( n != null ) {
|
||||||
|
n.lower.upper = n.upper;
|
||||||
|
n.upper.lower = n.lower;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains( T val ) {
|
||||||
|
return quickMap.containsKey( val );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insert( T val ) {
|
||||||
|
if ( !quickMap.containsKey( val ) ) {
|
||||||
|
Node insertBefore = head.upper;
|
||||||
|
// Find the first node that the value is NOT greater than
|
||||||
|
// Otherwise, break and insert before
|
||||||
|
while ( insertBefore.value != null && isGreaterThan.apply( val, insertBefore.value ) ) {
|
||||||
|
insertBefore = insertBefore.upper;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node newNode = new Node( val );
|
||||||
|
|
||||||
|
newNode.lower = insertBefore.lower;
|
||||||
|
newNode.lower.upper = newNode;
|
||||||
|
newNode.upper = insertBefore;
|
||||||
|
|
||||||
|
insertBefore.lower = newNode;
|
||||||
|
|
||||||
|
quickMap.put( val, newNode );
|
||||||
|
} else {
|
||||||
|
// Should not really happen
|
||||||
|
throw new IllegalStateException( "Tried to insert value twice" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first value that is greater than the one provided
|
||||||
|
public T searchUpper( T val ) {
|
||||||
|
Node n = quickMap.get( val );
|
||||||
|
if ( n == null ) {
|
||||||
|
Node greater = head.upper;
|
||||||
|
while ( greater.value != null && isGreaterThan.apply( val, greater.value ) ) {
|
||||||
|
greater = greater.upper;
|
||||||
|
}
|
||||||
|
return greater.value;
|
||||||
|
} else {
|
||||||
|
return n.upper.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the highest value that is lower than the one provided
|
||||||
|
public T searchLower( T val ) {
|
||||||
|
Node n = quickMap.get( val );
|
||||||
|
if ( n == null ) {
|
||||||
|
Node lower = head.upper;
|
||||||
|
while ( lower.value != null && isGreaterThan.apply( val, lower.value ) ) {
|
||||||
|
lower = lower.upper;
|
||||||
|
}
|
||||||
|
return lower.lower.value;
|
||||||
|
} else {
|
||||||
|
return n.lower.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
head = new Node( null );
|
||||||
|
quickMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class Node {
|
||||||
|
Node lower;
|
||||||
|
Node upper;
|
||||||
|
T value;
|
||||||
|
|
||||||
|
Node( T val ) {
|
||||||
|
lower = this;
|
||||||
|
upper = this;
|
||||||
|
|
||||||
|
this.value = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator< T > iterator() {
|
||||||
|
return new Iterator< T >() {
|
||||||
|
Node current = head;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return current.upper != head;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
current = current.upper;
|
||||||
|
return current.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not necessary, for now
|
||||||
|
// @Override
|
||||||
|
// public void remove() {
|
||||||
|
// T val = current.value;
|
||||||
|
// current = current.lower;
|
||||||
|
// EdgeCollection.this.remove( val );
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This edge dictionary is a loosely sorted collection of regions that are organized by the _current_ event vertex,
|
||||||
|
* and its relationship with the upper edge of each region. Regions are sorted on insertion, and stored in a doubly
|
||||||
|
* linked list thereafter. Searching for a region entails moving backwards through the list starting at the tail
|
||||||
|
* until a region which matches the sorting method is found.
|
||||||
|
*
|
||||||
|
* There are no checks in place to prevent undefined behavior of any kind.
|
||||||
|
*
|
||||||
|
* Regions are sorted in ascending order.
|
||||||
|
*
|
||||||
|
* Searching for a region is done from head to tail, until a region that is greater than or equal has been found.
|
||||||
|
*/
|
||||||
|
public class EdgeDict {
|
||||||
|
Map< RegionOld, Node > quickMap = new HashMap< RegionOld, Node >();
|
||||||
|
/*
|
||||||
|
* The comparator function is responsible for determining whether a region is less than or equal to another
|
||||||
|
*
|
||||||
|
* Given o1 and o2, return true if o1 is less than or equal to o2
|
||||||
|
*/
|
||||||
|
BiFunction< RegionOld, RegionOld, Boolean > comparator;
|
||||||
|
Node head;
|
||||||
|
|
||||||
|
public EdgeDict( Supplier< VertexOld > supplier ) {
|
||||||
|
head = new Node( null );
|
||||||
|
|
||||||
|
head.after = head;
|
||||||
|
head.before = head;
|
||||||
|
|
||||||
|
comparator = ( o1, o2 ) -> {
|
||||||
|
VertexOld vert = supplier.get();
|
||||||
|
HalfEdge e1 = o1.upperEdge;
|
||||||
|
HalfEdge e2 = o2.upperEdge;
|
||||||
|
|
||||||
|
// Check if the left vertex is the current event
|
||||||
|
if ( e1.sym().origin == vert ) {
|
||||||
|
if ( e2.sym().origin == vert ) {
|
||||||
|
// Compare right vertices
|
||||||
|
// Contrived, why not check slope instead?
|
||||||
|
// Edge case is if slope is infinity
|
||||||
|
if ( e1.origin.lessThanOrEqualTo( e2.origin ) ) {
|
||||||
|
// Check if the right vertex is below the edge
|
||||||
|
return e1.origin.compareTo( e2.sym().origin, e2.origin ) <= 0;
|
||||||
|
}
|
||||||
|
return e2.origin.compareTo( e1.sym().origin, e1.origin ) >= 0;
|
||||||
|
}
|
||||||
|
return vert.compareTo( e2.sym().origin, e2.origin ) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( e2.sym().origin == vert ) {
|
||||||
|
return vert.compareTo( e1.sym().origin, e1.origin ) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure the distance and see if one is shorter
|
||||||
|
double d1 = vert.verticalDistance( e1.sym().origin, e1.origin );
|
||||||
|
double d2 = vert.verticalDistance( e2.sym().origin, e2.origin );
|
||||||
|
|
||||||
|
return d1 >= d2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegionOld above( RegionOld region ) {
|
||||||
|
return quickMap.get( region ).after.region;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegionOld below( RegionOld region ) {
|
||||||
|
return quickMap.get( region ).before.region;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insert( RegionOld region ) {
|
||||||
|
// Insert from the back
|
||||||
|
insertBefore( null, region );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove( RegionOld region ) {
|
||||||
|
Node node = quickMap.remove( region );
|
||||||
|
if ( node != null ) {
|
||||||
|
node.before.after = node.after.before;
|
||||||
|
}
|
||||||
|
region.upperEdge.region = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertBefore( RegionOld startRegion, RegionOld region ) {
|
||||||
|
Node node = new Node( region );
|
||||||
|
quickMap.put( region, node );
|
||||||
|
|
||||||
|
// Start at the tail if null
|
||||||
|
Node temp = startRegion == null ? head : quickMap.get( startRegion );
|
||||||
|
// Look for a region that is less than or equal to region, and insert after
|
||||||
|
|
||||||
|
do {
|
||||||
|
temp = temp.before;
|
||||||
|
} while ( temp.region != null && !comparator.apply( temp.region, region ) );
|
||||||
|
|
||||||
|
node.after = temp.after;
|
||||||
|
node.after.before = node;
|
||||||
|
temp.after = node;
|
||||||
|
node.before = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar to above
|
||||||
|
public RegionOld search( RegionOld region ) {
|
||||||
|
Node temp = head;
|
||||||
|
|
||||||
|
// Find the first region that is greater than or equal to region
|
||||||
|
do {
|
||||||
|
temp = temp.after;
|
||||||
|
} while ( temp.region != null && !comparator.apply( region, temp.region ) );
|
||||||
|
|
||||||
|
return temp.region;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class Node {
|
||||||
|
Node before;
|
||||||
|
Node after;
|
||||||
|
RegionOld region;
|
||||||
|
|
||||||
|
Node( RegionOld region ) {
|
||||||
|
this.region = region;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public class Face {
|
||||||
|
// An edge that is attached to this face
|
||||||
|
HalfEdge edge;
|
||||||
|
boolean inside;
|
||||||
|
|
||||||
|
public Face( HalfEdge edge ) {
|
||||||
|
this.edge = edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assign() {
|
||||||
|
HalfEdge temp = edge;
|
||||||
|
do {
|
||||||
|
temp.face = this;
|
||||||
|
temp = temp.nextLeft;
|
||||||
|
} while ( temp != edge );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public class HalfEdge {
|
||||||
|
// The half edge opposite of this one
|
||||||
|
HalfEdge opposite;
|
||||||
|
|
||||||
|
// The vertex that this half edge extends from
|
||||||
|
VertexOld origin;
|
||||||
|
// The face to the left of this edge
|
||||||
|
Face face;
|
||||||
|
|
||||||
|
// The next edge CCW around the origin
|
||||||
|
// Keeps the same origin
|
||||||
|
HalfEdge nextOrigin;
|
||||||
|
// The next edge CCW around the left face
|
||||||
|
// The destination becomes the origin
|
||||||
|
HalfEdge nextLeft;
|
||||||
|
|
||||||
|
RegionOld region;
|
||||||
|
int winding;
|
||||||
|
|
||||||
|
private HalfEdge() {
|
||||||
|
origin = new VertexOld( this );
|
||||||
|
nextOrigin = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The opposite, or symmetrical half edge to this one
|
||||||
|
public HalfEdge sym() {
|
||||||
|
return opposite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalfEdge prevOrigin() {
|
||||||
|
return sym().nextLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalfEdge prevR() {
|
||||||
|
return sym().nextOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalfEdge nextD() {
|
||||||
|
return prevR().sym();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Positive means left to right, aka the destination > origin
|
||||||
|
public boolean isPositiveEdge() {
|
||||||
|
return origin.lessThanOrEqualTo( opposite.origin );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "HalfEdge{origin=" + origin + ",opposite=" + opposite.origin + ",nextOrigin=" + nextOrigin.origin + ",nextLeft=" + nextLeft.origin + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HalfEdge makeEdge() {
|
||||||
|
// TODO Half edges are always made in pairs, should really simplify this and make it cleaner
|
||||||
|
HalfEdge main = new HalfEdge();
|
||||||
|
HalfEdge opposite = new HalfEdge();
|
||||||
|
|
||||||
|
main.opposite = opposite;
|
||||||
|
main.nextLeft = opposite;
|
||||||
|
opposite.opposite = main;
|
||||||
|
opposite.nextLeft = main;
|
||||||
|
|
||||||
|
Face face = new Face( main );
|
||||||
|
main.face = face;
|
||||||
|
opposite.face = face;
|
||||||
|
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void swapLinks( HalfEdge e1, HalfEdge e2 ) {
|
||||||
|
HalfEdge e1Next = e1.nextOrigin;
|
||||||
|
HalfEdge e2Next = e2.nextOrigin;
|
||||||
|
|
||||||
|
e1Next.sym().nextLeft = e2;
|
||||||
|
e2Next.sym().nextLeft = e1;
|
||||||
|
e1.nextOrigin = e2Next;
|
||||||
|
e2.nextOrigin = e1Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The basic operation for changing the mesh connectivity and topology. It changes the mesh so that
|
||||||
|
* original.nextOrigin = OLD( destination.nextOrigin )
|
||||||
|
* destination.nextOrigin = OLD( original.nextOrigin )
|
||||||
|
* where OLD( ... ) means the value before the splice operation
|
||||||
|
*
|
||||||
|
* This can have two effects on the vertex structure:
|
||||||
|
* if original.face == destination.face, then the face is split into two
|
||||||
|
* if original.face != destination.face, then the two faces are combined into one
|
||||||
|
* In both cases, destination.face is changed, but original.face is not.
|
||||||
|
*/
|
||||||
|
public static void splice( HalfEdge original, HalfEdge destination ) {
|
||||||
|
boolean mergeVertices = false;
|
||||||
|
boolean mergeFaces = false;
|
||||||
|
|
||||||
|
// Merging vertices
|
||||||
|
if ( original.origin != destination.origin ) {
|
||||||
|
mergeVertices = true;
|
||||||
|
HalfEdge prev = destination.origin.edge;
|
||||||
|
HalfEdge dest = prev;
|
||||||
|
do {
|
||||||
|
dest.origin = original.origin;
|
||||||
|
dest = dest.nextOrigin;
|
||||||
|
} while ( dest != prev );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merging faces
|
||||||
|
if ( original.face != destination.face ) {
|
||||||
|
mergeFaces = true;
|
||||||
|
HalfEdge prev = destination.face.edge;
|
||||||
|
HalfEdge dest = prev;
|
||||||
|
do {
|
||||||
|
dest.face = original.face;
|
||||||
|
dest = dest.nextLeft;
|
||||||
|
} while ( dest != prev );
|
||||||
|
}
|
||||||
|
|
||||||
|
swapLinks( original, destination );
|
||||||
|
|
||||||
|
if ( !mergeVertices ) {
|
||||||
|
// Splitting a vertex into two, so create
|
||||||
|
// a new vertex with destination as the edge,
|
||||||
|
// update all edges in the destination loop,
|
||||||
|
// and set the old vertex edge to this
|
||||||
|
|
||||||
|
// TODO Vertices always need a half edge, constructor?
|
||||||
|
new VertexOld( destination ).assign();
|
||||||
|
original.origin.edge = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !mergeFaces ) {
|
||||||
|
// Splitting a face/loop into two
|
||||||
|
// Similar to splitting a vertex
|
||||||
|
new Face( destination ).assign();;
|
||||||
|
original.face.edge = original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the edge. There are several cases:
|
||||||
|
* if edge.leftFace != edge.rightFace, then two faces/loops are joined into one
|
||||||
|
* else, one loop is being split into two, and the new loop will contain the opposite vertex
|
||||||
|
* This function could be implemented as two calls to splice, but it would be less efficient
|
||||||
|
*/
|
||||||
|
public static void delete( HalfEdge edge ) {
|
||||||
|
boolean mergeFaces = false;
|
||||||
|
HalfEdge sym = edge.sym();
|
||||||
|
|
||||||
|
if ( edge.face != sym.face ) {
|
||||||
|
mergeFaces = true;
|
||||||
|
HalfEdge original = edge.face.edge;
|
||||||
|
HalfEdge dest = original;
|
||||||
|
do {
|
||||||
|
dest.face = sym.face;
|
||||||
|
dest = dest.nextLeft;
|
||||||
|
} while ( dest != original );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( edge.nextOrigin == edge ) {
|
||||||
|
HalfEdge original = edge.origin.edge;
|
||||||
|
HalfEdge dest = original;
|
||||||
|
do {
|
||||||
|
dest.origin = null;
|
||||||
|
dest = dest.nextOrigin;
|
||||||
|
} while ( dest != original );
|
||||||
|
} else {
|
||||||
|
sym.face.edge = edge.prevOrigin();
|
||||||
|
edge.origin.edge = edge.nextOrigin;
|
||||||
|
|
||||||
|
splice( edge, edge.prevOrigin() );
|
||||||
|
if ( !mergeFaces ) {
|
||||||
|
// Creating a new face
|
||||||
|
new Face( edge ).assign();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( sym.nextOrigin == sym ) {
|
||||||
|
sym.face.assign();
|
||||||
|
sym.origin.assign();
|
||||||
|
} else {
|
||||||
|
edge.face.edge = sym.prevOrigin();
|
||||||
|
sym.origin.edge = sym.nextOrigin;
|
||||||
|
splice( sym, sym.prevOrigin() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HalfEdge addEdgeVertex( HalfEdge orig ) {
|
||||||
|
HalfEdge edge = makeEdge();
|
||||||
|
HalfEdge sym = edge.sym();
|
||||||
|
|
||||||
|
swapLinks( edge, orig.nextLeft );
|
||||||
|
|
||||||
|
edge.origin = orig.sym().origin;
|
||||||
|
|
||||||
|
VertexOld vert = new VertexOld( sym );
|
||||||
|
HalfEdge dest = sym;
|
||||||
|
do {
|
||||||
|
dest.origin = vert;
|
||||||
|
dest = dest.nextOrigin;
|
||||||
|
} while ( dest != sym );
|
||||||
|
sym.face = orig.face;
|
||||||
|
edge.face = orig.face;
|
||||||
|
|
||||||
|
return edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HalfEdge splitEdge( HalfEdge edge ) {
|
||||||
|
HalfEdge tempEdge = addEdgeVertex( edge );
|
||||||
|
HalfEdge sym = tempEdge.sym();
|
||||||
|
|
||||||
|
swapLinks( edge.sym(), edge.sym().prevOrigin() );
|
||||||
|
swapLinks( edge.sym(), sym );
|
||||||
|
|
||||||
|
edge.sym().origin = sym.origin;
|
||||||
|
sym.sym().origin.edge = sym.sym();
|
||||||
|
sym.sym().face = edge.sym().face;
|
||||||
|
|
||||||
|
return sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HalfEdge connect( HalfEdge e1, HalfEdge e2 ) {
|
||||||
|
boolean mergeFaces = false;
|
||||||
|
HalfEdge edge = makeEdge();
|
||||||
|
|
||||||
|
HalfEdge sym = edge.sym();
|
||||||
|
|
||||||
|
if ( e1.face != e2.face ) {
|
||||||
|
mergeFaces = true;
|
||||||
|
e2.face.edge = e1;
|
||||||
|
e2.face.assign();
|
||||||
|
}
|
||||||
|
|
||||||
|
swapLinks( edge, e1.nextLeft );
|
||||||
|
swapLinks( sym, e2 );
|
||||||
|
|
||||||
|
edge.origin = e1.sym().origin;
|
||||||
|
sym.origin = e2.origin;
|
||||||
|
edge.face = e1.face;
|
||||||
|
sym.face = e1.face;
|
||||||
|
|
||||||
|
e1.face.edge = sym;
|
||||||
|
|
||||||
|
if ( !mergeFaces ) {
|
||||||
|
new Face( edge ).assign();
|
||||||
|
}
|
||||||
|
|
||||||
|
return edge;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public class HalfWing {
|
||||||
|
protected HalfWing sym;
|
||||||
|
protected HalfWing prev;
|
||||||
|
protected HalfWing next;
|
||||||
|
|
||||||
|
protected Vertex origin;
|
||||||
|
|
||||||
|
public HalfWing() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
new HalfWing( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
private HalfWing( HalfWing o ) {
|
||||||
|
init();
|
||||||
|
|
||||||
|
sym = o;
|
||||||
|
o.sym = this;
|
||||||
|
|
||||||
|
next = o;
|
||||||
|
o.next = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
origin = new Vertex( this );
|
||||||
|
prev = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vertex getOrigin() {
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalfWing setOrigin( Vertex vert ) {
|
||||||
|
origin = vert;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalfWing getSym() {
|
||||||
|
return sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalfWing getPrev() {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalfWing setPrev( HalfWing wing ) {
|
||||||
|
this.prev = wing;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalfWing getNext() {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalfWing setNext( HalfWing wing ) {
|
||||||
|
this.next = wing;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vertex getDest() {
|
||||||
|
return sym.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d toVector2d() {
|
||||||
|
return getDest().getPosition().subtracted( getOrigin().getPosition() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isZero() {
|
||||||
|
return getOrigin().equals( getDest() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split this edge in half, and return the new edge
|
||||||
|
public HalfWing split() {
|
||||||
|
HalfWing wing = new HalfWing();
|
||||||
|
|
||||||
|
splice( wing, getNext() );
|
||||||
|
|
||||||
|
splice( getSym(), getNext() );
|
||||||
|
splice( getSym(), wing.getSym() );
|
||||||
|
|
||||||
|
wing.setOrigin( getDest() );
|
||||||
|
wing.getOrigin().setWing( wing );
|
||||||
|
|
||||||
|
getSym().setOrigin( wing.getDest() );
|
||||||
|
getDest().setWing( getSym() );
|
||||||
|
|
||||||
|
wing.getOrigin().update();
|
||||||
|
wing.getDest().update();
|
||||||
|
|
||||||
|
return wing.getSym();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void splice( HalfWing a, HalfWing b ) {
|
||||||
|
HalfWing ap = a.prev;
|
||||||
|
HalfWing bp = b.prev;
|
||||||
|
|
||||||
|
a.prev = bp;
|
||||||
|
b.prev = ap;
|
||||||
|
|
||||||
|
ap.sym.next = b;
|
||||||
|
bp.sym.next = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Mesh {
|
||||||
|
List< HalfEdge > edges;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public class Point {
|
||||||
|
double x, y;
|
||||||
|
|
||||||
|
public Point( double x, double y ) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
long temp;
|
||||||
|
temp = Double.doubleToLongBits(x);
|
||||||
|
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
temp = Double.doubleToLongBits(y);
|
||||||
|
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
Point other = (Point) obj;
|
||||||
|
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
|
||||||
|
return false;
|
||||||
|
if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Polygon {
|
||||||
|
List< Point > points;
|
||||||
|
|
||||||
|
public Polygon( List< Point > points ) {
|
||||||
|
this.points = points;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List< Point > getPoints() {
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public abstract class Region {
|
||||||
|
// Represents the area under a wing
|
||||||
|
public abstract boolean isInterior();
|
||||||
|
public abstract boolean equals( Object other );
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public class RegionOld {
|
||||||
|
HalfEdge upperEdge;
|
||||||
|
int windingNumber = 0;
|
||||||
|
|
||||||
|
boolean inside = false;
|
||||||
|
boolean fixUpperEdge = false;
|
||||||
|
boolean sentinel = false;
|
||||||
|
boolean dirty = false;
|
||||||
|
|
||||||
|
public void fixUpperEdge( HalfEdge edge ) {
|
||||||
|
HalfEdge.delete( upperEdge );
|
||||||
|
fixUpperEdge = false;
|
||||||
|
upperEdge = edge;
|
||||||
|
edge.region = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public class Scratch {
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
{
|
||||||
|
String t = "Vertex{x=4.25,y=0.5}\r\n" +
|
||||||
|
"Vertex{x=2.75,y=-2.5}\r\n" +
|
||||||
|
"Vertex{x=2.2045454545454546,y=-0.8636363636363638}\r\n" +
|
||||||
|
"Vertex{x=1.75,y=0.5}\r\n" +
|
||||||
|
"Vertex{x=1.25,y=-1.5}\r\n" +
|
||||||
|
"Vertex{x=1.25,y=2.0}\r\n" +
|
||||||
|
"Vertex{x=1.0,y=-1.0}\r\n" +
|
||||||
|
"Vertex{x=1.0,y=0.0}\r\n" +
|
||||||
|
"Vertex{x=1.0,y=0.5}\r\n" +
|
||||||
|
"Vertex{x=1.0,y=1.0}\r\n" +
|
||||||
|
"Vertex{x=1.0,y=1.25}\r\n" +
|
||||||
|
"Vertex{x=1.0,y=3.0}\r\n" +
|
||||||
|
"Vertex{x=0.9166666666666666,y=1.0}\r\n" +
|
||||||
|
"Vertex{x=0.75,y=0.5}\r\n" +
|
||||||
|
"Vertex{x=0.7222222222222222,y=0.41666666666666674}\r\n" +
|
||||||
|
"Vertex{x=0.6666666666666667,y=0.5}\r\n" +
|
||||||
|
"Vertex{x=0.5833333333333333,y=0.0}\r\n" +
|
||||||
|
"Vertex{x=0.5,y=-1.0}\r\n" +
|
||||||
|
"Vertex{x=0.33333333333333337,y=1.0}\r\n" +
|
||||||
|
"Vertex{x=0.2954545454545454,y=-0.8636363636363636}\r\n" +
|
||||||
|
"Vertex{x=0.24999999999999994,y=-1.0}\r\n" +
|
||||||
|
"Vertex{x=0.0,y=-2.0}\r\n" +
|
||||||
|
"Vertex{x=0.0,y=1.5}\r\n" +
|
||||||
|
"Vertex{x=-0.050000000000000024,y=-1.9}\r\n" +
|
||||||
|
"Vertex{x=-0.25,y=-2.5}\r\n" +
|
||||||
|
"Vertex{x=-0.33333333333333337,y=1.0}\r\n" +
|
||||||
|
"Vertex{x=-0.5,y=-1.0}\r\n" +
|
||||||
|
"Vertex{x=-0.6666666666666667,y=0.5}\r\n" +
|
||||||
|
"Vertex{x=-0.9999999999999999,y=-2.220446049250313E-16}\r\n" +
|
||||||
|
"Vertex{x=-1.0,y=-1.0}\r\n" +
|
||||||
|
"Vertex{x=-1.0,y=0.0}\r\n" +
|
||||||
|
"Vertex{x=-1.0,y=0.5}\r\n" +
|
||||||
|
"Vertex{x=-1.0,y=1.0}\r\n" +
|
||||||
|
"Vertex{x=-1.0,y=2.0}\r\n" +
|
||||||
|
"Vertex{x=-1.0,y=3.0}\r\n" +
|
||||||
|
"Vertex{x=-1.75,y=0.5}\r\n" +
|
||||||
|
"Vertex{x=-2.0,y=-1.0}\r\n" +
|
||||||
|
"Vertex{x=-2.0,y=0.0}\r\n" +
|
||||||
|
"Vertex{x=-2.0,y=1.0}\r\n" +
|
||||||
|
"Vertex{x=-2.0,y=2.0}\r\n" +
|
||||||
|
"Vertex{x=-5.0,y=0.0}\r\n" +
|
||||||
|
"Vertex{x=-5.0,y=1.0}";
|
||||||
|
String[] split = t.split( "\r\n" );
|
||||||
|
for ( int i = split.length - 1; i >= 0; i-- ) {
|
||||||
|
System.out.println( split[ i ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// {
|
||||||
|
// System.out.println( new Vector2d( 1, 0 ).cross( new Vector2d( 1, 1 ) ) );
|
||||||
|
//
|
||||||
|
// HalfWing toInsert = new HalfWing();
|
||||||
|
// HalfWing first = new HalfWing();
|
||||||
|
//
|
||||||
|
// toInsert.getOrigin().setPosition( new Vector2d( -.95454545454, -1.36363636 ) );
|
||||||
|
// toInsert.getDest().setPosition( new Vector2d( 0, -2 ) );
|
||||||
|
// first.getOrigin().setPosition( new Vector2d( -1.5, -3 ) );
|
||||||
|
// first.getDest().setPosition( new Vector2d( 0, -2 ) );
|
||||||
|
//
|
||||||
|
// toInsert.getOrigin().setPosition( new Vector2d( 0, 0 ) );
|
||||||
|
// toInsert.getDest().setPosition( new Vector2d( 2, 0 ) );
|
||||||
|
// first.getOrigin().setPosition( new Vector2d( 1, 1 ) );
|
||||||
|
// first.getDest().setPosition( new Vector2d( 2, 3 ) );
|
||||||
|
//
|
||||||
|
// System.out.println( greaterThanOrEqualTo( toInsert, first ) );
|
||||||
|
// }
|
||||||
|
// {
|
||||||
|
// Point[] points = {
|
||||||
|
// new Point( 1, -1 ),
|
||||||
|
// new Point( 1, 1 ),
|
||||||
|
// new Point( -1, 1 ),
|
||||||
|
// new Point( 0, 1 ),
|
||||||
|
// new Point( 1, 1 ),
|
||||||
|
// new Point( 0, -1 )
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// Vertex v = null;
|
||||||
|
// for ( Point point : points ) {
|
||||||
|
// HalfWing edge = new HalfWing();
|
||||||
|
// edge.getDest().setPosition( new Vector2d( point.x, point.y ) );
|
||||||
|
// if ( v == null ) {
|
||||||
|
// v = edge.getOrigin();
|
||||||
|
// } else {
|
||||||
|
// HalfWing.splice( edge, v.getWing() );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// v.update();
|
||||||
|
//
|
||||||
|
// System.out.println( "Before sort" );
|
||||||
|
// HalfWing edge = v.getWing();
|
||||||
|
// do {
|
||||||
|
// System.out.println( edge.getDest() );
|
||||||
|
// edge = edge.getPrev();
|
||||||
|
// } while ( edge != v.getWing() );
|
||||||
|
//
|
||||||
|
// v.sort();
|
||||||
|
//
|
||||||
|
// System.out.println( "After sort" );
|
||||||
|
// edge = v.getWing();
|
||||||
|
// do {
|
||||||
|
// System.out.println( edge.getDest() );
|
||||||
|
// edge = edge.getPrev();
|
||||||
|
// } while ( edge != v.getWing() );
|
||||||
|
// }
|
||||||
|
// {
|
||||||
|
// HalfWing a = new HalfWing();
|
||||||
|
// HalfWing b = new HalfWing();
|
||||||
|
//
|
||||||
|
// b.getSym().setOrigin( a.getDest() );
|
||||||
|
//
|
||||||
|
// a.getOrigin().setPosition( new Vector2d( -1, 1 ) );
|
||||||
|
// a.getSym().getOrigin().setPosition( new Vector2d( 1, 1 ) );
|
||||||
|
// b.getOrigin().setPosition( new Vector2d( 1, 0 ) );
|
||||||
|
//
|
||||||
|
// System.out.println( greaterThanOrEqualTo( b, a ) );
|
||||||
|
// }
|
||||||
|
// {
|
||||||
|
// Vertex v = null;
|
||||||
|
// for ( int i = 0; i < 100; i++ ) {
|
||||||
|
// double deg = Math.random() * 360;
|
||||||
|
// double dist = Math.random() * 100 + 100;
|
||||||
|
// double x = dist * Math.cos( Math.toRadians( deg ) );
|
||||||
|
// double y = dist * Math.sin( Math.toRadians( deg ) );
|
||||||
|
// HalfWing edge = new HalfWing();
|
||||||
|
// edge.getDest().setPosition( new Vector2d( x, y ) );
|
||||||
|
// if ( v == null ) {
|
||||||
|
// v = edge.getOrigin();
|
||||||
|
// } else {
|
||||||
|
// HalfWing.splice( edge, v.getWing() );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// v.update();
|
||||||
|
//
|
||||||
|
// System.out.println( "Before sort" );
|
||||||
|
// HalfWing edge = v.getWing();
|
||||||
|
// do {
|
||||||
|
// System.out.println( edge.getDest() );
|
||||||
|
// edge = edge.getPrev();
|
||||||
|
// } while ( edge != v.getWing() );
|
||||||
|
//
|
||||||
|
// v.sort();
|
||||||
|
//
|
||||||
|
// System.out.println( "After sort" );
|
||||||
|
// edge = v.getWing();
|
||||||
|
// do {
|
||||||
|
// System.out.println( edge.getDest() );
|
||||||
|
// edge = edge.getPrev();
|
||||||
|
// } while ( edge != v.getWing() );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// {
|
||||||
|
// Vector2d a1 = new Vector2d( 0, 0 );
|
||||||
|
// Vector2d a2 = new Vector2d( 0, 5 );
|
||||||
|
// Vector2d b1 = new Vector2d( 0, 0 );
|
||||||
|
// Vector2d b2 = new Vector2d( 0, 5 );
|
||||||
|
// System.out.println( isGreaterThan( a1, a2, b1, b2 ) );
|
||||||
|
// }
|
||||||
|
// HalfEdge upper = HalfEdge.makeEdge();
|
||||||
|
// HalfEdge lower = HalfEdge.makeEdge();
|
||||||
|
//
|
||||||
|
// upper.sym().origin = lower.sym().origin;
|
||||||
|
// upper.sym().origin.x = -1;
|
||||||
|
// upper.sym().origin.y = -1;
|
||||||
|
//
|
||||||
|
// upper.origin.x = 1;
|
||||||
|
// upper.origin.y = 1;
|
||||||
|
// lower.origin.x = 2;
|
||||||
|
// lower.origin.y = 2;
|
||||||
|
//
|
||||||
|
// System.out.println( "Right spliced? " + checkForRightSplice( upper, lower ) );
|
||||||
|
// System.out.println( upper );
|
||||||
|
// System.out.println( lower );
|
||||||
|
// System.out.println( upper.prevOrigin() );
|
||||||
|
|
||||||
|
// {
|
||||||
|
// Vertex v1 = new Vertex( null );
|
||||||
|
// v1.x = 0;
|
||||||
|
// v1.y = 0;
|
||||||
|
// Vertex v2 = new Vertex( null );
|
||||||
|
// v2.x = 5;
|
||||||
|
// v2.y = 0;
|
||||||
|
// Vertex v3 = new Vertex( null );
|
||||||
|
// v3.x = 1;
|
||||||
|
// v3.y = 1;
|
||||||
|
// Vertex v4 = new Vertex( null );
|
||||||
|
// v4.x = 6;
|
||||||
|
// v4.y = -4;
|
||||||
|
//
|
||||||
|
// System.out.println( "Colinear and overlapping" );
|
||||||
|
// System.out.println( Tessellator.intersection( v1, v2, v3, v4 ).x );
|
||||||
|
// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).x );
|
||||||
|
// }
|
||||||
|
// {
|
||||||
|
// Vertex v1 = new Vertex( null );
|
||||||
|
// v1.x = 0;
|
||||||
|
// v1.y = 0;
|
||||||
|
// Vertex v2 = new Vertex( null );
|
||||||
|
// v2.x = 5;
|
||||||
|
// v2.y = 0;
|
||||||
|
// Vertex v3 = new Vertex( null );
|
||||||
|
// v3.x = 6;
|
||||||
|
// v3.y = 0;
|
||||||
|
// Vertex v4 = new Vertex( null );
|
||||||
|
// v4.x = 10;
|
||||||
|
// v4.y = 0;
|
||||||
|
//
|
||||||
|
// System.out.println( "Colinear but not overlapping" );
|
||||||
|
// System.out.println( Tessellator.intersection( v1, v2, v3, v4 ).x );
|
||||||
|
// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).x );
|
||||||
|
// }
|
||||||
|
// {
|
||||||
|
// Vertex v1 = new Vertex( null );
|
||||||
|
// v1.x = 0;
|
||||||
|
// v1.y = 3;
|
||||||
|
// Vertex v2 = new Vertex( null );
|
||||||
|
// v2.x = 5;
|
||||||
|
// v2.y = 3;
|
||||||
|
// Vertex v3 = new Vertex( null );
|
||||||
|
// v3.x = -6;
|
||||||
|
// v3.y = 6;
|
||||||
|
// Vertex v4 = new Vertex( null );
|
||||||
|
// v4.x = 2.5;
|
||||||
|
// v4.y = 6;
|
||||||
|
//
|
||||||
|
// System.out.println( "Parallel and overlapping but not colinear" );
|
||||||
|
// System.out.println( Tessellator.intersection( v1, v2, v3, v4 ).x );
|
||||||
|
// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).x );
|
||||||
|
// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).y );
|
||||||
|
// }
|
||||||
|
// {
|
||||||
|
// Vertex v1 = new Vertex( null );
|
||||||
|
// v1.x = 0;
|
||||||
|
// v1.y = 3;
|
||||||
|
// Vertex v2 = new Vertex( null );
|
||||||
|
// v2.x = 5;
|
||||||
|
// v2.y = 3;
|
||||||
|
// Vertex v3 = new Vertex( null );
|
||||||
|
// v3.x = -100;
|
||||||
|
// v3.y = 6;
|
||||||
|
// Vertex v4 = new Vertex( null );
|
||||||
|
// v4.x = -1;
|
||||||
|
// v4.y = 6;
|
||||||
|
//
|
||||||
|
// System.out.println( "Parallel but not overlapping nor colinear" );
|
||||||
|
// System.out.println( Tessellator.intersection( v1, v2, v3, v4 ).x );
|
||||||
|
// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).x );
|
||||||
|
// System.out.println( Util.closestPoint( v1, v2, v3, v4 ).y );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// {
|
||||||
|
// HalfEdge root = HalfEdge.makeEdge();
|
||||||
|
// root.origin.y = -1;
|
||||||
|
// HalfEdge next = HalfEdge.makeEdge();
|
||||||
|
// next.sym().origin.x = 1;
|
||||||
|
// HalfEdge.swapLinks( root.sym(), next );
|
||||||
|
// HalfEdge newEdge = HalfEdge.addEdgeVertex( root );
|
||||||
|
// newEdge.sym().origin.x = -1;
|
||||||
|
// System.out.println( root );
|
||||||
|
// System.out.println( root.nextLeft );
|
||||||
|
// System.out.println( root.nextLeft.sym() );
|
||||||
|
// System.out.println( root.nextLeft.nextLeft.nextLeft.sym() );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// System.out.println( "---" );
|
||||||
|
// {
|
||||||
|
// HalfEdge root = HalfEdge.makeEdge();
|
||||||
|
// root.origin.y = -1;
|
||||||
|
// HalfEdge next = HalfEdge.makeEdge();
|
||||||
|
// next.sym().origin.x = 1;
|
||||||
|
// HalfEdge.swapLinks( root.sym(), next );
|
||||||
|
// HalfEdge newEdge = HalfEdge.splitEdge( root );
|
||||||
|
// newEdge.origin.x = -1;
|
||||||
|
// System.out.println( root );
|
||||||
|
// System.out.println( root.nextLeft );
|
||||||
|
// System.out.println( root.nextLeft.nextLeft );
|
||||||
|
// System.out.println( root.nextLeft.nextLeft.nextLeft );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean greaterThanOrEqualTo( HalfWing a, HalfWing b ) {
|
||||||
|
// Assume each region is marked by an upper edge going from right to left, up to down
|
||||||
|
if ( a.isZero() || b.isZero() ) {
|
||||||
|
throw new IllegalStateException( "Zero length edge detected" );
|
||||||
|
}
|
||||||
|
// if ( !( a.isPositive() && b.isPositive() ) ) {
|
||||||
|
// throw new IllegalStateException( "Negative edge detected" );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// a1 < a2
|
||||||
|
Vector2d a1 = a.getOrigin().getPosition();
|
||||||
|
Vector2d a2 = a.getDest().getPosition();
|
||||||
|
// b1 < b2
|
||||||
|
Vector2d b1 = b.getOrigin().getPosition();
|
||||||
|
Vector2d b2 = b.getDest().getPosition();
|
||||||
|
|
||||||
|
if ( a1.equals( b1 ) ) {
|
||||||
|
return b2.subtracted( b1 ).cross( a2.subtracted( a1 ) ) >= 0;
|
||||||
|
} else if ( a1.x < b1.x ) {
|
||||||
|
System.out.println( "LESS A" );
|
||||||
|
return b1.subtracted( a1 ).cross( a2.subtracted( a1 ) ) >= 0;
|
||||||
|
} else if ( a1.x > b1.x ) {
|
||||||
|
System.out.println( "MORAY" );
|
||||||
|
return b2.subtracted( b1 ).cross( a1.subtracted( b1 ) ) >= 0;
|
||||||
|
} else {
|
||||||
|
return a1.y > b1.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isGreaterThan( Vector2dOld a1, Vector2dOld a2, Vector2dOld b1, Vector2dOld b2 ) {
|
||||||
|
// Invariants:
|
||||||
|
// a1 < a2
|
||||||
|
// b1 < b2
|
||||||
|
// a.length > 0
|
||||||
|
// b.length > 0
|
||||||
|
// a and b do not intersect
|
||||||
|
// a and b do not overlap
|
||||||
|
|
||||||
|
if ( a1.equals( b1 ) ) {
|
||||||
|
return b2.subtract( b1 ).cross( a2.subtract( a1 ) ) >= 0;
|
||||||
|
} else if ( a1.x < b1.x ) {
|
||||||
|
return b1.subtract( a1 ).cross( a2.subtract( a1 ) ) >= 0;
|
||||||
|
} else if ( a1.x > b1.x ) {
|
||||||
|
return a1.subtract( b1 ).cross( b2.subtract( b1 ) ) >= 0;
|
||||||
|
} else {
|
||||||
|
return a1.y > b1.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkForRightSplice( HalfEdge upEdge, HalfEdge downEdge) {
|
||||||
|
// Check if the right vertex of the upper edge is to the left or below the lower edge's right vertex
|
||||||
|
if ( upEdge.origin.lessThanOrEqualTo( downEdge.origin ) ) {
|
||||||
|
// Check if the right vertex is above the lower edge
|
||||||
|
if ( upEdge.origin.compareTo( downEdge.sym().origin, downEdge.origin ) > 0 ) {
|
||||||
|
// If so, no splicing is necessary
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the right vertex of the upper edge is not equal to the right vertex of the lower edge
|
||||||
|
if ( !upEdge.origin.equals( downEdge.origin ) ) {
|
||||||
|
// Split the lower edge in two, with the new edge on the right
|
||||||
|
HalfEdge.splitEdge( downEdge.sym() );
|
||||||
|
// Move the right vertex of the lower edge to the
|
||||||
|
HalfEdge.splice( upEdge, downEdge.prevOrigin() );
|
||||||
|
|
||||||
|
} else if ( upEdge.origin != downEdge.origin ) {
|
||||||
|
// Merge the two vertices if they are not already the same
|
||||||
|
HalfEdge.splice( downEdge.prevOrigin(), upEdge );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if the lower edge's right vertex is on or below the upper edge
|
||||||
|
if ( downEdge.origin.compareTo( upEdge.sym().origin, upEdge.origin ) <= 0 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the upper edge
|
||||||
|
HalfEdge.splitEdge( upEdge.sym() );
|
||||||
|
// Move the upper edge to the lower edge's vertex
|
||||||
|
HalfEdge.splice( downEdge.prevOrigin(), upEdge );
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
1842
src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4j.java
Normal file
1842
src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Tess4j.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,263 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.RegionSimple.GluWindingRule;
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.region.WindingRuleSimple;
|
||||||
|
|
||||||
|
public class Tess4jTest extends JPanel {
|
||||||
|
JFrame f;
|
||||||
|
|
||||||
|
private int windowWidth = 1200;
|
||||||
|
private int windowHeight = 1000;
|
||||||
|
|
||||||
|
private int centerX = 600;
|
||||||
|
private int centerY = 400;
|
||||||
|
|
||||||
|
private Graphics g;
|
||||||
|
|
||||||
|
private int scale = 100;
|
||||||
|
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
new Tess4jTest();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tess4jTest() {
|
||||||
|
f = new JFrame( "Drawing Board" );
|
||||||
|
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
|
||||||
|
|
||||||
|
f.add( this );
|
||||||
|
|
||||||
|
f.setSize( windowWidth, windowHeight );
|
||||||
|
f.setVisible( true );
|
||||||
|
f.setResizable( true );
|
||||||
|
|
||||||
|
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawPoint( double x, double y ) {
|
||||||
|
g.fillRect( ( int ) x * scale, ( int ) y * scale, scale, scale );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector2d rotate( Vector2d a, double angle ) {
|
||||||
|
double cos = Math.cos( angle );
|
||||||
|
double sin = Math.sin( angle );
|
||||||
|
double ax = a.getX() * cos - a.getY() * sin;
|
||||||
|
double ay = a.getX() * sin + a.getY() * cos;
|
||||||
|
return new Vector2d( ax, ay );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paintComponent( Graphics g ) {
|
||||||
|
super.paintComponent( g );
|
||||||
|
this.g = g;
|
||||||
|
|
||||||
|
// Vertex v = null;
|
||||||
|
// for ( int i = 0; i < 100; i++ ) {
|
||||||
|
// double deg = Math.random() * 360;
|
||||||
|
// double dist = Math.random() * 100 + 100;
|
||||||
|
// double x = dist * Math.cos( Math.toRadians( deg ) );
|
||||||
|
// double y = dist * Math.sin( Math.toRadians( deg ) );
|
||||||
|
// HalfWing edge = new HalfWing();
|
||||||
|
// edge.getDest().setPosition( new Vector2d( x, y ) );
|
||||||
|
// if ( v == null ) {
|
||||||
|
// v = edge.getOrigin();
|
||||||
|
// } else {
|
||||||
|
// HalfWing.splice( edge, v.getWing() );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// v.update();
|
||||||
|
//
|
||||||
|
// HalfWing edge = v.getWing();
|
||||||
|
// int i = 0;
|
||||||
|
// double ic = 0;
|
||||||
|
// do {
|
||||||
|
// Vector2d dest = edge.getDest().getPosition();
|
||||||
|
// g.drawLine( centerX, centerY, ( int ) dest.getX() + centerX, ( int ) dest.getY() + centerY );
|
||||||
|
// dest = dest.normalized();
|
||||||
|
// g.drawString( i++ + "", ( int ) ( dest.x * 200 * ( ic + 1.1 ) ) + centerX, ( int ) ( dest.y * 200 * ( ic + 1.1 ) ) + centerY );
|
||||||
|
// ic = ( ic + 0.07 ) % .7;
|
||||||
|
// edge = edge.getPrev();
|
||||||
|
// } while ( edge != v.getWing() );
|
||||||
|
//
|
||||||
|
// v.sort();
|
||||||
|
//
|
||||||
|
// g.drawLine( centerX + 700, centerY, centerX + 700, centerY - 400 );
|
||||||
|
// edge = v.getWing();
|
||||||
|
// i = 0;
|
||||||
|
// ic = 0;
|
||||||
|
// do {
|
||||||
|
// Vector2d dest = edge.getDest().getPosition();
|
||||||
|
// g.drawLine( centerX + 700, centerY, ( int ) dest.getX() + centerX + 700, ( int ) dest.y + centerY );
|
||||||
|
// dest = dest.normalized();
|
||||||
|
// g.drawString( i++ + "", ( int ) ( dest.x * 200 * ( ic + 1.1 ) ) + centerX + 700, ( int ) ( dest.y * 200 * ( ic + 1.1 ) ) + centerY );
|
||||||
|
// ic = ( ic + 0.07 ) % .7;
|
||||||
|
// edge = edge.getPrev();
|
||||||
|
// } while ( edge != v.getWing() );
|
||||||
|
|
||||||
|
// Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
|
||||||
|
Tess4j< RegionSimple > tess4j = new Tess4j< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); }, new Vector2dComparator() {
|
||||||
|
@Override
|
||||||
|
public double compareX( Vector2d a, Vector2d b ) {
|
||||||
|
a = rotate( a, 128 );
|
||||||
|
b = rotate( b, 128 );
|
||||||
|
return a.x - b.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double compareY(Vector2d a, Vector2d b ) {
|
||||||
|
a = rotate( a, 128 );
|
||||||
|
b = rotate( b, 128 );
|
||||||
|
return b.y - a.y;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
// tess4j.addPolygon( new Polygon( Arrays.asList(
|
||||||
|
// new Point( -1, -1 ),
|
||||||
|
// new Point( 1, -1 ),
|
||||||
|
// new Point( 1, 1 ),
|
||||||
|
// new Point( -1, 1 )
|
||||||
|
// ) ), new WindingRuleSimple() );
|
||||||
|
// tess4j.addPolygon( new Polygon( Arrays.asList(
|
||||||
|
// new Point( -1, 1 ),
|
||||||
|
// new Point( 1, 1 ),
|
||||||
|
// new Point( 1, 3 ),
|
||||||
|
// new Point( -1, 3 )
|
||||||
|
// ) ), new WindingRuleSimple() );
|
||||||
|
// tess4j.addPolygon( new Polygon( Arrays.asList(
|
||||||
|
// new Point( -2, -1 ),
|
||||||
|
// new Point( -1, -1 ),
|
||||||
|
// new Point( -1, 2 ),
|
||||||
|
// new Point( -2, 2 )
|
||||||
|
// ) ), new WindingRuleSimple() );
|
||||||
|
// tess4j.addPolygon( new Polygon( Arrays.asList(
|
||||||
|
// new Point( -5, 0 ),
|
||||||
|
// new Point( -2, 0 ),
|
||||||
|
// new Point( -2, 1 ),
|
||||||
|
// new Point( -5, 1 )
|
||||||
|
// ) ), new WindingRuleSimple() );
|
||||||
|
// tess4j.addPolygon( new Polygon( Arrays.asList(
|
||||||
|
// new Point( -1.75, .5 ),
|
||||||
|
// new Point( 4.25, .5 ),
|
||||||
|
// new Point( -0.25, -2.5 ),
|
||||||
|
// new Point( 1.25, 2 ),
|
||||||
|
// new Point( 2.75, -2.5 )
|
||||||
|
// ) ), new WindingRuleSimple() );
|
||||||
|
// tess4j.addPolygon( new Polygon( Arrays.asList(
|
||||||
|
// new Point( -4, -2 ),
|
||||||
|
// new Point( -1, -2 ),
|
||||||
|
// new Point( -1, 1.5 ),
|
||||||
|
// new Point( -4, 1.5 )
|
||||||
|
// ) ), new WindingRuleSimple() );
|
||||||
|
// tess4j.addPolygon( new Polygon( Arrays.asList(
|
||||||
|
// new Point( -1, 0 ),
|
||||||
|
// new Point( 0, 1.5 ),
|
||||||
|
// new Point( 1, 0 )
|
||||||
|
// ) ), new WindingRuleSimple() );
|
||||||
|
// tess4j.addPolygon( new Polygon( Arrays.asList(
|
||||||
|
// new Point( 1, 0 ),
|
||||||
|
// new Point( 0, -2 ),
|
||||||
|
// new Point( -1, 0 )
|
||||||
|
// ) ), new WindingRuleSimple() );
|
||||||
|
for ( int i = 0; i < 4; i++ ) {
|
||||||
|
for ( int j = 0; j < 4; j++ ) {
|
||||||
|
tess4j.addPolygon( new Polygon( Arrays.asList(
|
||||||
|
new Point( i, j ),
|
||||||
|
new Point( i, j + 1 ),
|
||||||
|
new Point( i + 1, j + 1 ),
|
||||||
|
new Point( i + 1, j )
|
||||||
|
) ), new WindingRuleSimple() );
|
||||||
|
// tess4j.addPolygon( new Polygon( Arrays.asList(
|
||||||
|
// new Point( i, j ),
|
||||||
|
// new Point( i, j + 1 ),
|
||||||
|
// new Point( i + 1, j ),
|
||||||
|
// new Point( i + 1, j + 1 )
|
||||||
|
// ) ), new WindingRuleSimple() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tess4j.tessellate();
|
||||||
|
|
||||||
|
List< Polygon > polygons = tess4j.getPolygons();
|
||||||
|
|
||||||
|
System.out.println( "Polygon count" );
|
||||||
|
System.out.println( polygons.size() );
|
||||||
|
for ( Polygon p : polygons ) {
|
||||||
|
System.out.println( p.points.size() );
|
||||||
|
int size = p.points.size();
|
||||||
|
int[] xPoints = new int[ size ];
|
||||||
|
int[] yPoints = new int[ size ];
|
||||||
|
|
||||||
|
for ( int i = 0; i < p.points.size(); i++ ) {
|
||||||
|
Point point = p.points.get( i );
|
||||||
|
|
||||||
|
System.out.println( point.x + ", " + point.y );
|
||||||
|
Vector2d pVec = new Vector2d( point.x, point.y );
|
||||||
|
pVec = rotate( pVec, 0 );
|
||||||
|
point = new Point( pVec.getX(), pVec.getY() );
|
||||||
|
|
||||||
|
xPoints[ i ] = ( int ) ( point.x * scale ) + centerX;
|
||||||
|
yPoints[ i ] = 100 - ( int ) ( point.y * scale ) + centerY;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setColor( new Color( ( int ) ( Math.random() * 0xFFFFFF ) ) );
|
||||||
|
g.fillPolygon( xPoints, yPoints, size );
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setColor( Color.BLACK );
|
||||||
|
for ( Polygon p : polygons ) {
|
||||||
|
for ( Point point : p.points ) {
|
||||||
|
Vector2d pVec = new Vector2d( point.x, point.y );
|
||||||
|
pVec = rotate( pVec, 0 );
|
||||||
|
point = new Point( pVec.getX(), pVec.getY() );
|
||||||
|
|
||||||
|
double diff = scale * .05;
|
||||||
|
g.drawRect( ( int ) ( point.x * scale ) + centerX - ( int ) diff, 100 - ( int ) ( point.y * scale ) + centerY - ( int ) diff, ( int ) ( diff * 2 ), ( int ) ( diff * 2 ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.drawLine( centerX, 0, centerX, 2000 );
|
||||||
|
g.drawLine( 0, centerY + 100, 2000, centerY + 100 );
|
||||||
|
|
||||||
|
// Point[] points = {
|
||||||
|
// new Point( 0, -2 ),
|
||||||
|
// new Point( 1.5, -3.0 ),
|
||||||
|
// new Point( 0.0, -2.0 ),
|
||||||
|
// new Point( 0.9545454545454546, -1.3636363636363638 ),
|
||||||
|
// new Point( -0.8333333333333334, -1.0 ),
|
||||||
|
// new Point( 0.8333333333333334, -1.0 ),
|
||||||
|
// new Point( -0.5, 0.0 ),
|
||||||
|
// new Point( 0.5, 0.0 ),
|
||||||
|
// new Point( -0.16666666666666669, 1.0 ),
|
||||||
|
// new Point( 0.16666666666666669, 1.0 ),
|
||||||
|
// new Point( -0.16666666666666669, 1.0 ),
|
||||||
|
// new Point( 1, 1 ),
|
||||||
|
// new Point( -0.16666666666666669, 1.0 ),
|
||||||
|
// new Point( 0, 1.5 ),
|
||||||
|
// new Point( 0, 1.5 ),
|
||||||
|
// new Point( 0.16666666666666669, 1.0 ),
|
||||||
|
// new Point( -1, 2 ),
|
||||||
|
// new Point( 1, 2 ),
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// int size = points.length >> 1;
|
||||||
|
// for ( int i = 0; i < size;i++ ) {
|
||||||
|
// Point a = points[ i << 1 ];
|
||||||
|
// Point b = points[ ( i << 1 ) + 1 ];
|
||||||
|
//
|
||||||
|
// g.drawLine( ( int ) ( a.x * scale ) + centerX, ( int ) ( a.y * scale ) + centerY, ( int ) ( b.x * scale ) + centerX, ( int ) ( b.y * scale ) + centerY );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,506 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import org.joml.Math;
|
||||||
|
|
||||||
|
public class Tessellator {
|
||||||
|
// TODO Insecure
|
||||||
|
private static VertexOld vertex;
|
||||||
|
private static EdgeDict dict;
|
||||||
|
private static Queue< VertexOld > eventQueue;
|
||||||
|
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
List< Polygon > polygons = new ArrayList< Polygon >();
|
||||||
|
polygons.add( new Polygon( Arrays.asList(
|
||||||
|
new Point( -1, -1 ),
|
||||||
|
new Point( 1, -1 ),
|
||||||
|
new Point( 1, 1 ),
|
||||||
|
new Point( -1, 1 )
|
||||||
|
) ) );
|
||||||
|
tessellate( polygons );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tessellate( Collection< Polygon > shapes ) {
|
||||||
|
eventQueue = new PriorityQueue< VertexOld >();
|
||||||
|
dict = new EdgeDict( () -> { return vertex; } );
|
||||||
|
|
||||||
|
double minX = Double.MAX_VALUE;
|
||||||
|
double minY = Double.MAX_VALUE;
|
||||||
|
double maxX = Double.MIN_VALUE;
|
||||||
|
double maxY = Double.MIN_VALUE;
|
||||||
|
|
||||||
|
// Get all unique vertices that are in use
|
||||||
|
Set< VertexOld > vertexCollection = Collections.newSetFromMap( new WeakHashMap< VertexOld, Boolean >() );
|
||||||
|
for ( Polygon polygon : shapes ) {
|
||||||
|
HalfEdge edge = null;
|
||||||
|
for ( Point point : polygon.points ) {
|
||||||
|
if ( edge == null ) {
|
||||||
|
// Make a self loop with one edge and one vertex
|
||||||
|
edge = HalfEdge.makeEdge();
|
||||||
|
HalfEdge.splice( edge, edge.sym() );
|
||||||
|
} else {
|
||||||
|
HalfEdge.splitEdge( edge );
|
||||||
|
edge = edge.nextLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
edge.origin.x = point.x;
|
||||||
|
edge.origin.y = point.y;
|
||||||
|
|
||||||
|
// For now, assume reverse contours is false
|
||||||
|
edge.winding = 1;
|
||||||
|
edge.sym().winding = -1;
|
||||||
|
|
||||||
|
minX = Math.min( minX, point.x );
|
||||||
|
minY = Math.min( minY, point.y );
|
||||||
|
maxX = Math.max( maxX, point.x );
|
||||||
|
maxY = Math.max( maxY, point.y );
|
||||||
|
|
||||||
|
vertexCollection.add( edge.origin );
|
||||||
|
vertexCollection.add( edge.sym().origin );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eventQueue.addAll( vertexCollection );
|
||||||
|
|
||||||
|
// Add sentinel regions so that all polygons are between two edges/regions
|
||||||
|
addSentinel( minX, minY, maxX, maxY );
|
||||||
|
|
||||||
|
// Sweep over each vertex, from left to right, bottom to top
|
||||||
|
VertexOld current;
|
||||||
|
while ( !eventQueue.isEmpty() ) {
|
||||||
|
current = eventQueue.poll();
|
||||||
|
while ( true ) {
|
||||||
|
VertexOld next = eventQueue.peek();
|
||||||
|
// Get all vertices that overlap and merge them together
|
||||||
|
if ( next == null || !current.equals( next ) ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
next = eventQueue.poll();
|
||||||
|
HalfEdge.splice( current.edge, next.edge );
|
||||||
|
}
|
||||||
|
System.out.println( "SCANNING " + current );
|
||||||
|
sweepEvent( current );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addSentinel( double minX, double minY, double maxX, double maxY ) {
|
||||||
|
double width = ( maxX - minX ) + 0.01;
|
||||||
|
double height = ( maxY - minY ) + 0.01;
|
||||||
|
|
||||||
|
double lowerX = minX - width;
|
||||||
|
double lowerY = minY - height;
|
||||||
|
double upperX = maxX + width;
|
||||||
|
double upperY = maxY + height;
|
||||||
|
|
||||||
|
addSentinelRegion( lowerX, upperX, lowerY );
|
||||||
|
addSentinelRegion( lowerX, upperX, upperY );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addSentinelRegion( double startX, double endX, double y ) {
|
||||||
|
HalfEdge edge = HalfEdge.makeEdge();
|
||||||
|
|
||||||
|
edge.origin.x = endX;
|
||||||
|
edge.origin.y = y;
|
||||||
|
edge.sym().origin.x = startX;
|
||||||
|
edge.sym().origin.y = y;
|
||||||
|
|
||||||
|
vertex = edge.sym().origin;
|
||||||
|
|
||||||
|
RegionOld region = new RegionOld();
|
||||||
|
region.upperEdge = edge;
|
||||||
|
region.inside = false;
|
||||||
|
region.sentinel = true;
|
||||||
|
dict.insert( region );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sweepEvent( VertexOld vert ) {
|
||||||
|
vertex = vert;
|
||||||
|
|
||||||
|
// Find the region that this vertex belongs to
|
||||||
|
HalfEdge edge = vert.edge;
|
||||||
|
while ( edge.region == null ) {
|
||||||
|
edge = edge.nextOrigin;
|
||||||
|
if ( edge == vert.edge ) {
|
||||||
|
// This is a new vertex which is not associated with any region
|
||||||
|
// Connect it to something
|
||||||
|
connectLeftVertex( vert );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void connectLeftVertex( VertexOld vert ) {
|
||||||
|
RegionOld tmpRegion = new RegionOld();
|
||||||
|
tmpRegion.upperEdge = vert.edge.sym();
|
||||||
|
|
||||||
|
// Find the regions immediately above and below this vertex
|
||||||
|
RegionOld up = dict.search( tmpRegion );
|
||||||
|
RegionOld lower = dict.below( up );
|
||||||
|
if ( lower == null ) {
|
||||||
|
// Coplanar polygon
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Get the edges associated with both regions
|
||||||
|
HalfEdge upperEdge = up.upperEdge;
|
||||||
|
HalfEdge lowerEdge = lower.upperEdge;
|
||||||
|
|
||||||
|
// If the vertex lies on the upper edge... ???
|
||||||
|
if ( vert.compareTo( upperEdge.sym().origin, upperEdge.origin ) == 0 ) {
|
||||||
|
connectLeftDegenerate( up, vert );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the higher region
|
||||||
|
RegionOld region = lowerEdge.sym().origin.lessThanOrEqualTo( upperEdge.sym().origin ) ? up : lower;
|
||||||
|
if ( region.inside || region.fixUpperEdge ) {
|
||||||
|
HalfEdge edge;
|
||||||
|
if ( region == up ) {
|
||||||
|
edge = HalfEdge.connect( vert.edge.sym(), upperEdge.nextLeft );
|
||||||
|
} else {
|
||||||
|
edge = HalfEdge.connect( lowerEdge.nextD(), vert.edge ).sym();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( region.fixUpperEdge ) {
|
||||||
|
region.fixUpperEdge( edge );
|
||||||
|
} else {
|
||||||
|
computeWinding( addRegionBelow( up, edge ) );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addRightEdges( up, vertex.edge, vertex.edge, null, true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addRightEdges( RegionOld region, HalfEdge first, HalfEdge last, HalfEdge topLeft, boolean clean ) {
|
||||||
|
// Add all edges from first to last
|
||||||
|
// The last edge to be inserted is the edge CCW of last
|
||||||
|
|
||||||
|
System.out.println( "Region is " + region.upperEdge );
|
||||||
|
HalfEdge edge = first;
|
||||||
|
do {
|
||||||
|
// Create regions for all edges starting from first, before last, in CCW fashion
|
||||||
|
System.out.println( "Inserting region for edge " + edge );
|
||||||
|
// The region defining edge is always going to be right to left
|
||||||
|
addRegionBelow( region, edge.sym() );
|
||||||
|
edge = edge.nextOrigin;
|
||||||
|
} while ( edge != last );
|
||||||
|
|
||||||
|
// Get the most CCW edge that is to the right of the sweep line
|
||||||
|
if ( topLeft == null ) {
|
||||||
|
topLeft = dict.below( region ).upperEdge.prevR();
|
||||||
|
System.out.println( "Top left region is " + topLeft );
|
||||||
|
}
|
||||||
|
RegionOld previousRegion = region;
|
||||||
|
HalfEdge previousEdge = topLeft;
|
||||||
|
|
||||||
|
// Update winding and fix any problems with ordering
|
||||||
|
boolean firstTime = true;
|
||||||
|
while ( true ) {
|
||||||
|
// Iterate over each region from top to bottom
|
||||||
|
RegionOld tempRegion = dict.below( previousRegion );
|
||||||
|
// Get the left to right, bottom to top edge
|
||||||
|
HalfEdge tempEdge = tempRegion.upperEdge.sym();
|
||||||
|
System.out.println( "Looping for edge " + tempEdge );
|
||||||
|
|
||||||
|
// Break if the vertex changes
|
||||||
|
if ( tempEdge.origin != previousEdge.origin ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the previous edge is equal to the previous edge
|
||||||
|
// Otherwise there's a discrepancy and it needs to be relinked accordingly
|
||||||
|
if ( tempEdge.nextOrigin != previousEdge ) {
|
||||||
|
// Unlink the edge from its current position, and relink below the previous edge
|
||||||
|
HalfEdge.splice( tempEdge.nextOrigin, tempEdge );
|
||||||
|
HalfEdge.splice( previousEdge.prevOrigin(), tempEdge );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the winding number and if it is inside
|
||||||
|
tempRegion.windingNumber = previousRegion.windingNumber - tempEdge.winding;
|
||||||
|
tempRegion.inside = tempRegion.windingNumber % 2 == 1;
|
||||||
|
|
||||||
|
previousRegion.dirty = true;
|
||||||
|
|
||||||
|
if ( !firstTime && checkForRightSplice( previousRegion ) ) {
|
||||||
|
tempEdge.winding += previousEdge.winding;
|
||||||
|
|
||||||
|
HalfEdge.delete( previousEdge );
|
||||||
|
}
|
||||||
|
|
||||||
|
firstTime = false;
|
||||||
|
previousRegion = tempRegion;
|
||||||
|
previousEdge = tempEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( clean ) {
|
||||||
|
walkDirtyRegions( previousRegion );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkForLeftSplice( RegionOld region ) {
|
||||||
|
// Check the upper and lower edge of the region for any inconsistencies
|
||||||
|
// Simiar to checkForRightSplice, but the left vertices
|
||||||
|
RegionOld below = dict.below( region );
|
||||||
|
HalfEdge upEdge = region.upperEdge;
|
||||||
|
HalfEdge downEdge = below.upperEdge;
|
||||||
|
|
||||||
|
if ( upEdge.sym().origin.lessThanOrEqualTo( downEdge.sym().origin ) ) {
|
||||||
|
if ( downEdge.sym().origin.compareTo( upEdge.sym().origin, upEdge.origin ) < 0 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lower edge is above the upper edge, so splice
|
||||||
|
dict.above( region ).dirty = true;
|
||||||
|
region.dirty = true;
|
||||||
|
|
||||||
|
HalfEdge edge = HalfEdge.splitEdge( upEdge );
|
||||||
|
HalfEdge.splice( downEdge.sym(), edge );
|
||||||
|
edge.face.inside = region.inside;
|
||||||
|
} else {
|
||||||
|
if ( upEdge.sym().origin.compareTo( downEdge.sym().origin, downEdge.origin ) > 0 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
region.dirty = true;
|
||||||
|
below.dirty = true;
|
||||||
|
HalfEdge edge = HalfEdge.splitEdge( downEdge );
|
||||||
|
HalfEdge.splice( upEdge.nextLeft, downEdge.sym() );
|
||||||
|
edge.sym().face.inside = region.inside;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkForRightSplice( RegionOld region ) {
|
||||||
|
RegionOld below = dict.below( region );
|
||||||
|
HalfEdge upEdge = region.upperEdge;
|
||||||
|
HalfEdge downEdge = below.upperEdge;
|
||||||
|
|
||||||
|
// Check if the right vertex of the upper edge is to the left or below the lower edge's right vertex
|
||||||
|
if ( upEdge.origin.lessThanOrEqualTo( downEdge.origin ) ) {
|
||||||
|
// Check if the right vertex is above the lower edge
|
||||||
|
if ( upEdge.origin.compareTo( downEdge.sym().origin, downEdge.origin ) > 0 ) {
|
||||||
|
// If so, no splicing is necessary
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the right vertex of the upper edge is not equal to the right vertex of the lower edge
|
||||||
|
if ( !upEdge.origin.equals( downEdge.origin ) ) {
|
||||||
|
// Split the lower edge in two, with the new edge on the right
|
||||||
|
HalfEdge.splitEdge( downEdge.sym() );
|
||||||
|
// Move the right vertex of the lower edge to the
|
||||||
|
HalfEdge.splice( upEdge, downEdge.prevOrigin() );
|
||||||
|
|
||||||
|
region.dirty = true;
|
||||||
|
below.dirty = true;
|
||||||
|
} else if ( upEdge.origin != downEdge.origin ) {
|
||||||
|
// Merge the two vertices if they are not already the same
|
||||||
|
eventQueue.remove( upEdge.origin );
|
||||||
|
HalfEdge.splice( downEdge.prevOrigin(), upEdge );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if the lower edge's right vertex is on or below the upper edge
|
||||||
|
if ( downEdge.origin.compareTo( upEdge.sym().origin, upEdge.origin ) <= 0 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dict.above( region ).dirty = true;
|
||||||
|
region.dirty = true;
|
||||||
|
|
||||||
|
// Split the upper edge
|
||||||
|
HalfEdge.splitEdge( upEdge.sym() );
|
||||||
|
// Move the upper edge to the lower edge's vertex
|
||||||
|
HalfEdge.splice( downEdge.prevOrigin(), upEdge );
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void walkDirtyRegions( RegionOld region ) {
|
||||||
|
RegionOld lower = dict.below( region );
|
||||||
|
while ( true ) {
|
||||||
|
// Find the lowest dirty region
|
||||||
|
while ( lower.dirty ) {
|
||||||
|
region = lower;
|
||||||
|
lower = dict.below( lower );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !region.dirty ) {
|
||||||
|
lower = region;
|
||||||
|
region = dict.above( region );
|
||||||
|
if ( region == null || !region.dirty ) {
|
||||||
|
// No dirty regions left
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
region.dirty = false;
|
||||||
|
HalfEdge upperEdge = region.upperEdge;
|
||||||
|
HalfEdge lowerEdge = lower.upperEdge;
|
||||||
|
|
||||||
|
// If the start of the region edge is not the same as the start of the lower region edge
|
||||||
|
if ( upperEdge.sym().origin != lowerEdge.sym().origin ) {
|
||||||
|
// Consistency check
|
||||||
|
if ( checkForLeftSplice( region ) ) {
|
||||||
|
if ( lower.fixUpperEdge ) {
|
||||||
|
dict.remove( lower );
|
||||||
|
HalfEdge.delete( lowerEdge );
|
||||||
|
lower = dict.below( region );
|
||||||
|
lowerEdge = lower.upperEdge;
|
||||||
|
} else if ( region.fixUpperEdge ) {
|
||||||
|
dict.remove( region );
|
||||||
|
HalfEdge.delete( upperEdge );
|
||||||
|
region = dict.above( lower );
|
||||||
|
upperEdge = region.upperEdge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the end of the region edge is not the same as the end of the lower region edge
|
||||||
|
if ( upperEdge.origin != lowerEdge.origin ) {
|
||||||
|
if ( upperEdge.sym().origin != lowerEdge.sym().origin
|
||||||
|
&& !region.fixUpperEdge
|
||||||
|
&& !lower.fixUpperEdge
|
||||||
|
&& ( upperEdge.sym().origin == vertex || lowerEdge.sym().origin == vertex ) ) {
|
||||||
|
if ( checkForIntersect( region ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
checkForRightSplice( region );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matching edges
|
||||||
|
if ( upperEdge.origin == lowerEdge.origin && upperEdge.sym().origin == lowerEdge.sym().origin ) {
|
||||||
|
// Degenerate loop
|
||||||
|
lowerEdge.winding += upperEdge.winding;
|
||||||
|
dict.remove( region );
|
||||||
|
HalfEdge.delete( upperEdge );
|
||||||
|
region = dict.above( lower );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkForIntersect( RegionOld upper ) {
|
||||||
|
// Check the upper and lower edges of this region to see if they intersect
|
||||||
|
|
||||||
|
RegionOld lower = dict.below( upper );
|
||||||
|
HalfEdge upperEdge = upper.upperEdge;
|
||||||
|
HalfEdge lowerEdge = lower.upperEdge;
|
||||||
|
VertexOld upLeft = upperEdge.sym().origin;
|
||||||
|
VertexOld upRight = upperEdge.origin;
|
||||||
|
VertexOld downLeft = lowerEdge.sym().origin;
|
||||||
|
VertexOld downRight = lowerEdge.origin;
|
||||||
|
|
||||||
|
// Endpoints are the same, so no intersection
|
||||||
|
if ( upRight == downRight ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double upY = Math.min( upRight.y, upLeft.y );
|
||||||
|
double downY = Math.max( downRight.y, downLeft.y );
|
||||||
|
// The edges do not overlap vertically
|
||||||
|
if ( upY > downY ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any vertical intersection of the right point
|
||||||
|
if ( upRight.lessThanOrEqualTo( downRight ) ) {
|
||||||
|
if ( upRight.compareTo( downLeft, downRight ) > 0 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ( downRight.compareTo( upLeft, upRight ) < 0 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Compute intersection
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point intersection( VertexOld x1, VertexOld x2, VertexOld y1, VertexOld y2 ) {
|
||||||
|
Point point = new Point( 0, 0 );
|
||||||
|
if ( !x1.lessThanOrEqualTo( x2 ) ) {
|
||||||
|
VertexOld temp = x1;
|
||||||
|
x1 = x2;
|
||||||
|
x2 = temp;
|
||||||
|
}
|
||||||
|
if ( !y1.lessThanOrEqualTo( y2 ) ) {
|
||||||
|
VertexOld temp = y1;
|
||||||
|
y1 = y2;
|
||||||
|
y2 = temp;
|
||||||
|
}
|
||||||
|
if ( !x1.lessThanOrEqualTo( y1 ) ) {
|
||||||
|
VertexOld temp = x1;
|
||||||
|
x1 = y1;
|
||||||
|
y1 = temp;
|
||||||
|
VertexOld temp2 = x2;
|
||||||
|
x2 = y2;
|
||||||
|
y2 = temp2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare points
|
||||||
|
if ( !y1.lessThanOrEqualTo( x2 ) ) {
|
||||||
|
point.x = ( y1.x + x2.x ) / 2;
|
||||||
|
} else if ( x2.lessThanOrEqualTo( y2 ) ) {
|
||||||
|
double z1 = y1.verticalDistance( x1, x2 );
|
||||||
|
double z2 = x2.verticalDistance( y1, y2 );
|
||||||
|
if ( z1 + z1 < 0 ) {
|
||||||
|
z1 *= -1;
|
||||||
|
z2 *= -1;
|
||||||
|
}
|
||||||
|
point.x = interpolate( z1, y1.x, z2, x2.x );
|
||||||
|
} else {
|
||||||
|
double z1 = y1.compareTo( x1, x2 );
|
||||||
|
double z2 = - y2.compareTo( x1, x2 );
|
||||||
|
if ( z1 + z2 < 0 ) {
|
||||||
|
z1 *= -1;
|
||||||
|
z2 *= -1;
|
||||||
|
}
|
||||||
|
point.x = interpolate( z1, y1.x, z2, y2.x );
|
||||||
|
}
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double interpolate( double a, double x, double b, double y ) {
|
||||||
|
a = Math.max( a, 0 );
|
||||||
|
b = Math.max( b, 0 );
|
||||||
|
if ( a > b ) {
|
||||||
|
return y - ( ( x - y ) * ( b / ( a + b ) ) );
|
||||||
|
}
|
||||||
|
if ( b != 0 ) {
|
||||||
|
return x + ( ( y - x ) * ( a / ( a + b ) ) );
|
||||||
|
}
|
||||||
|
return ( x + y ) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void computeWinding( RegionOld region ) {
|
||||||
|
region.windingNumber = dict.above( region ).windingNumber + region.upperEdge.winding;
|
||||||
|
// TODO Using odd for now
|
||||||
|
region.inside = region.windingNumber % 2 == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void connectLeftDegenerate( RegionOld region, VertexOld vert ) {
|
||||||
|
throw new IllegalArgumentException( "connectLeftDegenerate is unimplemented!" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegionOld addRegionBelow( RegionOld above, HalfEdge edge ) {
|
||||||
|
RegionOld region = new RegionOld();
|
||||||
|
region.upperEdge = edge;
|
||||||
|
dict.insertBefore( above, region );
|
||||||
|
region.fixUpperEdge = false;
|
||||||
|
region.sentinel = false;
|
||||||
|
|
||||||
|
edge.region = region;
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
}
|
||||||
194
src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Util.java
Normal file
194
src/main/java/com/aaaaahhhhhhh/bananapuncher714/tess4j/Util.java
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.joml.Math;
|
||||||
|
|
||||||
|
public class Util {
|
||||||
|
protected static String toString( Object obj ) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.append( obj.getClass().getSimpleName() );
|
||||||
|
builder.append( '{' );
|
||||||
|
List< Field > fields = new ArrayList< Field >();
|
||||||
|
fields.addAll( Arrays.asList( obj.getClass().getDeclaredFields() ) );
|
||||||
|
fields.addAll( Arrays.asList( obj.getClass().getFields() ) );
|
||||||
|
|
||||||
|
for ( Iterator< Field > it = fields.iterator(); it.hasNext(); builder.append( "," ) ) {
|
||||||
|
Field field = it.next();
|
||||||
|
builder.append( field.getName() );
|
||||||
|
builder.append( '=' );
|
||||||
|
try {
|
||||||
|
builder.append( field.get( obj ) );
|
||||||
|
} catch ( IllegalArgumentException | IllegalAccessException e ) {
|
||||||
|
builder.append( "?" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append( '}' );
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional< Point > intersection( final VertexOld x1, final VertexOld x2, final VertexOld x3, final VertexOld x4 ) {
|
||||||
|
Point point = null;
|
||||||
|
|
||||||
|
final Vector2dOld p = x1.toVector();
|
||||||
|
final Vector2dOld r = x2.subtract( x1 );
|
||||||
|
final Vector2dOld q = x3.toVector();
|
||||||
|
final Vector2dOld s = x4.subtract( x3 );
|
||||||
|
|
||||||
|
final Vector2dOld qp = q.subtract( p );
|
||||||
|
final double qpr = qp.cross( r );
|
||||||
|
final double rs = r.cross( s );
|
||||||
|
|
||||||
|
// Are the two lines parallel
|
||||||
|
if ( rs == 0 ) {
|
||||||
|
final Vector2dOld u = x2.toVector();
|
||||||
|
final Vector2dOld v = x4.toVector();
|
||||||
|
|
||||||
|
// For normalizing against pr
|
||||||
|
final double rr = r.dot( r );
|
||||||
|
|
||||||
|
// t0 and t1 represent the distance of the second edge endpoints relative to the first edge normalized by r
|
||||||
|
// t0 = ( ( q - p ) . r ) / rr;
|
||||||
|
double t0 = qp.dot( r ) / rr;
|
||||||
|
// t1 = ( ( q + s - p ) . r ) / rr
|
||||||
|
double t1 = v.subtract( p ).dot( r ) / rr;
|
||||||
|
|
||||||
|
// If the vectors are pointing in the opposite directions, then swap t0 and t1
|
||||||
|
final boolean inverted = s.dot( r ) < 0;
|
||||||
|
if ( inverted ) {
|
||||||
|
double temp = t0;
|
||||||
|
t0 = t1;
|
||||||
|
t1 = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( qpr == 0 ) {
|
||||||
|
if ( t0 < 1 & t1 > 0 ) {
|
||||||
|
final double midt = Math.max( t0, 0 ) + Math.min( t1, 1 ) / 2.0;
|
||||||
|
final Vector2dOld midpoint = r.multiply( midt ).add( p );
|
||||||
|
point = midpoint.toPoint();
|
||||||
|
} else {
|
||||||
|
final Vector2dOld pu = t0 > 1 ? u : p;
|
||||||
|
// Same as the first edge but the second edge may be inverted
|
||||||
|
final Vector2dOld qv = ( inverted ^ t0 > 1 ) ? q : v;
|
||||||
|
final Vector2dOld midpoint = pu.add( qv ).divide( 2.0 );
|
||||||
|
point = midpoint.toPoint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Calculate t and u
|
||||||
|
final double t = qp.cross( s ) / rs;
|
||||||
|
final double u = qpr / rs;
|
||||||
|
|
||||||
|
// Do the line segments intersect
|
||||||
|
if ( t >= 0 && t <= 1 && u >= 0 && u <= 1 ) {
|
||||||
|
// Calculate the point of intersection
|
||||||
|
final Vector2dOld intersection = r.multiply( t ).add( p );
|
||||||
|
point = intersection.toPoint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.ofNullable( point );
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
|
||||||
|
// Vector based closest intersection method
|
||||||
|
public static Point closestPoint( final VertexOld x1, final VertexOld x2, final VertexOld x3, final VertexOld x4 ) {
|
||||||
|
Point point = new Point( 0, 0 );
|
||||||
|
|
||||||
|
final Vector2dOld p = x1.toVector();
|
||||||
|
final Vector2dOld r = x2.subtract( x1 );
|
||||||
|
final Vector2dOld q = x3.toVector();
|
||||||
|
final Vector2dOld s = x4.subtract( x3 );
|
||||||
|
|
||||||
|
final Vector2dOld qp = q.subtract( p );
|
||||||
|
final double qpr = qp.cross( r );
|
||||||
|
final double rs = r.cross( s );
|
||||||
|
|
||||||
|
// Are the two lines parallel
|
||||||
|
if ( rs == 0 ) {
|
||||||
|
final Vector2dOld u = x2.toVector();
|
||||||
|
final Vector2dOld v = x4.toVector();
|
||||||
|
|
||||||
|
// For normalizing against pr
|
||||||
|
final double rr = r.dot( r );
|
||||||
|
|
||||||
|
// t0 and t1 represent the distance of the second edge endpoints relative to the first edge normalized by r
|
||||||
|
// t0 = ( ( q - p ) . r ) / rr;
|
||||||
|
double t0 = qp.dot( r ) / rr;
|
||||||
|
// t1 = ( ( q + s - p ) . r ) / rr
|
||||||
|
double t1 = v.subtract( p ).dot( r ) / rr;
|
||||||
|
|
||||||
|
// If the vectors are pointing in the opposite directions, then swap t0 and t1
|
||||||
|
final boolean inverted = s.dot( r ) < 0;
|
||||||
|
if ( inverted ) {
|
||||||
|
double temp = t0;
|
||||||
|
t0 = t1;
|
||||||
|
t1 = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the lines overlap
|
||||||
|
if ( t0 < 1 && t1 > 0 ) {
|
||||||
|
// The midpoint is an arbitrary point that just happens to look good as an intersection
|
||||||
|
// Find the midpoint between the overlapping region
|
||||||
|
final double midt = Math.max( t0, 0 ) + Math.min( t1, 1 ) / 2.0;
|
||||||
|
// If colinear
|
||||||
|
if ( qpr == 0 ) {
|
||||||
|
// Do not need to calculate the distance between the two parallel lines since it is 0
|
||||||
|
final Vector2dOld midpoint = r.multiply( midt ).add( p );
|
||||||
|
point.x = midpoint.x;
|
||||||
|
point.y = midpoint.y;
|
||||||
|
} else {
|
||||||
|
// Calculate the midpoint along pr
|
||||||
|
final Vector2dOld midp = r.multiply( midt ).add( p );
|
||||||
|
// Calculate the perpendicular distance between both lines
|
||||||
|
final Vector2dOld perp = r.perpendicular();
|
||||||
|
final Vector2dOld z = perp.multiply( qp.dot( perp ) / rr );
|
||||||
|
// Sum the components
|
||||||
|
final Vector2dOld midpoint = midp.add( z.divide( 2.0 ) );
|
||||||
|
point.x = midpoint.x;
|
||||||
|
point.y = midpoint.y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The lines aren't really anywhere close to each other, so just find the midpoint between the 2 closest points
|
||||||
|
// If t0 > 1, then the second edge must lie to the right of the first edge
|
||||||
|
final Vector2dOld pu = t0 > 1 ? u : p;
|
||||||
|
// Same as the first edge but the second edge may be inverted
|
||||||
|
final Vector2dOld qv = ( inverted ^ t0 > 1 ) ? q : v;
|
||||||
|
final Vector2dOld midpoint = pu.add( qv ).divide( 2.0 );
|
||||||
|
point.x = midpoint.x;
|
||||||
|
point.y = midpoint.y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Calculate t and u
|
||||||
|
final double t = qp.cross( s ) / rs;
|
||||||
|
final double u = qpr / rs;
|
||||||
|
|
||||||
|
// Do the line segments intersect
|
||||||
|
if ( t >= 0 && t <= 1 && u >= 0 && u <= 1 ) {
|
||||||
|
// Calculate the point of intersection
|
||||||
|
final Vector2dOld intersection = r.multiply( t ).add( p );
|
||||||
|
point.x = intersection.x;
|
||||||
|
point.y = intersection.y;
|
||||||
|
} else {
|
||||||
|
// No intersection, so calculate the closest point between the two segments
|
||||||
|
// We have t and u, which we can use to find the closest endpoints
|
||||||
|
final double nearT = Math.max( Math.min( t, 1 ), 0 );
|
||||||
|
final double nearU = Math.max( Math.min( u, 1 ), 0 );
|
||||||
|
|
||||||
|
final Vector2dOld nearP = r.multiply( nearT ).add( p );
|
||||||
|
final Vector2dOld nearQ = s.multiply( nearU ).add( q );
|
||||||
|
|
||||||
|
point.x = ( nearP.x + nearQ.x ) / 2.0;
|
||||||
|
point.y = ( nearP.y + nearQ.y ) / 2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public class Vector2d {
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
|
||||||
|
public Vector2d() {
|
||||||
|
this( 0, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d( double x, double y ) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d( Vector2d o ) {
|
||||||
|
this( o.x, o.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d setX( double x ) {
|
||||||
|
this.x = x;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d setY( double y ) {
|
||||||
|
this.y = y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double cross( Vector2d o ) {
|
||||||
|
return ( x * o.y ) - ( y * o.x );
|
||||||
|
}
|
||||||
|
|
||||||
|
public double lengthSquared() {
|
||||||
|
return dot( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
public double length() {
|
||||||
|
return Math.sqrt( lengthSquared() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public double distanceSquared( Vector2d o ) {
|
||||||
|
return distanceSquared( this, o );
|
||||||
|
}
|
||||||
|
|
||||||
|
public double distance( Vector2d o ) {
|
||||||
|
return distance( this, o );
|
||||||
|
}
|
||||||
|
|
||||||
|
public double dot( Vector2d o ) {
|
||||||
|
return dot( this, o );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d add( double v ) {
|
||||||
|
x += v;
|
||||||
|
y += v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d added( double v ) {
|
||||||
|
return new Vector2d( x + v, y + v );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d add( Vector2d o ) {
|
||||||
|
x += o.x;
|
||||||
|
y += o.y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d added( Vector2d o ) {
|
||||||
|
return add( this, o );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d subtract( double v ) {
|
||||||
|
x -= v;
|
||||||
|
y -= v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d subtracted( double v ) {
|
||||||
|
return new Vector2d( x - v, y - v );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d subtract( Vector2d o ) {
|
||||||
|
x -= o.x;
|
||||||
|
y -= o.y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d subtracted( Vector2d o ) {
|
||||||
|
return new Vector2d( x - o.x, y - o.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d multiply( double v ) {
|
||||||
|
x *= v;
|
||||||
|
y *= v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d multiplied( double v ) {
|
||||||
|
return new Vector2d( x * v, y * v );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d multiply( Vector2d o ) {
|
||||||
|
x *= o.x;
|
||||||
|
y *= o.y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d multiplied( Vector2d o ) {
|
||||||
|
return multiply( this, o );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d divide( double v ) {
|
||||||
|
x /= v;
|
||||||
|
y /= v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d divided( double v ) {
|
||||||
|
return new Vector2d( x / v, y / v );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d divide( Vector2d o ) {
|
||||||
|
x /= o.x;
|
||||||
|
y /= o.y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d divided( Vector2d o ) {
|
||||||
|
return new Vector2d( x / o.x, y / o.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d normalize() {
|
||||||
|
return divide( length() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d normalized() {
|
||||||
|
return divided( length() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isZero() {
|
||||||
|
return x == 0 && y == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double dot( Vector2d a, Vector2d b ) {
|
||||||
|
return a.x * b.x + a.y * b.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2d add( Vector2d a, Vector2d b ) {
|
||||||
|
return new Vector2d( a.x + b.x, a.y + b.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2d multiply( Vector2d a, Vector2d b ) {
|
||||||
|
return new Vector2d( a.x * b.x, a.y * b.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double distanceSquared( Vector2d a, Vector2d b ) {
|
||||||
|
return a.subtracted( b ).lengthSquared();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double distance( Vector2d a, Vector2d b ) {
|
||||||
|
return Math.sqrt( distanceSquared( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
long temp;
|
||||||
|
temp = Double.doubleToLongBits(x);
|
||||||
|
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
temp = Double.doubleToLongBits(y);
|
||||||
|
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
Vector2d other = (Vector2d) obj;
|
||||||
|
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
|
||||||
|
return false;
|
||||||
|
if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Vector2d{x=" + x + ",y=" + y + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
public abstract class Vector2dComparator implements Comparator< Vector2d > {
|
||||||
|
public abstract double compareX( Vector2d a, Vector2d b );
|
||||||
|
public abstract double compareY( Vector2d a, Vector2d b );
|
||||||
|
|
||||||
|
// Sorts by x, then y
|
||||||
|
public final int compare( Vector2d a, Vector2d b ) {
|
||||||
|
double x = compareX( a, b );
|
||||||
|
if ( x == 0 ) {
|
||||||
|
return Double.compare( compareY( a, b ), 0 );
|
||||||
|
}
|
||||||
|
return Double.compare( x, 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public class Vector2dOld {
|
||||||
|
final double x;
|
||||||
|
final double y;
|
||||||
|
|
||||||
|
Vector2dOld( double x, double y ) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double cross( Vector2dOld other ) {
|
||||||
|
return ( x * other.y ) - ( y * other.x );
|
||||||
|
}
|
||||||
|
|
||||||
|
public double dot( Vector2dOld other ) {
|
||||||
|
return x * other.x + y * other.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2dOld subtract( Vector2dOld other ) {
|
||||||
|
return new Vector2dOld( x - other.x , y - other.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2dOld add( Vector2dOld other ) {
|
||||||
|
return new Vector2dOld( x + other.x, y + other.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2dOld perpendicular() {
|
||||||
|
return new Vector2dOld( -y, x );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2dOld multiply( double v ) {
|
||||||
|
return new Vector2dOld( x * v, y * v );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2dOld divide( double v ) {
|
||||||
|
return new Vector2dOld( x / v, y / v );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2dOld normalize() {
|
||||||
|
return divide( Math.sqrt( dot( this ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point toPoint() {
|
||||||
|
return new Point( x, y );
|
||||||
|
}
|
||||||
|
|
||||||
|
public double length() {
|
||||||
|
return Math.sqrt( dot( this ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
long temp;
|
||||||
|
temp = Double.doubleToLongBits(x);
|
||||||
|
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
temp = Double.doubleToLongBits(y);
|
||||||
|
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
Vector2dOld other = (Vector2dOld) obj;
|
||||||
|
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
|
||||||
|
return false;
|
||||||
|
if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Vector2d{x=" + x + ",y=" + y + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public class Vertex {
|
||||||
|
private HalfWing wing;
|
||||||
|
private Vector2d position;
|
||||||
|
|
||||||
|
public Vertex( HalfWing wing ) {
|
||||||
|
setWing( wing );
|
||||||
|
setPosition( new Vector2d() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalfWing getWing() {
|
||||||
|
return wing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vertex setWing( HalfWing wing ) {
|
||||||
|
this.wing = wing;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2d getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vertex setPosition( Vector2d vec ) {
|
||||||
|
this.position = vec;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
HalfWing wing = this.wing;
|
||||||
|
do {
|
||||||
|
wing.setOrigin( this );
|
||||||
|
} while ( ( wing = wing.getPrev() ) != this.wing );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((position == null) ? 0 : position.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
Vertex other = (Vertex) obj;
|
||||||
|
if (position == null) {
|
||||||
|
if (other.position != null)
|
||||||
|
return false;
|
||||||
|
} else if (!position.equals(other.position))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Vertex{x=" + position.getX() + ",y=" + position.getY() + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
public class VertexOld implements Comparable< VertexOld > {
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
|
||||||
|
// An edge that is attached to this vertex
|
||||||
|
HalfEdge edge;
|
||||||
|
|
||||||
|
public VertexOld( HalfEdge edge ) {
|
||||||
|
this.edge = edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assign() {
|
||||||
|
HalfEdge temp = edge;
|
||||||
|
do {
|
||||||
|
temp.origin = this;
|
||||||
|
temp = temp.nextOrigin;
|
||||||
|
} while ( temp != edge );
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEqualTo( VertexOld o ) {
|
||||||
|
return x == o.x && y == o.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean lessThanOrEqualTo( VertexOld o ) {
|
||||||
|
return ( x < o.x ) || ( x == o.x && y <= o.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean lessThanOrEqualTo2( VertexOld o ) {
|
||||||
|
return ( y < o.y ) || ( y == o.y && x <= o.x );
|
||||||
|
}
|
||||||
|
|
||||||
|
public double compareTo( VertexOld v1, VertexOld v2 ) {
|
||||||
|
double x1 = x - v1.x;
|
||||||
|
double x2 = v2.x - x;
|
||||||
|
|
||||||
|
if ( x1 + x2 > 0 ) {
|
||||||
|
return ( y - v2.y ) * x1 + ( y - v1.y ) * x2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2dOld subtract( VertexOld other ) {
|
||||||
|
return new Vector2dOld( x - other.x, y - other.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2dOld toVector() {
|
||||||
|
return new Vector2dOld( x, y );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the vertical distance from this point to the line segment represented by v1 and v2
|
||||||
|
*
|
||||||
|
* This is assuming the following is true:
|
||||||
|
* - v1 <= this <= v2
|
||||||
|
* If v1v2 is vertical (and thus passes through this), the result is 0
|
||||||
|
*/
|
||||||
|
public double verticalDistance( VertexOld v1, VertexOld v2 ) {
|
||||||
|
// x1 >= 0
|
||||||
|
double x1 = x - v1.x;
|
||||||
|
// x2 >= 0
|
||||||
|
double x2 = v2.x - x;
|
||||||
|
|
||||||
|
// Check if it is not vertical
|
||||||
|
if ( x1 + x2 > 0 ) {
|
||||||
|
// Find the slope
|
||||||
|
// Use smaller x value for better double precision, I suppose
|
||||||
|
if ( x1 < x2 ) {
|
||||||
|
return ( y - v1.y ) + ( ( v1.y - v2.y ) * ( x1 / ( x1 + x2 ) ) );
|
||||||
|
} else {
|
||||||
|
return ( y - v2.y ) + ( ( v2.y - v1.y ) * ( x2 / ( x1 + x2 ) ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
VertexOld other = (VertexOld) obj;
|
||||||
|
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
|
||||||
|
return false;
|
||||||
|
if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Vertex{x=" + x + ",y=" + y + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo( final VertexOld o ) {
|
||||||
|
final int sortX = Double.compare( x, o.x );
|
||||||
|
return sortX == 0 ? Double.compare( y, o.y ) : sortX;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A winding rule describes how a region is modified, particularly when crossing a border
|
||||||
|
* between a known region to an unknown region. The inverse winding rule must provide a
|
||||||
|
* symmetric change such that regions can be converted consistently. Normally, the same
|
||||||
|
* winding rule is applied to all polygons that need to be tessellated, such as the odd
|
||||||
|
* number rule, but if your polygons conform to different or more complex interior/exterior
|
||||||
|
* critera, then you can easily set different rules per polygon. An example might be if you
|
||||||
|
* have two sets of polygons that you would like to tessellate together, and distinctly mark
|
||||||
|
* regions where they might overlap. You can use a combination of two odd number rules to
|
||||||
|
* properly mark the boundaries.
|
||||||
|
*
|
||||||
|
* @author BananaPuncher714
|
||||||
|
*
|
||||||
|
* @param <T> A region type that can be modified by this rule
|
||||||
|
*/
|
||||||
|
public interface WindingRule< T extends Region > {
|
||||||
|
// Apply this rule to the region, and return a new region
|
||||||
|
T apply( T region );
|
||||||
|
// Get the inverse of this rule
|
||||||
|
WindingRule< T > inverse();
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j.region;
|
||||||
|
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.Region;
|
||||||
|
|
||||||
|
public class RegionSimple extends Region {
|
||||||
|
private int windingNumber;
|
||||||
|
private GluWindingRule rule;
|
||||||
|
|
||||||
|
public RegionSimple( RegionSimple o ) {
|
||||||
|
windingNumber = o.windingNumber;
|
||||||
|
rule = o.rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegionSimple( GluWindingRule rule ) {
|
||||||
|
this.windingNumber = 0;
|
||||||
|
this.rule = rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInterior() {
|
||||||
|
switch ( rule ) {
|
||||||
|
case ODD:
|
||||||
|
return ( windingNumber & 1 ) == 1;
|
||||||
|
case NONZERO:
|
||||||
|
return windingNumber != 0;
|
||||||
|
case POSITIVE:
|
||||||
|
return windingNumber > 0;
|
||||||
|
case NEGATIVE:
|
||||||
|
return windingNumber < 0;
|
||||||
|
case ABS_GEQ_TWO:
|
||||||
|
return Math.abs( windingNumber ) >= 2;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
RegionSimple other = (RegionSimple) obj;
|
||||||
|
if (rule != other.rule)
|
||||||
|
return false;
|
||||||
|
if (windingNumber != other.windingNumber)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Region: " + windingNumber + ", " + rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWindingNumber() {
|
||||||
|
return windingNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegionSimple setWindingNumber( int number ) {
|
||||||
|
this.windingNumber = number;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int increment() {
|
||||||
|
return ++windingNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int decrement() {
|
||||||
|
return --windingNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GluWindingRule {
|
||||||
|
ODD,
|
||||||
|
NONZERO,
|
||||||
|
POSITIVE,
|
||||||
|
NEGATIVE,
|
||||||
|
ABS_GEQ_TWO
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.aaaaahhhhhhh.bananapuncher714.tess4j.region;
|
||||||
|
|
||||||
|
import com.aaaaahhhhhhh.bananapuncher714.tess4j.WindingRule;
|
||||||
|
|
||||||
|
public class WindingRuleSimple implements WindingRule< RegionSimple > {
|
||||||
|
private final boolean inverse;
|
||||||
|
private WindingRuleSimple opposite;
|
||||||
|
|
||||||
|
public WindingRuleSimple() {
|
||||||
|
this( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
public WindingRuleSimple( boolean inverse ) {
|
||||||
|
this.inverse = inverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RegionSimple apply( RegionSimple region ) {
|
||||||
|
RegionSimple copy = new RegionSimple( region );
|
||||||
|
if ( inverse ) {
|
||||||
|
copy.decrement();
|
||||||
|
} else {
|
||||||
|
copy.increment();
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WindingRuleSimple inverse() {
|
||||||
|
if ( opposite == null ) {
|
||||||
|
opposite = new WindingRuleSimple( !inverse );
|
||||||
|
opposite.opposite = this;
|
||||||
|
}
|
||||||
|
return opposite;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
The task of triangulation can be completed in logarithmic time, based on the amount of regions and how many nodes each region contains. However, it is not
|
||||||
|
uncommon for regions to contain many segments that lie on the same line. When this is the case, often times the region can be split into multiple smaller
|
||||||
|
regions. The benefit of doing so is that there are less triangles that are generated, and each region can be triangulated in parallel, for a bonus speed
|
||||||
|
increase. The downside is that splitting a region in the most optimal method can be quite costly. It would take polynomial time based on the number of
|
||||||
|
nodes in order to find the combination of regions that would decrease triangle count the most. However, it can be argued that for certain region shapes
|
||||||
|
which may occur frequently such as the topology of connected Minecraft blocks, it may be worth the investment if time is less of an issue, and memory
|
||||||
|
usage matters more.
|
||||||
|
|
||||||
|
Ideal time: O(n) or O(nlog(n))
|
||||||
|
|
||||||
|
For each point:
|
||||||
|
- Find all intersecting edges
|
||||||
|
- Easy check, see which side both ends of each segment are on
|
||||||
|
- If different sides, then it intersects with the line
|
||||||
|
- Find all other segments on the same line
|
||||||
|
- Easy check, find all lines with the same slope and intersection point or something
|
||||||
|
- Need to check if segment can be connected to
|
||||||
|
- This means checking all intersecting edges
|
||||||
|
- Also need to check for individual points
|
||||||
|
- A segment and a point results in 1 less triangle
|
||||||
|
- Two segments results in 2 less triangles
|
||||||
|
- Find all intersections with potential segments
|
||||||
|
- For each potential segment, check for intersection with each other
|
||||||
|
- Count how many it intersects with
|
||||||
|
- Greedy method... Add lines with least amount of intersections first?
|
||||||
|
- According to the rule of mutual crosses, the total amount of intersections is always even
|
||||||
|
|
||||||
|
The overall algorithm can be broken down into 3 parts:
|
||||||
|
- Finding all possible segments between 3 or more points
|
||||||
|
- Removing all connections that intersect with the polygon edges
|
||||||
|
- Determining how many other possible segments intersect with each other
|
||||||
|
The first two are both O(n^2) time, which is unfortunate. It is unknown if it could
|
||||||
|
be made into O(logn) or better.
|
||||||
|
The last part is O(logn)
|
||||||
|
|
||||||
|
Obviously, one can determine which points are visible from one point by iterating
|
||||||
|
over every edge and building a depth field. Then, all potential connecting segments
|
||||||
|
can be found by checking each visible point.
|
||||||
|
Alternatively, one can check each point to see if it lies on the same segment?
|
||||||
|
A logical structure to hold a segment would be the two endpoints, and any points that are
|
||||||
|
contained by the...
|
||||||
67
src/main/resources/README.md
Normal file
67
src/main/resources/README.md
Normal 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.
|
||||||
14
src/main/resources/plugin.yml
Normal file
14
src/main/resources/plugin.yml
Normal 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
|
||||||
Reference in New Issue
Block a user