mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
Merge pull request #662 from adrianVmariano/master
path intersection speedups and misc fixes
This commit is contained in:
commit
5036a84b52
6 changed files with 227 additions and 170 deletions
|
@ -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
|
||||
|
|
117
paths.scad
117
paths.scad
|
@ -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;
|
||||
|
|
210
regions.scad
210
regions.scad
|
@ -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])
|
||||
|
|
12
skin.scad
12
skin.scad
|
@ -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")
|
||||
|
|
|
@ -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]];
|
||||
|
|
32
vnf.scad
32
vnf.scad
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue