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 ¶ms, 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