..
This commit is contained in:
@@ -220,12 +220,28 @@ namespace AGVNavigationCore.Controls
|
||||
{
|
||||
if (_editMode == EditMode.Move)
|
||||
{
|
||||
// 0. 핸들 선택 확인 (이미 선택된 노드가 있을 때)
|
||||
if (_selectedNode != null)
|
||||
{
|
||||
int handleIdx = GetHandleAt(worldPoint);
|
||||
if (handleIdx != -1)
|
||||
{
|
||||
_dragHandleIndex = handleIdx;
|
||||
_isDragging = true;
|
||||
_isPanning = false;
|
||||
Capture = true;
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 노드 선택 확인
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
if (hitNode != null)
|
||||
{
|
||||
_isDragging = true;
|
||||
_isPanning = false;
|
||||
_dragHandleIndex = -1; // 노드 전체 드래그
|
||||
_selectedNode = hitNode;
|
||||
_dragStartPosition = hitNode.Position;
|
||||
_dragOffset = new Point(worldPoint.X - hitNode.Position.X, worldPoint.Y - hitNode.Position.Y);
|
||||
@@ -322,8 +338,38 @@ namespace AGVNavigationCore.Controls
|
||||
// 노드 드래그
|
||||
if (_selectedNode != null)
|
||||
{
|
||||
_selectedNode.Position = newPosition;
|
||||
NodeMoved?.Invoke(this, _selectedNode);
|
||||
if (_dragHandleIndex != -1)
|
||||
{
|
||||
// 핸들 드래그 (포인트별 수정)
|
||||
if (_selectedNode is MapMagnet magnet)
|
||||
{
|
||||
if (_dragHandleIndex == 0) magnet.StartPoint = newPosition;
|
||||
else if (_dragHandleIndex == 1) magnet.EndPoint = newPosition;
|
||||
else if (_dragHandleIndex == 2 && magnet.ControlPoint != null)
|
||||
{
|
||||
magnet.ControlPoint.X = newPosition.X;
|
||||
magnet.ControlPoint.Y = newPosition.Y;
|
||||
}
|
||||
}
|
||||
else if (_selectedNode is MapMark mark)
|
||||
{
|
||||
// 마크는 중심점 대비 각도와 길이를 계산하여 수정
|
||||
var dx = newPosition.X - mark.Position.X;
|
||||
var dy = newPosition.Y - mark.Position.Y;
|
||||
|
||||
// 핸들 인덱스에 따라 각도 반전 (p1 vs p2)
|
||||
if (_dragHandleIndex == 0) { dx = -dx; dy = -dy; }
|
||||
|
||||
mark.Rotation = Math.Atan2(dy, dx) * 180.0 / Math.PI;
|
||||
mark.Length = Math.Sqrt(dx * dx + dy * dy) * 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 노드 전체 드래그
|
||||
_selectedNode.Position = newPosition;
|
||||
NodeMoved?.Invoke(this, _selectedNode);
|
||||
}
|
||||
moved = true;
|
||||
}
|
||||
|
||||
@@ -352,6 +398,7 @@ namespace AGVNavigationCore.Controls
|
||||
if (_isDragging && _canvasMode == CanvasMode.Edit)
|
||||
{
|
||||
_isDragging = false;
|
||||
_dragHandleIndex = -1;
|
||||
Capture = false; // 🔥 마우스 캡처 해제
|
||||
Cursor = GetCursorForMode(_editMode);
|
||||
}
|
||||
@@ -463,6 +510,25 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
}
|
||||
|
||||
if (_marks != null)
|
||||
{
|
||||
for (int i = _marks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var node = _marks[i];
|
||||
if (IsPointInNode(worldPoint, node))
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
if (_magnets != null)
|
||||
{
|
||||
for (int i = _magnets.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var node = _magnets[i];
|
||||
if (IsPointInNode(worldPoint, node))
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -477,6 +543,14 @@ namespace AGVNavigationCore.Controls
|
||||
{
|
||||
return IsPointInImage(point, image);
|
||||
}
|
||||
if (node is MapMark mark)
|
||||
{
|
||||
return IsPointInMark(point, mark);
|
||||
}
|
||||
if (node is MapMagnet magnet)
|
||||
{
|
||||
return IsPointInMagnet(point, magnet);
|
||||
}
|
||||
// 라벨과 이미지는 별도 리스트로 관리되므로 여기서 처리하지 않음
|
||||
// 하지만 혹시 모를 하위 호환성을 위해 타입 체크는 유지하되,
|
||||
// 실제 로직은 CircularNode 등으로 분기
|
||||
@@ -648,6 +722,55 @@ namespace AGVNavigationCore.Controls
|
||||
return imageRect.Contains(point);
|
||||
}
|
||||
|
||||
private bool IsPointInMark(Point point, MapMark mark)
|
||||
{
|
||||
int lineLength = (int)mark.Length;
|
||||
int halfLength = lineLength / 2;
|
||||
double radians = mark.Rotation * Math.PI / 180.0;
|
||||
int dx = (int)(halfLength * Math.Cos(radians));
|
||||
int dy = (int)(halfLength * Math.Sin(radians));
|
||||
|
||||
Point p1 = new Point(mark.Position.X - dx, mark.Position.Y - dy);
|
||||
Point p2 = new Point(mark.Position.X + dx, mark.Position.Y + dy);
|
||||
|
||||
// 마크 선택을 위해 약간 넉넉한 히트 영역 (7픽셀)
|
||||
return CalculatePointToLineDistance(point, p1, p2) <= 7 / _zoomFactor;
|
||||
}
|
||||
|
||||
private bool IsPointInMagnet(Point point, MapMagnet magnet)
|
||||
{
|
||||
// 마그넷은 두꺼우므로 (Pen Width 15) 절반인 7.5 정도를 히트 영역으로 잡음
|
||||
float hitThreshold = Math.Max(8f, 12f / _zoomFactor);
|
||||
|
||||
if (magnet.ControlPoint != null)
|
||||
{
|
||||
// 베지어 곡선 정밀 샘플링 (10개 세그먼트)
|
||||
Point prevPoint = magnet.StartPoint;
|
||||
for (int i = 1; i <= 10; i++)
|
||||
{
|
||||
float t = i / 10f;
|
||||
// Quadratic Bezier: (1-t)^2*P0 + 2(1-t)t*P1 + t^2*P2
|
||||
float u = 1 - t;
|
||||
float tt = t * t;
|
||||
float uu = u * u;
|
||||
|
||||
float x = uu * magnet.StartPoint.X + 2 * u * t * (float)magnet.ControlPoint.X + tt * magnet.EndPoint.X;
|
||||
float y = uu * magnet.StartPoint.Y + 2 * u * t * (float)magnet.ControlPoint.Y + tt * magnet.EndPoint.Y;
|
||||
Point currentPoint = new Point((int)x, (int)y);
|
||||
|
||||
if (CalculatePointToLineDistance(point, prevPoint, currentPoint) <= hitThreshold)
|
||||
return true;
|
||||
|
||||
prevPoint = currentPoint;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CalculatePointToLineDistance(point, magnet.StartPoint, magnet.EndPoint) <= hitThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
//private MapLabel GetLabelAt(Point worldPoint)
|
||||
//{
|
||||
// if (_labels == null) return null;
|
||||
@@ -833,7 +956,7 @@ namespace AGVNavigationCore.Controls
|
||||
/// <summary>
|
||||
/// 중복되지 않는 고유한 NodeId 생성
|
||||
/// </summary>
|
||||
private string GenerateUniqueNodeId()
|
||||
public string GenerateUniqueNodeId()
|
||||
{
|
||||
string nodeId;
|
||||
int counter = _nodeCounter;
|
||||
@@ -1053,8 +1176,8 @@ namespace AGVNavigationCore.Controls
|
||||
var C = lineEnd.X - lineStart.X;
|
||||
var D = lineEnd.Y - lineStart.Y;
|
||||
|
||||
var dot = A * C + B * D;
|
||||
var lenSq = C * C + D * D;
|
||||
var dot = (double)A * C + (double)B * D;
|
||||
var lenSq = (double)C * C + (double)D * D;
|
||||
|
||||
if (lenSq == 0) return CalculateDistance(point, lineStart);
|
||||
|
||||
@@ -1102,6 +1225,39 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private int GetHandleAt(Point worldPoint)
|
||||
{
|
||||
if (_selectedNode == null) return -1;
|
||||
|
||||
float hitTolerance = (HANDLE_SIZE + 4) / _zoomFactor;
|
||||
|
||||
if (_selectedNode is MapMagnet magnet)
|
||||
{
|
||||
if (CalculateDistance(worldPoint, magnet.StartPoint) <= hitTolerance) return 0;
|
||||
if (CalculateDistance(worldPoint, magnet.EndPoint) <= hitTolerance) return 1;
|
||||
if (magnet.ControlPoint != null)
|
||||
{
|
||||
if (CalculateDistance(worldPoint, new Point((int)magnet.ControlPoint.X, (int)magnet.ControlPoint.Y)) <= hitTolerance) return 2;
|
||||
}
|
||||
}
|
||||
else if (_selectedNode is MapMark mark)
|
||||
{
|
||||
int lineLength = (int)mark.Length;
|
||||
int halfLength = lineLength / 2;
|
||||
double radians = mark.Rotation * Math.PI / 180.0;
|
||||
int dx = (int)(halfLength * Math.Cos(radians));
|
||||
int dy = (int)(halfLength * Math.Sin(radians));
|
||||
|
||||
Point p1 = new Point(mark.Position.X - dx, mark.Position.Y - dy);
|
||||
Point p2 = new Point(mark.Position.X + dx, mark.Position.Y + dy);
|
||||
|
||||
if (CalculateDistance(worldPoint, p1) <= hitTolerance) return 0;
|
||||
if (CalculateDistance(worldPoint, p2) <= hitTolerance) return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region View Control Methods
|
||||
|
||||
Reference in New Issue
Block a user