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:
@@ -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 > {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Vertex vertex = vertexMap.get( point );
|
||||||
|
if ( vertex != null ) {
|
||||||
|
HalfEdge.splice( edge.getPrev(), vertex.getEdge() );
|
||||||
|
} else {
|
||||||
edge.getOrigin().setPosition( new Vector2d( point ) );
|
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,23 +320,21 @@ 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
|
// We must poll and add back later since priority queues are not
|
||||||
// sorted, so an iterator over its elements would not guarantee
|
// sorted, so an iterator over its elements would not guarantee
|
||||||
// the correct ordering unless we poll it.
|
// the correct ordering unless we poll it.
|
||||||
while ( ( other = queue.poll() ) != null ) {
|
for ( final Iterator< Vertex > it = vertexSet.iterator(); it.hasNext(); ) {
|
||||||
|
final Vertex other = it.next();
|
||||||
final Vector2d otherPos = other.getPosition();
|
final Vector2d otherPos = other.getPosition();
|
||||||
|
|
||||||
// Once the vertex scanned is past the tolerance in the X direction,
|
// Once the vertex scanned is past the tolerance in the X direction,
|
||||||
// we know there are no more vertices which can be merged with the
|
// we know there are no more vertices which can be merged with the
|
||||||
// current vertex.
|
// current vertex.
|
||||||
if ( compareX( otherPos, pos ) > VERTEX_TOLERANCE ) {
|
if ( compareX( otherPos, pos ) > VERTEX_TOLERANCE ) {
|
||||||
addBack.add( other );
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,12 +342,10 @@ public class Mesh< T extends Region > {
|
|||||||
// be considered "the same"
|
// be considered "the same"
|
||||||
if ( isSimilar( pos, otherPos ) ) {
|
if ( isSimilar( pos, otherPos ) ) {
|
||||||
HalfEdge.splice( vertex.getEdge(), other.getEdge() );
|
HalfEdge.splice( vertex.getEdge(), other.getEdge() );
|
||||||
} else {
|
|
||||||
addBack.add( other );
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queue.addAll( addBack );
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// Remove any zero-edges
|
// Remove any zero-edges
|
||||||
@@ -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,71 +521,45 @@ public class Mesh< T extends Region > {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scannedEdges.addAll( toInsert );
|
|
||||||
|
return toInsert;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void resolveCollinearEdges( final EdgeSet scannedEdges, final Vertex vertex ) {
|
||||||
* Given a set of edges within the scan region, check for
|
final Collection< HalfEdge > checked = new HashSet< HalfEdge >();
|
||||||
* intersections with a half edge.
|
for ( final HalfEdge edge : vertex ) {
|
||||||
*
|
if ( scannedEdges.contains( edge ) ) {
|
||||||
* @param scannedEdges
|
checked.add( edge );
|
||||||
* @param edge
|
|
||||||
*/
|
|
||||||
private void resolveEdgeIntersections( final Collection< Vertex > vertices, final EdgeSet scannedEdges, final HalfEdge edge ) {
|
|
||||||
// Use the positive edge for finding the intersection
|
|
||||||
final HalfEdge positiveEdge = isPositive( edge ) ? edge : edge.getSym();
|
|
||||||
for ( HalfEdge other : scannedEdges ) {
|
|
||||||
if ( !isPositive( other ) ) {
|
|
||||||
other = other.getSym();
|
|
||||||
}
|
}
|
||||||
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() ) {
|
|
||||||
// TODO Remove this
|
|
||||||
throw new IllegalStateException( "Invalid intersection" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final Vector2d point = intEdge.getPoint();
|
// Assume a and b are both positive
|
||||||
if ( isSimilar( point, other.getDest().getPosition() ) || isSimilar( point, positiveEdge.getDest().getPosition() ) ) {
|
// This assumes that if a and b are collinear, then they _must_ share
|
||||||
// TODO Remove this
|
// the same origin, since we are traversing through the vertices
|
||||||
throw new IllegalStateException( "Invalid intersection point" );
|
// 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 );
|
||||||
|
|
||||||
// Split the first edge
|
// Ignore edges that are not collinear
|
||||||
HalfEdge aSplit = edge.split();
|
if ( Math.abs( angle ) <= CROSS_TOLERANCE ) {
|
||||||
RegionRule< T > aRule = rules.get( edge );
|
|
||||||
rules.put( aSplit, aRule );
|
|
||||||
rules.put( aSplit.getSym(), aRule.inverse() );
|
|
||||||
|
|
||||||
// Update the position
|
HalfEdge shorter;
|
||||||
final Vertex origin = aSplit.getOrigin();
|
HalfEdge longer;
|
||||||
origin.setPosition( point );
|
|
||||||
|
|
||||||
// Split the other edge
|
// Compare which edge is shorter
|
||||||
HalfEdge bSplit = other.split();
|
if ( r.lengthSquared() < s.lengthSquared() ) {
|
||||||
RegionRule< T > bRule = rules.get( other );
|
shorter = edge;
|
||||||
rules.put( bSplit, bRule );
|
longer = other;
|
||||||
rules.put( bSplit.getSym(), bRule.inverse() );
|
} else {
|
||||||
|
shorter = other;
|
||||||
// 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;
|
longer = edge;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,57 +576,59 @@ public class Mesh< T extends Region > {
|
|||||||
|
|
||||||
// Splice the two together
|
// Splice the two together
|
||||||
HalfEdge.splice( shorter.getSym(), split );
|
HalfEdge.splice( shorter.getSym(), split );
|
||||||
} else {
|
}
|
||||||
// TODO Remove this
|
}
|
||||||
throw new IllegalStateException( "Unknown intersection type" );
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the intersection for two edges
|
* Given a set of edges within the scan region, check for
|
||||||
|
* intersections with a half edge.
|
||||||
*
|
*
|
||||||
* @param a
|
* @param scannedEdges
|
||||||
* @param b
|
* @param edge
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
private static Optional< Intersection > getIntersection( HalfEdge a, HalfEdge b ) {
|
private void resolveEdgeIntersections( final Collection< Vertex > vertices, final EdgeSet scannedEdges, final HalfEdge edge ) {
|
||||||
// Assume a and b are both positive
|
final Vector2d p = edge.getOrigin().getPosition();
|
||||||
// This assumes that if a and b are collinear, then they _must_ share
|
final double edgeOriginY = p.getY();
|
||||||
// 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
|
// Recalculate these if the edge is split
|
||||||
if ( Math.abs( angle ) <= CROSS_TOLERANCE ) {
|
double edgeDestY = edge.getDest().getPosition().getY();
|
||||||
|
Vector2d r = edge.toVector2d();
|
||||||
|
|
||||||
// Compare which edge is shorter
|
double edgeUpperY = Math.max( edgeOriginY, edgeDestY );
|
||||||
if ( r.lengthSquared() < s.lengthSquared() ) {
|
double edgeLowerY = Math.min( edgeOriginY, edgeDestY );
|
||||||
return Optional.of( new IntersectionCollinear( a, b ) );
|
|
||||||
} else {
|
// Use the positive edge for finding the intersection
|
||||||
return Optional.of( new IntersectionCollinear( b, a ) );
|
for ( HalfEdge other : scannedEdges ) {
|
||||||
|
if ( edge == other || edge == other.getSym() ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do simple Y axis check between the two edges to see if they overlap
|
||||||
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if ( isSimilar( a.getOrigin(), b.getOrigin() ) ) {
|
if ( edge.getOrigin() != other.getOrigin() && edge.getOrigin() != other.getDest() && !isSimilar( edge.getDest(), other.getDest() ) ) {
|
||||||
// TODO Remove this
|
|
||||||
throw new IllegalArgumentException( "Vertex not merged!" );
|
|
||||||
} else if ( !isSimilar( a.getDest(), b.getDest() ) ) {
|
|
||||||
// Both edges may intersect somewhere in the middle
|
// Both edges may intersect somewhere in the middle
|
||||||
final Vector2d p = a.getOrigin().getPosition();
|
final Vector2d q = other.getOrigin().getPosition();
|
||||||
final Vector2d r = a.toVector2d();
|
final Vector2d s = other.toVector2d();
|
||||||
final Vector2d q = b.getOrigin().getPosition();
|
|
||||||
final Vector2d s = b.toVector2d();
|
|
||||||
|
|
||||||
final Vector2d pq = q.subtracted( p );
|
final Vector2d pq = q.subtracted( p );
|
||||||
final double pqr = pq.cross( r );
|
|
||||||
final double rs = r.cross( s );
|
final double rs = r.cross( s );
|
||||||
|
final double pqr = pq.cross( r );
|
||||||
|
|
||||||
// Check if parallel
|
// Check if parallel
|
||||||
if ( rs != 0 ) {
|
if ( rs != 0 ) {
|
||||||
@@ -643,13 +643,40 @@ public class Mesh< T extends Region > {
|
|||||||
// They should NOT intersect at their endpoints
|
// They should NOT intersect at their endpoints
|
||||||
if ( t > tTol && t < ( 1 - tTol ) && u > uTol && u < ( 1 - uTol ) ) {
|
if ( t > tTol && t < ( 1 - tTol ) && u > uTol && u < ( 1 - uTol ) ) {
|
||||||
// Calculate the point of intersection
|
// Calculate the point of intersection
|
||||||
final Vector2d intersection = r.multiply( t ).add( p );
|
final Vector2d intersection = r.multiplied( t ).add( p );
|
||||||
|
|
||||||
return Optional.of( new IntersectionEdgeToEdge( intersection ) );
|
// 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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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 >();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,11 +263,18 @@ 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 ) );
|
||||||
|
|
||||||
|
data.vertices = mesh.getVertices();
|
||||||
|
data.edges = mesh.getEdges();
|
||||||
|
|
||||||
|
try {
|
||||||
mesh.generateRegions();
|
mesh.generateRegions();
|
||||||
long end = System.currentTimeMillis();
|
|
||||||
|
|
||||||
data.vertices = mesh.getVertices();
|
data.vertices = mesh.getVertices();
|
||||||
data.edges = mesh.getEdges();
|
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,19 +571,74 @@ 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;
|
||||||
|
|||||||
@@ -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 );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 );
|
||||||
|
|||||||
Reference in New Issue
Block a user