两圆的外切线与内切线的切点算法

最近想画两球水滴效果所有在网上找两圆外切点和内切点的算法,找了很久没有找到所以自己写了一个工具类来计算两圆的公切线点。具体效果如下图:

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

 

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