reordering and moving for improved docs

removed xxrot and xxflip
This commit is contained in:
Adrian Mariano 2021-09-11 18:48:23 -04:00
parent 60dbf8c73e
commit 2494de9368
9 changed files with 556 additions and 911 deletions

View File

@ -44,10 +44,10 @@ function _valid_line(line,dim,eps=EPSILON) =
function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps);
// Function: point_left_of_line2d()
/// Internal Function: point_left_of_line2d()
// Usage:
// pt = point_left_of_line2d(point, line);
// Topics: Geometry, Points, Lines
/// Topics: Geometry, Points, Lines
// Description:
// Return >0 if point is left of the line defined by `line`.
// Return =0 if point is on the line.
@ -55,7 +55,7 @@ function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps
// Arguments:
// point = The point to check position of.
// line = Array of two points forming the line segment to test against.
function point_left_of_line2d(point, line) =
function _point_left_of_line2d(point, line) =
assert( is_vector(point,2) && is_vector(line*point, 2), "Improper input." )
cross(line[0]-point, line[1]-line[0]);
@ -327,7 +327,7 @@ function line_closest_point(line, pt, bounded=false) =
// fast = If true, don't verify that all points are collinear. Default: false
// eps = How much variance is allowed in testing each point against the line. Default: `EPSILON` (1e-9)
function line_from_points(points, fast=false, eps=EPSILON) =
assert( is_path(points,dim=undef), "Improper point list." )
assert( is_path(points), "Invalid point list." )
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
let( pb = furthest_point(points[0],points) )
norm(points[pb]-points[0])<eps*max(norm(points[pb]),norm(points[0])) ? undef :
@ -340,6 +340,26 @@ function line_from_points(points, fast=false, eps=EPSILON) =
// Section: Planes
// Function: coplanar()
// Usage:
// test = coplanar(points,[eps]);
// Topics: Geometry, Coplanarity
// Description:
// Returns true if the given 3D points are non-collinear and are on a plane.
// Arguments:
// points = The points to test.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function coplanar(points, eps=EPSILON) =
assert( is_path(points,dim=3) , "Input should be a list of 3D points." )
assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative value." )
len(points)<=2 ? false
: let( ip = noncollinear_triple(points,error=false,eps=eps) )
ip == [] ? false :
let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]) )
_pointlist_greatest_distance(points,plane) < eps;
// Function: plane3pt()
// Usage:
// plane = plane3pt(p1, p2, p3);
@ -546,61 +566,6 @@ function plane_offset(plane) =
// Function: plane_closest_point()
// Usage:
// pts = plane_closest_point(plane, points);
// Topics: Geometry, Planes, Projection
// Description:
// Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or
// 3d points, return the closest 3D orthogonal projection of the points on the plane.
// In other words, for every point given, returns the closest point to it on the plane.
// Arguments:
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
// points = List of points to project
// Example(FlatSpin,VPD=500,VPT=[2,20,10]):
// points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100, $fn=36))));
// plane = plane_from_normal([1,0,1]);
// proj = plane_closest_point(plane,points);
// color("red") move_copies(points) sphere(d=2,$fn=12);
// color("blue") move_copies(proj) sphere(d=2,$fn=12);
// move(centroid(proj)) {
// rot(from=UP,to=plane_normal(plane)) {
// anchor_arrow(30);
// %cube([120,150,0.1],center=true);
// }
// }
function plane_closest_point(plane, points) =
is_vector(points,3) ? plane_closest_point(plane,[points])[0] :
assert( _valid_plane(plane), "Invalid plane." )
assert( is_matrix(points,undef,3), "Must supply 3D points.")
let(
plane = normalize_plane(plane),
n = point3d(plane)
)
[for(pi=points) pi - (pi*n - plane[3])*n];
// Function: point_plane_distance()
// Usage:
// dist = point_plane_distance(plane, point)
// Topics: Geometry, Planes, Distance
// Description:
// Given a plane as [A,B,C,D] where the cartesian equation for that plane
// is Ax+By+Cz=D, determines how far from that plane the given point is.
// The returned distance will be positive if the point is above the
// plane, meaning on the side where the plane normal points.
// If the point is below the plane, then the distance returned
// will be negative. The normal of the plane is [A,B,C].
// Arguments:
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
// point = The distance evaluation point.
function point_plane_distance(plane, point) =
assert( _valid_plane(plane), "Invalid input plane." )
assert( is_vector(point,3), "The point should be a 3D point." )
let( plane = normalize_plane(plane) )
point3d(plane)* point - plane[3];
// Returns [POINT, U] if line intersects plane at one point, where U is zero at line[0] and 1 at line[1]
// Returns [LINE, undef] if the line is on the plane.
// Returns undef if line is parallel to, but not on the given plane.
@ -616,36 +581,17 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
: [ line[0]-a/b*(line[1]-line[0]), -a/b ];
// Function: normalize_plane()
/// Internal Function: normalize_plane()
// Usage:
// nplane = normalize_plane(plane);
// Topics: Geometry, Planes
/// Topics: Geometry, Planes
// Description:
// Returns a new representation [A,B,C,D] of `plane` where norm([A,B,C]) is equal to one.
function normalize_plane(plane) =
function _normalize_plane(plane) =
assert( _valid_plane(plane), str("Invalid plane. ",plane ) )
plane/norm(point3d(plane));
// Function: plane_line_angle()
// Usage:
// angle = plane_line_angle(plane,line);
// Topics: Geometry, Planes, Lines, Angle
// Description:
// Compute the angle between a plane [A, B, C, D] and a 3d line, specified as a pair of 3d points [p1,p2].
// The resulting angle is signed, with the sign positive if the vector p2-p1 lies above the plane, on
// the same side of the plane as the plane's normal vector.
function plane_line_angle(plane, line) =
assert( _valid_plane(plane), "Invalid plane." )
assert( _valid_line(line,dim=3), "Invalid 3d line." )
let(
linedir = unit(line[1]-line[0]),
normal = plane_normal(plane),
sin_angle = linedir*normal,
cos_angle = norm(cross(linedir,normal))
) atan2(sin_angle,cos_angle);
// Function: plane_line_intersection()
// Usage:
// pt = plane_line_intersection(plane, line, [bounded], [eps]);
@ -765,23 +711,81 @@ function plane_intersection(plane1,plane2,plane3) =
[point, point+normal];
// Function: coplanar()
// Function: plane_line_angle()
// Usage:
// test = coplanar(points,[eps]);
// Topics: Geometry, Coplanarity
// angle = plane_line_angle(plane,line);
// Topics: Geometry, Planes, Lines, Angle
// Description:
// Returns true if the given 3D points are non-collinear and are on a plane.
// Compute the angle between a plane [A, B, C, D] and a 3d line, specified as a pair of 3d points [p1,p2].
// The resulting angle is signed, with the sign positive if the vector p2-p1 lies above the plane, on
// the same side of the plane as the plane's normal vector.
function plane_line_angle(plane, line) =
assert( _valid_plane(plane), "Invalid plane." )
assert( _valid_line(line,dim=3), "Invalid 3d line." )
let(
linedir = unit(line[1]-line[0]),
normal = plane_normal(plane),
sin_angle = linedir*normal,
cos_angle = norm(cross(linedir,normal))
) atan2(sin_angle,cos_angle);
// Function: plane_closest_point()
// Usage:
// pts = plane_closest_point(plane, points);
// Topics: Geometry, Planes, Projection
// Description:
// Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or
// 3d points, return the closest 3D orthogonal projection of the points on the plane.
// In other words, for every point given, returns the closest point to it on the plane.
// Arguments:
// points = The points to test.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function coplanar(points, eps=EPSILON) =
assert( is_path(points,dim=3) , "Input should be a list of 3D points." )
assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative value." )
len(points)<=2 ? false
: let( ip = noncollinear_triple(points,error=false,eps=eps) )
ip == [] ? false :
let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]) )
_pointlist_greatest_distance(points,plane) < eps;
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
// points = List of points to project
// Example(FlatSpin,VPD=500,VPT=[2,20,10]):
// points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100, $fn=36))));
// plane = plane_from_normal([1,0,1]);
// proj = plane_closest_point(plane,points);
// color("red") move_copies(points) sphere(d=2,$fn=12);
// color("blue") move_copies(proj) sphere(d=2,$fn=12);
// move(centroid(proj)) {
// rot(from=UP,to=plane_normal(plane)) {
// anchor_arrow(30);
// %cube([120,150,0.1],center=true);
// }
// }
function plane_closest_point(plane, points) =
is_vector(points,3) ? plane_closest_point(plane,[points])[0] :
assert( _valid_plane(plane), "Invalid plane." )
assert( is_matrix(points,undef,3), "Must supply 3D points.")
let(
plane = _normalize_plane(plane),
n = point3d(plane)
)
[for(pi=points) pi - (pi*n - plane[3])*n];
// Function: point_plane_distance()
// Usage:
// dist = point_plane_distance(plane, point)
// Topics: Geometry, Planes, Distance
// Description:
// Given a plane as [A,B,C,D] where the cartesian equation for that plane
// is Ax+By+Cz=D, determines how far from that plane the given point is.
// The returned distance will be positive if the point is above the
// plane, meaning on the side where the plane normal points.
// If the point is below the plane, then the distance returned
// will be negative. The normal of the plane is [A,B,C].
// Arguments:
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
// point = The distance evaluation point.
function point_plane_distance(plane, point) =
assert( _valid_plane(plane), "Invalid input plane." )
assert( is_vector(point,3), "The point should be a 3D point." )
let( plane = _normalize_plane(plane) )
point3d(plane)* point - plane[3];
// the maximum distance from points to the plane
@ -1217,58 +1221,6 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
[0, b, max_index(distlist)];
// Function: pointlist_bounds()
// Usage:
// pt_pair = pointlist_bounds(pts);
// Topics: Geometry, Bounding Boxes, Bounds
// Description:
// Finds the bounds containing all the points in `pts` which can be a list of points in any dimension.
// Returns a list of two items: a list of the minimums and a list of the maximums. For example, with
// 3d points `[[MINX, MINY, MINZ], [MAXX, MAXY, MAXZ]]`
// Arguments:
// pts = List of points.
function pointlist_bounds(pts) =
assert(is_path(pts,dim=undef,fast=true) , "Invalid pointlist." )
let(
select = ident(len(pts[0])),
spread = [
for(i=[0:len(pts[0])-1])
let( spreadi = pts*select[i] )
[ min(spreadi), max(spreadi) ]
]
) transpose(spread);
// Function: closest_point()
// Usage:
// index = closest_point(pt, points);
// Topics: Geometry, Points, Distance
// Description:
// Given a list of `points`, finds the index of the closest point to `pt`.
// Arguments:
// pt = The point to find the closest point to.
// points = The list of points to search.
function closest_point(pt, points) =
assert( is_vector(pt), "Invalid point." )
assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
min_index([for (p=points) norm(p-pt)]);
// Function: furthest_point()
// Usage:
// index = furthest_point(pt, points);
// Topics: Geometry, Points, Distance
// Description:
// Given a list of `points`, finds the index of the furthest point from `pt`.
// Arguments:
// pt = The point to find the farthest point from.
// points = The list of points to search.
function furthest_point(pt, points) =
assert( is_vector(pt), "Invalid point." )
assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
max_index([for (p=points) norm(p-pt)]);
// Section: Polygons
@ -1302,6 +1254,183 @@ function polygon_area(poly, signed=false) =
signed ? total : abs(total);
// Function: centroid()
// Usage:
// cpt = centroid(poly);
// Topics: Geometry, Polygons, Centroid
// Description:
// Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid.
// Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid.
// Collinear points produce an error. The results are meaningless for self-intersecting
// polygons or an error is produced.
// Arguments:
// poly = Points of the polygon from which the centroid is calculated.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function centroid(poly, eps=EPSILON) =
assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
let(
n = len(poly[0])==2 ? 1 :
let( plane = plane_from_points(poly, fast=true) )
assert( !is_undef(plane), "The polygon must be planar." )
plane_normal(plane),
v0 = poly[0] ,
val = sum([
for(i=[1:len(poly)-2])
let(
v1 = poly[i],
v2 = poly[i+1],
area = cross(v2-v0,v1-v0)*n
) [ area, (v0+v1+v2)*area ]
])
)
assert(!approx(val[0],0, eps), "The polygon is self-intersecting or its points are collinear.")
val[1]/val[0]/3;
// Function: polygon_normal()
// Usage:
// vec = polygon_normal(poly);
// Topics: Geometry, Polygons
// Description:
// Given a 3D simple planar polygon, returns a unit normal vector for the polygon. The vector
// is oriented so that if the normal points towards the viewer, the polygon winds in the clockwise
// direction. If the polygon has zero area, returns `undef`. If the polygon is self-intersecting
// the the result is undefined. It doesn't check for coplanarity.
// Arguments:
// poly = The list of 3D path points for the perimeter of the polygon.
function polygon_normal(poly) =
assert(is_path(poly,dim=3), "Invalid 3D polygon." )
let(
L=len(poly),
area_vec = sum([for(i=idx(poly))
cross(poly[(i+1)%L]-poly[0],
poly[(i+2)%L]-poly[(i+1)%L])])
)
area_vec==0 ? undef : unit(-area_vec);
// Function: point_in_polygon()
// Usage:
// test = point_in_polygon(point, poly, [eps])
// Topics: Geometry, Polygons
// Description:
// This function tests whether the given 2D point is inside, outside or on the boundary of
// the specified 2D polygon using either the Nonzero Winding rule or the Even-Odd rule.
// See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Evenodd_rule.
// The polygon is given as a list of 2D points, not including the repeated end point.
// Returns -1 if the point is outside the polygon.
// Returns 0 if the point is on the boundary.
// Returns 1 if the point lies in the interior.
// The polygon does not need to be simple: it may have self-intersections.
// But the polygon cannot have holes (it must be simply connected).
// Rounding errors may give mixed results for points on or near the boundary.
// Arguments:
// point = The 2D point to check position of.
// poly = The list of 2D path points forming the perimeter of the polygon.
// nonzero = The rule to use: true for "Nonzero" rule and false for "Even-Odd" (Default: true )
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function point_in_polygon(point, poly, nonzero=true, eps=EPSILON) =
// Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,
"The point and polygon should be in 2D. The polygon should have more that 2 points." )
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
// Does the point lie on any edges? If so return 0.
let(
on_brd = [
for (i = [0:1:len(poly)-1])
let( seg = select(poly,i,i+1) )
if (!approx(seg[0],seg[1],eps) )
point_on_segment(point, seg, eps=eps)? 1:0
]
)
sum(on_brd) > 0? 0 :
nonzero
? // Compute winding number and return 1 for interior, -1 for exterior
let(
windchk = [
for(i=[0:1:len(poly)-1])
let( seg=select(poly,i,i+1) )
if (!approx(seg[0],seg[1],eps=eps))
_point_above_below_segment(point, seg)
]
) sum(windchk) != 0 ? 1 : -1
: // or compute the crossings with the ray [point, point+[1,0]]
let(
n = len(poly),
cross = [
for(i=[0:n-1])
let(
p0 = poly[i]-point,
p1 = poly[(i+1)%n]-point
)
if (
( (p1.y>eps && p0.y<=eps) || (p1.y<=eps && p0.y>eps) )
&& -eps < p0.x - p0.y *(p1.x - p0.x)/(p1.y - p0.y)
) 1
]
) 2*(len(cross)%2)-1;
// Function: is_polygon_clockwise()
// Usage:
// test = is_polygon_clockwise(poly);
// Topics: Geometry, Polygons, Clockwise
// See Also: clockwise_polygon(), ccw_polygon(), reverse_polygon()
// Description:
// Return true if the given 2D simple polygon is in clockwise order, false otherwise.
// Results for complex (self-intersecting) polygon are indeterminate.
// Arguments:
// poly = The list of 2D path points for the perimeter of the polygon.
function is_polygon_clockwise(poly) =
assert(is_path(poly,dim=2), "Input should be a 2d path")
polygon_area(poly, signed=true)<-EPSILON;
// Function: clockwise_polygon()
// Usage:
// newpoly = clockwise_polygon(poly);
// Topics: Geometry, Polygons, Clockwise
// See Also: is_polygon_clockwise(), ccw_polygon(), reverse_polygon()
// Description:
// Given a 2D polygon path, returns the clockwise winding version of that path.
// Arguments:
// poly = The list of 2D path points for the perimeter of the polygon.
function clockwise_polygon(poly) =
assert(is_path(poly,dim=2), "Input should be a 2d polygon")
polygon_area(poly, signed=true)<0 ? poly : reverse_polygon(poly);
// Function: ccw_polygon()
// Usage:
// newpoly = ccw_polygon(poly);
// See Also: is_polygon_clockwise(), clockwise_polygon(), reverse_polygon()
// Topics: Geometry, Polygons, Clockwise
// Description:
// Given a 2D polygon poly, returns the counter-clockwise winding version of that poly.
// Arguments:
// poly = The list of 2D path points for the perimeter of the polygon.
function ccw_polygon(poly) =
assert(is_path(poly,dim=2), "Input should be a 2d polygon")
polygon_area(poly, signed=true)<0 ? reverse_polygon(poly) : poly;
// Function: reverse_polygon()
// Usage:
// newpoly = reverse_polygon(poly)
// Topics: Geometry, Polygons, Clockwise
// See Also: is_polygon_clockwise(), ccw_polygon(), clockwise_polygon()
// Description:
// Reverses a polygon's winding direction, while still using the same start point.
// Arguments:
// poly = The list of the path points for the perimeter of the polygon.
function reverse_polygon(poly) =
assert(is_path(poly), "Input should be a polygon")
[ poly[0], for(i=[len(poly)-1:-1:1]) poly[i] ];
// Function: polygon_shift()
// Usage:
// newpoly = polygon_shift(poly, i);
@ -1375,7 +1504,7 @@ function reindex_polygon(reference, poly, return_error=false) =
dim = len(reference[0]),
N = len(reference),
fixpoly = dim != 2? poly :
polygon_is_clockwise(reference)
is_polygon_clockwise(reference)
? clockwise_polygon(poly)
: ccw_polygon(poly),
I = [for(i=reference) 1],
@ -1427,334 +1556,6 @@ function align_polygon(reference, poly, angles, cp) =
) alignments[best][0];
// Function: centroid()
// Usage:
// cpt = centroid(poly);
// Topics: Geometry, Polygons, Centroid
// Description:
// Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid.
// Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid.
// Collinear points produce an error. The results are meaningless for self-intersecting
// polygons or an error is produced.
// Arguments:
// poly = Points of the polygon from which the centroid is calculated.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function centroid(poly, eps=EPSILON) =
assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
let(
n = len(poly[0])==2 ? 1 :
let( plane = plane_from_points(poly, fast=true) )
assert( !is_undef(plane), "The polygon must be planar." )
plane_normal(plane),
v0 = poly[0] ,
val = sum([
for(i=[1:len(poly)-2])
let(
v1 = poly[i],
v2 = poly[i+1],
area = cross(v2-v0,v1-v0)*n
) [ area, (v0+v1+v2)*area ]
])
)
assert(!approx(val[0],0, eps), "The polygon is self-intersecting or its points are collinear.")
val[1]/val[0]/3;
// Function: point_in_polygon()
// Usage:
// test = point_in_polygon(point, poly, [eps])
// Topics: Geometry, Polygons
// Description:
// This function tests whether the given 2D point is inside, outside or on the boundary of
// the specified 2D polygon using either the Nonzero Winding rule or the Even-Odd rule.
// See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Evenodd_rule.
// The polygon is given as a list of 2D points, not including the repeated end point.
// Returns -1 if the point is outside the polygon.
// Returns 0 if the point is on the boundary.
// Returns 1 if the point lies in the interior.
// The polygon does not need to be simple: it may have self-intersections.
// But the polygon cannot have holes (it must be simply connected).
// Rounding errors may give mixed results for points on or near the boundary.
// Arguments:
// point = The 2D point to check position of.
// poly = The list of 2D path points forming the perimeter of the polygon.
// nonzero = The rule to use: true for "Nonzero" rule and false for "Even-Odd" (Default: true )
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function point_in_polygon(point, poly, nonzero=true, eps=EPSILON) =
// Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,
"The point and polygon should be in 2D. The polygon should have more that 2 points." )
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
// Does the point lie on any edges? If so return 0.
let(
on_brd = [
for (i = [0:1:len(poly)-1])
let( seg = select(poly,i,i+1) )
if (!approx(seg[0],seg[1],eps) )
point_on_segment(point, seg, eps=eps)? 1:0
]
)
sum(on_brd) > 0? 0 :
nonzero
? // Compute winding number and return 1 for interior, -1 for exterior
let(
windchk = [
for(i=[0:1:len(poly)-1])
let( seg=select(poly,i,i+1) )
if (!approx(seg[0],seg[1],eps=eps))
_point_above_below_segment(point, seg)
]
) sum(windchk) != 0 ? 1 : -1
: // or compute the crossings with the ray [point, point+[1,0]]
let(
n = len(poly),
cross = [
for(i=[0:n-1])
let(
p0 = poly[i]-point,
p1 = poly[(i+1)%n]-point
)
if (
( (p1.y>eps && p0.y<=eps) || (p1.y<=eps && p0.y>eps) )
&& -eps < p0.x - p0.y *(p1.x - p0.x)/(p1.y - p0.y)
) 1
]
) 2*(len(cross)%2)-1;
// Function: polygon_is_clockwise()
// Usage:
// test = polygon_is_clockwise(poly);
// Topics: Geometry, Polygons, Clockwise
// See Also: clockwise_polygon(), ccw_polygon(), reverse_polygon()
// Description:
// Return true if the given 2D simple polygon is in clockwise order, false otherwise.
// Results for complex (self-intersecting) polygon are indeterminate.
// Arguments:
// poly = The list of 2D path points for the perimeter of the polygon.
function polygon_is_clockwise(poly) =
assert(is_path(poly,dim=2), "Input should be a 2d path")
polygon_area(poly, signed=true)<-EPSILON;
// Function: clockwise_polygon()
// Usage:
// newpoly = clockwise_polygon(poly);
// Topics: Geometry, Polygons, Clockwise
// See Also: polygon_is_clockwise(), ccw_polygon(), reverse_polygon()
// Description:
// Given a 2D polygon path, returns the clockwise winding version of that path.
// Arguments:
// poly = The list of 2D path points for the perimeter of the polygon.
function clockwise_polygon(poly) =
assert(is_path(poly,dim=2), "Input should be a 2d polygon")
polygon_area(poly, signed=true)<0 ? poly : reverse_polygon(poly);
// Function: ccw_polygon()
// Usage:
// newpoly = ccw_polygon(poly);
// See Also: polygon_is_clockwise(), clockwise_polygon(), reverse_polygon()
// Topics: Geometry, Polygons, Clockwise
// Description:
// Given a 2D polygon poly, returns the counter-clockwise winding version of that poly.
// Arguments:
// poly = The list of 2D path points for the perimeter of the polygon.
function ccw_polygon(poly) =
assert(is_path(poly,dim=2), "Input should be a 2d polygon")
polygon_area(poly, signed=true)<0 ? reverse_polygon(poly) : poly;
// Function: reverse_polygon()
// Usage:
// newpoly = reverse_polygon(poly)
// Topics: Geometry, Polygons, Clockwise
// See Also: polygon_is_clockwise(), ccw_polygon(), clockwise_polygon()
// Description:
// Reverses a polygon's winding direction, while still using the same start point.
// Arguments:
// poly = The list of the path points for the perimeter of the polygon.
function reverse_polygon(poly) =
assert(is_path(poly), "Input should be a polygon")
[ poly[0], for(i=[len(poly)-1:-1:1]) poly[i] ];
// Function: polygon_normal()
// Usage:
// vec = polygon_normal(poly);
// Topics: Geometry, Polygons
// Description:
// Given a 3D simple planar polygon, returns a unit normal vector for the polygon. The vector
// is oriented so that if the normal points towards the viewer, the polygon winds in the clockwise
// direction. If the polygon has zero area, returns `undef`. If the polygon is self-intersecting
// the the result is undefined. It doesn't check for coplanarity.
// Arguments:
// poly = The list of 3D path points for the perimeter of the polygon.
function polygon_normal(poly) =
assert(is_path(poly,dim=3), "Invalid 3D polygon." )
let(
L=len(poly),
area_vec = sum([for(i=idx(poly))
cross(poly[(i+1)%L]-poly[0],
poly[(i+2)%L]-poly[(i+1)%L])])
)
area_vec==0 ? undef : unit(-area_vec);
function _split_polygon_at_x(poly, x) =
let(
xs = subindex(poly,0)
) (min(xs) >= x || max(xs) <= x)? [poly] :
let(
poly2 = [
for (p = pair(poly,true)) each [
p[0],
if(
(p[0].x < x && p[1].x > x) ||
(p[1].x < x && p[0].x > x)
) let(
u = (x - p[0].x) / (p[1].x - p[0].x)
) [
x, // Important for later exact match tests
u*(p[1].y-p[0].y)+p[0].y,
u*(p[1].z-p[0].z)+p[0].z,
]
]
],
out1 = [for (p = poly2) if(p.x <= x) p],
out2 = [for (p = poly2) if(p.x >= x) p],
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(out1),
if (len(out2)>=3) each split_path_at_self_crossings(out2),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;
function _split_polygon_at_y(poly, y) =
let(
ys = subindex(poly,1)
) (min(ys) >= y || max(ys) <= y)? [poly] :
let(
poly2 = [
for (p = pair(poly,true)) each [
p[0],
if(
(p[0].y < y && p[1].y > y) ||
(p[1].y < y && p[0].y > y)
) let(
u = (y - p[0].y) / (p[1].y - p[0].y)
) [
u*(p[1].x-p[0].x)+p[0].x,
y, // Important for later exact match tests
u*(p[1].z-p[0].z)+p[0].z,
]
]
],
out1 = [for (p = poly2) if(p.y <= y) p],
out2 = [for (p = poly2) if(p.y >= y) p],
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(out1),
if (len(out2)>=3) each split_path_at_self_crossings(out2),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;
function _split_polygon_at_z(poly, z) =
let(
zs = subindex(poly,2)
) (min(zs) >= z || max(zs) <= z)? [poly] :
let(
poly2 = [
for (p = pair(poly,true)) each [
p[0],
if(
(p[0].z < z && p[1].z > z) ||
(p[1].z < z && p[0].z > z)
) let(
u = (z - p[0].z) / (p[1].z - p[0].z)
) [
u*(p[1].x-p[0].x)+p[0].x,
u*(p[1].y-p[0].y)+p[0].y,
z, // Important for later exact match tests
]
]
],
out1 = [for (p = poly2) if(p.z <= z) p],
out2 = [for (p = poly2) if(p.z >= z) p],
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(close_path(out1), closed=false),
if (len(out2)>=3) each split_path_at_self_crossings(close_path(out2), closed=false),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;
// Function: split_polygons_at_each_x()
// Usage:
// splitpolys = split_polygons_at_each_x(polys, xs);
// Topics: Geometry, Polygons, Intersections
// Description:
// Given a list of 3D polygons, splits all of them wherever they cross any X value given in `xs`.
// Arguments:
// polys = A list of 3D polygons to split.
// xs = A list of scalar X values to split at.
function split_polygons_at_each_x(polys, xs, _i=0) =
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
assert( is_vector(xs), "The split value list should contain only numbers." )
_i>=len(xs)? polys :
split_polygons_at_each_x(
[
for (poly = polys)
each _split_polygon_at_x(poly, xs[_i])
], xs, _i=_i+1
);
// Function: split_polygons_at_each_y()
// Usage:
// splitpolys = split_polygons_at_each_y(polys, ys);
// Topics: Geometry, Polygons, Intersections
// Description:
// Given a list of 3D polygons, splits all of them wherever they cross any Y value given in `ys`.
// Arguments:
// polys = A list of 3D polygons to split.
// ys = A list of scalar Y values to split at.
function split_polygons_at_each_y(polys, ys, _i=0) =
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
assert( is_vector(ys), "The split value list should contain only numbers." )
_i>=len(ys)? polys :
split_polygons_at_each_y(
[
for (poly = polys)
each _split_polygon_at_y(poly, ys[_i])
], ys, _i=_i+1
);
// Function: split_polygons_at_each_z()
// Usage:
// splitpolys = split_polygons_at_each_z(polys, zs);
// Topics: Geometry, Polygons, Intersections
// Description:
// Given a list of 3D polygons, splits all of them wherever they cross any Z value given in `zs`.
// Arguments:
// polys = A list of 3D polygons to split.
// zs = A list of scalar Z values to split at.
function split_polygons_at_each_z(polys, zs, _i=0) =
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
assert( is_vector(zs), "The split value list should contain only numbers." )
_i>=len(zs)? polys :
split_polygons_at_each_z(
[
for (poly = polys)
each _split_polygon_at_z(poly, zs[_i])
], zs, _i=_i+1
);
// Section: Convex Sets

View File

@ -721,7 +721,7 @@ function offset(
is_region(path)? (
assert(!return_faces, "return_faces not supported for regions.")
let(
path = [for (p=path) polygon_is_clockwise(p)? p : reverse(p)],
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]) [
@ -743,7 +743,7 @@ function offset(
let(
chamfer = is_def(r) ? false : chamfer,
quality = max(0,round(quality)),
flip_dir = closed && !polygon_is_clockwise(path)? -1 : 1,
flip_dir = closed && !is_polygon_clockwise(path)? -1 : 1,
d = flip_dir * (is_def(r) ? r : delta),
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

View File

@ -964,7 +964,7 @@ function offset_sweep(
["points", []],
],
path = check_and_fix_path(path, [2], closed=true),
clockwise = polygon_is_clockwise(path),
clockwise = is_polygon_clockwise(path),
dummy1 = _struct_valid(top,"offset_sweep","top"),
dummy2 = _struct_valid(bottom,"offset_sweep","bottom"),
top = struct_set(argspec, top, grow=false),
@ -1849,8 +1849,8 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
let(
// Determine which points are concave by making bottom 2d if necessary
bot_proj = len(bottom[0])==2 ? bottom : project_plane(select(bottom,0,2),bottom),
bottom_sign = polygon_is_clockwise(bot_proj) ? 1 : -1,
concave = [for(i=[0:N-1]) bottom_sign*sign(point_left_of_line2d(select(bot_proj,i+1), select(bot_proj, i-1,i)))>0],
bottom_sign = is_polygon_clockwise(bot_proj) ? 1 : -1,
concave = [for(i=[0:N-1]) bottom_sign*sign(_point_left_of_line2d(select(bot_proj,i+1), select(bot_proj, i-1,i)))>0],
top = is_undef(top) ? path3d(bottom,height/2) :
len(top[0])==2 ? path3d(top,height/2) :
top,

View File

@ -969,7 +969,7 @@ function path_sweep2d(shape, path, closed=false, caps, quality=1, style="min_edg
assert(!closed || !caps, "Cannot make closed shape with caps")
let(
profile = ccw_polygon(shape),
flip = closed && polygon_is_clockwise(path) ? -1 : 1,
flip = closed && is_polygon_clockwise(path) ? -1 : 1,
path = flip ? reverse(path) : path,
proflist= transpose(
[for(pt = profile)

View File

@ -43,9 +43,6 @@ test_circle_3points();
test_circle_point_tangents();
test_noncollinear_triple();
test_pointlist_bounds();
test_closest_point();
test_furthest_point();
test_polygon_area();
test_is_convex_polygon();
test_polygon_shift();
@ -54,7 +51,7 @@ test_reindex_polygon();
test_align_polygon();
test_centroid();
test_point_in_polygon();
test_polygon_is_clockwise();
test_is_polygon_clockwise();
test_clockwise_polygon();
test_ccw_polygon();
test_reverse_polygon();
@ -848,69 +845,14 @@ module test_point_in_polygon() {
*test_point_in_polygon();
module test_pointlist_bounds() {
pts = [
[-53,27,12],
[-63,97,36],
[84,-32,-5],
[63,-24,42],
[23,57,-42]
];
assert(pointlist_bounds(pts) == [[-63,-32,-42], [84,97,42]]);
pts2d = [
[-53,12],
[-63,36],
[84,-5],
[63,42],
[23,-42]
];
assert(pointlist_bounds(pts2d) == [[-63,-42],[84,42]]);
pts5d = [
[-53, 27, 12,-53, 12],
[-63, 97, 36,-63, 36],
[ 84,-32, -5, 84, -5],
[ 63,-24, 42, 63, 42],
[ 23, 57,-42, 23,-42]
];
assert(pointlist_bounds(pts5d) == [[-63,-32,-42,-63,-42],[84,97,42,84,42]]);
assert(pointlist_bounds([[3,4,5,6]]), [[3,4,5,6],[3,4,5,6]]);
}
*test_pointlist_bounds();
module test_closest_point() {
ptlist = [for (i=count(100)) rands(-100,100,2,seed_value=8463+i)];
testpts = [for (i=count(100)) rands(-100,100,2,seed_value=6834+i)];
for (pt = testpts) {
pidx = closest_point(pt,ptlist);
dists = [for (p=ptlist) norm(pt-p)];
mindist = min(dists);
assert(mindist == dists[pidx]);
module test_is_polygon_clockwise() {
assert(is_polygon_clockwise([[-1,1],[1,1],[1,-1],[-1,-1]]));
assert(!is_polygon_clockwise([[1,1],[-1,1],[-1,-1],[1,-1]]));
assert(is_polygon_clockwise(circle(d=100)));
assert(is_polygon_clockwise(square(100)));
}
}
*test_closest_point();
module test_furthest_point() {
ptlist = [for (i=count(100)) rands(-100,100,2,seed_value=8463+i)];
testpts = [for (i=count(100)) rands(-100,100,2,seed_value=6834+i)];
for (pt = testpts) {
pidx = furthest_point(pt,ptlist);
dists = [for (p=ptlist) norm(pt-p)];
mindist = max(dists);
assert(mindist == dists[pidx]);
}
}
*test_furthest_point();
module test_polygon_is_clockwise() {
assert(polygon_is_clockwise([[-1,1],[1,1],[1,-1],[-1,-1]]));
assert(!polygon_is_clockwise([[1,1],[-1,1],[-1,-1],[1,-1]]));
assert(polygon_is_clockwise(circle(d=100)));
assert(polygon_is_clockwise(square(100)));
}
*test_polygon_is_clockwise();
*test_is_polygon_clockwise();
module test_clockwise_polygon() {

View File

@ -207,7 +207,61 @@ module test_vector_nearest(){
}
test_vector_nearest();
cube();
module test_pointlist_bounds() {
pts = [
[-53,27,12],
[-63,97,36],
[84,-32,-5],
[63,-24,42],
[23,57,-42]
];
assert(pointlist_bounds(pts) == [[-63,-32,-42], [84,97,42]]);
pts2d = [
[-53,12],
[-63,36],
[84,-5],
[63,42],
[23,-42]
];
assert(pointlist_bounds(pts2d) == [[-63,-42],[84,42]]);
pts5d = [
[-53, 27, 12,-53, 12],
[-63, 97, 36,-63, 36],
[ 84,-32, -5, 84, -5],
[ 63,-24, 42, 63, 42],
[ 23, 57,-42, 23,-42]
];
assert(pointlist_bounds(pts5d) == [[-63,-32,-42,-63,-42],[84,97,42,84,42]]);
assert(pointlist_bounds([[3,4,5,6]]), [[3,4,5,6],[3,4,5,6]]);
}
test_pointlist_bounds();
module test_closest_point() {
ptlist = [for (i=count(100)) rands(-100,100,2,seed_value=8463+i)];
testpts = [for (i=count(100)) rands(-100,100,2,seed_value=6834+i)];
for (pt = testpts) {
pidx = closest_point(pt,ptlist);
dists = [for (p=ptlist) norm(pt-p)];
mindist = min(dists);
assert(mindist == dists[pidx]);
}
}
test_closest_point();
module test_furthest_point() {
ptlist = [for (i=count(100)) rands(-100,100,2,seed_value=8463+i)];
testpts = [for (i=count(100)) rands(-100,100,2,seed_value=6834+i)];
for (pt = testpts) {
pidx = furthest_point(pt,ptlist);
dists = [for (p=ptlist) norm(pt-p)];
mindist = max(dists);
assert(mindist == dists[pidx]);
}
}
test_furthest_point();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View File

@ -603,183 +603,6 @@ module zrot(a=0, p, cp)
function zrot(a=0, p, cp) = rot(a, cp=cp, p=p);
// Function&Module: xyrot()
//
// Usage: As Module
// xyrot(a, [cp=]) ...
// Usage: As a Function to rotate points
// rotated = xyrot(a, p, [cp=]);
// Usage: As a Function to get rotation matrix
// mat = xyrot(a, [cp=]);
//
// Topics: Affine, Matrices, Transforms, Rotation
// See Also: rot(), xrot(), yrot(), zrot(), xzrot(), yzrot(), xyzrot(), affine3d_rot_by_axis()
//
// Description:
// Rotates around the [1,1,0] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
// * Called as a module, rotates all children.
// * Called as a function with a `p` argument containing a point, returns the rotated point.
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
//
// Arguments:
// a = angle to rotate by in degrees.
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
// ---
// cp = centerpoint to rotate around. Default: [0,0,0]
//
// Example:
// #cylinder(h=50, r=10, center=true);
// xyrot(90) cylinder(h=50, r=10, center=true);
module xyrot(a=0, p, cp)
{
assert(is_undef(p), "Module form `xyrot()` does not accept p= argument.");
if (a==0) {
children(); // May be slightly faster?
} else {
mat = xyrot(a=a, cp=cp);
multmatrix(mat) children();
}
}
function xyrot(a=0, p, cp) = rot(a=a, v=[1,1,0], cp=cp, p=p);
// Function&Module: xzrot()
//
// Usage: As Module
// xzrot(a, [cp=]) ...
// Usage: As Function to rotate points
// rotated = xzrot(a, p, [cp=]);
// Usage: As Function to return rotation matrix
// mat = xzrot(a, [cp=]);
//
// Topics: Affine, Matrices, Transforms, Rotation
// See Also: rot(), xrot(), yrot(), zrot(), xyrot(), yzrot(), xyzrot(), affine3d_rot_by_axis()
//
// Description:
// Rotates around the [1,0,1] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
// * Called as a module, rotates all children.
// * Called as a function with a `p` argument containing a point, returns the rotated point.
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
//
// Arguments:
// a = angle to rotate by in degrees.
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
// ---
// cp = centerpoint to rotate around. Default: [0,0,0]
//
// Example:
// #cylinder(h=50, r=10, center=true);
// xzrot(90) cylinder(h=50, r=10, center=true);
module xzrot(a=0, p, cp)
{
assert(is_undef(p), "Module form `xzrot()` does not accept p= argument.");
if (a==0) {
children(); // May be slightly faster?
} else {
mat = xzrot(a=a, cp=cp);
multmatrix(mat) children();
}
}
function xzrot(a=0, p, cp) = rot(a=a, v=[1,0,1], cp=cp, p=p);
// Function&Module: yzrot()
//
// Usage: As Module
// yzrot(a, [cp=]) ...
// Usage: As Function to rotate points
// rotated = yzrot(a, p, [cp=]);
// Usage: As Function to return rotation matrix
// mat = yzrot(a, [cp=]);
//
// Topics: Affine, Matrices, Transforms, Rotation
// See Also: rot(), xrot(), yrot(), zrot(), xyrot(), xzrot(), xyzrot(), affine3d_rot_by_axis()
//
// Description:
// Rotates around the [0,1,1] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
// * Called as a module, rotates all children.
// * Called as a function with a `p` argument containing a point, returns the rotated point.
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
//
// Arguments:
// a = angle to rotate by in degrees.
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
// ---
// cp = centerpoint to rotate around. Default: [0,0,0]
//
// Example:
// #cylinder(h=50, r=10, center=true);
// yzrot(90) cylinder(h=50, r=10, center=true);
module yzrot(a=0, p, cp)
{
assert(is_undef(p), "Module form `yzrot()` does not accept p= argument.");
if (a==0) {
children(); // May be slightly faster?
} else {
mat = yzrot(a=a, cp=cp);
multmatrix(mat) children();
}
}
function yzrot(a=0, p, cp) = rot(a=a, v=[0,1,1], cp=cp, p=p);
// Function&Module: xyzrot()
//
// Usage: As Module
// xyzrot(a, [cp=]) ...
// Usage: As Function to rotate points
// rotated = xyzrot(a, p, [cp=]);
// Usage: As Function to return rotation matrix
// mat = xyzrot(a, [cp=]);
//
// Topics: Affine, Matrices, Transforms, Rotation
// See Also: rot(), xrot(), yrot(), zrot(), xyrot(), xzrot(), yzrot(), affine3d_rot_by_axis()
//
// Description:
// Rotates around the [1,1,1] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
// * Called as a module, rotates all children.
// * Called as a function with a `p` argument containing a point, returns the rotated point.
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
//
// Arguments:
// a = angle to rotate by in degrees.
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
// ---
// cp = centerpoint to rotate around. Default: [0,0,0]
//
// Example:
// #cylinder(h=50, r=10, center=true);
// xyzrot(90) cylinder(h=50, r=10, center=true);
module xyzrot(a=0, p, cp)
{
assert(is_undef(p), "Module form `xyzrot()` does not accept p= argument.");
if (a==0) {
children(); // May be slightly faster?
} else {
mat = xyzrot(a=a, cp=cp);
multmatrix(mat) children();
}
}
function xyzrot(a=0, p, cp) = rot(a=a, v=[1,1,1], cp=cp, p=p);
//////////////////////////////////////////////////////////////////////
// Section: Scaling and Mirroring
@ -1265,189 +1088,6 @@ function zflip(p, z=0) =
move([0,0,z],p=mirror([0,0,1],p=move([0,0,-z],p=p)));
// Function&Module: xyflip()
//
// Usage: As Module
// xyflip([cp]) ...
// Usage: As Function
// pt = xyflip(p, [cp]);
// Usage: Get Affine Matrix
// pt = xyflip([cp], [planar=]);
//
// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
// See Also: mirror(), xflip(), yflip(), zflip(), xzflip(), yzflip(), affine2d_mirror(), affine3d_mirror()
//
// Description:
// Mirrors/reflects across the origin [0,0,0], along the reflection plane where X=Y. If `cp` is given, the reflection plane passes through that point
// * Called as the built-in module, mirrors all children across the line/plane.
// * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
// * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
// * Called as a function without a `p` argument, and `planer=true`, returns the affine2d 3x3 mirror matrix.
// * Called as a function without a `p` argument, and `planar=false`, returns the affine3d 4x4 mirror matrix.
//
// Arguments:
// p = If given, the point, path, patch, or VNF to mirror. Function use only.
// cp = The centerpoint of the plane of reflection, given either as a point, or as a scalar distance away from the origin.
// ---
// planar = If true, and p is not given, returns a 2D affine transformation matrix. Function use only. Default: False
//
// Example(2D):
// xyflip() text("Foobar", size=20, halign="center");
//
// Example:
// left(10) frame_ref();
// right(10) xyflip() frame_ref();
//
// Example:
// xyflip(cp=-15) frame_ref();
//
// Example:
// xyflip(cp=[10,10,10]) frame_ref();
//
// Example: Called as Function for a 3D matrix
// mat = xyflip();
// multmatrix(mat) frame_ref();
//
// Example(2D): Called as Function for a 2D matrix
// mat = xyflip(planar=true);
// multmatrix(mat) text("Foobar", size=20, halign="center");
module xyflip(p, cp=0, planar) {
assert(is_undef(p), "Module form `xyflip()` does not accept p= argument.");
assert(is_undef(planar), "Module form `xyflip()` does not accept planar= argument.");
mat = xyflip(cp=cp);
multmatrix(mat) children();
}
function xyflip(p, cp=0, planar=false) =
assert(is_finite(cp) || is_vector(cp))
let(
v = unit([-1,1,0]),
n = planar? point2d(v) : v
)
cp == 0 || cp==[0,0,0]? mirror(n, p=p) :
let(
cp = is_finite(cp)? n * cp :
is_vector(cp)? assert(len(cp) == len(n)) cp :
assert(is_finite(cp) || is_vector(cp)),
mat = move(cp) * mirror(n) * move(-cp)
) is_undef(p)? mat : apply(mat, p);
// Function&Module: xzflip()
//
// Usage: As Module
// xzflip([cp]) ...
// Usage: As Function
// pt = xzflip([cp], p);
// Usage: Get Affine Matrix
// pt = xzflip([cp]);
//
// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
// See Also: mirror(), xflip(), yflip(), zflip(), xyflip(), yzflip(), affine2d_mirror(), affine3d_mirror()
//
// Description:
// Mirrors/reflects across the origin [0,0,0], along the reflection plane where X=Y. If `cp` is given, the reflection plane passes through that point
// * Called as the built-in module, mirrors all children across the line/plane.
// * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
// * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
// * Called as a function without a `p` argument, returns the affine3d 4x4 mirror matrix.
//
// Arguments:
// p = If given, the point, path, patch, or VNF to mirror. Function use only.
// cp = The centerpoint of the plane of reflection, given either as a point, or as a scalar distance away from the origin.
//
// Example:
// left(10) frame_ref();
// right(10) xzflip() frame_ref();
//
// Example:
// xzflip(cp=-15) frame_ref();
//
// Example:
// xzflip(cp=[10,10,10]) frame_ref();
//
// Example: Called as Function
// mat = xzflip();
// multmatrix(mat) frame_ref();
module xzflip(p, cp=0) {
assert(is_undef(p), "Module form `xzflip()` does not accept p= argument.");
mat = xzflip(cp=cp);
multmatrix(mat) children();
}
function xzflip(p, cp=0) =
assert(is_finite(cp) || is_vector(cp))
let( n = unit([-1,0,1]) )
cp == 0 || cp==[0,0,0]? mirror(n, p=p) :
let(
cp = is_finite(cp)? n * cp :
is_vector(cp,3)? cp :
assert(is_finite(cp) || is_vector(cp,3)),
mat = move(cp) * mirror(n) * move(-cp)
) is_undef(p)? mat : apply(mat, p);
// Function&Module: yzflip()
//
// Usage: As Module
// yzflip([x=]) ...
// Usage: As Function
// pt = yzflip(p, [x=]);
// Usage: Get Affine Matrix
// pt = yzflip([x=]);
//
// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
// See Also: mirror(), xflip(), yflip(), zflip(), xyflip(), xzflip(), affine2d_mirror(), affine3d_mirror()
//
// Description:
// Mirrors/reflects across the origin [0,0,0], along the reflection plane where X=Y. If `cp` is given, the reflection plane passes through that point
// * Called as the built-in module, mirrors all children across the line/plane.
// * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
// * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
// * Called as a function without a `p` argument, returns the affine3d 4x4 mirror matrix.
//
// Arguments:
// p = If given, the point, path, patch, or VNF to mirror. Function use only.
// cp = The centerpoint of the plane of reflection, given either as a point, or as a scalar distance away from the origin.
//
// Example:
// left(10) frame_ref();
// right(10) yzflip() frame_ref();
//
// Example:
// yzflip(cp=-15) frame_ref();
//
// Example:
// yzflip(cp=[10,10,10]) frame_ref();
//
// Example: Called as Function
// mat = yzflip();
// multmatrix(mat) frame_ref();
module yzflip(p, cp=0) {
assert(is_undef(p), "Module form `yzflip()` does not accept p= argument.");
mat = yzflip(cp=cp);
multmatrix(mat) children();
}
function yzflip(p, cp=0) =
assert(is_finite(cp) || is_vector(cp))
let( n = unit([0,-1,1]) )
cp == 0 || cp==[0,0,0]? mirror(n, p=p) :
let(
cp = is_finite(cp)? n * cp :
is_vector(cp,3)? cp :
assert(is_finite(cp) || is_vector(cp,3)),
mat = move(cp) * mirror(n) * move(-cp)
) is_undef(p)? mat : apply(mat, p);
//////////////////////////////////////////////////////////////////////
// Section: Other Transformations
//////////////////////////////////////////////////////////////////////

View File

@ -132,6 +132,28 @@ function v_lookup(x, v) =
lerp(lo,hi,u);
// Function: pointlist_bounds()
// Usage:
// pt_pair = pointlist_bounds(pts);
// Topics: Geometry, Bounding Boxes, Bounds
// Description:
// Finds the bounds containing all the points in `pts` which can be a list of points in any dimension.
// Returns a list of two items: a list of the minimums and a list of the maximums. For example, with
// 3d points `[[MINX, MINY, MINZ], [MAXX, MAXY, MAXZ]]`
// Arguments:
// pts = List of points.
function pointlist_bounds(pts) =
assert(is_path(pts,dim=undef,fast=true) , "Invalid pointlist." )
let(
select = ident(len(pts[0])),
spread = [
for(i=[0:len(pts[0])-1])
let( spreadi = pts*select[i] )
[ min(spreadi), max(spreadi) ]
]
) transpose(spread);
// Function: unit()
// Usage:
// unit(v, [error]);
@ -241,9 +263,41 @@ function vector_axis(v1,v2=undef,v3=undef) =
// Section: Vector Searching
// Function: closest_point()
// Usage:
// index = closest_point(pt, points);
// Topics: Geometry, Points, Distance
// Description:
// Given a list of `points`, finds the index of the closest point to `pt`.
// Arguments:
// pt = The point to find the closest point to.
// points = The list of points to search.
function closest_point(pt, points) =
assert( is_vector(pt), "Invalid point." )
assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
min_index([for (p=points) norm(p-pt)]);
// Function: furthest_point()
// Usage:
// index = furthest_point(pt, points);
// Topics: Geometry, Points, Distance
// Description:
// Given a list of `points`, finds the index of the furthest point from `pt`.
// Arguments:
// pt = The point to find the farthest point from.
// points = The list of points to search.
function furthest_point(pt, points) =
assert( is_vector(pt), "Invalid point." )
assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
max_index([for (p=points) norm(p-pt)]);
// Function: vector_search()
// Usage:
// indices = vector_search(query, r, target);

158
vnf.scad
View File

@ -694,8 +694,8 @@ function vnf_bend(vnf,r,d,axis="Z") =
[for(i = [1:1:steps-1]) i*step+bmin.x],
facepolys = [for (face=vnf[1]) select(verts,face)],
splits = axis=="X"?
split_polygons_at_each_y(facepolys, bend_at) :
split_polygons_at_each_x(facepolys, bend_at),
_split_polygons_at_each_y(facepolys, bend_at) :
_split_polygons_at_each_x(facepolys, bend_at),
newtris = _triangulate_planar_convex_polygons(splits),
bent_faces = [
for (tri = newtris) [
@ -712,6 +712,160 @@ function vnf_bend(vnf,r,d,axis="Z") =
) vnf_add_faces(faces=bent_faces);
function _split_polygon_at_x(poly, x) =
let(
xs = subindex(poly,0)
) (min(xs) >= x || max(xs) <= x)? [poly] :
let(
poly2 = [
for (p = pair(poly,true)) each [
p[0],
if(
(p[0].x < x && p[1].x > x) ||
(p[1].x < x && p[0].x > x)
) let(
u = (x - p[0].x) / (p[1].x - p[0].x)
) [
x, // Important for later exact match tests
u*(p[1].y-p[0].y)+p[0].y,
u*(p[1].z-p[0].z)+p[0].z,
]
]
],
out1 = [for (p = poly2) if(p.x <= x) p],
out2 = [for (p = poly2) if(p.x >= x) p],
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(out1),
if (len(out2)>=3) each split_path_at_self_crossings(out2),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;
function _split_polygon_at_y(poly, y) =
let(
ys = subindex(poly,1)
) (min(ys) >= y || max(ys) <= y)? [poly] :
let(
poly2 = [
for (p = pair(poly,true)) each [
p[0],
if(
(p[0].y < y && p[1].y > y) ||
(p[1].y < y && p[0].y > y)
) let(
u = (y - p[0].y) / (p[1].y - p[0].y)
) [
u*(p[1].x-p[0].x)+p[0].x,
y, // Important for later exact match tests
u*(p[1].z-p[0].z)+p[0].z,
]
]
],
out1 = [for (p = poly2) if(p.y <= y) p],
out2 = [for (p = poly2) if(p.y >= y) p],
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(out1),
if (len(out2)>=3) each split_path_at_self_crossings(out2),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;
function _split_polygon_at_z(poly, z) =
let(
zs = subindex(poly,2)
) (min(zs) >= z || max(zs) <= z)? [poly] :
let(
poly2 = [
for (p = pair(poly,true)) each [
p[0],
if(
(p[0].z < z && p[1].z > z) ||
(p[1].z < z && p[0].z > z)
) let(
u = (z - p[0].z) / (p[1].z - p[0].z)
) [
u*(p[1].x-p[0].x)+p[0].x,
u*(p[1].y-p[0].y)+p[0].y,
z, // Important for later exact match tests
]
]
],
out1 = [for (p = poly2) if(p.z <= z) p],
out2 = [for (p = poly2) if(p.z >= z) p],
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(close_path(out1), closed=false),
if (len(out2)>=3) each split_path_at_self_crossings(close_path(out2), closed=false),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;
/// Function: _split_polygons_at_each_x()
// Usage:
// splitpolys = split_polygons_at_each_x(polys, xs);
/// Topics: Geometry, Polygons, Intersections
// Description:
// Given a list of 3D polygons, splits all of them wherever they cross any X value given in `xs`.
// Arguments:
// polys = A list of 3D polygons to split.
// xs = A list of scalar X values to split at.
function _split_polygons_at_each_x(polys, xs, _i=0) =
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
assert( is_vector(xs), "The split value list should contain only numbers." )
_i>=len(xs)? polys :
split_polygons_at_each_x(
[
for (poly = polys)
each _split_polygon_at_x(poly, xs[_i])
], xs, _i=_i+1
);
///Internal Function: _split_polygons_at_each_y()
// Usage:
// splitpolys = _split_polygons_at_each_y(polys, ys);
/// Topics: Geometry, Polygons, Intersections
// Description:
// Given a list of 3D polygons, splits all of them wherever they cross any Y value given in `ys`.
// Arguments:
// polys = A list of 3D polygons to split.
// ys = A list of scalar Y values to split at.
function _split_polygons_at_each_y(polys, ys, _i=0) =
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
assert( is_vector(ys), "The split value list should contain only numbers." )
_i>=len(ys)? polys :
split_polygons_at_each_y(
[
for (poly = polys)
each _split_polygon_at_y(poly, ys[_i])
], ys, _i=_i+1
);
/// Internal Function: _split_polygons_at_each_z()
// Usage:
// splitpolys = split_polygons_at_each_z(polys, zs);
/// Topics: Geometry, Polygons, Intersections
// Description:
// Given a list of 3D polygons, splits all of them wherever they cross any Z value given in `zs`.
// Arguments:
// polys = A list of 3D polygons to split.
// zs = A list of scalar Z values to split at.
function split_polygons_at_each_z(polys, zs, _i=0) =
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
assert( is_vector(zs), "The split value list should contain only numbers." )
_i>=len(zs)? polys :
split_polygons_at_each_z(
[
for (poly = polys)
each _split_polygon_at_z(poly, zs[_i])
], zs, _i=_i+1
);
// Function&Module: vnf_validate()
// Usage: As Function
// fails = vnf_validate(vnf);