代码之家  ›  专栏  ›  技术社区  ›  liang.good

如何获得椭圆的边界?

  •  0
  • liang.good  · 技术社区  · 4 年前
    void ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle [, anticlockwise]);
    

    画布上下文2D API ellipse()方法创建一个以(x,y)为中心的椭圆弧,半径为radiusX和radiusY。路径从startAngle开始,到endAngle结束,沿逆时针方向行进。

    如何获得具有给定参数的椭圆的轴对齐边界框:x,y,半径x,半径y,旋转,startAngle,endAngle,逆时针?

    0 回复  |  直到 5 年前
        1
  •  5
  •   Blindman67    4 年前

    两种解决方案

    这个答案包含两个精确解——它不是近似值。

    解决方案是

    1. boundEllipseAll 将找到整个椭圆的边界。如果比完整解决方案复杂得多,但需要确保x半径大于y半径(例如,将椭圆旋转90度并交换x,y半径)

    2. boundEllipse 将找到椭圆线段的边界。它将适用于所有省略号,但我没有包括《特定常规武器公约》旗。要获得CCW椭圆的边界,请交换起始角度和结束角度。

      它的工作原理是首先找到起点和终点的x,y坐标,计算沿每个轴的最小值和最大值。然后计算极值角度,首先是x轴极值,然后是y轴极值。

      如果极值角度在起始角度和结束角度之间,则计算该角度的x,y位置,并根据最小和最大范围测试该点。

    有很大的优化空间,因为许多点只需要x或y部分,以及函数中的内部while循环 extrema 如果正在处理的轴的最小值和最大值发生变化,则可以提前退出。

    实例

    该示例确保我没有犯任何错误,并使用第二个解决方案,通过移动起始角和结束角、旋转和y轴半径来设置椭圆的动画。绘制边界框及其边界的椭圆。

    2022年4月更新

    示例显示了两个完整椭圆的使用 边界椭圆全部 和椭圆段 边界椭圆

    笔记 那个 边界椭圆 仅适用于椭圆段,其中 endAngle n和 startAngle I’我符合规则 {m <= n <= m + 2Pi}

    修复了中的错误 边界椭圆 endAngle == startAngle + 2 * Math.PI

    const ctx = canvas.getContext("2d");
    const W = 200, H= 180;
    const TAU = Math.PI * 2;
    const ellipse = {
        x: W / 2, 
        y: H / 2, 
        rx: W / 3, 
        ry: W / 3, 
        rotate: 0, 
        startAng: 0, 
        endAng: Math.PI * 2,
        dir: false,
    };
    function boundEllipseAll({x, y, rx, ry, rotate}) {
        const xAx = Math.cos(rotate);
        const xAy = Math.sin(rotate);    
        const w =  ((rx  * xAx) ** 2 + (ry * xAy) ** 2) ** 0.5;
        const h = ((rx * xAy) ** 2 + (ry * xAx) ** 2) ** 0.5;
        return {x: -w + x, y: -h + y, w: w * 2, h: h * 2};
    }
    function boundEllipse({x, y, rx, ry, rotate, startAng, endAng}) {
        const normalizeAng = ang => (ang % TAU + TAU) % TAU;
        const getPoint = ang => {
            const cA = Math.cos(ang);
            const sA = Math.sin(ang);
            return [cA * rx * xAx - sA * ry * xAy, cA * rx * xAy + sA * ry * xAx];
        }
        const extrema = a => {  // from angle
            var i = 0;
            while(i < 4) {
                const ang = normalizeAng(a + Math.PI * (i / 2));
                if ((ang > startAng && ang < endAng) || (ang + TAU  > startAng && ang + TAU < endAng)) {
                    const [xx, yy] = getPoint(ang);
                    minX = Math.min(minX, xx);
                    maxX = Math.max(maxX, xx);
                    minY = Math.min(minY, yy);
                    maxY = Math.max(maxY, yy);                
                }
                i ++;
            }        
        }
        // UPDATE bug fix (1) for full ellipse
        const checkFull = startAng !== endAng;  // Update fix (1)
        startAng = normalizeAng(startAng);
        endAng = normalizeAng(endAng);
        (checkFull && startAng === endAng) && (endAng += TAU); // Update fix (1)
        const xAx = Math.cos(rotate);
        const xAy = Math.sin(rotate);    
        endAng += endAng < startAng ? TAU : 0;
        const [sx, sy] = getPoint(startAng);
        const [ex, ey] = getPoint(endAng);
        var minX = Math.min(sx, ex);
        var maxX = Math.max(sx, ex);
        var minY = Math.min(sy, ey);
        var maxY = Math.max(sy, ey);    
        extrema(-Math.atan((ry * xAy) / (rx * xAx)));  // Add x Axis extremas
        extrema(-Math.atan((rx * xAy) / (ry * xAx)));  // Add y Axis extremas
        return {x: minX + x, y: minY + y, w: maxX - minX, h: maxY - minY};
    }
    function drawExtent({x,y,w,h}) {
        ctx.moveTo(x,y);
        ctx.rect(x, y, w, h);
    }
    function drawEllipse({x, y, rx, ry, rotate, startAng, endAng, dir}) {
        ctx.ellipse(x, y, rx, ry, rotate, startAng, endAng, dir);
    }
    function drawFullEllipse({x, y, rx, ry, rotate, dir}) {
        ctx.ellipse(x, y, rx, ry, rotate, 0, TAU, dir);
    }
    mainLoop(0);
    function mainLoop(time) {
        ctx.clearRect(0, 0, W, H);
        
        // Animate ellipse
        ellipse.startAng = time / 1000;
        ellipse.endAng = time / 2000;
        ellipse.rotate = Math.cos(time / 14000) * Math.PI * 2;
        ellipse.ry = Math.cos(time / 6000) * (W / 4 - 10) +  (W / 4);
        
        // Draw full ellipse and bounding box.
        ctx.strokeStyle = "#F008";
        ctx.beginPath();
        drawFullEllipse(ellipse);
        drawExtent(boundEllipseAll(ellipse));
        ctx.stroke();    
        
        // Draw ellipse segment and bounding box.
        ctx.strokeStyle = "#0008";
        ctx.beginPath();
        drawEllipse(ellipse);
        drawExtent(boundEllipse(ellipse));
        ctx.stroke();
    
        requestAnimationFrame(mainLoop)
    }
    canvas { border: 1px solid black }
    <canvas id="canvas" width="200" height="180"></canvas>