/**************************************************************************** ** ** This file is part of the LibreCAD project, a 2D CAD program ** ** Copyright (C) 2015 A. Stebich (librecad@mail.lordofbikes.de) ** Copyright (C) 2011-2012 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 #include #include "rs_circle.h" #include "rs_arc.h" #include "rs_line.h" #include "rs_constructionline.h" #include "rs_information.h" #include "rs_graphicview.h" #include "rs_painter.h" #include "rs_linetypepattern.h" #include "rs_math.h" #include "lc_hyperbola.h" #include "lc_quadratic.h" #include "rs_debug.h" RS_CircleData::RS_CircleData(RS_Vector const& center, double radius): center(center) , radius(radius) { } bool RS_CircleData::isValid() const { return (center.valid && radius>RS_TOLERANCE); } bool RS_CircleData::operator == (RS_CircleData const& rhs) const { if(! (center.valid && rhs.center.valid)) return false; if( center.squaredTo(rhs.center) > RS_TOLERANCE2) return false; return fabs(radius - rhs.radius)< RS_TOLERANCE; } std::ostream& operator << (std::ostream& os, const RS_CircleData& ad) { os << "(" << ad.center << "/" << ad.radius << ")"; return os; } /** * constructor. */ RS_Circle::RS_Circle(RS_EntityContainer* parent, const RS_CircleData& d) :RS_AtomicEntity(parent), data(d) { calculateBorders(); } RS_Entity* RS_Circle::clone() const { RS_Circle* c = new RS_Circle(*this); c->initId(); return c; } void RS_Circle::calculateBorders() { RS_Vector r(data.radius,data.radius); minV = data.center - r; maxV = data.center + r; } /** @return The center point (x) of this arc */ RS_Vector RS_Circle::getCenter() const { return data.center; } /** Sets new center. */ void RS_Circle::setCenter(const RS_Vector& c) { data.center = c; } /** @return The radius of this arc */ double RS_Circle::getRadius() const { return data.radius; } /** Sets new radius. */ void RS_Circle::setRadius(double r) { data.radius = r; } /** * @return Angle length in rad. */ double RS_Circle::getAngleLength() const { return 2*M_PI; } /** * @return Length of the circle which is the circumference. */ double RS_Circle::getLength() const { return 2*M_PI*data.radius; } bool RS_Circle::isTangent(const RS_CircleData& circleData) const{ const double d=circleData.center.distanceTo(data.center); // DEBUG_HEADER const double r0=fabs(circleData.radius); const double r1=fabs(data.radius); // std::cout<RS_TOLERANCE && c.valid ) { data.radius = fabs(r); data.center = c; return true; } else { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Circle::createFromCR(): " "Cannot create a circle with radius 0.0."); return false; } } /** * Creates this circle from two opposite points. * * @param p1 1st point. * @param p2 2nd point. */ bool RS_Circle::createFrom2P(const RS_Vector& p1, const RS_Vector& p2) { double r=0.5*p1.distanceTo(p2); if (r>RS_TOLERANCE) { data.radius = r; data.center = (p1+p2)*0.5; return true; } else { // RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Circle::createFrom2P(): " // "Cannot create a circle with radius 0.0."); return false; } } /** * Creates this circle from 3 given points which define the circle line. * * @param p1 1st point. * @param p2 2nd point. * @param p3 3rd point. */ bool RS_Circle::createFrom3P(const RS_Vector& p1, const RS_Vector& p2, const RS_Vector& p3) { RS_Vector vra=p2 - p1; RS_Vector vrb=p3 - p1; double ra2=vra.squared()*0.5; double rb2=vrb.squared()*0.5; double crossp=vra.x * vrb.y - vra.y * vrb.x; if (fabs(crossp)< RS_TOLERANCE2) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Circle::createFrom3P(): " "Cannot create a circle with radius 0.0."); return false; } crossp=1./crossp; data.center.set((ra2*vrb.y - rb2*vra.y)*crossp,(rb2*vra.x - ra2*vrb.x)*crossp); data.radius=data.center.magnitude(); data.center += p1; return true; } //*create Circle from 3 points //Author: Dongxu Li bool RS_Circle::createFrom3P(const RS_VectorSolutions& sol) { if(sol.getNumber() < 2) return false; if(sol.getNumber() == 2) return createFrom2P(sol.get(0),sol.get(1)); if((sol.get(1)-sol.get(2)).squared() < RS_TOLERANCE2) return createFrom2P(sol.get(0),sol.get(1)); RS_Vector vra(sol.get(1) - sol.get(0)); RS_Vector vrb(sol.get(2) - sol.get(0)); double ra2=vra.squared()*0.5; double rb2=vrb.squared()*0.5; double crossp=vra.x * vrb.y - vra.y * vrb.x; if (fabs(crossp)< RS_TOLERANCE2) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Circle::createFrom3P(): " "Cannot create a circle with radius 0.0."); return false; } crossp=1./crossp; data.center.set((ra2*vrb.y - rb2*vra.y)*crossp,(rb2*vra.x - ra2*vrb.x)*crossp); data.radius=data.center.magnitude(); data.center += sol.get(0); return true; } /** *create circle inscribled in a triangle * *Author: Dongxu Li */ bool RS_Circle::createInscribe(const RS_Vector& coord, const std::vector& lines){ if(lines.size()<3) return false; std::vector tri(lines); RS_VectorSolutions sol=RS_Information::getIntersectionLineLine(tri[0],tri[1]); if(sol.getNumber() == 0 ) {//move parallel to opposite std::swap(tri[1],tri[2]); sol=RS_Information::getIntersectionLineLine(tri[0],tri[1]); } if(sol.getNumber() == 0 ) return false; RS_Vector vp0(sol.get(0)); sol=RS_Information::getIntersectionLineLine(tri[2],tri[1]); if(sol.getNumber() == 0 ) return false; RS_Vector vp1(sol.get(0)); RS_Vector dvp(vp1-vp0); double a(dvp.squared()); if( a< RS_TOLERANCE2) return false; //three lines share a common intersecting point RS_Vector vp(coord - vp0); vp -= dvp*(RS_Vector::dotP(dvp,vp)/a); //normal component RS_Vector vl0(tri[0]->getEndpoint() - tri[0]->getStartpoint()); a=dvp.angle(); double angle0(0.5*(vl0.angle() + a)); if( RS_Vector::dotP(vp,vl0) <0.) { angle0 += 0.5*M_PI; } RS_Line line0(vp0, vp0+RS_Vector(angle0));//first bisecting line vl0=(tri[2]->getEndpoint() - tri[2]->getStartpoint()); angle0=0.5*(vl0.angle() + a+M_PI); if( RS_Vector::dotP(vp,vl0) <0.) { angle0 += 0.5*M_PI; } RS_Line line1(vp1, vp1+RS_Vector(angle0));//second bisection line sol=RS_Information::getIntersectionLineLine(&line0,&line1); if(sol.getNumber() == 0 ) return false; bool ret=createFromCR(sol.get(0),tri[1]->getDistanceToPoint(sol.get(0))); if(!ret) return false; for(auto p: lines){ if(! p->isTangent(data)) return false; } return true; } std::vector RS_Circle::offsetTwoSides(const double& distance) const { std::vector ret(0,nullptr); ret.push_back(new RS_Circle(nullptr, {getCenter(),getRadius()+distance})); if(fabs(getRadius()-distance)>RS_TOLERANCE) ret.push_back(new RS_Circle(nullptr, {getCenter(),fabs(getRadius()-distance)})); return ret; } RS_VectorSolutions RS_Circle::createTan1_2P(const RS_AtomicEntity* circle, const std::vector& points) { RS_VectorSolutions ret; if (!circle||points.size()<2) return ret; return LC_Quadratic::getIntersection( LC_Quadratic(circle,points[0]), LC_Quadratic(circle,points[1]) ); } /** * create a circle of radius r and tangential to two given entities */ RS_VectorSolutions RS_Circle::createTan2(const std::vector& circles, const double& r) { if(circles.size()<2) return false; auto e0=circles[0]->offsetTwoSides(r); auto e1=circles[1]->offsetTwoSides(r); RS_VectorSolutions centers; if(e0.size() && e1.size()) { for(auto it0=e0.begin();it0!=e0.end();it0++){ for(auto it1=e1.begin();it1!=e1.end();it1++){ centers.push_back(RS_Information::getIntersection(*it0,*it1)); } } } for(auto it0=e0.begin();it0!=e0.end();it0++){ delete *it0; } for(auto it0=e1.begin();it0!=e1.end();it0++){ delete *it0; } return centers; } std::vector RS_Circle::createTan3(const std::vector& circles) { std::vector ret; if(circles.size()!=3) return ret; std::vector cs; for(auto c: circles){ cs.emplace_back(RS_Circle(nullptr, {c->getCenter(),c->getRadius()})); } unsigned short flags=0; do{ for(unsigned short j=0u;j<3u;++j){ if(flags & (1u<print(RS_Debug::D_ERROR, "flags=%d\n",flags); auto list=solveAppolloniusSingle(cs); if(list.size()>=1){ for(RS_Circle& c0: list){ bool addNew=true; for(RS_Circle& c: ret){ if((c0.getCenter()-c.getCenter()).squared()getCenter().x<<" , "<<(*it)->getCenter().y<<" ), r= "<<(*it)->getRadius()< RS_Circle::solveAppolloniusSingle(const std::vector& circles) { // std::cout<<__FILE__<<" : "<<__func__<<" : line "<<__LINE__<(this); } RS_Vector vp(coord - data.center); double d(vp.magnitude()); if( d < RS_TOLERANCE ) return RS_Vector(false); vp =data.center+vp*(data.radius/d); // RS_DEBUG->print(RS_Debug::D_ERROR, "circle(%g, %g), r=%g: distance to point (%g, %g)\n",data.center.x,data.center.y,coord.x,coord.y); if(dist){ *dist=coord.distanceTo(vp); // RS_DEBUG->print(RS_Debug::D_ERROR, "circle(%g, %g), r=%g: distance to point (%g, %g)=%g\n",data.center.x,data.center.y,coord.x,coord.y,*dist); } return vp; } /** *find the tangential points from a given point, i.e., the tangent lines should pass * the given point and tangential points * *Author: Dongxu Li */ RS_VectorSolutions RS_Circle::getTangentPoint(const RS_Vector& point) const { RS_VectorSolutions ret; double r2(getRadius()*getRadius()); if(r2r2+getRadius()*2.*RS_TOLERANCE) { //external point RS_Vector vp1(-vp.y,vp.x); vp1*=getRadius()*sqrt(c2-r2)/c2; vp *= r2/c2; vp += getCenter(); if(vp1.squared()>RS_TOLERANCE2) { ret.push_back(vp+vp1); ret.push_back(vp-vp1); return ret; } } ret.push_back(point); return ret; } RS_Vector RS_Circle::getTangentDirection(const RS_Vector& point) const { RS_Vector vp(point-getCenter()); // double c2(vp.squared()); // if(c2((dAngleToPoint + 0.5 * dAngleSteps) / dAngleSteps); if( 0 < middlePoints) { // for nearest middle eliminate start/endpoints int iQuadrant = static_cast(dAngleToPoint / 0.5 / M_PI); int iQuadrantStep = iStepCount - iQuadrant * iCounts; if( 0 == iQuadrantStep) { ++iStepCount; } else if( iCounts == iQuadrantStep) { --iStepCount; } } vPoint.setPolar( data.radius, dAngleSteps * iStepCount); vPoint.move( data.center); if(dist) { *dist = vPoint.distanceTo( coord); } return vPoint; } RS_Vector RS_Circle::getNearestDist(double /*distance*/, const RS_Vector& /*coord*/, double* dist) const{ if (dist) { *dist = RS_MAXDOUBLE; } return RS_Vector(false); } RS_Vector RS_Circle::getNearestDist(double /*distance*/, bool /*startp*/) const{ return RS_Vector(false); } RS_Vector RS_Circle::getNearestOrthTan(const RS_Vector& coord, const RS_Line& normal, bool /*onEntity = false*/) const { if ( !coord.valid) { return RS_Vector(false); } RS_Vector vp0(coord-getCenter()); RS_Vector vp1(normal.getAngle1()); double d=RS_Vector::dotP(vp0,vp1); if(d >= 0. ) { return getCenter() + vp1*getRadius(); }else{ return getCenter() - vp1*getRadius(); } } void RS_Circle::move(const RS_Vector& offset) { data.center.move(offset); moveBorders(offset); // calculateBorders(); } /** * this function creates offset *@coord, position indicates the direction of offset *@distance, distance of offset * return true, if success, otherwise, false * *Author: Dongxu Li */ bool RS_Circle::offset(const RS_Vector& coord, const double& distance) { double r0(coord.distanceTo(getCenter())); if(r0 > getRadius()){ //external r0 = getRadius()+ fabs(distance); }else{ r0 = getRadius()- fabs(distance); if(r0toGraph(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)); std::vector vps; for(unsigned short i=0;i<4;i++){ const QPointF& vp(visualBox.at(i)); vps.emplace_back(vp.x(),vp.y()); } for(unsigned short i=0;i<4;i++){ RS_Line line{nullptr, {vps.at(i),vps.at((i+1)%4)}}; RS_Circle c0{nullptr, getData()}; if( RS_Information::getIntersection(&c0, &line, true).size()>0) return true; } if( getCenter().isInWindowOrdered(vpMin,vpMax)==false) return false; return (vpMin-getCenter()).squared() > getRadius()*getRadius(); } void RS_Circle::draw(RS_Painter* painter, RS_GraphicView* view, double& /*patternOffset*/) { // // draw circle as a 2 pi arc // RS_Arc arc(getParent(), RS_ArcData(getCenter(),getRadius(),0.,2.*M_PI, false)); // arc.setSelected(isSelected()); // arc.setPen(getPen()); // arc.draw(painter,view,patternOffset); painter->drawCircle(view->toGui(getCenter()), view->toGuiDX(getRadius())); } void RS_Circle::moveRef(const RS_Vector& ref, const RS_Vector& offset) { if(ref.distanceTo(data.center)<1.0e-4){ data.center += offset; return; } RS_Vector v1(data.radius, 0.0); RS_VectorSolutions sol; sol.push_back(data.center + v1); sol.push_back(data.center - v1); v1.set(0., data.radius); sol.push_back(data.center + v1); sol.push_back(data.center - v1); double dist; v1=sol.getClosest(ref,&dist); if(dist>1.0e-4) return; data.radius = data.center.distanceTo(v1 + offset); } /** 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_Circle::getQuadratic() const { std::vector ce(6,0.); ce[0]=1.; ce[2]=1.; ce[5]=-data.radius*data.radius; LC_Quadratic ret(ce); ret.move(data.center); return ret; } /** * @brief Returns area of full circle * Note: Circular arcs are handled separately by RS_Arc (areaLIneIntegral) * However, full ellipses and ellipse arcs are handled by RS_Ellipse * @return \pi r^2 */ double RS_Circle::areaLineIntegral() const { const double r = getRadius(); return M_PI*r*r; } /** * Dumps the circle's data to stdout. */ std::ostream& operator << (std::ostream& os, const RS_Circle& a) { os << " Circle: " << a.data << "\n"; return os; }