MEPP2 Project
QEM_3D.h
Go to the documentation of this file.
1 // Copyright (c) 2012-2022 University of Lyon and CNRS (France).
2 // All rights reserved.
3 //
4 // This file is part of MEPP2; you can redistribute it and/or modify
5 // it under the terms of the GNU Lesser General Public License as
6 // published by the Free Software Foundation; either version 3 of
7 // the License, or (at your option) any later version.
8 //
9 // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
10 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11 #pragma once
12 
13 #include "Error_metric.h"
14 
15 #include <Eigen/Dense>
16 
17 #include <map>
18 #include <tuple>
19 
20 namespace FEVV {
21 namespace Filters {
22 
25 template< typename HalfedgeGraph,
26  typename PointMap >
27 class QEM_3D : public Error_metric< HalfedgeGraph,
28  PointMap >
29 {
30 public:
34  typename boost::graph_traits< HalfedgeGraph >::halfedge_descriptor;
35  using edge_iterator =
36  typename boost::graph_traits< HalfedgeGraph >::edge_iterator;
38  typename boost::graph_traits< HalfedgeGraph >::edge_descriptor;
40  typename boost::graph_traits< HalfedgeGraph >::face_descriptor;
44 
45  typedef Error_metric< HalfedgeGraph,
46  PointMap > Super_class;
47 
48  typedef Eigen::MatrixXd Matrix;
49  typedef Eigen::VectorXd VectorX;
50  typedef typename std::tuple< Matrix, VectorX, double > Quadric;
51 
52 
54  HalfedgeGraph &g,
55  PointMap &pm,
57  *vkept,
59  &dequantiz)
60  : Super_class(g, pm, vkept, dequantiz)
61  {
62  }
63  ~QEM_3D(){}
64 
65  virtual void compute_error() override
66  {
67  if(!Super_class::_queue.empty())
68  {
69  typename Super_class::priority_queue_edges empty2;
70  std::swap(Super_class::_queue, empty2);
71  }
73  typename Super_class::edge2cost_map empty;
74  std::swap(Super_class::_edges_cost, empty);
75  if(Super_class::_edges_cost.empty())
76  {
77  faces_quadrics.clear();
78  auto face_pair = faces(Super_class::_g);
79  auto face_it = face_pair.first;
80  auto face_it_end = face_pair.second;
81  // Compute every face quadric
82  for( ; face_it != face_it_end; ++face_it)
83  {
84  Quadric curr_quadric = compute_face_quadric(*face_it);
85  faces_quadrics.emplace(*face_it, curr_quadric);
86  }
87 
88  auto vertices_pair = vertices(Super_class::_g);
89  auto vertices_it = vertices_pair.first;
90  auto vertices_it_end = vertices_pair.second;
91  // Compute every vertex quadric
92  for( ; vertices_it != vertices_it_end; ++vertices_it)
93  {
94  Quadric curr_vertex_quadric = compute_vertex_quadric(*vertices_it);
95  vertices_quadrics.emplace(*vertices_it, curr_vertex_quadric);
96  }
97  Super_class::_pm = get(boost::vertex_point, Super_class::_g);
98 
99  auto edge_iterator_pair = edges(Super_class::_g);
100  auto edge_ite = edge_iterator_pair.first;
101  int count = 0;
103  for( ; edge_ite != edge_iterator_pair.second;
104  ++edge_ite)
105  {
106  Point collapsePos;
107  collapsePos = Super_class::_vkept->compute_position(*edge_ite);
108  double weight = compute_cost_edge(*edge_ite, collapsePos);
109 
110  Super_class::_threshold += weight;
111  ++count;
113  Super_class::_queue.push(
114  std::make_tuple(*edge_ite, weight, collapsePos));
115  Super_class::_edges_cost.emplace(
116  *edge_ite, std::make_pair(weight, collapsePos));
117  }
118  Super_class::_threshold /= count;
119  }
120  }
121 
122  double compute_cost_edge(edge_descriptor e, const Point &collapsePos) override
123  {
124  auto current_halfedge = halfedge(e, Super_class::_g);
125 
126  vertex_descriptor v1 = target(current_halfedge, Super_class::_g);
127  vertex_descriptor v2 = source(current_halfedge, Super_class::_g);
128  Point dequantized_collapsePos =
129  Super_class::_dequantiz.dequantize(collapsePos);
130  Eigen::Vector4d v =
131  Eigen::Vector4d(Super_class::_gt.get_x(dequantized_collapsePos),
132  Super_class::_gt.get_y(dequantized_collapsePos),
133  Super_class::_gt.get_z(dequantized_collapsePos),
134  1);
135  auto vt = v.transpose();
136 
137  Matrix Q(4, 4);
138  Quadric Q1 = vertices_quadrics[v1];
139  Quadric Q2 = vertices_quadrics[v2];
140  Matrix A1 = std::get< 0 >(Q1);
141  Matrix A2 = std::get< 0 >(Q2);
142  VectorX B1 = std::get< 1 >(Q1);
143  VectorX B2 = std::get< 1 >(Q2);
144  double d1 = std::get< 2 >(Q1);
145  double d2 = std::get< 2 >(Q2);
146  for(int i = 0; i < 3; i++)
147  {
148  for(int j = 0; j < 3; j++)
149  {
150  Q(i, j) = A1(i, j) + A2(i, j);
151  }
152  }
153  for(int k = 0; k < 3; k++)
154  {
155  Q(3, k) = B1(k) + B2(k);
156  Q(k, 3) = B1(k) + B2(k);
157  }
158  Q(3, 3) = d1 + d2;
159  Matrix VtQ = vt * Q;
160  Matrix VtQV = VtQ * v;
161  double cost = VtQV(0, 0);
162  return cost;
163  }
164 
165  std::string get_as_string() const override { return "QEM_3D"; }
166 
167 protected:
168  std::map< face_descriptor, Quadric > faces_quadrics;
169 
170  std::map< vertex_descriptor, Quadric > vertices_quadrics;
171 
172 
173  double compute_triangle_area(const Point &p1, const Point &p2, const Point &p3) const
174  {
175  auto a2 = Super_class::_gt.length2(Super_class::_gt.sub_p(p2, p1));
176  auto b2 = Super_class::_gt.length2(Super_class::_gt.sub_p(p3, p2));
177  auto c2 = Super_class::_gt.length2(Super_class::_gt.sub_p(p1, p3));
178  double area2 = (2*(a2*b2 + b2*c2 + a2*c2) - (a2*a2 + b2*b2 + c2*c2))*0.0625f; // expanded Heron's formula
179  return sqrt(area2);
180  }
181 
183  {
186  const Point& p1 = get(Super_class::_pm, source(h1, Super_class::_g));
187  const Point& p2 = get(Super_class::_pm, target(h1, Super_class::_g));
188  const Point& p3 = get(Super_class::_pm, target(h2, Super_class::_g));
192 
193  return compute_triangle_area(p1_tmp, p2_tmp, p3_tmp);
194  }
195 
196  std::vector< double > get_plane_equation(face_descriptor f) const
197  {
200 
201  // get points a the face and dequantize them
202  const Point& p1 = get(Super_class::_pm, source(h1, Super_class::_g));
203  const Point& p2 = get(Super_class::_pm, target(h1, Super_class::_g));
204  const Point& p3 = get(Super_class::_pm, target(h2, Super_class::_g));
205 
209 
210 
211  auto x1 = Super_class::_gt.get_x(p1_tmp);
212  auto y1 = Super_class::_gt.get_y(p1_tmp);
213  auto z1 = Super_class::_gt.get_z(p1_tmp);
214 
215  auto x2 = Super_class::_gt.get_x(p2_tmp);
216  auto y2 = Super_class::_gt.get_y(p2_tmp);
217  auto z2 = Super_class::_gt.get_z(p2_tmp);
218 
219  auto x3 = Super_class::_gt.get_x(p3_tmp);
220  auto y3 = Super_class::_gt.get_y(p3_tmp);
221  auto z3 = Super_class::_gt.get_z(p3_tmp);
222 
223  // compute equation of a plane: compute normal vector to the plane
224  Eigen::Vector3d v1;
225  v1 << x2 - x1, y2 - y1, z2 - z1;
226  Eigen::Vector3d v2;
227  v2 << x3 - x1, y3 - y1, z3 - z1;
228  // cross product computes the normal vector to the plane
229  Eigen::Vector3d cp = v1.cross(v2);
230 
231  // normalize the vector
232  cp.normalize();
233  if(cp.hasNaN())
234  {
235  cp = Eigen::Vector3d(0, 0, 0);
236  }
237  double a = cp[0];
238  double b = cp[1];
239  double c = cp[2];
240  double d = -(a * x1 + b * y1 + c * z1);
241  std::vector< double > plane_eq;
242  plane_eq.reserve(4);
243  plane_eq.push_back(a);
244  plane_eq.push_back(b);
245  plane_eq.push_back(c);
246  plane_eq.push_back(d);
247  return plane_eq;
248  }
249 
250 
252  {
253  // the quadric of a face is a representation of the equation of this place
254  std::vector< double > plane_eq = get_plane_equation(f);
255  Eigen::Vector3d n(plane_eq[0], plane_eq[1], plane_eq[2]);
256 
257  double d = plane_eq[3];
258 
259  Matrix Af(3, 3);
260  VectorX Bf(3);
261  double Cf = d * d;
262 
263  for(size_t i = 0; i < 3; i++)
264  {
265  for(size_t j = 0; j < 3; j++)
266  {
267  Af(i, j) = n(i) * n(j);
268  }
269  }
270  Bf[0] = d * n(0);
271  Bf[1] = d * n(1);
272  Bf[2] = d * n(2);
273 
274  /* the quadric takes the form as a matrix:
275  |a^2 ab ac ad|
276  |ab b^2 bc bd|
277  |ac bc c^2 cd|
278  |ad bd cd d^2|
279  A = |a^2 ab ac |
280  |ab b^2 bc |
281  |ac bc c^2|
282  B = |ad bd cd |
283  C = d^2
284  */
285 
286 
287  return std::make_tuple(Af, Bf, Cf);
288  }
289 
291  {
292  // The quadric of a vertex is the sum of the quadrics of its adjacent faces.
293  boost::iterator_range<
294  CGAL::Halfedge_around_target_iterator< HalfedgeGraph > >
295  iterator_range = CGAL::halfedges_around_target(v, Super_class::_g);
296 
297  Matrix M(3, 3);
298  VectorX V(3);
299  double D = 0;
300  M << 0, 0, 0, 0, 0, 0, 0, 0, 0;
301  V << 0, 0, 0;
302  // get every adjacent quadric
303  for(auto h_v : iterator_range)
304  {
306  // avoid border case
307  if(f != boost::graph_traits< HalfedgeGraph >::null_face())
308  {
309  auto it_curr_quadric = faces_quadrics.find(f);
310  if(it_curr_quadric != faces_quadrics.end())
311  {
312  // if a quadric has been found (no border case)
314  M = M + area * std::get< 0 >(it_curr_quadric->second);
315  V = V + area * std::get< 1 >(it_curr_quadric->second);
316  D = D + area * std::get< 2 >(it_curr_quadric->second);
317  }
318  }
319  if(f == boost::graph_traits< HalfedgeGraph >::null_face() ||
320  CGAL::is_border(opposite(h_v, Super_class::_g), Super_class::_g))
321  {
322  // border case: create a perpendicular plane
324  face_descriptor f_opp;
325  if(f == boost::graph_traits< HalfedgeGraph >::null_face())
326  {
327  f_opp = face(h_v_opp, Super_class::_g);
328  }
329  else
330  {
331  f_opp = face(h_v, Super_class::_g);
332  }
333 
334  std::vector< double > plane_eq_opp = get_plane_equation(f_opp);
335  Eigen::Vector3d n(plane_eq_opp[0], plane_eq_opp[1], plane_eq_opp[2]);
336 
337  const Point& p1 = get(Super_class::_pm, source(h_v_opp, Super_class::_g));
338  const Point& p2 = get(Super_class::_pm, target(h_v_opp, Super_class::_g));
339 
342  double area = Super_class::_gt.length2(Super_class::_gt.sub_p(p2_tmp, p1_tmp)) * std::sqrt(3)/2.; // equilateral triangle area x 2
343 
344  auto x1 = Super_class::_gt.get_x(p1_tmp);
345  auto y1 = Super_class::_gt.get_y(p1_tmp);
346  auto z1 = Super_class::_gt.get_z(p1_tmp);
347 
348  auto x2 = Super_class::_gt.get_x(p2_tmp);
349  auto y2 = Super_class::_gt.get_y(p2_tmp);
350  auto z2 = Super_class::_gt.get_z(p2_tmp);
351 
352  Eigen::Vector3d v1;
353  v1 << x2 - x1, y2 - y1, z2 - z1;
354  Eigen::Vector3d v2;
355  v2 << x1 + n(0), y1 + n(1), z1 + n(2);
356  // equation of perpendicular plane
357  Eigen::Vector3d perpendicular_equation = v1.cross(v2);
358 
359  perpendicular_equation.normalize();
360  if(perpendicular_equation.hasNaN())
361  {
362  perpendicular_equation = Eigen::Vector3d(0, 0, 0);
363  }
364  double a = perpendicular_equation[0];
365  double b = perpendicular_equation[1];
366  double c = perpendicular_equation[2];
367  double d = -(a * x1 + b * y1 + c * z1);
368 
369  Matrix Af(3, 3);
370  VectorX Bf(3);
371  double Cf = d * d;
372 
373  for(size_t i = 0; i < 3; i++)
374  {
375  for(size_t j = 0; j < 3; j++)
376  {
377  Af(i, j) = perpendicular_equation(i) * perpendicular_equation(j);
378  }
379  }
380  Bf[0] = d * perpendicular_equation(0);
381  Bf[1] = d * perpendicular_equation(1);
382  Bf[2] = d * perpendicular_equation(2);
383  M = M + area * Af;
384  V = V + area * Bf;
385  D = D + area * Cf;
386  }
387  }
388  return std::make_tuple(M, V, D);
389  }
390 };
391 } // namespace Filters
392 } // namespace FEVV
FEVV::Filters::Error_metric::_queue
priority_queue_edges _queue
Definition: Error_metric.h:208
FEVV::Filters::Error_metric::_threshold
double _threshold
Definition: Error_metric.h:214
FEVV::DataStructures::AIF::vertices
std::pair< typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::vertex_iterator, typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::vertex_iterator > vertices(const FEVV::DataStructures::AIF::AIFMesh &sm)
Returns the iterator range of the vertices of the mesh.
Definition: Graph_traits_aif.h:172
FEVV::Filters::Error_metric::halfedge_descriptor
typename boost::graph_traits< HalfedgeGraph >::halfedge_descriptor halfedge_descriptor
Definition: Error_metric.h:67
FEVV::Filters::QEM_3D::compute_triangle_area
double compute_triangle_area(const Point &p1, const Point &p2, const Point &p3) const
Definition: QEM_3D.h:173
FEVV::DataStructures::AIF::next
boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::halfedge_descriptor next(typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::halfedge_descriptor h, const FEVV::DataStructures::AIF::AIFMesh &sm)
Returns the next halfedge around its face.
Definition: Graph_traits_aif.h:599
FEVV::Filters::Error_metric::_edges_cost
edge2cost_map _edges_cost
queue with the cost/weight as the key
Definition: Error_metric.h:209
FEVV::Filters::QEM_3D::~QEM_3D
~QEM_3D()
Definition: QEM_3D.h:63
FEVV::Filters::QEM_3D::compute_vertex_quadric
virtual Quadric compute_vertex_quadric(vertex_descriptor v) const
Definition: QEM_3D.h:290
FEVV::Filters::Error_metric::_pm
PointMap & _pm
Definition: Error_metric.h:206
FEVV::Filters::Error_metric::Point
typename FEVV::Geometry_traits< HalfedgeGraph >::Point Point
Definition: Error_metric.h:73
FEVV::Filters::QEM_3D::Quadric
std::tuple< Matrix, VectorX, double > Quadric
Definition: QEM_3D.h:50
FEVV::Filters::QEM_3D::Matrix
Eigen::MatrixXd Matrix
Definition: QEM_3D.h:48
FEVV::Filters::QEM_3D::compute_error
virtual void compute_error() override
Definition: QEM_3D.h:65
FEVV::DataStructures::AIF::edges
std::pair< typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::edge_iterator, typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::edge_iterator > edges(const FEVV::DataStructures::AIF::AIFMesh &sm)
Returns the iterator range of the edges of the mesh.
Definition: Graph_traits_aif.h:238
FEVV::Filters::QEM_3D::get_as_string
std::string get_as_string() const override
Definition: QEM_3D.h:165
Error_metric.h
FEVV::Filters::QEM_3D
Concrete class to compute the collapse cost of each edge in a mesh as the memoryless variant of QEM e...
Definition: QEM_3D.h:29
FEVV::GeometryTraits::sub_p
GeometryTraits::Vector sub_p(const typename GeometryTraits::Point &p1, const typename GeometryTraits::Point &p2)
Returns point P1 minus point P2.
Definition: Geometry_traits_operators.h:62
FEVV::Filters::Error_metric::priority_queue_edges
std::priority_queue< std::tuple< edge_descriptor, double, Point >, std::vector< std::tuple< edge_descriptor, double, Point > >, Compare_weights2< HalfedgeGraph > > priority_queue_edges
Definition: Error_metric.h:81
FEVV::DataStructures::AIF::opposite
boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::halfedge_descriptor opposite(typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::halfedge_descriptor h, const FEVV::DataStructures::AIF::AIFMesh &sm)
Returns the halfedge with source and target swapped.
Definition: Graph_traits_aif.h:625
FEVV::Geometry_traits
Refer to Geometry_traits_documentation_dummy for further documentation on provided types and algorith...
Definition: Geometry_traits.h:162
FEVV::Filters::Error_metric::_g
HalfedgeGraph & _g
Definition: Error_metric.h:204
FEVV::Filters::Uniform_dequantization::dequantize
Point dequantize(vertex_descriptor v)
Definition: Uniform_dequantization.h:82
FEVV::Filters::QEM_3D::faces_quadrics
std::map< face_descriptor, Quadric > faces_quadrics
Definition: QEM_3D.h:168
FEVV::DataStructures::AIF::source
boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::vertex_descriptor source(typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::edge_descriptor e, const FEVV::DataStructures::AIF::AIFMesh &)
Returns the source vertex of e.
Definition: Graph_traits_aif.h:387
FEVV::Filters::QEM_3D::VectorX
Eigen::VectorXd VectorX
Definition: QEM_3D.h:49
FEVV::Filters::Error_metric
Abstract class to compute the collapse cost of each edge in a mesh. It also manages a priority queue ...
Definition: Error_metric.h:62
FEVV::Filters::QEM_3D::compute_face_quadric
virtual Quadric compute_face_quadric(face_descriptor f) const
Definition: QEM_3D.h:251
FEVV::Filters::QEM_3D::vertices_quadrics
std::map< vertex_descriptor, Quadric > vertices_quadrics
Definition: QEM_3D.h:170
FEVV::get
FEVV::PCLPointCloudPointMap::value_type get(const FEVV::PCLPointCloudPointMap &pm, FEVV::PCLPointCloudPointMap::key_type key)
Specialization of get(point_map, key) for PCLPointCloud.
Definition: Graph_properties_pcl_point_cloud.h:117
FEVV::Filters::Kept_position::compute_position
virtual Point compute_position(typename boost::graph_traits< HalfedgeGraph >::edge_descriptor edge)=0
Compute the kept vertex position of an edge.
FEVV::Filters::Error_metric::Vector
typename FEVV::Geometry_traits< HalfedgeGraph >::Vector Vector
Definition: Error_metric.h:72
FEVV::Filters::Kept_position< HalfedgeGraph, PointMap >
FEVV::Filters::Error_metric::Geometry
typename FEVV::Geometry_traits< HalfedgeGraph > Geometry
Definition: Error_metric.h:74
FEVV
Interfaces for plugins These interfaces will be used for different plugins.
Definition: Assert.h:16
FEVV::DataStructures::AIF::faces
std::pair< typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::face_iterator, typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::face_iterator > faces(const FEVV::DataStructures::AIF::AIFMesh &sm)
Returns an iterator range over all faces of the mesh.
Definition: Graph_traits_aif.h:679
FEVV::Filters::QEM_3D::QEM_3D
QEM_3D(HalfedgeGraph &g, PointMap &pm, Kept_position< HalfedgeGraph, PointMap > *vkept, FEVV::Filters::Uniform_dequantization< HalfedgeGraph, PointMap > &dequantiz)
Definition: QEM_3D.h:53
FEVV::Filters::QEM_3D::vertex_descriptor
typename boost::graph_traits< HalfedgeGraph >::vertex_descriptor vertex_descriptor
Definition: QEM_3D.h:32
FEVV::DataStructures::AIF::halfedge
boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::halfedge_descriptor halfedge(typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::vertex_descriptor v, const FEVV::DataStructures::AIF::AIFMesh &sm)
Returns a halfedge with target v.
Definition: Graph_traits_aif.h:296
FEVV::Filters::QEM_3D::Super_class
Error_metric< HalfedgeGraph, PointMap > Super_class
Definition: QEM_3D.h:46
FEVV::DataStructures::AIF::target
boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::vertex_descriptor target(typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::edge_descriptor e, const FEVV::DataStructures::AIF::AIFMesh &)
Returns the target vertex of e.
Definition: Graph_traits_aif.h:400
FEVV::Filters::Error_metric::_vkept
Kept_position< HalfedgeGraph, PointMap > * _vkept
Definition: Error_metric.h:213
FEVV::Filters::Error_metric::edge2cost_map
std::map< edge_descriptor, std::pair< double, Point > > edge2cost_map
Definition: Error_metric.h:85
FEVV::Filters::Error_metric::_dequantiz
FEVV::Filters::Uniform_dequantization< HalfedgeGraph, PointMap > & _dequantiz
Definition: Error_metric.h:215
FEVV::Filters::Error_metric::edge_iterator
typename boost::graph_traits< HalfedgeGraph >::edge_iterator edge_iterator
Definition: Error_metric.h:69
FEVV::Filters::QEM_3D::face_descriptor
typename boost::graph_traits< HalfedgeGraph >::face_descriptor face_descriptor
Definition: QEM_3D.h:40
FEVV::Filters::Uniform_dequantization< HalfedgeGraph, PointMap >
FEVV::Filters::QEM_3D::get_plane_equation
std::vector< double > get_plane_equation(face_descriptor f) const
Definition: QEM_3D.h:196
FEVV::Filters::Error_metric::edge_descriptor
typename boost::graph_traits< HalfedgeGraph >::edge_descriptor edge_descriptor
Definition: Error_metric.h:71
msdm2::vertex_descriptor
boost::graph_traits< MeshT >::vertex_descriptor vertex_descriptor
Definition: msdm2_surfacemesh.h:33
FEVV::DataStructures::AIF::face
boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::face_descriptor face(typename boost::graph_traits< FEVV::DataStructures::AIF::AIFMesh >::halfedge_descriptor h, const FEVV::DataStructures::AIF::AIFMesh &)
Returns the face incident to halfedge h.
Definition: Graph_traits_aif.h:664
FEVV::Filters::QEM_3D::compute_cost_edge
double compute_cost_edge(edge_descriptor e, const Point &collapsePos) override
Definition: QEM_3D.h:122
FEVV::Filters::Error_metric::_gt
const Geometry _gt
Definition: Error_metric.h:205
FEVV::Filters::QEM_3D::compute_triangle_area_after_dequantization
double compute_triangle_area_after_dequantization(face_descriptor f) const
Definition: QEM_3D.h:182