Skip to content

Commit c9ec169

Browse files
authored
CalculateNormals and Smooth (#771)
* started implementation * update to Vec * refactor * snap normals to faces * added flat face sharpening * added Smooth member * Smooth working * refactor SetNormals * calculateNormals runs * CalculateNormals works * update test * add bindings * fix python bindings * fix format * Smooth -> SmoothOut * a few fixes * weird format bug * addressing feedback * update docs
1 parent 9b1d255 commit c9ec169

18 files changed

+528
-95
lines changed

.github/workflows/check_format.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212

1313
steps:
1414
- uses: actions/checkout@v4
15-
- uses: DoozyX/clang-format-lint-action@v0.14
15+
- uses: DoozyX/clang-format-lint-action@v0.17
1616
with:
1717
source: '.'
1818
exclude: '*/third_party'

.vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"cmake.configureArgs": [
121121
"-DBUILD_SHARED_LIBS=ON",
122122
"-DMANIFOLD_CBIND=ON",
123+
"-DMANIFOLD_PYBIND=ON",
123124
"-DMANIFOLD_EXPORT=ON",
124125
"-DMANIFOLD_DEBUG=ON",
125126
"-DMANIFOLD_PAR=TBB",

bindings/c/include/manifoldc.h

+5
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
139139
float ny, float nz);
140140
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
141141
ManifoldVec3 (*fun)(float, float, float));
142+
ManifoldManifold *manifold_smooth_out(void *mem, ManifoldManifold *m,
143+
float minSharpAngle, float minSmoothness);
142144
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine);
143145
ManifoldManifold *manifold_refine_to_length(void *mem, ManifoldManifold *m,
144146
float length);
@@ -188,6 +190,9 @@ ManifoldManifold *manifold_set_properties(
188190
void (*fun)(float *new_prop, ManifoldVec3 position, const float *old_prop));
189191
ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
190192
int gaussian_idx, int mean_idx);
193+
ManifoldManifold *manifold_calculate_normals(void *mem, ManifoldManifold *m,
194+
int normal_idx,
195+
int min_sharp_angle);
191196

192197
// CrossSection Shapes/Constructors
193198

bindings/c/manifoldc.cpp

+14
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,13 @@ ManifoldMeshGL *manifold_level_set_seq_context(
300300
return level_set_context(mem, sdf, bounds, edge_length, level, true, ctx);
301301
}
302302

303+
ManifoldManifold *manifold_smooth_out(void *mem, ManifoldManifold *m,
304+
float minSharpAngle,
305+
float minSmoothness) {
306+
auto smoothed = from_c(m)->SmoothOut(minSharpAngle, minSmoothness);
307+
return to_c(new (mem) Manifold(smoothed));
308+
}
309+
303310
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine) {
304311
auto refined = from_c(m)->Refine(refine);
305312
return to_c(new (mem) Manifold(refined));
@@ -543,6 +550,13 @@ ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
543550
return to_c(new (mem) Manifold(man));
544551
}
545552

553+
ManifoldManifold *manifold_calculate_normals(void *mem, ManifoldManifold *m,
554+
int normal_idx,
555+
int min_sharp_angle) {
556+
auto man = from_c(m)->CalculateNormals(normal_idx, min_sharp_angle);
557+
return to_c(new (mem) Manifold(man));
558+
}
559+
546560
// Static Quality Globals
547561

548562
void manifold_set_min_circular_angle(float degrees) {

bindings/python/examples/all_apis.py

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def all_manifold():
6969
m = Manifold.batch_hull([m, m.translate((0, 0, 1))])
7070
b = m.bounding_box()
7171
m = m.calculate_curvature(4, 5)
72+
m = m.calculate_normals(0)
7273
m = Manifold.compose([m, m.translate((5, 0, 0))])
7374
m = Manifold.cube((1, 1, 1))
7475
m = Manifold.cylinder(1, 1)
@@ -92,6 +93,7 @@ def all_manifold():
9293
c = m.project()
9394
m = m.refine(2)
9495
m = m.refine_to_length(0.1)
96+
m = m.smooth_out()
9597
i = Manifold.reserve_ids(1)
9698
m = m.scale((1, 2, 3))
9799
m = m.set_properties(3, lambda pos, prop: pos)

bindings/python/manifold3d.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,12 @@ NB_MODULE(manifold3d, m) {
315315
.def("calculate_curvature", &Manifold::CalculateCurvature,
316316
nb::arg("gaussian_idx"), nb::arg("mean_idx"),
317317
manifold__calculate_curvature__gaussian_idx__mean_idx)
318+
.def("calculate_normals", &Manifold::CalculateNormals,
319+
nb::arg("normal_idx"), nb::arg("min_sharp_angle") = 60,
320+
manifold__calculate_normals__normal_idx__min_sharp_angle)
321+
.def("smooth_out", &Manifold::SmoothOut, nb::arg("min_sharp_angle") = 60,
322+
nb::arg("min_smoothness") = 0,
323+
manifold__smooth_out__min_sharp_angle__min_smoothness)
318324
.def("refine", &Manifold::Refine, nb::arg("n"), manifold__refine__n)
319325
.def("refine_to_length", &Manifold::RefineToLength, nb::arg("length"),
320326
manifold__refine_to_length__length)

bindings/wasm/bindings.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ EMSCRIPTEN_BINDINGS(whatever) {
144144
.function("_GetMeshJS", &js::GetMeshJS)
145145
.function("refine", &Manifold::Refine)
146146
.function("refineToLength", &Manifold::RefineToLength)
147+
.function("smoothOut", &Manifold::SmoothOut)
147148
.function("_Warp", &man_js::Warp)
148149
.function("_SetProperties", &man_js::SetProperties)
149150
.function("transform", &man_js::Transform)
@@ -165,6 +166,7 @@ EMSCRIPTEN_BINDINGS(whatever) {
165166
.function("genus", &Manifold::Genus)
166167
.function("getProperties", &Manifold::GetProperties)
167168
.function("calculateCurvature", &Manifold::CalculateCurvature)
169+
.function("calculateNormals", &Manifold::CalculateNormals)
168170
.function("originalID", &Manifold::OriginalID)
169171
.function("asOriginal", &Manifold::AsOriginal);
170172

bindings/wasm/examples/worker.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ suite('Examples', () => {
116116
test('Scallop', async () => {
117117
const result = await runExample('Scallop');
118118
expect(result.genus).to.equal(0, 'Genus');
119-
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
120-
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
119+
expect(result.volume).to.be.closeTo(41100, 100, 'Volume');
120+
expect(result.surfaceArea).to.be.closeTo(7790, 10, 'Surface Area');
121121
});
122122

123123
test('Torus Knot', async () => {

bindings/wasm/examples/worker.ts

+23-4
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,29 @@ const manifoldStaticFunctions = [
5959
];
6060
// manifold member functions (that return a new manifold)
6161
const manifoldMemberFunctions = [
62-
'add', 'subtract', 'intersect', 'decompose', 'warp',
63-
'transform', 'translate', 'rotate', 'scale', 'mirror',
64-
'refine', 'refineToLength', 'setProperties', 'asOriginal', 'trimByPlane',
65-
'split', 'splitByPlane', 'slice', 'project', 'hull'
62+
'add',
63+
'subtract',
64+
'intersect',
65+
'decompose',
66+
'warp',
67+
'transform',
68+
'translate',
69+
'rotate',
70+
'scale',
71+
'mirror',
72+
'calculateCurvature',
73+
'calculateNormals',
74+
'smoothOut',
75+
'refine',
76+
'refineToLength',
77+
'setProperties',
78+
'asOriginal',
79+
'trimByPlane',
80+
'split',
81+
'splitByPlane',
82+
'slice',
83+
'project',
84+
'hull'
6685
];
6786
// CrossSection static methods (that return a new cross-section)
6887
const crossSectionStaticFunctions = [

bindings/wasm/manifold-encapsulated-types.d.ts

+36
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,24 @@ export class Manifold {
559559
*/
560560
warp(warpFunc: (vert: Vec3) => void): Manifold;
561561

562+
/**
563+
* Smooths out the Manifold by filling in the halfedgeTangent vectors. The
564+
* geometry will remain unchanged until Refine or RefineToLength is called to
565+
* interpolate the surface.
566+
*
567+
* @param minSharpAngle degrees, default 60. Any edges with angles greater
568+
* than this value will remain sharp. The rest will be smoothed to G1
569+
* continuity, with the caveat that flat faces of three or more triangles will
570+
* always remain flat. With a value of zero, the model is faceted, but in this
571+
* case there is no point in smoothing.
572+
*
573+
* @param minSmoothness range: 0 - 1, default 0. The smoothness applied to
574+
* sharp angles. The default gives a hard edge, while values > 0 will give a
575+
* small fillet on these sharp edges. A value of 1 is equivalent to a
576+
* minSharpAngle of 180 - all edges will be smooth.
577+
*/
578+
smoothOut(minSharpAngle?: number, minSmoothness?: number): Manifold;
579+
562580
/**
563581
* Increase the density of the mesh by splitting every edge into n pieces. For
564582
* instance, with n = 2, each triangle will be split into 4 triangles. These
@@ -618,6 +636,24 @@ export class Manifold {
618636
*/
619637
calculateCurvature(gaussianIdx: number, meanIdx: number): Manifold;
620638

639+
/**
640+
* Fills in vertex properties for normal vectors, calculated from the mesh
641+
* geometry. Flat faces composed of three or more triangles will remain flat.
642+
*
643+
* @param normalIdx The property channel in which to store the X
644+
* values of the normals. The X, Y, and Z channels will be sequential. The
645+
* property set will be automatically expanded to include up through normalIdx
646+
* + 2.
647+
*
648+
* @param minSharpAngle Any edges with angles greater than this value will
649+
* remain sharp, getting different normal vector properties on each side of
650+
* the edge. By default, no edges are sharp and all normals are shared. With a
651+
* value of zero, the model is faceted and all normals match their triangle
652+
* normals, but in this case it would be better not to calculate normals at
653+
* all.
654+
*/
655+
calculateNormals(normalIdx: number, minSharpAngle: number): Manifold;
656+
621657
// Boolean Operations
622658

623659
/**

src/manifold/include/manifold.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ struct MeshGL {
128128
* potentially Mesh if only basic geometry is required.
129129
*
130130
* In addition to storing geometric data, a Manifold can also store an arbitrary
131-
* number of vertex properties. These could be anything, e.g. UV coordinates,
132-
* colors, bone weights, etc, but this library is completely agnostic. All
131+
* number of vertex properties. These could be anything, e.g. normals, UV
132+
* coordinates, colors, etc, but this library is completely agnostic. All
133133
* properties are merely float values indexed by channel number. It is up to the
134134
* user to associate channel numbers with meaning.
135135
*
@@ -244,6 +244,8 @@ class Manifold {
244244
Manifold SetProperties(
245245
int, std::function<void(float*, glm::vec3, const float*)>) const;
246246
Manifold CalculateCurvature(int gaussianIdx, int meanIdx) const;
247+
Manifold CalculateNormals(int normalIdx, float minSharpAngle = 60) const;
248+
Manifold SmoothOut(float minSharpAngle = 60, float minSmoothness = 0) const;
247249
Manifold Refine(int) const;
248250
Manifold RefineToLength(float) const;
249251
// Manifold RefineToPrecision(float);

src/manifold/src/constructors.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Manifold Manifold::Smooth(const MeshGL& meshGL,
8888
std::vector<float> propertyTolerance(meshGL.numProp - 3, -1);
8989
std::shared_ptr<Impl> impl =
9090
std::make_shared<Impl>(meshGL, propertyTolerance);
91-
impl->CreateTangents(sharpenedEdges);
91+
impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges));
9292
return Manifold(impl);
9393
}
9494

@@ -128,7 +128,7 @@ Manifold Manifold::Smooth(const Mesh& mesh,
128128

129129
Impl::MeshRelationD relation = {(int)ReserveIDs(1)};
130130
std::shared_ptr<Impl> impl = std::make_shared<Impl>(mesh, relation);
131-
impl->CreateTangents(sharpenedEdges);
131+
impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges));
132132
return Manifold(impl);
133133
}
134134

src/manifold/src/impl.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,14 @@ struct Manifold::Impl {
137137
void SplitPinchedVerts();
138138

139139
// smoothing.cu
140-
void CreateTangents(const std::vector<Smoothness>&);
140+
std::vector<Smoothness> UpdateSharpenedEdges(
141+
const std::vector<Smoothness>&) const;
142+
Vec<bool> FlatFaces() const;
143+
Vec<int> VertFlatFace(const Vec<bool>&) const;
144+
std::vector<Smoothness> SharpenEdges(float minSharpAngle,
145+
float minSmoothness) const;
146+
void SetNormals(int normalIdx, float minSharpAngle);
147+
void CreateTangents(std::vector<Smoothness>);
141148
Vec<Barycentric> Subdivide(std::function<int(glm::vec3)>);
142149
void Refine(std::function<int(glm::vec3)>);
143150
};

src/manifold/src/manifold.cpp

+45
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,51 @@ Manifold Manifold::CalculateCurvature(int gaussianIdx, int meanIdx) const {
655655
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
656656
}
657657

658+
/**
659+
* Fills in vertex properties for normal vectors, calculated from the mesh
660+
* geometry. Flat faces composed of three or more triangles will remain flat.
661+
*
662+
* @param normalIdx The property channel in which to store the X
663+
* values of the normals. The X, Y, and Z channels will be sequential. The
664+
* property set will be automatically expanded to include up through normalIdx
665+
* + 2.
666+
*
667+
* @param minSharpAngle Any edges with angles greater than this value will
668+
* remain sharp, getting different normal vector properties on each side of the
669+
* edge. By default, no edges are sharp and all normals are shared. With a value
670+
* of zero, the model is faceted and all normals match their triangle normals,
671+
* but in this case it would be better not to calculate normals at all.
672+
*/
673+
Manifold Manifold::CalculateNormals(int normalIdx, float minSharpAngle) const {
674+
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
675+
pImpl->SetNormals(normalIdx, minSharpAngle);
676+
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
677+
}
678+
679+
/**
680+
* Smooths out the Manifold by filling in the halfedgeTangent vectors. The
681+
* geometry will remain unchanged until Refine or RefineToLength is called to
682+
* interpolate the surface.
683+
*
684+
* @param minSharpAngle degrees, default 60. Any edges with angles greater than
685+
* this value will remain sharp. The rest will be smoothed to G1 continuity,
686+
* with the caveat that flat faces of three or more triangles will always remain
687+
* flat. With a value of zero, the model is faceted, but in this case there is
688+
* no point in smoothing.
689+
*
690+
* @param minSmoothness range: 0 - 1, default 0. The smoothness applied to sharp
691+
* angles. The default gives a hard edge, while values > 0 will give a small
692+
* fillet on these sharp edges. A value of 1 is equivalent to a minSharpAngle of
693+
* 180 - all edges will be smooth.
694+
*/
695+
Manifold Manifold::SmoothOut(float minSharpAngle, float minSmoothness) const {
696+
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
697+
if (!IsEmpty()) {
698+
pImpl->CreateTangents(pImpl->SharpenEdges(minSharpAngle, minSmoothness));
699+
}
700+
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
701+
}
702+
658703
/**
659704
* Increase the density of the mesh by splitting every edge into n pieces. For
660705
* instance, with n = 2, each triangle will be split into 4 triangles. These

src/manifold/src/shared.h

+4
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ struct TriRef {
157157
/// The triangle index of the original triangle this was part of:
158158
/// Mesh.triVerts[tri].
159159
int tri;
160+
161+
bool SameFace(const TriRef& other) const {
162+
return meshID == other.meshID && tri == other.tri;
163+
}
160164
};
161165

162166
/**

0 commit comments

Comments
 (0)