diff --git a/geometry.scad b/geometry.scad index 21b4f29..80f6756 100644 --- a/geometry.scad +++ b/geometry.scad @@ -888,11 +888,49 @@ 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) / norm(normal); + assert( is_matrix([normal,pt],2,3) && !approx(norm(normal),0), + "Inputs `normal` and `pt` should be 3d vectors/points and `normal` cannot be zero." ) + concat(normal, normal*pt) / norm(normal); +// Eigenvalues for a 3x3 symmetrical matrix in decreasing order +// Based on: https://en.wikipedia.org/wiki/Eigenvalue_algorithm +function _eigenvals_symm_3(M) = + let( p1 = pow(M[0][1],2) + pow(M[0][2],2) + pow(M[1][2],2) ) + (p1, ); @@ -900,7 +938,7 @@ function plane_from_normal(normal, pt=[0,0,0]) = // Given a list of 3 or more coplanar 3D points, returns the coefficients of the normalized cartesian equation of a plane, // that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane where norm([A,B,C])=1. // 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. +// If `fast` is true, the polygon coplanarity check is skipped and a best fitted plane 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 @@ -914,17 +952,18 @@ function plane_from_normal(normal, pt=[0,0,0]) = 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 non-negative value." ) - let( - indices = noncollinear_triple(points,error=false) - ) - indices==[] ? undef : - let( - p1 = points[indices[0]], - p2 = points[indices[1]], - p3 = points[indices[2]], - plane = plane3pt(p1,p2,p3) - ) - fast || points_on_plane(points,plane,eps=eps) ? plane : undef; + len(points) == 3 + ? let( plane = plane3pt(points[0],points[1],points[2]) ) + plane==[] ? undef : plane + : let( + cov_evals = _covariance_evals(points), + pm = cov_evals[0], + evals = cov_evals[1], + M = cov_evals[2], + evec = _eigenvec_symm_3(M,evals,i=2) ) +// echo(error_points_plane= abs(max(points*evec)-pm*evec), limit=eps) + !fast && abs(max(points*evec)-pm*evec)>eps*evals[0] ? undef : + [ each evec, pm*evec] ; // Function: plane_from_polygon() @@ -934,7 +973,8 @@ function plane_from_points(points, fast=false, eps=EPSILON) = // Given a 3D planar polygon, returns the normalized cartesian equation of its plane. // Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane where norm([A,B,C])=1. // 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. +// 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: // poly = The planar 3D polygon to find the plane of. // fast = If true, doesn't verify that all points in the polygon are coplanar. Default: false @@ -948,14 +988,13 @@ function plane_from_points(points, fast=false, eps=EPSILON) = 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 non-negative value." ) - let( - poly = deduplicate(poly), - n = polygon_normal(poly), - plane = [n.x, n.y, n.z, n*poly[0]] - ) - fast? plane: coplanar(poly,eps=eps)? plane: []; - - + len(poly)==3 ? plane3pt(poly[0],poly[1],poly[2]) : + let( triple = sort(noncollinear_triple(poly,error=false)) ) + triple==[] ? [] : + let( plane = plane3pt(poly[triple[0]],poly[triple[1]],poly[triple[2]])) + fast? plane: points_on_plane(poly, plane, eps=eps)? plane: []; + + // Function: plane_normal() // Usage: // plane_normal(plane); @@ -1252,9 +1291,11 @@ function coplanar(points, eps=EPSILON) = 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); + let( + plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]), + normal = point3d(plane), + pt_nrm = points*normal ) + abs(max(max(pt_nrm)-plane[3], -min(pt_nrm)+plane[3])) < eps; // Function: points_on_plane() @@ -1665,11 +1706,11 @@ function noncollinear_triple(points,error=true,eps=EPSILON) = n = (pb-pa)/nrm, distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)] ) - max(distlist)