mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 00:09:41 +00:00
split_path_at_region_crossings does not return zero length segments
faster code for path and region intersections
This commit is contained in:
parent
b7c5b789b7
commit
dbee0abec6
2 changed files with 105 additions and 44 deletions
65
paths.scad
65
paths.scad
|
@ -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;
|
||||
|
|
84
regions.scad
84
regions.scad
|
@ -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])
|
||||
|
|
Loading…
Reference in a new issue