// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2014 Victor Titov (DeepSOIC) * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #define DEBUG_DERIVS 0 #if DEBUG_DERIVS #endif #include #include "Geo.h" namespace GCS { //----------------Point int Point::PushOwnParams(VEC_pD& pvec) const { int cnt = 0; pvec.push_back(x); cnt++; pvec.push_back(y); cnt++; return cnt; } void Point::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { x = pvec[cnt]; cnt++; y = pvec[cnt]; cnt++; } //----------------DeriVector2 DeriVector2::DeriVector2(const Point& p, const double* derivparam) : x(*p.x) , dx(0.0) , y(*p.y) , dy(0.0) { if (derivparam == p.x) { dx = 1.0; } if (derivparam == p.y) { dy = 1.0; } } double DeriVector2::length(double& dlength) const { double l = length(); if (l == 0) { dlength = 1.0; return l; } dlength = (x * dx + y * dy) / l; return l; } DeriVector2 DeriVector2::getNormalized() const { double l = length(); if (l == 0.0) { return DeriVector2(0, 0, dx, dy); } DeriVector2 rtn; rtn.x = x / l; rtn.y = y / l; // first, simply scale the derivative accordingly. rtn.dx = dx / l; rtn.dy = dy / l; // next, remove the collinear part of dx,dy (make a projection onto a normal) double dsc = rtn.dx * rtn.x + rtn.dy * rtn.y; // scalar product d*v rtn.dx -= dsc * rtn.x; // subtract the projection rtn.dy -= dsc * rtn.y; return rtn; } double DeriVector2::scalarProd(const DeriVector2& v2, double* dprd) const { if (dprd) { *dprd = dx * v2.x + x * v2.dx + dy * v2.y + y * v2.dy; } return x * v2.x + y * v2.y; } DeriVector2 DeriVector2::divD(double val, double dval) const { return {x / val, y / val, dx / val - x * dval / (val * val), dy / val - y * dval / (val * val)}; } double DeriVector2::crossProdZ(const DeriVector2& v2, double& dprd) const { dprd = dx * v2.y + x * v2.dy - dy * v2.x - y * v2.dx; return x * v2.y - y * v2.x; } DeriVector2 Curve::Value(double /*u*/, double /*du*/, const double* /*derivparam*/) const { assert(false /*Value() is not implemented*/); return {}; } //----------------Line DeriVector2 Line::CalculateNormal(const Point& p, const double* derivparam) const { (void)p; DeriVector2 p1v(p1, derivparam); DeriVector2 p2v(p2, derivparam); return p2v.subtr(p1v).rotate90ccw(); } DeriVector2 Line::Value(double u, double du, const double* derivparam) const { DeriVector2 p1v(p1, derivparam); DeriVector2 p2v(p2, derivparam); DeriVector2 line_vec = p2v.subtr(p1v); return p1v.sum(line_vec.multD(u, du)); } int Line::PushOwnParams(VEC_pD& pvec) { int cnt = 0; pvec.push_back(p1.x); cnt++; pvec.push_back(p1.y); cnt++; pvec.push_back(p2.x); cnt++; pvec.push_back(p2.y); cnt++; return cnt; } void Line::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { p1.x = pvec[cnt]; cnt++; p1.y = pvec[cnt]; cnt++; p2.x = pvec[cnt]; cnt++; p2.y = pvec[cnt]; cnt++; } Line* Line::Copy() { return new Line(*this); } //---------------circle DeriVector2 Circle::CalculateNormal(const Point& p, const double* derivparam) const { DeriVector2 cv(center, derivparam); DeriVector2 pv(p, derivparam); return cv.subtr(pv); } DeriVector2 Circle::Value(double u, double du, const double* derivparam) const { DeriVector2 cv(center, derivparam); double r, dr; r = *(this->rad); dr = (derivparam == this->rad) ? 1.0 : 0.0; DeriVector2 ex(r, 0.0, dr, 0.0); DeriVector2 ey = ex.rotate90ccw(); double si, dsi, co, dco; si = std::sin(u); dsi = du * std::cos(u); co = std::cos(u); dco = du * (-std::sin(u)); return cv.sum(ex.multD(co, dco).sum(ey.multD(si, dsi))); } int Circle::PushOwnParams(VEC_pD& pvec) { int cnt = 0; pvec.push_back(center.x); cnt++; pvec.push_back(center.y); cnt++; pvec.push_back(rad); cnt++; return cnt; } void Circle::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { center.x = pvec[cnt]; cnt++; center.y = pvec[cnt]; cnt++; rad = pvec[cnt]; cnt++; } Circle* Circle::Copy() { return new Circle(*this); } //------------arc int Arc::PushOwnParams(VEC_pD& pvec) { int cnt = 0; cnt += Circle::PushOwnParams(pvec); pvec.push_back(start.x); cnt++; pvec.push_back(start.y); cnt++; pvec.push_back(end.x); cnt++; pvec.push_back(end.y); cnt++; pvec.push_back(startAngle); cnt++; pvec.push_back(endAngle); cnt++; return cnt; } void Arc::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { Circle::ReconstructOnNewPvec(pvec, cnt); start.x = pvec[cnt]; cnt++; start.y = pvec[cnt]; cnt++; end.x = pvec[cnt]; cnt++; end.y = pvec[cnt]; cnt++; startAngle = pvec[cnt]; cnt++; endAngle = pvec[cnt]; cnt++; } Arc* Arc::Copy() { return new Arc(*this); } //--------------ellipse // this function is exposed to allow reusing pre-filled derivectors in constraints code double Ellipse::getRadMaj( const DeriVector2& center, const DeriVector2& f1, double b, double db, double& ret_dRadMaj ) const { double cf, dcf; cf = f1.subtr(center).length(dcf); DeriVector2 hack( b, cf, db, dcf ); // hack = a nonsense vector to calculate major radius with derivatives, useful just // because the calculation formula is the same as vector length formula return hack.length(ret_dRadMaj); } // returns major radius. The derivative by derivparam is returned into ret_dRadMaj argument. double Ellipse::getRadMaj(double* derivparam, double& ret_dRadMaj) const { DeriVector2 c(center, derivparam); DeriVector2 f1(focus1, derivparam); return getRadMaj(c, f1, *radmin, radmin == derivparam ? 1.0 : 0.0, ret_dRadMaj); } // returns the major radius (plain value, no derivatives) double Ellipse::getRadMaj() const { double dradmaj; // dummy return getRadMaj(nullptr, dradmaj); } DeriVector2 Ellipse::CalculateNormal(const Point& p, const double* derivparam) const { // fill some vectors in DeriVector2 cv(center, derivparam); DeriVector2 f1v(focus1, derivparam); DeriVector2 pv(p, derivparam); // calculation. // focus2: DeriVector2 f2v = cv.linCombi(2.0, f1v, -1.0); // 2*cv - f1v // pf1, pf2 = vectors from p to focus1,focus2 DeriVector2 pf1 = f1v.subtr(pv); DeriVector2 pf2 = f2v.subtr(pv); // return sum of normalized pf2, pf2 return pf1.getNormalized().sum(pf2.getNormalized()); } DeriVector2 Ellipse::Value(double u, double du, const double* derivparam) const { // In local coordinate system, value() of ellipse is: //(a*cos(u), b*sin(u)) // In global, it is (vector formula): // center + a_vec*cos(u) + b_vec*sin(u). // That's what is being computed here. // DeriVector2 c(this->center, derivparam); DeriVector2 f1(this->focus1, derivparam); DeriVector2 emaj = f1.subtr(c).getNormalized(); DeriVector2 emin = emaj.rotate90ccw(); double b, db; b = *(this->radmin); db = this->radmin == derivparam ? 1.0 : 0.0; double a, da; a = this->getRadMaj(c, f1, b, db, da); DeriVector2 a_vec = emaj.multD(a, da); DeriVector2 b_vec = emin.multD(b, db); // // sin, cos with derivatives: double co, dco, si, dsi; co = std::cos(u); dco = -std::sin(u) * du; si = std::sin(u); dsi = std::cos(u) * du; // point of ellipse at parameter value of u, in global coordinates return a_vec.multD(co, dco).sum(b_vec.multD(si, dsi)).sum(c); } int Ellipse::PushOwnParams(VEC_pD& pvec) { int cnt = 0; pvec.push_back(center.x); cnt++; pvec.push_back(center.y); cnt++; pvec.push_back(focus1.x); cnt++; pvec.push_back(focus1.y); cnt++; pvec.push_back(radmin); cnt++; return cnt; } void Ellipse::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { center.x = pvec[cnt]; cnt++; center.y = pvec[cnt]; cnt++; focus1.x = pvec[cnt]; cnt++; focus1.y = pvec[cnt]; cnt++; radmin = pvec[cnt]; cnt++; } Ellipse* Ellipse::Copy() { return new Ellipse(*this); } //---------------arc of ellipse int ArcOfEllipse::PushOwnParams(VEC_pD& pvec) { int cnt = 0; cnt += Ellipse::PushOwnParams(pvec); pvec.push_back(start.x); cnt++; pvec.push_back(start.y); cnt++; pvec.push_back(end.x); cnt++; pvec.push_back(end.y); cnt++; pvec.push_back(startAngle); cnt++; pvec.push_back(endAngle); cnt++; return cnt; } void ArcOfEllipse::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { Ellipse::ReconstructOnNewPvec(pvec, cnt); start.x = pvec[cnt]; cnt++; start.y = pvec[cnt]; cnt++; end.x = pvec[cnt]; cnt++; end.y = pvec[cnt]; cnt++; startAngle = pvec[cnt]; cnt++; endAngle = pvec[cnt]; cnt++; } ArcOfEllipse* ArcOfEllipse::Copy() { return new ArcOfEllipse(*this); } //---------------hyperbola // this function is exposed to allow reusing pre-filled derivectors in constraints code double Hyperbola::getRadMaj( const DeriVector2& center, const DeriVector2& f1, double b, double db, double& ret_dRadMaj ) const { double cf, dcf; cf = f1.subtr(center).length(dcf); double a, da; a = sqrt(cf * cf - b * b); da = (dcf * cf - db * b) / a; ret_dRadMaj = da; return a; } // returns major radius. The derivative by derivparam is returned into ret_dRadMaj argument. double Hyperbola::getRadMaj(double* derivparam, double& ret_dRadMaj) const { DeriVector2 c(center, derivparam); DeriVector2 f1(focus1, derivparam); return getRadMaj(c, f1, *radmin, radmin == derivparam ? 1.0 : 0.0, ret_dRadMaj); } // returns the major radius (plain value, no derivatives) double Hyperbola::getRadMaj() const { double dradmaj; // dummy return getRadMaj(nullptr, dradmaj); } DeriVector2 Hyperbola::CalculateNormal(const Point& p, const double* derivparam) const { // fill some vectors in DeriVector2 cv(center, derivparam); DeriVector2 f1v(focus1, derivparam); DeriVector2 pv(p, derivparam); // calculation. // focus2: DeriVector2 f2v = cv.linCombi(2.0, f1v, -1.0); // 2*cv - f1v // pf1, pf2 = vectors from p to focus1,focus2 DeriVector2 pf1 = f1v.subtr(pv).mult(-1.0); // <--- differs from ellipse normal calculation // code by inverting this vector DeriVector2 pf2 = f2v.subtr(pv); // return sum of normalized pf2, pf2 return pf1.getNormalized().sum(pf2.getNormalized()); } DeriVector2 Hyperbola::Value(double u, double du, const double* derivparam) const { // In local coordinate system, value() of hyperbola is: //(a*cosh(u), b*sinh(u)) // In global, it is (vector formula): // center + a_vec*cosh(u) + b_vec*sinh(u). // That's what is being computed here. // DeriVector2 c(this->center, derivparam); DeriVector2 f1(this->focus1, derivparam); DeriVector2 emaj = f1.subtr(c).getNormalized(); DeriVector2 emin = emaj.rotate90ccw(); double b, db; b = *(this->radmin); db = this->radmin == derivparam ? 1.0 : 0.0; double a, da; a = this->getRadMaj(c, f1, b, db, da); DeriVector2 a_vec = emaj.multD(a, da); DeriVector2 b_vec = emin.multD(b, db); // // sinh, cosh with derivatives: double co, dco, si, dsi; co = std::cosh(u); dco = std::sinh(u) * du; si = std::sinh(u); dsi = std::cosh(u) * du; // point of hyperbola at parameter value of u, in global coordinates return a_vec.multD(co, dco).sum(b_vec.multD(si, dsi)).sum(c); } int Hyperbola::PushOwnParams(VEC_pD& pvec) { int cnt = 0; pvec.push_back(center.x); cnt++; pvec.push_back(center.y); cnt++; pvec.push_back(focus1.x); cnt++; pvec.push_back(focus1.y); cnt++; pvec.push_back(radmin); cnt++; return cnt; } void Hyperbola::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { center.x = pvec[cnt]; cnt++; center.y = pvec[cnt]; cnt++; focus1.x = pvec[cnt]; cnt++; focus1.y = pvec[cnt]; cnt++; radmin = pvec[cnt]; cnt++; } Hyperbola* Hyperbola::Copy() { return new Hyperbola(*this); } //--------------- arc of hyperbola int ArcOfHyperbola::PushOwnParams(VEC_pD& pvec) { int cnt = 0; cnt += Hyperbola::PushOwnParams(pvec); pvec.push_back(start.x); cnt++; pvec.push_back(start.y); cnt++; pvec.push_back(end.x); cnt++; pvec.push_back(end.y); cnt++; pvec.push_back(startAngle); cnt++; pvec.push_back(endAngle); cnt++; return cnt; } void ArcOfHyperbola::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { Hyperbola::ReconstructOnNewPvec(pvec, cnt); start.x = pvec[cnt]; cnt++; start.y = pvec[cnt]; cnt++; end.x = pvec[cnt]; cnt++; end.y = pvec[cnt]; cnt++; startAngle = pvec[cnt]; cnt++; endAngle = pvec[cnt]; cnt++; } ArcOfHyperbola* ArcOfHyperbola::Copy() { return new ArcOfHyperbola(*this); } //---------------parabola DeriVector2 Parabola::CalculateNormal(const Point& p, const double* derivparam) const { // fill some vectors in DeriVector2 cv(vertex, derivparam); DeriVector2 f1v(focus1, derivparam); DeriVector2 pv(p, derivparam); // the normal is the vector from the focus to the intersection of ano thru the point p and // direction of the symmetry axis of the parabola with the directrix. As both point to directrix // and point to focus are of equal magnitude, we can work with unitary vectors to calculate the // normal, substraction of those vectors. return cv.subtr(f1v).getNormalized().subtr(f1v.subtr(pv).getNormalized()); } DeriVector2 Parabola::Value(double u, double du, const double* derivparam) const { // In local coordinate system, value() of parabola is: // P(U) = O + U*U/(4.*F)*XDir + U*YDir DeriVector2 c(this->vertex, derivparam); DeriVector2 f1(this->focus1, derivparam); DeriVector2 fv = f1.subtr(c); double f, df; f = fv.length(df); DeriVector2 xdir = fv.getNormalized(); DeriVector2 ydir = xdir.rotate90ccw(); DeriVector2 dirx = xdir.multD(u, du).multD(u, du).divD(4 * f, 4 * df); DeriVector2 diry = ydir.multD(u, du); DeriVector2 dir = dirx.sum(diry); DeriVector2 ret; // point of parabola at parameter value of u, in global coordinates ret = c.sum(dir); return ret; } int Parabola::PushOwnParams(VEC_pD& pvec) { int cnt = 0; pvec.push_back(vertex.x); cnt++; pvec.push_back(vertex.y); cnt++; pvec.push_back(focus1.x); cnt++; pvec.push_back(focus1.y); cnt++; return cnt; } void Parabola::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { vertex.x = pvec[cnt]; cnt++; vertex.y = pvec[cnt]; cnt++; focus1.x = pvec[cnt]; cnt++; focus1.y = pvec[cnt]; cnt++; } Parabola* Parabola::Copy() { return new Parabola(*this); } //--------------- arc of hyperbola int ArcOfParabola::PushOwnParams(VEC_pD& pvec) { int cnt = 0; cnt += Parabola::PushOwnParams(pvec); pvec.push_back(start.x); cnt++; pvec.push_back(start.y); cnt++; pvec.push_back(end.x); cnt++; pvec.push_back(end.y); cnt++; pvec.push_back(startAngle); cnt++; pvec.push_back(endAngle); cnt++; return cnt; } void ArcOfParabola::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { Parabola::ReconstructOnNewPvec(pvec, cnt); start.x = pvec[cnt]; cnt++; start.y = pvec[cnt]; cnt++; end.x = pvec[cnt]; cnt++; end.y = pvec[cnt]; cnt++; startAngle = pvec[cnt]; cnt++; endAngle = pvec[cnt]; cnt++; } ArcOfParabola* ArcOfParabola::Copy() { return new ArcOfParabola(*this); } // bspline DeriVector2 BSpline::CalculateNormal(const Point& p, const double* derivparam) const { // even if this method is call CalculateNormal, the returned vector is not the normal strictu // sensus but a normal vector, where the vector should point to the left when one walks along // the curve from start to end. // // https://forum.freecad.org/viewtopic.php?f=10&t=26312#p209486 if (mult[0] > degree && mult[mult.size() - 1] > degree) { // if endpoints through end poles if (*p.x == *start.x && *p.y == *start.y) { // and you are asking about the normal at start point // then tangency is defined by first to second poles DeriVector2 endpt(this->poles[1], derivparam); DeriVector2 spt(this->poles[0], derivparam); DeriVector2 tg = endpt.subtr(spt); return tg.rotate90ccw(); } if (*p.x == *end.x && *p.y == *end.y) { // and you are asking about the normal at end point // then tangency is defined by last to last but one poles DeriVector2 endpt(this->poles[poles.size() - 1], derivparam); DeriVector2 spt(this->poles[poles.size() - 2], derivparam); DeriVector2 tg = endpt.subtr(spt); return tg.rotate90ccw(); } // another point and we have no clue until we implement De Boor return {}; } // either periodic or abnormal endpoint multiplicity, we have no clue so currently // unsupported return {}; } DeriVector2 BSpline::CalculateNormal(const double* param, const double* derivparam) const { // TODO: is there any advantage in making this a `static`? size_t startpole = 0; for (size_t j = 1; j < mult.size() && *(knots[j]) <= *param; ++j) { startpole += mult[j]; } if (!periodic && startpole >= poles.size()) { startpole = poles.size() - degree - 1; } auto polexat = [&](size_t i) { return poles[(startpole + i) % poles.size()].x; }; auto poleyat = [&](size_t i) { return poles[(startpole + i) % poles.size()].y; }; auto weightat = [&](size_t i) { return weights[(startpole + i) % weights.size()]; }; double xsum, xslopesum; double ysum, yslopesum; double wsum, wslopesum; valueHomogenous(*param, &xsum, &ysum, &wsum, &xslopesum, &yslopesum, &wslopesum); // Tangent vector // This should in principle be identical to error gradient wrt curve parameter in // point-on-object DeriVector2 result(wsum * xslopesum - wslopesum * xsum, wsum * yslopesum - wslopesum * ysum); size_t numpoints = degree + 1; // get dx, dy of the normal as well for (size_t i = 0; i < numpoints; ++i) { if (derivparam != polexat(i) && derivparam != poleyat(i) && derivparam != weightat(i)) { continue; } VEC_D d(numpoints); d[i] = 1; double factor = splineValue(*param, startpole + degree, degree, d, flattenedknots); VEC_D sd(numpoints - 1); if (i > 0) { sd[i - 1] = 1.0 / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } if (i < numpoints - 1) { sd[i] = -1.0 / (flattenedknots[startpole + i + 1 + degree] - flattenedknots[startpole + i + 1]); } double slopefactor = splineValue(*param, startpole + degree, degree - 1, sd, flattenedknots); if (derivparam == polexat(i)) { result.dx = *weightat(i) * (wsum * slopefactor - wslopesum * factor); } else if (derivparam == poleyat(i)) { result.dy = *weightat(i) * (wsum * slopefactor - wslopesum * factor); } else if (derivparam == weightat(i)) { result.dx = degree * (factor * (xslopesum - wslopesum * (*polexat(i))) - slopefactor * (xsum - wsum * (*polexat(i)))); result.dy = degree * (factor * (yslopesum - wslopesum * (*poleyat(i))) - slopefactor * (ysum - wsum * (*poleyat(i)))); } break; } // the curve parameter being used by the constraint is not known to the geometry (there can be // many tangent constraints on the same curve after all). Assume that this is the param // provided. if (derivparam != param) { return result.rotate90ccw(); } // derivparam == param now. Done this way just to reduce "cognitive complexity". VEC_D sd(numpoints - 1), ssd(numpoints - 2); for (size_t i = 1; i < numpoints; ++i) { sd[i - 1] = (*weightat(i) - *weightat(i - 1)) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } for (size_t i = 1; i < numpoints - 1; ++i) { ssd[i - 1] = (sd[i] - sd[i - 1]) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } double wslopeslopesum = degree * (degree - 1) * BSpline::splineValue(*param, startpole + degree, degree - 2, ssd, flattenedknots); for (size_t i = 1; i < numpoints; ++i) { sd[i - 1] = (*polexat(i) * *weightat(i) - *polexat(i - 1) * *weightat(i - 1)) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } for (size_t i = 1; i < numpoints - 1; ++i) { ssd[i - 1] = (sd[i] - sd[i - 1]) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } double xslopeslopesum = degree * (degree - 1) * BSpline::splineValue(*param, startpole + degree, degree - 2, ssd, flattenedknots); for (size_t i = 1; i < numpoints; ++i) { sd[i - 1] = (*poleyat(i) * *weightat(i) - *poleyat(i - 1) * *weightat(i - 1)) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } for (size_t i = 1; i < numpoints - 1; ++i) { ssd[i - 1] = (sd[i] - sd[i - 1]) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } double yslopeslopesum = degree * (degree - 1) * BSpline::splineValue(*param, startpole + degree, degree - 2, ssd, flattenedknots); result.dx = wsum * xslopeslopesum - wslopeslopesum * xsum; result.dy = wsum * yslopeslopesum - wslopeslopesum * ysum; return result.rotate90ccw(); } DeriVector2 BSpline::Value(double u, double /*du*/, const double* /*derivparam*/) const { // TODO: is there any advantage in making this a `static`? size_t startpole = 0; for (size_t j = 1; j < mult.size() && *(knots[j]) <= u; ++j) { startpole += mult[j]; } if (!periodic && startpole >= poles.size()) { startpole = poles.size() - degree - 1; } // double xsum = 0., xslopesum = 0.; // double ysum = 0., yslopesum = 0.; // double wsum = 0., wslopesum = 0.; auto polexat = [&](size_t i) { return poles[(startpole + i) % poles.size()].x; }; auto poleyat = [&](size_t i) { return poles[(startpole + i) % poles.size()].y; }; auto weightat = [&](size_t i) { return weights[(startpole + i) % weights.size()]; }; size_t numpoints = degree + 1; // Tangent vector // This should in principle be identical to error gradient wrt curve parameter in // point-on-object VEC_D d(numpoints); for (size_t i = 0; i < numpoints; ++i) { d[i] = *polexat(i) * *weightat(i); } double xsum = splineValue(u, startpole + degree, degree, d, flattenedknots); for (size_t i = 0; i < numpoints; ++i) { d[i] = *poleyat(i) * *weightat(i); } double ysum = splineValue(u, startpole + degree, degree, d, flattenedknots); for (size_t i = 0; i < numpoints; ++i) { d[i] = *weightat(i); } double wsum = splineValue(u, startpole + degree, degree, d, flattenedknots); d.resize(numpoints - 1); for (size_t i = 1; i < numpoints; ++i) { d[i - 1] = (*polexat(i) * *weightat(i) - *polexat(i - 1) * *weightat(i - 1)) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } double xslopesum = degree * splineValue(u, startpole + degree, degree - 1, d, flattenedknots); for (size_t i = 1; i < numpoints; ++i) { d[i - 1] = (*poleyat(i) * *weightat(i) - *poleyat(i - 1) * *weightat(i - 1)) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } double yslopesum = degree * splineValue(u, startpole + degree, degree - 1, d, flattenedknots); for (size_t i = 1; i < numpoints; ++i) { d[i - 1] = (*weightat(i) - *weightat(i - 1)) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } double wslopesum = degree * splineValue(u, startpole + degree, degree - 1, d, flattenedknots); return { xsum / wsum, ysum / wsum, (wsum * xslopesum - wslopesum * xsum) / wsum / wsum, (wsum * yslopesum - wslopesum * ysum) / wsum / wsum }; } void BSpline::valueHomogenous( const double u, double* xw, double* yw, double* w, double* dxwdu, double* dywdu, double* dwdu ) const { // TODO: is there any advantage in making this a `static`? size_t startpole = 0; for (size_t j = 1; j < mult.size() && *(knots[j]) <= u; ++j) { startpole += mult[j]; } if (!periodic && startpole >= poles.size()) { startpole = poles.size() - degree - 1; } auto polexat = [&](size_t i) { return poles[(startpole + i) % poles.size()].x; }; auto poleyat = [&](size_t i) { return poles[(startpole + i) % poles.size()].y; }; auto weightat = [&](size_t i) { return weights[(startpole + i) % weights.size()]; }; size_t numpoints = degree + 1; VEC_D d(numpoints); for (size_t i = 0; i < numpoints; ++i) { d[i] = *polexat(i) * *weightat(i); } *xw = BSpline::splineValue(u, startpole + degree, degree, d, flattenedknots); for (size_t i = 0; i < numpoints; ++i) { d[i] = *poleyat(i) * *weightat(i); } *yw = BSpline::splineValue(u, startpole + degree, degree, d, flattenedknots); for (size_t i = 0; i < numpoints; ++i) { d[i] = *weightat(i); } *w = BSpline::splineValue(u, startpole + degree, degree, d, flattenedknots); d.resize(numpoints - 1); for (size_t i = 1; i < numpoints; ++i) { d[i - 1] = (*polexat(i) * *weightat(i) - *polexat(i - 1) * *weightat(i - 1)) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } *dxwdu = degree * BSpline::splineValue(u, startpole + degree, degree - 1, d, flattenedknots); for (size_t i = 1; i < numpoints; ++i) { d[i - 1] = (*poleyat(i) * *weightat(i) - *poleyat(i - 1) * *weightat(i - 1)) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } *dywdu = degree * BSpline::splineValue(u, startpole + degree, degree - 1, d, flattenedknots); for (size_t i = 1; i < numpoints; ++i) { d[i - 1] = (*weightat(i) - *weightat(i - 1)) / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); } *dwdu = degree * BSpline::splineValue(u, startpole + degree, degree - 1, d, flattenedknots); } int BSpline::PushOwnParams(VEC_pD& pvec) { std::size_t cnt = 0; for (const auto& pole : poles) { pvec.push_back(pole.x); pvec.push_back(pole.y); } cnt = cnt + poles.size() * 2; pvec.insert(pvec.end(), weights.begin(), weights.end()); cnt = cnt + weights.size(); pvec.insert(pvec.end(), knots.begin(), knots.end()); cnt = cnt + knots.size(); pvec.push_back(start.x); cnt++; pvec.push_back(start.y); cnt++; pvec.push_back(end.x); cnt++; pvec.push_back(end.y); cnt++; return static_cast(cnt); } void BSpline::ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) { for (auto& pole : poles) { pole.x = pvec[cnt]; cnt++; pole.y = pvec[cnt]; cnt++; } for (auto& weight : weights) { weight = pvec[cnt]; cnt++; } for (auto& knot : knots) { knot = pvec[cnt]; cnt++; } start.x = pvec[cnt]; cnt++; start.y = pvec[cnt]; cnt++; end.x = pvec[cnt]; cnt++; end.y = pvec[cnt]; cnt++; } BSpline* BSpline::Copy() { return new BSpline(*this); } double BSpline::getLinCombFactor(double x, size_t k, size_t i, unsigned int p) { // Adapted to C++ from the python implementation in the Wikipedia page for de Boor algorithm // https://en.wikipedia.org/wiki/De_Boor%27s_algorithm. // FIXME: This should probably be guaranteed by now, and done somewhere else // To elaborate: `flattenedknots` should be set up as soon as `knots` // and `mult` have been defined after creating the B-spline. // However, in the future the values of `knots` could go into the solver // as well, when alternatives may be needed to keep `flattenedknots` updated. // Slightly more detailed discussion here: // https://github.com/FreeCAD/FreeCAD/pull/7484#discussion_r1020858392 if (flattenedknots.empty()) { setupFlattenedKnots(); } std::vector d(p + 1, 0.0); // Ensure this is within range int idxOfPole = static_cast(i) + p - static_cast(k); if (idxOfPole < 0 || idxOfPole > static_cast(p)) { return 0.0; } d[idxOfPole] = 1.0; for (size_t r = 1; r < p + 1; ++r) { for (size_t j = p; j > r - 1; --j) { double alpha = (x - flattenedknots[j + k - p]) / (flattenedknots[j + 1 + k - r] - flattenedknots[j + k - p]); d[j] = (1.0 - alpha) * d[j - 1] + alpha * d[j]; } } return d[p]; } double BSpline::splineValue(double x, size_t k, unsigned int p, VEC_D& d, const VEC_D& flatknots) { for (size_t r = 1; r < p + 1; ++r) { for (size_t j = p; j > r - 1; --j) { double alpha = (x - flatknots[j + k - p]) / (flatknots[j + 1 + k - r] - flatknots[j + k - p]); d[j] = (1.0 - alpha) * d[j - 1] + alpha * d[j]; } } return p < d.size() ? d[p] : 0.0; } void BSpline::setupFlattenedKnots() { flattenedknots.clear(); for (size_t i = 0; i < knots.size(); ++i) { flattenedknots.insert(flattenedknots.end(), mult[i], *knots[i]); } // Adjust for periodic: see OCC documentation for explanation if (periodic) { double period = *knots.back() - *knots.front(); int c = degree + 1 - mult[0]; // number of knots to pad // Add capacity so that iterators remain valid flattenedknots.reserve(flattenedknots.size() + 2 * c); // get iterators first for convenience auto frontStart = flattenedknots.end() - mult.back() - c; auto frontEnd = flattenedknots.end() - mult.back(); auto backStart = flattenedknots.begin() + mult.front(); auto backEnd = flattenedknots.begin() + mult.front() + c; // creating new vectors because above iterators can become invalidated upon insert std::vector frontNew(frontStart, frontEnd); std::vector backNew(backStart, backEnd); flattenedknots.insert(flattenedknots.end(), backNew.begin(), backNew.end()); flattenedknots.insert(flattenedknots.begin(), frontNew.begin(), frontNew.end()); for (int i = 0; i < c; ++i) { *(flattenedknots.begin() + i) -= period; *(flattenedknots.end() - 1 - i) += period; } } } } // namespace GCS