SphereGenerator.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://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. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.geometry.euclidean.threed;

  22. import java.util.Arrays;
  23. import java.util.List;

  24. import org.hipparchus.fraction.BigFraction;
  25. import org.hipparchus.geometry.enclosing.EnclosingBall;
  26. import org.hipparchus.geometry.enclosing.SupportBallGenerator;
  27. import org.hipparchus.geometry.euclidean.twod.DiskGenerator;
  28. import org.hipparchus.geometry.euclidean.twod.Euclidean2D;
  29. import org.hipparchus.geometry.euclidean.twod.Vector2D;
  30. import org.hipparchus.util.FastMath;

  31. /** Class generating an enclosing ball from its support points.
  32.  */
  33. public class SphereGenerator implements SupportBallGenerator<Euclidean3D, Vector3D> {

  34.     /** Empty constructor.
  35.      * <p>
  36.      * This constructor is not strictly necessary, but it prevents spurious
  37.      * javadoc warnings with JDK 18 and later.
  38.      * </p>
  39.      * @since 3.0
  40.      */
  41.     public SphereGenerator() { // NOPMD - unnecessary constructor added intentionally to make javadoc happy
  42.         // nothing to do
  43.     }

  44.     /** {@inheritDoc} */
  45.     @Override
  46.     public EnclosingBall<Euclidean3D, Vector3D> ballOnSupport(final List<Vector3D> support) {

  47.         if (support.isEmpty()) {
  48.             return new EnclosingBall<>(Vector3D.ZERO, Double.NEGATIVE_INFINITY);
  49.         } else {
  50.             final Vector3D vA = support.get(0);
  51.             if (support.size() < 2) {
  52.                 return new EnclosingBall<>(vA, 0, vA);
  53.             } else {
  54.                 final Vector3D vB = support.get(1);
  55.                 if (support.size() < 3) {

  56.                     final Vector3D center = new Vector3D(0.5, vA, 0.5, vB);

  57.                     // we could have computed r directly from the vA and vB
  58.                     // (it was done this way up to Hipparchus 1.0), but as center
  59.                     // is approximated in the computation above, it is better to
  60.                     // take the final value of center and compute r from the distances
  61.                     // to center of all support points, using a max to ensure all support
  62.                     // points belong to the ball
  63.                     // see <https://github.com/Hipparchus-Math/hipparchus/issues/20>
  64.                     final double r = FastMath.max(Vector3D.distance(vA, center),
  65.                                                   Vector3D.distance(vB, center));
  66.                     return new EnclosingBall<>(center, r, vA, vB);

  67.                 } else {
  68.                     final Vector3D vC = support.get(2);
  69.                     if (support.size() < 4) {

  70.                         // delegate to 2D disk generator
  71.                         final Plane p = new Plane(vA, vB, vC,
  72.                                                   1.0e-10 * (vA.getNorm1() + vB.getNorm1() + vC.getNorm1()));
  73.                         final EnclosingBall<Euclidean2D, Vector2D> disk =
  74.                                 new DiskGenerator().ballOnSupport(Arrays.asList(p.toSubSpace(vA),
  75.                                                                                 p.toSubSpace(vB),
  76.                                                                                 p.toSubSpace(vC)));

  77.                         // convert back to 3D
  78.                         final Vector3D center = p.toSpace(disk.getCenter());

  79.                         // we could have computed r directly from the vA and vB
  80.                         // (it was done this way up to Hipparchus 1.0), but as center
  81.                         // is approximated in the computation above, it is better to
  82.                         // take the final value of center and compute r from the distances
  83.                         // to center of all support points, using a max to ensure all support
  84.                         // points belong to the ball
  85.                         // see <https://github.com/Hipparchus-Math/hipparchus/issues/20>
  86.                         final double r = FastMath.max(Vector3D.distance(vA, center),
  87.                                                       FastMath.max(Vector3D.distance(vB, center),
  88.                                                                    Vector3D.distance(vC, center)));
  89.                         return new EnclosingBall<>(center, r, vA, vB, vC);

  90.                     } else {
  91.                         final Vector3D vD = support.get(3);
  92.                         // a sphere is 3D can be defined as:
  93.                         // (1)   (x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = r^2
  94.                         // which can be written:
  95.                         // (2)   (x^2 + y^2 + z^2) - 2 x_0 x - 2 y_0 y - 2 z_0 z + (x_0^2 + y_0^2 + z_0^2 - r^2) = 0
  96.                         // or simply:
  97.                         // (3)   (x^2 + y^2 + z^2) + a x + b y + c z + d = 0
  98.                         // with sphere center coordinates -a/2, -b/2, -c/2
  99.                         // If the sphere exists, a b, c and d are a non zero solution to
  100.                         // [ (x^2  + y^2  + z^2)    x    y   z    1 ]   [ 1 ]   [ 0 ]
  101.                         // [ (xA^2 + yA^2 + zA^2)   xA   yA  zA   1 ]   [ a ]   [ 0 ]
  102.                         // [ (xB^2 + yB^2 + zB^2)   xB   yB  zB   1 ] * [ b ] = [ 0 ]
  103.                         // [ (xC^2 + yC^2 + zC^2)   xC   yC  zC   1 ]   [ c ]   [ 0 ]
  104.                         // [ (xD^2 + yD^2 + zD^2)   xD   yD  zD   1 ]   [ d ]   [ 0 ]
  105.                         // So the determinant of the matrix is zero. Computing this determinant
  106.                         // by expanding it using the minors m_ij of first row leads to
  107.                         // (4)   m_11 (x^2 + y^2 + z^2) - m_12 x + m_13 y - m_14 z + m_15 = 0
  108.                         // So by identifying equations (2) and (4) we get the coordinates
  109.                         // of center as:
  110.                         //      x_0 = +m_12 / (2 m_11)
  111.                         //      y_0 = -m_13 / (2 m_11)
  112.                         //      z_0 = +m_14 / (2 m_11)
  113.                         // Note that the minors m_11, m_12, m_13 and m_14 all have the last column
  114.                         // filled with 1.0, hence simplifying the computation
  115.                         final BigFraction[] c2 = {
  116.                             new BigFraction(vA.getX()), new BigFraction(vB.getX()),
  117.                             new BigFraction(vC.getX()), new BigFraction(vD.getX())
  118.                         };
  119.                         final BigFraction[] c3 = {
  120.                             new BigFraction(vA.getY()), new BigFraction(vB.getY()),
  121.                             new BigFraction(vC.getY()), new BigFraction(vD.getY())
  122.                         };
  123.                         final BigFraction[] c4 = {
  124.                             new BigFraction(vA.getZ()), new BigFraction(vB.getZ()),
  125.                             new BigFraction(vC.getZ()), new BigFraction(vD.getZ())
  126.                         };
  127.                         final BigFraction[] c1 = {
  128.                             c2[0].multiply(c2[0]).add(c3[0].multiply(c3[0])).add(c4[0].multiply(c4[0])),
  129.                             c2[1].multiply(c2[1]).add(c3[1].multiply(c3[1])).add(c4[1].multiply(c4[1])),
  130.                             c2[2].multiply(c2[2]).add(c3[2].multiply(c3[2])).add(c4[2].multiply(c4[2])),
  131.                             c2[3].multiply(c2[3]).add(c3[3].multiply(c3[3])).add(c4[3].multiply(c4[3]))
  132.                         };
  133.                         final BigFraction twoM11 = minor(c2, c3, c4).multiply(2);
  134.                         final BigFraction m12    = minor(c1, c3, c4);
  135.                         final BigFraction m13    = minor(c1, c2, c4);
  136.                         final BigFraction m14    = minor(c1, c2, c3);
  137.                         final Vector3D center    = new Vector3D( m12.divide(twoM11).doubleValue(),
  138.                                                                 -m13.divide(twoM11).doubleValue(),
  139.                                                                  m14.divide(twoM11).doubleValue());

  140.                         // we could have computed r directly from the minors above
  141.                         // (it was done this way up to Hipparchus 1.0), but as center
  142.                         // is approximated in the computation above, it is better to
  143.                         // take the final value of center and compute r from the distances
  144.                         // to center of all support points, using a max to ensure all support
  145.                         // points belong to the ball
  146.                         // see <https://github.com/Hipparchus-Math/hipparchus/issues/20>
  147.                         final double r = FastMath.max(Vector3D.distance(vA, center),
  148.                                                       FastMath.max(Vector3D.distance(vB, center),
  149.                                                                    FastMath.max(Vector3D.distance(vC, center),
  150.                                                                                 Vector3D.distance(vD, center))));
  151.                         return new EnclosingBall<>(center, r, vA, vB, vC, vD);

  152.                     }
  153.                 }
  154.             }
  155.         }
  156.     }

  157.     /** Compute a dimension 4 minor, when 4<sup>th</sup> column is known to be filled with 1.0.
  158.      * @param c1 first column
  159.      * @param c2 second column
  160.      * @param c3 third column
  161.      * @return value of the minor computed has an exact fraction
  162.      */
  163.     private BigFraction minor(final BigFraction[] c1, final BigFraction[] c2, final BigFraction[] c3) {
  164.         return      c2[0].multiply(c3[1]).multiply(c1[2].subtract(c1[3])).
  165.                 add(c2[0].multiply(c3[2]).multiply(c1[3].subtract(c1[1]))).
  166.                 add(c2[0].multiply(c3[3]).multiply(c1[1].subtract(c1[2]))).
  167.                 add(c2[1].multiply(c3[0]).multiply(c1[3].subtract(c1[2]))).
  168.                 add(c2[1].multiply(c3[2]).multiply(c1[0].subtract(c1[3]))).
  169.                 add(c2[1].multiply(c3[3]).multiply(c1[2].subtract(c1[0]))).
  170.                 add(c2[2].multiply(c3[0]).multiply(c1[1].subtract(c1[3]))).
  171.                 add(c2[2].multiply(c3[1]).multiply(c1[3].subtract(c1[0]))).
  172.                 add(c2[2].multiply(c3[3]).multiply(c1[0].subtract(c1[1]))).
  173.                 add(c2[3].multiply(c3[0]).multiply(c1[2].subtract(c1[1]))).
  174.                 add(c2[3].multiply(c3[1]).multiply(c1[0].subtract(c1[2]))).
  175.                 add(c2[3].multiply(c3[2]).multiply(c1[1].subtract(c1[0])));
  176.     }

  177. }