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
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.NoSuchElementException;
28 import java.util.Objects;
29 import java.util.TreeMap;
30 import java.util.function.Function;
31 import java.util.stream.Stream;
32 import java.util.stream.StreamSupport;
33
34 import org.apache.commons.geometry.core.Transform;
35 import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
36 import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
37 import org.apache.commons.geometry.euclidean.threed.Bounds3D;
38 import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
39 import org.apache.commons.geometry.euclidean.threed.Planes;
40 import org.apache.commons.geometry.euclidean.threed.Triangle3D;
41 import org.apache.commons.geometry.euclidean.threed.Vector3D;
42 import org.apache.commons.numbers.core.Precision;
43
44 /** A simple implementation of the {@link TriangleMesh} interface. This class ensures that
45 * faces always contain 3 valid references into the vertex list but does not enforce that
46 * the referenced vertices are unique or that they define a triangle with non-zero size. For
47 * example, a mesh could contain a face with 3 vertices that are considered equivalent by the
48 * configured precision context. Attempting to call the {@link TriangleMesh.Face#getPolygon()}
49 * method on such a face results in an exception. The
50 * {@link TriangleMesh.Face#definesPolygon()} method can be used to determine if a face defines
51 * a valid triangle.
52 *
53 * <p>Instances of this class are guaranteed to be immutable.</p>
54 */
55 public final class SimpleTriangleMesh implements TriangleMesh {
56
57 /** Vertices in the mesh. */
58 private final List<Vector3D> vertices;
59
60 /** Faces in the mesh. */
61 private final List<int[]> faces;
62
63 /** The bounds of the mesh. */
64 private final Bounds3D bounds;
65
66 /** Object used for floating point comparisons. */
67 private final Precision.DoubleEquivalence precision;
68
69 /** Construct a new instance from a vertex list and set of faces. No validation is
70 * performed on the input.
71 * @param vertices vertex list
72 * @param faces face indices list
73 * @param bounds mesh bounds
74 * @param precision precision context used when creating face polygons
75 */
76 private SimpleTriangleMesh(final List<Vector3D> vertices, final List<int[]> faces, final Bounds3D bounds,
77 final Precision.DoubleEquivalence precision) {
78 this.vertices = Collections.unmodifiableList(vertices);
79 this.faces = Collections.unmodifiableList(faces);
80 this.bounds = bounds;
81 this.precision = precision;
82 }
83
84 /** {@inheritDoc} */
85 @Override
86 public Iterable<Vector3D> vertices() {
87 return getVertices();
88 }
89
90 /** {@inheritDoc} */
91 @Override
92 public List<Vector3D> getVertices() {
93 return vertices;
94 }
95
96 /** {@inheritDoc} */
97 @Override
98 public int getVertexCount() {
99 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 }