You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kev/Drawer/Module/GeoSigmaDraw/IntersectionEraser.h

550 lines
14 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#pragma once
#include "stdafx.h"
#include "ItemEraser.h"
#include "SigmaDoc.h"
#include "ActionListItem.h"
#include "ActionBackupItem.h"
#include "Util.h"
#include <clipper2/clipper.h>
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<CCurveEx>();
std::vector<int> 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)
*
* 结果:
* 段AP2 → P3 → P4 → P5 → P2 (闭合)
* 段BP5 → 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<CPoint2D> 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<CPoint2D> 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<CCurveEx> MakeCurveEx(const std::vector<CPoint2D>& points)
{
auto pCurve = std::make_unique<CCurveEx>();
pCurve->Create(static_cast<int>(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<CCurveEx> pCurve)
{
auto pNewOne = std::make_unique<COne>();
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<CCurveEx&>(cutter).GetRange(range);
COne* pOne = m_pXy->GetAt(pos);
CCurveEx* pCurve = pOne->GetValueSafe<CCurveEx>();
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<CCurveEx>();
newCurve->Create(static_cast<int>(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<CCurveEx&>(cutter).GetRange(range);
COne* pOne = m_pXy->GetAt(pos);
CCurveEx* pCurve = pOne->GetValueSafe<CCurveEx>();
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<int32_t> 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<CCurveEx> SliceCurve(const CCurveEx& curve, int32_t startIndex, int32_t endIndex) const
{
auto newCurve = std::make_unique<CCurveEx>();
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<std::vector<int32_t>> GetSplitIndexs(const std::vector<int32_t>& vec, int32_t sep)
{
std::vector<std::vector<int32_t>> result, curr;
std::vector<int32_t> 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<CCurveEx&>(cutter).GetRange(range);
COne* pOne = m_pXy->GetAt(pos);
CCurveEx* pCurve = pOne->GetValueSafe<CCurveEx>();
if (!pCurve)
{
return;
}
CRect8 currRange(1e100, -1e100, -1e100, 1e100);
pCurve->GetRange(currRange);
if (!IsIntersecting(range, currRange))
{
return;
}
std::vector<int32_t> 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<CCurveEx>(*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<NormalEraseStrategy*>(m_strategy.get()) == nullptr)
{
m_strategy = std::make_unique<NormalEraseStrategy>();
}
else if (mode == EraserMode::Nodes && dynamic_cast<NodeEraseStrategy*>(m_strategy.get()) == nullptr)
{
m_strategy = std::make_unique<NodeEraseStrategy>();
}
else if (mode == EraserMode::Segments && dynamic_cast<SegmentEraseStrategy*>(m_strategy.get()) == nullptr)
{
m_strategy = std::make_unique<SegmentEraseStrategy>();
}
else if (mode == EraserMode::Relink && dynamic_cast<RelinkEraseStrategy*>(m_strategy.get()) == nullptr)
{
m_strategy = std::make_unique<RelinkEraseStrategy>();
}
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<ICurveEraseStrategy> m_strategy;
};