Speed improvement for vnf_vertex_array by changing deduplicate

testing.  Add "min_edge" style to vnf vertex array.
eliminate _skin_core, replace with vnf_vertex_array, add style option
to all modules/functions.
fix bug in path_normals (not normalized)
This commit is contained in:
Adrian Mariano
2021-04-13 19:27:42 -04:00
parent dea5949df8
commit 4a179920cf
3 changed files with 64 additions and 94 deletions

View File

@@ -380,7 +380,7 @@ function path_normals(path, tangents, closed=false) =
dim == 2 ? [tangents[i].y,-tangents[i].x]
: let(v=cross(cross(pts[1]-pts[0], pts[2]-pts[0]),tangents[i]))
assert(norm(v)>EPSILON, "3D path contains collinear points")
v
unit(v)
];

115
skin.scad
View File

@@ -13,9 +13,9 @@
// Function&Module: skin()
// Usage: As module:
// skin(profiles, slices, <z=>, <refine=>, <method=>, <sampling=>, <caps=>, <closed=>, <convexity=>, <anchor=>,<cp=>,<spin=>,<orient=>,<extent=>) <attachments>;
// skin(profiles, slices, <z=>, <refine=>, <method=>, <sampling=>, <caps=>, <closed=>, <style=>, <convexity=>, <anchor=>,<cp=>,<spin=>,<orient=>,<extent=>) <attachments>;
// Usage: As function:
// vnf = skin(profiles, slices, <z=>, <refine=>, <method=>, <sampling=>, <caps=>, <closed=>);
// vnf = skin(profiles, slices, <z=>, <refine=>, <method=>, <sampling=>, <caps=>, <closed=>, <style=>);
// Description:
// Given a list of two or more path `profiles` in 3d space, produces faces to skin a surface between
// the profiles. Optionally the first and last profiles can have endcaps, or the first and last profiles
@@ -152,6 +152,7 @@
// orient = Vector to rotate top towards after spin (module only)
// extent = use extent method for computing anchors. (module only) Default: false
// cp = set centerpoint for anchor computation. (module only) Default: object centroid
// style = vnf_vertex_array style. Default: "min_edge"
// Example:
// skin([octagon(4), circle($fn=70,r=2)], z=[0,3], slices=10);
// Example: Rotating the pentagon place the zero index at different locations, giving a twist
@@ -376,10 +377,10 @@
// stroke(zrot(30, p=yscale(0.5, p=circle(d=120))),width=10,closed=true);
// }
// }
module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, convexity=10,
module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, style="min_edge", convexity=10,
anchor="origin",cp,spin=0, orient=UP, extent=false)
{
vnf = skin(profiles, slices, refine, method, sampling, caps, closed, z);
vnf = skin(profiles, slices, refine, method, sampling, caps, closed, z, style);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
vnf_polyhedron(vnf,convexity=convexity);
@@ -388,7 +389,7 @@ module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=
}
function skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z) =
function skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, style="min_edge") =
assert(is_def(slices),"The slices argument must be specified.")
assert(is_list(profiles) && len(profiles)>1, "Must provide at least two profiles")
let( bad = [for(i=idx(profiles)) if (!(is_path(profiles[i]) && len(profiles[i])>2)) i])
@@ -483,61 +484,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
)
each subdivide_and_slice(pair,slices[i], nsamples, method=sampling)]
)
_skin_core(full_list, caps=fullcaps);
function _skin_core(profiles, caps) =
let(
vertices = [for (prof=profiles) each prof],
plens = [for (prof=profiles) len(prof)],
sidefaces = [
for(pidx=idx(profiles,e=-2))
let(
prof1 = profiles[pidx%len(profiles)],
prof2 = profiles[(pidx+1)%len(profiles)],
voff = default(sum([for (i=[0:1:pidx-1]) plens[i]]),0),
faces = [
for(
first = true,
finishing = false,
finished = false,
plen1 = len(prof1),
plen2 = len(prof2),
i=0, j=0, side=0;
!finished;
side =
let(
p1a = prof1[(i+0)%plen1],
p1b = prof1[(i+1)%plen1],
p2a = prof2[(j+0)%plen2],
p2b = prof2[(j+1)%plen2],
dist1 = norm(p1a-p2b),
dist2 = norm(p1b-p2a)
) (i==j) ? (dist1>dist2? 1 : 0) : (i<j ? 1 : 0) ,
p1 = voff + (i%plen1),
p2 = voff + (j%plen2) + plen1,
p3 = voff + (side? ((i+1)%plen1) : (((j+1)%plen2) + plen1)),
face = [p1, p3, p2],
i = i + (side? 1 : 0),
j = j + (side? 0 : 1),
first = false,
finished = finishing,
finishing = i>=plen1 && j>=plen2
) if (!first) face
]
) each faces
],
firstcap = !caps[0] ? [] : let(
prof1 = profiles[0]
) [[for (i=idx(prof1)) plens[0]-1-i]],
secondcap = !caps[1] ? [] : let(
prof2 = last(profiles),
eoff = sum(list_head(plens))
) [[for (i=idx(prof2)) eoff+i]]
) [vertices, concat(sidefaces,firstcap,secondcap)];
vnf_vertex_array(full_list, caps=fullcaps, col_wrap=true, style=style);
@@ -903,9 +850,9 @@ function associate_vertices(polygons, split, curpoly=0) =
// Function&Module: sweep()
// Usage: As Module
// sweep(shape, transforms, <closed>, <caps>, <convexity=>, <anchor=>, <spin=>, <orient=>, <extent=>) <attachments>;
// sweep(shape, transforms, <closed>, <caps>, <style>, <convexity=>, <anchor=>, <spin=>, <orient=>, <extent=>) <attachments>;
// Usage: As Function
// vnf = sweep(shape, transforms, <closed>, <caps>);
// vnf = sweep(shape, transforms, <closed>, <caps>, <style>);
// Description:
// The input `shape` must be a non-self-intersecting 2D polygon or region, and `transforms`
// is a list of 4x4 transformation matrices. The sweep algorithm applies each transformation in sequence
@@ -925,6 +872,7 @@ function associate_vertices(polygons, split, curpoly=0) =
// transforms = list of 4x4 matrices to apply
// closed = set to true to form a closed (torus) model. Default: false
// caps = true to create endcap faces when closed is false. Can be a singe boolean to specify endcaps at both ends, or a length 2 boolean array. Default is true if closed is false.
// style = vnf_vertex_array style. Default: "min_edge"
// ---
// convexity = convexity setting for use with polyhedron. (module only) Default: 10
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
@@ -953,7 +901,7 @@ function associate_vertices(polygons, split, curpoly=0) =
// inside = [for(i=[24:-1:2]) up(i)*rot(i)*scale(1.2*i/24+1)];
// sweep(shape, concat(outside,inside));
function sweep(shape, transforms, closed=false, caps) =
function sweep(shape, transforms, closed=false, caps, style="min_edge") =
assert(is_consistent(transforms, ident(4)), "Input transforms must be a list of numeric 4x4 matrices in sweep")
assert(is_path(shape,2) || is_region(shape), "Input shape must be a 2d path or a region.")
let(
@@ -979,13 +927,14 @@ function sweep(shape, transforms, closed=false, caps) =
vnf = vnf_merge(vnfs)
) vnf :
assert(len(shape)>=3, "shape must be a path of at least 3 non-colinear points")
_skin_core([for(i=[0:len(transforms)-(closed?0:1)]) apply(transforms[i%len(transforms)],path3d(shape))],caps=fullcaps);
vnf_vertex_array([for(i=[0:len(transforms)-(closed?0:1)]) apply(transforms[i%len(transforms)],path3d(shape))],
caps=fullcaps,col_wrap=true,style=style);
module sweep(shape, transforms, closed=false, caps, convexity=10,
module sweep(shape, transforms, closed=false, caps, style="min_edge", convexity=10,
anchor="origin",cp,spin=0, orient=UP, extent=false)
{
vnf = sweep(shape, transforms, closed, caps);
vnf = sweep(shape, transforms, closed, caps, style);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
vnf_polyhedron(vnf,convexity=convexity);
@@ -996,8 +945,8 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
// Function&Module: path_sweep()
// Usage: As module
// path_sweep(shape, path, <method>, <normal=>, <closed=>, <twist=>, <twist_by_length=>, <symmetry=>, <last_normal=>, <tangent=>, <relaxed=>, <caps=>, <convexity=>, <transforms=>, <anchor=>, <cp=>, <spin=>, <orient=>, <extent=>) <attachments>;
// vnf = path_sweep(shape, path, <method>, <normal=>, <closed=>, <twist=>, <twist_by_length=>, <symmetry=>, <last_normal=>, <tangent=>, <relaxed=>, <caps=>, <convexity=>, <transforms=>);
// path_sweep(shape, path, <method>, <normal=>, <closed=>, <twist=>, <twist_by_length=>, <symmetry=>, <last_normal=>, <tangent=>, <relaxed=>, <caps=>, <style=>, <convexity=>, <transforms=>, <anchor=>, <cp=>, <spin=>, <orient=>, <extent=>) <attachments>;
// vnf = path_sweep(shape, path, <method>, <normal=>, <closed=>, <twist=>, <twist_by_length=>, <symmetry=>, <last_normal=>, <tangent=>, <relaxed=>, <caps=>, <style=>, <convexity=>, <transforms=>);
// Description:
// Takes as input a 2D polygon path, and a 2d or 3d path and constructs a polyhedron by sweeping the shape along the path.
// When run as a module returns the polyhedron geometry. When run as a function returns a VNF by default or if you set `transforms=true`
@@ -1058,6 +1007,7 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
// tangent = a list of tangent vectors in case you need more accuracy (particularly at the end points of your curve)
// relaxed = set to true with the "manual" method to relax the orthogonality requirement of cross sections to the path tangent. Default: false
// caps = Can be a boolean or vector of two booleans. Set to false to disable caps at the two ends. Default: true
// style = vnf_vertex_array style. Default: "min_edge"
// transforms = set to true to return transforms instead of a VNF. These transforms can be manipulated and passed to sweep(). Default: false.
// convexity = convexity parameter for polyhedron(). Only accepted by the module version. Default: 10
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
@@ -1291,11 +1241,11 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
// circle(r=16,$fn=75),closed=true,
// twist=360/5*2,symmetry=5);
module path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
symmetry=1, last_normal, tangent, relaxed=false, caps, convexity=10,
symmetry=1, last_normal, tangent, relaxed=false, caps, style="min_edge", convexity=10,
anchor="origin",cp,spin=0, orient=UP, extent=false)
{
vnf = path_sweep(shape, path, method, normal, closed, twist, twist_by_length,
symmetry, last_normal, tangent, relaxed, caps);
symmetry, last_normal, tangent, relaxed, caps, style);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
vnf_polyhedron(vnf,convexity=convexity);
@@ -1305,7 +1255,7 @@ module path_sweep(shape, path, method="incremental", normal, closed=false, twist
function path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
symmetry=1, last_normal, tangent, relaxed=false, caps, transforms=false) =
symmetry=1, last_normal, tangent, relaxed=false, caps, style="min_edge", transforms=false) =
assert(!closed || twist % (360/symmetry)==0, str("For a closed sweep, twist must be a multiple of 360/symmetry = ",360/symmetry))
assert(closed || symmetry==1, "symmetry must be 1 when closed is false")
assert(is_integer(symmetry) && symmetry>0, "symmetry must be a positive integer")
@@ -1377,6 +1327,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
let (pathnormal = path_normals(path, tangents, closed))
assert(all_defined(pathnormal),"Natural normal vanishes on your curve, select a different method")
let( testnormals = [for(i=[0:len(pathnormal)-1-(closed?1:2)]) pathnormal[i]*select(pathnormal,i+2)],
a=[for(i=idx(testnormals)) testnormals[i]<.5 ? echo(str("Big change at index ",i," pn=",pathnormal[i]," pn2= ",select(pathnormal,i+2))):0],
dummy = min(testnormals) < .5 ? echo("WARNING: ***** Abrupt change in normal direction. Consider a different method *****") :0
)
[for(i=[0:L-(closed?0:1)]) let(
@@ -1393,14 +1344,14 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
apply(transform_list[L], rshape)),
dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model *****")
)
transforms ? transform_list : sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps);
transforms ? transform_list : sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps,style=style);
// Function&Module: path_sweep2d()
// Usage: as module
// path_sweep2d(shape, path, <closed>, <caps>, <quality>, <convexity=>, <anchor=>, <spin=>, <orient=>, <extent=>, <cp=>) <attachments>;
// path_sweep2d(shape, path, <closed>, <caps>, <quality>, <style>, <convexity=>, <anchor=>, <spin=>, <orient=>, <extent=>, <cp=>) <attachments>;
// Usage: as function
// vnf = path_sweep2d(shape, path, <closed>, <caps>, <quality>);
// vnf = path_sweep2d(shape, path, <closed>, <caps>, <quality>, <style>);
// Description:
// Takes an input 2D polygon (the shape) and a 2d path and constructs a polyhedron by sweeping the shape along the path.
// When run as a module returns the polyhedron geometry. When run as a function returns a VNF.
@@ -1417,6 +1368,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
// closed = path is a closed loop. Default: false
// caps = true to create endcap faces when closed is false. Can be a length 2 boolean array. Default is true if closed is false.
// quality = quality of offset used in calculation. Default: 1
// style = vnf_vertex_array style. Default: "min_edge"
// ---
// convexity = convexity parameter for polyhedron (module only) Default: 10
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
@@ -1440,7 +1392,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
// path_sweep2d(circle(r=3.25, $fn=32), select(ellipse,floor(L*.2),ceil(L*.8)),closed=false);
// path_sweep2d(circle(r=3.25, $fn=32), select(ellipse,floor(L*.7),ceil(L*.3)),closed=false);
function path_sweep2d(shape, path, closed=false, caps, quality=1) =
function path_sweep2d(shape, path, closed=false, caps, quality=1, style="min_edge") =
let(
caps = is_def(caps) ? caps
: closed ? false : true,
@@ -1464,15 +1416,16 @@ function path_sweep2d(shape, path, closed=false, caps, quality=1) =
]
)
)
_skin_core([
each proflist,
if (closed) proflist[0]
],caps=fullcaps);
vnf_vertex_array([
each proflist,
if (closed) proflist[0]
],caps=fullcaps,col_wrap=true,style=style);
module path_sweep2d(profile, path, closed=false, caps, quality=1, convexity=10,
module path_sweep2d(profile, path, closed=false, caps, quality=1, style="min_edge", convexity=10,
anchor="origin", cp, spin=0, orient=UP, extent=false)
{
vnf = path_sweep2d(profile, path, closed, caps, quality);
vnf = path_sweep2d(profile, path, closed, caps, quality, style);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
vnf_polyhedron(vnf,convexity=convexity);

View File

@@ -215,7 +215,12 @@ function vnf_triangulate(vnf) =
// Creates a VNF structure from a vertex list, by dividing the vertices into columns and rows,
// adding faces to tile the surface. You can optionally have faces added to wrap the last column
// back to the first column, or wrap the last row to the first. Endcaps can be added to either
// the first and/or last rows.
// the first and/or last rows. The style parameter determines how the quadrilaterals are divided into
// triangles. The default style is an arbitrary, systematic subdivision in the same direction. The "alt" style
// is the uniform subdivision in the other (alternate) direction. The "min_edge" style picks the shorter edge to
// subdivide for each quadrilateral, so the division may not be uniform across the shape. The "quincunx" style
// adds a vertex in the center of each quadrilateral and creates four triangles, and the "convex" style
// chooses the locally convex subdivision.
// Arguments:
// points = A list of vertices to divide into columns and rows.
// caps = If true, add endcap faces to the first AND last rows.
@@ -224,7 +229,7 @@ function vnf_triangulate(vnf) =
// col_wrap = If true, add faces to connect the last column to the first.
// row_wrap = If true, add faces to connect the last row to the first.
// reverse = If true, reverse all face normals.
// style = The style of subdividing the quads into faces. Valid options are "default", "alt", "quincunx", and "convex".
// style = The style of subdividing the quads into faces. Valid options are "default", "alt", "min_edge", "quincunx", and "convex".
// vnf = If given, add all the vertices and faces to this existing VNF structure.
// Example(3D):
// vnf = vnf_vertex_array(
@@ -290,7 +295,7 @@ function vnf_vertex_array(
) =
assert(!(any([caps,cap1,cap2]) && !col_wrap), "col_wrap must be true if caps are requested")
assert(!(any([caps,cap1,cap2]) && row_wrap), "Cannot combine caps with row_wrap")
assert(in_list(style,["default","alt","quincunx", "convex"]))
assert(in_list(style,["default","alt","quincunx", "convex","min_edge"]))
assert(is_consistent(points), "Non-rectangular or invalid point array")
let(
pts = flatten(points),
@@ -317,8 +322,9 @@ function vnf_vertex_array(
mean([pts[i1], pts[i2], pts[i3], pts[i4]])
]
)
vnf_merge(cleanup=true, [
vnf, [
vnf_merge(cleanup=false, [
vnf,
[
verts,
[
for (r = [0:1:rowcnt-1], c=[0:1:colcnt-1])
@@ -334,6 +340,14 @@ function vnf_vertex_array(
[[i1,i5,i2],[i2,i5,i3],[i3,i5,i4],[i4,i5,i1]]
: style=="alt"?
[[i1,i4,i2],[i2,i4,i3]]
: style=="min_edge"?
let(
d42=norm(pts[i4]-pts[i2]),
d13=norm(pts[i1]-pts[i3]),
shortface = d42<=d13 ? [[i1,i4,i2],[i2,i4,i3]]
: [[i1,i3,i2],[i1,i4,i3]]
)
shortface
: style=="convex"?
let(
fsets = [
@@ -347,17 +361,20 @@ function vnf_vertex_array(
)
fsets[test?0:1]
: [[i1,i3,i2],[i1,i4,i3]],
rfaces = reverse? [for (face=faces) reverse(face)] : faces,
dfaces = [for (face=rfaces)
let(dface = deduplicate_indexed(verts,face,closed=true))
if(len(dface)>=3) dface
]
// remove degenerate faces
culled_faces= [for(face=faces)
if (norm(verts[face[0]]-verts[face[1]])>EPSILON &&
norm(verts[face[1]]-verts[face[2]])>EPSILON &&
norm(verts[face[2]]-verts[face[0]])>EPSILON)
face
],
rfaces = reverse? [for (face=culled_faces) reverse(face)] : culled_faces
)
dfaces,
rfaces,
if (cap1) count(cols,reverse=!reverse),
if (cap2) count(cols,(rows-1)*cols, reverse=reverse)
]
]
]
]);