对随机线和弧进行排序
如何从线段和弧段的随机列表中创建连续路径,其中每条线段和弧段的方向是随机的。
步骤 1 圆弧端点
要填充形状,您需要等到所有线条和弧线都完成。然后你必须对它们进行排序,使它们形成一个连续的轮廓。
为此,您将需要起点和终点,它们是直线的起点和终点,但不是弧的坐标,因此您需要计算它们。
此函数将起点和终点添加到圆弧中。
function arcEnds(arc){
const d2r = d => d * Math.PI / 180; // deg to rad
arc.startPoint = {
x : Math.cos(d2r(arc.startAngle)) * arc.radius + arc.center.x,
y : Math.sin(d2r(arc.startAngle)) * arc.radius + arc.center.y
}
arc.endPoint = {
x : Math.cos(d2r(arc.endAngle)) * arc.radius + arc.center.x,
y : Math.sin(d2r(arc.endAngle)) * arc.radius + arc.center.y
}
return arc;
}
步骤 2 创建单个数组
要优化线的构造,您需要创建一个同时包含线和弧的数组。当您这样做时,您还可以计算圆弧端点。
function createSegmentArray(lines, arcs) {
return [...lines, ...arcs.map(arcEnds)]; // add lines and end point calculated arcs
}
步骤 3 创建函数以匹配点
您可以预期在端点计算中会出现一些浮点错误,因此您需要有一个容差,以便确定 2 个点是否相等。
有两种方法可以查看两个点是否在同一位置。我将使用距离的平方,因为它更整洁一些。
// 1 pixel tolerance
const isSame = (p1,p2) => (((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2)) < 1;
步骤 4 创建未排序和排序的数组
所以现在你创建数组 pf 段
const segs = createSegmentArray(image.lines, image.arcs);
创建第二个数组来保存有序段。
const orderedSegs = [];
Step 5 按匹配的终点或起点排序
然后从第一个段开始,将其从 segs 数组中删除,并找到匹配结束或开始的段(我们还不知道方向)。如果未找到匹配的端点,则形状未关闭且无法填充,因此抛出错误以指示这一点。您将不得不添加一个 catch 处理程序。或者您可能更喜欢跳出循环并标记一个错误
您还需要反转方向错误的段。对于一条线,您只需交换端点,对于圆,您需要交换端点和端点角度,并添加一个标志以指示方向已反转。
var current = segs.shift();
while(seg.length > 0){
let reverse; // if true the segment needs to be reversed
const index = segs.findIndex(seg => { // find segment with matching end or start
if(isSame(current.endPoint, seg.startPoint)){
reverse = false;
return true
}
if(isSame(current.endPoint, seg.endPoint)){
reverse = true;
return true
}
return false;
})
if(index === -1){ throw new Error("The shape is not closed and can not be filled") }
orderedSegs.push(current);
current = segs.splice(index,1)[0]; // get the connected seg
if(reverse){
if(current.center){ // is a circle
const t = current.endPoint;
current.endPoint = current.startPoint;
current.startPoint = t;
const t1 = current.endAngle;
current.endAngle= current.startAngle;
current.startAngle = t1;
current.reversed = true;
}else{
const t = current.endPoint;
current.endPoint = current.startPoint;
current.startPoint = t;
}
}
// loop until no more segments
}
// push the last seg onto the array
orderedSegs.push(current);
步骤 6 渲染结果。
现在所有点都按它们连接的顺序排列,你可以按该顺序渲染它们,但你必须反转弧线的方向
const d2r = d => d * Math.PI / 180; // deg to rad
var i;
ctx.beginPath();
// arcs have start points now so dont have to check type for first point
ctx.moveTo(orderedSegs[0].startPoint.x, orderedSegs[0].startPoint.y);
for(i = 0; i < orderedSegs.length; i++){
var seg = orderedSegs[i];
if(seg.center){ // is a arc
ctx.arc(
seg.center.x, seg.center.y, seg.radius,
d2r(seg.startAngle), d2r(seg.endAngle), seg.reverse
);
}else{
ctx.lineTo(seg.endPoint.x, seg.endPoint.y);
}
}
ctx.fill();
ctx.stroke();
这就是过程。
警告
如果形状上有孔,这将不起作用。对于每个段,必须有一个段具有匹配的结束点或起点。
如果第一个和最后一个之间没有段,它将起作用。它假设它们之间有一条线段,不会表示路径没有闭合。您将需要测试orderedSegs 数组中第一段的起点与orderedSegs 数组中最后一段的终点相同的isSame
如果有两个以上段连接的点,这将不起作用。如果是这样,创建的路径将沿着可能不是正确的段的第一个连接段移动。它不会完成排序并且会抛出错误,即使它可能已经找到了正确的封闭路径。
这将忽略圆弧方向并假定圆弧在方向上是统一的(全部为 CW 或全部为 CCW)。如果不是这样,那么您必须在渲染路径时进行适当的更正。
上面创建的弧的起点和终点可能与您为该线所拥有的终点不同。它们只是带有 x、y 的对象。如果您希望它们是同一类型的对象,则需要在 arcEnds 函数中执行此操作。
由于我没有数据集,我无法测试上面的代码,因此很可能存在任何数量的拼写错误。该代码仅作为流程逻辑的指南。您应该使用它来创建自己的版本,而不仅仅是复制和粘贴,因为它可能会由于拼写错误而引发错误。