Skip to content
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

Bezier solids? #1321

Closed
iloving opened this issue Dec 1, 2023 · 26 comments · Fixed by #1458
Closed

Bezier solids? #1321

iloving opened this issue Dec 1, 2023 · 26 comments · Fixed by #1458
Assignees

Comments

@iloving
Copy link

iloving commented Dec 1, 2023

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.

image
@adrianVmariano
Copy link
Collaborator

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.

@iloving
Copy link
Author

iloving commented Dec 1, 2023

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.

@adrianVmariano
Copy link
Collaborator

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.

@iloving
Copy link
Author

iloving commented Dec 1, 2023

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?

@adrianVmariano
Copy link
Collaborator

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.

@iloving
Copy link
Author

iloving commented Dec 1, 2023

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.

@adrianVmariano
Copy link
Collaborator

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.

@iloving
Copy link
Author

iloving commented Dec 1, 2023

🤔 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.

@revarbat
Copy link
Collaborator

revarbat commented Dec 2, 2023

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.

@revarbat
Copy link
Collaborator

revarbat commented Dec 2, 2023

There is merit to having a vnf_loft() function to do all that, though.

@adrianVmariano
Copy link
Collaborator

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?)

@revarbat
Copy link
Collaborator

revarbat commented Dec 2, 2023

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.

@adrianVmariano
Copy link
Collaborator

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.

@revarbat
Copy link
Collaborator

revarbat commented Dec 2, 2023

surfpts= is indeed a 2D array of 3D points, just like you would feed to vnf_vertex_array().

@revarbat
Copy link
Collaborator

revarbat commented Dec 2, 2023

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.

@adrianVmariano
Copy link
Collaborator

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.

@revarbat
Copy link
Collaborator

revarbat commented Dec 2, 2023

That sounds like an argument for bezier_patch_offset() as well as vnf_offset(). 😄

@adrianVmariano
Copy link
Collaborator

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?

@revarbat
Copy link
Collaborator

revarbat commented Dec 2, 2023

Err, the fact that I had already had to write that function for a non-bezier case, suggests it'd be useful as well.

@revarbat
Copy link
Collaborator

revarbat commented Dec 2, 2023

I was working with 3D surface grids generated by computing combinations of 2D function outputs.

@revarbat
Copy link
Collaborator

revarbat commented Dec 8, 2023

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];
Screenshot 2023-12-07 at 8 43 34 PM

@adrianVmariano
Copy link
Collaborator

A bezier version is simpler to implement, I think, since there's a function already available that computes exact normals.

@adrianVmariano
Copy link
Collaborator

Not sure if this is a good idea...but we could have an offset= arg for creating bezier surfaces.

@revarbat
Copy link
Collaborator

That seems rather elegant, actually.

@iloving
Copy link
Author

iloving commented Dec 16, 2023

Maybe not call it "offset" since this is supposed to be more of an extrusion operation and offset implies something else. loft=?

@adrianVmariano adrianVmariano linked a pull request Jul 26, 2024 that will close this issue
@adrianVmariano
Copy link
Collaborator

bezier_sheet and vnf_sheet address this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants