/**************************************************************************** ** ** This file is part of the LibreCAD project, a 2D CAD program ** ** Copyright (C) 2011-2015 Dongxu Li (dongxuli2011@gmail.com) ** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl) ** Copyright (C) 2001-2003 RibbonSoft. All rights reserved. ** ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file gpl-2.0.txt included in the ** packaging of this file. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ** ** This copyright notice MUST APPEAR in all copies of the script! ** **********************************************************************/ #include "rs_ellipse.h" #include "lc_quadratic.h" #include "lc_rect.h" #include "rs_circle.h" #include "rs_debug.h" #include "rs_entitycontainer.h" #include "rs_information.h" #include "rs_line.h" #include "rs_math.h" #include "rs_painter.h" #ifdef EMU_C99 #include "emu_c99.h" /* C99 math */ #endif // Workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-22829 // TODO: the Q_MOC_RUN detection shouldn't be necessary after this Qt bug is resolved #ifndef Q_MOC_RUN #include #include #include #if BOOST_VERSION > 104500 #include #endif #endif namespace{ //functor to solve for distance, used by snapDistance class EllipseDistanceFunctor { public: EllipseDistanceFunctor(RS_Ellipse const& ellipse, double const& target) : distance{target} , e{ellipse} , ra{e.getMajorRadius()} , k2{1. - e.getRatio() * e.getRatio()} , k2ra{k2 * ra} { } void setDistance(const double& target){ distance=target; } #if BOOST_VERSION > 104500 boost::tuples::tuple operator()(double const& z) const { #else boost::fusion::tuple operator()(double const& z) const { #endif double const cz=std::cos(z); double const sz=std::sin(z); //delta amplitude double const d=std::sqrt(1-k2*sz*sz); // return f(x), f'(x) and f''(x) #if BOOST_VERSION > 104500 return boost::tuples::make_tuple( #else return boost::fusion::make_tuple( #endif e.getEllipseLength(z) - distance, ra * d, k2ra * sz * cz / d ); } private: double distance; RS_Ellipse const& e; const double ra; const double k2; const double k2ra; }; /** * @brief getNearestDistHelper find end point after trimmed by amount * @param e ellipse which is not reversed, assume ratio (a/b) >= 1 * @param trimAmount the length of the trimmed is increased by this amount * @param coord current mouse position * @param dist if this pointer is not nullptr, save the distance from the new * end point to mouse position coord * @return the new end point of the trimmed. Only one end of the entity is * trimmed */ RS_Vector getNearestDistHelper(RS_Ellipse const& e, double trimAmount, RS_Vector const& coord, double* dist = nullptr) { double const x1 = e.getAngle1(); double const guess= x1 + M_PI; int const digits=std::numeric_limits::digits; double const wholeLength = e.getEllipseLength(0, 0); // start/end angle 0 is used for whole ellipses double trimmed = e.getLength() + trimAmount; // choose the end to trim by the mouse position coord bool const trimEnd = coord.squaredTo(e.getStartpoint()) <= coord.squaredTo(e.getEndpoint()); if (trimEnd) trimmed = trimAmount > 0 ? wholeLength - trimAmount : - trimAmount; //solve equation of the distance by second order newton_raphson EllipseDistanceFunctor X{e, trimmed}; using namespace boost::math::tools; double const sol = halley_iterate(X, guess, x1, x1 + 2 * M_PI - RS_TOLERANCE_ANGLE, digits); RS_Vector const vp = e.getEllipsePoint(sol); if (dist) *dist = vp.distanceTo(coord); return vp; } /** * @brief The ClosestElliptic class: find the closest point on an ellipse for a given point. * Intended for ellipses with small eccentricities. * Algorithm: Newton-Raphson * Added for issue #1653 */ class ClosestEllipticPoint { public: ClosestEllipticPoint(double a, double b, const RS_Vector& point): m_point{point} , c2{b * b - a * a} , ax2{2.*a*point.x} , by2{2.*b*point.y} {} // The elliptic angle of the closest point on ellipse. double getTheta() const { double theta = std::atan2(m_point.y, m_point.x); // find the zero point of the first order derivative by Newton-Raphson // the convergence should be good: maximum 16 recursions for (short i=0; i<16; ++i) { // The first and second derivatives over theta double d1 = ds2D1(theta); double d2 = ds2D2(theta); if (std::abs(d2) < RS_TOLERANCE || std::abs(d1) < RS_TOLERANCE) break; // Newton-Raphson theta -= d1/d2; } return theta; } private: // The first order derivative of ds2=dx^2+dy^2 over theta double ds2D1(double t) const { using namespace std; return c2*sin(2.*t) + ax2*sin(t) - by2*cos(t); } // The second order derivative of ds2=dx^2+dy^2 over theta double ds2D2(double t) const { using namespace std; return 2.*c2*cos(2.*t) + ax2*cos(t) + by2*sin(t); } RS_Vector m_point{}; double c2=0.; double ax2=0.; double by2=0.; }; /** * @brief The EllipseBorderHelper class a helper class to avoid infinite loop due to calculateBorders() * The only difference from RS_Ellipse is a no-op calculateBorders() method */ class EllipseBorderHelper: public RS_Ellipse { public: EllipseBorderHelper(const RS_Ellipse& ellipse): RS_Ellipse(ellipse) {} // No-op to avoid infinite loop in RS_Ellipse::calculateBorders() void calculateBorders() override {} }; } // anonymous namespace std::ostream& operator << (std::ostream& os, const RS_EllipseData& ed) { os << "(" << ed.center << " " << ed.majorP << " " << ed.ratio << " " << ed.angle1 << "," << ed.angle2 << ")"; return os; } /** * Constructor. */ RS_Ellipse::RS_Ellipse(RS_EntityContainer* parent, const RS_EllipseData& d) :LC_CachedLengthEntity(parent) ,data(d) { //calculateEndpoints(); calculateBorders(); } RS_Entity* RS_Ellipse::clone() const { auto* e = new RS_Ellipse(*this); return e; } /** * Calculates the boundary box of this ellipse. * @author Dongxu Li */ void RS_Ellipse::calculateBorders() { #ifndef EMU_C99 using std::isnormal; #endif if (std::abs(data.angle1) < RS_TOLERANCE_ANGLE && (std::abs(data.angle2) < RS_TOLERANCE_ANGLE)){ data.angle1 = 0; data.angle2 = 0; } data.isArc = isnormal(data.angle1) || isnormal(data.angle2); LC_Rect boundingBox = isEllipticArc() ? LC_Rect{ getStartpoint(), getEndpoint() } : LC_Rect{}; // x-range extremes are at this direction and its opposite, relative to the ellipse center const RS_Vector vpx{ getMajorP().x, -getRatio()*getMajorP().y }; mergeBoundingBox(boundingBox, vpx); // y-range extremes are at this direction and its opposite, relative to the ellipse center const RS_Vector vpy{ getMajorP().y, getRatio()*getMajorP().x }; mergeBoundingBox(boundingBox, vpy); minV = boundingBox.minP(); maxV = boundingBox.maxP(); data.angleDegrees = RS_Math::rad2deg(getAngle()); data.startAngleDegrees = RS_Math::rad2deg(data.reversed ? data.angle2 : data.angle1); data.otherAngleDegrees = RS_Math::rad2deg(data.reversed ? data.angle1 : data.angle2); data.angularLength = RS_Math::rad2deg(RS_Math::getAngleDifference(data.angle1, data.angle2, data.reversed)); if (std::abs(data.angularLength) < RS_TOLERANCE_ANGLE) { // check whether angles are via period if (RS_Math::getPeriodsCount(data.angle1, data.angle2, data.reversed) != 0) { data.angularLength = 360; // in degrees } } updateLength(); } void RS_Ellipse::mergeBoundingBox(LC_Rect& boundingBox, const RS_Vector& direction) { const double angle = direction.angle(); // Test the given direction and its opposite for(double a: {angle, angle + M_PI}) if(RS_Math::isAngleBetween(a, getAngle1(), getAngle2(), isReversed())) boundingBox = boundingBox.merge(getEllipsePoint(a)); } /** * return the foci of ellipse * * @author Dongxu Li */ RS_VectorSolutions RS_Ellipse::getFoci() const { RS_Ellipse e=*this; if(getRatio()>1.) e.switchMajorMinor(); RS_Vector vp(e.getMajorP()*sqrt(1.-e.getRatio()*e.getRatio())); return RS_VectorSolutions({getCenter()+vp, getCenter()-vp}); } RS_VectorSolutions RS_Ellipse::getRefPoints() const { RS_VectorSolutions ret; if(isEllipticArc()){ //no start/end point for whole ellipse ret.push_back(getStartpoint()); ret.push_back(getEndpoint()); } ret.push_back(data.center); ret.push_back(getFoci()); ret.push_back(getMajorPoint()); ret.push_back(getMinorPoint()); return ret; } RS_Vector RS_Ellipse::getNearestEndpoint(const RS_Vector& coord, double* dist)const { if (!isEllipticArc()) return RS_Vector{false}; RS_Vector startpoint = getStartpoint(); RS_Vector endpoint = getEndpoint(); double dist1 = (startpoint-coord).squared(); double dist2 = (endpoint-coord).squared(); if (dist21.) e.switchMajorMinor(); // required to be not reversed in getEllipseLength() if(e.isReversed()) { std::swap(e.data.angle1, e.data.angle2); e.setReversed(false); } cachedLength = e.getEllipseLength(e.data.angle1,e.data.angle2); } /** //Ellipse must have ratio<1, and not reversed *@ x1, ellipse angle *@ x2, ellipse angle //@return the arc length between ellipse angle x1, x2 * \author Dongxu Li **/ double RS_Ellipse::getEllipseLength(double x1, double x2) const{ double a(getMajorRadius()),k(getRatio()); k= std::sqrt(1-k*k);//elliptic modulus, or eccentricity // std::cout<<"1, angle1="<(k); } else { ret=0.; } x1=std::fmod(x1,M_PI); x2=std::fmod(x2,M_PI); if( std::abs(x2-x1)>RS_TOLERANCE_ANGLE) { ret += RS_Math::ellipticIntegral_2(k,x2)-RS_Math::ellipticIntegral_2(k,x1); } return a*ret; } /** * arc length from start point (angle1) */ double RS_Ellipse::getEllipseLength(double x2) const{ return getEllipseLength(getAngle1(),x2); } /** * get the point on the ellipse arc and with arc distance from the start point * the distance is expected to be within 0 and getLength() * using Newton-Raphson from boost * *@author: Dongxu Li */ RS_Vector RS_Ellipse::getNearestDist(double distance, const RS_Vector& coord, double* dist) const{ // RS_DEBUG->print("RS_Ellipse::getNearestDist() begin\n"); if( ! isEllipticArc() ) { // both angles being 0, whole ellipse // no end points for whole ellipse, therefore, no snap by distance from end points. return {}; } RS_Ellipse e(nullptr,data); if(e.getRatio()>1.) e.switchMajorMinor(); if(e.isReversed()) { std::swap(e.data.angle1,e.data.angle2); e.setReversed(false); } if(e.getMajorRadius() < RS_TOLERANCE) return {}; //ellipse too small if(getRatio() l0+RS_TOLERANCE) return {}; // can not trim more than the current length if(distance > l0-RS_TOLERANCE) return getNearestEndpoint(coord,dist); // trim to zero length return getNearestDistHelper(e, distance, coord, dist); } /** * switch the major/minor axis naming * * \author: Dongxu Li */ bool RS_Ellipse::switchMajorMinor(void) //switch naming of major/minor, return true if success { if (std::abs(data.ratio) < RS_TOLERANCE) return false; RS_Vector vp_start=getStartpoint(); RS_Vector vp_end=getEndpoint(); RS_Vector vp=getMajorP(); setMajorP(RS_Vector(- data.ratio*vp.y, data.ratio*vp.x)); //direction pi/2 relative to old MajorP; setRatio(1./data.ratio); if( isEllipticArc() ) { //only reset start/end points for ellipse arcs, i.e., angle1 angle2 are not both zero setAngle1(getEllipseAngle(vp_start)); setAngle2(getEllipseAngle(vp_end)); } calculateBorders(); return true; } /** * @return Start point of the entity. */ RS_Vector RS_Ellipse::getStartpoint() const { return isEllipticArc() ? getEllipsePoint(data.angle1) : RS_Vector{false}; } /** * @return End point of the entity. */ RS_Vector RS_Ellipse::getEndpoint() const { return isEllipticArc() ? getEllipsePoint(data.angle2) : RS_Vector{false}; } /** * @return Ellipse point by ellipse angle */ RS_Vector RS_Ellipse::getEllipsePoint(double a) const { RS_Vector point{a}; double ra=getMajorRadius(); point.scale(RS_Vector(ra,ra*getRatio())); point.rotate(getAngle()); point.move(getCenter()); return point; } /** \brief implemented using an analytical algorithm * find nearest point on ellipse to a given point * * @author Dongxu Li */ RS_Vector RS_Ellipse::getNearestPointOnEntity(const RS_Vector& coord, bool onEntity, double* dist, RS_Entity** entity)const{ RS_DEBUG->print("RS_Ellipse::getNearestPointOnEntity"); RS_Vector ret(false); if( !coord.valid ) { if (dist != nullptr) *dist=RS_MAXDOUBLE; return ret; } if (entity != nullptr) { *entity = const_cast(this); } ret=coord; ret.move(-getCenter()); ret.rotate(-getAngle()); double x=ret.x,y=ret.y; double a=getMajorRadius(); double b=a*getRatio(); // the tangential direction at the nearest RS_Vector perpendicular{-ret.y, ret.x}; //std::cout<<"(a= "< ce(4,0.); std::vector roots; //need to handle: a=b (i.e. a0=0); point close to the ellipse origin. if (a0 > RS_TOLERANCE && std::abs(getRatio() - 1.0) > RS_TOLERANCE && ret.squared() > RS_TOLERANCE2 ) { // a != b , ellipse ce[0]=-2.*twoax/twoa2b2; ce[1]= (twoax*twoax+twoby*twoby)/a0-1.; ce[2]= - ce[0]; ce[3]= -twoax*twoax/a0; //std::cout<<"1::find cosine, variable c, solve(c^4 +("<> directions; for(double cosTheta: roots) { if (std::abs(twoax-twoa2b2*cosTheta) > RS_TOLERANCE) { double const sinTheta=twoby*cosTheta/(twoax-twoa2b2*cosTheta); //sine directions.emplace_back(cosTheta, sinTheta); } else { directions.emplace_back(0., 1.); directions.emplace_back(0., -1.); } } for(const auto &[cosTheta, sinTheta]: directions) { //I don't understand the reason yet, but I can do without checking whether sine/cosine are valid //if (std::abs(s) > 1. ) continue; double const d2=twoa2b2+(twoax-2.*cosTheta*twoa2b2)*cosTheta+twoby*sinTheta; if (std::signbit(d2)) continue; // fartherest RS_Vector vp3{a*cosTheta, b*sinTheta}; double d=(vp3-ret).squared(); // std::cout<print(RS_Debug::D_ERROR,"RS_Ellipse::getNearestPointOnEntity() finds no minimum, this should not happen\n"); } if (dist != nullptr) { *dist = std::sqrt(dDistance); } ret.rotate(getAngle()); ret.move(getCenter()); // ret=vp2; if (onEntity) { if (!RS_Math::isAngleBetween(getEllipseAngle(ret), getAngle1(), getAngle2(), isReversed())) { // not on entity, use the nearest endpoint //std::cout<<"not on ellipse, ( "< t) return false; return RS_Math::isAngleBetween(vp.angle(),getAngle1(),getAngle2(),isReversed()); } RS_Vector RS_Ellipse::getNearestCenter(const RS_Vector& coord, double* dist) const{ RS_Vector vCenter = data.center; double distCenter = coord.distanceTo(data.center); RS_VectorSolutions vsFoci = getFoci(); if( 2 == vsFoci.getNumber()) { RS_Vector const& vFocus1 = vsFoci.get(0); RS_Vector const& vFocus2 = vsFoci.get(1); double distFocus1 = coord.distanceTo(vFocus1); double distFocus2 = coord.distanceTo(vFocus2); /* if (distFocus1 < distCenter) is true * then (distFocus1 < distFocus2) must be true too * and vice versa * no need to check this */ if( distFocus1 < distCenter) { vCenter = vFocus1; distCenter = distFocus1; } else if( distFocus2 < distCenter) { vCenter = vFocus2; distCenter = distFocus2; } } if (dist != nullptr) { *dist = distCenter; } return vCenter; } /** //create Ellipse with axes in x-/y- directions from 4 points * * *@author Dongxu Li */ bool RS_Ellipse::createFrom4P(const RS_VectorSolutions& sol){ if (sol.getNumber() != 4 ) return (false); //only do 4 points std::vector > mt; std::vector dn; int mSize(4); mt.resize(mSize); for(int i=0;i > mt; int mSize(sol.getNumber() -1); if( (sol.get(mSize) - sol.get(mSize-1)).squared() < RS_TOLERANCE15 ) { //remove the last point mSize--; } mt.resize(mSize); std::vector dn(mSize); switch(mSize){ case 2: for(int i=0;i& dn){ RS_DEBUG->print("RS_Ellipse::createFromQuadratic() begin\n"); if(dn.size()!=3) return false; // if(std::abs(dn[0]) b, d>0 // eigenvalue: ( a+b - s)/2, eigenvector: ( -c, d + s) // eigenvalue: ( a+b + s)/2, eigenvector: ( d + s, c) // } // { a= a+b ) return false; if(a>=b) { setMajorP(RS_Vector(atan2(d+s, -c))/sqrt(0.5*(a+b-s))); }else{ setMajorP(RS_Vector(atan2(-c, s-d))/sqrt(0.5*(a+b-s))); } setRatio(sqrt((a+b-s)/(a+b+s))); // start/end angle at 0. means a whole ellipse, instead of an elliptic arc setAngle1(0.); setAngle2(0.); RS_DEBUG->print("RS_Ellipse::createFromQuadratic(): successful\n"); return true; } bool RS_Ellipse::createFromQuadratic(const LC_Quadratic& q){ if (!q.isQuadratic()) return false; auto const& mQ=q.getQuad(); double const& a=mQ(0,0); double const& c=2.*mQ(0,1); double const& b=mQ(1,1); auto const& mL=q.getLinear(); double const& d=mL(0); double const& e=mL(1); double determinant=c*c-4.*a*b; if (determinant>= -DBL_EPSILON) return false; // find center of quadratic // 2 A x + C y = D // C x + 2 B y = E // x = (2BD - EC)/( 4AB - C^2) // y = (2AE - DC)/(4AB - C^2) const RS_Vector eCenter=RS_Vector(2.*b*d - e*c, 2.*a*e - d*c)/determinant; //generate centered quadratic LC_Quadratic qCentered=q; qCentered.move(-eCenter); if(qCentered.constTerm()>= -DBL_EPSILON) return false; const auto& mq2=qCentered.getQuad(); const double factor=-1./qCentered.constTerm(); //quadratic terms if(!createFromQuadratic({mq2(0,0)*factor, 2.*mq2(0,1)*factor, mq2(1,1)*factor})) return false; //move back to center move(eCenter); return true; } /** //create Ellipse inscribed in a quadrilateral * *algorithm: http://chrisjones.id.au/Ellipses/ellipse.html *finding the tangential points and ellipse center * *@author Dongxu Li */ bool RS_Ellipse::createInscribeQuadrilateral(const std::vector& lines, std::vector &tangent){ if (lines.size() != 4) return false; //only do 4 lines std::vector > quad(4); { //form quadrilateral from intersections RS_EntityContainer c0(nullptr, false); for(RS_Line*const p: lines){//copy the line pointers c0.addEntity(p); } RS_VectorSolutions const& s0=RS_Information::createQuadrilateral(c0); if(s0.size()!=4) return false; for(size_t i=0; i<4; ++i){ quad[i].reset(new RS_Line{s0[i], s0[(i+1)%4]}); } } //center of original square projected, intersection of diagonal RS_Vector centerProjection; { std::vector diagonal; diagonal.emplace_back(quad[0]->getStartpoint(), quad[1]->getEndpoint()); diagonal.emplace_back(quad[1]->getStartpoint(), quad[2]->getEndpoint()); RS_VectorSolutions const& sol=RS_Information::getIntersectionLineLine( & diagonal[0],& diagonal[1]); if(sol.getNumber()==0) {//this should not happen // RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Ellipse::createInscribeQuadrilateral(): can not locate projection Center"); RS_DEBUG->print("RS_Ellipse::createInscribeQuadrilateral(): can not locate projection Center"); return false; } centerProjection=sol.get(0); } // std::cout<<"RS_Ellipse::createInscribe(): centerProjection="< tangent;//holds the tangential points on edges, in the order of edges: 1 3 2 0 int parallel=0; int parallel_index=0; for(int i=0;i<=1;++i) { RS_VectorSolutions const& sol1=RS_Information::getIntersectionLineLine(quad[i].get(), quad[(i+2)%4].get()); RS_Vector direction; if(sol1.getNumber()==0) { direction=quad[i]->getEndpoint()-quad[i]->getStartpoint(); ++parallel; parallel_index=i; }else{ direction=sol1.get(0)-centerProjection; } // std::cout<<"Direction: "<getEndpoint(),(tangent[0]+tangent[2])*0.5); RS_Line cl1(quad[2]->getEndpoint(),(tangent[1]+tangent[2])*0.5); RS_VectorSolutions const& sol=RS_Information::getIntersection(&cl0, &cl1,false); if(sol.getNumber()==0){ //this should not happen // RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Ellipse::createInscribeQuadrilateral(): can not locate Ellipse Center"); RS_DEBUG->print("RS_Ellipse::createInscribeQuadrilateral(): can not locate Ellipse Center"); return false; } ellipseCenter=sol.get(0); } // qDebug()<<"parallel="<print("RS_Ellipse::createInscribeQuadrilateral(): trapezoid detected\n"); //trapezoid RS_Line* l0=quad[parallel_index].get(); RS_Line* l1=quad[(parallel_index+2)%4].get(); RS_Vector centerPoint=(l0->getMiddlePoint()+l1->getMiddlePoint())*0.5; //not symmetric, no inscribed ellipse if( std::abs(centerPoint.distanceTo(l0->getStartpoint()) - centerPoint.distanceTo(l0->getEndpoint()))>RS_TOLERANCE) return false; //symmetric RS_DEBUG->print("RS_Ellipse::createInscribeQuadrilateral(): symmetric trapezoid detected\n"); double d=l0->getDistanceToPoint(centerPoint); double l=((l0->getLength()+l1->getLength()))*0.25; double k= 4.*d/std::abs(l0->getLength()-l1->getLength()); double theta=d/(l*k); if(theta>=1. || dprint("RS_Ellipse::createInscribeQuadrilateral(): this should not happen\n"); return false; } theta=asin(theta); //major axis double a=d/(k*tan(theta)); setCenter(RS_Vector(0., 0.)); setMajorP(RS_Vector(a, 0.)); setRatio(d/a); rotate(l0->getAngle1()); setCenter(centerPoint); return true; } // double ratio; // std::cout<<"dn="< dn(3); RS_Vector angleVector(false); for(size_t i=0;i > mt; mt.clear(); const double symTolerance=20.*RS_TOLERANCE; for(const RS_Vector& vp: tangent){ //form the linear equation // need to remove duplicated {x^2, xy, y^2} terms due to symmetry (x => -x, y=> -y) // i.e. rotation of 180 degrees around ellipse center // std::cout<<"point : "< mtRow; mtRow.push_back(vp.x*vp.x); mtRow.push_back(vp.x*vp.y); mtRow.push_back(vp.y*vp.y); const double l= hypot(hypot(mtRow[0], mtRow[1]), mtRow[2]); bool addRow(true); for(const auto& v: mt){ RS_Vector const dv{v[0] - mtRow[0], v[1] - mtRow[1], v[2] - mtRow[2]}; if( dv.magnitude() < symTolerance*l){ //symmetric addRow=false; break; } } if(addRow) { mtRow.push_back(1.); mt.push_back(mtRow); } } // std::cout<<"mt.size()="<print("RS_Ellipse::createInscribeQuadrilateral(): parallelogram detected\n"); //fixme, need to handle degenerate case better // double angle(center.angleTo(tangent[0])); RS_Vector majorP(tangent[0]); double dx(majorP.magnitude()); if(dxprint(RS_Debug::D_WARNING,"No inscribed ellipse for non isosceles trapezoid"); return false; //invalid quadrilateral } if(! createFromQuadratic(dn)) return false; setCenter(ellipseCenter); if(angleVector.valid) {//need to rotate back, for the parallelogram case angleVector.y *= -1.; rotate(ellipseCenter,angleVector); } return true; } /** * a naive implementation of middle point * to accurately locate the middle point from arc length is possible by using elliptic integral to find the total arc length, then, using elliptic function to find the half length point */ RS_Vector RS_Ellipse::getMiddlePoint()const{ return getNearestMiddle(getCenter()); } /** * get Nearest equidistant point * *@author: Dongxu Li */ RS_Vector RS_Ellipse::getNearestMiddle(const RS_Vector& coord, double* dist, int middlePoints ) const{ RS_DEBUG->print("RS_Ellpse::getNearestMiddle(): begin\n"); if ( ! isEllipticArc() ) { //no middle point for whole ellipse, angle1=angle2=0 if (dist) { *dist = RS_MAXDOUBLE; } return RS_Vector(false); } double ra(getMajorRadius()); double rb(getRatio()*ra); if ( ra < RS_TOLERANCE || rb < RS_TOLERANCE ) { //zero radius, return the center RS_Vector vp(getCenter()); if (dist) { *dist = vp.distanceTo(coord); } return vp; } double amin=getCenter().angleTo(getStartpoint()); double amax=getCenter().angleTo(getEndpoint()); if(isReversed()) { std::swap(amin,amax); } double da=std::fmod(amax-amin+2.*M_PI, 2.*M_PI); if ( da < RS_TOLERANCE ) { da = 2.*M_PI; //whole ellipse } RS_Vector vp(getNearestPointOnEntity(coord,true,dist)); double a=getCenter().angleTo(vp); int counts(middlePoints + 1); int i = static_cast(std::fmod(a-amin+2.*M_PI,2.*M_PI)/da*counts+0.5); // remove end points i = std::max(i, 1); i = std::min(i, counts - 1); a=amin + da*(double(i)/double(counts))-getAngle(); vp.set(a); RS_Vector vp2(vp); vp2.scale( RS_Vector(1./ra,1./rb)); vp.scale(1./vp2.magnitude()); vp.rotate(getAngle()); vp.move(getCenter()); if (dist != nullptr) { *dist = vp.distanceTo(coord); } //RS_DEBUG->print("RS_Ellipse::getNearestMiddle: angle1=%g, angle2=%g, middle=%g\n",amin,amax,a); RS_DEBUG->print("RS_Ellpse::getNearestMiddle(): end\n"); return vp; } /** * get the tangential point of a tangential line orthogonal to a given line *@ normal, the given line *@ onEntity, should the tangential be required to on entity of the elliptic arc *@ coord, current cursor position * *@author: Dongxu Li */ RS_Vector RS_Ellipse::getNearestOrthTan( const RS_Vector &coord, const RS_Line &normal, bool onEntity) const{ if (!coord.valid){ return RS_Vector(false); } RS_Vector direction = normal.getEndpoint() - normal.getStartpoint(); if (direction.squared() < RS_TOLERANCE15){ //undefined direction return RS_Vector(false); } //scale to ellipse angle RS_Vector aV(-getAngle()); direction.rotate(aV); double angle = direction.scale(RS_Vector(1., getRatio())).angle(); double ra(getMajorRadius()); direction.set(ra * cos(angle), getRatio() * ra * sin(angle));//relative to center std::vector sol; for (int i = 0; i < 2; i++) { if (!onEntity || RS_Math::isAngleBetween(angle, getAngle1(), getAngle2(), isReversed())){ sol.push_back(i == 0 ? direction : - direction); } angle = RS_Math::correctAngle(angle + M_PI); } if (sol.size() < 1) return RS_Vector(false); aV.y *= -1.; for (auto &v: sol) { v.rotate(aV); } RS_Vector vp{}; switch (sol.size()) { case 0: return RS_Vector(false); case 2: if (RS_Vector::dotP(sol[1], coord - getCenter()) > 0.){ vp = sol[1]; break; } // fall-through default: vp = sol[0]; break; } return getCenter() + vp; } double RS_Ellipse::getBulge() const { double bulge = std::tan(std::abs(getAngleLength()) / 4.0); return isReversed() ? -bulge : bulge; } RS_Vector RS_Ellipse::dualLineTangentPoint(const RS_Vector& line) const{ // u x + v y = 1 // coordinates : dual // rotate (-a) : rotate(a) RS_Vector uv = RS_Vector{line}.rotate(-data.majorP.angle()); // slope = -b c/ a s ( a s, - b c) // x a s - b c y =0 -> s/c = b y / a x // elliptical angle double t = std::atan2(data.ratio * uv.y, uv.x); RS_Vector vp{data.majorP.magnitude()*std::cos(t), data.majorP.magnitude()*data.ratio*std::sin(t)}; vp.rotate(data.majorP.angle()); RS_Vector vp0 = data.center + vp; RS_Vector vp1 = data.center - vp; auto lineEqu = [&line](const RS_Vector& vp) { return std::abs(line.dotP(vp) + 1.); }; return lineEqu(vp0) < lineEqu(vp1) ? vp0 : vp1; } void RS_Ellipse::move(const RS_Vector& offset) { data.center.move(offset); //calculateEndpoints(); // minV.move(offset); // maxV.move(offset); moveBorders(offset); } void RS_Ellipse::rotate(const RS_Vector& center, double angle) { RS_Vector angleVector(angle); data.center.rotate(center, angleVector); data.majorP.rotate(angleVector); //calculateEndpoints(); calculateBorders(); } void RS_Ellipse::revertDirection(){ if (data.isArc) { std::swap(data.angle1, data.angle2); data.reversed = !data.reversed; calculateBorders(); } } void RS_Ellipse::rotate(const RS_Vector& center, const RS_Vector& angleVector) { data.center.rotate(center, angleVector); data.majorP.rotate(angleVector); //calculateEndpoints(); calculateBorders(); } void RS_Ellipse::rotate(double angle) {//rotate around origin RS_Vector aV(angle); data.center.rotate(aV); data.majorP.rotate(aV); calculateBorders(); } void RS_Ellipse::rotate(const RS_Vector& angleVector) {//rotate around origin data.center.rotate(angleVector); data.majorP.rotate(angleVector); //calculateEndpoints(); calculateBorders(); } /** * make sure angleLength() is not more than 2*M_PI */ void RS_Ellipse::correctAngles() { double *pa1= & data.angle1; double *pa2= & data.angle2; if (isReversed()) std::swap(pa1,pa2); *pa2 = *pa1 + std::fmod(*pa2 - *pa1, 2.*M_PI); if ( std::abs(data.angle1 - data.angle2) < RS_TOLERANCE_ANGLE && (std::abs(data.angle1) > RS_TOLERANCE_ANGLE)) { // we need this only if there are actual angles (arc). otherwise, adding 2pi will transform ellipse to // elliptic arc *pa2 += 2.*M_PI; } } void RS_Ellipse::moveStartpoint(const RS_Vector& pos) { data.angle1 = getEllipseAngle(pos); //data.angle1 = data.center.angleTo(pos); //calculateEndpoints(); correctAngles(); // make sure angleLength is no more than 2*M_PI calculateBorders(); } void RS_Ellipse::moveEndpoint(const RS_Vector& pos) { data.angle2 = getEllipseAngle(pos); //data.angle2 = data.center.angleTo(pos); //calculateEndpoints(); correctAngles(); // make sure angleLength is no more than 2*M_PI calculateBorders(); } RS2::Ending RS_Ellipse::getTrimPoint(const RS_Vector& trimCoord, const RS_Vector& /*trimPoint*/) { //double angEl = getEllipseAngle(trimPoint); double angM = getEllipseAngle(trimCoord); if (RS_Math::getAngleDifference(angM, data.angle1,isReversed()) > RS_Math::getAngleDifference(data.angle2,angM,isReversed())) { return RS2::EndingStart; } else { return RS2::EndingEnd; } } RS_Vector RS_Ellipse::prepareTrim(const RS_Vector& trimCoord, const RS_VectorSolutions& trimSol) { //special trimming for ellipse arc RS_DEBUG->print("RS_Ellipse::prepareTrim()"); if(!trimSol.hasValid()) return RS_Vector{false}; if(trimSol.getNumber() == 1) return trimSol.front(); double am=getEllipseAngle(trimCoord); std::vector ias; double ia(0.),ia2(0.); RS_Vector is,is2; for(size_t ii=0; ii da2) && (RS_Math::isAngleBetween(ia2,getAngle2(),ia,isReversed()))) ) { std::swap(is,is2); //std::cout<<"reset: angle1="<1.) { switchMajorMinor(); } else{ calculateBorders(); } return; } } //move major/minor points if ((ref-getMajorPoint()).squared()1.) { switchMajorMinor(); } else{ calculateBorders(); } return; } if ((ref-getMinorPoint()).squared()1.) { switchMajorMinor(); } else{ calculateBorders(); } return; } } /** return the equation of the entity for quadratic, return a vector contains: m0 x^2 + m1 xy + m2 y^2 + m3 x + m4 y + m5 =0 for linear: m0 x + m1 y + m2 =0 **/ LC_Quadratic RS_Ellipse::getQuadratic() const { std::vector ce(6,0.); ce[0]=data.majorP.squared(); ce[2]= data.ratio*data.ratio*ce[0]; if(ce[0]getWcsBoundingRect(); if (LC_Rect{getMin(), getMax()}.inArea(vpRect)) { // The whole ellipse/arc is visible in viewport double startAngle = RS_Math::rad2deg(getAngle1()); double endAngle = RS_Math::rad2deg(getAngle2()); if (isReversed()) { std::swap(startAngle, endAngle); } const double angularLength = RS_Math::rad2deg(getAngleLength()); painter->drawEllipseArcWCS(data.center, getMajorRadius(), data.ratio, data.angleDegrees, startAngle, endAngle, angularLength, false); return; } painter->updateDashOffset(this); // only draw the visible portion of the ellipse/arc // find visible portion by intersection with viewport borders in WCS // coordinates std::array vertices = vpRect.vertices(); /** angles at cross points */ std::vector crossPoints(0); double baseAngle=isReversed()?getAngle2():getAngle1(); for(unsigned short i=0; i RS_TOLERANCE_ANGLE) { crossPoints.push_back( RS_Math::getAngleDifference(baseAngle, getEllipseAngle(vp)) ); } } } RS_Vector vpStart = isReversed()?getEndpoint():getStartpoint(); RS_Vector vpEnd = isReversed()?getStartpoint():getEndpoint(); if(vpRect.inArea(vpStart, RS_TOLERANCE)) crossPoints.push_back(0.); if(vpRect.inArea(vpEnd, RS_TOLERANCE)) { const bool isArc = !std::isnormal(getAngle1()) || std::abs(getAngle2() - getAngle1() - 2. * M_PI) > RS_TOLERANCE_ANGLE; const double crossAngle = isArc ? RS_Math::getAngleDifference(baseAngle,isReversed()?getAngle1():getAngle2()) : 2. * M_PI; crossPoints.push_back(crossAngle); } //sorting std::sort(crossPoints.begin(),crossPoints.end()); //draw visible RS_Ellipse arc(*this); arc.setSelected(isSelected()); arc.setPen(getPen()); arc.setReversed(false); arc.calculateBorders(); // check for all arc segments to avoid possible tangential points as // intersections. Around a tangential point, both segments could be within // the viewport rectangular for(size_t i=1; idrawEllipseArcWCS(data.center, getMajorRadius(), data.ratio, data.angleDegrees, startAngle, endAngle, angularLength, false); } bool RS_Ellipse::isVisibleInWindow(const RS_Painter& painter) const { const LC_Rect& vpRect = painter.getWcsBoundingRect(); return LC_Rect{getMin(), getMax()}.overlaps(vpRect); } /** * Dumps the point's data to stdout. */ std::ostream& operator << (std::ostream& os, const RS_Ellipse& a) { os << " Ellipse: " << a.data << "\n"; return os; }