From eccb006f850b9becb816b0133aecc5a3c378bdf0 Mon Sep 17 00:00:00 2001 From: RonaldoCMP Date: Mon, 21 Jun 2021 19:08:13 +0100 Subject: [PATCH] Introduces convex collision and distance --- geometry.scad | 418 ++++++++++++++++++++++++++++++--------- tests/test_geometry.scad | 68 +++++-- 2 files changed, 380 insertions(+), 106 deletions(-) diff --git a/geometry.scad b/geometry.scad index 24fbac6..0221a2b 100644 --- a/geometry.scad +++ b/geometry.scad @@ -19,15 +19,8 @@ // edge = Array of two points forming the line segment to test against. // eps = Tolerance in geometric comparisons. 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 non-negative value." ) - 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 - && _dist2line(point-edge[0],unit(de))eps*max(norm(line[1]),norm(line[0])); //Internal function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps); @@ -85,22 +78,57 @@ function collinear(a, b, c, eps=EPSILON) = : noncollinear_triple(points,error=false,eps=eps)==[]; -// Function: distance_from_line() +// Function: point_line_distance() // Usage: -// distance_from_line(line, pt); +// point_line_distance(line, pt); // Description: // Finds the perpendicular distance of a point `pt` from the line `line`. // Arguments: // line = A list of two points, defining a line that both are on. // pt = A point to find the distance of from the line. // Example: -// distance_from_line([[-10,0], [10,0]], [3,8]); // Returns: 8 -function distance_from_line(line, pt) = +// dist = point_line_distance([3,8], [[-10,0], [10,0]]); // Returns: 8 +function point_line_distance(pt, line) = 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])); +// Function: point_segment_distance() +// Usage: +// dist = point_segment_distance(pt, seg); +// Description: +// Returns the closest distance of the given point to the given line segment. +// Arguments: +// pt = The point to check the distance of. +// seg = The two points representing the line segment to check the distance of. +// Example: +// dist = point_segment_distance([3,8], [[-10,0], [10,0]]); // Returns: 8 +// dist2 = point_segment_distance([14,3], [[-10,0], [10,0]]); // Returns: 5 +function point_segment_distance(pt, seg) = + assert( is_matrix(concat([pt],seg),3), + "Input should be a point and a valid segment with the dimension equal to the point." ) + norm(seg[0]-seg[1]) < EPSILON ? norm(pt-seg[0]) : + norm(pt-segment_closest_point(seg,pt)); + + +// Function: segment_distance() +// Usage: +// dist = segment_distance(seg1, seg2); +// Description: +// Returns the closest distance of the two given line segments. +// Arguments: +// seg1 = The list of two points representing the first line segment to check the distance of. +// seg2 = The list of two points representing the second line segment to check the distance of. +// Example: +// dist = segment_distance([[-14,3], [-15,9]], [[-10,0], [10,0]]); // Returns: 5 +// dist2 = segment_distance([[-5,5], [5,-5]], [[-10,3], [10,-3]]); // Returns: 0 +function segment_distance(seg1, seg2) = + assert( is_matrix(concat(seg1,seg2),4), + "Inputs should be two valid segments." ) + convex_distance(seg1,seg2); + + // Function: line_normal() // Usage: // line_normal([P1,P2]) @@ -436,17 +464,9 @@ 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(_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]), - segvec = (seg[1]-seg[0])/seglen, - projection = (pt-seg[0]) * segvec - ) - projection<=0 ? seg[0] : - projection>=seglen ? seg[1] : - seg[0] + projection*segvec; + assert( is_matrix(concat([pt],seg),3) , + "Invalid point or segment or incompatible dimensions." ) + pt + _closest_s1([seg[0]-pt, seg[1]-pt])[0]; // Function: line_from_points() @@ -454,7 +474,7 @@ function segment_closest_point(seg,pt) = // line_from_points(points, [fast], [eps]); // Description: // Given a list of 2 or more collinear points, returns a line containing them. -// If `fast` is false and the points are coincident, then `undef` is returned. +// If `fast` is false and the points are coincident or non-collinear, 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. @@ -464,7 +484,7 @@ 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 non-negative value." ) let( pb = furthest_point(points[0],points) ) - approx(norm(points[pb]-points[0]),0) ? undef : + norm(points[pb]-points[0])=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(_valid_plane(plane,eps=eps) && _valid_line(line,dim=3,eps=eps), "Invalid plane and/or 3d line.") assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition.") let( bounded = is_list(bounded)? bounded : [bounded, bounded], @@ -1182,7 +1202,7 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) = assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." ) assert(is_path(poly,dim=3), "Invalid polygon." ) assert(!is_list(bounded) || len(bounded)==2, "Invalid bound condition(s).") - assert(_valid_line(line,dim=3,eps=eps), "Invalid line." ) + assert(_valid_line(line,dim=3,eps=eps), "Invalid 3D line." ) let( bounded = is_list(bounded)? bounded : [bounded, bounded], poly = deduplicate(poly), @@ -1310,7 +1330,7 @@ function points_on_plane(points, plane, eps=EPSILON) = // plane = The [A,B,C,D] coefficients for the first plane equation `Ax+By+Cz=D`. // point = The 3D point to test. function in_front_of_plane(plane, point) = - distance_from_plane(plane, point) > EPSILON; + point_plane_distance(plane, point) > EPSILON; @@ -1636,7 +1656,7 @@ function circle_circle_tangents(c1,r1,c2,r2,d1,d2) = // eps = epsilon used for identifying the case with one solution. Default: 1e-9 function circle_line_intersection(c,r,d,line,bounded=false,eps=EPSILON) = let(r=get_radius(r=r,d=d,dflt=undef)) - assert(_valid_line(line,2), "Input 'line' is not a valid 2d line.") + assert(_valid_line(line,2), "Invalid 2d line.") assert(is_vector(c,2), "Circle center must be a 2-vector") assert(is_num(r) && r>0, "Radius must be positive") assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition") @@ -1680,14 +1700,14 @@ function noncollinear_triple(points,error=true,eps=EPSILON) = pb = points[b], nrm = norm(pa-pb) ) - approx(nrm, 0) + nrm <= eps*max(norm(pa),norm(pb)) ? assert(!error, "Cannot find three noncollinear points in pointlist.") [] : let( n = (pb-pa)/nrm, distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)] ) - max(distlist)0 && len(pts[0])>0 , "Invalid pointlist." ) - let(ptsT = transpose(pts)) - [ - [for(row=ptsT) min(row)], - [for(row=ptsT) max(row)] - ]; + assert(is_path(pts,dim=undef,fast=true) , "Invalid pointlist." ) + let( + select = ident(len(pts[0])), + spread = [for(i=[0:len(pts[0])-1]) + let( spreadi = pts*select[i] ) + [min(spreadi), max(spreadi)] ] ) + transpose(spread); // Function: closest_point() @@ -1747,7 +1768,7 @@ function furthest_point(pt, points) = // 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. For non-planar 3D polygon the result is []. +// If the polygon is self-crossing, the results are undefined. For non-planar 3D polygon the result is `undef`. // When `signed` is true, a signed area is returned; a positive area indicates a clockwise polygon. // Arguments: // poly = Polygon to compute the area of. @@ -1759,53 +1780,17 @@ function polygon_area(poly, signed=false) = ? let( total = sum([for(i=[1:1:len(poly)-2]) cross(poly[i]-poly[0],poly[i+1]-poly[0]) ])/2 ) signed ? total : abs(total) : let( plane = plane_from_polygon(poly) ) - plane==[]? [] : + plane==[]? undef : let( n = plane_normal(plane), - total = sum([ - for(i=[1:1:len(poly)-2]) - let( - v1 = poly[i] - poly[0], - v2 = poly[i+1] - poly[0] - ) - cross(v1,v2) - ])* n/2 + total = + sum([ for(i=[1:1:len(poly)-2]) + cross(poly[i]-poly[0], poly[i+1]-poly[0]) + ]) * n/2 ) signed ? total : abs(total); -// Function: is_convex_polygon() -// Usage: -// is_convex_polygon(poly); -// Description: -// Returns true if the given 2D or 3D polygon is convex. -// The result is meaningless if the polygon is not simple (self-intersecting) or non coplanar. -// If the points are collinear an error is generated. -// Arguments: -// poly = Polygon to check. -// eps = Tolerance for the collinearity test. Default: EPSILON. -// Example: -// is_convex_polygon(circle(d=50)); // Returns: true -// is_convex_polygon(rot([50,120,30], p=path3d(circle(1,$fn=50)))); // Returns: true -// 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,eps=EPSILON) = - assert(is_path(poly), "The input should be a 2D or 3D polygon." ) - let( lp = len(poly), - p0 = poly[0] ) - assert( lp>=3 , "A polygon must have at least 3 points" ) - let( crosses = [for(i=[0:1:lp-1]) cross(poly[(i+1)%lp]-poly[i], poly[(i+2)%lp]-poly[(i+1)%lp]) ] ) - len(p0)==2 - ? assert( !approx(sqrt(max(max(crosses),-min(crosses))),eps), "The points are collinear" ) - min(crosses) >=0 || max(crosses)<=0 - : let( prod = crosses*sum(crosses), - minc = min(prod), - maxc = max(prod) ) - assert( !approx(sqrt(max(maxc,-minc)),eps), "The points are collinear" ) - minc>=0 || maxc<=0; - - // Function: polygon_shift() // Usage: // polygon_shift(poly, i); @@ -1972,9 +1957,9 @@ function centroid(poly, eps=EPSILON) = // Returns -1 if the point is outside the polygon. // Returns 0 if the point is on the boundary. // Returns 1 if the point lies in the interior. -// The polygon does not need to be simple: it can have self-intersections. +// The polygon does not need to be simple: it may have self-intersections. // But the polygon cannot have holes (it must be simply connected). -// Rounding error may give mixed results for points on or near the boundary. +// Rounding errors 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. @@ -2067,7 +2052,7 @@ function ccw_polygon(poly) = // poly = The list of the path points for the perimeter of the polygon. 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]]; + [poly[0], for(i=[len(poly)-1:-1:1]) poly[i] ]; // Function: polygon_normal() @@ -2075,7 +2060,7 @@ function reverse_polygon(poly) = // n = polygon_normal(poly); // Description: // Given a 3D planar polygon, returns a unit-length normal vector for the -// clockwise orientation of the polygon. If the polygon points are collinear, returns []. +// clockwise orientation of the polygon. If the polygon points are collinear, returns `undef`. // It doesn't check for coplanarity. // Arguments: // poly = The list of 3D path points for the perimeter of the polygon. @@ -2083,7 +2068,7 @@ function polygon_normal(poly) = assert(is_path(poly,dim=3), "Invalid 3D polygon." ) len(poly)==3 ? point3d(plane3pt(poly[0],poly[1],poly[2])) : let( triple = sort(noncollinear_triple(poly,error=false)) ) - triple==[] ? [] : + triple==[] ? undef : point3d(plane3pt(poly[triple[0]],poly[triple[1]],poly[triple[2]])) ; @@ -2237,4 +2222,255 @@ function split_polygons_at_each_z(polys, zs, _i=0) = ); + +// Section: Convex Sets + + +// Function: is_convex_polygon() +// Usage: +// is_convex_polygon(poly); +// Description: +// Returns true if the given 2D or 3D polygon is convex. +// The result is meaningless if the polygon is not simple (self-intersecting) or non coplanar. +// If the points are collinear an error is generated. +// Arguments: +// poly = Polygon to check. +// eps = Tolerance for the collinearity test. Default: EPSILON. +// Example: +// is_convex_polygon(circle(d=50)); // Returns: true +// is_convex_polygon(rot([50,120,30], p=path3d(circle(1,$fn=50)))); // Returns: true +// 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,eps=EPSILON) = + assert(is_path(poly), "The input should be a 2D or 3D polygon." ) + let( lp = len(poly), + p0 = poly[0] ) + assert( lp>=3 , "A polygon must have at least 3 points" ) + let( crosses = [for(i=[0:1:lp-1]) cross(poly[(i+1)%lp]-poly[i], poly[(i+2)%lp]-poly[(i+1)%lp]) ] ) + len(p0)==2 + ? assert( !approx(sqrt(max(max(crosses),-min(crosses))),eps), "The points are collinear" ) + min(crosses) >=0 || max(crosses)<=0 + : let( prod = crosses*sum(crosses), + minc = min(prod), + maxc = max(prod) ) + assert( !approx(sqrt(max(maxc,-minc)),eps), "The points are collinear" ) + minc>=0 || maxc<=0; + + +// Function: convex_distance() +// Usage: +// convex_distance(pts1, pts2,); +// See also: +// convex_collision +// Description: +// Returns the smallest distance between a point in convex hull of `points1` +// and a point in the convex hull of `points2`. All the points in the lists +// should have the same dimension, either 2D or 3D. +// A zero result means the hulls intercept whithin a tolerance `eps`. +// Arguments: +// points1 - first list of 2d or 3d points. +// points2 - second list of 2d or 3d points. +// eps - tolerance in distance evaluations. Default: EPSILON. +// Example(2D): +// pts1 = move([-3,0], p=square(3,center=true)); +// pts2 = rot(a=45, p=square(2,center=true)); +// pts3 = [ [2,0], [1,2],[3,2], [3,-2], [1,-2] ]; +// polygon(pts1); +// polygon(pts2); +// polygon(pts3); +// echo(convex_distance(pts1,pts2)); // Returns: 0.0857864 +// echo(convex_distance(pts2,pts3)); // Returns: 0 +// Example(3D): +// sphr1 = sphere(2,$fn=10); +// sphr2 = move([4,0,0], p=sphr1); +// sphr3 = move([4.5,0,0], p=sphr1); +// vnf_polyhedron(sphr1); +// vnf_polyhedron(sphr2); +// echo(convex_distance(sphr1[0], sphr2[0])); // Returns: 0 +// echo(convex_distance(sphr1[0], sphr3[0])); // Returns: 0.5 +function convex_distance(points1, points2, eps=EPSILON) = + assert(is_matrix(points1) && is_matrix(points2,undef,len(points1[0])), + "The input list should be a consistent non empty list of points of same dimension.") + assert(len(points1[0])==2 || len(points1[0])==3 , + "The input points should be 2d or 3d points.") + let( d = points1[0]-points2[0] ) + norm(d)); +// See also: +// convex_distance +// Description: +// Returns `true` if the convex hull of `points1` intercepts the convex hull of `points2` +// otherwise, `false`. +// All the points in the lists should have the same dimension, either 2D or 3D. +// This function is tipically faster than `convex_distance` to find a non-collision. +// Arguments: +// points1 - first list of 2d or 3d points. +// points2 - second list of 2d or 3d points. +// eps - tolerance for the intersection tests. Default: EPSILON. +// Example(2D): +// pts1 = move([-3,0], p=square(3,center=true)); +// pts2 = rot(a=45, p=square(2,center=true)); +// pts3 = [ [2,0], [1,2],[3,2], [3,-2], [1,-2] ]; +// polygon(pts1); +// polygon(pts2); +// polygon(pts3); +// echo(convex_collision(pts1,pts2)); // Returns: false +// echo(convex_collision(pts2,pts3)); // Returns: true +// Example(3D): +// sphr1 = sphere(2,$fn=10); +// sphr2 = move([4,0,0], p=sphr1); +// sphr3 = move([4.5,0,0], p=sphr1); +// vnf_polyhedron(sphr1); +// vnf_polyhedron(sphr2); +// echo(convex_collision(sphr1[0], sphr2[0])); // Returns: true +// echo(convex_collision(sphr1[0], sphr3[0])); // Returns: false +// +function convex_collision(points1, points2, eps=EPSILON) = + assert(is_matrix(points1) && is_matrix(points2,undef,len(points1[0])), + "The input list should be a consistent non empty list of points of same dimension.") + assert(len(points1[0])==2 || len(points1[0])==3 , + "The input points should be 2d or 3d points.") + let( d = points1[0]-points2[0] ) + norm(d) eps ? false : // no collision + let( newsplx = _closest_simplex(concat(simplex,[v]),eps) ) + _GJK_collide(points1, points2, newsplx[0], newsplx[1], eps); + + +// given a simplex s, returns a pair: +// - the point of the s closest to the origin +// - the smallest sub-simplex of s that contains that point +function _closest_simplex(s,eps=EPSILON) = + assert(len(s)>=2 && len(s)<=4, "Internal error.") + len(s)==2 ? _closest_s1(s,eps) : + len(s)==3 ? _closest_s2(s,eps) + : _closest_s3(s,eps); + + +// find the closest to a 1-simplex +// Based on: http://uu.diva-portal.org/smash/get/diva2/FFULLTEXT01.pdf +function _closest_s1(s,eps=EPSILON) = + norm(s[1]-s[0])1 ? [ s[1], [s[1]] ] : + [ s[0]+t*c, s ]; + + +// find the closest to a 2-simplex +// Based on: http://uu.diva-portal.org/smash/get/diva2/FFULLTEXT01.pdf +function _closest_s2(s,eps=EPSILON) = + let( + dim = len(s[0]), + a = dim==3 ? s[0]: [ each s[0], 0] , + b = dim==3 ? s[1]: [ each s[1], 0] , + c = dim==3 ? s[2]: [ each s[2], 0] , + ab = norm(a-b), + bc = norm(b-c), + ca = norm(c-a), + nr = cross(b-a,c-a) + ) + norm(nr) <= eps*max(ab,bc,ca) // degenerate case + ? let( i = max_index([ab, bc, ca]) ) + _closest_s1([s[i],s[(i+1)%3]],eps) +// considering that s[2] was the last inserted vertex in s, +// the only possible outcomes are : +// s, [s[0],s[2]] and [s[1],s[2]] + : let( + class = (cross(nr,a-b)*a<0 ? 1 : 0 ) + + (cross(nr,c-a)*a<0 ? 2 : 0 ) + + (cross(nr,b-c)*b<0 ? 4 : 0 ) + ) + assert( class!=1, "Internal error" ) + class==0 ? [ nr*(nr*a)/(nr*nr), s] : // origin projects (or is) on the tri +// class==1 ? _closest_s1([s[0],s[1]]) : + class==2 ? _closest_s1([s[0],s[2]],eps) : + class==4 ? _closest_s1([s[1],s[2]],eps) : +// class==3 ? a*(a-b)> 0 ? _closest_s1([s[0],s[1]]) : _closest_s1([s[0],s[2]]) : + class==3 ? _closest_s1([s[0],s[2]],eps) : +// class==5 ? b*(b-c)<=0 ? _closest_s1([s[0],s[1]]) : _closest_s1([s[1],s[2]]) : + class==5 ? _closest_s1([s[1],s[2]],eps) : + c*(c-a)>0 ? _closest_s1([s[0],s[2]],eps) : _closest_s1([s[1],s[2]],eps); + + +// find the closest to a 3-simplex +// it seems that degenerate 3-simplices are correctly manage without extra code +function _closest_s3(s,eps=EPSILON) = + assert( len(s[0])==3 && len(s)==4, "Internal error." ) + let( nr = cross(s[1]-s[0],s[2]-s[0]), + sz = [ norm(s[1]-s[0]), norm(s[1]-s[2]), norm(s[2]-s[0]) ] ) + norm(nr)0)==(nrm*s[i]<0) ) i ] + ) + len(facing)==0 ? [ [0,0,0], s ] : // origin is inside the simplex + len(facing)==1 ? _closest_s2(tris[facing[0]], eps) : + let( // look for the origin-facing tri closest to the origin + closest = [for(i=facing) _closest_s2(tris[i], eps) ], + dist = [for(cl=closest) norm(cl[0]) ], + nearest = min_index(dist) + ) + closest[nearest]; + + +function _tri_normal(tri) = cross(tri[1]-tri[0],tri[2]-tri[0]); + + +function _support_diff(p1,p2,d) = + let( p1d = p1*d, p2d = p2*d ) + p1[search(max(p1d),p1d,1)[0]] - p2[search(min(p2d),p2d,1)[0]]; + + + // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad index 04f5126..31fc8ac 100644 --- a/tests/test_geometry.scad +++ b/tests/test_geometry.scad @@ -9,7 +9,9 @@ include <../std.scad> test_point_on_segment2d(); test_point_left_of_line2d(); test_collinear(); -test_distance_from_line(); +test_point_line_distance(); +test_point_segment_distance(); +test_segment_distance(); test_line_normal(); test_line_intersection(); //test_line_ray_intersection(); @@ -44,7 +46,7 @@ test_plane_normal(); test_plane_offset(); test_projection_on_plane(); test_plane_point_nearest_origin(); -test_distance_from_plane(); +test_point_plane_distance(); test__general_plane_line_intersection(); test_plane_line_angle(); @@ -88,7 +90,7 @@ test_cleanup_path(); test_simplify_path(); test_simplify_path_indexed(); test_is_region(); - +test_convex_distance(); // to be used when there are two alternative symmetrical outcomes // from a function like a plane output; v must be a vector @@ -230,7 +232,7 @@ module test__general_plane_line_intersection() { interspoint = line1[0]+inters1[1]*(line1[1]-line1[0]); assert_approx(inters1[0],interspoint, info1); assert_approx(point3d(plane1)*inters1[0], plane1[3], info1); // interspoint on the plane - assert_approx(distance_from_plane(plane1, inters1[0]), 0, info1); // inters1[0] on the plane + assert_approx(point_plane_distance(plane1, inters1[0]), 0, info1); // inters1[0] on the plane } // line parallel to the plane @@ -351,13 +353,35 @@ module test_collinear() { *test_collinear(); -module test_distance_from_line() { - assert(abs(distance_from_line([[-10,-10,-10], [10,10,10]], [1,1,1])) < EPSILON); - assert(abs(distance_from_line([[-10,-10,-10], [10,10,10]], [-1,-1,-1])) < EPSILON); - 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); +module test_point_line_distance() { + assert_approx(point_line_distance([1,1,1], [[-10,-10,-10], [10,10,10]]), 0); + assert_approx(point_line_distance([-1,-1,-1], [[-10,-10,-10], [10,10,10]]), 0); + assert_approx(point_line_distance([1,-1,0], [[-10,-10,-10], [10,10,10]]), sqrt(2)); + assert_approx(point_line_distance([8,-8,0], [[-10,-10,-10], [10,10,10]]), 8*sqrt(2)); } -*test_distance_from_line(); +*test_point_line_distance(); + + +module test_point_segment_distance() { + assert_approx(point_segment_distance([3,8], [[-10,0], [10,0]]), 8); + assert_approx(point_segment_distance([14,3], [[-10,0], [10,0]]), 5); +} +*test_point_segment_distance(); + + +module test_segment_distance() { + assert_approx(segment_distance([[-14,3], [-14,9]], [[-10,0], [10,0]]), 5); + assert_approx(segment_distance([[-14,3], [-15,9]], [[-10,0], [10,0]]), 5); + assert_approx(segment_distance([[14,3], [14,9]], [[-10,0], [10,0]]), 5); + assert_approx(segment_distance([[-14,-3], [-14,-9]], [[-10,0], [10,0]]), 5); + assert_approx(segment_distance([[-14,-3], [-15,-9]], [[-10,0], [10,0]]), 5); + assert_approx(segment_distance([[14,-3], [14,-9]], [[-10,0], [10,0]]), 5); + assert_approx(segment_distance([[14,3], [14,-3]], [[-10,0], [10,0]]), 4); + assert_approx(segment_distance([[-14,3], [-14,-3]], [[-10,0], [10,0]]), 4); + assert_approx(segment_distance([[-6,5], [4,-5]], [[-10,0], [10,0]]), 0); + assert_approx(segment_distance([[-5,5], [5,-5]], [[-10,3], [10,-3]]), 0); +} +*test_segment_distance(); module test_line_normal() { @@ -713,12 +737,12 @@ module test_plane_normal() { *test_plane_normal(); -module test_distance_from_plane() { +module test_point_plane_distance() { plane1 = plane3pt([-10,0,0], [0,10,0], [10,0,0]); - assert(distance_from_plane(plane1, [0,0,5]) == 5); - assert(distance_from_plane(plane1, [5,5,8]) == 8); + assert(point_plane_distance(plane1, [0,0,5]) == 5); + assert(point_plane_distance(plane1, [5,5,8]) == 8); } -*test_distance_from_plane(); +*test_point_plane_distance(); module test_polygon_line_intersection() { @@ -1051,6 +1075,20 @@ module test_is_region() { } *test_is_region(); - +module test_convex_distance() { + c1 = circle(10,$fn=24); + c2 = move([15,0], p=c1); + assert(convex_distance(c1, c2)==0); + c3 = move([22,0],c1); + assert(abs(convex_distance(c1, c3)-2)