| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #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" |
| | #endif |
| | |
| | |
| | #ifndef Q_MOC_RUN |
| | #include <boost/version.hpp> |
| | #include <boost/math/tools/roots.hpp> |
| | #include <boost/math/special_functions/ellint_2.hpp> |
| | #if BOOST_VERSION > 104500 |
| | #include <boost/tuple/tuple.hpp> |
| | #endif |
| | #endif |
| |
|
| | namespace{ |
| |
|
| | |
| | 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<double, double, double> operator()(double const& z) const { |
| | #else |
| | boost::fusion::tuple<double, double, double> operator()(double const& z) const { |
| | #endif |
| | double const cz=std::cos(z); |
| | double const sz=std::sin(z); |
| | |
| | double const d=std::sqrt(1-k2*sz*sz); |
| | |
| | #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; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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<double>::digits; |
| |
|
| | double const wholeLength = e.getEllipseLength(0, 0); |
| |
|
| | double trimmed = e.getLength() + trimAmount; |
| |
|
| | |
| | bool const trimEnd = coord.squaredTo(e.getStartpoint()) <= coord.squaredTo(e.getEndpoint()); |
| |
|
| | if (trimEnd) |
| | trimmed = trimAmount > 0 ? wholeLength - trimAmount : - trimAmount; |
| |
|
| | |
| | EllipseDistanceFunctor X{e, trimmed}; |
| | using namespace boost::math::tools; |
| | double const sol = |
| | halley_iterate<EllipseDistanceFunctor,double>(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; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | 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} |
| | {} |
| |
|
| | |
| | double getTheta() const |
| | { |
| | double theta = std::atan2(m_point.y, m_point.x); |
| | |
| | |
| | for (short i=0; i<16; ++i) { |
| | |
| | double d1 = ds2D1(theta); |
| | double d2 = ds2D2(theta); |
| | if (std::abs(d2) < RS_TOLERANCE || std::abs(d1) < RS_TOLERANCE) |
| | break; |
| | |
| | theta -= d1/d2; |
| | } |
| | return theta; |
| | } |
| |
|
| | private: |
| |
|
| | |
| | double ds2D1(double t) const |
| | { |
| | using namespace std; |
| | return c2*sin(2.*t) + ax2*sin(t) - by2*cos(t); |
| | } |
| |
|
| | |
| | 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.; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | class EllipseBorderHelper: public RS_Ellipse { |
| | public: |
| | EllipseBorderHelper(const RS_Ellipse& ellipse): |
| | RS_Ellipse(ellipse) |
| | {} |
| |
|
| | |
| | void calculateBorders() override |
| | {} |
| | }; |
| | } |
| |
|
| |
|
| | std::ostream& operator << (std::ostream& os, const RS_EllipseData& ed) { |
| | os << "(" << ed.center << |
| | " " << ed.majorP << |
| | " " << ed.ratio << |
| | " " << ed.angle1 << |
| | "," << ed.angle2 << |
| | ")"; |
| | return os; |
| | } |
| |
|
| | |
| | |
| | |
| | RS_Ellipse::RS_Ellipse(RS_EntityContainer* parent, |
| | const RS_EllipseData& d) |
| | :LC_CachedLengthEntity(parent) |
| | ,data(d) { |
| | |
| | calculateBorders(); |
| | } |
| |
|
| | RS_Entity* RS_Ellipse::clone() const { |
| | auto* e = new RS_Ellipse(*this); |
| | return e; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | 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{}; |
| |
|
| | |
| | const RS_Vector vpx{ getMajorP().x, -getRatio()*getMajorP().y }; |
| | mergeBoundingBox(boundingBox, vpx); |
| |
|
| | |
| | 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) { |
| | |
| | if (RS_Math::getPeriodsCount(data.angle1, data.angle2, data.reversed) != 0) { |
| | data.angularLength = 360; |
| | } |
| | } |
| |
|
| | updateLength(); |
| | } |
| |
|
| | void RS_Ellipse::mergeBoundingBox(LC_Rect& boundingBox, const RS_Vector& direction) |
| | { |
| | const double angle = direction.angle(); |
| | |
| | for(double a: {angle, angle + M_PI}) |
| | if(RS_Math::isAngleBetween(a, getAngle1(), getAngle2(), isReversed())) |
| | boundingBox = boundingBox.merge(getEllipsePoint(a)); |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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()){ |
| | |
| | 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 (dist2<dist1) { |
| | if (dist) { |
| | *dist = sqrt(dist2); |
| | } |
| | return endpoint; |
| | } else { |
| | if (dist) { |
| | *dist = sqrt(dist1); |
| | } |
| | return startpoint; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | RS_VectorSolutions RS_Ellipse::getTangentPoint(const RS_Vector& point) const { |
| | RS_Vector point2(point); |
| | point2.move(-getCenter()); |
| | RS_Vector aV(-getAngle()); |
| | point2.rotate(aV); |
| | RS_VectorSolutions sol; |
| | double a=getMajorRadius(); |
| | if(a<RS_TOLERANCE || getRatio()<RS_TOLERANCE) return sol; |
| | RS_Circle c(nullptr, RS_CircleData(RS_Vector(0.,0.),a)); |
| | point2.y /=getRatio(); |
| | sol=c.getTangentPoint(point2); |
| | sol.scale(RS_Vector(1.,getRatio())); |
| | aV.y *= -1.; |
| | sol.rotate(aV); |
| | sol.move(getCenter()); |
| | return sol; |
| | } |
| |
|
| | RS_Vector RS_Ellipse::getTangentDirection(const RS_Vector &point) const { |
| | RS_Vector vp = point - getCenter(); |
| | RS_Vector aV{-getAngle()}; |
| | vp.rotate(aV); |
| | vp.y /= getRatio(); |
| | double a = getMajorRadius(); |
| | if (a < RS_TOLERANCE || getRatio() < RS_TOLERANCE) |
| | return {}; |
| | RS_Circle c(nullptr, RS_CircleData(RS_Vector(0., 0.), a)); |
| | RS_Vector direction = c.getTangentDirection(vp); |
| | direction.y *= getRatio(); |
| | aV.y *= -1.; |
| | direction.rotate(aV); |
| | return isReversed() ? -direction : direction; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | void RS_Ellipse::updateLength() { |
| | |
| | EllipseBorderHelper e{*this}; |
| |
|
| | |
| | if(e.getRatio()>1.) |
| | e.switchMajorMinor(); |
| |
|
| | |
| | if(e.isReversed()) { |
| | std::swap(e.data.angle1, e.data.angle2); |
| | e.setReversed(false); |
| | } |
| | cachedLength = e.getEllipseLength(e.data.angle1,e.data.angle2); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | double RS_Ellipse::getEllipseLength(double x1, double x2) const{ |
| | double a(getMajorRadius()),k(getRatio()); |
| | k= std::sqrt(1-k*k); |
| | |
| | |
| | x1=RS_Math::correctAngle(x1); |
| | x2=RS_Math::correctAngle(x2); |
| | |
| | if(x2 < x1+RS_TOLERANCE_ANGLE) x2 += 2.*M_PI; |
| | double ret = 0.; |
| | |
| | if( x2 >= M_PI) { |
| | |
| | ret= (static_cast<int>((x2+RS_TOLERANCE_ANGLE)/M_PI) - |
| | (static_cast<int>((x1+RS_TOLERANCE_ANGLE)/M_PI) |
| | ))*2; |
| | |
| | ret*=boost::math::ellint_2<double>(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; |
| | } |
| |
|
| | |
| | |
| | |
| | double RS_Ellipse::getEllipseLength(double x2) const{ |
| | return getEllipseLength(getAngle1(),x2); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | RS_Vector RS_Ellipse::getNearestDist(double distance, |
| | const RS_Vector& coord, |
| | double* dist) const{ |
| | |
| | if( ! isEllipticArc() ) { |
| | |
| | |
| | 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 {}; |
| |
|
| | if(getRatio()<RS_TOLERANCE) { |
| | |
| | RS_Line line{e.minV,e.maxV}; |
| | return line.getNearestDist(distance, coord, dist); |
| | } |
| | double x1=e.getAngle1(); |
| | double x2=e.getAngle2(); |
| | if(x2 < x1+RS_TOLERANCE_ANGLE) x2 += 2 * M_PI; |
| | double const l0=e.getEllipseLength(x1,x2); |
| | |
| | if(distance > l0+RS_TOLERANCE) |
| | return {}; |
| | if(distance > l0-RS_TOLERANCE) |
| | return getNearestEndpoint(coord,dist); |
| |
|
| | return getNearestDistHelper(e, distance, coord, dist); |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | bool RS_Ellipse::switchMajorMinor(void) |
| | |
| | { |
| | 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)); |
| | setRatio(1./data.ratio); |
| | if( isEllipticArc() ) { |
| | |
| | setAngle1(getEllipseAngle(vp_start)); |
| | setAngle2(getEllipseAngle(vp_end)); |
| | } |
| | calculateBorders(); |
| | return true; |
| | } |
| |
|
| | |
| | |
| | |
| | RS_Vector RS_Ellipse::getStartpoint() const { |
| | return isEllipticArc() ? getEllipsePoint(data.angle1) : RS_Vector{false}; |
| | } |
| |
|
| | |
| | |
| | |
| | RS_Vector RS_Ellipse::getEndpoint() const { |
| | return isEllipticArc() ? getEllipsePoint(data.angle2) : RS_Vector{false}; |
| | } |
| |
|
| | |
| | |
| | |
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | 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<RS_Ellipse*>(this); |
| | } |
| | ret=coord; |
| | ret.move(-getCenter()); |
| | ret.rotate(-getAngle()); |
| | double x=ret.x,y=ret.y; |
| | double a=getMajorRadius(); |
| | double b=a*getRatio(); |
| | |
| | RS_Vector perpendicular{-ret.y, ret.x}; |
| | |
| | |
| | double twoa2b2=2*(a*a-b*b); |
| | double twoax=2*a*x; |
| | double twoby=2*b*y; |
| | double a0=twoa2b2*twoa2b2; |
| | std::vector<double> ce(4,0.); |
| | std::vector<double> roots; |
| |
|
| | |
| | if (a0 > RS_TOLERANCE && std::abs(getRatio() - 1.0) > RS_TOLERANCE && ret.squared() > RS_TOLERANCE2 ) { |
| | |
| | ce[0]=-2.*twoax/twoa2b2; |
| | ce[1]= (twoax*twoax+twoby*twoby)/a0-1.; |
| | ce[2]= - ce[0]; |
| | ce[3]= -twoax*twoax/a0; |
| | |
| | roots=RS_Math::quarticSolver(ce); |
| | } else { |
| | |
| | double theta = ClosestEllipticPoint{a, b, ret}.getTheta(); |
| | roots.push_back(std::cos(theta)); |
| | |
| | roots.push_back(-roots.front()); |
| | } |
| | if(roots.empty()) { |
| | |
| | std::cout<<"(a= "<<a<<" b= "<<b<<" x= "<<x<<" y= "<<y<<" )\n"; |
| | std::cout<<"finding minimum for ("<<x<<"-"<<a<<"*cos(t))^2+("<<y<<"-"<<b<<"*sin(t))^2\n"; |
| | std::cout<<"2::find cosine, variable c, solve(c^4 +("<<ce[0]<<")*c^3+("<<ce[1]<<")*c^2+("<<ce[2]<<")*c+("<<ce[3]<<")=0,c)\n"; |
| | std::cout<<ce[0]<<' '<<ce[1]<<' '<<ce[2]<<' '<<ce[3]<<std::endl; |
| | std::cerr<<"RS_Math::RS_Ellipse::getNearestPointOnEntity() finds no root from quartic, this should not happen\n"; |
| | return RS_Vector(coord); |
| | } |
| |
|
| | |
| | double dDistance = RS_MAXDOUBLE*RS_MAXDOUBLE; |
| | |
| | std::vector<std::pair<double, double>> directions; |
| | for(double cosTheta: roots) { |
| | if (std::abs(twoax-twoa2b2*cosTheta) > RS_TOLERANCE) { |
| | double const sinTheta=twoby*cosTheta/(twoax-twoa2b2*cosTheta); |
| | directions.emplace_back(cosTheta, sinTheta); |
| | } else { |
| | directions.emplace_back(0., 1.); |
| | directions.emplace_back(0., -1.); |
| | } |
| | } |
| | for(const auto &[cosTheta, sinTheta]: directions) { |
| | |
| | |
| | double const d2=twoa2b2+(twoax-2.*cosTheta*twoa2b2)*cosTheta+twoby*sinTheta; |
| | if (std::signbit(d2)) |
| | continue; |
| | RS_Vector vp3{a*cosTheta, b*sinTheta}; |
| | double d=(vp3-ret).squared(); |
| | |
| | if( ret.valid && d>dDistance) |
| | continue; |
| | ret=vp3; |
| | dDistance=d; |
| | |
| | } |
| | if( ! ret.valid ) { |
| | |
| | |
| | |
| | |
| | RS_DEBUG->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()); |
| | |
| | if (onEntity) { |
| | if (!RS_Math::isAngleBetween(getEllipseAngle(ret), getAngle1(), getAngle2(), isReversed())) { |
| | |
| | ret=getNearestEndpoint(coord,dist); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | return ret; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | bool RS_Ellipse::isPointOnEntity(const RS_Vector& coord, |
| | double tolerance) const { |
| | double t=std::abs(tolerance); |
| | double a=getMajorRadius(); |
| | double b=a*getRatio(); |
| | RS_Vector vp((coord - getCenter()).rotate(-getAngle())); |
| | if ( a<RS_TOLERANCE ) { |
| | |
| | if(std::abs(vp.x)<RS_TOLERANCE && std::abs(vp.y) < b) return true; |
| | return false; |
| | } |
| | if ( b<RS_TOLERANCE ) { |
| | |
| | if (std::abs(vp.y)<RS_TOLERANCE && std::abs(vp.x) < a) return true; |
| | return false; |
| | } |
| | vp.scale(RS_Vector(1./a,1./b)); |
| |
|
| | if (std::abs(vp.squared()-1.) > 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) { |
| | vCenter = vFocus1; |
| | distCenter = distFocus1; |
| | } |
| | else if( distFocus2 < distCenter) { |
| | vCenter = vFocus2; |
| | distCenter = distFocus2; |
| | } |
| | } |
| |
|
| | if (dist != nullptr) { |
| | *dist = distCenter; |
| | } |
| | return vCenter; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | bool RS_Ellipse::createFrom4P(const RS_VectorSolutions& sol){ |
| | if (sol.getNumber() != 4 ) return (false); |
| | std::vector<std::vector<double> > mt; |
| | std::vector<double> dn; |
| | int mSize(4); |
| | mt.resize(mSize); |
| | for(int i=0;i<mSize;i++) { |
| | mt[i].resize(mSize+1); |
| | mt[i][0]=sol.get(i).x * sol.get(i).x; |
| | mt[i][1]=sol.get(i).x ; |
| | mt[i][2]=sol.get(i).y * sol.get(i).y; |
| | mt[i][3]=sol.get(i).y ; |
| | mt[i][4]=1.; |
| | } |
| | if ( ! RS_Math::linearSolver(mt,dn)) return false; |
| | double d(1.+0.25*(dn[1]*dn[1]/dn[0]+dn[3]*dn[3]/dn[2])); |
| | if(std::abs(dn[0])<RS_TOLERANCE15 |
| | ||std::abs(dn[2])<RS_TOLERANCE15 |
| | ||d/dn[0]<RS_TOLERANCE15 |
| | ||d/dn[2]<RS_TOLERANCE15 |
| | ) { |
| | |
| | return false; |
| | } |
| | data.center.set(-0.5*dn[1]/dn[0],-0.5*dn[3]/dn[2]); |
| | d=sqrt(d/dn[0]); |
| | data.majorP.set(d,0.); |
| | data.ratio=sqrt(dn[0]/dn[2]); |
| | data.angle1=0.; |
| | data.angle2=0.; |
| | |
| | |
| | |
| | |
| | |
| | return true; |
| |
|
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | bool RS_Ellipse::createFromCenter3Points(const RS_VectorSolutions& sol) { |
| | if(sol.getNumber()<3) return false; |
| | std::vector<std::vector<double> > mt; |
| | int mSize(sol.getNumber() -1); |
| | if( (sol.get(mSize) - sol.get(mSize-1)).squared() < RS_TOLERANCE15 ) { |
| | |
| | mSize--; |
| | } |
| |
|
| | mt.resize(mSize); |
| | std::vector<double> dn(mSize); |
| | switch(mSize){ |
| | case 2: |
| | for(int i=0;i<mSize;i++){ |
| | mt[i].resize(mSize+1); |
| | RS_Vector vp(sol.get(i+1)-sol.get(0)); |
| | mt[i][0]=vp.x*vp.x; |
| | mt[i][1]=vp.y*vp.y; |
| | mt[i][2]=1.; |
| | } |
| | if ( ! RS_Math::linearSolver(mt,dn) ) return false; |
| | if( dn[0]<RS_TOLERANCE15 || dn[1]<RS_TOLERANCE15) return false; |
| | setMajorP(RS_Vector(1./sqrt(dn[0]),0.)); |
| | setRatio(sqrt(dn[0]/dn[1])); |
| | setAngle1(0.); |
| | setAngle2(0.); |
| | setCenter(sol.get(0)); |
| | return true; |
| |
|
| | case 3: |
| | for(int i=0;i<mSize;i++){ |
| | mt[i].resize(mSize+1); |
| | RS_Vector vp(sol.get(i+1)-sol.get(0)); |
| | mt[i][0]=vp.x*vp.x; |
| | mt[i][1]=vp.x*vp.y; |
| | mt[i][2]=vp.y*vp.y; |
| | mt[i][3]=1.; |
| | } |
| | if ( ! RS_Math::linearSolver(mt,dn) ) return false; |
| | setCenter(sol.get(0)); |
| | return createFromQuadratic(dn); |
| | default: |
| | return false; |
| | } |
| | return false; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | bool RS_Ellipse::createFromQuadratic(const std::vector<double>& dn){ |
| | RS_DEBUG->print("RS_Ellipse::createFromQuadratic() begin\n"); |
| | if(dn.size()!=3) return false; |
| | |
| |
|
| | |
| | |
| | |
| | double a=dn[0]; |
| | const double c=dn[1]; |
| | double b=dn[2]; |
| |
|
| | |
| | const double d = a - b; |
| | const double s=hypot(d,c); |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | if(s >= 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))); |
| |
|
| | |
| | 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; |
| | |
| | |
| | |
| | |
| | |
| | const RS_Vector eCenter=RS_Vector(2.*b*d - e*c, 2.*a*e - d*c)/determinant; |
| | |
| | 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(); |
| | |
| | if(!createFromQuadratic({mq2(0,0)*factor, 2.*mq2(0,1)*factor, mq2(1,1)*factor})) return false; |
| |
|
| | |
| | move(eCenter); |
| | return true; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | bool RS_Ellipse::createInscribeQuadrilateral(const std::vector<RS_Line*>& lines, std::vector<RS_Vector> &tangent){ |
| | if (lines.size() != 4) |
| | return false; |
| |
|
| | std::vector<std::unique_ptr<RS_Line> > quad(4); |
| | { |
| | RS_EntityContainer c0(nullptr, false); |
| | for(RS_Line*const p: lines){ |
| | 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]}); |
| | } |
| | } |
| |
|
| | |
| | RS_Vector centerProjection; |
| | { |
| | std::vector<RS_Line> 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) { |
| | |
| | RS_DEBUG->print("RS_Ellipse::createInscribeQuadrilateral(): can not locate projection Center"); |
| | return false; |
| | } |
| | centerProjection=sol.get(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; |
| | } |
| | |
| | RS_Line l(centerProjection, centerProjection+direction); |
| | for(int k=1;k<=3;k+=2){ |
| | RS_VectorSolutions sol2=RS_Information::getIntersectionLineLine(&l, quad[(i+k)%4].get()); |
| | if(sol2.size()) tangent.push_back(sol2.get(0)); |
| | } |
| | } |
| |
|
| | if(tangent.size()<3) return false; |
| |
|
| | |
| | RS_Vector ellipseCenter; |
| | { |
| | RS_Line cl0(quad[1]->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){ |
| | |
| | |
| | RS_DEBUG->print("RS_Ellipse::createInscribeQuadrilateral(): can not locate Ellipse Center"); |
| | return false; |
| | } |
| | ellipseCenter=sol.get(0); |
| | } |
| | |
| | if(parallel==1){ |
| | RS_DEBUG->print("RS_Ellipse::createInscribeQuadrilateral(): trapezoid detected\n"); |
| | |
| | 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; |
| | |
| | if( std::abs(centerPoint.distanceTo(l0->getStartpoint()) - centerPoint.distanceTo(l0->getEndpoint()))>RS_TOLERANCE) |
| | return false; |
| | |
| | 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. || d<RS_TOLERANCE) { |
| | RS_DEBUG->print("RS_Ellipse::createInscribeQuadrilateral(): this should not happen\n"); |
| | return false; |
| | } |
| | theta=asin(theta); |
| |
|
| | |
| | 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; |
| |
|
| | } |
| | |
| | |
| | std::vector<double> dn(3); |
| | RS_Vector angleVector(false); |
| |
|
| | for(size_t i=0;i<tangent.size();i++) { |
| | tangent[i] -= ellipseCenter; |
| | } |
| | std::vector<std::vector<double> > mt; |
| | mt.clear(); |
| | const double symTolerance=20.*RS_TOLERANCE; |
| | for(const RS_Vector& vp: tangent){ |
| | |
| | |
| | |
| | |
| | std::vector<double> 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){ |
| | |
| | addRow=false; |
| | break; |
| | } |
| | } |
| | if(addRow) { |
| | mtRow.push_back(1.); |
| | mt.push_back(mtRow); |
| | } |
| | } |
| | |
| | switch(mt.size()){ |
| | case 2:{ |
| | RS_DEBUG->print("RS_Ellipse::createInscribeQuadrilateral(): parallelogram detected\n"); |
| |
|
| | |
| | |
| | RS_Vector majorP(tangent[0]); |
| | double dx(majorP.magnitude()); |
| | if(dx<RS_TOLERANCE2) return false; |
| | angleVector.set(majorP.x/dx,-majorP.y/dx); |
| | for(size_t i=0;i<tangent.size();i++)tangent[i].rotate(angleVector); |
| |
|
| | RS_Vector minorP(tangent[2]); |
| | double dy2(minorP.squared()); |
| | if(std::abs(minorP.y)<RS_TOLERANCE || dy2<RS_TOLERANCE2) return false; |
| | |
| | |
| | |
| | |
| | |
| | |
| | double ia2=1./(dx*dx); |
| | double ib2=1./(minorP.y*minorP.y); |
| | |
| | |
| | |
| | |
| | dn[0]=ia2; |
| | dn[1]=-2.*ia2*minorP.x/minorP.y; |
| | dn[2]=ib2*ia2*minorP.x*minorP.x+ib2; |
| | } |
| | break; |
| | case 4: |
| | mt.pop_back(); |
| | if ( ! RS_Math::linearSolver(mt,dn) ) return false; |
| | break; |
| | default: |
| | RS_DEBUG->print(RS_Debug::D_WARNING,"No inscribed ellipse for non isosceles trapezoid"); |
| | return false; |
| | } |
| |
|
| | if(! createFromQuadratic(dn)) return false; |
| | setCenter(ellipseCenter); |
| |
|
| | if(angleVector.valid) { |
| | angleVector.y *= -1.; |
| | rotate(ellipseCenter,angleVector); |
| | } |
| | return true; |
| |
|
| | } |
| |
|
| | |
| | |
| | |
| | |
| | RS_Vector RS_Ellipse::getMiddlePoint()const{ |
| | return getNearestMiddle(getCenter()); |
| | } |
| | |
| | |
| | |
| | |
| | |
| | RS_Vector RS_Ellipse::getNearestMiddle(const RS_Vector& coord, |
| | double* dist, |
| | int middlePoints |
| | ) const{ |
| | RS_DEBUG->print("RS_Ellpse::getNearestMiddle(): begin\n"); |
| | if ( ! isEllipticArc() ) { |
| | |
| | if (dist) { |
| | *dist = RS_MAXDOUBLE; |
| | } |
| | return RS_Vector(false); |
| | } |
| | double ra(getMajorRadius()); |
| | double rb(getRatio()*ra); |
| | if ( ra < RS_TOLERANCE || rb < RS_TOLERANCE ) { |
| | |
| | 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; |
| | } |
| | RS_Vector vp(getNearestPointOnEntity(coord,true,dist)); |
| | double a=getCenter().angleTo(vp); |
| | int counts(middlePoints + 1); |
| | int i = static_cast<int>(std::fmod(a-amin+2.*M_PI,2.*M_PI)/da*counts+0.5); |
| | |
| | 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_Ellpse::getNearestMiddle(): end\n"); |
| | return vp; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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){ |
| | |
| | return RS_Vector(false); |
| | } |
| | |
| | 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)); |
| | std::vector<RS_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; |
| | } |
| | |
| | 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{ |
| | |
| | |
| | |
| | RS_Vector uv = RS_Vector{line}.rotate(-data.majorP.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); |
| | |
| | |
| | |
| | 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); |
| | |
| | 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); |
| | |
| | calculateBorders(); |
| | } |
| |
|
| | void RS_Ellipse::rotate(double angle) { |
| | RS_Vector aV(angle); |
| | data.center.rotate(aV); |
| | data.majorP.rotate(aV); |
| | calculateBorders(); |
| | } |
| |
|
| | void RS_Ellipse::rotate(const RS_Vector& angleVector) { |
| | data.center.rotate(angleVector); |
| | data.majorP.rotate(angleVector); |
| | |
| | calculateBorders(); |
| | } |
| |
|
| | |
| | |
| | |
| | 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)) { |
| | |
| | |
| | *pa2 += 2.*M_PI; |
| | } |
| | } |
| |
|
| | void RS_Ellipse::moveStartpoint(const RS_Vector& pos) { |
| | data.angle1 = getEllipseAngle(pos); |
| | |
| | |
| | correctAngles(); |
| | calculateBorders(); |
| | } |
| |
|
| | void RS_Ellipse::moveEndpoint(const RS_Vector& pos) { |
| | data.angle2 = getEllipseAngle(pos); |
| | |
| | |
| | correctAngles(); |
| | calculateBorders(); |
| | } |
| |
|
| |
|
| | RS2::Ending RS_Ellipse::getTrimPoint(const RS_Vector& trimCoord, |
| | const RS_Vector& ) { |
| |
|
| | |
| | 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) { |
| | |
| | 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<double> ias; |
| | double ia(0.),ia2(0.); |
| | RS_Vector is,is2; |
| | for(size_t ii=0; ii<trimSol.getNumber(); ++ii) { |
| | ias.push_back(getEllipseAngle(trimSol.get(ii))); |
| | if( !ii || std::abs( remainder( ias[ii] - am, 2*M_PI)) < std::abs( remainder( ia -am, 2*M_PI)) ) { |
| | ia = ias[ii]; |
| | is = trimSol.get(ii); |
| | } |
| | } |
| | std::sort(ias.begin(),ias.end()); |
| | for(size_t ii=0; ii<trimSol.getNumber(); ++ii) { |
| | if ( ! RS_Math::isSameDirection(ia,ias[ii],RS_TOLERANCE)) continue; |
| | if( RS_Math::isAngleBetween(am,ias[(ii+trimSol.getNumber()-1)% trimSol.getNumber()],ia,false)) { |
| | ia2=ias[(ii+trimSol.getNumber()-1)% trimSol.getNumber()]; |
| | } else { |
| | ia2=ias[(ii+1)% trimSol.getNumber()]; |
| | } |
| | break; |
| | } |
| | for(const RS_Vector& vp: trimSol) { |
| | if ( ! RS_Math::isSameDirection(ia2,getEllipseAngle(vp),RS_TOLERANCE)) continue; |
| | is2=vp; |
| | break; |
| | } |
| | if(RS_Math::isSameDirection(getAngle1(),getAngle2(),RS_TOLERANCE_ANGLE) |
| | || RS_Math::isSameDirection(ia2,ia,RS_TOLERANCE) ) { |
| | |
| | if( !RS_Math::isAngleBetween(am,ia,ia2,isReversed())) { |
| | std::swap(ia,ia2); |
| | std::swap(is,is2); |
| | } |
| | setAngle1(ia); |
| | setAngle2(ia2); |
| | double da1=std::abs(remainder(getAngle1()-am,2*M_PI)); |
| | double da2=std::abs(remainder(getAngle2()-am,2*M_PI)); |
| | if(da2<da1) { |
| | std::swap(is,is2); |
| | } |
| |
|
| | } else { |
| | double dia=std::abs(remainder(ia-am,2*M_PI)); |
| | double dia2=std::abs(remainder(ia2-am,2*M_PI)); |
| | double ai_min=std::min(dia,dia2); |
| | double da1=std::abs(remainder(getAngle1()-am,2*M_PI)); |
| | double da2=std::abs(remainder(getAngle2()-am,2*M_PI)); |
| | double da_min=std::min(da1,da2); |
| | if( da_min < ai_min ) { |
| | |
| | bool irev= RS_Math::isAngleBetween(am,ia2,ia, isReversed()) ; |
| | if ( RS_Math::isAngleBetween(ia,getAngle1(),getAngle2(), isReversed()) && |
| | RS_Math::isAngleBetween(ia2,getAngle1(),getAngle2(), isReversed()) ) { |
| | if(irev) { |
| | setAngle2(ia); |
| | setAngle1(ia2); |
| | } else { |
| | setAngle1(ia); |
| | setAngle2(ia2); |
| | } |
| | da1=std::abs(remainder(getAngle1()-am,2*M_PI)); |
| | da2=std::abs(remainder(getAngle2()-am,2*M_PI)); |
| | } |
| | if( ((da1 < da2) && (RS_Math::isAngleBetween(ia2,ia,getAngle1(),isReversed()))) || |
| | ((da1 > da2) && (RS_Math::isAngleBetween(ia2,getAngle2(),ia,isReversed()))) |
| | ) { |
| | std::swap(is,is2); |
| | |
| | } |
| | } else { |
| | |
| | if( dia > dia2) { |
| | std::swap(is,is2); |
| | std::swap(ia,ia2); |
| | } |
| | if(RS_Math::isAngleBetween(ia,getAngle1(),getAngle2(),isReversed())) { |
| | if(std::abs(ia - getAngle1()) > RS_TOLERANCE_ANGLE && RS_Math::isAngleBetween(am,getAngle1(),ia,isReversed())) { |
| | setAngle2(ia); |
| | } else { |
| | setAngle1(ia); |
| | } |
| | } |
| | } |
| | } |
| | return is; |
| | } |
| |
|
| | double RS_Ellipse::getEllipseAngle(const RS_Vector& pos) const { |
| | RS_Vector m = pos-data.center; |
| | m.rotate(-data.majorP.angle()); |
| | m.x *= data.ratio; |
| | return m.angle(); |
| | } |
| |
|
| | const RS_EllipseData& RS_Ellipse::getData() const{ |
| | return data; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | void RS_Ellipse::scale(const RS_Vector& center, const RS_Vector& factor) { |
| | RS_Vector vpStart; |
| | RS_Vector vpEnd; |
| | if(isEllipticArc()){ |
| | |
| | vpStart=getStartpoint().scale(center,factor); |
| | vpEnd=getEndpoint().scale(center,factor); |
| | } |
| | data.center.scale(center, factor); |
| | RS_Vector vp1(getMajorP()); |
| | double a(vp1.magnitude()); |
| | if(a<RS_TOLERANCE) return; |
| | vp1 *= 1./a; |
| | double ct=vp1.x; |
| | double ct2 = ct*ct; |
| | double st=vp1.y; |
| | double st2=1.0 - ct2; |
| | double kx2= factor.x * factor.x; |
| | double ky2= factor.y * factor.y; |
| | |
| | double b=getRatio()*a; |
| | double cA=0.5*a*a*(kx2*ct2+ky2*st2); |
| | double cB=0.5*b*b*(kx2*st2+ky2*ct2); |
| | double cC=a*b*ct*st*(ky2-kx2); |
| | if (factor.x < 0) |
| | setReversed(!isReversed()); |
| | if (factor.y < 0) |
| | setReversed(!isReversed()); |
| | RS_Vector vp(cA-cB,cC); |
| | vp1.set(a,b); |
| | vp1.scale(RS_Vector(0.5*vp.angle())); |
| | vp1.rotate(RS_Vector(ct,st)); |
| | vp1.scale(factor); |
| | setMajorP(vp1); |
| | a=cA+cB; |
| | b=vp.magnitude(); |
| | setRatio( sqrt((a - b)/(a + b) )); |
| | if( isEllipticArc() ) { |
| | |
| | setAngle1(getEllipseAngle(vpStart)); |
| | setAngle2(getEllipseAngle(vpEnd)); |
| | correctAngles(); |
| | } |
| |
|
| | |
| | scaleBorders(center,factor); |
| | |
| |
|
| | } |
| |
|
| | |
| | |
| | |
| | RS_Entity& RS_Ellipse::shear(double k) |
| | { |
| | RS_Ellipse e1 = *this; |
| | e1.createFromQuadratic(e1.getQuadratic().shear(k)); |
| | if (isArc()) { |
| | e1.moveStartpoint(getStartpoint().shear(k)); |
| | e1.moveEndpoint(getEndpoint().shear(k)); |
| | } |
| | *this = e1; |
| | return *this; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | bool RS_Ellipse::isEllipticArc() const{ |
| | |
| | |
| | |
| | |
| | return data.isArc; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | void RS_Ellipse::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) { |
| | RS_Vector center=getCenter(); |
| | RS_Vector majorp = center + getMajorP(); |
| | RS_Vector startpoint,endpoint; |
| | bool isArc = isEllipticArc(); |
| | if (isArc) { |
| | startpoint = getStartpoint(); |
| | endpoint = getEndpoint(); |
| | } |
| |
|
| | center.mirror(axisPoint1, axisPoint2); |
| | majorp.mirror(axisPoint1, axisPoint2); |
| |
|
| | setCenter(center); |
| | setReversed(!isReversed()); |
| | setMajorP(majorp - center); |
| | if( isArc ) { |
| | |
| | startpoint.mirror(axisPoint1, axisPoint2); |
| | endpoint.mirror(axisPoint1, axisPoint2); |
| | setAngle1( getEllipseAngle(startpoint)); |
| | setAngle2( getEllipseAngle(endpoint)); |
| | correctAngles(); |
| | } |
| | calculateBorders(); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | double RS_Ellipse::getDirection1() const { |
| | RS_Vector vp; |
| | if (isReversed()){ |
| | vp.set(sin(getAngle1()), -getRatio()*cos(getAngle1())); |
| | } else { |
| | vp.set(-sin(getAngle1()), getRatio()*cos(getAngle1())); |
| | } |
| | return vp.angle()+getAngle(); |
| | } |
| |
|
| | |
| | double RS_Ellipse::getDirection2() const { |
| | RS_Vector vp; |
| | if (isReversed()){ |
| | vp.set(-sin(getAngle2()), getRatio()*cos(getAngle2())); |
| | } else { |
| | vp.set(sin(getAngle2()), -getRatio()*cos(getAngle2())); |
| | } |
| | return vp.angle()+getAngle(); |
| | } |
| |
|
| | void RS_Ellipse::moveRef(const RS_Vector& ref, const RS_Vector& offset) { |
| | if(isEllipticArc()){ |
| | RS_Vector startpoint = getStartpoint(); |
| | RS_Vector endpoint = getEndpoint(); |
| |
|
| | |
| | |
| | if ((ref-startpoint).squared()<RS_TOLERANCE_ANGLE) { |
| | moveStartpoint(startpoint+offset); |
| | correctAngles(); |
| | return; |
| | } |
| | if ((ref-endpoint).squared()<RS_TOLERANCE_ANGLE) { |
| | moveEndpoint(endpoint+offset); |
| | |
| | return; |
| | } |
| | } |
| | if ((ref-getCenter()).squared()<RS_TOLERANCE_ANGLE) { |
| | |
| | setCenter(getCenter()+offset); |
| | calculateBorders(); |
| | return; |
| | } |
| |
|
| | if(data.ratio>1.) { |
| | switchMajorMinor(); |
| | } |
| | auto foci=getFoci(); |
| | for(size_t i=0; i< 2 ; i++){ |
| | if ((ref-foci.at(i)).squared()<RS_TOLERANCE_ANGLE) { |
| | auto focusNew=foci.at(i) + offset; |
| | |
| | auto center = getCenter() + offset*0.5; |
| | RS_Vector majorP; |
| | if(getMajorP().dotP( foci.at(i) - getCenter()) >= 0.){ |
| | majorP = focusNew - center; |
| | }else{ |
| | majorP = center - focusNew; |
| | } |
| | double d=getMajorP().magnitude(); |
| | double c=0.5*focusNew.distanceTo(foci.at(1-i)); |
| | double k=majorP.magnitude(); |
| | if(k<RS_TOLERANCE2 || d < RS_TOLERANCE || |
| | c >= d - RS_TOLERANCE) return; |
| | |
| | |
| | majorP *= d/k; |
| | setCenter(center); |
| | setMajorP(majorP); |
| | setRatio(sqrt(d*d-c*c)/d); |
| | correctAngles(); |
| | if(data.ratio>1.) { |
| | switchMajorMinor(); |
| | } |
| | else{ |
| | calculateBorders(); |
| | } |
| | return; |
| | } |
| | } |
| |
|
| | |
| | if ((ref-getMajorPoint()).squared()<RS_TOLERANCE_ANGLE) { |
| | RS_Vector majorP=getMajorP()+offset; |
| | double r=majorP.magnitude(); |
| | if(r<RS_TOLERANCE) return; |
| | double ratio = getRatio()*getMajorRadius()/r; |
| | setMajorP(majorP); |
| | setRatio(ratio); |
| | if(data.ratio>1.) { |
| | switchMajorMinor(); |
| | } |
| | else{ |
| | calculateBorders(); |
| | } |
| | return; |
| | } |
| | if ((ref-getMinorPoint()).squared()<RS_TOLERANCE_ANGLE) { |
| | RS_Vector minorP=getMinorPoint() + offset; |
| | double r2=getMajorP().squared(); |
| | if(r2<RS_TOLERANCE2) return; |
| | RS_Vector projected= getCenter() + |
| | getMajorP()*getMajorP().dotP(minorP-getCenter())/r2; |
| | double r=(minorP - projected).magnitude(); |
| | if(r<RS_TOLERANCE) return; |
| | double ratio = getRatio()*r/getMinorRadius(); |
| | setRatio(ratio); |
| | if(data.ratio>1.) { |
| | switchMajorMinor(); |
| | } |
| | else{ |
| | calculateBorders(); |
| | } |
| | return; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | LC_Quadratic RS_Ellipse::getQuadratic() const |
| | { |
| | std::vector<double> ce(6,0.); |
| | ce[0]=data.majorP.squared(); |
| | ce[2]= data.ratio*data.ratio*ce[0]; |
| | if(ce[0]<RS_TOLERANCE2 || ce[2]<RS_TOLERANCE2){ |
| | return LC_Quadratic(); |
| | } |
| | ce[0]=1./ce[0]; |
| | ce[2]=1./ce[2]; |
| | ce[5]=-1.; |
| | LC_Quadratic ret(ce); |
| | ret.rotate(getAngle()); |
| | ret.move(data.center); |
| | return ret; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | double RS_Ellipse::areaLineIntegral() const{ |
| | const double a=getMajorRadius(); |
| | const double b=getMinorRadius(); |
| | if(!isEllipticArc()) |
| | return M_PI*a*b; |
| | const double ab=a*b; |
| | const double r2=a*a+b*b; |
| | const double& cx=data.center.x; |
| | const double aE=getAngle(); |
| | const double& a0=data.angle1; |
| | const double& a1=data.angle2; |
| | const double fStart=cx*getStartpoint().y+0.25*r2*sin(2.*aE)*cos(a0)*cos(a0)-0.25*ab*(2.*sin(aE)*sin(aE)*sin(2.*a0)-sin(2.*a0)); |
| | const double fEnd=cx*getEndpoint().y+0.25*r2*sin(2.*aE)*cos(a1)*cos(a1)-0.25*ab*(2.*sin(aE)*sin(aE)*sin(2.*a1)-sin(2.*a1)); |
| | if (isReversed()) { |
| | return fEnd-fStart - 0.5 * a * b * getAngleLength(); |
| | } else { |
| | return fEnd-fStart + 0.5 * a * b * getAngleLength(); |
| | } |
| | } |
| |
|
| | bool RS_Ellipse::isReversed() const { |
| | return data.reversed; |
| | } |
| |
|
| | void RS_Ellipse::setReversed(bool r) { |
| | data.reversed = r; |
| | } |
| |
|
| | double RS_Ellipse::getAngle() const { |
| | return data.majorP.angle(); |
| | } |
| |
|
| | double RS_Ellipse::getAngle1() const { |
| | return data.angle1; |
| | } |
| |
|
| | void RS_Ellipse::setAngle1(double a1) { |
| | data.angle1 = a1; |
| | data.isArc = std::isnormal(data.angle1) || std::isnormal(data.angle2); |
| | } |
| |
|
| | double RS_Ellipse::getAngle2() const { |
| | return data.angle2; |
| | } |
| |
|
| | void RS_Ellipse::setAngle2(double a2) { |
| | data.angle2 = a2; |
| | data.isArc = std::isnormal(data.angle1) || std::isnormal(data.angle2); |
| | } |
| |
|
| | RS_Vector RS_Ellipse::getCenter() const { |
| | return data.center; |
| | } |
| |
|
| | void RS_Ellipse::setCenter(const RS_Vector& c) { |
| | data.center = c; |
| | } |
| |
|
| |
|
| | const RS_Vector& RS_Ellipse::getMajorP() const { |
| | return data.majorP; |
| | } |
| |
|
| | void RS_Ellipse::setMajorP(const RS_Vector& p) { |
| | data.majorP = p; |
| | } |
| |
|
| | double RS_Ellipse::getRatio() const { |
| | return data.ratio; |
| | } |
| |
|
| | void RS_Ellipse::setRatio(double r) { |
| | data.ratio = r; |
| | } |
| |
|
| | double RS_Ellipse::getAngleLength() const { |
| | double a = getAngle1(); |
| | double b = getAngle2(); |
| |
|
| | if (isReversed()) |
| | std::swap(a, b); |
| | double ret = RS_Math::correctAngle(b - a); |
| | |
| | if (std::abs(std::remainder(ret, 2. * M_PI)) < RS_TOLERANCE_ANGLE) { |
| | ret = 2 * M_PI; |
| | } |
| |
|
| | return ret; |
| | } |
| |
|
| |
|
| | double RS_Ellipse::getMajorRadius() const { |
| | return data.majorP.magnitude(); |
| | } |
| |
|
| | RS_Vector RS_Ellipse::getMajorPoint() const{ |
| | return data.center + data.majorP; |
| | } |
| |
|
| | RS_Vector RS_Ellipse::getMinorPoint() const{ |
| | return data.center + |
| | RS_Vector(-data.majorP.y, data.majorP.x)*data.ratio; |
| | } |
| |
|
| | double RS_Ellipse::getMinorRadius() const { |
| | return data.majorP.magnitude()*data.ratio; |
| | } |
| |
|
| | void RS_Ellipse::draw(RS_Painter* painter) { |
| | const LC_Rect& vpRect = painter->getWcsBoundingRect(); |
| | if (LC_Rect{getMin(), getMax()}.inArea(vpRect)) { |
| | |
| | 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); |
| |
|
| | |
| | |
| | |
| | std::array<RS_Vector, 4> vertices = vpRect.vertices(); |
| | |
| | std::vector<double> crossPoints(0); |
| |
|
| | double baseAngle=isReversed()?getAngle2():getAngle1(); |
| | for(unsigned short i=0; i<vertices.size(); i++){ |
| | RS_Line line{vertices.at(i), vertices.at((i+1)%vertices.size())}; |
| | RS_VectorSolutions vpIts=RS_Information::getIntersection(this, &line, true); |
| | if (vpIts.empty()) |
| | continue; |
| | for(const RS_Vector& vp: vpIts){ |
| | auto ap1=getTangentDirection(vp).angle(); |
| | auto ap2=line.getTangentDirection(vp).angle(); |
| | |
| | if(std::abs(std::remainder(ap2 - ap1, M_PI) ) > 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); |
| | } |
| |
|
| | |
| | std::sort(crossPoints.begin(),crossPoints.end()); |
| | |
| |
|
| | RS_Ellipse arc(*this); |
| | arc.setSelected(isSelected()); |
| | arc.setPen(getPen()); |
| | arc.setReversed(false); |
| | arc.calculateBorders(); |
| | |
| | |
| | |
| | for(size_t i=1; i<crossPoints.size(); ++i){ |
| | const RS_Vector& middlePoint = arc.getEllipsePoint(baseAngle+ (crossPoints[i-1] + crossPoints[i]) * 0.5); |
| | |
| | if (vpRect.inArea(middlePoint, RS_TOLERANCE)) { |
| | arc.setAngle1(baseAngle+crossPoints[i-1]); |
| | arc.setAngle2(baseAngle+crossPoints[i]); |
| | arc.drawVisible(painter); |
| | } |
| | } |
| | } |
| |
|
| | |
| | void RS_Ellipse::drawVisible(RS_Painter* painter) const |
| | { |
| | if(!isVisibleInWindow(*painter)) |
| | return; |
| | double startAngle = RS_Math::rad2deg(getAngle1()); |
| | double endAngle = RS_Math::rad2deg(getAngle2()); |
| | double angularLength = RS_Math::rad2deg(getAngleLength()); |
| | if (data.reversed) { |
| | std::swap(startAngle, endAngle); |
| | } |
| | painter->drawEllipseArcWCS(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); |
| | } |
| |
|
| | |
| | |
| | |
| | std::ostream& operator << (std::ostream& os, const RS_Ellipse& a) { |
| | os << " Ellipse: " << a.data << "\n"; |
| | return os; |
| | } |
| |
|