From 57236506ff4a369796399c0bb3775d086d71219e Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 27 Aug 2019 00:34:09 -0700 Subject: [PATCH] Added VNF functions and modules. --- geometry.scad | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) diff --git a/geometry.scad b/geometry.scad index f9108e1..5f24657 100644 --- a/geometry.scad +++ b/geometry.scad @@ -1645,5 +1645,296 @@ module region(r) } +// Section: Creating Polyhedrons with VNF Structures +// VNF stands for "Vertices'N'Faces". VNF structures are 2-item lists, `[VERTICES,FACES]` where the +// first item is a list of vertex points, and the second is a list of face indices into the vertex +// list. Each VNF is self contained, with face indices referring only to its own vertex list. +// You can construct a `polyhedron()` in parts by describing each part in a self-contained VNF, then +// merge the various VNFs to get the completed polyhedron vertex list and faces. + + +// Function: is_vnf() +// Description: Returns true if the given value looks passingly like a VNF structure. +function is_vnf(x) = is_list(x) && len(x)==2 && is_list(x[0]) && is_list(x[1]) && (x[0]==[] || is_vector(x[0][0])) && (x[1]==[] || is_vector(x[1][0])); + + +// Function: is_vnf_list() +// Description: Returns true if the given value looks passingly like a list of VNF structures. +function is_vnf_list(x) = is_list(x) && all([for (v=x) is_vnf(v)]); + + +// Function: vnf_vertices() +// Description: Given a VNF structure, returns the list of vertex points. +function vnf_vertices(vnf) = vnf[0]; + + +// Function: vnf_faces() +// Description: Given a VNF structure, returns the list of faces, where each face is a list of indices into the VNF vertex list. +function vnf_faces(vnf) = vnf[1]; + + +// Function: vnf_get_vertex() +// Usage: +// vvnf = vnf_get_vertex(vnf, p); +// Description: +// Finds the index number of the given vertex point `p` in the given VNF structure `vnf`. If said +// point does not already exist in the VNF vertex list, it is added. Returns: `[INDEX, VNF]` where +// INDEX if the index of the point, and VNF is the possibly modified new VNF structure. +// If `p` is given as a list of points, then INDEX will be a list of indices. +// Arguments: +// vnf = The VNF structue to get the point index from. +// p = The point, or list of points to get the index of. +// Example: +// vnf1 = vnf_get_vertex(p=[3,5,8]); // Returns: [0, [[[3,5,8]],[]]] +// vnf2 = vnf_get_vertex(vnf1, p=[3,2,1]); // Returns: [1, [[[3,5,8],[3,2,1]],[]]] +// vnf3 = vnf_get_vertex(vnf2, p=[3,5,8]); // Returns: [0, [[[3,5,8],[3,2,1]],[]]] +// vnf4 = vnf_get_vertex(vnf3, p=[[1,3,2],[3,2,1]]); // Returns: [[1,2], [[[3,5,8],[3,2,1],[1,3,2]],[]]] +function vnf_get_vertex(vnf=[[],[]], p) = + is_path(p)? _vnf_get_vertices(vnf, p) : + let( + p = quant(p,1/1024), // OpenSCAD internally quantizes objects to 1/1024. + v = search([p], vnf[0])[0] + ) [ + v != []? v : len(vnf[0]), + [ + concat(vnf[0], v != []? [] : [p]), + vnf[1] + ] + ]; + + +// Internal use only +function _vnf_get_vertices(vnf=[[],[]], pts, _i=0, _idxs=[]) = + _i>=len(pts)? [_idxs, vnf] : + let( + vvnf = vnf_get_vertex(vnf, pts[_i]) + ) _vnf_get_vertices(vvnf[1], pts, _i=_i+1, _idxs=concat(_idxs,[vvnf[0]])); + + +// Function: vnf_add_face() +// Usage: +// vnf_add_face(vnf, pts); +// Description: +// Given a VNF structure and a list of face vertex points, adds the face to the VNF structure. +// Returns the modified VNF structure `[VERTICES, FACES]`. It is up to the caller to make +// sure that the points are in the correct order to make the face normal point outwards. +// Arguments: +// vnf = The VNF structure to add a face to. +// pts = The vertex points for the face. +function vnf_add_face(vnf=[[],[]], pts) = + let(base=len(vnf[0])) [ + concat(vnf[0],pts), + concat(vnf[1],[[for (i=idx(pts)) i+base]]) + ]; + /* + let( + vvnf = vnf_get_vertex(vnf, pts), + face = deduplicate(vvnf[0], closed=true), + vnf2 = vvnf[1] + ) [ + vnf_vertices(vnf2), + concat(vnf_faces(vnf2), len(face)>2? [face] : []) + ]; + */ + + +// Function: vnf_add_faces() +// Usage: +// vnf_add_faces(vnf, faces); +// Description: +// Given a VNF structure and a list of faces, where each face is given as a list of vertex points, +// adds the faces to the VNF structure. Returns the modified VNF structure `[VERTICES, FACES]`. +// It is up to the caller to make sure that the points are in the correct order to make the face +// normals point outwards. +// Arguments: +// vnf = The VNF structure to add a face to. +// faces = The list of faces, where each face is given as a list of vertex points. +function vnf_add_faces(vnf=[[],[]], faces, _i=0) = + _i=len(vnfs)? _acc : + vnf_merge( + vnfs, _i=_i+1, + _acc = let(base=len(_acc[0])) [ + concat(_acc[0], vnfs[_i][0]), + concat(_acc[1], [for (f=vnfs[_i][1]) [for (i=f) i+base]]), + ] + ); + + +// Function: vnf_triangulate() +// Usage: +// vnf2 = vnf_triangulate(vnf); +// Description: +// Forces triangulation of faces in the VNF that have more than 3 vertices. +function vnf_triangulate(vnf) = + let( + vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf + ) [vnf[0], triangulate_faces(vnf[0], vnf[1])]; + + +// Function: vnf_vertex_array() +// Usage: +// vnf = vnf_vertex_array(points, cols, [caps], [cap1], [cap2], [reverse], [col_wrap], [row_wrap], [vnf]); +// Description: +// 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. +// Arguments: +// points = A list of vertices to divide into columns and rows. +// cols = The number of points in a column. +// caps = If true, add endcap faces to the first AND last rows. +// cap1 = If true, add an endcap face to the first row. +// cap2 = If true, add an endcap face to the last row. +// 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", and "quincunx". +// vnf = If given, add all the vertices and faces to this existing VNF structure. +// Example(3D): +// vnf = vnf_vertex_array( +// points=[ +// for (h = [0:5:180-EPSILON], t = [0:5:360-EPSILON]) +// cylindrical_to_xyz(100 + 12 * cos((h/2 + t)*6), t, h) +// ], +// cols=360/5, col_wrap=true, caps=true, reverse=true, style="alt" +// ); +// vnf_polyhedron(vnf); +// Example(3D): Both `col_wrap` and `row_wrap` are true to make a torus. +// profile = circle(d=20); +// vnf = vnf_vertex_array( +// points=[ +// for (a=[0:5:360-EPSILON]) +// each zrot(a,p=right(30,p=xrot(90,p=path3d(profile)))) +// ], +// cols=len(profile), col_wrap=true, row_wrap=true, reverse=true +// ); +// vnf_polyhedron(vnf); +// Example(3D): Möbius Strip. Note that `row_wrap` is not used, and the first and last profile copies are the same. +// profile = square([1,10],center=true); +// vnf = vnf_vertex_array( +// points=[ +// for (a=[0:5:360]) each +// zrot(a,p=right(30,p=xrot(90,p=zrot(a/2+60,p=path3d(profile))))) +// ], +// cols=len(profile), col_wrap=true, reverse=true +// ); +// vnf_polyhedron(vnf); +// Example(3D): Assembling a Polyhedron from Multiple Parts +// profile = circle(d=100); +// wall_points=[ +// for (a=[-90:2:90]) +// each up(a, p=scale([1-0.1*cos(a*6),1-0.1*cos((a+90)*6),1], p=path3d(profile))) +// ]; +// cap_profile = select(wall_points,0,len(profile)-1); +// cap_points=[ +// for (a=[0:0.01:1.001]) each +// up(90-5*sin(a*360*2), p=scale([a,a,1], p=cap_profile)) +// ]; +// vnf1 = vnf_vertex_array( +// points=wall_points, cols=len(profile), col_wrap=true +// ); +// vnf2 = vnf_vertex_array( +// points=down(90, p=zscale(-1, p=cap_points)), +// cols=len(profile), col_wrap=true +// ); +// vnf3 = vnf_vertex_array( +// points=up(90, p=cap_points), +// cols=len(profile), col_wrap=true, reverse=true +// ); +// vnf_polyhedron([vnf1, vnf2, vnf3]); +function vnf_vertex_array( + points, cols, + caps, cap1, cap2, + col_wrap=false, + row_wrap=false, + reverse=false, + style="default", + vnf=[[],[]] +) = + assert(len(points)%cols==0) + assert((!caps)||(caps&&col_wrap)) + assert(in_list(style,["default","alt","quincunx"])) + let( + pcnt = len(points), + rows = pcnt/cols, + cap1 = first_defined([cap1,caps,false]), + cap2 = first_defined([cap2,caps,false]), + colcnt = cols - (col_wrap?0:1), + rowcnt = rows - (row_wrap?0:1) + ) + vnf_merge([ + vnf, [ + concat( + points, + style!="quincunx"? [] : [ + for (r = [0:1:rowcnt-1]) ( + for (c = [0:1:colcnt-1]) ( + let( + i1 = ((r+0)%rows)*cols + ((c+0)%cols), + i2 = ((r+1)%rows)*cols + ((c+0)%cols), + i3 = ((r+1)%rows)*cols + ((c+1)%cols), + i4 = ((r+0)%rows)*cols + ((c+1)%cols) + ) mean([points[i1], points[i2], points[i3], points[i4]]) + ) + ) + ] + ), + concat( + [ + for (r = [0:1:rowcnt-1]) ( + for (c = [0:1:colcnt-1]) each ( + let( + i1 = ((r+0)%rows)*cols + ((c+0)%cols), + i2 = ((r+1)%rows)*cols + ((c+0)%cols), + i3 = ((r+1)%rows)*cols + ((c+1)%cols), + i4 = ((r+0)%rows)*cols + ((c+1)%cols) + ) + style=="quincunx"? ( + let(i5 = pcnt + r*colcnt + c) + reverse? [[i1,i2,i5],[i2,i3,i5],[i3,i4,i5],[i4,i1,i5]] : [[i1,i5,i2],[i2,i5,i3],[i3,i5,i4],[i4,i5,i1]] + ) : style=="alt"? ( + reverse? [[i1,i2,i4],[i2,i3,i4]] : [[i1,i4,i2],[i2,i4,i3]] + ) : ( + reverse? [[i1,i2,i3],[i1,i3,i4]] : [[i1,i3,i2],[i1,i4,i3]] + ) + ) + ) + ], + !cap1? [] : [ + reverse? + [for (c = [0:1:cols-1]) c] : + [for (c = [cols-1:-1:0]) c] + ], + !cap2? [] : [ + reverse? + [for (c = [cols-1:-1:0]) (rows-1)*cols + c] : + [for (c = [0:1:cols-1]) (rows-1)*cols + c] + ] + ) + ] + ]); + + +// Module: vnf_polyhedron() +// Usage: +// vnf_polyhedron(vnf); +// vnf_polyhedron([VNF, VNF, VNF, ...]); +// Description: +// Given a VNF structure, or a list of VNF structures, creates a polyhedron from them. +// Arguments: +// vnf = A VNF structure, or list of VNF structures. +module vnf_polyhedron(vnf) { + vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf; + polyhedron(vnf[0], vnf[1]); +} + // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap