View Javadoc
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  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.geometry.euclidean.threed;
23  
24  import java.util.ArrayList;
25  
26  import org.hipparchus.geometry.Point;
27  import org.hipparchus.geometry.euclidean.twod.Euclidean2D;
28  import org.hipparchus.geometry.euclidean.twod.PolygonsSet;
29  import org.hipparchus.geometry.euclidean.twod.Vector2D;
30  import org.hipparchus.geometry.partitioning.AbstractSubHyperplane;
31  import org.hipparchus.geometry.partitioning.BSPTree;
32  import org.hipparchus.geometry.partitioning.BSPTreeVisitor;
33  import org.hipparchus.geometry.partitioning.BoundaryAttribute;
34  import org.hipparchus.geometry.partitioning.RegionFactory;
35  import org.hipparchus.geometry.partitioning.SubHyperplane;
36  import org.hipparchus.util.FastMath;
37  
38  /** Extractor for {@link PolygonsSet polyhedrons sets} outlines.
39   * <p>This class extracts the 2D outlines from {{@link PolygonsSet
40   * polyhedrons sets} in a specified projection plane.</p>
41   */
42  public class OutlineExtractor {
43  
44      /** Abscissa axis of the projection plane. */
45      private final Vector3D u;
46  
47      /** Ordinate axis of the projection plane. */
48      private final Vector3D v;
49  
50      /** Normal of the projection plane (viewing direction). */
51      private final Vector3D w;
52  
53      /** Build an extractor for a specific projection plane.
54       * @param u abscissa axis of the projection point
55       * @param v ordinate axis of the projection point
56       */
57      public OutlineExtractor(final Vector3D u, final Vector3D v) {
58          this.u = u;
59          this.v = v;
60          w = Vector3D.crossProduct(u, v);
61      }
62  
63      /** Extract the outline of a polyhedrons set.
64       * @param polyhedronsSet polyhedrons set whose outline must be extracted
65       * @return an outline, as an array of loops.
66       */
67      public Vector2D[][] getOutline(final PolyhedronsSet polyhedronsSet) {
68  
69          // project all boundary facets into one polygons set
70          final BoundaryProjector projector = new BoundaryProjector(polyhedronsSet.getTolerance());
71          polyhedronsSet.getTree(true).visit(projector);
72          final PolygonsSet projected = projector.getProjected();
73  
74          // Remove the spurious intermediate vertices from the outline
75          final Vector2D[][] outline = projected.getVertices();
76          for (int i = 0; i < outline.length; ++i) {
77              final Vector2D[] rawLoop = outline[i];
78              int end = rawLoop.length;
79              int j = 0;
80              while (j < end) {
81                  if (pointIsBetween(rawLoop, end, j)) {
82                      // the point should be removed
83                      for (int k = j; k < (end - 1); ++k) {
84                          rawLoop[k] = rawLoop[k + 1];
85                      }
86                      --end;
87                  } else {
88                      // the point remains in the loop
89                      ++j;
90                  }
91              }
92              if (end != rawLoop.length) {
93                  // resize the array
94                  outline[i] = new Vector2D[end];
95                  System.arraycopy(rawLoop, 0, outline[i], 0, end);
96              }
97          }
98  
99          return outline;
100 
101     }
102 
103     /** Check if a point is geometrically between its neighbor in an array.
104      * <p>The neighbors are computed considering the array is a loop
105      * (i.e. point at index (n-1) is before point at index 0)</p>
106      * @param loop points array
107      * @param n number of points to consider in the array
108      * @param i index of the point to check (must be between 0 and n-1)
109      * @return true if the point is exactly between its neighbors
110      */
111     private boolean pointIsBetween(final Vector2D[] loop, final int n, final int i) {
112         final Vector2D previous = loop[(i + n - 1) % n];
113         final Vector2D current  = loop[i];
114         final Vector2D next     = loop[(i + 1) % n];
115         final double dx1       = current.getX() - previous.getX();
116         final double dy1       = current.getY() - previous.getY();
117         final double dx2       = next.getX()    - current.getX();
118         final double dy2       = next.getY()    - current.getY();
119         final double cross     = dx1 * dy2 - dx2 * dy1;
120         final double dot       = dx1 * dx2 + dy1 * dy2;
121         final double d1d2      = FastMath.sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2));
122         return (FastMath.abs(cross) <= (1.0e-6 * d1d2)) && (dot >= 0.0);
123     }
124 
125     /** Visitor projecting the boundary facets on a plane. */
126     private class BoundaryProjector implements BSPTreeVisitor<Euclidean3D> {
127 
128         /** Projection of the polyhedrons set on the plane. */
129         private PolygonsSet projected;
130 
131         /** Tolerance below which points are considered identical. */
132         private final double tolerance;
133 
134         /** Simple constructor.
135          * @param tolerance tolerance below which points are considered identical
136          */
137         BoundaryProjector(final double tolerance) {
138             this.projected = new PolygonsSet(new BSPTree<Euclidean2D>(Boolean.FALSE), tolerance);
139             this.tolerance = tolerance;
140         }
141 
142         /** {@inheritDoc} */
143         @Override
144         public Order visitOrder(final BSPTree<Euclidean3D> node) {
145             return Order.MINUS_SUB_PLUS;
146         }
147 
148         /** {@inheritDoc} */
149         @Override
150         public void visitInternalNode(final BSPTree<Euclidean3D> node) {
151             @SuppressWarnings("unchecked")
152             final BoundaryAttribute<Euclidean3D> attribute =
153                 (BoundaryAttribute<Euclidean3D>) node.getAttribute();
154             if (attribute.getPlusOutside() != null) {
155                 addContribution(attribute.getPlusOutside());
156             }
157             if (attribute.getPlusInside() != null) {
158                 addContribution(attribute.getPlusInside());
159             }
160         }
161 
162         /** {@inheritDoc} */
163         @Override
164         public void visitLeafNode(final BSPTree<Euclidean3D> node) {
165         }
166 
167         /** Add he contribution of a boundary facet.
168          * @param facet boundary facet
169          */
170         private void addContribution(final SubHyperplane<Euclidean3D> facet) {
171 
172             // extract the vertices of the facet
173             final AbstractSubHyperplane<Euclidean3D, Euclidean2D> absFacet =
174                 (AbstractSubHyperplane<Euclidean3D, Euclidean2D>) facet;
175             final Plane plane    = (Plane) facet.getHyperplane();
176 
177             final double scal = plane.getNormal().dotProduct(w);
178             if (FastMath.abs(scal) > 1.0e-3) {
179                 Vector2D[][] vertices =
180                     ((PolygonsSet) absFacet.getRemainingRegion()).getVertices();
181 
182                 if (scal < 0) {
183                     // the facet is seen from the back of the plane,
184                     // we need to invert its boundary orientation
185                     final Vector2D[][] newVertices = new Vector2D[vertices.length][];
186                     for (int i = 0; i < vertices.length; ++i) {
187                         final Vector2D[] loop = vertices[i];
188                         final Vector2D[] newLoop = new Vector2D[loop.length];
189                         if (loop[0] == null) {
190                             newLoop[0] = null;
191                             for (int j = 1; j < loop.length; ++j) {
192                                 newLoop[j] = loop[loop.length - j];
193                             }
194                         } else {
195                             for (int j = 0; j < loop.length; ++j) {
196                                 newLoop[j] = loop[loop.length - (j + 1)];
197                             }
198                         }
199                         newVertices[i] = newLoop;
200                     }
201 
202                     // use the reverted vertices
203                     vertices = newVertices;
204 
205                 }
206 
207                 // compute the projection of the facet in the outline plane
208                 final ArrayList<SubHyperplane<Euclidean2D>> edges = new ArrayList<>();
209                 for (Vector2D[] loop : vertices) {
210                     final boolean closed = loop[0] != null;
211                     int previous         = closed ? (loop.length - 1) : 1;
212                     final Vector3D previous3D = plane.toSpace((Point<Euclidean2D>) loop[previous]);
213                     int current          = (previous + 1) % loop.length;
214                     Vector2D pPoint      = new Vector2D(previous3D.dotProduct(u), previous3D.dotProduct(v));
215                     while (current < loop.length) {
216 
217                         final Vector3D current3D = plane.toSpace((Point<Euclidean2D>) loop[current]);
218                         final Vector2D  cPoint    = new Vector2D(current3D.dotProduct(u),
219                                                                  current3D.dotProduct(v));
220                         final org.hipparchus.geometry.euclidean.twod.Line line =
221                             new org.hipparchus.geometry.euclidean.twod.Line(pPoint, cPoint, tolerance);
222                         SubHyperplane<Euclidean2D> edge = line.wholeHyperplane();
223 
224                         if (closed || (previous != 1)) {
225                             // the previous point is a real vertex
226                             // it defines one bounding point of the edge
227                             final double angle = line.getAngle() + 0.5 * FastMath.PI;
228                             final org.hipparchus.geometry.euclidean.twod.Line l =
229                                 new org.hipparchus.geometry.euclidean.twod.Line(pPoint, angle, tolerance);
230                             edge = edge.split(l).getPlus();
231                         }
232 
233                         if (closed || (current != (loop.length - 1))) {
234                             // the current point is a real vertex
235                             // it defines one bounding point of the edge
236                             final double angle = line.getAngle() + 0.5 * FastMath.PI;
237                             final org.hipparchus.geometry.euclidean.twod.Line l =
238                                 new org.hipparchus.geometry.euclidean.twod.Line(cPoint, angle, tolerance);
239                             edge = edge.split(l).getMinus();
240                         }
241 
242                         edges.add(edge);
243 
244                         previous   = current++;
245                         pPoint     = cPoint;
246 
247                     }
248                 }
249                 final PolygonsSet projectedFacet = new PolygonsSet(edges, tolerance);
250 
251                 // add the contribution of the facet to the global outline
252                 projected = (PolygonsSet) new RegionFactory<Euclidean2D>().union(projected, projectedFacet);
253 
254             }
255         }
256 
257         /** Get the projection of the polyhedrons set on the plane.
258          * @return projection of the polyhedrons set on the plane
259          */
260         public PolygonsSet getProjected() {
261             return projected;
262         }
263 
264     }
265 
266 }