圆弧包围盒计算方法 (C++实现)
计算圆弧包围盒的关键是找出圆弧在X轴和Y轴方向上的最小值和最大值。圆弧的包围盒需要考虑起始点、终止点以及可能的极值点。
基本思路
- 计算起始点和终止点
- 检查圆弧是否经过圆的四个极值点(最左、最右、最上、最下)
- 根据圆弧的扫掠角度范围确定包含哪些极值点
- 综合所有点计算最小/最大坐标
C++实现代码
cpp
#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
// 二维点结构体
struct Point2D {
double x, y;
Point2D(double x = 0, double y = 0) : x(x), y(y) {}
// 用于比较
bool operator<(const Point2D& other) const {
return (x < other.x) || (x == other.x && y < other.y);
}
};
// 圆弧参数结构体
struct ArcParams {
Point2D center; // 圆心
double radius; // 半径
double startAngle; // 起始角度(弧度)
double endAngle; // 终止角度(弧度)
bool clockwise; // 方向(true为顺时针)
};
// 圆弧包围盒计算类
class ArcBoundingBox {
private:
// 规范化角度到 [0, 2π)
static double normalizeAngle(double angle) {
while (angle < 0) angle += 2 * M_PI;
while (angle >= 2 * M_PI) angle -= 2 * M_PI;
return angle;
}
// 判断角度是否在圆弧范围内
static bool isAngleInArc(double angle, double startAngle, double endAngle, bool clockwise) {
angle = normalizeAngle(angle);
startAngle = normalizeAngle(startAngle);
endAngle = normalizeAngle(endAngle);
if (clockwise) {
// 顺时针方向:角度递减
if (startAngle >= endAngle) {
return angle <= startAngle && angle >= endAngle;
} else {
return angle <= startAngle || angle >= endAngle;
}
} else {
// 逆时针方向:角度递增
if (startAngle <= endAngle) {
return angle >= startAngle && angle <= endAngle;
} else {
return angle >= startAngle || angle <= endAngle;
}
}
}
public:
// 计算圆弧包围盒
// 返回值: {minX, minY, maxX, maxY}
static std::vector<double> calculate(const ArcParams& arc) {
std::vector<double> bounds(4); // minX, minY, maxX, maxY
// 计算起始点和终止点
Point2D startPoint, endPoint;
startPoint.x = arc.center.x + arc.radius * cos(arc.startAngle);
startPoint.y = arc.center.y + arc.radius * sin(arc.startAngle);
endPoint.x = arc.center.x + arc.radius * cos(arc.endAngle);
endPoint.y = arc.center.y + arc.radius * sin(arc.endAngle);
// 初始化为起始点
double minX = startPoint.x;
double maxX = startPoint.x;
double minY = startPoint.y;
double maxY = startPoint.y;
// 添加终止点
minX = std::min(minX, endPoint.x);
maxX = std::max(maxX, endPoint.x);
minY = std::min(minY, endPoint.y);
maxY = std::max(maxY, endPoint.y);
// 圆的四个极值点对应的角度
const double extremeAngles[4] = {
0, // 最右点 (0度)
M_PI / 2, // 最上点 (90度)
M_PI, // 最左点 (180度)
3 * M_PI / 2 // 最下点 (270度)
};
// 检查每个极值点是否在圆弧上
for (int i = 0; i < 4; i++) {
double angle = extremeAngles[i];
if (isAngleInArc(angle, arc.startAngle, arc.endAngle, arc.clockwise)) {
Point2D extremePoint;
extremePoint.x = arc.center.x + arc.radius * cos(angle);
extremePoint.y = arc.center.y + arc.radius * sin(angle);
minX = std::min(minX, extremePoint.x);
maxX = std::max(maxX, extremePoint.x);
minY = std::min(minY, extremePoint.y);
maxY = std::max(maxY, extremePoint.y);
}
}
bounds[0] = minX;
bounds[1] = minY;
bounds[2] = maxX;
bounds[3] = maxY;
return bounds;
}
// 方法2:使用角度范围分析(更高效)
static std::vector<double> calculateOptimized(const ArcParams& arc) {
std::vector<double> bounds(4);
// 获取规范化后的角度
double startAngle = normalizeAngle(arc.startAngle);
double endAngle = normalizeAngle(arc.endAngle);
// 计算扫掠角度
double sweepAngle;
if (arc.clockwise) {
if (startAngle >= endAngle) {
sweepAngle = startAngle - endAngle;
} else {
sweepAngle = startAngle + (2 * M_PI - endAngle);
}
} else {
if (endAngle >= startAngle) {
sweepAngle = endAngle - startAngle;
} else {
sweepAngle = (2 * M_PI - startAngle) + endAngle;
}
}
// 如果扫掠角度大于等于360度,则是整圆
if (sweepAngle >= 2 * M_PI - 1e-10) {
bounds[0] = arc.center.x - arc.radius; // minX
bounds[1] = arc.center.y - arc.radius; // minY
bounds[2] = arc.center.x + arc.radius; // maxX
bounds[3] = arc.center.y + arc.radius; // maxY
return bounds;
}
// 获取起始点和终止点
Point2D startPoint, endPoint;
startPoint.x = arc.center.x + arc.radius * cos(startAngle);
startPoint.y = arc.center.y + arc.radius * sin(startAngle);
endPoint.x = arc.center.x + arc.radius * cos(endAngle);
endPoint.y = arc.center.y + arc.radius * sin(endAngle);
// 初始化包围盒
double minX = std::min(startPoint.x, endPoint.x);
double maxX = std::max(startPoint.x, endPoint.x);
double minY = std::min(startPoint.y, endPoint.y);
double maxY = std::max(startPoint.y, endPoint.y);
// 确定圆弧经过的角度区间
double angle1, angle2;
if (arc.clockwise) {
angle1 = endAngle;
angle2 = startAngle;
if (angle1 > angle2) angle1 -= 2 * M_PI;
} else {
angle1 = startAngle;
angle2 = endAngle;
if (angle1 > angle2) angle2 += 2 * M_PI;
}
// 检查四个极值点(0, 90, 180, 270度)
const double testAngles[] = {0, M_PI/2, M_PI, 3*M_PI/2};
for (int i = 0; i < 4; i++) {
double angle = testAngles[i];
// 检查角度是否在区间内(考虑周期性)
if (angle >= angle1 && angle <= angle2) {
Point2D p;
p.x = arc.center.x + arc.radius * cos(angle);
p.y = arc.center.y + arc.radius * sin(angle);
minX = std::min(minX, p.x);
maxX = std::max(maxX, p.x);
minY = std::min(minY, p.y);
maxY = std::max(maxY, p.y);
}
// 检查角度+2π是否在区间内
angle += 2 * M_PI;
if (angle >= angle1 && angle <= angle2) {
Point2D p;
p.x = arc.center.x + arc.radius * cos(testAngles[i]); // 使用原始角度
p.y = arc.center.y + arc.radius * sin(testAngles[i]);
minX = std::min(minX, p.x);
maxX = std::max(maxX, p.x);
minY = std::min(minY, p.y);
maxY = std::max(maxY, p.y);
}
}
bounds[0] = minX;
bounds[1] = minY;
bounds[2] = maxX;
bounds[3] = maxY;
return bounds;
}
// 方法3:使用采样法(通用但可能不够精确)
static std::vector<double> calculateBySampling(const ArcParams& arc, int samples = 100) {
std::vector<double> bounds(4);
// 计算扫掠角度
double sweepAngle;
if (arc.clockwise) {
if (arc.startAngle >= arc.endAngle) {
sweepAngle = arc.startAngle - arc.endAngle;
} else {
sweepAngle = arc.startAngle + (2 * M_PI - arc.endAngle);
}
} else {
if (arc.endAngle >= arc.startAngle) {
sweepAngle = arc.endAngle - arc.startAngle;
} else {
sweepAngle = (2 * M_PI - arc.startAngle) + arc.endAngle;
}
}
// 初始化
double minX = HUGE_VAL, maxX = -HUGE_VAL;
double minY = HUGE_VAL, maxY = -HUGE_VAL;
// 采样
for (int i = 0; i <= samples; i++) {
double t = (double)i / samples;
double angle;
if (arc.clockwise) {
angle = arc.startAngle - t * sweepAngle;
} else {
angle = arc.startAngle + t * sweepAngle;
}
double x = arc.center.x + arc.radius * cos(angle);
double y = arc.center.y + arc.radius * sin(angle);
minX = std::min(minX, x);
maxX = std::max(maxX, x);
minY = std::min(minY, y);
maxY = std::max(maxY, y);
}
bounds[0] = minX;
bounds[1] = minY;
bounds[2] = maxX;
bounds[3] = maxY;
return bounds;
}
// 可视化输出包围盒
static void printBoundingBox(const std::vector<double>& bounds) {
std::cout << "Bounding Box:" << std::endl;
std::cout << " Min X: " << bounds[0] << std::endl;
std::cout << " Min Y: " << bounds[1] << std::endl;
std::cout << " Max X: " << bounds[2] << std::endl;
std::cout << " Max Y: " << bounds[3] << std::endl;
std::cout << " Width: " << bounds[2] - bounds[0] << std::endl;
std::cout << " Height: " << bounds[3] - bounds[1] << std::endl;
}
};
// 测试函数
void testArcBoundingBox() {
std::cout << "=== 圆弧包围盒计算测试 ===" << std::endl;
// 测试用例1: 90度圆弧(第一象限)
{
std::cout << "\n测试1: 90度圆弧 (0° 到 90°)" << std::endl;
ArcParams arc;
arc.center = Point2D(0, 0);
arc.radius = 10;
arc.startAngle = 0;
arc.endAngle = M_PI / 2;
arc.clockwise = false;
auto bounds = ArcBoundingBox::calculate(arc);
ArcBoundingBox::printBoundingBox(bounds);
}
// 测试用例2: 270度圆弧
{
std::cout << "\n测试2: 270度圆弧 (45° 到 315°)" << std::endl;
ArcParams arc;
arc.center = Point2D(5, 5);
arc.radius = 8;
arc.startAngle = M_PI / 4; // 45°
arc.endAngle = 7 * M_PI / 4; // 315°
arc.clockwise = false;
auto bounds = ArcBoundingBox::calculateOptimized(arc);
ArcBoundingBox::printBoundingBox(bounds);
}
// 测试用例3: 整圆
{
std::cout << "\n测试3: 整圆 (0° 到 360°)" << std::endl;
ArcParams arc;
arc.center = Point2D(10, 10);
arc.radius = 5;
arc.startAngle = 0;
arc.endAngle = 2 * M_PI;
arc.clockwise = false;
auto bounds = ArcBoundingBox::calculateOptimized(arc);
ArcBoundingBox::printBoundingBox(bounds);
}
// 测试用例4: 跨越0度的圆弧
{
std::cout << "\n测试4: 跨越0度的圆弧 (315° 到 45°)" << std::endl;
ArcParams arc;
arc.center = Point2D(0, 0);
arc.radius = 6;
arc.startAngle = 7 * M_PI / 4; // 315°
arc.endAngle = M_PI / 4; // 45°
arc.clockwise = false;
auto bounds1 = ArcBoundingBox::calculate(arc);
auto bounds2 = ArcBoundingBox::calculateOptimized(arc);
auto bounds3 = ArcBoundingBox::calculateBySampling(arc, 1000);
std::cout << "方法1结果:" << std::endl;
ArcBoundingBox::printBoundingBox(bounds1);
std::cout << "\n方法2结果:" << std::endl;
ArcBoundingBox::printBoundingBox(bounds2);
std::cout << "\n方法3结果:" << std::endl;
ArcBoundingBox::printBoundingBox(bounds3);
}
// 测试用例5: 顺时针圆弧
{
std::cout << "\n测试5: 顺时针圆弧 (90° 到 0°)" << std::endl;
ArcParams arc;
arc.center = Point2D(3, 3);
arc.radius = 4;
arc.startAngle = M_PI / 2; // 90°
arc.endAngle = 0; // 0°
arc.clockwise = true;
auto bounds = ArcBoundingBox::calculateOptimized(arc);
ArcBoundingBox::printBoundingBox(bounds);
}
}
// 主函数
int main() {
testArcBoundingBox();
return 0;
}
算法说明
方法1: calculate()
- 原理: 检查起始点、终止点和四个极值点(0°, 90°, 180°, 270°)
- 优点: 精确,只考虑关键点
- 缺点: 需要判断每个极值点是否在圆弧上
方法2: calculateOptimized()
- 原理: 通过角度区间分析确定包含哪些极值点
- 优点: 高效,避免不必要的计算
- 特别处理: 处理跨越0°的圆弧和整圆情况
方法3: calculateBySampling()
- 原理: 在圆弧上均匀采样,计算采样点的包围盒
- 优点: 通用性强,适用于任意曲线
- 缺点: 精度取决于采样密度,计算量大
关键注意事项
- 角度规范化: 确保所有角度在[0, 2π)范围内
- 方向处理: 正确处理顺时针和逆时针圆弧
- 整圆判断: 扫掠角度≥2π时,包围盒就是圆的包围盒
- 跨越0度: 需要特别处理起始角大于终止角的情况
- 浮点精度: 使用适当的容差(如1e-10)避免浮点误差
应用场景
- 图形渲染: 确定圆弧的显示区域
- 碰撞检测: 快速判断圆弧是否与其他图形相交
- CAD/CAM: 加工路径规划
- 游戏开发: 角色移动范围限制
这个实现提供了三种不同方法,您可以根据具体需求选择使用。通常,calculateOptimized()方法在精度和效率之间取得了良好平衡。