Create standalone physics plugin

This commit is contained in:
2026-03-18 01:03:39 -04:00
parent 70049922c5
commit 3f4b4c6975
10 changed files with 883 additions and 444 deletions

View File

@@ -66,19 +66,6 @@
<release>21</release> <release>21</release>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.aaaaahhhhhhh.bananapuncher714.minietest.MeshingTest6</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

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

View File

@@ -59,36 +59,34 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.util.NmsUtil;
import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.util.NmsUtil.BlockShapeType; import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.util.NmsUtil.BlockShapeType;
public class MeshEnvironment { public class MeshEnvironment {
private JavaPlugin plugin; private JavaPlugin plugin;
private File cacheDirectory; private File cacheDirectory;
private Map< ChunkLocation, Collection< Location > > updateLocations = new TreeMap< ChunkLocation, Collection< Location > >(); private Map< ChunkLocation, Collection< Location > > updateLocations = new TreeMap< ChunkLocation, Collection< Location > >();
private Map< ChunkLocation, ChunkMesh > chunkMeshes = new HashMap< ChunkLocation, ChunkMesh >(); private Map< ChunkLocation, ChunkMesh > chunkMeshes = new HashMap< ChunkLocation, ChunkMesh >();
private Map< ChunkLocation, ChunkTask > tasks = new HashMap< ChunkLocation, ChunkTask >(); private Map< ChunkLocation, ChunkTask > tasks = new HashMap< ChunkLocation, ChunkTask >();
private ChunkMeshCache cache; private ChunkMeshCache cache;
private ReentrantLock lock = new ReentrantLock(); private EnvironmentHandler handler;
private ReentrantLock lock = new ReentrantLock();
final BukkitTask task = Bukkit.getScheduler().runTaskTimer( plugin, () -> { final BukkitTask task = Bukkit.getScheduler().runTaskTimer( plugin, () -> {
// final Collection< ChunkLocation > locations = new ArrayDeque< ChunkLocation >();
lock.lock(); lock.lock();
updateLocations.entrySet().stream().forEach( e -> { updateLocations.entrySet().stream().forEach( e -> {
ChunkMesh mesh = chunkMeshes.get( e.getKey() ); ChunkMesh mesh = chunkMeshes.get( e.getKey() );
if ( mesh != null ) { if ( mesh != null ) {
final Collection< Location > locs = update( mesh, e.getValue() ); final Collection< Location > locs = update( mesh, e.getValue() );
e.getValue().removeAll( locs ); 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() ); updateLocations.values().removeIf( c -> c.isEmpty() );
lock.unlock(); lock.unlock();
}, 1, 1 ); }, 1, 1 );
@@ -104,41 +102,41 @@ public class MeshEnvironment {
return locations; return locations;
}; };
private Listener eventListener = new Listener() { private Listener eventListener = new Listener() {
@EventHandler @EventHandler
private void onChunkLoad( ChunkLoadEvent event ) { private void onChunkLoad( ChunkLoadEvent event ) {
final ChunkLocation location = new ChunkLocation( event.getChunk() ); final ChunkLocation location = new ChunkLocation( event.getChunk() );
lock.lock(); lock.lock();
try { try {
if ( chunkMeshes.containsKey( location ) ) { if ( chunkMeshes.containsKey( location ) ) {
// Do nothing // Do nothing
} else { } else {
final ChunkTask task = tasks.get( location ); final ChunkTask task = tasks.get( location );
if ( task == null ) { if ( task == null ) {
loadMesh( location ); loadMesh( location );
} else if ( task.type == ChunkTaskType.LOAD ) { } else if ( task.type == ChunkTaskType.LOAD ) {
// Do nothing // Do nothing
} else if ( task.type == ChunkTaskType.UNLOAD ) { } else if ( task.type == ChunkTaskType.UNLOAD ) {
// Do nothing // Do nothing
} }
} }
} finally { } finally {
lock.unlock(); lock.unlock();
} }
/* /*
* Order of operations: * Order of operations:
* - Check if: * - Check if:
* - The mesh is loaded * - The mesh is loaded
* - Do nothing * - Do nothing
* - There is an unload queued * - There is an unload queued
* - Do nothing * - Do nothing
* - There is a load queued * - There is a load queued
* - Do nothing * - Do nothing
* - Nothing is queued * - Nothing is queued
* - Queue a load * - Queue a load
*/ */
} }
@EventHandler @EventHandler
@@ -190,94 +188,107 @@ public class MeshEnvironment {
} }
}; };
public MeshEnvironment( JavaPlugin plugin, File cacheDirectory ) { public MeshEnvironment( JavaPlugin plugin, File cacheDirectory ) {
this.plugin = plugin; this.plugin = plugin;
this.cacheDirectory = cacheDirectory; this.cacheDirectory = cacheDirectory;
cache = new ChunkMeshCache( new File( cacheDirectory, "meshes" ) ); cache = new ChunkMeshCache( new File( cacheDirectory, "meshes" ) );
} }
public void activate() { public void activate() {
Bukkit.getPluginManager().registerEvents( eventListener, plugin ); Bukkit.getPluginManager().registerEvents( eventListener, plugin );
} }
public void deactivate() { public void deactivate() {
HandlerList.unregisterAll( eventListener ); HandlerList.unregisterAll( eventListener );
} }
private void completeTask( ChunkLocation location ) { public void load( ChunkLocation location ) {
lock.lock(); loadMesh( location );
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 ) { public void setHandler( EnvironmentHandler handler ) {
ChunkTask task = new ChunkTask(); this.handler = handler;
lock.lock(); }
try {
tasks.put( location, task );
task.type = ChunkTaskType.LOAD; private void completeTask( ChunkLocation location ) {
task.task = Bukkit.getScheduler().runTaskAsynchronously( plugin, () -> { lock.lock();
final ChunkMesh mesh = cache.load( location ); try {
if ( mesh != null ) { final ChunkTask task = tasks.remove( location );
lock.lock(); if ( task.type == ChunkTaskType.LOAD ) {
try { handler.onMeshLoad( location, chunkMeshes.get( location ) );
// Save the mesh if ( !location.isLoaded() ) {
if ( !chunkMeshes.containsKey( location ) ) { // Update all and unload
chunkMeshes.put( location, mesh ); 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 );
completeTask( location ); if ( handler != null ) {
} finally { handler.onMeshUnload( location, mesh );
lock.unlock(); }
} }
} else { } else {
// Process and generate the mesh // Chunk is loaded, but we saved. Do nothing
Bukkit.getScheduler().runTask( plugin, () -> { }
generateMesh( plugin, location.getWorld(), location.getChunk(), m -> { }
lock.lock(); } finally {
try { lock.unlock();
chunkMeshes.put( location, m ); }
}
completeTask( location ); private void loadMesh( ChunkLocation location ) {
} finally { ChunkTask task = new ChunkTask();
lock.unlock(); lock.lock();
} try {
} ); tasks.put( location, task );
} );
}
} );
} finally {
lock.unlock();
}
}
private void unloadMesh( ChunkLocation location ) { task.type = ChunkTaskType.LOAD;
ChunkTask task = new ChunkTask(); 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(); lock.lock();
try { try {
tasks.put( location, task ); tasks.put( location, task );
@@ -306,46 +317,51 @@ public class MeshEnvironment {
} finally { } finally {
lock.unlock(); lock.unlock();
} }
} }
private boolean applyUpdatesToMesh( ChunkLocation location ) { private boolean applyUpdatesToMesh( ChunkLocation location ) {
lock.lock(); lock.lock();
try { try {
final Collection< Location > locations = updateLocations.get( location ); final Collection< Location > locations = updateLocations.get( location );
final ChunkMesh mesh = chunkMeshes.get( location ); final ChunkMesh mesh = chunkMeshes.get( location );
if ( locations != null && !locations.isEmpty() && mesh != null ) { if ( locations != null && !locations.isEmpty() && mesh != null ) {
locations.removeAll( update( mesh, locations ) ); final Collection< Location > removed = update( mesh, locations );
locations.removeAll( removed );
return locations.isEmpty(); if ( handler != null && !removed.isEmpty() ) {
} handler.onMeshUpdate( location, mesh );
} finally { }
lock.unlock();
}
return true; return locations.isEmpty();
} }
} finally {
lock.unlock();
}
private static void generateSpareMesh( JavaPlugin plugin, World world, Collection< Vector > locations, Consumer< SparseMesh > callback ) { return true;
}
public static void generateSpareMesh( JavaPlugin plugin, World world, Collection< Vector > locations, Consumer< SparseMesh > callback ) {
if ( locations.isEmpty() ) { if ( locations.isEmpty() ) {
return; return;
} }
boolean set = false; boolean set = false;
Vector min = new Vector(); Vector min = new Vector();
Vector max = new Vector(); Vector max = new Vector();
for ( Vector v : locations ) { for ( Vector v : locations ) {
if ( !set ) { if ( !set ) {
set = true; set = true;
min = v.toBlockVector(); min = v.toBlockVector();
max = v.toBlockVector(); max = v.toBlockVector();
} else { } else {
min = Vector.getMinimum( min, v.toBlockVector() ); min = Vector.getMinimum( min, v.toBlockVector() );
max = Vector.getMaximum( max, v.toBlockVector() ); max = Vector.getMaximum( max, v.toBlockVector() );
} }
} }
final SparseMesh sparseMesh = new SparseMesh( min, max ); final SparseMesh sparseMesh = new SparseMesh( min, max );
final Map< MinecraftPlane, Collection< Polygon > > polygons = new TreeMap< MinecraftPlane, Collection< Polygon > >(); final Map< MinecraftPlane, Collection< Polygon > > polygons = new TreeMap< MinecraftPlane, Collection< Polygon > >();
@@ -361,9 +377,9 @@ public class MeshEnvironment {
}; };
for ( final Vector location : locations ) { for ( final Vector location : locations ) {
final Block block = world.getBlockAt( location.getBlockX(), location.getBlockY(), location.getBlockZ() ); final Block block = world.getBlockAt( location.getBlockX(), location.getBlockY(), location.getBlockZ() );
if ( !( block.isEmpty() && block.isLiquid() ) ) { if ( !( block.isEmpty() && block.isLiquid() ) ) {
final BlockData data = block.getBlockData(); final BlockData data = block.getBlockData();
final Location loc = block.getLocation(); final Location loc = block.getLocation();
@@ -374,12 +390,12 @@ public class MeshEnvironment {
if ( boundingBoxes.length == 0 ) { if ( boundingBoxes.length == 0 ) {
// No bounding box // No bounding box
information.type = BlockDataType.NONE; information.type = BlockDataType.NONE;
} else if ( boundingBoxes.length == 1 && boundingBoxes[ 0 ].getVolume() == 1 ) { } else if ( boundingBoxes.length == 1 && boundingBoxes[ 0 ].getVolume() == 1 ) {
// Solid // Solid
information.type = BlockDataType.SOLID; information.type = BlockDataType.SOLID;
} else { } else {
information.type = BlockDataType.COMPLEX; information.type = BlockDataType.COMPLEX;
// Complex bounding shape // Complex bounding shape
// Add the voxel shape to the chunk mesh // Add the voxel shape to the chunk mesh
@@ -396,19 +412,19 @@ public class MeshEnvironment {
new Point( minY, maxZ ), new Point( minY, maxZ ),
new Point( maxY, maxZ ), new Point( maxY, maxZ ),
new Point( maxY, minZ ) new Point( maxY, minZ )
) ); ) );
final Polygon yPoly = new Polygon( Arrays.asList( final Polygon yPoly = new Polygon( Arrays.asList(
new Point( minX, minZ ), new Point( minX, minZ ),
new Point( minX, maxZ ), new Point( minX, maxZ ),
new Point( maxX, maxZ ), new Point( maxX, maxZ ),
new Point( maxX, minZ ) new Point( maxX, minZ )
) ); ) );
final Polygon zPoly = new Polygon( Arrays.asList( final Polygon zPoly = new Polygon( Arrays.asList(
new Point( minX, minY ), new Point( minX, minY ),
new Point( minX, maxY ), new Point( minX, maxY ),
new Point( maxX, maxY ), new Point( maxX, maxY ),
new Point( maxX, minY ) new Point( maxX, minY )
) ); ) );
getPolygons.apply( new MinecraftPlane( PlaneAxis.X, minX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.X, minX ) ).add( xPoly );
getPolygons.apply( new MinecraftPlane( PlaneAxis.X, maxX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.X, maxX ) ).add( xPoly );
@@ -421,62 +437,62 @@ public class MeshEnvironment {
information.boundingBoxes = boundingBoxes; information.boundingBoxes = boundingBoxes;
sparseMesh.setBlockDataInformationFor( information, loc.toVector().subtract( min ) ); sparseMesh.setBlockDataInformationFor( information, loc.toVector().subtract( min ) );
} }
} }
} }
// Now generate facets for each solid block based on if the side they are on is visible // Now generate facets for each solid block based on if the side they are on is visible
for ( final Vector location : locations ) { for ( final Vector location : locations ) {
final Vector blockVector = location.toBlockVector().subtract( min ); final Vector blockVector = location.toBlockVector().subtract( min );
final BlockDataInformation info = sparseMesh.getBlockDataInformationFor( blockVector ); final BlockDataInformation info = sparseMesh.getBlockDataInformationFor( blockVector );
if ( info != null ) { if ( info != null ) {
if ( info.type == BlockDataType.SOLID ) { if ( info.type == BlockDataType.SOLID ) {
final Polygon xPoly = new Polygon( Arrays.asList( final Polygon xPoly = new Polygon( Arrays.asList(
new Point( blockVector.getY(), blockVector.getZ() ), new Point( blockVector.getY(), blockVector.getZ() ),
new Point( blockVector.getY(), blockVector.getZ() + 1 ), new Point( blockVector.getY(), blockVector.getZ() + 1 ),
new Point( blockVector.getY() + 1, blockVector.getZ() + 1 ), new Point( blockVector.getY() + 1, blockVector.getZ() + 1 ),
new Point( blockVector.getY() + 1, blockVector.getZ() ) new Point( blockVector.getY() + 1, blockVector.getZ() )
) ); ) );
final Polygon yPoly = new Polygon( Arrays.asList( final Polygon yPoly = new Polygon( Arrays.asList(
new Point( blockVector.getX(), blockVector.getZ() ), new Point( blockVector.getX(), blockVector.getZ() ),
new Point( blockVector.getX(), blockVector.getZ() + 1 ), new Point( blockVector.getX(), blockVector.getZ() + 1 ),
new Point( blockVector.getX() + 1, blockVector.getZ() + 1 ), new Point( blockVector.getX() + 1, blockVector.getZ() + 1 ),
new Point( blockVector.getX() + 1, blockVector.getZ() ) new Point( blockVector.getX() + 1, blockVector.getZ() )
) ); ) );
final Polygon zPoly = new Polygon( Arrays.asList( final Polygon zPoly = new Polygon( Arrays.asList(
new Point( blockVector.getX(), blockVector.getY() ), new Point( blockVector.getX(), blockVector.getY() ),
new Point( blockVector.getX(), blockVector.getY() + 1 ), new Point( blockVector.getX(), blockVector.getY() + 1 ),
new Point( blockVector.getX() + 1, blockVector.getY() + 1 ), new Point( blockVector.getX() + 1, blockVector.getY() + 1 ),
new Point( blockVector.getX() + 1, blockVector.getY() ) new Point( blockVector.getX() + 1, blockVector.getY() )
) ); ) );
BlockDataInformation adjacent; BlockDataInformation adjacent;
if ( ( adjacent = sparseMesh.getBlockDataInformationFor( blockVector.clone().subtract( new Vector( 0, 1, 0 ) ) ) ) == null || adjacent.type != BlockDataType.SOLID ) { 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 ); 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 ) { 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 ); 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 ) { 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 ); 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 ) { 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 ); 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 ) { 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 ); 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 ) { 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 ); getPolygons.apply( new MinecraftPlane( PlaneAxis.X, blockVector.getX() + 1 ) ).add( xPoly );
} }
} }
} else { } else {
// Shouldn't be null btw // Shouldn't be null btw
} }
} }
} }
@@ -492,7 +508,7 @@ public class MeshEnvironment {
} }
// TODO Use ChunkSnapshot in the future // 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 worldMinHeight = world.getMinHeight();
final int worldMaxHeight = world.getMaxHeight(); final int worldMaxHeight = world.getMaxHeight();
final int worldHeight = worldMaxHeight - worldMinHeight; final int worldHeight = worldMaxHeight - worldMinHeight;
@@ -555,19 +571,19 @@ public class MeshEnvironment {
new Point( minY, maxZ ), new Point( minY, maxZ ),
new Point( maxY, maxZ ), new Point( maxY, maxZ ),
new Point( maxY, minZ ) new Point( maxY, minZ )
) ); ) );
final Polygon yPoly = new Polygon( Arrays.asList( final Polygon yPoly = new Polygon( Arrays.asList(
new Point( minX, minZ ), new Point( minX, minZ ),
new Point( minX, maxZ ), new Point( minX, maxZ ),
new Point( maxX, maxZ ), new Point( maxX, maxZ ),
new Point( maxX, minZ ) new Point( maxX, minZ )
) ); ) );
final Polygon zPoly = new Polygon( Arrays.asList( final Polygon zPoly = new Polygon( Arrays.asList(
new Point( minX, minY ), new Point( minX, minY ),
new Point( minX, maxY ), new Point( minX, maxY ),
new Point( maxX, maxY ), new Point( maxX, maxY ),
new Point( maxX, minY ) new Point( maxX, minY )
) ); ) );
getPolygons.apply( new MinecraftPlane( PlaneAxis.X, minX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.X, minX ) ).add( xPoly );
getPolygons.apply( new MinecraftPlane( PlaneAxis.X, maxX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.X, maxX ) ).add( xPoly );
@@ -599,19 +615,19 @@ public class MeshEnvironment {
new Point( y, z + 1 ), new Point( y, z + 1 ),
new Point( y + 1, z + 1 ), new Point( y + 1, z + 1 ),
new Point( y + 1, z ) new Point( y + 1, z )
) ); ) );
final Polygon yPoly = new Polygon( Arrays.asList( final Polygon yPoly = new Polygon( Arrays.asList(
new Point( x, z ), new Point( x, z ),
new Point( x, z + 1 ), new Point( x, z + 1 ),
new Point( x + 1, z + 1 ), new Point( x + 1, z + 1 ),
new Point( x + 1, z ) new Point( x + 1, z )
) ); ) );
final Polygon zPoly = new Polygon( Arrays.asList( final Polygon zPoly = new Polygon( Arrays.asList(
new Point( x, y ), new Point( x, y ),
new Point( x, y + 1 ), new Point( x, y + 1 ),
new Point( x + 1, y + 1 ), new Point( x + 1, y + 1 ),
new Point( x + 1, y ) new Point( x + 1, y )
) ); ) );
// Check each face // Check each face
if ( y == 0 || chunkMesh.getType( index - 256 ) != BlockDataType.SOLID ) { if ( y == 0 || chunkMesh.getType( index - 256 ) != BlockDataType.SOLID ) {
@@ -654,7 +670,7 @@ public class MeshEnvironment {
} ); } );
} }
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() ) { if ( locations.isEmpty() ) {
return Collections.emptySet(); return Collections.emptySet();
} }
@@ -697,14 +713,14 @@ public class MeshEnvironment {
final Vector position = location.toVector().subtract( mesh.getMinimumCorner() ); final Vector position = location.toVector().subtract( mesh.getMinimumCorner() );
final BlockDataInformation information = mesh.getBlockDataInformationFor( position ); final BlockDataInformation information = mesh.getBlockDataInformationFor( position );
if ( information != null ) { if ( information != null ) {
canRemove.add( location ); canRemove.add( location );
if ( information.isBlockData( data ) ) { if ( information.isBlockData( data ) ) {
// Ignore if the block data has not changed // Ignore if the block data has not changed
continue; continue;
} else { } else {
final List< BoundingBox > boxes = NmsUtil.getShape( data, location, BlockShapeType.COLLISION_SHAPE ).e().stream() 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() ); .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; final BoundingBox[] otherBoxes = information.boundingBoxes;
if ( boxes.size() == 0 ) { if ( boxes.size() == 0 ) {
if ( oldType == BlockDataType.NONE ) { if ( oldType == BlockDataType.NONE ) {
@@ -764,19 +780,19 @@ public class MeshEnvironment {
new Point( minY, maxZ ), new Point( minY, maxZ ),
new Point( maxY, maxZ ), new Point( maxY, maxZ ),
new Point( maxY, minZ ) new Point( maxY, minZ )
) ); ) );
final Polygon yPoly = new Polygon( Arrays.asList( final Polygon yPoly = new Polygon( Arrays.asList(
new Point( minX, minZ ), new Point( minX, minZ ),
new Point( minX, maxZ ), new Point( minX, maxZ ),
new Point( maxX, maxZ ), new Point( maxX, maxZ ),
new Point( maxX, minZ ) new Point( maxX, minZ )
) ); ) );
final Polygon zPoly = new Polygon( Arrays.asList( final Polygon zPoly = new Polygon( Arrays.asList(
new Point( minX, minY ), new Point( minX, minY ),
new Point( minX, maxY ), new Point( minX, maxY ),
new Point( maxX, maxY ), new Point( maxX, maxY ),
new Point( maxX, minY ) new Point( maxX, minY )
) ); ) );
getPolygons.apply( new MinecraftPlane( PlaneAxis.X, minX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.X, minX ) ).add( xPoly );
getPolygons.apply( new MinecraftPlane( PlaneAxis.X, maxX ) ).add( xPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.X, maxX ) ).add( xPoly );
@@ -785,7 +801,7 @@ public class MeshEnvironment {
getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, minZ ) ).add( zPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, minZ ) ).add( zPoly );
getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, maxZ ) ).add( zPoly ); getPolygons.apply( new MinecraftPlane( PlaneAxis.Z, maxZ ) ).add( zPoly );
} }
} }
} }
} }
@@ -810,11 +826,11 @@ public class MeshEnvironment {
// Add all polygons that were previously generated // Add all polygons that were previously generated
for ( final Polygon polygon : planeMesh.generatedRegions ) { for ( final Polygon polygon : planeMesh.generatedRegions ) {
mesher.addPolygon( polygon, RegionRuleWinding.CLOCKWISE ); mesher.addPolygon( polygon, RegionRuleWinding.CLOCKWISE );
} }
} }
for ( final Polygon polygon : entry.getValue() ) { for ( final Polygon polygon : entry.getValue() ) {
mesher.addPolygon( polygon, RegionRuleWinding.CLOCKWISE ); mesher.addPolygon( polygon, RegionRuleWinding.CLOCKWISE );
} }
meshMap.put( plane, mesher ); meshMap.put( plane, mesher );
@@ -834,9 +850,9 @@ public class MeshEnvironment {
final PlaneMesh newMesh = new PlaneMesh(); final PlaneMesh newMesh = new PlaneMesh();
newMesh.completedPolygons = new ArrayList< IndexedTriangle >(); newMesh.completedPolygons = new ArrayList< IndexedTriangle >();
for ( final Polygon tri : tris ) { 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 ) { 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 ); final Vector vert = plane.convert( point );
// Find an existing vertex reference if one exists // Find an existing vertex reference if one exists
@@ -862,18 +878,18 @@ public class MeshEnvironment {
} }
switch ( i ) { switch ( i ) {
case 0: case 0:
newPoly.setPoint1( ref ); newPoly.setPoint1( ref );
break; break;
case 1: case 1:
newPoly.setPoint2( ref ); newPoly.setPoint2( ref );
break; break;
case 2: case 2:
newPoly.setPoint3( ref ); newPoly.setPoint3( ref );
break; break;
default: default:
// Should never be here! // Should never be here!
} }
} }
newMesh.completedPolygons.add( newPoly ); newMesh.completedPolygons.add( newPoly );
} }
@@ -891,8 +907,8 @@ public class MeshEnvironment {
return canRemove; return canRemove;
} }
private static MeshResult generateMeshFrom( final Map< MinecraftPlane, Collection< Polygon > > polygons ) { public static MeshResult generateMeshFrom( final Map< MinecraftPlane, Collection< Polygon > > polygons ) {
final Map< MinecraftPlane, Mesh< RegionSimple > > meshMap = new HashMap< MinecraftPlane, Mesh< RegionSimple > >(); final Map< MinecraftPlane, Mesh< RegionSimple > > meshMap = new HashMap< MinecraftPlane, Mesh< RegionSimple > >();
for ( final Entry< MinecraftPlane, Collection< Polygon > > entry : polygons.entrySet() ) { for ( final Entry< MinecraftPlane, Collection< Polygon > > entry : polygons.entrySet() ) {
final MinecraftPlane plane = entry.getKey(); final MinecraftPlane plane = entry.getKey();
final Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> new RegionSimple( GluWindingRule.ODD ) ); final Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> new RegionSimple( GluWindingRule.ODD ) );
@@ -922,45 +938,45 @@ public class MeshEnvironment {
for ( final Polygon tri : tris ) { 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 ) { 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 ); final Vector vert = plane.convert( point );
final VectorReference tempRef = new VectorReference( vert ); final VectorReference tempRef = new VectorReference( vert );
// Find an existing vertex reference if one exists // Find an existing vertex reference if one exists
VectorReference ref = null; VectorReference ref = null;
if ( ref == null ) { if ( ref == null ) {
final VectorReference upperRef = vertices.ceiling( tempRef ); final VectorReference upperRef = vertices.ceiling( tempRef );
if ( upperRef != null && upperRef.vector.distanceSquared( vert ) < 1e-8 ) { if ( upperRef != null && upperRef.vector.distanceSquared( vert ) < 1e-8 ) {
ref = upperRef; ref = upperRef;
} else { } else {
final VectorReference lowerRef = vertices.floor( tempRef ); final VectorReference lowerRef = vertices.floor( tempRef );
if ( lowerRef != null && lowerRef.vector.distanceSquared( vert ) < 1e-8 ) { if ( lowerRef != null && lowerRef.vector.distanceSquared( vert ) < 1e-8 ) {
ref = lowerRef; ref = lowerRef;
} }
} }
} }
if ( ref == null ) { if ( ref == null ) {
ref = tempRef; ref = tempRef;
ref.referenceCount = 1; ref.referenceCount = 1;
vertices.add( ref ); vertices.add( ref );
} else { } else {
++ref.referenceCount; ++ref.referenceCount;
} }
switch ( i ) { switch ( i ) {
case 0: case 0:
newPoly.setPoint1( ref ); newPoly.setPoint1( ref );
break; break;
case 1: case 1:
newPoly.setPoint2( ref ); newPoly.setPoint2( ref );
break; break;
case 2: case 2:
newPoly.setPoint3( ref ); newPoly.setPoint3( ref );
break; break;
default: default:
// Should never be here! // Should never be here!
} }
} }
newMesh.completedPolygons.add( newPoly ); newMesh.completedPolygons.add( newPoly );
@@ -989,9 +1005,9 @@ public class MeshEnvironment {
} }
} }
private static class MeshResult { public static class MeshResult {
Map< MinecraftPlane, PlaneMesh > meshes; Map< MinecraftPlane, PlaneMesh > meshes;
TreeSet< VectorReference > points; TreeSet< VectorReference > points;
} }
private enum ChunkTaskType { private enum ChunkTaskType {

View File

@@ -1,14 +1,6 @@
name: MC-Mesh-Environment name: MC-Mesh-Environment
main: com.aaaaahhhhhhh.bananapuncher714.minietest.MiniePlugin main: com.aaaaahhhhhhh.bananapuncher714.mesh.environment.McMeshEnvironmentPlugin
version: 0.0.1 version: 0.0.1
description: BulletJME and MC-Mesh integration description: BulletJME and MC-Mesh integration
author: BananaPuncher714 author: BananaPuncher714
api-version: 1.13 api-version: 1.13
#libraries:
#- com.github.stephengold:Minie:7.5.0
commands:
minie:
description: Main Minie command
aliases: []
permission: minie

53
plugin/physics/pom.xml Normal file
View File

@@ -0,0 +1,53 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.aaaaahhhhhhh.bananapuncher714</groupId>
<artifactId>mc-mesh-plugin</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>mc-mesh-plugin-physics</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.21.10-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.aaaaahhhhhhh.bananapuncher714</groupId>
<artifactId>mc-mesh-plugin-environment</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package com.aaaaahhhhhhh.bananapuncher714.mesh.environment; package com.aaaaahhhhhhh.bananapuncher714.mesh.physics;
import java.io.File; import java.io.File;
import java.io.FileWriter; 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.ChunkLocation;
import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.ChunkMesh; import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.ChunkMesh;
import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.IndexedPolygon; 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.MinecraftPlane;
import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.PlaneMesh; import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.PlaneMesh;
import com.aaaaahhhhhhh.bananapuncher714.mesh.environment.objects.VectorReference; 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() ) { for ( final Entry< Vector, VectorRef > entry : vertices.entrySet() ) {
final Vector existingVec = entry.getKey(); 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(); ref = entry.getValue();
break; break;
} }
@@ -1058,7 +1059,7 @@ public class MiniePlugin extends JavaPlugin {
if ( ref == null ) { if ( ref == null ) {
ref = new VectorRef(); ref = new VectorRef();
vertices.put( vec, ref ); vertices.put( new Vector( vec.getX(), vec.getY(), vec.getZ() ), ref );
} }
triangle.refs.add( ref ); triangle.refs.add( ref );
@@ -1313,9 +1314,9 @@ public class MiniePlugin extends JavaPlugin {
// Ignore any planes that are empty // Ignore any planes that are empty
if ( !tris.isEmpty() ) { if ( !tris.isEmpty() ) {
final PlaneMesh newMesh = new PlaneMesh(); final PlaneMesh newMesh = new PlaneMesh();
newMesh.completedPolygons = new ArrayList< IndexedPolygon >(); newMesh.completedPolygons = new ArrayList< IndexedTriangle >();
for ( final Polygon tri : tris ) { for ( final Polygon tri : tris ) {
final IndexedPolygon newPoly = new IndexedPolygon(); final IndexedTriangle newPoly = new IndexedTriangle();
for ( final Point point : tri.getPoints() ) { for ( final Point point : tri.getPoints() ) {
final Vector vert = plane.convert( point ); final Vector vert = plane.convert( point );
@@ -1342,7 +1343,7 @@ public class MiniePlugin extends JavaPlugin {
++ref.referenceCount; ++ref.referenceCount;
} }
newPoly.points.add( ref ); // newPoly.points.add( ref );
} }
newMesh.completedPolygons.add( newPoly ); newMesh.completedPolygons.add( newPoly );
} }
@@ -1536,7 +1537,7 @@ public class MiniePlugin extends JavaPlugin {
if ( planeMesh != null ) { if ( planeMesh != null ) {
// Reset all indexed polygons(triangles) for this plane // Reset all indexed polygons(triangles) for this plane
for ( final IndexedPolygon poly : planeMesh.completedPolygons ) { 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 // Add all polygons that were previously generated
@@ -1563,9 +1564,9 @@ public class MiniePlugin extends JavaPlugin {
// Ignore any planes that are empty // Ignore any planes that are empty
if ( !tris.isEmpty() ) { if ( !tris.isEmpty() ) {
final PlaneMesh newMesh = new PlaneMesh(); final PlaneMesh newMesh = new PlaneMesh();
newMesh.completedPolygons = new ArrayList< IndexedPolygon >(); newMesh.completedPolygons = new ArrayList< IndexedTriangle >();
for ( final Polygon tri : tris ) { for ( final Polygon tri : tris ) {
final IndexedPolygon newPoly = new IndexedPolygon(); final IndexedTriangle newPoly = new IndexedTriangle();
for ( final Point point : tri.getPoints() ) { for ( final Point point : tri.getPoints() ) {
final Vector vert = plane.convert( point ); final Vector vert = plane.convert( point );
@@ -1591,7 +1592,7 @@ public class MiniePlugin extends JavaPlugin {
++ref.referenceCount; ++ref.referenceCount;
} }
newPoly.points.add( ref ); // newPoly.points.add( ref );
} }
newMesh.completedPolygons.add( newPoly ); newMesh.completedPolygons.add( newPoly );
} }
@@ -1667,12 +1668,12 @@ public class MiniePlugin extends JavaPlugin {
} }
final Triangle triangle = new Triangle(); final Triangle triangle = new Triangle();
for ( final Vector vec : facet.points ) { for ( final Vector3d vec : facet.points ) {
VectorRef ref = null; VectorRef ref = null;
for ( final Entry< Vector, VectorRef > entry : vertices.entrySet() ) { for ( final Entry< Vector, VectorRef > entry : vertices.entrySet() ) {
final Vector existingVec = entry.getKey(); 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(); ref = entry.getValue();
break; break;
} }
@@ -1680,7 +1681,7 @@ public class MiniePlugin extends JavaPlugin {
if ( ref == null ) { if ( ref == null ) {
ref = new VectorRef(); ref = new VectorRef();
vertices.put( vec, ref ); vertices.put( new Vector( vec.getX(), vec.getY(), vec.getZ() ), ref );
} }
triangle.refs.add( ref ); triangle.refs.add( ref );
@@ -1765,10 +1766,10 @@ public class MiniePlugin extends JavaPlugin {
try ( FileWriter writer = new FileWriter( file ) ) { try ( FileWriter writer = new FileWriter( file ) ) {
for ( final Facet facet : facets ) { 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" ); 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" ); writer.write( point.getX() + " " + point.getY() + " " + point.getZ() + "\n" );
} }
} }

View File

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

View File

@@ -13,5 +13,6 @@
<modules> <modules>
<module>environment</module> <module>environment</module>
<module>physics</module>
</modules> </modules>
</project> </project>