001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.math3.geometry.euclidean.threed; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.List; 023 024import org.apache.commons.math3.exception.MathIllegalArgumentException; 025import org.apache.commons.math3.exception.NumberIsTooSmallException; 026import org.apache.commons.math3.exception.util.LocalizedFormats; 027import org.apache.commons.math3.geometry.Point; 028import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; 029import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; 030import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet; 031import org.apache.commons.math3.geometry.euclidean.twod.SubLine; 032import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; 033import org.apache.commons.math3.geometry.partitioning.AbstractRegion; 034import org.apache.commons.math3.geometry.partitioning.BSPTree; 035import org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor; 036import org.apache.commons.math3.geometry.partitioning.BoundaryAttribute; 037import org.apache.commons.math3.geometry.partitioning.Hyperplane; 038import org.apache.commons.math3.geometry.partitioning.Region; 039import org.apache.commons.math3.geometry.partitioning.RegionFactory; 040import org.apache.commons.math3.geometry.partitioning.SubHyperplane; 041import org.apache.commons.math3.geometry.partitioning.Transform; 042import org.apache.commons.math3.util.FastMath; 043 044/** This class represents a 3D region: a set of polyhedrons. 045 * @since 3.0 046 */ 047public class PolyhedronsSet extends AbstractRegion<Euclidean3D, Euclidean2D> { 048 049 /** Default value for tolerance. */ 050 private static final double DEFAULT_TOLERANCE = 1.0e-10; 051 052 /** Build a polyhedrons set representing the whole real line. 053 * @param tolerance tolerance below which points are considered identical 054 * @since 3.3 055 */ 056 public PolyhedronsSet(final double tolerance) { 057 super(tolerance); 058 } 059 060 /** Build a polyhedrons set from a BSP tree. 061 * <p>The leaf nodes of the BSP tree <em>must</em> have a 062 * {@code Boolean} attribute representing the inside status of 063 * the corresponding cell (true for inside cells, false for outside 064 * cells). In order to avoid building too many small objects, it is 065 * recommended to use the predefined constants 066 * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p> 067 * <p> 068 * This constructor is aimed at expert use, as building the tree may 069 * be a difficult task. It is not intended for general use and for 070 * performances reasons does not check thoroughly its input, as this would 071 * require walking the full tree each time. Failing to provide a tree with 072 * the proper attributes, <em>will</em> therefore generate problems like 073 * {@link NullPointerException} or {@link ClassCastException} only later on. 074 * This limitation is known and explains why this constructor is for expert 075 * use only. The caller does have the responsibility to provided correct arguments. 076 * </p> 077 * @param tree inside/outside BSP tree representing the region 078 * @param tolerance tolerance below which points are considered identical 079 * @since 3.3 080 */ 081 public PolyhedronsSet(final BSPTree<Euclidean3D> tree, final double tolerance) { 082 super(tree, tolerance); 083 } 084 085 /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by sub-hyperplanes. 086 * <p>The boundary is provided as a collection of {@link 087 * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the 088 * interior part of the region on its minus side and the exterior on 089 * its plus side.</p> 090 * <p>The boundary elements can be in any order, and can form 091 * several non-connected sets (like for example polyhedrons with holes 092 * or a set of disjoint polyhedrons considered as a whole). In 093 * fact, the elements do not even need to be connected together 094 * (their topological connections are not used here). However, if the 095 * boundary does not really separate an inside open from an outside 096 * open (open having here its topological meaning), then subsequent 097 * calls to the {@link Region#checkPoint(Point) checkPoint} method will 098 * not be meaningful anymore.</p> 099 * <p>If the boundary is empty, the region will represent the whole 100 * space.</p> 101 * @param boundary collection of boundary elements, as a 102 * collection of {@link SubHyperplane SubHyperplane} objects 103 * @param tolerance tolerance below which points are considered identical 104 * @since 3.3 105 */ 106 public PolyhedronsSet(final Collection<SubHyperplane<Euclidean3D>> boundary, 107 final double tolerance) { 108 super(boundary, tolerance); 109 } 110 111 /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by connected vertices. 112 * <p> 113 * The boundary is provided as a list of vertices and a list of facets. 114 * Each facet is specified as an integer array containing the arrays vertices 115 * indices in the vertices list. Each facet normal is oriented by right hand 116 * rule to the facet vertices list. 117 * </p> 118 * <p> 119 * Some basic sanity checks are performed but not everything is thoroughly 120 * assessed, so it remains under caller responsibility to ensure the vertices 121 * and facets are consistent and properly define a polyhedrons set. 122 * </p> 123 * @param vertices list of polyhedrons set vertices 124 * @param facets list of facets, as vertices indices in the vertices list 125 * @param tolerance tolerance below which points are considered identical 126 * @exception MathIllegalArgumentException if some basic sanity checks fail 127 * @since 3.5 128 */ 129 public PolyhedronsSet(final List<Vector3D> vertices, final List<int[]> facets, 130 final double tolerance) { 131 super(buildBoundary(vertices, facets, tolerance), tolerance); 132 } 133 134 /** Build a parallellepipedic box. 135 * @param xMin low bound along the x direction 136 * @param xMax high bound along the x direction 137 * @param yMin low bound along the y direction 138 * @param yMax high bound along the y direction 139 * @param zMin low bound along the z direction 140 * @param zMax high bound along the z direction 141 * @param tolerance tolerance below which points are considered identical 142 * @since 3.3 143 */ 144 public PolyhedronsSet(final double xMin, final double xMax, 145 final double yMin, final double yMax, 146 final double zMin, final double zMax, 147 final double tolerance) { 148 super(buildBoundary(xMin, xMax, yMin, yMax, zMin, zMax, tolerance), tolerance); 149 } 150 151 /** Build a polyhedrons set representing the whole real line. 152 * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(double)} 153 */ 154 @Deprecated 155 public PolyhedronsSet() { 156 this(DEFAULT_TOLERANCE); 157 } 158 159 /** Build a polyhedrons set from a BSP tree. 160 * <p>The leaf nodes of the BSP tree <em>must</em> have a 161 * {@code Boolean} attribute representing the inside status of 162 * the corresponding cell (true for inside cells, false for outside 163 * cells). In order to avoid building too many small objects, it is 164 * recommended to use the predefined constants 165 * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p> 166 * @param tree inside/outside BSP tree representing the region 167 * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(BSPTree, double)} 168 */ 169 @Deprecated 170 public PolyhedronsSet(final BSPTree<Euclidean3D> tree) { 171 this(tree, DEFAULT_TOLERANCE); 172 } 173 174 /** Build a polyhedrons set from a Boundary REPresentation (B-rep). 175 * <p>The boundary is provided as a collection of {@link 176 * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the 177 * interior part of the region on its minus side and the exterior on 178 * its plus side.</p> 179 * <p>The boundary elements can be in any order, and can form 180 * several non-connected sets (like for example polyhedrons with holes 181 * or a set of disjoint polyhedrons considered as a whole). In 182 * fact, the elements do not even need to be connected together 183 * (their topological connections are not used here). However, if the 184 * boundary does not really separate an inside open from an outside 185 * open (open having here its topological meaning), then subsequent 186 * calls to the {@link Region#checkPoint(Point) checkPoint} method will 187 * not be meaningful anymore.</p> 188 * <p>If the boundary is empty, the region will represent the whole 189 * space.</p> 190 * @param boundary collection of boundary elements, as a 191 * collection of {@link SubHyperplane SubHyperplane} objects 192 * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(Collection, double)} 193 */ 194 @Deprecated 195 public PolyhedronsSet(final Collection<SubHyperplane<Euclidean3D>> boundary) { 196 this(boundary, DEFAULT_TOLERANCE); 197 } 198 199 /** Build a parallellepipedic box. 200 * @param xMin low bound along the x direction 201 * @param xMax high bound along the x direction 202 * @param yMin low bound along the y direction 203 * @param yMax high bound along the y direction 204 * @param zMin low bound along the z direction 205 * @param zMax high bound along the z direction 206 * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(double, double, 207 * double, double, double, double, double)} 208 */ 209 @Deprecated 210 public PolyhedronsSet(final double xMin, final double xMax, 211 final double yMin, final double yMax, 212 final double zMin, final double zMax) { 213 this(xMin, xMax, yMin, yMax, zMin, zMax, DEFAULT_TOLERANCE); 214 } 215 216 /** Build a parallellepipedic box boundary. 217 * @param xMin low bound along the x direction 218 * @param xMax high bound along the x direction 219 * @param yMin low bound along the y direction 220 * @param yMax high bound along the y direction 221 * @param zMin low bound along the z direction 222 * @param zMax high bound along the z direction 223 * @param tolerance tolerance below which points are considered identical 224 * @return boundary tree 225 * @since 3.3 226 */ 227 private static BSPTree<Euclidean3D> buildBoundary(final double xMin, final double xMax, 228 final double yMin, final double yMax, 229 final double zMin, final double zMax, 230 final double tolerance) { 231 if ((xMin >= xMax - tolerance) || (yMin >= yMax - tolerance) || (zMin >= zMax - tolerance)) { 232 // too thin box, build an empty polygons set 233 return new BSPTree<Euclidean3D>(Boolean.FALSE); 234 } 235 final Plane pxMin = new Plane(new Vector3D(xMin, 0, 0), Vector3D.MINUS_I, tolerance); 236 final Plane pxMax = new Plane(new Vector3D(xMax, 0, 0), Vector3D.PLUS_I, tolerance); 237 final Plane pyMin = new Plane(new Vector3D(0, yMin, 0), Vector3D.MINUS_J, tolerance); 238 final Plane pyMax = new Plane(new Vector3D(0, yMax, 0), Vector3D.PLUS_J, tolerance); 239 final Plane pzMin = new Plane(new Vector3D(0, 0, zMin), Vector3D.MINUS_K, tolerance); 240 final Plane pzMax = new Plane(new Vector3D(0, 0, zMax), Vector3D.PLUS_K, tolerance); 241 @SuppressWarnings("unchecked") 242 final Region<Euclidean3D> boundary = 243 new RegionFactory<Euclidean3D>().buildConvex(pxMin, pxMax, pyMin, pyMax, pzMin, pzMax); 244 return boundary.getTree(false); 245 } 246 247 /** Build boundary from vertices and facets. 248 * @param vertices list of polyhedrons set vertices 249 * @param facets list of facets, as vertices indices in the vertices list 250 * @param tolerance tolerance below which points are considered identical 251 * @return boundary as a list of sub-hyperplanes 252 * @exception MathIllegalArgumentException if some basic sanity checks fail 253 * @since 3.5 254 */ 255 private static List<SubHyperplane<Euclidean3D>> buildBoundary(final List<Vector3D> vertices, 256 final List<int[]> facets, 257 final double tolerance) { 258 259 // check vertices distances 260 for (int i = 0; i < vertices.size() - 1; ++i) { 261 final Vector3D vi = vertices.get(i); 262 for (int j = i + 1; j < vertices.size(); ++j) { 263 if (Vector3D.distance(vi, vertices.get(j)) <= tolerance) { 264 throw new MathIllegalArgumentException(LocalizedFormats.CLOSE_VERTICES, 265 vi.getX(), vi.getY(), vi.getZ()); 266 } 267 } 268 } 269 270 // find how vertices are referenced by facets 271 final int[][] references = findReferences(vertices, facets); 272 273 // find how vertices are linked together by edges along the facets they belong to 274 final int[][] successors = successors(vertices, facets, references); 275 276 // check edges orientations 277 for (int vA = 0; vA < vertices.size(); ++vA) { 278 for (final int vB : successors[vA]) { 279 280 if (vB >= 0) { 281 // when facets are properly oriented, if vB is the successor of vA on facet f1, 282 // then there must be an adjacent facet f2 where vA is the successor of vB 283 boolean found = false; 284 for (final int v : successors[vB]) { 285 found = found || (v == vA); 286 } 287 if (!found) { 288 final Vector3D start = vertices.get(vA); 289 final Vector3D end = vertices.get(vB); 290 throw new MathIllegalArgumentException(LocalizedFormats.EDGE_CONNECTED_TO_ONE_FACET, 291 start.getX(), start.getY(), start.getZ(), 292 end.getX(), end.getY(), end.getZ()); 293 } 294 } 295 } 296 } 297 298 final List<SubHyperplane<Euclidean3D>> boundary = new ArrayList<SubHyperplane<Euclidean3D>>(); 299 300 for (final int[] facet : facets) { 301 302 // define facet plane from the first 3 points 303 Plane plane = new Plane(vertices.get(facet[0]), vertices.get(facet[1]), vertices.get(facet[2]), 304 tolerance); 305 306 // check all points are in the plane 307 final Vector2D[] two2Points = new Vector2D[facet.length]; 308 for (int i = 0 ; i < facet.length; ++i) { 309 final Vector3D v = vertices.get(facet[i]); 310 if (!plane.contains(v)) { 311 throw new MathIllegalArgumentException(LocalizedFormats.OUT_OF_PLANE, 312 v.getX(), v.getY(), v.getZ()); 313 } 314 two2Points[i] = plane.toSubSpace(v); 315 } 316 317 // create the polygonal facet 318 boundary.add(new SubPlane(plane, new PolygonsSet(tolerance, two2Points))); 319 320 } 321 322 return boundary; 323 324 } 325 326 /** Find the facets that reference each edges. 327 * @param vertices list of polyhedrons set vertices 328 * @param facets list of facets, as vertices indices in the vertices list 329 * @return references array such that r[v][k] = f for some k if facet f contains vertex v 330 * @exception MathIllegalArgumentException if some facets have fewer than 3 vertices 331 * @since 3.5 332 */ 333 private static int[][] findReferences(final List<Vector3D> vertices, final List<int[]> facets) { 334 335 // find the maximum number of facets a vertex belongs to 336 final int[] nbFacets = new int[vertices.size()]; 337 int maxFacets = 0; 338 for (final int[] facet : facets) { 339 if (facet.length < 3) { 340 throw new NumberIsTooSmallException(LocalizedFormats.WRONG_NUMBER_OF_POINTS, 341 3, facet.length, true); 342 } 343 for (final int index : facet) { 344 maxFacets = FastMath.max(maxFacets, ++nbFacets[index]); 345 } 346 } 347 348 // set up the references array 349 final int[][] references = new int[vertices.size()][maxFacets]; 350 for (int[] r : references) { 351 Arrays.fill(r, -1); 352 } 353 for (int f = 0; f < facets.size(); ++f) { 354 for (final int v : facets.get(f)) { 355 // vertex v is referenced by facet f 356 int k = 0; 357 while (k < maxFacets && references[v][k] >= 0) { 358 ++k; 359 } 360 references[v][k] = f; 361 } 362 } 363 364 return references; 365 366 } 367 368 /** Find the successors of all vertices among all facets they belong to. 369 * @param vertices list of polyhedrons set vertices 370 * @param facets list of facets, as vertices indices in the vertices list 371 * @param references facets references array 372 * @return indices of vertices that follow vertex v in some facet (the array 373 * may contain extra entries at the end, set to negative indices) 374 * @exception MathIllegalArgumentException if the same vertex appears more than 375 * once in the successors list (which means one facet orientation is wrong) 376 * @since 3.5 377 */ 378 private static int[][] successors(final List<Vector3D> vertices, final List<int[]> facets, 379 final int[][] references) { 380 381 // create an array large enough 382 final int[][] successors = new int[vertices.size()][references[0].length]; 383 for (final int[] s : successors) { 384 Arrays.fill(s, -1); 385 } 386 387 for (int v = 0; v < vertices.size(); ++v) { 388 for (int k = 0; k < successors[v].length && references[v][k] >= 0; ++k) { 389 390 // look for vertex v 391 final int[] facet = facets.get(references[v][k]); 392 int i = 0; 393 while (i < facet.length && facet[i] != v) { 394 ++i; 395 } 396 397 // we have found vertex v, we deduce its successor on current facet 398 successors[v][k] = facet[(i + 1) % facet.length]; 399 for (int l = 0; l < k; ++l) { 400 if (successors[v][l] == successors[v][k]) { 401 final Vector3D start = vertices.get(v); 402 final Vector3D end = vertices.get(successors[v][k]); 403 throw new MathIllegalArgumentException(LocalizedFormats.FACET_ORIENTATION_MISMATCH, 404 start.getX(), start.getY(), start.getZ(), 405 end.getX(), end.getY(), end.getZ()); 406 } 407 } 408 409 } 410 } 411 412 return successors; 413 414 } 415 416 /** {@inheritDoc} */ 417 @Override 418 public PolyhedronsSet buildNew(final BSPTree<Euclidean3D> tree) { 419 return new PolyhedronsSet(tree, getTolerance()); 420 } 421 422 /** {@inheritDoc} */ 423 @Override 424 protected void computeGeometricalProperties() { 425 426 // compute the contribution of all boundary facets 427 getTree(true).visit(new FacetsContributionVisitor()); 428 429 if (getSize() < 0) { 430 // the polyhedrons set as a finite outside 431 // surrounded by an infinite inside 432 setSize(Double.POSITIVE_INFINITY); 433 setBarycenter((Point<Euclidean3D>) Vector3D.NaN); 434 } else { 435 // the polyhedrons set is finite, apply the remaining scaling factors 436 setSize(getSize() / 3.0); 437 setBarycenter((Point<Euclidean3D>) new Vector3D(1.0 / (4 * getSize()), (Vector3D) getBarycenter())); 438 } 439 440 } 441 442 /** Visitor computing geometrical properties. */ 443 private class FacetsContributionVisitor implements BSPTreeVisitor<Euclidean3D> { 444 445 /** Simple constructor. */ 446 FacetsContributionVisitor() { 447 setSize(0); 448 setBarycenter((Point<Euclidean3D>) new Vector3D(0, 0, 0)); 449 } 450 451 /** {@inheritDoc} */ 452 public Order visitOrder(final BSPTree<Euclidean3D> node) { 453 return Order.MINUS_SUB_PLUS; 454 } 455 456 /** {@inheritDoc} */ 457 public void visitInternalNode(final BSPTree<Euclidean3D> node) { 458 @SuppressWarnings("unchecked") 459 final BoundaryAttribute<Euclidean3D> attribute = 460 (BoundaryAttribute<Euclidean3D>) node.getAttribute(); 461 if (attribute.getPlusOutside() != null) { 462 addContribution(attribute.getPlusOutside(), false); 463 } 464 if (attribute.getPlusInside() != null) { 465 addContribution(attribute.getPlusInside(), true); 466 } 467 } 468 469 /** {@inheritDoc} */ 470 public void visitLeafNode(final BSPTree<Euclidean3D> node) { 471 } 472 473 /** Add he contribution of a boundary facet. 474 * @param facet boundary facet 475 * @param reversed if true, the facet has the inside on its plus side 476 */ 477 private void addContribution(final SubHyperplane<Euclidean3D> facet, final boolean reversed) { 478 479 final Region<Euclidean2D> polygon = ((SubPlane) facet).getRemainingRegion(); 480 final double area = polygon.getSize(); 481 482 if (Double.isInfinite(area)) { 483 setSize(Double.POSITIVE_INFINITY); 484 setBarycenter((Point<Euclidean3D>) Vector3D.NaN); 485 } else { 486 487 final Plane plane = (Plane) facet.getHyperplane(); 488 final Vector3D facetB = plane.toSpace(polygon.getBarycenter()); 489 double scaled = area * facetB.dotProduct(plane.getNormal()); 490 if (reversed) { 491 scaled = -scaled; 492 } 493 494 setSize(getSize() + scaled); 495 setBarycenter((Point<Euclidean3D>) new Vector3D(1.0, (Vector3D) getBarycenter(), scaled, facetB)); 496 497 } 498 499 } 500 501 } 502 503 /** Get the first sub-hyperplane crossed by a semi-infinite line. 504 * @param point start point of the part of the line considered 505 * @param line line to consider (contains point) 506 * @return the first sub-hyperplane crossed by the line after the 507 * given point, or null if the line does not intersect any 508 * sub-hyperplane 509 */ 510 public SubHyperplane<Euclidean3D> firstIntersection(final Vector3D point, final Line line) { 511 return recurseFirstIntersection(getTree(true), point, line); 512 } 513 514 /** Get the first sub-hyperplane crossed by a semi-infinite line. 515 * @param node current node 516 * @param point start point of the part of the line considered 517 * @param line line to consider (contains point) 518 * @return the first sub-hyperplane crossed by the line after the 519 * given point, or null if the line does not intersect any 520 * sub-hyperplane 521 */ 522 private SubHyperplane<Euclidean3D> recurseFirstIntersection(final BSPTree<Euclidean3D> node, 523 final Vector3D point, 524 final Line line) { 525 526 final SubHyperplane<Euclidean3D> cut = node.getCut(); 527 if (cut == null) { 528 return null; 529 } 530 final BSPTree<Euclidean3D> minus = node.getMinus(); 531 final BSPTree<Euclidean3D> plus = node.getPlus(); 532 final Plane plane = (Plane) cut.getHyperplane(); 533 534 // establish search order 535 final double offset = plane.getOffset((Point<Euclidean3D>) point); 536 final boolean in = FastMath.abs(offset) < getTolerance(); 537 final BSPTree<Euclidean3D> near; 538 final BSPTree<Euclidean3D> far; 539 if (offset < 0) { 540 near = minus; 541 far = plus; 542 } else { 543 near = plus; 544 far = minus; 545 } 546 547 if (in) { 548 // search in the cut hyperplane 549 final SubHyperplane<Euclidean3D> facet = boundaryFacet(point, node); 550 if (facet != null) { 551 return facet; 552 } 553 } 554 555 // search in the near branch 556 final SubHyperplane<Euclidean3D> crossed = recurseFirstIntersection(near, point, line); 557 if (crossed != null) { 558 return crossed; 559 } 560 561 if (!in) { 562 // search in the cut hyperplane 563 final Vector3D hit3D = plane.intersection(line); 564 if (hit3D != null && line.getAbscissa(hit3D) > line.getAbscissa(point)) { 565 final SubHyperplane<Euclidean3D> facet = boundaryFacet(hit3D, node); 566 if (facet != null) { 567 return facet; 568 } 569 } 570 } 571 572 // search in the far branch 573 return recurseFirstIntersection(far, point, line); 574 575 } 576 577 /** Check if a point belongs to the boundary part of a node. 578 * @param point point to check 579 * @param node node containing the boundary facet to check 580 * @return the boundary facet this points belongs to (or null if it 581 * does not belong to any boundary facet) 582 */ 583 private SubHyperplane<Euclidean3D> boundaryFacet(final Vector3D point, 584 final BSPTree<Euclidean3D> node) { 585 final Vector2D point2D = ((Plane) node.getCut().getHyperplane()).toSubSpace((Point<Euclidean3D>) point); 586 @SuppressWarnings("unchecked") 587 final BoundaryAttribute<Euclidean3D> attribute = 588 (BoundaryAttribute<Euclidean3D>) node.getAttribute(); 589 if ((attribute.getPlusOutside() != null) && 590 (((SubPlane) attribute.getPlusOutside()).getRemainingRegion().checkPoint(point2D) == Location.INSIDE)) { 591 return attribute.getPlusOutside(); 592 } 593 if ((attribute.getPlusInside() != null) && 594 (((SubPlane) attribute.getPlusInside()).getRemainingRegion().checkPoint(point2D) == Location.INSIDE)) { 595 return attribute.getPlusInside(); 596 } 597 return null; 598 } 599 600 /** Rotate the region around the specified point. 601 * <p>The instance is not modified, a new instance is created.</p> 602 * @param center rotation center 603 * @param rotation vectorial rotation operator 604 * @return a new instance representing the rotated region 605 */ 606 public PolyhedronsSet rotate(final Vector3D center, final Rotation rotation) { 607 return (PolyhedronsSet) applyTransform(new RotationTransform(center, rotation)); 608 } 609 610 /** 3D rotation as a Transform. */ 611 private static class RotationTransform implements Transform<Euclidean3D, Euclidean2D> { 612 613 /** Center point of the rotation. */ 614 private Vector3D center; 615 616 /** Vectorial rotation. */ 617 private Rotation rotation; 618 619 /** Cached original hyperplane. */ 620 private Plane cachedOriginal; 621 622 /** Cached 2D transform valid inside the cached original hyperplane. */ 623 private Transform<Euclidean2D, Euclidean1D> cachedTransform; 624 625 /** Build a rotation transform. 626 * @param center center point of the rotation 627 * @param rotation vectorial rotation 628 */ 629 RotationTransform(final Vector3D center, final Rotation rotation) { 630 this.center = center; 631 this.rotation = rotation; 632 } 633 634 /** {@inheritDoc} */ 635 public Vector3D apply(final Point<Euclidean3D> point) { 636 final Vector3D delta = ((Vector3D) point).subtract(center); 637 return new Vector3D(1.0, center, 1.0, rotation.applyTo(delta)); 638 } 639 640 /** {@inheritDoc} */ 641 public Plane apply(final Hyperplane<Euclidean3D> hyperplane) { 642 return ((Plane) hyperplane).rotate(center, rotation); 643 } 644 645 /** {@inheritDoc} */ 646 public SubHyperplane<Euclidean2D> apply(final SubHyperplane<Euclidean2D> sub, 647 final Hyperplane<Euclidean3D> original, 648 final Hyperplane<Euclidean3D> transformed) { 649 if (original != cachedOriginal) { 650 // we have changed hyperplane, reset the in-hyperplane transform 651 652 final Plane oPlane = (Plane) original; 653 final Plane tPlane = (Plane) transformed; 654 final Vector3D p00 = oPlane.getOrigin(); 655 final Vector3D p10 = oPlane.toSpace((Point<Euclidean2D>) new Vector2D(1.0, 0.0)); 656 final Vector3D p01 = oPlane.toSpace((Point<Euclidean2D>) new Vector2D(0.0, 1.0)); 657 final Vector2D tP00 = tPlane.toSubSpace((Point<Euclidean3D>) apply(p00)); 658 final Vector2D tP10 = tPlane.toSubSpace((Point<Euclidean3D>) apply(p10)); 659 final Vector2D tP01 = tPlane.toSubSpace((Point<Euclidean3D>) apply(p01)); 660 661 cachedOriginal = (Plane) original; 662 cachedTransform = 663 org.apache.commons.math3.geometry.euclidean.twod.Line.getTransform(tP10.getX() - tP00.getX(), 664 tP10.getY() - tP00.getY(), 665 tP01.getX() - tP00.getX(), 666 tP01.getY() - tP00.getY(), 667 tP00.getX(), 668 tP00.getY()); 669 670 } 671 return ((SubLine) sub).applyTransform(cachedTransform); 672 } 673 674 } 675 676 /** Translate the region by the specified amount. 677 * <p>The instance is not modified, a new instance is created.</p> 678 * @param translation translation to apply 679 * @return a new instance representing the translated region 680 */ 681 public PolyhedronsSet translate(final Vector3D translation) { 682 return (PolyhedronsSet) applyTransform(new TranslationTransform(translation)); 683 } 684 685 /** 3D translation as a transform. */ 686 private static class TranslationTransform implements Transform<Euclidean3D, Euclidean2D> { 687 688 /** Translation vector. */ 689 private Vector3D translation; 690 691 /** Cached original hyperplane. */ 692 private Plane cachedOriginal; 693 694 /** Cached 2D transform valid inside the cached original hyperplane. */ 695 private Transform<Euclidean2D, Euclidean1D> cachedTransform; 696 697 /** Build a translation transform. 698 * @param translation translation vector 699 */ 700 TranslationTransform(final Vector3D translation) { 701 this.translation = translation; 702 } 703 704 /** {@inheritDoc} */ 705 public Vector3D apply(final Point<Euclidean3D> point) { 706 return new Vector3D(1.0, (Vector3D) point, 1.0, translation); 707 } 708 709 /** {@inheritDoc} */ 710 public Plane apply(final Hyperplane<Euclidean3D> hyperplane) { 711 return ((Plane) hyperplane).translate(translation); 712 } 713 714 /** {@inheritDoc} */ 715 public SubHyperplane<Euclidean2D> apply(final SubHyperplane<Euclidean2D> sub, 716 final Hyperplane<Euclidean3D> original, 717 final Hyperplane<Euclidean3D> transformed) { 718 if (original != cachedOriginal) { 719 // we have changed hyperplane, reset the in-hyperplane transform 720 721 final Plane oPlane = (Plane) original; 722 final Plane tPlane = (Plane) transformed; 723 final Vector2D shift = tPlane.toSubSpace((Point<Euclidean3D>) apply(oPlane.getOrigin())); 724 725 cachedOriginal = (Plane) original; 726 cachedTransform = 727 org.apache.commons.math3.geometry.euclidean.twod.Line.getTransform(1, 0, 0, 1, 728 shift.getX(), 729 shift.getY()); 730 731 } 732 733 return ((SubLine) sub).applyTransform(cachedTransform); 734 735 } 736 737 } 738 739}