展开凸多边形的填充

时间:2010-09-20 08:18:18

标签: language-agnostic math geometry polygon computational-geometry

我有P1点的凸多边形N。这个多边形可以是任何形状或比例(只要它仍然是凸的)。

我需要使用原始多边形几何计算另一个多边形P2,但需要按给定数量的单位“展开”。算法可以用于扩展凸多边形?

5 个答案:

答案 0 :(得分:38)

Shapes with their inflated equivalents

要展开凸多边形,请绘制与每条边平行的线和给定数量的单位。然后使用新线的交点作为扩展多边形的顶点。最后的javascript / canvas遵循这个功能细分:

第1步:确定哪一方是“出局”

顶点(点)的顺序很重要。在凸多边形中,它们可以按顺时针(CW)或逆时针(CCW)顺序列出。在CW多边形中,将其中一个边缘旋转90度 CCW 以获得向外的法线。在CCW多边形上,改为 CW

CW and CCW polygons

如果事先不知道顶点的转弯方向,请检查第二条边从第一条边开始转动。在凸多边形中,剩余边缘将继续朝相同方向转动:

  1. Find the CW normal of the first edge。我们还不知道它是向内还是向外。

  2. 使用我们计算的法线计算第二条边的dot product。如果第二个边缘变为CW,则点积将为正。否则将是否定的。

  3. Dot product to find turn direction

    数学:

    // in vector terms:
    v01 = p1 - p0                      // first edge, as a vector
    v12 = p2 - p1                      // second edge, as a vector
    n01 = (v01.y, -v01.x)              // CW normal of first edge
    d = v12 * n01                      // dot product
    
    // and in x,y terms:
    v01 = (p1.x-p0.x, p1.y-p0.y)       // first edge, as a vector
    v12 = (p2.x-p1.x, p2.y-p1.y)       // second edge, as a vector
    n01 = (v01.y, -v01.x)              // CW normal of first edge
    d = v12.x * n01.x + v12.y * n01.y; // dot product: v12 * n01
    
    if (d > 0) {
        // the polygon is CW
    } else {
        // the polygon is CCW
    }
    
    // and what if d==0 ?
    // -- that means the second edge continues in the same
    // direction as a first.  keep looking for an edge that
    // actually turns either CW or CCW.
    

    代码:

    function vecDot(v1, v2) {
        return v1.x * v2.x + v1.y * v2.y;
    }
    
    function vecRot90CW(v) {
        return { x: v.y, y: -v.x };
    }
    
    function vecRot90CCW(v) {
        return { x: -v.y, y: v.x };
    }
    
    function polyIsCw(p) {
        return vecDot(
            vecRot90CW({ x: p[1].x - p[0].x, y: p[1].y - p[0].y }),
            { x: p[2].x - p[1].x, y: p[2].y - p[1].y }) >= 0;
    }
    
    var rot = polyIsCw(p) ? vecRot90CCW : vecRot90CW;
    

    第2步:找到与多边形边平行的线

    现在我们知道哪一侧出来了,我们可以精确地计算与每个多边形边缘平行的线。这是我们的策略:

    1. 对于每条边,计算其向外的法线

    2. Normalize正常,使其长度变为一个单位

    3. 将法线乘以我们希望扩展多边形来自原始的距离

    4. 将乘法法线添加到边的两端。这将在平行线上给出两点。这两点足以定义平行线。

    5. Parallel line by adding a weighted normal vector

      代码:

      // given two vertices pt0 and pt1, a desired distance, and a function rot()
      // that turns a vector 90 degrees outward:
      
      function vecUnit(v) {
          var len = Math.sqrt(v.x * v.x + v.y * v.y);
          return { x: v.x / len, y: v.y / len };
      }
      
      function vecMul(v, s) {
          return { x: v.x * s, y: v.y * s };
      }
      
      var v01 = { x: pt1.x - pt0.x, y: pt1.y - pt0.y };  // edge vector
      var d01 = vecMul(vecUnit(rot(v01)), distance);     // multiplied unit normal
      var ptx0 = { x: pt0.x + d01.x, y: pt0.y + d01.y }; // two points on the
      var ptx1 = { x: pt1.x + d01.x, y: pt1.y + d01.y }; //     parallel line
      

      第3步:计算平行线的交叉点

      - 这些将是扩展多边形的顶点。

      intersection of expanded polygon edges

      数学:

      通过两点 P1 P2 的一行可以描述为:

      P = P1 + t * (P2 - P1)
      

      两行可以描述为

      P = P1 + t * (P2 - P1)
      P = P3 + u * (P4 - P3)
      

      他们的交集必须在两条线上:

      P = P1 + t * (P2 - P1) = P3 + u * (P4 - P3)
      

      这可以按摩看起来像:

      (P2 - P1) * t + (P3 - P4) * u = P3 - P1
      

      以x,y表示的是:

      (P2.x - P1.x) * t + (P3.x - P4.x) * u = P3.x - P1.x
      (P2.y - P1.y) * t + (P3.y - P4.y) * u = P3.y - P1.y
      

      由于点P1,P2,P3和P4是已知的,因此以下值也是如此:

      a1 = P2.x - P1.x    a2 = P2.y - P1.y
      b1 = P3.x - P4.x    b2 = P3.y - P4.y
      c1 = P3.x - P1.x    c2 = P3.y - P1.y
      

      这将我们的等式缩短为:

      a1*t + b1*u = c1
      a2*t + b2*u = c2    
      

      解决 t 让我们:

      t = (b1*c2 - b2*c1)/(a2*b1 - a1*b2)
      

      让我们在P = P1 + t * (P2 - P1)找到交叉点。

      代码:

      function intersect(line1, line2) {
          var a1 = line1[1].x - line1[0].x;
          var b1 = line2[0].x - line2[1].x;
          var c1 = line2[0].x - line1[0].x;
      
          var a2 = line1[1].y - line1[0].y;
          var b2 = line2[0].y - line2[1].y;
          var c2 = line2[0].y - line1[0].y;
      
          var t = (b1*c2 - b2*c1) / (a2*b1 - a1*b2);
      
          return {
              x: line1[0].x + t * (line1[1].x - line1[0].x),
              y: line1[0].y + t * (line1[1].y - line1[0].y)
          };
      }
      

      第4步:处理特殊情况

      有许多特殊情况需要引起注意。作为练习留给读者......

      1. 当两条边之间存在非常尖锐的角度时,展开的顶点可能非常远离原始顶点。如果超出某个阈值,您可能需要考虑剪切扩展边缘。在极端情况下,角度为零,这表明扩展顶点处于无穷大,导致算术中除以零。小心。

      2. 当前两条边在同一条线上时,您无法通过查看它们来判断它是CW还是CCW多边形。看看更多的边缘。

      3. 非凸多边形更有趣......并且不在这里处理。

      4. 完整示例代码

        将其放入支持画布的浏览器中。我在Windows上使用了Chrome 6。三角形及其扩展版本应该有效。

        browser snapshot

        <html>
            <head>
                    <style type="text/css">
                        canvas { border: 1px solid #ccc; }
                    </style>
                    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
                    <script type="text/javascript">
                        $(function() {
                            var canvas = document.getElementById('canvas');  
                            if (canvas.getContext) {  
                                var context = canvas.getContext('2d');  
        
                                // math for expanding a polygon
        
                                function vecUnit(v) {
                                    var len = Math.sqrt(v.x * v.x + v.y * v.y);
                                    return { x: v.x / len, y: v.y / len };
                                }
        
                                function vecMul(v, s) {
                                    return { x: v.x * s, y: v.y * s };
                                }
        
                                function vecDot(v1, v2) {
                                    return v1.x * v2.x + v1.y * v2.y;
                                }
        
                                function vecRot90CW(v) {
                                    return { x: v.y, y: -v.x };
                                }
        
                                function vecRot90CCW(v) {
                                    return { x: -v.y, y: v.x };
                                }
        
                                function intersect(line1, line2) {
                                    var a1 = line1[1].x - line1[0].x;
                                    var b1 = line2[0].x - line2[1].x;
                                    var c1 = line2[0].x - line1[0].x;
        
                                    var a2 = line1[1].y - line1[0].y;
                                    var b2 = line2[0].y - line2[1].y;
                                    var c2 = line2[0].y - line1[0].y;
        
                                    var t = (b1*c2 - b2*c1) / (a2*b1 - a1*b2);
        
                                    return {
                                        x: line1[0].x + t * (line1[1].x - line1[0].x),
                                        y: line1[0].y + t * (line1[1].y - line1[0].y)
                                    };
                                }
        
                                function polyIsCw(p) {
                                    return vecDot(
                                        vecRot90CW({ x: p[1].x - p[0].x, y: p[1].y - p[0].y }),
                                        { x: p[2].x - p[1].x, y: p[2].y - p[1].y }) >= 0;
                                }
        
                                function expandPoly(p, distance) {
                                    var expanded = [];
                                    var rot = polyIsCw(p) ? vecRot90CCW : vecRot90CW;
        
                                    for (var i = 0; i < p.length; ++i) {
        
                                        // get this point (pt1), the point before it
                                        // (pt0) and the point that follows it (pt2)
                                        var pt0 = p[(i > 0) ? i - 1 : p.length - 1];
                                        var pt1 = p[i];
                                        var pt2 = p[(i < p.length - 1) ? i + 1 : 0];
        
                                        // find the line vectors of the lines going
                                        // into the current point
                                        var v01 = { x: pt1.x - pt0.x, y: pt1.y - pt0.y };
                                        var v12 = { x: pt2.x - pt1.x, y: pt2.y - pt1.y };
        
                                        // find the normals of the two lines, multiplied
                                        // to the distance that polygon should inflate
                                        var d01 = vecMul(vecUnit(rot(v01)), distance);
                                        var d12 = vecMul(vecUnit(rot(v12)), distance);
        
                                        // use the normals to find two points on the
                                        // lines parallel to the polygon lines
                                        var ptx0  = { x: pt0.x + d01.x, y: pt0.y + d01.y };
                                        var ptx10 = { x: pt1.x + d01.x, y: pt1.y + d01.y };
                                        var ptx12 = { x: pt1.x + d12.x, y: pt1.y + d12.y };
                                        var ptx2  = { x: pt2.x + d12.x, y: pt2.y + d12.y };
        
                                        // find the intersection of the two lines, and
                                        // add it to the expanded polygon
                                        expanded.push(intersect([ptx0, ptx10], [ptx12, ptx2]));
                                    }
                                    return expanded;
                                }
        
                                // drawing and animating a sample polygon on a canvas
        
                                function drawPoly(p) {
                                    context.beginPath();
                                    context.moveTo(p[0].x, p[0].y);
                                    for (var i = 0; i < p.length; ++i) {
                                        context.lineTo(p[i].x, p[i].y);
                                    }
                                    context.closePath();
                                    context.fill();
                                    context.stroke();
                                }
        
                                function drawPolyWithMargin(p, margin) {
                                    context.fillStyle = "rgb(255,255,255)";  
                                    context.strokeStyle = "rgb(200,150,150)";  
                                    drawPoly(expandPoly(p, margin));
        
                                    context.fillStyle = "rgb(150,100,100)";  
                                    context.strokeStyle = "rgb(200,150,150)";  
                                    drawPoly(p);
                                }
        
                                var p = [{ x: 100, y: 100 }, { x: 200, y: 120 }, { x: 80, y: 200 }];
                                setInterval(function() {
                                    for (var i in p) {
                                        var pt = p[i];
                                        if (pt.vx === undefined) {
                                            pt.vx = 5 * (Math.random() - 0.5);
                                            pt.vy = 5 * (Math.random() - 0.5);
                                        }
        
                                        pt.x += pt.vx;
                                        pt.y += pt.vy;
        
                                        if (pt.x < 0 || pt.x > 400) { pt.vx = -pt.vx; }
                                        if (pt.y < 0 || pt.y > 400) { pt.vy = -pt.vy; }
                                    }
                                    context.clearRect(0, 0, 800, 400);
                                    drawPolyWithMargin(p, 10);
                                }, 50);
                            }
                        });
                    </script>
            </head>
            <body>
                <canvas id="canvas" width="400" height="400"></canvas>  
            </body>
        </html>
        

        示例代码免责声明:

        • 为了清楚起见,样本牺牲了一些效率。在您的代码中,您可能只想计算每个边的扩展并行一次,而不是在此处计算两次

        • 画布的y坐标向下增长,反转CW / CCW逻辑。事情继续有效,因为我们只需要在与多边形相反的方向上转动向外法线 - 并且两者都被翻转。

答案 1 :(得分:1)

如果多边形以原点为中心,只需将每个点乘以一个公共比例因子即可。

如果多边形不在原点上居中,则首先进行平移,使中心位于原点,缩放,然后将其转换回原点。

发表评论后

您似乎希望所有点都移动距离原点相同的距离。 您可以通过将标准化向量移到此点来为每个点执行此操作。将其乘以“展开常数”并将结果向量添加回原始点。

n.b。如果中心不是此解决方案的起源,您仍需要翻译 - 修改 - 翻译。

答案 2 :(得分:1)

对于原始的每个线段,找到段的中点m和(单位长度)向外的正常u。然后,扩展多边形的相应段将位于通过m + n * u(您希望用n扩展原始值)的线上,使用正常的u。要找到扩展多边形的顶点,您需要找到连续线对的交集。

答案 3 :(得分:0)

让多边形的点为A1,B1,C1 ...现在你有从A1到B1的线,然后从B1到C1 ...我们想要计算多边形P2的点A2,B2,C2。

如果平分角度,例如A1 B1 C1,您将有一条沿您想要的方向前进的线。现在你可以在它上面找到一个B2点,它是距离平分线B1的适当距离。 对多边形P1的所有点重复此操作。

答案 4 :(得分:0)

看直骷髅。正如在这里暗示的那样,非凸多边形存在许多棘手的问题,你可以毫不留情地实现这些问题!