Partial refactor
This commit is contained in:
2
core/algorithm/README.md
Normal file
2
core/algorithm/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# MC-Mesh Core Algorithm
|
||||
The fundamental standalone tesselation library
|
||||
25
core/algorithm/pom.xml
Normal file
25
core/algorithm/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.aaaaahhhhhhh.bananapuncher714</groupId>
|
||||
<artifactId>mc-mesh-core</artifactId>
|
||||
<version>0.0.1</version>
|
||||
</parent>
|
||||
<artifactId>mc-mesh-core-algorithm</artifactId>
|
||||
<version>0.0.1</version>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.14.1</version>
|
||||
<configuration>
|
||||
<release>8</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm;
|
||||
|
||||
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 >();
|
||||
|
||||
public Point getStart() {
|
||||
return points.get( 0 );
|
||||
}
|
||||
|
||||
public Point getEnd() {
|
||||
return points.get( points.size() - 1 );
|
||||
}
|
||||
|
||||
public List< Point > getPoints() {
|
||||
return Collections.unmodifiableList( points );
|
||||
}
|
||||
|
||||
public List< Chain > getIntersections() {
|
||||
return Collections.unmodifiableList( intersections );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
/**
|
||||
* A set for easily checking if a set contains/does not contain a half-edge or its sym.
|
||||
*
|
||||
* @author BananaPuncher714
|
||||
*/
|
||||
public class EdgeSet extends LinkedHashSet< HalfEdge > {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 4042266887750818558L;
|
||||
|
||||
@Override
|
||||
public boolean add( HalfEdge edge ) {
|
||||
if ( !contains( edge ) ) {
|
||||
return super.add( edge );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove( Object object ) {
|
||||
if ( object instanceof HalfEdge ) {
|
||||
HalfEdge edge = ( HalfEdge ) object;
|
||||
final boolean r1 = super.remove( edge );
|
||||
final boolean r2 = super.remove( edge.getSym() );
|
||||
|
||||
if ( r1 && r2 ) {
|
||||
throw new IllegalStateException( "Removed edge was added twice!" );
|
||||
}
|
||||
|
||||
return r1 || r2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains( Object object ) {
|
||||
if ( object instanceof HalfEdge ) {
|
||||
HalfEdge edge = ( HalfEdge ) object;
|
||||
return super.contains( edge ) || super.contains( edge.getSym() );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm;
|
||||
|
||||
/**
|
||||
* A half-edge class used to represent a DCEL(doubly connected edge list): https://en.wikipedia.org/wiki/Doubly_connected_edge_list
|
||||
*
|
||||
* In this case, it is intended to be used for a 2d PSLG(planar straight-line graph), but can probably be repurposed
|
||||
* for some other nefarious use case.
|
||||
*
|
||||
* An edge is a line segment connecting an origin to a destination. A half-edge is a partial line segment containing only
|
||||
* the origin, but not the destination.
|
||||
*
|
||||
* Each half-edge structure contains:
|
||||
* - A reference to the origin
|
||||
* - A reference to the symmetrical, or opposite half-edge which is attached to the destination.
|
||||
* - A reference to the next clockwise half-edge of the opposite half edge. This can be used to
|
||||
* traverse counter-clockwise through the half-edges of a polygon.
|
||||
* - A reference to the previous edge, which is the next counter-clockwise edge of this half edge.
|
||||
* This can be used to traverse all the edges of a vertex, in counter clockwise order.
|
||||
*
|
||||
* For a given half-edge attached to two points that contain no other edges, the previous edge is
|
||||
* itself, and the next edge is the same as the opposite edge.
|
||||
*
|
||||
* There are 2 main operations that are used when manipulating half-edges to attach/detach them from
|
||||
* a vertex:
|
||||
* - splicing
|
||||
* - splitting
|
||||
*
|
||||
* Splicing is the most important of the 2 operations, by a large margin. Given two half-edges, A and B,
|
||||
* swap the previous edges, and set the next edge for both of the previous edges to the opposite half edge.
|
||||
* This has the desired effect of either detaching two edges if they are connected to the same vertex, or
|
||||
* attaching two separate half-edges together to the same vertex.
|
||||
*
|
||||
* Splitting is adding an edge between the opposite of an edge and attaching it to the destination. It
|
||||
* splits a single edge into two edges with a vertex in the middle.
|
||||
*
|
||||
* @author BananaPuncher714
|
||||
*/
|
||||
public class HalfEdge {
|
||||
/**
|
||||
* The origin
|
||||
*/
|
||||
protected Vertex origin;
|
||||
|
||||
/**
|
||||
* The symmetrical half-edge
|
||||
*/
|
||||
protected HalfEdge sym;
|
||||
/**
|
||||
* The half-edge counter-clockwise on the same origin
|
||||
*/
|
||||
protected HalfEdge prev;
|
||||
/**
|
||||
* The half-edge clockwise from the symmetrical half-edge
|
||||
*/
|
||||
protected HalfEdge next;
|
||||
|
||||
// TODO Consider adding a constructor with a origin and destination
|
||||
|
||||
public HalfEdge() {
|
||||
init();
|
||||
|
||||
// Whenever we create a new half-edge, we need to make
|
||||
// sure that it has a symmetrical half-edge.
|
||||
new HalfEdge( this );
|
||||
}
|
||||
|
||||
private HalfEdge( HalfEdge o ) {
|
||||
init();
|
||||
|
||||
sym = o;
|
||||
o.sym = this;
|
||||
|
||||
next = o;
|
||||
o.next = this;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
origin = new Vertex( this );
|
||||
prev = this;
|
||||
}
|
||||
|
||||
public Vertex getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the origin. Does not affect any additional
|
||||
*
|
||||
* @param vert
|
||||
* @return
|
||||
*/
|
||||
public HalfEdge setOrigin( Vertex vert ) {
|
||||
origin = vert;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HalfEdge getSym() {
|
||||
return sym;
|
||||
}
|
||||
|
||||
public HalfEdge getPrev() {
|
||||
return prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the previous half-edge. Does not update additional properties.
|
||||
*
|
||||
* @param edge The half-edge to set as the previous half-edge.
|
||||
* @return This half-edge
|
||||
*/
|
||||
public HalfEdge setPrev( HalfEdge edge ) {
|
||||
this.prev = edge;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HalfEdge getNext() {
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next half-edge. Does not update additional properties.
|
||||
*
|
||||
* @param edge The half-edge to set as the next half-edge.
|
||||
* @return This half-edge
|
||||
*/
|
||||
public HalfEdge setNext( HalfEdge edge ) {
|
||||
this.next = edge;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vertex getDest() {
|
||||
return sym.origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the difference between the destination position and the origin position
|
||||
*
|
||||
* @return A vector representing the destination minus the origin
|
||||
*/
|
||||
public Vector2d toVector2d() {
|
||||
return getDest().getPosition().subtracted( getOrigin().getPosition() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the origin and destination are the same
|
||||
*
|
||||
* @return If the origin is equal to the destination
|
||||
*/
|
||||
public boolean isZero() {
|
||||
return getOrigin().equals( getDest() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Split this half-edge in half and return the newly created
|
||||
* half-edge, as the next half-edge of this half-edge.
|
||||
*
|
||||
* @return A new half-edge with a default initialized origin.
|
||||
*/
|
||||
public HalfEdge split() {
|
||||
final HalfEdge edge = new HalfEdge();
|
||||
|
||||
splice( edge, getNext() );
|
||||
|
||||
splice( getSym(), getNext() );
|
||||
splice( getSym(), edge.getSym() );
|
||||
|
||||
edge.setOrigin( getDest() );
|
||||
edge.getOrigin().setEdge( edge );
|
||||
|
||||
getSym().setOrigin( edge.getDest() );
|
||||
getDest().setEdge( getSym() );
|
||||
|
||||
edge.getOrigin().setEdge( edge );
|
||||
|
||||
return edge.getSym();
|
||||
}
|
||||
|
||||
/**
|
||||
* Splice two half-edges together. A commutative operation.
|
||||
*
|
||||
* This does _not_ modify any vertices.
|
||||
*
|
||||
* @param a The first half-edge
|
||||
* @param b The second half-edge
|
||||
*/
|
||||
public static void splice( HalfEdge a, HalfEdge b ) {
|
||||
final HalfEdge ap = a.prev;
|
||||
final HalfEdge bp = b.prev;
|
||||
|
||||
a.prev = bp;
|
||||
b.prev = ap;
|
||||
|
||||
ap.sym.next = b;
|
||||
bp.sym.next = a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format( "HalfEdge{orig=%1$s,dest=%2$s}", getOrigin(), getDest() );
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm;
|
||||
|
||||
/**
|
||||
* A 2d Cartesian coordinate
|
||||
*
|
||||
* @author BananaPuncher714
|
||||
*/
|
||||
public class Point {
|
||||
protected double x, y;
|
||||
|
||||
public Point( final Point other ) {
|
||||
this( other.x, other.y );
|
||||
}
|
||||
|
||||
public Point( double x, double y ) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public Point setX( double x ) {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Point setY( double y ) {
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(x);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(y);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Point other = (Point) obj;
|
||||
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
|
||||
return false;
|
||||
if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format( "Point{x=%f,y=%f}", x, y );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A list of points denoting the shape of the polygon. May contain self intersections,
|
||||
* duplicate vertices, and overlapping edges.
|
||||
*
|
||||
* @author BananaPuncher714
|
||||
*/
|
||||
public class Polygon {
|
||||
protected List< Point > points;
|
||||
|
||||
public Polygon( List< Point > points ) {
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
public List< Point > getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public double area() {
|
||||
double area = 0;
|
||||
for ( int i = 0; i < points.size(); ++i ) {
|
||||
final Vector2d a = new Vector2d( points.get( i ) );
|
||||
final Vector2d b = new Vector2d( points.get( ( i + 1 ) % points.size() ) );
|
||||
area += a.cross( b );
|
||||
}
|
||||
return Math.abs( area ) / 2.0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm;
|
||||
|
||||
/**
|
||||
* A line segment
|
||||
*/
|
||||
public class Segment {
|
||||
private final Point start;
|
||||
private final Point end;
|
||||
|
||||
public Segment( Point start, Point end ) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public Point getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public Point getEnd() {
|
||||
return end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class SortedEdgeCollection< T > implements Iterable< T > {
|
||||
Map< T, Node > quickMap = new ConcurrentHashMap< T, Node >();
|
||||
Node head = new Node( null );
|
||||
|
||||
BiFunction< T, T, Boolean > isGreaterThan;
|
||||
|
||||
public SortedEdgeCollection( BiFunction< T, T, Boolean > compare ) {
|
||||
isGreaterThan = compare;
|
||||
}
|
||||
|
||||
public T bottom() {
|
||||
return head.upper == head ? null : head.upper.value;
|
||||
}
|
||||
|
||||
public T upper( T val ) {
|
||||
Node n = quickMap.get( val );
|
||||
return n == null ? null : n.upper.value;
|
||||
}
|
||||
|
||||
public T lower( T val ) {
|
||||
Node n = quickMap.get( val );
|
||||
return n == null ? null : n.lower.value;
|
||||
}
|
||||
|
||||
public boolean remove( T val ) {
|
||||
Node n = quickMap.remove( val );
|
||||
if ( n != null ) {
|
||||
n.lower.upper = n.upper;
|
||||
n.upper.lower = n.lower;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains( T val ) {
|
||||
return quickMap.containsKey( val );
|
||||
}
|
||||
|
||||
public void insert( T val ) {
|
||||
if ( !quickMap.containsKey( val ) ) {
|
||||
Node insertBefore = head.upper;
|
||||
// Find the first node that the value is NOT greater than
|
||||
// Otherwise, break and insert before
|
||||
while ( insertBefore.value != null && isGreaterThan.apply( val, insertBefore.value ) ) {
|
||||
insertBefore = insertBefore.upper;
|
||||
}
|
||||
|
||||
Node newNode = new Node( val );
|
||||
|
||||
newNode.lower = insertBefore.lower;
|
||||
newNode.lower.upper = newNode;
|
||||
newNode.upper = insertBefore;
|
||||
|
||||
insertBefore.lower = newNode;
|
||||
|
||||
quickMap.put( val, newNode );
|
||||
} else {
|
||||
// Should not really happen
|
||||
throw new IllegalStateException( "Tried to insert value twice" );
|
||||
}
|
||||
}
|
||||
|
||||
// Find the first value that is greater than the one provided
|
||||
public T searchUpper( T val ) {
|
||||
Node n = quickMap.get( val );
|
||||
if ( n == null ) {
|
||||
Node greater = head.upper;
|
||||
while ( greater.value != null && isGreaterThan.apply( val, greater.value ) ) {
|
||||
greater = greater.upper;
|
||||
}
|
||||
return greater.value;
|
||||
} else {
|
||||
return n.upper.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the highest value that is lower than the one provided
|
||||
public T searchLower( T val ) {
|
||||
Node n = quickMap.get( val );
|
||||
if ( n == null ) {
|
||||
Node lower = head.upper;
|
||||
while ( lower.value != null && isGreaterThan.apply( val, lower.value ) ) {
|
||||
lower = lower.upper;
|
||||
}
|
||||
return lower.lower.value;
|
||||
} else {
|
||||
return n.lower.value;
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
head = new Node( null );
|
||||
quickMap.clear();
|
||||
}
|
||||
|
||||
protected class Node {
|
||||
Node lower;
|
||||
Node upper;
|
||||
T value;
|
||||
|
||||
Node( T val ) {
|
||||
lower = this;
|
||||
upper = this;
|
||||
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator< T > iterator() {
|
||||
return new Iterator< T >() {
|
||||
Node current = head;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return current.upper != head;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
current = current.upper;
|
||||
return current.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
T val = current.value;
|
||||
current = current.lower;
|
||||
SortedEdgeCollection.this.remove( val );
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm;
|
||||
|
||||
/**
|
||||
* A basic 2d vector implementation.
|
||||
*
|
||||
* @author BananaPuncher714
|
||||
*/
|
||||
public class Vector2d extends Point {
|
||||
public Vector2d() {
|
||||
this( 0, 0 );
|
||||
}
|
||||
|
||||
public Vector2d( double x, double y ) {
|
||||
super( x, y );
|
||||
}
|
||||
|
||||
public Vector2d( Point o ) {
|
||||
this( o.x, o.y );
|
||||
}
|
||||
|
||||
public double angle( Point o ) {
|
||||
return Math.atan2( cross( o ), dot( o ) );
|
||||
}
|
||||
|
||||
public double cross( Point o ) {
|
||||
return ( x * o.y ) - ( y * o.x );
|
||||
}
|
||||
|
||||
public double lengthSquared() {
|
||||
return dot( this );
|
||||
}
|
||||
|
||||
public double length() {
|
||||
return Math.sqrt( lengthSquared() );
|
||||
}
|
||||
|
||||
public double distanceSquared( Point o ) {
|
||||
return distanceSquared( this, o );
|
||||
}
|
||||
|
||||
public double distance( Point o ) {
|
||||
return distance( this, o );
|
||||
}
|
||||
|
||||
public double dot( Point o ) {
|
||||
return dot( this, o );
|
||||
}
|
||||
|
||||
public Vector2d add( double v ) {
|
||||
x += v;
|
||||
y += v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector2d added( double v ) {
|
||||
return new Vector2d( x + v, y + v );
|
||||
}
|
||||
|
||||
public Vector2d add( Point o ) {
|
||||
x += o.x;
|
||||
y += o.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector2d added( Point o ) {
|
||||
return add( this, o );
|
||||
}
|
||||
|
||||
public Vector2d absolute() {
|
||||
x = Math.abs( x );
|
||||
y = Math.abs( y );
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector2d absoluteOf() {
|
||||
return new Vector2d( Math.abs( x ), Math.abs( y ) );
|
||||
}
|
||||
|
||||
public Vector2d subtract( double v ) {
|
||||
x -= v;
|
||||
y -= v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector2d subtracted( double v ) {
|
||||
return new Vector2d( x - v, y - v );
|
||||
}
|
||||
|
||||
public Vector2d subtract( Point o ) {
|
||||
x -= o.x;
|
||||
y -= o.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector2d subtracted( Point o ) {
|
||||
return new Vector2d( x - o.x, y - o.y );
|
||||
}
|
||||
|
||||
public Vector2d multiply( double v ) {
|
||||
x *= v;
|
||||
y *= v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector2d multiplied( double v ) {
|
||||
return new Vector2d( x * v, y * v );
|
||||
}
|
||||
|
||||
public Vector2d multiply( Point o ) {
|
||||
x *= o.x;
|
||||
y *= o.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector2d multiplied( Point o ) {
|
||||
return multiply( this, o );
|
||||
}
|
||||
|
||||
public Vector2d divide( double v ) {
|
||||
x /= v;
|
||||
y /= v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector2d divided( double v ) {
|
||||
return new Vector2d( x / v, y / v );
|
||||
}
|
||||
|
||||
public Vector2d divide( Point o ) {
|
||||
x /= o.x;
|
||||
y /= o.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector2d divided( Point o ) {
|
||||
return new Vector2d( x / o.x, y / o.y );
|
||||
}
|
||||
|
||||
public Vector2d normalize() {
|
||||
return divide( length() );
|
||||
}
|
||||
|
||||
public Vector2d normalized() {
|
||||
return divided( length() );
|
||||
}
|
||||
|
||||
public boolean isZero() {
|
||||
return x == 0 && y == 0;
|
||||
}
|
||||
|
||||
public static double dot( Point a, Point b ) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
public static Vector2d add( Point a, Point b ) {
|
||||
return new Vector2d( a.x + b.x, a.y + b.y );
|
||||
}
|
||||
|
||||
public static Vector2d multiply( Point a, Point b ) {
|
||||
return new Vector2d( a.x * b.x, a.y * b.y );
|
||||
}
|
||||
|
||||
public static double distanceSquared( Vector2d a, Point b ) {
|
||||
return a.subtracted( b ).lengthSquared();
|
||||
}
|
||||
|
||||
public static double distance( Vector2d a, Point b ) {
|
||||
return Math.sqrt( distanceSquared( a, b ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(x);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(y);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Vector2d other = (Vector2d) obj;
|
||||
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
|
||||
return false;
|
||||
if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format( "Vector2d{x=%f,y=%f}", x, y );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* The vertex class is used to represent a point in space. For our use cases, it is going to be 2d.
|
||||
*
|
||||
* A vertex contains two pieces of information:
|
||||
* - The position
|
||||
* - A half-edge which is attached to this vertex
|
||||
*
|
||||
* To traverse over all the edges which are connected to this vertex,
|
||||
* loop over the previous edge of the half edge until all of the edges
|
||||
* have been reached at least once.
|
||||
*
|
||||
* Whenever performing a splicing operation on an edge, it is a good
|
||||
* idea to update all edges attached to this vertex to point to this
|
||||
* vertex, in case if they are pointing at a different one.
|
||||
*
|
||||
* @author BananaPuncher714
|
||||
*/
|
||||
public class Vertex implements Iterable< HalfEdge > {
|
||||
protected HalfEdge edge;
|
||||
protected Vector2d position;
|
||||
|
||||
public Vertex( final HalfEdge edge ) {
|
||||
this( edge, new Vector2d() );
|
||||
}
|
||||
|
||||
public Vertex( final HalfEdge edge, final Vector2d position ) {
|
||||
setEdge( edge );
|
||||
setPosition( position );
|
||||
}
|
||||
|
||||
public HalfEdge getEdge() {
|
||||
return edge;
|
||||
}
|
||||
|
||||
public Vertex setEdge( HalfEdge edge ) {
|
||||
this.edge = edge;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector2d getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public Vertex setPosition( Vector2d vec ) {
|
||||
this.position = vec;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* O(n)
|
||||
*/
|
||||
public int size() {
|
||||
int size = 0;
|
||||
for ( final Iterator< HalfEdge > it = iterator(); it.hasNext(); it.next() ) {
|
||||
++size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all attached half-edges to point to this vertex
|
||||
* as the origin
|
||||
*/
|
||||
public void update() {
|
||||
HalfEdge edge = this.edge;
|
||||
do {
|
||||
edge.setOrigin( this );
|
||||
} while ( ( edge = edge.getPrev() ) != this.edge );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator< HalfEdge > iterator() {
|
||||
return new Iterator< HalfEdge >() {
|
||||
private final HalfEdge start = edge;
|
||||
private HalfEdge index = null;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index != start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HalfEdge next() {
|
||||
if ( index == null ) {
|
||||
index = start.getPrev();
|
||||
return start;
|
||||
} else {
|
||||
final HalfEdge curr = index;
|
||||
index = index.getPrev();
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Vertex other = (Vertex) obj;
|
||||
if (position == null) {
|
||||
if (other.position != null)
|
||||
return false;
|
||||
} else if (!position.equals(other.position))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vertex{x=" + position.getX() + ",y=" + position.getY() + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm.region;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
public class CompositeRegionRule< T extends Region > implements RegionRule< T > {
|
||||
final protected List< RegionRule< T > > rules;
|
||||
|
||||
public CompositeRegionRule() {
|
||||
this.rules = new ArrayList< RegionRule< T > >();
|
||||
}
|
||||
|
||||
public CompositeRegionRule( final List< RegionRule< T > > rules ) {
|
||||
this.rules = new ArrayList< RegionRule< T > >( rules );
|
||||
}
|
||||
|
||||
public CompositeRegionRule< T > addRule( final RegionRule< T > rule ) {
|
||||
rules.add( rule );
|
||||
return this;
|
||||
}
|
||||
|
||||
public List< RegionRule< T > > getRules() {
|
||||
return rules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T apply( T region ) {
|
||||
for ( final RegionRule< T > rule : rules ) {
|
||||
region = rule.apply( region );
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionRule< T > inverse() {
|
||||
CompositeRegionRule< T > rule = new CompositeRegionRule< T >();
|
||||
for ( ListIterator< RegionRule< T > > it = rules.listIterator(); it.hasPrevious(); ) {
|
||||
rule.rules.add( it.previous().inverse() );
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionRule< T > apply( RegionRule< T > rule ) {
|
||||
final CompositeRegionRule< T > copy = new CompositeRegionRule< T >( rules );
|
||||
if ( rule instanceof CompositeRegionRule ) {
|
||||
final CompositeRegionRule< T > other = ( CompositeRegionRule< T > ) rule;
|
||||
for ( RegionRule< T > r : other.getRules() ) {
|
||||
copy.addRule( r );
|
||||
}
|
||||
} else {
|
||||
copy.addRule( rule );
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm.region;
|
||||
|
||||
/**
|
||||
* A continuous portion in space that can be clearly defined as "inside" or "outside".
|
||||
*
|
||||
* The equality check is mainly used to guarantee winding rule consistency.
|
||||
*
|
||||
* @author BananaPuncher714
|
||||
*/
|
||||
public abstract class Region {
|
||||
// Represents the area under a wing
|
||||
public abstract boolean isInterior();
|
||||
public abstract boolean equals( Object other );
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm.region;
|
||||
|
||||
/**
|
||||
* A region rule describes how a region is modified, particularly when crossing a border
|
||||
* between a known region to an unknown region. The inverse region rule must provide a
|
||||
* symmetric change such that applying the region rule then the inverse, the resulting region
|
||||
* must be equivalent to the original region. Normally, the same rule is applied to all polygons
|
||||
* that need to be tessellated, such as the odd number rule, but if your polygons conform to different or
|
||||
* more complex interior/exterior critera, then you can easily set different rules per polygon.
|
||||
* An example might be if you have two sets of polygons that you would like to tessellate
|
||||
* together, and distinctly mark regions where they might overlap. You can use a combination
|
||||
* of two odd number rules to properly mark the boundaries.
|
||||
*
|
||||
* For more information:
|
||||
* - https://en.wikipedia.org/wiki/Nonzero-rule
|
||||
*
|
||||
* @author BananaPuncher714
|
||||
*
|
||||
* @param <T> A region type that can be modified by this rule
|
||||
*/
|
||||
public interface RegionRule< T extends Region > {
|
||||
// Apply this rule to the region, and return a new region
|
||||
T apply( T region );
|
||||
// Get the inverse of this rule
|
||||
RegionRule< T > inverse();
|
||||
// Apply this rule to the given rule, and return a new rule
|
||||
RegionRule< T > apply( RegionRule< T > rule );
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm.region.simple;
|
||||
|
||||
import com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm.region.CompositeRegionRule;
|
||||
import com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm.region.RegionRule;
|
||||
|
||||
/**
|
||||
* A simple winding number rule.
|
||||
*
|
||||
* Changes the winding number for a given region.
|
||||
*
|
||||
* @author BananaPuncher714
|
||||
*
|
||||
*/
|
||||
public class RegionRuleWinding implements RegionRule< RegionSimple > {
|
||||
public static final RegionRuleWinding CLOCKWISE = new RegionRuleWinding( 1 );
|
||||
public static final RegionRuleWinding COUNTER_CLOCKWISE = CLOCKWISE.inverse();
|
||||
|
||||
private final int amount;
|
||||
private RegionRuleWinding inverse;
|
||||
|
||||
private RegionRuleWinding( final int amount ) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionSimple apply( final RegionSimple region ) {
|
||||
RegionSimple copy = new RegionSimple( region );
|
||||
copy.increment( amount );
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionRuleWinding inverse() {
|
||||
if ( inverse == null ) {
|
||||
inverse = new RegionRuleWinding( -amount );
|
||||
inverse.inverse = this;
|
||||
}
|
||||
return inverse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionRule< RegionSimple > apply( final RegionRule< RegionSimple > rule ) {
|
||||
if ( rule instanceof RegionRuleWinding ) {
|
||||
final RegionRuleWinding other = ( RegionRuleWinding ) rule;
|
||||
return new RegionRuleWinding( amount + other.amount );
|
||||
} else {
|
||||
final CompositeRegionRule< RegionSimple > composite = new CompositeRegionRule< RegionSimple >();
|
||||
composite.addRule( this ).addRule( rule );
|
||||
return composite;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format( "RegionRuleWinding{amount=%1$d}", amount );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm.region.simple;
|
||||
|
||||
import com.aaaaahhhhhhh.bananapuncher714.mesh.algorithm.region.Region;
|
||||
|
||||
/**
|
||||
* A region with a winding number. The interior-ness of
|
||||
* this region is determined by the GLU winding rule:
|
||||
*
|
||||
* https://www.songho.ca/opengl/gl_tessellation.html#winding
|
||||
*
|
||||
* @author BananaPuncher714
|
||||
*/
|
||||
public class RegionSimple extends Region {
|
||||
protected int windingNumber;
|
||||
protected GluWindingRule rule;
|
||||
|
||||
public RegionSimple( RegionSimple o ) {
|
||||
windingNumber = o.windingNumber;
|
||||
rule = o.rule;
|
||||
}
|
||||
|
||||
public RegionSimple( GluWindingRule rule ) {
|
||||
this.windingNumber = 0;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterior() {
|
||||
switch ( rule ) {
|
||||
case ODD:
|
||||
return ( windingNumber & 1 ) == 1;
|
||||
case NONZERO:
|
||||
return windingNumber != 0;
|
||||
case POSITIVE:
|
||||
return windingNumber > 0;
|
||||
case NEGATIVE:
|
||||
return windingNumber < 0;
|
||||
case ABS_GEQ_TWO:
|
||||
return Math.abs( windingNumber ) >= 2;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
RegionSimple other = (RegionSimple) obj;
|
||||
if (rule != other.rule)
|
||||
return false;
|
||||
if (windingNumber != other.windingNumber)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format( "Region{number=%1$d,rule=%2$s}", windingNumber, rule );
|
||||
}
|
||||
|
||||
public int getWindingNumber() {
|
||||
return windingNumber;
|
||||
}
|
||||
|
||||
public RegionSimple setWindingNumber( int number ) {
|
||||
this.windingNumber = number;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int increment( final int amount ) {
|
||||
return windingNumber += amount;
|
||||
}
|
||||
|
||||
public enum GluWindingRule {
|
||||
ODD,
|
||||
NONZERO,
|
||||
POSITIVE,
|
||||
NEGATIVE,
|
||||
ABS_GEQ_TWO
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user