SimpleTriangleMesh.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.threed.mesh;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.Comparator;
  23. import java.util.Iterator;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.NoSuchElementException;
  27. import java.util.Objects;
  28. import java.util.TreeMap;
  29. import java.util.function.Function;
  30. import java.util.stream.Stream;
  31. import java.util.stream.StreamSupport;

  32. import org.apache.commons.geometry.core.Transform;
  33. import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
  34. import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
  35. import org.apache.commons.geometry.euclidean.threed.Bounds3D;
  36. import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
  37. import org.apache.commons.geometry.euclidean.threed.Planes;
  38. import org.apache.commons.geometry.euclidean.threed.Triangle3D;
  39. import org.apache.commons.geometry.euclidean.threed.Vector3D;
  40. import org.apache.commons.numbers.core.Precision;

  41. /** A simple implementation of the {@link TriangleMesh} interface. This class ensures that
  42.  * faces always contain 3 valid references into the vertex list but does not enforce that
  43.  * the referenced vertices are unique or that they define a triangle with non-zero size. For
  44.  * example, a mesh could contain a face with 3 vertices that are considered equivalent by the
  45.  * configured precision context. Attempting to call the {@link TriangleMesh.Face#getPolygon()}
  46.  * method on such a face results in an exception. The
  47.  * {@link TriangleMesh.Face#definesPolygon()} method can be used to determine if a face defines
  48.  * a valid triangle.
  49.  *
  50.  * <p>Instances of this class are guaranteed to be immutable.</p>
  51.  */
  52. public final class SimpleTriangleMesh implements TriangleMesh {

  53.     /** Vertices in the mesh. */
  54.     private final List<Vector3D> vertices;

  55.     /** Faces in the mesh. */
  56.     private final List<int[]> faces;

  57.     /** The bounds of the mesh. */
  58.     private final Bounds3D bounds;

  59.     /** Object used for floating point comparisons. */
  60.     private final Precision.DoubleEquivalence precision;

  61.     /** Construct a new instance from a vertex list and set of faces. No validation is
  62.      * performed on the input.
  63.      * @param vertices vertex list
  64.      * @param faces face indices list
  65.      * @param bounds mesh bounds
  66.      * @param precision precision context used when creating face polygons
  67.      */
  68.     private SimpleTriangleMesh(final List<Vector3D> vertices, final List<int[]> faces, final Bounds3D bounds,
  69.             final Precision.DoubleEquivalence precision) {
  70.         this.vertices = Collections.unmodifiableList(vertices);
  71.         this.faces = Collections.unmodifiableList(faces);
  72.         this.bounds = bounds;
  73.         this.precision = precision;
  74.     }

  75.     /** {@inheritDoc} */
  76.     @Override
  77.     public Iterable<Vector3D> vertices() {
  78.         return getVertices();
  79.     }

  80.     /** {@inheritDoc} */
  81.     @Override
  82.     public List<Vector3D> getVertices() {
  83.         return vertices;
  84.     }

  85.     /** {@inheritDoc} */
  86.     @Override
  87.     public int getVertexCount() {
  88.         return vertices.size();
  89.     }

  90.     /** {@inheritDoc} */
  91.     @Override
  92.     public Iterable<TriangleMesh.Face> faces() {
  93.         return () -> new FaceIterator<>(Function.identity());
  94.     }

  95.     /** {@inheritDoc} */
  96.     @Override
  97.     public List<TriangleMesh.Face> getFaces() {
  98.         final int count = getFaceCount();

  99.         final List<Face> faceList = new ArrayList<>(count);
  100.         for (int i = 0; i < count; ++i) {
  101.             faceList.add(getFace(i));
  102.         }

  103.         return faceList;
  104.     }

  105.     /** {@inheritDoc} */
  106.     @Override
  107.     public int getFaceCount() {
  108.         return faces.size();
  109.     }

  110.     /** {@inheritDoc} */
  111.     @Override
  112.     public TriangleMesh.Face getFace(final int index) {
  113.         return new SimpleTriangleFace(index, faces.get(index));
  114.     }

  115.     /** {@inheritDoc} */
  116.     @Override
  117.     public Bounds3D getBounds() {
  118.         return bounds;
  119.     }

  120.     /** Get the precision context for the mesh. This context is used during construction of
  121.      * face {@link Triangle3D} instances.
  122.      * @return the precision context for the mesh
  123.      */
  124.     public Precision.DoubleEquivalence getPrecision() {
  125.         return precision;
  126.     }

  127.     /** {@inheritDoc} */
  128.     @Override
  129.     public Stream<PlaneConvexSubset> boundaryStream() {
  130.         return createFaceStream(Face::getPolygon);
  131.     }

  132.     /** {@inheritDoc} */
  133.     @Override
  134.     public Stream<Triangle3D> triangleStream() {
  135.         return createFaceStream(Face::getPolygon);
  136.     }

  137.     /** {@inheritDoc} */
  138.     @Override
  139.     public SimpleTriangleMesh transform(final Transform<Vector3D> transform) {
  140.         // only the vertices and bounds are modified; the faces are the same
  141.         final Bounds3D.Builder boundsBuilder = Bounds3D.builder();
  142.         final List<Vector3D> tVertices = new ArrayList<>(vertices.size());

  143.         Vector3D tVertex;
  144.         for (final Vector3D vertex : vertices) {
  145.             tVertex = transform.apply(vertex);

  146.             boundsBuilder.add(tVertex);
  147.             tVertices.add(tVertex);
  148.         }

  149.         final Bounds3D tBounds = boundsBuilder.hasBounds() ?
  150.                 boundsBuilder.build() :
  151.                 null;

  152.         return new SimpleTriangleMesh(tVertices, faces, tBounds, precision);
  153.     }

  154.     /** Return this instance if the given precision context is equal to the current precision context.
  155.      * Otherwise, create a new mesh with the given precision context but the same vertices, faces, and
  156.      * bounds.
  157.      * @param meshPrecision precision context to use when generating face polygons
  158.      * @return a mesh instance with the given precision context and the same mesh structure as the current
  159.      *      instance
  160.      */
  161.     @Override
  162.     public SimpleTriangleMesh toTriangleMesh(final Precision.DoubleEquivalence meshPrecision) {
  163.         if (this.precision.equals(meshPrecision)) {
  164.             return this;
  165.         }

  166.         return new SimpleTriangleMesh(vertices, faces, bounds, meshPrecision);
  167.     }

  168.     /** {@inheritDoc} */
  169.     @Override
  170.     public String toString() {
  171.         final StringBuilder sb = new StringBuilder();
  172.         sb.append(getClass().getSimpleName())
  173.             .append("[vertexCount= ")
  174.             .append(getVertexCount())
  175.             .append(", faceCount= ")
  176.             .append(getFaceCount())
  177.             .append(", bounds= ")
  178.             .append(getBounds())
  179.             .append(']');

  180.         return sb.toString();
  181.     }

  182.     /** Create a stream containing the results of applying {@code fn} to each face in
  183.      * the mesh.
  184.      * @param <T> Stream element type
  185.      * @param fn function used to extract the stream values from each face
  186.      * @return a stream containing the results of applying {@code fn} to each face in
  187.      *      the mesh
  188.      */
  189.     private <T> Stream<T> createFaceStream(final Function<TriangleMesh.Face, T> fn) {
  190.         final Iterable<T> iterable = () -> new FaceIterator<>(fn);
  191.         return StreamSupport.stream(iterable.spliterator(), false);
  192.     }

  193.     /** Return a builder for creating new triangle mesh objects.
  194.      * @param precision precision object used for floating point comparisons
  195.      * @return a builder for creating new triangle mesh objects
  196.      */
  197.     public static Builder builder(final Precision.DoubleEquivalence precision) {
  198.         return new Builder(precision);
  199.     }

  200.     /** Construct a new triangle mesh from the given vertices and face indices.
  201.      * @param vertices vertices for the mesh
  202.      * @param faces face indices for the mesh
  203.      * @param precision precision context used for floating point comparisons
  204.      * @return a new triangle mesh instance
  205.      * @throws IllegalArgumentException if any of the face index arrays does not have exactly 3 elements or
  206.      *       if any index is not a valid index into the vertex list
  207.      */
  208.     public static SimpleTriangleMesh from(final Vector3D[] vertices, final int[][] faces,
  209.                                           final Precision.DoubleEquivalence precision) {
  210.         return from(Arrays.asList(vertices), Arrays.asList(faces), precision);
  211.     }

  212.     /** Construct a new triangle mesh from the given vertices and face indices.
  213.      * @param vertices vertices for the mesh
  214.      * @param faces face indices for the mesh
  215.      * @param precision precision context used for floating point comparisons
  216.      * @return a new triangle mesh instance
  217.      * @throws IllegalArgumentException if any of the face index arrays does not have exactly 3 elements or
  218.      *       if any index is not a valid index into the vertex list
  219.      */
  220.     public static SimpleTriangleMesh from(final Collection<Vector3D> vertices, final Collection<int[]> faces,
  221.                                           final Precision.DoubleEquivalence precision) {
  222.         final Builder builder = builder(precision);

  223.         return builder.addVertices(vertices)
  224.                 .addFaces(faces)
  225.                 .build();
  226.     }

  227.     /** Construct a new mesh instance containing all triangles from the given boundary
  228.      * source. Equivalent vertices are reused wherever possible.
  229.      * @param boundarySrc boundary source to construct a mesh from
  230.      * @param precision precision context used for floating point comparisons
  231.      * @return new mesh instance containing all triangles from the given boundary
  232.      *      source
  233.      * @throws IllegalStateException if any boundary in the boundary source has infinite size and cannot
  234.      *      be converted to triangles
  235.      */
  236.     public static SimpleTriangleMesh from(final BoundarySource3D boundarySrc,
  237.             final Precision.DoubleEquivalence precision) {
  238.         final Builder builder = builder(precision);
  239.         try (Stream<Triangle3D> stream = boundarySrc.triangleStream()) {
  240.             stream.forEach(tri -> builder.addFaceUsingVertices(
  241.                 tri.getPoint1(),
  242.                 tri.getPoint2(),
  243.                 tri.getPoint3()));
  244.         }

  245.         return builder.build();
  246.     }

  247.     /** Internal implementation of {@link TriangleMesh.Face}.
  248.      */
  249.     private final class SimpleTriangleFace implements TriangleMesh.Face {

  250.         /** The index of the face in the mesh. */
  251.         private final int index;

  252.         /** Vertex indices for the face. */
  253.         private final int[] vertexIndices;

  254.         SimpleTriangleFace(final int index, final int[] vertexIndices) {
  255.             this.index = index;
  256.             this.vertexIndices = vertexIndices;
  257.         }

  258.         /** {@inheritDoc} */
  259.         @Override
  260.         public int getIndex() {
  261.             return index;
  262.         }

  263.         /** {@inheritDoc} */
  264.         @Override
  265.         public int[] getVertexIndices() {
  266.             return vertexIndices.clone();
  267.         }

  268.         /** {@inheritDoc} */
  269.         @Override
  270.         public List<Vector3D> getVertices() {
  271.             return Arrays.asList(
  272.                     getPoint1(),
  273.                     getPoint2(),
  274.                     getPoint3());
  275.         }

  276.         /** {@inheritDoc} */
  277.         @Override
  278.         public Vector3D getPoint1() {
  279.             return vertices.get(vertexIndices[0]);
  280.         }

  281.         /** {@inheritDoc} */
  282.         @Override
  283.         public Vector3D getPoint2() {
  284.             return vertices.get(vertexIndices[1]);
  285.         }

  286.         /** {@inheritDoc} */
  287.         @Override
  288.         public Vector3D getPoint3() {
  289.             return vertices.get(vertexIndices[2]);
  290.         }

  291.         /** {@inheritDoc} */
  292.         @Override
  293.         public boolean definesPolygon() {
  294.             final Vector3D p1 = getPoint1();
  295.             final Vector3D v1 = p1.vectorTo(getPoint2());
  296.             final Vector3D v2 = p1.vectorTo(getPoint3());

  297.             return !precision.eqZero(v1.cross(v2).norm());
  298.         }

  299.         /** {@inheritDoc} */
  300.         @Override
  301.         public Triangle3D getPolygon() {
  302.             return Planes.triangleFromVertices(
  303.                     getPoint1(),
  304.                     getPoint2(),
  305.                     getPoint3(),
  306.                     precision);
  307.         }

  308.         /** {@inheritDoc} */
  309.         @Override
  310.         public String toString() {
  311.             final StringBuilder sb = new StringBuilder();
  312.             sb.append(getClass().getSimpleName())
  313.                 .append("[index= ")
  314.                 .append(getIndex())
  315.                 .append(", vertexIndices= ")
  316.                 .append(Arrays.toString(getVertexIndices()))
  317.                 .append(", vertices= ")
  318.                 .append(getVertices())
  319.                 .append(']');

  320.             return sb.toString();
  321.         }
  322.     }

  323.     /** Internal class for iterating through the mesh faces and extracting a value from each.
  324.      * @param <T> Type returned by the iterator
  325.      */
  326.     private final class FaceIterator<T> implements Iterator<T> {

  327.         /** The current index of the iterator. */
  328.         private int index;

  329.         /** Function to apply to each face in the mesh. */
  330.         private final Function<? super TriangleMesh.Face, T> fn;

  331.         /** Construct a new instance for iterating through the mesh faces and extracting
  332.          * a value from each.
  333.          * @param fn function to apply to each face in order to obtain the iterated value
  334.          */
  335.         FaceIterator(final Function<? super TriangleMesh.Face, T> fn) {
  336.             this.fn = fn;
  337.         }

  338.         /** {@inheritDoc} */
  339.         @Override
  340.         public boolean hasNext() {
  341.             return index < faces.size();
  342.         }

  343.         /** {@inheritDoc} */
  344.         @Override
  345.         public T next() {
  346.             if (hasNext()) {
  347.                 final Face face = getFace(index++);
  348.                 return fn.apply(face);
  349.             }
  350.             throw new NoSuchElementException();
  351.         }
  352.     }

  353.     /** Builder class for creating mesh instances.
  354.      */
  355.     public static final class Builder {

  356.         /** List of vertices. */
  357.         private final ArrayList<Vector3D> vertices = new ArrayList<>();

  358.         /** Map of vertices to their first occurrence in the vertex list. */
  359.         private Map<Vector3D, Integer> vertexIndexMap;

  360.         /** List of face vertex indices. */
  361.         private final ArrayList<int[]> faces = new ArrayList<>();

  362.         /** Object used to construct the 3D bounds of the vertex list. */
  363.         private final Bounds3D.Builder boundsBuilder = Bounds3D.builder();

  364.         /** Precision context used for floating point comparisons; this value may be null
  365.          * if vertices are not to be combined in this builder.
  366.          */
  367.         private final Precision.DoubleEquivalence precision;

  368.         /** Flag set to true once a mesh is constructed from this builder. */
  369.         private boolean built;

  370.         /** Construct a new builder.
  371.          * @param precision precision context used for floating point comparisons; may
  372.          *      be null if vertices are not to be combined in this builder.
  373.          */
  374.         private Builder(final Precision.DoubleEquivalence precision) {
  375.             Objects.requireNonNull(precision, "Precision context must not be null");

  376.             this.precision = precision;
  377.         }

  378.         /** Use a vertex in the constructed mesh. If an equivalent vertex already exist, as determined
  379.          * by the configured {@link Precision.DoubleEquivalence}, then the index of the previously added
  380.          * vertex is returned. Otherwise, the given vertex is added to the vertex list and the index
  381.          * of the new entry is returned. This is in contrast with the {@link #addVertex(Vector3D)},
  382.          * which always adds a new entry to the vertex list.
  383.          * @param vertex vertex to use
  384.          * @return the index of the added vertex or an equivalent vertex that was added previously
  385.          * @see #addVertex(Vector3D)
  386.          */
  387.         public int useVertex(final Vector3D vertex) {
  388.             final int nextIdx = vertices.size();
  389.             final int actualIdx = addToVertexIndexMap(vertex, nextIdx, getVertexIndexMap());

  390.             // add to the vertex list if not already present
  391.             if (actualIdx == nextIdx) {
  392.                 addToVertexList(vertex);
  393.             }

  394.             return actualIdx;
  395.         }

  396.         /** Add a vertex directly to the vertex list, returning the index of the added vertex.
  397.          * The vertex is added regardless of whether or not an equivalent vertex already
  398.          * exists in the list. This is in contrast with the {@link #useVertex(Vector3D)} method,
  399.          * which only adds a new entry to the vertex list if an equivalent one does not
  400.          * already exist.
  401.          * @param vertex the vertex to append
  402.          * @return the index of the appended vertex in the vertex list
  403.          */
  404.         public int addVertex(final Vector3D vertex) {
  405.             final int idx = addToVertexList(vertex);

  406.             if (vertexIndexMap != null) {
  407.                 // add to the map in order to keep it in sync
  408.                 addToVertexIndexMap(vertex, idx, vertexIndexMap);
  409.             }

  410.             return idx;
  411.         }

  412.         /** Add a group of vertices directly to the vertex list. No equivalent vertices are reused.
  413.          * @param newVertices vertices to append
  414.          * @return this instance
  415.          * @see #addVertex(Vector3D)
  416.          */
  417.         public Builder addVertices(final Vector3D[] newVertices) {
  418.             return addVertices(Arrays.asList(newVertices));
  419.         }

  420.         /** Add a group of vertices directly to the vertex list. No equivalent vertices are reused.
  421.          * @param newVertices vertices to append
  422.          * @return this instance
  423.          * @see #addVertex(Vector3D)
  424.          */
  425.         public Builder addVertices(final Collection<? extends Vector3D> newVertices) {
  426.             final int newSize = vertices.size() + newVertices.size();
  427.             ensureVertexCapacity(newSize);

  428.             for (final Vector3D vertex : newVertices) {
  429.                 addVertex(vertex);
  430.             }

  431.             return this;
  432.         }

  433.         /** Ensure that this instance has enough capacity to store at least {@code numVertices}
  434.          * number of vertices without reallocating space. This can be used to help improve performance
  435.          * and memory usage when creating meshes with large numbers of vertices.
  436.          * @param numVertices the number of vertices to ensure that this instance can contain
  437.          * @return this instance
  438.          */
  439.         public Builder ensureVertexCapacity(final int numVertices) {
  440.             vertices.ensureCapacity(numVertices);
  441.             return this;
  442.         }

  443.         /** Get the current number of vertices in this mesh.
  444.          * @return the current number of vertices in this mesh
  445.          */
  446.         public int getVertexCount() {
  447.             return vertices.size();
  448.         }

  449.         /** Get the vertex at the given index.
  450.          * @param index index of the vertex to retrieve
  451.          * @return vertex at the given index
  452.          * @throws IndexOutOfBoundsException if the index is out of bounds of the mesh vertex list
  453.          */
  454.         public Vector3D getVertex(final int index) {
  455.             return vertices.get(index);
  456.         }

  457.         /** Append a face to this mesh.
  458.          * @param index1 index of the first vertex in the face
  459.          * @param index2 index of the second vertex in the face
  460.          * @param index3 index of the third vertex in the face
  461.          * @return this instance
  462.          * @throws IllegalArgumentException if any of the arguments is not a valid index into
  463.          *      the current vertex list
  464.          */
  465.         public Builder addFace(final int index1, final int index2, final int index3) {
  466.             validateCanModify();

  467.             final int[] indices = {
  468.                 validateVertexIndex(index1),
  469.                 validateVertexIndex(index2),
  470.                 validateVertexIndex(index3)
  471.             };

  472.             faces.add(indices);

  473.             return this;
  474.         }

  475.         /** Append a face to this mesh.
  476.          * @param face array containing the 3 vertex indices defining the face
  477.          * @return this instance
  478.          * @throws IllegalArgumentException if {@code face} does not contain exactly 3 elements
  479.          *      or if any of the vertex indices is not a valid index into the current vertex list
  480.          */
  481.         public Builder addFace(final int[] face) {
  482.             if (face.length != EuclideanUtils.TRIANGLE_VERTEX_COUNT) {
  483.                 throw new IllegalArgumentException("Face must contain " + EuclideanUtils.TRIANGLE_VERTEX_COUNT +
  484.                         " vertex indices; found " + face.length);
  485.             }

  486.             addFace(face[0], face[1], face[2]);

  487.             return this;
  488.         }

  489.         /** Append a group of faces to this mesh.
  490.          * @param faceIndices faces to append
  491.          * @return this instance
  492.          * @throws IllegalArgumentException if any of the face index arrays does not have exactly 3 elements or
  493.          *       if any index is not a valid index into the current vertex list
  494.          */
  495.         public Builder addFaces(final int[][] faceIndices) {
  496.             return addFaces(Arrays.asList(faceIndices));
  497.         }

  498.         /** Append a group of faces to this mesh.
  499.          * @param faceIndices faces to append
  500.          * @return this instance
  501.          * @throws IllegalArgumentException if any of the face index arrays does not have exactly 3 elements or
  502.          *       if any index is not a valid index into the current vertex list
  503.          */
  504.         public Builder addFaces(final Collection<int[]> faceIndices) {
  505.             final int newSize = faces.size() + faceIndices.size();
  506.             ensureFaceCapacity(newSize);

  507.             for (final int[] face : faceIndices) {
  508.                 addFace(face);
  509.             }

  510.             return this;
  511.         }

  512.         /** Add a face to this mesh, only adding vertices to the vertex list if equivalent vertices are
  513.          * not found.
  514.          * @param p1 first face vertex
  515.          * @param p2 second face vertex
  516.          * @param p3 third face vertex
  517.          * @return this instance
  518.          * @see #useVertex(Vector3D)
  519.          */
  520.         public Builder addFaceUsingVertices(final Vector3D p1, final Vector3D p2, final Vector3D p3) {
  521.             return addFace(
  522.                         useVertex(p1),
  523.                         useVertex(p2),
  524.                         useVertex(p3)
  525.                     );
  526.         }

  527.         /** Add a face and its vertices to this mesh. The vertices are always added to the vertex list,
  528.          * regardless of whether or not equivalent vertices exist in the vertex list.
  529.          * @param p1 first face vertex
  530.          * @param p2 second face vertex
  531.          * @param p3 third face vertex
  532.          * @return this instance
  533.          * @see #addVertex(Vector3D)
  534.          */
  535.         public Builder addFaceAndVertices(final Vector3D p1, final Vector3D p2, final Vector3D p3) {
  536.             return addFace(
  537.                         addVertex(p1),
  538.                         addVertex(p2),
  539.                         addVertex(p3)
  540.                     );
  541.         }

  542.         /** Ensure that this instance has enough capacity to store at least {@code numFaces}
  543.          * number of faces without reallocating space. This can be used to help improve performance
  544.          * and memory usage when creating meshes with large numbers of faces.
  545.          * @param numFaces the number of faces to ensure that this instance can contain
  546.          * @return this instance
  547.          */
  548.         public Builder ensureFaceCapacity(final int numFaces) {
  549.             faces.ensureCapacity(numFaces);
  550.             return this;
  551.         }

  552.         /** Get the current number of faces in this mesh.
  553.          * @return the current number of faces in this meshr
  554.          */
  555.         public int getFaceCount() {
  556.             return faces.size();
  557.         }

  558.         /** Build a triangle mesh containing the vertices and faces in this builder.
  559.          * @return a triangle mesh containing the vertices and faces in this builder
  560.          */
  561.         public SimpleTriangleMesh build() {
  562.             built = true;

  563.             final Bounds3D bounds = boundsBuilder.hasBounds() ?
  564.                     boundsBuilder.build() :
  565.                     null;

  566.             vertices.trimToSize();
  567.             faces.trimToSize();

  568.             return new SimpleTriangleMesh(
  569.                     vertices,
  570.                     faces,
  571.                     bounds,
  572.                     precision);
  573.         }

  574.         /** Get the vertex index map, creating and initializing it if needed.
  575.          * @return the vertex index map
  576.          */
  577.         private Map<Vector3D, Integer> getVertexIndexMap() {
  578.             if (vertexIndexMap == null) {
  579.                 vertexIndexMap = new TreeMap<>(new FuzzyVectorComparator(precision));

  580.                 // populate the index map
  581.                 final int size = vertices.size();
  582.                 for (int i = 0; i < size; ++i) {
  583.                     addToVertexIndexMap(vertices.get(i), i, vertexIndexMap);
  584.                 }
  585.             }
  586.             return vertexIndexMap;
  587.         }

  588.         /** Add a vertex to the given vertex index map. The vertex is inserted and mapped to {@code targetidx}
  589.          *  if an equivalent vertex does not already exist. The index now associated with the given vertex
  590.          *  or its equivalent is returned.
  591.          * @param vertex vertex to add
  592.          * @param targetIdx the index to associate with the vertex if no equivalent vertex has already been
  593.          *      mapped
  594.          * @param map vertex index map
  595.          * @return the index now associated with the given vertex or its equivalent
  596.          */
  597.         private int addToVertexIndexMap(final Vector3D vertex, final int targetIdx,
  598.                 final Map<? super Vector3D, Integer> map) {
  599.             validateCanModify();

  600.             final Integer actualIdx = map.putIfAbsent(vertex, targetIdx);

  601.             return actualIdx != null ?
  602.                     actualIdx :
  603.                     targetIdx;
  604.         }

  605.         /** Append the given vertex to the end of the vertex list. The index of the vertex is returned.
  606.          * @param vertex the vertex to append
  607.          * @return the index of the appended vertex
  608.          */
  609.         private int addToVertexList(final Vector3D vertex) {
  610.             validateCanModify();

  611.             boundsBuilder.add(vertex);

  612.             final int idx = vertices.size();
  613.             vertices.add(vertex);

  614.             return idx;
  615.         }

  616.         /** Throw an exception if the given vertex index is not valid.
  617.          * @param idx vertex index to validate
  618.          * @return the validated index
  619.          * @throws IllegalArgumentException if the given index is not a valid index into
  620.          *      the vertices list
  621.          */
  622.         private int validateVertexIndex(final int idx) {
  623.             if (idx < 0 || idx >= vertices.size()) {
  624.                 throw new IllegalArgumentException("Invalid vertex index: " + idx);
  625.             }

  626.             return idx;
  627.         }

  628.         /** Throw an exception if the builder has been used to construct a mesh instance
  629.          * and can no longer be modified.
  630.          */
  631.         private void validateCanModify() {
  632.             if (built) {
  633.                 throw new IllegalStateException("Builder instance cannot be modified: mesh construction is complete");
  634.             }
  635.         }
  636.     }

  637.     /** Comparator used to sort vectors using non-strict ("fuzzy") comparisons.
  638.      * Vectors are considered equal if their values in all coordinate dimensions
  639.      * are equivalent as evaluated by the precision context.
  640.      */
  641.     private static final class FuzzyVectorComparator implements Comparator<Vector3D> {
  642.         /** Precision context to determine floating-point equality. */
  643.         private final Precision.DoubleEquivalence precision;

  644.         /** Construct a new instance that uses the given precision context for
  645.          * floating point comparisons.
  646.          * @param precision precision context used for floating point comparisons
  647.          */
  648.         FuzzyVectorComparator(final Precision.DoubleEquivalence precision) {
  649.             this.precision = precision;
  650.         }

  651.         /** {@inheritDoc} */
  652.         @Override
  653.         public int compare(final Vector3D a, final Vector3D b) {
  654.             int result = precision.compare(a.getX(), b.getX());
  655.             if (result == 0) {
  656.                 result = precision.compare(a.getY(), b.getY());
  657.                 if (result == 0) {
  658.                     result = precision.compare(a.getZ(), b.getZ());
  659.                 }
  660.             }

  661.             return result;
  662.         }
  663.     }
  664. }