计算圆弧扇形的包围矩形
Calculating the Bounding Rectangle of a Circular Sector

原始链接: https://asawicki.info/news_1791_calculating_the_bounding_rectangle_of_a_circular_sector

本文详细介绍了一种高效计算二维圆扇形包围矩形算法,这种形状在游戏中(例如攻击或法术)表示作用区域非常有用。圆扇形由其顶点(中心)、方向、半角和半径定义。 核心思想是找到沿X轴和Y轴的极端点,这些点定义了包围矩形的最小和最大角点。最初,顶点和两个“边缘点”(使用方向和半角计算)被考虑在内。然而,这些并不总是足够的。 该算法通过检查位于主轴半径距离的四个附加点(-X、+X、-Y、+Y)来扩展,*仅当*它们落在扇形的角度范围内时才进行检查。然后,最终的包围矩形由所有考虑点的最小和最大坐标确定。 重要的是,该算法避免直接使用`atan2`等三角函数,而是依赖于向量运算以提高性能和稳定性。预先计算正弦和余弦值可以进一步优化该过程。 可以在[shadertoy.com/view/w3jcRw](https://www.shadertoy.com/view/w3jcRw)处找到实时演示。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 计算圆弧的包围矩形 (asawicki.info) 29 分,ibobev 1 天前 | 隐藏 | 过去 | 收藏 | 1 条评论 optiot 1 天前 [–] 哦,很酷!几年前,当我试图计算圆形表盘圆周上弧线的包围框时,我做过类似(但不同)的事情:https://www.desmos.com/calculator/4e5c41c2ab 我不记得我具体在做什么,但我想你也许可以通过使用模或 floor/ceil 直接计算轴上的点来跳过 for 循环。回复 考虑申请 YC 的 2026 年冬季批次!申请截止日期为 11 月 10 日 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Sun
19
Oct 2025

This article will be short and straight to the point. While working with geometry in 2D, I was recently looking for an algorithm to calculate the bounding box of a specific shape that I initially called a "cone". Actually, as I'm talking about 2D, I should rather say I needed the bounding rectangle of a circular sector - a part of a circle with a limited angle around an axis pointing in a specific direction.

When developing a 2D game, this shape can represent, for example, the area of effect of an attack, such as punching nearby enemies, firing a shotgun, spraying some substance ahead, or casting a magical spell. Calculating its bounding rectangle can be useful for querying a space-partitioning data structure (like a grid, a quadtree, etc.) for potentially affected objects.

I prototyped my solution in ShaderToy, which you can see here: shadertoy.com/view/w3jcRw.

A circular sector is described by:

  • vec2 apex - the starting point and the center of the circle that this shape is part of
  • vec2 direction - a vector pointing in the direction of the axis (must be normalized)
  • float halfAngle - the angle between the axis and the edges, or half of the angle between the opposing edges (in radians, in range 0...π)
    • For 0, the shape becomes just a line segment.
    • For π, it becomes a full circle.
  • float radius - the radius of the circle that this shape is part of

The output bounding rectangle is described by just vec2 MinPos, MaxPox - two points defining the minimum and maximum coordinates it contains.

To calculate the bounding rectangle of our cone, we need to consider all possible points that extend the furthest along the X and Y axes, and take their min/max. The first such point is the apex. The next two are what I call "edge points."

However, there are cases where this is not enough. We also need to check four "extra points" located at a distance of radius from the apex along -X, +X, -Y, +Y, as long as each of these points belongs to the cone.

My final algorithm in GLSL is:

void CalcConeBoundingRect(vec2 apex, vec2 direction, float halfAngle, float radius,
    out vec2 boundingRectMinPos, out vec2 boundingRectMaxPos)
{
    float sinHalfAngle = sin(halfAngle);
    float cosHalfAngle = cos(halfAngle);
    vec2 edgeVec1 = vec2(
        direction.x * cosHalfAngle - direction.y * sinHalfAngle,
        direction.y * cosHalfAngle + direction.x * sinHalfAngle);
    vec2 edgeVec2 = vec2(
        direction.x * cosHalfAngle + direction.y * sinHalfAngle,
        direction.y * cosHalfAngle - direction.x * sinHalfAngle);
    vec2 edgePoint1 = apex + edgeVec1 * radius;
    vec2 edgePoint2 = apex + edgeVec2 * radius;
    boundingRectMinPos = min(min(edgePoint1, edgePoint2), apex);
    boundingRectMaxPos = max(max(edgePoint1, edgePoint2), apex);
    
    vec2 unitVec[4] = vec2[](
        vec2(-1.0, 0.0), vec2(1.0, 0.0),
        vec2(0.0, -1.0), vec2(0.0, 1.0));
    for(int i = 0; i < 4; ++i)
    {
        if(dot(unitVec[i], direction) >= cosHalfAngle)
        {
            vec2 extraPoint = apex + unitVec[i] * radius;
            boundingRectMinPos = min(boundingRectMinPos, extraPoint);
            boundingRectMaxPos = max(boundingRectMaxPos, extraPoint);
        }
    }
}

Note that we don't use raw angles here, apart from the initial parameter. We don't call the atan2 function, nor do we compare whether one angle is smaller than another. We simply operate on vectors - a common theme in well-designed geometric algorithms.

The algorithm can be optimized further if we store the sine and cosine of the angle in advance. Alternatively, if we have only one of them, we can compute the other using the formula below. This way, we never need to use the raw angle value at all.

float sinHalfAngle = sqrt(1.0 - cosHalfAngle * cosHalfAngle);

EDIT: Big thanks to Matthew Arcus for suggesting an improvement to the code! I applied it to the listing above.

Comments | #math Share

联系我们 contact @ memedata.com