Initial commit for historical purposes

This commit is contained in:
2025-05-02 00:29:08 -04:00
commit a1899a52b7
64 changed files with 7754 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.util.Vector;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkLocation;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Facet;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.MeshBuilder;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Plane;
public class MeshTest {
private static final File BASE = new File( System.getProperty( "user.dir" ) );
private static final File CHUNK_DIR = new File( BASE, "chunks" );
public static void main( String[] args ) {
if ( CHUNK_DIR.exists() && CHUNK_DIR.isDirectory() ) {
System.out.println( "Found " + CHUNK_DIR.list().length + " files" );
for ( File file : CHUNK_DIR.listFiles() ) {
doStuff( file );
break;
}
} else {
System.err.println( "No such directory exists: " + CHUNK_DIR );
}
}
private static void doStuff( File file ) {
String name = file.getName();
String[] split = name.split( "," );
ChunkLocation location = new ChunkLocation( split[ 0 ], Integer.parseInt( split[ 1 ] ), Integer.parseInt( split[ 2 ] ) );
System.out.println( "Parsing chunk " + location );
// First parse the file to get a list of all bounding boxes that we can use
List< AABB > boxes = new ArrayList< AABB >();
try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) {
String line;
while ( ( line = reader.readLine() ) != null ) {
if ( !line.isEmpty() ) {
String[] values = line.split( "," );
double minX = Double.parseDouble( values[ 0 ] );
double minY = Double.parseDouble( values[ 1 ] );
double minZ = Double.parseDouble( values[ 2 ] );
double maxX = Double.parseDouble( values[ 3 ] );
double maxY = Double.parseDouble( values[ 4 ] );
double maxZ = Double.parseDouble( values[ 5 ] );
boxes.add( new AABB( minX, minY, minZ, maxX, maxY, maxZ ) );
}
}
} catch ( FileNotFoundException e ) {
e.printStackTrace();
return;
} catch ( IOException e ) {
e.printStackTrace();
return;
}
System.out.println( "Found " + boxes.size() + " boxes" );
// Now that we have a bunch of bounding boxes, do whatever
MeshBuilder builder = new MeshBuilder();
int i = 0;
for ( AABB box : boxes ) {
for ( Facet facet : generateFacetsFor( box ) ) {
builder.addFacet( facet );
}
}
System.out.println( "Planes: " + builder.planes.size() );
for ( Plane plane : builder.planes ) {
System.out.println( "Plane" );
System.out.println( "Norm:\t" + plane.normal );
System.out.println( "Ref:\t" + plane.point );
System.out.println( "Size:\t" + plane.polygons.size() );
}
/*
* The general plan is as follows:
* - Create a new mesh object
* - Insert faces one by one until we have all the shapes we want
* - In this case, it happens to be perfect AABB
* - Process the faces
* - Sort them into their correct plane of existence, along with their normal
* - In this case, their unique plane can be defined by their normal, and a point
* - Use the first point of the polygon to compare
* - Use the smallest point we can find as the reference point
* - O(1) operation per face, I suppose
* - Need to check point and normal with tolerance which is why
* - Now that we have faces each in their assorted group, we can start to tessellate each one
* - Generate even/odd regions and remove any colinear edges
* - Save this as the pre-mesh
* - Now trianglate each group and put that into a mesh collision shape or gimpact shape or whatnot
* - Use an IndexedMesh with the two array constructor
* - Put into game and test
*/
}
private static List< Facet > generateFacetsFor( AABB box ) {
List< Facet > facets = new ArrayList< Facet >();
Vector p1 = new Vector( box.xmin, box.ymin, box.zmin );
Vector p2 = new Vector( box.xmin, box.ymin, box.zmax );
Vector p3 = new Vector( box.xmin, box.ymax, box.zmin );
Vector p4 = new Vector( box.xmin, box.ymax, box.zmax );
Vector p5 = new Vector( box.xmax, box.ymin, box.zmin );
Vector p6 = new Vector( box.xmax, box.ymin, box.zmax );
Vector p7 = new Vector( box.xmax, box.ymax, box.zmin );
Vector p8 = new Vector( box.xmax, box.ymax, box.zmax );
{
Facet facet = new Facet();
facet.points.add( p1 );
facet.points.add( p2 );
facet.points.add( p4 );
facet.points.add( p3 );
facet.normal = new Vector( -1, 0, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p5 );
facet.points.add( p6 );
facet.points.add( p8 );
facet.points.add( p7 );
facet.normal = new Vector( 1, 0, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p1 );
facet.points.add( p2 );
facet.points.add( p6 );
facet.points.add( p5 );
facet.normal = new Vector( 0, -1, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p3 );
facet.points.add( p4 );
facet.points.add( p7 );
facet.points.add( p8 );
facet.normal = new Vector( 0, 1, 0 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p1 );
facet.points.add( p3 );
facet.points.add( p7 );
facet.points.add( p5 );
facet.normal = new Vector( 0, 0, -1 );
facets.add( facet );
}
{
Facet facet = new Facet();
facet.points.add( p2 );
facet.points.add( p4 );
facet.points.add( p8 );
facet.points.add( p6 );
facet.normal = new Vector( 0, 0, 1 );
facets.add( facet );
}
return facets;
}
static class AABB {
double xmin, ymin, zmin;
double xmax, ymax, zmax;
AABB( double xmin, double ymin, double zmin, double xmax, double ymax, double zmax ) {
this.xmin = xmin;
this.ymax = ymin;
this.zmin = zmin;
this.xmax = xmax;
this.ymax = ymax;
this.zmax = zmax;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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 + "}";
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
package com.aaaaahhhhhhh.bananapuncher714.tess4j;
import java.util.List;
public class Mesh {
List< HalfEdge > edges;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View 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;
}
}

View File

@@ -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 + "}";
}
}

View File

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

View File

@@ -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 + "}";
}
}

View File

@@ -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() + "}";
}
}

View File

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

View File

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

View File

@@ -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
}
}

View File

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

View File

@@ -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...