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