move polygon_line_intersection to the polygon section

This commit is contained in:
Adrian Mariano 2022-01-27 16:46:56 -05:00
parent 9b51d77462
commit 7588b3c09b

View file

@ -701,186 +701,6 @@ function plane_line_intersection(plane, line, bounded=false, eps=EPSILON) =
res[0];
// Function: polygon_line_intersection()
// Usage:
// pt = polygon_line_intersection(poly, line, [bounded], [nonzero], [eps]);
// Topics: Geometry, Polygons, Lines, Intersection
// Description:
// Takes a possibly bounded line, and a 2D or 3D planar polygon, and finds their intersection.
// If the line does not intersect the polygon then `undef` returns `undef`.
// In 3D if the line is not on the plane of the polygon but intersects it then you get a single intersection point.
// Otherwise the polygon and line are in the same plane, or when your input is 2D, ou will get a list of segments and
// single point lists. Use `is_vector` to distinguish these two cases.
// .
// In the 2D case, when single points are in the intersection they appear on the segment list as lists of a single point
// (like single point segments) so a single point intersection in 2D has the form `[[[x,y,z]]]` as compared
// to a single point intersection in 3D which has the form `[x,y,z]`. You can identify whether an entry in the
// segment list is a true segment by checking its length, which will be 2 for a segment and 1 for a point.
// Arguments:
// poly = The 3D planar polygon to find the intersection with.
// line = A list of two distinct 3D points on the line.
// bounded = If false, the line is considered unbounded. If true, it is treated as a bounded line segment. If given as `[true, false]` or `[false, true]`, the boundedness of the points are specified individually, allowing the line to be treated as a half-bounded ray. Default: false (unbounded)
// nonzero = set to true to use the nonzero rule for determining it points are in a polygon. See point_in_polygon. Default: false.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
// Example(3D): The line intersects the 3d hexagon in a single point.
// hex = zrot(140,p=rot([-45,40,20],p=path3d(hexagon(r=15))));
// line = [[5,0,-13],[-3,-5,13]];
// isect = polygon_line_intersection(hex,line);
// stroke(hex,closed=true);
// stroke(line);
// color("red")move(isect)sphere(r=1,$fn=12);
// Example(2D): In 2D things are more complicated. The output is a list of intersection parts, in the simplest case a single segment.
// hex = hexagon(r=15);
// line = [[-20,10],[25,-7]];
// isect = polygon_line_intersection(hex,line);
// stroke(hex,closed=true);
// stroke(line,endcaps="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) sphere(r=1);
// else
// stroke(part);
// Example(2D): Here the line is treated as a ray.
// hex = hexagon(r=15);
// line = [[0,0],[25,-7]];
// isect = polygon_line_intersection(hex,line,RAY);
// stroke(hex,closed=true);
// stroke(line,endcap2="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Here the intersection is a single point, which is returned as a single point "path" on the path list.
// hex = hexagon(r=15);
// line = [[15,-10],[15,13]];
// isect = polygon_line_intersection(hex,line,RAY);
// stroke(hex,closed=true);
// stroke(line,endcap2="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Another way to get a single segment
// hex = hexagon(r=15);
// line = rot(30,p=[[15,-10],[15,25]],cp=[15,0]);
// isect = polygon_line_intersection(hex,line,RAY);
// stroke(hex,closed=true);
// stroke(line,endcap2="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Single segment again
// star = star(r=15,n=8,step=2);
// line = [[20,-5],[-5,20]];
// isect = polygon_line_intersection(star,line,RAY);
// stroke(star,closed=true);
// stroke(line,endcap2="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Solution is two points
// star = star(r=15,n=8,step=3);
// line = rot(22.5,p=[[15,-10],[15,20]],cp=[15,0]);
// isect = polygon_line_intersection(star,line,SEGMENT);
// stroke(star,closed=true);
// stroke(line);
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Solution is list of three segments
// star = star(r=25,ir=9,n=8);
// line = [[-25,12],[25,12]];
// isect = polygon_line_intersection(star,line);
// stroke(star,closed=true);
// stroke(line,endcaps="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Solution is a mixture of segments and points
// star = star(r=25,ir=9,n=7);
// line = [left(10,p=star[8]), right(50,p=star[8])];
// isect = polygon_line_intersection(star,line);
// stroke(star,closed=true);
// stroke(line,endcaps="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps=EPSILON) =
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
assert(is_path(poly,dim=[2,3]), "Invalid polygon." )
assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition.")
assert(_valid_line(line,dim=len(poly[0]),eps=eps), "Line invalid or does not match polygon dimension." )
let(
bounded = force_list(bounded,2),
poly = deduplicate(poly)
)
len(poly[0])==2 ? // planar case
let(
linevec = unit(line[1] - line[0]),
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_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
for(part = parts)
if (point_in_polygon(mean(part), poly, nonzero=nonzero, eps=eps) >=0 )
part
else if(len(part)==2 && point_in_polygon(part[1], poly, nonzero=nonzero, eps=eps) == 0)
[part[1]] // Add segment end if it is on the polygon
]
)
(len(inside)==0 ? undef : _merge_segments(inside, [inside[0]], eps))
: // 3d case
let(indices = _noncollinear_triple(poly))
indices==[] ? undef : // Polygon is collinear
let(
plane = plane3pt(poly[indices[0]], poly[indices[1]], poly[indices[2]]),
plane_isect = plane_line_intersection(plane, line, bounded, eps)
)
is_undef(plane_isect) ? undef :
is_vector(plane_isect,3) ?
let(
poly2d = project_plane(plane,poly),
pt2d = project_plane(plane, plane_isect)
)
(point_in_polygon(pt2d, poly2d, nonzero=nonzero, eps=eps) < 0 ? undef : plane_isect)
: // Case where line is on the polygon plane
let(
poly2d = project_plane(plane, poly),
line2d = project_plane(plane, line),
segments = polygon_line_intersection(poly2d, line2d, bounded=bounded, nonzero=nonzero, eps=eps)
)
segments==undef ? undef
: [for(seg=segments) len(seg)==2 ? lift_plane(plane,seg) : [lift_plane(plane,seg[0])]];
function _merge_segments(insegs,outsegs, eps, i=1) =
i==len(insegs) ? outsegs :
approx(last(last(outsegs)), insegs[i][0], eps)
? _merge_segments(insegs, [each list_head(outsegs),[last(outsegs)[0],last(insegs[i])]], eps, i+1)
: _merge_segments(insegs, [each outsegs, insegs[i]], eps, i+1);
// Function: plane_intersection()
// Usage:
@ -1795,6 +1615,191 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
// Function: polygon_line_intersection()
// Usage:
// pt = polygon_line_intersection(poly, line, [bounded], [nonzero], [eps]);
// Topics: Geometry, Polygons, Lines, Intersection
// Description:
// Takes a possibly bounded line, and a 2D or 3D planar polygon, and finds their intersection. Note the polygon is
// treated as its boundary and interior, so the intersection may include both points and line segments.
// If the line does not intersect the polygon returns `undef`.
// In 3D if the line is not on the plane of the polygon but intersects it then you get a single intersection point.
// Otherwise the polygon and line are in the same plane, or when your input is 2D, you will get a list of segments and
// single point lists. Use `is_vector` to distinguish these two cases.
// .
// In the 2D case, a common result is a list containing a single segment, which lists the two intersection points
// with the boundary of the polygon.
// When single points are in the intersection (the line just touches a polygon corner) they appear on the segment
// list as lists of a single point
// (like single point segments) so a single point intersection in 2D has the form `[[[x,y,z]]]` as compared
// to a single point intersection in 3D which has the form `[x,y,z]`. You can identify whether an entry in the
// segment list is a true segment by checking its length, which will be 2 for a segment and 1 for a point.
// Arguments:
// poly = The 3D planar polygon to find the intersection with.
// line = A list of two distinct 3D points on the line.
// bounded = If false, the line is considered unbounded. If true, it is treated as a bounded line segment. If given as `[true, false]` or `[false, true]`, the boundedness of the points are specified individually, allowing the line to be treated as a half-bounded ray. Default: false (unbounded)
// nonzero = set to true to use the nonzero rule for determining it points are in a polygon. See point_in_polygon. Default: false.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
// Example(3D): The line intersects the 3d hexagon in a single point.
// hex = zrot(140,p=rot([-45,40,20],p=path3d(hexagon(r=15))));
// line = [[5,0,-13],[-3,-5,13]];
// isect = polygon_line_intersection(hex,line);
// stroke(hex,closed=true);
// stroke(line);
// color("red")move(isect)sphere(r=1,$fn=12);
// Example(2D): In 2D things are more complicated. The output is a list of intersection parts, in the simplest case a single segment.
// hex = hexagon(r=15);
// line = [[-20,10],[25,-7]];
// isect = polygon_line_intersection(hex,line);
// stroke(hex,closed=true);
// stroke(line,endcaps="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) sphere(r=1);
// else
// stroke(part);
// Example(2D): Here the line is treated as a ray.
// hex = hexagon(r=15);
// line = [[0,0],[25,-7]];
// isect = polygon_line_intersection(hex,line,RAY);
// stroke(hex,closed=true);
// stroke(line,endcap2="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Here the intersection is a single point, which is returned as a single point "path" on the path list.
// hex = hexagon(r=15);
// line = [[15,-10],[15,13]];
// isect = polygon_line_intersection(hex,line,RAY);
// stroke(hex,closed=true);
// stroke(line,endcap2="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Another way to get a single segment
// hex = hexagon(r=15);
// line = rot(30,p=[[15,-10],[15,25]],cp=[15,0]);
// isect = polygon_line_intersection(hex,line,RAY);
// stroke(hex,closed=true);
// stroke(line,endcap2="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Single segment again
// star = star(r=15,n=8,step=2);
// line = [[20,-5],[-5,20]];
// isect = polygon_line_intersection(star,line,RAY);
// stroke(star,closed=true);
// stroke(line,endcap2="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Solution is two points
// star = star(r=15,n=8,step=3);
// line = rot(22.5,p=[[15,-10],[15,20]],cp=[15,0]);
// isect = polygon_line_intersection(star,line,SEGMENT);
// stroke(star,closed=true);
// stroke(line);
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Solution is list of three segments
// star = star(r=25,ir=9,n=8);
// line = [[-25,12],[25,12]];
// isect = polygon_line_intersection(star,line);
// stroke(star,closed=true);
// stroke(line,endcaps="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
// Example(2D): Solution is a mixture of segments and points
// star = star(r=25,ir=9,n=7);
// line = [left(10,p=star[8]), right(50,p=star[8])];
// isect = polygon_line_intersection(star,line);
// stroke(star,closed=true);
// stroke(line,endcaps="arrow2");
// color("red")
// for(part=isect)
// if(len(part)==1)
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps=EPSILON) =
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
assert(is_path(poly,dim=[2,3]), "Invalid polygon." )
assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition.")
assert(_valid_line(line,dim=len(poly[0]),eps=eps), "Line invalid or does not match polygon dimension." )
let(
bounded = force_list(bounded,2),
poly = deduplicate(poly)
)
len(poly[0])==2 ? // planar case
let(
linevec = unit(line[1] - line[0]),
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_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
for(part = parts)
if (point_in_polygon(mean(part), poly, nonzero=nonzero, eps=eps) >=0 )
part
else if(len(part)==2 && point_in_polygon(part[1], poly, nonzero=nonzero, eps=eps) == 0)
[part[1]] // Add segment end if it is on the polygon
]
)
(len(inside)==0 ? undef : _merge_segments(inside, [inside[0]], eps))
: // 3d case
let(indices = _noncollinear_triple(poly))
indices==[] ? undef : // Polygon is collinear
let(
plane = plane3pt(poly[indices[0]], poly[indices[1]], poly[indices[2]]),
plane_isect = plane_line_intersection(plane, line, bounded, eps)
)
is_undef(plane_isect) ? undef :
is_vector(plane_isect,3) ?
let(
poly2d = project_plane(plane,poly),
pt2d = project_plane(plane, plane_isect)
)
(point_in_polygon(pt2d, poly2d, nonzero=nonzero, eps=eps) < 0 ? undef : plane_isect)
: // Case where line is on the polygon plane
let(
poly2d = project_plane(plane, poly),
line2d = project_plane(plane, line),
segments = polygon_line_intersection(poly2d, line2d, bounded=bounded, nonzero=nonzero, eps=eps)
)
segments==undef ? undef
: [for(seg=segments) len(seg)==2 ? lift_plane(plane,seg) : [lift_plane(plane,seg[0])]];
function _merge_segments(insegs,outsegs, eps, i=1) =
i==len(insegs) ? outsegs :
approx(last(last(outsegs)), insegs[i][0], eps)
? _merge_segments(insegs, [each list_head(outsegs),[last(outsegs)[0],last(insegs[i])]], eps, i+1)
: _merge_segments(insegs, [each outsegs, insegs[i]], eps, i+1);
// Function: polygon_triangulate()
// Usage: