Update comments

This commit is contained in:
2025-05-23 22:15:40 -04:00
parent 702a4288b2
commit 20cf9e9ec5
6 changed files with 223 additions and 190 deletions

View File

@@ -4,6 +4,10 @@ import java.util.ArrayList;
import java.util.Collections;
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 {
protected List< Point > points = new ArrayList< Point >();
protected List< Chain > intersections = new ArrayList< Chain >();

View File

@@ -36,6 +36,7 @@ import com.aaaaahhhhhhh.bananapuncher714.mesh.region.RegionRule;
* The entire algorithm from polygons to triangles can be summarized as follows:
* 1. Simplify the polygons into one massive DCEL/PSLG. This includes:
* - 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 collinear edges
* - 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
* 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
* since this step adds edges and vertices to the PSLG, which may be undesirable for reusability.
* Then, we remove collinear edges which may have been generated as a result of the monotone partitioning.
* 3. Meshing and triangulating the resulting polygons. Firstly, the mesh is duplicated so that
* the original mesh can be reused for future modifications, since this step adds edges and
* 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
* respect to Y. The PSLG is not guaranteed to be comprised of simple polygons, and may contain
* holes.
*
* 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 a linear-time algorithm.
*
* 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
*/
@@ -91,10 +94,18 @@ public class Mesh< T extends Region > {
protected Collection< MeshEventHandler > handlers = new LinkedHashSet< MeshEventHandler >();
protected boolean mergeChains = true;
public Mesh( final Supplier< T > 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 ) {
// Is it at least a triangle?
if ( poly.getPoints().size() < 3 ) {
@@ -135,7 +146,7 @@ public class Mesh< T extends Region > {
}
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() {
@@ -155,6 +166,14 @@ public class Mesh< T extends Region > {
return rules.size();
}
public boolean isMergeChains() {
return mergeChains;
}
public void setMergeChains( boolean merge ) {
mergeChains = merge;
}
public void clear() {
state = MeshState.TRIANGULATION_READY;
vertices.clear();
@@ -232,7 +251,7 @@ public class Mesh< T extends Region > {
}
/**
*
* Process this mesh and triangulate it
*
* @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,
// then the origin and destination must be the same
if ( edge.getOrigin() != edge.getDest() ) {
// TODO Remove this
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 ( !scannedEdges.remove( edge ) ) {
// TODO Remove this
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
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;
}
@@ -424,14 +460,17 @@ public class Mesh< T extends Region > {
continue;
} else if ( isSimilar( v, b ) || isSimilar( v, a ) ) {
// The vertices should have been merged already
// TODO Remove this
throw new IllegalStateException( "Vertex not merged" );
} else if ( edge.isZero() ) {
// TODO Remove this
throw new IllegalStateException( "Edge is zero!" );
} else {
// Equations taken from https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
final double edgeLenSq = a.distanceSquared( b );
final Vector2d toB = b.subtracted( a );
final double t = v.subtracted( a ).dot( toB ) / edgeLenSq;
// TODO Maybe check for tolerance here?
if ( t >= 0 && t <= 1 ) {
// Get perpendicular distance from v to the line formed by a + b
final Vector2d projection = toB.multiplied( t ).add( a );
@@ -482,11 +521,13 @@ public class Mesh< T extends Region > {
final IntersectionEdgeToEdge intEdge = ( IntersectionEdgeToEdge ) intersection;
if ( positiveEdge.getDest() == other.getDest() ) {
// TODO Remove this
throw new IllegalStateException( "Invalid intersection" );
}
final Vector2d point = intEdge.getPoint();
if ( isSimilar( point, other.getDest().getPosition() ) || isSimilar( point, positiveEdge.getDest().getPosition() ) ) {
// TODO Remove this
throw new IllegalStateException( "Invalid intersection point" );
}
@@ -538,6 +579,7 @@ public class Mesh< T extends Region > {
// Splice the two together
HalfEdge.splice( shorter.getSym(), split );
} else {
// TODO Remove this
throw new IllegalStateException( "Unknown intersection type" );
}
}
@@ -575,6 +617,7 @@ public class Mesh< T extends Region > {
}
}
} else if ( isSimilar( a.getOrigin(), b.getOrigin() ) ) {
// TODO Remove this
throw new IllegalArgumentException( "Vertex not merged!" );
} else if ( !isSimilar( a.getDest(), b.getDest() ) ) {
// Both edges may intersect somewhere in the middle
@@ -710,6 +753,7 @@ public class Mesh< T extends Region > {
for ( final HalfEdge edge : vertex ) {
if ( !isPositive( edge ) ) {
if ( !edges.remove( edge.getSym() ) ) {
// TODO Remove this
throw new IllegalStateException( "Negative edge was not added to the edge collection" );
}
} else {
@@ -810,16 +854,33 @@ public class Mesh< T extends Region > {
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;
}
// 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 ) {
final Vector2d UP = new Vector2d( 0, 1 );
final List< HalfEdge > edges = new ArrayList< HalfEdge >();
for ( final HalfEdge edge : vertex ) {
if ( edge.isZero() ) {
// TODO Remove this
throw new IllegalStateException( "Edge has length of 0" );
}
edges.add( edge );
@@ -877,6 +938,7 @@ public class Mesh< T extends Region > {
HalfEdge edge = internalEdge;
if ( edge.getPrev() == edge ) {
// TODO Remove this
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
// have made it past the simplification process where two vertices may be within
// VERTEX_TOLERANCE of each other horizontally. Realistically not necessary, and
// probably more of a false assurance having it here than not.
final TreeSet< Vertex > fuzzyVertices = new TreeSet< Vertex >( Mesh::fuzzyCompare );
fuzzyVertices.addAll( newVertices.values() );
// At this point each vertex should be connected to the correct edges
// but needs to be sorted first to ensure that the order is correct.
final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare );
vertices.addAll( newVertices.values() );
// 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 );
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;
if ( mergeChains ) {
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 );
}
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 ) );
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 );
}
mergeChains( chains, edges );
final TreeSet< Vertex > vertices = new TreeSet< Vertex >( Mesh::compare );
vertices.addAll( fuzzyVertices );
final Collection< EdgePolygon > polys = partitionMonotone( vertices, edges );
if ( handlers != null ) {
@@ -1088,19 +1147,16 @@ public class Mesh< T extends Region > {
.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.
* 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.
* Each link represents 3 directly collinear vertices.
*
* 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 ) {
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 );
if ( links == null ) {
// TODO Remove this
throw new IllegalStateException( "No links found for scanned vertex!" );
} else if ( links.size() < 2 ) {
// TODO Remove this
throw new IllegalStateException( "Missing links!" );
}
@@ -1248,17 +1306,13 @@ public class Mesh< T extends Region > {
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 );
if ( links == null ) {
// TODO Remove this
throw new IllegalStateException( "No links found for scanned vertex!" );
} else if ( links.size() < 2 ) {
// TODO Remove this
throw new IllegalStateException( "Missing links!" );
}
@@ -1284,6 +1338,7 @@ public class Mesh< T extends Region > {
if ( region.upper != MAX_CROSS ) {
final List< HalfEdge > prevLinks = region.links.get( region.upper );
if ( prevLinks.size() < 2 ) {
// TODO Remove this
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
if ( region.links.size() > 1 ) {
// TODO Remove this
throw new IllegalStateException( "Too many links!" );
}
}
@@ -1309,6 +1365,7 @@ public class Mesh< T extends Region > {
links.add( region.upperEdge );
links.add( edge );
if ( region.links.put( region.upper, links ) != null ) {
// TODO Remove this
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 );
if ( links == null ) {
// TODO Remove this
throw new IllegalStateException( "No links found for scanned vertex!" );
} else if ( links.size() < 2 ) {
// TODO Remove this
throw new IllegalStateException( "Missing links!" );
}
@@ -1377,16 +1436,13 @@ public class Mesh< T extends Region > {
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 );
if ( links == null ) {
// TODO Remove this
throw new IllegalStateException( "No links found for scanned vertex!" );
} else if ( links.size() < 2 ) {
// TODO Remove this
throw new IllegalStateException( "Missing links!" );
}
@@ -1412,6 +1468,7 @@ public class Mesh< T extends Region > {
if ( region.lower != MIN_CROSS ) {
final List< HalfEdge > prevLinks = region.links.get( region.lower );
if ( prevLinks.size() < 2 ) {
// TODO Remove this
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
if ( region.links.size() > 1 ) {
// TODO Remove this
throw new IllegalStateException( "Too many links!" );
}
}
@@ -1437,6 +1495,7 @@ public class Mesh< T extends Region > {
links.add( region.lowerEdge );
links.add( edge );
if ( region.links.put( region.lower, links ) != null ) {
// TODO Remove this
throw new IllegalStateException( "Cross value already used!" );
}
}
@@ -1481,16 +1540,20 @@ public class Mesh< T extends Region > {
final Vector2d p = chainOrigin.getPosition();
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 -> {
if ( other.getMidpoint() == chainOrigin && other.getDest() == chainMidpoint ) {
// The new chain is a continuation of this chain
other.next = chain;
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() ) {
// 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.
@@ -1521,31 +1584,6 @@ public class Mesh< T extends Region > {
}
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 ) {
// Likewise, the new chain's midpoint may be another existing chain's
// destination. We need to check for that case too.
@@ -1702,6 +1740,7 @@ public class Mesh< T extends Region > {
} else {
leftGoing.add( edge );
if ( !edgeCollection.remove( edge.getSym() ) ) {
// TODO Remove this
throw new IllegalStateException( "Edge not added to the edge collection!" );
}
}
@@ -1761,6 +1800,7 @@ public class Mesh< T extends Region > {
if ( toSplit != null ) {
if ( rightGoing.size() < 2 ) {
// TODO Remove this
throw new IllegalStateException( "Vertex has less than 2 right edges!" );
}
@@ -1861,6 +1901,7 @@ public class Mesh< T extends Region > {
newPartitions.add( copy );
} else {
// TODO Remove this
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.
if ( leftGoing.size() < 2 ) {
// TODO Remove this
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() );
if ( top == null ) {
// TODO Remove this
throw new IllegalStateException( "Upper partition not found!" );
} else if ( bottom == null ) {
// TODO Remove this
throw new IllegalStateException( "Lower partition not found!" );
}
@@ -1956,6 +2000,7 @@ public class Mesh< T extends Region > {
if ( partition != null ) {
if ( partition.upper != left.getSym() ) {
// TODO Remove this
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
partition.add( event, new VisibilityRegion( left, right ) );
} else {
// TODO Remove this
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.lower != left.getSym() ) {
// TODO Remove this
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
partition.add( event, new VisibilityRegion( right, left ) );
} else {
// TODO Remove this
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() ) {
// TODO Remove this
throw new IllegalStateException( "Dangling partitions!" );
}
@@ -2023,6 +2072,16 @@ public class Mesh< T extends Region > {
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 ) {
final Collection< Link > remainingChains = new LinkedHashSet< Link >( chains );
final Collection< Link > selectedChains = new HashSet< Link >();
@@ -2060,6 +2119,14 @@ public class Mesh< T extends Region > {
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 ) {
final Collection< Link > chains = new HashSet< Link >( selected );
while ( !chains.isEmpty() ) {
@@ -2106,6 +2173,7 @@ public class Mesh< T extends Region > {
if ( interior.contains( head ) ) {
// Left handed
if ( right != null ) {
// TODO Remove this
throw new IllegalStateException( "Unterminated left edge!" );
}
right = new HalfEdge();
@@ -2144,6 +2212,7 @@ public class Mesh< T extends Region > {
} else {
// Right handed
if ( left != null ) {
// TODO Remove this
throw new IllegalStateException( "Unterminated right edge!" );
}
left = new HalfEdge();
@@ -2328,7 +2397,7 @@ public class Mesh< T extends Region > {
edge = edge.getPrev();
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 ) -> {
// Assume each region is marked by an upper edge going from right to left, up to down
if ( a.isZero() || b.isZero() ) {
// TODO Remove this
throw new IllegalStateException( "Zero length edge detected" );
}
if ( !( isPositive( a ) && isPositive( b ) ) ) {
// TODO Remove this
throw new IllegalStateException( "Negative edge detected" );
}
@@ -2419,6 +2490,7 @@ public class Mesh< T extends Region > {
} else {
leftGoing.add( edge );
if ( !edgeCollection.remove( edge.getSym() ) ) {
// TODO Remove this
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
// edge, and it cannot be part of either edge
if ( upper == null ) {
// TODO Remove this
throw new IllegalStateException( "Lower does not have an upper edge!" );
} if ( !interior.contains( upper.getSym() ) ) {
// TODO Remove this
throw new IllegalStateException( "Polygon is not simple!" );
} else if ( upper.getOrigin() == event || lower.getOrigin() == event ) {
// TODO Remove this
throw new IllegalStateException( "Vertex is invalid!" );
}
@@ -2464,6 +2539,7 @@ public class Mesh< T extends Region > {
eventMark = new MarkedEdge( lower, upper );
}
} else if ( rightGoing.isEmpty() && leftGoing.isEmpty() ) {
// TODO Remove this
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
// that must be because this vertex is left marked
if ( !leftGoing.isEmpty() ) {
// TODO Remove this
throw new IllegalStateException( "Vertex is not left-marked!" );
}
@@ -2583,6 +2660,7 @@ public class Mesh< T extends Region > {
if ( isPositive( edge ) ) {
edgeCollection.insert( edge );
} else {
// TODO Remove this
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.
if ( !rightMarked.isEmpty() ) {
// TODO Remove this
throw new IllegalStateException( "Unresolved right marked vertices!" );
}
@@ -2648,6 +2727,14 @@ public class Mesh< T extends Region > {
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 ) {
// Go down each monotone chain and connect the vertices where possible.
// 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
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 Set< HalfEdge > scanned = new HashSet< HalfEdge >();
for ( final HalfEdge edge : polygon.getEdges() ) {
@@ -2771,6 +2860,7 @@ public class Mesh< T extends Region > {
} while ( scanned.add( temp = temp.getNext() ) );
if ( points.size() != 3 ) {
// TODO Remove this
throw new IllegalStateException( "Not a triangle: " + points.size() );
}
polygons.add( new Polygon( points ) );
@@ -2821,28 +2911,6 @@ public class Mesh< T extends Region > {
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 ) {
if ( a == b ) {
return 0;

View File

@@ -1,7 +1,7 @@
package com.aaaaahhhhhhh.bananapuncher714.mesh;
/**
* A 2d cartesian coordinate
* A 2d Cartesian coordinate
*
* @author BananaPuncher714
*/

View File

@@ -1,5 +1,8 @@
package com.aaaaahhhhhhh.bananapuncher714.mesh;
/**
* A line segment
*/
public class Segment {
private final Point start;
private final Point end;

View File

@@ -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 {
}
}
}

View File

@@ -24,7 +24,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import javax.swing.JButton;
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" ) );
masterPlanes.add( planes.get( 177 ) );
// masterPlanes.add( planes.get( 177 ) );
masterPlanes.addAll( planes );
// masterPlanes.add( getTestPlane() );
@@ -969,9 +969,9 @@ public class MeshingTest2 extends JPanel {
g.setColor( Color.MAGENTA );
drawOnBoard( g, "Triangle count: " + data.polygons.size(), 0, -10 );
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 );
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();