Expose path_cut_points()

This commit is contained in:
Adrian Mariano 2022-10-12 23:38:20 -04:00
parent b15b0ffc88
commit 68a26db513
5 changed files with 111 additions and 108 deletions

View file

@ -982,7 +982,7 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed)
);
distOK = is_def(n) || (min(distances)>=0 && max(distances)<=length);
assert(distOK,"Cannot fit all of the copies");
cutlist = _path_cut_points(path, distances, closed, direction=true);
cutlist = path_cut_points(path, distances, closed, direction=true);
planar = len(path[0])==2;
if (true) for(i=[0:1:len(cutlist)-1]) {
$pos = cutlist[i][0];

View file

@ -332,7 +332,7 @@ module stroke(
}
} else {
dummy=assert(trim1<path_length(path)-trim2, "Path is too short for endcap(s). Try a smaller width, or set endcap_length to a smaller value.");
pathcut = _path_cut_points(path, [trim1, path_length(path)-trim2], closed=false);
pathcut = path_cut_points(path, [trim1, path_length(path)-trim2], closed=false);
pathcut_su = _cut_to_seg_u_form(pathcut,path);
path2 = _path_cut_getpaths(path, pathcut, closed=false)[1];
widths = _path_select(width, pathcut_su[0][0], pathcut_su[0][1], pathcut_su[1][0], pathcut_su[1][1]);

View file

@ -514,7 +514,7 @@ function resample_path(path, n, spacing, closed=true) =
// 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)
cuts = path_cut_points(path, distlist, closed=closed)
)
[ each column(cuts,0),
if (!closed) last(path) // Then add last point here
@ -709,47 +709,109 @@ function path_torsion(path, closed=false) =
// Section: Breaking paths up into subpaths
/// Internal Function: _path_cut_points()
///
/// Usage:
/// cuts = _path_cut_points(path, dists, [closed=], [direction=]);
///
/// Description:
/// Cuts a path at a list of distances from the first point in the path. Returns a list of the cut
/// points and indices of the next point in the path after that point. So for example, a return
/// value entry of [[2,3], 5] means that the cut point was [2,3] and the next point on the path after
/// this point is path[5]. If the path is too short then _path_cut_points returns undef. If you set
/// `direction` to true then `_path_cut_points` will also return the tangent vector to the path and a normal
/// vector to the path. It tries to find a normal vector that is coplanar to the path near the cut
/// point. If this fails it will return a normal vector parallel to the xy plane. The output with
/// direction vectors will be `[point, next_index, tangent, normal]`.
/// .
/// If you give the very last point of the path as a cut point then the returned index will be
/// one larger than the last index (so it will not be a valid index). If you use the closed
/// option then the returned index will be equal to the path length for cuts along the closing
/// path segment, and if you give a point equal to the path length you will get an
/// index of len(path)+1 for the index.
///
/// Arguments:
/// path = path to cut
/// dists = distances where the path should be cut (a list) or a scalar single distance
/// ---
/// closed = set to true if the curve is closed. Default: false
/// direction = set to true to return direction vectors. Default: false
///
/// Example(NORENDER):
/// square=[[0,0],[1,0],[1,1],[0,1]];
/// _path_cut_points(square, [.5,1.5,2.5]); // Returns [[[0.5, 0], 1], [[1, 0.5], 2], [[0.5, 1], 3]]
/// _path_cut_points(square, [0,1,2,3]); // Returns [[[0, 0], 1], [[1, 0], 2], [[1, 1], 3], [[0, 1], 4]]
/// _path_cut_points(square, [0,0.8,1.6,2.4,3.2], closed=true); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], [[0, 0.8], 4]]
/// _path_cut_points(square, [0,0.8,1.6,2.4,3.2]); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], undef]
function _path_cut_points(path, dists, closed=false, direction=false) =
// Function: path_cut()
// Topics: Paths
// See Also: split_path_at_self_crossings()
// Usage:
// path_list = path_cut(path, cutdist, [closed]);
// Description:
// Given a list of distances in `cutdist`, cut the path into
// subpaths at those lengths, returning a list of paths.
// If the input path is closed then the final path will include the
// original starting point. The list of cut distances must be
// in ascending order and should not include the endpoints: 0
// or len(path). If you repeat a distance you will get an
// empty list in that position in the output. If you give an
// empty cutdist array you will get the input path as output
// (without the final vertex doubled in the case of a closed path).
// Arguments:
// path = path of any dimension or a 1-region
// cutdist = Distance or list of distances where path is cut
// closed = If true, treat the path as a closed polygon. Default: false
// Example(2D,NoAxes):
// path = circle(d=100);
// segs = path_cut(path, [50, 200], closed=true);
// rainbow(segs) stroke($item, endcaps="butt", width=3);
function path_cut(path,cutdist,closed) =
is_num(cutdist) ? path_cut(path,[cutdist],closed) :
is_1region(path) ? path_cut(path[0], cutdist, default(closed,true)):
let(closed=default(closed,false))
assert(is_bool(closed))
assert(is_vector(cutdist))
assert(last(cutdist)<path_length(path,closed=closed),"Cut distances must be smaller than the path length")
assert(cutdist[0]>0, "Cut distances must be strictly positive")
let(
cutlist = path_cut_points(path,cutdist,closed=closed)
)
_path_cut_getpaths(path, cutlist, closed);
function _path_cut_getpaths(path, cutlist, closed) =
let(
cuts = len(cutlist)
)
[
[ each list_head(path,cutlist[0][1]-1),
if (!approx(cutlist[0][0], path[cutlist[0][1]-1])) cutlist[0][0]
],
for(i=[0:1:cuts-2])
cutlist[i][0]==cutlist[i+1][0] && cutlist[i][1]==cutlist[i+1][1] ? []
:
[ if (!approx(cutlist[i][0], select(path,cutlist[i][1]))) cutlist[i][0],
each slice(path, cutlist[i][1], cutlist[i+1][1]-1),
if (!approx(cutlist[i+1][0], select(path,cutlist[i+1][1]-1))) cutlist[i+1][0],
],
[
if (!approx(cutlist[cuts-1][0], select(path,cutlist[cuts-1][1]))) cutlist[cuts-1][0],
each select(path,cutlist[cuts-1][1],closed ? 0 : -1)
]
];
// Function: path_cut_points()
//
// Usage:
// cuts = path_cut_points(path, cutdist, [closed=], [direction=]);
//
// Description:
// Cuts a path at a list of distances from the first point in the path. Returns a list of the cut
// points and indices of the next point in the path after that point. So for example, a return
// value entry of [[2,3], 5] means that the cut point was [2,3] and the next point on the path after
// this point is path[5]. If the path is too short then path_cut_points returns undef. If you set
// `direction` to true then `path_cut_points` will also return the tangent vector to the path and a normal
// vector to the path. It tries to find a normal vector that is coplanar to the path near the cut
// point. If this fails it will return a normal vector parallel to the xy plane. The output with
// direction vectors will be `[point, next_index, tangent, normal]`.
// .
// If you give the very last point of the path as a cut point then the returned index will be
// one larger than the last index (so it will not be a valid index). If you use the closed
// option then the returned index will be equal to the path length for cuts along the closing
// path segment, and if you give a point equal to the path length you will get an
// index of len(path)+1 for the index.
//
// Arguments:
// path = path to cut
// cutdist = distances where the path should be cut (a list) or a scalar single distance
// ---
// closed = set to true if the curve is closed. Default: false
// direction = set to true to return direction vectors. Default: false
//
// Example(NORENDER):
// square=[[0,0],[1,0],[1,1],[0,1]];
// path_cut_points(square, [.5,1.5,2.5]); // Returns [[[0.5, 0], 1], [[1, 0.5], 2], [[0.5, 1], 3]]
// path_cut_points(square, [0,1,2,3]); // Returns [[[0, 0], 1], [[1, 0], 2], [[1, 1], 3], [[0, 1], 4]]
// path_cut_points(square, [0,0.8,1.6,2.4,3.2], closed=true); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], [[0, 0.8], 4]]
// path_cut_points(square, [0,0.8,1.6,2.4,3.2]); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], undef]
function path_cut_points(path, cutdist, closed=false, direction=false) =
let(long_enough = len(path) >= (closed ? 3 : 2))
assert(long_enough,len(path)<2 ? "Two points needed to define a path" : "Closed path must include three points")
is_num(dists) ? _path_cut_points(path, [dists],closed, direction)[0] :
assert(is_vector(dists))
assert(is_increasing(dists), "Cut distances must be an increasing list")
let(cuts = _path_cut_points_recurse(path,dists,closed))
is_num(cutdist) ? path_cut_points(path, [cutdist],closed, direction)[0] :
assert(is_vector(cutdist))
assert(is_increasing(cutdist), "Cut distances must be an increasing list")
let(cuts = path_cut_points_recurse(path,cutdist,closed))
!direction
? cuts
: let(
@ -759,7 +821,7 @@ function _path_cut_points(path, dists, closed=false, direction=false) =
hstack(cuts, list_to_matrix(dir,1), list_to_matrix(normals,1));
// Main recursive path cut function
function _path_cut_points_recurse(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[]) =
function path_cut_points_recurse(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[]) =
dind == len(dists) ? result :
let(
lastpt = len(result)==0? [] : last(result)[0], // location of last cut point
@ -768,7 +830,7 @@ function _path_cut_points_recurse(path, dists, closed=false, pind=0, dtotal=0, d
? [lerp(lastpt,select(path,pind),(dists[dind]-dtotal)/dpartial),pind]
: _path_cut_single(path, dists[dind]-dtotal-dpartial, closed, pind)
)
_path_cut_points_recurse(path, dists, closed, nextpoint[1], dists[dind],dind+1, concat(result, [nextpoint]));
path_cut_points_recurse(path, dists, closed, nextpoint[1], dists[dind],dind+1, concat(result, [nextpoint]));
// Search for a single cut point in the path
@ -826,65 +888,6 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) =
];
// Function: path_cut()
// Topics: Paths
// See Also: split_path_at_self_crossings()
// Usage:
// path_list = path_cut(path, cutdist, [closed]);
// Description:
// Given a list of distances in `cutdist`, cut the path into
// subpaths at those lengths, returning a list of paths.
// If the input path is closed then the final path will include the
// original starting point. The list of cut distances must be
// in ascending order and should not include the endpoints: 0
// or len(path). If you repeat a distance you will get an
// empty list in that position in the output. If you give an
// empty cutdist array you will get the input path as output
// (without the final vertex doubled in the case of a closed path).
// Arguments:
// path = path of any dimension or a 1-region
// cutdist = Distance or list of distances where path is cut
// closed = If true, treat the path as a closed polygon. Default: false
// Example(2D,NoAxes):
// path = circle(d=100);
// segs = path_cut(path, [50, 200], closed=true);
// rainbow(segs) stroke($item, endcaps="butt", width=3);
function path_cut(path,cutdist,closed) =
is_num(cutdist) ? path_cut(path,[cutdist],closed) :
is_1region(path) ? path_cut(path[0], cutdist, default(closed,true)):
let(closed=default(closed,false))
assert(is_bool(closed))
assert(is_vector(cutdist))
assert(last(cutdist)<path_length(path,closed=closed),"Cut distances must be smaller than the path length")
assert(cutdist[0]>0, "Cut distances must be strictly positive")
let(
cutlist = _path_cut_points(path,cutdist,closed=closed)
)
_path_cut_getpaths(path, cutlist, closed);
function _path_cut_getpaths(path, cutlist, closed) =
let(
cuts = len(cutlist)
)
[
[ each list_head(path,cutlist[0][1]-1),
if (!approx(cutlist[0][0], path[cutlist[0][1]-1])) cutlist[0][0]
],
for(i=[0:1:cuts-2])
cutlist[i][0]==cutlist[i+1][0] && cutlist[i][1]==cutlist[i+1][1] ? []
:
[ if (!approx(cutlist[i][0], select(path,cutlist[i][1]))) cutlist[i][0],
each slice(path, cutlist[i][1], cutlist[i+1][1]-1),
if (!approx(cutlist[i+1][0], select(path,cutlist[i+1][1]-1))) cutlist[i+1][0],
],
[
if (!approx(cutlist[cuts-1][0], select(path,cutlist[cuts-1][1]))) cutlist[cuts-1][0],
each select(path,cutlist[cuts-1][1],closed ? 0 : -1)
]
];
// internal function
// converts pathcut output form to a [segment, u]
// form list that works withi path_select

View file

@ -845,8 +845,8 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
assert(d_first<path_length(revresult),str("Path ",i," is too short for specified cut distance ",d_first))
assert(d_next<path_length(nextpath), str("Path ",i+1," is too short for specified cut distance ",d_next))
let(
firstcut = _path_cut_points(revresult, d_first, direction=true),
nextcut = _path_cut_points(nextpath, d_next, direction=true)
firstcut = path_cut_points(revresult, d_first, direction=true),
nextcut = path_cut_points(nextpath, d_next, direction=true)
)
assert(!loop || nextcut[1] < len(revresult)-1-firstcut[1], "Path is too short to close the loop")
let(
@ -1197,8 +1197,8 @@ function _stroke_end(width,left, right, spec) =
90-vector_angle([newright[1],newright[0],newleft[0]])/2,
jointleft = 8*cutleft/cos(leftangle)/(1+4*bez_k),
jointright = 8*cutright/cos(rightangle)/(1+4*bez_k),
pathcutleft = _path_cut_points(newleft,abs(jointleft)),
pathcutright = _path_cut_points(newright,abs(jointright)),
pathcutleft = path_cut_points(newleft,abs(jointleft)),
pathcutright = path_cut_points(newright,abs(jointright)),
leftdelete = intright? pathcutleft[1] : pathcutleft[1] + pathclip[1] -1,
rightdelete = intright? pathcutright[1] + pathclip[1] -1 : pathcutright[1],
leftcorner = line_intersection([pathcutleft[0], newleft[pathcutleft[1]]], [newright[0],newleft[0]]),

View file

@ -2966,7 +2966,7 @@ module path_text(path, text, font, size, thickness, lettersize, offset=0, revers
start = center ? (path_length(path) - textlength)/2 : 0;
pts = _path_cut_points(path, add_scalar([0, each cumsum(lsize)],start+lsize[0]/2), direction=true);
pts = path_cut_points(path, add_scalar([0, each cumsum(lsize)],start+lsize[0]/2), direction=true);
usernorm = is_def(normal);
usetop = is_def(top);