Skip to content

Commit 80f7af3

Browse files
committed
Add fieldWarp and radialWarp
1 parent 771c155 commit 80f7af3

8 files changed

Lines changed: 158 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
- `polygonizeLines()` to `PGS_Processing`. Computes the polygonal faces formed by a set of intersecting line segments.
1111
- Additional method signature for `PGS_Processing.generateRandomGridPoints()` that accepts a random seed.
12+
- `fieldWarp()` to `PGS_Morphology`. Warps a shape by displacing vertices according to a 2D noise vector field.
13+
- `radialWarp()` to `PGS_Morphology`. Warps a shape by displacing vertices along a line between each vertex and the shape centroid.
1214
- Expand PGS_Conversion to support conversion between:
1315
- `TRIANGLES` PShapeâžœJTS `MultiPolygon`
1416
- `QUADS` PShapeâžœJTS `MultiPolygon`

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,17 @@ Much of the functionality (but by no means all) is demonstrated below:
244244
<td valign="top" ><img src="resources/morphology/round2.gif"></td>
245245
</tr>
246246

247+
<tr>
248+
<td align="center" valign="center" colspan="2"><b>Radial Warp</td>
249+
<td align="center" valign="center" colspan="2"><b>Field Warp</td>
250+
</tr>
251+
<tr>
252+
<td valign="top" width="25%"><img src="resources/morphology/radialWarp1.gif"></td>
253+
<td valign="top" width="25%"><img src="resources/morphology/radialWarp2.gif"></td>
254+
<td valign="top" width="25%"><img src="resources/morphology/fieldWarp.gif"></td>
255+
<td valign="top" width="25%"><img src="resources/morphology/fieldWarp2.gif"></td>
256+
</tr>
257+
247258
<tr>
248259
<td align="center" valign="center"><b>Simplification</td>
249260
<td align="center" valign="center" colspan="2"><b>Chaikin Cutting</td>

pom.xml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@
136136
<version>24.4</version>
137137
</dependency>
138138
<dependency>
139-
<groupId>org.locationtech.jts</groupId>
140-
<artifactId>jts-core</artifactId>
141-
<version>1.18.1</version>
139+
<groupId>com.github.locationtech</groupId>
140+
<artifactId>jts</artifactId>
141+
<version>6cf20ff392</version>
142142
</dependency>
143143
<dependency>
144144
<groupId>com.github.twak</groupId>
@@ -153,7 +153,7 @@
153153
<dependency>
154154
<groupId>org.tinfour</groupId>
155155
<artifactId>TinfourCore</artifactId>
156-
<version>2.1.6</version>
156+
<version>2.1.7</version>
157157
</dependency>
158158
<dependency>
159159
<groupId>io.github.earcut4j</groupId>
@@ -197,6 +197,11 @@
197197
<artifactId>balaban-intersection</artifactId>
198198
<version>1.0.0</version>
199199
</dependency>
200+
<dependency>
201+
<groupId>com.github.micycle1</groupId>
202+
<artifactId>UniformNoise</artifactId>
203+
<version>1.1</version>
204+
</dependency>
200205
</dependencies>
201206

202207
</project>

resources/morphology/fieldWarp.gif

1.31 MB
Loading
1.27 MB
Loading
740 KB
Loading
711 KB
Loading

src/main/java/micycle/pgs/PGS_Morphology.java

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
import static micycle.pgs.PGS_Conversion.fromPShape;
44
import static micycle.pgs.PGS_Conversion.toPShape;
55

6+
import java.util.List;
7+
68
import org.geotools.geometry.jts.JTS;
9+
import org.locationtech.jts.densify.Densifier;
710
import org.locationtech.jts.geom.Geometry;
811
import org.locationtech.jts.geom.LineString;
12+
import org.locationtech.jts.geom.Point;
913
import org.locationtech.jts.geom.Polygon;
14+
import org.locationtech.jts.geom.util.GeometryFixer;
1015
import org.locationtech.jts.simplify.DouglasPeuckerSimplifier;
1116
import org.locationtech.jts.simplify.TopologyPreservingSimplifier;
1217
import org.locationtech.jts.simplify.VWSimplifier;
@@ -17,7 +22,9 @@
1722
import micycle.pgs.utility.GaussianLineSmoothing;
1823
import processing.core.PConstants;
1924
import processing.core.PShape;
25+
import processing.core.PVector;
2026
import uk.osgb.algorithm.minkowski_sum.Minkowski_Sum;
27+
import micycle.uniformnoise.UniformNoise;
2128

2229
/**
2330
* Methods that affect the geometry or topology of shapes.
@@ -214,4 +221,133 @@ public static PShape chaikinCut(PShape shape, double ratio, int iterations) {
214221
return cut;
215222
}
216223

224+
/**
225+
* Warps/perturbs a shape by displacing vertices along a line between each
226+
* vertex and the shape centroid.
227+
*
228+
* <p>
229+
* Inputs may be densified before warping.
230+
*
231+
* @param shape a polygonal shape
232+
* @param magnitude magnitude of the displacement. The value defines the
233+
* maximum euclidean displacement of a vertex compared to the
234+
* shape centroid
235+
* @param warpOffset offset angle that determines at which angle to begin the
236+
* displacement.
237+
* @param densify whether to densify the shape (using distance=1) before
238+
* warping. When true, shapes with long edges will undergo
239+
* warping along the whole edge (rather than only at the
240+
* original vertices).
241+
* @return
242+
*/
243+
public static PShape radialWarp(PShape shape, double magnitude, double warpOffset, boolean densify) {
244+
Geometry g = fromPShape(shape);
245+
if (!g.getGeometryType().equals(Geometry.TYPENAME_POLYGON)) {
246+
System.err.println("radialWarp() expects (single) polygon input. The geometry resolved to a " + g.getGeometryType());
247+
return shape;
248+
}
249+
250+
final Point point = g.getCentroid();
251+
final PVector c = new PVector((float) point.getX(), (float) point.getY());
252+
253+
final List<PVector> coords;
254+
255+
if (densify) {
256+
final Densifier d = new Densifier(fromPShape(shape));
257+
d.setDistanceTolerance(1);
258+
d.setValidate(false);
259+
coords = PGS_Conversion.toPVector(toPShape(d.getResultGeometry()));
260+
} else {
261+
coords = PGS_Conversion.toPVector(shape);
262+
}
263+
264+
final UniformNoise noise = new UniformNoise(1337);
265+
coords.forEach(coord -> {
266+
PVector heading = PVector.sub(coord, c); // vector from center to each vertex
267+
final double angle = heading.heading() + warpOffset;
268+
float perturbation = noise.uniformNoise(Math.cos(angle), Math.sin(angle));
269+
perturbation -= 0.5f; // [0...1] -> [-0.5...0.5]
270+
perturbation *= magnitude * 2;
271+
coord.add(heading.normalize().mult(perturbation)); // add perturbation to vertex
272+
});
273+
return PGS_Conversion.fromPVector(coords);
274+
}
275+
276+
/**
277+
* Warps/perturbs a shape by displacing vertices according to a 2D noise vector
278+
* field.
279+
*
280+
* <p>
281+
* Inputs may be densified before warping.
282+
*
283+
* @param shape a polygonal shape
284+
* @param magnitude magnitude of the displacement (acting as noise value
285+
* multiplier). The value defines the maximum displacement of
286+
* a vertex in the both x and y axes.
287+
* @param noiseScale the scale of the 2D noise vector field. This affects how of
288+
* the coarseness of warping. Smaller values (~0.2) lead to
289+
* more fine warping (at edges), whereas larger values (~2)
290+
* affect the shape geometry at a larger scale.
291+
* @param densify whether to densify the shape (using distance=1) before
292+
* warping. When true, shapes with long edges will undergo
293+
* warping along the whole edge (rather than only at the
294+
* original vertices).
295+
* @return
296+
* @see #fieldWarp(PShape, double, double, double, boolean, int)
297+
*/
298+
public static PShape fieldWarp(PShape shape, double magnitude, double noiseScale, boolean densify) {
299+
return fieldWarp(shape, magnitude, noiseScale, 0, densify, 1337);
300+
}
301+
302+
/**
303+
* Warps/perturbs a shape by displacing vertices according to a 2D noise vector
304+
* field.
305+
*
306+
* <p>
307+
* Inputs may be densified before warping.
308+
*
309+
* @param shape a polygonal shape
310+
* @param magnitude magnitude of the displacement (acting as noise value
311+
* multiplier). The value defines the maximum displacement of
312+
* a vertex in the both x and y axes.
313+
* @param noiseScale the scale of the 2D noise vector field. This affects how of
314+
* the coarseness of warping. Smaller values (~0.2) lead to
315+
* more fine warping (at edges), whereas larger values (~2)
316+
* affect the shape geometry at a larger scale.
317+
* @param time used to offset the underlying noise field and hence animate
318+
* the warping over time
319+
* @param densify whether to densify the shape (using distance=1) before
320+
* warping. When true, shapes with long edges will undergo
321+
* warping along the whole edge (rather than only at the
322+
* original vertices).
323+
* @param noiseSeed a seed to pass to the underlying noise generator
324+
*
325+
* @see #fieldWarp(PShape, double, double, boolean)
326+
* @return
327+
*/
328+
public static PShape fieldWarp(PShape shape, double magnitude, double noiseScale, double time, boolean densify, int noiseSeed) {
329+
float scale = (float) noiseScale * 500f;
330+
final List<PVector> coords;
331+
332+
if (densify) {
333+
final Densifier d = new Densifier(fromPShape(shape));
334+
d.setDistanceTolerance(1);
335+
d.setValidate(false);
336+
coords = PGS_Conversion.toPVector(toPShape(d.getResultGeometry()));
337+
} else {
338+
coords = PGS_Conversion.toPVector(shape);
339+
}
340+
341+
final UniformNoise noise = new UniformNoise(noiseSeed);
342+
343+
coords.forEach(coord -> {
344+
// float dx = noise.uniformNoise(coord.x / scale, coord.y / scale, time) - 0.5f;
345+
float dx = noise.uniformNoise(coord.x / scale, coord.y / scale + time) - 0.5f;
346+
// float dy = noise.uniformNoise(coord.x / scale, coord.y / scale, 100 + time) - 0.5f;
347+
float dy = noise.uniformNoise(coord.x / scale + (101 + time), coord.y / scale + (101 + time)) - 0.5f;
348+
coord.add(dx * (float) magnitude * 2, dy * (float) magnitude * 2);
349+
});
350+
return toPShape(GeometryFixer.fix(fromPShape(PGS_Conversion.fromPVector(coords))));
351+
}
352+
217353
}

0 commit comments

Comments
 (0)