From 3bf22cd236754a7f022941e9ad3ca1d139481034 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Sun, 16 Aug 2020 23:33:11 +0100
Subject: [PATCH 01/18] In-depth review

Besides param validation and some formating, changes:

A. add new functions:

1. _valid_line()
2. _valid_plane()
3. line_from_points()
4. projection_on_plane()
5. points_on_plane()

B. rename/redefine/remove functions:

1. points_are_coplanar() >> coplanar()
2. collinear() works with list of points as well as coplanar()
3. find_noncollinear_points >> noncollinear_triple
4. collinear_indexed() removed
5. polygon_is_convex() >> is_convex_polygon()

C. recode/optimize the codes of the functions:

1. point_on_segment2d()
2. point_left_of_line2d()
3. distance_from_line()
4. line_closest_point()
5. plane_from_polygon()
6. _general_plane_line_intersection()
7. polygon_line_intersection()
8. find_circle_2tangents()
9. find_circle_3points()
10. polygon_area()
11. is_convex_polygon()
12. reindex_polygon()
13. centroid()
14. polygon_is_clockwise()
15. clockwise_polygon()
16. ccw_polygon()

The function name changes were updated in:
test_geometry.scad
hull.scad
rounding.scad
vnf.scad

Regression tests for the new external functions were included in test_geometry.scad.

Unsolved questions:
1. why sorting the indices in plane_from_points and polygon_line_intersection?
2. aren't redundant plane_from_polygon() and plane_from_points()?
---
 geometry.scad            | 1014 +++++++++++++++++++++++++-------------
 tests/test_geometry.scad |  379 +++++++++-----
 2 files changed, 910 insertions(+), 483 deletions(-)

diff --git a/geometry.scad b/geometry.scad
index 443c026..b3f5ee6 100644
--- a/geometry.scad
+++ b/geometry.scad
@@ -21,85 +21,86 @@
 //   edge = Array of two points forming the line segment to test against.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
 function point_on_segment2d(point, edge, eps=EPSILON) =
-    approx(point,edge[0],eps=eps) || approx(point,edge[1],eps=eps) ||  // The point is an endpoint
-    sign(edge[0].x-point.x)==sign(point.x-edge[1].x)  // point is in between the
-        && sign(edge[0].y-point.y)==sign(point.y-edge[1].y)  // edge endpoints
-        && approx(point_left_of_segment2d(point, edge),0,eps=eps);  // and on the line defined by edge
-
-
-// Function: point_left_of_segment2d()
-// Usage:
-//   point_left_of_segment2d(point, edge);
-// Description:
-//   Return >0 if point is left of the line defined by edge.
-//   Return =0 if point is on the line.
-//   Return <0 if point is right of the line.
-// Arguments:
-//   point = The point to check position of.
-//   edge = Array of two points forming the line segment to test against.
-function point_left_of_segment2d(point, edge) =
-    (edge[1].x-edge[0].x) * (point.y-edge[0].y) - (point.x-edge[0].x) * (edge[1].y-edge[0].y);
+    assert( is_vector(point,2), "Invalid point." )
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    assert( _valid_line(edge,eps=eps), "Invalid segment." )
+    approx(point,edge[0],eps=eps) 
+    || approx(point,edge[1],eps=eps) // The point is an endpoint
+    || sign(edge[0].x-point.x)==sign(point.x-edge[1].x)  // point is in between the
+    || ( sign(edge[0].y-point.y)==sign(point.y-edge[1].y)  // edge endpoints
+        && approx(point_left_of_line2d(point, edge),0,eps=eps) );  // and on the line defined by edge
 
+function point_on_segment2d(point, edge, eps=EPSILON) =
+    assert( is_vector(point,2), "Invalid point." )
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    assert( _valid_line(edge,eps=eps), "Invalid segment." )
+    let( dp = point-edge[0],
+         de = edge[1]-edge[0],
+         ne = norm(de) ) 
+    ( dp*de >= -eps*ne ) 
+    && ( (dp-de)*de <= eps*ne )              // point projects on the segment
+    && _dist(point-edge[0],unit(de))<eps;   // point is on the line 
+    
+    
+//Internal - distance from point `d` to the line passing through the origin with unit direction n
+//_dist works for any dimension
+function _dist(d,n) = norm(d-(d * n) * n);
 
 // Internal non-exposed function.
 function _point_above_below_segment(point, edge) =
     edge[0].y <= point.y? (
-        (edge[1].y > point.y && point_left_of_segment2d(point, edge) > 0)? 1 : 0
+        (edge[1].y > point.y && point_left_of_line2d(point, edge) > 0)? 1 : 0
     ) : (
-        (edge[1].y <= point.y && point_left_of_segment2d(point, edge) < 0)? -1 : 0
+        (edge[1].y <= point.y && point_left_of_line2d(point, edge) < 0)? -1 : 0
     );
 
+//Internal
+function _valid_line(line,dim,eps=EPSILON) = 
+    is_matrix(line,2,dim) 
+    && ! approx(norm(line[1]-line[0]), 0, eps); 
+    
+//Internal
+function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps);
+
+
+// Function: point_left_of_line2d()
+// Usage:
+//   point_left_of_line2d(point, line);
+// Description:
+//   Return >0 if point is left of the line defined by `line`.
+//   Return =0 if point is on the line.
+//   Return <0 if point is right of the line.
+// Arguments:
+//   point = The point to check position of.
+//   line  = Array of two points forming the line segment to test against.
+function point_left_of_line2d(point, line) =
+    assert( is_vector(point,2) && is_vector(line*point, 2), "Improper input." )
+    cross(line[0]-point, line[1]-line[0]);
+
 
 // Function: collinear()
 // Usage:
-//   collinear(a, b, c, [eps]);
+//   collinear(a, [b, c], [eps]);
 // Description:
-//   Returns true if three points are co-linear.
+//   Returns true if the points `a`, `b` and `c` are co-linear or if the list of points `a` is collinear.
 // Arguments:
-//   a = First point.
-//   b = Second point.
-//   c = Third point.
+//   a = First point or list of points.
+//   b = Second point or undef; it should be undef if `c` is undef
+//   c = Third point or undef.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
 function collinear(a, b, c, eps=EPSILON) =
-    approx(a,b,eps=eps)? true :
-    distance_from_line([a,b], c) < eps;
+    assert( is_path([a,b,c],dim=undef)
+            || ( is_undef(b) && is_undef(c) && is_path(a,dim=undef) ), 
+            "Input should be 3 points or a list of points with same dimension.")
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    let( points = is_def(c) ? [a,b,c]: a )
+    len(points)<3 ? true
+    : noncollinear_triple(points,error=false,eps=eps)==[];
+ 
 
+//*** valid for any dimension
 
-// Function: collinear_indexed()
-// Usage:
-//   collinear_indexed(points, a, b, c, [eps]);
-// Description:
-//   Returns true if three points are co-linear.
-// Arguments:
-//   points = A list of points.
-//   a = Index in `points` of first point.
-//   b = Index in `points` of second point.
-//   c = Index in `points` of third point.
-//   eps = Acceptable max angle variance.  Default: EPSILON (1e-9) degrees.
-function collinear_indexed(points, a, b, c, eps=EPSILON) =
-    let(
-        p1=points[a],
-        p2=points[b],
-        p3=points[c]
-    ) collinear(p1, p2, p3, eps);
-
-
-// Function: points_are_collinear()
-// Usage:
-//   points_are_collinear(points);
-// Description:
-//   Given a list of points, returns true if all points in the list are collinear.
-// Arguments:
-//   points = The list of points to test.
-//   eps = How much variance is allowed in testing that each point is on the same line.  Default: `EPSILON` (1e-9)
-function points_are_collinear(points, eps=EPSILON) =
-    let(
-        a = furthest_point(points[0], points),
-        b = furthest_point(points[a], points),
-        pa = points[a],
-        pb = points[b]
-    ) all([for (pt = points) collinear(pa, pb, pt, eps=eps)]);
-
+ 
 
 // Function: distance_from_line()
 // Usage:
@@ -112,10 +113,11 @@ function points_are_collinear(points, eps=EPSILON) =
 // Example:
 //   distance_from_line([[-10,0], [10,0]], [3,8]);  // Returns: 8
 function distance_from_line(line, pt) =
-    let(a=line[0], n=unit(line[1]-a), d=a-pt)
-    norm(d - ((d * n) * n));
-
-
+    assert( _valid_line(line) && is_vector(pt,len(line[0])), 
+            "Invalid line, invalid point or incompatible dimensions." )
+    _dist(pt-line[0],unit(line[1]-line[0]));
+    
+    
 // Function: line_normal()
 // Usage:
 //   line_normal([P1,P2])
@@ -133,9 +135,11 @@ function distance_from_line(line, pt) =
 //   color("green") stroke([p1,p1+10*n], endcap2="arrow2");
 //   color("blue") move_copies([p1,p2]) circle(d=2, $fn=12);
 function line_normal(p1,p2) =
-    is_undef(p2)?
-        assert(is_path(p1,2)) line_normal(p1[0],p1[1]) :
-        assert(is_vector(p1,2)&&is_vector(p2,2)) unit([p1.y-p2.y,p2.x-p1.x]);
+    is_undef(p2)
+    ?   assert( len(p1)==2 && !is_undef(p1[1]) , "Invalid input." ) 
+        line_normal(p1[0],p1[1]) 
+    :   assert( _valid_line([p1,p2],dim=2), "Invalid line." ) 
+        unit([p1.y-p2.y,p2.x-p1.x]);
 
 
 // 2D Line intersection from two segments.
@@ -166,7 +170,10 @@ function _general_line_intersection(s1,s2,eps=EPSILON) =
 //   l2 = Second 2D line, given as a list of two 2D points on the line.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
 function line_intersection(l1,l2,eps=EPSILON) =
-    let(isect = _general_line_intersection(l1,l2,eps=eps)) isect[0];
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    assert( _valid_line(l1,dim=2,eps=eps) &&_valid_line(l2,dim=2,eps=eps), "Invalid line(s)." )
+    let(isect = _general_line_intersection(l1,l2,eps=eps)) 
+    isect[0];
 
 
 // Function: line_ray_intersection()
@@ -180,9 +187,12 @@ function line_intersection(l1,l2,eps=EPSILON) =
 //   ray = The 2D ray, given as a list `[START,POINT]` of the 2D start-point START, and a 2D point POINT on the ray.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
 function line_ray_intersection(line,ray,eps=EPSILON) =
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    assert( _valid_line(line,dim=2,eps=eps) && _valid_line(ray,dim=2,eps=eps), "Invalid line or ray." )
     let(
         isect = _general_line_intersection(line,ray,eps=eps)
-    ) isect[2]<0-eps? undef : isect[0];
+    ) 
+    (isect[2]<0-eps) ? undef : isect[0];
 
 
 // Function: line_segment_intersection()
@@ -196,6 +206,8 @@ function line_ray_intersection(line,ray,eps=EPSILON) =
 //   segment = The bounded 2D line segment, given as a list of the two 2D endpoints of the segment.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
 function line_segment_intersection(line,segment,eps=EPSILON) =
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    assert( _valid_line(line,  dim=2,eps=eps) &&_valid_line(segment,dim=2,eps=eps), "Invalid line or segment." )
     let(
         isect = _general_line_intersection(line,segment,eps=eps)
     ) isect[2]<0-eps || isect[2]>1+eps ? undef : isect[0];
@@ -212,9 +224,12 @@ function line_segment_intersection(line,segment,eps=EPSILON) =
 //   r2 = Second 2D ray, given as a list `[START,POINT]` of the 2D start-point START, and a 2D point POINT on the ray.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
 function ray_intersection(r1,r2,eps=EPSILON) =
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    assert( _valid_line(r1,dim=2,eps=eps) && _valid_line(r2,dim=2,eps=eps), "Invalid ray(s)." )
     let(
         isect = _general_line_intersection(r1,r2,eps=eps)
-    ) isect[1]<0-eps || isect[2]<0-eps? undef : isect[0];
+    ) 
+    isect[1]<0-eps || isect[2]<0-eps ? undef : isect[0];
 
 
 // Function: ray_segment_intersection()
@@ -228,9 +243,16 @@ function ray_intersection(r1,r2,eps=EPSILON) =
 //   segment = The bounded 2D line segment, given as a list of the two 2D endpoints of the segment.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
 function ray_segment_intersection(ray,segment,eps=EPSILON) =
+    assert( _valid_line(ray,dim=2,eps=eps) && _valid_line(segment,dim=2,eps=eps), "Invalid ray or segment." )
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
     let(
         isect = _general_line_intersection(ray,segment,eps=eps)
-    ) isect[1]<0-eps || isect[2]<0-eps || isect[2]>1+eps ? undef : isect[0];
+    ) 
+    isect[1]<0-eps 
+    || isect[2]<0-eps 
+    || isect[2]>1+eps
+    ? undef 
+    : isect[0];
 
 
 // Function: segment_intersection()
@@ -244,9 +266,17 @@ function ray_segment_intersection(ray,segment,eps=EPSILON) =
 //   s2 = Second 2D segment, given as a list of the two 2D endpoints of the line segment.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
 function segment_intersection(s1,s2,eps=EPSILON) =
+    assert( _valid_line(s1,dim=2,eps=eps) && _valid_line(s2,dim=2,eps=eps), "Invalid segment(s)." )
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
     let(
         isect = _general_line_intersection(s1,s2,eps=eps)
-    ) isect[1]<0-eps || isect[1]>1+eps || isect[2]<0-eps || isect[2]>1+eps ? undef : isect[0];
+    ) 
+    isect[1]<0-eps 
+    || isect[1]>1+eps 
+    || isect[2]<0-eps 
+    || isect[2]>1+eps 
+    ? undef 
+    : isect[0];
 
 
 // Function: line_closest_point()
@@ -311,6 +341,12 @@ function line_closest_point(line,pt) =
     )
     line[0] + projection*segvec;
 
+function line_closest_point(line,pt) =
+    assert(_valid_line(line), "Invalid line." )
+    assert( is_vector(pt,len(line[0])), "Invalid point or incompatible dimensions." )
+    let( n = unit( line[0]- line[1]) )
+    line[1]+((pt- line[1]) * n) * n;
+
 
 // Function: ray_closest_point()
 // Usage:
@@ -364,9 +400,8 @@ function line_closest_point(line,pt) =
 //   color("blue") translate(pt) sphere(r=1,$fn=12);
 //   color("red") translate(p2) sphere(r=1,$fn=12);
 function ray_closest_point(ray,pt) =
-    assert(is_path(ray)&&len(ray)==2)
-    assert(same_shape(pt,ray[0]))
-    assert(!approx(ray[0],ray[1]))
+    assert( _valid_line(ray), "Invalid ray." )
+    assert(is_vector(pt,len(ray[0])), "Invalid point or incompatible dimensions." )
     let(
         seglen = norm(ray[1]-ray[0]),
         segvec = (ray[1]-ray[0])/seglen,
@@ -428,8 +463,8 @@ function ray_closest_point(ray,pt) =
 //   color("blue") translate(pt) sphere(r=1,$fn=12);
 //   color("red") translate(p2) sphere(r=1,$fn=12);
 function segment_closest_point(seg,pt) =
-    assert(is_path(seg)&&len(seg)==2)
-    assert(same_shape(pt,seg[0]))
+    assert(_valid_line(seg), "Invalid segment." )
+    assert(len(pt)==len(seg[0]), "Incompatible dimensions." )
     approx(seg[0],seg[1])? seg[0] :
     let(
         seglen = norm(seg[1]-seg[0]),
@@ -440,6 +475,26 @@ function segment_closest_point(seg,pt) =
     projection>=seglen ? seg[1] :
     seg[0] + projection*segvec;
 
+    
+// Function: line_from_points()
+// Usage:
+//   line_from_points(points, [fast], [eps]);
+// Description:
+//   Given a list of 2 or more colinear points, returns a line containing them.
+//   If `fast` is false and the points are coincident, then `undef` is returned.
+//   if `fast` is true, then the collinearity test is skipped and a line passing through 2 distinct arbitrary points is returned.
+// Arguments:
+//   points = The list of points to find the line through.
+//   fast = If true, don't verify that all points are collinear.  Default: false
+//   eps = How much variance is allowed in testing each point against the line.  Default: `EPSILON` (1e-9)
+function line_from_points(points, fast=false, eps=EPSILON) =
+    assert( is_path(points,dim=undef), "Improper point list." )
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    let( pb = furthest_point(points[0],points) )
+    approx(norm(points[pb]-points[0]),0) ? undef :
+    fast || collinear(points) ? [points[pb], points[0]] : undef;
+
+
 
 // Section: 2D Triangles
 
@@ -486,21 +541,30 @@ function segment_closest_point(seg,pt) =
 //   ang = tri_calc(adj=20,hyp=30)[3];
 //   ang2 = tri_calc(adj=20,hyp=40)[4];
 function tri_calc(ang,ang2,adj,opp,hyp) =
-    assert(ang==undef || ang2==undef,"You cannot specify both ang and ang2.")
-    assert(num_defined([ang,ang2,adj,opp,hyp])==2, "You must specify exactly two arguments.")
+    assert(ang==undef || ang2==undef,"At most one angle is allowed.")
+    assert(num_defined([ang,ang2,adj,opp,hyp])==2, "Exactly two arguments must be given.")
     let(
-        ang = ang!=undef? assert(ang>0&&ang<90) ang :
-            ang2!=undef? (90-ang2) :
-            adj==undef? asin(constrain(opp/hyp,-1,1)) :
-            opp==undef? acos(constrain(adj/hyp,-1,1)) :
-            atan2(opp,adj),
-        ang2 = ang2!=undef? assert(ang2>0&&ang2<90) ang2 : (90-ang),
-        adj = adj!=undef? assert(adj>0) adj :
-            (opp!=undef? (opp/tan(ang)) : (hyp*cos(ang))),
-        opp = opp!=undef? assert(opp>0) opp :
-            (adj!=undef? (adj*tan(ang)) : (hyp*sin(ang))),
-        hyp = hyp!=undef? assert(hyp>0) assert(adj<hyp) assert(opp<hyp) hyp :
-            (adj!=undef? (adj/cos(ang)) : (opp/sin(ang)))
+        ang   = ang!=undef
+                ? assert(ang>0&&ang<90, "The input angles should be acute angles." ) ang 
+                : ang2!=undef ? (90-ang2) 
+                : adj==undef ? asin(constrain(opp/hyp,-1,1)) 
+                : opp==undef ? acos(constrain(adj/hyp,-1,1)) 
+                : atan2(opp,adj),
+        ang2 =  ang2!=undef
+                ? assert(ang2>0&&ang2<90, "The input angles should be acute angles." ) ang2 
+                : (90-ang),
+        adj  =  adj!=undef
+                ? assert(adj>0, "Triangle side lengths should be positive." ) adj 
+                : (opp!=undef? (opp/tan(ang)) : (hyp*cos(ang))),
+        opp  =  opp!=undef
+                ? assert(opp>0, "Triangle side lengths should be positive." )  opp 
+                : (adj!=undef? (adj*tan(ang)) : (hyp*sin(ang))),
+        hyp  =  hyp!=undef
+                ? assert(hyp>0, "Triangle side lengths should be positive." ) 
+                  assert(adj<hyp && opp<hyp, "Hyphotenuse length should be greater than the other sides." )
+                  hyp 
+                : (adj!=undef? (adj/cos(ang)) 
+                : (opp/sin(ang)))
     )
     [adj, opp, hyp, ang, ang2];
 
@@ -517,8 +581,8 @@ function tri_calc(ang,ang2,adj,opp,hyp) =
 // Example:
 //   hyp = hyp_opp_to_adj(5,3);  // Returns: 4
 function hyp_opp_to_adj(hyp,opp) =
-    assert(is_num(hyp)&&hyp>=0)
-    assert(is_num(opp)&&opp>=0)
+    assert(is_finite(hyp+opp) && hyp>=0 && opp>=0, 
+           "Triangle side lengths should be a positive numbers." )
     sqrt(hyp*hyp-opp*opp);
 
 
@@ -534,8 +598,8 @@ function hyp_opp_to_adj(hyp,opp) =
 // Example:
 //   adj = hyp_ang_to_adj(8,60);  // Returns: 4
 function hyp_ang_to_adj(hyp,ang) =
-    assert(is_num(hyp)&&hyp>=0)
-    assert(is_num(ang)&&ang>0&&ang<90)
+    assert(is_finite(hyp) && hyp>=0, "Triangle side length should be a positive number." )
+    assert(is_finite(ang) && ang>0 && ang<90, "The angle should be an acute angle." )
     hyp*cos(ang);
 
 
@@ -551,8 +615,8 @@ function hyp_ang_to_adj(hyp,ang) =
 // Example:
 //   adj = opp_ang_to_adj(8,30);  // Returns: 4
 function opp_ang_to_adj(opp,ang) =
-    assert(is_num(opp)&&opp>=0)
-    assert(is_num(ang)&&ang>0&&ang<90)
+    assert(is_finite(opp) && opp>=0, "Triangle side length should be a positive number." )
+    assert(is_finite(ang) && ang>0 && ang<90, "The angle should be an acute angle." )
     opp/tan(ang);
 
 
@@ -567,8 +631,8 @@ function opp_ang_to_adj(opp,ang) =
 // Example:
 //   opp = hyp_adj_to_opp(5,4);  // Returns: 3
 function hyp_adj_to_opp(hyp,adj) =
-    assert(is_num(hyp)&&hyp>=0)
-    assert(is_num(adj)&&adj>=0)
+    assert(is_finite(hyp) && hyp>=0 && is_finite(adj) && adj>=0, 
+           "Triangle side lengths should be a positive numbers." )
     sqrt(hyp*hyp-adj*adj);
 
 
@@ -583,8 +647,8 @@ function hyp_adj_to_opp(hyp,adj) =
 // Example:
 //   opp = hyp_ang_to_opp(8,30);  // Returns: 4
 function hyp_ang_to_opp(hyp,ang) =
-    assert(is_num(hyp)&&hyp>=0)
-    assert(is_num(ang)&&ang>0&&ang<90)
+    assert(is_finite(hyp)&&hyp>=0, "Triangle side length should be a positive number." )
+    assert(is_finite(ang) && ang>0 && ang<90, "The angle should be an acute angle." )
     hyp*sin(ang);
 
 
@@ -599,8 +663,8 @@ function hyp_ang_to_opp(hyp,ang) =
 // Example:
 //   opp = adj_ang_to_opp(8,45);  // Returns: 8
 function adj_ang_to_opp(adj,ang) =
-    assert(is_num(adj)&&adj>=0)
-    assert(is_num(ang)&&ang>0&&ang<90)
+    assert(is_finite(adj)&&adj>=0, "Triangle side length should be a positive number." )
+    assert(is_finite(ang) && ang>0 && ang<90, "The angle should be an acute angle." )
     adj*tan(ang);
 
 
@@ -615,8 +679,8 @@ function adj_ang_to_opp(adj,ang) =
 // Example:
 //   hyp = adj_opp_to_hyp(3,4);  // Returns: 5
 function adj_opp_to_hyp(adj,opp) =
-    assert(is_num(adj)&&adj>=0)
-    assert(is_num(opp)&&opp>=0)
+    assert(is_finite(opp) && opp>=0 && is_finite(adj) && adj>=0, 
+           "Triangle side lengths should be a positive numbers." )
     norm([opp,adj]);
 
 
@@ -631,8 +695,8 @@ function adj_opp_to_hyp(adj,opp) =
 // Example:
 //   hyp = adj_ang_to_hyp(4,60);  // Returns: 8
 function adj_ang_to_hyp(adj,ang) =
-    assert(is_num(adj)&&adj>=0)
-    assert(is_num(ang)&&ang>=0&&ang<90)
+    assert(is_finite(adj) && adj>=0, "Triangle side length should be a positive number." )
+    assert(is_finite(ang) && ang>0 && ang<90, "The angle should be an acute angle." )
     adj/cos(ang);
 
 
@@ -647,8 +711,8 @@ function adj_ang_to_hyp(adj,ang) =
 // Example:
 //   hyp = opp_ang_to_hyp(4,30);  // Returns: 8
 function opp_ang_to_hyp(opp,ang) =
-    assert(is_num(opp)&&opp>=0)
-    assert(is_num(ang)&&ang>0&&ang<=90)
+    assert(is_finite(opp) && opp>=0, "Triangle side length should be a positive number." )
+    assert(is_finite(ang) && ang>0 && ang<90, "The angle should be an acute angle." )
     opp/sin(ang);
 
 
@@ -663,8 +727,8 @@ function opp_ang_to_hyp(opp,ang) =
 // Example:
 //   ang = hyp_adj_to_ang(8,4);  // Returns: 60 degrees
 function hyp_adj_to_ang(hyp,adj) =
-    assert(is_num(hyp)&&hyp>0)
-    assert(is_num(adj)&&adj>=0)
+    assert(is_finite(hyp) && hyp>0 && is_finite(adj) && adj>=0, 
+            "Triangle side lengths should be positive numbers." )
     acos(adj/hyp);
 
 
@@ -679,8 +743,8 @@ function hyp_adj_to_ang(hyp,adj) =
 // Example:
 //   ang = hyp_opp_to_ang(8,4);  // Returns: 30 degrees
 function hyp_opp_to_ang(hyp,opp) =
-    assert(is_num(hyp)&&hyp>0)
-    assert(is_num(opp)&&opp>=0)
+    assert(is_finite(hyp+opp) && hyp>0 && opp>=0, 
+            "Triangle side lengths should be positive numbers." )
     asin(opp/hyp);
 
 
@@ -695,8 +759,8 @@ function hyp_opp_to_ang(hyp,opp) =
 // Example:
 //   ang = adj_opp_to_ang(sqrt(3)/2,0.5);  // Returns: 30 degrees
 function adj_opp_to_ang(adj,opp) =
-    assert(is_num(adj)&&adj>=0)
-    assert(is_num(opp)&&opp>=0)
+    assert(is_finite(adj+opp) && adj>0 && opp>=0, 
+            "Triangle side lengths should be positive numbers." )
     atan2(opp,adj);
 
 
@@ -709,8 +773,11 @@ function adj_opp_to_ang(adj,opp) =
 // Examples:
 //   triangle_area([0,0], [5,10], [10,0]);  // Returns -50
 //   triangle_area([10,0], [5,10], [0,0]);  // Returns 50
-function triangle_area(a,b,c) =
-    len(a)==3? 0.5*norm(cross(c-a,c-b)) : (
+function triangle_area(a,b,c) = 
+    assert( is_path([a,b,c]),
+            "Invalid points or incompatible dimensions." )    
+    len(a)==3 ? 0.5*norm(cross(c-a,c-b)) 
+    : (
         a.x * (b.y - c.y) +
         b.x * (c.y - a.y) +
         c.x * (a.y - b.y)
@@ -720,44 +787,53 @@ function triangle_area(a,b,c) =
 
 // Section: Planes
 
+
 // Function: plane3pt()
 // Usage:
 //   plane3pt(p1, p2, p3);
 // Description:
-//   Generates the cartesian equation of a plane from three non-collinear points on the plane.
+//   Generates the cartesian equation of a plane from three 3d points.
 //   Returns [A,B,C,D] where Ax + By + Cz = D is the equation of a plane.
+//   Returns [], if the points are collinear.
 // Arguments:
 //   p1 = The first point on the plane.
 //   p2 = The second point on the plane.
 //   p3 = The third point on the plane.
 function plane3pt(p1, p2, p3) =
+    assert( is_path([p1,p2,p3],dim=3) && len(p1)==3,
+            "Invalid points or incompatible dimensions." )    
     let(
-        p1=point3d(p1),
-        p2=point3d(p2),
-        p3=point3d(p3),
-        normal = unit(cross(p3-p1, p2-p1))
-    ) concat(normal, [normal*p1]);
+        crx = cross(p3-p1, p2-p1),
+        nrm = norm(crx)
+    ) 
+    approx(nrm,0) ? [] :
+    concat(crx/nrm, [crx*p1]/nrm);
 
 
 // Function: plane3pt_indexed()
 // Usage:
 //   plane3pt_indexed(points, i1, i2, i3);
 // Description:
-//   Given a list of points, and the indices of three of those points,
+//   Given a list of 3d points, and the indices of three of those points,
 //   generates the cartesian equation of a plane that those points all
-//   lie on.  Requires that the three indexed points be non-collinear.
-//   Returns [A,B,C,D] where Ax+By+Cz=D is the equation of a plane.
+//   lie on. If the points are not collinear, returns [A,B,C,D] where Ax+By+Cz=D is the equation of a plane.
+//   If they are collinear, returns [].
 // Arguments:
 //   points = A list of points.
 //   i1 = The index into `points` of the first point on the plane.
 //   i2 = The index into `points` of the second point on the plane.
 //   i3 = The index into `points` of the third point on the plane.
 function plane3pt_indexed(points, i1, i2, i3) =
+    assert( is_vector([i1,i2,i3]) && min(i1,i2,i3)>=0 && is_list(points) && max(i1,i2,i3)<len(points),
+		        "Invalid or out of range indices." )
+    assert( is_path([points[i1], points[i2], points[i3]],dim=3),
+            "Improper points or improper dimensions." )
     let(
         p1 = points[i1],
         p2 = points[i2],
         p3 = points[i3]
-    ) plane3pt(p1,p2,p3);
+    ) 
+    plane3pt(p1,p2,p3);
 
 
 // Function: plane_from_normal()
@@ -768,6 +844,8 @@ function plane3pt_indexed(points, i1, i2, i3) =
 // Example:
 //   plane_from_normal([0,0,1], [2,2,2]);  // Returns the xy plane passing through the point (2,2,2)
 function plane_from_normal(normal, pt=[0,0,0]) =
+  assert( is_matrix([normal,pt],2,3) && !approx(norm(normal),0), 
+          "Inputs `normal` and `pt` should 3d vectors/points and `normal` cannot be zero." )
   concat(normal, [normal*pt]);
 
 
@@ -775,11 +853,10 @@ function plane_from_normal(normal, pt=[0,0,0]) =
 // Usage:
 //   plane_from_points(points, [fast], [eps]);
 // Description:
-//   Given a list of 3 or more coplanar points, returns the cartesian equation of a plane.
-//   Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane.
-//   If not all the points in the points list are coplanar, then `undef` is returned.
-//   If `fast` is true, then a list where not all points are coplanar will result
-//   in an invalid plane value, as all coplanar checks are skipped.
+//   Given a list of 3 or more coplanar 3D points, returns the coefficients of the cartesian equation of a plane,
+//   that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane.
+//   If `fast` is false and the points in the list are collinear or not coplanar, then `undef` is returned.
+//   if `fast` is true, then the coplanarity test is skipped and a plane passing through 3 non-collinear arbitrary points is returned.
 // Arguments:
 //   points = The list of points to find the plane of.
 //   fast = If true, don't verify that all points in the list are coplanar.  Default: false
@@ -791,31 +868,34 @@ function plane_from_normal(normal, pt=[0,0,0]) =
 //   cp = centroid(xyzpath);
 //   move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow();
 function plane_from_points(points, fast=false, eps=EPSILON) =
+    assert( is_path(points,dim=3), "Improper 3d point list." )
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
     let(
         points = deduplicate(points),
-        indices = sort(find_noncollinear_points(points)),
+        indices = noncollinear_triple(points,error=false)
+    )
+    indices==[] ? undef :
+    let(
+        indices = sort(indices), // why sorting?
         p1 = points[indices[0]],
         p2 = points[indices[1]],
         p3 = points[indices[2]],
-        plane = plane3pt(p1,p2,p3),
-        all_coplanar = fast || all([
-            for (pt = points) coplanar(plane,pt,eps=eps)
-        ])
-    ) all_coplanar? plane : undef;
+        plane = plane3pt(p1,p2,p3)
+    )
+    fast || points_on_plane(points,plane,eps=eps) ? plane : undef;
 
 
 // Function: plane_from_polygon()
 // Usage:
 //   plane_from_polygon(points, [fast], [eps]);
 // Description:
-//   Given a 3D planar polygon, returns the cartesian equation of a plane.
+//   Given a 3D planar polygon, returns the cartesian equation of its plane.
 //   Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane.
-//   If not all the points in the polygon are coplanar, then `undef` is returned.
-//   If `fast` is true, then a polygon where not all points are coplanar will
-//   result in an invalid plane value, as all coplanar checks are skipped.
+//   If not all the points in the polygon are coplanar, then [] is returned.
+//   If `fast` is true, the polygon coplanarity check is skipped and the plane may not contain all polygon points.
 // Arguments:
 //   poly = The planar 3D polygon to find the plane of.
-//   fast = If true, don't verify that all points in the polygon are coplanar.  Default: false
+//   fast = If true, doesn't verify that all points in the polygon are coplanar.  Default: false
 //   eps = How much variance is allowed in testing that each point is on the same plane.  Default: `EPSILON` (1e-9)
 // Example(3D):
 //   xyzpath = rot(45, v=[0,1,0], p=path3d(star(n=5,step=2,d=100), 70));
@@ -824,25 +904,29 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
 //   cp = centroid(xyzpath);
 //   move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow();
 function plane_from_polygon(poly, fast=false, eps=EPSILON) =
+    assert( is_path(poly,dim=3), "Invalid polygon." )
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
     let(
         poly = deduplicate(poly),
         n = polygon_normal(poly),
         plane = [n.x, n.y, n.z, n*poly[0]]
-    ) fast? plane : let(
-        all_coplanar = [
-            for (pt = poly)
-            if (!coplanar(plane,pt,eps=eps)) 1
-        ] == []
-    ) all_coplanar? plane :
-    undef;
+    ) 
+    fast? plane: coplanar(poly,eps=eps)? plane: [];
 
+//***
+// I don't see why this function uses a criterium different from plane_from_points.
+// In practical terms, what is the difference of finding a plane from points and from polygon?
+// The docs don't clarify.
+// These functions should be consistent if they are both necessary. The docs might reflect their distinction.
 
 // Function: plane_normal()
 // Usage:
 //   plane_normal(plane);
 // Description:
 //   Returns the unit length normal vector for the given plane.
-function plane_normal(plane) = unit([for (i=[0:2]) plane[i]]);
+function plane_normal(plane) = 
+    assert( _valid_plane(plane), "Invalid input plane." )
+    unit([plane.x, plane.y, plane.z]);
 
 
 // Function: plane_offset()
@@ -851,7 +935,9 @@ function plane_normal(plane) = unit([for (i=[0:2]) plane[i]]);
 // Description:
 //   Returns D, or the scalar offset of the plane from the origin. This can be a negative value.
 //   The absolute value of this is the distance of the plane from the origin at its closest approach.
-function plane_offset(plane) = plane[3];
+function plane_offset(plane) =  
+    assert( _valid_plane(plane), "Invalid input plane." )
+    plane[3]/norm([plane.x, plane.y, plane.z]);
 
 
 // Function: plane_transform()
@@ -859,8 +945,8 @@ function plane_offset(plane) = plane[3];
 //   mat = plane_transform(plane);
 // Description:
 //   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, returns a 3D affine
-//   transformation matrix that will rotate and translate from points on that plane
-//   to points on the XY plane.  You can generally then use `path2d()` to drop the
+//   transformation matrix that will linear transform points on that plane
+//   into points on the XY plane.  You can generally then use `path2d()` to drop the
 //   Z coordinates, so you can work with the points in 2D.
 // Arguments:
 //   plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
@@ -875,7 +961,34 @@ function plane_transform(plane) =
     let(
         n = plane_normal(plane),
         cp = n * plane[3]
-    ) rot(from=n, to=UP) * move(-cp);
+        ) 
+    rot(from=n, to=UP) * move(-cp);
+
+
+// Function: plane_projection()
+// Usage:
+//   plane_projection(points);
+// Description:
+//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the projection
+//   of the points on the plane.
+// Arguments:
+//   plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
+//   points = List of points to project
+// Example(3D):
+//   points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100))));
+//   plane = plane3pt([1,0,0],[0,1,0],[0,0,1]);
+//   proj = plane_projection(plane,points);
+function plane_projection(plane, points) =
+    assert( _valid_plane(plane), "Invalid plane." )
+    assert( is_path(points), "Invalid list of points or dimension." )
+    let( 
+        p  = len(points[0])==2
+             ? [for(pi=points) point3d(pi) ]
+             : points, 
+        plane = plane/norm([plane.x,plane.y,plane.z]),
+        n = [plane.x,plane.y,plane.z]
+        ) 
+    [for(pi=p) pi - (pi*n - plane[3])*n];
 
 
 // Function: plane_point_nearest_origin()
@@ -899,9 +1012,12 @@ function plane_point_nearest_origin(plane) =
 //   will be negative.  The normal of the plane is the same as [A,B,C].
 // Arguments:
 //   plane = The [A,B,C,D] values for the equation of the plane.
-//   point = The point to test.
+//   point = The distance evaluation point.
 function distance_from_plane(plane, point) =
-    [plane.x, plane.y, plane.z] * point3d(point) - plane[3];
+    assert( _valid_plane(plane), "Invalid input plane." )
+    assert( is_vector(point,3), "The point should be a 3D point." )
+    let( nrml = [plane.x, plane.y, plane.z] )
+    ( nrml* point - plane[3])/norm(nrml);
 
 
 // Function: closest_point_on_plane()
@@ -911,13 +1027,16 @@ function distance_from_plane(plane, point) =
 //   Takes a point, and a plane [A,B,C,D] where the equation of that plane is `Ax+By+Cz=D`.
 //   Returns the coordinates of the closest point on that plane to the given `point`.
 // Arguments:
-//   plane = The [A,B,C,D] values for the equation of the plane.
+//   plane = The [A,B,C,D] coefficients for the equation of the plane.
 //   point = The 3D point to find the closest point to.
 function closest_point_on_plane(plane, point) =
+    assert( _valid_plane(plane), "Invalid input plane." )
+    assert( is_vector(point,3), "Invalid point." )
     let(
-        n = unit(plane_normal(plane)),
+        n = unit([plane.x, plane.y, plane.z]),
         d = distance_from_plane(plane, point)
-    ) point - n*d;
+        ) 
+    point - n*d;
 
 
 // Returns [POINT, U] if line intersects plane at one point.
@@ -931,7 +1050,7 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
         u = p1 - p0,
         d = n * u
     ) abs(d)<eps? (
-        coplanar(plane, p0)? [line,undef] :  // Line on plane
+        points_on_plane(p0,plane,eps)? [line,undef] :  // Line on plane
         undef  // Line parallel to plane
     ) : let(
         v0 = closest_point_on_plane(plane, [0,0,0]),
@@ -940,6 +1059,12 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
         pt = s1 * u + p0
     ) [pt, s1];
 
+function _general_plane_line_intersection(plane, line, eps=EPSILON) =
+    let( a = plane*[each line[0],-1],
+         b = plane*[each(line[1]-line[0]),-1] )
+    approx(b,0,eps) 
+    ? points_on_plane(line[0],plane,eps)? [line,undef]: undef
+    : [ line[0]+a/b*(line[1]-line[0]), a/b ];
 
 // Function: plane_line_angle()
 // Usage: plane_line_angle(plane,line)
@@ -948,38 +1073,41 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
 //   The resulting angle is signed, with the sign positive if the vector p2-p1 lies on 
 //   the same side of the plane as the plane's normal vector.  
 function plane_line_angle(plane, line) =
+    assert( _valid_plane(plane), "Invalid plane." )
+    assert( _valid_line(line), "Invalid line." )
     let(
         vect = line[1]-line[0],
         zplane = plane_normal(plane),
         sin_angle = vect*zplane/norm(zplane)/norm(vect)
-    ) asin(constrain(sin_angle,-1,1));
+        ) 
+    asin(constrain(sin_angle,-1,1));
 
 
 // Function: plane_line_intersection()
 // Usage:
-//   pt = plane_line_intersection(plane, line, [eps]);
+//   pt = plane_line_intersection(plane, line, [bounded], [eps]);
 // Description:
 //   Takes a line, and a plane [A,B,C,D] where the equation of that plane is `Ax+By+Cz=D`.
 //   If `line` intersects `plane` at one point, then that intersection point is returned.
 //   If `line` lies on `plane`, then the original given `line` is returned.
-//   If `line` is parallel to, but not on `plane`, then `undef` is returned.
+//   If `line` is parallel to, but not on `plane`, then undef is returned.
 // Arguments:
 //   plane = The [A,B,C,D] values for the equation of the plane.
-//   line = A list of two 3D points that are on the line.
+//   line = A list of two distinct 3D points that are on the line.
 //   bounded = If false, the line is considered unbounded.  If true, it is treated as a bounded line segment.  If given as `[true, false]` or `[false, true]`, the boundedness of the points are specified individually, allowing the line to be treated as a half-bounded ray.  Default: false (unbounded)
-//   eps = The epsilon error value to determine whether the line is too close to parallel to the plane.  Default: `EPSILON` (1e-9)
+//   eps = The tolerance value in determining whether the line is parallel to the plane.  Default: `EPSILON` (1e-9)
 function plane_line_intersection(plane, line, bounded=false, eps=EPSILON) =
-    assert(is_vector(plane)&&len(plane)==4, "Invalid plane value.")
-    assert(is_path(line)&&len(line)==2, "Invalid line value.")
-    assert(!approx(line[0],line[1]), "The two points defining the line must not be the same point.")
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    assert(_valid_plane(plane,eps=eps) && _valid_line(line,dim=3,eps=eps), "Invalid plane and/or line.")
+    assert(is_bool(bounded) || (is_list(bounded) && len(bounded)==2), "Invalid bound condition(s).")
     let(
         bounded = is_list(bounded)? bounded : [bounded, bounded],
         res = _general_plane_line_intersection(plane, line, eps=eps)
     )
-    is_undef(res)? undef :
-    is_undef(res[1])? res[0] :
-    bounded[0]&&res[1]<0? undef :
-    bounded[1]&&res[1]>1? undef :
+    is_undef(res) ? undef :
+    is_undef(res[1]) ? res[0] :
+    bounded[0] && res[1]<0 ? undef :
+    bounded[1] && res[1]>1 ? undef :
     res[0];
 
 
@@ -988,22 +1116,28 @@ function plane_line_intersection(plane, line, bounded=false, eps=EPSILON) =
 //   pt = polygon_line_intersection(poly, line, [bounded], [eps]);
 // Description:
 //   Takes a possibly bounded line, and a 3D planar polygon, and finds their intersection point.
-//   If the line is on the plane as the polygon, and intersects, then a list of 3D line
-//   segments is returned, one for each section of the line that is inside the polygon.
-//   If the line is not on the plane of the polygon, but intersects, then the 3D intersection
-//   point is returned.  If the line does not intersect the polygon, then `undef` is returned.
+//   If the line and the polygon are on the same plane then returns a list, possibly empty, of 3D line
+//   segments, one for each section of the line that is inside the polygon.
+//   If the line is not on the plane of the polygon, but intersects it, then returns the 3D intersection
+//   point.  If the line does not intersect the polygon, then `undef` is returned.
 // Arguments:
 //   poly = The 3D planar polygon to find the intersection with.
-//   line = A list of two 3D points that are on the line.
+//   line = A list of two distinct 3D points on the line.
 //   bounded = If false, the line is considered unbounded.  If true, it is treated as a bounded line segment.  If given as `[true, false]` or `[false, true]`, the boundedness of the points are specified individually, allowing the line to be treated as a half-bounded ray.  Default: false (unbounded)
-//   eps = The epsilon error value to determine whether the line is too close to parallel to the plane.  Default: `EPSILON` (1e-9)
+//   eps = The tolerance value in determining whether the line is parallel to the plane.  Default: `EPSILON` (1e-9)
 function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
-    assert(is_path(poly))
-    assert(is_path(line)&&len(line)==2)
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    assert(is_path(poly,dim=3), "Invalid polygon." )
+    assert(is_bool(bounded) || (is_list(bounded) && len(bounded)==2), "Invalid bound condition(s).")
+    assert(_valid_line(line,dim=3,eps=eps), "Invalid line." )
     let(
         bounded = is_list(bounded)? bounded : [bounded, bounded],
         poly = deduplicate(poly),
-        indices = sort(find_noncollinear_points(poly)),
+        indices = noncollinear_triple(poly)
+    )
+    indices==[] ? undef :
+    let(
+        indices = sort(indices), // why sorting?
         p1 = poly[indices[0]],
         p2 = poly[indices[1]],
         p3 = poly[indices[2]],
@@ -1011,34 +1145,31 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
         res = _general_plane_line_intersection(plane, line, eps=eps)
     )
     is_undef(res)? undef :
-    is_undef(res[1])? (
-        let(
-            // Line is on polygon plane.
+    is_undef(res[1])
+    ? ( let(// Line is on polygon plane.
             linevec = unit(line[1] - line[0]),
             lp1 = line[0] + (bounded[0]? 0 : -1000000) * linevec,
             lp2 = line[1] + (bounded[1]? 0 :  1000000) * linevec,
             poly2d = clockwise_polygon(project_plane(poly, p1, p2, p3)),
             line2d = project_plane([lp1,lp2], p1, p2, p3),
             parts = split_path_at_region_crossings(line2d, [poly2d], closed=false),
-            inside = [
-                for (part = parts)
-                if (point_in_polygon(mean(part), poly2d)>0) part
-            ]
-        ) !inside? undef :
+            inside = [for (part = parts)
+                          if (point_in_polygon(mean(part), poly2d)>0) part
+                     ]
+        ) 
+        !inside? undef :
         let(
-            isegs = [
-                for (seg = inside)
-                lift_plane(seg, p1, p2, p3)
-            ]
-        ) isegs
-    ) :
-    bounded[0]&&res[1]<0? undef :
-    bounded[1]&&res[1]>1? undef :
-    let(
-        proj = clockwise_polygon(project_plane(poly, p1, p2, p3)),
-        pt = project_plane(res[0], p1, p2, p3)
-    ) point_in_polygon(pt, proj) < 0? undef :
-    res[0];
+            isegs = [for (seg = inside) lift_plane(seg, p1, p2, p3) ]
+        ) 
+        isegs
+    ) 
+    :   bounded[0]&&res[1]<0? [] :
+        bounded[1]&&res[1]>1? [] :
+        let(
+            proj = clockwise_polygon(project_plane(poly, p1, p2, p3)),
+            pt = project_plane(res[0], p1, p2, p3)
+        ) 
+        point_in_polygon(pt, proj) < 0 ? undef : res[0];
 
 
 // Function: plane_intersection()
@@ -1047,53 +1178,62 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
 // Description:
 //   Compute the point which is the intersection of the three planes, or the line intersection of two planes.
 //   If you give three planes the intersection is returned as a point.  If you give two planes the intersection
-//   is returned as a list of two points on the line of intersection.  If any of the input planes are parallel
-//   then returns undef.  
+//   is returned as a list of two points on the line of intersection.  If any two input planes are parallel
+//   or coincident then returns undef.  
 function plane_intersection(plane1,plane2,plane3) =
-    is_def(plane3)? let(
-        matrix = [for(p=[plane1,plane2,plane3]) select(p,0,2)],
-        rhs = [for(p=[plane1,plane2,plane3]) p[3]]
-    ) linear_solve(matrix,rhs) :
-    let(
-        normal = cross(plane_normal(plane1), plane_normal(plane2))
-    ) approx(norm(normal),0) ? undef :
-    let(
-        matrix = [for(p=[plane1,plane2]) select(p,0,2)],
-        rhs = [for(p=[plane1,plane2]) p[3]],
-        point = linear_solve(matrix,rhs)
-    ) is_undef(point)? undef :
-    [point, point+normal];
+    assert( _valid_plane(plane1) && _valid_plane(plane2) && (is_undef(plane3) ||_valid_plane(plane3)),
+                "The input must be 2 or 3 planes." )
+    is_def(plane3)
+    ?   let(
+          matrix = [for(p=[plane1,plane2,plane3]) select(p,0,2)],
+          rhs = [for(p=[plane1,plane2,plane3]) p[3]]
+        ) 
+        linear_solve(matrix,rhs)
+    :   let( normal = cross(plane_normal(plane1), plane_normal(plane2)) ) 
+        approx(norm(normal),0) ? undef :
+        let(
+            matrix = [for(p=[plane1,plane2]) select(p,0,2)],
+            rhs = [for(p=[plane1,plane2]) p[3]],
+            point = linear_solve(matrix,rhs)
+        ) 
+        point==[]? undef: [point, point+normal];
 
 
 // Function: coplanar()
 // Usage:
-//   coplanar(plane, point);
+//   coplanar(points,eps);
 // Description:
-//   Given a plane as [A,B,C,D] where the cartesian equation for that plane
-//   is Ax+By+Cz=D, determines if the given point is on that plane.
-//   Returns true if the point is on that plane.
+//   Returns true if the given 3D points are non-collinear and are on a plane.
 // Arguments:
-//   plane = The [A,B,C,D] values for the equation of the plane.
-//   point = The point to test.
-//   eps = How much variance is allowed in testing that each point is on the same plane.  Default: `EPSILON` (1e-9)
-function coplanar(plane, point, eps=EPSILON) =
-    abs(distance_from_plane(plane, point)) <= eps;
+//   points = The points to test.
+//   eps = How much variance is allowed in the planarity test.  Default: `EPSILON` (1e-9)
+function coplanar(points, eps=EPSILON) =
+    assert( is_path(points,dim=3) , "Input should be a list of 3D points." )
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative number." )
+    len(points)<=2 ? false
+    :   let( ip = noncollinear_triple(points,error=false,eps=eps) ) 
+        ip == [] ? false : 
+        let( plane  = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]), 
+             normal = point3d(plane) )
+        max( points*normal ) - plane[3]< eps*norm(normal);
 
-
-// Function: points_are_coplanar()
+    
+// Function: points_on_plane()
 // Usage:
-//   points_are_coplanar(points, [eps]);
+//   points_on_plane(points, plane, eps);
 // Description:
-//   Given a list of points, returns true if all points in the list are coplanar.
+//   Returns true if the given 3D points are on the given plane.
 // Arguments:
-//   points = The list of points to test.
-//   eps = How much variance is allowed in testing that each point is on the same plane.  Default: `EPSILON` (1e-9)
-function points_are_coplanar(points, eps=EPSILON) =
-    points_are_collinear(points, eps=eps)? true :
-    let(
-        plane = plane_from_points(points, fast=true, eps=eps)
-    ) all([for (pt = points) coplanar(plane, pt, eps=eps)]);
-
+//   plane = The plane to test the points on.
+//   points = The list of 3D points to test.
+//   eps = How much variance is allowed in the planarity testing.  Default: `EPSILON` (1e-9)
+function points_on_plane(points, plane, eps=EPSILON) =
+    assert( _valid_plane(plane), "Invalid plane." )
+    assert( is_matrix(points,undef,3) && len(points)>0, "Invalid pointlist." ) // using is_matrix it accepts len(points)==1
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    let( normal = point3d(plane),
+         pt_nrm = points*normal )
+    abs(max( max(pt_nrm) - plane[3], -min(pt_nrm)+plane[3]))< eps*norm(normal);
 
 
 // Function: in_front_of_plane()
@@ -1101,12 +1241,12 @@ function points_are_coplanar(points, eps=EPSILON) =
 //   in_front_of_plane(plane, point);
 // Description:
 //   Given a plane as [A,B,C,D] where the cartesian equation for that plane
-//   is Ax+By+Cz=D, determines if the given point is on the side of that
+//   is Ax+By+Cz=D, determines if the given 3D point is on the side of that
 //   plane that the normal points towards.  The normal of the plane is the
 //   same as [A,B,C].
 // Arguments:
-//   plane = The [A,B,C,D] values for the equation of the plane.
-//   point = The point to test.
+//   plane = The [A,B,C,D] coefficients for the equation of the plane.
+//   point = The 3D point to test.
 function in_front_of_plane(plane, point) =
     distance_from_plane(plane, point) > EPSILON;
 
@@ -1156,37 +1296,45 @@ function in_front_of_plane(plane, point) =
 function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
     let(r = get_radius(r=r, d=d, dflt=undef))
     assert(r!=undef, "Must specify either r or d.")
-    (is_undef(pt2) && is_undef(pt3) && is_list(pt1))? find_circle_2tangents(pt1[0], pt1[1], pt1[2], r=r) :
-    collinear(pt1, pt2, pt3)? undef :
-    let(
-        v1 = unit(pt1 - pt2),
-        v2 = unit(pt3 - pt2),
-        vmid = unit(mean([v1, v2])),
-        n = vector_axis(v1, v2),
-        a = vector_angle(v1, v2),
-        hyp = r / sin(a/2),
-        cp = pt2 + hyp * vmid
-    ) !tangents? [cp, n] :
-    let(
-        x = hyp * cos(a/2),
-        tp1 = pt2 + x * v1,
-        tp2 = pt2 + x * v2,
-        fff=echo(tp1=tp1,cp=cp,pt2=pt2),
-        dang1 = vector_angle(tp1-cp,pt2-cp),
-        dang2 = vector_angle(tp2-cp,pt2-cp)
-    ) [cp, n, tp1, tp2, dang1, dang2];
+    assert( ( is_path(pt1) && len(pt1)==3 && is_undef(pt2) && is_undef(pt3)) 
+            || (is_matrix([pt1,pt2,pt3]) && (len(pt1)==2 || len(pt1)==3) ),
+            "Invalid input points." )
+    is_undef(pt2) 
+    ? find_circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents) 
+    : collinear(pt1, pt2, pt3)? undef :
+        let(
+            v1 = unit(pt1 - pt2),
+            v2 = unit(pt3 - pt2),
+            vmid = unit(mean([v1, v2])),
+            n = vector_axis(v1, v2),
+            a = vector_angle(v1, v2),
+            hyp = r / sin(a/2),
+            cp = pt2 + hyp * vmid
+            ) 
+        !tangents ? [cp, n] :
+        let(
+            x = hyp * cos(a/2),
+            tp1 = pt2 + x * v1,
+            tp2 = pt2 + x * v2,
+//            fff=echo(tp1=tp1,cp=cp,pt2=pt2),
+            dang1 = vector_angle(tp1-cp,pt2-cp),
+            dang2 = vector_angle(tp2-cp,pt2-cp)
+            ) 
+        [cp, n, tp1, tp2, dang1, dang2];
 
 
 // Function: find_circle_3points()
 // Usage:
-//   find_circle_3points(pt1, pt2, pt3);
+//   find_circle_3points(pt1, [pt2, pt3]);
 // Description:
 //   Returns the [CENTERPOINT, RADIUS, NORMAL] of the circle that passes through three non-collinear
-//   points.  The centerpoint will be a 2D or 3D vector, depending on the points input.  If all three
+//   points where NORMAL is the normal vector of the plane that the circle is on (UP or DOWN if the points are 2D).
+//   The centerpoint will be a 2D or 3D vector, depending on the points input.  If all three
 //   points are 2D, then the resulting centerpoint will be 2D, and the normal will be UP ([0,0,1]).
 //   If any of the points are 3D, then the resulting centerpoint will be 3D.  If the three points are
 //   collinear, then `[undef,undef,undef]` will be returned.  The normal will be a normalized 3D
 //   vector with a non-negative Z axis.
+//   Instead of 3 arguments, it is acceptable to input the 3 points in a list `pt1`, leaving `pt2`and `pt3` as undef.
 // Arguments:
 //   pt1 = The first point.
 //   pt2 = The second point.
@@ -1198,34 +1346,65 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 //   translate(circ[0]) color("red") circle(d=3, $fn=12);
 //   move_copies(pts) color("blue") circle(d=3, $fn=12);
 function find_circle_3points(pt1, pt2, pt3) =
-    (is_undef(pt2) && is_undef(pt3) && is_list(pt1))? find_circle_3points(pt1[0], pt1[1], pt1[2]) :
-    collinear(pt1,pt2,pt3)? [undef,undef,undef] :
-    let(
-        v1 = pt1-pt2,
-        v2 = pt3-pt2,
-        n = vector_axis(v1,v2),
-        n2 = n.z<0? -n : n
-    ) len(pt1)+len(pt2)+len(pt3)>6? (
+    (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
+    ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
+    :   assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) 
+                && max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
+                "Invalid point(s)." )
+        collinear(pt1,pt2,pt3)? [undef,undef,undef] :
         let(
-            a = project_plane(pt1, pt1, pt2, pt3),
-            b = project_plane(pt2, pt1, pt2, pt3),
-            c = project_plane(pt3, pt1, pt2, pt3),
-            res = find_circle_3points(a, b, c)
-        ) res[0]==undef? [undef,undef,undef] : let(
-            cp = lift_plane(res[0], pt1, pt2, pt3),
-            r = norm(pt2-cp)
-        ) [cp, r, n2]
-    ) : let(
-        mp1 = pt2 + v1/2,
-        mp2 = pt2 + v2/2,
-        mpv1 = rot(90, v=n, p=v1),
-        mpv2 = rot(90, v=n, p=v2),
-        l1 = [mp1, mp1+mpv1],
-        l2 = [mp2, mp2+mpv2],
-        isect = line_intersection(l1,l2)
-    ) is_undef(isect)? [undef,undef,undef] : let(
-        r = norm(pt2-isect)
-    ) [isect, r, n2];
+            v1 = pt1-pt2,
+            v2 = pt3-pt2,
+            n = vector_axis(v1,v2),
+            n2 = n.z<0? -n : n
+        ) len(pt1)+len(pt2)+len(pt3)>6? (
+            let(
+                a = project_plane(pt1, pt1, pt2, pt3),
+                b = project_plane(pt2, pt1, pt2, pt3),
+                c = project_plane(pt3, pt1, pt2, pt3),
+                res = find_circle_3points(a, b, c)
+            ) res[0]==undef? [undef,undef,undef] : let(
+                cp = lift_plane(res[0], pt1, pt2, pt3),
+                r = norm(pt2-cp)
+            ) [cp, r, n2]
+        ) : let(
+            mp1 = pt2 + v1/2,
+            mp2 = pt2 + v2/2,
+            mpv1 = rot(90, v=n, p=v1),
+            mpv2 = rot(90, v=n, p=v2),
+            l1 = [mp1, mp1+mpv1],
+            l2 = [mp2, mp2+mpv2],
+            isect = line_intersection(l1,l2)
+        ) is_undef(isect)? [undef,undef,undef] : let(
+            r = norm(pt2-isect)
+        ) [isect, r, n2];
+        
+function find_circle_3points(pt1, pt2, pt3) =
+    (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
+    ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
+    :   assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) 
+                && max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
+                "Invalid point(s)." )
+        collinear(pt1,pt2,pt3)? [undef,undef,undef] :
+        let(
+            v  = [ point3d(pt1), point3d(pt2), point3d(pt3) ], // triangle vertices
+            ed = [for(i=[0:2]) v[(i+1)%3]-v[i] ],    // triangle edge vectors
+            pm = [for(i=[0:2]) v[(i+1)%3]+v[i] ]/2,  // edge mean points
+            es = sortidx( [for(di=ed) norm(di) ] ),   
+            e1 = ed[es[1]],                          // take the 2 longest edges
+            e2 = ed[es[2]],
+            n0 = vector_axis(e1,e2),                 // normal standardization 
+            n  = n0.z<0? -n0 : n0,
+            sc = plane_intersection(                 
+                    [ each e1, e1*pm[es[1]] ],       // planes orthogonal to 2 edges
+                    [ each e2, e2*pm[es[2]] ],
+                    [ each n,  n*v[0] ] ) ,          // triangle plane
+            cp = len(pt1)+len(pt2)+len(pt3)>6 ? sc: [sc.x, sc.y], 
+            r  = norm(sc-v[0])
+            )
+        [ cp, r, n ];
+     
+     
 
 
 
@@ -1233,14 +1412,14 @@ function find_circle_3points(pt1, pt2, pt3) =
 // Usage:
 //   tangents = circle_point_tangents(r|d, cp, pt);
 // Description:
-//   Given a circle and a point outside that circle, finds the tangent point(s) on the circle for a
+//   Given a 2d circle and a 2d point outside that circle, finds the 2d tangent point(s) on the circle for a
 //   line passing through the point.  Returns list of zero or more sublists of [ANG, TANGPT]
 // Arguments:
 //   r = Radius of the circle.
 //   d = Diameter of the circle.
-//   cp = The coordinates of the circle centerpoint.
-//   pt = The coordinates of the external point.
-// Example(2D):
+//   cp = The coordinates of the 2d circle centerpoint.
+//   pt = The coordinates of the 2d external point.
+// Example:
 //   cp = [-10,-10];  r = 30;  pt = [30,10];
 //   tanpts = subindex(circle_point_tangents(r=r, cp=cp, pt=pt),1);
 //   color("yellow") translate(cp) circle(r=r);
@@ -1248,9 +1427,8 @@ function find_circle_3points(pt1, pt2, pt3) =
 //   color("red") move_copies(tanpts) circle(d=3,$fn=12);
 //   color("blue") move_copies([cp,pt]) circle(d=3,$fn=12);
 function circle_point_tangents(r, d, cp, pt) =
-    assert(is_num(r) || is_num(d))
-    assert(is_vector(cp))
-    assert(is_vector(pt))
+    assert(is_finite(r) || is_finite(d), "Invalid radius or diameter." )
+    assert(is_path([cp, pt],dim=2), "Invalid center point or external point.")
     let(
         r = get_radius(r=r, d=d, dflt=1),
         delta = pt - cp,
@@ -1268,7 +1446,7 @@ function circle_point_tangents(r, d, cp, pt) =
 // Function: circle_circle_tangents()
 // Usage: circle_circle_tangents(c1, r1|d1, c2, r2|d2)
 // Description:
-//   Computes lines tangents to a pair of circles.  Returns a list of line endpoints [p1,p2] where
+//   Computes 2d lines tangents to a pair of circles in 2d.  Returns a list of line endpoints [p1,p2] where
 //   p2 is the tangent point on circle 1 and p2 is the tangent point on circle 2.
 //   If four tangents exist then the first one the left hand exterior tangent as regarded looking from
 //   circle 1 toward circle 2.  The second value is the right hand exterior tangent.  The third entry
@@ -1314,6 +1492,7 @@ function circle_point_tangents(r, d, cp, pt) =
 //   move(c2) stroke(circle(r=r2), width=.1, closed=true);
 //   echo(pts);   // Returns []
 function circle_circle_tangents(c1,r1,c2,r2,d1,d2) =
+    assert( is_path([c1,c2],dim=2), "Invalid center point(s)." )
     let(
         r1 = get_radius(r1=r1,d1=d1),
         r2 = get_radius(r1=r2,d1=d2),
@@ -1342,25 +1521,32 @@ function circle_circle_tangents(c1,r1,c2,r2,d1,d2) =
 // Section: Pointlists
 
 
-// Function: find_noncollinear_points()
+// Function: noncollinear_triple()
 // Usage:
-//   find_noncollinear_points(points);
+//   noncollinear_triple(points);
 // Description:
 //   Finds the indices of three good non-collinear points from the points list `points`.
-function find_noncollinear_points(points,error=true,eps=EPSILON) =
+//   If all points are collinear, returns [].
+function noncollinear_triple(points,error=true,eps=EPSILON) =
+    assert( is_path(points), "Invalid input points." )
+    assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative number." )
     let(
         pa = points[0],
-        b = furthest_point(pa, points),
-        n = unit(points[b]-pa),
-        relpoints = [for(pt=points) pt-pa],
-        proj = relpoints * n,
-        distlist = [for(i=[0:len(points)-1]) norm(relpoints[i]-proj[i]*n)]
-    )
-    max(distlist)<eps
+        b  = furthest_point(pa, points),
+        pb = points[b],
+        nrm = norm(pa-pb)
+        )
+    approx(nrm, 0)
     ? assert(!error, "Cannot find three noncollinear points in pointlist.")
-      []
-    : [0,b,max_index(distlist)];
-
+        []
+    :   let(
+            n = (pb-pa)/nrm,
+            distlist = [for(i=[0:len(points)-1]) _dist(points[i]-pa, n)]   
+           )
+        max(distlist)<eps
+        ?  assert(!error, "Cannot find three noncollinear points in pointlist.")
+           []
+        :  [0,b,max_index(distlist)];    
 
 // Function: pointlist_bounds()
 // Usage:
@@ -1372,13 +1558,14 @@ function find_noncollinear_points(points,error=true,eps=EPSILON) =
 // Arguments:
 //   pts = List of points.
 function pointlist_bounds(pts) =
-    assert(is_matrix(pts))
+    assert(is_matrix(pts) && len(pts)>0 && len(pts[0])>0 , "Invalid pointlist." ) 
     let(ptsT = transpose(pts))
     [
       [for(row=ptsT) min(row)],
       [for(row=ptsT) max(row)]
     ];
 
+
 // Function: closest_point()
 // Usage:
 //   closest_point(pt, points);
@@ -1388,6 +1575,8 @@ function pointlist_bounds(pts) =
 //   pt = The point to find the closest point to.
 //   points = The list of points to search.
 function closest_point(pt, points) =
+    assert( is_vector(pt), "Invalid point." )
+    assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
     min_index([for (p=points) norm(p-pt)]);
 
 
@@ -1400,6 +1589,8 @@ function closest_point(pt, points) =
 //   pt = The point to find the farthest point from.
 //   points = The list of points to search.
 function furthest_point(pt, points) =
+    assert( is_vector(pt), "Invalid point." )
+    assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
     max_index([for (p=points) norm(p-pt)]);
 
 
@@ -1410,8 +1601,14 @@ function furthest_point(pt, points) =
 // Usage:
 //   area = polygon_area(poly);
 // Description:
-//   Given a 2D or 3D planar polygon, returns the area of that polygon.  If the polygon is self-crossing, the results are undefined.
+//   Given a 2D or 3D planar polygon, returns the area of that polygon.  
+//   If the polygon is self-crossing, the results are undefined. For non-planar points the result is undef.
+//   When `signed` is true, a signed area is returned; a positive area indicates a counterclockwise polygon.
+// Arguments:
+//   poly = polygon to compute the area of.
+//   signed = if true, a signed area is returned (default: false)
 function polygon_area(poly) =
+    assert(is_path(poly), "Invalid polygon." )
     len(poly)<3? 0 :
     len(poly[0])==2? 0.5*sum([for(i=[0:1:len(poly)-1]) det2(select(poly,i,i+1))]) :
     let(
@@ -1422,19 +1619,33 @@ function polygon_area(poly) =
         total = sum([for (i=[0:1:len(poly)-1]) cross(poly[i], select(poly,i+1))]),
         res = abs(total * n) / 2
     ) res;
+    
+function polygon_area(poly, signed=false) =
+    assert(is_path(poly), "Invalid polygon." )
+    len(poly)<3 ? 0 :
+    len(poly[0])==2
+    ? sum([for(i=[1:1:len(poly)-2]) cross(poly[i]-poly[0],poly[i+1]-poly[0]) ])/2
+    : let( plane = plane_from_points(poly) )
+      plane==undef? undef :
+      let( n = unit(plane_normal(plane)), 
+           total = sum([for(i=[1:1:len(poly)-1]) cross(poly[i]-poly[0],poly[i+1]-poly[0])*n ])/2
+          )
+      signed ? total : abs(total);
 
 
-// Function: polygon_is_convex()
+// Function: is_convex_polygon()
 // Usage:
-//   polygon_is_convex(poly);
+//   is_convex_polygon(poly);
 // Description:
-//   Returns true if the given polygon is convex.  Result is undefined if the polygon is self-intersecting.
+//   Returns true if the given 2D polygon is convex.  The result is meaningless if the polygon is not simple (self-intersecting).
+//   If the points are collinear the result is true. 
 // Example:
-//   polygon_is_convex(circle(d=50));  // Returns: true
+//   is_convex_polygon(circle(d=50));  // Returns: true
 // Example:
 //   spiral = [for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]];
-//   polygon_is_convex(spiral);  // Returns: false
-function polygon_is_convex(poly) =
+//   is_convex_polygon(spiral);  // Returns: false
+function is_convex_polygon(poly) =
+    assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
     let(
         l = len(poly),
         c = [for (i=idx(poly)) cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l])]
@@ -1442,6 +1653,19 @@ function polygon_is_convex(poly) =
     len([for (x=c) if(x>0) 1])==0 ||
     len([for (x=c) if(x<0) 1])==0;
 
+function is_convex_polygon(poly) =
+    assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
+    let( l = len(poly) )
+    len([for( i = l-1, 
+              c = cross(poly[(i+1)%l]-poly[i], poly[(i+2)%l]-poly[(i+1)%l]), 
+              s = sign(c);
+            i>=0 && sign(c)==s;
+              i = i-1, 
+              c = i<0? 0: cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l]), 
+              s = s==0 ? sign(c) : s
+            ) i 
+        ])== l;
+
 
 // Function: polygon_shift()
 // Usage:
@@ -1454,6 +1678,7 @@ function polygon_is_convex(poly) =
 // Example:
 //   polygon_shift([[3,4], [8,2], [0,2], [-4,0]], 2);   // Returns [[0,2], [-4,0], [3,4], [8,2]]
 function polygon_shift(poly, i) =
+    assert(is_path(poly), "Invalid polygon." )
     list_rotate(cleanup_path(poly), i);
 
 
@@ -1463,6 +1688,8 @@ function polygon_shift(poly, i) =
 // Description:
 //   Given a polygon `path`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
 function polygon_shift_to_closest_point(path, pt) =
+    assert(is_vector(pt), "Invalid point." )
+    assert(is_path(path,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
     let(
         path = cleanup_path(path),
         dists = [for (p=path) norm(p-pt)],
@@ -1500,8 +1727,9 @@ function polygon_shift_to_closest_point(path, pt) =
 //   color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
 //   color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
 function reindex_polygon(reference, poly, return_error=false) = 
-    assert(is_path(reference) && is_path(poly))
-    assert(len(reference)==len(poly), "Polygons must be the same length in reindex_polygon")
+    assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
+           "Invalid polygon(s) or incompatible dimensions. " )
+    assert(len(reference)==len(poly), "The polygons must have the same length.")
     let(
         dim = len(reference[0]),
         N = len(reference),
@@ -1525,13 +1753,34 @@ function reindex_polygon(reference, poly, return_error=false) =
     return_error? [optimal_poly, min(sums)] :
     optimal_poly;
 
+function reindex_polygon(reference, poly, return_error=false) = 
+    assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
+           "Invalid polygon(s) or incompatible dimensions. " )
+    assert(len(reference)==len(poly), "The polygons must have the same length.")
+    let(
+        dim = len(reference[0]),
+        N = len(reference),
+        fixpoly = dim != 2? poly :
+                  polygon_is_clockwise(reference)
+                  ? clockwise_polygon(poly)
+                  : ccw_polygon(poly),
+        I   = [for(i=[0:N-1]) 1],
+        val = [ for(k=[0:N-1]) 
+                  [for(i=[0:N-1]) 
+                     (reference[i]*poly[(i+k)%N]) ] ]*I,
+        optimal_poly = polygon_shift(fixpoly, max_index(val))
+      )
+    return_error? [optimal_poly, min(poly*(I*poly)-2*val)] :
+    optimal_poly;
+    
+
 
 // Function: align_polygon()
 // Usage:
 //   newpoly = align_polygon(reference, poly, angles, [cp]);
 // Description:
-//   Tries the list or range of angles to find a rotation of the specified polygon that best aligns
-//   with the reference polygon.  For each angle, the polygon is reindexed, which is a costly operation
+//   Tries the list or range of angles to find a rotation of the specified 2D polygon that best aligns
+//   with the reference 2D polygon.  For each angle, the polygon is reindexed, which is a costly operation
 //   so if run time is a problem, use a smaller sampling of angles.  Returns the rotated and reindexed
 //   polygon.
 // Arguments:
@@ -1546,9 +1795,11 @@ function reindex_polygon(reference, poly, return_error=false) =
 //   color("red") move_copies(scale(1.4,p=align_polygon(pentagon,hexagon,[0:10:359]))) circle(r=.1);
 //   move_copies(concat(pentagon,hexagon))circle(r=.1);
 function align_polygon(reference, poly, angles, cp) =
-    assert(is_path(reference) && is_path(poly))
-    assert(len(reference)==len(poly), "Polygons must be the same length to be aligned in align_polygon")
-    assert(is_num(angles[0]), "The `angle` parameter to align_polygon must be a range or vector")
+    assert(is_path(reference,dim=2) && is_path(poly,dim=2),
+           "Invalid polygon(s). " )
+    assert(len(reference)==len(poly), "The polygons must have the same length.")
+    assert( (is_vector(angles) && len(angles)>0) || valid_range(angles),
+            "The `angle` parameter must be a range or a non void list of numbers.")
     let(     // alignments is a vector of entries of the form: [polygon, error]
         alignments = [
             for(angle=angles) reindex_polygon(
@@ -1569,23 +1820,49 @@ function align_polygon(reference, poly, angles, cp) =
 //   Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid.
 //   If the polygon is self-intersecting, the results are undefined.
 function centroid(poly) =
-    len(poly[0])==2? (
-        sum([
+    assert( is_path(poly), "The input must be a 2D or 3D polygon." )
+    len(poly[0])==2
+    ? sum([
             for(i=[0:len(poly)-1])
             let(segment=select(poly,i,i+1))
             det2(segment)*sum(segment)
-        ]) / 6 / polygon_area(poly)
-    ) : (
-        let(
-            n = plane_normal(plane_from_points(poly)),
-            p1 = vector_angle(n,UP)>15? vector_axis(n,UP) : vector_axis(n,RIGHT),
-            p2 = vector_axis(n,p1),
-            cp = mean(poly),
-            proj = project_plane(poly,cp,cp+p1,cp+p2),
-            cxy = centroid(proj)
-        ) lift_plane(cxy,cp,cp+p1,cp+p2)
-    );
+          ]) / 6 / polygon_area(poly)
+    : let( plane = plane_from_points(poly, fast=true) )
+      assert( !is_undef(plane), "The polygon must be planar." )
+      let(
+        n = plane_normal(plane),
+        p1 = vector_angle(n,UP)>15? vector_axis(n,UP) : vector_axis(n,RIGHT),
+        p2 = vector_axis(n,p1),
+        cp = mean(poly),
+        proj = project_plane(poly,cp,cp+p1,cp+p2),
+        cxy = centroid(proj)
+         ) 
+      lift_plane(cxy,cp,cp+p1,cp+p2);
 
+function centroid(poly) =
+    assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
+    len(poly[0])==2
+    ? sum([
+            for(i=[0:len(poly)-1])
+            let(segment=select(poly,i,i+1))
+            det2(segment)*sum(segment)
+          ]) / 6 / polygon_area(poly)
+    : let( plane = plane_from_points(poly, fast=true) )
+      assert( !is_undef(plane), "The polygon must be planar." )
+      let(
+        n = plane_normal(plane),
+        val = sum([for(i=[1:len(poly)-2])
+                      let(
+                         v0 = poly[0],
+                         v1 = poly[i],
+                         v2 = poly[i+1],
+                         area = cross(v2-v0,v1-v0)*n
+                         )
+                      [ area, (v0+v1+v2)*area ]
+                  ] )
+          )
+      val[1]/val[0]/3;
+            
 
 // Function: point_in_polygon()
 // Usage:
@@ -1606,11 +1883,29 @@ function centroid(poly) =
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
 function point_in_polygon(point, path, eps=EPSILON) =
     // Original algorithm from http://geomalgorithms.com/a03-_inclusion.html
+    assert( is_vector(point,2) && is_path(path,dim=2) && len(path)>2,
+            "The point and polygon should be in 2D. The polygon should have more that 2 points." )
+    assert( is_finite(eps) && eps>=0, "Invalid tolerance." )
     // Does the point lie on any edges?  If so return 0.
-    sum([for(i=[0:1:len(path)-1]) let(seg=select(path,i,i+1)) if(!approx(seg[0],seg[1],eps=eps)) point_on_segment2d(point, seg, eps=eps)?1:0]) > 0? 0 :
+    let(
+        on_brd = [for(i=[0:1:len(path)-1]) 
+                    let( seg = select(path,i,i+1) ) 
+                    if( !approx(seg[0],seg[1],eps=eps) ) 
+                        point_on_segment2d(point, seg, eps=eps)? 1:0 ]
+        )
+    sum(on_brd) > 0? 0 :
     // Otherwise compute winding number and return 1 for interior, -1 for exterior
-    sum([for(i=[0:1:len(path)-1]) let(seg=select(path,i,i+1)) if(!approx(seg[0],seg[1],eps=eps)) _point_above_below_segment(point, seg)]) != 0? 1 : -1;
+    let(
+        windchk = [for(i=[0:1:len(path)-1]) 
+                    let(seg=select(path,i,i+1)) 
+                    if(!approx(seg[0],seg[1],eps=eps)) 
+                        _point_above_below_segment(point, seg)
+                  ]
+        )
+    sum(windchk) != 0 ? 1 : -1;
 
+//**
+// this function should be optimized avoiding the call of other functions
 
 // Function: polygon_is_clockwise()
 // Usage:
@@ -1621,7 +1916,7 @@ function point_in_polygon(point, path, eps=EPSILON) =
 // Arguments:
 //   path = The list of 2D path points for the perimeter of the polygon.
 function polygon_is_clockwise(path) =
-    assert(is_path(path) && len(path[0])==2, "Input must be a 2d path")
+    assert(is_path(path,dim=2), "Input should be a 2d polygon")
     let(
         minx = min(subindex(path,0)),
         lowind = search(minx, path, 0, 0),
@@ -1631,6 +1926,9 @@ function polygon_is_clockwise(path) =
         extreme = select(lowind,extreme_sub)
     ) det2([select(path,extreme+1)-path[extreme], select(path, extreme-1)-path[extreme]])<0;
 
+function polygon_is_clockwise(path) =
+    assert(is_path(path,dim=2), "Input should be a 2d path")
+    polygon_area(path, signed=true)<0;
 
 // Function: clockwise_polygon()
 // Usage:
@@ -1638,7 +1936,8 @@ function polygon_is_clockwise(path) =
 // Description:
 //   Given a 2D polygon path, returns the clockwise winding version of that path.
 function clockwise_polygon(path) =
-    polygon_is_clockwise(path)? path : reverse_polygon(path);
+    assert(is_path(path,dim=2), "Input should be a 2d polygon")
+    polygon_area(path, signed=true)<0 ? path : reverse_polygon(path);
 
 
 // Function: ccw_polygon()
@@ -1647,7 +1946,8 @@ function clockwise_polygon(path) =
 // Description:
 //   Given a 2D polygon path, returns the counter-clockwise winding version of that path.
 function ccw_polygon(path) =
-    polygon_is_clockwise(path)? reverse_polygon(path) : path;
+    assert(is_path(path,dim=2), "Input should be a 2d polygon")
+    polygon_area(path, signed=true)<0 ? reverse_polygon(path) : path;
 
 
 // Function: reverse_polygon()
@@ -1656,6 +1956,7 @@ function ccw_polygon(path) =
 // Description:
 //   Reverses a polygon's winding direction, while still using the same start point.
 function reverse_polygon(poly) =
+    assert(is_path(poly), "Input should be a polygon")
     let(lp=len(poly)) [for (i=idx(poly)) poly[(lp-i)%lp]];
 
 
@@ -1666,6 +1967,7 @@ function reverse_polygon(poly) =
 //   Given a 3D planar polygon, returns a unit-length normal vector for the
 //   clockwise orientation of the polygon. 
 function polygon_normal(poly) =
+    assert(is_path(poly,dim=3), "Invalid 3D polygon." )
     let(
         poly = path3d(cleanup_path(poly)),
         p0 = poly[0],
@@ -1772,6 +2074,9 @@ function _split_polygon_at_z(poly, z) =
 //   polys = A list of 3D polygons to split.
 //   xs = A list of scalar X values to split at.
 function split_polygons_at_each_x(polys, xs, _i=0) =
+    assert( is_consistent(polys) && is_path(poly[0],dim=3) ,
+            "The input list should contains only 3D polygons." )
+    assert( is_finite(xs), "The split value list should contain only numbers." )  
     _i>=len(xs)? polys :
     split_polygons_at_each_x(
         [
@@ -1779,6 +2084,17 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
             each _split_polygon_at_x(poly, xs[_i])
         ], xs, _i=_i+1
     );
+    
+//***
+// all the functions split_polygons_at_ may generate non simple polygons even from simple polygon inputs:
+//     split_polygons_at_each_y([[[-1,1,0],[0,0,0],[1,1,0],[1,-1,0],[-1,-1,0]]],[0])
+// produces:
+//   [  [[0, 0, 0], [1, 0, 0], [1, -1, 0], [-1, -1, 0], [-1, 0, 0]]
+//      [[-1, 1, 0], [0, 0, 0], [1, 1, 0], [1, 0, 0], [-1, 0, 0]] ]
+// and the second polygon is self-intersecting
+// besides, it fails in some simple cases as triangles:
+//   split_polygons_at_each_y([ [-1,-1,0],[1,-1,0],[0,1,0]],[0])==[]
+// this last failure may be fatal for vnf_bend
 
 
 // Function: split_polygons_at_each_y()
@@ -1790,6 +2106,9 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
 //   polys = A list of 3D polygons to split.
 //   ys = A list of scalar Y values to split at.
 function split_polygons_at_each_y(polys, ys, _i=0) =
+    assert( is_consistent(polys) && is_path(poly[0],dim=3) ,
+            "The input list should contains only 3D polygons." )
+    assert( is_finite(ys), "The split value list should contain only numbers." )  
     _i>=len(ys)? polys :
     split_polygons_at_each_y(
         [
@@ -1808,6 +2127,9 @@ function split_polygons_at_each_y(polys, ys, _i=0) =
 //   polys = A list of 3D polygons to split.
 //   zs = A list of scalar Z values to split at.
 function split_polygons_at_each_z(polys, zs, _i=0) =
+    assert( is_consistent(polys) && is_path(poly[0],dim=3) ,
+            "The input list should contains only 3D polygons." )
+    assert( is_finite(zs), "The split value list should contain only numbers." )  
     _i>=len(zs)? polys :
     split_polygons_at_each_z(
         [
diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad
index 8f780f4..da01a97 100644
--- a/tests/test_geometry.scad
+++ b/tests/test_geometry.scad
@@ -1,6 +1,144 @@
 include <../std.scad>
 
 
+//the commented lines are for tests to be written
+//the tests are ordered as they appear in geometry.scad
+
+test_point_on_segment2d();
+test_point_left_of_line2d();
+test_collinear();
+test_distance_from_line();
+test_line_normal();
+test_line_intersection();
+//test_line_ray_intersection();
+test_line_segment_intersection();
+//test_ray_intersection();
+//test_ray_segment_intersection();
+test_segment_intersection();
+test_line_closest_point();
+//test_ray_closest_point();
+test_segment_closest_point();
+test_line_from_points();
+test_tri_calc();
+//test_hyp_opp_to_adj();
+//test_hyp_ang_to_adj();
+//test_opp_ang_to_adj();
+//test_hyp_adj_to_opp();
+//test_hyp_ang_to_opp();
+//test_adj_ang_to_opp();
+//test_adj_opp_to_hyp();
+//test_adj_ang_to_hyp();
+//test_opp_ang_to_hyp();
+//test_hyp_adj_to_ang();
+//test_hyp_opp_to_ang();
+//test_adj_opp_to_ang();
+test_triangle_area();
+test_plane3pt();
+test_plane3pt_indexed();
+//test_plane_from_normal();
+test_plane_from_points();
+//test_plane_from_polygon();
+test_plane_normal();
+//test_plane_offset();
+//test_plane_transform();
+test_plane_projection();
+//test_plane_point_nearest_origin();
+test_distance_from_plane();
+
+test_find_circle_2tangents();
+test_find_circle_3points();
+test_circle_point_tangents();
+test_tri_functions();
+//test_closest_point_on_plane();
+//test__general_plane_line_intersection();
+//test_plane_line_angle();
+//test_plane_line_intersection();
+//test_polygon_line_intersection();
+//test_plane_intersection();
+test_coplanar();
+test_points_on_plane();
+test_in_front_of_plane();
+//test_find_circle_2tangents();
+//test_find_circle_3points();
+//test_circle_point_tangents();
+//test_circle_circle_tangents();
+test_noncollinear_triple();
+test_pointlist_bounds();
+test_closest_point();
+test_furthest_point();
+test_polygon_area();
+test_is_convex_polygon();
+test_polygon_shift();
+test_polygon_shift_to_closest_point();
+test_reindex_polygon();
+test_align_polygon();
+test_centroid();
+test_point_in_polygon();
+test_polygon_is_clockwise();
+test_clockwise_polygon();
+test_ccw_polygon();
+test_reverse_polygon();
+//test_polygon_normal();
+//test_split_polygons_at_each_x();
+//test_split_polygons_at_each_y();
+//test_split_polygons_at_each_z();
+
+//tests to migrate to other files
+test_is_path();
+test_is_closed_path();
+test_close_path();
+test_cleanup_path();
+test_simplify_path();
+test_simplify_path_indexed();
+test_is_region();
+
+// to be used when there are two alternative symmetrical outcomes 
+// from a function like a plane output.
+function standardize(v) = 
+    v==[]? [] :
+    sign([for(vi=v) if( ! approx(vi,0)) vi,0 ][0])*v;
+
+module test_points_on_plane() {
+    pts     = [for(i=[0:40]) rands(-1,1,3) ];
+    dir     = rands(-10,10,3);
+    normal0 = unit([1,2,3]);
+    ang     = rands(0,360,1)[0];
+    normal  = rot(a=ang,p=normal0);
+    plane   = [each normal, normal*dir];
+    prj_pts = plane_projection(plane,pts);
+    assert(points_on_plane(prj_pts,plane));
+    assert(!points_on_plane(concat(pts,[normal-dir]),plane));
+}
+*test_points_on_plane();
+
+module test_plane_projection(){
+    ang     = rands(0,360,1)[0];
+    dir     = rands(-10,10,3);
+    normal0 = unit([1,2,3]);
+    normal  = rot(a=ang,p=normal0);
+    plane0  = [each normal0, 0];
+    plane   = [each normal,  0];
+    planem  = [each normal, normal*dir];
+    pts     = [for(i=[1:10]) rands(-1,1,3)];
+    assert_approx( plane_projection(plane,pts),
+                   plane_projection(plane,plane_projection(plane,pts)));
+    assert_approx( plane_projection(plane,pts),
+                   rot(a=ang,p=plane_projection(plane0,rot(a=-ang,p=pts))));    
+    assert_approx( move((-normal*dir)*normal,p=plane_projection(planem,pts)),
+                   plane_projection(plane,pts));
+    assert_approx( move((normal*dir)*normal,p=plane_projection(plane,pts)),
+                   plane_projection(planem,pts));
+}
+*test_plane_projection();
+
+module test_line_from_points() {
+    assert_approx(line_from_points([[1,0],[0,0],[-1,0]]),[[-1,0],[1,0]]);
+    assert_approx(line_from_points([[1,1],[0,1],[-1,1]]),[[-1,1],[1,1]]);
+    assert(line_from_points([[1,1],[0,1],[-1,0]])==undef);
+    assert(line_from_points([[1,1],[0,1],[-1,0]],fast=true)== [[-1,0],[1,1]]);
+}
+*test_line_from_points();
+
 module test_point_on_segment2d() {
     assert(point_on_segment2d([-15,0], [[-10,0], [10,0]]) == false);
     assert(point_on_segment2d([-10,0], [[-10,0], [10,0]]) == true);
@@ -29,42 +167,28 @@ module test_point_on_segment2d() {
     assert(point_on_segment2d([ 10, 10], [[-10,-10], [10,10]]) == true);
     assert(point_on_segment2d([ 15, 15], [[-10,-10], [10,10]]) == false);
 }
-test_point_on_segment2d();
+*test_point_on_segment2d();
 
 
-module test_point_left_of_segment() {
-    assert(point_left_of_segment2d([ -3,  0], [[-10,-10], [10,10]]) > 0);
-    assert(point_left_of_segment2d([  0,  0], [[-10,-10], [10,10]]) == 0);
-    assert(point_left_of_segment2d([  3,  0], [[-10,-10], [10,10]]) < 0);
+module test_point_left_of_line2d() {
+    assert(point_left_of_line2d([ -3,  0], [[-10,-10], [10,10]]) > 0);
+    assert(point_left_of_line2d([  0,  0], [[-10,-10], [10,10]]) == 0);
+    assert(point_left_of_line2d([  3,  0], [[-10,-10], [10,10]]) < 0);
 }
-test_point_left_of_segment();
-
+*test_point_left_of_line2d();
 
 module test_collinear() {
     assert(collinear([-10,-10], [-15, -16], [10,10]) == false);
+    assert(collinear([[-10,-10], [-15, -16], [10,10]]) == false);
     assert(collinear([-10,-10], [-15, -15], [10,10]) == true);
+    assert(collinear([[-10,-10], [-15, -15], [10,10]]) == true);
     assert(collinear([-10,-10], [ -3,   0], [10,10]) == false);
     assert(collinear([-10,-10], [  0,   0], [10,10]) == true);
     assert(collinear([-10,-10], [  3,   0], [10,10]) == false);
     assert(collinear([-10,-10], [ 15,  15], [10,10]) == true);
     assert(collinear([-10,-10], [ 15,  16], [10,10]) == false);
 }
-test_collinear();
-
-
-module test_collinear_indexed() {
-    pts = [
-        [-20,-20], [-10,-20], [0,-10], [10,0], [20,10], [20,20], [15,30]
-    ];
-    assert(collinear_indexed(pts, 0,1,2) == false);
-    assert(collinear_indexed(pts, 1,2,3) == true);
-    assert(collinear_indexed(pts, 2,3,4) == true);
-    assert(collinear_indexed(pts, 3,4,5) == false);
-    assert(collinear_indexed(pts, 4,5,6) == false);
-    assert(collinear_indexed(pts, 4,3,2) == true);
-    assert(collinear_indexed(pts, 0,5,6) == false);
-}
-test_collinear_indexed();
+*test_collinear();
 
 
 module test_distance_from_line() {
@@ -73,7 +197,7 @@ module test_distance_from_line() {
     assert(abs(distance_from_line([[-10,-10,-10], [10,10,10]], [1,-1,0]) - sqrt(2)) < EPSILON);
     assert(abs(distance_from_line([[-10,-10,-10], [10,10,10]], [8,-8,0]) - 8*sqrt(2)) < EPSILON);
 }
-test_distance_from_line();
+*test_distance_from_line();
 
 
 module test_line_normal() {
@@ -97,7 +221,7 @@ module test_line_normal() {
         assert(approx(n2, n1));
     }
 }
-test_line_normal();
+*test_line_normal();
 
 
 module test_line_intersection() {
@@ -110,7 +234,7 @@ module test_line_intersection() {
     assert(line_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [-10, 10]]) == [0,0]);
     assert(line_intersection([[ -8,  0], [ 12,  4]], [[ 12,  0], [ -8,  4]]) == [2,2]);
 }
-test_line_intersection();
+*test_line_intersection();
 
 
 module test_segment_intersection() {
@@ -126,7 +250,7 @@ module test_segment_intersection() {
     assert(segment_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [-10, 10]]) == [0,0]);
     assert(segment_intersection([[ -8,  0], [ 12,  4]], [[ 12,  0], [ -8,  4]]) == [2,2]);
 }
-test_segment_intersection();
+*test_segment_intersection();
 
 
 module test_line_segment_intersection() {
@@ -141,7 +265,7 @@ module test_line_segment_intersection() {
     assert(line_segment_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [  1, -1]]) == undef);
     assert(line_segment_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [ -1,  1]]) == [0,0]);
 }
-test_line_segment_intersection();
+*test_line_segment_intersection();
 
 
 module test_line_closest_point() {
@@ -151,7 +275,7 @@ module test_line_closest_point() {
     assert(approx(line_closest_point([[-10,-20], [10,20]], [1,2]+[2,-1]), [1,2]));
     assert(approx(line_closest_point([[-10,-20], [10,20]], [13,31]), [15,30]));
 }
-test_line_closest_point();
+*test_line_closest_point();
 
 
 module test_segment_closest_point() {
@@ -162,10 +286,10 @@ module test_segment_closest_point() {
     assert(approx(segment_closest_point([[-10,-20], [10,20]], [13,31]), [10,20]));
     assert(approx(segment_closest_point([[-10,-20], [10,20]], [15,25]), [10,20]));
 }
-test_segment_closest_point();
-
+*test_segment_closest_point();
 
 module test_find_circle_2tangents() {
+//** missing tests with arg tangent=true
     assert(approx(find_circle_2tangents([10,10],[0,0],[10,-10],r=10/sqrt(2))[0],[10,0]));
     assert(approx(find_circle_2tangents([-10,10],[0,0],[-10,-10],r=10/sqrt(2))[0],[-10,0]));
     assert(approx(find_circle_2tangents([-10,10],[0,0],[10,10],r=10/sqrt(2))[0],[0,10]));
@@ -174,9 +298,9 @@ module test_find_circle_2tangents() {
     assert(approx(find_circle_2tangents([10,0],[0,0],[0,-10],r=10)[0],[10,-10]));
     assert(approx(find_circle_2tangents([0,-10],[0,0],[-10,0],r=10)[0],[-10,-10]));
     assert(approx(find_circle_2tangents([-10,0],[0,0],[0,10],r=10)[0],[-10,10]));
-    assert(approx(find_circle_2tangents(polar_to_xy(10,60),[0,0],[10,0],r=10)[0],polar_to_xy(20,30)));
+    assert_approx(find_circle_2tangents(polar_to_xy(10,60),[0,0],[10,0],r=10)[0],polar_to_xy(20,30));
 }
-test_find_circle_2tangents();
+*test_find_circle_2tangents();
 
 
 module test_find_circle_3points() {
@@ -291,7 +415,7 @@ module test_find_circle_3points() {
         }
     }
 }
-test_find_circle_3points();
+*test_find_circle_3points();
 
 
 module test_circle_point_tangents() {
@@ -304,7 +428,7 @@ module test_circle_point_tangents() {
         assert(approx(flatten(got), flatten(expected)));
     }
 }
-test_circle_point_tangents();
+*test_circle_point_tangents();
 
 
 module test_tri_calc() {
@@ -327,23 +451,9 @@ module test_tri_calc() {
         assert(approx(tri_calc(hyp=hyp, ang2=ang2), expected));
     }
 }
-test_tri_calc();
+*test_tri_calc();
 
 
-// Dummy modules to show up in coverage check script.
-module test_hyp_opp_to_adj();
-module test_hyp_ang_to_adj();
-module test_opp_ang_to_adj();
-module test_hyp_adj_to_opp();
-module test_hyp_ang_to_opp();
-module test_adj_ang_to_opp();
-module test_adj_opp_to_hyp();
-module test_adj_ang_to_hyp();
-module test_opp_ang_to_hyp();
-module test_hyp_adj_to_ang();
-module test_hyp_opp_to_ang();
-module test_adj_opp_to_ang();
-
 module test_tri_functions() {
     sides = rands(1,100,100,seed_value=8181);
     for (p = pair_wrap(sides)) {
@@ -365,7 +475,7 @@ module test_tri_functions() {
         assert_approx(adj_opp_to_ang(adj,opp), ang);
     }
 }
-test_tri_functions();
+*test_tri_functions();
 
 
 module test_triangle_area() {
@@ -373,7 +483,7 @@ module test_triangle_area() {
     assert(abs(triangle_area([0,0], [0,10], [0,15])) < EPSILON);
     assert(abs(triangle_area([0,0], [10,0], [0,10]) - 50) < EPSILON);
 }
-test_triangle_area();
+*test_triangle_area();
 
 
 module test_plane3pt() {
@@ -384,8 +494,7 @@ module test_plane3pt() {
     assert(plane3pt([0,0,0], [10,10,0], [20,0,0]) == [0,0,1,0]);
     assert(plane3pt([0,0,2], [10,10,2], [20,0,2]) == [0,0,1,2]);
 }
-test_plane3pt();
-
+*test_plane3pt();
 
 module test_plane3pt_indexed() {
     pts = [ [0,0,0], [10,0,0], [0,10,0], [0,0,10] ];
@@ -395,11 +504,11 @@ module test_plane3pt_indexed() {
     assert(plane3pt_indexed(pts, 0,1,3) == [0,1,0,0]);
     assert(plane3pt_indexed(pts, 0,3,1) == [0,-1,0,0]);
     assert(plane3pt_indexed(pts, 0,2,1) == [0,0,1,0]);
-    assert(plane3pt_indexed(pts, 0,1,2) == [0,0,-1,0]);
-    assert(plane3pt_indexed(pts, 3,2,1) == [s13,s13,s13,10*s13]);
-    assert(plane3pt_indexed(pts, 1,2,3) == [-s13,-s13,-s13,-10*s13]);
+    assert_approx(plane3pt_indexed(pts, 0,1,2), [0,0,-1,0]);
+    assert_approx(plane3pt_indexed(pts, 3,2,1), [s13,s13,s13,10*s13]);
+    assert_approx(plane3pt_indexed(pts, 1,2,3), [-s13,-s13,-s13,-10*s13]);
 }
-test_plane3pt_indexed();
+*test_plane3pt_indexed();
 
 
 module test_plane_from_points() {
@@ -410,7 +519,7 @@ module test_plane_from_points() {
     assert(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]) == [0,0,1,0]);
     assert(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]) == [0,0,1,2]);
 }
-test_plane_from_points();
+*test_plane_from_points();
 
 
 module test_plane_normal() {
@@ -421,7 +530,7 @@ module test_plane_normal() {
     assert(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])) == [0,0,1]);
     assert(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])) == [0,0,1]);
 }
-test_plane_normal();
+*test_plane_normal();
 
 
 module test_distance_from_plane() {
@@ -429,20 +538,16 @@ module test_distance_from_plane() {
     assert(distance_from_plane(plane1, [0,0,5]) == 5);
     assert(distance_from_plane(plane1, [5,5,8]) == 8);
 }
-test_distance_from_plane();
+*test_distance_from_plane();
 
 
 module test_coplanar() {
-    plane = plane3pt([0,0,0], [0,10,10], [10,0,10]);
-    assert(coplanar(plane, [5,5,10]) == true);
-    assert(coplanar(plane, [10/3,10/3,20/3]) == true);
-    assert(coplanar(plane, [0,0,0]) == true);
-    assert(coplanar(plane, [1,1,0]) == false);
-    assert(coplanar(plane, [-1,1,0]) == true);
-    assert(coplanar(plane, [1,-1,0]) == true);
-    assert(coplanar(plane, [5,5,5]) == false);
+    assert(coplanar([ [5,5,1],[0,0,1],[-1,-1,1] ]) == false);
+    assert(coplanar([ [5,5,1],[0,0,0],[-1,-1,1] ]) == true);
+    assert(coplanar([ [0,0,0],[1,0,1],[1,1,1], [0,1,2] ]) == false);
+    assert(coplanar([ [0,0,0],[1,0,1],[1,1,2], [0,1,1] ]) == true);
 }
-test_coplanar();
+*test_coplanar();
 
 
 module test_in_front_of_plane() {
@@ -455,7 +560,7 @@ module test_in_front_of_plane() {
     assert(in_front_of_plane(plane, [0,0,5]) == true);
     assert(in_front_of_plane(plane, [0,0,-5]) == false);
 }
-test_in_front_of_plane();
+*test_in_front_of_plane();
 
 
 module test_is_path() {
@@ -470,35 +575,43 @@ module test_is_path() {
     assert(is_path([[1,2,3],[4,5,6]]));
     assert(is_path([[1,2,3],[4,5,6],[7,8,9]]));
 }
-test_is_path();
+*test_is_path();
 
 
 module test_is_closed_path() {
     assert(!is_closed_path([[1,2,3],[4,5,6],[1,8,9]]));
     assert(is_closed_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]));
 }
-test_is_closed_path();
+*test_is_closed_path();
 
 
 module test_close_path() {
     assert(close_path([[1,2,3],[4,5,6],[1,8,9]]) == [[1,2,3],[4,5,6],[1,8,9],[1,2,3]]);
     assert(close_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]) == [[1,2,3],[4,5,6],[1,8,9],[1,2,3]]);
 }
-test_close_path();
+*test_close_path();
 
 
 module test_cleanup_path() {
     assert(cleanup_path([[1,2,3],[4,5,6],[1,8,9]]) == [[1,2,3],[4,5,6],[1,8,9]]);
     assert(cleanup_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]) == [[1,2,3],[4,5,6],[1,8,9]]);
 }
-test_cleanup_path();
+*test_cleanup_path();
 
 
 module test_polygon_area() {
     assert(approx(polygon_area([[1,1],[-1,1],[-1,-1],[1,-1]]), 4));
     assert(approx(polygon_area(circle(r=50,$fn=1000)), -PI*50*50, eps=0.1));
 }
-test_polygon_area();
+*test_polygon_area();
+
+
+module test_is_convex_polygon() {
+    assert(is_convex_polygon([[1,1],[-1,1],[-1,-1],[1,-1]]));
+    assert(is_convex_polygon(circle(r=50,$fn=1000)));
+    assert(!is_convex_polygon([[1,1],[0,0],[-1,1],[-1,-1],[1,-1]]));
+}
+*test_is_convex_polygon();
 
 
 module test_polygon_shift() {
@@ -506,7 +619,7 @@ module test_polygon_shift() {
     assert(polygon_shift(path,1) == [[-1,1],[-1,-1],[1,-1],[1,1]]);
     assert(polygon_shift(path,2) == [[-1,-1],[1,-1],[1,1],[-1,1]]);
 }
-test_polygon_shift();
+*test_polygon_shift();
 
 
 module test_polygon_shift_to_closest_point() {
@@ -516,56 +629,45 @@ module test_polygon_shift_to_closest_point() {
     assert(polygon_shift_to_closest_point(path,[-1.1,-1.1]) == [[-1,-1],[1,-1],[1,1],[-1,1]]);
     assert(polygon_shift_to_closest_point(path,[1.1,-1.1]) == [[1,-1],[1,1],[-1,1],[-1,-1]]);
 }
-test_polygon_shift_to_closest_point();
+*test_polygon_shift_to_closest_point();
 
 
-/*
-module test_first_noncollinear(){
-    pts = [
-        [1,1], [2,2], [3,3], [4,4], [4,5], [5,6]
-    ];
-    assert(first_noncollinear(0,1,pts) == 4);
-    assert(first_noncollinear(1,0,pts) == 4);
-    assert(first_noncollinear(0,2,pts) == 4);
-    assert(first_noncollinear(2,0,pts) == 4);
-    assert(first_noncollinear(1,2,pts) == 4);
-    assert(first_noncollinear(2,1,pts) == 4);
-    assert(first_noncollinear(0,3,pts) == 4);
-    assert(first_noncollinear(3,0,pts) == 4);
-    assert(first_noncollinear(1,3,pts) == 4);
-    assert(first_noncollinear(3,1,pts) == 4);
-    assert(first_noncollinear(2,3,pts) == 4);
-    assert(first_noncollinear(3,2,pts) == 4);
-    assert(first_noncollinear(0,4,pts) == 1);
-    assert(first_noncollinear(4,0,pts) == 1);
-    assert(first_noncollinear(1,4,pts) == 0);
-    assert(first_noncollinear(4,1,pts) == 0);
-    assert(first_noncollinear(2,4,pts) == 0);
-    assert(first_noncollinear(4,2,pts) == 0);
-    assert(first_noncollinear(3,4,pts) == 0);
-    assert(first_noncollinear(4,3,pts) == 0);
-    assert(first_noncollinear(0,5,pts) == 1);
-    assert(first_noncollinear(5,0,pts) == 1);
-    assert(first_noncollinear(1,5,pts) == 0);
-    assert(first_noncollinear(5,1,pts) == 0);
-    assert(first_noncollinear(2,5,pts) == 0);
-    assert(first_noncollinear(5,2,pts) == 0);
-    assert(first_noncollinear(3,5,pts) == 0);
-    assert(first_noncollinear(5,3,pts) == 0);
-    assert(first_noncollinear(4,5,pts) == 0);
-    assert(first_noncollinear(5,4,pts) == 0);
+module test_reindex_polygon() {
+   pent = subdivide_path([for(i=[0:4])[sin(72*i),cos(72*i)]],5);
+   circ = circle($fn=5,r=2.2);
+   assert_approx(reindex_polygon(circ,pent), [[0.951056516295,0.309016994375],[0.587785252292,-0.809016994375],[-0.587785252292,-0.809016994375],[-0.951056516295,0.309016994375],[0,1]]);
+   poly = [[-1,1],[-1,-1],[1,-1],[1,1],[0,0]];
+   ref  = [for(i=[0:4])[sin(72*i),cos(72*i)]];
+   assert_approx(reindex_polygon(ref,poly),[[0,0],[1,1],[1,-1],[-1,-1],[-1,1]]);
 }
-test_first_noncollinear();
-*/
+*test_reindex_polygon();
 
 
-module test_find_noncollinear_points() {
-    assert(find_noncollinear_points([[1,1],[2,2],[3,3],[4,4],[4,5],[5,6]]) == [0,5,3]);
-    assert(find_noncollinear_points([[1,1],[2,2],[8,3],[4,4],[4,5],[5,6]]) == [0,2,5]);
+module test_align_polygon() {
+   pentagon = subdivide_path(pentagon(side=2),10);
+   hexagon  = subdivide_path(hexagon(side=2.7),10);
+   aligned =  [[2.7,0],[2.025,-1.16913429511],[1.35,-2.33826859022],
+               [-1.35,-2.33826859022],[-2.025,-1.16913429511],[-2.7,0],
+               [-2.025,1.16913429511],[-1.35,2.33826859022],[1.35,2.33826859022],
+               [2.025,1.16913429511]];
+   assert_approx(align_polygon(pentagon,hexagon,[0:10:359]), aligned);
+   aligned2 = [[1.37638192047,0],[1.37638192047,-1],[0.425325404176,-1.30901699437],
+               [-0.525731112119,-1.61803398875],[-1.11351636441,-0.809016994375],
+               [-1.7013016167,0],[-1.11351636441,0.809016994375],
+               [-0.525731112119,1.61803398875],[0.425325404176,1.30901699437],
+               [1.37638192047,1]];
+   assert_approx(align_polygon(hexagon,pentagon,[0:10:359]), aligned2);
+}
+*test_align_polygon();
+
+
+module test_noncollinear_triple() {
+    assert(noncollinear_triple([[1,1],[2,2],[3,3],[4,4],[4,5],[5,6]]) == [0,5,3]);
+    assert(noncollinear_triple([[1,1],[2,2],[8,3],[4,4],[4,5],[5,6]]) == [0,2,5]);
     u = unit([5,3]);
-    assert_equal(find_noncollinear_points([for(i = [2,3,4,5,7,12,15]) i * u], error=false),[]);
+    assert_equal(noncollinear_triple([for(i = [2,3,4,5,7,12,15]) i * u], error=false),[]);
 }
-test_find_noncollinear_points();
+*test_noncollinear_triple();
 
 
 module test_centroid() {
@@ -573,15 +675,18 @@ module test_centroid() {
     assert_approx(centroid(circle(d=100)), [0,0]);
     assert_approx(centroid(rect([40,60],rounding=10,anchor=LEFT)), [20,0]);
     assert_approx(centroid(rect([40,60],rounding=10,anchor=FWD)), [0,30]);
+    poly = [for(a=[0:90:360]) 
+              move([1,2.5,3.1], rot(p=[cos(a),sin(a),0],from=[0,0,1],to=[1,1,1])) ];
+    assert_approx(centroid(poly), [1,2.5,3.1]);
 }
-test_centroid();
+*test_centroid();
 
 
 module test_simplify_path() {
     path = [[-20,-20], [-10,-20], [0,-10], [10,0], [20,10], [20,20], [15,30]];
     assert(simplify_path(path) == [[-20,-20], [-10,-20], [20,10], [20,20], [15,30]]);
 }
-test_simplify_path();
+*test_simplify_path();
 
 
 module test_simplify_path_indexed() {
@@ -589,7 +694,7 @@ module test_simplify_path_indexed() {
     path = [4,6,1,0,3,2,5];
     assert(simplify_path_indexed(pts, path) == [4,6,3,2,5]);
 }
-test_simplify_path_indexed();
+*test_simplify_path_indexed();
 
 
 module test_point_in_polygon() {
@@ -605,7 +710,7 @@ module test_point_in_polygon() {
     assert(point_in_polygon([0,10], poly) == 0);
     assert(point_in_polygon([0,-10], poly) == 0);
 }
-test_point_in_polygon();
+*test_point_in_polygon();
 
 
 module test_pointlist_bounds() {
@@ -626,16 +731,16 @@ module test_pointlist_bounds() {
     ];
     assert(pointlist_bounds(pts2d) == [[-63,-42],[84,42]]);
     pts5d = [
-        [-53,27,12,-53,12],
-        [-63,97,36,-63,36],
-        [84,-32,-5,84,-5], 
-        [63,-24,42,63,42], 
-        [23,57,-42,23,-42]
+        [-53, 27, 12,-53, 12],
+        [-63, 97, 36,-63, 36],
+        [ 84,-32, -5, 84, -5], 
+        [ 63,-24, 42, 63, 42], 
+        [ 23, 57,-42, 23,-42]
     ];
     assert(pointlist_bounds(pts5d) == [[-63,-32,-42,-63,-42],[84,97,42,84,42]]);
     assert(pointlist_bounds([[3,4,5,6]]), [[3,4,5,6],[3,4,5,6]]);
 }
-test_pointlist_bounds();
+*test_pointlist_bounds();
 
 
 module test_closest_point() {
@@ -648,7 +753,7 @@ module test_closest_point() {
         assert(mindist == dists[pidx]);
     }
 }
-test_closest_point();
+*test_closest_point();
 
 
 module test_furthest_point() {
@@ -661,7 +766,7 @@ module test_furthest_point() {
         assert(mindist == dists[pidx]);
     }
 }
-test_furthest_point();
+*test_furthest_point();
 
 
 module test_polygon_is_clockwise() {
@@ -670,7 +775,7 @@ module test_polygon_is_clockwise() {
     assert(polygon_is_clockwise(circle(d=100)));
     assert(polygon_is_clockwise(square(100)));
 }
-test_polygon_is_clockwise();
+*test_polygon_is_clockwise();
 
 
 module test_clockwise_polygon() {
@@ -679,7 +784,7 @@ module test_clockwise_polygon() {
     assert(clockwise_polygon(path) == path);
     assert(clockwise_polygon(rpath) == path);
 }
-test_clockwise_polygon();
+*test_clockwise_polygon();
 
 
 module test_ccw_polygon() {
@@ -688,7 +793,7 @@ module test_ccw_polygon() {
     assert(ccw_polygon(path) == rpath);
     assert(ccw_polygon(rpath) == rpath);
 }
-test_ccw_polygon();
+*test_ccw_polygon();
 
 
 module test_reverse_polygon() {
@@ -697,7 +802,7 @@ module test_reverse_polygon() {
     assert(reverse_polygon(path) == rpath);
     assert(reverse_polygon(rpath) == path);
 }
-test_reverse_polygon();
+*test_reverse_polygon();
 
 
 module test_is_region() {
@@ -709,7 +814,7 @@ module test_is_region() {
     assert(!is_region(true));
     assert(!is_region("foo"));
 }
-test_is_region();
+*test_is_region();
 
 
 

From b4e26c035cdaa24cfe7c4067da07f72410bd427b Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Sun, 16 Aug 2020 23:34:31 +0100
Subject: [PATCH 02/18] Changes to noncollinear_triple

---
 hull.scad     |  6 +++---
 rounding.scad | 10 +++++-----
 vnf.scad      | 12 +++++++++++-
 3 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/hull.scad b/hull.scad
index da7d2c9..874e2c8 100644
--- a/hull.scad
+++ b/hull.scad
@@ -92,7 +92,7 @@ function hull2d_path(points) =
     assert(is_path(points,2),"Invalid input to hull2d_path")
     len(points) < 2 ? []
   : len(points) == 2 ? [0,1]
-  : let(tri=find_noncollinear_points(points, error=false))
+  : let(tri=noncollinear_triple(points, error=false))
     tri == [] ? _hull_collinear(points)
   : let(
         remaining = [ for (i = [0:1:len(points)-1]) if (i != tri[0] && i!=tri[1] && i!=tri[2]) i ],
@@ -170,7 +170,7 @@ function hull3d_faces(points) =
     assert(is_path(points,3),"Invalid input to hull3d_faces")
     len(points) < 3 ? list_range(len(points))
   : let ( // start with a single non-collinear triangle
-          tri = find_noncollinear_points(points, error=false)
+          tri = noncollinear_triple(points, error=false)
         )
     tri==[] ? _hull_collinear(points)
   : let(
@@ -250,7 +250,7 @@ function _find_conflicts(point, planes) = [
 
 
 function _find_first_noncoplanar(plane, points, i) = 
-    (i >= len(points) || !coplanar(plane, points[i]))? i :
+    (i >= len(points) || !points_on_plane([points[i]],plane))? i :
     _find_first_noncoplanar(plane, points, i+1);
 
 
diff --git a/rounding.scad b/rounding.scad
index 254d035..2dc2ea4 100644
--- a/rounding.scad
+++ b/rounding.scad
@@ -675,7 +675,7 @@ module offset_sweep(
                                 r = offset_type=="round"? this_offset : undef,
                                 do_chamfer = offset_type == "chamfer"
                         )
-                        assert(num_defined([r,delta])==1,"Must set `offset` to \"round\" or \"delta")
+                        assert(num_defined([r,delta])==1,str("Must set `offset` to ",round," or ",delta)
                         let(
                                 vertices_faces = offset(
                                         path, r=r, delta=delta, chamfer = do_chamfer, closed=true,
@@ -1532,7 +1532,7 @@ function rounded_prism(bottom, top, joint_bot, joint_top, joint_sides, k_bot, k_
      // Determine which points are concave by making bottom 2d if necessary
      bot_proj = len(bottom[0])==2 ? bottom :  project_plane(bottom, select(bottom,0,2)),
      bottom_sign = polygon_is_clockwise(bot_proj) ? 1 : -1,
-     concave = [for(i=[0:N-1]) bottom_sign*sign(point_left_of_segment2d(select(bot_proj,i+1), select(bot_proj, i-1,i)))>0],
+     concave = [for(i=[0:N-1]) bottom_sign*sign(point_left_of_line2d(select(bot_proj,i+1), select(bot_proj, i-1,i)))>0],
      top = is_undef(top) ? path3d(bottom,height/2) :
            len(top[0])==2 ? path3d(top,height/2) :
            top,
@@ -1547,11 +1547,11 @@ function rounded_prism(bottom, top, joint_bot, joint_top, joint_sides, k_bot, k_
    assert(jsvecok || jssingleok,
           str("Argument joint_sides is invalid.  All entries must be nonnegative, and it must be a number, 2-vector, or a length ",N," list those."))
    assert(is_num(k_sides) || is_vector(k_sides,N), str("Curvature parameter k_sides must be a number or length ",N," vector"))
-   assert(points_are_coplanar(bottom))
-   assert(points_are_coplanar(top))
+   assert(coplanar(bottom))
+   assert(coplanar(top))
    assert(!is_num(k_sides) || (k_sides>=0 && k_sides<=1), "Curvature parameter k_sides must be in interval [0,1]")
    let(
-     non_coplanar=[for(i=[0:N-1]) if (!points_are_coplanar(concat(select(top,i,i+1), select(bottom,i,i+1)))) [i,(i+1)%N]],
+     non_coplanar=[for(i=[0:N-1]) if (!coplanar(concat(select(top,i,i+1), select(bottom,i,i+1)))) [i,(i+1)%N]],
      k_sides_vec = is_num(k_sides) ? repeat(k_sides, N) : k_sides,
      kbad = [for(i=[0:N-1]) if (k_sides_vec[i]<0 || k_sides_vec[i]>1) i],
      joint_sides_vec = jssingleok ? repeat(joint_sides,N) : joint_sides,
diff --git a/vnf.scad b/vnf.scad
index b72902e..05fd82f 100644
--- a/vnf.scad
+++ b/vnf.scad
@@ -403,6 +403,16 @@ function _triangulate_planar_convex_polygons(polys) =
         outtris = concat(tris, newtris, newtris2)
     ) outtris;
 
+//**
+// this function may produce degenerate triangles:
+//    _triangulate_planar_convex_polygons([ [for(i=[0:1]) [i,i],
+//                                           [1,-1], [-1,-1],
+//                                           for(i=[-1:0]) [i,i] ] ] )
+//    == [[[-1, -1], [ 0,  0], [0,  0]]
+//        [[-1, -1], [-1, -1], [0,  0]]
+//        [[ 1, -1], [-1, -1], [0,  0]]
+//        [[ 0,  0], [ 1,  1], [1, -1]] ]
+//
 
 // Function: vnf_bend()
 // Usage:
@@ -647,7 +657,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
         nonplanars = unique([
             for (face = faces) let(
                 faceverts = [for (k=face) varr[k]]
-            ) if (!points_are_coplanar(faceverts)) [
+            ) if (!coplanar(faceverts)) [
                 "ERROR",
                 "NONPLANAR",
                 "Face vertices are not coplanar",

From 143ba0646719baf49b7a9e4719738b8575d472fb Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Sun, 16 Aug 2020 23:38:17 +0100
Subject: [PATCH 03/18] Minor edits

---
 math.scad | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/math.scad b/math.scad
index 158b1f2..888d048 100644
--- a/math.scad
+++ b/math.scad
@@ -675,7 +675,7 @@ function convolve(p,q) =
 // Usage: linear_solve(A,b)
 // Description:
 //   Solves the linear system Ax=b.  If A is square and non-singular the unique solution is returned.  If A is overdetermined
-//   the least squares solution is returned.  If A is underdetermined, the minimal norm solution is returned.
+//   the least squares solution is returned. If A is underdetermined, the minimal norm solution is returned.
 //   If A is rank deficient or singular then linear_solve returns [].  If b is a matrix that is compatible with A
 //   then the problem is solved for the matrix valued right hand side and a matrix is returned.  Note that if you 
 //   want to solve Ax=b1 and Ax=b2 that you need to form the matrix transpose([b1,b2]) for the right hand side and then
@@ -686,7 +686,7 @@ function linear_solve(A,b) =
         m = len(A),
         n = len(A[0])
     )
-    assert(is_vector(b,m) || is_matrix(b,m),"Incompatible matrix and right hand side")
+    assert(is_vector(b,m) || is_matrix(b,m),"Invalid right hand side or incompatible with the matrix")
     let (
         qr = m<n? qr_factor(transpose(A)) : qr_factor(A),
         maxdim = max(n,m),
@@ -727,7 +727,7 @@ function qr_factor(A) =
       n = len(A[0])
     )
     let(
-        qr =_qr_factor(A, Q=ident(m), column=0, m = m, n=n),
+        qr = _qr_factor(A, Q=ident(m), column=0, m = m, n=n),
         Rzero = 
           let( R = qr[1] )
           [ for(i=[0:m-1]) [
@@ -745,7 +745,13 @@ function _qr_factor(A,Q, column, m, n) =
         u = x - concat([alpha],repeat(0,m-1)),
         v = alpha==0 ? u : u / norm(u),
         Qc = ident(len(x)) - 2*outer_product(v,v),
-        Qf = [for(i=[0:m-1]) [for(j=[0:m-1]) i<column || j<column ? (i==j ? 1 : 0) : Qc[i-column][j-column]]]
+        Qf = [for(i=[0:m-1]) 
+                [for(j=[0:m-1]) 
+                    i<column || j<column 
+                    ? (i==j ? 1 : 0) 
+                    : Qc[i-column][j-column]
+                ]
+             ]
     )
     _qr_factor(Qf*A, Q*Qf, column+1, m, n);
 

From bf44fe5b039eedf7f68655f4b5f6bde67ca3bc2a Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Sun, 16 Aug 2020 23:39:11 +0100
Subject: [PATCH 04/18] Minor edits

---
 common.scad | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/common.scad b/common.scad
index eebd141..4545886 100644
--- a/common.scad
+++ b/common.scad
@@ -289,12 +289,15 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 //   d = Most general diameter.
 //   dflt = Value to return if all other values given are `undef`.
 function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = (
-    !is_undef(r1)? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") r1 :
-    !is_undef(r2)? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") r2 :
-    !is_undef(d1)? d1/2 :
-    !is_undef(d2)? d2/2 :
-    !is_undef(r)? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.") r :
-    !is_undef(d)? d/2 :
+    !is_undef(r1)? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
+		               assert(is_finite(r1), "Invalid radius r1." ) r1 :
+    !is_undef(r2)? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.")
+                   assert(is_finite(r2), "Invalid radius r2." ) r2 :
+    !is_undef(d1)? assert(is_finite(d1), "Invalid diameter d1." ) d1/2 :
+    !is_undef(d2)? assert(is_finite(d2), "Invalid diameter d2." ) d2/2 :
+    !is_undef(r)?  assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.") 
+		               r ://assert(is_finite(r), "Invalid radius r." ) r : // this assert causes an error in shapes
+    !is_undef(d)?  assert(is_finite(d), "Invalid diameter d." ) d/2 :
     dflt
 );
 

From 288f203bb634f0dc9ca00a445ba8522fcbaf6047 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Sun, 16 Aug 2020 23:42:59 +0100
Subject: [PATCH 05/18] Revert "Minor edits"

This reverts commit bf44fe5b039eedf7f68655f4b5f6bde67ca3bc2a.
---
 common.scad | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/common.scad b/common.scad
index 4545886..eebd141 100644
--- a/common.scad
+++ b/common.scad
@@ -289,15 +289,12 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 //   d = Most general diameter.
 //   dflt = Value to return if all other values given are `undef`.
 function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = (
-    !is_undef(r1)? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
-		               assert(is_finite(r1), "Invalid radius r1." ) r1 :
-    !is_undef(r2)? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.")
-                   assert(is_finite(r2), "Invalid radius r2." ) r2 :
-    !is_undef(d1)? assert(is_finite(d1), "Invalid diameter d1." ) d1/2 :
-    !is_undef(d2)? assert(is_finite(d2), "Invalid diameter d2." ) d2/2 :
-    !is_undef(r)?  assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.") 
-		               r ://assert(is_finite(r), "Invalid radius r." ) r : // this assert causes an error in shapes
-    !is_undef(d)?  assert(is_finite(d), "Invalid diameter d." ) d/2 :
+    !is_undef(r1)? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") r1 :
+    !is_undef(r2)? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") r2 :
+    !is_undef(d1)? d1/2 :
+    !is_undef(d2)? d2/2 :
+    !is_undef(r)? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.") r :
+    !is_undef(d)? d/2 :
     dflt
 );
 

From 2efd0ca5d01d92d7f7de6e1bac36b82be17e7605 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Sun, 16 Aug 2020 23:54:00 +0100
Subject: [PATCH 06/18] Function name change

plane_projection >> projection_on_plane
---
 geometry.scad            | 10 +++++-----
 tests/test_geometry.scad | 24 ++++++++++++------------
 2 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/geometry.scad b/geometry.scad
index b3f5ee6..eff67bc 100644
--- a/geometry.scad
+++ b/geometry.scad
@@ -825,7 +825,7 @@ function plane3pt(p1, p2, p3) =
 //   i3 = The index into `points` of the third point on the plane.
 function plane3pt_indexed(points, i1, i2, i3) =
     assert( is_vector([i1,i2,i3]) && min(i1,i2,i3)>=0 && is_list(points) && max(i1,i2,i3)<len(points),
-		        "Invalid or out of range indices." )
+            "Invalid or out of range indices." )
     assert( is_path([points[i1], points[i2], points[i3]],dim=3),
             "Improper points or improper dimensions." )
     let(
@@ -965,9 +965,9 @@ function plane_transform(plane) =
     rot(from=n, to=UP) * move(-cp);
 
 
-// Function: plane_projection()
+// Function: projection_on_plane()
 // Usage:
-//   plane_projection(points);
+//   projection_on_plane(points);
 // Description:
 //   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the projection
 //   of the points on the plane.
@@ -977,8 +977,8 @@ function plane_transform(plane) =
 // Example(3D):
 //   points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100))));
 //   plane = plane3pt([1,0,0],[0,1,0],[0,0,1]);
-//   proj = plane_projection(plane,points);
-function plane_projection(plane, points) =
+//   proj = projection_on_plane(plane,points);
+function projection_on_plane(plane, points) =
     assert( _valid_plane(plane), "Invalid plane." )
     assert( is_path(points), "Invalid list of points or dimension." )
     let( 
diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad
index da01a97..ceeb905 100644
--- a/tests/test_geometry.scad
+++ b/tests/test_geometry.scad
@@ -41,7 +41,7 @@ test_plane_from_points();
 test_plane_normal();
 //test_plane_offset();
 //test_plane_transform();
-test_plane_projection();
+test_projection_on_plane();
 //test_plane_point_nearest_origin();
 test_distance_from_plane();
 
@@ -105,13 +105,13 @@ module test_points_on_plane() {
     ang     = rands(0,360,1)[0];
     normal  = rot(a=ang,p=normal0);
     plane   = [each normal, normal*dir];
-    prj_pts = plane_projection(plane,pts);
+    prj_pts = projection_on_plane(plane,pts);
     assert(points_on_plane(prj_pts,plane));
     assert(!points_on_plane(concat(pts,[normal-dir]),plane));
 }
 *test_points_on_plane();
 
-module test_plane_projection(){
+module test_projection_on_plane(){
     ang     = rands(0,360,1)[0];
     dir     = rands(-10,10,3);
     normal0 = unit([1,2,3]);
@@ -120,16 +120,16 @@ module test_plane_projection(){
     plane   = [each normal,  0];
     planem  = [each normal, normal*dir];
     pts     = [for(i=[1:10]) rands(-1,1,3)];
-    assert_approx( plane_projection(plane,pts),
-                   plane_projection(plane,plane_projection(plane,pts)));
-    assert_approx( plane_projection(plane,pts),
-                   rot(a=ang,p=plane_projection(plane0,rot(a=-ang,p=pts))));    
-    assert_approx( move((-normal*dir)*normal,p=plane_projection(planem,pts)),
-                   plane_projection(plane,pts));
-    assert_approx( move((normal*dir)*normal,p=plane_projection(plane,pts)),
-                   plane_projection(planem,pts));
+    assert_approx( projection_on_plane(plane,pts),
+                   projection_on_plane(plane,projection_on_plane(plane,pts)));
+    assert_approx( projection_on_plane(plane,pts),
+                   rot(a=ang,p=projection_on_plane(plane0,rot(a=-ang,p=pts))));    
+    assert_approx( move((-normal*dir)*normal,p=projection_on_plane(planem,pts)),
+                   projection_on_plane(plane,pts));
+    assert_approx( move((normal*dir)*normal,p=projection_on_plane(plane,pts)),
+                   projection_on_plane(planem,pts));
 }
-*test_plane_projection();
+*test_projection_on_plane();
 
 module test_line_from_points() {
     assert_approx(line_from_points([[1,0],[0,0],[-1,0]]),[[-1,0],[1,0]]);

From 2b12659d009019c340e183f052d148e679797ecc Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Mon, 17 Aug 2020 00:18:33 +0100
Subject: [PATCH 07/18] refine arg validation of get_radius

It is assumed that args r and d in get_radius can be a finite number or a vector with dimension 1 or 2.
---
 common.scad | 29 ++++++++++++++++++++++-------
 1 file changed, 22 insertions(+), 7 deletions(-)

diff --git a/common.scad b/common.scad
index eebd141..51d8363 100644
--- a/common.scad
+++ b/common.scad
@@ -289,13 +289,28 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 //   d = Most general diameter.
 //   dflt = Value to return if all other values given are `undef`.
 function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = (
-    !is_undef(r1)? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") r1 :
-    !is_undef(r2)? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") r2 :
-    !is_undef(d1)? d1/2 :
-    !is_undef(d2)? d2/2 :
-    !is_undef(r)? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.") r :
-    !is_undef(d)? d/2 :
-    dflt
+    !is_undef(r1)
+		? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
+				assert(is_finite(r1), "Invalid radius r1." )
+				r1 
+    : !is_undef(r2)
+			? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
+				assert(is_finite(r2), "Invalid radius r2." )
+				r2
+		: !is_undef(d1)
+		  ? assert(is_finite(d1), "Invalid diameter d1." )
+				d1/2
+		: !is_undef(d2)
+			? assert(is_finite(d2), "Invalid diameter d2." )
+				d2/2
+		: !is_undef(r)
+			? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
+				assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
+				r 
+		: !is_undef(d)
+			? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." )
+			d/2
+		: dflt
 );
 
 // Function: get_height()

From f4a8138b37be3ddafdc14434b7724b344158c3c8 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Tue, 18 Aug 2020 11:01:42 +0100
Subject: [PATCH 08/18] Revert "Merge remote-tracking branch
 'upstream/revarbat_dev'"

This reverts commit ee80b1d08f2e26b2350e2cf9512e4729ad680a73, reversing
changes made to 2b12659d009019c340e183f052d148e679797ecc.
---
 attachments.scad | 48 ++++++++++++++++-------------------
 rounding.scad    |  2 +-
 shapes.scad      |  4 +--
 skin.scad        | 65 +++++++++++-------------------------------------
 version.scad     |  2 +-
 5 files changed, 41 insertions(+), 80 deletions(-)

diff --git a/attachments.scad b/attachments.scad
index c2c969e..6004e29 100644
--- a/attachments.scad
+++ b/attachments.scad
@@ -458,9 +458,10 @@ function find_anchor(anchor, geom) =
             eps = 1/2048,
             points = vnf[0],
             faces = vnf[1],
-            rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), points),
+            rpts = rot(from=anchor, to=RIGHT, p=move(point3d(-cp), p=points)),
             hits = [
-                for (face = faces) let(
+                for (i = idx(faces)) let(
+                    face = faces[i],
                     verts = select(rpts, face)
                 ) if (
                     max(subindex(verts,0)) >= -eps &&
@@ -469,40 +470,35 @@ function find_anchor(anchor, geom) =
                     min(subindex(verts,1)) <=  eps &&
                     min(subindex(verts,2)) <=  eps
                 ) let(
-                    poly = select(points, face),
-                    pt = polygon_line_intersection(poly, [cp,cp+anchor], bounded=[true,false], eps=eps)
-                ) if (!is_undef(pt)) let(
-                    plane = plane_from_polygon(poly),
-                    n = unit(plane_normal(plane))
-                )
-                [norm(pt-cp), n, pt]
+                    pt = polygon_line_intersection(
+                        select(points, face),
+                        [CENTER,anchor], eps=eps
+                    )
+                ) if (!is_undef(pt)) [norm(pt), i, pt]
             ]
         )
         assert(len(hits)>0, "Anchor vector does not intersect with the shape.  Attachment failed.")
         let(
             furthest = max_index(subindex(hits,0)),
+            pos = point3d(cp) + hits[furthest][2],
             dist = hits[furthest][0],
-            pos = hits[furthest][2],
-            hitnorms = [for (hit = hits) if (approx(hit[0],dist,eps=eps)) hit[1]],
-            unorms = len(hitnorms) > 7
-              ? unique([for (nn = hitnorms) quant(nn,1e-9)])
-              : [
-                    for (i = idx(hitnorms)) let(
-                        nn = hitnorms[i],
-                        isdup = [
-                            for (j = [i+1:1:len(hitnorms)-1])
-                            if (approx(nn, hitnorms[j])) 1
-                        ] != []
-                    ) if (!isdup) nn
-                ],
-            n = unit(sum(unorms)),
-            oang = approx(point2d(n), [0,0])? 0 : atan2(n.y, n.x) + 90
+            nfaces = [for (hit = hits) if(approx(hit[0],dist,eps=eps)) hit[1]],
+            n = unit(
+                sum([
+                    for (i = nfaces) let(
+                        faceverts = select(points, faces[i]),
+                        faceplane = plane_from_points(faceverts),
+                        nrm = plane_normal(faceplane)
+                    ) nrm
+                ]) / len(nfaces),
+                UP
+            )
         )
         [anchor, pos, n, oang]
     ) : type == "vnf_extent"? ( //vnf
         let(
             vnf=geom[1],
-            rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
+            rpts = rot(from=anchor, to=RIGHT, p=move(point3d(-cp), p=vnf[0])),
             maxx = max(subindex(rpts,0)),
             idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
             mm = pointlist_bounds(select(rpts,idxs)),
@@ -853,7 +849,7 @@ module attachable(
 
 // Module: position()
 // Usage:
-//   position(from) ...
+//   position(from, [overlap]) ...
 // Description:
 //   Attaches children to a parent object at an anchor point.
 // Arguments:
diff --git a/rounding.scad b/rounding.scad
index 94b8143..2dc2ea4 100644
--- a/rounding.scad
+++ b/rounding.scad
@@ -1962,4 +1962,4 @@ module bent_cutout_mask(r, thickness, path, convexity=10)
 }
 
 
-// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
\ No newline at end of file
+// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/shapes.scad b/shapes.scad
index d612b0a..cb6d4e5 100644
--- a/shapes.scad
+++ b/shapes.scad
@@ -102,8 +102,8 @@ module cuboid(
                 if (edges == EDGES_ALL && trimcorners) {
                     if (chamfer<0) {
                         cube(size, center=true) {
-                            attach(TOP,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
-                            attach(BOT,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
+                            attach(TOP) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
+                            attach(BOT) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
                         }
                     } else {
                         isize = [for (v = size) max(0.001, v-2*chamfer)];
diff --git a/skin.scad b/skin.scad
index 87ae8d4..30d8a17 100644
--- a/skin.scad
+++ b/skin.scad
@@ -16,8 +16,7 @@ include <vnf.scad>
 
 // Function&Module: skin()
 // Usage: As module:
-//   skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z], [convexity],
-//        [anchor],[cp],[spin],[orient],[extent]);
+//   skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z]);
 // Usage: As function:
 //   vnf = skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z]);
 // Description:
@@ -118,12 +117,6 @@ include <vnf.scad>
 //   caps = true to create endcap faces when closed is false.  Can be a length 2 boolean array.  Default is true if closed is false.
 //   method = method for connecting profiles, one of "distance", "tangent", "direct" or "reindex".  Default: "direct".
 //   z = array of height values for each profile if the profiles are 2d
-//   convexity = convexity setting for use with polyhedron.  (module only) Default: 10
-//   anchor = Translate so anchor point is at the origin.  (module only) Default: "origin"
-//   spin = Rotate this many degrees around Z axis after anchor.  (module only) Default: 0
-//   orient = Vector to rotate top towards after spin  (module only)
-//   extent = use extent method for computing anchors. (module only)  Default: false
-//   cp = set centerpoint for anchor computation.  (module only) Default: object centroid
 // Example:
 //   skin([octagon(4), circle($fn=70,r=2)], z=[0,3], slices=10);
 // Example: Rotating the pentagon place the zero index at different locations, giving a twist
@@ -322,15 +315,11 @@ include <vnf.scad>
 //              stroke(zrot(30, p=yscale(0.5, p=circle(d=120))),width=10,closed=true);
 //          }
 //      }
-module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, convexity=10,
-            anchor="origin",cp,spin=0, orient=UP, extent=false)
+
+
+module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, convexity=10)
 {
-    vnf = skin(profiles, slices, refine, method, sampling, caps, closed, z);
-    attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
-    {      
-        vnf_polyhedron(vnf,convexity=convexity);
-        children();
-    }
+      vnf_polyhedron(skin(profiles, slices, refine, method, sampling, caps, closed, z), convexity=convexity);
 }        
 
 
@@ -814,12 +803,6 @@ function associate_vertices(polygons, split, curpoly=0) =
 //   transformations = list of 4x4 matrices to apply
 //   closed = set to true to form a closed (torus) model.  Default: false
 //   caps = true to create endcap faces when closed is false.  Can be a singe boolean to specify endcaps at both ends, or a length 2 boolean array.  Default is true if closed is false.
-//   convexity = convexity setting for use with polyhedron.  (module only) Default: 10
-//   anchor = Translate so anchor point is at the origin.  (module only) Default: "origin"
-//   spin = Rotate this many degrees around Z axis after anchor.  (module only) Default: 0
-//   orient = Vector to rotate top towards after spin  (module only)
-//   extent = use extent method for computing anchors. (module only)  Default: false
-//   cp = set centerpoint for anchor computation.  (module only) Default: object centroid
 // Example: This is the "sweep-drop" example from list-comprehension-demos.
 //   function drop(t) = 100 * 0.5 * (1 - cos(180 * t)) * sin(180 * t) + 1;
 //   function path(t) = [0, 0, 80 + 80 * cos(180 * t)];
@@ -856,16 +839,9 @@ function sweep(shape, transformations, closed=false, caps) =
   assert(!closed || !caps, "Cannot make closed shape with caps")
   _skin_core([for(i=[0:len(transformations)-(closed?0:1)]) apply(transformations[i%len(transformations)],path3d(shape))],caps=fullcaps);
 
-module sweep(shape, transformations, closed=false, caps, convexity=10,
-             anchor="origin",cp,spin=0, orient=UP, extent=false)
-{
-    vnf = sweep(shape, transformations, closed, caps);
-    attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
-    {      
-        vnf_polyhedron(vnf,convexity=convexity);
-        children();
-    }
-}        
+module sweep(shape, transformations, closed=false, caps, convexity=10) {
+  vnf_polyhedron(sweep(shape, transformations, closed, caps), convexity=convexity);
+}  
 
 
 // Function&Module: path_sweep()
@@ -930,13 +906,8 @@ module sweep(shape, transformations, closed=false, caps, convexity=10,
 //   tangent = a list of tangent vectors in case you need more accuracy (particularly at the end points of your curve)
 //   relaxed = set to true with the "manual" method to relax the orthogonality requirement of cross sections to the path tangent.  Default: false
 //   caps = Can be a boolean or vector of two booleans.  Set to false to disable caps at the two ends.  Default: true
-//   transforms = set to true to return transforms instead of a VNF.  These transforms can be manipulated and passed to sweep().  Default: false.
 //   convexity = convexity parameter for polyhedron().  Only accepted by the module version.  Default: 10
-//   anchor = Translate so anchor point is at the origin.  (module only) Default: "origin"
-//   spin = Rotate this many degrees around Z axis after anchor.  (module only) Default: 0
-//   orient = Vector to rotate top towards after spin  (module only)
-//   extent = use extent method for computing anchors. (module only)  Default: false
-//   cp = set centerpoint for anchor computation.  (module only) Default: object centroid
+//   transforms = set to true to return transforms instead of a VNF.  These transforms can be manipulated and passed to sweep().  Default: false. 
 //
 // Example(2D): We'll use this shape in several examples
 //   ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[  7, 2],[  7, 7],[ 10, 7],[ 10, 0]];
@@ -1150,19 +1121,13 @@ module sweep(shape, transformations, closed=false, caps, convexity=10,
 //   outside = [for(i=[0:len(trans)-1]) trans[i]*scale(lerp(1,1.5,i/(len(trans)-1)))];
 //   inside = [for(i=[len(trans)-1:-1:0]) trans[i]*scale(lerp(1.1,1.4,i/(len(trans)-1)))];
 //   sweep(shape, concat(outside,inside),closed=true);
-module path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
-                    symmetry=1, last_normal, tangent, relaxed=false, caps, convexity=10,
-                    anchor="origin",cp,spin=0, orient=UP, extent=false)
-{
-    vnf = path_sweep(shape, path, method, normal, closed, twist, twist_by_length,
-                    symmetry, last_normal, tangent, relaxed, caps);
-    attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
-    {      
-        vnf_polyhedron(vnf,convexity=convexity);
-        children();
-    }
-}        
 
+module path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
+                    symmetry=1, last_normal, tangent, relaxed=false, caps, convexity=10)
+{
+  vnf_polyhedron(path_sweep(shape, path, method, normal, closed, twist, twist_by_length,
+                    symmetry, last_normal, tangent, relaxed, caps), convexity=convexity);
+}
 
 function path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
                     symmetry=1, last_normal, tangent, relaxed=false, caps, transforms=false) = 
diff --git a/version.scad b/version.scad
index 1420f58..e6ba60b 100644
--- a/version.scad
+++ b/version.scad
@@ -8,7 +8,7 @@
 //////////////////////////////////////////////////////////////////////
 
 
-BOSL_VERSION = [2,0,405];
+BOSL_VERSION = [2,0,402];
 
 
 // Section: BOSL Library Version Functions

From 5fab080f8e8d6ddee90a6c584a21cfffaff307f5 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Tue, 18 Aug 2020 11:04:56 +0100
Subject: [PATCH 09/18] Revert "Revert "Merge remote-tracking branch
 'upstream/revarbat_dev'""

This reverts commit f4a8138b37be3ddafdc14434b7724b344158c3c8.
---
 attachments.scad | 48 +++++++++++++++++++-----------------
 rounding.scad    |  2 +-
 shapes.scad      |  4 +--
 skin.scad        | 63 +++++++++++++++++++++++++++++++++++++-----------
 version.scad     |  2 +-
 5 files changed, 79 insertions(+), 40 deletions(-)

diff --git a/attachments.scad b/attachments.scad
index 6004e29..c2c969e 100644
--- a/attachments.scad
+++ b/attachments.scad
@@ -458,10 +458,9 @@ function find_anchor(anchor, geom) =
             eps = 1/2048,
             points = vnf[0],
             faces = vnf[1],
-            rpts = rot(from=anchor, to=RIGHT, p=move(point3d(-cp), p=points)),
+            rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), points),
             hits = [
-                for (i = idx(faces)) let(
-                    face = faces[i],
+                for (face = faces) let(
                     verts = select(rpts, face)
                 ) if (
                     max(subindex(verts,0)) >= -eps &&
@@ -470,35 +469,40 @@ function find_anchor(anchor, geom) =
                     min(subindex(verts,1)) <=  eps &&
                     min(subindex(verts,2)) <=  eps
                 ) let(
-                    pt = polygon_line_intersection(
-                        select(points, face),
-                        [CENTER,anchor], eps=eps
-                    )
-                ) if (!is_undef(pt)) [norm(pt), i, pt]
+                    poly = select(points, face),
+                    pt = polygon_line_intersection(poly, [cp,cp+anchor], bounded=[true,false], eps=eps)
+                ) if (!is_undef(pt)) let(
+                    plane = plane_from_polygon(poly),
+                    n = unit(plane_normal(plane))
+                )
+                [norm(pt-cp), n, pt]
             ]
         )
         assert(len(hits)>0, "Anchor vector does not intersect with the shape.  Attachment failed.")
         let(
             furthest = max_index(subindex(hits,0)),
-            pos = point3d(cp) + hits[furthest][2],
             dist = hits[furthest][0],
-            nfaces = [for (hit = hits) if(approx(hit[0],dist,eps=eps)) hit[1]],
-            n = unit(
-                sum([
-                    for (i = nfaces) let(
-                        faceverts = select(points, faces[i]),
-                        faceplane = plane_from_points(faceverts),
-                        nrm = plane_normal(faceplane)
-                    ) nrm
-                ]) / len(nfaces),
-                UP
-            )
+            pos = hits[furthest][2],
+            hitnorms = [for (hit = hits) if (approx(hit[0],dist,eps=eps)) hit[1]],
+            unorms = len(hitnorms) > 7
+              ? unique([for (nn = hitnorms) quant(nn,1e-9)])
+              : [
+                    for (i = idx(hitnorms)) let(
+                        nn = hitnorms[i],
+                        isdup = [
+                            for (j = [i+1:1:len(hitnorms)-1])
+                            if (approx(nn, hitnorms[j])) 1
+                        ] != []
+                    ) if (!isdup) nn
+                ],
+            n = unit(sum(unorms)),
+            oang = approx(point2d(n), [0,0])? 0 : atan2(n.y, n.x) + 90
         )
         [anchor, pos, n, oang]
     ) : type == "vnf_extent"? ( //vnf
         let(
             vnf=geom[1],
-            rpts = rot(from=anchor, to=RIGHT, p=move(point3d(-cp), p=vnf[0])),
+            rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
             maxx = max(subindex(rpts,0)),
             idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
             mm = pointlist_bounds(select(rpts,idxs)),
@@ -849,7 +853,7 @@ module attachable(
 
 // Module: position()
 // Usage:
-//   position(from, [overlap]) ...
+//   position(from) ...
 // Description:
 //   Attaches children to a parent object at an anchor point.
 // Arguments:
diff --git a/rounding.scad b/rounding.scad
index 2dc2ea4..94b8143 100644
--- a/rounding.scad
+++ b/rounding.scad
@@ -1962,4 +1962,4 @@ module bent_cutout_mask(r, thickness, path, convexity=10)
 }
 
 
-// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
+// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
\ No newline at end of file
diff --git a/shapes.scad b/shapes.scad
index cb6d4e5..d612b0a 100644
--- a/shapes.scad
+++ b/shapes.scad
@@ -102,8 +102,8 @@ module cuboid(
                 if (edges == EDGES_ALL && trimcorners) {
                     if (chamfer<0) {
                         cube(size, center=true) {
-                            attach(TOP) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
-                            attach(BOT) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
+                            attach(TOP,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
+                            attach(BOT,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
                         }
                     } else {
                         isize = [for (v = size) max(0.001, v-2*chamfer)];
diff --git a/skin.scad b/skin.scad
index 30d8a17..87ae8d4 100644
--- a/skin.scad
+++ b/skin.scad
@@ -16,7 +16,8 @@ include <vnf.scad>
 
 // Function&Module: skin()
 // Usage: As module:
-//   skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z]);
+//   skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z], [convexity],
+//        [anchor],[cp],[spin],[orient],[extent]);
 // Usage: As function:
 //   vnf = skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z]);
 // Description:
@@ -117,6 +118,12 @@ include <vnf.scad>
 //   caps = true to create endcap faces when closed is false.  Can be a length 2 boolean array.  Default is true if closed is false.
 //   method = method for connecting profiles, one of "distance", "tangent", "direct" or "reindex".  Default: "direct".
 //   z = array of height values for each profile if the profiles are 2d
+//   convexity = convexity setting for use with polyhedron.  (module only) Default: 10
+//   anchor = Translate so anchor point is at the origin.  (module only) Default: "origin"
+//   spin = Rotate this many degrees around Z axis after anchor.  (module only) Default: 0
+//   orient = Vector to rotate top towards after spin  (module only)
+//   extent = use extent method for computing anchors. (module only)  Default: false
+//   cp = set centerpoint for anchor computation.  (module only) Default: object centroid
 // Example:
 //   skin([octagon(4), circle($fn=70,r=2)], z=[0,3], slices=10);
 // Example: Rotating the pentagon place the zero index at different locations, giving a twist
@@ -315,11 +322,15 @@ include <vnf.scad>
 //              stroke(zrot(30, p=yscale(0.5, p=circle(d=120))),width=10,closed=true);
 //          }
 //      }
-
-
-module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, convexity=10)
+module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, convexity=10,
+            anchor="origin",cp,spin=0, orient=UP, extent=false)
 {
-      vnf_polyhedron(skin(profiles, slices, refine, method, sampling, caps, closed, z), convexity=convexity);
+    vnf = skin(profiles, slices, refine, method, sampling, caps, closed, z);
+    attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
+    {      
+        vnf_polyhedron(vnf,convexity=convexity);
+        children();
+    }
 }        
 
 
@@ -803,6 +814,12 @@ function associate_vertices(polygons, split, curpoly=0) =
 //   transformations = list of 4x4 matrices to apply
 //   closed = set to true to form a closed (torus) model.  Default: false
 //   caps = true to create endcap faces when closed is false.  Can be a singe boolean to specify endcaps at both ends, or a length 2 boolean array.  Default is true if closed is false.
+//   convexity = convexity setting for use with polyhedron.  (module only) Default: 10
+//   anchor = Translate so anchor point is at the origin.  (module only) Default: "origin"
+//   spin = Rotate this many degrees around Z axis after anchor.  (module only) Default: 0
+//   orient = Vector to rotate top towards after spin  (module only)
+//   extent = use extent method for computing anchors. (module only)  Default: false
+//   cp = set centerpoint for anchor computation.  (module only) Default: object centroid
 // Example: This is the "sweep-drop" example from list-comprehension-demos.
 //   function drop(t) = 100 * 0.5 * (1 - cos(180 * t)) * sin(180 * t) + 1;
 //   function path(t) = [0, 0, 80 + 80 * cos(180 * t)];
@@ -839,9 +856,16 @@ function sweep(shape, transformations, closed=false, caps) =
   assert(!closed || !caps, "Cannot make closed shape with caps")
   _skin_core([for(i=[0:len(transformations)-(closed?0:1)]) apply(transformations[i%len(transformations)],path3d(shape))],caps=fullcaps);
 
-module sweep(shape, transformations, closed=false, caps, convexity=10) {
-  vnf_polyhedron(sweep(shape, transformations, closed, caps), convexity=convexity);
-}  
+module sweep(shape, transformations, closed=false, caps, convexity=10,
+             anchor="origin",cp,spin=0, orient=UP, extent=false)
+{
+    vnf = sweep(shape, transformations, closed, caps);
+    attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
+    {      
+        vnf_polyhedron(vnf,convexity=convexity);
+        children();
+    }
+}        
 
 
 // Function&Module: path_sweep()
@@ -906,8 +930,13 @@ module sweep(shape, transformations, closed=false, caps, convexity=10) {
 //   tangent = a list of tangent vectors in case you need more accuracy (particularly at the end points of your curve)
 //   relaxed = set to true with the "manual" method to relax the orthogonality requirement of cross sections to the path tangent.  Default: false
 //   caps = Can be a boolean or vector of two booleans.  Set to false to disable caps at the two ends.  Default: true
+//   transforms = set to true to return transforms instead of a VNF.  These transforms can be manipulated and passed to sweep().  Default: false.
 //   convexity = convexity parameter for polyhedron().  Only accepted by the module version.  Default: 10
-//   transforms = set to true to return transforms instead of a VNF.  These transforms can be manipulated and passed to sweep().  Default: false. 
+//   anchor = Translate so anchor point is at the origin.  (module only) Default: "origin"
+//   spin = Rotate this many degrees around Z axis after anchor.  (module only) Default: 0
+//   orient = Vector to rotate top towards after spin  (module only)
+//   extent = use extent method for computing anchors. (module only)  Default: false
+//   cp = set centerpoint for anchor computation.  (module only) Default: object centroid
 //
 // Example(2D): We'll use this shape in several examples
 //   ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[  7, 2],[  7, 7],[ 10, 7],[ 10, 0]];
@@ -1121,13 +1150,19 @@ module sweep(shape, transformations, closed=false, caps, convexity=10) {
 //   outside = [for(i=[0:len(trans)-1]) trans[i]*scale(lerp(1,1.5,i/(len(trans)-1)))];
 //   inside = [for(i=[len(trans)-1:-1:0]) trans[i]*scale(lerp(1.1,1.4,i/(len(trans)-1)))];
 //   sweep(shape, concat(outside,inside),closed=true);
-
 module path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
-                    symmetry=1, last_normal, tangent, relaxed=false, caps, convexity=10)
+                    symmetry=1, last_normal, tangent, relaxed=false, caps, convexity=10,
+                    anchor="origin",cp,spin=0, orient=UP, extent=false)
 {
-  vnf_polyhedron(path_sweep(shape, path, method, normal, closed, twist, twist_by_length,
-                    symmetry, last_normal, tangent, relaxed, caps), convexity=convexity);
-}
+    vnf = path_sweep(shape, path, method, normal, closed, twist, twist_by_length,
+                    symmetry, last_normal, tangent, relaxed, caps);
+    attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
+    {      
+        vnf_polyhedron(vnf,convexity=convexity);
+        children();
+    }
+}        
+
 
 function path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
                     symmetry=1, last_normal, tangent, relaxed=false, caps, transforms=false) = 
diff --git a/version.scad b/version.scad
index e6ba60b..1420f58 100644
--- a/version.scad
+++ b/version.scad
@@ -8,7 +8,7 @@
 //////////////////////////////////////////////////////////////////////
 
 
-BOSL_VERSION = [2,0,402];
+BOSL_VERSION = [2,0,405];
 
 
 // Section: BOSL Library Version Functions

From 29672105eda4a73218d91ba7c7cfc49776dd1b31 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Tue, 18 Aug 2020 23:27:38 +0100
Subject: [PATCH 10/18] Update geometry.scad

Removed all double definitions
---
 geometry.scad | 161 +-------------------------------------------------
 1 file changed, 1 insertion(+), 160 deletions(-)

diff --git a/geometry.scad b/geometry.scad
index eff67bc..7d02252 100644
--- a/geometry.scad
+++ b/geometry.scad
@@ -20,16 +20,6 @@
 //   point = The point to test.
 //   edge = Array of two points forming the line segment to test against.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
-function point_on_segment2d(point, edge, eps=EPSILON) =
-    assert( is_vector(point,2), "Invalid point." )
-    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
-    assert( _valid_line(edge,eps=eps), "Invalid segment." )
-    approx(point,edge[0],eps=eps) 
-    || approx(point,edge[1],eps=eps) // The point is an endpoint
-    || sign(edge[0].x-point.x)==sign(point.x-edge[1].x)  // point is in between the
-    || ( sign(edge[0].y-point.y)==sign(point.y-edge[1].y)  // edge endpoints
-        && approx(point_left_of_line2d(point, edge),0,eps=eps) );  // and on the line defined by edge
-
 function point_on_segment2d(point, edge, eps=EPSILON) =
     assert( is_vector(point,2), "Invalid point." )
     assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
@@ -98,10 +88,6 @@ function collinear(a, b, c, eps=EPSILON) =
     : noncollinear_triple(points,error=false,eps=eps)==[];
  
 
-//*** valid for any dimension
-
- 
-
 // Function: distance_from_line()
 // Usage:
 //   distance_from_line(line, pt);
@@ -330,17 +316,6 @@ function segment_intersection(s1,s2,eps=EPSILON) =
 //   stroke(line, endcaps="arrow2");
 //   color("blue") translate(pt) sphere(r=1,$fn=12);
 //   color("red") translate(p2) sphere(r=1,$fn=12);
-function line_closest_point(line,pt) =
-    assert(is_path(line)&&len(line)==2)
-    assert(same_shape(pt,line[0]))
-    assert(!approx(line[0],line[1]))
-    let(
-        seglen = norm(line[1]-line[0]),
-        segvec = (line[1]-line[0])/seglen,
-        projection = (pt-line[0]) * segvec
-    )
-    line[0] + projection*segvec;
-
 function line_closest_point(line,pt) =
     assert(_valid_line(line), "Invalid line." )
     assert( is_vector(pt,len(line[0])), "Invalid point or incompatible dimensions." )
@@ -1042,23 +1017,6 @@ function closest_point_on_plane(plane, point) =
 // Returns [POINT, U] if line intersects plane at one point.
 // Returns [LINE, undef] if the line is on the plane.
 // Returns undef if line is parallel to, but not on the given plane.
-function _general_plane_line_intersection(plane, line, eps=EPSILON) =
-    let(
-        p0 = line[0],
-        p1 = line[1],
-        n = plane_normal(plane),
-        u = p1 - p0,
-        d = n * u
-    ) abs(d)<eps? (
-        points_on_plane(p0,plane,eps)? [line,undef] :  // Line on plane
-        undef  // Line parallel to plane
-    ) : let(
-        v0 = closest_point_on_plane(plane, [0,0,0]),
-        w = p0 - v0,
-        s1 = (-n * w) / d,
-        pt = s1 * u + p0
-    ) [pt, s1];
-
 function _general_plane_line_intersection(plane, line, eps=EPSILON) =
     let( a = plane*[each line[0],-1],
          b = plane*[each(line[1]-line[0]),-1] )
@@ -1345,40 +1303,6 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 //   translate(circ[0]) color("green") stroke(circle(r=circ[1]),closed=true,$fn=72);
 //   translate(circ[0]) color("red") circle(d=3, $fn=12);
 //   move_copies(pts) color("blue") circle(d=3, $fn=12);
-function find_circle_3points(pt1, pt2, pt3) =
-    (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
-    ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
-    :   assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) 
-                && max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
-                "Invalid point(s)." )
-        collinear(pt1,pt2,pt3)? [undef,undef,undef] :
-        let(
-            v1 = pt1-pt2,
-            v2 = pt3-pt2,
-            n = vector_axis(v1,v2),
-            n2 = n.z<0? -n : n
-        ) len(pt1)+len(pt2)+len(pt3)>6? (
-            let(
-                a = project_plane(pt1, pt1, pt2, pt3),
-                b = project_plane(pt2, pt1, pt2, pt3),
-                c = project_plane(pt3, pt1, pt2, pt3),
-                res = find_circle_3points(a, b, c)
-            ) res[0]==undef? [undef,undef,undef] : let(
-                cp = lift_plane(res[0], pt1, pt2, pt3),
-                r = norm(pt2-cp)
-            ) [cp, r, n2]
-        ) : let(
-            mp1 = pt2 + v1/2,
-            mp2 = pt2 + v2/2,
-            mpv1 = rot(90, v=n, p=v1),
-            mpv2 = rot(90, v=n, p=v2),
-            l1 = [mp1, mp1+mpv1],
-            l2 = [mp2, mp2+mpv2],
-            isect = line_intersection(l1,l2)
-        ) is_undef(isect)? [undef,undef,undef] : let(
-            r = norm(pt2-isect)
-        ) [isect, r, n2];
-        
 function find_circle_3points(pt1, pt2, pt3) =
     (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
     ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
@@ -1403,9 +1327,6 @@ function find_circle_3points(pt1, pt2, pt3) =
             r  = norm(sc-v[0])
             )
         [ cp, r, n ];
-     
-     
-
 
 
 // Function: circle_point_tangents()
@@ -1607,19 +1528,6 @@ function furthest_point(pt, points) =
 // Arguments:
 //   poly = polygon to compute the area of.
 //   signed = if true, a signed area is returned (default: false)
-function polygon_area(poly) =
-    assert(is_path(poly), "Invalid polygon." )
-    len(poly)<3? 0 :
-    len(poly[0])==2? 0.5*sum([for(i=[0:1:len(poly)-1]) det2(select(poly,i,i+1))]) :
-    let(
-        plane = plane_from_points(poly)
-    ) plane==undef? undef :
-    let(
-        n = unit(plane_normal(plane)),
-        total = sum([for (i=[0:1:len(poly)-1]) cross(poly[i], select(poly,i+1))]),
-        res = abs(total * n) / 2
-    ) res;
-    
 function polygon_area(poly, signed=false) =
     assert(is_path(poly), "Invalid polygon." )
     len(poly)<3 ? 0 :
@@ -1644,15 +1552,6 @@ function polygon_area(poly, signed=false) =
 // Example:
 //   spiral = [for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]];
 //   is_convex_polygon(spiral);  // Returns: false
-function is_convex_polygon(poly) =
-    assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
-    let(
-        l = len(poly),
-        c = [for (i=idx(poly)) cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l])]
-    )
-    len([for (x=c) if(x>0) 1])==0 ||
-    len([for (x=c) if(x<0) 1])==0;
-
 function is_convex_polygon(poly) =
     assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
     let( l = len(poly) )
@@ -1726,33 +1625,6 @@ function polygon_shift_to_closest_point(path, pt) =
 //   move_copies(concat(circ,pent)) circle(r=.1,$fn=32);
 //   color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
 //   color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
-function reindex_polygon(reference, poly, return_error=false) = 
-    assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
-           "Invalid polygon(s) or incompatible dimensions. " )
-    assert(len(reference)==len(poly), "The polygons must have the same length.")
-    let(
-        dim = len(reference[0]),
-        N = len(reference),
-        fixpoly = dim != 2? poly :
-            polygon_is_clockwise(reference)? clockwise_polygon(poly) :
-            ccw_polygon(poly),
-        dist = [
-            // Matrix of all pairwise distances
-            for (p1=reference) [
-                for (p2=fixpoly) norm(p1-p2)
-            ]
-        ],
-        // Compute the sum of all distance pairs for a each shift
-        sums = [
-            for(shift=[0:1:N-1]) sum([
-                for(i=[0:1:N-1]) dist[i][(i+shift)%N]
-            ])
-        ],
-        optimal_poly = polygon_shift(fixpoly,min_index(sums))
-    )
-    return_error? [optimal_poly, min(sums)] :
-    optimal_poly;
-
 function reindex_polygon(reference, poly, return_error=false) = 
     assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
            "Invalid polygon(s) or incompatible dimensions. " )
@@ -1774,7 +1646,6 @@ function reindex_polygon(reference, poly, return_error=false) =
     optimal_poly;
     
 
-
 // Function: align_polygon()
 // Usage:
 //   newpoly = align_polygon(reference, poly, angles, [cp]);
@@ -1819,26 +1690,6 @@ function align_polygon(reference, poly, angles, cp) =
 //   Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid.
 //   Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid.
 //   If the polygon is self-intersecting, the results are undefined.
-function centroid(poly) =
-    assert( is_path(poly), "The input must be a 2D or 3D polygon." )
-    len(poly[0])==2
-    ? sum([
-            for(i=[0:len(poly)-1])
-            let(segment=select(poly,i,i+1))
-            det2(segment)*sum(segment)
-          ]) / 6 / polygon_area(poly)
-    : let( plane = plane_from_points(poly, fast=true) )
-      assert( !is_undef(plane), "The polygon must be planar." )
-      let(
-        n = plane_normal(plane),
-        p1 = vector_angle(n,UP)>15? vector_axis(n,UP) : vector_axis(n,RIGHT),
-        p2 = vector_axis(n,p1),
-        cp = mean(poly),
-        proj = project_plane(poly,cp,cp+p1,cp+p2),
-        cxy = centroid(proj)
-         ) 
-      lift_plane(cxy,cp,cp+p1,cp+p2);
-
 function centroid(poly) =
     assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
     len(poly[0])==2
@@ -1915,21 +1766,11 @@ function point_in_polygon(point, path, eps=EPSILON) =
 //   Results for complex (self-intersecting) polygon are indeterminate.
 // Arguments:
 //   path = The list of 2D path points for the perimeter of the polygon.
-function polygon_is_clockwise(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d polygon")
-    let(
-        minx = min(subindex(path,0)),
-        lowind = search(minx, path, 0, 0),
-        lowpts = select(path, lowind),
-        miny = min(subindex(lowpts, 1)),
-        extreme_sub = search(miny, lowpts, 1, 1)[0],
-        extreme = select(lowind,extreme_sub)
-    ) det2([select(path,extreme+1)-path[extreme], select(path, extreme-1)-path[extreme]])<0;
-
 function polygon_is_clockwise(path) =
     assert(is_path(path,dim=2), "Input should be a 2d path")
     polygon_area(path, signed=true)<0;
 
+
 // Function: clockwise_polygon()
 // Usage:
 //   clockwise_polygon(path);

From 12963296bb2a85bd4499cd6d6f6ee107415a8b65 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Thu, 20 Aug 2020 22:03:20 +0100
Subject: [PATCH 12/18] In observance of owner's last review

Eliminate double definitions.
Eliminate unneeded comments.

In common.scad redefine num_defined(), all_defined() and get_radius().

In geometry.scad:
- change name _dist to _dist2line
- simplify _point_above_below_segment() and triangle_area()
- change some arg names for uniformity (path>>poly)
- change point_in_polygon() to accept the Even-odd rule as alternative
- and other minor edits

Update tests_geometry to the new funcionalities.
---
 common.scad              |  67 +++-----
 geometry.scad            | 341 ++++++++++-----------------------------
 tests/test_geometry.scad |  55 ++++---
 3 files changed, 145 insertions(+), 318 deletions(-)

diff --git a/common.scad b/common.scad
index 51d8363..db4f3bb 100644
--- a/common.scad
+++ b/common.scad
@@ -129,11 +129,6 @@ function is_list_of(list,pattern) =
     is_list(list) &&
     []==[for(entry=0*list) if (entry != pattern) entry];
 
-function _list_pattern(list) =
-  is_list(list) ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0]
-                : 0;
-
-
 
 // Function: is_consistent()
 // Usage:
@@ -198,11 +193,11 @@ function first_defined(v,recursive=false,_i=0) =
             is_undef(first_defined(v[_i],recursive=recursive))
         )
     )? first_defined(v,recursive=recursive,_i=_i+1) : v[_i];
-
+    
 
 // Function: one_defined()
 // Usage:
-//   one_defined(vars, names, [required])
+//   one_defined(vars, names, <required>)
 // Description:
 //   Examines the input list `vars` and returns the entry which is not `undef`.  If more
 //   than one entry is `undef` then issues an assertion specifying "Must define exactly one of" followed
@@ -221,8 +216,7 @@ function one_defined(vars, names, required=true) =
 
 // Function: num_defined()
 // Description: Counts how many items in list `v` are not `undef`.
-function num_defined(v,_i=0,_cnt=0) = _i>=len(v)? _cnt : num_defined(v,_i+1,_cnt+(is_undef(v[_i])? 0 : 1));
-
+function num_defined(v) = len([for(vi=v) if(!is_undef(vi)) 1]);
 
 // Function: any_defined()
 // Description:
@@ -239,8 +233,8 @@ function any_defined(v,recursive=false) = first_defined(v,recursive=recursive) !
 // Arguments:
 //   v = The list whose items are being checked.
 //   recursive = If true, any sublists are evaluated recursively.
-function all_defined(v,recursive=false) = max([for (x=v) is_undef(x)||(recursive&&is_list(x)&&!all_defined(x))? 1 : 0])==0;
-
+function all_defined(v,recursive=false) = 
+    []==[for (x=v) if(is_undef(x)||(recursive && is_list(x) && !all_defined(x,recursive))) 0 ];
 
 
 
@@ -249,7 +243,7 @@ function all_defined(v,recursive=false) = max([for (x=v) is_undef(x)||(recursive
 
 // Function: get_anchor()
 // Usage:
-//   get_anchor(anchor,center,[uncentered],[dflt]);
+//   get_anchor(anchor,center,<uncentered>,<dflt>);
 // 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.
@@ -270,7 +264,7 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 
 // Function: get_radius()
 // Usage:
-//   get_radius([r1], [r2], [r], [d1], [d2], [d], [dflt]);
+//   get_radius(<r1>, <r2>, <r>, <d1>, <d2>, <d>, <dflt>);
 // Description:
 //   Given various radii and diameters, returns the most specific radius.
 //   If a diameter is most specific, returns half its value, giving the radius.
@@ -288,34 +282,23 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 //   r = Most general radius.
 //   d = Most general diameter.
 //   dflt = Value to return if all other values given are `undef`.
-function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = (
-    !is_undef(r1)
-		? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
-				assert(is_finite(r1), "Invalid radius r1." )
-				r1 
-    : !is_undef(r2)
-			? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
-				assert(is_finite(r2), "Invalid radius r2." )
-				r2
-		: !is_undef(d1)
-		  ? assert(is_finite(d1), "Invalid diameter d1." )
-				d1/2
-		: !is_undef(d2)
-			? assert(is_finite(d2), "Invalid diameter d2." )
-				d2/2
-		: !is_undef(r)
-			? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
-				assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
-				r 
-		: !is_undef(d)
-			? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." )
-			d/2
-		: dflt
-);
+function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = 
+    assert(num_defined([r1,d1,r2,d2])<2, "Conflicting or redundant radius/diameter arguments given.")
+    !is_undef(r1) ?   assert(is_finite(r1), "Invalid radius r1." ) r1 
+    : !is_undef(r2) ? assert(is_finite(r2), "Invalid radius r2." ) r2
+    : !is_undef(d1) ? assert(is_finite(d1), "Invalid diameter d1." ) d1/2
+    : !is_undef(d2) ? assert(is_finite(d2), "Invalid diameter d2." ) d2/2
+    : !is_undef(r)
+      ? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
+        assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
+        r 
+    : !is_undef(d) ? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." ) d/2
+    : dflt;
+
 
 // Function: get_height()
 // Usage:
-//   get_height([h],[l],[height],[dflt])
+//   get_height(<h>,<l>,<height>,<dflt>)
 // Description:
 //   Given several different parameters for height check that height is not multiply defined
 //   and return a single value.  If the three values `l`, `h`, and `height` are all undefined
@@ -332,7 +315,7 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) =
 
 // Function: scalar_vec3()
 // Usage:
-//   scalar_vec3(v, [dflt]);
+//   scalar_vec3(v, <dflt>);
 // Description:
 //   If `v` is a scalar, and `dflt==undef`, returns `[v, v, v]`.
 //   If `v` is a scalar, and `dflt!=undef`, returns `[v, dflt, dflt]`.
@@ -384,7 +367,7 @@ function _valstr(x) =
 
 // Module: assert_approx()
 // Usage:
-//   assert_approx(got, expected, [info]);
+//   assert_approx(got, expected, <info>);
 // Description:
 //   Tests if the value gotten is what was expected.  If not, then
 //   the expected and received values are printed to the console and
@@ -411,7 +394,7 @@ module assert_approx(got, expected, info) {
 
 // Module: assert_equal()
 // Usage:
-//   assert_equal(got, expected, [info]);
+//   assert_equal(got, expected, <info>);
 // Description:
 //   Tests if the value gotten is what was expected.  If not, then
 //   the expected and received values are printed to the console and
@@ -438,7 +421,7 @@ module assert_equal(got, expected, info) {
 
 // Module: shape_compare()
 // Usage:
-//   shape_compare([eps]) {test_shape(); expected_shape();}
+//   shape_compare(<eps>) {test_shape(); expected_shape();}
 // Description:
 //   Compares two child shapes, returning empty geometry if they are very nearly the same shape and size.
 //   Returns the differential geometry if they are not nearly the same shape and size.
diff --git a/geometry.scad b/geometry.scad
index eff67bc..fff7ebf 100644
--- a/geometry.scad
+++ b/geometry.scad
@@ -23,36 +23,25 @@
 function point_on_segment2d(point, edge, eps=EPSILON) =
     assert( is_vector(point,2), "Invalid point." )
     assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
-    assert( _valid_line(edge,eps=eps), "Invalid segment." )
-    approx(point,edge[0],eps=eps) 
-    || approx(point,edge[1],eps=eps) // The point is an endpoint
-    || sign(edge[0].x-point.x)==sign(point.x-edge[1].x)  // point is in between the
-    || ( sign(edge[0].y-point.y)==sign(point.y-edge[1].y)  // edge endpoints
-        && approx(point_left_of_line2d(point, edge),0,eps=eps) );  // and on the line defined by edge
-
-function point_on_segment2d(point, edge, eps=EPSILON) =
-    assert( is_vector(point,2), "Invalid point." )
-    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
-    assert( _valid_line(edge,eps=eps), "Invalid segment." )
+    assert( _valid_line(edge,2,eps=eps), "Invalid segment." )
     let( dp = point-edge[0],
          de = edge[1]-edge[0],
          ne = norm(de) ) 
     ( dp*de >= -eps*ne ) 
-    && ( (dp-de)*de <= eps*ne )              // point projects on the segment
-    && _dist(point-edge[0],unit(de))<eps;   // point is on the line 
+    && ( (dp-de)*de <= eps*ne )                  // point projects on the segment
+    && _dist2line(point-edge[0],unit(de))<eps;   // point is on the line 
     
     
 //Internal - distance from point `d` to the line passing through the origin with unit direction n
-//_dist works for any dimension
-function _dist(d,n) = norm(d-(d * n) * n);
+//_dist2line works for any dimension
+function _dist2line(d,n) = norm(d-(d * n) * n);
 
 // Internal non-exposed function.
 function _point_above_below_segment(point, edge) =
-    edge[0].y <= point.y? (
-        (edge[1].y > point.y && point_left_of_line2d(point, edge) > 0)? 1 : 0
-    ) : (
-        (edge[1].y <= point.y && point_left_of_line2d(point, edge) < 0)? -1 : 0
-    );
+    let( edge = edge - [point, point] )
+    edge[0].y <= 0 
+		?   (edge[1].y >  0 && cross(edge[0], edge[1]-edge[0]) > 0) ?  1 : 0
+    :   (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0 ;
 
 //Internal
 function _valid_line(line,dim,eps=EPSILON) = 
@@ -98,10 +87,6 @@ function collinear(a, b, c, eps=EPSILON) =
     : noncollinear_triple(points,error=false,eps=eps)==[];
  
 
-//*** valid for any dimension
-
- 
-
 // Function: distance_from_line()
 // Usage:
 //   distance_from_line(line, pt);
@@ -115,7 +100,7 @@ function collinear(a, b, c, eps=EPSILON) =
 function distance_from_line(line, pt) =
     assert( _valid_line(line) && is_vector(pt,len(line[0])), 
             "Invalid line, invalid point or incompatible dimensions." )
-    _dist(pt-line[0],unit(line[1]-line[0]));
+    _dist2line(pt-line[0],unit(line[1]-line[0]));
     
     
 // Function: line_normal()
@@ -330,17 +315,6 @@ function segment_intersection(s1,s2,eps=EPSILON) =
 //   stroke(line, endcaps="arrow2");
 //   color("blue") translate(pt) sphere(r=1,$fn=12);
 //   color("red") translate(p2) sphere(r=1,$fn=12);
-function line_closest_point(line,pt) =
-    assert(is_path(line)&&len(line)==2)
-    assert(same_shape(pt,line[0]))
-    assert(!approx(line[0],line[1]))
-    let(
-        seglen = norm(line[1]-line[0]),
-        segvec = (line[1]-line[0])/seglen,
-        projection = (pt-line[0]) * segvec
-    )
-    line[0] + projection*segvec;
-
 function line_closest_point(line,pt) =
     assert(_valid_line(line), "Invalid line." )
     assert( is_vector(pt,len(line[0])), "Invalid point or incompatible dimensions." )
@@ -774,14 +748,10 @@ function adj_opp_to_ang(adj,opp) =
 //   triangle_area([0,0], [5,10], [10,0]);  // Returns -50
 //   triangle_area([10,0], [5,10], [0,0]);  // Returns 50
 function triangle_area(a,b,c) = 
-    assert( is_path([a,b,c]),
-            "Invalid points or incompatible dimensions." )    
-    len(a)==3 ? 0.5*norm(cross(c-a,c-b)) 
-    : (
-        a.x * (b.y - c.y) +
-        b.x * (c.y - a.y) +
-        c.x * (a.y - b.y)
-    ) / 2;
+    assert( is_path([a,b,c]), "Invalid points or incompatible dimensions." )    
+    len(a)==3 
+		? 0.5*norm(cross(c-a,c-b)) 
+    : 0.5*cross(c-a,c-b);
 
 
 
@@ -851,7 +821,7 @@ function plane_from_normal(normal, pt=[0,0,0]) =
 
 // Function: plane_from_points()
 // Usage:
-//   plane_from_points(points, [fast], [eps]);
+//   plane_from_points(points, <fast>, <eps>);
 // Description:
 //   Given a list of 3 or more coplanar 3D points, returns the coefficients of the cartesian equation of a plane,
 //   that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane.
@@ -876,7 +846,6 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
     )
     indices==[] ? undef :
     let(
-        indices = sort(indices), // why sorting?
         p1 = points[indices[0]],
         p2 = points[indices[1]],
         p3 = points[indices[2]],
@@ -913,11 +882,6 @@ function plane_from_polygon(poly, fast=false, eps=EPSILON) =
     ) 
     fast? plane: coplanar(poly,eps=eps)? plane: [];
 
-//***
-// I don't see why this function uses a criterium different from plane_from_points.
-// In practical terms, what is the difference of finding a plane from points and from polygon?
-// The docs don't clarify.
-// These functions should be consistent if they are both necessary. The docs might reflect their distinction.
 
 // Function: plane_normal()
 // Usage:
@@ -969,8 +933,8 @@ function plane_transform(plane) =
 // Usage:
 //   projection_on_plane(points);
 // Description:
-//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the projection
-//   of the points on the plane.
+//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the 3D orthogonal 
+//   projection of the points on the plane.
 // Arguments:
 //   plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
 //   points = List of points to project
@@ -1042,23 +1006,6 @@ function closest_point_on_plane(plane, point) =
 // Returns [POINT, U] if line intersects plane at one point.
 // Returns [LINE, undef] if the line is on the plane.
 // Returns undef if line is parallel to, but not on the given plane.
-function _general_plane_line_intersection(plane, line, eps=EPSILON) =
-    let(
-        p0 = line[0],
-        p1 = line[1],
-        n = plane_normal(plane),
-        u = p1 - p0,
-        d = n * u
-    ) abs(d)<eps? (
-        points_on_plane(p0,plane,eps)? [line,undef] :  // Line on plane
-        undef  // Line parallel to plane
-    ) : let(
-        v0 = closest_point_on_plane(plane, [0,0,0]),
-        w = p0 - v0,
-        s1 = (-n * w) / d,
-        pt = s1 * u + p0
-    ) [pt, s1];
-
 function _general_plane_line_intersection(plane, line, eps=EPSILON) =
     let( a = plane*[each line[0],-1],
          b = plane*[each(line[1]-line[0]),-1] )
@@ -1066,6 +1013,7 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
     ? points_on_plane(line[0],plane,eps)? [line,undef]: undef
     : [ line[0]+a/b*(line[1]-line[0]), a/b ];
 
+
 // Function: plane_line_angle()
 // Usage: plane_line_angle(plane,line)
 // Description:
@@ -1137,7 +1085,7 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
     )
     indices==[] ? undef :
     let(
-        indices = sort(indices), // why sorting?
+        indices = sort(indices),
         p1 = poly[indices[0]],
         p2 = poly[indices[1]],
         p3 = poly[indices[2]],
@@ -1201,7 +1149,7 @@ function plane_intersection(plane1,plane2,plane3) =
 
 // Function: coplanar()
 // Usage:
-//   coplanar(points,eps);
+//   coplanar(points,<eps>);
 // Description:
 //   Returns true if the given 3D points are non-collinear and are on a plane.
 // Arguments:
@@ -1220,7 +1168,7 @@ function coplanar(points, eps=EPSILON) =
     
 // Function: points_on_plane()
 // Usage:
-//   points_on_plane(points, plane, eps);
+//   points_on_plane(points, plane, <eps>);
 // Description:
 //   Returns true if the given 3D points are on the given plane.
 // Arguments:
@@ -1256,7 +1204,7 @@ function in_front_of_plane(plane, point) =
 
 // Function: find_circle_2tangents()
 // Usage:
-//   find_circle_2tangents(pt1, pt2, pt3, r|d, [tangents]);
+//   find_circle_2tangents(pt1, pt2, pt3, r|d, <tangents>);
 // Description:
 //   Given a pair of rays with a common origin, and a known circle radius/diameter, finds
 //   the centerpoint for the circle of that size that touches both rays tangentally.
@@ -1325,7 +1273,8 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 
 // Function: find_circle_3points()
 // Usage:
-//   find_circle_3points(pt1, [pt2, pt3]);
+//   find_circle_3points(pt1, pt2, pt3);
+//   find_circle_3points([pt1, pt2, pt3]);
 // Description:
 //   Returns the [CENTERPOINT, RADIUS, NORMAL] of the circle that passes through three non-collinear
 //   points where NORMAL is the normal vector of the plane that the circle is on (UP or DOWN if the points are 2D).
@@ -1345,40 +1294,6 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 //   translate(circ[0]) color("green") stroke(circle(r=circ[1]),closed=true,$fn=72);
 //   translate(circ[0]) color("red") circle(d=3, $fn=12);
 //   move_copies(pts) color("blue") circle(d=3, $fn=12);
-function find_circle_3points(pt1, pt2, pt3) =
-    (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
-    ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
-    :   assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) 
-                && max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
-                "Invalid point(s)." )
-        collinear(pt1,pt2,pt3)? [undef,undef,undef] :
-        let(
-            v1 = pt1-pt2,
-            v2 = pt3-pt2,
-            n = vector_axis(v1,v2),
-            n2 = n.z<0? -n : n
-        ) len(pt1)+len(pt2)+len(pt3)>6? (
-            let(
-                a = project_plane(pt1, pt1, pt2, pt3),
-                b = project_plane(pt2, pt1, pt2, pt3),
-                c = project_plane(pt3, pt1, pt2, pt3),
-                res = find_circle_3points(a, b, c)
-            ) res[0]==undef? [undef,undef,undef] : let(
-                cp = lift_plane(res[0], pt1, pt2, pt3),
-                r = norm(pt2-cp)
-            ) [cp, r, n2]
-        ) : let(
-            mp1 = pt2 + v1/2,
-            mp2 = pt2 + v2/2,
-            mpv1 = rot(90, v=n, p=v1),
-            mpv2 = rot(90, v=n, p=v2),
-            l1 = [mp1, mp1+mpv1],
-            l2 = [mp2, mp2+mpv2],
-            isect = line_intersection(l1,l2)
-        ) is_undef(isect)? [undef,undef,undef] : let(
-            r = norm(pt2-isect)
-        ) [isect, r, n2];
-        
 function find_circle_3points(pt1, pt2, pt3) =
     (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
     ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
@@ -1404,9 +1319,6 @@ function find_circle_3points(pt1, pt2, pt3) =
             )
         [ cp, r, n ];
      
-     
-
-
 
 // Function: circle_point_tangents()
 // Usage:
@@ -1442,7 +1354,6 @@ function circle_point_tangents(r, d, cp, pt) =
     ) [for (ang=angs) [ang, cp + r*[cos(ang),sin(ang)]]];
 
 
-
 // Function: circle_circle_tangents()
 // Usage: circle_circle_tangents(c1, r1|d1, c2, r2|d2)
 // Description:
@@ -1541,13 +1452,14 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
         []
     :   let(
             n = (pb-pa)/nrm,
-            distlist = [for(i=[0:len(points)-1]) _dist(points[i]-pa, n)]   
+            distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)]   
            )
         max(distlist)<eps
         ?  assert(!error, "Cannot find three noncollinear points in pointlist.")
            []
         :  [0,b,max_index(distlist)];    
 
+
 // Function: pointlist_bounds()
 // Usage:
 //   pointlist_bounds(pts);
@@ -1607,19 +1519,6 @@ function furthest_point(pt, points) =
 // Arguments:
 //   poly = polygon to compute the area of.
 //   signed = if true, a signed area is returned (default: false)
-function polygon_area(poly) =
-    assert(is_path(poly), "Invalid polygon." )
-    len(poly)<3? 0 :
-    len(poly[0])==2? 0.5*sum([for(i=[0:1:len(poly)-1]) det2(select(poly,i,i+1))]) :
-    let(
-        plane = plane_from_points(poly)
-    ) plane==undef? undef :
-    let(
-        n = unit(plane_normal(plane)),
-        total = sum([for (i=[0:1:len(poly)-1]) cross(poly[i], select(poly,i+1))]),
-        res = abs(total * n) / 2
-    ) res;
-    
 function polygon_area(poly, signed=false) =
     assert(is_path(poly), "Invalid polygon." )
     len(poly)<3 ? 0 :
@@ -1644,15 +1543,6 @@ function polygon_area(poly, signed=false) =
 // Example:
 //   spiral = [for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]];
 //   is_convex_polygon(spiral);  // Returns: false
-function is_convex_polygon(poly) =
-    assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
-    let(
-        l = len(poly),
-        c = [for (i=idx(poly)) cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l])]
-    )
-    len([for (x=c) if(x>0) 1])==0 ||
-    len([for (x=c) if(x<0) 1])==0;
-
 function is_convex_polygon(poly) =
     assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
     let( l = len(poly) )
@@ -1686,15 +1576,15 @@ function polygon_shift(poly, i) =
 // Usage:
 //   polygon_shift_to_closest_point(path, pt);
 // Description:
-//   Given a polygon `path`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
-function polygon_shift_to_closest_point(path, pt) =
+//   Given a polygon `poly`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
+function polygon_shift_to_closest_point(poly, pt) =
     assert(is_vector(pt), "Invalid point." )
-    assert(is_path(path,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
+    assert(is_path(poly,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
     let(
-        path = cleanup_path(path),
-        dists = [for (p=path) norm(p-pt)],
+        poly = cleanup_path(poly),
+        dists = [for (p=poly) norm(p-pt)],
         closest = min_index(dists)
-    ) select(path,closest,closest+len(path)-1);
+    ) select(poly,closest,closest+len(poly)-1);
 
 
 // Function: reindex_polygon()
@@ -1726,33 +1616,6 @@ function polygon_shift_to_closest_point(path, pt) =
 //   move_copies(concat(circ,pent)) circle(r=.1,$fn=32);
 //   color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
 //   color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
-function reindex_polygon(reference, poly, return_error=false) = 
-    assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
-           "Invalid polygon(s) or incompatible dimensions. " )
-    assert(len(reference)==len(poly), "The polygons must have the same length.")
-    let(
-        dim = len(reference[0]),
-        N = len(reference),
-        fixpoly = dim != 2? poly :
-            polygon_is_clockwise(reference)? clockwise_polygon(poly) :
-            ccw_polygon(poly),
-        dist = [
-            // Matrix of all pairwise distances
-            for (p1=reference) [
-                for (p2=fixpoly) norm(p1-p2)
-            ]
-        ],
-        // Compute the sum of all distance pairs for a each shift
-        sums = [
-            for(shift=[0:1:N-1]) sum([
-                for(i=[0:1:N-1]) dist[i][(i+shift)%N]
-            ])
-        ],
-        optimal_poly = polygon_shift(fixpoly,min_index(sums))
-    )
-    return_error? [optimal_poly, min(sums)] :
-    optimal_poly;
-
 function reindex_polygon(reference, poly, return_error=false) = 
     assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
            "Invalid polygon(s) or incompatible dimensions. " )
@@ -1774,10 +1637,9 @@ function reindex_polygon(reference, poly, return_error=false) =
     optimal_poly;
     
 
-
 // Function: align_polygon()
 // Usage:
-//   newpoly = align_polygon(reference, poly, angles, [cp]);
+//   newpoly = align_polygon(reference, poly, angles, <cp>);
 // Description:
 //   Tries the list or range of angles to find a rotation of the specified 2D polygon that best aligns
 //   with the reference 2D polygon.  For each angle, the polygon is reindexed, which is a costly operation
@@ -1819,26 +1681,6 @@ function align_polygon(reference, poly, angles, cp) =
 //   Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid.
 //   Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid.
 //   If the polygon is self-intersecting, the results are undefined.
-function centroid(poly) =
-    assert( is_path(poly), "The input must be a 2D or 3D polygon." )
-    len(poly[0])==2
-    ? sum([
-            for(i=[0:len(poly)-1])
-            let(segment=select(poly,i,i+1))
-            det2(segment)*sum(segment)
-          ]) / 6 / polygon_area(poly)
-    : let( plane = plane_from_points(poly, fast=true) )
-      assert( !is_undef(plane), "The polygon must be planar." )
-      let(
-        n = plane_normal(plane),
-        p1 = vector_angle(n,UP)>15? vector_axis(n,UP) : vector_axis(n,RIGHT),
-        p2 = vector_axis(n,p1),
-        cp = mean(poly),
-        proj = project_plane(poly,cp,cp+p1,cp+p2),
-        cxy = centroid(proj)
-         ) 
-      lift_plane(cxy,cp,cp+p1,cp+p2);
-
 function centroid(poly) =
     assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
     len(poly[0])==2
@@ -1866,10 +1708,11 @@ function centroid(poly) =
 
 // Function: point_in_polygon()
 // Usage:
-//   point_in_polygon(point, path, [eps])
+//   point_in_polygon(point, poly, <eps>)
 // Description:
 //   This function tests whether the given 2D point is inside, outside or on the boundary of
-//   the specified 2D polygon using the Winding Number method.
+//   the specified 2D polygon using either the Nonzero Winding rule or the Even-Odd rule.
+//   See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Even–odd_rule.
 //   The polygon is given as a list of 2D points, not including the repeated end point.
 //   Returns -1 if the point is outside the polyon.
 //   Returns 0 if the point is on the boundary.
@@ -1879,75 +1722,81 @@ function centroid(poly) =
 //   Rounding error may give mixed results for points on or near the boundary.
 // Arguments:
 //   point = The 2D point to check position of.
-//   path = The list of 2D path points forming the perimeter of the polygon.
+//   poly = The list of 2D path points forming the perimeter of the polygon.
+//   nonzero = The rule to use: true for "Nonzero" rule and false for "Even-Odd" (Default: true )
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
-function point_in_polygon(point, path, eps=EPSILON) =
-    // Original algorithm from http://geomalgorithms.com/a03-_inclusion.html
-    assert( is_vector(point,2) && is_path(path,dim=2) && len(path)>2,
+function point_in_polygon(point, poly, eps=EPSILON, nonzero=true) =
+    // Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
+    assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,
             "The point and polygon should be in 2D. The polygon should have more that 2 points." )
     assert( is_finite(eps) && eps>=0, "Invalid tolerance." )
     // Does the point lie on any edges?  If so return 0.
     let(
-        on_brd = [for(i=[0:1:len(path)-1]) 
-                    let( seg = select(path,i,i+1) ) 
-                    if( !approx(seg[0],seg[1],eps=eps) ) 
+        on_brd = [for(i=[0:1:len(poly)-1]) 
+                    let( seg = select(poly,i,i+1) ) 
+                    if( !approx(seg[0],seg[1],eps=EPSILON) ) 
                         point_on_segment2d(point, seg, eps=eps)? 1:0 ]
         )
-    sum(on_brd) > 0? 0 :
-    // Otherwise compute winding number and return 1 for interior, -1 for exterior
-    let(
-        windchk = [for(i=[0:1:len(path)-1]) 
-                    let(seg=select(path,i,i+1)) 
-                    if(!approx(seg[0],seg[1],eps=eps)) 
-                        _point_above_below_segment(point, seg)
-                  ]
-        )
-    sum(windchk) != 0 ? 1 : -1;
+    sum(on_brd) > 0
+    ? 0 
+    :   nonzero
+        ?    // Compute winding number and return 1 for interior, -1 for exterior
+            let(
+                windchk = [for(i=[0:1:len(poly)-1]) 
+                            let(seg=select(poly,i,i+1)) 
+                            if(!approx(seg[0],seg[1],eps=eps)) 
+                                _point_above_below_segment(point, seg)
+                          ]
+                )
+            sum(windchk) != 0 ? 1 : -1
+        :   // or compute the crossings with the ray [point, point+[1,0]]
+            let( 
+              n  = len(poly),
+              cross = 
+                [for(i=[0:n-1])
+                    let( 
+                      p0 = poly[i]-point, 
+                      p1 = poly[(i+1)%n]-point
+                      )
+                    if( ( (p1.y>eps && p0.y<=0) || (p1.y<=0 && p0.y>eps) )
+                        &&  0 < p0.x - p0.y *(p1.x - p0.x)/(p1.y - p0.y) )
+                    1
+                ]
+            )
+            2*(len(cross)%2)-1;;
 
-//**
-// this function should be optimized avoiding the call of other functions
 
 // Function: polygon_is_clockwise()
 // Usage:
-//   polygon_is_clockwise(path);
+//   polygon_is_clockwise(poly);
 // Description:
 //   Return true if the given 2D simple polygon is in clockwise order, false otherwise.
 //   Results for complex (self-intersecting) polygon are indeterminate.
 // Arguments:
-//   path = The list of 2D path points for the perimeter of the polygon.
-function polygon_is_clockwise(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d polygon")
-    let(
-        minx = min(subindex(path,0)),
-        lowind = search(minx, path, 0, 0),
-        lowpts = select(path, lowind),
-        miny = min(subindex(lowpts, 1)),
-        extreme_sub = search(miny, lowpts, 1, 1)[0],
-        extreme = select(lowind,extreme_sub)
-    ) det2([select(path,extreme+1)-path[extreme], select(path, extreme-1)-path[extreme]])<0;
+//   poly = The list of 2D path points for the perimeter of the polygon.
+function polygon_is_clockwise(poly) =
+    assert(is_path(poly,dim=2), "Input should be a 2d path")
+    polygon_area(poly, signed=true)<0;
 
-function polygon_is_clockwise(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d path")
-    polygon_area(path, signed=true)<0;
 
 // Function: clockwise_polygon()
 // Usage:
-//   clockwise_polygon(path);
+//   clockwise_polygon(poly);
 // Description:
 //   Given a 2D polygon path, returns the clockwise winding version of that path.
-function clockwise_polygon(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d polygon")
-    polygon_area(path, signed=true)<0 ? path : reverse_polygon(path);
+function clockwise_polygon(poly) =
+    assert(is_path(poly,dim=2), "Input should be a 2d polygon")
+    polygon_area(poly, signed=true)<0 ? poly : reverse_polygon(poly);
 
 
 // Function: ccw_polygon()
 // Usage:
-//   ccw_polygon(path);
+//   ccw_polygon(poly);
 // Description:
-//   Given a 2D polygon path, returns the counter-clockwise winding version of that path.
-function ccw_polygon(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d polygon")
-    polygon_area(path, signed=true)<0 ? reverse_polygon(path) : path;
+//   Given a 2D polygon poly, returns the counter-clockwise winding version of that poly.
+function ccw_polygon(poly) =
+    assert(is_path(poly,dim=2), "Input should be a 2d polygon")
+    polygon_area(poly, signed=true)<0 ? reverse_polygon(poly) : poly;
 
 
 // Function: reverse_polygon()
@@ -1969,7 +1818,7 @@ function reverse_polygon(poly) =
 function polygon_normal(poly) =
     assert(is_path(poly,dim=3), "Invalid 3D polygon." )
     let(
-        poly = path3d(cleanup_path(poly)),
+        poly = cleanup_path(poly),
         p0 = poly[0],
         n = sum([
             for (i=[1:1:len(poly)-2])
@@ -2085,17 +1934,6 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
         ], xs, _i=_i+1
     );
     
-//***
-// all the functions split_polygons_at_ may generate non simple polygons even from simple polygon inputs:
-//     split_polygons_at_each_y([[[-1,1,0],[0,0,0],[1,1,0],[1,-1,0],[-1,-1,0]]],[0])
-// produces:
-//   [  [[0, 0, 0], [1, 0, 0], [1, -1, 0], [-1, -1, 0], [-1, 0, 0]]
-//      [[-1, 1, 0], [0, 0, 0], [1, 1, 0], [1, 0, 0], [-1, 0, 0]] ]
-// and the second polygon is self-intersecting
-// besides, it fails in some simple cases as triangles:
-//   split_polygons_at_each_y([ [-1,-1,0],[1,-1,0],[0,1,0]],[0])==[]
-// this last failure may be fatal for vnf_bend
-
 
 // Function: split_polygons_at_each_y()
 // Usage:
@@ -2106,9 +1944,9 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
 //   polys = A list of 3D polygons to split.
 //   ys = A list of scalar Y values to split at.
 function split_polygons_at_each_y(polys, ys, _i=0) =
-    assert( is_consistent(polys) && is_path(poly[0],dim=3) ,
-            "The input list should contains only 3D polygons." )
-    assert( is_finite(ys), "The split value list should contain only numbers." )  
+//    assert( is_consistent(polys) && is_path(polys[0],dim=3) , // not all polygons should have the same length!!!
+  //          "The input list should contains only 3D polygons." )
+    assert( is_finite(ys) || is_vector(ys), "The split value list should contain only numbers." )  //***
     _i>=len(ys)? polys :
     split_polygons_at_each_y(
         [
@@ -2139,5 +1977,4 @@ function split_polygons_at_each_z(polys, zs, _i=0) =
     );
 
 
-
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad
index ceeb905..0950e1c 100644
--- a/tests/test_geometry.scad
+++ b/tests/test_geometry.scad
@@ -98,6 +98,8 @@ function standardize(v) =
     v==[]? [] :
     sign([for(vi=v) if( ! approx(vi,0)) vi,0 ][0])*v;
 
+module assert_std(vc,ve) { assert(standardize(vc)==standardize(ve)); }
+
 module test_points_on_plane() {
     pts     = [for(i=[0:40]) rands(-1,1,3) ];
     dir     = rands(-10,10,3);
@@ -487,48 +489,47 @@ module test_triangle_area() {
 
 
 module test_plane3pt() {
-    assert(plane3pt([0,0,20], [0,10,10], [0,0,0]) == [1,0,0,0]);
-    assert(plane3pt([2,0,20], [2,10,10], [2,0,0]) == [1,0,0,2]);
-    assert(plane3pt([0,0,0], [10,0,10], [0,0,20]) == [0,1,0,0]);
-    assert(plane3pt([0,2,0], [10,2,10], [0,2,20]) == [0,1,0,2]);
-    assert(plane3pt([0,0,0], [10,10,0], [20,0,0]) == [0,0,1,0]);
-    assert(plane3pt([0,0,2], [10,10,2], [20,0,2]) == [0,0,1,2]);
+    assert_std(plane3pt([0,0,20], [0,10,10], [0,0,0]), [1,0,0,0]);
+    assert_std(plane3pt([2,0,20], [2,10,10], [2,0,0]), [1,0,0,2]);
+    assert_std(plane3pt([0,0,0], [10,0,10], [0,0,20]), [0,1,0,0]);
+    assert_std(plane3pt([0,2,0], [10,2,10], [0,2,20]), [0,1,0,2]);
+    assert_std(plane3pt([0,0,0], [10,10,0], [20,0,0]), [0,0,1,0]);
+    assert_std(plane3pt([0,0,2], [10,10,2], [20,0,2]), [0,0,1,2]);
 }
 *test_plane3pt();
 
 module test_plane3pt_indexed() {
     pts = [ [0,0,0], [10,0,0], [0,10,0], [0,0,10] ];
     s13 = sqrt(1/3);
-    assert(plane3pt_indexed(pts, 0,3,2) == [1,0,0,0]);
-    assert(plane3pt_indexed(pts, 0,2,3) == [-1,0,0,0]);
-    assert(plane3pt_indexed(pts, 0,1,3) == [0,1,0,0]);
-    assert(plane3pt_indexed(pts, 0,3,1) == [0,-1,0,0]);
-    assert(plane3pt_indexed(pts, 0,2,1) == [0,0,1,0]);
+    assert_std(plane3pt_indexed(pts, 0,3,2), [1,0,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,2,3), [-1,0,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,1,3), [0,1,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,3,1), [0,-1,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,2,1), [0,0,1,0]);
     assert_approx(plane3pt_indexed(pts, 0,1,2), [0,0,-1,0]);
     assert_approx(plane3pt_indexed(pts, 3,2,1), [s13,s13,s13,10*s13]);
     assert_approx(plane3pt_indexed(pts, 1,2,3), [-s13,-s13,-s13,-10*s13]);
 }
 *test_plane3pt_indexed();
 
-
 module test_plane_from_points() {
-    assert(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]) == [1,0,0,0]);
-    assert(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]) == [1,0,0,2]);
-    assert(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]) == [0,1,0,0]);
-    assert(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]) == [0,1,0,2]);
-    assert(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]) == [0,0,1,0]);
-    assert(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]) == [0,0,1,2]);
+    assert_std(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]), [1,0,0,0]);
+    assert_std(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]), [1,0,0,2]);
+    assert_std(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]), [0,1,0,0]);
+    assert_std(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]), [0,1,0,2]);
+    assert_std(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]), [0,0,1,0]);
+    assert_std(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]), [0,0,1,2]);
 }
 *test_plane_from_points();
 
 
 module test_plane_normal() {
-    assert(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])) == [1,0,0]);
-    assert(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])) == [1,0,0]);
-    assert(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])) == [0,1,0]);
-    assert(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])) == [0,1,0]);
-    assert(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])) == [0,0,1]);
-    assert(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])) == [0,0,1]);
+    assert_std(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])), [1,0,0]);
+    assert_std(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])), [1,0,0]);
+    assert_std(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])), [0,1,0]);
+    assert_std(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])), [0,1,0]);
+    assert_std(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])), [0,0,1]);
+    assert_std(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])), [0,0,1]);
 }
 *test_plane_normal();
 
@@ -699,16 +700,22 @@ module test_simplify_path_indexed() {
 
 module test_point_in_polygon() {
     poly = [for (a=[0:30:359]) 10*[cos(a),sin(a)]];
+    poly2 = [ [-3,-3],[2,-3],[2,1],[-1,1],[-1,-1],[1,-1],[1,2],[-3,2] ];
     assert(point_in_polygon([0,0], poly) == 1);
     assert(point_in_polygon([20,0], poly) == -1);
+    assert(point_in_polygon([20,0], poly,EPSILON,nonzero=false) == -1);
     assert(point_in_polygon([5,5], poly) == 1);
     assert(point_in_polygon([-5,5], poly) == 1);
     assert(point_in_polygon([-5,-5], poly) == 1);
     assert(point_in_polygon([5,-5], poly) == 1);
+    assert(point_in_polygon([5,-5], poly,EPSILON,nonzero=false) == 1);
     assert(point_in_polygon([-10,-10], poly) == -1);
     assert(point_in_polygon([10,0], poly) == 0);
     assert(point_in_polygon([0,10], poly) == 0);
     assert(point_in_polygon([0,-10], poly) == 0);
+    assert(point_in_polygon([0,-10], poly,EPSILON,nonzero=false) == 0);
+    assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=true) == 1);
+    assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=false) == -1);
 }
 *test_point_in_polygon();
 

From 5051fe59775f322e1dc33b5be8f6bd06b4ce00f2 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Thu, 20 Aug 2020 22:22:55 +0100
Subject: [PATCH 13/18] Revert "In observance of owner's last review"

This reverts commit 12963296bb2a85bd4499cd6d6f6ee107415a8b65.
---
 common.scad              |  67 +++++---
 geometry.scad            | 341 +++++++++++++++++++++++++++++----------
 tests/test_geometry.scad |  55 +++----
 3 files changed, 318 insertions(+), 145 deletions(-)

diff --git a/common.scad b/common.scad
index db4f3bb..51d8363 100644
--- a/common.scad
+++ b/common.scad
@@ -129,6 +129,11 @@ function is_list_of(list,pattern) =
     is_list(list) &&
     []==[for(entry=0*list) if (entry != pattern) entry];
 
+function _list_pattern(list) =
+  is_list(list) ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0]
+                : 0;
+
+
 
 // Function: is_consistent()
 // Usage:
@@ -193,11 +198,11 @@ function first_defined(v,recursive=false,_i=0) =
             is_undef(first_defined(v[_i],recursive=recursive))
         )
     )? first_defined(v,recursive=recursive,_i=_i+1) : v[_i];
-    
+
 
 // Function: one_defined()
 // Usage:
-//   one_defined(vars, names, <required>)
+//   one_defined(vars, names, [required])
 // Description:
 //   Examines the input list `vars` and returns the entry which is not `undef`.  If more
 //   than one entry is `undef` then issues an assertion specifying "Must define exactly one of" followed
@@ -216,7 +221,8 @@ function one_defined(vars, names, required=true) =
 
 // Function: num_defined()
 // Description: Counts how many items in list `v` are not `undef`.
-function num_defined(v) = len([for(vi=v) if(!is_undef(vi)) 1]);
+function num_defined(v,_i=0,_cnt=0) = _i>=len(v)? _cnt : num_defined(v,_i+1,_cnt+(is_undef(v[_i])? 0 : 1));
+
 
 // Function: any_defined()
 // Description:
@@ -233,8 +239,8 @@ function any_defined(v,recursive=false) = first_defined(v,recursive=recursive) !
 // Arguments:
 //   v = The list whose items are being checked.
 //   recursive = If true, any sublists are evaluated recursively.
-function all_defined(v,recursive=false) = 
-    []==[for (x=v) if(is_undef(x)||(recursive && is_list(x) && !all_defined(x,recursive))) 0 ];
+function all_defined(v,recursive=false) = max([for (x=v) is_undef(x)||(recursive&&is_list(x)&&!all_defined(x))? 1 : 0])==0;
+
 
 
 
@@ -243,7 +249,7 @@ function all_defined(v,recursive=false) =
 
 // Function: get_anchor()
 // Usage:
-//   get_anchor(anchor,center,<uncentered>,<dflt>);
+//   get_anchor(anchor,center,[uncentered],[dflt]);
 // 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.
@@ -264,7 +270,7 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 
 // Function: get_radius()
 // Usage:
-//   get_radius(<r1>, <r2>, <r>, <d1>, <d2>, <d>, <dflt>);
+//   get_radius([r1], [r2], [r], [d1], [d2], [d], [dflt]);
 // Description:
 //   Given various radii and diameters, returns the most specific radius.
 //   If a diameter is most specific, returns half its value, giving the radius.
@@ -282,23 +288,34 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 //   r = Most general radius.
 //   d = Most general diameter.
 //   dflt = Value to return if all other values given are `undef`.
-function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = 
-    assert(num_defined([r1,d1,r2,d2])<2, "Conflicting or redundant radius/diameter arguments given.")
-    !is_undef(r1) ?   assert(is_finite(r1), "Invalid radius r1." ) r1 
-    : !is_undef(r2) ? assert(is_finite(r2), "Invalid radius r2." ) r2
-    : !is_undef(d1) ? assert(is_finite(d1), "Invalid diameter d1." ) d1/2
-    : !is_undef(d2) ? assert(is_finite(d2), "Invalid diameter d2." ) d2/2
-    : !is_undef(r)
-      ? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
-        assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
-        r 
-    : !is_undef(d) ? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." ) d/2
-    : dflt;
-
+function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = (
+    !is_undef(r1)
+		? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
+				assert(is_finite(r1), "Invalid radius r1." )
+				r1 
+    : !is_undef(r2)
+			? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
+				assert(is_finite(r2), "Invalid radius r2." )
+				r2
+		: !is_undef(d1)
+		  ? assert(is_finite(d1), "Invalid diameter d1." )
+				d1/2
+		: !is_undef(d2)
+			? assert(is_finite(d2), "Invalid diameter d2." )
+				d2/2
+		: !is_undef(r)
+			? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
+				assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
+				r 
+		: !is_undef(d)
+			? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." )
+			d/2
+		: dflt
+);
 
 // Function: get_height()
 // Usage:
-//   get_height(<h>,<l>,<height>,<dflt>)
+//   get_height([h],[l],[height],[dflt])
 // Description:
 //   Given several different parameters for height check that height is not multiply defined
 //   and return a single value.  If the three values `l`, `h`, and `height` are all undefined
@@ -315,7 +332,7 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) =
 
 // Function: scalar_vec3()
 // Usage:
-//   scalar_vec3(v, <dflt>);
+//   scalar_vec3(v, [dflt]);
 // Description:
 //   If `v` is a scalar, and `dflt==undef`, returns `[v, v, v]`.
 //   If `v` is a scalar, and `dflt!=undef`, returns `[v, dflt, dflt]`.
@@ -367,7 +384,7 @@ function _valstr(x) =
 
 // Module: assert_approx()
 // Usage:
-//   assert_approx(got, expected, <info>);
+//   assert_approx(got, expected, [info]);
 // Description:
 //   Tests if the value gotten is what was expected.  If not, then
 //   the expected and received values are printed to the console and
@@ -394,7 +411,7 @@ module assert_approx(got, expected, info) {
 
 // Module: assert_equal()
 // Usage:
-//   assert_equal(got, expected, <info>);
+//   assert_equal(got, expected, [info]);
 // Description:
 //   Tests if the value gotten is what was expected.  If not, then
 //   the expected and received values are printed to the console and
@@ -421,7 +438,7 @@ module assert_equal(got, expected, info) {
 
 // Module: shape_compare()
 // Usage:
-//   shape_compare(<eps>) {test_shape(); expected_shape();}
+//   shape_compare([eps]) {test_shape(); expected_shape();}
 // Description:
 //   Compares two child shapes, returning empty geometry if they are very nearly the same shape and size.
 //   Returns the differential geometry if they are not nearly the same shape and size.
diff --git a/geometry.scad b/geometry.scad
index fff7ebf..eff67bc 100644
--- a/geometry.scad
+++ b/geometry.scad
@@ -23,25 +23,36 @@
 function point_on_segment2d(point, edge, eps=EPSILON) =
     assert( is_vector(point,2), "Invalid point." )
     assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
-    assert( _valid_line(edge,2,eps=eps), "Invalid segment." )
+    assert( _valid_line(edge,eps=eps), "Invalid segment." )
+    approx(point,edge[0],eps=eps) 
+    || approx(point,edge[1],eps=eps) // The point is an endpoint
+    || sign(edge[0].x-point.x)==sign(point.x-edge[1].x)  // point is in between the
+    || ( sign(edge[0].y-point.y)==sign(point.y-edge[1].y)  // edge endpoints
+        && approx(point_left_of_line2d(point, edge),0,eps=eps) );  // and on the line defined by edge
+
+function point_on_segment2d(point, edge, eps=EPSILON) =
+    assert( is_vector(point,2), "Invalid point." )
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    assert( _valid_line(edge,eps=eps), "Invalid segment." )
     let( dp = point-edge[0],
          de = edge[1]-edge[0],
          ne = norm(de) ) 
     ( dp*de >= -eps*ne ) 
-    && ( (dp-de)*de <= eps*ne )                  // point projects on the segment
-    && _dist2line(point-edge[0],unit(de))<eps;   // point is on the line 
+    && ( (dp-de)*de <= eps*ne )              // point projects on the segment
+    && _dist(point-edge[0],unit(de))<eps;   // point is on the line 
     
     
 //Internal - distance from point `d` to the line passing through the origin with unit direction n
-//_dist2line works for any dimension
-function _dist2line(d,n) = norm(d-(d * n) * n);
+//_dist works for any dimension
+function _dist(d,n) = norm(d-(d * n) * n);
 
 // Internal non-exposed function.
 function _point_above_below_segment(point, edge) =
-    let( edge = edge - [point, point] )
-    edge[0].y <= 0 
-		?   (edge[1].y >  0 && cross(edge[0], edge[1]-edge[0]) > 0) ?  1 : 0
-    :   (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0 ;
+    edge[0].y <= point.y? (
+        (edge[1].y > point.y && point_left_of_line2d(point, edge) > 0)? 1 : 0
+    ) : (
+        (edge[1].y <= point.y && point_left_of_line2d(point, edge) < 0)? -1 : 0
+    );
 
 //Internal
 function _valid_line(line,dim,eps=EPSILON) = 
@@ -87,6 +98,10 @@ function collinear(a, b, c, eps=EPSILON) =
     : noncollinear_triple(points,error=false,eps=eps)==[];
  
 
+//*** valid for any dimension
+
+ 
+
 // Function: distance_from_line()
 // Usage:
 //   distance_from_line(line, pt);
@@ -100,7 +115,7 @@ function collinear(a, b, c, eps=EPSILON) =
 function distance_from_line(line, pt) =
     assert( _valid_line(line) && is_vector(pt,len(line[0])), 
             "Invalid line, invalid point or incompatible dimensions." )
-    _dist2line(pt-line[0],unit(line[1]-line[0]));
+    _dist(pt-line[0],unit(line[1]-line[0]));
     
     
 // Function: line_normal()
@@ -315,6 +330,17 @@ function segment_intersection(s1,s2,eps=EPSILON) =
 //   stroke(line, endcaps="arrow2");
 //   color("blue") translate(pt) sphere(r=1,$fn=12);
 //   color("red") translate(p2) sphere(r=1,$fn=12);
+function line_closest_point(line,pt) =
+    assert(is_path(line)&&len(line)==2)
+    assert(same_shape(pt,line[0]))
+    assert(!approx(line[0],line[1]))
+    let(
+        seglen = norm(line[1]-line[0]),
+        segvec = (line[1]-line[0])/seglen,
+        projection = (pt-line[0]) * segvec
+    )
+    line[0] + projection*segvec;
+
 function line_closest_point(line,pt) =
     assert(_valid_line(line), "Invalid line." )
     assert( is_vector(pt,len(line[0])), "Invalid point or incompatible dimensions." )
@@ -748,10 +774,14 @@ function adj_opp_to_ang(adj,opp) =
 //   triangle_area([0,0], [5,10], [10,0]);  // Returns -50
 //   triangle_area([10,0], [5,10], [0,0]);  // Returns 50
 function triangle_area(a,b,c) = 
-    assert( is_path([a,b,c]), "Invalid points or incompatible dimensions." )    
-    len(a)==3 
-		? 0.5*norm(cross(c-a,c-b)) 
-    : 0.5*cross(c-a,c-b);
+    assert( is_path([a,b,c]),
+            "Invalid points or incompatible dimensions." )    
+    len(a)==3 ? 0.5*norm(cross(c-a,c-b)) 
+    : (
+        a.x * (b.y - c.y) +
+        b.x * (c.y - a.y) +
+        c.x * (a.y - b.y)
+    ) / 2;
 
 
 
@@ -821,7 +851,7 @@ function plane_from_normal(normal, pt=[0,0,0]) =
 
 // Function: plane_from_points()
 // Usage:
-//   plane_from_points(points, <fast>, <eps>);
+//   plane_from_points(points, [fast], [eps]);
 // Description:
 //   Given a list of 3 or more coplanar 3D points, returns the coefficients of the cartesian equation of a plane,
 //   that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane.
@@ -846,6 +876,7 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
     )
     indices==[] ? undef :
     let(
+        indices = sort(indices), // why sorting?
         p1 = points[indices[0]],
         p2 = points[indices[1]],
         p3 = points[indices[2]],
@@ -882,6 +913,11 @@ function plane_from_polygon(poly, fast=false, eps=EPSILON) =
     ) 
     fast? plane: coplanar(poly,eps=eps)? plane: [];
 
+//***
+// I don't see why this function uses a criterium different from plane_from_points.
+// In practical terms, what is the difference of finding a plane from points and from polygon?
+// The docs don't clarify.
+// These functions should be consistent if they are both necessary. The docs might reflect their distinction.
 
 // Function: plane_normal()
 // Usage:
@@ -933,8 +969,8 @@ function plane_transform(plane) =
 // Usage:
 //   projection_on_plane(points);
 // Description:
-//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the 3D orthogonal 
-//   projection of the points on the plane.
+//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the projection
+//   of the points on the plane.
 // Arguments:
 //   plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
 //   points = List of points to project
@@ -1006,6 +1042,23 @@ function closest_point_on_plane(plane, point) =
 // Returns [POINT, U] if line intersects plane at one point.
 // Returns [LINE, undef] if the line is on the plane.
 // Returns undef if line is parallel to, but not on the given plane.
+function _general_plane_line_intersection(plane, line, eps=EPSILON) =
+    let(
+        p0 = line[0],
+        p1 = line[1],
+        n = plane_normal(plane),
+        u = p1 - p0,
+        d = n * u
+    ) abs(d)<eps? (
+        points_on_plane(p0,plane,eps)? [line,undef] :  // Line on plane
+        undef  // Line parallel to plane
+    ) : let(
+        v0 = closest_point_on_plane(plane, [0,0,0]),
+        w = p0 - v0,
+        s1 = (-n * w) / d,
+        pt = s1 * u + p0
+    ) [pt, s1];
+
 function _general_plane_line_intersection(plane, line, eps=EPSILON) =
     let( a = plane*[each line[0],-1],
          b = plane*[each(line[1]-line[0]),-1] )
@@ -1013,7 +1066,6 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
     ? points_on_plane(line[0],plane,eps)? [line,undef]: undef
     : [ line[0]+a/b*(line[1]-line[0]), a/b ];
 
-
 // Function: plane_line_angle()
 // Usage: plane_line_angle(plane,line)
 // Description:
@@ -1085,7 +1137,7 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
     )
     indices==[] ? undef :
     let(
-        indices = sort(indices),
+        indices = sort(indices), // why sorting?
         p1 = poly[indices[0]],
         p2 = poly[indices[1]],
         p3 = poly[indices[2]],
@@ -1149,7 +1201,7 @@ function plane_intersection(plane1,plane2,plane3) =
 
 // Function: coplanar()
 // Usage:
-//   coplanar(points,<eps>);
+//   coplanar(points,eps);
 // Description:
 //   Returns true if the given 3D points are non-collinear and are on a plane.
 // Arguments:
@@ -1168,7 +1220,7 @@ function coplanar(points, eps=EPSILON) =
     
 // Function: points_on_plane()
 // Usage:
-//   points_on_plane(points, plane, <eps>);
+//   points_on_plane(points, plane, eps);
 // Description:
 //   Returns true if the given 3D points are on the given plane.
 // Arguments:
@@ -1204,7 +1256,7 @@ function in_front_of_plane(plane, point) =
 
 // Function: find_circle_2tangents()
 // Usage:
-//   find_circle_2tangents(pt1, pt2, pt3, r|d, <tangents>);
+//   find_circle_2tangents(pt1, pt2, pt3, r|d, [tangents]);
 // Description:
 //   Given a pair of rays with a common origin, and a known circle radius/diameter, finds
 //   the centerpoint for the circle of that size that touches both rays tangentally.
@@ -1273,8 +1325,7 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 
 // Function: find_circle_3points()
 // Usage:
-//   find_circle_3points(pt1, pt2, pt3);
-//   find_circle_3points([pt1, pt2, pt3]);
+//   find_circle_3points(pt1, [pt2, pt3]);
 // Description:
 //   Returns the [CENTERPOINT, RADIUS, NORMAL] of the circle that passes through three non-collinear
 //   points where NORMAL is the normal vector of the plane that the circle is on (UP or DOWN if the points are 2D).
@@ -1294,6 +1345,40 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 //   translate(circ[0]) color("green") stroke(circle(r=circ[1]),closed=true,$fn=72);
 //   translate(circ[0]) color("red") circle(d=3, $fn=12);
 //   move_copies(pts) color("blue") circle(d=3, $fn=12);
+function find_circle_3points(pt1, pt2, pt3) =
+    (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
+    ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
+    :   assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) 
+                && max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
+                "Invalid point(s)." )
+        collinear(pt1,pt2,pt3)? [undef,undef,undef] :
+        let(
+            v1 = pt1-pt2,
+            v2 = pt3-pt2,
+            n = vector_axis(v1,v2),
+            n2 = n.z<0? -n : n
+        ) len(pt1)+len(pt2)+len(pt3)>6? (
+            let(
+                a = project_plane(pt1, pt1, pt2, pt3),
+                b = project_plane(pt2, pt1, pt2, pt3),
+                c = project_plane(pt3, pt1, pt2, pt3),
+                res = find_circle_3points(a, b, c)
+            ) res[0]==undef? [undef,undef,undef] : let(
+                cp = lift_plane(res[0], pt1, pt2, pt3),
+                r = norm(pt2-cp)
+            ) [cp, r, n2]
+        ) : let(
+            mp1 = pt2 + v1/2,
+            mp2 = pt2 + v2/2,
+            mpv1 = rot(90, v=n, p=v1),
+            mpv2 = rot(90, v=n, p=v2),
+            l1 = [mp1, mp1+mpv1],
+            l2 = [mp2, mp2+mpv2],
+            isect = line_intersection(l1,l2)
+        ) is_undef(isect)? [undef,undef,undef] : let(
+            r = norm(pt2-isect)
+        ) [isect, r, n2];
+        
 function find_circle_3points(pt1, pt2, pt3) =
     (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
     ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
@@ -1319,6 +1404,9 @@ function find_circle_3points(pt1, pt2, pt3) =
             )
         [ cp, r, n ];
      
+     
+
+
 
 // Function: circle_point_tangents()
 // Usage:
@@ -1354,6 +1442,7 @@ function circle_point_tangents(r, d, cp, pt) =
     ) [for (ang=angs) [ang, cp + r*[cos(ang),sin(ang)]]];
 
 
+
 // Function: circle_circle_tangents()
 // Usage: circle_circle_tangents(c1, r1|d1, c2, r2|d2)
 // Description:
@@ -1452,14 +1541,13 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
         []
     :   let(
             n = (pb-pa)/nrm,
-            distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)]   
+            distlist = [for(i=[0:len(points)-1]) _dist(points[i]-pa, n)]   
            )
         max(distlist)<eps
         ?  assert(!error, "Cannot find three noncollinear points in pointlist.")
            []
         :  [0,b,max_index(distlist)];    
 
-
 // Function: pointlist_bounds()
 // Usage:
 //   pointlist_bounds(pts);
@@ -1519,6 +1607,19 @@ function furthest_point(pt, points) =
 // Arguments:
 //   poly = polygon to compute the area of.
 //   signed = if true, a signed area is returned (default: false)
+function polygon_area(poly) =
+    assert(is_path(poly), "Invalid polygon." )
+    len(poly)<3? 0 :
+    len(poly[0])==2? 0.5*sum([for(i=[0:1:len(poly)-1]) det2(select(poly,i,i+1))]) :
+    let(
+        plane = plane_from_points(poly)
+    ) plane==undef? undef :
+    let(
+        n = unit(plane_normal(plane)),
+        total = sum([for (i=[0:1:len(poly)-1]) cross(poly[i], select(poly,i+1))]),
+        res = abs(total * n) / 2
+    ) res;
+    
 function polygon_area(poly, signed=false) =
     assert(is_path(poly), "Invalid polygon." )
     len(poly)<3 ? 0 :
@@ -1543,6 +1644,15 @@ function polygon_area(poly, signed=false) =
 // Example:
 //   spiral = [for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]];
 //   is_convex_polygon(spiral);  // Returns: false
+function is_convex_polygon(poly) =
+    assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
+    let(
+        l = len(poly),
+        c = [for (i=idx(poly)) cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l])]
+    )
+    len([for (x=c) if(x>0) 1])==0 ||
+    len([for (x=c) if(x<0) 1])==0;
+
 function is_convex_polygon(poly) =
     assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
     let( l = len(poly) )
@@ -1576,15 +1686,15 @@ function polygon_shift(poly, i) =
 // Usage:
 //   polygon_shift_to_closest_point(path, pt);
 // Description:
-//   Given a polygon `poly`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
-function polygon_shift_to_closest_point(poly, pt) =
+//   Given a polygon `path`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
+function polygon_shift_to_closest_point(path, pt) =
     assert(is_vector(pt), "Invalid point." )
-    assert(is_path(poly,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
+    assert(is_path(path,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
     let(
-        poly = cleanup_path(poly),
-        dists = [for (p=poly) norm(p-pt)],
+        path = cleanup_path(path),
+        dists = [for (p=path) norm(p-pt)],
         closest = min_index(dists)
-    ) select(poly,closest,closest+len(poly)-1);
+    ) select(path,closest,closest+len(path)-1);
 
 
 // Function: reindex_polygon()
@@ -1616,6 +1726,33 @@ function polygon_shift_to_closest_point(poly, pt) =
 //   move_copies(concat(circ,pent)) circle(r=.1,$fn=32);
 //   color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
 //   color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
+function reindex_polygon(reference, poly, return_error=false) = 
+    assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
+           "Invalid polygon(s) or incompatible dimensions. " )
+    assert(len(reference)==len(poly), "The polygons must have the same length.")
+    let(
+        dim = len(reference[0]),
+        N = len(reference),
+        fixpoly = dim != 2? poly :
+            polygon_is_clockwise(reference)? clockwise_polygon(poly) :
+            ccw_polygon(poly),
+        dist = [
+            // Matrix of all pairwise distances
+            for (p1=reference) [
+                for (p2=fixpoly) norm(p1-p2)
+            ]
+        ],
+        // Compute the sum of all distance pairs for a each shift
+        sums = [
+            for(shift=[0:1:N-1]) sum([
+                for(i=[0:1:N-1]) dist[i][(i+shift)%N]
+            ])
+        ],
+        optimal_poly = polygon_shift(fixpoly,min_index(sums))
+    )
+    return_error? [optimal_poly, min(sums)] :
+    optimal_poly;
+
 function reindex_polygon(reference, poly, return_error=false) = 
     assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
            "Invalid polygon(s) or incompatible dimensions. " )
@@ -1637,9 +1774,10 @@ function reindex_polygon(reference, poly, return_error=false) =
     optimal_poly;
     
 
+
 // Function: align_polygon()
 // Usage:
-//   newpoly = align_polygon(reference, poly, angles, <cp>);
+//   newpoly = align_polygon(reference, poly, angles, [cp]);
 // Description:
 //   Tries the list or range of angles to find a rotation of the specified 2D polygon that best aligns
 //   with the reference 2D polygon.  For each angle, the polygon is reindexed, which is a costly operation
@@ -1681,6 +1819,26 @@ function align_polygon(reference, poly, angles, cp) =
 //   Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid.
 //   Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid.
 //   If the polygon is self-intersecting, the results are undefined.
+function centroid(poly) =
+    assert( is_path(poly), "The input must be a 2D or 3D polygon." )
+    len(poly[0])==2
+    ? sum([
+            for(i=[0:len(poly)-1])
+            let(segment=select(poly,i,i+1))
+            det2(segment)*sum(segment)
+          ]) / 6 / polygon_area(poly)
+    : let( plane = plane_from_points(poly, fast=true) )
+      assert( !is_undef(plane), "The polygon must be planar." )
+      let(
+        n = plane_normal(plane),
+        p1 = vector_angle(n,UP)>15? vector_axis(n,UP) : vector_axis(n,RIGHT),
+        p2 = vector_axis(n,p1),
+        cp = mean(poly),
+        proj = project_plane(poly,cp,cp+p1,cp+p2),
+        cxy = centroid(proj)
+         ) 
+      lift_plane(cxy,cp,cp+p1,cp+p2);
+
 function centroid(poly) =
     assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
     len(poly[0])==2
@@ -1708,11 +1866,10 @@ function centroid(poly) =
 
 // Function: point_in_polygon()
 // Usage:
-//   point_in_polygon(point, poly, <eps>)
+//   point_in_polygon(point, path, [eps])
 // Description:
 //   This function tests whether the given 2D point is inside, outside or on the boundary of
-//   the specified 2D polygon using either the Nonzero Winding rule or the Even-Odd rule.
-//   See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Even–odd_rule.
+//   the specified 2D polygon using the Winding Number method.
 //   The polygon is given as a list of 2D points, not including the repeated end point.
 //   Returns -1 if the point is outside the polyon.
 //   Returns 0 if the point is on the boundary.
@@ -1722,81 +1879,75 @@ function centroid(poly) =
 //   Rounding error may give mixed results for points on or near the boundary.
 // Arguments:
 //   point = The 2D point to check position of.
-//   poly = The list of 2D path points forming the perimeter of the polygon.
-//   nonzero = The rule to use: true for "Nonzero" rule and false for "Even-Odd" (Default: true )
+//   path = The list of 2D path points forming the perimeter of the polygon.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
-function point_in_polygon(point, poly, eps=EPSILON, nonzero=true) =
-    // Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
-    assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,
+function point_in_polygon(point, path, eps=EPSILON) =
+    // Original algorithm from http://geomalgorithms.com/a03-_inclusion.html
+    assert( is_vector(point,2) && is_path(path,dim=2) && len(path)>2,
             "The point and polygon should be in 2D. The polygon should have more that 2 points." )
     assert( is_finite(eps) && eps>=0, "Invalid tolerance." )
     // Does the point lie on any edges?  If so return 0.
     let(
-        on_brd = [for(i=[0:1:len(poly)-1]) 
-                    let( seg = select(poly,i,i+1) ) 
-                    if( !approx(seg[0],seg[1],eps=EPSILON) ) 
+        on_brd = [for(i=[0:1:len(path)-1]) 
+                    let( seg = select(path,i,i+1) ) 
+                    if( !approx(seg[0],seg[1],eps=eps) ) 
                         point_on_segment2d(point, seg, eps=eps)? 1:0 ]
         )
-    sum(on_brd) > 0
-    ? 0 
-    :   nonzero
-        ?    // Compute winding number and return 1 for interior, -1 for exterior
-            let(
-                windchk = [for(i=[0:1:len(poly)-1]) 
-                            let(seg=select(poly,i,i+1)) 
-                            if(!approx(seg[0],seg[1],eps=eps)) 
-                                _point_above_below_segment(point, seg)
-                          ]
-                )
-            sum(windchk) != 0 ? 1 : -1
-        :   // or compute the crossings with the ray [point, point+[1,0]]
-            let( 
-              n  = len(poly),
-              cross = 
-                [for(i=[0:n-1])
-                    let( 
-                      p0 = poly[i]-point, 
-                      p1 = poly[(i+1)%n]-point
-                      )
-                    if( ( (p1.y>eps && p0.y<=0) || (p1.y<=0 && p0.y>eps) )
-                        &&  0 < p0.x - p0.y *(p1.x - p0.x)/(p1.y - p0.y) )
-                    1
-                ]
-            )
-            2*(len(cross)%2)-1;;
+    sum(on_brd) > 0? 0 :
+    // Otherwise compute winding number and return 1 for interior, -1 for exterior
+    let(
+        windchk = [for(i=[0:1:len(path)-1]) 
+                    let(seg=select(path,i,i+1)) 
+                    if(!approx(seg[0],seg[1],eps=eps)) 
+                        _point_above_below_segment(point, seg)
+                  ]
+        )
+    sum(windchk) != 0 ? 1 : -1;
 
+//**
+// this function should be optimized avoiding the call of other functions
 
 // Function: polygon_is_clockwise()
 // Usage:
-//   polygon_is_clockwise(poly);
+//   polygon_is_clockwise(path);
 // Description:
 //   Return true if the given 2D simple polygon is in clockwise order, false otherwise.
 //   Results for complex (self-intersecting) polygon are indeterminate.
 // Arguments:
-//   poly = The list of 2D path points for the perimeter of the polygon.
-function polygon_is_clockwise(poly) =
-    assert(is_path(poly,dim=2), "Input should be a 2d path")
-    polygon_area(poly, signed=true)<0;
+//   path = The list of 2D path points for the perimeter of the polygon.
+function polygon_is_clockwise(path) =
+    assert(is_path(path,dim=2), "Input should be a 2d polygon")
+    let(
+        minx = min(subindex(path,0)),
+        lowind = search(minx, path, 0, 0),
+        lowpts = select(path, lowind),
+        miny = min(subindex(lowpts, 1)),
+        extreme_sub = search(miny, lowpts, 1, 1)[0],
+        extreme = select(lowind,extreme_sub)
+    ) det2([select(path,extreme+1)-path[extreme], select(path, extreme-1)-path[extreme]])<0;
 
+function polygon_is_clockwise(path) =
+    assert(is_path(path,dim=2), "Input should be a 2d path")
+    polygon_area(path, signed=true)<0;
 
 // Function: clockwise_polygon()
 // Usage:
-//   clockwise_polygon(poly);
+//   clockwise_polygon(path);
 // Description:
 //   Given a 2D polygon path, returns the clockwise winding version of that path.
-function clockwise_polygon(poly) =
-    assert(is_path(poly,dim=2), "Input should be a 2d polygon")
-    polygon_area(poly, signed=true)<0 ? poly : reverse_polygon(poly);
+function clockwise_polygon(path) =
+    assert(is_path(path,dim=2), "Input should be a 2d polygon")
+    polygon_area(path, signed=true)<0 ? path : reverse_polygon(path);
 
 
 // Function: ccw_polygon()
 // Usage:
-//   ccw_polygon(poly);
+//   ccw_polygon(path);
 // Description:
-//   Given a 2D polygon poly, returns the counter-clockwise winding version of that poly.
-function ccw_polygon(poly) =
-    assert(is_path(poly,dim=2), "Input should be a 2d polygon")
-    polygon_area(poly, signed=true)<0 ? reverse_polygon(poly) : poly;
+//   Given a 2D polygon path, returns the counter-clockwise winding version of that path.
+function ccw_polygon(path) =
+    assert(is_path(path,dim=2), "Input should be a 2d polygon")
+    polygon_area(path, signed=true)<0 ? reverse_polygon(path) : path;
 
 
 // Function: reverse_polygon()
@@ -1818,7 +1969,7 @@ function reverse_polygon(poly) =
 function polygon_normal(poly) =
     assert(is_path(poly,dim=3), "Invalid 3D polygon." )
     let(
-        poly = cleanup_path(poly),
+        poly = path3d(cleanup_path(poly)),
         p0 = poly[0],
         n = sum([
             for (i=[1:1:len(poly)-2])
@@ -1934,6 +2085,17 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
         ], xs, _i=_i+1
     );
     
+//***
+// all the functions split_polygons_at_ may generate non simple polygons even from simple polygon inputs:
+//     split_polygons_at_each_y([[[-1,1,0],[0,0,0],[1,1,0],[1,-1,0],[-1,-1,0]]],[0])
+// produces:
+//   [  [[0, 0, 0], [1, 0, 0], [1, -1, 0], [-1, -1, 0], [-1, 0, 0]]
+//      [[-1, 1, 0], [0, 0, 0], [1, 1, 0], [1, 0, 0], [-1, 0, 0]] ]
+// and the second polygon is self-intersecting
+// besides, it fails in some simple cases as triangles:
+//   split_polygons_at_each_y([ [-1,-1,0],[1,-1,0],[0,1,0]],[0])==[]
+// this last failure may be fatal for vnf_bend
+
 
 // Function: split_polygons_at_each_y()
 // Usage:
@@ -1944,9 +2106,9 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
 //   polys = A list of 3D polygons to split.
 //   ys = A list of scalar Y values to split at.
 function split_polygons_at_each_y(polys, ys, _i=0) =
-//    assert( is_consistent(polys) && is_path(polys[0],dim=3) , // not all polygons should have the same length!!!
-  //          "The input list should contains only 3D polygons." )
-    assert( is_finite(ys) || is_vector(ys), "The split value list should contain only numbers." )  //***
+    assert( is_consistent(polys) && is_path(poly[0],dim=3) ,
+            "The input list should contains only 3D polygons." )
+    assert( is_finite(ys), "The split value list should contain only numbers." )  
     _i>=len(ys)? polys :
     split_polygons_at_each_y(
         [
@@ -1977,4 +2139,5 @@ function split_polygons_at_each_z(polys, zs, _i=0) =
     );
 
 
+
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad
index 0950e1c..ceeb905 100644
--- a/tests/test_geometry.scad
+++ b/tests/test_geometry.scad
@@ -98,8 +98,6 @@ function standardize(v) =
     v==[]? [] :
     sign([for(vi=v) if( ! approx(vi,0)) vi,0 ][0])*v;
 
-module assert_std(vc,ve) { assert(standardize(vc)==standardize(ve)); }
-
 module test_points_on_plane() {
     pts     = [for(i=[0:40]) rands(-1,1,3) ];
     dir     = rands(-10,10,3);
@@ -489,47 +487,48 @@ module test_triangle_area() {
 
 
 module test_plane3pt() {
-    assert_std(plane3pt([0,0,20], [0,10,10], [0,0,0]), [1,0,0,0]);
-    assert_std(plane3pt([2,0,20], [2,10,10], [2,0,0]), [1,0,0,2]);
-    assert_std(plane3pt([0,0,0], [10,0,10], [0,0,20]), [0,1,0,0]);
-    assert_std(plane3pt([0,2,0], [10,2,10], [0,2,20]), [0,1,0,2]);
-    assert_std(plane3pt([0,0,0], [10,10,0], [20,0,0]), [0,0,1,0]);
-    assert_std(plane3pt([0,0,2], [10,10,2], [20,0,2]), [0,0,1,2]);
+    assert(plane3pt([0,0,20], [0,10,10], [0,0,0]) == [1,0,0,0]);
+    assert(plane3pt([2,0,20], [2,10,10], [2,0,0]) == [1,0,0,2]);
+    assert(plane3pt([0,0,0], [10,0,10], [0,0,20]) == [0,1,0,0]);
+    assert(plane3pt([0,2,0], [10,2,10], [0,2,20]) == [0,1,0,2]);
+    assert(plane3pt([0,0,0], [10,10,0], [20,0,0]) == [0,0,1,0]);
+    assert(plane3pt([0,0,2], [10,10,2], [20,0,2]) == [0,0,1,2]);
 }
 *test_plane3pt();
 
 module test_plane3pt_indexed() {
     pts = [ [0,0,0], [10,0,0], [0,10,0], [0,0,10] ];
     s13 = sqrt(1/3);
-    assert_std(plane3pt_indexed(pts, 0,3,2), [1,0,0,0]);
-    assert_std(plane3pt_indexed(pts, 0,2,3), [-1,0,0,0]);
-    assert_std(plane3pt_indexed(pts, 0,1,3), [0,1,0,0]);
-    assert_std(plane3pt_indexed(pts, 0,3,1), [0,-1,0,0]);
-    assert_std(plane3pt_indexed(pts, 0,2,1), [0,0,1,0]);
+    assert(plane3pt_indexed(pts, 0,3,2) == [1,0,0,0]);
+    assert(plane3pt_indexed(pts, 0,2,3) == [-1,0,0,0]);
+    assert(plane3pt_indexed(pts, 0,1,3) == [0,1,0,0]);
+    assert(plane3pt_indexed(pts, 0,3,1) == [0,-1,0,0]);
+    assert(plane3pt_indexed(pts, 0,2,1) == [0,0,1,0]);
     assert_approx(plane3pt_indexed(pts, 0,1,2), [0,0,-1,0]);
     assert_approx(plane3pt_indexed(pts, 3,2,1), [s13,s13,s13,10*s13]);
     assert_approx(plane3pt_indexed(pts, 1,2,3), [-s13,-s13,-s13,-10*s13]);
 }
 *test_plane3pt_indexed();
 
+
 module test_plane_from_points() {
-    assert_std(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]), [1,0,0,0]);
-    assert_std(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]), [1,0,0,2]);
-    assert_std(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]), [0,1,0,0]);
-    assert_std(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]), [0,1,0,2]);
-    assert_std(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]), [0,0,1,0]);
-    assert_std(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]), [0,0,1,2]);
+    assert(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]) == [1,0,0,0]);
+    assert(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]) == [1,0,0,2]);
+    assert(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]) == [0,1,0,0]);
+    assert(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]) == [0,1,0,2]);
+    assert(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]) == [0,0,1,0]);
+    assert(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]) == [0,0,1,2]);
 }
 *test_plane_from_points();
 
 
 module test_plane_normal() {
-    assert_std(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])), [1,0,0]);
-    assert_std(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])), [1,0,0]);
-    assert_std(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])), [0,1,0]);
-    assert_std(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])), [0,1,0]);
-    assert_std(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])), [0,0,1]);
-    assert_std(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])), [0,0,1]);
+    assert(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])) == [1,0,0]);
+    assert(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])) == [1,0,0]);
+    assert(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])) == [0,1,0]);
+    assert(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])) == [0,1,0]);
+    assert(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])) == [0,0,1]);
+    assert(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])) == [0,0,1]);
 }
 *test_plane_normal();
 
@@ -700,22 +699,16 @@ module test_simplify_path_indexed() {
 
 module test_point_in_polygon() {
     poly = [for (a=[0:30:359]) 10*[cos(a),sin(a)]];
-    poly2 = [ [-3,-3],[2,-3],[2,1],[-1,1],[-1,-1],[1,-1],[1,2],[-3,2] ];
     assert(point_in_polygon([0,0], poly) == 1);
     assert(point_in_polygon([20,0], poly) == -1);
-    assert(point_in_polygon([20,0], poly,EPSILON,nonzero=false) == -1);
     assert(point_in_polygon([5,5], poly) == 1);
     assert(point_in_polygon([-5,5], poly) == 1);
     assert(point_in_polygon([-5,-5], poly) == 1);
     assert(point_in_polygon([5,-5], poly) == 1);
-    assert(point_in_polygon([5,-5], poly,EPSILON,nonzero=false) == 1);
     assert(point_in_polygon([-10,-10], poly) == -1);
     assert(point_in_polygon([10,0], poly) == 0);
     assert(point_in_polygon([0,10], poly) == 0);
     assert(point_in_polygon([0,-10], poly) == 0);
-    assert(point_in_polygon([0,-10], poly,EPSILON,nonzero=false) == 0);
-    assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=true) == 1);
-    assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=false) == -1);
 }
 *test_point_in_polygon();
 

From 99e815f077dcd374d3365bb20ed12791dce06e2b Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Thu, 20 Aug 2020 22:36:26 +0100
Subject: [PATCH 14/18] In observance of owner's last review

Eliminate double definitions.
Eliminate unneeded comments.

In common.scad redefine num_defined(), all_defined() and get_radius().

In geometry.scad:
- change name _dist to _dist2line
- simplify _point_above_below_segment() and triangle_area()
- change some arg names for uniformity (path>>poly)
- change point_in_polygon() to accept the Even-odd rule as alternative
- and other minor edits

Update tests_geometry to the new funcionalities.
---
 common.scad              |  67 +++-----
 geometry.scad            | 341 ++++++++++-----------------------------
 tests/test_geometry.scad |  55 ++++---
 3 files changed, 145 insertions(+), 318 deletions(-)

diff --git a/common.scad b/common.scad
index 51d8363..db4f3bb 100644
--- a/common.scad
+++ b/common.scad
@@ -129,11 +129,6 @@ function is_list_of(list,pattern) =
     is_list(list) &&
     []==[for(entry=0*list) if (entry != pattern) entry];
 
-function _list_pattern(list) =
-  is_list(list) ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0]
-                : 0;
-
-
 
 // Function: is_consistent()
 // Usage:
@@ -198,11 +193,11 @@ function first_defined(v,recursive=false,_i=0) =
             is_undef(first_defined(v[_i],recursive=recursive))
         )
     )? first_defined(v,recursive=recursive,_i=_i+1) : v[_i];
-
+    
 
 // Function: one_defined()
 // Usage:
-//   one_defined(vars, names, [required])
+//   one_defined(vars, names, <required>)
 // Description:
 //   Examines the input list `vars` and returns the entry which is not `undef`.  If more
 //   than one entry is `undef` then issues an assertion specifying "Must define exactly one of" followed
@@ -221,8 +216,7 @@ function one_defined(vars, names, required=true) =
 
 // Function: num_defined()
 // Description: Counts how many items in list `v` are not `undef`.
-function num_defined(v,_i=0,_cnt=0) = _i>=len(v)? _cnt : num_defined(v,_i+1,_cnt+(is_undef(v[_i])? 0 : 1));
-
+function num_defined(v) = len([for(vi=v) if(!is_undef(vi)) 1]);
 
 // Function: any_defined()
 // Description:
@@ -239,8 +233,8 @@ function any_defined(v,recursive=false) = first_defined(v,recursive=recursive) !
 // Arguments:
 //   v = The list whose items are being checked.
 //   recursive = If true, any sublists are evaluated recursively.
-function all_defined(v,recursive=false) = max([for (x=v) is_undef(x)||(recursive&&is_list(x)&&!all_defined(x))? 1 : 0])==0;
-
+function all_defined(v,recursive=false) = 
+    []==[for (x=v) if(is_undef(x)||(recursive && is_list(x) && !all_defined(x,recursive))) 0 ];
 
 
 
@@ -249,7 +243,7 @@ function all_defined(v,recursive=false) = max([for (x=v) is_undef(x)||(recursive
 
 // Function: get_anchor()
 // Usage:
-//   get_anchor(anchor,center,[uncentered],[dflt]);
+//   get_anchor(anchor,center,<uncentered>,<dflt>);
 // 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.
@@ -270,7 +264,7 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 
 // Function: get_radius()
 // Usage:
-//   get_radius([r1], [r2], [r], [d1], [d2], [d], [dflt]);
+//   get_radius(<r1>, <r2>, <r>, <d1>, <d2>, <d>, <dflt>);
 // Description:
 //   Given various radii and diameters, returns the most specific radius.
 //   If a diameter is most specific, returns half its value, giving the radius.
@@ -288,34 +282,23 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 //   r = Most general radius.
 //   d = Most general diameter.
 //   dflt = Value to return if all other values given are `undef`.
-function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = (
-    !is_undef(r1)
-		? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
-				assert(is_finite(r1), "Invalid radius r1." )
-				r1 
-    : !is_undef(r2)
-			? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
-				assert(is_finite(r2), "Invalid radius r2." )
-				r2
-		: !is_undef(d1)
-		  ? assert(is_finite(d1), "Invalid diameter d1." )
-				d1/2
-		: !is_undef(d2)
-			? assert(is_finite(d2), "Invalid diameter d2." )
-				d2/2
-		: !is_undef(r)
-			? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
-				assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
-				r 
-		: !is_undef(d)
-			? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." )
-			d/2
-		: dflt
-);
+function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = 
+    assert(num_defined([r1,d1,r2,d2])<2, "Conflicting or redundant radius/diameter arguments given.")
+    !is_undef(r1) ?   assert(is_finite(r1), "Invalid radius r1." ) r1 
+    : !is_undef(r2) ? assert(is_finite(r2), "Invalid radius r2." ) r2
+    : !is_undef(d1) ? assert(is_finite(d1), "Invalid diameter d1." ) d1/2
+    : !is_undef(d2) ? assert(is_finite(d2), "Invalid diameter d2." ) d2/2
+    : !is_undef(r)
+      ? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
+        assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
+        r 
+    : !is_undef(d) ? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." ) d/2
+    : dflt;
+
 
 // Function: get_height()
 // Usage:
-//   get_height([h],[l],[height],[dflt])
+//   get_height(<h>,<l>,<height>,<dflt>)
 // Description:
 //   Given several different parameters for height check that height is not multiply defined
 //   and return a single value.  If the three values `l`, `h`, and `height` are all undefined
@@ -332,7 +315,7 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) =
 
 // Function: scalar_vec3()
 // Usage:
-//   scalar_vec3(v, [dflt]);
+//   scalar_vec3(v, <dflt>);
 // Description:
 //   If `v` is a scalar, and `dflt==undef`, returns `[v, v, v]`.
 //   If `v` is a scalar, and `dflt!=undef`, returns `[v, dflt, dflt]`.
@@ -384,7 +367,7 @@ function _valstr(x) =
 
 // Module: assert_approx()
 // Usage:
-//   assert_approx(got, expected, [info]);
+//   assert_approx(got, expected, <info>);
 // Description:
 //   Tests if the value gotten is what was expected.  If not, then
 //   the expected and received values are printed to the console and
@@ -411,7 +394,7 @@ module assert_approx(got, expected, info) {
 
 // Module: assert_equal()
 // Usage:
-//   assert_equal(got, expected, [info]);
+//   assert_equal(got, expected, <info>);
 // Description:
 //   Tests if the value gotten is what was expected.  If not, then
 //   the expected and received values are printed to the console and
@@ -438,7 +421,7 @@ module assert_equal(got, expected, info) {
 
 // Module: shape_compare()
 // Usage:
-//   shape_compare([eps]) {test_shape(); expected_shape();}
+//   shape_compare(<eps>) {test_shape(); expected_shape();}
 // Description:
 //   Compares two child shapes, returning empty geometry if they are very nearly the same shape and size.
 //   Returns the differential geometry if they are not nearly the same shape and size.
diff --git a/geometry.scad b/geometry.scad
index eff67bc..dc86187 100644
--- a/geometry.scad
+++ b/geometry.scad
@@ -23,36 +23,25 @@
 function point_on_segment2d(point, edge, eps=EPSILON) =
     assert( is_vector(point,2), "Invalid point." )
     assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
-    assert( _valid_line(edge,eps=eps), "Invalid segment." )
-    approx(point,edge[0],eps=eps) 
-    || approx(point,edge[1],eps=eps) // The point is an endpoint
-    || sign(edge[0].x-point.x)==sign(point.x-edge[1].x)  // point is in between the
-    || ( sign(edge[0].y-point.y)==sign(point.y-edge[1].y)  // edge endpoints
-        && approx(point_left_of_line2d(point, edge),0,eps=eps) );  // and on the line defined by edge
-
-function point_on_segment2d(point, edge, eps=EPSILON) =
-    assert( is_vector(point,2), "Invalid point." )
-    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
-    assert( _valid_line(edge,eps=eps), "Invalid segment." )
+    assert( _valid_line(edge,2,eps=eps), "Invalid segment." )
     let( dp = point-edge[0],
          de = edge[1]-edge[0],
          ne = norm(de) ) 
     ( dp*de >= -eps*ne ) 
-    && ( (dp-de)*de <= eps*ne )              // point projects on the segment
-    && _dist(point-edge[0],unit(de))<eps;   // point is on the line 
+    && ( (dp-de)*de <= eps*ne )                  // point projects on the segment
+    && _dist2line(point-edge[0],unit(de))<eps;   // point is on the line 
     
     
 //Internal - distance from point `d` to the line passing through the origin with unit direction n
-//_dist works for any dimension
-function _dist(d,n) = norm(d-(d * n) * n);
+//_dist2line works for any dimension
+function _dist2line(d,n) = norm(d-(d * n) * n);
 
 // Internal non-exposed function.
 function _point_above_below_segment(point, edge) =
-    edge[0].y <= point.y? (
-        (edge[1].y > point.y && point_left_of_line2d(point, edge) > 0)? 1 : 0
-    ) : (
-        (edge[1].y <= point.y && point_left_of_line2d(point, edge) < 0)? -1 : 0
-    );
+    let( edge = edge - [point, point] )
+    edge[0].y <= 0 
+    ?   (edge[1].y >  0 && cross(edge[0], edge[1]-edge[0]) > 0) ?  1 : 0
+    :   (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0 ;
 
 //Internal
 function _valid_line(line,dim,eps=EPSILON) = 
@@ -98,10 +87,6 @@ function collinear(a, b, c, eps=EPSILON) =
     : noncollinear_triple(points,error=false,eps=eps)==[];
  
 
-//*** valid for any dimension
-
- 
-
 // Function: distance_from_line()
 // Usage:
 //   distance_from_line(line, pt);
@@ -115,7 +100,7 @@ function collinear(a, b, c, eps=EPSILON) =
 function distance_from_line(line, pt) =
     assert( _valid_line(line) && is_vector(pt,len(line[0])), 
             "Invalid line, invalid point or incompatible dimensions." )
-    _dist(pt-line[0],unit(line[1]-line[0]));
+    _dist2line(pt-line[0],unit(line[1]-line[0]));
     
     
 // Function: line_normal()
@@ -330,17 +315,6 @@ function segment_intersection(s1,s2,eps=EPSILON) =
 //   stroke(line, endcaps="arrow2");
 //   color("blue") translate(pt) sphere(r=1,$fn=12);
 //   color("red") translate(p2) sphere(r=1,$fn=12);
-function line_closest_point(line,pt) =
-    assert(is_path(line)&&len(line)==2)
-    assert(same_shape(pt,line[0]))
-    assert(!approx(line[0],line[1]))
-    let(
-        seglen = norm(line[1]-line[0]),
-        segvec = (line[1]-line[0])/seglen,
-        projection = (pt-line[0]) * segvec
-    )
-    line[0] + projection*segvec;
-
 function line_closest_point(line,pt) =
     assert(_valid_line(line), "Invalid line." )
     assert( is_vector(pt,len(line[0])), "Invalid point or incompatible dimensions." )
@@ -774,14 +748,10 @@ function adj_opp_to_ang(adj,opp) =
 //   triangle_area([0,0], [5,10], [10,0]);  // Returns -50
 //   triangle_area([10,0], [5,10], [0,0]);  // Returns 50
 function triangle_area(a,b,c) = 
-    assert( is_path([a,b,c]),
-            "Invalid points or incompatible dimensions." )    
-    len(a)==3 ? 0.5*norm(cross(c-a,c-b)) 
-    : (
-        a.x * (b.y - c.y) +
-        b.x * (c.y - a.y) +
-        c.x * (a.y - b.y)
-    ) / 2;
+    assert( is_path([a,b,c]), "Invalid points or incompatible dimensions." )    
+    len(a)==3 
+    ? 0.5*norm(cross(c-a,c-b)) 
+    : 0.5*cross(c-a,c-b);
 
 
 
@@ -851,7 +821,7 @@ function plane_from_normal(normal, pt=[0,0,0]) =
 
 // Function: plane_from_points()
 // Usage:
-//   plane_from_points(points, [fast], [eps]);
+//   plane_from_points(points, <fast>, <eps>);
 // Description:
 //   Given a list of 3 or more coplanar 3D points, returns the coefficients of the cartesian equation of a plane,
 //   that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane.
@@ -876,7 +846,6 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
     )
     indices==[] ? undef :
     let(
-        indices = sort(indices), // why sorting?
         p1 = points[indices[0]],
         p2 = points[indices[1]],
         p3 = points[indices[2]],
@@ -913,11 +882,6 @@ function plane_from_polygon(poly, fast=false, eps=EPSILON) =
     ) 
     fast? plane: coplanar(poly,eps=eps)? plane: [];
 
-//***
-// I don't see why this function uses a criterium different from plane_from_points.
-// In practical terms, what is the difference of finding a plane from points and from polygon?
-// The docs don't clarify.
-// These functions should be consistent if they are both necessary. The docs might reflect their distinction.
 
 // Function: plane_normal()
 // Usage:
@@ -969,8 +933,8 @@ function plane_transform(plane) =
 // Usage:
 //   projection_on_plane(points);
 // Description:
-//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the projection
-//   of the points on the plane.
+//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the 3D orthogonal 
+//   projection of the points on the plane.
 // Arguments:
 //   plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
 //   points = List of points to project
@@ -1042,23 +1006,6 @@ function closest_point_on_plane(plane, point) =
 // Returns [POINT, U] if line intersects plane at one point.
 // Returns [LINE, undef] if the line is on the plane.
 // Returns undef if line is parallel to, but not on the given plane.
-function _general_plane_line_intersection(plane, line, eps=EPSILON) =
-    let(
-        p0 = line[0],
-        p1 = line[1],
-        n = plane_normal(plane),
-        u = p1 - p0,
-        d = n * u
-    ) abs(d)<eps? (
-        points_on_plane(p0,plane,eps)? [line,undef] :  // Line on plane
-        undef  // Line parallel to plane
-    ) : let(
-        v0 = closest_point_on_plane(plane, [0,0,0]),
-        w = p0 - v0,
-        s1 = (-n * w) / d,
-        pt = s1 * u + p0
-    ) [pt, s1];
-
 function _general_plane_line_intersection(plane, line, eps=EPSILON) =
     let( a = plane*[each line[0],-1],
          b = plane*[each(line[1]-line[0]),-1] )
@@ -1066,6 +1013,7 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
     ? points_on_plane(line[0],plane,eps)? [line,undef]: undef
     : [ line[0]+a/b*(line[1]-line[0]), a/b ];
 
+
 // Function: plane_line_angle()
 // Usage: plane_line_angle(plane,line)
 // Description:
@@ -1137,7 +1085,7 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
     )
     indices==[] ? undef :
     let(
-        indices = sort(indices), // why sorting?
+        indices = sort(indices),
         p1 = poly[indices[0]],
         p2 = poly[indices[1]],
         p3 = poly[indices[2]],
@@ -1201,7 +1149,7 @@ function plane_intersection(plane1,plane2,plane3) =
 
 // Function: coplanar()
 // Usage:
-//   coplanar(points,eps);
+//   coplanar(points,<eps>);
 // Description:
 //   Returns true if the given 3D points are non-collinear and are on a plane.
 // Arguments:
@@ -1220,7 +1168,7 @@ function coplanar(points, eps=EPSILON) =
     
 // Function: points_on_plane()
 // Usage:
-//   points_on_plane(points, plane, eps);
+//   points_on_plane(points, plane, <eps>);
 // Description:
 //   Returns true if the given 3D points are on the given plane.
 // Arguments:
@@ -1256,7 +1204,7 @@ function in_front_of_plane(plane, point) =
 
 // Function: find_circle_2tangents()
 // Usage:
-//   find_circle_2tangents(pt1, pt2, pt3, r|d, [tangents]);
+//   find_circle_2tangents(pt1, pt2, pt3, r|d, <tangents>);
 // Description:
 //   Given a pair of rays with a common origin, and a known circle radius/diameter, finds
 //   the centerpoint for the circle of that size that touches both rays tangentally.
@@ -1325,7 +1273,8 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 
 // Function: find_circle_3points()
 // Usage:
-//   find_circle_3points(pt1, [pt2, pt3]);
+//   find_circle_3points(pt1, pt2, pt3);
+//   find_circle_3points([pt1, pt2, pt3]);
 // Description:
 //   Returns the [CENTERPOINT, RADIUS, NORMAL] of the circle that passes through three non-collinear
 //   points where NORMAL is the normal vector of the plane that the circle is on (UP or DOWN if the points are 2D).
@@ -1345,40 +1294,6 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 //   translate(circ[0]) color("green") stroke(circle(r=circ[1]),closed=true,$fn=72);
 //   translate(circ[0]) color("red") circle(d=3, $fn=12);
 //   move_copies(pts) color("blue") circle(d=3, $fn=12);
-function find_circle_3points(pt1, pt2, pt3) =
-    (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
-    ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
-    :   assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) 
-                && max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
-                "Invalid point(s)." )
-        collinear(pt1,pt2,pt3)? [undef,undef,undef] :
-        let(
-            v1 = pt1-pt2,
-            v2 = pt3-pt2,
-            n = vector_axis(v1,v2),
-            n2 = n.z<0? -n : n
-        ) len(pt1)+len(pt2)+len(pt3)>6? (
-            let(
-                a = project_plane(pt1, pt1, pt2, pt3),
-                b = project_plane(pt2, pt1, pt2, pt3),
-                c = project_plane(pt3, pt1, pt2, pt3),
-                res = find_circle_3points(a, b, c)
-            ) res[0]==undef? [undef,undef,undef] : let(
-                cp = lift_plane(res[0], pt1, pt2, pt3),
-                r = norm(pt2-cp)
-            ) [cp, r, n2]
-        ) : let(
-            mp1 = pt2 + v1/2,
-            mp2 = pt2 + v2/2,
-            mpv1 = rot(90, v=n, p=v1),
-            mpv2 = rot(90, v=n, p=v2),
-            l1 = [mp1, mp1+mpv1],
-            l2 = [mp2, mp2+mpv2],
-            isect = line_intersection(l1,l2)
-        ) is_undef(isect)? [undef,undef,undef] : let(
-            r = norm(pt2-isect)
-        ) [isect, r, n2];
-        
 function find_circle_3points(pt1, pt2, pt3) =
     (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
     ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
@@ -1404,9 +1319,6 @@ function find_circle_3points(pt1, pt2, pt3) =
             )
         [ cp, r, n ];
      
-     
-
-
 
 // Function: circle_point_tangents()
 // Usage:
@@ -1442,7 +1354,6 @@ function circle_point_tangents(r, d, cp, pt) =
     ) [for (ang=angs) [ang, cp + r*[cos(ang),sin(ang)]]];
 
 
-
 // Function: circle_circle_tangents()
 // Usage: circle_circle_tangents(c1, r1|d1, c2, r2|d2)
 // Description:
@@ -1541,13 +1452,14 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
         []
     :   let(
             n = (pb-pa)/nrm,
-            distlist = [for(i=[0:len(points)-1]) _dist(points[i]-pa, n)]   
+            distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)]   
            )
         max(distlist)<eps
         ?  assert(!error, "Cannot find three noncollinear points in pointlist.")
            []
         :  [0,b,max_index(distlist)];    
 
+
 // Function: pointlist_bounds()
 // Usage:
 //   pointlist_bounds(pts);
@@ -1607,19 +1519,6 @@ function furthest_point(pt, points) =
 // Arguments:
 //   poly = polygon to compute the area of.
 //   signed = if true, a signed area is returned (default: false)
-function polygon_area(poly) =
-    assert(is_path(poly), "Invalid polygon." )
-    len(poly)<3? 0 :
-    len(poly[0])==2? 0.5*sum([for(i=[0:1:len(poly)-1]) det2(select(poly,i,i+1))]) :
-    let(
-        plane = plane_from_points(poly)
-    ) plane==undef? undef :
-    let(
-        n = unit(plane_normal(plane)),
-        total = sum([for (i=[0:1:len(poly)-1]) cross(poly[i], select(poly,i+1))]),
-        res = abs(total * n) / 2
-    ) res;
-    
 function polygon_area(poly, signed=false) =
     assert(is_path(poly), "Invalid polygon." )
     len(poly)<3 ? 0 :
@@ -1644,15 +1543,6 @@ function polygon_area(poly, signed=false) =
 // Example:
 //   spiral = [for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]];
 //   is_convex_polygon(spiral);  // Returns: false
-function is_convex_polygon(poly) =
-    assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
-    let(
-        l = len(poly),
-        c = [for (i=idx(poly)) cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l])]
-    )
-    len([for (x=c) if(x>0) 1])==0 ||
-    len([for (x=c) if(x<0) 1])==0;
-
 function is_convex_polygon(poly) =
     assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
     let( l = len(poly) )
@@ -1686,15 +1576,15 @@ function polygon_shift(poly, i) =
 // Usage:
 //   polygon_shift_to_closest_point(path, pt);
 // Description:
-//   Given a polygon `path`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
-function polygon_shift_to_closest_point(path, pt) =
+//   Given a polygon `poly`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
+function polygon_shift_to_closest_point(poly, pt) =
     assert(is_vector(pt), "Invalid point." )
-    assert(is_path(path,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
+    assert(is_path(poly,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
     let(
-        path = cleanup_path(path),
-        dists = [for (p=path) norm(p-pt)],
+        poly = cleanup_path(poly),
+        dists = [for (p=poly) norm(p-pt)],
         closest = min_index(dists)
-    ) select(path,closest,closest+len(path)-1);
+    ) select(poly,closest,closest+len(poly)-1);
 
 
 // Function: reindex_polygon()
@@ -1726,33 +1616,6 @@ function polygon_shift_to_closest_point(path, pt) =
 //   move_copies(concat(circ,pent)) circle(r=.1,$fn=32);
 //   color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
 //   color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
-function reindex_polygon(reference, poly, return_error=false) = 
-    assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
-           "Invalid polygon(s) or incompatible dimensions. " )
-    assert(len(reference)==len(poly), "The polygons must have the same length.")
-    let(
-        dim = len(reference[0]),
-        N = len(reference),
-        fixpoly = dim != 2? poly :
-            polygon_is_clockwise(reference)? clockwise_polygon(poly) :
-            ccw_polygon(poly),
-        dist = [
-            // Matrix of all pairwise distances
-            for (p1=reference) [
-                for (p2=fixpoly) norm(p1-p2)
-            ]
-        ],
-        // Compute the sum of all distance pairs for a each shift
-        sums = [
-            for(shift=[0:1:N-1]) sum([
-                for(i=[0:1:N-1]) dist[i][(i+shift)%N]
-            ])
-        ],
-        optimal_poly = polygon_shift(fixpoly,min_index(sums))
-    )
-    return_error? [optimal_poly, min(sums)] :
-    optimal_poly;
-
 function reindex_polygon(reference, poly, return_error=false) = 
     assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
            "Invalid polygon(s) or incompatible dimensions. " )
@@ -1774,10 +1637,9 @@ function reindex_polygon(reference, poly, return_error=false) =
     optimal_poly;
     
 
-
 // Function: align_polygon()
 // Usage:
-//   newpoly = align_polygon(reference, poly, angles, [cp]);
+//   newpoly = align_polygon(reference, poly, angles, <cp>);
 // Description:
 //   Tries the list or range of angles to find a rotation of the specified 2D polygon that best aligns
 //   with the reference 2D polygon.  For each angle, the polygon is reindexed, which is a costly operation
@@ -1819,26 +1681,6 @@ function align_polygon(reference, poly, angles, cp) =
 //   Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid.
 //   Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid.
 //   If the polygon is self-intersecting, the results are undefined.
-function centroid(poly) =
-    assert( is_path(poly), "The input must be a 2D or 3D polygon." )
-    len(poly[0])==2
-    ? sum([
-            for(i=[0:len(poly)-1])
-            let(segment=select(poly,i,i+1))
-            det2(segment)*sum(segment)
-          ]) / 6 / polygon_area(poly)
-    : let( plane = plane_from_points(poly, fast=true) )
-      assert( !is_undef(plane), "The polygon must be planar." )
-      let(
-        n = plane_normal(plane),
-        p1 = vector_angle(n,UP)>15? vector_axis(n,UP) : vector_axis(n,RIGHT),
-        p2 = vector_axis(n,p1),
-        cp = mean(poly),
-        proj = project_plane(poly,cp,cp+p1,cp+p2),
-        cxy = centroid(proj)
-         ) 
-      lift_plane(cxy,cp,cp+p1,cp+p2);
-
 function centroid(poly) =
     assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
     len(poly[0])==2
@@ -1866,10 +1708,11 @@ function centroid(poly) =
 
 // Function: point_in_polygon()
 // Usage:
-//   point_in_polygon(point, path, [eps])
+//   point_in_polygon(point, poly, <eps>)
 // Description:
 //   This function tests whether the given 2D point is inside, outside or on the boundary of
-//   the specified 2D polygon using the Winding Number method.
+//   the specified 2D polygon using either the Nonzero Winding rule or the Even-Odd rule.
+//   See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Even–odd_rule.
 //   The polygon is given as a list of 2D points, not including the repeated end point.
 //   Returns -1 if the point is outside the polyon.
 //   Returns 0 if the point is on the boundary.
@@ -1879,75 +1722,81 @@ function centroid(poly) =
 //   Rounding error may give mixed results for points on or near the boundary.
 // Arguments:
 //   point = The 2D point to check position of.
-//   path = The list of 2D path points forming the perimeter of the polygon.
+//   poly = The list of 2D path points forming the perimeter of the polygon.
+//   nonzero = The rule to use: true for "Nonzero" rule and false for "Even-Odd" (Default: true )
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
-function point_in_polygon(point, path, eps=EPSILON) =
-    // Original algorithm from http://geomalgorithms.com/a03-_inclusion.html
-    assert( is_vector(point,2) && is_path(path,dim=2) && len(path)>2,
+function point_in_polygon(point, poly, eps=EPSILON, nonzero=true) =
+    // Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
+    assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,
             "The point and polygon should be in 2D. The polygon should have more that 2 points." )
     assert( is_finite(eps) && eps>=0, "Invalid tolerance." )
     // Does the point lie on any edges?  If so return 0.
     let(
-        on_brd = [for(i=[0:1:len(path)-1]) 
-                    let( seg = select(path,i,i+1) ) 
-                    if( !approx(seg[0],seg[1],eps=eps) ) 
+        on_brd = [for(i=[0:1:len(poly)-1]) 
+                    let( seg = select(poly,i,i+1) ) 
+                    if( !approx(seg[0],seg[1],eps=EPSILON) ) 
                         point_on_segment2d(point, seg, eps=eps)? 1:0 ]
         )
-    sum(on_brd) > 0? 0 :
-    // Otherwise compute winding number and return 1 for interior, -1 for exterior
-    let(
-        windchk = [for(i=[0:1:len(path)-1]) 
-                    let(seg=select(path,i,i+1)) 
-                    if(!approx(seg[0],seg[1],eps=eps)) 
-                        _point_above_below_segment(point, seg)
-                  ]
-        )
-    sum(windchk) != 0 ? 1 : -1;
+    sum(on_brd) > 0
+    ? 0 
+    :   nonzero
+        ?    // Compute winding number and return 1 for interior, -1 for exterior
+            let(
+                windchk = [for(i=[0:1:len(poly)-1]) 
+                            let(seg=select(poly,i,i+1)) 
+                            if(!approx(seg[0],seg[1],eps=eps)) 
+                                _point_above_below_segment(point, seg)
+                          ]
+                )
+            sum(windchk) != 0 ? 1 : -1
+        :   // or compute the crossings with the ray [point, point+[1,0]]
+            let( 
+              n  = len(poly),
+              cross = 
+                [for(i=[0:n-1])
+                    let( 
+                      p0 = poly[i]-point, 
+                      p1 = poly[(i+1)%n]-point
+                      )
+                    if( ( (p1.y>eps && p0.y<=0) || (p1.y<=0 && p0.y>eps) )
+                        &&  0 < p0.x - p0.y *(p1.x - p0.x)/(p1.y - p0.y) )
+                    1
+                ]
+            )
+            2*(len(cross)%2)-1;;
 
-//**
-// this function should be optimized avoiding the call of other functions
 
 // Function: polygon_is_clockwise()
 // Usage:
-//   polygon_is_clockwise(path);
+//   polygon_is_clockwise(poly);
 // Description:
 //   Return true if the given 2D simple polygon is in clockwise order, false otherwise.
 //   Results for complex (self-intersecting) polygon are indeterminate.
 // Arguments:
-//   path = The list of 2D path points for the perimeter of the polygon.
-function polygon_is_clockwise(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d polygon")
-    let(
-        minx = min(subindex(path,0)),
-        lowind = search(minx, path, 0, 0),
-        lowpts = select(path, lowind),
-        miny = min(subindex(lowpts, 1)),
-        extreme_sub = search(miny, lowpts, 1, 1)[0],
-        extreme = select(lowind,extreme_sub)
-    ) det2([select(path,extreme+1)-path[extreme], select(path, extreme-1)-path[extreme]])<0;
+//   poly = The list of 2D path points for the perimeter of the polygon.
+function polygon_is_clockwise(poly) =
+    assert(is_path(poly,dim=2), "Input should be a 2d path")
+    polygon_area(poly, signed=true)<0;
 
-function polygon_is_clockwise(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d path")
-    polygon_area(path, signed=true)<0;
 
 // Function: clockwise_polygon()
 // Usage:
-//   clockwise_polygon(path);
+//   clockwise_polygon(poly);
 // Description:
 //   Given a 2D polygon path, returns the clockwise winding version of that path.
-function clockwise_polygon(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d polygon")
-    polygon_area(path, signed=true)<0 ? path : reverse_polygon(path);
+function clockwise_polygon(poly) =
+    assert(is_path(poly,dim=2), "Input should be a 2d polygon")
+    polygon_area(poly, signed=true)<0 ? poly : reverse_polygon(poly);
 
 
 // Function: ccw_polygon()
 // Usage:
-//   ccw_polygon(path);
+//   ccw_polygon(poly);
 // Description:
-//   Given a 2D polygon path, returns the counter-clockwise winding version of that path.
-function ccw_polygon(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d polygon")
-    polygon_area(path, signed=true)<0 ? reverse_polygon(path) : path;
+//   Given a 2D polygon poly, returns the counter-clockwise winding version of that poly.
+function ccw_polygon(poly) =
+    assert(is_path(poly,dim=2), "Input should be a 2d polygon")
+    polygon_area(poly, signed=true)<0 ? reverse_polygon(poly) : poly;
 
 
 // Function: reverse_polygon()
@@ -1969,7 +1818,7 @@ function reverse_polygon(poly) =
 function polygon_normal(poly) =
     assert(is_path(poly,dim=3), "Invalid 3D polygon." )
     let(
-        poly = path3d(cleanup_path(poly)),
+        poly = cleanup_path(poly),
         p0 = poly[0],
         n = sum([
             for (i=[1:1:len(poly)-2])
@@ -2085,17 +1934,6 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
         ], xs, _i=_i+1
     );
     
-//***
-// all the functions split_polygons_at_ may generate non simple polygons even from simple polygon inputs:
-//     split_polygons_at_each_y([[[-1,1,0],[0,0,0],[1,1,0],[1,-1,0],[-1,-1,0]]],[0])
-// produces:
-//   [  [[0, 0, 0], [1, 0, 0], [1, -1, 0], [-1, -1, 0], [-1, 0, 0]]
-//      [[-1, 1, 0], [0, 0, 0], [1, 1, 0], [1, 0, 0], [-1, 0, 0]] ]
-// and the second polygon is self-intersecting
-// besides, it fails in some simple cases as triangles:
-//   split_polygons_at_each_y([ [-1,-1,0],[1,-1,0],[0,1,0]],[0])==[]
-// this last failure may be fatal for vnf_bend
-
 
 // Function: split_polygons_at_each_y()
 // Usage:
@@ -2106,9 +1944,9 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
 //   polys = A list of 3D polygons to split.
 //   ys = A list of scalar Y values to split at.
 function split_polygons_at_each_y(polys, ys, _i=0) =
-    assert( is_consistent(polys) && is_path(poly[0],dim=3) ,
-            "The input list should contains only 3D polygons." )
-    assert( is_finite(ys), "The split value list should contain only numbers." )  
+//    assert( is_consistent(polys) && is_path(polys[0],dim=3) , // not all polygons should have the same length!!!
+  //          "The input list should contains only 3D polygons." )
+    assert( is_finite(ys) || is_vector(ys), "The split value list should contain only numbers." )  //***
     _i>=len(ys)? polys :
     split_polygons_at_each_y(
         [
@@ -2139,5 +1977,4 @@ function split_polygons_at_each_z(polys, zs, _i=0) =
     );
 
 
-
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad
index ceeb905..0950e1c 100644
--- a/tests/test_geometry.scad
+++ b/tests/test_geometry.scad
@@ -98,6 +98,8 @@ function standardize(v) =
     v==[]? [] :
     sign([for(vi=v) if( ! approx(vi,0)) vi,0 ][0])*v;
 
+module assert_std(vc,ve) { assert(standardize(vc)==standardize(ve)); }
+
 module test_points_on_plane() {
     pts     = [for(i=[0:40]) rands(-1,1,3) ];
     dir     = rands(-10,10,3);
@@ -487,48 +489,47 @@ module test_triangle_area() {
 
 
 module test_plane3pt() {
-    assert(plane3pt([0,0,20], [0,10,10], [0,0,0]) == [1,0,0,0]);
-    assert(plane3pt([2,0,20], [2,10,10], [2,0,0]) == [1,0,0,2]);
-    assert(plane3pt([0,0,0], [10,0,10], [0,0,20]) == [0,1,0,0]);
-    assert(plane3pt([0,2,0], [10,2,10], [0,2,20]) == [0,1,0,2]);
-    assert(plane3pt([0,0,0], [10,10,0], [20,0,0]) == [0,0,1,0]);
-    assert(plane3pt([0,0,2], [10,10,2], [20,0,2]) == [0,0,1,2]);
+    assert_std(plane3pt([0,0,20], [0,10,10], [0,0,0]), [1,0,0,0]);
+    assert_std(plane3pt([2,0,20], [2,10,10], [2,0,0]), [1,0,0,2]);
+    assert_std(plane3pt([0,0,0], [10,0,10], [0,0,20]), [0,1,0,0]);
+    assert_std(plane3pt([0,2,0], [10,2,10], [0,2,20]), [0,1,0,2]);
+    assert_std(plane3pt([0,0,0], [10,10,0], [20,0,0]), [0,0,1,0]);
+    assert_std(plane3pt([0,0,2], [10,10,2], [20,0,2]), [0,0,1,2]);
 }
 *test_plane3pt();
 
 module test_plane3pt_indexed() {
     pts = [ [0,0,0], [10,0,0], [0,10,0], [0,0,10] ];
     s13 = sqrt(1/3);
-    assert(plane3pt_indexed(pts, 0,3,2) == [1,0,0,0]);
-    assert(plane3pt_indexed(pts, 0,2,3) == [-1,0,0,0]);
-    assert(plane3pt_indexed(pts, 0,1,3) == [0,1,0,0]);
-    assert(plane3pt_indexed(pts, 0,3,1) == [0,-1,0,0]);
-    assert(plane3pt_indexed(pts, 0,2,1) == [0,0,1,0]);
+    assert_std(plane3pt_indexed(pts, 0,3,2), [1,0,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,2,3), [-1,0,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,1,3), [0,1,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,3,1), [0,-1,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,2,1), [0,0,1,0]);
     assert_approx(plane3pt_indexed(pts, 0,1,2), [0,0,-1,0]);
     assert_approx(plane3pt_indexed(pts, 3,2,1), [s13,s13,s13,10*s13]);
     assert_approx(plane3pt_indexed(pts, 1,2,3), [-s13,-s13,-s13,-10*s13]);
 }
 *test_plane3pt_indexed();
 
-
 module test_plane_from_points() {
-    assert(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]) == [1,0,0,0]);
-    assert(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]) == [1,0,0,2]);
-    assert(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]) == [0,1,0,0]);
-    assert(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]) == [0,1,0,2]);
-    assert(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]) == [0,0,1,0]);
-    assert(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]) == [0,0,1,2]);
+    assert_std(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]), [1,0,0,0]);
+    assert_std(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]), [1,0,0,2]);
+    assert_std(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]), [0,1,0,0]);
+    assert_std(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]), [0,1,0,2]);
+    assert_std(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]), [0,0,1,0]);
+    assert_std(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]), [0,0,1,2]);
 }
 *test_plane_from_points();
 
 
 module test_plane_normal() {
-    assert(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])) == [1,0,0]);
-    assert(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])) == [1,0,0]);
-    assert(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])) == [0,1,0]);
-    assert(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])) == [0,1,0]);
-    assert(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])) == [0,0,1]);
-    assert(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])) == [0,0,1]);
+    assert_std(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])), [1,0,0]);
+    assert_std(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])), [1,0,0]);
+    assert_std(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])), [0,1,0]);
+    assert_std(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])), [0,1,0]);
+    assert_std(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])), [0,0,1]);
+    assert_std(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])), [0,0,1]);
 }
 *test_plane_normal();
 
@@ -699,16 +700,22 @@ module test_simplify_path_indexed() {
 
 module test_point_in_polygon() {
     poly = [for (a=[0:30:359]) 10*[cos(a),sin(a)]];
+    poly2 = [ [-3,-3],[2,-3],[2,1],[-1,1],[-1,-1],[1,-1],[1,2],[-3,2] ];
     assert(point_in_polygon([0,0], poly) == 1);
     assert(point_in_polygon([20,0], poly) == -1);
+    assert(point_in_polygon([20,0], poly,EPSILON,nonzero=false) == -1);
     assert(point_in_polygon([5,5], poly) == 1);
     assert(point_in_polygon([-5,5], poly) == 1);
     assert(point_in_polygon([-5,-5], poly) == 1);
     assert(point_in_polygon([5,-5], poly) == 1);
+    assert(point_in_polygon([5,-5], poly,EPSILON,nonzero=false) == 1);
     assert(point_in_polygon([-10,-10], poly) == -1);
     assert(point_in_polygon([10,0], poly) == 0);
     assert(point_in_polygon([0,10], poly) == 0);
     assert(point_in_polygon([0,-10], poly) == 0);
+    assert(point_in_polygon([0,-10], poly,EPSILON,nonzero=false) == 0);
+    assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=true) == 1);
+    assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=false) == -1);
 }
 *test_point_in_polygon();
 

From 5462616e1efaaa72e26b4fb906e62e11b1ff82fb Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Thu, 20 Aug 2020 22:36:50 +0100
Subject: [PATCH 15/18] Revert "In observance of owner's last review"

This reverts commit 99e815f077dcd374d3365bb20ed12791dce06e2b.
---
 common.scad              |  67 +++++---
 geometry.scad            | 341 +++++++++++++++++++++++++++++----------
 tests/test_geometry.scad |  55 +++----
 3 files changed, 318 insertions(+), 145 deletions(-)

diff --git a/common.scad b/common.scad
index db4f3bb..51d8363 100644
--- a/common.scad
+++ b/common.scad
@@ -129,6 +129,11 @@ function is_list_of(list,pattern) =
     is_list(list) &&
     []==[for(entry=0*list) if (entry != pattern) entry];
 
+function _list_pattern(list) =
+  is_list(list) ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0]
+                : 0;
+
+
 
 // Function: is_consistent()
 // Usage:
@@ -193,11 +198,11 @@ function first_defined(v,recursive=false,_i=0) =
             is_undef(first_defined(v[_i],recursive=recursive))
         )
     )? first_defined(v,recursive=recursive,_i=_i+1) : v[_i];
-    
+
 
 // Function: one_defined()
 // Usage:
-//   one_defined(vars, names, <required>)
+//   one_defined(vars, names, [required])
 // Description:
 //   Examines the input list `vars` and returns the entry which is not `undef`.  If more
 //   than one entry is `undef` then issues an assertion specifying "Must define exactly one of" followed
@@ -216,7 +221,8 @@ function one_defined(vars, names, required=true) =
 
 // Function: num_defined()
 // Description: Counts how many items in list `v` are not `undef`.
-function num_defined(v) = len([for(vi=v) if(!is_undef(vi)) 1]);
+function num_defined(v,_i=0,_cnt=0) = _i>=len(v)? _cnt : num_defined(v,_i+1,_cnt+(is_undef(v[_i])? 0 : 1));
+
 
 // Function: any_defined()
 // Description:
@@ -233,8 +239,8 @@ function any_defined(v,recursive=false) = first_defined(v,recursive=recursive) !
 // Arguments:
 //   v = The list whose items are being checked.
 //   recursive = If true, any sublists are evaluated recursively.
-function all_defined(v,recursive=false) = 
-    []==[for (x=v) if(is_undef(x)||(recursive && is_list(x) && !all_defined(x,recursive))) 0 ];
+function all_defined(v,recursive=false) = max([for (x=v) is_undef(x)||(recursive&&is_list(x)&&!all_defined(x))? 1 : 0])==0;
+
 
 
 
@@ -243,7 +249,7 @@ function all_defined(v,recursive=false) =
 
 // Function: get_anchor()
 // Usage:
-//   get_anchor(anchor,center,<uncentered>,<dflt>);
+//   get_anchor(anchor,center,[uncentered],[dflt]);
 // 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.
@@ -264,7 +270,7 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 
 // Function: get_radius()
 // Usage:
-//   get_radius(<r1>, <r2>, <r>, <d1>, <d2>, <d>, <dflt>);
+//   get_radius([r1], [r2], [r], [d1], [d2], [d], [dflt]);
 // Description:
 //   Given various radii and diameters, returns the most specific radius.
 //   If a diameter is most specific, returns half its value, giving the radius.
@@ -282,23 +288,34 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 //   r = Most general radius.
 //   d = Most general diameter.
 //   dflt = Value to return if all other values given are `undef`.
-function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = 
-    assert(num_defined([r1,d1,r2,d2])<2, "Conflicting or redundant radius/diameter arguments given.")
-    !is_undef(r1) ?   assert(is_finite(r1), "Invalid radius r1." ) r1 
-    : !is_undef(r2) ? assert(is_finite(r2), "Invalid radius r2." ) r2
-    : !is_undef(d1) ? assert(is_finite(d1), "Invalid diameter d1." ) d1/2
-    : !is_undef(d2) ? assert(is_finite(d2), "Invalid diameter d2." ) d2/2
-    : !is_undef(r)
-      ? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
-        assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
-        r 
-    : !is_undef(d) ? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." ) d/2
-    : dflt;
-
+function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = (
+    !is_undef(r1)
+		? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
+				assert(is_finite(r1), "Invalid radius r1." )
+				r1 
+    : !is_undef(r2)
+			? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
+				assert(is_finite(r2), "Invalid radius r2." )
+				r2
+		: !is_undef(d1)
+		  ? assert(is_finite(d1), "Invalid diameter d1." )
+				d1/2
+		: !is_undef(d2)
+			? assert(is_finite(d2), "Invalid diameter d2." )
+				d2/2
+		: !is_undef(r)
+			? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
+				assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
+				r 
+		: !is_undef(d)
+			? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." )
+			d/2
+		: dflt
+);
 
 // Function: get_height()
 // Usage:
-//   get_height(<h>,<l>,<height>,<dflt>)
+//   get_height([h],[l],[height],[dflt])
 // Description:
 //   Given several different parameters for height check that height is not multiply defined
 //   and return a single value.  If the three values `l`, `h`, and `height` are all undefined
@@ -315,7 +332,7 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) =
 
 // Function: scalar_vec3()
 // Usage:
-//   scalar_vec3(v, <dflt>);
+//   scalar_vec3(v, [dflt]);
 // Description:
 //   If `v` is a scalar, and `dflt==undef`, returns `[v, v, v]`.
 //   If `v` is a scalar, and `dflt!=undef`, returns `[v, dflt, dflt]`.
@@ -367,7 +384,7 @@ function _valstr(x) =
 
 // Module: assert_approx()
 // Usage:
-//   assert_approx(got, expected, <info>);
+//   assert_approx(got, expected, [info]);
 // Description:
 //   Tests if the value gotten is what was expected.  If not, then
 //   the expected and received values are printed to the console and
@@ -394,7 +411,7 @@ module assert_approx(got, expected, info) {
 
 // Module: assert_equal()
 // Usage:
-//   assert_equal(got, expected, <info>);
+//   assert_equal(got, expected, [info]);
 // Description:
 //   Tests if the value gotten is what was expected.  If not, then
 //   the expected and received values are printed to the console and
@@ -421,7 +438,7 @@ module assert_equal(got, expected, info) {
 
 // Module: shape_compare()
 // Usage:
-//   shape_compare(<eps>) {test_shape(); expected_shape();}
+//   shape_compare([eps]) {test_shape(); expected_shape();}
 // Description:
 //   Compares two child shapes, returning empty geometry if they are very nearly the same shape and size.
 //   Returns the differential geometry if they are not nearly the same shape and size.
diff --git a/geometry.scad b/geometry.scad
index dc86187..eff67bc 100644
--- a/geometry.scad
+++ b/geometry.scad
@@ -23,25 +23,36 @@
 function point_on_segment2d(point, edge, eps=EPSILON) =
     assert( is_vector(point,2), "Invalid point." )
     assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
-    assert( _valid_line(edge,2,eps=eps), "Invalid segment." )
+    assert( _valid_line(edge,eps=eps), "Invalid segment." )
+    approx(point,edge[0],eps=eps) 
+    || approx(point,edge[1],eps=eps) // The point is an endpoint
+    || sign(edge[0].x-point.x)==sign(point.x-edge[1].x)  // point is in between the
+    || ( sign(edge[0].y-point.y)==sign(point.y-edge[1].y)  // edge endpoints
+        && approx(point_left_of_line2d(point, edge),0,eps=eps) );  // and on the line defined by edge
+
+function point_on_segment2d(point, edge, eps=EPSILON) =
+    assert( is_vector(point,2), "Invalid point." )
+    assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
+    assert( _valid_line(edge,eps=eps), "Invalid segment." )
     let( dp = point-edge[0],
          de = edge[1]-edge[0],
          ne = norm(de) ) 
     ( dp*de >= -eps*ne ) 
-    && ( (dp-de)*de <= eps*ne )                  // point projects on the segment
-    && _dist2line(point-edge[0],unit(de))<eps;   // point is on the line 
+    && ( (dp-de)*de <= eps*ne )              // point projects on the segment
+    && _dist(point-edge[0],unit(de))<eps;   // point is on the line 
     
     
 //Internal - distance from point `d` to the line passing through the origin with unit direction n
-//_dist2line works for any dimension
-function _dist2line(d,n) = norm(d-(d * n) * n);
+//_dist works for any dimension
+function _dist(d,n) = norm(d-(d * n) * n);
 
 // Internal non-exposed function.
 function _point_above_below_segment(point, edge) =
-    let( edge = edge - [point, point] )
-    edge[0].y <= 0 
-    ?   (edge[1].y >  0 && cross(edge[0], edge[1]-edge[0]) > 0) ?  1 : 0
-    :   (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0 ;
+    edge[0].y <= point.y? (
+        (edge[1].y > point.y && point_left_of_line2d(point, edge) > 0)? 1 : 0
+    ) : (
+        (edge[1].y <= point.y && point_left_of_line2d(point, edge) < 0)? -1 : 0
+    );
 
 //Internal
 function _valid_line(line,dim,eps=EPSILON) = 
@@ -87,6 +98,10 @@ function collinear(a, b, c, eps=EPSILON) =
     : noncollinear_triple(points,error=false,eps=eps)==[];
  
 
+//*** valid for any dimension
+
+ 
+
 // Function: distance_from_line()
 // Usage:
 //   distance_from_line(line, pt);
@@ -100,7 +115,7 @@ function collinear(a, b, c, eps=EPSILON) =
 function distance_from_line(line, pt) =
     assert( _valid_line(line) && is_vector(pt,len(line[0])), 
             "Invalid line, invalid point or incompatible dimensions." )
-    _dist2line(pt-line[0],unit(line[1]-line[0]));
+    _dist(pt-line[0],unit(line[1]-line[0]));
     
     
 // Function: line_normal()
@@ -315,6 +330,17 @@ function segment_intersection(s1,s2,eps=EPSILON) =
 //   stroke(line, endcaps="arrow2");
 //   color("blue") translate(pt) sphere(r=1,$fn=12);
 //   color("red") translate(p2) sphere(r=1,$fn=12);
+function line_closest_point(line,pt) =
+    assert(is_path(line)&&len(line)==2)
+    assert(same_shape(pt,line[0]))
+    assert(!approx(line[0],line[1]))
+    let(
+        seglen = norm(line[1]-line[0]),
+        segvec = (line[1]-line[0])/seglen,
+        projection = (pt-line[0]) * segvec
+    )
+    line[0] + projection*segvec;
+
 function line_closest_point(line,pt) =
     assert(_valid_line(line), "Invalid line." )
     assert( is_vector(pt,len(line[0])), "Invalid point or incompatible dimensions." )
@@ -748,10 +774,14 @@ function adj_opp_to_ang(adj,opp) =
 //   triangle_area([0,0], [5,10], [10,0]);  // Returns -50
 //   triangle_area([10,0], [5,10], [0,0]);  // Returns 50
 function triangle_area(a,b,c) = 
-    assert( is_path([a,b,c]), "Invalid points or incompatible dimensions." )    
-    len(a)==3 
-    ? 0.5*norm(cross(c-a,c-b)) 
-    : 0.5*cross(c-a,c-b);
+    assert( is_path([a,b,c]),
+            "Invalid points or incompatible dimensions." )    
+    len(a)==3 ? 0.5*norm(cross(c-a,c-b)) 
+    : (
+        a.x * (b.y - c.y) +
+        b.x * (c.y - a.y) +
+        c.x * (a.y - b.y)
+    ) / 2;
 
 
 
@@ -821,7 +851,7 @@ function plane_from_normal(normal, pt=[0,0,0]) =
 
 // Function: plane_from_points()
 // Usage:
-//   plane_from_points(points, <fast>, <eps>);
+//   plane_from_points(points, [fast], [eps]);
 // Description:
 //   Given a list of 3 or more coplanar 3D points, returns the coefficients of the cartesian equation of a plane,
 //   that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane.
@@ -846,6 +876,7 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
     )
     indices==[] ? undef :
     let(
+        indices = sort(indices), // why sorting?
         p1 = points[indices[0]],
         p2 = points[indices[1]],
         p3 = points[indices[2]],
@@ -882,6 +913,11 @@ function plane_from_polygon(poly, fast=false, eps=EPSILON) =
     ) 
     fast? plane: coplanar(poly,eps=eps)? plane: [];
 
+//***
+// I don't see why this function uses a criterium different from plane_from_points.
+// In practical terms, what is the difference of finding a plane from points and from polygon?
+// The docs don't clarify.
+// These functions should be consistent if they are both necessary. The docs might reflect their distinction.
 
 // Function: plane_normal()
 // Usage:
@@ -933,8 +969,8 @@ function plane_transform(plane) =
 // Usage:
 //   projection_on_plane(points);
 // Description:
-//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the 3D orthogonal 
-//   projection of the points on the plane.
+//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the projection
+//   of the points on the plane.
 // Arguments:
 //   plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
 //   points = List of points to project
@@ -1006,6 +1042,23 @@ function closest_point_on_plane(plane, point) =
 // Returns [POINT, U] if line intersects plane at one point.
 // Returns [LINE, undef] if the line is on the plane.
 // Returns undef if line is parallel to, but not on the given plane.
+function _general_plane_line_intersection(plane, line, eps=EPSILON) =
+    let(
+        p0 = line[0],
+        p1 = line[1],
+        n = plane_normal(plane),
+        u = p1 - p0,
+        d = n * u
+    ) abs(d)<eps? (
+        points_on_plane(p0,plane,eps)? [line,undef] :  // Line on plane
+        undef  // Line parallel to plane
+    ) : let(
+        v0 = closest_point_on_plane(plane, [0,0,0]),
+        w = p0 - v0,
+        s1 = (-n * w) / d,
+        pt = s1 * u + p0
+    ) [pt, s1];
+
 function _general_plane_line_intersection(plane, line, eps=EPSILON) =
     let( a = plane*[each line[0],-1],
          b = plane*[each(line[1]-line[0]),-1] )
@@ -1013,7 +1066,6 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
     ? points_on_plane(line[0],plane,eps)? [line,undef]: undef
     : [ line[0]+a/b*(line[1]-line[0]), a/b ];
 
-
 // Function: plane_line_angle()
 // Usage: plane_line_angle(plane,line)
 // Description:
@@ -1085,7 +1137,7 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
     )
     indices==[] ? undef :
     let(
-        indices = sort(indices),
+        indices = sort(indices), // why sorting?
         p1 = poly[indices[0]],
         p2 = poly[indices[1]],
         p3 = poly[indices[2]],
@@ -1149,7 +1201,7 @@ function plane_intersection(plane1,plane2,plane3) =
 
 // Function: coplanar()
 // Usage:
-//   coplanar(points,<eps>);
+//   coplanar(points,eps);
 // Description:
 //   Returns true if the given 3D points are non-collinear and are on a plane.
 // Arguments:
@@ -1168,7 +1220,7 @@ function coplanar(points, eps=EPSILON) =
     
 // Function: points_on_plane()
 // Usage:
-//   points_on_plane(points, plane, <eps>);
+//   points_on_plane(points, plane, eps);
 // Description:
 //   Returns true if the given 3D points are on the given plane.
 // Arguments:
@@ -1204,7 +1256,7 @@ function in_front_of_plane(plane, point) =
 
 // Function: find_circle_2tangents()
 // Usage:
-//   find_circle_2tangents(pt1, pt2, pt3, r|d, <tangents>);
+//   find_circle_2tangents(pt1, pt2, pt3, r|d, [tangents]);
 // Description:
 //   Given a pair of rays with a common origin, and a known circle radius/diameter, finds
 //   the centerpoint for the circle of that size that touches both rays tangentally.
@@ -1273,8 +1325,7 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 
 // Function: find_circle_3points()
 // Usage:
-//   find_circle_3points(pt1, pt2, pt3);
-//   find_circle_3points([pt1, pt2, pt3]);
+//   find_circle_3points(pt1, [pt2, pt3]);
 // Description:
 //   Returns the [CENTERPOINT, RADIUS, NORMAL] of the circle that passes through three non-collinear
 //   points where NORMAL is the normal vector of the plane that the circle is on (UP or DOWN if the points are 2D).
@@ -1294,6 +1345,40 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 //   translate(circ[0]) color("green") stroke(circle(r=circ[1]),closed=true,$fn=72);
 //   translate(circ[0]) color("red") circle(d=3, $fn=12);
 //   move_copies(pts) color("blue") circle(d=3, $fn=12);
+function find_circle_3points(pt1, pt2, pt3) =
+    (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
+    ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
+    :   assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) 
+                && max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
+                "Invalid point(s)." )
+        collinear(pt1,pt2,pt3)? [undef,undef,undef] :
+        let(
+            v1 = pt1-pt2,
+            v2 = pt3-pt2,
+            n = vector_axis(v1,v2),
+            n2 = n.z<0? -n : n
+        ) len(pt1)+len(pt2)+len(pt3)>6? (
+            let(
+                a = project_plane(pt1, pt1, pt2, pt3),
+                b = project_plane(pt2, pt1, pt2, pt3),
+                c = project_plane(pt3, pt1, pt2, pt3),
+                res = find_circle_3points(a, b, c)
+            ) res[0]==undef? [undef,undef,undef] : let(
+                cp = lift_plane(res[0], pt1, pt2, pt3),
+                r = norm(pt2-cp)
+            ) [cp, r, n2]
+        ) : let(
+            mp1 = pt2 + v1/2,
+            mp2 = pt2 + v2/2,
+            mpv1 = rot(90, v=n, p=v1),
+            mpv2 = rot(90, v=n, p=v2),
+            l1 = [mp1, mp1+mpv1],
+            l2 = [mp2, mp2+mpv2],
+            isect = line_intersection(l1,l2)
+        ) is_undef(isect)? [undef,undef,undef] : let(
+            r = norm(pt2-isect)
+        ) [isect, r, n2];
+        
 function find_circle_3points(pt1, pt2, pt3) =
     (is_undef(pt2) && is_undef(pt3) && is_list(pt1))
     ? find_circle_3points(pt1[0], pt1[1], pt1[2]) 
@@ -1319,6 +1404,9 @@ function find_circle_3points(pt1, pt2, pt3) =
             )
         [ cp, r, n ];
      
+     
+
+
 
 // Function: circle_point_tangents()
 // Usage:
@@ -1354,6 +1442,7 @@ function circle_point_tangents(r, d, cp, pt) =
     ) [for (ang=angs) [ang, cp + r*[cos(ang),sin(ang)]]];
 
 
+
 // Function: circle_circle_tangents()
 // Usage: circle_circle_tangents(c1, r1|d1, c2, r2|d2)
 // Description:
@@ -1452,14 +1541,13 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
         []
     :   let(
             n = (pb-pa)/nrm,
-            distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)]   
+            distlist = [for(i=[0:len(points)-1]) _dist(points[i]-pa, n)]   
            )
         max(distlist)<eps
         ?  assert(!error, "Cannot find three noncollinear points in pointlist.")
            []
         :  [0,b,max_index(distlist)];    
 
-
 // Function: pointlist_bounds()
 // Usage:
 //   pointlist_bounds(pts);
@@ -1519,6 +1607,19 @@ function furthest_point(pt, points) =
 // Arguments:
 //   poly = polygon to compute the area of.
 //   signed = if true, a signed area is returned (default: false)
+function polygon_area(poly) =
+    assert(is_path(poly), "Invalid polygon." )
+    len(poly)<3? 0 :
+    len(poly[0])==2? 0.5*sum([for(i=[0:1:len(poly)-1]) det2(select(poly,i,i+1))]) :
+    let(
+        plane = plane_from_points(poly)
+    ) plane==undef? undef :
+    let(
+        n = unit(plane_normal(plane)),
+        total = sum([for (i=[0:1:len(poly)-1]) cross(poly[i], select(poly,i+1))]),
+        res = abs(total * n) / 2
+    ) res;
+    
 function polygon_area(poly, signed=false) =
     assert(is_path(poly), "Invalid polygon." )
     len(poly)<3 ? 0 :
@@ -1543,6 +1644,15 @@ function polygon_area(poly, signed=false) =
 // Example:
 //   spiral = [for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]];
 //   is_convex_polygon(spiral);  // Returns: false
+function is_convex_polygon(poly) =
+    assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
+    let(
+        l = len(poly),
+        c = [for (i=idx(poly)) cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l])]
+    )
+    len([for (x=c) if(x>0) 1])==0 ||
+    len([for (x=c) if(x<0) 1])==0;
+
 function is_convex_polygon(poly) =
     assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
     let( l = len(poly) )
@@ -1576,15 +1686,15 @@ function polygon_shift(poly, i) =
 // Usage:
 //   polygon_shift_to_closest_point(path, pt);
 // Description:
-//   Given a polygon `poly`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
-function polygon_shift_to_closest_point(poly, pt) =
+//   Given a polygon `path`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
+function polygon_shift_to_closest_point(path, pt) =
     assert(is_vector(pt), "Invalid point." )
-    assert(is_path(poly,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
+    assert(is_path(path,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
     let(
-        poly = cleanup_path(poly),
-        dists = [for (p=poly) norm(p-pt)],
+        path = cleanup_path(path),
+        dists = [for (p=path) norm(p-pt)],
         closest = min_index(dists)
-    ) select(poly,closest,closest+len(poly)-1);
+    ) select(path,closest,closest+len(path)-1);
 
 
 // Function: reindex_polygon()
@@ -1616,6 +1726,33 @@ function polygon_shift_to_closest_point(poly, pt) =
 //   move_copies(concat(circ,pent)) circle(r=.1,$fn=32);
 //   color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
 //   color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
+function reindex_polygon(reference, poly, return_error=false) = 
+    assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
+           "Invalid polygon(s) or incompatible dimensions. " )
+    assert(len(reference)==len(poly), "The polygons must have the same length.")
+    let(
+        dim = len(reference[0]),
+        N = len(reference),
+        fixpoly = dim != 2? poly :
+            polygon_is_clockwise(reference)? clockwise_polygon(poly) :
+            ccw_polygon(poly),
+        dist = [
+            // Matrix of all pairwise distances
+            for (p1=reference) [
+                for (p2=fixpoly) norm(p1-p2)
+            ]
+        ],
+        // Compute the sum of all distance pairs for a each shift
+        sums = [
+            for(shift=[0:1:N-1]) sum([
+                for(i=[0:1:N-1]) dist[i][(i+shift)%N]
+            ])
+        ],
+        optimal_poly = polygon_shift(fixpoly,min_index(sums))
+    )
+    return_error? [optimal_poly, min(sums)] :
+    optimal_poly;
+
 function reindex_polygon(reference, poly, return_error=false) = 
     assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
            "Invalid polygon(s) or incompatible dimensions. " )
@@ -1637,9 +1774,10 @@ function reindex_polygon(reference, poly, return_error=false) =
     optimal_poly;
     
 
+
 // Function: align_polygon()
 // Usage:
-//   newpoly = align_polygon(reference, poly, angles, <cp>);
+//   newpoly = align_polygon(reference, poly, angles, [cp]);
 // Description:
 //   Tries the list or range of angles to find a rotation of the specified 2D polygon that best aligns
 //   with the reference 2D polygon.  For each angle, the polygon is reindexed, which is a costly operation
@@ -1681,6 +1819,26 @@ function align_polygon(reference, poly, angles, cp) =
 //   Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid.
 //   Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid.
 //   If the polygon is self-intersecting, the results are undefined.
+function centroid(poly) =
+    assert( is_path(poly), "The input must be a 2D or 3D polygon." )
+    len(poly[0])==2
+    ? sum([
+            for(i=[0:len(poly)-1])
+            let(segment=select(poly,i,i+1))
+            det2(segment)*sum(segment)
+          ]) / 6 / polygon_area(poly)
+    : let( plane = plane_from_points(poly, fast=true) )
+      assert( !is_undef(plane), "The polygon must be planar." )
+      let(
+        n = plane_normal(plane),
+        p1 = vector_angle(n,UP)>15? vector_axis(n,UP) : vector_axis(n,RIGHT),
+        p2 = vector_axis(n,p1),
+        cp = mean(poly),
+        proj = project_plane(poly,cp,cp+p1,cp+p2),
+        cxy = centroid(proj)
+         ) 
+      lift_plane(cxy,cp,cp+p1,cp+p2);
+
 function centroid(poly) =
     assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
     len(poly[0])==2
@@ -1708,11 +1866,10 @@ function centroid(poly) =
 
 // Function: point_in_polygon()
 // Usage:
-//   point_in_polygon(point, poly, <eps>)
+//   point_in_polygon(point, path, [eps])
 // Description:
 //   This function tests whether the given 2D point is inside, outside or on the boundary of
-//   the specified 2D polygon using either the Nonzero Winding rule or the Even-Odd rule.
-//   See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Even–odd_rule.
+//   the specified 2D polygon using the Winding Number method.
 //   The polygon is given as a list of 2D points, not including the repeated end point.
 //   Returns -1 if the point is outside the polyon.
 //   Returns 0 if the point is on the boundary.
@@ -1722,81 +1879,75 @@ function centroid(poly) =
 //   Rounding error may give mixed results for points on or near the boundary.
 // Arguments:
 //   point = The 2D point to check position of.
-//   poly = The list of 2D path points forming the perimeter of the polygon.
-//   nonzero = The rule to use: true for "Nonzero" rule and false for "Even-Odd" (Default: true )
+//   path = The list of 2D path points forming the perimeter of the polygon.
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
-function point_in_polygon(point, poly, eps=EPSILON, nonzero=true) =
-    // Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
-    assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,
+function point_in_polygon(point, path, eps=EPSILON) =
+    // Original algorithm from http://geomalgorithms.com/a03-_inclusion.html
+    assert( is_vector(point,2) && is_path(path,dim=2) && len(path)>2,
             "The point and polygon should be in 2D. The polygon should have more that 2 points." )
     assert( is_finite(eps) && eps>=0, "Invalid tolerance." )
     // Does the point lie on any edges?  If so return 0.
     let(
-        on_brd = [for(i=[0:1:len(poly)-1]) 
-                    let( seg = select(poly,i,i+1) ) 
-                    if( !approx(seg[0],seg[1],eps=EPSILON) ) 
+        on_brd = [for(i=[0:1:len(path)-1]) 
+                    let( seg = select(path,i,i+1) ) 
+                    if( !approx(seg[0],seg[1],eps=eps) ) 
                         point_on_segment2d(point, seg, eps=eps)? 1:0 ]
         )
-    sum(on_brd) > 0
-    ? 0 
-    :   nonzero
-        ?    // Compute winding number and return 1 for interior, -1 for exterior
-            let(
-                windchk = [for(i=[0:1:len(poly)-1]) 
-                            let(seg=select(poly,i,i+1)) 
-                            if(!approx(seg[0],seg[1],eps=eps)) 
-                                _point_above_below_segment(point, seg)
-                          ]
-                )
-            sum(windchk) != 0 ? 1 : -1
-        :   // or compute the crossings with the ray [point, point+[1,0]]
-            let( 
-              n  = len(poly),
-              cross = 
-                [for(i=[0:n-1])
-                    let( 
-                      p0 = poly[i]-point, 
-                      p1 = poly[(i+1)%n]-point
-                      )
-                    if( ( (p1.y>eps && p0.y<=0) || (p1.y<=0 && p0.y>eps) )
-                        &&  0 < p0.x - p0.y *(p1.x - p0.x)/(p1.y - p0.y) )
-                    1
-                ]
-            )
-            2*(len(cross)%2)-1;;
+    sum(on_brd) > 0? 0 :
+    // Otherwise compute winding number and return 1 for interior, -1 for exterior
+    let(
+        windchk = [for(i=[0:1:len(path)-1]) 
+                    let(seg=select(path,i,i+1)) 
+                    if(!approx(seg[0],seg[1],eps=eps)) 
+                        _point_above_below_segment(point, seg)
+                  ]
+        )
+    sum(windchk) != 0 ? 1 : -1;
 
+//**
+// this function should be optimized avoiding the call of other functions
 
 // Function: polygon_is_clockwise()
 // Usage:
-//   polygon_is_clockwise(poly);
+//   polygon_is_clockwise(path);
 // Description:
 //   Return true if the given 2D simple polygon is in clockwise order, false otherwise.
 //   Results for complex (self-intersecting) polygon are indeterminate.
 // Arguments:
-//   poly = The list of 2D path points for the perimeter of the polygon.
-function polygon_is_clockwise(poly) =
-    assert(is_path(poly,dim=2), "Input should be a 2d path")
-    polygon_area(poly, signed=true)<0;
+//   path = The list of 2D path points for the perimeter of the polygon.
+function polygon_is_clockwise(path) =
+    assert(is_path(path,dim=2), "Input should be a 2d polygon")
+    let(
+        minx = min(subindex(path,0)),
+        lowind = search(minx, path, 0, 0),
+        lowpts = select(path, lowind),
+        miny = min(subindex(lowpts, 1)),
+        extreme_sub = search(miny, lowpts, 1, 1)[0],
+        extreme = select(lowind,extreme_sub)
+    ) det2([select(path,extreme+1)-path[extreme], select(path, extreme-1)-path[extreme]])<0;
 
+function polygon_is_clockwise(path) =
+    assert(is_path(path,dim=2), "Input should be a 2d path")
+    polygon_area(path, signed=true)<0;
 
 // Function: clockwise_polygon()
 // Usage:
-//   clockwise_polygon(poly);
+//   clockwise_polygon(path);
 // Description:
 //   Given a 2D polygon path, returns the clockwise winding version of that path.
-function clockwise_polygon(poly) =
-    assert(is_path(poly,dim=2), "Input should be a 2d polygon")
-    polygon_area(poly, signed=true)<0 ? poly : reverse_polygon(poly);
+function clockwise_polygon(path) =
+    assert(is_path(path,dim=2), "Input should be a 2d polygon")
+    polygon_area(path, signed=true)<0 ? path : reverse_polygon(path);
 
 
 // Function: ccw_polygon()
 // Usage:
-//   ccw_polygon(poly);
+//   ccw_polygon(path);
 // Description:
-//   Given a 2D polygon poly, returns the counter-clockwise winding version of that poly.
-function ccw_polygon(poly) =
-    assert(is_path(poly,dim=2), "Input should be a 2d polygon")
-    polygon_area(poly, signed=true)<0 ? reverse_polygon(poly) : poly;
+//   Given a 2D polygon path, returns the counter-clockwise winding version of that path.
+function ccw_polygon(path) =
+    assert(is_path(path,dim=2), "Input should be a 2d polygon")
+    polygon_area(path, signed=true)<0 ? reverse_polygon(path) : path;
 
 
 // Function: reverse_polygon()
@@ -1818,7 +1969,7 @@ function reverse_polygon(poly) =
 function polygon_normal(poly) =
     assert(is_path(poly,dim=3), "Invalid 3D polygon." )
     let(
-        poly = cleanup_path(poly),
+        poly = path3d(cleanup_path(poly)),
         p0 = poly[0],
         n = sum([
             for (i=[1:1:len(poly)-2])
@@ -1934,6 +2085,17 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
         ], xs, _i=_i+1
     );
     
+//***
+// all the functions split_polygons_at_ may generate non simple polygons even from simple polygon inputs:
+//     split_polygons_at_each_y([[[-1,1,0],[0,0,0],[1,1,0],[1,-1,0],[-1,-1,0]]],[0])
+// produces:
+//   [  [[0, 0, 0], [1, 0, 0], [1, -1, 0], [-1, -1, 0], [-1, 0, 0]]
+//      [[-1, 1, 0], [0, 0, 0], [1, 1, 0], [1, 0, 0], [-1, 0, 0]] ]
+// and the second polygon is self-intersecting
+// besides, it fails in some simple cases as triangles:
+//   split_polygons_at_each_y([ [-1,-1,0],[1,-1,0],[0,1,0]],[0])==[]
+// this last failure may be fatal for vnf_bend
+
 
 // Function: split_polygons_at_each_y()
 // Usage:
@@ -1944,9 +2106,9 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
 //   polys = A list of 3D polygons to split.
 //   ys = A list of scalar Y values to split at.
 function split_polygons_at_each_y(polys, ys, _i=0) =
-//    assert( is_consistent(polys) && is_path(polys[0],dim=3) , // not all polygons should have the same length!!!
-  //          "The input list should contains only 3D polygons." )
-    assert( is_finite(ys) || is_vector(ys), "The split value list should contain only numbers." )  //***
+    assert( is_consistent(polys) && is_path(poly[0],dim=3) ,
+            "The input list should contains only 3D polygons." )
+    assert( is_finite(ys), "The split value list should contain only numbers." )  
     _i>=len(ys)? polys :
     split_polygons_at_each_y(
         [
@@ -1977,4 +2139,5 @@ function split_polygons_at_each_z(polys, zs, _i=0) =
     );
 
 
+
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad
index 0950e1c..ceeb905 100644
--- a/tests/test_geometry.scad
+++ b/tests/test_geometry.scad
@@ -98,8 +98,6 @@ function standardize(v) =
     v==[]? [] :
     sign([for(vi=v) if( ! approx(vi,0)) vi,0 ][0])*v;
 
-module assert_std(vc,ve) { assert(standardize(vc)==standardize(ve)); }
-
 module test_points_on_plane() {
     pts     = [for(i=[0:40]) rands(-1,1,3) ];
     dir     = rands(-10,10,3);
@@ -489,47 +487,48 @@ module test_triangle_area() {
 
 
 module test_plane3pt() {
-    assert_std(plane3pt([0,0,20], [0,10,10], [0,0,0]), [1,0,0,0]);
-    assert_std(plane3pt([2,0,20], [2,10,10], [2,0,0]), [1,0,0,2]);
-    assert_std(plane3pt([0,0,0], [10,0,10], [0,0,20]), [0,1,0,0]);
-    assert_std(plane3pt([0,2,0], [10,2,10], [0,2,20]), [0,1,0,2]);
-    assert_std(plane3pt([0,0,0], [10,10,0], [20,0,0]), [0,0,1,0]);
-    assert_std(plane3pt([0,0,2], [10,10,2], [20,0,2]), [0,0,1,2]);
+    assert(plane3pt([0,0,20], [0,10,10], [0,0,0]) == [1,0,0,0]);
+    assert(plane3pt([2,0,20], [2,10,10], [2,0,0]) == [1,0,0,2]);
+    assert(plane3pt([0,0,0], [10,0,10], [0,0,20]) == [0,1,0,0]);
+    assert(plane3pt([0,2,0], [10,2,10], [0,2,20]) == [0,1,0,2]);
+    assert(plane3pt([0,0,0], [10,10,0], [20,0,0]) == [0,0,1,0]);
+    assert(plane3pt([0,0,2], [10,10,2], [20,0,2]) == [0,0,1,2]);
 }
 *test_plane3pt();
 
 module test_plane3pt_indexed() {
     pts = [ [0,0,0], [10,0,0], [0,10,0], [0,0,10] ];
     s13 = sqrt(1/3);
-    assert_std(plane3pt_indexed(pts, 0,3,2), [1,0,0,0]);
-    assert_std(plane3pt_indexed(pts, 0,2,3), [-1,0,0,0]);
-    assert_std(plane3pt_indexed(pts, 0,1,3), [0,1,0,0]);
-    assert_std(plane3pt_indexed(pts, 0,3,1), [0,-1,0,0]);
-    assert_std(plane3pt_indexed(pts, 0,2,1), [0,0,1,0]);
+    assert(plane3pt_indexed(pts, 0,3,2) == [1,0,0,0]);
+    assert(plane3pt_indexed(pts, 0,2,3) == [-1,0,0,0]);
+    assert(plane3pt_indexed(pts, 0,1,3) == [0,1,0,0]);
+    assert(plane3pt_indexed(pts, 0,3,1) == [0,-1,0,0]);
+    assert(plane3pt_indexed(pts, 0,2,1) == [0,0,1,0]);
     assert_approx(plane3pt_indexed(pts, 0,1,2), [0,0,-1,0]);
     assert_approx(plane3pt_indexed(pts, 3,2,1), [s13,s13,s13,10*s13]);
     assert_approx(plane3pt_indexed(pts, 1,2,3), [-s13,-s13,-s13,-10*s13]);
 }
 *test_plane3pt_indexed();
 
+
 module test_plane_from_points() {
-    assert_std(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]), [1,0,0,0]);
-    assert_std(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]), [1,0,0,2]);
-    assert_std(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]), [0,1,0,0]);
-    assert_std(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]), [0,1,0,2]);
-    assert_std(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]), [0,0,1,0]);
-    assert_std(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]), [0,0,1,2]);
+    assert(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]) == [1,0,0,0]);
+    assert(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]) == [1,0,0,2]);
+    assert(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]) == [0,1,0,0]);
+    assert(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]) == [0,1,0,2]);
+    assert(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]) == [0,0,1,0]);
+    assert(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]) == [0,0,1,2]);
 }
 *test_plane_from_points();
 
 
 module test_plane_normal() {
-    assert_std(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])), [1,0,0]);
-    assert_std(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])), [1,0,0]);
-    assert_std(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])), [0,1,0]);
-    assert_std(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])), [0,1,0]);
-    assert_std(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])), [0,0,1]);
-    assert_std(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])), [0,0,1]);
+    assert(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])) == [1,0,0]);
+    assert(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])) == [1,0,0]);
+    assert(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])) == [0,1,0]);
+    assert(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])) == [0,1,0]);
+    assert(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])) == [0,0,1]);
+    assert(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])) == [0,0,1]);
 }
 *test_plane_normal();
 
@@ -700,22 +699,16 @@ module test_simplify_path_indexed() {
 
 module test_point_in_polygon() {
     poly = [for (a=[0:30:359]) 10*[cos(a),sin(a)]];
-    poly2 = [ [-3,-3],[2,-3],[2,1],[-1,1],[-1,-1],[1,-1],[1,2],[-3,2] ];
     assert(point_in_polygon([0,0], poly) == 1);
     assert(point_in_polygon([20,0], poly) == -1);
-    assert(point_in_polygon([20,0], poly,EPSILON,nonzero=false) == -1);
     assert(point_in_polygon([5,5], poly) == 1);
     assert(point_in_polygon([-5,5], poly) == 1);
     assert(point_in_polygon([-5,-5], poly) == 1);
     assert(point_in_polygon([5,-5], poly) == 1);
-    assert(point_in_polygon([5,-5], poly,EPSILON,nonzero=false) == 1);
     assert(point_in_polygon([-10,-10], poly) == -1);
     assert(point_in_polygon([10,0], poly) == 0);
     assert(point_in_polygon([0,10], poly) == 0);
     assert(point_in_polygon([0,-10], poly) == 0);
-    assert(point_in_polygon([0,-10], poly,EPSILON,nonzero=false) == 0);
-    assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=true) == 1);
-    assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=false) == -1);
 }
 *test_point_in_polygon();
 

From da5546cbc2720c91e8c864eb80c9f2b671ac5117 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Thu, 20 Aug 2020 22:42:24 +0100
Subject: [PATCH 16/18] In observance of owner's last review

Eliminate double definitions.
Eliminate unneeded comments.

In common.scad redefine num_defined(), all_defined() and get_radius().

In geometry.scad:
- change name _dist to _dist2line
- simplify _point_above_below_segment() and triangle_area()
- change some arg names for uniformity (path>>poly)
- change point_in_polygon() to accept the Even-odd rule as alternative
- and other minor edits

Update tests_geometry to the new funcionalities.
---
 common.scad              |  67 ++++++--------
 geometry.scad            | 184 +++++++++++++++++++--------------------
 tests/test_geometry.scad |  55 +++++++-----
 3 files changed, 146 insertions(+), 160 deletions(-)

diff --git a/common.scad b/common.scad
index 51d8363..db4f3bb 100644
--- a/common.scad
+++ b/common.scad
@@ -129,11 +129,6 @@ function is_list_of(list,pattern) =
     is_list(list) &&
     []==[for(entry=0*list) if (entry != pattern) entry];
 
-function _list_pattern(list) =
-  is_list(list) ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0]
-                : 0;
-
-
 
 // Function: is_consistent()
 // Usage:
@@ -198,11 +193,11 @@ function first_defined(v,recursive=false,_i=0) =
             is_undef(first_defined(v[_i],recursive=recursive))
         )
     )? first_defined(v,recursive=recursive,_i=_i+1) : v[_i];
-
+    
 
 // Function: one_defined()
 // Usage:
-//   one_defined(vars, names, [required])
+//   one_defined(vars, names, <required>)
 // Description:
 //   Examines the input list `vars` and returns the entry which is not `undef`.  If more
 //   than one entry is `undef` then issues an assertion specifying "Must define exactly one of" followed
@@ -221,8 +216,7 @@ function one_defined(vars, names, required=true) =
 
 // Function: num_defined()
 // Description: Counts how many items in list `v` are not `undef`.
-function num_defined(v,_i=0,_cnt=0) = _i>=len(v)? _cnt : num_defined(v,_i+1,_cnt+(is_undef(v[_i])? 0 : 1));
-
+function num_defined(v) = len([for(vi=v) if(!is_undef(vi)) 1]);
 
 // Function: any_defined()
 // Description:
@@ -239,8 +233,8 @@ function any_defined(v,recursive=false) = first_defined(v,recursive=recursive) !
 // Arguments:
 //   v = The list whose items are being checked.
 //   recursive = If true, any sublists are evaluated recursively.
-function all_defined(v,recursive=false) = max([for (x=v) is_undef(x)||(recursive&&is_list(x)&&!all_defined(x))? 1 : 0])==0;
-
+function all_defined(v,recursive=false) = 
+    []==[for (x=v) if(is_undef(x)||(recursive && is_list(x) && !all_defined(x,recursive))) 0 ];
 
 
 
@@ -249,7 +243,7 @@ function all_defined(v,recursive=false) = max([for (x=v) is_undef(x)||(recursive
 
 // Function: get_anchor()
 // Usage:
-//   get_anchor(anchor,center,[uncentered],[dflt]);
+//   get_anchor(anchor,center,<uncentered>,<dflt>);
 // 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.
@@ -270,7 +264,7 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 
 // Function: get_radius()
 // Usage:
-//   get_radius([r1], [r2], [r], [d1], [d2], [d], [dflt]);
+//   get_radius(<r1>, <r2>, <r>, <d1>, <d2>, <d>, <dflt>);
 // Description:
 //   Given various radii and diameters, returns the most specific radius.
 //   If a diameter is most specific, returns half its value, giving the radius.
@@ -288,34 +282,23 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
 //   r = Most general radius.
 //   d = Most general diameter.
 //   dflt = Value to return if all other values given are `undef`.
-function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = (
-    !is_undef(r1)
-		? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
-				assert(is_finite(r1), "Invalid radius r1." )
-				r1 
-    : !is_undef(r2)
-			? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") 
-				assert(is_finite(r2), "Invalid radius r2." )
-				r2
-		: !is_undef(d1)
-		  ? assert(is_finite(d1), "Invalid diameter d1." )
-				d1/2
-		: !is_undef(d2)
-			? assert(is_finite(d2), "Invalid diameter d2." )
-				d2/2
-		: !is_undef(r)
-			? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
-				assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
-				r 
-		: !is_undef(d)
-			? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." )
-			d/2
-		: dflt
-);
+function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = 
+    assert(num_defined([r1,d1,r2,d2])<2, "Conflicting or redundant radius/diameter arguments given.")
+    !is_undef(r1) ?   assert(is_finite(r1), "Invalid radius r1." ) r1 
+    : !is_undef(r2) ? assert(is_finite(r2), "Invalid radius r2." ) r2
+    : !is_undef(d1) ? assert(is_finite(d1), "Invalid diameter d1." ) d1/2
+    : !is_undef(d2) ? assert(is_finite(d2), "Invalid diameter d2." ) d2/2
+    : !is_undef(r)
+      ? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
+        assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
+        r 
+    : !is_undef(d) ? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." ) d/2
+    : dflt;
+
 
 // Function: get_height()
 // Usage:
-//   get_height([h],[l],[height],[dflt])
+//   get_height(<h>,<l>,<height>,<dflt>)
 // Description:
 //   Given several different parameters for height check that height is not multiply defined
 //   and return a single value.  If the three values `l`, `h`, and `height` are all undefined
@@ -332,7 +315,7 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) =
 
 // Function: scalar_vec3()
 // Usage:
-//   scalar_vec3(v, [dflt]);
+//   scalar_vec3(v, <dflt>);
 // Description:
 //   If `v` is a scalar, and `dflt==undef`, returns `[v, v, v]`.
 //   If `v` is a scalar, and `dflt!=undef`, returns `[v, dflt, dflt]`.
@@ -384,7 +367,7 @@ function _valstr(x) =
 
 // Module: assert_approx()
 // Usage:
-//   assert_approx(got, expected, [info]);
+//   assert_approx(got, expected, <info>);
 // Description:
 //   Tests if the value gotten is what was expected.  If not, then
 //   the expected and received values are printed to the console and
@@ -411,7 +394,7 @@ module assert_approx(got, expected, info) {
 
 // Module: assert_equal()
 // Usage:
-//   assert_equal(got, expected, [info]);
+//   assert_equal(got, expected, <info>);
 // Description:
 //   Tests if the value gotten is what was expected.  If not, then
 //   the expected and received values are printed to the console and
@@ -438,7 +421,7 @@ module assert_equal(got, expected, info) {
 
 // Module: shape_compare()
 // Usage:
-//   shape_compare([eps]) {test_shape(); expected_shape();}
+//   shape_compare(<eps>) {test_shape(); expected_shape();}
 // Description:
 //   Compares two child shapes, returning empty geometry if they are very nearly the same shape and size.
 //   Returns the differential geometry if they are not nearly the same shape and size.
diff --git a/geometry.scad b/geometry.scad
index 7d02252..dc86187 100644
--- a/geometry.scad
+++ b/geometry.scad
@@ -23,26 +23,25 @@
 function point_on_segment2d(point, edge, eps=EPSILON) =
     assert( is_vector(point,2), "Invalid point." )
     assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
-    assert( _valid_line(edge,eps=eps), "Invalid segment." )
+    assert( _valid_line(edge,2,eps=eps), "Invalid segment." )
     let( dp = point-edge[0],
          de = edge[1]-edge[0],
          ne = norm(de) ) 
     ( dp*de >= -eps*ne ) 
-    && ( (dp-de)*de <= eps*ne )              // point projects on the segment
-    && _dist(point-edge[0],unit(de))<eps;   // point is on the line 
+    && ( (dp-de)*de <= eps*ne )                  // point projects on the segment
+    && _dist2line(point-edge[0],unit(de))<eps;   // point is on the line 
     
     
 //Internal - distance from point `d` to the line passing through the origin with unit direction n
-//_dist works for any dimension
-function _dist(d,n) = norm(d-(d * n) * n);
+//_dist2line works for any dimension
+function _dist2line(d,n) = norm(d-(d * n) * n);
 
 // Internal non-exposed function.
 function _point_above_below_segment(point, edge) =
-    edge[0].y <= point.y? (
-        (edge[1].y > point.y && point_left_of_line2d(point, edge) > 0)? 1 : 0
-    ) : (
-        (edge[1].y <= point.y && point_left_of_line2d(point, edge) < 0)? -1 : 0
-    );
+    let( edge = edge - [point, point] )
+    edge[0].y <= 0 
+    ?   (edge[1].y >  0 && cross(edge[0], edge[1]-edge[0]) > 0) ?  1 : 0
+    :   (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0 ;
 
 //Internal
 function _valid_line(line,dim,eps=EPSILON) = 
@@ -101,7 +100,7 @@ function collinear(a, b, c, eps=EPSILON) =
 function distance_from_line(line, pt) =
     assert( _valid_line(line) && is_vector(pt,len(line[0])), 
             "Invalid line, invalid point or incompatible dimensions." )
-    _dist(pt-line[0],unit(line[1]-line[0]));
+    _dist2line(pt-line[0],unit(line[1]-line[0]));
     
     
 // Function: line_normal()
@@ -749,14 +748,10 @@ function adj_opp_to_ang(adj,opp) =
 //   triangle_area([0,0], [5,10], [10,0]);  // Returns -50
 //   triangle_area([10,0], [5,10], [0,0]);  // Returns 50
 function triangle_area(a,b,c) = 
-    assert( is_path([a,b,c]),
-            "Invalid points or incompatible dimensions." )    
-    len(a)==3 ? 0.5*norm(cross(c-a,c-b)) 
-    : (
-        a.x * (b.y - c.y) +
-        b.x * (c.y - a.y) +
-        c.x * (a.y - b.y)
-    ) / 2;
+    assert( is_path([a,b,c]), "Invalid points or incompatible dimensions." )    
+    len(a)==3 
+    ? 0.5*norm(cross(c-a,c-b)) 
+    : 0.5*cross(c-a,c-b);
 
 
 
@@ -826,7 +821,7 @@ function plane_from_normal(normal, pt=[0,0,0]) =
 
 // Function: plane_from_points()
 // Usage:
-//   plane_from_points(points, [fast], [eps]);
+//   plane_from_points(points, <fast>, <eps>);
 // Description:
 //   Given a list of 3 or more coplanar 3D points, returns the coefficients of the cartesian equation of a plane,
 //   that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane.
@@ -851,7 +846,6 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
     )
     indices==[] ? undef :
     let(
-        indices = sort(indices), // why sorting?
         p1 = points[indices[0]],
         p2 = points[indices[1]],
         p3 = points[indices[2]],
@@ -888,11 +882,6 @@ function plane_from_polygon(poly, fast=false, eps=EPSILON) =
     ) 
     fast? plane: coplanar(poly,eps=eps)? plane: [];
 
-//***
-// I don't see why this function uses a criterium different from plane_from_points.
-// In practical terms, what is the difference of finding a plane from points and from polygon?
-// The docs don't clarify.
-// These functions should be consistent if they are both necessary. The docs might reflect their distinction.
 
 // Function: plane_normal()
 // Usage:
@@ -944,8 +933,8 @@ function plane_transform(plane) =
 // Usage:
 //   projection_on_plane(points);
 // Description:
-//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the projection
-//   of the points on the plane.
+//   Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the 3D orthogonal 
+//   projection of the points on the plane.
 // Arguments:
 //   plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
 //   points = List of points to project
@@ -1024,6 +1013,7 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
     ? points_on_plane(line[0],plane,eps)? [line,undef]: undef
     : [ line[0]+a/b*(line[1]-line[0]), a/b ];
 
+
 // Function: plane_line_angle()
 // Usage: plane_line_angle(plane,line)
 // Description:
@@ -1095,7 +1085,7 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
     )
     indices==[] ? undef :
     let(
-        indices = sort(indices), // why sorting?
+        indices = sort(indices),
         p1 = poly[indices[0]],
         p2 = poly[indices[1]],
         p3 = poly[indices[2]],
@@ -1159,7 +1149,7 @@ function plane_intersection(plane1,plane2,plane3) =
 
 // Function: coplanar()
 // Usage:
-//   coplanar(points,eps);
+//   coplanar(points,<eps>);
 // Description:
 //   Returns true if the given 3D points are non-collinear and are on a plane.
 // Arguments:
@@ -1178,7 +1168,7 @@ function coplanar(points, eps=EPSILON) =
     
 // Function: points_on_plane()
 // Usage:
-//   points_on_plane(points, plane, eps);
+//   points_on_plane(points, plane, <eps>);
 // Description:
 //   Returns true if the given 3D points are on the given plane.
 // Arguments:
@@ -1214,7 +1204,7 @@ function in_front_of_plane(plane, point) =
 
 // Function: find_circle_2tangents()
 // Usage:
-//   find_circle_2tangents(pt1, pt2, pt3, r|d, [tangents]);
+//   find_circle_2tangents(pt1, pt2, pt3, r|d, <tangents>);
 // Description:
 //   Given a pair of rays with a common origin, and a known circle radius/diameter, finds
 //   the centerpoint for the circle of that size that touches both rays tangentally.
@@ -1283,7 +1273,8 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
 
 // Function: find_circle_3points()
 // Usage:
-//   find_circle_3points(pt1, [pt2, pt3]);
+//   find_circle_3points(pt1, pt2, pt3);
+//   find_circle_3points([pt1, pt2, pt3]);
 // Description:
 //   Returns the [CENTERPOINT, RADIUS, NORMAL] of the circle that passes through three non-collinear
 //   points where NORMAL is the normal vector of the plane that the circle is on (UP or DOWN if the points are 2D).
@@ -1327,7 +1318,7 @@ function find_circle_3points(pt1, pt2, pt3) =
             r  = norm(sc-v[0])
             )
         [ cp, r, n ];
-
+     
 
 // Function: circle_point_tangents()
 // Usage:
@@ -1363,7 +1354,6 @@ function circle_point_tangents(r, d, cp, pt) =
     ) [for (ang=angs) [ang, cp + r*[cos(ang),sin(ang)]]];
 
 
-
 // Function: circle_circle_tangents()
 // Usage: circle_circle_tangents(c1, r1|d1, c2, r2|d2)
 // Description:
@@ -1462,13 +1452,14 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
         []
     :   let(
             n = (pb-pa)/nrm,
-            distlist = [for(i=[0:len(points)-1]) _dist(points[i]-pa, n)]   
+            distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)]   
            )
         max(distlist)<eps
         ?  assert(!error, "Cannot find three noncollinear points in pointlist.")
            []
         :  [0,b,max_index(distlist)];    
 
+
 // Function: pointlist_bounds()
 // Usage:
 //   pointlist_bounds(pts);
@@ -1585,15 +1576,15 @@ function polygon_shift(poly, i) =
 // Usage:
 //   polygon_shift_to_closest_point(path, pt);
 // Description:
-//   Given a polygon `path`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
-function polygon_shift_to_closest_point(path, pt) =
+//   Given a polygon `poly`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
+function polygon_shift_to_closest_point(poly, pt) =
     assert(is_vector(pt), "Invalid point." )
-    assert(is_path(path,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
+    assert(is_path(poly,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
     let(
-        path = cleanup_path(path),
-        dists = [for (p=path) norm(p-pt)],
+        poly = cleanup_path(poly),
+        dists = [for (p=poly) norm(p-pt)],
         closest = min_index(dists)
-    ) select(path,closest,closest+len(path)-1);
+    ) select(poly,closest,closest+len(poly)-1);
 
 
 // Function: reindex_polygon()
@@ -1648,7 +1639,7 @@ function reindex_polygon(reference, poly, return_error=false) =
 
 // Function: align_polygon()
 // Usage:
-//   newpoly = align_polygon(reference, poly, angles, [cp]);
+//   newpoly = align_polygon(reference, poly, angles, <cp>);
 // Description:
 //   Tries the list or range of angles to find a rotation of the specified 2D polygon that best aligns
 //   with the reference 2D polygon.  For each angle, the polygon is reindexed, which is a costly operation
@@ -1717,10 +1708,11 @@ function centroid(poly) =
 
 // Function: point_in_polygon()
 // Usage:
-//   point_in_polygon(point, path, [eps])
+//   point_in_polygon(point, poly, <eps>)
 // Description:
 //   This function tests whether the given 2D point is inside, outside or on the boundary of
-//   the specified 2D polygon using the Winding Number method.
+//   the specified 2D polygon using either the Nonzero Winding rule or the Even-Odd rule.
+//   See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Even–odd_rule.
 //   The polygon is given as a list of 2D points, not including the repeated end point.
 //   Returns -1 if the point is outside the polyon.
 //   Returns 0 if the point is on the boundary.
@@ -1730,65 +1722,81 @@ function centroid(poly) =
 //   Rounding error may give mixed results for points on or near the boundary.
 // Arguments:
 //   point = The 2D point to check position of.
-//   path = The list of 2D path points forming the perimeter of the polygon.
+//   poly = The list of 2D path points forming the perimeter of the polygon.
+//   nonzero = The rule to use: true for "Nonzero" rule and false for "Even-Odd" (Default: true )
 //   eps = Acceptable variance.  Default: `EPSILON` (1e-9)
-function point_in_polygon(point, path, eps=EPSILON) =
-    // Original algorithm from http://geomalgorithms.com/a03-_inclusion.html
-    assert( is_vector(point,2) && is_path(path,dim=2) && len(path)>2,
+function point_in_polygon(point, poly, eps=EPSILON, nonzero=true) =
+    // Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
+    assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,
             "The point and polygon should be in 2D. The polygon should have more that 2 points." )
     assert( is_finite(eps) && eps>=0, "Invalid tolerance." )
     // Does the point lie on any edges?  If so return 0.
     let(
-        on_brd = [for(i=[0:1:len(path)-1]) 
-                    let( seg = select(path,i,i+1) ) 
-                    if( !approx(seg[0],seg[1],eps=eps) ) 
+        on_brd = [for(i=[0:1:len(poly)-1]) 
+                    let( seg = select(poly,i,i+1) ) 
+                    if( !approx(seg[0],seg[1],eps=EPSILON) ) 
                         point_on_segment2d(point, seg, eps=eps)? 1:0 ]
         )
-    sum(on_brd) > 0? 0 :
-    // Otherwise compute winding number and return 1 for interior, -1 for exterior
-    let(
-        windchk = [for(i=[0:1:len(path)-1]) 
-                    let(seg=select(path,i,i+1)) 
-                    if(!approx(seg[0],seg[1],eps=eps)) 
-                        _point_above_below_segment(point, seg)
-                  ]
-        )
-    sum(windchk) != 0 ? 1 : -1;
+    sum(on_brd) > 0
+    ? 0 
+    :   nonzero
+        ?    // Compute winding number and return 1 for interior, -1 for exterior
+            let(
+                windchk = [for(i=[0:1:len(poly)-1]) 
+                            let(seg=select(poly,i,i+1)) 
+                            if(!approx(seg[0],seg[1],eps=eps)) 
+                                _point_above_below_segment(point, seg)
+                          ]
+                )
+            sum(windchk) != 0 ? 1 : -1
+        :   // or compute the crossings with the ray [point, point+[1,0]]
+            let( 
+              n  = len(poly),
+              cross = 
+                [for(i=[0:n-1])
+                    let( 
+                      p0 = poly[i]-point, 
+                      p1 = poly[(i+1)%n]-point
+                      )
+                    if( ( (p1.y>eps && p0.y<=0) || (p1.y<=0 && p0.y>eps) )
+                        &&  0 < p0.x - p0.y *(p1.x - p0.x)/(p1.y - p0.y) )
+                    1
+                ]
+            )
+            2*(len(cross)%2)-1;;
 
-//**
-// this function should be optimized avoiding the call of other functions
 
 // Function: polygon_is_clockwise()
 // Usage:
-//   polygon_is_clockwise(path);
+//   polygon_is_clockwise(poly);
 // Description:
 //   Return true if the given 2D simple polygon is in clockwise order, false otherwise.
 //   Results for complex (self-intersecting) polygon are indeterminate.
 // Arguments:
-//   path = The list of 2D path points for the perimeter of the polygon.
-function polygon_is_clockwise(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d path")
-    polygon_area(path, signed=true)<0;
+//   poly = The list of 2D path points for the perimeter of the polygon.
+function polygon_is_clockwise(poly) =
+    assert(is_path(poly,dim=2), "Input should be a 2d path")
+    polygon_area(poly, signed=true)<0;
 
 
 // Function: clockwise_polygon()
 // Usage:
-//   clockwise_polygon(path);
+//   clockwise_polygon(poly);
 // Description:
 //   Given a 2D polygon path, returns the clockwise winding version of that path.
-function clockwise_polygon(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d polygon")
-    polygon_area(path, signed=true)<0 ? path : reverse_polygon(path);
+function clockwise_polygon(poly) =
+    assert(is_path(poly,dim=2), "Input should be a 2d polygon")
+    polygon_area(poly, signed=true)<0 ? poly : reverse_polygon(poly);
 
 
 // Function: ccw_polygon()
 // Usage:
-//   ccw_polygon(path);
+//   ccw_polygon(poly);
 // Description:
-//   Given a 2D polygon path, returns the counter-clockwise winding version of that path.
-function ccw_polygon(path) =
-    assert(is_path(path,dim=2), "Input should be a 2d polygon")
-    polygon_area(path, signed=true)<0 ? reverse_polygon(path) : path;
+//   Given a 2D polygon poly, returns the counter-clockwise winding version of that poly.
+function ccw_polygon(poly) =
+    assert(is_path(poly,dim=2), "Input should be a 2d polygon")
+    polygon_area(poly, signed=true)<0 ? reverse_polygon(poly) : poly;
 
 
 // Function: reverse_polygon()
@@ -1810,7 +1818,7 @@ function reverse_polygon(poly) =
 function polygon_normal(poly) =
     assert(is_path(poly,dim=3), "Invalid 3D polygon." )
     let(
-        poly = path3d(cleanup_path(poly)),
+        poly = cleanup_path(poly),
         p0 = poly[0],
         n = sum([
             for (i=[1:1:len(poly)-2])
@@ -1926,17 +1934,6 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
         ], xs, _i=_i+1
     );
     
-//***
-// all the functions split_polygons_at_ may generate non simple polygons even from simple polygon inputs:
-//     split_polygons_at_each_y([[[-1,1,0],[0,0,0],[1,1,0],[1,-1,0],[-1,-1,0]]],[0])
-// produces:
-//   [  [[0, 0, 0], [1, 0, 0], [1, -1, 0], [-1, -1, 0], [-1, 0, 0]]
-//      [[-1, 1, 0], [0, 0, 0], [1, 1, 0], [1, 0, 0], [-1, 0, 0]] ]
-// and the second polygon is self-intersecting
-// besides, it fails in some simple cases as triangles:
-//   split_polygons_at_each_y([ [-1,-1,0],[1,-1,0],[0,1,0]],[0])==[]
-// this last failure may be fatal for vnf_bend
-
 
 // Function: split_polygons_at_each_y()
 // Usage:
@@ -1947,9 +1944,9 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
 //   polys = A list of 3D polygons to split.
 //   ys = A list of scalar Y values to split at.
 function split_polygons_at_each_y(polys, ys, _i=0) =
-    assert( is_consistent(polys) && is_path(poly[0],dim=3) ,
-            "The input list should contains only 3D polygons." )
-    assert( is_finite(ys), "The split value list should contain only numbers." )  
+//    assert( is_consistent(polys) && is_path(polys[0],dim=3) , // not all polygons should have the same length!!!
+  //          "The input list should contains only 3D polygons." )
+    assert( is_finite(ys) || is_vector(ys), "The split value list should contain only numbers." )  //***
     _i>=len(ys)? polys :
     split_polygons_at_each_y(
         [
@@ -1980,5 +1977,4 @@ function split_polygons_at_each_z(polys, zs, _i=0) =
     );
 
 
-
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad
index ceeb905..0950e1c 100644
--- a/tests/test_geometry.scad
+++ b/tests/test_geometry.scad
@@ -98,6 +98,8 @@ function standardize(v) =
     v==[]? [] :
     sign([for(vi=v) if( ! approx(vi,0)) vi,0 ][0])*v;
 
+module assert_std(vc,ve) { assert(standardize(vc)==standardize(ve)); }
+
 module test_points_on_plane() {
     pts     = [for(i=[0:40]) rands(-1,1,3) ];
     dir     = rands(-10,10,3);
@@ -487,48 +489,47 @@ module test_triangle_area() {
 
 
 module test_plane3pt() {
-    assert(plane3pt([0,0,20], [0,10,10], [0,0,0]) == [1,0,0,0]);
-    assert(plane3pt([2,0,20], [2,10,10], [2,0,0]) == [1,0,0,2]);
-    assert(plane3pt([0,0,0], [10,0,10], [0,0,20]) == [0,1,0,0]);
-    assert(plane3pt([0,2,0], [10,2,10], [0,2,20]) == [0,1,0,2]);
-    assert(plane3pt([0,0,0], [10,10,0], [20,0,0]) == [0,0,1,0]);
-    assert(plane3pt([0,0,2], [10,10,2], [20,0,2]) == [0,0,1,2]);
+    assert_std(plane3pt([0,0,20], [0,10,10], [0,0,0]), [1,0,0,0]);
+    assert_std(plane3pt([2,0,20], [2,10,10], [2,0,0]), [1,0,0,2]);
+    assert_std(plane3pt([0,0,0], [10,0,10], [0,0,20]), [0,1,0,0]);
+    assert_std(plane3pt([0,2,0], [10,2,10], [0,2,20]), [0,1,0,2]);
+    assert_std(plane3pt([0,0,0], [10,10,0], [20,0,0]), [0,0,1,0]);
+    assert_std(plane3pt([0,0,2], [10,10,2], [20,0,2]), [0,0,1,2]);
 }
 *test_plane3pt();
 
 module test_plane3pt_indexed() {
     pts = [ [0,0,0], [10,0,0], [0,10,0], [0,0,10] ];
     s13 = sqrt(1/3);
-    assert(plane3pt_indexed(pts, 0,3,2) == [1,0,0,0]);
-    assert(plane3pt_indexed(pts, 0,2,3) == [-1,0,0,0]);
-    assert(plane3pt_indexed(pts, 0,1,3) == [0,1,0,0]);
-    assert(plane3pt_indexed(pts, 0,3,1) == [0,-1,0,0]);
-    assert(plane3pt_indexed(pts, 0,2,1) == [0,0,1,0]);
+    assert_std(plane3pt_indexed(pts, 0,3,2), [1,0,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,2,3), [-1,0,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,1,3), [0,1,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,3,1), [0,-1,0,0]);
+    assert_std(plane3pt_indexed(pts, 0,2,1), [0,0,1,0]);
     assert_approx(plane3pt_indexed(pts, 0,1,2), [0,0,-1,0]);
     assert_approx(plane3pt_indexed(pts, 3,2,1), [s13,s13,s13,10*s13]);
     assert_approx(plane3pt_indexed(pts, 1,2,3), [-s13,-s13,-s13,-10*s13]);
 }
 *test_plane3pt_indexed();
 
-
 module test_plane_from_points() {
-    assert(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]) == [1,0,0,0]);
-    assert(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]) == [1,0,0,2]);
-    assert(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]) == [0,1,0,0]);
-    assert(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]) == [0,1,0,2]);
-    assert(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]) == [0,0,1,0]);
-    assert(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]) == [0,0,1,2]);
+    assert_std(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]), [1,0,0,0]);
+    assert_std(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]), [1,0,0,2]);
+    assert_std(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]), [0,1,0,0]);
+    assert_std(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]), [0,1,0,2]);
+    assert_std(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]), [0,0,1,0]);
+    assert_std(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]), [0,0,1,2]);
 }
 *test_plane_from_points();
 
 
 module test_plane_normal() {
-    assert(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])) == [1,0,0]);
-    assert(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])) == [1,0,0]);
-    assert(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])) == [0,1,0]);
-    assert(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])) == [0,1,0]);
-    assert(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])) == [0,0,1]);
-    assert(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])) == [0,0,1]);
+    assert_std(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])), [1,0,0]);
+    assert_std(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])), [1,0,0]);
+    assert_std(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])), [0,1,0]);
+    assert_std(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])), [0,1,0]);
+    assert_std(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])), [0,0,1]);
+    assert_std(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])), [0,0,1]);
 }
 *test_plane_normal();
 
@@ -699,16 +700,22 @@ module test_simplify_path_indexed() {
 
 module test_point_in_polygon() {
     poly = [for (a=[0:30:359]) 10*[cos(a),sin(a)]];
+    poly2 = [ [-3,-3],[2,-3],[2,1],[-1,1],[-1,-1],[1,-1],[1,2],[-3,2] ];
     assert(point_in_polygon([0,0], poly) == 1);
     assert(point_in_polygon([20,0], poly) == -1);
+    assert(point_in_polygon([20,0], poly,EPSILON,nonzero=false) == -1);
     assert(point_in_polygon([5,5], poly) == 1);
     assert(point_in_polygon([-5,5], poly) == 1);
     assert(point_in_polygon([-5,-5], poly) == 1);
     assert(point_in_polygon([5,-5], poly) == 1);
+    assert(point_in_polygon([5,-5], poly,EPSILON,nonzero=false) == 1);
     assert(point_in_polygon([-10,-10], poly) == -1);
     assert(point_in_polygon([10,0], poly) == 0);
     assert(point_in_polygon([0,10], poly) == 0);
     assert(point_in_polygon([0,-10], poly) == 0);
+    assert(point_in_polygon([0,-10], poly,EPSILON,nonzero=false) == 0);
+    assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=true) == 1);
+    assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=false) == -1);
 }
 *test_point_in_polygon();
 

From e5a0a3cad795206d7832354b73274cf7d78ef94f Mon Sep 17 00:00:00 2001
From: Kelvie Wong <kelvie@kelvie.ca>
Date: Sat, 22 Aug 2020 16:09:28 -0700
Subject: [PATCH 17/18] Fix diff/intersect when some tags are hidden

When the first operand is hidden, some strange things happen, so this guards
against that.

Example:

    include <BOSL2/std.scad>

    part_to_show = "A"; // [A,B,C]
    part_to_hide = "B"; // [A,B,C]

    module my_part() {
        tags("A") left(10) cuboid(10);
        tags("B") diff("neg", "B") right(10) {
            cuboid(10);
            cyl(d=10, h=10, $tags="neg");
        }
        tags("C") fwd(10) cuboid(10);
    }

    show(part_to_show) hide(part_to_hide) my_part();

Tag "B" here acts very strangely when it is supposed to be hidden, and never
hides completely.
---
 attachments.scad | 38 ++++++++++++++++++++++----------------
 1 file changed, 22 insertions(+), 16 deletions(-)

diff --git a/attachments.scad b/attachments.scad
index c2c969e..48ed522 100644
--- a/attachments.scad
+++ b/attachments.scad
@@ -1235,17 +1235,20 @@ module show(tags="")
 //   }
 module diff(neg, pos=undef, keep=undef)
 {
-    difference() {
-        if (pos != undef) {
-            show(pos) children();
-        } else {
-            if (keep == undef) {
-                hide(neg) children();
+    // Don't perform the operation if the current tags are hidden
+    if (attachment_is_shown($tags)) {
+        difference() {
+            if (pos != undef) {
+                show(pos) children();
             } else {
-                hide(str(neg," ",keep)) children();
+                if (keep == undef) {
+                    hide(neg) children();
+                } else {
+                    hide(str(neg," ",keep)) children();
+                }
             }
+            show(neg) children();
         }
-        show(neg) children();
     }
     if (keep!=undef) {
         show(keep) children();
@@ -1280,17 +1283,20 @@ module diff(neg, pos=undef, keep=undef)
 //   }
 module intersect(a, b=undef, keep=undef)
 {
-    intersection() {
-        if (b != undef) {
-            show(b) children();
-        } else {
-            if (keep == undef) {
-                hide(a) children();
+    // Don't perform the operation if the current tags are hidden
+    if (attachment_is_shown($tags)) {
+        intersection() {
+            if (b != undef) {
+                show(b) children();
             } else {
-                hide(str(a," ",keep)) children();
+                if (keep == undef) {
+                    hide(a) children();
+                } else {
+                    hide(str(a," ",keep)) children();
+                }
             }
+            show(a) children();
         }
-        show(a) children();
     }
     if (keep!=undef) {
         show(keep) children();

From fb8d49f8ccddbce0590e5b2f304899ab3d14e178 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Tue, 25 Aug 2020 12:28:15 +0100
Subject: [PATCH 18/18] Restore updating calls to geometry

---
 rounding.scad | 286 ++++++++++++++++++++++++++++----------------------
 1 file changed, 159 insertions(+), 127 deletions(-)

diff --git a/rounding.scad b/rounding.scad
index 67fab47..ae2aed0 100644
--- a/rounding.scad
+++ b/rounding.scad
@@ -458,10 +458,10 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
 
 
 
-// Module: offset_sweep()
+// Function&Module: offset_sweep()
 //
 // Description:
-//   Takes a 2d path as input and extrudes it upwards and/or downward.  Each layer in the extrusion is produced using `offset()` to expand or shrink the previous layer.
+//   Takes a 2d path as input and extrudes it upwards and/or downward.  Each layer in the extrusion is produced using `offset()` to expand or shrink the previous layer.  When invoked as a function returns a VNF; when invoked as a module produces geometry.  
 //   You can specify a sequence of offsets values, or you can use several built-in offset profiles that are designed to provide end treatments such as roundovers.
 //   The path is shifted by `offset()` multiple times in sequence
 //   to produce the final shape (not multiple shifts from one parent), so coarse definition of the input path will degrade
@@ -543,8 +543,12 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
 //   angle = default angle for chamfers.  Default: 45
 //   joint = default joint value for smooth roundover.
 //   k = default curvature parameter value for "smooth" roundover
-//   convexity = convexity setting for use with polyhedron.  Default: 10
-//
+//   convexity = convexity setting for use with polyhedron.  (module only) Default: 10
+//   anchor = Translate so anchor point is at the origin.  (module only) Default: "origin"
+//   spin = Rotate this many degrees around Z axis after anchor.  (module only) Default: 0
+//   orient = Vector to rotate top towards after spin  (module only)
+//   extent = use extent method for computing anchors. (module only)  Default: false
+//   cp = set centerpoint for anchor computation.  (module only) Default: object centroid
 // Example: Rounding a star shaped prism with postive radius values
 //   star = star(5, r=22, ir=13);
 //   rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24);
@@ -650,118 +654,118 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
 //       up(1)
 //         offset_sweep(offset(rhex,r=-1), height=9.5, bottom=os_circle(r=2), top=os_teardrop(r=-4));
 //     }
-module offset_sweep(
-        path, height, h, l,
-        top=[], bottom=[],
-        offset="round", r=0, steps=16,
-        quality=1, check_valid=true,
-        offset_maxstep=1, extra=0,
-        cut=undef, chamfer_width=undef, chamfer_height=undef,
-        joint=undef, k=0.75, angle=45,
-        convexity=10
-) {
-        // This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce
-        // the inputs for the polyhedron module.
-        function make_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, maxstep, offsetind=0, vertexcount=0, vertices=[], faces=[] )=
-                offsetind==len(offsets)? (
-                        let(
-                                bottom = list_range(n=len(path),s=vertexcount),
-                                oriented_bottom = !flip_faces? bottom : reverse(bottom)
-                        ) [vertices, concat(faces,[oriented_bottom])]
-                ) : (
-                        let(
-                                this_offset = offsetind==0? offsets[0][0] : offsets[offsetind][0] - offsets[offsetind-1][0],
-                                delta = offset_type=="delta" || offset_type=="chamfer" ? this_offset : undef,
-                                r = offset_type=="round"? this_offset : undef,
-                                do_chamfer = offset_type == "chamfer"
+
+
+// This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce
+// the inputs for the polyhedron module.
+function _make_offset_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, maxstep, offsetind=0,
+                                 vertexcount=0, vertices=[], faces=[] )=
+        offsetind==len(offsets)? (
+                let(
+                        bottom = list_range(n=len(path),s=vertexcount),
+                        oriented_bottom = !flip_faces? bottom : reverse(bottom)
+                ) [vertices, concat(faces,[oriented_bottom])]
+        ) : (
+                let(
+                        this_offset = offsetind==0? offsets[0][0] : offsets[offsetind][0] - offsets[offsetind-1][0],
+                        delta = offset_type=="delta" || offset_type=="chamfer" ? this_offset : undef,
+                        r = offset_type=="round"? this_offset : undef,
+                        do_chamfer = offset_type == "chamfer"
+                )
+                let(
+                        vertices_faces = offset(
+                                path, r=r, delta=delta, chamfer = do_chamfer, closed=true,
+                                check_valid=check_valid, quality=quality,
+                                maxstep=maxstep, return_faces=true,
+                                firstface_index=vertexcount,
+                                flip_faces=flip_faces
                         )
-                        assert(num_defined([r,delta])==1,str("Must set `offset` to ",round," or ",delta)
-                        let(
-                                vertices_faces = offset(
-                                        path, r=r, delta=delta, chamfer = do_chamfer, closed=true,
-                                        check_valid=check_valid, quality=quality,
-                                        maxstep=maxstep, return_faces=true,
-                                        firstface_index=vertexcount,
-                                        flip_faces=flip_faces
-                                )
-                        )
-                        make_polyhedron(
-                                vertices_faces[0], offsets, offset_type,
-                                flip_faces, quality, check_valid, maxstep,
-                                offsetind+1, vertexcount+len(path),
-                                vertices=concat(
-                                        vertices,
-                                        zip(vertices_faces[0],repeat(offsets[offsetind][1],len(vertices_faces[0])))
-                                ),
-                                faces=concat(faces, vertices_faces[1])
-                        )
-                );
-
-
-        argspec = [
-                ["r",r],
-                ["extra",extra],
-                ["type","circle"],
-                ["check_valid",check_valid],
-                ["quality",quality],
-                ["offset_maxstep", offset_maxstep],
-                ["steps",steps],
-                ["offset",offset],
-                ["chamfer_width",chamfer_width],
-                ["chamfer_height",chamfer_height],
-                ["angle",angle],
-                ["cut",cut],
-                ["joint",joint],
-                ["k", k],
-                ["points", []],
-        ];
-
-        path = check_and_fix_path(path, [2], closed=true);
-        clockwise = polygon_is_clockwise(path);
-
-        top = struct_set(argspec, top, grow=false);
-        bottom = struct_set(argspec, bottom, grow=false);
-
-        //  This code does not work.  It hits the error in make_polyhedron from offset being wrong
-        //  before this code executes.  Had to move the test into make_polyhedron, which is ugly since it's in the loop
-        //offsetsok = in_list(struct_val(top, "offset"),["round","delta"]) &&
-        //      in_list(struct_val(bottom, "offset"),["round","delta"]);
-        //assert(offsetsok,"Offsets must be one of \"round\" or \"delta\"");
-
-
-        offsets_bot = _rounding_offsets(bottom, -1);
-        offsets_top = _rounding_offsets(top, 1);
-
-        if (offset == "chamfer" && (len(offsets_bot)>5 || len(offsets_top)>5)) {
-          echo("WARNING: You have selected offset=\"chamfer\", which leads to exponential growth in the vertex count and requested many layers.  This can be slow or run out of recursion depth.");
-        }
-        // "Extra" height enlarges the result beyond the requested height, so subtract it
-        bottom_height = len(offsets_bot)==0 ? 0 : abs(select(offsets_bot,-1)[1]) - struct_val(bottom,"extra");
-        top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra");
-
-        height = get_height(l=l,h=h,height=height,dflt=bottom_height+top_height);
-        assert(height>=0, "Height must be nonnegative");
-
-        middle = height-bottom_height-top_height;
-        assert(
-                middle>=0, str(
-                        "Specified end treatments (bottom height = ",bottom_height,
-                        " top_height = ",top_height,") are too large for extrusion height (",height,")"
+                )
+                _make_offset_polyhedron(
+                        vertices_faces[0], offsets, offset_type,
+                        flip_faces, quality, check_valid, maxstep,
+                        offsetind+1, vertexcount+len(path),
+                        vertices=concat(
+                                vertices,
+                                zip(vertices_faces[0],repeat(offsets[offsetind][1],len(vertices_faces[0])))
+                        ),
+                        faces=concat(faces, vertices_faces[1])
                 )
         );
-        initial_vertices_bot = path3d(path);
 
-        vertices_faces_bot = make_polyhedron(
+
+function offset_sweep(
+                       path, height, h, l,
+                       top=[], bottom=[],
+                       offset="round", r=0, steps=16,
+                       quality=1, check_valid=true,
+                       offset_maxstep=1, extra=0,
+                       cut=undef, chamfer_width=undef, chamfer_height=undef,
+                       joint=undef, k=0.75, angle=45
+                      ) =
+    let(
+        argspec = [
+                   ["r",r],
+                   ["extra",extra],
+                   ["type","circle"],
+                   ["check_valid",check_valid],
+                   ["quality",quality],
+                   ["offset_maxstep", offset_maxstep],
+                   ["steps",steps],
+                   ["offset",offset],
+                   ["chamfer_width",chamfer_width],
+                   ["chamfer_height",chamfer_height],
+                   ["angle",angle],
+                   ["cut",cut],
+                   ["joint",joint],
+                   ["k", k],
+                   ["points", []],
+        ],
+        path = check_and_fix_path(path, [2], closed=true),
+        clockwise = polygon_is_clockwise(path),
+        
+        top = struct_set(argspec, top, grow=false),
+        bottom = struct_set(argspec, bottom, grow=false),
+
+        //  This code does not work.  It hits the error in _make_offset_polyhedron from offset being wrong
+        //  before this code executes.  Had to move the test into _make_offset_polyhedron, which is ugly since it's in the loop
+        offsetsok = in_list(struct_val(top, "offset"),["round","delta"])
+                    && in_list(struct_val(bottom, "offset"),["round","delta"])
+    )
+    assert(offsetsok,"Offsets must be one of \"round\" or \"delta\"")
+    let( 
+        offsets_bot = _rounding_offsets(bottom, -1),
+        offsets_top = _rounding_offsets(top, 1),
+        dummy = offset == "chamfer" && (len(offsets_bot)>5 || len(offsets_top)>5)
+                ? echo("WARNING: You have selected offset=\"chamfer\", which leads to exponential growth in the vertex count and requested more than 5 layers.  This can be slow or run out of recursion depth.")
+                : 0,
+
+        // "Extra" height enlarges the result beyond the requested height, so subtract it
+        bottom_height = len(offsets_bot)==0 ? 0 : abs(select(offsets_bot,-1)[1]) - struct_val(bottom,"extra"),
+        top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra"),
+
+        height = get_height(l=l,h=h,height=height,dflt=bottom_height+top_height),
+        middle = height-bottom_height-top_height
+    )
+    assert(height>=0, "Height must be nonnegative") 
+    assert(middle>=0, str("Specified end treatments (bottom height = ",bottom_height,
+                          " top_height = ",top_height,") are too large for extrusion height (",height,")"
+                         )
+    )
+    let(
+        initial_vertices_bot = path3d(path),
+
+        vertices_faces_bot = _make_offset_polyhedron(
                 path, offsets_bot, struct_val(bottom,"offset"), clockwise,
                 struct_val(bottom,"quality"),
                 struct_val(bottom,"check_valid"),
                 struct_val(bottom,"offset_maxstep"),
                 vertices=initial_vertices_bot
-        );
+        ),
 
-        top_start_ind = len(vertices_faces_bot[0]);
-        initial_vertices_top = zip(path, repeat(middle,len(path)));
-        vertices_faces_top = make_polyhedron(
+        top_start_ind = len(vertices_faces_bot[0]),
+        initial_vertices_top = zip(path, repeat(middle,len(path))),
+        vertices_faces_top = _make_offset_polyhedron(
                 path, move(p=offsets_top,[0,middle]),
                 struct_val(top,"offset"), !clockwise,
                 struct_val(top,"quality"),
@@ -769,20 +773,39 @@ module offset_sweep(
                 struct_val(top,"offset_maxstep"),
                 vertexcount=top_start_ind,
                 vertices=initial_vertices_top
-        );
+        ),
         middle_faces = middle==0 ? [] : [
                 for(i=[0:len(path)-1]) let(
                         oneface=[i, (i+1)%len(path), top_start_ind+(i+1)%len(path), top_start_ind+i]
                 ) !clockwise ? reverse(oneface) : oneface
-        ];
-        up(bottom_height) {
-                polyhedron(
-                        concat(vertices_faces_bot[0],vertices_faces_top[0]),
-                        faces=concat(vertices_faces_bot[1], vertices_faces_top[1], middle_faces),
-                        convexity=convexity
-                );
-        }
-}
+        ]
+    )
+    [up(bottom_height, concat(vertices_faces_bot[0],vertices_faces_top[0])),  // Vertices
+     concat(vertices_faces_bot[1], vertices_faces_top[1], middle_faces)];     // Faces
+
+
+module offset_sweep(path, height, h, l,
+                    top=[], bottom=[],
+                    offset="round", r=0, steps=16,
+                    quality=1, check_valid=true,
+                    offset_maxstep=1, extra=0,
+                    cut=undef, chamfer_width=undef, chamfer_height=undef,
+                    joint=undef, k=0.75, angle=45,
+                    convexity=10,anchor="origin",cp,
+                    spin=0, orient=UP, extent=false)
+{
+    vnf = offset_sweep(path=path, height=height, h=h, l=l, top=top, bottom=bottom, offset=offset, r=0, steps=steps,
+                       quality=quality, check_valid=true, offset_maxstep=1, extra=0, cut=cut, chamfer_width=chamfer_width,
+                       chamfer_height=chamfer_height, joint=joint, k=k, angle=angle);
+  
+    attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
+    {
+        vnf_polyhedron(vnf,convexity=convexity);
+        children();
+    }   
+}   
+
+
 
 function os_circle(r,cut,extra,check_valid, quality,steps, offset_maxstep, offset) =
         assert(num_defined([r,cut])==1, "Must define exactly one of `r` and `cut`")
@@ -924,7 +947,6 @@ function os_profile(points, extra,check_valid, quality, offset_maxstep, offset)
 //   joint = default joint value for smooth roundover.
 //   k = default curvature parameter value for "smooth" roundover
 //   convexity = convexity setting for use with polyhedron.  Default: 10
-//
 // Example: Chamfered elliptical prism.  If you stretch a chamfered cylinder the chamfer will be uneven.
 //   convex_offset_extrude(bottom = os_chamfer(height=-2), top=os_chamfer(height=1), height=7)
 //   xscale(4)circle(r=6,$fn=64);
@@ -1364,8 +1386,6 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
         }
 }
 
-
-
 function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
    let(
      N = len(top),
@@ -1395,8 +1415,8 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
                     let(
                        prev_corner = prev_offset + abs(rtop_in)*in_prev,
                        next_corner = next_offset + abs(rtop_in)*in_next,
-                       prev_degenerate = is_undef(ray_intersection([far_corner, far_corner+prev], [prev_offset, prev_offset+in_prev])),
-                       next_degenerate = is_undef(ray_intersection([far_corner, far_corner+next], [next_offset, next_offset+in_next]))
+                       prev_degenerate = is_undef(ray_intersection(path2d([far_corner, far_corner+prev]), path2d([prev_offset, prev_offset+in_prev]))),
+                       next_degenerate = is_undef(ray_intersection(path2d([far_corner, far_corner+next]), path2d([next_offset, next_offset+in_next])))
                     )
                     [ prev_degenerate ? far_corner : prev_corner,
                       far_corner,
@@ -1452,6 +1472,11 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
 //   splinesteps = number of segments to use for curved patches.  Default: 16
 //   debug = turn on debug mode which displays illegal polyhedra and shows the bezier corner patches for troubleshooting purposes.  Default: False
 //   convexity = convexity parameter for polyhedron(), only for module version.  Default: 10
+//   anchor = Translate so anchor point is at the origin.  (module only) Default: "origin"
+//   spin = Rotate this many degrees around Z axis after anchor.  (module only) Default: 0
+//   orient = Vector to rotate top towards after spin  (module only)
+//   extent = use extent method for computing anchors. (module only)  Default: false
+//   cp = set centerpoint for anchor computation.  (module only) Default: object centroid
 // Example: Uniformly rounded pentagonal prism
 //   rounded_prism(pentagon(3), height=3, joint_top=0.5, joint_bot=0.5, joint_sides=0.5);
 // Example: Maximum possible rounding.
@@ -1500,15 +1525,21 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
 //   rounded_prism(apply(yrot(95),path3d(hexagon(3))), apply(yrot(95), path3d(hexagon(3),3)), joint_top=2, joint_bot=1, joint_sides=1);
 
 module rounded_prism(bottom, top, joint_bot, joint_top, joint_sides, k_bot, k_top, k_sides,
-                     k=0.5, splinesteps=16, h, length, l, height, convexity=10, debug=false)
+                     k=0.5, splinesteps=16, h, length, l, height, convexity=10, debug=false,
+                     anchor="origin",cp,spin=0, orient=UP, extent=false)
 {
   result = rounded_prism(bottom=bottom, top=top, joint_bot=joint_bot, joint_top=joint_top, joint_sides=joint_sides,
                          k_bot=k_bot, k_top=k_top, k_sides=k_sides, k=k, splinesteps=splinesteps, h=h, length=length, height=height, l=l,debug=debug);
-  if (debug){
-      vnf_polyhedron(result[1], convexity=convexity);
-      trace_bezier_patches(result[0], showcps=true, splinesteps=splinesteps, $fn=16, showdots=false, showpatch=false);
+  vnf = debug ? result[1] : result;
+  attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
+  {
+    if (debug){
+        vnf_polyhedron(vnf, convexity=convexity);
+        trace_bezier_patches(result[0], showcps=true, splinesteps=splinesteps, $fn=16, showdots=false, showpatch=false);
+    }
+    else vnf_polyhedron(vnf,convexity=convexity);
+    children();
   }
-  else vnf_polyhedron(result,convexity=convexity);
 }
 
 
@@ -1880,7 +1911,7 @@ function _circle_mask(r) =
 //     $fn=128;
 //     difference(){
 //       tube(or=r, wall=2, h=45);
-//       bent_cutout_mask(r-1, 2.1, apply(back(15),subdivide_path(round_corners(star(n=7,ir=5,or=10), cut=flatten(repeat([0.5,0],7))),14*15,closed=true)));
+//       bent_cutout_mask(r-1, 2.1, apply(back(15),subdivide_path(round_corners(star(n=7,ir=5,or=10), cut=flatten(repeat([0.5,0],7)),$fn=32),14*15,closed=true)));
 //     }
 //   }
 // Example(2D): Cutting a slot in a cylinder is tricky if you want rounded corners at the top.  This slot profile has slightly angled top edges to blend into the top edge of the cylinder.
@@ -1944,6 +1975,7 @@ function _circle_mask(r) =
 
 module bent_cutout_mask(r, thickness, path, convexity=10)
 {
+  no_children($children);
   assert(is_path(path,2),"Input path must be a 2d path")
   assert(r-thickness>0, "Thickness too large for radius");
   assert(thickness>0, "Thickness must be positive");