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

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;
};