using Dto;
using Emgu.CV.Flann;
using Enum;
using HandyControl.Controls;
using SharpDX.Direct3D9;
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 Wpf_AiSportsMicrospace.Service;
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.Sports;
using Yztob.AiSports.Sensors.Abstractions;
using Yztob.AiSports.Sensors.Things;
namespace Wpf_AiSportsMicrospace.Views
{
///
/// GroupJumpRope.xaml 的交互逻辑
///
public partial class GroupJumpRope : UserControl
{
private Main _mainWin => Application.Current.MainWindow as Main;
private MediaPlayer _mediaPlayer = new MediaPlayer();
List RankingItemList = new();
private readonly SportDetectionQueue _detectQueue;
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 _jumpStatus = new Dictionary();
private GroupJumpRopeContext _groupJumpRopeContext;
public GroupJumpRope()
{
InitializeComponent();
_detectQueue = new SportDetectionQueue();
Loaded += UserControl_Loaded;
Unloaded += UserControl_Unloaded;
_groupJumpRopeContext = new GroupJumpRopeContext();
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
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)
{
var 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),
};
// 增加图片的大小,调整比例
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 void OnHumanFrameUpdated(object sender, List humans)
{
try
{
if (humans == null || humans.Count == 0) return;
switch (_currentGameState)
{
case GameState.NotStarted: // 未开始
int rightWaving = DetectRightHandRaise(humans);
if (rightWaving >= 3)
{
switch (rightWaving)
{
case (int)WavingAction.FirstHand:
StartCountdown(3);
break;
case (int)WavingAction.Raising:
UpdateCountdown();
break;
case (int)WavingAction.RaiseHand:
FinishCountdown();
_currentGameState = GameState.Running; // 倒计时完成 → 游戏开始
break;
}
}
break;
case GameState.Running: // 游戏进行中
UpdateCircleCounts(humans);
// 可选:判断游戏结束条件,将状态切换到 Finished
// if (CheckGameFinished()) _currentGameState = GameState.Finished;
break;
case GameState.Finished: // 游戏完成
int leftWaving = DetectLeftHandRaise(humans);
if (leftWaving == 5)
{
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
_mainWin.WebcamClient.StopExtract();
// 举左手逻辑,例如结束动画或退出
var newPage = new Home();
_mainWin?.SwitchPageWithMaskAnimation(newPage, true);
}
break;
}
}
catch (Exception ex)
{
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
}
}
private int _currentCountdown = 3;
private DateTime _lastUpdateTime = DateTime.Now;
private void StartCountdown(int start = 3)
{
_mainWin.ShowCountDownAnimation();
//_currentCountdown = start;
//countdownText.Text = _currentCountdown.ToString();
//countdownGrid.Visibility = Visibility.Visible;
//_lastUpdateTime = DateTime.Now;
Utils.PlayBackgroundMusic("countdown_3.mp3", false);
}
private void UpdateCountdown()
{
if ((DateTime.Now - _lastUpdateTime).TotalSeconds >= 1)
{
_lastUpdateTime = DateTime.Now;
_currentCountdown--;
if (_currentCountdown > 0)
{
countdownText.Text = _currentCountdown.ToString();
}
else
{
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";
});
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
_mainWin.WebcamClient.StopExtract();
RankingItemList = _groupJumpRopeContext.UpdateRankList();
ShowRankingBoard(RankingItemList);
Utils.StopBackgroundMusic();
_currentGameState = GameState.Finished;
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
_mainWin.WebcamClient.StartExtract();
}
private DateTime? _raiseStartTime;
private bool _firstHandTriggered;
private int _lastCountdownSecond = 3;
private DateTime? _countdownStartTime;
public int DetectRightHandRaise(List humans)
{
if (humans == null || humans.Count == 0)
return (int)WavingAction.None;
foreach (var human in humans)
{
if (human?.Keypoints == null)
continue;
// --- 筛选右脚踝坐标 ---
var rightAnkle = human.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
if (rightAnkle == null)
continue;
double xNorm = rightAnkle.X / 1920;
double yNorm = rightAnkle.Y / 1080;
// 仅检测中心区域内的人
if (!(xNorm >= 0.44 && xNorm <= 0.57 && yNorm >= 0.81))
continue;
// --- 获取右臂关键点 ---
var rightWrist = human.Keypoints.FirstOrDefault(k => k.Name == "right_wrist");
var rightElbow = human.Keypoints.FirstOrDefault(k => k.Name == "right_elbow");
const double raiseThreshold = 60; // 举手阈值
const double holdDuration = 1; // 持续时间(秒)
const int countdownSeconds = 3; // 倒计时长度
bool handRaised = false;
if (rightWrist != null && rightElbow != null)
{
double verticalRise = rightElbow.Y - rightWrist.Y;
handRaised = verticalRise >= raiseThreshold;
}
// ---------- 第一步:持续举手触发倒计时 ----------
if (!_firstHandTriggered)
{
if (handRaised)
{
_raiseStartTime ??= DateTime.Now;
var holdElapsed = (DateTime.Now - _raiseStartTime.Value).TotalSeconds;
if (holdElapsed >= holdDuration)
{
// 举手达到指定时间,启动倒计时
_firstHandTriggered = true;
_countdownStartTime = DateTime.Now;
_lastCountdownSecond = countdownSeconds;
return (int)WavingAction.FirstHand;
}
}
else
{
// 手放下,重置举手开始时间
_raiseStartTime = DateTime.Now;
}
continue; // 本人未触发,检测下一个人
}
// ---------- 第二步:倒计时逻辑 ----------
var countdownElapsed = (DateTime.Now - _countdownStartTime.Value).TotalSeconds;
int currentSecond = countdownSeconds - (int)Math.Floor(countdownElapsed);
if (currentSecond > 0 && currentSecond != _lastCountdownSecond)
{
_lastCountdownSecond = currentSecond;
Console.WriteLine($"倒计时:{currentSecond}");
return (int)WavingAction.Raising; // 倒计时中
}
if (countdownElapsed >= countdownSeconds)
{
ResetRaiseState();
return (int)WavingAction.RaiseHand; // 举手完成
}
return (int)WavingAction.Raising;
}
// 没有任何中心区域内的人举手
return (int)WavingAction.None;
}
public int DetectLeftHandRaise(List humans)
{
if (humans == null || humans.Count == 0)
return (int)WavingAction.None;
foreach (var human in humans)
{
if (human?.Keypoints == null)
continue;
// --- 筛选右脚踝坐标 ---
var rightAnkle = human.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
if (rightAnkle == null)
continue;
double xNorm = rightAnkle.X / 1920;
double yNorm = rightAnkle.Y / 1080;
// 仅检测中心区域内的人
if (!(xNorm >= 0.44 && xNorm <= 0.57 && yNorm >= 0.81))
continue;
// 获取左手关键点
var leftWrist = human.Keypoints.FirstOrDefault(k => k.Name == "left_wrist");
var leftElbow = human.Keypoints.FirstOrDefault(k => k.Name == "left_elbow");
if (leftWrist == null || leftElbow == null)
continue;
const double raiseThreshold = 60; // 举手阈值
// 判断左手是否举起
double verticalRise = leftElbow.Y - leftWrist.Y;
if (verticalRise >= raiseThreshold)
{
return (int)WavingAction.RaiseHand; // 一旦检测到左手举起,立即返回
}
}
return (int)WavingAction.None; // 没有检测到举手
}
private void ResetRaiseState()
{
_raiseStartTime = null;
_countdownStartTime = null;
_firstHandTriggered = false;
_lastCountdownSecond = 3;
}
private void DrawCirclesWithText()
{
userBox.Children.Clear();
_groupJumpRopeContext.Sports.Clear();
_groupJumpRopeContext.UserList.Clear();
_groupJumpRopeContext.UserNumberList.Clear();
double imgWidth = userBox.ActualWidth;
double imgHeight = userBox.ActualHeight;
double radius = 100;
for (int i = 0; i < _groupJumpRopeContext.CirclePositions.Count; i++)
{
var pos = _groupJumpRopeContext.CirclePositions[i];
double x = pos.XNorm * imgWidth;
double y = pos.YNorm * imgHeight;
// 绘制发光圆
var userItem = AddUserItem(x, y, i);
// 绑定运动对象
var sport = SportBase.Create("rope-skipping");
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 humans)
{
double radiusNormX = 0.07;
double radiusNormY = 0.14;
for (int i = 0; i < _groupJumpRopeContext.CirclePositions.Count; i++)
{
var circleX = _groupJumpRopeContext.CirclePositions[i].XNorm;
var circleY = _groupJumpRopeContext.CirclePositions[i].YNorm;
Human humanInCircle = null;
// 找圈内的人
foreach (var hu in humans)
{
var rightFoot = hu.Keypoints.FirstOrDefault(k => k.Name == "right_ankle");
var leftFoot = hu.Keypoints.FirstOrDefault(k => k.Name == "left_ankle");
if (rightFoot == null || leftFoot == null)
continue;
double xRightNorm = rightFoot.X / userBox.ActualWidth;
double xLeftNorm = leftFoot.X / userBox.ActualWidth;
double yRightNorm = rightFoot.Y / userBox.ActualHeight;
double yLeftNorm = leftFoot.Y / userBox.ActualHeight;
bool outOfCircle =
xRightNorm < (circleX - radiusNormX) || xRightNorm > (circleX + radiusNormX) ||
xLeftNorm < (circleX - radiusNormX) || xLeftNorm > (circleX + radiusNormX) ||
yRightNorm < (circleY - radiusNormY) || yRightNorm > (circleY + radiusNormY) ||
yLeftNorm < (circleY - radiusNormY) || yLeftNorm > (circleY + radiusNormY);
if (!outOfCircle)
{
humanInCircle = hu;
break; // 每圈只处理一个人
}
}
// 根据是否有人和跳绳数字判断状态
bool hasHuman = humanInCircle != null;
lock (_updateLock)
{
_groupJumpRopeContext.UserList[i].ImageState = GetJumpState(i, hasHuman);
}
// 推送计数
if (hasHuman)
_groupJumpRopeContext.Sports[i].Pushing(humanInCircle);
}
}
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 - 265, centerY - 500, 0, 0);
userBox.Children.Add(userItem);
_groupJumpRopeContext.UserList.Add(userItem);
_groupJumpRopeContext.UserNumberList.Add("0");
return userItem; //
}
public void ShowRankingBoard(List 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 HideElement(string name , int time)
{
var Element = FindName(name) as Grid;
if (Element == null) return;
var fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(time));
fadeOut.Completed += (s, e) => Element.Visibility = Visibility.Collapsed;
Element.BeginAnimation(UIElement.OpacityProperty, fadeOut);
}
/*
* 控制元素限时(渐隐动画
* 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);
}
}
}