2026-03-24 17:00:27 +08:00

595 lines
22 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 AiSportsMicrospaceDB.DBContext;
using Common;
using Dto;
using Emgu.CV.Flann;
using Emgu.CV.Structure;
using Enum;
using HandyControl.Controls;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SharpDX.Direct3D9;
using SportExpand;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
using System.Windows.Threading;
using Wpf_AiSportsMicrospace.Common;
using Wpf_AiSportsMicrospace.Dto;
using Wpf_AiSportsMicrospace.Enum;
using Wpf_AiSportsMicrospace.MyUserControl;
using WpfAnimatedGif;
using Yztob.AiSports.Common;
using Yztob.AiSports.Common.Implement;
using Yztob.AiSports.Inferences.Abstractions;
using Yztob.AiSports.Inferences.Things;
using Yztob.AiSports.Postures;
using Yztob.AiSports.Postures.Sports;
using Yztob.AiSports.Sensors.Abstractions;
using Yztob.AiSports.Sensors.Things;
using static System.Formats.Asn1.AsnWriter;
namespace Wpf_AiSportsMicrospace.Views
{
/// <summary>
/// GroupJumpRope.xaml 的交互逻辑
/// </summary>
public partial class GroupJumpRope : UserControl
{
private Main _mainWin => Application.Current.MainWindow as Main;
private MediaPlayer _mediaPlayer = new MediaPlayer();
List<RankItem> RankingItemList = new();
SportOperate sportOperate = new SportOperate();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private GameState _currentGameState = GameState.NotStarted;
private readonly object _updateLock = new object();
private readonly Dictionary<int, (string lastNumber, DateTime lastChangeTime, string currentState)> _jumpStatus = new Dictionary<int, (string, DateTime, string)>();
private GroupJumpRopeContext _groupJumpRopeContext;
Image tipImage;
public GroupJumpRope()
{
InitializeComponent();
Loaded += UserControl_Loaded;
Unloaded += UserControl_Unloaded;
_groupJumpRopeContext = new GroupJumpRopeContext();
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// 使用局部变量保存事件处理方法,以便解绑
Action<VideoFrame> handler = null;
var app = (App)Application.Current;
using var scope = App.AppHost.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 获取配置
var pointCount = await db.BasicConfig
.Where(x => x.Code == AppSettings.Code)
.Select(x => x.HomeWidth)
.FirstOrDefaultAsync();
var cfg = await db.GroupPoints.FirstOrDefaultAsync(x => x.Code == AppSettings.Code);
if (pointCount == 0 || cfg == null)
{
HandyControl.Controls.MessageBox.Error("配置加载失败!", "提示");
return;
}
_groupJumpRopeContext.CirclePositions = Utils.GetPointsList(pointCount, cfg);
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
_mainWin.WebcamClient.StartExtract();
DrawCirclesWithText();
// 播放音乐
PlayMusic("raisehand.mp3");
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
}
private void PlayMusic(string musicFileName)
{
// 获取项目根目录
string projectRoot = System.IO.Path.Combine(AppContext.BaseDirectory, @"..\..\..");
string musicPath = System.IO.Path.Combine(projectRoot, "Resources", "Music", musicFileName);
string imgPath = System.IO.Path.Combine(projectRoot, "Resources", "Img", "提示图.png");
if (!File.Exists(musicPath))
{
Console.WriteLine($"音乐文件不存在: {musicPath}");
return;
}
_mediaPlayer.Open(new Uri(musicPath, UriKind.Absolute));
ShowCenterTip(imgPath, TimeSpan.FromSeconds(3));
// 监听播放完成事件
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
//_mainWin.WebcamClient.StartExtract();
_mediaPlayer.Play();
}
private void ShowCenterTip(string imagePath, TimeSpan duration)
{
tipImage = new Image
{
Source = new BitmapImage(new Uri(imagePath, UriKind.Absolute)),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Opacity = 0,
Margin = new Thickness(0, -100, 0, 0),
Name= "TipImage"
};
// 增加图片的大小,调整比例
tipImage.Width = 1920 * 0.9; // 宽度为 Canvas 宽度的 90%
tipImage.Height = 1080 * 0.6; // 高度为 Canvas 高度的 60%
// 将图片添加到 Overlay Canvas
userBox.Children.Add(tipImage);
// 渐变出现动画
var fadeInAnimation = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromSeconds(1.5)
};
tipImage.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation);
// 定时移除,并且渐变消失
//Task.Delay(duration).ContinueWith(_ =>
//{
// Dispatcher.Invoke(() =>
// {
// // 渐变消失动画
// var fadeOutAnimation = new DoubleAnimation
// {
// From = 1,
// To = 0,
// Duration = TimeSpan.FromSeconds(1.5)
// };
// tipImage.BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation);
// // 完成后移除图片
// fadeOutAnimation.Completed += (s, e) =>
// {
// userBox.Children.Remove(tipImage);
// };
// });
//});
}
private void MediaPlayer_MediaEnded(object sender, EventArgs e)
{
// 音乐播放完成后的逻辑
Console.WriteLine("音乐播放完成!");
// 可在这里绑定抽帧事件
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
}
private bool _isCountdownStarted = false; // 新增标记
private void OnHumanFrameUpdated(object sender, List<Human> humans)
{
if (humans == null || humans.Count == 0) return;
switch (_currentGameState)
{
case GameState.NotStarted: // 未开始
if (!_isCountdownStarted) // 只有倒计时未开始才检测左手
{
int leftWaving = sportOperate.VerifyLeftWavingAction(humans);
if (leftWaving == 5)
{
// 举左手逻辑,例如结束动画或退出
_isCountdownStarted = true;
var newPage = new Home();
_mainWin?.SwitchPageWithMaskAnimation(newPage, true);
return; // 提前退出
}
}
int rightWaving = sportOperate.VerifyRightWavingAction(humans);
if (rightWaving >= 3 && RankingGrid.Visibility != Visibility.Visible)
{
switch (rightWaving)
{
case (int)WavingAction.FirstHand:
_currentGameState = GameState.Running;
StartCountdown(3);
_isCountdownStarted = true; // 倒计时开始,停止左手检测
break;
}
}
break;
case GameState.Running: // 游戏进行中
UpdateCircleCounts(humans);
break;
}
}
private async void StartCountdown(int start = 3)
{
tipImage.Visibility = Visibility.Collapsed;
tipImage.Opacity = 0;
userBox.Children.Remove(tipImage);
_mainWin.ShowCountDownAnimation();
//_currentCountdown = start;
Utils.PlayBackgroundMusic("countdown_3.mp3", false);
//开始人脸识别=============================================
//Action<VideoFrame> handler = null;
//handler = async (frame) =>
//{
// var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
// double[] xValues = [.. _groupJumpRopeContext.CirclePositions.Select(p => p.XNorm)];
// var studentList = Utils.FacialRecognition(buffer, xValues);
// if (studentList != null && studentList.Count > 0)
// {
// // 解绑事件
// _mainWin.FacialRecognitionEvent -= handler;
// for (int i = 0; i < studentList.Count; i++)
// {
// var student = studentList[i];
// if (student != null)
// {
// _groupJumpRopeContext.UserList[i].DisplayText = student.StudentName;
// }
// }
// }
// // 这里可以继续处理 studentList
//};
//// 绑定事件
//_mainWin.FacialRecognitionEvent += handler;
await Task.Delay(3000);
FinishCountdown();
}
private async void FinishCountdown()
{
countdownText.Text = "GO!";
countdownGrid.Visibility = Visibility.Hidden;
// 播放背景音乐(循环)
Utils.PlayBackgroundMusic("homeprojectselected1.mp3", true);
// 启动60秒倒计时(独立任务)
StartGameCountdown(60);
}
private async void StartGameCountdown(int seconds)
{
countdownGrid.Visibility = Visibility.Visible;
for (int i = seconds; i >= 0; i--)
{
countdownText.Text = i.ToString();
await Task.Delay(1000);
}
countdownGrid.Visibility = Visibility.Hidden;
_groupJumpRopeContext.UserList.ForEach(x =>
{
x.ImageState = "1";
});
RankingItemList = _groupJumpRopeContext.UpdateRankList();
ShowRankingBoard(RankingItemList);
Utils.StopBackgroundMusic();
_currentGameState = GameState.NotStarted;
_isCountdownStarted = false;
}
private void DrawCirclesWithText()
{
userBox.Children.Clear();
_groupJumpRopeContext.Sports.Clear();
_groupJumpRopeContext.UserList.Clear();
_groupJumpRopeContext.UserNumberList.Clear();
for (int i = 0; i < _groupJumpRopeContext.CirclePositions.Count; i++)
{
var pos = _groupJumpRopeContext.CirclePositions[i];
double x = pos.XNorm;
double y = pos.YNorm;
// 绘制发光圆
var userItem = AddUserItem(x, y, i);
// 绑定运动对象
var sport = SportBase.Create("rope-skipping");
Optimizes.Optimizeing(sport);
int indexCopy = i;
var currentItem = userItem;
// 订阅事件
sport.OnTicked += (count, times) =>
{
// 更新UI
userItem.NumberText = count.ToString();
// 更新数字源
_groupJumpRopeContext.UserNumberList[indexCopy] = count.ToString();
// 改变状态为“跳绳中”
if (userItem.ImageState != "2")
userItem.ImageState = "2";
};
sport.Start();
_groupJumpRopeContext.Sports.Add(sport);
}
}
//private void UpdateCircleCounts(List<Human> humans)
//{
// if (humans == null || humans.Count == 0)
// return;
// double halfW = AppSettings.AreaSize.X;
// double halfH = AppSettings.AreaSize.Y;
// var circles = _groupJumpRopeContext.CirclePositions;
// var sports = _groupJumpRopeContext.Sports;
// int circleCount = circles.Count;
// // 每个圈匹配到的人
// Human[] circleHumans = new Human[circleCount];
// // 1️⃣ 批量匹配圈内的人(保留原验证逻辑)
// foreach (var h in humans)
// {
// for (int i = 0; i < circleCount; i++)
// {
// // 已经有人占用该圈
// if (circleHumans[i] != null)
// continue;
// var circle = circles[i];
// double cx = circle.XNorm;
// double cy = circle.YNorm;
// // ⭐ 保留你的原验证
// if (sportOperate.IsAnyFootInCircle(h, cx, cy, halfW, halfH))
// {
// circleHumans[i] = h;
// break; // 每个人只匹配一个圈
// }
// }
// }
// // 2️⃣ 推送跳绳计数
// for (int i = 0; i < circleCount; i++)
// {
// var human = circleHumans[i];
// if (human != null)
// {
// sports[i].Pushing(human);
// }
// }
//}
private void UpdateCircleCounts(List<Human> humans)
{
double halfW = AppSettings.AreaSize.X;
double halfH = AppSettings.AreaSize.Y;
int circleCount = _groupJumpRopeContext.CirclePositions.Count;
for (int i = 0; i < circleCount; i++)
{
var circle = _groupJumpRopeContext.CirclePositions[i];
double cx = circle.XNorm;
double cy = circle.YNorm;
Human targetHuman = null;
// 1️⃣ 找圈内的人(单脚命中即可)
foreach (var h in humans)
{
if (sportOperate.IsAnyFootInCircle(h, cx, cy, halfW, halfH))
{
targetHuman = h;
break; // 每个圈只认一个人
}
}
bool hasHuman = targetHuman != null;
// 3️⃣ 推送跳绳计数
if (hasHuman)
{
_groupJumpRopeContext.Sports[i].Pushing(targetHuman);
}
// 2️⃣ 更新视觉状态
lock (_updateLock)
{
_groupJumpRopeContext.UserList[i].ImageState =
GetJumpState(i, hasHuman);
}
}
}
private string GetJumpState(int circleIndex, bool humanInCircle)
{
if (!humanInCircle)
{
// 无人 → 出圈
return "3";
}
string currentNumber = _groupJumpRopeContext.UserNumberList[circleIndex];
if (!_jumpStatus.ContainsKey(circleIndex))
{
_jumpStatus[circleIndex] = (currentNumber, DateTime.Now, "1"); // 初始状态先为停止
return "1";
}
var (lastNumber, lastChangeTime, lastState) = _jumpStatus[circleIndex];
if (currentNumber != lastNumber)
{
// 数字变化 → 跳绳中
_jumpStatus[circleIndex] = (currentNumber, DateTime.Now, "2");
return "2";
}
// 数字未变化,判断是否超过 2 秒
double elapsed = (DateTime.Now - lastChangeTime).TotalSeconds;
if (elapsed >= 0.8)
{
// 超过 2 秒未变化 → 停止
_jumpStatus[circleIndex] = (currentNumber, lastChangeTime, "1");
return "1";
}
// 维持上次状态
return lastState;
}
private SportUserItem AddUserItem(double centerX, double centerY, int index)
{
var userItem = new SportUserItem();
userItem.Width = 270;
userItem.Height = 560;
userItem.DisplayText = _groupJumpRopeContext.UseNameList[index];
userItem.VerticalAlignment = VerticalAlignment.Top;
userItem.HorizontalAlignment = HorizontalAlignment.Left;
userItem.ImageState = "1";
//userItem.Margin = new Thickness(centerX - (index == 0 ? 80 : index == 6 ? 190 : 135), centerY - 540, 0, 0);
userItem.Margin = new Thickness(centerX - 25 - 100, centerY - 490, 0, 0);
userBox.Children.Add(userItem);
_groupJumpRopeContext.UserList.Add(userItem);
_groupJumpRopeContext.UserNumberList.Add("0");
return userItem; //
}
public void ShowRankingBoard(List<RankItem> list)
{
var scoreGrid = FindName("ScoreGrid") as Grid;
name1.Text = list[0].Name;
name2.Text = list[1].Name;
name3.Text = list[2].Name;
number1.Text = list[0].Number.ToString();
number2.Text = list[1].Number.ToString();
number3.Text = list[2].Number.ToString();
foreach (var item in list)
{
var grid = new Grid
{
Width = 1344,
Height = 71,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(0, 69 * item.Rank, 0, 0)
};
var border = new Border
{
Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#fff"))
};
grid.Children.Add(border);
var rankText = new TextBlock
{
Text = item.Rank.ToString(),
FontSize = 24,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
TextAlignment = TextAlignment.Center,
Width = 200
};
grid.Children.Add(rankText);
var positionText = new TextBlock
{
Text = item.Name,
FontSize = 24,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
grid.Children.Add(positionText);
var scoreText = new TextBlock
{
Text = item.Number.ToString(),
FontSize = 24,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Center,
TextAlignment = TextAlignment.Center,
Width = 200
};
grid.Children.Add(scoreText);
ScoreGrid.Children.Add(grid);
}
showElement("RankingGrid");
//var rankingGrid = FindName("RankingGrid") as Grid;
//if (rankingGrid == null) return;
//rankingGrid.Visibility = Visibility.Visible;
//var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(600));
//rankingGrid.BeginAnimation(UIElement.OpacityProperty, fadeIn);
}
/*
* 控制元素限时(渐隐动画
* time: 动画持续时间(毫秒)
* name: 元素名称
*/
public void showElement(string name, int time = 600)
{
var Element = FindName(name) as FrameworkElement;
if (Element == null) return;
Element.Visibility = Visibility.Visible;
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(time));
Element.BeginAnimation(UIElement.OpacityProperty, fadeIn);
}
}
}