..
This commit is contained in:
@@ -478,8 +478,32 @@ namespace AGVNavigationCore.Controls
|
||||
// But this method draws normal first.
|
||||
// So I should refactor to calculate path first, then draw?
|
||||
// Or just draw highlight on top with alpha?
|
||||
// Let's draw highlight on top with non-filled center? No, it's a line.
|
||||
// I'll draw highlight on top for now, maybe with alpha.
|
||||
// Let's draw highlight on top for now, maybe with alpha.
|
||||
}
|
||||
|
||||
// 선택된 마그넷 핸들 그리기
|
||||
if (magnet == _selectedNode && _canvasMode == CanvasMode.Edit)
|
||||
{
|
||||
using (var handleBrush = new SolidBrush(Color.White))
|
||||
using (var handlePen = new Pen(Color.Black, 1))
|
||||
{
|
||||
float size = HANDLE_SIZE / _zoomFactor;
|
||||
float half = size / 2;
|
||||
|
||||
// 시작점, 끝점 핸들
|
||||
g.FillRectangle(handleBrush, startPoint.X - half, startPoint.Y - half, size, size);
|
||||
g.DrawRectangle(handlePen, startPoint.X - half, startPoint.Y - half, size, size);
|
||||
g.FillRectangle(handleBrush, endPoint.X - half, endPoint.Y - half, size, size);
|
||||
g.DrawRectangle(handlePen, endPoint.X - half, endPoint.Y - half, size, size);
|
||||
|
||||
// 제어점 핸들 (곡선일 경우)
|
||||
if (magnet.ControlPoint != null)
|
||||
{
|
||||
var cp = magnet.ControlPoint;
|
||||
g.FillRectangle(handleBrush, (float)cp.X - half, (float)cp.Y - half, size, size);
|
||||
g.DrawRectangle(handlePen, (float)cp.X - half, (float)cp.Y - half, size, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,11 +512,10 @@ namespace AGVNavigationCore.Controls
|
||||
if (_marks == null) return; // _marks 리스트 사용
|
||||
|
||||
int sensorSize = 12; // 크기 설정
|
||||
int lineLength = 20; // 선 길이 설정
|
||||
int halfLength = lineLength / 2;
|
||||
|
||||
foreach (var mark in _marks)
|
||||
{
|
||||
int lineLength = (int)mark.Length; // 저장된 길이 사용
|
||||
int halfLength = lineLength / 2;
|
||||
Point p = mark.Position;
|
||||
double radians = mark.Rotation * Math.PI / 180.0;
|
||||
|
||||
@@ -514,6 +537,22 @@ namespace AGVNavigationCore.Controls
|
||||
g.DrawLine(highlightPen, p1, p2);
|
||||
}
|
||||
}
|
||||
|
||||
// 선택된 마크 핸들 그리기
|
||||
if (mark == _selectedNode && _canvasMode == CanvasMode.Edit)
|
||||
{
|
||||
using (var handleBrush = new SolidBrush(Color.White))
|
||||
using (var handlePen = new Pen(Color.Black, 1))
|
||||
{
|
||||
float size = HANDLE_SIZE / _zoomFactor;
|
||||
float half = size / 2;
|
||||
|
||||
g.FillRectangle(handleBrush, p1.X - half, p1.Y - half, size, size);
|
||||
g.DrawRectangle(handlePen, p1.X - half, p1.Y - half, size, size);
|
||||
g.FillRectangle(handleBrush, p2.X - half, p2.Y - half, size, size);
|
||||
g.DrawRectangle(handlePen, p2.X - half, p2.Y - half, size, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -109,6 +109,8 @@ namespace AGVNavigationCore.Controls
|
||||
private MapNode _connectionStartNode;
|
||||
private Point _connectionEndPoint;
|
||||
private int _mouseMoveCounter = 0; // 디버그용: MouseMove 실행 횟수
|
||||
private int _dragHandleIndex = -1; // 드래그 중인 핸들 인덱스
|
||||
private const int HANDLE_SIZE = 8; // 편집 핸들 크기
|
||||
|
||||
// 영역 선택 관련
|
||||
private bool _isAreaSelecting;
|
||||
@@ -341,6 +343,17 @@ namespace AGVNavigationCore.Controls
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public PointF PanOffset
|
||||
{
|
||||
get => _panOffset;
|
||||
set
|
||||
{
|
||||
_panOffset = value;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그리드 표시 여부
|
||||
/// </summary>
|
||||
|
||||
@@ -56,17 +56,25 @@ namespace AGVNavigationCore.Models
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 시작점 Point 반환
|
||||
/// 시작점 Point 반환 및 설정
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[JsonIgnore]
|
||||
public Point StartPoint => new Point((int)P1.X, (int)P1.Y);
|
||||
public Point StartPoint
|
||||
{
|
||||
get => new Point((int)P1.X, (int)P1.Y);
|
||||
set { P1.X = value.X; P1.Y = value.Y; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 끝점 Point 반환
|
||||
/// 끝점 Point 반환 및 설정
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[JsonIgnore]
|
||||
public Point EndPoint => new Point((int)P2.X, (int)P2.Y);
|
||||
public Point EndPoint
|
||||
{
|
||||
get => new Point((int)P2.X, (int)P2.Y);
|
||||
set { P2.X = value.X; P2.Y = value.Y; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,5 +33,9 @@ namespace AGVNavigationCore.Models
|
||||
[Category("위치 정보")]
|
||||
[Description("마크의 회전 각도")]
|
||||
public double Rotation { get; set; }
|
||||
|
||||
[Category("위치 정보")]
|
||||
[Description("마크의 길이")]
|
||||
public double Length { get; set; } = 20.0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user