Added polygon_shift_to_closest_point(), clockwise_polygon(), ccw_polygon()

This commit is contained in:
Revar Desmera 2019-08-20 20:47:29 -07:00
parent be6575b5fd
commit 700acae000
2 changed files with 73 additions and 34 deletions

View File

@ -417,16 +417,6 @@ function plane3pt(p1, p2, p3) =
) concat(normal, [normal*p1]); ) concat(normal, [normal*p1]);
function plane_from_pointslist(points) =
let(
points = deduplicate(points),
indices = find_noncollinear_points(points),
p1 = points[indices[0]],
p2 = points[indices[1]],
p3 = points[indices[2]]
) plane3pt(p1,p2,p3);
// Function: plane3pt_indexed() // Function: plane3pt_indexed()
// Usage: // Usage:
// plane3pt_indexed(points, i1, i2, i3); // plane3pt_indexed(points, i1, i2, i3);
@ -448,6 +438,22 @@ function plane3pt_indexed(points, i1, i2, i3) =
) plane3pt(p1,p2,p3); ) plane3pt(p1,p2,p3);
// Function: plane_from_pointslist()
// Usage:
// plane_from_pointslist(points);
// Description:
// Given a list of coplanar points, returns the cartesian equation of a plane.
// Returns [A,B,C,D] where Ax+By+Cz+D=0 is the equation of the plane.
function plane_from_pointslist(points) =
let(
points = deduplicate(points),
indices = find_noncollinear_points(points),
p1 = points[indices[0]],
p2 = points[indices[1]],
p3 = points[indices[2]]
) plane3pt(p1,p2,p3);
// Function: plane_normal() // Function: plane_normal()
// Usage: // Usage:
// plane_normal(plane); // plane_normal(plane);
@ -574,6 +580,21 @@ function polygon_area(vertices) =
0.5*sum([for(i=[0:len(vertices)-1]) det2(select(vertices,i,i+1))]); 0.5*sum([for(i=[0:len(vertices)-1]) det2(select(vertices,i,i+1))]);
// Function: polygon_shift_to_closest_point()
// Usage:
// polygon_shift_to_closest_point(path, pt);
// Description:
// Given a polygon `path`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
function polygon_shift_to_closest_point(path, pt) =
let(
path = cleanup_path(path),
closest = path_closest_point(path,pt),
seg = select(path,closest[0],closest[0]+1),
u = norm(closest[1]-seg[0]) / norm(seg[1]-seg[0]),
segnum = closest[0] + (u>0.5? 1 : 0)
) select(path,segnum,segnum+len(path)-1);
// Function: first_noncollinear() // Function: first_noncollinear()
// Usage: // Usage:
// first_noncollinear(i1, i2, points); // first_noncollinear(i1, i2, points);
@ -740,24 +761,6 @@ function point_in_polygon(point, path, eps=EPSILON) =
sum([for(i=[0:1:len(path)-1]) let(seg=select(path,i,i+1)) if(!approx(seg[0],seg[1],eps=eps)) _point_above_below_segment(point, seg)]) != 0? 1 : -1; sum([for(i=[0:1:len(path)-1]) let(seg=select(path,i,i+1)) if(!approx(seg[0],seg[1],eps=eps)) _point_above_below_segment(point, seg)]) != 0? 1 : -1;
// Function: point_in_region()
// Usage:
// point_in_region(point, region);
// Description:
// Tests if a point is inside, outside, or on the border of a region.
// Returns -1 if the point is outside the region.
// Returns 0 if the point is on the boundary.
// Returns 1 if the point lies inside the region.
// Arguments:
// point = The point to test.
// region = The region to test against. Given as a list of polygon paths.
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
function point_in_region(point, region, eps=EPSILON, _i=0, _cnt=0) =
(_i >= len(region))? ((_cnt%2==1)? 1 : -1) : let(
pip = point_in_polygon(point, region[_i], eps=eps)
) pip==0? 0 : point_in_region(point, region, eps=eps, _i=_i+1, _cnt = _cnt + (pip>0? 1 : 0));
// Function: pointlist_bounds() // Function: pointlist_bounds()
// Usage: // Usage:
// pointlist_bounds(pts); // pointlist_bounds(pts);
@ -800,15 +803,15 @@ function furthest_point(pt, points) =
) i; ) i;
// Function: polygon_clockwise() // Function: polygon_is_clockwise()
// Usage: // Usage:
// polygon_clockwise(path); // polygon_is_clockwise(path);
// Description: // Description:
// Return true if the given 2D simple polygon is in clockwise order, false otherwise. // Return true if the given 2D simple polygon is in clockwise order, false otherwise.
// Results for complex (self-intersecting) polygon are indeterminate. // Results for complex (self-intersecting) polygon are indeterminate.
// Arguments: // Arguments:
// path = The list of 2D path points for the perimeter of the polygon. // path = The list of 2D path points for the perimeter of the polygon.
function polygon_clockwise(path) = function polygon_is_clockwise(path) =
let( let(
minx = min(subindex(path,0)), minx = min(subindex(path,0)),
lowind = search(minx, path, 0, 0), lowind = search(minx, path, 0, 0),
@ -819,6 +822,24 @@ function polygon_clockwise(path) =
) det2([select(path,extreme+1)-path[extreme], select(path, extreme-1)-path[extreme]])<0; ) det2([select(path,extreme+1)-path[extreme], select(path, extreme-1)-path[extreme]])<0;
// Function: clockwise_polygon()
// Usage:
// clockwise_polygon(path);
// Description:
// Given a polygon path, returns the clockwise winding version of that path.
function clockwise_polygon(path) =
polygon_is_clockwise(path)? path : reverse(path);
// Function: ccw_polygon()
// Usage:
// ccw_polygon(path);
// Description:
// Given a polygon path, returns the counter-clockwise winding version of that path.
function ccw_polygon(path) =
polygon_is_clockwise(path)? reverse(path) : path;
// Section: Regions and Boolean 2D Geometry // Section: Regions and Boolean 2D Geometry
@ -872,6 +893,24 @@ function check_and_fix_path(path,valid_dim=undef,closed=false) =
function cleanup_region(region, eps=EPSILON) = [for (path=region) cleanup_path(path, eps=eps)]; function cleanup_region(region, eps=EPSILON) = [for (path=region) cleanup_path(path, eps=eps)];
// Function: point_in_region()
// Usage:
// point_in_region(point, region);
// Description:
// Tests if a point is inside, outside, or on the border of a region.
// Returns -1 if the point is outside the region.
// Returns 0 if the point is on the boundary.
// Returns 1 if the point lies inside the region.
// Arguments:
// point = The point to test.
// region = The region to test against. Given as a list of polygon paths.
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
function point_in_region(point, region, eps=EPSILON, _i=0, _cnt=0) =
(_i >= len(region))? ((_cnt%2==1)? 1 : -1) : let(
pip = point_in_polygon(point, region[_i], eps=eps)
) pip==0? 0 : point_in_region(point, region, eps=eps, _i=_i+1, _cnt = _cnt + (pip>0? 1 : 0));
// Function: region_path_crossings() // Function: region_path_crossings()
// Usage: // Usage:
// region_path_crossings(path, region); // region_path_crossings(path, region);
@ -1142,7 +1181,7 @@ function offset(
is_region(path)? ( is_region(path)? (
assert(!return_faces, "return_faces not supported for regions.") assert(!return_faces, "return_faces not supported for regions.")
let( let(
path = [for (p=path) polygon_clockwise(p)? p : reverse(p)], path = [for (p=path) polygon_is_clockwise(p)? p : reverse(p)],
rgn = exclusive_or([for (p = path) [p]]), rgn = exclusive_or([for (p = path) [p]]),
pathlist = sort(idx=0,[ pathlist = sort(idx=0,[
for (i=[0:1:len(rgn)-1]) [ for (i=[0:1:len(rgn)-1]) [
@ -1164,7 +1203,7 @@ function offset(
let( let(
chamfer = is_def(r) ? false : chamfer, chamfer = is_def(r) ? false : chamfer,
quality = max(0,round(quality)), quality = max(0,round(quality)),
flip_dir = closed && !polygon_clockwise(path) ? -1 : 1, flip_dir = closed && !polygon_is_clockwise(path) ? -1 : 1,
d = flip_dir * (is_def(r) ? r : delta), d = flip_dir * (is_def(r) ? r : delta),
shiftsegs = [for(i=[0:len(path)-1]) _shift_segment(select(path,i,i+1), d)], shiftsegs = [for(i=[0:len(path)-1]) _shift_segment(select(path,i,i+1), d)],
// good segments are ones where no point on the segment is less than distance d from any point on the path // good segments are ones where no point on the segment is less than distance d from any point on the path

View File

@ -636,7 +636,7 @@ module rounded_sweep(
top = struct_set(argspec, top, grow=false); top = struct_set(argspec, top, grow=false);
bottom = struct_set(argspec, bottom, grow=false); bottom = struct_set(argspec, bottom, grow=false);
clockwise = polygon_clockwise(path); clockwise = polygon_is_clockwise(path);
assert(height>=0, "Height must be nonnegative"); assert(height>=0, "Height must be nonnegative");