网上三点求外接圆基本都是二维的(平面),三维的求解很少而且个别是错的。
计算图形学一般处理三维的点,所以这里我们介绍三维中三点(三角形)的外接圆。如果需要二维(平面)的求解,只需要设z=0。问题描述:已知空间三点的坐标为(x1,y1,z1),(x2,y2,z2),(x3,y3,z3),求这三个点所确定的空间圆的圆心坐标和半径。最早找到的是利用三点共面和三点到空间圆心坐标的距离相等来计算,具体推导见《空间3点求三点所在空间圆的圆心空间坐标》。实现代码如下:
struct Point3
{
double x, y, z;
Point3(double P0, double P1, double P2)
{
x = P0;
y = P1;
z = P2;
}
};
int solveCenterPointOfCircle(std::vector pd, double centerpoint[])
{
double a1, b1, c1, d1;
double a2, b2, c2, d2;
double a3, b3, c3, d3;
double x1 = pd[0].x, y1 = pd[0].y, z1 = pd[0].z;
double x2 = pd[1].x, y2 = pd[1].y, z2 = pd[1].z;
double x3 = pd[2].x, y3 = pd[2].y, z3 = pd[2].z;
a1 = (y1*z2 - y2*z1 - y1*z3 + y3*z1 + y2*z3 - y3*z2);
b1 = -(x1*z2 - x2*z1 - x1*z3 + x3*z1 + x2*z3 - x3*z2);
c1 = (x1*y2 - x2*y1 - x1*y3 + x3*y1 + x2*y3 - x3*y2);
d1 = -(x1*y2*z3 - x1*y3*z2 - x2*y1*z3 + x2*y3*z1 + x3*y1*z2 - x3*y2*z1);
a2 = 2 * (x2 - x1);
b2 = 2 * (y2 - y1);
c2 = 2 * (z2 - z1);
d2 = x1 * x1 + y1 * y1 + z1 * z1 - x2 * x2 - y2 * y2 - z2 * z2;
a3 = 2 * (x3 - x1);
b3 = 2 * (y3 - y1);
c3 = 2 * (z3 - z1);
d3 = x1 * x1 + y1 * y1 + z1 * z1 - x3 * x3 - y3 * y3 - z3 * z3;
centerpoint[0] = -(b1*c2*d3 - b1*c3*d2 - b2*c1*d3 + b2*c3*d1 + b3*c1*d2 - b3*c2*d1)
/ (a1*b2*c3 - a1*b3*c2 - a2*b1*c3 + a2*b3*c1 + a3*b1*c2 - a3*b2*c1);
centerpoint[1] = (a1*c2*d3 - a1*c3*d2 - a2*c1*d3 + a2*c3*d1 + a3*c1*d2 - a3*c2*d1)
/ (a1*b2*c3 - a1*b3*c2 - a2*b1*c3 + a2*b3*c1 + a3*b1*c2 - a3*b2*c1);
centerpoint[2] = -(a1*b2*d3 - a1*b3*d2 - a2*b1*d3 + a2*b3*d1 + a3*b1*d2 - a3*b2*d1)
/ (a1*b2*c3 - a1*b3*c2 - a2*b1*c3 + a2*b3*c1 + a3*b1*c2 - a3*b2*c1);
centerpoint[3] = sqrt(pow(pd[0].x - centerpoint[0], 2) + pow(pd[0].y - centerpoint[1], 2) + pow(pd[0].z - centerpoint[2], 2));
return 0;
}
上面是求解比较复杂,编码实现也比较难以理解。网上有篇《求三维空间中的三角形外接圆圆心坐标的算法》从几何角度求解,外接圆的圆心与每条边的中点的组成直线垂直于每条边,这显然是不对。因为三角形每条边的中点和边自身向量组成的三个平面相交与一个条直线。我们只需要将第三条边的条件换成三角形所在平面的法向量垂直于平面上经过圆心的直线。这时候的三个平面相交于一个点。代码实现如下。
//vector(c) = vector(b) - vector(a)
static void getVectorDiff(const double *a, const double *b, double *c, int n = 3)
{
for (int i = 0; i < n; i++)
c[i] = b[i] - a[i];
}
//vector(a) inner product vector(b)
static double getInnerProduct(const double *a, const double *b, int n = 3)
{
double result = 0.0;
for (int i = 0; i < n; i++)
result += a[i] * b[i];
return result;
}
static void getVectorCenter(const double *a, const double *b, double *c, int n = 3)
{
for (int i = 0; i < n; i++)
c[i] = (a[i] + b[i]) / 2.0;
}
//Determinants of third-order matrices
static float getVector3Det(const double *v1, const double *v2, const double *v3)
{
double a[3][3];
for (int i = 0; i < 3; ++i) {
a[i][0] = v1[i];
a[i][1] = v2[i];
a[i][2] = v3[i];
}
return a[0][0] * a[1][1] * a[2][2] -
a[0][0] * a[1][2] * a[2][1] -
a[0][1] * a[1][0] * a[2][2] +
a[0][1] * a[1][2] * a[2][0] +
a[0][2] * a[1][0] * a[2][1] -
a[0][2] * a[1][1] * a[2][0];
}
//Euclidean Distance
static double getEuclideanDistance(const double *v1, const double *v2, int n = 3)
{
double result = 0.0;
for (int i = 0; i < n; i++)
result += pow(v1[i] - v2[i], 2);
return sqrt(result);
}
/********************************************************************************/
/* Finding the Center and Radius of a Triangle Circle in Three-Dimensional Space*/
/*circle = {x, y, z radius} */
/********************************************************************************/
static bool triangleCircumCentre(double* pt[3], double circle[4])
{
double line_center1[3], line_center2[3], line_center3[3];
double normal1[3], normal2[3], normal3[3];
double result[3], rep1[3], rep2[3], rep3[3];
float d_det;
getVectorCenter(pt[0], pt[1], line_center1);
getVectorCenter(pt[0], pt[2], line_center2);
getVectorCenter(pt[1], pt[2], line_center3);
getVectorDiff(pt[0], pt[1], normal1);
getVectorDiff(pt[0], pt[2], normal2);
vtkMath::Cross(normal1, normal2, normal3);
result[0] = vtkMath::Dot(line_center1, normal1);
result[1] = vtkMath::Dot(line_center2, normal2);
result[2] = vtkMath::Dot(pt[0], normal3);
rep1[0] = normal1[0];
rep1[1] = normal2[0];
rep1[2] = normal3[0];
rep2[0] = normal1[1];
rep2[1] = normal2[1];
rep2[2] = normal3[1];
rep3[0] = normal1[2];
rep3[1] = normal2[2];
rep3[2] = normal3[2];
d_det = getVector3Det(rep1, rep2, rep3);
if (fabs(d_det) < 1e-15)
return false;
//Cramer Rule
circle[0] = getVector3Det(result, rep2, rep3) / d_det;
circle[1] = getVector3Det(rep1, result, rep3) / d_det;
circle[2] = getVector3Det(rep1, rep2, result) / d_det;
circle[3] = getEuclideanDistance(circle, pt[0]);
return true;
}
下面我们用示例验证上面结果的正确性,运行结果如下。三个白球表示三角形的三个顶点,红色的大球就是求得外接球。可以看出三角形正好经过外接圆的圆心。完整代码下载。
无