/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.geometry.euclidean.twod; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.regex.Pattern; import org.apache.commons.geometry.core.GeometryTestUtils; import org.apache.commons.geometry.core.precision.DoublePrecisionContext; import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext; import org.apache.commons.geometry.euclidean.EuclideanTestUtils; import org.apache.commons.numbers.angle.PlaneAngleRadians; import org.apache.commons.numbers.core.Precision; import org.junit.Assert; import org.junit.Test; public class Vector2DTest { private static final double EPS = Math.ulp(1d); @Test public void testConstants() { // act/assert checkVector(Vector2D.ZERO, 0, 0); checkVector(Vector2D.Unit.PLUS_X, 1, 0); checkVector(Vector2D.Unit.MINUS_X, -1, 0); checkVector(Vector2D.Unit.PLUS_Y, 0, 1); checkVector(Vector2D.Unit.MINUS_Y, 0, -1); checkVector(Vector2D.NaN, Double.NaN, Double.NaN); checkVector(Vector2D.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); checkVector(Vector2D.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); } @Test public void testConstants_normalize() { // act/assert GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.normalize(), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NaN.normalize(), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.normalize(), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.normalize(), IllegalArgumentException.class); Assert.assertSame(Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X.normalize()); Assert.assertSame(Vector2D.Unit.MINUS_X, Vector2D.Unit.MINUS_X.normalize()); Assert.assertSame(Vector2D.Unit.PLUS_Y, Vector2D.Unit.PLUS_Y.normalize()); Assert.assertSame(Vector2D.Unit.MINUS_Y, Vector2D.Unit.MINUS_Y.normalize()); } @Test public void testCoordinateAscendingOrder() { // arrange Comparator<Vector2D> cmp = Vector2D.COORDINATE_ASCENDING_ORDER; // act/assert Assert.assertEquals(0, cmp.compare(Vector2D.of(1, 2), Vector2D.of(1, 2))); Assert.assertEquals(-1, cmp.compare(Vector2D.of(0, 2), Vector2D.of(1, 2))); Assert.assertEquals(-1, cmp.compare(Vector2D.of(1, 1), Vector2D.of(1, 2))); Assert.assertEquals(1, cmp.compare(Vector2D.of(2, 2), Vector2D.of(1, 2))); Assert.assertEquals(1, cmp.compare(Vector2D.of(1, 3), Vector2D.of(1, 2))); Assert.assertEquals(-1, cmp.compare(Vector2D.of(1, 3), null)); Assert.assertEquals(1, cmp.compare(null, Vector2D.of(1, 2))); Assert.assertEquals(0, cmp.compare(null, null)); } @Test public void testCoordinates() { // arrange Vector2D v = Vector2D.of(1, 2); // act/assert Assert.assertEquals(1.0, v.getX(), EPS); Assert.assertEquals(2.0, v.getY(), EPS); } @Test public void testToArray() { // arrange Vector2D oneTwo = Vector2D.of(1, 2); // act double[] array = oneTwo.toArray(); // assert Assert.assertEquals(2, array.length); Assert.assertEquals(1.0, array[0], EPS); Assert.assertEquals(2.0, array[1], EPS); } @Test public void testDimension() { // arrange Vector2D v = Vector2D.of(1, 2); // act/assert Assert.assertEquals(2, v.getDimension()); } @Test public void testNaN() { // act/assert Assert.assertTrue(Vector2D.of(0, Double.NaN).isNaN()); Assert.assertTrue(Vector2D.of(Double.NaN, 0).isNaN()); Assert.assertFalse(Vector2D.of(1, 1).isNaN()); Assert.assertFalse(Vector2D.of(1, Double.NEGATIVE_INFINITY).isNaN()); Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, 1).isNaN()); } @Test public void testInfinite() { // act/assert Assert.assertTrue(Vector2D.of(0, Double.NEGATIVE_INFINITY).isInfinite()); Assert.assertTrue(Vector2D.of(Double.NEGATIVE_INFINITY, 0).isInfinite()); Assert.assertTrue(Vector2D.of(0, Double.POSITIVE_INFINITY).isInfinite()); Assert.assertTrue(Vector2D.of(Double.POSITIVE_INFINITY, 0).isInfinite()); Assert.assertFalse(Vector2D.of(1, 1).isInfinite()); Assert.assertFalse(Vector2D.of(0, Double.NaN).isInfinite()); Assert.assertFalse(Vector2D.of(Double.NEGATIVE_INFINITY, Double.NaN).isInfinite()); Assert.assertFalse(Vector2D.of(Double.NaN, Double.NEGATIVE_INFINITY).isInfinite()); Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, Double.NaN).isInfinite()); Assert.assertFalse(Vector2D.of(Double.NaN, Double.POSITIVE_INFINITY).isInfinite()); } @Test public void testFinite() { // act/assert Assert.assertTrue(Vector2D.ZERO.isFinite()); Assert.assertTrue(Vector2D.of(1, 1).isFinite()); Assert.assertFalse(Vector2D.of(0, Double.NEGATIVE_INFINITY).isFinite()); Assert.assertFalse(Vector2D.of(Double.NEGATIVE_INFINITY, 0).isFinite()); Assert.assertFalse(Vector2D.of(0, Double.POSITIVE_INFINITY).isFinite()); Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, 0).isFinite()); Assert.assertFalse(Vector2D.of(0, Double.NaN).isFinite()); Assert.assertFalse(Vector2D.of(Double.NEGATIVE_INFINITY, Double.NaN).isFinite()); Assert.assertFalse(Vector2D.of(Double.NaN, Double.NEGATIVE_INFINITY).isFinite()); Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, Double.NaN).isFinite()); Assert.assertFalse(Vector2D.of(Double.NaN, Double.POSITIVE_INFINITY).isFinite()); } @Test public void testGetZero() { // act/assert checkVector(Vector2D.of(1.0, 1.0).getZero(), 0, 0); } @Test public void testNorm() { // act/assert Assert.assertEquals(0.0, Vector2D.of(0, 0).norm(), EPS); Assert.assertEquals(5.0, Vector2D.of(3, 4).norm(), EPS); Assert.assertEquals(5.0, Vector2D.of(3, -4).norm(), EPS); Assert.assertEquals(5.0, Vector2D.of(-3, 4).norm(), EPS); Assert.assertEquals(5.0, Vector2D.of(-3, -4).norm(), EPS); Assert.assertEquals(Math.sqrt(5.0), Vector2D.of(-1, -2).norm(), EPS); } @Test public void testNorm_unitVectors() { // arrange Vector2D v = Vector2D.of(2.0, 3.0).normalize(); // act/assert Assert.assertEquals(1.0, v.norm(), 0.0); } @Test public void testNormSq() { // act/assert Assert.assertEquals(0.0, Vector2D.of(0, 0).normSq(), EPS); Assert.assertEquals(25.0, Vector2D.of(3, 4).normSq(), EPS); Assert.assertEquals(25.0, Vector2D.of(3, -4).normSq(), EPS); Assert.assertEquals(25.0, Vector2D.of(-3, 4).normSq(), EPS); Assert.assertEquals(25.0, Vector2D.of(-3, -4).normSq(), EPS); Assert.assertEquals(5.0, Vector2D.of(-1, -2).normSq(), EPS); } @Test public void testNormSq_unitVectors() { // arrange Vector2D v = Vector2D.of(2.0, 3.0).normalize(); // act/assert Assert.assertEquals(1.0, v.normSq(), 0.0); } @Test public void testWithNorm() { // act/assert checkVector(Vector2D.of(3, 4).withNorm(1.0), 0.6, 0.8); checkVector(Vector2D.of(4, 3).withNorm(1.0), 0.8, 0.6); checkVector(Vector2D.of(-3, 4).withNorm(0.5), -0.3, 0.4); checkVector(Vector2D.of(3, -4).withNorm(2.0), 1.2, -1.6); checkVector(Vector2D.of(-3, -4).withNorm(3.0), -1.8, 3.0 * Math.sin(Math.atan2(-4, -3))); checkVector(Vector2D.of(0.5, 0.5).withNorm(2), Math.sqrt(2), Math.sqrt(2)); } @Test public void testWithNorm_illegalNorm() { // act/assert GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.withNorm(2.0), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NaN.withNorm(2.0), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.withNorm(2.0), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.withNorm(2.0), IllegalArgumentException.class); } @Test public void testWithNorm_unitVectors() { // arrange double eps = 1e-14; Vector2D v = Vector2D.of(2.0, -3.0).normalize(); // act/assert checkVector(Vector2D.Unit.PLUS_X.withNorm(2.5), 2.5, 0.0); checkVector(Vector2D.Unit.MINUS_Y.withNorm(3.14), 0.0, -3.14); for (int i = -10; i <= 10; i++) { Assert.assertEquals(Math.abs((double) i), v.withNorm(i).norm(), eps); } } @Test public void testAdd() { // arrange Vector2D v1 = Vector2D.of(-1, 2); Vector2D v2 = Vector2D.of(3, -4); Vector2D v3 = Vector2D.of(5, 6); // act/assert checkVector(v1.add(v1), -2, 4); checkVector(v1.add(v2), 2, -2); checkVector(v2.add(v1), 2, -2); checkVector(v1.add(v3), 4, 8); checkVector(v3.add(v1), 4, 8); } @Test public void testAdd_scaled() { // arrange Vector2D v1 = Vector2D.of(-1, 2); Vector2D v2 = Vector2D.of(3, -4); Vector2D v3 = Vector2D.of(5, 6); // act/assert checkVector(v1.add(2, v1), -3, 6); checkVector(v1.add(0, v2), -1, 2); checkVector(v2.add(1, v1), 2, -2); checkVector(v1.add(-1, v3), -6, -4); checkVector(v3.add(-2, v1), 7, 2); } @Test public void testSubtract() { // arrange Vector2D v1 = Vector2D.of(-1, 2); Vector2D v2 = Vector2D.of(3, -4); Vector2D v3 = Vector2D.of(5, 6); // act/assert checkVector(v1.subtract(v1), 0, 0); checkVector(v1.subtract(v2), -4, 6); checkVector(v2.subtract(v1), 4, -6); checkVector(v1.subtract(v3), -6, -4); checkVector(v3.subtract(v1), 6, 4); } @Test public void testSubtract_scaled() { // arrange Vector2D v1 = Vector2D.of(-1, 2); Vector2D v2 = Vector2D.of(3, -4); Vector2D v3 = Vector2D.of(5, 6); // act/assert checkVector(v1.subtract(2, v1), 1, -2); checkVector(v1.subtract(0, v2), -1, 2); checkVector(v2.subtract(1, v1), 4, -6); checkVector(v1.subtract(-1, v3), 4, 8); checkVector(v3.subtract(-2, v1), 3, 10); } @Test public void testNormalize() { // act/assert checkVector(Vector2D.of(100, 0).normalize(), 1, 0); checkVector(Vector2D.of(-100, 0).normalize(), -1, 0); checkVector(Vector2D.of(0, 100).normalize(), 0, 1); checkVector(Vector2D.of(0, -100).normalize(), 0, -1); checkVector(Vector2D.of(-1, 2).normalize(), -1.0 / Math.sqrt(5), 2.0 / Math.sqrt(5)); } @Test public void testNormalize_illegalNorm() { // act/assert GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.normalize(), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NaN.normalize(), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.normalize(), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.normalize(), IllegalArgumentException.class); } @Test public void testNormalize_isIdempotent() { // arrange double invSqrt2 = 1.0 / Math.sqrt(2); Vector2D v = Vector2D.of(2, 2).normalize(); // act/assert Assert.assertSame(v, v.normalize()); checkVector(v.normalize(), invSqrt2, invSqrt2); } @Test public void testNegate() { // act/assert checkVector(Vector2D.of(1, 2).negate(), -1, -2); checkVector(Vector2D.of(-3, -4).negate(), 3, 4); checkVector(Vector2D.of(5, -6).negate().negate(), 5, -6); } @Test public void testNegate_unitVectors() { // arrange Vector2D v1 = Vector2D.of(1.0, 1.0).normalize(); Vector2D v2 = Vector2D.of(-1.0, -2.0).normalize(); Vector2D v3 = Vector2D.of(2.0, -3.0).normalize(); // act/assert checkVector(v1.negate(), -1.0 / Math.sqrt(2.0), -1.0 / Math.sqrt(2.0)); checkVector(v2.negate(), 1.0 / Math.sqrt(5.0), 2.0 / Math.sqrt(5.0)); checkVector(v3.negate(), -2.0 / Math.sqrt(13.0), 3.0 / Math.sqrt(13.0)); } @Test public void testScalarMultiply() { // act/assert checkVector(Vector2D.of(1, 2).multiply(0), 0, 0); checkVector(Vector2D.of(1, 2).multiply(3), 3, 6); checkVector(Vector2D.of(1, 2).multiply(-3), -3, -6); checkVector(Vector2D.of(2, 3).multiply(1.5), 3, 4.5); checkVector(Vector2D.of(2, 3).multiply(-1.5), -3, -4.5); } @Test public void testDistance() { // arrange Vector2D v1 = Vector2D.of(1, 1); Vector2D v2 = Vector2D.of(4, 5); Vector2D v3 = Vector2D.of(-1, 0); // act/assert Assert.assertEquals(0, v1.distance(v1), EPS); Assert.assertEquals(5, v1.distance(v2), EPS); Assert.assertEquals(5, v2.distance(v1), EPS); Assert.assertEquals(Math.sqrt(5), v1.distance(v3), EPS); Assert.assertEquals(Math.sqrt(5), v3.distance(v1), EPS); } @Test public void testDistanceSq() { // arrange Vector2D v1 = Vector2D.of(1, 1); Vector2D v2 = Vector2D.of(4, 5); Vector2D v3 = Vector2D.of(-1, 0); // act/assert Assert.assertEquals(0, v1.distanceSq(v1), EPS); Assert.assertEquals(25, v1.distanceSq(v2), EPS); Assert.assertEquals(25, v2.distanceSq(v1), EPS); Assert.assertEquals(5, v1.distanceSq(v3), EPS); Assert.assertEquals(5, v3.distanceSq(v1), EPS); } @Test public void testDotProduct() { // arrange Vector2D v1 = Vector2D.of(1, 1); Vector2D v2 = Vector2D.of(4, 5); Vector2D v3 = Vector2D.of(-1, 0); // act/assert Assert.assertEquals(2, v1.dot(v1), EPS); Assert.assertEquals(41, v2.dot(v2), EPS); Assert.assertEquals(1, v3.dot(v3), EPS); Assert.assertEquals(9, v1.dot(v2), EPS); Assert.assertEquals(9, v2.dot(v1), EPS); Assert.assertEquals(-1, v1.dot(v3), EPS); Assert.assertEquals(-1, v3.dot(v1), EPS); Assert.assertEquals(1, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.PLUS_X), EPS); Assert.assertEquals(0, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.PLUS_Y), EPS); Assert.assertEquals(-1, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.MINUS_X), EPS); Assert.assertEquals(0, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.MINUS_Y), EPS); } @Test public void testOrthogonal() { // arrange double invSqrt2 = 1.0 / Math.sqrt(2.0); // act/assert checkVector(Vector2D.of(3, 0).orthogonal(), 0.0, 1.0); checkVector(Vector2D.of(1.0, 1.0).orthogonal(), -invSqrt2, invSqrt2); checkVector(Vector2D.of(0, 2).orthogonal(), -1.0, 0.0); checkVector(Vector2D.of(-1.0, 1.0).orthogonal(), -invSqrt2, -invSqrt2); checkVector(Vector2D.Unit.MINUS_X.orthogonal(), 0.0, -1.0); checkVector(Vector2D.of(-1.0, -1.0).orthogonal(), invSqrt2, -invSqrt2); checkVector(Vector2D.Unit.MINUS_Y.orthogonal(), 1.0, 0.0); checkVector(Vector2D.of(1.0, -1.0).orthogonal(), invSqrt2, invSqrt2); } @Test public void testOrthogonal_fullCircle() { for (double az = 0.0; az <= PlaneAngleRadians.TWO_PI; az += 0.25) { // arrange Vector2D v = PolarCoordinates.toCartesian(Math.PI, az); //act Vector2D ortho = v.orthogonal(); // assert Assert.assertEquals(1.0, ortho.norm(), EPS); Assert.assertEquals(0.0, v.dot(ortho), EPS); } } @Test public void testOrthogonal_illegalNorm() { // act/assert GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.orthogonal(), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NaN.orthogonal(), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.orthogonal(), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.orthogonal(), IllegalArgumentException.class); } @Test public void testOrthogonal_givenDirection() { // arrange double invSqrt2 = 1.0 / Math.sqrt(2.0); // act/assert checkVector(Vector2D.Unit.PLUS_X.orthogonal(Vector2D.of(-1.0, 0.1)), 0.0, 1.0); checkVector(Vector2D.Unit.PLUS_Y.orthogonal(Vector2D.of(2.0, 2.0)), 1.0, 0.0); checkVector(Vector2D.of(2.9, 2.9).orthogonal(Vector2D.of(1.0, 0.22)), invSqrt2, -invSqrt2); checkVector(Vector2D.of(2.9, 2.9).orthogonal(Vector2D.of(0.22, 1.0)), -invSqrt2, invSqrt2); } @Test public void testOrthogonal_givenDirection_illegalNorm() { // act/assert GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.orthogonal(Vector2D.Unit.PLUS_X), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NaN.orthogonal(Vector2D.Unit.PLUS_X), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.orthogonal(Vector2D.Unit.PLUS_X), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.orthogonal(Vector2D.Unit.PLUS_X), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.ZERO), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.NaN), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.POSITIVE_INFINITY), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.NEGATIVE_INFINITY), IllegalArgumentException.class); } @Test public void testOrthogonal_givenDirection_directionIsCollinear() { // act/assert GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.Unit.PLUS_X), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.Unit.MINUS_X), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.of(1.0, 1.0).orthogonal(Vector2D.of(2.0, 2.0)), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.of(-1.01, -1.01).orthogonal(Vector2D.of(20.1, 20.1)), IllegalArgumentException.class); } @Test public void testAngle() { // act/assert Assert.assertEquals(0, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.PLUS_X), EPS); Assert.assertEquals(PlaneAngleRadians.PI, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.MINUS_X), EPS); Assert.assertEquals(PlaneAngleRadians.PI_OVER_TWO, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.PLUS_Y), EPS); Assert.assertEquals(PlaneAngleRadians.PI_OVER_TWO, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.MINUS_Y), EPS); Assert.assertEquals(PlaneAngleRadians.PI / 4, Vector2D.of(1, 1).angle(Vector2D.of(1, 0)), EPS); Assert.assertEquals(PlaneAngleRadians.PI / 4, Vector2D.of(1, 0).angle(Vector2D.of(1, 1)), EPS); Assert.assertEquals(0.004999958333958323, Vector2D.of(20.0, 0.0).angle(Vector2D.of(20.0, 0.1)), EPS); } @Test public void testAngle_illegalNorm() { // arrange Vector2D v = Vector2D.of(1.0, 1.0); // act/assert GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.angle(v), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NaN.angle(v), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.angle(v), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.angle(v), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.ZERO), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.NaN), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.POSITIVE_INFINITY), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.NEGATIVE_INFINITY), IllegalArgumentException.class); } @Test public void testSignedArea() { // arrange double eps = 1e-10; Vector2D a = Vector2D.Unit.PLUS_X; Vector2D b = Vector2D.Unit.PLUS_Y; Vector2D c = Vector2D.of(1, 1).withNorm(2.0); Vector2D d = Vector2D.of(-1, 1).withNorm(3.0); // act/assert Assert.assertEquals(1.0, a.signedArea(b), eps); Assert.assertEquals(-1.0, b.signedArea(a), eps); double xAxisAndCArea = 2 * Math.cos(0.25 * PlaneAngleRadians.PI); Assert.assertEquals(xAxisAndCArea, a.signedArea(c), eps); Assert.assertEquals(-xAxisAndCArea, c.signedArea(a), eps); double xAxisAndDArea = 3 * Math.cos(0.25 * PlaneAngleRadians.PI); Assert.assertEquals(xAxisAndDArea, a.signedArea(d), eps); Assert.assertEquals(-xAxisAndDArea, d.signedArea(a), eps); Assert.assertEquals(6.0, c.signedArea(d), eps); Assert.assertEquals(-6.0, d.signedArea(c), eps); } @Test public void testSignedArea_collinear() { // arrange Vector2D a = Vector2D.Unit.PLUS_X; Vector2D b = Vector2D.Unit.PLUS_Y; Vector2D c = Vector2D.of(-3, 8); // act/assert Assert.assertEquals(0.0, a.signedArea(a), EPS); Assert.assertEquals(0.0, b.signedArea(b), EPS); Assert.assertEquals(0.0, c.signedArea(c), EPS); Assert.assertEquals(0.0, a.signedArea(a.multiply(100.0)), EPS); Assert.assertEquals(0.0, b.signedArea(b.negate()), EPS); Assert.assertEquals(0.0, c.signedArea(c.multiply(-0.03)), EPS); } @Test public void testProject() { // arrange Vector2D v1 = Vector2D.of(3.0, 4.0); Vector2D v2 = Vector2D.of(1.0, 4.0); // act/assert checkVector(Vector2D.ZERO.project(v1), 0.0, 0.0); checkVector(v1.project(v1), 3.0, 4.0); checkVector(v1.project(v1.negate()), 3.0, 4.0); checkVector(v1.project(Vector2D.Unit.PLUS_X), 3.0, 0.0); checkVector(v1.project(Vector2D.Unit.MINUS_X), 3.0, 0.0); checkVector(v1.project(Vector2D.Unit.PLUS_Y), 0.0, 4.0); checkVector(v1.project(Vector2D.Unit.MINUS_Y), 0.0, 4.0); checkVector(v2.project(v1), (19.0 / 25.0) * 3.0, (19.0 / 25.0) * 4.0); } @Test public void testProject_baseHasIllegalNorm() { // arrange Vector2D v = Vector2D.of(1.0, 1.0); // act/assert GeometryTestUtils.assertThrows(() -> v.project(Vector2D.ZERO), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> v.project(Vector2D.NaN), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> v.project(Vector2D.POSITIVE_INFINITY), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> v.project(Vector2D.NEGATIVE_INFINITY), IllegalArgumentException.class); } @Test public void testReject() { // arrange Vector2D v1 = Vector2D.of(3.0, 4.0); Vector2D v2 = Vector2D.of(1.0, 4.0); // act/assert checkVector(Vector2D.ZERO.reject(v1), 0.0, 0.0); checkVector(v1.reject(v1), 0.0, 0.0); checkVector(v1.reject(v1.negate()), 0.0, 0.0); checkVector(v1.reject(Vector2D.Unit.PLUS_X), 0.0, 4.0); checkVector(v1.reject(Vector2D.Unit.MINUS_X), 0.0, 4.0); checkVector(v1.reject(Vector2D.Unit.PLUS_Y), 3.0, 0.0); checkVector(v1.reject(Vector2D.Unit.MINUS_Y), 3.0, 0.0); checkVector(v2.reject(v1), -32.0 / 25.0, (6.0 / 25.0) * 4.0); } @Test public void testReject_baseHasIllegalNorm() { // arrange Vector2D v = Vector2D.of(1.0, 1.0); // act/assert GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.ZERO), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.NaN), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.POSITIVE_INFINITY), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.NEGATIVE_INFINITY), IllegalArgumentException.class); } @Test public void testProjectAndReject_areComplementary() { // arrange double eps = 1e-12; // act/assert checkProjectAndRejectFullCircle(Vector2D.of(1.0, 0.0), 1.0, eps); checkProjectAndRejectFullCircle(Vector2D.of(0.0, 1.0), 2.0, eps); checkProjectAndRejectFullCircle(Vector2D.of(1.0, 1.0), 3.0, eps); checkProjectAndRejectFullCircle(Vector2D.of(-2.0, 0.0), 4.0, eps); checkProjectAndRejectFullCircle(Vector2D.of(0.0, -2.0), 5.0, eps); checkProjectAndRejectFullCircle(Vector2D.of(-2.0, -2.0), 6.0, eps); } private void checkProjectAndRejectFullCircle(Vector2D vec, double baseMag, double eps) { for (double theta = 0.0; theta <= PlaneAngleRadians.TWO_PI; theta += 0.5) { Vector2D base = PolarCoordinates.toCartesian(baseMag, theta); Vector2D proj = vec.project(base); Vector2D rej = vec.reject(base); // ensure that the projection and rejection sum to the original vector EuclideanTestUtils.assertCoordinatesEqual(vec, proj.add(rej), eps); double angle = base.angle(vec); // check the angle between the projection and the base; this will // be undefined when the angle between the original vector and the // base is pi/2 (which means that the projection is the zero vector) if (angle < PlaneAngleRadians.PI_OVER_TWO) { Assert.assertEquals(0.0, proj.angle(base), eps); } else if (angle > PlaneAngleRadians.PI_OVER_TWO) { Assert.assertEquals(PlaneAngleRadians.PI, proj.angle(base), eps); } // check the angle between the rejection and the base; this should // always be pi/2 except for when the angle between the original vector // and the base is 0 or pi, in which case the rejection is the zero vector. if (angle > 0.0 && angle < PlaneAngleRadians.PI) { Assert.assertEquals(PlaneAngleRadians.PI_OVER_TWO, rej.angle(base), eps); } } } @Test public void testVectorTo() { // arrange Vector2D p1 = Vector2D.of(1, 1); Vector2D p2 = Vector2D.of(4, 5); Vector2D p3 = Vector2D.of(-1, 0); // act/assert checkVector(p1.vectorTo(p1), 0, 0); checkVector(p1.vectorTo(p2), 3, 4); checkVector(p2.vectorTo(p1), -3, -4); checkVector(p1.vectorTo(p3), -2, -1); checkVector(p3.vectorTo(p1), 2, 1); } @Test public void testDirectionTo() { // act/assert double invSqrt2 = 1.0 / Math.sqrt(2); Vector2D p1 = Vector2D.of(1, 1); Vector2D p2 = Vector2D.of(1, 5); Vector2D p3 = Vector2D.of(-2, -2); // act/assert checkVector(p1.directionTo(p2), 0, 1); checkVector(p2.directionTo(p1), 0, -1); checkVector(p1.directionTo(p3), -invSqrt2, -invSqrt2); checkVector(p3.directionTo(p1), invSqrt2, invSqrt2); } @Test public void testDirectionTo_illegalNorm() { // arrange Vector2D p = Vector2D.of(1, 2); // act/assert GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.directionTo(Vector2D.ZERO), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> p.directionTo(p), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> p.directionTo(Vector2D.NaN), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.directionTo(p), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> p.directionTo(Vector2D.POSITIVE_INFINITY), IllegalArgumentException.class); } @Test public void testLerp() { // arrange Vector2D v1 = Vector2D.of(1, -5); Vector2D v2 = Vector2D.of(-4, 0); Vector2D v3 = Vector2D.of(10, -4); // act/assert checkVector(v1.lerp(v1, 0), 1, -5); checkVector(v1.lerp(v1, 1), 1, -5); checkVector(v1.lerp(v2, -0.25), 2.25, -6.25); checkVector(v1.lerp(v2, 0), 1, -5); checkVector(v1.lerp(v2, 0.25), -0.25, -3.75); checkVector(v1.lerp(v2, 0.5), -1.5, -2.5); checkVector(v1.lerp(v2, 0.75), -2.75, -1.25); checkVector(v1.lerp(v2, 1), -4, 0); checkVector(v1.lerp(v2, 1.25), -5.25, 1.25); checkVector(v1.lerp(v3, 0), 1, -5); checkVector(v1.lerp(v3, 0.25), 3.25, -4.75); checkVector(v1.lerp(v3, 0.5), 5.5, -4.5); checkVector(v1.lerp(v3, 0.75), 7.75, -4.25); checkVector(v1.lerp(v3, 1), 10, -4); } @Test public void testTransform() { // arrange AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() .scale(2) .translate(1, 2); Vector2D v1 = Vector2D.of(1, 2); Vector2D v2 = Vector2D.of(-4, -5); // act/assert checkVector(v1.transform(transform), 3, 6); checkVector(v2.transform(transform), -7, -8); } @Test public void testPrecisionEquals() { // arrange DoublePrecisionContext smallEps = new EpsilonDoublePrecisionContext(1e-6); DoublePrecisionContext largeEps = new EpsilonDoublePrecisionContext(1e-1); Vector2D vec = Vector2D.of(1, -2); // act/assert Assert.assertTrue(vec.eq(vec, smallEps)); Assert.assertTrue(vec.eq(vec, largeEps)); Assert.assertTrue(vec.eq(Vector2D.of(1.0000007, -2.0000009), smallEps)); Assert.assertTrue(vec.eq(Vector2D.of(1.0000007, -2.0000009), largeEps)); Assert.assertFalse(vec.eq(Vector2D.of(1.004, -2), smallEps)); Assert.assertFalse(vec.eq(Vector2D.of(1, -2.004), smallEps)); Assert.assertTrue(vec.eq(Vector2D.of(1.004, -2.004), largeEps)); Assert.assertFalse(vec.eq(Vector2D.of(1, -3), smallEps)); Assert.assertFalse(vec.eq(Vector2D.of(2, -2), smallEps)); Assert.assertFalse(vec.eq(Vector2D.of(1, -3), largeEps)); Assert.assertFalse(vec.eq(Vector2D.of(2, -2), largeEps)); } @Test public void testIsZero() { // arrange DoublePrecisionContext smallEps = new EpsilonDoublePrecisionContext(1e-6); DoublePrecisionContext largeEps = new EpsilonDoublePrecisionContext(1e-1); // act/assert Assert.assertTrue(Vector2D.of(0.0, -0.0).isZero(smallEps)); Assert.assertTrue(Vector2D.of(-0.0, 0.0).isZero(largeEps)); Assert.assertTrue(Vector2D.of(-1e-7, 1e-7).isZero(smallEps)); Assert.assertTrue(Vector2D.of(1e-7, 1e-7).isZero(largeEps)); Assert.assertFalse(Vector2D.of(1e-2, 0.0).isZero(smallEps)); Assert.assertFalse(Vector2D.of(0.0, 1e-2).isZero(smallEps)); Assert.assertTrue(Vector2D.of(1e-2, -1e-2).isZero(largeEps)); Assert.assertFalse(Vector2D.of(0.2, 0.0).isZero(smallEps)); Assert.assertFalse(Vector2D.of(0.0, 0.2).isZero(smallEps)); Assert.assertFalse(Vector2D.of(0.2, 0.2).isZero(smallEps)); Assert.assertFalse(Vector2D.of(-0.2, 0.0).isZero(largeEps)); Assert.assertFalse(Vector2D.of(0.0, -0.2).isZero(largeEps)); Assert.assertFalse(Vector2D.of(-0.2, -0.2).isZero(largeEps)); } @Test public void testHashCode() { // arrange Vector2D u = Vector2D.of(1, 1); Vector2D v = Vector2D.of(1 + 10 * Precision.EPSILON, 1 + 10 * Precision.EPSILON); Vector2D w = Vector2D.of(1, 1); // act/assert Assert.assertTrue(u.hashCode() != v.hashCode()); Assert.assertEquals(u.hashCode(), w.hashCode()); Assert.assertEquals(Vector2D.of(0, Double.NaN).hashCode(), Vector2D.NaN.hashCode()); Assert.assertEquals(Vector2D.of(Double.NaN, 0).hashCode(), Vector2D.NaN.hashCode()); Assert.assertEquals(Vector2D.of(0, Double.NaN).hashCode(), Vector2D.of(Double.NaN, 0).hashCode()); } @Test public void testEquals() { // arrange Vector2D u1 = Vector2D.of(1, 2); Vector2D u2 = Vector2D.of(1, 2); // act/assert Assert.assertFalse(u1.equals(null)); Assert.assertFalse(u1.equals(new Object())); Assert.assertEquals(u1, u1); Assert.assertEquals(u1, u2); Assert.assertNotEquals(u1, Vector2D.of(-1, -2)); Assert.assertNotEquals(u1, Vector2D.of(1 + 10 * Precision.EPSILON, 2)); Assert.assertNotEquals(u1, Vector2D.of(1, 2 + 10 * Precision.EPSILON)); Assert.assertEquals(Vector2D.of(0, Double.NaN), Vector2D.of(Double.NaN, 0)); Assert.assertEquals(Vector2D.of(0, Double.POSITIVE_INFINITY), Vector2D.of(0, Double.POSITIVE_INFINITY)); Assert.assertNotEquals(Vector2D.of(Double.POSITIVE_INFINITY, 0), Vector2D.of(0, Double.POSITIVE_INFINITY)); Assert.assertEquals(Vector2D.of(Double.NEGATIVE_INFINITY, 0), Vector2D.of(Double.NEGATIVE_INFINITY, 0)); Assert.assertNotEquals(Vector2D.of(0, Double.NEGATIVE_INFINITY), Vector2D.of(Double.NEGATIVE_INFINITY, 0)); } @Test public void testToString() { // arrange Vector2D v = Vector2D.of(1, 2); Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}\\)"); // act String str = v.toString(); // assert Assert.assertTrue("Expected string " + str + " to match regex " + pattern, pattern.matcher(str).matches()); } @Test public void testParse() { // act/assert checkVector(Vector2D.parse("(1, 2)"), 1, 2); checkVector(Vector2D.parse("(-1, -2)"), -1, -2); checkVector(Vector2D.parse("(0.01, -1e-3)"), 1e-2, -1e-3); checkVector(Vector2D.parse("(NaN, -Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY); checkVector(Vector2D.parse(Vector2D.ZERO.toString()), 0, 0); checkVector(Vector2D.parse(Vector2D.Unit.MINUS_X.toString()), -1, 0); } @Test(expected = IllegalArgumentException.class) public void testParse_failure() { // act/assert Vector2D.parse("abc"); } @Test public void testOf() { // act/assert checkVector(Vector2D.of(0, 1), 0, 1); checkVector(Vector2D.of(-1, -2), -1, -2); checkVector(Vector2D.of(Math.PI, Double.NaN), Math.PI, Double.NaN); checkVector(Vector2D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY); } @Test public void testOf_arrayArg() { // act/assert checkVector(Vector2D.of(new double[] {0, 1}), 0, 1); checkVector(Vector2D.of(new double[] {-1, -2}), -1, -2); checkVector(Vector2D.of(new double[] {Math.PI, Double.NaN}), Math.PI, Double.NaN); checkVector(Vector2D.of(new double[] {Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY}), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY); } @Test(expected = IllegalArgumentException.class) public void testOf_arrayArg_invalidDimensions() { // act/assert Vector2D.of(new double[] {0.0}); } @Test public void testUnitFrom_coordinates() { // arrange double invSqrt2 = 1.0 / Math.sqrt(2.0); // act/assert checkVector(Vector2D.Unit.from(2.0, -2.0), invSqrt2, -invSqrt2); checkVector(Vector2D.Unit.from(-4.0, 4.0), -invSqrt2, invSqrt2); } @Test public void testUnitFrom_vector() { // arrange double invSqrt2 = 1.0 / Math.sqrt(2.0); Vector2D vec = Vector2D.of(2.0, -2.0); Vector2D.Unit unitVec = Vector2D.Unit.from(2.0, -2.0); // act/assert checkVector(Vector2D.Unit.from(vec), invSqrt2, -invSqrt2); Assert.assertSame(unitVec, Vector2D.Unit.from(unitVec)); } @Test public void testUnitFrom_illegalNorm() { GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(0.0, 0.0), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(Double.NaN, 1.0), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(1.0, Double.NEGATIVE_INFINITY), IllegalArgumentException.class); GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(1.0, Double.POSITIVE_INFINITY), IllegalArgumentException.class); } @Test public void testMax() { // act/assert EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-100, 1), Vector2D.max(Arrays.asList(Vector2D.of(-100, 1))), EPS); EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 1), Vector2D.max(Arrays.asList(Vector2D.of(-100, 1), Vector2D.of(0, 1))), EPS); EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 0), Vector2D.max(Vector2D.of(-2, 0), Vector2D.of(-1, -5), Vector2D.of(-10, -10)), EPS); } @Test public void testMax_noPointsGiven() { // arrange String msg = "Cannot compute vector max: no vectors given"; // act/assert GeometryTestUtils.assertThrows(() -> { Vector2D.max(new ArrayList<>()); }, IllegalArgumentException.class, msg); } @Test public void testMin() { // act/assert EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-100, 1), Vector2D.min(Arrays.asList(Vector2D.of(-100, 1))), EPS); EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-100, 1), Vector2D.min(Arrays.asList(Vector2D.of(-100, 1), Vector2D.of(0, 1))), EPS); EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-10, -10), Vector2D.min(Vector2D.of(-2, 0), Vector2D.of(-1, -5), Vector2D.of(-10, -10)), EPS); } @Test public void testMin_noPointsGiven() { // arrange String msg = "Cannot compute vector min: no vectors given"; // act/assert GeometryTestUtils.assertThrows(() -> { Vector2D.min(new ArrayList<>()); }, IllegalArgumentException.class, msg); } @Test public void testCentroid() { // act/assert EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2), Vector2D.centroid(Vector2D.of(1, 2)), EPS); EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2.5, 3.5), Vector2D.centroid(Vector2D.of(1, 2), Vector2D.of(2, 3), Vector2D.of(3, 4), Vector2D.of(4, 5)), EPS); EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2), Vector2D.centroid(Arrays.asList(Vector2D.of(1, 2))), EPS); EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 1), Vector2D.centroid(Arrays.asList(Vector2D.of(1, 2), Vector2D.of(1, 2), Vector2D.ZERO, Vector2D.ZERO)), EPS); } @Test public void testCentroid_noPointsGiven() { // arrange String msg = "Cannot compute centroid: no points given"; // act/assert GeometryTestUtils.assertThrows(() -> { Vector2D.centroid(new ArrayList<>()); }, IllegalArgumentException.class, msg); } @Test public void testLinearCombination1() { // arrange Vector2D p1 = Vector2D.of(1, 2); // act/assert checkVector(Vector2D.linearCombination(0, p1), 0, 0); checkVector(Vector2D.linearCombination(1, p1), 1, 2); checkVector(Vector2D.linearCombination(-1, p1), -1, -2); checkVector(Vector2D.linearCombination(0.5, p1), 0.5, 1); checkVector(Vector2D.linearCombination(-0.5, p1), -0.5, -1); } @Test public void testLinearCombination2() { // arrange Vector2D p1 = Vector2D.of(1, 2); Vector2D p2 = Vector2D.of(-3, -4); // act/assert checkVector(Vector2D.linearCombination(2, p1, -3, p2), 11, 16); checkVector(Vector2D.linearCombination(-3, p1, 2, p2), -9, -14); } @Test public void testLinearCombination3() { // arrange Vector2D p1 = Vector2D.of(1, 2); Vector2D p2 = Vector2D.of(-3, -4); Vector2D p3 = Vector2D.of(5, 6); // act/assert checkVector(Vector2D.linearCombination(2, p1, -3, p2, 4, p3), 31, 40); checkVector(Vector2D.linearCombination(-3, p1, 2, p2, -4, p3), -29, -38); } @Test public void testLinearCombination4() { // arrange Vector2D p1 = Vector2D.of(1, 2); Vector2D p2 = Vector2D.of(-3, -4); Vector2D p3 = Vector2D.of(5, 6); Vector2D p4 = Vector2D.of(-7, -8); // act/assert checkVector(Vector2D.linearCombination(2, p1, -3, p2, 4, p3, -5, p4), 66, 80); checkVector(Vector2D.linearCombination(-3, p1, 2, p2, -4, p3, 5, p4), -64, -78); } @Test public void testUnitFactoryOptimization() { // An already normalized vector will avoid unnecessary creation. final Vector2D v = Vector2D.of(4, 5).normalize(); Assert.assertSame(v, v.normalize()); } private void checkVector(Vector2D v, double x, double y) { checkVector(v, x, y, EPS); } private void checkVector(Vector2D v, double x, double y, double eps) { Assert.assertEquals(x, v.getX(), eps); Assert.assertEquals(y, v.getY(), eps); } }