Merge pull request #742 from adrianVmariano/master

hull & partitions reorg
This commit is contained in:
Revar Desmera 2021-12-16 21:36:39 -08:00 committed by GitHub
commit a3e6f3e021
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 777 additions and 791 deletions

View file

@ -2123,6 +2123,254 @@ function __is_polygon_in_list(poly, polys, i) =
__is_polygon_in_list(poly, polys, i+1);
// Section: Convex Hull
// This section originally based on Oskar Linde's Hull:
// - https://github.com/openscad/scad-utils
// Function: hull()
// Usage:
// hull(points);
// Description:
// Takes a list of 2D or 3D points (but not both in the same list) and returns either the list of
// indexes into `points` that forms the 2D convex hull perimeter path, or the list of faces that
// form the 3d convex hull surface. Each face is a list of indexes into `points`. If the input
// points are co-linear, the result will be the indexes of the two extrema points. If the input
// points are co-planar, the results will be a simple list of vertex indices that will form a planar
// perimeter. Otherwise a list of faces will be returned, where each face is a simple list of
// vertex indices for the perimeter of the face.
// Arguments:
// points = The set of 2D or 3D points to find the hull of.
function hull(points) =
assert(is_path(points),"Invalid input to hull")
len(points[0]) == 2
? hull2d_path(points)
: hull3d_faces(points);
// Module: hull_points()
// Usage:
// hull_points(points, [fast]);
// Description:
// If given a list of 2D points, creates a 2D convex hull polygon that encloses all those points.
// If given a list of 3D points, creates a 3D polyhedron that encloses all the points. This should
// handle about 4000 points in slow mode. If `fast` is set to true, this should be able to handle
// far more. When fast mode is off, 3d hulls that lie in a plane will produce a single face of a polyhedron, which can be viewed in preview but will not render.
// Arguments:
// points = The list of points to form a hull around.
// fast = If true for 3d case, uses a faster cheat that may handle more points, but also may emit warnings that can stop your script if you have "Halt on first warning" enabled. Ignored for the 2d case. Default: false
// Example(2D):
// pts = [[-10,-10], [0,10], [10,10], [12,-10]];
// hull_points(pts);
// Example:
// pts = [for (phi = [30:60:150], theta = [0:60:359]) spherical_to_xyz(10, theta, phi)];
// hull_points(pts);
module hull_points(points, fast=false) {
assert(is_path(points))
assert(len(points)>=3, "Point list must contain 3 points")
if (len(points[0])==2)
hull() polygon(points=points);
else {
if (fast) {
extra = len(points)%3;
faces = [
[for(i=[0:1:extra+2])i], // If vertex count not divisible by 3, combine extras with first 3
for(i=[extra+3:3:len(points)-3])[i,i+1,i+2]
];
hull() polyhedron(points=points, faces=faces);
} else {
faces = hull(points);
if (is_num(faces[0])){
if (len(faces)<=2) echo("Hull contains only two points");
else polyhedron(points=points, faces=[faces]);
}
else polyhedron(points=points, faces=faces);
}
}
}
function _backtracking(i,points,h,t,m,all) =
m<t || _is_cw(points[i], points[h[m-1]], points[h[m-2]],all) ? m :
_backtracking(i,points,h,t,m-1,all) ;
// clockwise check (2d)
function _is_cw(a,b,c,all) =
all ? cross(a-c,b-c)<=EPSILON*norm(a-c)*norm(b-c) :
cross(a-c,b-c)<-EPSILON*norm(a-c)*norm(b-c);
// Function: hull2d_path()
// Usage:
// hull2d_path(points,all)
// Description:
// Takes a list of arbitrary 2D points, and finds the convex hull polygon to enclose them.
// Returns a path as a list of indices into `points`.
// When all==true, returns extra points that are on edges of the hull.
// Arguments:
// points = list of 2d points to get the hull of.
// all = when true, includes all points on the edges of the convex hull. Default: false.
// Example(2D):
// pts = [[-10,-10], [0,10], [10,10], [12,-10]];
// path = hull2d_path(pts);
// move_copies(pts) color("red") circle(1,$fn=12);
// polygon(points=pts, paths=[path]);
//
// Code based on this method:
// https://www.hackerearth.com/practice/math/geometry/line-sweep-technique/tutorial/
//
function hull2d_path(points, all=false) =
assert(is_path(points,2),"Invalid input to hull2d_path")
len(points) < 2 ? [] :
let( n = len(points),
ip = sortidx(points) )
// lower hull points
let( lh =
[ for( i = 2,
k = 2,
h = [ip[0],ip[1]]; // current list of hull point indices
i <= n;
k = i<n ? _backtracking(ip[i],points,h,2,k,all)+1 : k,
h = i<n ? [for(j=[0:1:k-2]) h[j], ip[i]] : [],
i = i+1
) if( i==n ) h ][0] )
// concat lower hull points with upper hull ones
[ for( i = n-2,
k = len(lh),
t = k+1,
h = lh; // current list of hull point indices
i >= -1;
k = i>=0 ? _backtracking(ip[i],points,h,t,k,all)+1 : k,
h = [for(j=[0:1:k-2]) h[j], if(i>0) ip[i]],
i = i-1
) if( i==-1 ) h ][0] ;
function _hull_collinear(points) =
let(
a = points[0],
i = max_index([for(pt=points) norm(pt-a)]),
n = points[i] - a
)
norm(n)==0 ? [0]
:
let(
points1d = [ for(p = points) (p-a)*n ],
min_i = min_index(points1d),
max_i = max_index(points1d)
) [min_i, max_i];
// Function: hull3d_faces()
// Usage:
// hull3d_faces(points)
// Description:
// Takes a list of arbitrary 3D points, and finds the convex hull polyhedron to enclose
// them. Returns a list of triangular faces, where each face is a list of indexes into the given `points`
// list. The output will be valid for use with the polyhedron command, but may include vertices that are in the interior of a face of the hull, so it is not
// necessarily the minimal representation of the hull.
// If all points passed to it are coplanar, then the return is the list of indices of points
// forming the convex hull polygon.
// Example(3D):
// pts = [[-20,-20,0], [20,-20,0], [0,20,5], [0,0,20]];
// faces = hull3d_faces(pts);
// move_copies(pts) color("red") sphere(1);
// %polyhedron(points=pts, faces=faces);
function hull3d_faces(points) =
assert(is_path(points,3),"Invalid input to hull3d_faces")
len(points) < 3 ? count(len(points))
: let ( // start with a single non-collinear triangle
tri = _noncollinear_triple(points, error=false)
)
tri==[] ? _hull_collinear(points)
: let(
a = tri[0],
b = tri[1],
c = tri[2],
plane = plane3pt_indexed(points, a, b, c),
d = _find_first_noncoplanar(plane, points)
)
d == len(points)
? /* all coplanar*/
let (
pts2d = project_plane([points[a], points[b], points[c]],points),
hull2d = hull2d_path(pts2d)
) hull2d
: let(
remaining = [for (i = [0:1:len(points)-1]) if (i!=a && i!=b && i!=c && i!=d) i],
// Build an initial tetrahedron.
// Swap b, c if d is in front of triangle t.
ifop = _is_point_above_plane(plane, points[d]),
bc = ifop? [c,b] : [b,c],
b = bc[0],
c = bc[1],
triangles = [
[a,b,c],
[d,b,a],
[c,d,a],
[b,d,c]
],
// calculate the plane equations
planes = [ for (t = triangles) plane3pt_indexed(points, t[0], t[1], t[2]) ]
) _hull3d_iterative(points, triangles, planes, remaining);
// Adds the remaining points one by one to the convex hull
function _hull3d_iterative(points, triangles, planes, remaining, _i=0) = //let( EPSILON=1e-12 )
_i >= len(remaining) ? triangles :
let (
// pick a point
i = remaining[_i],
// evaluate the triangle plane equations at point i
planeq_val = planes*[each points[i], -1],
// find the triangles that are in conflict with the point (point not inside)
conflicts = [for (i = [0:1:len(planeq_val)-1]) if (planeq_val[i]>EPSILON) i ],
// collect the halfedges of all triangles that are in conflict
halfedges = [
for(c = conflicts, i = [0:2])
[triangles[c][i], triangles[c][(i+1)%3]]
],
// find the outer perimeter of the set of conflicting triangles
horizon = _remove_internal_edges(halfedges),
// generate new triangles connecting point i to each horizon halfedge vertices
tri2add = [ for (h = horizon) concat(h,i) ],
// add tria2add and remove conflict triangles
new_triangles =
concat( tri2add,
[ for (i = [0:1:len(planes)-1]) if (planeq_val[i]<=EPSILON) triangles[i] ]
),
// add the plane equations of new added triangles and remove the plane equations of the conflict ones
new_planes =
[ for (t = tri2add) plane3pt_indexed(points, t[0], t[1], t[2]) ,
for (i = [0:1:len(planes)-1]) if (planeq_val[i]<=EPSILON) planes[i] ]
) _hull3d_iterative(
points,
new_triangles,
new_planes,
remaining,
_i+1
);
function _remove_internal_edges(halfedges) = [
for (h = halfedges)
if (!in_list(reverse(h), halfedges))
h
];
function _find_first_noncoplanar(plane, points, i=0) =
(i >= len(points) || !are_points_on_plane([points[i]],plane))? i :
_find_first_noncoplanar(plane, points, i+1);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
// Section: Convex Sets

252
hull.scad
View file

@ -1,252 +0,0 @@
//////////////////////////////////////////////////////////////////////
// LibFile: hull.scad
// Functions to create 2D and 3D convex hulls.
// Derived from Oskar Linde's Hull:
// - https://github.com/openscad/scad-utils
// Includes:
// include <BOSL2/std.scad>
// include <BOSL2/hull.scad>
//////////////////////////////////////////////////////////////////////
// Section: Convex Hulls
// Function: hull()
// Usage:
// hull(points);
// Description:
// Takes a list of 2D or 3D points (but not both in the same list) and returns either the list of
// indexes into `points` that forms the 2D convex hull perimeter path, or the list of faces that
// form the 3d convex hull surface. Each face is a list of indexes into `points`. If the input
// points are co-linear, the result will be the indexes of the two extrema points. If the input
// points are co-planar, the results will be a simple list of vertex indices that will form a planar
// perimeter. Otherwise a list of faces will be returned, where each face is a simple list of
// vertex indices for the perimeter of the face.
// Arguments:
// points = The set of 2D or 3D points to find the hull of.
function hull(points) =
assert(is_path(points),"Invalid input to hull")
len(points[0]) == 2
? hull2d_path(points)
: hull3d_faces(points);
// Module: hull_points()
// Usage:
// hull_points(points, [fast]);
// Description:
// If given a list of 2D points, creates a 2D convex hull polygon that encloses all those points.
// If given a list of 3D points, creates a 3D polyhedron that encloses all the points. This should
// handle about 4000 points in slow mode. If `fast` is set to true, this should be able to handle
// far more. When fast mode is off, 3d hulls that lie in a plane will produce a single face of a polyhedron, which can be viewed in preview but will not render.
// Arguments:
// points = The list of points to form a hull around.
// fast = If true for 3d case, uses a faster cheat that may handle more points, but also may emit warnings that can stop your script if you have "Halt on first warning" enabled. Ignored for the 2d case. Default: false
// Example(2D):
// pts = [[-10,-10], [0,10], [10,10], [12,-10]];
// hull_points(pts);
// Example:
// pts = [for (phi = [30:60:150], theta = [0:60:359]) spherical_to_xyz(10, theta, phi)];
// hull_points(pts);
module hull_points(points, fast=false) {
assert(is_path(points))
assert(len(points)>=3, "Point list must contain 3 points")
if (len(points[0])==2)
hull() polygon(points=points);
else {
if (fast) {
extra = len(points)%3;
faces = [
[for(i=[0:1:extra+2])i], // If vertex count not divisible by 3, combine extras with first 3
for(i=[extra+3:3:len(points)-3])[i,i+1,i+2]
];
hull() polyhedron(points=points, faces=faces);
} else {
faces = hull(points);
if (is_num(faces[0])){
if (len(faces)<=2) echo("Hull contains only two points");
else polyhedron(points=points, faces=[faces]);
}
else polyhedron(points=points, faces=faces);
}
}
}
function _backtracking(i,points,h,t,m,all) =
m<t || _is_cw(points[i], points[h[m-1]], points[h[m-2]],all) ? m :
_backtracking(i,points,h,t,m-1,all) ;
// clockwise check (2d)
function _is_cw(a,b,c,all) =
all ? cross(a-c,b-c)<=EPSILON*norm(a-c)*norm(b-c) :
cross(a-c,b-c)<-EPSILON*norm(a-c)*norm(b-c);
// Function: hull2d_path()
// Usage:
// hull2d_path(points,all)
// Description:
// Takes a list of arbitrary 2D points, and finds the convex hull polygon to enclose them.
// Returns a path as a list of indices into `points`.
// When all==true, returns extra points that are on edges of the hull.
// Arguments:
// points = list of 2d points to get the hull of.
// all = when true, includes all points on the edges of the convex hull. Default: false.
// Example(2D):
// pts = [[-10,-10], [0,10], [10,10], [12,-10]];
// path = hull2d_path(pts);
// move_copies(pts) color("red") circle(1,$fn=12);
// polygon(points=pts, paths=[path]);
//
// Code based on this method:
// https://www.hackerearth.com/practice/math/geometry/line-sweep-technique/tutorial/
//
function hull2d_path(points, all=false) =
assert(is_path(points,2),"Invalid input to hull2d_path")
len(points) < 2 ? [] :
let( n = len(points),
ip = sortidx(points) )
// lower hull points
let( lh =
[ for( i = 2,
k = 2,
h = [ip[0],ip[1]]; // current list of hull point indices
i <= n;
k = i<n ? _backtracking(ip[i],points,h,2,k,all)+1 : k,
h = i<n ? [for(j=[0:1:k-2]) h[j], ip[i]] : [],
i = i+1
) if( i==n ) h ][0] )
// concat lower hull points with upper hull ones
[ for( i = n-2,
k = len(lh),
t = k+1,
h = lh; // current list of hull point indices
i >= -1;
k = i>=0 ? _backtracking(ip[i],points,h,t,k,all)+1 : k,
h = [for(j=[0:1:k-2]) h[j], if(i>0) ip[i]],
i = i-1
) if( i==-1 ) h ][0] ;
function _hull_collinear(points) =
let(
a = points[0],
i = max_index([for(pt=points) norm(pt-a)]),
n = points[i] - a
)
norm(n)==0 ? [0]
:
let(
points1d = [ for(p = points) (p-a)*n ],
min_i = min_index(points1d),
max_i = max_index(points1d)
) [min_i, max_i];
// Function: hull3d_faces()
// Usage:
// hull3d_faces(points)
// Description:
// Takes a list of arbitrary 3D points, and finds the convex hull polyhedron to enclose
// them. Returns a list of triangular faces, where each face is a list of indexes into the given `points`
// list. The output will be valid for use with the polyhedron command, but may include vertices that are in the interior of a face of the hull, so it is not
// necessarily the minimal representation of the hull.
// If all points passed to it are coplanar, then the return is the list of indices of points
// forming the convex hull polygon.
// Example(3D):
// pts = [[-20,-20,0], [20,-20,0], [0,20,5], [0,0,20]];
// faces = hull3d_faces(pts);
// move_copies(pts) color("red") sphere(1);
// %polyhedron(points=pts, faces=faces);
function hull3d_faces(points) =
assert(is_path(points,3),"Invalid input to hull3d_faces")
len(points) < 3 ? count(len(points))
: let ( // start with a single non-collinear triangle
tri = _noncollinear_triple(points, error=false)
)
tri==[] ? _hull_collinear(points)
: let(
a = tri[0],
b = tri[1],
c = tri[2],
plane = plane3pt_indexed(points, a, b, c),
d = _find_first_noncoplanar(plane, points)
)
d == len(points)
? /* all coplanar*/
let (
pts2d = project_plane([points[a], points[b], points[c]],points),
hull2d = hull2d_path(pts2d)
) hull2d
: let(
remaining = [for (i = [0:1:len(points)-1]) if (i!=a && i!=b && i!=c && i!=d) i],
// Build an initial tetrahedron.
// Swap b, c if d is in front of triangle t.
ifop = _is_point_above_plane(plane, points[d]),
bc = ifop? [c,b] : [b,c],
b = bc[0],
c = bc[1],
triangles = [
[a,b,c],
[d,b,a],
[c,d,a],
[b,d,c]
],
// calculate the plane equations
planes = [ for (t = triangles) plane3pt_indexed(points, t[0], t[1], t[2]) ]
) _hull3d_iterative(points, triangles, planes, remaining);
// Adds the remaining points one by one to the convex hull
function _hull3d_iterative(points, triangles, planes, remaining, _i=0) = //let( EPSILON=1e-12 )
_i >= len(remaining) ? triangles :
let (
// pick a point
i = remaining[_i],
// evaluate the triangle plane equations at point i
planeq_val = planes*[each points[i], -1],
// find the triangles that are in conflict with the point (point not inside)
conflicts = [for (i = [0:1:len(planeq_val)-1]) if (planeq_val[i]>EPSILON) i ],
// collect the halfedges of all triangles that are in conflict
halfedges = [
for(c = conflicts, i = [0:2])
[triangles[c][i], triangles[c][(i+1)%3]]
],
// find the outer perimeter of the set of conflicting triangles
horizon = _remove_internal_edges(halfedges),
// generate new triangles connecting point i to each horizon halfedge vertices
tri2add = [ for (h = horizon) concat(h,i) ],
// add tria2add and remove conflict triangles
new_triangles =
concat( tri2add,
[ for (i = [0:1:len(planes)-1]) if (planeq_val[i]<=EPSILON) triangles[i] ]
),
// add the plane equations of new added triangles and remove the plane equations of the conflict ones
new_planes =
[ for (t = tri2add) plane3pt_indexed(points, t[0], t[1], t[2]) ,
for (i = [0:1:len(planes)-1]) if (planeq_val[i]<=EPSILON) planes[i] ]
) _hull3d_iterative(
points,
new_triangles,
new_planes,
remaining,
_i+1
);
function _remove_internal_edges(halfedges) = [
for (h = halfedges)
if (!in_list(reverse(h), halfedges))
h
];
function _find_first_noncoplanar(plane, points, i=0) =
(i >= len(points) || !are_points_on_plane([points[i]],plane))? i :
_find_first_noncoplanar(plane, points, i+1);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -8,9 +8,8 @@
// FileFootnotes: STD=Included in std.scad
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Section: Volume Division Mutators
// Section: Bounding Box
//////////////////////////////////////////////////////////////////////
// Module: bounding_box()
@ -96,341 +95,6 @@ module bounding_box(excess=0, planar=false) {
}
// Function&Module: half_of()
//
// Usage: as module
// half_of(v, [cp], [s], [planar]) ...
// Usage: as function
// result = half_of(p,v,[cp]);
//
// Description:
// Slices an object at a cut plane, and masks away everything that is on one side. The v parameter is either a plane specification or
// a normal vector. The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// When called as a function, you must supply a vnf, path or region in p. If planar is set to true for the module version the operation
// is performed in 2D and UP and DOWN are treated as equivalent to BACK and FWD respectively.
//
// Arguments:
// p = path, region or VNF to slice. (Function version)
// v = Normal of plane to slice at. Keeps everything on the side the normal points to. Default: [0,0,1] (UP)
// cp = If given as a scalar, moves the cut plane along the normal by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0]
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// planar = If true, perform a 2D operation. When planar, a `v` of `UP` or `DOWN` becomes equivalent of `BACK` and `FWD` respectively. (Module version). Default: false.
//
// Examples:
// half_of(DOWN+BACK, cp=[0,-10,0]) cylinder(h=40, r1=10, r2=0, center=false);
// half_of(DOWN+LEFT, s=200) sphere(d=150);
// Example(2D):
// half_of([1,1], planar=true) circle(d=50);
module half_of(v=UP, cp, s=100, planar=false)
{
cp = is_vector(v,4)? assert(cp==undef, "Don't use cp with plane definition.") plane_normal(v) * v[3] :
is_vector(cp)? cp :
is_num(cp)? cp*unit(v) :
[0,0,0];
v = is_vector(v,4)? plane_normal(v) : v;
if (cp != [0,0,0]) {
translate(cp) half_of(v=v, s=s, planar=planar) translate(-cp) children();
} else if (planar) {
v = (v==UP)? BACK : (v==DOWN)? FWD : v;
ang = atan2(v.y, v.x);
difference() {
children();
rotate(ang+90) {
back(s/2) square(s, center=true);
}
}
} else {
difference() {
children();
rot(from=UP, to=-v) {
up(s/2) cube(s, center=true);
}
}
}
}
function half_of(p, v=UP, cp) =
is_vnf(p) ?
assert(is_vector(v) && (len(v)==3 || len(v)==4),str("Must give 3-vector or plane specification",v))
assert(select(v,0,2)!=[0,0,0], "vector v must be nonzero")
let(
plane = is_vector(v,4) ? assert(cp==undef, "Don't use cp with plane definition.") v
: is_undef(cp) ? [each v, 0]
: is_num(cp) ? [each v, cp*(v*v)/norm(v)]
: assert(is_vector(cp,3),"Centerpoint must be a 3-vector")
[each v, cp*v]
)
vnf_halfspace(plane, p)
: is_path(p) || is_region(p) ?
let(
v = (v==UP)? BACK : (v==DOWN)? FWD : v,
cp = is_undef(cp) ? [0,0]
: is_num(cp) ? v*cp
: assert(is_vector(cp,2) || (is_vector(cp,3) && cp.z==0),"Centerpoint must be 2-vector")
cp
)
assert(is_vector(v,2) || (is_vector(v,3) && v.z==0),"Must give 2-vector")
assert(!all_zero(v), "Vector v must be nonzero")
let(
bounds = pointlist_bounds(move(-cp,p)),
L = 2*max(flatten(bounds)),
n = unit(v),
u = [-n.y,n.x],
box = [cp+u*L, cp+(v+u)*L, cp+(v-u)*L, cp-u*L]
)
intersection(box,p)
: assert(false, "Input must be a region, path or VNF");
/* This code cut 3d paths but leaves behind connecting line segments
is_path(p) ?
//assert(len(p[0]) == d, str("path must have dimension ", d))
let(z = [for(x=p) (x-cp)*v])
[ for(i=[0:len(p)-1]) each concat(z[i] >= 0 ? [p[i]] : [],
// we assume a closed path here;
// to make this correct for an open path,
// just replace this by [] when i==len(p)-1:
let(j=(i+1)%len(p))
// the remaining path may have flattened sections, but this cannot
// create self-intersection or whiskers:
z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ]
:
*/
// Function&Module: left_half()
//
// Usage: as module
// left_half([s], [x]) ...
// left_half(planar=true, [s], [x]) ...
// Usage: as function
// result = left_half(p, [x]);
//
// Description:
// Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
//
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// x = The X coordinate of the cut-plane. Default: 0
// planar = If true, perform a 2D operation. (Module version) Default: false.
// Examples:
// left_half() sphere(r=20);
// left_half(x=-8) sphere(r=20);
// Example(2D):
// left_half(planar=true) circle(r=20);
module left_half(s=100, x=0, planar=false)
{
dir = LEFT;
difference() {
children();
translate([x,0,0]-dir*s/2) {
if (planar) {
square(s, center=true);
} else {
cube(s, center=true);
}
}
}
}
function left_half(p,x=0) = half_of(p, LEFT, [x,0,0]);
// Function&Module: right_half()
//
// Usage: as module
// right_half([s], [x]) ...
// right_half(planar=true, [s], [x]) ...
// Usage: as function
// result = right_half(p, [x]);
//
// Description:
// Slices an object at a vertical Y-Z cut plane, and masks away everything that is left of it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// x = The X coordinate of the cut-plane. Default: 0
// planar = If true, perform a 2D operation. (Module version) Default: false.
// Examples(FlatSpin,VPD=175):
// right_half() sphere(r=20);
// right_half(x=-5) sphere(r=20);
// Example(2D):
// right_half(planar=true) circle(r=20);
module right_half(s=100, x=0, planar=false)
{
dir = RIGHT;
difference() {
children();
translate([x,0,0]-dir*s/2) {
if (planar) {
square(s, center=true);
} else {
cube(s, center=true);
}
}
}
}
function right_half(p,x=0) = half_of(p, RIGHT, [x,0,0]);
// Function&Module: front_half()
//
// Usage:
// front_half([s], [y]) ...
// front_half(planar=true, [s], [y]) ...
// Usage: as function
// result = front_half(p, [y]);
//
// Description:
// Slices an object at a vertical X-Z cut plane, and masks away everything that is behind it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// y = The Y coordinate of the cut-plane. Default: 0
// planar = If true, perform a 2D operation. (Module version) Default: false.
// Examples(FlatSpin,VPD=175):
// front_half() sphere(r=20);
// front_half(y=5) sphere(r=20);
// Example(2D):
// front_half(planar=true) circle(r=20);
module front_half(s=100, y=0, planar=false)
{
dir = FWD;
difference() {
children();
translate([0,y,0]-dir*s/2) {
if (planar) {
square(s, center=true);
} else {
cube(s, center=true);
}
}
}
}
function front_half(p,y=0) = half_of(p, FRONT, [0,y,0]);
// Function&Module: back_half()
//
// Usage:
// back_half([s], [y]) ...
// back_half(planar=true, [s], [y]) ...
// Usage: as function
// result = back_half(p, [y]);
//
// Description:
// Slices an object at a vertical X-Z cut plane, and masks away everything that is in front of it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// y = The Y coordinate of the cut-plane. Default: 0
// planar = If true, perform a 2D operation. (Module version) Default: false.
// Examples:
// back_half() sphere(r=20);
// back_half(y=8) sphere(r=20);
// Example(2D):
// back_half(planar=true) circle(r=20);
module back_half(s=100, y=0, planar=false)
{
dir = BACK;
difference() {
children();
translate([0,y,0]-dir*s/2) {
if (planar) {
square(s, center=true);
} else {
cube(s, center=true);
}
}
}
}
function back_half(p,y=0) = half_of(p, BACK, [0,y,0]);
// Function&Module: bottom_half()
//
// Usage:
// bottom_half([s], [z]) ...
// Usage: as function
// result = bottom_half(p, [z]);
//
// Description:
// Slices an object at a horizontal X-Y cut plane, and masks away everything that is above it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// z = The Z coordinate of the cut-plane. Default: 0
// Examples:
// bottom_half() sphere(r=20);
// bottom_half(z=-10) sphere(r=20);
module bottom_half(s=100, z=0)
{
dir = DOWN;
difference() {
children();
translate([0,0,z]-dir*s/2) {
cube(s, center=true);
}
}
}
function bottom_half(p,z=0) = half_of(p,BOTTOM,[0,0,z]);
// Function&Module: top_half()
//
// Usage:
// top_half([s], [z]) ...
// result = top_half(p, [z]);
//
// Description:
// Slices an object at a horizontal X-Y cut plane, and masks away everything that is below it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// z = The Z coordinate of the cut-plane. Default: 0
// Examples(Spin,VPD=175):
// top_half() sphere(r=20);
// top_half(z=5) sphere(r=20);
module top_half(s=100, z=0)
{
dir = UP;
difference() {
children();
translate([0,0,z]-dir*s/2) {
cube(s, center=true);
}
}
}
function top_half(p,z=0) = half_of(p,UP,[0,0,z]);
//////////////////////////////////////////////////////////////////////
// Section: Warp Mutators
//////////////////////////////////////////////////////////////////////

View file

@ -1,15 +1,352 @@
//////////////////////////////////////////////////////////////////////
// LibFile: partitions.scad
// Modules to help partition large objects into smaller parts that can be reassembled.
// Cut objects with a plane, or partition them into interlocking pieces for easy printing of large objects.
// Includes:
// include <BOSL2/std.scad>
// include <BOSL2/partitions.scad>
// FileGroup: Advanced Modeling
// FileSummary: Modules to help partition large objects into smaller assembled parts.
// FileSummary: Cut objects with a plane or partition them into interlocking pieces.
// FileFootnotes: STD=Included in std.scad
//////////////////////////////////////////////////////////////////////
// Section: Partitioning
// Section: Planar Cutting
// Function&Module: half_of()
//
// Usage: as module
// half_of(v, [cp], [s], [planar]) ...
// Usage: as function
// result = half_of(p,v,[cp]);
//
// Description:
// Slices an object at a cut plane, and masks away everything that is on one side. The v parameter is either a plane specification or
// a normal vector. The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// When called as a function, you must supply a vnf, path or region in p. If planar is set to true for the module version the operation
// is performed in 2D and UP and DOWN are treated as equivalent to BACK and FWD respectively.
//
// Arguments:
// p = path, region or VNF to slice. (Function version)
// v = Normal of plane to slice at. Keeps everything on the side the normal points to. Default: [0,0,1] (UP)
// cp = If given as a scalar, moves the cut plane along the normal by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0]
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// planar = If true, perform a 2D operation. When planar, a `v` of `UP` or `DOWN` becomes equivalent of `BACK` and `FWD` respectively. (Module version). Default: false.
//
// Examples:
// half_of(DOWN+BACK, cp=[0,-10,0]) cylinder(h=40, r1=10, r2=0, center=false);
// half_of(DOWN+LEFT, s=200) sphere(d=150);
// Example(2D):
// half_of([1,1], planar=true) circle(d=50);
module half_of(v=UP, cp, s=100, planar=false)
{
cp = is_vector(v,4)? assert(cp==undef, "Don't use cp with plane definition.") plane_normal(v) * v[3] :
is_vector(cp)? cp :
is_num(cp)? cp*unit(v) :
[0,0,0];
v = is_vector(v,4)? plane_normal(v) : v;
if (cp != [0,0,0]) {
translate(cp) half_of(v=v, s=s, planar=planar) translate(-cp) children();
} else if (planar) {
v = (v==UP)? BACK : (v==DOWN)? FWD : v;
ang = atan2(v.y, v.x);
difference() {
children();
rotate(ang+90) {
back(s/2) square(s, center=true);
}
}
} else {
difference() {
children();
rot(from=UP, to=-v) {
up(s/2) cube(s, center=true);
}
}
}
}
function half_of(p, v=UP, cp) =
is_vnf(p) ?
assert(is_vector(v) && (len(v)==3 || len(v)==4),str("Must give 3-vector or plane specification",v))
assert(select(v,0,2)!=[0,0,0], "vector v must be nonzero")
let(
plane = is_vector(v,4) ? assert(cp==undef, "Don't use cp with plane definition.") v
: is_undef(cp) ? [each v, 0]
: is_num(cp) ? [each v, cp*(v*v)/norm(v)]
: assert(is_vector(cp,3),"Centerpoint must be a 3-vector")
[each v, cp*v]
)
vnf_halfspace(plane, p)
: is_path(p) || is_region(p) ?
let(
v = (v==UP)? BACK : (v==DOWN)? FWD : v,
cp = is_undef(cp) ? [0,0]
: is_num(cp) ? v*cp
: assert(is_vector(cp,2) || (is_vector(cp,3) && cp.z==0),"Centerpoint must be 2-vector")
cp
)
assert(is_vector(v,2) || (is_vector(v,3) && v.z==0),"Must give 2-vector")
assert(!all_zero(v), "Vector v must be nonzero")
let(
bounds = pointlist_bounds(move(-cp,p)),
L = 2*max(flatten(bounds)),
n = unit(v),
u = [-n.y,n.x],
box = [cp+u*L, cp+(v+u)*L, cp+(v-u)*L, cp-u*L]
)
intersection(box,p)
: assert(false, "Input must be a region, path or VNF");
/* This code cut 3d paths but leaves behind connecting line segments
is_path(p) ?
//assert(len(p[0]) == d, str("path must have dimension ", d))
let(z = [for(x=p) (x-cp)*v])
[ for(i=[0:len(p)-1]) each concat(z[i] >= 0 ? [p[i]] : [],
// we assume a closed path here;
// to make this correct for an open path,
// just replace this by [] when i==len(p)-1:
let(j=(i+1)%len(p))
// the remaining path may have flattened sections, but this cannot
// create self-intersection or whiskers:
z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ]
:
*/
// Function&Module: left_half()
//
// Usage: as module
// left_half([s], [x]) ...
// left_half(planar=true, [s], [x]) ...
// Usage: as function
// result = left_half(p, [x]);
//
// Description:
// Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
//
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// x = The X coordinate of the cut-plane. Default: 0
// planar = If true, perform a 2D operation. (Module version) Default: false.
// Examples:
// left_half() sphere(r=20);
// left_half(x=-8) sphere(r=20);
// Example(2D):
// left_half(planar=true) circle(r=20);
module left_half(s=100, x=0, planar=false)
{
dir = LEFT;
difference() {
children();
translate([x,0,0]-dir*s/2) {
if (planar) {
square(s, center=true);
} else {
cube(s, center=true);
}
}
}
}
function left_half(p,x=0) = half_of(p, LEFT, [x,0,0]);
// Function&Module: right_half()
//
// Usage: as module
// right_half([s], [x]) ...
// right_half(planar=true, [s], [x]) ...
// Usage: as function
// result = right_half(p, [x]);
//
// Description:
// Slices an object at a vertical Y-Z cut plane, and masks away everything that is left of it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// x = The X coordinate of the cut-plane. Default: 0
// planar = If true, perform a 2D operation. (Module version) Default: false.
// Examples(FlatSpin,VPD=175):
// right_half() sphere(r=20);
// right_half(x=-5) sphere(r=20);
// Example(2D):
// right_half(planar=true) circle(r=20);
module right_half(s=100, x=0, planar=false)
{
dir = RIGHT;
difference() {
children();
translate([x,0,0]-dir*s/2) {
if (planar) {
square(s, center=true);
} else {
cube(s, center=true);
}
}
}
}
function right_half(p,x=0) = half_of(p, RIGHT, [x,0,0]);
// Function&Module: front_half()
//
// Usage:
// front_half([s], [y]) ...
// front_half(planar=true, [s], [y]) ...
// Usage: as function
// result = front_half(p, [y]);
//
// Description:
// Slices an object at a vertical X-Z cut plane, and masks away everything that is behind it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// y = The Y coordinate of the cut-plane. Default: 0
// planar = If true, perform a 2D operation. (Module version) Default: false.
// Examples(FlatSpin,VPD=175):
// front_half() sphere(r=20);
// front_half(y=5) sphere(r=20);
// Example(2D):
// front_half(planar=true) circle(r=20);
module front_half(s=100, y=0, planar=false)
{
dir = FWD;
difference() {
children();
translate([0,y,0]-dir*s/2) {
if (planar) {
square(s, center=true);
} else {
cube(s, center=true);
}
}
}
}
function front_half(p,y=0) = half_of(p, FRONT, [0,y,0]);
// Function&Module: back_half()
//
// Usage:
// back_half([s], [y]) ...
// back_half(planar=true, [s], [y]) ...
// Usage: as function
// result = back_half(p, [y]);
//
// Description:
// Slices an object at a vertical X-Z cut plane, and masks away everything that is in front of it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// y = The Y coordinate of the cut-plane. Default: 0
// planar = If true, perform a 2D operation. (Module version) Default: false.
// Examples:
// back_half() sphere(r=20);
// back_half(y=8) sphere(r=20);
// Example(2D):
// back_half(planar=true) circle(r=20);
module back_half(s=100, y=0, planar=false)
{
dir = BACK;
difference() {
children();
translate([0,y,0]-dir*s/2) {
if (planar) {
square(s, center=true);
} else {
cube(s, center=true);
}
}
}
}
function back_half(p,y=0) = half_of(p, BACK, [0,y,0]);
// Function&Module: bottom_half()
//
// Usage:
// bottom_half([s], [z]) ...
// Usage: as function
// result = bottom_half(p, [z]);
//
// Description:
// Slices an object at a horizontal X-Y cut plane, and masks away everything that is above it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// z = The Z coordinate of the cut-plane. Default: 0
// Examples:
// bottom_half() sphere(r=20);
// bottom_half(z=-10) sphere(r=20);
module bottom_half(s=100, z=0)
{
dir = DOWN;
difference() {
children();
translate([0,0,z]-dir*s/2) {
cube(s, center=true);
}
}
}
function bottom_half(p,z=0) = half_of(p,BOTTOM,[0,0,z]);
// Function&Module: top_half()
//
// Usage:
// top_half([s], [z]) ...
// result = top_half(p, [z]);
//
// Description:
// Slices an object at a horizontal X-Y cut plane, and masks away everything that is below it.
// The s parameter is needed for the module
// version to control the size of the masking cube. If s is too large then the preview display will flip around and display the
// wrong half, but if it is too small it won't fully mask your model.
// Arguments:
// p = VNF, region or path to slice (function version)
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may display the wrong half. (Module version) Default: 100
// z = The Z coordinate of the cut-plane. Default: 0
// Examples(Spin,VPD=175):
// top_half() sphere(r=20);
// top_half(z=5) sphere(r=20);
module top_half(s=100, z=0)
{
dir = UP;
difference() {
children();
translate([0,0,z]-dir*s/2) {
cube(s, center=true);
}
}
}
function top_half(p,z=0) = half_of(p,UP,[0,0,z]);
// Section: Partioning into Interlocking Pieces
function _partition_subpath(type) =

View file

@ -9,9 +9,6 @@
//////////////////////////////////////////////////////////////////////
include <hull.scad>
// CommonCode:
// $fn=96;

View file

@ -30,13 +30,12 @@ include <quaternions.scad>
include <affine.scad>
include <coords.scad>
include <geometry.scad>
include <hull.scad>
include <regions.scad>
include <strings.scad>
include <skin.scad>
include <vnf.scad>
include <utility.scad>
include <partitions.scad>
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1026,6 +1026,192 @@ module test_rot_decode() {
*test_rot_decode();
function standard_faces(faces) =
sort([for(face=faces)
list_rotate(face, min_index(face))]);
module test_hull() {
assert_equal(hull([[3,4],[5,5]]), [0,1]);
assert_equal(hull([[3,4,1],[5,5,3]]), [0,1]);
test_collinear_2d = let(u = unit([5,3])) [ for(i = [9,2,3,4,5,7,12,15,13]) i * u ];
assert_equal(sort(hull(test_collinear_2d)), [1,7]);
test_collinear_3d = let(u = unit([5,3,2])) [ for(i = [9,2,3,4,5,7,12,15,13]) i * u ];
assert_equal(sort(hull(test_collinear_3d)), [1,7]);
/* // produces some extra points along edges
test_square_2d = [for(x=[1:5], y=[2:6]) [x,y]];
echo(test_square_2d);
move_copies(test_square_2d) circle(r=.1,$fn=16);
color("red")move_copies(select(test_square_2d,hull(test_square_2d))) circle(r=.1,$fn=16);
*/
/* // also produces extra points along edges
test_square_2d = rot(22,p=[for(x=[1:5], y=[2:6]) [x,y]]);
echo(test_square_2d);
move_copies(test_square_2d) circle(r=.1,$fn=16);
color("red")move_copies(select(test_square_2d,hull(test_square_2d))) circle(r=.1,$fn=16);
*/
rand10_2d = [[1.55356, -1.98965], [4.23157, -0.947788], [-4.06193, -1.55463],
[1.23889, -3.73133], [-1.02637, -4.0155], [4.26806, -4.61909],
[3.59556, -3.1574], [-2.77776, -4.21857], [-3.66253,-4.34458], [1.82324, 0.102025]];
assert_equal(sort(hull(rand10_2d)), [1,2,5,8,9]);
rand75_2d = [[-3.14743, -3.28139], [0.15343, -0.370249], [0.082565, 3.95939], [-2.56925, -3.16262], [-1.59463, 4.20893],
[-4.90744, -1.21374], [-1.0819, -1.93703], [-3.72723, -3.0744], [-3.34339, 1.53535], [3.15803, -0.307388], [4.23289,
4.46259], [1.73624, 1.38918], [3.72087, -1.55028], [1.2604, 2.30502], [-0.966431, 1.673], [-3.26866, -0.531443], [1.52605,
0.991804], [-1.26305, 1.0737], [-4.31943, 4.11932], [0.488101, 0.0425981], [1.0233, -0.723037], [-4.73406, 2.14568],
[-4.75915, 3.83262], [4.90999, -2.76668], [1.91971, -3.8604], [4.38594, -0.761767], [-0.352984, 1.55291], [2.02714,
-0.340099], [1.76052, 2.09196], [-1.27485, -4.39477], [4.36364, 3.84964], [0.593612, -4.00028], [3.06833, -3.67117],
[4.26834, -4.21213], [4.60226, -0.120432], [-2.45646, 2.60327], [-4.79461, 3.83724], [-3.29755, 0.760159], [0.218423,
4.1687], [-0.115829, -2.06242], [-3.96188, 3.21568], [4.3018, -2.5299], [-4.41694, 4.75173], [-3.8393, 2.82212], [-1.14268,
1.80751], [2.05805, 1.68593], [-3.0159, -2.91139], [-1.44828, -1.93564], [-0.265887, 0.519893], [-0.457361, -0.610096],
[-0.426359, -2.37315], [-3.1018, 2.31141], [0.179141, -3.56242], [-0.491786, 0.813055], [-3.28502, -1.18933], [0.0914813,
2.16122], [4.5777, 4.83972], [-1.07096, 2.74992], [-0.698689, 3.9032], [-1.21809, -1.54434], [3.14457, 4.92302], [-4.63176,
2.81952], [4.84414, 4.63699], [2.4259, -0.747268], [-1.52088, -4.58305], [1.6961, -3.73678], [-0.483003, -3.67283],
[-3.72746, -0.284265], [2.07629, 1.99902], [-3.12698, -0.96353], [4.02254, 3.41521], [-0.963391, -3.2143], [0.315255,
0.593049], [1.57006, 1.80436], [4.60957, -2.86325]];
assert_equal(sort(hull(rand75_2d)),[5,7,23,33,36,42,56,60,62,64]);
rand10_2d_rot = rot([22,44,12], p=path3d(rand10_2d));
assert_equal(sort(hull(rand10_2d_rot)), [1,2,5,8,9]);
rand75_2d_rot = rot([122,-44,32], p=path3d(rand75_2d));
assert_equal(sort(hull(rand75_2d_rot)), [5,7,23,33,36,42,56,60,62,64]);
testpoints_on_sphere = [ for(p =
[
[1,PHI,0], [-1,PHI,0], [1,-PHI,0], [-1,-PHI,0],
[0,1,PHI], [0,-1,PHI], [0,1,-PHI], [0,-1,-PHI],
[PHI,0,1], [-PHI,0,1], [PHI,0,-1], [-PHI,0,-1]
])
unit(p)
];
assert_equal(standard_faces(hull(testpoints_on_sphere)),
standard_faces([[8, 4, 0], [0, 4, 1], [4, 8, 5], [8, 2, 5], [2, 3, 5], [0, 1, 6], [3, 2, 7], [1, 4, 9], [4, 5, 9],
[5, 3, 9], [8, 0, 10], [2, 8, 10], [0, 6, 10], [6, 7, 10], [7, 2, 10], [6, 1, 11], [3, 7, 11], [7, 6, 11], [1, 9, 11], [9, 3, 11]]));
rand10_3d = [[14.0893, -15.2751, 21.0843], [-14.1564, 17.5751, 3.32094], [17.4966, 12.1717, 18.0607], [24.5489, 9.64591, 10.4738], [-12.0233, -24.4368, 13.1614],
[6.24019, -18.4135, 24.9554], [11.9438, -15.9724, -22.6454], [11.6147, 7.56059, 7.5667], [-19.7491, 9.42769, 15.3419], [-10.3726, 16.3559, 3.38503]];
assert_equal(standard_faces(hull(rand10_3d)),
standard_faces([[3, 6, 0], [1, 3, 2], [3, 0, 2], [6, 1, 4], [0, 6, 5], [6, 4, 5], [2, 0, 5], [1, 2, 8], [2, 5, 8], [4, 1, 8], [5, 4, 8], [6, 3, 9], [3, 1, 9], [1, 6, 9]]));
rand25_3d = [[-20.5261, 14.5058, -11.6349], [16.4625, 20.1316, 12.9816], [-14.0268, 5.58802, 17.686], [-5.47944, 16.2501,
5.3086], [20.2168, -11.8466, 12.4598], [14.4633, -15.1479, 4.82151], [12.7897, 5.25704, 19.6205], [11.2456,
18.2794, -3.47074], [-1.87665, 22.9852, 1.99367], [-15.6052, -2.11009, 14.0096], [-10.7389, -14.569,
5.6121], [24.5965, 17.9039, 20.8313], [-13.7054, 13.3362, 1.50374], [10.1111, -23.1494, 19.9305], [14.154,
19.6682, -0.170182], [-22.6438, 22.7429, -0.776773], [-9.75056, 17.8896, -8.04152], [23.1746, 20.5475,
22.6957], [-10.5356, -4.32407, -7.0911], [2.20779, -8.30749, 6.87185], [23.2643, 2.64462, -19.0087],
[24.4055, 24.4504, 23.4777], [-3.84086, -6.98473, -10.2889], [0.178043, -16.07, 16.8081], [-8.86482,
-12.8256, 14.7418], [11.1759, -11.5614, -11.643], [7.16751, 13.9344, -19.1675], [2.26602, -10.5374,
0.125718], [-13.9053, 11.1143, -21.9289], [24.9018, -23.5307, -21.4684], [-13.6609, -19.6495, -8.91583],
[-16.5393, -22.4105, -6.91617], [-4.11378, -3.14362, -5.6881], [7.50883, -17.5284, -0.0615319], [-7.41739,
0.0721313, -7.47111], [22.6975, -7.99655, 14.0555], [-13.3644, 9.26993, 20.858], [-13.6889, 16.7462,
-14.5836], [16.5137, 3.90703, -5.49396], [-6.75614, -11.1444, -24.5309], [22.9868, 10.0028, 12.2866],
[-4.81079, -0.967785, -10.4726], [-0.949023, 23.1441, -2.08208], [16.1256, -8.2295, -24.0113], [6.45274,
-7.21416, 23.1409], [22.8274, 1.07038, 19.1756], [-10.6256, -10.0112, -6.12274], [6.29254, -7.81875,
-24.4037], [22.8538, 8.78163, -6.82567], [-1.96142, 19.1728, -1.726]];
assert_equal(sort(hull(rand25_3d)),sort([[21, 29, 11], [29, 21, 20], [21, 14, 20], [20, 14, 26], [15, 0, 28], [13, 29, 31], [0, 15,
31], [15, 9, 31], [9, 24, 31], [24, 13, 31], [28, 0, 31], [11, 29, 35], [29, 13, 35], [15,
21, 36], [9, 15, 36], [24, 9, 36], [13, 24, 36], [15, 28, 37], [28, 26, 37], [28, 31, 39],
[31, 29, 39], [14, 21, 42], [21, 15, 42], [26, 14, 42], [15, 37, 42], [37, 26, 42], [29, 20,
43], [39, 29, 43], [20, 26, 43], [26, 28, 43], [21, 13, 44], [13, 36, 44], [36, 21, 44],
[21, 11, 45], [11, 35, 45], [13, 21, 45], [35, 13, 45], [28, 39, 47], [39, 43, 47], [43, 28, 47]]));
/* // Inconsistently treats coplanar faces: sometimes face center vertex is included in output, sometimes not
test_cube_3d = [for(x=[1:3], y=[1:3], z=[1:3]) [x,y,z]];
assert_equal(hull(test_cube_3d), [[3, 2, 0], [2, 3, 4], [26, 2, 5], [2, 4, 5], [4, 3, 6], [5, 4, 6], [5, 6, 7], [6, 26, 7], [26, 5, 8],
[5, 7, 8], [7, 26, 8], [0, 2, 9], [3, 0, 9], [6, 3, 9], [9, 2, 10], [2, 26, 11], [10, 2, 11], [6, 9, 12],
[26, 6, 15], [6, 12, 15], [9, 10, 18], [10, 11, 18], [12, 9, 18], [15, 12, 18], [26, 18, 19], [18, 11, 19],
[11, 26, 20], [26, 19, 20], [19, 11, 20], [15, 18, 21], [18, 26, 21], [26, 15, 24], [15, 21, 24], [21, 26, 24]]);
echo(len=len(hull(test_cube_3d)));
*/
}
test_hull();
module test_hull2d_path() {
assert_equal(hull([[3,4],[5,5]]), [0,1]);
assert_equal(hull([[3,4,1],[5,5,3]]), [0,1]);
test_collinear_2d = let(u = unit([5,3])) [ for(i = [9,2,3,4,5,7,12,15,13]) i * u ];
assert_equal(sort(hull(test_collinear_2d)), [1,7]);
test_collinear_3d = let(u = unit([5,3,2])) [ for(i = [9,2,3,4,5,7,12,15,13]) i * u ];
assert_equal(sort(hull(test_collinear_3d)), [1,7]);
rand10_2d = [[1.55356, -1.98965], [4.23157, -0.947788], [-4.06193, -1.55463],
[1.23889, -3.73133], [-1.02637, -4.0155], [4.26806, -4.61909],
[3.59556, -3.1574], [-2.77776, -4.21857], [-3.66253,-4.34458], [1.82324, 0.102025]];
assert_equal(sort(hull(rand10_2d)), [1,2,5,8,9]);
rand75_2d = [[-3.14743, -3.28139], [0.15343, -0.370249], [0.082565, 3.95939], [-2.56925, -3.16262], [-1.59463, 4.20893],
[-4.90744, -1.21374], [-1.0819, -1.93703], [-3.72723, -3.0744], [-3.34339, 1.53535], [3.15803, -0.307388], [4.23289,
4.46259], [1.73624, 1.38918], [3.72087, -1.55028], [1.2604, 2.30502], [-0.966431, 1.673], [-3.26866, -0.531443], [1.52605,
0.991804], [-1.26305, 1.0737], [-4.31943, 4.11932], [0.488101, 0.0425981], [1.0233, -0.723037], [-4.73406, 2.14568],
[-4.75915, 3.83262], [4.90999, -2.76668], [1.91971, -3.8604], [4.38594, -0.761767], [-0.352984, 1.55291], [2.02714,
-0.340099], [1.76052, 2.09196], [-1.27485, -4.39477], [4.36364, 3.84964], [0.593612, -4.00028], [3.06833, -3.67117],
[4.26834, -4.21213], [4.60226, -0.120432], [-2.45646, 2.60327], [-4.79461, 3.83724], [-3.29755, 0.760159], [0.218423,
4.1687], [-0.115829, -2.06242], [-3.96188, 3.21568], [4.3018, -2.5299], [-4.41694, 4.75173], [-3.8393, 2.82212], [-1.14268,
1.80751], [2.05805, 1.68593], [-3.0159, -2.91139], [-1.44828, -1.93564], [-0.265887, 0.519893], [-0.457361, -0.610096],
[-0.426359, -2.37315], [-3.1018, 2.31141], [0.179141, -3.56242], [-0.491786, 0.813055], [-3.28502, -1.18933], [0.0914813,
2.16122], [4.5777, 4.83972], [-1.07096, 2.74992], [-0.698689, 3.9032], [-1.21809, -1.54434], [3.14457, 4.92302], [-4.63176,
2.81952], [4.84414, 4.63699], [2.4259, -0.747268], [-1.52088, -4.58305], [1.6961, -3.73678], [-0.483003, -3.67283],
[-3.72746, -0.284265], [2.07629, 1.99902], [-3.12698, -0.96353], [4.02254, 3.41521], [-0.963391, -3.2143], [0.315255,
0.593049], [1.57006, 1.80436], [4.60957, -2.86325]];
assert_equal(sort(hull(rand75_2d)),[5,7,23,33,36,42,56,60,62,64]);
rand10_2d_rot = rot([22,44,12], p=path3d(rand10_2d));
assert_equal(sort(hull(rand10_2d_rot)), [1,2,5,8,9]);
rand75_2d_rot = rot([122,-44,32], p=path3d(rand75_2d));
assert_equal(sort(hull(rand75_2d_rot)), [5,7,23,33,36,42,56,60,62,64]);
}
test_hull2d_path();
module test_hull3d_faces() {
testpoints_on_sphere = [ for(p =
[
[1,PHI,0], [-1,PHI,0], [1,-PHI,0], [-1,-PHI,0],
[0,1,PHI], [0,-1,PHI], [0,1,-PHI], [0,-1,-PHI],
[PHI,0,1], [-PHI,0,1], [PHI,0,-1], [-PHI,0,-1]
])
unit(p)
];
assert_equal(standard_faces(hull(testpoints_on_sphere)),
standard_faces([[8, 4, 0], [0, 4, 1], [4, 8, 5], [8, 2, 5], [2, 3, 5], [0, 1, 6], [3, 2, 7], [1, 4, 9], [4, 5, 9],
[5, 3, 9], [8, 0, 10], [2, 8, 10], [0, 6, 10], [6, 7, 10], [7, 2, 10], [6, 1, 11], [3, 7, 11], [7, 6, 11], [1, 9, 11], [9, 3, 11]]));
rand10_3d = [[14.0893, -15.2751, 21.0843], [-14.1564, 17.5751, 3.32094], [17.4966, 12.1717, 18.0607], [24.5489, 9.64591, 10.4738], [-12.0233, -24.4368, 13.1614],
[6.24019, -18.4135, 24.9554], [11.9438, -15.9724, -22.6454], [11.6147, 7.56059, 7.5667], [-19.7491, 9.42769, 15.3419], [-10.3726, 16.3559, 3.38503]];
assert_equal(standard_faces(hull(rand10_3d)),
standard_faces([[3, 6, 0], [1, 3, 2], [3, 0, 2], [6, 1, 4], [0, 6, 5], [6, 4, 5], [2, 0, 5], [1, 2, 8], [2, 5, 8], [4, 1, 8], [5, 4, 8], [6, 3, 9], [3, 1, 9], [1, 6, 9]]));
rand25_3d = [[-20.5261, 14.5058, -11.6349], [16.4625, 20.1316, 12.9816], [-14.0268, 5.58802, 17.686], [-5.47944, 16.2501,
5.3086], [20.2168, -11.8466, 12.4598], [14.4633, -15.1479, 4.82151], [12.7897, 5.25704, 19.6205], [11.2456,
18.2794, -3.47074], [-1.87665, 22.9852, 1.99367], [-15.6052, -2.11009, 14.0096], [-10.7389, -14.569,
5.6121], [24.5965, 17.9039, 20.8313], [-13.7054, 13.3362, 1.50374], [10.1111, -23.1494, 19.9305], [14.154,
19.6682, -0.170182], [-22.6438, 22.7429, -0.776773], [-9.75056, 17.8896, -8.04152], [23.1746, 20.5475,
22.6957], [-10.5356, -4.32407, -7.0911], [2.20779, -8.30749, 6.87185], [23.2643, 2.64462, -19.0087],
[24.4055, 24.4504, 23.4777], [-3.84086, -6.98473, -10.2889], [0.178043, -16.07, 16.8081], [-8.86482,
-12.8256, 14.7418], [11.1759, -11.5614, -11.643], [7.16751, 13.9344, -19.1675], [2.26602, -10.5374,
0.125718], [-13.9053, 11.1143, -21.9289], [24.9018, -23.5307, -21.4684], [-13.6609, -19.6495, -8.91583],
[-16.5393, -22.4105, -6.91617], [-4.11378, -3.14362, -5.6881], [7.50883, -17.5284, -0.0615319], [-7.41739,
0.0721313, -7.47111], [22.6975, -7.99655, 14.0555], [-13.3644, 9.26993, 20.858], [-13.6889, 16.7462,
-14.5836], [16.5137, 3.90703, -5.49396], [-6.75614, -11.1444, -24.5309], [22.9868, 10.0028, 12.2866],
[-4.81079, -0.967785, -10.4726], [-0.949023, 23.1441, -2.08208], [16.1256, -8.2295, -24.0113], [6.45274,
-7.21416, 23.1409], [22.8274, 1.07038, 19.1756], [-10.6256, -10.0112, -6.12274], [6.29254, -7.81875,
-24.4037], [22.8538, 8.78163, -6.82567], [-1.96142, 19.1728, -1.726]];
assert_equal(sort(hull(rand25_3d)), sort([[21, 29, 11], [29, 21, 20], [21, 14, 20], [20, 14, 26], [15, 0, 28], [13, 29, 31], [0, 15,
31], [15, 9, 31], [9, 24, 31], [24, 13, 31], [28, 0, 31], [11, 29, 35], [29, 13, 35], [15,
21, 36], [9, 15, 36], [24, 9, 36], [13, 24, 36], [15, 28, 37], [28, 26, 37], [28, 31, 39],
[31, 29, 39], [14, 21, 42], [21, 15, 42], [26, 14, 42], [15, 37, 42], [37, 26, 42], [29, 20,
43], [39, 29, 43], [20, 26, 43], [26, 28, 43], [21, 13, 44], [13, 36, 44], [36, 21, 44],
[21, 11, 45], [11, 35, 45], [13, 21, 45], [35, 13, 45], [28, 39, 47], [39, 43, 47], [43, 28, 47]]));
}
test_hull3d_faces();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1,192 +0,0 @@
include <../std.scad>
include <../hull.scad>
function standard_faces(faces) =
sort([for(face=faces)
list_rotate(face, min_index(face))]);
module test_hull() {
assert_equal(hull([[3,4],[5,5]]), [0,1]);
assert_equal(hull([[3,4,1],[5,5,3]]), [0,1]);
test_collinear_2d = let(u = unit([5,3])) [ for(i = [9,2,3,4,5,7,12,15,13]) i * u ];
assert_equal(sort(hull(test_collinear_2d)), [1,7]);
test_collinear_3d = let(u = unit([5,3,2])) [ for(i = [9,2,3,4,5,7,12,15,13]) i * u ];
assert_equal(sort(hull(test_collinear_3d)), [1,7]);
/* // produces some extra points along edges
test_square_2d = [for(x=[1:5], y=[2:6]) [x,y]];
echo(test_square_2d);
move_copies(test_square_2d) circle(r=.1,$fn=16);
color("red")move_copies(select(test_square_2d,hull(test_square_2d))) circle(r=.1,$fn=16);
*/
/* // also produces extra points along edges
test_square_2d = rot(22,p=[for(x=[1:5], y=[2:6]) [x,y]]);
echo(test_square_2d);
move_copies(test_square_2d) circle(r=.1,$fn=16);
color("red")move_copies(select(test_square_2d,hull(test_square_2d))) circle(r=.1,$fn=16);
*/
rand10_2d = [[1.55356, -1.98965], [4.23157, -0.947788], [-4.06193, -1.55463],
[1.23889, -3.73133], [-1.02637, -4.0155], [4.26806, -4.61909],
[3.59556, -3.1574], [-2.77776, -4.21857], [-3.66253,-4.34458], [1.82324, 0.102025]];
assert_equal(sort(hull(rand10_2d)), [1,2,5,8,9]);
rand75_2d = [[-3.14743, -3.28139], [0.15343, -0.370249], [0.082565, 3.95939], [-2.56925, -3.16262], [-1.59463, 4.20893],
[-4.90744, -1.21374], [-1.0819, -1.93703], [-3.72723, -3.0744], [-3.34339, 1.53535], [3.15803, -0.307388], [4.23289,
4.46259], [1.73624, 1.38918], [3.72087, -1.55028], [1.2604, 2.30502], [-0.966431, 1.673], [-3.26866, -0.531443], [1.52605,
0.991804], [-1.26305, 1.0737], [-4.31943, 4.11932], [0.488101, 0.0425981], [1.0233, -0.723037], [-4.73406, 2.14568],
[-4.75915, 3.83262], [4.90999, -2.76668], [1.91971, -3.8604], [4.38594, -0.761767], [-0.352984, 1.55291], [2.02714,
-0.340099], [1.76052, 2.09196], [-1.27485, -4.39477], [4.36364, 3.84964], [0.593612, -4.00028], [3.06833, -3.67117],
[4.26834, -4.21213], [4.60226, -0.120432], [-2.45646, 2.60327], [-4.79461, 3.83724], [-3.29755, 0.760159], [0.218423,
4.1687], [-0.115829, -2.06242], [-3.96188, 3.21568], [4.3018, -2.5299], [-4.41694, 4.75173], [-3.8393, 2.82212], [-1.14268,
1.80751], [2.05805, 1.68593], [-3.0159, -2.91139], [-1.44828, -1.93564], [-0.265887, 0.519893], [-0.457361, -0.610096],
[-0.426359, -2.37315], [-3.1018, 2.31141], [0.179141, -3.56242], [-0.491786, 0.813055], [-3.28502, -1.18933], [0.0914813,
2.16122], [4.5777, 4.83972], [-1.07096, 2.74992], [-0.698689, 3.9032], [-1.21809, -1.54434], [3.14457, 4.92302], [-4.63176,
2.81952], [4.84414, 4.63699], [2.4259, -0.747268], [-1.52088, -4.58305], [1.6961, -3.73678], [-0.483003, -3.67283],
[-3.72746, -0.284265], [2.07629, 1.99902], [-3.12698, -0.96353], [4.02254, 3.41521], [-0.963391, -3.2143], [0.315255,
0.593049], [1.57006, 1.80436], [4.60957, -2.86325]];
assert_equal(sort(hull(rand75_2d)),[5,7,23,33,36,42,56,60,62,64]);
rand10_2d_rot = rot([22,44,12], p=path3d(rand10_2d));
assert_equal(sort(hull(rand10_2d_rot)), [1,2,5,8,9]);
rand75_2d_rot = rot([122,-44,32], p=path3d(rand75_2d));
assert_equal(sort(hull(rand75_2d_rot)), [5,7,23,33,36,42,56,60,62,64]);
testpoints_on_sphere = [ for(p =
[
[1,PHI,0], [-1,PHI,0], [1,-PHI,0], [-1,-PHI,0],
[0,1,PHI], [0,-1,PHI], [0,1,-PHI], [0,-1,-PHI],
[PHI,0,1], [-PHI,0,1], [PHI,0,-1], [-PHI,0,-1]
])
unit(p)
];
assert_equal(standard_faces(hull(testpoints_on_sphere)),
standard_faces([[8, 4, 0], [0, 4, 1], [4, 8, 5], [8, 2, 5], [2, 3, 5], [0, 1, 6], [3, 2, 7], [1, 4, 9], [4, 5, 9],
[5, 3, 9], [8, 0, 10], [2, 8, 10], [0, 6, 10], [6, 7, 10], [7, 2, 10], [6, 1, 11], [3, 7, 11], [7, 6, 11], [1, 9, 11], [9, 3, 11]]));
rand10_3d = [[14.0893, -15.2751, 21.0843], [-14.1564, 17.5751, 3.32094], [17.4966, 12.1717, 18.0607], [24.5489, 9.64591, 10.4738], [-12.0233, -24.4368, 13.1614],
[6.24019, -18.4135, 24.9554], [11.9438, -15.9724, -22.6454], [11.6147, 7.56059, 7.5667], [-19.7491, 9.42769, 15.3419], [-10.3726, 16.3559, 3.38503]];
assert_equal(standard_faces(hull(rand10_3d)),
standard_faces([[3, 6, 0], [1, 3, 2], [3, 0, 2], [6, 1, 4], [0, 6, 5], [6, 4, 5], [2, 0, 5], [1, 2, 8], [2, 5, 8], [4, 1, 8], [5, 4, 8], [6, 3, 9], [3, 1, 9], [1, 6, 9]]));
rand25_3d = [[-20.5261, 14.5058, -11.6349], [16.4625, 20.1316, 12.9816], [-14.0268, 5.58802, 17.686], [-5.47944, 16.2501,
5.3086], [20.2168, -11.8466, 12.4598], [14.4633, -15.1479, 4.82151], [12.7897, 5.25704, 19.6205], [11.2456,
18.2794, -3.47074], [-1.87665, 22.9852, 1.99367], [-15.6052, -2.11009, 14.0096], [-10.7389, -14.569,
5.6121], [24.5965, 17.9039, 20.8313], [-13.7054, 13.3362, 1.50374], [10.1111, -23.1494, 19.9305], [14.154,
19.6682, -0.170182], [-22.6438, 22.7429, -0.776773], [-9.75056, 17.8896, -8.04152], [23.1746, 20.5475,
22.6957], [-10.5356, -4.32407, -7.0911], [2.20779, -8.30749, 6.87185], [23.2643, 2.64462, -19.0087],
[24.4055, 24.4504, 23.4777], [-3.84086, -6.98473, -10.2889], [0.178043, -16.07, 16.8081], [-8.86482,
-12.8256, 14.7418], [11.1759, -11.5614, -11.643], [7.16751, 13.9344, -19.1675], [2.26602, -10.5374,
0.125718], [-13.9053, 11.1143, -21.9289], [24.9018, -23.5307, -21.4684], [-13.6609, -19.6495, -8.91583],
[-16.5393, -22.4105, -6.91617], [-4.11378, -3.14362, -5.6881], [7.50883, -17.5284, -0.0615319], [-7.41739,
0.0721313, -7.47111], [22.6975, -7.99655, 14.0555], [-13.3644, 9.26993, 20.858], [-13.6889, 16.7462,
-14.5836], [16.5137, 3.90703, -5.49396], [-6.75614, -11.1444, -24.5309], [22.9868, 10.0028, 12.2866],
[-4.81079, -0.967785, -10.4726], [-0.949023, 23.1441, -2.08208], [16.1256, -8.2295, -24.0113], [6.45274,
-7.21416, 23.1409], [22.8274, 1.07038, 19.1756], [-10.6256, -10.0112, -6.12274], [6.29254, -7.81875,
-24.4037], [22.8538, 8.78163, -6.82567], [-1.96142, 19.1728, -1.726]];
assert_equal(sort(hull(rand25_3d)),sort([[21, 29, 11], [29, 21, 20], [21, 14, 20], [20, 14, 26], [15, 0, 28], [13, 29, 31], [0, 15,
31], [15, 9, 31], [9, 24, 31], [24, 13, 31], [28, 0, 31], [11, 29, 35], [29, 13, 35], [15,
21, 36], [9, 15, 36], [24, 9, 36], [13, 24, 36], [15, 28, 37], [28, 26, 37], [28, 31, 39],
[31, 29, 39], [14, 21, 42], [21, 15, 42], [26, 14, 42], [15, 37, 42], [37, 26, 42], [29, 20,
43], [39, 29, 43], [20, 26, 43], [26, 28, 43], [21, 13, 44], [13, 36, 44], [36, 21, 44],
[21, 11, 45], [11, 35, 45], [13, 21, 45], [35, 13, 45], [28, 39, 47], [39, 43, 47], [43, 28, 47]]));
/* // Inconsistently treats coplanar faces: sometimes face center vertex is included in output, sometimes not
test_cube_3d = [for(x=[1:3], y=[1:3], z=[1:3]) [x,y,z]];
assert_equal(hull(test_cube_3d), [[3, 2, 0], [2, 3, 4], [26, 2, 5], [2, 4, 5], [4, 3, 6], [5, 4, 6], [5, 6, 7], [6, 26, 7], [26, 5, 8],
[5, 7, 8], [7, 26, 8], [0, 2, 9], [3, 0, 9], [6, 3, 9], [9, 2, 10], [2, 26, 11], [10, 2, 11], [6, 9, 12],
[26, 6, 15], [6, 12, 15], [9, 10, 18], [10, 11, 18], [12, 9, 18], [15, 12, 18], [26, 18, 19], [18, 11, 19],
[11, 26, 20], [26, 19, 20], [19, 11, 20], [15, 18, 21], [18, 26, 21], [26, 15, 24], [15, 21, 24], [21, 26, 24]]);
echo(len=len(hull(test_cube_3d)));
*/
}
test_hull();
module test_hull2d_path() {
assert_equal(hull([[3,4],[5,5]]), [0,1]);
assert_equal(hull([[3,4,1],[5,5,3]]), [0,1]);
test_collinear_2d = let(u = unit([5,3])) [ for(i = [9,2,3,4,5,7,12,15,13]) i * u ];
assert_equal(sort(hull(test_collinear_2d)), [1,7]);
test_collinear_3d = let(u = unit([5,3,2])) [ for(i = [9,2,3,4,5,7,12,15,13]) i * u ];
assert_equal(sort(hull(test_collinear_3d)), [1,7]);
rand10_2d = [[1.55356, -1.98965], [4.23157, -0.947788], [-4.06193, -1.55463],
[1.23889, -3.73133], [-1.02637, -4.0155], [4.26806, -4.61909],
[3.59556, -3.1574], [-2.77776, -4.21857], [-3.66253,-4.34458], [1.82324, 0.102025]];
assert_equal(sort(hull(rand10_2d)), [1,2,5,8,9]);
rand75_2d = [[-3.14743, -3.28139], [0.15343, -0.370249], [0.082565, 3.95939], [-2.56925, -3.16262], [-1.59463, 4.20893],
[-4.90744, -1.21374], [-1.0819, -1.93703], [-3.72723, -3.0744], [-3.34339, 1.53535], [3.15803, -0.307388], [4.23289,
4.46259], [1.73624, 1.38918], [3.72087, -1.55028], [1.2604, 2.30502], [-0.966431, 1.673], [-3.26866, -0.531443], [1.52605,
0.991804], [-1.26305, 1.0737], [-4.31943, 4.11932], [0.488101, 0.0425981], [1.0233, -0.723037], [-4.73406, 2.14568],
[-4.75915, 3.83262], [4.90999, -2.76668], [1.91971, -3.8604], [4.38594, -0.761767], [-0.352984, 1.55291], [2.02714,
-0.340099], [1.76052, 2.09196], [-1.27485, -4.39477], [4.36364, 3.84964], [0.593612, -4.00028], [3.06833, -3.67117],
[4.26834, -4.21213], [4.60226, -0.120432], [-2.45646, 2.60327], [-4.79461, 3.83724], [-3.29755, 0.760159], [0.218423,
4.1687], [-0.115829, -2.06242], [-3.96188, 3.21568], [4.3018, -2.5299], [-4.41694, 4.75173], [-3.8393, 2.82212], [-1.14268,
1.80751], [2.05805, 1.68593], [-3.0159, -2.91139], [-1.44828, -1.93564], [-0.265887, 0.519893], [-0.457361, -0.610096],
[-0.426359, -2.37315], [-3.1018, 2.31141], [0.179141, -3.56242], [-0.491786, 0.813055], [-3.28502, -1.18933], [0.0914813,
2.16122], [4.5777, 4.83972], [-1.07096, 2.74992], [-0.698689, 3.9032], [-1.21809, -1.54434], [3.14457, 4.92302], [-4.63176,
2.81952], [4.84414, 4.63699], [2.4259, -0.747268], [-1.52088, -4.58305], [1.6961, -3.73678], [-0.483003, -3.67283],
[-3.72746, -0.284265], [2.07629, 1.99902], [-3.12698, -0.96353], [4.02254, 3.41521], [-0.963391, -3.2143], [0.315255,
0.593049], [1.57006, 1.80436], [4.60957, -2.86325]];
assert_equal(sort(hull(rand75_2d)),[5,7,23,33,36,42,56,60,62,64]);
rand10_2d_rot = rot([22,44,12], p=path3d(rand10_2d));
assert_equal(sort(hull(rand10_2d_rot)), [1,2,5,8,9]);
rand75_2d_rot = rot([122,-44,32], p=path3d(rand75_2d));
assert_equal(sort(hull(rand75_2d_rot)), [5,7,23,33,36,42,56,60,62,64]);
}
test_hull2d_path();
module test_hull3d_faces() {
testpoints_on_sphere = [ for(p =
[
[1,PHI,0], [-1,PHI,0], [1,-PHI,0], [-1,-PHI,0],
[0,1,PHI], [0,-1,PHI], [0,1,-PHI], [0,-1,-PHI],
[PHI,0,1], [-PHI,0,1], [PHI,0,-1], [-PHI,0,-1]
])
unit(p)
];
assert_equal(standard_faces(hull(testpoints_on_sphere)),
standard_faces([[8, 4, 0], [0, 4, 1], [4, 8, 5], [8, 2, 5], [2, 3, 5], [0, 1, 6], [3, 2, 7], [1, 4, 9], [4, 5, 9],
[5, 3, 9], [8, 0, 10], [2, 8, 10], [0, 6, 10], [6, 7, 10], [7, 2, 10], [6, 1, 11], [3, 7, 11], [7, 6, 11], [1, 9, 11], [9, 3, 11]]));
rand10_3d = [[14.0893, -15.2751, 21.0843], [-14.1564, 17.5751, 3.32094], [17.4966, 12.1717, 18.0607], [24.5489, 9.64591, 10.4738], [-12.0233, -24.4368, 13.1614],
[6.24019, -18.4135, 24.9554], [11.9438, -15.9724, -22.6454], [11.6147, 7.56059, 7.5667], [-19.7491, 9.42769, 15.3419], [-10.3726, 16.3559, 3.38503]];
assert_equal(standard_faces(hull(rand10_3d)),
standard_faces([[3, 6, 0], [1, 3, 2], [3, 0, 2], [6, 1, 4], [0, 6, 5], [6, 4, 5], [2, 0, 5], [1, 2, 8], [2, 5, 8], [4, 1, 8], [5, 4, 8], [6, 3, 9], [3, 1, 9], [1, 6, 9]]));
rand25_3d = [[-20.5261, 14.5058, -11.6349], [16.4625, 20.1316, 12.9816], [-14.0268, 5.58802, 17.686], [-5.47944, 16.2501,
5.3086], [20.2168, -11.8466, 12.4598], [14.4633, -15.1479, 4.82151], [12.7897, 5.25704, 19.6205], [11.2456,
18.2794, -3.47074], [-1.87665, 22.9852, 1.99367], [-15.6052, -2.11009, 14.0096], [-10.7389, -14.569,
5.6121], [24.5965, 17.9039, 20.8313], [-13.7054, 13.3362, 1.50374], [10.1111, -23.1494, 19.9305], [14.154,
19.6682, -0.170182], [-22.6438, 22.7429, -0.776773], [-9.75056, 17.8896, -8.04152], [23.1746, 20.5475,
22.6957], [-10.5356, -4.32407, -7.0911], [2.20779, -8.30749, 6.87185], [23.2643, 2.64462, -19.0087],
[24.4055, 24.4504, 23.4777], [-3.84086, -6.98473, -10.2889], [0.178043, -16.07, 16.8081], [-8.86482,
-12.8256, 14.7418], [11.1759, -11.5614, -11.643], [7.16751, 13.9344, -19.1675], [2.26602, -10.5374,
0.125718], [-13.9053, 11.1143, -21.9289], [24.9018, -23.5307, -21.4684], [-13.6609, -19.6495, -8.91583],
[-16.5393, -22.4105, -6.91617], [-4.11378, -3.14362, -5.6881], [7.50883, -17.5284, -0.0615319], [-7.41739,
0.0721313, -7.47111], [22.6975, -7.99655, 14.0555], [-13.3644, 9.26993, 20.858], [-13.6889, 16.7462,
-14.5836], [16.5137, 3.90703, -5.49396], [-6.75614, -11.1444, -24.5309], [22.9868, 10.0028, 12.2866],
[-4.81079, -0.967785, -10.4726], [-0.949023, 23.1441, -2.08208], [16.1256, -8.2295, -24.0113], [6.45274,
-7.21416, 23.1409], [22.8274, 1.07038, 19.1756], [-10.6256, -10.0112, -6.12274], [6.29254, -7.81875,
-24.4037], [22.8538, 8.78163, -6.82567], [-1.96142, 19.1728, -1.726]];
assert_equal(sort(hull(rand25_3d)), sort([[21, 29, 11], [29, 21, 20], [21, 14, 20], [20, 14, 26], [15, 0, 28], [13, 29, 31], [0, 15,
31], [15, 9, 31], [9, 24, 31], [24, 13, 31], [28, 0, 31], [11, 29, 35], [29, 13, 35], [15,
21, 36], [9, 15, 36], [24, 9, 36], [13, 24, 36], [15, 28, 37], [28, 26, 37], [28, 31, 39],
[31, 29, 39], [14, 21, 42], [21, 15, 42], [26, 14, 42], [15, 37, 42], [37, 26, 42], [29, 20,
43], [39, 29, 43], [20, 26, 43], [26, 28, 43], [21, 13, 44], [13, 36, 44], [36, 21, 44],
[21, 11, 45], [11, 35, 45], [13, 21, 45], [35, 13, 45], [28, 39, 47], [39, 43, 47], [43, 28, 47]]));
}
test_hull3d_faces();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1,5 +1,4 @@
include <../std.scad>
include <../hull.scad>
module test_cube() {