diff --git a/plugin/environment/pom.xml b/plugin/environment/pom.xml index 70c1ee1..f5a90c5 100644 --- a/plugin/environment/pom.xml +++ b/plugin/environment/pom.xml @@ -66,19 +66,6 @@ 21 - - org.apache.maven.plugins - maven-jar-plugin - 3.5.0 - - - - true - com.aaaaahhhhhhh.bananapuncher714.minietest.MeshingTest6 - - - - \ No newline at end of file diff --git a/plugin/environment/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/environment/EnvironmentHandler.java b/plugin/environment/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/environment/EnvironmentHandler.java new file mode 100644 index 0000000..5e4af7c --- /dev/null +++ b/plugin/environment/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/environment/EnvironmentHandler.java @@ -0,0 +1,10 @@ +package com.aaaaahhhhhhh.bananapuncher714.mesh.environment; + +import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.ChunkLocation; +import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.ChunkMesh; + +public interface EnvironmentHandler { + public void onMeshLoad( ChunkLocation location, ChunkMesh mesh ); + public void onMeshUnload( ChunkLocation location, ChunkMesh mesh ); + public void onMeshUpdate( ChunkLocation location, ChunkMesh mesh ); +} diff --git a/plugin/environment/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/environment/MeshEnvironment.java b/plugin/environment/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/environment/MeshEnvironment.java index 6bf6c31..1895605 100644 --- a/plugin/environment/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/environment/MeshEnvironment.java +++ b/plugin/environment/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/environment/MeshEnvironment.java @@ -59,36 +59,34 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.util.NmsUtil; import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.util.NmsUtil.BlockShapeType; public class MeshEnvironment { - private JavaPlugin plugin; - private File cacheDirectory; - - private Map< ChunkLocation, Collection< Location > > updateLocations = new TreeMap< ChunkLocation, Collection< Location > >(); - private Map< ChunkLocation, ChunkMesh > chunkMeshes = new HashMap< ChunkLocation, ChunkMesh >(); - - private Map< ChunkLocation, ChunkTask > tasks = new HashMap< ChunkLocation, ChunkTask >(); - - private ChunkMeshCache cache; - - private ReentrantLock lock = new ReentrantLock(); - + private JavaPlugin plugin; + private File cacheDirectory; + + private Map< ChunkLocation, Collection< Location > > updateLocations = new TreeMap< ChunkLocation, Collection< Location > >(); + private Map< ChunkLocation, ChunkMesh > chunkMeshes = new HashMap< ChunkLocation, ChunkMesh >(); + + private Map< ChunkLocation, ChunkTask > tasks = new HashMap< ChunkLocation, ChunkTask >(); + + private ChunkMeshCache cache; + + private EnvironmentHandler handler; + + private ReentrantLock lock = new ReentrantLock(); + final BukkitTask task = Bukkit.getScheduler().runTaskTimer( plugin, () -> { -// final Collection< ChunkLocation > locations = new ArrayDeque< ChunkLocation >(); lock.lock(); updateLocations.entrySet().stream().forEach( e -> { ChunkMesh mesh = chunkMeshes.get( e.getKey() ); if ( mesh != null ) { final Collection< Location > locs = update( mesh, e.getValue() ); e.getValue().removeAll( locs ); -// if ( !locs.isEmpty() ) { -// locations.add( e.getKey() ); -// } + + if ( handler != null && !locs.isEmpty() ) { + handler.onMeshUpdate( e.getKey(), mesh ); + } } } ); -// if ( !locations.isEmpty() ) { -// space.activateAll( true ); -// } - updateLocations.values().removeIf( c -> c.isEmpty() ); lock.unlock(); }, 1, 1 ); @@ -103,48 +101,48 @@ public class MeshEnvironment { lock.unlock(); return locations; }; - - private Listener eventListener = new Listener() { + + private Listener eventListener = new Listener() { @EventHandler private void onChunkLoad( ChunkLoadEvent event ) { - final ChunkLocation location = new ChunkLocation( event.getChunk() ); - - lock.lock(); - try { - if ( chunkMeshes.containsKey( location ) ) { - // Do nothing - } else { - final ChunkTask task = tasks.get( location ); - if ( task == null ) { - loadMesh( location ); - } else if ( task.type == ChunkTaskType.LOAD ) { - // Do nothing - } else if ( task.type == ChunkTaskType.UNLOAD ) { - // Do nothing - } - } - } finally { - lock.unlock(); - } - - /* - * Order of operations: - * - Check if: - * - The mesh is loaded - * - Do nothing - * - There is an unload queued - * - Do nothing - * - There is a load queued - * - Do nothing - * - Nothing is queued - * - Queue a load - */ + final ChunkLocation location = new ChunkLocation( event.getChunk() ); + + lock.lock(); + try { + if ( chunkMeshes.containsKey( location ) ) { + // Do nothing + } else { + final ChunkTask task = tasks.get( location ); + if ( task == null ) { + loadMesh( location ); + } else if ( task.type == ChunkTaskType.LOAD ) { + // Do nothing + } else if ( task.type == ChunkTaskType.UNLOAD ) { + // Do nothing + } + } + } finally { + lock.unlock(); + } + + /* + * Order of operations: + * - Check if: + * - The mesh is loaded + * - Do nothing + * - There is an unload queued + * - Do nothing + * - There is a load queued + * - Do nothing + * - Nothing is queued + * - Queue a load + */ } - + @EventHandler private void onChunkUnload( ChunkUnloadEvent event ) { final ChunkLocation location = new ChunkLocation( event.getChunk() ); - + lock.lock(); try { if ( chunkMeshes.containsKey( location ) ) { @@ -168,7 +166,7 @@ public class MeshEnvironment { } finally { lock.unlock(); } - + /* * Order of operations: * - Check if: @@ -182,106 +180,119 @@ public class MeshEnvironment { * - Queue an unload */ } - + @EventHandler( priority = EventPriority.MONITOR ) private void onBlockUpdateEvent( final BlockPhysicsEvent event ) { final ChunkLocation chunkLocation = new ChunkLocation( event.getBlock().getChunk() ); getLocationFor.apply( chunkLocation ).add( event.getBlock().getLocation() ); } }; - - public MeshEnvironment( JavaPlugin plugin, File cacheDirectory ) { - this.plugin = plugin; - this.cacheDirectory = cacheDirectory; - cache = new ChunkMeshCache( new File( cacheDirectory, "meshes" ) ); - } - - public void activate() { - Bukkit.getPluginManager().registerEvents( eventListener, plugin ); - } - - public void deactivate() { - HandlerList.unregisterAll( eventListener ); - } - - private void completeTask( ChunkLocation location ) { - lock.lock(); - try { - final ChunkTask task = tasks.remove( location ); - if ( task.type == ChunkTaskType.LOAD ) { - if ( !location.isLoaded() ) { - // Update all and unload - unloadMesh( location ); - } else { - // Chunk is still loaded, do nothing - } - } else if ( task.type == ChunkTaskType.UNLOAD ) { - if ( !location.isLoaded() ) { - // Check if there are any updates that we missed - // If so, then queue another unload with the newest mesh - // Otherwise, remove the location from our list of locations - if ( updateLocations.containsKey( location ) ) { - unloadMesh( location ); - } else { - chunkMeshes.remove( location ); - } - } else { - // Chunk is loaded, but we saved. Do nothing - } - } - } finally { - lock.unlock(); - } - } - - private void loadMesh( ChunkLocation location ) { - ChunkTask task = new ChunkTask(); - lock.lock(); - try { - tasks.put( location, task ); - - task.type = ChunkTaskType.LOAD; - task.task = Bukkit.getScheduler().runTaskAsynchronously( plugin, () -> { - final ChunkMesh mesh = cache.load( location ); - if ( mesh != null ) { - lock.lock(); - try { - // Save the mesh - if ( !chunkMeshes.containsKey( location ) ) { - chunkMeshes.put( location, mesh ); - } - completeTask( location ); - } finally { - lock.unlock(); - } - } else { - // Process and generate the mesh - Bukkit.getScheduler().runTask( plugin, () -> { - generateMesh( plugin, location.getWorld(), location.getChunk(), m -> { - lock.lock(); - try { - chunkMeshes.put( location, m ); - - completeTask( location ); - } finally { - lock.unlock(); - } - } ); - } ); - } - } ); - } finally { - lock.unlock(); - } - } + public MeshEnvironment( JavaPlugin plugin, File cacheDirectory ) { + this.plugin = plugin; + this.cacheDirectory = cacheDirectory; + cache = new ChunkMeshCache( new File( cacheDirectory, "meshes" ) ); + } - private void unloadMesh( ChunkLocation location ) { - ChunkTask task = new ChunkTask(); + public void activate() { + Bukkit.getPluginManager().registerEvents( eventListener, plugin ); + } + + public void deactivate() { + HandlerList.unregisterAll( eventListener ); + } + + public void load( ChunkLocation location ) { + loadMesh( location ); + } + + public void setHandler( EnvironmentHandler handler ) { + this.handler = handler; + } + + private void completeTask( ChunkLocation location ) { + lock.lock(); + try { + final ChunkTask task = tasks.remove( location ); + if ( task.type == ChunkTaskType.LOAD ) { + handler.onMeshLoad( location, chunkMeshes.get( location ) ); + if ( !location.isLoaded() ) { + // Update all and unload + unloadMesh( location ); + } else { + // Chunk is still loaded, do nothing + } + } else if ( task.type == ChunkTaskType.UNLOAD ) { + if ( !location.isLoaded() ) { + // Check if there are any updates that we missed + // If so, then queue another unload with the newest mesh + // Otherwise, remove the location from our list of locations + if ( updateLocations.containsKey( location ) ) { + unloadMesh( location ); + } else { + final ChunkMesh mesh = chunkMeshes.remove( location ); + + if ( handler != null ) { + handler.onMeshUnload( location, mesh ); + } + } + } else { + // Chunk is loaded, but we saved. Do nothing + } + } + } finally { + lock.unlock(); + } + } + + private void loadMesh( ChunkLocation location ) { + ChunkTask task = new ChunkTask(); + lock.lock(); + try { + tasks.put( location, task ); + + task.type = ChunkTaskType.LOAD; + task.task = Bukkit.getScheduler().runTaskAsynchronously( plugin, () -> { + final ChunkMesh mesh = cache.load( location ); + if ( mesh != null ) { + lock.lock(); + try { + // Save the mesh + if ( !chunkMeshes.containsKey( location ) ) { + chunkMeshes.put( location, mesh ); + } + + completeTask( location ); + } finally { + lock.unlock(); + } + } else { + // Process and generate the mesh + Bukkit.getScheduler().runTask( plugin, () -> { + generateMesh( plugin, location.getWorld(), location.getChunk(), m -> { + lock.lock(); + try { + chunkMeshes.put( location, m ); + + completeTask( location ); + } finally { + lock.unlock(); + } + } ); + } ); + } + } ); + } finally { + lock.unlock(); + } + } + + private void unloadMesh( ChunkLocation location ) { + ChunkTask task = new ChunkTask(); lock.lock(); try { tasks.put( location, task ); - + if ( !applyUpdatesToMesh( location ) ) { // Not good! Piston issue somewhere } @@ -297,7 +308,7 @@ public class MeshEnvironment { } else { // Not really good } - + completeTask( location ); } finally { lock.unlock(); @@ -306,50 +317,55 @@ public class MeshEnvironment { } finally { lock.unlock(); } - } - - private boolean applyUpdatesToMesh( ChunkLocation location ) { - lock.lock(); - try { - final Collection< Location > locations = updateLocations.get( location ); - final ChunkMesh mesh = chunkMeshes.get( location ); - - if ( locations != null && !locations.isEmpty() && mesh != null ) { - locations.removeAll( update( mesh, locations ) ); - - return locations.isEmpty(); - } - } finally { - lock.unlock(); - } - - return true; - } - - private static void generateSpareMesh( JavaPlugin plugin, World world, Collection< Vector > locations, Consumer< SparseMesh > callback ) { + } + + private boolean applyUpdatesToMesh( ChunkLocation location ) { + lock.lock(); + try { + final Collection< Location > locations = updateLocations.get( location ); + final ChunkMesh mesh = chunkMeshes.get( location ); + + if ( locations != null && !locations.isEmpty() && mesh != null ) { + final Collection< Location > removed = update( mesh, locations ); + locations.removeAll( removed ); + + if ( handler != null && !removed.isEmpty() ) { + handler.onMeshUpdate( location, mesh ); + } + + return locations.isEmpty(); + } + } finally { + lock.unlock(); + } + + return true; + } + + public static void generateSpareMesh( JavaPlugin plugin, World world, Collection< Vector > locations, Consumer< SparseMesh > callback ) { if ( locations.isEmpty() ) { return; } - - boolean set = false; - Vector min = new Vector(); - Vector max = new Vector(); - - for ( Vector v : locations ) { - if ( !set ) { - set = true; - - min = v.toBlockVector(); - max = v.toBlockVector(); - } else { - min = Vector.getMinimum( min, v.toBlockVector() ); - max = Vector.getMaximum( max, v.toBlockVector() ); - } - } - + + boolean set = false; + Vector min = new Vector(); + Vector max = new Vector(); + + for ( Vector v : locations ) { + if ( !set ) { + set = true; + + min = v.toBlockVector(); + max = v.toBlockVector(); + } else { + min = Vector.getMinimum( min, v.toBlockVector() ); + max = Vector.getMaximum( max, v.toBlockVector() ); + } + } + final SparseMesh sparseMesh = new SparseMesh( min, max ); final Map< MinecraftPlane, Collection< Polygon > > polygons = new TreeMap< MinecraftPlane, Collection< Polygon > >(); - + { final Function< MinecraftPlane, Collection< Polygon > > getPolygons = p -> { Collection< Polygon > polygonCollection = polygons.get( p ); @@ -359,29 +375,29 @@ public class MeshEnvironment { } return polygonCollection; }; - + for ( final Vector location : locations ) { - final Block block = world.getBlockAt( location.getBlockX(), location.getBlockY(), location.getBlockZ() ); - - if ( !( block.isEmpty() && block.isLiquid() ) ) { + final Block block = world.getBlockAt( location.getBlockX(), location.getBlockY(), location.getBlockZ() ); + + if ( !( block.isEmpty() && block.isLiquid() ) ) { final BlockData data = block.getBlockData(); final Location loc = block.getLocation(); - + // Start the box at 0, 0, 0 final BoundingBox[] boundingBoxes = NmsUtil.getShape( data, loc, BlockShapeType.COLLISION_SHAPE ).e().stream().map( aabb -> new BoundingBox( aabb.a, aabb.b, aabb.c, aabb.d, aabb.e, aabb.f ) ).toArray( BoundingBox[]::new ); - + final BlockDataInformation information = new BlockDataInformation( data ); - + if ( boundingBoxes.length == 0 ) { // No bounding box - information.type = BlockDataType.NONE; + information.type = BlockDataType.NONE; } else if ( boundingBoxes.length == 1 && boundingBoxes[ 0 ].getVolume() == 1 ) { // Solid - information.type = BlockDataType.SOLID; + information.type = BlockDataType.SOLID; } else { - information.type = BlockDataType.COMPLEX; + information.type = BlockDataType.COMPLEX; // Complex bounding shape - + // Add the voxel shape to the chunk mesh for ( final BoundingBox aabb : boundingBoxes ) { final double minX = aabb.getMinX() + loc.getBlockX() - min.getBlockX(); @@ -390,26 +406,26 @@ public class MeshEnvironment { final double maxX = aabb.getMaxX() + loc.getBlockX() - min.getBlockX(); final double maxY = aabb.getMaxY() + loc.getBlockY() - min.getBlockY(); final double maxZ = aabb.getMaxZ() + loc.getBlockZ() - min.getBlockZ(); - + final Polygon xPoly = new Polygon( Arrays.asList( new Point( minY, minZ ), new Point( minY, maxZ ), new Point( maxY, maxZ ), new Point( maxY, minZ ) - ) ); + ) ); final Polygon yPoly = new Polygon( Arrays.asList( new Point( minX, minZ ), new Point( minX, maxZ ), new Point( maxX, maxZ ), new Point( maxX, minZ ) - ) ); + ) ); final Polygon zPoly = new Polygon( Arrays.asList( new Point( minX, minY ), new Point( minX, maxY ), new Point( maxX, maxY ), new Point( maxX, minY ) - ) ); - + ) ); + getPolygons.apply( new MinecraftPlane( PlaneAxis.X, minX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.X, maxX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.Y, minY ) ).add( yPoly ); @@ -417,89 +433,89 @@ public class MeshEnvironment { getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, minZ ) ).add( zPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, maxZ ) ).add( zPoly ); } - + information.boundingBoxes = boundingBoxes; sparseMesh.setBlockDataInformationFor( information, loc.toVector().subtract( min ) ); } - } + } } - + // Now generate facets for each solid block based on if the side they are on is visible for ( final Vector location : locations ) { - final Vector blockVector = location.toBlockVector().subtract( min ); - final BlockDataInformation info = sparseMesh.getBlockDataInformationFor( blockVector ); - if ( info != null ) { - if ( info.type == BlockDataType.SOLID ) { - final Polygon xPoly = new Polygon( Arrays.asList( - new Point( blockVector.getY(), blockVector.getZ() ), - new Point( blockVector.getY(), blockVector.getZ() + 1 ), - new Point( blockVector.getY() + 1, blockVector.getZ() + 1 ), - new Point( blockVector.getY() + 1, blockVector.getZ() ) - ) ); - final Polygon yPoly = new Polygon( Arrays.asList( - new Point( blockVector.getX(), blockVector.getZ() ), - new Point( blockVector.getX(), blockVector.getZ() + 1 ), - new Point( blockVector.getX() + 1, blockVector.getZ() + 1 ), - new Point( blockVector.getX() + 1, blockVector.getZ() ) - ) ); - final Polygon zPoly = new Polygon( Arrays.asList( - new Point( blockVector.getX(), blockVector.getY() ), - new Point( blockVector.getX(), blockVector.getY() + 1 ), - new Point( blockVector.getX() + 1, blockVector.getY() + 1 ), - new Point( blockVector.getX() + 1, blockVector.getY() ) - ) ); - - BlockDataInformation adjacent; - if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().subtract( new Vector( 0, 1, 0 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { - getPolygons.apply( new MinecraftPlane( PlaneAxis.Y, blockVector.getY() ) ).add( yPoly ); - } - - if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().add( new Vector( 0, 1, 0 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { - getPolygons.apply( new MinecraftPlane( PlaneAxis.Y, blockVector.getY() + 1 ) ).add( yPoly ); - } - - if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().subtract( new Vector( 0, 0, 1 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { - getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, blockVector.getZ() ) ).add( zPoly ); - } - - if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().add( new Vector( 0, 0, 1 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { - getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, blockVector.getZ() + 1 ) ).add( zPoly ); - } - - if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().subtract( new Vector( 1, 0, 0 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { - getPolygons.apply( new MinecraftPlane( PlaneAxis.X, blockVector.getX() ) ).add( xPoly ); - } - - if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().add( new Vector( 1, 0, 0 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { - getPolygons.apply( new MinecraftPlane( PlaneAxis.X, blockVector.getX() + 1 ) ).add( xPoly ); - } - } - } else { - // Shouldn't be null btw - } + final Vector blockVector = location.toBlockVector().subtract( min ); + final BlockDataInformation info = sparseMesh.getBlockDataInformationFor( blockVector ); + if ( info != null ) { + if ( info.type == BlockDataType.SOLID ) { + final Polygon xPoly = new Polygon( Arrays.asList( + new Point( blockVector.getY(), blockVector.getZ() ), + new Point( blockVector.getY(), blockVector.getZ() + 1 ), + new Point( blockVector.getY() + 1, blockVector.getZ() + 1 ), + new Point( blockVector.getY() + 1, blockVector.getZ() ) + ) ); + final Polygon yPoly = new Polygon( Arrays.asList( + new Point( blockVector.getX(), blockVector.getZ() ), + new Point( blockVector.getX(), blockVector.getZ() + 1 ), + new Point( blockVector.getX() + 1, blockVector.getZ() + 1 ), + new Point( blockVector.getX() + 1, blockVector.getZ() ) + ) ); + final Polygon zPoly = new Polygon( Arrays.asList( + new Point( blockVector.getX(), blockVector.getY() ), + new Point( blockVector.getX(), blockVector.getY() + 1 ), + new Point( blockVector.getX() + 1, blockVector.getY() + 1 ), + new Point( blockVector.getX() + 1, blockVector.getY() ) + ) ); + + BlockDataInformation adjacent; + if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().subtract( new Vector( 0, 1, 0 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { + getPolygons.apply( new MinecraftPlane( PlaneAxis.Y, blockVector.getY() ) ).add( yPoly ); + } + + if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().add( new Vector( 0, 1, 0 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { + getPolygons.apply( new MinecraftPlane( PlaneAxis.Y, blockVector.getY() + 1 ) ).add( yPoly ); + } + + if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().subtract( new Vector( 0, 0, 1 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { + getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, blockVector.getZ() ) ).add( zPoly ); + } + + if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().add( new Vector( 0, 0, 1 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { + getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, blockVector.getZ() + 1 ) ).add( zPoly ); + } + + if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().subtract( new Vector( 1, 0, 0 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { + getPolygons.apply( new MinecraftPlane( PlaneAxis.X, blockVector.getX() ) ).add( xPoly ); + } + + if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().add( new Vector( 1, 0, 0 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { + getPolygons.apply( new MinecraftPlane( PlaneAxis.X, blockVector.getX() + 1 ) ).add( xPoly ); + } + } + } else { + // Shouldn't be null btw + } } } - + Bukkit.getScheduler().runTaskAsynchronously( plugin, () -> { // Mesh the data and get the output final MeshResult meshResult = generateMeshFrom( polygons ); - + sparseMesh.planes = meshResult.meshes; sparseMesh.pointReferences = meshResult.points; - + callback.accept( sparseMesh ); } ); } - + // TODO Use ChunkSnapshot in the future - private static void generateMesh( JavaPlugin plugin, World world, final Chunk chunk, Consumer< ChunkMesh > callback ) { + public static void generateMesh( JavaPlugin plugin, World world, final Chunk chunk, Consumer< ChunkMesh > callback ) { final int worldMinHeight = world.getMinHeight(); final int worldMaxHeight = world.getMaxHeight(); final int worldHeight = worldMaxHeight - worldMinHeight; - + final ChunkMesh chunkMesh = new ChunkMesh( new BoundingBox( 0, 0, 0, 16, worldHeight, 16 ) ); final Map< MinecraftPlane, Collection< Polygon > > polygons = new TreeMap< MinecraftPlane, Collection< Polygon > >(); - + { final Function< MinecraftPlane, Collection< Polygon > > getPolygons = p -> { Collection< Polygon > polygonCollection = polygons.get( p ); @@ -509,7 +525,7 @@ public class MeshEnvironment { } return polygonCollection; }; - + // When we would have inserted each facet, instead, find or create the // corresponding plane, and add directly. // Also, create a chunk mesh and populate the structs @@ -524,10 +540,10 @@ public class MeshEnvironment { if ( !( block.isEmpty() && block.isLiquid() ) ) { final BlockData data = block.getBlockData(); final Location loc = block.getLocation(); - + // Start the box at 0, 0, 0 final BoundingBox[] boundingBoxes = NmsUtil.getShape( data, loc, BlockShapeType.COLLISION_SHAPE ).e().stream().map( aabb -> new BoundingBox( aabb.a, aabb.b, aabb.c, aabb.d, aabb.e, aabb.f ) ).toArray( BoundingBox[]::new ); - + if ( boundingBoxes.length == 0 ) { // No bounding box chunkMesh.setType( index, BlockDataType.NONE ); @@ -537,10 +553,10 @@ public class MeshEnvironment { } else { chunkMesh.setType( index, BlockDataType.COMPLEX ); // Complex bounding shape - + // Only store the old block data if we know it is not empty or solid!! chunkMesh.setData( index, data ); - + // Add the voxel shape to the chunk mesh for ( final BoundingBox aabb : boundingBoxes ) { final double minX = aabb.getMinX() + x; @@ -549,26 +565,26 @@ public class MeshEnvironment { final double maxX = aabb.getMaxX() + x; final double maxY = aabb.getMaxY() + relY; final double maxZ = aabb.getMaxZ() + z; - + final Polygon xPoly = new Polygon( Arrays.asList( new Point( minY, minZ ), new Point( minY, maxZ ), new Point( maxY, maxZ ), new Point( maxY, minZ ) - ) ); + ) ); final Polygon yPoly = new Polygon( Arrays.asList( new Point( minX, minZ ), new Point( minX, maxZ ), new Point( maxX, maxZ ), new Point( maxX, minZ ) - ) ); + ) ); final Polygon zPoly = new Polygon( Arrays.asList( new Point( minX, minY ), new Point( minX, maxY ), new Point( maxX, maxY ), new Point( maxX, minY ) - ) ); - + ) ); + getPolygons.apply( new MinecraftPlane( PlaneAxis.X, minX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.X, maxX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.Y, minY ) ).add( yPoly ); @@ -576,7 +592,7 @@ public class MeshEnvironment { getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, minZ ) ).add( zPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, maxZ ) ).add( zPoly ); } - + chunkMesh.setBoxes( index, boundingBoxes ); } } else { @@ -585,7 +601,7 @@ public class MeshEnvironment { } } } - + // Now generate facets for each solid block based on if the side they are on is visible for ( int y = 0; y < worldHeight; ++y ) { final int yIndex = y << 8; @@ -599,41 +615,41 @@ public class MeshEnvironment { new Point( y, z + 1 ), new Point( y + 1, z + 1 ), new Point( y + 1, z ) - ) ); + ) ); final Polygon yPoly = new Polygon( Arrays.asList( new Point( x, z ), new Point( x, z + 1 ), new Point( x + 1, z + 1 ), new Point( x + 1, z ) - ) ); + ) ); final Polygon zPoly = new Polygon( Arrays.asList( new Point( x, y ), new Point( x, y + 1 ), new Point( x + 1, y + 1 ), new Point( x + 1, y ) - ) ); - + ) ); + // Check each face if ( y == 0 || chunkMesh.getType( index - 256 ) != BlockDataType.SOLID ) { getPolygons.apply( new MinecraftPlane( PlaneAxis.Y, y ) ).add( yPoly ); } - + if ( y == worldHeight - 1 || chunkMesh.getType( index + 256 ) != BlockDataType.SOLID ) { getPolygons.apply( new MinecraftPlane( PlaneAxis.Y, y + 1 ) ).add( yPoly ); } - + if ( z == 0 || chunkMesh.getType( index - 16 ) != BlockDataType.SOLID ) { getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, z ) ).add( zPoly ); } - + if ( z == 15 || chunkMesh.getType( index + 16 ) != BlockDataType.SOLID ) { getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, z + 1 ) ).add( zPoly ); } - + if ( x == 0 || chunkMesh.getType( index - 1 ) != BlockDataType.SOLID ) { getPolygons.apply( new MinecraftPlane( PlaneAxis.X, x ) ).add( xPoly ); } - + if ( x == 15 || chunkMesh.getType( index + 1 ) != BlockDataType.SOLID ) { getPolygons.apply( new MinecraftPlane( PlaneAxis.X, x + 1 ) ).add( xPoly ); } @@ -642,23 +658,23 @@ public class MeshEnvironment { } } } - + Bukkit.getScheduler().runTaskAsynchronously( plugin, () -> { // Mesh the data and get the output final MeshResult meshResult = generateMeshFrom( polygons ); - + chunkMesh.planes = meshResult.meshes; chunkMesh.pointReferences = meshResult.points; - + callback.accept( chunkMesh ); } ); } - - private static Collection< Location > update( final AbstractMesh mesh, final Collection< Location > locations ) { + + public static Collection< Location > update( final AbstractMesh mesh, final Collection< Location > locations ) { if ( locations.isEmpty() ) { return Collections.emptySet(); } - + final Collection< Location > canRemove = new ArrayDeque< Location >(); // Check each location final Map< MinecraftPlane, Collection< Polygon > > polygons = new TreeMap< MinecraftPlane, Collection< Polygon > >(); @@ -670,41 +686,41 @@ public class MeshEnvironment { } return polygonCollection; }; - + final BiFunction< List< BoundingBox >, BoundingBox[], Boolean > isEqual = ( a, b ) -> { if ( a.size() == b.length ) { for ( int i = 0; i < b.length; ++i ) { final BoundingBox b1 = a.get( i ); final BoundingBox b2 = b[ i ]; - + if ( !b1.equals( b2 ) ) { return false; } } } - + return true; }; - + for ( final Location location : locations ) { final BlockData data = location.getBlock().getBlockData(); - + // Defer moving piston updates until another tick when it's done moving if ( data.getMaterial() == Material.MOVING_PISTON ) { continue; } - + final Vector position = location.toVector().subtract( mesh.getMinimumCorner() ); final BlockDataInformation information = mesh.getBlockDataInformationFor( position ); if ( information != null ) { - canRemove.add( location ); - if ( information.isBlockData( data ) ) { - // Ignore if the block data has not changed - continue; - } else { - final List< BoundingBox > boxes = NmsUtil.getShape( data, location, BlockShapeType.COLLISION_SHAPE ).e().stream() + canRemove.add( location ); + if ( information.isBlockData( data ) ) { + // Ignore if the block data has not changed + continue; + } else { + final List< BoundingBox > boxes = NmsUtil.getShape( data, location, BlockShapeType.COLLISION_SHAPE ).e().stream() .map( aabb -> new BoundingBox( aabb.a, aabb.b, aabb.c, aabb.d, aabb.e, aabb.f ) ).collect( Collectors.toList() ); - final BlockDataType oldType = information.type; + final BlockDataType oldType = information.type; final BoundingBox[] otherBoxes = information.boundingBoxes; if ( boxes.size() == 0 ) { if ( oldType == BlockDataType.NONE ) { @@ -714,7 +730,7 @@ public class MeshEnvironment { BlockDataInformation newInfo = new BlockDataInformation( data ); newInfo.type = BlockDataType.NONE; newInfo.boundingBoxes = null; - + mesh.setBlockDataInformationFor( newInfo, position ); } else if ( boxes.size() == 1 && boxes.get( 0 ).getVolume() == 1 ) { if ( oldType == BlockDataType.SOLID ) { @@ -724,7 +740,7 @@ public class MeshEnvironment { BlockDataInformation newInfo = new BlockDataInformation( data ); newInfo.type = BlockDataType.SOLID; newInfo.boundingBoxes = null; - + mesh.setBlockDataInformationFor( newInfo, position ); } else { if ( otherBoxes != null && isEqual.apply( boxes, otherBoxes ) ) { @@ -734,22 +750,22 @@ public class MeshEnvironment { BlockDataInformation newInfo = new BlockDataInformation( data ); newInfo.type = BlockDataType.COMPLEX; newInfo.boundingBoxes = boxes.toArray( BoundingBox[]::new ); - + mesh.setBlockDataInformationFor( newInfo, position ); } - + if ( oldType == BlockDataType.SOLID ) { boxes.add( new BoundingBox( 0, 0, 0, 1, 1, 1 ) ); } else if ( oldType == BlockDataType.COMPLEX ) { if ( otherBoxes == null || otherBoxes.length == 0 ) { throw new IllegalStateException( "Complex shape but no bounding boxes!" ); } - + for ( final BoundingBox box : otherBoxes ) { boxes.add( box ); } } - + // Add the voxel shape to the chunk mesh for ( final BoundingBox aabb : boxes ) { final double minX = aabb.getMinX() + position.getX(); @@ -758,26 +774,26 @@ public class MeshEnvironment { final double maxX = aabb.getMaxX() + position.getX(); final double maxY = aabb.getMaxY() + position.getY(); final double maxZ = aabb.getMaxZ() + position.getZ(); - + final Polygon xPoly = new Polygon( Arrays.asList( new Point( minY, minZ ), new Point( minY, maxZ ), new Point( maxY, maxZ ), new Point( maxY, minZ ) - ) ); + ) ); final Polygon yPoly = new Polygon( Arrays.asList( new Point( minX, minZ ), new Point( minX, maxZ ), new Point( maxX, maxZ ), new Point( maxX, minZ ) - ) ); + ) ); final Polygon zPoly = new Polygon( Arrays.asList( new Point( minX, minY ), new Point( minX, maxY ), new Point( maxX, maxY ), new Point( maxX, minY ) - ) ); - + ) ); + getPolygons.apply( new MinecraftPlane( PlaneAxis.X, minX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.X, maxX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.Y, minY ) ).add( yPoly ); @@ -785,63 +801,63 @@ public class MeshEnvironment { getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, minZ ) ).add( zPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, maxZ ) ).add( zPoly ); } - } + } } } - + // No polygons found... which means nothing changed if ( polygons.isEmpty() ) { return canRemove; } - + final Map< MinecraftPlane, Mesh< RegionSimple > > meshMap = new HashMap< MinecraftPlane, Mesh< RegionSimple > >(); - + // Now mesh the blocks asynchronously and combine, then do the bare minimum to regenerate the chunk mesh for ( final Entry< MinecraftPlane, Collection< Polygon > > entry : polygons.entrySet() ) { final MinecraftPlane plane = entry.getKey(); final PlaneMesh planeMesh = mesh.planes.remove( plane ); final Mesh< RegionSimple > mesher = new Mesh< RegionSimple >( () -> new RegionSimple( GluWindingRule.ODD ) ); - + if ( planeMesh != null ) { // Reset all indexed polygons(triangles) for this plane for ( final IndexedTriangle poly : planeMesh.completedPolygons ) { poly.getPoints().parallelStream().forEach( p -> --p.referenceCount ); } - + // Add all polygons that were previously generated for ( final Polygon polygon : planeMesh.generatedRegions ) { - mesher.addPolygon( polygon, RegionRuleWinding.CLOCKWISE ); + mesher.addPolygon( polygon, RegionRuleWinding.CLOCKWISE ); } } for ( final Polygon polygon : entry.getValue() ) { - mesher.addPolygon( polygon, RegionRuleWinding.CLOCKWISE ); + mesher.addPolygon( polygon, RegionRuleWinding.CLOCKWISE ); } - + meshMap.put( plane, mesher ); } - + // Mesh each plane final Map< MinecraftPlane, CompleteMesh > triangles = meshMap.entrySet().parallelStream().collect( Collectors.toConcurrentMap( e -> e.getKey(), e -> new CompleteMesh( e.getKey(), e.getValue() ) ) ); - + for ( final Entry< MinecraftPlane, CompleteMesh > entry : triangles.entrySet() ) { final MinecraftPlane plane = entry.getKey(); final CompleteMesh completeMesh = entry.getValue(); final Collection< Polygon > tris = completeMesh.triangles; final Mesh< RegionSimple > mesher = completeMesh.mesh; - + // Ignore any planes that are empty if ( !tris.isEmpty() ) { final PlaneMesh newMesh = new PlaneMesh(); newMesh.completedPolygons = new ArrayList< IndexedTriangle >(); for ( final Polygon tri : tris ) { - final IndexedTriangle newPoly = new IndexedTriangle(); + final IndexedTriangle newPoly = new IndexedTriangle(); for ( int i = 0; i < tri.getPoints().size() && i < 3; ++i ) { - final Point point = tri.getPoints().get( i ); + final Point point = tri.getPoints().get( i ); final Vector vert = plane.convert( point ); - + // Find an existing vertex reference if one exists final VectorReference tempRef = new VectorReference( vert ); - + VectorReference ref = null; final VectorReference upperRef = mesh.pointReferences.ceiling( tempRef ); if ( upperRef != null && upperRef.vector.distanceSquared( vert ) < 1e-8 ) { @@ -852,7 +868,7 @@ public class MeshEnvironment { ref = lowerRef; } } - + if ( ref == null ) { ref = tempRef; ref.referenceCount = 1; @@ -860,39 +876,39 @@ public class MeshEnvironment { } else { ++ref.referenceCount; } - + switch ( i ) { - case 0: - newPoly.setPoint1( ref ); - break; - case 1: - newPoly.setPoint2( ref ); - break; - case 2: - newPoly.setPoint3( ref ); - break; - default: - // Should never be here! - } + case 0: + newPoly.setPoint1( ref ); + break; + case 1: + newPoly.setPoint2( ref ); + break; + case 2: + newPoly.setPoint3( ref ); + break; + default: + // Should never be here! + } } newMesh.completedPolygons.add( newPoly ); } - + // Save the generated regions as an intermediary step so we can re-use them later newMesh.generatedRegions = mesher.getPolygons(); - + mesh.planes.put( plane, newMesh ); } } - + // Remove all vector references that have a reference count of 0 mesh.pointReferences.removeIf( v -> v.referenceCount <= 0 ); - + return canRemove; } - - private static MeshResult generateMeshFrom( final Map< MinecraftPlane, Collection< Polygon > > polygons ) { - final Map< MinecraftPlane, Mesh< RegionSimple > > meshMap = new HashMap< MinecraftPlane, Mesh< RegionSimple > >(); + + public static MeshResult generateMeshFrom( final Map< MinecraftPlane, Collection< Polygon > > polygons ) { + final Map< MinecraftPlane, Mesh< RegionSimple > > meshMap = new HashMap< MinecraftPlane, Mesh< RegionSimple > >(); for ( final Entry< MinecraftPlane, Collection< Polygon > > entry : polygons.entrySet() ) { final MinecraftPlane plane = entry.getKey(); final Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> new RegionSimple( GluWindingRule.ODD ) ); @@ -901,20 +917,20 @@ public class MeshEnvironment { } meshMap.put( plane, mesh ); } - + // Mesh each plane final Map< MinecraftPlane, CompleteMesh > triangles = meshMap.entrySet().parallelStream().collect( Collectors.toConcurrentMap( e -> e.getKey(), e -> new CompleteMesh( e.getKey(), e.getValue() ) ) ); - + // Construct a series of indexed polygons final TreeSet< VectorReference > vertices = new TreeSet< VectorReference >(); - + final MeshResult result = new MeshResult(); for ( final Entry< MinecraftPlane, CompleteMesh > entry : triangles.entrySet() ) { final MinecraftPlane plane = entry.getKey(); final CompleteMesh completeMesh = entry.getValue(); final Collection< Polygon > tris = completeMesh.triangles; final Mesh< RegionSimple > mesh = completeMesh.mesh; - + // Ignore any planes that are empty if ( !tris.isEmpty() ) { final PlaneMesh newMesh = new PlaneMesh(); @@ -922,63 +938,63 @@ public class MeshEnvironment { for ( final Polygon tri : tris ) { final IndexedTriangle newPoly = new IndexedTriangle(); for ( int i = 0; i < tri.getPoints().size() && i < 3; ++i ) { - final Point point = tri.getPoints().get( i ); - final Vector vert = plane.convert( point ); - - final VectorReference tempRef = new VectorReference( vert ); - // Find an existing vertex reference if one exists - VectorReference ref = null; - if ( ref == null ) { - final VectorReference upperRef = vertices.ceiling( tempRef ); - if ( upperRef != null && upperRef.vector.distanceSquared( vert ) < 1e-8 ) { - ref = upperRef; - } else { - final VectorReference lowerRef = vertices.floor( tempRef ); - if ( lowerRef != null && lowerRef.vector.distanceSquared( vert ) < 1e-8 ) { - ref = lowerRef; - } - } - } - - if ( ref == null ) { - ref = tempRef; - ref.referenceCount = 1; - vertices.add( ref ); - } else { - ++ref.referenceCount; - } - - switch ( i ) { - case 0: - newPoly.setPoint1( ref ); - break; - case 1: - newPoly.setPoint2( ref ); - break; - case 2: - newPoly.setPoint3( ref ); - break; - default: - // Should never be here! - } - + final Point point = tri.getPoints().get( i ); + final Vector vert = plane.convert( point ); + + final VectorReference tempRef = new VectorReference( vert ); + // Find an existing vertex reference if one exists + VectorReference ref = null; + if ( ref == null ) { + final VectorReference upperRef = vertices.ceiling( tempRef ); + if ( upperRef != null && upperRef.vector.distanceSquared( vert ) < 1e-8 ) { + ref = upperRef; + } else { + final VectorReference lowerRef = vertices.floor( tempRef ); + if ( lowerRef != null && lowerRef.vector.distanceSquared( vert ) < 1e-8 ) { + ref = lowerRef; + } + } + } + + if ( ref == null ) { + ref = tempRef; + ref.referenceCount = 1; + vertices.add( ref ); + } else { + ++ref.referenceCount; + } + + switch ( i ) { + case 0: + newPoly.setPoint1( ref ); + break; + case 1: + newPoly.setPoint2( ref ); + break; + case 2: + newPoly.setPoint3( ref ); + break; + default: + // Should never be here! + } + } newMesh.completedPolygons.add( newPoly ); } - + // Save the generated regions as an intermediary step so we can re-use them later newMesh.generatedRegions = mesh.getPolygons(); - + result.meshes.put( plane, newMesh ); } } - + // Add all vector references result.points = vertices; - + return result; } - + private static class CompleteMesh { Mesh< RegionSimple > mesh; Collection< Polygon > triangles; @@ -988,16 +1004,16 @@ public class MeshEnvironment { triangles = mesh.meshify(); } } - - private static class MeshResult { - Map< MinecraftPlane, PlaneMesh > meshes; - TreeSet< VectorReference > points; + + public static class MeshResult { + Map< MinecraftPlane, PlaneMesh > meshes; + TreeSet< VectorReference > points; } - + private enum ChunkTaskType { LOAD, UNLOAD } - + private class ChunkTask { BukkitTask task; ChunkTaskType type; diff --git a/plugin/environment/src/main/resources/plugin.yml b/plugin/environment/src/main/resources/plugin.yml index 6e7032b..b5fa074 100644 --- a/plugin/environment/src/main/resources/plugin.yml +++ b/plugin/environment/src/main/resources/plugin.yml @@ -1,14 +1,6 @@ name: MC-Mesh-Environment -main: com.aaaaahhhhhhh.bananapuncher714.minietest.MiniePlugin +main: com.aaaaahhhhhhh.bananapuncher714.mesh.environment.McMeshEnvironmentPlugin version: 0.0.1 description: BulletJME and MC-Mesh integration author: BananaPuncher714 -api-version: 1.13 -#libraries: -#- com.github.stephengold:Minie:7.5.0 - -commands: - minie: - description: Main Minie command - aliases: [] - permission: minie \ No newline at end of file +api-version: 1.13 \ No newline at end of file diff --git a/plugin/physics/pom.xml b/plugin/physics/pom.xml new file mode 100644 index 0000000..1ba62d7 --- /dev/null +++ b/plugin/physics/pom.xml @@ -0,0 +1,53 @@ + + 4.0.0 + + com.aaaaahhhhhhh.bananapuncher714 + mc-mesh-plugin + 0.0.1 + + mc-mesh-plugin-physics + 0.0.1-SNAPSHOT + + + + org.spigotmc + spigot + 1.21.10-R0.1-SNAPSHOT + provided + + + com.aaaaahhhhhhh.bananapuncher714 + mc-mesh-plugin-environment + 0.0.1-SNAPSHOT + provided + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + 21 + + + + + \ No newline at end of file diff --git a/plugin/physics/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/physics/AuxiliaryDisplayStruct.java b/plugin/physics/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/physics/AuxiliaryDisplayStruct.java new file mode 100644 index 0000000..85ecf04 --- /dev/null +++ b/plugin/physics/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/physics/AuxiliaryDisplayStruct.java @@ -0,0 +1,22 @@ +package com.aaaaahhhhhhh.bananapuncher714.mesh.physics; + +import org.bukkit.entity.Display; +import org.bukkit.util.Vector; + +class AuxiliaryDisplayStruct { + Display display; + Vector offset; + Vector scale; + + 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; + } +} \ No newline at end of file diff --git a/plugin/physics/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/physics/McMeshPhysicsPlugin.java b/plugin/physics/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/physics/McMeshPhysicsPlugin.java new file mode 100644 index 0000000..693fbb3 --- /dev/null +++ b/plugin/physics/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/physics/McMeshPhysicsPlugin.java @@ -0,0 +1,342 @@ +package com.aaaaahhhhhhh.bananapuncher714.mesh.physics; + +import java.io.File; +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.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Player; +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.mesh.command.CommandParameters; +import com.aaaaahhhhhhh.bananapuncher714.mesh.command.SubCommand; +import com.aaaaahhhhhhh.bananapuncher714.mesh.command.executor.CommandExecutableMessage; +import com.aaaaahhhhhhh.bananapuncher714.mesh.command.validator.InputValidatorDouble; +import com.aaaaahhhhhhh.bananapuncher714.mesh.command.validator.InputValidatorInt; +import com.aaaaahhhhhhh.bananapuncher714.mesh.command.validator.sender.SenderValidatorPlayer; +import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.EnvironmentHandler; +import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.MeshEnvironment; +import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.ChunkLocation; +import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.ChunkMesh; +import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.util.NmsUtil; +import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.util.NmsUtil.BlockShapeType; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsSpace.BroadphaseType; +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.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.collision.shapes.infos.IndexedMesh; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; + +public class McMeshPhysicsPlugin extends JavaPlugin { + private static final int TIME_STEPS = 5; + private static final float SIMULATION_SPEED = 1f; + + private MeshEnvironment environment; + + 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 Map< ChunkLocation, PhysicsRigidBody > chunks = new HashMap< ChunkLocation, PhysicsRigidBody >(); + + @Override + public void onEnable() { + registerCommands(); + + space = new PhysicsSpace( BroadphaseType.DBVT ); + space.setAccuracy( 1f / ( TIME_STEPS * 20f ) ); + + environment = new MeshEnvironment( this, new File( getDataFolder(), "environment" ) ); + environment.setHandler( new EnvironmentHandler() { + @Override + public void onMeshLoad( ChunkLocation location, ChunkMesh mesh ) { + // Now, create a physics object from the chunk mesh + final IndexedMesh nativeMesh = mesh.toIndexedMesh(); + // Enable quantized AABB compression... hopefully that doesn't mess with the accuracy or anything... + final MeshCollisionShape meshCollision = new MeshCollisionShape( true, nativeMesh ); + + // Create a rigid body + PhysicsRigidBody rigid = new PhysicsRigidBody( meshCollision, PhysicsBody.massForStatic ); + rigid.setPhysicsLocation( new Vector3f( location.getX() << 4, location.getWorld().getMinHeight(), location.getZ() << 4 ) ); + rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) ); + rigid.setKinematic( false ); + + Bukkit.getScheduler().runTask( McMeshPhysicsPlugin.this, () -> { + space.addCollisionObject( rigid ); + + PhysicsRigidBody old = chunks.put( location, rigid ); + if ( old != null ) { + space.remove( old ); + } + } ); + } + + @Override + public void onMeshUnload( ChunkLocation location, ChunkMesh mesh ) { + space.removeCollisionObject( chunks.get( location ) ); + } + + @Override + public void onMeshUpdate( ChunkLocation location, ChunkMesh mesh ) { + // Now, create a physics object from the chunk mesh + final IndexedMesh nativeMesh = mesh.toIndexedMesh(); + // Enable quantized AABB compression... hopefully that doesn't mess with the accuracy or anything... + final MeshCollisionShape meshCollision = new MeshCollisionShape( true, nativeMesh ); + + // Create a rigid body + PhysicsRigidBody rigid = new PhysicsRigidBody( meshCollision, PhysicsBody.massForStatic ); + rigid.setPhysicsLocation( new Vector3f( location.getX() << 4, location.getWorld().getMinHeight(), location.getZ() << 4 ) ); + rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) ); + rigid.setKinematic( false ); + + Bukkit.getScheduler().runTask( McMeshPhysicsPlugin.this, () -> { + space.addCollisionObject( rigid ); + + PhysicsRigidBody old = chunks.put( location, rigid ); + if ( old != null ) { + space.remove( old ); + } + } ); + + space.activateAll( true ); + } + } ); + + 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< 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 ); + + 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" ) + .add( new SubCommand( new InputValidatorDouble( 0.01, 100 ) ) + .defaultTo( this::spawnBlock ) ) + .defaultTo( this::spawnBlock ) ) + .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 void spawnBlock( final CommandSender sender, final String[] args, final CommandParameters params ) { + Player player = ( Player ) sender; + Location loc = player.getLocation(); + + // Optional scale + double scale = 1; + if ( params.size() > 2 ) { + scale = params.getFirst( double.class ); + } + + loc.setPitch( 0 ); + loc.setDirection( new Vector( 0, 0, 0 ) ); + + BlockData displayData; + ItemStack item = player.getInventory().getItemInMainHand(); + if ( item != null && item.getType() != Material.AIR && item.getType().isBlock() ) { + displayData = item.getType().createBlockData(); + } else { + displayData = Material.TNT.createBlockData(); + } + + BoundingBox[] boxes = NmsUtil.convertFrom( NmsUtil.getShape( displayData, null, BlockShapeType.SHAPE ) ); + Vector blockCenter = calculateCenter( boxes ); + + CollisionShape box; + if ( boxes.length == 0 ) { + player.sendMessage( "No bounding box detected! Wrong method?" ); + return; + } + + if ( boxes.length == 1 ) { + Vector min = boxes[ 0 ].getMin(); + Vector center = boxes[ 0 ].getCenter().subtract( min ); + box = new BoxCollisionShape( ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() ); + } else { + CompoundCollisionShape compound = new CompoundCollisionShape(); + for ( BoundingBox aabb : boxes ) { + Vector min = aabb.getMin(); + Vector center = aabb.getCenter(); + Vector half = center.clone().subtract( min ); + center.subtract( blockCenter ); + CollisionShape subShape = new BoxCollisionShape( ( float ) half.getX(), ( float ) half.getY(), ( float ) half.getZ() ); + compound.addChildShape( subShape, ( float ) center.getX(), ( float ) center.getY(), ( float ) center.getZ() ); + } + box = compound; + } + + box.setScale( ( float ) scale ); + + PhysicsRigidBody rigid = new PhysicsRigidBody( box, 1f ); + + // Since the center of the rigid body and the box shape are different, we need to offset the location + rigid.setPhysicsLocation( new Vector3f( ( float ) ( loc.getX() + blockCenter.getX() ), ( float ) ( loc.getY() + blockCenter.getY() ), ( float ) ( loc.getZ() + blockCenter.getZ() ) ) ); + + // Convert the display direction vector to a quaternion + rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) ); + + rigid.setMass( rigid.getMass() * ( float ) ( scale * scale ) ); + + space.addCollisionObject( rigid ); + + // Create the display + BlockDisplay display = loc.getWorld().spawn( loc, BlockDisplay.class, d -> { + d.setBlock( displayData ); + d.setViewRange( 1000 ); + } ); + + linkedDisplays.put( rigid, new AuxiliaryDisplayStruct( display, blockCenter.multiply( scale ) ).setScale( new Vector( scale, scale, scale ) ) ); + + player.sendMessage( "Created display" ); + } + + 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 ); + } +} diff --git a/plugin/environment/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/environment/MiniePlugin.java b/plugin/physics/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/physics/MiniePlugin.java similarity index 98% rename from plugin/environment/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/environment/MiniePlugin.java rename to plugin/physics/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/physics/MiniePlugin.java index 9f6a4a6..55b4d0a 100644 --- a/plugin/environment/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/environment/MiniePlugin.java +++ b/plugin/physics/src/main/java/com/aaaaahhhhhhh/bananapuncher714/mesh/physics/MiniePlugin.java @@ -1,4 +1,4 @@ -package com.aaaaahhhhhhh.bananapuncher714.mesh.environment; +package com.aaaaahhhhhhh.bananapuncher714.mesh.physics; import java.io.File; import java.io.FileWriter; @@ -75,6 +75,7 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.BlockDataType; import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.ChunkLocation; import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.ChunkMesh; import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.IndexedPolygon; +import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.IndexedTriangle; import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.MinecraftPlane; import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.PlaneMesh; import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.VectorReference; @@ -1050,7 +1051,7 @@ public class MiniePlugin extends JavaPlugin { for ( final Entry< Vector, VectorRef > entry : vertices.entrySet() ) { final Vector existingVec = entry.getKey(); - if ( existingVec.distanceSquared( vec ) < 1e-8 ) { + if ( existingVec.distanceSquared( new Vector( vec.getX(), vec.getY(), vec.getZ() ) ) < 1e-8 ) { ref = entry.getValue(); break; } @@ -1058,7 +1059,7 @@ public class MiniePlugin extends JavaPlugin { if ( ref == null ) { ref = new VectorRef(); - vertices.put( vec, ref ); + vertices.put( new Vector( vec.getX(), vec.getY(), vec.getZ() ), ref ); } triangle.refs.add( ref ); @@ -1313,9 +1314,9 @@ public class MiniePlugin extends JavaPlugin { // Ignore any planes that are empty if ( !tris.isEmpty() ) { final PlaneMesh newMesh = new PlaneMesh(); - newMesh.completedPolygons = new ArrayList< IndexedPolygon >(); + newMesh.completedPolygons = new ArrayList< IndexedTriangle >(); for ( final Polygon tri : tris ) { - final IndexedPolygon newPoly = new IndexedPolygon(); + final IndexedTriangle newPoly = new IndexedTriangle(); for ( final Point point : tri.getPoints() ) { final Vector vert = plane.convert( point ); @@ -1342,7 +1343,7 @@ public class MiniePlugin extends JavaPlugin { ++ref.referenceCount; } - newPoly.points.add( ref ); +// newPoly.points.add( ref ); } newMesh.completedPolygons.add( newPoly ); } @@ -1536,7 +1537,7 @@ public class MiniePlugin extends JavaPlugin { if ( planeMesh != null ) { // Reset all indexed polygons(triangles) for this plane for ( final IndexedPolygon poly : planeMesh.completedPolygons ) { - poly.points.parallelStream().forEach( p -> --p.referenceCount ); +// poly.points.parallelStream().forEach( p -> --p.referenceCount ); } // Add all polygons that were previously generated @@ -1563,9 +1564,9 @@ public class MiniePlugin extends JavaPlugin { // Ignore any planes that are empty if ( !tris.isEmpty() ) { final PlaneMesh newMesh = new PlaneMesh(); - newMesh.completedPolygons = new ArrayList< IndexedPolygon >(); + newMesh.completedPolygons = new ArrayList< IndexedTriangle >(); for ( final Polygon tri : tris ) { - final IndexedPolygon newPoly = new IndexedPolygon(); + final IndexedTriangle newPoly = new IndexedTriangle(); for ( final Point point : tri.getPoints() ) { final Vector vert = plane.convert( point ); @@ -1591,7 +1592,7 @@ public class MiniePlugin extends JavaPlugin { ++ref.referenceCount; } - newPoly.points.add( ref ); +// newPoly.points.add( ref ); } newMesh.completedPolygons.add( newPoly ); } @@ -1667,12 +1668,12 @@ public class MiniePlugin extends JavaPlugin { } final Triangle triangle = new Triangle(); - for ( final Vector vec : facet.points ) { + for ( final Vector3d vec : facet.points ) { VectorRef ref = null; for ( final Entry< Vector, VectorRef > entry : vertices.entrySet() ) { final Vector existingVec = entry.getKey(); - if ( existingVec.distanceSquared( vec ) < 1e-8 ) { + if ( existingVec.distanceSquared( new Vector( vec.getX(), vec.getY(), vec.getZ() ) ) < 1e-8 ) { ref = entry.getValue(); break; } @@ -1680,7 +1681,7 @@ public class MiniePlugin extends JavaPlugin { if ( ref == null ) { ref = new VectorRef(); - vertices.put( vec, ref ); + vertices.put( new Vector( vec.getX(), vec.getY(), vec.getZ() ), ref ); } triangle.refs.add( ref ); @@ -1765,10 +1766,10 @@ public class MiniePlugin extends JavaPlugin { try ( FileWriter writer = new FileWriter( file ) ) { for ( final Facet facet : facets ) { - final Vector normal = facet.normal; + final Vector3d normal = facet.normal; writer.write( facet.points.size() + " " + normal.getX() + " " + normal.getY() + " " + normal.getZ() + "\n" ); - for ( final Vector point : facet.points ) { + for ( final Vector3d point : facet.points ) { writer.write( point.getX() + " " + point.getY() + " " + point.getZ() + "\n" ); } } diff --git a/plugin/physics/src/main/resources/plugin.yml b/plugin/physics/src/main/resources/plugin.yml new file mode 100644 index 0000000..255eac4 --- /dev/null +++ b/plugin/physics/src/main/resources/plugin.yml @@ -0,0 +1,15 @@ +name: MC-Mesh-Physics +main: com.aaaaahhhhhhh.bananapuncher714.mesh.physics.McMeshPhysicsPlugin +version: 0.0.1 +description: BulletJME and MC-Mesh integration +author: BananaPuncher714 +api-version: 1.13 +depend: [ "Mc-Mesh-Environment" ] +#libraries: +#- com.github.stephengold:Minie:7.5.0 + +commands: + minie: + description: Main Minie command + aliases: [] + permission: minie \ No newline at end of file diff --git a/plugin/pom.xml b/plugin/pom.xml index 0960211..9efc589 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -13,5 +13,6 @@ environment + physics \ No newline at end of file