从数组中获取外部点并围绕它们绘制以创建最大的形状

时间:2016-05-24 11:46:17

标签: javascript arrays canvas drawing coordinates

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

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

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

我想知道的是:

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

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

干杯。

2 个答案:

答案 0 :(得分:1)

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

enter image description here

  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,并找到与源段最快相交的段(==距离最小)。 (此交叉段是标记#9到图示标记#3)

  7. 如果没有相交线,则使用源线的x1,y1,并将x1,y1称为"交叉点"。 没有交叉点的线段位于插图上的标记#3和标记#4之间。

  8. 在交叉点处,您必须确定是否转向相交的段x0,y0或朝向它的x1,y1。顺时针方向"通过始终前往交叉段x1,y1。

  9. 交叉点x,y点和交叉线x0,y0(或x1,y1)之间的新线段现在是新的"源段"。 这个新的源片段是插图

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

    • 如果没有,请使用此新的源代码段返回步骤#3。

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

    此算法将找到2个线段的交点(如果有):

    // 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)
        });
    }
    

答案 1 :(得分:1)

这是另一种分析像素以获取装配周长的替代方案。它使用Marching Squares算法来获取周长。 Stackoverflow的K3N做了一个很好的Marching Squares script,它也允许简化得到的点集。

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

enter image description here

以下是示例代码和演示:

&#13;
&#13;
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)
    });
}
&#13;
body{ background-color:white; }
#canvas{border:1px solid red; margin:0 auto; }
&#13;
<script src="https://cdn.rawgit.com/epistemex/msqr/master/msqr.min.js"></script>
<canvas id="canvas" width=300 height=300></canvas>
&#13;
&#13;
&#13;