3Dの当たり判定やcullingをする際に、境界球が必要となる場面は多く、この分野ではRitterの境界球という有名なアルゴリズムがあります。
原作はACMのGraphics Gemsのサイトから取得できるのですが、記法がとても古く、マルチスレッドも考慮されていないので、そのまま使うのはナンセンスです。
どこかに現代語訳(?)した物が無いかと探してみたのですが、無いようなので下記に作成しました。
/*
An Efficient Bounding Sphere
by Jack Ritter
from "Graphics Gems", Academic Press, 1990
*/
/* Routine to calculate tight bounding sphere over */
/* a set of points in 3D */
/* This contains the routine find_bounding_sphere(), */
/* the struct definition, and the globals used for parameters. */
/* The abs() of all coordinates must be < BIGNUMBER */
/* Code written by Jack Ritter and Lyle Rains. */
#include <stdio.h>
#include <math.h>
const double BIGNUMBER = 100000000.0; // Please set very large coord that is out of the world.
struct Point3d {
double x, y, z;
};
inline double sq(double x) { return x * x; }
void CalcBoundingSphere(const Point3d* pPoints, int nPoints,
Point3d* pCenter, double* radius) {
Point3d xmin = {0};
Point3d xmax = {0};
Point3d ymin = {0};
Point3d ymax = {0};
Point3d zmin = {0};
Point3d zmax = {0};
// FIRST PASS: find 6 minima/maxima pPoints
xmin.x = ymin.y = zmin.z = BIGNUMBER; // initialize for min/max compare
xmax.x = ymax.y = zmax.z = -BIGNUMBER;
for (int i=0; i < nPoints; i++)
{
if (pPoints[i].x < xmin.x)
xmin = pPoints[i]; // New xminimum point
if (pPoints[i].x > xmax.x)
xmax = pPoints[i];
if (pPoints[i].y < ymin.y)
ymin = pPoints[i];
if (pPoints[i].y > ymax.y)
ymax = pPoints[i];
if (pPoints[i].z < zmin.z)
zmin = pPoints[i];
if (pPoints[i].z > zmax.z)
zmax = pPoints[i];
}
// Set span = distance between the 2 pPoints *min & *max (squared)
double xspan = sq(xmax.x - xmin.x) + sq(xmax.y - xmin.y) + sq(xmax.z - xmin.z);
double yspan = sq(ymax.x - ymin.x) + sq(ymax.y - ymin.y) + sq(ymax.z - ymin.z);
double zspan = sq(zmax.x - zmin.x) + sq(zmax.y - zmin.y) + sq(zmax.z - zmin.z);
// Set pPoints dia1 & dia2 to the maximally separated pair
Point3d dia1 = xmin;
Point3d dia2 = xmax; // assume xspan biggest
double maxspan = xspan;
if (yspan > maxspan)
{
maxspan = yspan;
dia1 = ymin; dia2 = ymax;
}
if (zspan > maxspan)
{
dia1 = zmin; dia2 = zmax;
}
// dia1,dia2 is a diameter of initial sphere
// calc initial pCenter
pCenter->x = (dia1.x+dia2.x)/2.0;
pCenter->y = (dia1.y+dia2.y)/2.0;
pCenter->z = (dia1.z+dia2.z)/2.0;
// calculate initial radius**2 and radius
double rad_sq = sq(dia2.x-pCenter->x) + sq(dia2.y-pCenter->y) + sq(dia2.z-pCenter->z);
*radius = sqrt(rad_sq);
/* SECOND PASS: increment current sphere */
for (int i = 0; i < nPoints; i++)
{
double old_to_p_sq = sq(pPoints[i].x-pCenter->x) + sq(pPoints[i].y-pCenter->y) + sq(pPoints[i].z-pCenter->z);
if (old_to_p_sq > rad_sq) /* do r**2 test first */
{ /* this point is outside of current sphere */
double old_to_p = sqrt(old_to_p_sq);
/* calc radius of new sphere */
*radius = (*radius + old_to_p) / 2.0;
rad_sq = sq(*radius); /* for next r**2 compare */
double old_to_new = old_to_p - *radius;
/* calc pCenter of new sphere */
pCenter->x = (*radius*pCenter->x + old_to_new * pPoints[i].x) / old_to_p;
pCenter->y = (*radius*pCenter->y + old_to_new * pPoints[i].y) / old_to_p;
pCenter->z = (*radius*pCenter->z + old_to_new * pPoints[i].z) / old_to_p;
}
}
}
// テスト用
void main(void) {
Point3d p[] = { {0, 0, 0}, {1, 1, 1}, {1, 0, 1}, {1, -1, 0} };
Point3d center;
double r = 0;
CalcBoundingSphere(p, 4, &center, &r);
printf("Result (%lf,%lf,%lf) %lf\n", center.x, center.y, center.z, r);
}
上のコードのライセンスは元のGraphicsGemsのそれと同様です。
(自己責任という条件で、商用非商用問わず使用改変再配布自由。)