1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is:
17  *     GTS - Library for the manipulation of triangulated surfaces
18  *     Copyright (C) 1999 Stephane Popinet
19  * and:
20  *     OGF/Graphite: Geometry and Graphics Programming Library + Utilities
21  *     Copyright (C) 2000-2003 Bruno Levy
22  *     Contact: Bruno Levy levy@loria.fr
23  *         ISA Project
24  *         LORIA, INRIA Lorraine,
25  *         Campus Scientifique, BP 239
26  *         54506 VANDOEUVRE LES NANCY CEDEX
27  *         FRANCE
28  */
29 
30 /** \file
31  * \ingroup freestyle
32  * \brief GTS - Library for the manipulation of triangulated surfaces
33  * \brief OGF/Graphite: Geometry and Graphics Programming Library + Utilities
34  */
35 
36 #include <assert.h>
37 #include <cstdlib>  // for malloc and free
38 #include <set>
39 #include <stack>
40 
41 #include "Curvature.h"
42 #include "WEdge.h"
43 
44 #include "../geometry/normal_cycle.h"
45 
46 #include "BLI_math.h"
47 
48 namespace Freestyle {
49 
angle_obtuse(WVertex * v,WFace * f)50 static bool angle_obtuse(WVertex *v, WFace *f)
51 {
52   WOEdge *e;
53   f->getOppositeEdge(v, e);
54 
55   Vec3r vec1(e->GetaVertex()->GetVertex() - v->GetVertex());
56   Vec3r vec2(e->GetbVertex()->GetVertex() - v->GetVertex());
57   return ((vec1 * vec2) < 0);
58 }
59 
60 // FIXME
61 // WVvertex is useless but kept for history reasons
triangle_obtuse(WVertex * UNUSED (v),WFace * f)62 static bool triangle_obtuse(WVertex *UNUSED(v), WFace *f)
63 {
64   bool b = false;
65   for (int i = 0; i < 3; i++) {
66     b = b || ((f->getEdgeList()[i]->GetVec() * f->getEdgeList()[(i + 1) % 3]->GetVec()) < 0);
67   }
68   return b;
69 }
70 
cotan(WVertex * vo,WVertex * v1,WVertex * v2)71 static real cotan(WVertex *vo, WVertex *v1, WVertex *v2)
72 {
73   /* cf. Appendix B of [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   /* denom can be zero if u==v.  Returning 0 is acceptable, based on the callers of this function
83    * below. */
84   if (denom == 0.0) {
85     return 0.0;
86   }
87   return (udotv / denom);
88 }
89 
angle_from_cotan(WVertex * vo,WVertex * v1,WVertex * v2)90 static real angle_from_cotan(WVertex *vo, WVertex *v1, WVertex *v2)
91 {
92   /* cf. Appendix B and the caption of Table 1 from [Meyer et al 2002] */
93   real udotv, denom;
94 
95   Vec3r u(v1->GetVertex() - vo->GetVertex());
96   Vec3r v(v2->GetVertex() - vo->GetVertex());
97 
98   udotv = u * v;
99   denom = sqrt(u.squareNorm() * v.squareNorm() - udotv * udotv);
100 
101   /* Note: I assume this is what they mean by using atan2(). -Ray Jones */
102 
103   /* tan = denom/udotv = y/x (see man page for atan2) */
104   return (fabs(atan2(denom, udotv)));
105 }
106 
107 /*! gts_vertex_mean_curvature_normal:
108  *  \param v: a #WVertex.
109  *  \param s: a #GtsSurface.
110  *  \param Kh: the Mean Curvature Normal at \a v.
111  *
112  *  Computes the Discrete Mean Curvature Normal approximation at \a v.
113  *  The mean curvature at \a v is half the magnitude of the vector \a Kh.
114  *
115  *  Note: the normal computed is not unit length, and may point either into or out of the surface,
116  * depending on the curvature at \a v. It is the responsibility of the caller of the function to
117  * use the mean curvature normal appropriately.
118  *
119  *  This approximation is from the paper:
120  *      Discrete Differential-Geometry Operators for Triangulated 2-Manifolds
121  *      Mark Meyer, Mathieu Desbrun, Peter Schroder, Alan H. Barr
122  *      VisMath '02, Berlin (Germany)
123  *      http://www-grail.usc.edu/pubs.html
124  *
125  *  Returns: %true if the operator could be evaluated, %false if the evaluation failed for some
126  * reason (@v is boundary or is the endpoint of a non-manifold edge.)
127  */
gts_vertex_mean_curvature_normal(WVertex * v,Vec3r & Kh)128 bool gts_vertex_mean_curvature_normal(WVertex *v, Vec3r &Kh)
129 {
130   real area = 0.0;
131 
132   if (!v) {
133     return false;
134   }
135 
136   /* this operator is not defined for boundary edges */
137   if (v->isBoundary()) {
138     return false;
139   }
140 
141   WVertex::incoming_edge_iterator itE;
142 
143   for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
144     area += (*itE)->GetaFace()->getArea();
145   }
146 
147   Kh = Vec3r(0.0, 0.0, 0.0);
148 
149   for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
150     WOEdge *e = (*itE)->getPrevOnFace();
151 #if 0
152     if ((e->GetaVertex() == v) || (e->GetbVertex() == v)) {
153       cerr << "BUG ";
154     }
155 #endif
156     WVertex *v1 = e->GetaVertex();
157     WVertex *v2 = e->GetbVertex();
158     real temp;
159 
160     temp = cotan(v1, v, v2);
161     Kh = Vec3r(Kh + temp * (v2->GetVertex() - v->GetVertex()));
162 
163     temp = cotan(v2, v, v1);
164     Kh = Vec3r(Kh + temp * (v1->GetVertex() - v->GetVertex()));
165   }
166   if (area > 0.0) {
167     Kh[0] /= 2 * area;
168     Kh[1] /= 2 * area;
169     Kh[2] /= 2 * area;
170   }
171   else {
172     return false;
173   }
174 
175   return true;
176 }
177 
178 /*! gts_vertex_gaussian_curvature:
179  *  \param v: a #WVertex.
180  *  \param s: a #GtsSurface.
181  *  \param Kg: the Discrete Gaussian Curvature approximation at \a v.
182  *
183  *  Computes the Discrete Gaussian Curvature approximation at \a v.
184  *
185  *  This approximation is from the paper:
186  *      Discrete Differential-Geometry Operators for Triangulated 2-Manifolds
187  *      Mark Meyer, Mathieu Desbrun, Peter Schroder, Alan H. Barr
188  *      VisMath '02, Berlin (Germany)
189  *      http://www-grail.usc.edu/pubs.html
190  *
191  *  Returns: %true if the operator could be evaluated, %false if the evaluation failed for some
192  * reason (@v is boundary or is the endpoint of a non-manifold edge.)
193  */
gts_vertex_gaussian_curvature(WVertex * v,real * Kg)194 bool gts_vertex_gaussian_curvature(WVertex *v, real *Kg)
195 {
196   real area = 0.0;
197   real angle_sum = 0.0;
198 
199   if (!v) {
200     return false;
201   }
202   if (!Kg) {
203     return false;
204   }
205 
206   /* this operator is not defined for boundary edges */
207   if (v->isBoundary()) {
208     *Kg = 0.0;
209     return false;
210   }
211 
212   WVertex::incoming_edge_iterator itE;
213   for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
214     area += (*itE)->GetaFace()->getArea();
215   }
216 
217   for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
218     WOEdge *e = (*itE)->getPrevOnFace();
219     WVertex *v1 = e->GetaVertex();
220     WVertex *v2 = e->GetbVertex();
221     angle_sum += angle_from_cotan(v, v1, v2);
222   }
223 
224   *Kg = (2.0 * M_PI - angle_sum) / area;
225 
226   return true;
227 }
228 
229 /*! gts_vertex_principal_curvatures:
230  *  @Kh: mean curvature.
231  *  @Kg: Gaussian curvature.
232  *  @K1: first principal curvature.
233  *  @K2: second principal curvature.
234  *
235  *  Computes the principal curvatures at a point given the mean and Gaussian curvatures at that
236  * point.
237  *
238  *  The mean curvature can be computed as one-half the magnitude of the vector computed by
239  *  gts_vertex_mean_curvature_normal().
240  *
241  *  The Gaussian curvature can be computed with gts_vertex_gaussian_curvature().
242  */
gts_vertex_principal_curvatures(real Kh,real Kg,real * K1,real * K2)243 void gts_vertex_principal_curvatures(real Kh, real Kg, real *K1, real *K2)
244 {
245   real temp = Kh * Kh - Kg;
246 
247   if (!K1 || !K2) {
248     return;
249   }
250 
251   if (temp < 0.0) {
252     temp = 0.0;
253   }
254   temp = sqrt(temp);
255   *K1 = Kh + temp;
256   *K2 = Kh - temp;
257 }
258 
259 /* from Maple */
linsolve(real m11,real m12,real b1,real m21,real m22,real b2,real * x1,real * x2)260 static void linsolve(real m11, real m12, real b1, real m21, real m22, real b2, real *x1, real *x2)
261 {
262   real temp;
263 
264   temp = 1.0 / (m21 * m12 - m11 * m22);
265   *x1 = (m12 * b2 - m22 * b1) * temp;
266   *x2 = (m11 * b2 - m21 * b1) * temp;
267 }
268 
269 /* from Maple - largest eigenvector of [a b; b c] */
eigenvector(real a,real b,real c,Vec3r e)270 static void eigenvector(real a, real b, real c, Vec3r e)
271 {
272   if (b == 0.0) {
273     e[0] = 0.0;
274   }
275   else {
276     e[0] = -(c - a - sqrt(c * c - 2 * a * c + a * a + 4 * b * b)) / (2 * b);
277   }
278   e[1] = 1.0;
279   e[2] = 0.0;
280 }
281 
282 /*! gts_vertex_principal_directions:
283  *  \param v: a #WVertex.
284  *  \param s: a #GtsSurface.
285  *  \param Kh: mean curvature normal (a #Vec3r).
286  *  \param Kg: Gaussian curvature (a real).
287  *  \param e1: first principal curvature direction (direction of largest curvature).
288  *  \param e2: second principal curvature direction.
289  *
290  *  Computes the principal curvature directions at a point given \a Kh and \a Kg,
291  *  the mean curvature normal and Gaussian curvatures at that point, computed with
292  *  gts_vertex_mean_curvature_normal() and gts_vertex_gaussian_curvature(), respectively.
293  *
294  *  Note that this computation is very approximate and tends to be unstable. Smoothing of the
295  * surface or the principal directions may be necessary to achieve reasonable results.
296  */
gts_vertex_principal_directions(WVertex * v,Vec3r Kh,real Kg,Vec3r & e1,Vec3r & e2)297 void gts_vertex_principal_directions(WVertex *v, Vec3r Kh, real Kg, Vec3r &e1, Vec3r &e2)
298 {
299   Vec3r N;
300   real normKh;
301 
302   Vec3r basis1, basis2, d, eig;
303   real ve2, vdotN;
304   real aterm_da, bterm_da, cterm_da, const_da;
305   real aterm_db, bterm_db, cterm_db, const_db;
306   real a, b, c;
307   real K1, K2;
308   real *weights, *kappas, *d1s, *d2s;
309   int edge_count;
310   real err_e1, err_e2;
311   int e;
312   WVertex::incoming_edge_iterator itE;
313 
314   /* compute unit normal */
315   normKh = Kh.norm();
316 
317   if (normKh > 0.0) {
318     Kh.normalize();
319   }
320   else {
321     /* This vertex is a point of zero mean curvature (flat or saddle point). Compute a normal by
322      * averaging the adjacent triangles
323      */
324     N[0] = N[1] = N[2] = 0.0;
325 
326     for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
327       N = Vec3r(N + (*itE)->GetaFace()->GetNormal());
328     }
329     real normN = N.norm();
330     if (normN <= 0.0) {
331       return;
332     }
333     N.normalize();
334   }
335 
336   /* construct a basis from N: */
337   /* set basis1 to any component not the largest of N */
338   basis1[0] = basis1[1] = basis1[2] = 0.0;
339   if (fabs(N[0]) > fabs(N[1])) {
340     basis1[1] = 1.0;
341   }
342   else {
343     basis1[0] = 1.0;
344   }
345 
346   /* make basis2 orthogonal to N */
347   basis2 = (N ^ basis1);
348   basis2.normalize();
349 
350   /* make basis1 orthogonal to N and basis2 */
351   basis1 = (N ^ basis2);
352   basis1.normalize();
353 
354   aterm_da = bterm_da = cterm_da = const_da = 0.0;
355   aterm_db = bterm_db = cterm_db = const_db = 0.0;
356   int nb_edges = v->GetEdges().size();
357 
358   weights = (real *)malloc(sizeof(real) * nb_edges);
359   kappas = (real *)malloc(sizeof(real) * nb_edges);
360   d1s = (real *)malloc(sizeof(real) * nb_edges);
361   d2s = (real *)malloc(sizeof(real) * nb_edges);
362   edge_count = 0;
363 
364   for (itE = v->incoming_edges_begin(); itE != v->incoming_edges_end(); itE++) {
365     WOEdge *e;
366     WFace *f1, *f2;
367     real weight, kappa, d1, d2;
368     Vec3r vec_edge;
369     if (!*itE) {
370       continue;
371     }
372     e = *itE;
373 
374     /* since this vertex passed the tests in gts_vertex_mean_curvature_normal(), this should be
375      * true. */
376     // g_assert(gts_edge_face_number (e, s) == 2);
377 
378     /* identify the two triangles bordering e in s */
379     f1 = e->GetaFace();
380     f2 = e->GetbFace();
381 
382     /* We are solving for the values of the curvature tensor
383      *     B = [ a b ; b c ].
384      *  The computations here are from section 5 of [Meyer et al 2002].
385      *
386      *  The first step is to calculate the linear equations governing the values of (a,b,c). These
387      * can be computed by setting the derivatives of the error E to zero (section 5.3).
388      *
389      *  Since a + c = norm(Kh), we only compute the linear equations for dE/da and dE/db. (NB:
390      * [Meyer et al 2002] has the equation a + b = norm(Kh), but I'm almost positive this is
391      * incorrect).
392      *
393      *  Note that the w_ij (defined in section 5.2) are all scaled by (1/8*A_mixed). We drop this
394      * uniform scale factor because the solution of the linear equations doesn't rely on it.
395      *
396      *  The terms of the linear equations are xterm_dy with x in {a,b,c} and y in {a,b}. There are
397      * also const_dy terms that are the constant factors in the equations.
398      */
399 
400     /* find the vector from v along edge e */
401     vec_edge = Vec3r(-1 * e->GetVec());
402 
403     ve2 = vec_edge.squareNorm();
404     vdotN = vec_edge * N;
405 
406     /* section 5.2 - There is a typo in the computation of kappa. The edges should be x_j-x_i. */
407     kappa = 2.0 * vdotN / ve2;
408 
409     /* section 5.2 */
410 
411     /* I don't like performing a minimization where some of the weights can be negative (as can be
412      * the case if f1 or f2 are obtuse). To ensure all-positive weights, we check for obtuseness.
413      */
414     weight = 0.0;
415     if (!triangle_obtuse(v, f1)) {
416       weight += ve2 *
417                 cotan(
418                     f1->GetNextOEdge(e->twin())->GetbVertex(), e->GetaVertex(), e->GetbVertex()) /
419                 8.0;
420     }
421     else {
422       if (angle_obtuse(v, f1)) {
423         weight += ve2 * f1->getArea() / 4.0;
424       }
425       else {
426         weight += ve2 * f1->getArea() / 8.0;
427       }
428     }
429 
430     if (!triangle_obtuse(v, f2)) {
431       weight += ve2 * cotan(f2->GetNextOEdge(e)->GetbVertex(), e->GetaVertex(), e->GetbVertex()) /
432                 8.0;
433     }
434     else {
435       if (angle_obtuse(v, f2)) {
436         weight += ve2 * f1->getArea() / 4.0;
437       }
438       else {
439         weight += ve2 * f1->getArea() / 8.0;
440       }
441     }
442 
443     /* projection of edge perpendicular to N (section 5.3) */
444     d[0] = vec_edge[0] - vdotN * N[0];
445     d[1] = vec_edge[1] - vdotN * N[1];
446     d[2] = vec_edge[2] - vdotN * N[2];
447     d.normalize();
448 
449     /* not explicit in the paper, but necessary. Move d to 2D basis. */
450     d1 = d * basis1;
451     d2 = d * basis2;
452 
453     /* store off the curvature, direction of edge, and weights for later use */
454     weights[edge_count] = weight;
455     kappas[edge_count] = kappa;
456     d1s[edge_count] = d1;
457     d2s[edge_count] = d2;
458     edge_count++;
459 
460     /* Finally, update the linear equations */
461     aterm_da += weight * d1 * d1 * d1 * d1;
462     bterm_da += weight * d1 * d1 * 2 * d1 * d2;
463     cterm_da += weight * d1 * d1 * d2 * d2;
464     const_da += weight * d1 * d1 * (-kappa);
465 
466     aterm_db += weight * d1 * d2 * d1 * d1;
467     bterm_db += weight * d1 * d2 * 2 * d1 * d2;
468     cterm_db += weight * d1 * d2 * d2 * d2;
469     const_db += weight * d1 * d2 * (-kappa);
470   }
471 
472   /* now use the identity (Section 5.3) a + c = |Kh| = 2 * kappa_h */
473   aterm_da -= cterm_da;
474   const_da += cterm_da * normKh;
475 
476   aterm_db -= cterm_db;
477   const_db += cterm_db * normKh;
478 
479   /* check for solvability of the linear system */
480   if (((aterm_da * bterm_db - aterm_db * bterm_da) != 0.0) &&
481       ((const_da != 0.0) || (const_db != 0.0))) {
482     linsolve(aterm_da, bterm_da, -const_da, aterm_db, bterm_db, -const_db, &a, &b);
483 
484     c = normKh - a;
485 
486     eigenvector(a, b, c, eig);
487   }
488   else {
489     /* region of v is planar */
490     eig[0] = 1.0;
491     eig[1] = 0.0;
492   }
493 
494   /* Although the eigenvectors of B are good estimates of the principal directions, it seems that
495    * which one is attached to which curvature direction is a bit arbitrary. This may be a bug in my
496    * implementation, or just a side-effect of the inaccuracy of B due to the discrete nature of the
497    * sampling.
498    *
499    * To overcome this behavior, we'll evaluate which assignment best matches the given eigenvectors
500    * by comparing the curvature estimates computed above and the curvatures calculated from the
501    * discrete differential operators.
502    */
503 
504   gts_vertex_principal_curvatures(0.5 * normKh, Kg, &K1, &K2);
505 
506   err_e1 = err_e2 = 0.0;
507   /* loop through the values previously saved */
508   for (e = 0; e < edge_count; e++) {
509     real weight, kappa, d1, d2;
510     real temp1, temp2;
511     real delta;
512 
513     weight = weights[e];
514     kappa = kappas[e];
515     d1 = d1s[e];
516     d2 = d2s[e];
517 
518     temp1 = fabs(eig[0] * d1 + eig[1] * d2);
519     temp1 = temp1 * temp1;
520     temp2 = fabs(eig[1] * d1 - eig[0] * d2);
521     temp2 = temp2 * temp2;
522 
523     /* err_e1 is for K1 associated with e1 */
524     delta = K1 * temp1 + K2 * temp2 - kappa;
525     err_e1 += weight * delta * delta;
526 
527     /* err_e2 is for K1 associated with e2 */
528     delta = K2 * temp1 + K1 * temp2 - kappa;
529     err_e2 += weight * delta * delta;
530   }
531   free(weights);
532   free(kappas);
533   free(d1s);
534   free(d2s);
535 
536   /* rotate eig by a right angle if that would decrease the error */
537   if (err_e2 < err_e1) {
538     real temp = eig[0];
539 
540     eig[0] = eig[1];
541     eig[1] = -temp;
542   }
543 
544   e1[0] = eig[0] * basis1[0] + eig[1] * basis2[0];
545   e1[1] = eig[0] * basis1[1] + eig[1] * basis2[1];
546   e1[2] = eig[0] * basis1[2] + eig[1] * basis2[2];
547   e1.normalize();
548 
549   /* make N,e1,e2 a right handed coordinate system */
550   e2 = N ^ e1;
551   e2.normalize();
552 }
553 
554 namespace OGF {
555 
556 #if 0
557 inline static real angle(WOEdge *h)
558 {
559   const Vec3r &n1 = h->GetbFace()->GetNormal();
560   const Vec3r &n2 = h->GetaFace()->GetNormal();
561   const Vec3r v = h->GetVec();
562   real sine = (n1 ^ n2) * v / v.norm();
563   if (sine >= 1.0) {
564     return M_PI / 2.0;
565   }
566   if (sine <= -1.0) {
567     return -M_PI / 2.0;
568   }
569   return ::asin(sine);
570 }
571 #endif
572 
573 // precondition1: P is inside the sphere
574 // precondition2: P,V points to the outside of the sphere (i.e. OP.V > 0)
sphere_clip_vector(const Vec3r & O,real r,const Vec3r & P,Vec3r & V)575 static bool sphere_clip_vector(const Vec3r &O, real r, const Vec3r &P, Vec3r &V)
576 {
577   Vec3r W = P - O;
578   real a = V.squareNorm();
579   real b = 2.0 * V * W;
580   real c = W.squareNorm() - r * r;
581   real delta = b * b - 4 * a * c;
582   if (delta < 0) {
583     // Should not happen, but happens sometimes (numerical precision)
584     return true;
585   }
586   real t = -b + ::sqrt(delta) / (2.0 * a);
587   if (t < 0.0) {
588     // Should not happen, but happens sometimes (numerical precision)
589     return true;
590   }
591   if (t >= 1.0) {
592     // Inside the sphere
593     return false;
594   }
595 
596   V[0] = (t * V.x());
597   V[1] = (t * V.y());
598   V[2] = (t * V.z());
599 
600   return true;
601 }
602 
603 // TODO: check optimizations:
604 // use marking ? (measure *timings* ...)
compute_curvature_tensor(WVertex * start,real radius,NormalCycle & nc)605 void compute_curvature_tensor(WVertex *start, real radius, NormalCycle &nc)
606 {
607   // in case we have a non-manifold vertex, skip it...
608   if (start->isBoundary()) {
609     return;
610   }
611 
612   std::set<WVertex *> vertices;
613   const Vec3r &O = start->GetVertex();
614   std::stack<WVertex *> S;
615   S.push(start);
616   vertices.insert(start);
617   while (!S.empty()) {
618     WVertex *v = S.top();
619     S.pop();
620     if (v->isBoundary()) {
621       continue;
622     }
623     const Vec3r &P = v->GetVertex();
624     WVertex::incoming_edge_iterator woeit = v->incoming_edges_begin();
625     WVertex::incoming_edge_iterator woeitend = v->incoming_edges_end();
626     for (; woeit != woeitend; ++woeit) {
627       WOEdge *h = *woeit;
628       if ((v == start) || h->GetVec() * (O - P) > 0.0) {
629         Vec3r V(-1 * h->GetVec());
630         bool isect = sphere_clip_vector(O, radius, P, V);
631         assert(h->GetOwner()->GetNumberOfOEdges() ==
632                2);  // Because otherwise v->isBoundary() would be true
633         nc.accumulate_dihedral_angle(V, h->GetAngle());
634 
635         if (!isect) {
636           WVertex *w = h->GetaVertex();
637           if (vertices.find(w) == vertices.end()) {
638             vertices.insert(w);
639             S.push(w);
640           }
641         }
642       }
643     }
644   }
645 }
646 
compute_curvature_tensor_one_ring(WVertex * start,NormalCycle & nc)647 void compute_curvature_tensor_one_ring(WVertex *start, NormalCycle &nc)
648 {
649   // in case we have a non-manifold vertex, skip it...
650   if (start->isBoundary()) {
651     return;
652   }
653 
654   WVertex::incoming_edge_iterator woeit = start->incoming_edges_begin();
655   WVertex::incoming_edge_iterator woeitend = start->incoming_edges_end();
656   for (; woeit != woeitend; ++woeit) {
657     WOEdge *h = (*woeit)->twin();
658     nc.accumulate_dihedral_angle(h->GetVec(), h->GetAngle());
659     WOEdge *hprev = h->getPrevOnFace();
660     nc.accumulate_dihedral_angle(hprev->GetVec(), hprev->GetAngle());
661   }
662 }
663 
664 }  // namespace OGF
665 
666 } /* namespace Freestyle */
667