fix: 줌/팬 시 마우스 좌표 변환 버그 수정
주요 변경사항: - Graphics Transform 순서 수정 (Translate → Scale) - ScreenToWorld/WorldToScreen 공식 통일 - 마우스 휠 줌 계산 수정 - FitToNodes, PanTo 계산 수정 - GetVisibleBounds 공식 수정 - 이제 줌 상태에서 노드 선택이 정확하게 작동 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -21,9 +21,10 @@ namespace AGVNavigationCore.Controls
|
|||||||
g.InterpolationMode = InterpolationMode.High;
|
g.InterpolationMode = InterpolationMode.High;
|
||||||
|
|
||||||
// 변환 행렬 설정 (줌 및 팬)
|
// 변환 행렬 설정 (줌 및 팬)
|
||||||
|
// 순서: Translate 먼저, Scale 나중 (Append로 순서 보장)
|
||||||
var transform = new Matrix();
|
var transform = new Matrix();
|
||||||
transform.Scale(_zoomFactor, _zoomFactor);
|
|
||||||
transform.Translate(_panOffset.X, _panOffset.Y);
|
transform.Translate(_panOffset.X, _panOffset.Y);
|
||||||
|
transform.Scale(_zoomFactor, _zoomFactor, System.Drawing.Drawing2D.MatrixOrder.Append);
|
||||||
g.Transform = transform;
|
g.Transform = transform;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -1716,10 +1717,12 @@ namespace AGVNavigationCore.Controls
|
|||||||
|
|
||||||
private Rectangle GetVisibleBounds()
|
private Rectangle GetVisibleBounds()
|
||||||
{
|
{
|
||||||
var left = (int)(-_panOffset.X / _zoomFactor);
|
// Graphics Transform: Screen = (World + Pan) * Zoom
|
||||||
var top = (int)(-_panOffset.Y / _zoomFactor);
|
// World = Screen / Zoom - Pan
|
||||||
var right = (int)((Width - _panOffset.X) / _zoomFactor);
|
var left = (int)(0 / _zoomFactor - _panOffset.X);
|
||||||
var bottom = (int)((Height - _panOffset.Y) / _zoomFactor);
|
var top = (int)(0 / _zoomFactor - _panOffset.Y);
|
||||||
|
var right = (int)(Width / _zoomFactor - _panOffset.X);
|
||||||
|
var bottom = (int)(Height / _zoomFactor - _panOffset.Y);
|
||||||
|
|
||||||
return new Rectangle(left, top, right - left, bottom - top);
|
return new Rectangle(left, top, right - left, bottom - top);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,8 +190,10 @@ namespace AGVNavigationCore.Controls
|
|||||||
Point mouseScreenPos = e.Location;
|
Point mouseScreenPos = e.Location;
|
||||||
|
|
||||||
// 줌 전 마우스가 가리키는 월드 좌표
|
// 줌 전 마우스가 가리키는 월드 좌표
|
||||||
float worldX_before = (mouseScreenPos.X - _panOffset.X) / _zoomFactor;
|
// Graphics Transform: Screen = (World + Pan) * Zoom
|
||||||
float worldY_before = (mouseScreenPos.Y - _panOffset.Y) / _zoomFactor;
|
// 역변환: World = Screen / Zoom - Pan
|
||||||
|
float worldX_before = mouseScreenPos.X / _zoomFactor - _panOffset.X;
|
||||||
|
float worldY_before = mouseScreenPos.Y / _zoomFactor - _panOffset.Y;
|
||||||
|
|
||||||
// 이전 줌 팩터 저장
|
// 이전 줌 팩터 저장
|
||||||
float oldZoom = _zoomFactor;
|
float oldZoom = _zoomFactor;
|
||||||
@@ -203,8 +205,10 @@ namespace AGVNavigationCore.Controls
|
|||||||
_zoomFactor = Math.Max(_zoomFactor / 1.15f, 0.1f); // 축소
|
_zoomFactor = Math.Max(_zoomFactor / 1.15f, 0.1f); // 축소
|
||||||
|
|
||||||
// 줌 후에도 마우스가 같은 월드 좌표를 가리키도록 팬 오프셋 조정
|
// 줌 후에도 마우스가 같은 월드 좌표를 가리키도록 팬 오프셋 조정
|
||||||
_panOffset.X = (int)(mouseScreenPos.X - worldX_before * _zoomFactor);
|
// mouseScreen = (worldBefore + newPan) * newZoom
|
||||||
_panOffset.Y = (int)(mouseScreenPos.Y - worldY_before * _zoomFactor);
|
// newPan = mouseScreen / newZoom - worldBefore
|
||||||
|
_panOffset.X = (int)(mouseScreenPos.X / _zoomFactor - worldX_before);
|
||||||
|
_panOffset.Y = (int)(mouseScreenPos.Y / _zoomFactor - worldY_before);
|
||||||
|
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
@@ -216,9 +220,10 @@ namespace AGVNavigationCore.Controls
|
|||||||
private Point ScreenToWorld(Point screenPoint)
|
private Point ScreenToWorld(Point screenPoint)
|
||||||
{
|
{
|
||||||
// 스크린 좌표를 월드 좌표로 변환
|
// 스크린 좌표를 월드 좌표로 변환
|
||||||
// 역순으로: 팬 오프셋 제거 → 줌 적용
|
// Graphics Transform: Screen = (World + Pan) * Zoom
|
||||||
float worldX = (screenPoint.X - _panOffset.X) / _zoomFactor;
|
// 역변환: World = Screen / Zoom - Pan
|
||||||
float worldY = (screenPoint.Y - _panOffset.Y) / _zoomFactor;
|
float worldX = screenPoint.X / _zoomFactor - _panOffset.X;
|
||||||
|
float worldY = screenPoint.Y / _zoomFactor - _panOffset.Y;
|
||||||
|
|
||||||
return new Point((int)worldX, (int)worldY);
|
return new Point((int)worldX, (int)worldY);
|
||||||
}
|
}
|
||||||
@@ -226,10 +231,10 @@ namespace AGVNavigationCore.Controls
|
|||||||
private Point WorldToScreen(Point worldPoint)
|
private Point WorldToScreen(Point worldPoint)
|
||||||
{
|
{
|
||||||
// 월드 좌표를 스크린 좌표로 변환
|
// 월드 좌표를 스크린 좌표로 변환
|
||||||
// 순서: 줌 적용 → 팬 오프셋 추가
|
// Graphics Transform: Screen = (World + Pan) * Zoom
|
||||||
return new Point(
|
return new Point(
|
||||||
(int)(worldPoint.X * _zoomFactor + _panOffset.X),
|
(int)((worldPoint.X + _panOffset.X) * _zoomFactor),
|
||||||
(int)(worldPoint.Y * _zoomFactor + _panOffset.Y)
|
(int)((worldPoint.Y + _panOffset.Y) * _zoomFactor)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,8 +749,11 @@ namespace AGVNavigationCore.Controls
|
|||||||
var centerX = (minX + maxX) / 2;
|
var centerX = (minX + maxX) / 2;
|
||||||
var centerY = (minY + maxY) / 2;
|
var centerY = (minY + maxY) / 2;
|
||||||
|
|
||||||
_panOffset.X = (int)(Width / 2 - centerX * _zoomFactor);
|
// Graphics Transform: Screen = (World + Pan) * Zoom
|
||||||
_panOffset.Y = (int)(Height / 2 - centerY * _zoomFactor);
|
// 중앙에 위치: Width/2 = (centerX + Pan) * Zoom
|
||||||
|
// Pan = Width/2 / Zoom - centerX
|
||||||
|
_panOffset.X = (int)(Width / 2 / _zoomFactor - centerX);
|
||||||
|
_panOffset.Y = (int)(Height / 2 / _zoomFactor - centerY);
|
||||||
|
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
@@ -765,8 +773,11 @@ namespace AGVNavigationCore.Controls
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void PanTo(Point worldPoint)
|
public void PanTo(Point worldPoint)
|
||||||
{
|
{
|
||||||
_panOffset.X = (int)(Width / 2 - worldPoint.X * _zoomFactor);
|
// Graphics Transform: Screen = (World + Pan) * Zoom
|
||||||
_panOffset.Y = (int)(Height / 2 - worldPoint.Y * _zoomFactor);
|
// 중앙에 위치: Width/2 = (worldPoint + Pan) * Zoom
|
||||||
|
// Pan = Width/2 / Zoom - worldPoint
|
||||||
|
_panOffset.X = (int)(Width / 2 / _zoomFactor - worldPoint.X);
|
||||||
|
_panOffset.Y = (int)(Height / 2 / _zoomFactor - worldPoint.Y);
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1382,14 +1382,12 @@ namespace AGVSimulator.Forms
|
|||||||
{
|
{
|
||||||
logItem.Success = true;
|
logItem.Success = true;
|
||||||
logItem.Message = "성공";
|
logItem.Message = "성공";
|
||||||
//logItem.DetailedPath = string.Join(" → ", currentPath.GetDetailedInfo());
|
|
||||||
logItem.DetailedPath = currentPath.GetDetailedPathInfo();
|
logItem.DetailedPath = currentPath.GetDetailedPathInfo();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logItem.Success = false;
|
logItem.Success = false;
|
||||||
logItem.Message = $"도킹 검증 실패: {dockingValidation.ValidationError}";
|
logItem.Message = $"도킹 검증 실패: {dockingValidation.ValidationError}";
|
||||||
//logItem.DetailedPath = string.Join(" → ", currentPath.GetDetailedInfo());
|
|
||||||
logItem.DetailedPath = currentPath.GetDetailedPathInfo();
|
logItem.DetailedPath = currentPath.GetDetailedPathInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user