兩圓的外切線與內切線的切點算法

最近想畫兩球水滴效果所有在網上找兩圓外切點和內切點的算法,找了很久沒有找到所以自己寫了一個工具類來計算兩圓的公切線點。具體效果如下圖:

根據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;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章