EuclideanUtils.java

  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. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import java.util.Iterator;
  22. import java.util.List;
  23. import java.util.function.Function;

  24. import org.apache.commons.geometry.euclidean.threed.Vector3D;

  25. /** Class containing utilities and algorithms intended to be internal to the library.
  26.  * Absolutely no guarantees are made regarding the stability of this API.
  27.  */
  28. public final class EuclideanUtils {

  29.     /** Number of vertices in a triangle, i.e. {@code 3}. */
  30.     public static final int TRIANGLE_VERTEX_COUNT = 3;

  31.     /** Utility class; no instantiation. */
  32.     private EuclideanUtils() { }

  33.     /** Convert a convex polygon defined by a list of vertices into a triangle fan. The vertex forming the largest
  34.      * interior angle in the polygon is selected as the base of the triangle fan. Callers are responsible for
  35.      * ensuring that the given list of vertices define a geometrically valid convex polygon; no validation (except
  36.      * for a check on the minimum number of vertices) is performed.
  37.      * @param <T> triangle result type
  38.      * @param vertices vertices defining a convex polygon
  39.      * @param fn function accepting the vertices of each triangle as a list and returning the object used
  40.      *      to represent that triangle in the result; each argument to this function is guaranteed to
  41.      *      contain 3 vertices
  42.      * @return a list containing the return results of the function when passed the vertices for each
  43.      *      triangle in order
  44.      * @throws IllegalArgumentException if fewer than 3 vertices are given
  45.      */
  46.     public static <T> List<T> convexPolygonToTriangleFan(final List<Vector3D> vertices,
  47.            final Function<List<Vector3D>, T> fn) {
  48.         final int size = vertices.size();
  49.         if (size < TRIANGLE_VERTEX_COUNT) {
  50.             throw new IllegalArgumentException("Cannot create triangle fan: " + TRIANGLE_VERTEX_COUNT +
  51.                     " or more vertices are required but found only " + vertices.size());
  52.         } else if (size == TRIANGLE_VERTEX_COUNT) {
  53.             return Collections.singletonList(fn.apply(vertices));
  54.         }

  55.         final List<T> triangles = new ArrayList<>(size - 2);

  56.         final int fanIdx = findBestTriangleFanIndex(vertices);
  57.         int vertexIdx = (fanIdx + 1) % size;

  58.         final Vector3D fanBase = vertices.get(fanIdx);
  59.         Vector3D vertexA = vertices.get(vertexIdx);
  60.         Vector3D vertexB;

  61.         vertexIdx = (vertexIdx + 1) % size;
  62.         while (vertexIdx != fanIdx) {
  63.             vertexB = vertices.get(vertexIdx);

  64.             triangles.add(fn.apply(Arrays.asList(fanBase, vertexA, vertexB)));

  65.             vertexA = vertexB;
  66.             vertexIdx = (vertexIdx + 1) % size;
  67.         }

  68.         return triangles;
  69.     }

  70.     /** Find the index of the best vertex to use as the base for a triangle fan split of the convex polygon
  71.      * defined by the given vertices. The best vertex is the one that forms the largest interior angle in the
  72.      * polygon since a split at that point will help prevent the creation of very thin triangles.
  73.      * @param vertices vertices defining the convex polygon; must not be empty; no validation is performed
  74.      *      to ensure that the vertices actually define a convex polygon
  75.      * @return the index of the best vertex to use as the base for a triangle fan split of the convex polygon
  76.      */
  77.     private static int findBestTriangleFanIndex(final List<Vector3D> vertices) {
  78.         final Iterator<Vector3D> it = vertices.iterator();

  79.         Vector3D curPt = it.next();
  80.         Vector3D nextPt;

  81.         final Vector3D lastVec = vertices.get(vertices.size() - 1).directionTo(curPt);
  82.         Vector3D incomingVec = lastVec;
  83.         Vector3D outgoingVec;

  84.         int bestIdx = 0;
  85.         double bestDot = -1.0;

  86.         int idx = 0;
  87.         double dot;
  88.         while (it.hasNext()) {
  89.             nextPt = it.next();
  90.             outgoingVec = curPt.directionTo(nextPt);

  91.             dot = incomingVec.dot(outgoingVec);
  92.             if (dot > bestDot) {
  93.                 bestIdx = idx;
  94.                 bestDot = dot;
  95.             }

  96.             curPt = nextPt;
  97.             incomingVec = outgoingVec;

  98.             ++idx;
  99.         }

  100.         // handle the last vertex on its own
  101.         dot = incomingVec.dot(lastVec);
  102.         if (dot > bestDot) {
  103.             bestIdx = idx;
  104.         }

  105.         return bestIdx;
  106.     }
  107. }