mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
commit
8de2283f91
3 changed files with 93 additions and 323 deletions
|
@ -272,7 +272,7 @@ module zcopies(spacing, n, l, sp)
|
|||
// Module: grid2d()
|
||||
//
|
||||
// Description:
|
||||
// Makes a square or hexagonal grid of copies of children.
|
||||
// Makes a square or hexagonal grid of copies of children, with an optional masking polygon or region.
|
||||
//
|
||||
// Usage:
|
||||
// grid2d(spacing, size, [stagger], [scale], [inside]) ...
|
||||
|
@ -287,6 +287,7 @@ module zcopies(spacing, n, l, sp)
|
|||
// n = How many columns and rows of copies to make. Can be given as `[COLS,ROWS]`, or just as a scalar that specifies both. If staggered, count both staggered and unstaggered columns and rows. Default: 2 (3 if staggered)
|
||||
// stagger = If true, make a staggered (hexagonal) grid. If false, make square grid. If `"alt"`, makes alternate staggered pattern. Default: false
|
||||
// inside = If given a list of polygon points, or a region, only creates copies whose center would be inside the polygon or region. Polygon can be concave and/or self crossing.
|
||||
// nonzero = If inside is set to a polygon with self-crossings then use the nonzero method for deciding if points are in the polygon. Default: false
|
||||
//
|
||||
// Side Effects:
|
||||
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
|
||||
|
@ -323,7 +324,7 @@ module zcopies(spacing, n, l, sp)
|
|||
// zrot(180/6)
|
||||
// cylinder(h=20, d=10/cos(180/6)+0.01, $fn=6);
|
||||
// }
|
||||
module grid2d(spacing, n, size, stagger=false, inside=undef)
|
||||
module grid2d(spacing, n, size, stagger=false, inside=undef, nonzero=false)
|
||||
{
|
||||
assert(in_list(stagger, [false, true, "alt"]));
|
||||
bounds = is_undef(inside)? undef :
|
||||
|
@ -357,8 +358,8 @@ module grid2d(spacing, n, size, stagger=false, inside=undef)
|
|||
pos = v_mul([col,row],spacing) - offset;
|
||||
if (
|
||||
is_undef(inside) ||
|
||||
(is_path(inside) && point_in_polygon(pos, inside)>=0) ||
|
||||
(is_region(inside) && point_in_region(pos, inside)>=0)
|
||||
(is_path(inside) && point_in_polygon(pos, inside, nonzero=nonzero)>=0) ||
|
||||
(is_region(inside) && point_in_region(pos, inside, nonzero=nonzero)>=0)
|
||||
) {
|
||||
$col = col;
|
||||
$row = row;
|
||||
|
|
|
@ -26,6 +26,23 @@
|
|||
// bounded = boolean or list of two booleans defining endpoint conditions for the line. If false treat the line as an unbounded line. If true treat it as a segment. If [true,false] treat as a ray, based at the first endpoint. Default: false
|
||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||
function is_point_on_line(point, line, bounded=false, eps=EPSILON) =
|
||||
assert(is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||
assert(is_vector(point), "Point must be a vector")
|
||||
assert(_valid_line(line, len(point),eps),"Given line is not valid")
|
||||
_is_point_on_line(point, line, bounded,eps);
|
||||
|
||||
function _is_point_on_line(point, line, bounded=false, eps=EPSILON) =
|
||||
let(
|
||||
v1 = (line[1]-line[0]),
|
||||
v0 = (point-line[0]),
|
||||
t = v0*v1/(v1*v1),
|
||||
bounded = force_list(bounded,2)
|
||||
)
|
||||
abs(cross(v0,v1))<eps*norm(v1)
|
||||
&& (!bounded[0] || t>=-eps)
|
||||
&& (!bounded[1] || t<1+eps) ;
|
||||
|
||||
function xis_point_on_line(point, line, bounded=false, eps=EPSILON) =
|
||||
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||
point_line_distance(point, line, bounded)<eps;
|
||||
|
||||
|
@ -763,7 +780,7 @@ function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps
|
|||
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),
|
||||
parts = split_region_at_region_crossings(boundedline, [poly], closed1=false)[0][0],
|
||||
inside = [
|
||||
if(point_in_polygon(parts[0][0], poly, nonzero=nonzero, eps=eps) == 0)
|
||||
[parts[0][0]], // Add starting point if it is on the polygon
|
||||
|
@ -1546,7 +1563,7 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
|
|||
for (i = [0:1:len(poly)-1])
|
||||
let( seg = select(poly,i,i+1) )
|
||||
if (!approx(seg[0],seg[1],eps) )
|
||||
is_point_on_line(point, seg, SEGMENT, eps=eps)? 1:0
|
||||
_is_point_on_line(point, seg, SEGMENT, eps=eps)? 1:0
|
||||
]
|
||||
)
|
||||
sum(on_brd) > 0? 0 :
|
||||
|
|
386
regions.scad
386
regions.scad
|
@ -148,7 +148,6 @@ function point_in_region(point, region, eps=EPSILON, _i=0, _cnt=0) =
|
|||
: point_in_region(point, region, eps=eps, _i=_i+1, _cnt = _cnt + (pip>0? 1 : 0));
|
||||
|
||||
|
||||
|
||||
// Function: is_region_simple()
|
||||
// Usage:
|
||||
// bool = is_region_simple(region, [eps]);
|
||||
|
@ -159,13 +158,11 @@ function point_in_region(point, region, eps=EPSILON, _i=0, _cnt=0) =
|
|||
// 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,closed=true,eps)) 1] == [];
|
||||
|
||||
/* &&
|
||||
[for(p=region) if (!is_path_simple(p,closed=true,eps)) 1] == []
|
||||
&&
|
||||
[for(i=[0:1:len(region)-2])
|
||||
if (_path_region_intersections(region[i], list_tail(region,i+1), eps=eps) != []) 1
|
||||
] ==[];
|
||||
*/
|
||||
|
||||
function _clockwise_region(r) = [for(p=r) clockwise_polygon(p)];
|
||||
|
||||
|
@ -196,100 +193,20 @@ function __are_regions_equal(region1, region2, i) =
|
|||
__are_regions_equal(region1, region2, i+1);
|
||||
|
||||
|
||||
/// Internal Function: _path_region_intersections()
|
||||
/// Internal Function: _region_region_intersections()
|
||||
/// Usage:
|
||||
/// _path_region_intersections(path, region, [closed], [eps]);
|
||||
/// risect = _region_region_intersections(region1, region2, [closed1], [closed2], [eps]
|
||||
/// Description:
|
||||
/// 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 old_path_region_intersections(path, region, closed=true, eps=EPSILON) =
|
||||
let(
|
||||
pathclosed = closed && !is_closed_path(path),
|
||||
pathlen = len(path),
|
||||
regionsegs = [for(poly=region) each pair(poly, is_closed_path(poly)?false:true)]
|
||||
)
|
||||
sort(
|
||||
[for(si = [0:1:len(path)-(pathclosed?1:2)])
|
||||
let(
|
||||
a1 = path[si],
|
||||
a2 = path[(si+1)%pathlen],
|
||||
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(rseg=regionsegs)
|
||||
let(
|
||||
b1 = rseg[0],
|
||||
b2 = rseg[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],rseg,eps)
|
||||
)
|
||||
if (isect && isect[1]>=-eps && isect[1]<=1+eps
|
||||
&& isect[2]>=-eps && isect[2]<=1+eps)
|
||||
[si,isect[1]]
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
// find the intersection points of a path and the polygons of a
|
||||
// region; only crossing intersections are caught, no collinear
|
||||
// intersection is returned.
|
||||
function _path_region_intersections(path, region, closed=true, eps=EPSILON, extra=[]) =
|
||||
let( path = closed ? close_path(path,eps=eps) : path )
|
||||
_sort_vectors(
|
||||
[ each extra,
|
||||
for(si = [0:1:len(path)-2]) let(
|
||||
a1 = path[si],
|
||||
a2 = path[si+1],
|
||||
nrm = norm(a1-a2)
|
||||
)
|
||||
if( nrm>eps ) let( // ignore zero-length path edges
|
||||
seg_normal = [-(a2-a1).y, (a2-a1).x]/nrm,
|
||||
ref = a1*seg_normal
|
||||
)
|
||||
// `signs[j]` is the sign of the signed distance from
|
||||
// poly vertex j to the line [a1,a2] where near zero
|
||||
// distances are snapped to zero; poly edges
|
||||
// with equal signs at its vertices cannot intersect
|
||||
// the path edge [a1,a2] or they are collinear and
|
||||
// further tests can be discarded.
|
||||
for(poly=region) let(
|
||||
poly = close_path(poly),
|
||||
signs = [for(v=poly*seg_normal) v-ref> eps ? 1 : v-ref<-eps ? -1 : 0]
|
||||
)
|
||||
if(max(signs)>=0 && min(signs)<=0 ) // some edge edge intersects line [a1,a2]
|
||||
for(j=[0:1:len(poly)-2])
|
||||
if( signs[j]!=signs[j+1] ) let( // exclude non-crossing and collinear segments
|
||||
b1 = poly[j],
|
||||
b2 = poly[j+1],
|
||||
isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps)
|
||||
)
|
||||
if ( isect
|
||||
// && isect[1]> (si==0 && !closed? -eps: 0)
|
||||
&& isect[1]>= -eps
|
||||
&& isect[1]<= 1+eps
|
||||
// && isect[2]> 0
|
||||
&& isect[2]>= -eps
|
||||
&& isect[2]<= 1+eps )
|
||||
[si,isect[1]]
|
||||
]);
|
||||
|
||||
|
||||
|
||||
// Returns a list [reg1,reg2] such that reg1[i] is a list of intersection points for path i
|
||||
// in region1 having the form [seg, u].
|
||||
/// Returns a pair of sorted lists such that risect[0] is a list of intersection
|
||||
/// points for every path in region1, and similarly risect[1] is a list of intersection
|
||||
/// points for the paths in region2. For each path the intersection list is
|
||||
/// a sorted list of the form [SEGMENT, U]. You can specify that the paths in either
|
||||
/// region be regarded as open paths if desired. Default is to treat them as
|
||||
/// regions and hence the paths as closed polygons.
|
||||
/// .
|
||||
/// Included as intersection points are points where region1 touches itself at a vertex or
|
||||
/// region2 touches itself at a vertex. (The paths are assumed to have no self crossings.
|
||||
/// Self crossings of the paths in the regions are not returned.)
|
||||
function _region_region_intersections(region1, region2, closed1=true,closed2=true, eps=EPSILON) =
|
||||
let(
|
||||
intersections = [
|
||||
|
@ -353,9 +270,36 @@ function _region_region_intersections(region1, region2, closed1=true,closed2=tru
|
|||
[for(i=[0:1]) [for(j=counts[i]) _sort_vectors(select(risect[i],pathind[i][j]))]];
|
||||
|
||||
|
||||
|
||||
// Function: split_region_at_region_crossings()
|
||||
// Usage:
|
||||
// split_region = split_region_at_region_crossings(region1, region2, [closed1], [closed2], [eps])
|
||||
// Description:
|
||||
// Splits region1 at the places where polygons in region1 touches each other at corners and at locations
|
||||
// where region1 intersections region2. Split region2 similarly with respect to region1.
|
||||
// The return is a pair of results of the form [split1, split2] where split1=[frags1,frags2,...]
|
||||
// and frags1 is a list of path pieces (in order) from the first path of the region.
|
||||
// You can pass a single path in for either region, but the output will be a singleton list, as ify
|
||||
// you passed in a singleton region.
|
||||
// Arguments:
|
||||
// region1 = first region
|
||||
// region2 = second region
|
||||
// closed1 = if false then treat region1 as list of open paths. Default: true
|
||||
// closed2 = if false then treat region2 as list of open paths. Default: true
|
||||
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
|
||||
// Example:
|
||||
// path = square(50,center=false);
|
||||
// region = [circle(d=80), circle(d=40)];
|
||||
// paths = split_region_at_region_crossings(path, region);
|
||||
// color("#aaa") region(region);
|
||||
// rainbow(paths[0][0]) stroke($item, width=2);
|
||||
// right(110){
|
||||
// color("#aaa") region([path]);
|
||||
// rainbow(flatten(paths[1])) stroke($item, width=2);
|
||||
// }
|
||||
function split_region_at_region_crossings(region1, region2, closed1=true, closed2=true, eps=EPSILON) =
|
||||
let(
|
||||
region1=force_region(region1),
|
||||
region2=force_region(region2),
|
||||
xings = _region_region_intersections(region1, region2, closed1, closed2, eps),
|
||||
regions = [region1,region2],
|
||||
closed = [closed1,closed2]
|
||||
|
@ -382,51 +326,13 @@ function split_region_at_region_crossings(region1, region2, closed1=true, closed
|
|||
|
||||
|
||||
|
||||
|
||||
// Function: split_path_at_region_crossings()
|
||||
// Usage:
|
||||
// paths = split_path_at_region_crossings(path, region, [eps]);
|
||||
// Description:
|
||||
// Splits a path into sub-paths wherever the path crosses the perimeter of a region.
|
||||
// Splits may occur mid-segment, so new vertices will be created at the intersection points.
|
||||
// Arguments:
|
||||
// path = The path to split up.
|
||||
// region = The region to check for perimeter crossings of.
|
||||
// closed = If true, treat path as a closed polygon. Default: true
|
||||
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
|
||||
// Example(2D):
|
||||
// path = square(50,center=false);
|
||||
// region = [circle(d=80), circle(d=40)];
|
||||
// paths = split_path_at_region_crossings(path, region);
|
||||
// color("#aaa") region(region);
|
||||
// rainbow(paths) stroke($item, closed=false, width=2);
|
||||
function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON, extra=[]) =
|
||||
let(
|
||||
path = deduplicate(path, eps=eps),
|
||||
region = [for (path=region) deduplicate(path, eps=eps)],
|
||||
xings = _path_region_intersections(path, region, closed=closed, eps=eps, extra=extra),
|
||||
crossings = deduplicate(
|
||||
concat([[0,0]], xings, [[len(path)-1,1]]),
|
||||
eps=eps
|
||||
),
|
||||
subpaths = [
|
||||
for (p = pair(crossings))
|
||||
deduplicate(
|
||||
_path_select(path, p[0][0], p[0][1], p[1][0], p[1][1], closed=closed),
|
||||
eps=eps
|
||||
)
|
||||
]
|
||||
)
|
||||
[for(s=subpaths) if (len(s)>1) s];
|
||||
|
||||
|
||||
// Function: region_parts()
|
||||
// Usage:
|
||||
// rgns = region_parts(region);
|
||||
// Description:
|
||||
// Divides a region into a list of connected regions. Each connected region has exactly one outside boundary
|
||||
// and zero or more outlines defining internal holes. Note that behavior is undefined on invalid regions whose
|
||||
// components intersect each other.
|
||||
// Divides a region into a list of connected regions. Each connected region has exactly one clockwise outside boundary
|
||||
// and zero or more counter-clockwise outlines defining internal holes. Note that behavior is undefined on invalid regions whose
|
||||
// components cross each other.
|
||||
// Example(2D,NoAxes):
|
||||
// R = [for(i=[1:7]) square(i,center=true)];
|
||||
// region_list = region_parts(R);
|
||||
|
@ -439,70 +345,9 @@ function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON,
|
|||
// right(5,square(i,center=true))];
|
||||
// region_list = region_parts(R);
|
||||
// rainbow(region_list) region($item);
|
||||
|
||||
|
||||
function old_region_parts(region) =
|
||||
let(
|
||||
paths = sort(idx=0,
|
||||
[
|
||||
for(i = idx(region))
|
||||
let(
|
||||
pt = mean([region[i][0],region[i][1]]),
|
||||
cnt = sum([for (j = idx(region))
|
||||
if (i!=j && point_in_polygon(pt, region[j]) >=0) 1])
|
||||
)
|
||||
[cnt, region[i]]
|
||||
]),
|
||||
|
||||
outs = [
|
||||
for (candout = paths)
|
||||
let(
|
||||
lev = candout[0],
|
||||
parent = candout[1]
|
||||
)
|
||||
if (lev % 2 == 0)
|
||||
[
|
||||
clockwise_polygon(parent),
|
||||
for (path = paths)
|
||||
if (
|
||||
path[0] == lev+1
|
||||
&& point_in_polygon(
|
||||
lerp(path[1][0], path[1][1], 0.5)
|
||||
,parent
|
||||
) >= 0
|
||||
)
|
||||
ccw_polygon(path[1])
|
||||
]
|
||||
]
|
||||
)
|
||||
outs;
|
||||
|
||||
|
||||
function inside(region, ins=[]) =
|
||||
let(
|
||||
i = len(ins)
|
||||
)
|
||||
i==len(region) ? ins
|
||||
:
|
||||
let(
|
||||
pt=mean([region[i][0],region[i][1]])
|
||||
)
|
||||
i==0 ? inside(region,
|
||||
[[0,
|
||||
for(j=[1:1:len(region)-1]) point_in_polygon(pt,region[j])>=0 ? 1 : 0]])
|
||||
: let(
|
||||
prev = [for(j=[0:i-1]) point_in_polygon(pt,region[j])>=0 ? 1 : 0],
|
||||
check = sum(bselect(ins,prev),repeat(0,len(region))),
|
||||
next = [for(j=[i+1:1:len(region)-1]) check[j]>0 ? 1 : point_in_polygon(pt,region[j])>=0 ? 1 : 0]
|
||||
)
|
||||
inside(region, [
|
||||
each ins,
|
||||
[each prev, 0, each next]
|
||||
]);
|
||||
|
||||
|
||||
function region_parts(region) =
|
||||
let(
|
||||
region = force_region(region),
|
||||
inside = [for(i=idx(region))
|
||||
let(pt = mean([region[i][0], region[i][1]]))
|
||||
[for(j=idx(region)) i==j ? 0
|
||||
|
@ -824,52 +669,6 @@ function _point_dist(path,pathseg_unit,pathseg_len,pt) =
|
|||
]);
|
||||
|
||||
|
||||
function _offset_region(region, r, delta, chamfer, check_valid, quality,closed,return_faces,firstface_index,flip_faces) =
|
||||
let(
|
||||
reglist = [for(R=region_parts(region)) force_region(R)],
|
||||
ofsregs = [for(R=reglist)
|
||||
difference([for(i=idx(R)) offset(R[i], r=u_mul(i>0?-1:1,r), delta=u_mul(i>0?-1:1,delta),
|
||||
chamfer=chamfer, check_valid=check_valid, quality=quality,closed=true)])]
|
||||
)
|
||||
union(ofsregs);
|
||||
|
||||
|
||||
function d_offset_region(
|
||||
paths, r, delta, chamfer, closed,
|
||||
check_valid, quality,
|
||||
return_faces, firstface_index,
|
||||
flip_faces, _acc=[], _i=0
|
||||
) =
|
||||
_i>=len(paths)? _acc :
|
||||
_offset_region(
|
||||
paths, _i=_i+1,
|
||||
_acc = (paths[_i].x % 2 == 0)? (
|
||||
union(_acc, [
|
||||
offset(
|
||||
paths[_i].y,
|
||||
r=r, delta=delta, chamfer=chamfer, closed=closed,
|
||||
check_valid=check_valid, quality=quality,
|
||||
return_faces=return_faces, firstface_index=firstface_index,
|
||||
flip_faces=flip_faces
|
||||
)
|
||||
])
|
||||
) : (
|
||||
difference(_acc, [
|
||||
offset(
|
||||
paths[_i].y,
|
||||
r=u_mul(-1,r), delta=u_mul(-1,delta), chamfer=chamfer, closed=closed,
|
||||
check_valid=check_valid, quality=quality,
|
||||
return_faces=return_faces, firstface_index=firstface_index,
|
||||
flip_faces=flip_faces
|
||||
)
|
||||
])
|
||||
),
|
||||
r=r, delta=delta, chamfer=chamfer, closed=closed,
|
||||
check_valid=check_valid, quality=quality,
|
||||
return_faces=return_faces, firstface_index=firstface_index, flip_faces=flip_faces
|
||||
);
|
||||
|
||||
|
||||
// Function: offset()
|
||||
// Usage:
|
||||
// offsetpath = offset(path, [r|delta], [chamfer], [closed], [check_valid], [quality])
|
||||
|
@ -972,30 +771,14 @@ function offset(
|
|||
quality=1, return_faces=false, firstface_index=0,
|
||||
flip_faces=false
|
||||
) =
|
||||
is_region(path)? _offset_region(path,r=r,delta=delta,chamfer=chamfer,quality=quality,check_valid=check_valid)
|
||||
|
||||
/*
|
||||
(
|
||||
is_region(path)?
|
||||
assert(!return_faces, "return_faces not supported for regions.")
|
||||
let(
|
||||
path = [for (p=path) clockwise_polygon(p)],
|
||||
rgn = exclusive_or([for (p = path) [p]]),
|
||||
pathlist = sort(idx=0,[
|
||||
for (i=[0:1:len(rgn)-1]) [
|
||||
sum(concat([0],[
|
||||
for (j=[0:1:len(rgn)-1]) if (i!=j)
|
||||
point_in_polygon(rgn[i][0],rgn[j])>=0? 1 : 0
|
||||
])),
|
||||
rgn[i]
|
||||
]
|
||||
])
|
||||
) _offset_region(
|
||||
pathlist, r=r, delta=delta, chamfer=chamfer, closed=true,
|
||||
check_valid=check_valid, quality=quality,
|
||||
return_faces=return_faces, firstface_index=firstface_index,
|
||||
flip_faces=flip_faces
|
||||
ofsregs = [for(R=region_parts(path))
|
||||
difference([for(i=idx(R)) offset(R[i], r=u_mul(i>0?-1:1,r), delta=u_mul(i>0?-1:1,delta),
|
||||
chamfer=chamfer, check_valid=check_valid, quality=quality,closed=true)])]
|
||||
)
|
||||
)*/
|
||||
union(ofsregs)
|
||||
: let(rcount = num_defined([r,delta]))
|
||||
assert(rcount==1,"Must define exactly one of 'delta' and 'r'")
|
||||
let(
|
||||
|
@ -1088,52 +871,21 @@ function offset(
|
|||
)
|
||||
) return_faces? [edges,faces] : edges;
|
||||
|
||||
/// Internal Function: _tag_subpaths()
|
||||
/// splits the polygon (path) into subpaths by region crossing and then tags each subpath:
|
||||
/// "O" - the subpath is outside the region
|
||||
/// "I" - the subpath is inside the region's interior
|
||||
/// "S" - the subpath is on the region's border and the polygon and region are on the same side of the subpath
|
||||
/// "U" - the subpath is on the region's border and the polygon and region meet at the subpath (from opposite sides)
|
||||
/// The return has the form of a list with entries [TAG, SUBPATH]
|
||||
function _tag_subpaths(region1, region2, keep, eps=EPSILON) =
|
||||
// We have to compute common vertices between paths in the region because
|
||||
// they can be places where the path must be cut, even though they aren't
|
||||
// found my the split_path function.
|
||||
let(
|
||||
keepS = search("S",keep)!=[],
|
||||
keepU = search("U",keep)!=[],
|
||||
keepoutside = search("O",keep) !=[],
|
||||
keepinside = search("I",keep) !=[],
|
||||
points = flatten(region1),
|
||||
tree = len(points)>0 ? vector_search_tree(points): undef
|
||||
)
|
||||
[for(p=region1)
|
||||
let(
|
||||
path = deduplicate(p),
|
||||
self_int = is_undef(tree)?[]:[for(i=idx(path)) if (len(vector_search(path[i], eps, tree))>1) [i,0]],
|
||||
subpaths = split_path_at_region_crossings(path, region2, eps=eps, extra=self_int)
|
||||
)
|
||||
for (subpath = subpaths)
|
||||
let(
|
||||
midpt = mean([subpath[0], subpath[1]]),
|
||||
rel = point_in_region(midpt,region2,eps=eps),
|
||||
keepthis = rel<0 ? keepoutside
|
||||
: rel>0 ? keepinside
|
||||
: !(keepS || keepU) ? false
|
||||
: let(
|
||||
sidept = midpt + 0.01*line_normal(subpath[0],subpath[1]),
|
||||
rel1 = point_in_region(sidept,region1,eps=eps)>0,
|
||||
rel2 = point_in_region(sidept,region2,eps=eps)>0
|
||||
)
|
||||
rel1==rel2 ? keepS : keepU
|
||||
)
|
||||
if (keepthis) subpath
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
function _keep_some_region_parts(region1, region2, keep1, keep2, eps=EPSILON) =
|
||||
/// Internal Function: _filter_region_parts()
|
||||
///
|
||||
/// splits region1 into subpaths where either it touches itself or crosses region2. Classifies all of the
|
||||
/// subpaths as described below and keeps the ones listed in keep1. A similar process is performed for region2.
|
||||
/// All of the kept subpaths are assembled into polygons and returned as a lst.
|
||||
/// .
|
||||
/// The four types of subpath from the region are defined relative to the second region:
|
||||
/// "O" - the subpath is outside the second region
|
||||
/// "I" - the subpath is in the second region's interior
|
||||
/// "S" - the subpath is on the 2nd region's border and the two regions interiors are on the same side of the subpath
|
||||
/// "U" - the subpath is on the 2nd region's border and the two regions meet at the subpath from opposite sides
|
||||
/// You specify which type of subpaths to keep with a string of the desired types such as "OS".
|
||||
function _filter_region_parts(region1, region2, keep1, keep2, eps=EPSILON) =
|
||||
// We have to compute common vertices between paths in the region because
|
||||
// they can be places where the path must be cut, even though they aren't
|
||||
// found my the split_path function.
|
||||
|
@ -1192,8 +944,8 @@ function union(regions=[],b=undef,c=undef,eps=EPSILON) =
|
|||
len(regions)==1? regions[0] :
|
||||
let(regions=[for (r=regions) quant(is_path(r)? [r] : r, 1/65536)])
|
||||
union([
|
||||
_keep_some_region_parts(regions[0],regions[1],"OS", "O", eps=eps),
|
||||
for (i=[2:1:len(regions)-1]) regions[i]
|
||||
_filter_region_parts(regions[0],regions[1],"OS", "O", eps=eps),
|
||||
for (i=[2:1:len(regions)-1]) regions[i]
|
||||
],
|
||||
eps=eps
|
||||
);
|
||||
|
@ -1224,7 +976,7 @@ function difference(regions=[],b=undef,c=undef,eps=EPSILON) =
|
|||
regions[0]==[] ? [] :
|
||||
let(regions=[for (r=regions) quant(is_path(r)? [r] : r, 1/65536)])
|
||||
difference([
|
||||
_keep_some_region_parts(regions[0],regions[1],"OU", "I", eps=eps),
|
||||
_filter_region_parts(regions[0],regions[1],"OU", "I", eps=eps),
|
||||
for (i=[2:1:len(regions)-1]) regions[i]
|
||||
],
|
||||
eps=eps
|
||||
|
@ -1255,7 +1007,7 @@ function intersection(regions=[],b=undef,c=undef,eps=EPSILON) =
|
|||
: regions[0]==[] || regions[1]==[] ? []
|
||||
: let(regions=[for (r=regions) quant(is_path(r)? [r] : r, 1/65536)])
|
||||
intersection([
|
||||
_keep_some_region_parts(regions[0],regions[1],"IS","I",eps=eps),
|
||||
_filter_region_parts(regions[0],regions[1],"IS","I",eps=eps),
|
||||
for (i=[2:1:len(regions)-1]) regions[i]
|
||||
],
|
||||
eps=eps
|
||||
|
@ -1293,7 +1045,7 @@ function exclusive_or(regions=[],b=undef,c=undef,eps=EPSILON) =
|
|||
len(regions)==1? regions[0] :
|
||||
let(regions=[for (r=regions) is_path(r)? [r] : r])
|
||||
exclusive_or([
|
||||
_keep_some_region_parts(regions[0],regions[1],"IO","IO",eps=eps),
|
||||
_filter_region_parts(regions[0],regions[1],"IO","IO",eps=eps),
|
||||
for (i=[2:1:len(regions)-1]) regions[i]
|
||||
],
|
||||
eps=eps
|
||||
|
|
Loading…
Reference in a new issue