diff --git a/math.scad b/math.scad index 720d0e6..bf676e4 100644 --- a/math.scad +++ b/math.scad @@ -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); diff --git a/skin.scad b/skin.scad index 92536aa..79cd244 100644 --- a/skin.scad +++ b/skin.scad @@ -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. diff --git a/tests/test_math.scad b/tests/test_math.scad index ae9801d..103be26 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -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)); diff --git a/tests/test_vectors.scad b/tests/test_vectors.scad index ce8dea0..b23ccb0 100644 --- a/tests/test_vectors.scad +++ b/tests/test_vectors.scad @@ -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); diff --git a/vectors.scad b/vectors.scad index cd45624..8a6ca60 100644 --- a/vectors.scad +++ b/vectors.scad @@ -36,7 +36,8 @@ // 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_of(v,1) // is_list(v) && is_num(v[0]) && is_num(0*(v*v)) + && len(v)>0 && (is_undef(length) || len(v)==length) && (is_undef(zero) || ((norm(v) >= eps) == !zero)) && (!all_nonzero || all_nonzero(v)) ;