Partial refactor

This commit is contained in:
2026-03-16 18:35:19 -04:00
parent 14b1647c47
commit 95e374682b
93 changed files with 345 additions and 331 deletions

2
core/algorithm/README.md Normal file
View File

@@ -0,0 +1,2 @@
# MC-Mesh Core Algorithm
The fundamental standalone tesselation library

25
core/algorithm/pom.xml Normal file
View 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>

View File

@@ -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 );
}
}

View File

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

View File

@@ -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() );
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -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 );
}
}

View File

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

View File

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

View File

@@ -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 );
}
};
}
}

View File

@@ -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 );
}
}

View File

@@ -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() + "}";
}
}

View File

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

View File

@@ -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 );
}

View File

@@ -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 );
}

View File

@@ -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 );
}
}

View File

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