diff --git a/src/Mod/Mesh/App/Core/Approximation.cpp b/src/Mod/Mesh/App/Core/Approximation.cpp index 4938448bee..d34b7f894d 100644 --- a/src/Mod/Mesh/App/Core/Approximation.cpp +++ b/src/Mod/Mesh/App/Core/Approximation.cpp @@ -33,6 +33,7 @@ #include "Utilities.h" #include +#include #include #include #include @@ -45,6 +46,8 @@ //#define FC_USE_EIGEN #include +#include +#include #ifdef FC_USE_EIGEN #include #endif @@ -787,6 +790,98 @@ void SurfaceFit::GetCoefficients(double& a,double& b,double& c,double& d,double& // ------------------------------------------------------------------------------- +namespace MeshCore { + +struct LMCylinderFunctor +{ + Eigen::MatrixXd measuredValues; + + // Compute 'm' errors, one for each data point, for the given parameter values in 'x' + int operator()(const Eigen::VectorXd &x, Eigen::VectorXd &fvec) const + { + // 'x' has dimensions n x 1 + // It contains the current estimates for the parameters. + + // 'fvec' has dimensions m x 1 + // It will contain the error for each data point. + double aParam = x(0); // dir_x + double bParam = x(1); // dir_y + double cParam = x(2); // dir_z + double dParam = x(3); // cnt_x + double eParam = x(4); // cnt_y + double fParam = x(5); // cnt_z + double gParam = x(6); // radius + + // use distance functions (fvec(i)) for cylinders as defined in the paper: + // Least-Squares Fitting Algorithms of the NIST Algorithm Testing System + for (int i = 0; i < values(); i++) { + double xValue = measuredValues(i, 0); + double yValue = measuredValues(i, 1); + double zValue = measuredValues(i, 2); + + double a = aParam/(sqrt(aParam*aParam + bParam*bParam + cParam*cParam)); + double b = bParam/(sqrt(aParam*aParam + bParam*bParam + cParam*cParam)); + double c = cParam/(sqrt(aParam*aParam + bParam*bParam + cParam*cParam)); + double x = dParam; + double y = eParam; + double z = fParam; + double u = c * (yValue - y) - b * (zValue - z); + double v = a * (zValue - z) - c * (xValue - x); + double w = b * (xValue - x) - a * (yValue - y); + + fvec(i) = sqrt(u*u + v*v + w*w) - gParam; + } + return 0; + } + + // Compute the jacobian of the errors + int df(const Eigen::VectorXd &x, Eigen::MatrixXd &fjac) const + { + // 'x' has dimensions n x 1 + // It contains the current estimates for the parameters. + + // 'fjac' has dimensions m x n + // It will contain the jacobian of the errors, calculated numerically in this case. + + double epsilon; + epsilon = 1e-5; + + for (int i = 0; i < x.size(); i++) { + Eigen::VectorXd xPlus(x); + xPlus(i) += epsilon; + Eigen::VectorXd xMinus(x); + xMinus(i) -= epsilon; + + Eigen::VectorXd fvecPlus(values()); + operator()(xPlus, fvecPlus); + + Eigen::VectorXd fvecMinus(values()); + operator()(xMinus, fvecMinus); + + Eigen::VectorXd fvecDiff(values()); + fvecDiff = (fvecPlus - fvecMinus) / (2.0f * epsilon); + + fjac.block(0, i, values(), 1) = fvecDiff; + } + + return 0; + } + + // Number of data points, i.e. values. + int m; + + // Returns 'm', the number of values. + int values() const { return m; } + + // The number of parameters, i.e. inputs. + int n; + + // Returns 'n', the number of inputs. + int inputs() const { return n; } +}; + +} + CylinderFit::CylinderFit() : _vBase(0,0,0) , _vAxis(0,0,1) @@ -804,6 +899,7 @@ float CylinderFit::Fit() return FLOAT_MAX; _bIsFitted = true; +#if 0 std::vector input; std::transform(_vPoints.begin(), _vPoints.end(), std::back_inserter(input), [](const Base::Vector3f& v) { return Wm4::Vector3d(v.x, v.y, v.z); }); @@ -815,9 +911,57 @@ float CylinderFit::Fit() _vAxis = Base::convertTo(axis); _fRadius = float(radius); - // TODO - _fLastResult = double(fit); +#else + int m = static_cast(_vPoints.size()); + int n = 7; + + Eigen::MatrixXd measuredValues(m, 3); + int index = 0; + for (const auto& it : _vPoints) { + measuredValues(index, 0) = it.x; + measuredValues(index, 1) = it.y; + measuredValues(index, 2) = it.z; + index++; + } + + Eigen::VectorXd x(n); + x(0) = 1.0; // initial value for dir_x + x(1) = 1.0; // initial value for dir_y + x(2) = 1.0; // initial value for dir_z + x(3) = 0.0; // initial value for cnt_x + x(4) = 0.0; // initial value for cnt_y + x(5) = 0.0; // initial value for cnt_z + x(6) = 0.0; // initial value for radius + + // + // Run the LM optimization + // Create a LevenbergMarquardt object and pass it the functor. + // + + LMCylinderFunctor functor; + functor.measuredValues = measuredValues; + functor.m = m; + functor.n = n; + + Eigen::LevenbergMarquardt lm(functor); + int status = lm.minimize(x); + Base::Console().Log("Cylinder fit: %d, iterations: %d, gradient norm: %f\n", status, lm.iter, lm.gnorm); + + _vAxis.x = x(0); + _vAxis.y = x(1); + _vAxis.z = x(2); + _vAxis.Normalize(); + + _vBase.x = x(3); + _vBase.y = x(4); + _vBase.z = x(5); + + _fRadius = x(6); + + _fLastResult = lm.gnorm; +#endif + return _fLastResult; }