1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
39
40
41
42 public class OutlineExtractor {
43
44
45 private final Vector3D u;
46
47
48 private final Vector3D v;
49
50
51 private final Vector3D w;
52
53
54
55
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
64
65
66
67 public Vector2D[][] getOutline(final PolyhedronsSet polyhedronsSet) {
68
69
70 final BoundaryProjector projector = new BoundaryProjector(polyhedronsSet.getTolerance());
71 polyhedronsSet.getTree(true).visit(projector);
72 final PolygonsSet projected = projector.getProjected();
73
74
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
83 for (int k = j; k < (end - 1); ++k) {
84 rawLoop[k] = rawLoop[k + 1];
85 }
86 --end;
87 } else {
88
89 ++j;
90 }
91 }
92 if (end != rawLoop.length) {
93
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
104
105
106
107
108
109
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
126 private class BoundaryProjector implements BSPTreeVisitor<Euclidean3D> {
127
128
129 private PolygonsSet projected;
130
131
132 private final double tolerance;
133
134
135
136
137 BoundaryProjector(final double tolerance) {
138 this.projected = new PolygonsSet(new BSPTree<Euclidean2D>(Boolean.FALSE), tolerance);
139 this.tolerance = tolerance;
140 }
141
142
143 @Override
144 public Order visitOrder(final BSPTree<Euclidean3D> node) {
145 return Order.MINUS_SUB_PLUS;
146 }
147
148
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
163 @Override
164 public void visitLeafNode(final BSPTree<Euclidean3D> node) {
165 }
166
167
168
169
170 private void addContribution(final SubHyperplane<Euclidean3D> facet) {
171
172
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
184
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
203 vertices = newVertices;
204
205 }
206
207
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
226
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
235
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
252 projected = (PolygonsSet) new RegionFactory<Euclidean2D>().union(projected, projectedFacet);
253
254 }
255 }
256
257
258
259
260 public PolygonsSet getProjected() {
261 return projected;
262 }
263
264 }
265
266 }