Skip to content

Commit cdcbd32

Browse files
authored
Smoothing fixes (#796)
* fixed nan normals * re-identify flat faces after refine * fix color interpolation * fix build
1 parent c738f1e commit cdcbd32

File tree

5 files changed

+71
-11
lines changed

5 files changed

+71
-11
lines changed

src/manifold/src/impl.h

+1
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ struct Manifold::Impl {
178178
std::vector<Smoothness> SharpenEdges(float minSharpAngle,
179179
float minSmoothness) const;
180180
void SetNormals(int normalIdx, float minSharpAngle);
181+
void LinearizeFlatTangents();
181182
void CreateTangents(int normalIdx);
182183
void CreateTangents(std::vector<Smoothness>);
183184
Vec<Barycentric> Subdivide(std::function<int(glm::vec3)>);

src/manifold/src/smoothing.cpp

+35-4
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,8 @@ struct SmoothBezier {
8484
const glm::vec3 edgeVec =
8585
impl->vertPos_[edge.endVert] - impl->vertPos_[edge.startVert];
8686
const glm::vec3 edgeNormal =
87-
(impl->faceNormal_[edge.face] +
88-
impl->faceNormal_[impl->halfedge_[edge.pairedHalfedge].face]) /
89-
2.0f;
87+
impl->faceNormal_[edge.face] +
88+
impl->faceNormal_[impl->halfedge_[edge.pairedHalfedge].face];
9089
glm::vec3 dir =
9190
glm::cross(glm::cross(edgeNormal, edgeVec), vertNormal[edge.startVert]);
9291
tangent = CircularTangent(dir, edgeVec);
@@ -966,7 +965,7 @@ void Manifold::Impl::SetNormals(int normalIdx, float minSharpAngle) {
966965
group.push_back(normals.size() - 1);
967966
float dot = glm::dot(here.edgeVec, next.edgeVec);
968967
const float phi =
969-
dot >= 1 ? 0
968+
dot >= 1 ? kTolerance
970969
: (dot <= -1 ? glm::pi<float>() : glm::acos(dot));
971970
normals.back() += faceNormal_[next.face] * phi;
972971
});
@@ -1014,6 +1013,35 @@ void Manifold::Impl::SetNormals(int normalIdx, float minSharpAngle) {
10141013
}
10151014
}
10161015

1016+
/**
1017+
* Tangents get flattened to create sharp edges by setting their weight to zero.
1018+
* This is the natural limit of reducing the weight to increase the sharpness
1019+
* smoothly. This limit gives a decent shape, but it causes the parameterization
1020+
* to be stretched and compresses it near the edges, which is good for resolving
1021+
* tight curvature, but bad for property interpolation. This function fixes the
1022+
* parameter stretch at the limit for sharp edges, since there is no curvature
1023+
* to resolve. Note this also changes the overall shape - making it more evenly
1024+
* curved.
1025+
*/
1026+
void Manifold::Impl::LinearizeFlatTangents() {
1027+
const int n = halfedgeTangent_.size();
1028+
for_each_n(autoPolicy(n), zip(halfedgeTangent_.begin(), countAt(0)), n,
1029+
[this](thrust::tuple<glm::vec4&, int> inOut) {
1030+
glm::vec4& tangent = thrust::get<0>(inOut);
1031+
const int halfedge = thrust::get<1>(inOut);
1032+
if (tangent.w != 0) {
1033+
return;
1034+
}
1035+
1036+
const glm::vec4 otherTangent =
1037+
halfedgeTangent_[halfedge_[halfedge].pairedHalfedge];
1038+
glm::vec3 edge = vertPos_[halfedge_[halfedge].endVert] +
1039+
glm::vec3(otherTangent) -
1040+
vertPos_[halfedge_[halfedge].startVert];
1041+
tangent = glm::vec4(edge / 3.0f, 1);
1042+
});
1043+
}
1044+
10171045
/**
10181046
* Calculates halfedgeTangent_, allowing the manifold to be refined and
10191047
* smoothed. The tangents form weighted cubic Beziers along each edge. This
@@ -1094,6 +1122,7 @@ void Manifold::Impl::CreateTangents(int normalIdx) {
10941122
});
10951123
}
10961124
}
1125+
LinearizeFlatTangents();
10971126
}
10981127

10991128
/**
@@ -1207,6 +1236,7 @@ void Manifold::Impl::CreateTangents(std::vector<Smoothness> sharpenedEdges) {
12071236
});
12081237
}
12091238
}
1239+
LinearizeFlatTangents();
12101240
}
12111241

12121242
/**
@@ -1517,6 +1547,7 @@ void Manifold::Impl::Refine(std::function<int(glm::vec3)> edgeDivisions) {
15171547
// being non-coplanar, and hence not being related to the original faces.
15181548
meshRelation_.originalID = ReserveIDs(1);
15191549
InitializeOriginal();
1550+
CreateFaces();
15201551
}
15211552

15221553
halfedgeTangent_.resize(0);

test/manifold_test.cpp

+21-7
Original file line numberDiff line numberDiff line change
@@ -357,30 +357,44 @@ TEST(Manifold, RefineQuads) {
357357
EXPECT_EQ(cylinder.NumTri(), 16892);
358358
auto prop = cylinder.GetProperties();
359359
EXPECT_NEAR(prop.volume, 2 * glm::pi<float>(), 0.003);
360-
EXPECT_NEAR(prop.surfaceArea, 6 * glm::pi<float>(), 0.0003);
360+
EXPECT_NEAR(prop.surfaceArea, 6 * glm::pi<float>(), 0.004);
361+
const MeshGL out = cylinder.GetMeshGL();
362+
CheckGL(out);
363+
364+
const MeshGL baseline = WithPositionColors(cylinder);
365+
float maxDiff = 0;
366+
for (int i = 0; i < out.vertProperties.size(); ++i) {
367+
maxDiff = glm::max(
368+
maxDiff, glm::abs(out.vertProperties[i] - baseline.vertProperties[i]));
369+
}
370+
// This has a wide tolerance because the triangle colors on the ends are still
371+
// being stretched out into circular arcs, which introduces unavoidable error.
372+
EXPECT_LE(maxDiff, 0.07);
361373

362374
#ifdef MANIFOLD_EXPORT
363375
ExportOptions options2;
376+
options2.mat.metalness = 0;
377+
options2.mat.roughness = 0.5;
364378
options2.mat.colorChannels = {3, 4, 5, -1};
365-
if (options.exportModels)
366-
ExportMesh("refinedCylinder.glb", cylinder.GetMeshGL(), options2);
379+
if (options.exportModels) ExportMesh("refinedCylinder.glb", out, options2);
367380
#endif
368381
}
369382

370383
TEST(Manifold, SmoothFlat) {
371384
Manifold cone = Manifold::Cylinder(5, 10, 5, 12).SmoothOut();
372385
Manifold smooth = cone.RefineToLength(0.1).CalculateNormals(0);
373386
auto prop = smooth.GetProperties();
374-
EXPECT_NEAR(prop.volume, 1159.02, 0.01);
375-
EXPECT_NEAR(prop.surfaceArea, 771.45, 0.01);
387+
EXPECT_NEAR(prop.volume, 1142.9, 0.01);
388+
EXPECT_NEAR(prop.surfaceArea, 764.28, 0.01);
389+
MeshGL out = smooth.GetMeshGL();
390+
CheckGL(out);
376391

377392
#ifdef MANIFOLD_EXPORT
378393
ExportOptions options2;
379394
options2.faceted = false;
380395
options2.mat.normalChannels = {3, 4, 5};
381396
options2.mat.roughness = 0;
382-
if (options.exportModels)
383-
ExportMesh("smoothCone.glb", smooth.GetMeshGL(), options2);
397+
if (options.exportModels) ExportMesh("smoothCone.glb", out, options2);
384398
#endif
385399
}
386400

test/test.h

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ MeshGL WithPositionColors(const Manifold& in);
5757
MeshGL WithNormals(const Manifold& in);
5858
float GetMaxProperty(const MeshGL& mesh, int channel);
5959
float GetMinProperty(const MeshGL& mesh, int channel);
60+
void CheckFinite(const MeshGL& mesh);
6061
void Identical(const Mesh& mesh1, const Mesh& mesh2);
6162
void RelatedGL(const Manifold& out, const std::vector<MeshGL>& originals,
6263
bool checkNormals = false, bool updateNormals = false);

test/test_main.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,18 @@ float GetMinProperty(const MeshGL& mesh, int channel) {
305305
return min;
306306
}
307307

308+
void CheckFinite(const MeshGL& mesh) {
309+
for (float v : mesh.vertProperties) {
310+
ASSERT_TRUE(glm::isfinite(v));
311+
}
312+
for (float v : mesh.runTransform) {
313+
ASSERT_TRUE(glm::isfinite(v));
314+
}
315+
for (float v : mesh.halfedgeTangent) {
316+
ASSERT_TRUE(glm::isfinite(v));
317+
}
318+
}
319+
308320
void Identical(const Mesh& mesh1, const Mesh& mesh2) {
309321
ASSERT_EQ(mesh1.vertPos.size(), mesh2.vertPos.size());
310322
for (int i = 0; i < mesh1.vertPos.size(); ++i)
@@ -455,6 +467,7 @@ void CheckGL(const Manifold& manifold) {
455467
EXPECT_EQ(meshGL.runTransform.size(), 12 * meshGL.runOriginalID.size());
456468
}
457469
EXPECT_EQ(meshGL.faceID.size(), meshGL.NumTri());
470+
CheckFinite(meshGL);
458471
}
459472

460473
#ifdef MANIFOLD_EXPORT

0 commit comments

Comments
 (0)