sdraw_mods_cvr/ml_vet/Main.cs
2025-05-21 02:15:01 +03:00

171 lines
7 KiB
C#

using ABI_RC.Core;
using ABI_RC.Core.Player;
using ABI_RC.Core.Player.EyeMovement;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.FaceTracking;
using ABI_RC.Systems.FaceTracking.Impl;
using System.Reflection;
using System.Threading;
using UnityEngine;
using ViveSR.anipal.Eye;
namespace ml_vet
{
public class ViveEyeTracking : MelonLoader.MelonMod
{
const string c_gameSettingName = "ImplementationVRViveFaceTracking";
static ViveEyeTracking ms_instance = null;
readonly object m_lock = new object();
EyeData_v2 m_threadEyeData; // Shared between threads
SingleEyeData m_combinedEyeData;
SingleEyeData m_leftEyeData;
SingleEyeData m_rightEyeData;
Vector2 m_openness = Vector2.one;
Vector3 m_gazeDirection = Vector3.forward;
bool m_faceTrackingEnabled = false;
public override void OnInitializeMelon()
{
ms_instance = this;
Settings.Init();
HarmonyInstance.Patch(
typeof(SRanipalTrackingModule).GetMethod(nameof(SRanipalTrackingModule.Initialize), BindingFlags.Public | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(ViveEyeTracking).GetMethod(nameof(SRanipalTrackingModuleUpdate_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(typeof(EyeMovementController).GetMethod("Update", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(ViveEyeTracking).GetMethod(nameof(EyeMovementControllerUpdate_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("UpdatePlayerAvatarMovementData", BindingFlags.Instance | BindingFlags.NonPublic),
null,
new HarmonyLib.HarmonyMethod(typeof(ViveEyeTracking).GetMethod(nameof(OnPlayerAvatarMovementDataUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForRootLogic(HarmonyInstance));
}
System.Collections.IEnumerator WaitForRootLogic(HarmonyLib.Harmony p_harmony)
{
while(RootLogic.Instance == null)
yield return null;
while(MetaPort.Instance == null)
yield return null;
p_harmony.Patch(
typeof(FaceTrackingManager).GetMethod(nameof(FaceTrackingManager.SubmitNewEyeData), BindingFlags.Public | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(ViveEyeTracking).GetMethod(nameof(FaceTrackingManagerSubmitNewEyeData_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
m_faceTrackingEnabled = MetaPort.Instance.settings.GetSettingsBool(c_gameSettingName);
MetaPort.Instance.settings.settingBoolChanged.AddListener(this.OnGameSettingBoolChanged);
}
public override void OnDeinitializeMelon()
{
ms_instance = null;
}
public override void OnUpdate()
{
if(Settings.Enabled && m_faceTrackingEnabled && Utils.IsInVR())
{
if(Monitor.TryEnter(m_lock))
{
m_combinedEyeData = m_threadEyeData.verbose_data.combined.eye_data;
m_leftEyeData = m_threadEyeData.verbose_data.left;
m_rightEyeData = m_threadEyeData.verbose_data.right;
Monitor.Exit(m_lock);
}
if(m_combinedEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_GAZE_DIRECTION_VALIDITY))
{
m_gazeDirection = m_combinedEyeData.gaze_direction_normalized;
m_gazeDirection.x *= -1f;
}
if(m_leftEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_EYE_OPENNESS_VALIDITY))
m_openness.x = m_leftEyeData.eye_openness;
if(m_rightEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_EYE_OPENNESS_VALIDITY))
m_openness.y = m_rightEyeData.eye_openness;
}
}
void OnGameSettingBoolChanged(string p_name, bool p_state)
{
if(p_name == c_gameSettingName)
m_faceTrackingEnabled = p_state;
}
bool IsReadyForUpdate() => (Settings.Enabled && m_faceTrackingEnabled && FaceTrackingManager.Instance.IsEyeDataAvailable());
// Patches
static void SRanipalTrackingModuleUpdate_Prefix(ref bool useEye, bool useLip)
{
// Hijack face tracking module to be eye tracking module as well, because it is SRanipal too and can initialize without issues
if(useLip && Settings.Enabled)
useEye = true;
}
static void FaceTrackingManagerSubmitNewEyeData_Postfix(ref EyeData_v2 eyeData) => ms_instance?.OnEyeDataPostSubmit(ref eyeData);
void OnEyeDataPostSubmit(ref EyeData_v2 p_data)
{
// This is called not in main thread
try
{
Monitor.Enter(m_lock);
m_threadEyeData = p_data;
Monitor.Exit(m_lock);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void EyeMovementControllerUpdate_Postfix(ref EyeMovementController __instance) => ms_instance?.OnEyeMovementControllerPostUpdate(__instance);
void OnEyeMovementControllerPostUpdate(EyeMovementController p_controller)
{
try
{
if(p_controller.IsLocal && IsReadyForUpdate())
{
p_controller.manualBlinking = true;
p_controller.blinkProgress = 1f - Mathf.Clamp01((m_openness.x + m_openness.y) * 0.5f);
p_controller.manualViewTarget = true;
p_controller.targetViewPosition = p_controller.ViewPointTransform.position + p_controller.ViewPointTransform.rotation * (m_gazeDirection * 5f);
}
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPlayerAvatarMovementDataUpdate_Postfix(ref PlayerSetup __instance, PlayerAvatarMovementData ____playerAvatarMovementData) => ms_instance?.OnPlayerAvatarMovementDataPostUpdate(__instance, ____playerAvatarMovementData);
void OnPlayerAvatarMovementDataPostUpdate(PlayerSetup p_instance, PlayerAvatarMovementData p_data)
{
try
{
if(IsReadyForUpdate() && (p_instance.EyeMovementController != null))
{
p_data.EyeTrackingBlinkProgressLeft = 1f - Mathf.Clamp01(m_openness.x);
p_data.EyeTrackingBlinkProgressRight = 1f - Mathf.Clamp01(m_openness.y);
}
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}