计算所有顶点的AABB获取一个逼近包围球
第一步:获取各坐标轴上的6个端点(这里不理解,“各坐标轴上的6个端点”是什么?),可以选择其中间隔距离最远的两个顶点,这两个顶点的中心即为球心,且两顶点距离的一半则是球体半径。
第二步:全部顶点再次循环。对于位于当前球体外部的所有顶点,该步骤将更新一个新球体包含原球体以及外部顶点。即相对于原球心,新球体的直径将延伸至外部顶点。
第一步的代码MostSeparatedPointsOnAABB()和SphereFromDistantPoints()如下:
//Compute indices to the two most separated points of the (up to) six points
//defining the AABB encompassing the point set. Return these as min and max.
void MostSeparatedPointsOnAABB(int &min, int &max, Point pt[], int numPts) {
//First find most extreme points along principal axes
int minx = 0, maxx = 0, miny = 0, maxy = 0, minz = 0, maxz = 0;
for(int i = 1; i < numPts; i++) {
if(pt[i].x < pt[minx].x) minx = i;
if(pt[i].x > pt[maxx].x) maxx = i;
if(pt[i].y < pt[miny].y) miny = i;
if(pt[i].y > pt[maxy].y) maxy = i;
if(pt[i].z < pt[minz].z) minz = i;
if(pt[i].z > pt[maxz].z) maxz = i;
}
//Compute the squared distances for the three pairs of points
float dist2x = Dot(pt[maxx] - pt[minx], pt[maxx] - pt[minx]);
float dist2y = Dot(pt[maxy] - pt[miny], pt[maxy] - pt[miny]);
float dist2z = Dot(pt[maxz] - pt[minz], pt[maxz] - pt[minz]);
//Pick the pair (min, max) of points most distant
min = minx;
max = maxx;
if(dist2y > dist2x && dist2y > dist2z) {
max = maxy;
min = miny;
}
if(dist2z > dist2x && dist2z > dist2y) {
max = maxz;
min = minz;
}
}
void SphereFromDistantPoints(Sphere &s, Point pt[], int numPts) {
//Find the most separated point pair defining the encompassing AABB
int min, max;
MostSeparatedPointsOnAABB(min, max, pt, numPts);
//Set up sphere to just encompass these two points
s.c = (pt[min] + pt[max]) * 0.5f;
s.r = Dot(pt[max] - s.c, pt[max] - s.c);
s.r = Sqrt(s.r);
}
第二步:
//Given Sphere s and Point p, update s (if needed) to just encompass p
void SphereOfSphereAndPt(Sphere &s, Point &p) {
//Compute squared distance between point and sphere center
Vector d = p - s.c;
float dist2 = Dot(d, d);
//Only update s if point p is outside it
if(dis2 > s.r * s.r) {
float dist = Sqrt(dist2);
float newRadius = (s.r + dist) * 0.5f;
float k = (newRadius - s.r) / dist;
s.r = newRadius;
s.c += d*k;
}
}
注意,代码中的d不是单位向量,故要除以dist使其变成单位向量
计算逼近包围球全部代码如下:
void RitterSphere(Sphere &s, Point pt[], int numPts) {
//Get sphere encompassing two approximately most distant points
SphereFromDistantPoints(s, pt, numPts);
//Grow sphere to include all points
for(int i = 0; i < numPts; i++) {
SphereOfSphereAndPt(s, pt[i]);
}
}
以下是一个由2D三角形计算求得的逼近包围圆
最大离散方向包围球
利用统计方法分析顶点云并确定最大离散方向。若给定该方向,将距离最远的两点投影至该方向上,即可得到球心和半径。
利用协方差矩阵可以获取该轴。对于协方差矩阵特征向量和特征值有以下关系:若特征向量为最大特征向量,则特征值为沿该轴的顶点数据具有的最大方差值;若为最小特征向量,则特征值为沿该轴的顶点数据具有的最小方差值。
关于计算协方差矩阵的方法以及其他相关计算(如Jacobi方法)在此不加赘述。
void RigenSphere(Sphere &eigSphere, Point pt[], int numPts)
{
Matrix33 m,v;
//compute the covariance matrix m
CovarianceMatrix(m, pt, numPts);
//Decompose it into eigenvectors (in v) and eigenvalues(in m)
Jacobi(m,v);
//Find the compose with largest magnitude eigenvalue(largest spread)
Vector e;
int maxc = 0;
float maxf, maxe = Abs(m[0][0]);
if((maxf = Abs(m[1][1])) > maxe) maxc = 1, maxe = maxf;
if((maxf = Abs(m[2][2])) > maxe) maxc = 2, maxe = maxf;
e[0] = v[0][maxc];
e[1] = v[1][maxc];
e[2] = v[2][maxc];
//Find the most exrteme points along direction ‘e’
int imin, imax;
ExtremePointsAlongDirection(e, pt, numPts, &imin, &imax);
Point minpt = pt[imin];
Point maxpt = pt[imax];
float dist = Sqrt(Dot(maxpt - minpt, maxpt - minpt));
eigSphere.r = dist*0.5f;
eigSphere.c = (minpt + maxpt)*0.5f;
}
改进后的包围球代码如下:
void RitterEigenSphere(Sphere &s, Point pt[], int numPts) {
//Start with sphere from maximum spread
EigenSphere(s, pt, numPts);
//Grow shpere to include all points
for(int i = 0; i < numPts; i++)
SphereOfSphereAndPt(s, pt[i]);
}
迭代修正包围球
上面的逼近包围球不甚让人满意,可以考虑减少半径后再次包围,像这样迭代多次求得更可靠的包围球。
代码如下:
//迭代修正包围球
void RitterIterative(Sphere &s, POINT pt[], int numPts) {
int ITER_NUM = 8;
RitterSphere(s, pt, numPts);
Sphere s2 = s;
while(ITER_NUM -- ) {
s2.r *= 0.95f;
for(int i = 0; i < numPts; i++) {
SphereOfSphereAndPt(s2,pt[i]);
}
if(s2.r < s.r) s = s2;
}
}
修正后的包围球: