_assemble_path_from_fragments discards zero area polygons

split_path_at_self_crossings discards zero-length (single point) "segments"
polygon_line_intersection bugfix
remove debug echoes
This commit is contained in:
Adrian Mariano 2021-09-24 17:33:18 -04:00
parent a2c5e49e2d
commit 7776ae8e67
3 changed files with 58 additions and 30 deletions

View file

@ -34,12 +34,6 @@ function is_point_on_line(point, line, bounded=false, eps=EPSILON) =
//_dist2line works for any dimension
function _dist2line(d,n) = norm(d-(d * n) * n);
// Internal non-exposed function.
function _point_above_below_segment(point, edge) =
let( edge = edge - [point, point] )
edge[0].y <= 0
? (edge[1].y > 0 && cross(edge[0], edge[1]-edge[0]) > 0) ? 1 : 0
: (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0;
//Internal
function _valid_line(line,dim,eps=EPSILON) =
@ -762,7 +756,7 @@ function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps
len(poly[0])==2 ? // planar case
let(
linevec = unit(line[1] - line[0]),
bound = 100*max(flatten(pointlist_bounds(poly))),
bound = 100*max(v_abs(flatten(pointlist_bounds(poly)))),
boundedline = [line[0] + (bounded[0]? 0 : -bound) * linevec,
line[1] + (bounded[1]? 0 : bound) * linevec],
parts = split_path_at_region_crossings(boundedline, [poly], closed=false),
@ -1520,6 +1514,15 @@ function polygon_normal(poly) =
// color(point_in_polygon(p,path,nonzero=false)==1 ? "green" : "red")
// move(p)circle(r=1, $fn=12);
// }
// Internal function for point_in_polygon
function _point_above_below_segment(point, edge) =
let( edge = edge - [point, point] )
edge[0].y <= 0
? (edge[1].y > 0 && cross(edge[0], edge[1]-edge[0]) > 0) ? 1 : 0
: (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0;
function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
// Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,

View file

@ -186,12 +186,15 @@ function path_length_fractions(path, closed=false) =
/// Usage:
/// isects = _path_self_intersections(path, [closed], [eps]);
/// Description:
/// Locates all self intersections of the given path. Returns a list of intersections, where
/// Locates all self intersection points of the given path. Returns a list of intersections, where
/// each intersection is a list like [POINT, SEGNUM1, PROPORTION1, SEGNUM2, PROPORTION2] where
/// POINT is the coordinates of the intersection point, SEGNUMs are the integer indices of the
/// intersecting segments along the path, and the PROPORTIONS are the 0.0 to 1.0 proportions
/// of how far along those segments they intersect at. A proportion of 0.0 indicates the start
/// of the segment, and a proportion of 1.0 indicates the end of the segment.
/// .
/// Note that this function does not return self-intersecting segments, only the points
/// where non-parallel segments intersect.
/// Arguments:
/// path = The path to find self intersections of.
/// closed = If true, treat path like a closed polygon. Default: true
@ -527,8 +530,7 @@ function path_normals(path, tangents, closed=false) =
: select(path,i-1,i+1)
)
dim == 2 ? [tangents[i].y,-tangents[i].x]
: let( fff=i==10?echo(pts=pts, tangent=tangents[10],cp=cross(pts[1]-pts[0], pts[2]-pts[0])):0,
v=cross(cross(pts[1]-pts[0], pts[2]-pts[0]),tangents[i]))
: let( v=cross(cross(pts[1]-pts[0], pts[2]-pts[0]),tangents[i]))
assert(norm(v)>EPSILON, "3D path contains collinear points")
unit(v)
];
@ -959,7 +961,7 @@ function split_path_at_self_crossings(path, closed=true, eps=EPSILON) =
section = _path_select(path, s1, u1, s2, u2, closed=closed),
outpath = deduplicate(eps=eps, section)
)
outpath
if (len(outpath)>1) outpath
];
@ -1011,6 +1013,36 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
// left(100)region(outside);
// rainbow(outside)
// stroke($item,closed=true);
// Example:
// N=12;
// ang=360/N;
// sr=10;
// path = turtle(["angle", 90+ang/2,
// "move", sr, "left",
// "move", 2*sr*sin(ang/2), "left",
// "repeat", 4,
// ["move", 2*sr, "left",
// "move", 2*sr*sin(ang/2), "left"],
// "move", sr]);
// stroke(path, width=.3);
// right(20)rainbow(polygon_parts(path)) polygon($item);
// Example: overlapping path segments disappear
// path = [[0,0], [10,0], [10,10], [0,10],[0,20], [20,10],[10,10], [0,10],[0,0]];
// stroke(path,width=0.3);
// right(22)stroke(polygon_parts(path)[0], width=0.3, closed=true);
// Example: Path segments disappear outside as well
// path = turtle(["repeat", 3, ["move", 17, "left", "move", 10, "left", "move", 7, "left", "move", 10, "left"]]);
// back(2)stroke(path,width=.3);
// fwd(12)rainbow(polygon_parts(path)) polygon($item);
// Example: This shape has six components
// path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 17, "left"]]);
// polygon(path);
// right(22)rainbow(polygon_parts(path)) polygon($item);
// Example: when the loops of the shape overlap then nonzero gives a different result than the even-odd method.
// path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 10, "left"]]);
// polygon(path);
// right(27)rainbow(polygon_parts(path)) polygon($item);
// move([16,-14])rainbow(polygon_parts(path,nonzero=true)) polygon($item);
function polygon_parts(path, nonzero=false, closed=true, eps=EPSILON) =
let(
path = cleanup_path(path, eps=eps),
@ -1115,6 +1147,7 @@ function _assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0,
/// _assemble_path_fragments(subpaths);
/// Description:
/// Given a list of paths, assembles them together into complete closed polygon paths if it can.
/// Polygons with area < eps will be discarded and not returned.
/// Arguments:
/// fragments = List of paths to be assembled into complete polygons.
/// eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9)
@ -1141,7 +1174,7 @@ function _assemble_path_fragments(fragments, eps=EPSILON, _finished=[]) =
result = l_area < r_area? result_l : result_r,
newpath = cleanup_path(result[0]),
remainder = result[1],
finished = concat(_finished, [newpath])
finished = min(l_area,r_area)<eps ? _finished : concat(_finished, [newpath])
) _assemble_path_fragments(
fragments=remainder,
eps=eps,
@ -1150,7 +1183,4 @@ function _assemble_path_fragments(fragments, eps=EPSILON, _finished=[]) =
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -206,22 +206,18 @@ function __regions_equal(region1, region2, i) =
/// region = Region to test for crossings of.
/// closed = If true, treat path as a closed polygon. Default: true
/// eps = Acceptable variance. Default: `EPSILON` (1e-9)
function _region_path_crossings(path, region, closed=true, eps=EPSILON) = sort([
function _region_path_crossings(path, region, closed=true, eps=EPSILON) =
let(
segs = pair(closed? close_path(path) : cleanup_path(path))
) for (
si = idx(segs),
p = close_region(region),
s2 = pair(p)
) let (
isect = _general_line_intersection(segs[si], s2, eps=eps)
) if (
!is_undef(isect[0]) &&
isect[1] >= 0-eps && isect[1] < 1+eps &&
isect[2] >= 0-eps && isect[2] < 1+eps
)
[si, isect[1]]
]);
sort([for (si = idx(segs), p = close_region(region), s2 = pair(p))
let (
isect = _general_line_intersection(segs[si], s2, eps=eps)
)
if (!is_undef(isect[0]) && isect[1] >= 0-eps && isect[1] < 1+eps
&& isect[2] >= 0-eps && isect[2] < 1+eps )
[si, isect[1]]
]);
// Function: split_path_at_region_crossings()
@ -768,8 +764,7 @@ function offset(
// Note if !closed the last corner doesn't matter, so exclude it
parallelcheck =
(len(sharpcorners)==2 && !closed) ||
all_defined(closed? sharpcorners : select(sharpcorners, 1,-2)),
f=echo(sharpcorners=sharpcorners)
all_defined(closed? sharpcorners : select(sharpcorners, 1,-2))
)
assert(parallelcheck, "Path contains sequential parallel segments (either 180 deg turn or 0 deg turn")
let(