1 /*
2  * Software License Agreement (BSD License)
3  *
4  *  Copyright (c) 2012-, Open Perception, Inc.
5  *  All rights reserved.
6  *
7  *  Redistribution and use in source and binary forms, with or without
8  *  modification, are permitted provided that the following conditions
9  *  are met:
10  *
11  *   * Redistributions of source code must retain the above copyright
12  *     notice, this list of conditions and the following disclaimer.
13  *   * Redistributions in binary form must reproduce the above
14  *     copyright notice, this list of conditions and the following
15  *     disclaimer in the documentation and/or other materials provided
16  *     with the distribution.
17  *   * Neither the name of the copyright holder(s) nor the names of its
18  *     contributors may be used to endorse or promote products derived
19  *     from this software without specific prior written permission.
20  *
21  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  *  POSSIBILITY OF SUCH DAMAGE.
33  *
34  *
35  *
36  */
37 
38 #include <stdexcept>
39 #include <pcl/surface/on_nurbs/fitting_sphere_pdm.h>
40 #include <pcl/pcl_macros.h>
41 #include <Eigen/Cholesky> // for ldlt
42 #include <Eigen/Geometry> // for cross
43 
44 using namespace pcl;
45 using namespace on_nurbs;
46 using namespace Eigen;
47 
FittingSphere(int order,NurbsDataSurface * data)48 FittingSphere::FittingSphere (int order, NurbsDataSurface *data)
49 {
50   if (order < 2)
51     throw std::runtime_error ("[FittingSphere::FittingSphere] Error order to low (order<2).");
52 
53   ON::Begin ();
54 
55   m_data = data;
56   m_nurbs = initNurbsSphere (order, m_data);
57 
58   this->init ();
59 }
60 
FittingSphere(NurbsDataSurface * data,const ON_NurbsSurface & ns)61 FittingSphere::FittingSphere (NurbsDataSurface *data, const ON_NurbsSurface &ns)
62 {
63   ON::Begin ();
64 
65   m_nurbs = ON_NurbsSurface (ns);
66   m_data = data;
67 
68   this->init ();
69 }
70 
71 void
init()72 FittingSphere::init ()
73 {
74   in_max_steps = 100;
75   in_accuracy = 1e-4;
76 
77   m_quiet = true;
78 }
79 
80 void
assemble(double smoothness)81 FittingSphere::assemble (double smoothness)
82 {
83   int cp_red = (m_nurbs.m_order[1] - 2);
84   int ncp = m_nurbs.m_cv_count[0] * (m_nurbs.m_cv_count[1] - 2 * cp_red);
85   int nInt = int (m_data->interior.size ());
86   int nCageRegInt = (m_nurbs.m_cv_count[0] - 2) * (m_nurbs.m_cv_count[1] - 2 * cp_red);
87   int nCageRegBnd = 2 * (m_nurbs.m_cv_count[1] - 2 * cp_red);
88 
89   double wInt = 1.0;
90   double wCageRegInt = smoothness;
91   double wCageRegBnd = smoothness;
92 
93   int nrows = nInt + nCageRegInt + nCageRegBnd;
94 
95   if (!m_quiet)
96     printf ("[FittingSphere::assemble] %dx%d (invmap: %f %d)\n", nrows, ncp, in_accuracy, in_max_steps);
97 
98   m_solver.assign (nrows, ncp, 3);
99 
100   unsigned row (0);
101 
102   // interior points should lie on surface
103   assembleInterior (wInt, row);
104 
105   // cage regularisation
106   if (nCageRegInt > 0)
107     addCageInteriorRegularisation (wCageRegInt, row);
108 
109   if (nCageRegBnd > 0)
110   {
111     addCageBoundaryRegularisation (wCageRegBnd, WEST, row);
112     addCageBoundaryRegularisation (wCageRegBnd, EAST, row);
113   }
114 }
115 
116 void
solve(double damp)117 FittingSphere::solve (double damp)
118 {
119   if (m_solver.solve ())
120     updateSurf (damp);
121 }
122 
123 void
updateSurf(double)124 FittingSphere::updateSurf (double)
125 {
126   int cp_red = (m_nurbs.m_order[1] - 2);
127 
128   for (int j = 0; j < m_nurbs.m_cv_count[1] - 2 * cp_red; j++)
129   {
130     for (int i = 0; i < m_nurbs.m_cv_count[0]; i++)
131     {
132 
133       int A = grc2gl (i, j);
134 
135       ON_3dPoint cp;
136       cp.x = m_solver.x (A, 0);
137       cp.y = m_solver.x (A, 1);
138       cp.z = m_solver.x (A, 2);
139 
140       m_nurbs.SetCV (i, j + cp_red, cp);
141     }
142   }
143 
144   for (int j = 0; j < cp_red; j++)
145   {
146     for (int i = 0; i < m_nurbs.m_cv_count[0]; i++)
147     {
148 
149       ON_3dPoint cp;
150       m_nurbs.GetCV (i, m_nurbs.m_cv_count[1] - 1 - cp_red + j, cp);
151       m_nurbs.SetCV (i, j, cp);
152 
153       m_nurbs.GetCV (i, cp_red - j, cp);
154       m_nurbs.SetCV (i, m_nurbs.m_cv_count[1] - 1 - j, cp);
155     }
156   }
157 }
158 
159 void
setInvMapParams(int in_max_steps,double in_accuracy)160 FittingSphere::setInvMapParams (int in_max_steps, double in_accuracy)
161 {
162   this->in_max_steps = in_max_steps;
163   this->in_accuracy = in_accuracy;
164 }
165 
166 ON_NurbsSurface
initNurbsSphere(int order,NurbsDataSurface * data,Eigen::Vector3d)167 FittingSphere::initNurbsSphere (int order, NurbsDataSurface *data, Eigen::Vector3d)
168 {
169   Eigen::Vector3d mean = NurbsTools::computeMean (data->interior);
170 
171   Eigen::Vector3d _min (DBL_MAX, DBL_MAX, DBL_MAX);
172   Eigen::Vector3d _max (-DBL_MAX, -DBL_MAX, -DBL_MAX);
173   for (const auto &i : data->interior)
174   {
175     Eigen::Vector3d p = i - mean;
176 
177     if (p (0) < _min (0))
178       _min (0) = p (0);
179     if (p (1) < _min (1))
180       _min (1) = p (1);
181     if (p (2) < _min (2))
182       _min (2) = p (2);
183 
184     if (p (0) > _max (0))
185       _max (0) = p (0);
186     if (p (1) > _max (1))
187       _max (1) = p (1);
188     if (p (2) > _max (2))
189       _max (2) = p (2);
190   }
191 
192   int ncpsU (order + 2);
193   int ncpsV (2 * order + 4);
194   ON_NurbsSurface nurbs (3, false, order, order, ncpsU, ncpsV);
195   nurbs.MakeClampedUniformKnotVector (0, 1.0);
196   nurbs.MakePeriodicUniformKnotVector (1, 1.0 / (ncpsV - order + 1));
197 
198   double dcu = (_max (2) - _min (2)) / (ncpsU - 3);
199   double dcv = (2.0 * M_PI) / (ncpsV - order + 1);
200 
201   double rx = std::max<double> (std::fabs (_min (0)), std::fabs (_max (0)));
202   double ry = std::max<double> (std::fabs (_min (1)), std::fabs (_max (1)));
203 
204   Eigen::Vector3d cv_t, cv;
205   for (int i = 1; i < ncpsU - 1; i++)
206   //  for (int i = 0; i < ncpsU; i++)
207   {
208     for (int j = 0; j < ncpsV; j++)
209     {
210 
211       cv (0) = rx * sin (dcv * j);
212       cv (1) = ry * std::cos (dcv * j);
213       cv (2) = _min (2) + dcu * (i - 1);
214       cv_t = cv + mean;
215       nurbs.SetCV (i, j, ON_3dPoint (cv_t (0), cv_t (1), cv_t (2)));
216     }
217   }
218 
219   for (int j = 0; j < ncpsV; j++)
220   {
221     //    cv (0) = 0.0;
222     //    cv (1) = 0.0;
223     cv (0) = 0.01 * rx * sin (dcv * j);
224     cv (1) = 0.01 * ry * std::cos (dcv * j);
225     cv (2) = _min (2);
226     cv_t = cv + mean;
227     nurbs.SetCV (0, j, ON_3dPoint (cv_t (0), cv_t (1), cv_t (2)));
228   }
229 
230   for (int j = 0; j < ncpsV; j++)
231   {
232     //    cv (0) = 0.0;
233     //    cv (1) = 0.0;
234     cv (0) = 0.01 * rx * sin (dcv * j);
235     cv (1) = 0.01 * ry * std::cos (dcv * j);
236     cv (2) = _max (2);
237     cv_t = cv + mean;
238     nurbs.SetCV (ncpsU - 1, j, ON_3dPoint (cv_t (0), cv_t (1), cv_t (2)));
239   }
240 
241   return nurbs;
242 }
243 
244 std::vector<double>
getElementVector(const ON_NurbsSurface & nurbs,int dim)245 FittingSphere::getElementVector (const ON_NurbsSurface &nurbs, int dim) // !
246 {
247   std::vector<double> result;
248 
249   if (dim == 0)
250   {
251     int idx_min = 0;
252     int idx_max = nurbs.KnotCount (0) - 1;
253     if (nurbs.IsClosed (0))
254     {
255       idx_min = nurbs.Order (0) - 2;
256       idx_max = nurbs.KnotCount (0) - nurbs.Order (0) + 1;
257     }
258 
259     const double* knotsU = nurbs.Knot (0);
260 
261     result.push_back (knotsU[idx_min]);
262 
263     //for(int E=(m_nurbs.m_order[0]-2); E<(m_nurbs.m_knot_capacity[0]-m_nurbs.m_order[0]+2); E++) {
264     for (int E = idx_min + 1; E <= idx_max; E++)
265     {
266 
267       if (knotsU[E] != knotsU[E - 1]) // do not count double knots
268         result.push_back (knotsU[E]);
269 
270     }
271 
272   }
273   else if (dim == 1)
274   {
275     int idx_min = 0;
276     int idx_max = nurbs.KnotCount (1) - 1;
277     if (nurbs.IsClosed (1))
278     {
279       idx_min = nurbs.Order (1) - 2;
280       idx_max = nurbs.KnotCount (1) - nurbs.Order (1) + 1;
281     }
282     const double* knotsV = nurbs.Knot (1);
283 
284     result.push_back (knotsV[idx_min]);
285 
286     //for(int F=(m_nurbs.m_order[1]-2); F<(m_nurbs.m_knot_capacity[1]-m_nurbs.m_order[1]+2); F++) {
287     for (int F = idx_min + 1; F <= idx_max; F++)
288     {
289 
290       if (knotsV[F] != knotsV[F - 1])
291         result.push_back (knotsV[F]);
292 
293     }
294 
295   }
296   else
297     printf ("[FittingSphere::getElementVector] Error, index exceeds problem dimensions!\n");
298 
299   return result;
300 }
301 
302 void
assembleInterior(double wInt,unsigned & row)303 FittingSphere::assembleInterior (double wInt, unsigned &row)
304 {
305   m_data->interior_line_start.clear ();
306   m_data->interior_line_end.clear ();
307   m_data->interior_error.clear ();
308   m_data->interior_normals.clear ();
309   unsigned nInt = unsigned (m_data->interior.size ());
310   for (unsigned p = 0; p < nInt; p++)
311   {
312     Vector3d pcp;
313     pcp = m_data->interior[p];
314 
315     // inverse mapping
316     Vector2d params;
317     Vector3d pt, tu, tv, n;
318     double error;
319     if (p < m_data->interior_param.size ())
320     {
321       params = inverseMapping (m_nurbs, pcp, m_data->interior_param[p], error, pt, tu, tv, in_max_steps, in_accuracy);
322       m_data->interior_param[p] = params;
323     }
324     else
325     {
326       params = findClosestElementMidPoint (m_nurbs, pcp);
327       params = inverseMapping (m_nurbs, pcp, params, error, pt, tu, tv, in_max_steps, in_accuracy);
328       m_data->interior_param.push_back (params);
329     }
330     m_data->interior_error.push_back (error);
331 
332     n = tu.cross (tv);
333     n.normalize ();
334 
335     m_data->interior_normals.push_back (n);
336     m_data->interior_line_start.push_back (pcp);
337     m_data->interior_line_end.push_back (pt);
338 
339     addPointConstraint (params, pcp, wInt, row);
340   }
341 }
342 
343 void
addPointConstraint(const Eigen::Vector2d & params,const Eigen::Vector3d & point,double weight,unsigned & row)344 FittingSphere::addPointConstraint (const Eigen::Vector2d &params, const Eigen::Vector3d &point, double weight,
345                                    unsigned &row)
346 {
347   double *N0 = new double[m_nurbs.m_order[0] * m_nurbs.m_order[0]];
348   double *N1 = new double[m_nurbs.m_order[1] * m_nurbs.m_order[1]];
349 
350   int E = ON_NurbsSpanIndex (m_nurbs.m_order[0], m_nurbs.m_cv_count[0], m_nurbs.m_knot[0], params (0), 0, 0);
351   int F = ON_NurbsSpanIndex (m_nurbs.m_order[1], m_nurbs.m_cv_count[1], m_nurbs.m_knot[1], params (1), 0, 0);
352 
353   ON_EvaluateNurbsBasis (m_nurbs.m_order[0], m_nurbs.m_knot[0] + E, params (0), N0);
354   ON_EvaluateNurbsBasis (m_nurbs.m_order[1], m_nurbs.m_knot[1] + F, params (1), N1);
355 
356   m_solver.f (row, 0, point (0) * weight);
357   m_solver.f (row, 1, point (1) * weight);
358   m_solver.f (row, 2, point (2) * weight);
359 
360   for (int i = 0; i < m_nurbs.m_order[0]; i++)
361   {
362 
363     for (int j = 0; j < m_nurbs.m_order[1]; j++)
364     {
365 
366       m_solver.K (row, lrc2gl (E, F, i, j), weight * N0[i] * N1[j]);
367 
368     } // j
369 
370   } // i
371 
372   row++;
373 
374   delete[] N1;
375   delete[] N0;
376 }
377 
378 void
addCageInteriorRegularisation(double weight,unsigned & row)379 FittingSphere::addCageInteriorRegularisation (double weight, unsigned &row)
380 {
381   int cp_red = (m_nurbs.m_order[1] - 2);
382 
383   for (int i = 1; i < (m_nurbs.m_cv_count[0] - 1); i++)
384   {
385     for (int j = 0; j < (m_nurbs.m_cv_count[1] - 2 * cp_red); j++)
386     {
387 
388       m_solver.f (row, 0, 0.0);
389       m_solver.f (row, 1, 0.0);
390       m_solver.f (row, 2, 0.0);
391 
392       m_solver.K (row, grc2gl (i + 0, j + 0), -4.0 * weight);
393       m_solver.K (row, grc2gl (i + 0, j - 1), 1.0 * weight);
394       m_solver.K (row, grc2gl (i + 0, j + 1), 1.0 * weight);
395       m_solver.K (row, grc2gl (i - 1, j + 0), 1.0 * weight);
396       m_solver.K (row, grc2gl (i + 1, j + 0), 1.0 * weight);
397 
398       row++;
399     }
400   }
401 }
402 
403 void
addCageBoundaryRegularisation(double weight,int side,unsigned & row)404 FittingSphere::addCageBoundaryRegularisation (double weight, int side, unsigned &row)
405 {
406   int cp_red = (m_nurbs.m_order[1] - 2);
407   int i = 0;
408   int j = 0;
409 
410   switch (side)
411   {
412     case EAST:
413       i = m_nurbs.m_cv_count[0] - 1;
414       PCL_FALLTHROUGH
415     case WEST:
416       for (j = 1; j < (m_nurbs.m_cv_count[1] - 2 * cp_red) + 1; j++)
417       {
418 
419         m_solver.f (row, 0, 0.0);
420         m_solver.f (row, 1, 0.0);
421         m_solver.f (row, 2, 0.0);
422 
423         m_solver.K (row, grc2gl (i, j + 0), -2.0 * weight);
424         m_solver.K (row, grc2gl (i, j - 1), 1.0 * weight);
425         m_solver.K (row, grc2gl (i, j + 1), 1.0 * weight);
426 
427         row++;
428       }
429       break;
430   }
431 }
432 
433 Vector2d
inverseMapping(const ON_NurbsSurface & nurbs,const Vector3d & pt,const Vector2d & hint,double & error,Vector3d & p,Vector3d & tu,Vector3d & tv,int maxSteps,double accuracy,bool quiet)434 FittingSphere::inverseMapping (const ON_NurbsSurface &nurbs, const Vector3d &pt, const Vector2d &hint, double &error,
435                                Vector3d &p, Vector3d &tu, Vector3d &tv, int maxSteps, double accuracy, bool quiet)
436 {
437   double pointAndTangents[9];
438 
439   Vector2d current, delta;
440   Matrix2d A;
441   Vector2d b;
442   Vector3d r;
443   std::vector<double> elementsU = getElementVector (nurbs, 0);
444   std::vector<double> elementsV = getElementVector (nurbs, 1);
445   double minU = elementsU[0];
446   double minV = elementsV[0];
447   double maxU = elementsU[elementsU.size () - 1];
448   double maxV = elementsV[elementsV.size () - 1];
449 
450   current = hint;
451 
452   for (int k = 0; k < maxSteps; k++)
453   {
454 
455     nurbs.Evaluate (current (0), current (1), 1, 3, pointAndTangents);
456     p (0) = pointAndTangents[0];
457     p (1) = pointAndTangents[1];
458     p (2) = pointAndTangents[2];
459     tu (0) = pointAndTangents[3];
460     tu (1) = pointAndTangents[4];
461     tu (2) = pointAndTangents[5];
462     tv (0) = pointAndTangents[6];
463     tv (1) = pointAndTangents[7];
464     tv (2) = pointAndTangents[8];
465 
466     r = p - pt;
467 
468     b (0) = -r.dot (tu);
469     b (1) = -r.dot (tv);
470 
471     A (0, 0) = tu.dot (tu);
472     A (0, 1) = tu.dot (tv);
473     A (1, 0) = A (0, 1);
474     A (1, 1) = tv.dot (tv);
475 
476     delta = A.ldlt ().solve (b);
477 
478     if (delta.norm () < accuracy)
479     {
480 
481       error = r.norm ();
482       return current;
483 
484     }
485     current += delta;
486     if (current (0) < minU)
487       current (0) = minU;
488     else if (current (0) > maxU)
489       current (0) = maxU;
490 
491     if (current (1) < minV)
492       current (1) = maxV - (minV - current (1));
493     else if (current (1) > maxV)
494       current (1) = minV + (current (1) - maxV);
495   }
496 
497   error = r.norm ();
498 
499   if (!quiet)
500   {
501     printf ("[FittingSphere::inverseMapping] Warning: Method did not converge (%e %d)\n", accuracy, maxSteps);
502     printf ("  %f %f ... %f %f\n", hint (0), hint (1), current (0), current (1));
503   }
504 
505   return current;
506 }
507 
508 Vector2d
findClosestElementMidPoint(const ON_NurbsSurface & nurbs,const Vector3d & pt)509 FittingSphere::findClosestElementMidPoint (const ON_NurbsSurface &nurbs, const Vector3d &pt)
510 {
511   Vector2d hint;
512   Vector3d r;
513   std::vector<double> elementsU = getElementVector (nurbs, 0);
514   std::vector<double> elementsV = getElementVector (nurbs, 1);
515 
516   double d_shortest = std::numeric_limits<double>::max ();
517   for (std::size_t i = 0; i < elementsU.size () - 1; i++)
518   {
519     for (std::size_t j = 0; j < elementsV.size () - 1; j++)
520     {
521       double points[3];
522 
523       double xi = elementsU[i] + 0.5 * (elementsU[i + 1] - elementsU[i]);
524       double eta = elementsV[j] + 0.5 * (elementsV[j + 1] - elementsV[j]);
525 
526       nurbs.Evaluate (xi, eta, 0, 3, points);
527       r (0) = points[0] - pt (0);
528       r (1) = points[1] - pt (1);
529       r (2) = points[2] - pt (2);
530 
531       double d = r.squaredNorm ();
532 
533       if ((i == 0 && j == 0) || d < d_shortest)
534       {
535         d_shortest = d;
536         hint (0) = xi;
537         hint (1) = eta;
538       }
539     }
540   }
541 
542   return hint;
543 }
544 
545