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.
415 lines
11 KiB
C++
415 lines
11 KiB
C++
#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,
|
|
};
|
|
|
|
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 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>();
|
|
}
|
|
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;
|
|
};
|
|
|