8000 CalculateNormals and Smooth by elalish · Pull Request #771 · elalish/manifold · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

CalculateNormals and Smooth #771

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

Merged
merged 22 commits into from
Mar 19, 2024
Merged

CalculateNormals and Smooth #771

merged 22 commits into from
Mar 19, 2024

Conversation

elalish
Copy link
Owner
@elalish elalish commented Mar 12, 2024

This is the next iteration on improving our mesh smoothing, now that we have RefineToLength: we now have a Smooth member function that is slightly higher-level than our Manifold::Smooth constructor. Rather than specifying smoothness at particular edges, now you just specify an angle beyond which edges become sharpened. You can also specify a minSmoothness to soften those sharp edges uniformly.

Additionally, I've added CalculateNormals, which works like CalculateCurvature, but makes vert normals for smooth reflections and e.g. glTF export. It uses the same sharp angle specification as the above Smooth and automatically duplicates prop verts as necessary to create sharp edges.

Both of these functions take special care with flat faces of three or more triangles - these are always considered flat and so have all their vert normals match their face normal. This also makes it easy to have proper curvature applied when e.g. a circular fillet meets a flat face, in which case traditional pseudo-normals give poor results.

I'm not considering quads to be flat as I plan to give them special handling in our Smooth/Refine functions soon.

@elalish elalish self-assigned this Mar 12, 2024
@elalish
Copy link
Owner Author
elalish commented Mar 12, 2024

I should mention this is technically a breaking change in that the behavior of Manifold::Smooth has been updated to match the new Smooth (handling flat faces correctly and some other small tweaks), but I'm going to consider this bug fixes since I never liked the previous behavior and I doubt it has much usage yet. More changes will be coming in the next PRs.

.def("calculate_normals", &Manifold::CalculateNormals,
nb::arg("normal_idx"), nb::arg("min_sharp_angle"),
manifold__calculate_normals__normal_idx__min_sharp_angle)
.def("smooth", &Manifold::Smooth, nb::arg("min_sharp_angle"),
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wrongbad I appear to have messed up these python bindings somehow or maybe just the docs? Any thoughts on what I did wrong?

@elalish elalish requested a review from pca006132 March 13, 2024 14:47
@pca006132
Copy link
Collaborator

Will have a look tmr. Btw, any idea why the test failed?

@elalish
Copy link
Owner Author
elalish commented Mar 13, 2024

Will have a look tmr. Btw, any idea why the test failed?

I messed up the python bindings somehow.

Copy link
Collaborator
@pca006132 pca006132 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some little comments for now, has to sleep. Also, I think for the python issue you can use the following patch:

diff --git a/bindings/python/manifold3d.cpp b/bindings/python/manifold3d.cpp
index c85b61c..a4f0bf7 100644
--- a/bindings/python/manifold3d.cpp
+++ b/bindings/python/manifold3d.cpp
@@ -318,9 +318,13 @@ NB_MODULE(manifold3d, m) {
       .def("calculate_normals", &Manifold::CalculateNormals,
            nb::arg("normal_idx"), nb::arg("min_sharp_angle"),
            manifold__calculate_normals__normal_idx__min_sharp_angle)
-      .def("smooth", &Manifold::Smooth, nb::arg("min_sharp_angle"),
-           nb::arg("min_smoothness"),
-           manifold__smooth__min_sharp_angle__min_smoothness)
+      .def(
+          "smooth",
+          [](const Manifold &self, float minSharpAngle, float minSmoothness) {
+            return self.Smooth(minSharpAngle, minSmoothness);
+          },
+          nb::arg("min_sharp_angle") = 60, nb::arg("min_smoothness") = 0,
+          manifold__smooth__min_sharp_angle__min_smoothness)
       .def("refine", &Manifold::Refine, nb::arg("n"), manifold__refine__n)
       .def("refine_to_length", &Manifold::RefineToLength, nb::arg("length"),
            manifold__refine_to_length__length)

I think nanobind is just confused about the 3 overloads we have for Smooth.

std::unordered_map<int, int> oldHalfedge2New;
for (int tri = 0; tri < NumTri(); ++tri) {
int oldTri = meshRelation_.triRef[tri].tri;
for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious if a vector can be used instead, and maybe we can have parallelism here?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that first, but it's made difficult because there are potentially more triangles in the old indices than the new and I'm not sure how many.

}
}
if (faceNeighbors > 1) {
triIsFlatFace[tri] = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can parallelize this as well?
if we want to avoid repeated work, we should check faceTris[tri] before the start of the j loop.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for correctness we can't do the early-out.

: vertNormal_[vert];
int lastProp = -1;
do {
current = NextHalfedge(halfedge_[current].pairedHalfedge);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thinking about it, maybe we should just make a halfedge iterator that loops through all the halfedges connected to a vertex, to make this pattern more readable to beginners.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's a good idea - I just feel like I'm often making small tweaks to it: stopping part-way instead of doing the full circle 8000 , or sometimes including vs excluding the first or last edge. Would love some thoughts on ways to make this pattern more generic, because I do use it all the time.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be possible to make an iterator out of this. I will open an issue about this.

for (int e = 0; e < halfedge_.size(); ++e) {
if (!halfedge_[e].IsForward()) continue;
const int pair = halfedge_[e].pairedHalfedge;
const float dihedral = glm::degrees(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it will be slightly faster if we convert the angle into radian and only compare using radian, instead of doing conversion of degree to radian in a loop. same for the dihedral calculation below as well.

if (prop == lastProp) continue;
lastProp = prop;
for (int i = 0; i < oldNumProp; ++i)
meshRelation_.properties[prop * numProp + i] =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::copy?

.def(
"smooth",
[](const Manifold &self, float minSharpAngle, float minSmoothness) {
return self.Smooth(minSharpAngle, minSmoothness);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pca006132 Alas, no luck here. I wish the error wasn't such a pile of spew. Also, even when I build with MANIFOLD_PYBIND=ON I don't get the error - is something else required to make it build?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird... will debug this

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that nanobind is unhappy with smooth being both a static and instance method for Manifold`.

Critical nanobind error: nb::detail::nb_func_new("smooth"): mismatched static/instance method flags in function overloads!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to make the names different? Kind of annoying, but not the end of the world.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I think that can fix the issue.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'm going with SmoothOut - slightly more verb sounding.

@wrongbad
Copy link
Contributor

When I try to build your branch it passes for me. Maybe you should push your branch with this in pyproject.toml and see what the CI build says then

cmake.args = ["-DCMAKE_BUILD_TYPE=Debug", . . .

@elalish elalish merged commit c9ec169 into master Mar 19, 2024
@elalish elalish deleted the calculateNormals branch March 19, 2024 17:29
@elalish elalish mentioned this pull request Mar 25, 2024
@elalish elalish mentioned this pull request Jun 12, 2024
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 this pull request may close these issues.

3 participants
0