PolyhedronsSet.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.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.Collection;
  25. import java.util.List;

  26. import org.hipparchus.exception.LocalizedCoreFormats;
  27. import org.hipparchus.exception.MathIllegalArgumentException;
  28. import org.hipparchus.exception.MathRuntimeException;
  29. import org.hipparchus.geometry.LocalizedGeometryFormats;
  30. import org.hipparchus.geometry.Point;
  31. import org.hipparchus.geometry.euclidean.oned.Euclidean1D;
  32. import org.hipparchus.geometry.euclidean.oned.OrientedPoint;
  33. import org.hipparchus.geometry.euclidean.oned.SubOrientedPoint;
  34. import org.hipparchus.geometry.euclidean.oned.Vector1D;
  35. import org.hipparchus.geometry.euclidean.twod.Euclidean2D;
  36. import org.hipparchus.geometry.euclidean.twod.PolygonsSet;
  37. import org.hipparchus.geometry.euclidean.twod.SubLine;
  38. import org.hipparchus.geometry.euclidean.twod.Vector2D;
  39. import org.hipparchus.geometry.partitioning.AbstractRegion;
  40. import org.hipparchus.geometry.partitioning.BSPTree;
  41. import org.hipparchus.geometry.partitioning.BSPTreeVisitor;
  42. import org.hipparchus.geometry.partitioning.BoundaryAttribute;
  43. import org.hipparchus.geometry.partitioning.InteriorPointFinder;
  44. import org.hipparchus.geometry.partitioning.Region;
  45. import org.hipparchus.geometry.partitioning.RegionFactory;
  46. import org.hipparchus.geometry.partitioning.SubHyperplane;
  47. import org.hipparchus.geometry.partitioning.Transform;
  48. import org.hipparchus.util.FastMath;

  49. /** This class represents a 3D region: a set of polyhedrons.
  50.  */
  51. public class PolyhedronsSet
  52.     extends AbstractRegion<Euclidean3D, Vector3D, Plane, SubPlane,
  53.                            Euclidean2D, Vector2D, org.hipparchus.geometry.euclidean.twod.Line, SubLine> {

  54.     /** Build a polyhedrons set representing the whole real line.
  55.      * @param tolerance tolerance below which points are considered identical
  56.      */
  57.     public PolyhedronsSet(final double tolerance) {
  58.         super(tolerance);
  59.     }

  60.     /** Build a polyhedrons set from a BSP tree.
  61.      * <p>The leaf nodes of the BSP tree <em>must</em> have a
  62.      * {@code Boolean} attribute representing the inside status of
  63.      * the corresponding cell (true for inside cells, false for outside
  64.      * cells). In order to avoid building too many small objects, it is
  65.      * recommended to use the predefined constants
  66.      * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
  67.      * <p>
  68.      * This constructor is aimed at expert use, as building the tree may
  69.      * be a difficult task. It is not intended for general use and for
  70.      * performances reasons does not check thoroughly its input, as this would
  71.      * require walking the full tree each time. Failing to provide a tree with
  72.      * the proper attributes, <em>will</em> therefore generate problems like
  73.      * {@link NullPointerException} or {@link ClassCastException} only later on.
  74.      * This limitation is known and explains why this constructor is for expert
  75.      * use only. The caller does have the responsibility to provided correct arguments.
  76.      * </p>
  77.      * @param tree inside/outside BSP tree representing the region
  78.      * @param tolerance tolerance below which points are considered identical
  79.      */
  80.     public PolyhedronsSet(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> tree, final double tolerance) {
  81.         super(tree, tolerance);
  82.     }

  83.     /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by sub-hyperplanes.
  84.      * <p>The boundary is provided as a collection of {@link
  85.      * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
  86.      * interior part of the region on its minus side and the exterior on
  87.      * its plus side.</p>
  88.      * <p>The boundary elements can be in any order, and can form
  89.      * several non-connected sets (like for example polyhedrons with holes
  90.      * or a set of disjoint polyhedrons considered as a whole). In
  91.      * fact, the elements do not even need to be connected together
  92.      * (their topological connections are not used here). However, if the
  93.      * boundary does not really separate an inside open from an outside
  94.      * open (open having here its topological meaning), then subsequent
  95.      * calls to the {@link Region#checkPoint(Point) checkPoint} method will
  96.      * not be meaningful anymore.</p>
  97.      * <p>If the boundary is empty, the region will represent the whole
  98.      * space.</p>
  99.      * @param boundary collection of boundary elements, as a
  100.      * collection of {@link SubHyperplane SubHyperplane} objects
  101.      * @param tolerance tolerance below which points are considered identical
  102.      */
  103.     public PolyhedronsSet(final Collection<SubPlane> boundary,
  104.                           final double tolerance) {
  105.         super(boundary, tolerance);
  106.     }

  107.     /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by connected vertices.
  108.      * <p>
  109.      * The boundary is provided as a list of vertices and a list of facets.
  110.      * Each facet is specified as an integer array containing the arrays vertices
  111.      * indices in the vertices list. Each facet normal is oriented by right hand
  112.      * rule to the facet vertices list.
  113.      * </p>
  114.      * <p>
  115.      * Some basic sanity checks are performed but not everything is thoroughly
  116.      * assessed, so it remains under caller responsibility to ensure the vertices
  117.      * and facets are consistent and properly define a polyhedrons set.
  118.      * </p>
  119.      * @param vertices list of polyhedrons set vertices
  120.      * @param facets list of facets, as vertices indices in the vertices list
  121.      * @param tolerance tolerance below which points are considered identical
  122.      * @exception MathIllegalArgumentException if some basic sanity checks fail
  123.      */
  124.     public PolyhedronsSet(final List<Vector3D> vertices, final List<int[]> facets,
  125.                           final double tolerance) {
  126.         super(buildBoundary(vertices, facets, tolerance), tolerance);
  127.     }

  128.     /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by connected vertices.
  129.      * <p>
  130.      * Some basic sanity checks are performed but not everything is thoroughly
  131.      * assessed, so it remains under caller responsibility to ensure the vertices
  132.      * and facets are consistent and properly define a polyhedrons set.
  133.      * </p>
  134.      * @param brep Boundary REPresentation of the polyhedron to build
  135.      * @param tolerance tolerance below which points are considered identical
  136.      * @exception MathIllegalArgumentException if some basic sanity checks fail
  137.      * @since 1.2
  138.      */
  139.     public PolyhedronsSet(final BRep brep, final double tolerance) {
  140.         super(buildBoundary(brep.getVertices(), brep.getFacets(), tolerance), tolerance);
  141.     }

  142.     /** Build a parallellepipedic box.
  143.      * @param xMin low bound along the x direction
  144.      * @param xMax high bound along the x direction
  145.      * @param yMin low bound along the y direction
  146.      * @param yMax high bound along the y direction
  147.      * @param zMin low bound along the z direction
  148.      * @param zMax high bound along the z direction
  149.      * @param tolerance tolerance below which points are considered identical
  150.      */
  151.     public PolyhedronsSet(final double xMin, final double xMax,
  152.                           final double yMin, final double yMax,
  153.                           final double zMin, final double zMax,
  154.                           final double tolerance) {
  155.         super(buildBoundary(xMin, xMax, yMin, yMax, zMin, zMax, tolerance), tolerance);
  156.     }

  157.     /** Build a parallellepipedic box boundary.
  158.      * @param xMin low bound along the x direction
  159.      * @param xMax high bound along the x direction
  160.      * @param yMin low bound along the y direction
  161.      * @param yMax high bound along the y direction
  162.      * @param zMin low bound along the z direction
  163.      * @param zMax high bound along the z direction
  164.      * @param tolerance tolerance below which points are considered identical
  165.      * @return boundary tree
  166.      */
  167.     private static BSPTree<Euclidean3D, Vector3D, Plane, SubPlane>
  168.         buildBoundary(final double xMin, final double xMax,
  169.                       final double yMin, final double yMax,
  170.                       final double zMin, final double zMax,
  171.                       final double tolerance) {
  172.         if ((xMin >= xMax - tolerance) || (yMin >= yMax - tolerance) || (zMin >= zMax - tolerance)) {
  173.             // too thin box, build an empty polygons set
  174.             return new BSPTree<>(Boolean.FALSE);
  175.         }
  176.         final Plane pxMin = new Plane(new Vector3D(xMin, 0,    0),   Vector3D.MINUS_I, tolerance);
  177.         final Plane pxMax = new Plane(new Vector3D(xMax, 0,    0),   Vector3D.PLUS_I,  tolerance);
  178.         final Plane pyMin = new Plane(new Vector3D(0,    yMin, 0),   Vector3D.MINUS_J, tolerance);
  179.         final Plane pyMax = new Plane(new Vector3D(0,    yMax, 0),   Vector3D.PLUS_J,  tolerance);
  180.         final Plane pzMin = new Plane(new Vector3D(0,    0,   zMin), Vector3D.MINUS_K, tolerance);
  181.         final Plane pzMax = new Plane(new Vector3D(0,    0,   zMax), Vector3D.PLUS_K,  tolerance);
  182.         final RegionFactory<Euclidean3D, Vector3D, Plane, SubPlane> factory = new RegionFactory<>();
  183.         return factory.buildConvex(pxMin, pxMax, pyMin, pyMax, pzMin, pzMax).getTree(false);
  184.     }

  185.     /** Build boundary from vertices and facets.
  186.      * @param vertices list of polyhedrons set vertices
  187.      * @param facets list of facets, as vertices indices in the vertices list
  188.      * @param tolerance tolerance below which points are considered identical
  189.      * @return boundary as a list of sub-hyperplanes
  190.      * @exception MathIllegalArgumentException if some basic sanity checks fail
  191.      */
  192.     private static List<SubPlane> buildBoundary(final List<Vector3D> vertices, final List<int[]> facets, final double tolerance) {

  193.         // check vertices distances
  194.         for (int i = 0; i < vertices.size() - 1; ++i) {
  195.             final Vector3D vi = vertices.get(i);
  196.             for (int j = i + 1; j < vertices.size(); ++j) {
  197.                 if (Vector3D.distance(vi, vertices.get(j)) <= tolerance) {
  198.                     throw new MathIllegalArgumentException(LocalizedGeometryFormats.CLOSE_VERTICES,
  199.                                                            vi.getX(), vi.getY(), vi.getZ());
  200.                 }
  201.             }
  202.         }

  203.         // find how vertices are referenced by facets
  204.         final int[][] references = findReferences(vertices, facets);

  205.         // find how vertices are linked together by edges along the facets they belong to
  206.         final int[][] successors = successors(vertices, facets, references);

  207.         // check edges orientations
  208.         for (int vA = 0; vA < vertices.size(); ++vA) {
  209.             for (final int vB : successors[vA]) {

  210.                 if (vB >= 0) {
  211.                     // when facets are properly oriented, if vB is the successor of vA on facet f1,
  212.                     // then there must be an adjacent facet f2 where vA is the successor of vB
  213.                     boolean found = false;
  214.                     for (final int v : successors[vB]) {
  215.                         found = found || (v == vA);
  216.                     }
  217.                     if (!found) {
  218.                         final Vector3D start = vertices.get(vA);
  219.                         final Vector3D end   = vertices.get(vB);
  220.                         throw new MathIllegalArgumentException(LocalizedGeometryFormats.EDGE_CONNECTED_TO_ONE_FACET,
  221.                                                                start.getX(), start.getY(), start.getZ(),
  222.                                                                end.getX(),   end.getY(),   end.getZ());
  223.                     }
  224.                 }
  225.             }
  226.         }

  227.         final List<SubPlane> boundary = new ArrayList<>();

  228.         for (final int[] facet : facets) {

  229.             // define facet plane from the first 3 points
  230.             Plane plane = new Plane(vertices.get(facet[0]), vertices.get(facet[1]), vertices.get(facet[2]),
  231.                                     tolerance);

  232.             // check all points are in the plane
  233.             final Vector2D[] two2Points = new Vector2D[facet.length];
  234.             for (int i = 0 ; i < facet.length; ++i) {
  235.                 final Vector3D v = vertices.get(facet[i]);
  236.                 if (!plane.contains(v)) {
  237.                     throw new MathIllegalArgumentException(LocalizedGeometryFormats.OUT_OF_PLANE,
  238.                                                            v.getX(), v.getY(), v.getZ());
  239.                 }
  240.                 two2Points[i] = plane.toSubSpace(v);
  241.             }

  242.             // create the polygonal facet
  243.             boundary.add(new SubPlane(plane, new PolygonsSet(tolerance, two2Points)));

  244.         }

  245.         return boundary;

  246.     }

  247.     /** Find the facets that reference each edges.
  248.      * @param vertices list of polyhedrons set vertices
  249.      * @param facets list of facets, as vertices indices in the vertices list
  250.      * @return references array such that r[v][k] = f for some k if facet f contains vertex v
  251.      * @exception MathIllegalArgumentException if some facets have fewer than 3 vertices
  252.      */
  253.     private static int[][] findReferences(final List<Vector3D> vertices, final List<int[]> facets) {

  254.         // find the maximum number of facets a vertex belongs to
  255.         final int[] nbFacets = new int[vertices.size()];
  256.         int maxFacets  = 0;
  257.         for (final int[] facet : facets) {
  258.             if (facet.length < 3) {
  259.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.WRONG_NUMBER_OF_POINTS,
  260.                                                     3, facet.length, true);
  261.             }
  262.             for (final int index : facet) {
  263.                 maxFacets = FastMath.max(maxFacets, ++nbFacets[index]);
  264.             }
  265.         }

  266.         // set up the references array
  267.         final int[][] references = new int[vertices.size()][maxFacets];
  268.         for (int[] r : references) {
  269.             Arrays.fill(r, -1);
  270.         }
  271.         for (int f = 0; f < facets.size(); ++f) {
  272.             for (final int v : facets.get(f)) {
  273.                 // vertex v is referenced by facet f
  274.                 int k = 0;
  275.                 while (k < maxFacets && references[v][k] >= 0) {
  276.                     ++k;
  277.                 }
  278.                 references[v][k] = f;
  279.             }
  280.         }

  281.         return references;

  282.     }

  283.     /** Find the successors of all vertices among all facets they belong to.
  284.      * @param vertices list of polyhedrons set vertices
  285.      * @param facets list of facets, as vertices indices in the vertices list
  286.      * @param references facets references array
  287.      * @return indices of vertices that follow vertex v in some facet (the array
  288.      * may contain extra entries at the end, set to negative indices)
  289.      * @exception MathIllegalArgumentException if the same vertex appears more than
  290.      * once in the successors list (which means one facet orientation is wrong)
  291.      */
  292.     private static int[][] successors(final List<Vector3D> vertices, final List<int[]> facets,
  293.                                       final int[][] references) {

  294.         // create an array large enough
  295.         final int[][] successors = new int[vertices.size()][references[0].length];
  296.         for (final int[] s : successors) {
  297.             Arrays.fill(s, -1);
  298.         }

  299.         for (int v = 0; v < vertices.size(); ++v) {
  300.             for (int k = 0; k < successors[v].length && references[v][k] >= 0; ++k) {

  301.                 // look for vertex v
  302.                 final int[] facet = facets.get(references[v][k]);
  303.                 int i = 0;
  304.                 while (i < facet.length && facet[i] != v) {
  305.                     ++i;
  306.                 }

  307.                 // we have found vertex v, we deduce its successor on current facet
  308.                 successors[v][k] = facet[(i + 1) % facet.length];
  309.                 for (int l = 0; l < k; ++l) {
  310.                     if (successors[v][l] == successors[v][k]) {
  311.                         final Vector3D start = vertices.get(v);
  312.                         final Vector3D end   = vertices.get(successors[v][k]);
  313.                         throw new MathIllegalArgumentException(LocalizedGeometryFormats.FACET_ORIENTATION_MISMATCH,
  314.                                                                start.getX(), start.getY(), start.getZ(),
  315.                                                                end.getX(),   end.getY(),   end.getZ());
  316.                     }
  317.                 }

  318.             }
  319.         }

  320.         return successors;

  321.     }

  322.     /** {@inheritDoc} */
  323.     @Override
  324.     public PolyhedronsSet buildNew(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> tree) {
  325.         return new PolyhedronsSet(tree, getTolerance());
  326.     }

  327.     /** {@inheritDoc} */
  328.     @Override
  329.     public Vector3D getInteriorPoint() {
  330.         final InteriorPointFinder<Euclidean3D, Vector3D, Plane, SubPlane> finder =
  331.                 new InteriorPointFinder<>(Vector3D.ZERO);
  332.         getTree(false).visit(finder);
  333.         final BSPTree.InteriorPoint<Euclidean3D, Vector3D> interior = finder.getPoint();
  334.         return interior == null ? null : interior.getPoint();
  335.     }

  336.     /** Get the boundary representation of the instance.
  337.      * <p>
  338.      * The boundary representation can be extracted <em>only</em> from
  339.      * bounded polyhedrons sets. If the polyhedrons set is unbounded,
  340.      * a {@link MathRuntimeException} will be thrown.
  341.      * </p>
  342.      * <p>
  343.      * The boundary representation extracted is not minimal, as for
  344.      * example canonical facets may be split into several smaller
  345.      * independent sub-facets sharing the same plane and connected by
  346.      * their edges.
  347.      * </p>
  348.      * <p>
  349.      * As the {@link BRep B-Rep} representation does not support
  350.      * facets with several boundary loops (for example facets with
  351.      * holes), an exception is triggered when attempting to extract
  352.      * B-Rep from such complex polyhedrons sets.
  353.      * </p>
  354.      * @return boundary representation of the instance
  355.      * @exception MathRuntimeException if polyhedrons is unbounded
  356.      * @since 1.2
  357.      */
  358.     public BRep getBRep() throws MathRuntimeException {
  359.         BRepExtractor extractor = new BRepExtractor(getTolerance());
  360.         getTree(true).visit(extractor);
  361.         return extractor.getBRep();
  362.     }

  363.     /** Visitor extracting BRep. */
  364.     private static class BRepExtractor implements BSPTreeVisitor<Euclidean3D, Vector3D, Plane, SubPlane> {

  365.         /** Tolerance for vertices identification. */
  366.         private final double tolerance;

  367.         /** Extracted vertices. */
  368.         private final List<Vector3D> vertices;

  369.         /** Extracted facets. */
  370.         private final List<int[]> facets;

  371.         /** Simple constructor.
  372.          * @param tolerance tolerance for vertices identification
  373.          */
  374.         BRepExtractor(final double tolerance) {
  375.             this.tolerance = tolerance;
  376.             this.vertices  = new ArrayList<>();
  377.             this.facets    = new ArrayList<>();
  378.         }

  379.         /** Get the BRep.
  380.          * @return extracted BRep
  381.          */
  382.         public BRep getBRep() {
  383.             return new BRep(vertices, facets);
  384.         }

  385.         /** {@inheritDoc} */
  386.         @Override
  387.         public Order visitOrder(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
  388.             return Order.MINUS_SUB_PLUS;
  389.         }

  390.         /** {@inheritDoc} */
  391.         @Override
  392.         public void visitInternalNode(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
  393.             @SuppressWarnings("unchecked")
  394.             final BoundaryAttribute<Euclidean3D, Vector3D, Plane, SubPlane> attribute =
  395.                 (BoundaryAttribute<Euclidean3D, Vector3D, Plane, SubPlane>) node.getAttribute();
  396.             if (attribute.getPlusOutside() != null) {
  397.                 addContribution(attribute.getPlusOutside(), false);
  398.             }
  399.             if (attribute.getPlusInside() != null) {
  400.                 addContribution(attribute.getPlusInside(), true);
  401.             }
  402.         }

  403.         /** {@inheritDoc} */
  404.         @Override
  405.         public void visitLeafNode(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
  406.         }

  407.         /** Add the contribution of a boundary facet.
  408.          * @param facet boundary facet
  409.          * @param reversed if true, the facet has the inside on its plus side
  410.          * @exception MathRuntimeException if facet is unbounded
  411.          */
  412.         private void addContribution(final SubPlane facet, final boolean reversed)
  413.             throws MathRuntimeException {

  414.             final PolygonsSet polygon = (PolygonsSet) facet.getRemainingRegion();
  415.             final Vector2D[][] loops2D = polygon.getVertices();
  416.             if (loops2D.length == 0) {
  417.                 throw new MathRuntimeException(LocalizedGeometryFormats.OUTLINE_BOUNDARY_LOOP_OPEN);
  418.             } else if (loops2D.length > 1) {
  419.                 throw new MathRuntimeException(LocalizedGeometryFormats.FACET_WITH_SEVERAL_BOUNDARY_LOOPS);
  420.             } else {
  421.                 for (final Vector2D[] loop2D : polygon.getVertices()) {
  422.                     final int[] loop3D = new int[loop2D.length];
  423.                     for (int i = 0; i < loop2D.length ; ++i) {
  424.                         if (loop2D[i] == null) {
  425.                             throw new MathRuntimeException(LocalizedGeometryFormats.OUTLINE_BOUNDARY_LOOP_OPEN);
  426.                         }
  427.                         loop3D[reversed ? loop2D.length - 1 - i : i] = getVertexIndex(facet.getHyperplane().toSpace(loop2D[i]));
  428.                     }
  429.                     facets.add(loop3D);
  430.                 }
  431.             }

  432.         }

  433.         /** Get the index of a vertex.
  434.          * @param vertex vertex as a 3D point
  435.          * @return index of the vertex
  436.          */
  437.         private int getVertexIndex(final Vector3D vertex) {

  438.             for (int i = 0; i < vertices.size(); ++i) {
  439.                 if (Vector3D.distance(vertex, vertices.get(i)) <= tolerance) {
  440.                     // the vertex is already known
  441.                     return i;
  442.                 }
  443.             }

  444.             // the vertex is a new one, add it
  445.             vertices.add(vertex);
  446.             return vertices.size() - 1;

  447.         }

  448.     }

  449.     /** {@inheritDoc} */
  450.     @Override
  451.     protected void computeGeometricalProperties() {

  452.         // compute the contribution of all boundary facets
  453.         getTree(true).visit(new FacetsContributionVisitor());

  454.         if (getSize() < 0) {
  455.             // the polyhedrons set as a finite outside
  456.             // surrounded by an infinite inside
  457.             setSize(Double.POSITIVE_INFINITY);
  458.             setBarycenter(Vector3D.NaN);
  459.         } else {
  460.             // the polyhedrons set is finite, apply the remaining scaling factors
  461.             setSize(getSize() / 3.0);
  462.             setBarycenter(new Vector3D(1.0 / (4 * getSize()), getBarycenter()));
  463.         }

  464.     }

  465.     /** Visitor computing geometrical properties. */
  466.     private class FacetsContributionVisitor implements BSPTreeVisitor<Euclidean3D, Vector3D, Plane, SubPlane> {

  467.         /** Simple constructor. */
  468.         FacetsContributionVisitor() {
  469.             setSize(0);
  470.             setBarycenter(new Vector3D(0, 0, 0));
  471.         }

  472.         /** {@inheritDoc} */
  473.         @Override
  474.         public Order visitOrder(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
  475.             return Order.MINUS_SUB_PLUS;
  476.         }

  477.         /** {@inheritDoc} */
  478.         @Override
  479.         public void visitInternalNode(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
  480.             @SuppressWarnings("unchecked")
  481.             final BoundaryAttribute<Euclidean3D, Vector3D, Plane, SubPlane> attribute =
  482.                 (BoundaryAttribute<Euclidean3D, Vector3D, Plane, SubPlane>) node.getAttribute();
  483.             if (attribute.getPlusOutside() != null) {
  484.                 addContribution(attribute.getPlusOutside(), false);
  485.             }
  486.             if (attribute.getPlusInside() != null) {
  487.                 addContribution(attribute.getPlusInside(), true);
  488.             }
  489.         }

  490.         /** {@inheritDoc} */
  491.         @Override
  492.         public void visitLeafNode(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
  493.         }

  494.         /** Add he contribution of a boundary facet.
  495.          * @param facet boundary facet
  496.          * @param reversed if true, the facet has the inside on its plus side
  497.          */
  498.         private void addContribution(final SubPlane facet, final boolean reversed) {

  499.             final Region<Euclidean2D, Vector2D, org.hipparchus.geometry.euclidean.twod.Line, SubLine> polygon =
  500.                     facet.getRemainingRegion();
  501.             final double area = polygon.getSize();

  502.             if (Double.isInfinite(area)) {
  503.                 setSize(Double.POSITIVE_INFINITY);
  504.                 setBarycenter(Vector3D.NaN);
  505.             } else {

  506.                 final Plane    plane  = facet.getHyperplane();
  507.                 final Vector3D facetB = plane.toSpace(polygon.getBarycenter());
  508.                 double   scaled = area * facetB.dotProduct(plane.getNormal());
  509.                 if (reversed) {
  510.                     scaled = -scaled;
  511.                 }

  512.                 setSize(getSize() + scaled);
  513.                 setBarycenter(new Vector3D(1.0, getBarycenter(), scaled, facetB));

  514.             }

  515.         }

  516.     }

  517.     /** Get the first sub-hyperplane crossed by a semi-infinite line.
  518.      * @param point start point of the part of the line considered
  519.      * @param line line to consider (contains point)
  520.      * @return the first sub-hyperplane crossed by the line after the
  521.      * given point, or null if the line does not intersect any
  522.      * sub-hyperplane
  523.      */
  524.     public SubHyperplane<Euclidean3D, Vector3D, Plane, SubPlane> firstIntersection(final Vector3D point, final Line line) {
  525.         return recurseFirstIntersection(getTree(true), point, line);
  526.     }

  527.     /** Get the first sub-hyperplane crossed by a semi-infinite line.
  528.      * @param node current node
  529.      * @param point start point of the part of the line considered
  530.      * @param line line to consider (contains point)
  531.      * @return the first sub-hyperplane crossed by the line after the
  532.      * given point, or null if the line does not intersect any
  533.      * sub-hyperplane
  534.      */
  535.     private SubPlane recurseFirstIntersection(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node,
  536.                                               final Vector3D point, final Line line) {

  537.         final SubPlane cut = node.getCut();
  538.         if (cut == null) {
  539.             return null;
  540.         }
  541.         final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> minus = node.getMinus();
  542.         final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> plus  = node.getPlus();

  543.         // establish search order
  544.         final double offset = cut.getHyperplane().getOffset(point);
  545.         final boolean in    = FastMath.abs(offset) < getTolerance();
  546.         final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> near;
  547.         final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> far;
  548.         if (offset < 0) {
  549.             near = minus;
  550.             far  = plus;
  551.         } else {
  552.             near = plus;
  553.             far  = minus;
  554.         }

  555.         if (in) {
  556.             // search in the cut hyperplane
  557.             final SubPlane facet = boundaryFacet(point, node);
  558.             if (facet != null) {
  559.                 return facet;
  560.             }
  561.         }

  562.         // search in the near branch
  563.         final SubPlane crossed = recurseFirstIntersection(near, point, line);
  564.         if (crossed != null) {
  565.             return crossed;
  566.         }

  567.         if (!in) {
  568.             // search in the cut hyperplane
  569.             final Vector3D hit3D = cut.getHyperplane().intersection(line);
  570.             if (hit3D != null && line.getAbscissa(hit3D) > line.getAbscissa(point)) {
  571.                 final SubPlane facet = boundaryFacet(hit3D, node);
  572.                 if (facet != null) {
  573.                     return facet;
  574.                 }
  575.             }
  576.         }

  577.         // search in the far branch
  578.         return recurseFirstIntersection(far, point, line);

  579.     }

  580.     /** Check if a point belongs to the boundary part of a node.
  581.      * @param point point to check
  582.      * @param node node containing the boundary facet to check
  583.      * @return the boundary facet this points belongs to (or null if it
  584.      * does not belong to any boundary facet)
  585.      */
  586.     private SubPlane boundaryFacet(final Vector3D point, final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
  587.         final Vector2D point2D = node.getCut().getHyperplane().toSubSpace(point);
  588.         @SuppressWarnings("unchecked")
  589.         final BoundaryAttribute<Euclidean3D, Vector3D, Plane, SubPlane> attribute =
  590.             (BoundaryAttribute<Euclidean3D, Vector3D, Plane, SubPlane>) node.getAttribute();
  591.         if ((attribute.getPlusOutside() != null) &&
  592.             (attribute.getPlusOutside().getRemainingRegion().checkPoint(point2D) != Location.OUTSIDE)) {
  593.             return attribute.getPlusOutside();
  594.         }
  595.         if ((attribute.getPlusInside() != null) &&
  596.             (attribute.getPlusInside().getRemainingRegion().checkPoint(point2D) != Location.OUTSIDE)) {
  597.             return attribute.getPlusInside();
  598.         }
  599.         return null;
  600.     }

  601.     /** Rotate the region around the specified point.
  602.      * <p>The instance is not modified, a new instance is created.</p>
  603.      * @param center rotation center
  604.      * @param rotation vectorial rotation operator
  605.      * @return a new instance representing the rotated region
  606.      */
  607.     public PolyhedronsSet rotate(final Vector3D center, final Rotation rotation) {
  608.         return (PolyhedronsSet) applyTransform(new RotationTransform(center, rotation));
  609.     }

  610.     /** 3D rotation as a Transform. */
  611.     private static class RotationTransform
  612.         implements Transform<Euclidean3D, Vector3D, Plane, SubPlane,
  613.                              Euclidean2D, Vector2D, org.hipparchus.geometry.euclidean.twod.Line, SubLine> {

  614.         /** Center point of the rotation. */
  615.         private final Vector3D   center;

  616.         /** Vectorial rotation. */
  617.         private final Rotation   rotation;

  618.         /** Cached original hyperplane. */
  619.         private Plane cachedOriginal;

  620.         /** Cached 2D transform valid inside the cached original hyperplane. */
  621.         private Transform<Euclidean2D, Vector2D, org.hipparchus.geometry.euclidean.twod.Line, SubLine,
  622.                           Euclidean1D, Vector1D, OrientedPoint, SubOrientedPoint> cachedTransform;

  623.         /** Build a rotation transform.
  624.          * @param center center point of the rotation
  625.          * @param rotation vectorial rotation
  626.          */
  627.         RotationTransform(final Vector3D center, final Rotation rotation) {
  628.             this.center   = center;
  629.             this.rotation = rotation;
  630.         }

  631.         /** {@inheritDoc} */
  632.         @Override
  633.         public Vector3D apply(final Vector3D point) {
  634.             final Vector3D delta = point.subtract(center);
  635.             return new Vector3D(1.0, center, 1.0, rotation.applyTo(delta));
  636.         }

  637.         /** {@inheritDoc} */
  638.         @Override
  639.         public Plane apply(final Plane hyperplane) {
  640.             return hyperplane.rotate(center, rotation);
  641.         }

  642.         /** {@inheritDoc} */
  643.         @Override
  644.         public SubLine apply(final SubLine sub, final Plane original, final Plane transformed) {
  645.             if (original != cachedOriginal) {
  646.                 // we have changed hyperplane, reset the in-hyperplane transform

  647.                 final Vector3D p00    = original.getOrigin();
  648.                 final Vector3D p10    = original.toSpace(new Vector2D(1.0, 0.0));
  649.                 final Vector3D p01    = original.toSpace(new Vector2D(0.0, 1.0));
  650.                 final Vector2D tP00   = transformed.toSubSpace(apply(p00));
  651.                 final Vector2D tP10   = transformed.toSubSpace(apply(p10));
  652.                 final Vector2D tP01   = transformed.toSubSpace(apply(p01));

  653.                 cachedOriginal  = original;
  654.                 cachedTransform = org.hipparchus.geometry.euclidean.twod.Line.getTransform(tP10.getX() - tP00.getX(),
  655.                                                                                            tP10.getY() - tP00.getY(),
  656.                                                                                            tP01.getX() - tP00.getX(),
  657.                                                                                            tP01.getY() - tP00.getY(),
  658.                                                                                            tP00.getX(),
  659.                                                                                            tP00.getY());

  660.             }
  661.             return sub.applyTransform(cachedTransform);
  662.         }

  663.     }

  664.     /** Translate the region by the specified amount.
  665.      * <p>The instance is not modified, a new instance is created.</p>
  666.      * @param translation translation to apply
  667.      * @return a new instance representing the translated region
  668.      */
  669.     public PolyhedronsSet translate(final Vector3D translation) {
  670.         return (PolyhedronsSet) applyTransform(new TranslationTransform(translation));
  671.     }

  672.     /** 3D translation as a transform. */
  673.     private static class TranslationTransform
  674.         implements Transform<Euclidean3D, Vector3D, Plane, SubPlane,
  675.                              Euclidean2D, Vector2D, org.hipparchus.geometry.euclidean.twod.Line, SubLine> {

  676.         /** Translation vector. */
  677.         private final Vector3D   translation;

  678.         /** Cached original hyperplane. */
  679.         private Plane cachedOriginal;

  680.         /** Cached 2D transform valid inside the cached original hyperplane. */
  681.         private Transform<Euclidean2D, Vector2D, org.hipparchus.geometry.euclidean.twod.Line, SubLine,
  682.                           Euclidean1D, Vector1D, OrientedPoint, SubOrientedPoint> cachedTransform;

  683.         /** Build a translation transform.
  684.          * @param translation translation vector
  685.          */
  686.         TranslationTransform(final Vector3D translation) {
  687.             this.translation = translation;
  688.         }

  689.         /** {@inheritDoc} */
  690.         @Override
  691.         public Vector3D apply(final Vector3D point) {
  692.             return new Vector3D(1.0, point, 1.0, translation);
  693.         }

  694.         /** {@inheritDoc} */
  695.         @Override
  696.         public Plane apply(final Plane hyperplane) {
  697.             return hyperplane.translate(translation);
  698.         }

  699.         /** {@inheritDoc} */
  700.         @Override
  701.         public SubLine apply(final SubLine sub, final Plane original, final Plane transformed) {
  702.             if (original != cachedOriginal) {
  703.                 // we have changed hyperplane, reset the in-hyperplane transform

  704.                 final Vector2D shift  = transformed.toSubSpace(apply(original.getOrigin()));

  705.                 cachedOriginal  = original;
  706.                 cachedTransform = org.hipparchus.geometry.euclidean.twod.Line.getTransform(1, 0, 0, 1,
  707.                                                                                            shift.getX(),
  708.                                                                                            shift.getY());

  709.             }

  710.             return sub.applyTransform(cachedTransform);

  711.         }

  712.     }

  713.     /** Container for Boundary REPresentation (B-Rep).
  714.      * <p>
  715.      * The boundary is provided as a list of vertices and a list of facets.
  716.      * Each facet is specified as an integer array containing the arrays vertices
  717.      * indices in the vertices list. Each facet normal is oriented by right hand
  718.      * rule to the facet vertices list.
  719.      * </p>
  720.      * @see PolyhedronsSet#PolyhedronsSet(BSPTree, double)
  721.      * @see PolyhedronsSet#getBRep()
  722.      * @since 1.2
  723.      */
  724.     public static class BRep {

  725.         /** List of polyhedrons set vertices. */
  726.         private final List<Vector3D> vertices;

  727.         /** List of facets, as vertices indices in the vertices list. */
  728.         private final List<int[]> facets;

  729.         /** Simple constructor.
  730.          * @param vertices list of polyhedrons set vertices
  731.          * @param facets list of facets, as vertices indices in the vertices list
  732.          */
  733.         public BRep(final List<Vector3D> vertices, final List<int[]> facets) {
  734.             this.vertices = vertices;
  735.             this.facets   = facets;
  736.         }

  737.         /** Get the extracted vertices.
  738.          * @return extracted vertices
  739.          */
  740.         public List<Vector3D> getVertices() {
  741.             return vertices;
  742.         }

  743.         /** Get the extracted facets.
  744.          * @return extracted facets
  745.          */
  746.         public List<int[]> getFacets() {
  747.             return facets;
  748.         }

  749.     }

  750. }