DLL_PUBLIC double CDECL area(ClipperLib::IntPoint* path, size_t count) { ClipperLib::Path v = ClipperLib::Path(); for(size_t i = 0; i < count; i++) { v.emplace(v.end(), path[i].X, path[i].Y); } return ClipperLib::Area(v); }
void Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input, ClipperLib::Path &output) { output.clear(); for (Slic3r::Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit) { output.push_back(ClipperLib::IntPoint( (*pit).x, (*pit).y )); } }
//============================================================== // Static functions //============================================================== DLL_PUBLIC bool CDECL orientation(ClipperLib::IntPoint* path, size_t count) { ClipperLib::Path v = ClipperLib::Path(); for(size_t i = 0; i < count; i++) { v.emplace(v.end(), path[i].X, path[i].Y); } return ClipperLib::Orientation(v); }
void ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, T &output) { output.points.clear(); for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) { output.points.push_back(Slic3r::Point( (*pit).X, (*pit).Y )); } }
static ClipperLib::Path toClipperPath(const GeometryCoordinates& ring) { ClipperLib::Path result; result.reserve(ring.size()); for (const auto& p : ring) { result.emplace_back(p.x, p.y); } return result; }
Vector<Vector2> expand(const Vector<Vector2> &points, const Rect2i &rect, float epsilon = 2.0) { int size = points.size(); ERR_FAIL_COND_V(size < 2, Vector<Vector2>()); ClipperLib::Path subj; ClipperLib::PolyTree solution; ClipperLib::PolyTree out; for (int i = 0; i < points.size(); i++) { subj << ClipperLib::IntPoint(points[i].x * PRECISION, points[i].y * PRECISION); } ClipperLib::ClipperOffset co; co.AddPath(subj, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); co.Execute(solution, epsilon * PRECISION); ClipperLib::PolyNode *p = solution.GetFirst(); ERR_FAIL_COND_V(!p, points); while (p->IsHole()) { p = p->GetNext(); } //turn the result into simply polygon (AKA, fix overlap) //clamp into the specified rect ClipperLib::Clipper cl; cl.StrictlySimple(true); cl.AddPath(p->Contour, ClipperLib::ptSubject, true); //create the clipping rect ClipperLib::Path clamp; clamp.push_back(ClipperLib::IntPoint(0, 0)); clamp.push_back(ClipperLib::IntPoint(rect.size.width * PRECISION, 0)); clamp.push_back(ClipperLib::IntPoint(rect.size.width * PRECISION, rect.size.height * PRECISION)); clamp.push_back(ClipperLib::IntPoint(0, rect.size.height * PRECISION)); cl.AddPath(clamp, ClipperLib::ptClip, true); cl.Execute(ClipperLib::ctIntersection, out); Vector<Vector2> outPoints; ClipperLib::PolyNode *p2 = out.GetFirst(); while (p2->IsHole()) { p2 = p2->GetNext(); } int lasti = p2->Contour.size() - 1; Vector2 prev = Vector2(p2->Contour[lasti].X / PRECISION, p2->Contour[lasti].Y / PRECISION); for (unsigned int i = 0; i < p2->Contour.size(); i++) { Vector2 cur = Vector2(p2->Contour[i].X / PRECISION, p2->Contour[i].Y / PRECISION); if (cur.distance_to(prev) > 0.5) { outPoints.push_back(cur); prev = cur; } } return outPoints; }
void ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, T* output) { PROFILE_FUNC(); output->points.clear(); output->points.reserve(input.size()); for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) output->points.push_back(Slic3r::Point( (*pit).X, (*pit).Y )); }
std::vector<Vec2> AutoPolygon::expand(const std::vector<Vec2>& points, const cocos2d::Rect &rect, const float& epsilon) { auto size = points.size(); // if there are less than 3 points, then we have nothing if(size<3) { log("AUTOPOLYGON: cannot expand points for %s with less than 3 points, e: %f", _filename.c_str(), epsilon); return std::vector<Vec2>(); } ClipperLib::Path subj; ClipperLib::PolyTree solution; ClipperLib::PolyTree out; for(std::vector<Vec2>::const_iterator it = points.begin(); it<points.end(); it++) { subj << ClipperLib::IntPoint(it-> x* PRECISION, it->y * PRECISION); } ClipperLib::ClipperOffset co; co.AddPath(subj, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); co.Execute(solution, epsilon * PRECISION); ClipperLib::PolyNode* p = solution.GetFirst(); if(!p) { log("AUTOPOLYGON: Clipper failed to expand the points"); return points; } while(p->IsHole()){ p = p->GetNext(); } //turn the result into simply polygon (AKA, fix overlap) //clamp into the specified rect ClipperLib::Clipper cl; cl.StrictlySimple(true); cl.AddPath(p->Contour, ClipperLib::ptSubject, true); //create the clipping rect ClipperLib::Path clamp; clamp.push_back(ClipperLib::IntPoint(0, 0)); clamp.push_back(ClipperLib::IntPoint(rect.size.width/_scaleFactor * PRECISION, 0)); clamp.push_back(ClipperLib::IntPoint(rect.size.width/_scaleFactor * PRECISION, rect.size.height/_scaleFactor * PRECISION)); clamp.push_back(ClipperLib::IntPoint(0, rect.size.height/_scaleFactor * PRECISION)); cl.AddPath(clamp, ClipperLib::ptClip, true); cl.Execute(ClipperLib::ctIntersection, out); std::vector<Vec2> outPoints; ClipperLib::PolyNode* p2 = out.GetFirst(); while(p2->IsHole()){ p2 = p2->GetNext(); } auto end = p2->Contour.end(); for(std::vector<ClipperLib::IntPoint>::const_iterator pt = p2->Contour.begin(); pt < end; pt++) { outPoints.push_back(Vec2(pt->X/PRECISION, pt->Y/PRECISION)); } return outPoints; }
ClipperLib::Path polygon::path(base_int denom) const { ClipperLib::Path ret; for(auto v : this->vertexes) { ret.push_back(ClipperLib::IntPoint((ClipperLib::cInt)(v.x.numerator() * (denom / v.x.denominator())), (ClipperLib::cInt)(v.y.numerator() * (denom / v.y.denominator())))); } return ret; }
ClipperLib::Path Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input) { ClipperLib::Path output; output.reserve(input.points.size()); for (Slic3r::Points::const_reverse_iterator pit = input.points.rbegin(); pit != input.points.rend(); ++pit) output.emplace_back((*pit)(0), (*pit)(1)); return output; }
void unscaleClipperPolygon(ClipperLib::Path &polygon) { PROFILE_FUNC(); for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) { pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; pit->X >>= CLIPPER_OFFSET_POWER_OF_2; pit->Y >>= CLIPPER_OFFSET_POWER_OF_2; } }
std::string SVG::get_path_d(const ClipperLib::Path &path, double scale, bool closed) const { std::ostringstream d; d << "M "; for (ClipperLib::Path::const_iterator p = path.begin(); p != path.end(); ++p) { d << COORD(scale * p->X - origin.x) << " "; d << COORD(scale * p->Y - origin.y) << " "; } if (closed) d << "z"; return d.str(); }
ClipperLib::Path Clipper::toClipper(const ofPolyline& polyline, ClipperLib::cInt scale) { ClipperLib::Path path; for (auto& vertex: polyline.getVertices()) { path.push_back(toClipper(vertex, scale)); } return path; }
void Grasp_Calculator::path_to_double_polygon(DPolygon2D &double_polygon, ClipperLib::Path int_polygon) { ClipperLib::cInt factor = 100000; double_polygon.clear(); for (std::vector<IntPoint>::iterator ip = int_polygon.begin(); ip != int_polygon.end(); ++ip) { DoublePoint2D d2p; d2p.x = ((double)ip->X) / factor; d2p.y = ((double)ip->Y) / factor; double_polygon.push_back(d2p); } }
DLL_PUBLIC void CDECL add_offset_path(ClipperLib::ClipperOffset *ptr, ClipperLib::IntPoint* path, size_t count, ClipperLib::JoinType joinType, ClipperLib::EndType endType) { ClipperLib::Path v = ClipperLib::Path(); for(size_t i = 0; i < count; i++) { v.emplace(v.end(), path[i].X, path[i].Y); } try { ptr->AddPath(v, joinType, endType); } catch(ClipperLib::clipperException e) { printf(e.what()); } }
DLL_PUBLIC bool CDECL add_path(ClipperLib::Clipper *ptr, ClipperLib::IntPoint* path, size_t count, ClipperLib::PolyType polyType, bool closed) { ClipperLib::Path v = ClipperLib::Path(); for(size_t i = 0; i < count; i++) { v.emplace(v.end(), path[i].X, path[i].Y); } bool result = false; try { result = ptr->AddPath(v, polyType, closed); } catch(ClipperLib::clipperException e) { printf(e.what()); } return result; }
// Set the objects (defined by contour points) to be models in the world and scene. void World::setObjectsToBeModeled(const std::vector<std::vector<cv::Point>> contours) { int contourSize = (int)contours.size(); for(int i = 0; i < contourSize; i++ ) { std::vector<cv::Point> currentShape = contours[i]; int numOfPoints = (int)currentShape.size(); b2Vec2 * vertices = new b2Vec2[numOfPoints]; ClipperLib::Paths* polygons = new ClipperLib::Paths(); ClipperLib::Path polygon; for (int j = 0; j < numOfPoints; j++) { vertices[j].x = currentShape[j].x / PTM_RATIO; vertices[j].y = currentShape[j].y / PTM_RATIO; //cv::line(m_scene, currentShape[j], currentShape[(j + 1) % numOfPoints], cv::Scalar(0,0,255)); //std::cout << "[" << vertices[j].x << "," <<vertices[j].y << "]" << std::endl; polygon.push_back(ClipperLib::IntPoint(currentShape[j].x, currentShape[j].y)); } b2BodyDef objectBodyDef; objectBodyDef.type = b2_staticBody; b2Body *objectBody = m_world->CreateBody(&objectBodyDef); objectBody->SetUserData(polygons); polygons->push_back(polygon); b2EdgeShape objectEdgeShape; b2FixtureDef objectShapeDef; objectShapeDef.shape = &objectEdgeShape; for (int j = 0; j < numOfPoints - 1; j++) { objectEdgeShape.Set(vertices[j], vertices[j+1]); objectBody->CreateFixture(&objectShapeDef); } objectEdgeShape.Set(vertices[numOfPoints - 1], vertices[0]); objectBody->CreateFixture(&objectShapeDef); m_objectBodies.push_back(objectBody); delete[] vertices; } }
ClipperLib::Path pathToClipperPath(const panda::types::Path& path) { ClipperLib::Path out; auto maxVal = std::numeric_limits<ClipperLib::cInt>::max(); ClipperLib::IntPoint prevPt(maxVal, maxVal); for (const auto& pt : path.points) { auto newPt = convert(pt); if (newPt == prevPt) continue; out.push_back(newPt); prevPt = newPt; } if (path.points.size() > 1 && path.points.back() == path.points.front()) out.pop_back(); return out; }
void BooleanTool::rebuildCoordinate( ClipperLib::Path::size_type index, const ClipperLib::Path& polygon, const PolyMap& polymap, PathObject* object, bool start_new_part) { MapCoord coord(0.001 * polygon.at(index).X, 0.001 * polygon.at(index).Y); if (polymap.contains(polygon.at(index))) { PathCoordInfo info = polymap.value(polygon.at(index)); MapCoord& original = info.first->path->getCoordinate(info.second->index); if (original.isDashPoint()) coord.setDashPoint(true); } object->addCoordinate(coord, start_new_part); }
ClipperLib::Path ElementGeometryClipper::createPathFromBoundingBox() { double xMin = quadKeyBbox_.minPoint.longitude, yMin = quadKeyBbox_.minPoint.latitude, xMax = quadKeyBbox_.maxPoint.longitude, yMax = quadKeyBbox_.maxPoint.latitude; ClipperLib::Path rect; rect.push_back(ClipperLib::IntPoint(static_cast<ClipperLib::cInt>(xMin*Scale), static_cast<ClipperLib::cInt>(yMin*Scale))); rect.push_back(ClipperLib::IntPoint(static_cast<ClipperLib::cInt>(xMax*Scale), static_cast<ClipperLib::cInt>(yMin*Scale))); rect.push_back(ClipperLib::IntPoint(static_cast<ClipperLib::cInt>(xMax*Scale), static_cast<ClipperLib::cInt>(yMax*Scale))); rect.push_back(ClipperLib::IntPoint(static_cast<ClipperLib::cInt>(xMin*Scale), static_cast<ClipperLib::cInt>(yMax*Scale))); return std::move(rect); }
panda::types::Path clipperPathToPath(const ClipperLib::Path& path) { panda::types::Path out; if (path.empty()) return out; for (const auto& pt : path) out.points.push_back(convert(pt)); if (out.points.front() != out.points.back()) out.points.push_back(out.points.front()); return out; }
static GeometryCoordinates fromClipperPath(const ClipperLib::Path& path) { GeometryCoordinates result; result.reserve(path.size() + 1); result.reserve(path.size()); for (const auto& p : path) { using Coordinate = GeometryCoordinates::coordinate_type; assert(p.x >= std::numeric_limits<Coordinate>::min()); assert(p.x <= std::numeric_limits<Coordinate>::max()); assert(p.y >= std::numeric_limits<Coordinate>::min()); assert(p.y <= std::numeric_limits<Coordinate>::max()); result.emplace_back(Coordinate(p.x), Coordinate(p.y)); } // Clipper does not repeat initial point, but our geometry model requires it. if (!result.empty()) { result.push_back(result.front()); } return result; }
void Polygon2d_TestModule() { // "This structure contains a sequence of IntPoint vertices defining a // single contour" ClipperLib::Path aPath; SEGMENTS aSegments; aPath.resize( 4 ); aPath[0] = ClipperLib::IntPoint( -2, -2 ); aPath[1] = ClipperLib::IntPoint( 2, -2 ); aPath[2] = ClipperLib::IntPoint( 2, 2 ); aPath[3] = ClipperLib::IntPoint( -2, 2 ); // It must be an outter polygon wxASSERT( ClipperLib::Orientation( aPath ) ); polygon_Convert( aPath, aSegments, 1.0f ); wxASSERT( aPath.size() == aSegments.size() ); wxASSERT( aSegments[0].m_Start == SFVEC2F( -2.0f, 2.0f ) ); wxASSERT( aSegments[1].m_Start == SFVEC2F( 2.0f, 2.0f ) ); wxASSERT( aSegments[2].m_Start == SFVEC2F( 2.0f, -2.0f ) ); wxASSERT( aSegments[3].m_Start == SFVEC2F( -2.0f, -2.0f ) ); wxASSERT( polygon_IsPointInside( aSegments, SFVEC2F( 0.0f, 0.0f ) ) ); wxASSERT( polygon_IsPointInside( aSegments, SFVEC2F( -1.9f, -1.9f ) ) ); wxASSERT( polygon_IsPointInside( aSegments, SFVEC2F( -1.9f, 1.9f ) ) ); wxASSERT( polygon_IsPointInside( aSegments, SFVEC2F( 1.9f, 1.9f ) ) ); wxASSERT( polygon_IsPointInside( aSegments, SFVEC2F( 1.9f, -1.9f ) ) ); wxASSERT( polygon_IsPointInside( aSegments, SFVEC2F( -2.1f, -2.0f ) ) == false ); wxASSERT( polygon_IsPointInside( aSegments, SFVEC2F( -2.1f, 2.0f ) ) == false ); wxASSERT( polygon_IsPointInside( aSegments, SFVEC2F( 2.1f, 2.0f ) ) == false ); wxASSERT( polygon_IsPointInside( aSegments, SFVEC2F( 2.1f, -2.0f ) ) == false ); }
static void polygon_Convert( const ClipperLib::Path &aPath, SEGMENTS &aOutSegment, float aBiuTo3DunitsScale ) { aOutSegment.resize( aPath.size() ); for( unsigned i = 0; i < aPath.size(); i++ ) { aOutSegment[i].m_Start = SFVEC2F( (float) aPath[i].X * aBiuTo3DunitsScale, (float)-aPath[i].Y * aBiuTo3DunitsScale ); } unsigned int i; unsigned int j = aOutSegment.size () - 1; for( i = 0; i < aOutSegment.size (); j = i++ ) { // Calculate constants for each segment aOutSegment[i].m_inv_JY_minus_IY = 1.0f / ( aOutSegment[j].m_Start.y - aOutSegment[i].m_Start.y ); aOutSegment[i].m_JX_minus_IX = (aOutSegment[j].m_Start.x - aOutSegment[i].m_Start.x); } }
void buildMansardShape(const utymap::meshing::Polygon& polygon, ClipperLib::Path& offsetShape, std::size_t index) { if (!ClipperLib::Orientation(offsetShape)) std::reverse(offsetShape.begin(), offsetShape.end()); // build top utymap::meshing::Polygon topShape(offsetShape.size(), 0); std::vector<utymap::meshing::Vector2> topShapeVertices; topShapeVertices.reserve(offsetShape.size()); for (const auto& p : offsetShape) { topShapeVertices.push_back(utymap::meshing::Vector2(p.X / Scale, p.Y/ Scale)); } topShape.addContour(topShapeVertices); auto topOptions = utymap::meshing::MeshBuilder::Options(0, 0, colorNoiseFreq_, height_, gradient_, minHeight_); builderContext_.meshBuilder.addPolygon(meshContext_.mesh, topShape, topOptions); // build sides auto sideOptions = utymap::meshing::MeshBuilder::Options( 0, 0, colorNoiseFreq_, 0, gradient_, 0); double topHeight = minHeight_ + height_; auto size = polygon.points.size(); for (std::size_t i = 0; i < size; i += 2) { auto topIndex = i; auto bottomIndex = (index + i) % size; auto nextTopIndex = (i + 2) % size; auto nextBottomIndex = (index + i + 2) % size; auto v0 = utymap::meshing::Vector3(polygon.points[bottomIndex], minHeight_, polygon.points[bottomIndex + 1]); auto v1 = utymap::meshing::Vector3(polygon.points[nextBottomIndex], minHeight_, polygon.points[nextBottomIndex + 1]); auto v2 = utymap::meshing::Vector3(topShape.points[nextTopIndex], topHeight, topShape.points[nextTopIndex + 1]); auto v3 = utymap::meshing::Vector3(topShape.points[topIndex], topHeight, topShape.points[topIndex + 1]); builderContext_.meshBuilder.addTriangle(meshContext_.mesh, v2, v0, v3, sideOptions, false); builderContext_.meshBuilder.addTriangle(meshContext_.mesh, v0, v2, v1, sideOptions, false); } }
geo::Polygon<geo::Ring<Vector>> Environment::subtract(geo::Polygon<geo::Ring<Vector>> const& poly, geo::Ring<Vector> const& ring) { ClipperLib::Path subj; ClipperLib::Paths solution; ClipperLib::Clipper c; for (Vector const& v : poly.ering) subj.push_back(ClipperLib::IntPoint((int)v.x, (int)v.y)); c.AddPath(subj, ClipperLib::ptSubject, true); for (Ring const& ring : poly.irings) { subj.clear(); for (Vector const& v : ring) subj.push_back(ClipperLib::IntPoint((int)v.x, (int)v.y)); std::reverse(subj.begin(), subj.end()); c.AddPath(subj, ClipperLib::ptSubject, true); } subj.clear(); for (Vector const& v : ring) subj.push_back(ClipperLib::IntPoint((int)v.x, (int)v.y)); c.AddPath(subj, ClipperLib::ptClip, true); c.Execute(ClipperLib::ctDifference, solution); geo::Polygon<geo::Ring<Vector>> ans; for (ClipperLib::IntPoint const& pt : solution[0]) { ans.ering.push_back({pt.X, pt.Y}); } for (int i = 1; i < solution.size(); ++i) { ClipperLib::Path const& path = solution[i]; geo::Ring<Vector> ring; for (ClipperLib::IntPoint const& pt : path) ring.push_back({pt.X, pt.Y}); ans.irings.push_back(ring); } geo::correct(ans); return ans; }
inline void setCoordinates(T& t, const ClipperLib::Path& path) { t.coordinates.reserve(path.size()); for (const auto& c : path) { t.coordinates.push_back(GeoCoordinate(c.Y / Scale, c.X / Scale)); } }
void BooleanTool::rebuildTwoIndexSegment( ClipperLib::Path::size_type start_index, ClipperLib::Path::size_type end_index, bool sequence_increasing, const ClipperLib::Path& polygon, const PolyMap& polymap, PathObject* object) { Q_UNUSED(sequence_increasing); // only used in Q_ASSERT. PathCoordInfo start_info = polymap.value(polygon.at(start_index)); PathCoordInfo end_info = polymap.value(polygon.at(end_index)); PathObject* original = end_info.first->path; bool coords_increasing; bool is_curve; int coord_index; if (start_info.second->index == end_info.second->index) { coord_index = end_info.second->index; bool found = checkSegmentMatch(original, coord_index, polygon, start_index, end_index, coords_increasing, is_curve); if (!found) { object->getCoordinate(object->getCoordinateCount() - 1).setCurveStart(false); rebuildCoordinate(end_index, polygon, polymap, object); return; } Q_ASSERT(coords_increasing == sequence_increasing); } else { coord_index = end_info.second->index; bool found = checkSegmentMatch(original, coord_index, polygon, start_index, end_index, coords_increasing, is_curve); if (!found) { coord_index = start_info.second->index; found = checkSegmentMatch(original, coord_index, polygon, start_index, end_index, coords_increasing, is_curve); if (!found) { object->getCoordinate(object->getCoordinateCount() - 1).setCurveStart(false); rebuildCoordinate(end_index, polygon, polymap, object); return; } } } if (!is_curve) object->getCoordinate(object->getCoordinateCount() - 1).setCurveStart(false); if (coords_increasing) { object->addCoordinate(resetCoordinate(original->getCoordinate(coord_index + 1))); if (is_curve) { object->addCoordinate(original->getCoordinate(coord_index + 2)); object->addCoordinate(resetCoordinate(original->getCoordinate(coord_index + 3))); } } else { if (is_curve) { object->addCoordinate(resetCoordinate(original->getCoordinate(coord_index + 2))); object->addCoordinate(original->getCoordinate(coord_index + 1)); } object->addCoordinate(resetCoordinate(original->getCoordinate(coord_index + 0))); } }
void BooleanTool::rebuildSegment( ClipperLib::Path::size_type start_index, ClipperLib::Path::size_type end_index, bool sequence_increasing, const ClipperLib::Path& polygon, const PolyMap& polymap, PathObject* object) { auto num_points = polygon.size(); object->getCoordinate(object->getCoordinateCount() - 1).setCurveStart(true); if ((start_index + 1) % num_points == end_index) { // This could happen for a straight line or a very flat curve - take coords directly from original rebuildTwoIndexSegment(start_index, end_index, sequence_increasing, polygon, polymap, object); return; } // Get polygon point coordinates const auto& start_point = polygon.at(start_index); const auto& second_point = polygon.at((start_index + 1) % num_points); const auto& second_last_point = polygon.at((end_index - 1) % num_points); const auto& end_point = polygon.at(end_index); // Try to find the middle coordinates in the same part bool found = false; PathCoordInfo second_info{ nullptr, nullptr }; PathCoordInfo second_last_info{ nullptr, nullptr }; for (auto second_it = polymap.find(second_point); second_it != polymap.end(); ++second_it) { for (auto second_last_it = polymap.find(second_last_point); second_last_it != polymap.end() && second_last_it.key() == second_last_point; ++second_last_it) { if (second_it->first == second_last_it->first && second_it->second->index == second_last_it->second->index) { // Same part found = true; second_info = *second_it; second_last_info = *second_last_it; break; } } if (found) break; } if (!found) { // Need unambiguous path part information to find the original object with high probability qDebug() << "BooleanTool::rebuildSegment: cannot identify original object!"; rebuildSegmentFromPathOnly(start_point, second_point, second_last_point, end_point, object); return; } const PathPart* original_path = second_info.first; // Try to find the outer coordinates in the same part PathCoordInfo start_info{ nullptr, nullptr }; for (auto start_it = polymap.find(start_point); start_it != polymap.end() && start_it.key() == start_point; ++start_it) { if (start_it->first == original_path) { start_info = *start_it; break; } } Q_ASSERT(!start_info.first || start_info.first == second_info.first); PathCoordInfo end_info{ nullptr, nullptr }; for (auto end_it = polymap.find(end_point); end_it != polymap.end() && end_it.key() == end_point; ++end_it) { if (end_it->first == original_path) { end_info = *end_it; break; } } Q_ASSERT(!end_info.first || end_info.first == second_info.first); const PathObject* original = original_path->path; auto edge_start = second_info.second->index; if (edge_start == second_info.first->last_index) edge_start = second_info.first->first_index; // Find out start tangent auto start_param = 0.0; MapCoord start_coord = MapCoord(0.001 * start_point.X, 0.001 * start_point.Y); MapCoord start_tangent; MapCoord end_tangent; MapCoord end_coord; double start_error_sq, end_error_sq; // Maximum difference in mm from reconstructed start and end coords to the // intersection points returned by Clipper const double error_bound = 0.4; if (sequence_increasing) { if ( second_info.second->param == 0.0 || ( start_info.first && start_info.second->param == 0.0 && ( start_info.second->index == edge_start || (start_info.second->index == start_info.first->last_index && start_info.first->first_index == edge_start) ) ) ) { // Take coordinates directly start_tangent = original->getCoordinate(edge_start + 1); end_tangent = original->getCoordinate(edge_start + 2); start_error_sq = start_coord.distanceSquaredTo(original->getCoordinate(edge_start + 0)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in increasing direct case: " << sqrt(start_error_sq); } else { // Approximate coords const PathCoord* prev_coord = second_info.second - 1; auto dx = second_point.X - start_point.X; auto dy = second_point.Y - start_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_start_param = (second_info.second->param - prev_coord->param) * point_dist / qMax(1e-7f, (second_info.second->clen - prev_coord->clen)); start_param = qBound(0.0, second_info.second->param - delta_start_param, 1.0); MapCoordF unused, o2, o3, o4; PathCoord::splitBezierCurve(MapCoordF(original->getCoordinate(edge_start + 0)), MapCoordF(original->getCoordinate(edge_start + 1)), MapCoordF(original->getCoordinate(edge_start + 2)), MapCoordF(original->getCoordinate(edge_start + 3)), start_param, unused, unused, o2, o3, o4); start_tangent = MapCoord(o3); end_tangent = MapCoord(o4); start_error_sq = start_coord.distanceSquaredTo(MapCoord(o2)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in increasing general case: " << sqrt(start_error_sq); } // Find better end point approximation and its tangent if ( second_last_info.second->param == 0.0 || (end_info.first && end_info.second->param == 0.0 && ( end_info.second->index == edge_start+3 || (end_info.second->index == end_info.first->first_index && end_info.first->last_index == edge_start+3) ) ) ) { // Take coordinates directly end_coord = original->getCoordinate(edge_start + 3); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in increasing direct case: " << sqrt(end_error_sq); } else { // Approximate coords const PathCoord* next_coord = second_last_info.second + 1; auto next_coord_param = next_coord->param; if (next_coord_param == 0.0) next_coord_param = 1.0; auto dx = end_point.X - second_last_point.X; auto dy = end_point.Y - second_last_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_end_param = (next_coord_param - second_last_info.second->param) * point_dist / qMax(1e-7f, (next_coord->clen - second_last_info.second->clen)); auto end_param = (second_last_info.second->param + delta_end_param - start_param) / (1.0 - start_param); MapCoordF o0, o1, o2, unused; PathCoord::splitBezierCurve(MapCoordF(start_coord), MapCoordF(start_tangent), MapCoordF(end_tangent), MapCoordF(original->getCoordinate(edge_start + 3)), end_param, o0, o1, o2, unused, unused); start_tangent = MapCoord(o0); end_tangent = MapCoord(o1); end_coord = MapCoord(o2); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in increasing general case: " << sqrt(end_error_sq); } } else // if (!sequence_increasing) { if ( second_info.second->param == 0.0 || ( start_info.first && start_info.second->param == 0.0 && ( start_info.second->index == edge_start+3 || (start_info.second->index == start_info.first->first_index && start_info.first->last_index == edge_start+3) ) ) ) { // Take coordinates directly start_tangent = original->getCoordinate(edge_start + 2); end_tangent = original->getCoordinate(edge_start + 1); start_error_sq = start_coord.distanceSquaredTo(original->getCoordinate(edge_start + 3)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in decreasing direct case: " << sqrt(start_error_sq); } else { // Approximate coords const PathCoord* next_coord = second_info.second + 1; auto next_coord_param = next_coord->param; if (next_coord_param == 0.0) next_coord_param = 1.0; auto dx = second_point.X - start_point.X; auto dy = second_point.Y - start_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_start_param = (next_coord_param - second_info.second->param) * point_dist / qMax(1e-7f, (next_coord->clen - second_info.second->clen)); start_param = qBound(0.0, 1.0 - second_info.second->param + delta_start_param, 1.0); MapCoordF unused, o2, o3, o4; PathCoord::splitBezierCurve(MapCoordF(original->getCoordinate(edge_start + 3)), MapCoordF(original->getCoordinate(edge_start + 2)), MapCoordF(original->getCoordinate(edge_start + 1)), MapCoordF(original->getCoordinate(edge_start + 0)), start_param, unused, unused, o2, o3, o4); start_tangent = MapCoord(o3); end_tangent = MapCoord(o4); start_error_sq = start_coord.distanceSquaredTo(MapCoord(o2)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in decreasing general case: " << sqrt(start_error_sq); } // Find better end point approximation and its tangent if ( second_last_info.second->param == 0.0 || ( end_info.first && end_info.second->param == 0.0 && ( end_info.second->index == edge_start || (end_info.second->index == end_info.first->last_index && end_info.first->first_index == edge_start) ) ) ) { // Take coordinates directly end_coord = original->getCoordinate(edge_start + 0); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in decreasing direct case: " << sqrt(end_error_sq); } else { // Approximate coords const PathCoord* prev_coord = second_last_info.second - 1; auto dx = end_point.X - second_last_point.X; auto dy = end_point.Y - second_last_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_end_param = (second_last_info.second->param - prev_coord->param) * point_dist / qMax(1e-7f, (second_last_info.second->clen - prev_coord->clen)); auto end_param = (1.0 - second_last_info.second->param + delta_end_param) / (1 - start_param); MapCoordF o0, o1, o2, unused; PathCoord::splitBezierCurve(MapCoordF(start_coord), MapCoordF(start_tangent), MapCoordF(end_tangent), MapCoordF(original->getCoordinate(edge_start + 0)), end_param, o0, o1, o2, unused, unused); start_tangent = MapCoord(o0); end_tangent = MapCoord(o1); end_coord = MapCoord(o2); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in decreasing general case: " << sqrt(end_error_sq); } } if (start_error_sq <= error_bound && end_error_sq <= error_bound) { // Rebuild bezier curve using information from original curve object->addCoordinate(start_tangent); object->addCoordinate(end_tangent); object->addCoordinate(resetCoordinate(end_coord)); } else { // Rebuild bezier curve approximately using tangents derived from result polygon rebuildSegmentFromPathOnly(start_point, second_point, second_last_point, end_point, object); } }
void BooleanTool::polygonToPathPart(const ClipperLib::Path& polygon, const PolyMap& polymap, PathObject* object) { auto num_points = polygon.size(); if (num_points < 3) return; // Index of first used point in polygon auto part_start_index = 0u; auto cur_info = PathCoordInfo{ nullptr, nullptr }; // Check if we can find either an unknown intersection point // or a path coord with parameter 0. // This gives a starting point to search for curves to rebuild // (because we cannot start in the middle of a curve) for (; part_start_index < num_points; ++part_start_index) { auto current_point = polygon.at(part_start_index); if (!polymap.contains(current_point)) break; if (polymap.value(current_point).second->param == 0.0) { cur_info = polymap.value(current_point); break; } } if (part_start_index == num_points) { // Did not find a valid starting point. Return the part as a polygon. for (auto i = 0u; i < num_points; ++i) object->addCoordinate(MapCoord(0.001 * polygon.at(i).X, 0.001 * polygon.at(i).Y), (i == 0)); object->parts().back().setClosed(true, true); return; } // Add the first point to the object rebuildCoordinate(part_start_index, polygon, polymap, object, true); // Index of first segment point in polygon auto segment_start_index = part_start_index; bool have_sequence = false; bool sequence_increasing = false; bool stop_before = false; // Advance along the boundary and rebuild the curve for every sequence // of path coord pointers with the same path and index. auto i = part_start_index; do { ++i; if (i >= num_points) i = 0; PathCoordInfo new_info{ nullptr, nullptr }; auto new_point = polygon.at(i); if (polymap.contains(new_point)) new_info = polymap.value(new_point); if (cur_info.first && cur_info.first == new_info.first) { // Same original part auto cur_coord_index = cur_info.second->index; MapCoord& cur_coord = cur_info.first->path->getCoordinate(cur_coord_index); auto new_coord_index = new_info.second->index; MapCoord& new_coord = new_info.first->path->getCoordinate(new_coord_index); auto cur_coord_index_adjusted = cur_coord_index; if (cur_coord_index_adjusted == new_info.first->first_index) cur_coord_index_adjusted = new_info.first->last_index; auto new_coord_index_adjusted = new_coord_index; if (new_coord_index_adjusted == new_info.first->first_index) new_coord_index_adjusted = new_info.first->last_index; if (cur_coord_index == new_coord_index) { // Somewhere on a curve bool param_increasing = new_info.second->param > cur_info.second->param; if (!have_sequence) { have_sequence = true; sequence_increasing = param_increasing; } else if (have_sequence && sequence_increasing != param_increasing) { stop_before = true; } } else if (new_info.second->param == 0.0 && ( (cur_coord.isCurveStart() && new_coord_index_adjusted == cur_coord_index + 3) || (!cur_coord.isCurveStart() && new_coord_index_adjusted == cur_coord_index + 1) ) ) { // Original curve is from cur_coord_index to new_coord_index_adjusted. if (!have_sequence) { have_sequence = true; sequence_increasing = true; } else { stop_before = !sequence_increasing; } } else if (cur_info.second->param == 0.0 && ( (new_coord.isCurveStart() && new_coord_index + 3 == cur_coord_index_adjusted) || (!new_coord.isCurveStart() && new_coord_index + 1 == cur_coord_index_adjusted) ) ) { // Original curve is from new_coord_index to cur_coord_index_adjusted. if (!have_sequence) { have_sequence = true; sequence_increasing = false; } else { stop_before = sequence_increasing; } } else if ((segment_start_index + 1) % num_points != i) { // Not immediately after segment_start_index stop_before = true; } } if (i == part_start_index || stop_before || (new_info.second && new_info.second->param == 0.0) || (cur_info.first && (cur_info.first != new_info.first || cur_info.second->index != new_info.second->index) && i != (segment_start_index + 1) % num_points) || !new_info.first) { if (stop_before) { if (i == 0) i = num_points - 1; else --i; } if (have_sequence) // A sequence of at least two points belonging to the same curve rebuildSegment(segment_start_index, i, sequence_increasing, polygon, polymap, object); else // A single straight edge rebuildCoordinate(i, polygon, polymap, object); if (stop_before) { ++i; if (i >= num_points) i = 0; rebuildCoordinate(i, polygon, polymap, object); stop_before = false; } segment_start_index = i; have_sequence = false; } cur_info = new_info; } while (i != part_start_index); object->parts().back().connectEnds(); }