Merge branch 'master' into revarbat_dev

This commit is contained in:
Garth Minette 2021-04-20 18:58:37 -07:00
commit 8fd4073d44
4 changed files with 188 additions and 309 deletions

View file

@ -407,7 +407,7 @@ function all_defined(v,recursive=false) =
// Usage:
// anchr = get_anchor(anchor,center,<uncentered>,<dflt>);
// Topics: Argument Handling
// See Also: get_radius(), get_named_args()
// See Also: get_radius()
// Description:
// Calculated the correct anchor from `anchor` and `center`. In order:
// - If `center` is not `undef` and `center` evaluates as true, then `CENTER` (`[0,0,0]`) is returned.
@ -437,7 +437,7 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
// Usage:
// r = get_radius(<r1=>, <r2=>, <r=>, <d1=>, <d2=>, <d=>, <dflt=>);
// Topics: Argument Handling
// See Also: get_anchor(), get_named_args()
// See Also: get_anchor()
// Description:
// Given various radii and diameters, returns the most specific radius. If a diameter is most
// specific, returns half its value, giving the radius. If no radii or diameters are defined,
@ -482,88 +482,6 @@ function get_radius(r1, r2, r, d1, d2, d, dflt) =
rad;
// Function: get_named_args()
// Usage:
// function f(pos1=_UNDEF, pos2=_UNDEF,...,named1=_UNDEF, named2=_UNDEF, ...) = let(args = get_named_args([pos1, pos2, ...], [[named1, default1], [named2, default2], ...]), named1=args[0], named2=args[1], ...)
// Topics: Argument Handling
// See Also: get_anchor(), get_radius()
// Description:
// Given the values of some positional and named arguments, returns a list of the values assigned to
// named parameters. in the following steps:
// - First, all named parameters which were explicitly assigned in the function call take their
// provided value.
// - Then, any positional arguments are assigned to remaining unassigned
// parameters; this is governed both by the `priority` entries (if there are `N` positional
// arguments, then the `N` parameters with lowest `priority` value will be assigned) and by the
// order of the positional arguments (matching that of the assigned named parameters). If no
// priority is given, then these two ordering coincide: parameters are assigned in order, starting
// from the first one.
// - Finally, any remaining named parameters can take default values. If no default values are
// given, then `undef` is used.
// .
// This allows an author to declare a function prototype with named or optional parameters, so that
// the user may then call this function using either positional or named parameters. In practice the
// author will declare the function as using *both* positional and named parameters, and let
// `get_named_args()` do the parsing from the whole set of arguments. See the example below.
// .
// This supports the user explicitly passing `undef` as a function argument. To distinguish between
// an intentional `undef` and the absence of an argument, we use a custom `_UNDEF` value as a guard
// marking the absence of any arguments (in practice, `_UNDEF` is a random-generated string, which
// will never coincide with any useful user value). This forces the author to declare all the
// function parameters as having `_UNDEF` as their default value.
// Arguments:
// positional = The list of values of positional arguments.
// named = The list of named arguments; each entry of the list has the form `[passed-value, <default-value>, <priority>]`, where `passed-value` is the value that was passed at function call; `default-value` is the value that will be used if nothing is read from either named or positional arguments; `priority` is the priority assigned to this argument (lower means more priority, default value is `+inf`). Since stable sorting is used, if no priority at all is given, all arguments will be read in order.
// _undef = The default value used by the calling function for all arguments. The default value, `_UNDEF`, is a random string. This value **must** be the default value of all parameters in the outer function call (see example below).
//
// Example: a function with prototype `f(named1,< <named2>, named3 >)`
// function f(_p1=_UNDEF, _p2=_UNDEF, _p3=_UNDEF,
// arg1=_UNDEF, arg2=_UNDEF, arg3=_UNDEF) =
// let(named = get_named_args([_p1, _p2, _p3],
// [[arg1, "default1",0], [arg2, "default2",2], [arg3, "default3",1]]))
// named;
// // all default values or all parameters provided:
// echo(f());
// // ["default1", "default2", "default3"]
// echo(f("given2", "given3", arg1="given1"));
// // ["given1", "given2", "given3"]
//
// // arg1 has highest priority, and arg3 is higher than arg2:
// echo(f("given1"));
// // ["given1", "default2", "default3"]
// echo(f("given3", arg1="given1"));
// // ["given1", "default2", "given3"]
//
// // explicitly passing undef is allowed:
// echo(f(undef, arg1="given1", undef));
// // ["given1", undef, undef]
/* Note: however tempting it might be, it is *not* possible to accept
* named argument as a list [named1, named2, ...] (without default
* values), because the values [named1, named2...] themselves might be
* lists, and we will not be able to distinguish the two cases. */
function get_named_args(positional, named, _undef=_UNDEF) =
let(deft = [for(p=named) p[1]], // default is undef
// indices of the values to fetch from positional args:
unknown = [for(x=enumerate(named)) if(x[1][0]==_undef) x[0]],
// number of values given to positional arguments:
n_positional = count_true([for(p=positional) p!=_undef]))
assert(n_positional <= len(unknown),
str("too many positional arguments (", n_positional, " given, ",
len(unknown), " required)"))
let(
// those elements which have no priority assigned go last (prio=+):
prio = sortidx([for(u=unknown) default(named[u][2], 1/0)]),
// list of indices of values assigned from positional arguments:
assigned = [for(a=sort([for(i=[0:1:n_positional-1]) prio[i]]))
unknown[a]])
[ for(e = enumerate(named))
let(idx=e[0], val=e[1][0], ass=search(idx, assigned))
val != _undef ? val :
ass != [] ? positional[ass[0]] :
deft[idx] ];
// Function: scalar_vec3()
// Usage:
// vec = scalar_vec3(v, <dflt>);

View file

@ -96,23 +96,23 @@ module bounding_box(excess=0, planar=false) {
// Function&Module: half_of()
//
// Usage: as module
// half_of(v, <cp>, <s>) ...
// half_of(v, <cp>, <s>, <planar>) ...
// Usage: as function
// half_of(v, <cp>, p, <s>)...
// result = half_of(p,v,<cp>);
//
// Description:
// Slices an object at a cut plane, and masks away everything that is on one side.
// * Called as a function with a path in the `p` argument, returns the
// intersection of path `p` and given half-space.
// * Called as a function with a 2D path in the `p` argument
// and a 2D vector `p`, returns the intersection of path `p` and given
// half-plane.
// Slices an object at a cut plane, and masks away everything that is on one side. The v parameter is either a plane specification or
// a normal vector. The s parameter is needed for the module
// version to control the size of the masking cube, which affects preview display.
// When called as a function, you must supply a vnf, path or region in p. If planar is set to true for the module version the operation
// is performed in and UP and DOWN are treated as equivalent to BACK and FWD respectively.
//
// Arguments:
// p = path, region or VNF to slice. (Function version)
// v = Normal of plane to slice at. Keeps everything on the side the normal points to. Default: [0,0,1] (UP)
// cp = If given as a scalar, moves the cut plane along the normal by the given amount. If given as a point, specifies a point on the cut plane. This can be used to shift where it slices the object at. Default: [0,0,0]
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: 100
// planar = If true, this becomes a 2D operation. When planar, a `v` of `UP` or `DOWN` becomes equivalent of `BACK` and `FWD` respectively.
// cp = If given as a scalar, moves the cut plane along the normal by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0]
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Ignored for function version. Default: 1000
// planar = If true, perform a 2D operation. When planar, a `v` of `UP` or `DOWN` becomes equivalent of `BACK` and `FWD` respectively.
//
// Examples:
// half_of(DOWN+BACK, cp=[0,-10,0]) cylinder(h=40, r1=10, r2=0, center=false);
@ -147,23 +147,43 @@ module half_of(v=UP, cp, s=1000, planar=false)
}
}
function half_of(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF, _arg4=_UNDEF,
v=_UNDEF, cp=_UNDEF, p=_UNDEF, s=_UNDEF) =
let(args=get_named_args([_arg1, _arg2, _arg3, _arg4],
[[v,undef,0], [cp,0,2], [p,undef,1], [s, 1e4]]),
v=args[0], cp0=args[1], p=args[2], s=args[3],
cp = is_num(cp0) ? cp0*unit(v) : cp0)
assert(is_vector(v,2)||is_vector(v,3),
"must provide a half-plane or half-space")
let(d=len(v))
assert(len(cp) == d, str("cp must have dimension ", d))
is_vector(p) ?
assert(len(p) == d, str("vector must have dimension ", d))
let(z=(p-cp)*v) (z >= 0 ? p : p - (z*v)/(v*v))
:
p == [] ? [] : // special case: empty path remains empty
function half_of(p, v=UP, cp) =
is_vnf(p) ?
assert(is_vector(v) && (len(v)==3 || len(v)==4),str("Must give 3-vector or plane specification",v))
assert(select(v,0,2)!=[0,0,0], "vector v must be nonzero")
let(
plane = is_vector(v,4) ? assert(cp==undef, "Don't use cp with plane definition.") v
: is_undef(cp) ? [each v, 0]
: is_num(cp) ? [each v, cp*(v*v)/norm(v)]
: assert(is_vector(cp,3),"Centerpoint must be a 3-vector")
[each v, cp*v]
)
vnf_halfspace(plane, p)
: is_path(p) || is_region(p) ?
let(
v = (v==UP)? BACK : (v==DOWN)? FWD : v,
cp = is_undef(cp) ? [0,0]
: is_num(cp) ? v*cp
: assert(is_vector(cp,2) || (is_vector(cp,3) && cp.z==0),"Centerpoint must be 2-vector")
cp
)
assert(is_vector(v,2) || (is_vector(v,3) && v.z==0),"Must give 2-vector")
assert(!all_zero(v), "Vector v must be nonzero")
let(
bounds = pointlist_bounds(move(-cp,p)),
L = 2*max(flatten(bounds)),
n = unit(v),
u = [-n.y,n.x],
box = [cp+u*L, cp+(v+u)*L, cp+(v-u)*L, cp-u*L]
)
intersection(box,p)
: assert(false, "Input must be a region, path or VNF");
/* This code cut 3d paths but leaves behind connecting line segments
is_path(p) ?
assert(len(p[0]) == d, str("path must have dimension ", d))
//assert(len(p[0]) == d, str("path must have dimension ", d))
let(z = [for(x=p) (x-cp)*v])
[ for(i=[0:len(p)-1]) each concat(z[i] >= 0 ? [p[i]] : [],
// we assume a closed path here;
@ -174,17 +194,8 @@ function half_of(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF, _arg4=_UNDEF,
// create self-intersection or whiskers:
z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ]
:
is_vnf(p) ?
// we must put is_vnf() before is_region(), because most triangulated
// VNFs will pass is_region() test
vnf_halfspace(halfspace=concat(v,[-v*cp]), vnf=p) :
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)
:
assert(false, "must pass either a point, a path, a region, or a VNF");
*/
// Function&Module: left_half()
//
@ -192,17 +203,16 @@ function half_of(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF, _arg4=_UNDEF,
// left_half(<s>, <x>) ...
// left_half(planar=true, <s>, <x>) ...
// Usage: as function
// left_half(<s>, <x>, path)
// left_half(<s>, <x>, region)
// left_half(<s>, <x>, vnf)
// result = left_half(p, <x>);
//
// Description:
// Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it.
//
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// x = The X coordinate of the cut-plane. Default: 0
// planar = If true, this becomes a 2D operation.
// planar = If true, perform a 2D operation.
//
// Examples:
// left_half() sphere(r=20);
@ -223,28 +233,26 @@ module left_half(s=1000, x=0, planar=false)
}
}
}
function left_half(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF,
x=_UNDEF, p=_UNDEF, s=_UNDEF) =
let(args=get_named_args([_arg1, _arg2, _arg3],
[[x, 0,1], [p,undef,0], [s, 1e4]]),
x=args[0], p=args[1], s=args[2])
half_of(v=[-1,0,0], cp=x, p=p);
function left_half(p,x=0) = half_of(p, LEFT, [x,0,0]);
// Function&Module: right_half()
//
// Usage:
// Usage: as module
// right_half([s], [x]) ...
// right_half(planar=true, [s], [x]) ...
// Usage: as function
// result = right_half(p, <x>);
//
// Description:
// Slices an object at a vertical Y-Z cut plane, and masks away everything that is left of it.
//
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// x = The X coordinate of the cut-plane. Default: 0
// planar = If true, this becomes a 2D operation.
// planar = If true perform a 2D operation.
//
// Examples(FlatSpin,VPD=175):
// right_half() sphere(r=20);
@ -265,28 +273,26 @@ module right_half(s=1000, x=0, planar=false)
}
}
}
function right_half(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF,
x=_UNDEF, p=_UNDEF, s=_UNDEF) =
let(args=get_named_args([_arg1, _arg2, _arg3],
[[x, 0,1], [p,undef,0], [s, 1e4]]),
x=args[0], p=args[1], s=args[2])
half_of(v=[1,0,0], cp=x, p=p);
function right_half(p,x=0) = half_of(p, RIGHT, [x,0,0]);
// Function&Module: front_half()
//
// Usage:
// front_half([s], [y]) ...
// front_half(planar=true, [s], [y]) ...
// front_half(<s>, <y>) ...
// front_half(planar=true, <s>, <y>) ...
// Usage: as function
// result = front_half(p, <y>);
//
// Description:
// Slices an object at a vertical X-Z cut plane, and masks away everything that is behind it.
//
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// y = The Y coordinate of the cut-plane. Default: 0
// planar = If true, this becomes a 2D operation.
// planar = If true perform a 2D operation.
//
// Examples(FlatSpin,VPD=175):
// front_half() sphere(r=20);
@ -307,28 +313,26 @@ module front_half(s=1000, y=0, planar=false)
}
}
}
function front_half(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF,
x=_UNDEF, p=_UNDEF, s=_UNDEF) =
let(args=get_named_args([_arg1, _arg2, _arg3],
[[x, 0,1], [p,undef,0], [s, 1e4]]),
x=args[0], p=args[1], s=args[2])
half_of(v=[0,-1,0], cp=x, p=p);
function front_half(p,y=0) = half_of(p, FRONT, [0,y,0]);
// Function&Module: back_half()
//
// Usage:
// back_half([s], [y]) ...
// back_half(planar=true, [s], [y]) ...
// back_half(<s>, <y>) ...
// back_half(planar=true, <s>, <y>) ...
// Usage: as function
// result = back_half(p, <y>);
//
// Description:
// Slices an object at a vertical X-Z cut plane, and masks away everything that is in front of it.
//
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// y = The Y coordinate of the cut-plane. Default: 0
// planar = If true, this becomes a 2D operation.
// planar = If true perform a 2D operation.
//
// Examples:
// back_half() sphere(r=20);
@ -349,24 +353,22 @@ module back_half(s=1000, y=0, planar=false)
}
}
}
function back_half(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF,
x=_UNDEF, p=_UNDEF, s=_UNDEF) =
let(args=get_named_args([_arg1, _arg2, _arg3],
[[x, 0,1], [p,undef,0], [s, 1e4]]),
x=args[0], p=args[1], s=args[2])
half_of(v=[0,1,0], cp=x, p=p);
function back_half(p,y=0) = half_of(p, BACK, [0,y,0]);
// Function&Module: bottom_half()
//
// Usage:
// bottom_half([s], [z]) ...
// bottom_half(<s>, <z>) ...
// Usage: as function
// result = bottom_half(p, <z>);
//
// Description:
// Slices an object at a horizontal X-Y cut plane, and masks away everything that is above it.
//
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// z = The Z coordinate of the cut-plane. Default: 0
//
@ -383,24 +385,21 @@ module bottom_half(s=1000, z=0)
}
}
}
function bottom_half(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF,
x=_UNDEF, p=_UNDEF, s=_UNDEF) =
let(args=get_named_args([_arg1, _arg2, _arg3],
[[x, 0,1], [p,undef,0], [s, 1e4]]),
x=args[0], p=args[1], s=args[2])
half_of(v=[0,0,-1], cp=x, p=p);
function bottom_half(p,z=0) = half_of(p,BOTTOM,[0,0,z]);
// Function&Module: top_half()
//
// Usage:
// top_half([s], [z]) ...
// top_half(<s>, <z>) ...
// result = top_half(p, <z>);
//
// Description:
// Slices an object at a horizontal X-Y cut plane, and masks away everything that is below it.
//
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// z = The Z coordinate of the cut-plane. Default: 0
//
@ -417,12 +416,7 @@ module top_half(s=1000, z=0)
}
}
}
function top_half(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF,
x=_UNDEF, p=_UNDEF, s=_UNDEF) =
let(args=get_named_args([_arg1, _arg2, _arg3],
[[x, 0,1], [p,undef,0], [s, 1e4]]),
x=args[0], p=args[1], s=args[2])
half_of(v=[0,0,1], cp=x, p=p);
function top_half(p,z=0) = half_of(p,UP,[0,0,z]);

View file

@ -6,7 +6,7 @@
//////////////////////////////////////////////////////////////////////
BOSL_VERSION = [2,0,613];
BOSL_VERSION = [2,0,616];
// Section: BOSL Library Version Functions

239
vnf.scad
View file

@ -1031,144 +1031,111 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) {
// Function: vnf_halfspace()
// Usage:
// vnf_halfspace([a,b,c,d], vnf)
// newvnf = vnf_halfspace(plane, vnf, <closed>);
// Description:
// returns the intersection of the VNF with the given half-space.
// Returns the intersection of the vnf with a half space. The half space is defined by
// plane = [A,B,C,D], taking the side where the normal [A,B,C] points: Ax+By+CzD.
// If closed is set to false then the cut face is not included in the vnf. This could
// allow further extension of the vnf by merging with other vnfs.
// Arguments:
// halfspace = half-space to intersect with, given as the four coefficients of the affine inequation a\*x+b\*y+c\*z d.
// plane = plane defining the boundary of the half space
// vnf = vnf to cut
// closed = if false do not return include cut face(s). Default: true
// Example:
// vnf = cube(10,center=true);
// cutvnf = vnf_halfspace([-1,1,-1,0], vnf);
// vnf_polyhedron(cutvnf);
// Example: Cut face has 2 components
// vnf = path_sweep(circle(r=4, $fn=16),
// circle(r=20, $fn=64),closed=true);
// cutvnf = vnf_halfspace([-1,1,-4,0], vnf);
// vnf_polyhedron(cutvnf);
// Example: Cut face is not simply connected
// vnf = path_sweep(circle(r=4, $fn=16),
// circle(r=20, $fn=64),closed=true);
// cutvnf = vnf_halfspace([0,0.7,-4,0], vnf);
// vnf_polyhedron(cutvnf);
// Example: Cut object has multiple components
// function knot(a,b,t) = // rolling knot
// [ a * cos (3 * t) / (1 - b* sin (2 *t)),
// a * sin( 3 * t) / (1 - b* sin (2 *t)),
// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))];
// a = 0.8; b = sqrt (1 - a * a);
// ksteps = 400;
// knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)];
// ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]];
// knot=path_sweep(ushape, knot_path, closed=true, method="incremental");
// cut_knot = vnf_halfspace([1,0,0,0], knot);
// vnf_polyhedron(cut_knot);
function vnf_halfspace(plane, vnf, closed=true) =
let(
inside = [for(x=vnf[0]) plane*[each x,-1] >= 0 ? 1 : 0],
vertexmap = [0,each cumsum(inside)],
faces_edges_vertices = _vnfcut(plane, vnf[0],vertexmap,inside, vnf[1], last(vertexmap)),
newvert = concat(bselect(vnf[0],inside), faces_edges_vertices[2])
)
closed==false ? [newvert, faces_edges_vertices[0]] :
let(
allpaths = _assemble_paths(newvert, faces_edges_vertices[1]),
newpaths = [for(p=allpaths) if (len(p)>=3) p
else assert(approx(p[0],p[1]),"Orphan edge found when assembling cut edges.")
]
)
len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)]
:
let(
faceregion = [for(p=newpaths) project_plane(select(newvert,p), plane)],
facevnf = region_faces(faceregion,reverse=true),
faceverts = lift_plane(facevnf[0], plane)
)
vnf_merge([[newvert, faces_edges_vertices[0]], [faceverts, facevnf[1]]]);
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_path_search_edge(edge, paths, i=0, ret=[undef,undef]) =
/* given an oriented edge [x,y] and a set of oriented paths,
* returns the indices [i,j] of paths [before, after] given edge
*/
// termination condition
i >= len(paths) ? ret:
_vnf_halfspace_path_search_edge(edge, paths, i+1,
[last(paths[i]) == edge[0] ? i : ret[0],
paths[i][0] == edge[1] ? i : ret[1]]);
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 = _vnf_halfspace_path_search_edge(e, paths))
_vnf_halfspace_paths(edges, i+1,
// we keep all paths untouched by e[i]
concat([for(i=[0:1:len(paths)-1]) if(i!= s[0] && i != s[1]) paths[i]],
is_undef(s[0])? (
// fresh e: create a new path
is_undef(s[1]) ? [e] :
// e attaches to beginning of previous path
[concat([e[0]], paths[s[1]])]
) :// edge attaches to end of previous path
is_undef(s[1]) ? [concat(paths[s[0]], [e[1]])] :
// edge merges two paths
s[0] != s[1] ? [concat(paths[s[0]], paths[s[1]])] :
// edge closes a loop
[concat(paths[s[0]], [e[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),
reg = [for(p=paths) project_plane(select(coords,p), halfspace)],
regvnf = region_faces(reg,reverse=true),
regvert = lift_plane(regvnf[0], halfspace)
//loops=[for(p=paths) if(coords[p[0]] == coords[last(p)]) reverse(p)])
)
vnf_merge([[coords, newfaces], [regvert, regvnf[1]]]);
// [coords, concat(newfaces, loops)];
function _assemble_paths(vertices, edges, paths=[],i=0) =
i==len(edges) ? paths :
norm(vertices[edges[i][0]]-vertices[edges[i][1]])<EPSILON ? echo(degen=i)_assemble_paths(vertices,edges,paths,i+1) :
let( // Find paths that connects on left side and right side of the edges (if one exists)
left = [for(j=idx(paths)) if (approx(vertices[last(paths[j])],vertices[edges[i][0]])) j],
right = [for(j=idx(paths)) if (approx(vertices[edges[i][1]],vertices[paths[j][0]])) j]
)
assert(len(left)<=1 && len(right)<=1)
let(
keep_path = list_remove(paths,concat(left,right)),
update_path = left==[] && right==[] ? edges[i]
: left==[] ? concat([edges[i][0]],paths[right[0]])
: right==[] ? concat(paths[left[0]],[edges[i][1]])
: left != right ? concat(paths[left[0]], paths[right[0]])
: paths[left[0]]
)
_assemble_paths(vertices, edges, concat(keep_path, [update_path]), i+1);
function _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount, newfaces=[], newedges=[], newvertices=[], i=0) =
i==len(faces) ? [newfaces, newedges, newvertices] :
let(
pts_inside = select(inside,faces[i])
)
all(pts_inside) ? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,
concat(newfaces, [select(vertexmap,faces[i])]), newedges, newvertices, i+1):
!any(pts_inside) ? _vnfcut(plane, vertices, vertexmap,inside, faces, vertcount, newfaces, newedges, newvertices, i+1):
let(
first = search([[1,0]],pair(pts_inside,wrap=true),0)[0],
second = search([[0,1]],pair(pts_inside,wrap=true),0)[0]
)
assert(len(first)==1 && len(second)==1, "Found concave face in VNF. Run vnf_triangulate first to ensure convex faces.")
let(
newface = [each select(vertexmap,select(faces[i],second[0]+1,first[0])),vertcount, vertcount+1],
newvert = [plane_line_intersection(plane, select(vertices,select(faces[i],first[0],first[0]+1)),eps=0),
plane_line_intersection(plane, select(vertices,select(faces[i],second[0],second[0]+1)),eps=0)]
)
true //!approx(newvert[0],newvert[1])
? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+2,
concat(newfaces, [newface]), concat(newedges,[[vertcount+1,vertcount]]),concat(newvertices,newvert),i+1)
:len(newface)>3
? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+1,
concat(newfaces, [list_head(newface)]), newedges,concat(newvertices,[newvert[0]]),i+1)
:
_vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,newfaces, newedges, newvert, i+1);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap