/**************************************************************************** ** ** This file is part of the LibreCAD project, a 2D CAD program ** ** 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! ** **********************************************************************/ #ifdef HAS_BOOST #include #include #include #include #include #endif #include #include "rs_ellipse.h" #include "rs_graphic.h" #include "rs_graphicview.h" #include "rs_painter.h" #include "rs_information.h" #include "rs_linetypepattern.h" /** * Constructor. */ RS_Ellipse::RS_Ellipse(RS_EntityContainer* parent, const RS_EllipseData& d) :RS_AtomicEntity(parent), data(d) { //calculateEndpoints(); calculateBorders(); } /** * Recalculates the endpoints using the angles and the radius. */ /* void RS_Ellipse::calculateEndpoints() { double angle = data.majorP.angle(); double radius1 = getMajorRadius(); double radius2 = getMinorRadius(); startpoint.set(data.center.x + cos(data.angle1) * radius1, data.center.y + sin(data.angle1) * radius2); startpoint.rotate(data.center, angle); endpoint.set(data.center.x + cos(data.angle2) * radius1, data.center.y + sin(data.angle2) * radius2); endpoint.rotate(data.center, angle); } */ /** * Calculates the boundary box of this ellipse. */ void RS_Ellipse::calculateBorders() { // RS_DEBUG->print("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); // RS_DEBUG->print("RS_Ellipse::calculateBorders: OK"); } /** * return the foci of ellipse * *@Author: Dongxu Li */ RS_VectorSolutions RS_Ellipse::getFoci() const { RS_Vector vp(getMajorP()*sqrt(1.-getRatio()*getRatio())); return RS_VectorSolutions(getCenter()+vp, getCenter()-vp); } RS_VectorSolutions RS_Ellipse::getRefPoints() { RS_VectorSolutions ret; if(isArc()){ //no start/end point for whole ellipse ret.push_back(getStartpoint()); ret.push_back(getEndpoint()); } ret.push_back(data.center); ret.appendTo(getFoci()); 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 //return the arc length between ellipse angle x1, x2 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 += ellipticIntegral_2(k,x2)-ellipticIntegral_2(k,x1); } return a*ret; } /** * arc length from start point (angle1) */ double RS_Ellipse::getEllipseLength( double x2) const { return getEllipseLength(getAngle1(),x2); } /** * wrapper of elliptic integral of the second type, Legendre form *@k the elliptic modulus or eccentricity *@phi elliptic angle, must be within range of [0, M_PI] * *Author: Dongxu Li */ double RS_Ellipse::ellipticIntegral_2(const double& k, const double& phi) { double a= remainder(phi-M_PI/2.,M_PI); if(a>0.) { return boost::math::ellint_2(k,a); } else { return - boost::math::ellint_2(k,fabs(a)); } } /** * 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) { // RS_DEBUG->print("RS_Ellipse::getNearestDist() begin\n"); if( ! isArc() ) { // both angles being 0, whole ellipse // no end points for whole ellipse, therefore, no snap by distance from end points. return RS_Vector(false); } RS_Ellipse e(NULL,data); if(e.getRatio()>1.) e.switchMajorMinor(); double ra=e.getMajorRadius(); double rb=e.getRatio()*ra; if(e.isReversed()) { std::swap(e.data.angle1,e.data.angle2); e.setReversed(false); } if(ra l+RS_TOLERANCE) return(RS_Vector(false)); if(distance > l-RS_TOLERANCE) return(getNearestEndpoint(coord,dist)); double guess= distance*(ra+rb)/(2.*ra*rb); guess=(RS_Vector(x1+guess).scale(RS_Vector(e.getRatio(),1.))).angle();//convert to ellipse angle if( guess < x1) guess += 2.*M_PI; if( ! RS_Math::isAngleBetween(guess,x1,x2,false)) { guess=x1 +0.5*RS_Math::getAngleDifference(x1,x2); } int digits=std::numeric_limits::digits; // solve equation of the distance by second order newton_raphson EllipseDistanceFunctor X(&e,distance); //std::cout<<"RS_Ellipse::getNearestDist() dist="<(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= "< RS_TOLERANCE*RS_TOLERANCE ) { // 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; s=twoby*roots[i]/(twoax-twoa2b2*roots[i]); //sine //if (fabs(s) > 1. ) continue; 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!=NULL) { *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()); // if ( getCenter().distanceTo(coord) < tolerance ) { // if (getMajorRadius() < tolerance || getMinorRadius() < tolerance ) { // return true; // } else { // return false; // } // } // double dist = getDistanceToPoint(coord, NULL, RS2::ResolveNone); // return (dist<=tolerance); } RS_Vector RS_Ellipse::getNearestCenter(const RS_Vector& coord, double* dist) { if (dist!=NULL) { *dist = coord.distanceTo(data.center); } return data.center; } /** //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 QVector > mt; QVector 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_TOLERANCE*RS_TOLERANCE ) { //remove the last point mSize--; } mt.resize(mSize); QVector dn(mSize); switch(mSize){ case 2: for(int i=0;i& dn){ if(fabs(dn[0]) ip; for(int i=1;i<4;i++){//find intersections //(0,i) // std::cout<<"(0,"<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; } RS_Vector centerProjection(sol.get(0)); // std::cout<<"RS_Ellipse::createInscribe(): centerProjection="< edge; //form the closed quadrilateral with ordered edges edge.push_back(RS_Line(ip[0].getStartpoint(),ip[1].getStartpoint())); edge.push_back(RS_Line(ip[1].getStartpoint(),ip[0].getEndpoint())); edge.push_back(RS_Line(ip[0].getEndpoint(),ip[1].getEndpoint())); edge.push_back(RS_Line(ip[1].getEndpoint(),ip[0].getStartpoint())); QVector tangent;//holds the tangential points on edges, in the order of edges: 1 3 2 0 for(int i=0;i<=1;i++) { RS_VectorSolutions sol1=RS_Information::getIntersection(& edge[i],& edge[(i+2)%edge.size()],false); RS_Vector direction; if(sol1.getNumber()==0) { direction=edge[i].getEndpoint()-edge[i].getStartpoint(); }else{ direction=sol1.get(0)-centerProjection; } // std::cout<<"Direction: "<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; } RS_Vector center(sol.get(0)); // std::cout<<"line0: "<<*cl0< mtRow; mtRow.push_back(vp.x*vp.x); mtRow.push_back(vp.x*vp.y); mtRow.push_back(vp.y*vp.y); bool addRow(true); for(int j=0;jprint("RS_Ellpse::getNearestMiddle(): begin\n"); if ( ! isArc() ) { //no middle point for whole ellipse, angle1=angle2=0 if (dist!=NULL) { *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!=NULL) { *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!=NULL) { *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 ) { if ( !coord.valid ) { return RS_Vector(false); } RS_Vector direction=normal.getEndpoint() - normal.getStartpoint(); if (direction.squared()< RS_TOLERANCE*RS_TOLERANCE) { //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 QList sol; for(int i=0;i<2;i++){ if(!onEntity || RS_Math::isAngleBetween(angle,getAngle1(),getAngle2(),isReversed())) { if(i){ sol.append(- direction); }else{ sol.append(direction); } } angle=RS_Math::correctAngle(angle+M_PI); } if(sol.size()<1) return RS_Vector(false); aV.y*=-1.; for(int i=0;i0.) { vp=sol[1]; break; } default: vp=sol[0]; } return getCenter() + vp; } double RS_Ellipse::getDistanceToPoint(const RS_Vector& coord, RS_Entity** entity, RS2::ResolveLevel, double /*solidDist*/) const{ if( entity != NULL) { *entity=const_cast(this); } double dToEntity = RS_MAXDOUBLE; getNearestPointOnEntity(coord, true, &dToEntity, entity); // RVT 6 Jan 2011 : Add selection by center point double dToCenter=data.center.distanceTo(coord); return std::min(dToEntity,dToCenter); } 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) > RS_Math::getAngleDifference(data.angle2,angM)) { 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); QList ias; double ia(0.),ia2(0.); RS_Vector is,is2; for(int ii=0; ii da2) && (RS_Math::isAngleBetween(ia2,getAngle2(),ia,isReversed()))) ) { std::swap(is,is2); //std::cout<<"reset: angle1="<getFactor().x); double rb(getRatio()*ra); if(rbdrawLine(view->toGui(minV),view->toGui(maxV)); return; } double mAngle=getAngle(); RS_Vector cp(view->toGui(getCenter())); if ( !isSelected() && ( getPen().getLineType()==RS2::SolidLine || view->getDrawingMode()==RS2::ModePreview)) { painter->drawEllipse(cp, ra, rb, mAngle, getAngle1(), getAngle2(), isReversed()); return; } // Pattern: RS_LineTypePattern* pat; if (isSelected()) { pat = &patternSelected; } else { pat = view->getPattern(getPen().getLineType()); } if (pat==NULL) { 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); int i(0),j(0); double* ds = new double[pat->num>0?pat->num:0]; if(pat->num>0){ while( inum){ ds[i]=pat->pattern[i] ;//pattern length i++; } j=i; }else { delete[] ds; RS_DEBUG->print(RS_Debug::D_WARNING,"Invalid pattern when drawing ellipse"); painter->drawEllipse(cp, ra, rb, mAngle, a1, a2, false); return; } double tot=0.0; double curA(a1); double da,a3; bool notDone(true); for(i=0;notDone;i=(i+1)%j) {//draw patterned ellipse a3 = curA + fabs(ds[i])/RS_Vector(ra*sin(curA),rb*cos(curA)).magnitude(); if(a3>a2){ a3=a2; notDone=false; } tot += da; if (ds[i]>0.){ painter->drawEllipse(cp, ra, rb, mAngle, curA, a3, false); } curA=a3; } delete[] ds; } /** * Dumps the point's data to stdout. */ std::ostream& operator << (std::ostream& os, const RS_Ellipse& a) { os << " Ellipse: " << a.data << "\n"; return os; }