/* * Copyright (C) 2011-2016, Peter Abeles. All Rights Reserved. * * This file is part of Geometric Regression Library (GeoRegression). * * Licensed 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 georegression.geometry; import georegression.geometry.algs.TangentLinesTwoEllipses_F64; import georegression.metric.Intersection2D_F64; import georegression.metric.UtilAngle; import georegression.misc.GrlConstants; import georegression.struct.line.LineGeneral2D_F64; import georegression.struct.point.Point2D_F64; import georegression.struct.point.Vector2D_F64; import georegression.struct.shapes.EllipseQuadratic_F64; import georegression.struct.shapes.EllipseRotated_F64; import org.junit.Test; import java.util.Random; import static org.junit.Assert.*; /** * @author Peter Abeles */ public class TestUtilEllipse_F64 { Random rand = new Random(234); @Test public void convert_back_forth() { convert_back_forth(0,0,4.5,3,0); convert_back_forth(1,2,4.5,3,0); convert_back_forth(0,0,4.5,3,(double)Math.PI/4); convert_back_forth(0,0,4.5,3,0.1); convert_back_forth(-2,1.5,4.5,3,-0.1); convert_back_forth(1,2,3,1.5,0); convert_back_forth(1,2,3,1.5,1.5); // see if it can handle a circle convert_back_forth(0,0,3,3,0); } @Test public void convert_back_forth_random() { for( int i = 0; i < 100; i++ ) { double x = (rand.nextDouble()-0.5)*2; double y = (rand.nextDouble()-0.5)*2; double theta = (rand.nextDouble()-0.5)*(double)Math.PI; double b = rand.nextDouble()*2+0.1; double a = b+rand.nextDouble(); convert_back_forth(x,y,a,b,theta); } } public void convert_back_forth( double x0 , double y0, double a, double b, double phi ) { // should be scale invariant convert_back_forth(x0,y0,a,b,phi,1); convert_back_forth(x0,y0,a,b,phi,-1); } public void convert_back_forth( double x0 , double y0, double a, double b, double phi , double scale ) { EllipseRotated_F64 rotated = new EllipseRotated_F64(x0,y0,a,b,phi); EllipseQuadratic_F64 quad = new EllipseQuadratic_F64(); EllipseRotated_F64 found = new EllipseRotated_F64(); UtilEllipse_F64.convert(rotated,quad); quad.a *= scale; quad.b *= scale; quad.c *= scale; quad.d *= scale; quad.e *= scale; quad.f *= scale; UtilEllipse_F64.convert(quad,found); assertEquals(rotated.center.x,found.center.x, GrlConstants.DOUBLE_TEST_TOL); assertEquals(rotated.center.y,found.center.y, GrlConstants.DOUBLE_TEST_TOL); assertEquals(rotated.a,found.a, GrlConstants.DOUBLE_TEST_TOL); assertEquals(rotated.b,found.b, GrlConstants.DOUBLE_TEST_TOL); assertEquals(rotated.phi,found.phi, GrlConstants.DOUBLE_TEST_TOL); } @Test public void convert_rotated_to_quad() { EllipseRotated_F64 rotated = new EllipseRotated_F64(1,2,4.5,3,0.2); Point2D_F64 p = UtilEllipse_F64.computePoint(0.45,rotated,null); double eval = UtilEllipse_F64.evaluate(p.x,p.y,rotated); assertEquals(1,eval, GrlConstants.DOUBLE_TEST_TOL); EllipseQuadratic_F64 quad = new EllipseQuadratic_F64(); UtilEllipse_F64.convert(rotated,quad); eval = UtilEllipse_F64.evaluate(p.x,p.y,quad); assertEquals(0,eval, GrlConstants.DOUBLE_TEST_TOL); } /** * Tests computePoint and evaluate(rotated) by computes points around the ellipse and seeing if they * meet the expected results. */ @Test public void computePoint_evaluate_rotated() { EllipseRotated_F64 rotated = new EllipseRotated_F64(1,2,4.5,3,0.2); for( int i = 0; i < 100; i++ ) { double t = Math.PI*2*i/100.0; Point2D_F64 p = UtilEllipse_F64.computePoint(t,rotated,null); double eval = UtilEllipse_F64.evaluate(p.x,p.y,rotated); assertEquals(1,eval, GrlConstants.DOUBLE_TEST_TOL); } } @Test public void computePoint_evaluate_quadratic() { EllipseRotated_F64 rotated = new EllipseRotated_F64(1,2,4.5,3,0.2); EllipseQuadratic_F64 quad = new EllipseQuadratic_F64(); UtilEllipse_F64.convert(rotated,quad); for( int i = 0; i < 100; i++ ) { double t = GrlConstants.PI*2*i/100.0; Point2D_F64 p = UtilEllipse_F64.computePoint(t,rotated,null); double eval = UtilEllipse_F64.evaluate(p.x,p.y,quad); assertEquals(0,eval, GrlConstants.DOUBLE_TEST_TOL); } } /** * Try a few simple cases */ @Test public void computePoint_rotated() { EllipseRotated_F64 rotated = new EllipseRotated_F64(1,2,3,2,GrlConstants.PId2); Point2D_F64 p = UtilEllipse_F64.computePoint(0,rotated,null); assertEquals(1.0,p.x, GrlConstants.DOUBLE_TEST_TOL); assertEquals(2+3,p.y, GrlConstants.DOUBLE_TEST_TOL); p = UtilEllipse_F64.computePoint(GrlConstants.PId2,rotated,null); assertEquals(-1.0,p.x, GrlConstants.DOUBLE_TEST_TOL); assertEquals( 2.0,p.y, GrlConstants.DOUBLE_TEST_TOL); } @Test public void computeAngle() { EllipseRotated_F64 rotated = new EllipseRotated_F64(1,2,4.5,3,0.2); for( int i = 0; i <= 100; i++ ) { double t = GrlConstants.PI*2*i/100.0 - GrlConstants.PI; Point2D_F64 p = UtilEllipse_F64.computePoint(t,rotated,null); double found = UtilEllipse_F64.computeAngle(p,rotated); // System.out.println(t+" "+found); assertTrue(UtilAngle.dist(t, found) <= GrlConstants.DOUBLE_TEST_TOL); } } @Test public void computeTangent_rotated() { double delta = GrlConstants.DOUBLE_TEST_TOL; // axis aligned case EllipseRotated_F64 rotated = new EllipseRotated_F64(1,2,4.5,3,0); for (int i = 0; i < 20; i++) { double theta = i*GrlConstants.PI*2.0/20.0; checkTangent(theta,rotated,delta); } // rotated case rotated = new EllipseRotated_F64(1,2,4.5,3,0.4); for (int i = 0; i < 20; i++) { double theta = i*GrlConstants.PI*2.0/20.0; checkTangent(theta,rotated,delta); } } private void checkTangent( double t , EllipseRotated_F64 ellipse , double delta ) { Vector2D_F64 found = UtilEllipse_F64.computeTangent(t,ellipse,null); Vector2D_F64 expected = numericalTangent(t,ellipse,delta); double error0 = found.distance(expected); expected.x *= -1; expected.y *= -1; double error1 = found.distance(expected); double error = Math.min(error0,error1); assertEquals(0,error,Math.sqrt(delta)); } private Vector2D_F64 numericalTangent( double t , EllipseRotated_F64 ellipse , double delta ) { Point2D_F64 a = UtilEllipse_F64.computePoint(t-delta,ellipse,null); Point2D_F64 b = UtilEllipse_F64.computePoint(t+delta,ellipse,null); Vector2D_F64 output = new Vector2D_F64(); output.x = (b.x-a.x)/(2.0*delta); output.y = (b.y-a.y)/(2.0*delta); output.normalize(); return output; } @Test public void tangentLines_point_ellipse() { // simple case with a circle at the origin checkTangentLines( -2,2, new EllipseRotated_F64(0,0,2,2,0)); checkTangentLines( 0,-10, new EllipseRotated_F64(0,0,2,2,0)); checkTangentLines( -10,0, new EllipseRotated_F64(0,0,2,2,0)); checkTangentLines( -10,1, new EllipseRotated_F64(0,0,2,2,0)); checkTangentLines( 1,-10, new EllipseRotated_F64(0,0,2,2,0)); // test failure case. Inside checkTangentLinesFail( 0,0, new EllipseRotated_F64(0,0,2,2,0)); checkTangentLinesFail( 0.05,0, new EllipseRotated_F64(0,0,2,2,0)); checkTangentLinesFail( 0,0.1, new EllipseRotated_F64(0,0,2,2,0)); checkTangentLinesFail( 0.05,0.1, new EllipseRotated_F64(0,0,2,2,0)); // same, but not circular checkTangentLines( -2,2, new EllipseRotated_F64(0,0,2,3,0)); checkTangentLines( 0,-10, new EllipseRotated_F64(0,0,2,3,0)); checkTangentLines( -10,0, new EllipseRotated_F64(0,0,2,3,0)); checkTangentLines( -10,1, new EllipseRotated_F64(0,0,2,3,0)); checkTangentLines( 1,-10, new EllipseRotated_F64(0,0,2,3,0)); // same, but translated double x = 1.2, y = -0.5; checkTangentLines( -2+x , 2 +y, new EllipseRotated_F64(x,y,2,3,0)); checkTangentLines( 0+x , -10+y, new EllipseRotated_F64(x,y,2,3,0)); checkTangentLines( -10+x, 0 +y, new EllipseRotated_F64(x,y,2,3,0)); checkTangentLines( -10+x, 1 +y, new EllipseRotated_F64(x,y,2,3,0)); checkTangentLines( 1+x , -10+y, new EllipseRotated_F64(x,y,2,3,0)); // same, but translated and rotated checkTangentLines( -3+x , 3 +y, new EllipseRotated_F64(x,y,2,3,0.1)); checkTangentLines( 0+x , -10+y, new EllipseRotated_F64(x,y,2,3,0.1)); checkTangentLines( -10+x, 0 +y, new EllipseRotated_F64(x,y,2,3,0.1)); checkTangentLines( -10+x, 1 +y, new EllipseRotated_F64(x,y,2,3,0.1)); checkTangentLines( 1+x , -10+y, new EllipseRotated_F64(x,y,2,3,0.1)); } public void checkTangentLinesFail( double x, double y , EllipseRotated_F64 ellipse ) { Point2D_F64 pt = new Point2D_F64(x,y); Point2D_F64 pointA = new Point2D_F64(); Point2D_F64 pointB = new Point2D_F64(); assertFalse(UtilEllipse_F64.tangentLines(pt,ellipse,pointA,pointB)); } public void checkTangentLines( double x, double y , EllipseRotated_F64 ellipse ) { Point2D_F64 pt = new Point2D_F64(x,y); Point2D_F64 pointA = new Point2D_F64(); Point2D_F64 pointB = new Point2D_F64(); assertTrue(UtilEllipse_F64.tangentLines(pt,ellipse,pointA,pointB)); LineGeneral2D_F64 lineA = UtilLine2D_F64.convert(pt,pointA,(LineGeneral2D_F64)null); LineGeneral2D_F64 lineB = UtilLine2D_F64.convert(pt,pointB,(LineGeneral2D_F64)null); // the point should pass through both lines assertEquals(0, lineA.evaluate(pt.x,pt.y), GrlConstants.DOUBLE_TEST_TOL); assertEquals(0, lineB.evaluate(pt.x,pt.y), GrlConstants.DOUBLE_TEST_TOL); // if it's tangent there should only be one point of intersection Point2D_F64 pA = new Point2D_F64(); Point2D_F64 pB = new Point2D_F64(); assertTrue( 0 < Intersection2D_F64.intersection(lineA,ellipse,pA,pB, GrlConstants.DOUBLE_TEST_TOL)); assertEquals(0,pA.distance(pB) , Math.sqrt(GrlConstants.DOUBLE_TEST_TOL) ); assertTrue( 0 < Intersection2D_F64.intersection(lineB,ellipse,pA,pB, GrlConstants.DOUBLE_TEST_TOL)); assertEquals(0,pA.distance(pB) , Math.sqrt(GrlConstants.DOUBLE_TEST_TOL*20.0) ); // Make sure the lines are not identical boolean idential = true; idential &= Math.abs( lineA.A - lineB.A ) <= GrlConstants.DOUBLE_TEST_TOL; idential &= Math.abs( lineA.B - lineB.B ) <= GrlConstants.DOUBLE_TEST_TOL; idential &= Math.abs( lineA.C - lineB.C ) <= GrlConstants.DOUBLE_TEST_TOL; assertFalse( idential ); } /** * Very basic unit test. The more rigerous one is in * {@link georegression.geometry.algs.TestTangentLinesTwoEllipses_F64} */ @Test public void tangentLines_ellipse_ellipse() { EllipseRotated_F64 ellipseA = new EllipseRotated_F64(0,1,4,2,0.1); EllipseRotated_F64 ellipseB = new EllipseRotated_F64(-6,1.2,1.5,0.8,-0.6); Point2D_F64 tangentA0 = new Point2D_F64(); Point2D_F64 tangentA1 = new Point2D_F64(); Point2D_F64 tangentA2 = new Point2D_F64(); Point2D_F64 tangentA3 = new Point2D_F64(); Point2D_F64 tangentB0 = new Point2D_F64(); Point2D_F64 tangentB1 = new Point2D_F64(); Point2D_F64 tangentB2 = new Point2D_F64(); Point2D_F64 tangentB3 = new Point2D_F64(); UtilEllipse_F64.tangentLines(ellipseA,ellipseB, tangentA0,tangentA1,tangentA2,tangentA3, tangentB0,tangentB1,tangentB2,tangentB3); Point2D_F64 fooA0 = new Point2D_F64(); Point2D_F64 fooA1 = new Point2D_F64(); Point2D_F64 fooA2 = new Point2D_F64(); Point2D_F64 fooA3 = new Point2D_F64(); Point2D_F64 fooB0 = new Point2D_F64(); Point2D_F64 fooB1 = new Point2D_F64(); Point2D_F64 fooB2 = new Point2D_F64(); Point2D_F64 fooB3 = new Point2D_F64(); // see if it produces the same results as invoking the algorithm directly TangentLinesTwoEllipses_F64 alg = new TangentLinesTwoEllipses_F64(GrlConstants.DOUBLE_TEST_TOL,10); alg.process(ellipseA,ellipseB, fooA0,fooA1,fooA2,fooA3, fooB0,fooB1,fooB2,fooB3); assertEquals( 0, fooA0.distance(tangentA0), GrlConstants.DOUBLE_TEST_TOL); assertEquals( 0, fooA1.distance(tangentA1), GrlConstants.DOUBLE_TEST_TOL); assertEquals( 0, fooA2.distance(tangentA2), GrlConstants.DOUBLE_TEST_TOL); assertEquals( 0, fooA3.distance(tangentA3), GrlConstants.DOUBLE_TEST_TOL); assertEquals( 0, fooB0.distance(tangentB0), GrlConstants.DOUBLE_TEST_TOL); assertEquals( 0, fooB1.distance(tangentB1), GrlConstants.DOUBLE_TEST_TOL); assertEquals( 0, fooB2.distance(tangentB2), GrlConstants.DOUBLE_TEST_TOL); assertEquals( 0, fooB3.distance(tangentB3), GrlConstants.DOUBLE_TEST_TOL); } }