1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.geometry.euclidean.internal;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.function.Function;
25
26 import org.apache.commons.geometry.euclidean.threed.Vector3D;
27
28 /** Class containing utilities and algorithms intended to be internal to the library.
29 * Absolutely no guarantees are made regarding the stability of this API.
30 */
31 public final class EuclideanUtils {
32
33 /** Number of vertices in a triangle, i.e. {@code 3}. */
34 public static final int TRIANGLE_VERTEX_COUNT = 3;
35
36 /** Utility class; no instantiation. */
37 private EuclideanUtils() { }
38
39 /** Convert a convex polygon defined by a list of vertices into a triangle fan. The vertex forming the largest
40 * interior angle in the polygon is selected as the base of the triangle fan. Callers are responsible for
41 * ensuring that the given list of vertices define a geometrically valid convex polygon; no validation (except
42 * for a check on the minimum number of vertices) is performed.
43 * @param <T> triangle result type
44 * @param vertices vertices defining a convex polygon
45 * @param fn function accepting the vertices of each triangle as a list and returning the object used
46 * to represent that triangle in the result; each argument to this function is guaranteed to
47 * contain 3 vertices
48 * @return a list containing the return results of the function when passed the vertices for each
49 * triangle in order
50 * @throws IllegalArgumentException if fewer than 3 vertices are given
51 */
52 public static <T> List<T> convexPolygonToTriangleFan(final List<Vector3D> vertices,
53 final Function<List<Vector3D>, T> fn) {
54 final int size = vertices.size();
55 if (size < TRIANGLE_VERTEX_COUNT) {
56 throw new IllegalArgumentException("Cannot create triangle fan: " + TRIANGLE_VERTEX_COUNT +
57 " or more vertices are required but found only " + vertices.size());
58 } else if (size == TRIANGLE_VERTEX_COUNT) {
59 return Collections.singletonList(fn.apply(vertices));
60 }
61
62 final List<T> triangles = new ArrayList<>(size - 2);
63
64 final int fanIdx = findBestTriangleFanIndex(vertices);
65 int vertexIdx = (fanIdx + 1) % size;
66
67 final Vector3D fanBase = vertices.get(fanIdx);
68 Vector3D vertexA = vertices.get(vertexIdx);
69 Vector3D vertexB;
70
71 vertexIdx = (vertexIdx + 1) % size;
72 while (vertexIdx != fanIdx) {
73 vertexB = vertices.get(vertexIdx);
74
75 triangles.add(fn.apply(Arrays.asList(fanBase, vertexA, vertexB)));
76
77 vertexA = vertexB;
78 vertexIdx = (vertexIdx + 1) % size;
79 }
80
81 return triangles;
82 }
83
84 /** Find the index of the best vertex to use as the base for a triangle fan split of the convex polygon
85 * defined by the given vertices. The best vertex is the one that forms the largest interior angle in the
86 * polygon since a split at that point will help prevent the creation of very thin triangles.
87 * @param vertices vertices defining the convex polygon; must not be empty; no validation is performed
88 * to ensure that the vertices actually define a convex polygon
89 * @return the index of the best vertex to use as the base for a triangle fan split of the convex polygon
90 */
91 private static int findBestTriangleFanIndex(final List<Vector3D> vertices) {
92 final Iterator<Vector3D> it = vertices.iterator();
93
94 Vector3D curPt = it.next();
95 Vector3D nextPt;
96
97 final Vector3D lastVec = vertices.get(vertices.size() - 1).directionTo(curPt);
98 Vector3D incomingVec = lastVec;
99 Vector3D outgoingVec;
100
101 int bestIdx = 0;
102 double bestDot = -1.0;
103
104 int idx = 0;
105 double dot;
106 while (it.hasNext()) {
107 nextPt = it.next();
108 outgoingVec = curPt.directionTo(nextPt);
109
110 dot = incomingVec.dot(outgoingVec);
111 if (dot > bestDot) {
112 bestIdx = idx;
113 bestDot = dot;
114 }
115
116 curPt = nextPt;
117 incomingVec = outgoingVec;
118
119 ++idx;
120 }
121
122 // handle the last vertex on its own
123 dot = incomingVec.dot(lastVec);
124 if (dot > bestDot) {
125 bestIdx = idx;
126 }
127
128 return bestIdx;
129 }
130 }