// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2011 Konstantinos Poulios * * * * 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 * * * ***************************************************************************/ #ifdef _MSC_VER # pragma warning(disable : 4251) #endif #include #include #include #define DEBUG_DERIVS 0 #if DEBUG_DERIVS # include #endif #include #include "Constraints.h" namespace GCS { /////////////////////////////////////// // Constraints /////////////////////////////////////// Constraint::Constraint() : origpvec(0) , pvec(0) , scale(1.) , tag(0) , pvecChangedFlag(true) , driving(true) , internalAlignment(Alignment::NoInternalAlignment) {} void Constraint::redirectParams(const MAP_pD_pD& redirectionmap) { int i = 0; for (VEC_pD::iterator param = origpvec.begin(); param != origpvec.end(); ++param, i++) { MAP_pD_pD::const_iterator it = redirectionmap.find(*param); if (it != redirectionmap.end()) { pvec[i] = it->second; } } pvecChangedFlag = true; } void Constraint::revertParams() { pvec = origpvec; pvecChangedFlag = true; } ConstraintType Constraint::getTypeId() { return None; } void Constraint::rescale(double coef) { scale = coef * 1.0; } double Constraint::maxStep(MAP_pD_D& /*dir*/, double lim) { return lim; } int Constraint::findParamInPvec(double* param) { int ret = -1; for (std::size_t i = 0; i < pvec.size(); i++) { if (param == pvec[i]) { ret = static_cast(i); break; } } return ret; } // -------------------------------------------------------- // Equal ConstraintEqual::ConstraintEqual(double* p1, double* p2, double p1p2ratio) : ratio(p1p2ratio) { pvec.push_back(p1); pvec.push_back(p2); origpvec = pvec; rescale(); } ConstraintType ConstraintEqual::getTypeId() { return Equal; } double ConstraintEqual::error() { return scale * (*param1() - ratio * (*param2())); } double ConstraintEqual::grad(double* param) { double deriv = 0.; if (param == param1()) { deriv += 1; } if (param == param2()) { deriv += -1; } return scale * deriv; } // -------------------------------------------------------- // Weighted Linear Combination ConstraintWeightedLinearCombination::ConstraintWeightedLinearCombination( size_t givennumpoles, const std::vector& givenpvec, const std::vector& givenfactors ) : factors(givenfactors) , numpoles(givennumpoles) { pvec = givenpvec; assert(pvec.size() == 2 * numpoles + 1); assert(factors.size() == numpoles); origpvec = pvec; rescale(); } ConstraintType ConstraintWeightedLinearCombination::getTypeId() { return WeightedLinearCombination; } double ConstraintWeightedLinearCombination::error() { // Explanation of the math here: // https://forum.freecad.org/viewtopic.php?f=9&t=71130&start=120#p635538 double sum = 0; double wsum = 0; for (size_t i = 0; i < numpoles; ++i) { double wcontrib = *weightat(i) * factors[i]; wsum += wcontrib; sum += *poleat(i) * wcontrib; } return scale * ((*thepoint()) * wsum - sum); } double ConstraintWeightedLinearCombination::grad(double* param) { // Equations are from here: // https://forum.freecad.org/viewtopic.php?f=9&t=71130&start=120#p635538 double deriv = 0.; if (param == thepoint()) { // Eq. (11) double wsum = 0; for (size_t i = 0; i < numpoles; ++i) { wsum += *weightat(i) * factors[i]; } deriv = wsum; return scale * deriv; } for (size_t i = 0; i < numpoles; ++i) { if (param == poleat(i)) { // Eq. (12) deriv = -(*weightat(i) * factors[i]); return scale * deriv; } if (param == weightat(i)) { // Eq. (13) deriv = (*thepoint() - *poleat(i)) * factors[i]; return scale * deriv; } } return scale * deriv; } // -------------------------------------------------------- // Center of Gravity ConstraintCenterOfGravity::ConstraintCenterOfGravity( const std::vector& givenpvec, const std::vector& givenweights ) : weights(givenweights) , numpoints(givenpvec.size() - 1) { pvec = givenpvec; assert(pvec.size() > 1); assert(weights.size() == numpoints); origpvec = pvec; rescale(); } ConstraintType ConstraintCenterOfGravity::getTypeId() { return CenterOfGravity; } double ConstraintCenterOfGravity::error() { double sum = 0; for (size_t i = 0; i < numpoints; ++i) { sum += *pointat(i) * weights[i]; } return scale * (*thecenter() - sum); } double ConstraintCenterOfGravity::grad(double* param) { double deriv = 0.; if (param == thecenter()) { deriv = 1; } for (size_t i = 0; i < numpoints; ++i) { if (param == pointat(i)) { deriv = -weights[i]; } } return scale * deriv; } // -------------------------------------------------------- // Slope at B-spline knot ConstraintSlopeAtBSplineKnot::ConstraintSlopeAtBSplineKnot(BSpline& b, Line& l, size_t knotindex) { // set up pvec: pole x-coords, pole y-coords, pole weights, // line point 1 coords, line point 2 coords numpoles = b.degree - b.mult[knotindex] + 1; // slope at knot doesn't make sense if there's only C0 continuity assert(numpoles >= 2); pvec.reserve(3 * numpoles + 4); // `startpole` is the first pole affecting the knot with `knotindex` size_t startpole = 0; // See `System::addConstraintInternalAlignmentKnotPoint()` for some elaboration for (size_t j = 1; j <= knotindex; ++j) { startpole += b.mult[j]; } if (!b.periodic && startpole >= b.poles.size()) { startpole = b.poles.size() - 1; } for (size_t i = 0; i < numpoles; ++i) { pvec.push_back(b.poles[(startpole + i) % b.poles.size()].x); } for (size_t i = 0; i < numpoles; ++i) { pvec.push_back(b.poles[(startpole + i) % b.poles.size()].y); } for (size_t i = 0; i < numpoles; ++i) { pvec.push_back(b.weights[(startpole + i) % b.weights.size()]); } pvec.push_back(l.p1.x); pvec.push_back(l.p1.y); pvec.push_back(l.p2.x); pvec.push_back(l.p2.y); // Set up factors to get slope at knot point std::vector tempfactors((numpoles + 1), 1.0 / (numpoles + 1)); factors.resize(numpoles); slopefactors.resize(numpoles); for (size_t i = 0; i < numpoles + 1; ++i) { tempfactors[i] = b.getLinCombFactor( *(b.knots[knotindex]), startpole + b.degree, startpole + i, b.degree - 1 ) / (b.flattenedknots[startpole + b.degree + i] - b.flattenedknots[startpole + i]); } for (size_t i = 0; i < numpoles; ++i) { factors[i] = b.getLinCombFactor(*(b.knots[knotindex]), startpole + b.degree, startpole + i); slopefactors[i] = b.degree * (tempfactors[i] - tempfactors[i + 1]); } origpvec = pvec; ConstraintSlopeAtBSplineKnot::rescale(); } ConstraintType ConstraintSlopeAtBSplineKnot::getTypeId() { return SlopeAtBSplineKnot; } void ConstraintSlopeAtBSplineKnot::rescale(double coef) { double slopex = 0., slopey = 0.; for (size_t i = 0; i < numpoles; ++i) { slopex += *polexat(i) * slopefactors[i]; slopey += *poleyat(i) * slopefactors[i]; } scale = coef / sqrt((slopex * slopex + slopey * slopey)); } double ConstraintSlopeAtBSplineKnot::error() { double xsum = 0., xslopesum = 0.; double ysum = 0., yslopesum = 0.; double wsum = 0., wslopesum = 0.; for (size_t i = 0; i < numpoles; ++i) { double wcontrib = *weightat(i) * factors[i]; double wslopecontrib = *weightat(i) * slopefactors[i]; wsum += wcontrib; xsum += *polexat(i) * wcontrib; ysum += *poleyat(i) * wcontrib; wslopesum += wslopecontrib; xslopesum += *polexat(i) * wslopecontrib; yslopesum += *poleyat(i) * wslopecontrib; } // This is actually wsum^2 * the respective slopes // See Eq (19) from: // https://forum.freecad.org/viewtopic.php?f=9&t=71130&start=120#p635538 double slopex = wsum * xslopesum - wslopesum * xsum; double slopey = wsum * yslopesum - wslopesum * ysum; // Normalizing it ensures that the cross product is not zero just because // one vector is zero. double linex = *linep2x() - *linep1x(); double liney = *linep2y() - *linep1y(); double dirx = linex / sqrt(linex * linex + liney * liney); double diry = liney / sqrt(linex * linex + liney * liney); // error is the cross product return scale * (slopex * diry - slopey * dirx); } double ConstraintSlopeAtBSplineKnot::grad(double* param) { // Equations are from here: // https://forum.freecad.org/viewtopic.php?f=9&t=71130&start=120#p635538 double result = 0.0; double linex = *linep2x() - *linep1x(); double liney = *linep2y() - *linep1y(); double dirx = linex / sqrt(linex * linex + liney * liney); double diry = liney / sqrt(linex * linex + liney * liney); for (size_t i = 0; i < numpoles; ++i) { if (param == polexat(i)) { // Eq. (21) double wsum = 0., wslopesum = 0.; for (size_t j = 0; j < numpoles; ++j) { double wcontrib = *weightat(j) * factors[j]; double wslopecontrib = *weightat(j) * slopefactors[j]; wsum += wcontrib; wslopesum += wslopecontrib; } result = (wsum * slopefactors[i] - wslopesum * factors[i]) * diry; return scale * result; } if (param == poleyat(i)) { // Eq. (21) double wsum = 0., wslopesum = 0.; for (size_t i = 0; i < numpoles; ++i) { double wcontrib = *weightat(i) * factors[i]; double wslopecontrib = *weightat(i) * slopefactors[i]; wsum += wcontrib; wslopesum += wslopecontrib; } result = -(wsum * slopefactors[i] - wslopesum * factors[i]) * dirx; return scale * result; } if (param == weightat(i)) { // Eq. (22) double xsum = 0., xslopesum = 0.; double ysum = 0., yslopesum = 0.; for (size_t j = 0; j < numpoles; ++j) { double wcontrib = *weightat(j) * factors[j]; double wslopecontrib = *weightat(j) * slopefactors[j]; xsum += wcontrib * (*polexat(j) - *polexat(i)); xslopesum += wslopecontrib * (*polexat(j) - *polexat(i)); ysum += wcontrib * (*poleyat(j) - *poleyat(i)); yslopesum += wslopecontrib * (*poleyat(j) - *poleyat(i)); } result = (factors[i] * xslopesum - slopefactors[i] * xsum) * diry - (factors[i] * yslopesum - slopefactors[i] * ysum) * dirx; return scale * result; } } double slopex = 0., slopey = 0.; auto getSlopes = [&]() { double xsum = 0., xslopesum = 0.; double ysum = 0., yslopesum = 0.; double wsum = 0., wslopesum = 0.; for (size_t i = 0; i < numpoles; ++i) { double wcontrib = *weightat(i) * factors[i]; double wslopecontrib = *weightat(i) * slopefactors[i]; wsum += wcontrib; xsum += *polexat(i) * wcontrib; ysum += *poleyat(i) * wcontrib; wslopesum += wslopecontrib; xslopesum += *polexat(i) * wslopecontrib; yslopesum += *poleyat(i) * wslopecontrib; } // This is actually wsum^2 * the respective slopes slopex = wsum * xslopesum - wslopesum * xsum; slopey = wsum * yslopesum - wslopesum * ysum; }; if (param == linep1x()) { getSlopes(); double dDirxDLinex = (liney * liney) / pow(linex * linex + liney * liney, 1.5); double dDiryDLinex = -(linex * liney) / pow(linex * linex + liney * liney, 1.5); // NOTE: d(linex)/d(x1) = -1 result = slopex * (-dDiryDLinex) - slopey * (-dDirxDLinex); return scale * result; } if (param == linep2x()) { getSlopes(); double dDirxDLinex = (liney * liney) / pow(linex * linex + liney * liney, 1.5); double dDiryDLinex = -(linex * liney) / pow(linex * linex + liney * liney, 1.5); // NOTE: d(linex)/d(x2) = 1 result = slopex * dDiryDLinex - slopey * dDirxDLinex; return scale * result; } if (param == linep1y()) { getSlopes(); double dDirxDLiney = -(linex * liney) / pow(linex * linex + liney * liney, 1.5); double dDiryDLiney = (linex * linex) / pow(linex * linex + liney * liney, 1.5); // NOTE: d(liney)/d(y1) = -1 result = slopex * (-dDiryDLiney) - slopey * (-dDirxDLiney); return scale * result; } if (param == linep2y()) { getSlopes(); double dDirxDLiney = -(linex * liney) / pow(linex * linex + liney * liney, 1.5); double dDiryDLiney = (linex * linex) / pow(linex * linex + liney * liney, 1.5); // NOTE: d(liney)/d(y2) = 1 result = slopex * dDiryDLiney - slopey * dDirxDLiney; return scale * result; } return scale * result; } // -------------------------------------------------------- // Point On BSpline ConstraintPointOnBSpline::ConstraintPointOnBSpline( double* point, double* initparam, int coordidx, BSpline& b ) : bsp(b) { // This is always going to be true numpoints = bsp.degree + 1; pvec.reserve(2 + 2 * b.poles.size()); pvec.push_back(point); pvec.push_back(initparam); setStartPole(*initparam); for (size_t i = 0; i < b.poles.size(); ++i) { if (coordidx == 0) { pvec.push_back(b.poles[i].x); } else { pvec.push_back(b.poles[i].y); } } for (size_t i = 0; i < b.weights.size(); ++i) { pvec.push_back(b.weights[i]); } if (bsp.flattenedknots.empty()) { bsp.setupFlattenedKnots(); } origpvec = pvec; rescale(); } ConstraintType ConstraintPointOnBSpline::getTypeId() { return PointOnBSpline; } void ConstraintPointOnBSpline::setStartPole(double u) { // The startpole logic is repeated in a lot of places, // for example in GCS and slope at knot // find relevant poles startpole = 0; for (size_t j = 1; j < bsp.mult.size() && *(bsp.knots[j]) <= u; ++j) { startpole += bsp.mult[j]; } if (!bsp.periodic && startpole >= bsp.poles.size()) { startpole = bsp.poles.size() - bsp.degree - 1; } } double ConstraintPointOnBSpline::error() { if (*theparam() < bsp.flattenedknots[startpole + bsp.degree] || *theparam() > bsp.flattenedknots[startpole + bsp.degree + 1]) { setStartPole(*theparam()); } double sum = 0; double wsum = 0; // TODO: maybe make it global so it doesn't have to be created every time VEC_D d(numpoints); for (size_t i = 0; i < numpoints; ++i) { d[i] = *poleat(i) * *weightat(i); } sum = BSpline::splineValue(*theparam(), startpole + bsp.degree, bsp.degree, d, bsp.flattenedknots); for (size_t i = 0; i < numpoints; ++i) { d[i] = *weightat(i); } wsum = BSpline::splineValue(*theparam(), startpole + bsp.degree, bsp.degree, d, bsp.flattenedknots); // TODO: Change the poles as the point moves between pieces return scale * (*thepoint() * wsum - sum); } double ConstraintPointOnBSpline::grad(double* gcsparam) { double deriv = 0.; if (gcsparam == thepoint()) { VEC_D d(numpoints); for (size_t i = 0; i < numpoints; ++i) { d[i] = *weightat(i); } double wsum = BSpline::splineValue( *theparam(), startpole + bsp.degree, bsp.degree, d, bsp.flattenedknots ); deriv += wsum; } if (gcsparam == theparam()) { VEC_D d(numpoints - 1); for (size_t i = 1; i < numpoints; ++i) { d[i - 1] = (*poleat(i) * *weightat(i) - *poleat(i - 1) * *weightat(i - 1)) / (bsp.flattenedknots[startpole + i + bsp.degree] - bsp.flattenedknots[startpole + i]); } double slopevalue = BSpline::splineValue( *theparam(), startpole + bsp.degree, bsp.degree - 1, d, bsp.flattenedknots ); for (size_t i = 1; i < numpoints; ++i) { d[i - 1] = (*weightat(i) - *weightat(i - 1)) / (bsp.flattenedknots[startpole + i + bsp.degree] - bsp.flattenedknots[startpole + i]); } double wslopevalue = BSpline::splineValue( *theparam(), startpole + bsp.degree, bsp.degree - 1, d, bsp.flattenedknots ); deriv += (*thepoint() * wslopevalue - slopevalue) * bsp.degree; } for (size_t i = 0; i < numpoints; ++i) { if (gcsparam == poleat(i)) { auto factorsI = bsp.getLinCombFactor(*theparam(), startpole + bsp.degree, startpole + i); deriv += -(*weightat(i) * factorsI); } if (gcsparam == weightat(i)) { auto factorsI = bsp.getLinCombFactor(*theparam(), startpole + bsp.degree, startpole + i); deriv += (*thepoint() - *poleat(i)) * factorsI; } } return scale * deriv; } // Difference ConstraintDifference::ConstraintDifference(double* p1, double* p2, double* d) { pvec.push_back(p1); pvec.push_back(p2); pvec.push_back(d); origpvec = pvec; rescale(); } ConstraintType ConstraintDifference::getTypeId() { return Difference; } double ConstraintDifference::error() { return scale * (*param2() - *param1() - *difference()); } double ConstraintDifference::grad(double* param) { double deriv = 0.; if (param == param1()) { deriv += -1; } if (param == param2()) { deriv += 1; } if (param == difference()) { deriv += -1; } return scale * deriv; } // -------------------------------------------------------- // P2PDistance ConstraintP2PDistance::ConstraintP2PDistance(Point& p1, Point& p2, double* d) { pvec.push_back(p1.x); pvec.push_back(p1.y); pvec.push_back(p2.x); pvec.push_back(p2.y); pvec.push_back(d); origpvec = pvec; rescale(); } ConstraintType ConstraintP2PDistance::getTypeId() { return P2PDistance; } double ConstraintP2PDistance::error() { double dx = (*p1x() - *p2x()); double dy = (*p1y() - *p2y()); double d = sqrt(dx * dx + dy * dy); double dist = *distance(); return scale * (d - dist); } double ConstraintP2PDistance::grad(double* param) { double deriv = 0.; if (param == p1x() || param == p1y() || param == p2x() || param == p2y()) { double dx = (*p1x() - *p2x()); double dy = (*p1y() - *p2y()); double d = sqrt(dx * dx + dy * dy); if (param == p1x()) { deriv += dx / d; } if (param == p1y()) { deriv += dy / d; } if (param == p2x()) { deriv += -dx / d; } if (param == p2y()) { deriv += -dy / d; } } if (param == distance()) { deriv += -1.; } return scale * deriv; } double ConstraintP2PDistance::maxStep(MAP_pD_D& dir, double lim) { MAP_pD_D::iterator it; it = dir.find(distance()); if (it != dir.end()) { if (it->second < 0.) { lim = std::min(lim, -(*distance()) / it->second); } } // restrict actual distance change double ddx = 0., ddy = 0.; it = dir.find(p1x()); if (it != dir.end()) { ddx += it->second; } it = dir.find(p1y()); if (it != dir.end()) { ddy += it->second; } it = dir.find(p2x()); if (it != dir.end()) { ddx -= it->second; } it = dir.find(p2y()); if (it != dir.end()) { ddy -= it->second; } double dd = sqrt(ddx * ddx + ddy * ddy); double dist = *distance(); if (dd > dist) { double dx = (*p1x() - *p2x()); double dy = (*p1y() - *p2y()); double d = sqrt(dx * dx + dy * dy); if (dd > d) { lim = std::min(lim, std::max(d, dist) / dd); } } return lim; } // -------------------------------------------------------- // P2PAngle ConstraintP2PAngle::ConstraintP2PAngle(Point& p1, Point& p2, double* a, double da_) : da(da_) { pvec.push_back(p1.x); pvec.push_back(p1.y); pvec.push_back(p2.x); pvec.push_back(p2.y); pvec.push_back(a); origpvec = pvec; rescale(); } ConstraintType ConstraintP2PAngle::getTypeId() { return P2PAngle; } double ConstraintP2PAngle::error() { double dx = (*p2x() - *p1x()); double dy = (*p2y() - *p1y()); double a = *angle() + da; double ca = cos(a); double sa = sin(a); double x = dx * ca + dy * sa; double y = -dx * sa + dy * ca; return scale * atan2(y, x); } double ConstraintP2PAngle::grad(double* param) { double deriv = 0.; if (param == p1x() || param == p1y() || param == p2x() || param == p2y()) { double dx = (*p2x() - *p1x()); double dy = (*p2y() - *p1y()); double a = *angle() + da; double ca = cos(a); double sa = sin(a); double x = dx * ca + dy * sa; double y = -dx * sa + dy * ca; double r2 = dx * dx + dy * dy; dx = -y / r2; dy = x / r2; if (param == p1x()) { deriv += (-ca * dx + sa * dy); } if (param == p1y()) { deriv += (-sa * dx - ca * dy); } if (param == p2x()) { deriv += (ca * dx - sa * dy); } if (param == p2y()) { deriv += (sa * dx + ca * dy); } } if (param == angle()) { deriv += -1; } return scale * deriv; } double ConstraintP2PAngle::maxStep(MAP_pD_D& dir, double lim) { constexpr double pi_18 = std::numbers::pi / 18; MAP_pD_D::iterator it = dir.find(angle()); if (it != dir.end()) { double step = std::abs(it->second); if (step > pi_18) { lim = std::min(lim, pi_18 / step); } } return lim; } // -------------------------------------------------------- // P2LDistance ConstraintP2LDistance::ConstraintP2LDistance(Point& p, Line& l, double* d) { pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(l.p1.x); pvec.push_back(l.p1.y); pvec.push_back(l.p2.x); pvec.push_back(l.p2.y); pvec.push_back(d); origpvec = pvec; rescale(); } ConstraintType ConstraintP2LDistance::getTypeId() { return P2LDistance; } double ConstraintP2LDistance::error() { double x0 = *p0x(), x1 = *p1x(), x2 = *p2x(); double y0 = *p0y(), y1 = *p1y(), y2 = *p2y(); double dist = *distance(); double dx = x2 - x1; double dy = y2 - y1; double d = sqrt(dx * dx + dy * dy); // line length double area = std::abs(-x0 * dy + y0 * dx + x1 * y2 - x2 * y1); return scale * (area / d - dist); } double ConstraintP2LDistance::grad(double* param) { double deriv = 0.; if (param == p0x() || param == p0y() || param == p1x() || param == p1y() || param == p2x() || param == p2y()) { double x0 = *p0x(), x1 = *p1x(), x2 = *p2x(); double y0 = *p0y(), y1 = *p1y(), y2 = *p2y(); double dx = x2 - x1; double dy = y2 - y1; double d2 = dx * dx + dy * dy; double d = sqrt(d2); double area = -x0 * dy + y0 * dx + x1 * y2 - x2 * y1; if (param == p0x()) { deriv += (y1 - y2) / d; } if (param == p0y()) { deriv += (x2 - x1) / d; } if (param == p1x()) { deriv += ((y2 - y0) * d + (dx / d) * area) / d2; } if (param == p1y()) { deriv += ((x0 - x2) * d + (dy / d) * area) / d2; } if (param == p2x()) { deriv += ((y0 - y1) * d - (dx / d) * area) / d2; } if (param == p2y()) { deriv += ((x1 - x0) * d - (dy / d) * area) / d2; } if (area < 0) { deriv *= -1; } } if (param == distance()) { deriv += -1; } return scale * deriv; } double ConstraintP2LDistance::maxStep(MAP_pD_D& dir, double lim) { MAP_pD_D::iterator it; it = dir.find(distance()); if (it != dir.end()) { if (it->second < 0.) { lim = std::min(lim, -(*distance()) / it->second); } } // restrict actual area change double darea = 0.; double x0 = *p0x(), x1 = *p1x(), x2 = *p2x(); double y0 = *p0y(), y1 = *p1y(), y2 = *p2y(); it = dir.find(p0x()); if (it != dir.end()) { darea += (y1 - y2) * it->second; } it = dir.find(p0y()); if (it != dir.end()) { darea += (x2 - x1) * it->second; } it = dir.find(p1x()); if (it != dir.end()) { darea += (y2 - y0) * it->second; } it = dir.find(p1y()); if (it != dir.end()) { darea += (x0 - x2) * it->second; } it = dir.find(p2x()); if (it != dir.end()) { darea += (y0 - y1) * it->second; } it = dir.find(p2y()); if (it != dir.end()) { darea += (x1 - x0) * it->second; } darea = std::abs(darea); if (darea > 0.) { double dx = x2 - x1; double dy = y2 - y1; double area = 0.3 * (*distance()) * sqrt(dx * dx + dy * dy); if (darea > area) { area = std::max(area, 0.3 * std::abs(-x0 * dy + y0 * dx + x1 * y2 - x2 * y1)); if (darea > area) { lim = std::min(lim, area / darea); } } } return lim; } // -------------------------------------------------------- // PointOnLine ConstraintPointOnLine::ConstraintPointOnLine(Point& p, Line& l) { pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(l.p1.x); pvec.push_back(l.p1.y); pvec.push_back(l.p2.x); pvec.push_back(l.p2.y); origpvec = pvec; rescale(); } ConstraintPointOnLine::ConstraintPointOnLine(Point& p, Point& lp1, Point& lp2) { pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(lp1.x); pvec.push_back(lp1.y); pvec.push_back(lp2.x); pvec.push_back(lp2.y); origpvec = pvec; rescale(); } ConstraintType ConstraintPointOnLine::getTypeId() { return PointOnLine; } double ConstraintPointOnLine::error() { double x0 = *p0x(), x1 = *p1x(), x2 = *p2x(); double y0 = *p0y(), y1 = *p1y(), y2 = *p2y(); double dx = x2 - x1; double dy = y2 - y1; double d = sqrt(dx * dx + dy * dy); double area = -x0 * dy + y0 * dx + x1 * y2 - x2 * y1; return scale * area / d; } double ConstraintPointOnLine::grad(double* param) { double deriv = 0.; if (param == p0x() || param == p0y() || param == p1x() || param == p1y() || param == p2x() || param == p2y()) { double x0 = *p0x(), x1 = *p1x(), x2 = *p2x(); double y0 = *p0y(), y1 = *p1y(), y2 = *p2y(); double dx = x2 - x1; double dy = y2 - y1; double d2 = dx * dx + dy * dy; double d = sqrt(d2); double area = -x0 * dy + y0 * dx + x1 * y2 - x2 * y1; if (param == p0x()) { deriv += (y1 - y2) / d; } if (param == p0y()) { deriv += (x2 - x1) / d; } if (param == p1x()) { deriv += ((y2 - y0) * d + (dx / d) * area) / d2; } if (param == p1y()) { deriv += ((x0 - x2) * d + (dy / d) * area) / d2; } if (param == p2x()) { deriv += ((y0 - y1) * d - (dx / d) * area) / d2; } if (param == p2y()) { deriv += ((x1 - x0) * d - (dy / d) * area) / d2; } } return scale * deriv; } // -------------------------------------------------------- // PointOnPerpBisector ConstraintPointOnPerpBisector::ConstraintPointOnPerpBisector(Point& p, Line& l) { pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(l.p1.x); pvec.push_back(l.p1.y); pvec.push_back(l.p2.x); pvec.push_back(l.p2.y); origpvec = pvec; rescale(); } ConstraintPointOnPerpBisector::ConstraintPointOnPerpBisector(Point& p, Point& lp1, Point& lp2) { pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(lp1.x); pvec.push_back(lp1.y); pvec.push_back(lp2.x); pvec.push_back(lp2.y); origpvec = pvec; rescale(); } ConstraintType ConstraintPointOnPerpBisector::getTypeId() { return PointOnPerpBisector; } void ConstraintPointOnPerpBisector::errorgrad(double* err, double* grad, double* param) { DeriVector2 p0(Point(p0x(), p0y()), param); DeriVector2 p1(Point(p1x(), p1y()), param); DeriVector2 p2(Point(p2x(), p2y()), param); DeriVector2 d1 = p0.subtr(p1); DeriVector2 d2 = p0.subtr(p2); DeriVector2 D = p2.subtr(p1).getNormalized(); double projd1, dprojd1; projd1 = d1.scalarProd(D, &dprojd1); double projd2, dprojd2; projd2 = d2.scalarProd(D, &dprojd2); if (err) { *err = projd1 + projd2; } if (grad) { *grad = dprojd1 + dprojd2; } } // -------------------------------------------------------- // Parallel ConstraintParallel::ConstraintParallel(Line& l1, Line& l2) { pvec.push_back(l1.p1.x); pvec.push_back(l1.p1.y); pvec.push_back(l1.p2.x); pvec.push_back(l1.p2.y); pvec.push_back(l2.p1.x); pvec.push_back(l2.p1.y); pvec.push_back(l2.p2.x); pvec.push_back(l2.p2.y); origpvec = pvec; ConstraintParallel::rescale(); } ConstraintType ConstraintParallel::getTypeId() { return Parallel; } void ConstraintParallel::rescale(double coef) { double dx1 = (*l1p1x() - *l1p2x()); double dy1 = (*l1p1y() - *l1p2y()); double dx2 = (*l2p1x() - *l2p2x()); double dy2 = (*l2p1y() - *l2p2y()); scale = coef / sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2)); } double ConstraintParallel::error() { double dx1 = (*l1p1x() - *l1p2x()); double dy1 = (*l1p1y() - *l1p2y()); double dx2 = (*l2p1x() - *l2p2x()); double dy2 = (*l2p1y() - *l2p2y()); return scale * (dx1 * dy2 - dy1 * dx2); } double ConstraintParallel::grad(double* param) { double deriv = 0.; if (param == l1p1x()) { deriv += (*l2p1y() - *l2p2y()); // = dy2 } if (param == l1p2x()) { deriv += -(*l2p1y() - *l2p2y()); // = -dy2 } if (param == l1p1y()) { deriv += -(*l2p1x() - *l2p2x()); // = -dx2 } if (param == l1p2y()) { deriv += (*l2p1x() - *l2p2x()); // = dx2 } if (param == l2p1x()) { deriv += -(*l1p1y() - *l1p2y()); // = -dy1 } if (param == l2p2x()) { deriv += (*l1p1y() - *l1p2y()); // = dy1 } if (param == l2p1y()) { deriv += (*l1p1x() - *l1p2x()); // = dx1 } if (param == l2p2y()) { deriv += -(*l1p1x() - *l1p2x()); // = -dx1 } return scale * deriv; } // -------------------------------------------------------- // Perpendicular ConstraintPerpendicular::ConstraintPerpendicular(Line& l1, Line& l2) { pvec.push_back(l1.p1.x); pvec.push_back(l1.p1.y); pvec.push_back(l1.p2.x); pvec.push_back(l1.p2.y); pvec.push_back(l2.p1.x); pvec.push_back(l2.p1.y); pvec.push_back(l2.p2.x); pvec.push_back(l2.p2.y); origpvec = pvec; ConstraintPerpendicular::rescale(); } ConstraintPerpendicular::ConstraintPerpendicular(Point& l1p1, Point& l1p2, Point& l2p1, Point& l2p2) { pvec.push_back(l1p1.x); pvec.push_back(l1p1.y); pvec.push_back(l1p2.x); pvec.push_back(l1p2.y); pvec.push_back(l2p1.x); pvec.push_back(l2p1.y); pvec.push_back(l2p2.x); pvec.push_back(l2p2.y); origpvec = pvec; ConstraintPerpendicular::rescale(); } ConstraintType ConstraintPerpendicular::getTypeId() { return Perpendicular; } void ConstraintPerpendicular::rescale(double coef) { double dx1 = (*l1p1x() - *l1p2x()); double dy1 = (*l1p1y() - *l1p2y()); double dx2 = (*l2p1x() - *l2p2x()); double dy2 = (*l2p1y() - *l2p2y()); scale = coef / sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2)); } double ConstraintPerpendicular::error() { double dx1 = (*l1p1x() - *l1p2x()); double dy1 = (*l1p1y() - *l1p2y()); double dx2 = (*l2p1x() - *l2p2x()); double dy2 = (*l2p1y() - *l2p2y()); return scale * (dx1 * dx2 + dy1 * dy2); } double ConstraintPerpendicular::grad(double* param) { double deriv = 0.; if (param == l1p1x()) { deriv += (*l2p1x() - *l2p2x()); // = dx2 } if (param == l1p2x()) { deriv += -(*l2p1x() - *l2p2x()); // = -dx2 } if (param == l1p1y()) { deriv += (*l2p1y() - *l2p2y()); // = dy2 } if (param == l1p2y()) { deriv += -(*l2p1y() - *l2p2y()); // = -dy2 } if (param == l2p1x()) { deriv += (*l1p1x() - *l1p2x()); // = dx1 } if (param == l2p2x()) { deriv += -(*l1p1x() - *l1p2x()); // = -dx1 } if (param == l2p1y()) { deriv += (*l1p1y() - *l1p2y()); // = dy1 } if (param == l2p2y()) { deriv += -(*l1p1y() - *l1p2y()); // = -dy1 } return scale * deriv; } // -------------------------------------------------------- // L2LAngle ConstraintL2LAngle::ConstraintL2LAngle(Line& l1, Line& l2, double* a) { pvec.push_back(l1.p1.x); pvec.push_back(l1.p1.y); pvec.push_back(l1.p2.x); pvec.push_back(l1.p2.y); pvec.push_back(l2.p1.x); pvec.push_back(l2.p1.y); pvec.push_back(l2.p2.x); pvec.push_back(l2.p2.y); pvec.push_back(a); origpvec = pvec; rescale(); } ConstraintL2LAngle::ConstraintL2LAngle(Point& l1p1, Point& l1p2, Point& l2p1, Point& l2p2, double* a) { pvec.push_back(l1p1.x); pvec.push_back(l1p1.y); pvec.push_back(l1p2.x); pvec.push_back(l1p2.y); pvec.push_back(l2p1.x); pvec.push_back(l2p1.y); pvec.push_back(l2p2.x); pvec.push_back(l2p2.y); pvec.push_back(a); origpvec = pvec; rescale(); } ConstraintType ConstraintL2LAngle::getTypeId() { return L2LAngle; } double ConstraintL2LAngle::error() { double dx1 = (*l1p2x() - *l1p1x()); double dy1 = (*l1p2y() - *l1p1y()); double dx2 = (*l2p2x() - *l2p1x()); double dy2 = (*l2p2y() - *l2p1y()); double a = atan2(dy1, dx1) + *angle(); double ca = cos(a); double sa = sin(a); double x2 = dx2 * ca + dy2 * sa; double y2 = -dx2 * sa + dy2 * ca; return scale * atan2(y2, x2); } double ConstraintL2LAngle::grad(double* param) { double deriv = 0.; if (param == l1p1x() || param == l1p1y() || param == l1p2x() || param == l1p2y()) { double dx1 = (*l1p2x() - *l1p1x()); double dy1 = (*l1p2y() - *l1p1y()); double r2 = dx1 * dx1 + dy1 * dy1; if (param == l1p1x()) { deriv += -dy1 / r2; } if (param == l1p1y()) { deriv += dx1 / r2; } if (param == l1p2x()) { deriv += dy1 / r2; } if (param == l1p2y()) { deriv += -dx1 / r2; } } if (param == l2p1x() || param == l2p1y() || param == l2p2x() || param == l2p2y()) { double dx1 = (*l1p2x() - *l1p1x()); double dy1 = (*l1p2y() - *l1p1y()); double dx2 = (*l2p2x() - *l2p1x()); double dy2 = (*l2p2y() - *l2p1y()); double a = atan2(dy1, dx1) + *angle(); double ca = cos(a); double sa = sin(a); double x2 = dx2 * ca + dy2 * sa; double y2 = -dx2 * sa + dy2 * ca; double r2 = dx2 * dx2 + dy2 * dy2; dx2 = -y2 / r2; dy2 = x2 / r2; if (param == l2p1x()) { deriv += (-ca * dx2 + sa * dy2); } if (param == l2p1y()) { deriv += (-sa * dx2 - ca * dy2); } if (param == l2p2x()) { deriv += (ca * dx2 - sa * dy2); } if (param == l2p2y()) { deriv += (sa * dx2 + ca * dy2); } } if (param == angle()) { deriv += -1; } return scale * deriv; } double ConstraintL2LAngle::maxStep(MAP_pD_D& dir, double lim) { constexpr double pi_18 = std::numbers::pi / 18; MAP_pD_D::iterator it = dir.find(angle()); if (it != dir.end()) { double step = std::abs(it->second); if (step > pi_18) { lim = std::min(lim, pi_18 / step); } } return lim; } // -------------------------------------------------------- // MidpointOnLine ConstraintMidpointOnLine::ConstraintMidpointOnLine(Line& l1, Line& l2) { pvec.push_back(l1.p1.x); pvec.push_back(l1.p1.y); pvec.push_back(l1.p2.x); pvec.push_back(l1.p2.y); pvec.push_back(l2.p1.x); pvec.push_back(l2.p1.y); pvec.push_back(l2.p2.x); pvec.push_back(l2.p2.y); origpvec = pvec; rescale(); } ConstraintMidpointOnLine::ConstraintMidpointOnLine(Point& l1p1, Point& l1p2, Point& l2p1, Point& l2p2) { pvec.push_back(l1p1.x); pvec.push_back(l1p1.y); pvec.push_back(l1p2.x); pvec.push_back(l1p2.y); pvec.push_back(l2p1.x); pvec.push_back(l2p1.y); pvec.push_back(l2p2.x); pvec.push_back(l2p2.y); origpvec = pvec; rescale(); } ConstraintType ConstraintMidpointOnLine::getTypeId() { return MidpointOnLine; } double ConstraintMidpointOnLine::error() { double x0 = ((*l1p1x()) + (*l1p2x())) / 2; double y0 = ((*l1p1y()) + (*l1p2y())) / 2; double x1 = *l2p1x(), x2 = *l2p2x(); double y1 = *l2p1y(), y2 = *l2p2y(); double dx = x2 - x1; double dy = y2 - y1; double d = sqrt(dx * dx + dy * dy); double area = -x0 * dy + y0 * dx + x1 * y2 - x2 * y1; // = 2*(triangle area) return scale * area / d; } double ConstraintMidpointOnLine::grad(double* param) { double deriv = 0.; if (param == l1p1x() || param == l1p1y() || param == l1p2x() || param == l1p2y() || param == l2p1x() || param == l2p1y() || param == l2p2x() || param == l2p2y()) { double x0 = ((*l1p1x()) + (*l1p2x())) / 2; double y0 = ((*l1p1y()) + (*l1p2y())) / 2; double x1 = *l2p1x(), x2 = *l2p2x(); double y1 = *l2p1y(), y2 = *l2p2y(); double dx = x2 - x1; double dy = y2 - y1; double d2 = dx * dx + dy * dy; double d = sqrt(d2); double area = -x0 * dy + y0 * dx + x1 * y2 - x2 * y1; if (param == l1p1x()) { deriv += (y1 - y2) / (2 * d); } if (param == l1p1y()) { deriv += (x2 - x1) / (2 * d); } if (param == l1p2x()) { deriv += (y1 - y2) / (2 * d); } if (param == l1p2y()) { deriv += (x2 - x1) / (2 * d); } if (param == l2p1x()) { deriv += ((y2 - y0) * d + (dx / d) * area) / d2; } if (param == l2p1y()) { deriv += ((x0 - x2) * d + (dy / d) * area) / d2; } if (param == l2p2x()) { deriv += ((y0 - y1) * d - (dx / d) * area) / d2; } if (param == l2p2y()) { deriv += ((x1 - x0) * d - (dy / d) * area) / d2; } } return scale * deriv; } // -------------------------------------------------------- // TangentCircumf ConstraintTangentCircumf::ConstraintTangentCircumf( Point& p1, Point& p2, double* rad1, double* rad2, bool internal_ ) : internal(internal_) { pvec.push_back(p1.x); pvec.push_back(p1.y); pvec.push_back(p2.x); pvec.push_back(p2.y); pvec.push_back(rad1); pvec.push_back(rad2); origpvec = pvec; rescale(); } ConstraintType ConstraintTangentCircumf::getTypeId() { return TangentCircumf; } double ConstraintTangentCircumf::error() { double dx = (*c1x() - *c2x()); double dy = (*c1y() - *c2y()); double d_sq = dx * dx + dy * dy; // Handle the singularity for near-concentric circles. // When concentric, tangency is equivalent to equal radii. // We switch to the robust 'r1 - r2 = 0' formulation, which has a // constant non-zero gradient, avoiding the singularity. if (d_sq < 1e-14) { return scale * (*r1() - *r2()); } if (internal) { return scale * (d_sq - (*r1() - *r2()) * (*r1() - *r2())); } else { return scale * (d_sq - (*r1() + *r2()) * (*r1() + *r2())); } } double ConstraintTangentCircumf::grad(double* param) { double deriv = 0.; if (param == c1x() || param == c1y() || param == c2x() || param == c2y() || param == r1() || param == r2()) { double dx = (*c1x() - *c2x()); double dy = (*c1y() - *c2y()); double d_sq = dx * dx + dy * dy; // Provide the gradient corresponding to the robust 'r1 - r2 = 0' error function. // This gradient is constant and non-zero, preventing the false redundancy report. if (d_sq < 1e-14) { if (param == r1()) { deriv = 1.0; } else if (param == r2()) { deriv = -1.0; } // The gradient is 0 for all other parameters (center coordinates). return scale * deriv; } if (param == c1x()) { deriv += 2 * dx; } if (param == c1y()) { deriv += 2 * dy; } if (param == c2x()) { deriv += 2 * -dx; } if (param == c2y()) { deriv += 2 * -dy; } if (internal) { if (param == r1()) { deriv += 2 * (*r2() - *r1()); } if (param == r2()) { deriv += 2 * (*r1() - *r2()); } } else { if (param == r1()) { deriv += -2 * (*r1() + *r2()); } if (param == r2()) { deriv += -2 * (*r1() + *r2()); } } } return scale * deriv; } // -------------------------------------------------------- // ConstraintPointOnEllipse ConstraintPointOnEllipse::ConstraintPointOnEllipse(Point& p, Ellipse& e) { pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(e.center.x); pvec.push_back(e.center.y); pvec.push_back(e.focus1.x); pvec.push_back(e.focus1.y); pvec.push_back(e.radmin); origpvec = pvec; rescale(); } ConstraintType ConstraintPointOnEllipse::getTypeId() { return PointOnEllipse; } double ConstraintPointOnEllipse::error() { double X_0 = *p1x(); double Y_0 = *p1y(); double X_c = *cx(); double Y_c = *cy(); double X_F1 = *f1x(); double Y_F1 = *f1y(); double b = *rmin(); double err = sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)) - 2 * sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)); return scale * err; } double ConstraintPointOnEllipse::grad(double* param) { double deriv = 0.; if (param == p1x() || param == p1y() || param == f1x() || param == f1y() || param == cx() || param == cy() || param == rmin()) { double X_0 = *p1x(); double Y_0 = *p1y(); double X_c = *cx(); double Y_c = *cy(); double X_F1 = *f1x(); double Y_F1 = *f1y(); double b = *rmin(); if (param == p1x()) { deriv += (X_0 - X_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + (X_0 + X_F1 - 2 * X_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == p1y()) { deriv += (Y_0 - Y_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + (Y_0 + Y_F1 - 2 * Y_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == f1x()) { deriv += -(X_0 - X_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) - 2 * (X_F1 - X_c) / sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + (X_0 + X_F1 - 2 * X_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == f1y()) { deriv += -(Y_0 - Y_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) - 2 * (Y_F1 - Y_c) / sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + (Y_0 + Y_F1 - 2 * Y_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == cx()) { deriv += 2 * (X_F1 - X_c) / sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) - 2 * (X_0 + X_F1 - 2 * X_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == cy()) { deriv += 2 * (Y_F1 - Y_c) / sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) - 2 * (Y_0 + Y_F1 - 2 * Y_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == rmin()) { deriv += -2 * b / sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)); } } return scale * deriv; } // -------------------------------------------------------- // ConstraintEllipseTangentLine ConstraintEllipseTangentLine::ConstraintEllipseTangentLine(Line& l, Ellipse& e) : l(l) , e(e) { this->l.PushOwnParams(pvec); this->e.PushOwnParams(pvec); // DeepSOIC: hopefully, this won't push arc's parameters origpvec = pvec; pvecChangedFlag = true; rescale(); } void ConstraintEllipseTangentLine::ReconstructGeomPointers() { int i = 0; l.ReconstructOnNewPvec(pvec, i); e.ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } ConstraintType ConstraintEllipseTangentLine::getTypeId() { return TangentEllipseLine; } void ConstraintEllipseTangentLine::errorgrad(double* err, double* grad, double* param) { // DeepSOIC equation // https://forum.freecad.org/viewtopic.php?f=10&t=7520&start=140 if (pvecChangedFlag) { ReconstructGeomPointers(); } DeriVector2 p1(l.p1, param); DeriVector2 p2(l.p2, param); DeriVector2 f1(e.focus1, param); DeriVector2 c(e.center, param); DeriVector2 f2 = c.linCombi(2.0, f1, -1.0); // 2*cv - f1v // mirror F1 against the line DeriVector2 nl = l.CalculateNormal(l.p1, param).getNormalized(); double distF1L = 0, ddistF1L = 0; // distance F1 to line distF1L = f1.subtr(p1).scalarProd(nl, &ddistF1L); DeriVector2 f1m = f1.sum(nl.multD(-2 * distF1L, -2 * ddistF1L)); // f1m = f1 mirrored // calculate distance form f1m to f2 double distF1mF2, ddistF1mF2; distF1mF2 = f2.subtr(f1m).length(ddistF1mF2); // calculate major radius (to compare the distance to) double dradmin = (param == e.radmin) ? 1.0 : 0.0; double radmaj, dradmaj; radmaj = e.getRadMaj(c, f1, *e.radmin, dradmin, dradmaj); if (err) { *err = distF1mF2 - 2 * radmaj; } if (grad) { *grad = ddistF1mF2 - 2 * dradmaj; } } // -------------------------------------------------------- // ConstraintInternalAlignmentPoint2Ellipse ConstraintInternalAlignmentPoint2Ellipse::ConstraintInternalAlignmentPoint2Ellipse( Ellipse& e, Point& p1, InternalAlignmentType alignmentType ) : e(e) , p(p1) , AlignmentType(alignmentType) { pvec.push_back(p.x); pvec.push_back(p.y); this->e.PushOwnParams(pvec); origpvec = pvec; rescale(); } void ConstraintInternalAlignmentPoint2Ellipse::ReconstructGeomPointers() { int i = 0; p.x = pvec[i]; i++; p.y = pvec[i]; i++; e.ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } ConstraintType ConstraintInternalAlignmentPoint2Ellipse::getTypeId() { return InternalAlignmentPoint2Ellipse; } void ConstraintInternalAlignmentPoint2Ellipse::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } // todo: prefill only what's needed, not everything DeriVector2 c(e.center, param); DeriVector2 f1(e.focus1, param); DeriVector2 emaj = f1.subtr(c).getNormalized(); DeriVector2 emin = emaj.rotate90ccw(); DeriVector2 pv(p, param); double b, db; // minor radius b = *e.radmin; db = (e.radmin == param) ? 1.0 : 0.0; // major radius double a, da; a = e.getRadMaj(c, f1, b, db, da); DeriVector2 poa; // point to align to bool by_y_not_by_x = false; // a flag to indicate if the alignment error function is for y // (false - x, true - y) switch (AlignmentType) { case EllipsePositiveMajorX: case EllipsePositiveMajorY: poa = c.sum(emaj.multD(a, da)); by_y_not_by_x = AlignmentType == EllipsePositiveMajorY; break; case EllipseNegativeMajorX: case EllipseNegativeMajorY: poa = c.sum(emaj.multD(-a, -da)); by_y_not_by_x = AlignmentType == EllipseNegativeMajorY; break; case EllipsePositiveMinorX: case EllipsePositiveMinorY: poa = c.sum(emin.multD(b, db)); by_y_not_by_x = AlignmentType == EllipsePositiveMinorY; break; case EllipseNegativeMinorX: case EllipseNegativeMinorY: poa = c.sum(emin.multD(-b, -db)); by_y_not_by_x = AlignmentType == EllipseNegativeMinorY; break; case EllipseFocus2X: case EllipseFocus2Y: poa = c.linCombi(2.0, f1, -1.0); by_y_not_by_x = AlignmentType == EllipseFocus2Y; break; default: // shouldn't happen poa = pv; // align to the point itself, doing nothing essentially } if (err) { *err = by_y_not_by_x ? pv.y - poa.y : pv.x - poa.x; } if (grad) { *grad = by_y_not_by_x ? pv.dy - poa.dy : pv.dx - poa.dx; } } // -------------------------------------------------------- // ConstraintInternalAlignmentPoint2Hyperbola ConstraintInternalAlignmentPoint2Hyperbola::ConstraintInternalAlignmentPoint2Hyperbola( Hyperbola& e, Point& p1, InternalAlignmentType alignmentType ) : e(e) , p(p1) , AlignmentType(alignmentType) { pvec.push_back(p.x); pvec.push_back(p.y); this->e.PushOwnParams(pvec); origpvec = pvec; rescale(); } void ConstraintInternalAlignmentPoint2Hyperbola::ReconstructGeomPointers() { int i = 0; p.x = pvec[i]; i++; p.y = pvec[i]; i++; e.ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } ConstraintType ConstraintInternalAlignmentPoint2Hyperbola::getTypeId() { return InternalAlignmentPoint2Hyperbola; } void ConstraintInternalAlignmentPoint2Hyperbola::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } // todo: prefill only what's needed, not everything DeriVector2 c(e.center, param); DeriVector2 f1(e.focus1, param); DeriVector2 emaj = f1.subtr(c).getNormalized(); DeriVector2 emin = emaj.rotate90ccw(); DeriVector2 pv(p, param); double b, db; // minor radius b = *e.radmin; db = (e.radmin == param) ? 1.0 : 0.0; // major radius double a, da; a = e.getRadMaj(c, f1, b, db, da); DeriVector2 poa; // point to align to bool by_y_not_by_x = false; // a flag to indicate if the alignment error function is for y // (false - x, true - y) switch (AlignmentType) { case HyperbolaPositiveMajorX: case HyperbolaPositiveMajorY: poa = c.sum(emaj.multD(a, da)); by_y_not_by_x = AlignmentType == HyperbolaPositiveMajorY; break; case HyperbolaNegativeMajorX: case HyperbolaNegativeMajorY: poa = c.sum(emaj.multD(-a, -da)); by_y_not_by_x = AlignmentType == HyperbolaNegativeMajorY; break; case HyperbolaPositiveMinorX: case HyperbolaPositiveMinorY: { DeriVector2 pa = c.sum(emaj.multD(a, da)); // DeriVector2 A(pa.x,pa.y); // poa = A.sum(emin.multD(b, db)); poa = pa.sum(emin.multD(b, db)); by_y_not_by_x = AlignmentType == HyperbolaPositiveMinorY; break; } case HyperbolaNegativeMinorX: case HyperbolaNegativeMinorY: { DeriVector2 pa = c.sum(emaj.multD(a, da)); // DeriVector2 A(pa.x,pa.y); // poa = A.sum(emin.multD(-b, -db)); poa = pa.sum(emin.multD(-b, -db)); by_y_not_by_x = AlignmentType == HyperbolaNegativeMinorY; break; } default: // shouldn't happen poa = pv; // align to the point itself, doing nothing essentially } if (err) { *err = by_y_not_by_x ? pv.y - poa.y : pv.x - poa.x; } if (grad) { *grad = by_y_not_by_x ? pv.dy - poa.dy : pv.dx - poa.dx; } } // -------------------------------------------------------- // ConstraintEqualMajorAxesEllipse ConstraintEqualMajorAxesConic::ConstraintEqualMajorAxesConic(MajorRadiusConic* a1, MajorRadiusConic* a2) : e1(a1) , e2(a2) { this->e1->PushOwnParams(pvec); this->e2->PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } void ConstraintEqualMajorAxesConic::ReconstructGeomPointers() { int i = 0; e1->ReconstructOnNewPvec(pvec, i); e2->ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } ConstraintType ConstraintEqualMajorAxesConic::getTypeId() { return EqualMajorAxesConic; } void ConstraintEqualMajorAxesConic::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } double a1, da1; a1 = e1->getRadMaj(param, da1); double a2, da2; a2 = e2->getRadMaj(param, da2); if (err) { *err = a2 - a1; } if (grad) { *grad = da2 - da1; } } // ConstraintEqualFocalDistance ConstraintEqualFocalDistance::ConstraintEqualFocalDistance(ArcOfParabola* a1, ArcOfParabola* a2) { this->e1 = a1; this->e1->PushOwnParams(pvec); this->e2 = a2; this->e2->PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } void ConstraintEqualFocalDistance::ReconstructGeomPointers() { int i = 0; e1->ReconstructOnNewPvec(pvec, i); e2->ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } ConstraintType ConstraintEqualFocalDistance::getTypeId() { return EqualFocalDistance; } void ConstraintEqualFocalDistance::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } DeriVector2 focus1(this->e1->focus1, param); DeriVector2 vertex1(this->e1->vertex, param); DeriVector2 focalvect1 = vertex1.subtr(focus1); double focal1, dfocal1; focal1 = focalvect1.length(dfocal1); DeriVector2 focus2(this->e2->focus1, param); DeriVector2 vertex2(this->e2->vertex, param); DeriVector2 focalvect2 = vertex2.subtr(focus2); double focal2, dfocal2; focal2 = focalvect2.length(dfocal2); if (err) { *err = focal2 - focal1; } if (grad) { *grad = dfocal2 - dfocal1; } } // -------------------------------------------------------- // ConstraintCurveValue ConstraintCurveValue::ConstraintCurveValue(Point& p, double* pcoord, Curve& c, double* u) : crv(c.Copy()) { pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(pcoord); pvec.push_back(u); crv->PushOwnParams(pvec); pvecChangedFlag = true; origpvec = pvec; rescale(); } ConstraintCurveValue::~ConstraintCurveValue() { delete this->crv; this->crv = nullptr; } void ConstraintCurveValue::ReconstructGeomPointers() { int i = 0; p.x = pvec[i]; i++; p.y = pvec[i]; i++; i++; // we have an inline function for point coordinate i++; // we have an inline function for the parameterU this->crv->ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } ConstraintType ConstraintCurveValue::getTypeId() { return CurveValue; } void ConstraintCurveValue::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } double u, du; u = *(this->u()); du = (param == this->u()) ? 1.0 : 0.0; DeriVector2 P_to; // point of curve at parameter value of u, in global coordinates P_to = this->crv->Value(u, du, param); DeriVector2 P_from(this->p, param); // point to be constrained DeriVector2 err_vec = P_from.subtr(P_to); if (this->pcoord() == this->p.x) { // this constraint is for X projection if (err) { *err = err_vec.x; } if (grad) { *grad = err_vec.dx; } } else if (this->pcoord() == this->p.y) { // this constraint is for Y projection if (err) { *err = err_vec.y; } if (grad) { *grad = err_vec.dy; } } else { assert(false /*this constraint is neither X nor Y. Nothing to do..*/); } } double ConstraintCurveValue::maxStep(MAP_pD_D& /*dir*/, double lim) { return lim; } // -------------------------------------------------------- // ConstraintPointOnHyperbola ConstraintPointOnHyperbola::ConstraintPointOnHyperbola(Point& p, Hyperbola& e) { pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(e.center.x); pvec.push_back(e.center.y); pvec.push_back(e.focus1.x); pvec.push_back(e.focus1.y); pvec.push_back(e.radmin); origpvec = pvec; rescale(); } ConstraintPointOnHyperbola::ConstraintPointOnHyperbola(Point& p, ArcOfHyperbola& e) { pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(e.center.x); pvec.push_back(e.center.y); pvec.push_back(e.focus1.x); pvec.push_back(e.focus1.y); pvec.push_back(e.radmin); origpvec = pvec; rescale(); } ConstraintType ConstraintPointOnHyperbola::getTypeId() { return PointOnHyperbola; } double ConstraintPointOnHyperbola::error() { double X_0 = *p1x(); double Y_0 = *p1y(); double X_c = *cx(); double Y_c = *cy(); double X_F1 = *f1x(); double Y_F1 = *f1y(); double b = *rmin(); // Full sage worksheet at: // https://forum.freecad.org/viewtopic.php?f=10&t=8038&p=110447#p110447 // // Err = |PF2| - |PF1| - 2*a // sage code: // C = vector([X_c,Y_c]) // F2 = C+(C-F1) // X_F2 = F2[0] // Y_F2 = F2[1] // a = sqrt((F1-C)*(F1-C)-b*b); // show(a) // DM=sqrt((P-F2)*(P-F2))-sqrt((P-F1)*(P-F1))-2*a // show(DM.simplify_radical()) double err = -sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)) - 2 * sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)); return scale * err; } double ConstraintPointOnHyperbola::grad(double* param) { double deriv = 0.; if (param == p1x() || param == p1y() || param == f1x() || param == f1y() || param == cx() || param == cy() || param == rmin()) { double X_0 = *p1x(); double Y_0 = *p1y(); double X_c = *cx(); double Y_c = *cy(); double X_F1 = *f1x(); double Y_F1 = *f1y(); double b = *rmin(); if (param == p1x()) { deriv += -(X_0 - X_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + (X_0 + X_F1 - 2 * X_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == p1y()) { deriv += -(Y_0 - Y_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + (Y_0 + Y_F1 - 2 * Y_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == f1x()) { deriv += (X_0 - X_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) - 2 * (X_F1 - X_c) / sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + (X_0 + X_F1 - 2 * X_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == f1y()) { deriv += (Y_0 - Y_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) - 2 * (Y_F1 - Y_c) / sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + (Y_0 + Y_F1 - 2 * Y_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == cx()) { deriv += 2 * (X_F1 - X_c) / sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) - 2 * (X_0 + X_F1 - 2 * X_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == cy()) { deriv += 2 * (Y_F1 - Y_c) / sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) - 2 * (Y_0 + Y_F1 - 2 * Y_c) / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); } if (param == rmin()) { deriv += 2 * b / sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)); } } return scale * deriv; } // -------------------------------------------------------- // ConstraintPointOnParabola ConstraintPointOnParabola::ConstraintPointOnParabola(Point& p, Parabola& e) : parab(e.Copy()) { pvec.push_back(p.x); pvec.push_back(p.y); parab->PushOwnParams(pvec); pvecChangedFlag = true; origpvec = pvec; rescale(); } ConstraintPointOnParabola::ConstraintPointOnParabola(Point& p, ArcOfParabola& e) : parab(e.Copy()) { pvec.push_back(p.x); pvec.push_back(p.y); parab->PushOwnParams(pvec); pvecChangedFlag = true; origpvec = pvec; rescale(); } ConstraintPointOnParabola::~ConstraintPointOnParabola() { delete this->parab; this->parab = nullptr; } void ConstraintPointOnParabola::ReconstructGeomPointers() { int i = 0; p.x = pvec[i]; i++; p.y = pvec[i]; i++; this->parab->ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } ConstraintType ConstraintPointOnParabola::getTypeId() { return PointOnParabola; } void ConstraintPointOnParabola::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } DeriVector2 focus(this->parab->focus1, param); DeriVector2 vertex(this->parab->vertex, param); DeriVector2 point(this->p, param); // point to be constrained to parabola DeriVector2 focalvect = focus.subtr(vertex); DeriVector2 xdir = focalvect.getNormalized(); DeriVector2 point_to_focus = point.subtr(focus); double focal, dfocal; focal = focalvect.length(dfocal); double pf, dpf; pf = point_to_focus.length(dpf); double proj, dproj; proj = point_to_focus.scalarProd(xdir, &dproj); if (err) { *err = pf - 2 * focal - proj; } if (grad) { *grad = dpf - 2 * dfocal - dproj; } } // -------------------------------------------------------- // ConstraintAngleViaPoint ConstraintAngleViaPoint::ConstraintAngleViaPoint(Curve& acrv1, Curve& acrv2, Point p, double* angle) : crv1(acrv1.Copy()) , crv2(acrv2.Copy()) { pvec.push_back(angle); pvec.push_back(p.x); pvec.push_back(p.y); crv1->PushOwnParams(pvec); crv2->PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } ConstraintAngleViaPoint::~ConstraintAngleViaPoint() { delete crv1; crv1 = nullptr; delete crv2; crv2 = nullptr; } void ConstraintAngleViaPoint::ReconstructGeomPointers() { int cnt = 0; cnt++; // skip angle - we have an inline function for that poa.x = pvec[cnt]; cnt++; poa.y = pvec[cnt]; cnt++; crv1->ReconstructOnNewPvec(pvec, cnt); crv2->ReconstructOnNewPvec(pvec, cnt); pvecChangedFlag = false; } ConstraintType ConstraintAngleViaPoint::getTypeId() { return AngleViaPoint; } double ConstraintAngleViaPoint::error() { if (pvecChangedFlag) { ReconstructGeomPointers(); } double ang = *angle(); DeriVector2 n1 = crv1->CalculateNormal(poa); DeriVector2 n2 = crv2->CalculateNormal(poa); // rotate n1 by angle DeriVector2 n1r(n1.x * cos(ang) - n1.y * sin(ang), n1.x * sin(ang) + n1.y * cos(ang)); // calculate angle between n1r and n2. Since we have rotated the n1, the angle is the error // function. for our atan2, y is a dot product (n2) * (n1r rotated ccw by 90 degrees). // x is a dot product (n2) * (n1r) double err = atan2(-n2.x * n1r.y + n2.y * n1r.x, n2.x * n1r.x + n2.y * n1r.y); // essentially, the function is equivalent to atan2(n2)-(atan2(n1)+angle). The only difference // is behavior when normals are zero (the intended result is also zero in this case). return scale * err; } double ConstraintAngleViaPoint::grad(double* param) { // first of all, check that we need to compute anything. if (findParamInPvec(param) == -1) { return 0.0; } double deriv = 0.; if (pvecChangedFlag) { ReconstructGeomPointers(); } if (param == angle()) { deriv += -1.0; } DeriVector2 n1 = crv1->CalculateNormal(poa, param); DeriVector2 n2 = crv2->CalculateNormal(poa, param); deriv -= ((-n1.dx) * n1.y / pow(n1.length(), 2) + n1.dy * n1.x / pow(n1.length(), 2)); deriv += ((-n2.dx) * n2.y / pow(n2.length(), 2) + n2.dy * n2.x / pow(n2.length(), 2)); return scale * deriv; } // -------------------------------------------------------- // ConstraintAngleViaTwoPoints ConstraintAngleViaTwoPoints::ConstraintAngleViaTwoPoints( Curve& acrv1, Curve& acrv2, Point p1, Point p2, double* angle ) : crv1(acrv1.Copy()) , crv2(acrv2.Copy()) { pvec.push_back(angle); pvec.push_back(p1.x); pvec.push_back(p1.y); pvec.push_back(p2.x); pvec.push_back(p2.y); crv1->PushOwnParams(pvec); crv2->PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } ConstraintAngleViaTwoPoints::~ConstraintAngleViaTwoPoints() { delete crv1; crv1 = nullptr; delete crv2; crv2 = nullptr; } void ConstraintAngleViaTwoPoints::ReconstructGeomPointers() { int cnt = 0; cnt++; // skip angle - we have an inline function for that poa1.x = pvec[cnt]; cnt++; poa1.y = pvec[cnt]; cnt++; poa2.x = pvec[cnt]; cnt++; poa2.y = pvec[cnt]; cnt++; crv1->ReconstructOnNewPvec(pvec, cnt); crv2->ReconstructOnNewPvec(pvec, cnt); pvecChangedFlag = false; } ConstraintType ConstraintAngleViaTwoPoints::getTypeId() { return AngleViaTwoPoints; } double ConstraintAngleViaTwoPoints::error() { if (pvecChangedFlag) { ReconstructGeomPointers(); } double ang = *angle(); DeriVector2 n1 = crv1->CalculateNormal(poa1); DeriVector2 n2 = crv2->CalculateNormal(poa2); // rotate n1 by angle DeriVector2 n1r(n1.x * cos(ang) - n1.y * sin(ang), n1.x * sin(ang) + n1.y * cos(ang)); // calculate angle between n1r and n2. Since we have rotated the n1, the angle is the error // function. for our atan2, y is a dot product (n2) * (n1r rotated ccw by 90 degrees). // x is a dot product (n2) * (n1r) double err = atan2(-n2.x * n1r.y + n2.y * n1r.x, n2.x * n1r.x + n2.y * n1r.y); // essentially, the function is equivalent to atan2(n2)-(atan2(n1)+angle). The only difference // is behavior when normals are zero (the intended result is also zero in this case). return scale * err; } double ConstraintAngleViaTwoPoints::grad(double* param) { // first of all, check that we need to compute anything. if (findParamInPvec(param) == -1) { return 0.0; } double deriv = 0.; if (pvecChangedFlag) { ReconstructGeomPointers(); } if (param == angle()) { deriv += -1.0; } DeriVector2 n1 = crv1->CalculateNormal(poa1, param); DeriVector2 n2 = crv2->CalculateNormal(poa2, param); deriv -= ((-n1.dx) * n1.y / pow(n1.length(), 2) + n1.dy * n1.x / pow(n1.length(), 2)); deriv += ((-n2.dx) * n2.y / pow(n2.length(), 2) + n2.dy * n2.x / pow(n2.length(), 2)); return scale * deriv; } // -------------------------------------------------------- // ConstraintAngleViaPointAndParam ConstraintAngleViaPointAndParam::ConstraintAngleViaPointAndParam( Curve& acrv1, Curve& acrv2, Point p, double* cparam, double* angle ) : crv1(acrv1.Copy()) , crv2(acrv2.Copy()) { pvec.push_back(angle); pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(cparam); crv1->PushOwnParams(pvec); crv2->PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } ConstraintAngleViaPointAndParam::~ConstraintAngleViaPointAndParam() { delete crv1; crv1 = nullptr; delete crv2; crv2 = nullptr; } void ConstraintAngleViaPointAndParam::ReconstructGeomPointers() { int cnt = 0; cnt++; // skip angle - we have an inline function for that poa.x = pvec[cnt]; cnt++; poa.y = pvec[cnt]; cnt++; cnt++; // skip cparam crv1->ReconstructOnNewPvec(pvec, cnt); crv2->ReconstructOnNewPvec(pvec, cnt); pvecChangedFlag = false; } ConstraintType ConstraintAngleViaPointAndParam::getTypeId() { return AngleViaPointAndParam; } double ConstraintAngleViaPointAndParam::error() { if (pvecChangedFlag) { ReconstructGeomPointers(); } double ang = *angle(); DeriVector2 n1 = crv1->CalculateNormal(cparam()); DeriVector2 n2 = crv2->CalculateNormal(poa); // rotate n1 by angle DeriVector2 n1r(n1.x * cos(ang) - n1.y * sin(ang), n1.x * sin(ang) + n1.y * cos(ang)); // calculate angle between n1r and n2. Since we have rotated the n1, the angle is the error // function. for our atan2, y is a dot product (n2) * (n1r rotated ccw by 90 degrees). // x is a dot product (n2) * (n1r) double err = atan2(-n2.x * n1r.y + n2.y * n1r.x, n2.x * n1r.x + n2.y * n1r.y); // essentially, the function is equivalent to atan2(n2)-(atan2(n1)+angle). The only difference // is behavior when normals are zero (the intended result is also zero in this case). return scale * err; } double ConstraintAngleViaPointAndParam::grad(double* param) { // first of all, check that we need to compute anything. if (findParamInPvec(param) == -1) { return 0.0; } double deriv = 0.; if (pvecChangedFlag) { ReconstructGeomPointers(); } if (param == angle()) { deriv += -1.0; } DeriVector2 n1 = crv1->CalculateNormal(cparam(), param); DeriVector2 n2 = crv2->CalculateNormal(poa, param); deriv -= ((-n1.dx) * n1.y / pow(n1.length(), 2) + n1.dy * n1.x / pow(n1.length(), 2)); deriv += ((-n2.dx) * n2.y / pow(n2.length(), 2) + n2.dy * n2.x / pow(n2.length(), 2)); return scale * deriv; } // -------------------------------------------------------- // ConstraintAngleViaPointAndTwoParams ConstraintAngleViaPointAndTwoParams::ConstraintAngleViaPointAndTwoParams( Curve& acrv1, Curve& acrv2, Point p, double* cparam1, double* cparam2, double* angle ) : crv1(acrv1.Copy()) , crv2(acrv2.Copy()) { pvec.push_back(angle); pvec.push_back(p.x); pvec.push_back(p.y); pvec.push_back(cparam1); pvec.push_back(cparam2); crv1->PushOwnParams(pvec); crv2->PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } ConstraintAngleViaPointAndTwoParams::~ConstraintAngleViaPointAndTwoParams() { delete crv1; crv1 = nullptr; delete crv2; crv2 = nullptr; } void ConstraintAngleViaPointAndTwoParams::ReconstructGeomPointers() { int cnt = 0; cnt++; // skip angle - we have an inline function for that poa.x = pvec[cnt]; cnt++; poa.y = pvec[cnt]; cnt++; cnt++; // skip cparam1 - we have an inline function for that cnt++; // skip cparam2 - we have an inline function for that crv1->ReconstructOnNewPvec(pvec, cnt); crv2->ReconstructOnNewPvec(pvec, cnt); pvecChangedFlag = false; } ConstraintType ConstraintAngleViaPointAndTwoParams::getTypeId() { return AngleViaPointAndTwoParams; } double ConstraintAngleViaPointAndTwoParams::error() { if (pvecChangedFlag) { ReconstructGeomPointers(); } double ang = *angle(); DeriVector2 n1 = crv1->CalculateNormal(cparam1()); DeriVector2 n2 = crv2->CalculateNormal(cparam2()); // rotate n1 by angle DeriVector2 n1r(n1.x * cos(ang) - n1.y * sin(ang), n1.x * sin(ang) + n1.y * cos(ang)); // calculate angle between n1r and n2. Since we have rotated the n1, the angle is the error // function. for our atan2, y is a dot product (n2) * (n1r rotated ccw by 90 degrees). // x is a dot product (n2) * (n1r) double err = atan2(-n2.x * n1r.y + n2.y * n1r.x, n2.x * n1r.x + n2.y * n1r.y); // essentially, the function is equivalent to atan2(n2)-(atan2(n1)+angle). The only difference // is behavior when normals are zero (the intended result is also zero in this case). return scale * err; } double ConstraintAngleViaPointAndTwoParams::grad(double* param) { // first of all, check that we need to compute anything. if (findParamInPvec(param) == -1) { return 0.0; } double deriv = 0.; if (pvecChangedFlag) { ReconstructGeomPointers(); } if (param == angle()) { deriv += -1.0; } DeriVector2 n1 = crv1->CalculateNormal(cparam1(), param); DeriVector2 n2 = crv2->CalculateNormal(cparam2(), param); deriv -= ((-n1.dx) * n1.y / pow(n1.length(), 2) + n1.dy * n1.x / pow(n1.length(), 2)); deriv += ((-n2.dx) * n2.y / pow(n2.length(), 2) + n2.dy * n2.x / pow(n2.length(), 2)); return scale * deriv; } // -------------------------------------------------------- // ConstraintSnell ConstraintSnell::ConstraintSnell( Curve& r1, Curve& r2, Curve& b, Point p, double* n1, double* n2, bool flipn1, bool flipn2 ) : ray1(r1.Copy()) , ray2(r2.Copy()) , boundary(b.Copy()) , flipn1(flipn1) , flipn2(flipn2) { pvec.push_back(n1); pvec.push_back(n2); pvec.push_back(p.x); pvec.push_back(p.y); ray1->PushOwnParams(pvec); ray2->PushOwnParams(pvec); boundary->PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } ConstraintSnell::~ConstraintSnell() { delete ray1; ray1 = nullptr; delete ray2; ray2 = nullptr; delete boundary; boundary = nullptr; } void ConstraintSnell::ReconstructGeomPointers() { int cnt = 0; cnt++; cnt++; // skip n1, n2 - we have an inline function for that poa.x = pvec[cnt]; cnt++; poa.y = pvec[cnt]; cnt++; ray1->ReconstructOnNewPvec(pvec, cnt); ray2->ReconstructOnNewPvec(pvec, cnt); boundary->ReconstructOnNewPvec(pvec, cnt); pvecChangedFlag = false; } ConstraintType ConstraintSnell::getTypeId() { return Snell; } // error and gradient combined. Values are returned through pointers. void ConstraintSnell::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } DeriVector2 tang1 = ray1->CalculateNormal(poa, param).rotate90cw().getNormalized(); DeriVector2 tang2 = ray2->CalculateNormal(poa, param).rotate90cw().getNormalized(); DeriVector2 tangB = boundary->CalculateNormal(poa, param).rotate90cw().getNormalized(); double sin1, dsin1, sin2, dsin2; sin1 = tang1.scalarProd(tangB, &dsin1); // sinus of angle of incidence sin2 = tang2.scalarProd(tangB, &dsin2); if (flipn1) { sin1 = -sin1; dsin1 = -dsin1; } if (flipn2) { sin2 = -sin2; dsin2 = -dsin2; } double dn1 = (param == n1()) ? 1.0 : 0.0; double dn2 = (param == n2()) ? 1.0 : 0.0; if (err) { *err = *n1() * sin1 - *n2() * sin2; } if (grad) { *grad = dn1 * sin1 + *n1() * dsin1 - dn2 * sin2 - *n2() * dsin2; } } // -------------------------------------------------------- // ConstraintEqualLineLength ConstraintEqualLineLength::ConstraintEqualLineLength(Line& l1, Line& l2) : l1(l1) , l2(l2) { this->l1.PushOwnParams(pvec); this->l2.PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } void ConstraintEqualLineLength::ReconstructGeomPointers() { int i = 0; l1.ReconstructOnNewPvec(pvec, i); l2.ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } ConstraintType ConstraintEqualLineLength::getTypeId() { return EqualLineLength; } void ConstraintEqualLineLength::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } DeriVector2 p1(l1.p1, param); DeriVector2 p2(l1.p2, param); DeriVector2 p3(l2.p1, param); DeriVector2 p4(l2.p2, param); DeriVector2 v1 = p1.subtr(p2); DeriVector2 v2 = p3.subtr(p4); double length1, dlength1; length1 = v1.length(dlength1); double length2, dlength2; length2 = v2.length(dlength2); if (err) { *err = length2 - length1; } if (grad) { *grad = dlength2 - dlength1; // if the one of the lines gets vertical or horizontal, the gradients will become zero. this // will affect the diagnose function and the detection of dependent/independent parameters. // // So here we maintain the very small derivative of 1e-10 when the gradient is under such // value, such that the diagnose function with pivot threshold of 1e-13 treats the value as // non-zero and correctly detects and can tell apart when a parameter is fully constrained // or just locked into a maximum/minimum if (fabs(*grad) < 1e-10) { double surrogate = 1e-10; if (param == l1.p1.x) { *grad = v1.x > 0 ? surrogate : -surrogate; } if (param == l1.p1.y) { *grad = v1.y > 0 ? surrogate : -surrogate; } if (param == l1.p2.x) { *grad = v1.x > 0 ? -surrogate : surrogate; } if (param == l1.p2.y) { *grad = v1.y > 0 ? -surrogate : surrogate; } if (param == l2.p1.x) { *grad = v2.x > 0 ? surrogate : -surrogate; } if (param == l2.p1.y) { *grad = v2.y > 0 ? surrogate : -surrogate; } if (param == l2.p2.x) { *grad = v2.x > 0 ? -surrogate : surrogate; } if (param == l2.p2.y) { *grad = v2.y > 0 ? -surrogate : surrogate; } } } } // -------------------------------------------------------- // ConstraintC2CDistance ConstraintC2CDistance::ConstraintC2CDistance(Circle& c1, Circle& c2, double* d) : c1(c1) , c2(c2) { pvec.push_back(d); this->c1.PushOwnParams(pvec); this->c2.PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } void ConstraintC2CDistance::ReconstructGeomPointers() { int i = 0; i++; // skip the first parameter as there is the inline function distance for it c1.ReconstructOnNewPvec(pvec, i); c2.ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } ConstraintType ConstraintC2CDistance::getTypeId() { return C2CDistance; } void ConstraintC2CDistance::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } DeriVector2 ct1(c1.center, param); DeriVector2 ct2(c2.center, param); DeriVector2 vector_ct12 = ct1.subtr(ct2); double length_ct12, dlength_ct12; length_ct12 = vector_ct12.length(dlength_ct12); // outer case (defined as the centers of the circles are outside the center of the other // circles) it may well be that the circles intersect. if (length_ct12 >= *c1.rad && length_ct12 >= *c2.rad) { if (err) { *err = length_ct12 - (*c2.rad + *c1.rad + *distance()); } else if (grad) { double drad = (param == c2.rad || param == c1.rad || param == distance()) ? -1.0 : 0.0; *grad = dlength_ct12 + drad; } } else { double* bigradius = (*c1.rad >= *c2.rad) ? c1.rad : c2.rad; double* smallradius = (*c1.rad >= *c2.rad) ? c2.rad : c1.rad; double smallspan = *smallradius + length_ct12 + *distance(); if (err) { *err = *bigradius - smallspan; } else if (grad) { double drad = 0.0; if (param == bigradius) { drad = 1.0; } else if (param == smallradius) { drad = -1.0; } else if (param == distance()) { drad = (*distance() < 0.) ? 1.0 : -1.0; } if (length_ct12 > 1e-13) { *grad = -dlength_ct12 + drad; } else { // concentric case *grad = drad; } } } } // -------------------------------------------------------- // ConstraintC2LDistance ConstraintC2LDistance::ConstraintC2LDistance(Circle& c, Line& l, double* d) : circle(c) , line(l) { pvec.push_back(d); this->circle.PushOwnParams(pvec); this->line.PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } ConstraintType ConstraintC2LDistance::getTypeId() { return C2LDistance; } void ConstraintC2LDistance::ReconstructGeomPointers() { int i = 0; i++; // skip the first parameter as there is the inline function distance for it circle.ReconstructOnNewPvec(pvec, i); line.ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } void ConstraintC2LDistance::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } DeriVector2 ct(circle.center, param); DeriVector2 p1(line.p1, param); DeriVector2 p2(line.p2, param); DeriVector2 v_line = p2.subtr(p1); DeriVector2 v_p1ct = ct.subtr(p1); // center to line distance (=h) and its derivative (=dh) double darea = 0.0; double area = v_line.crossProdZ(v_p1ct, darea); // parallelogram oriented area double dlength; double length = v_line.length(dlength); // vector product (cross vector) has a magnitude corresponding to the area of // the parallelogram defined by the vectors above. The area of the triangle is // half the parallelogram area. The height of the triangle is the area divided by // the base, which is the distance from the center of the circle to the line. // // However, the vector (which points in z direction), can be positive or negative. // the area is the absolute value double h = std::abs(area) / length; // darea is the magnitude of a vector in the z direction, which makes the area vector // increase or decrease. If area vector is negative a negative value makes the area increase // and a positive value makes it decrease. darea = std::signbit(area) ? -darea : darea; double dh = (darea - h * dlength) / length; if (err) { if (h < *circle.rad) { *err = *circle.rad - std::abs(*distance()) - h; } else { *err = *circle.rad + std::abs(*distance()) - h; } } else if (grad) { if (param == distance() || param == circle.rad) { if (h < *circle.rad) { *grad = -1.0; } else { *grad = 1.0; } } else { *grad = -dh; } } } // -------------------------------------------------------- // ConstraintP2CDistance ConstraintP2CDistance::ConstraintP2CDistance(Point& p, Circle& c, double* d) : circle(c) , pt(p) { pvec.push_back(d); this->circle.PushOwnParams(pvec); this->pt.PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } ConstraintType ConstraintP2CDistance::getTypeId() { return P2CDistance; } void ConstraintP2CDistance::ReconstructGeomPointers() { int i = 0; i++; // skip the first parameter as there is the inline function distance for it circle.ReconstructOnNewPvec(pvec, i); pt.ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } void ConstraintP2CDistance::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } DeriVector2 ct(circle.center, param); DeriVector2 p(pt, param); DeriVector2 v_length = ct.subtr(p); double dlength; double length = v_length.length(dlength); if (err) { *err = *circle.rad + *distance() - length; if (length < *circle.rad) { *err = *circle.rad - *distance() - length; } } else if (grad) { if (param == distance()) { *grad = 1.0; if (length < *circle.rad) { *grad = -1.0; } } else if (param == circle.rad) { *grad = 1.0; } else { *grad = -dlength; } } } // -------------------------------------------------------- // ConstraintArcLength ConstraintArcLength::ConstraintArcLength(Arc& a, double* d) : arc(a) { pvec.push_back(d); this->arc.PushOwnParams(pvec); origpvec = pvec; pvecChangedFlag = true; rescale(); } void ConstraintArcLength::ReconstructGeomPointers() { int i = 0; i++; // skip the first parameter as there is the inline function distance for it arc.ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } ConstraintType ConstraintArcLength::getTypeId() { return ArcLength; } void ConstraintArcLength::errorgrad(double* err, double* grad, double* param) { if (pvecChangedFlag) { ReconstructGeomPointers(); } double rad = *arc.rad; double endA = *arc.endAngle; double startA = *arc.startAngle; // Assume positive angles and CCW arc while (startA < 0.) { startA += 2. * std::numbers::pi; } while (endA < startA) { endA += 2. * std::numbers::pi; } if (err) { *err = rad * (endA - startA) - *distance(); } else if (grad) { if (param == distance()) { // if constraint is not driving it varies on distance(). *grad = -1.; } else { double dRad = param == arc.rad ? 1. : 0.; double dStartA = param == arc.startAngle ? 1. : 0.; double dEndA = param == arc.endAngle ? 1. : 0.; *grad = rad * (dEndA - dStartA) + dRad * (endA - startA); } } } } // namespace GCS