using System; using System.Collections.Generic; using System.Diagnostics; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; using OpenCvSharp; using OpenCvSharp.WpfExtensions; using System.Windows.Media.Imaging; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.GameTask.AutoPathing.Model; using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum; using BetterGenshinImpact.GameTask.Common.Map; using BetterGenshinImpact.Helpers; using System.Linq; using BetterGenshinImpact.Core.Recognition.OpenCv; using BetterGenshinImpact.GameTask; using BetterGenshinImpact.GameTask.Common.Map.Maps; using BetterGenshinImpact.GameTask.Common.Map.Maps.Base; namespace BetterGenshinImpact.ViewModel.Windows; /// /// TODO 需要支持更多地图 /// public partial class MapViewerViewModel : ObservableObject { [ObservableProperty] private WriteableBitmap _mapBitmap; // private readonly Mat _all256Map = new(Global.Absolute(@"Assets/Map/mainMap256Block.png")); private Mat _currentPathingMap = new(); // 2048级别 private Rect _currentPathingRect = new(); // 2048级别 private readonly string _mapName; private Mat _mapImage; private int _scale = 1; public MapViewerViewModel(string mapName) { if (string.IsNullOrEmpty(mapName)) { mapName = nameof(MapTypes.Teyvat); } _mapName = mapName; Init(mapName); var matchingMethod = TaskContext.Instance().Config.PathingConditionConfig.MapMatchingMethod; var center = MapManager.GetMap(_mapName, matchingMethod).ConvertGenshinMapCoordinatesToImageCoordinates(new Point2f(512, 512)); _mapBitmap = ClipMat(new Point2f(center.X, center.Y)).ToWriteableBitmap(); WeakReferenceMessenger.Default.Register>(this, (sender, msg) => { if (msg.PropertyName == "SendCurrentPosition") { UIDispatcherHelper.Invoke(() => { Debug.WriteLine("更新地图位置"); try { MapBitmap.Lock(); WriteableBitmapConverter.ToWriteableBitmap(ClipMat((Point2f)msg.NewValue), MapBitmap); } catch (Exception ex) { Debug.WriteLine(ex); } finally { MapBitmap.Unlock(); } }); } else if (msg.PropertyName == "UpdateCurrentPathing") { Debug.WriteLine("更新当前追踪的路径图像"); _currentPathingMap = GenTaskMat((PathingTask)msg.NewValue); } }); } private void Init(string mapName) { if (mapName == MapTypes.Teyvat.ToString()) { _mapImage = new Mat(Global.Absolute(@"Assets/Map/Teyvat/Teyvat_0_256.png")); // 最特殊,图像坐标是 2048 级别的,路径展示窗口是 256 级别的 _scale = 2048 / 256; } else if (mapName == MapTypes.TheChasm.ToString()) { _mapImage = new Mat(Global.Absolute(@"Assets/Map/TheChasm/TheChasm_0_1024.png")); } else if (mapName == MapTypes.Enkanomiya.ToString()) { _mapImage = new Mat(Global.Absolute(@"Assets/Map/Enkanomiya/Enkanomiya_0_1024.png")); } else if (mapName == MapTypes.SeaOfBygoneEras.ToString()) { _mapImage = new Mat(Global.Absolute(@"Assets/Map/SeaOfBygoneEras/SeaOfBygoneEras_0_1024.png")); } else if (mapName == MapTypes.AncientSacredMountain.ToString()) { _mapImage = new Mat(Global.Absolute(@"Assets/Map/AncientSacredMountain/AncientSacredMountain_0_1024.png")); } else { throw new Exception("暂时不支持展示路径的地图类型:" + mapName); } } public Mat ClipMat(Point2f pos) { try { var len = 256; pos = new Point2f(pos.X - _currentPathingRect.X, pos.Y - _currentPathingRect.Y); Rect rect = new((int)pos.X - len, (int)pos.Y - len, len * 2, len * 2); // 处理越界 if (rect.X < 0) { rect.X = 0; } if (rect.Y < 0) { rect.Y = 0; } if (rect.X + rect.Width > _mapImage.Width) { rect.Width = _mapImage.Width - rect.X; } if (rect.Y + rect.Height > _mapImage.Height) { rect.Height = _mapImage.Height - rect.Y; } // 实现剪切 Mat 的逻辑 if (_currentPathingMap.Empty()) { Debug.WriteLine("_currentPathingMap 未初始化"); return new Mat(_mapImage, new Rect(rect.X / _scale, rect.Y / _scale, rect.Width, rect.Height)); } else { Mat clipMat = new(_currentPathingMap, rect); clipMat = clipMat.Clone(); // 绘制中心点 Cv2.Circle(clipMat, new Point(len, len), 3, new Scalar(0, 255, 0), 2); return clipMat; } } catch { // 返回一张全黑的图 var blackMat = new Mat(256, 256, MatType.CV_8UC3, new Scalar(0, 0, 0)); return blackMat; } } /// /// /// /// /// 2048级别的图像 public Mat GenTaskMat(PathingTask pathingTask) { // 获取路径点位并转换为当前主要展示的地图点位 2048 级别 var points = pathingTask.Positions; var mapPoints = points.Select(ConvertToMapPoint).ToList(); var offsetRect = CalcRect(mapPoints); var offsetRectZoom = new Rect(offsetRect.X / _scale, offsetRect.Y / _scale, offsetRect.Width / _scale, offsetRect.Height / _scale); // 把 map 的局部转化为 实际展示图(提瓦特是256,其他的1024) 级别 Mat taskMat = new Mat(_mapImage, offsetRectZoom); taskMat = ResizeHelper.Resize(taskMat, _scale); // 设置线条粗细 int thickness = 2; // 绘制点位和连线 for (int i = 0; i < mapPoints.Count - 1; i++) { var startPoint = mapPoints[i] - offsetRect.TopLeft; var endPoint = mapPoints[i + 1] - offsetRect.TopLeft; var lineColor = GetLineColor(points[i], points[i + 1]); var circleColor = GetCircleColor(points[i]); // 绘制点 DrawCircle(taskMat, startPoint, circleColor, thickness); // 绘制线 DrawLine(taskMat, startPoint, endPoint, lineColor, 1); } // 绘制最后一个点 var lastPoint = mapPoints[^1] - offsetRect.TopLeft; var lastCircleColor = GetCircleColor(points[^1]); DrawCircle(taskMat, lastPoint, lastCircleColor, thickness); return taskMat; } private Rect CalcRect(List mapPoints) { // 计算其最大外接矩形 _currentPathingRect = Cv2.BoundingRect(mapPoints); // 把矩形范围扩大一半 _currentPathingRect.X -= 512; _currentPathingRect.Y -= 512; _currentPathingRect.Width += 1024; _currentPathingRect.Height += 1024; return _currentPathingRect; } private Point ConvertToMapPoint(Waypoint point) { var matchingMethod = TaskContext.Instance().Config.PathingConditionConfig.MapMatchingMethod; var (x, y) = MapManager.GetMap(_mapName, matchingMethod).ConvertGenshinMapCoordinatesToImageCoordinates(new Point2f((float)point.X, (float)point.Y)); return new Point(x, y); } private Scalar GetLineColor(Waypoint startPoint, Waypoint endPoint) { if (endPoint.Type == WaypointType.Path.Code || startPoint.Type == WaypointType.Teleport.Code) { return new Scalar(255, 0, 0); // 蓝色 } else if (endPoint.Type == WaypointType.Target.Code) { return new Scalar(0, 0, 255); // 红色 } return new Scalar(0, 0, 255); // 默认红色 } private Scalar GetCircleColor(Waypoint point) { if (point.Type == WaypointType.Path.Code || point.Type == WaypointType.Teleport.Code) { return new Scalar(255, 0, 0); // 蓝色 } else if (point.Type == WaypointType.Target.Code) { return new Scalar(0, 0, 255); // 红色 } return new Scalar(0, 0, 255); // 默认红色 } private void DrawCircle(Mat mat, Point point, Scalar color, int thickness) { Cv2.Circle(mat, point, 3, color, thickness); } private void DrawLine(Mat mat, Point startPoint, Point endPoint, Scalar color, int thickness) { Cv2.Line(mat, startPoint, endPoint, color, thickness); } }