From 9bb22dc7c53b468d53c0d9b6d0f36041ef2d3253 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Thu, 7 Oct 2021 21:31:58 -0400 Subject: [PATCH] doc tweaks --- drawing.scad | 8 +-- geometry.scad | 179 ++++++++++++++++++++++++++++++-------------------- hull.scad | 2 +- paths.scad | 53 +++++++-------- regions.scad | 42 +++++++++++- skin.scad | 6 +- 6 files changed, 185 insertions(+), 105 deletions(-) diff --git a/drawing.scad b/drawing.scad index 72bf323..52a9789 100644 --- a/drawing.scad +++ b/drawing.scad @@ -103,7 +103,7 @@ // stroke(path, width=3, joints="diamond", endcaps="arrow2", endcap_angle=0, endcap_width=5, joint_angle=0, joint_width=5); // Example(2D): Joints and Endcaps // path = [for (a=[0:30:360]) [a-180, 60*sin(a)]]; -// stroke(path, width=3, joints="dot", endcaps="arrow2", joint_angle=0); +// stroke(path, width=8, joints="dot", endcaps="arrow2"); // Example(2D): Custom Endcap Shapes // path = [[0,100], [100,100], [200,0], [100,-100], [100,0]]; // arrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]]; @@ -852,13 +852,13 @@ function _normal_segment(p1,p2) = // stroke(path,width=.2); // Example(2DMed): pentagonal spiral // path = turtle(["move","left",360/5,"addlength",1],repeat=50); -// stroke(path,width=.2); +// stroke(path,width=.7); // Example(2DMed): yet another spiral, without using `repeat` // path = turtle(concat(["angle",71],flatten(repeat(["move","left","addlength",1],50)))); -// stroke(path,width=.2); +// stroke(path,width=.7); // Example(2DMed): The previous spiral grows linearly and eventually intersects itself. This one grows geometrically and does not. // path = turtle(["move","left",71,"scale",1.05],repeat=50); -// stroke(path,width=.05); +// stroke(path,width=.15); // Example(2D): Koch Snowflake // function koch_unit(depth) = // depth==0 ? ["move"] : diff --git a/geometry.scad b/geometry.scad index b9fd66c..8dae42f 100644 --- a/geometry.scad +++ b/geometry.scad @@ -30,12 +30,12 @@ function is_point_on_line(point, line, bounded=false, eps=EPSILON) = point_line_distance(point, line, bounded)eps*max(norm(line[1]),norm(line[0])); @@ -45,16 +45,16 @@ function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps /// Internal Function: point_left_of_line2d() -// Usage: -// pt = point_left_of_line2d(point, line); +/// Usage: +/// pt = point_left_of_line2d(point, line); /// 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. -// Return <0 if point is right of the line. -// Arguments: -// point = The point to check position of. -// line = Array of two points forming the line segment to test against. +/// Description: +/// 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 right of the line. +/// 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) = assert( is_vector(point,2) && is_vector(line*point, 2), "Improper input." ) cross(line[0]-point, line[1]-line[0]); @@ -372,7 +372,7 @@ function is_coplanar(points, eps=EPSILON) = // Description: // Generates the normalized cartesian equation of a plane from three 3d points. // Returns [A,B,C,D] where Ax + By + Cz = D is the equation of a plane. -// Returns [], if the points are collinear. +// Returns undef, if the points are collinear. // Arguments: // p1 = The first point on the plane. // p2 = The second point on the plane. @@ -383,7 +383,7 @@ function plane3pt(p1, p2, p3) = let( crx = cross(p3-p1, p2-p1), nrm = norm(crx) - ) approx(nrm,0) ? [] : + ) approx(nrm,0) ? undef : concat(crx, crx*p1)/nrm; @@ -480,24 +480,23 @@ function _covariance_evec_eval(points) = // Description: // Given a list of 3 or more coplanar 3D points, returns the coefficients of the normalized cartesian equation of a plane, // that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane and norm([A,B,C])=1. -// If `fast` is false and the points in the list are collinear or not coplanar, then [] is returned. -// If `fast` is true, the polygon coplanarity check is skipped and a best fitted plane is returned. +// If `fast` is false and the points in the list are collinear or not coplanar, then `undef` is returned. +// If `fast` is true, the polygon coplanarity check is skipped and a best fitting plane is returned. // Arguments: // points = The list of points to find the plane of. // fast = If true, don't verify the point coplanarity. Default: false // eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9) // Example(3D): -// xyzpath = rot(45, v=[-0.3,1,0], p=path3d(star(n=6,id=70,d=100), 70)); -// plane = plane_from_points(xyzpath); -// #stroke(xyzpath,closed=true); -// cp = centroid(xyzpath); -// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(); +// points = rot(45, v=[-0.3,1,0], p=path3d(random_points(25,2,scale=55,seed=47), 70)); +// plane = plane_from_points(points); +// #move_copies(points)sphere(d=3); +// cp = mean(points); +// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(50); function plane_from_points(points, fast=false, eps=EPSILON) = assert( is_path(points,dim=3), "Improper 3d point list." ) assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." ) len(points) == 3 - ? let( plane = plane3pt(points[0],points[1],points[2]) ) - plane==[] ? [] : plane + ? plane3pt(points[0],points[1],points[2]) : let( covmix = _covariance_evec_eval(points), pm = covmix[0], @@ -517,7 +516,7 @@ function plane_from_points(points, fast=false, eps=EPSILON) = // Given a 3D planar polygon, returns the normalized cartesian equation of its plane. // Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane where norm([A,B,C])=1. // If not all the points in the polygon are coplanar, then [] is returned. -// If `fast` is false and the points in the list are collinear or not coplanar, then [] is returned. +// If `fast` is false and the points in the list are collinear or not coplanar, then `undef` is returned. // if `fast` is true, then the coplanarity test is skipped and a plane passing through 3 non-collinear arbitrary points is returned. // Arguments: // poly = The planar 3D polygon to find the plane of. @@ -526,20 +525,20 @@ function plane_from_points(points, fast=false, eps=EPSILON) = // Example(3D): // xyzpath = rot(45, v=[0,1,0], p=path3d(star(n=5,step=2,d=100), 70)); // plane = plane_from_polygon(xyzpath); -// #stroke(xyzpath,closed=true); -// cp = centroid(xyzpath); -// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(); +// #stroke(xyzpath,closed=true,width=3); +// cp = polygon_centroid(xyzpath); +// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(45); 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." ) let( poly_normal = polygon_normal(poly) ) - is_undef(poly_normal) ? [] : + is_undef(poly_normal) ? undef : let( plane = plane_from_normal(poly_normal, poly[0]) ) - fast? plane: are_points_on_plane(poly, plane, eps=eps)? plane: []; + fast? plane: are_points_on_plane(poly, plane, eps=eps)? plane: undef; // Function: plane_normal() @@ -876,11 +875,11 @@ function plane_line_angle(plane, line) = // 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)) { +// color("red") move_copies(points) sphere(d=4,$fn=12); +// color("blue") move_copies(proj) sphere(d=4,$fn=12); +// move(polygon_centroid(proj)) { // rot(from=UP,to=plane_normal(plane)) { -// anchor_arrow(30); +// anchor_arrow(50); // %cube([120,150,0.1],center=true); // } // } @@ -1249,40 +1248,40 @@ function circle_point_tangents(r, d, cp, pt) = // r2 = Radius of the second circle. // d1 = Diameter of the first circle. // d2 = Diameter of the second circle. -// Example(2D): Four tangents, first in green, second in black, third in blue, last in red. +// Example(2D,NoAxes): Four tangents, first in green, second in black, third in blue, last in red. // $fn=32; // c1 = [3,4]; r1 = 2; // c2 = [7,10]; r2 = 3; // pts = circle_circle_tangents(c1,r1,c2,r2); -// move(c1) stroke(circle(r=r1), width=.1, closed=true); -// move(c2) stroke(circle(r=r2), width=.1, closed=true); +// move(c1) stroke(circle(r=r1), width=0.2, closed=true); +// move(c2) stroke(circle(r=r2), width=0.2, closed=true); // colors = ["green","black","blue","red"]; -// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1); -// Example(2D): Circles overlap so only exterior tangents exist. +// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=0.2); +// Example(2D,NoAxes): Circles overlap so only exterior tangents exist. // $fn=32; // c1 = [4,4]; r1 = 3; // c2 = [7,7]; r2 = 2; // pts = circle_circle_tangents(c1,r1,c2,r2); -// move(c1) stroke(circle(r=r1), width=.1, closed=true); -// move(c2) stroke(circle(r=r2), width=.1, closed=true); +// move(c1) stroke(circle(r=r1), width=0.2, closed=true); +// move(c2) stroke(circle(r=r2), width=0.2, closed=true); // colors = ["green","black","blue","red"]; -// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1); -// Example(2D): Circles are tangent. Only exterior tangents are returned. The degenerate internal tangent is not returned. +// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=0.2); +// Example(2D,NoAxes): Circles are tangent. Only exterior tangents are returned. The degenerate internal tangent is not returned. // $fn=32; // c1 = [4,4]; r1 = 4; // c2 = [4,10]; r2 = 2; // pts = circle_circle_tangents(c1,r1,c2,r2); -// move(c1) stroke(circle(r=r1), width=.1, closed=true); -// move(c2) stroke(circle(r=r2), width=.1, closed=true); +// move(c1) stroke(circle(r=r1), width=0.2, closed=true); +// move(c2) stroke(circle(r=r2), width=0.2, closed=true); // colors = ["green","black","blue","red"]; -// for(i=[0:1:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1); -// Example(2D): One circle is inside the other: no tangents exist. If the interior circle is tangent the single degenerate tangent will not be returned. +// for(i=[0:1:len(pts)-1]) color(colors[i]) stroke(pts[i],width=0.2); +// Example(2D,NoAxes): One circle is inside the other: no tangents exist. If the interior circle is tangent the single degenerate tangent will not be returned. // $fn=32; // c1 = [4,4]; r1 = 4; // c2 = [5,5]; r2 = 2; // pts = circle_circle_tangents(c1,r1,c2,r2); -// move(c1) stroke(circle(r=r1), width=.1, closed=true); -// move(c2) stroke(circle(r=r2), width=.1, closed=true); +// move(c1) stroke(circle(r=r1), width=0.2, closed=true); +// move(c2) stroke(circle(r=r2), width=0.2, closed=true); // echo(pts); // Returns [] function circle_circle_tangents(c1,r1,c2,r2,d1,d2) = assert( is_path([c1,c2],dim=2), "Invalid center point(s)." ) @@ -1359,7 +1358,8 @@ function noncollinear_triple(points,error=true,eps=EPSILON) = // Topics: Geometry, Polygons, Area // Description: // Given a 2D or 3D simple planar polygon, returns the area of that polygon. -// If the polygon is self-intersecting or non-planar, the result is `undef.` +// If the polygon is non-planar the result is `undef.` If the polygon is self-intersecting +// then the return will be a meaningless number. // When `signed` is true and the polygon is 2d, a signed area is returned: a positive area indicates a counter-clockwise polygon. // The area of 3d polygons is always nonnegative. // Arguments: @@ -1372,8 +1372,8 @@ function polygon_area(poly, signed=false) = ? let( total = sum([for(i=[1:1:len(poly)-2]) cross(poly[i]-poly[0],poly[i+1]-poly[0]) ])/2 ) signed ? total : abs(total) : let( plane = plane_from_polygon(poly) ) - plane==[]? undef : - let( + is_undef(plane) ? undef : + let( f=echo(plane=plane), n = plane_normal(plane), total = -sum([ for(i=[1:1:len(poly)-2]) @@ -1383,9 +1383,9 @@ function polygon_area(poly, signed=false) = signed ? total : abs(total); -// Function: centroid() +// Function: polygon_centroid() // Usage: -// cpt = centroid(poly); +// cpt = polygon_centroid(poly); // Topics: Geometry, Polygons, Centroid // Description: // Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid. @@ -1395,13 +1395,13 @@ function polygon_area(poly, signed=false) = // 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) = +function polygon_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." ) + let( plane = plane_from_points(poly, fast=false) ,dd=echo(p=plane)) + assert(!is_undef(plane), "The polygon must be planar." ) plane_normal(plane), v0 = poly[0] , val = sum([ @@ -1591,10 +1591,10 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) = // poly = Array of vertices for the polygon. // ind = A list indexing the vertices of the polygon in `poly`. // eps = A maximum tolerance in geometrical tests. Default: EPSILON -// Example(2D): +// Example(2D,NoAxes): // poly = star(id=10, od=15,n=11); // tris = polygon_triangulate(poly); -// for(tri=tris) stroke(select(poly,tri), width=.1, closed=true); +// for(tri=tris) stroke(select(poly,tri), width=.2, closed=true); // Example(3D): // include // vnf = regular_polyhedron_info(name="dodecahedron",side=5,info="vnf"); @@ -1696,6 +1696,7 @@ function _is_cw2(a,b,c,eps=EPSILON) = cross(a-c,b-c)0) || valid_range(angles), + "The `angle` parameter must be a range or a non void list of numbers.") + let( // alignments is a vector of entries of the form: [polygon, error] + alignments = [ + for(angle=angles) + reindex_polygon( + reference, + zrot(angle,p=poly,cp=cp), + return_error=true + ) + ], + scores = subindex(alignments,1), + minscore = min(scores), + minind = [for(i=idx(scores)) if (scores[i] (i==0 && !closed? -eps: 0) +// && isect[1]> (i==0 && !closed? -eps: 0) // Apparently too strict + && isect[1]>=-eps && isect[1]<= 1+eps && isect[2]> 0 && isect[2]<= 1+eps) @@ -380,9 +381,9 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length // Example(2D): // path = pentagon(d=100); // spath = subdivide_long_segments(path, 10, closed=true); -// stroke(path); -// color("lightgreen") move_copies(path) circle(d=5,$fn=12); -// color("blue") move_copies(spath) circle(d=3,$fn=12); +// stroke(path,width=2,closed=true); +// color("red") move_copies(path) circle(d=9,$fn=12); +// color("blue") move_copies(spath) circle(d=5,$fn=12); function subdivide_long_segments(path, maxlen, closed=false) = assert(is_path(path)) assert(is_finite(maxlen)) @@ -496,20 +497,20 @@ function path_closest_point(path, pt) = // path = path to find the tagent vectors for // closed = set to true of the path is closed. Default: false // uniform = set to false to correct for non-uniform sampling. Default: true -// Example(3D): A shape with non-uniform sampling gives distorted derivatives that may be undesirable. Note that derivatives tilt towards the long edges of the rectangle. +// Example(2D): A shape with non-uniform sampling gives distorted derivatives that may be undesirable. Note that derivatives tilt towards the long edges of the rectangle. // rect = square([10,3]); // tangents = path_tangents(rect,closed=true); -// stroke(rect,closed=true, width=0.1); +// stroke(rect,closed=true, width=0.25); // color("purple") // for(i=[0:len(tangents)-1]) -// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.1, endcap2="arrow2"); -// Example(3D): Setting uniform to false corrects the distorted derivatives for this example: +// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.25, endcap2="arrow2"); +// Example(2D): Setting uniform to false corrects the distorted derivatives for this example: // rect = square([10,3]); // tangents = path_tangents(rect,closed=true,uniform=false); -// stroke(rect,closed=true, width=0.1); +// stroke(rect,closed=true, width=0.25); // color("purple") // for(i=[0:len(tangents)-1]) -// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.1, endcap2="arrow2"); +// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.25, endcap2="arrow2"); function path_tangents(path, closed=false, uniform=true) = assert(is_path(path)) !uniform ? [for(t=deriv(path,closed=closed, h=path_segment_lengths(path,closed))) unit(t)] @@ -881,10 +882,10 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) = // path = The original path to split. // cutdist = Distance or list of distances where path is cut // closed = If true, treat the path as a closed polygon. -// Example(2D): +// Example(2D,NoAxes): // path = circle(d=100); // segs = path_cut(path, [50, 200], closed=true); -// rainbow(segs) stroke($item); +// rainbow(segs) stroke($item, endcaps="butt", width=3); function path_cut(path,cutdist,closed) = is_num(cutdist) ? path_cut(path,[cutdist],closed) : assert(is_vector(cutdist)) @@ -947,10 +948,10 @@ function _cut_to_seg_u_form(pathcut, path, closed) = // path = The path to split up. // closed = If true, treat path as a closed polygon. Default: true // eps = Acceptable variance. Default: `EPSILON` (1e-9) -// Example(2D): +// Example(2D,NoAxes): // path = [ [-100,100], [0,-50], [100,100], [100,-100], [0,50], [-100,-100] ]; // paths = split_path_at_self_crossings(path); -// rainbow(paths) stroke($item, closed=false, width=2); +// rainbow(paths) stroke($item, closed=false, width=3); function split_path_at_self_crossings(path, closed=true, eps=EPSILON) = let( path = cleanup_path(path, eps=eps), @@ -1011,25 +1012,25 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) = // path = The path to split up. // nonzero = If true use the nonzero method for checking if a point is in a polygon. Otherwise use the even-odd method. Default: false // eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9) -// Example(2D): This cross-crossing polygon breaks up into its 3 components (regardless of the value of nonzero). +// Example(2D,NoAxes): This cross-crossing polygon breaks up into its 3 components (regardless of the value of nonzero). // path = [ // [-100,100], [0,-50], [100,100], // [100,-100], [0,50], [-100,-100] // ]; // splitpaths = polygon_parts(path); // rainbow(splitpaths) stroke($item, closed=true, width=3); -// Example(2D): With nonzero=false you get even-odd mode which matches OpenSCAD, so the pentagram breaks apart into its five points. +// Example(2D,NoAxes): With nonzero=false you get even-odd mode which matches OpenSCAD, so the pentagram breaks apart into its five points. // pentagram = turtle(["move",100,"left",144], repeat=4); // left(100)polygon(pentagram); // rainbow(polygon_parts(pentagram,nonzero=false)) -// stroke($item,closed=true); -// Example(2D): With nonzero=true you get only the outer perimeter. You can use this to create the polygon using the nonzero method, which is not supported by OpenSCAD. +// stroke($item,closed=true,width=2.5); +// Example(2D,NoAxes): With nonzero=true you get only the outer perimeter. You can use this to create the polygon using the nonzero method, which is not supported by OpenSCAD. // pentagram = turtle(["move",100,"left",144], repeat=4); // outside = polygon_parts(pentagram,nonzero=true); // left(100)region(outside); // rainbow(outside) -// stroke($item,closed=true); -// Example(2D): +// stroke($item,closed=true,width=2.5); +// Example(2D,NoAxes): // N=12; // ang=360/N; // sr=10; @@ -1042,19 +1043,19 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) = // "move", sr]); // stroke(path, width=.3); // right(20)rainbow(polygon_parts(path)) polygon($item); -// Example(2D): overlapping path segments disappear +// Example(2D,NoAxes): overlapping path segments disappear // path = [[0,0], [10,0], [10,10], [0,10],[0,20], [20,10],[10,10], [0,10],[0,0]]; // stroke(path,width=0.3); // right(22)stroke(polygon_parts(path)[0], width=0.3, closed=true); -// Example(2D): Path segments disappear outside as well +// Example(2D,NoAxes): Path segments disappear outside as well // path = turtle(["repeat", 3, ["move", 17, "left", "move", 10, "left", "move", 7, "left", "move", 10, "left"]]); -// back(2)stroke(path,width=.3); -// fwd(12)rainbow(polygon_parts(path)) polygon($item); -// Example(2D): This shape has six components +// back(2)stroke(path,width=.5); +// fwd(12)rainbow(polygon_parts(path)) stroke($item, closed=true, width=0.5); +// Example(2D,NoAxes): This shape has six components // path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 17, "left"]]); // polygon(path); // right(22)rainbow(polygon_parts(path)) polygon($item); -// Example(2D): when the loops of the shape overlap then nonzero gives a different result than the even-odd method. +// Example(2D,NoAxes): When the loops of the shape overlap then nonzero gives a different result than the even-odd method. // path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 10, "left"]]); // polygon(path); // right(27)rainbow(polygon_parts(path)) polygon($item); diff --git a/regions.scad b/regions.scad index a4c66ba..d66570a 100644 --- a/regions.scad +++ b/regions.scad @@ -183,7 +183,7 @@ function __are_regions_equal(region1, region2, i) = /// region = Region to test for crossings of. /// closed = If true, treat path as a closed polygon. Default: true /// eps = Acceptable variance. Default: `EPSILON` (1e-9) -function _path_region_intersections(path, region, closed=true, eps=EPSILON) = +function old_path_region_intersections(path, region, closed=true, eps=EPSILON) = let( pathclosed = closed && !is_closed_path(path), pathlen = len(path), @@ -218,6 +218,46 @@ function _path_region_intersections(path, region, closed=true, eps=EPSILON) = ); +// find the intersection points of a path and the polygons of a +// region; only crossing intersections are caught, no collinear +// intersection is returned. +function _path_region_intersections(path, region, closed=true, eps=EPSILON) = + let( path = closed ? close_path(path,eps=eps) : path ) + _sort_vectors( + [for(si = [0:1:len(path)-2]) let( + a1 = path[si], + a2 = path[si+1], + nrm = norm(a1-a2) + ) + if( nrm>eps ) let( // ignore zero-length path edges + seg_normal = [-(a2-a1).y, (a2-a1).x]/nrm, + ref = a1*seg_normal + ) + // `signs[j]` is the sign of the signed distance from + // poly vertex j to the line [a1,a2] where near zero + // distances are snapped to zero; poly edges + // with equal signs at its vertices cannot intersect + // the path edge [a1,a2] or they are collinear and + // further tests can be discarded. + for(poly=region) let( + poly = close_path(poly), + signs = [for(v=poly*seg_normal) v-ref> eps ? 1 : v-ref<-eps ? -1 : 0] + ) + if(max(signs)>=0 && min(signs)<=0 ) // some edge edge intersects line [a1,a2] + for(j=[0:1:len(poly)-2]) + if( signs[j]!=signs[j+1] ) let( // exclude non-crossing and collinear segments + b1 = poly[j], + b2 = poly[j+1], + isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps) + ) + if ( isect + && isect[1]> (si==0 && !closed? -eps: 0) + && isect[1]<= 1+eps + && isect[2]> 0 + && isect[2]<= 1+eps ) + [si,isect[1]] + ]); + // Function: split_path_at_region_crossings() // Usage: diff --git a/skin.scad b/skin.scad index 3854462..e2ad45a 100644 --- a/skin.scad +++ b/skin.scad @@ -276,12 +276,12 @@ // Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Another "distance" example: // off = [0,2]; // shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]); -// rshape = rot(180,cp=centroid(shape)+off, p=shape); +// rshape = rot(180,cp=polygon_centroid(shape)+off, p=shape); // skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15); // Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Slightly shifting the profile changes the optimal linkage // off = [0,1]; // shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]); -// rshape = rot(180,cp=centroid(shape)+off, p=shape); +// rshape = rot(180,cp=polygon_centroid(shape)+off, p=shape); // skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15); // Example(FlatSpin,VPD=444,VPT=[0,0,50]): This optimal solution doesn't look terrible: // prof1 = path3d([[-50,-50], [-50,50], [50,50], [25,25], [50,0], [25,-25], [50,-50]]); @@ -1589,7 +1589,7 @@ function _skin_tangent_match(poly1, poly2) = swap = len(poly1)>len(poly2), big = swap ? poly1 : poly2, small = swap ? poly2 : poly1, - curve_offset = centroid(small)-centroid(big), + curve_offset = polygon_centroid(small)-polygon_centroid(big), cutpts = [for(i=[0:len(small)-1]) _find_one_tangent(big, select(small,i,i+1),curve_offset=curve_offset)], shift = last(cutpts)+1, newbig = polygon_shift(big, shift),