/**************************************************************************** ** ** This file is part of the LibreCAD project, a 2D CAD program ** ** Copyright (C) 2015 A. Stebich (librecad@mail.lordofbikes.de) ** 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 "rs_information.h" #include "rs_entitycontainer.h" #include "rs_vector.h" #include "rs_arc.h" #include "rs_circle.h" #include "rs_constructionline.h" #include "rs_ellipse.h" #include "rs_line.h" #include "rs_polyline.h" #include "lc_quadratic.h" #include "lc_splinepoints.h" #include "rs_math.h" #include "lc_rect.h" #include "rs_debug.h" /** * Default constructor. * * @param container The container to which we will add * entities. Usually that's an RS_Graphic entity but * it can also be a polyline, text, ... */ RS_Information::RS_Information(RS_EntityContainer& container): container(&container) { } /** * @return true: if the entity is a dimensioning entity. * false: otherwise */ bool RS_Information::isDimension(RS2::EntityType type) { switch(type){ case RS2::EntityDimAligned: case RS2::EntityDimLinear: case RS2::EntityDimRadial: case RS2::EntityDimDiametric: case RS2::EntityDimAngular: return true; default: return false; } } /** * @retval true the entity can be trimmed. * i.e. it is in a graphic or in a polyline. */ bool RS_Information::isTrimmable(RS_Entity* e) { if (e) { if (e->getParent()) { switch(e->getParent()->rtti()){ case RS2::EntityPolyline: case RS2::EntityContainer: case RS2::EntityGraphic: case RS2::EntityBlock: return true; default: return false; } } } return false; } /** * @retval true the two entities can be trimmed to each other; * i.e. they are in a graphic or in the same polyline. */ bool RS_Information::isTrimmable(RS_Entity* e1, RS_Entity* e2) { if (e1 && e2) { if (e1->getParent() && e2->getParent()) { if (e1->getParent()->rtti()==RS2::EntityPolyline && e2->getParent()->rtti()==RS2::EntityPolyline && e1->getParent()==e2->getParent()) { // in the same polyline RS_Polyline* pl = static_cast(e1->getParent()); int idx1 = pl->findEntity(e1); int idx2 = pl->findEntity(e2); RS_DEBUG->print("RS_Information::isTrimmable: " "idx1: %d, idx2: %d", idx1, idx2); if (abs(idx1-idx2)==1 || (pl->isClosed() && abs(idx1-idx2)==int(pl->count()-1))) { // directly following entities return true; } else { // not directly following entities return false; } } else if ((e1->getParent()->rtti()==RS2::EntityContainer || e1->getParent()->rtti()==RS2::EntityGraphic || e1->getParent()->rtti()==RS2::EntityBlock) && (e2->getParent()->rtti()==RS2::EntityContainer || e2->getParent()->rtti()==RS2::EntityGraphic || e2->getParent()->rtti()==RS2::EntityBlock)) { // normal entities: return true; } } else { // independent entities with the same parent: return (e1->getParent()==e2->getParent()); } } return false; } /** * Gets the nearest end point to the given coordinate. * * @param coord Coordinate (typically a mouse coordinate) * * @return the coordinate found or an invalid vector * if there are no elements at all in this graphics * container. */ RS_Vector RS_Information::getNearestEndpoint(const RS_Vector& coord, double* dist) const { return container->getNearestEndpoint(coord, dist); } /** * Gets the nearest point to the given coordinate which is on an entity. * * @param coord Coordinate (typically a mouse coordinate) * @param dist Pointer to a double which will contain the * measured distance after return or nullptr * @param entity Pointer to a pointer which will point to the * entity on which the point is or nullptr * * @return the coordinate found or an invalid vector * if there are no elements at all in this graphics * container. */ RS_Vector RS_Information::getNearestPointOnEntity(const RS_Vector& coord, bool onEntity, double* dist, RS_Entity** entity) const { return container->getNearestPointOnEntity(coord, onEntity, dist, entity); } /** * Gets the nearest entity to the given coordinate. * * @param coord Coordinate (typically a mouse coordinate) * @param dist Pointer to a double which will contain the * masured distance after return * @param level Level of resolving entities. * * @return the entity found or nullptr if there are no elements * at all in this graphics container. */ RS_Entity* RS_Information::getNearestEntity(const RS_Vector& coord, double* dist, RS2::ResolveLevel level) const { return container->getNearestEntity(coord, dist, level); } /** * Calculates the intersection point(s) between two entities. * * @param onEntities true: only return intersection points which are * on both entities. * false: return all intersection points. * * @todo support more entities * * @return All intersections of the two entities. The tangent flag in * RS_VectorSolutions is set if one intersection is a tangent point. */ RS_VectorSolutions RS_Information::getIntersection(RS_Entity const* e1, RS_Entity const* e2, bool onEntities) { RS_VectorSolutions ret; const double tol = 1.0e-4; if (!(e1 && e2) ) { RS_DEBUG->print("RS_Information::getIntersection() for nullptr entities"); return ret; } if (e1->getId() == e2->getId()) { RS_DEBUG->print("RS_Information::getIntersection() of the same entity"); return ret; } // unsupported entities / entity combinations: if ( e1->rtti()==RS2::EntityMText || e2->rtti()==RS2::EntityMText || e1->rtti()==RS2::EntityText || e2->rtti()==RS2::EntityText || isDimension(e1->rtti()) || isDimension(e2->rtti())) { return ret; } if (onEntities && !(e1->isConstruction() || e2->isConstruction())) { // a little check to avoid doing unneeded intersections, an attempt to avoid O(N^2) increasing of checking two-entity information LC_Rect const rect1{e1->getMin(), e1->getMax()}; LC_Rect const rect2{e2->getMin(), e2->getMax()}; if (onEntities && !rect1.intersects(rect2, RS_TOLERANCE)) { return ret; } } //avoid intersections between line segments the same spline /* ToDo: 24 Aug 2011, Dongxu Li, if rtti() is not defined for the parent, the following check for splines may still cause segfault */ if ( e1->getParent() && e1->getParent() == e2->getParent()) { if ( e1->getParent()->rtti()==RS2::EntitySpline ) { //do not calculate intersections from neighboring lines of a spline if ( abs(e1->getParent()->findEntity(e1) - e1->getParent()->findEntity(e2)) <= 1 ) { return ret; } } } if(e1->rtti() == RS2::EntitySplinePoints || e2->rtti() == RS2::EntitySplinePoints) { ret = LC_SplinePoints::getIntersection(e1, e2); } else { // issue #484 , quadratic intersection solver is not robust enough for quadratic-quadratic // TODO, implement a robust algorithm for quadratic based solvers, and detecting entity type // circles/arcs can be removed auto isArc = [](RS_Entity const* e) { auto type = e->rtti(); return type == RS2::EntityCircle || type == RS2::EntityArc; }; if(isArc(e1) && isArc(e2)){ //use specialized arc-arc intersection solver ret=getIntersectionArcArc(e1, e2); }else{ const auto qf1=e1->getQuadratic(); const auto qf2=e2->getQuadratic(); ret=LC_Quadratic::getIntersection(qf1,qf2); } } RS_VectorSolutions ret2; for(const RS_Vector& vp: ret){ if (!vp.valid) continue; if (onEntities) { //ignore intersections not on entity if (!( (e1->isConstruction(true) || e1->isPointOnEntity(vp, tol)) && (e2->isConstruction(true) || e2->isPointOnEntity(vp, tol)) ) ) { // std::cout<<"Ignored intersection "<isPointOnEntity(ret.get(i), tol)="<isPointOnEntity(vp, tol) // <<"\t(e2->isPointOnEntity(ret.get(i), tol)="<isPointOnEntity(vp, tol)<getTangentDirection(vp); RS_Vector direction2=e2->getTangentDirection(vp); if( direction1.valid && direction2.valid && fabs(fabs(direction1.dotP(direction2)) - sqrt(direction1.squared()*direction2.squared())) < sqrt(tol)*tol ) ret2.setTangent(true); //TODO, make the following tangential test, nearest test work for all entity types // RS_Entity *lpLine = nullptr, // *lpCircle = nullptr; // if( RS2::EntityLine == e1->rtti() && RS2::EntityCircle == e2->rtti()) { // lpLine = e1; // lpCircle = e2; // } // else if( RS2::EntityCircle == e1->rtti() && RS2::EntityLine == e2->rtti()) { // lpLine = e2; // lpCircle = e1; // } // if( nullptr != lpLine && nullptr != lpCircle) { // double dist = 0.0; // RS_Vector nearest = lpLine->getNearestPointOnEntity( lpCircle->getCenter(), false, &dist); // // special case: line touches circle tangent // if( nearest.valid && fabs( dist - lpCircle->getRadius()) < tol) { // ret.set(i,nearest); // ret2.setTangent(true); // } // } ret2.push_back(vp); } return ret2; } /** * @return Intersection between two lines. */ RS_VectorSolutions RS_Information::getIntersectionLineLine(RS_Line* e1, RS_Line* e2) { RS_VectorSolutions ret; if (!(e1 && e2)) { RS_DEBUG->print("RS_Information::getIntersectionLineLin() for nullptr entities"); return ret; } RS_Vector p1 = e1->getStartpoint(); RS_Vector p2 = e1->getEndpoint(); RS_Vector p3 = e2->getStartpoint(); RS_Vector p4 = e2->getEndpoint(); double num = ((p4.x-p3.x)*(p1.y-p3.y) - (p4.y-p3.y)*(p1.x-p3.x)); double div = ((p4.y-p3.y)*(p2.x-p1.x) - (p4.x-p3.x)*(p2.y-p1.y)); if (fabs(div)>RS_TOLERANCE && fabs(remainder(e1->getAngle1()-e2->getAngle1(), M_PI))>=RS_TOLERANCE*10.) { double u = num / div; double xs = p1.x + u * (p2.x-p1.x); double ys = p1.y + u * (p2.y-p1.y); ret = RS_VectorSolutions({RS_Vector(xs, ys)}); } // lines are parallel else { ret = RS_VectorSolutions(); } return ret; } /** * @return One or two intersection points between given entities. */ RS_VectorSolutions RS_Information::getIntersectionLineArc(RS_Line* line, RS_Arc* arc) { RS_VectorSolutions ret; if (!(line && arc)) return ret; double dist=0.0; RS_Vector nearest; nearest = line->getNearestPointOnEntity(arc->getCenter(), false, &dist); // special case: arc touches line (tangent): if (nearest.valid && fabs(dist - arc->getRadius()) < 1.0e-4) { ret = RS_VectorSolutions({nearest}); ret.setTangent(true); return ret; } RS_Vector p = line->getStartpoint(); RS_Vector d = line->getEndpoint() - line->getStartpoint(); double d2=d.squared(); RS_Vector c = arc->getCenter(); double r = arc->getRadius(); RS_Vector delta = p - c; if (d2getMiddlePoint()}); } return ret; } //intersection // solution = p + t d; //| p -c+ t d|^2 = r^2 // |d|^2 t^2 + 2 (p-c).d t + |p-c|^2 -r^2 = 0 double a1 = RS_Vector::dotP(delta,d); double term1 = a1*a1 - d2*(delta.squared()-r*r); // std::cout<<" discriminant= "<getNearestPointOnEntity(c, false)}); ret.setTangent(true); // std::cout<<"Tangential point: "<RS_TOLERANCE2) { ret.push_back(vp); } angleVector.y *= -1.; ret.rotate(center, angleVector); // std::cout<<"found Ellipse-Line intersections: "<print("RS_Information::getIntersectionEllipseLine(): done"); return ret; } /** * Checks if the given coordinate is inside the given contour. * * @param point Coordinate to check. * @param contour One or more entities which shape a contour. * If the given contour is not closed, the result is undefined. * The entities don't need to be in a specific order. * @param onContour Will be set to true if the given point it exactly * on the contour. */ bool RS_Information::isPointInsideContour(const RS_Vector& point, RS_EntityContainer* contour, bool* onContour) { if (!contour) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Information::isPointInsideContour: contour is nullptr"); return false; } if (point.x < contour->getMin().x || point.x > contour->getMax().x || point.y < contour->getMin().y || point.y > contour->getMax().y) { return false; } double width = contour->getSize().x+1.0; bool sure; int counter; int tries = 0; double rayAngle = 0.0; do { sure = true; // create ray: RS_Vector v = RS_Vector::polar(width*10.0, rayAngle); RS_Line ray{point, point+v}; counter = 0; RS_VectorSolutions sol; if (onContour) { *onContour = false; } for (RS_Entity* e = contour->firstEntity(RS2::ResolveAll); e; e = contour->nextEntity(RS2::ResolveAll)) { // intersection(s) from ray with contour entity: sol = RS_Information::getIntersection(&ray, e, true); for (int i=0; i<=1; ++i) { RS_Vector p = sol.get(i); if (p.valid) { // point is on the contour itself if (p.distanceTo(point)<1.0e-5) { if (onContour) { *onContour = true; } } else { if (e->rtti()==RS2::EntityLine) { RS_Line* line = (RS_Line*)e; // ray goes through startpoint of line: if (p.distanceTo(line->getStartpoint())<1.0e-4) { if (RS_Math::correctAngle(line->getAngle1())getEndpoint())<1.0e-4) { if (RS_Math::correctAngle(line->getAngle2())rtti()==RS2::EntityArc) { RS_Arc* arc = (RS_Arc*)e; if (p.distanceTo(arc->getStartpoint())<1.0e-4) { double dir = arc->getDirection1(); if ((dir=1.0e-5) || ((dir>2*M_PI-1.0e-5 || dir<1.0e-5) && arc->getCenter().y>p.y)) { counter++; sure = false; } } else if (p.distanceTo(arc->getEndpoint())<1.0e-4) { double dir = arc->getDirection2(); if ((dir=1.0e-5) || ((dir>2*M_PI-1.0e-5 || dir<1.0e-5) && arc->getCenter().y>p.y)) { counter++; sure = false; } } else { counter++; } } else if (e->rtti()==RS2::EntityCircle) { // tangent: if (i==0 && sol.get(1).valid==false) { if (!sol.isTangent()) { counter++; } else { sure = false; } } else if (i==1 || sol.get(1).valid==true) { counter++; } } else if (e->rtti()==RS2::EntityEllipse) { RS_Ellipse* ellipse=static_cast(e); if(ellipse->isArc()){ if (p.distanceTo(ellipse->getStartpoint())<1.0e-4) { double dir = ellipse->getDirection1(); if ((dir=1.0e-5) || ((dir>2*M_PI-1.0e-5 || dir<1.0e-5) && ellipse->getCenter().y>p.y)) { counter++; sure = false; } } else if (p.distanceTo(ellipse->getEndpoint())<1.0e-4) { double dir = ellipse->getDirection2(); if ((dir=1.0e-5) || ((dir>2*M_PI-1.0e-5 || dir<1.0e-5) && ellipse->getCenter().y>p.y)) { counter++; sure = false; } } else { counter++; } }else{ // tangent: if (i==0 && sol.get(1).valid==false) { if (!sol.isTangent()) { counter++; } else { sure = false; } } else if (i==1 || sol.get(1).valid==true) { counter++; } } } } } } } rayAngle+=0.02; tries++; } while (!sure && rayAngle<2*M_PI && tries<6); // remove double intersections: /* QList is2; bool done; RS_Vector* av; do { done = true; double minDist = RS_MAXDOUBLE; double dist; av = nullptr; for (RS_Vector* v = is.first(); v; v = is.next()) { dist = point.distanceTo(*v); if (dist lines; for(auto e: c){ if(e->rtti()!=RS2::EntityLine) return ret; lines.push_back(static_cast(e)); } if(lines.size()!=4) return ret; //find intersections std::vector vertices; for(auto it=lines.begin()+1; it != lines.end(); ++it){ for(auto jt=lines.begin(); jt != it; ++jt){ RS_VectorSolutions const& sol=RS_Information::getIntersectionLineLine(*it, *jt); if(sol.size()){ vertices.push_back(sol.at(0)); } } } // std::cout<<"vertices.size()="<getDirection1(); std::vector::iterator> left; std::vector::iterator> right; for(auto it=vertices.begin(); it != vertices.end(); ++it){ RS_Vector const& dir=*it - pl->getNearestPointOnEntity(*it, false); if(dir.squared()