From 16204c6724b3e37d48c778b30eaa079721c41df8 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 17:45:20 +0100 Subject: [PATCH] Allowed half_of() to work on paths, regions and surfaces. --- common.scad | 46 +++++++++++++++++++ mutators.scad | 19 ++++---- vnf.scad | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 9 deletions(-) diff --git a/common.scad b/common.scad index 0a08f5d..24b181a 100644 --- a/common.scad +++ b/common.scad @@ -312,7 +312,53 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = assert(num_defined([h,l,height])<=1,"You must specify only one of `l`, `h`, and `height`") first_defined([h,l,height,dflt]); +// Function: get_named_args(anonymous, named, _undef) +// Usage: +// function f(anon1=_undef, anon2=_undef,..., +// named1=_undef, named2=_undef, ...) = +// let(args = get_named_args([anon1, anon2, ...], +// [[named1, default1], [named2, default2], ...])) +// ... +// Description: +// given a set of anonymous and named arguments, returns the values of +// named arguments, in order. +// - All named arguments which were provided by the user take the +// value provided. +// - All named arguments which were not provided by the user are +// affected from anonymous arguments, in order. +// - Any remaining named arguments take the provided default values. +// Arguments: +// anonymous = the list of values of anonymous arguments. +// named = the list of [passed-value, default value] of named arguments. +// _undef = the default value used by the calling function for all +// arguments (this is *not* undef, or any value that the user might +// purposely want to use as an argument value). +// Examples: +// function f(arg1=_undef, arg2=_undef, arg3=_undef, +// named1=_undef, named2=_undef, named3=_undef) = +// let(named = get_named_args([arg1, arg2, arg3], +// [[named1, "default1"], [named2, "default2"], [named3, "default3"]])) +// named; +// echo(f()); // ["default1", "default2", "default3"] +// echo(f("given2", "given3", named1="given1")); // ["given1", "given2", "given3"] +// echo(f("given1")); // ["given1", "default2", "default3"] +// echo(f(named1="given1", "given2")); // ["given1", "given2", "default3"] +// echo(f(undef, named1="given1", undef)); // ["given1", undef, undef] +// a value that the user should never enter randomly; +// result of `dd if=/dev/random bs=32 count=1 |base64` : +_undef="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI"; + +function get_named_args(anonymous, named,_undef=_undef) = + /* u: set of undefined indices in named arguments */ + let(from_anon = [for(p=enumerate(named)) if(p[1][0]==_undef) p[0]], + n = len(anonymous)) + echo("from_anon:", from_anon) + [ for(e = enumerate(named)) + // if the value is defined, return it: + e[1][0] != _undef ? e[1][0] : + let(k = anonymous[search(e[0], from_anon)[0]]) + k != _undef ? k : e[1][1] ]; // Function: scalar_vec3() // Usage: // scalar_vec3(v, ); diff --git a/mutators.scad b/mutators.scad index cca657f..49115f4 100644 --- a/mutators.scad +++ b/mutators.scad @@ -118,7 +118,7 @@ module half_of(v=UP, cp, s=1000, planar=false) } } -function half_of(v, arg1, arg2, cp, p, s=1e4) = +function half_of(v, arg1, arg2, s=1e4, cp, p) = /* may be called as either: * p= cp= * 1. (v, p) arg1 0 @@ -158,14 +158,15 @@ function half_of(v, arg1, arg2, cp, p, s=1e4) = // create self-intersection or whiskers: z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ] : - assert(is_region(p), str("must provide point, path or region")) - assert(len(v) == 2, str("3D vector not compatible with region")) - let(u=unit(v), w=[-u[1], u[0]], - R=[[cp+s*w, cp+s*(v+v), cp+s*(v-w), cp-s*w]]) // bounding region - intersection(R, p); - // FIXME: find something intelligent to do if p is a VNF - // FIXME: scadlib csg.scad, csg_hspace() - + is_region(p) ? + assert(len(v) == 2, str("3D vector not compatible with region")) + let(u=unit(v), w=[-u[1], u[0]], + R=[[cp+s*w, cp+s*(v+v), cp+s*(v-w), cp-s*w]]) // half-plane + intersection(R, p) + : + is_vnf(p) ? + vnf_halfspace(halfspace=concat(v,[-v*cp]), vnf=p) : + assert(false, "must pass either a point, a path, a region, or a VNF"); // Module: left_half() // diff --git a/vnf.scad b/vnf.scad index bd5121a..8b40841 100644 --- a/vnf.scad +++ b/vnf.scad @@ -827,5 +827,130 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) { color([0.5,0.5,0.5,0.5]) vnf_polyhedron(vnf); } +// Section: VNF transformations +// +// Function: vnf_halfspace(halfspace, vnf) +// Usage: +// vnf_halfspace([a,b,c,d], vnf) +// Description: +// returns the intersection of the VNF with the given half-space. +// Arguments: +// halfspace = half-space to intersect with, given as the four +// coefficients of the affine inequation a*x+b*y+c*z+d ≥ 0. +function _vnf_halfspace_pts(halfspace, points, faces, + inside=undef, coords=[], map=[]) = +/* Recursive function to compute the intersection of points (and edges, + * but not faces) with with the half-space. + * Parameters: + * halfspace a vector(4) + * points a list of points3d + * faces a list of indexes in points + * inside a vector{bool} determining which points belong to the + * half-space; if undef, it is initialized at first loop. + * coords the coordinates of the points in the intersection + * map the logical map (old point) → (new point(s)): + * if point i is kept, then map[i] = new-index-for-i; + * if point i is dropped, then map[i] = [[j1, k1], [j2, k2], …], + * where points j1,… are kept (old index) + * and k1,… are the matching intersections (new index). + * Returns the triple [coords, map, inside]. + * + */ + let(i=len(map), n=len(coords)) // we are currently processing point i + // termination test: + i >= len(points) ? [ coords, map, inside ] : + let(inside = !is_undef(inside) ? inside : + [for(x=points) halfspace*concat(x,[1]) >= 0], + pi = points[i]) + // inside half-space: keep the point (and reindex) + inside[i] ? _vnf_halfspace_pts(halfspace, points, faces, inside, + concat(coords, [pi]), concat(map, [n])) + : // else: compute adjacent vertices (adj) + let(adj = unique([for(f=faces) let(m=len(f), j=search(i, f)[0]) + each if(j!=undef) [f[(j+1)%m], f[(j+m-1)%m]] ]), + // filter those which lie in half-space: + adj2 = [for(x=adj) if(inside[x]) x], + zi = halfspace*concat(pi, [1])) + _vnf_halfspace_pts(halfspace, points, faces, inside, + // new points: we append all these intersection points + concat(coords, [for(j=adj2) let(zj=halfspace*concat(points[j],[1])) + (zi*points[j]-zj*pi)/(zi-zj)]), + // map: we add the info + concat(map, [[for(y=enumerate(adj2)) [y[1], n+y[0]]]])); +function _vnf_halfspace_face(face, map, inside, i=0, + newface=[], newedge=[], exit) = +/* Recursive function to intersect a face of the VNF with the half-plane. + * Arguments: + * face: the list of points of the face (old indices). + * map: as produced by _vnf_halfspace_pts + * inside: vector{bool} containing half-space info + * i: index for iteration + * exit: boolean; is first point in newedge an exit or an entrance from + * half-space? + * newface: list of (new indexes of) points on the face + * newedge: list of new points on the plane (even number of points) + * Return value: [newface, new-edges], where new-edges is a list of + * pairs [entrance-node, exit-node] (new indices). + */ +// termination condition: + (i >= len(face)) ? [ newface, + // if exit==true then we return newedge[1,0], newedge[3,2], ... + // otherwise newedge[0,1], newedge[2,3], ...; + // all edges are oriented (entrance->exit), so that by following the + // arrows we obtain a correctly-oriented face: + let(k = exit ? 0 : 1) + [for(i=[0:2:len(newedge)-2]) [newedge[i+k], newedge[i+1-k]]] ] + : // recursion case: p is current point on face, q is next point + let(p = face[i], q = face[(i+1)%len(face)], + // if p is inside half-plane, keep it in the new face: + newface0 = inside[p] ? concat(newface, [map[p]]) : newface) + // if the current segment does not intersect, this is all: + inside[p] == inside[q] ? _vnf_halfspace_face(face, map, inside, i+1, + newface0, newedge, exit) + : // otherwise, we must add the intersection point: + // rename the two points p,q as inner and outer point: + let(in = inside[p] ? p : q, out = p+q-in, + inter=[for(a=map[out]) if(a[0]==in) a[1]][0]) + _vnf_halfspace_face(face, map, inside, i+1, + concat(newface0, [inter]), + concat(newedge, [inter]), + is_undef(exit) ? inside[p] : exit); +function _vnf_halfspace_paths(edges, i=0, paths=[]) = +/* given a set of oriented edges [x,y], + returns all paths [x,y,z,..] that may be formed from these edges. + A closed path will be returned with equal first and last point. + i: index of currently examined edge + */ + i >= len(edges) ? paths : // termination condition + let(e = edges[i], + s = [for(x=enumerate(paths)) if(x[1][len(x[1])-1] == e[0]) x[0]]) + _vnf_halfspace_paths(edges, i+1, + // if cannot attach to previous path: create a new one + s == [] ? concat(paths, [e]) : + // otherwise, attach to found path + [for(x=enumerate(paths)) x[0]==s[0] ? concat(x[1], [e[1]]) : x[1]]); +function vnf_halfspace(_arg1=_undef, _arg2=_undef, + halfspace=_undef, vnf=_undef) = + // here is where we wish that OpenSCAD had array lvalues... + let(args=get_named_args([_arg1, _arg2], [[halfspace],[vnf]]), + halfspace=args[0], vnf=args[1]) + assert(is_vector(halfspace, 4), + "half-space must be passed as a length 4 affine form") + assert(is_vnf(vnf), "must pass a vnf") + // read points + let(tmp1=_vnf_halfspace_pts(halfspace, vnf[0], vnf[1]), + coords=tmp1[0], map=tmp1[1], inside=tmp1[2], + // cut faces and generate edges + tmp2= [for(f=vnf[1]) _vnf_halfspace_face(f, map, inside)], + newfaces=[for(x=tmp2) if(x[0]!=[]) x[0]], + newedges=[for(x=tmp2) each x[1]], + // generate new faces + paths=_vnf_halfspace_paths(newedges), + loops=[for(p=paths) if(p[0] == p[len(p)-1]) p]) + [coords, concat(newfaces, loops)]; + +// +// // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap +//