Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/logging/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Algorithm 一种求闭bezier曲线边界盒的算法?_Algorithm_Bezier - Fatal编程技术网

Algorithm 一种求闭bezier曲线边界盒的算法?

Algorithm 一种求闭bezier曲线边界盒的算法?,algorithm,bezier,Algorithm,Bezier,我正在寻找一种算法,用于在笛卡尔坐标轴上查找闭合二次贝塞尔曲线的边界框(最大/最小点): input: C (a closed bezier curve) output: A B C D points 注意:上图显示了一条平滑的曲线。这不可能是顺利的。(有角)好吧,我想说的是,首先将所有端点添加到边界框中。然后,遍历所有贝塞尔元素。我假设这个公式是这样的: 从中,分别提取X和Y的两个公式。通过取导数(零交叉点)测试两个极值。然后将相应的点也添加到边界框中。我认为贝塞尔曲线的控制点形成了一个

我正在寻找一种算法,用于在笛卡尔坐标轴上查找闭合二次贝塞尔曲线的边界框(最大/最小点):

input: C (a closed bezier curve)
output: A B C D points


注意:上图显示了一条平滑的曲线。这不可能是顺利的。(有角)

好吧,我想说的是,首先将所有端点添加到边界框中。然后,遍历所有贝塞尔元素。我假设这个公式是这样的:


从中,分别提取X和Y的两个公式。通过取导数(零交叉点)测试两个极值。然后将相应的点也添加到边界框中。

我认为贝塞尔曲线的控制点形成了一个包围曲线的凸包。如果您只需要一个轴对齐的边界框,我认为您需要为所有线段的每个控制点找到每个(x,y)的最小值和最大值


我想那可能不是一个很紧的盒子。也就是说,这个盒子可能比它需要的稍大一些,但它简单且计算速度快。我想这取决于你的要求。

使用De Casteljau算法来近似高阶曲线。下面是它对三次曲线的工作原理

函数getCurveBounds(ax、ay、bx、by、cx、cy、dx、dy) { 变量px,py,qx,qy,rx,ry,sx,sy,tx,ty, 托比,托比,托比,托比,托比,托比,托比,托比,托比, torx、tory、totx、toty; 变量x,y,minx,miny,maxx,maxy; minx=miny=Number.正无穷大; maxx=maxy=Number.NEGATIVE_无穷大; tobx=bx-ax;toby=by-ay;//方向 tocx=cx-bx;tocy=cy-by; todx=dx-cx;tody=dy-cy; var step=1/40;//精度 for(var d=0;d是一种蛮力,但在许多情况下都有效。它的问题是迭代次数。实际形状和坐标之间的距离会影响结果的精度。要找到足够精确的答案,你必须迭代几十次,可能更多。如果曲线出现急转弯,则可能会失败

更好的解决方案是找到一阶导数根,如卓越网站所述。请阅读“找到曲线的端点”一节

上面的链接具有二次曲线和三次曲线的算法

提问者对二次曲线感兴趣,所以这个答案的其余部分可能无关紧要,因为我提供了计算三次曲线极值的代码

下面是三个Javascript代码,其中第一个(代码1)是我建议使用的代码


**代码1**

在测试processingjs和Raphael的解决方案后,我发现它们有一些限制和/或缺陷。然后,我进一步搜索并找到了Bonsai和it's,这是基于NISHIO Hirokazu的Python脚本。两者都有一个缺点,即使用
==
测试双重相等。当我将它们更改为数字健壮的比较时,脚本成功在所有情况下都100%正确。我使用数千条随机路径和所有共线情况测试了脚本,所有测试都成功:

代码如下。通常都需要左、右、顶和底值,但在某些情况下,知道局部极值点的坐标和相应的t值是很好的。因此我添加了两个变量:
tvalues
points
。删除关于它们的代码,就可以快速稳定地计算边界框离子功能

// Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
// Original version: NISHIO Hirokazu
// Modifications: Timo

var pow = Math.pow,
  sqrt = Math.sqrt,
  min = Math.min,
  max = Math.max;
  abs = Math.abs;

function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3)
{
  var tvalues = new Array();
  var bounds = [new Array(), new Array()];
  var points = new Array();

  var a, b, c, t, t1, t2, b2ac, sqrtb2ac;
  for (var i = 0; i < 2; ++i)
  {
    if (i == 0)
    {
      b = 6 * x0 - 12 * x1 + 6 * x2;
      a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
      c = 3 * x1 - 3 * x0;
    }
    else
    {
      b = 6 * y0 - 12 * y1 + 6 * y2;
      a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
      c = 3 * y1 - 3 * y0;
    }

    if (abs(a) < 1e-12) // Numerical robustness
    {
      if (abs(b) < 1e-12) // Numerical robustness
      {
        continue;
      }
      t = -c / b;
      if (0 < t && t < 1)
      {
        tvalues.push(t);
      }
      continue;
    }
    b2ac = b * b - 4 * c * a;
    sqrtb2ac = sqrt(b2ac);
    if (b2ac < 0)
    {
      continue;
    }
    t1 = (-b + sqrtb2ac) / (2 * a);
    if (0 < t1 && t1 < 1)
    {
      tvalues.push(t1);
    }
    t2 = (-b - sqrtb2ac) / (2 * a);
    if (0 < t2 && t2 < 1)
    {
      tvalues.push(t2);
    }
  }

  var x, y, j = tvalues.length,
    jlen = j,
    mt;
  while (j--)
  {
    t = tvalues[j];
    mt = 1 - t;
    x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
    bounds[0][j] = x;

    y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
    bounds[1][j] = y;
    points[j] = {
      X: x,
      Y: y
    };
  }

  tvalues[jlen] = 0;
  tvalues[jlen + 1] = 1;
  points[jlen] = {
    X: x0,
    Y: y0
  };
  points[jlen + 1] = {
    X: x3,
    Y: y3
  };
  bounds[0][jlen] = x0;
  bounds[1][jlen] = y0;
  bounds[0][jlen + 1] = x3;
  bounds[1][jlen + 1] = y3;
  tvalues.length = bounds[0].length = bounds[1].length = points.length = jlen + 2;

  return {
    left: min.apply(null, bounds[0]),
    top: min.apply(null, bounds[1]),
    right: max.apply(null, bounds[0]),
    bottom: max.apply(null, bounds[1]),
    points: points, // local extremes
    tvalues: tvalues // t values of local extremes
  };
};

// Usage:
var bounds = getBoundsOfCurve(532,333,117,305,28,93,265,42);
console.log(JSON.stringify(bounds));
// Prints: {"left":135.77684049079755,"top":42,"right":532,"bottom":333,"points":[{"X":135.77684049079755,"Y":144.86387466397255},{"X":532,"Y":333},{"X":265,"Y":42}],"tvalues":[0.6365030674846626,0,1]} 
//(x0,y0) is start point; (x1,y1),(x2,y2) is control points; (x3,y3) is end point.
function bezierMinMax(x0, y0, x1, y1, x2, y2, x3, y3) {
    var tvalues = [], xvalues = [], yvalues = [],
        a, b, c, t, t1, t2, b2ac, sqrtb2ac;
    for (var i = 0; i < 2; ++i) {
        if (i == 0) {
            b = 6 * x0 - 12 * x1 + 6 * x2;
            a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
            c = 3 * x1 - 3 * x0;
        } else {
            b = 6 * y0 - 12 * y1 + 6 * y2;
            a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
            c = 3 * y1 - 3 * y0;
        }
        if (Math.abs(a) < 1e-12) {
            if (Math.abs(b) < 1e-12) {
                continue;
            }
            t = -c / b;
            if (0 < t && t < 1) {
                tvalues.push(t);
            }
            continue;
        }
        b2ac = b * b - 4 * c * a;
        if (b2ac < 0) {
            continue;
        }
        sqrtb2ac = Math.sqrt(b2ac);
        t1 = (-b + sqrtb2ac) / (2 * a);
        if (0 < t1 && t1 < 1) {
            tvalues.push(t1);
        }
        t2 = (-b - sqrtb2ac) / (2 * a);
        if (0 < t2 && t2 < 1) {
            tvalues.push(t2);
        }
    }

    var j = tvalues.length, mt;
    while (j--) {
        t = tvalues[j];
        mt = 1 - t;
        xvalues[j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
        yvalues[j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
    }

    xvalues.push(x0,x3);
    yvalues.push(y0,y3);

    return {
        min: {x: Math.min.apply(0, xvalues), y: Math.min.apply(0, yvalues)},
        max: {x: Math.max.apply(0, xvalues), y: Math.max.apply(0, yvalues)}
    };
}

我认为被接受的答案是好的,但我只是想为其他试图这样做的人提供一点解释

考虑一个二次贝塞尔曲线,起点为p1,终点为p2,控制点为pc,该曲线有三个参数方程:

  • pa(t)=p1+t(pc-p1)
  • pb(t)=pc+t(p2 pc)
  • p(t)=pa(t)+t*(pb(t)-pa(t))
  • 在所有情况下,
    t
    都从0运行到1,包括0到1

    前两个是线性的,分别定义了从
    p1
    pc
    和从
    pc
    p2
    的线段。第三个是二次的,只要在表达式中替换
    pa(t)
    pb(t)
    ,这就是实际定义曲线上点的线段

    实际上,这些方程中的每一个都是一对方程,一个用于水平方向,一个用于垂直方向。参数曲线的优点在于,x和y可以相互独立地处理。这些方程完全相同,只需将上述方程中的
    x
    y
    替换为
    p
    纳什

    重要的一点是,方程式3中定义的线段,从
    pa(t)
    pb(t)
    (对于
    t
    的特定值)与相应点的曲线相切
    p(t)
    。要找到曲线的局部极值,需要找到切线平坦的参数值(即临界点)。对于垂直尺寸,您希望找到
    t
    的值,以便
    ya(t)=yb(t)
    ,这使切线的斜率为0。对于水平尺寸,找到
    t
    以便
    xa(t)=xb(t)
    ,这使切线具有无限斜率(即垂直线).在每种情况下,您都可以将t的值插回到方程式1(或2,甚至3)中,以获得该极值的位置

    换句话说,要找到曲线的垂直极值,只取方程式1和方程式2的y分量,将它们彼此相等,然后求解
    t
    ;将其插入方程式1的y分量,以获得该极值的y值。要获得曲线的完整y范围,请找到该极值y值和y值的最小值-两个端点的分量,同样找到所有三个端点的最大值。重复x以获得水平极限
    function computeCubicBaseValue(a,b,c,d,t) {
        var mt = 1-t;
        return mt*mt*mt*a + 3*mt*mt*t*b + 3*mt*t*t*c + t*t*t*d; 
    }
    
    function computeCubicFirstDerivativeRoots(a,b,c,d) {
        var ret = [-1,-1];
      var tl = -a+2*b-c;
      var tr = -Math.sqrt(-a*(c-d) + b*b - b*(c+d) +c*c);
      var dn = -a+3*b-3*c+d;
        if(dn!=0) { ret[0] = (tl+tr)/dn; ret[1] = (tl-tr)/dn; }
        return ret; 
    }
    
    function computeCubicBoundingBox(xa,ya,xb,yb,xc,yc,xd,yd)
    {
        // find the zero point for x and y in the derivatives
      var minx = 9999;
      var maxx = -9999;
        if(xa<minx) { minx=xa; }
        if(xa>maxx) { maxx=xa; }
        if(xd<minx) { minx=xd; }
        if(xd>maxx) { maxx=xd; }
        var ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd);
        for(var i=0; i<ts.length;i++) {
          var t = ts[i];
            if(t>=0 && t<=1) {
              var x = computeCubicBaseValue(t, xa, xb, xc, xd);
              var y = computeCubicBaseValue(t, ya, yb, yc, yd);
                if(x<minx) { minx=x; }
                if(x>maxx) { maxx=x; }}}
    
      var miny = 9999;
      var maxy = -9999;
        if(ya<miny) { miny=ya; }
        if(ya>maxy) { maxy=ya; }
        if(yd<miny) { miny=yd; }
        if(yd>maxy) { maxy=yd; }
        ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd);
        for(i=0; i<ts.length;i++) {
          var t = ts[i];
            if(t>=0 && t<=1) {
              var x = computeCubicBaseValue(t, xa, xb, xc, xd);
              var y = computeCubicBaseValue(t, ya, yb, yc, yd);
                if(y<miny) { miny=y; }
                if(y>maxy) { maxy=y; }}}
    
        // bounding box corner coordinates
        var bbox = [minx,miny, maxx,miny, maxx,maxy, minx,maxy ];
        return bbox;
    }
    
    Array.max = function( array ){
      return Math.max.apply( Math, array );
    };
    Array.min = function( array ){
      return Math.min.apply( Math, array );
    };
    
    var findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
            var t1 = 1 - t;
            return {
                x: t1*t1*t1*p1x + t1*t1*3*t*c1x + t1*3*t*t * c2x + t*t*t * p2x,
                y: t1*t1*t1*p1y + t1*t1*3*t*c1y + t1*3*t*t * c2y + t*t*t * p2y
            };
    };
    var cubicBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
            var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
                b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
                c = p1x - c1x,
                t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
                t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
                y = [p1y, p2y],
                x = [p1x, p2x],
                dot, dots=[];
            Math.abs(t1) > "1e12" && (t1 = 0.5);
            Math.abs(t2) > "1e12" && (t2 = 0.5);
            if (t1 >= 0 && t1 <= 1) {
                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
                x.push(dot.x);
                y.push(dot.y);
                dots.push({X:dot.x, Y:dot.y});
            }
            if (t2 >= 0 && t2 <= 1) {
                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
                x.push(dot.x);
                y.push(dot.y);
                dots.push({X:dot.x, Y:dot.y});
            }
            a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
            b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
            c = p1y - c1y;
            t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
            t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
            Math.abs(t1) > "1e12" && (t1 = 0.5);
            Math.abs(t2) > "1e12" && (t2 = 0.5);
            if (t1 >= 0 && t1 <= 1) {
                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
                x.push(dot.x);
                y.push(dot.y);
                dots.push({X:dot.x, Y:dot.y});
            }
            if (t2 >= 0 && t2 <= 1) {
                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
                x.push(dot.x);
                y.push(dot.y);
                dots.push({X:dot.x, Y:dot.y});
            }
            // remove duplicate dots
                    var dots2 = [];
                    var l = dots.length;
                    for(var i=0; i<l; i++) {
                      for(var j=i+1; j<l; j++) {
                        if (dots[i].X === dots[j].X && dots[i].Y === dots[j].Y)
                          j = ++i;
                      }
                      dots2.push({X: dots[i].X, Y: dots[i].Y});
                    }
            return {
            min: {x: Array.min(x), y: Array.min(y)},
            max: {x: Array.max(x), y: Array.max(y)},
            dots: dots2 // these are the extrema points
          };
        };
    
    //(x0,y0) is start point; (x1,y1),(x2,y2) is control points; (x3,y3) is end point.
    function bezierMinMax(x0, y0, x1, y1, x2, y2, x3, y3) {
        var tvalues = [], xvalues = [], yvalues = [],
            a, b, c, t, t1, t2, b2ac, sqrtb2ac;
        for (var i = 0; i < 2; ++i) {
            if (i == 0) {
                b = 6 * x0 - 12 * x1 + 6 * x2;
                a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
                c = 3 * x1 - 3 * x0;
            } else {
                b = 6 * y0 - 12 * y1 + 6 * y2;
                a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
                c = 3 * y1 - 3 * y0;
            }
            if (Math.abs(a) < 1e-12) {
                if (Math.abs(b) < 1e-12) {
                    continue;
                }
                t = -c / b;
                if (0 < t && t < 1) {
                    tvalues.push(t);
                }
                continue;
            }
            b2ac = b * b - 4 * c * a;
            if (b2ac < 0) {
                continue;
            }
            sqrtb2ac = Math.sqrt(b2ac);
            t1 = (-b + sqrtb2ac) / (2 * a);
            if (0 < t1 && t1 < 1) {
                tvalues.push(t1);
            }
            t2 = (-b - sqrtb2ac) / (2 * a);
            if (0 < t2 && t2 < 1) {
                tvalues.push(t2);
            }
        }
    
        var j = tvalues.length, mt;
        while (j--) {
            t = tvalues[j];
            mt = 1 - t;
            xvalues[j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
            yvalues[j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
        }
    
        xvalues.push(x0,x3);
        yvalues.push(y0,y3);
    
        return {
            min: {x: Math.min.apply(0, xvalues), y: Math.min.apply(0, yvalues)},
            max: {x: Math.max.apply(0, xvalues), y: Math.max.apply(0, yvalues)}
        };
    }
    
    CGPoint CubicBezierPointAt(CGPoint p1, CGPoint p2, CGPoint p3, CGPoint p4, CGFloat t) {
    
       CGFloat x = CubicBezier(p1.x, p2.x, p3.x, p4.x, t);
       CGFloat y = CubicBezier(p1.y, p2.y, p3.y, p4.y, t);
    
       return CGPointMake(x, y);
    }
    
    // array containing TopLeft and BottomRight points for curve`s enclosing bounds
    NSArray* CubicBezierExtremums(CGPoint p1, CGPoint p2, CGPoint p3, CGPoint p4) {
    
       CGFloat a, b, c, t, t1, t2, b2ac, sqrtb2ac;
       NSMutableArray *tValues = [NSMutableArray new];
    
       for (int i = 0; i < 2; i++) {
          if (i == 0) {
             a = 3 * (-p1.x + 3 * p2.x - 3 * p3.x + p4.x);
             b = 6 * (p1.x - 2 * p2.x +  p3.x);
             c = 3 * (p2.x - p1.x);
          }
          else {
             a = 3 * (-p1.y + 3 * p2.y - 3 * p3.y + p4.y);
             b = 6 * (p1.y - 2 * p2.y +  p3.y);
             c = 3 * (p2.y - p1.y);
          }
    
          if(ABS(a) < CGFLOAT_MIN) {// Numerical robustness
             if (ABS(b) < CGFLOAT_MIN) {// Numerical robustness
                continue;
             }
    
             t = -c / b;
    
             if (t > 0 && t < 1) {
                [tValues addObject:[NSNumber numberWithDouble:t]];
             }
             continue;
          }
    
          b2ac = pow(b, 2) - 4 * c * a;
    
          if (b2ac < 0) {
             continue;
          }
    
          sqrtb2ac = sqrt(b2ac);
    
          t1 = (-b + sqrtb2ac) / (2 * a);
    
          if (t1 > 0.0 && t1 < 1.0) {
             [tValues addObject:[NSNumber numberWithDouble:t1]];
          }
    
          t2 = (-b - sqrtb2ac) / (2 * a);
    
          if (t2 > 0.0 && t2 < 1.0) {
             [tValues addObject:[NSNumber numberWithDouble:t2]];
          }
       }
    
       int j = (int)tValues.count;
    
       CGFloat x = 0;
       CGFloat y = 0;
       NSMutableArray *xValues = [NSMutableArray new];
       NSMutableArray *yValues = [NSMutableArray new];
    
       while (j--) {
          t = [[tValues objectAtIndex:j] doubleValue];
          x = CubicBezier(p1.x, p2.x, p3.x, p4.x, t);
          y = CubicBezier(p1.y, p2.y, p3.y, p4.y, t);
          [xValues addObject:[NSNumber numberWithDouble:x]];
          [yValues addObject:[NSNumber numberWithDouble:y]];
       }
    
       [xValues addObject:[NSNumber numberWithDouble:p1.x]];
       [xValues addObject:[NSNumber numberWithDouble:p4.x]];
       [yValues addObject:[NSNumber numberWithDouble:p1.y]];
       [yValues addObject:[NSNumber numberWithDouble:p4.y]];
    
       //find minX, minY, maxX, maxY
       CGFloat minX = [[xValues valueForKeyPath:@"@min.self"] doubleValue];
       CGFloat minY = [[yValues valueForKeyPath:@"@min.self"] doubleValue];
       CGFloat maxX = [[xValues valueForKeyPath:@"@max.self"] doubleValue];
       CGFloat maxY = [[yValues valueForKeyPath:@"@max.self"] doubleValue];
    
       CGPoint origin = CGPointMake(minX, minY);
       CGPoint bottomRight = CGPointMake(maxX, maxY);
    
       NSArray *toReturn = [NSArray arrayWithObjects:
                            [NSValue valueWithCGPoint:origin],
                            [NSValue valueWithCGPoint:bottomRight],
                            nil];
    
       return toReturn;
    }