Skip to content

Commit ccafb84

Browse files
committed
adjust skeleton selection (remove kendzi)
1 parent 2de71c9 commit ccafb84

File tree

2 files changed

+26
-106
lines changed

2 files changed

+26
-106
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
* Optimised `PGS_CirclePacking.tangencyPack()`. It's now around 1.5-2x faster and has higher precision.
2121
* `PGS_Conversion.roundVertexCoords()` now returns a rounded copy of the input (rather than mutating the input).
2222
* Outputs from `PGS_Conversion.toDualGraph()` will now always iterate deterministically on inputs with the same geometry but having a different structure.
23+
* `PGS_Contour.straightSkeleton()` now always uses a more robust approach (which has been sped up considerably too).
2324

2425
### Fixed
2526
* `PGS_Morphology.rounding()` no longer gives invalid results.

src/main/java/micycle/pgs/PGS_Contour.java

+25-106
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.Map;
1515
import java.util.Set;
1616
import java.util.stream.Collectors;
17+
import java.util.stream.Stream;
1718

1819
import javax.vecmath.Point3d;
1920

@@ -234,15 +235,26 @@ public static PShape chordalAxis(PShape shape) {
234235
* consisting of straight-line segments only. Roughly, it is the geometric graph
235236
* whose edges are the traces of vertices of shrinking mitered offset curves of
236237
* the polygon.
237-
*
238+
* <p>
239+
* For a single polygon, this method returns a GROUP PShape containing three
240+
* children:
241+
* <ul>
242+
* <li>Child 0: GROUP PShape consisting of skeleton faces.</li>
243+
* <li>Child 1: LINES PShape representing branches, which are lines connecting
244+
* the skeleton to the polygon's edge.</li>
245+
* <li>Child 2: LINES PShape composed of bones, depicting the pure straight
246+
* skeleton of the polygon.</li>
247+
* </ul>
248+
* <p>
249+
* For multi-polygons, the method returns a master GROUP PShape. This master
250+
* shape includes multiple skeleton GROUP shapes, each corresponding to a single
251+
* polygon and structured as described above.
252+
*
238253
* @param shape a single polygon (that can contain holes), or a multi polygon
239254
* (whose polygons can contain holes)
240-
* @return when the input is a single polygon, returns a GROUP PShape containing
241-
* 3 children: child 1 = GROUP PShape of skeleton faces; child 2 = LINES
242-
* PShape of branches (lines that connect skeleton to edge); child 3 =
243-
* LINES PShape of bones (the pure straight skeleton). For
244-
* multi-polygons, a master GROUP shape of skeleton GROUP shapes
245-
* (described above) is returned.
255+
*
256+
* @return PShape based on the input polygon structure, either as a single or
257+
* multi-polygon skeleton representation.
246258
*/
247259
public static PShape straightSkeleton(PShape shape) {
248260
final Geometry g = fromPShape(shape);
@@ -257,25 +269,7 @@ public static PShape straightSkeleton(PShape shape) {
257269
return shape;
258270
}
259271

260-
/**
261-
*
262-
* @param polygon a single polygon that can contain holes
263-
* @return
264-
*/
265272
private static PShape straightSkeleton(Polygon polygon) {
266-
/*
267-
* Kenzi implementation (since PGS 1.3.0) is much faster (~50x!) but can fail on
268-
* more complicated inputs. Therefore try Kenzi implementation first, but fall
269-
* back to Twak implementation if it fails.
270-
*/
271-
try {
272-
return straightSkeletonKendzi(polygon);
273-
} catch (Exception e) {
274-
return straightSkeletonTwak(polygon);
275-
}
276-
}
277-
278-
private static PShape straightSkeletonTwak(Polygon polygon) {
279273
if (polygon.getCoordinates().length > 1000) {
280274
polygon = (Polygon) DouglasPeuckerSimplifier.simplify(polygon, 2);
281275
}
@@ -303,7 +297,7 @@ private static PShape straightSkeletonTwak(Polygon polygon) {
303297
skeleton.skeleton(); // compute skeleton
304298

305299
skeleton.output.faces.values().forEach(f -> {
306-
final List<Point3d> vertices = f.getLoopL().iterator().next().asList();
300+
List<Point3d> vertices = f.getLoopL().iterator().next().stream().toList();
307301
List<PVector> faceVertices = new ArrayList<>();
308302

309303
for (int i = 0; i < vertices.size(); i++) {
@@ -352,95 +346,20 @@ private static PShape straightSkeletonTwak(Polygon polygon) {
352346
return lines;
353347
}
354348

355-
private static PShape straightSkeletonKendzi(Polygon polygon) {
356-
final LinearRing[] rings = new LinearRingIterator(polygon).getLinearRings();
357-
Set<Vector2dc> edgeCoordsSet = new HashSet<>();
358-
final List<Vector2dc> points = ringToVec(rings[0], edgeCoordsSet);
359-
final List<List<Vector2dc>> holes = new ArrayList<>();
360-
for (int i = 1; i < rings.length; i++) {
361-
holes.add(ringToVec(rings[i], edgeCoordsSet));
362-
}
363-
364-
final SkeletonOutput so = kendzi.math.geometry.skeleton.Skeleton.skeleton(points, holes, new SkeletonConfiguration());
365-
final PShape skeleton = new PShape(PConstants.GROUP);
366-
final PShape faces = new PShape(PConstants.GROUP);
367-
/*
368-
* Create PEdges first to prevent lines being duplicated in output shapes since
369-
* faces share branches and bones.
370-
*/
371-
final Set<PEdge> branchEdges = new HashSet<>();
372-
final Set<PEdge> boneEdges = new HashSet<>();
373-
so.getFaces().forEach(f -> {
374-
/*
375-
* q stores the index of second vertex of the face that is a shape vertex. This
376-
* is used to rotate f.getPoints() so that the vertices of every face PShape
377-
* begin at the shape edge.
378-
*/
379-
int q = 0;
380-
for (int i = 0; i < f.getPoints().size(); i++) {
381-
final Vector2dc p1 = f.getPoints().get(i);
382-
final Vector2dc p2 = f.getPoints().get((i + 1) % f.getPoints().size());
383-
final boolean a = edgeCoordsSet.contains(p1);
384-
final boolean b = edgeCoordsSet.contains(p2);
385-
if (a ^ b) { // branch (xor)
386-
branchEdges.add(new PEdge(p1.x(), p1.y(), p2.x(), p2.y()));
387-
q = i;
388-
} else {
389-
if (!a) { // bone
390-
boneEdges.add(new PEdge(p1.x(), p1.y(), p2.x(), p2.y()));
391-
} else {
392-
q = i;
393-
}
394-
}
395-
}
396-
397-
List<PVector> faceVertices = new ArrayList<>(f.getPoints().size());
398-
Collections.rotate(f.getPoints(), -q + 1);
399-
f.getPoints().forEach(p -> faceVertices.add(new PVector((float) p.x(), (float) p.y())));
400-
401-
PShape face = PGS_Conversion.fromPVector(faceVertices);
402-
face.setStroke(true);
403-
face.setStrokeWeight(2);
404-
face.setStroke(ColorUtils.composeColor(147, 112, 219));
405-
faces.addChild(face);
406-
});
407-
408-
final PShape bones = prepareLinesPShape(null, null, 4);
409-
boneEdges.forEach(e -> {
410-
bones.vertex(e.a.x, e.a.y);
411-
bones.vertex(e.b.x, e.b.y);
412-
});
413-
bones.endShape();
414-
415-
final PShape branches = prepareLinesPShape(ColorUtils.composeColor(40, 235, 180), null, null);
416-
branchEdges.forEach(e -> {
417-
branches.vertex(e.a.x, e.a.y);
418-
branches.vertex(e.b.x, e.b.y);
419-
});
420-
branches.endShape();
421-
422-
skeleton.addChild(faces);
423-
skeleton.addChild(branches);
424-
skeleton.addChild(bones);
425-
426-
return skeleton;
427-
}
428-
429349
/**
430-
* Generates a topographic-like isoline contour map from the shape's vertices.
431-
* The "elevation" (or z value) of points is the euclidean distance between a
432-
* point in the shape and the given "high" point.
350+
* Generates a topographic-like isoline contour map from the shape's vertices
351+
* and a given "high point". Isolines represent the "elevation", or euclidean
352+
* distance, between a location in the shape and the "high point".
433353
* <p>
434354
* Assigns each point feature a number equal to the distance between geometry's
435355
* centroid and the point.
436356
*
437-
* @param shape
357+
* @param shape the bounds in which to draw isolines
438358
* @param highPoint position of "high" point within the shape
439359
* @param intervalSpacing distance between successive isolines
440-
* @return PShape containing isolines linework
360+
* @return PShape containing isolines linework
441361
*/
442362
public static PShape isolines(PShape shape, PVector highPoint, double intervalSpacing) {
443-
444363
/*
445364
* Also See:
446365
* https://github.com/hageldave/JPlotter/blob/master/jplotter/src/main/java/

0 commit comments

Comments
 (0)