-
Notifications
You must be signed in to change notification settings - Fork 126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Apply textures to surface defined by point grid #1514
Comments
Applying of textures assumes that the surface follows a regular pattern so that the texture can be mapped to the surface. For skin this is not the case. In fact sometimes sections of the surface will vanish completely to a point. The texture algorithm requires full repetition of the square tiles that form the texture. So I don't think we know an algorithm for applying a texture to a skin(), which means that we are unable to provide the desired feature. |
Perhaps I erred in using skin as an analogy. I'm really referring to a regular grid of 3D points, like an array of paths, with each path having the same number of vertices. I was thinking, if we have an arbitrary grid of 3D points (like an array of equal-length paths), there should be a way to apply a vnf texture to each grid square. This is effectively what In the case of texturing, say, a surface constructed from a square transitioning to a circle, the square would be sampled to have the same number of vertices as the circle, and the paths in between would be linear (or even bezier) interpolations from each vertex on the square to a corresponding vertex on the circle. The sweep would be from the square to the circle, interpolating the paths in between, and applying the texture as appropriate. I'll say again, I feel like the underlying machinery to do this already exists in BOSL2. I have a grid of 3D coordinates defining some curved surface. The grid can be open (like made from a Bezier patch) or looped around to be closed. The texture is applied along the grid. |
Yeah, if you assume that the point count matches and there is no degeneracy, then in that case, you could create a texture by replacing every "square" by a texture block. That would potentially make a pretty fine texture. It might make more sense to add that capability to vnf_vertex_array, which doesn't have all the support for resampling and the creation of degenerate vertices. (In other words, skin() extends vnf_vertex_array() in precisely the way that makes the geometry invalid for this texturing application.) I wonder if implementing this could provide the core for addressing #1346 |
I've changed the title of this issue to make more sense. Yes, simply fitting a texture unit into every "square" would potentially make a fine texture, depending on how you set the 3D coordinates in each square. I was thinking it could work like the existing sweep functions that interpolate the textures over a surface. The surface is already defined, it's already in grid form, like an unwrapped sphere. It would be up to the programmer to ensure that the grid contains well-behaved values. In my case, I would be defining a series of same-length polygons that change in elevation, and change shape slightly, at each z step. This would be like a |
I think that putting a texture tile on each grid square is reasonably straight forward. Using a larger span to define a warping operation for the texture is not so easy. In the case of the existing textured sweeps, we have outside information about the geometry, and it's regular. I'm concerned it could be super slow---@revarbat did all the texture coding and did a bunch of optimizations to make it fast. I wonder if lookup() could be used to do this interpolation. |
What if we assume the underlying geometry of an array of paths is also regular, if what is required is that the points are uniformly spaced? Would that help? For example, a square with n equally spaced points transitioning to a circle of n equally spaced points would consist of parallel paths with equally spaced points.
The builtin
|
I think if you require the points to be "equally spaced" it creates demands that are too limiting, ultimately. Yeah, I was thinking that to use lookup you'd break the data apart into components. But now that I think about this a little more carefully, I don't think that would work, because the x components depend on the y components, so they can't be done separately. |
It occurs to me also that the "squares" of the grid need to be triangulated to figure out where to place the texture points. Or at least you somehow need to resolve the ambiguity of 4 non-coplanar points defining a surface so you can place the texture on that surface. |
Well, I wouldn't say it's a requirement, rather a recommendation. The designer should be free to experiment and take risks. And in most of my use cases, my distribution of points around a polyhedron are reasonably uniform in localized areas, if not in total. I wouldn't say the points you get from rotating a path around a circle are equally spaced, and yet things work out.
Why? Triangulation isn't needed for any polyhedron. Try it; OpenSCAD triangulates it for you. All of my polyhedron designs use quads, and they render just fine and there are no STL errors in the output. If all you're doing is aligning the corners of a texture square to the corners of a grid square, wouldn't all that the other texture vertices need is to align each point with an interpolated surface normal of the grid square? (I know that's harder than I'm making it sound.) Interpolated from the corners, of course, for which the normal would be determined by the adjacent squares. BOSL2 already advises for I've used |
If we require equal spacing that limits the application to surfaces with zero Gaussian curvature, which is a very strong limit, and excludes many things people would probably want to do. The situation with triangulation is very confusing to me. Revar claims that he's seen CGAL fail on non-triangular faces, so he has written triangulation into everything in BOSL2. When you do rotate_sweep it triangulates before passing the result to OpenSCAD. But when a surface is coarsely sampled with noncoplanar quads the choice of triangularization can completely change its appearance. If the grid is coarsely sampled, you therefore might need to triangulate it to define what it does in between its grid points. I can imagine somebody wanting to put one texture tile per grid square or even 2x2 texture tiles on one grid square, so the coarse sampling case could be important. If the sampling is fine, presumably a bilinear interpolation would be fine. I think the biggest challenge really in all of this is identifying the correct quadrilateral and the interpolation distances. I guess if you define the texture as mapping to a specific number of grid cells then it becomes straight forward, but I'm not sure if that is a reasonable way to do things. Your code breaks if you add more samples in that case. |
Keep in mind that if we add a capability we want it to be reasonable generic, not a specialized capability tuned to the specific needs of one user. We don't want the next user to come along and say "Well, that texturing VNF feature is nice and all, but it doesn't work on my slightly different case...." So we need to think about what that generic capability would look like, what are the different cases of interest, and can we address all those cases, or clearly exclude cases. BTW, I think you should try not triangulating your texture VNF. I found that triangulated texture VNFs produce more artifacts when they get sliced to wrap around a curve. That's one of those examples of how triangulating seemed to cause problems, but Revar had made all the texture VNFs fully triangulated. I actually went through and untriangulated most of them. And of course, that raises an additional complication I hadn't even thought of, that of possibly slicing the VNF tile to wrap it around the mesh you're trying to texture. It's not clear how you would know where to do that. |
I agree it should be generic. So why not make I had a look at the skins.scad code and quickly got lost. Yes, I agree about triangulating. It shouldn't matter if I use 'vnf_triangulate()' or rely on OpenSCAD to triangulate things automatically, either way I am not deciding on where the triangles are. I do notice differences in my example in the "thrown together" view if I use By the way, a while back I had written a useful function and I just realized it's equivalent to |
The reason not to start simple is that if the goal is to eventually handle the other case, it might turn out that the effort for the simple implementation was completely wasted if it happens that the other one also handles the simple case. Your basket texture looks great when placed onto a cylinder with a lot of points, but not quite as awesome when placed onto a circle with a low point count. If we implemented the one texture per grid square algorithm it wouldn't be ideal for your application. I'm not sure what you were trying to figure out from reading the skin.scad code. The algorithms in there are some of the most complex in BOSL2, especially the algorithm for point matching mismatched polygons in skin(). But that is exactly the thing that will make textures bomb because it will produce triangular vertices. The right approach is to make a texturing version of vnf_vertex_array. That's what the methods in skin.scad actually call to construct their final result. No, actually in various situations it definitely DOES matter if your VNF is triangulated. There's a whole family of surface smoothing algorithms that operate on faces and do different things if the surface is triangulated. It's much more of a pain to compute things about polyhedra on triangulated ones. And when you apply a VNF texture to a curved object, the VNF tile gets sliced and that slicing can introduce funny looking artifacts, which tend to be more of an issue the more faces there are in the VNF---so worse if it's triangulated. I actually thought there was a bug because it was producing such weird looking results when I ran across the behavior. It doesn't appears that your basket suffers from the problem though. I think it's because you have right angles instead of sloping sides on the edges of the "bands" of the weave. I tried it out and didn't notice much effect from triangulating or not in that case. More general version of vnf_tri_array() sounds potentially useful. Does it do the same thing as the existing code on the cases covered by the existing code? |
Yes, I've finished the more general version of My new version is backward-compatible with the existing one, but includes some optional parameters. The function call would look like this, with default values shown:
The additional parameters are The new code isn't any longer than the original. It does rely on a recursive triangulation function I wrote, which walks around the vertices of two paths in irregular order, depending on which of the next vertices result in the smallest possible triangle. I expected this algorithm to produce minor triangulation differences from the original The cone on the left is from the published example, using the original Question about code comments: It seems that any comments starting with |
I'm going to change
|
How about instead add a new example and give a text noting that this example has larger length changes, resulting in the more triangles meeting at points and longer skinnier triangles. You may have noticed that we're not parsimonious with examples. I was pondering the cone and am a little surprised that the triangles reverse in exactly the same places. If you want a few more test cases to play with, run your code on the rounded_prism examples. Most of them are using vnf_tri_array() indirectly. |
I could have an example that does both, showing rows different by 1, 2, or more. I didn't really see the need to retain the examples illustrating the restrictive cases (rows different by 1 or 2 points) because that isn't a restriction anymore. I was also surprised that the cones looked the same. I even overlayed the two wireframes and didn't see a difference. I see documentation for |
This is the demo I came up with to show the effects of wrapping rows and columns and capping. A simple open-ended shape is made from a series of arcs with different number of vertices at different elevations:
Clockwise from upper left:
It is not possible to wrap rows while adding caps. If you do, the row wrapping overrides the caps. Also I had to set It would be nice if the renderer on the wiki could render in the "thrown together" mode so that the purple interior faces are visible. It's harder to see what's going on with colors the same on both sides of a face. |
That would be the "ThrownTogether" meta tag for the Example or Figure. |
Note that it's OpenSCAD that has chosen clockwise as the correct direction for polygons, not BOSL2. Having multiple examples may be useful because users may be looking for a thing that looks like what they want to do, or what they want their result to look like, and a more complex example may demonstrate the power but not look like a simpler case to a user. The case of 1 and 2 offset is more regular looking as well and the contrast where you start getting multiple skinny triangles is notable. Instead of having row wrapping override caps you should assert an error for the incompatible options. Always better to assert an error rather than guessing what the user meant. There are a bunch of rounded_prism examples. The wiki is broken. Look in the rounding.scad code for the examples. This actually gets to a point you asked and I didn't answer. (Don't recall where you asked.) You can put your comments where ever you want. The docsgen system only processes comments that start with the magic header names and it stops as soon as it reaches a blank line. A stray blank line nuked the rounded_prism docs. :( |
Oh, another thing, if you're putting that above example in the wiki, make it four examples. Yeah, some code is repeated, but the example renders in the wiki are small with each one separated they get their own, clear caption. (Just about any time I've done combined pics like that I end up splitting them up.) |
Thank you. Yes, I can make separate examples. I mainly wanted to know if that was an acceptable demo. About assert: I've always felt that software should fall back gracefully rather than exit with an error. If you're going to connect the rows of an array, it makes sense to ignore the caps. However, since asserting errors is the convention here, I'll do that. I grepped "vnf_tri_array" in all the source files in the BOSL2 library, and I found only two instances that call it: |
Well, the problem with "fall back gracefully" is that you have to guess how to fall back, and if you don't guess the user's intent, it can instead result in massive confusion, where the user is like WTF, why didn't it obey the foobar option I gave it and the problem is the user didn't realize or forgot that he gave some other option that was incompatible, and the code decided to ignore foobar when what the user wanted was to enforce foobar and ignore the other thing. Or for your example, obviously if you're going to add caps you need to ignore the instruction to connect rows! Unless you have a compelling case that the "graceful fallback" absolutely must be the right thing to do, you should assert an error so the user knows they gave bogus input and they can fix it to be what they want, and not bumble around in confusion. I have personally been in this very state of confusion with BOSL2 code that didn't assert errors for incompatible options, so I really believe it to be a real concern. (I have gone an added asserts to the code specifically because I spent too long confused by something.) In my opinion, nothing is worse than user confusion, and the supposed convenience of "doing the right thing" on invalid code doesn't match the inconvenience of the potential confusion that comes as a side effect. I think Revar may be less concerned about this issue, as I know he's done things where different options override other options and the first option gets ignored. I don't like that kind of thing and have generally tried to stamp it out. If you ask for something to be rounded and chamfered it should be an ERROR, not the code picks its favorite for you, and you stare in dumfounded puzzlement at a chamfer and try different rounding radii with no effect. So did you subsequently grep for bezier_vnf_degenerate_patch to find all uses of that, like in particular, the ones that are in rounded_prism()? The entire reason that vnf_tri_array() even exists is that without it, sometimes rounded_prism produced geometry that failed CGAL due to points in the patch getting too close together. |
So in thinking about this, your algorithm uses geometric information. Mine does not. So differences will occur even if the point count doesn't change from one row to the next, if the points happen to be spaced unevenly between the rows. So if you make the geometry weird you'll get bigger differences, like if the points in one row are really far away from the next row your code will make a bunch of triangles that share a vertex and mine will make a regular triangulation. I'm not sure which result is better---perhaps incorporating geometric information improves things. It's a kind of pathological case. I don't know if there are any important cases where things are very different. I also wonder if there's any alternative to segment length for the objective function. Hmmm. I also am now wondering if I implemented something like this for skin() and didn't think about broader applicability. In the final example above it's interesting that symmetry is broken for your code. I guess that happens because an equality occurs in the inequality test? |
I finished testing all the examples in rounded_prism(). All worked as expected. One of the examples is given deliberately as an example of failure, but I think it may be possible to make that shape with the new vnf_tri_array().
I am working on an improvment to mitigate this -- basically, if two rows are equal in length it will make two triangles per interval instead of potentially making multiple triangles from one point. This should provide a happy medium: Unequal length rows get triangulated my new way, but equal length rows get triangulated so that every quad still gets two triangles. This would avoid the ambiguity in relationships between the vertices of two same-length polygons where one is rotated in its plane, like that failed rounded prism example.
I remember giving this some thought when I developed the algorithm. At first I considered computing the area of each possible new triangle and picking the smallest one, but then after drawing several examples out I figured out that the shortest next segment accomplishes the same thing.
Yes, I think so. I'm comparing two distances with By the way, this conversation has veered way off the original objective of this ticket. I think it should be closed, and if the requested feature is planned for the future, it would be best to create another ticket for it. When I create a new PR for this replacment of vnf_tri_array() I'm sure there will be more to discuss. |
Yeah, you did kinda change the subject there. It may possibly make more sense for me to delete the posts on the vnf_tri_array to keep the thoughts on the previous topic rather than start a new issue. I'm not sure. Do you think you can write a summary for a new issue that captures everything we both said? I guess you can decide. One observation I can't remember if I made is that if we solved this problem we could instantly solve #1346 by invoking it. The matter of triangulation is more complicated than it seems at first. Could you triangulate equal row length pairs by just calling vnf_vertex_array on the pair? One complication that arises here is do you support all the styles for the equal row pairs, in an effort to make the tri_array code match the regular array code on regular arrays. And this gets to the question: of the 9 (?) supported styles, are they all different on some example? And how should a user decide between them. I have no clue. Issue #521 has been open for a long time asking for the answer. I know somebody claimed at some point than the minimal area division is optimal. Do we even know that the division into quads and subsquent bisection of quads is always best? Maybe your greedy length division is better sometimes, even though it doesn't make quads. (Note: I don't know what "better" means.) Presumably it all becomes not so important in the limit as the grid size goes to zero, but for coarse grids, as I noted before, it can completely change the shape, as you can see from the "diamonds" texture where you can get 3 completely different results depending on the style parameter. |
Problem description
I can use
skin()
to create a skin between any two arbitrary polygon shapes. I can use textures withlinear_sweep()
, 'rotate_sweep()`, etc. But I just cannot figure out how to apply a texture to a skin.I feel like the infrastructure for this feature already exists, and maybe it's possible to do with the existing BOSL2 functionality, but I cannot figure it out.
For example, here's a skin between a rounded square and a circle:
skin()
doesn't take a texture parameter.Solution I'd like
Basically I am looking for a version of
linear_sweep()
that transitions between two polygons while applying a texture.Better still, if
skin()
would accept a texture parameter, then I could apply a texture to any surface that can be represented by a grid of [x,y,z] coordinates.Alternatives considered
I considered using
sweep_attach()
to attach my texture vnf but this gets extremeley unwieldy and unreliable.The text was updated successfully, but these errors were encountered: