mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-01-16 13:50:23 +01:00
Implemented solution for issue #159
This commit is contained in:
parent
7ea3faee72
commit
182688cf02
58
coords.scad
58
coords.scad
@ -143,15 +143,21 @@ function xy_to_polar(x,y=undef) = let(
|
||||
|
||||
|
||||
// Function: project_plane()
|
||||
// Usage:
|
||||
// xy = project_plane(point, a, b, c);
|
||||
// xy = project_plane(point, [A,B,C]];
|
||||
// Usage: With 3 Points
|
||||
// xyz = project_plane(point, a, b, c);
|
||||
// Usage: With Pointlist
|
||||
// xyz = project_plane(point, POINTLIST);
|
||||
// Usage: With Plane Definition [A,B,C,D] Where Ax+By+Cz=D
|
||||
// xyz = project_plane(point, PLANE);
|
||||
// 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.
|
||||
// Converts the given 3D point from global coordinates to the 2D planar coordinates of the closest
|
||||
// point on the plane. 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 converting
|
||||
// them back to the original 3D plane.
|
||||
// Can be called one of three ways:
|
||||
// - Given three points, `a`, `b`, and `c`, the planar coordinate system will have `[0,0]` at point `a`, and the Y+ axis will be towards point `b`.
|
||||
// - Given a list of points, finds three reasonably spaced non-collinear points in the list and uses them as points `a`, `b`, and `c` as above.
|
||||
// - Given a plane definition `[A,B,C,D]` where `Ax+By+Cz=D`, the closest point on that plane to the global origin at `[0,0,0]` will be the planar coordinate origin `[0,0]`.
|
||||
// Arguments:
|
||||
// point = The 3D point, or list of 3D points to project into the plane's 2D coordinate system.
|
||||
// a = A 3D point that the plane passes through. Used to define the plane.
|
||||
@ -164,8 +170,14 @@ function xy_to_polar(x,y=undef) = let(
|
||||
// xy2 = project_plane(pt, [a,b,c]);
|
||||
function project_plane(point, a, b, c) =
|
||||
is_undef(b) && is_undef(c) && is_list(a)? let(
|
||||
indices = find_noncollinear_points(a)
|
||||
) project_plane(point, a[indices[0]], a[indices[1]], a[indices[2]]) :
|
||||
mat = is_vector(a,4)? plane_transform(a) :
|
||||
assert(is_path(a) && len(a)>=3)
|
||||
plane_transform(plane_from_points(a)),
|
||||
pts = is_vector(point)? point2d(apply(mat,point)) :
|
||||
is_path(point)? path2d(apply(mat,point)) :
|
||||
is_region(point)? [for (x=point) path2d(apply(mat,x))] :
|
||||
assert(false, "point must be a 3D point, path, or region.")
|
||||
) pts :
|
||||
assert(is_vector(a))
|
||||
assert(is_vector(b))
|
||||
assert(is_vector(c))
|
||||
@ -180,13 +192,18 @@ function project_plane(point, a, b, c) =
|
||||
|
||||
|
||||
// Function: lift_plane()
|
||||
// Usage:
|
||||
// Usage: With 3 Points
|
||||
// xyz = lift_plane(point, a, b, c);
|
||||
// xyz = lift_plane(point, [A,B,C]);
|
||||
// Usage: With Pointlist
|
||||
// xyz = lift_plane(point, POINTLIST);
|
||||
// Usage: With Plane Definition [A,B,C,D] Where Ax+By+Cz=D
|
||||
// xyz = lift_plane(point, PLANE);
|
||||
// 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`.
|
||||
// Converts the given 2D point from planar coordinates to the global 3D coordinates of the point on the plane.
|
||||
// Can be called one of three ways:
|
||||
// - Given three points, `a`, `b`, and `c`, the planar coordinate system will have `[0,0]` at point `a`, and the Y+ axis will be towards point `b`.
|
||||
// - Given a list of points, finds three non-collinear points in the list and uses them as points `a`, `b`, and `c` as above.
|
||||
// - Given a plane definition `[A,B,C,D]` where `Ax+By+Cz=D`, the closest point on that plane to the global origin at `[0,0,0]` will be the planar coordinate origin `[0,0]`.
|
||||
// Arguments:
|
||||
// point = The 2D point, or list of 2D points in the plane's coordinate system to get the 3D position of.
|
||||
// a = A 3D point that the plane passes through. Used to define the plane.
|
||||
@ -194,8 +211,15 @@ function project_plane(point, a, b, c) =
|
||||
// c = A 3D point that the plane passes through. Used to define the plane.
|
||||
function lift_plane(point, a, b, c) =
|
||||
is_undef(b) && is_undef(c) && is_list(a)? let(
|
||||
indices = find_noncollinear_points(a)
|
||||
) lift_plane(point, a[indices[0]], a[indices[1]], a[indices[2]]) :
|
||||
mat = is_vector(a,4)? plane_transform(a) :
|
||||
assert(is_path(a) && len(a)>=3)
|
||||
plane_transform(plane_from_points(a)),
|
||||
imat = matrix_inverse(mat),
|
||||
pts = is_vector(point)? apply(imat,point3d(point)) :
|
||||
is_path(point)? apply(imat,path3d(point)) :
|
||||
is_region(point)? [for (x=point) apply(imat,path3d(x))] :
|
||||
assert(false, "point must be a 2D point, path, or region.")
|
||||
) pts :
|
||||
assert(is_vector(a))
|
||||
assert(is_vector(b))
|
||||
assert(is_vector(c))
|
||||
|
@ -998,9 +998,13 @@ module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=tr
|
||||
// Example:
|
||||
// mirror_copy(UP+BACK, cp=[0,-5,-5]) rot(from=UP, to=BACK+UP) cylinder(d1=10, d2=0, h=20);
|
||||
// color("blue",0.25) translate([0,-5,-5]) rot(from=UP, to=BACK+UP) cube([15,15,0.01], center=true);
|
||||
module mirror_copy(v=[0,0,1], offset=0, cp=[0,0,0])
|
||||
module mirror_copy(v=[0,0,1], offset=0, cp)
|
||||
{
|
||||
nv = v/norm(v);
|
||||
cp = is_vector(v,4)? plane_normal(v) * v[3] :
|
||||
is_vector(cp)? cp :
|
||||
is_num(cp)? cp*unit(v) :
|
||||
[0,0,0];
|
||||
nv = is_vector(v,4)? plane_normal(v) : unit(v);
|
||||
off = nv*offset;
|
||||
if (cp == [0,0,0]) {
|
||||
translate(off) {
|
||||
|
183
geometry.scad
183
geometry.scad
@ -84,6 +84,23 @@ function collinear_indexed(points, a, b, c, eps=EPSILON) =
|
||||
) collinear(p1, p2, p3, eps);
|
||||
|
||||
|
||||
// Function: points_are_collinear()
|
||||
// Usage:
|
||||
// points_are_collinear(points);
|
||||
// Description:
|
||||
// Given a list of points, returns true if all points in the list are collinear.
|
||||
// Arguments:
|
||||
// points = The list of points to test.
|
||||
// eps = How much variance is allowed in testing that each point is on the same line. Default: `EPSILON` (1e-9)
|
||||
function points_are_collinear(points, eps=EPSILON) =
|
||||
let(
|
||||
a = furthest_point(points[0], points),
|
||||
b = furthest_point(points[a], points),
|
||||
pa = points[a],
|
||||
pb = points[b]
|
||||
) all([for (pt = points) collinear(pa, pb, pt, eps=eps)]);
|
||||
|
||||
|
||||
// Function: distance_from_line()
|
||||
// Usage:
|
||||
// distance_from_line(line, pt);
|
||||
@ -584,38 +601,14 @@ function plane3pt_indexed(points, i1, i2, i3) =
|
||||
) plane3pt(p1,p2,p3);
|
||||
|
||||
|
||||
// Function: plane_intersection()
|
||||
// Usage:
|
||||
// plane_intersection(plane1, plane2, [plane3])
|
||||
// Description:
|
||||
// Compute the point which is the intersection of the three planes, or the line intersection of two planes.
|
||||
// If you give three planes the intersection is returned as a point. If you give two planes the intersection
|
||||
// is returned as a list of two points on the line of intersection. If any of the input planes are parallel
|
||||
// then returns undef.
|
||||
function plane_intersection(plane1,plane2,plane3) =
|
||||
is_def(plane3)? let(
|
||||
matrix = [for(p=[plane1,plane2,plane3]) select(p,0,2)],
|
||||
rhs = [for(p=[plane1,plane2,plane3]) p[3]]
|
||||
) linear_solve(matrix,rhs) :
|
||||
let(
|
||||
normal = cross(plane_normal(plane1), plane_normal(plane2))
|
||||
) approx(norm(normal),0) ? undef :
|
||||
let(
|
||||
matrix = [for(p=[plane1,plane2]) select(p,0,2)],
|
||||
rhs = [for(p=[plane1,plane2]) p[3]],
|
||||
point = linear_solve(matrix,rhs)
|
||||
) is_undef(point)? undef :
|
||||
[point, point+normal];
|
||||
|
||||
|
||||
// Function: plane_from_normal()
|
||||
// Usage:
|
||||
// plane_from_normal(normal, pt)
|
||||
// plane_from_normal(normal, [pt])
|
||||
// Description:
|
||||
// Returns a plane defined by a normal vector and a point.
|
||||
// Example:
|
||||
// plane_from_normal([0,0,1], [2,2,2]); // Returns the xy plane passing through the point (2,2,2)
|
||||
function plane_from_normal(normal, pt) =
|
||||
function plane_from_normal(normal, pt=[0,0,0]) =
|
||||
concat(normal, [normal*pt]);
|
||||
|
||||
|
||||
@ -632,6 +625,12 @@ function plane_from_normal(normal, pt) =
|
||||
// points = The list of points to find the plane of.
|
||||
// fast = If true, don't verify that all points in the list are coplanar. Default: false
|
||||
// eps = How much variance is allowed in testing that each point is on the same plane. Default: `EPSILON` (1e-9)
|
||||
// Example:
|
||||
// xyzpath = rot(45, v=[-0.3,1,0], p=path3d(star(n=6,id=70,d=100), 70));
|
||||
// plane = plane_from_points(xyzpath);
|
||||
// #stroke(xyzpath,closed=true);
|
||||
// cp = centroid(xyzpath);
|
||||
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow();
|
||||
function plane_from_points(points, fast=false, eps=EPSILON) =
|
||||
let(
|
||||
points = deduplicate(points),
|
||||
@ -646,6 +645,39 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
|
||||
) all_coplanar? plane : undef;
|
||||
|
||||
|
||||
// Function: plane_from_polygon()
|
||||
// Usage:
|
||||
// plane_from_polygon(points, [fast], [eps]);
|
||||
// Description:
|
||||
// Given a 3D planar polygon, returns the cartesian equation of a plane.
|
||||
// Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane.
|
||||
// If not all the points in the polygon are coplanar, then `undef` is returned.
|
||||
// If `fast` is true, then a polygon where not all points are coplanar will
|
||||
// result in an invalid plane value, as all coplanar checks are skipped.
|
||||
// Arguments:
|
||||
// poly = The planar 3D polygon to find the plane of.
|
||||
// fast = If true, don't verify that all points in the polygon are coplanar. Default: false
|
||||
// eps = How much variance is allowed in testing that each point is on the same plane. Default: `EPSILON` (1e-9)
|
||||
// Example:
|
||||
// xyzpath = rot(45, v=[0,1,0], p=path3d(star(n=5,step=2,d=100), 70));
|
||||
// plane = plane_from_polygon(xyzpath);
|
||||
// #stroke(xyzpath,closed=true);
|
||||
// cp = centroid(xyzpath);
|
||||
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow();
|
||||
function plane_from_polygon(poly, fast=false, eps=EPSILON) =
|
||||
let(
|
||||
poly = deduplicate(poly),
|
||||
n = polygon_normal(poly),
|
||||
plane = [n.x, n.y, n.z, n*poly[0]]
|
||||
) fast? plane : let(
|
||||
all_coplanar = [
|
||||
for (pt = poly)
|
||||
if (!coplanar(plane,pt,eps=eps)) 1
|
||||
] == []
|
||||
) all_coplanar? plane :
|
||||
undef;
|
||||
|
||||
|
||||
// Function: plane_normal()
|
||||
// Usage:
|
||||
// plane_normal(plane);
|
||||
@ -654,6 +686,48 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
|
||||
function plane_normal(plane) = unit([for (i=[0:2]) plane[i]]);
|
||||
|
||||
|
||||
// Function: plane_offset()
|
||||
// Usage:
|
||||
// d = plane_offset(plane);
|
||||
// Description:
|
||||
// Returns D, or the scalar offset of the plane from the origin. This can be a negative value.
|
||||
// The absolute value of this is the distance of the plane from the origin at its closest approach.
|
||||
function plane_offset(plane) = plane[3];
|
||||
|
||||
|
||||
// Function: plane_transform()
|
||||
// Usage:
|
||||
// mat = plane_transform(plane);
|
||||
// Description:
|
||||
// Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, returns a 3D affine
|
||||
// transformation matrix that will rotate and translate from points on that plane
|
||||
// to points on the XY plane. You can generally then use `path2d()` to drop the
|
||||
// Z coordinates, so you can work with the points in 2D.
|
||||
// Arguments:
|
||||
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
|
||||
// Example:
|
||||
// xyzpath = move([10,20,30], p=yrot(25, p=path3d(circle(d=100))));
|
||||
// plane = plane_from_points(xyzpath);
|
||||
// mat = plane_transform(plane);
|
||||
// xypath = path2d(apply(mat, xyzpath));
|
||||
// #stroke(xyzpath,closed=true);
|
||||
// stroke(xypath,closed=true);
|
||||
function plane_transform(plane) =
|
||||
let(
|
||||
n = plane_normal(plane),
|
||||
cp = n * plane[3]
|
||||
) rot(from=n, to=UP) * move(-cp);
|
||||
|
||||
|
||||
// Function: plane_point_nearest_origin()
|
||||
// Usage:
|
||||
// pt = plane_point_nearest_origin(plane);
|
||||
// Description:
|
||||
// Returns the point on the plane that is closest to the origin.
|
||||
function plane_point_nearest_origin(plane) =
|
||||
plane_normal(plane) * plane[3];
|
||||
|
||||
|
||||
// Function: distance_from_plane()
|
||||
// Usage:
|
||||
// distance_from_plane(plane, point)
|
||||
@ -808,21 +882,28 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
|
||||
res[0];
|
||||
|
||||
|
||||
// Function: points_are_collinear()
|
||||
// Function: plane_intersection()
|
||||
// Usage:
|
||||
// points_are_collinear(points);
|
||||
// plane_intersection(plane1, plane2, [plane3])
|
||||
// Description:
|
||||
// Given a list of points, returns true if all points in the list are collinear.
|
||||
// Arguments:
|
||||
// points = The list of points to test.
|
||||
// eps = How much variance is allowed in testing that each point is on the same line. Default: `EPSILON` (1e-9)
|
||||
function points_are_collinear(points, eps=EPSILON) =
|
||||
// Compute the point which is the intersection of the three planes, or the line intersection of two planes.
|
||||
// If you give three planes the intersection is returned as a point. If you give two planes the intersection
|
||||
// is returned as a list of two points on the line of intersection. If any of the input planes are parallel
|
||||
// then returns undef.
|
||||
function plane_intersection(plane1,plane2,plane3) =
|
||||
is_def(plane3)? let(
|
||||
matrix = [for(p=[plane1,plane2,plane3]) select(p,0,2)],
|
||||
rhs = [for(p=[plane1,plane2,plane3]) p[3]]
|
||||
) linear_solve(matrix,rhs) :
|
||||
let(
|
||||
a = furthest_point(points[0], points),
|
||||
b = furthest_point(points[a], points),
|
||||
pa = points[a],
|
||||
pb = points[b]
|
||||
) all([for (pt = points) collinear(pa, pb, pt, eps=eps)]);
|
||||
normal = cross(plane_normal(plane1), plane_normal(plane2))
|
||||
) approx(norm(normal),0) ? undef :
|
||||
let(
|
||||
matrix = [for(p=[plane1,plane2]) select(p,0,2)],
|
||||
rhs = [for(p=[plane1,plane2]) p[3]],
|
||||
point = linear_solve(matrix,rhs)
|
||||
) is_undef(point)? undef :
|
||||
[point, point+normal];
|
||||
|
||||
|
||||
// Function: coplanar()
|
||||
@ -1289,7 +1370,7 @@ function centroid(poly) =
|
||||
// Usage:
|
||||
// point_in_polygon(point, path, [eps])
|
||||
// Description:
|
||||
// This function tests whether the given point is inside, outside or on the boundary of
|
||||
// This function tests whether the given 2D point is inside, outside or on the boundary of
|
||||
// the specified 2D polygon using the Winding Number method.
|
||||
// The polygon is given as a list of 2D points, not including the repeated end point.
|
||||
// Returns -1 if the point is outside the polyon.
|
||||
@ -1299,7 +1380,7 @@ function centroid(poly) =
|
||||
// But the polygon cannot have holes (it must be simply connected).
|
||||
// Rounding error may give mixed results for points on or near the boundary.
|
||||
// Arguments:
|
||||
// point = The point to check position of.
|
||||
// point = The 2D point to check position of.
|
||||
// path = The list of 2D path points forming the perimeter of the polygon.
|
||||
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
|
||||
function point_in_polygon(point, path, eps=EPSILON) =
|
||||
@ -1334,7 +1415,7 @@ function polygon_is_clockwise(path) =
|
||||
// Usage:
|
||||
// clockwise_polygon(path);
|
||||
// Description:
|
||||
// Given a polygon path, returns the clockwise winding version of that path.
|
||||
// Given a 2D polygon path, returns the clockwise winding version of that path.
|
||||
function clockwise_polygon(path) =
|
||||
polygon_is_clockwise(path)? path : reverse_polygon(path);
|
||||
|
||||
@ -1343,7 +1424,7 @@ function clockwise_polygon(path) =
|
||||
// Usage:
|
||||
// ccw_polygon(path);
|
||||
// Description:
|
||||
// Given a polygon path, returns the counter-clockwise winding version of that path.
|
||||
// Given a 2D polygon path, returns the counter-clockwise winding version of that path.
|
||||
function ccw_polygon(path) =
|
||||
polygon_is_clockwise(path)? reverse_polygon(path) : path;
|
||||
|
||||
@ -1357,5 +1438,23 @@ function reverse_polygon(poly) =
|
||||
let(lp=len(poly)) [for (i=idx(poly)) poly[(lp-i)%lp]];
|
||||
|
||||
|
||||
// Function: polygon_normal()
|
||||
// Usage:
|
||||
// n = polygon_normal(poly);
|
||||
// Description:
|
||||
// Given a 3D planar polygon, returns a unit-length normal vector for the
|
||||
// clockwise orientation of the polygon.
|
||||
// Example:
|
||||
function polygon_normal(poly) =
|
||||
let(
|
||||
poly = path3d(cleanup_path(poly)),
|
||||
p0 = poly[0],
|
||||
n = sum([
|
||||
for (i=[1:1:len(poly)-2])
|
||||
cross(poly[i+1]-p0, poly[i]-p0)
|
||||
])
|
||||
) unit(n);
|
||||
|
||||
|
||||
|
||||
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
||||
|
@ -31,9 +31,13 @@
|
||||
// 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=[0,0,0], s=1000, planar=false)
|
||||
module half_of(v=UP, cp, s=1000, planar=false)
|
||||
{
|
||||
cp = is_num(cp)? cp*unit(v) : cp;
|
||||
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) {
|
||||
|
15
skin.scad
15
skin.scad
@ -474,13 +474,14 @@ function _skin_core(profiles, caps) =
|
||||
|
||||
|
||||
// Function: subdivide_and_slice()
|
||||
// Usage: subdivide_and_slice(profiles, slices, [numpoints], [method], [closed])
|
||||
// Description: Subdivides the input profiles to have length `numpoints` where
|
||||
// `numpoints` must be at least as big as the largest input profile.
|
||||
// By default `numpoints` is set equal to the length of the largest profile.
|
||||
// You can set `numpoints="lcm"` to sample to the least common multiple of
|
||||
// all curves, which will avoid sampling artifacts but may produce a huge output.
|
||||
// After subdivision, profiles are sliced.
|
||||
// Usage:
|
||||
// subdivide_and_slice(profiles, slices, [numpoints], [method], [closed])
|
||||
// Description:
|
||||
// Subdivides the input profiles to have length `numpoints` where `numpoints` must be at least as
|
||||
// big as the largest input profile. By default `numpoints` is set equal to the length of the
|
||||
// largest profile. You can set `numpoints="lcm"` to sample to the least common multiple of all
|
||||
// curves, which will avoid sampling artifacts but may produce a huge output. After subdivision,
|
||||
// profiles are sliced.
|
||||
// Arguments:
|
||||
// profiles = profiles to operate on
|
||||
// slices = number of slices to insert between each pair of profiles. May be a vector
|
||||
|
@ -8,7 +8,7 @@
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
BOSL_VERSION = [2,0,284];
|
||||
BOSL_VERSION = [2,0,285];
|
||||
|
||||
|
||||
// Section: BOSL Library Version Functions
|
||||
|
Loading…
x
Reference in New Issue
Block a user