mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-03-04 00:19:36 +00:00
Merge pull request #644 from adrianVmariano/master
polygon_normal bug fix plus further doc streamlining and org
This commit is contained in:
commit
87e7e6e2f4
12 changed files with 566 additions and 1065 deletions
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: point_left_of_line2d()
|
||||
/// Internal Function: point_left_of_line2d()
|
||||
// Usage:
|
||||
// pt = point_left_of_line2d(point, line);
|
||||
// Topics: Geometry, Points, Lines
|
||||
/// Topics: Geometry, Points, Lines
|
||||
// Description:
|
||||
// Return >0 if point is left of the line defined by `line`.
|
||||
// Return =0 if point is on the line.
|
||||
|
@ -55,7 +55,7 @@ function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps
|
|||
// Arguments:
|
||||
// point = The point to check position of.
|
||||
// line = Array of two points forming the line segment to test against.
|
||||
function point_left_of_line2d(point, line) =
|
||||
function _point_left_of_line2d(point, line) =
|
||||
assert( is_vector(point,2) && is_vector(line*point, 2), "Improper input." )
|
||||
cross(line[0]-point, line[1]-line[0]);
|
||||
|
||||
|
@ -155,15 +155,18 @@ function line_normal(p1,p2) =
|
|||
// 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 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) =
|
||||
let(
|
||||
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(
|
||||
t = det2([s1[0],s2[0]]-s2) / 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(bounded2) || is_bool(bounded2) || is_bool_list(bounded2,2), "Invalid value for \"bounded2\"")
|
||||
let(isect = _general_line_intersection(line1,line2,eps=eps))
|
||||
is_undef(isect[0]) ? undef :
|
||||
is_undef(isect) ? undef :
|
||||
let(
|
||||
bounded1 = force_list(first_defined([bounded1,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
|
||||
// eps = How much variance is allowed in testing each point against the line. Default: `EPSILON` (1e-9)
|
||||
function line_from_points(points, fast=false, eps=EPSILON) =
|
||||
assert( is_path(points,dim=undef), "Improper point list." )
|
||||
assert( is_path(points), "Invalid point list." )
|
||||
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||
let( pb = furthest_point(points[0],points) )
|
||||
norm(points[pb]-points[0])<eps*max(norm(points[pb]),norm(points[0])) ? undef :
|
||||
|
@ -337,6 +340,26 @@ function line_from_points(points, fast=false, eps=EPSILON) =
|
|||
// Section: Planes
|
||||
|
||||
|
||||
// Function: coplanar()
|
||||
// Usage:
|
||||
// test = coplanar(points,[eps]);
|
||||
// Topics: Geometry, Coplanarity
|
||||
// Description:
|
||||
// Returns true if the given 3D points are non-collinear and are on a plane.
|
||||
// Arguments:
|
||||
// points = The points to test.
|
||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||
function coplanar(points, eps=EPSILON) =
|
||||
assert( is_path(points,dim=3) , "Input should be a list of 3D points." )
|
||||
assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative value." )
|
||||
len(points)<=2 ? false
|
||||
: let( ip = noncollinear_triple(points,error=false,eps=eps) )
|
||||
ip == [] ? false :
|
||||
let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]) )
|
||||
_pointlist_greatest_distance(points,plane) < eps;
|
||||
|
||||
|
||||
|
||||
// Function: plane3pt()
|
||||
// Usage:
|
||||
// plane = plane3pt(p1, p2, p3);
|
||||
|
@ -390,7 +413,8 @@ function plane3pt_indexed(points, i1, i2, i3) =
|
|||
// plane = plane_from_normal(normal, [pt])
|
||||
// Topics: Geometry, Planes
|
||||
// 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:
|
||||
// normal = Normal vector to 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) =
|
||||
assert( is_path(poly,dim=3), "Invalid polygon." )
|
||||
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( triple = sort(noncollinear_triple(poly,error=false)) )
|
||||
triple==[] ? [] :
|
||||
let( plane = plane3pt(poly[triple[0]],poly[triple[1]],poly[triple[2]]))
|
||||
let(
|
||||
poly_normal = polygon_normal(poly)
|
||||
)
|
||||
is_undef(poly_normal) ? [] :
|
||||
let(
|
||||
plane = plane_from_normal(poly_normal, poly[0])
|
||||
)
|
||||
fast? plane: points_on_plane(poly, plane, eps=eps)? plane: [];
|
||||
|
||||
|
||||
|
@ -539,77 +566,7 @@ function plane_offset(plane) =
|
|||
|
||||
|
||||
|
||||
// Function: projection_on_plane()
|
||||
// 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 [POINT, U] if line intersects plane at one point, where U is zero at line[0] and 1 at line[1]
|
||||
// Returns [LINE, undef] if the line is on the plane.
|
||||
// Returns undef if line is parallel to, but not on the given plane.
|
||||
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 ];
|
||||
|
||||
|
||||
// Function: normalize_plane()
|
||||
/// Internal Function: normalize_plane()
|
||||
// Usage:
|
||||
// nplane = normalize_plane(plane);
|
||||
// Topics: Geometry, Planes
|
||||
/// Topics: Geometry, Planes
|
||||
// Description:
|
||||
// Returns a new representation [A,B,C,D] of `plane` where norm([A,B,C]) is equal to one.
|
||||
function normalize_plane(plane) =
|
||||
function _normalize_plane(plane) =
|
||||
assert( _valid_plane(plane), str("Invalid plane. ",plane ) )
|
||||
plane/norm(point3d(plane));
|
||||
|
||||
|
||||
// Function: plane_line_angle()
|
||||
// Usage:
|
||||
// angle = plane_line_angle(plane,line);
|
||||
// Topics: Geometry, Planes, Lines, Angle
|
||||
// Description:
|
||||
// Compute the angle between a plane [A, B, C, D] and a 3d line, specified as a pair of 3d points [p1,p2].
|
||||
// The resulting angle is signed, with the sign positive if the vector p2-p1 lies above the plane, on
|
||||
// the same side of the plane as the plane's normal vector.
|
||||
function plane_line_angle(plane, line) =
|
||||
assert( _valid_plane(plane), "Invalid plane." )
|
||||
assert( _valid_line(line,dim=3), "Invalid 3d line." )
|
||||
let(
|
||||
linedir = unit(line[1]-line[0]),
|
||||
normal = plane_normal(plane),
|
||||
sin_angle = linedir*normal,
|
||||
cos_angle = norm(cross(linedir,normal))
|
||||
) atan2(sin_angle,cos_angle);
|
||||
|
||||
|
||||
// Function: plane_line_intersection()
|
||||
// Usage:
|
||||
// pt = plane_line_intersection(plane, line, [bounded], [eps]);
|
||||
|
@ -700,10 +638,10 @@ function plane_line_intersection(plane, 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_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." )
|
||||
let(
|
||||
bounded = is_list(bounded)? bounded : [bounded, bounded],
|
||||
bounded = force_list(bounded,2),
|
||||
poly = deduplicate(poly),
|
||||
indices = noncollinear_triple(poly)
|
||||
) indices==[] ? undef :
|
||||
|
@ -773,23 +711,81 @@ function plane_intersection(plane1,plane2,plane3) =
|
|||
[point, point+normal];
|
||||
|
||||
|
||||
// Function: coplanar()
|
||||
|
||||
// Function: plane_line_angle()
|
||||
// Usage:
|
||||
// test = coplanar(points,[eps]);
|
||||
// Topics: Geometry, Coplanarity
|
||||
// angle = plane_line_angle(plane,line);
|
||||
// Topics: Geometry, Planes, Lines, Angle
|
||||
// Description:
|
||||
// Returns true if the given 3D points are non-collinear and are on a plane.
|
||||
// Compute the angle between a plane [A, B, C, D] and a 3d line, specified as a pair of 3d points [p1,p2].
|
||||
// The resulting angle is signed, with the sign positive if the vector p2-p1 lies above the plane, on
|
||||
// the same side of the plane as the plane's normal vector.
|
||||
function plane_line_angle(plane, line) =
|
||||
assert( _valid_plane(plane), "Invalid plane." )
|
||||
assert( _valid_line(line,dim=3), "Invalid 3d line." )
|
||||
let(
|
||||
linedir = unit(line[1]-line[0]),
|
||||
normal = plane_normal(plane),
|
||||
sin_angle = linedir*normal,
|
||||
cos_angle = norm(cross(linedir,normal))
|
||||
) atan2(sin_angle,cos_angle);
|
||||
|
||||
|
||||
|
||||
// Function: plane_closest_point()
|
||||
// Usage:
|
||||
// pts = plane_closest_point(plane, points);
|
||||
// Topics: Geometry, Planes, Projection
|
||||
// Description:
|
||||
// Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or
|
||||
// 3d points, return the closest 3D orthogonal projection of the points on the plane.
|
||||
// In other words, for every point given, returns the closest point to it on the plane.
|
||||
// Arguments:
|
||||
// points = The points to test.
|
||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||
function coplanar(points, eps=EPSILON) =
|
||||
assert( is_path(points,dim=3) , "Input should be a list of 3D points." )
|
||||
assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative value." )
|
||||
len(points)<=2 ? false
|
||||
: let( ip = noncollinear_triple(points,error=false,eps=eps) )
|
||||
ip == [] ? false :
|
||||
let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]) )
|
||||
_pointlist_greatest_distance(points,plane) < eps;
|
||||
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
|
||||
// points = List of points to project
|
||||
// Example(FlatSpin,VPD=500,VPT=[2,20,10]):
|
||||
// points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100, $fn=36))));
|
||||
// plane = plane_from_normal([1,0,1]);
|
||||
// proj = plane_closest_point(plane,points);
|
||||
// color("red") move_copies(points) sphere(d=2,$fn=12);
|
||||
// color("blue") move_copies(proj) sphere(d=2,$fn=12);
|
||||
// move(centroid(proj)) {
|
||||
// rot(from=UP,to=plane_normal(plane)) {
|
||||
// anchor_arrow(30);
|
||||
// %cube([120,150,0.1],center=true);
|
||||
// }
|
||||
// }
|
||||
function plane_closest_point(plane, points) =
|
||||
is_vector(points,3) ? plane_closest_point(plane,[points])[0] :
|
||||
assert( _valid_plane(plane), "Invalid plane." )
|
||||
assert( is_matrix(points,undef,3), "Must supply 3D points.")
|
||||
let(
|
||||
plane = _normalize_plane(plane),
|
||||
n = point3d(plane)
|
||||
)
|
||||
[for(pi=points) pi - (pi*n - plane[3])*n];
|
||||
|
||||
|
||||
// Function: point_plane_distance()
|
||||
// Usage:
|
||||
// dist = point_plane_distance(plane, point)
|
||||
// Topics: Geometry, Planes, Distance
|
||||
// Description:
|
||||
// Given a plane as [A,B,C,D] where the cartesian equation for that plane
|
||||
// is Ax+By+Cz=D, determines how far from that plane the given point is.
|
||||
// The returned distance will be positive if the point is above the
|
||||
// plane, meaning on the side where the plane normal points.
|
||||
// If the point is below the plane, then the distance returned
|
||||
// will be negative. The normal of the plane is [A,B,C].
|
||||
// Arguments:
|
||||
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
|
||||
// point = The distance evaluation point.
|
||||
function point_plane_distance(plane, point) =
|
||||
assert( _valid_plane(plane), "Invalid input plane." )
|
||||
assert( is_vector(point,3), "The point should be a 3D point." )
|
||||
let( plane = _normalize_plane(plane) )
|
||||
point3d(plane)* point - plane[3];
|
||||
|
||||
|
||||
|
||||
// the maximum distance from points to the plane
|
||||
|
@ -1194,7 +1190,11 @@ function circle_line_intersection(c,r,d,line,bounded=false,eps=EPSILON) =
|
|||
// test = noncollinear_triple(points);
|
||||
// Topics: Geometry, Noncollinearity
|
||||
// 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 .
|
||||
// Arguments:
|
||||
// points = List of input points.
|
||||
|
@ -1221,58 +1221,6 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
|
|||
[0, b, max_index(distlist)];
|
||||
|
||||
|
||||
// Function: pointlist_bounds()
|
||||
// Usage:
|
||||
// pt_pair = pointlist_bounds(pts);
|
||||
// Topics: Geometry, Bounding Boxes, Bounds
|
||||
// Description:
|
||||
// Finds the bounds containing all the points in `pts` which can be a list of points in any dimension.
|
||||
// Returns a list of two items: a list of the minimums and a list of the maximums. For example, with
|
||||
// 3d points `[[MINX, MINY, MINZ], [MAXX, MAXY, MAXZ]]`
|
||||
// Arguments:
|
||||
// pts = List of points.
|
||||
function pointlist_bounds(pts) =
|
||||
assert(is_path(pts,dim=undef,fast=true) , "Invalid pointlist." )
|
||||
let(
|
||||
select = ident(len(pts[0])),
|
||||
spread = [
|
||||
for(i=[0:len(pts[0])-1])
|
||||
let( spreadi = pts*select[i] )
|
||||
[ min(spreadi), max(spreadi) ]
|
||||
]
|
||||
) transpose(spread);
|
||||
|
||||
|
||||
// Function: closest_point()
|
||||
// Usage:
|
||||
// index = closest_point(pt, points);
|
||||
// Topics: Geometry, Points, Distance
|
||||
// Description:
|
||||
// Given a list of `points`, finds the index of the closest point to `pt`.
|
||||
// Arguments:
|
||||
// pt = The point to find the closest point to.
|
||||
// points = The list of points to search.
|
||||
function closest_point(pt, points) =
|
||||
assert( is_vector(pt), "Invalid point." )
|
||||
assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
|
||||
min_index([for (p=points) norm(p-pt)]);
|
||||
|
||||
|
||||
// Function: furthest_point()
|
||||
// Usage:
|
||||
// index = furthest_point(pt, points);
|
||||
// Topics: Geometry, Points, Distance
|
||||
// Description:
|
||||
// Given a list of `points`, finds the index of the furthest point from `pt`.
|
||||
// Arguments:
|
||||
// pt = The point to find the farthest point from.
|
||||
// points = The list of points to search.
|
||||
function furthest_point(pt, points) =
|
||||
assert( is_vector(pt), "Invalid point." )
|
||||
assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
|
||||
max_index([for (p=points) norm(p-pt)]);
|
||||
|
||||
|
||||
|
||||
// Section: Polygons
|
||||
|
||||
|
@ -1306,6 +1254,183 @@ function polygon_area(poly, signed=false) =
|
|||
signed ? total : abs(total);
|
||||
|
||||
|
||||
// Function: centroid()
|
||||
// Usage:
|
||||
// cpt = centroid(poly);
|
||||
// Topics: Geometry, Polygons, Centroid
|
||||
// Description:
|
||||
// Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid.
|
||||
// Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid.
|
||||
// Collinear points produce an error. The results are meaningless for self-intersecting
|
||||
// polygons or an error is produced.
|
||||
// Arguments:
|
||||
// poly = Points of the polygon from which the centroid is calculated.
|
||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||
function centroid(poly, eps=EPSILON) =
|
||||
assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
|
||||
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||
let(
|
||||
n = len(poly[0])==2 ? 1 :
|
||||
let( plane = plane_from_points(poly, fast=true) )
|
||||
assert( !is_undef(plane), "The polygon must be planar." )
|
||||
plane_normal(plane),
|
||||
v0 = poly[0] ,
|
||||
val = sum([
|
||||
for(i=[1:len(poly)-2])
|
||||
let(
|
||||
v1 = poly[i],
|
||||
v2 = poly[i+1],
|
||||
area = cross(v2-v0,v1-v0)*n
|
||||
) [ area, (v0+v1+v2)*area ]
|
||||
])
|
||||
)
|
||||
assert(!approx(val[0],0, eps), "The polygon is self-intersecting or its points are collinear.")
|
||||
val[1]/val[0]/3;
|
||||
|
||||
|
||||
|
||||
// Function: polygon_normal()
|
||||
// Usage:
|
||||
// vec = polygon_normal(poly);
|
||||
// Topics: Geometry, Polygons
|
||||
// Description:
|
||||
// Given a 3D simple planar polygon, returns a unit normal vector for the polygon. The vector
|
||||
// is oriented so that if the normal points towards the viewer, the polygon winds in the clockwise
|
||||
// direction. If the polygon has zero area, returns `undef`. If the polygon is self-intersecting
|
||||
// the the result is undefined. It doesn't check for coplanarity.
|
||||
// Arguments:
|
||||
// poly = The list of 3D path points for the perimeter of the polygon.
|
||||
function polygon_normal(poly) =
|
||||
assert(is_path(poly,dim=3), "Invalid 3D polygon." )
|
||||
let(
|
||||
L=len(poly),
|
||||
area_vec = sum([for(i=idx(poly))
|
||||
cross(poly[(i+1)%L]-poly[0],
|
||||
poly[(i+2)%L]-poly[(i+1)%L])])
|
||||
)
|
||||
area_vec==0 ? undef : unit(-area_vec);
|
||||
|
||||
|
||||
// Function: point_in_polygon()
|
||||
// Usage:
|
||||
// test = point_in_polygon(point, poly, [eps])
|
||||
// Topics: Geometry, Polygons
|
||||
// Description:
|
||||
// This function tests whether the given 2D point is inside, outside or on the boundary of
|
||||
// the specified 2D polygon using either the Nonzero Winding rule or the Even-Odd rule.
|
||||
// See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/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()
|
||||
// Usage:
|
||||
// newpoly = polygon_shift(poly, i);
|
||||
|
@ -1379,7 +1504,7 @@ function reindex_polygon(reference, poly, return_error=false) =
|
|||
dim = len(reference[0]),
|
||||
N = len(reference),
|
||||
fixpoly = dim != 2? poly :
|
||||
polygon_is_clockwise(reference)
|
||||
is_polygon_clockwise(reference)
|
||||
? clockwise_polygon(poly)
|
||||
: ccw_polygon(poly),
|
||||
I = [for(i=reference) 1],
|
||||
|
@ -1431,330 +1556,6 @@ function align_polygon(reference, poly, angles, cp) =
|
|||
) alignments[best][0];
|
||||
|
||||
|
||||
// Function: centroid()
|
||||
// Usage:
|
||||
// cpt = centroid(poly);
|
||||
// Topics: Geometry, Polygons, Centroid
|
||||
// Description:
|
||||
// Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid.
|
||||
// Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid.
|
||||
// Collinear points produce an error. The results are meaningless for self-intersecting
|
||||
// polygons or an error is produced.
|
||||
// Arguments:
|
||||
// poly = Points of the polygon from which the centroid is calculated.
|
||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||
function centroid(poly, eps=EPSILON) =
|
||||
assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
|
||||
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||
let(
|
||||
n = len(poly[0])==2 ? 1 :
|
||||
let( plane = plane_from_points(poly, fast=true) )
|
||||
assert( !is_undef(plane), "The polygon must be planar." )
|
||||
plane_normal(plane),
|
||||
v0 = poly[0] ,
|
||||
val = sum([
|
||||
for(i=[1:len(poly)-2])
|
||||
let(
|
||||
v1 = poly[i],
|
||||
v2 = poly[i+1],
|
||||
area = cross(v2-v0,v1-v0)*n
|
||||
) [ area, (v0+v1+v2)*area ]
|
||||
])
|
||||
)
|
||||
assert(!approx(val[0],0, eps), "The polygon is self-intersecting or its points are collinear.")
|
||||
val[1]/val[0]/3;
|
||||
|
||||
|
||||
|
||||
// Function: point_in_polygon()
|
||||
// Usage:
|
||||
// test = point_in_polygon(point, poly, [eps])
|
||||
// Topics: Geometry, Polygons
|
||||
// Description:
|
||||
// This function tests whether the given 2D point is inside, outside or on the boundary of
|
||||
// the specified 2D polygon using either the Nonzero Winding rule or the Even-Odd rule.
|
||||
// See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/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
|
||||
|
|
|
@ -721,7 +721,7 @@ function offset(
|
|||
is_region(path)? (
|
||||
assert(!return_faces, "return_faces not supported for regions.")
|
||||
let(
|
||||
path = [for (p=path) polygon_is_clockwise(p)? p : reverse(p)],
|
||||
path = [for (p=path) clockwise_polygon(p)],
|
||||
rgn = exclusive_or([for (p = path) [p]]),
|
||||
pathlist = sort(idx=0,[
|
||||
for (i=[0:1:len(rgn)-1]) [
|
||||
|
@ -743,7 +743,7 @@ function offset(
|
|||
let(
|
||||
chamfer = is_def(r) ? false : chamfer,
|
||||
quality = max(0,round(quality)),
|
||||
flip_dir = closed && !polygon_is_clockwise(path)? -1 : 1,
|
||||
flip_dir = closed && !is_polygon_clockwise(path)? -1 : 1,
|
||||
d = flip_dir * (is_def(r) ? r : delta),
|
||||
shiftsegs = [for(i=[0:len(path)-1]) _shift_segment(select(path,i,i+1), d)],
|
||||
// good segments are ones where no point on the segment is less than distance d from any point on the path
|
||||
|
|
|
@ -964,7 +964,7 @@ function offset_sweep(
|
|||
["points", []],
|
||||
],
|
||||
path = check_and_fix_path(path, [2], closed=true),
|
||||
clockwise = polygon_is_clockwise(path),
|
||||
clockwise = is_polygon_clockwise(path),
|
||||
dummy1 = _struct_valid(top,"offset_sweep","top"),
|
||||
dummy2 = _struct_valid(bottom,"offset_sweep","bottom"),
|
||||
top = struct_set(argspec, top, grow=false),
|
||||
|
@ -1849,8 +1849,8 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
|
|||
let(
|
||||
// Determine which points are concave by making bottom 2d if necessary
|
||||
bot_proj = len(bottom[0])==2 ? bottom : project_plane(select(bottom,0,2),bottom),
|
||||
bottom_sign = polygon_is_clockwise(bot_proj) ? 1 : -1,
|
||||
concave = [for(i=[0:N-1]) bottom_sign*sign(point_left_of_line2d(select(bot_proj,i+1), select(bot_proj, i-1,i)))>0],
|
||||
bottom_sign = is_polygon_clockwise(bot_proj) ? 1 : -1,
|
||||
concave = [for(i=[0:N-1]) bottom_sign*sign(_point_left_of_line2d(select(bot_proj,i+1), select(bot_proj, i-1,i)))>0],
|
||||
top = is_undef(top) ? path3d(bottom,height/2) :
|
||||
len(top[0])==2 ? path3d(top,height/2) :
|
||||
top,
|
||||
|
|
|
@ -969,7 +969,7 @@ function path_sweep2d(shape, path, closed=false, caps, quality=1, style="min_edg
|
|||
assert(!closed || !caps, "Cannot make closed shape with caps")
|
||||
let(
|
||||
profile = ccw_polygon(shape),
|
||||
flip = closed && polygon_is_clockwise(path) ? -1 : 1,
|
||||
flip = closed && is_polygon_clockwise(path) ? -1 : 1,
|
||||
path = flip ? reverse(path) : path,
|
||||
proflist= transpose(
|
||||
[for(pt = profile)
|
||||
|
|
|
@ -7,7 +7,6 @@ include <../std.scad>
|
|||
|
||||
|
||||
test_point_on_segment();
|
||||
test_point_left_of_line2d();
|
||||
test_collinear();
|
||||
test_point_line_distance();
|
||||
test_segment_distance();
|
||||
|
@ -26,13 +25,11 @@ test_plane_from_points();
|
|||
test_plane_from_polygon();
|
||||
test_plane_normal();
|
||||
test_plane_offset();
|
||||
test_projection_on_plane();
|
||||
test_plane_point_nearest_origin();
|
||||
test_plane_closest_point();
|
||||
test_point_plane_distance();
|
||||
|
||||
test__general_plane_line_intersection();
|
||||
test_plane_line_angle();
|
||||
test_normalize_plane();
|
||||
test_plane_line_intersection();
|
||||
test_polygon_line_intersection();
|
||||
test_plane_intersection();
|
||||
|
@ -44,9 +41,6 @@ test_circle_3points();
|
|||
test_circle_point_tangents();
|
||||
|
||||
test_noncollinear_triple();
|
||||
test_pointlist_bounds();
|
||||
test_closest_point();
|
||||
test_furthest_point();
|
||||
test_polygon_area();
|
||||
test_is_convex_polygon();
|
||||
test_polygon_shift();
|
||||
|
@ -55,7 +49,7 @@ test_reindex_polygon();
|
|||
test_align_polygon();
|
||||
test_centroid();
|
||||
test_point_in_polygon();
|
||||
test_polygon_is_clockwise();
|
||||
test_is_polygon_clockwise();
|
||||
test_clockwise_polygon();
|
||||
test_ccw_polygon();
|
||||
test_reverse_polygon();
|
||||
|
@ -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))));
|
||||
|
||||
|
||||
module test_normalize_plane(){
|
||||
module test__normalize_plane(){
|
||||
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(plane*plane2[3],plane2*plane[3]);
|
||||
}
|
||||
*test_normalize_plane();
|
||||
test__normalize_plane();
|
||||
|
||||
module test_plane_line_intersection(){
|
||||
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();
|
||||
|
||||
|
||||
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(){
|
||||
plane = rands(-1,1,4)+[2,0,0,0]; // a valid 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);
|
||||
}
|
||||
*test_plane_offset();
|
||||
|
@ -248,14 +232,14 @@ module test_points_on_plane() {
|
|||
ang = rands(0,360,1)[0];
|
||||
normal = rot(a=ang,p=normal0);
|
||||
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]]);
|
||||
assert(points_on_plane(prj_pts,plane),info);
|
||||
assert(!points_on_plane(concat(pts,[normal-dir]),plane),info);
|
||||
}
|
||||
*test_points_on_plane();
|
||||
|
||||
module test_projection_on_plane(){
|
||||
module test_plane_closest_point(){
|
||||
ang = rands(0,360,1)[0];
|
||||
dir = rands(-10,10,3);
|
||||
normal0 = unit([1,2,3]);
|
||||
|
@ -265,16 +249,16 @@ module test_projection_on_plane(){
|
|||
planem = [each normal, normal*dir];
|
||||
pts = [for(i=[1:10]) rands(-1,1,3)];
|
||||
info = info_str([["ang = ",ang],["dir = ",dir]]);
|
||||
assert_approx( projection_on_plane(plane,pts),
|
||||
projection_on_plane(plane,projection_on_plane(plane,pts)),info);
|
||||
assert_approx( projection_on_plane(plane,pts),
|
||||
rot(a=ang,p=projection_on_plane(plane0,rot(a=-ang,p=pts))),info);
|
||||
assert_approx( move((-normal*dir)*normal,p=projection_on_plane(planem,pts)),
|
||||
projection_on_plane(plane,pts),info);
|
||||
assert_approx( move((normal*dir)*normal,p=projection_on_plane(plane,pts)),
|
||||
projection_on_plane(planem,pts),info);
|
||||
assert_approx( plane_closest_point(plane,pts),
|
||||
plane_closest_point(plane,plane_closest_point(plane,pts)),info);
|
||||
assert_approx( plane_closest_point(plane,pts),
|
||||
rot(a=ang,p=plane_closest_point(plane0,rot(a=-ang,p=pts))),info);
|
||||
assert_approx( move((-normal*dir)*normal,p=plane_closest_point(planem,pts)),
|
||||
plane_closest_point(plane,pts),info);
|
||||
assert_approx( move((normal*dir)*normal,p=plane_closest_point(plane,pts)),
|
||||
plane_closest_point(planem,pts),info);
|
||||
}
|
||||
*test_projection_on_plane();
|
||||
*test_plane_closest_point();
|
||||
|
||||
module test_line_from_points() {
|
||||
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();
|
||||
|
||||
|
||||
module test_point_left_of_line2d() {
|
||||
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([ 3, 0], [[-10,-10], [10,10]]) < 0);
|
||||
module test__point_left_of_line2d() {
|
||||
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([ 3, 0], [[-10,-10], [10,10]]) < 0);
|
||||
}
|
||||
*test_point_left_of_line2d();
|
||||
test__point_left_of_line2d();
|
||||
|
||||
module test_collinear() {
|
||||
assert(collinear([-10,-10], [-15, -16], [10,10]) == false);
|
||||
|
@ -859,69 +843,14 @@ module test_point_in_polygon() {
|
|||
*test_point_in_polygon();
|
||||
|
||||
|
||||
module test_pointlist_bounds() {
|
||||
pts = [
|
||||
[-53,27,12],
|
||||
[-63,97,36],
|
||||
[84,-32,-5],
|
||||
[63,-24,42],
|
||||
[23,57,-42]
|
||||
];
|
||||
assert(pointlist_bounds(pts) == [[-63,-32,-42], [84,97,42]]);
|
||||
pts2d = [
|
||||
[-53,12],
|
||||
[-63,36],
|
||||
[84,-5],
|
||||
[63,42],
|
||||
[23,-42]
|
||||
];
|
||||
assert(pointlist_bounds(pts2d) == [[-63,-42],[84,42]]);
|
||||
pts5d = [
|
||||
[-53, 27, 12,-53, 12],
|
||||
[-63, 97, 36,-63, 36],
|
||||
[ 84,-32, -5, 84, -5],
|
||||
[ 63,-24, 42, 63, 42],
|
||||
[ 23, 57,-42, 23,-42]
|
||||
];
|
||||
assert(pointlist_bounds(pts5d) == [[-63,-32,-42,-63,-42],[84,97,42,84,42]]);
|
||||
assert(pointlist_bounds([[3,4,5,6]]), [[3,4,5,6],[3,4,5,6]]);
|
||||
|
||||
module test_is_polygon_clockwise() {
|
||||
assert(is_polygon_clockwise([[-1,1],[1,1],[1,-1],[-1,-1]]));
|
||||
assert(!is_polygon_clockwise([[1,1],[-1,1],[-1,-1],[1,-1]]));
|
||||
assert(is_polygon_clockwise(circle(d=100)));
|
||||
assert(is_polygon_clockwise(square(100)));
|
||||
}
|
||||
*test_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();
|
||||
|
||||
|
||||
module test_polygon_is_clockwise() {
|
||||
assert(polygon_is_clockwise([[-1,1],[1,1],[1,-1],[-1,-1]]));
|
||||
assert(!polygon_is_clockwise([[1,1],[-1,1],[-1,-1],[1,-1]]));
|
||||
assert(polygon_is_clockwise(circle(d=100)));
|
||||
assert(polygon_is_clockwise(square(100)));
|
||||
}
|
||||
*test_polygon_is_clockwise();
|
||||
*test_is_polygon_clockwise();
|
||||
|
||||
|
||||
module test_clockwise_polygon() {
|
||||
|
|
|
@ -17,8 +17,8 @@ module test_circle() {
|
|||
for (pt = circle(r=100)) {
|
||||
assert(approx(norm(pt),100));
|
||||
}
|
||||
assert(polygon_is_clockwise(circle(d=200)));
|
||||
assert(polygon_is_clockwise(circle(r=100)));
|
||||
assert(is_polygon_clockwise(circle(d=200)));
|
||||
assert(is_polygon_clockwise(circle(r=100)));
|
||||
assert(len(circle(d=100,$fn=6)) == 6);
|
||||
assert(len(circle(d=100,$fn=36)) == 36);
|
||||
}
|
||||
|
|
|
@ -217,32 +217,6 @@ module 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() {
|
||||
pts2d = 50 * [for (x=[-1,0,1],y=[-1,0,1]) [x,y]];
|
||||
|
@ -403,67 +377,10 @@ module 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() {
|
||||
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();
|
||||
|
||||
|
|
|
@ -207,7 +207,61 @@ module test_vector_nearest(){
|
|||
}
|
||||
test_vector_nearest();
|
||||
|
||||
cube();
|
||||
|
||||
module test_pointlist_bounds() {
|
||||
pts = [
|
||||
[-53,27,12],
|
||||
[-63,97,36],
|
||||
[84,-32,-5],
|
||||
[63,-24,42],
|
||||
[23,57,-42]
|
||||
];
|
||||
assert(pointlist_bounds(pts) == [[-63,-32,-42], [84,97,42]]);
|
||||
pts2d = [
|
||||
[-53,12],
|
||||
[-63,36],
|
||||
[84,-5],
|
||||
[63,42],
|
||||
[23,-42]
|
||||
];
|
||||
assert(pointlist_bounds(pts2d) == [[-63,-42],[84,42]]);
|
||||
pts5d = [
|
||||
[-53, 27, 12,-53, 12],
|
||||
[-63, 97, 36,-63, 36],
|
||||
[ 84,-32, -5, 84, -5],
|
||||
[ 63,-24, 42, 63, 42],
|
||||
[ 23, 57,-42, 23,-42]
|
||||
];
|
||||
assert(pointlist_bounds(pts5d) == [[-63,-32,-42,-63,-42],[84,97,42,84,42]]);
|
||||
assert(pointlist_bounds([[3,4,5,6]]), [[3,4,5,6],[3,4,5,6]]);
|
||||
}
|
||||
test_pointlist_bounds();
|
||||
|
||||
|
||||
module test_closest_point() {
|
||||
ptlist = [for (i=count(100)) rands(-100,100,2,seed_value=8463+i)];
|
||||
testpts = [for (i=count(100)) rands(-100,100,2,seed_value=6834+i)];
|
||||
for (pt = testpts) {
|
||||
pidx = closest_point(pt,ptlist);
|
||||
dists = [for (p=ptlist) norm(pt-p)];
|
||||
mindist = min(dists);
|
||||
assert(mindist == dists[pidx]);
|
||||
}
|
||||
}
|
||||
test_closest_point();
|
||||
|
||||
|
||||
module test_furthest_point() {
|
||||
ptlist = [for (i=count(100)) rands(-100,100,2,seed_value=8463+i)];
|
||||
testpts = [for (i=count(100)) rands(-100,100,2,seed_value=6834+i)];
|
||||
for (pt = testpts) {
|
||||
pidx = furthest_point(pt,ptlist);
|
||||
dists = [for (p=ptlist) norm(pt-p)];
|
||||
mindist = max(dists);
|
||||
assert(mindist == dists[pidx]);
|
||||
}
|
||||
}
|
||||
test_furthest_point();
|
||||
|
||||
|
||||
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
||||
|
|
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&Module: xyrot()
|
||||
//
|
||||
// Usage: As Module
|
||||
// xyrot(a, [cp=]) ...
|
||||
// Usage: As a Function to rotate points
|
||||
// rotated = xyrot(a, p, [cp=]);
|
||||
// Usage: As a Function to get rotation matrix
|
||||
// mat = xyrot(a, [cp=]);
|
||||
//
|
||||
// Topics: Affine, Matrices, Transforms, Rotation
|
||||
// See Also: rot(), xrot(), yrot(), zrot(), xzrot(), yzrot(), xyzrot(), affine3d_rot_by_axis()
|
||||
//
|
||||
// Description:
|
||||
// Rotates around the [1,1,0] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
|
||||
// * Called as a module, rotates all children.
|
||||
// * Called as a function with a `p` argument containing a point, returns the rotated point.
|
||||
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
|
||||
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
|
||||
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
|
||||
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
|
||||
//
|
||||
// Arguments:
|
||||
// a = angle to rotate by in degrees.
|
||||
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
|
||||
// ---
|
||||
// cp = centerpoint to rotate around. Default: [0,0,0]
|
||||
//
|
||||
// Example:
|
||||
// #cylinder(h=50, r=10, center=true);
|
||||
// xyrot(90) cylinder(h=50, r=10, center=true);
|
||||
module xyrot(a=0, p, cp)
|
||||
{
|
||||
assert(is_undef(p), "Module form `xyrot()` does not accept p= argument.");
|
||||
if (a==0) {
|
||||
children(); // May be slightly faster?
|
||||
} else {
|
||||
mat = xyrot(a=a, cp=cp);
|
||||
multmatrix(mat) children();
|
||||
}
|
||||
}
|
||||
|
||||
function xyrot(a=0, p, cp) = rot(a=a, v=[1,1,0], cp=cp, p=p);
|
||||
|
||||
|
||||
// Function&Module: xzrot()
|
||||
//
|
||||
// Usage: As Module
|
||||
// xzrot(a, [cp=]) ...
|
||||
// Usage: As Function to rotate points
|
||||
// rotated = xzrot(a, p, [cp=]);
|
||||
// Usage: As Function to return rotation matrix
|
||||
// mat = xzrot(a, [cp=]);
|
||||
//
|
||||
// Topics: Affine, Matrices, Transforms, Rotation
|
||||
// See Also: rot(), xrot(), yrot(), zrot(), xyrot(), yzrot(), xyzrot(), affine3d_rot_by_axis()
|
||||
//
|
||||
// Description:
|
||||
// Rotates around the [1,0,1] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
|
||||
// * Called as a module, rotates all children.
|
||||
// * Called as a function with a `p` argument containing a point, returns the rotated point.
|
||||
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
|
||||
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
|
||||
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
|
||||
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
|
||||
//
|
||||
// Arguments:
|
||||
// a = angle to rotate by in degrees.
|
||||
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
|
||||
// ---
|
||||
// cp = centerpoint to rotate around. Default: [0,0,0]
|
||||
//
|
||||
// Example:
|
||||
// #cylinder(h=50, r=10, center=true);
|
||||
// xzrot(90) cylinder(h=50, r=10, center=true);
|
||||
module xzrot(a=0, p, cp)
|
||||
{
|
||||
assert(is_undef(p), "Module form `xzrot()` does not accept p= argument.");
|
||||
if (a==0) {
|
||||
children(); // May be slightly faster?
|
||||
} else {
|
||||
mat = xzrot(a=a, cp=cp);
|
||||
multmatrix(mat) children();
|
||||
}
|
||||
}
|
||||
|
||||
function xzrot(a=0, p, cp) = rot(a=a, v=[1,0,1], cp=cp, p=p);
|
||||
|
||||
|
||||
// Function&Module: yzrot()
|
||||
//
|
||||
// Usage: As Module
|
||||
// yzrot(a, [cp=]) ...
|
||||
// Usage: As Function to rotate points
|
||||
// rotated = yzrot(a, p, [cp=]);
|
||||
// Usage: As Function to return rotation matrix
|
||||
// mat = yzrot(a, [cp=]);
|
||||
//
|
||||
// Topics: Affine, Matrices, Transforms, Rotation
|
||||
// See Also: rot(), xrot(), yrot(), zrot(), xyrot(), xzrot(), xyzrot(), affine3d_rot_by_axis()
|
||||
//
|
||||
// Description:
|
||||
// Rotates around the [0,1,1] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
|
||||
// * Called as a module, rotates all children.
|
||||
// * Called as a function with a `p` argument containing a point, returns the rotated point.
|
||||
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
|
||||
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
|
||||
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
|
||||
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
|
||||
//
|
||||
// Arguments:
|
||||
// a = angle to rotate by in degrees.
|
||||
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
|
||||
// ---
|
||||
// cp = centerpoint to rotate around. Default: [0,0,0]
|
||||
//
|
||||
// Example:
|
||||
// #cylinder(h=50, r=10, center=true);
|
||||
// yzrot(90) cylinder(h=50, r=10, center=true);
|
||||
module yzrot(a=0, p, cp)
|
||||
{
|
||||
assert(is_undef(p), "Module form `yzrot()` does not accept p= argument.");
|
||||
if (a==0) {
|
||||
children(); // May be slightly faster?
|
||||
} else {
|
||||
mat = yzrot(a=a, cp=cp);
|
||||
multmatrix(mat) children();
|
||||
}
|
||||
}
|
||||
|
||||
function yzrot(a=0, p, cp) = rot(a=a, v=[0,1,1], cp=cp, p=p);
|
||||
|
||||
|
||||
// Function&Module: xyzrot()
|
||||
//
|
||||
// Usage: As Module
|
||||
// xyzrot(a, [cp=]) ...
|
||||
// Usage: As Function to rotate points
|
||||
// rotated = xyzrot(a, p, [cp=]);
|
||||
// Usage: As Function to return rotation matrix
|
||||
// mat = xyzrot(a, [cp=]);
|
||||
//
|
||||
// Topics: Affine, Matrices, Transforms, Rotation
|
||||
// See Also: rot(), xrot(), yrot(), zrot(), xyrot(), xzrot(), yzrot(), affine3d_rot_by_axis()
|
||||
//
|
||||
// Description:
|
||||
// Rotates around the [1,1,1] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
|
||||
// * Called as a module, rotates all children.
|
||||
// * Called as a function with a `p` argument containing a point, returns the rotated point.
|
||||
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
|
||||
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
|
||||
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
|
||||
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
|
||||
//
|
||||
// Arguments:
|
||||
// a = angle to rotate by in degrees.
|
||||
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
|
||||
// ---
|
||||
// cp = centerpoint to rotate around. Default: [0,0,0]
|
||||
//
|
||||
// Example:
|
||||
// #cylinder(h=50, r=10, center=true);
|
||||
// xyzrot(90) cylinder(h=50, r=10, center=true);
|
||||
module xyzrot(a=0, p, cp)
|
||||
{
|
||||
assert(is_undef(p), "Module form `xyzrot()` does not accept p= argument.");
|
||||
if (a==0) {
|
||||
children(); // May be slightly faster?
|
||||
} else {
|
||||
mat = xyzrot(a=a, cp=cp);
|
||||
multmatrix(mat) children();
|
||||
}
|
||||
}
|
||||
|
||||
function xyzrot(a=0, p, cp) = rot(a=a, v=[1,1,1], cp=cp, p=p);
|
||||
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Section: Scaling and Mirroring
|
||||
|
@ -1265,189 +1088,6 @@ function zflip(p, z=0) =
|
|||
move([0,0,z],p=mirror([0,0,1],p=move([0,0,-z],p=p)));
|
||||
|
||||
|
||||
// Function&Module: xyflip()
|
||||
//
|
||||
// Usage: As Module
|
||||
// xyflip([cp]) ...
|
||||
// Usage: As Function
|
||||
// pt = xyflip(p, [cp]);
|
||||
// Usage: Get Affine Matrix
|
||||
// pt = xyflip([cp], [planar=]);
|
||||
//
|
||||
// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
|
||||
// See Also: mirror(), xflip(), yflip(), zflip(), xzflip(), yzflip(), affine2d_mirror(), affine3d_mirror()
|
||||
//
|
||||
// Description:
|
||||
// Mirrors/reflects across the origin [0,0,0], along the reflection plane where X=Y. If `cp` is given, the reflection plane passes through that point
|
||||
// * Called as the built-in module, mirrors all children across the line/plane.
|
||||
// * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
|
||||
// * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
|
||||
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
|
||||
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
|
||||
// * Called as a function without a `p` argument, and `planer=true`, returns the affine2d 3x3 mirror matrix.
|
||||
// * Called as a function without a `p` argument, and `planar=false`, returns the affine3d 4x4 mirror matrix.
|
||||
//
|
||||
// Arguments:
|
||||
// p = If given, the point, path, patch, or VNF to mirror. Function use only.
|
||||
// cp = The centerpoint of the plane of reflection, given either as a point, or as a scalar distance away from the origin.
|
||||
// ---
|
||||
// planar = If true, and p is not given, returns a 2D affine transformation matrix. Function use only. Default: False
|
||||
//
|
||||
// Example(2D):
|
||||
// xyflip() text("Foobar", size=20, halign="center");
|
||||
//
|
||||
// Example:
|
||||
// left(10) frame_ref();
|
||||
// right(10) xyflip() frame_ref();
|
||||
//
|
||||
// Example:
|
||||
// xyflip(cp=-15) frame_ref();
|
||||
//
|
||||
// Example:
|
||||
// xyflip(cp=[10,10,10]) frame_ref();
|
||||
//
|
||||
// Example: Called as Function for a 3D matrix
|
||||
// mat = xyflip();
|
||||
// multmatrix(mat) frame_ref();
|
||||
//
|
||||
// Example(2D): Called as Function for a 2D matrix
|
||||
// mat = xyflip(planar=true);
|
||||
// multmatrix(mat) text("Foobar", size=20, halign="center");
|
||||
module xyflip(p, cp=0, planar) {
|
||||
assert(is_undef(p), "Module form `xyflip()` does not accept p= argument.");
|
||||
assert(is_undef(planar), "Module form `xyflip()` does not accept planar= argument.");
|
||||
mat = xyflip(cp=cp);
|
||||
multmatrix(mat) children();
|
||||
}
|
||||
|
||||
function xyflip(p, cp=0, planar=false) =
|
||||
assert(is_finite(cp) || is_vector(cp))
|
||||
let(
|
||||
v = unit([-1,1,0]),
|
||||
n = planar? point2d(v) : v
|
||||
)
|
||||
cp == 0 || cp==[0,0,0]? mirror(n, p=p) :
|
||||
let(
|
||||
cp = is_finite(cp)? n * cp :
|
||||
is_vector(cp)? assert(len(cp) == len(n)) cp :
|
||||
assert(is_finite(cp) || is_vector(cp)),
|
||||
mat = move(cp) * mirror(n) * move(-cp)
|
||||
) is_undef(p)? mat : apply(mat, p);
|
||||
|
||||
|
||||
// Function&Module: xzflip()
|
||||
//
|
||||
// Usage: As Module
|
||||
// xzflip([cp]) ...
|
||||
// Usage: As Function
|
||||
// pt = xzflip([cp], p);
|
||||
// Usage: Get Affine Matrix
|
||||
// pt = xzflip([cp]);
|
||||
//
|
||||
// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
|
||||
// See Also: mirror(), xflip(), yflip(), zflip(), xyflip(), yzflip(), affine2d_mirror(), affine3d_mirror()
|
||||
//
|
||||
// Description:
|
||||
// Mirrors/reflects across the origin [0,0,0], along the reflection plane where X=Y. If `cp` is given, the reflection plane passes through that point
|
||||
// * Called as the built-in module, mirrors all children across the line/plane.
|
||||
// * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
|
||||
// * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
|
||||
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
|
||||
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
|
||||
// * Called as a function without a `p` argument, returns the affine3d 4x4 mirror matrix.
|
||||
//
|
||||
// Arguments:
|
||||
// p = If given, the point, path, patch, or VNF to mirror. Function use only.
|
||||
// cp = The centerpoint of the plane of reflection, given either as a point, or as a scalar distance away from the origin.
|
||||
//
|
||||
// Example:
|
||||
// left(10) frame_ref();
|
||||
// right(10) xzflip() frame_ref();
|
||||
//
|
||||
// Example:
|
||||
// xzflip(cp=-15) frame_ref();
|
||||
//
|
||||
// Example:
|
||||
// xzflip(cp=[10,10,10]) frame_ref();
|
||||
//
|
||||
// Example: Called as Function
|
||||
// mat = xzflip();
|
||||
// multmatrix(mat) frame_ref();
|
||||
module xzflip(p, cp=0) {
|
||||
assert(is_undef(p), "Module form `xzflip()` does not accept p= argument.");
|
||||
mat = xzflip(cp=cp);
|
||||
multmatrix(mat) children();
|
||||
}
|
||||
|
||||
function xzflip(p, cp=0) =
|
||||
assert(is_finite(cp) || is_vector(cp))
|
||||
let( n = unit([-1,0,1]) )
|
||||
cp == 0 || cp==[0,0,0]? mirror(n, p=p) :
|
||||
let(
|
||||
cp = is_finite(cp)? n * cp :
|
||||
is_vector(cp,3)? cp :
|
||||
assert(is_finite(cp) || is_vector(cp,3)),
|
||||
mat = move(cp) * mirror(n) * move(-cp)
|
||||
) is_undef(p)? mat : apply(mat, p);
|
||||
|
||||
|
||||
// Function&Module: yzflip()
|
||||
//
|
||||
// Usage: As Module
|
||||
// yzflip([x=]) ...
|
||||
// Usage: As Function
|
||||
// pt = yzflip(p, [x=]);
|
||||
// Usage: Get Affine Matrix
|
||||
// pt = yzflip([x=]);
|
||||
//
|
||||
// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
|
||||
// See Also: mirror(), xflip(), yflip(), zflip(), xyflip(), xzflip(), affine2d_mirror(), affine3d_mirror()
|
||||
//
|
||||
// Description:
|
||||
// Mirrors/reflects across the origin [0,0,0], along the reflection plane where X=Y. If `cp` is given, the reflection plane passes through that point
|
||||
// * Called as the built-in module, mirrors all children across the line/plane.
|
||||
// * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
|
||||
// * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
|
||||
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
|
||||
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
|
||||
// * Called as a function without a `p` argument, returns the affine3d 4x4 mirror matrix.
|
||||
//
|
||||
// Arguments:
|
||||
// p = If given, the point, path, patch, or VNF to mirror. Function use only.
|
||||
// cp = The centerpoint of the plane of reflection, given either as a point, or as a scalar distance away from the origin.
|
||||
//
|
||||
// Example:
|
||||
// left(10) frame_ref();
|
||||
// right(10) yzflip() frame_ref();
|
||||
//
|
||||
// Example:
|
||||
// yzflip(cp=-15) frame_ref();
|
||||
//
|
||||
// Example:
|
||||
// yzflip(cp=[10,10,10]) frame_ref();
|
||||
//
|
||||
// Example: Called as Function
|
||||
// mat = yzflip();
|
||||
// multmatrix(mat) frame_ref();
|
||||
module yzflip(p, cp=0) {
|
||||
assert(is_undef(p), "Module form `yzflip()` does not accept p= argument.");
|
||||
mat = yzflip(cp=cp);
|
||||
multmatrix(mat) children();
|
||||
}
|
||||
|
||||
function yzflip(p, cp=0) =
|
||||
assert(is_finite(cp) || is_vector(cp))
|
||||
let( n = unit([0,-1,1]) )
|
||||
cp == 0 || cp==[0,0,0]? mirror(n, p=p) :
|
||||
let(
|
||||
cp = is_finite(cp)? n * cp :
|
||||
is_vector(cp,3)? cp :
|
||||
assert(is_finite(cp) || is_vector(cp,3)),
|
||||
mat = move(cp) * mirror(n) * move(-cp)
|
||||
) is_undef(p)? mat : apply(mat, p);
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Section: Other Transformations
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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`.
|
||||
function triangulate_face(points, face) =
|
||||
let(
|
||||
points = path3d(points),
|
||||
face = deduplicate_indexed(points,face),
|
||||
count = len(face)
|
||||
)
|
||||
|
@ -158,21 +159,21 @@ function triangulate_face(points, face) =
|
|||
(clipable_ear)? // There is no point inside the ear.
|
||||
is_only_noncolinear_vertex(points, face, cv)?
|
||||
// In the point&line degeneracy clip to somewhere in the middle of the line.
|
||||
flatten([
|
||||
triangulate_face(points, select(face, cv, (cv+2)%count)),
|
||||
triangulate_face(points, select(face, (cv+2)%count, cv))
|
||||
])
|
||||
concat(
|
||||
triangulate_face(points, select(face, cv, (cv+2)%count)),
|
||||
triangulate_face(points, select(face, (cv+2)%count, cv))
|
||||
)
|
||||
:
|
||||
// Otherwise the ear is safe to clip.
|
||||
flatten([
|
||||
[select(face, pv, nv)],
|
||||
triangulate_face(points, select(face, nv, pv))
|
||||
])
|
||||
[
|
||||
select(face, pv, nv),
|
||||
each triangulate_face(points, select(face, nv, pv))
|
||||
]
|
||||
: // 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, diagonal_point, cv))
|
||||
]);
|
||||
);
|
||||
|
||||
|
||||
// Function: triangulate_faces()
|
||||
|
|
54
vectors.scad
54
vectors.scad
|
@ -132,6 +132,28 @@ function v_lookup(x, v) =
|
|||
lerp(lo,hi,u);
|
||||
|
||||
|
||||
// Function: pointlist_bounds()
|
||||
// Usage:
|
||||
// pt_pair = pointlist_bounds(pts);
|
||||
// Topics: Geometry, Bounding Boxes, Bounds
|
||||
// Description:
|
||||
// Finds the bounds containing all the points in `pts` which can be a list of points in any dimension.
|
||||
// Returns a list of two items: a list of the minimums and a list of the maximums. For example, with
|
||||
// 3d points `[[MINX, MINY, MINZ], [MAXX, MAXY, MAXZ]]`
|
||||
// Arguments:
|
||||
// pts = List of points.
|
||||
function pointlist_bounds(pts) =
|
||||
assert(is_path(pts,dim=undef,fast=true) , "Invalid pointlist." )
|
||||
let(
|
||||
select = ident(len(pts[0])),
|
||||
spread = [
|
||||
for(i=[0:len(pts[0])-1])
|
||||
let( spreadi = pts*select[i] )
|
||||
[ min(spreadi), max(spreadi) ]
|
||||
]
|
||||
) transpose(spread);
|
||||
|
||||
|
||||
// Function: unit()
|
||||
// Usage:
|
||||
// unit(v, [error]);
|
||||
|
@ -241,9 +263,41 @@ function vector_axis(v1,v2=undef,v3=undef) =
|
|||
|
||||
|
||||
|
||||
|
||||
// Section: Vector Searching
|
||||
|
||||
|
||||
|
||||
// Function: closest_point()
|
||||
// Usage:
|
||||
// index = closest_point(pt, points);
|
||||
// Topics: Geometry, Points, Distance
|
||||
// Description:
|
||||
// Given a list of `points`, finds the index of the closest point to `pt`.
|
||||
// Arguments:
|
||||
// pt = The point to find the closest point to.
|
||||
// points = The list of points to search.
|
||||
function closest_point(pt, points) =
|
||||
assert( is_vector(pt), "Invalid point." )
|
||||
assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
|
||||
min_index([for (p=points) norm(p-pt)]);
|
||||
|
||||
|
||||
// Function: furthest_point()
|
||||
// Usage:
|
||||
// index = furthest_point(pt, points);
|
||||
// Topics: Geometry, Points, Distance
|
||||
// Description:
|
||||
// Given a list of `points`, finds the index of the furthest point from `pt`.
|
||||
// Arguments:
|
||||
// pt = The point to find the farthest point from.
|
||||
// points = The list of points to search.
|
||||
function furthest_point(pt, points) =
|
||||
assert( is_vector(pt), "Invalid point." )
|
||||
assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
|
||||
max_index([for (p=points) norm(p-pt)]);
|
||||
|
||||
|
||||
// Function: vector_search()
|
||||
// Usage:
|
||||
// indices = vector_search(query, r, target);
|
||||
|
|
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],
|
||||
facepolys = [for (face=vnf[1]) select(verts,face)],
|
||||
splits = axis=="X"?
|
||||
split_polygons_at_each_y(facepolys, bend_at) :
|
||||
split_polygons_at_each_x(facepolys, bend_at),
|
||||
_split_polygons_at_each_y(facepolys, bend_at) :
|
||||
_split_polygons_at_each_x(facepolys, bend_at),
|
||||
newtris = _triangulate_planar_convex_polygons(splits),
|
||||
bent_faces = [
|
||||
for (tri = newtris) [
|
||||
|
@ -712,6 +712,111 @@ function vnf_bend(vnf,r,d,axis="Z") =
|
|||
) vnf_add_faces(faces=bent_faces);
|
||||
|
||||
|
||||
|
||||
function _split_polygon_at_x(poly, x) =
|
||||
let(
|
||||
xs = subindex(poly,0)
|
||||
) (min(xs) >= x || max(xs) <= x)? [poly] :
|
||||
let(
|
||||
poly2 = [
|
||||
for (p = pair(poly,true)) each [
|
||||
p[0],
|
||||
if(
|
||||
(p[0].x < x && p[1].x > x) ||
|
||||
(p[1].x < x && p[0].x > x)
|
||||
) let(
|
||||
u = (x - p[0].x) / (p[1].x - p[0].x)
|
||||
) [
|
||||
x, // Important for later exact match tests
|
||||
u*(p[1].y-p[0].y)+p[0].y,
|
||||
u*(p[1].z-p[0].z)+p[0].z,
|
||||
]
|
||||
]
|
||||
],
|
||||
out1 = [for (p = poly2) if(p.x <= x) p],
|
||||
out2 = [for (p = poly2) if(p.x >= x) p],
|
||||
out3 = [
|
||||
if (len(out1)>=3) each split_path_at_self_crossings(out1),
|
||||
if (len(out2)>=3) each split_path_at_self_crossings(out2),
|
||||
],
|
||||
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
|
||||
) out;
|
||||
|
||||
|
||||
function _split_polygon_at_y(poly, y) =
|
||||
let(
|
||||
ys = subindex(poly,1)
|
||||
) (min(ys) >= y || max(ys) <= y)? [poly] :
|
||||
let(
|
||||
poly2 = [
|
||||
for (p = pair(poly,true)) each [
|
||||
p[0],
|
||||
if(
|
||||
(p[0].y < y && p[1].y > y) ||
|
||||
(p[1].y < y && p[0].y > y)
|
||||
) let(
|
||||
u = (y - p[0].y) / (p[1].y - p[0].y)
|
||||
) [
|
||||
u*(p[1].x-p[0].x)+p[0].x,
|
||||
y, // Important for later exact match tests
|
||||
u*(p[1].z-p[0].z)+p[0].z,
|
||||
]
|
||||
]
|
||||
],
|
||||
out1 = [for (p = poly2) if(p.y <= y) p],
|
||||
out2 = [for (p = poly2) if(p.y >= y) p],
|
||||
out3 = [
|
||||
if (len(out1)>=3) each split_path_at_self_crossings(out1),
|
||||
if (len(out2)>=3) each split_path_at_self_crossings(out2),
|
||||
],
|
||||
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
|
||||
) out;
|
||||
|
||||
|
||||
|
||||
/// Function: _split_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()
|
||||
// Usage: As Function
|
||||
// fails = vnf_validate(vnf);
|
||||
|
|
Loading…
Reference in a new issue