Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PGS 2.1 #135

Draft
wants to merge 24 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ All notable changes to PGS will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Dates are *YYYY-MM-DD*.

## **2.1** *(2025-xx-xx)*

### Added
* `smoothLaneRiesenfeld` to `PGS_Morphology`. Smooths a shape using Lane-Riesenfeld curve subdivision with 4-point refinement to reduce contraction.
* Additional method signature for `PGS_Conversion.roundVertexCoords()` that accepts a number of decimal places.
* `interiorAngles()` to `PGS_ShapePredicates`. Calculates all interior angles of a polygon.
* `forEachShape()` and `forEachShapeWithIndex()`* to `PGS_Processing`. Applies a specified transformation function of a desired type `T` to each child of the given PShape, returning a list of `T` (*additionally with child's index).
* `maximumInscribedTriangle()` to `PGS_Optimisation`. Finds an approximate largest area triangle (of arbitrary orientation) contained within a polygon.
* `closestPoint()` to `PGS_Optimisation`. Finds the closest point in a collection of points to a specified point.
* `distanceTree()` to `PGS_Contour`. Generates a tree structure representing the shortest paths from a start point to all other vertices in a mesh.

### Changes
* Optimised `PGS_CirclePacking.tangencyPack()`. It's now around 1.5-2x faster and has higher precision.
* `PGS_Conversion.roundVertexCoords()` now returns a rounded copy of the input (rather than mutating the input).
* Outputs from `PGS_Conversion.toDualGraph()` will now always iterate deterministically on inputs with the same geometry but having a different structure.
* `PGS_Contour.straightSkeleton()` now always uses a more robust approach (which has been sped up considerably too).

### Fixed
* `PGS_Morphology.rounding()` no longer gives invalid results.
* `PGS_ShapePredicates.elongation()` now correctly measures shape elongation (previously inverted, now returns 1 for highly elongated shapes).
* `PGS_Conversion.toGraph()` now processes `LINES` shapes correctly.
* `PGS_Meshing.urquhartFaces()` no longer errors on triangulation inputs with no constraints.

### Removed

## **2.0** *(2025-01-11)*

**NOTE: Beginning at v2.0, PGS is built with Java 17.**
Expand Down
28 changes: 10 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Library functionality is split over the following classes:
* `PGS_Contour`
* Methods that produce various contours from shapes: medial axes, straight skeletons, offset curves, etc.
* `PGS_Conversion`
* Conversion between *Processing* PShapes and *JTS* Geometries (amongst other formats)
* Conversion between *Processing* PShapes and *JTS* Geometries (amongst other formats).
* `PGS_Hull`
* Convex and concave hulls of polygons and point sets.
* `PGS_Meshing`
Expand Down Expand Up @@ -172,23 +172,15 @@ Much of the functionality (but by no means all) is demonstrated below:


### Metrics
* Length/perimeter
* Width & Height
* Diameter
* Circularity
* Similarity
* Sphericity
* Elongation
* Density
* Holes
* Maximum interior angle
* Is simple?
* Is convex?
* Equal? (structural and topological equivalence)
* Distance
* Area
* Centroid
* Median

| <!-- --> | <!-- --> | <!-- --> |
|-------------|-------------|-------------|
| Length/perimeter | Similarity | Is simple? |
| Width & Height | Sphericity | Is convex? |
| Diameter | Elongation | Equal? (structural and topological equivalence) |
| Circularity | Density | Distance |
| Area | Holes | Centroid |
| Interior angles | Maximum interior angle | Median |

## *Contour*

Expand Down
18 changes: 15 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>micycle</groupId>
<artifactId>PGS</artifactId>
<version>2.0</version>
<version>2.1-SNAPSHOT</version>
<name>Processing Geometry Suite</name>
<description>Geometric algorithms for Processing</description>

Expand Down Expand Up @@ -88,7 +88,7 @@
<profile>
<id>uber-jar</id>
<build>
<finalName>${fatJarName}</finalName>
<finalName>${fatJarName}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down Expand Up @@ -215,7 +215,7 @@
<dependency>
<groupId>com.github.twak</groupId>
<artifactId>campskeleton</artifactId>
<version>8df5b241d5</version>
<version>453a603706</version>
</dependency>
<dependency>
<groupId>org.jgrapht</groupId>
Expand Down Expand Up @@ -274,6 +274,12 @@
<groupId>com.github.micycle1</groupId>
<artifactId>space-filling-curves</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>quil</groupId>
<artifactId>processing-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.micycle1</groupId>
Expand All @@ -289,6 +295,12 @@
<groupId>com.github.micycle1</groupId>
<artifactId>TrapMap</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>quil</groupId>
<artifactId>processing-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>it.unimi.dsi</groupId>
Expand Down
Binary file modified resources/contour/distanceField.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/contour/isolines.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/morphology/round.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 80 additions & 21 deletions src/main/java/micycle/pgs/PGS.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package micycle.pgs;

import static micycle.pgs.PGS_Conversion.fromPShape;
import static micycle.pgs.PGS_Conversion.toPShape;
import static processing.core.PConstants.GROUP;
import static processing.core.PConstants.LINES;
import static processing.core.PConstants.ROUND;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -12,6 +16,7 @@
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.UnaryOperator;

import org.jgrapht.graph.SimpleWeightedGraph;
import org.locationtech.jts.geom.Coordinate;
Expand Down Expand Up @@ -196,31 +201,26 @@ static final float getPShapeStrokeWeight(final PShape sh) {
}

/**
* Requires a closed hole
*
* @param points
* @return
* For Processing's y-axis down system, a negative area from the shoelace
* formula in the function's logic will actually correspond to what is visually
* counter-clockwise in Processing.
*/
static final boolean isClockwise(List<PVector> points) {
boolean closed = true;
if (points.get(0).equals(points.get(points.size() - 1))) {
closed = false;
points.add(points.get(0)); // mutate list
}
double area = 0;
static boolean isClockwise(final List<PVector> points) {
if (points == null || points.size() < 3) {
throw new IllegalArgumentException("Polygon must have at least 3 points.");
}

for (int i = 0; i < (points.size()); i++) {
int j = (i + 1) % points.size();
area += points.get(i).x * points.get(j).y;
area -= points.get(j).x * points.get(i).y;
}
double area = 0;
int n = points.size();

if (!closed) {
points.remove(points.size() - 1); // revert mutation
}
for (int i = 0; i < n; i++) {
PVector p1 = points.get(i);
PVector p2 = points.get((i + 1) % n);
area += (p1.x * p2.y - p2.x * p1.y);
}

return (area < 0);
}
return area < 0; // negative area means clockwise (in standard y-up system)
}

/**
* Nodes (optional) then polygonizes a set of line segments.
Expand Down Expand Up @@ -557,4 +557,63 @@ public void remove() {
}
}

/**
* Applies a given function to all lineal elements (LineString and LinearRing)
* in the input PShape. The function processes each LineString or LinearRing and
* returns a modified LineString. The method preserves the structure of the
* input geometry, including exterior-hole relationships in polygons and
* multi-geometries.
*
* @param shape The input PShape containing lineal elements to process.
* @param function A UnaryOperator that takes a LineString or LinearRing and
* returns a modified LineString.
* @return A new PShape with the processed lineal elements. Returns an empty
* PShape for unsupported geometry types.
* @since 2.1
*/
static PShape applyToLinealGeometries(PShape shape, UnaryOperator<LineString> function) {
Geometry g = fromPShape(shape);
switch (g.getGeometryType()) {
case Geometry.TYPENAME_GEOMETRYCOLLECTION :
case Geometry.TYPENAME_MULTIPOLYGON :
case Geometry.TYPENAME_MULTILINESTRING :
PShape group = new PShape(GROUP);
for (int i = 0; i < g.getNumGeometries(); i++) {
group.addChild(applyToLinealGeometries(toPShape(g.getGeometryN(i)), function));
}
return group;
case Geometry.TYPENAME_LINEARRING :
case Geometry.TYPENAME_POLYGON :
// Preserve exterior-hole relations
LinearRing[] rings = new LinearRingIterator(g).getLinearRings();
LinearRing[] processedRings = new LinearRing[rings.length];
for (int i = 0; i < rings.length; i++) {
LinearRing ring = rings[i];
LineString out = function.apply(ring);
if (out.isClosed()) {
processedRings[i] = GEOM_FACTORY.createLinearRing(out.getCoordinates());
} else {
System.err.println("Output LineString is not closed. Closing it automatically.");
Coordinate[] closedCoords = Arrays.copyOf(out.getCoordinates(), out.getNumPoints() + 1);
closedCoords[closedCoords.length - 1] = closedCoords[0]; // Close the ring
processedRings[i] = GEOM_FACTORY.createLinearRing(closedCoords);
}
}
if (processedRings.length == 0) {
return new PShape();
}
LinearRing[] holes = null;
if (processedRings.length > 1) {
holes = Arrays.copyOfRange(processedRings, 1, processedRings.length);
}
return toPShape(GEOM_FACTORY.createPolygon(processedRings[0], holes));
case Geometry.TYPENAME_LINESTRING :
LineString l = (LineString) g;
LineString out = function.apply(l);
return toPShape(out);
default :
return new PShape(); // Return empty (so element is invisible if not processed)
}
}

}
6 changes: 4 additions & 2 deletions src/main/java/micycle/pgs/PGS_Coloring.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
* @since 1.2.0
*/
public final class PGS_Coloring {

public static long SEED = 1337;

private PGS_Coloring() {
}
Expand Down Expand Up @@ -256,10 +258,10 @@ private static Coloring<PShape> findColoring(Collection<PShape> shapes, Coloring
break;
case RLF_BRUTE_FORCE_4COLOR :
int iterations = 0;
long seed = 1337;
long seed = SEED; // init as default
do {
coloring = new RLFColoring<>(graph, seed).getColoring();
seed = ThreadLocalRandom.current().nextLong();
seed = ThreadLocalRandom.current().nextLong(); // randomise seed
iterations++;
} while (coloring.getNumberColors() > 4 && iterations < 250);
break;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/micycle/pgs/PGS_Construction.java
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ public static PShape createRandomSFCurve(int nColumns, int nRows, double cellWid
* @param cellWidth visual/pixel width of each cell
* @param cellHeight visual/pixel width of each cell
* @param seed random seed
* @return a stroked PATH PShape
* @return a mitered stroked PATH PShape
* @see #createRandomSFCurve(int, int, double, double)
* @since 1.4.0
*/
Expand Down
Loading
Loading