mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-01-01 09:49:45 +00:00
Rewrote decompose_path() and assemble_path_fragments()
This commit is contained in:
parent
ba42270e22
commit
d4677923ba
1 changed files with 103 additions and 83 deletions
186
geometry.scad
186
geometry.scad
|
@ -579,14 +579,31 @@ function path_self_intersections(path, closed=true, eps=EPSILON) =
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
function _tag_self_crossing_subpaths(path, closed=true, eps=EPSILON) =
|
||||||
|
let(
|
||||||
|
subpaths = split_path_at_self_crossings(
|
||||||
|
path, closed=closed, eps=eps
|
||||||
|
)
|
||||||
|
) [
|
||||||
|
for (subpath = subpaths) let(
|
||||||
|
seg = select(subpath,0,1),
|
||||||
|
mp = mean(seg),
|
||||||
|
n = line_normal(seg) / 2048,
|
||||||
|
p1 = mp + n,
|
||||||
|
p2 = mp - n,
|
||||||
|
p1in = point_in_polygon(p1, path) >= 0,
|
||||||
|
p2in = point_in_polygon(p2, path) >= 0,
|
||||||
|
tag = (p1in && p2in)? "I" : "O"
|
||||||
|
) [tag, subpath]
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
// Function: decompose_path()
|
// Function: decompose_path()
|
||||||
// Usage:
|
// Usage:
|
||||||
// splitpaths = decompose_path(path, [closed], [eps]);
|
// splitpaths = decompose_path(path, [closed], [eps]);
|
||||||
// Description:
|
// Description:
|
||||||
// Given a possibly self-intersecting path, splits it up into a list of non-intersecting sub-paths.
|
// Given a possibly self-crossing path, decompose it into non-crossing paths that are on the perimeter
|
||||||
// If the given path is not a closed polygon, then the first returned subpath will not be closed.
|
// of the areas bounded by that path.
|
||||||
// All other returned subpaths should be considered as closed polygons. Subpaths of crossing areas
|
|
||||||
// will have the opposite clockwise-ness from the first path returned.
|
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// path = The path to split up.
|
// path = The path to split up.
|
||||||
// closed = If true, treat path like a closed polygon. Default: true
|
// closed = If true, treat path like a closed polygon. Default: true
|
||||||
|
@ -600,30 +617,11 @@ function path_self_intersections(path, closed=true, eps=EPSILON) =
|
||||||
function decompose_path(path, closed=true, eps=EPSILON) =
|
function decompose_path(path, closed=true, eps=EPSILON) =
|
||||||
let(
|
let(
|
||||||
path = cleanup_path(path, eps=eps),
|
path = cleanup_path(path, eps=eps),
|
||||||
isects = path_self_intersections(path, closed, eps)
|
tagged = _tag_self_crossing_subpaths(path, closed=closed, eps=eps),
|
||||||
) isects==[]? [path] :
|
kept = [for (sub = tagged) if(sub[0] == "O") sub[1]],
|
||||||
let(
|
outregion = assemble_path_fragments(kept, eps=eps)
|
||||||
isect = isects[0],
|
) outregion;
|
||||||
plen = len(path)
|
|
||||||
) concat(
|
|
||||||
decompose_path(
|
|
||||||
let(
|
|
||||||
subpath1 = path_subselect(path, 0, 0, isect[1], isect[2], closed=closed),
|
|
||||||
subpath2 = path_subselect(path, isect[3], isect[4], plen-(closed?0:1), 1, closed=closed),
|
|
||||||
patha = cleanup_path(deduplicate(concat(subpath1, subpath2), eps=eps), eps=eps)
|
|
||||||
) patha,
|
|
||||||
closed=closed,
|
|
||||||
eps=eps
|
|
||||||
),
|
|
||||||
decompose_path(
|
|
||||||
let(
|
|
||||||
subpath3 = path_subselect(path, isect[1], isect[2], isect[3], isect[4], closed=closed),
|
|
||||||
pathb = cleanup_path(subpath3, eps=eps)
|
|
||||||
) pathb,
|
|
||||||
closed=true,
|
|
||||||
eps=eps
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Function: path_subselect()
|
// Function: path_subselect()
|
||||||
// Usage:
|
// Usage:
|
||||||
|
@ -737,72 +735,94 @@ function centroid(vertices) =
|
||||||
]) / 6 / polygon_area(vertices);
|
]) / 6 / polygon_area(vertices);
|
||||||
|
|
||||||
|
|
||||||
|
function _extreme_angle_fragment(seg, fragments, rightmost=true, eps=EPSILON) =
|
||||||
|
!fragments? [undef, []] :
|
||||||
|
let(
|
||||||
|
delta = seg[1] - seg[0],
|
||||||
|
segang = atan2(delta.y,delta.x),
|
||||||
|
frags = [
|
||||||
|
for (i = idx(fragments)) let(
|
||||||
|
fragment = fragments[i],
|
||||||
|
fwdmatch = approx(seg[1], fragment[0], eps=eps),
|
||||||
|
bakmatch = approx(seg[1], select(fragment,-1), eps=eps)
|
||||||
|
) [
|
||||||
|
fwdmatch,
|
||||||
|
bakmatch,
|
||||||
|
bakmatch? reverse(fragment) : fragment
|
||||||
|
]
|
||||||
|
],
|
||||||
|
angs = [
|
||||||
|
for (frag = frags)
|
||||||
|
(frag[0] || frag[1])? let(
|
||||||
|
delta2 = frag[2][1] - frag[2][0],
|
||||||
|
segang2 = atan2(delta2.y, delta2.x)
|
||||||
|
) modang(segang2 - segang) : (
|
||||||
|
rightmost? 999 : -999
|
||||||
|
)
|
||||||
|
],
|
||||||
|
fi = rightmost? min_index(angs) : max_index(angs)
|
||||||
|
) abs(angs[fi]) > 360? [undef, fragments] : let(
|
||||||
|
remainder = [for (i=idx(fragments)) if (i!=fi) fragments[i]],
|
||||||
|
frag = frags[fi],
|
||||||
|
foundfrag = frag[2]
|
||||||
|
) [foundfrag, remainder];
|
||||||
|
|
||||||
|
|
||||||
// Function: assemble_path_fragments()
|
// Function: assemble_path_fragments()
|
||||||
// Usage:
|
// Usage:
|
||||||
// assemble_path_fragments(subpaths);
|
// assemble_path_fragments(subpaths);
|
||||||
// Description:
|
// Description:
|
||||||
// Given a list of incomplete paths, assembles them together into complete closed paths if it can.
|
// Given a list of incomplete paths, assembles them together into complete closed paths if it can.
|
||||||
function assemble_path_fragments(subpaths,eps=EPSILON,_finished=[]) =
|
function assemble_path_fragments(fragments, rightmost=false, eps=EPSILON, _finished=[]) =
|
||||||
len(subpaths)<=1? concat(_finished, subpaths) :
|
len(fragments)==0? _finished :
|
||||||
let(
|
let(
|
||||||
path = subpaths[0]
|
path = fragments[0],
|
||||||
|
newfrags = slice(fragments, 1, -1),
|
||||||
|
finished = concat(_finished, [path])
|
||||||
) is_closed_path(path, eps=eps)? (
|
) is_closed_path(path, eps=eps)? (
|
||||||
assemble_path_fragments(
|
// starting fragment is already closed
|
||||||
[for (i=[1:1:len(subpaths)-1]) subpaths[i]],
|
assemble_path_fragments(fragments=newfrags, rightmost=rightmost, eps=eps, _finished=finished)
|
||||||
eps=eps,
|
|
||||||
_finished=concat(_finished, [path])
|
|
||||||
)
|
|
||||||
) : let(
|
) : let(
|
||||||
matches = [
|
// Find rightmost/leftmost continuation fragment
|
||||||
for (i=[1:1:len(subpaths)-1], rev1=[0,1], rev2=[0,1]) let(
|
extrema = _extreme_angle_fragment(
|
||||||
idx1 = rev1? 0 : len(path)-1,
|
seg=select(path,-2,-1),
|
||||||
idx2 = rev2? len(subpaths[i])-1 : 0
|
fragments=slice(fragments,1,-1),
|
||||||
) if (approx(path[idx1], subpaths[i][idx2], eps=eps)) [
|
rightmost=rightmost,
|
||||||
i, concat(
|
eps=eps
|
||||||
rev1? reverse(path) : path,
|
),
|
||||||
select(rev2? reverse(subpaths[i]) : subpaths[i], 1,-1)
|
foundfrag = extrema[0],
|
||||||
)
|
remainder = extrema[1],
|
||||||
]
|
newfrags = remainder,
|
||||||
]
|
finished = concat(_finished, [path])
|
||||||
) len(matches)==0? (
|
) is_undef(foundfrag)? (
|
||||||
assemble_path_fragments(
|
// No remaining fragments connect! INCOMPLETE PATH!
|
||||||
select(subpaths,1,-1),
|
// Treat it as complete.
|
||||||
eps=eps,
|
assemble_path_fragments(fragments=newfrags, rightmost=rightmost, eps=eps, _finished=finished)
|
||||||
_finished=concat(_finished, [path])
|
) : is_closed_path(foundfrag, eps=eps)? (
|
||||||
)
|
let(
|
||||||
) : is_closed_path(matches[0][1], eps=eps)? (
|
newfrags = concat([path], remainder),
|
||||||
assemble_path_fragments(
|
finished = concat(_finished, [foundfrag])
|
||||||
[for (i=[1:1:len(subpaths)-1]) if(i != matches[0][0]) subpaths[i]],
|
|
||||||
eps=eps,
|
|
||||||
_finished=concat(_finished, [matches[0][1]])
|
|
||||||
)
|
)
|
||||||
|
// Found fragment is already closed
|
||||||
|
assemble_path_fragments(fragments=newfrags, rightmost=rightmost, eps=eps, _finished=finished)
|
||||||
) : let(
|
) : let(
|
||||||
subpath = matches[0][1],
|
fragend = select(foundfrag,-1),
|
||||||
splen = len(subpath),
|
hits = [for (i = idx(path,end=-2)) if(approx(path[i],fragend,eps=eps)) i]
|
||||||
conn1 = [for (i=[1:splen-1]) if (approx(subpath[0],subpath[i])) i],
|
) hits? (
|
||||||
conn2 = [for (i=[0:splen-2]) if (approx(subpath[splen-1],subpath[i])) i]
|
let(
|
||||||
) (conn1 != [] || conn2 != [])? let(
|
// Found fragment intersects with initial path
|
||||||
finpath = select(subpath, 0, conn1!=[]? conn1[0] : conn2[0]),
|
hitidx = select(hits,-1),
|
||||||
subpath2 = select(subpath, conn1!=[]? conn1[0] : conn2[0], -1)
|
newpath = slice(path,0,hitidx+1),
|
||||||
) (
|
newfrags = concat(len(newpath)>1? [newpath] : [], remainder),
|
||||||
assemble_path_fragments(
|
finished = concat(_finished,[concat(slice(path,hitidx,-2),foundfrag)])
|
||||||
concat(
|
|
||||||
[subpath2],
|
|
||||||
[for (i = [1:1:len(subpaths)-1]) if(i != matches[0][0]) subpaths[i]]
|
|
||||||
),
|
|
||||||
eps=eps,
|
|
||||||
_finished=concat(_finished, [finpath])
|
|
||||||
)
|
)
|
||||||
) : (
|
assemble_path_fragments(fragments=newfrags, rightmost=rightmost, eps=eps, _finished=finished)
|
||||||
assemble_path_fragments(
|
) : let(
|
||||||
concat(
|
// Path still incomplete. Continue building it.
|
||||||
[matches[0][1]],
|
newpath = concat(path, slice(foundfrag, 1, -1)),
|
||||||
[for (i = [1:1:len(subpaths)-1]) if(i != matches[0][0]) subpaths[i]]
|
newfrags = concat([newpath], remainder)
|
||||||
),
|
)
|
||||||
eps=eps,
|
assemble_path_fragments(fragments=newfrags, rightmost=rightmost, eps=eps, _finished=_finished);
|
||||||
_finished=_finished
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// Function: simplify_path()
|
// Function: simplify_path()
|
||||||
|
|
Loading…
Reference in a new issue