mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-08-09 00:57:12 +02:00
Merge pull request #644 from adrianVmariano/master
polygon_normal bug fix plus further doc streamlining and org
This commit is contained in:
801
geometry.scad
801
geometry.scad
@@ -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 _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:
|
// Usage:
|
||||||
// pt = point_left_of_line2d(point, line);
|
// pt = point_left_of_line2d(point, line);
|
||||||
// Topics: Geometry, Points, Lines
|
/// Topics: Geometry, Points, Lines
|
||||||
// Description:
|
// Description:
|
||||||
// Return >0 if point is left of the line defined by `line`.
|
// Return >0 if point is left of the line defined by `line`.
|
||||||
// Return =0 if point is on the 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:
|
// Arguments:
|
||||||
// point = The point to check position of.
|
// point = The point to check position of.
|
||||||
// line = Array of two points forming the line segment to test against.
|
// 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." )
|
assert( is_vector(point,2) && is_vector(line*point, 2), "Improper input." )
|
||||||
cross(line[0]-point, line[1]-line[0]);
|
cross(line[0]-point, line[1]-line[0]);
|
||||||
|
|
||||||
@@ -155,15 +155,18 @@ function line_normal(p1,p2) =
|
|||||||
// of the intersection point along s2. The proportional values run over
|
// of the intersection point along s2. The proportional values run over
|
||||||
// the range of 0 to 1 for each segment, so if it is in this range, then
|
// the range of 0 to 1 for each segment, so if it is in this range, then
|
||||||
// the intersection lies on the segment. Otherwise it lies somewhere on
|
// the intersection lies on the segment. Otherwise it lies somewhere on
|
||||||
// the extension of the segment. Result is undef for coincident lines.
|
// the extension of the segment. If lines are parallel or coincident then
|
||||||
|
// it returns undef.
|
||||||
function _general_line_intersection(s1,s2,eps=EPSILON) =
|
function _general_line_intersection(s1,s2,eps=EPSILON) =
|
||||||
let(
|
let(
|
||||||
denominator = det2([s1[0],s2[0]]-[s1[1],s2[1]])
|
denominator = det2([s1[0],s2[0]]-[s1[1],s2[1]])
|
||||||
) approx(denominator,0,eps=eps)? [undef,undef,undef] :
|
)
|
||||||
|
approx(denominator,0,eps=eps) ? undef :
|
||||||
let(
|
let(
|
||||||
t = det2([s1[0],s2[0]]-s2) / denominator,
|
t = det2([s1[0],s2[0]]-s2) / denominator,
|
||||||
u = det2([s1[0],s1[0]]-[s2[0],s1[1]]) / denominator
|
u = det2([s1[0],s1[0]]-[s2[0],s1[1]]) / denominator
|
||||||
) [s1[0]+t*(s1[1]-s1[0]), t, u];
|
)
|
||||||
|
[s1[0]+t*(s1[1]-s1[0]), t, u];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -215,7 +218,7 @@ function line_intersection(line1, line2, bounded1, bounded2, bounded, eps=EPSILO
|
|||||||
assert( is_undef(bounded1) || is_bool(bounded1) || is_bool_list(bounded1,2), "Invalid value for \"bounded1\"")
|
assert( is_undef(bounded1) || is_bool(bounded1) || is_bool_list(bounded1,2), "Invalid value for \"bounded1\"")
|
||||||
assert( is_undef(bounded2) || is_bool(bounded2) || is_bool_list(bounded2,2), "Invalid value for \"bounded2\"")
|
assert( is_undef(bounded2) || is_bool(bounded2) || is_bool_list(bounded2,2), "Invalid value for \"bounded2\"")
|
||||||
let(isect = _general_line_intersection(line1,line2,eps=eps))
|
let(isect = _general_line_intersection(line1,line2,eps=eps))
|
||||||
is_undef(isect[0]) ? undef :
|
is_undef(isect) ? undef :
|
||||||
let(
|
let(
|
||||||
bounded1 = force_list(first_defined([bounded1,bounded,false]),2),
|
bounded1 = force_list(first_defined([bounded1,bounded,false]),2),
|
||||||
bounded2 = force_list(first_defined([bounded2,bounded,false]),2),
|
bounded2 = force_list(first_defined([bounded2,bounded,false]),2),
|
||||||
@@ -324,7 +327,7 @@ function line_closest_point(line, pt, bounded=false) =
|
|||||||
// fast = If true, don't verify that all points are collinear. Default: 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)
|
// 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) =
|
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." )
|
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||||
let( pb = furthest_point(points[0],points) )
|
let( pb = furthest_point(points[0],points) )
|
||||||
norm(points[pb]-points[0])<eps*max(norm(points[pb]),norm(points[0])) ? undef :
|
norm(points[pb]-points[0])<eps*max(norm(points[pb]),norm(points[0])) ? undef :
|
||||||
@@ -337,6 +340,26 @@ function line_from_points(points, fast=false, eps=EPSILON) =
|
|||||||
// Section: Planes
|
// 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()
|
// Function: plane3pt()
|
||||||
// Usage:
|
// Usage:
|
||||||
// plane = plane3pt(p1, p2, p3);
|
// plane = plane3pt(p1, p2, p3);
|
||||||
@@ -390,7 +413,8 @@ function plane3pt_indexed(points, i1, i2, i3) =
|
|||||||
// plane = plane_from_normal(normal, [pt])
|
// plane = plane_from_normal(normal, [pt])
|
||||||
// Topics: Geometry, Planes
|
// Topics: Geometry, Planes
|
||||||
// Description:
|
// Description:
|
||||||
// Returns a plane defined by a normal vector and a point.
|
// Returns a plane defined by a normal vector and a point. If you omit `pt` you will get a plane
|
||||||
|
// passing through the origin.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// normal = Normal vector to the plane to find.
|
// normal = Normal vector to the plane to find.
|
||||||
// pt = Point 3D on the plane to find.
|
// pt = Point 3D on the plane to find.
|
||||||
@@ -503,10 +527,13 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
|
|||||||
function plane_from_polygon(poly, fast=false, eps=EPSILON) =
|
function plane_from_polygon(poly, fast=false, eps=EPSILON) =
|
||||||
assert( is_path(poly,dim=3), "Invalid polygon." )
|
assert( is_path(poly,dim=3), "Invalid polygon." )
|
||||||
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||||
len(poly)==3 ? plane3pt(poly[0],poly[1],poly[2]) :
|
let(
|
||||||
let( triple = sort(noncollinear_triple(poly,error=false)) )
|
poly_normal = polygon_normal(poly)
|
||||||
triple==[] ? [] :
|
)
|
||||||
let( plane = plane3pt(poly[triple[0]],poly[triple[1]],poly[triple[2]]))
|
is_undef(poly_normal) ? [] :
|
||||||
|
let(
|
||||||
|
plane = plane_from_normal(poly_normal, poly[0])
|
||||||
|
)
|
||||||
fast? plane: points_on_plane(poly, plane, eps=eps)? plane: [];
|
fast? plane: points_on_plane(poly, plane, eps=eps)? plane: [];
|
||||||
|
|
||||||
|
|
||||||
@@ -539,77 +566,7 @@ function plane_offset(plane) =
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Function: projection_on_plane()
|
// Returns [POINT, U] if line intersects plane at one point, where U is zero at line[0] and 1 at line[1]
|
||||||
// Usage:
|
|
||||||
// pts = projection_on_plane(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 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 = projection_on_plane(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 projection_on_plane(plane, points) =
|
|
||||||
assert( _valid_plane(plane), "Invalid plane." )
|
|
||||||
assert( is_matrix(points,undef,3), "Invalid list of points or dimension." )
|
|
||||||
let(
|
|
||||||
p = len(points[0])==2
|
|
||||||
? [for(pi=points) point3d(pi) ]
|
|
||||||
: points,
|
|
||||||
plane = normalize_plane(plane),
|
|
||||||
n = point3d(plane)
|
|
||||||
)
|
|
||||||
[for(pi=p) pi - (pi*n - plane[3])*n];
|
|
||||||
|
|
||||||
|
|
||||||
// Function: plane_point_nearest_origin()
|
|
||||||
// Usage:
|
|
||||||
// pt = plane_point_nearest_origin(plane);
|
|
||||||
// Topics: Geometry, Planes, Distance
|
|
||||||
// Description:
|
|
||||||
// Returns the point on the plane that is closest to the origin.
|
|
||||||
// Arguments:
|
|
||||||
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
|
|
||||||
function plane_point_nearest_origin(plane) =
|
|
||||||
let( plane = normalize_plane(plane) )
|
|
||||||
point3d(plane) * plane[3];
|
|
||||||
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// Returns [LINE, undef] if the line is on the plane.
|
// Returns [LINE, undef] if the line is on the plane.
|
||||||
// Returns undef if line is parallel to, but not on the given plane.
|
// Returns undef if line is parallel to, but not on the given plane.
|
||||||
function _general_plane_line_intersection(plane, line, eps=EPSILON) =
|
function _general_plane_line_intersection(plane, line, eps=EPSILON) =
|
||||||
@@ -624,36 +581,17 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
|
|||||||
: [ line[0]-a/b*(line[1]-line[0]), -a/b ];
|
: [ line[0]-a/b*(line[1]-line[0]), -a/b ];
|
||||||
|
|
||||||
|
|
||||||
// Function: normalize_plane()
|
/// Internal Function: normalize_plane()
|
||||||
// Usage:
|
// Usage:
|
||||||
// nplane = normalize_plane(plane);
|
// nplane = normalize_plane(plane);
|
||||||
// Topics: Geometry, Planes
|
/// Topics: Geometry, Planes
|
||||||
// Description:
|
// Description:
|
||||||
// Returns a new representation [A,B,C,D] of `plane` where norm([A,B,C]) is equal to one.
|
// 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 ) )
|
assert( _valid_plane(plane), str("Invalid plane. ",plane ) )
|
||||||
plane/norm(point3d(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()
|
// Function: plane_line_intersection()
|
||||||
// Usage:
|
// Usage:
|
||||||
// pt = plane_line_intersection(plane, line, [bounded], [eps]);
|
// pt = plane_line_intersection(plane, line, [bounded], [eps]);
|
||||||
@@ -700,10 +638,10 @@ function plane_line_intersection(plane, line, bounded=false, eps=EPSILON) =
|
|||||||
function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
|
function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
|
||||||
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
|
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
|
||||||
assert(is_path(poly,dim=3), "Invalid polygon." )
|
assert(is_path(poly,dim=3), "Invalid polygon." )
|
||||||
assert(!is_list(bounded) || len(bounded)==2, "Invalid bound condition(s).")
|
assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition.")
|
||||||
assert(_valid_line(line,dim=3,eps=eps), "Invalid 3D line." )
|
assert(_valid_line(line,dim=3,eps=eps), "Invalid 3D line." )
|
||||||
let(
|
let(
|
||||||
bounded = is_list(bounded)? bounded : [bounded, bounded],
|
bounded = force_list(bounded,2),
|
||||||
poly = deduplicate(poly),
|
poly = deduplicate(poly),
|
||||||
indices = noncollinear_triple(poly)
|
indices = noncollinear_triple(poly)
|
||||||
) indices==[] ? undef :
|
) indices==[] ? undef :
|
||||||
@@ -773,23 +711,81 @@ function plane_intersection(plane1,plane2,plane3) =
|
|||||||
[point, point+normal];
|
[point, point+normal];
|
||||||
|
|
||||||
|
|
||||||
// Function: coplanar()
|
|
||||||
|
// Function: plane_line_angle()
|
||||||
// Usage:
|
// Usage:
|
||||||
// test = coplanar(points,[eps]);
|
// angle = plane_line_angle(plane,line);
|
||||||
// Topics: Geometry, Coplanarity
|
// Topics: Geometry, Planes, Lines, Angle
|
||||||
// Description:
|
// 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:
|
// Arguments:
|
||||||
// points = The points to test.
|
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
|
||||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
// points = List of points to project
|
||||||
function coplanar(points, eps=EPSILON) =
|
// Example(FlatSpin,VPD=500,VPT=[2,20,10]):
|
||||||
assert( is_path(points,dim=3) , "Input should be a list of 3D points." )
|
// points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100, $fn=36))));
|
||||||
assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative value." )
|
// plane = plane_from_normal([1,0,1]);
|
||||||
len(points)<=2 ? false
|
// proj = plane_closest_point(plane,points);
|
||||||
: let( ip = noncollinear_triple(points,error=false,eps=eps) )
|
// color("red") move_copies(points) sphere(d=2,$fn=12);
|
||||||
ip == [] ? false :
|
// color("blue") move_copies(proj) sphere(d=2,$fn=12);
|
||||||
let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]) )
|
// move(centroid(proj)) {
|
||||||
_pointlist_greatest_distance(points,plane) < eps;
|
// 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
|
// the maximum distance from points to the plane
|
||||||
@@ -1194,7 +1190,11 @@ function circle_line_intersection(c,r,d,line,bounded=false,eps=EPSILON) =
|
|||||||
// test = noncollinear_triple(points);
|
// test = noncollinear_triple(points);
|
||||||
// Topics: Geometry, Noncollinearity
|
// Topics: Geometry, Noncollinearity
|
||||||
// Description:
|
// Description:
|
||||||
// Finds the indices of three good non-collinear points from the pointlist `points`.
|
// Finds the indices of three non-collinear points from the pointlist `points`.
|
||||||
|
// It selects two well separated points to define a line and chooses the third point
|
||||||
|
// to be the point farthest off the line. The points do not necessarily having the
|
||||||
|
// same winding direction as the polygon so they cannot be used to determine the
|
||||||
|
// winding direction or the direction of the normal.
|
||||||
// If all points are collinear returns [] when `error=true` or an error otherwise .
|
// If all points are collinear returns [] when `error=true` or an error otherwise .
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// points = List of input points.
|
// points = List of input points.
|
||||||
@@ -1221,58 +1221,6 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
|
|||||||
[0, b, max_index(distlist)];
|
[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
|
// Section: Polygons
|
||||||
|
|
||||||
@@ -1306,6 +1254,183 @@ function polygon_area(poly, signed=false) =
|
|||||||
signed ? total : abs(total);
|
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/Even–odd_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()
|
// Function: polygon_shift()
|
||||||
// Usage:
|
// Usage:
|
||||||
// newpoly = polygon_shift(poly, i);
|
// newpoly = polygon_shift(poly, i);
|
||||||
@@ -1379,7 +1504,7 @@ function reindex_polygon(reference, poly, return_error=false) =
|
|||||||
dim = len(reference[0]),
|
dim = len(reference[0]),
|
||||||
N = len(reference),
|
N = len(reference),
|
||||||
fixpoly = dim != 2? poly :
|
fixpoly = dim != 2? poly :
|
||||||
polygon_is_clockwise(reference)
|
is_polygon_clockwise(reference)
|
||||||
? clockwise_polygon(poly)
|
? clockwise_polygon(poly)
|
||||||
: ccw_polygon(poly),
|
: ccw_polygon(poly),
|
||||||
I = [for(i=reference) 1],
|
I = [for(i=reference) 1],
|
||||||
@@ -1431,330 +1556,6 @@ function align_polygon(reference, poly, angles, cp) =
|
|||||||
) alignments[best][0];
|
) 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/Even–odd_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 planar polygon, returns a unit-length normal vector for the
|
|
||||||
// clockwise orientation of the polygon. If the polygon points are collinear, returns `undef`.
|
|
||||||
// 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." )
|
|
||||||
len(poly)==3 ? point3d(plane3pt(poly[0],poly[1],poly[2])) :
|
|
||||||
let( triple = sort(noncollinear_triple(poly,error=false)) )
|
|
||||||
triple==[] ? undef :
|
|
||||||
point3d(plane3pt(poly[triple[0]],poly[triple[1]],poly[triple[2]])) ;
|
|
||||||
|
|
||||||
|
|
||||||
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
|
// Section: Convex Sets
|
||||||
|
@@ -721,7 +721,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_is_clockwise(p)? p : reverse(p)],
|
path = [for (p=path) clockwise_polygon(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]) [
|
||||||
@@ -743,7 +743,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_is_clockwise(path)? -1 : 1,
|
flip_dir = closed && !is_polygon_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
|
||||||
|
@@ -964,7 +964,7 @@ function offset_sweep(
|
|||||||
["points", []],
|
["points", []],
|
||||||
],
|
],
|
||||||
path = check_and_fix_path(path, [2], closed=true),
|
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"),
|
dummy1 = _struct_valid(top,"offset_sweep","top"),
|
||||||
dummy2 = _struct_valid(bottom,"offset_sweep","bottom"),
|
dummy2 = _struct_valid(bottom,"offset_sweep","bottom"),
|
||||||
top = struct_set(argspec, top, grow=false),
|
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(
|
let(
|
||||||
// Determine which points are concave by making bottom 2d if necessary
|
// 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),
|
bot_proj = len(bottom[0])==2 ? bottom : project_plane(select(bottom,0,2),bottom),
|
||||||
bottom_sign = polygon_is_clockwise(bot_proj) ? 1 : -1,
|
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],
|
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) :
|
top = is_undef(top) ? path3d(bottom,height/2) :
|
||||||
len(top[0])==2 ? path3d(top,height/2) :
|
len(top[0])==2 ? path3d(top,height/2) :
|
||||||
top,
|
top,
|
||||||
|
@@ -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")
|
assert(!closed || !caps, "Cannot make closed shape with caps")
|
||||||
let(
|
let(
|
||||||
profile = ccw_polygon(shape),
|
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,
|
path = flip ? reverse(path) : path,
|
||||||
proflist= transpose(
|
proflist= transpose(
|
||||||
[for(pt = profile)
|
[for(pt = profile)
|
||||||
|
@@ -7,7 +7,6 @@ include <../std.scad>
|
|||||||
|
|
||||||
|
|
||||||
test_point_on_segment();
|
test_point_on_segment();
|
||||||
test_point_left_of_line2d();
|
|
||||||
test_collinear();
|
test_collinear();
|
||||||
test_point_line_distance();
|
test_point_line_distance();
|
||||||
test_segment_distance();
|
test_segment_distance();
|
||||||
@@ -26,13 +25,11 @@ test_plane_from_points();
|
|||||||
test_plane_from_polygon();
|
test_plane_from_polygon();
|
||||||
test_plane_normal();
|
test_plane_normal();
|
||||||
test_plane_offset();
|
test_plane_offset();
|
||||||
test_projection_on_plane();
|
test_plane_closest_point();
|
||||||
test_plane_point_nearest_origin();
|
|
||||||
test_point_plane_distance();
|
test_point_plane_distance();
|
||||||
|
|
||||||
test__general_plane_line_intersection();
|
test__general_plane_line_intersection();
|
||||||
test_plane_line_angle();
|
test_plane_line_angle();
|
||||||
test_normalize_plane();
|
|
||||||
test_plane_line_intersection();
|
test_plane_line_intersection();
|
||||||
test_polygon_line_intersection();
|
test_polygon_line_intersection();
|
||||||
test_plane_intersection();
|
test_plane_intersection();
|
||||||
@@ -44,9 +41,6 @@ test_circle_3points();
|
|||||||
test_circle_point_tangents();
|
test_circle_point_tangents();
|
||||||
|
|
||||||
test_noncollinear_triple();
|
test_noncollinear_triple();
|
||||||
test_pointlist_bounds();
|
|
||||||
test_closest_point();
|
|
||||||
test_furthest_point();
|
|
||||||
test_polygon_area();
|
test_polygon_area();
|
||||||
test_is_convex_polygon();
|
test_is_convex_polygon();
|
||||||
test_polygon_shift();
|
test_polygon_shift();
|
||||||
@@ -55,7 +49,7 @@ test_reindex_polygon();
|
|||||||
test_align_polygon();
|
test_align_polygon();
|
||||||
test_centroid();
|
test_centroid();
|
||||||
test_point_in_polygon();
|
test_point_in_polygon();
|
||||||
test_polygon_is_clockwise();
|
test_is_polygon_clockwise();
|
||||||
test_clockwise_polygon();
|
test_clockwise_polygon();
|
||||||
test_ccw_polygon();
|
test_ccw_polygon();
|
||||||
test_reverse_polygon();
|
test_reverse_polygon();
|
||||||
@@ -94,13 +88,13 @@ function info_str(list,i=0,string=chr(10)) =
|
|||||||
: info_str(list,i+1,str(string,str(list[i][0],_valstr(list[i][1]),chr(10))));
|
: info_str(list,i+1,str(string,str(list[i][0],_valstr(list[i][1]),chr(10))));
|
||||||
|
|
||||||
|
|
||||||
module test_normalize_plane(){
|
module test__normalize_plane(){
|
||||||
plane = rands(-5,5,4,seed=333)+[10,0,0,0];
|
plane = rands(-5,5,4,seed=333)+[10,0,0,0];
|
||||||
plane2 = normalize_plane(plane);
|
plane2 = _normalize_plane(plane);
|
||||||
assert_approx(norm(point3d(plane2)),1);
|
assert_approx(norm(point3d(plane2)),1);
|
||||||
assert_approx(plane*plane2[3],plane2*plane[3]);
|
assert_approx(plane*plane2[3],plane2*plane[3]);
|
||||||
}
|
}
|
||||||
*test_normalize_plane();
|
test__normalize_plane();
|
||||||
|
|
||||||
module test_plane_line_intersection(){
|
module test_plane_line_intersection(){
|
||||||
line = [rands(-1,1,3,seed=74),rands(-1,1,3,seed=99)+[2,0,0]];
|
line = [rands(-1,1,3,seed=74),rands(-1,1,3,seed=99)+[2,0,0]];
|
||||||
@@ -149,20 +143,10 @@ module test_plane_intersection(){
|
|||||||
*test_plane_intersection();
|
*test_plane_intersection();
|
||||||
|
|
||||||
|
|
||||||
module test_plane_point_nearest_origin(){
|
|
||||||
point = rands(-1,1,3)+[2,0,0]; // a non zero vector
|
|
||||||
plane = [ each point, point*point]; // a plane containing `point`
|
|
||||||
info = info_str([["point = ",point],["plane = ",plane]]);
|
|
||||||
assert_approx(plane_point_nearest_origin(plane),point,info);
|
|
||||||
assert_approx(plane_point_nearest_origin([each point,5]),5*unit(point)/norm(point),info);
|
|
||||||
}
|
|
||||||
test_plane_point_nearest_origin();
|
|
||||||
|
|
||||||
|
|
||||||
module test_plane_offset(){
|
module test_plane_offset(){
|
||||||
plane = rands(-1,1,4)+[2,0,0,0]; // a valid plane
|
plane = rands(-1,1,4)+[2,0,0,0]; // a valid plane
|
||||||
info = info_str([["plane = ",plane]]);
|
info = info_str([["plane = ",plane]]);
|
||||||
assert_approx(plane_offset(plane), normalize_plane(plane)[3],info);
|
assert_approx(plane_offset(plane), _normalize_plane(plane)[3],info);
|
||||||
assert_approx(plane_offset([1,1,1,1]), 1/sqrt(3),info);
|
assert_approx(plane_offset([1,1,1,1]), 1/sqrt(3),info);
|
||||||
}
|
}
|
||||||
*test_plane_offset();
|
*test_plane_offset();
|
||||||
@@ -248,14 +232,14 @@ module test_points_on_plane() {
|
|||||||
ang = rands(0,360,1)[0];
|
ang = rands(0,360,1)[0];
|
||||||
normal = rot(a=ang,p=normal0);
|
normal = rot(a=ang,p=normal0);
|
||||||
plane = [each normal, normal*dir];
|
plane = [each normal, normal*dir];
|
||||||
prj_pts = projection_on_plane(plane,pts);
|
prj_pts = plane_closest_point(plane,pts);
|
||||||
info = info_str([["pts = ",pts],["dir = ",dir],["ang = ",ang]]);
|
info = info_str([["pts = ",pts],["dir = ",dir],["ang = ",ang]]);
|
||||||
assert(points_on_plane(prj_pts,plane),info);
|
assert(points_on_plane(prj_pts,plane),info);
|
||||||
assert(!points_on_plane(concat(pts,[normal-dir]),plane),info);
|
assert(!points_on_plane(concat(pts,[normal-dir]),plane),info);
|
||||||
}
|
}
|
||||||
*test_points_on_plane();
|
*test_points_on_plane();
|
||||||
|
|
||||||
module test_projection_on_plane(){
|
module test_plane_closest_point(){
|
||||||
ang = rands(0,360,1)[0];
|
ang = rands(0,360,1)[0];
|
||||||
dir = rands(-10,10,3);
|
dir = rands(-10,10,3);
|
||||||
normal0 = unit([1,2,3]);
|
normal0 = unit([1,2,3]);
|
||||||
@@ -265,16 +249,16 @@ module test_projection_on_plane(){
|
|||||||
planem = [each normal, normal*dir];
|
planem = [each normal, normal*dir];
|
||||||
pts = [for(i=[1:10]) rands(-1,1,3)];
|
pts = [for(i=[1:10]) rands(-1,1,3)];
|
||||||
info = info_str([["ang = ",ang],["dir = ",dir]]);
|
info = info_str([["ang = ",ang],["dir = ",dir]]);
|
||||||
assert_approx( projection_on_plane(plane,pts),
|
assert_approx( plane_closest_point(plane,pts),
|
||||||
projection_on_plane(plane,projection_on_plane(plane,pts)),info);
|
plane_closest_point(plane,plane_closest_point(plane,pts)),info);
|
||||||
assert_approx( projection_on_plane(plane,pts),
|
assert_approx( plane_closest_point(plane,pts),
|
||||||
rot(a=ang,p=projection_on_plane(plane0,rot(a=-ang,p=pts))),info);
|
rot(a=ang,p=plane_closest_point(plane0,rot(a=-ang,p=pts))),info);
|
||||||
assert_approx( move((-normal*dir)*normal,p=projection_on_plane(planem,pts)),
|
assert_approx( move((-normal*dir)*normal,p=plane_closest_point(planem,pts)),
|
||||||
projection_on_plane(plane,pts),info);
|
plane_closest_point(plane,pts),info);
|
||||||
assert_approx( move((normal*dir)*normal,p=projection_on_plane(plane,pts)),
|
assert_approx( move((normal*dir)*normal,p=plane_closest_point(plane,pts)),
|
||||||
projection_on_plane(planem,pts),info);
|
plane_closest_point(planem,pts),info);
|
||||||
}
|
}
|
||||||
*test_projection_on_plane();
|
*test_plane_closest_point();
|
||||||
|
|
||||||
module test_line_from_points() {
|
module test_line_from_points() {
|
||||||
assert_approx(line_from_points([[1,0],[0,0],[-1,0]]),[[-1,0],[1,0]]);
|
assert_approx(line_from_points([[1,0],[0,0],[-1,0]]),[[-1,0],[1,0]]);
|
||||||
@@ -315,12 +299,12 @@ module test_point_on_segment() {
|
|||||||
*test_point_on_segment();
|
*test_point_on_segment();
|
||||||
|
|
||||||
|
|
||||||
module test_point_left_of_line2d() {
|
module test__point_left_of_line2d() {
|
||||||
assert(point_left_of_line2d([ -3, 0], [[-10,-10], [10,10]]) > 0);
|
assert(_point_left_of_line2d([ -3, 0], [[-10,-10], [10,10]]) > 0);
|
||||||
assert(point_left_of_line2d([ 0, 0], [[-10,-10], [10,10]]) == 0);
|
assert(_point_left_of_line2d([ 0, 0], [[-10,-10], [10,10]]) == 0);
|
||||||
assert(point_left_of_line2d([ 3, 0], [[-10,-10], [10,10]]) < 0);
|
assert(_point_left_of_line2d([ 3, 0], [[-10,-10], [10,10]]) < 0);
|
||||||
}
|
}
|
||||||
*test_point_left_of_line2d();
|
test__point_left_of_line2d();
|
||||||
|
|
||||||
module test_collinear() {
|
module test_collinear() {
|
||||||
assert(collinear([-10,-10], [-15, -16], [10,10]) == false);
|
assert(collinear([-10,-10], [-15, -16], [10,10]) == false);
|
||||||
@@ -859,69 +843,14 @@ module test_point_in_polygon() {
|
|||||||
*test_point_in_polygon();
|
*test_point_in_polygon();
|
||||||
|
|
||||||
|
|
||||||
module test_pointlist_bounds() {
|
|
||||||
pts = [
|
module test_is_polygon_clockwise() {
|
||||||
[-53,27,12],
|
assert(is_polygon_clockwise([[-1,1],[1,1],[1,-1],[-1,-1]]));
|
||||||
[-63,97,36],
|
assert(!is_polygon_clockwise([[1,1],[-1,1],[-1,-1],[1,-1]]));
|
||||||
[84,-32,-5],
|
assert(is_polygon_clockwise(circle(d=100)));
|
||||||
[63,-24,42],
|
assert(is_polygon_clockwise(square(100)));
|
||||||
[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();
|
*test_is_polygon_clockwise();
|
||||||
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
|
|
||||||
module test_clockwise_polygon() {
|
module test_clockwise_polygon() {
|
||||||
|
@@ -17,8 +17,8 @@ module test_circle() {
|
|||||||
for (pt = circle(r=100)) {
|
for (pt = circle(r=100)) {
|
||||||
assert(approx(norm(pt),100));
|
assert(approx(norm(pt),100));
|
||||||
}
|
}
|
||||||
assert(polygon_is_clockwise(circle(d=200)));
|
assert(is_polygon_clockwise(circle(d=200)));
|
||||||
assert(polygon_is_clockwise(circle(r=100)));
|
assert(is_polygon_clockwise(circle(r=100)));
|
||||||
assert(len(circle(d=100,$fn=6)) == 6);
|
assert(len(circle(d=100,$fn=6)) == 6);
|
||||||
assert(len(circle(d=100,$fn=36)) == 36);
|
assert(len(circle(d=100,$fn=36)) == 36);
|
||||||
}
|
}
|
||||||
|
@@ -217,32 +217,6 @@ module test_zflip() {
|
|||||||
test_zflip();
|
test_zflip();
|
||||||
|
|
||||||
|
|
||||||
module test_xyflip() {
|
|
||||||
assert_approx(xyflip(), [[0,1,0,0],[1,0,0,0],[0,0,1,0],[0,0,0,1]]);
|
|
||||||
assert_approx(xyflip(p=[1,2,3]), [2,1,3]);
|
|
||||||
// Verify that module at least doesn't crash.
|
|
||||||
xyflip() nil();
|
|
||||||
}
|
|
||||||
test_xyflip();
|
|
||||||
|
|
||||||
|
|
||||||
module test_xzflip() {
|
|
||||||
assert_approx(xzflip(), [[0,0,1,0],[0,1,0,0],[1,0,0,0],[0,0,0,1]]);
|
|
||||||
assert_approx(xzflip(p=[1,2,3]), [3,2,1]);
|
|
||||||
// Verify that module at least doesn't crash.
|
|
||||||
xzflip() nil();
|
|
||||||
}
|
|
||||||
test_xzflip();
|
|
||||||
|
|
||||||
|
|
||||||
module test_yzflip() {
|
|
||||||
assert_approx(yzflip(), [[1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]]);
|
|
||||||
assert_approx(yzflip(p=[1,2,3]), [1,3,2]);
|
|
||||||
// Verify that module at least doesn't crash.
|
|
||||||
yzflip() nil();
|
|
||||||
}
|
|
||||||
test_yzflip();
|
|
||||||
|
|
||||||
|
|
||||||
module test_rot() {
|
module test_rot() {
|
||||||
pts2d = 50 * [for (x=[-1,0,1],y=[-1,0,1]) [x,y]];
|
pts2d = 50 * [for (x=[-1,0,1],y=[-1,0,1]) [x,y]];
|
||||||
@@ -403,67 +377,10 @@ module test_zrot() {
|
|||||||
test_zrot();
|
test_zrot();
|
||||||
|
|
||||||
|
|
||||||
module test_xyrot() {
|
|
||||||
vals = [-270,-135,-90,45,0,30,45,90,135,147,180];
|
|
||||||
path = path3d(pentagon(d=100), 50);
|
|
||||||
for (a=vals) {
|
|
||||||
m = affine3d_rot_by_axis(RIGHT+BACK,a);
|
|
||||||
assert_approx(xyrot(a), m);
|
|
||||||
assert_approx(xyrot(a, p=path[0]), apply(m, path[0]));
|
|
||||||
assert_approx(xyrot(a, p=path), apply(m, path));
|
|
||||||
// Verify that module at least doesn't crash.
|
|
||||||
xyrot(a) nil();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test_xyrot();
|
|
||||||
|
|
||||||
|
|
||||||
module test_xzrot() {
|
|
||||||
vals = [-270,-135,-90,45,0,30,45,90,135,147,180];
|
|
||||||
path = path3d(pentagon(d=100), 50);
|
|
||||||
for (a=vals) {
|
|
||||||
m = affine3d_rot_by_axis(RIGHT+UP,a);
|
|
||||||
assert_approx(xzrot(a), m);
|
|
||||||
assert_approx(xzrot(a, p=path[0]), apply(m, path[0]));
|
|
||||||
assert_approx(xzrot(a, p=path), apply(m, path));
|
|
||||||
// Verify that module at least doesn't crash.
|
|
||||||
xzrot(a) nil();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test_xzrot();
|
|
||||||
|
|
||||||
|
|
||||||
module test_yzrot() {
|
|
||||||
vals = [-270,-135,-90,45,0,30,45,90,135,147,180];
|
|
||||||
path = path3d(pentagon(d=100), 50);
|
|
||||||
for (a=vals) {
|
|
||||||
m = affine3d_rot_by_axis(BACK+UP,a);
|
|
||||||
assert_approx(yzrot(a), m);
|
|
||||||
assert_approx(yzrot(a, p=path[0]), apply(m, path[0]));
|
|
||||||
assert_approx(yzrot(a, p=path), apply(m, path));
|
|
||||||
// Verify that module at least doesn't crash.
|
|
||||||
yzrot(a) nil();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test_yzrot();
|
|
||||||
|
|
||||||
|
|
||||||
module test_xyzrot() {
|
|
||||||
vals = [-270,-135,-90,45,0,30,45,90,135,147,180];
|
|
||||||
path = path3d(pentagon(d=100), 50);
|
|
||||||
for (a=vals) {
|
|
||||||
m = affine3d_rot_by_axis(RIGHT+BACK+UP,a);
|
|
||||||
assert_approx(xyzrot(a), m);
|
|
||||||
assert_approx(xyzrot(a, p=path[0]), apply(m, path[0]));
|
|
||||||
assert_approx(xyzrot(a, p=path), apply(m, path));
|
|
||||||
// Verify that module at least doesn't crash.
|
|
||||||
xyzrot(a) nil();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test_xyzrot();
|
|
||||||
|
|
||||||
module test_frame_map() {
|
module test_frame_map() {
|
||||||
assert(approx(frame_map(x=[1,1,0], y=[-1,1,0]), affine3d_zrot(45)));
|
assert(approx(frame_map(x=[1,1,0], y=[-1,1,0]), affine3d_zrot(45)));
|
||||||
|
assert(approx(frame_map(x=[0,1,0], y=[0,0,1]), rot(v=[1,1,1],a=120)));
|
||||||
}
|
}
|
||||||
test_frame_map();
|
test_frame_map();
|
||||||
|
|
||||||
|
@@ -207,7 +207,61 @@ module test_vector_nearest(){
|
|||||||
}
|
}
|
||||||
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
|
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
||||||
|
360
transforms.scad
360
transforms.scad
@@ -603,183 +603,6 @@ module zrot(a=0, p, cp)
|
|||||||
function zrot(a=0, p, cp) = rot(a, cp=cp, p=p);
|
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
|
// 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)));
|
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
|
// Section: Other Transformations
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
@@ -130,6 +130,7 @@ function is_only_noncolinear_vertex(points, facelist, vertex) =
|
|||||||
// face = The face, given as a list of indices into the vertex array `points`.
|
// face = The face, given as a list of indices into the vertex array `points`.
|
||||||
function triangulate_face(points, face) =
|
function triangulate_face(points, face) =
|
||||||
let(
|
let(
|
||||||
|
points = path3d(points),
|
||||||
face = deduplicate_indexed(points,face),
|
face = deduplicate_indexed(points,face),
|
||||||
count = len(face)
|
count = len(face)
|
||||||
)
|
)
|
||||||
@@ -158,21 +159,21 @@ function triangulate_face(points, face) =
|
|||||||
(clipable_ear)? // There is no point inside the ear.
|
(clipable_ear)? // There is no point inside the ear.
|
||||||
is_only_noncolinear_vertex(points, face, cv)?
|
is_only_noncolinear_vertex(points, face, cv)?
|
||||||
// In the point&line degeneracy clip to somewhere in the middle of the line.
|
// In the point&line degeneracy clip to somewhere in the middle of the line.
|
||||||
flatten([
|
concat(
|
||||||
triangulate_face(points, select(face, cv, (cv+2)%count)),
|
triangulate_face(points, select(face, cv, (cv+2)%count)),
|
||||||
triangulate_face(points, select(face, (cv+2)%count, cv))
|
triangulate_face(points, select(face, (cv+2)%count, cv))
|
||||||
])
|
)
|
||||||
:
|
:
|
||||||
// Otherwise the ear is safe to clip.
|
// Otherwise the ear is safe to clip.
|
||||||
flatten([
|
[
|
||||||
[select(face, pv, nv)],
|
select(face, pv, nv),
|
||||||
triangulate_face(points, select(face, nv, pv))
|
each triangulate_face(points, select(face, nv, pv))
|
||||||
])
|
]
|
||||||
: // If there is a point inside the ear, make a diagonal and clip along that.
|
: // If there is a point inside the ear, make a diagonal and clip along that.
|
||||||
flatten([
|
concat(
|
||||||
triangulate_face(points, select(face, cv, diagonal_point)),
|
triangulate_face(points, select(face, cv, diagonal_point)),
|
||||||
triangulate_face(points, select(face, diagonal_point, cv))
|
triangulate_face(points, select(face, diagonal_point, cv))
|
||||||
]);
|
);
|
||||||
|
|
||||||
|
|
||||||
// Function: triangulate_faces()
|
// Function: triangulate_faces()
|
||||||
|
54
vectors.scad
54
vectors.scad
@@ -132,6 +132,28 @@ function v_lookup(x, v) =
|
|||||||
lerp(lo,hi,u);
|
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()
|
// Function: unit()
|
||||||
// Usage:
|
// Usage:
|
||||||
// unit(v, [error]);
|
// unit(v, [error]);
|
||||||
@@ -241,9 +263,41 @@ function vector_axis(v1,v2=undef,v3=undef) =
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Section: Vector Searching
|
// 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()
|
// Function: vector_search()
|
||||||
// Usage:
|
// Usage:
|
||||||
// indices = vector_search(query, r, target);
|
// indices = vector_search(query, r, target);
|
||||||
|
109
vnf.scad
109
vnf.scad
@@ -694,8 +694,8 @@ function vnf_bend(vnf,r,d,axis="Z") =
|
|||||||
[for(i = [1:1:steps-1]) i*step+bmin.x],
|
[for(i = [1:1:steps-1]) i*step+bmin.x],
|
||||||
facepolys = [for (face=vnf[1]) select(verts,face)],
|
facepolys = [for (face=vnf[1]) select(verts,face)],
|
||||||
splits = axis=="X"?
|
splits = axis=="X"?
|
||||||
split_polygons_at_each_y(facepolys, bend_at) :
|
_split_polygons_at_each_y(facepolys, bend_at) :
|
||||||
split_polygons_at_each_x(facepolys, bend_at),
|
_split_polygons_at_each_x(facepolys, bend_at),
|
||||||
newtris = _triangulate_planar_convex_polygons(splits),
|
newtris = _triangulate_planar_convex_polygons(splits),
|
||||||
bent_faces = [
|
bent_faces = [
|
||||||
for (tri = newtris) [
|
for (tri = newtris) [
|
||||||
@@ -712,6 +712,111 @@ function vnf_bend(vnf,r,d,axis="Z") =
|
|||||||
) vnf_add_faces(faces=bent_faces);
|
) 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_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
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Function&Module: vnf_validate()
|
// Function&Module: vnf_validate()
|
||||||
// Usage: As Function
|
// Usage: As Function
|
||||||
// fails = vnf_validate(vnf);
|
// fails = vnf_validate(vnf);
|
||||||
|
Reference in New Issue
Block a user