【问题标题】:Get the outside points from an array and draw around them to create the biggest shape从数组中获取外部点并围绕它们绘制以创建最大的形状
【发布时间】:2016-05-24 11:46:16
【问题描述】:

好的,伙计们,我所拥有的是一堆较小的形状(所有正方形都有顶部、右侧、底部和左侧 4 个点,每个都有一个 x 和 y)。我从所有正方形中提取了一个包含所有点的数组,如下所示:

[[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1], [2, 2], ... 等等]

可能有数百个正方形聚集在一起形成任何形状。

我想知道的是:

我将如何提取所有点的外围周围的所有点,然后循环遍历它们,以便我可以围绕这些点的外部绘制一条路径,以创建所有正方形的轮廓形状一个集群。

我正在使用 javascript 和画布来绘制我的形状。

干杯。

【问题讨论】:

  • 你会用一些虚拟数据制作一个小的 jsfiddle 吗?这样我就可以尝试一下...@Daniel
  • 通过说...“所有具有 4 点顶部、右侧、底部和左侧的正方形,每个正方形都有一个 x 和 y”你的意思是左上、右上、左下和右下角坐标。?如果每个正方形的 4 个角点都有一组单独的数组,那将是非常合乎逻辑的。但似乎很有趣。我会考虑的。
  • 好吧,想想它们是正方形点的坐标这一事实并不重要,我只是有一堆点,我想在周围画一个轮廓。我会试着做一个小提琴。
  • 好的,知道了.. 但是这些坐标是否按照它们的连接方式排序..?如果不是,那么它可能会成为一个无法解决或多重解决方案的问题,因为您可以通过多种方式连接任意点,这样您仍然可以得到一个不同的闭合形状,并且没有线相互交叉。这就是为什么最好将每个正方形的 4 个点放在一个单独的数组中。在开始之前了解这些很重要,以免最终陷入困境。
  • 要解决这个问题,您需要定义定义任何两个框是否连接的规则。然后将所有连接的盒子分组。从最左上角开始,通过顺时针移动到下一个框进行迭代。如果需要跨越连接,则不能移动到框。当您返回起点时,您已经绑定了该组的所有成员。如果没有您没有给出的连接规则(仅暗示可能是空间的),问题将无法解决。

标签: javascript arrays canvas drawing coordinates


【解决方案1】:

获取外部路径的蛮力方法概述(未优化!)

  1. 从所有矩形的边创建线段,并将所有线段放入一个数组中。线段对象的形状可能是这样的:{id:1,x0:,y0:,x1:,y1:}

    • 红线段:[#1,#6], [#6,#7], [#7,#8], [#8,#1]
    • 蓝色线段:[#3,#4], [#4,#10], [#10,#9], [#9,#3]
  2. 遍历数组并找到最左边的 x0 段。如果有多个段具有最左侧的 x0,则从该子集中选择具有最顶部 y0 的段。 (这是插图中的标记#1)

  3. 将此称为“源片段”(图中的标记#1 到标记#6)

  4. 遍历数组并找到与源段相交的段(如果有)。不要针对自己测试源代码段;-)。您可以使用下面的line-line intersection 算法来查找与源线的交点。线-线相交算法返回 2 条线段的交点(如果有)。 (插图上的标记#2)

  5. 计算源段的 x0,y0 和交点的 x,y 点之间的距离。您可以使用距离公式计算距离(图中标记#1和标记#2之间的距离)

    var dx = intersection.x - source.x0;
    var dy = intersection.y - source.y0;
    var distance=Math.sqrt(dx*dx+dy*dy);
    
  6. 对每个段与源段执行步骤#4-#5,并找到与源段最快相交的段(==距离最小)。 (图中这个相交的线段是marker#9到marker#3)

  7. 如果没有相交线,则使用源线的 x1,y1 并将 x1,y1 称为“交点”。 图中marker#3和marker#4之间有一条不相交的线段。

  8. 在交点处,您必须确定是转向相交线段的 x0,y0 还是转向它的 x1,y1。通过始终转到相交段的 x1,y1 来“顺时针”移动。

  9. 交点 x,y 点和相交线的 x0,y0(或 x1,y1)之间的新线段现在是新的“源线段”。 这个新的源片段是插图上的标记#2 到标记#3

    • 如果新源的末端 x,y 回到您在步骤#2 中找到的相同的原始 x,y,那么您已经求解了周长。恭喜! 当您从插图上的标记#8 移动到标记#1 时会发生这种情况

    • 如果没有,请使用这个新的源片段返回步骤#3。

注意:此方法将仅查找附加(触摸)矩形 - 不会发现任何断开连接的矩形。您可能想要做的另一项任务是查看是否有任何矩形断开连接并决定如何处理断开连接的矩形。 插图上的绿色矩形是断开的。

这个算法会找到两条线段的交点(如果有的话):

// Get interseting point of 2 line segments (if any)
// Attribution: http://paulbourke.net/geometry/pointlineplane/
function line2lineIntersection(p0,p1,p2,p3) {

    var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
    var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
    var denominator  = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);        

    // Test if Coincident
    // If the denominator and numerator for the ua and ub are 0
    //    then the two lines are coincident.    
    if(unknownA==0 && unknownB==0 && denominator==0){return(null);}

    // Test if Parallel 
    // If the denominator for the equations for ua and ub is 0
    //     then the two lines are parallel. 
    if (denominator == 0) return null;

    // If the intersection of line segments is required 
    // then it is only necessary to test if ua and ub lie between 0 and 1.
    // Whichever one lies within that range then the corresponding
    // line segment contains the intersection point. 
    // If both lie within the range of 0 to 1 then 
    // the intersection point is within both line segments. 
    unknownA /= denominator;
    unknownB /= denominator;

    var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)

    if(!isIntersecting){return(null);}

    return({
        x: p0.x + unknownA * (p1.x-p0.x),
        y: p0.y + unknownA * (p1.y-p0.y)
    });
}

【讨论】:

    【解决方案2】:

    这是另一种分析像素以获取组件周长的替代方法。它使用 Marching Squares 算法来获取该周长。 Stackoverflow 的 K3N 做了一个很好的Marching Squares script,它还允许简化生成的点集。

    然后将每个周长点捕捉到已知的矩形顶点或已知的矩形交点。

    这是示例代码和演示:

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    
    var r1={x:20,y:60,w:100,h:40,color:'red',id:0}
    var r2={x:50,y:20,w:40,h:60,color:'blue',id:1}
    var r3={x:100,y:80,w:100,h:60,color:'green',id:2}
    var r4={x:160,y:80,w:70,h:60,color:'purple',id:3}
    var rects=[r1,r2,r3,r4];
    var rpts=[];
    
    ctx.translate(0.50,0.50);
    var nextId=0;
    for(var i=0;i<rects.length;i++){
        var r=rects[i];
        var x=r.x;
        var y=r.y;
        var w=r.w;
        var h=r.h;
        ctx.strokeStyle=r.color;
        ctx.strokeRect(x,y,w,h);
        rpts.push({x:x,y:y},{x:x+w,y:y},{x:x+w,y:y+h},{x:x,y:y+h});
    }
    
    function multiRectVertices(rects){
        var pts=[];
        var segs=[];
    
        // rectangle vertices
        for(var r=0;r<rects.length;r++){
            var rect=rects[r];
            var x=rect.x;
            var y=rect.y;
            var w=rect.w;
            var h=rect.h;
            pts.push([x,y]);
            pts.push([x+w,y]);
            pts.push([x+w,y+h]);
            pts.push([x,y+h]);
            segs.push({x0:x,   y0:y,   x1:x+w, y1:y});
            segs.push({x0:x+w, y0:y,   x1:x+w, y1:y+h});
            segs.push({x0:x+w, y0:y+h, x1:x,   y1:y+h});
            segs.push({x0:x,   y0:y+h, x1:x,   y1:y});
        }
    
        // intersection points
        for(var s=0;s<segs.length;s++){
            line0=segs[s];
            for(var i=s+1;i<segs.length;i++){
                var intersection=line2lineIntersection(line0,segs[i]);
                if(intersection){ pts.push([intersection.x,intersection.y]);}
            }
        }
    
        //
        return(pts);
    }
    
    // get rect vertices and intersections
    var rectPts=multiRectVertices(rects);
    
    // get simplified perimeter based on pixels
    // Attribution: K3N on Stackoverflow, epistemex on GitHub: 
    // https://github.com/epistemex/msqr 
    var msPts=MSQR(ctx,{alpha:200,tolerance:1.1})[0];
    
    alignMSPts(msPts,rectPts);
    
    function alignMSPts(msPts,rectPts){
        var pt,rpt,px,py,rx,ry,dx,dy,minDist,dist;
        for(var i=0;i<msPts.length;i++){
            minDist=1000000*100000;
            pt=msPts[i];
            px=pt.x;
            py=pt.y;
            for(var j=0;j<rectPts.length;j++){
                rpt=rectPts[i];
                rx=rpt.x;
                ry=rpt.y;
                dx=px-rx;
                dy=py-ry;
                dist=dx*dx+dy*dy;
                if(dist<minDist){
                    minDist=dist;
                    msPts[i]={x:rx,y:ry};
                }
            }
        }
    }
    
    ctx.beginPath();
    ctx.moveTo(msPts[0].x,msPts[0].y);
    for(var i=1;i<msPts.length;i++){
        ctx.lineTo(msPts[i].x,msPts[i].y);
    }
    ctx.strokeStyle='black';
    ctx.lineWidth=3;
    ctx.closePath();
    ctx.stroke();
    
    
    ctx.fillStyle='gold';
    for(var i=0;i<msPts.length;i++){
        ctx.beginPath();
        ctx.arc(msPts[i].x,msPts[i].y,3,0,Math.PI*2);
        ctx.fill();
    }
    
    
    // Get interseting point of 2 line segments (if any)
    // Attribution: http://paulbourke.net/geometry/pointlineplane/
    // Segment #1: [x0,y0] to [x1,y1], Segment#2: [x2,y2] to [x3,y3]
    function line2lineIntersection(line0,line1){
        var x0=line0.x0;
        var y0=line0.y0;
        var x1=line0.x1;
        var y1=line0.y1;
        var x2=line1.x0;
        var y2=line1.y0;
        var x3=line1.x1;
        var y3=line1.y1;
        var unknownA = (x3-x2) * (y0-y2) - (y3-y2) * (x0-x2);
        var unknownB = (x1-x0) * (y0-y2) - (y1-y0) * (x0-x2);
        var denominator  = (y3-y2) * (x1-x0) - (x3-x2) * (y1-y0);        
        if(unknownA==0 && unknownB==0 && denominator==0){return(null);}
        if (denominator == 0) return null;
        unknownA /= denominator;
        unknownB /= denominator;
        var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)
        if(!isIntersecting){return(null);}
        return({
            x: x0 + unknownA * (x1-x0),
            y: y0 + unknownA * (y1-y0)
        });
    }
    body{ background-color:white; }
    #canvas{border:1px solid red; margin:0 auto; }
    <script src="https://cdn.rawgit.com/epistemex/msqr/master/msqr.min.js"></script>
    <canvas id="canvas" width=300 height=300></canvas>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-18
      • 2013-11-10
      • 1970-01-01
      • 1970-01-01
      • 2013-12-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多