diff options
Diffstat (limited to 'src/main/java/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java')
-rw-r--r-- | src/main/java/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java | 739 |
1 files changed, 739 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java new file mode 100644 index 0000000..f190e22 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java @@ -0,0 +1,739 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.math3.geometry.euclidean.threed; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.math3.exception.MathIllegalArgumentException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.exception.util.LocalizedFormats; +import org.apache.commons.math3.geometry.Point; +import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; +import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; +import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet; +import org.apache.commons.math3.geometry.euclidean.twod.SubLine; +import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import org.apache.commons.math3.geometry.partitioning.AbstractRegion; +import org.apache.commons.math3.geometry.partitioning.BSPTree; +import org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor; +import org.apache.commons.math3.geometry.partitioning.BoundaryAttribute; +import org.apache.commons.math3.geometry.partitioning.Hyperplane; +import org.apache.commons.math3.geometry.partitioning.Region; +import org.apache.commons.math3.geometry.partitioning.RegionFactory; +import org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import org.apache.commons.math3.geometry.partitioning.Transform; +import org.apache.commons.math3.util.FastMath; + +/** This class represents a 3D region: a set of polyhedrons. + * @since 3.0 + */ +public class PolyhedronsSet extends AbstractRegion<Euclidean3D, Euclidean2D> { + + /** Default value for tolerance. */ + private static final double DEFAULT_TOLERANCE = 1.0e-10; + + /** Build a polyhedrons set representing the whole real line. + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolyhedronsSet(final double tolerance) { + super(tolerance); + } + + /** Build a polyhedrons set from a BSP tree. + * <p>The leaf nodes of the BSP tree <em>must</em> have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p> + * <p> + * This constructor is aimed at expert use, as building the tree may + * be a difficult task. It is not intended for general use and for + * performances reasons does not check thoroughly its input, as this would + * require walking the full tree each time. Failing to provide a tree with + * the proper attributes, <em>will</em> therefore generate problems like + * {@link NullPointerException} or {@link ClassCastException} only later on. + * This limitation is known and explains why this constructor is for expert + * use only. The caller does have the responsibility to provided correct arguments. + * </p> + * @param tree inside/outside BSP tree representing the region + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolyhedronsSet(final BSPTree<Euclidean3D> tree, final double tolerance) { + super(tree, tolerance); + } + + /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by sub-hyperplanes. + * <p>The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.</p> + * <p>The boundary elements can be in any order, and can form + * several non-connected sets (like for example polyhedrons with holes + * or a set of disjoint polyhedrons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link Region#checkPoint(Point) checkPoint} method will + * not be meaningful anymore.</p> + * <p>If the boundary is empty, the region will represent the whole + * space.</p> + * @param boundary collection of boundary elements, as a + * collection of {@link SubHyperplane SubHyperplane} objects + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolyhedronsSet(final Collection<SubHyperplane<Euclidean3D>> boundary, + final double tolerance) { + super(boundary, tolerance); + } + + /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by connected vertices. + * <p> + * The boundary is provided as a list of vertices and a list of facets. + * Each facet is specified as an integer array containing the arrays vertices + * indices in the vertices list. Each facet normal is oriented by right hand + * rule to the facet vertices list. + * </p> + * <p> + * Some basic sanity checks are performed but not everything is thoroughly + * assessed, so it remains under caller responsibility to ensure the vertices + * and facets are consistent and properly define a polyhedrons set. + * </p> + * @param vertices list of polyhedrons set vertices + * @param facets list of facets, as vertices indices in the vertices list + * @param tolerance tolerance below which points are considered identical + * @exception MathIllegalArgumentException if some basic sanity checks fail + * @since 3.5 + */ + public PolyhedronsSet(final List<Vector3D> vertices, final List<int[]> facets, + final double tolerance) { + super(buildBoundary(vertices, facets, tolerance), tolerance); + } + + /** Build a parallellepipedic box. + * @param xMin low bound along the x direction + * @param xMax high bound along the x direction + * @param yMin low bound along the y direction + * @param yMax high bound along the y direction + * @param zMin low bound along the z direction + * @param zMax high bound along the z direction + * @param tolerance tolerance below which points are considered identical + * @since 3.3 + */ + public PolyhedronsSet(final double xMin, final double xMax, + final double yMin, final double yMax, + final double zMin, final double zMax, + final double tolerance) { + super(buildBoundary(xMin, xMax, yMin, yMax, zMin, zMax, tolerance), tolerance); + } + + /** Build a polyhedrons set representing the whole real line. + * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(double)} + */ + @Deprecated + public PolyhedronsSet() { + this(DEFAULT_TOLERANCE); + } + + /** Build a polyhedrons set from a BSP tree. + * <p>The leaf nodes of the BSP tree <em>must</em> have a + * {@code Boolean} attribute representing the inside status of + * the corresponding cell (true for inside cells, false for outside + * cells). In order to avoid building too many small objects, it is + * recommended to use the predefined constants + * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p> + * @param tree inside/outside BSP tree representing the region + * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(BSPTree, double)} + */ + @Deprecated + public PolyhedronsSet(final BSPTree<Euclidean3D> tree) { + this(tree, DEFAULT_TOLERANCE); + } + + /** Build a polyhedrons set from a Boundary REPresentation (B-rep). + * <p>The boundary is provided as a collection of {@link + * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the + * interior part of the region on its minus side and the exterior on + * its plus side.</p> + * <p>The boundary elements can be in any order, and can form + * several non-connected sets (like for example polyhedrons with holes + * or a set of disjoint polyhedrons considered as a whole). In + * fact, the elements do not even need to be connected together + * (their topological connections are not used here). However, if the + * boundary does not really separate an inside open from an outside + * open (open having here its topological meaning), then subsequent + * calls to the {@link Region#checkPoint(Point) checkPoint} method will + * not be meaningful anymore.</p> + * <p>If the boundary is empty, the region will represent the whole + * space.</p> + * @param boundary collection of boundary elements, as a + * collection of {@link SubHyperplane SubHyperplane} objects + * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(Collection, double)} + */ + @Deprecated + public PolyhedronsSet(final Collection<SubHyperplane<Euclidean3D>> boundary) { + this(boundary, DEFAULT_TOLERANCE); + } + + /** Build a parallellepipedic box. + * @param xMin low bound along the x direction + * @param xMax high bound along the x direction + * @param yMin low bound along the y direction + * @param yMax high bound along the y direction + * @param zMin low bound along the z direction + * @param zMax high bound along the z direction + * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(double, double, + * double, double, double, double, double)} + */ + @Deprecated + public PolyhedronsSet(final double xMin, final double xMax, + final double yMin, final double yMax, + final double zMin, final double zMax) { + this(xMin, xMax, yMin, yMax, zMin, zMax, DEFAULT_TOLERANCE); + } + + /** Build a parallellepipedic box boundary. + * @param xMin low bound along the x direction + * @param xMax high bound along the x direction + * @param yMin low bound along the y direction + * @param yMax high bound along the y direction + * @param zMin low bound along the z direction + * @param zMax high bound along the z direction + * @param tolerance tolerance below which points are considered identical + * @return boundary tree + * @since 3.3 + */ + private static BSPTree<Euclidean3D> buildBoundary(final double xMin, final double xMax, + final double yMin, final double yMax, + final double zMin, final double zMax, + final double tolerance) { + if ((xMin >= xMax - tolerance) || (yMin >= yMax - tolerance) || (zMin >= zMax - tolerance)) { + // too thin box, build an empty polygons set + return new BSPTree<Euclidean3D>(Boolean.FALSE); + } + final Plane pxMin = new Plane(new Vector3D(xMin, 0, 0), Vector3D.MINUS_I, tolerance); + final Plane pxMax = new Plane(new Vector3D(xMax, 0, 0), Vector3D.PLUS_I, tolerance); + final Plane pyMin = new Plane(new Vector3D(0, yMin, 0), Vector3D.MINUS_J, tolerance); + final Plane pyMax = new Plane(new Vector3D(0, yMax, 0), Vector3D.PLUS_J, tolerance); + final Plane pzMin = new Plane(new Vector3D(0, 0, zMin), Vector3D.MINUS_K, tolerance); + final Plane pzMax = new Plane(new Vector3D(0, 0, zMax), Vector3D.PLUS_K, tolerance); + @SuppressWarnings("unchecked") + final Region<Euclidean3D> boundary = + new RegionFactory<Euclidean3D>().buildConvex(pxMin, pxMax, pyMin, pyMax, pzMin, pzMax); + return boundary.getTree(false); + } + + /** Build boundary from vertices and facets. + * @param vertices list of polyhedrons set vertices + * @param facets list of facets, as vertices indices in the vertices list + * @param tolerance tolerance below which points are considered identical + * @return boundary as a list of sub-hyperplanes + * @exception MathIllegalArgumentException if some basic sanity checks fail + * @since 3.5 + */ + private static List<SubHyperplane<Euclidean3D>> buildBoundary(final List<Vector3D> vertices, + final List<int[]> facets, + final double tolerance) { + + // check vertices distances + for (int i = 0; i < vertices.size() - 1; ++i) { + final Vector3D vi = vertices.get(i); + for (int j = i + 1; j < vertices.size(); ++j) { + if (Vector3D.distance(vi, vertices.get(j)) <= tolerance) { + throw new MathIllegalArgumentException(LocalizedFormats.CLOSE_VERTICES, + vi.getX(), vi.getY(), vi.getZ()); + } + } + } + + // find how vertices are referenced by facets + final int[][] references = findReferences(vertices, facets); + + // find how vertices are linked together by edges along the facets they belong to + final int[][] successors = successors(vertices, facets, references); + + // check edges orientations + for (int vA = 0; vA < vertices.size(); ++vA) { + for (final int vB : successors[vA]) { + + if (vB >= 0) { + // when facets are properly oriented, if vB is the successor of vA on facet f1, + // then there must be an adjacent facet f2 where vA is the successor of vB + boolean found = false; + for (final int v : successors[vB]) { + found = found || (v == vA); + } + if (!found) { + final Vector3D start = vertices.get(vA); + final Vector3D end = vertices.get(vB); + throw new MathIllegalArgumentException(LocalizedFormats.EDGE_CONNECTED_TO_ONE_FACET, + start.getX(), start.getY(), start.getZ(), + end.getX(), end.getY(), end.getZ()); + } + } + } + } + + final List<SubHyperplane<Euclidean3D>> boundary = new ArrayList<SubHyperplane<Euclidean3D>>(); + + for (final int[] facet : facets) { + + // define facet plane from the first 3 points + Plane plane = new Plane(vertices.get(facet[0]), vertices.get(facet[1]), vertices.get(facet[2]), + tolerance); + + // check all points are in the plane + final Vector2D[] two2Points = new Vector2D[facet.length]; + for (int i = 0 ; i < facet.length; ++i) { + final Vector3D v = vertices.get(facet[i]); + if (!plane.contains(v)) { + throw new MathIllegalArgumentException(LocalizedFormats.OUT_OF_PLANE, + v.getX(), v.getY(), v.getZ()); + } + two2Points[i] = plane.toSubSpace(v); + } + + // create the polygonal facet + boundary.add(new SubPlane(plane, new PolygonsSet(tolerance, two2Points))); + + } + + return boundary; + + } + + /** Find the facets that reference each edges. + * @param vertices list of polyhedrons set vertices + * @param facets list of facets, as vertices indices in the vertices list + * @return references array such that r[v][k] = f for some k if facet f contains vertex v + * @exception MathIllegalArgumentException if some facets have fewer than 3 vertices + * @since 3.5 + */ + private static int[][] findReferences(final List<Vector3D> vertices, final List<int[]> facets) { + + // find the maximum number of facets a vertex belongs to + final int[] nbFacets = new int[vertices.size()]; + int maxFacets = 0; + for (final int[] facet : facets) { + if (facet.length < 3) { + throw new NumberIsTooSmallException(LocalizedFormats.WRONG_NUMBER_OF_POINTS, + 3, facet.length, true); + } + for (final int index : facet) { + maxFacets = FastMath.max(maxFacets, ++nbFacets[index]); + } + } + + // set up the references array + final int[][] references = new int[vertices.size()][maxFacets]; + for (int[] r : references) { + Arrays.fill(r, -1); + } + for (int f = 0; f < facets.size(); ++f) { + for (final int v : facets.get(f)) { + // vertex v is referenced by facet f + int k = 0; + while (k < maxFacets && references[v][k] >= 0) { + ++k; + } + references[v][k] = f; + } + } + + return references; + + } + + /** Find the successors of all vertices among all facets they belong to. + * @param vertices list of polyhedrons set vertices + * @param facets list of facets, as vertices indices in the vertices list + * @param references facets references array + * @return indices of vertices that follow vertex v in some facet (the array + * may contain extra entries at the end, set to negative indices) + * @exception MathIllegalArgumentException if the same vertex appears more than + * once in the successors list (which means one facet orientation is wrong) + * @since 3.5 + */ + private static int[][] successors(final List<Vector3D> vertices, final List<int[]> facets, + final int[][] references) { + + // create an array large enough + final int[][] successors = new int[vertices.size()][references[0].length]; + for (final int[] s : successors) { + Arrays.fill(s, -1); + } + + for (int v = 0; v < vertices.size(); ++v) { + for (int k = 0; k < successors[v].length && references[v][k] >= 0; ++k) { + + // look for vertex v + final int[] facet = facets.get(references[v][k]); + int i = 0; + while (i < facet.length && facet[i] != v) { + ++i; + } + + // we have found vertex v, we deduce its successor on current facet + successors[v][k] = facet[(i + 1) % facet.length]; + for (int l = 0; l < k; ++l) { + if (successors[v][l] == successors[v][k]) { + final Vector3D start = vertices.get(v); + final Vector3D end = vertices.get(successors[v][k]); + throw new MathIllegalArgumentException(LocalizedFormats.FACET_ORIENTATION_MISMATCH, + start.getX(), start.getY(), start.getZ(), + end.getX(), end.getY(), end.getZ()); + } + } + + } + } + + return successors; + + } + + /** {@inheritDoc} */ + @Override + public PolyhedronsSet buildNew(final BSPTree<Euclidean3D> tree) { + return new PolyhedronsSet(tree, getTolerance()); + } + + /** {@inheritDoc} */ + @Override + protected void computeGeometricalProperties() { + + // compute the contribution of all boundary facets + getTree(true).visit(new FacetsContributionVisitor()); + + if (getSize() < 0) { + // the polyhedrons set as a finite outside + // surrounded by an infinite inside + setSize(Double.POSITIVE_INFINITY); + setBarycenter((Point<Euclidean3D>) Vector3D.NaN); + } else { + // the polyhedrons set is finite, apply the remaining scaling factors + setSize(getSize() / 3.0); + setBarycenter((Point<Euclidean3D>) new Vector3D(1.0 / (4 * getSize()), (Vector3D) getBarycenter())); + } + + } + + /** Visitor computing geometrical properties. */ + private class FacetsContributionVisitor implements BSPTreeVisitor<Euclidean3D> { + + /** Simple constructor. */ + FacetsContributionVisitor() { + setSize(0); + setBarycenter((Point<Euclidean3D>) new Vector3D(0, 0, 0)); + } + + /** {@inheritDoc} */ + public Order visitOrder(final BSPTree<Euclidean3D> node) { + return Order.MINUS_SUB_PLUS; + } + + /** {@inheritDoc} */ + public void visitInternalNode(final BSPTree<Euclidean3D> node) { + @SuppressWarnings("unchecked") + final BoundaryAttribute<Euclidean3D> attribute = + (BoundaryAttribute<Euclidean3D>) node.getAttribute(); + if (attribute.getPlusOutside() != null) { + addContribution(attribute.getPlusOutside(), false); + } + if (attribute.getPlusInside() != null) { + addContribution(attribute.getPlusInside(), true); + } + } + + /** {@inheritDoc} */ + public void visitLeafNode(final BSPTree<Euclidean3D> node) { + } + + /** Add he contribution of a boundary facet. + * @param facet boundary facet + * @param reversed if true, the facet has the inside on its plus side + */ + private void addContribution(final SubHyperplane<Euclidean3D> facet, final boolean reversed) { + + final Region<Euclidean2D> polygon = ((SubPlane) facet).getRemainingRegion(); + final double area = polygon.getSize(); + + if (Double.isInfinite(area)) { + setSize(Double.POSITIVE_INFINITY); + setBarycenter((Point<Euclidean3D>) Vector3D.NaN); + } else { + + final Plane plane = (Plane) facet.getHyperplane(); + final Vector3D facetB = plane.toSpace(polygon.getBarycenter()); + double scaled = area * facetB.dotProduct(plane.getNormal()); + if (reversed) { + scaled = -scaled; + } + + setSize(getSize() + scaled); + setBarycenter((Point<Euclidean3D>) new Vector3D(1.0, (Vector3D) getBarycenter(), scaled, facetB)); + + } + + } + + } + + /** Get the first sub-hyperplane crossed by a semi-infinite line. + * @param point start point of the part of the line considered + * @param line line to consider (contains point) + * @return the first sub-hyperplane crossed by the line after the + * given point, or null if the line does not intersect any + * sub-hyperplane + */ + public SubHyperplane<Euclidean3D> firstIntersection(final Vector3D point, final Line line) { + return recurseFirstIntersection(getTree(true), point, line); + } + + /** Get the first sub-hyperplane crossed by a semi-infinite line. + * @param node current node + * @param point start point of the part of the line considered + * @param line line to consider (contains point) + * @return the first sub-hyperplane crossed by the line after the + * given point, or null if the line does not intersect any + * sub-hyperplane + */ + private SubHyperplane<Euclidean3D> recurseFirstIntersection(final BSPTree<Euclidean3D> node, + final Vector3D point, + final Line line) { + + final SubHyperplane<Euclidean3D> cut = node.getCut(); + if (cut == null) { + return null; + } + final BSPTree<Euclidean3D> minus = node.getMinus(); + final BSPTree<Euclidean3D> plus = node.getPlus(); + final Plane plane = (Plane) cut.getHyperplane(); + + // establish search order + final double offset = plane.getOffset((Point<Euclidean3D>) point); + final boolean in = FastMath.abs(offset) < getTolerance(); + final BSPTree<Euclidean3D> near; + final BSPTree<Euclidean3D> far; + if (offset < 0) { + near = minus; + far = plus; + } else { + near = plus; + far = minus; + } + + if (in) { + // search in the cut hyperplane + final SubHyperplane<Euclidean3D> facet = boundaryFacet(point, node); + if (facet != null) { + return facet; + } + } + + // search in the near branch + final SubHyperplane<Euclidean3D> crossed = recurseFirstIntersection(near, point, line); + if (crossed != null) { + return crossed; + } + + if (!in) { + // search in the cut hyperplane + final Vector3D hit3D = plane.intersection(line); + if (hit3D != null && line.getAbscissa(hit3D) > line.getAbscissa(point)) { + final SubHyperplane<Euclidean3D> facet = boundaryFacet(hit3D, node); + if (facet != null) { + return facet; + } + } + } + + // search in the far branch + return recurseFirstIntersection(far, point, line); + + } + + /** Check if a point belongs to the boundary part of a node. + * @param point point to check + * @param node node containing the boundary facet to check + * @return the boundary facet this points belongs to (or null if it + * does not belong to any boundary facet) + */ + private SubHyperplane<Euclidean3D> boundaryFacet(final Vector3D point, + final BSPTree<Euclidean3D> node) { + final Vector2D point2D = ((Plane) node.getCut().getHyperplane()).toSubSpace((Point<Euclidean3D>) point); + @SuppressWarnings("unchecked") + final BoundaryAttribute<Euclidean3D> attribute = + (BoundaryAttribute<Euclidean3D>) node.getAttribute(); + if ((attribute.getPlusOutside() != null) && + (((SubPlane) attribute.getPlusOutside()).getRemainingRegion().checkPoint(point2D) == Location.INSIDE)) { + return attribute.getPlusOutside(); + } + if ((attribute.getPlusInside() != null) && + (((SubPlane) attribute.getPlusInside()).getRemainingRegion().checkPoint(point2D) == Location.INSIDE)) { + return attribute.getPlusInside(); + } + return null; + } + + /** Rotate the region around the specified point. + * <p>The instance is not modified, a new instance is created.</p> + * @param center rotation center + * @param rotation vectorial rotation operator + * @return a new instance representing the rotated region + */ + public PolyhedronsSet rotate(final Vector3D center, final Rotation rotation) { + return (PolyhedronsSet) applyTransform(new RotationTransform(center, rotation)); + } + + /** 3D rotation as a Transform. */ + private static class RotationTransform implements Transform<Euclidean3D, Euclidean2D> { + + /** Center point of the rotation. */ + private Vector3D center; + + /** Vectorial rotation. */ + private Rotation rotation; + + /** Cached original hyperplane. */ + private Plane cachedOriginal; + + /** Cached 2D transform valid inside the cached original hyperplane. */ + private Transform<Euclidean2D, Euclidean1D> cachedTransform; + + /** Build a rotation transform. + * @param center center point of the rotation + * @param rotation vectorial rotation + */ + RotationTransform(final Vector3D center, final Rotation rotation) { + this.center = center; + this.rotation = rotation; + } + + /** {@inheritDoc} */ + public Vector3D apply(final Point<Euclidean3D> point) { + final Vector3D delta = ((Vector3D) point).subtract(center); + return new Vector3D(1.0, center, 1.0, rotation.applyTo(delta)); + } + + /** {@inheritDoc} */ + public Plane apply(final Hyperplane<Euclidean3D> hyperplane) { + return ((Plane) hyperplane).rotate(center, rotation); + } + + /** {@inheritDoc} */ + public SubHyperplane<Euclidean2D> apply(final SubHyperplane<Euclidean2D> sub, + final Hyperplane<Euclidean3D> original, + final Hyperplane<Euclidean3D> transformed) { + if (original != cachedOriginal) { + // we have changed hyperplane, reset the in-hyperplane transform + + final Plane oPlane = (Plane) original; + final Plane tPlane = (Plane) transformed; + final Vector3D p00 = oPlane.getOrigin(); + final Vector3D p10 = oPlane.toSpace((Point<Euclidean2D>) new Vector2D(1.0, 0.0)); + final Vector3D p01 = oPlane.toSpace((Point<Euclidean2D>) new Vector2D(0.0, 1.0)); + final Vector2D tP00 = tPlane.toSubSpace((Point<Euclidean3D>) apply(p00)); + final Vector2D tP10 = tPlane.toSubSpace((Point<Euclidean3D>) apply(p10)); + final Vector2D tP01 = tPlane.toSubSpace((Point<Euclidean3D>) apply(p01)); + + cachedOriginal = (Plane) original; + cachedTransform = + org.apache.commons.math3.geometry.euclidean.twod.Line.getTransform(tP10.getX() - tP00.getX(), + tP10.getY() - tP00.getY(), + tP01.getX() - tP00.getX(), + tP01.getY() - tP00.getY(), + tP00.getX(), + tP00.getY()); + + } + return ((SubLine) sub).applyTransform(cachedTransform); + } + + } + + /** Translate the region by the specified amount. + * <p>The instance is not modified, a new instance is created.</p> + * @param translation translation to apply + * @return a new instance representing the translated region + */ + public PolyhedronsSet translate(final Vector3D translation) { + return (PolyhedronsSet) applyTransform(new TranslationTransform(translation)); + } + + /** 3D translation as a transform. */ + private static class TranslationTransform implements Transform<Euclidean3D, Euclidean2D> { + + /** Translation vector. */ + private Vector3D translation; + + /** Cached original hyperplane. */ + private Plane cachedOriginal; + + /** Cached 2D transform valid inside the cached original hyperplane. */ + private Transform<Euclidean2D, Euclidean1D> cachedTransform; + + /** Build a translation transform. + * @param translation translation vector + */ + TranslationTransform(final Vector3D translation) { + this.translation = translation; + } + + /** {@inheritDoc} */ + public Vector3D apply(final Point<Euclidean3D> point) { + return new Vector3D(1.0, (Vector3D) point, 1.0, translation); + } + + /** {@inheritDoc} */ + public Plane apply(final Hyperplane<Euclidean3D> hyperplane) { + return ((Plane) hyperplane).translate(translation); + } + + /** {@inheritDoc} */ + public SubHyperplane<Euclidean2D> apply(final SubHyperplane<Euclidean2D> sub, + final Hyperplane<Euclidean3D> original, + final Hyperplane<Euclidean3D> transformed) { + if (original != cachedOriginal) { + // we have changed hyperplane, reset the in-hyperplane transform + + final Plane oPlane = (Plane) original; + final Plane tPlane = (Plane) transformed; + final Vector2D shift = tPlane.toSubSpace((Point<Euclidean3D>) apply(oPlane.getOrigin())); + + cachedOriginal = (Plane) original; + cachedTransform = + org.apache.commons.math3.geometry.euclidean.twod.Line.getTransform(1, 0, 0, 1, + shift.getX(), + shift.getY()); + + } + + return ((SubLine) sub).applyTransform(cachedTransform); + + } + + } + +} |