BOSL2
BOSL2 copied to clipboard
Bezier solids?
Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] I want to create a complex curved wall that is a bezier surface but 2mm thick, but it is time consuming to meticulously construct all the sides.
Describe the solution you'd like There are many modules and functions that extrude polygons along paths and even bezier curves, but there is nothing for bezier patches
Not sure what the simplest option is... maybe a bezier patch equivalent of path_copies?
Describe alternatives you've considered Constructing each side individually
Example Code
// Code goes here.
Additional context I've attached a screenshot of the kind of object I'm trying to create.
I don't understand what you are trying to do. You want to put together several copies of that bezier patch? You want to make that shape 2mm thick, like a warped sheet? We need more information. What would the ideal (but possibly unrealistic or impractical) capability look like? What would a "do_what_I_need()" function do to help with the above.
The path_copies function puts an object at points on a path. If there were a bezier_patch version it would presumably put an object over some sort of grid on the bezier patch. That doesn't sound related at all to your needs.
You want to make that shape 2mm thick, like a warped sheet? We need more information.
This exactly. Sorry, I'm relatively new to this stuff so I didn't know how best to describe it.
So you want to start with a bezier patch and construct a shape that is uniformly thick? Or would it be good enough to translate the shape by 2mm, which would produce something with thickness that would vary depending on the curves of the original patch. The former thing is hard---that's a 3d offset operation. The second thing is relatively easy, though I can't immediately think of a simple clean way to construct the skinny edge between the two shapes.
The 3d offset operation can probably be done for shapes without too much curvature by using bezier_patch_normals to get normals everywhere and then just offsetting the vertices of the original patch (and reversing it). That doesn't solve the skinny edge problem, though.
Yeah that exactly. I want to construct a shape that is uniformly thick.
Translating & reversing a copy only partially helps because I'll still need to create additional patches to cover the remaining 4 sides, as you said.
That's why I was thinking a stopgap solution would be to somehow convert the patch into a set of points, copy a primitive object to each of those points, and then union it all. Not exactly elegant but it should work?
The task of making the edges is fussy, but really not difficult. The task of making an offset second patch is I think trickier. The way to do it is make the VNF and then make the second VNF by shifting the points individually based on the patch normals.
I can't think of a way that your idea of copying some primitive object to all the points would make any sense. What is needed is to construct the faces, either by figuring out the face list coordinates from the combined VNF of both sides, or by extracting the points from both bezier patch boundaries and then using vnf_vertex_array.
Like I said... not exactly elegant. :) I haven't played around with VNF yet cause I'm not well experienced with the low-level stuff, so it occurred to me that a higher-level solution would be more helpful to people.
Yeah, there's no question that high level stuff is better. I mean, it's easier for everyone. We write the low level stuff so you don't have to. But the question that needs answering is what generic capability does it make sense to add. I could write a "thin sheet" function that would create a thin sheet from a bezier or potentially even a generic vnf. It would fail if the thickness was too large, where "too large" would depend on the curvature of the input. Is this a generically useful capability? I'm uncertain. I could also make a "join" function that would build the edges between the two patches.
It's not always clear what the right generic building blocks are.
🤔 Good question. I can see value in both. For my particular use-case, the "thin sheet" function would make the most sense since since it would directly solve what I was trying to do.
On the other hand, if the join function was able to support combining patches that weren't exactly the same, then that would add additional flexibility.
Off the top of my head, I'd convert the bezier surface into a grid of 3D points, make a copy of that and move()
it to be the bottom, reverse it, concat it to the original set of points, and pass the whole thing to vnf_vertex_array()
with row_wrap and caps.
There is merit to having a vnf_loft()
function to do all that, though.
I already suggested making a translated reversed copy, @revarbat , but that doesn't make a uniformly thick surface. It will vary in thickness depending on the curves of the bezier patch. I also don't understand how your proposed use of vnf_vertex_array would work. It seems like it would create a ton of internal faces everywhere, linking every point to every other point with a face, instead of just creating the boundary faces.
If you make vnf_sweep() then you lose the derivative information that is in the bezier, which seems undesirable.
(Is lofting different than sweeping?)
I used "loft" as a verb because that's what another CAD I've used calls it when you take a surface and move it to make a thicker volume. Anyways, I recently did a thing where I took a 2D grid with heights and lofted it to give it volume. (Though I called it extrude at the time.)
It abuses the endcap and col_wrap features of vnf_vertex_array()
to get all four sides of the extrusion from the two bumpy faces.
function surf_extrude(surfpts, height) =
vnf_vertex_array(
[for (row=surfpts) concat(row, down(height, p=reverse(row)))],
caps=true, col_wrap=true, row_wrap=false
);
A true constant-thickness extrusion/lofting would be equivalent to implementing 3D offset()
with all the error checking and face eliding that that implies.
I'm not following how surf_extrude() works. Is surfpts a 2d array of points rather than just a list of vnf points?
Yes, a robust constant thickness extrusion would be hard/impossible. But a 2mm offset may be possible for many shapes that don't have very close points in the VNF. In other words, the case where no points are deleted by the offset can be handled.
surfpts=
is indeed a 2D array of 3D points, just like you would feed to vnf_vertex_array()
.
Well, you can, for every vertex in the original surface, calculate the normals of all the faces surrounded that vertex, find the average of those normals, and use that vector for moving the vertex by a given distance. If the faces aren't too small, you can get away without error checking.
Or, you know, if it's a bezier patch you can use the bezer_patch_normals() call which will give you exact normals. That's a reason to want a bezier specific implementation.
That sounds like an argument for bezier_patch_offset()
as well as vnf_offset()
. :smile:
Not sure that makes sense, because bezier_patch_offset() would be producing a vnf output. We don't know how to offset a patch into another patch, do we?
Err, the fact that I had already had to write that function for a non-bezier case, suggests it'd be useful as well.
I was working with 3D surface grids generated by computing combinations of 2D function outputs.
Here's a very naive vnf_offset()
with no error checking that offsets each vertex based on the averaged normals of the surrounding faces. Getting from that to an extrusion/lofting function will take more work.
function vnf_offset(vnf, dist) =
let(
verts = vnf[0],
faces = vnf[1],
face_planes = [
for (face = faces)
let(
poly = [for (k = face) verts[k]],
n = unit(polygon_normal(poly)),
opoly = move(n*dist, p=poly)
)
plane_from_polygon(opoly)
],
vert_faces = group_data(
[for (i = idx(faces), vert = faces[i]) vert],
[for (i = idx(faces), vert = faces[i]) i]
),
vert_planes = [
for (i = idx(verts))
unique([ for (j = vert_faces[i]) face_planes[j] ])
],
nuverts = [
for (i = idx(verts))
let(
vert = verts[i],
planes = vert_planes[i],
lp = len(planes)
)
lp==0 ? vert :
let(
norms = [for (plane=planes) plane_normal(plane)],
n = unit(sum(norms))
)
vert + dist*n
]
)
[nuverts, faces];
A bezier version is simpler to implement, I think, since there's a function already available that computes exact normals.
Not sure if this is a good idea...but we could have an offset=
arg for creating bezier surfaces.
That seems rather elegant, actually.
Maybe not call it "offset" since this is supposed to be more of an extrusion operation and offset implies something else. loft=
?
bezier_sheet and vnf_sheet address this