hiding some infrastructure, added is_path_simple()

This commit is contained in:
Adrian Mariano 2021-09-22 14:59:18 -04:00
parent 43f37908df
commit 2f895cb8d1
5 changed files with 353 additions and 332 deletions

View File

@ -113,7 +113,7 @@ function point_line_distance(pt, line, bounded=false) =
// Function: segment_distance()
// Usage:
// dist = segment_distance(seg1, seg2);
// dist = segment_distance(seg1, seg2, [eps]);
// Topics: Geometry, Segments, Distance
// See Also: convex_collision(), convex_distance()
// Description:
@ -121,12 +121,13 @@ function point_line_distance(pt, line, bounded=false) =
// Arguments:
// seg1 = The list of two points representing the first line segment to check the distance of.
// seg2 = The list of two points representing the second line segment to check the distance of.
// eps = tolerance for point comparisons
// Example:
// dist = segment_distance([[-14,3], [-15,9]], [[-10,0], [10,0]]); // Returns: 5
// dist2 = segment_distance([[-5,5], [5,-5]], [[-10,3], [10,-3]]); // Returns: 0
function segment_distance(seg1, seg2) =
function segment_distance(seg1, seg2,eps=EPSILON) =
assert( is_matrix(concat(seg1,seg2),4), "Inputs should be two valid segments." )
convex_distance(seg1,seg2);
convex_distance(seg1,seg2,eps);
// Function: line_normal()

View File

@ -6,7 +6,7 @@
//////////////////////////////////////////////////////////////////////
// Section: Functions
// Section: Utility Functions
// Function: is_path()
@ -72,7 +72,7 @@ function cleanup_path(path, eps=EPSILON) =
is_closed_path(path,eps=eps)? [for (i=[0:1:len(path)-2]) path[i]] : path;
/// internal Function: _path_select()
/// Internal Function: _path_select()
/// Usage:
/// _path_select(path,s1,u1,s2,u2,[closed]):
/// Description:
@ -125,6 +125,8 @@ function path_merge_collinear(path, closed=false, eps=EPSILON) =
) [for (i=indices) path[i]];
// Section: Path length calculation
// Function: path_length()
// Usage:
@ -179,54 +181,30 @@ function path_length_fractions(path, closed=false) =
) partial_len / total_len;
// Function: path_closest_point()
// Usage:
// path_closest_point(path, pt);
// Description:
// Finds the closest path segment, and point on that segment to the given point.
// Returns `[SEGNUM, POINT]`
// Arguments:
// path = The path to find the closest point on.
// pt = the point to find the closest point to.
// Example(2D):
// path = circle(d=100,$fn=6);
// pt = [20,10];
// closest = path_closest_point(path, pt);
// stroke(path, closed=true);
// color("blue") translate(pt) circle(d=3, $fn=12);
// color("red") translate(closest[1]) circle(d=3, $fn=12);
function path_closest_point(path, pt) =
let(
pts = [for (seg=idx(path)) line_closest_point(select(path,seg,seg+1),pt,SEGMENT)],
dists = [for (p=pts) norm(p-pt)],
min_seg = min_index(dists)
) [min_seg, pts[min_seg]];
// Function: path_self_intersections()
// Usage:
// isects = path_self_intersections(path, [eps]);
// Description:
// Locates all self intersections 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.
// Arguments:
// path = The path to find self intersections of.
// closed = If true, treat path like a closed polygon. Default: true
// eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9)
// Example(2D):
// path = [
// [-100,100], [0,-50], [100,100], [100,-100], [0,50], [-100,-100]
// ];
// isects = path_self_intersections(path, closed=true);
// // isects == [[[-33.3333, 0], 0, 0.666667, 4, 0.333333], [[33.3333, 0], 1, 0.333333, 3, 0.666667]]
// stroke(path, closed=true, width=1);
// for (isect=isects) translate(isect[0]) color("blue") sphere(d=10);
function path_self_intersections(path, closed=true, eps=EPSILON) =
/// Internal Function: _path_self_intersections()
/// Usage:
/// isects = _path_self_intersections(path, [closed], [eps]);
/// Description:
/// Locates all self intersections 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.
/// Arguments:
/// path = The path to find self intersections of.
/// closed = If true, treat path like a closed polygon. Default: true
/// eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9)
/// Example(2D):
/// path = [
/// [-100,100], [0,-50], [100,100], [100,-100], [0,50], [-100,-100]
/// ];
/// isects = _path_self_intersections(path, closed=true);
/// // isects == [[[-33.3333, 0], 0, 0.666667, 4, 0.333333], [[33.3333, 0], 1, 0.333333, 3, 0.666667]]
/// stroke(path, closed=true, width=1);
/// for (isect=isects) translate(isect[0]) color("blue") sphere(d=10);
function _path_self_intersections(path, closed=true, eps=EPSILON) =
let(
path = cleanup_path(path, eps=eps),
plen = len(path)
@ -261,7 +239,230 @@ function path_self_intersections(path, closed=true, eps=EPSILON) =
// Section: Geometric Properties of Paths
// Section: Resampling: changing the number of points in a path
// Input `data` is a list that sums to an integer.
// Returns rounded version of input data so that every
// entry is rounded to an integer and the sum is the same as
// that of the input. Works by rounding an entry in the list
// and passing the rounding error forward to the next entry.
// This will generally distribute the error in a uniform manner.
function _sum_preserving_round(data, index=0) =
index == len(data)-1 ? list_set(data, len(data)-1, round(data[len(data)-1])) :
let(
newval = round(data[index]),
error = newval - data[index]
) _sum_preserving_round(
list_set(data, [index,index+1], [newval, data[index+1]-error]),
index+1
);
// Function: subdivide_path()
// Usage:
// newpath = subdivide_path(path, [N|refine], method);
// Description:
// Takes a path as input (closed or open) and subdivides the path to produce a more
// finely sampled path. The new points can be distributed proportional to length
// (`method="length"`) or they can be divided up evenly among all the path segments
// (`method="segment"`). If the extra points don't fit evenly on the path then the
// algorithm attempts to distribute them uniformly. The `exact` option requires that
// the final length is exactly as requested. If you set it to `false` then the
// algorithm will favor uniformity and the output path may have a different number of
// points due to rounding error.
// .
// With the `"segment"` method you can also specify a vector of lengths. This vector,
// `N` specfies the desired point count on each segment: with vector input, `subdivide_path`
// attempts to place `N[i]-1` points on segment `i`. The reason for the -1 is to avoid
// double counting the endpoints, which are shared by pairs of segments, so that for
// a closed polygon the total number of points will be sum(N). Note that with an open
// path there is an extra point at the end, so the number of points will be sum(N)+1.
// Arguments:
// path = path to subdivide
// N = scalar total number of points desired or with `method="segment"` can be a vector requesting `N[i]-1` points on segment i.
// refine = number of points to add each segment.
// closed = set to false if the path is open. Default: True
// exact = if true return exactly the requested number of points, possibly sacrificing uniformity. If false, return uniform point sample that may not match the number of points requested. Default: True
// method = One of `"length"` or `"segment"`. If `"length"`, adds vertices evenly along the total path length. If `"segment"`, adds points evenly among the segments. Default: `"length"`
// Example(2D):
// mypath = subdivide_path(square([2,2],center=true), 12);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D):
// mypath = subdivide_path(square([8,2],center=true), 12);
// move_copies(mypath)circle(r=.2,$fn=32);
// Example(2D):
// mypath = subdivide_path(square([8,2],center=true), 12, method="segment");
// move_copies(mypath)circle(r=.2,$fn=32);
// Example(2D):
// mypath = subdivide_path(square([2,2],center=true), 17, closed=false);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D): Specifying different numbers of points on each segment
// mypath = subdivide_path(hexagon(side=2), [2,3,4,5,6,7], method="segment");
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D): Requested point total is 14 but 15 points output due to extra end point
// mypath = subdivide_path(pentagon(side=2), [3,4,3,4], method="segment", closed=false);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D): Since 17 is not divisible by 5, a completely uniform distribution is not possible.
// mypath = subdivide_path(pentagon(side=2), 17);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D): With `exact=false` a uniform distribution, but only 15 points
// mypath = subdivide_path(pentagon(side=2), 17, exact=false);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D): With `exact=false` you can also get extra points, here 20 instead of requested 18
// mypath = subdivide_path(pentagon(side=2), 18, exact=false);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(FlatSpin,VPD=15,VPT=[0,0,1.5]): Three-dimensional paths also work
// mypath = subdivide_path([[0,0,0],[2,0,1],[2,3,2]], 12);
// move_copies(mypath)sphere(r=.1,$fn=32);
function subdivide_path(path, N, refine, closed=true, exact=true, method="length") =
assert(is_path(path))
assert(method=="length" || method=="segment")
assert(num_defined([N,refine]),"Must give exactly one of N and refine")
let(
N = !is_undef(N)? N :
!is_undef(refine)? len(path) * refine :
undef
)
assert((is_num(N) && N>0) || is_vector(N),"Parameter N to subdivide_path must be postive number or vector")
let(
count = len(path) - (closed?0:1),
add_guess = method=="segment"? (
is_list(N)? (
assert(len(N)==count,"Vector parameter N to subdivide_path has the wrong length")
add_scalar(N,-1)
) : repeat((N-len(path)) / count, count)
) : // method=="length"
assert(is_num(N),"Parameter N to subdivide path must be a number when method=\"length\"")
let(
path_lens = concat(
[ for (i = [0:1:len(path)-2]) norm(path[i+1]-path[i]) ],
closed? [norm(path[len(path)-1]-path[0])] : []
),
add_density = (N - len(path)) / sum(path_lens)
)
path_lens * add_density,
add = exact? _sum_preserving_round(add_guess) :
[for (val=add_guess) round(val)]
) concat(
[
for (i=[0:1:count]) each [
for(j=[0:1:add[i]])
lerp(path[i],select(path,i+1), j/(add[i]+1))
]
],
closed? [] : [last(path)]
);
// Function: subdivide_long_segments()
// Topics: Paths, Path Subdivision
// See Also: subdivide_path(), subdivide_and_slice(), path_add_jitter(), jittered_poly()
// Usage:
// spath = subdivide_long_segments(path, maxlen, [closed=]);
// Description:
// Evenly subdivides long `path` segments until they are all shorter than `maxlen`.
// Arguments:
// path = The path to subdivide.
// maxlen = The maximum allowed path segment length.
// ---
// closed = If true, treat path like a closed polygon. Default: true
// Example:
// path = pentagon(d=100);
// spath = subdivide_long_segments(path, 10, closed=true);
// stroke(path);
// color("lightgreen") move_copies(path) circle(d=5,$fn=12);
// color("blue") move_copies(spath) circle(d=3,$fn=12);
function subdivide_long_segments(path, maxlen, closed=false) =
assert(is_path(path))
assert(is_finite(maxlen))
assert(is_bool(closed))
[
for (p=pair(path,closed)) let(
steps = ceil(norm(p[1]-p[0])/maxlen)
) each lerpn(p[0], p[1], steps, false),
if (!closed) last(path)
];
// Function: resample_path()
// Usage:
// newpath = resample_path(path, N|spacing, [closed]);
// Description:
// Compute a uniform resampling of the input path. If you specify `N` then the output path will have N
// points spaced uniformly (by linear interpolation along the input path segments). The only points of the
// input path that are guaranteed to appear in the output path are the starting and ending points.
// If you specify `spacing` then the length you give will be rounded to the nearest spacing that gives
// a uniform sampling of the path and the resulting uniformly sampled path is returned.
// Note that because this function operates on a discrete input path the quality of the output depends on
// the sampling of the input. If you want very accurate output, use a lot of points for the input.
// Arguments:
// path = path to resample
// N = Number of points in output
// spacing = Approximate spacing desired
// closed = set to true if path is closed. Default: false
function resample_path(path, N, spacing, closed=false) =
assert(is_path(path))
assert(num_defined([N,spacing])==1,"Must define exactly one of N and spacing")
assert(is_bool(closed))
let(
length = path_length(path,closed),
// In the open path case decrease N by 1 so that we don't try to get
// path_cut to return the endpoint (which might fail due to rounding)
// Add last point later
N = is_def(N) ? N-(closed?0:1) : round(length/spacing),
distlist = lerpn(0,length,N,false),
cuts = _path_cut_points(path, distlist, closed=closed)
)
[ each subindex(cuts,0),
if (!closed) last(path) // Then add last point here
];
// Section: Path Geometry
// Function: is_path_simple()
// Usage:
// bool = is_path_simple(path, [closed], [eps]);
// Description:
// Returns true if the path is simple, meaning that it has no self-intersections.
// If closed is set to true then treat the path as a polygon.
// Arguments:
// path = path to check
// 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) =
_path_self_intersections(path,closed=closed,eps=eps) == [];
// Function: path_closest_point()
// Usage:
// path_closest_point(path, pt);
// Description:
// Finds the closest path segment, and point on that segment to the given point.
// Returns `[SEGNUM, POINT]`
// Arguments:
// path = The path to find the closest point on.
// pt = the point to find the closest point to.
// Example(2D):
// path = circle(d=100,$fn=6);
// pt = [20,10];q
// closest = path_closest_point(path, pt);
// stroke(path, closed=true);
// color("blue") translate(pt) circle(d=3, $fn=12);
// color("red") translate(closest[1]) circle(d=3, $fn=12);
function path_closest_point(path, pt) =
let(
pts = [for (seg=idx(path)) line_closest_point(select(path,seg,seg+1),pt,SEGMENT)],
dists = [for (p=pts) norm(p-pt)],
min_seg = min_index(dists)
) [min_seg, pts[min_seg]];
// Function: path_tangents()
// Usage:
@ -522,228 +723,6 @@ function _corner_roundover_path(p1, p2, p3, r, d) =
// Function: path_add_jitter()
// Topics: Paths
// See Also: jittered_poly(), subdivide_long_segments()
// Usage:
// jpath = path_add_jitter(path, [dist], [closed=]);
// Description:
// Adds tiny jitter offsets to collinear points in the given path so that they
// are no longer collinear. This is useful for preserving subdivision on long
// straight segments, when making geometry with `polygon()`, for use with
// `linear_exrtrude()` with a `twist()`.
// Arguments:
// path = The path to add jitter to.
// dist = The amount to jitter points by. Default: 1/512 (0.00195)
// ---
// closed = If true, treat path like a closed polygon. Default: true
// Example(3D):
// d = 100; h = 75; quadsize = 5;
// path = pentagon(d=d);
// spath = subdivide_long_segments(path, quadsize, closed=true);
// jpath = path_add_jitter(spath, closed=true);
// linear_extrude(height=h, twist=72, slices=h/quadsize)
// polygon(jpath);
function path_add_jitter(path, dist=1/512, closed=true) =
assert(is_path(path))
assert(is_finite(dist))
assert(is_bool(closed))
[
path[0],
for (i=idx(path,s=1,e=closed?-1:-2)) let(
n = line_normal([path[i-1],path[i]])
) path[i] + n * (is_collinear(select(path,i-1,i+1))? (dist * ((i%2)*2-1)) : 0),
if (!closed) last(path)
];
// Section: Resampling: changing the number of points in a path
// Input `data` is a list that sums to an integer.
// Returns rounded version of input data so that every
// entry is rounded to an integer and the sum is the same as
// that of the input. Works by rounding an entry in the list
// and passing the rounding error forward to the next entry.
// This will generally distribute the error in a uniform manner.
function _sum_preserving_round(data, index=0) =
index == len(data)-1 ? list_set(data, len(data)-1, round(data[len(data)-1])) :
let(
newval = round(data[index]),
error = newval - data[index]
) _sum_preserving_round(
list_set(data, [index,index+1], [newval, data[index+1]-error]),
index+1
);
// Section: Changing sampling of paths
// Function: subdivide_path()
// Usage:
// newpath = subdivide_path(path, [N|refine], method);
// Description:
// Takes a path as input (closed or open) and subdivides the path to produce a more
// finely sampled path. The new points can be distributed proportional to length
// (`method="length"`) or they can be divided up evenly among all the path segments
// (`method="segment"`). If the extra points don't fit evenly on the path then the
// algorithm attempts to distribute them uniformly. The `exact` option requires that
// the final length is exactly as requested. If you set it to `false` then the
// algorithm will favor uniformity and the output path may have a different number of
// points due to rounding error.
// .
// With the `"segment"` method you can also specify a vector of lengths. This vector,
// `N` specfies the desired point count on each segment: with vector input, `subdivide_path`
// attempts to place `N[i]-1` points on segment `i`. The reason for the -1 is to avoid
// double counting the endpoints, which are shared by pairs of segments, so that for
// a closed polygon the total number of points will be sum(N). Note that with an open
// path there is an extra point at the end, so the number of points will be sum(N)+1.
// Arguments:
// path = path to subdivide
// N = scalar total number of points desired or with `method="segment"` can be a vector requesting `N[i]-1` points on segment i.
// refine = number of points to add each segment.
// closed = set to false if the path is open. Default: True
// exact = if true return exactly the requested number of points, possibly sacrificing uniformity. If false, return uniform point sample that may not match the number of points requested. Default: True
// method = One of `"length"` or `"segment"`. If `"length"`, adds vertices evenly along the total path length. If `"segment"`, adds points evenly among the segments. Default: `"length"`
// Example(2D):
// mypath = subdivide_path(square([2,2],center=true), 12);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D):
// mypath = subdivide_path(square([8,2],center=true), 12);
// move_copies(mypath)circle(r=.2,$fn=32);
// Example(2D):
// mypath = subdivide_path(square([8,2],center=true), 12, method="segment");
// move_copies(mypath)circle(r=.2,$fn=32);
// Example(2D):
// mypath = subdivide_path(square([2,2],center=true), 17, closed=false);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D): Specifying different numbers of points on each segment
// mypath = subdivide_path(hexagon(side=2), [2,3,4,5,6,7], method="segment");
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D): Requested point total is 14 but 15 points output due to extra end point
// mypath = subdivide_path(pentagon(side=2), [3,4,3,4], method="segment", closed=false);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D): Since 17 is not divisible by 5, a completely uniform distribution is not possible.
// mypath = subdivide_path(pentagon(side=2), 17);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D): With `exact=false` a uniform distribution, but only 15 points
// mypath = subdivide_path(pentagon(side=2), 17, exact=false);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(2D): With `exact=false` you can also get extra points, here 20 instead of requested 18
// mypath = subdivide_path(pentagon(side=2), 18, exact=false);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(FlatSpin,VPD=15,VPT=[0,0,1.5]): Three-dimensional paths also work
// mypath = subdivide_path([[0,0,0],[2,0,1],[2,3,2]], 12);
// move_copies(mypath)sphere(r=.1,$fn=32);
function subdivide_path(path, N, refine, closed=true, exact=true, method="length") =
assert(is_path(path))
assert(method=="length" || method=="segment")
assert(num_defined([N,refine]),"Must give exactly one of N and refine")
let(
N = !is_undef(N)? N :
!is_undef(refine)? len(path) * refine :
undef
)
assert((is_num(N) && N>0) || is_vector(N),"Parameter N to subdivide_path must be postive number or vector")
let(
count = len(path) - (closed?0:1),
add_guess = method=="segment"? (
is_list(N)? (
assert(len(N)==count,"Vector parameter N to subdivide_path has the wrong length")
add_scalar(N,-1)
) : repeat((N-len(path)) / count, count)
) : // method=="length"
assert(is_num(N),"Parameter N to subdivide path must be a number when method=\"length\"")
let(
path_lens = concat(
[ for (i = [0:1:len(path)-2]) norm(path[i+1]-path[i]) ],
closed? [norm(path[len(path)-1]-path[0])] : []
),
add_density = (N - len(path)) / sum(path_lens)
)
path_lens * add_density,
add = exact? _sum_preserving_round(add_guess) :
[for (val=add_guess) round(val)]
) concat(
[
for (i=[0:1:count]) each [
for(j=[0:1:add[i]])
lerp(path[i],select(path,i+1), j/(add[i]+1))
]
],
closed? [] : [last(path)]
);
// Function: subdivide_long_segments()
// Topics: Paths, Path Subdivision
// See Also: subdivide_path(), subdivide_and_slice(), path_add_jitter(), jittered_poly()
// Usage:
// spath = subdivide_long_segments(path, maxlen, [closed=]);
// Description:
// Evenly subdivides long `path` segments until they are all shorter than `maxlen`.
// Arguments:
// path = The path to subdivide.
// maxlen = The maximum allowed path segment length.
// ---
// closed = If true, treat path like a closed polygon. Default: true
// Example:
// path = pentagon(d=100);
// spath = subdivide_long_segments(path, 10, closed=true);
// stroke(path);
// color("lightgreen") move_copies(path) circle(d=5,$fn=12);
// color("blue") move_copies(spath) circle(d=3,$fn=12);
function subdivide_long_segments(path, maxlen, closed=false) =
assert(is_path(path))
assert(is_finite(maxlen))
assert(is_bool(closed))
[
for (p=pair(path,closed)) let(
steps = ceil(norm(p[1]-p[0])/maxlen)
) each lerpn(p[0], p[1], steps, false),
if (!closed) last(path)
];
// Function: resample_path()
// Usage:
// newpath = resample_path(path, N|spacing, [closed]);
// Description:
// Compute a uniform resampling of the input path. If you specify `N` then the output path will have N
// points spaced uniformly (by linear interpolation along the input path segments). The only points of the
// input path that are guaranteed to appear in the output path are the starting and ending points.
// If you specify `spacing` then the length you give will be rounded to the nearest spacing that gives
// a uniform sampling of the path and the resulting uniformly sampled path is returned.
// Note that because this function operates on a discrete input path the quality of the output depends on
// the sampling of the input. If you want very accurate output, use a lot of points for the input.
// Arguments:
// path = path to resample
// N = Number of points in output
// spacing = Approximate spacing desired
// closed = set to true if path is closed. Default: false
function resample_path(path, N, spacing, closed=false) =
assert(is_path(path))
assert(num_defined([N,spacing])==1,"Must define exactly one of N and spacing")
assert(is_bool(closed))
let(
length = path_length(path,closed),
// In the open path case decrease N by 1 so that we don't try to get
// path_cut to return the endpoint (which might fail due to rounding)
// Add last point later
N = is_def(N) ? N-(closed?0:1) : round(length/spacing),
distlist = lerpn(0,length,N,false),
cuts = _path_cut_points(path, distlist, closed=closed)
)
[ each subindex(cuts,0),
if (!closed) last(path) // Then add last point here
];
// Section: Breaking paths up into subpaths
@ -963,7 +942,7 @@ function split_path_at_self_crossings(path, closed=true, eps=EPSILON) =
[[0, 0]],
sort([
for (
a = path_self_intersections(path, closed=closed, eps=eps),
a = _path_self_intersections(path, closed=closed, eps=eps),
ss = [ [a[1],a[2]], [a[3],a[4]] ]
) if (ss[0] != undef) ss
]),

View File

@ -29,31 +29,17 @@ function is_region(x) = is_list(x) && is_path(x.x);
function close_region(region, eps=EPSILON) = [for (path=region) close_path(path, eps=eps)];
// Module: region()
// Function: cleanup_region()
// Usage:
// region(r);
// cleanup_region(region);
// Description:
// Creates 2D polygons for the given region. The region given is a list of closed 2D paths.
// Each path will be effectively exclusive-ORed from all other paths in the region, so if a
// path is inside another path, it will be effectively subtracted from it.
// Example(2D):
// region([circle(d=50), square(25,center=true)]);
// Example(2D):
// rgn = concat(
// [for (d=[50:-10:10]) circle(d=d-5)],
// [square([60,10], center=true)]
// );
// region(rgn);
module region(r)
{
points = flatten(r);
paths = [
for (i=[0:1:len(r)-1]) let(
start = default(sum([for (j=[0:1:i-1]) len(r[j])]),0)
) [for (k=[0:1:len(r[i])-1]) start+k]
];
polygon(points=points, paths=paths);
}
// For all paths in the given region, if the last point coincides with the first point, removes the last point.
// Arguments:
// region = The region to clean up. Given as a list of polygon paths.
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
function cleanup_region(region, eps=EPSILON) =
[for (path=region) cleanup_path(path, eps=eps)];
// Function: check_and_fix_path()
@ -91,16 +77,34 @@ function check_and_fix_path(path, valid_dim=undef, closed=false, name="path") =
closed && approx(path[0], last(path))? list_head(path) : path;
// Function: cleanup_region()
// Module: region()
// Usage:
// cleanup_region(region);
// region(r);
// Description:
// For all paths in the given region, if the last point coincides with the first point, removes the last point.
// Arguments:
// region = The region to clean up. Given as a list of polygon paths.
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
function cleanup_region(region, eps=EPSILON) =
[for (path=region) cleanup_path(path, eps=eps)];
// Creates 2D polygons for the given region. The region given is a list of closed 2D paths.
// Each path will be effectively exclusive-ORed from all other paths in the region, so if a
// path is inside another path, it will be effectively subtracted from it.
// Example(2D):
// region([circle(d=50), square(25,center=true)]);
// Example(2D):
// rgn = concat(
// [for (d=[50:-10:10]) circle(d=d-5)],
// [square([60,10], center=true)]
// );
// region(rgn);
module region(r)
{
points = flatten(r);
paths = [
for (i=[0:1:len(r)-1]) let(
start = default(sum([for (j=[0:1:i-1]) len(r[j])]),0)
) [for (k=[0:1:len(r[i])-1]) start+k]
];
polygon(points=points, paths=paths);
}
// Function: point_in_region()
@ -121,6 +125,7 @@ function point_in_region(point, region, eps=EPSILON, _i=0, _cnt=0) =
) pip==0? 0 : point_in_region(point, region, eps=eps, _i=_i+1, _cnt = _cnt + (pip>0? 1 : 0));
// Function: polygons_equal()
// Usage:
// b = polygons_equal(poly1, poly2, [eps])
@ -151,23 +156,23 @@ function __polygons_equal(poly1, poly2, eps, st) =
max([for(d=poly1-select(poly2,st,st-1)) d*d])<eps*eps;
// Function: poly_in_polygons()
// Function: is_polygon_in_list()
// Topics: Polygons, Comparators
// See Also: polygons_equal(), regions_equal()
// Usage:
// bool = poly_in_polygons(poly, polys);
// bool = is_polygon_in_list(poly, polys);
// Description:
// Returns true if one of the polygons in `polys` is equivalent to the polygon `poly`.
// Arguments:
// poly = The polygon to search for.
// polys = The list of polygons to look for the polygon in.
function poly_in_polygons(poly, polys) =
__poly_in_polygons(poly, polys, 0);
function is_polygon_in_list(poly, polys) =
__is_polygon_in_list(poly, polys, 0);
function __poly_in_polygons(poly, polys, i) =
function __is_polygon_in_list(poly, polys, i) =
i >= len(polys)? false :
polygons_equal(poly, polys[i])? true :
__poly_in_polygons(poly, polys, i+1);
__is_polygon_in_list(poly, polys, i+1);
// Function: regions_equal()
@ -187,21 +192,21 @@ function regions_equal(region1, region2) =
function __regions_equal(region1, region2, i) =
i >= len(region1)? true :
!poly_in_polygons(region1[i], region2)? false :
!is_polygon_in_list(region1[i], region2)? false :
__regions_equal(region1, region2, i+1);
// Function: region_path_crossings()
// Usage:
// region_path_crossings(path, region);
// Description:
// Returns a sorted list of [SEGMENT, U] that describe where a given path is crossed by a second path.
// 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) = sort([
/// Internal Function: _region_path_crossings()
/// Usage:
/// _region_path_crossings(path, region);
/// Description:
/// Returns a sorted list of [SEGMENT, U] that describe where a given path is crossed by a second path.
/// 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) = sort([
let(
segs = pair(closed? close_path(path) : cleanup_path(path))
) for (
@ -240,7 +245,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 = _region_path_crossings(path, region, closed=closed, eps=eps),
crossings = deduplicate(
concat([[0,0]], xings, [[len(path)-1,1]]),
eps=eps

View File

@ -1931,8 +1931,8 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
top_patch[i][4][4]
]
],
top_intersections = path_self_intersections(faces[0]),
bot_intersections = path_self_intersections(faces[1]),
top_simple = is_path_simple(faces[0],closed=true),
bot_simple = is_path_simple(faces[1],closed=true),
// verify vertical edges
verify_vert =
[for(i=[0:N-1],j=[0:4])
@ -1949,9 +1949,9 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
)
if (!is_collinear(hline_top) || !is_collinear(hline_bot)) [i,j]]
)
assert(debug || top_intersections==[],
assert(debug || top_simple,
"Roundovers interfere with each other on top face: either input is self intersecting or top joint length is too large")
assert(debug || bot_intersections==[],
assert(debug || bot_simple,
"Roundovers interfere with each other on bottom face: either input is self intersecting or top joint length is too large")
assert(debug || (verify_vert==[] && verify_horiz==[]), "Curvature continuity failed")
let(

View File

@ -810,9 +810,45 @@ module star(n, r, ir, d, or, od, id, step, realign=false, align_tip, align_pit,
/// Internal Function: _path_add_jitter()
/// Topics: Paths
/// See Also: jittered_poly(), subdivide_long_segments()
/// Usage:
/// jpath = _path_add_jitter(path, [dist], [closed=]);
/// Description:
/// Adds tiny jitter offsets to collinear points in the given path so that they
/// are no longer collinear. This is useful for preserving subdivision on long
/// straight segments, when making geometry with `polygon()`, for use with
/// `linear_exrtrude()` with a `twist()`.
/// Arguments:
/// path = The path to add jitter to.
/// dist = The amount to jitter points by. Default: 1/512 (0.00195)
/// ---
/// closed = If true, treat path like a closed polygon. Default: true
/// Example(3D):
/// d = 100; h = 75; quadsize = 5;
/// path = pentagon(d=d);
/// spath = subdivide_long_segments(path, quadsize, closed=true);
/// jpath = _path_add_jitter(spath, closed=true);
/// linear_extrude(height=h, twist=72, slices=h/quadsize)
/// polygon(jpath);
function _path_add_jitter(path, dist=1/512, closed=true) =
assert(is_path(path))
assert(is_finite(dist))
assert(is_bool(closed))
[
path[0],
for (i=idx(path,s=1,e=closed?-1:-2)) let(
n = line_normal([path[i-1],path[i]])
) path[i] + n * (is_collinear(select(path,i-1,i+1))? (dist * ((i%2)*2-1)) : 0),
if (!closed) last(path)
];
// Module: jittered_poly()
// Topics: Extrusions
// See Also: path_add_jitter(), subdivide_long_segments()
// See Also: _path_add_jitter(), subdivide_long_segments()
// Usage:
// jittered_poly(path, [dist]);
// Description:
@ -829,7 +865,7 @@ module star(n, r, ir, d, or, od, id, step, realign=false, align_tip, align_pit,
// linear_extrude(height=h, twist=72, slices=h/quadsize)
// jittered_poly(spath);
module jittered_poly(path, dist=1/512) {
polygon(path_add_jitter(path, dist, closed=true));
polygon(_path_add_jitter(path, dist, closed=true));
}