如何计算贝塞尔曲线的面积?

问题描述 投票:24回答:5

给出以下路径(例如)描述SVG立方贝塞尔曲线:“M300,140C300,40,500,40,500,140”,并假设连接端点300,140到500,140的直线(关闭曲线下面积),是否可能计算如此封闭的面积?

任何人都可以建议一个公式(或JavaScript)来实现这一目标吗?

javascript svg
5个回答
49
投票

任意精度的Convert the path to a polygon,然后calculate the area of the polygon

互动演示:Area of Path via Subdivision

以上演示的核心是使用adaptively subdividing path into a polygoncomputing the area of a polygon的函数:

// path:      an SVG <path> element
// threshold: a 'close-enough' limit (ignore subdivisions with area less than this)
// segments:  (optional) how many segments to subdivisions to create at each level
// returns:   a new SVG <polygon> element
function pathToPolygonViaSubdivision(path,threshold,segments){
  if (!threshold) threshold = 0.0001; // Get really, really close
  if (!segments)  segments = 3;       // 2 segments creates 0-area triangles

  var points = subdivide( ptWithLength(0), ptWithLength( path.getTotalLength() ) );
  for (var i=points.length;i--;) points[i] = [points[i].x,points[i].y];

  var doc  = path.ownerDocument;
  var poly = doc.createElementNS('http://www.w3.org/2000/svg','polygon');
  poly.setAttribute('points',points.join(' '));
  return poly;

  // Record the distance along the path with the point for later reference
  function ptWithLength(d) {
    var pt = path.getPointAtLength(d); pt.d = d; return pt;
  }

  // Create segments evenly spaced between two points on the path.
  // If the area of the result is less than the threshold return the endpoints.
  // Otherwise, keep the intermediary points and subdivide each consecutive pair.
  function subdivide(p1,p2){
    var pts=[p1];
    for (var i=1,step=(p2.d-p1.d)/segments;i<segments;i++){
      pts[i] = ptWithLength(p1.d + step*i);
    }
    pts.push(p2);
    if (polyArea(pts)<=threshold) return [p1,p2];
    else {
      var result = [];
      for (var i=1;i<pts.length;++i){
        var mids = subdivide(pts[i-1], pts[i]);
        mids.pop(); // We'll get the last point as the start of the next pair
        result = result.concat(mids)
      }
      result.push(p2);
      return result;
    }
  }

  // Calculate the area of an polygon represented by an array of points
  function polyArea(points){
    var p1,p2;
    for(var area=0,len=points.length,i=0;i<len;++i){
      p1 = points[i];
      p2 = points[(i-1+len)%len]; // Previous point, with wraparound
      area += (p2.x+p1.x) * (p2.y-p1.y);
    }
    return Math.abs(area/2);
  }
}
// Return the area for an SVG <polygon> or <polyline>
// Self-crossing polys reduce the effective 'area'
function polyArea(poly){
  var area=0,pts=poly.points,len=pts.numberOfItems;
  for(var i=0;i<len;++i){
    var p1 = pts.getItem(i), p2=pts.getItem((i+-1+len)%len);
    area += (p2.x+p1.x) * (p2.y-p1.y);
  }
  return Math.abs(area/2);
}

以下是原始答案,它使用不同的(非自适应)技术将<path>转换为<polygon>

Interactive Demo: http://phrogz.net/svg/area_of_path.xhtml

上面的演示使用了approximating a path with a polygoncomputing the area of a polygon的函数。

// Calculate the area of an SVG polygon/polyline
function polyArea(poly){
  var area=0,pts=poly.points,len=pts.numberOfItems;
  for(var i=0;i<len;++i){
    var p1 = pts.getItem(i), p2=pts.getItem((i+len-1)%len);
    area += (p2.x+p1.x) * (p2.y-p1.y);
  }
  return Math.abs(area/2);
}

// Create a <polygon> approximation for an SVG <path>
function pathToPolygon(path,samples){
  if (!samples) samples = 0;
  var doc = path.ownerDocument;
  var poly = doc.createElementNS('http://www.w3.org/2000/svg','polygon');

  // Put all path segments in a queue
  for (var segs=[],s=path.pathSegList,i=s.numberOfItems-1;i>=0;--i)
    segs[i] = s.getItem(i);
  var segments = segs.concat();

  var seg,lastSeg,points=[],x,y;
  var addSegmentPoint = function(s){
    if (s.pathSegType == SVGPathSeg.PATHSEG_CLOSEPATH){

    }else{
      if (s.pathSegType%2==1 && s.pathSegType>1){
        x+=s.x; y+=s.y;
      }else{
        x=s.x; y=s.y;
      }          
      var last = points[points.length-1];
      if (!last || x!=last[0] || y!=last[1]) points.push([x,y]);
    }
  };
  for (var d=0,len=path.getTotalLength(),step=len/samples;d<=len;d+=step){
    var seg = segments[path.getPathSegAtLength(d)];
    var pt  = path.getPointAtLength(d);
    if (seg != lastSeg){
      lastSeg = seg;
      while (segs.length && segs[0]!=seg) addSegmentPoint( segs.shift() );
    }
    var last = points[points.length-1];
    if (!last || pt.x!=last[0] || pt.y!=last[1]) points.push([pt.x,pt.y]);
  }
  for (var i=0,len=segs.length;i<len;++i) addSegmentPoint(segs[i]);
  for (var i=0,len=points.length;i<len;++i) points[i] = points[i].join(',');
  poly.setAttribute('points',points.join(' '));
  return poly;
}

11
投票

我犹豫是要发表评论或完整回复。但是,简单的谷歌搜索“区域贝塞尔曲线”导致前三个链接(第一个是同一个帖子),在:

http://objectmix.com/graphics/133553-area-closed-bezier-curve.htmlarchived

使用发散定理提供封闭形式的解决方案。我很惊讶OP没有找到这个链接。

如果网站出现故障,请复制文本,并将回复的作者Kalle Rutanen记入帐户:

一个有趣的问题。对于2D中的任何分段可微曲线,以下一般过程为您提供曲线/曲线系列内的区域。对于多项式曲线(贝塞尔曲线),您将获得闭合形式的解。

设g(t)为分段可微曲线,0 <= t <= 1.g(t)顺时针定向,g(1)= g(0)。

设F(x,y)= [x,y] / 2

然后div(F(x,y))= 1,其中div表示发散。

现在,散度定理给出了闭合曲线g(t)内的区域作为沿曲线的线积分:

int(点(F(g(t)),perp(g'(t)))dt,t = 0..1)=(1/2)* int(点(g(t),perp(g') (t)))dt,t = 0..1)

perp(x,y)=( - y,x)

其中int用于集成,'用于区分,点用于点积。必须将积分拼接到与平滑曲线段对应的部分。

现在举个例子。取Bezier度3和一个这样的曲线与控制点(x0,y0),(x1,y1),(x2,y2),(x3,y3)。该曲线的积分是:

I:= 3/10 * y1 * x0 - 3/20 * y1 * x2 - 3/20 * y1 * x3 - 3/10 * y0 * x1 - 3/20 * y0 * x2 - 1/20 * y0 * x3 + 3/20 * y2 * x0 + 3/20 * y2 * x1 - 3/10 * y2 * x3 + 1/20 * y3 * x0 + 3/20 * y3 * x1 + 3/10 * y3 * x2

为序列中的每条曲线计算并加起来。总和是曲线包围的区域(假设曲线形成一个循环)。

如果曲线只包含一条贝塞尔曲线,那么它必须是x3 = x0和y3 = y0,并且面积为:

面积:= 3/20 * y1 * x0 - 3/20 * y1 * x2 - 3/20 * y0 * x1 + 3/20 * y0 * x2 - 3/20 * y2 * x0 + 3/20 * y2 * x1

希望我没有犯错误。

- Kalle Rutanen http://kaba.hilvi.org


2
投票

首先,我对bezier曲线并不那么熟悉,但我知道它们是连续的函数。如果确保三次曲线不与自身相交,则可以在给定的封闭域([ab])上将其以闭合形式(我的意思是使用解析积分)进行积分,并减去由末端形成的三角形区域。连接直线和X轴。如果与Bezier曲线和末端连接直线相交,您可以分成几个部分并尝试以一致的方式分别计算每个区域。

对我来说,合适的搜索词是“连续函数积分”“积分”“函数下的区域”“微积分”

当然,您可以从贝塞尔曲线fn生成离散数据,并获得离散的X-Y数据并近似计算积分。


2
投票

我喜欢Phrogz接受的答案中的解决方案,但我也看得更远,并找到了一种方法,使用CompoundPath类和area属性对Paper.js做同样的事情。 See my Paper.js demo

使用阈值0时,结果(表面积= 11856)与Phrogz's demo完全相同,但处理速度似乎更快!我知道加载Paper.js只是为了计算表面积是有点过头了,但是如果你正在考虑实现一个框架或者想要调查Paper.js是如何做到的那样......


1
投票

我有同样的问题,但我没有使用JavaScript,所以我不能使用@Phrogz接受的答案。 此外,在接受的答案中使用的SVGPathElement.getPointAtLength()根据Mozilla弃用。

当用点qazxsw poi,qazxsw poi,qazxsw poi和qazxsw poi描述Bézier曲线时(其中(x0/y0)是起点,(x1/y1)是终点),你可以使用参数化形式:

(x2/y2)(来源:(x3/y3)

B(t)是Bézier曲线上的点,Pi是Bézier曲线定义点(见上文,P0是起点,......)。 t是0≤t≤1的运行变量。

这种形式可以很容易地逼近Bézier曲线:您可以使用t = i / npoints生成任意多个点。 (请注意,您必须添加起点和终点)。结果是一个多边形。然后你可以使用(x0/y0)(就像@Phrogz在他的解决方案中所做的那样)来计算面积。请注意,对于鞋带公式,点的顺序很重要。通过使用t作为参数,订单将始终是正确的。

(x3/y3)

为了匹配这里的问题,代码片段中的交互式示例也是用javascript编写的。这可以用于其他语言。它不使用任何javascript(或svg)特定命令(图纸除外)。请注意,这需要支持HTML5的浏览器才能工作。


© www.soinside.com 2019 - 2024. All rights reserved.