From 3bc59ceeb1175e6433b23511e929691dfca9e414 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Fri, 22 Aug 2025 18:24:50 -0700 Subject: [PATCH 1/8] Fixed octa spheroid() symmetry. --- shapes3d.scad | 131 +++++++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/shapes3d.scad b/shapes3d.scad index de8e5817..81b92380 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3359,12 +3359,69 @@ function _dual_vertices(vnf) = ]; +function _make_octa_sphere(r) = + let( + subdivs = quantup(segs(r),4)/4, + edge1 = [for (p = [0:1:subdivs]) spherical_to_xyz(r,0,p/subdivs*90)], + edge2 = zrot(90, p=edge1), + edge3 = xrot(-90, p=edge1), + get_pts = function(e1, e2) [ + [ e1[0] ], + for (p = [1:1:subdivs]) + let( + p1 = e1[p], + p2 = e2[p], + vec = vector_axis(p1, p2), + ang = vector_angle(p1, p2) + ) [ + for (t = [0:1:p]) + let( + subang = lerp(0, ang, t/p), + pt = rot(a=subang, v=vec, p=p1) + ) pt + ] + ], + pts1 = get_pts(edge1, edge2), + pts2 = get_pts(reverse(edge3), reverse(edge1)), + pts3 = get_pts(reverse(edge2), edge3), + rot_tri = function(tri) + let( + ll = len(last(tri)) - 1 + ) [ + for (u = [0:1:ll]) [ + for (v = [0:1:u]) + let(c = u-v, r = ll-v) + tri[r][c] + ] + ], + pts2b = rot_tri(rot_tri(pts2)), + pts3b = rot_tri(pts3), + pts = [ + for (u = [0:1:subdivs]) [ + for (v = [0:1:u]) + let( + p1 = pts1[u][v], + p2 = pts2b[u][v], + p3 = pts3b[u][v], + mean = (p1 + p2 + p3) / 3 + ) mean + ] + ], + octant_vnf = vnf_tri_array(pts), + top_vnf = vnf_join([ + for (a=[0:90:359]) + zrot(a, p=octant_vnf) + ]), + bot_vnf = zflip(p=top_vnf), + full_vnf = vnf_join([top_vnf, bot_vnf]) + ) full_vnf; + + function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orient=UP) = let( r = get_radius(r=r, d=d, dflt=1), hsides = segs(r), vsides = max(2,ceil(hsides/2)), - octa_steps = round(max(4,hsides)/4), icosa_steps = round(max(5,hsides)/5), stagger = style=="stagger" ) @@ -3390,6 +3447,10 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or ) [reorient(anchor,spin,orient, r=r, p=dualvert), faces] : + style=="octa" ? + let( vnf = _make_octa_sphere(r) ) + reorient(anchor,spin,orient, r=r, p=vnf) + : style=="icosa" ? // subdivide faces of an icosahedron and project them onto a sphere let( N = icosa_steps-1, @@ -3445,19 +3506,6 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or spherical_to_xyz(r, theta, phi), spherical_to_xyz(r, 0, 180) ] - : style=="octa"? - let( - meridians = [ - 1, - for (i = [1:1:octa_steps]) i*4, - for (i = [octa_steps-1:-1:1]) i*4, - 1, - ] - ) - [ - for (i=idx(meridians), j=[0:1:meridians[i]-1]) - spherical_to_xyz(r, j*360/meridians[i], i*180/(len(meridians)-1)) - ] : assert(in_list(style,["orig","aligned","stagger","octa","icosa"])), lv = len(verts), faces = circum && style=="stagger" ? @@ -3499,52 +3547,15 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or ] ) ] - : style=="orig"? [ - [for (i=[0:1:hsides-1]) hsides-i-1], - [for (i=[0:1:hsides-1]) lv-hsides+i], - for (i=[0:1:vsides-2], j=[0:1:hsides-1]) - each [ - [(i+1)*hsides+j, i*hsides+j, i*hsides+(j+1)%hsides], - [(i+1)*hsides+j, i*hsides+(j+1)%hsides, (i+1)*hsides+(j+1)%hsides], - ] - ] - : /*style=="octa"?*/ - let( - meridians = [ - 0, 1, - for (i = [1:1:octa_steps]) i*4, - for (i = [octa_steps-1:-1:1]) i*4, - 1, - ], - offs = cumsum(meridians), - pc = last(offs)-1, - os = octa_steps * 2 - ) - [ - for (i=[0:1:3]) [0, 1+(i+1)%4, 1+i], - for (i=[0:1:3]) [pc-0, pc-(1+(i+1)%4), pc-(1+i)], - for (i=[1:1:octa_steps-1]) - let(m = meridians[i+2]/4) - for (j=[0:1:3], k=[0:1:m-1]) - let( - m1 = meridians[i+1], - m2 = meridians[i+2], - p1 = offs[i+0] + (j*m1/4 + k+0) % m1, - p2 = offs[i+0] + (j*m1/4 + k+1) % m1, - p3 = offs[i+1] + (j*m2/4 + k+0) % m2, - p4 = offs[i+1] + (j*m2/4 + k+1) % m2, - p5 = offs[os-i+0] + (j*m1/4 + k+0) % m1, - p6 = offs[os-i+0] + (j*m1/4 + k+1) % m1, - p7 = offs[os-i-1] + (j*m2/4 + k+0) % m2, - p8 = offs[os-i-1] + (j*m2/4 + k+1) % m2 - ) - each [ - [p1, p4, p3], - if (k Date: Fri, 22 Aug 2025 18:36:19 -0700 Subject: [PATCH 2/8] Minor radius correction. --- shapes3d.scad | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shapes3d.scad b/shapes3d.scad index 81b92380..4638079c 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3390,8 +3390,8 @@ function _make_octa_sphere(r) = ) [ for (u = [0:1:ll]) [ for (v = [0:1:u]) - let(c = u-v, r = ll-v) - tri[r][c] + let(col = u-v, row = ll-v) + tri[row][col] ] ], pts2b = rot_tri(rot_tri(pts2)), @@ -3404,7 +3404,7 @@ function _make_octa_sphere(r) = p2 = pts2b[u][v], p3 = pts3b[u][v], mean = (p1 + p2 + p3) / 3 - ) mean + ) unit(mean) * r ] ], octant_vnf = vnf_tri_array(pts), From 96c7623f8687d540c985cd00400123d14fe8a461 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Sun, 24 Aug 2025 15:44:45 -0700 Subject: [PATCH 3/8] Added code documentation to _make_octa_sphere() --- shapes3d.scad | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/shapes3d.scad b/shapes3d.scad index 4638079c..cf645e99 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3361,29 +3361,34 @@ function _dual_vertices(vnf) = function _make_octa_sphere(r) = let( + // Get number of triangles on each side of each octant. subdivs = quantup(segs(r),4)/4, + // Get octant edge vertices, on sphere surface, for all three octant edges. edge1 = [for (p = [0:1:subdivs]) spherical_to_xyz(r,0,p/subdivs*90)], edge2 = zrot(90, p=edge1), edge3 = xrot(-90, p=edge1), + // Given two edges, calculate interior vertices by rotating along greater circles. get_pts = function(e1, e2) [ - [ e1[0] ], + [ e1[0] ], // shared vertex where edges meet. for (p = [1:1:subdivs]) let( - p1 = e1[p], + p1 = e1[p], // for each matching pair of edge vertices... p2 = e2[p], - vec = vector_axis(p1, p2), - ang = vector_angle(p1, p2) + vec = vector_axis(p1, p2), // get rotation axis... + ang = vector_angle(p1, p2) // and angle between them, WRT the origin. ) [ - for (t = [0:1:p]) + for (t = [0:1:p]) // Subdivide this greater circle let( subang = lerp(0, ang, t/p), pt = rot(a=subang, v=vec, p=p1) ) pt ] ], + // Calculate all the triangular vertex arrays for all three edge pairings. pts1 = get_pts(edge1, edge2), pts2 = get_pts(reverse(edge3), reverse(edge1)), pts3 = get_pts(reverse(edge2), edge3), + // Rotate the calculated triangular arrays to match up. rot_tri = function(tri) let( ll = len(last(tri)) - 1 @@ -3396,6 +3401,7 @@ function _make_octa_sphere(r) = ], pts2b = rot_tri(rot_tri(pts2)), pts3b = rot_tri(pts3), + // Average the respecive vertices from each triangular array, and normalize them to the radius. pts = [ for (u = [0:1:subdivs]) [ for (v = [0:1:u]) @@ -3403,21 +3409,25 @@ function _make_octa_sphere(r) = p1 = pts1[u][v], p2 = pts2b[u][v], p3 = pts3b[u][v], - mean = (p1 + p2 + p3) / 3 + mean = p1 + p2 + p3 ) unit(mean) * r ] ], + // Calculate the triangulations of the averaged octant vertices. octant_vnf = vnf_tri_array(pts), + // Make 4 rotated copies of the octant to get the top of the sphere. top_vnf = vnf_join([ for (a=[0:90:359]) zrot(a, p=octant_vnf) ]), + // Copy the top, flipped on the Z axis to get the bottom, and put them together into one VNF. bot_vnf = zflip(p=top_vnf), full_vnf = vnf_join([top_vnf, bot_vnf]) ) full_vnf; function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orient=UP) = + assert(in_list(style, ["orig", "aligned", "stagger", "octa", "icosa"])) let( r = get_radius(r=r, d=d, dflt=1), hsides = segs(r), @@ -3547,7 +3557,7 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or ] ) ] - : /*style=="orig"?*/ + : style=="orig"? [ [for (i=[0:1:hsides-1]) hsides-i-1], [for (i=[0:1:hsides-1]) lv-hsides+i], @@ -3556,6 +3566,7 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or [(i+1)*hsides+j, i*hsides+(j+1)%hsides, (i+1)*hsides+(j+1)%hsides], ] ] + : assert(in_list(style,["orig","aligned","stagger","octa","icosa"])) ) [reorient(anchor,spin,orient, r=r, p=verts), faces]; From e468ab79b3a50ddb419b8f0e6ccc51554fc4a9ac Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Sun, 24 Aug 2025 17:58:03 -0700 Subject: [PATCH 4/8] Optimized octa sphere generation into one pass. --- shapes3d.scad | 75 ++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 43 deletions(-) diff --git a/shapes3d.scad b/shapes3d.scad index cf645e99..96eae4c9 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3359,67 +3359,56 @@ function _dual_vertices(vnf) = ]; + function _make_octa_sphere(r) = let( // Get number of triangles on each side of each octant. subdivs = quantup(segs(r),4)/4, - // Get octant edge vertices, on sphere surface, for all three octant edges. + + // Get known exact octant edge vertices, on sphere surface, for all three octant edges. edge1 = [for (p = [0:1:subdivs]) spherical_to_xyz(r,0,p/subdivs*90)], edge2 = zrot(90, p=edge1), edge3 = xrot(-90, p=edge1), - // Given two edges, calculate interior vertices by rotating along greater circles. - get_pts = function(e1, e2) [ - [ e1[0] ], // shared vertex where edges meet. - for (p = [1:1:subdivs]) - let( - p1 = e1[p], // for each matching pair of edge vertices... - p2 = e2[p], - vec = vector_axis(p1, p2), // get rotation axis... - ang = vector_angle(p1, p2) // and angle between them, WRT the origin. - ) [ - for (t = [0:1:p]) // Subdivide this greater circle - let( - subang = lerp(0, ang, t/p), - pt = rot(a=subang, v=vec, p=p1) - ) pt - ] - ], - // Calculate all the triangular vertex arrays for all three edge pairings. - pts1 = get_pts(edge1, edge2), - pts2 = get_pts(reverse(edge3), reverse(edge1)), - pts3 = get_pts(reverse(edge2), edge3), - // Rotate the calculated triangular arrays to match up. - rot_tri = function(tri) + + // Function to calculate greater arc point between two points + greater_arc_point = function(p1, p2, u) let( - ll = len(last(tri)) - 1 - ) [ - for (u = [0:1:ll]) [ - for (v = [0:1:u]) - let(col = u-v, row = ll-v) - tri[row][col] - ] - ], - pts2b = rot_tri(rot_tri(pts2)), - pts3b = rot_tri(pts3), - // Average the respecive vertices from each triangular array, and normalize them to the radius. + vec = vector_axis(p1, p2), + ang = vector_angle(p1, p2), + subang = lerp(0, ang, u), + pt = rot(a=subang, v=vec, p=p1) + ) pt, + + // Calculate greater circle subdivisions between octant edges to get vertices. pts = [ - for (u = [0:1:subdivs]) [ - for (v = [0:1:u]) - let( - p1 = pts1[u][v], - p2 = pts2b[u][v], - p3 = pts3b[u][v], - mean = p1 + p2 + p3 - ) unit(mean) * r + for (row = [0:1:subdivs]) [ + for (col = [0:1:row]) + if (col == 0) + edge1[row] // Point on first edge is exact. (± FP rounding) + else if (col == row) + edge2[row] // Point on second edge is exact. (± FP rounding) + else if (row == subdivs) + edge3[subdivs-col] // Point on third edge is exact. (± FP rounding) + else // Calculating interior point. + let( + u1 = col / row, + u2 = subdivs-row+col, + p1 = greater_arc_point(edge1[row], edge2[row], col/row), + p2 = greater_arc_point(edge1[row-col], edge3[row-col], col/(subdivs-row+col)), + p3 = greater_arc_point(edge2[col], edge3[subdivs-col], (row-col)/(subdivs-col)), + ) unit(p1 + p2 + p3) * r // Average greater circle points beween the three edge pairs. ] ], + // Calculate the triangulations of the averaged octant vertices. octant_vnf = vnf_tri_array(pts), + // Make 4 rotated copies of the octant to get the top of the sphere. top_vnf = vnf_join([ for (a=[0:90:359]) zrot(a, p=octant_vnf) ]), + // Copy the top, flipped on the Z axis to get the bottom, and put them together into one VNF. bot_vnf = zflip(p=top_vnf), full_vnf = vnf_join([top_vnf, bot_vnf]) From 0b9c3e58bfbb7a0f4d4008e1ca8d6a2d6b69b69c Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Sun, 24 Aug 2025 18:28:29 -0700 Subject: [PATCH 5/8] Fixed trailing comma. --- shapes3d.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shapes3d.scad b/shapes3d.scad index 96eae4c9..1f0d71e4 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3395,7 +3395,7 @@ function _make_octa_sphere(r) = u2 = subdivs-row+col, p1 = greater_arc_point(edge1[row], edge2[row], col/row), p2 = greater_arc_point(edge1[row-col], edge3[row-col], col/(subdivs-row+col)), - p3 = greater_arc_point(edge2[col], edge3[subdivs-col], (row-col)/(subdivs-col)), + p3 = greater_arc_point(edge2[col], edge3[subdivs-col], (row-col)/(subdivs-col)) ) unit(p1 + p2 + p3) * r // Average greater circle points beween the three edge pairs. ] ], From dfe5baf2303f66caf5fa99b14fc3d8d905e10f45 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Sun, 24 Aug 2025 19:15:51 -0700 Subject: [PATCH 6/8] Removed unised code in octa sphere. --- shapes3d.scad | 2 -- 1 file changed, 2 deletions(-) diff --git a/shapes3d.scad b/shapes3d.scad index 1f0d71e4..e80ef9b1 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3391,8 +3391,6 @@ function _make_octa_sphere(r) = edge3[subdivs-col] // Point on third edge is exact. (± FP rounding) else // Calculating interior point. let( - u1 = col / row, - u2 = subdivs-row+col, p1 = greater_arc_point(edge1[row], edge2[row], col/row), p2 = greater_arc_point(edge1[row-col], edge3[row-col], col/(subdivs-row+col)), p3 = greater_arc_point(edge2[col], edge3[subdivs-col], (row-col)/(subdivs-col)) From e1002748d3dfcdb53c7c3ba456ba9cce0879d8d1 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Mon, 25 Aug 2025 05:22:16 -0700 Subject: [PATCH 7/8] Removed VNF construction from octa spheroids. --- shapes3d.scad | 114 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 22 deletions(-) diff --git a/shapes3d.scad b/shapes3d.scad index e80ef9b1..a8216412 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3370,14 +3370,20 @@ function _make_octa_sphere(r) = edge2 = zrot(90, p=edge1), edge3 = xrot(-90, p=edge1), - // Function to calculate greater arc point between two points - greater_arc_point = function(p1, p2, u) - let( - vec = vector_axis(p1, p2), - ang = vector_angle(p1, p2), - subang = lerp(0, ang, u), - pt = rot(a=subang, v=vec, p=p1) - ) pt, + // Precache edge points, axes, and angles + edge_pts1 = [for (i = [0:1:subdivs]) [edge1[i], edge2[i]]], + edge_pts2 = [for (i = [0:1:subdivs]) [edge1[subdivs-i], edge3[subdivs-i]]], + edge_pts3 = [for (i = [0:1:subdivs]) [edge2[subdivs-i], edge3[i]]], + + // Precache greater circle axes + edge_axis1 = [UP, for (i = [1:1:subdivs]) vector_axis(edge_pts1[i][0], edge_pts1[i][1])], + edge_axis2 = [UP, for (i = [1:1:subdivs]) vector_axis(edge_pts2[i][0], edge_pts2[i][1])], + edge_axis3 = [UP, for (i = [1:1:subdivs]) vector_axis(edge_pts3[i][0], edge_pts3[i][1])], + + // Precache greater circle angles + edge_ang1 = [0, for (i = [1:1:subdivs]) vector_angle(edge_pts1[i][0], edge_pts1[i][1])], + edge_ang2 = [0, for (i = [1:1:subdivs]) vector_angle(edge_pts2[i][0], edge_pts2[i][1])], + edge_ang3 = [0, for (i = [1:1:subdivs]) vector_angle(edge_pts3[i][0], edge_pts3[i][1])], // Calculate greater circle subdivisions between octant edges to get vertices. pts = [ @@ -3391,26 +3397,90 @@ function _make_octa_sphere(r) = edge3[subdivs-col] // Point on third edge is exact. (± FP rounding) else // Calculating interior point. let( - p1 = greater_arc_point(edge1[row], edge2[row], col/row), - p2 = greater_arc_point(edge1[row-col], edge3[row-col], col/(subdivs-row+col)), - p3 = greater_arc_point(edge2[col], edge3[subdivs-col], (row-col)/(subdivs-col)) + i1 = row, + i2 = subdivs - row + col, + i3 = subdivs - col, + u1 = col / i1, + u2 = col / i2, + u3 = (row - col) / i3, + p1 = rot(a=lerp(0, edge_ang1[i1], u1), v=edge_axis1[i1], p=edge_pts1[i1][0]), + p2 = rot(a=lerp(0, edge_ang2[i2], u2), v=edge_axis2[i2], p=edge_pts2[i2][0]), + p3 = rot(a=lerp(0, edge_ang3[i3], u3), v=edge_axis3[i3], p=edge_pts3[i3][0]) ) unit(p1 + p2 + p3) * r // Average greater circle points beween the three edge pairs. ] ], - // Calculate the triangulations of the averaged octant vertices. - octant_vnf = vnf_tri_array(pts), + rows = [ + [ pts[0][0] ], - // Make 4 rotated copies of the octant to get the top of the sphere. - top_vnf = vnf_join([ - for (a=[0:90:359]) - zrot(a, p=octant_vnf) - ]), + for (row = [1:1:subdivs]) [ + for (a = [0:90:359]) + each zrot(a, p=select(pts[row], [0:1:row-1])) + ], - // Copy the top, flipped on the Z axis to get the bottom, and put them together into one VNF. - bot_vnf = zflip(p=top_vnf), - full_vnf = vnf_join([top_vnf, bot_vnf]) - ) full_vnf; + for (row = [subdivs-1:-1:1]) [ + for (a = [0:90:359]) + each zrot(a, p=zflip(p=select(pts[row], [0:1:row-1]))) + ], + + [ zflip(p=pts[0][0]) ] + ], + verts = flatten(rows), + offsets = cumsum([0, for (row = rows) len(row)]), + faces = [ + for (i = [0:1:3]) + let( + l1 = len(rows[1]), + o1 = offsets[1] + ) + [i+o1, 0, (i+1)%l1+o1], + + for (j = [1:1:subdivs-1]) + let( + l1 = len(rows[j]), + l2 = len(rows[j+1]), + o1 = offsets[j], + o2 = offsets[j+1], + q1 = len(rows[j])/4, + q2 = len(rows[j+1])/4 + ) + for (n = [0:1:3]) + each [ + [o2+q2*n, o1+q1*n, o2+(q2*n+1)%l2], + for (i = [0:1:q1-1]) each [ + [o2+(i+q2*n+1)%l2, o1+(i+q1*n+1)%l1, o2+(i+q2*n+2)%l2], + [o2+(i+q2*n+1)%l2, o1+(i+q1*n)%l1, o1+(i+q1*n+1)%l1] + ] + ], + + for (j = [subdivs:1:2*subdivs-2]) + let( + l1 = len(rows[j]), + l2 = len(rows[j+1]), + o1 = offsets[j], + o2 = offsets[j+1], + q1 = len(rows[j])/4, + q2 = len(rows[j+1])/4 + ) + for (n = [0:1:3]) + each [ + [o2+q2*n, o1+q1*n, o1+(q1*n+1)%l1], + for (i = [0:1:q1-2]) each [ + [o2+(i+q2*n)%l2, o1+(i+q1*n+1)%l1, o2+(i+q2*n+1)%l2], + [o1+(i+q1*n+2)%l1, o2+(i+q2*n+1)%l2, o1+(i+q1*n+1)%l1] + ] + ], + + for (i = [0:1:3]) + let( + row = len(rows) -2, + l1 = len(rows[row]), + o1 = offsets[row], + o2 = offsets[row+1] + ) + [o2, o1+i, o1+(i+1)%l1] + ] + ) [verts, faces]; function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orient=UP) = From b71668bdcc67356030e2cc295f8c828705050f0d Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Mon, 25 Aug 2025 05:28:13 -0700 Subject: [PATCH 8/8] Corrected a comment in octa sphere code. --- shapes3d.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shapes3d.scad b/shapes3d.scad index a8216412..8a16c0fb 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3370,7 +3370,7 @@ function _make_octa_sphere(r) = edge2 = zrot(90, p=edge1), edge3 = xrot(-90, p=edge1), - // Precache edge points, axes, and angles + // Precache edge points edge_pts1 = [for (i = [0:1:subdivs]) [edge1[i], edge2[i]]], edge_pts2 = [for (i = [0:1:subdivs]) [edge1[subdivs-i], edge3[subdivs-i]]], edge_pts3 = [for (i = [0:1:subdivs]) [edge2[subdivs-i], edge3[i]]],