Blender V4.3
Curvature.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 1999 Stephane Popinet
2 * SPDX-FileCopyrightText: 2000-2003 `Bruno Levy <levy@loria.fr>`
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 *
6 * The Original Code is:
7 * - GTS - Library for the manipulation of triangulated surfaces.
8 * - OGF/Graphite: Geometry and Graphics Programming Library + Utilities.
9 */
10
17#include <cassert>
18#include <cstdlib> // for malloc and free
19#include <set>
20#include <stack>
21
22#include "Curvature.h"
23#include "WEdge.h"
24
25#include "BLI_math_base.h"
26
28
29namespace Freestyle {
30
31static bool angle_obtuse(WVertex *v, WFace *f)
32{
33 WOEdge *e;
34 f->getOppositeEdge(v, e);
35
36 Vec3r vec1(e->GetaVertex()->GetVertex() - v->GetVertex());
37 Vec3r vec2(e->GetbVertex()->GetVertex() - v->GetVertex());
38 return ((vec1 * vec2) < 0);
39}
40
41// FIXME
42// WVvertex is useless but kept for history reasons
43static bool triangle_obtuse(WVertex * /*v*/, WFace *f)
44{
45 bool b = false;
46 for (int i = 0; i < 3; i++) {
47 b = b || ((f->getEdgeList()[i]->GetVec() * f->getEdgeList()[(i + 1) % 3]->GetVec()) < 0);
48 }
49 return b;
50}
51
52static real cotan(WVertex *vo, WVertex *v1, WVertex *v2)
53{
54 /* cf. Appendix B of [Meyer et al 2002] */
55 real udotv, denom;
56
57 Vec3r u(v1->GetVertex() - vo->GetVertex());
58 Vec3r v(v2->GetVertex() - vo->GetVertex());
59
60 udotv = u * v;
61 denom = sqrt(u.squareNorm() * v.squareNorm() - udotv * udotv);
62
63 /* denom can be zero if u==v. Returning 0 is acceptable, based on the callers of this function
64 * below. */
65 if (denom == 0.0) {
66 return 0.0;
67 }
68 return (udotv / denom);
69}
70
72{
73 /* cf. Appendix B and the caption of Table 1 from [Meyer et al 2002] */
74 real udotv, denom;
75
76 Vec3r u(v1->GetVertex() - vo->GetVertex());
77 Vec3r v(v2->GetVertex() - vo->GetVertex());
78
79 udotv = u * v;
80 denom = sqrt(u.squareNorm() * v.squareNorm() - udotv * udotv);
81
82 /* NOTE(Ray Jones): I assume this is what they mean by using #atan2. */
83
84 /* tan = denom/udotv = y/x (see man page for atan2) */
85 return fabs(atan2(denom, udotv));
86}
87
89{
90 real area = 0.0;
91
92 if (!v) {
93 return false;
94 }
95
96 /* this operator is not defined for boundary edges */
97 if (v->isBoundary()) {
98 return false;
99 }
100
102
103 for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
104 area += (*itE)->GetaFace()->getArea();
105 }
106
107 Kh = Vec3r(0.0, 0.0, 0.0);
108
109 for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
110 WOEdge *e = (*itE)->getPrevOnFace();
111#if 0
112 if ((e->GetaVertex() == v) || (e->GetbVertex() == v)) {
113 cerr << "BUG ";
114 }
115#endif
116 WVertex *v1 = e->GetaVertex();
117 WVertex *v2 = e->GetbVertex();
118 real temp;
119
120 temp = cotan(v1, v, v2);
121 Kh = Vec3r(Kh + temp * (v2->GetVertex() - v->GetVertex()));
122
123 temp = cotan(v2, v, v1);
124 Kh = Vec3r(Kh + temp * (v1->GetVertex() - v->GetVertex()));
125 }
126 if (area > 0.0) {
127 Kh[0] /= 2 * area;
128 Kh[1] /= 2 * area;
129 Kh[2] /= 2 * area;
130 }
131 else {
132 return false;
133 }
134
135 return true;
136}
137
139{
140 real area = 0.0;
141 real angle_sum = 0.0;
142
143 if (!v) {
144 return false;
145 }
146 if (!Kg) {
147 return false;
148 }
149
150 /* this operator is not defined for boundary edges */
151 if (v->isBoundary()) {
152 *Kg = 0.0;
153 return false;
154 }
155
157 for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
158 area += (*itE)->GetaFace()->getArea();
159 }
160
161 for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
162 WOEdge *e = (*itE)->getPrevOnFace();
163 WVertex *v1 = e->GetaVertex();
164 WVertex *v2 = e->GetbVertex();
165 angle_sum += angle_from_cotan(v, v1, v2);
166 }
167
168 *Kg = (2.0 * M_PI - angle_sum) / area;
169
170 return true;
171}
172
174{
175 real temp = Kh * Kh - Kg;
176
177 if (!K1 || !K2) {
178 return;
179 }
180
181 if (temp < 0.0) {
182 temp = 0.0;
183 }
184 temp = sqrt(temp);
185 *K1 = Kh + temp;
186 *K2 = Kh - temp;
187}
188
189/* from Maple */
190static void linsolve(real m11, real m12, real b1, real m21, real m22, real b2, real *x1, real *x2)
191{
192 real temp;
193
194 temp = 1.0 / (m21 * m12 - m11 * m22);
195 *x1 = (m12 * b2 - m22 * b1) * temp;
196 *x2 = (m11 * b2 - m21 * b1) * temp;
197}
198
199/* from Maple - largest eigenvector of [a b; b c] */
200static void eigenvector(real a, real b, real c, Vec3r e)
201{
202 if (b == 0.0) {
203 e[0] = 0.0;
204 }
205 else {
206 e[0] = -(c - a - sqrt(c * c - 2 * a * c + a * a + 4 * b * b)) / (2 * b);
207 }
208 e[1] = 1.0;
209 e[2] = 0.0;
210}
211
213{
214 Vec3r N;
215 real normKh;
216
217 Vec3r basis1, basis2, d, eig;
218 real ve2, vdotN;
219 real aterm_da, bterm_da, cterm_da, const_da;
220 real aterm_db, bterm_db, cterm_db, const_db;
221 real a, b, c;
222 real K1, K2;
223 real *weights, *kappas, *d1s, *d2s;
224 int edge_count;
225 real err_e1, err_e2;
226 int e;
228
229 /* compute unit normal */
230 normKh = Kh.norm();
231
232 if (normKh > 0.0) {
233 Kh.normalize();
234 }
235 else {
236 /* This vertex is a point of zero mean curvature (flat or saddle point). Compute a normal by
237 * averaging the adjacent triangles
238 */
239 N[0] = N[1] = N[2] = 0.0;
240
241 for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
242 N = Vec3r(N + (*itE)->GetaFace()->GetNormal());
243 }
244 real normN = N.norm();
245 if (normN <= 0.0) {
246 return;
247 }
248 N.normalize();
249 }
250
251 /* construct a basis from N: */
252 /* set basis1 to any component not the largest of N */
253 basis1[0] = basis1[1] = basis1[2] = 0.0;
254 if (fabs(N[0]) > fabs(N[1])) {
255 basis1[1] = 1.0;
256 }
257 else {
258 basis1[0] = 1.0;
259 }
260
261 /* make basis2 orthogonal to N */
262 basis2 = (N ^ basis1);
263 basis2.normalize();
264
265 /* make basis1 orthogonal to N and basis2 */
266 basis1 = (N ^ basis2);
267 basis1.normalize();
268
269 aterm_da = bterm_da = cterm_da = const_da = 0.0;
270 aterm_db = bterm_db = cterm_db = const_db = 0.0;
271 int nb_edges = v->GetEdges().size();
272
273 weights = (real *)malloc(sizeof(real) * nb_edges);
274 kappas = (real *)malloc(sizeof(real) * nb_edges);
275 d1s = (real *)malloc(sizeof(real) * nb_edges);
276 d2s = (real *)malloc(sizeof(real) * nb_edges);
277 edge_count = 0;
278
279 for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
280 WOEdge *e;
281 WFace *f1, *f2;
282 real weight, kappa, d1, d2;
283 Vec3r vec_edge;
284 if (!*itE) {
285 continue;
286 }
287 e = *itE;
288
289 /* Since this vertex passed the tests in gts_vertex_mean_curvature_normal(),
290 * this should be true. */
291 // g_assert(gts_edge_face_number (e, s) == 2);
292
293 /* Identify the two triangles bordering e in s. */
294 f1 = e->GetaFace();
295 f2 = e->GetbFace();
296
297 /* We are solving for the values of the curvature tensor
298 * `B = [ a b ; b c ]`.
299 * The computations here are from section 5 of [Meyer et al 2002].
300 *
301 * The first step is to calculate the linear equations governing the values of (a,b,c). These
302 * can be computed by setting the derivatives of the error E to zero (section 5.3).
303 *
304 * Since a + c = norm(Kh), we only compute the linear equations for `dE/da` and `dE/db`.
305 * (NOTE: [Meyer et al 2002] has the equation `a + b = norm(Kh)`,
306 * but I'm almost positive this is incorrect).
307 *
308 * Note that the w_ij (defined in section 5.2) are all scaled by `(1/8*A_mixed)`. We drop this
309 * uniform scale factor because the solution of the linear equations doesn't rely on it.
310 *
311 * The terms of the linear equations are xterm_dy with x in {a,b,c} and y in {a,b}. There are
312 * also const_dy terms that are the constant factors in the equations.
313 */
314
315 /* find the vector from v along edge e */
316 vec_edge = Vec3r(-1 * e->GetVec());
317
318 ve2 = vec_edge.squareNorm();
319 vdotN = vec_edge * N;
320
321 /* section 5.2 - There is a typo in the computation of kappa. The edges should be x_j-x_i. */
322 kappa = 2.0 * vdotN / ve2;
323
324 /* section 5.2 */
325
326 /* I don't like performing a minimization where some of the weights can be negative (as can be
327 * the case if f1 or f2 are obtuse). To ensure all-positive weights, we check for obtuseness.
328 */
329 weight = 0.0;
330 if (!triangle_obtuse(v, f1)) {
331 weight += ve2 *
332 cotan(
333 f1->GetNextOEdge(e->twin())->GetbVertex(), e->GetaVertex(), e->GetbVertex()) /
334 8.0;
335 }
336 else {
337 if (angle_obtuse(v, f1)) {
338 weight += ve2 * f1->getArea() / 4.0;
339 }
340 else {
341 weight += ve2 * f1->getArea() / 8.0;
342 }
343 }
344
345 if (!triangle_obtuse(v, f2)) {
346 weight += ve2 * cotan(f2->GetNextOEdge(e)->GetbVertex(), e->GetaVertex(), e->GetbVertex()) /
347 8.0;
348 }
349 else {
350 if (angle_obtuse(v, f2)) {
351 weight += ve2 * f1->getArea() / 4.0;
352 }
353 else {
354 weight += ve2 * f1->getArea() / 8.0;
355 }
356 }
357
358 /* projection of edge perpendicular to N (section 5.3) */
359 d[0] = vec_edge[0] - vdotN * N[0];
360 d[1] = vec_edge[1] - vdotN * N[1];
361 d[2] = vec_edge[2] - vdotN * N[2];
362 d.normalize();
363
364 /* not explicit in the paper, but necessary. Move d to 2D basis. */
365 d1 = d * basis1;
366 d2 = d * basis2;
367
368 /* store off the curvature, direction of edge, and weights for later use */
369 weights[edge_count] = weight;
370 kappas[edge_count] = kappa;
371 d1s[edge_count] = d1;
372 d2s[edge_count] = d2;
373 edge_count++;
374
375 /* Finally, update the linear equations */
376 aterm_da += weight * d1 * d1 * d1 * d1;
377 bterm_da += weight * d1 * d1 * 2 * d1 * d2;
378 cterm_da += weight * d1 * d1 * d2 * d2;
379 const_da += weight * d1 * d1 * (-kappa);
380
381 aterm_db += weight * d1 * d2 * d1 * d1;
382 bterm_db += weight * d1 * d2 * 2 * d1 * d2;
383 cterm_db += weight * d1 * d2 * d2 * d2;
384 const_db += weight * d1 * d2 * (-kappa);
385 }
386
387 /* now use the identity (Section 5.3) a + c = |Kh| = 2 * kappa_h */
388 aterm_da -= cterm_da;
389 const_da += cterm_da * normKh;
390
391 aterm_db -= cterm_db;
392 const_db += cterm_db * normKh;
393
394 /* check for solvability of the linear system */
395 if (((aterm_da * bterm_db - aterm_db * bterm_da) != 0.0) &&
396 ((const_da != 0.0) || (const_db != 0.0)))
397 {
398 linsolve(aterm_da, bterm_da, -const_da, aterm_db, bterm_db, -const_db, &a, &b);
399
400 c = normKh - a;
401
402 eigenvector(a, b, c, eig);
403 }
404 else {
405 /* region of v is planar */
406 eig[0] = 1.0;
407 eig[1] = 0.0;
408 }
409
410 /* Although the eigenvectors of B are good estimates of the principal directions, it seems that
411 * which one is attached to which curvature direction is a bit arbitrary. This may be a bug in my
412 * implementation, or just a side-effect of the inaccuracy of B due to the discrete nature of the
413 * sampling.
414 *
415 * To overcome this behavior, we'll evaluate which assignment best matches the given eigenvectors
416 * by comparing the curvature estimates computed above and the curvatures calculated from the
417 * discrete differential operators.
418 */
419
420 gts_vertex_principal_curvatures(0.5 * normKh, Kg, &K1, &K2);
421
422 err_e1 = err_e2 = 0.0;
423 /* loop through the values previously saved */
424 for (e = 0; e < edge_count; e++) {
425 real weight, kappa, d1, d2;
426 real temp1, temp2;
427 real delta;
428
429 weight = weights[e];
430 kappa = kappas[e];
431 d1 = d1s[e];
432 d2 = d2s[e];
433
434 temp1 = fabs(eig[0] * d1 + eig[1] * d2);
435 temp1 = temp1 * temp1;
436 temp2 = fabs(eig[1] * d1 - eig[0] * d2);
437 temp2 = temp2 * temp2;
438
439 /* err_e1 is for K1 associated with e1 */
440 delta = K1 * temp1 + K2 * temp2 - kappa;
441 err_e1 += weight * delta * delta;
442
443 /* err_e2 is for K1 associated with e2 */
444 delta = K2 * temp1 + K1 * temp2 - kappa;
445 err_e2 += weight * delta * delta;
446 }
447 free(weights);
448 free(kappas);
449 free(d1s);
450 free(d2s);
451
452 /* rotate eig by a right angle if that would decrease the error */
453 if (err_e2 < err_e1) {
454 real temp = eig[0];
455
456 eig[0] = eig[1];
457 eig[1] = -temp;
458 }
459
460 e1[0] = eig[0] * basis1[0] + eig[1] * basis2[0];
461 e1[1] = eig[0] * basis1[1] + eig[1] * basis2[1];
462 e1[2] = eig[0] * basis1[2] + eig[1] * basis2[2];
463 e1.normalize();
464
465 /* make N,e1,e2 a right handed coordinate system */
466 e2 = N ^ e1;
467 e2.normalize();
468}
469
470namespace OGF {
471
472#if 0
473inline static real angle(WOEdge *h)
474{
475 const Vec3r &n1 = h->GetbFace()->GetNormal();
476 const Vec3r &n2 = h->GetaFace()->GetNormal();
477 const Vec3r v = h->GetVec();
478 real sine = (n1 ^ n2) * v / v.norm();
479 if (sine >= 1.0) {
480 return M_PI_2;
481 }
482 if (sine <= -1.0) {
483 return -M_PI_2;
484 }
485 return ::asin(sine);
486}
487#endif
488
489// precondition1: P is inside the sphere
490// precondition2: P,V points to the outside of the sphere (i.e. OP.V > 0)
491static bool sphere_clip_vector(const Vec3r &O, real r, const Vec3r &P, Vec3r &V)
492{
493 Vec3r W = P - O;
494 real a = V.squareNorm();
495 real b = 2.0 * V * W;
496 real c = W.squareNorm() - r * r;
497 real delta = b * b - 4 * a * c;
498 if (delta < 0) {
499 // Should not happen, but happens sometimes (numerical precision)
500 return true;
501 }
502 real t = -b + ::sqrt(delta) / (2.0 * a);
503 if (t < 0.0) {
504 // Should not happen, but happens sometimes (numerical precision)
505 return true;
506 }
507 if (t >= 1.0) {
508 // Inside the sphere
509 return false;
510 }
511
512 V[0] = (t * V.x());
513 V[1] = (t * V.y());
514 V[2] = (t * V.z());
515
516 return true;
517}
518
519/* TODO: check optimizations:
520 * use marking ? (measure *timings* ...). */
522{
523 // in case we have a non-manifold vertex, skip it...
524 if (start->isBoundary()) {
525 return;
526 }
527
528 std::set<WVertex *> vertices;
529 const Vec3r &O = start->GetVertex();
530 std::stack<WVertex *> S;
531 S.push(start);
532 vertices.insert(start);
533 while (!S.empty()) {
534 WVertex *v = S.top();
535 S.pop();
536 if (v->isBoundary()) {
537 continue;
538 }
539 const Vec3r &P = v->GetVertex();
540 WVertex::incoming_edge_iterator woeit = v->incoming_edges_begin();
541 WVertex::incoming_edge_iterator woeitend = v->incoming_edges_end();
542 for (; woeit != woeitend; ++woeit) {
543 WOEdge *h = *woeit;
544 if ((v == start) || h->GetVec() * (O - P) > 0.0) {
545 Vec3r V(-1 * h->GetVec());
546 bool isect = sphere_clip_vector(O, radius, P, V);
547 assert(h->GetOwner()->GetNumberOfOEdges() ==
548 2); // Because otherwise v->isBoundary() would be true
550
551 if (!isect) {
552 WVertex *w = h->GetaVertex();
553 if (vertices.find(w) == vertices.end()) {
554 vertices.insert(w);
555 S.push(w);
556 }
557 }
558 }
559 }
560 }
561}
562
564{
565 // in case we have a non-manifold vertex, skip it...
566 if (start->isBoundary()) {
567 return;
568 }
569
570 WVertex::incoming_edge_iterator woeit = start->incoming_edges_begin();
571 WVertex::incoming_edge_iterator woeitend = start->incoming_edges_end();
572 for (; woeit != woeitend; ++woeit) {
573 WOEdge *h = (*woeit)->twin();
575 WOEdge *hprev = h->getPrevOnFace();
576 nc.accumulate_dihedral_angle(hprev->GetVec(), hprev->GetAngle());
577 }
578}
579
580} // namespace OGF
581
582} /* namespace Freestyle */
sqrt(x)+1/max(0
void BLI_kdtree_nd_ free(KDTree *tree)
#define M_PI_2
#define M_PI
GTS - Library for the manipulation of triangulated surfaces.
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:125
Classes to define a Winged Edge data structure.
ATTR_WARN_UNUSED_RESULT const BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
void accumulate_dihedral_angle(const Vec3r &edge, real angle, real neigh_area=1.0)
value_type squareNorm() const
Definition VecMat.h:99
Vec< T, N > & normalize()
Definition VecMat.h:104
value_type norm() const
Definition VecMat.h:94
short GetNumberOfOEdges()
Definition WEdge.h:586
float getArea()
Definition WEdge.cpp:409
const vector< WOEdge * > & getEdgeList()
Definition WEdge.h:716
WOEdge * GetNextOEdge(WOEdge *iOEdge)
Definition WEdge.h:856
bool getOppositeEdge(const WVertex *v, WOEdge *&e)
Definition WEdge.cpp:380
WOEdge * twin()
Definition WEdge.cpp:201
WVertex * GetaVertex()
Definition WEdge.h:387
const Vec3f & GetVec()
Definition WEdge.h:412
WVertex * GetbVertex()
Definition WEdge.h:392
WOEdge * getPrevOnFace()
Definition WEdge.cpp:206
WEdge * GetOwner()
Definition WEdge.h:407
const float GetAngle()
Definition WEdge.h:417
Vec3f & GetVertex()
Definition WEdge.h:73
local_group_size(16, 16) .push_constant(Type b
ccl_device_inline float2 fabs(const float2 a)
#define N
VecMat::Vec3< real > Vec3r
Definition Geom.h:30
static bool sphere_clip_vector(const Vec3r &O, real r, const Vec3r &P, Vec3r &V)
void compute_curvature_tensor(WVertex *start, real radius, NormalCycle &nc)
void compute_curvature_tensor_one_ring(WVertex *start, NormalCycle &nc)
inherits from class Rep
Definition AppCanvas.cpp:20
static uint c
Definition RandGen.cpp:87
void gts_vertex_principal_directions(WVertex *v, Vec3r Kh, real Kg, Vec3r &e1, Vec3r &e2)
bool gts_vertex_gaussian_curvature(WVertex *v, real *Kg)
static real angle_from_cotan(WVertex *vo, WVertex *v1, WVertex *v2)
Definition Curvature.cpp:71
bool gts_vertex_mean_curvature_normal(WVertex *v, Vec3r &Kh)
Definition Curvature.cpp:88
static void eigenvector(real a, real b, real c, Vec3r e)
static bool angle_obtuse(WVertex *v, WFace *f)
Definition Curvature.cpp:31
static void linsolve(real m11, real m12, real b1, real m21, real m22, real b2, real *x1, real *x2)
static real cotan(WVertex *vo, WVertex *v1, WVertex *v2)
Definition Curvature.cpp:52
static uint a[3]
Definition RandGen.cpp:82
static bool triangle_obtuse(WVertex *, WFace *f)
Definition Curvature.cpp:43
void gts_vertex_principal_curvatures(real Kh, real Kg, real *K1, real *K2)
double real
Definition Precision.h:14
CCL_NAMESPACE_BEGIN struct Window V