EmbeddedTreePlaneSubset.java

  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.  *      http://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. package org.apache.commons.geometry.euclidean.threed;

  18. import java.util.ArrayList;
  19. import java.util.List;

  20. import org.apache.commons.geometry.core.Transform;
  21. import org.apache.commons.geometry.core.partitioning.Hyperplane;
  22. import org.apache.commons.geometry.core.partitioning.Split;
  23. import org.apache.commons.geometry.euclidean.twod.ConvexArea;
  24. import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
  25. import org.apache.commons.geometry.euclidean.twod.Vector2D;
  26. import org.apache.commons.geometry.euclidean.twod.rotation.Rotation2D;
  27. import org.apache.commons.numbers.core.Precision;

  28. /** Class representing an arbitrary subset of a plane using a {@link RegionBSPTree2D}.
  29.  * This class can represent convex, non-convex, finite, infinite, and empty regions.
  30.  *
  31.  * <p>This class is mutable and <em>not</em> thread safe.</p>
  32.  */
  33. public final class EmbeddedTreePlaneSubset extends AbstractEmbeddedRegionPlaneSubset {

  34.     /** The 2D region representing the area on the plane. */
  35.     private final RegionBSPTree2D region;

  36.     /** Construct a new, empty plane subset for the given plane.
  37.      * @param plane plane containing the subset
  38.      */
  39.     public EmbeddedTreePlaneSubset(final EmbeddingPlane plane) {
  40.         this(plane, false);
  41.     }

  42.     /** Construct a new subset for the given plane. If {@code full}
  43.      * is true, then the subset will cover the entire plane; otherwise,
  44.      * it will be empty.
  45.      * @param plane plane containing the subset
  46.      * @param full if true, the subset will cover the entire space;
  47.      *      otherwise it will be empty
  48.      */
  49.     public EmbeddedTreePlaneSubset(final EmbeddingPlane plane, final boolean full) {
  50.         this(plane, new RegionBSPTree2D(full));
  51.     }

  52.     /** Construct a new instance from its defining plane and subspace region.
  53.      * @param plane plane containing the subset
  54.      * @param region subspace region for the plane subset
  55.      */
  56.     public EmbeddedTreePlaneSubset(final EmbeddingPlane plane, final RegionBSPTree2D region) {
  57.         super(plane);

  58.         this.region = region;
  59.     }

  60.     /** {@inheritDoc} */
  61.     @Override
  62.     public PlaneSubset.Embedded getEmbedded() {
  63.         return this;
  64.     }

  65.     /** {@inheritDoc} */
  66.     @Override
  67.     public RegionBSPTree2D getSubspaceRegion() {
  68.         return region;
  69.     }

  70.     /** {@inheritDoc} */
  71.     @Override
  72.     public List<PlaneConvexSubset> toConvex() {
  73.         final List<ConvexArea> areas = region.toConvex();

  74.         final List<PlaneConvexSubset> facets = new ArrayList<>(areas.size());

  75.         for (final ConvexArea area : areas) {
  76.             facets.add(Planes.subsetFromConvexArea(getPlane(), area));
  77.         }

  78.         return facets;
  79.     }

  80.     /** {@inheritDoc} */
  81.     @Override
  82.     public List<Triangle3D> toTriangles() {
  83.         final EmbeddingPlane plane = getPlane();
  84.         final List<Triangle3D> triangles = new ArrayList<>();

  85.         List<Vector3D> vertices;
  86.         for (final ConvexArea area : region.toConvex()) {
  87.             if (area.isInfinite()) {
  88.                 throw new IllegalStateException("Cannot convert infinite plane subset to triangles: " + this);
  89.             }

  90.             vertices = plane.toSpace(area.getVertices());

  91.             triangles.addAll(Planes.convexPolygonToTriangleFan(plane, vertices));
  92.         }

  93.         return triangles;
  94.     }

  95.     /** {@inheritDoc} */
  96.     @Override
  97.     public Bounds3D getBounds() {
  98.         return getBoundsFromSubspace(region);
  99.     }

  100.     /** {@inheritDoc}
  101.      *
  102.      * <p>In all cases, the current instance is not modified. However, In order to avoid
  103.      * unnecessary copying, this method will use the current instance as the split value when
  104.      * the instance lies entirely on the plus or minus side of the splitter. For example, if
  105.      * this instance lies entirely on the minus side of the splitter, the plane subset
  106.      * returned by {@link Split#getMinus()} will be this instance. Similarly, {@link Split#getPlus()}
  107.      * will return the current instance if it lies entirely on the plus side. Callers need to make
  108.      * special note of this, since this class is mutable.</p>
  109.      */
  110.     @Override
  111.     public Split<EmbeddedTreePlaneSubset> split(final Hyperplane<Vector3D> splitter) {
  112.         return Planes.subspaceSplit((Plane) splitter, this,
  113.             (p, r) -> new EmbeddedTreePlaneSubset(p, (RegionBSPTree2D) r));
  114.     }

  115.     /** {@inheritDoc} */
  116.     @Override
  117.     public EmbeddedTreePlaneSubset transform(final Transform<Vector3D> transform) {
  118.         final EmbeddingPlane.SubspaceTransform subTransform =
  119.                 getPlane().getEmbedding().subspaceTransform(transform);

  120.         final RegionBSPTree2D tRegion = RegionBSPTree2D.empty();
  121.         tRegion.copy(region);
  122.         tRegion.transform(subTransform.getTransform());

  123.         return new EmbeddedTreePlaneSubset(subTransform.getPlane(), tRegion);
  124.     }

  125.     /** Add a plane convex subset to this instance.
  126.      * @param subset plane convex subset to add
  127.      * @throws IllegalArgumentException if the given plane subset is not from
  128.      *      a plane equivalent to this instance
  129.      */
  130.     public void add(final PlaneConvexSubset subset) {
  131.         Planes.validatePlanesEquivalent(getPlane(), subset.getPlane());

  132.         final PlaneConvexSubset.Embedded embedded = subset.getEmbedded();
  133.         final Rotation2D rot = getEmbeddedRegionRotation(embedded);

  134.         final ConvexArea subspaceArea = embedded.getSubspaceRegion();

  135.         final ConvexArea toAdd = rot != null ?
  136.                 subspaceArea.transform(rot) :
  137.                 subspaceArea;

  138.         region.add(toAdd);
  139.     }

  140.     /** Add a plane subset to this instance.
  141.      * @param subset plane subset to add
  142.      * @throws IllegalArgumentException if the given plane subset is not from
  143.      *      a plane equivalent to this instance
  144.      */
  145.     public void add(final EmbeddedTreePlaneSubset subset) {
  146.         Planes.validatePlanesEquivalent(getPlane(), subset.getPlane());

  147.         final RegionBSPTree2D otherTree = subset.getSubspaceRegion();
  148.         final Rotation2D rot = getEmbeddedRegionRotation(subset);

  149.         final RegionBSPTree2D regionToAdd;
  150.         if (rot != null) {
  151.             // we need to transform the subspace region before adding
  152.             regionToAdd = otherTree.copy();
  153.             regionToAdd.transform(rot);
  154.         } else {
  155.             regionToAdd = otherTree;
  156.         }

  157.         region.union(regionToAdd);
  158.     }

  159.     /** Construct a rotation transform used to transform the subspace of the given embedded region plane
  160.      * subset into the subspace of this instance. Returns null if no transform is needed. This method must only
  161.      * be called with embedded regions that share an equivalent plane with this instance, meaning that the
  162.      * planes have the same origin point and normal
  163.      * @param embedded the embedded region plane subset to compare with the current instance
  164.      * @return a rotation transform to convert from the subspace of the argument into the current subspace; returns
  165.      *      null if no such transform is needed
  166.      */
  167.     private Rotation2D getEmbeddedRegionRotation(final PlaneSubset.Embedded embedded) {
  168.         // check if we need to apply a rotation to the given embedded subspace
  169.         final EmbeddingPlane thisPlane = getPlane();
  170.         final EmbeddingPlane otherPlane = embedded.getPlane();

  171.         final Precision.DoubleEquivalence precision = thisPlane.getPrecision();

  172.         final double uDot = thisPlane.getU().dot(otherPlane.getU());
  173.         if (!precision.eq(uDot, 1.0)) {
  174.             final Vector2D otherPlaneU = thisPlane.toSubspace(otherPlane.getOrigin().add(otherPlane.getU()));
  175.             final double angle = Math.atan2(otherPlaneU.getY(), otherPlaneU.getX());

  176.             return Rotation2D.of(angle);
  177.         }

  178.         return null;
  179.     }
  180. }