Correct bugs in polygon_triangulation and _cleave_connected_region(

This commit is contained in:
RonaldoCMP
2021-11-01 04:42:02 +00:00
parent 94033a0bfd
commit c5da41ed8c
5 changed files with 307 additions and 80 deletions

125
vnf.scad
View File

@@ -318,14 +318,13 @@ function vnf_merge(vnfs, cleanup=false, eps=EPSILON) =
cleanup? _vnf_cleanup(verts,faces,eps) : [verts,faces];
function _vnf_cleanup(verts,faces,eps) =
let(
dedup = vector_search(verts,eps,verts), // collect vertex duplicates
map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices
offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets
map2 = list(idx(verts))-offset, // map old vertex indices to new indices
nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // eliminates all unreferenced vertices
nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // this doesn't eliminate unreferenced vertices
nfaces =
[ for(face=faces)
let(
@@ -388,7 +387,7 @@ function _join_paths_at_vertices(path1,path2,v1,v2) =
// Given a region that is connected and has its outer border in region[0],
// produces a polygon with the same points that has overlapping connected paths
// to join internal holes to the outer border. Output is a single path.
function _cleave_connected_region(region) =
function _old_cleave_connected_region(region) =
len(region)==0? [] :
len(region)<=1? clockwise_polygon(region[0]) :
let(
@@ -411,8 +410,126 @@ function _cleave_connected_region(region) =
]
)
assert(len(orgn)<len(region))
_cleave_connected_region(orgn);
_old_cleave_connected_region(orgn);
/// Internal Function: _cleave_connected_region(region, eps)
/// Description:
/// Given a region that is connected and has its outer border in region[0],
/// produces a polygon with the same points that has overlapping connected paths
/// to join internal holes to the outer border. Output is a single path.
/// It expect that region[0] be a simple closed CW path and that each hole,
/// region[i] for i>0, be a simple closed CCW path.
/// The paths are also supposed to be disjoint except for common vertices and
/// common edges but no crossing.
/// This function implements an extension of the algorithm discussed in:
/// https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
function _cleave_connected_region(region, eps=EPSILON) =
len(region)==1 ? region[0] :
let(
outer = deduplicate(clockwise_polygon(region[0])), //
holes = [for(i=[1:1:len(region)-1]) // possibly unneeded
let(poly=region[i]) //
deduplicate( ccw_polygon(poly) ) ], //
extridx = [for(li=holes) max_index(column(li,0)) ],
// the right extreme vertex for each hole sorted by decreasing x values
extremes = sort( [for(i=idx(holes)) [ i, extridx[i], -holes[i][extridx[i]].x] ], idx=2 )
)
_polyHoles(outer, holes, extremes, eps, 0);
// connect the hole paths one at a time to the outer path.
// 'extremes' is the list of the right extreme vertex of each hole sorted by decreasing abscissas
// see _cleave_connected_region(region, eps)
function _polyHoles(outer, holes, extremes, eps=EPSILON, n=0) =
let(
extr = extremes[n], //
hole = holes[extr[0]], // hole path to bridge to the outer path
ipt = extr[1], // index of the hole point with maximum abscissa
brdg = _bridge(hole[ipt], outer, eps) // the index of a point in outer to bridge hole[ipt] to
)
assert(brdg!=undef, "Error: check input polygon restrictions")
let(
l = len(outer),
lh = len(hole),
// the new outer polygon bridging the hole to the old outer
npoly =
approx(outer[brdg], hole[ipt], eps)
? [ for(i=[brdg: 1: brdg+l]) outer[i%l] ,
for(i=[ipt+1:1: ipt+lh-1]) hole[i%lh] ]
: [ for(i=[brdg: 1: brdg+l]) outer[i%l] ,
for(i=[ipt:1: ipt+lh]) hole[i%lh] ]
)
n==len(holes)-1 ? npoly :
_polyHoles(npoly, holes, extremes, eps, n+1);
// find a point in outer to be connected to pt in the interior of outer
// by a segment that not cross or touch any non adjacente edge of outer.
// return the index of a vertex in the outer path where the bridge should end
// see _polyHoles(outer, holes, extremes, eps)
function _bridge(pt, outer,eps) =
// find the intersection of a ray from pt to the right
// with the boundary of the outer cycle
let(
l = len(outer),
crxs =
[for( i=idx(outer) )
let( edge = select(outer,i,i+1) )
// consider just descending outer edges at right of pt crossing ordinate pt.y
if( (edge[0].y> pt.y)
&& (edge[1].y<=pt.y)
&& ( norm(edge[1]-pt)<eps // accepts touching vertices
|| _tri_class([pt, edge[0], edge[1]], eps)>0 ) )
[ i,
// the point of edge with ordinate pt.y
abs(pt.y-edge[1].y)<eps ? edge[1] :
let( u = (pt-edge[1]).y / (edge[0]-edge[1]).y )
(1-u)*edge[1] + u*edge[0]
]
]
)
assert(crxs!=[], "Error: check input polygon restrictions")
let(
// the intersection point nearest to pt
minX = min([for(p=crxs) p[1].x]),
crxcand = [for(crx=crxs) if(crx[1].x < minX+eps) crx ],
nearest = min_index([for(crx=crxcand) outer[crx[0]].y]),
proj = crxcand[nearest],
vert0 = outer[proj[0]], // the two vertices of the nearest crossing edge
vert1 = outer[(proj[0]+1)%l],
isect = proj[1] // the intersection point
)
// if pt touches the middle of an outer edge -> error
assert( ! approx(pt,isect,eps) || approx(pt,vert0,eps) || approx(pt,vert1,eps),
"There is a forbidden self_intersection" )
norm(pt-vert0) < eps ? proj[0] : // if pt touches an outer vertex, return its index
norm(pt-vert1) < eps ? (proj[0]+1)%l :
let(
// the edge [vert0, vert1] necessarily satisfies vert0.y > vert1.y
// indices of candidates to an outer bridge point
cand =
(vert0.x > pt.x)
? [ proj[0],
// select reflex vertices inside of the triangle [pt, vert0, isect]
for(i=idx(outer))
if( _tri_class(select(outer,i-1,i+1),eps) <= 0
&& _pt_in_tri(outer[i], [pt, vert0, isect], eps)>=0 )
i
]
: [ (proj[0]+1)%l,
// select reflex vertices inside of the triangle [pt, isect, vert1]
for(i=idx(outer))
if( _tri_class(select(outer,i-1,i+1),eps) <= 0
&& _pt_in_tri(outer[i], [pt, isect, vert1], eps)>=0 )
i
],
// choose the candidate outer[i] such that the line [pt, outer[i]] has minimum slope
// among those with minimum slope choose the nearest to pt
slopes = [for(i=cand) 1-abs(outer[i].x-pt.x)/norm(outer[i]-pt) ],
min_slp = min(slopes),
cand2 = [for(i=idx(cand)) if(slopes[i]<=min_slp+eps) cand[i] ],
nearest = min_index([for(i=cand2) norm(pt-outer[i]) ])
)
cand2[nearest];
// Function: vnf_from_region()