Added better chunk meshing

Fixed some collinear vertices not being scanned
Added live block mesh updates
This commit is contained in:
2025-05-31 02:37:26 -04:00
parent d45d644b32
commit 13c92dcb43
20 changed files with 1663 additions and 133 deletions

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ chunk_models_new
chunks_test
chunk_models_test
facets
polygons

View File

@@ -16,6 +16,7 @@ import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -92,7 +93,8 @@ public class Mesh< T extends Region > {
protected MeshState state = MeshState.TRIANGULATION_READY;
protected Collection< MeshEventHandler > handlers = new LinkedHashSet< MeshEventHandler >();
protected Collection< MeshChainEventHandler > chainHandlers = new LinkedHashSet< MeshChainEventHandler >();
protected Collection< MeshPartitionEventHandler > partitionHandlers = new LinkedHashSet< MeshPartitionEventHandler >();
protected boolean mergeChains = true;
@@ -113,7 +115,7 @@ public class Mesh< T extends Region > {
}
HalfEdge edge = null;
for ( Point point : poly.getPoints() ) {
for ( final Point point : poly.getPoints() ) {
if ( edge == null ) {
edge = new HalfEdge();
HalfEdge.splice( edge, edge.getSym() );
@@ -138,16 +140,28 @@ public class Mesh< T extends Region > {
}
}
public boolean addHandler( final MeshEventHandler handler ) {
return handlers.add( handler );
public boolean addPartitionHandler( final MeshPartitionEventHandler handler ) {
return partitionHandlers.add( handler );
}
public boolean removeHandler( final MeshEventHandler handler ) {
return handlers.remove( handler );
public boolean removePartitionHandler( final MeshPartitionEventHandler handler ) {
return partitionHandlers.remove( handler );
}
public Collection< MeshEventHandler > getHandlers() {
return Collections.unmodifiableCollection( handlers );
public boolean addChainHandler( final MeshChainEventHandler handler ) {
return chainHandlers.add( handler );
}
public boolean removeChainHandler( final MeshChainEventHandler handler ) {
return chainHandlers.remove( handler );
}
public Collection< MeshChainEventHandler > getChainHandlers() {
return Collections.unmodifiableCollection( chainHandlers );
}
public Collection< MeshPartitionEventHandler > getPartitionHandlers() {
return Collections.unmodifiableCollection( partitionHandlers );
}
public Set< Point > getVertices() {
@@ -163,12 +177,35 @@ public class Mesh< T extends Region > {
return set.parallelStream().map( e -> new Segment( pointMap.get( e.getOrigin() ), pointMap.get( e.getDest() ) ) ).collect( Collectors.toSet() );
}
public Collection< Polygon > getPolygons() {
if ( state != MeshState.TRIANGULATION_READY ) {
throw new IllegalStateException( "Cannot form polygons!" );
}
final Collection< Polygon > polygons = new ArrayDeque< Polygon >();
final Map< Vertex, Point > pointMap = vertices.parallelStream().collect( Collectors.toMap( Function.identity(), v -> new Point( v.getPosition() ) ) );
final Collection< HalfEdge > scanned = new HashSet< HalfEdge >();
for ( HalfEdge edge : interiorEdges ) {
if ( scanned.add( edge ) ) {
final List< Point > points = new ArrayList< Point >();
do {
points.add( pointMap.get( edge.getOrigin() ) );
} while ( scanned.add( edge = edge.getNext() ) );
polygons.add( new Polygon( points ) );
}
}
return polygons;
}
public int getVertexCount() {
return vertices.size();
}
public int getEdgeCount() {
return rules.size();
return rules.size() >> 1;
}
public boolean isMergeChains() {
@@ -305,7 +342,8 @@ public class Mesh< T extends Region > {
vertexSet.parallelStream().forEach( Vertex::update );
// Do a quick merging of edges so we don't have to deal with them later
mergeOverlappingEdges( vertexSet );
// Not really necessary, but _may_ offer a speedup.
// mergeOverlappingEdges( vertexSet );
/*
* As we iterate through each vertex, there are certain operations
@@ -320,6 +358,8 @@ public class Mesh< T extends Region > {
// This set contains the non-redundant vertices.
final Set< Vertex > scannedVertices = new HashSet< Vertex >();
// TODO Remove zero edges
while ( !vertexSet.isEmpty() ) {
final Vertex vertex = vertexSet.pollFirst();
final Vector2d pos = vertex.getPosition();
@@ -350,7 +390,7 @@ public class Mesh< T extends Region > {
{
// Remove any zero-edges
HalfEdge validEdge = null;
for ( HalfEdge edge : vertex ) {
for ( final HalfEdge edge : vertex ) {
if ( edge.isZero() ) {
// At this point in time, if the edge is zero,
// then the origin and destination must be the same
@@ -537,11 +577,18 @@ public class Mesh< T extends Region > {
// This assumes that if a and b are collinear, then they _must_ share
// the same origin, since we are traversing through the vertices
// starting with the smallest.
for ( final HalfEdge edge : vertex ) {
final Collection< HalfEdge > scanned = new HashSet< HalfEdge >();
HalfEdge edge = vertex.getEdge();
while ( scanned.add( edge ) ) {
// Only check this edge if it is not already scanned
if ( checked.add( edge ) ) {
final Vector2d r = edge.toVector2d().normalize();
for ( final HalfEdge other : vertex ) {
// Keep track of which edges we have already looped over during this sub-loop
// which we have not checked with the main loop
final Collection< HalfEdge > subScanned = new HashSet< HalfEdge >( scanned );
HalfEdge other = edge.getPrev();
while ( subScanned.add( other ) ) {
if ( !checked.contains( other ) ) {
// Do not split edges that are pretty much equal, since they will get merged later
if ( !isSimilar( edge.getDest(), other.getDest() ) ) {
@@ -550,7 +597,6 @@ public class Mesh< T extends Region > {
// Ignore edges that are not collinear
if ( Math.abs( angle ) <= CROSS_TOLERANCE ) {
HalfEdge shorter;
HalfEdge longer;
@@ -563,24 +609,54 @@ public class Mesh< T extends Region > {
longer = edge;
}
// Update the winding rule for the split
final HalfEdge split = longer.split();
RegionRule< T > rule = rules.get( longer );
rules.put( split, rule );
rules.put( split.getSym(), rule.inverse() );
{
// Update the winding rule for the split
final HalfEdge split = longer.split();
final RegionRule< T > rule = rules.get( longer );
rules.put( split, rule );
rules.put( split.getSym(), rule.inverse() );
// Update the vertex for the split
final Vertex vert = shorter.getDest();
split.setOrigin( vert );
longer.getSym().setOrigin( vert );
// Update the vertex for the split
final Vertex vert = shorter.getDest();
split.setOrigin( vert );
longer.getSym().setOrigin( vert );
vert.setEdge( split );
// Splice the two together
HalfEdge.splice( shorter.getSym(), split );
// Splice the two together
HalfEdge.splice( shorter.getSym(), split );
}
/*
* Now that we've successfully split the shorter and longer edges,
* remove the other edge, but keep the current edge that we are
* iterating over...
*/
{
// Get and merge the rule
RegionRule< T > rule = rules.get( edge );
rule = rule.apply( rules.remove( other ) );
rules.remove( other.getSym() );
// Now remove the edge
HalfEdge.splice( other, other.getSym().getNext() );
HalfEdge.splice( other.getSym(), other.getNext() );
rules.put( edge, rule );
rules.put( edge.getSym(), rule.inverse() );
}
other = edge.getPrev();
} else {
other = other.getPrev();
}
}
}
}
}
edge = edge.getPrev();
}
}
@@ -1103,7 +1179,7 @@ public class Mesh< T extends Region > {
if ( mergeChains ) {
final Collection< Link > allChains = findChains( vertices, edges );
final Collection< Link > chains = maximizeChains( allChains );
if ( !handlers.isEmpty() ) {
if ( !chainHandlers.isEmpty() ) {
final Collection< Chain > newChains = new HashSet< Chain >();
final Collection< Link > seen = new HashSet< Link >();
@@ -1127,7 +1203,7 @@ public class Mesh< T extends Region > {
}
}
handlers.forEach( h -> h.onChainGenerationEvent( newChains ) );
chainHandlers.forEach( h -> h.onChainGenerationEvent( newChains ) );
final Collection< Link > selectedSeen = new HashSet< Link >();
final Collection< Chain > selectedChains = new HashSet< Chain >();
@@ -1151,7 +1227,7 @@ public class Mesh< T extends Region > {
}
}
handlers.forEach( h -> h.onPreChainMergeEvent( selectedChains ) );
chainHandlers.forEach( h -> h.onPreChainMergeEvent( selectedChains ) );
}
mergeChains( chains, edges );
@@ -1159,7 +1235,7 @@ public class Mesh< T extends Region > {
final Collection< EdgePolygon > polys = partitionMonotone( vertices, edges );
if ( handlers != null ) {
if ( partitionHandlers != null ) {
final Collection< Polygon > polygons = new LinkedHashSet< Polygon >();
for ( EdgePolygon p : polys ) {
@@ -1172,7 +1248,7 @@ public class Mesh< T extends Region > {
polygons.add( new Polygon( points ) );
}
handlers.forEach( h -> h.onPartitionEvent( polygons ) );
partitionHandlers.forEach( h -> h.onPartitionEvent( polygons ) );
}
if ( mergeChains ) {
@@ -3076,10 +3152,13 @@ public class Mesh< T extends Region > {
}
public static interface MeshEventHandler {
public static interface MeshPartitionEventHandler {
default void onPartitionEvent( final Collection< Polygon > polygons ) {};
}
public static interface MeshChainEventHandler {
default void onChainGenerationEvent( final Collection< Chain > chains ) {};
default void onPreChainMergeEvent( final Collection< Chain > chains ) {};
default void onPartitionEvent( final Collection< Polygon > polygons ) {};
}
protected enum MeshState {

View File

@@ -0,0 +1,51 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.ReentrantLock;
import org.bukkit.Chunk;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkLocation;
import com.aaaaahhhhhhh.bananapuncher714.minietest.objects.ChunkMesh;
public class ChunkMeshCache {
final File directory;
Map< ChunkLocation, ChunkMesh > meshes = new TreeMap< ChunkLocation, ChunkMesh >();
Collection< ChunkLocation > shouldRemove = new HashSet< ChunkLocation >();
ReentrantLock lock = new ReentrantLock();
public ChunkMeshCache( final File directory ) {
this.directory = directory;
}
public void onChunkLoad( final Chunk chunk ) {
final ChunkLocation location = new ChunkLocation( chunk );
// Unqueue this chunk for removal if it's queued
shouldRemove.remove( location );
// TODO Add a ticket
chunk.addPluginChunkTicket( null );
// Does the mesh map contain the current location?
{
// If not, then load or mesh the chunk
// Emit a chunk mesh complete event
}
}
public void saveAndRemove( final ChunkLocation location ) {
lock.lock();
meshes.remove( location );
lock.unlock();
}
public void saveAndRemoveAll() {
lock.unlock();
}
}

View File

@@ -17,6 +17,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -38,7 +39,8 @@ import org.bukkit.util.Vector;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Chain;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh.MeshEventHandler;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh.MeshChainEventHandler;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh.MeshPartitionEventHandler;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Segment;
@@ -134,7 +136,7 @@ public class MeshingTest2 extends JPanel {
final List< Plane > masterPlanes = new ArrayList< Plane >();
System.out.println( "Adding test plane" );
masterPlanes.add( getTestPlane() );
masterPlanes.addAll( getTestPlanes() );
System.out.println( "Chunk files: " + CHUNK_DIR.listFiles().length );
int limit = 1;
@@ -279,7 +281,7 @@ public class MeshingTest2 extends JPanel {
System.out.println( "After generating regions vertex count: " + mesh.getVertices().size() );
System.out.println( "After generating regions edge count: " + ( mesh.getEdgeCount() / 2 ) );
mesh.addHandler( new MeshEventHandler() {
mesh.addChainHandler( new MeshChainEventHandler() {
@Override
public void onChainGenerationEvent( Collection< Chain > chains ) {
data.chains = chains;
@@ -310,7 +312,9 @@ public class MeshingTest2 extends JPanel {
// }
// }
}
} );
mesh.addPartitionHandler( new MeshPartitionEventHandler() {
@Override
public void onPartitionEvent( Collection< Polygon > polygons ) {
data.polygons = polygons;
@@ -391,7 +395,7 @@ public class MeshingTest2 extends JPanel {
private static PlaneData test() {
final PlaneData data = new PlaneData();
final Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
mesh.addHandler( new MeshEventHandler() {
mesh.addChainHandler( new MeshChainEventHandler() {
@Override
public void onChainGenerationEvent( Collection< Chain > chains ) {
data.chains = chains;
@@ -552,11 +556,13 @@ public class MeshingTest2 extends JPanel {
return data;
}
private static Plane getTestPlane() {
final Plane plane = new Plane();
private static Collection< Plane > getTestPlanes() {
final Collection< Plane > planes = new ArrayDeque< Plane >();
{
final Plane plane = new Plane();
plane.normal = new Vector( 0, 0, 1 );
plane.point = new Vector( 1, 0, 0 );
plane.normal = new Vector( 0, 0, 1 );
plane.point = new Vector( 1, 0, 0 );
// plane.polygons.add( new Polygon( Arrays.asList(
// new Point( 1, 0 ),
@@ -586,62 +592,113 @@ public class MeshingTest2 extends JPanel {
// new Point( 0, 1 )
// ) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -1, -1 ),
new Point( 1, -1 ),
new Point( 1, 1 ),
new Point( -1, 1 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -1, -1 ),
new Point( 1, -1 ),
new Point( 1, 1 ),
new Point( -1, 1 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -1, -1 ),
new Point( 1, -1 ),
new Point( 1, 1 ),
new Point( -1, 1 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -1, -1 ),
new Point( 1, -1 ),
new Point( 1, 1 ),
new Point( -1, 1 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -1, -1 ),
new Point( 1, -1 ),
new Point( 1, 1 ),
new Point( -1, 1 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -1, 1 ),
new Point( 1, 1 ),
new Point( 1, 3 ),
new Point( -1, 3 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -2, -1 ),
new Point( -1, -1 ),
new Point( -1, 2 ),
new Point( -2, 2 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -4, -2 ),
new Point( -1, -2 ),
new Point( -1, 1.5 ),
new Point( -4, 1.5 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -3, 0 ),
new Point( 3, 0 ),
new Point( -1.5, -3 ),
new Point( 0, 1.5 ),
new Point( 1.5, -3 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -1, 0 ),
new Point( 0, 1.5 ),
new Point( 1, 0 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( 1, 0 ),
new Point( 0, -2 ),
new Point( -1, 0 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -1, -1 ),
new Point( 1, -1 ),
new Point( 1, 1 ),
new Point( -1, 1 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -1, 1 ),
new Point( 1, 1 ),
new Point( 1, 3 ),
new Point( -1, 3 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -2, -1 ),
new Point( -1, -1 ),
new Point( -1, 2 ),
new Point( -2, 2 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -4, -2 ),
new Point( -1, -2 ),
new Point( -1, 1.5 ),
new Point( -4, 1.5 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -3, 0 ),
new Point( 3, 0 ),
new Point( -1.5, -3 ),
new Point( 0, 1.5 ),
new Point( 1.5, -3 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( -1, 0 ),
new Point( 0, 1.5 ),
new Point( 1, 0 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( 1, 0 ),
new Point( 0, -2 ),
new Point( -1, 0 )
) ) );
return plane;
planes.add( plane );
}
{
final Plane plane = new Plane();
plane.normal = new Vector( 0, 0, 1 );
plane.point = new Vector( 1, 0, 0 );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( 0, 0 ),
new Point( 3, 0 ),
new Point( 3, 2 ),
new Point( 2, 2 ),
new Point( 2, 1 ),
new Point( 1, 1 ),
new Point( 1, 2 ),
new Point( 0, 2 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( 1, 1 ),
new Point( 2, 1 ),
new Point( 2, 2 ),
new Point( 1, 2 )
) ) );
planes.add( plane );
}
{
final Plane plane = new Plane();
plane.normal = new Vector( 0, 0, 1 );
plane.point = new Vector( 1, 0, 0 );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( 0, 0 ),
new Point( 3, 0 ),
new Point( 3, 2 ),
new Point( 0, 2 )
) ) );
plane.polygons.add( new Polygon( Arrays.asList(
new Point( 1, 1 ),
new Point( 2, 1 ),
new Point( 2, 2 ),
new Point( 1, 2 )
) ) );
planes.add( plane );
}
return planes;
}
private static List< Facet > generateFacetsFor( AABB box ) {

View File

@@ -8,7 +8,11 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.bukkit.util.Vector;
@@ -27,6 +31,8 @@ public class MeshingTest5 {
private static final File BASE = new File( System.getProperty( "user.dir" ) );
private static final File FACET_DIR = new File( BASE, "facets" );
private static Collection< Facet > facets = Collections.synchronizedCollection( new LinkedHashSet< Facet >() );
public static void main( String[] args ) {
if ( FACET_DIR.exists() && FACET_DIR.isDirectory() ) {
System.out.println( "Found " + FACET_DIR.list().length + " files" );
@@ -100,7 +106,9 @@ public class MeshingTest5 {
mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE );
}
mesh.meshify();
synchronized( facets ) {
facets.addAll( plane.convert( mesh.meshify() ) );
}
} else {
System.out.println( "No data!" );
}

View File

@@ -38,7 +38,8 @@ import org.bukkit.util.Vector;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Chain;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh.MeshEventHandler;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh.MeshChainEventHandler;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh.MeshPartitionEventHandler;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Segment;
@@ -292,7 +293,7 @@ public class MeshingTest6 extends JPanel {
System.out.println( "After generating regions vertex count: " + mesh.getVertices().size() );
System.out.println( "After generating regions edge count: " + ( mesh.getEdgeCount() / 2 ) );
mesh.addHandler( new MeshEventHandler() {
mesh.addChainHandler( new MeshChainEventHandler() {
@Override
public void onChainGenerationEvent( Collection< Chain > chains ) {
data.chains = chains;
@@ -323,7 +324,9 @@ public class MeshingTest6 extends JPanel {
}
}
}
} );
mesh.addPartitionHandler( new MeshPartitionEventHandler() {
@Override
public void onPartitionEvent( Collection< Polygon > polygons ) {
data.polygons = polygons;
@@ -411,7 +414,7 @@ public class MeshingTest6 extends JPanel {
private static PlaneData test() {
final PlaneData data = new PlaneData();
final Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
mesh.addHandler( new MeshEventHandler() {
mesh.addChainHandler( new MeshChainEventHandler() {
@Override
public void onChainGenerationEvent( Collection< Chain > chains ) {
data.chains = chains;

View File

@@ -0,0 +1,95 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Mesh;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionRuleWinding;
import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple;
import com.aaaaahhhhhhh.bananapuncher714.mesh.region.simple.RegionSimple.GluWindingRule;
public class MeshingTest7 {
private static final File BASE = new File( System.getProperty( "user.dir" ) );
private static final File POLYGON_DIR = new File( BASE, "polygons" );
public static void main( String[] args ) {
if ( POLYGON_DIR.exists() && POLYGON_DIR.isDirectory() ) {
System.out.println( "Found " + POLYGON_DIR.list().length + " files" );
final long allStart = System.currentTimeMillis();
final AtomicInteger index = new AtomicInteger( 0 );
final Collection< File > files = Arrays.asList( POLYGON_DIR.listFiles() );
files.parallelStream().forEach( f -> {
final StringBuilder builder = new StringBuilder();
builder.append( "Meshing file: " + f + "\n" );
final long start = System.currentTimeMillis();
final List< Polygon > polygons = mesh( f, builder );
Mesh< RegionSimple > mesh = new Mesh< RegionSimple >( () -> { return new RegionSimple( GluWindingRule.ODD ); } );
for ( Polygon poly : polygons ) {
mesh.addPolygon( poly, RegionRuleWinding.CLOCKWISE );
}
mesh.meshify();
long time = System.currentTimeMillis() - start;
builder.append( "\tTook " + time + "ms\n" );
builder.append( "\tCompleted " + index.incrementAndGet() + "/" + files.size() );
System.out.println( builder.toString() );
} );
System.out.println( "Took a total of " + ( System.currentTimeMillis() - allStart ) + "ms" );
} else {
System.err.println( "No such directory exists: " + POLYGON_DIR );
}
}
private static List< Polygon > mesh( File file, final StringBuilder stringBuilder ) {
// First parse the file to get a list of all polygons
final List< Polygon > polys = new ArrayList< Polygon >();
try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) {
String line;
while ( ( line = reader.readLine() ) != null ) {
if ( !line.isEmpty() ) {
final int pointCount = Integer.parseInt( line );
List< Point > points = new ArrayList< Point >( pointCount );
int point = 0;
while ( point < pointCount && ( line = reader.readLine() ) != null ) {
if ( !line.isEmpty() ) {
String[] values2 = line.split( " " );
final double px = Double.parseDouble( values2[ 0 ] );
final double py = Double.parseDouble( values2[ 1 ] );
points.add( new Point( px, py ) );
++point;
}
}
polys.add( new Polygon( points ) ) ;
}
}
} catch ( FileNotFoundException e ) {
e.printStackTrace();
return null;
} catch ( IOException e ) {
e.printStackTrace();
return null;
}
return polys;
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.io.Serializable;
import org.bukkit.util.BoundingBox;
public class Aabb implements Serializable {
private static final long serialVersionUID = 6518512405568765913L;
public final int minX;
public final int minY;
public final int minZ;
public final int maxX;
public final int maxY;
public final int maxZ;
public final int width;
public final int depth;
public final int height;
public Aabb( final BoundingBox box ) {
this.minX = ( int ) box.getMinX();
this.maxX = ( int ) box.getMaxX();
this.minY = ( int ) box.getMinY();
this.maxY = ( int ) box.getMaxY();
this.minZ = ( int ) box.getMinZ();
this.maxZ = ( int ) box.getMaxZ();
this.width = maxX - minX;
this.depth = maxZ - minZ;
this.height = maxY - minY;
}
public int getVolume() {
return width * depth * height;
}
}

View File

@@ -0,0 +1,5 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
public enum BlockDataType {
NONE, COMPLEX, SOLID
}

View File

@@ -0,0 +1,45 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.util.BitSet;
public class BlockDataTypeSet {
private static final int BLOCK_SIZE;
private static final BitSet[] SETS;
static {
final int length = BlockDataType.values().length;
BLOCK_SIZE = 32 - Integer.numberOfLeadingZeros( length );
SETS = new BitSet[ length ];
// Perhaps not the fastest, since it sets each bit in N time
for ( int i = 0; i < length; ++i ) {
final BitSet set = new BitSet();
for ( int j = 0; j < BLOCK_SIZE; ++j ) {
set.set( j, ( ( i >> j ) & 1 ) == 1 );
}
SETS[ i ] = set;
}
}
private BitSet buffer;
public BlockDataTypeSet( final int size ) {
buffer = new BitSet( size * BLOCK_SIZE );
}
public void set( final int index, final BlockDataType type ) {
final BitSet set = SETS[ type.ordinal() ];
for ( int i = 0; i < BLOCK_SIZE; ++i ) {
buffer.set( ( index * BLOCK_SIZE ) + i, set.get( i ) );
}
}
public BlockDataType get( final int index ) {
final int bufferIndex = index * BLOCK_SIZE;
int ord = 0;
for ( int i = buffer.nextSetBit( bufferIndex ); i >= bufferIndex && i < bufferIndex + BLOCK_SIZE; i = buffer.nextSetBit( i + 1 ) ) {
ord |= 1 << ( i - bufferIndex );
}
return BlockDataType.values()[ ord ];
}
}

View File

@@ -12,7 +12,7 @@ import org.bukkit.World;
*
* @author BananaPuncher714
*/
public class ChunkLocation {
public class ChunkLocation implements Comparable< ChunkLocation > {
private int x, z;
private String worldName;
private World world;
@@ -283,4 +283,16 @@ public class ChunkLocation {
public String toString() {
return "ChunkLocation{x:" + x + ",z:" + z + ",world:" + worldName + "}";
}
@Override
public int compareTo( ChunkLocation o ) {
final int worldCompare = worldName.compareTo( o.worldName );
if ( worldCompare == 0 ) {
if ( x == o.x ) {
return Integer.compare( z, o.z );
}
return Integer.compare( x, o.x );
}
return worldCompare;
}
}

View File

@@ -0,0 +1,98 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import org.bukkit.block.data.BlockData;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import com.jme3.bullet.collision.shapes.infos.IndexedMesh;
import com.jme3.math.Vector3f;
public class ChunkMesh {
// TODO Public for now while I figure out how to organize the stuff properly
private int[] data;
private BoundingBox[][] boxes;
private BlockDataTypeSet blockTypes;
public TreeSet< VectorReference > pointReferences = new TreeSet< VectorReference >();
public Map< MinecraftPlane, PlaneMesh > planes = new TreeMap< MinecraftPlane, PlaneMesh >();
private Aabb box;
public ChunkMesh( BoundingBox box ) {
this.box = new Aabb( 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 ) {
blockTypes.set( index, type );
}
public BlockDataType getType( final int index ) {
return blockTypes.get( index );
}
public void setData( final int index, final BlockData data ) {
this.data[ index ] = data.getAsString().hashCode();
}
public void setBoxes( final int index, final BoundingBox[] boxes ) {
this.boxes[ index ] = boxes;
}
public boolean isBlockAt( final int index, final BlockData data ) {
return blockTypes.get( index ) == BlockDataType.COMPLEX && this.data[ index ] == data.getAsString().hashCode();
}
public BoundingBox[] getBoxesAt( final int index ) {
return boxes[ index ];
}
public PlaneMesh getOrAdd( final MinecraftPlane plane ) {
PlaneMesh results = planes.get( plane );
if ( results == null ) {
results = new PlaneMesh();
planes.put( plane, results );
}
return results;
}
public IndexedMesh toIndexedMesh() {
final Vector3f[] positionArray = new Vector3f[ pointReferences.size() ];
int vertexIndex = 0;
for ( final VectorReference ref : pointReferences ) {
final Vector vector = ref.vector;
positionArray[ vertexIndex ] = new Vector3f( ( float ) vector.getX(), ( float ) vector.getY(), ( float ) vector.getZ() );
ref.index = vertexIndex;
++vertexIndex;
}
final int totalTriangleCount = planes.values().stream().mapToInt( m -> m.completedPolygons.size() ).sum();
int triangleIndex = 0;
final int[] indexArray = new int[ totalTriangleCount * 3 ];
for ( final PlaneMesh mesh : planes.values() ) {
for ( final IndexedPolygon triangle : mesh.completedPolygons ) {
if ( triangle.points.size() != 3 ) {
throw new IllegalStateException( "Triangle does not have exactly 3 vertices!" );
}
final int indexArrayStart = triangleIndex * 3;
indexArray[ indexArrayStart ] = triangle.points.get( 0 ).index;
indexArray[ indexArrayStart + 1 ] = triangle.points.get( 1 ).index;
indexArray[ indexArrayStart + 2 ] = triangle.points.get( 2 ).index;
++triangleIndex;
}
}
return new IndexedMesh( positionArray, indexArray );
}
}

View File

@@ -1,12 +0,0 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.util.Collection;
import java.util.Collections;
public class Component {
public Collection< Component > getComponents() {
return Collections.emptySet();
}
}

View File

@@ -0,0 +1,8 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.util.ArrayList;
import java.util.List;
public class IndexedPolygon {
public List< VectorReference > points = new ArrayList< VectorReference >();
}

View File

@@ -0,0 +1,74 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.io.Serializable;
import org.bukkit.util.Vector;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Point;
public class MinecraftPlane implements Comparable< MinecraftPlane > {
public final PlaneAxis normal;
public final double offset;
public MinecraftPlane( final PlaneAxis axis, final double offset ) {
this.normal = axis;
this.offset = offset;
}
public Vector convert( final Point point ) {
switch ( normal ) {
case X:
return new Vector( offset, point.getX(), point.getY() );
case Y:
return new Vector( point.getX(), offset, point.getY() );
case Z:
return new Vector( point.getX(), point.getY(), offset );
}
throw new IllegalStateException( "No normal set!" );
}
@Override
public int compareTo( final MinecraftPlane o ) {
if ( normal == o.normal ) {
return Double.compare( offset, o.offset );
}
return normal.compareTo( o.normal );
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((normal == null) ? 0 : normal.hashCode());
long temp;
temp = Double.doubleToLongBits(offset);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MinecraftPlane other = (MinecraftPlane) obj;
if (normal != other.normal)
return false;
if (Double.doubleToLongBits(offset) != Double.doubleToLongBits(other.offset))
return false;
return true;
}
@Override
public String toString() {
return "MinecraftPlane{axis=" + normal + ",offset=" + offset + "}";
}
public static enum PlaneAxis {
X, Y, Z
}
}

View File

@@ -0,0 +1,10 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import java.util.Collection;
import com.aaaaahhhhhhh.bananapuncher714.mesh.Polygon;
public class PlaneMesh {
public Collection< Polygon > generatedRegions;
public Collection< IndexedPolygon > completedPolygons;
}

View File

@@ -0,0 +1,29 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.objects;
import org.bukkit.util.Vector;
public class VectorReference implements Comparable< VectorReference > {
public Vector vector;
public int referenceCount = 0;
public int index = 0;
public VectorReference( final Vector vector ) {
this.vector = vector;
}
@Override
public int compareTo( final VectorReference o ) {
final Vector oVec = o.vector;
if ( vector.getX() == oVec.getX() ) {
if ( vector.getY() == oVec.getY() ) {
if ( vector.getZ() == oVec.getZ() ) {
return 0;
}
return Double.compare( vector.getZ(), oVec.getZ() );
}
return Double.compare( vector.getY(), oVec.getY() );
}
return Double.compare( vector.getX(), oVec.getX() );
}
}

View File

@@ -0,0 +1,5 @@
package com.aaaaahhhhhhh.bananapuncher714.minietest.util;
public class ChunkMeshUtil {
}

View File

@@ -105,7 +105,7 @@ public final class FileUtil {
file.delete();
}
public static < T extends Serializable > T readObject( Class< T > clazz, File file ) throws IOException, ClassNotFoundException {
public static < T extends Serializable > T readObject( File file ) throws IOException, ClassNotFoundException {
if ( !file.exists() ) {
return null;
}