View Javadoc

    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
    19  import java.text.MessageFormat;
    20  import java.util.ArrayList;
    21  import java.util.Arrays;
    22  import java.util.List;
    23  import java.util.stream.Collectors;
    24
    25  import org.apache.commons.geometry.core.Transform;
    26  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
    27  import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
    28  import org.apache.commons.geometry.euclidean.twod.ConvexArea;
    29  import org.apache.commons.geometry.euclidean.twod.LineConvexSubset;
    30  import org.apache.commons.geometry.euclidean.twod.Lines;
    31  import org.apache.commons.geometry.euclidean.twod.Vector2D;
    32  import org.apache.commons.geometry.euclidean.twod.rotation.Rotation2D;
    33
    34  /** Class representing parallelograms, i.e. quadrilaterals with two pairs of parallel sides.
    35   * @see <a href="https://en.wikipedia.org/wiki/Parallelogram">Parallelogram</a>
    36   */
    37  public final class Parallelogram extends ConvexArea {
    38
    39      /** Vertices defining a square with sides of length 1 centered on the origin. */
    40      private static final List<Vector2D> UNIT_SQUARE_VERTICES = Arrays.asList(
    41                  Vector2D.of(-0.5, -0.5),
    42                  Vector2D.of(0.5, -0.5),
    43                  Vector2D.of(0.5, 0.5),
    44                  Vector2D.of(-0.5, 0.5)
    45              );
    46
    47      /** Simple constructor. Callers are responsible for ensuring that the given path
    48       * represents a parallelogram. No validation is performed.
    49       * @param boundaries the boundaries of the parallelogram; this must be a list
    50       *      with 4 elements
    51       */
    52      private Parallelogram(final List<LineConvexSubset> boundaries) {
    53          super(boundaries);
    54      }
    55
    56      /** Return a new instance representing a unit square centered on the origin.
    57       * The vertices of this square are:
    58       * <pre>
    59       * [
    60       *      (-0.5 -0.5),
    61       *      (0.5, -0.5),
    62       *      (0.5, 0.5),
    63       *      (-0.5, 0.5)
    64       * ]
    65       * </pre>
    66       * @param precision precision context used to construct boundaries
    67       * @return a new instance representing a unit square centered on the origin
    68       */
    69      public static Parallelogram unitSquare(final DoublePrecisionContext precision) {
    70          return fromTransformedUnitSquare(AffineTransformMatrix2D.identity(), precision);
    71      }
    72
    73      /** Return a new instance representing an axis-aligned rectangle. The points {@code a}
    74       * and {@code b} are taken to represent opposite corner points in the rectangle and may be specified in
    75       * any order.
    76       * @param a first corner point in the rectangle (opposite of {@code b})
    77       * @param b second corner point in the rectangle (opposite of {@code a})
    78       * @param precision precision context used to construct boundaries
    79       * @return a new instance representing an axis-aligned rectangle
    80       * @throws IllegalArgumentException if the length of any side of the parallelogram is zero,
    81       *      as determined by the given precision context
    82       */
    83      public static Parallelogram axisAligned(final Vector2D a, final Vector2D b,
    84              final DoublePrecisionContext precision) {
    85
    86          final double minX = Math.min(a.getX(), b.getX());
    87          final double maxX = Math.max(a.getX(), b.getX());
    88
    89          final double minY = Math.min(a.getY(), b.getY());
    90          final double maxY = Math.max(a.getY(), b.getY());
    91
    92          final double xDelta = maxX - minX;
    93          final double yDelta = maxY - minY;
    94
    95          final Vector2D scale = Vector2D.of(xDelta, yDelta);
    96          final Vector2D position = Vector2D.of(
    97                      (0.5 * xDelta) + minX,
    98                      (0.5 * yDelta) + minY
    99                  );
    100
    101         return builder(precision)
    102                 .setScale(scale)
    103                 .setPosition(position)
    104                 .build();
    105     }
    106
    107     /** Create a new instance by transforming a unit square centered at the origin. The vertices
    108      * of this input square are:
    109      * <pre>
    110      * [
    111      *      (-0.5 -0.5),
    112      *      (0.5, -0.5),
    113      *      (0.5, 0.5),
    114      *      (-0.5, 0.5)
    115      * ]
    116      * </pre>
    117      * @param transform the transform to apply to the unit square
    118      * @param precision precision context used to construct boundaries
    119      * @return a new instance constructed by transforming the unit square
    120      * @throws IllegalArgumentException if the length of any side of the parallelogram is zero,
    121      *      as determined by the given precision context
    122      */
    123     public static Parallelogram fromTransformedUnitSquare(final Transform<Vector2D> transform,
    124             final DoublePrecisionContext precision) {
    125
    126         final List<Vector2D> vertices = UNIT_SQUARE_VERTICES.stream()
    127                 .map(transform).collect(Collectors.toList());
    128
    129         final int len = vertices.size();
    130         final boolean preservesOrientation = transform.preservesOrientation();
    131
    132         final List<LineConvexSubset> boundaries = new ArrayList<>(UNIT_SQUARE_VERTICES.size());
    133
    134         Vector2D p0;
    135         Vector2D p1;
    136         LineConvexSubset boundary;
    137         for (int i = 0; i < len; ++i) {
    138             p0 = vertices.get(i);
    139             p1 = vertices.get((i + 1) % len);
    140
    141             if (precision.eqZero(p0.distance(p1))) {
    142                 throw new IllegalArgumentException(MessageFormat.format(
    143                         "Parallelogram has zero size: vertices {0} and {1} are equivalent", p0, p1));
    144             }
    145
    146             boundary = preservesOrientation ?
    147                     Lines.segmentFromPoints(p0, p1, precision) :
    148                     Lines.segmentFromPoints(p1, p0, precision);
    149
    150             boundaries.add(boundary);
    151         }
    152
    153         return new Parallelogram(boundaries);
    154     }
    155
    156     /** Return a new {@link Builder} instance to use for constructing parallelograms.
    157      * @param precision precision context used to create boundaries
    158      * @return a new {@link Builder} instance
    159      */
    160     public static Builder builder(final DoublePrecisionContext precision) {
    161         return new Builder(precision);
    162     }
    163
    164     /** Class designed to aid construction of {@link Parallelogram} instances. Parallelograms are constructed
    165      * by transforming the vertices of a unit square centered at the origin with a transform built from
    166      * the values configured here. The transformations applied are <em>scaling</em>, <em>rotation</em>,
    167      * and <em>translation</em>, in that order. When applied in this order, the scale factors determine
    168      * the width and height of the parallelogram, the rotation determines the orientation, and the translation
    169      * determines the position of the center point.
    170      */
    171     public static final class Builder {
    172
    173         /** Amount to scale the parallelogram. */
    174         private Vector2D scale = Vector2D.of(1, 1);
    175
    176         /** The rotation of the parallelogram. */
    177         private Rotation2D rotation = Rotation2D.identity();
    178
    179         /** Amount to translate the parallelogram. */
    180         private Vector2D position = Vector2D.ZERO;
    181
    182         /** Precision context used to construct boundaries. */
    183         private final DoublePrecisionContext precision;
    184
    185         /** Construct a new instance configured with the given precision context.
    186          * @param precision precision context used to create boundaries
    187          */
    188         private Builder(final DoublePrecisionContext precision) {
    189             this.precision = precision;
    190         }
    191
    192         /** Set the center position of the created parallelogram.
    193          * @param pos center position of the created parallelogram
    194          * @return this instance
    195          */
    196         public Builder setPosition(final Vector2D pos) {
    197             this.position = pos;
    198             return this;
    199         }
    200
    201         /** Set the scaling for the created parallelogram. The scale
    202          * values determine the lengths of the respective sides in the
    203          * created parallelogram.
    204          * @param scaleFactors scale factors
    205          * @return this instance
    206          */
    207         public Builder setScale(final Vector2D scaleFactors) {
    208             this.scale = scaleFactors;
    209             return this;
    210         }
    211
    212         /** Set the scaling for the created parallelogram. The scale
    213          * values determine the lengths of the respective sides in the
    214          * created parallelogram.
    215          * @param x x scale factor
    216          * @param y y scale factor
    217          * @return this instance
    218          */
    219         public Builder setScale(final double x, final double y) {
    220             return setScale(Vector2D.of(x, y));
    221         }
    222
    223         /** Set the scaling for the created parallelogram. The given scale
    224          * factor is applied to both the x and y directions.
    225          * @param scaleFactor scale factor for x and y directions
    226          * @return this instance
    227          */
    228         public Builder setScale(final double scaleFactor) {
    229             return setScale(scaleFactor, scaleFactor);
    230         }
    231
    232         /** Set the rotation of the created parallelogram.
    233          * @param rot the rotation of the created parallelogram
    234          * @return this instance
    235          */
    236         public Builder setRotation(final Rotation2D rot) {
    237             this.rotation = rot;
    238             return this;
    239         }
    240
    241         /** Set the rotation of the created parallelogram such that the
    242          * relative x-axis of the shape points in the given direction.
    243          * @param xDirection the direction of the relative x-axis
    244          * @return this instance
    245          * @throws IllegalArgumentException if the given vector cannot be normalized
    246          * @see #setRotation(Rotation2D)
    247          */
    248         public Builder setXDirection(final Vector2D xDirection) {
    249             return setRotation(
    250                     Rotation2D.createVectorRotation(Vector2D.Unit.PLUS_X, xDirection));
    251         }
    252
    253         /** Set the rotation of the created parallelogram such that the
    254          * relative y-axis of the shape points in the given direction.
    255          * @param yDirection the direction of the relative y-axis
    256          * @return this instance
    257          * @throws IllegalArgumentException if the given vector cannot be normalized
    258          * @see #setRotation(Rotation2D)
    259          */
    260         public Builder setYDirection(final Vector2D yDirection) {
    261             return setRotation(
    262                     Rotation2D.createVectorRotation(Vector2D.Unit.PLUS_Y, yDirection));
    263         }
    264
    265         /** Build a new parallelogram instance with the values configured in this builder.
    266          * @return a new parallelogram instance
    267          * @throws IllegalArgumentException if the length of any side of the parallelogram is zero,
    268          *      as determined by the configured precision context
    269          * @see Parallelogram#fromTransformedUnitSquare(Transform, DoublePrecisionContext)
    270          */
    271         public Parallelogram build() {
    272             final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(scale)
    273                     .rotate(rotation)
    274                     .translate(position);
    275
    276             return fromTransformedUnitSquare(transform, precision);
    277         }
    278     }
    279 }