Jack 4d94fbf54f
All checks were successful
Build and push image / Build (push) Successful in 49s
i live in spain but the S is silent
2023-09-28 00:53:27 +01:00

545 lines
22 KiB
C#
Executable File

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ksBroadcastingNetwork.Structs;
namespace ksBroadcastingTestClient.Autopilot
{
public enum CameraTypeEnum { Tv1, Tv2, RearWing, Onboard, Helicam, Pitlane, Unknown }
public interface ICamManager
{
float GetTVCamPressure(CameraTypeEnum cameraType, AutopilotCarViewModel car);
void GetPreferredCameraWithWeight(out AnyCamera preferredCamera, out float rawValue, Dictionary<CameraTypeEnum, float> camPressures, bool isFocusedCar);
}
public class AnyCamera : KSObservableObject
{
public string Set { get; }
public string Name { get; }
public CameraTypeEnum CamType { get; set; }
public bool IsActive { get => Get<bool>(); set { Set(value); NotifyUpdate(nameof(IndicatorColor)); } }
public virtual System.Windows.Media.Brush IndicatorColor
{
get
{
if (IsActive)
return System.Windows.Media.Brushes.Green;
return System.Windows.Media.Brushes.Gray;
}
}
public AnyCamera(string set, string name)
{
Set = set;
Name = name;
}
}
class TVCamJsonData
{
public string SetName { get; set; }
public string CamName { get; set; }
public float SplinePosStart { get; set; }
public float SplinePosEnd { get; set; }
public int EntrySpeed { get; set; }
public int ExitSpeed { get; set; }
public static TVCamJsonData FromCam(TVCam cam)
{
return new TVCamJsonData()
{
SetName = cam.Set,
CamName = cam.Name,
SplinePosStart = cam.SplinePosStart,
SplinePosEnd = cam.SplinePosEnd,
EntrySpeed = cam.EntrySpeed,
ExitSpeed = cam.ExitSpeed
};
}
public void UpdateTVCam(TVCam cam)
{
if (string.Equals(SetName, cam.Set) && string.Equals(CamName, cam.Name))
{
if (cam.SplinePosStart < 0f)
cam.SplinePosStart = SplinePosStart;
if (cam.SplinePosEnd < 0f)
cam.SplinePosEnd = SplinePosEnd;
if (cam.EntrySpeed <= 0f)
cam.EntrySpeed = EntrySpeed;
if (cam.ExitSpeed <= 0f)
cam.ExitSpeed = ExitSpeed;
}
}
}
public class TVCam : AnyCamera
{
public float SplinePosStart { get; internal set; } = -1f;
public float SplinePosEnd { get; internal set; } = -1f;
public override System.Windows.Media.Brush IndicatorColor
{
get
{
if (SplinePosStart < 0f)
return IsActive ? System.Windows.Media.Brushes.Brown : System.Windows.Media.Brushes.Red;
if (SplinePosEnd < 0f)
return IsActive ? System.Windows.Media.Brushes.RosyBrown : System.Windows.Media.Brushes.Orange;
return base.IndicatorColor;
}
}
public int EntrySpeed { get; internal set; }
public int ExitSpeed { get; internal set; }
public TVCam PreviousCam { get; internal set; }
public TVCam(string set, string name)
: base(set, name)
{
}
internal void SetEntry(float splinePosition, int kmh)
{
SplinePosStart = splinePosition;
EntrySpeed = kmh;
NotifyUpdate(nameof(IndicatorColor));
}
internal void SetExit(float splinePosition, int kmh)
{
SplinePosEnd = splinePosition;
ExitSpeed = kmh;
NotifyUpdate(nameof(IndicatorColor));
}
public override string ToString()
{
var startIsSet = SplinePosStart < 0f ? "-" : "|";
var endIsSet = SplinePosStart < 0f ? "|" : "|";
return $"{startIsSet}{Name}{endIsSet}";
}
}
public class CameraManagementViewModel : KSObservableObject, ICamManager
{
public Dictionary<string, List<TVCam>> TVCameraSets { get; private set; }
public Dictionary<string, List<AnyCamera>> OtherCameraSets { get; private set; }
public Dictionary<string, List<AnyCamera>> AllCameraSets { get; private set; }
TVCam OldTVCam = null;
float LastFocusedCarSplinePosition = -1f;
int LastFocusedCarSpeed = -1;
int LastFocusedCarId = -1;
private AnyCamera CurrentCam;
public DateTime CurrentCamSetActiveSince { get; private set; }
private Random R = new Random();
private Dictionary<CameraTypeEnum, DateTime> CamTypeLastActive { get; } = new Dictionary<CameraTypeEnum, DateTime>();
public float TVCamLearningProgress { get => Get<float>(); private set { if (Set(value)) NotifyUpdate(nameof(CameraState)); } }
public string CameraState
{
get
{
if (TVCameraSets == null || TVCameraSets.Any())
return $"Waiting for camera definitions";
else if (TVCamLearningProgress < 1f)
return $"Learning TV cams {TVCamLearningProgress:P0}";
else
{
return $"Current camera xy";
}
}
}
public string TrackName { get; private set; }
public float TrackMeters { get; private set; }
internal void Update(TrackData trackUpdate)
{
TrackName = trackUpdate.TrackName;
TrackMeters = trackUpdate.TrackMeters;
if (TVCameraSets == null)
{
OldTVCam = null;
TVCameraSets = new Dictionary<string, List<TVCam>>();
OtherCameraSets = new Dictionary<string, List<AnyCamera>>();
AllCameraSets = new Dictionary<string, List<AnyCamera>>();
foreach (var tvCamSet in trackUpdate.CameraSets.Where(x => x.Key.StartsWith("set")))
{
// we'll exclude the VR sets, usually can't use them but confuses the learning of TV cams
if (tvCamSet.Key.EndsWith("vr", StringComparison.InvariantCultureIgnoreCase))
continue;
TVCameraSets.Add(tvCamSet.Key, new List<TVCam>());
AllCameraSets.Add(tvCamSet.Key, new List<AnyCamera>());
var lastTvCam = (TVCam)null;
foreach (var tvCamName in tvCamSet.Value)
{
var cam = new TVCam(tvCamSet.Key, tvCamName);
if (lastTvCam != null)
cam.PreviousCam = lastTvCam;
lastTvCam = cam;
TVCameraSets[tvCamSet.Key].Add(cam);
AllCameraSets[tvCamSet.Key].Add(cam);
}
if (TVCameraSets[tvCamSet.Key].Count > 1)
TVCameraSets[tvCamSet.Key].First().PreviousCam = TVCameraSets[tvCamSet.Key].Last();
}
foreach (var camSet in trackUpdate.CameraSets.Where(x => !x.Key.StartsWith("set")))
{
OtherCameraSets.Add(camSet.Key, new List<AnyCamera>());
AllCameraSets.Add(camSet.Key, new List<AnyCamera>());
foreach (var otherCamName in camSet.Value)
{
var cam = new AnyCamera(camSet.Key, otherCamName);
OtherCameraSets[camSet.Key].Add(cam);
AllCameraSets[camSet.Key].Add(cam);
}
}
foreach (var camera in AllCameraSets.SelectMany(x => x.Value))
{
camera.CamType = EvaluateCameraType(trackUpdate.TrackName, camera.Set, camera.Name);
}
foreach (var camType in AllCameraSets.SelectMany(x => x.Value).Select(x => x.CamType).Distinct())
{
CamTypeLastActive.Add(camType, DateTime.Now.AddMinutes(-10));
}
TryLoadTVCameraDefs(TVCameraSets.SelectMany(x => x.Value), TrackName);
UpdateTVCamLearningProgress();
NotifyUpdate(nameof(AllCameraSets));
NotifyUpdate(nameof(CameraState));
}
}
private CameraTypeEnum EvaluateCameraType(string trackName, string set, string name)
{
// This should go to a cfg file or so
if (set.Contains("set1"))
return CameraTypeEnum.Tv1;
if (set.Contains("set2"))
return CameraTypeEnum.Tv2;
if (set.Contains("heli") || set.Contains("Heli"))
return CameraTypeEnum.Helicam;
if (set == "pitlane")
return CameraTypeEnum.Pitlane;
// the rest should be some kind of onboard, we only look for the rear wing one precisely
if (name == "Onboard3")
return CameraTypeEnum.RearWing;
// aeh nobody wants to see chasecams in br
if (name.Contains("chase") || name.Contains("chase"))
return CameraTypeEnum.Unknown;
return CameraTypeEnum.Onboard;
}
internal void RealtimeUpdate(RealtimeUpdate update)
{
try
{
if (AllCameraSets.ContainsKey(update.ActiveCameraSet))
{
var cam = AllCameraSets[update.ActiveCameraSet].Single(x => x.Name == update.ActiveCamera);
if (!cam.IsActive)
{
// Cam changed!
var lastCams = AllCameraSets.SelectMany(x => x.Value).Where(x => x.IsActive);
if (lastCams.Count() == 1)
{
// regular case, we have a last and new cam
var oldCam = lastCams.Single();
// To learn the stuff, we need to memorize the old cam
OldTVCam = (oldCam as TVCam);
}
else if (lastCams.Count() > 1)
Debug.WriteLine($"There are {lastCams.Count()} active cams, something went horribly wrong");
foreach (var item in lastCams)
item.IsActive = false;
cam.IsActive = true;
if (cam.CamType != CurrentCam?.CamType)
CurrentCamSetActiveSince = DateTime.Now;
CurrentCam = cam;
}
}
if (CurrentCam != null)
CamTypeLastActive[CurrentCam.CamType] = DateTime.Now;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
internal TVCam GetForcedCameraSet()
{
if (TVCamLearningProgress < 1f)
{
foreach (var item in TVCameraSets.SelectMany(x => x.Value))
{
if (item.SplinePosEnd < 0f || item.SplinePosStart < 0f)
if (item.Set == CurrentCam?.Set)
return null;
else
return item;
}
}
return null;
}
internal AnyCamera GetTVCamByIndex(int camIndex)
{
foreach (var set in TVCameraSets?.Skip(camIndex))
return set.Value.FirstOrDefault();
return null;
}
internal void RealtimeUpdateFocusedCar(int focusedCarId, float splinePosition, int kmh)
{
if (OldTVCam != null && !OldTVCam.IsActive && TVCamLearningProgress < 1f && LastFocusedCarId == focusedCarId)
{
var currentTVCam = CurrentCam as TVCam;
// Looks like we can update the end of the old cam
// BUT we need to make sure it's the one before the current cam
if (currentTVCam != null)
{
if (currentTVCam.PreviousCam == OldTVCam)
{
// Cool, now we can learn where the new one begins
if (currentTVCam.SplinePosStart < 0f)
currentTVCam.SetEntry(splinePosition, kmh);
// Additionally, the old cam may learn the exit
if (OldTVCam.SplinePosEnd < 0f)
OldTVCam.SetExit(LastFocusedCarSplinePosition, LastFocusedCarSpeed);
var oldProgress = TVCamLearningProgress;
UpdateTVCamLearningProgress();
if (oldProgress != TVCamLearningProgress && TVCamLearningProgress == 1f)
SaveTVCameraDefs(TVCameraSets.SelectMany(x => x.Value), TrackName);
}
}
}
LastFocusedCarSplinePosition = splinePosition;
LastFocusedCarSpeed = kmh;
LastFocusedCarId = focusedCarId;
}
private void UpdateTVCamLearningProgress()
{
// Now we may have an update to the LearningProcess
int tvCams = 0;
int finalizedTvCams = 0;
foreach (var item in TVCameraSets.SelectMany(x => x.Value))
{
if (item.SplinePosEnd > -1f && item.SplinePosStart > -1f)
{
// ready to go
finalizedTvCams++;
}
tvCams++;
}
if (tvCams > 0)
TVCamLearningProgress = finalizedTvCams / (float)tvCams;
else
TVCamLearningProgress = 0;
}
public float GetTVCamPressure(CameraTypeEnum cameraType, AutopilotCarViewModel car)
{
if (TVCameraSets == null)
{
Debug.WriteLine($"There are no TV cams (yet?)");
return 1f;
}
if (TVCamLearningProgress < 1f)
{
// while we learn, it's better to stick to a car and let us see all the cams as quick as possible
return 0.1f;
}
var setName = TVCameraSets.SelectMany(x => x.Value).FirstOrDefault(x => x.CamType == cameraType)?.Set;
// Complicated stuff. So the big targets are
// 1) avoid changing focus to a car inside the same TV cam, that looks just weird
// 2) avoid changing focus to a car that is at the end of the given camera zone
// Third is avoid changing focus from a car that just entered a new cam, but this is handled in the car view model
// let's find the camera this car would be in
var potentialTVCam = TVCameraSets[setName].FirstOrDefault(x => x.SplinePosStart < car.SplinePosition && x.SplinePosEnd > car.SplinePosition);
if (potentialTVCam == null)
{
// mh what's this?
return 0.1f;
}
var timeToEnd = (potentialTVCam.SplinePosEnd - car.SplinePosition) * TrackMeters / (car.Kmh / 3.6f);
// then the pressure is a function of "at least 2s", then we'll blend into full green light at 8s
var pressure = Math.Min(Math.Max(timeToEnd - 2, 0), 6) / 6f;
// Now 1): is this cars not focusing, but we would use the same cam when focused?
if (CurrentCam == potentialTVCam)
// We are the focused car, always - even if this is called during a pre-step
return Math.Max(0.8f, pressure);
// 2) How much time will we have until this camera ends?
if (car.Kmh < 20)
return 0.1f;
// TODO: we could edit the pressure to have a healthy ratio between the cam sets, otherwise it'd be random
// and set2 may win over proportionally often due to the longer scenes
return pressure;
}
public void GetPreferredCameraWithWeight(out AnyCamera preferredCamera, out float rawValue, Dictionary<CameraTypeEnum, float> camPressures, bool isFocusedCar)
{
// First we'll edit the camPressures based on settings, recent cameras and strict rules - the car didn't know shit about cams after all
foreach (var item in camPressures)
{
// apply modifiers
}
// now strict rules
var camSetActiveSinceMinutes = Convert.ToSingle(Math.Max((DateTime.Now - CurrentCamSetActiveSince).TotalMinutes, 0.5) - 0.5);
var lastCamSetSwitchSeconds = Convert.ToSingle((DateTime.Now - CurrentCamSetActiveSince).TotalSeconds);
foreach (var camType in camPressures.Keys.ToArray())
{
var isCurrentCam = camType == CurrentCam?.CamType;
var thisCamLastActiveMinutes = (DateTime.Now - CamTypeLastActive[camType]).TotalMinutes;
if (thisCamLastActiveMinutes < 5.0 && !isCurrentCam)
camPressures[camType] *= Math.Min(Convert.ToSingle(thisCamLastActiveMinutes / 5.0), 1f);
switch (camType)
{
case CameraTypeEnum.RearWing:
case CameraTypeEnum.Onboard:
{
// Additionally these are nice, but shouldn't be in too often, so we'll doubledip this calculation
if (thisCamLastActiveMinutes < 5.0 && !isCurrentCam)
{
camPressures[camType] *= Convert.ToSingle(thisCamLastActiveMinutes / 5.0);
}
}
break;
default:
break;
}
if (isCurrentCam && isFocusedCar)
{
var camIsGettingOldFactor = 1f;
if (camType == CameraTypeEnum.Tv1 || camType == CameraTypeEnum.Tv2)
camIsGettingOldFactor = 1f - Math.Min(Math.Max(camSetActiveSinceMinutes - 0.5f, 0f) / 3f, 1f);
else
camIsGettingOldFactor = 1f - Math.Min(Math.Max(camSetActiveSinceMinutes - 0.3f, 0f) / 2f, 2f);
camPressures[camType] *= camIsGettingOldFactor;
}
if (!isCurrentCam && isFocusedCar)
{
// on the other hand we don't want to switch too quickly;
if (lastCamSetSwitchSeconds < 20)
{
var camIsYoungFactor = 1f;
if (CurrentCam?.CamType == CameraTypeEnum.Tv1 || CurrentCam?.CamType == CameraTypeEnum.Tv2)
camIsYoungFactor = Math.Min(Math.Max(lastCamSetSwitchSeconds - 10f, 0f) / 30f, 1f);
else
camIsYoungFactor = Math.Min(Math.Max(lastCamSetSwitchSeconds - 5f, 0f) / 15f, 1f);
camPressures[camType] = Math.Min(camIsYoungFactor + camPressures[camType], 1f);
}
}
// In any way we do not want to focus a new car in the same camera, except for TV cams - but here we don't want to jump
// into the same or one we recently saw
if (!isFocusedCar)
{
if (camType == CurrentCam?.CamType)
camPressures[camType] = 0f;
}
}
var winner = camPressures.OrderByDescending(x => x.Value).First();
// now we'll locate the camera for that, if it's not the same
if (winner.Key == CurrentCam?.CamType || winner.Value < 0.1)
preferredCamera = null; // null means stick witch it
else
{
// otherwise, we'll select a random cam that matches the type
var candidates = AllCameraSets.SelectMany(x => x.Value).Where(x => x.CamType == winner.Key).ToArray();
if (!candidates.Any())
{
Debug.WriteLine($"Requested cam type {winner.Key} isn't available");
preferredCamera = null;
}
else
{
preferredCamera = candidates[R.Next(candidates.Length)];
}
}
rawValue = winner.Value;
}
private static void SaveTVCameraDefs(IEnumerable<TVCam> tVCameraSets, string track)
{
var defs = tVCameraSets.Select(x => TVCamJsonData.FromCam(x));
var json = Newtonsoft.Json.JsonConvert.SerializeObject(defs);
if (!System.IO.Directory.Exists("camDefs"))
System.IO.Directory.CreateDirectory("camDefs");
System.IO.File.WriteAllText($"camDefs/{track}.json", json);
}
private static void TryLoadTVCameraDefs(IEnumerable<TVCam> tVCameraSets, string track)
{
if (!System.IO.File.Exists($"camDefs/{track}.json"))
return;
var json = System.IO.File.ReadAllText($"camDefs/{track}.json");
try
{
var camDefs = Newtonsoft.Json.JsonConvert.DeserializeObject<IEnumerable<TVCamJsonData>>(json);
foreach (var item in tVCameraSets)
{
var camDef = camDefs.FirstOrDefault(x => string.Equals(x.SetName, item.Set) && string.Equals(x.CamName, item.Name));
if (camDef != null)
camDef.UpdateTVCam(item);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
}