Visual Computing Library  devel
Loading...
Searching...
No Matches
curvature.h
1/*****************************************************************************
2 * VCLib *
3 * Visual Computing Library *
4 * *
5 * Copyright(C) 2021-2025 *
6 * Visual Computing Lab *
7 * ISTI - Italian National Research Council *
8 * *
9 * All rights reserved. *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the Mozilla Public License Version 2.0 as published *
13 * by the Mozilla Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * Mozilla Public License Version 2.0 *
20 * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. *
21 ****************************************************************************/
22
23#ifndef VCL_ALGORITHMS_MESH_UPDATE_CURVATURE_H
24#define VCL_ALGORITHMS_MESH_UPDATE_CURVATURE_H
25
26#include <vclib/algorithms/mesh/intersection.h>
27#include <vclib/algorithms/mesh/point_sampling.h>
28#include <vclib/algorithms/mesh/stat.h>
29#include <vclib/algorithms/mesh/update/normal.h>
30
31#include <vclib/algorithms/core.h>
32#include <vclib/mesh.h>
33#include <vclib/space/complex.h>
34#include <vclib/space/core.h>
35
36#include <mutex>
37
38namespace vcl {
39
40typedef enum {
41 VCL_PRINCIPAL_CURVATURE_TAUBIN95,
42 VCL_PRINCIPAL_CURVATURE_PCA
43} VCLibPrincipalCurvatureAlgorithm;
44
45template<FaceMeshConcept MeshType, LoggerConcept LogType = NullLogger>
46void updatePrincipalCurvatureTaubin95(MeshType& m, LogType& log = nullLogger)
47{
48 requirePerVertexPrincipalCurvature(m);
49 requirePerVertexAdjacentFaces(m);
51
52 using VertexType = MeshType::VertexType;
53 using PositionType = VertexType::PositionType;
54 using NormalType = VertexType::NormalType;
55 using ScalarType = PositionType::ScalarType;
56 using FaceType = MeshType::FaceType;
57
58 // Auxiliary data structure
59 struct AdjVertex
60 {
61 const VertexType* vert;
62 double doubleArea;
63 bool isBorder;
64 };
65
66 log.log(0, "Updating per vertex normals...");
67
68 updatePerVertexNormalsAngleWeighted(m);
69 normalizePerVertexNormals(m);
70
71 log.log(5, "Computing per vertex curvature...");
72 // log every 5%, starting from 5% to 100%
73 log.startProgress("", m.vertexNumber(), 5, 5, 100);
74
75 for (VertexType& v : m.vertices()) {
76 std::vector<ScalarType> weights;
77 std::vector<AdjVertex> vertices;
78
79 MeshPos<FaceType> pos(v.adjFace(0), &v);
80 const VertexType* firstVertex = pos.adjVertex();
81 const VertexType* tmpVertex;
82 float totalDoubleAreaSize = 0;
83
84 // compute the area of each triangle around the central vertex as well
85 // as their total area
86 do {
87 pos.nextEdgeAdjacentToV();
88 tmpVertex = pos.adjVertex();
89 AdjVertex adjV;
90 adjV.isBorder = pos.isEdgeOnBorder();
91 adjV.vert = tmpVertex;
92 adjV.doubleArea = faceArea(*pos.face()) * 2;
93 totalDoubleAreaSize += adjV.doubleArea;
94 vertices.push_back(adjV);
95 } while (tmpVertex != firstVertex);
96
97 weights.reserve(vertices.size());
98
99 for (int i = 0; i < vertices.size(); ++i) {
100 if (vertices[i].isBorder) {
101 weights.push_back(vertices[i].doubleArea / totalDoubleAreaSize);
102 }
103 else {
104 weights.push_back(
105 0.5f *
106 (vertices[i].doubleArea +
107 vertices[(i - 1) % vertices.size()].doubleArea) /
108 totalDoubleAreaSize);
109 }
110 assert(weights.back() < 1.0f);
111 }
112
113 // compute I-NN^t to be used for computing the T_i's
114 Matrix33<ScalarType> Tp;
115
116 NormalType n = v.normal();
117 for (int i = 0; i < 3; ++i)
118 Tp(i, i) = 1.0f - std::pow(n[i], 2);
119 Tp(0, 1) = Tp(1, 0) = -1.0f * (n[0] * n[1]);
120 Tp(1, 2) = Tp(2, 1) = -1.0f * (n[1] * n[2]);
121 Tp(0, 2) = Tp(2, 0) = -1.0f * (n[0] * n[2]);
122
123 // for all neighbors vi compute the directional curvatures k_i and the
124 // T_i compute M by summing all w_i k_i T_i T_i^t
125 Matrix33<ScalarType> tempMatrix;
126 Matrix33<ScalarType> M = Matrix33<ScalarType>::Zero();
127 for (size_t i = 0; i < vertices.size(); ++i) {
128 PositionType edge = (v.position() - vertices[i].vert->position());
129 float curvature =
130 (2.0f * (v.normal().dot(edge))) / edge.squaredNorm();
131 PositionType t = Tp * edge;
132 t.normalize();
133 tempMatrix = t.outerProduct(t);
134 M += tempMatrix * weights[i] * curvature;
135 }
136
137 // compute vector W for the Householder matrix
138 PositionType w;
139 PositionType e1(1.0f, 0.0f, 0.0f);
140 if ((e1 - v.normal()).squaredNorm() > (e1 + v.normal()).squaredNorm())
141 w = e1 - v.normal();
142 else
143 w = e1 + v.normal();
144 w.normalize();
145
146 // compute the Householder matrix I - 2WW^t
147 Matrix33<ScalarType> Q = Matrix33<ScalarType>::Identity();
148 tempMatrix = w.outerProduct(w);
149 Q -= tempMatrix * 2.0f;
150
151 // compute matrix Q^t M Q
152 Matrix33<ScalarType> QtMQ = (Q.transpose() * M * Q);
153
154 Eigen::Matrix<ScalarType, 1, 3> T1 = Q.col(1);
155 Eigen::Matrix<ScalarType, 1, 3> T2 = Q.col(2);
156
157 // find sin and cos for the Givens rotation
158 ScalarType s, c;
159 // Gabriel Taubin hint and Valentino Fiorin impementation
160 ScalarType alpha = QtMQ(1, 1) - QtMQ(2, 2);
161 ScalarType beta = QtMQ(2, 1);
162
163 ScalarType h[2];
164 ScalarType delta =
165 std::sqrt(4.0f * std::pow(alpha, 2) + 16.0f * std::pow(beta, 2));
166 h[0] = (2.0f * alpha + delta) / (2.0f * beta);
167 h[1] = (2.0f * alpha - delta) / (2.0f * beta);
168
169 ScalarType t[2];
170 ScalarType bestC, bestS;
171 ScalarType minError = std::numeric_limits<ScalarType>::infinity();
172 for (uint i = 0; i < 2; i++) {
173 delta = std::sqrt(std::pow(h[i], 2) + 4.0f);
174 t[0] = (h[i] + delta) / 2.0f;
175 t[1] = (h[i] - delta) / 2.0f;
176
177 for (uint j = 0; j < 2; j++) {
178 ScalarType squaredT = std::pow(t[j], 2);
179 ScalarType denominator = 1.0f + squaredT;
180
181 s = (2.0f * t[j]) / denominator;
182 c = (1 - squaredT) / denominator;
183
184 ScalarType approximation =
185 c * s * alpha + (std::pow(c, 2) - std::pow(s, 2)) * beta;
186 ScalarType angleSimilarity =
187 std::abs(std::acos(c) / std::asin(s));
188 ScalarType error =
189 std::abs(1.0f - angleSimilarity) + std::abs(approximation);
190 if (error < minError) {
191 minError = error;
192 bestC = c;
193 bestS = s;
194 }
195 }
196 }
197 c = bestC;
198 s = bestS;
199
200 Eigen::Matrix2f minor2x2;
201 Eigen::Matrix2f S;
202
203 // diagonalize M
204 minor2x2(0, 0) = QtMQ(1, 1);
205 minor2x2(0, 1) = QtMQ(1, 2);
206 minor2x2(1, 0) = QtMQ(2, 1);
207 minor2x2(1, 1) = QtMQ(2, 2);
208
209 S(0, 0) = S(1, 1) = c;
210 S(0, 1) = s;
211 S(1, 0) = -1.0f * s;
212
213 Eigen::Matrix2f StMS = S.transpose() * minor2x2 * S;
214
215 // compute curvatures and curvature directions
216 ScalarType principalCurv1 = (3.0f * StMS(0, 0)) - StMS(1, 1);
217 ScalarType principalCurv2 = (3.0f * StMS(1, 1)) - StMS(0, 0);
218
219 Eigen::Matrix<ScalarType, 1, 3> principalDir1 = T1 * c - T2 * s;
220 Eigen::Matrix<ScalarType, 1, 3> principalDir2 = T1 * s + T2 * c;
221
222 v.principalCurvature().maxDir() = principalDir1;
223 v.principalCurvature().minDir() = principalDir2;
224 v.principalCurvature().maxValue() = principalCurv1;
225 v.principalCurvature().minValue() = principalCurv2;
226
227 log.progress(m.index(v));
228 }
229
230 log.endProgress();
231 log.log(100, "Per vertex curvature computed.");
232}
233
246template<FaceMeshConcept MeshType, LoggerConcept LogType = NullLogger>
247void updatePrincipalCurvaturePCA(
248 MeshType& m,
249 typename MeshType::VertexType::PositionType::ScalarType radius,
250 bool montecarloSampling = true,
251 LogType& log = nullLogger)
252{
253 using VertexType = MeshType::VertexType;
254 using PositionType = VertexType::PositionType;
255 using ScalarType = PositionType::ScalarType;
256 using NormalType = VertexType::NormalType;
257 using FaceType = MeshType::FaceType;
258
259 using VGrid = StaticGrid3<VertexType*, ScalarType>;
260 using VGridIterator = VGrid::ConstIterator;
261
262 VGrid pGrid;
263 ScalarType area;
264
265 log.log(0, "Updating per vertex normals...");
266
267 updatePerVertexNormalsAngleWeighted(m);
268 normalizePerVertexNormals(m);
269
270 log.log(0, "Computing per vertex curvature...");
271 log.startProgress("", m.vertexNumber());
272
273 if (montecarloSampling) {
274 area = surfaceArea(m);
275 pGrid = VGrid(m.vertices() | views::addrOf);
276 pGrid.build();
277 }
278
279 parallelFor(m.vertices(), [&](VertexType& v) {
280 // for (VertexType& v : m.vertices()) {
281 Matrix33<ScalarType> A, eigenvectors;
282 PositionType bp, eigenvalues;
283 if (montecarloSampling) {
284 Sphere s(v.position(), radius);
285 std::vector<VGridIterator> vec = pGrid.valuesInSphere(s);
286 std::vector<PositionType> points;
287 points.reserve(vec.size());
288 for (const auto& it : vec) {
289 points.push_back(it->second->position());
290 }
291 A = covarianceMatrixOfPointCloud(points);
292 A *= area * area / 1000;
293 }
294 else {
295 Sphere<ScalarType> sph(v.position(), radius);
296 MeshType tmpMesh = intersection(m, sph);
297
298 A = covarianceMatrixOfMesh(tmpMesh);
299 }
300
301 Eigen::SelfAdjointEigenSolver<Eigen::Matrix<ScalarType, 3, 3>> eig(A);
302 eigenvalues = PositionType(eig.eigenvalues());
303 eigenvectors = eig.eigenvectors(); // eigenvector are stored as columns.
304 // get the estimate of curvatures from eigenvalues and eigenvectors
305 // find the 2 most tangent eigenvectors (by finding the one closest to
306 // the normal)
307 uint best = 0;
308 ScalarType bestv = std::abs(
309 v.normal().dot(PositionType(eigenvectors.col(0).normalized())));
310 for (uint i = 1; i < 3; ++i) {
311 ScalarType prod = std::abs(
312 v.normal().dot(PositionType(eigenvectors.col(i).normalized())));
313 if (prod > bestv) {
314 bestv = prod;
315 best = i;
316 }
317 }
318 v.principalCurvature().maxDir() =
319 (eigenvectors.col((best + 1) % 3).normalized());
320 v.principalCurvature().minDir() =
321 (eigenvectors.col((best + 2) % 3).normalized());
322
323 Matrix33<ScalarType> rot;
324 ScalarType angle;
325 angle = acos(v.principalCurvature().maxDir().dot(v.normal()));
326
327 rot = rotationMatrix<Matrix33<ScalarType>>(
328 PositionType(v.principalCurvature().maxDir().cross(v.normal())),
329 -(M_PI * 0.5 - angle));
330
331 v.principalCurvature().maxDir() = rot * v.principalCurvature().maxDir();
332
333 angle = acos(v.principalCurvature().minDir().dot(v.normal()));
334
335 rot = rotationMatrix<Matrix33<ScalarType>>(
336 PositionType(v.principalCurvature().minDir().cross(v.normal())),
337 -(M_PI * 0.5 - angle));
338
339 v.principalCurvature().minDir() = rot * v.principalCurvature().minDir();
340
341 // computes the curvature values
342 const ScalarType r5 = std::pow(radius, 5);
343 const ScalarType r6 = r5 * radius;
344 v.principalCurvature().maxValue() =
345 (2.0 / 5.0) *
346 (4.0 * M_PI * r5 + 15 * eigenvalues[(best + 2) % 3] -
347 45.0 * eigenvalues[(best + 1) % 3]) /
348 (M_PI * r6);
349 v.principalCurvature().minValue() =
350 (2.0 / 5.0) *
351 (4.0 * M_PI * r5 + 15 * eigenvalues[(best + 1) % 3] -
352 45.0 * eigenvalues[(best + 2) % 3]) /
353 (M_PI * r6);
354 if (v.principalCurvature().maxValue() <
355 v.principalCurvature().minValue()) {
356 std::swap(
357 v.principalCurvature().minValue(),
358 v.principalCurvature().maxValue());
359 std::swap(
360 v.principalCurvature().minDir(),
361 v.principalCurvature().maxDir());
362 }
363
364 log.progress(m.index(v));
365 //}
366 });
367
368 log.endProgress();
369 log.log(100, "Per vertex curvature computed.");
370}
371
372template<FaceMeshConcept MeshType, LoggerConcept LogType = NullLogger>
373void updatePrincipalCurvature(MeshType& m, LogType& log = nullLogger)
374{
375 requirePerVertexPrincipalCurvature(m);
376
377 updatePrincipalCurvatureTaubin95(m, log);
378}
379
380template<FaceMeshConcept MeshType, LoggerConcept LogType = NullLogger>
381void updatePrincipalCurvature(
382 MeshType& m,
383 VCLibPrincipalCurvatureAlgorithm alg = VCL_PRINCIPAL_CURVATURE_TAUBIN95,
384 LogType& log = nullLogger)
385{
386 requirePerVertexPrincipalCurvature(m);
387
388 double radius;
389 switch (alg) {
390 case VCL_PRINCIPAL_CURVATURE_TAUBIN95:
391 updatePrincipalCurvatureTaubin95(m, log);
392 break;
393 case VCL_PRINCIPAL_CURVATURE_PCA:
394 radius = boundingBox(m).diagonal() * 0.1;
395 updatePrincipalCurvaturePCA(m, radius, true, log);
396 }
397}
398
399} // namespace vcl
400
401#endif // VCL_ALGORITHMS_MESH_UPDATE_CURVATURE_H
auto diagonal() const
Calculates the diagonal length of the box.
Definition box.h:246
Box()
The Empty constructor of a box, initializes a null box.
Definition box.h:65
PointT size() const
Computes the size of the box.
Definition box.h:267
NullLogger nullLogger
The nullLogger object is an object of type NullLogger that is used as default argument in the functio...
Definition null_logger.h:123
auto boundingBox(const PointType &p)
Compute the bounding box of a single point.
Definition bounding_box.h:59
auto faceArea(const FaceType &f)
Computes the area of a face. Works both for triangle and polygonal faces, and it is optimized in case...
Definition geometry.h:108
void requirePerFaceAdjacentFaces(const MeshType &m)
This function asserts that a Mesh has a FaceContainer, the Face has a AdjacentFaces Component,...
Definition face_requirements.h:960
constexpr detail::VerticesView vertices
A view that allows to iterate over the Vertex elements of an object.
Definition vertex.h:92
constexpr detail::AddressOfView addrOf
The addrOf view applies the address-of operator & on the input view.
Definition pointers.h:120