/**************************************************************************** ** ** 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 "rs_circle.h" #include "rs_line.h" #include "rs_graphic.h" #include "rs_graphicview.h" #include "rs_painter.h" #include "rs_information.h" #include "rs_linetypepattern.h" #include "rs_math.h" #include "lc_quadratic.h" #include "rs_painterqt.h" #include "rs_debug.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::math::tuple operator()(double const& z) const { #else boost::fusion::tuple operator()(double const& z) const { #endif double const cz=cos(z); double const sz=sin(z); //delta amplitude double const d=sqrt(1-k2*sz*sz); // return f(x), f'(x) and f''(x) #if BOOST_VERSION > 104500 return boost::math::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* 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; } } 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) :RS_AtomicEntity(parent) ,data(d) { //calculateEndpoints(); calculateBorders(); } RS_Entity* RS_Ellipse::clone() const { RS_Ellipse* e = new RS_Ellipse(*this); e->initId(); return e; } /** * Calculates the boundary box of this ellipse. * @author Dongxu Li */ void RS_Ellipse::calculateBorders() { RS_Vector startpoint = getStartpoint(); RS_Vector endpoint = getEndpoint(); double minX = std::min(startpoint.x, endpoint.x); double minY = std::min(startpoint.y, endpoint.y); double maxX = std::max(startpoint.x, endpoint.x); double maxY = std::max(startpoint.y, endpoint.y); RS_Vector vp; double amin,amax,a; // x range // vp.set(radius1*cos(angle),radius2*sin(angle)); vp.set(getMajorP().x,getRatio()*getMajorP().y); a=vp.angle(); amin=RS_Math::correctAngle(getAngle1()+a); // to the range of 0 to 2*M_PI amax=RS_Math::correctAngle(getAngle2()+a); // to the range of 0 to 2*M_PI if( RS_Math::isAngleBetween(M_PI,amin,amax,isReversed()) ) { minX= data.center.x-vp.magnitude(); } if( RS_Math::isAngleBetween(2.*M_PI,amin,amax,isReversed()) ) { maxX= data.center.x+vp.magnitude(); } //y range vp.set(getMajorP().y, -getRatio()*getMajorP().x); a=vp.angle(); amin=RS_Math::correctAngle(getAngle1()+a); // to the range of 0 to 2*M_PI amax=RS_Math::correctAngle(getAngle2()+a); // to the range of 0 to 2*M_PI if( RS_Math::isAngleBetween(M_PI,amin,amax,isReversed()) ) { minY= data.center.y-vp.magnitude(); } if( RS_Math::isAngleBetween(2.*M_PI,amin,amax,isReversed()) ) { maxY= data.center.y+vp.magnitude(); } minV.set(minX, minY); maxV.set(maxX, maxY); } /** * 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 { double dist1, dist2; RS_Vector startpoint = getStartpoint(); RS_Vector endpoint = getEndpoint(); dist1 = (startpoint-coord).squared(); dist2 = (endpoint-coord).squared(); if (dist21.) e.switchMajorMinor(); if(e.isReversed()) { e.setReversed(false); std::swap(e.data.angle1,e.data.angle2); } return 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= 1-k*k;//elliptic modulus, or eccentricity // std::cout<<"1, angle1="<(k); } else { ret=0.; } x1=fmod(x1,M_PI); x2=fmod(x2,M_PI); if( fabs(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 (fabs(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)); } return true; } /** * @return Start point of the entity. */ RS_Vector RS_Ellipse::getStartpoint() const { if(isEllipticArc()) return getEllipsePoint(data.angle1); return RS_Vector(false); } /** * @return End point of the entity. */ RS_Vector RS_Ellipse::getEndpoint() const { if(isEllipticArc()) return getEllipsePoint(data.angle2); return RS_Vector(false); } /** * @return Ellipse point by ellipse angle */ RS_Vector RS_Ellipse::getEllipsePoint(const double& a) const { RS_Vector p(a); double ra=getMajorRadius(); p.scale(RS_Vector(ra,ra*getRatio())); p.rotate(getAngle()); p.move(getCenter()); return p; } /** \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 ) *dist=RS_MAXDOUBLE; return ret; } if (entity) { *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(); //std::cout<<"(a= "< ce(4,0.); std::vector roots(0,0.); //need to handle a=b if(a0 > 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 +("<1.) continue; double const s=twoby*roots[i]/(twoax-twoa2b2*roots[i]); //sine //if (fabs(s) > 1. ) continue; double const d2=twoa2b2+(twoax-2.*roots[i]*twoa2b2)*roots[i]+twoby*s; if (d2<0) continue; // fartherest RS_Vector vp3; vp3.set(a*roots[i],b*s); d=(vp3-ret).squared(); // std::cout<print(RS_Debug::D_ERROR,"RS_Ellipse::getNearestPointOnEntity() finds no minimum, this should not happen\n"); } if (dist) { *dist = 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) { *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(fabs(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) { 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( fabs(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/fabs(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=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(fmod(a-amin+2.*M_PI,2.*M_PI)/da*counts+0.5)); if(!i) i++; // remove end points if(i==counts) i--; 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) { *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())) { if(i){ sol.push_back(- direction); }else{ sol.push_back(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; } 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, const double& angle) { RS_Vector angleVector(angle); data.center.rotate(center, angleVector); data.majorP.rotate(angleVector); //calculateEndpoints(); 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( const 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 + fmod(*pa2 - *pa1, 2.*M_PI); if ( fabs(data.angle1 - data.angle2) < RS_TOLERANCE_ANGLE ) *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.get(0)); 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(); return; } } //move major/minor points if ((ref-getMajorPoint()).squared()1.) switchMajorMinor(); return; } if ((ref-getMinorPoint()).squared()1.) switchMajorMinor(); return; } } /** whether the entity's bounding box intersects with visible portion of graphic view //fix me, need to handle overlay container separately */ bool RS_Ellipse::isVisibleInWindow(RS_GraphicView* view) const { RS_Vector vpMin(view->toGraph(0,view->getHeight())); RS_Vector vpMax(view->toGraph(view->getWidth(),0)); //viewport QRectF visualRect(vpMin.x,vpMin.y,vpMax.x-vpMin.x, vpMax.y-vpMin.y); QPolygonF visualBox(visualRect); std::vector vps; for(unsigned short i=0;i<4;i++){ const QPointF& vp(visualBox.at(i)); vps.push_back(RS_Vector(vp.x(),vp.y())); } //check for intersection points with viewport for(unsigned short i=0;i<4;i++){ RS_Line line{vps.at(i),vps.at((i+1)%4)}; RS_Ellipse e0(nullptr, getData()); if( RS_Information::getIntersection(&e0, &line, true).size()>0) return true; } //is startpoint within viewport QRectF ellipseRect(minV.x, minV.y, maxV.x - minV.x, maxV.y - minV.y); return ellipseRect.intersects(visualRect); } /** 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]toGraph(0,view->getHeight())); RS_Vector vpMax(view->toGraph(view->getWidth(),0)); QPolygonF visualBox(QRectF(vpMin.x,vpMin.y,vpMax.x-vpMin.x, vpMax.y-vpMin.y)); RS_Vector vpStart(isReversed()?getEndpoint():getStartpoint()); RS_Vector vpEnd(isReversed()?getStartpoint():getEndpoint()); std::vector vertex(0); for(unsigned short i=0;i<4;i++){ const QPointF& vp(visualBox.at(i)); vertex.emplace_back(vp.x(),vp.y()); } /** angles at cross points */ std::vector crossPoints(0); double baseAngle=isReversed()?getAngle2():getAngle1(); for(unsigned short i=0;i<4;i++){ RS_Line line{vertex.at(i),vertex.at((i+1)%4)}; auto vpIts=RS_Information::getIntersection( static_cast(this), &line, true); // std::cout<<"vpIts.size()="<getFactor().x); double rb(getRatio()*ra); if(std::min(ra, rb) < RS_TOLERANCE) {//ellipse too small painter->drawLine(view->toGui(minV),view->toGui(maxV)); return; } bool drawAsSelected = isSelected() && !(view->isPrinting() || view->isPrintPreview()); double mAngle=getAngle(); RS_Vector cp(view->toGui(getCenter())); if (!drawAsSelected && ( getPen().getLineType()==RS2::SolidLine || view->getDrawingMode()==RS2::ModePreview)) { painter->drawEllipse(cp, ra, rb, mAngle, getAngle1(), getAngle2(), isReversed()); return; } // Pattern: const RS_LineTypePattern* pat = nullptr; if (drawAsSelected) { pat = &RS_LineTypePattern::patternSelected; } else { pat = view->getPattern(getPen().getLineType()); } if (!pat) { RS_DEBUG->print(RS_Debug::D_WARNING, "Invalid pattern for Ellipse"); return; } // Pen to draw pattern is always solid: RS_Pen pen = painter->getPen(); pen.setLineType(RS2::SolidLine); double a1(RS_Math::correctAngle(getAngle1())); double a2(RS_Math::correctAngle(getAngle2())); if (isReversed()) std::swap(a1,a2); if(a2 setPen(pen); if(pat->num <= 0){ RS_DEBUG->print(RS_Debug::D_WARNING,"Invalid pattern when drawing ellipse"); painter->drawEllipse(cp, ra, rb, mAngle, a1, a2, false); return; } std::vector ds(pat->num, 0.); double dpmm=static_cast(painter)->getDpmm(); for (size_t i = 0; i < pat->num; i++) { ds[i]= dpmm * pat->pattern[i]; //pattern length if(fabs(ds[i]) < 1.) ds[i] = copysign(1., ds[i]); } double curA(a1); bool notDone(true); //draw patterned ellipse for (size_t i=0; notDone; i = (i + 1) % pat->num) { double nextA = curA + std::abs(ds[i])/ RS_Vector(ra*sin(curA), rb*cos(curA)).magnitude(); if (nextA > a2){ nextA = a2; notDone = false; } if (ds[i] > 0.) painter->drawEllipse(cp, ra, rb, mAngle, curA, nextA, false); curA=nextA; } } /** * Dumps the point's data to stdout. */ std::ostream& operator << (std::ostream& os, const RS_Ellipse& a) { os << " Ellipse: " << a.data << "\n"; return os; }