mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-01-16 13:50:23 +01:00
doc tweaks for skin(), faster 2d hull()
This commit is contained in:
parent
5ce8ba4728
commit
863c410404
85
hull.scad
85
hull.scad
@ -74,6 +74,15 @@ module hull_points(points, fast=false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _backtracking(i,points,h,t,m) =
|
||||||
|
m<t || _is_cw(points[i], points[h[m-1]], points[h[m-2]]) ? m :
|
||||||
|
_backtracking(i,points,h,t,m-1) ;
|
||||||
|
|
||||||
|
// clockwise check (2d)
|
||||||
|
function _is_cw(a,b,c) = cross(a-c,b-c)<=0;
|
||||||
|
|
||||||
|
|
||||||
// Function: hull2d_path()
|
// Function: hull2d_path()
|
||||||
// Usage:
|
// Usage:
|
||||||
// hull2d_path(points)
|
// hull2d_path(points)
|
||||||
@ -85,34 +94,42 @@ module hull_points(points, fast=false) {
|
|||||||
// path = hull2d_path(pts);
|
// path = hull2d_path(pts);
|
||||||
// move_copies(pts) color("red") sphere(1);
|
// move_copies(pts) color("red") sphere(1);
|
||||||
// polygon(points=pts, paths=[path]);
|
// 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) =
|
function hull2d_path(points) =
|
||||||
assert(is_path(points,2),"Invalid input to hull2d_path")
|
assert(is_path(points,2),"Invalid input to hull2d_path")
|
||||||
len(points) < 2 ? []
|
len(points) < 2 ? []
|
||||||
: len(points) == 2 ? [0,1]
|
: len(points) == 2 ? [0,1]
|
||||||
: let(tri=noncollinear_triple(points, error=false))
|
: let(tri=noncollinear_triple(points, error=false))
|
||||||
tri == [] ? _hull_collinear(points)
|
tri == [] ? _hull_collinear(points)
|
||||||
: let(
|
:
|
||||||
remaining = [ for (i = [0:1:len(points)-1]) if (i != tri[0] && i!=tri[1] && i!=tri[2]) i ],
|
assert(is_path(points,2))
|
||||||
ccw = triangle_area(points[tri[0]], points[tri[1]], points[tri[2]]) > 0,
|
assert(len(points)>=3, "Point list must contain at least 3 points.")
|
||||||
polygon = ccw ? [tri[0],tri[1],tri[2]] : [tri[0],tri[2],tri[1]]
|
let( n = len(points),
|
||||||
) _hull2d_iterative(points, polygon, remaining);
|
ip = sortidx(points) )
|
||||||
|
// lower hull points
|
||||||
|
let( lh =
|
||||||
|
[ for( i = 2,
|
||||||
// Adds the remaining points one by one to the convex hull
|
k = 2,
|
||||||
function _hull2d_iterative(points, polygon, remaining, _i=0) =
|
h = [ip[0],ip[1]]; // current list of hull point indices
|
||||||
(_i >= len(remaining))? polygon : let (
|
i <= n;
|
||||||
// pick a point
|
k = i<n ? _backtracking(ip[i],points,h,2,k)+1 : k,
|
||||||
i = remaining[_i],
|
h = i<n ? [for(j=[0:1:k-2]) h[j], ip[i]] : [],
|
||||||
// find the segments that are in conflict with the point (point not inside)
|
i = i+1
|
||||||
conflicts = _find_conflicting_segments(points, polygon, points[i])
|
) if( i==n ) h ][0] )
|
||||||
// no conflicts, skip point and move on
|
// concat lower hull points with upper hull ones
|
||||||
) (len(conflicts) == 0)? _hull2d_iterative(points, polygon, remaining, _i+1) : let(
|
[ for( i = n-2,
|
||||||
// find the first conflicting segment and the first not conflicting
|
k = len(lh),
|
||||||
// conflict will be sorted, if not wrapping around, do it the easy way
|
t = k+1,
|
||||||
polygon = _remove_conflicts_and_insert_point(polygon, conflicts, i)
|
h = lh; // current list of hull point indices
|
||||||
) _hull2d_iterative(points, polygon, remaining, _i+1);
|
i >= -1;
|
||||||
|
k = i>=0 ? _backtracking(ip[i],points,h,t,k)+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) =
|
function _hull_collinear(points) =
|
||||||
let(
|
let(
|
||||||
@ -124,30 +141,6 @@ function _hull_collinear(points) =
|
|||||||
) [min_i, max_i];
|
) [min_i, max_i];
|
||||||
|
|
||||||
|
|
||||||
function _find_conflicting_segments(points, polygon, point) = [
|
|
||||||
for (i = [0:1:len(polygon)-1]) let(
|
|
||||||
j = (i+1) % len(polygon),
|
|
||||||
p1 = points[polygon[i]],
|
|
||||||
p2 = points[polygon[j]],
|
|
||||||
area = triangle_area(p1, p2, point)
|
|
||||||
) if (area < 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:1:len(polygon)-1]) if (!in_list(i, conflicts)) i ],
|
|
||||||
new_indices = concat(nonconflicting, (nonconflicting[len(nonconflicting)-1]+1) % len(polygon)),
|
|
||||||
polygon = concat([ for (i = new_indices) polygon[i] ], point)
|
|
||||||
) polygon : let(
|
|
||||||
before_conflicts = [ for(i = [0:1:min(conflicts)]) polygon[i] ],
|
|
||||||
after_conflicts = (max(conflicts) >= (len(polygon)-1))? [] : [ for(i = [max(conflicts)+1:1:len(polygon)-1]) polygon[i] ],
|
|
||||||
polygon = concat(before_conflicts, point, after_conflicts)
|
|
||||||
) polygon;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Function: hull3d_faces()
|
// Function: hull3d_faces()
|
||||||
// Usage:
|
// Usage:
|
||||||
// hull3d_faces(points)
|
// hull3d_faces(points)
|
||||||
|
54
skin.scad
54
skin.scad
@ -47,31 +47,29 @@
|
|||||||
// profiles that you specify. It is generally best if the triangles forming your polyhedron
|
// profiles that you specify. It is generally best if the triangles forming your polyhedron
|
||||||
// are approximately equilateral. The `slices` parameter specifies the number of slices to insert
|
// are approximately equilateral. The `slices` parameter specifies the number of slices to insert
|
||||||
// between each pair of profiles, either a scalar to insert the same number everywhere, or a vector
|
// between each pair of profiles, either a scalar to insert the same number everywhere, or a vector
|
||||||
// to insert a different number between each pair. To resample the profiles you can use set
|
// to insert a different number between each pair.
|
||||||
// `refine=N` which will place `N` points on each edge of your profile. This has the effect of
|
// .
|
||||||
// multiplying the number of points by N, so a profile with 8 points will have 8*N points after
|
// Resampling may occur, depending on the `method` parameter, to make profiles compatible.
|
||||||
// refinement. Note that when dealing with continuous curves it is always better to adjust the
|
// To force (possibly additional) resampling of the profiles to increase the point density you can set `refine=N`, which
|
||||||
|
// will multiply the number of points on your profile by `N`. You can choose between two resampling
|
||||||
|
// schemes using the `sampling` option, which you can set to `"length"` or `"segment"`.
|
||||||
|
// The length resampling method resamples proportional to length.
|
||||||
|
// The segment method divides each segment of a profile into the same number of points.
|
||||||
|
// This means that if you refine a profile with the "segment" method you will get N points
|
||||||
|
// on each edge, but if you refine a profile with the "length" method you will get new points
|
||||||
|
// distributed around the profile based on length, so small segments will get fewer new points than longer ones.
|
||||||
|
// A uniform division may be impossible, in which case the code computes an approximation, which may result
|
||||||
|
// in arbitrary distribution of extra points. See `subdivide_path` for more details.
|
||||||
|
// Note that when dealing with continuous curves it is always better to adjust the
|
||||||
// sampling in your code to generate the desired sampling rather than using the `refine` argument.
|
// sampling in your code to generate the desired sampling rather than using the `refine` argument.
|
||||||
// .
|
// .
|
||||||
// Two methods are available for resampling, `"length"` and `"segment"`. Specify them using
|
|
||||||
// the `sampling` argument. The length resampling method resamples proportional to length.
|
|
||||||
// The segment method divides each segment of a profile into the same number of points.
|
|
||||||
// A uniform division may be impossible, in which case the code computes an approximation.
|
|
||||||
// See `subdivide_path` for more details.
|
|
||||||
//
|
|
||||||
// You can choose from four methods for specifying alignment for incommensurate profiles.
|
// You can choose from four methods for specifying alignment for incommensurate profiles.
|
||||||
// The available methods are `"distance"`, `"tangent"`, `"direct"` and `"reindex"`.
|
// The available methods are `"distance"`, `"tangent"`, `"direct"` and `"reindex"`.
|
||||||
// It is useful to distinguish between continuous curves like a circle and discrete profiles
|
// It is useful to distinguish between continuous curves like a circle and discrete profiles
|
||||||
// like a hexagon or star, because the algorithms' suitability depend on this distinction.
|
// like a hexagon or star, because the algorithms' suitability depend on this distinction.
|
||||||
// .
|
// .
|
||||||
// The "direct" and "reindex" methods work by resampling the profiles if necessary. As noted above,
|
// The default method for aligning profiles is `method="direct"`.
|
||||||
// for continuous input curves, it is better to generate your curves directly at the desired sample size,
|
// If you simply supply a list of compatible profiles it will link them up
|
||||||
// but for mapping between a discrete profile like a hexagon and a circle, the hexagon must be resampled
|
|
||||||
// to match the circle. You can do this in two different ways using the `sampling` parameter. The default
|
|
||||||
// of `sampling="length"` approximates a uniform length sampling of the profile. The other option
|
|
||||||
// is `sampling="segment"` which attempts to place the same number of new points on each segment.
|
|
||||||
// If the segments are of varying length, this will produce a different result. Note that "direct" is
|
|
||||||
// the default method. If you simply supply a list of compatible profiles it will link them up
|
|
||||||
// exactly as you have provided them. You may find that profiles you want to connect define the
|
// exactly as you have provided them. You may find that profiles you want to connect define the
|
||||||
// right shapes but the point lists don't start from points that you want aligned in your skinned
|
// right shapes but the point lists don't start from points that you want aligned in your skinned
|
||||||
// polyhedron. You can correct this yourself using `reindex_polygon`, or you can use the "reindex"
|
// polyhedron. You can correct this yourself using `reindex_polygon`, or you can use the "reindex"
|
||||||
@ -79,12 +77,25 @@
|
|||||||
// in the polyhedron---in will produce the least twisted possible result. This algorithm has quadratic
|
// in the polyhedron---in will produce the least twisted possible result. This algorithm has quadratic
|
||||||
// run time so it can be slow with very large profiles.
|
// run time so it can be slow with very large profiles.
|
||||||
// .
|
// .
|
||||||
|
// When the profiles are incommensurate, the "direct" and "reindex" resampling them to match. As noted above,
|
||||||
|
// for continuous input curves, it is better to generate your curves directly at the desired sample size,
|
||||||
|
// but for mapping between a discrete profile like a hexagon and a circle, the hexagon must be resampled
|
||||||
|
// to match the circle. When you use "direct" or "reindex" the default `sampling` value is
|
||||||
|
// of `sampling="length"` to approximate a uniform length sampling of the profile. This will generally
|
||||||
|
// produce the natural result for connecting two continuously sampled profiles or a continuous
|
||||||
|
// profile and a polygonal one. However depending on your particular case,
|
||||||
|
// `sampling="segment"` may produce a more pleasing result. These two approaches differ only when
|
||||||
|
// the segments of your input profiles have unequal length.
|
||||||
|
// .
|
||||||
// The "distance" and "tangent" methods work by duplicating vertices to create
|
// The "distance" and "tangent" methods work by duplicating vertices to create
|
||||||
// triangular faces. The "distance" method finds the global minimum distance method for connecting two
|
// triangular faces. The "distance" method finds the global minimum distance method for connecting two
|
||||||
// profiles. This algorithm generally produces a good result when both profiles are discrete ones with
|
// profiles. This algorithm generally produces a good result when both profiles are discrete ones with
|
||||||
// a small number of vertices. It is computationally intensive (O(N^3)) and may be
|
// a small number of vertices. It is computationally intensive (O(N^3)) and may be
|
||||||
// slow on large inputs. The resulting surfaces generally have curved faces, so be
|
// slow on large inputs. The resulting surfaces generally have curved faces, so be
|
||||||
// sure to select a sufficiently large value for `slices` and `refine`.
|
// sure to select a sufficiently large value for `slices` and `refine`. Note that for
|
||||||
|
// this method, `sampling` must be set to `"segment"`, and hence this is the default setting.
|
||||||
|
// Using sampling by length would ignore the repeated vertices and ruin the alignment.
|
||||||
|
// .
|
||||||
// The `"tangent"` method generally produces good results when
|
// The `"tangent"` method generally produces good results when
|
||||||
// connecting a discrete polygon to a convex, finely sampled curve. It works by finding
|
// connecting a discrete polygon to a convex, finely sampled curve. It works by finding
|
||||||
// a plane that passed through each edge of the polygon that is tangent to
|
// a plane that passed through each edge of the polygon that is tangent to
|
||||||
@ -92,9 +103,8 @@
|
|||||||
// all of the tangent points from each other. It connects all of the points of the curve to the corners of the discrete
|
// all of the tangent points from each other. It connects all of the points of the curve to the corners of the discrete
|
||||||
// polygon using triangular faces. Using `refine` with this method will have little effect on the model, so
|
// polygon using triangular faces. Using `refine` with this method will have little effect on the model, so
|
||||||
// you should do it only for agreement with other profiles, and these models are linear, so extra slices also
|
// you should do it only for agreement with other profiles, and these models are linear, so extra slices also
|
||||||
// have no effect. For best efficiency set `refine=1` and `slices=0`. When you use refinement with either
|
// have no effect. For best efficiency set `refine=1` and `slices=0`. As with the "distance" method, refinement
|
||||||
// of these methods, it is always the "segment" based resampling described above. This is necessary because
|
// must be done using the "segment" sampling scheme to preserve alignment across duplicated points.
|
||||||
// sampling by length will ignore the repeated vertices and break the alignment.
|
|
||||||
// .
|
// .
|
||||||
// It is possible to specify `method` and `refine` as arrays, but it is important to observe
|
// It is possible to specify `method` and `refine` as arrays, but it is important to observe
|
||||||
// matching rules when you do this. If a pair of profiles is connected using "tangent" or "distance"
|
// matching rules when you do this. If a pair of profiles is connected using "tangent" or "distance"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user