Allowed half_of() to work on paths, regions and surfaces.

This commit is contained in:
Jerome Plut 2020-12-03 17:45:20 +01:00
parent 96bd60aceb
commit 16204c6724
3 changed files with 181 additions and 9 deletions

View file

@ -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, <dflt>);

View file

@ -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()
//

125
vnf.scad
View file

@ -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
//