/**************************************************************************** ** ** 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 "lc_quadratic.h" #include "rs_circle.h" #include "rs_debug.h" #include "rs_information.h" #include "rs_line.h" #include "rs_math.h" #include "rs_painter.h" namespace { // tangent condition tolerance // two circles are considered tangent, if the distance is within this factor of the radii constexpr double Tangent_Tolerance_Factor = 1e-6; /** * @brief isCollinearXY whether the 2x3 matrix has degenerate columns * @param mat - a 2x3 linear equation to solve an Appollonius * @return true, if the matrix is degenerate, i.e. the 3 input circle centers have identical * x or y-coordinates */ bool identicalXOrY(const std::vector>& mat) { // matrix must be 2x3 in dimension assert(mat.size() >= 2 && mat.front().size() >= 3); const auto isDegenerateCol = [&mat] (size_t column) { return RS_Math::equal(std::max(std::abs(mat[0][column]), std::abs(mat[1][column])), 0., RS_TOLERANCE); }; // first(x) or second(y) column return isDegenerateCol(0) || isDegenerateCol(1); } } 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 std::abs(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) :LC_CachedLengthEntity(parent), data(d) { calculateBorders(); } RS_Circle::RS_Circle(const RS_CircleData& d) :LC_CachedLengthEntity(nullptr), data(d) { calculateBorders(); } RS_Entity* RS_Circle::clone() const { auto c = new RS_Circle(*this); return c; } void RS_Circle::calculateBorders() { RS_Vector r{data.radius, data.radius}; minV = data.center - r; maxV = data.center + r; updateLength(); } /** @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. */ void RS_Circle::updateLength() { cachedLength = 2.0 * M_PI * data.radius; } bool RS_Circle::isTangent(const RS_CircleData& circleData) const{ const double d=circleData.center.distanceTo(data.center); double r0=std::abs(circleData.radius); double r1=std::abs(data.radius); if (r0 < r1) { std::swap(r0, r1); } const double tol = Tangent_Tolerance_Factor * r0; if (r1 < tol || d < tol) { return false; } const double tangentTol = std::max(200.*RS_TOLERANCE, tol); // Internal or external tangency bool ret = std::abs(d-r0+r1)RS_TOLERANCE && c.valid ) { data.radius = std::abs(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 (std::abs(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 (std::abs(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.isEmpty()) { //move parallel to opposite std::swap(tri[1], tri[2]); sol = RS_Information::getIntersectionLineLine(tri[0], tri[1]); } if (sol.isEmpty()) { return false; } RS_Vector vp0(sol.get(0)); sol = RS_Information::getIntersectionLineLine(tri[2], tri[1]); if (sol.isEmpty()) { 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.isEmpty()) { 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(std::abs(getRadius()-distance)>RS_TOLERANCE) ret.push_back(new RS_Circle(nullptr, {getCenter(),std::abs(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 (RS_AtomicEntity* 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 << j)) { cs[j].setRadius(-std::abs(cs[j].getRadius())); } else { cs[j].setRadius(std::abs(cs[j].getRadius())); } } // RS_DEBUG->print(RS_Debug::D_ERROR, "flags=%d\n",flags); std::vector list = solveAolloniusSingle(cs); if (list.empty()) { list = solveApolloniusHyperbola(cs); } if (list.size() >= 1) { for (RS_Circle& c0 : list) { bool addNew = true; for (RS_Circle& c : ret) { if ((c0.getCenter() - c.getCenter()).squared() < RS_TOLERANCE15 && std::abs( c0.getRadius() - c.getRadius()) < RS_TOLERANCE) { addNew = false; break; } } if (addNew) ret.push_back(c0); } } } while (++flags < 8u); // std::cout<<__FILE__<<" : "<<__func__<<" : line "<<__LINE__< RS_Circle::solveApolloniusHyperbola(const std::vector& circles){ assert(circles.size() == 3); std::vector centers; std::vector radii; for (const RS_Circle& c : circles) { if (!c.getCenter().valid) { return {}; } centers.push_back(c.getCenter()); radii.push_back(c.getRadius()); } size_t i0 = (centers[0] == centers[1] || centers[0] == centers[2]) ? 1 : 0; std::vector ret; LC_Quadratic lc0(&(circles[i0]), &(circles[(i0 + 1) % 3])); LC_Quadratic lc1(&(circles[i0]), &(circles[(i0 + 2) % 3])); RS_VectorSolutions c0 = LC_Quadratic::getIntersection(lc0, lc1); for (size_t i = 0; i < c0.size(); i++) { const double dc = c0[i].distanceTo(centers[i0]); ret.push_back(RS_Circle(nullptr, {c0[i], std::abs(dc - radii[i0])})); if (dc > radii[i0]) { ret.push_back(RS_Circle(nullptr, {c0[i], dc + radii[i0]})); } } return ret; } RS_VectorSolutions RS_Circle::getRefPoints() const{ RS_Vector v1(data.radius, 0.0); RS_Vector v2(0.0, data.radius); return RS_VectorSolutions ({data.center,data.center+v1, data.center+v2,data.center-v1, data.center-v2}); } /** * @brief compute nearest endpoint, intersection with X/Y axis at 0, 90, 180 and 270 degree * * Use getNearestMiddle() method to compute the nearest circle quadrant endpoints * * @param coord coordinates to compute, e.g. mouse cursor position * @param dist double pointer to return distance between mouse pointer and nearest entity point * @return the nearest intersection of the circle with X/Y axis. */ RS_Vector RS_Circle::getNearestEndpoint(const RS_Vector& coord, double* dist /*= nullptr*/) const{ return getNearestMiddle( coord, dist, 0); } RS_Vector RS_Circle::getNearestPointOnEntity(const RS_Vector& coord, bool /*onEntity*/, double* dist, RS_Entity** entity)const { if (entity) { *entity = const_cast(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 radius = getRadius(); double r2(radius * radius); if (r2 < RS_TOLERANCE2){ return ret; //circle too small } RS_Vector vp(point - getCenter()); double c2(vp.squared()); if (c2 < r2 - radius * 2. * RS_TOLERANCE) { //inside point, no tangential point return ret; } if (c2 > r2 + radius * 2. * RS_TOLERANCE) { //external point RS_Vector vp1(-vp.y, vp.x); vp1 *= radius * 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(); } } RS_Vector RS_Circle::dualLineTangentPoint(const RS_Vector& line) const{ RS_Vector dr = line.normalized() * data.radius; RS_Vector vp0 = data.center + dr; RS_Vector vp1 = data.center - dr; auto lineEqu = [&line](const RS_Vector& vp) { return std::abs(line.dotP(vp) + 1.); }; return lineEqu(vp0) < lineEqu(vp1) ? vp0 : vp1; } 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) { /* bool increase = coord.x > 0; double newRadius; if (increase){ newRadius = getRadius() + std::abs(distance); } else{ newRadius = getRadius() - std::abs(distance); if(newRadius < RS_TOLERANCE) { return false; } }*/ double dist(coord.distanceTo(getCenter())); double newRadius; if (dist > getRadius()) { //external newRadius = getRadius() + fabs(distance); } else { newRadius = getRadius() - fabs(distance); if (newRadius < RS_TOLERANCE) { return false; } } setRadius(newRadius); calculateBorders(); setRadius(newRadius); calculateBorders(); return true; } void RS_Circle::rotate(const RS_Vector& center, double angle) { data.center.rotate(center, angle); calculateBorders(); } void RS_Circle::rotate(const RS_Vector& center, const RS_Vector& angleVector) { data.center.rotate(center, angleVector); calculateBorders(); } void RS_Circle::scale(const RS_Vector& center, const RS_Vector& factor) { data.center.scale(center, factor); //radius always is positive data.radius *= std::abs(factor.x); scaleBorders(center,factor); } double RS_Circle::getDirection1() const{ return M_PI_2; } double RS_Circle::getDirection2() const { return M_PI_2 * 3.0; } void RS_Circle::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) { data.center.mirror(axisPoint1, axisPoint2); calculateBorders(); } RS_Entity& RS_Circle::shear(double k){ if (!std::isnormal(k)) { assert(!"shear() should not not be called for circle"); } return *this; } void RS_Circle::draw(RS_Painter* painter) { painter->drawEntityCircle(this); } void RS_Circle::moveRef(const RS_Vector& ref, const RS_Vector& offset) { if (ref.distanceTo(data.center) < 1.0e-4) { data.center += offset; calculateBorders(); 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) { calculateBorders(); return; } else { data.radius = data.center.distanceTo(v1 + offset); calculateBorders(); } } /** 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; }