mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +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(
|
let(
|
||||||
path = cleanup_path(path, eps=eps),
|
path = cleanup_path(path, eps=eps),
|
||||||
plen = len(path)
|
plen = len(path)
|
||||||
) [
|
)
|
||||||
for (i = [0:1:plen-(closed?2:3)], j=[i+2:1:plen-(closed?1:2)]) let(
|
[
|
||||||
a1 = path[i],
|
for (i = [0:1:plen-(closed?2:3)])
|
||||||
a2 = path[(i+1)%plen],
|
let(
|
||||||
b1 = path[j],
|
a1 = path[i],
|
||||||
b2 = path[(j+1)%plen],
|
a2 = path[(i+1)%plen],
|
||||||
isect =
|
maxax = max(a1.x,a2.x),
|
||||||
(max(a1.x, a2.x) < min(b1.x, b2.x))? undef :
|
minax = min(a1.x,a2.x),
|
||||||
(min(a1.x, a2.x) > max(b1.x, b2.x))? undef :
|
maxay = max(a1.y,a2.y),
|
||||||
(max(a1.y, a2.y) < min(b1.y, b2.y))? undef :
|
minay = min(a1.y,a2.y)
|
||||||
(min(a1.y, a2.y) > max(b1.y, b2.y))? undef :
|
)
|
||||||
|
for(j=[i+2:1:plen-(closed?1:2)])
|
||||||
let(
|
let(
|
||||||
c = a1-a2,
|
b1 = path[j],
|
||||||
d = b1-b2,
|
b2 = path[(j+1)%plen],
|
||||||
denom = (c.x*d.y)-(c.y*d.x)
|
isect =
|
||||||
) abs(denom)<eps? undef :
|
maxax < b1.x && maxax < b2.x ||
|
||||||
let(
|
minax > b1.x && minax > b2.x ||
|
||||||
e = a1-b1,
|
maxay < b1.y && maxay < b2.y ||
|
||||||
t = ((e.x*d.y)-(e.y*d.x)) / denom,
|
minay > b1.y && minay > b2.y
|
||||||
u = ((e.x*c.y)-(e.y*c.x)) / denom
|
? undef
|
||||||
) [a1+t*(a2-a1), t, u]
|
: _general_line_intersection([a1,a2],[b1,b2])
|
||||||
) if (
|
)
|
||||||
(!closed || i!=0 || j!=plen-1) &&
|
if ((!closed || i!=0 || j!=plen-1)
|
||||||
isect != undef &&
|
&& isect != undef
|
||||||
isect[1]>=-eps && isect[1]<=1+eps &&
|
&& isect[1]>=-eps && isect[1]<=1+eps
|
||||||
isect[2]>=-eps && isect[2]<=1+eps
|
&& isect[2]>=-eps && isect[2]<=1+eps)
|
||||||
) [isect[0], i, isect[1], j, isect[2]]
|
[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
|
// 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)
|
// eps = Epsilon error value used for determine if points coincide. Default: `EPSILON` (1e-9)
|
||||||
function is_path_simple(path, closed=false, eps=EPSILON) =
|
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) == [];
|
_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);
|
// polygon(path);
|
||||||
// right(27)rainbow(polygon_parts(path)) polygon($item);
|
// right(27)rainbow(polygon_parts(path)) polygon($item);
|
||||||
// move([16,-14])rainbow(polygon_parts(path,nonzero=true)) 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(
|
let(
|
||||||
path = cleanup_path(path, eps=eps),
|
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]],
|
kept = [for (sub = tagged) if(sub[0] == "O") sub[1]],
|
||||||
outregion = _assemble_path_fragments(kept, eps=eps)
|
outregion = _assemble_path_fragments(kept, eps=eps)
|
||||||
) outregion;
|
) outregion;
|
||||||
|
|
84
regions.scad
84
regions.scad
|
@ -109,7 +109,7 @@ module region(r)
|
||||||
|
|
||||||
// Function: point_in_region()
|
// Function: point_in_region()
|
||||||
// Usage:
|
// Usage:
|
||||||
// point_in_region(point, region);
|
// check = point_in_region(point, region);
|
||||||
// Description:
|
// Description:
|
||||||
// Tests if a point is inside, outside, or on the border of a region.
|
// Tests if a point is inside, outside, or on the border of a region.
|
||||||
// Returns -1 if the point is outside the 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()
|
// Function: polygons_equal()
|
||||||
// Usage:
|
// Usage:
|
||||||
// b = polygons_equal(poly1, poly2, [eps])
|
// b = polygons_equal(poly1, poly2, [eps])
|
||||||
|
@ -196,28 +227,49 @@ function __regions_equal(region1, region2, i) =
|
||||||
__regions_equal(region1, region2, i+1);
|
__regions_equal(region1, region2, i+1);
|
||||||
|
|
||||||
|
|
||||||
/// Internal Function: _region_path_crossings()
|
/// Internal Function: _path_region_intersections()
|
||||||
/// Usage:
|
/// Usage:
|
||||||
/// _region_path_crossings(path, region);
|
/// _path_region_intersections(path, region);
|
||||||
/// Description:
|
/// 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:
|
/// Arguments:
|
||||||
/// path = The path to find crossings on.
|
/// path = The path to find crossings on.
|
||||||
/// region = Region to test for crossings of.
|
/// region = Region to test for crossings of.
|
||||||
/// closed = If true, treat path as a closed polygon. Default: true
|
/// closed = If true, treat path as a closed polygon. Default: true
|
||||||
/// eps = Acceptable variance. Default: `EPSILON` (1e-9)
|
/// 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(
|
let(
|
||||||
segs = pair(closed? close_path(path) : cleanup_path(path))
|
segs = pair(closed? close_path(path) : cleanup_path(path))
|
||||||
)
|
)
|
||||||
sort([for (si = idx(segs), p = close_region(region), s2 = pair(p))
|
sort(
|
||||||
let (
|
[for(si = idx(segs))
|
||||||
isect = _general_line_intersection(segs[si], s2, eps=eps)
|
let(
|
||||||
)
|
a1 = segs[si][0],
|
||||||
if (!is_undef(isect[0]) && isect[1] >= 0-eps && isect[1] < 1+eps
|
a2 = segs[si][1],
|
||||||
&& isect[2] >= 0-eps && isect[2] < 1+eps )
|
maxax = max(a1.x,a2.x),
|
||||||
[si, isect[1]]
|
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()
|
// Function: split_path_at_region_crossings()
|
||||||
|
@ -241,7 +293,7 @@ function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON)
|
||||||
let(
|
let(
|
||||||
path = deduplicate(path, eps=eps),
|
path = deduplicate(path, eps=eps),
|
||||||
region = [for (path=region) 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(
|
crossings = deduplicate(
|
||||||
concat([[0,0]], xings, [[len(path)-1,1]]),
|
concat([[0,0]], xings, [[len(path)-1,1]]),
|
||||||
eps=eps
|
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()
|
// Function: split_nested_region()
|
||||||
|
@ -986,7 +1038,7 @@ function intersection(regions=[],b=undef,c=undef,eps=EPSILON) =
|
||||||
// circle(d=40);
|
// circle(d=40);
|
||||||
// }
|
// }
|
||||||
function exclusive_or(regions=[],b=undef,c=undef,eps=EPSILON) =
|
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] :
|
len(regions)<=1? regions[0] :
|
||||||
exclusive_or(
|
exclusive_or(
|
||||||
let(regions=[for (r=regions) is_path(r)? [r] : r])
|
let(regions=[for (r=regions) is_path(r)? [r] : r])
|
||||||
|
|
Loading…
Reference in a new issue