remove polygon_shift, hide noncollinear_triple

modify glued circle to not produce duplicate points
This commit is contained in:
Adrian Mariano 2021-11-11 18:50:26 -05:00
parent ec87be11ec
commit 477dd55781
11 changed files with 61 additions and 89 deletions

View file

@ -1952,8 +1952,6 @@ module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tags="anchor-arrow") {
// Module: expose_anchors() // Module: expose_anchors()
// Usage: // Usage:
// expose_anchors(opacity) {child1() show_anchors(); child2() show_anchors(); ...} // expose_anchors(opacity) {child1() show_anchors(); child2() show_anchors(); ...}

View file

@ -153,7 +153,7 @@ function is_collinear(a, b, c, eps=EPSILON) =
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." ) assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
let( points = is_def(c) ? [a,b,c]: a ) let( points = is_def(c) ? [a,b,c]: a )
len(points)<3 ? true : len(points)<3 ? true :
noncollinear_triple(points,error=false,eps=eps) == []; _noncollinear_triple(points,error=false,eps=eps) == [];
// Function: point_line_distance() // Function: point_line_distance()
@ -429,7 +429,7 @@ function is_coplanar(points, eps=EPSILON) =
assert( is_path(points,dim=3) , "Input should be a list of 3D points." ) 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." ) assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative value." )
len(points)<=2 ? false len(points)<=2 ? false
: let( ip = noncollinear_triple(points,error=false,eps=eps) ) : let( ip = _noncollinear_triple(points,error=false,eps=eps) )
ip == [] ? false : ip == [] ? false :
let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]) ) let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]) )
_pointlist_greatest_distance(points,plane) < eps; _pointlist_greatest_distance(points,plane) < eps;
@ -850,7 +850,7 @@ function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps
) )
(len(inside)==0 ? undef : _merge_segments(inside, [inside[0]], eps)) (len(inside)==0 ? undef : _merge_segments(inside, [inside[0]], eps))
: // 3d case : // 3d case
let(indices = noncollinear_triple(poly)) let(indices = _noncollinear_triple(poly))
indices==[] ? undef : // Polygon is collinear indices==[] ? undef : // Polygon is collinear
let( let(
plane = plane3pt(poly[indices[0]], poly[indices[1]], poly[indices[2]]), plane = plane3pt(poly[indices[0]], poly[indices[1]], poly[indices[2]]),
@ -1384,25 +1384,22 @@ function circle_circle_tangents(c1,r1,c2,r2,d1,d2) =
// Section: Pointlists /// Internal Function: _noncollinear_triple()
/// Usage:
/// test = _noncollinear_triple(points);
// Function: noncollinear_triple() /// Topics: Geometry, Noncollinearity
// Usage: /// Description:
// test = noncollinear_triple(points); /// Finds the indices of three non-collinear points from the pointlist `points`.
// Topics: Geometry, Noncollinearity /// It selects two well separated points to define a line and chooses the third point
// Description: /// to be the point farthest off the line. The points do not necessarily having the
// Finds the indices of three non-collinear points from the pointlist `points`. /// same winding direction as the polygon so they cannot be used to determine the
// It selects two well separated points to define a line and chooses the third point /// winding direction or the direction of the normal.
// to be the point farthest off the line. The points do not necessarily having the /// If all points are collinear returns [] when `error=true` or an error otherwise .
// same winding direction as the polygon so they cannot be used to determine the /// Arguments:
// winding direction or the direction of the normal. /// points = List of input points.
// If all points are collinear returns [] when `error=true` or an error otherwise . /// error = Defines the behaviour for collinear input points. When `true`, produces an error, otherwise returns []. Default: `true`.
// Arguments: /// eps = Tolerance for collinearity test. Default: EPSILON.
// points = List of input points. function _noncollinear_triple(points,error=true,eps=EPSILON) =
// error = Defines the behaviour for collinear input points. When `true`, produces an error, otherwise returns []. Default: `true`.
// eps = Tolerance for collinearity test. Default: EPSILON.
function noncollinear_triple(points,error=true,eps=EPSILON) =
assert( is_path(points), "Invalid input points." ) assert( is_path(points), "Invalid input points." )
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." ) assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
len(points)<3 ? [] : len(points)<3 ? [] :
@ -1953,26 +1950,6 @@ function reverse_polygon(poly) =
[ poly[0], for(i=[len(poly)-1:-1:1]) poly[i] ]; [ poly[0], for(i=[len(poly)-1:-1:1]) poly[i] ];
// Function: polygon_shift()
// Usage:
// newpoly = polygon_shift(poly, i);
// Topics: Geometry, Polygons
// Description:
// Given a polygon `poly`, rotates the point ordering so that the first point in the polygon path is the one at index `i`.
// This is identical to `list_rotate` except that it checks for doubled endpoints and removed them if present.
// Arguments:
// poly = The list of points in the polygon path.
// i = The index of the point to shift to the front of the path.
// Example:
// polygon_shift([[3,4], [8,2], [0,2], [-4,0]], 2); // Returns [[0,2], [-4,0], [3,4], [8,2]]
function polygon_shift(poly, i) =
let(poly=force_path(poly,"poly"))
assert(is_path(poly), "Invalid polygon." )
list_rotate(cleanup_path(poly), i);
// Function: reindex_polygon() // Function: reindex_polygon()
// Usage: // Usage:
// newpoly = reindex_polygon(reference, poly); // newpoly = reindex_polygon(reference, poly);
@ -2021,7 +1998,7 @@ function reindex_polygon(reference, poly, return_error=false) =
[for(i=[0:N-1]) [for(i=[0:N-1])
norm(reference[i]-fixpoly[(i+k)%N]) ] ]*I, norm(reference[i]-fixpoly[(i+k)%N]) ] ]*I,
min_ind = min_index(val), min_ind = min_index(val),
optimal_poly = polygon_shift(fixpoly, min_ind) optimal_poly = list_rotate(fixpoly, min_ind)
) )
return_error? [optimal_poly, val[min_ind]] : return_error? [optimal_poly, val[min_ind]] :
optimal_poly; optimal_poly;
@ -2175,7 +2152,7 @@ function is_polygon_convex(poly,eps=EPSILON) =
? let( size = max([for(p=poly) norm(p-p0)]), tol=pow(size,2)*eps ) ? let( size = max([for(p=poly) norm(p-p0)]), tol=pow(size,2)*eps )
assert( size>eps, "The polygon is self-crossing or its points are collinear" ) assert( size>eps, "The polygon is self-crossing or its points are collinear" )
min(crosses) >=-tol || max(crosses)<=tol min(crosses) >=-tol || max(crosses)<=tol
: let( ip = noncollinear_triple(poly,error=false,eps=eps) ) : let( ip = _noncollinear_triple(poly,error=false,eps=eps) )
assert( ip!=[], "The points are collinear") assert( ip!=[], "The points are collinear")
let( let(
crx = cross(poly[ip[1]]-poly[ip[0]],poly[ip[2]]-poly[ip[1]]), crx = cross(poly[ip[1]]-poly[ip[0]],poly[ip[2]]-poly[ip[1]]),

View file

@ -166,7 +166,7 @@ function hull3d_faces(points) =
assert(is_path(points,3),"Invalid input to hull3d_faces") assert(is_path(points,3),"Invalid input to hull3d_faces")
len(points) < 3 ? count(len(points)) len(points) < 3 ? count(len(points))
: let ( // start with a single non-collinear triangle : let ( // start with a single non-collinear triangle
tri = noncollinear_triple(points, error=false) tri = _noncollinear_triple(points, error=false)
) )
tri==[] ? _hull_collinear(points) tri==[] ? _hull_collinear(points)
: let( : let(

View file

@ -497,7 +497,7 @@ function reverse(x) =
// Topics: List Handling // Topics: List Handling
// See Also: select(), reverse() // See Also: select(), reverse()
// Description: // Description:
// Rotates the contents of a list by `n` positions left. // Rotates the contents of a list by `n` positions left, so that list[n] becomes the first entry of the list.
// If `n` is negative, then the rotation is `abs(n)` positions to the right. // If `n` is negative, then the rotation is `abs(n)` positions to the right.
// If `list` is a string, then a string is returned with the characters rotates within the string. // If `list` is a string, then a string is returned with the characters rotates within the string.
// Arguments: // Arguments:

View file

@ -1132,7 +1132,7 @@ function os_mask(mask, out=false, extra,check_valid, quality, offset) =
) )
assert(len(origin_index)==1,"Cannot find origin in the mask") assert(len(origin_index)==1,"Cannot find origin in the mask")
let( let(
points = ([for(pt=polygon_shift(mask,origin_index[0])) [xfactor*max(pt.x,0),-max(pt.y,0)]]) points = ([for(pt=list_rotate(mask,origin_index[0])) [xfactor*max(pt.x,0),-max(pt.y,0)]])
) )
os_profile(deduplicate(move(-points[1],p=list_tail(points))), extra,check_valid,quality,offset); os_profile(deduplicate(move(-points[1],p=list_tail(points))), extra,check_valid,quality,offset);

View file

@ -367,7 +367,7 @@ function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false
each arc(N=steps, cp=p, r=rounding, start=a+180/n, angle=-360/n) each arc(N=steps, cp=p, r=rounding, start=a+180/n, angle=-360/n)
], ],
maxx_idx = max_index(column(path2,0)), maxx_idx = max_index(column(path2,0)),
path3 = polygon_shift(path2,maxx_idx) path3 = list_rotate(path2,maxx_idx)
) path3 ) path3
), ),
path = apply(mat, path4), path = apply(mat, path4),
@ -1009,7 +1009,7 @@ function teardrop2d(r, ang=45, cap_h, d, anchor=CENTER, spin=0) =
], closed=true ], closed=true
), ),
maxx_idx = max_index(column(path,0)), maxx_idx = max_index(column(path,0)),
path2 = polygon_shift(path,maxx_idx) path2 = list_rotate(path,maxx_idx)
) reorient(anchor,spin, two_d=true, path=path2, p=path2); ) reorient(anchor,spin, two_d=true, path=path2, p=path2);
@ -1051,20 +1051,23 @@ function glued_circles(r, spread=10, tangent=30, d, anchor=CENTER, spin=0) =
ea1 = 270+tangent, ea1 = 270+tangent,
lobearc = ea1-sa1, lobearc = ea1-sa1,
lobesegs = ceil(segs(r)*lobearc/360), lobesegs = ceil(segs(r)*lobearc/360),
lobestep = lobearc / lobesegs,
sa2 = 270-tangent, sa2 = 270-tangent,
ea2 = 270+tangent, ea2 = 270+tangent,
subarc = ea2-sa2, subarc = ea2-sa2,
arcsegs = ceil(segs(r2)*abs(subarc)/360), arcsegs = ceil(segs(r2)*abs(subarc)/360),
arcstep = subarc / arcsegs, // In the tangent zero case the inner curves are missing so we need to complete the two
path = concat( // outer curves. In the other case the inner curves are present and endpoint=false
[for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep) r * [cos(a),sin(a)] - cp1], // prevents point duplication.
tangent==0? [] : [for (i=[0:1:arcsegs]) let(a=ea2-i*arcstep+180) r2 * [cos(a),sin(a)] - cp2], path = tangent==0 ?
[for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep+180) r * [cos(a),sin(a)] + cp1], concat(arc(N=lobesegs+1, r=r, cp=-cp1, angle=[sa1,ea1]),
tangent==0? [] : [for (i=[0:1:arcsegs]) let(a=ea2-i*arcstep) r2 * [cos(a),sin(a)] + cp2] arc(N=lobesegs+1, r=r, cp=cp1, angle=[sa1+180,ea1+180]))
), :
concat(arc(N=lobesegs, r=r, cp=-cp1, angle=[sa1,ea1], endpoint=false),
[for(theta=lerpn(ea2+180,ea2-subarc+180,arcsegs,endpoint=false)) r2*[cos(theta),sin(theta)] - cp2],
arc(N=lobesegs, r=r, cp=cp1, angle=[sa1+180,ea1+180], endpoint=false),
[for(theta=lerpn(ea2,ea2-subarc,arcsegs,endpoint=false)) r2*[cos(theta),sin(theta)] + cp2]),
maxx_idx = max_index(column(path,0)), maxx_idx = max_index(column(path,0)),
path2 = reverse_polygon(polygon_shift(path,maxx_idx)) path2 = reverse_polygon(list_rotate(path,maxx_idx))
) reorient(anchor,spin, two_d=true, path=path2, extent=true, p=path2); ) reorient(anchor,spin, two_d=true, path=path2, extent=true, p=path2);

View file

@ -344,7 +344,7 @@
// hex = path3d(hexagon(side=flare*sidelen, align_side=RIGHT, anchor="side0"),height); // hex = path3d(hexagon(side=flare*sidelen, align_side=RIGHT, anchor="side0"),height);
// pentmate = path3d(pentagon(side=flare*sidelen,align_side=LEFT,anchor="side0"),height); // pentmate = path3d(pentagon(side=flare*sidelen,align_side=LEFT,anchor="side0"),height);
// // Native index would require mapping first and last vertices together, which is not allowed, so shift // // Native index would require mapping first and last vertices together, which is not allowed, so shift
// hexmate = polygon_shift( // hexmate = list_rotate(
// path3d(apply(move(pushvec)*rot(angle),hexagon(side=sidelen,align_side=LEFT,anchor="side0"))), // path3d(apply(move(pushvec)*rot(angle),hexagon(side=sidelen,align_side=LEFT,anchor="side0"))),
// -1); // -1);
// join_vertex = lerp( // join_vertex = lerp(
@ -1540,7 +1540,7 @@ function _skin_distance_match(poly1,poly2) =
; ;
i<=len(big) i<=len(big)
; ;
shifted = polygon_shift(big,i), shifted = list_rotate(big,i),
result =_dp_distance_array(small, shifted, abort_thresh = bestcost), result =_dp_distance_array(small, shifted, abort_thresh = bestcost),
bestmap = result[0]<bestcost ? result[1] : bestmap, bestmap = result[0]<bestcost ? result[1] : bestmap,
bestpoly = result[0]<bestcost ? shifted : bestpoly, bestpoly = result[0]<bestcost ? shifted : bestpoly,
@ -1555,8 +1555,8 @@ function _skin_distance_match(poly1,poly2) =
// These shifts are needed to handle the case when points from both ends of one curve map to a single point on the other // These shifts are needed to handle the case when points from both ends of one curve map to a single point on the other
bigshift = len(bigmap) - max(max_index(bigmap,all=true))-1, bigshift = len(bigmap) - max(max_index(bigmap,all=true))-1,
smallshift = len(smallmap) - max(max_index(smallmap,all=true))-1, smallshift = len(smallmap) - max(max_index(smallmap,all=true))-1,
newsmall = polygon_shift(repeat_entries(small,unique_count(smallmap)[1]),smallshift), newsmall = list_rotate(repeat_entries(small,unique_count(smallmap)[1]),smallshift),
newbig = polygon_shift(repeat_entries(map_poly[1],unique_count(bigmap)[1]),bigshift) newbig = list_rotate(repeat_entries(map_poly[1],unique_count(bigmap)[1]),bigshift)
) )
swap ? [newbig, newsmall] : [newsmall,newbig]; swap ? [newbig, newsmall] : [newsmall,newbig];
@ -1571,8 +1571,8 @@ function _skin_aligned_distance_match(poly1, poly2) =
map = _dp_extract_map(result[1]), map = _dp_extract_map(result[1]),
shift0 = len(map[0]) - max(max_index(map[0],all=true))-1, shift0 = len(map[0]) - max(max_index(map[0],all=true))-1,
shift1 = len(map[1]) - max(max_index(map[1],all=true))-1, shift1 = len(map[1]) - max(max_index(map[1],all=true))-1,
new0 = polygon_shift(repeat_entries(poly1,unique_count(map[0])[1]),shift0), new0 = list_rotate(repeat_entries(poly1,unique_count(map[0])[1]),shift0),
new1 = polygon_shift(repeat_entries(poly2,unique_count(map[1])[1]),shift1) new1 = list_rotate(repeat_entries(poly2,unique_count(map[1])[1]),shift1)
) )
[new0,new1]; [new0,new1];
@ -1598,7 +1598,7 @@ function _skin_tangent_match(poly1, poly2) =
curve_offset = centroid(small)-centroid(big), curve_offset = centroid(small)-centroid(big),
cutpts = [for(i=[0:len(small)-1]) _find_one_tangent(big, select(small,i,i+1),curve_offset=curve_offset)], cutpts = [for(i=[0:len(small)-1]) _find_one_tangent(big, select(small,i,i+1),curve_offset=curve_offset)],
shift = last(cutpts)+1, shift = last(cutpts)+1,
newbig = polygon_shift(big, shift), newbig = list_rotate(big, shift),
repeat_counts = [for(i=[0:len(small)-1]) posmod(cutpts[i]-select(cutpts,i-1),len(big))], repeat_counts = [for(i=[0:len(small)-1]) posmod(cutpts[i]-select(cutpts,i-1),len(big))],
newsmall = repeat_entries(small,repeat_counts) newsmall = repeat_entries(small,repeat_counts)
) )

View file

@ -40,10 +40,9 @@ test_circle_2tangents();
test_circle_3points(); test_circle_3points();
test_circle_point_tangents(); test_circle_point_tangents();
test_noncollinear_triple(); test__noncollinear_triple();
test_polygon_area(); test_polygon_area();
test_is_polygon_convex(); test_is_polygon_convex();
test_polygon_shift();
test_reindex_polygon(); test_reindex_polygon();
test_align_polygon(); test_align_polygon();
test_centroid(); test_centroid();
@ -787,15 +786,6 @@ module test_is_polygon_convex() {
*test_is_polygon_convex(); *test_is_polygon_convex();
module test_polygon_shift() {
path = [[1,1],[-1,1],[-1,-1],[1,-1]];
assert(polygon_shift(path,1) == [[-1,1],[-1,-1],[1,-1],[1,1]]);
assert(polygon_shift(path,2) == [[-1,-1],[1,-1],[1,1],[-1,1]]);
}
*test_polygon_shift();
module test_reindex_polygon() { module test_reindex_polygon() {
pent = subdivide_path([for(i=[0:4])[sin(72*i),cos(72*i)]],5); pent = subdivide_path([for(i=[0:4])[sin(72*i),cos(72*i)]],5);
circ = circle($fn=5,r=2.2); circ = circle($fn=5,r=2.2);
@ -827,13 +817,13 @@ module test_align_polygon() {
*test_align_polygon(); *test_align_polygon();
module test_noncollinear_triple() { module test__noncollinear_triple() {
assert(noncollinear_triple([[1,1],[2,2],[3,3],[4,4],[4,5],[5,6]]) == [0,5,3]); assert(_noncollinear_triple([[1,1],[2,2],[3,3],[4,4],[4,5],[5,6]]) == [0,5,3]);
assert(noncollinear_triple([[1,1],[2,2],[8,3],[4,4],[4,5],[5,6]]) == [0,2,5]); assert(_noncollinear_triple([[1,1],[2,2],[8,3],[4,4],[4,5],[5,6]]) == [0,2,5]);
u = unit([5,3]); u = unit([5,3]);
assert_equal(noncollinear_triple([for(i = [2,3,4,5,7,12,15]) i * u], error=false),[]); assert_equal(_noncollinear_triple([for(i = [2,3,4,5,7,12,15]) i * u], error=false),[]);
} }
*test_noncollinear_triple(); *test__noncollinear_triple();
module test_centroid() { module test_centroid() {

View file

@ -133,6 +133,9 @@ module test_list_rotate() {
assert(list_rotate([1,2,3,4,5],5) == [1,2,3,4,5]); assert(list_rotate([1,2,3,4,5],5) == [1,2,3,4,5]);
assert(list_rotate([1,2,3,4,5],6) == [2,3,4,5,1]); assert(list_rotate([1,2,3,4,5],6) == [2,3,4,5,1]);
assert(list_rotate([],3) == []); assert(list_rotate([],3) == []);
path = [[1,1],[-1,1],[-1,-1],[1,-1]];
assert(list_rotate(path,1) == [[-1,1],[-1,-1],[1,-1],[1,1]]);
assert(list_rotate(path,2) == [[-1,-1],[1,-1],[1,1],[-1,1]]);
} }
test_list_rotate(); test_list_rotate();

View file

@ -100,10 +100,11 @@ test_teardrop2d();
module test_glued_circles() { module test_glued_circles() {
$fn=24; $fn=24;
assert_approx(glued_circles(r=15, spread=40, tangent=30), [[35,0],[34.4888873943,-3.88228567654],[32.9903810568,-7.5],[30.6066017178,-10.6066017178],[27.5,-12.9903810568],[23.8822856765,-14.4888873943],[20,-15],[16.1177143235,-14.4888873943],[12.5,-12.9903810568],[12.5,-12.9903810568],[6.47047612756,-10.4928704942],[0,-9.64101615138],[-6.47047612756,-10.4928704942],[-12.5,-12.9903810568],[-12.5,-12.9903810568],[-16.1177143235,-14.4888873943],[-20,-15],[-23.8822856765,-14.4888873943],[-27.5,-12.9903810568],[-30.6066017178,-10.6066017178],[-32.9903810568,-7.5],[-34.4888873943,-3.88228567654],[-35,0],[-34.4888873943,3.88228567654],[-32.9903810568,7.5],[-30.6066017178,10.6066017178],[-27.5,12.9903810568],[-23.8822856765,14.4888873943],[-20,15],[-16.1177143235,14.4888873943],[-12.5,12.9903810568],[-6.47047612756,10.4928704942],[0,9.64101615138],[6.47047612756,10.4928704942],[12.5,12.9903810568],[12.5,12.9903810568],[16.1177143235,14.4888873943],[20,15],[23.8822856765,14.4888873943],[27.5,12.9903810568],[30.6066017178,10.6066017178],[32.9903810568,7.5],[34.4888873943,3.88228567654]]); assert_approx(glued_circles(r=15, spread=40, tangent=30), deduplicate([[35,0],[34.4888873943,-3.88228567654],[32.9903810568,-7.5],[30.6066017178,-10.6066017178],[27.5,-12.9903810568],[23.8822856765,-14.4888873943],[20,-15],[16.1177143235,-14.4888873943],[12.5,-12.9903810568],[12.5,-12.9903810568],[6.47047612756,-10.4928704942],[0,-9.64101615138],[-6.47047612756,-10.4928704942],[-12.5,-12.9903810568],[-12.5,-12.9903810568],[-16.1177143235,-14.4888873943],[-20,-15],[-23.8822856765,-14.4888873943],[-27.5,-12.9903810568],[-30.6066017178,-10.6066017178],[-32.9903810568,-7.5],[-34.4888873943,-3.88228567654],[-35,0],[-34.4888873943,3.88228567654],[-32.9903810568,7.5],[-30.6066017178,10.6066017178],[-27.5,12.9903810568],[-23.8822856765,14.4888873943],[-20,15],[-16.1177143235,14.4888873943],[-12.5,12.9903810568],[-6.47047612756,10.4928704942],[0,9.64101615138],[6.47047612756,10.4928704942],[12.5,12.9903810568],[12.5,12.9903810568],[16.1177143235,14.4888873943],[20,15],[23.8822856765,14.4888873943],[27.5,12.9903810568],[30.6066017178,10.6066017178],[32.9903810568,7.5],[34.4888873943,3.88228567654]]));
assert_approx(glued_circles(d=30, spread=40, tangent=30), [[35,0],[34.4888873943,-3.88228567654],[32.9903810568,-7.5],[30.6066017178,-10.6066017178],[27.5,-12.9903810568],[23.8822856765,-14.4888873943],[20,-15],[16.1177143235,-14.4888873943],[12.5,-12.9903810568],[12.5,-12.9903810568],[6.47047612756,-10.4928704942],[0,-9.64101615138],[-6.47047612756,-10.4928704942],[-12.5,-12.9903810568],[-12.5,-12.9903810568],[-16.1177143235,-14.4888873943],[-20,-15],[-23.8822856765,-14.4888873943],[-27.5,-12.9903810568],[-30.6066017178,-10.6066017178],[-32.9903810568,-7.5],[-34.4888873943,-3.88228567654],[-35,0],[-34.4888873943,3.88228567654],[-32.9903810568,7.5],[-30.6066017178,10.6066017178],[-27.5,12.9903810568],[-23.8822856765,14.4888873943],[-20,15],[-16.1177143235,14.4888873943],[-12.5,12.9903810568],[-6.47047612756,10.4928704942],[0,9.64101615138],[6.47047612756,10.4928704942],[12.5,12.9903810568],[12.5,12.9903810568],[16.1177143235,14.4888873943],[20,15],[23.8822856765,14.4888873943],[27.5,12.9903810568],[30.6066017178,10.6066017178],[32.9903810568,7.5],[34.4888873943,3.88228567654]]); assert_approx(glued_circles(d=30, spread=40, tangent=30),deduplicate( [[35,0],[34.4888873943,-3.88228567654],[32.9903810568,-7.5],[30.6066017178,-10.6066017178],[27.5,-12.9903810568],[23.8822856765,-14.4888873943],[20,-15],[16.1177143235,-14.4888873943],[12.5,-12.9903810568],[12.5,-12.9903810568],[6.47047612756,-10.4928704942],[0,-9.64101615138],[-6.47047612756,-10.4928704942],[-12.5,-12.9903810568],[-12.5,-12.9903810568],[-16.1177143235,-14.4888873943],[-20,-15],[-23.8822856765,-14.4888873943],[-27.5,-12.9903810568],[-30.6066017178,-10.6066017178],[-32.9903810568,-7.5],[-34.4888873943,-3.88228567654],[-35,0],[-34.4888873943,3.88228567654],[-32.9903810568,7.5],[-30.6066017178,10.6066017178],[-27.5,12.9903810568],[-23.8822856765,14.4888873943],[-20,15],[-16.1177143235,14.4888873943],[-12.5,12.9903810568],[-6.47047612756,10.4928704942],[0,9.64101615138],[6.47047612756,10.4928704942],[12.5,12.9903810568],[12.5,12.9903810568],[16.1177143235,14.4888873943],[20,15],[23.8822856765,14.4888873943],[27.5,12.9903810568],[30.6066017178,10.6066017178],[32.9903810568,7.5],[34.4888873943,3.88228567654]]));
assert_approx(glued_circles(d=30, spread=30, tangent=45), [[30,0],[29.4888873943,-3.88228567654],[27.9903810568,-7.5],[25.6066017178,-10.6066017178],[22.5,-12.9903810568],[18.8822856765,-14.4888873943],[15,-15],[11.1177143235,-14.4888873943],[7.5,-12.9903810568],[4.3933982822,-10.6066017178],[4.3933982822,-10.6066017178],[3.1066017178,-9.61920798589],[1.60809538023,-8.99850633757],[0,-8.7867965644],[-1.60809538023,-8.99850633757],[-3.1066017178,-9.61920798589],[-4.3933982822,-10.6066017178],[-4.3933982822,-10.6066017178],[-7.5,-12.9903810568],[-11.1177143235,-14.4888873943],[-15,-15],[-18.8822856765,-14.4888873943],[-22.5,-12.9903810568],[-25.6066017178,-10.6066017178],[-27.9903810568,-7.5],[-29.4888873943,-3.88228567654],[-30,0],[-29.4888873943,3.88228567654],[-27.9903810568,7.5],[-25.6066017178,10.6066017178],[-22.5,12.9903810568],[-18.8822856765,14.4888873943],[-15,15],[-11.1177143235,14.4888873943],[-7.5,12.9903810568],[-4.3933982822,10.6066017178],[-3.1066017178,9.61920798589],[-1.60809538023,8.99850633757],[0,8.7867965644],[1.60809538023,8.99850633757],[3.1066017178,9.61920798589],[4.3933982822,10.6066017178],[4.3933982822,10.6066017178],[7.5,12.9903810568],[11.1177143235,14.4888873943],[15,15],[18.8822856765,14.4888873943],[22.5,12.9903810568],[25.6066017178,10.6066017178],[27.9903810568,7.5],[29.4888873943,3.88228567654]]); assert_approx(glued_circles(d=30, spread=30, tangent=45),deduplicate( [[30,0],[29.4888873943,-3.88228567654],[27.9903810568,-7.5],[25.6066017178,-10.6066017178],[22.5,-12.9903810568],[18.8822856765,-14.4888873943],[15,-15],[11.1177143235,-14.4888873943],[7.5,-12.9903810568],[4.3933982822,-10.6066017178],[4.3933982822,-10.6066017178],[3.1066017178,-9.61920798589],[1.60809538023,-8.99850633757],[0,-8.7867965644],[-1.60809538023,-8.99850633757],[-3.1066017178,-9.61920798589],[-4.3933982822,-10.6066017178],[-4.3933982822,-10.6066017178],[-7.5,-12.9903810568],[-11.1177143235,-14.4888873943],[-15,-15],[-18.8822856765,-14.4888873943],[-22.5,-12.9903810568],[-25.6066017178,-10.6066017178],[-27.9903810568,-7.5],[-29.4888873943,-3.88228567654],[-30,0],[-29.4888873943,3.88228567654],[-27.9903810568,7.5],[-25.6066017178,10.6066017178],[-22.5,12.9903810568],[-18.8822856765,14.4888873943],[-15,15],[-11.1177143235,14.4888873943],[-7.5,12.9903810568],[-4.3933982822,10.6066017178],[-3.1066017178,9.61920798589],[-1.60809538023,8.99850633757],[0,8.7867965644],[1.60809538023,8.99850633757],[3.1066017178,9.61920798589],[4.3933982822,10.6066017178],[4.3933982822,10.6066017178],[7.5,12.9903810568],[11.1177143235,14.4888873943],[15,15],[18.8822856765,14.4888873943],[22.5,12.9903810568],[25.6066017178,10.6066017178],[27.9903810568,7.5],[29.4888873943,3.88228567654]]));
assert_approx(glued_circles(d=30, spread=30, tangent=-30), [[30,0],[29.4888873943,-3.88228567654],[27.9903810568,-7.5],[25.6066017178,-10.6066017178],[22.5,-12.9903810568],[22.5,-12.9903810568],[11.6468570296,-17.4859000695],[0,-19.0192378865],[-11.6468570296,-17.4859000695],[-22.5,-12.9903810568],[-22.5,-12.9903810568],[-25.6066017178,-10.6066017178],[-27.9903810568,-7.5],[-29.4888873943,-3.88228567654],[-30,0],[-29.4888873943,3.88228567654],[-27.9903810568,7.5],[-25.6066017178,10.6066017178],[-22.5,12.9903810568],[-11.6468570296,17.4859000695],[0,19.0192378865],[11.6468570296,17.4859000695],[22.5,12.9903810568],[22.5,12.9903810568],[25.6066017178,10.6066017178],[27.9903810568,7.5],[29.4888873943,3.88228567654]]); assert_approx(glued_circles(d=30, spread=30, tangent=-30), deduplicate([[30,0],[29.4888873943,-3.88228567654],[27.9903810568,-7.5],[25.6066017178,-10.6066017178],[22.5,-12.9903810568],[22.5,-12.9903810568],[11.6468570296,-17.4859000695],[0,-19.0192378865],[-11.6468570296,-17.4859000695],[-22.5,-12.9903810568],[-22.5,-12.9903810568],[-25.6066017178,-10.6066017178],[-27.9903810568,-7.5],[-29.4888873943,-3.88228567654],[-30,0],[-29.4888873943,3.88228567654],[-27.9903810568,7.5],[-25.6066017178,10.6066017178],[-22.5,12.9903810568],[-11.6468570296,17.4859000695],[0,19.0192378865],[11.6468570296,17.4859000695],[22.5,12.9903810568],[22.5,12.9903810568],[25.6066017178,10.6066017178],[27.9903810568,7.5],[29.4888873943,3.88228567654]]));
assert_approx(glued_circles(d=30, spread=50, tangent=0),[[40, 0], [39.4888873943, -3.88228567654], [37.9903810568, -7.5], [35.6066017178, -10.6066017178], [32.5, -12.9903810568], [28.8822856765, -14.4888873943], [25, -15], [-25, -15], [-28.8822856765, -14.4888873943], [-32.5, -12.9903810568], [-35.6066017178, -10.6066017178], [-37.9903810568, -7.5], [-39.4888873943, -3.88228567654], [-40, 0], [-39.4888873943, 3.88228567654], [-37.9903810568, 7.5], [-35.6066017178, 10.6066017178], [-32.5, 12.9903810568], [-28.8822856765, 14.4888873943], [-25, 15], [25, 15], [28.8822856765, 14.4888873943], [32.5, 12.9903810568], [35.6066017178, 10.6066017178], [37.9903810568, 7.5], [39.4888873943, 3.88228567654]]);
} }
test_glued_circles(); test_glued_circles();

View file

@ -351,8 +351,8 @@ function _path_path_closest_vertices(path1,path2) =
function _join_paths_at_vertices(path1,path2,v1,v2) = function _join_paths_at_vertices(path1,path2,v1,v2) =
let( let(
repeat_start = !approx(path1[v1],path2[v2]), repeat_start = !approx(path1[v1],path2[v2]),
path1 = clockwise_polygon(polygon_shift(path1,v1)), path1 = clockwise_polygon(list_rotate(path1,v1)),
path2 = ccw_polygon(polygon_shift(path2,v2)) path2 = ccw_polygon(list_rotate(path2,v2))
) )
[ [
each path1, each path1,