FreeCAD/src/Mod/Sketcher/App/planegcs/Geo.cpp
Abdullah Tahiri e53dc90c9a Sketcher: BSpline simplified endpoint tangency/perpendicularity solver implementation
=====================================================================================

Support for tangency/perpendicularity using angle via point for BSpline with appropriate
endpoint multiplicity so that the endpoints goes thru the first and last poles (control points).

Warning: Not applicable to periodic BSplines.
Warning: Not applicable to any non-periodic BSpline with inappropriate endpoint conditions.
2017-01-15 00:42:25 +01:00

724 lines
21 KiB
C++

/***************************************************************************
* Copyright (c) Victor Titov (DeepSOIC) *
* (vv.titov@gmail.com) 2014 *
* *
* 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 "Geo.h"
#include <cassert>
namespace GCS{
DeriVector2::DeriVector2(const Point &p, double *derivparam)
{
x=*p.x; y=*p.y;
dx=0.0; 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;
} else {
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);
} else {
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 DeriVector2(x/val,y/val,
dx/val - x*dval/(val*val),
dy/val - y*dval/(val*val)
);
}
DeriVector2 Curve::Value(double /*u*/, double /*du*/, double* /*derivparam*/)
{
assert(false /*Value() is not implemented*/);
return DeriVector2();
}
//----------------Line
DeriVector2 Line::CalculateNormal(Point &/*p*/, double* derivparam)
{
DeriVector2 p1v(p1, derivparam);
DeriVector2 p2v(p2, derivparam);
return p2v.subtr(p1v).rotate90ccw();
}
DeriVector2 Line::Value(double u, double du, double* derivparam)
{
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()
{
Line* crv = new Line(*this);
return crv;
}
//---------------circle
DeriVector2 Circle::CalculateNormal(Point &p, double* derivparam)
{
DeriVector2 cv (center, derivparam);
DeriVector2 pv (p, derivparam);
return cv.subtr(pv);
}
DeriVector2 Circle::Value(double u, double du, double* derivparam)
{
//(x,y) = center + cos(u)*(r,0) + sin(u)*(0,r)
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()
{
Circle* crv = new Circle(*this);
return crv;
}
//------------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()
{
Arc* crv = new Arc(*this);
return crv;
}
//--------------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)
{
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)
{
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()
{
double dradmaj;//dummy
return getRadMaj(0,dradmaj);
}
DeriVector2 Ellipse::CalculateNormal(Point &p, double* derivparam)
{
//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
DeriVector2 ret = pf1.getNormalized().sum(pf2.getNormalized());
//numeric derivatives for testing
#if 0 //make sure to enable DEBUG_DERIVS when enabling
if(derivparam) {
double const eps = 0.00001;
double oldparam = *derivparam;
DeriVector2 v0 = this->CalculateNormal(p);
*derivparam += eps;
DeriVector2 vr = this->CalculateNormal(p);
*derivparam = oldparam - eps;
DeriVector2 vl = this->CalculateNormal(p);
*derivparam = oldparam;
//If not nasty, real derivative should be between left one and right one
DeriVector2 numretl ((v0.x-vl.x)/eps, (v0.y-vl.y)/eps);
DeriVector2 numretr ((vr.x-v0.x)/eps, (vr.y-v0.y)/eps);
assert(ret.dx <= std::max(numretl.x,numretr.x) );
assert(ret.dx >= std::min(numretl.x,numretr.x) );
assert(ret.dy <= std::max(numretl.y,numretr.y) );
assert(ret.dy >= std::min(numretl.y,numretr.y) );
}
#endif
return ret;
}
DeriVector2 Ellipse::Value(double u, double du, double* derivparam)
{
//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.
// <construct a_vec, b_vec>
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);
// </construct a_vec, b_vec>
// 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;
DeriVector2 ret; //point of ellipse at parameter value of u, in global coordinates
ret = a_vec.multD(co,dco).sum(b_vec.multD(si,dsi)).sum(c);
return ret;
}
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()
{
Ellipse* crv = new Ellipse(*this);
return crv;
}
//---------------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()
{
ArcOfEllipse* crv = new ArcOfEllipse(*this);
return crv;
}
//---------------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)
{
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)
{
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()
{
double dradmaj;//dummy
return getRadMaj(0,dradmaj);
}
DeriVector2 Hyperbola::CalculateNormal(Point &p, double* derivparam)
{
//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
DeriVector2 ret = pf1.getNormalized().sum(pf2.getNormalized());
return ret;
}
DeriVector2 Hyperbola::Value(double u, double du, double* derivparam)
{
//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.
// <construct a_vec, b_vec>
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);
// </construct a_vec, b_vec>
// 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;
DeriVector2 ret; //point of hyperbola at parameter value of u, in global coordinates
ret = a_vec.multD(co,dco).sum(b_vec.multD(si,dsi)).sum(c);
return ret;
}
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()
{
Hyperbola* crv = new Hyperbola(*this);
return crv;
}
//--------------- 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()
{
ArcOfHyperbola* crv = new ArcOfHyperbola(*this);
return crv;
}
//---------------parabola
DeriVector2 Parabola::CalculateNormal(Point &p, double* derivparam)
{
//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 symetry 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.
DeriVector2 ret = cv.subtr(f1v).getNormalized().subtr(f1v.subtr(pv).getNormalized());
return ret;
}
DeriVector2 Parabola::Value(double u, double du, double* derivparam)
{
//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()
{
Parabola* crv = new Parabola(*this);
return crv;
}
//--------------- 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()
{
ArcOfParabola* crv = new ArcOfParabola(*this);
return crv;
}
// bspline
DeriVector2 BSpline::CalculateNormal(Point& p, double* derivparam)
{
// place holder
DeriVector2 ret;
if (mult[0] > degree && mult[mult.size()-1] > degree) {
// if endpoints thru 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 npt(this->poles[2], derivparam); // next pole to decide normal direction
DeriVector2 tg = endpt.subtr(spt);
DeriVector2 nv = npt.subtr(spt);
if ( tg.scalarProd(nv) > 0 )
ret = tg.rotate90cw();
else
ret = tg.rotate90ccw();
}
else 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 npt(this->poles[poles.size()-3], derivparam); // next pole to decide normal direction
DeriVector2 tg = endpt.subtr(spt);
DeriVector2 nv = npt.subtr(spt);
if ( tg.scalarProd(nv) > 0 )
ret = tg.rotate90ccw();
else
ret = tg.rotate90cw();
} else {
// another point and we have no clue until we implement De Boor
ret = DeriVector2();
}
}
else {
// either periodic or abnormal endpoint multiplicity, we have no clue so currently unsupported
ret = DeriVector2();
}
return ret;
}
DeriVector2 BSpline::Value(double u, double du, double* derivparam)
{
// place holder
DeriVector2 ret = DeriVector2();
return ret;
}
int BSpline::PushOwnParams(VEC_pD &pvec)
{
int cnt=0;
for(VEC_P::const_iterator it = poles.begin(); it != poles.end(); ++it) {
pvec.push_back( (*it).x );
pvec.push_back( (*it).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 cnt;
}
void BSpline::ReconstructOnNewPvec(VEC_pD &pvec, int &cnt)
{
for(VEC_P::iterator it = poles.begin(); it != poles.end(); ++it) {
(*it).x = pvec[cnt]; cnt++;
(*it).y = pvec[cnt]; cnt++;
}
for(VEC_pD::iterator it = weights.begin(); it != weights.end(); ++it) {
(*it) = pvec[cnt]; cnt++;
}
for(VEC_pD::iterator it = knots.begin(); it != knots.end(); ++it) {
(*it) = 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()
{
BSpline* crv = new BSpline(*this);
return crv;
}
}//namespace GCS