Merge pull request #471 from adrianVmariano/master

This commit is contained in:
Revar Desmera 2021-03-15 02:28:08 -07:00 committed by GitHub
commit af7ac13577
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 75 additions and 60 deletions

View file

@ -185,49 +185,40 @@ function valid_range(x) =
: ( x[1]<0 && x[0]>=x[2] ) );
// Function: is_list_of()
// Usage:
// bool = is_list_of(list, pattern);
// Topics: Type Checking
// See Also: typeof(), is_type(), is_str(), is_def(), is_int(), is_range()
// Description:
// Tests whether the input is a list whose entries are all numeric lists that have the same
// list shape as the pattern.
// Example:
// is_list_of([3,4,5], 0); // Returns true
// is_list_of([3,4,undef], 0); // Returns false
// is_list_of([[3,4],[4,5]], [1,1]); // Returns true
// is_list_of([[3,"a"],[4,true]], [1,undef]); // Returns true
// is_list_of([[3,4], 6, [4,5]], [1,1]); // Returns false
// is_list_of([[1,[3,4]], [4,[5,6]]], [1,[2,3]]); // Returns true
// is_list_of([[1,[3,INF]], [4,[5,6]]], [1,[2,3]]); // Returns false
// is_list_of([], [1,[2,3]]); // Returns true
function is_list_of(list,pattern) =
let(pattern = 0*pattern)
is_list(list) &&
[]==[for(entry=0*list) if (entry != pattern) entry];
// Function: is_consistent()
// Usage:
// bool = is_consistent(list);
// bool = is_consistent(list, <pattern>);
// Topics: Type Checking
// See Also: typeof(), is_type(), is_str(), is_def(), is_int(), is_range(), is_homogeneous()
// Description:
// Tests whether input is a list of entries which all have the same list structure
// and are filled with finite numerical data. It returns `true`for the empty list.
// and are filled with finite numerical data. You can optionally specify a required
// list structure with the pattern argument. It returns `true` for the empty list.
// Arguments:
// list = list to check
// pattern = optional pattern required to match
// Example:
// is_consistent([3,4,5]); // Returns true
// is_consistent([[3,4],[4,5],[6,7]]); // Returns true
// is_consistent([[3,4,5],[3,4]]); // Returns false
// is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]); // Returns true
// is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]); // Returns false
function is_consistent(list) =
/*is_list(list) &&*/ is_list_of(list, _list_pattern(list[0]));
// is_consistent([3,4,5], 0); // Returns true
// is_consistent([3,4,undef], 0); // Returns false
// is_consistent([[3,4],[4,5]], [1,1]); // Returns true
// is_consistent([[3,"a"],[4,true]], [1,undef]); // Returns true
// is_consistent([[3,4], 6, [4,5]], [1,1]); // Returns false
// is_consistent([[1,[3,4]], [4,[5,6]]], [1,[2,3]]); // Returns true
// is_consistent([[1,[3,INF]], [4,[5,6]]], [1,[2,3]]); // Returns false
// is_consistent([], [1,[2,3]]); // Returns true
function is_consistent(list, pattern) =
is_list(list)
&& (len(list)==0
|| (let(pattern = is_undef(pattern) ? _list_pattern(list[0]): _list_pattern(pattern) )
[]==[for(entry=0*list) if (entry != pattern) entry]));
//Internal function
//Creates a list with the same structure of `list` with each of its elements substituted by 0.
//Creates a list with the same structure of `list` with each of its elements replaced by 0.
function _list_pattern(list) =
is_list(list)
? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0]

View file

@ -585,7 +585,7 @@ function lcm(a,b=[]) =
function sum(v, dflt=0) =
v==[]? dflt :
assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
is_vector(v) ? [for(i=[1:len(v)]) 1]*v :
is_vector(v) || is_matrix(v) ? [for(i=[1:len(v)]) 1]*v :
_sum(v,v[0]*0);
function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
@ -990,6 +990,7 @@ function is_matrix(A,m,n,square=false) =
&& is_list(A[0])
&& (( is_undef(n) && len(A[0]) ) || len(A[0])==n)
&& (!square || len(A) == len(A[0]))
&& is_vector(A[0])
&& is_consistent(A);

View file

@ -33,14 +33,18 @@
// For this operation to be well-defined, the profiles must all have the same vertex count and
// we must assume that profiles are aligned so that vertex `i` links to vertex `i` on all polygons.
// Many interesting cases do not comply with this restriction. Two basic methods can handle
// these cases: either add points to edges (resample) so that the profiles are compatible,
// or repeat vertices. Repeating vertices allows two edges to terminate at the same point, creating
// triangular faces. You can adjust non-matching profiles yourself
// these cases: either subdivide edges (insert additional points along edges)
// or duplicate vertcies (insert edges of length 0) so that both polygons have
// the same number of points.
// Duplicating vertices allows two distinct points in one polygon to connect to a single point
// in the other one, creating
// triangular faces. You can adjust non-matching polygons yourself
// either by resampling them using `subdivide_path` or by duplicating vertices using
// `repeat_entries`. It is OK to pass a profile that has the same vertex repeated, such as
// `repeat_entries`. It is OK to pass a polygon that has the same vertex repeated, such as
// a square with 5 points (two of which are identical), so that it can match up to a pentagon.
// Such a combination would create a triangular face at the location of the duplicated vertex.
// Alternatively, `skin` provides methods (described below) for matching up incompatible paths.
// Alternatively, `skin` provides methods (described below) for inserting additional vertices
// automatically to make incompatible paths match.
// .
// In order for skinned surfaces to look good it is usually necessary to use a fine sampling of
// points on all of the profiles, and a large number of extra interpolated slices between the
@ -77,7 +81,7 @@
// 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.
// .
// When the profiles are incommensurate, the "direct" and "reindex" resampling them to match. As noted above,
// When the profiles are incommensurate, the "direct" and "reindex" resample 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
@ -88,23 +92,35 @@
// the segments of your input profiles have unequal length.
// .
// The "distance", "fast_distance" and "tangent" methods work by duplicating vertices to create
// 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
// triangular faces. In the skined object created by two polygons, every vertex of a polygon must
// have an edge that connects to some vertex on the other one. If you connect two squares this can be
// accomplished with four edges, but if you want to connect a square to a pentagon you must add a
// fifth edge for the "extra" vertex on the pentagon. You must now decide which vertex on the square to
// connect the "extra" edge to. How do you decide where to put that fifth edge? The "distance" method answers this
// question by using an optimization: it minimizes the total length of all the edges connecting
// the two polygons. 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
// slow on large inputs. The resulting surfaces generally have curved faces, so be
// 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 "fast_distance" method is similar to "distance", but it makes the assumption that an edge should
// connect the first vertices of the two polygons. This reduces the run time to O(N^2) and makes
// The "fast_distance" method restricts the optimization by assuming that an edge should connect
// vertex 0 of the two polygons. This reduces the run time to O(N^2) and makes
// the method usable on profiles with more points if you take care to index the inputs to match.
// .
// The `"tangent"` method generally produces good results when
// 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
// the curve. It may fail if the curved profile is non-convex, or doesn't have enough points to distinguish
// 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
// connecting a discrete polygon to a convex, finely sampled curve. Given a polygon and a curve, consider one edge
// on the polygon. Find a plane passing through the edge that is tangent to the curve. The endpoints of the edge and
// the point of tangency define a triangular face in the output polyhedron. If you work your way around the polygon
// edges, you can establish a series of triangular faces in this way, with edges linking the polygon to the curve.
// You can then complete the edge assignment by connecting all the edges in between the triangular faces together,
// with many edges meeting at each polygon vertex. The result is an alternation of flat triangular faces with conical
// curves joining them. Another way to think about it is that it splits the points on the curve up into groups and
// connects all the points in one group to the same vertex on the polygon.
// .
// The "tangent" method may fail if the curved profile is non-convex, or doesn't have enough points to distinguish
// all of the tangent points from each other. The algorithm treats whichever input profile has fewer points as the polygon
// and the other one as the curve. 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
// have no effect. For best efficiency set `refine=1` and `slices=0`. As with the "distance" method, refinement
// must be done using the "segment" sampling scheme to preserve alignment across duplicated points.
@ -220,7 +236,7 @@
// interior = regular_ngon(n=len(base), d=60);
// right_half()
// skin([ sub_base, base, base, sub_base, interior], z=[0,2,height, height, 2], slices=0, refine=1, method="reindex");
// Example: Connecting a pentagon and circle with the "tangent" method produces triangular faces.
// Example: Connecting a pentagon and circle with the "tangent" method produces large triangular faces and cone shaped corners.
// skin([pentagon(4), circle($fn=80,r=2)], z=[0,3], slices=10, method="tangent");
// Example: rounding corners of a square. Note that `$fn` makes the number of points constant, and avoiding the `rounding=0` case keeps everything simple. In this case, the connections between profiles are linear, so there is no benefit to setting `slices` bigger than zero.
// shapes = [for(i=[.01:.045:2])zrot(-i*180/2,cp=[-8,0,0],p=xrot(90,p=path3d(regular_ngon(n=4, side=4, rounding=i, $fn=64))))];
@ -905,7 +921,7 @@ function associate_vertices(polygons, split, curpoly=0) =
// sweep(shape, concat(outside,inside));
function sweep(shape, transforms, closed=false, caps) =
assert(is_list_of(transforms, ident(4)), "Input transforms must be a list of numeric 4x4 matrices in sweep")
assert(is_consistent(transforms, ident(4)), "Input transforms must be a list of numeric 4x4 matrices in sweep")
assert(is_path(shape,2) || is_region(shape), "Input shape must be a 2d path or a region.")
let(
caps = is_def(caps) ? caps :

View file

@ -217,16 +217,6 @@ module test_valid_range() {
}
test_valid_range();
module test_is_list_of() {
assert(is_list_of([3,4,5], 0));
assert(!is_list_of([3,4,undef], 0));
assert(is_list_of([[3,4],[4,5]], [1,1]));
assert(!is_list_of([[3,4], 6, [4,5]], [1,1]));
assert(is_list_of([[1,[3,4]], [4,[5,6]]], [1,[2,3]]));
assert(!is_list_of([[1,[3,INF]], [4,[5,6]]], [1,[2,3]]));
}
test_is_list_of();
module test_is_consistent() {
assert(is_consistent([]));
assert(is_consistent([[],[]]));
@ -238,6 +228,13 @@ module test_is_consistent() {
assert(!is_consistent([[3,4,5],[3,4]]));
assert(is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]));
assert(!is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]));
assert(is_consistent([3,4,5], 0));
assert(!is_consistent([3,4,undef], 0));
assert(is_consistent([[3,4],[4,5]], [1,1]));
assert(!is_consistent([[3,4], 6, [4,5]], [1,1]));
assert(is_consistent([[1,[3,4]], [4,[5,6]]], [1,[2,3]]));
assert(!is_consistent([[1,[3,INF]], [4,[5,6]]], [1,[2,3]]));
}
test_is_consistent();

View file

@ -83,6 +83,12 @@ module test_is_matrix() {
assert(!is_matrix([[2,3,4],[5,6,7]],n=5));
assert(!is_matrix([[2,3],[5,6],[8,9]],m=2,n=3));
assert(!is_matrix([[2,3,4],[5,6,7]],m=3,n=2));
assert(!is_matrix([ [2,[3,4]],
[4,[5,6]]]));
assert(!is_matrix([[3,4],[undef,3]]));
assert(!is_matrix([[3,4],[3,"foo"]]));
assert(!is_matrix([[3,4],[3,3,2]]));
assert(!is_matrix([ [3,4],6]));
assert(!is_matrix(undef));
assert(!is_matrix(NAN));
assert(!is_matrix(INF));

View file

@ -11,6 +11,10 @@ module test_is_vector() {
assert(is_vector(1) == false);
assert(is_vector("foo") == false);
assert(is_vector(true) == false);
assert(is_vector([3,4,"foo"]) == false);
assert(is_vector([3,4,[4,5]]) == false);
assert(is_vector([3,4,undef]) == false);
assert(is_vector(["foo","bar"]) == false);
assert(is_vector([0,0,0],zero=true) == true);
assert(is_vector([0,0,0],zero=false) == false);

View file

@ -421,8 +421,8 @@ turtle state: sequence of transformations ("path") so far
function _turtle3d_state_valid(state) =
is_list(state)
&& is_list_of(state[0],ident(4))
&& is_list_of(state[1],ident(4))
&& is_consistent(state[0],ident(4))
&& is_consistent(state[1],ident(4))
&& is_num(state[2])
&& is_num(state[3])
&& is_num(state[4]);

View file

@ -36,7 +36,7 @@
// is_vector([1,1,1],all_nonzero=false); // Returns true
// is_vector([],zero=false); // Returns false
function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) =
is_list(v) && is_num(v[0]) && is_num(0*(v*v))
is_list(v) && len(v)>0 && []==[for(vi=v) if(!is_num(vi)) 0]
&& (is_undef(length) || len(v)==length)
&& (is_undef(zero) || ((norm(v) >= eps) == !zero))
&& (!all_nonzero || all_nonzero(v)) ;