mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-01-01 09:49:45 +00:00
Merge pull request #693 from RonaldoCMP/master
Minor changes in triangulate code and docs,
This commit is contained in:
commit
12279bbee2
3 changed files with 101 additions and 104 deletions
176
geometry.scad
176
geometry.scad
|
@ -1603,17 +1603,25 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
|
||||||
// Description:
|
// Description:
|
||||||
// Given a simple polygon in 2D or 3D, triangulates it and returns a list
|
// Given a simple polygon in 2D or 3D, triangulates it and returns a list
|
||||||
// of triples indexing into the polygon vertices. When the optional argument `ind` is
|
// of triples indexing into the polygon vertices. When the optional argument `ind` is
|
||||||
// given, the it is used as an index list into `poly` to define the polygon. In that case,
|
// given, it is used as an index list into `poly` to define the polygon. In that case,
|
||||||
// `poly` may have a length greater than `ind`. Otherwise, all points in `poly`
|
// `poly` may have a length greater than `ind`. When `ind` is undefined, all points in `poly`
|
||||||
// are considered as vertices of the polygon.
|
// are considered as vertices of the polygon.
|
||||||
// .
|
// .
|
||||||
// The function may issue an error if it finds that the polygon is not simple
|
|
||||||
// (self-intersecting) or its vertices are collinear. It can work for 3d non-planar polygons
|
|
||||||
// if they are close enough to planar but may otherwise issue an error for this case.
|
|
||||||
// .
|
|
||||||
// For 2d polygons, the output triangles will have the same winding (CW or CCW) of
|
// For 2d polygons, the output triangles will have the same winding (CW or CCW) of
|
||||||
// the input polygon. For 3d polygons, the triangle windings will induce a normal
|
// the input polygon. For 3d polygons, the triangle windings will induce a normal
|
||||||
// vector with the same direction of the polygon normal.
|
// vector with the same direction of the polygon normal.
|
||||||
|
// .
|
||||||
|
// The function produce correct triangulations for some non-twisted non-simple polygons.
|
||||||
|
// A polygon is non-twisted iff it is simple or there is a partition of it in
|
||||||
|
// simple polygons with the same winding. These polygons may have "touching" vertices
|
||||||
|
// (two vertices having the same coordinates, but distinct adjacencies) and "contact" edges
|
||||||
|
// (edges whose vertex pairs have the same pairwise coordinates but are in reversed order) but has
|
||||||
|
// no self-crossing. See examples bellow. If all polygon edges are contact edges, returns an empty list.
|
||||||
|
// .
|
||||||
|
// Self-crossing polygons have no consistent winding and usually produce an error but
|
||||||
|
// when an error is not issued the outputs are not correct triangulations. The function
|
||||||
|
// can work for 3d non-planar polygons if they are close enough to planar but may otherwise
|
||||||
|
// issue an error for this case.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// poly = Array of vertices for the polygon.
|
// poly = Array of vertices for the polygon.
|
||||||
// ind = A list indexing the vertices of the polygon in `poly`.
|
// ind = A list indexing the vertices of the polygon in `poly`.
|
||||||
|
@ -1621,7 +1629,28 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
|
||||||
// Example(2D,NoAxes):
|
// Example(2D,NoAxes):
|
||||||
// poly = star(id=10, od=15,n=11);
|
// poly = star(id=10, od=15,n=11);
|
||||||
// tris = polygon_triangulate(poly);
|
// tris = polygon_triangulate(poly);
|
||||||
// for(tri=tris) stroke(select(poly,tri), width=.2, closed=true);
|
// color("lightblue") for(tri=tris) polygon(select(poly,tri));
|
||||||
|
// color("magenta") up(2) stroke(poly,.25,closed=true);
|
||||||
|
// color("black") up(3) vnf_debug([poly,[]],faces=false,size=1);
|
||||||
|
// Example(2D,NoAxes): a polygon with a hole and one "contact" edge
|
||||||
|
// poly = [ [-10,0], [10,0], [0,10], [-10,0], [-4,4], [4,4], [0,2], [-4,4] ];
|
||||||
|
// tris = polygon_triangulate(poly);
|
||||||
|
// color("lightblue") for(tri=tris) polygon(select(poly,tri));
|
||||||
|
// color("magenta") up(2) stroke(poly,.25,closed=true);
|
||||||
|
// color("black") up(3) vnf_debug([poly,[]],faces=false,size=1);
|
||||||
|
// Example(2D,NoAxes): a polygon with "touching" vertices and no holes
|
||||||
|
// poly = [ [0,0], [5,5], [-5,5], [0,0], [-5,-5], [5,-5] ];
|
||||||
|
// tris = polygon_triangulate(poly);
|
||||||
|
// color("lightblue") for(tri=tris) polygon(select(poly,tri));
|
||||||
|
// color("magenta") up(2) stroke(poly,.25,closed=true);
|
||||||
|
// color("black") up(3) vnf_debug([poly,[]],faces=false,size=1);
|
||||||
|
// Example(2D,NoAxes): a polygon with "contact" edges and no holes
|
||||||
|
// poly = [ [0,0], [10,0], [10,10], [0,10], [0,0], [3,3], [7,3],
|
||||||
|
// [7,7], [7,3], [3,3] ];
|
||||||
|
// tris = polygon_triangulate(poly); // see from the top
|
||||||
|
// color("lightblue") for(tri=tris) polygon(select(poly,tri));
|
||||||
|
// color("magenta") up(2) stroke(poly,.25,closed=true);
|
||||||
|
// color("black") up(3) vnf_debug([poly,[]],faces=false,size=1);
|
||||||
// Example(3D):
|
// Example(3D):
|
||||||
// include <BOSL2/polyhedra.scad>
|
// include <BOSL2/polyhedra.scad>
|
||||||
// vnf = regular_polyhedron_info(name="dodecahedron",side=5,info="vnf");
|
// vnf = regular_polyhedron_info(name="dodecahedron",side=5,info="vnf");
|
||||||
|
@ -1630,82 +1659,43 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
|
||||||
// color("blue")
|
// color("blue")
|
||||||
// vnf_wireframe(vnf_tri, width=.15);
|
// vnf_wireframe(vnf_tri, width=.15);
|
||||||
function polygon_triangulate(poly, ind, eps=EPSILON) =
|
function polygon_triangulate(poly, ind, eps=EPSILON) =
|
||||||
assert(is_path(poly), "Polygon `poly` should be a list of 2d or 3d points")
|
assert(is_path(poly) && len(poly)>=3, "Polygon `poly` should be a list of at least three 2d or 3d points")
|
||||||
assert(is_undef(ind)
|
assert(is_undef(ind)
|
||||||
|| (is_vector(ind) && min(ind)>=0 && max(ind)<len(poly) ),
|
|| (is_vector(ind) && min(ind)>=0 && max(ind)<len(poly) ),
|
||||||
"Improper or out of bounds list of indices")
|
"Improper or out of bounds list of indices")
|
||||||
let( ind = deduplicate_indexed(poly,is_undef(ind) ? count(len(poly)) : ind) )
|
let( ind = is_undef(ind) ? count(len(poly)) : ind )
|
||||||
len(ind) == 3 ? [ind] :
|
len(ind) == 3
|
||||||
len(ind) < 3 ? [] :
|
? _is_degenerate([poly[ind[0]], poly[ind[1]], poly[ind[2]]], eps) ? [] :
|
||||||
len(poly[ind[0]]) == 3
|
// no zero area
|
||||||
? // represents the polygon projection on its plane as a 2d polygon
|
assert( norm(scalar_vec3(cross(poly[ind[1]]-poly[ind[0]], poly[ind[2]]-poly[ind[0]]))) > 2*eps,
|
||||||
let(
|
"The polygon vertices are collinear.")
|
||||||
pts = select(poly,ind),
|
[ind]
|
||||||
nrm = polygon_normal(pts)
|
: len(poly[ind[0]]) == 3
|
||||||
)
|
? // represents the polygon projection on its plane as a 2d polygon
|
||||||
// here, instead of an error, it might return [] or undef
|
let(
|
||||||
assert( nrm!=undef,
|
ind = deduplicate_indexed(poly, ind, eps)
|
||||||
"The polygon has self-intersections or its vertices are collinear or non coplanar.")
|
)
|
||||||
let(
|
len(ind)<3 ? [] :
|
||||||
imax = max_index([for(p=pts) norm(p-pts[0]) ]),
|
let(
|
||||||
v1 = unit( pts[imax] - pts[0] ),
|
pts = select(poly,ind),
|
||||||
v2 = cross(v1,nrm),
|
nrm = polygon_normal(pts)
|
||||||
prpts = pts*transpose([v1,v2])
|
)
|
||||||
)
|
// here, instead of an error, it might return [] or undef
|
||||||
[for(tri=_triangulate(prpts, count(len(ind)), eps)) select(ind,tri) ]
|
assert( nrm!=undef,
|
||||||
: let( cw = is_polygon_clockwise(select(poly, ind)) )
|
"The polygon has self-intersections or its vertices are collinear or non coplanar.")
|
||||||
cw
|
let(
|
||||||
? [for(tri=_triangulate( poly, reverse(ind), eps )) reverse(tri) ]
|
imax = max_index([for(p=pts) norm(p-pts[0]) ]),
|
||||||
: _triangulate( poly, ind, eps );
|
v1 = unit( pts[imax] - pts[0] ),
|
||||||
|
v2 = cross(v1,nrm),
|
||||||
|
prpts = pts*transpose([v1,v2])
|
||||||
|
)
|
||||||
|
[for(tri=_triangulate(prpts, count(len(ind)), eps)) select(ind,tri) ]
|
||||||
|
: let( cw = is_polygon_clockwise(select(poly, ind)) )
|
||||||
|
cw
|
||||||
|
? [for(tri=_triangulate( poly, reverse(ind), eps )) reverse(tri) ]
|
||||||
|
: _triangulate( poly, ind, eps );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// requires ccw 2d polygons
|
|
||||||
// returns ccw triangles
|
|
||||||
function _old_triangulate(poly, ind, eps=EPSILON, tris=[]) =
|
|
||||||
len(ind)==3 ? concat(tris,[ind]) :
|
|
||||||
let( ear = _get_ear(poly,ind,eps) )
|
|
||||||
assert( ear!=undef,
|
|
||||||
"The polygon has self-intersections or its vertices are collinear or non coplanar.")
|
|
||||||
let(
|
|
||||||
ear_tri = select(ind,ear,ear+2),
|
|
||||||
indr = select(ind,ear+2, ear) // indices of the remaining points
|
|
||||||
)
|
|
||||||
_triangulate(poly, indr, eps, concat(tris,[ear_tri]));
|
|
||||||
|
|
||||||
// search a valid ear from the remaining polygon
|
|
||||||
function _old_get_ear(poly, ind, eps, _i=0) =
|
|
||||||
_i>=len(ind) ? undef : // poly has no ears
|
|
||||||
let( // the _i-th ear candidate
|
|
||||||
p0 = poly[ind[_i]],
|
|
||||||
p1 = poly[ind[(_i+1)%len(ind)]],
|
|
||||||
p2 = poly[ind[(_i+2)%len(ind)]]
|
|
||||||
)
|
|
||||||
// if it is not a convex vertex, try the next one
|
|
||||||
_is_cw2(p0,p1,p2,eps) ? _get_ear(poly,ind,eps, _i=_i+1) :
|
|
||||||
let( // vertex p1 is convex; check if the triangle contains any other point
|
|
||||||
to_tst = select(ind,_i+3, _i-1),
|
|
||||||
pt2tst = select(poly,to_tst), // points other than p0, p1 and p2
|
|
||||||
q = [(p0-p2).y, (p2-p0).x], // orthogonal to ray [p0,p2] pointing right
|
|
||||||
q0 = q*p0,
|
|
||||||
atleft = [for(p=pt2tst) if(p*q<=q0) p ]
|
|
||||||
)
|
|
||||||
atleft==[] ? _i : // no point inside -> an ear
|
|
||||||
let(
|
|
||||||
q = [(p2-p1).y, (p1-p2).x], // orthogonal to ray [p1,p2] pointing right
|
|
||||||
q0 = q*p2,
|
|
||||||
atleft = [for(p=atleft) if(p*q<=q0) p ]
|
|
||||||
)
|
|
||||||
atleft==[] ? _i : // no point inside -> an ear
|
|
||||||
let(
|
|
||||||
q = [(p1-p0).y, (p0-p1).x], // orthogonal to ray [p1,p0] pointing right
|
|
||||||
q0 = q*p1,
|
|
||||||
atleft = [for(p=atleft) if(p*q<=q0) p ]
|
|
||||||
)
|
|
||||||
atleft==[] ? _i : // no point inside -> an ear
|
|
||||||
// check the next ear candidate
|
|
||||||
_get_ear(poly, ind, eps, _i=_i+1);
|
|
||||||
|
|
||||||
function _triangulate(poly, ind, eps=EPSILON, tris=[]) =
|
function _triangulate(poly, ind, eps=EPSILON, tris=[]) =
|
||||||
len(ind)==3
|
len(ind)==3
|
||||||
? _is_degenerate(select(poly,ind),eps)
|
? _is_degenerate(select(poly,ind),eps)
|
||||||
|
@ -1714,18 +1704,18 @@ function _triangulate(poly, ind, eps=EPSILON, tris=[]) =
|
||||||
: let( ear = _get_ear(poly,ind,eps) )
|
: let( ear = _get_ear(poly,ind,eps) )
|
||||||
assert( ear!=undef,
|
assert( ear!=undef,
|
||||||
"The polygon has self-intersections or its vertices are collinear or non coplanar.")
|
"The polygon has self-intersections or its vertices are collinear or non coplanar.")
|
||||||
ear<0 // degenerate ear
|
is_list(ear) // degenerate ear
|
||||||
? let( indr = select(ind,-ear+1, -ear-1) ) // discard it
|
? _triangulate(poly, select(ind,ear[0]+2, ear[0]), eps, tris) // discard it
|
||||||
_triangulate(poly, indr, eps, tris)
|
|
||||||
: let(
|
: let(
|
||||||
ear_tri = select(ind,ear,ear+2),
|
ear_tri = select(ind,ear,ear+2),
|
||||||
indr = select(ind,ear+2, ear) // indices of the remaining points
|
indr = select(ind,ear+2, ear) // remaining point indices
|
||||||
)
|
)
|
||||||
_triangulate(poly, indr, eps, concat(tris,[ear_tri]));
|
_triangulate(poly, indr, eps, concat(tris,[ear_tri]));
|
||||||
|
|
||||||
|
|
||||||
// a returned ear will be:
|
// a returned ear will be:
|
||||||
// 1. a CCW triangle without points inside except possibly at its vertices
|
// 1. a CCW (non-degenerate) triangle, made of subsequent vertices, without other
|
||||||
|
// points inside except possibly at its vertices
|
||||||
// 2. or a degenerate triangle where two vertices are coincident
|
// 2. or a degenerate triangle where two vertices are coincident
|
||||||
// the returned ear is specified by the index of `ind` of its first vertex
|
// the returned ear is specified by the index of `ind` of its first vertex
|
||||||
function _get_ear(poly, ind, eps, _i=0) =
|
function _get_ear(poly, ind, eps, _i=0) =
|
||||||
|
@ -1735,29 +1725,25 @@ function _get_ear(poly, ind, eps, _i=0) =
|
||||||
p1 = poly[ind[(_i+1)%len(ind)]],
|
p1 = poly[ind[(_i+1)%len(ind)]],
|
||||||
p2 = poly[ind[(_i+2)%len(ind)]]
|
p2 = poly[ind[(_i+2)%len(ind)]]
|
||||||
)
|
)
|
||||||
// if it is a degenerate triangle, return it (codified)
|
// degenerate triangles are returned codified
|
||||||
_is_degenerate([p0,p1,p2],eps) ? -(_i+1) :
|
_is_degenerate([p0,p1,p2],eps) ? [_i] :
|
||||||
// if it is not a convex vertex, try the next one
|
// if it is not a convex vertex, check the next one
|
||||||
_is_cw2(p0,p1,p2,eps) ? _get_ear(poly,ind,eps, _i=_i+1) :
|
_is_cw2(p0,p1,p2,eps) ? _get_ear(poly,ind,eps, _i=_i+1) :
|
||||||
let( // vertex p1 is convex
|
let( // vertex p1 is convex
|
||||||
// check if the triangle contains any other point
|
// check if the triangle contains any other point
|
||||||
// except possibly its own vertices
|
// except possibly its own vertices
|
||||||
to_tst = select(ind,_i+3, _i-1),
|
to_tst = select(ind,_i+3, _i-1),
|
||||||
pt2tst = select(poly,to_tst), // points other than p0, p1 and p2
|
|
||||||
q = [(p0-p2).y, (p2-p0).x], // orthogonal to ray [p0,p2] pointing right
|
q = [(p0-p2).y, (p2-p0).x], // orthogonal to ray [p0,p2] pointing right
|
||||||
q0 = q*p0,
|
|
||||||
r = [(p2-p1).y, (p1-p2).x], // orthogonal to ray [p2,p1] pointing right
|
r = [(p2-p1).y, (p1-p2).x], // orthogonal to ray [p2,p1] pointing right
|
||||||
r0 = r*p2,
|
|
||||||
s = [(p1-p0).y, (p0-p1).x], // orthogonal to ray [p1,p0] pointing right
|
s = [(p1-p0).y, (p0-p1).x], // orthogonal to ray [p1,p0] pointing right
|
||||||
s0 = s*p1,
|
inside = [for(p=select(poly,to_tst)) // for vertices other than p0, p1 and p2
|
||||||
inside = [for(p=pt2tst)
|
if( (p-p0)*q<=0 && (p-p2)*r<=0 && (p-p1)*s<=0 // p is on the triangle
|
||||||
if( p*q<=q0 && p*r<=r0 && p*s<=s0 ) // p is in the triangle
|
&& norm(p-p0)>eps // but not on any vertex of it
|
||||||
if( norm(p-p0)>eps // and doesn't coincide with
|
&& norm(p-p1)>eps
|
||||||
&& norm(p-p1)>eps // any of its vertices
|
|
||||||
&& norm(p-p2)>eps )
|
&& norm(p-p2)>eps )
|
||||||
p ]
|
p ]
|
||||||
)
|
)
|
||||||
inside==[] ? _i : // no point inside -> an ear
|
inside==[] ? _i : // found an ear
|
||||||
// check the next ear candidate
|
// check the next ear candidate
|
||||||
_get_ear(poly, ind, eps, _i=_i+1);
|
_get_ear(poly, ind, eps, _i=_i+1);
|
||||||
|
|
||||||
|
|
13
math.scad
13
math.scad
|
@ -689,17 +689,12 @@ function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
|
||||||
// cumsum([[1,2,3], [3,4,5], [5,6,7]]); // returns [[1,2,3], [4,6,8], [9,12,15]]
|
// cumsum([[1,2,3], [3,4,5], [5,6,7]]); // returns [[1,2,3], [4,6,8], [9,12,15]]
|
||||||
function cumsum(v) =
|
function cumsum(v) =
|
||||||
assert(is_consistent(v), "The input is not consistent." )
|
assert(is_consistent(v), "The input is not consistent." )
|
||||||
_cumsum(v,_i=0,_acc=[]);
|
len(v)<=1 ? v :
|
||||||
|
_cumsum(v,_i=1,_acc=[v[0]]);
|
||||||
|
|
||||||
function _cumsum(v,_i=0,_acc=[]) =
|
function _cumsum(v,_i=0,_acc=[]) =
|
||||||
_i==len(v) ? _acc :
|
_i>=len(v) ? _acc :
|
||||||
_cumsum(
|
_cumsum( v, _i+1, [ each _acc, _acc[len(_acc)-1] + v[_i] ] );
|
||||||
v, _i+1,
|
|
||||||
concat(
|
|
||||||
_acc,
|
|
||||||
[_i==0 ? v[_i] : last(_acc) + v[_i]]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// Function: sum_of_sines()
|
// Function: sum_of_sines()
|
||||||
|
|
|
@ -48,6 +48,7 @@ test_reindex_polygon();
|
||||||
test_align_polygon();
|
test_align_polygon();
|
||||||
test_polygon_centroid();
|
test_polygon_centroid();
|
||||||
test_point_in_polygon();
|
test_point_in_polygon();
|
||||||
|
test_polygon_triangulate();
|
||||||
test_is_polygon_clockwise();
|
test_is_polygon_clockwise();
|
||||||
test_clockwise_polygon();
|
test_clockwise_polygon();
|
||||||
test_ccw_polygon();
|
test_ccw_polygon();
|
||||||
|
@ -78,6 +79,21 @@ function info_str(list,i=0,string=chr(10)) =
|
||||||
: info_str(list,i+1,str(string,str(list[i][0],_valstr(list[i][1]),chr(10))));
|
: info_str(list,i+1,str(string,str(list[i][0],_valstr(list[i][1]),chr(10))));
|
||||||
|
|
||||||
|
|
||||||
|
module test_polygon_triangulate() {
|
||||||
|
poly0 = [ [0,0,1], [10,0,2], [10,10,0] ];
|
||||||
|
poly1 = [ [-10,0,-10], [10,0,10], [0,10,0], [-10,0,-10], [-4,4,-4], [4,4,4], [0,2,0], [-4,4,-4] ];
|
||||||
|
poly2 = [ [0,0], [5,5], [-5,5], [0,0], [-5,-5], [5,-5] ];
|
||||||
|
poly3 = [ [0,0], [10,0], [10,10], [10,13], [10,10], [0,10], [0,0], [3,3], [7,3], [7,7], [7,3], [3,3] ];
|
||||||
|
tris0 = sort(polygon_triangulate(poly0));
|
||||||
|
assert(approx(tris0, [[0, 1, 2]]));
|
||||||
|
tris1 = (polygon_triangulate(poly1));
|
||||||
|
assert(approx(tris1,( [[2, 3, 4], [6, 7, 0], [2, 4, 5], [6, 0, 1], [1, 2, 5], [5, 6, 1]])));
|
||||||
|
tris2 = (polygon_triangulate(poly2));
|
||||||
|
assert(approx(tris2,([[0, 1, 2], [3, 4, 5]])));
|
||||||
|
tris3 = (polygon_triangulate(poly3));
|
||||||
|
assert(approx(tris3,( [[5, 6, 7], [7, 8, 9], [10, 11, 0], [5, 7, 9], [10, 0, 1], [4, 5, 9], [9, 10, 1], [1, 4, 9]])));
|
||||||
|
}
|
||||||
|
|
||||||
module test__normalize_plane(){
|
module test__normalize_plane(){
|
||||||
plane = rands(-5,5,4,seed=333)+[10,0,0,0];
|
plane = rands(-5,5,4,seed=333)+[10,0,0,0];
|
||||||
plane2 = _normalize_plane(plane);
|
plane2 = _normalize_plane(plane);
|
||||||
|
|
Loading…
Reference in a new issue