#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 pAction = std::make_unique(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(); *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(); 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; }