Add de/serialization for chunk meshes

Add half-baked loading and saving to avoid having to mesh the chunk each time
This commit is contained in:
2025-05-31 05:13:11 -04:00
parent 13c92dcb43
commit 793d1e846a
6 changed files with 371 additions and 92 deletions

View File

@@ -3,6 +3,9 @@ package com.aaaaahhhhhhh.bananapuncher714.minietest;
import java.io.File; import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -20,6 +23,7 @@ import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang.SystemUtils; import org.apache.commons.lang.SystemUtils;
@@ -75,6 +79,7 @@ import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.PlaneMesh;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.VectorReference; import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.VectorReference;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Facet; import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.Facet;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.MeshBuilder; import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.mesh.MeshBuilder;
import com.aaaaahhhhhhh.bananapuncher714.minietest.util.ChunkMeshUtil;
import com.aaaaahhhhhhh.bananapuncher714.minietest.util.FileUtil; import com.aaaaahhhhhhh.bananapuncher714.minietest.util.FileUtil;
import com.jme3.bullet.PhysicsSpace; import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsSpace.BroadphaseType; import com.jme3.bullet.PhysicsSpace.BroadphaseType;
@@ -129,9 +134,6 @@ public class MiniePlugin extends JavaPlugin {
private Map< ChunkLocation, ChunkMesh > chunkMeshes = new HashMap< ChunkLocation, ChunkMesh >(); private Map< ChunkLocation, ChunkMesh > chunkMeshes = new HashMap< ChunkLocation, ChunkMesh >();
private Map< ChunkLocation, BukkitTask > loadMeshTasks = new ConcurrentHashMap< ChunkLocation, BukkitTask >();
private Map< ChunkLocation, BukkitTask > saveMeshTasks = new ConcurrentHashMap< ChunkLocation, BukkitTask >();
@Override @Override
public void onEnable() { public void onEnable() {
FileUtil.saveToFile( getResource( "native/windows/x86_64/bulletjme.dll" ), new File( getDataFolder() + "/lib", "bulletjme.dll" ), false ); FileUtil.saveToFile( getResource( "native/windows/x86_64/bulletjme.dll" ), new File( getDataFolder() + "/lib", "bulletjme.dll" ), false );
@@ -145,6 +147,11 @@ public class MiniePlugin extends JavaPlugin {
registerCommands(); registerCommands();
// Don't log anything
PhysicsSpace.logger.setLevel( Level.WARNING );
PhysicsSpace.loggerC.setLevel( Level.WARNING );
PhysicsRigidBody.logger2.setLevel( Level.WARNING );
space = new PhysicsSpace( BroadphaseType.DBVT ); space = new PhysicsSpace( BroadphaseType.DBVT );
PlaneCollisionShape plane = new PlaneCollisionShape( new Plane( new Vector3f( 0, 1, 0 ), 0 ) ); PlaneCollisionShape plane = new PlaneCollisionShape( new Plane( new Vector3f( 0, 1, 0 ), 0 ) );
planeBody = new PhysicsRigidBody( plane, PhysicsBody.massForStatic ); planeBody = new PhysicsRigidBody( plane, PhysicsBody.massForStatic );
@@ -186,10 +193,10 @@ public class MiniePlugin extends JavaPlugin {
private void onChunkLoad( ChunkLoadEvent event ) { private void onChunkLoad( ChunkLoadEvent event ) {
if ( event.getWorld().getName().equalsIgnoreCase( "world" ) ) { if ( event.getWorld().getName().equalsIgnoreCase( "world" ) ) {
// getLogger().info( "Loading chunk " + event.getChunk().getX() + ", " + event.getChunk().getZ() ); // getLogger().info( "Loading chunk " + event.getChunk().getX() + ", " + event.getChunk().getZ() );
generateMesh( event.getChunk() ); // generateMesh( event.getChunk() );
// scanAndGenerate( event.getChunk() ); // scanAndGenerate( event.getChunk() );
// saveFacets( event.getChunk() ); // saveFacets( event.getChunk() );
// loadOrGenerateMesh( event.getChunk() ); loadOrGenerateMesh( event.getChunk() );
} }
} }
@@ -200,13 +207,12 @@ public class MiniePlugin extends JavaPlugin {
if ( chunks.containsKey( loc ) ) { if ( chunks.containsKey( loc ) ) {
space.removeCollisionObject( chunks.get( loc ) ); space.removeCollisionObject( chunks.get( loc ) );
chunks.remove( loc ); chunks.remove( loc );
chunkMeshes.remove( loc );
} }
// final ChunkMesh mesh = chunkMeshes.get( loc ); final ChunkMesh mesh = chunkMeshes.remove( loc );
// if ( mesh != null ) { if ( mesh != null ) {
// attemptToSaveMesh( loc, mesh ); saveMesh( loc, mesh );
// } }
} }
} }
@@ -247,8 +253,8 @@ public class MiniePlugin extends JavaPlugin {
getLogger().info( "There are " + Bukkit.getWorld( "world" ).getLoadedChunks().length + " chunks" ); getLogger().info( "There are " + Bukkit.getWorld( "world" ).getLoadedChunks().length + " chunks" );
// getLogger().info( "Converted " + saveAllChunks( Bukkit.getWorld( "world" ) ) ); // getLogger().info( "Converted " + saveAllChunks( Bukkit.getWorld( "world" ) ) );
// loadOrGenerateWorld( Bukkit.getWorld( "world" ) ); loadOrGenerateWorld( Bukkit.getWorld( "world" ) );
scanAndGenerate( Bukkit.getWorld( "world" ) ); // scanAndGenerate( Bukkit.getWorld( "world" ) );
// saveAllFacets( Bukkit.getWorld( "world" ) ); // saveAllFacets( Bukkit.getWorld( "world" ) );
Bukkit.getScheduler().runTaskTimer( this, () -> { Bukkit.getScheduler().runTaskTimer( this, () -> {
@@ -707,93 +713,62 @@ public class MiniePlugin extends JavaPlugin {
} }
private void loadOrGenerateMesh( final Chunk chunk ) { private void loadOrGenerateMesh( final Chunk chunk ) {
// chunk.addPluginChunkTicket( this );
final ChunkLocation location = new ChunkLocation( chunk ); final ChunkLocation location = new ChunkLocation( chunk );
if ( !chunkMeshes.containsKey( location ) && !loadMeshTasks.containsKey( location ) ) { final ChunkMesh mesh = loadMesh( location );
loadMeshTasks.put( location, Bukkit.getScheduler().runTaskAsynchronously( this, () -> { if ( mesh == null ) {
final ChunkMesh mesh = loadMesh( location ); generateMesh( chunk );
if ( mesh == null ) { } else {
Bukkit.getScheduler().runTask( this, () -> { // Now, create a physics object from the chunk mesh
generateMesh( chunk ); final IndexedMesh nativeMesh = mesh.toIndexedMesh();
} ); // Enable quantized AABB compression... hopefully that doesn't mess with the accuracy or anything...
} else { final MeshCollisionShape meshShape = new MeshCollisionShape( true, nativeMesh );
// 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 meshShape = new MeshCollisionShape( true, nativeMesh );
// Create a rigid body
PhysicsRigidBody rigid = new PhysicsRigidBody( meshShape, PhysicsBody.massForStatic );
rigid.setPhysicsLocation( new Vector3f( chunk.getX() << 4, chunk.getWorld().getMinHeight(), chunk.getZ() << 4 ) );
rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) );
rigid.setKinematic( false );
Bukkit.getScheduler().runTask( this, () -> {
// Save our chunk mesh
chunkMeshes.put( location, mesh );
space.addCollisionObject( rigid );
PhysicsRigidBody old = chunks.put( new ChunkLocation( chunk ), rigid ); // Create a rigid body
if ( old != null ) { PhysicsRigidBody rigid = new PhysicsRigidBody( meshShape, PhysicsBody.massForStatic );
getLogger().warning( "Old rigid body found!" ); rigid.setPhysicsLocation( new Vector3f( chunk.getX() << 4, chunk.getWorld().getMinHeight(), chunk.getZ() << 4 ) );
space.remove( old ); rigid.setPhysicsRotation( new Quaternion( 0, 0, 0, 1 ) );
} rigid.setKinematic( false );
chunk.removePluginChunkTicket( this ); // Save our chunk mesh
chunkMeshes.put( location, mesh );
loadMeshTasks.remove( location );
} ); space.addCollisionObject( rigid );
}
} ) ); PhysicsRigidBody old = chunks.put( new ChunkLocation( chunk ), rigid );
} else if ( !saveMeshTasks.containsKey( location ) ) { if ( old != null ) {
chunk.removePluginChunkTicket( this ); getLogger().warning( "Old rigid body found!" );
space.remove( old );
}
} }
} }
private void attemptToSaveMesh( final ChunkLocation location, final ChunkMesh mesh ) {
// getLogger().info( "Attempting to save " + location );
saveMeshTasks.put( location, Bukkit.getScheduler().runTaskAsynchronously( this, () -> {
saveMesh( location, mesh );
Bukkit.getScheduler().runTask( this, () -> {
if ( location.isLoaded() ) {
location.getChunk().removePluginChunkTicket( this );
} else {
chunkMeshes.remove( location );
final PhysicsRigidBody old = chunks.remove( location );
if ( old != null ) {
space.remove( old );
}
}
saveMeshTasks.remove( location );
} );
} ) );
}
private ChunkMesh loadMesh( final ChunkLocation location ) { private ChunkMesh loadMesh( final ChunkLocation location ) {
// getLogger().info( "Attempting to load " + location ); final File file = new File( getDataFolder(), "meshes/" + location.getWorldName() + "/" + location.getX() + "/" + location.getZ() + ".cmesh" );
final File file = new File( getDataFolder(), "meshes/" + location.getWorldName() + "/" + location.getX() + "/" + location.getZ() );
if ( file.exists() ) { if ( file.exists() ) {
// try { try {
// final ChunkMesh mesh = FileUtil.readObject( file ); final byte[] data = Files.readAllBytes( file.toPath() );
// final ChunkMesh mesh = ChunkMeshUtil.deserialize( data );
// return mesh;
// } catch ( ClassNotFoundException | IOException e ) { return mesh;
// e.printStackTrace(); } catch ( IOException e ) {
// } e.printStackTrace();
}
} }
return null; return null;
} }
private void saveMesh( final ChunkLocation location, final ChunkMesh mesh ) { private void saveMesh( final ChunkLocation location, final ChunkMesh mesh ) {
final File file = new File( getDataFolder(), "meshes/" + location.getWorldName() + "/" + location.getX() + "/" + location.getZ() ); final File file = new File( getDataFolder(), "meshes/" + location.getWorldName() + "/" + location.getX() + "/" + location.getZ() + ".cmesh" );
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
// FileUtil.writeObject( mesh, file ); final byte[] data = ChunkMeshUtil.serialize( mesh );
try {
Files.write( file.toPath(), data, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE, StandardOpenOption.WRITE );
} catch ( IOException e ) {
e.printStackTrace();
}
} }
private Collection< Facet > getFacetsConservative( Chunk chunk ) { private Collection< Facet > getFacetsConservative( Chunk chunk ) {
@@ -1407,11 +1382,7 @@ public class MiniePlugin extends JavaPlugin {
} }
final long time = System.currentTimeMillis() - start; final long time = System.currentTimeMillis() - start;
getLogger().info( "Took " + time + "ms to mesh chunk " + chunk.getX() + ", " + chunk.getZ() + " into " + mesh.countMeshVertices() + " vertices and " + mesh.countMeshTriangles() + " triangles" ); // getLogger().info( "Took " + time + "ms to mesh chunk " + chunk.getX() + ", " + chunk.getZ() + " into " + mesh.countMeshVertices() + " vertices and " + mesh.countMeshTriangles() + " triangles" );
chunk.removePluginChunkTicket( this );
loadMeshTasks.remove( chunkLocation );
} ); } );
} ); } );
} }

View File

@@ -18,6 +18,19 @@ public class Aabb implements Serializable {
public final int depth; public final int depth;
public final int height; public final int height;
public Aabb( final int minX, final int minY, final int minZ, final int maxX, final int maxY, final int maxZ ) {
this.minX = minX;
this.minY = minY;
this.minZ = minZ;
this.maxX = maxX;
this.maxY = maxY;
this.maxZ = maxZ;
this.width = maxX - minX;
this.depth = maxZ - minZ;
this.height = maxY - minY;
}
public Aabb( final BoundingBox box ) { public Aabb( final BoundingBox box ) {
this.minX = ( int ) box.getMinX(); this.minX = ( int ) box.getMinX();
this.maxX = ( int ) box.getMaxX(); this.maxX = ( int ) box.getMaxX();

View File

@@ -2,6 +2,8 @@ package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.util.BitSet; import java.util.BitSet;
import com.aaaaahhhhhhh.bananapuncher714.minietest.util.BinaryWriter;
public class BlockDataTypeSet { public class BlockDataTypeSet {
private static final int BLOCK_SIZE; private static final int BLOCK_SIZE;
private static final BitSet[] SETS; private static final BitSet[] SETS;
@@ -21,7 +23,11 @@ public class BlockDataTypeSet {
} }
} }
private BitSet buffer; protected BitSet buffer;
public BlockDataTypeSet( final byte[] arr ) {
buffer = BitSet.valueOf( arr );
}
public BlockDataTypeSet( final int size ) { public BlockDataTypeSet( final int size ) {
buffer = new BitSet( size * BLOCK_SIZE ); buffer = new BitSet( size * BLOCK_SIZE );
@@ -42,4 +48,8 @@ public class BlockDataTypeSet {
} }
return BlockDataType.values()[ ord ]; return BlockDataType.values()[ ord ];
} }
public byte[] toByteArray() {
return buffer.toByteArray();
}
} }

View File

@@ -1,6 +1,13 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects; package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
@@ -8,6 +15,10 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.util.BoundingBox; import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.MinecraftPlane.PlaneAxis;
import com.aaaaahhhhhhh.bananapuncher714.minietest.util.BinaryWriter;
import com.jme3.bullet.collision.shapes.infos.IndexedMesh; import com.jme3.bullet.collision.shapes.infos.IndexedMesh;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
@@ -28,7 +39,16 @@ public class ChunkMesh {
boxes = new BoundingBox[ volume ][]; boxes = new BoundingBox[ volume ][];
blockTypes = new BlockDataTypeSet( volume ); blockTypes = new BlockDataTypeSet( volume );
} }
public ChunkMesh( Aabb box ) {
this.box = box;
final int volume = this.box.getVolume();
data = new int[ volume ];
boxes = new BoundingBox[ volume ][];
blockTypes = new BlockDataTypeSet( volume );
}
public void setType( final int index, final BlockDataType type ) { public void setType( final int index, final BlockDataType type ) {
blockTypes.set( index, type ); blockTypes.set( index, type );
} }
@@ -95,4 +115,159 @@ public class ChunkMesh {
return new IndexedMesh( positionArray, indexArray ); return new IndexedMesh( positionArray, indexArray );
} }
public void writeTo( final BinaryWriter writer ) {
// Write the bounding box
writer.write( box.minX );
writer.write( box.minY );
writer.write( box.minZ );
writer.write( box.maxX );
writer.write( box.maxY );
writer.write( box.maxZ );
// Write our data blocks
for ( final int hash : data ) {
writer.write( hash );
}
// Write our bounding boxes
for ( final BoundingBox[] boxArr : boxes ) {
// Write how many boxes
if ( boxArr == null ) {
writer.write( ( int ) 0 );
} else {
writer.write( boxArr.length );
for ( final BoundingBox box : boxArr ) {
writer.write( box.getMinX() );
writer.write( box.getMinY() );
writer.write( box.getMinZ() );
writer.write( box.getMaxX() );
writer.write( box.getMaxY() );
writer.write( box.getMaxZ() );
}
}
}
// Write our block types
final byte[] blockTypeBuf = blockTypes.toByteArray();
writer.write( blockTypeBuf.length );
writer.write( blockTypeBuf );
// Write our point references
writer.write( pointReferences.size() );
for ( final VectorReference ref : pointReferences ) {
writer.write( ref.vector.getX() );
writer.write( ref.vector.getY() );
writer.write( ref.vector.getZ() );
writer.write( ref.index );
writer.write( ref.referenceCount );
}
// Write our planes
writer.write( planes.size() );
for ( final Entry< MinecraftPlane, PlaneMesh > entry : planes.entrySet() ) {
final MinecraftPlane plane = entry.getKey();
writer.write( plane.normal.ordinal() );
writer.write( plane.offset );
final PlaneMesh mesh = entry.getValue();
// Write each region
writer.write( mesh.generatedRegions.size() );
for ( final Polygon poly : mesh.generatedRegions ) {
writer.write( poly.getPoints().size() );
for ( final Point p : poly.getPoints() ) {
writer.write( p.getX() );
writer.write( p.getY() );
}
}
// Write each triangle
writer.write( mesh.completedPolygons.size() );
for ( final IndexedPolygon poly : mesh.completedPolygons ) {
writer.write( poly.points.size() );
for ( final VectorReference ref : poly.points ) {
writer.write( ref.index );
}
}
}
}
public static ChunkMesh readFrom( final ByteBuffer buffer ) {
final Aabb box = new Aabb( buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt() );
final ChunkMesh mesh = new ChunkMesh( box );
final int size = box.getVolume();
buffer.asIntBuffer().get( mesh.data );
buffer.position( buffer.position() + ( mesh.data.length << 2 ) );
// Read our boxes
for ( int i = 0; i < size; ++i ) {
final int boxCount = buffer.getInt();
if ( boxCount > 0 ) {
final BoundingBox[] boxes = new BoundingBox[ boxCount ];
for ( int j = 0; j < boxCount; ++j ) {
boxes[ j ] = new BoundingBox( buffer.getDouble(), buffer.getDouble(), buffer.getDouble(), buffer.getDouble(), buffer.getDouble(), buffer.getDouble() );
}
mesh.boxes[ i ] = boxes;
}
}
// Read the block types
final int blockTypeBufSize = buffer.getInt();
final byte[] bitSet = new byte[ blockTypeBufSize ];
buffer.get( bitSet );
mesh.blockTypes = new BlockDataTypeSet( bitSet );
// Read the plane references
final int pointCount = buffer.getInt();
final VectorReference[] references = new VectorReference[ pointCount ];
for ( int i = 0; i < pointCount; ++i ) {
final VectorReference reference = new VectorReference( new Vector( buffer.getDouble(), buffer.getDouble(), buffer.getDouble() ) );
reference.index = buffer.getInt();
reference.referenceCount = buffer.getInt();
references[ reference.index ] = reference;
mesh.pointReferences.add( reference );
}
// Get the planes
final int planeCount = buffer.getInt();
for ( int i = 0; i < planeCount; ++i ) {
final MinecraftPlane plane = new MinecraftPlane( PlaneAxis.values()[ buffer.getInt() ], buffer.getDouble() );
final PlaneMesh planeMesh = new PlaneMesh();
planeMesh.generatedRegions = new ArrayDeque< Polygon >();
planeMesh.completedPolygons = new ArrayDeque< IndexedPolygon >();
final int regionCount = buffer.getInt();
for ( int j = 0; j < regionCount; ++j ) {
final int vertexCount = buffer.getInt();
final List< Point > points = new ArrayList< Point >();
for ( int k = 0; k < vertexCount; ++k ) {
points.add( new Point( buffer.getDouble(), buffer.getDouble() ) );
}
planeMesh.generatedRegions.add( new Polygon( points ) );
}
final int triangleCount = buffer.getInt();
for ( int j = 0; j < triangleCount; ++j ) {
final int vertexCount = buffer.getInt();
final IndexedPolygon triangle = new IndexedPolygon();
for ( int k = 0; k < vertexCount; ++k ) {
triangle.points.add( references[ buffer.getInt() ] );
}
planeMesh.completedPolygons.add( triangle );
}
mesh.planes.put( plane, planeMesh );
}
return mesh;
}
} }

View File

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

View File

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