最近想畫兩球水滴效果所有在網上找兩圓外切點和內切點的算法,找了很久沒有找到所以自己寫了一個工具類來計算兩圓的公切線點。具體效果如下圖:
根據CircleUtils類的getCircleTangentPointOut方法返回外切點座標[r1p1, r1p2, r2p1, r2p2],依次爲左邊圓的兩個切點座標和右邊兩個切點座標。
根據CircleUtils類的getCircleTangentPointIn方法返回外切點座標[r1p1, r1p2, r2p1, r2p2],依次爲左邊圓的兩個切點座標和右邊兩個切點座標。
根據獲取座標繪製線段和點,如下代碼
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(0XFF00FF00);
mPaint.setStyle(Paint.Style.FILL);
float r1 = 300;
float r2 = 200;
canvas.drawCircle(mDownX, mDownY, r1, mPaint);
canvas.drawCircle(mCurrentX, mCurrentY, r2, mPaint);
PointF[] points2 = CircleUtils.getCircleTangentPointOut(new PointF(mDownX, mDownY), r1, new PointF(mCurrentX, mCurrentY), r2);
if (points2 != null) {
mPaint.setColor(0XFF0000FF);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawLine(points2[0].x, points2[0].y, points2[2].x, points2[2].y, mPaint);
canvas.drawLine(points2[1].x, points2[1].y, points2[3].x, points2[3].y, mPaint);
}
PointF[] points1 = CircleUtils.getCircleTangentPointIn(new PointF(mDownX, mDownY), r1, new PointF(mCurrentX, mCurrentY), r2);
if (points1 != null) {
mPaint.setColor(0XFF0000FF);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawLine(points1[0].x, points1[0].y, points1[3].x, points1[3].y, mPaint);
canvas.drawLine(points1[1].x, points1[1].y, points1[2].x, points1[2].y, mPaint);
}
if (points2 != null) {
mPaint.setColor(0XFFFF0000);
mPaint.setStyle(Paint.Style.FILL);
for (PointF p : points2) {
canvas.drawCircle(p.x, p.y, 5, mPaint);
}
}
if (points1 != null) {
mPaint.setColor(0XFFFF0000);
mPaint.setStyle(Paint.Style.FILL);
for (PointF p : points1) {
canvas.drawCircle(p.x, p.y, 5, mPaint);
}
}
}
CircleUtils工具類如下代碼
public class CircleUtils {
//返回兩圓外切點座標
public static PointF[] getCircleTangentPointOut(PointF c1, float r1, PointF c2, float r2){
float centerLine = getPointDistance(c1, c2);
if(centerLine > Math.abs(r1-r2)){
//計算外切
PointF[] points = new PointF[8];
//圓心連線與圓1的交點
PointF r1Point = ratioPoint(c1, c2, r1/centerLine);
points[6] = r1Point;
//圓心連線與圓2的交點
PointF r2Point = ratioPoint(c1, c2, (centerLine-r2)/centerLine);
points[7] = r2Point;
//兩元交點連線和兩圓焦點在左邊圓的角度
float angleR1 = getAngle(r1, centerLine, r2);
//兩元交點連線和兩圓焦點在右邊圓的角度
float angleR2 = getAngle(r2, centerLine, r1);
//外切線與圓心連線的角度(0~90度之間的角度)
float angle = (float) Math.acos(Math.abs(r1-r2)/centerLine);
//兩圓的交點
points[4] = rotatePoint(r1Point, c1, angleR1);
points[5] = rotatePoint(r2Point, c2, angleR2);
if(r1>=r2){
//切線與第一個圓的交點
points[0] = rotatePoint(r1Point, c1, angle);
points[1] = rotatePoint(r1Point, c1, -angle);
//切線與第二個圓的交點
points[2] = rotatePoint(r2Point, c2, -(float) (Math.PI-angle));
points[3] = rotatePoint(r2Point, c2, (float) (Math.PI-angle));
}else{
//切線與第一個圓的交點
points[0] = rotatePoint(r1Point, c1, (float) (Math.PI-angle));
points[1] = rotatePoint(r1Point, c1, -(float) (Math.PI-angle));
//切線與第二個圓的交點
points[2] = rotatePoint(r2Point, c2, -angle);
points[3] = rotatePoint(r2Point, c2, angle);
}
return points;
}
return null;
}
//返回兩圓內切點座標
public static PointF[] getCircleTangentPointIn(PointF c1, float r1, PointF c2, float r2){
float centerLine = getPointDistance(c1, c2);
if(centerLine > r1+r2){
//計算內切
PointF[] points = new PointF[7];
//內切線焦點
points[4] = new PointF((c1.x*r2+c2.x*r1)/(r1+r2), (c1.y*r2+c2.y*r1)/(r1+r2));
float l1 = centerLine*r1/(r1+r2);
float l2 = centerLine*r2/(r1+r2);
//圓心連線與圓1的交點
points[5] = ratioPoint(c1, points[4], r1/l1);
float angle = (float) Math.acos(r1/l1);
//第1個圓的切點
points[0] = rotatePoint(points[5], c1, angle);
points[1] = rotatePoint(points[5], c1, -angle);
//圓心連線與圓2的交點
points[6] = ratioPoint(points[4], c2, (l2-r2)/l2);
//第2個圓的切點
points[2] = rotatePoint(points[6], c2, -angle);
points[3] = rotatePoint(points[6], c2, angle);
return points;
}
return null;
}
//根據點 a, b, c位置距離爲ab, bc, ac獲取b點在ac上的垂點d,返回垂點d
public static PointF getVerticalPoint( PointF a, PointF b, PointF c){
float ab =getPointDistance(a, b);
float ac =getPointDistance(a, c);
float bc =getPointDistance(b, c);
return getVerticalPoint(ab, ac, bc, a, c);
}
public static PointF getVerticalPoint(float ab,float ac, float bc, PointF a, PointF c){
float angle = getAngle(ab, ac, bc);
float ratio = (float) (Math.cos(angle)*ab)/ac;
return ratioPoint(a, c, ratio);
}
//返回兩點之間的距離
public static float getPointDistance(PointF a, PointF b){
return (float) Math.sqrt(Math.pow(a.x-b.x,2)+Math.pow(a.y-b.y, 2));
}
//根據點 a, b, c位置距離爲ab, bc, ac獲取a點角度
public static float getAngle(float ab,float ac, float bc){
return (float) Math.acos((ab*ab+ac*ac-bc*bc)/(2*ab*ac));
}
//獲取一個點,起始點到該點長度除以起始點到結束點長度的比例爲ratio
public static PointF ratioPoint(PointF startPoint, PointF endPoint, float ratio){
if(startPoint == null){
startPoint = new PointF(0, 0);
}
PointF ret = new PointF();
float x = endPoint.x-startPoint.x;
float y = endPoint.y-startPoint.y;
ret.x = x*ratio+startPoint.x;
ret.y = y*ratio+startPoint.y;
return ret;
}
//空間一個點圍繞center點旋轉angle角度後的位置
public static PointF rotatePoint(PointF point, PointF center, float angle){
if(center == null){
center = new PointF(0, 0);
}
PointF ret = new PointF();
//獲取相對位置
float x = point.x-center.x;
float y = point.y-center.y;
//根據選擇矩陣旋轉後加上中心點位置
ret.x = (float) ((x*Math.cos(angle)-y*Math.sin(angle))+center.x);
ret.y = (float) ((x*Math.sin(angle)+y*Math.cos(angle))+center.y);
return ret;
}
}