001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.euclidean.threed.mesh;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Comparator;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.NoSuchElementException;
028import java.util.Objects;
029import java.util.TreeMap;
030import java.util.function.Function;
031import java.util.stream.Stream;
032import java.util.stream.StreamSupport;
033
034import org.apache.commons.geometry.core.Transform;
035import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
036import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
037import org.apache.commons.geometry.euclidean.threed.Bounds3D;
038import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
039import org.apache.commons.geometry.euclidean.threed.Planes;
040import org.apache.commons.geometry.euclidean.threed.Triangle3D;
041import org.apache.commons.geometry.euclidean.threed.Vector3D;
042import org.apache.commons.numbers.core.Precision;
043
044/** A simple implementation of the {@link TriangleMesh} interface. This class ensures that
045 * faces always contain 3 valid references into the vertex list but does not enforce that
046 * the referenced vertices are unique or that they define a triangle with non-zero size. For
047 * example, a mesh could contain a face with 3 vertices that are considered equivalent by the
048 * configured precision context. Attempting to call the {@link TriangleMesh.Face#getPolygon()}
049 * method on such a face results in an exception. The
050 * {@link TriangleMesh.Face#definesPolygon()} method can be used to determine if a face defines
051 * a valid triangle.
052 *
053 * <p>Instances of this class are guaranteed to be immutable.</p>
054 */
055public final class SimpleTriangleMesh implements TriangleMesh {
056
057    /** Vertices in the mesh. */
058    private final List<Vector3D> vertices;
059
060    /** Faces in the mesh. */
061    private final List<int[]> faces;
062
063    /** The bounds of the mesh. */
064    private final Bounds3D bounds;
065
066    /** Object used for floating point comparisons. */
067    private final Precision.DoubleEquivalence precision;
068
069    /** Construct a new instance from a vertex list and set of faces. No validation is
070     * performed on the input.
071     * @param vertices vertex list
072     * @param faces face indices list
073     * @param bounds mesh bounds
074     * @param precision precision context used when creating face polygons
075     */
076    private SimpleTriangleMesh(final List<Vector3D> vertices, final List<int[]> faces, final Bounds3D bounds,
077            final Precision.DoubleEquivalence precision) {
078        this.vertices = Collections.unmodifiableList(vertices);
079        this.faces = Collections.unmodifiableList(faces);
080        this.bounds = bounds;
081        this.precision = precision;
082    }
083
084    /** {@inheritDoc} */
085    @Override
086    public Iterable<Vector3D> vertices() {
087        return getVertices();
088    }
089
090    /** {@inheritDoc} */
091    @Override
092    public List<Vector3D> getVertices() {
093        return vertices;
094    }
095
096    /** {@inheritDoc} */
097    @Override
098    public int getVertexCount() {
099        return vertices.size();
100    }
101
102    /** {@inheritDoc} */
103    @Override
104    public Iterable<TriangleMesh.Face> faces() {
105        return () -> new FaceIterator<>(Function.identity());
106    }
107
108    /** {@inheritDoc} */
109    @Override
110    public List<TriangleMesh.Face> getFaces() {
111        final int count = getFaceCount();
112
113        final List<Face> faceList = new ArrayList<>(count);
114        for (int i = 0; i < count; ++i) {
115            faceList.add(getFace(i));
116        }
117
118        return faceList;
119    }
120
121    /** {@inheritDoc} */
122    @Override
123    public int getFaceCount() {
124        return faces.size();
125    }
126
127    /** {@inheritDoc} */
128    @Override
129    public TriangleMesh.Face getFace(final int index) {
130        return new SimpleTriangleFace(index, faces.get(index));
131    }
132
133    /** {@inheritDoc} */
134    @Override
135    public Bounds3D getBounds() {
136        return bounds;
137    }
138
139    /** Get the precision context for the mesh. This context is used during construction of
140     * face {@link Triangle3D} instances.
141     * @return the precision context for the mesh
142     */
143    public Precision.DoubleEquivalence getPrecision() {
144        return precision;
145    }
146
147    /** {@inheritDoc} */
148    @Override
149    public Stream<PlaneConvexSubset> boundaryStream() {
150        return createFaceStream(Face::getPolygon);
151    }
152
153    /** {@inheritDoc} */
154    @Override
155    public Stream<Triangle3D> triangleStream() {
156        return createFaceStream(Face::getPolygon);
157    }
158
159    /** {@inheritDoc} */
160    @Override
161    public SimpleTriangleMesh transform(final Transform<Vector3D> transform) {
162        // only the vertices and bounds are modified; the faces are the same
163        final Bounds3D.Builder boundsBuilder = Bounds3D.builder();
164        final List<Vector3D> tVertices = new ArrayList<>(vertices.size());
165
166        Vector3D tVertex;
167        for (final Vector3D vertex : vertices) {
168            tVertex = transform.apply(vertex);
169
170            boundsBuilder.add(tVertex);
171            tVertices.add(tVertex);
172        }
173
174        final Bounds3D tBounds = boundsBuilder.hasBounds() ?
175                boundsBuilder.build() :
176                null;
177
178        return new SimpleTriangleMesh(tVertices, faces, tBounds, precision);
179    }
180
181    /** Return this instance if the given precision context is equal to the current precision context.
182     * Otherwise, create a new mesh with the given precision context but the same vertices, faces, and
183     * bounds.
184     * @param meshPrecision precision context to use when generating face polygons
185     * @return a mesh instance with the given precision context and the same mesh structure as the current
186     *      instance
187     */
188    @Override
189    public SimpleTriangleMesh toTriangleMesh(final Precision.DoubleEquivalence meshPrecision) {
190        if (this.precision.equals(meshPrecision)) {
191            return this;
192        }
193
194        return new SimpleTriangleMesh(vertices, faces, bounds, meshPrecision);
195    }
196
197    /** {@inheritDoc} */
198    @Override
199    public String toString() {
200        final StringBuilder sb = new StringBuilder();
201        sb.append(getClass().getSimpleName())
202            .append("[vertexCount= ")
203            .append(getVertexCount())
204            .append(", faceCount= ")
205            .append(getFaceCount())
206            .append(", bounds= ")
207            .append(getBounds())
208            .append(']');
209
210        return sb.toString();
211    }
212
213    /** Create a stream containing the results of applying {@code fn} to each face in
214     * the mesh.
215     * @param <T> Stream element type
216     * @param fn function used to extract the stream values from each face
217     * @return a stream containing the results of applying {@code fn} to each face in
218     *      the mesh
219     */
220    private <T> Stream<T> createFaceStream(final Function<TriangleMesh.Face, T> fn) {
221        final Iterable<T> iterable = () -> new FaceIterator<>(fn);
222        return StreamSupport.stream(iterable.spliterator(), false);
223    }
224
225    /** Return a builder for creating new triangle mesh objects.
226     * @param precision precision object used for floating point comparisons
227     * @return a builder for creating new triangle mesh objects
228     */
229    public static Builder builder(final Precision.DoubleEquivalence precision) {
230        return new Builder(precision);
231    }
232
233    /** Construct a new triangle mesh from the given vertices and face indices.
234     * @param vertices vertices for the mesh
235     * @param faces face indices for the mesh
236     * @param precision precision context used for floating point comparisons
237     * @return a new triangle mesh instance
238     * @throws IllegalArgumentException if any of the face index arrays does not have exactly 3 elements or
239     *       if any index is not a valid index into the vertex list
240     */
241    public static SimpleTriangleMesh from(final Vector3D[] vertices, final int[][] faces,
242                                          final Precision.DoubleEquivalence precision) {
243        return from(Arrays.asList(vertices), Arrays.asList(faces), precision);
244    }
245
246    /** Construct a new triangle mesh from the given vertices and face indices.
247     * @param vertices vertices for the mesh
248     * @param faces face indices for the mesh
249     * @param precision precision context used for floating point comparisons
250     * @return a new triangle mesh instance
251     * @throws IllegalArgumentException if any of the face index arrays does not have exactly 3 elements or
252     *       if any index is not a valid index into the vertex list
253     */
254    public static SimpleTriangleMesh from(final Collection<Vector3D> vertices, final Collection<int[]> faces,
255                                          final Precision.DoubleEquivalence precision) {
256        final Builder builder = builder(precision);
257
258        return builder.addVertices(vertices)
259                .addFaces(faces)
260                .build();
261    }
262
263    /** Construct a new mesh instance containing all triangles from the given boundary
264     * source. Equivalent vertices are reused wherever possible.
265     * @param boundarySrc boundary source to construct a mesh from
266     * @param precision precision context used for floating point comparisons
267     * @return new mesh instance containing all triangles from the given boundary
268     *      source
269     * @throws IllegalStateException if any boundary in the boundary source has infinite size and cannot
270     *      be converted to triangles
271     */
272    public static SimpleTriangleMesh from(final BoundarySource3D boundarySrc,
273            final Precision.DoubleEquivalence precision) {
274        final Builder builder = builder(precision);
275        try (Stream<Triangle3D> stream = boundarySrc.triangleStream()) {
276            stream.forEach(tri -> builder.addFaceUsingVertices(
277                tri.getPoint1(),
278                tri.getPoint2(),
279                tri.getPoint3()));
280        }
281
282        return builder.build();
283    }
284
285    /** Internal implementation of {@link TriangleMesh.Face}.
286     */
287    private final class SimpleTriangleFace implements TriangleMesh.Face {
288
289        /** The index of the face in the mesh. */
290        private final int index;
291
292        /** Vertex indices for the face. */
293        private final int[] vertexIndices;
294
295        SimpleTriangleFace(final int index, final int[] vertexIndices) {
296            this.index = index;
297            this.vertexIndices = vertexIndices;
298        }
299
300        /** {@inheritDoc} */
301        @Override
302        public int getIndex() {
303            return index;
304        }
305
306        /** {@inheritDoc} */
307        @Override
308        public int[] getVertexIndices() {
309            return vertexIndices.clone();
310        }
311
312        /** {@inheritDoc} */
313        @Override
314        public List<Vector3D> getVertices() {
315            return Arrays.asList(
316                    getPoint1(),
317                    getPoint2(),
318                    getPoint3());
319        }
320
321        /** {@inheritDoc} */
322        @Override
323        public Vector3D getPoint1() {
324            return vertices.get(vertexIndices[0]);
325        }
326
327        /** {@inheritDoc} */
328        @Override
329        public Vector3D getPoint2() {
330            return vertices.get(vertexIndices[1]);
331        }
332
333        /** {@inheritDoc} */
334        @Override
335        public Vector3D getPoint3() {
336            return vertices.get(vertexIndices[2]);
337        }
338
339        /** {@inheritDoc} */
340        @Override
341        public boolean definesPolygon() {
342            final Vector3D p1 = getPoint1();
343            final Vector3D v1 = p1.vectorTo(getPoint2());
344            final Vector3D v2 = p1.vectorTo(getPoint3());
345
346            return !precision.eqZero(v1.cross(v2).norm());
347        }
348
349        /** {@inheritDoc} */
350        @Override
351        public Triangle3D getPolygon() {
352            return Planes.triangleFromVertices(
353                    getPoint1(),
354                    getPoint2(),
355                    getPoint3(),
356                    precision);
357        }
358
359        /** {@inheritDoc} */
360        @Override
361        public String toString() {
362            final StringBuilder sb = new StringBuilder();
363            sb.append(getClass().getSimpleName())
364                .append("[index= ")
365                .append(getIndex())
366                .append(", vertexIndices= ")
367                .append(Arrays.toString(getVertexIndices()))
368                .append(", vertices= ")
369                .append(getVertices())
370                .append(']');
371
372            return sb.toString();
373        }
374    }
375
376    /** Internal class for iterating through the mesh faces and extracting a value from each.
377     * @param <T> Type returned by the iterator
378     */
379    private final class FaceIterator<T> implements Iterator<T> {
380
381        /** The current index of the iterator. */
382        private int index;
383
384        /** Function to apply to each face in the mesh. */
385        private final Function<? super TriangleMesh.Face, T> fn;
386
387        /** Construct a new instance for iterating through the mesh faces and extracting
388         * a value from each.
389         * @param fn function to apply to each face in order to obtain the iterated value
390         */
391        FaceIterator(final Function<? super TriangleMesh.Face, T> fn) {
392            this.fn = fn;
393        }
394
395        /** {@inheritDoc} */
396        @Override
397        public boolean hasNext() {
398            return index < faces.size();
399        }
400
401        /** {@inheritDoc} */
402        @Override
403        public T next() {
404            if (hasNext()) {
405                final Face face = getFace(index++);
406                return fn.apply(face);
407            }
408            throw new NoSuchElementException();
409        }
410    }
411
412    /** Builder class for creating mesh instances.
413     */
414    public static final class Builder {
415
416        /** List of vertices. */
417        private final ArrayList<Vector3D> vertices = new ArrayList<>();
418
419        /** Map of vertices to their first occurrence in the vertex list. */
420        private Map<Vector3D, Integer> vertexIndexMap;
421
422        /** List of face vertex indices. */
423        private final ArrayList<int[]> faces = new ArrayList<>();
424
425        /** Object used to construct the 3D bounds of the vertex list. */
426        private final Bounds3D.Builder boundsBuilder = Bounds3D.builder();
427
428        /** Precision context used for floating point comparisons; this value may be null
429         * if vertices are not to be combined in this builder.
430         */
431        private final Precision.DoubleEquivalence precision;
432
433        /** Flag set to true once a mesh is constructed from this builder. */
434        private boolean built;
435
436        /** Construct a new builder.
437         * @param precision precision context used for floating point comparisons; may
438         *      be null if vertices are not to be combined in this builder.
439         */
440        private Builder(final Precision.DoubleEquivalence precision) {
441            Objects.requireNonNull(precision, "Precision context must not be null");
442
443            this.precision = precision;
444        }
445
446        /** Use a vertex in the constructed mesh. If an equivalent vertex already exist, as determined
447         * by the configured {@link Precision.DoubleEquivalence}, then the index of the previously added
448         * vertex is returned. Otherwise, the given vertex is added to the vertex list and the index
449         * of the new entry is returned. This is in contrast with the {@link #addVertex(Vector3D)},
450         * which always adds a new entry to the vertex list.
451         * @param vertex vertex to use
452         * @return the index of the added vertex or an equivalent vertex that was added previously
453         * @see #addVertex(Vector3D)
454         */
455        public int useVertex(final Vector3D vertex) {
456            final int nextIdx = vertices.size();
457            final int actualIdx = addToVertexIndexMap(vertex, nextIdx, getVertexIndexMap());
458
459            // add to the vertex list if not already present
460            if (actualIdx == nextIdx) {
461                addToVertexList(vertex);
462            }
463
464            return actualIdx;
465        }
466
467        /** Add a vertex directly to the vertex list, returning the index of the added vertex.
468         * The vertex is added regardless of whether or not an equivalent vertex already
469         * exists in the list. This is in contrast with the {@link #useVertex(Vector3D)} method,
470         * which only adds a new entry to the vertex list if an equivalent one does not
471         * already exist.
472         * @param vertex the vertex to append
473         * @return the index of the appended vertex in the vertex list
474         */
475        public int addVertex(final Vector3D vertex) {
476            final int idx = addToVertexList(vertex);
477
478            if (vertexIndexMap != null) {
479                // add to the map in order to keep it in sync
480                addToVertexIndexMap(vertex, idx, vertexIndexMap);
481            }
482
483            return idx;
484        }
485
486        /** Add a group of vertices directly to the vertex list. No equivalent vertices are reused.
487         * @param newVertices vertices to append
488         * @return this instance
489         * @see #addVertex(Vector3D)
490         */
491        public Builder addVertices(final Vector3D[] newVertices) {
492            return addVertices(Arrays.asList(newVertices));
493        }
494
495        /** Add a group of vertices directly to the vertex list. No equivalent vertices are reused.
496         * @param newVertices vertices to append
497         * @return this instance
498         * @see #addVertex(Vector3D)
499         */
500        public Builder addVertices(final Collection<? extends Vector3D> newVertices) {
501            final int newSize = vertices.size() + newVertices.size();
502            ensureVertexCapacity(newSize);
503
504            for (final Vector3D vertex : newVertices) {
505                addVertex(vertex);
506            }
507
508            return this;
509        }
510
511        /** Ensure that this instance has enough capacity to store at least {@code numVertices}
512         * number of vertices without reallocating space. This can be used to help improve performance
513         * and memory usage when creating meshes with large numbers of vertices.
514         * @param numVertices the number of vertices to ensure that this instance can contain
515         * @return this instance
516         */
517        public Builder ensureVertexCapacity(final int numVertices) {
518            vertices.ensureCapacity(numVertices);
519            return this;
520        }
521
522        /** Get the current number of vertices in this mesh.
523         * @return the current number of vertices in this mesh
524         */
525        public int getVertexCount() {
526            return vertices.size();
527        }
528
529        /** Get the vertex at the given index.
530         * @param index index of the vertex to retrieve
531         * @return vertex at the given index
532         * @throws IndexOutOfBoundsException if the index is out of bounds of the mesh vertex list
533         */
534        public Vector3D getVertex(final int index) {
535            return vertices.get(index);
536        }
537
538        /** Append a face to this mesh.
539         * @param index1 index of the first vertex in the face
540         * @param index2 index of the second vertex in the face
541         * @param index3 index of the third vertex in the face
542         * @return this instance
543         * @throws IllegalArgumentException if any of the arguments is not a valid index into
544         *      the current vertex list
545         */
546        public Builder addFace(final int index1, final int index2, final int index3) {
547            validateCanModify();
548
549            final int[] indices = {
550                validateVertexIndex(index1),
551                validateVertexIndex(index2),
552                validateVertexIndex(index3)
553            };
554
555            faces.add(indices);
556
557            return this;
558        }
559
560        /** Append a face to this mesh.
561         * @param face array containing the 3 vertex indices defining the face
562         * @return this instance
563         * @throws IllegalArgumentException if {@code face} does not contain exactly 3 elements
564         *      or if any of the vertex indices is not a valid index into the current vertex list
565         */
566        public Builder addFace(final int[] face) {
567            if (face.length != EuclideanUtils.TRIANGLE_VERTEX_COUNT) {
568                throw new IllegalArgumentException("Face must contain " + EuclideanUtils.TRIANGLE_VERTEX_COUNT +
569                        " vertex indices; found " + face.length);
570            }
571
572            addFace(face[0], face[1], face[2]);
573
574            return this;
575        }
576
577        /** Append a group of faces to this mesh.
578         * @param faceIndices faces to append
579         * @return this instance
580         * @throws IllegalArgumentException if any of the face index arrays does not have exactly 3 elements or
581         *       if any index is not a valid index into the current vertex list
582         */
583        public Builder addFaces(final int[][] faceIndices) {
584            return addFaces(Arrays.asList(faceIndices));
585        }
586
587        /** Append a group of faces to this mesh.
588         * @param faceIndices faces to append
589         * @return this instance
590         * @throws IllegalArgumentException if any of the face index arrays does not have exactly 3 elements or
591         *       if any index is not a valid index into the current vertex list
592         */
593        public Builder addFaces(final Collection<int[]> faceIndices) {
594            final int newSize = faces.size() + faceIndices.size();
595            ensureFaceCapacity(newSize);
596
597            for (final int[] face : faceIndices) {
598                addFace(face);
599            }
600
601            return this;
602        }
603
604        /** Add a face to this mesh, only adding vertices to the vertex list if equivalent vertices are
605         * not found.
606         * @param p1 first face vertex
607         * @param p2 second face vertex
608         * @param p3 third face vertex
609         * @return this instance
610         * @see #useVertex(Vector3D)
611         */
612        public Builder addFaceUsingVertices(final Vector3D p1, final Vector3D p2, final Vector3D p3) {
613            return addFace(
614                        useVertex(p1),
615                        useVertex(p2),
616                        useVertex(p3)
617                    );
618        }
619
620        /** Add a face and its vertices to this mesh. The vertices are always added to the vertex list,
621         * regardless of whether or not equivalent vertices exist in the vertex list.
622         * @param p1 first face vertex
623         * @param p2 second face vertex
624         * @param p3 third face vertex
625         * @return this instance
626         * @see #addVertex(Vector3D)
627         */
628        public Builder addFaceAndVertices(final Vector3D p1, final Vector3D p2, final Vector3D p3) {
629            return addFace(
630                        addVertex(p1),
631                        addVertex(p2),
632                        addVertex(p3)
633                    );
634        }
635
636        /** Ensure that this instance has enough capacity to store at least {@code numFaces}
637         * number of faces without reallocating space. This can be used to help improve performance
638         * and memory usage when creating meshes with large numbers of faces.
639         * @param numFaces the number of faces to ensure that this instance can contain
640         * @return this instance
641         */
642        public Builder ensureFaceCapacity(final int numFaces) {
643            faces.ensureCapacity(numFaces);
644            return this;
645        }
646
647        /** Get the current number of faces in this mesh.
648         * @return the current number of faces in this meshr
649         */
650        public int getFaceCount() {
651            return faces.size();
652        }
653
654        /** Build a triangle mesh containing the vertices and faces in this builder.
655         * @return a triangle mesh containing the vertices and faces in this builder
656         */
657        public SimpleTriangleMesh build() {
658            built = true;
659
660            final Bounds3D bounds = boundsBuilder.hasBounds() ?
661                    boundsBuilder.build() :
662                    null;
663
664            vertices.trimToSize();
665            faces.trimToSize();
666
667            return new SimpleTriangleMesh(
668                    vertices,
669                    faces,
670                    bounds,
671                    precision);
672        }
673
674        /** Get the vertex index map, creating and initializing it if needed.
675         * @return the vertex index map
676         */
677        private Map<Vector3D, Integer> getVertexIndexMap() {
678            if (vertexIndexMap == null) {
679                vertexIndexMap = new TreeMap<>(new FuzzyVectorComparator(precision));
680
681                // populate the index map
682                final int size = vertices.size();
683                for (int i = 0; i < size; ++i) {
684                    addToVertexIndexMap(vertices.get(i), i, vertexIndexMap);
685                }
686            }
687            return vertexIndexMap;
688        }
689
690        /** Add a vertex to the given vertex index map. The vertex is inserted and mapped to {@code targetidx}
691         *  if an equivalent vertex does not already exist. The index now associated with the given vertex
692         *  or its equivalent is returned.
693         * @param vertex vertex to add
694         * @param targetIdx the index to associate with the vertex if no equivalent vertex has already been
695         *      mapped
696         * @param map vertex index map
697         * @return the index now associated with the given vertex or its equivalent
698         */
699        private int addToVertexIndexMap(final Vector3D vertex, final int targetIdx,
700                final Map<? super Vector3D, Integer> map) {
701            validateCanModify();
702
703            final Integer actualIdx = map.putIfAbsent(vertex, targetIdx);
704
705            return actualIdx != null ?
706                    actualIdx :
707                    targetIdx;
708        }
709
710        /** Append the given vertex to the end of the vertex list. The index of the vertex is returned.
711         * @param vertex the vertex to append
712         * @return the index of the appended vertex
713         */
714        private int addToVertexList(final Vector3D vertex) {
715            validateCanModify();
716
717            boundsBuilder.add(vertex);
718
719            final int idx = vertices.size();
720            vertices.add(vertex);
721
722            return idx;
723        }
724
725        /** Throw an exception if the given vertex index is not valid.
726         * @param idx vertex index to validate
727         * @return the validated index
728         * @throws IllegalArgumentException if the given index is not a valid index into
729         *      the vertices list
730         */
731        private int validateVertexIndex(final int idx) {
732            if (idx < 0 || idx >= vertices.size()) {
733                throw new IllegalArgumentException("Invalid vertex index: " + idx);
734            }
735
736            return idx;
737        }
738
739        /** Throw an exception if the builder has been used to construct a mesh instance
740         * and can no longer be modified.
741         */
742        private void validateCanModify() {
743            if (built) {
744                throw new IllegalStateException("Builder instance cannot be modified: mesh construction is complete");
745            }
746        }
747    }
748
749    /** Comparator used to sort vectors using non-strict ("fuzzy") comparisons.
750     * Vectors are considered equal if their values in all coordinate dimensions
751     * are equivalent as evaluated by the precision context.
752     */
753    private static final class FuzzyVectorComparator implements Comparator<Vector3D> {
754        /** Precision context to determine floating-point equality. */
755        private final Precision.DoubleEquivalence precision;
756
757        /** Construct a new instance that uses the given precision context for
758         * floating point comparisons.
759         * @param precision precision context used for floating point comparisons
760         */
761        FuzzyVectorComparator(final Precision.DoubleEquivalence precision) {
762            this.precision = precision;
763        }
764
765        /** {@inheritDoc} */
766        @Override
767        public int compare(final Vector3D a, final Vector3D b) {
768            int result = precision.compare(a.getX(), b.getX());
769            if (result == 0) {
770                result = precision.compare(a.getY(), b.getY());
771                if (result == 0) {
772                    result = precision.compare(a.getZ(), b.getZ());
773                }
774            }
775
776            return result;
777        }
778    }
779}