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.core.precision.DoublePrecisionContext; 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; 042 043/** A simple implementation of the {@link TriangleMesh} interface. This class ensures that 044 * faces always contain 3 valid references into the vertex list but does not enforce that 045 * the referenced vertices are unique or that they define a triangle with non-zero size. For 046 * example, a mesh could contain a face with 3 vertices that are considered equivalent by the 047 * configured precision context. Attempting to call the {@link TriangleMesh.Face#getPolygon()} 048 * method on such a face results in an exception. The 049 * {@link TriangleMesh.Face#definesPolygon()} method can be used to determine if a face defines 050 * a valid triangle. 051 * 052 * <p>Instances of this class are guaranteed to be immutable.</p> 053 */ 054public final class SimpleTriangleMesh implements TriangleMesh { 055 056 /** Vertices in the mesh. */ 057 private final List<Vector3D> vertices; 058 059 /** Faces in the mesh. */ 060 private final List<int[]> faces; 061 062 /** The bounds of the mesh. */ 063 private final Bounds3D bounds; 064 065 /** Object used for floating point comparisons. */ 066 private final DoublePrecisionContext precision; 067 068 /** Construct a new instance from a vertex list and set of faces. No validation is 069 * performed on the input. 070 * @param vertices vertex list 071 * @param faces face indices list 072 * @param bounds mesh bounds 073 * @param precision precision context used when creating face polygons 074 */ 075 private SimpleTriangleMesh(final List<Vector3D> vertices, final List<int[]> faces, final Bounds3D bounds, 076 final DoublePrecisionContext precision) { 077 this.vertices = Collections.unmodifiableList(vertices); 078 this.faces = Collections.unmodifiableList(faces); 079 this.bounds = bounds; 080 this.precision = precision; 081 } 082 083 /** {@inheritDoc} */ 084 @Override 085 public Iterable<Vector3D> vertices() { 086 return getVertices(); 087 } 088 089 /** {@inheritDoc} */ 090 @Override 091 public List<Vector3D> getVertices() { 092 return vertices; 093 } 094 095 /** {@inheritDoc} */ 096 @Override 097 public int getVertexCount() { 098 return vertices.size(); 099 } 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}