fixed sum to handle larger input

added all_equal
added vnf_tri_array as public
added bezier_patch_degenerate as public
This commit is contained in:
Adrian Mariano 2021-04-11 22:29:25 -04:00
parent 1451b827ec
commit d4d5794ef2
4 changed files with 214 additions and 125 deletions

View file

@ -1222,6 +1222,193 @@ function bezier_patch(patch, splinesteps=16, vnf=EMPTY_VNF, style="default") =
) vnf; ) vnf;
// Function: bezier_patch_degenerate()
// Usage:
// vnf = bezier_patch_degenerate(patch, <splinesteps>, <reverse>);
// vnf_edges = bezier_patch_degenerate(patch, <splinesteps>, <reverse>, return_edges=true);
// Description:
// Returns a VNF for a degenerate rectangular bezier patch where some of the corners of the patch are
// equal. If the resulting patch has no faces then returns an empty VNF. Note that due to the degeneracy,
// the shape of the patch can be triangular even though the actual underlying patch is a rectangle. This is
// a different method for creating triangular bezier patches than the triangular patch.
// If you specify return_edges then the return is a list whose first element is the vnf and whose second
// element lists the edges in the order [left, right, top, bottom], where each list is a list of the actual
// point values, but possibly only a single point if that edge is degenerate.
// The method checks for various types of degeneracy and uses a triangular or partly triangular array of sample points.
// See examples below for the types of degeneracy detected and how the patch is sampled for those cases.
// Note that splinesteps is the same for both directions of the patch, so it cannot be an array.
// Arguments:
// patch = Patch to process
// splinesteps = Number of segments to produce on each side. Default: 16
// reverse = reverse direction of faces. Default: false
// return_edges = if true return the points on the four edges: [left, right, top, bottom]. Default: false
// Example: This quartic patch is degenerate at one corner, where a row of control points are equal. Processing this degenerate patch normally produces excess triangles near the degenerate point.
// splinesteps=8;
// patch=[
// repeat([-12.5, 12.5, 15],5),
// [[-6.25, 11.25, 15], [-6.25, 8.75, 15], [-6.25, 6.25, 15], [-8.75, 6.25, 15], [-11.25, 6.25, 15]],
// [[0, 10, 15], [0, 5, 15], [0, 0, 15], [-5, 0, 15], [-10, 0, 15]],
// [[0, 10, 8.75], [0, 5, 8.75], [0, 0, 8.75], [-5, 0, 8.75], [-10, 0, 8.75]],
// [[0, 10, 2.5], [0, 5, 2.5], [0, 0, 2.5], [-5, 0, 2.5], [-10, 0, 2.5]]
// ];
// vnf_wireframe((bezier_patch(patch, splinesteps)),d=0.1);
// color("red")move_copies(flatten(patch)) sphere(r=0.3,$fn=9);
// Example: With bezier_patch_degenerate the degenerate point does not have excess triangles. The top half of the patch decreases the number of sampled points by 2 for each row.
// splinesteps=8;
// patch=[
// repeat([-12.5, 12.5, 15],5),
// [[-6.25, 11.25, 15], [-6.25, 8.75, 15], [-6.25, 6.25, 15], [-8.75, 6.25, 15], [-11.25, 6.25, 15]],
// [[0, 10, 15], [0, 5, 15], [0, 0, 15], [-5, 0, 15], [-10, 0, 15]],
// [[0, 10, 8.75], [0, 5, 8.75], [0, 0, 8.75], [-5, 0, 8.75], [-10, 0, 8.75]],
// [[0, 10, 2.5], [0, 5, 2.5], [0, 0, 2.5], [-5, 0, 2.5], [-10, 0, 2.5]]
// ];
// vnf_wireframe(bezier_patch_degenerate(patch, splinesteps),d=0.1);
// color("red")move_copies(flatten(patch)) sphere(r=0.3,$fn=9);
// Example: With splinesteps odd you get one "odd" row where the point count decreases by 1 instead of 2. You may prefer even values for splinesteps to avoid this.
// splinesteps=7;
// patch=[
// repeat([-12.5, 12.5, 15],5),
// [[-6.25, 11.25, 15], [-6.25, 8.75, 15], [-6.25, 6.25, 15], [-8.75, 6.25, 15], [-11.25, 6.25, 15]],
// [[0, 10, 15], [0, 5, 15], [0, 0, 15], [-5, 0, 15], [-10, 0, 15]],
// [[0, 10, 8.75], [0, 5, 8.75], [0, 0, 8.75], [-5, 0, 8.75], [-10, 0, 8.75]],
// [[0, 10, 2.5], [0, 5, 2.5], [0, 0, 2.5], [-5, 0, 2.5], [-10, 0, 2.5]]
// ];
// vnf_wireframe(bezier_patch_degenerate(patch, splinesteps),d=0.1);
// color("red")move_copies(flatten(patch)) sphere(r=0.3,$fn=9);
// Example: A more extreme degeneracy occurs when the top half of a patch is degenerate to a line. (For odd length patches the middle row must be degenerate to trigger this style.) In this case the number of points in each row decreases by 1 for every row. It doesn't matter of splinesteps is odd or even.
// splinesteps=8;
// patch = [[[10, 0, 0], [10, -10.4, 0], [10, -20.8, 0], [1.876, -14.30, 0], [-6.24, -7.8, 0]],
// [[5, 0, 0], [5, -5.2, 0], [5, -10.4, 0], [0.938, -7.15, 0], [-3.12, -3.9, 0]],
// repeat([0,0,0],5),
// repeat([0,0,5],5),
// repeat([0,0,10],5)
// ];
// vnf_wireframe(bezier_patch_degenerate(patch, splinesteps),d=0.1);
// color("red")move_copies(flatten(patch)) sphere(r=0.3,$fn=9);
// Example: Here is a degenerate cubic patch.
// splinesteps=8;
// patch = [ [ [-20,0,0], [-10,0,0],[0,10,0],[0,20,0] ],
// [ [-20,0,10], [-10,0,10],[0,10,10],[0,20,10]],
// [ [-10,0,20], [-5,0,20], [0,5,20], [0,10,20]],
// repeat([0,0,30],4)
// ];
// color("red")move_copies(flatten(patch)) sphere(r=0.3,$fn=9);
// vnf_wireframe(bezier_patch_degenerate(patch, splinesteps),d=0.1);
// Example: A more extreme degenerate cubic patch, where two rows are equal.
// splinesteps=8;
// patch = [ [ [-20,0,0], [-10,0,0],[0,10,0],[0,20,0] ],
// [ [-20,0,10], [-10,0,10],[0,10,10],[0,20,10] ],
// repeat([-10,10,20],4),
// repeat([-10,10,30],4)
// ];
// color("red")move_copies(flatten(patch)) sphere(r=0.3,$fn=9);
// vnf_wireframe(bezier_patch_degenerate(patch, splinesteps),d=0.1);
// Example: Quadratic patch degenerate at the right side:
// splinesteps=8;
// patch = [[[0, -10, 0],[10, -5, 0],[20, 0, 0]],
// [[0, 0, 0], [10, 0, 0], [20, 0, 0]],
// [[0, 0, 10], [10, 0, 5], [20, 0, 0]]];
// vnf_wireframe(bezier_patch_degenerate(patch, splinesteps),d=0.1);
// color("red")move_copies(flatten(patch)) sphere(r=0.3,$fn=9);
// Example: Cubic patch degenerate at both ends. In this case the point count changes by 2 at every row.
// splinesteps=8;
// patch = [
// repeat([10,-10,0],4),
// [ [-20,0,0], [-1,0,0],[0,10,0],[0,20,0] ],
// [ [-20,0,10], [-10,0,10],[0,10,10],[0,20,10] ],
// repeat([-10,10,20],4),
// ];
// vnf_wireframe(bezier_patch_degenerate(patch, splinesteps),d=0.1);
// color("red")move_copies(flatten(patch)) sphere(r=0.3,$fn=9);
function bezier_patch_degenerate(patch, splinesteps=16, reverse=false, return_edges=false) =
!return_edges ? bezier_patch_degenerate(patch, splinesteps, reverse, true)[0] :
assert(is_rectpatch(patch), "Must supply rectangular bezier patch")
assert(is_int(splinesteps) && splinesteps>=3, "splinesteps must be an integer 3 or larger")
let(
row_degen = [for(row=patch) all_equal(row)],
col_degen = [for(col=transpose(patch)) all_equal(col)],
top_degen = row_degen[0],
bot_degen = last(row_degen),
left_degen = col_degen[0],
right_degen = last(col_degen),
samplepts = lerpn(0,1,splinesteps+1)
)
all(row_degen) && all(col_degen) ? // fully degenerate case
[EMPTY_VNF, repeat([patch[0][0]],4)] :
all(row_degen) ? // degenerate to a line (top to bottom)
let(pts = bezier_points(subindex(patch,0), samplepts))
[EMPTY_VNF, [pts,pts,[pts[0]],[last(pts)]]] :
all(col_degen) ? // degenerate to a line (left to right)
let(pts = bezier_points(patch[0], samplepts))
[EMPTY_VNF, [[pts[0]], [last(pts)], pts, pts]] :
!top_degen && !bot_degen && !left_degen && !right_degen ? // non-degenerate case
let(pts = bezier_patch_points(patch, samplepts, samplepts))
[
vnf_vertex_array(pts, reverse=!reverse),
[subindex(pts,0), subindex(pts,len(pts)-1), pts[0], last(pts)]
] :
top_degen && bot_degen ?
let(
rowcount = [
each list([3:2:splinesteps]),
if (splinesteps%2==0) splinesteps+1,
each reverse(list([3:2:splinesteps]))
],
bpatch = [for(i=[0:1:len(patch[0])-1]) bezier_points(subindex(patch,i), samplepts)],
pts = [
[bpatch[0][0]],
for(j=[0:splinesteps-2]) bezier_points(subindex(bpatch,j+1), lerpn(0,1,rowcount[j])),
[last(bpatch[0])]
],
vnf = vnf_tri_array(pts, reverse=reverse)
) [
vnf,
[
subindex(pts,0),
[for(row=pts) last(row)],
pts[0],
last(pts),
]
] :
bot_degen ? // only bottom is degenerate
let(
result = bezier_patch_degenerate(reverse(patch), splinesteps=splinesteps, reverse=!reverse, return_edges=true)
)
[
result[0],
[reverse(result[1][0]), reverse(result[1][1]), (result[1][3]), (result[1][2])]
] :
top_degen ? // only top is degenerate
let(
full_degen = len(patch)>=4 && all(select(row_degen,1,ceil(len(patch)/2-1))),
rowmax = full_degen ? count(splinesteps+1) :
[for(j=[0:splinesteps]) j<=splinesteps/2 ? 2*j : splinesteps],
bpatch = [for(i=[0:1:len(patch[0])-1]) bezier_points(subindex(patch,i), samplepts)],
pts = [
[bpatch[0][0]],
for(j=[1:splinesteps]) bezier_points(subindex(bpatch,j), lerpn(0,1,rowmax[j]+1))
],
vnf = vnf_tri_array(pts, reverse=reverse)
) [
vnf,
[
subindex(pts,0),
[for(row=pts) last(row)],
pts[0],
last(pts),
]
] :
// must have left or right degeneracy, so transpose and recurse
let(
result = bezier_patch_degenerate(transpose(patch), splinesteps=splinesteps, reverse=!reverse, return_edges=true)
)
[result[0],
select(result[1],[2,3,0,1])
];
function _tri_count(n) = (n*(1+n))/2; function _tri_count(n) = (n*(1+n))/2;

View file

@ -1856,10 +1856,10 @@ function reindex_polygon(reference, poly, return_error=false) =
polygon_is_clockwise(reference) polygon_is_clockwise(reference)
? clockwise_polygon(poly) ? clockwise_polygon(poly)
: ccw_polygon(poly), : ccw_polygon(poly),
I = [for(i=[0:N-1]) 1], I = [for(i=reference) 1],
val = [ for(k=[0:N-1]) val = [ for(k=[0:N-1])
          [for(i=[0:N-1]) [for(i=[0:N-1])
             (reference[i]*poly[(i+k)%N]) ] ]*I, (reference[i]*poly[(i+k)%N]) ] ]*I,
optimal_poly = polygon_shift(fixpoly, max_index(val)) optimal_poly = polygon_shift(fixpoly, max_index(val))
) )
return_error? [optimal_poly, min(poly*(I*poly)-2*val)] : return_error? [optimal_poly, min(poly*(I*poly)-2*val)] :

View file

@ -583,7 +583,7 @@ function lcm(a,b=[]) =
function sum(v, dflt=0) = function sum(v, dflt=0) =
v==[]? dflt : v==[]? dflt :
assert(is_consistent(v), "Input to sum is non-numeric or inconsistent") assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
is_vector(v) || is_matrix(v) ? [for(i=[1:len(v)]) 1]*v : is_vector(v) || is_matrix(v) ? [for(i=v) 1]*v :
_sum(v,v[0]*0); _sum(v,v[0]*0);
function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1); function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
@ -1155,6 +1155,18 @@ function all_nonnegative(x) =
false; false;
// Function all_equal()
// Usage:
// b = all_equal(vec,<eps>);
// Description:
// Returns true if all of the entries in vec are equal to each other, or approximately equal to each other if eps is set.
// Arguments:
// vec = vector to check
// eps = Set to tolerance for approximate equality. Default: 0
function all_equal(vec,eps=0) =
eps==0 ? [for(v=vec) if (v!=vec[0]) v] == []
: [for(v=vec) if (!approx(v,vec[0])) v] == [];
// Function: approx() // Function: approx()
// Usage: // Usage:
// b = approx(a,b,<eps>) // b = approx(a,b,<eps>)

View file

@ -1779,8 +1779,8 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
// M = path3d(turtle(["left", 180, "length",3,"move", "left", "move", 3, "right", "move", "right", "move", 4, "right", "move", 3, "right", "move", 2])); // M = path3d(turtle(["left", 180, "length",3,"move", "left", "move", 3, "right", "move", "right", "move", 4, "right", "move", 3, "right", "move", 2]));
// rounded_prism(M, apply(right(1)*scale(.75)*up(3),M), joint_top=0.5, joint_bot=0.2, joint_sides=[.2,1,1,0.5,1.5,.5,2], splinesteps=32); // rounded_prism(M, apply(right(1)*scale(.75)*up(3),M), joint_top=0.5, joint_bot=0.2, joint_sides=[.2,1,1,0.5,1.5,.5,2], splinesteps=32);
// Example: this example shows most of the different types of patches that rounded_prism creates. Note that some of the patches are close to interfering with each other across the top of the polyhedron, which would create an invalid result. // Example: this example shows most of the different types of patches that rounded_prism creates. Note that some of the patches are close to interfering with each other across the top of the polyhedron, which would create an invalid result.
// N = apply(rot(180)*yscale(.8),turtle(["length",3,"left", "move", 2, "right", 135, "move", sqrt(2), "left", "move", sqrt(2), "right", 135, "move", 2])); N = apply(rot(180)*yscale(.8),turtle(["length",3,"left", "move", 2, "right", 135, "move", sqrt(2), "left", "move", sqrt(2), "right", 135, "move", 2]));
// rounded_prism(N, height=3, joint_bot=0.5, joint_top=1.25, joint_sides=[[1,1.75],0,.5,.5,2], debug=true); rounded_prism(N, height=3, joint_bot=0.5, joint_top=1.25, joint_sides=[[1,1.75],0,.5,.5,2], debug=true);
// Example: This object has different scales on its different axies. Here is the largest symmetric rounding that fits. Note that the rounding is slightly smaller than the object dimensions because of roundoff error. // Example: This object has different scales on its different axies. Here is the largest symmetric rounding that fits. Note that the rounding is slightly smaller than the object dimensions because of roundoff error.
// rounded_prism(square([100.1,30.1]), height=8.1, joint_top=4, joint_bot=4, joint_sides=15, k_sides=0.3, splinesteps=32); // rounded_prism(square([100.1,30.1]), height=8.1, joint_top=4, joint_bot=4, joint_sides=15, k_sides=0.3, splinesteps=32);
// Example: Using asymetric rounding enables a much more rounded form: // Example: Using asymetric rounding enables a much more rounded form:
@ -1886,8 +1886,8 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
let( let(
// Entries in the next two lists have the form [edges, vnf] where // Entries in the next two lists have the form [edges, vnf] where
// edges is a list [leftedge, rightedge, topedge, botedge] // edges is a list [leftedge, rightedge, topedge, botedge]
top_samples = [for(patch=top_patch) bezier_patch_degenerate(patch,splinesteps,reverse=true) ], top_samples = [for(patch=top_patch) bezier_patch_degenerate(patch,splinesteps,reverse=true,return_edges=true) ],
bot_samples = [for(patch=bot_patch) bezier_patch_degenerate(patch,splinesteps,reverse=false) ], bot_samples = [for(patch=bot_patch) bezier_patch_degenerate(patch,splinesteps,reverse=false,return_edges=true) ],
leftidx=0, leftidx=0,
rightidx=1, rightidx=1,
topidx=2, topidx=2,
@ -1895,14 +1895,14 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
edge_points = edge_points =
[for(i=[0:N-1]) [for(i=[0:N-1])
let( let(
top_edge = [ top_samples[i][0][rightidx], select(top_samples, i+1)[0][leftidx]], top_edge = [ top_samples[i][1][rightidx], select(top_samples, i+1)[1][leftidx]],
bot_edge = [ select(bot_samples, i+1)[0][leftidx], bot_samples[i][0][rightidx]], bot_edge = [ select(bot_samples, i+1)[1][leftidx], bot_samples[i][1][rightidx]],
vert_edge = [ bot_samples[i][0][botidx], top_samples[i][0][botidx]] vert_edge = [ bot_samples[i][1][botidx], top_samples[i][1][botidx]]
) )
each [top_edge, bot_edge, vert_edge] ], each [top_edge, bot_edge, vert_edge] ],
faces = [ faces = [
[for(i=[0:N-1]) each top_samples[i][0][topidx]], [for(i=[0:N-1]) each top_samples[i][1][topidx]],
[for(i=[N-1:-1:0]) each reverse(bot_samples[i][0][topidx])], [for(i=[N-1:-1:0]) each reverse(bot_samples[i][1][topidx])],
for(i=[0:N-1]) [ for(i=[0:N-1]) [
bot_patch[i][4][4], bot_patch[i][4][4],
select(bot_patch,i+1)[4][0], select(bot_patch,i+1)[4][0],
@ -1934,8 +1934,8 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
"Roundovers interfere with each other on bottom face: either input is self intersecting or top joint length is too large") "Roundovers interfere with each other on bottom face: either input is self intersecting or top joint length is too large")
assert(debug || (verify_vert==[] && verify_horiz==[]), "Curvature continuity failed") assert(debug || (verify_vert==[] && verify_horiz==[]), "Curvature continuity failed")
let( let(
vnf = vnf_merge([ each subindex(top_samples,1), vnf = vnf_merge([ each subindex(top_samples,0),
each subindex(bot_samples,1), each subindex(bot_samples,0),
for(pts=edge_points) vnf_vertex_array(pts), for(pts=edge_points) vnf_vertex_array(pts),
vnf_triangulate(vnf_add_faces(EMPTY_VNF,faces)) vnf_triangulate(vnf_add_faces(EMPTY_VNF,faces))
]) ])
@ -1943,116 +1943,6 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
debug ? [concat(top_patch, bot_patch), vnf] : vnf; debug ? [concat(top_patch, bot_patch), vnf] : vnf;
// This function takes a bezier patch as input and returns [edges, vnf], where
// edges = [leftedge, rightedge, topedge, bottomedge]
// gives the points along the edges of the patch, and the vnf is the patch vnf.
// It checks for various types of degeneracy and uses half or full triangular
// sampling on degenerate patches.
function bezier_patch_degenerate(patch, splinesteps=16, reverse=false) =
assert(is_num(splinesteps), "splinesteps must be a number")
let(
top_degen = patch[0][0] == last(patch[0]),
bot_degen = last(patch)[0] == last(last(patch)),
left_degen = patch[0][0] == last(patch)[0],
right_degen = last(patch[0]) == last(last(patch)),
samplepts = count(splinesteps+1)/splinesteps
)
top_degen && bot_degen && left_degen && right_degen ? // fully degenerate case
[repeat([patch[0][0]],4), EMPTY_VNF] :
top_degen && bot_degen ? // double degenerate case (top/bot)
let(
pts = bezier_points(subindex(patch,0), samplepts)
)
[[pts,pts,[pts[0]],[last(pts)]], EMPTY_VNF] :
left_degen && right_degen ? // double degenerate case (sides)
let(
pts = bezier_points(patch[0], samplepts)
)
[[[pts[0]], [last(pts)], pts, pts], EMPTY_VNF] :
!top_degen && !bot_degen ? // non-degenerate case
let(
k=echo("non-degenerate case"),
pts = bezier_patch_points(patch, samplepts, samplepts)
)
[
[subindex(pts,0), subindex(pts,len(pts)-1), pts[0], last(pts)],
vnf_vertex_array(pts, reverse=reverse)
] :
bot_degen ? // only bottom is degenerate
let(
result = bezier_patch_degenerate(reverse(patch), splinesteps=splinesteps, reverse=!reverse)
)
[
[reverse(result[0][0]), reverse(result[0][1]), (result[0][3]), (result[0][2])],
result[1]
] :
// at this point top_degen is true // only top is degenerate
let(
full_degen = patch[1][0] == last(patch[1]),
rowmax = full_degen ? count(splinesteps+1) :
[for(j=[0:splinesteps]) j<=splinesteps/2 ? 2*j : splinesteps],
vbb=echo("single degenerate case"),
bpatch = [for(i=[0:1:len(patch[0])-1]) bezier_points(subindex(patch,i), samplepts)],
pts = [
[bpatch[0][0]],
for(j=[1:splinesteps]) bezier_points(subindex(bpatch,j), lerpn(0,1,rowmax[j]+1))
],
vnf = vnf_tri_array(pts, reverse=reverse)
) [
[
subindex(pts,0),
[for(row=pts) last(row)],
pts[0],
last(pts),
],
vnf
];
// This function produces a vnf with a triangulation for a list of rows
// where the number of points between rows differs by at most 2.
// It's a generalization of vnf_vertex_array.
function vnf_tri_array(points, row_wrap=false, reverse=false) =
let(
lens = [for(row=points) len(row)],
rowstarts = [0,each cumsum(lens)],
faces =
[for(i=[0:1:len(points) - 1 - (row_wrap ? 0 : 1)]) each
let(
rowstart = rowstarts[i],
nextrow = select(rowstarts,i+1),
delta = select(lens,i+1)-lens[i]
)
delta == 0 ?
[for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow] : [j+rowstart, j+rowstart+1, j+nextrow],
for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+nextrow, j+nextrow+1] : [j+rowstart+1, j+nextrow+1, j+nextrow]] :
delta == 1 ?
[for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow+1] : [j+rowstart, j+rowstart+1, j+nextrow+1],
for(j=[0:1:lens[i]-1]) reverse ? [j+rowstart, j+nextrow, j+nextrow+1] : [j+rowstart, j+nextrow+1, j+nextrow]] :
delta == -1 ?
[for(j=[0:1:lens[i]-3]) reverse ? [j+rowstart+1, j+nextrow, j+nextrow+1]: [j+rowstart+1, j+nextrow+1, j+nextrow],
for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow] : [j+rowstart, j+rowstart+1, j+nextrow]] :
let(count = floor((lens[i]-1)/2))
delta == 2 ?
[
for(j=[0:1:count-1]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow+1] : [j+rowstart, j+rowstart+1, j+nextrow+1], // top triangles left
for(j=[count:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow+2] : [j+rowstart, j+rowstart+1, j+nextrow+2], // top triangles right
for(j=[0:1:count]) reverse ? [j+rowstart, j+nextrow, j+nextrow+1] : [j+rowstart, j+nextrow+1, j+nextrow], // bot triangles left
for(j=[count+1:1:select(lens,i+1)-2]) reverse ? [j+rowstart-1, j+nextrow, j+nextrow+1] : [j+rowstart-1, j+nextrow+1, j+nextrow], // bot triangles right
] :
delta == -2 ?
[
for(j=[0:1:count-2]) reverse ? [j+nextrow, j+nextrow+1, j+rowstart+1] : [j+nextrow, j+rowstart+1, j+nextrow+1],
for(j=[count-1:1:lens[i]-4]) reverse ? [j+nextrow,j+nextrow+1,j+rowstart+2] : [j+nextrow,j+rowstart+2, j+nextrow+1],
for(j=[0:1:count-1]) reverse ? [j+nextrow, j+rowstart+1, j+rowstart] : [j+nextrow, j+rowstart, j+rowstart+1],
for(j=[count:1:select(lens,i+1)]) reverse ? [ j+nextrow-1, j+rowstart+1, j+rowstart]: [ j+nextrow-1, j+rowstart, j+rowstart+1],
] :
assert(false,str("Unsupported row length difference of ",delta, " between row ",i," and ",i+1))
])
[flatten(points), faces];
// Converts a 2d path to a path on a cylinder at radius r // Converts a 2d path to a path on a cylinder at radius r
function _cyl_hole(r, path) = function _cyl_hole(r, path) =