mirror of
https://github.com/rsheldiii/KeyV2.git
synced 2024-11-23 05:34:23 +00:00
325 lines
9.1 KiB
OpenSCAD
325 lines
9.1 KiB
OpenSCAD
|
|
// NOTE: this code uses
|
|
// * experimental let() syntax
|
|
// * experimental list comprehension syntax
|
|
// * search() bugfix and feature addition
|
|
// * vector min()/max()
|
|
|
|
// Calculates the convex hull of a set of points.
|
|
// The result is expressed in point indices.
|
|
// If the points are collinear (or 2d), the result is a convex
|
|
// polygon [i1,i2,i3,...], otherwise a triangular
|
|
// polyhedron [[i1,i2,i3],[i2,i3,i4],...]
|
|
|
|
function hull(points) =
|
|
!(len(points) > 0) ? [] :
|
|
len(points[0]) == 2 ? convexhull2d(points) :
|
|
len(points[0]) == 3 ? convexhull3d(points) : [];
|
|
|
|
epsilon = 1e-9;
|
|
|
|
// 2d version
|
|
function convexhull2d(points) =
|
|
len(points) < 3 ? [] : let(
|
|
a=0, b=1,
|
|
|
|
c = find_first_noncollinear([a,b], points, 2)
|
|
|
|
) c == len(points) ? convexhull_collinear(points) : let(
|
|
|
|
remaining = [ for (i = [2:len(points)-1]) if (i != c) i ],
|
|
|
|
polygon = area_2d(points[a], points[b], points[c]) > 0 ? [a,b,c] : [b,a,c]
|
|
|
|
) convex_hull_iterative_2d(points, polygon, remaining);
|
|
|
|
|
|
// Adds the remaining points one by one to the convex hull
|
|
function convex_hull_iterative_2d(points, polygon, remaining, i_=0) = i_ >= len(remaining) ? polygon :
|
|
let (
|
|
// pick a point
|
|
i = remaining[i_],
|
|
|
|
// find the segments that are in conflict with the point (point not inside)
|
|
conflicts = find_conflicting_segments(points, polygon, points[i])
|
|
|
|
// no conflicts, skip point and move on
|
|
) len(conflicts) == 0 ? convex_hull_iterative_2d(points, polygon, remaining, i_+1) : let(
|
|
|
|
// find the first conflicting segment and the first not conflicting
|
|
// conflict will be sorted, if not wrapping around, do it the easy way
|
|
polygon = remove_conflicts_and_insert_point(polygon, conflicts, i)
|
|
) convex_hull_iterative_2d(
|
|
points,
|
|
polygon,
|
|
remaining,
|
|
i_+1
|
|
);
|
|
|
|
function find_conflicting_segments(points, polygon, point) = [
|
|
for (i = [0:len(polygon)-1]) let(j = (i+1) % len(polygon))
|
|
if (area_2d(points[polygon[i]], points[polygon[j]], point) < 0)
|
|
i
|
|
];
|
|
|
|
// remove the conflicting segments from the polygon
|
|
function remove_conflicts_and_insert_point(polygon, conflicts, point) =
|
|
conflicts[0] == 0 ? let(
|
|
nonconflicting = [ for(i = [0:len(polygon)-1]) if (!contains(conflicts, i)) i ],
|
|
new_indices = concat(nonconflicting, (nonconflicting[len(nonconflicting)-1]+1) % len(polygon)),
|
|
polygon = concat([ for (i = new_indices) polygon[i] ], point)
|
|
) polygon : let(
|
|
prior_to_first_conflict = [ for(i = [0:1:min(conflicts)]) polygon[i] ],
|
|
after_last_conflict = [ for(i = [max(conflicts)+1:1:len(polygon)-1]) polygon[i] ],
|
|
polygon = concat(prior_to_first_conflict, point, after_last_conflict)
|
|
) polygon;
|
|
|
|
|
|
// 3d version
|
|
function convexhull3d(points) =
|
|
len(points) < 3 ? [ for(i = [0:1:len(points)-1]) i ] : let (
|
|
|
|
// start with a single triangle
|
|
a=0, b=1, c=2,
|
|
plane = plane(points,a,b,c),
|
|
|
|
d = find_first_noncoplanar(plane, points, 3)
|
|
|
|
) d == len(points) ? /* all coplanar*/ let (
|
|
|
|
pts2d = [ for (p = points) plane_project(p, points[a], points[b], points[c]) ],
|
|
hull2d = convexhull2d(pts2d)
|
|
|
|
) hull2d : let(
|
|
|
|
remaining = [for (i = [3:len(points)-1]) if (i != d) i],
|
|
|
|
// Build an initial tetrahedron
|
|
|
|
// swap b,c if d is in front of triangle t
|
|
bc = in_front(plane, points[d]) ? [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) plane(points, t[0], t[1], t[2]) ]
|
|
|
|
) convex_hull_iterative(points, triangles, planes, remaining);
|
|
|
|
// A plane equation (normal, offset)
|
|
function plane(points, a, b, c) = let(
|
|
normal = unit(cross(points[c]-points[a], points[b]-points[a]))
|
|
) [
|
|
normal,
|
|
normal * points[a]
|
|
];
|
|
|
|
// Adds the remaining points one by one to the convex hull
|
|
function convex_hull_iterative(points, triangles, planes, remaining, i_=0) = i_ >= len(remaining) ? triangles :
|
|
let (
|
|
// pick a point
|
|
i = remaining[i_],
|
|
|
|
// find the triangles that are in conflict with the point (point not inside)
|
|
conflicts = find_conflicts(points[i], planes),
|
|
|
|
// for all triangles that are in conflict, collect their halfedges
|
|
halfedges = [
|
|
for(c = conflicts)
|
|
for(i = [0:2]) let(j = (i+1)%3)
|
|
[triangles[c][i], triangles[c][j]]
|
|
],
|
|
|
|
// find the outer perimeter of the set of conflicting triangles
|
|
horizon = remove_internal_edges(halfedges),
|
|
|
|
// generate a new triangle for each horizon halfedge together with the picked point i
|
|
new_triangles = [ for (h = horizon) concat(h,i) ],
|
|
|
|
// calculate the corresponding plane equations
|
|
new_planes = [ for (t = new_triangles) plane(points, t[0], t[1], t[2]) ]
|
|
|
|
) convex_hull_iterative(
|
|
points,
|
|
// remove the conflicting triangles and add the new ones
|
|
concat(remove_elements(triangles, conflicts), new_triangles),
|
|
concat(remove_elements(planes, conflicts), new_planes),
|
|
remaining,
|
|
i_+1
|
|
);
|
|
|
|
function convexhull_collinear(points) = let(
|
|
n = points[1] - points[0],
|
|
a = points[0],
|
|
points1d = [ for(p = points) (p-a)*n ],
|
|
min_i = min_index(points1d),
|
|
max_i = max_index(points1d)
|
|
) [ min_i, max_i ];
|
|
|
|
function min_index(values,min_,min_i_,i_) =
|
|
i_ == undef ? min_index(values,values[0],0,1) :
|
|
i_ >= len(values) ? min_i_ :
|
|
values[i_] < min_ ? min_index(values,values[i_],i_,i_+1)
|
|
: min_index(values,min_,min_i_,i_+1);
|
|
|
|
function max_index(values,max_,max_i_,i_) =
|
|
i_ == undef ? max_index(values,values[0],0,1) :
|
|
i_ >= len(values) ? max_i_ :
|
|
values[i_] > max_ ? max_index(values,values[i_],i_,i_+1)
|
|
: max_index(values,max_,max_i_,i_+1);
|
|
|
|
function remove_elements(array, elements) = [
|
|
for (i = [0:len(array)-1])
|
|
if (!search(i, elements))
|
|
array[i]
|
|
];
|
|
|
|
function remove_internal_edges(halfedges) = [
|
|
for (h = halfedges)
|
|
if (!contains(halfedges, reverse(h)))
|
|
h
|
|
];
|
|
|
|
function plane_project(point, a, b, c) = let(
|
|
u = b-a,
|
|
v = c-a,
|
|
n = cross(u,v),
|
|
w = cross(n,u),
|
|
relpoint = point-a
|
|
) [relpoint * u, relpoint * w];
|
|
|
|
function plane_unproject(point, a, b, c) = let(
|
|
u = b-a,
|
|
v = c-a,
|
|
n = cross(u,v),
|
|
w = cross(n,u)
|
|
) a + point[0] * u + point[1] * w;
|
|
|
|
function reverse(arr) = [ for (i = [len(arr)-1:-1:0]) arr[i] ];
|
|
|
|
function contains(arr, element) = search([element],arr)[0] != [] ? true : false;
|
|
|
|
function find_conflicts(point, planes) = [
|
|
for (i = [0:len(planes)-1])
|
|
if (in_front(planes[i], point))
|
|
i
|
|
];
|
|
|
|
function find_first_noncollinear(line, points, i) =
|
|
i >= len(points) ? len(points) :
|
|
collinear(points[line[0]],
|
|
points[line[1]],
|
|
points[i]) ? find_first_noncollinear(line, points, i+1)
|
|
: i;
|
|
|
|
function find_first_noncoplanar(plane, points, i) =
|
|
i >= len(points) ? len(points) :
|
|
coplanar(plane, points[i]) ? find_first_noncoplanar(plane, points, i+1)
|
|
: i;
|
|
|
|
function distance(plane, point) = plane[0] * point - plane[1];
|
|
|
|
function in_front(plane, point) = distance(plane, point) > epsilon;
|
|
|
|
function coplanar(plane, point) = abs(distance(plane,point)) <= epsilon;
|
|
|
|
function unit(v) = v/norm(v);
|
|
|
|
function area_2d(a,b,c) = (
|
|
a[0] * (b[1] - c[1]) +
|
|
b[0] * (c[1] - a[1]) +
|
|
c[0] * (a[1] - b[1])) / 2;
|
|
|
|
function collinear(a,b,c) = abs(area_2d(a,b,c)) < epsilon;
|
|
|
|
function spherical(cartesian) = [
|
|
atan2(cartesian[1], cartesian[0]),
|
|
asin(cartesian[2])
|
|
];
|
|
|
|
function cartesian(spherical) = [
|
|
cos(spherical[1]) * cos(spherical[0]),
|
|
cos(spherical[1]) * sin(spherical[0]),
|
|
sin(spherical[1])
|
|
];
|
|
|
|
|
|
/// TESTCODE
|
|
|
|
|
|
phi = 1.618033988749895;
|
|
|
|
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)
|
|
];
|
|
|
|
testpoints_spherical = [ for(p = testpoints_on_sphere) spherical(p) ];
|
|
testpoints_circular = [ for(a = [0:15:360-epsilon]) [cos(a),sin(a)] ];
|
|
|
|
testpoints_coplanar = let(u = unit([1,3,7]), v = unit([-2,1,-2])) [ for(i = [1:10]) rands(-1,1,1)[0] * u + rands(-1,1,1)[0] * v ];
|
|
|
|
testpoints_collinear_2d = let(u = unit([5,3])) [ for(i = [1:20]) rands(-1,1,1)[0] * u ];
|
|
testpoints_collinear_3d = let(u = unit([5,3,-5])) [ for(i = [1:20]) rands(-1,1,1)[0] * u ];
|
|
|
|
testpoints2d = 20 * [for (i = [1:10]) concat(rands(-1,1,2))];
|
|
testpoints3d = 20 * [for (i = [1:50]) concat(rands(-1,1,3))];
|
|
|
|
// All points are on the sphere, no point should be red
|
|
translate([-50,0]) visualize_hull(20*testpoints_on_sphere);
|
|
|
|
// 2D points
|
|
translate([50,0]) visualize_hull(testpoints2d);
|
|
|
|
// All points on a circle, no point should be red
|
|
translate([0,50]) visualize_hull(20*testpoints_circular);
|
|
|
|
// All points 3d but collinear
|
|
translate([0,-50]) visualize_hull(20*testpoints_coplanar);
|
|
|
|
// Collinear
|
|
translate([50,50]) visualize_hull(20*testpoints_collinear_2d);
|
|
|
|
// Collinear
|
|
translate([-50,50]) visualize_hull(20*testpoints_collinear_3d);
|
|
|
|
// 3D points
|
|
visualize_hull(testpoints3d);
|
|
|
|
|
|
module visualize_hull(points) {
|
|
|
|
hull = hull(points);
|
|
|
|
%if (len(hull) > 0 && len(hull[0]) > 0)
|
|
polyhedron(points=points, faces = hull);
|
|
else
|
|
polyhedron(points=points, faces = [hull]);
|
|
|
|
for (i = [0:len(points)-1]) assign(p = points[i], $fn = 16) {
|
|
translate(p) {
|
|
if (hull_contains_index(hull,i)) {
|
|
color("blue") sphere(1);
|
|
} else {
|
|
color("red") sphere(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
function hull_contains_index(hull, index) =
|
|
search(index,hull,1,0) ||
|
|
search(index,hull,1,1) ||
|
|
search(index,hull,1,2);
|
|
|
|
}
|