split_path_at_region_crossings does not return zero length segments

faster code for path and region intersections
This commit is contained in:
Adrian Mariano 2021-09-27 18:33:44 -04:00
parent b7c5b789b7
commit dbee0abec6
2 changed files with 105 additions and 44 deletions

View file

@ -211,33 +211,34 @@ function _path_self_intersections(path, closed=true, eps=EPSILON) =
let(
path = cleanup_path(path, eps=eps),
plen = len(path)
) [
for (i = [0:1:plen-(closed?2:3)], j=[i+2:1:plen-(closed?1:2)]) let(
a1 = path[i],
a2 = path[(i+1)%plen],
b1 = path[j],
b2 = path[(j+1)%plen],
isect =
(max(a1.x, a2.x) < min(b1.x, b2.x))? undef :
(min(a1.x, a2.x) > max(b1.x, b2.x))? undef :
(max(a1.y, a2.y) < min(b1.y, b2.y))? undef :
(min(a1.y, a2.y) > max(b1.y, b2.y))? undef :
)
[
for (i = [0:1:plen-(closed?2:3)])
let(
a1 = path[i],
a2 = path[(i+1)%plen],
maxax = max(a1.x,a2.x),
minax = min(a1.x,a2.x),
maxay = max(a1.y,a2.y),
minay = min(a1.y,a2.y)
)
for(j=[i+2:1:plen-(closed?1:2)])
let(
c = a1-a2,
d = b1-b2,
denom = (c.x*d.y)-(c.y*d.x)
) abs(denom)<eps? undef :
let(
e = a1-b1,
t = ((e.x*d.y)-(e.y*d.x)) / denom,
u = ((e.x*c.y)-(e.y*c.x)) / denom
) [a1+t*(a2-a1), t, u]
) if (
(!closed || i!=0 || j!=plen-1) &&
isect != undef &&
isect[1]>=-eps && isect[1]<=1+eps &&
isect[2]>=-eps && isect[2]<=1+eps
) [isect[0], i, isect[1], j, isect[2]]
b1 = path[j],
b2 = path[(j+1)%plen],
isect =
maxax < b1.x && maxax < b2.x ||
minax > b1.x && minax > b2.x ||
maxay < b1.y && maxay < b2.y ||
minay > b1.y && minay > b2.y
? undef
: _general_line_intersection([a1,a2],[b1,b2])
)
if ((!closed || i!=0 || j!=plen-1)
&& isect != undef
&& isect[1]>=-eps && isect[1]<=1+eps
&& isect[2]>=-eps && isect[2]<=1+eps)
[isect[0], i, isect[1], j, isect[2]]
];
@ -440,6 +441,14 @@ function resample_path(path, N, spacing, closed=false) =
// closed = set to true to treat path as a polygon. Default: false
// eps = Epsilon error value used for determine if points coincide. Default: `EPSILON` (1e-9)
function is_path_simple(path, closed=false, eps=EPSILON) =
[for(i=[0:1:len(path)-(closed?2:3)])
let(v1=path[i+1]-path[i],
v2=select(path,i+2)-path[i+1],
normv1 = norm(v1),
normv2 = norm(v2)
)
if (/*approx(normv1,0) || approx(normv2,0) ||*/ approx(v1*v2/normv1/normv2,-1)) 1] == []
&&
_path_self_intersections(path,closed=closed,eps=eps) == [];
@ -1043,10 +1052,10 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
// 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) =
function polygon_parts(path, nonzero=false, eps=EPSILON) =
let(
path = cleanup_path(path, eps=eps),
tagged = _tag_self_crossing_subpaths(path, nonzero=nonzero, closed=closed, eps=eps),
tagged = _tag_self_crossing_subpaths(path, nonzero=nonzero, closed=true, eps=eps),
kept = [for (sub = tagged) if(sub[0] == "O") sub[1]],
outregion = _assemble_path_fragments(kept, eps=eps)
) outregion;

View file

@ -109,7 +109,7 @@ module region(r)
// Function: point_in_region()
// Usage:
// point_in_region(point, region);
// check = point_in_region(point, region);
// Description:
// Tests if a point is inside, outside, or on the border of a region.
// Returns -1 if the point is outside the region.
@ -126,6 +126,37 @@ function point_in_region(point, region, eps=EPSILON, _i=0, _cnt=0) =
// Function: is_region_simple()
// Usage:
// bool = is_region_simple(region, [eps]);
// Description:
// Returns true if the region is entirely non-self-intersecting, meaning that it is
// formed from a list of simple polygons that do not intersect each other.
// Arguments:
// region = region to check
// eps = tolerance for geometric omparisons. Default: `EPSILON` = 1e-9
function is_region_simple(region, eps=EPSILON) =
[for(p=region) if (!is_path_simple(p)) 1] == []
&&
[for(i=[0:1:len(region)-2]) if (_path_region_intersections(region[i],[for(j=[i+1:1:len(region)-1]) region[j]]) != []) 1] ==[];
function approx_sign(x) = approx(x,0) ? 0 : sign(x);
function do_segments_intersect(s1,s2) =
let(
a1=cross(s1[1]-s1[0], s2[0]-s1[1]),
a2=cross(s1[1]-s1[0], s2[1]-s1[1]),
a3=cross(s2[1]-s2[0], s1[0]-s2[1]),
a4=cross(s2[1]-s2[0], s1[1]-s2[1])
)
approx_sign(a1)!=approx_sign(a2) && approx_sign(a3)!=approx_sign(a4);
// Note: parallel intersecting lines seem to have all the a's equal approx to zero
// Function: polygons_equal()
// Usage:
// b = polygons_equal(poly1, poly2, [eps])
@ -196,28 +227,49 @@ function __regions_equal(region1, region2, i) =
__regions_equal(region1, region2, i+1);
/// Internal Function: _region_path_crossings()
/// Internal Function: _path_region_intersections()
/// Usage:
/// _region_path_crossings(path, region);
/// _path_region_intersections(path, region);
/// Description:
/// Returns a sorted list of [SEGMENT, U] that describe where a given path is crossed by a second path.
/// Returns a sorted list of [SEGMENT, U] that describe where a given path intersects the region
// in a single point. (Note that intersections of collinear segments, where the intersection is another segment, are
// ignored.)
/// Arguments:
/// path = The path to find crossings on.
/// 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) =
function _path_region_intersections(path, region, closed=true, eps=EPSILON) =
let(
segs = pair(closed? close_path(path) : cleanup_path(path))
)
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]]
]);
sort(
[for(si = idx(segs))
let(
a1 = segs[si][0],
a2 = segs[si][1],
maxax = max(a1.x,a2.x),
minax = min(a1.x,a2.x),
maxay = max(a1.y,a2.y),
minay = min(a1.y,a2.y)
)
for(p=close_region(region), s2=pair(p))
let(
b1 = s2[0],
b2 = s2[1],
isect =
maxax < b1.x && maxax < b2.x ||
minax > b1.x && minax > b2.x ||
maxay < b1.y && maxay < b2.y ||
minay > b1.y && minay > b2.y
? undef
: _general_line_intersection([a1,a2],[b1,b2],eps)
)
if (isect && isect[1]>=-eps && isect[1]<=1+eps
&& isect[2]>=-eps && isect[2]<=1+eps)
[si,isect[1]]
]
);
// Function: split_path_at_region_crossings()
@ -241,7 +293,7 @@ function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON)
let(
path = deduplicate(path, eps=eps),
region = [for (path=region) deduplicate(path, eps=eps)],
xings = _region_path_crossings(path, region, closed=closed, eps=eps),
xings = _path_region_intersections(path, region, closed=closed, eps=eps),
crossings = deduplicate(
concat([[0,0]], xings, [[len(path)-1,1]]),
eps=eps
@ -254,7 +306,7 @@ function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON)
)
]
)
subpaths;
[for(s=subpaths) if (len(s)>1) s];
// Function: split_nested_region()
@ -986,7 +1038,7 @@ function intersection(regions=[],b=undef,c=undef,eps=EPSILON) =
// circle(d=40);
// }
function exclusive_or(regions=[],b=undef,c=undef,eps=EPSILON) =
b!=undef? exclusive_or(concat([regions],[b],c==undef?[]:[c]),eps=eps) :
b!=undef? exclusive_or([regions, b, if(is_def(c)) c],eps=eps) :
len(regions)<=1? regions[0] :
exclusive_or(
let(regions=[for (r=regions) is_path(r)? [r] : r])