2019-10-21 23:44:39 +00:00
//////////////////////////////////////////////////////////////////////
// LibFile: vnf.scad
// VNF structures, holding Vertices 'N' Faces for use with `polyhedron().`
// To use, add the following lines to the beginning of your file:
// ```
// use <BOSL2/std.scad>
// use <BOSL2/vnf.scad>
// ```
//////////////////////////////////////////////////////////////////////
2019-11-04 03:12:50 +00:00
include < triangulation.scad >
2019-10-21 23:44:39 +00:00
// 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.
2020-03-10 01:35:14 +00:00
EMPTY_VNF = [ [ ] , [ ] ] ; // The standard empty VNF with no vertices or faces.
2019-10-21 23:44:39 +00:00
// Function: is_vnf()
2020-03-22 08:12:51 +00:00
// Usage:
// bool = is_vnf(x);
// Description:
// Returns true if the given value looks like a VNF structure.
function is_vnf ( x ) =
is_list ( x ) &&
len ( x ) = = 2 &&
is_list ( x [ 0 ] ) &&
is_list ( x [ 1 ] ) &&
( x [ 0 ] = = [ ] || ( len ( x [ 0 ] ) >= 3 && is_vector ( x [ 0 ] [ 0 ] ) ) ) &&
( x [ 1 ] = = [ ] || is_vector ( x [ 1 ] [ 0 ] ) ) ;
2019-10-21 23:44:39 +00:00
// 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 ] ;
2020-03-19 21:06:11 +00:00
// Function: vnf_quantize()
// Usage:
// vnf2 = vnf_quantize(vnf,[q]);
// Description:
// Quantizes the vertex coordinates of the VNF to the given quanta `q`.
// Arguments:
// vnf = The VNF to quantize.
// q = The quanta to quantize the VNF coordinates to.
function vnf_quantize ( vnf , q = pow ( 2 , - 12 ) ) =
[ [ for ( pt = vnf [ 0 ] ) quant ( pt , q ) ] , vnf [ 1 ] ] ;
2019-10-21 23:44:39 +00:00
// Function: vnf_get_vertex()
// Usage:
2020-03-20 20:54:38 +00:00
// vvnf = vnf_get_vertex(vnf, p);
2019-10-21 23:44:39 +00:00
// 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]],[]]]
2020-03-20 20:54:38 +00:00
function vnf_get_vertex ( vnf = EMPTY_VNF , p ) =
2019-10-21 23:44:39 +00:00
let (
2020-03-31 06:09:56 +00:00
p = is_vector ( p ) ? [ p ] : p ,
res = set_union ( vnf [ 0 ] , p , get_indices = true )
)
[ res [ 0 ] , [ res [ 1 ] , vnf [ 1 ] ] ] ;
2019-10-21 23:44:39 +00:00
// 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.
2020-03-20 20:54:38 +00:00
function vnf_add_face ( vnf = EMPTY_VNF , pts ) =
2019-11-07 00:17:33 +00:00
assert ( is_vnf ( vnf ) )
assert ( is_path ( pts ) )
2019-10-21 23:44:39 +00:00
let (
2020-03-31 06:09:56 +00:00
res = set_union ( vnf [ 0 ] , pts , get_indices = true ) ,
face = deduplicate ( res [ 0 ] , closed = true )
2019-10-21 23:44:39 +00:00
) [
2020-03-31 06:09:56 +00:00
res [ 1 ] ,
concat ( vnf [ 1 ] , len ( face ) > 2 ? [ face ] : [ ] )
2019-10-21 23:44:39 +00:00
] ;
// Function: vnf_add_faces()
// Usage:
2020-03-20 20:54:38 +00:00
// vnf_add_faces(vnf, faces);
2019-10-21 23:44:39 +00:00
// 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.
2020-03-31 10:27:07 +00:00
function vnf_add_faces ( vnf = EMPTY_VNF , faces ) =
2020-03-31 06:09:56 +00:00
assert ( is_vnf ( vnf ) )
assert ( is_list ( faces ) )
let (
2020-03-31 10:27:07 +00:00
res = set_union ( vnf [ 0 ] , flatten ( faces ) , get_indices = true ) ,
2020-03-31 06:09:56 +00:00
idxs = res [ 0 ] ,
nverts = res [ 1 ] ,
2020-03-31 10:27:07 +00:00
offs = cumsum ( [ 0 , for ( face = faces ) len ( face ) ] ) ,
2020-03-31 06:09:56 +00:00
ifaces = [
for ( i = idx ( faces ) ) [
for ( j = idx ( faces [ i ] ) )
idxs [ offs [ i ] + j ]
]
]
) [
nverts ,
concat ( vnf [ 1 ] , ifaces )
] ;
2019-10-21 23:44:39 +00:00
// Function: vnf_merge()
// Usage:
// vnf = vnf_merge([VNF, VNF, VNF, ...]);
// Description:
// Given a list of VNF structures, merges them all into a single VNF structure.
2020-03-10 01:35:14 +00:00
function vnf_merge ( vnfs = [ ] , _i = 0 , _acc = EMPTY_VNF ) =
2019-11-07 00:17:33 +00:00
( assert ( is_vnf_list ( vnfs ) ) _i >= len ( vnfs ) ) ? _acc :
2019-10-21 23:44:39 +00:00
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 ] ] ) ,
]
) ;
2020-03-15 11:25:37 +00:00
// Function: vnf_compact()
// Usage:
2020-03-20 20:54:38 +00:00
// cvnf = vnf_compact(vnf);
2020-03-15 11:25:37 +00:00
// Description:
// Takes a VNF and consolidates all duplicate vertices, and drops unreferenced vertices.
2020-03-20 20:54:38 +00:00
function vnf_compact ( vnf ) =
2020-03-15 11:25:37 +00:00
let (
2020-03-31 10:27:07 +00:00
vnf = is_vnf_list ( vnf ) ? vnf_merge ( vnf ) : vnf ,
2020-03-15 11:25:37 +00:00
verts = vnf [ 0 ] ,
faces = [
for ( face = vnf [ 1 ] ) [
for ( i = face ) verts [ i ]
]
]
2020-03-20 20:54:38 +00:00
) vnf_add_faces ( faces = faces ) ;
2020-03-15 11:25:37 +00:00
2019-10-21 23:44:39 +00:00
// 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 (
2020-03-31 10:27:07 +00:00
vnf = is_vnf_list ( vnf ) ? vnf_merge ( vnf ) : vnf ,
verts = vnf [ 0 ]
) [ verts , triangulate_faces ( verts , vnf [ 1 ] ) ] ;
2019-10-21 23:44:39 +00:00
// Function: vnf_vertex_array()
// Usage:
2020-02-29 05:42:22 +00:00
// vnf = vnf_vertex_array(points, [caps], [cap1], [cap2], [reverse], [col_wrap], [row_wrap], [vnf]);
2019-10-21 23:44:39 +00:00
// 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.
// 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]) [
// for (t = [0:5:360-EPSILON])
// cylindrical_to_xyz(100 + 12 * cos((h/2 + t)*6), t, h)
// ]
// ],
// 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.
// vnf = vnf_vertex_array(
// points=[
// for (a=[0:5:360-EPSILON])
2020-03-15 18:50:41 +00:00
// apply(
// zrot(a) * right(30) * xrot(90),
2020-03-15 18:54:11 +00:00
// path3d(circle(d=20))
2019-10-21 23:44:39 +00:00
// )
// ],
// 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.
// vnf = vnf_vertex_array(
// points=[
2020-03-15 18:50:41 +00:00
// for (a=[0:5:360]) apply(
// zrot(a) * right(30) * xrot(90) * zrot(a/2+60),
2020-03-15 18:54:11 +00:00
// path3d(square([1,10], center=true))
2019-10-21 23:44:39 +00:00
// )
// ],
// col_wrap=true, reverse=true
// );
// vnf_polyhedron(vnf);
// Example(3D): Assembling a Polyhedron from Multiple Parts
// wall_points = [
2020-03-15 18:50:41 +00:00
// for (a = [-90:2:90]) apply(
// up(a) * scale([1-0.1*cos(a*6),1-0.1*cos((a+90)*6),1]),
2020-03-15 18:54:11 +00:00
// path3d(circle(d=100))
2019-10-21 23:44:39 +00:00
// )
// ];
// cap = [
2020-03-15 18:50:41 +00:00
// for (a = [0:0.01:1+EPSILON]) apply(
// up(90-5*sin(a*360*2)) * scale([a,a,1]),
// wall_points[0]
2019-10-21 23:44:39 +00:00
// )
// ];
// cap1 = [for (p=cap) down(90, p=zscale(-1, p=p))];
// cap2 = [for (p=cap) up(90, p=p)];
// vnf1 = vnf_vertex_array(points=wall_points, col_wrap=true);
// vnf2 = vnf_vertex_array(points=cap1, col_wrap=true);
// vnf3 = vnf_vertex_array(points=cap2, col_wrap=true, reverse=true);
// vnf_polyhedron([vnf1, vnf2, vnf3]);
function vnf_vertex_array (
points ,
caps , cap1 , cap2 ,
col_wrap = false ,
row_wrap = false ,
reverse = false ,
style = "default" ,
2020-03-10 01:35:14 +00:00
vnf = EMPTY_VNF
2019-10-21 23:44:39 +00:00
) =
assert ( ( ! caps ) || ( caps && col_wrap ) )
assert ( in_list ( style , [ "default" , "alt" , "quincunx" ] ) )
let (
pts = flatten ( points ) ,
2019-11-07 00:17:33 +00:00
pcnt = len ( pts ) ,
2019-10-21 23:44:39 +00:00
rows = len ( points ) ,
cols = len ( points [ 0 ] ) ,
errchk = [ for ( row = points ) assert ( len ( row ) = = cols , "All rows much have the same number of columns." ) 0 ] ,
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 (
pts ,
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 ( [ pts [ i1 ] , pts [ i2 ] , pts [ i3 ] , pts [ 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.
2020-01-10 04:10:46 +00:00
// convexity = Max number of times a line could intersect a wall of the shape.
module vnf_polyhedron ( vnf , convexity = 2 ) {
2019-10-21 23:44:39 +00:00
vnf = is_vnf_list ( vnf ) ? vnf_merge ( vnf ) : vnf ;
2020-01-10 04:10:46 +00:00
polyhedron ( vnf [ 0 ] , vnf [ 1 ] , convexity = convexity ) ;
2019-10-21 23:44:39 +00:00
}
2020-03-30 23:17:27 +00:00
2020-03-21 05:15:41 +00:00
// Function: vnf_volume()
// Usage:
// vol = vnf_volume(vnf);
// Description:
2020-03-21 13:19:02 +00:00
// Returns the volume enclosed by the given manifold VNF. The VNF must describe a valid polyhedron with consistent face direction and
// no holes; otherwise the results are undefined. Returns a positive volume if face direction is clockwise and a negative volume
// if face direction is counter-clockwise.
2020-03-21 05:15:41 +00:00
function vnf_volume ( vnf ) =
2020-03-31 10:27:07 +00:00
let (
vnf = vnf_triangulate ( vnf ) ,
verts = vnf [ 0 ]
) sum ( [
for ( face_index = vnf [ 1 ] ) let (
face = select ( verts , face_index ) ,
n = cross ( face [ 2 ] - face [ 0 ] , face [ 1 ] - face [ 0 ] )
) face [ 0 ] * n
] ) / 6 ;
2020-03-21 05:15:41 +00:00
// Function: vnf_centroid()
// Usage:
// vol = vnf_centroid(vnf);
// Description:
2020-03-21 13:19:02 +00:00
// Returns the centroid of the given manifold VNF. The VNF must describe a valid polyhedron with consistent face direction and
2020-03-21 14:58:57 +00:00
// no holes; otherwise the results are undefined.
// Algorithm from: https://wwwf.imperial.ac.uk/~rn/centroid.pdf
2020-03-21 05:15:41 +00:00
function vnf_centroid ( vnf ) =
let (
vnf = vnf_triangulate ( vnf ) ,
2020-03-31 10:27:07 +00:00
verts = vnf [ 0 ] ,
2020-03-21 05:15:41 +00:00
val = sum ( [
for ( face_index = vnf [ 1 ] )
let (
2020-03-31 10:27:07 +00:00
face = select ( verts , face_index ) ,
2020-03-21 05:15:41 +00:00
n = cross ( face [ 2 ] - face [ 0 ] , face [ 1 ] - face [ 0 ] )
) [
face [ 0 ] * n ,
vmul ( n ,
2020-03-21 13:19:02 +00:00
sqr ( face [ 0 ] + face [ 1 ] ) +
sqr ( face [ 0 ] + face [ 2 ] ) +
sqr ( face [ 1 ] + face [ 2 ] )
2020-03-21 05:15:41 +00:00
)
]
] )
) val [ 1 ] / val [ 0 ] / 8 ;
2020-05-14 12:14:23 +00:00
function _triangulate_planar_convex_polygons ( polys ) =
polys = = [ ] ? [ ] :
let (
tris = [ for ( poly = polys ) if ( len ( poly ) = = 3 ) poly ] ,
bigs = [ for ( poly = polys ) if ( len ( poly ) > 3 ) poly ] ,
newtris = [ for ( poly = bigs ) select ( poly , - 2 , 0 ) ] ,
newbigs = [ for ( poly = bigs ) select ( poly , 0 , - 2 ) ] ,
newtris2 = _triangulate_planar_convex_polygons ( newbigs ) ,
outtris = concat ( tris , newtris , newtris2 )
) outtris ;
2020-05-18 08:51:26 +00:00
// Function: vnf_bend()
2020-05-14 12:14:23 +00:00
// Usage:
2020-05-18 08:51:26 +00:00
// bentvnf = vnf_bend(vnf);
2020-05-14 12:14:23 +00:00
// Description:
// Given a VNF that is entirely above, or entirely below the Z=0 plane, bends the VNF around the
// Y axis, splitting up faces as necessary. Returns the bent VNF. Will error out if the VNF
// straddles the Z=0 plane, or if the bent VNF would wrap more than completely around. The 1:1
// radius is where the curved length of the bent VNF matches the length of the original VNF. If the
// `r` or `d` arguments are given, then they will specify the 1:1 radius or diameter. If they are
// not given, then the 1:1 radius will be defined by the distance of the furthest vertex in the
// original VNF from the Z=0 plane. You can adjust the granularity of the bend using the standard
// `$fa`, `$fs`, and `$fn` variables.
// Arguments:
// vnf = The original VNF to bend.
// r = If given, the radius where the size of the original shape is the same as in the original.
// d = If given, the diameter where the size of the original shape is the same as in the original.
2020-05-18 08:51:26 +00:00
// axis = The axis to wrap around. "X", "Y", or "Z". Default: "Z"
2020-05-14 12:14:23 +00:00
// Example(3D):
// vnf0 = cube([100,40,10], center=true);
// vnf1 = up(35, p=vnf0);
// vnf2 = down(50, p=vnf0);
2020-05-18 08:51:26 +00:00
// bent1 = vnf_bend(vnf1);
// bent2 = vnf_bend(vnf2);
2020-05-14 12:14:23 +00:00
// vnf_polyhedron([bent1,bent2]);
// Example(3D):
// vnf0 = linear_sweep(star(n=5,step=2,d=100), height=10);
// vnf1 = up(35, p=vnf0);
// vnf2 = down(50, p=vnf0);
2020-05-18 08:51:26 +00:00
// bent1 = vnf_bend(vnf1);
// bent2 = vnf_bend(vnf2);
2020-05-14 12:14:23 +00:00
// vnf_polyhedron([bent1,bent2]);
// Example(3D):
// rgn = union(rect([100,20],center=true), rect([20,100],center=true));
// vnf0 = linear_sweep(zrot(45,p=rgn), height=10);
// vnf1 = up(35, p=vnf0);
// vnf2 = down(50, p=vnf0);
2020-05-18 08:51:26 +00:00
// bent1 = vnf_bend(vnf1);
// bent2 = vnf_bend(vnf2);
2020-05-14 12:14:23 +00:00
// vnf_polyhedron([bent1,bent2]);
2020-05-18 08:51:26 +00:00
// Example(3D): Bending Around X Axis.
// rgnr = union(
// rect([20,100],center=true),
// back(50, p=trapezoid(w1=40, w2=0, h=20, anchor=FRONT))
// );
// vnf0 = xrot(00,p=linear_sweep(rgnr, height=10));
// vnf1 = up(50, p=vnf0);
// #vnf_polyhedron(vnf1);
// bent1 = vnf_bend(vnf1, axis="X");
// vnf_polyhedron([bent1]);
// Example(3D): Bending Around Y Axis.
// rgn = union(
// rect([20,100],center=true),
// back(50, p=trapezoid(w1=40, w2=0, h=20, anchor=FRONT))
// );
// rgnr = zrot(-90, p=rgn);
// vnf0 = xrot(00,p=linear_sweep(rgnr, height=10));
// vnf1 = up(50, p=vnf0);
// #vnf_polyhedron(vnf1);
// bent1 = vnf_bend(vnf1, axis="Y");
// vnf_polyhedron([bent1]);
// Example(3D): Bending Around Z Axis.
// rgn = union(
// rect([20,100],center=true),
// back(50, p=trapezoid(w1=40, w2=0, h=20, anchor=FRONT))
// );
// rgnr = zrot(90, p=rgn);
// vnf0 = xrot(90,p=linear_sweep(rgnr, height=10));
// vnf1 = fwd(50, p=vnf0);
// #vnf_polyhedron(vnf1);
// bent1 = vnf_bend(vnf1, axis="Z");
// vnf_polyhedron([bent1]);
function vnf_bend ( vnf , r , d , axis = "Z" ) =
2020-05-14 12:14:23 +00:00
let (
2020-05-18 08:51:26 +00:00
chk_axis = assert ( in_list ( axis , [ "X" , "Y" , "Z" ] ) ) ,
2020-05-14 12:14:23 +00:00
vnf = vnf_triangulate ( vnf ) ,
verts = vnf [ 0 ] ,
bounds = pointlist_bounds ( verts ) ,
bmin = bounds [ 0 ] ,
bmax = bounds [ 1 ] ,
2020-05-18 08:51:26 +00:00
dflt = axis = = "Z" ?
max ( abs ( bmax . y ) , abs ( bmin . y ) ) :
max ( abs ( bmax . z ) , abs ( bmin . z ) ) ,
r = get_radius ( r = r , d = d , dflt = dflt ) ,
width = axis = = "X" ? ( bmax . y - bmin . y ) : ( bmax . x - bmin . x )
2020-05-14 12:14:23 +00:00
)
assert ( width < = 2 * PI * r , "Shape would wrap more than completely around the cylinder." )
let (
2020-05-18 08:51:26 +00:00
span_chk = axis = = "Z" ?
assert ( bmin . y > 0 || bmax . y < 0 , "Entire shape MUST be completely in front of or behind y=0." ) :
assert ( bmin . z > 0 || bmax . z < 0 , "Entire shape MUST be completely above or below z=0." ) ,
2020-05-14 12:14:23 +00:00
min_ang = 180 * bmin . x / ( PI * r ) ,
max_ang = 180 * bmax . x / ( PI * r ) ,
ang_span = max_ang - min_ang ,
steps = ceil ( segs ( r ) * ang_span / 360 ) ,
step = width / steps ,
2020-05-18 08:51:26 +00:00
bend_at = axis = = "X" ? [ for ( i = [ 1 : 1 : steps - 1 ] ) i * step + bmin . y ] :
[ for ( i = [ 1 : 1 : steps - 1 ] ) i * step + bmin . x ] ,
2020-05-14 12:14:23 +00:00
facepolys = [ for ( face = vnf [ 1 ] ) select ( verts , face ) ] ,
2020-05-18 08:51:26 +00:00
splits = axis = = "X" ?
split_polygons_at_each_y ( facepolys , bend_at ) :
split_polygons_at_each_x ( facepolys , bend_at ) ,
2020-05-14 12:14:23 +00:00
newtris = _triangulate_planar_convex_polygons ( splits ) ,
bent_faces = [
for ( tri = newtris ) [
for ( p = tri ) let (
2020-05-18 08:51:26 +00:00
a = axis = = "X" ? 180 * p . y / ( r * PI ) * sign ( bmax . z ) :
axis = = "Y" ? 180 * p . x / ( r * PI ) * sign ( bmax . z ) :
180 * p . x / ( r * PI ) * sign ( bmax . y )
)
axis = = "X" ? [ p . x , p . z * sin ( a ) , p . z * cos ( a ) ] :
axis = = "Y" ? [ p . z * sin ( a ) , p . y , p . z * cos ( a ) ] :
[ p . y * sin ( a ) , p . y * cos ( a ) , p . z ]
2020-05-14 12:14:23 +00:00
]
]
) vnf_add_faces ( faces = bent_faces ) ;
2020-03-21 05:15:41 +00:00
2020-03-15 11:25:37 +00:00
// Function&Module: vnf_validate()
// Usage: As Function
// fails = vnf_validate(vnf);
// Usage: As Module
// vnf_validate(vnf);
// Description:
// When called as a function, returns a list of non-manifold errors with the given VNF.
// Each error has the format `[ERR_OR_WARN,CODE,MESG,POINTS,COLOR]`.
// When called as a module, echoes the non-manifold errors to the console, and color hilites the
// bad edges and vertices, overlaid on a transparent gray polyhedron of the VNF.
//
2020-03-16 09:57:03 +00:00
// Currently checks for these problems:
// Type | Color | Code | Message
// ------- | -------- | ------------ | ---------------------------------
// WARNING | Yellow | BIG_FACE | Face has more than 3 vertices, and may confuse CGAL
2020-03-19 21:06:11 +00:00
// WARNING | Brown | NULL_FACE | Face has zero area
2020-03-16 09:57:03 +00:00
// ERROR | Cyan | NONPLANAR | Face vertices are not coplanar
// ERROR | Orange | OVRPOP_EDGE | Too many faces attached at edge
// ERROR | Violet | REVERSAL | Faces reverse across edge
// ERROR | Red | T_JUNCTION | Vertex is mid-edge on another Face
2020-03-17 08:16:08 +00:00
// ERROR | Blue | FACE_ISECT | Faces intersect
2020-03-16 09:57:03 +00:00
// ERROR | Magenta | HOLE_EDGE | Edge bounds Hole
2020-03-15 11:25:37 +00:00
//
// Still to implement:
// - Overlapping coplanar faces.
// Arguments:
// vnf = The VNF to validate.
2020-03-15 18:20:59 +00:00
// size = The width of the lines and diameter of points used to highlight edges and vertices. Module only. Default: 1
2020-03-17 08:16:08 +00:00
// check_isects = If true, performs slow checks for intersecting faces. Default: false
2020-03-16 09:57:03 +00:00
// Example: BIG_FACE Warnings; Faces with More Than 3 Vertices. CGAL often will fail to accept that a face is planar after a rotation, if it has more than 3 vertices.
// vnf = skin([
// path3d(regular_ngon(n=3, d=100),0),
// path3d(regular_ngon(n=5, d=100),100)
// ], slices=0, caps=true, method="tangent");
// vnf_validate(vnf);
// Example: NONPLANAR Errors; Face Vertices are Not Coplanar
// a = [ 0, 0,-50];
// b = [-50,-50, 50];
// c = [-50, 50, 50];
// d = [ 50, 50, 60];
// e = [ 50,-50, 50];
// vnf = vnf_add_faces(faces=[
// [a, b, e], [a, c, b], [a, d, c], [a, e, d], [b, c, d, e]
// ]);
// vnf_validate(vnf);
// Example: OVRPOP_EDGE Errors; More Than Two Faces Attached to the Same Edge. This confuses CGAL, and can lead to failed renders.
// vnf = vnf_triangulate(linear_sweep(union(square(50), square(50,anchor=BACK+RIGHT)), height=50));
// vnf_validate(vnf);
// Example: REVERSAL Errors; Faces Reversed Across Edge
// vnf1 = skin([
// path3d(square(100,center=true),0),
// path3d(square(100,center=true),100),
// ], slices=0, caps=false);
// vnf = vnf_add_faces(vnf=vnf1, faces=[
// [[-50,-50, 0], [ 50, 50, 0], [-50, 50, 0]],
// [[-50,-50, 0], [ 50,-50, 0], [ 50, 50, 0]],
// [[-50,-50,100], [-50, 50,100], [ 50, 50,100]],
// [[-50,-50,100], [ 50,-50,100], [ 50, 50,100]],
// ]);
// vnf_validate(vnf);
// Example: T_JUNCTION Errors; Vertex is Mid-Edge on Another Face.
// vnf1 = skin([
// path3d(square(100,center=true),0),
// path3d(square(100,center=true),100),
// ], slices=0, caps=false);
// vnf = vnf_add_faces(vnf=vnf1, faces=[
// [[-50,-50,0], [50,50,0], [-50,50,0]],
// [[-50,-50,0], [50,-50,0], [50,50,0]],
// [[-50,-50,100], [-50,50,100], [0,50,100]],
// [[-50,-50,100], [0,50,100], [0,-50,100]],
// [[0,-50,100], [0,50,100], [50,50,100]],
// [[0,-50,100], [50,50,100], [50,-50,100]],
// ]);
// vnf_validate(vnf);
2020-03-17 08:16:08 +00:00
// Example: FACE_ISECT Errors; Faces Intersect
// vnf = vnf_merge([
// vnf_triangulate(linear_sweep(square(100,center=true), height=100)),
// move([75,35,30],p=vnf_triangulate(linear_sweep(square(100,center=true), height=100)))
// ]);
// vnf_validate(vnf,size=2,check_isects=true);
2020-03-16 09:57:03 +00:00
// Example: HOLE_EDGE Errors; Edges Adjacent to Holes.
2020-03-15 11:25:37 +00:00
// vnf = skin([
2020-03-16 09:57:03 +00:00
// path3d(regular_ngon(n=4, d=100),0),
// path3d(regular_ngon(n=5, d=100),100)
2020-03-15 11:25:37 +00:00
// ], slices=0, caps=false);
2020-03-17 08:16:08 +00:00
// vnf_validate(vnf,size=2);
2020-03-20 20:54:38 +00:00
function vnf_validate ( vnf , show_warns = true , check_isects = false ) =
2020-03-31 10:27:07 +00:00
assert ( is_path ( vnf [ 0 ] ) )
2020-03-15 11:25:37 +00:00
let (
2020-03-20 20:54:38 +00:00
vnf = vnf_compact ( vnf ) ,
2020-03-31 10:27:07 +00:00
varr = vnf [ 0 ] ,
faces = vnf [ 1 ] ,
2020-03-15 11:25:37 +00:00
edges = sort ( [
2020-03-31 10:27:07 +00:00
for ( face = faces , edge = pair_wrap ( face ) )
2020-03-15 11:25:37 +00:00
edge [ 0 ] < edge [ 1 ] ? edge : [ edge [ 1 ] , edge [ 0 ] ]
] ) ,
edgecnts = unique_count ( edges ) ,
uniq_edges = edgecnts [ 0 ] ,
2020-03-19 21:06:11 +00:00
big_faces = ! show_warns ? [ ] : [
2020-03-31 10:27:07 +00:00
for ( face = faces )
2020-03-16 09:57:03 +00:00
if ( len ( face ) > 3 ) [
"WARNING" ,
"BIG_FACE" ,
"Face has more than 3 vertices, and may confuse CGAL" ,
2020-03-31 10:27:07 +00:00
[ for ( i = face ) varr [ i ] ] ,
2020-03-16 09:57:03 +00:00
"yellow"
2020-03-15 11:25:37 +00:00
]
] ,
2020-03-19 21:06:11 +00:00
null_faces = ! show_warns ? [ ] : [
2020-03-31 10:27:07 +00:00
for ( face = faces ) let (
2020-04-26 11:29:21 +00:00
face = deduplicate ( face , closed = true )
)
if ( len ( face ) >= 3 ) let (
2020-03-31 10:27:07 +00:00
faceverts = [ for ( k = face ) varr [ k ] ] ,
2020-04-26 11:29:21 +00:00
area = polygon_area ( faceverts )
) if ( is_num ( area ) && abs ( area ) < EPSILON ) [
2020-03-19 21:06:11 +00:00
"WARNING" ,
"NULL_FACE" ,
2020-04-26 11:29:21 +00:00
str ( "Face has zero area: " , fmt_float ( abs ( area ) , 15 ) ) ,
2020-03-31 10:27:07 +00:00
faceverts ,
2020-03-19 21:06:11 +00:00
"brown"
]
] ,
2020-03-16 09:57:03 +00:00
nonplanars = unique ( [
2020-03-31 10:27:07 +00:00
for ( face = faces ) let (
faceverts = [ for ( k = face ) varr [ k ] ]
) if ( ! points_are_coplanar ( faceverts ) ) [
2020-03-16 09:57:03 +00:00
"ERROR" ,
"NONPLANAR" ,
"Face vertices are not coplanar" ,
2020-03-31 10:27:07 +00:00
faceverts ,
2020-03-16 09:57:03 +00:00
"cyan"
]
] ) ,
overpop_edges = unique ( [
2020-03-15 11:25:37 +00:00
for ( i = idx ( uniq_edges ) )
if ( edgecnts [ 1 ] [ i ] > 2 ) [
2020-03-16 09:57:03 +00:00
"ERROR" ,
"OVRPOP_EDGE" ,
2020-03-15 11:25:37 +00:00
"Too many faces attached at Edge" ,
2020-03-31 10:27:07 +00:00
[ for ( i = uniq_edges [ i ] ) varr [ i ] ] ,
2020-03-16 09:57:03 +00:00
"#f70"
2020-03-15 11:25:37 +00:00
]
2020-03-16 09:57:03 +00:00
] ) ,
reversals = unique ( [
2020-03-31 10:27:07 +00:00
for ( i = idx ( faces ) , j = idx ( faces ) ) if ( i ! = j )
2020-04-26 11:29:21 +00:00
if ( len ( deduplicate ( faces [ i ] , closed = true ) ) >= 3 )
if ( len ( deduplicate ( faces [ j ] , closed = true ) ) >= 3 )
2020-03-31 10:27:07 +00:00
for ( edge1 = pair_wrap ( faces [ i ] ) )
for ( edge2 = pair_wrap ( faces [ j ] ) )
2020-03-19 21:06:11 +00:00
if ( edge1 = = edge2 ) // Valid adjacent faces will never have the same vertex ordering.
2020-03-31 10:27:07 +00:00
if ( _edge_not_reported ( edge1 , varr , overpop_edges ) )
2020-03-16 09:57:03 +00:00
[
"ERROR" ,
"REVERSAL" ,
"Faces Reverse Across Edge" ,
2020-03-31 10:27:07 +00:00
[ for ( i = edge1 ) varr [ i ] ] ,
2020-03-16 09:57:03 +00:00
"violet"
]
] ) ,
t_juncts = unique ( [
2020-03-31 10:27:07 +00:00
for ( v = idx ( varr ) , edge = uniq_edges )
2020-03-15 11:25:37 +00:00
if ( v ! = edge [ 0 ] && v ! = edge [ 1 ] ) let (
2020-03-31 10:27:07 +00:00
a = varr [ edge [ 0 ] ] ,
b = varr [ v ] ,
c = varr [ edge [ 1 ] ] ,
2020-03-15 11:25:37 +00:00
pt = segment_closest_point ( [ a , c ] , b )
2020-03-19 21:06:11 +00:00
) if ( pt = = b ) [
2020-03-15 11:25:37 +00:00
"ERROR" ,
2020-03-16 09:57:03 +00:00
"T_JUNCTION" ,
2020-03-15 11:25:37 +00:00
"Vertex is mid-edge on another Face" ,
[ b ] ,
"red"
]
2020-03-16 09:57:03 +00:00
] ) ,
2020-03-17 08:16:08 +00:00
isect_faces = ! check_isects ? [ ] : unique ( [
2020-03-31 10:27:07 +00:00
for ( i = [ 0 : 1 : len ( faces ) - 2 ] )
for ( j = [ i + 1 : 1 : len ( faces ) - 1 ] ) let (
f1 = faces [ i ] ,
f2 = faces [ j ] ,
2020-03-17 08:16:08 +00:00
shared_edges = [
for ( edge1 = pair_wrap ( f1 ) , edge2 = pair_wrap ( f2 ) ) let (
e1 = edge1 [ 0 ] < edge1 [ 1 ] ? edge1 : [ edge1 [ 1 ] , edge1 [ 0 ] ] ,
e2 = edge2 [ 0 ] < edge2 [ 1 ] ? edge2 : [ edge2 [ 1 ] , edge2 [ 0 ] ]
) if ( e1 = = e2 ) 1
]
)
if ( ! shared_edges ) let (
2020-03-31 10:27:07 +00:00
plane1 = plane3pt_indexed ( varr , f1 [ 0 ] , f1 [ 1 ] , f1 [ 2 ] ) ,
plane2 = plane3pt_indexed ( varr , f2 [ 0 ] , f2 [ 1 ] , f2 [ 2 ] ) ,
2020-03-17 08:16:08 +00:00
line = plane_intersection ( plane1 , plane2 )
)
if ( ! is_undef ( line ) ) let (
2020-03-31 10:27:07 +00:00
poly1 = select ( varr , f1 ) ,
2020-03-17 08:16:08 +00:00
isects = polygon_line_intersection ( poly1 , line )
)
if ( ! is_undef ( isects ) )
for ( isect = isects )
if ( len ( isect ) > 1 ) let (
2020-03-31 10:27:07 +00:00
poly2 = select ( varr , f2 ) ,
2020-03-17 08:16:08 +00:00
isects2 = polygon_line_intersection ( poly2 , isect , bounded = true )
)
if ( ! is_undef ( isects2 ) )
for ( seg = isects2 )
2020-03-19 21:06:11 +00:00
if ( seg [ 0 ] ! = seg [ 1 ] ) [
2020-03-17 08:16:08 +00:00
"ERROR" ,
"FACE_ISECT" ,
"Faces intersect" ,
seg ,
"blue"
]
] ) ,
2020-03-16 09:57:03 +00:00
hole_edges = unique ( [
for ( i = idx ( uniq_edges ) )
if ( edgecnts [ 1 ] [ i ] < 2 )
2020-03-31 10:27:07 +00:00
if ( _pts_not_reported ( uniq_edges [ i ] , varr , t_juncts ) )
if ( _pts_not_reported ( uniq_edges [ i ] , varr , isect_faces ) )
2020-03-16 09:57:03 +00:00
[
2020-03-15 11:25:37 +00:00
"ERROR" ,
2020-03-16 09:57:03 +00:00
"HOLE_EDGE" ,
"Edge bounds Hole" ,
2020-03-31 10:27:07 +00:00
[ for ( i = uniq_edges [ i ] ) varr [ i ] ] ,
2020-03-16 09:57:03 +00:00
"magenta"
2020-03-15 11:25:37 +00:00
]
2020-03-16 09:57:03 +00:00
] )
) concat (
2020-03-19 21:06:11 +00:00
big_faces ,
null_faces ,
2020-03-16 09:57:03 +00:00
nonplanars ,
overpop_edges ,
reversals ,
t_juncts ,
2020-03-17 08:16:08 +00:00
isect_faces ,
2020-03-16 09:57:03 +00:00
hole_edges
) ;
2020-03-31 10:27:07 +00:00
function _pts_not_reported ( pts , varr , reports ) =
2020-03-16 09:57:03 +00:00
[
for ( i = pts , report = reports , pt = report [ 3 ] )
2020-03-31 10:27:07 +00:00
if ( varr [ i ] = = pt ) 1
2020-03-16 09:57:03 +00:00
] = = [ ] ;
2020-03-31 10:27:07 +00:00
function _edge_not_reported ( edge , varr , reports ) =
2020-03-16 09:57:03 +00:00
let (
2020-03-31 10:27:07 +00:00
edge = sort ( [ for ( i = edge ) varr [ i ] ] )
2020-03-16 09:57:03 +00:00
) [
for ( report = reports ) let (
pts = sort ( report [ 3 ] )
) if ( len ( pts ) = = 2 && edge = = pts ) 1
] = = [ ] ;
2020-03-15 11:25:37 +00:00
2020-03-19 21:06:11 +00:00
module vnf_validate ( vnf , size = 1 , show_warns = true , check_isects = false ) {
faults = vnf_validate (
vnf , show_warns = show_warns ,
check_isects = check_isects
) ;
2020-03-15 11:25:37 +00:00
for ( fault = faults ) {
typ = fault [ 0 ] ;
err = fault [ 1 ] ;
msg = fault [ 2 ] ;
pts = fault [ 3 ] ;
clr = fault [ 4 ] ;
echo ( str ( typ , " " , err , ": " , msg , " at " , pts ) ) ;
color ( clr ) {
if ( len ( pts ) = = 2 ) {
2020-03-15 18:20:59 +00:00
stroke ( pts , width = size ) ;
2020-03-15 11:25:37 +00:00
} else if ( len ( pts ) > 2 ) {
2020-03-15 18:20:59 +00:00
stroke ( pts , width = size , closed = true ) ;
2020-03-15 11:25:37 +00:00
polyhedron ( pts , [ [ for ( i = idx ( pts ) ) i ] ] ) ;
} else {
2020-03-25 01:31:22 +00:00
move_copies ( pts ) sphere ( d = size * 3 , $fn = 18 ) ;
2020-03-15 11:25:37 +00:00
}
}
}
2020-03-16 10:01:07 +00:00
color ( [ 0.5 , 0.5 , 0.5 , 0.5 ] ) vnf_polyhedron ( vnf ) ;
2020-03-15 11:25:37 +00:00
}
2019-10-21 23:44:39 +00:00
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap