Speedup the mesh simplification

Change EdgeSet to use LinkedHashSet for better iteration, potentially.
Map polygon points to existing vertices to reduce unnecessary vertex duplication
Attempt to merge overlapping edges initially before doing anything else when simplifying
Use tree sets instead of priority queues for constant time poll
Split intersection calculation method into two separate methods
Rework which edges are actively within the scan zone to reduce the amount of edges to check when looking for intersections. This should help make the entire algorithm closer to O(nlogn)
Removed intersection classes
Added method to resolve only collinear edges around a vertex
Added method to resolve edge intersections directly from the edge, rather than the positive side
Added simple Y check to see if two edges can even intersect
Added method to calculate absolute vector
Added test plane by default to MeshingTest2(the plane viewer)
This commit is contained in:
2025-05-25 21:27:25 -04:00
parent 83e3b9443b
commit 7c49e004b7
6 changed files with 336 additions and 267 deletions

View File

@@ -1,13 +1,13 @@
package com.aaaaahhhhhhh.bananapuncher714.mesh; package com.aaaaahhhhhhh.bananapuncher714.mesh;
import java.util.HashSet; import java.util.LinkedHashSet;
/** /**
* A set for easily checking if a set contains/does not contain a half-edge or its sym. * A set for easily checking if a set contains/does not contain a half-edge or its sym.
* *
* @author BananaPuncher714 * @author BananaPuncher714
*/ */
public class EdgeSet extends HashSet< HalfEdge > { public class EdgeSet extends LinkedHashSet< HalfEdge > {
/** /**
* *
*/ */

View File

@@ -9,12 +9,9 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Optional;
import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
@@ -81,6 +78,9 @@ public class Mesh< T extends Region > {
public static final double ANGLE_TOLERANCE = Math.toRadians( 1e-10 ); public static final double ANGLE_TOLERANCE = Math.toRadians( 1e-10 );
public static final double CROSS_TOLERANCE = Math.sin( ANGLE_TOLERANCE ); public static final double CROSS_TOLERANCE = Math.sin( ANGLE_TOLERANCE );
// Point to vertex map, to avoid duplicating vertices when inserting polygons
protected Map< Point, Vertex > vertexMap = new HashMap< Point, Vertex >();
// All points in the graph // All points in the graph
protected Collection< Vertex > vertices = new ArrayDeque< Vertex >(); protected Collection< Vertex > vertices = new ArrayDeque< Vertex >();
// All rules associated with a given half edge // All rules associated with a given half edge
@@ -122,13 +122,18 @@ public class Mesh< T extends Region > {
edge = edge.split(); edge = edge.split();
} }
edge.getOrigin().setPosition( new Vector2d( point ) ); final Vertex vertex = vertexMap.get( point );
if ( vertex != null ) {
HalfEdge.splice( edge.getPrev(), vertex.getEdge() );
} else {
edge.getOrigin().setPosition( new Vector2d( point ) );
vertices.add( edge.getOrigin() );
}
rules.put( edge, rule ); rules.put( edge, rule );
rules.put( edge.getSym(), rule.inverse() ); rules.put( edge.getSym(), rule.inverse() );
vertices.add( edge.getOrigin() );
state = MeshState.DIRTY; state = MeshState.DIRTY;
} }
} }
@@ -179,6 +184,7 @@ public class Mesh< T extends Region > {
vertices.clear(); vertices.clear();
rules.clear(); rules.clear();
interiorEdges.clear(); interiorEdges.clear();
vertexMap.clear();
} }
public Mesh< T > copyOf() { public Mesh< T > copyOf() {
@@ -244,7 +250,7 @@ public class Mesh< T extends Region > {
} }
} }
copy.vertices.parallelStream().forEach( v -> sort( v ) ); copy.vertices.parallelStream().forEach( v -> { sort( v ); copy.vertexMap.put( v.getPosition(), v ); } );
copy.state = state; copy.state = state;
return copy; return copy;
@@ -282,9 +288,24 @@ public class Mesh< T extends Region > {
* will be simple, since there can be holes. * will be simple, since there can be holes.
*/ */
public void simplify() { public void simplify() {
// Create a queue and insert all vertices in O(nlogn) time. // Create a sorted set with duplicate vertices allowed
final Queue< Vertex > queue = new PriorityQueue< Vertex >( Mesh::compare ); // Insert all vertices in O(nlogn) time.
queue.addAll( vertices ); // Use a treeset instead of a priority queue for constant time poll first
final TreeSet< Vertex > vertexSet = new TreeSet< Vertex >( ( a, b ) -> {
final int compare = compare( a, b );
if ( compare == 0 ) {
return Integer.compare( a.hashCode(), b.hashCode() );
} else {
return compare;
}
} );
vertexSet.addAll( vertices );
// Need to update vertices if using the vertex map when inserting polygons
vertexSet.parallelStream().forEach( Vertex::update );
// Do a quick merging of edges so we don't have to deal with them later
mergeOverlappingEdges( vertexSet );
/* /*
* As we iterate through each vertex, there are certain operations * As we iterate through each vertex, there are certain operations
@@ -299,35 +320,31 @@ public class Mesh< T extends Region > {
// This set contains the non-redundant vertices. // This set contains the non-redundant vertices.
final Set< Vertex > scannedVertices = new HashSet< Vertex >(); final Set< Vertex > scannedVertices = new HashSet< Vertex >();
Vertex vertex; while ( !vertexSet.isEmpty() ) {
while ( ( vertex = queue.poll() ) != null ) { final Vertex vertex = vertexSet.pollFirst();
final Vector2d pos = vertex.getPosition(); final Vector2d pos = vertex.getPosition();
{
final List< Vertex > addBack = new LinkedList< Vertex >();
Vertex other;
// We must poll and add back later since priority queues are not
// sorted, so an iterator over its elements would not guarantee
// the correct ordering unless we poll it.
while ( ( other = queue.poll() ) != null ) {
final Vector2d otherPos = other.getPosition();
// Once the vertex scanned is past the tolerance in the X direction, // We must poll and add back later since priority queues are not
// we know there are no more vertices which can be merged with the // sorted, so an iterator over its elements would not guarantee
// current vertex. // the correct ordering unless we poll it.
if ( compareX( otherPos, pos ) > VERTEX_TOLERANCE ) { for ( final Iterator< Vertex > it = vertexSet.iterator(); it.hasNext(); ) {
addBack.add( other ); final Vertex other = it.next();
break; final Vector2d otherPos = other.getPosition();
}
// Check if the vertices are close enough that they can // Once the vertex scanned is past the tolerance in the X direction,
// be considered "the same" // we know there are no more vertices which can be merged with the
if ( isSimilar( pos, otherPos ) ) { // current vertex.
HalfEdge.splice( vertex.getEdge(), other.getEdge() ); if ( compareX( otherPos, pos ) > VERTEX_TOLERANCE ) {
} else { break;
addBack.add( other ); }
}
// Check if the vertices are close enough that they can
// be considered "the same"
if ( isSimilar( pos, otherPos ) ) {
HalfEdge.splice( vertex.getEdge(), other.getEdge() );
it.remove();
} }
queue.addAll( addBack );
} }
{ {
@@ -358,14 +375,8 @@ public class Mesh< T extends Region > {
} }
} }
// Add all edges from this vertex if they do not already exist in
// the set of scanned edges
for ( final HalfEdge edge : vertex ) {
scannedEdges.add( edge );
}
// Add edges that belong to vertices up to VERTEX_TOLERANCE away in the X direction // Add edges that belong to vertices up to VERTEX_TOLERANCE away in the X direction
for ( final Iterator< Vertex > it = queue.iterator(); it.hasNext(); ) { for ( final Iterator< Vertex > it = vertexSet.iterator(); it.hasNext(); ) {
final Vertex other = it.next(); final Vertex other = it.next();
final Vector2d otherPos = other.getPosition(); final Vector2d otherPos = other.getPosition();
@@ -377,28 +388,36 @@ public class Mesh< T extends Region > {
// horizontally onto the exact same x position as this vertex. // horizontally onto the exact same x position as this vertex.
// It saves us from having fuzzy errors in the future. // It saves us from having fuzzy errors in the future.
otherPos.setX( pos.getX() ); otherPos.setX( pos.getX() );
for ( final HalfEdge edge : other ) {
scannedEdges.add( edge );
}
} }
// Update all edges this vertex is connected to for consistency // Update all edges this vertex is connected to for consistency
vertex.update(); vertex.update();
// Merge any vertices with edges that they may be on // Merge any vertices that are on edges
resolveVertexIntersections( scannedEdges, vertex ); resolveVertexIntersections( scannedEdges, vertex );
// Remove any collinear edges attached to this vertex
resolveCollinearEdges( scannedEdges, vertex );
// Remove any intersections with edges from other vertices
for ( final HalfEdge edge : vertex ) { for ( final HalfEdge edge : vertex ) {
// Resolve any intersections belonging to this edge which are still // Resolve any intersections belonging to this edge which are still
// being scanned // being scanned, but only if it's a new edge
if ( scannedEdges.contains( edge ) ) { if ( !scannedEdges.contains( edge ) && !scannedVertices.contains( edge.getDest() ) ) {
resolveEdgeIntersections( queue, scannedEdges, edge ); resolveEdgeIntersections( vertexSet, scannedEdges, edge );
} }
} }
// Consider this vertex "scanned"
scannedVertices.add( vertex );
// Remove all edges from previously scanned vertices that are no longer useful // Remove all edges from previously scanned vertices that are no longer useful
for ( final HalfEdge edge : vertex ) { for ( final HalfEdge edge : vertex ) {
if ( vertex == edge.getDest() ) {
// TODO Remove this
throw new IllegalStateException( "Zero length edge!" );
}
if ( scannedVertices.contains( edge.getDest() ) ) { if ( scannedVertices.contains( edge.getDest() ) ) {
/* /*
* Remove the half edge and it's sym if the destination * Remove the half edge and it's sym if the destination
@@ -412,13 +431,16 @@ public class Mesh< T extends Region > {
// TODO Remove this // TODO Remove this
throw new IllegalStateException( "Tried to remove an invalid edge!" ); throw new IllegalStateException( "Tried to remove an invalid edge!" );
} }
} else {
scannedEdges.add( edge );
} }
} }
scannedVertices.add( vertex );
} }
mergeOverlappingEdges( scannedVertices ); mergeOverlappingEdges( scannedVertices );
scannedVertices.parallelStream().forEach( Mesh::sort );
// Update the list of vertices with our new updated list of vertices // Update the list of vertices with our new updated list of vertices
vertices = scannedVertices; vertices = scannedVertices;
@@ -446,7 +468,7 @@ public class Mesh< T extends Region > {
* @param scannedEdges * @param scannedEdges
* @param vertex * @param vertex
*/ */
private void resolveVertexIntersections( final EdgeSet scannedEdges, final Vertex vertex ) { private Collection< HalfEdge > resolveVertexIntersections( final EdgeSet scannedEdges, final Vertex vertex ) {
// Split any edges passing through vertex, which do not include // Split any edges passing through vertex, which do not include
// the vertex as the origin or destination // the vertex as the origin or destination
final Vector2d v = vertex.getPosition(); final Vector2d v = vertex.getPosition();
@@ -455,6 +477,8 @@ public class Mesh< T extends Region > {
final Vector2d a = edge.getOrigin().getPosition(); final Vector2d a = edge.getOrigin().getPosition();
final Vector2d b = edge.getDest().getPosition(); final Vector2d b = edge.getDest().getPosition();
// TODO Can parallelize finding the edges, then do the merging sequentially afterwards
if ( v == a || v == b ) { if ( v == a || v == b ) {
// Skip if this edge // Skip if this edge
continue; continue;
@@ -497,7 +521,67 @@ public class Mesh< T extends Region > {
} }
} }
} }
scannedEdges.addAll( toInsert );
return toInsert;
}
private void resolveCollinearEdges( final EdgeSet scannedEdges, final Vertex vertex ) {
final Collection< HalfEdge > checked = new HashSet< HalfEdge >();
for ( final HalfEdge edge : vertex ) {
if ( scannedEdges.contains( edge ) ) {
checked.add( edge );
}
}
// Assume a and b are both positive
// 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 ) {
// 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 ) {
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() ) ) {
final Vector2d s = other.toVector2d().normalize();
final double angle = r.cross( s );
// Ignore edges that are not collinear
if ( Math.abs( angle ) <= CROSS_TOLERANCE ) {
HalfEdge shorter;
HalfEdge longer;
// Compare which edge is shorter
if ( r.lengthSquared() < s.lengthSquared() ) {
shorter = edge;
longer = other;
} else {
shorter = other;
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 vertex for the split
final Vertex vert = shorter.getDest();
split.setOrigin( vert );
longer.getSym().setOrigin( vert );
// Splice the two together
HalfEdge.splice( shorter.getSym(), split );
}
}
}
}
}
}
} }
/** /**
@@ -508,150 +592,93 @@ public class Mesh< T extends Region > {
* @param edge * @param edge
*/ */
private void resolveEdgeIntersections( final Collection< Vertex > vertices, final EdgeSet scannedEdges, final HalfEdge edge ) { private void resolveEdgeIntersections( final Collection< Vertex > vertices, final EdgeSet scannedEdges, final HalfEdge edge ) {
final Vector2d p = edge.getOrigin().getPosition();
final double edgeOriginY = p.getY();
// Recalculate these if the edge is split
double edgeDestY = edge.getDest().getPosition().getY();
Vector2d r = edge.toVector2d();
double edgeUpperY = Math.max( edgeOriginY, edgeDestY );
double edgeLowerY = Math.min( edgeOriginY, edgeDestY );
// Use the positive edge for finding the intersection // Use the positive edge for finding the intersection
final HalfEdge positiveEdge = isPositive( edge ) ? edge : edge.getSym();
for ( HalfEdge other : scannedEdges ) { for ( HalfEdge other : scannedEdges ) {
if ( !isPositive( other ) ) { if ( edge == other || edge == other.getSym() ) {
other = other.getSym(); continue;
} }
final Optional< Intersection > optInt = getIntersection( positiveEdge, other );
if ( optInt.isPresent() ) {
final Intersection intersection = optInt.get();
if ( intersection instanceof IntersectionEdgeToEdge ) {
final IntersectionEdgeToEdge intEdge = ( IntersectionEdgeToEdge ) intersection;
if ( positiveEdge.getDest() == other.getDest() ) { // Do simple Y axis check between the two edges to see if they overlap
// TODO Remove this {
throw new IllegalStateException( "Invalid intersection" ); final double otherOriginY = other.getOrigin().getPosition().getY();
final double otherDestY = other.getDest().getPosition().getY();
final double otherUpperY = Math.max( otherOriginY, otherDestY );
final double otherLowerY = Math.min( otherOriginY, otherDestY );
if ( !( edgeLowerY < otherUpperY && otherLowerY < edgeUpperY ) ) {
continue;
}
}
if ( edge.getOrigin() != other.getOrigin() && edge.getOrigin() != other.getDest() && !isSimilar( edge.getDest(), other.getDest() ) ) {
// Both edges may intersect somewhere in the middle
final Vector2d q = other.getOrigin().getPosition();
final Vector2d s = other.toVector2d();
final Vector2d pq = q.subtracted( p );
final double rs = r.cross( s );
final double pqr = pq.cross( r );
// Check if parallel
if ( rs != 0 ) {
// Calculate t and u
final double t = pq.cross( s ) / rs;
final double u = pqr / rs;
final double tTol = VERTEX_TOLERANCE / r.length();
final double uTol = VERTEX_TOLERANCE / s.length();
// Do the line segments intersect
// They should NOT intersect at their endpoints
if ( t > tTol && t < ( 1 - tTol ) && u > uTol && u < ( 1 - uTol ) ) {
// Calculate the point of intersection
final Vector2d intersection = r.multiplied( t ).add( p );
// Split the first edge
HalfEdge aSplit = edge.split();
RegionRule< T > aRule = rules.get( edge );
rules.put( aSplit, aRule );
rules.put( aSplit.getSym(), aRule.inverse() );
// Update the position
final Vertex aSplitOrigin = aSplit.getOrigin();
aSplitOrigin.setPosition( intersection );
// Split the other edge
HalfEdge bSplit = other.split();
RegionRule< T > bRule = rules.get( other );
rules.put( bSplit, bRule );
rules.put( bSplit.getSym(), bRule.inverse() );
// Splice the edges together into the same vertex
HalfEdge.splice( bSplit, aSplit );
aSplitOrigin.update();
vertices.add( aSplitOrigin );
// Recalculate the edge values
edgeDestY = intersection.getY();
r = edge.toVector2d();
edgeUpperY = Math.max( edgeOriginY, edgeDestY );
edgeLowerY = Math.min( edgeOriginY, edgeDestY );
} }
final Vector2d point = intEdge.getPoint();
if ( isSimilar( point, other.getDest().getPosition() ) || isSimilar( point, positiveEdge.getDest().getPosition() ) ) {
// TODO Remove this
throw new IllegalStateException( "Invalid intersection point" );
}
// Split the first edge
HalfEdge aSplit = edge.split();
RegionRule< T > aRule = rules.get( edge );
rules.put( aSplit, aRule );
rules.put( aSplit.getSym(), aRule.inverse() );
// Update the position
final Vertex origin = aSplit.getOrigin();
origin.setPosition( point );
// Split the other edge
HalfEdge bSplit = other.split();
RegionRule< T > bRule = rules.get( other );
rules.put( bSplit, bRule );
rules.put( bSplit.getSym(), bRule.inverse() );
// Splice the edges together into the same vertex
HalfEdge.splice( bSplit, aSplit );
origin.update();
vertices.add( origin );
} else if ( intersection instanceof IntersectionCollinear ) {
final IntersectionCollinear intCol = ( IntersectionCollinear ) intersection;
final HalfEdge shorter = intCol.getShorterEdge();
HalfEdge longer = intCol.getLongerEdge();
// Use the original edge for splitting to preserve the order of
// edges around the current vertex
if ( longer == positiveEdge ) {
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 vertex for the split
final Vertex vert = shorter.getDest();
split.setOrigin( vert );
longer.getSym().setOrigin( vert );
// Splice the two together
HalfEdge.splice( shorter.getSym(), split );
} else {
// TODO Remove this
throw new IllegalStateException( "Unknown intersection type" );
} }
} }
} }
} }
/**
* Calculate the intersection for two edges
*
* @param a
* @param b
* @return
*/
private static Optional< Intersection > getIntersection( HalfEdge a, HalfEdge b ) {
// Assume a and b are both positive
// 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.
if ( a.getOrigin() == b.getOrigin() ) {
// Do not split edges that are pretty much equal, since they will get merged later
if ( !isSimilar( a.getDest(), b.getDest() ) ) {
final Vector2d r = a.toVector2d().normalize();
final Vector2d s = b.toVector2d().normalize();
final double angle = r.cross( s );
// Ignore edges that are not collinear
if ( Math.abs( angle ) <= CROSS_TOLERANCE ) {
// Compare which edge is shorter
if ( r.lengthSquared() < s.lengthSquared() ) {
return Optional.of( new IntersectionCollinear( a, b ) );
} else {
return Optional.of( new IntersectionCollinear( b, a ) );
}
}
}
} else if ( isSimilar( a.getOrigin(), b.getOrigin() ) ) {
// TODO Remove this
throw new IllegalArgumentException( "Vertex not merged!" );
} else if ( !isSimilar( a.getDest(), b.getDest() ) ) {
// Both edges may intersect somewhere in the middle
final Vector2d p = a.getOrigin().getPosition();
final Vector2d r = a.toVector2d();
final Vector2d q = b.getOrigin().getPosition();
final Vector2d s = b.toVector2d();
final Vector2d pq = q.subtracted( p );
final double pqr = pq.cross( r );
final double rs = r.cross( s );
// Check if parallel
if ( rs != 0 ) {
// Calculate t and u
final double t = pq.cross( s ) / rs;
final double u = pqr / rs;
final double tTol = VERTEX_TOLERANCE / r.length();
final double uTol = VERTEX_TOLERANCE / s.length();
// Do the line segments intersect
// They should NOT intersect at their endpoints
if ( t > tTol && t < ( 1 - tTol ) && u > uTol && u < ( 1 - uTol ) ) {
// Calculate the point of intersection
final Vector2d intersection = r.multiply( t ).add( p );
return Optional.of( new IntersectionEdgeToEdge( intersection ) );
}
}
}
return Optional.empty();
}
/** /**
* The goal for this method is to sort vertices * The goal for this method is to sort vertices
* and to ensure that edges with the same endpoint and * and to ensure that edges with the same endpoint and
@@ -659,8 +686,6 @@ public class Mesh< T extends Region > {
*/ */
private void mergeOverlappingEdges( final Collection< Vertex > vertices ) { private void mergeOverlappingEdges( final Collection< Vertex > vertices ) {
for ( final Vertex vertex : vertices ) { for ( final Vertex vertex : vertices ) {
sort( vertex );
// Keep track of which ranges of edges are attached to which // Keep track of which ranges of edges are attached to which
final Map< Vertex, Queue< HalfEdge > > ranges = new HashMap< Vertex, Queue< HalfEdge > >(); final Map< Vertex, Queue< HalfEdge > > ranges = new HashMap< Vertex, Queue< HalfEdge > >();
@@ -729,8 +754,15 @@ public class Mesh< T extends Region > {
throw new IllegalStateException( "Mesh is not simplified!" ); throw new IllegalStateException( "Mesh is not simplified!" );
} }
final Queue< Vertex > queue = new PriorityQueue< Vertex >( Mesh::compare ); final Collection< Vertex > vertexSet = new TreeSet< Vertex >( ( a, b ) -> {
queue.addAll( vertices ); final int compare = compare( a, b );
if ( compare == 0 ) {
return Integer.compare( a.hashCode(), b.hashCode() );
} else {
return compare;
}
} );
vertexSet.addAll( vertices );
// Keep track of what regions belong to what edge // Keep track of what regions belong to what edge
final Map< HalfEdge, T > regions = new HashMap< HalfEdge, T >(); final Map< HalfEdge, T > regions = new HashMap< HalfEdge, T >();
@@ -747,8 +779,7 @@ public class Mesh< T extends Region > {
final Set< HalfEdge > toRemove = new HashSet< HalfEdge >(); final Set< HalfEdge > toRemove = new HashSet< HalfEdge >();
// Assume the edges on each vertex is already sorted // Assume the edges on each vertex is already sorted
Vertex vertex; for ( Vertex vertex : vertexSet ) {
while ( ( vertex = queue.poll() ) != null ) {
// Insert all edges into the edge set // Insert all edges into the edge set
for ( final HalfEdge edge : vertex ) { for ( final HalfEdge edge : vertex ) {
if ( !isPositive( edge ) ) { if ( !isPositive( edge ) ) {
@@ -854,6 +885,9 @@ public class Mesh< T extends Region > {
vertices = keep; vertices = keep;
vertexMap.clear();
vertices.forEach( v -> vertexMap.put( v.getPosition(), v ) );
/* /*
* Invariants: * Invariants:
* - All vertices have an even number of edges * - All vertices have an even number of edges
@@ -2760,7 +2794,7 @@ public class Mesh< T extends Region > {
// Keep track of which edge to insert new edges for the given vertex // Keep track of which edge to insert new edges for the given vertex
final Map< Vertex, HalfEdge > insertEdges = new HashMap< Vertex, HalfEdge >(); final Map< Vertex, HalfEdge > insertEdges = new HashMap< Vertex, HalfEdge >();
final Queue< HalfEdge > edges = new PriorityQueue< HalfEdge >( ( a, b ) -> { final TreeSet< HalfEdge > edges = new TreeSet< HalfEdge >( ( a, b ) -> {
return compare( a.getOrigin(), b.getOrigin() ); return compare( a.getOrigin(), b.getOrigin() );
} ); } );
edges.addAll( polygon.getEdges() ); edges.addAll( polygon.getEdges() );
@@ -2768,12 +2802,12 @@ public class Mesh< T extends Region > {
final Stack< HalfEdge > stack = new Stack< HalfEdge >(); final Stack< HalfEdge > stack = new Stack< HalfEdge >();
// Add the first two vertices/edges // Add the first two vertices/edges
stack.add( edges.poll() ); stack.add( edges.pollFirst() );
stack.add( edges.poll() ); stack.add( edges.pollFirst() );
HalfEdge prev = stack.peek(); HalfEdge prev = stack.peek();
while ( edges.size() > 1 ) { while ( edges.size() > 1 ) {
final HalfEdge edge = edges.poll(); final HalfEdge edge = edges.pollFirst();
final boolean isEdgePositive = isPositive( edge ); final boolean isEdgePositive = isPositive( edge );
if ( isEdgePositive ^ isPositive( stack.peek() ) ) { if ( isEdgePositive ^ isPositive( stack.peek() ) ) {
@@ -2865,7 +2899,7 @@ public class Mesh< T extends Region > {
// The last edge must be a negative edge, since // The last edge must be a negative edge, since
// the last vertex must terminate the polygon and // the last vertex must terminate the polygon and
// therefore have no right-going edges. // therefore have no right-going edges.
final HalfEdge last = edges.poll(); final HalfEdge last = edges.pollFirst();
stack.pop(); stack.pop();
final boolean isPositive = isPositive( stack.peek() ); final boolean isPositive = isPositive( stack.peek() );
HalfEdge insertEdge = last; HalfEdge insertEdge = last;
@@ -2999,48 +3033,6 @@ public class Mesh< T extends Region > {
return v; return v;
} }
/*
* Represents an intersection
*/
private static abstract class Intersection {
}
/*
* When 2 edges both intersect somewhere in the middle
*/
private static class IntersectionEdgeToEdge extends Intersection {
private final Vector2d point;
IntersectionEdgeToEdge( Vector2d point ) {
this.point = point;
}
Vector2d getPoint() {
return point;
}
}
/*
* When 2 edges are collinear, and do not share the same endpoint
*/
private static class IntersectionCollinear extends Intersection {
private final HalfEdge shorter;
private final HalfEdge longer;
IntersectionCollinear( HalfEdge shorter, HalfEdge longer ) {
this.shorter = shorter;
this.longer = longer;
}
public HalfEdge getShorterEdge() {
return shorter;
}
public HalfEdge getLongerEdge() {
return longer;
}
}
private static class EdgePolygon { private static class EdgePolygon {
private Set< HalfEdge > edges = new HashSet< HalfEdge >(); private Set< HalfEdge > edges = new HashSet< HalfEdge >();
private Set< Vertex > vertices = new HashSet< Vertex >(); private Set< Vertex > vertices = new HashSet< Vertex >();

View File

@@ -66,6 +66,17 @@ public class Vector2d extends Point {
return add( this, o ); return add( this, o );
} }
public Vector2d absolute() {
x = Math.abs( x );
y = Math.abs( y );
return this;
}
public Vector2d absoluteOf() {
return new Vector2d( Math.abs( x ), Math.abs( y ) );
}
public Vector2d subtract( double v ) { public Vector2d subtract( double v ) {
x -= v; x -= v;
y -= v; y -= v;

View File

@@ -133,6 +133,9 @@ public class MeshingTest2 extends JPanel {
// } // }
final List< Plane > masterPlanes = new ArrayList< Plane >(); final List< Plane > masterPlanes = new ArrayList< Plane >();
System.out.println( "Adding test plane" );
masterPlanes.add( getTestPlane() );
System.out.println( "Chunk files: " + CHUNK_DIR.listFiles().length ); System.out.println( "Chunk files: " + CHUNK_DIR.listFiles().length );
int limit = 1; int limit = 1;
for ( File file : CHUNK_DIR.listFiles() ) { for ( File file : CHUNK_DIR.listFiles() ) {
@@ -144,12 +147,10 @@ public class MeshingTest2 extends JPanel {
} }
} }
// final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-1,-1" ) ); // final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-10,-10" ) );
// masterPlanes.add( planes.get( 20 ) ); // masterPlanes.add( planes.get( 27 ) );
// masterPlanes.addAll( planes ); // masterPlanes.addAll( planes );
// masterPlanes.add( getTestPlane() );
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -262,12 +263,19 @@ public class MeshingTest2 extends JPanel {
System.out.println( "After simplify vertex count: " + mesh.getVertices().size() ); System.out.println( "After simplify vertex count: " + mesh.getVertices().size() );
System.out.println( "After simplify edge count: " + ( mesh.getEdgeCount() / 2 ) ); System.out.println( "After simplify edge count: " + ( mesh.getEdgeCount() / 2 ) );
mesh.generateRegions();
long end = System.currentTimeMillis();
data.vertices = mesh.getVertices(); data.vertices = mesh.getVertices();
data.edges = mesh.getEdges(); data.edges = mesh.getEdges();
try {
mesh.generateRegions();
data.vertices = mesh.getVertices();
data.edges = mesh.getEdges();
} catch ( Exception e ) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println( "After generating regions vertex count: " + mesh.getVertices().size() ); System.out.println( "After generating regions vertex count: " + mesh.getVertices().size() );
System.out.println( "After generating regions edge count: " + ( mesh.getEdgeCount() / 2 ) ); System.out.println( "After generating regions edge count: " + ( mesh.getEdgeCount() / 2 ) );
@@ -547,6 +555,9 @@ public class MeshingTest2 extends JPanel {
private static Plane getTestPlane() { private static Plane getTestPlane() {
final Plane plane = new 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( // plane.polygons.add( new Polygon( Arrays.asList(
// new Point( 1, 0 ), // new Point( 1, 0 ),
// new Point( 2, 1 ), // new Point( 2, 1 ),
@@ -560,20 +571,75 @@ public class MeshingTest2 extends JPanel {
// new Point( 0, 3 ), // new Point( 0, 3 ),
// new Point( 1, 2 ), // new Point( 1, 2 ),
// new Point( 0, 1 ) // new Point( 0, 1 )
// plane.polygons.add( new Polygon( Arrays.asList(
// new Point( 1, 0 ),
// new Point( 2, 1 ),
// new Point( 3, 0 ),
// new Point( 4, 1 ),
// new Point( 3, 2 ),
// new Point( 4, 3 ),
// new Point( 3, 4 ),
// new Point( 2, 3 ),
// new Point( 1, 4 ),
// new Point( 0, 3 ),
// new Point( 1, 2 ),
// 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, 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( plane.polygons.add( new Polygon( Arrays.asList(
new Point( 1, 0 ), new Point( 1, 0 ),
new Point( 2, 1 ), new Point( 0, -2 ),
new Point( 3, 0 ), new Point( -1, 0 )
new Point( 4, 1 ), ) ) );
new Point( 3, 2 ),
new Point( 4, 3 ),
new Point( 3, 4 ),
new Point( 2, 3 ),
new Point( 1, 4 ),
new Point( 0, 3 ),
new Point( 1, 2 ),
new Point( 0, 1 )
) ) );
return plane; return plane;
} }

View File

@@ -48,13 +48,13 @@ public class MeshingTest3 {
System.out.println( "\tTook " + time + "ms" ); System.out.println( "\tTook " + time + "ms" );
} }
// final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-1,-1" ) ); // 123 // final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-10,-10" ) ); // 25
// for ( int i = 0; i < planes.size(); ++i ) { // for ( int i = 0; i < planes.size(); ++i ) {
// System.out.println( "Meshing plane " + i ); // System.out.println( "Meshing plane " + i );
// process( planes.get( i ) ); // process( planes.get( i ) );
// } // }
// process( planes.get( 20 ) ); // process( planes.get( 27 ) );
} else { } else {
System.err.println( "No such directory exists: " + CHUNK_DIR ); System.err.println( "No such directory exists: " + CHUNK_DIR );
} }

View File

@@ -583,7 +583,7 @@ public class MiniePlugin extends JavaPlugin {
final Location loc = block.getLocation(); final Location loc = block.getLocation();
// Start the box at 0, 0, 0 // Start the box at 0, 0, 0
final BoundingBox[] boundingBoxes = convertFrom( getShape( data, loc, BlockShapeType.VISUAL_SHAPE ), new Vector( x, y - worldMinHeight, z ) ); final BoundingBox[] boundingBoxes = convertFrom( getShape( data, loc, BlockShapeType.COLLISION_SHAPE ), new Vector( x, y - worldMinHeight, z ) );
for ( BoundingBox box : boundingBoxes ) { for ( BoundingBox box : boundingBoxes ) {
boxes.add( box ); boxes.add( box );