GEOMETRY-121: adding EuclideanUtils class
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/EuclideanUtils.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/EuclideanUtils.java
new file mode 100644
index 0000000..a692b69
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/EuclideanUtils.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+
+/** Class containing utilities and algorithms intended to be internal to the library.
+ * Absolutely no guarantees are made regarding the stability of this API.
+ */
+public final class EuclideanUtils {
+
+ /** Utility class; no instantiation. */
+ private EuclideanUtils() { }
+
+ /** Convert a convex polygon defined by a list of vertices into a triangle fan. The vertex forming the largest
+ * interior angle in the polygon is selected as the base of the triangle fan. Callers are responsible for
+ * ensuring that the given list of vertices define a geometrically valid convex polygon; no validation (except
+ * for a check on the minimum number of vertices) is performed.
+ * @param <T> triangle result type
+ * @param vertices vertices defining a convex polygon
+ * @param fn function accepting the vertices of each triangle as a list and returning the object used
+ * to represent that triangle in the result; each argument to this function is guaranteed to
+ * contain 3 vertices
+ * @return a list containing the return results of the function when passed the vertices for each
+ * triangle in order
+ * @throws IllegalArgumentException if fewer than 3 vertices are given
+ */
+ public static <T> List<T> convexPolygonToTriangleFan(final List<Vector3D> vertices,
+ final Function<List<Vector3D>, T> fn) {
+ final int size = vertices.size();
+ if (size < 3) {
+ throw new IllegalArgumentException("Cannot create triangle fan: 3 or more vertices are required " +
+ "but found only " + vertices.size());
+ } else if (size == 3) {
+ return Collections.singletonList(fn.apply(vertices));
+ }
+
+ final List<T> triangles = new ArrayList<>(size - 2);
+
+ final int fanIdx = findBestTriangleFanIndex(vertices);
+ int vertexIdx = (fanIdx + 1) % size;
+
+ final Vector3D fanBase = vertices.get(fanIdx);
+ Vector3D vertexA = vertices.get(vertexIdx);
+ Vector3D vertexB;
+
+ vertexIdx = (vertexIdx + 1) % size;
+ while (vertexIdx != fanIdx) {
+ vertexB = vertices.get(vertexIdx);
+
+ triangles.add(fn.apply(Arrays.asList(fanBase, vertexA, vertexB)));
+
+ vertexA = vertexB;
+ vertexIdx = (vertexIdx + 1) % size;
+ }
+
+ return triangles;
+ }
+
+ /** Find the index of the best vertex to use as the base for a triangle fan split of the convex polygon
+ * defined by the given vertices. The best vertex is the one that forms the largest interior angle in the
+ * polygon since a split at that point will help prevent the creation of very thin triangles.
+ * @param vertices vertices defining the convex polygon; must not be empty; no validation is performed
+ * to ensure that the vertices actually define a convex polygon
+ * @return the index of the best vertex to use as the base for a triangle fan split of the convex polygon
+ */
+ private static int findBestTriangleFanIndex(final List<Vector3D> vertices) {
+ final Iterator<Vector3D> it = vertices.iterator();
+
+ Vector3D curPt = it.next();
+ Vector3D nextPt;
+
+ final Vector3D lastVec = vertices.get(vertices.size() - 1).directionTo(curPt);
+ Vector3D incomingVec = lastVec;
+ Vector3D outgoingVec;
+
+ int bestIdx = 0;
+ double bestDot = -1.0;
+
+ int idx = 0;
+ double dot;
+ while (it.hasNext()) {
+ nextPt = it.next();
+ outgoingVec = curPt.directionTo(nextPt);
+
+ dot = incomingVec.dot(outgoingVec);
+ if (dot > bestDot) {
+ bestIdx = idx;
+ bestDot = dot;
+ }
+
+ curPt = nextPt;
+ incomingVec = outgoingVec;
+
+ ++idx;
+ }
+
+ // handle the last vertex on its own
+ dot = incomingVec.dot(lastVec);
+ if (dot > bestDot) {
+ bestIdx = idx;
+ }
+
+ return bestIdx;
+ }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java
index 1ad8127..30622d8 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java
@@ -20,16 +20,14 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
import java.util.function.BiFunction;
-import java.util.function.Function;
import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SplitLocation;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
import org.apache.commons.geometry.euclidean.threed.line.Line3D;
import org.apache.commons.geometry.euclidean.threed.line.LineConvexSubset3D;
import org.apache.commons.geometry.euclidean.twod.ConvexArea;
@@ -400,98 +398,6 @@
return new PlaneRegionExtruder(plane, extrusionVector, precision).extrude(region);
}
- /** Convert a convex polygon defined by a list of vertices into a triangle fan. The vertex forming the largest
- * interior angle in the polygon is selected as the base of the triangle fan. Callers are responsible for
- * ensuring that the given list of vertices define a geometrically valid convex polygon; no validation (except
- * for a check on the minimum number of vertices) is performed.
- * @param <T> triangle result type
- * @param vertices vertices defining a convex polygon
- * @param fn function accepting the vertices of each triangle as a list and returning the object used
- * to represent that triangle in the result; each argument to this function is guaranteed to
- * contain 3 vertices
- * @return a list containing the return results of the function when passed the vertices for each
- * triangle in order
- * @throws IllegalArgumentException if fewer than 3 vertices are given
- */
- public static <T> List<T> convexPolygonToTriangleFan(final List<Vector3D> vertices,
- final Function<List<Vector3D>, T> fn) {
- final int size = vertices.size();
- if (size < 3) {
- throw new IllegalArgumentException("Cannot create triangle fan: 3 or more vertices are required " +
- "but found only " + vertices.size());
- } else if (size == 3) {
- return Collections.singletonList(fn.apply(vertices));
- }
-
- final List<T> triangles = new ArrayList<>(size - 2);
-
- final int fanIdx = findBestTriangleFanIndex(vertices);
- int vertexIdx = (fanIdx + 1) % size;
-
- final Vector3D fanBase = vertices.get(fanIdx);
- Vector3D vertexA = vertices.get(vertexIdx);
- Vector3D vertexB;
-
- vertexIdx = (vertexIdx + 1) % size;
- while (vertexIdx != fanIdx) {
- vertexB = vertices.get(vertexIdx);
-
- triangles.add(fn.apply(Arrays.asList(fanBase, vertexA, vertexB)));
-
- vertexA = vertexB;
- vertexIdx = (vertexIdx + 1) % size;
- }
-
- return triangles;
- }
-
- /** Find the index of the best vertex to use as the base for a triangle fan split of the convex polygon
- * defined by the given vertices. The best vertex is the one that forms the largest interior angle in the
- * polygon since a split at that point will help prevent the creation of very thin triangles.
- * @param vertices vertices defining the convex polygon; must not be empty; no validation is performed
- * to ensure that the vertices actually define a convex polygon
- * @return the index of the best vertex to use as the base for a triangle fan split of the convex polygon
- */
- private static int findBestTriangleFanIndex(final List<Vector3D> vertices) {
- final Iterator<Vector3D> it = vertices.iterator();
-
- Vector3D curPt = it.next();
- Vector3D nextPt;
-
- final Vector3D lastVec = vertices.get(vertices.size() - 1).directionTo(curPt);
- Vector3D incomingVec = lastVec;
- Vector3D outgoingVec;
-
- int bestIdx = 0;
- double bestDot = -1.0;
-
- int idx = 0;
- double dot;
- while (it.hasNext()) {
- nextPt = it.next();
- outgoingVec = curPt.directionTo(nextPt);
-
- dot = incomingVec.dot(outgoingVec);
- if (dot > bestDot) {
- bestIdx = idx;
- bestDot = dot;
- }
-
- curPt = nextPt;
- incomingVec = outgoingVec;
-
- ++idx;
- }
-
- // handle the last vertex on its own
- dot = incomingVec.dot(lastVec);
- if (dot > bestDot) {
- bestIdx = idx;
- }
-
- return bestIdx;
- }
-
/** Get the unique intersection of the plane subset with the given line. Null is
* returned if no unique intersection point exists (ie, the line and plane are
* parallel or coincident) or the line does not intersect the plane subset.
@@ -622,7 +528,7 @@
* @throws IllegalArgumentException if fewer than 3 vertices are given
*/
static List<Triangle3D> convexPolygonToTriangleFan(final Plane plane, final List<Vector3D> vertices) {
- return convexPolygonToTriangleFan(vertices,
+ return EuclideanUtils.convexPolygonToTriangleFan(vertices,
tri -> new SimpleTriangle3D(plane, tri.get(0), tri.get(1), tri.get(2)));
}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/EuclideanUtilsTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/EuclideanUtilsTest.java
new file mode 100644
index 0000000..4082a60
--- /dev/null
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/EuclideanUtilsTest.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class EuclideanUtilsTest {
+
+ private static final double TEST_EPS = 1e-10;
+
+ private static final DoublePrecisionContext TEST_PRECISION =
+ new EpsilonDoublePrecisionContext(TEST_EPS);
+
+ @Test
+ public void testConvexPolygonToTriangleFan_threeVertices() {
+ // arrange
+ final Vector3D p1 = Vector3D.ZERO;
+ final Vector3D p2 = Vector3D.of(1, 0, 0);
+ final Vector3D p3 = Vector3D.of(0, 1, 0);
+
+ final List<List<Vector3D>> tris = new ArrayList<>();
+
+ // act
+ EuclideanUtils.convexPolygonToTriangleFan(Arrays.asList(p1, p2, p3), tris::add);
+
+ // assert
+ Assertions.assertEquals(1, tris.size());
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p1, p2, p3), tris.get(0), TEST_PRECISION);
+ }
+
+ @Test
+ public void testConvexPolygonToTriangleFan_fourVertices() {
+ // arrange
+ final Vector3D p1 = Vector3D.ZERO;
+ final Vector3D p2 = Vector3D.of(1, 0, 0);
+ final Vector3D p3 = Vector3D.of(1, 1, 0);
+ final Vector3D p4 = Vector3D.of(0, 1, 0);
+
+ final List<List<Vector3D>> tris = new ArrayList<>();
+
+ // act
+ EuclideanUtils.convexPolygonToTriangleFan(Arrays.asList(p1, p2, p3, p4), tris::add);
+
+ // assert
+ Assertions.assertEquals(2, tris.size());
+
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p1, p2, p3), tris.get(0), TEST_PRECISION);
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p1, p3, p4), tris.get(1), TEST_PRECISION);
+ }
+
+ @Test
+ public void testConvexPolygonToTriangleFan_fourVertices_chooseLargestInteriorAngleForBase() {
+ // arrange
+ final Vector3D p1 = Vector3D.ZERO;
+ final Vector3D p2 = Vector3D.of(1, 0, 0);
+ final Vector3D p3 = Vector3D.of(2, 1, 0);
+ final Vector3D p4 = Vector3D.of(1.5, 1, 0);
+
+ final List<List<Vector3D>> tris = new ArrayList<>();
+
+ // act
+ EuclideanUtils.convexPolygonToTriangleFan(Arrays.asList(p1, p2, p3, p4), tris::add);
+
+ // assert
+ Assertions.assertEquals(2, tris.size());
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p4, p1, p2), tris.get(0), TEST_PRECISION);
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p4, p2, p3), tris.get(1), TEST_PRECISION);
+ }
+
+ @Test
+ public void testConvexPolygonToTriangleFan_fourVertices_distancesLessThanPrecision() {
+ // This test checks that the triangle fan algorithm is not affected by the distances between
+ // the vertices, just as long as the points are not exactly equal. Callers are responsible for
+ // ensuring that the points are actually distinct according to the relevant precision context.
+
+ // arrange
+ final Vector3D p1 = Vector3D.ZERO;
+ final Vector3D p2 = Vector3D.of(1e-20, 0, 0);
+ final Vector3D p3 = Vector3D.of(1e-20, 1e-20, 0);
+ final Vector3D p4 = Vector3D.of(0, 1e-20, 0);
+
+ final List<List<Vector3D>> tris = new ArrayList<>();
+
+ // act
+ EuclideanUtils.convexPolygonToTriangleFan(Arrays.asList(p1, p2, p3, p4), tris::add);
+
+ // assert
+ Assertions.assertEquals(2, tris.size());
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p1, p2, p3), tris.get(0), TEST_PRECISION);
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p1, p3, p4), tris.get(1), TEST_PRECISION);
+ }
+
+ @Test
+ public void testConvexPolygonToTriangleFan_sixVertices() {
+ // arrange
+ final Vector3D p1 = Vector3D.ZERO;
+ final Vector3D p2 = Vector3D.of(1, -1, 0);
+ final Vector3D p3 = Vector3D.of(1.5, -1, 0);
+ final Vector3D p4 = Vector3D.of(5, 0, 0);
+ final Vector3D p5 = Vector3D.of(3, 1, 0);
+ final Vector3D p6 = Vector3D.of(0.5, 1, 0);
+
+ final List<List<Vector3D>> tris = new ArrayList<>();
+
+ // act
+ EuclideanUtils.convexPolygonToTriangleFan(Arrays.asList(p1, p2, p3, p4, p5, p6), tris::add);
+
+ // assert
+ Assertions.assertEquals(4, tris.size());
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p3, p4, p5), tris.get(0), TEST_PRECISION);
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p3, p5, p6), tris.get(1), TEST_PRECISION);
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p3, p6, p1), tris.get(2), TEST_PRECISION);
+ EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p3, p1, p2), tris.get(3), TEST_PRECISION);
+ }
+
+ @Test
+ public void testConvexPolygonToTriangleFan_notEnoughVertices() {
+ // arrange
+ final String baseMsg = "Cannot create triangle fan: 3 or more vertices are required but found only ";
+
+ // act/assert
+ GeometryTestUtils.assertThrowsWithMessage(() -> {
+ EuclideanUtils.convexPolygonToTriangleFan(Collections.emptyList(), Function.identity());
+ }, IllegalArgumentException.class, baseMsg + "0");
+
+ GeometryTestUtils.assertThrowsWithMessage(() -> {
+ EuclideanUtils.convexPolygonToTriangleFan(Collections.singletonList(Vector3D.ZERO), Function.identity());
+ }, IllegalArgumentException.class, baseMsg + "1");
+
+ GeometryTestUtils.assertThrowsWithMessage(() -> {
+ EuclideanUtils.convexPolygonToTriangleFan(Arrays.asList(Vector3D.ZERO, Vector3D.of(1, 0, 0)), Function.identity());
+ }, IllegalArgumentException.class, baseMsg + "2");
+ }
+}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlanesTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlanesTest.java
index bfbb491..00c2994 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlanesTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlanesTest.java
@@ -559,59 +559,6 @@
}
@Test
- public void testConvexPolygonToTriangleFan_fourVertices_chooseLargestInteriorAngleForBase() {
- // arrange
- final Plane plane = Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION);
- final Vector3D p1 = Vector3D.ZERO;
- final Vector3D p2 = Vector3D.of(1, 0, 0);
- final Vector3D p3 = Vector3D.of(2, 1, 0);
- final Vector3D p4 = Vector3D.of(1.5, 1, 0);
-
- // act
- final List<Triangle3D> tris = Planes.convexPolygonToTriangleFan(plane, Arrays.asList(p1, p2, p3, p4));
-
- // assert
- Assertions.assertEquals(2, tris.size());
-
- final Triangle3D a = tris.get(0);
- Assertions.assertSame(plane, a.getPlane());
- EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p4, p1, p2), a.getVertices(), TEST_PRECISION);
-
- final Triangle3D b = tris.get(1);
- Assertions.assertSame(plane, b.getPlane());
- EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p4, p2, p3), b.getVertices(), TEST_PRECISION);
- }
-
- @Test
- public void testConvexPolygonToTriangleFan_fourVertices_distancesLessThanPrecision() {
- // This test checks that the triangle fan algorithm is not affected by the distances between
- // the vertices, just as long as the points are not exactly equal. Callers are responsible for
- // ensuring that the points are actually distinct according to the relevant precision context.
-
- // arrange
- final Plane plane = Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION);
- final Vector3D p1 = Vector3D.ZERO;
- final Vector3D p2 = Vector3D.of(1e-20, 0, 0);
- final Vector3D p3 = Vector3D.of(1e-20, 1e-20, 0);
- final Vector3D p4 = Vector3D.of(0, 1e-20, 0);
-
- // act
- final List<Triangle3D> tris = Planes.convexPolygonToTriangleFan(plane, Arrays.asList(p1, p2, p3, p4));
-
- // assert
- Assertions.assertEquals(2, tris.size());
-
- final Triangle3D a = tris.get(0);
- Assertions.assertSame(plane, a.getPlane());
- EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p1, p2, p3), a.getVertices(), TEST_PRECISION);
-
- final Triangle3D b = tris.get(1);
- Assertions.assertSame(plane, b.getPlane());
- EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p1, p3, p4), b.getVertices(), TEST_PRECISION);
- }
-
-
- @Test
public void testConvexPolygonToTriangleFan_sixVertices() {
// arrange
final Plane plane = Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION);
diff --git a/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/stl/StlBoundaryWriteHandler3D.java b/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/stl/StlBoundaryWriteHandler3D.java
index 47b6877..f785cac 100644
--- a/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/stl/StlBoundaryWriteHandler3D.java
+++ b/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/stl/StlBoundaryWriteHandler3D.java
@@ -23,9 +23,9 @@
import java.util.List;
import java.util.stream.Stream;
+import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
-import org.apache.commons.geometry.euclidean.threed.Planes;
import org.apache.commons.geometry.euclidean.threed.Triangle3D;
import org.apache.commons.geometry.euclidean.threed.Vector3D;
import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
@@ -149,7 +149,8 @@
facet = it.next();
attributeValue = getFacetAttributeValue(facet);
- for (final List<Vector3D> tri : Planes.convexPolygonToTriangleFan(facet.getVertices(), t -> t)) {
+ for (final List<Vector3D> tri :
+ EuclideanUtils.convexPolygonToTriangleFan(facet.getVertices(), t -> t)) {
dataWriter.writeTriangle(
tri.get(0),
diff --git a/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/stl/TextStlWriter.java b/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/stl/TextStlWriter.java
index e13cd08..22cf33b 100644
--- a/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/stl/TextStlWriter.java
+++ b/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/stl/TextStlWriter.java
@@ -20,8 +20,8 @@
import java.io.Writer;
import java.util.List;
+import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
-import org.apache.commons.geometry.euclidean.threed.Planes;
import org.apache.commons.geometry.euclidean.threed.Triangle3D;
import org.apache.commons.geometry.euclidean.threed.Vector3D;
import org.apache.commons.geometry.io.core.utils.AbstractTextFormatWriter;
@@ -131,7 +131,7 @@
* @throws IOException if an I/O error occurs
*/
public void writeTriangles(final List<Vector3D> vertices, final Vector3D normal) throws IOException {
- for (final List<Vector3D> triangle : Planes.convexPolygonToTriangleFan(vertices, t -> t)) {
+ for (final List<Vector3D> triangle : EuclideanUtils.convexPolygonToTriangleFan(vertices, t -> t)) {
writeTriangle(
triangle.get(0),
triangle.get(1),