/* Dongxu Li's Version, 19 Aug 2011 * scale an ellipse * Find the eigen vactors and eigen values by optimization * original ellipse equation, * x= a cos t * y= b sin t * rotated by angle, * * x = a cos t cos (angle) - b sin t sin(angle) * y = a cos t sin (angle) + b sin t cos(angle) * scaled by ( kx, ky), * x *= kx * y *= ky * find the maximum and minimum of x^2 + y^2, */ void RS_Ellipse::scale(RS_Vector center, RS_Vector factor) { data.center.scale(center, factor); RS_Vector vpStart=getStartpoint().scale(getCenter(),factor); RS_Vector vpEnd=getEndpoint().scale(getCenter(),factor);; double ct=cos(getAngle()); double ct2 = ct*ct; // cos^2 angle double st=sin(getAngle()); double st2=1.0 - ct2; // sin^2 angle double kx2= factor.x * factor.x; double ky2= factor.y * factor.y; double a=getMajorRadius(); double b=getMinorRadius(); 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); RS_Vector vp(cA-cB,cC); setMajorP(RS_Vector(a,b).scale(RS_Vector(vp.angle())).rotate(RS_Vector(ct,st)).scale(factor)); a=cA+cB; b=vp.magnitude(); setRatio( sqrt((a - b)/(a + b) )); if( std::isnormal(getAngle1()) || std::isnormal(getAngle2() ) ) { //only reset start/end points for ellipse arcs, i.e., angle1 angle2 are not both zero setAngle1(getEllipseAngle(vpStart)); setAngle2(getEllipseAngle(vpEnd)); } correctAngles();//avoid extra 2.*M_PI in angles //calculateEndpoints(); 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(); }
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=getStartpoint(); 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( std::isnormal(getAngle1()) || std::isnormal(getAngle2() ) ) { //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; }
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; } }
/** * mirror by the axis defined by axisPoint1 and axisPoint2 */ void RS_Ellipse::mirror(RS_Vector axisPoint1, RS_Vector axisPoint2) { RS_Vector center=getCenter(); RS_Vector mp = center + getMajorP(); RS_Vector startpoint = getStartpoint(); RS_Vector endpoint = getEndpoint(); center.mirror(axisPoint1, axisPoint2); mp.mirror(axisPoint1, axisPoint2); startpoint.mirror(axisPoint1, axisPoint2); endpoint.mirror(axisPoint1, axisPoint2); setCenter(center); setReversed(!isReversed()); setMajorP(mp - center); if( std::isnormal(getAngle1()) || std::isnormal(getAngle2() ) ) { //only reset start/end points for ellipse arcs, i.e., angle1 angle2 are not both zero setAngle1( getEllipseAngle(startpoint)); setAngle2( getEllipseAngle(endpoint)); } /* old version data.majorP = mp - data.center; double a = axisPoint1.angleTo(axisPoint2); RS_Vector vec; vec.setPolar(1.0, data.angle1); vec.mirror(RS_Vector(0.0,0.0), axisPoint2-axisPoint1); data.angle1 = vec.angle() - 2*a; vec.setPolar(1.0, data.angle2); vec.mirror(RS_Vector(0.0,0.0), axisPoint2-axisPoint1); data.angle2 = vec.angle() - 2*a; data.reversed = (!data.reversed); */ //calculateEndpoints(); correctAngles();//avoid extra 2.*M_PI in angles calculateBorders(); }
RS2::Ending RS_Ellipse::getTrimPoint(const RS_Vector& coord, const RS_Vector& trimPoint) { double angEl = getEllipseAngle(trimPoint); double angM = getEllipseAngle(coord); if (RS_Math::getAngleDifference(angM, angEl)>M_PI) { //if (data.reversed) { // return RS2::EndingEnd; //} //else { return RS2::EndingStart; //} } else { //if (data.reversed) { // return RS2::EndingStart; //} //else { return RS2::EndingEnd; //} } }
void RS_Ellipse::moveEndpoint(const RS_Vector& pos) { data.angle2 = getEllipseAngle(pos); //data.angle2 = data.center.angleTo(pos); //calculateEndpoints(); calculateBorders(); }
RS_Vector RS_Ellipse::prepareTrim(const RS_Vector& trimCoord, const RS_VectorSolutions& trimSol) { //special trimming for ellipse arc if( ! trimSol.hasValid() ) return (RS_Vector(false)); if( trimSol.getNumber() == 1 ) return (trimSol.get(0)); double am=getEllipseAngle(trimCoord); QList<double> ias; double ia(0.),ia2(0.); RS_Vector is,is2; for(int ii=0; ii<trimSol.getNumber(); ii++) { //find closest according ellipse angle ias.append(getEllipseAngle(trimSol.get(ii))); if( !ii || fabs( remainder( ias[ii] - am, 2*M_PI)) < fabs( remainder( ia -am, 2*M_PI)) ) { ia = ias[ii]; is = trimSol.get(ii); } } qSort(ias.begin(),ias.end()); for(int ii=0; ii<trimSol.getNumber(); ii++) { //find segment to enclude trimCoord 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(int ii=0; ii<trimSol.getNumber(); ii++) { //find segment to enclude trimCoord if ( ! RS_Math::isSameDirection(ia2,getEllipseAngle(trimSol.get(ii)),RS_TOLERANCE)) continue; is2=trimSol.get(ii); break; } if(RS_Math::isSameDirection(getAngle1(),getAngle2(),RS_TOLERANCE_ANGLE) || RS_Math::isSameDirection(ia2,ia,RS_TOLERANCE) ) { //whole ellipse if( !RS_Math::isAngleBetween(am,ia,ia2,isReversed())) { std::swap(ia,ia2); std::swap(is,is2); } setAngle1(ia); setAngle2(ia2); double da1=fabs(remainder(getAngle1()-am,2*M_PI)); double da2=fabs(remainder(getAngle2()-am,2*M_PI)); if(da2<da1) { std::swap(is,is2); } } else { double dia=fabs(remainder(ia-am,2*M_PI)); double dia2=fabs(remainder(ia2-am,2*M_PI)); double ai_min=std::min(dia,dia2); double da1=fabs(remainder(getAngle1()-am,2*M_PI)); double da2=fabs(remainder(getAngle2()-am,2*M_PI)); double da_min=std::min(da1,da2); if( da_min < ai_min ) { //trimming one end of arc 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=fabs(remainder(getAngle1()-am,2*M_PI)); da2=fabs(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); //std::cout<<"reset: angle1="<<getAngle1()<<" angle2="<<getAngle2()<<" am="<< am<<" is="<<getEllipseAngle(is)<<" ia2="<<ia2<<std::endl; } } else { //choose intersection as new end if( dia > dia2) { std::swap(is,is2); std::swap(ia,ia2); } if(RS_Math::isAngleBetween(ia,getAngle1(),getAngle2(),isReversed())) { if(RS_Math::isAngleBetween(am,getAngle1(),ia,isReversed())) { setAngle2(ia); } else { setAngle1(ia); } } } } return is; }
RS_Vector RS_Ellipse::getNearestPointOnEntity(const RS_Vector& coord, bool onEntity, double* dist, RS_Entity** entity) { RS_DEBUG->print("RS_Ellipse::getNearestPointOnEntity"); RS_Vector ret(false); if( ! coord.valid ) { if ( dist != NULL ) *dist=RS_MAXDOUBLE; return ret; } if (entity!=NULL) { *entity = this; } ret=coord; ret.move(-getCenter()); ret.rotate(-getAngle()); double x=ret.x,y=ret.y; double a=getMajorRadius(); double b=getMinorRadius(); //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"; double twoa2b2=2*(a*a-b*b); double twoax=2*a*x; double twoby=2*b*y; double a0=twoa2b2*twoa2b2; double ce[4]; double roots[4]; unsigned int counts=0; //need to handle a=b if(a0 > 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 +("<<ce[0]<<")*c^3+("<<ce[1]<<")*c^2+("<<ce[2]<<")*c+("<<ce[3]<<")=0,c)\n"; counts=RS_Math::quarticSolver(ce,roots); } else {//a=b, quadratic equation for circle counts=2; a0=twoby/twoax; roots[0]=sqrt(1./(1.+a0*a0)); roots[1]=-roots[0]; } if(!counts) { //this should not happen 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); // better not to return invalid: return RS_Vector(false); } RS_Vector vp2(false); double d(RS_MAXDOUBLE),d2,s,dDistance(RS_MAXDOUBLE); //double ea; for(unsigned int i=0; i<counts; i++) { //I don't understand the reason yet, but I can do without checking whether sine/cosine are valid //if ( fabs(roots[i])>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.distanceTo(ret); // std::cout<<i<<" Checking: cos= "<<roots[i]<<" sin= "<<s<<" angle= "<<atan2(roots[i],s)<<" ds2= "<<d<<" d="<<d2<<std::endl; if( vp2.valid && d>dDistance) continue; vp2=vp3; dDistance=d; // ea=atan2(roots[i],s); } if( ! vp2.valid ) { //this should not happen std::cout<<ce[0]<<' '<<ce[1]<<' '<<ce[2]<<' '<<ce[3]<<std::endl; std::cout<<"(x,y)=( "<<x<<" , "<<y<<" ) a= "<<a<<" b= "<<b<<" sine= "<<s<<" d2= "<<d2<<" dist= "<<d<<std::endl; std::cout<<"RS_Ellipse::getNearestPointOnEntity() finds no minimum, this should not happen\n"; } if (dist!=NULL) { *dist = dDistance; } vp2.rotate(getAngle()); vp2.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, ( "<<getAngle1()<<" "<<getEllipseAngle(ret)<<" "<<getAngle2()<<" ) reversed= "<<isReversed()<<"\n"; ret=getNearestEndpoint(coord,dist); } } if(! ret.valid) { std::cout<<"RS_Ellipse::getNearestOnEntity() returns invalid by mistake. This should not happen!"<<std::endl; } return ret; }