From 8d5ddbe008d430c06ca24899c4b1e911ef56503c Mon Sep 17 00:00:00 2001 From: ChiKyun Kim Date: Tue, 16 Sep 2025 15:55:28 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=A2=8C=ED=91=9C=20=EB=B3=80=ED=99=98?= =?UTF-8?q?=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=88=98=EC=A0=95=20-=20Matri?= =?UTF-8?q?x=20=EC=97=AD=EB=B3=80=ED=99=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ScreenToWorld 함수를 Matrix 역변환 방식으로 변경하여 줌/팬 상태에서도 정확한 마우스 좌표 변환 구현. 좌측 영역 객체 선택 문제 해결. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../Controls/UnifiedAGVCanvas.Mouse.cs | 56 ++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs b/Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs index 7611d5f..69b3e9e 100644 --- a/Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs +++ b/Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs @@ -14,8 +14,6 @@ namespace AGVNavigationCore.Controls { Focus(); // 포커스 설정 - if (_canvasMode == CanvasMode.ViewOnly) return; - var worldPoint = ScreenToWorld(e.Location); var hitNode = GetNodeAt(worldPoint); @@ -45,8 +43,6 @@ namespace AGVNavigationCore.Controls private void UnifiedAGVCanvas_MouseDoubleClick(object sender, MouseEventArgs e) { - if (_canvasMode == CanvasMode.ViewOnly) return; - var worldPoint = ScreenToWorld(e.Location); var hitNode = GetNodeAt(worldPoint); @@ -63,18 +59,7 @@ namespace AGVNavigationCore.Controls if (e.Button == MouseButtons.Left) { - // 목적지 선택 모드 처리 (시뮬레이터) - if (_editMode == EditMode.SelectTarget) - { - var hitNode = GetNodeAt(worldPoint); - if (hitNode != null) - { - TargetNodeSelected?.Invoke(this, hitNode); - return; - } - } - - if (_canvasMode == CanvasMode.Edit && _editMode == EditMode.Move) + if (_editMode == EditMode.Move) { var hitNode = GetNodeAt(worldPoint); if (hitNode != null) @@ -208,10 +193,17 @@ namespace AGVNavigationCore.Controls private Point ScreenToWorld(Point screenPoint) { - return new Point( - (int)((screenPoint.X - _panOffset.X) / _zoomFactor), - (int)((screenPoint.Y - _panOffset.Y) / _zoomFactor) - ); + // 변환 행렬 생성 (렌더링과 동일) + var transform = new System.Drawing.Drawing2D.Matrix(); + transform.Scale(_zoomFactor, _zoomFactor); + transform.Translate(_panOffset.X, _panOffset.Y); + + // 역변환 행렬로 화면 좌표를 월드 좌표로 변환 + transform.Invert(); + var points = new System.Drawing.PointF[] { new System.Drawing.PointF(screenPoint.X, screenPoint.Y) }; + transform.TransformPoints(points); + + return new Point((int)points[0].X, (int)points[0].Y); } private Point WorldToScreen(Point worldPoint) @@ -268,7 +260,7 @@ namespace AGVNavigationCore.Controls // 화면에서 최소 20픽셀 정도의 히트 영역을 확보하되, 노드 크기보다 작아지지 않게 함 var minHitRadiusInScreen = 20; var hitRadius = Math.Max(NODE_RADIUS, minHitRadiusInScreen / _zoomFactor); - + var distance = Math.Sqrt( Math.Pow(node.Position.X - point.X, 2) + Math.Pow(node.Position.Y - point.Y, 2) @@ -282,7 +274,7 @@ namespace AGVNavigationCore.Controls var minHitRadiusInScreen = 20; var radius = Math.Max(NODE_RADIUS, minHitRadiusInScreen / _zoomFactor); var center = node.Position; - + // 5각형 꼭짓점 계산 var points = new Point[5]; for (int i = 0; i < 5; i++) @@ -303,7 +295,7 @@ namespace AGVNavigationCore.Controls var minHitRadiusInScreen = 20; var radius = Math.Max(NODE_RADIUS, minHitRadiusInScreen / _zoomFactor); var center = node.Position; - + // 삼각형 꼭짓점 계산 var points = new Point[3]; for (int i = 0; i < 3; i++) @@ -345,21 +337,21 @@ namespace AGVNavigationCore.Controls private bool IsPointInLabelNode(Point point, MapNode node) { var text = string.IsNullOrEmpty(node.LabelText) ? node.NodeId : node.LabelText; - + // 임시 Graphics로 텍스트 크기 측정 using (var tempBitmap = new Bitmap(1, 1)) using (var tempGraphics = Graphics.FromImage(tempBitmap)) { var font = new Font(node.FontFamily, node.FontSize, node.FontStyle); var textSize = tempGraphics.MeasureString(text, font); - + var textRect = new Rectangle( (int)(node.Position.X - textSize.Width / 2), (int)(node.Position.Y - textSize.Height / 2), (int)textSize.Width, (int)textSize.Height ); - + font.Dispose(); return textRect.Contains(point); } @@ -627,7 +619,7 @@ namespace AGVNavigationCore.Controls private (MapNode From, MapNode To)? GetConnectionAt(Point worldPoint) { const int CONNECTION_HIT_TOLERANCE = 10; - + // 모든 연결선을 확인하여 클릭한 위치와 가장 가까운 연결선 찾기 foreach (var fromNode in _nodes) { @@ -645,7 +637,7 @@ namespace AGVNavigationCore.Controls } } } - + return null; } @@ -659,11 +651,11 @@ namespace AGVNavigationCore.Controls var dot = A * C + B * D; var lenSq = C * C + D * D; - + if (lenSq == 0) return CalculateDistance(point, lineStart); - + var param = dot / lenSq; - + Point xx, yy; if (param < 0) { @@ -680,7 +672,7 @@ namespace AGVNavigationCore.Controls xx = new Point((int)(lineStart.X + param * C), (int)(lineStart.Y + param * D)); yy = xx; } - + return CalculateDistance(point, xx); }