Update comments
This commit is contained in:
@@ -4,6 +4,10 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A chain represents a series of points which can be connected to reduce
|
||||||
|
* the amount of vertices overall within a given mesh.
|
||||||
|
*/
|
||||||
public class Chain {
|
public class Chain {
|
||||||
protected List< Point > points = new ArrayList< Point >();
|
protected List< Point > points = new ArrayList< Point >();
|
||||||
protected List< Chain > intersections = new ArrayList< Chain >();
|
protected List< Chain > intersections = new ArrayList< Chain >();
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.region.RegionRule;
|
|||||||
* The entire algorithm from polygons to triangles can be summarized as follows:
|
* The entire algorithm from polygons to triangles can be summarized as follows:
|
||||||
* 1. Simplify the polygons into one massive DCEL/PSLG. This includes:
|
* 1. Simplify the polygons into one massive DCEL/PSLG. This includes:
|
||||||
* - Merging vertices that are within VERTEX_TOLERANCE of each other
|
* - Merging vertices that are within VERTEX_TOLERANCE of each other
|
||||||
|
* - Shifting vertices that are within VERTEX_TOLERANCE horizontally
|
||||||
* - Merging vertices on edges that are at most VERTEX_TOLERANCE away
|
* - Merging vertices on edges that are at most VERTEX_TOLERANCE away
|
||||||
* - Merging collinear edges
|
* - Merging collinear edges
|
||||||
* - Creating new vertices where two or more edges intersect
|
* - Creating new vertices where two or more edges intersect
|
||||||
@@ -54,22 +55,24 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.region.RegionRule;
|
|||||||
* This step in the algorithm is destructive in the sense that it removes edges, but it
|
* This step in the algorithm is destructive in the sense that it removes edges, but it
|
||||||
* does not move any vertices. This step also removes merges collinear edges where possible.
|
* does not move any vertices. This step also removes merges collinear edges where possible.
|
||||||
*
|
*
|
||||||
* 3. Meshing and triangulating the resulting polygons. Firstly, a copy of the PSLG is generated
|
* 3. Meshing and triangulating the resulting polygons. Firstly, the mesh is duplicated so that
|
||||||
* since this step adds edges and vertices to the PSLG, which may be undesirable for reusability.
|
* the original mesh can be reused for future modifications, since this step adds edges and
|
||||||
* Then, we remove collinear edges which may have been generated as a result of the monotone partitioning.
|
* vertices to the PSLG, which may be undesirable for reusability. Then, if the user decides to,
|
||||||
|
* (which is enabled by default), chain links can be calculated and merged to optimally minimize
|
||||||
|
* the total amount of triangles used to triangluate this mesh.
|
||||||
|
*
|
||||||
* Following a general O(nlogn) algorithm, the PSLG is split into polygons strictly monotone with
|
* Following a general O(nlogn) algorithm, the PSLG is split into polygons strictly monotone with
|
||||||
* respect to Y. The PSLG is not guaranteed to be comprised of simple polygons, and may contain
|
* respect to Y. The PSLG is not guaranteed to be comprised of simple polygons, and may contain
|
||||||
* holes.
|
* holes.
|
||||||
*
|
*
|
||||||
* Once complete, the simple polygons are separated into their own PSLG(that is, they do not share
|
* Once complete, the simple polygons are separated into their own PSLG(that is, they do not share
|
||||||
* any vertices or edge POJOs), and can be triangulated in parallel. The triangulation method used
|
* any vertices or edge objects), and can be triangulated in parallel. The triangulation method used
|
||||||
* is the algorithm described in Computation Geometry Algorithms and Applications 3rd Ed., which
|
* is the algorithm described in Computation Geometry Algorithms and Applications 3rd Ed., which
|
||||||
* is a linear-time algorithm.
|
* is a linear-time algorithm.
|
||||||
*
|
*
|
||||||
* The time complexity for the entire algorithm should be roughly O(n^2logn).
|
* 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 Remove guaranteed checks or add some compile time thing to remove
|
||||||
* TODO Update this description
|
|
||||||
*
|
*
|
||||||
* @author BananaPuncher714
|
* @author BananaPuncher714
|
||||||
*/
|
*/
|
||||||
@@ -91,10 +94,18 @@ public class Mesh< T extends Region > {
|
|||||||
|
|
||||||
protected Collection< MeshEventHandler > handlers = new LinkedHashSet< MeshEventHandler >();
|
protected Collection< MeshEventHandler > handlers = new LinkedHashSet< MeshEventHandler >();
|
||||||
|
|
||||||
|
protected boolean mergeChains = true;
|
||||||
|
|
||||||
public Mesh( final Supplier< T > defaultRegionSupplier ) {
|
public Mesh( final Supplier< T > defaultRegionSupplier ) {
|
||||||
this.defaultRegionSupplier = defaultRegionSupplier;
|
this.defaultRegionSupplier = defaultRegionSupplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a polygon with a rule for the edges of the polygon to this mesh.
|
||||||
|
*
|
||||||
|
* @param poly
|
||||||
|
* @param rule
|
||||||
|
*/
|
||||||
public void addPolygon( final Polygon poly, final RegionRule< T > rule ) {
|
public void addPolygon( final Polygon poly, final RegionRule< T > rule ) {
|
||||||
// Is it at least a triangle?
|
// Is it at least a triangle?
|
||||||
if ( poly.getPoints().size() < 3 ) {
|
if ( poly.getPoints().size() < 3 ) {
|
||||||
@@ -135,7 +146,7 @@ public class Mesh< T extends Region > {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Set< Point > getVertices() {
|
public Set< Point > getVertices() {
|
||||||
return vertices.parallelStream().map( v -> { return new Point( v.getPosition() ); } ).collect( Collectors.toSet() );
|
return vertices.parallelStream().map( v -> new Point( v.getPosition() ) ).collect( Collectors.toSet() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set< Segment > getEdges() {
|
public Set< Segment > getEdges() {
|
||||||
@@ -155,6 +166,14 @@ public class Mesh< T extends Region > {
|
|||||||
return rules.size();
|
return rules.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isMergeChains() {
|
||||||
|
return mergeChains;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMergeChains( boolean merge ) {
|
||||||
|
mergeChains = merge;
|
||||||
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
state = MeshState.TRIANGULATION_READY;
|
state = MeshState.TRIANGULATION_READY;
|
||||||
vertices.clear();
|
vertices.clear();
|
||||||
@@ -232,7 +251,7 @@ public class Mesh< T extends Region > {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Process this mesh and triangulate it
|
||||||
*
|
*
|
||||||
* @return A collection of triangles
|
* @return A collection of triangles
|
||||||
*/
|
*/
|
||||||
@@ -319,6 +338,7 @@ public class Mesh< T extends Region > {
|
|||||||
// At this point in time, if the edge is zero,
|
// At this point in time, if the edge is zero,
|
||||||
// then the origin and destination must be the same
|
// then the origin and destination must be the same
|
||||||
if ( edge.getOrigin() != edge.getDest() ) {
|
if ( edge.getOrigin() != edge.getDest() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Zero edge but the origin and destination are not the same!" );
|
throw new IllegalStateException( "Zero edge but the origin and destination are not the same!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,6 +409,7 @@ public class Mesh< T extends Region > {
|
|||||||
* If both or none are there, then that is an issue.
|
* If both or none are there, then that is an issue.
|
||||||
*/
|
*/
|
||||||
if ( !scannedEdges.remove( edge ) ) {
|
if ( !scannedEdges.remove( edge ) ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Tried to remove an invalid edge!" );
|
throw new IllegalStateException( "Tried to remove an invalid edge!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,6 +422,21 @@ public class Mesh< T extends Region > {
|
|||||||
// 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;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At this point, each vertex should be merged and moved to the
|
||||||
|
* correct position, and all edges should be connected to the
|
||||||
|
* correct vertex, but not necessarily in the right order.
|
||||||
|
*
|
||||||
|
* Invariants:
|
||||||
|
* - All vertices are at least VERTEX_TOLERANCE away from each other
|
||||||
|
* - All vertices are at least VERTEX_TOLERANCE away from each other horizontally
|
||||||
|
* - All vertices will not be moved after this
|
||||||
|
* - No vertices have only 1 edge
|
||||||
|
* - No vertices are within VERTEX_TOLERANCE of another edge which they are not part of
|
||||||
|
* - No edges have length of 0
|
||||||
|
* - No edges intersect or overlap
|
||||||
|
*/
|
||||||
|
|
||||||
state = MeshState.SIMPLIFIED;
|
state = MeshState.SIMPLIFIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,14 +460,17 @@ public class Mesh< T extends Region > {
|
|||||||
continue;
|
continue;
|
||||||
} else if ( isSimilar( v, b ) || isSimilar( v, a ) ) {
|
} else if ( isSimilar( v, b ) || isSimilar( v, a ) ) {
|
||||||
// The vertices should have been merged already
|
// The vertices should have been merged already
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Vertex not merged" );
|
throw new IllegalStateException( "Vertex not merged" );
|
||||||
} else if ( edge.isZero() ) {
|
} else if ( edge.isZero() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Edge is zero!" );
|
throw new IllegalStateException( "Edge is zero!" );
|
||||||
} else {
|
} else {
|
||||||
// Equations taken from https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
|
// Equations taken from https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
|
||||||
final double edgeLenSq = a.distanceSquared( b );
|
final double edgeLenSq = a.distanceSquared( b );
|
||||||
final Vector2d toB = b.subtracted( a );
|
final Vector2d toB = b.subtracted( a );
|
||||||
final double t = v.subtracted( a ).dot( toB ) / edgeLenSq;
|
final double t = v.subtracted( a ).dot( toB ) / edgeLenSq;
|
||||||
|
// TODO Maybe check for tolerance here?
|
||||||
if ( t >= 0 && t <= 1 ) {
|
if ( t >= 0 && t <= 1 ) {
|
||||||
// Get perpendicular distance from v to the line formed by a + b
|
// Get perpendicular distance from v to the line formed by a + b
|
||||||
final Vector2d projection = toB.multiplied( t ).add( a );
|
final Vector2d projection = toB.multiplied( t ).add( a );
|
||||||
@@ -482,11 +521,13 @@ public class Mesh< T extends Region > {
|
|||||||
final IntersectionEdgeToEdge intEdge = ( IntersectionEdgeToEdge ) intersection;
|
final IntersectionEdgeToEdge intEdge = ( IntersectionEdgeToEdge ) intersection;
|
||||||
|
|
||||||
if ( positiveEdge.getDest() == other.getDest() ) {
|
if ( positiveEdge.getDest() == other.getDest() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Invalid intersection" );
|
throw new IllegalStateException( "Invalid intersection" );
|
||||||
}
|
}
|
||||||
|
|
||||||
final Vector2d point = intEdge.getPoint();
|
final Vector2d point = intEdge.getPoint();
|
||||||
if ( isSimilar( point, other.getDest().getPosition() ) || isSimilar( point, positiveEdge.getDest().getPosition() ) ) {
|
if ( isSimilar( point, other.getDest().getPosition() ) || isSimilar( point, positiveEdge.getDest().getPosition() ) ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Invalid intersection point" );
|
throw new IllegalStateException( "Invalid intersection point" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,6 +579,7 @@ 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 {
|
} else {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Unknown intersection type" );
|
throw new IllegalStateException( "Unknown intersection type" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -575,6 +617,7 @@ public class Mesh< T extends Region > {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ( isSimilar( a.getOrigin(), b.getOrigin() ) ) {
|
} else if ( isSimilar( a.getOrigin(), b.getOrigin() ) ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalArgumentException( "Vertex not merged!" );
|
throw new IllegalArgumentException( "Vertex not merged!" );
|
||||||
} else if ( !isSimilar( a.getDest(), b.getDest() ) ) {
|
} else if ( !isSimilar( a.getDest(), b.getDest() ) ) {
|
||||||
// Both edges may intersect somewhere in the middle
|
// Both edges may intersect somewhere in the middle
|
||||||
@@ -710,6 +753,7 @@ public class Mesh< T extends Region > {
|
|||||||
for ( final HalfEdge edge : vertex ) {
|
for ( final HalfEdge edge : vertex ) {
|
||||||
if ( !isPositive( edge ) ) {
|
if ( !isPositive( edge ) ) {
|
||||||
if ( !edges.remove( edge.getSym() ) ) {
|
if ( !edges.remove( edge.getSym() ) ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Negative edge was not added to the edge collection" );
|
throw new IllegalStateException( "Negative edge was not added to the edge collection" );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -810,16 +854,33 @@ public class Mesh< T extends Region > {
|
|||||||
|
|
||||||
vertices = keep;
|
vertices = keep;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Invariants:
|
||||||
|
* - All vertices have an even number of edges
|
||||||
|
* - All vertices are in the correct order
|
||||||
|
* - There are no consecutive collinear edges part of the same polygon
|
||||||
|
* - Exactly one side of every edge is an interior edge
|
||||||
|
*/
|
||||||
|
|
||||||
state = MeshState.TRIANGULATION_READY;
|
state = MeshState.TRIANGULATION_READY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the edges around this vertex.
|
/**
|
||||||
|
* Sort this vertex such that the edges on this vertex are in counter
|
||||||
|
* clockwise order, then set the vertex's edge to the least edge.
|
||||||
|
*
|
||||||
|
* Multiple vertices can be sorted in parallel without affecting the other.
|
||||||
|
*
|
||||||
|
* @param vertex
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private static List< HalfEdge > sort( Vertex vertex ) {
|
private static List< HalfEdge > sort( Vertex vertex ) {
|
||||||
final Vector2d UP = new Vector2d( 0, 1 );
|
final Vector2d UP = new Vector2d( 0, 1 );
|
||||||
final List< HalfEdge > edges = new ArrayList< HalfEdge >();
|
final List< HalfEdge > edges = new ArrayList< HalfEdge >();
|
||||||
|
|
||||||
for ( final HalfEdge edge : vertex ) {
|
for ( final HalfEdge edge : vertex ) {
|
||||||
if ( edge.isZero() ) {
|
if ( edge.isZero() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Edge has length of 0" );
|
throw new IllegalStateException( "Edge has length of 0" );
|
||||||
}
|
}
|
||||||
edges.add( edge );
|
edges.add( edge );
|
||||||
@@ -877,6 +938,7 @@ public class Mesh< T extends Region > {
|
|||||||
HalfEdge edge = internalEdge;
|
HalfEdge edge = internalEdge;
|
||||||
|
|
||||||
if ( edge.getPrev() == edge ) {
|
if ( edge.getPrev() == edge ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Double sided interior edges detected" );
|
throw new IllegalStateException( "Double sided interior edges detected" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -996,74 +1058,71 @@ public class Mesh< T extends Region > {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Have a single set of edges. Use a fuzzy compare here for anything that might
|
// At this point each vertex should be connected to the correct edges
|
||||||
// have made it past the simplification process where two vertices may be within
|
// but needs to be sorted first to ensure that the order is correct.
|
||||||
// VERTEX_TOLERANCE of each other horizontally. Realistically not necessary, and
|
final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare );
|
||||||
// probably more of a false assurance having it here than not.
|
vertices.addAll( newVertices.values() );
|
||||||
final TreeSet< Vertex > fuzzyVertices = new TreeSet< Vertex >( Mesh::fuzzyCompare );
|
|
||||||
fuzzyVertices.addAll( newVertices.values() );
|
|
||||||
|
|
||||||
// Sort each vertex so that the edge they are pointing to is the least edge
|
// Sort each vertex so that the edge they are pointing to is the least edge
|
||||||
fuzzyVertices.parallelStream().forEach( v -> sort( v ) );
|
vertices.parallelStream().forEach( v -> sort( v ) );
|
||||||
|
|
||||||
final Collection< Link > allChains = findChains( fuzzyVertices, edges );
|
if ( mergeChains ) {
|
||||||
final Collection< Link > chains = maximizeChains( allChains );
|
final Collection< Link > allChains = findChains( vertices, edges );
|
||||||
if ( !handlers.isEmpty() ) {
|
final Collection< Link > chains = maximizeChains( allChains );
|
||||||
final Collection< Chain > newChains = new HashSet< Chain >();
|
if ( !handlers.isEmpty() ) {
|
||||||
final Collection< Link > seen = new HashSet< Link >();
|
final Collection< Chain > newChains = new HashSet< Chain >();
|
||||||
|
final Collection< Link > seen = new HashSet< Link >();
|
||||||
for ( final Link link : allChains ) {
|
|
||||||
if ( seen.add( link ) ) {
|
for ( final Link link : allChains ) {
|
||||||
Link head = link;
|
if ( seen.add( link ) ) {
|
||||||
while ( head.previous != null ) {
|
Link head = link;
|
||||||
head = head.previous;
|
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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
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 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers.forEach( h -> h.onChainGenerationEvent( newChains ) );
|
mergeChains( chains, edges );
|
||||||
|
|
||||||
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 );
|
|
||||||
|
|
||||||
final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare );
|
|
||||||
vertices.addAll( fuzzyVertices );
|
|
||||||
|
|
||||||
final Collection< EdgePolygon > polys = partitionMonotone( vertices, edges );
|
final Collection< EdgePolygon > polys = partitionMonotone( vertices, edges );
|
||||||
|
|
||||||
if ( handlers != null ) {
|
if ( handlers != null ) {
|
||||||
@@ -1088,19 +1147,16 @@ public class Mesh< T extends Region > {
|
|||||||
.collect( Collectors.toSet() );
|
.collect( Collectors.toSet() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* This algorithm revolves around first generating chains, then sorting which chains and chain links to keep.
|
* Find all chain links that occur within the provided mesh. Mark which ones intersect, and which links are connected.
|
||||||
*
|
*
|
||||||
* A chain is a collection of chain links.
|
* Each link represents 3 directly collinear vertices.
|
||||||
* A chain link is 3 collinear vertices.
|
|
||||||
* 2 chain links are considered linked if the first link's 2 last vertices are the same as the second link's 2 first vertices.
|
|
||||||
* For example, given collinear vertices A, B, C and D, chain links u and v are linked together if
|
|
||||||
* chain link u has vertices A, B, C, and if chain link v has vertices B, C and D. No more than 2 chain links
|
|
||||||
* can be linked together at one end. Two chains are considered crossing if a link from both chains intersects each other.
|
|
||||||
* At most 3 chain links can intersect another chain link, since an intersection only occurs if the segment formed by 2
|
|
||||||
* vertices from one chain link intersects another segment formed by 2 vertices from a different chain link.
|
|
||||||
*
|
*
|
||||||
* The complexity comes from analyzing which combinations of chain links provides the greatest reduction of vertices.
|
* Time complexity: O(n^2logn)
|
||||||
|
*
|
||||||
|
* @param vertices
|
||||||
|
* @param interior
|
||||||
|
* @return
|
||||||
*/
|
*/
|
||||||
private static Collection< Link > findChains( 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 );
|
final Vector2d CROSS = new Vector2d( 1, 0 );
|
||||||
@@ -1214,8 +1270,10 @@ public class Mesh< T extends Region > {
|
|||||||
final List< HalfEdge > links = region.links.get( region.upper );
|
final List< HalfEdge > links = region.links.get( region.upper );
|
||||||
|
|
||||||
if ( links == null ) {
|
if ( links == null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "No links found for scanned vertex!" );
|
throw new IllegalStateException( "No links found for scanned vertex!" );
|
||||||
} else if ( links.size() < 2 ) {
|
} else if ( links.size() < 2 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Missing links!" );
|
throw new IllegalStateException( "Missing links!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1248,17 +1306,13 @@ public class Mesh< T extends Region > {
|
|||||||
region.upper = region.lower;
|
region.upper = region.lower;
|
||||||
}
|
}
|
||||||
|
|
||||||
// There should be exactly 1 link, the lower region's link
|
|
||||||
if ( region.links.size() != 1 ) {
|
|
||||||
throw new IllegalStateException( "Dangling link!" );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final List< HalfEdge > links = region.links.get( region.upper );
|
final List< HalfEdge > links = region.links.get( region.upper );
|
||||||
|
|
||||||
if ( links == null ) {
|
if ( links == null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "No links found for scanned vertex!" );
|
throw new IllegalStateException( "No links found for scanned vertex!" );
|
||||||
} else if ( links.size() < 2 ) {
|
} else if ( links.size() < 2 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Missing links!" );
|
throw new IllegalStateException( "Missing links!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1284,6 +1338,7 @@ public class Mesh< T extends Region > {
|
|||||||
if ( region.upper != MAX_CROSS ) {
|
if ( region.upper != MAX_CROSS ) {
|
||||||
final List< HalfEdge > prevLinks = region.links.get( region.upper );
|
final List< HalfEdge > prevLinks = region.links.get( region.upper );
|
||||||
if ( prevLinks.size() < 2 ) {
|
if ( prevLinks.size() < 2 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Missing links!" );
|
throw new IllegalStateException( "Missing links!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1295,6 +1350,7 @@ public class Mesh< T extends Region > {
|
|||||||
|
|
||||||
// As of right now there should only be 1 or 0 links
|
// As of right now there should only be 1 or 0 links
|
||||||
if ( region.links.size() > 1 ) {
|
if ( region.links.size() > 1 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Too many links!" );
|
throw new IllegalStateException( "Too many links!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1309,6 +1365,7 @@ public class Mesh< T extends Region > {
|
|||||||
links.add( region.upperEdge );
|
links.add( region.upperEdge );
|
||||||
links.add( edge );
|
links.add( edge );
|
||||||
if ( region.links.put( region.upper, links ) != null ) {
|
if ( region.links.put( region.upper, links ) != null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Cross value already used!" );
|
throw new IllegalStateException( "Cross value already used!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1344,8 +1401,10 @@ public class Mesh< T extends Region > {
|
|||||||
final List< HalfEdge > links = region.links.get( region.lower );
|
final List< HalfEdge > links = region.links.get( region.lower );
|
||||||
|
|
||||||
if ( links == null ) {
|
if ( links == null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "No links found for scanned vertex!" );
|
throw new IllegalStateException( "No links found for scanned vertex!" );
|
||||||
} else if ( links.size() < 2 ) {
|
} else if ( links.size() < 2 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Missing links!" );
|
throw new IllegalStateException( "Missing links!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1377,16 +1436,13 @@ public class Mesh< T extends Region > {
|
|||||||
region.lower = region.upper;
|
region.lower = region.upper;
|
||||||
}
|
}
|
||||||
|
|
||||||
// There should be exactly 1 link, the lower region's link
|
|
||||||
if ( region.links.size() != 1 ) {
|
|
||||||
throw new IllegalStateException( "Dangling link!" );
|
|
||||||
}
|
|
||||||
|
|
||||||
final List< HalfEdge > links = region.links.get( region.lower );
|
final List< HalfEdge > links = region.links.get( region.lower );
|
||||||
|
|
||||||
if ( links == null ) {
|
if ( links == null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "No links found for scanned vertex!" );
|
throw new IllegalStateException( "No links found for scanned vertex!" );
|
||||||
} else if ( links.size() < 2 ) {
|
} else if ( links.size() < 2 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Missing links!" );
|
throw new IllegalStateException( "Missing links!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1412,6 +1468,7 @@ public class Mesh< T extends Region > {
|
|||||||
if ( region.lower != MIN_CROSS ) {
|
if ( region.lower != MIN_CROSS ) {
|
||||||
final List< HalfEdge > prevLinks = region.links.get( region.lower );
|
final List< HalfEdge > prevLinks = region.links.get( region.lower );
|
||||||
if ( prevLinks.size() < 2 ) {
|
if ( prevLinks.size() < 2 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Missing links!" );
|
throw new IllegalStateException( "Missing links!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1423,6 +1480,7 @@ public class Mesh< T extends Region > {
|
|||||||
|
|
||||||
// As of right now there should only be 1 or 0 links
|
// As of right now there should only be 1 or 0 links
|
||||||
if ( region.links.size() > 1 ) {
|
if ( region.links.size() > 1 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Too many links!" );
|
throw new IllegalStateException( "Too many links!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1437,6 +1495,7 @@ public class Mesh< T extends Region > {
|
|||||||
links.add( region.lowerEdge );
|
links.add( region.lowerEdge );
|
||||||
links.add( edge );
|
links.add( edge );
|
||||||
if ( region.links.put( region.lower, links ) != null ) {
|
if ( region.links.put( region.lower, links ) != null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Cross value already used!" );
|
throw new IllegalStateException( "Cross value already used!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1481,16 +1540,20 @@ public class Mesh< T extends Region > {
|
|||||||
|
|
||||||
final Vector2d p = chainOrigin.getPosition();
|
final Vector2d p = chainOrigin.getPosition();
|
||||||
final Vector2d r = chainDestination.getPosition().subtracted( p );
|
final Vector2d r = chainDestination.getPosition().subtracted( p );
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There are two cases where it might seem possible, but are not:
|
||||||
|
* - Where this link is the previous link for an existing link
|
||||||
|
* - Where the destination of this link is another link's midpoint
|
||||||
|
*
|
||||||
|
* This is because it depends on the new link's destination being less than
|
||||||
|
* any other vertex in all the existing links, which should never be possible.
|
||||||
|
*/
|
||||||
chains.links.parallelStream().forEach( other -> {
|
chains.links.parallelStream().forEach( other -> {
|
||||||
if ( other.getMidpoint() == chainOrigin && other.getDest() == chainMidpoint ) {
|
if ( other.getMidpoint() == chainOrigin && other.getDest() == chainMidpoint ) {
|
||||||
// The new chain is a continuation of this chain
|
// The new chain is a continuation of this chain
|
||||||
other.next = chain;
|
other.next = chain;
|
||||||
chain.previous = other;
|
chain.previous = other;
|
||||||
// } else if ( chainMidpoint == other.getOrigin() && chainDestination == other.getMidpoint() ) {
|
|
||||||
// You may think that this is a valid case, but it is not possible for this to occur since the
|
|
||||||
// new chain's destination must always be greater than or equal to the destination of all existing chains.
|
|
||||||
// other.previous = chain;
|
|
||||||
// chain.next = other;
|
|
||||||
} else if ( chainOrigin == other.getMidpoint() ) {
|
} else if ( chainOrigin == other.getMidpoint() ) {
|
||||||
// In the case that the new chain's origin is another chain's midpoint,
|
// 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.
|
// we need to check which direction the other chain is facing.
|
||||||
@@ -1521,31 +1584,6 @@ public class Mesh< T extends Region > {
|
|||||||
}
|
}
|
||||||
other.intersections.add( chain );
|
other.intersections.add( chain );
|
||||||
}
|
}
|
||||||
// } else if ( chainDestination == other.getMidpoint() ) {
|
|
||||||
// For the same reason above, this case is also impossible.
|
|
||||||
// final Vector2d otherDirection = other.getDest().getPosition().subtracted( other.getOrigin().getPosition() );
|
|
||||||
// final double cross = r.cross( otherDirection );
|
|
||||||
//
|
|
||||||
// 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 ) {
|
} else if ( other.getDest() == chainMidpoint ) {
|
||||||
// Likewise, the new chain's midpoint may be another existing chain's
|
// Likewise, the new chain's midpoint may be another existing chain's
|
||||||
// destination. We need to check for that case too.
|
// destination. We need to check for that case too.
|
||||||
@@ -1702,6 +1740,7 @@ public class Mesh< T extends Region > {
|
|||||||
} else {
|
} else {
|
||||||
leftGoing.add( edge );
|
leftGoing.add( edge );
|
||||||
if ( !edgeCollection.remove( edge.getSym() ) ) {
|
if ( !edgeCollection.remove( edge.getSym() ) ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Edge not added to the edge collection!" );
|
throw new IllegalStateException( "Edge not added to the edge collection!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1761,6 +1800,7 @@ public class Mesh< T extends Region > {
|
|||||||
if ( toSplit != null ) {
|
if ( toSplit != null ) {
|
||||||
|
|
||||||
if ( rightGoing.size() < 2 ) {
|
if ( rightGoing.size() < 2 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Vertex has less than 2 right edges!" );
|
throw new IllegalStateException( "Vertex has less than 2 right edges!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1861,6 +1901,7 @@ public class Mesh< T extends Region > {
|
|||||||
|
|
||||||
newPartitions.add( copy );
|
newPartitions.add( copy );
|
||||||
} else {
|
} else {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "A partition with an interior edge does not exist!" );
|
throw new IllegalStateException( "A partition with an interior edge does not exist!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1899,6 +1940,7 @@ public class Mesh< T extends Region > {
|
|||||||
// into a single partition.
|
// into a single partition.
|
||||||
|
|
||||||
if ( leftGoing.size() < 2 ) {
|
if ( leftGoing.size() < 2 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Vertex has less than 2 left edges!" );
|
throw new IllegalStateException( "Vertex has less than 2 left edges!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1909,8 +1951,10 @@ public class Mesh< T extends Region > {
|
|||||||
final Partition bottom = upperEdgeMap.remove( bottomLeft.getSym() );
|
final Partition bottom = upperEdgeMap.remove( bottomLeft.getSym() );
|
||||||
|
|
||||||
if ( top == null ) {
|
if ( top == null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Upper partition not found!" );
|
throw new IllegalStateException( "Upper partition not found!" );
|
||||||
} else if ( bottom == null ) {
|
} else if ( bottom == null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Lower partition not found!" );
|
throw new IllegalStateException( "Lower partition not found!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1956,6 +2000,7 @@ public class Mesh< T extends Region > {
|
|||||||
if ( partition != null ) {
|
if ( partition != null ) {
|
||||||
|
|
||||||
if ( partition.upper != left.getSym() ) {
|
if ( partition.upper != left.getSym() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Partition upper edge is inconsistent!" );
|
throw new IllegalStateException( "Partition upper edge is inconsistent!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1971,6 +2016,7 @@ public class Mesh< T extends Region > {
|
|||||||
// Add a new region for this vertex
|
// Add a new region for this vertex
|
||||||
partition.add( event, new VisibilityRegion( left, right ) );
|
partition.add( event, new VisibilityRegion( left, right ) );
|
||||||
} else {
|
} else {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Interior edge does not have a partition!" );
|
throw new IllegalStateException( "Interior edge does not have a partition!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1984,6 +2030,7 @@ public class Mesh< T extends Region > {
|
|||||||
if ( partition != null ) {
|
if ( partition != null ) {
|
||||||
|
|
||||||
if ( partition.lower != left.getSym() ) {
|
if ( partition.lower != left.getSym() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Partition lower edge is inconsistent!" );
|
throw new IllegalStateException( "Partition lower edge is inconsistent!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1998,6 +2045,7 @@ public class Mesh< T extends Region > {
|
|||||||
// Add a new region for this vertex
|
// Add a new region for this vertex
|
||||||
partition.add( event, new VisibilityRegion( right, left ) );
|
partition.add( event, new VisibilityRegion( right, left ) );
|
||||||
} else {
|
} else {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Interior edge does not have a partition!" );
|
throw new IllegalStateException( "Interior edge does not have a partition!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2014,6 +2062,7 @@ public class Mesh< T extends Region > {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( !upperEdgeMap.isEmpty() || !lowerEdgeMap.isEmpty() ) {
|
if ( !upperEdgeMap.isEmpty() || !lowerEdgeMap.isEmpty() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Dangling partitions!" );
|
throw new IllegalStateException( "Dangling partitions!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2023,6 +2072,16 @@ public class Mesh< T extends Region > {
|
|||||||
return aggregateLinks;
|
return aggregateLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a collection of chain links, maximize the amount of non intersecting
|
||||||
|
* links. Use a greedy method to select whichever links have the least amount
|
||||||
|
* of intersections, until there are no more chain links left to be selected.
|
||||||
|
*
|
||||||
|
* Time complexity: O(n^2) worst case
|
||||||
|
*
|
||||||
|
* @param chains
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private static Collection< Link > maximizeChains( final Collection< Link > chains ) {
|
private static Collection< Link > maximizeChains( final Collection< Link > chains ) {
|
||||||
final Collection< Link > remainingChains = new LinkedHashSet< Link >( chains );
|
final Collection< Link > remainingChains = new LinkedHashSet< Link >( chains );
|
||||||
final Collection< Link > selectedChains = new HashSet< Link >();
|
final Collection< Link > selectedChains = new HashSet< Link >();
|
||||||
@@ -2060,6 +2119,14 @@ public class Mesh< T extends Region > {
|
|||||||
return selectedChains;
|
return selectedChains;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a collection of chain links, merge them into the resulting mesh.
|
||||||
|
*
|
||||||
|
* Time complexity: O(n)
|
||||||
|
*
|
||||||
|
* @param selected
|
||||||
|
* @param interior
|
||||||
|
*/
|
||||||
private static void mergeChains( final Collection< Link > selected, final Collection< HalfEdge > interior ) {
|
private static void mergeChains( final Collection< Link > selected, final Collection< HalfEdge > interior ) {
|
||||||
final Collection< Link > chains = new HashSet< Link >( selected );
|
final Collection< Link > chains = new HashSet< Link >( selected );
|
||||||
while ( !chains.isEmpty() ) {
|
while ( !chains.isEmpty() ) {
|
||||||
@@ -2106,6 +2173,7 @@ public class Mesh< T extends Region > {
|
|||||||
if ( interior.contains( head ) ) {
|
if ( interior.contains( head ) ) {
|
||||||
// Left handed
|
// Left handed
|
||||||
if ( right != null ) {
|
if ( right != null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Unterminated left edge!" );
|
throw new IllegalStateException( "Unterminated left edge!" );
|
||||||
}
|
}
|
||||||
right = new HalfEdge();
|
right = new HalfEdge();
|
||||||
@@ -2144,6 +2212,7 @@ public class Mesh< T extends Region > {
|
|||||||
} else {
|
} else {
|
||||||
// Right handed
|
// Right handed
|
||||||
if ( left != null ) {
|
if ( left != null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Unterminated right edge!" );
|
throw new IllegalStateException( "Unterminated right edge!" );
|
||||||
}
|
}
|
||||||
left = new HalfEdge();
|
left = new HalfEdge();
|
||||||
@@ -2328,7 +2397,7 @@ public class Mesh< T extends Region > {
|
|||||||
edge = edge.getPrev();
|
edge = edge.getPrev();
|
||||||
|
|
||||||
if ( --size < 0 ) {
|
if ( --size < 0 ) {
|
||||||
throw new IllegalStateException( "Not good vertex! With only " + vertex.size() );
|
throw new IllegalStateException( "Checked too many edges!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2359,9 +2428,11 @@ public class Mesh< T extends Region > {
|
|||||||
final SortedEdgeCollection< HalfEdge > edgeCollection = new SortedEdgeCollection< HalfEdge >( ( a, b ) -> {
|
final SortedEdgeCollection< HalfEdge > edgeCollection = new SortedEdgeCollection< HalfEdge >( ( a, b ) -> {
|
||||||
// Assume each region is marked by an upper edge going from right to left, up to down
|
// Assume each region is marked by an upper edge going from right to left, up to down
|
||||||
if ( a.isZero() || b.isZero() ) {
|
if ( a.isZero() || b.isZero() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Zero length edge detected" );
|
throw new IllegalStateException( "Zero length edge detected" );
|
||||||
}
|
}
|
||||||
if ( !( isPositive( a ) && isPositive( b ) ) ) {
|
if ( !( isPositive( a ) && isPositive( b ) ) ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Negative edge detected" );
|
throw new IllegalStateException( "Negative edge detected" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2419,6 +2490,7 @@ public class Mesh< T extends Region > {
|
|||||||
} else {
|
} else {
|
||||||
leftGoing.add( edge );
|
leftGoing.add( edge );
|
||||||
if ( !edgeCollection.remove( edge.getSym() ) ) {
|
if ( !edgeCollection.remove( edge.getSym() ) ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Edge not added to the edge collection!" );
|
throw new IllegalStateException( "Edge not added to the edge collection!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2453,10 +2525,13 @@ public class Mesh< T extends Region > {
|
|||||||
// The current vertex must be between the upper and the lower
|
// The current vertex must be between the upper and the lower
|
||||||
// edge, and it cannot be part of either edge
|
// edge, and it cannot be part of either edge
|
||||||
if ( upper == null ) {
|
if ( upper == null ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Lower does not have an upper edge!" );
|
throw new IllegalStateException( "Lower does not have an upper edge!" );
|
||||||
} if ( !interior.contains( upper.getSym() ) ) {
|
} if ( !interior.contains( upper.getSym() ) ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Polygon is not simple!" );
|
throw new IllegalStateException( "Polygon is not simple!" );
|
||||||
} else if ( upper.getOrigin() == event || lower.getOrigin() == event ) {
|
} else if ( upper.getOrigin() == event || lower.getOrigin() == event ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Vertex is invalid!" );
|
throw new IllegalStateException( "Vertex is invalid!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2464,6 +2539,7 @@ public class Mesh< T extends Region > {
|
|||||||
eventMark = new MarkedEdge( lower, upper );
|
eventMark = new MarkedEdge( lower, upper );
|
||||||
}
|
}
|
||||||
} else if ( rightGoing.isEmpty() && leftGoing.isEmpty() ) {
|
} else if ( rightGoing.isEmpty() && leftGoing.isEmpty() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Cannot have a vertex with no edges!" );
|
throw new IllegalStateException( "Cannot have a vertex with no edges!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2503,6 +2579,7 @@ public class Mesh< T extends Region > {
|
|||||||
// upper/lower region as this vertex, then
|
// upper/lower region as this vertex, then
|
||||||
// that must be because this vertex is left marked
|
// that must be because this vertex is left marked
|
||||||
if ( !leftGoing.isEmpty() ) {
|
if ( !leftGoing.isEmpty() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Vertex is not left-marked!" );
|
throw new IllegalStateException( "Vertex is not left-marked!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2583,6 +2660,7 @@ public class Mesh< T extends Region > {
|
|||||||
if ( isPositive( edge ) ) {
|
if ( isPositive( edge ) ) {
|
||||||
edgeCollection.insert( edge );
|
edgeCollection.insert( edge );
|
||||||
} else {
|
} else {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Edge suddenly changed sign!" );
|
throw new IllegalStateException( "Edge suddenly changed sign!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2590,6 +2668,7 @@ public class Mesh< T extends Region > {
|
|||||||
|
|
||||||
// All right marked vertices MUST be resolved by now.
|
// All right marked vertices MUST be resolved by now.
|
||||||
if ( !rightMarked.isEmpty() ) {
|
if ( !rightMarked.isEmpty() ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Unresolved right marked vertices!" );
|
throw new IllegalStateException( "Unresolved right marked vertices!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2648,6 +2727,14 @@ public class Mesh< T extends Region > {
|
|||||||
return newPolygons;
|
return newPolygons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate triangles as described in Computational Geometry Algorithms and Applications 3rd Edition.
|
||||||
|
*
|
||||||
|
* A linear time algorithm.
|
||||||
|
*
|
||||||
|
* @param polygon
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private static Collection< Polygon > triangulate( final EdgePolygon polygon ) {
|
private static Collection< Polygon > triangulate( final EdgePolygon polygon ) {
|
||||||
// Go down each monotone chain and connect the vertices where possible.
|
// Go down each monotone chain and connect the vertices where possible.
|
||||||
// Implements the O(n) triangulation of a polygon as described in
|
// Implements the O(n) triangulation of a polygon as described in
|
||||||
@@ -2757,8 +2844,10 @@ public class Mesh< T extends Region > {
|
|||||||
// TODO Remove this NOW
|
// TODO Remove this NOW
|
||||||
toSort.parallelStream().forEach( v -> sort( v ) );
|
toSort.parallelStream().forEach( v -> sort( v ) );
|
||||||
|
|
||||||
Map< Vertex, Point > pointMap = polygon.vertices.parallelStream().collect( Collectors.toMap( Function.identity(), v -> { return new Point( v.getPosition() ); } ) );
|
// Convert each vertex to a point
|
||||||
|
final Map< Vertex, Point > pointMap = polygon.vertices.parallelStream().collect( Collectors.toMap( Function.identity(), v -> new Point( v.getPosition() ) ) );
|
||||||
|
|
||||||
|
// Convert each triangle to a polygon
|
||||||
final Collection< Polygon > polygons = new ArrayDeque< Polygon >();
|
final Collection< Polygon > polygons = new ArrayDeque< Polygon >();
|
||||||
final Set< HalfEdge > scanned = new HashSet< HalfEdge >();
|
final Set< HalfEdge > scanned = new HashSet< HalfEdge >();
|
||||||
for ( final HalfEdge edge : polygon.getEdges() ) {
|
for ( final HalfEdge edge : polygon.getEdges() ) {
|
||||||
@@ -2771,6 +2860,7 @@ public class Mesh< T extends Region > {
|
|||||||
} while ( scanned.add( temp = temp.getNext() ) );
|
} while ( scanned.add( temp = temp.getNext() ) );
|
||||||
|
|
||||||
if ( points.size() != 3 ) {
|
if ( points.size() != 3 ) {
|
||||||
|
// TODO Remove this
|
||||||
throw new IllegalStateException( "Not a triangle: " + points.size() );
|
throw new IllegalStateException( "Not a triangle: " + points.size() );
|
||||||
}
|
}
|
||||||
polygons.add( new Polygon( points ) );
|
polygons.add( new Polygon( points ) );
|
||||||
@@ -2821,28 +2911,6 @@ public class Mesh< T extends Region > {
|
|||||||
return compare( edge.getOrigin(), edge.getDest() ) <= 0;
|
return compare( edge.getOrigin(), edge.getDest() ) <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Since sometimes vertices will be off by a few floating point errors,
|
|
||||||
* it will cause problems because we have checks for tolerance, but if
|
|
||||||
* the sorting for vertices does not, it causes issues.
|
|
||||||
*/
|
|
||||||
private static int fuzzyCompare( final Vertex a, final Vertex b ) {
|
|
||||||
if ( a == b ) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final Vector2d aPos = a.getPosition();
|
|
||||||
final Vector2d bPos = b.getPosition();
|
|
||||||
|
|
||||||
final double xDiff = aPos.getX() - bPos.getX();
|
|
||||||
if ( Math.abs( xDiff ) < VERTEX_TOLERANCE ) {
|
|
||||||
return Double.compare( aPos.getY(), bPos.getY() );
|
|
||||||
} else {
|
|
||||||
return Double.compare( aPos.getX(), bPos.getX() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int compare( final Vertex a, final Vertex b ) {
|
private static int compare( final Vertex a, final Vertex b ) {
|
||||||
if ( a == b ) {
|
if ( a == b ) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.aaaaahhhhhhh.bananapuncher714.mesh;
|
package com.aaaaahhhhhhh.bananapuncher714.mesh;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A 2d cartesian coordinate
|
* A 2d Cartesian coordinate
|
||||||
*
|
*
|
||||||
* @author BananaPuncher714
|
* @author BananaPuncher714
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.aaaaahhhhhhh.bananapuncher714.mesh;
|
package com.aaaaahhhhhhh.bananapuncher714.mesh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A line segment
|
||||||
|
*/
|
||||||
public class Segment {
|
public class Segment {
|
||||||
private final Point start;
|
private final Point start;
|
||||||
private final Point end;
|
private final Point end;
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package com.aaaaahhhhhhh.bananapuncher714.mesh;
|
|
||||||
|
|
||||||
public class Test {
|
|
||||||
public static void main( String[] args ) {
|
|
||||||
final Vector2d a = new Vector2d( -1, -1 );
|
|
||||||
final Vector2d b = new Vector2d( 10, 10 );
|
|
||||||
final Vector2d c = new Vector2d( 0, -1 );
|
|
||||||
final Vector2d d = new Vector2d( 10, 11 );
|
|
||||||
|
|
||||||
test( a, b, c, d );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void test( final Vector2d a, final Vector2d b, final Vector2d c, final Vector2d d ) {
|
|
||||||
final Vector2d ab = b.subtracted( a );
|
|
||||||
final Vector2d cd = d.subtracted( c );
|
|
||||||
|
|
||||||
final Vector2d ac = c.subtracted( a );
|
|
||||||
|
|
||||||
final double acab = ac.cross( ab );
|
|
||||||
final double abcd = ab.cross( cd );
|
|
||||||
|
|
||||||
System.out.println( "Parallel value: " + abcd );
|
|
||||||
if ( abcd != 0 ) {
|
|
||||||
final double s = ac.cross( cd );
|
|
||||||
final double t = s / abcd;
|
|
||||||
final double u = acab / abcd;
|
|
||||||
|
|
||||||
System.out.println( "S: " + s );
|
|
||||||
System.out.println( "T: " + t );
|
|
||||||
System.out.println( "U: " + u );
|
|
||||||
|
|
||||||
System.out.println( "AB Length: " + ( t * ab.length() ) );
|
|
||||||
System.out.println( "CD Length: " + ( u * cd.length() ) );
|
|
||||||
|
|
||||||
// Gives the intersection correctly
|
|
||||||
System.out.println( "AB int: " + ab.multiplied( t ).add( a ) );
|
|
||||||
System.out.println( "CD int: " + cd.multiplied( u ).add( c ) );
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,6 @@ import java.util.Collections;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JComboBox;
|
import javax.swing.JComboBox;
|
||||||
@@ -142,7 +141,8 @@ public class MeshingTest2 extends JPanel {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-4,-6" ) );
|
final List< Plane > planes = mesh( new File( CHUNK_DIR, "world,-4,-6" ) );
|
||||||
masterPlanes.add( planes.get( 177 ) );
|
// masterPlanes.add( planes.get( 177 ) );
|
||||||
|
masterPlanes.addAll( planes );
|
||||||
|
|
||||||
// masterPlanes.add( getTestPlane() );
|
// masterPlanes.add( getTestPlane() );
|
||||||
|
|
||||||
@@ -969,9 +969,9 @@ public class MeshingTest2 extends JPanel {
|
|||||||
g.setColor( Color.MAGENTA );
|
g.setColor( Color.MAGENTA );
|
||||||
drawOnBoard( g, "Triangle count: " + data.polygons.size(), 0, -10 );
|
drawOnBoard( g, "Triangle count: " + data.polygons.size(), 0, -10 );
|
||||||
g.setColor( Color.RED );
|
g.setColor( Color.RED );
|
||||||
drawOnBoard( g, "Total Chain count: " + data.chains.size(), 0, -15 );
|
drawOnBoard( g, "Total chain count: " + data.chains.size(), 0, -15 );
|
||||||
g.setColor( Color.BLUE );
|
g.setColor( Color.BLUE );
|
||||||
drawOnBoard( g, "Possible chain count: " + data.selectedChains.size(), 0, -20 );
|
drawOnBoard( g, "Optimal chain count: " + data.selectedChains.size(), 0, -20 );
|
||||||
}
|
}
|
||||||
|
|
||||||
java.awt.Point p = MouseInfo.getPointerInfo().getLocation();
|
java.awt.Point p = MouseInfo.getPointerInfo().getLocation();
|
||||||
|
|||||||
Reference in New Issue
Block a user