|
|
|
@@ -8,6 +8,7 @@ import java.util.Collections;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.Iterator;
|
|
|
|
|
import java.util.LinkedHashSet;
|
|
|
|
|
import java.util.LinkedList;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
@@ -18,6 +19,7 @@ import java.util.Queue;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
import java.util.Stack;
|
|
|
|
|
import java.util.TreeSet;
|
|
|
|
|
import java.util.function.Function;
|
|
|
|
|
import java.util.function.Supplier;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
@@ -67,6 +69,7 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.region.RegionRule;
|
|
|
|
|
* The time complexity for the entire algorithm should be roughly O(n^2logn).
|
|
|
|
|
*
|
|
|
|
|
* TODO Remove guaranteed checks or add some compile time thing to remove
|
|
|
|
|
* TODO Update this description
|
|
|
|
|
*
|
|
|
|
|
* @author BananaPuncher714
|
|
|
|
|
*/
|
|
|
|
@@ -86,8 +89,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
|
|
|
|
|
protected MeshState state = MeshState.TRIANGULATION_READY;
|
|
|
|
|
|
|
|
|
|
// TODO Remove
|
|
|
|
|
public Collection< Chain > chains;
|
|
|
|
|
protected Collection< MeshEventHandler > handlers = new LinkedHashSet< MeshEventHandler >();
|
|
|
|
|
|
|
|
|
|
public Mesh( final Supplier< T > defaultRegionSupplier ) {
|
|
|
|
|
this.defaultRegionSupplier = defaultRegionSupplier;
|
|
|
|
@@ -120,11 +122,36 @@ public class Mesh< T extends Region > {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Collection< Vertex > getVertices() {
|
|
|
|
|
return vertices;
|
|
|
|
|
public boolean addHandler( final MeshEventHandler handler ) {
|
|
|
|
|
return handlers.add( handler );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getRuleSize() {
|
|
|
|
|
public boolean removeHandler( final MeshEventHandler handler ) {
|
|
|
|
|
return handlers.remove( handler );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Collection< MeshEventHandler > getHandlers() {
|
|
|
|
|
return Collections.unmodifiableCollection( handlers );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Set< Point > getVertices() {
|
|
|
|
|
return vertices.parallelStream().map( v -> { return new Point( v.getPosition() ); } ).collect( Collectors.toSet() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Set< Segment > getEdges() {
|
|
|
|
|
final EdgeSet set = new EdgeSet();
|
|
|
|
|
set.addAll( rules.keySet() );
|
|
|
|
|
|
|
|
|
|
final Map< Vertex, Point > pointMap = vertices.parallelStream().collect( Collectors.toMap( Function.identity(), v -> new Point( v.getPosition() ) ) );
|
|
|
|
|
|
|
|
|
|
return set.parallelStream().map( e -> new Segment( pointMap.get( e.getOrigin() ), pointMap.get( e.getDest() ) ) ).collect( Collectors.toSet() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getVertexCount() {
|
|
|
|
|
return vertices.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getEdgeCount() {
|
|
|
|
|
return rules.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -764,6 +791,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
|
|
|
|
|
// Sort the edges around this vertex.
|
|
|
|
|
private static List< HalfEdge > sort( Vertex vertex ) {
|
|
|
|
|
final Vector2d UP = new Vector2d( 0, 1 );
|
|
|
|
|
final List< HalfEdge > edges = new ArrayList< HalfEdge >();
|
|
|
|
|
|
|
|
|
|
for ( final HalfEdge edge : vertex ) {
|
|
|
|
@@ -773,23 +801,22 @@ public class Mesh< T extends Region > {
|
|
|
|
|
edges.add( edge );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO Can really just figure this out based on the absolute angle?
|
|
|
|
|
// relative to the (0, 1) vector...
|
|
|
|
|
// Sort the edges in a counter clockwise direction,
|
|
|
|
|
// where 11:59 is the least, and 12:00 is the greatest
|
|
|
|
|
Collections.sort( edges, ( e1, e2 ) -> {
|
|
|
|
|
// This assumes that no edge has length of 0
|
|
|
|
|
final boolean e1p = isPositive( e1 );
|
|
|
|
|
final boolean e2p = isPositive( e2 );
|
|
|
|
|
|
|
|
|
|
if ( e1p ^ e2p ) {
|
|
|
|
|
return e1p ? 1 : -1;
|
|
|
|
|
} else if ( e1.getDest() == e2.getDest() ) {
|
|
|
|
|
return 0;
|
|
|
|
|
} else {
|
|
|
|
|
final double cross = e2.toVector2d().cross( e1.toVector2d() );
|
|
|
|
|
return Double.compare( cross, 0 );
|
|
|
|
|
final Vector2d vec1 = e1.toVector2d().normalize();
|
|
|
|
|
final Vector2d vec2 = e2.toVector2d().normalize();
|
|
|
|
|
double cross1 = vec1.angle( UP );
|
|
|
|
|
if ( cross1 < 0 ) {
|
|
|
|
|
cross1 += 10;
|
|
|
|
|
}
|
|
|
|
|
double cross2 = vec2.angle( UP );
|
|
|
|
|
if ( cross2 < 0 ) {
|
|
|
|
|
cross2 += 10;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Double.compare( cross2, cross1 );
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
// Only need to re-organize the edges if there are 3 or more
|
|
|
|
@@ -891,9 +918,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
return toRemove;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO Provide triangles
|
|
|
|
|
// and preferrably a simple polygon
|
|
|
|
|
public Collection< EdgePolygon > mesh() {
|
|
|
|
|
public Collection< Polygon > mesh() {
|
|
|
|
|
if ( state != MeshState.TRIANGULATION_READY ) {
|
|
|
|
|
throw new IllegalStateException( "Mesh has not been split into regions!" );
|
|
|
|
|
}
|
|
|
|
@@ -904,7 +929,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
final Map< Vertex, Vertex > newVertices = new HashMap< Vertex, Vertex >();
|
|
|
|
|
|
|
|
|
|
// Keep track of all newly added interior edges
|
|
|
|
|
final Collection< HalfEdge > edges = new HashSet< HalfEdge >();
|
|
|
|
|
final Collection< HalfEdge > edges = new LinkedHashSet< HalfEdge >();
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We now want to create a completely separate PSLG so that we don't modify
|
|
|
|
@@ -954,53 +979,67 @@ public class Mesh< T extends Region > {
|
|
|
|
|
// Sort each vertex so that the edge they are pointing to is the least edge
|
|
|
|
|
vertices.parallelStream().forEach( v -> sort( v ) );
|
|
|
|
|
|
|
|
|
|
final Collection< Chain > chains = reduceCollinear2( vertices, edges );
|
|
|
|
|
this.chains = new ArrayDeque< Chain >();
|
|
|
|
|
for ( Chain chain : chains ) {
|
|
|
|
|
List< HalfEdge > newEdges = new ArrayList< HalfEdge >();
|
|
|
|
|
for ( HalfEdge e : chain.links ) {
|
|
|
|
|
HalfEdge edge = new HalfEdge();
|
|
|
|
|
edge.getOrigin().setPosition( e.getOrigin().getPosition() );
|
|
|
|
|
edge.getDest().setPosition( e.getDest().getPosition() );
|
|
|
|
|
newEdges.add( edge );
|
|
|
|
|
final Collection< Link > allChains = findChains( vertices, edges );
|
|
|
|
|
final Collection< Link > chains = maximizeChains( allChains );
|
|
|
|
|
if ( !handlers.isEmpty() ) {
|
|
|
|
|
final Collection< Chain > newChains = new HashSet< Chain >();
|
|
|
|
|
final Collection< Link > seen = new HashSet< Link >();
|
|
|
|
|
|
|
|
|
|
for ( final Link link : allChains ) {
|
|
|
|
|
if ( seen.add( link ) ) {
|
|
|
|
|
Link head = link;
|
|
|
|
|
while ( head.previous != null ) {
|
|
|
|
|
head = head.previous;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Chain chain = new Chain();
|
|
|
|
|
Link previous;
|
|
|
|
|
do {
|
|
|
|
|
chain.points.add( new Point( head.getOrigin().getPosition() ) );
|
|
|
|
|
seen.add( head );
|
|
|
|
|
previous = head;
|
|
|
|
|
} while ( ( head = head.next ) != null );
|
|
|
|
|
chain.points.add( new Point( previous.getMidpoint().getPosition() ) );
|
|
|
|
|
chain.points.add( new Point( previous.getDest().getPosition() ) );
|
|
|
|
|
newChains.add( chain );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.chains.add( new Chain( newEdges ) );
|
|
|
|
|
|
|
|
|
|
handlers.forEach( h -> h.onChainGenerationEvent( newChains ) );
|
|
|
|
|
|
|
|
|
|
final Collection< Link > selectedSeen = new HashSet< Link >();
|
|
|
|
|
final Collection< Chain > selectedChains = new HashSet< Chain >();
|
|
|
|
|
for ( final Link link : chains ) {
|
|
|
|
|
if ( selectedSeen.add( link ) ) {
|
|
|
|
|
Link head = link;
|
|
|
|
|
while ( chains.contains( head.previous ) ) {
|
|
|
|
|
head = head.previous;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Chain chain = new Chain();
|
|
|
|
|
Link previous;
|
|
|
|
|
do {
|
|
|
|
|
chain.points.add( new Point( head.getOrigin().getPosition() ) );
|
|
|
|
|
selectedSeen.add( head );
|
|
|
|
|
previous = head;
|
|
|
|
|
} while ( ( head = head.next ) != null && chains.contains( head ) );
|
|
|
|
|
chain.points.add( new Point( previous.getMidpoint().getPosition() ) );
|
|
|
|
|
chain.points.add( new Point( previous.getDest().getPosition() ) );
|
|
|
|
|
selectedChains.add( chain );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handlers.forEach( h -> h.onPreChainMergeEvent( selectedChains ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mergeChains( chains, edges );
|
|
|
|
|
|
|
|
|
|
return partitionMonotone( vertices, edges ).parallelStream()
|
|
|
|
|
.map( Mesh::triangulate )
|
|
|
|
|
.flatMap( p -> p.parallelStream() )
|
|
|
|
|
.collect( Collectors.toSet() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO Make not static
|
|
|
|
|
public static class Chain {
|
|
|
|
|
List< HalfEdge > links;
|
|
|
|
|
Set< Chain > intersections = new HashSet< Chain >();
|
|
|
|
|
Chain previous;
|
|
|
|
|
Chain next;
|
|
|
|
|
|
|
|
|
|
Chain( final Collection< HalfEdge > links ) {
|
|
|
|
|
this.links = new ArrayList< HalfEdge >( links );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Vertex getOrigin() {
|
|
|
|
|
return links.get( 0 ).getOrigin();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Vertex getMidpoint() {
|
|
|
|
|
return links.get( 1 ).getOrigin();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Vertex getDest() {
|
|
|
|
|
return links.get( 2 ).getOrigin();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List< HalfEdge > getLinks() {
|
|
|
|
|
return links;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* This algorithm revolves around first generating chains, then sorting which chains and chain links to keep.
|
|
|
|
|
*
|
|
|
|
@@ -1015,7 +1054,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
*
|
|
|
|
|
* The complexity comes from analyzing which combinations of chain links provides the greatest reduction of vertices.
|
|
|
|
|
*/
|
|
|
|
|
private static Collection< Chain > reduceCollinear2( final TreeSet< Vertex > vertices, final Collection< HalfEdge > interior ) {
|
|
|
|
|
private static Collection< Link > findChains( final TreeSet< Vertex > vertices, final Collection< HalfEdge > interior ) {
|
|
|
|
|
final Vector2d CROSS = new Vector2d( 1, 0 );
|
|
|
|
|
// Set maximum and minimum cross values that won't occur naturally
|
|
|
|
|
final double MAX_CROSS = 1.1;
|
|
|
|
@@ -1092,7 +1131,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
|
|
|
|
|
// Keep track of all chains that were formed in this current partition.
|
|
|
|
|
// For all new chains, see what previous chains it may intersect with
|
|
|
|
|
Collection< Chain > chains = new HashSet< Chain >();
|
|
|
|
|
Collection< Link > chains = new LinkedHashSet< Link >();
|
|
|
|
|
|
|
|
|
|
Partition( final HalfEdge lower, final HalfEdge upper ) {
|
|
|
|
|
this.lower = lower;
|
|
|
|
@@ -1133,7 +1172,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
// and we don't really care about what comes after
|
|
|
|
|
links.add( edge );
|
|
|
|
|
|
|
|
|
|
final Chain chain = new Chain( links );
|
|
|
|
|
final Link chain = new Link( links );
|
|
|
|
|
addChain( chain );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1174,7 +1213,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
// and we don't really care about what comes after
|
|
|
|
|
links.add( edge );
|
|
|
|
|
|
|
|
|
|
final Chain chain = new Chain( links );
|
|
|
|
|
final Link chain = new Link( links );
|
|
|
|
|
addChain( chain );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1263,7 +1302,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
// and we don't really care about what comes after
|
|
|
|
|
links.add( edge );
|
|
|
|
|
|
|
|
|
|
final Chain chain = new Chain( links );
|
|
|
|
|
final Link chain = new Link( links );
|
|
|
|
|
addChain( chain );
|
|
|
|
|
}
|
|
|
|
|
} else if ( lowerCross > region.lower ) {
|
|
|
|
@@ -1302,7 +1341,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
// and we don't really care about what comes after
|
|
|
|
|
links.add( edge );
|
|
|
|
|
|
|
|
|
|
final Chain chain = new Chain( links );
|
|
|
|
|
final Link chain = new Link( links );
|
|
|
|
|
addChain( chain );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1360,30 +1399,34 @@ public class Mesh< T extends Region > {
|
|
|
|
|
* Check all previous chains for intersections and stuff. This method
|
|
|
|
|
* is unfortunately polynomial time(O(n^2)) and there's not much we can do about it.
|
|
|
|
|
*
|
|
|
|
|
* Maybe parallelize the intersection checking though? We'd need to ensure that the
|
|
|
|
|
* intersection collection for each chain is synchronized, but otherwise entirely possible.
|
|
|
|
|
*
|
|
|
|
|
* TODO Good idea, now someone needs to implement that.
|
|
|
|
|
*
|
|
|
|
|
* Also, just because the origin or destination may be the midpoint of another chain
|
|
|
|
|
* does not necessarily mean they coincide with each other... such as if the origin
|
|
|
|
|
* or endpoint are connected to the midpoint for one chain, but not the other
|
|
|
|
|
*/
|
|
|
|
|
void addChain( final Chain chain ) {
|
|
|
|
|
void addChain( final Link chain ) {
|
|
|
|
|
final Vertex chainOrigin = chain.getOrigin();
|
|
|
|
|
final Vertex chainMidpoint = chain.getMidpoint();
|
|
|
|
|
final Vertex chainDestination = chain.getDest();
|
|
|
|
|
|
|
|
|
|
for ( final Chain other : chains ) {
|
|
|
|
|
for ( final Link other : chains ) {
|
|
|
|
|
if ( other.getOrigin() == chainOrigin && other.getDest() == chainDestination ) {
|
|
|
|
|
// Already added this chain, so break out early
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Synchronize the intersections for this chain so that we can check each
|
|
|
|
|
// other chain for intersection in parallel. The order of the intersections
|
|
|
|
|
// doesn't affect the reproducibility either, so it's fine.
|
|
|
|
|
final Collection< Link > intersections = Collections.synchronizedCollection( chain.intersections );
|
|
|
|
|
|
|
|
|
|
final HalfEdge firstEdge = chain.links.get( 0 );
|
|
|
|
|
final HalfEdge secondEdge = chain.links.get( 1 );
|
|
|
|
|
final Vector2d secondEdgeVec = secondEdge.toVector2d();
|
|
|
|
|
|
|
|
|
|
final Vector2d p = chainOrigin.getPosition();
|
|
|
|
|
final Vector2d r = chainDestination.getPosition().subtracted( p );
|
|
|
|
|
for ( final Chain other : chains ) {
|
|
|
|
|
chains.parallelStream().forEach( other -> {
|
|
|
|
|
if ( other.getMidpoint() == chainOrigin && other.getDest() == chainMidpoint ) {
|
|
|
|
|
// The new chain is a continuation of this chain
|
|
|
|
|
other.next = chain;
|
|
|
|
@@ -1391,8 +1434,56 @@ public class Mesh< T extends Region > {
|
|
|
|
|
} else if ( compare( other.getDest(), chainOrigin ) > 0 ) {
|
|
|
|
|
// Do the chains even overlap?
|
|
|
|
|
if ( chainOrigin == other.getMidpoint() ) {
|
|
|
|
|
chain.intersections.add( other );
|
|
|
|
|
other.intersections.add( chain );
|
|
|
|
|
// In the case that the new chain's origin is another chain's midpoint,
|
|
|
|
|
// we need to check which direction the other chain is facing.
|
|
|
|
|
// This is because the new chain only counts as intersecting with the other
|
|
|
|
|
// chain if and only if the creation of this chain would prevent the other
|
|
|
|
|
// chain from being linked correctly.
|
|
|
|
|
|
|
|
|
|
final Vector2d otherDirection = other.getDest().getPosition().subtracted( other.getOrigin().getPosition() );
|
|
|
|
|
final double cross = otherDirection.cross( r );
|
|
|
|
|
|
|
|
|
|
boolean intersects = true;
|
|
|
|
|
final HalfEdge firstLink = other.links.get( 0 );
|
|
|
|
|
final HalfEdge secondLink = other.links.get( 1 );
|
|
|
|
|
if ( firstLink.getDest() == other.getMidpoint() ) {
|
|
|
|
|
// Is the first link a direct connection?
|
|
|
|
|
intersects = interior.contains( firstLink ) ^ cross < 0;
|
|
|
|
|
} else if ( secondLink.getDest() == other.getDest() ) {
|
|
|
|
|
// Is the second link a direct connection?
|
|
|
|
|
intersects = interior.contains( secondLink ) ^ cross < 0;
|
|
|
|
|
} else {
|
|
|
|
|
// The chain is comprised of only vertices...
|
|
|
|
|
intersects = otherDirection.cross( secondLink.toVector2d() ) < 0 ^ cross < 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( intersects ) {
|
|
|
|
|
synchronized ( intersections ) {
|
|
|
|
|
intersections.add( other );
|
|
|
|
|
}
|
|
|
|
|
other.intersections.add( chain );
|
|
|
|
|
}
|
|
|
|
|
} else if ( other.getDest() == chainMidpoint ) {
|
|
|
|
|
// Likewise, the new chain's midpoint may be another existing chain's
|
|
|
|
|
// destination. We need to check for that case too.
|
|
|
|
|
final Vector2d otherDirection = other.getOrigin().getPosition().subtracted( other.getDest().getPosition() ).normalize();
|
|
|
|
|
final double cross = r.cross( otherDirection );
|
|
|
|
|
|
|
|
|
|
boolean intersects = true;
|
|
|
|
|
if ( firstEdge.getDest() == chainMidpoint ) {
|
|
|
|
|
intersects = interior.contains( firstEdge ) ^ cross < 0;
|
|
|
|
|
} else if ( secondEdge.getDest() == chainDestination ) {
|
|
|
|
|
intersects = interior.contains( secondEdge ) ^ cross < 0;
|
|
|
|
|
} else {
|
|
|
|
|
intersects = r.cross( secondEdgeVec ) < 0 ^ cross < 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( intersects ) {
|
|
|
|
|
synchronized ( intersections ) {
|
|
|
|
|
intersections.add( other );
|
|
|
|
|
}
|
|
|
|
|
other.intersections.add( chain );
|
|
|
|
|
}
|
|
|
|
|
} else if ( other.getOrigin() != chainOrigin && other.getDest() != chainDestination ) {
|
|
|
|
|
// Make sure the chains don't share the same origin or destination
|
|
|
|
|
|
|
|
|
@@ -1411,20 +1502,23 @@ public class Mesh< T extends Region > {
|
|
|
|
|
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
|
|
|
|
|
// Allow for overlap
|
|
|
|
|
if ( t >= 0 && t <= 1 && u > -uTol && u < ( 1 + uTol ) ) {
|
|
|
|
|
if ( t > -tTol && t < ( 1 + tTol ) && u > -uTol && u < ( 1 + uTol ) ) {
|
|
|
|
|
// The chains intersect! We don't care about where.
|
|
|
|
|
|
|
|
|
|
chain.intersections.add( other );
|
|
|
|
|
synchronized ( intersections ) {
|
|
|
|
|
intersections.add( other );
|
|
|
|
|
}
|
|
|
|
|
other.intersections.add( chain );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} );
|
|
|
|
|
chains.add( chain );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1487,7 +1581,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
final Map< HalfEdge, Partition > lowerEdgeMap = new HashMap< HalfEdge, Partition >();
|
|
|
|
|
|
|
|
|
|
// Keep track of all completed chain links that we have
|
|
|
|
|
final Collection< Chain > chains = new HashSet< Chain >();
|
|
|
|
|
final Collection< Link > chains = new LinkedHashSet< Link >();
|
|
|
|
|
|
|
|
|
|
// Keep track of edges from bottom top
|
|
|
|
|
final SortedEdgeCollection< HalfEdge > edgeCollection = new SortedEdgeCollection< HalfEdge >( Mesh::greaterThanOrEqualTo );
|
|
|
|
@@ -1620,30 +1714,21 @@ public class Mesh< T extends Region > {
|
|
|
|
|
// Create a new visibility region
|
|
|
|
|
final VisibilityRegion copyRegion = new VisibilityRegion( region.lowerEdge, region.upperEdge );
|
|
|
|
|
|
|
|
|
|
// Update the new region's lower links
|
|
|
|
|
if ( region.lower != MIN_CROSS ) {
|
|
|
|
|
copyRegion.lower = region.lower;
|
|
|
|
|
copyRegion.links.put( region.lower, region.links.remove( region.lower ) );
|
|
|
|
|
|
|
|
|
|
// Obviously, whenever we remove a region from the links, we need to check
|
|
|
|
|
// if the region even has any more links... otherwise it's clearly dead and
|
|
|
|
|
// needs to be removed entirely.
|
|
|
|
|
if ( region.links.isEmpty() ) {
|
|
|
|
|
regionIt.remove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
region.lower = MIN_CROSS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Inherit the upper region's links
|
|
|
|
|
// This may cause duplication of chains, but
|
|
|
|
|
// we should be able to check them when we add them
|
|
|
|
|
// TODO Can this be simplified?
|
|
|
|
|
// As in, can we prove that this chain will connect, while
|
|
|
|
|
// the upper region's chain will not, or vice versa?
|
|
|
|
|
|
|
|
|
|
// Copy the old region's lower and upper links
|
|
|
|
|
if ( region.lower != MIN_CROSS ) {
|
|
|
|
|
copyRegion.lower = region.lower;
|
|
|
|
|
copyRegion.links.put( region.lower, region.links.get( region.lower ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Inherit the upper region's links
|
|
|
|
|
if ( region.upper != MAX_CROSS ) {
|
|
|
|
|
copyRegion.upper = region.upper;
|
|
|
|
|
|
|
|
|
|
copyRegion.links.put( region.upper, region.links.get( region.upper ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1822,19 +1907,17 @@ public class Mesh< T extends Region > {
|
|
|
|
|
throw new IllegalStateException( "Dangling partitions!" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Given a chain link, select the links which intersect the least, and remove any that it intersects with from the
|
|
|
|
|
// queue. Essentially, a greedy method, which is more or less guaranteed optimal results.
|
|
|
|
|
// This is also unfortunately an O(n^2) time algorithm that can't be simplified.
|
|
|
|
|
|
|
|
|
|
// Keep track of all chains that we want
|
|
|
|
|
final Collection< Chain > selectedChains = new HashSet< Chain >();
|
|
|
|
|
// Keep track of all remaining chains that needs to be scanned
|
|
|
|
|
final Collection< Chain > remainingChains = new HashSet< Chain >( chains );
|
|
|
|
|
return chains;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Collection< Link > maximizeChains( final Collection< Link > chains ) {
|
|
|
|
|
final Collection< Link > remainingChains = new LinkedHashSet< Link >( chains );
|
|
|
|
|
final Collection< Link > selectedChains = new HashSet< Link >();
|
|
|
|
|
|
|
|
|
|
// Do a quick scan and remove any chains that have 0 intersections, since we can get
|
|
|
|
|
// this done in linear time and all at once
|
|
|
|
|
for ( final Iterator< Chain > it = remainingChains.iterator(); it.hasNext(); ) {
|
|
|
|
|
final Chain chain = it.next();
|
|
|
|
|
for ( final Iterator< Link > it = remainingChains.iterator(); it.hasNext(); ) {
|
|
|
|
|
final Link chain = it.next();
|
|
|
|
|
|
|
|
|
|
if ( chain.intersections.size() == 0 ) {
|
|
|
|
|
selectedChains.add( chain );
|
|
|
|
@@ -1845,7 +1928,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
// Chain links
|
|
|
|
|
while ( !remainingChains.isEmpty() ) {
|
|
|
|
|
// Find the chain with the least amount of intersections
|
|
|
|
|
final Chain least = remainingChains.parallelStream().min( ( c1, c2 ) -> {
|
|
|
|
|
final Link least = remainingChains.parallelStream().min( ( c1, c2 ) -> {
|
|
|
|
|
return Integer.compare( c1.intersections.size(), c2.intersections.size() );
|
|
|
|
|
} ).get();
|
|
|
|
|
|
|
|
|
@@ -1861,14 +1944,17 @@ public class Mesh< T extends Region > {
|
|
|
|
|
selectedChains.add( least );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Collection< Chain > cs = new HashSet< Chain >( selectedChains );
|
|
|
|
|
|
|
|
|
|
while ( !selectedChains.isEmpty() ) {
|
|
|
|
|
return selectedChains;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void mergeChains( final Collection< Link > selected, final Collection< HalfEdge > interior ) {
|
|
|
|
|
final Collection< Link > chains = new HashSet< Link >( selected );
|
|
|
|
|
while ( !chains.isEmpty() ) {
|
|
|
|
|
// Get a chain
|
|
|
|
|
Chain chain = selectedChains.iterator().next();
|
|
|
|
|
Link chain = chains.iterator().next();
|
|
|
|
|
|
|
|
|
|
// Get the head of the chain
|
|
|
|
|
while ( selectedChains.contains( chain.previous ) ) {
|
|
|
|
|
while ( chains.contains( chain.previous ) ) {
|
|
|
|
|
chain = chain.previous;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1893,9 +1979,9 @@ public class Mesh< T extends Region > {
|
|
|
|
|
interior.add( right.getSym() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Chain previousChain;
|
|
|
|
|
Link previousChain;
|
|
|
|
|
do {
|
|
|
|
|
selectedChains.remove( chain );
|
|
|
|
|
chains.remove( chain );
|
|
|
|
|
|
|
|
|
|
final HalfEdge head = chain.links.get( 0 );
|
|
|
|
|
final Vertex origin = chain.getOrigin();
|
|
|
|
@@ -2045,7 +2131,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
previousChain = chain;
|
|
|
|
|
} while ( ( chain = chain.next ) != null && selectedChains.contains( chain ) );
|
|
|
|
|
} while ( ( chain = chain.next ) != null && chains.contains( chain ) );
|
|
|
|
|
|
|
|
|
|
// Terminate the chain
|
|
|
|
|
final Vertex destination = previousChain.getDest();
|
|
|
|
@@ -2087,10 +2173,8 @@ public class Mesh< T extends Region > {
|
|
|
|
|
left.getSym().setOrigin( destination );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Use the vertex's edge as the hint, basically because we have no better edge to start from
|
|
|
|
|
private static HalfEdge findPrevious( final Vector2d vector, final Vertex vertex ) {
|
|
|
|
|
return findPrevious( vector, vertex, vertex.getEdge() );
|
|
|
|
@@ -2111,6 +2195,8 @@ public class Mesh< T extends Region > {
|
|
|
|
|
|
|
|
|
|
final Vector2d normalized = vector.normalized();
|
|
|
|
|
|
|
|
|
|
int size = vertex.size();
|
|
|
|
|
|
|
|
|
|
// This is a fairly simple use case, so compare absolute angles
|
|
|
|
|
HalfEdge edge = hint;
|
|
|
|
|
while ( true ) {
|
|
|
|
@@ -2127,6 +2213,10 @@ public class Mesh< T extends Region > {
|
|
|
|
|
lowest = angle;
|
|
|
|
|
}
|
|
|
|
|
edge = edge.getPrev();
|
|
|
|
|
|
|
|
|
|
if ( --size < 0 ) {
|
|
|
|
|
throw new IllegalStateException( "Not good vertex! With only " + vertex.size() );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -2445,7 +2535,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
return newPolygons;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Collection< EdgePolygon > triangulate( final EdgePolygon polygon ) {
|
|
|
|
|
private static Collection< Polygon > triangulate( final EdgePolygon polygon ) {
|
|
|
|
|
// Go down each monotone chain and connect the vertices where possible.
|
|
|
|
|
// Implements the O(n) triangulation of a polygon as described in
|
|
|
|
|
// Computation Geometry Algorithms and Applications 3rd Ed.
|
|
|
|
@@ -2454,7 +2544,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
if ( polygon.getEdges().size() < 3 || polygon.getVertices().size() < 3 ) {
|
|
|
|
|
throw new IllegalStateException( "Invalid amount of verts/edges!" );
|
|
|
|
|
} else if ( polygon.getEdges().size() == 3 ) {
|
|
|
|
|
return Arrays.asList( polygon );
|
|
|
|
|
return Arrays.asList( new Polygon( polygon.getVertices().stream().map( v -> new Point( v.getPosition() ) ).toList() ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Queue< HalfEdge > edges = new PriorityQueue< HalfEdge >( ( a, b ) -> {
|
|
|
|
@@ -2552,23 +2642,22 @@ public class Mesh< T extends Region > {
|
|
|
|
|
// Lazy solution, just sort the vertices
|
|
|
|
|
// Eventually we will remove this
|
|
|
|
|
toSort.parallelStream().forEach( v -> sort( v ) );
|
|
|
|
|
|
|
|
|
|
// TODO Convert to triangles or something?
|
|
|
|
|
final Collection< EdgePolygon > polygons = new ArrayDeque< EdgePolygon >();
|
|
|
|
|
|
|
|
|
|
final Collection< Polygon > polygons = new ArrayDeque< Polygon >();
|
|
|
|
|
final Set< HalfEdge > scanned = new HashSet< HalfEdge >();
|
|
|
|
|
for ( final HalfEdge edge : polygon.getEdges() ) {
|
|
|
|
|
if ( scanned.add( edge ) ) {
|
|
|
|
|
HalfEdge temp = edge;
|
|
|
|
|
EdgePolygon poly = new EdgePolygon();
|
|
|
|
|
|
|
|
|
|
final List< Point > points = new ArrayList< Point >();
|
|
|
|
|
do {
|
|
|
|
|
poly.addEdge( temp );
|
|
|
|
|
points.add( new Point( temp.getOrigin().getPosition() ) );
|
|
|
|
|
} while ( scanned.add( temp = temp.getNext() ) );
|
|
|
|
|
|
|
|
|
|
if ( poly.getVertices().size() != 3 ) {
|
|
|
|
|
throw new IllegalStateException( "Not a triangle! " + poly.getVertices().size() );
|
|
|
|
|
if ( points.size() != 3 ) {
|
|
|
|
|
throw new IllegalStateException( "Not a triangle: " + points.size() );
|
|
|
|
|
}
|
|
|
|
|
polygons.add( poly );
|
|
|
|
|
polygons.add( new Polygon( points ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -2702,7 +2791,7 @@ public class Mesh< T extends Region > {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class EdgePolygon {
|
|
|
|
|
private static class EdgePolygon {
|
|
|
|
|
private Set< HalfEdge > edges = new HashSet< HalfEdge >();
|
|
|
|
|
private Set< Vertex > vertices = new HashSet< Vertex >();
|
|
|
|
|
|
|
|
|
@@ -2720,6 +2809,36 @@ public class Mesh< T extends Region > {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class Link {
|
|
|
|
|
List< HalfEdge > links;
|
|
|
|
|
Set< Link > intersections = new LinkedHashSet< Link >();
|
|
|
|
|
Link previous;
|
|
|
|
|
Link next;
|
|
|
|
|
|
|
|
|
|
Link( final Collection< HalfEdge > links ) {
|
|
|
|
|
this.links = new ArrayList< HalfEdge >( links );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vertex getOrigin() {
|
|
|
|
|
return links.get( 0 ).getOrigin();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vertex getMidpoint() {
|
|
|
|
|
return links.get( 1 ).getOrigin();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vertex getDest() {
|
|
|
|
|
return links.get( 2 ).getOrigin();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static interface MeshEventHandler {
|
|
|
|
|
default void onChainGenerationEvent( final Collection< Chain > chains ) {};
|
|
|
|
|
default void onPreChainMergeEvent( final Collection< Chain > chains ) {};
|
|
|
|
|
default void onPartitionEvent( final Collection< Polygon > polygons ) {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected enum MeshState {
|
|
|
|
|
DIRTY,
|
|
|
|
|
SIMPLIFIED,
|
|
|
|
|