| | |
| |
|
| | |
| |
|
| | |
| |
|
| | #include "Area.h" |
| | #include "clipper.hpp" |
| | using namespace ClipperLib; |
| |
|
| | #define TPolygon Path |
| | #define TPolyPolygon Paths |
| |
|
| | bool CArea::HolesLinked() |
| | { |
| | return false; |
| | } |
| |
|
| | |
| | double CArea::m_clipper_scale = 10000.0; |
| |
|
| | class DoubleAreaPoint |
| | { |
| | public: |
| | double X, Y; |
| |
|
| | DoubleAreaPoint(double x, double y) |
| | { |
| | X = x; |
| | Y = y; |
| | } |
| | DoubleAreaPoint(const IntPoint& p) |
| | { |
| | X = (double)(p.X) / CArea::m_clipper_scale; |
| | Y = (double)(p.Y) / CArea::m_clipper_scale; |
| | } |
| | IntPoint int_point() |
| | { |
| | return IntPoint((long64)(X * CArea::m_clipper_scale), (long64)(Y * CArea::m_clipper_scale)); |
| | } |
| | }; |
| |
|
| | static std::list<DoubleAreaPoint> pts_for_AddVertex; |
| |
|
| | static void AddPoint(const DoubleAreaPoint& p) |
| | { |
| | pts_for_AddVertex.push_back(p); |
| | } |
| |
|
| | static void AddVertex(const CVertex& vertex, const CVertex* prev_vertex) |
| | { |
| | if (vertex.m_type == 0 || prev_vertex == NULL) { |
| | AddPoint(DoubleAreaPoint(vertex.m_p.x * CArea::m_units, vertex.m_p.y * CArea::m_units)); |
| | } |
| | else { |
| | if (vertex.m_p != prev_vertex->m_p) { |
| | double phi, dphi, dx, dy; |
| | int Segments; |
| | int i; |
| | double ang1, ang2, phit; |
| |
|
| | dx = (prev_vertex->m_p.x - vertex.m_c.x) * CArea::m_units; |
| | dy = (prev_vertex->m_p.y - vertex.m_c.y) * CArea::m_units; |
| |
|
| | ang1 = atan2(dy, dx); |
| | if (ang1 < 0) { |
| | ang1 += 2.0 * PI; |
| | } |
| | dx = (vertex.m_p.x - vertex.m_c.x) * CArea::m_units; |
| | dy = (vertex.m_p.y - vertex.m_c.y) * CArea::m_units; |
| | ang2 = atan2(dy, dx); |
| | if (ang2 < 0) { |
| | ang2 += 2.0 * PI; |
| | } |
| |
|
| | if (vertex.m_type == -1) { |
| | if (ang2 > ang1) { |
| | phit = 2.0 * PI - ang2 + ang1; |
| | } |
| | else { |
| | phit = ang1 - ang2; |
| | } |
| | } |
| | else { |
| | if (ang1 > ang2) { |
| | phit = -(2.0 * PI - ang1 + ang2); |
| | } |
| | else { |
| | phit = -(ang2 - ang1); |
| | } |
| | } |
| |
|
| | |
| | double radius = sqrt(dx * dx + dy * dy); |
| | dphi = 2 * acos((radius - CArea::m_accuracy) / radius); |
| |
|
| | |
| | if (phit > 0) { |
| | Segments = (int)ceil(phit / dphi); |
| | } |
| | else { |
| | Segments = (int)ceil(-phit / dphi); |
| | } |
| |
|
| | if (Segments < CArea::m_min_arc_points) { |
| | Segments = CArea::m_min_arc_points; |
| | } |
| | |
| | |
| |
|
| | dphi = phit / (Segments); |
| |
|
| | double px = prev_vertex->m_p.x * CArea::m_units; |
| | double py = prev_vertex->m_p.y * CArea::m_units; |
| |
|
| | for (i = 1; i <= Segments; i++) { |
| | dx = px - vertex.m_c.x * CArea::m_units; |
| | dy = py - vertex.m_c.y * CArea::m_units; |
| | phi = atan2(dy, dx); |
| |
|
| | double nx = vertex.m_c.x * CArea::m_units + radius * cos(phi - dphi); |
| | double ny = vertex.m_c.y * CArea::m_units + radius * sin(phi - dphi); |
| |
|
| | AddPoint(DoubleAreaPoint(nx, ny)); |
| |
|
| | px = nx; |
| | py = ny; |
| | } |
| | } |
| | } |
| | } |
| |
|
| | static void MakeLoop( |
| | const DoubleAreaPoint& pt0, |
| | const DoubleAreaPoint& pt1, |
| | const DoubleAreaPoint& pt2, |
| | double radius |
| | ) |
| | { |
| | Point p0(pt0.X, pt0.Y); |
| | Point p1(pt1.X, pt1.Y); |
| | Point p2(pt2.X, pt2.Y); |
| | Point forward0 = p1 - p0; |
| | Point right0(forward0.y, -forward0.x); |
| | right0.normalize(); |
| | Point forward1 = p2 - p1; |
| | Point right1(forward1.y, -forward1.x); |
| | right1.normalize(); |
| |
|
| | int arc_dir = (radius > 0) ? 1 : -1; |
| |
|
| | CVertex v0(0, p1 + right0 * radius, Point(0, 0)); |
| | CVertex v1(arc_dir, p1 + right1 * radius, p1); |
| | CVertex v2(0, p2 + right1 * radius, Point(0, 0)); |
| |
|
| | double save_units = CArea::m_units; |
| | CArea::m_units = 1.0; |
| |
|
| | AddVertex(v1, &v0); |
| | AddVertex(v2, &v1); |
| |
|
| | CArea::m_units = save_units; |
| | } |
| |
|
| | static void OffsetWithLoops(const TPolyPolygon& pp, TPolyPolygon& pp_new, double inwards_value) |
| | { |
| | Clipper c; |
| | c.StrictlySimple(CArea::m_clipper_simple); |
| |
|
| | bool inwards = (inwards_value > 0); |
| | bool reverse = false; |
| | double radius = -fabs(inwards_value); |
| |
|
| | if (inwards) { |
| | |
| | TPolygon p; |
| | p.push_back(DoubleAreaPoint(-10000.0, -10000.0).int_point()); |
| | p.push_back(DoubleAreaPoint(-10000.0, 10000.0).int_point()); |
| | p.push_back(DoubleAreaPoint(10000.0, 10000.0).int_point()); |
| | p.push_back(DoubleAreaPoint(10000.0, -10000.0).int_point()); |
| | c.AddPath(p, ptSubject, true); |
| | } |
| | else { |
| | reverse = true; |
| | } |
| |
|
| | for (unsigned int i = 0; i < pp.size(); i++) { |
| | const TPolygon& p = pp[i]; |
| |
|
| | pts_for_AddVertex.clear(); |
| |
|
| | if (p.size() > 2) { |
| | if (reverse) { |
| | for (std::size_t j = p.size() - 1; j > 1; j--) { |
| | MakeLoop(p[j], p[j - 1], p[j - 2], radius); |
| | } |
| | MakeLoop(p[1], p[0], p[p.size() - 1], radius); |
| | MakeLoop(p[0], p[p.size() - 1], p[p.size() - 2], radius); |
| | } |
| | else { |
| | MakeLoop(p[p.size() - 2], p[p.size() - 1], p[0], radius); |
| | MakeLoop(p[p.size() - 1], p[0], p[1], radius); |
| | for (std::size_t j = 2; j < p.size(); j++) { |
| | MakeLoop(p[j - 2], p[j - 1], p[j], radius); |
| | } |
| | } |
| |
|
| | TPolygon loopy_polygon; |
| | loopy_polygon.reserve(pts_for_AddVertex.size()); |
| | for (std::list<DoubleAreaPoint>::iterator It = pts_for_AddVertex.begin(); |
| | It != pts_for_AddVertex.end(); |
| | It++) { |
| | loopy_polygon.push_back(It->int_point()); |
| | } |
| | c.AddPath(loopy_polygon, ptSubject, true); |
| | pts_for_AddVertex.clear(); |
| | } |
| | } |
| |
|
| | |
| | c.Execute(ctUnion, pp_new, pftNonZero, pftNonZero); |
| |
|
| | if (inwards) { |
| | |
| | if (pp_new.size() > 0) { |
| | pp_new.erase(pp_new.begin()); |
| | } |
| | } |
| | else { |
| | |
| | TPolyPolygon copy = pp_new; |
| | pp_new.clear(); |
| | pp_new.resize(copy.size()); |
| | for (unsigned int i = 0; i < copy.size(); i++) { |
| | const TPolygon& p = copy[i]; |
| | TPolygon p_new; |
| | p_new.resize(p.size()); |
| | std::size_t size_minus_one = p.size() - 1; |
| | for (std::size_t j = 0; j < p.size(); j++) { |
| | p_new[j] = p[size_minus_one - j]; |
| | } |
| | pp_new[i] = p_new; |
| | } |
| | } |
| | } |
| |
|
| | static void MakeObround(const Point& pt0, const CVertex& vt1, double radius) |
| | { |
| | Span span(pt0, vt1); |
| | Point forward0 = span.GetVector(0.0); |
| | Point forward1 = span.GetVector(1.0); |
| | Point right0(forward0.y, -forward0.x); |
| | Point right1(forward1.y, -forward1.x); |
| | right0.normalize(); |
| | right1.normalize(); |
| |
|
| | CVertex v0(pt0 + right0 * radius); |
| | CVertex v1(vt1.m_type, vt1.m_p + right1 * radius, vt1.m_c); |
| | CVertex v2(1, vt1.m_p + right1 * -radius, vt1.m_p); |
| | CVertex v3(-vt1.m_type, pt0 + right0 * -radius, vt1.m_c); |
| | CVertex v4(1, pt0 + right0 * radius, pt0); |
| |
|
| | double save_units = CArea::m_units; |
| | CArea::m_units = 1.0; |
| |
|
| | AddVertex(v0, NULL); |
| | AddVertex(v1, &v0); |
| | AddVertex(v2, &v1); |
| | AddVertex(v3, &v2); |
| | AddVertex(v4, &v3); |
| |
|
| | CArea::m_units = save_units; |
| | } |
| |
|
| | static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon& pp_new, double radius) |
| | { |
| | Clipper c; |
| | c.StrictlySimple(CArea::m_clipper_simple); |
| | pp_new.clear(); |
| |
|
| | for (std::list<CCurve>::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); |
| | It++) { |
| | c.Clear(); |
| | c.AddPaths(pp_new, ptSubject, true); |
| | pp_new.clear(); |
| | pts_for_AddVertex.clear(); |
| |
|
| | const CCurve& curve = *It; |
| | const CVertex* prev_vertex = NULL; |
| | for (std::list<CVertex>::const_iterator It2 = curve.m_vertices.begin(); |
| | It2 != curve.m_vertices.end(); |
| | It2++) { |
| | const CVertex& vertex = *It2; |
| | if (prev_vertex) { |
| | MakeObround(prev_vertex->m_p, vertex, radius); |
| |
|
| | TPolygon loopy_polygon; |
| | loopy_polygon.reserve(pts_for_AddVertex.size()); |
| | for (std::list<DoubleAreaPoint>::iterator It = pts_for_AddVertex.begin(); |
| | It != pts_for_AddVertex.end(); |
| | It++) { |
| | loopy_polygon.push_back(It->int_point()); |
| | } |
| | c.AddPath(loopy_polygon, ptSubject, true); |
| | pts_for_AddVertex.clear(); |
| | } |
| | prev_vertex = &vertex; |
| | } |
| | c.Execute(ctUnion, pp_new, pftNonZero, pftNonZero); |
| | } |
| |
|
| |
|
| | |
| | TPolyPolygon copy = pp_new; |
| | pp_new.clear(); |
| | pp_new.resize(copy.size()); |
| | for (unsigned int i = 0; i < copy.size(); i++) { |
| | const TPolygon& p = copy[i]; |
| | TPolygon p_new; |
| | p_new.resize(p.size()); |
| | std::size_t size_minus_one = p.size() - 1; |
| | for (std::size_t j = 0; j < p.size(); j++) { |
| | p_new[j] = p[size_minus_one - j]; |
| | } |
| | pp_new[i] = p_new; |
| | } |
| | } |
| |
|
| | static void MakePoly(const CCurve& curve, TPolygon& p, bool reverse = false) |
| | { |
| | pts_for_AddVertex.clear(); |
| | const CVertex* prev_vertex = NULL; |
| |
|
| | if (!curve.m_vertices.size()) { |
| | return; |
| | } |
| | if (!curve.IsClosed()) { |
| | AddVertex(curve.m_vertices.front(), NULL); |
| | } |
| |
|
| | for (std::list<CVertex>::const_iterator It2 = curve.m_vertices.begin(); |
| | It2 != curve.m_vertices.end(); |
| | It2++) { |
| | const CVertex& vertex = *It2; |
| | if (prev_vertex) { |
| | AddVertex(vertex, prev_vertex); |
| | } |
| | prev_vertex = &vertex; |
| | } |
| |
|
| | p.resize(pts_for_AddVertex.size()); |
| | if (reverse) { |
| | std::size_t i = pts_for_AddVertex.size() - 1; |
| | for (std::list<DoubleAreaPoint>::iterator It = pts_for_AddVertex.begin(); |
| | It != pts_for_AddVertex.end(); |
| | It++, i--) { |
| | p[i] = It->int_point(); |
| | } |
| | } |
| | else { |
| | unsigned int i = 0; |
| | for (std::list<DoubleAreaPoint>::iterator It = pts_for_AddVertex.begin(); |
| | It != pts_for_AddVertex.end(); |
| | It++, i++) { |
| | p[i] = It->int_point(); |
| | } |
| | } |
| | } |
| |
|
| | static void MakePolyPoly(const CArea& area, TPolyPolygon& pp, bool reverse = true) |
| | { |
| | pp.clear(); |
| |
|
| | for (std::list<CCurve>::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); |
| | It++) { |
| | pp.push_back(TPolygon()); |
| | MakePoly(*It, pp.back(), reverse); |
| | } |
| | } |
| |
|
| | static void SetFromResult(CCurve& curve, TPolygon& p, bool reverse = true, bool is_closed = true) |
| | { |
| | if (CArea::m_clipper_clean_distance >= Point::tolerance) { |
| | CleanPolygon(p, CArea::m_clipper_clean_distance); |
| | } |
| |
|
| | for (unsigned int j = 0; j < p.size(); j++) { |
| | const IntPoint& pt = p[j]; |
| | DoubleAreaPoint dp(pt); |
| | CVertex vertex(0, Point(dp.X / CArea::m_units, dp.Y / CArea::m_units), Point(0.0, 0.0)); |
| | if (reverse) { |
| | curve.m_vertices.push_front(vertex); |
| | } |
| | else { |
| | curve.m_vertices.push_back(vertex); |
| | } |
| | } |
| | if (is_closed) { |
| | |
| | if (reverse) { |
| | curve.m_vertices.push_front(curve.m_vertices.back()); |
| | } |
| | else { |
| | curve.m_vertices.push_back(curve.m_vertices.front()); |
| | } |
| | } |
| |
|
| | if (CArea::m_fit_arcs) { |
| | curve.FitArcs(); |
| | } |
| | } |
| |
|
| | static void SetFromResult( |
| | CArea& area, |
| | TPolyPolygon& pp, |
| | bool reverse = true, |
| | bool is_closed = true, |
| | bool clear = true |
| | ) |
| | { |
| | |
| | if (clear) { |
| | area.m_curves.clear(); |
| | } |
| |
|
| | for (unsigned int i = 0; i < pp.size(); i++) { |
| | TPolygon& p = pp[i]; |
| |
|
| | area.m_curves.emplace_back(); |
| | CCurve& curve = area.m_curves.back(); |
| | SetFromResult(curve, p, reverse, is_closed); |
| | } |
| | } |
| |
|
| | void CArea::Subtract(const CArea& a2) |
| | { |
| | Clipper c; |
| | c.StrictlySimple(CArea::m_clipper_simple); |
| | TPolyPolygon pp1, pp2; |
| | MakePolyPoly(*this, pp1); |
| | MakePolyPoly(a2, pp2); |
| | c.AddPaths(pp1, ptSubject, true); |
| | c.AddPaths(pp2, ptClip, true); |
| | TPolyPolygon solution; |
| | c.Execute(ctDifference, solution); |
| | SetFromResult(*this, solution); |
| | } |
| |
|
| | void CArea::Intersect(const CArea& a2) |
| | { |
| | Clipper c; |
| | c.StrictlySimple(CArea::m_clipper_simple); |
| | TPolyPolygon pp1, pp2; |
| | MakePolyPoly(*this, pp1); |
| | MakePolyPoly(a2, pp2); |
| | c.AddPaths(pp1, ptSubject, true); |
| | c.AddPaths(pp2, ptClip, true); |
| | TPolyPolygon solution; |
| | c.Execute(ctIntersection, solution); |
| | SetFromResult(*this, solution); |
| | } |
| |
|
| | void CArea::Union(const CArea& a2) |
| | { |
| | Clipper c; |
| | c.StrictlySimple(CArea::m_clipper_simple); |
| | TPolyPolygon pp1, pp2; |
| | MakePolyPoly(*this, pp1); |
| | MakePolyPoly(a2, pp2); |
| | c.AddPaths(pp1, ptSubject, true); |
| | c.AddPaths(pp2, ptClip, true); |
| | TPolyPolygon solution; |
| | c.Execute(ctUnion, solution); |
| | SetFromResult(*this, solution); |
| | } |
| |
|
| | |
| | CArea CArea::UniteCurves(std::list<CCurve>& curves) |
| | { |
| | Clipper c; |
| | c.StrictlySimple(CArea::m_clipper_simple); |
| |
|
| | TPolyPolygon pp; |
| |
|
| | for (std::list<CCurve>::iterator It = curves.begin(); It != curves.end(); It++) { |
| | CCurve& curve = *It; |
| | TPolygon p; |
| | MakePoly(curve, p); |
| | pp.push_back(p); |
| | } |
| |
|
| | c.AddPaths(pp, ptSubject, true); |
| | TPolyPolygon solution; |
| | c.Execute(ctUnion, solution, pftNonZero, pftNonZero); |
| | CArea area; |
| | SetFromResult(area, solution); |
| | return area; |
| | } |
| |
|
| | void CArea::Xor(const CArea& a2) |
| | { |
| | Clipper c; |
| | c.StrictlySimple(CArea::m_clipper_simple); |
| | TPolyPolygon pp1, pp2; |
| | MakePolyPoly(*this, pp1); |
| | MakePolyPoly(a2, pp2); |
| | c.AddPaths(pp1, ptSubject, true); |
| | c.AddPaths(pp2, ptClip, true); |
| | TPolyPolygon solution; |
| | c.Execute(ctXor, solution); |
| | SetFromResult(*this, solution); |
| | } |
| |
|
| | void CArea::Offset(double inwards_value) |
| | { |
| | TPolyPolygon pp, pp2; |
| | MakePolyPoly(*this, pp, false); |
| | OffsetWithLoops(pp, pp2, inwards_value * m_units); |
| | SetFromResult(*this, pp2, false); |
| | this->Reorder(); |
| | } |
| |
|
| | void CArea::PopulateClipper(Clipper& c, PolyType type) const |
| | { |
| | int skipped = 0; |
| | for (std::list<CCurve>::const_iterator It = m_curves.begin(); It != m_curves.end(); It++) { |
| | const CCurve& curve = *It; |
| | bool closed = curve.IsClosed(); |
| | if (!closed) { |
| | if (type == ptClip) { |
| | ++skipped; |
| | continue; |
| | } |
| | } |
| | TPolygon p; |
| | MakePoly(curve, p, false); |
| | c.AddPath(p, type, closed); |
| | } |
| | if (skipped) { |
| | std::cout << "libarea: warning skipped " << skipped << " open wires" << std::endl; |
| | } |
| | } |
| |
|
| | void CArea::Clip(ClipType op, const CArea* a, PolyFillType subjFillType, PolyFillType clipFillType) |
| | { |
| | Clipper c; |
| | c.StrictlySimple(CArea::m_clipper_simple); |
| | PopulateClipper(c, ptSubject); |
| | if (a) { |
| | a->PopulateClipper(c, ptClip); |
| | } |
| | PolyTree tree; |
| | c.Execute(op, tree, subjFillType, clipFillType); |
| | TPolyPolygon solution; |
| | ClosedPathsFromPolyTree(tree, solution); |
| | SetFromResult(*this, solution); |
| | solution.clear(); |
| | OpenPathsFromPolyTree(tree, solution); |
| | SetFromResult(*this, solution, false, false, false); |
| | } |
| |
|
| | void CArea::OffsetWithClipper( |
| | double offset, |
| | JoinType joinType , |
| | EndType endType , |
| | double miterLimit , |
| | double roundPrecision |
| | ) |
| | { |
| | offset *= m_units * m_clipper_scale; |
| | if (roundPrecision == 0.0) { |
| | |
| | double dphi = acos(1.0 - m_accuracy * m_clipper_scale / fabs(offset)); |
| | int Segments = (int)ceil(PI / dphi); |
| | if (Segments < 2 * CArea::m_min_arc_points) { |
| | Segments = 2 * CArea::m_min_arc_points; |
| | } |
| | |
| | |
| | dphi = PI / Segments; |
| | roundPrecision = (1.0 - cos(dphi)) * fabs(offset); |
| | } |
| | else { |
| | roundPrecision *= m_clipper_scale; |
| | } |
| |
|
| | ClipperOffset clipper(miterLimit, roundPrecision); |
| | TPolyPolygon pp, pp2; |
| | MakePolyPoly(*this, pp, false); |
| | int i = 0; |
| | for (const CCurve& c : m_curves) { |
| | clipper.AddPath(pp[i++], joinType, c.IsClosed() ? etClosedPolygon : endType); |
| | } |
| | clipper.Execute(pp2, (long64)(offset)); |
| | SetFromResult(*this, pp2, false); |
| | this->Reorder(); |
| | } |
| |
|
| | void CArea::Thicken(double value) |
| | { |
| | TPolyPolygon pp; |
| | OffsetSpansWithObrounds(*this, pp, value * m_units); |
| | SetFromResult(*this, pp, false); |
| | this->Reorder(); |
| | } |
| |
|
| | void UnFitArcs(CCurve& curve) |
| | { |
| | pts_for_AddVertex.clear(); |
| | const CVertex* prev_vertex = NULL; |
| | for (std::list<CVertex>::const_iterator It2 = curve.m_vertices.begin(); |
| | It2 != curve.m_vertices.end(); |
| | It2++) { |
| | const CVertex& vertex = *It2; |
| | AddVertex(vertex, prev_vertex); |
| | prev_vertex = &vertex; |
| | } |
| |
|
| | curve.m_vertices.clear(); |
| |
|
| | for (std::list<DoubleAreaPoint>::iterator It = pts_for_AddVertex.begin(); |
| | It != pts_for_AddVertex.end(); |
| | It++) { |
| | DoubleAreaPoint& pt = *It; |
| | CVertex vertex(0, Point(pt.X / CArea::m_units, pt.Y / CArea::m_units), Point(0.0, 0.0)); |
| | curve.m_vertices.push_back(vertex); |
| | } |
| | } |
| |
|