Added convex_hull()

This commit is contained in:
Revar Desmera
2019-04-16 15:34:54 -07:00
parent fde93d9991
commit 96739c3ea0
5 changed files with 556 additions and 25 deletions

264
math.scad
View File

@@ -44,6 +44,8 @@ include <compat.scad>
PHI = (1+sqrt(5))/2; // The golden ratio phi.
EPSILON = 1e-9; // A really small value useful in comparing FP numbers. ie: abs(a-b)<EPSILON
// Function: Cpi()
@@ -94,6 +96,36 @@ function quantup(x,y) = ceil(x/y)*y;
function constrain(v, minval, maxval) = min(maxval, max(minval, v));
// Function: min_index()
// Usage:
// min_index(vals);
// Description:
// Returns the index of the minimal value in the given list.
function min_index(vals, _minval, _minidx, _i=0) =
_i>=len(vals)? _minidx :
min_index(
vals,
((_minval == undef || vals[_i] < _minval)? vals[_i] : _minval),
((_minval == undef || vals[_i] < _minval)? _i : _minidx),
_i+1
);
// Function: max_index()
// Usage:
// max_index(vals);
// Description:
// Returns the index of the maximum value in the given list.
function max_index(vals, _maxval, _maxidx, _i=0) =
_i>=len(vals)? _maxidx :
max_index(
vals,
((_maxval == undef || vals[_i] > _maxval)? vals[_i] : _maxval),
((_maxval == undef || vals[_i] > _maxval)? _i : _maxidx),
_i+1
);
// Function: posmod()
// Usage:
// posmod(x,m)
@@ -788,6 +820,20 @@ function unique(arr) =
// Function: list_remove()
// Usage:
// list_remove(list, elements)
// Description:
// Remove all items from `list` whose indexes are in `elements`.
// Arguments:
// list = The list to remove items from.
// elements = The list of indexes of items to remove.
function list_remove(list, elements) = [
for (i = [0:len(list)-1]) if (!search(i, elements)) list[i]
];
// Internal. Not exposed.
function _array_dim_recurse(v) =
!is_list(v[0])? (
@@ -1114,6 +1160,42 @@ function xy_to_polar(x,y=undef) = let(
) [norm([xx,yy]), atan2(yy,xx)];
// Function: xyz_to_planar()
// Usage:
// xyz_to_planar(point, a, b, c);
// Description:
// Given three points defining a plane, returns the projected planar
// [X,Y] coordinates of the closest point to a 3D `point`. The origin
// of the planar coordinate system [0,0] will be at point `a`, and the
// Y+ axis direction will be towards point `b`. This coordinate system
// can be useful in taking a set of nearly coplanar points, and converting
// them to a pure XY set of coordinates for manipulation, before convering
// them back to the original 3D plane.
function xyz_to_planar(point, a, b, c) = let(
u = normalize(b-a),
v = normalize(c-a),
n = normalize(cross(u,v)),
w = normalize(cross(n,u)),
relpoint = point-a
) [relpoint * w, relpoint * u];
// Function: planar_to_xyz()
// Usage:
// planar_to_xyz(point, a, b, c);
// Description:
// Given three points defining a plane, converts a planar [X,Y]
// coordinate to the actual corresponding 3D point on the plane.
// The origin of the planar coordinate system [0,0] will be at point
// `a`, and the Y+ axis direction will be towards point `b`.
function planar_to_xyz(point, a, b, c) = let(
u = normalize(b-a),
v = normalize(c-a),
n = normalize(cross(u,v)),
w = normalize(cross(n,u))
) a + point.x * w + point.y * u;
// Function: cylindrical_to_xyz()
// Usage:
// cylindrical_to_xyz(r, theta, z)
@@ -1618,4 +1700,186 @@ function pointlist_bounds(pts) = [
];
// Function: triangle_area2d()
// Usage:
// triangle_area2d(a,b,c);
// Description:
// Returns the area of a triangle formed between three vertices.
// Result will be negative if the points are in clockwise order.
// Examples:
// triangle_area2d([0,0], [5,10], [10,0]); // Returns -50
// triangle_area2d([10,0], [5,10], [0,0]); // Returns 50
function triangle_area2d(a,b,c) =
(
a.x * (b.y - c.y) +
b.x * (c.y - a.y) +
c.x * (a.y - b.y)
) / 2;
// Function: right_of_line2d()
// Usage:
// right_of_line2d(line, pt)
// Description:
// Returns true if the given point is to the left of the given line.
// Arguments:
// line = A list of two points.
// pt = The point to test.
function right_of_line2d(line, pt) =
triangle_area2d(line[0], line[1], pt) < 0;
// Function: collinear()
// Usage:
// collinear(a, b, c, [eps]);
// Description:
// Returns true if three points are co-linear.
// Arguments:
// a = First point.
// b = Second point.
// c = Third point.
// eps = Acceptable max angle variance. Default: EPSILON (1e-9) degrees.
function collinear(a, b, c, eps=EPSILON) =
abs(vector_angle(b-a,c-a)) < eps;
// Function: collinear_indexed()
// Usage:
// collinear_indexed(points, a, b, c, [eps]);
// Description:
// Returns true if three points are co-linear.
// Arguments:
// points = A list of points.
// a = Index in `points` of first point.
// b = Index in `points` of second point.
// c = Index in `points` of third point.
// eps = Acceptable max angle variance. Default: EPSILON (1e-9) degrees.
function collinear_indexed(points, a, b, c, eps=EPSILON) =
let(
p1=points[a],
p2=points[b],
p3=points[c]
) abs(vector_angle(p2-p1,p3-p1)) < eps;
// Function: plane3pt()
// Usage:
// plane3pt(p1, p2, p3);
// Description:
// Generates the cartesian equation of a plane from three non-colinear points on the plane.
// Returns [A,B,C,D] where Ax+By+Cz+D=0 is the equation of a plane.
// Arguments:
// p1 = The first point on the plane.
// p2 = The second point on the plane.
// p3 = The third point on the plane.
function plane3pt(p1, p2, p3) =
let(normal = normalize(cross(p3-p1, p2-p1))) concat(normal, [normal*p1]);
// Function: plane3pt_indexed()
// Usage:
// plane3pt_indexed(points, i1, i2, i3);
// Description:
// Given a list of points, and the indexes of three of those points,
// generates the cartesian equation of a plane that those points all
// lie on. Requires that the three indexed points be non-collinear.
// Returns [A,B,C,D] where Ax+By+Cz+D=0 is the equation of a plane.
// Arguments:
// points = A list of points.
// i1 = The index into `points` of the first point on the plane.
// i2 = The index into `points` of the second point on the plane.
// i3 = The index into `points` of the third point on the plane.
function plane3pt_indexed(points, i1, i2, i3) =
let(
p1 = points[i1],
p2 = points[i2],
p3 = points[i3],
normal = normalize(cross(p3-p1, p2-p1))
) concat(normal, [normal*p1]);
// Function: distance_from_plane()
// Usage:
// distance_from_plane(plane, point)
// Description:
// Given a plane as [A,B,C,D] where the cartesian equation for that plane
// is Ax+By+Cz+D=0, determines how far from that plane the given point is.
// The returned distance will be positive if the point is in front of the
// plane; on the same side of the plane as the normal of that plane points
// towards. If the point is behind the plane, then the distance returned
// will be negative. The normal of the plane is the same as [A,B,C].
// Arguments:
// plane = The [A,B,C,D] values for the equation of the plane.
// point = The point to test.
function distance_from_plane(plane, point) =
[plane.x, plane.y, plane.z] * point - plane[3];
// Function: coplanar()
// Usage:
// coplanar(plane, point);
// Description:
// Given a plane as [A,B,C,D] where the cartesian equation for that plane
// is Ax+By+Cz+D=0, determines if the given point is on that plane.
// Returns true if the point is on that plane.
// Arguments:
// plane = The [A,B,C,D] values for the equation of the plane.
// point = The point to test.
function coplanar(plane, point) =
abs(distance_from_plane(plane, point)) <= EPSILON;
// Function: in_front_of_plane()
// Usage:
// in_front_of_plane(plane, point);
// Description:
// Given a plane as [A,B,C,D] where the cartesian equation for that plane
// is Ax+By+Cz+D=0, determines if the given point is on the side of that
// plane that the normal points towards. The normal of the plane is the
// same as [A,B,C].
// Arguments:
// plane = The [A,B,C,D] values for the equation of the plane.
// point = The point to test.
function in_front_of_plane(plane, point) =
distance_from_plane(plane, point) > EPSILON;
// Function: simplify_path()
// Description:
// Takes a path and removes unnecessary collinear points.
// Usage:
// simplify_path(path, [eps])
// Arguments:
// path = A list of 2D path points.
// eps = Largest angle variance allowed. Default: EPSILON (1-e9) degrees.
function simplify_path(path, eps=EPSILON, _a=0, _b=2, _acc=[]) =
(_b >= len(path))? concat([path[0]], _acc, [path[len(path)-1]]) :
simplify_path(
path, eps,
(collinear_indexed(path, _a, _b-1, _b, eps=eps)? _a : _b-1),
_b+1,
(collinear_indexed(path, _a, _b-1, _b, eps=eps)? _acc : concat(_acc, [path[_b-1]]))
);
// Function: simplify_path_indexed()
// Description:
// Takes a list of points, and a path as a list of indexes into `points`,
// and removes all path points that are unecessarily collinear.
// Usage:
// simplify_path_indexed(path, eps)
// Arguments:
// points = A list of points.
// path = A list of indexes into `points` that forms a path.
// eps = Largest angle variance allowed. Default: EPSILON (1-e9) degrees.
function simplify_path_indexed(points, path, eps=EPSILON, _a=0, _b=2, _acc=[]) =
(_b >= len(path))? concat([path[0]], _acc, [path[len(path)-1]]) :
simplify_path_indexed(
points, path, eps,
(collinear_indexed(points, path[_a], path[_b-1], path[_b], eps=eps)? _a : _b-1),
_b+1,
(collinear_indexed(points, path[_a], path[_b-1], path[_b], eps=eps)? _acc : concat(_acc, [path[_b-1]]))
);
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap