已知圆弧的起始角、圆心和半径 如何计算圆弧包围盒

圆弧包围盒计算方法 (C++实现)

计算圆弧包围盒的关键是找出圆弧在X轴和Y轴方向上的最小值和最大值。圆弧的包围盒需要考虑起始点、终止点以及可能的极值点。

基本思路

  1. 计算起始点和终止点
  2. 检查圆弧是否经过圆的四个极值点(最左、最右、最上、最下)
  3. 根据圆弧的扫掠角度范围确定包含哪些极值点
  4. 综合所有点计算最小/最大坐标

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()

  • 原理: 在圆弧上均匀采样,计算采样点的包围盒
  • 优点: 通用性强,适用于任意曲线
  • 缺点: 精度取决于采样密度,计算量大

关键注意事项

  1. 角度规范化: 确保所有角度在[0, 2π)范围内
  2. 方向处理: 正确处理顺时针和逆时针圆弧
  3. 整圆判断: 扫掠角度≥2π时,包围盒就是圆的包围盒
  4. 跨越0度: 需要特别处理起始角大于终止角的情况
  5. 浮点精度: 使用适当的容差(如1e-10)避免浮点误差

应用场景

  • 图形渲染: 确定圆弧的显示区域
  • 碰撞检测: 快速判断圆弧是否与其他图形相交
  • CAD/CAM: 加工路径规划
  • 游戏开发: 角色移动范围限制

这个实现提供了三种不同方法,您可以根据具体需求选择使用。通常,calculateOptimized()方法在精度和效率之间取得了良好平衡。

相关推荐
寻寻觅觅☆11 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
fpcc11 小时前
并行编程实战——CUDA编程的Parallel Task类型
c++·cuda
ceclar12312 小时前
C++使用format
开发语言·c++·算法
lanhuazui1013 小时前
C++ 中什么时候用::(作用域解析运算符)
c++
charlee4413 小时前
从零实现一个生产级 RAG 语义搜索系统:C++ + ONNX + FAISS 实战
c++·faiss·onnx·rag·语义搜索
老约家的可汗13 小时前
初识C++
开发语言·c++
crescent_悦13 小时前
C++:Product of Polynomials
开发语言·c++
小坏坏的大世界14 小时前
CMakeList.txt模板与 Visual Studio IDE 操作对比表
c++·visual studio
乐观勇敢坚强的老彭14 小时前
c++寒假营day03
java·开发语言·c++
愚者游世15 小时前
brace-or-equal initializers(花括号或等号初始化器)各版本异同
开发语言·c++·程序人生·面试·visual studio