Parallelogram.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.twod.shape;

  18. import java.text.MessageFormat;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.List;
  22. import java.util.stream.Collectors;

  23. import org.apache.commons.geometry.core.Transform;
  24. import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
  25. import org.apache.commons.geometry.euclidean.twod.ConvexArea;
  26. import org.apache.commons.geometry.euclidean.twod.LineConvexSubset;
  27. import org.apache.commons.geometry.euclidean.twod.Lines;
  28. import org.apache.commons.geometry.euclidean.twod.Vector2D;
  29. import org.apache.commons.geometry.euclidean.twod.rotation.Rotation2D;
  30. import org.apache.commons.numbers.core.Precision;

  31. /** Class representing parallelograms, i.e. quadrilaterals with two pairs of parallel sides.
  32.  * @see <a href="https://en.wikipedia.org/wiki/Parallelogram">Parallelogram</a>
  33.  */
  34. public final class Parallelogram extends ConvexArea {

  35.     /** Vertices defining a square with sides of length 1 centered on the origin. */
  36.     private static final List<Vector2D> UNIT_SQUARE_VERTICES = Arrays.asList(
  37.                 Vector2D.of(-0.5, -0.5),
  38.                 Vector2D.of(0.5, -0.5),
  39.                 Vector2D.of(0.5, 0.5),
  40.                 Vector2D.of(-0.5, 0.5)
  41.             );

  42.     /** Simple constructor. Callers are responsible for ensuring that the given path
  43.      * represents a parallelogram. No validation is performed.
  44.      * @param boundaries the boundaries of the parallelogram; this must be a list
  45.      *      with 4 elements
  46.      */
  47.     private Parallelogram(final List<LineConvexSubset> boundaries) {
  48.         super(boundaries);
  49.     }

  50.     /** Return a new instance representing a unit square centered on the origin.
  51.      * The vertices of this square are:
  52.      * <pre>
  53.      * [
  54.      *      (-0.5 -0.5),
  55.      *      (0.5, -0.5),
  56.      *      (0.5, 0.5),
  57.      *      (-0.5, 0.5)
  58.      * ]
  59.      * </pre>
  60.      * @param precision precision context used to construct boundaries
  61.      * @return a new instance representing a unit square centered on the origin
  62.      */
  63.     public static Parallelogram unitSquare(final Precision.DoubleEquivalence precision) {
  64.         return fromTransformedUnitSquare(AffineTransformMatrix2D.identity(), precision);
  65.     }

  66.     /** Return a new instance representing an axis-aligned rectangle. The points {@code a}
  67.      * and {@code b} are taken to represent opposite corner points in the rectangle and may be specified in
  68.      * any order.
  69.      * @param a first corner point in the rectangle (opposite of {@code b})
  70.      * @param b second corner point in the rectangle (opposite of {@code a})
  71.      * @param precision precision context used to construct boundaries
  72.      * @return a new instance representing an axis-aligned rectangle
  73.      * @throws IllegalArgumentException if the length of any side of the parallelogram is zero,
  74.      *      as determined by the given precision context
  75.      */
  76.     public static Parallelogram axisAligned(final Vector2D a, final Vector2D b,
  77.             final Precision.DoubleEquivalence precision) {

  78.         final double minX = Math.min(a.getX(), b.getX());
  79.         final double maxX = Math.max(a.getX(), b.getX());

  80.         final double minY = Math.min(a.getY(), b.getY());
  81.         final double maxY = Math.max(a.getY(), b.getY());

  82.         final double xDelta = maxX - minX;
  83.         final double yDelta = maxY - minY;

  84.         final Vector2D scale = Vector2D.of(xDelta, yDelta);
  85.         final Vector2D position = Vector2D.of(
  86.                     (0.5 * xDelta) + minX,
  87.                     (0.5 * yDelta) + minY
  88.                 );

  89.         return builder(precision)
  90.                 .setScale(scale)
  91.                 .setPosition(position)
  92.                 .build();
  93.     }

  94.     /** Create a new instance by transforming a unit square centered at the origin. The vertices
  95.      * of this input square are:
  96.      * <pre>
  97.      * [
  98.      *      (-0.5 -0.5),
  99.      *      (0.5, -0.5),
  100.      *      (0.5, 0.5),
  101.      *      (-0.5, 0.5)
  102.      * ]
  103.      * </pre>
  104.      * @param transform the transform to apply to the unit square
  105.      * @param precision precision context used to construct boundaries
  106.      * @return a new instance constructed by transforming the unit square
  107.      * @throws IllegalArgumentException if the length of any side of the parallelogram is zero,
  108.      *      as determined by the given precision context
  109.      */
  110.     public static Parallelogram fromTransformedUnitSquare(final Transform<Vector2D> transform,
  111.             final Precision.DoubleEquivalence precision) {

  112.         final List<Vector2D> vertices = UNIT_SQUARE_VERTICES.stream()
  113.                 .map(transform).collect(Collectors.toList());

  114.         final int len = vertices.size();
  115.         final boolean preservesOrientation = transform.preservesOrientation();

  116.         final List<LineConvexSubset> boundaries = new ArrayList<>(UNIT_SQUARE_VERTICES.size());

  117.         Vector2D p0;
  118.         Vector2D p1;
  119.         LineConvexSubset boundary;
  120.         for (int i = 0; i < len; ++i) {
  121.             p0 = vertices.get(i);
  122.             p1 = vertices.get((i + 1) % len);

  123.             if (precision.eqZero(p0.distance(p1))) {
  124.                 throw new IllegalArgumentException(MessageFormat.format(
  125.                         "Parallelogram has zero size: vertices {0} and {1} are equivalent", p0, p1));
  126.             }

  127.             boundary = preservesOrientation ?
  128.                     Lines.segmentFromPoints(p0, p1, precision) :
  129.                     Lines.segmentFromPoints(p1, p0, precision);

  130.             boundaries.add(boundary);
  131.         }

  132.         return new Parallelogram(boundaries);
  133.     }

  134.     /** Return a new {@link Builder} instance to use for constructing parallelograms.
  135.      * @param precision precision context used to create boundaries
  136.      * @return a new {@link Builder} instance
  137.      */
  138.     public static Builder builder(final Precision.DoubleEquivalence precision) {
  139.         return new Builder(precision);
  140.     }

  141.     /** Class designed to aid construction of {@link Parallelogram} instances. Parallelograms are constructed
  142.      * by transforming the vertices of a unit square centered at the origin with a transform built from
  143.      * the values configured here. The transformations applied are <em>scaling</em>, <em>rotation</em>,
  144.      * and <em>translation</em>, in that order. When applied in this order, the scale factors determine
  145.      * the width and height of the parallelogram, the rotation determines the orientation, and the translation
  146.      * determines the position of the center point.
  147.      */
  148.     public static final class Builder {

  149.         /** Amount to scale the parallelogram. */
  150.         private Vector2D scale = Vector2D.of(1, 1);

  151.         /** The rotation of the parallelogram. */
  152.         private Rotation2D rotation = Rotation2D.identity();

  153.         /** Amount to translate the parallelogram. */
  154.         private Vector2D position = Vector2D.ZERO;

  155.         /** Precision context used to construct boundaries. */
  156.         private final Precision.DoubleEquivalence precision;

  157.         /** Construct a new instance configured with the given precision context.
  158.          * @param precision precision context used to create boundaries
  159.          */
  160.         private Builder(final Precision.DoubleEquivalence precision) {
  161.             this.precision = precision;
  162.         }

  163.         /** Set the center position of the created parallelogram.
  164.          * @param pos center position of the created parallelogram
  165.          * @return this instance
  166.          */
  167.         public Builder setPosition(final Vector2D pos) {
  168.             this.position = pos;
  169.             return this;
  170.         }

  171.         /** Set the scaling for the created parallelogram. The scale
  172.          * values determine the lengths of the respective sides in the
  173.          * created parallelogram.
  174.          * @param scaleFactors scale factors
  175.          * @return this instance
  176.          */
  177.         public Builder setScale(final Vector2D scaleFactors) {
  178.             this.scale = scaleFactors;
  179.             return this;
  180.         }

  181.         /** Set the scaling for the created parallelogram. The scale
  182.          * values determine the lengths of the respective sides in the
  183.          * created parallelogram.
  184.          * @param x x scale factor
  185.          * @param y y scale factor
  186.          * @return this instance
  187.          */
  188.         public Builder setScale(final double x, final double y) {
  189.             return setScale(Vector2D.of(x, y));
  190.         }

  191.         /** Set the scaling for the created parallelogram. The given scale
  192.          * factor is applied to both the x and y directions.
  193.          * @param scaleFactor scale factor for x and y directions
  194.          * @return this instance
  195.          */
  196.         public Builder setScale(final double scaleFactor) {
  197.             return setScale(scaleFactor, scaleFactor);
  198.         }

  199.         /** Set the rotation of the created parallelogram.
  200.          * @param rot the rotation of the created parallelogram
  201.          * @return this instance
  202.          */
  203.         public Builder setRotation(final Rotation2D rot) {
  204.             this.rotation = rot;
  205.             return this;
  206.         }

  207.         /** Set the rotation of the created parallelogram such that the
  208.          * relative x-axis of the shape points in the given direction.
  209.          * @param xDirection the direction of the relative x-axis
  210.          * @return this instance
  211.          * @throws IllegalArgumentException if the given vector cannot be normalized
  212.          * @see #setRotation(Rotation2D)
  213.          */
  214.         public Builder setXDirection(final Vector2D xDirection) {
  215.             return setRotation(
  216.                     Rotation2D.createVectorRotation(Vector2D.Unit.PLUS_X, xDirection));
  217.         }

  218.         /** Set the rotation of the created parallelogram such that the
  219.          * relative y-axis of the shape points in the given direction.
  220.          * @param yDirection the direction of the relative y-axis
  221.          * @return this instance
  222.          * @throws IllegalArgumentException if the given vector cannot be normalized
  223.          * @see #setRotation(Rotation2D)
  224.          */
  225.         public Builder setYDirection(final Vector2D yDirection) {
  226.             return setRotation(
  227.                     Rotation2D.createVectorRotation(Vector2D.Unit.PLUS_Y, yDirection));
  228.         }

  229.         /** Build a new parallelogram instance with the values configured in this builder.
  230.          * @return a new parallelogram instance
  231.          * @throws IllegalArgumentException if the length of any side of the parallelogram is zero,
  232.          *      as determined by the configured precision context
  233.          * @see Parallelogram#fromTransformedUnitSquare(Transform, Precision.DoubleEquivalence)
  234.          */
  235.         public Parallelogram build() {
  236.             final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(scale)
  237.                     .rotate(rotation)
  238.                     .translate(position);

  239.             return fromTransformedUnitSquare(transform, precision);
  240.         }
  241.     }
  242. }