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.partitioning;
23  
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import org.hipparchus.geometry.Point;
28  import org.hipparchus.geometry.Space;
29  import org.hipparchus.geometry.partitioning.Region.Location;
30  import org.hipparchus.util.FastMath;
31  
32  /** Local tree visitor to compute projection on boundary.
33   * @param <S> Type of the space.
34   * @param <T> Type of the sub-space.
35   */
36  class BoundaryProjector<S extends Space, T extends Space> implements BSPTreeVisitor<S> {
37  
38      /** Original point. */
39      private final Point<S> original;
40  
41      /** Current best projected point. */
42      private Point<S> projected;
43  
44      /** Leaf node closest to the test point. */
45      private BSPTree<S> leaf;
46  
47      /** Current offset. */
48      private double offset;
49  
50      /** Simple constructor.
51       * @param original original point
52       */
53      BoundaryProjector(final Point<S> original) {
54          this.original  = original;
55          this.projected = null;
56          this.leaf      = null;
57          this.offset    = Double.POSITIVE_INFINITY;
58      }
59  
60      /** {@inheritDoc} */
61      @Override
62      public Order visitOrder(final BSPTree<S> node) {
63          // we want to visit the tree so that the first encountered
64          // leaf is the one closest to the test point
65          if (node.getCut().getHyperplane().getOffset(original) <= 0) {
66              return Order.MINUS_SUB_PLUS;
67          } else {
68              return Order.PLUS_SUB_MINUS;
69          }
70      }
71  
72      /** {@inheritDoc} */
73      @Override
74      public void visitInternalNode(final BSPTree<S> node) {
75  
76          // project the point on the cut sub-hyperplane
77          final Hyperplane<S> hyperplane = node.getCut().getHyperplane();
78          final double signedOffset = hyperplane.getOffset(original);
79          if (FastMath.abs(signedOffset) < offset) {
80  
81              // project point
82              final Point<S> regular = hyperplane.project(original);
83  
84              // get boundary parts
85              final List<Region<T>> boundaryParts = boundaryRegions(node);
86  
87              // check if regular projection really belongs to the boundary
88              boolean regularFound = false;
89              for (final Region<T> part : boundaryParts) {
90                  if (!regularFound && belongsToPart(regular, hyperplane, part)) {
91                      // the projected point lies in the boundary
92                      projected    = regular;
93                      offset       = FastMath.abs(signedOffset);
94                      regularFound = true;
95                  }
96              }
97  
98              if (!regularFound) {
99                  // the regular projected point is not on boundary,
100                 // so we have to check further if a singular point
101                 // (i.e. a vertex in 2D case) is a possible projection
102                 for (final Region<T> part : boundaryParts) {
103                     final Point<S> spI = singularProjection(regular, hyperplane, part);
104                     if (spI != null) {
105                         final double distance = original.distance(spI);
106                         if (distance < offset) {
107                             projected = spI;
108                             offset    = distance;
109                         }
110                     }
111                 }
112 
113             }
114 
115         }
116 
117     }
118 
119     /** {@inheritDoc} */
120     @Override
121     public void visitLeafNode(final BSPTree<S> node) {
122         if (leaf == null) {
123             // this is the first leaf we visit,
124             // it is the closest one to the original point
125             leaf = node;
126         }
127     }
128 
129     /** Get the projection.
130      * @return projection
131      */
132     public BoundaryProjection<S> getProjection() {
133 
134         // fix offset sign
135         offset = FastMath.copySign(offset, (Boolean) leaf.getAttribute() ? -1 : +1);
136 
137         return new BoundaryProjection<S>(original, projected, offset);
138 
139     }
140 
141     /** Extract the regions of the boundary on an internal node.
142      * @param node internal node
143      * @return regions in the node sub-hyperplane
144      */
145     private List<Region<T>> boundaryRegions(final BSPTree<S> node) {
146 
147         final List<Region<T>> regions = new ArrayList<>(2);
148 
149         @SuppressWarnings("unchecked")
150         final BoundaryAttribute<S> ba = (BoundaryAttribute<S>) node.getAttribute();
151         addRegion(ba.getPlusInside(),  regions);
152         addRegion(ba.getPlusOutside(), regions);
153 
154         return regions;
155 
156     }
157 
158     /** Add a boundary region to a list.
159      * @param sub sub-hyperplane defining the region
160      * @param list to fill up
161      */
162     private void addRegion(final SubHyperplane<S> sub, final List<Region<T>> list) {
163         if (sub != null) {
164             @SuppressWarnings("unchecked")
165             final Region<T> region = ((AbstractSubHyperplane<S, T>) sub).getRemainingRegion();
166             if (region != null) {
167                 list.add(region);
168             }
169         }
170     }
171 
172     /** Check if a projected point lies on a boundary part.
173      * @param point projected point to check
174      * @param hyperplane hyperplane into which the point was projected
175      * @param part boundary part
176      * @return true if point lies on the boundary part
177      */
178     private boolean belongsToPart(final Point<S> point, final Hyperplane<S> hyperplane,
179                                   final Region<T> part) {
180 
181         // there is a non-null sub-space, we can dive into smaller dimensions
182         @SuppressWarnings("unchecked")
183         final Embedding<S, T> embedding = (Embedding<S, T>) hyperplane;
184         return part.checkPoint(embedding.toSubSpace(point)) != Location.OUTSIDE;
185 
186     }
187 
188     /** Get the projection to the closest boundary singular point.
189      * @param point projected point to check
190      * @param hyperplane hyperplane into which the point was projected
191      * @param part boundary part
192      * @return projection to a singular point of boundary part (may be null)
193      */
194     private Point<S> singularProjection(final Point<S> point, final Hyperplane<S> hyperplane,
195                                         final Region<T> part) {
196 
197         // there is a non-null sub-space, we can dive into smaller dimensions
198         @SuppressWarnings("unchecked")
199         final Embedding<S, T> embedding = (Embedding<S, T>) hyperplane;
200         final BoundaryProjection<T> bp = part.projectToBoundary(embedding.toSubSpace(point));
201 
202         // back to initial dimension
203         return (bp.getProjected() == null) ? null : embedding.toSpace(bp.getProjected());
204 
205     }
206 
207 }