#pragma once #include "stdafx.h" #include "ItemEraser.h" #include "SigmaDoc.h" #include "ActionListItem.h" #include "ActionBackupItem.h" #include "Util.h" #include enum class EraserMode { Normal, Segments, Nodes, Relink, }; class ICurveEraseStrategy { public: virtual ~ICurveEraseStrategy() = default; virtual void SetContext(CXy* pXy, CPositionList* pPlDel, CPositionList* pPlAdd) = 0; virtual void Erase(POSITION pos, const CCurveEx& cutter) = 0; }; class RelinkEraseStrategy : public ICurveEraseStrategy { public: void SetContext(CXy* pXy, CPositionList* pPlDel, CPositionList* pPlAdd) override { m_pXy = pXy; m_plDel = pPlDel; m_plAdd = pPlAdd; } // 由于这个操作实际上只需要两个点,我们这里要求传入的 cutter 只有两个点,而且这两个点要和被切割的线重合 void Erase(POSITION pos, const CCurveEx& cutter) override { COne* pOne = m_pXy->GetAt(pos); CCurveEx* pCurve = pOne->GetValueSafe(); std::vector idxs; for (int32_t i = 0; i < pCurve->num; ++i) { if (IsPointInPolygon(pCurve->x[i], pCurve->y[i], cutter)) { idxs.push_back(i); if (idxs.size() == 2) { break; } } } if (idxs.size() >= 2) { SplitClosedPath(pCurve, pos, idxs[0], idxs[1]); } } private: /** * @brief 从一个闭合路径(环形)上切出两个顶点,将路径分成两段, * 并且分别将这两段的两端连接起来,形成两个新的闭合路径。 * * 举例: * 原路径(闭合):P0 → P1 → P2 → P3 → P4 → P5 → P6 → P0 * 切点:idx1 = 2 (P2), idx2 = 5 (P5) * * 结果: * 段A:P2 → P3 → P4 → P5 → P2 (闭合) * 段B:P5 → P6 → P0 → P1 → P2 → P5 (闭合) * * @param pCurve 原始闭合路径(顶点顺序表示环) * @param pos pCurve 的 POSITION * @param idx1 第一个切割点索引(必须在顶点上) * @param idx2 第二个切割点索引(必须在顶点上) * @return */ void SplitClosedPath(CCurveEx *pCurve, POSITION pos, int idx1, int idx2) { int n = pCurve->num; if (idx1 == idx2 || n < 3) { throw std::runtime_error("Invalid cut positions"); } if (idx1 > idx2) { std::swap(idx1, idx2); } // 段A: idx1 → idx2 std::vector partA; for (int i = idx1; i <= idx2; ++i) { partA.push_back({ pCurve->x[i], pCurve->y[i] }); } partA.push_back({ pCurve->x[idx1], pCurve->y[idx1] }); // 闭合 // 段B: idx2 → idx1(绕回) std::vector partB; for (int i = idx2; i < n; ++i) { partB.push_back({ pCurve->x[i], pCurve->y[i] }); } for (int i = 0; i <= idx1; ++i) { partB.push_back({ pCurve->x[i], pCurve->y[i] }); } partB.push_back({ pCurve->x[idx2], pCurve->y[idx2] }); // 闭合 m_plDel->AddTail(pos); COne* pOne = m_pXy->GetAt(pos); assert(pOne != nullptr); AddCurve(pOne, MakeCurveEx(partA)); AddCurve(pOne, MakeCurveEx(partB)); } std::unique_ptr MakeCurveEx(const std::vector& points) { auto pCurve = std::make_unique(); pCurve->Create(static_cast(points.size())); for (size_t i = 0; i < points.size(); i++) { pCurve->x[i] = points[i].x0; pCurve->y[i] = points[i].y0; } pCurve->GetLocation(); return pCurve; } void AddCurve(COne* pOldOne, std::unique_ptr pCurve) { auto pNewOne = std::make_unique(); CLayer* pLayer = pOldOne->GetLayer(); pNewOne->SetLayer(pLayer); pNewOne->SetValueSafe(pCurve.release()); pNewOne->CloneOtherParameter(*pOldOne); POSITION newPos = m_pXy->AddTailOne(pNewOne.release()); m_plAdd->AddTail(newPos); } CXy* m_pXy = nullptr; CPositionList* m_plDel = nullptr; CPositionList* m_plAdd = nullptr; }; class NormalEraseStrategy : public ICurveEraseStrategy { public: void SetContext(CXy* pXy, CPositionList* pPlDel, CPositionList* pPlAdd) override { m_pXy = pXy; m_plDel = pPlDel; m_plAdd = pPlAdd; } void Erase(POSITION pos, const CCurveEx& cutter) override { const auto erasePolygon = CurveToPathD(cutter); if (erasePolygon.size() < 3) { return; } CRect8 range(1e100, -1e100, -1e100, 1e100); const_cast(cutter).GetRange(range); COne* pOne = m_pXy->GetAt(pos); CCurveEx* pCurve = pOne->GetValueSafe(); if (!pCurve) { return; } CRect8 currRange(1e100, -1e100, -1e100, 1e100); pCurve->GetRange(currRange); if (!IsIntersecting(range, currRange)) { return; } auto sourcePath = CurveToPathD(*pCurve); // 如果完全包含,则只需要将这条曲线直接删除 if (IsPathInsidePolygon(sourcePath, erasePolygon)) { m_plDel->AddTail(pos); return; } const Clipper2Lib::PathsD subject{ sourcePath }; const Clipper2Lib::PathsD clip{ erasePolygon }; Clipper2Lib::ClipperD clipper; clipper.AddOpenSubject(subject); clipper.AddClip(clip); Clipper2Lib::PathsD closed_paths; Clipper2Lib::PathsD open_paths; clipper.Execute(Clipper2Lib::ClipType::Difference, Clipper2Lib::FillRule::NonZero, closed_paths, open_paths); // FIXME: 我不知道能不能用这个判断是否进行了切割,先暂时这么处理 if (open_paths.empty()) { return; } m_plDel->AddTail(pos); for (const auto& path : open_paths) { if (path.size() < 2) continue; auto newCurve = std::make_unique(); newCurve->Create(static_cast(path.size())); for (size_t i = 0; i < path.size(); ++i) { newCurve->x[i] = path[i].x; newCurve->y[i] = path[i].y; } newCurve->GetLocation(); POSITION newPos = m_pXy->AddElement(newCurve.release(), DOUBLEFOX_CURVE); COne* pNewOne = m_pXy->GetAt(newPos); pNewOne->SetLayer(pOne->GetLayer()); pNewOne->CloneOtherParameter(*pOne); m_plAdd->AddTail(newPos); } } private: Clipper2Lib::PathD CurveToPathD(const CCurveEx& curve) const { Clipper2Lib::PathD path; for (int32_t i = 0; i < curve.num; i++) { double x = curve.x[i]; double y = curve.y[i]; path.emplace_back(x, y); } return path; } bool IsPathInsidePolygon(const Clipper2Lib::PathD& path, const Clipper2Lib::PathD& polygon) { if (polygon.empty()) { return false; } const auto& clip = polygon; for (const auto& pt : path) { if (Clipper2Lib::PointInPolygon(pt, clip) != Clipper2Lib::PointInPolygonResult::IsInside) { return false; // 只要有一个点不在多边形内,就认为不完全包含 } } return true; } CXy* m_pXy = nullptr; CPositionList* m_plDel = nullptr; CPositionList* m_plAdd = nullptr; }; class SegmentEraseStrategy : public ICurveEraseStrategy { public: void SetContext(CXy* pXy, CPositionList* pPlDel, CPositionList* pPlAdd) override { m_pXy = pXy; m_plDel = pPlDel; m_plAdd = pPlAdd; } void Erase(POSITION pos, const CCurveEx& cutter) override { CRect8 range(1e100, -1e100, -1e100, 1e100); const_cast(cutter).GetRange(range); COne* pOne = m_pXy->GetAt(pos); CCurveEx* pCurve = pOne->GetValueSafe(); if (pCurve == nullptr) { return; } CRect8 currRange(1e100, -1e100, -1e100, 1e100); pCurve->GetRange(currRange); if (!IsIntersecting(range, currRange)) { return; } if (IsInRect(currRange, range)) { m_plDel->AddTail(pos); return; } DoCut(pCurve, cutter, pos, pOne); } private: void DoCut(CCurveEx* pOriginCurve, const CCurveEx& cutter, POSITION pos, COne* pOne) { std::vector segmentsIntersection; for (int32_t i = 0; i < pOriginCurve->num - 1; i++) { double p0_x = pOriginCurve->x[i], p0_y = pOriginCurve->y[i]; double p1_x = pOriginCurve->x[i + 1], p1_y = pOriginCurve->y[i + 1]; bool result = IsPointInPolygon(p0_x, p0_y, cutter) && IsPointInPolygon(p1_x, p1_y, cutter); if (!result) result = IsIntersection(p0_x, p0_y, p1_x, p1_y, cutter); segmentsIntersection.push_back(result ? 1 : 0); } if (NoneOf(segmentsIntersection, [](int32_t n) { return n == 1; })) { return; } auto ranges = GetSplitIndexs(segmentsIntersection, 1); for (const auto& vec : ranges) { auto newCurve = SliceCurve(*pOriginCurve, vec[0], *(vec.end() - 1) + 2); POSITION newPos = m_pXy->AddElement(newCurve.release(), DOUBLEFOX_CURVE); COne* pNewOne = m_pXy->GetAt(newPos); pNewOne->SetLayer(pOne->GetLayer()); pNewOne->CloneOtherParameter(*pOne); pNewOne->SetName(pOne->GetName()); m_plAdd->AddTail(newPos); } m_plDel->AddTail(pos); } std::unique_ptr SliceCurve(const CCurveEx& curve, int32_t startIndex, int32_t endIndex) const { auto newCurve = std::make_unique(); int32_t count = endIndex - startIndex; newCurve->Create(count); memcpy(newCurve->x, curve.x + startIndex, count * sizeof(double)); memcpy(newCurve->y, curve.y + startIndex, count * sizeof(double)); newCurve->GetLocation(); return newCurve; } bool IsIntersection(double x1, double y1, double x2, double y2, const CCurveEx& curve) const { for (int32_t i = 0; i < curve.num - 1; i++) { double x3 = curve.x[i], y3 = curve.y[i]; double x4 = curve.x[i + 1], y4 = curve.y[i + 1]; double ix = 0.0, iy = 0.0; if (GetLineIntersection(x1, y1, x2, y2, x3, y3, x4, y4, ix, iy) > 0) { return true; } } return false; } std::vector> GetSplitIndexs(const std::vector& vec, int32_t sep) { std::vector> result, curr; std::vector temp; for (size_t i = 0; i < vec.size(); i++) { if (vec[i] == sep) { if (!temp.empty()) { result.push_back(temp); temp.clear(); } } else { temp.push_back((int32_t)i); } } if (!temp.empty()) { result.push_back(temp); } return result; } CXy* m_pXy = nullptr; CPositionList* m_plDel = nullptr; CPositionList* m_plAdd = nullptr; }; class NodeEraseStrategy : public ICurveEraseStrategy { public: void SetContext(CXy* pXy, CPositionList* pPlDel, CPositionList* pPlAdd) override { m_pXy = pXy; m_plDel = pPlDel; m_plAdd = pPlAdd; } void Erase(POSITION pos, const CCurveEx& cutter) override { CRect8 range(1e100, -1e100, -1e100, 1e100); const_cast(cutter).GetRange(range); COne* pOne = m_pXy->GetAt(pos); CCurveEx* pCurve = pOne->GetValueSafe(); if (!pCurve) { return; } CRect8 currRange(1e100, -1e100, -1e100, 1e100); pCurve->GetRange(currRange); if (!IsIntersecting(range, currRange)) { return; } std::vector toRemove; for (int32_t i = 0; i < pCurve->num; ++i) { if (IsPointInPolygon(pCurve->x[i], pCurve->y[i], cutter)) { toRemove.push_back(i); } } if (toRemove.empty()) { return; } auto pNewCurve = std::make_unique(*pCurve); for (auto it = toRemove.rbegin(); it != toRemove.rend(); ++it) { RemoveCurveIndex(*pNewCurve, *it); } m_plDel->AddTail(pos); if (pNewCurve->num >= 2) { POSITION newPos = m_pXy->AddElement(pNewCurve.release(), DOUBLEFOX_CURVE); COne* pNewOne = m_pXy->GetAt(newPos); pNewOne->SetLayer(pOne->GetLayer()); pNewOne->CloneOtherParameter(*pOne); m_plAdd->AddTail(newPos); } } private: CXy* m_pXy = nullptr; CPositionList* m_plDel = nullptr; CPositionList* m_plAdd = nullptr; }; class Eraser { public: Eraser() { SetMode(EraserMode::Normal); } void SetXY(CXy* pXy) { m_pXy = pXy; } void Cut(const CCurveEx& curve) { m_plAdd.RemoveAll(); m_plDel.RemoveAll(); if (!m_pXy) { return; } CXyElementFilter filter; filter.addType(DOUBLEFOX_CURVE); CPositionList select; m_pXy->GetElement(filter, select); m_strategy->SetContext(m_pXy, &m_plDel, &m_plAdd); for (POSITION pos : CListToStdVector(select)) { m_strategy->Erase(pos, curve); } } void SetMode(EraserMode mode) { if (mode == EraserMode::Normal && dynamic_cast(m_strategy.get()) == nullptr) { m_strategy = std::make_unique(); } else if (mode == EraserMode::Nodes && dynamic_cast(m_strategy.get()) == nullptr) { m_strategy = std::make_unique(); } else if (mode == EraserMode::Segments && dynamic_cast(m_strategy.get()) == nullptr) { m_strategy = std::make_unique(); } else if (mode == EraserMode::Relink && dynamic_cast(m_strategy.get()) == nullptr) { m_strategy = std::make_unique(); } m_mode = mode; } EraserMode GetMode() const { return m_mode; } CPositionList m_plDel; CPositionList m_plAdd; private: EraserMode m_mode; CXy* m_pXy = nullptr; std::unique_ptr m_strategy; };