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