Files
better-genshin-impact/BetterGenshinImpact/ViewModel/Windows/MapViewerViewModel.cs

263 lines
9.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>
/// TODO 需要支持更多地图
/// </summary>
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<PropertyChangedMessage<object>>(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;
}
}
/// <summary>
///
/// </summary>
/// <param name="pathingTask"></param>
/// <returns>2048级别的图像</returns>
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<Point> 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);
}
}