/*
* 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.algs;
import georegression.geometry.UtilLine2D_F32;
import georegression.metric.Intersection2D_F32;
import georegression.struct.line.LineGeneral2D_F32;
import georegression.struct.line.LineSegment2D_F32;
import georegression.struct.point.Point2D_F32;
import georegression.struct.shapes.EllipseRotated_F32;
import static georegression.geometry.UtilEllipse_F32.tangentLines;
/**
* <p>
* Iterative algorithm for finding the 4 pairs of tangent lines between two ellipses. The ellipses are assumed
* to not intersect. Line 0 and line 3 will not intersect the line joining the center of the two
* ellipses while line 1 and 2 will.
* </p>
*
* Algorithm: While a closed form solution does exist, it is very complex and an iterative solution is used
* here instead.
* <ol>
* <li>Initialize by finding four lines which are approximately tangent. Two will cross the center line
* and two will not. See code for details</li>
* <li>For each end point on a line find the two tangent points on the other ellipse. Keep the line
* which either crosses or does not cross the center line.</li>
* <li>Repeat unit the location of each end points does not change significantly or the maximum number of
* iterations has been exceeded</li>
* </ol>
*
* @author Peter Abeles
*/
public class TangentLinesTwoEllipses_F32 {
// convergence parameters
private float convergenceTol;
private int maxIterations = 10;
// storage
private Point2D_F32 temp0 = new Point2D_F32();
private Point2D_F32 temp1 = new Point2D_F32();
// storage for the change in point positions
float sumDifference;
// true of the optimization converged before it ran out of iterations
private boolean converged;
LineSegment2D_F32 centerLine = new LineSegment2D_F32();
// storage for local workspace
LineSegment2D_F32 tempLine = new LineSegment2D_F32();
LineGeneral2D_F32 lineGeneral = new LineGeneral2D_F32();
Point2D_F32 junk = new Point2D_F32();
/**
* Constructor that configures optimization parameters
*
*
* @param convergenceTol Tolerance for when the iterations will stop. Try 1e-8 for floats and 1e-4 for floats
* @param maxIterations Maximum number of iterations
*/
public TangentLinesTwoEllipses_F32(float convergenceTol,
int maxIterations) {
this.convergenceTol = convergenceTol;
this.maxIterations = maxIterations;
}
/**
* <p>Selects 4 pairs of points. Each point in the pair represents an end point in a line segment which is tangent
* to both ellipseA and ellipseB. Both ellipses are assumed to not intersect each other. If a fatal error
* occurs the function will return false. However it can return true and did not converge. To check for
* convergence call {@link #isConverged()}.</p>
*
* <p>Line 0 and line 3 will not intersect the line joining the center of the two ellipses while line 1 and 2 will.</p>
*
* @param ellipseA (Input) An ellipse
* @param ellipseB (Input) An ellipse
* @param tangentA0 (Output) Tangent point on A for line segment 0
* @param tangentA1 (Output) Tangent point on A for line segment 1
* @param tangentA2 (Output) Tangent point on A for line segment 2
* @param tangentA3 (Output) Tangent point on A for line segment 3
* @param tangentB0 (Output) Tangent point on B for line segment 0
* @param tangentB1 (Output) Tangent point on B for line segment 1
* @param tangentB2 (Output) Tangent point on B for line segment 2
* @param tangentB3 (Output) Tangent point on B for line segment 3
* @return true if no fatal error, false if one happened.
*/
public boolean process(EllipseRotated_F32 ellipseA , EllipseRotated_F32 ellipseB ,
Point2D_F32 tangentA0 , Point2D_F32 tangentA1 ,
Point2D_F32 tangentA2 , Point2D_F32 tangentA3 ,
Point2D_F32 tangentB0 , Point2D_F32 tangentB1 ,
Point2D_F32 tangentB2 , Point2D_F32 tangentB3 )
{
converged = false;
// initialize by picking an arbitrary point on A and then finding the points on B in which
// a line is tangent to B and passes through the point on A
if (!initialize(ellipseA, ellipseB,
tangentA0, tangentA1, tangentA2, tangentA3,
tangentB0, tangentB1, tangentB2, tangentB3))
return false;
// update the location of each point until it converges or the maximum number of iterations has been exceeded
int iteration = 0;
for( ;iteration < maxIterations; iteration++ ) {
boolean allGood = false;
sumDifference = 0;
if( !selectTangent(tangentA0,tangentB0,ellipseB,tangentB0, false) )
return false;
if( !selectTangent(tangentA1,tangentB1,ellipseB,tangentB1, true) )
return false;
if( !selectTangent(tangentA2,tangentB2,ellipseB,tangentB2, true) )
return false;
if( !selectTangent(tangentA3,tangentB3,ellipseB,tangentB3, false) )
return false;
if( (float)Math.sqrt(sumDifference)/4.0f <= convergenceTol ) {
allGood = true;
}
sumDifference = 0;
if( !selectTangent(tangentB0,tangentA0,ellipseA,tangentA0, false) )
return false;
if( !selectTangent(tangentB1,tangentA1,ellipseA,tangentA1, true) )
return false;
if( !selectTangent(tangentB2,tangentA2,ellipseA,tangentA2, true) )
return false;
if( !selectTangent(tangentB3,tangentA3,ellipseA,tangentA3, false) )
return false;
if( allGood && (float)Math.sqrt(sumDifference)/4.0f <= convergenceTol ) {
break;
}
}
converged = iteration < maxIterations;
return true;
}
/**
* Select the initial tangent points on the ellipses. This is done by:
*
* 1) picking an arbitrary point on ellipseA.
* 2) Find tangent points on B using point from step 1
* 3) Use those two tangent points on B to find 4 points on ellipse A
* 4) Use those 4 points to select 4 points on B.
*/
boolean initialize(EllipseRotated_F32 ellipseA, EllipseRotated_F32 ellipseB,
Point2D_F32 tangentA0, Point2D_F32 tangentA1,
Point2D_F32 tangentA2, Point2D_F32 tangentA3,
Point2D_F32 tangentB0, Point2D_F32 tangentB1,
Point2D_F32 tangentB2, Point2D_F32 tangentB3) {
centerLine.set(ellipseA.center,ellipseB.center);
UtilLine2D_F32.convert(centerLine, lineGeneral);
Intersection2D_F32.intersection(lineGeneral, ellipseA, temp0, temp1, -1);
if (temp0.distance2(ellipseB.center) < temp1.distance2(ellipseB.center)) {
tangentA0.set(temp0);
} else {
tangentA0.set(temp1);
}
// Two seed points for B. This points will be on two different sides of center line
if( !tangentLines(tangentA0,ellipseB,tangentB0,tangentB1) )
return false;
// Find initial seed of 4 points on ellipse A. Careful which pairs of points cross or
// don't cross the center line
if( !selectTangent(tangentB0,tangentA0,ellipseA,tangentA0, false))
return false;
if( !selectTangent(tangentB0,tangentA0,ellipseA,tangentA1, true))
return false;
if( !selectTangent(tangentB1,tangentA0,ellipseA,tangentA2, true))
return false;
if( !selectTangent(tangentB1,tangentA0,ellipseA,tangentA3, false))
return false;
// not all of the B's have been initialized. That's ok. It will just have a large error
// the first iteration
return true;
}
/**
* Selects a tangent point on the ellipse which is closest to the original source point of A.
* @param a Point that the tangent lines pass through
* @param previousTangent Source point which generated 'a'
* @param ellipse Ellipse which the lines will be tangent to
* @param tangent (Output) Storage for the selected tangent point
* @return true if everything went well or false if finding tangent lines barfed
*/
boolean selectTangent( Point2D_F32 a , Point2D_F32 previousTangent ,
EllipseRotated_F32 ellipse, Point2D_F32 tangent ,
boolean cross )
{
if( !tangentLines(a,ellipse,temp0,temp1) )
return false;
tempLine.a = a;
tempLine.b = temp0;
boolean crossed0 = Intersection2D_F32.intersection(centerLine,tempLine,junk) != null;
tempLine.b = temp1;
boolean crossed1 = Intersection2D_F32.intersection(centerLine,tempLine,junk) != null;
if( crossed0 == crossed1 )
throw new RuntimeException("Well this didn't work");
if( cross == crossed0 ) {
sumDifference += previousTangent.distance2(temp0);
tangent.set(temp0);
} else {
sumDifference += previousTangent.distance2(temp1);
tangent.set(temp1);
}
return true;
}
public boolean isConverged() {
return converged;
}
public float getConvergenceTol() {
return convergenceTol;
}
public void setConvergenceTol(float convergenceTol) {
this.convergenceTol = convergenceTol;
}
public int getMaxIterations() {
return maxIterations;
}
public void setMaxIterations(int maxIterations) {
this.maxIterations = maxIterations;
}
}