diff --git a/src/changes/changes.xml b/src/changes/changes.xml index fdbd35d21..a070b71f2 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -51,6 +51,9 @@ If the output is not quite correct, check for invisible trailing spaces! + + Added Emo Welzl algorithm to find the smallest enclosing ball of a collection of points. + Fixed an indexing problem in "BicubicSplineInterpolatingFunction" which resulted in wrong interpolations. diff --git a/src/main/java/org/apache/commons/math3/geometry/enclosing/Encloser.java b/src/main/java/org/apache/commons/math3/geometry/enclosing/Encloser.java new file mode 100644 index 000000000..4e9270430 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/geometry/enclosing/Encloser.java @@ -0,0 +1,39 @@ +/* + * 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.math3.geometry.enclosing; + +import java.util.List; + +import org.apache.commons.math3.geometry.Point; +import org.apache.commons.math3.geometry.Space; + +/** Interface for algorithms computing enclosing balls. + * @param Space type. + * @param

Point type. + * @version $Id$ + * @see EnclosingBall + * @since 3.3 + */ +public interface Encloser> { + + /** Find a ball enclosing a list of points. + * @param points points to enclose + * @return enclosing ball + */ + EnclosingBall enclose(List

points); + +} diff --git a/src/main/java/org/apache/commons/math3/geometry/enclosing/EnclosingBall.java b/src/main/java/org/apache/commons/math3/geometry/enclosing/EnclosingBall.java new file mode 100644 index 000000000..29a9d6657 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/geometry/enclosing/EnclosingBall.java @@ -0,0 +1,104 @@ +/* + * 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.math3.geometry.enclosing; + +import java.io.Serializable; + +import org.apache.commons.math3.geometry.Point; +import org.apache.commons.math3.geometry.Space; + +/** This class represents a ball enclosing some points. + * @param Space type. + * @param

Point type. + * @version $Id$ + * @see Space + * @see Point + * @see Encloser + * @since 3.3 + */ +public class EnclosingBall> implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 20140126L; + + /** Center of the ball. */ + private final P center; + + /** Radius of the ball. */ + private final double radius; + + /** Support points used to define the ball. */ + private final P[] support; + + /** Simple constructor. + * @param center center of the ball + * @param radius radius of the ball + * @param support support points used to define the ball + */ + public EnclosingBall(final P center, final double radius, final P ... support) { + this.center = center; + this.radius = radius; + this.support = support.clone(); + } + + /** Get the center of the ball. + * @return center of the ball + */ + public P getCenter() { + return center; + } + + /** Get the radius of the ball. + * @return radius of the ball (can be negative if the ball is empty) + */ + public double getRadius() { + return radius; + } + + /** Get the support points used to define the ball. + * @return support points used to define the ball + */ + public P[] getSupport() { + return support.clone(); + } + + /** Get the number of support points used to define the ball. + * @return number of support points used to define the ball + */ + public int getSupportSize() { + return support.length; + } + + /** Check if a point is within the ball or at boundary. + * @param point point to test + * @return true if the point is within the ball or at boundary + */ + public boolean contains(final P point) { + return point.distance(center) <= radius; + } + + /** Check if a point is within an enlarged ball or at boundary. + * @param point point to test + * @param margin margin to consider + * @return true if the point is within the ball enlarged + * by the margin or at boundary + */ + public boolean contains(final P point, final double margin) { + return point.distance(center) <= radius + margin; + } + +} diff --git a/src/main/java/org/apache/commons/math3/geometry/enclosing/SupportBallGenerator.java b/src/main/java/org/apache/commons/math3/geometry/enclosing/SupportBallGenerator.java new file mode 100644 index 000000000..064acb197 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/geometry/enclosing/SupportBallGenerator.java @@ -0,0 +1,43 @@ +/* + * 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.math3.geometry.enclosing; + +import java.util.List; + +import org.apache.commons.math3.geometry.Point; +import org.apache.commons.math3.geometry.Space; + +/** Interface for generating balls based on support points. + *

+ * This generator is used in the {@link WelzlEncloser Emo Welzl} algorithm + * and its derivatives. + *

+ * @param Space type. + * @param

Point type. + * @version $Id$ + * @see EnclosingBall + * @since 3.3 + */ +public interface SupportBallGenerator> { + + /** Create a ball whose boundary lies on prescribed support points. + * @param support support points (may be empty) + * @return ball whose boundary lies on the prescribed support points + */ + EnclosingBall ballOnSupport(List

support); + +} diff --git a/src/main/java/org/apache/commons/math3/geometry/enclosing/WelzlEncloser.java b/src/main/java/org/apache/commons/math3/geometry/enclosing/WelzlEncloser.java new file mode 100644 index 000000000..ac26151ea --- /dev/null +++ b/src/main/java/org/apache/commons/math3/geometry/enclosing/WelzlEncloser.java @@ -0,0 +1,177 @@ +/* + * 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.math3.geometry.enclosing; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.math3.geometry.Point; +import org.apache.commons.math3.geometry.Space; + +/** Class implementing Emo Welzl algorithm to find the smallest enclosing ball in linear time. + *

+ * The class implements the algorithm described in paper Smallest + * Enclosing Disks (Balls and Ellipsoids) by Emo Welzl, Lecture Notes in Computer Science + * 555 (1991) 359-370. The pivoting improvement published in the paper Fast and + * Robust Smallest Enclosing Balls, by Bernd Gärtner and further modified in + * paper + * Efficient Computation of Smallest Enclosing Balls in Three Dimensions by Linus Källberg + * to avoid performing local copies of data have been included. + *

+ * @param Space type. + * @param

Point type. + * @version $Id$ + * @since 3.3 + */ +public class WelzlEncloser> implements Encloser { + + /** Tolerance below which points are consider to be identical. */ + private final double tolerance; + + /** Maximum number of points to define a ball. */ + private final int max; + + /** Generator for balls on support. */ + private final SupportBallGenerator generator; + + /** Simple constructor. + * @param tolerance below which points are consider to be identical + * @param dimension dimension of the space + * @param generator generator for balls on support + */ + protected WelzlEncloser(final double tolerance, final int dimension, + final SupportBallGenerator generator) { + this.tolerance = tolerance; + this.max = dimension + 1; + this.generator = generator; + } + + /** {@inheritDoc} */ + public EnclosingBall enclose(final List

points) { + + if (points == null || points.isEmpty()) { + // return an empty ball + return generator.ballOnSupport(new ArrayList

()); + } + + // Emo Welzl algorithm with Bernd Gärtner and Linus Källberg improvements + return pivotingBall(points); + + } + + /** Compute enclosing ball using Gärtner's pivoting heuristic. + * @param points points to be enclosed + * @return enclosing ball + */ + private EnclosingBall pivotingBall(final List

points) { + + List

extreme = new ArrayList

(max); + List

support = new ArrayList

(max); + + // start with only first point selected as a candidate support + extreme.add(points.get(0)); + EnclosingBall ball = moveToFrontBall(extreme, support); + + while (true) { + + // select the point farthest to current ball + final P farthest = selectFarthest(points, ball); + if (ball.contains(farthest, tolerance)) { + // we have found a ball containing all points + return ball; + } + + // recurse search, restricted to the small subset containing support and farthest point + support.clear(); + support.add(farthest); + ball = moveToFrontBall(extreme, support); + + // it was an interesting point, move it to the front + // according to Gärtner's heuristic + extreme.add(0, farthest); + + // prune the least interesting points + extreme.subList(ball.getSupportSize(), extreme.size()).clear(); + + + } + } + + /** Compute enclosing ball using Welzl's move to front heuristic. + * @param extreme subset of extreme points + * @param support points that must belong to the ball support + * @return enclosing ball, for the extreme subset only + */ + private EnclosingBall moveToFrontBall(final List

extreme, final List

support) { + + // create a new ball on the prescribed support + EnclosingBall ball = generator.ballOnSupport(support); + + if (ball.getSupportSize() < max) { + + for (int i = 0; i < extreme.size(); ++i) { + final P pi = extreme.get(i); + if (!ball.contains(pi, tolerance)) { + + // we have found an outside point, + // enlarge the ball by adding it to the support + support.add(pi); + ball = moveToFrontBall(extreme.subList(i + 1, extreme.size()), support); + + // it was an interesting point, move it to the front + // according to Welzl's heuristic + for (int j = i; j > 1; --j) { + extreme.set(j, extreme.get(j - 1)); + } + extreme.set(0, pi); + + } + } + + } + + return ball; + + } + + /** Select the point farthest to the current ball. + * @param points points to be enclosed + * @param ball current ball + * @return farthest point + */ + public P selectFarthest(final List

points, final EnclosingBall ball) { + + final P center = ball.getCenter(); + P farthest = null; + double dMax = -1.0; + + for (final P point : points) { + final double d = point.distance(center); + if (d > dMax) { + farthest = point; + dMax = d; + } + } + + return farthest; + + } + +} diff --git a/src/main/java/org/apache/commons/math3/geometry/enclosing/package-info.java b/src/main/java/org/apache/commons/math3/geometry/enclosing/package-info.java new file mode 100644 index 000000000..32c5ef3ad --- /dev/null +++ b/src/main/java/org/apache/commons/math3/geometry/enclosing/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ +/** + * + *

+ * This package provides interfaces and classes related to the smallest enclosing ball roblem. + *

+ * + */ +package org.apache.commons.math3.geometry.enclosing; diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/BallGenerator.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/BallGenerator.java new file mode 100644 index 000000000..cd8d78c3e --- /dev/null +++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/BallGenerator.java @@ -0,0 +1,69 @@ +/* + * 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.math3.geometry.euclidean.twod; + +import java.util.List; + +import org.apache.commons.math3.geometry.enclosing.EnclosingBall; +import org.apache.commons.math3.geometry.enclosing.SupportBallGenerator; +import org.apache.commons.math3.util.MathArrays; + +/** Class implementing Emo Welzl algorithm to find the smallest enclosing ball in linear time. + * @version $Id$ + * @since 3.3 + */ +public class BallGenerator implements SupportBallGenerator { + + /** {@inheritDoc} */ + public EnclosingBall ballOnSupport(final List support) { + + if (support.size() < 1) { + return new EnclosingBall(Vector2D.ZERO, -1.0); + } else { + final Vector2D vA = support.get(0); + if (support.size() < 2) { + return new EnclosingBall(vA, 0, vA); + } else { + final Vector2D vB = support.get(1); + if (support.size() < 3) { + return new EnclosingBall(new Vector2D(0.5, vA, 0.5, vB), + 0.5 * vA.distance(vB), + vA, vB); + } else { + final Vector2D vC = support.get(2); + final Vector2D bc = vB.subtract(vC); + final Vector2D ca = vC.subtract(vA); + final Vector2D ab = vA.subtract(vB); + final double vA2 = vA.getNormSq(); + final double vB2 = vB.getNormSq(); + final double vC2 = vC.getNormSq(); + final double d = 2 * MathArrays.linearCombination(vA.getX(), bc.getY(), + vB.getX(), ca.getY(), + vC.getX(), ab.getY()); + final Vector2D center = new Vector2D( MathArrays.linearCombination(vA2, bc.getY(), + vB2, ca.getY(), + vC2, ab.getY()) / d, + -MathArrays.linearCombination(vA2, bc.getX(), + vB2, ca.getX(), + vC2, ab.getX()) / d); + return new EnclosingBall(center, center.distance(vA), vA, vB, vC); + } + } + } + } + +} diff --git a/src/test/java/org/apache/commons/math3/geometry/enclosing/WelzlEncloserTest.java b/src/test/java/org/apache/commons/math3/geometry/enclosing/WelzlEncloserTest.java new file mode 100644 index 000000000..cb78d0415 --- /dev/null +++ b/src/test/java/org/apache/commons/math3/geometry/enclosing/WelzlEncloserTest.java @@ -0,0 +1,158 @@ +/* + * 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.math3.geometry.enclosing; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.math3.geometry.euclidean.twod.BallGenerator; +import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; +import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.random.Well1024a; +import org.junit.Assert; +import org.junit.Test; + + +public class WelzlEncloserTest { + + @Test + public void testNullList() { + BallGenerator generator = new BallGenerator(); + WelzlEncloser encloser = + new WelzlEncloser(1.0e-10, 2, generator); + EnclosingBall ball = encloser.enclose(null); + Assert.assertTrue(ball.getRadius() < 0); + } + + @Test + public void testNoPoints() { + BallGenerator generator = new BallGenerator(); + WelzlEncloser encloser = + new WelzlEncloser(1.0e-10, 2, generator); + EnclosingBall ball = encloser.enclose(new ArrayList()); + Assert.assertTrue(ball.getRadius() < 0); + } + + @Test + public void testRegularPoints() { + List list = buildList(22, 26, 30, 38, 64, 28, 8, 54, 11, 15); + checkBall(list, Arrays.asList(list.get(2), list.get(3), list.get(4))); + } + + @Test + public void testSolutionOnDiameter() { + List list = buildList(22, 26, 30, 38, 64, 28, 8, 54); + checkBall(list, Arrays.asList(list.get(2), list.get(3))); + } + + @Test + public void testLargeSamples() { + RandomGenerator random = new Well1024a(0xa2a63cad12c01fb2l); + for (int k = 0; k < 100; ++k) { + int nbPoints = random.nextInt(10000); + List points = new ArrayList(); + for (int i = 0; i < nbPoints; ++i) { + double x = random.nextDouble(); + double y = random.nextDouble(); + points.add(new Vector2D(x, y)); + } + checkBall(points); + } + } + + private List buildList(final double ... coordinates) { + List list = new ArrayList(coordinates.length / 2); + for (int i = 0; i < coordinates.length; i += 2) { + list.add(new Vector2D(coordinates[i], coordinates[i + 1])); + } + return list; + } + + private void checkBall(List points, List refSupport) { + + EnclosingBall ball = checkBall(points); + + // compare computed ball with expected ball + BallGenerator generator = new BallGenerator(); + EnclosingBall expected = generator.ballOnSupport(refSupport); + Assert.assertEquals(refSupport.size(), ball.getSupportSize()); + Assert.assertEquals(expected.getRadius(), ball.getRadius(), 1.0e-10); + Assert.assertEquals(expected.getCenter().getX(), ball.getCenter().getX(), 1.0e-10); + Assert.assertEquals(expected.getCenter().getY(), ball.getCenter().getY(), 1.0e-10); + + for (Vector2D s : ball.getSupport()) { + boolean found = false; + for (Vector2D rs : refSupport) { + if (s == rs) { + found = true; + } + } + Assert.assertTrue(found); + } + + // check removing any point of the support ball fails to enclose the point + for (int i = 0; i < ball.getSupportSize(); ++i) { + List reducedSupport = new ArrayList(); + int count = 0; + for (Vector2D s : ball.getSupport()) { + if (count++ != i) { + reducedSupport.add(s); + } + } + EnclosingBall reducedBall = generator.ballOnSupport(reducedSupport); + boolean foundOutside = false; + for (int j = 0; j < points.size() && !foundOutside; ++j) { + if (!reducedBall.contains(points.get(j), 1.0e-10)) { + foundOutside = true; + } + } + Assert.assertTrue(foundOutside); + } + + } + + private EnclosingBall checkBall(List points) { + + WelzlEncloser encloser = + new WelzlEncloser(1.0e-10, 2, new BallGenerator()); + EnclosingBall ball = encloser.enclose(points); + + // all points are enclosed + for (Vector2D v : points) { + Assert.assertTrue(ball.contains(v, 1.0e-10)); + } + + for (Vector2D v : points) { + boolean inSupport = false; + for (Vector2D s : ball.getSupport()) { + if (v == s) { + inSupport = true; + } + } + if (inSupport) { + // points on the support should be outside of reduced ball + Assert.assertFalse(ball.contains(v, -0.001)); + } + } + + return ball; + + } + +} diff --git a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/BallGeneratorTest.java b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/BallGeneratorTest.java new file mode 100644 index 000000000..688ad6e82 --- /dev/null +++ b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/BallGeneratorTest.java @@ -0,0 +1,96 @@ +/* + * 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.math3.geometry.euclidean.twod; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.math3.geometry.enclosing.EnclosingBall; +import org.junit.Assert; +import org.junit.Test; + + +public class BallGeneratorTest { + + @Test + public void testSupport0Point() { + List support = Arrays.asList(new Vector2D[0]); + EnclosingBall ball = new BallGenerator().ballOnSupport(support); + Assert.assertTrue(ball.getRadius() < 0); + Assert.assertEquals(0, ball.getSupportSize()); + Assert.assertEquals(0, ball.getSupport().length); + } + + @Test + public void testSupport1Point() { + List support = Arrays.asList(new Vector2D(1, 2)); + EnclosingBall ball = new BallGenerator().ballOnSupport(support); + Assert.assertEquals(0.0, ball.getRadius(), 1.0e-10); + Assert.assertTrue(ball.contains(support.get(0))); + Assert.assertTrue(ball.contains(support.get(0), 0.5)); + Assert.assertFalse(ball.contains(new Vector2D(support.get(0).getX() + 0.1, + support.get(0).getY() - 0.1), + 0.001)); + Assert.assertTrue(ball.contains(new Vector2D(support.get(0).getX() + 0.1, + support.get(0).getY() - 0.1), + 0.5)); + Assert.assertEquals(0, support.get(0).distance(ball.getCenter()), 1.0e-10); + Assert.assertEquals(1, ball.getSupportSize()); + Assert.assertTrue(support.get(0) == ball.getSupport()[0]); + } + + @Test + public void testSupport2Points() { + List support = Arrays.asList(new Vector2D(1, 0), + new Vector2D(3, 0)); + EnclosingBall ball = new BallGenerator().ballOnSupport(support); + Assert.assertEquals(1.0, ball.getRadius(), 1.0e-10); + int i = 0; + for (Vector2D v : support) { + Assert.assertTrue(ball.contains(v)); + Assert.assertEquals(1.0, v.distance(ball.getCenter()), 1.0e-10); + Assert.assertTrue(v == ball.getSupport()[i++]); + } + Assert.assertTrue(ball.contains(new Vector2D(2, 0.9))); + Assert.assertFalse(ball.contains(Vector2D.ZERO)); + Assert.assertEquals(0.0, new Vector2D(2, 0).distance(ball.getCenter()), 1.0e-10); + Assert.assertEquals(2, ball.getSupportSize()); + } + + @Test + public void testSupport3Points() { + List support = Arrays.asList(new Vector2D(1, 0), + new Vector2D(3, 0), + new Vector2D(2, 2)); + EnclosingBall ball = new BallGenerator().ballOnSupport(support); + Assert.assertEquals(5.0 / 4.0, ball.getRadius(), 1.0e-10); + int i = 0; + for (Vector2D v : support) { + Assert.assertTrue(ball.contains(v)); + Assert.assertEquals(5.0 / 4.0, v.distance(ball.getCenter()), 1.0e-10); + Assert.assertTrue(v == ball.getSupport()[i++]); + } + Assert.assertTrue(ball.contains(new Vector2D(2, 0.9))); + Assert.assertFalse(ball.contains(new Vector2D(0.9, 0))); + Assert.assertFalse(ball.contains(new Vector2D(3.1, 0))); + Assert.assertTrue(ball.contains(new Vector2D(2.0, -0.499))); + Assert.assertFalse(ball.contains(new Vector2D(2.0, -0.501))); + Assert.assertEquals(0.0, new Vector2D(2.0, 3.0 / 4.0).distance(ball.getCenter()), 1.0e-10); + Assert.assertEquals(3, ball.getSupportSize()); + } + +}