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;
    18
    19  import org.apache.commons.geometry.core.GeometryTestUtils;
    20  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
    21  import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
    22  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
    23  import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D;
    24  import org.apache.commons.geometry.euclidean.oned.Vector1D;
    25  import org.apache.commons.geometry.euclidean.twod.Line.SubspaceTransform;
    26  import org.apache.commons.numbers.angle.PlaneAngleRadians;
    27  import org.junit.Assert;
    28  import org.junit.Test;
    29
    30  public class LineTest {
    31
    32      private static final double TEST_EPS = 1e-10;
    33
    34      private static final DoublePrecisionContext TEST_PRECISION =
    35              new EpsilonDoublePrecisionContext(TEST_EPS);
    36
    37      @Test
    38      public void testFromPoints() {
    39          // act/assert
    40          checkLine(Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION),
    41                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
    42          checkLine(Lines.fromPoints(Vector2D.ZERO, Vector2D.of(100, 0), TEST_PRECISION),
    43                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
    44          checkLine(Lines.fromPoints(Vector2D.of(100, 0), Vector2D.ZERO, TEST_PRECISION),
    45                  Vector2D.ZERO, Vector2D.Unit.MINUS_X);
    46          checkLine(Lines.fromPoints(Vector2D.of(-100, 0), Vector2D.of(100, 0), TEST_PRECISION),
    47                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
    48
    49          checkLine(Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 2), TEST_PRECISION),
    50                  Vector2D.of(-1, 1), Vector2D.of(1, 1).normalize());
    51          checkLine(Lines.fromPoints(Vector2D.of(0, 2), Vector2D.of(-2, 0), TEST_PRECISION),
    52                  Vector2D.of(-1, 1), Vector2D.of(-1, -1).normalize());
    53      }
    54
    55      @Test
    56      public void testFromPoints_pointsTooClose() {
    57          // act/assert
    58          GeometryTestUtils.assertThrows(() -> Lines.fromPoints(Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X, TEST_PRECISION),
    59                  IllegalArgumentException.class, "Line direction cannot be zero");
    60          GeometryTestUtils.assertThrows(() -> Lines.fromPoints(Vector2D.Unit.PLUS_X, Vector2D.of(1 + 1e-11, 1e-11), TEST_PRECISION),
    61                  IllegalArgumentException.class, "Line direction cannot be zero");
    62      }
    63
    64      @Test
    65      public void testFromPointAndDirection() {
    66          // act/assert
    67          checkLine(Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION),
    68                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
    69          checkLine(Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.of(100, 0), TEST_PRECISION),
    70                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
    71          checkLine(Lines.fromPointAndDirection(Vector2D.of(-100, 0), Vector2D.of(100, 0), TEST_PRECISION),
    72                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
    73
    74          checkLine(Lines.fromPointAndDirection(Vector2D.of(-2, 0), Vector2D.of(1, 1), TEST_PRECISION),
    75                  Vector2D.of(-1, 1), Vector2D.of(1, 1).normalize());
    76          checkLine(Lines.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(-1, -1), TEST_PRECISION),
    77                  Vector2D.of(-1, 1), Vector2D.of(-1, -1).normalize());
    78      }
    79
    80      @Test
    81      public void testFromPointAndDirection_directionIsZero() {
    82          // act/assert
    83          GeometryTestUtils.assertThrows(() -> Lines.fromPointAndDirection(Vector2D.Unit.PLUS_X, Vector2D.ZERO, TEST_PRECISION),
    84                  IllegalArgumentException.class, "Line direction cannot be zero");
    85          GeometryTestUtils.assertThrows(() -> Lines.fromPointAndDirection(Vector2D.Unit.PLUS_X, Vector2D.of(1e-11, -1e-12), TEST_PRECISION),
    86                  IllegalArgumentException.class, "Line direction cannot be zero");
    87      }
    88
    89      @Test
    90      public void testFromPointAndAngle() {
    91          // act/assert
    92          checkLine(Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION),
    93                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
    94          checkLine(Lines.fromPointAndAngle(Vector2D.of(1, 1), PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION),
    95                  Vector2D.of(1, 0), Vector2D.Unit.PLUS_Y);
    96          checkLine(Lines.fromPointAndAngle(Vector2D.of(-1, -1), PlaneAngleRadians.PI, TEST_PRECISION),
    97                  Vector2D.of(0, -1), Vector2D.Unit.MINUS_X);
    98          checkLine(Lines.fromPointAndAngle(Vector2D.of(1, -1), -PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION),
    99                  Vector2D.of(1, 0), Vector2D.Unit.MINUS_Y);
    100         checkLine(Lines.fromPointAndAngle(Vector2D.of(-1, 1), PlaneAngleRadians.TWO_PI, TEST_PRECISION),
    101                 Vector2D.of(0, 1), Vector2D.Unit.PLUS_X);
    102     }
    103
    104     @Test
    105     public void testGetAngle() {
    106         // arrange
    107         final Vector2D vec = Vector2D.of(1, 2);
    108
    109         for (double theta = -4 * PlaneAngleRadians.PI; theta < 2 * PlaneAngleRadians.PI; theta += 0.1) {
    110             final Line line = Lines.fromPointAndAngle(vec, theta, TEST_PRECISION);
    111
    112             // act/assert
    113             Assert.assertEquals(PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(theta),
    114                     line.getAngle(), TEST_EPS);
    115         }
    116     }
    117
    118     @Test
    119     public void testGetAngle_multiplesOfPi() {
    120         // arrange
    121         final Vector2D vec = Vector2D.of(-1, -2);
    122
    123         // act/assert
    124         Assert.assertEquals(0, Lines.fromPointAndAngle(vec, 0.0, TEST_PRECISION).getAngle(), TEST_EPS);
    125         Assert.assertEquals(PlaneAngleRadians.PI, Lines.fromPointAndAngle(vec, PlaneAngleRadians.PI, TEST_PRECISION).getAngle(), TEST_EPS);
    126         Assert.assertEquals(0, Lines.fromPointAndAngle(vec, PlaneAngleRadians.TWO_PI, TEST_PRECISION).getAngle(), TEST_EPS);
    127
    128         Assert.assertEquals(0, Lines.fromPointAndAngle(vec, -2 * PlaneAngleRadians.PI, TEST_PRECISION).getAngle(), TEST_EPS);
    129         Assert.assertEquals(PlaneAngleRadians.PI, Lines.fromPointAndAngle(vec, -3 * PlaneAngleRadians.PI, TEST_PRECISION).getAngle(), TEST_EPS);
    130         Assert.assertEquals(0, Lines.fromPointAndAngle(vec, -4 * PlaneAngleRadians.TWO_PI, TEST_PRECISION).getAngle(), TEST_EPS);
    131     }
    132
    133     @Test
    134     public void testGetDirection() {
    135         // act/assert
    136         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X,
    137                 Lines.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 0), TEST_PRECISION).getDirection(), TEST_EPS);
    138         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
    139                 Lines.fromPoints(Vector2D.of(0, 1), Vector2D.of(0, -1), TEST_PRECISION).getDirection(), TEST_EPS);
    140
    141         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X,
    142                 Lines.fromPoints(Vector2D.of(2, 2), Vector2D.of(1, 2), TEST_PRECISION).getDirection(), TEST_EPS);
    143         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X,
    144                 Lines.fromPoints(Vector2D.of(10, -2), Vector2D.of(10.1, -2), TEST_PRECISION).getDirection(), TEST_EPS);
    145
    146         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
    147                 Lines.fromPoints(Vector2D.of(3, 2), Vector2D.of(3, 1), TEST_PRECISION).getDirection(), TEST_EPS);
    148         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_Y,
    149                 Lines.fromPoints(Vector2D.of(-3, 10), Vector2D.of(-3, 10.1), TEST_PRECISION).getDirection(), TEST_EPS);
    150
    151         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, -1).normalize(),
    152                 Lines.fromPoints(Vector2D.of(0, 2), Vector2D.of(2, 0), TEST_PRECISION).getDirection(), TEST_EPS);
    153         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 1).normalize(),
    154                 Lines.fromPoints(Vector2D.of(2, 0), Vector2D.of(0, 2), TEST_PRECISION).getDirection(), TEST_EPS);
    155     }
    156
    157     @Test
    158     public void testGetOffsetDirection() {
    159         // act/assert
    160         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
    161                 Lines.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 0), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
    162         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X,
    163                 Lines.fromPoints(Vector2D.of(0, 1), Vector2D.of(0, -1), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
    164
    165         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_Y,
    166                 Lines.fromPoints(Vector2D.of(2, 2), Vector2D.of(1, 2), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
    167         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
    168                 Lines.fromPoints(Vector2D.of(10, -2), Vector2D.of(10.1, -2), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
    169
    170         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X,
    171                 Lines.fromPoints(Vector2D.of(3, 2), Vector2D.of(3, 1), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
    172         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X,
    173                 Lines.fromPoints(Vector2D.of(-3, 10), Vector2D.of(-3, 10.1), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
    174
    175         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, -1).normalize(),
    176                 Lines.fromPoints(Vector2D.of(0, 2), Vector2D.of(2, 0), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
    177         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1).normalize(),
    178                 Lines.fromPoints(Vector2D.of(2, 0), Vector2D.of(0, 2), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
    179     }
    180
    181     @Test
    182     public void testGetOrigin() {
    183         // act/assert
    184         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO,
    185                 Lines.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 0), TEST_PRECISION).getOrigin(), TEST_EPS);
    186         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO,
    187                 Lines.fromPoints(Vector2D.of(0, 1), Vector2D.of(0, -1), TEST_PRECISION).getOrigin(), TEST_EPS);
    188
    189         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2),
    190                 Lines.fromPoints(Vector2D.of(2, 2), Vector2D.of(3, 2), TEST_PRECISION).getOrigin(), TEST_EPS);
    191         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -2),
    192                 Lines.fromPoints(Vector2D.of(10, -2), Vector2D.of(10.1, -2), TEST_PRECISION).getOrigin(), TEST_EPS);
    193
    194         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 0),
    195                 Lines.fromPoints(Vector2D.of(3, 2), Vector2D.of(3, 1), TEST_PRECISION).getOrigin(), TEST_EPS);
    196         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 0),
    197                 Lines.fromPoints(Vector2D.of(-3, 10), Vector2D.of(-3, 10.1), TEST_PRECISION).getOrigin(), TEST_EPS);
    198
    199         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1),
    200                 Lines.fromPoints(Vector2D.of(0, 2), Vector2D.of(2, 0), TEST_PRECISION).getOrigin(), TEST_EPS);
    201         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1),
    202                 Lines.fromPoints(Vector2D.of(2, 0), Vector2D.of(0, 2), TEST_PRECISION).getOrigin(), TEST_EPS);
    203     }
    204
    205     @Test
    206     public void testGetOriginOffset() {
    207         // arrange
    208         final double sqrt2 = Math.sqrt(2);
    209
    210         // act/assert
    211         Assert.assertEquals(0.0,
    212                 Lines.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
    213         Assert.assertEquals(0.0,
    214                 Lines.fromPoints(Vector2D.of(0, 0), Vector2D.of(-1, -1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
    215
    216         Assert.assertEquals(sqrt2,
    217                 Lines.fromPoints(Vector2D.of(-1, 1), Vector2D.of(0, 2), TEST_PRECISION).getOriginOffset(), TEST_EPS);
    218         Assert.assertEquals(-sqrt2,
    219                 Lines.fromPoints(Vector2D.of(0, -2), Vector2D.of(1, -1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
    220
    221         Assert.assertEquals(-sqrt2,
    222                 Lines.fromPoints(Vector2D.of(0, 2), Vector2D.of(-1, 1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
    223         Assert.assertEquals(sqrt2,
    224                 Lines.fromPoints(Vector2D.of(1, -1), Vector2D.of(0, -2), TEST_PRECISION).getOriginOffset(), TEST_EPS);
    225     }
    226
    227     @Test
    228     public void testGetPrecision() {
    229         // act/assert
    230         Assert.assertSame(TEST_PRECISION, Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION).getPrecision());
    231         Assert.assertSame(TEST_PRECISION, Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION).getPrecision());
    232         Assert.assertSame(TEST_PRECISION, Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION).getPrecision());
    233     }
    234
    235     @Test
    236     public void testReverse() {
    237         // arrange
    238         final Vector2D pt = Vector2D.of(0, 1);
    239         final Vector2D dir = Vector2D.Unit.PLUS_X;
    240         final Line line = Lines.fromPointAndDirection(pt, dir, TEST_PRECISION);
    241
    242         // act
    243         final Line reversed = line.reverse();
    244         final Line doubleReversed = reversed.reverse();
    245
    246         // assert
    247         checkLine(reversed, pt, dir.negate());
    248         Assert.assertEquals(-1, reversed.getOriginOffset(), TEST_EPS);
    249
    250         checkLine(doubleReversed, pt, dir);
    251         Assert.assertEquals(1, doubleReversed.getOriginOffset(), TEST_EPS);
    252     }
    253
    254     @Test
    255     public void testAbscissa() {
    256         // arrange
    257         final Line line = Lines.fromPoints(Vector2D.of(-2, -2), Vector2D.of(2, 1), TEST_PRECISION);
    258
    259         // act/assert
    260         Assert.assertEquals(0.0, line.abscissa(Vector2D.of(-3, 4)), TEST_EPS);
    261         Assert.assertEquals(0.0, line.abscissa(Vector2D.of(3, -4)), TEST_EPS);
    262         Assert.assertEquals(5.0, line.abscissa(Vector2D.of(7, -1)), TEST_EPS);
    263         Assert.assertEquals(-5.0, line.abscissa(Vector2D.of(-1, -7)), TEST_EPS);
    264     }
    265
    266     @Test
    267     public void testToSubspace() {
    268         // arrange
    269         final Line line = Lines.fromPoints(Vector2D.of(2, 1), Vector2D.of(-2, -2), TEST_PRECISION);
    270
    271         // act/assert
    272         Assert.assertEquals(0.0, line.toSubspace(Vector2D.of(-3, 4)).getX(), TEST_EPS);
    273         Assert.assertEquals(0.0, line.toSubspace(Vector2D.of(3, -4)).getX(), TEST_EPS);
    274         Assert.assertEquals(-5.0, line.toSubspace(Vector2D.of(7, -1)).getX(), TEST_EPS);
    275         Assert.assertEquals(5.0, line.toSubspace(Vector2D.of(-1, -7)).getX(), TEST_EPS);
    276     }
    277
    278     @Test
    279     public void testToSpace_throughOrigin() {
    280         // arrange
    281         final double invSqrt2 = 1 / Math.sqrt(2);
    282         final Vector2D dir = Vector2D.of(invSqrt2, invSqrt2);
    283
    284         final Line line = Lines.fromPoints(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION);
    285
    286         // act/assert
    287         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, line.toSpace(Vector1D.of(0)), TEST_EPS);
    288
    289         for (int i = 0; i < 100; ++i) {
    290             EuclideanTestUtils.assertCoordinatesEqual(dir.multiply(i), line.toSpace(Vector1D.of(i)), TEST_EPS);
    291             EuclideanTestUtils.assertCoordinatesEqual(dir.multiply(-i), line.toSpace(Vector1D.of(-i)), TEST_EPS);
    292         }
    293     }
    294
    295     @Test
    296     public void testToSpace_offsetFromOrigin() {
    297         // arrange
    298         final double angle = PlaneAngleRadians.PI / 6;
    299         final double cos = Math.cos(angle);
    300         final double sin = Math.sin(angle);
    301         final Vector2D pt = Vector2D.of(-5, 0);
    302
    303         final double h = Math.abs(pt.getX()) * cos;
    304         final double d = h * cos;
    305         final Vector2D origin = Vector2D.of(
    306                     pt.getX() + d,
    307                     h * sin
    308                 );
    309         final Vector2D dir = Vector2D.of(cos, sin);
    310
    311         final Line line = Lines.fromPointAndAngle(pt, angle, TEST_PRECISION);
    312
    313         // act/assert
    314         EuclideanTestUtils.assertCoordinatesEqual(origin, line.toSpace(Vector1D.of(0)), TEST_EPS);
    315
    316         for (int i = 0; i < 100; ++i) {
    317             EuclideanTestUtils.assertCoordinatesEqual(origin.add(dir.multiply(i)), line.toSpace(Vector1D.of(i)), TEST_EPS);
    318             EuclideanTestUtils.assertCoordinatesEqual(origin.add(dir.multiply(-i)), line.toSpace(Vector1D.of(-i)), TEST_EPS);
    319         }
    320     }
    321
    322     @Test
    323     public void testIntersection() {
    324         // arrange
    325         final Line a = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    326         final Line b = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
    327         final Line c = Lines.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
    328         final Line d = Lines.fromPointAndDirection(Vector2D.of(0, -1), Vector2D.of(2, -1), TEST_PRECISION);
    329
    330         // act/assert
    331         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, a.intersection(b), TEST_EPS);
    332         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, b.intersection(a), TEST_EPS);
    333
    334         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, 0), a.intersection(c), TEST_EPS);
    335         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, 0), c.intersection(a), TEST_EPS);
    336
    337         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 0), a.intersection(d), TEST_EPS);
    338         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 0), d.intersection(a), TEST_EPS);
    339
    340         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), b.intersection(c), TEST_EPS);
    341         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), c.intersection(b), TEST_EPS);
    342
    343         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -1), b.intersection(d), TEST_EPS);
    344         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -1), d.intersection(b), TEST_EPS);
    345
    346         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 0.5), c.intersection(d), TEST_EPS);
    347         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 0.5), d.intersection(c), TEST_EPS);
    348     }
    349
    350     @Test
    351     public void testIntersection_parallel() {
    352         // arrange
    353         final Line a = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    354         final Line b = Lines.fromPointAndDirection(Vector2D.of(0, 1), Vector2D.Unit.PLUS_X, TEST_PRECISION);
    355
    356         final Line c = Lines.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
    357         final Line d = Lines.fromPointAndDirection(Vector2D.of(0, -1), Vector2D.of(2, 1), TEST_PRECISION);
    358
    359         // act/assert
    360         Assert.assertNull(a.intersection(b));
    361         Assert.assertNull(b.intersection(a));
    362
    363         Assert.assertNull(c.intersection(d));
    364         Assert.assertNull(d.intersection(c));
    365     }
    366
    367     @Test
    368     public void testIntersection_coincident() {
    369         // arrange
    370         final Line a = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    371         final Line b = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    372
    373         final Line c = Lines.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
    374         final Line d = Lines.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
    375
    376         // act/assert
    377         Assert.assertNull(a.intersection(b));
    378         Assert.assertNull(b.intersection(a));
    379
    380         Assert.assertNull(c.intersection(d));
    381         Assert.assertNull(d.intersection(c));
    382     }
    383
    384     @Test
    385     public void testAngle() {
    386         // arrange
    387         final Line a = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
    388         final Line b = Lines.fromPointAndAngle(Vector2D.of(1, 4), PlaneAngleRadians.PI, TEST_PRECISION);
    389         final Line c = Lines.fromPointAndDirection(Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION);
    390
    391         // act/assert
    392         Assert.assertEquals(0.0, a.angle(a), TEST_EPS);
    393         Assert.assertEquals(-PlaneAngleRadians.PI, a.angle(b), TEST_EPS);
    394         Assert.assertEquals(0.25 * PlaneAngleRadians.PI, a.angle(c), TEST_EPS);
    395
    396         Assert.assertEquals(0.0, b.angle(b), TEST_EPS);
    397         Assert.assertEquals(-PlaneAngleRadians.PI, b.angle(a), TEST_EPS);
    398         Assert.assertEquals(-0.75 * PlaneAngleRadians.PI, b.angle(c), TEST_EPS);
    399
    400         Assert.assertEquals(0.0, c.angle(c), TEST_EPS);
    401         Assert.assertEquals(-0.25 * PlaneAngleRadians.PI, c.angle(a), TEST_EPS);
    402         Assert.assertEquals(0.75 * PlaneAngleRadians.PI, c.angle(b), TEST_EPS);
    403     }
    404
    405     @Test
    406     public void testProject() {
    407         // --- arrange
    408         final Line xAxis = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    409         final Line yAxis = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
    410
    411         final double diagonalYIntercept = 1;
    412         final Vector2D diagonalDir = Vector2D.of(1, 2);
    413         final Line diagonal = Lines.fromPointAndDirection(Vector2D.of(0, diagonalYIntercept), diagonalDir, TEST_PRECISION);
    414
    415         EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
    416             final Vector2D pt = Vector2D.of(x, y);
    417
    418             // --- act/assert
    419             EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(x, 0), xAxis.project(pt), TEST_EPS);
    420             EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, y), yAxis.project(pt), TEST_EPS);
    421
    422             final Vector2D diagonalPt = diagonal.project(pt);
    423             Assert.assertTrue(diagonal.contains(diagonalPt));
    424             Assert.assertEquals(diagonal.distance(pt), pt.distance(diagonalPt), TEST_EPS);
    425
    426             // check that y = mx + b is true
    427             Assert.assertEquals(diagonalPt.getY(),
    428                     (diagonalDir.getY() * diagonalPt.getX() / diagonalDir.getX()) + diagonalYIntercept, TEST_EPS);
    429         });
    430     }
    431
    432     @Test
    433     public void testSpan() {
    434         // arrange
    435         final Line line = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    436
    437         // act
    438         final LineConvexSubset result = line.span();
    439
    440         // assert
    441         Assert.assertSame(line, result.getHyperplane());
    442         Assert.assertSame(line, result.getLine());
    443     }
    444
    445     @Test
    446     public void testSegment_doubles() {
    447         // arrange
    448         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
    449
    450         // act
    451         final Segment segment = line.segment(1, 2);
    452
    453         // assert
    454         Assert.assertSame(line, segment.getLine());
    455         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segment.getStartPoint(), TEST_EPS);
    456         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 1), segment.getEndPoint(), TEST_EPS);
    457     }
    458
    459     @Test
    460     public void testSegment_pointsOnLine() {
    461         // arrange
    462         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
    463
    464         // act
    465         final Segment segment = line.segment(Vector2D.of(3, 1), Vector2D.of(2, 1));
    466
    467         // assert
    468         Assert.assertSame(line, segment.getLine());
    469         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 1), segment.getStartPoint(), TEST_EPS);
    470         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 1), segment.getEndPoint(), TEST_EPS);
    471     }
    472
    473     @Test
    474     public void testSegment_pointsProjectedOnLine() {
    475         // arrange
    476         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
    477
    478         // act
    479         final Segment segment = line.segment(Vector2D.of(-3, 2), Vector2D.of(2, -1));
    480
    481         // assert
    482         Assert.assertSame(line, segment.getLine());
    483         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), segment.getStartPoint(), TEST_EPS);
    484         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 1), segment.getEndPoint(), TEST_EPS);
    485     }
    486
    487     @Test
    488     public void testLineTo_pointOnLine() {
    489         // arrange
    490         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), PlaneAngleRadians.PI, TEST_PRECISION);
    491
    492         // act
    493         final ReverseRay halfLine = line.reverseRayTo(Vector2D.of(-3, 1));
    494
    495         // assert
    496         Assert.assertSame(line, halfLine.getLine());
    497         Assert.assertTrue(halfLine.isInfinite());
    498         Assert.assertNull(halfLine.getStartPoint());
    499         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), halfLine.getEndPoint(), TEST_EPS);
    500
    501         Assert.assertTrue(halfLine.contains(Vector2D.of(1, 1)));
    502         Assert.assertFalse(halfLine.contains(Vector2D.of(-4, 1)));
    503     }
    504
    505     @Test
    506     public void testLineTo_pointProjectedOnLine() {
    507         // arrange
    508         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), PlaneAngleRadians.PI, TEST_PRECISION);
    509
    510         // act
    511         final ReverseRay halfLine = line.reverseRayTo(Vector2D.of(-3, 5));
    512
    513         // assert
    514         Assert.assertSame(line, halfLine.getLine());
    515         Assert.assertTrue(halfLine.isInfinite());
    516         Assert.assertNull(halfLine.getStartPoint());
    517         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), halfLine.getEndPoint(), TEST_EPS);
    518
    519         Assert.assertTrue(halfLine.contains(Vector2D.of(1, 1)));
    520         Assert.assertFalse(halfLine.contains(Vector2D.of(-4, 1)));
    521     }
    522
    523     @Test
    524     public void testLineTo_double() {
    525         // arrange
    526         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), PlaneAngleRadians.PI, TEST_PRECISION);
    527
    528         // act
    529         final ReverseRay halfLine = line.reverseRayTo(-1);
    530
    531         // assert
    532         Assert.assertSame(line, halfLine.getLine());
    533         Assert.assertTrue(halfLine.isInfinite());
    534         Assert.assertNull(halfLine.getStartPoint());
    535         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), halfLine.getEndPoint(), TEST_EPS);
    536
    537         Assert.assertTrue(halfLine.contains(Vector2D.of(2, 1)));
    538         Assert.assertFalse(halfLine.contains(Vector2D.of(-4, 1)));
    539     }
    540
    541     @Test
    542     public void testRayFrom_pointOnLine() {
    543         // arrange
    544         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), PlaneAngleRadians.PI, TEST_PRECISION);
    545
    546         // act
    547         final Ray ray = line.rayFrom(Vector2D.of(-3, 1));
    548
    549         // assert
    550         Assert.assertSame(line, ray.getLine());
    551         Assert.assertTrue(ray.isInfinite());
    552         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), ray.getStartPoint(), TEST_EPS);
    553         Assert.assertNull(ray.getEndPoint());
    554
    555         Assert.assertFalse(ray.contains(Vector2D.of(1, 1)));
    556         Assert.assertTrue(ray.contains(Vector2D.of(-4, 1)));
    557     }
    558
    559     @Test
    560     public void testRayFrom_pointProjectedOnLine() {
    561         // arrange
    562         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), PlaneAngleRadians.PI, TEST_PRECISION);
    563
    564         // act
    565         final Ray ray = line.rayFrom(Vector2D.of(-3, 5));
    566
    567         // assert
    568         Assert.assertSame(line, ray.getLine());
    569         Assert.assertTrue(ray.isInfinite());
    570         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), ray.getStartPoint(), TEST_EPS);
    571         Assert.assertNull(ray.getEndPoint());
    572
    573         Assert.assertFalse(ray.contains(Vector2D.of(1, 1)));
    574         Assert.assertTrue(ray.contains(Vector2D.of(-4, 1)));
    575     }
    576
    577     @Test
    578     public void testRayFrom_double() {
    579         // arrange
    580         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), PlaneAngleRadians.PI, TEST_PRECISION);
    581
    582         // act
    583         final Ray ray = line.rayFrom(-1);
    584
    585         // assert
    586         Assert.assertSame(line, ray.getLine());
    587         Assert.assertTrue(ray.isInfinite());
    588         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), ray.getStartPoint(), TEST_EPS);
    589         Assert.assertNull(ray.getEndPoint());
    590
    591         Assert.assertFalse(ray.contains(Vector2D.of(2, 1)));
    592         Assert.assertTrue(ray.contains(Vector2D.of(-4, 1)));
    593     }
    594
    595     @Test
    596     public void testOffset_parallelLines() {
    597         // arrange
    598         final double dist = Math.sin(Math.atan2(2, 1));
    599
    600         final Line a = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
    601         final Line b = Lines.fromPoints(Vector2D.of(-3, 0), Vector2D.of(0, 6), TEST_PRECISION);
    602         final Line c = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
    603         final Line d = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, -2), TEST_PRECISION);
    604
    605         // act/assert
    606         Assert.assertEquals(-dist, a.offset(b), TEST_EPS);
    607         Assert.assertEquals(dist, b.offset(a), TEST_EPS);
    608
    609         Assert.assertEquals(dist, a.offset(c), TEST_EPS);
    610         Assert.assertEquals(-dist, c.offset(a), TEST_EPS);
    611
    612         Assert.assertEquals(3 * dist, a.offset(d), TEST_EPS);
    613         Assert.assertEquals(3 * dist, d.offset(a), TEST_EPS);
    614     }
    615
    616     @Test
    617     public void testOffset_coincidentLines() {
    618         // arrange
    619         final Line a = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
    620         final Line b = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
    621         final Line c = b.reverse();
    622
    623         // act/assert
    624         Assert.assertEquals(0, a.offset(a), TEST_EPS);
    625
    626         Assert.assertEquals(0, a.offset(b), TEST_EPS);
    627         Assert.assertEquals(0, b.offset(a), TEST_EPS);
    628
    629         Assert.assertEquals(0, a.offset(c), TEST_EPS);
    630         Assert.assertEquals(0, c.offset(a), TEST_EPS);
    631     }
    632
    633     @Test
    634     public void testOffset_nonParallelLines() {
    635         // arrange
    636         final Line a = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    637         final Line b = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
    638         final Line c = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
    639         final Line d = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, 4), TEST_PRECISION);
    640
    641         // act/assert
    642         Assert.assertEquals(0, a.offset(b), TEST_EPS);
    643         Assert.assertEquals(0, b.offset(a), TEST_EPS);
    644
    645         Assert.assertEquals(0, a.offset(c), TEST_EPS);
    646         Assert.assertEquals(0, c.offset(a), TEST_EPS);
    647
    648         Assert.assertEquals(0, a.offset(d), TEST_EPS);
    649         Assert.assertEquals(0, d.offset(a), TEST_EPS);
    650     }
    651
    652     @Test
    653     public void testOffset_point() {
    654         // arrange
    655         final Line line = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
    656         final Line reversed = line.reverse();
    657
    658         // act/assert
    659         Assert.assertEquals(0.0, line.offset(Vector2D.of(-0.5, 1)), TEST_EPS);
    660         Assert.assertEquals(0.0, line.offset(Vector2D.of(-1.5, -1)), TEST_EPS);
    661         Assert.assertEquals(0.0, line.offset(Vector2D.of(0.5, 3)), TEST_EPS);
    662
    663         final double d = Math.sin(Math.atan2(2, 1));
    664
    665         Assert.assertEquals(d, line.offset(Vector2D.ZERO), TEST_EPS);
    666         Assert.assertEquals(-d, line.offset(Vector2D.of(-1, 2)), TEST_EPS);
    667
    668         Assert.assertEquals(-d, reversed.offset(Vector2D.ZERO), TEST_EPS);
    669         Assert.assertEquals(d, reversed.offset(Vector2D.of(-1, 2)), TEST_EPS);
    670     }
    671
    672     @Test
    673     public void testOffset_point_permute() {
    674         // arrange
    675         final Line line = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
    676         final Vector2D lineOrigin = line.getOrigin();
    677
    678         EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
    679             final Vector2D pt = Vector2D.of(x, y);
    680
    681             // act
    682             final double offset = line.offset(pt);
    683
    684             // arrange
    685             final Vector2D vec = lineOrigin.vectorTo(pt).reject(line.getDirection());
    686             final double dot = vec.dot(line.getOffsetDirection());
    687             final double expected = Math.signum(dot) * vec.norm();
    688
    689             Assert.assertEquals(expected, offset, TEST_EPS);
    690         });
    691     }
    692
    693     @Test
    694     public void testSimilarOrientation() {
    695         // arrange
    696         final Line a = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
    697         final Line b = Lines.fromPointAndAngle(Vector2D.of(4, 5), 0.0, TEST_PRECISION);
    698         final Line c = Lines.fromPointAndAngle(Vector2D.of(-1, -3), 0.4 * PlaneAngleRadians.PI, TEST_PRECISION);
    699         final Line d = Lines.fromPointAndAngle(Vector2D.of(1, 0), -0.4 * PlaneAngleRadians.PI, TEST_PRECISION);
    700
    701         final Line e = Lines.fromPointAndAngle(Vector2D.of(6, -3), PlaneAngleRadians.PI, TEST_PRECISION);
    702         final Line f = Lines.fromPointAndAngle(Vector2D.of(8, 5), 0.8 * PlaneAngleRadians.PI, TEST_PRECISION);
    703         final Line g = Lines.fromPointAndAngle(Vector2D.of(6, -3), -0.8 * PlaneAngleRadians.PI, TEST_PRECISION);
    704
    705         // act/assert
    706         Assert.assertTrue(a.similarOrientation(a));
    707         Assert.assertTrue(a.similarOrientation(b));
    708         Assert.assertTrue(b.similarOrientation(a));
    709         Assert.assertTrue(a.similarOrientation(c));
    710         Assert.assertTrue(c.similarOrientation(a));
    711         Assert.assertTrue(a.similarOrientation(d));
    712         Assert.assertTrue(d.similarOrientation(a));
    713
    714         Assert.assertFalse(c.similarOrientation(d));
    715         Assert.assertFalse(d.similarOrientation(c));
    716
    717         Assert.assertTrue(e.similarOrientation(f));
    718         Assert.assertTrue(f.similarOrientation(e));
    719         Assert.assertTrue(e.similarOrientation(g));
    720         Assert.assertTrue(g.similarOrientation(e));
    721
    722         Assert.assertFalse(a.similarOrientation(e));
    723         Assert.assertFalse(e.similarOrientation(a));
    724     }
    725
    726     @Test
    727     public void testSimilarOrientation_orthogonal() {
    728         // arrange
    729         final Line a = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    730         final Line b = Lines.fromPointAndDirection(Vector2D.of(4, 5), Vector2D.Unit.PLUS_Y, TEST_PRECISION);
    731         final Line c = Lines.fromPointAndDirection(Vector2D.of(-4, -5), Vector2D.Unit.MINUS_Y, TEST_PRECISION);
    732
    733         // act/assert
    734         Assert.assertTrue(a.similarOrientation(b));
    735         Assert.assertTrue(b.similarOrientation(a));
    736         Assert.assertTrue(a.similarOrientation(c));
    737         Assert.assertTrue(c.similarOrientation(a));
    738     }
    739
    740     @Test
    741     public void testDistance_parallelLines() {
    742         // arrange
    743         final double dist = Math.sin(Math.atan2(2, 1));
    744
    745         final Line a = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
    746         final Line b = Lines.fromPoints(Vector2D.of(-3, 0), Vector2D.of(0, 6), TEST_PRECISION);
    747         final Line c = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
    748         final Line d = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, -2), TEST_PRECISION);
    749
    750         // act/assert
    751         Assert.assertEquals(dist, a.distance(b), TEST_EPS);
    752         Assert.assertEquals(dist, b.distance(a), TEST_EPS);
    753
    754         Assert.assertEquals(dist, a.distance(c), TEST_EPS);
    755         Assert.assertEquals(dist, c.distance(a), TEST_EPS);
    756
    757         Assert.assertEquals(3 * dist, a.distance(d), TEST_EPS);
    758         Assert.assertEquals(3 * dist, d.distance(a), TEST_EPS);
    759     }
    760
    761     @Test
    762     public void testDistance_coincidentLines() {
    763         // arrange
    764         final Line a = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
    765         final Line b = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
    766         final Line c = b.reverse();
    767
    768         // act/assert
    769         Assert.assertEquals(0, a.distance(a), TEST_EPS);
    770
    771         Assert.assertEquals(0, a.distance(b), TEST_EPS);
    772         Assert.assertEquals(0, b.distance(a), TEST_EPS);
    773
    774         Assert.assertEquals(0, a.distance(c), TEST_EPS);
    775         Assert.assertEquals(0, c.distance(a), TEST_EPS);
    776     }
    777
    778     @Test
    779     public void testDistance_nonParallelLines() {
    780         // arrange
    781         final Line a = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    782         final Line b = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
    783         final Line c = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
    784         final Line d = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, 4), TEST_PRECISION);
    785
    786         // act/assert
    787         Assert.assertEquals(0, a.distance(b), TEST_EPS);
    788         Assert.assertEquals(0, b.distance(a), TEST_EPS);
    789
    790         Assert.assertEquals(0, a.distance(c), TEST_EPS);
    791         Assert.assertEquals(0, c.distance(a), TEST_EPS);
    792
    793         Assert.assertEquals(0, a.distance(d), TEST_EPS);
    794         Assert.assertEquals(0, d.distance(a), TEST_EPS);
    795     }
    796
    797     @Test
    798     public void testDistance() {
    799         // arrange
    800         final Line line = Lines.fromPoints(Vector2D.of(2, 1), Vector2D.of(-2, -2), TEST_PRECISION);
    801
    802         // act/assert
    803         Assert.assertEquals(0, line.distance(line.getOrigin()), TEST_EPS);
    804         Assert.assertEquals(+5.0, line.distance(Vector2D.of(5, -3)), TEST_EPS);
    805         Assert.assertEquals(+5.0, line.distance(Vector2D.of(-5, 2)), TEST_EPS);
    806     }
    807
    808     @Test
    809     public void testPointAt() {
    810         // arrange
    811         final Vector2D origin = Vector2D.of(-1, 1);
    812         final double d = Math.sqrt(2);
    813         final Line line = Lines.fromPointAndDirection(origin, Vector2D.of(1, 1), TEST_PRECISION);
    814
    815         // act/assert
    816         EuclideanTestUtils.assertCoordinatesEqual(origin, line.pointAt(0, 0), TEST_EPS);
    817         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, line.pointAt(0, d), TEST_EPS);
    818         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 2), line.pointAt(0, -d), TEST_EPS);
    819
    820         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 0), line.pointAt(-d, 0), TEST_EPS);
    821         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), line.pointAt(d, 0), TEST_EPS);
    822
    823         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), line.pointAt(d, d), TEST_EPS);
    824         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), line.pointAt(-d, -d), TEST_EPS);
    825     }
    826
    827     @Test
    828     public void testPointAt_abscissaOffsetRoundtrip() {
    829         // arrange
    830         final Line line = Lines.fromPoints(Vector2D.of(2, 1), Vector2D.of(-2, -2), TEST_PRECISION);
    831
    832         for (double abscissa = -2.0; abscissa < 2.0; abscissa += 0.2) {
    833             for (double offset = -2.0; offset < 2.0; offset += 0.2) {
    834
    835                 // act
    836                 final Vector2D point = line.pointAt(abscissa, offset);
    837
    838                 // assert
    839                 Assert.assertEquals(abscissa, line.toSubspace(point).getX(), TEST_EPS);
    840                 Assert.assertEquals(offset, line.offset(point), TEST_EPS);
    841             }
    842         }
    843     }
    844
    845     @Test
    846     public void testContains_line() {
    847         // arrange
    848         final Vector2D pt = Vector2D.of(1, 2);
    849         final Vector2D dir = Vector2D.of(3, 7);
    850         final Line a = Lines.fromPointAndDirection(pt, dir, TEST_PRECISION);
    851         final Line b = Lines.fromPointAndDirection(Vector2D.of(0, -4), dir, TEST_PRECISION);
    852         final Line c = Lines.fromPointAndDirection(Vector2D.of(-2, -2), dir.negate(), TEST_PRECISION);
    853         final Line d = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    854
    855         final Line e = Lines.fromPointAndDirection(pt, dir, TEST_PRECISION);
    856         final Line f = Lines.fromPointAndDirection(pt, dir.negate(), TEST_PRECISION);
    857
    858         // act/assert
    859         Assert.assertTrue(a.contains(a));
    860
    861         Assert.assertTrue(a.contains(e));
    862         Assert.assertTrue(e.contains(a));
    863
    864         Assert.assertTrue(a.contains(f));
    865         Assert.assertTrue(f.contains(a));
    866
    867         Assert.assertFalse(a.contains(b));
    868         Assert.assertFalse(a.contains(c));
    869         Assert.assertFalse(a.contains(d));
    870     }
    871
    872     @Test
    873     public void testIsParallel_closeToEpsilon() {
    874         // arrange
    875         final double eps = 1e-3;
    876         final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(eps);
    877
    878         final Vector2D p = Vector2D.of(1, 2);
    879
    880         final Line line = Lines.fromPointAndAngle(p, 0.0, precision);
    881
    882         // act/assert
    883         final Vector2D offset1 = Vector2D.of(0, 1e-4);
    884         final Vector2D offset2 = Vector2D.of(0, 2e-3);
    885
    886         Assert.assertTrue(line.contains(Lines.fromPointAndAngle(p.add(offset1), 0.0, precision)));
    887         Assert.assertTrue(line.contains(Lines.fromPointAndAngle(p.subtract(offset1), 0.0, precision)));
    888
    889         Assert.assertFalse(line.contains(Lines.fromPointAndAngle(p.add(offset2), 0.0, precision)));
    890         Assert.assertFalse(line.contains(Lines.fromPointAndAngle(p.subtract(offset2), 0.0, precision)));
    891
    892         Assert.assertTrue(line.contains(Lines.fromPointAndAngle(p, 1e-4, precision)));
    893         Assert.assertFalse(line.contains(Lines.fromPointAndAngle(p, 1e-2, precision)));
    894     }
    895
    896     @Test
    897     public void testContains_point() {
    898         // arrange
    899         final Vector2D p1 = Vector2D.of(-1, 0);
    900         final Vector2D p2 = Vector2D.of(0, 2);
    901         final Line line = Lines.fromPoints(p1, p2, TEST_PRECISION);
    902
    903         // act/assert
    904         Assert.assertTrue(line.contains(p1));
    905         Assert.assertTrue(line.contains(p2));
    906
    907         Assert.assertFalse(line.contains(Vector2D.ZERO));
    908         Assert.assertFalse(line.contains(Vector2D.of(100, 79)));
    909
    910         final Vector2D offset1 = Vector2D.of(0.1, 0);
    911         final Vector2D offset2 = Vector2D.of(0, -0.1);
    912         Vector2D v;
    913         for (double t = -2; t <= 2; t += 0.1) {
    914             v = p1.lerp(p2, t);
    915
    916             Assert.assertTrue(line.contains(v));
    917
    918             Assert.assertFalse(line.contains(v.add(offset1)));
    919             Assert.assertFalse(line.contains(v.add(offset2)));
    920         }
    921     }
    922
    923     @Test
    924     public void testContains_point_closeToEpsilon() {
    925         // arrange
    926         final double eps = 1e-3;
    927         final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(eps);
    928
    929         final Vector2D p1 = Vector2D.of(-1, 0);
    930         final Vector2D p2 = Vector2D.of(0, 2);
    931         final Vector2D mid = p1.lerp(p2, 0.5);
    932
    933         final Line line = Lines.fromPoints(p1, p2, precision);
    934         final Vector2D dir = line.getOffsetDirection();
    935
    936         // act/assert
    937         Assert.assertTrue(line.contains(mid.add(dir.multiply(1e-4))));
    938         Assert.assertTrue(line.contains(mid.add(dir.multiply(-1e-4))));
    939
    940         Assert.assertFalse(line.contains(mid.add(dir.multiply(2e-3))));
    941         Assert.assertFalse(line.contains(mid.add(dir.multiply(-2e-3))));
    942     }
    943
    944     @Test
    945     public void testDistance_point() {
    946         // arrange
    947         final Line line = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
    948         final Line reversed = line.reverse();
    949
    950         // act/assert
    951         Assert.assertEquals(0.0, line.distance(Vector2D.of(-0.5, 1)), TEST_EPS);
    952         Assert.assertEquals(0.0, line.distance(Vector2D.of(-1.5, -1)), TEST_EPS);
    953         Assert.assertEquals(0.0, line.distance(Vector2D.of(0.5, 3)), TEST_EPS);
    954
    955         final double d = Math.sin(Math.atan2(2, 1));
    956
    957         Assert.assertEquals(d, line.distance(Vector2D.ZERO), TEST_EPS);
    958         Assert.assertEquals(d, line.distance(Vector2D.of(-1, 2)), TEST_EPS);
    959
    960         Assert.assertEquals(d, reversed.distance(Vector2D.ZERO), TEST_EPS);
    961         Assert.assertEquals(d, reversed.distance(Vector2D.of(-1, 2)), TEST_EPS);
    962     }
    963
    964     @Test
    965     public void testDistance_point_permute() {
    966         // arrange
    967         final Line line = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
    968         final Vector2D lineOrigin = line.getOrigin();
    969
    970         EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
    971             final Vector2D pt = Vector2D.of(x, y);
    972
    973             // act
    974             final double dist = line.distance(pt);
    975
    976             // arrange
    977             final Vector2D vec = lineOrigin.vectorTo(pt).reject(line.getDirection());
    978             Assert.assertEquals(vec.norm(), dist, TEST_EPS);
    979         });
    980     }
    981
    982     @Test
    983     public void testIsParallel() {
    984         // arrange
    985         final Vector2D dir = Vector2D.of(3, 7);
    986         final Line a = Lines.fromPointAndDirection(Vector2D.of(1, 2), dir, TEST_PRECISION);
    987         final Line b = Lines.fromPointAndDirection(Vector2D.of(0, -4), dir, TEST_PRECISION);
    988         final Line c = Lines.fromPointAndDirection(Vector2D.of(-2, -2), dir.negate(), TEST_PRECISION);
    989         final Line d = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    990
    991         // act/assert
    992         Assert.assertTrue(a.isParallel(a));
    993
    994         Assert.assertTrue(a.isParallel(b));
    995         Assert.assertTrue(b.isParallel(a));
    996
    997         Assert.assertTrue(a.isParallel(c));
    998         Assert.assertTrue(c.isParallel(a));
    999
    1000         Assert.assertFalse(a.isParallel(d));
    1001         Assert.assertFalse(d.isParallel(a));
    1002     }
    1003
    1004     @Test
    1005     public void testIsParallel_closeToParallel() {
    1006         // arrange
    1007         final double eps = 1e-3;
    1008         final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(eps);
    1009
    1010         final Vector2D p1 = Vector2D.of(1, 2);
    1011         final Vector2D p2 = Vector2D.of(1, -2);
    1012
    1013         final Line line = Lines.fromPointAndAngle(p1, 0.0, precision);
    1014
    1015         // act/assert
    1016         Assert.assertTrue(line.isParallel(Lines.fromPointAndAngle(p2, 1e-4, precision)));
    1017         Assert.assertFalse(line.isParallel(Lines.fromPointAndAngle(p2, 1e-2, precision)));
    1018     }
    1019
    1020     @Test
    1021     public void testTransform() {
    1022         // arrange
    1023         final AffineTransformMatrix2D scale = AffineTransformMatrix2D.createScale(2, 3);
    1024         final AffineTransformMatrix2D reflect = AffineTransformMatrix2D.createScale(-1, 1);
    1025         final AffineTransformMatrix2D translate = AffineTransformMatrix2D.createTranslation(3, 4);
    1026         final AffineTransformMatrix2D rotate = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
    1027         final AffineTransformMatrix2D rotateAroundPt = AffineTransformMatrix2D.createRotation(Vector2D.of(0, 1), PlaneAngleRadians.PI_OVER_TWO);
    1028
    1029         final Vector2D p1 = Vector2D.of(0, 1);
    1030         final Vector2D p2 = Vector2D.of(1, 0);
    1031
    1032         final Line horizontal = Lines.fromPointAndDirection(p1, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    1033         final Line vertical = Lines.fromPointAndDirection(p2, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
    1034         final Line diagonal = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION);
    1035
    1036         // act/assert
    1037         Assert.assertSame(TEST_PRECISION, horizontal.transform(scale).getPrecision());
    1038
    1039         checkLine(horizontal.transform(scale), Vector2D.of(0, 3), Vector2D.Unit.PLUS_X);
    1040         checkLine(vertical.transform(scale), Vector2D.of(2, 0), Vector2D.Unit.PLUS_Y);
    1041         checkLine(diagonal.transform(scale), Vector2D.ZERO, Vector2D.of(2, 3).normalize());
    1042
    1043         checkLine(horizontal.transform(reflect), p1, Vector2D.Unit.MINUS_X);
    1044         checkLine(vertical.transform(reflect), Vector2D.of(-1, 0), Vector2D.Unit.PLUS_Y);
    1045         checkLine(diagonal.transform(reflect), Vector2D.ZERO, Vector2D.of(-1, 1).normalize());
    1046
    1047         checkLine(horizontal.transform(translate), Vector2D.of(0, 5), Vector2D.Unit.PLUS_X);
    1048         checkLine(vertical.transform(translate), Vector2D.of(4, 0), Vector2D.Unit.PLUS_Y);
    1049         checkLine(diagonal.transform(translate), Vector2D.of(-0.5, 0.5), Vector2D.of(1, 1).normalize());
    1050
    1051         checkLine(horizontal.transform(rotate), Vector2D.of(-1, 0), Vector2D.Unit.PLUS_Y);
    1052         checkLine(vertical.transform(rotate), Vector2D.of(0, 1), Vector2D.Unit.MINUS_X);
    1053         checkLine(diagonal.transform(rotate), Vector2D.ZERO, Vector2D.of(-1, 1).normalize());
    1054
    1055         checkLine(horizontal.transform(rotateAroundPt), Vector2D.ZERO, Vector2D.Unit.PLUS_Y);
    1056         checkLine(vertical.transform(rotateAroundPt), Vector2D.of(0, 2), Vector2D.Unit.MINUS_X);
    1057         checkLine(diagonal.transform(rotateAroundPt), Vector2D.of(1, 1), Vector2D.of(-1, 1).normalize());
    1058     }
    1059
    1060     @Test
    1061     public void testTransform_collapsedPoints() {
    1062         // arrange
    1063         final AffineTransformMatrix2D scaleCollapse = AffineTransformMatrix2D.createScale(0, 1);
    1064         final Line line = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    1065
    1066         // act/assert
    1067         GeometryTestUtils.assertThrows(() -> {
    1068             line.transform(scaleCollapse);
    1069         }, IllegalArgumentException.class, "Line direction cannot be zero");
    1070     }
    1071
    1072     @Test
    1073     public void testSubspaceTransform() {
    1074         // arrange
    1075         final Line line = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(1, 1), TEST_PRECISION);
    1076
    1077         // act/assert
    1078         checkSubspaceTransform(line.subspaceTransform(AffineTransformMatrix2D.createScale(2, 3)),
    1079                 Vector2D.of(2, 0), Vector2D.Unit.PLUS_Y,
    1080                 Vector2D.of(2, 0), Vector2D.of(2, 3));
    1081
    1082         checkSubspaceTransform(line.subspaceTransform(AffineTransformMatrix2D.createTranslation(2, 3)),
    1083                 Vector2D.of(3, 0), Vector2D.Unit.PLUS_Y,
    1084                 Vector2D.of(3, 3), Vector2D.of(3, 4));
    1085
    1086         checkSubspaceTransform(line.subspaceTransform(AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO)),
    1087                 Vector2D.of(0, 1), Vector2D.Unit.MINUS_X,
    1088                 Vector2D.of(0, 1), Vector2D.of(-1, 1));
    1089     }
    1090
    1091     private void checkSubspaceTransform(final SubspaceTransform st, final Vector2D origin, final Vector2D dir, final Vector2D tZero, final Vector2D tOne) {
    1092
    1093         final Line line = st.getLine();
    1094         final AffineTransformMatrix1D transform = st.getTransform();
    1095
    1096         checkLine(line, origin, dir);
    1097
    1098         EuclideanTestUtils.assertCoordinatesEqual(tZero, line.toSpace(transform.apply(Vector1D.ZERO)), TEST_EPS);
    1099         EuclideanTestUtils.assertCoordinatesEqual(tOne, line.toSpace(transform.apply(Vector1D.Unit.PLUS)), TEST_EPS);
    1100     }
    1101
    1102     @Test
    1103     public void testSubspaceTransform_transformsPointsCorrectly() {
    1104         // arrange
    1105         final Line line = Lines.fromPointAndDirection(Vector2D.of(1, 0), Vector2D.of(1, 1), TEST_PRECISION);
    1106
    1107         EuclideanTestUtils.permuteSkipZero(-2, 2, 0.5, (a, b) -> {
    1108             // create a somewhat complicate transform to try to hit all of the edge cases
    1109             final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(Vector2D.of(a, b))
    1110                     .rotate(a * b)
    1111                     .scale(0.1, 4);
    1112
    1113             // act
    1114             final SubspaceTransform st = line.subspaceTransform(transform);
    1115
    1116             // assert
    1117             for (double x = -5.0; x <= 5.0; x += 1) {
    1118                 final Vector1D subPt = Vector1D.of(x);
    1119                 final Vector2D expected = transform.apply(line.toSpace(subPt));
    1120                 final Vector2D actual = st.getLine().toSpace(
    1121                         st.getTransform().apply(subPt));
    1122
    1123                 EuclideanTestUtils.assertCoordinatesEqual(expected, actual, TEST_EPS);
    1124             }
    1125         });
    1126     }
    1127
    1128     @Test
    1129     public void testEq() {
    1130         // arrange
    1131         final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-3);
    1132
    1133         final Vector2D p = Vector2D.of(1, 2);
    1134         final double angle = 1.0;
    1135
    1136         final Line a = Lines.fromPointAndAngle(p, angle, precision);
    1137         final Line b = Lines.fromPointAndAngle(Vector2D.ZERO, angle, precision);
    1138         final Line c = Lines.fromPointAndAngle(p, angle + 1.0, precision);
    1139
    1140         final Line d = Lines.fromPointAndAngle(p, angle, precision);
    1141         final Line e = Lines.fromPointAndAngle(p.add(Vector2D.of(1e-4, 1e-4)), angle, precision);
    1142         final Line f = Lines.fromPointAndAngle(p, angle + 1e-4, precision);
    1143
    1144         // act/assert
    1145         Assert.assertTrue(a.eq(a, precision));
    1146
    1147         Assert.assertTrue(a.eq(d, precision));
    1148         Assert.assertTrue(d.eq(a, precision));
    1149
    1150         Assert.assertTrue(a.eq(e, precision));
    1151         Assert.assertTrue(e.eq(a, precision));
    1152
    1153         Assert.assertTrue(a.eq(f, precision));
    1154         Assert.assertTrue(f.eq(a, precision));
    1155
    1156         Assert.assertFalse(a.eq(b, precision));
    1157         Assert.assertFalse(a.eq(c, precision));
    1158     }
    1159
    1160     @Test
    1161     public void testHashCode() {
    1162         // arrange
    1163         final DoublePrecisionContext precision1 = new EpsilonDoublePrecisionContext(1e-4);
    1164         final DoublePrecisionContext precision2 = new EpsilonDoublePrecisionContext(1e-5);
    1165
    1166         final Vector2D p = Vector2D.of(1, 2);
    1167         final Vector2D v = Vector2D.of(1, 1);
    1168
    1169         final Line a = Lines.fromPointAndDirection(p, v, precision1);
    1170         final Line b = Lines.fromPointAndDirection(Vector2D.ZERO, v, precision1);
    1171         final Line c = Lines.fromPointAndDirection(p, v.negate(), precision1);
    1172         final Line d = Lines.fromPointAndDirection(p, v, precision2);
    1173         final Line e = Lines.fromPointAndDirection(p, v, precision1);
    1174
    1175         // act/assert
    1176         final int aHash = a.hashCode();
    1177
    1178         Assert.assertEquals(aHash, a.hashCode());
    1179         Assert.assertEquals(aHash, e.hashCode());
    1180
    1181         Assert.assertNotEquals(aHash, b.hashCode());
    1182         Assert.assertNotEquals(aHash, c.hashCode());
    1183         Assert.assertNotEquals(aHash, d.hashCode());
    1184     }
    1185
    1186     @Test
    1187     public void testEquals() {
    1188      // arrange
    1189         final DoublePrecisionContext precision1 = new EpsilonDoublePrecisionContext(1e-4);
    1190         final DoublePrecisionContext precision2 = new EpsilonDoublePrecisionContext(1e-5);
    1191
    1192         final Vector2D p = Vector2D.of(1, 2);
    1193         final Vector2D v = Vector2D.of(1, 1);
    1194
    1195         final Line a = Lines.fromPointAndDirection(p, v, precision1);
    1196         final Line b = Lines.fromPointAndDirection(Vector2D.ZERO, v, precision1);
    1197         final Line c = Lines.fromPointAndDirection(p, v.negate(), precision1);
    1198         final Line d = Lines.fromPointAndDirection(p, v, precision2);
    1199         final Line e = Lines.fromPointAndDirection(p, v, precision1);
    1200
    1201         // act/assert
    1202         Assert.assertEquals(a, a);
    1203         Assert.assertEquals(a, e);
    1204         Assert.assertEquals(e, a);
    1205
    1206         Assert.assertFalse(a.equals(null));
    1207         Assert.assertFalse(a.equals(new Object()));
    1208
    1209         Assert.assertNotEquals(a, b);
    1210         Assert.assertNotEquals(a, c);
    1211         Assert.assertNotEquals(a, d);
    1212     }
    1213
    1214     @Test
    1215     public void testToString() {
    1216         // arrange
    1217         final Line line = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
    1218
    1219         // act
    1220         final String str = line.toString();
    1221
    1222         // assert
    1223         Assert.assertTrue(str.contains("Line"));
    1224         Assert.assertTrue(str.contains("origin= (0.0, 0.0)"));
    1225         Assert.assertTrue(str.contains("direction= (1.0, 0.0)"));
    1226     }
    1227
    1228     /**
    1229      * Check that the line has the given defining properties.
    1230      * @param line
    1231      * @param origin
    1232      * @param dir
    1233      */
    1234     private void checkLine(final Line line, final Vector2D origin, final Vector2D dir) {
    1235         EuclideanTestUtils.assertCoordinatesEqual(origin, line.getOrigin(), TEST_EPS);
    1236         EuclideanTestUtils.assertCoordinatesEqual(dir, line.getDirection(), TEST_EPS);
    1237     }
    1238 }