Merge pull request #662 from adrianVmariano/master

path intersection speedups and misc fixes
This commit is contained in:
Revar Desmera 2021-09-28 22:34:45 -07:00 committed by GitHub
commit 5036a84b52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 227 additions and 170 deletions

View file

@ -158,7 +158,12 @@ function line_normal(p1,p2) =
// the intersection lies on the segment. Otherwise it lies somewhere on
// the extension of the segment. If lines are parallel or coincident then
// it returns undef.
// This kludge of calling path2d is because vnf_bend passed 3d input. FIXME!
function _general_line_intersection(s1,s2,eps=EPSILON) =
len(s1[0])==3 ? _general_line_intersection(path2d(s1), path2d(s2),eps)
:
let(
denominator = cross(s1[0]-s1[1],s2[0]-s2[1])
)
@ -1471,7 +1476,7 @@ function polygon_normal(poly) =
// color("red")back(28/(2/3))text("Even-Odd", size=5/(2/3), halign="center");
// }
// right(40){
// dp = polygon_parts(path,closed=true);
// dp = polygon_parts(path,nonzero=true);
// region(dp);
// color("red"){stroke(path,width=1,closed=true);
// back(28/(2/3))text("Nonzero", size=5/(2/3), halign="center");
@ -1482,7 +1487,7 @@ function polygon_normal(poly) =
// poly = The list of 2D points forming the perimeter of the polygon.
// nonzero = The rule to use: true for "Nonzero" rule and false for "Even-Odd". Default: false (Even-Odd)
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
// Example(2D): With nonzero set to true, we get this result. Green dots are inside the polygon and red are outside:
// Example(2D): With nonzero set to false (the default), we get this result. Green dots are inside the polygon and red are outside:
// a=20*2/3;
// b=30*2/3;
// ofs = 17*2/3;
@ -1496,9 +1501,9 @@ function polygon_normal(poly) =
// pts = [[0,0],[10,0],[0,20]];
// for(p=pts){
// color(point_in_polygon(p,path)==1 ? "green" : "red")
// move(p)circle(r=1, $fn=12);
// move(p)circle(r=1.5, $fn=12);
// }
// Example(2D): With nonzero set to false, one dot changes color:
// Example(2D): With nonzero set to true, one dot changes color:
// a=20*2/3;
// b=30*2/3;
// ofs = 17*2/3;
@ -1511,8 +1516,8 @@ function polygon_normal(poly) =
// stroke(path,closed=true);
// pts = [[0,0],[10,0],[0,20]];
// for(p=pts){
// color(point_in_polygon(p,path,nonzero=false)==1 ? "green" : "red")
// move(p)circle(r=1, $fn=12);
// color(point_in_polygon(p,path,nonzero=true)==1 ? "green" : "red")
// move(p)circle(r=1.5, $fn=12);
// }
// Internal function for point_in_polygon

View file

@ -125,6 +125,56 @@ function path_merge_collinear(path, closed=false, eps=EPSILON) =
) [for (i=indices) path[i]];
// Function: are_polygons_equal()
// Usage:
// b = are_polygons_equal(poly1, poly2, [eps])
// Description:
// Returns true if poly1 and poly2 are the same polongs
// within given epsilon tolerance.
// Arguments:
// poly1 = first polygon
// poly2 = second polygon
// eps = tolerance for comparison
// Example(NORENDER):
// are_polygons_equal(pentagon(r=4),
// rot(360/5, p=pentagon(r=4))); // returns true
// are_polygons_equal(pentagon(r=4),
// rot(90, p=pentagon(r=4))); // returns false
function are_polygons_equal(poly1, poly2, eps=EPSILON) =
let(
poly1 = cleanup_path(poly1),
poly2 = cleanup_path(poly2),
l1 = len(poly1),
l2 = len(poly2)
) l1 != l2 ? false :
let( maybes = find_first_match(poly1[0], poly2, eps=eps, all=true) )
maybes == []? false :
[for (i=maybes) if (_are_polygons_equal(poly1, poly2, eps, i)) 1] != [];
function _are_polygons_equal(poly1, poly2, eps, st) =
max([for(d=poly1-select(poly2,st,st-1)) d*d])<eps*eps;
// Function: is_polygon_in_list()
// Topics: Polygons, Comparators
// See Also: are_polygons_equal(), are_regions_equal()
// Usage:
// bool = is_polygon_in_list(poly, polys);
// Description:
// Returns true if one of the polygons in `polys` is equivalent to the polygon `poly`.
// Arguments:
// poly = The polygon to search for.
// polys = The list of polygons to look for the polygon in.
function is_polygon_in_list(poly, polys) =
__is_polygon_in_list(poly, polys, 0);
function __is_polygon_in_list(poly, polys, i) =
i >= len(polys)? false :
are_polygons_equal(poly, polys[i])? true :
__is_polygon_in_list(poly, polys, i+1);
// Section: Path length calculation
@ -211,33 +261,34 @@ function _path_self_intersections(path, closed=true, eps=EPSILON) =
let(
path = cleanup_path(path, eps=eps),
plen = len(path)
) [
for (i = [0:1:plen-(closed?2:3)], j=[i+2:1:plen-(closed?1:2)]) let(
a1 = path[i],
a2 = path[(i+1)%plen],
b1 = path[j],
b2 = path[(j+1)%plen],
isect =
(max(a1.x, a2.x) < min(b1.x, b2.x))? undef :
(min(a1.x, a2.x) > max(b1.x, b2.x))? undef :
(max(a1.y, a2.y) < min(b1.y, b2.y))? undef :
(min(a1.y, a2.y) > max(b1.y, b2.y))? undef :
)
[
for (i = [0:1:plen-(closed?2:3)])
let(
a1 = path[i],
a2 = path[(i+1)%plen],
maxax = max(a1.x,a2.x),
minax = min(a1.x,a2.x),
maxay = max(a1.y,a2.y),
minay = min(a1.y,a2.y)
)
for(j=[i+2:1:plen-(closed?1:2)])
let(
c = a1-a2,
d = b1-b2,
denom = (c.x*d.y)-(c.y*d.x)
) abs(denom)<eps? undef :
let(
e = a1-b1,
t = ((e.x*d.y)-(e.y*d.x)) / denom,
u = ((e.x*c.y)-(e.y*c.x)) / denom
) [a1+t*(a2-a1), t, u]
) if (
(!closed || i!=0 || j!=plen-1) &&
isect != undef &&
isect[1]>=-eps && isect[1]<=1+eps &&
isect[2]>=-eps && isect[2]<=1+eps
) [isect[0], i, isect[1], j, isect[2]]
b1 = path[j],
b2 = path[(j+1)%plen],
isect =
maxax < b1.x && maxax < b2.x ||
minax > b1.x && minax > b2.x ||
maxay < b1.y && maxay < b2.y ||
minay > b1.y && minay > b2.y
? undef
: _general_line_intersection([a1,a2],[b1,b2])
)
if ((!closed || i!=0 || j!=plen-1)
&& isect != undef
&& isect[1]>=-eps && isect[1]<=1+eps
&& isect[2]>=-eps && isect[2]<=1+eps)
[isect[0], i, isect[1], j, isect[2]]
];
@ -434,12 +485,22 @@ function resample_path(path, N, spacing, closed=false) =
// bool = is_path_simple(path, [closed], [eps]);
// Description:
// Returns true if the path is simple, meaning that it has no self-intersections.
// Repeated points are not considered self-intersections: a path with such points can
// still be simple.
// If closed is set to true then treat the path as a polygon.
// Arguments:
// path = path to check
// closed = set to true to treat path as a polygon. Default: false
// eps = Epsilon error value used for determine if points coincide. Default: `EPSILON` (1e-9)
function is_path_simple(path, closed=false, eps=EPSILON) =
[for(i=[0:1:len(path)-(closed?2:3)])
let(v1=path[i+1]-path[i],
v2=select(path,i+2)-path[i+1],
normv1 = norm(v1),
normv2 = norm(v2)
)
if (approx(v1*v2/normv1/normv2,-1)) 1] == []
&&
_path_self_intersections(path,closed=closed,eps=eps) == [];
@ -1043,10 +1104,10 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
// polygon(path);
// right(27)rainbow(polygon_parts(path)) polygon($item);
// move([16,-14])rainbow(polygon_parts(path,nonzero=true)) polygon($item);
function polygon_parts(path, nonzero=false, closed=true, eps=EPSILON) =
function polygon_parts(path, nonzero=false, eps=EPSILON) =
let(
path = cleanup_path(path, eps=eps),
tagged = _tag_self_crossing_subpaths(path, nonzero=nonzero, closed=closed, eps=eps),
tagged = _tag_self_crossing_subpaths(path, nonzero=nonzero, closed=true, eps=eps),
kept = [for (sub = tagged) if(sub[0] == "O") sub[1]],
outregion = _assemble_path_fragments(kept, eps=eps)
) outregion;

View file

@ -1,6 +1,10 @@
//////////////////////////////////////////////////////////////////////
// LibFile: regions.scad
// Regions and 2D boolean geometry
// This file provides 2D boolean geometry operations on paths, where you can
// compute the intersection or union of the shape defined by point lists, producing
// a new point list. Of course, boolean operations may produce shapes with multiple
// components. To handle that, we use "regions" which are defined by sets of
// multiple paths.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
@ -11,6 +15,16 @@
// Section: Regions
// A region is a list of non-crossing simple polygons. Simple polygons are those without self intersections,
// and the polygons of a region can touch at corners, but their segments should not
// cross each other. The actual geometry of the region is defined by XORing together
// all of the polygons on the list. This may sound obscure, but it simply means that nested
// boundaries make rings in the obvious fashion, and non-nested shapes simply union together.
// Checking that the polygons on a list are simple and non-crossing can be a time consuming test,
// so it is not done automatically. It is your responsibility to ensure that your regions are
// compliant. You can construct regions by making a list of polygons, or by using
// boolean function operations such as union() or difference(). And if you must you
// can clean up an ill-formed region using sanitize_region().
// Function: is_region()
@ -78,6 +92,16 @@ function check_and_fix_path(path, valid_dim=undef, closed=false, name="path") =
// Function: sanitize_region()
// Usage:
// r_fixed = sanitize_region(r);
// Description:
// Takes a malformed input region that contains self-intersecting polygons or polygons
// that cross each other and converts it into a properly defined region without
// these defects.
function sanitize_region(r) = exclusive_or([for(poly=r) each polygon_parts(poly)]);
// Module: region()
// Usage:
@ -109,7 +133,7 @@ module region(r)
// Function: point_in_region()
// Usage:
// point_in_region(point, region);
// check = point_in_region(point, region);
// Description:
// Tests if a point is inside, outside, or on the border of a region.
// Returns -1 if the point is outside the region.
@ -120,64 +144,35 @@ module region(r)
// region = The region to test against. Given as a list of polygon paths.
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
function point_in_region(point, region, eps=EPSILON, _i=0, _cnt=0) =
(_i >= len(region))? ((_cnt%2==1)? 1 : -1) : let(
pip = point_in_polygon(point, region[_i], eps=eps)
) pip==0? 0 : point_in_region(point, region, eps=eps, _i=_i+1, _cnt = _cnt + (pip>0? 1 : 0));
_i >= len(region) ? ((_cnt%2==1)? 1 : -1)
: let(
pip = point_in_polygon(point, region[_i], eps=eps)
)
pip==0? 0
: point_in_region(point, region, eps=eps, _i=_i+1, _cnt = _cnt + (pip>0? 1 : 0));
// Function: polygons_equal()
// Function: is_region_simple()
// Usage:
// b = polygons_equal(poly1, poly2, [eps])
// bool = is_region_simple(region, [eps]);
// Description:
// Returns true if poly1 and poly2 are the same polongs
// within given epsilon tolerance.
// Returns true if the region is entirely non-self-intersecting, meaning that it is
// formed from a list of simple polygons that do not intersect each other.
// Arguments:
// poly1 = first polygon
// poly2 = second polygon
// eps = tolerance for comparison
// Example(NORENDER):
// polygons_equal(pentagon(r=4),
// rot(360/5, p=pentagon(r=4))); // returns true
// polygons_equal(pentagon(r=4),
// rot(90, p=pentagon(r=4))); // returns false
function polygons_equal(poly1, poly2, eps=EPSILON) =
let(
poly1 = cleanup_path(poly1),
poly2 = cleanup_path(poly2),
l1 = len(poly1),
l2 = len(poly2)
) l1 != l2 ? false :
let( maybes = find_first_match(poly1[0], poly2, eps=eps, all=true) )
maybes == []? false :
[for (i=maybes) if (__polygons_equal(poly1, poly2, eps, i)) 1] != [];
function __polygons_equal(poly1, poly2, eps, st) =
max([for(d=poly1-select(poly2,st,st-1)) d*d])<eps*eps;
// region = region to check
// eps = tolerance for geometric omparisons. Default: `EPSILON` = 1e-9
function is_region_simple(region, eps=EPSILON) =
[for(p=region) if (!is_path_simple(p,closed=true,eps)) 1] == []
&&
[for(i=[0:1:len(region)-2])
if (_path_region_intersections(region[i], list_tail(region,i+1), eps=eps) != []) 1
] ==[];
// Function: is_polygon_in_list()
// Topics: Polygons, Comparators
// See Also: polygons_equal(), regions_equal()
// Function: are_regions_equal()
// Usage:
// bool = is_polygon_in_list(poly, polys);
// Description:
// Returns true if one of the polygons in `polys` is equivalent to the polygon `poly`.
// Arguments:
// poly = The polygon to search for.
// polys = The list of polygons to look for the polygon in.
function is_polygon_in_list(poly, polys) =
__is_polygon_in_list(poly, polys, 0);
function __is_polygon_in_list(poly, polys, i) =
i >= len(polys)? false :
polygons_equal(poly, polys[i])? true :
__is_polygon_in_list(poly, polys, i+1);
// Function: regions_equal()
// Usage:
// b = regions_equal(region1, region2, [eps])
// b = are_regions_equal(region1, region2, [eps])
// Description:
// Returns true if the components of region1 and region2 are the same polygons (in any order)
// within given epsilon tolerance.
@ -185,39 +180,63 @@ function __is_polygon_in_list(poly, polys, i) =
// region1 = first region
// region2 = second region
// eps = tolerance for comparison
function regions_equal(region1, region2) =
function are_regions_equal(region1, region2) =
assert(is_region(region1) && is_region(region2))
len(region1) != len(region2)? false :
__regions_equal(region1, region2, 0);
__are_regions_equal(region1, region2, 0);
function __regions_equal(region1, region2, i) =
function __are_regions_equal(region1, region2, i) =
i >= len(region1)? true :
!is_polygon_in_list(region1[i], region2)? false :
__regions_equal(region1, region2, i+1);
__are_regions_equal(region1, region2, i+1);
/// Internal Function: _region_path_crossings()
/// Internal Function: _path_region_intersections()
/// Usage:
/// _region_path_crossings(path, region);
/// _path_region_intersections(path, region, [closed], [eps]);
/// Description:
/// Returns a sorted list of [SEGMENT, U] that describe where a given path is crossed by a second path.
/// Returns a sorted list of [SEGMENT, U] that describe where a given path intersects the region
// in a single point. (Note that intersections of collinear segments, where the intersection is another segment, are
// ignored.)
/// Arguments:
/// path = The path to find crossings on.
/// region = Region to test for crossings of.
/// closed = If true, treat path as a closed polygon. Default: true
/// eps = Acceptable variance. Default: `EPSILON` (1e-9)
function _region_path_crossings(path, region, closed=true, eps=EPSILON) =
function _path_region_intersections(path, region, closed=true, eps=EPSILON) =
let(
segs = pair(closed? close_path(path) : cleanup_path(path))
pathclosed = closed && !is_closed_path(path),
pathlen = len(path),
regionsegs = [for(poly=region) each pair(poly, is_closed_path(poly)?false:true)]
)
sort([for (si = idx(segs), p = close_region(region), s2 = pair(p))
let (
isect = _general_line_intersection(segs[si], s2, eps=eps)
)
if (!is_undef(isect[0]) && isect[1] >= 0-eps && isect[1] < 1+eps
&& isect[2] >= 0-eps && isect[2] < 1+eps )
[si, isect[1]]
]);
sort(
[for(si = [0:1:len(path)-(pathclosed?1:2)])
let(
a1 = path[si],
a2 = path[(si+1)%pathlen],
maxax = max(a1.x,a2.x),
minax = min(a1.x,a2.x),
maxay = max(a1.y,a2.y),
minay = min(a1.y,a2.y)
)
for(rseg=regionsegs)
let(
b1 = rseg[0],
b2 = rseg[1],
isect =
maxax < b1.x && maxax < b2.x ||
minax > b1.x && minax > b2.x ||
maxay < b1.y && maxay < b2.y ||
minay > b1.y && minay > b2.y
? undef
: _general_line_intersection([a1,a2],rseg,eps)
)
if (isect && isect[1]>=-eps && isect[1]<=1+eps
&& isect[2]>=-eps && isect[2]<=1+eps)
[si,isect[1]]
]
);
// Function: split_path_at_region_crossings()
@ -241,7 +260,7 @@ function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON)
let(
path = deduplicate(path, eps=eps),
region = [for (path=region) deduplicate(path, eps=eps)],
xings = _region_path_crossings(path, region, closed=closed, eps=eps),
xings = _path_region_intersections(path, region, closed=closed, eps=eps),
crossings = deduplicate(
concat([[0,0]], xings, [[len(path)-1,1]]),
eps=eps
@ -254,7 +273,7 @@ function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON)
)
]
)
subpaths;
[for(s=subpaths) if (len(s)>1) s];
// Function: split_nested_region()
@ -831,23 +850,32 @@ function offset(
)
) return_faces? [edges,faces] : edges;
/// Internal Function: _tag_subpaths()
/// splits the polygon (path) into subpaths by region crossing and then tags each subpath:
/// "O" - the subpath is outside the region
/// "I" - the subpath is inside the region's interior
/// "S" - the subpath is on the region's border and the polygon and region are on the same side of the subpath
/// "U" - the subpath is on the region's border and the polygon and region meet at the subpath (from opposite sides)
/// The return has the form of a list with entries [TAG, SUBPATH]
function _tag_subpaths(path, region, eps=EPSILON) =
let(
subpaths = split_path_at_region_crossings(path, region, eps=eps),
tagged = [
for (sub = subpaths) let(
subpath = deduplicate(sub)
) if (len(sub)>1) let(
midpt = lerp(subpath[0], subpath[1], 0.5),
rel = point_in_region(midpt,region,eps=eps)
) rel<0? ["O", subpath] : rel>0? ["I", subpath] : let(
vec = unit(subpath[1]-subpath[0]),
perp = rot(90, planar=true, p=vec),
sidept = midpt + perp*0.01,
rel1 = point_in_polygon(sidept,path,eps=eps)>0,
rel2 = point_in_region(sidept,region,eps=eps)>0
) rel1==rel2? ["S", subpath] : ["U", subpath]
for (subpath = subpaths)
let(
midpt = mean([subpath[0], subpath[1]]),
rel = point_in_region(midpt,region,eps=eps)
)
rel<0? ["O", subpath]
: rel>0? ["I", subpath]
: let(
vec = unit(subpath[1]-subpath[0]),
perp = rot(90, planar=true, p=vec),
sidept = midpt + perp*0.01,
rel1 = point_in_polygon(sidept,path,eps=eps)>0,
rel2 = point_in_region(sidept,region,eps=eps)>0
)
rel1==rel2? ["S", subpath] : ["U", subpath]
]
) tagged;
@ -858,16 +886,14 @@ function _tag_region_subpaths(region1, region2, eps=EPSILON) =
function _tagged_region(region1,region2,keep1,keep2,eps=EPSILON) =
let(
region1 = close_region(region1, eps=eps),
region2 = close_region(region2, eps=eps),
tagged1 = _tag_region_subpaths(region1, region2, eps=eps),
tagged2 = _tag_region_subpaths(region2, region1, eps=eps),
tagged = concat(
[for (tagpath = tagged1) if (in_list(tagpath[0], keep1)) tagpath[1]],
[for (tagpath = tagged2) if (in_list(tagpath[0], keep2)) tagpath[1]]
),
outregion = _assemble_path_fragments(tagged, eps=eps)
) outregion;
tagged = [
for (tagpath = tagged1) if (in_list(tagpath[0], keep1)) tagpath[1],
for (tagpath = tagged2) if (in_list(tagpath[0], keep2)) tagpath[1]
]
)
_assemble_path_fragments(tagged, eps=eps);
@ -986,7 +1012,7 @@ function intersection(regions=[],b=undef,c=undef,eps=EPSILON) =
// circle(d=40);
// }
function exclusive_or(regions=[],b=undef,c=undef,eps=EPSILON) =
b!=undef? exclusive_or(concat([regions],[b],c==undef?[]:[c]),eps=eps) :
b!=undef? exclusive_or([regions, b, if(is_def(c)) c],eps=eps) :
len(regions)<=1? regions[0] :
exclusive_or(
let(regions=[for (r=regions) is_path(r)? [r] : r])

View file

@ -402,7 +402,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
legal_methods = ["direct","reindex","distance","fast_distance","tangent"],
caps = is_def(caps) ? caps :
closed ? false : true,
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
capsOK = is_bool(caps) || is_bool_list(caps,2),
fullcaps = is_bool(caps) ? [caps,caps] : caps,
refine = is_list(refine) ? refine : repeat(refine, len(profiles)),
slices = is_list(slices) ? slices : repeat(slices, profcount),
@ -831,7 +831,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
path = path3d(path),
caps = is_def(caps) ? caps :
closed ? false : true,
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
capsOK = is_bool(caps) || is_bool_list(caps,2),
fullcaps = is_bool(caps) ? [caps,caps] : caps,
normalOK = is_undef(normal) || (method!="natural" && is_vector(normal,3))
|| (method=="manual" && same_shape(normal,path))
@ -910,8 +910,8 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
: let( rshape = is_path(shape) ? [path3d(shape)]
: [for(s=shape) path3d(s)]
)
regions_equal(apply(transform_list[0], rshape),
apply(transform_list[L], rshape)),
are_regions_equal(apply(transform_list[0], rshape),
apply(transform_list[L], rshape)),
dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model *****")
)
transforms ? transform_list : sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps,style=style);
@ -966,7 +966,7 @@ function path_sweep2d(shape, path, closed=false, caps, quality=1, style="min_edg
let(
caps = is_def(caps) ? caps
: closed ? false : true,
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
capsOK = is_bool(caps) || is_bool_list(caps,2),
fullcaps = is_bool(caps) ? [caps,caps] : caps,
shape = check_and_fix_path(shape,valid_dim=2,closed=true,name="shape")
)
@ -1095,7 +1095,7 @@ function sweep(shape, transforms, closed=false, caps, style="min_edge") =
let(
caps = is_def(caps) ? caps :
closed ? false : true,
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
capsOK = is_bool(caps) || is_bool_list(caps,2),
fullcaps = is_bool(caps) ? [caps,caps] : caps
)
assert(len(transforms), "transformation must be length 2 or more")

View file

@ -34,15 +34,6 @@ module test_vnf_faces() {
test_vnf_faces();
module test_vnf_get_vertex() {
vnf = [[[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]],[[0,1,2],[0,3,1],[1,3,2],[2,3,0]]];
assert(vnf_get_vertex(vnf,[0,1,-1]) == [2,vnf]);
assert(vnf_get_vertex(vnf,[0,1,2]) == [4,[concat(vnf[0],[[0,1,2]]),vnf[1]]]);
assert(vnf_get_vertex(vnf,[[0,1,-1],[0,1,2]]) == [[2,4],[concat(vnf[0],[[0,1,2]]),vnf[1]]]);
}
test_vnf_get_vertex();
module test_vnf_add_face() {
verts = [[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]];
faces = [[0,1,2],[0,3,1],[1,3,2],[2,3,0]];

View file

@ -10,13 +10,15 @@
// 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.
// Constant: EMPTY_VNF
// Description:
// The empty VNF data structure. Equal to `[[],[]]`.
EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
@ -413,34 +415,6 @@ function vnf_vertices(vnf) = vnf[0];
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 to the returned VNF.
// Returns: `[INDEX, VNF]` where INDEX is the index of the point in the returned VNF's vertex list,
// and VNF is the possibly modified new VNF structure. If `p` is given as a list of points, then
// the returned 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=EMPTY_VNF, p) =
let(
isvec = is_vector(p),
pts = isvec? [p] : p,
res = set_union(vnf[0], pts, get_indices=true)
) [
(isvec? res[0][0] : res[0]),
[ res[1], vnf[1] ]
];
// Section: Altering the VNF Internals