好的伙计们,所以我拥有的是一堆较小的形状(所有正方形有4个点,顶部,右侧,底部和左侧,每个都有x和y)。我从所有正方形中提取了所有点的数组,如下所示:
[[0,0],[0,1],[1,0],[1,1],[2,0],[2,1],[2,2],......等]
可能有数百个正方形聚集在一起形成任何形状。
我想知道的是:
如何提取所有点周围的所有点,然后遍历它们,以便我可以在这些点的外部绘制一条路径,以创建所有正方形的轮廓形状一个集群。
我使用javascript和画布绘制我的形状。
干杯。
答案 0 :(得分:1)
获取外部路径的强力方法概述(未优化!)
从所有矩形的边创建线段,并将所有线段放在一个数组中。线段对象的形状可能如下:{id:1,x0:,y0:,x1:,y1:}
循环遍历数组,找到最左边的x0段。如果最左边的x0有多个段,则从该子集中选择具有最顶层y0的段。 (这是插图中的标记#1)
将其称为"来源细分" (标记#1到插图标记#6)。
循环遍历数组并找到与源段相交的段(如果有)。不要对自己的源段进行测试;-)。您可以使用下面的line-line intersection
算法查找与源代码行的交集。线 - 线交叉算法返回2个线段的交叉点(如果有的话)。 (插图中标记#2)
计算源段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);
对于每个段与源段执行步骤#4-#5,并找到与源段最快相交的段(==距离最小)。 (此交叉段是标记#9到图示标记#3)
如果没有相交线,则使用源线的x1,y1,并将x1,y1称为"交叉点"。 没有交叉点的线段位于插图上的标记#3和标记#4之间。
在交叉点处,您必须确定是否转向相交的段x0,y0或朝向它的x1,y1。顺时针方向"通过始终前往交叉段x1,y1。
交叉点x,y点和交叉线x0,y0(或x1,y1)之间的新线段现在是新的"源段"。 这个新的源片段是插图
上标记#3到标记#3的标记#2如果新源的结束x,y返回到您在步骤#2中找到的相同原始x,y,那么您已经解决了周长。恭喜! 当您从插图标记#8行至标记#1时,会发生这种情况
如果没有,请使用此新的源代码段返回步骤#3。
注意:此方法仅查找附加(触摸)的行 - 不会发现任何断开连接的矩形。您可能想要做的另一项任务是查看是否有任何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,它也允许简化得到的点集。
然后将每个周边点捕捉到已知的矩形顶点或已知的矩形交点。
以下是示例代码和演示:
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;