OutlineExtractor.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 org.hipparchus.geometry.euclidean.twod.Euclidean2D;
  24. import org.hipparchus.geometry.euclidean.twod.Line;
  25. import org.hipparchus.geometry.euclidean.twod.PolygonsSet;
  26. import org.hipparchus.geometry.euclidean.twod.SubLine;
  27. import org.hipparchus.geometry.euclidean.twod.Vector2D;
  28. import org.hipparchus.geometry.partitioning.BSPTree;
  29. import org.hipparchus.geometry.partitioning.BSPTreeVisitor;
  30. import org.hipparchus.geometry.partitioning.BoundaryAttribute;
  31. import org.hipparchus.geometry.partitioning.RegionFactory;
  32. import org.hipparchus.util.FastMath;
  33. import org.hipparchus.util.MathUtils;

  34. /** Extractor for {@link PolygonsSet polyhedrons sets} outlines.
  35.  * <p>This class extracts the 2D outlines from {{@link PolygonsSet
  36.  * polyhedrons sets} in a specified projection plane.</p>
  37.  */
  38. public class OutlineExtractor {

  39.     /** Abscissa axis of the projection plane. */
  40.     private final Vector3D u;

  41.     /** Ordinate axis of the projection plane. */
  42.     private final Vector3D v;

  43.     /** Normal of the projection plane (viewing direction). */
  44.     private final Vector3D w;

  45.     /** Build an extractor for a specific projection plane.
  46.      * @param u abscissa axis of the projection point
  47.      * @param v ordinate axis of the projection point
  48.      */
  49.     public OutlineExtractor(final Vector3D u, final Vector3D v) {
  50.         this.u = u;
  51.         this.v = v;
  52.         w = Vector3D.crossProduct(u, v);
  53.     }

  54.     /** Extract the outline of a polyhedrons set.
  55.      * @param polyhedronsSet polyhedrons set whose outline must be extracted
  56.      * @return an outline, as an array of loops.
  57.      */
  58.     public Vector2D[][] getOutline(final PolyhedronsSet polyhedronsSet) {

  59.         // project all boundary facets into one polygons set
  60.         final BoundaryProjector projector = new BoundaryProjector(polyhedronsSet.getTolerance());
  61.         polyhedronsSet.getTree(true).visit(projector);
  62.         final PolygonsSet projected = projector.getProjected();

  63.         // Remove the spurious intermediate vertices from the outline
  64.         final Vector2D[][] outline = projected.getVertices();
  65.         for (int i = 0; i < outline.length; ++i) {
  66.             final Vector2D[] rawLoop = outline[i];
  67.             int end = rawLoop.length;
  68.             int j = 0;
  69.             while (j < end) {
  70.                 if (pointIsBetween(rawLoop, end, j)) {
  71.                     // the point should be removed
  72.                     for (int k = j; k < (end - 1); ++k) {
  73.                         rawLoop[k] = rawLoop[k + 1];
  74.                     }
  75.                     --end;
  76.                 } else {
  77.                     // the point remains in the loop
  78.                     ++j;
  79.                 }
  80.             }
  81.             if (end != rawLoop.length) {
  82.                 // resize the array
  83.                 outline[i] = new Vector2D[end];
  84.                 System.arraycopy(rawLoop, 0, outline[i], 0, end);
  85.             }
  86.         }

  87.         return outline;

  88.     }

  89.     /** Check if a point is geometrically between its neighbor in an array.
  90.      * <p>The neighbors are computed considering the array is a loop
  91.      * (i.e. point at index (n-1) is before point at index 0)</p>
  92.      * @param loop points array
  93.      * @param n number of points to consider in the array
  94.      * @param i index of the point to check (must be between 0 and n-1)
  95.      * @return true if the point is exactly between its neighbors
  96.      */
  97.     private boolean pointIsBetween(final Vector2D[] loop, final int n, final int i) {
  98.         final Vector2D previous = loop[(i + n - 1) % n];
  99.         final Vector2D current  = loop[i];
  100.         final Vector2D next     = loop[(i + 1) % n];
  101.         final double dx1       = current.getX() - previous.getX();
  102.         final double dy1       = current.getY() - previous.getY();
  103.         final double dx2       = next.getX()    - current.getX();
  104.         final double dy2       = next.getY()    - current.getY();
  105.         final double cross     = dx1 * dy2 - dx2 * dy1;
  106.         final double dot       = dx1 * dx2 + dy1 * dy2;
  107.         final double d1d2      = FastMath.sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2));
  108.         return (FastMath.abs(cross) <= (1.0e-6 * d1d2)) && (dot >= 0.0);
  109.     }

  110.     /** Visitor projecting the boundary facets on a plane. */
  111.     private class BoundaryProjector implements BSPTreeVisitor<Euclidean3D, Vector3D, Plane, SubPlane> {

  112.         /** Projection of the polyhedrons set on the plane. */
  113.         private PolygonsSet projected;

  114.         /** Tolerance below which points are considered identical. */
  115.         private final double tolerance;

  116.         /** Simple constructor.
  117.          * @param tolerance tolerance below which points are considered identical
  118.          */
  119.         BoundaryProjector(final double tolerance) {
  120.             this.projected = new PolygonsSet(new BSPTree<>(Boolean.FALSE), tolerance);
  121.             this.tolerance = tolerance;
  122.         }

  123.         /** {@inheritDoc} */
  124.         @Override
  125.         public Order visitOrder(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
  126.             return Order.MINUS_SUB_PLUS;
  127.         }

  128.         /** {@inheritDoc} */
  129.         @Override
  130.         public void visitInternalNode(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
  131.             @SuppressWarnings("unchecked")
  132.             final BoundaryAttribute<Euclidean3D, Vector3D, Plane, SubPlane> attribute =
  133.                 (BoundaryAttribute<Euclidean3D, Vector3D, Plane, SubPlane>) node.getAttribute();
  134.             if (attribute.getPlusOutside() != null) {
  135.                 addContribution(attribute.getPlusOutside());
  136.             }
  137.             if (attribute.getPlusInside() != null) {
  138.                 addContribution(attribute.getPlusInside());
  139.             }
  140.         }

  141.         /** {@inheritDoc} */
  142.         @Override
  143.         public void visitLeafNode(final BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
  144.         }

  145.         /** Add he contribution of a boundary facet.
  146.          * @param facet boundary facet
  147.          */
  148.         private void addContribution(final SubPlane facet) {

  149.             final double scal = facet.getHyperplane().getNormal().dotProduct(w);
  150.             if (FastMath.abs(scal) > 1.0e-3) {
  151.                 Vector2D[][] vertices =
  152.                     ((PolygonsSet) facet.getRemainingRegion()).getVertices();

  153.                 if (scal < 0) {
  154.                     // the facet is seen from the back of the plane,
  155.                     // we need to invert its boundary orientation
  156.                     final Vector2D[][] newVertices = new Vector2D[vertices.length][];
  157.                     for (int i = 0; i < vertices.length; ++i) {
  158.                         final Vector2D[] loop = vertices[i];
  159.                         final Vector2D[] newLoop = new Vector2D[loop.length];
  160.                         if (loop[0] == null) {
  161.                             newLoop[0] = null;
  162.                             for (int j = 1; j < loop.length; ++j) {
  163.                                 newLoop[j] = loop[loop.length - j];
  164.                             }
  165.                         } else {
  166.                             for (int j = 0; j < loop.length; ++j) {
  167.                                 newLoop[j] = loop[loop.length - (j + 1)];
  168.                             }
  169.                         }
  170.                         newVertices[i] = newLoop;
  171.                     }

  172.                     // use the reverted vertices
  173.                     vertices = newVertices;

  174.                 }

  175.                 // compute the projection of the facet in the outline plane
  176.                 final ArrayList<SubLine> edges = new ArrayList<>();
  177.                 for (Vector2D[] loop : vertices) {
  178.                     final boolean closed = loop[0] != null;
  179.                     int previous         = closed ? (loop.length - 1) : 1;
  180.                     final Vector3D previous3D = facet.getHyperplane().toSpace(loop[previous]);
  181.                     int current          = (previous + 1) % loop.length;
  182.                     Vector2D pPoint      = new Vector2D(previous3D.dotProduct(u), previous3D.dotProduct(v));
  183.                     while (current < loop.length) {

  184.                         final Vector3D current3D = facet.getHyperplane().toSpace(loop[current]);
  185.                         final Vector2D  cPoint    = new Vector2D(current3D.dotProduct(u),
  186.                                                                  current3D.dotProduct(v));
  187.                         final Line line = new Line(pPoint, cPoint, tolerance);
  188.                         SubLine edge = line.wholeHyperplane();

  189.                         if (closed || (previous != 1)) {
  190.                             // the previous point is a real vertex
  191.                             // it defines one bounding point of the edge
  192.                             final double angle = line.getAngle() + MathUtils.SEMI_PI;
  193.                             final Line l = new Line(pPoint, angle, tolerance);
  194.                             edge = edge.split(l).getPlus();
  195.                         }

  196.                         if (closed || (current != (loop.length - 1))) {
  197.                             // the current point is a real vertex
  198.                             // it defines one bounding point of the edge
  199.                             final double angle = line.getAngle() + MathUtils.SEMI_PI;
  200.                             final Line l = new Line(cPoint, angle, tolerance);
  201.                             edge = edge.split(l).getMinus();
  202.                         }

  203.                         edges.add(edge);

  204.                         previous   = current++;
  205.                         pPoint     = cPoint;

  206.                     }
  207.                 }
  208.                 final PolygonsSet projectedFacet = new PolygonsSet(edges, tolerance);

  209.                 // add the contribution of the facet to the global outline
  210.                 final RegionFactory<Euclidean2D, Vector2D, Line, SubLine> factory = new RegionFactory<>();
  211.                 projected = (PolygonsSet) factory.union(projected, projectedFacet);

  212.             }
  213.         }

  214.         /** Get the projection of the polyhedrons set on the plane.
  215.          * @return projection of the polyhedrons set on the plane
  216.          */
  217.         public PolygonsSet getProjected() {
  218.             return projected;
  219.         }

  220.     }

  221. }