• 【计算几何模板】求两个圆的交点


    问题描述:

    给两个相交的圆,第一个圆的圆心为((x_1, \, y_1)),半径为(r_1),第二个圆的圆心为((x_2, \, y_2)),半径为(r_2),求两个圆的交点。

    问题分析:

    《训练指南》上求两圆交点的模板用了atan2acos等库函数,精度损失比较严重。
    下面介绍一种精度损失较小的做法:
    原文地址

    首先回顾一下圆的两种表示方法:

    • 圆的标准方程:((x-x_0)^2+(y-y_0)^2=r^2)
    • 圆的参数方程:(left{egin{matrix} x=x_0+rcdot cos heta \ y=y_0+rcdot sin heta end{matrix} ight.)

    将第一个圆的参数方程带入第二个圆的标准方程:
    ((x_1+r_1cos heta-x_2)^2+(y_1+r_1sin heta-y_2)^2=r_2^2)
    展开后得到:
    (2r_1(x_1-x_2)cos heta+2r_1(y_1-y_2)sin heta=r_2^2-r_1^2-(x_1-x_2)^2-(y_1-y_2)^2)
    令:
    (a=2r_1(x_1-x_2))
    (b=2r_1(y_1-y_2))
    (c=r_2^2-r_1^2-(x_1-x_2)^2-(y_1-y_2)^2)
    原式变为:
    (a cos heta+b sin heta=c)
    (cos heta=x, \, sin heta=sqrt{1-x^2}),关于(sin heta)的正负后面再判断。
    代入方程,得到,(ax+bsqrt{1-x^2}=c)
    移项再两边平方,((ax-c)^2=b^2(1-x^2))
    整理得,((a^2+b^2)x^2-2acx+c^2-b^2=0)
    下面就是解一元二次方程了。

    (sin heta)(cos heta)代回到第一个圆的参数方程,能得到交点的坐标。
    如果该点不在第二个圆上,说明(sin heta)是个负数,还需要对这个交点稍作调整。
    还有一种特殊情况就是,如果已经确定有两个不同的交点,但解出来的(cos heta)值只有一个。
    说明对应的(sin heta)值必然一正一负。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    #include <vector>
    
    typedef long double LD;
    
    const LD eps = 1e-10;
    
    int dcmp(LD x) {
    	if(fabs(x) < eps) return 0;
    	return x < 0 ? -1 : 1;
    }
    
    LD sqr(LD x) { return x * x; }
    
    struct Point
    {
    	LD x, y;
    	Point(LD x = 0, LD y = 0):x(x), y(y) {}
    	void read() { cin >> x >> y; }
    };
    
    Point operator - (const Point& A, const Point& B) {
    	return Point(A.x - B.x, A.y - B.y);
    }
    
    bool operator == (const Point& A, const Point& B) {
    	return dcmp(A.x - B.x) == 0 && dcmp(A.y - B.x) == 0;
    }
    
    LD Dot(const Point& A, const Point& B) {
    	return A.x * B.x + A.y * B.y;
    }
    
    LD Length(const Point& A) { return sqrt(Dot(A, A)); }
    
    struct Circle
    {
    	Point c;
    	LD r;
    	Circle() {}
    	Circle(Point c, LD r):c(c), r(r) {}
    };
    
    int getCircleIntersection(Circle C1, Circle C2) {
    	LD &r1 = C1.r, &r2 = C2.r;
    	LD &x1 = C1.c.x, &x2 = C2.c.x, &y1 = C1.c.y, &y2 = C2.c.y;
    	LD d = Length(C1.c - C2.c);
    	if(dcmp(fabs(r1-r2) - d) > 0) return -1;
    	if(dcmp(r1 + r2 - d) < 0) return 0;
    	LD d2 = Dot(C1.c - C2.c, C1.c - C2.c);
    	LD a = r1*(x1-x2)*2, b = r1*(y1-y2)*2, c = r2*r2-r1*r1-d*d;
    	LD p = a*a+b*b, q = -a*c*2, r = c*c-b*b;
    
    	LD cosa, sina, cosb, sinb;
    	//One Intersection
    	if(dcmp(d - (r1 + r2)) == 0 || dcmp(d - fabs(r1 - r2)) == 0) {
    		cosa = -q / p / 2;
    		sina = sqrt(1 - sqr(cosa));
    		Point p(x1 + C1.r * cosa, y1 + C1.r * sina);
    		if(!OnCircle(p, C2)) p.y = y1 - C1.r * sina;
    		inter.push_back(p);
    		return 1;
    	}
    	//Two Intersections
    	LD delta = sqrt(q * q - p * r * 4);
    	cosa = (delta - q) / p / 2;
    	cosb = (-delta - q) / p / 2;
    	sina = sqrt(1 - sqr(cosa));
    	sinb = sqrt(1 - sqr(cosb));
    	Point p1(x1 + C1.r * cosa, y1 + C1.r * sina);
    	Point p2(x1 + C1.r * cosb, y1 + C1.r * sinb);
    	if(!OnCircle(p1, C2)) p1.y = y1 - C1.r * sina;
    	if(!OnCircle(p2, C2)) p2.y = y1 - C1.r * sinb;
    	if(p1 == p2)  p1.y = y1 - C1.r * sina;
    	inter.push_back(p1);
    	inter.push_back(p2);
    	return 2;
    }
    
  • 相关阅读:
    使用 SailingEase WinForm 框架构建复合式应用程序(插件式应用程序)
    SailingEase WinForm 应用程序开发框架
    SailingEase WinForm 框架 DEMO 下载
    SailingEase WinForm 开发框架
    .net动态编译
    VS2010 如何修改程序菜单字体大小?
    Android C++回收机制(转)
    遥测的死区
    发现个开源很好的C++框架库,共享一下
    mongodb查询例子
  • 原文地址:https://www.cnblogs.com/AOQNRMGYXLMV/p/5098304.html
Copyright © 2020-2023  润新知