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/CurveEditorDefault.cpp

1219 lines
28 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.

#include "StdAfx.h"
#include "CurveEditorDefault.h"
#include "SigmaDoc.h"
#include "SigmaView.h"
#include "ActionModifiedItem.h"
#include "ActionBackupItem.h"
#include "DrawOperator/TypeDefine.h"
#include "ActionCurveEdit.h"
#include "ActionCurveEditAddPoint.h"
#include "ActionCurveEditDeletePoint.h"
#include "HandleDrawer.h"
#include "SegmentSnappingEngine.h"
#include "Util.h"
void CurveEditorDefault::OnDraw(CXyDC* pDC)
{
POSITION pos = GetPos();
if (pos == 0)
return;
// 调用子类实现的扩展点
CCurveEx* pCurve = (CCurveEx*)GetDoc()->GetDraw()->GetAtValue(GetPos());
}
void CurveEditorDefault::DrawAssistant(CDC* pDC, int mouseX, int mouseY)
{
CCurveEx* pCurve = GetControlCurve();
if (pCurve == nullptr) return;
m_handleDrawer->Init(pCurve);
m_handleDrawer->DrawAll(pDC);
}
void CurveEditorDefault::OnLButtonDown(CDC* pDC, UINT nFlags, CPoint point, int vk)
{
SetScreenDC(pDC);
m_nDragSegmentIndex = -1; // 重置线段拖动索引
// 检查:如果没有选中节点,检查是否可以拖动线段
if (this->GetHandleIndex() < 0)
{
// 检查是否处于线段拖动模式
if (GetDoc()->GetEditLineStatus() == CURVE_STATE_DRAG_SEGMENT)
{
int segIndex = HitTestSegment(point);
if (segIndex >= 0)
{
// 进入线段拖动模式
m_nDragSegmentIndex = segIndex;
m_nLButtonDownPoint = point;
c_down = GetDC()->GetReal(point);
c_last = c_down;
DrawMoveSegment(pDC); // 绘制初始状态
return;
}
}
if (!IsCanAddHandle(point))
return;
}
// 清空上次的临时点列表
if (NewPointList.GetCount() > 0)
NewPointList.RemoveAll();
// 记录鼠标按下位置(屏幕坐标和世界坐标)
m_nLButtonDownPoint = point;
c_down = GetDC()->GetReal(point);
c_last = c_down;
// 调用扩展点:绘制预览
OnDrawDragPreview();
}
int CurveEditorDefault::OnMouseMove(CDC* dc, UINT nFlags, CPoint point)
{
SetScreenDC(dc);
POSITION pos = GetPos();
if (pos == 0)
return 1;
CXyDC* pDC = GetDC();
if (IsDragging(nFlags)) // 鼠标左键按下
{
HandleMouseDrag(pDC, point);
}
else
{
HandleMouseHover(dc, point);
}
m_handleDrawer->DrawFocusHandle(dc, GetHandleIndex());
return 1;
}
void CurveEditorDefault::OnLButtonUp(CDC* pDC, UINT nFlags, CPoint point, int vk)
{
SetScreenDC(pDC);
if (m_bLButtonDownWhenMouseMove == false)
return;
m_bLButtonDownWhenMouseMove = false;
// 处理线段拖动结束
if (m_nDragSegmentIndex >= 0)
{
// 1. [擦除] 使用上一次的 c_last 擦除屏幕上的虚影
DrawMoveSegment(pDC);
NBase::CPoint2D rawMouse = GetDC()->GetReal(point);
c_last = CalculateSnappedMousePos(rawMouse, m_nDragSegmentIndex);
CCurveEx* pValue = GetControlCurve();
int i = m_nDragSegmentIndex;
// 安全检查
if (i < 0 || i >= pValue->num - 1)
{
m_nDragSegmentIndex = -1;
ReleaseCapture();
return;
}
// 3. [Undo/Redo 准备]
CActionCurveEdit* pAction = new CActionCurveEdit(GetDoc(), 0, GetPos());
// 备份移动前的节点位置
pAction->BackupUndoNode(i, pValue->x[i], pValue->y[i]);
pAction->BackupUndoNode(i + 1, pValue->x[i + 1], pValue->y[i + 1]);
if (m_lastSnapResult.snapType == Geometry::SnapType::PARALLEL_LINE)
{
// 策略 A平行吸附 -> 强制共线 (Projection)
const Geometry::Segment& targetSeg = m_lastSnapResult.targetSegment;
// 构造当前点 (转换类型)
Geometry::Point pStart = { pValue->x[i], pValue->y[i] };
Geometry::Point pEnd = { pValue->x[i + 1], pValue->y[i + 1] };
// 计算投影因子 t (相对于目标线段)
double t1 = Geometry::GeometryUtils::getProjectionFactor(pStart, targetSeg);
double t2 = Geometry::GeometryUtils::getProjectionFactor(pEnd, targetSeg);
Geometry::Point dir = targetSeg.direction();
// 计算投影后的新坐标 (Start + t * Dir)
Geometry::Point newStart = targetSeg.start + (dir * t1);
Geometry::Point newEnd = targetSeg.start + (dir * t2);
// 直接赋值新坐标,修正微小的角度偏差
pValue->x[i] = newStart.x;
pValue->y[i] = newStart.y;
pValue->x[i + 1] = newEnd.x;
pValue->y[i + 1] = newEnd.y;
}
else
{
// 策略 B普通移动 -> 刚体平移 (Rigid Translation)
dfPoint pt1, pt2;
GetXY(pValue, i, pt1);
GetXY(pValue, i + 1, pt2);
double mouseDx = c_last.x0 - c_down.x0;
double mouseDy = c_last.y0 - c_down.y0;
// 计算沿法线的偏移量
NBase::CPoint2D offset = CalculateNormalOffset(pt1, pt2, mouseDx, mouseDy);
pValue->x[i] += offset.x0;
pValue->y[i] += offset.y0;
pValue->x[i + 1] += offset.x0;
pValue->y[i + 1] += offset.y0;
}
if (pValue->bAutoLocation)
pValue->GetLocation();
// 6. [Redo 记录]
pAction->BackupRedoNode(i, pValue->x[i], pValue->y[i]);
pAction->BackupRedoNode(i + 1, pValue->x[i + 1], pValue->y[i + 1]);
GetDoc()->SetActionItem(pAction);
// 7. [收尾工作]
SetModifiedFlag(TRUE);
GetDoc()->Modified();
m_nDragSegmentIndex = -1;
ReleaseCapture();
// 刷新显示
Invalidate();
return;
}
if (this->GetHandleIndex() < 0)
{
CPoint2D pt = GetDC()->GetReal(point);
POSITION pos = GetDoc()->GetSelectedItem(pt);
if (pos == GetPos()) return;
EndEdit();
return;
}
c_last = GetDC()->GetReal(point);
// 擦除预览线
OnDrawDragPreview();
if (c_last == c_down)
return;
// 应用修改
OnCalculateDragEffect(nullptr);
SetModifiedFlag(TRUE);
GetDoc()->Modified();
SetHandleIndex(-1);
ReleaseCapture();
}
void CurveEditorDefault::OnLButtonDblClk(UINT nFlags, CPoint point)
{
POSITION pos = GetPos();
if (pos == nullptr) return;
COne* pOne = GetDoc()->GetDraw()->GetAt(pos);
if (pOne == nullptr) return;
std::unique_ptr<CActionModifiedItem> pAction = std::make_unique<CActionModifiedItem>(GetDoc(), 0);
pAction->BackupOldItem(pos, pOne);
if (GetHandleIndex() >= 0)
{
if (IsCaptureState()) ReleaseCapture();
int index = GetHandleIndex();
CCurveEx* pcurve = GetControlCurve();
CRect8 range(1e300, -1e300, -1e300, 1e300);
for (int i = index - 1; i <= index + 1; i++)
{
if (i < 0 || i > pcurve->num - 1) continue;
range.CombinRect(pcurve->x[i], pcurve->y[i], pcurve->x[i], pcurve->y[i]);
}
DeleteHandle(GetHandleIndex());
SetHandleIndex(-1);
CRect rt = GetDC()->GetScreen(range);
CSize8 sz = GetDoc()->GetSelectSize();
CSize size((int)sz.cx + 1, (int)sz.cy + 1);
rt.InflateRect(size);
}
else if (IsCanAddHandle(point))
{
int ret = AddHandle(point);
if (ret < 0) return;
}
pAction->BackupNewItem();
GetDoc()->SetActionItem(pAction.release());
GetDoc()->Modified();
if (GetDoc()->GetItem()->GetType() == ITEM_CURVE_EDIT)
{
EndEdit();
}
}
BOOL CurveEditorDefault::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
return TRUE;
}
BOOL CurveEditorDefault::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
switch (nChar)
{
case VK_ESCAPE:
if (!IsCaptureState()) break;
OnDrawDragPreview();
DrawSelectHandle(GetHandleIndex());
SetHandleIndex(-1);
ReleaseCapture();
return TRUE;
}
return FALSE;
}
BOOL CurveEditorDefault::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
switch (nChar)
{
case VK_ESCAPE:
EndEdit();
return TRUE;
}
return FALSE;
}
int CurveEditorDefault::GetSubMenu()
{
return 6;
}
void CurveEditorDefault::SetPos(POSITION pos)
{
CItem::SetPos(pos);
m_pBakOne.reset(); // 清除旧备份
if (pos)
{
COne* pOne = GetDoc()->GetDraw()->GetAt(GetPos());
m_pBakOne = std::make_unique<COne>();
*m_pBakOne = *pOne;
// 调用钩子模式特定的初始化样条模式会计算m_pMarkCurve
OnModeInitialize();
}
}
void CurveEditorDefault::Clear(void)
{
m_pBakOne.reset(); // 清除备份
if (GetPos() != nullptr)
{
COne* pOne = GetDoc()->GetDraw()->GetAt(GetPos());
CRect8 range(1e100, -1e100, -1e100, 1e100);
pOne->GetRange(range);
CRect rt = GetDC()->GetScreen(range);
rt.NormalizeRect();
rt.InflateRect(GetDoc()->GetHandleSize());
rt.InflateRect(1, 1);
}
}
void CurveEditorDefault::Draw(CDC* pDC)
{
POSITION pos = GetPos();
if (pos == 0) return;
CCurveEx* pValue = GetControlCurve();
DrawHandlesByCDC(pDC, pValue);
}
int CurveEditorDefault::GetNumberOfNode()
{
CCurveEx* pc = GetControlCurve();
if (pc == 0) return -1;
return pc->num;
}
void CurveEditorDefault::EreaseHandles(CDC* pDC)
{
m_handleDrawer->ClearAll(pDC);
}
// ========== 编辑会话管理(完全实现)==========
void CurveEditorDefault::EndEdit(void)
{
if (GetPos() == nullptr) return;
COne* pOne = GetDoc()->GetDraw()->GetAt(GetPos());
CRect8 range(1e100, -1e100, -1e100, 1e100);
pOne->GetRange(range);
CRect rt = GetDC()->GetScreen(range);
rt.NormalizeRect();
rt.InflateRect(GetDoc()->GetHandleSize());
rt.InflateRect(1, 1);
if (((CCurveEx*)pOne->GetValue())->num < 2)
{
if (::AfxMessageBox(IDS_STRING_CURVE_ONLY_ONE_POINT, MB_YESNO | MB_ICONQUESTION) == IDYES)
{
*pOne = *m_pBakOne;
CPositionList list;
list.AddTail(GetPos());
CItem::SetPos(nullptr);
GetDoc()->InvalidateDelete(list);
return;
}
}
if (IsModified())
{
CActionItem* pAction = new CActionModifiedItem(GetDoc(), IDS_STRING_TOOLBAR_EDIT);
((CActionModifiedItem*)pAction)->BackupOldItem(GetPos(), m_pBakOne.get());
GetDoc()->SetActionItem(pAction);
}
}
void CurveEditorDefault::CancelAll(void)
{
if (m_pBakOne)
{
COne* pOne = GetDoc()->GetDraw()->GetAt(GetPos());
*pOne = *m_pBakOne;
GetMarkCurve();
}
}
BOOL CurveEditorDefault::IsCanCancelAll(void)
{
return m_pBakOne != nullptr ? TRUE : FALSE;
}
void CurveEditorDefault::GetXY(CCurveEx* pValue, int nIndex, dfPoint& point)
{
pValue->GetPoint(nIndex, point);
}
CCurveEx* CurveEditorDefault::GetControlCurve(void)
{
POSITION pos = GetPos();
if (pos == nullptr) return nullptr;
return (CCurveEx*)GetDoc()->GetDraw()->GetAtValue(pos);
}
int CurveEditorDefault::HitTestHandle(CPoint point)
{
return HitTestHandle(GetControlCurve(), point);
}
int CurveEditorDefault::HitTestHandle(CCurveEx* pValue, CPoint point)
{
if (pValue == nullptr) return -1;
CRect rt;
for (int i = 0; i < pValue->num; i++)
{
rt = GetDoc()->m_itemTracker.GetHandleRect(
GetDC()->GetSX(pValue->x[i]),
GetDC()->GetSY(pValue->y[i]));
if (rt.PtInRect(point))
return i;
}
return -1;
}
bool CurveEditorDefault::IsDragging(int nFlags) const
{
return nFlags & 1;
}
int CurveEditorDefault::HandleMouseDrag(CXyDC* pDC, CPoint point)
{
m_bLButtonDownWhenMouseMove = true;
// 处理线段拖动
if (m_nDragSegmentIndex >= 0)
{
// 1. [擦除]:此时 c_last 还是上一次的坐标
DrawMoveSegment(GetScreenDC());
// 2. [更新]:将 c_last 更新为当前鼠标位置的真实坐标
NBase::CPoint2D rawMouse = GetDC()->GetReal(point);
c_last = CalculateSnappedMousePos(rawMouse, m_nDragSegmentIndex);
// 3. [绘制]:使用新的 c_last 画出新线
DrawMoveSegment(GetScreenDC());
return 1;
}
// 当没有选择节点,而且能增加时,只要左键按下移动时自动增加一个节点
int i = GetHandleIndex();
if (i < 0)
{
CSize8 sz = GetDoc()->GetSelectSize();
if (AfxGetPublicFunction()->Distance(m_nLButtonDownPoint.x, m_nLButtonDownPoint.y, point.x, point.y) > sz.cx)
{
i = AddHandle(m_nLButtonDownPoint);
if (i < 0) return 1;
SetHandleIndex(i);
c_down = GetDC()->GetReal(m_nLButtonDownPoint);
c_last = c_down;
// 第一次绘制预览线
OnDrawDragPreview();
}
else
return 1;
}
// 擦除上一次的预览
OnDrawDragPreview();
// 更新位置
c_last = pDC->GetReal(point);
CPointList list;
OnCalculateDragEffect(&list);
// 绘制新的预览(使用更新后的 NewPointList
OnDrawDragPreview();
CString str, string;
auto curve = std::make_unique<CCurveEx>();
curve->SetPoints(list, 2);
COne* pOne = GetDoc()->GetDraw()->GetAt(GetPos());
CCurveEx* pValue = (CCurveEx*)pOne->GetValue();
OnDragMove(GetHandleIndex(), c_last);
return 1;
}
int CurveEditorDefault::HandleMouseHover(CDC* dc, CPoint point)
{
// 鼠标移动时的控制点高亮显示
CCurveEx* pValue = GetControlCurve();
if (pValue == 0)
{
SetHandleIndex(-1);
int handleIndex = m_handleDrawer->EreaseFocusHandle(dc);
m_handleDrawer->DrawOneHandle(dc, handleIndex);
}
else
{
int i = HitTestHandle(pValue, point);
if (i >= 0)
{
SetHandleIndex(i);
}
else
{
SetHandleIndex(-1);
int handleIndex = m_handleDrawer->EreaseFocusHandle(dc);
m_handleDrawer->DrawOneHandle(dc, handleIndex);
}
}
return 1;
}
void CurveEditorDefault::DeleteHandle(int nIndex)
{
CCurveEx* pValue = GetControlCurve();
BOOL bAttachProcess = TRUE;
CPointList oldPoints;
CPointList newPoints;
if (bAttachProcess)
{
dfPoint pt;
if (nIndex != 0)
{
pValue->GetPoint(nIndex - 1, pt);
oldPoints.AddTail(pt);
newPoints.AddTail(pt);
}
pValue->GetPoint(nIndex, pt);
oldPoints.AddTail(pt);
if (nIndex < pValue->num - 1)
{
pValue->GetPoint(nIndex + 1, pt);
oldPoints.AddTail(pt);
newPoints.AddTail(pt);
}
}
PointList.RemoveAll();
dfPoint dd;
for (int i = 0; i < pValue->num; i++)
{
dd.x0 = pValue->x[i];
dd.y0 = pValue->y[i];
dd.z0 = pValue->z[i];
dd.l = pValue->l[i];
if (i == nIndex) continue;
PointList.AddTail(dd);
}
pValue->SetPoints(PointList, pValue->nPoint, pValue->bAutoLocation);
PointList.RemoveAll();
SetModifiedFlag(TRUE);
this->SetHandleIndex(-1);
if (bAttachProcess)
{
if (oldPoints.GetCount() > 0)
AttachProcess(oldPoints, newPoints);
}
}
int CurveEditorDefault::AddHandle(double l0)
{
CCurveEx* pValue = (CCurveEx*)GetDoc()->GetDraw()->GetAtValue(GetPos());
dfPoint add;
add.l = l0;
pValue->GetCoordinate(add.l, add.x0, add.y0, add.z0);
return AddHandle(pValue, add);
}
int CurveEditorDefault::AddHandle(CCurveEx* pValue, dfPoint add)
{
if (pValue == nullptr) return -1;
int nIndex = -1;
PointList.RemoveAll();
if (add.l < pValue->l[0])
{
pValue->GetPoint(PointList);
PointList.AddHead(add);
nIndex = 0;
}
else if (add.l > pValue->l[pValue->num - 1])
{
pValue->GetPoint(PointList);
PointList.AddTail(add);
nIndex = (int)PointList.GetCount() - 1;
}
else
{
dfPoint dd;
for (int i = 0; i < pValue->num; i++)
{
dd.x0 = pValue->x[i];
dd.y0 = pValue->y[i];
dd.z0 = pValue->z[i];
dd.l = pValue->l[i];
PointList.AddTail(dd);
if (i == pValue->num - 1) break;
if (add.l > pValue->l[i] && add.l < pValue->l[i + 1])
{
PointList.AddTail(add);
nIndex = i + 1;
}
}
}
pValue->SetPoints(PointList, pValue->nPoint, pValue->bAutoLocation);
PointList.RemoveAll();
GetDoc()->Modified();
SetModifiedFlag(TRUE);
return nIndex;
}
int CurveEditorDefault::AddHandle(CPoint point)
{
double l0;
if (!IsCanAddHandle(point, &l0)) return -1;
return AddHandle(l0);
}
BOOL CurveEditorDefault::IsCanAddHandle(CPoint point, double* pl0)
{
POSITION pos = GetPos();
if (pos == nullptr) return FALSE;
CCurveEx* pValue = (CCurveEx*)GetDoc()->GetDraw()->GetAtValue(pos);
if (pValue == nullptr) return FALSE;
CPoint2D pt = GetDC()->GetReal(point);
double l0;
double dis = pValue->PointDistance(pt.x0, pt.y0, l0);
CSize8 sz = GetDoc()->GetSelectSize();
int dx = GetDC()->GetScreenWidth(dis);
int dy = GetDC()->GetScreenHeight(dis);
if (pl0) *pl0 = l0;
if (dx > sz.cx || dy > sz.cy) return FALSE;
return TRUE;
}
void CurveEditorDefault::AttachProcess(CPointList& oldPoints, CPointList& newPoints)
{
}
void CurveEditorDefault::DrawMoveLine(void)
{
OnDrawDragPreview();
}
void CurveEditorDefault::GetMarkCurve(void)
{
}
CPoint2D CurveEditorDefault::GetCDown() const
{
return c_down;
}
CPoint2D CurveEditorDefault::GetCLast() const
{
return c_last;
}
void CurveEditorDefault::DrawHandle(CXyDC* pDC, CCurveEx* pCurve)
{
if (pCurve == nullptr) return;
CPoint ptPrev;
CRect rt;
dfPoint pt;
CPoint point;
for (int i = 0; i < pCurve->num; i++)
{
GetXY(pCurve, i, pt);
point.x = pDC->GetSX(pt.x0);
point.y = pDC->GetSY(pt.y0);
if (i > 0)
{
if (point.x == ptPrev.x && point.y == ptPrev.y && i != GetHandleIndex())
continue;
}
if (i == GetHandleIndex())
{
if (i == 0)
rt = GetFirstNodeHandleRectFocus(point);
else
rt = GetDoc()->m_itemTracker.GetHandleRectFocus(point);
}
else
{
if (i == 0)
rt = GetDoc()->m_itemTracker.GetHandleRectFocus(point);
else
rt = GetDoc()->m_itemTracker.GetHandleRect(point);
}
GetDoc()->m_itemTracker.DrawHandle(m_pScreenDC, rt, TRACKER_CIRCLE);
ptPrev = point;
}
}
void CurveEditorDefault::DrawHandlesByCDC(CDC* pDC, CCurveEx* pCurve)
{
if (pCurve == nullptr) return;
m_handleDrawer->ClearAll(pDC);
m_handleDrawer->Init(pCurve);
m_handleDrawer->DrawAll(pDC);
}
void CurveEditorDefault::DrawSelectHandle(int nHandle)
{
CCurveEx* pValue = GetControlCurve();
if (pValue == nullptr) return;
if (nHandle < 0 || nHandle >= pValue->num) return;
CPoint point;
point.x = GetDC()->GetSX(pValue->x[nHandle]);
point.y = GetDC()->GetSY(pValue->y[nHandle]);
CRect rt;
if (nHandle == 0)
rt = GetDoc()->m_itemTracker.GetHandleRectFocus(point);
else
rt = GetDoc()->m_itemTracker.GetHandleRect(point);
GetDoc()->m_itemTracker.DrawHandle(m_pScreenDC, rt, m_nHandleDrawMode);
if (nHandle == 0)
rt = GetFirstNodeHandleRectFocus(point);
else
rt = GetDoc()->m_itemTracker.GetHandleRectFocus(point);
GetDoc()->m_itemTracker.DrawHandle(m_pScreenDC, rt, m_nHandleDrawMode);
}
CRect CurveEditorDefault::GetRangeWidthIndex(int nIndex)
{
CCurveEx* pValue = GetControlCurve();
if (pValue == nullptr || nIndex < 0 || nIndex >= pValue->num)
{
return CRect(0, 0, 0, 0);
}
CRect8 rt(pValue->x[nIndex], pValue->y[nIndex], pValue->x[nIndex], pValue->y[nIndex]);
if (nIndex > 0)
rt.CombinRect(pValue->x[nIndex - 1], pValue->y[nIndex - 1], pValue->x[nIndex - 1], pValue->y[nIndex - 1]);
if (nIndex < pValue->num - 1)
rt.CombinRect(pValue->x[nIndex + 1], pValue->y[nIndex + 1], pValue->x[nIndex + 1], pValue->y[nIndex + 1]);
CRect rect = GetDC()->GetScreen(rt);
rect.InflateRect(GetDoc()->GetHandleSize());
return rect;
}
CRect CurveEditorDefault::GetFirstNodeHandleRectFocus(CPoint point)
{
CRect rt = GetDoc()->m_itemTracker.GetHandleRectFocus(point);
rt.InflateRect(1, 1);
return rt;
}
void CurveEditorDefault::SetDrawMode(int nModeX, int nModeY)
{
m_nModeX = nModeX;
m_nModeY = nModeY;
}
int CurveEditorDefault::GetOffsetMode(void)
{
return GetDoc()->GetEditLineStatus() % 4;
}
void CurveEditorDefault::OnDrawDragPreview()
{
// ========== 默认模式:只绘制两条线段 ==========
int i = GetHandleIndex();
if (i < 0) return;
CCurveEx* pValue = GetControlCurve();
if (pValue == nullptr || i >= pValue->num)
{
return;
}
CXyDC* pDC = GetDC();
CPen pen(PS_SOLID, 0, DRAG_LINE_COLOR);
CPen* op = (CPen*)m_pScreenDC->SelectObject(&pen);
int od = m_pScreenDC->SetROP2(R2_NOTXORPEN);
double dx = c_last.x0 - c_down.x0;
double dy = c_last.y0 - c_down.y0;
dfPoint pt;
// 绘制前一段线(当前节点到前一个节点)
if (i > 0)
{
GetXY(pValue, i - 1, pt);
CPoint ptS1 = pDC->GetScreen(pt);
m_pScreenDC->MoveTo(ptS1);
GetXY(pValue, i, pt);
pt.x0 += dx;
pt.y0 += dy;
CPoint ptS2 = pDC->GetScreen(pt);
m_pScreenDC->LineTo(ptS2.x, ptS2.y);
}
// 绘制后一段线(当前节点到后一个节点)
if (i < pValue->num - 1)
{
GetXY(pValue, i + 1, pt);
CPoint ptS1 = pDC->GetScreen(pt);
m_pScreenDC->MoveTo(ptS1);
GetXY(pValue, i, pt);
pt.x0 += dx;
pt.y0 += dy;
CPoint ptS2 = pDC->GetScreen(pt);
m_pScreenDC->LineTo(ptS2.x, ptS2.y);
}
m_pScreenDC->SetROP2(od);
m_pScreenDC->SelectObject(op);
}
void CurveEditorDefault::OnCalculateDragEffect(CPointList* pList)
{
// pList != nullptr预览模式不实际修改曲线用于状态栏显示
// pList == nullptr实际修改模式创建 Undo/Redo 动作)
BOOL bRedraw = TRUE;
BOOL bAttachProcess = TRUE; // 是否处理绑定曲线
CCurveEx* pValue = (CCurveEx*)GetDoc()->GetDraw()->GetAtValue(GetPos());
if (pList)
{
// 预览模式:创建临时副本
CCurveEx* pc = new CCurveEx;
*pc = *pValue;
pValue = pc;
bRedraw = FALSE;
bAttachProcess = FALSE;
}
CPointList oldPoints; // 记录移动前的坐标(用于绑定处理)
CPointList newPoints; // 记录移动后的坐标(用于绑定处理)
dfPoint pt;
CRect rt;
// ========== 默认模式:直接移动当前节点 ==========
int i = GetHandleIndex();
if (bRedraw)
{
// 基类中已经有 GetRangeWidthIndex这里注释掉避免重复
// rt = GetRangeWidthIndex(i);
}
// 为了绑定处理,记录原坐标点
if (bAttachProcess)
{
if (i - 1 >= 0)
{
pValue->GetPoint(i - 1, pt); oldPoints.AddTail(pt);
}
pValue->GetPoint(i, pt); oldPoints.AddTail(pt);
if (i + 1 <= pValue->num - 1)
{
pValue->GetPoint(i + 1, pt); oldPoints.AddTail(pt);
}
}
CActionCurveEdit* pAction = 0;
if (pList == nullptr)
{
pAction = new CActionCurveEdit(GetDoc(), 0, GetPos());
pAction->BackupUndoNode(i, pValue->x[i], pValue->y[i]);
}
pValue->x[i] += (c_last.x0 - c_down.x0);
pValue->y[i] += (c_last.y0 - c_down.y0);
if (pValue->bAutoLocation) pValue->GetLocation();
if (pList == nullptr)
{
pAction->BackupRedoNode(i, pValue->x[i], pValue->y[i]);
GetDoc()->SetActionItem(pAction);
}
if (bAttachProcess)
{
// 为了绑定处理,记录新坐标点
if (i - 1 >= 0)
{
pValue->GetPoint(i - 1, pt); newPoints.AddTail(pt);
}
pValue->GetPoint(i, pt); newPoints.AddTail(pt);
if (i + 1 <= pValue->num - 1)
{
pValue->GetPoint(i + 1, pt); newPoints.AddTail(pt);
}
AttachProcess(oldPoints, newPoints);
}
if (bRedraw)
{
// rt = GetRangeWidthIndex(i);
}
if (pList)
{
pValue->GetPoint(*pList);
delete pValue;
}
}
void CurveEditorDefault::OnModeInitialize()
{
// Default implementation - does nothing
}
void CurveEditorDefault::OnDragMove(int nIndex, CPoint2D pt)
{
// Default implementation - does nothing
}
// ========== 线段拖动相关辅助函数实现 ==========
int CurveEditorDefault::HitTestSegment(CPoint point)
{
CCurveEx* pValue = GetControlCurve();
if (!pValue || pValue->num < 2) return -1;
CXyDC* pDC = GetDC();
double distThreshold = 5.0; // 选中容差(像素)
for (int i = 0; i < pValue->num - 1; i++)
{
// 获取线段两端的屏幕坐标
CPoint p1 = pDC->GetScreen(pValue->x[i], pValue->y[i]);
CPoint p2 = pDC->GetScreen(pValue->x[i + 1], pValue->y[i + 1]);
// 计算点到线段的距离
double dist = DistancePointToLine(point.x, point.y, p1.x, p1.y, p2.x, p2.y);
// 简单判断范围:鼠标必须在以此线段为对角线的矩形稍微外扩的范围内
CRect rt(p1, p2);
rt.NormalizeRect();
rt.InflateRect((int)distThreshold + 2, (int)distThreshold + 2);
if (rt.PtInRect(point) && dist < distThreshold)
{
return i; // 返回线段起始点索引
}
}
return -1;
}
void CurveEditorDefault::DrawMoveSegment(CDC* pDC)
{
// 1. 基础校验:如果没有正在拖动的线段,直接返回
if (m_nDragSegmentIndex < 0) return;
CCurveEx* pValue = GetControlCurve();
// 校验索引范围:确保不会越界访问
if (m_nDragSegmentIndex >= pValue->num - 1) return;
// 2. 获取原始线段的起点和终点坐标 (从曲线数据中读取)
dfPoint pt1, pt2;
GetXY(pValue, m_nDragSegmentIndex, pt1);
GetXY(pValue, m_nDragSegmentIndex + 1, pt2);
// 定义最终用于绘制的屏幕坐标变量
double drawX1, drawY1, drawX2, drawY2;
// ========================================================================
// 核心分支:根据吸附类型决定绘制策略
// ========================================================================
bool isSnapping = (m_lastSnapResult.snapType == Geometry::SnapType::PARALLEL_LINE);
if (isSnapping)
{
// 策略 A平行吸附 -> 投影显示 (Projection)
const Geometry::Segment& targetSeg = m_lastSnapResult.targetSegment;
// 构造几何点
Geometry::Point pStart = { pt1.x0, pt1.y0 };
Geometry::Point pEnd = { pt2.x0, pt2.y0 };
// 计算投影因子 t (相对于目标线段)
double t1 = Geometry::GeometryUtils::getProjectionFactor(pStart, targetSeg);
double t2 = Geometry::GeometryUtils::getProjectionFactor(pEnd, targetSeg);
// 获取目标线段的方向向量
Geometry::Point dir = targetSeg.direction();
// 计算投影后的新坐标
Geometry::Point newStart = targetSeg.start + (dir * t1);
Geometry::Point newEnd = targetSeg.start + (dir * t2);
// 赋值给绘制变量
drawX1 = newStart.x;
drawY1 = newStart.y;
drawX2 = newEnd.x;
drawY2 = newEnd.y;
}
else
{
// 策略 B普通移动 -> 刚体平移 (Rigid Translation)
double mouseDx = c_last.x0 - c_down.x0;
double mouseDy = c_last.y0 - c_down.y0;
// 计算沿法线的投影偏移量
NBase::CPoint2D offset = CalculateNormalOffset(pt1, pt2, mouseDx, mouseDy);
// 原始坐标 + 偏移量 = 绘制坐标
drawX1 = pt1.x0 + offset.x0;
drawY1 = pt1.y0 + offset.y0;
drawX2 = pt2.x0 + offset.x0;
drawY2 = pt2.y0 + offset.y0;
}
// ========================================================================
// 屏幕绘制 (XOR 模式)
// ========================================================================
// 获取坐标转换器
CXyDC* pXyDC = GetDC();
COLORREF penColor = isSnapping ? RGB(255, 0, 0) : DRAG_LINE_COLOR;
// 设置画笔:实线,颜色由 DRAG_LINE_COLOR 定义
CPen pen(PS_SOLID, 0, penColor);
CPen* op = m_pScreenDC->SelectObject(&pen);
// 设置绘图模式为 R2_NOTXORPEN (异或模式)
int od = m_pScreenDC->SetROP2(R2_NOTXORPEN);
// 将真实坐标转换为屏幕像素坐标
CPoint sp1 = pXyDC->GetScreen(drawX1, drawY1);
CPoint sp2 = pXyDC->GetScreen(drawX2, drawY2);
// 绘制线条
m_pScreenDC->MoveTo(sp1);
m_pScreenDC->LineTo(sp2);
// 恢复绘图上下文环境
m_pScreenDC->SetROP2(od);
m_pScreenDC->SelectObject(op);
}
NBase::CPoint2D CurveEditorDefault::CalculateNormalOffset(dfPoint p1, dfPoint p2, double dx, double dy)
{
// 线段向量
double segX = p2.x0 - p1.x0;
double segY = p2.y0 - p1.y0;
// 法线向量 (-y, x)
double normX = -segY;
double normY = segX;
// 归一化
double len = sqrt(normX * normX + normY * normY);
if (len < 1e-9) return { 0.0, 0.0 };
normX /= len;
normY /= len;
// 点积投影:鼠标位移在法线上的长度
double projection = dx * normX + dy * normY;
// 返回最终的偏移向量
return { projection * normX, projection * normY };
}
double CurveEditorDefault::DistancePointToLine(double px, double py, double x1, double y1, double x2, double y2)
{
// 线段长度的平方
double l2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
// 如果线段长度为0起点和终点重合直接计算点到点的距离
if (l2 == 0.0)
return sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
// 计算投影参数 t = ((P - A) . (B - A)) / |B - A|^2
double t = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / l2;
// 限制 t 在 [0, 1] 范围内,从而将计算限制在线段上
if (t < 0.0) t = 0.0;
else if (t > 1.0) t = 1.0;
// 计算线段上最近的投影点坐标
double projX = x1 + t * (x2 - x1);
double projY = y1 + t * (y2 - y1);
// 返回点到投影点的欧几里得距离
return sqrt((px - projX) * (px - projX) + (py - projY) * (py - projY));
}
CRect8 CurveEditorDefault::GetLineSegmentAABB(const dfPoint& point1, const dfPoint& point2, double width) const
{
double halfWidth = width / 2.0;
double rawMinX = min(point1.x0, point2.x0);
double rawMaxX = max(point1.x0, point2.x0);
double rawMinY = min(point1.y0, point2.y0);
double rawMaxY = max(point1.y0, point2.y0);
return CRect8(rawMinX - halfWidth, rawMaxY + halfWidth, rawMaxX + halfWidth, rawMinY - halfWidth);
}
NBase::CPoint2D CurveEditorDefault::CalculateSnappedMousePos(NBase::CPoint2D rawMouse, int segmentIndex)
{
if (!GetDoc()->IsSnapEnabled())
{
return rawMouse;
}
CCurveEx* pValue = GetControlCurve();
if (!pValue || segmentIndex < 0 || segmentIndex >= pValue->num - 1)
return rawMouse;
// 1. 准备数据
dfPoint pt1, pt2;
GetXY(pValue, segmentIndex, pt1);
GetXY(pValue, segmentIndex + 1, pt2);
// 计算当前线段的单位法线 (这是我们的移动轨道)
double segDx = pt2.x0 - pt1.x0;
double segDy = pt2.y0 - pt1.y0;
double nx = -segDy;
double ny = segDx;
double len = std::hypot(nx, ny);
if (len < 1e-9) return rawMouse;
nx /= len;
ny /= len;
// 2. 计算"幽灵"线段位置
double mouseDx = rawMouse.x0 - c_down.x0;
double mouseDy = rawMouse.y0 - c_down.y0;
double projLen = mouseDx * nx + mouseDy * ny;
NBase::CPoint2D normalOffset = { projLen * nx, projLen * ny };
Geometry::Segment currentSeg;
currentSeg.start = { pt1.x0 + normalOffset.x0, pt1.y0 + normalOffset.y0 };
currentSeg.end = { pt2.x0 + normalOffset.x0, pt2.y0 + normalOffset.y0 };
// 3. 调用吸附引擎 (标准接口,不需要传法线)
Geometry::SnapConfig config;
config.snapDistance = GetDC()->GetRealWidth(20.0);
config.angleTolerance = 5.0;
Geometry::SnappingEngine engine(GetDoc()->m_pXy, GetSpatialIndex(), config);
Geometry::SnapResult res = engine.getSnapResult(currentSeg);
m_lastSnapResult = res;
// 4. 编辑器层面的后期处理:计算共线补偿
if (res.snapType != Geometry::SnapType::NONE)
{
double finalOffsetX = res.offset.x;
double finalOffsetY = res.offset.y;
// 【改动点】如果是平行线吸附,使用目标线段计算交点
if (res.snapType == Geometry::SnapType::PARALLEL_LINE)
{
// P_target
double tx = res.targetSegment.start.x;
double ty = res.targetSegment.start.y;
// Target Direction (V)
double vx = res.targetSegment.end.x - tx;
double vy = res.targetSegment.end.y - ty;
// 我们需要求解方程:
// CurrentStart + t * Normal = TargetStart + u * TargetDir
// 这等价于求两个向量叉乘
double dx = currentSeg.start.x - tx;
double dy = currentSeg.start.y - ty;
// 二维叉积: A cross B = Ax * By - Ay * Bx
double det = nx * vy - ny * vx;
// 只要 det != 0 (即法线不平行于目标线,也就是说原线段不垂直于目标线)
if (std::abs(det) > 1e-9)
{
// 根据克拉默法则 (Cramer's Rule) 求 t (沿法线移动的距离)
double t = (vx * dy - vy * dx) / det;
finalOffsetX = nx * t;
finalOffsetY = ny * t;
}
}
rawMouse.x0 += finalOffsetX;
rawMouse.y0 += finalOffsetY;
}
return rawMouse;
}