Archived BetterFingerTracking

This commit is contained in:
SDraw 2026-06-20 15:57:51 +03:00
parent aa8def3192
commit 92357d3076
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
16 changed files with 0 additions and 0 deletions

BIN
archived/ml_bft/.github/img_01.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

View file

@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEngine;
namespace ml_bft
{
static class AssetsHandler
{
readonly static string ms_namespace = typeof(AssetsHandler).Namespace;
static readonly List<string> ms_assets = new List<string>()
{
"ovr_fingers.asset"
};
static readonly Dictionary<string, AssetBundle> ms_loadedAssets = new Dictionary<string, AssetBundle>();
static readonly Dictionary<string, GameObject> ms_loadedObjects = new Dictionary<string, GameObject>();
public static void Load()
{
Assembly l_assembly = Assembly.GetExecutingAssembly();
foreach(string l_assetName in ms_assets)
{
try
{
Stream l_assetStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + l_assetName);
if(l_assetStream != null)
{
MemoryStream l_memorySteam = new MemoryStream((int)l_assetStream.Length);
l_assetStream.CopyTo(l_memorySteam);
AssetBundle l_assetBundle = AssetBundle.LoadFromMemory(l_memorySteam.ToArray(), 0);
if(l_assetBundle != null)
{
l_assetBundle.hideFlags |= HideFlags.DontUnloadUnusedAsset;
ms_loadedAssets.Add(l_assetName, l_assetBundle);
}
else
MelonLoader.MelonLogger.Warning("Unable to load bundled '" + l_assetName + "' asset");
}
else
MelonLoader.MelonLogger.Warning("Unable to get bundled '" + l_assetName + "' asset stream");
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Warning("Unable to load bundled '" + l_assetName + "' asset, reason: " + e.Message);
}
}
}
public static GameObject GetAsset(string p_name)
{
GameObject l_result = null;
if(ms_loadedObjects.ContainsKey(p_name))
{
l_result = Object.Instantiate(ms_loadedObjects[p_name]);
l_result.SetActive(true);
l_result.hideFlags |= HideFlags.DontUnloadUnusedAsset;
}
else
{
foreach(var l_pair in ms_loadedAssets)
{
if(l_pair.Value.Contains(p_name))
{
GameObject l_bundledObject = (GameObject)l_pair.Value.LoadAsset(p_name, typeof(GameObject));
if(l_bundledObject != null)
{
ms_loadedObjects.Add(p_name, l_bundledObject);
l_result = Object.Instantiate(l_bundledObject);
l_result.SetActive(true);
l_result.hideFlags |= HideFlags.DontUnloadUnusedAsset;
}
break;
}
}
}
return l_result;
}
public static void Unload()
{
foreach(var l_pair in ms_loadedAssets)
Object.Destroy(l_pair.Value);
ms_loadedAssets.Clear();
}
}
}

View file

@ -0,0 +1,358 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.GameEventSystem;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.InputManagement;
using System.Collections.Generic;
using UnityEngine;
namespace ml_bft
{
class FingerSystem
{
enum PlaneType
{
OXZ,
OYX
}
struct RotationOffset
{
public Transform m_target;
public Transform m_source;
public Quaternion m_offset;
public void Reset()
{
m_source = null;
m_target = null;
m_offset = Quaternion.identity;
}
}
static readonly HumanBodyBones[] ms_leftFingerBones =
{
HumanBodyBones.LeftThumbProximal, HumanBodyBones.LeftThumbIntermediate, HumanBodyBones.LeftThumbDistal,
HumanBodyBones.LeftIndexProximal, HumanBodyBones.LeftIndexIntermediate, HumanBodyBones.LeftIndexDistal,
HumanBodyBones.LeftMiddleProximal, HumanBodyBones.LeftMiddleIntermediate, HumanBodyBones.LeftMiddleDistal,
HumanBodyBones.LeftRingProximal, HumanBodyBones.LeftRingIntermediate, HumanBodyBones.LeftRingDistal,
HumanBodyBones.LeftLittleProximal, HumanBodyBones.LeftLittleIntermediate, HumanBodyBones.LeftLittleDistal
};
static readonly HumanBodyBones[] ms_rightFingerBones =
{
HumanBodyBones.RightThumbProximal, HumanBodyBones.RightThumbIntermediate, HumanBodyBones.RightThumbDistal,
HumanBodyBones.RightIndexProximal, HumanBodyBones.RightIndexIntermediate, HumanBodyBones.RightIndexDistal,
HumanBodyBones.RightMiddleProximal, HumanBodyBones.RightMiddleIntermediate, HumanBodyBones.RightMiddleDistal,
HumanBodyBones.RightRingProximal, HumanBodyBones.RightRingIntermediate, HumanBodyBones.RightRingDistal,
HumanBodyBones.RightLittleProximal, HumanBodyBones.RightLittleIntermediate, HumanBodyBones.RightLittleDistal
};
static readonly (HumanBodyBones, HumanBodyBones, bool)[] ms_fingersChains =
{
(HumanBodyBones.LeftThumbProximal,HumanBodyBones.LeftThumbIntermediate,true), (HumanBodyBones.LeftThumbIntermediate, HumanBodyBones.LeftThumbDistal,true), (HumanBodyBones.LeftThumbDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftIndexProximal,HumanBodyBones.LeftIndexIntermediate,true), (HumanBodyBones.LeftIndexIntermediate, HumanBodyBones.LeftIndexDistal,true), (HumanBodyBones.LeftIndexDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftMiddleProximal,HumanBodyBones.LeftMiddleIntermediate,true), (HumanBodyBones.LeftMiddleIntermediate, HumanBodyBones.LeftMiddleDistal,true), (HumanBodyBones.LeftMiddleDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftRingProximal,HumanBodyBones.LeftRingIntermediate,true), (HumanBodyBones.LeftRingIntermediate, HumanBodyBones.LeftRingDistal,true), (HumanBodyBones.LeftRingDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftLittleProximal,HumanBodyBones.LeftLittleIntermediate,true), (HumanBodyBones.LeftLittleIntermediate, HumanBodyBones.LeftLittleDistal,true), (HumanBodyBones.LeftLittleDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.RightThumbProximal,HumanBodyBones.RightThumbIntermediate,false), (HumanBodyBones.RightThumbIntermediate, HumanBodyBones.RightThumbDistal,false), (HumanBodyBones.RightThumbDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightIndexProximal,HumanBodyBones.RightIndexIntermediate,false), (HumanBodyBones.RightIndexIntermediate, HumanBodyBones.RightIndexDistal,false), (HumanBodyBones.RightIndexDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightMiddleProximal,HumanBodyBones.RightMiddleIntermediate,false), (HumanBodyBones.RightMiddleIntermediate, HumanBodyBones.RightMiddleDistal,false), (HumanBodyBones.RightMiddleDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightRingProximal,HumanBodyBones.RightRingIntermediate,false), (HumanBodyBones.RightRingIntermediate, HumanBodyBones.RightRingDistal,false), (HumanBodyBones.RightRingDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightLittleProximal,HumanBodyBones.RightLittleIntermediate,false), (HumanBodyBones.RightLittleIntermediate, HumanBodyBones.RightLittleDistal,false),(HumanBodyBones.RightLittleDistal,HumanBodyBones.LastBone,false),
};
static readonly Vector3[] ms_directions =
{
Vector3.forward,
Vector3.back,
Vector3.left,
Vector3.right,
Vector3.up,
Vector3.down,
};
public static FingerSystem Instance { get; private set; } = null;
RotationOffset m_leftHandOffset; // From avatar hand to controller wrist
RotationOffset m_rightHandOffset;
readonly List<RotationOffset> m_leftFingerOffsets = null; // From controller finger bone to avatar finger bone
readonly List<RotationOffset> m_rightFingerOffsets = null;
public readonly float[] m_lastValues;
bool m_ready = false;
HumanPose m_pose;
internal FingerSystem()
{
if(Instance == null)
Instance = this;
m_leftFingerOffsets = new List<RotationOffset>();
m_rightFingerOffsets = new List<RotationOffset>();
m_pose = new HumanPose();
m_lastValues = new float[40];
CVRGameEventSystem.Avatar.OnLocalAvatarLoad.AddListener(this.OnAvatarSetup);
CVRGameEventSystem.Avatar.OnLocalAvatarClear.AddListener(this.OnAvatarClear);
GameEvents.OnAvatarReuse.AddListener(this.OnAvatarReuse);
GameEvents.OnIKSystemLateUpdate.AddListener(this.OnIKSystemLateUpdate);
}
internal void Cleanup()
{
if(Instance == this)
Instance = null;
m_leftFingerOffsets.Clear();
m_rightFingerOffsets.Clear();
m_ready = false;
CVRGameEventSystem.Avatar.OnLocalAvatarLoad.RemoveListener(this.OnAvatarSetup);
CVRGameEventSystem.Avatar.OnLocalAvatarClear.RemoveListener(this.OnAvatarClear);
GameEvents.OnAvatarReuse.RemoveListener(this.OnAvatarReuse);
GameEvents.OnIKSystemLateUpdate.RemoveListener(this.OnIKSystemLateUpdate);
}
internal void OnAvatarSetup(CVRAvatar p_avatar)
{
try
{
Animator l_animator = PlayerSetup.Instance.Animator;
if(l_animator.isHuman)
{
IKSystem.Instance.SetAvatarPose(IKSystem.AvatarPose.TPose);
PlayerSetup.Instance.AvatarTransform.localPosition = Vector3.zero;
PlayerSetup.Instance.AvatarTransform.localRotation = Quaternion.identity;
InputHandler.Instance.Rebind(PlayerSetup.Instance.transform.rotation);
foreach(var l_tuple in ms_fingersChains)
{
ReorientateTowards(
PlayerSetup.Instance.transform,
l_animator.GetBoneTransform(l_tuple.Item1),
(l_tuple.Item2 != HumanBodyBones.LastBone) ? l_animator.GetBoneTransform(l_tuple.Item2) : null,
InputHandler.Instance.GetSourceForBone(l_tuple.Item1, l_tuple.Item3),
InputHandler.Instance.GetSourceForBone(l_tuple.Item2, l_tuple.Item3),
PlaneType.OXZ
);
ReorientateTowards(
PlayerSetup.Instance.transform,
l_animator.GetBoneTransform(l_tuple.Item1),
(l_tuple.Item2 != HumanBodyBones.LastBone) ? l_animator.GetBoneTransform(l_tuple.Item2) : null,
InputHandler.Instance.GetSourceForBone(l_tuple.Item1, l_tuple.Item3),
InputHandler.Instance.GetSourceForBone(l_tuple.Item2, l_tuple.Item3),
PlaneType.OYX
);
}
// Bind hands
m_leftHandOffset.m_source = l_animator.GetBoneTransform(HumanBodyBones.LeftHand);
m_leftHandOffset.m_target = InputHandler.Instance.GetSourceForBone(HumanBodyBones.LeftHand, true);
if((m_leftHandOffset.m_source != null) && (m_leftHandOffset.m_target != null))
m_leftHandOffset.m_offset = Quaternion.Inverse(m_leftHandOffset.m_source.rotation) * m_leftHandOffset.m_target.rotation;
m_rightHandOffset.m_source = l_animator.GetBoneTransform(HumanBodyBones.RightHand);
m_rightHandOffset.m_target = InputHandler.Instance.GetSourceForBone(HumanBodyBones.RightHand, false);
if((m_rightHandOffset.m_source != null) && (m_rightHandOffset.m_target != null))
m_rightHandOffset.m_offset = Quaternion.Inverse(m_rightHandOffset.m_source.rotation) * m_rightHandOffset.m_target.rotation;
// Bind fingers
foreach(HumanBodyBones p_bone in ms_leftFingerBones)
{
Transform l_avatarBone = l_animator.GetBoneTransform(p_bone);
Transform l_controllerBone = InputHandler.Instance.GetSourceForBone(p_bone, true);
if((l_avatarBone != null) && (l_controllerBone != null))
{
RotationOffset l_offset = new RotationOffset();
l_offset.m_source = l_controllerBone;
l_offset.m_target = l_avatarBone;
l_offset.m_offset = Quaternion.Inverse(l_controllerBone.rotation) * l_avatarBone.rotation;
m_leftFingerOffsets.Add(l_offset);
}
}
foreach(HumanBodyBones p_bone in ms_rightFingerBones)
{
Transform l_avatarBone = l_animator.GetBoneTransform(p_bone);
Transform l_controllerBone = InputHandler.Instance.GetSourceForBone(p_bone, false);
if((l_avatarBone != null) && (l_controllerBone != null))
{
RotationOffset l_offset = new RotationOffset();
l_offset.m_source = l_controllerBone;
l_offset.m_target = l_avatarBone;
l_offset.m_offset = Quaternion.Inverse(l_controllerBone.rotation) * l_avatarBone.rotation;
m_rightFingerOffsets.Add(l_offset);
}
}
m_ready = ((m_leftFingerOffsets.Count > 0) || (m_rightFingerOffsets.Count > 0));
}
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
internal void OnAvatarClear(CVRAvatar p_avatar)
{
try
{
m_ready = false;
m_pose = new HumanPose();
m_leftHandOffset.Reset();
m_rightHandOffset.Reset();
m_leftFingerOffsets.Clear();
m_rightFingerOffsets.Clear();
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
internal void OnAvatarReuse()
{
OnAvatarClear(PlayerSetup.Instance.AvatarDescriptor);
OnAvatarSetup(PlayerSetup.Instance.AvatarDescriptor);
}
internal void OnIKSystemLateUpdate(HumanPoseHandler p_handler, Transform p_hips)
{
if(m_ready && MetaPort.Instance.isUsingVr && (p_handler != null) && Settings.SkeletalInput)
{
if(CVRInputManager.Instance.IsLeftControllerTracking())
{
Quaternion l_turnBack = (m_leftHandOffset.m_source.rotation * m_leftHandOffset.m_offset) * Quaternion.Inverse(m_leftHandOffset.m_target.rotation);
foreach(var l_offset in m_leftFingerOffsets)
l_offset.m_target.rotation = l_turnBack * (l_offset.m_source.rotation * l_offset.m_offset);
}
if(CVRInputManager.Instance.IsRightControllerTracking())
{
Quaternion l_turnBack = (m_rightHandOffset.m_source.rotation * m_rightHandOffset.m_offset) * Quaternion.Inverse(m_rightHandOffset.m_target.rotation);
foreach(var l_offset in m_rightFingerOffsets)
l_offset.m_target.rotation = l_turnBack * (l_offset.m_source.rotation * l_offset.m_offset);
}
p_handler.GetHumanPose(ref m_pose);
m_lastValues[0] = m_pose.muscles[(int)MuscleIndex.LeftThumb1Stretched];
m_lastValues[1] = m_pose.muscles[(int)MuscleIndex.LeftThumb2Stretched];
m_lastValues[2] = m_pose.muscles[(int)MuscleIndex.LeftThumb3Stretched];
m_lastValues[3] = m_pose.muscles[(int)MuscleIndex.LeftThumbSpread];
m_lastValues[4] = m_pose.muscles[(int)MuscleIndex.LeftIndex1Stretched];
m_lastValues[5] = m_pose.muscles[(int)MuscleIndex.LeftIndex2Stretched];
m_lastValues[6] = m_pose.muscles[(int)MuscleIndex.LeftIndex3Stretched];
m_lastValues[7] = m_pose.muscles[(int)MuscleIndex.LeftIndexSpread];
m_lastValues[8] = m_pose.muscles[(int)MuscleIndex.LeftMiddle1Stretched];
m_lastValues[9] = m_pose.muscles[(int)MuscleIndex.LeftMiddle2Stretched];
m_lastValues[10] = m_pose.muscles[(int)MuscleIndex.LeftMiddle3Stretched];
m_lastValues[11] = m_pose.muscles[(int)MuscleIndex.LeftMiddleSpread];
m_lastValues[12] = m_pose.muscles[(int)MuscleIndex.LeftRing1Stretched];
m_lastValues[13] = m_pose.muscles[(int)MuscleIndex.LeftRing2Stretched];
m_lastValues[14] = m_pose.muscles[(int)MuscleIndex.LeftRing3Stretched];
m_lastValues[15] = m_pose.muscles[(int)MuscleIndex.LeftRingSpread];
m_lastValues[16] = m_pose.muscles[(int)MuscleIndex.LeftLittle1Stretched];
m_lastValues[17] = m_pose.muscles[(int)MuscleIndex.LeftLittle2Stretched];
m_lastValues[18] = m_pose.muscles[(int)MuscleIndex.LeftLittle3Stretched];
m_lastValues[19] = m_pose.muscles[(int)MuscleIndex.LeftLittleSpread];
m_lastValues[20] = m_pose.muscles[(int)MuscleIndex.RightThumb1Stretched];
m_lastValues[21] = m_pose.muscles[(int)MuscleIndex.RightThumb2Stretched];
m_lastValues[22] = m_pose.muscles[(int)MuscleIndex.RightThumb3Stretched];
m_lastValues[23] = m_pose.muscles[(int)MuscleIndex.RightThumbSpread];
m_lastValues[24] = m_pose.muscles[(int)MuscleIndex.RightIndex1Stretched];
m_lastValues[25] = m_pose.muscles[(int)MuscleIndex.RightIndex2Stretched];
m_lastValues[26] = m_pose.muscles[(int)MuscleIndex.RightIndex3Stretched];
m_lastValues[27] = m_pose.muscles[(int)MuscleIndex.RightIndexSpread];
m_lastValues[28] = m_pose.muscles[(int)MuscleIndex.RightMiddle1Stretched];
m_lastValues[29] = m_pose.muscles[(int)MuscleIndex.RightMiddle2Stretched];
m_lastValues[30] = m_pose.muscles[(int)MuscleIndex.RightMiddle3Stretched];
m_lastValues[31] = m_pose.muscles[(int)MuscleIndex.RightMiddleSpread];
m_lastValues[32] = m_pose.muscles[(int)MuscleIndex.RightRing1Stretched];
m_lastValues[33] = m_pose.muscles[(int)MuscleIndex.RightRing2Stretched];
m_lastValues[34] = m_pose.muscles[(int)MuscleIndex.RightRing3Stretched];
m_lastValues[35] = m_pose.muscles[(int)MuscleIndex.RightRingSpread];
m_lastValues[36] = m_pose.muscles[(int)MuscleIndex.RightLittle1Stretched];
m_lastValues[37] = m_pose.muscles[(int)MuscleIndex.RightLittle2Stretched];
m_lastValues[38] = m_pose.muscles[(int)MuscleIndex.RightLittle3Stretched];
m_lastValues[39] = m_pose.muscles[(int)MuscleIndex.RightLittleSpread];
if(Settings.MechanimFilter && (p_hips != null))
{
// Yoinked from IKSystem.OnPostSolverUpdateGeneral
Vector3 l_pos = p_hips.position;
Quaternion l_rot = p_hips.rotation;
p_handler.SetHumanPose(ref m_pose);
p_hips.SetPositionAndRotation(l_pos, l_rot);
}
}
}
static void ReorientateTowards(Transform root, Transform p_source, Transform p_sourceEnd, Transform p_target, Transform p_targetEnd, PlaneType p_plane)
{
if((root != null) && (p_target != null) && (p_source != null))
{
Quaternion l_rootInv = Quaternion.Inverse(root.rotation);
Vector3 l_targetDir = l_rootInv * (((p_targetEnd != null) ? p_targetEnd.position : GuessEnd(p_target)) - p_target.position);
Vector3 l_sourceDir = l_rootInv * (((p_sourceEnd != null) ? p_sourceEnd.position : GuessEnd(p_source)) - p_source.position);
switch(p_plane)
{
case PlaneType.OXZ:
l_targetDir.y = 0f;
l_sourceDir.y = 0f;
break;
case PlaneType.OYX:
l_targetDir.z = 0f;
l_sourceDir.z = 0f;
break;
}
l_targetDir = Vector3.Normalize(l_targetDir);
l_sourceDir = Vector3.Normalize(l_sourceDir);
Quaternion l_targetRot = Quaternion.identity;
Quaternion l_sourceRot = Quaternion.identity;
switch(p_plane)
{
case PlaneType.OXZ:
l_targetRot = Quaternion.LookRotation(l_targetDir, Vector3.up);
l_sourceRot = Quaternion.LookRotation(l_sourceDir, Vector3.up);
break;
case PlaneType.OYX:
l_targetRot = Quaternion.LookRotation(l_targetDir, Vector3.forward);
l_sourceRot = Quaternion.LookRotation(l_sourceDir, Vector3.forward);
break;
}
Quaternion l_diff = Quaternion.Inverse(l_targetRot) * l_sourceRot;
if(p_plane == PlaneType.OYX)
l_diff = Quaternion.Euler(0f, 0f, l_diff.eulerAngles.y);
Quaternion l_adjusted = l_diff * (l_rootInv * p_target.rotation);
p_target.rotation = root.rotation * l_adjusted;
}
}
static Vector3 GuessEnd(Transform p_target)
{
Vector3 l_result = p_target.position;
if(p_target.parent != null)
{
float l_dot = -1f;
Vector3 l_axisDir = p_target.position - p_target.parent.position;
foreach(Vector3 l_dir in ms_directions)
{
Vector3 l_rotDir = p_target.rotation * l_dir;
float l_stepDot = Vector3.Dot(l_rotDir, l_axisDir);
if(l_stepDot >= l_dot)
{
l_dot = l_stepDot;
l_result = p_target.position + l_rotDir;
}
}
}
return l_result;
}
}
}

View file

@ -0,0 +1,95 @@
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.InputManagement;
using System;
using System.Reflection;
using UnityEngine;
namespace ml_bft
{
static class GameEvents
{
internal class GameEvent
{
event Action m_action;
public void AddListener(Action p_listener) => m_action += p_listener;
public void RemoveListener(Action p_listener) => m_action -= p_listener;
public void Invoke() => m_action?.Invoke();
}
internal class GameEvent<T1, T2>
{
event Action<T1, T2> m_action;
public void AddListener(Action<T1, T2> p_listener) => m_action += p_listener;
public void RemoveListener(Action<T1, T2> p_listener) => m_action -= p_listener;
public void Invoke(T1 p_objA, T2 p_objB) => m_action?.Invoke(p_objA, p_objB);
}
public static readonly GameEvent OnAvatarReuse = new GameEvent();
public static readonly GameEvent OnInputUpdate = new GameEvent();
public static readonly GameEvent<HumanPoseHandler, Transform> OnIKSystemLateUpdate = new GameEvent<HumanPoseHandler, Transform>();
internal static void Init(HarmonyLib.Harmony p_instance)
{
try
{
p_instance.Patch(
typeof(IKSystem).GetMethod(nameof(IKSystem.ReinitializeAvatar), BindingFlags.Instance | BindingFlags.Public),
null,
new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnAvatarReinitialize_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRInputManager).GetMethod("UpdateInput", BindingFlags.Instance | BindingFlags.NonPublic),
null,
new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnInputUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(IKSystem).GetMethod("LateUpdate", BindingFlags.Instance | BindingFlags.NonPublic),
null,
new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnIKSystemLateUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnAvatarReinitialize_Postfix()
{
try
{
OnAvatarReuse.Invoke();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnInputUpdate_Postfix()
{
try
{
OnInputUpdate.Invoke();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnIKSystemLateUpdate_Postfix(HumanPoseHandler ____humanPoseHandler, Transform ____hipTransform)
{
try
{
OnIKSystemLateUpdate.Invoke(____humanPoseHandler, ____hipTransform);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -0,0 +1,286 @@
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;
namespace ml_bft
{
class HandHandlerVR
{
// 31 bones in each hand, get index at Valve.VR.SteamVR_Skeleton_JointIndexes or SteamVR_Skeleton_JointIndexEnum
const int c_fingerBonesCount = (int)SteamVR_Skeleton_JointIndexEnum.pinkyAux + 1;
readonly bool m_left = false;
readonly List<Transform> m_bones = null;
readonly List<Quaternion> m_localRotations = null;
Transform m_prefabRoot = null;
readonly List<Renderer> m_renderers = null;
SteamVR_Action_Skeleton m_skeletonAction;
public HandHandlerVR(Transform p_root, bool p_left)
{
m_left = p_left;
m_bones = new List<Transform>();
m_localRotations = new List<Quaternion>();
m_renderers = new List<Renderer>();
for(int i = 0; i < c_fingerBonesCount; i++)
{
m_bones.Add(null);
m_localRotations.Add(Quaternion.identity);
}
// Fill finger transforms
m_prefabRoot = AssetsHandler.GetAsset(string.Format("assets/steamvr/models/[openvr] {0}.prefab", m_left ? "left" : "right")).transform;
m_prefabRoot.name = "[FingersTracking_VR]";
m_prefabRoot.parent = p_root;
m_prefabRoot.localPosition = Vector3.zero;
m_prefabRoot.localRotation = Quaternion.identity;
m_prefabRoot.GetComponentsInChildren(true, m_renderers);
// Ah yes, the stupid code
char l_side = (m_left ? 'l' : 'r');
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.root] = m_prefabRoot.Find("Root");
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.wrist] = m_prefabRoot.Find(string.Format("Root/wrist_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbProximal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_thumb_0_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbMiddle] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_thumb_0_{0}/finger_thumb_1_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbDistal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_thumb_0_{0}/finger_thumb_1_{0}/finger_thumb_2_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbTip] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_thumb_0_{0}/finger_thumb_1_{0}/finger_thumb_2_{0}/finger_thumb_{0}_end", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexMetacarpal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_index_meta_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexProximal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_index_meta_{0}/finger_index_0_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexMiddle] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_index_meta_{0}/finger_index_0_{0}/finger_index_1_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexDistal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_index_meta_{0}/finger_index_0_{0}/finger_index_1_{0}/finger_index_2_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexTip] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_index_meta_{0}/finger_index_0_{0}/finger_index_1_{0}/finger_index_2_{0}/finger_index_{0}_end", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleMetacarpal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_middle_meta_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleProximal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_middle_meta_{0}/finger_middle_0_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleMiddle] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_middle_meta_{0}/finger_middle_0_{0}/finger_middle_1_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleDistal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_middle_meta_{0}/finger_middle_0_{0}/finger_middle_1_{0}/finger_middle_2_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleTip] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_middle_meta_{0}/finger_middle_0_{0}/finger_middle_1_{0}/finger_middle_2_{0}/finger_middle_{0}_end", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringMetacarpal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_ring_meta_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringProximal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_ring_meta_{0}/finger_ring_0_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringMiddle] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_ring_meta_{0}/finger_ring_0_{0}/finger_ring_1_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringDistal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_ring_meta_{0}/finger_ring_0_{0}/finger_ring_1_{0}/finger_ring_2_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringTip] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_ring_meta_{0}/finger_ring_0_{0}/finger_ring_1_{0}/finger_ring_2_{0}/finger_ring_{0}_end", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyMetacarpal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_pinky_meta_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyProximal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_pinky_meta_{0}/finger_pinky_0_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyMiddle] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_pinky_meta_{0}/finger_pinky_0_{0}/finger_pinky_1_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyDistal] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_pinky_meta_{0}/finger_pinky_0_{0}/finger_pinky_1_{0}/finger_pinky_2_{0}", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyTip] = m_prefabRoot.Find(string.Format("Root/wrist_{0}/finger_pinky_meta_{0}/finger_pinky_0_{0}/finger_pinky_1_{0}/finger_pinky_2_{0}/finger_pinky_{0}_end", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbAux] = m_prefabRoot.Find(string.Format("Root/finger_thumb_{0}_aux", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexAux] = m_prefabRoot.Find(string.Format("Root/finger_index_{0}_aux", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleAux] = m_prefabRoot.Find(string.Format("Root/finger_middle_{0}_aux", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringAux] = m_prefabRoot.Find(string.Format("Root/finger_ring_{0}_aux", l_side));
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyAux] = m_prefabRoot.Find(string.Format("Root/finger_pinky_{0}_aux", l_side));
// Remember local rotations
for(int i = 0; i < c_fingerBonesCount; i++)
{
if(m_bones[i] != null)
m_localRotations[i] = m_bones[i].localRotation;
}
m_skeletonAction = SteamVR_Input.GetAction<SteamVR_Action_Skeleton>(p_left ? "SkeletonLeftHand" : "SkeletonRightHand");
OnShowHandsChanged(Settings.ShowHands);
OnMotionRangeChanged(Settings.MotionRange);
Settings.OnSkeletalInputChanged.AddListener(this.OnSkeletalInputChanged);
Settings.OnShowHandsChanged.AddListener(this.OnShowHandsChanged);
Settings.OnMotionRangeChanged.AddListener(this.OnMotionRangeChanged);
}
public void Cleanup()
{
if(m_prefabRoot != null)
Object.Destroy(m_prefabRoot.gameObject);
m_prefabRoot = null;
m_bones.Clear();
m_localRotations.Clear();
m_renderers.Clear();
m_skeletonAction = null;
Settings.OnSkeletalInputChanged.RemoveListener(this.OnSkeletalInputChanged);
Settings.OnShowHandsChanged.RemoveListener(this.OnShowHandsChanged);
Settings.OnMotionRangeChanged.RemoveListener(this.OnMotionRangeChanged);
}
public void Update()
{
if(m_skeletonAction != null)
{
var l_rotations = m_skeletonAction.GetBoneRotations();
var l_positions = m_skeletonAction.GetBonePositions();
for(int i = 0; i < c_fingerBonesCount; i++)
{
if(m_bones[i] != null)
{
m_bones[i].localRotation = l_rotations[i];
m_bones[i].localPosition = l_positions[i];
}
}
}
}
public Transform GetSourceForBone(HumanBodyBones p_bone)
{
Transform l_result = null;
switch(p_bone)
{
case HumanBodyBones.LeftHand:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.wrist] : null);
break;
case HumanBodyBones.LeftThumbProximal:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbProximal] : null);
break;
case HumanBodyBones.LeftThumbIntermediate:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbMiddle] : null);
break;
case HumanBodyBones.LeftThumbDistal:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbDistal] : null);
break;
case HumanBodyBones.LeftIndexProximal:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexProximal] : null);
break;
case HumanBodyBones.LeftIndexIntermediate:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexMiddle] : null);
break;
case HumanBodyBones.LeftIndexDistal:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexDistal] : null);
break;
case HumanBodyBones.LeftMiddleProximal:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleProximal] : null);
break;
case HumanBodyBones.LeftMiddleIntermediate:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleMiddle] : null);
break;
case HumanBodyBones.LeftMiddleDistal:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleDistal] : null);
break;
case HumanBodyBones.LeftRingProximal:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringProximal] : null);
break;
case HumanBodyBones.LeftRingIntermediate:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringMiddle] : null);
break;
case HumanBodyBones.LeftRingDistal:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringDistal] : null);
break;
case HumanBodyBones.LeftLittleProximal:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyProximal] : null);
break;
case HumanBodyBones.LeftLittleIntermediate:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyMiddle] : null);
break;
case HumanBodyBones.LeftLittleDistal:
l_result = (m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyDistal] : null);
break;
case HumanBodyBones.RightHand:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.wrist] : null);
break;
case HumanBodyBones.RightThumbProximal:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbProximal] : null);
break;
case HumanBodyBones.RightThumbIntermediate:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbMiddle] : null);
break;
case HumanBodyBones.RightThumbDistal:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.thumbDistal] : null);
break;
case HumanBodyBones.RightIndexProximal:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexProximal] : null);
break;
case HumanBodyBones.RightIndexIntermediate:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexMiddle] : null);
break;
case HumanBodyBones.RightIndexDistal:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.indexDistal] : null);
break;
case HumanBodyBones.RightMiddleProximal:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleProximal] : null);
break;
case HumanBodyBones.RightMiddleIntermediate:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleMiddle] : null);
break;
case HumanBodyBones.RightMiddleDistal:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.middleDistal] : null);
break;
case HumanBodyBones.RightRingProximal:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringProximal] : null);
break;
case HumanBodyBones.RightRingIntermediate:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringMiddle] : null);
break;
case HumanBodyBones.RightRingDistal:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.ringDistal] : null);
break;
case HumanBodyBones.RightLittleProximal:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyProximal] : null);
break;
case HumanBodyBones.RightLittleIntermediate:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyMiddle] : null);
break;
case HumanBodyBones.RightLittleDistal:
l_result = (!m_left ? m_bones[(int)SteamVR_Skeleton_JointIndexEnum.pinkyDistal] : null);
break;
}
return l_result;
}
public void Rebind(Quaternion p_base)
{
for(int i = 0; i < c_fingerBonesCount; i++)
{
if(m_bones[i] != null)
m_bones[i].localRotation = m_localRotations[i];
}
if(m_bones[(int)SteamVR_Skeleton_JointIndexEnum.root] != null)
m_bones[(int)SteamVR_Skeleton_JointIndexEnum.root].rotation = p_base * (m_left ? Quaternion.Euler(0f, -90f, -90f) : Quaternion.Euler(0f, 90f, 90f));
}
// Settings
void OnSkeletalInputChanged(bool p_state)
{
OnShowHandsChanged(Settings.ShowHands);
}
void OnShowHandsChanged(bool p_state)
{
foreach(var l_render in m_renderers)
{
if(l_render != null)
l_render.enabled = (Settings.SkeletalInput && p_state);
}
}
void OnMotionRangeChanged(Settings.MotionRangeType p_mode)
{
switch(p_mode)
{
case Settings.MotionRangeType.WithController:
m_skeletonAction?.SetRangeOfMotion(EVRSkeletalMotionRange.WithController);
break;
case Settings.MotionRangeType.WithoutController:
m_skeletonAction?.SetRangeOfMotion(EVRSkeletalMotionRange.WithoutController);
break;
}
}
}
}

View file

@ -0,0 +1,159 @@
using ABI_RC.Core.Savior;
using ABI_RC.Systems.InputManagement;
using ABI_RC.Systems.VRModeSwitch;
using UnityEngine;
namespace ml_bft
{
// Not an actual module, but can be used as one
class InputHandler
{
public static InputHandler Instance { get; private set; } = null;
bool m_active = false;
HandHandlerVR m_leftHandHandler = null;
HandHandlerVR m_rightHandHandler = null;
internal InputHandler()
{
if(Instance == null)
Instance = this;
m_active = false;
if(MetaPort.Instance.isUsingVr)
SetupHandlers();
VRModeSwitchEvents.OnCompletedVRModeSwitch.AddListener(this.OnVRModeSwitch);
Settings.OnSkeletalInputChanged.AddListener(this.OnSkeletalInputChanged);
GameEvents.OnInputUpdate.AddListener(this.OnInputUpdate);
}
internal void Cleanup()
{
if(Instance == this)
Instance = null;
RemoveHandlers();
VRModeSwitchEvents.OnCompletedVRModeSwitch.RemoveListener(this.OnVRModeSwitch);
Settings.OnSkeletalInputChanged.RemoveListener(this.OnSkeletalInputChanged);
GameEvents.OnInputUpdate.RemoveListener(this.OnInputUpdate);
}
void SetupHandlers()
{
if(!CheckVR.Instance.forceOpenXr)
{
m_leftHandHandler = new HandHandlerVR(CVRInputManager.Instance.leftHandTransform, true);
m_rightHandHandler = new HandHandlerVR(CVRInputManager.Instance.rightHandTransform, false);
m_active = true;
}
}
void RemoveHandlers()
{
m_leftHandHandler?.Cleanup();
m_leftHandHandler = null;
m_rightHandHandler?.Cleanup();
m_rightHandHandler = null;
m_active = false;
}
public void Rebind(Quaternion p_base)
{
if(m_active)
{
m_leftHandHandler?.Rebind(p_base);
m_rightHandHandler?.Rebind(p_base);
}
}
public Transform GetSourceForBone(HumanBodyBones p_bone, bool p_left)
{
Transform l_result;
if(p_left)
l_result = m_leftHandHandler?.GetSourceForBone(p_bone);
else
l_result = m_rightHandHandler?.GetSourceForBone(p_bone);
return l_result;
}
// Game events
internal void OnInputUpdate()
{
if(m_active && Settings.SkeletalInput)
{
m_leftHandHandler?.Update();
m_rightHandHandler?.Update();
CVRInputManager.Instance.individualFingerTracking = true;
CVRInputManager.Instance.finger1StretchedLeftThumb = FingerSystem.Instance.m_lastValues[0];
CVRInputManager.Instance.finger2StretchedLeftThumb = FingerSystem.Instance.m_lastValues[1];
CVRInputManager.Instance.finger3StretchedLeftThumb = FingerSystem.Instance.m_lastValues[2];
CVRInputManager.Instance.fingerSpreadLeftThumb = FingerSystem.Instance.m_lastValues[3];
CVRInputManager.Instance.finger1StretchedLeftIndex = FingerSystem.Instance.m_lastValues[4];
CVRInputManager.Instance.finger2StretchedLeftIndex = FingerSystem.Instance.m_lastValues[5];
CVRInputManager.Instance.finger3StretchedLeftIndex = FingerSystem.Instance.m_lastValues[6];
CVRInputManager.Instance.fingerSpreadLeftIndex = FingerSystem.Instance.m_lastValues[7];
CVRInputManager.Instance.finger1StretchedLeftMiddle = FingerSystem.Instance.m_lastValues[8];
CVRInputManager.Instance.finger2StretchedLeftMiddle = FingerSystem.Instance.m_lastValues[9];
CVRInputManager.Instance.finger3StretchedLeftMiddle = FingerSystem.Instance.m_lastValues[10];
CVRInputManager.Instance.fingerSpreadLeftMiddle = FingerSystem.Instance.m_lastValues[11];
CVRInputManager.Instance.finger1StretchedLeftRing = FingerSystem.Instance.m_lastValues[12];
CVRInputManager.Instance.finger2StretchedLeftRing = FingerSystem.Instance.m_lastValues[13];
CVRInputManager.Instance.finger3StretchedLeftRing = FingerSystem.Instance.m_lastValues[14];
CVRInputManager.Instance.fingerSpreadLeftRing = FingerSystem.Instance.m_lastValues[15];
CVRInputManager.Instance.finger1StretchedLeftPinky = FingerSystem.Instance.m_lastValues[16];
CVRInputManager.Instance.finger2StretchedLeftPinky = FingerSystem.Instance.m_lastValues[17];
CVRInputManager.Instance.finger3StretchedLeftPinky = FingerSystem.Instance.m_lastValues[18];
CVRInputManager.Instance.fingerSpreadLeftPinky = FingerSystem.Instance.m_lastValues[19];
CVRInputManager.Instance.finger1StretchedRightThumb = FingerSystem.Instance.m_lastValues[20];
CVRInputManager.Instance.finger2StretchedRightThumb = FingerSystem.Instance.m_lastValues[21];
CVRInputManager.Instance.finger3StretchedRightThumb = FingerSystem.Instance.m_lastValues[22];
CVRInputManager.Instance.fingerSpreadRightThumb = FingerSystem.Instance.m_lastValues[23];
CVRInputManager.Instance.finger1StretchedRightIndex = FingerSystem.Instance.m_lastValues[24];
CVRInputManager.Instance.finger2StretchedRightIndex = FingerSystem.Instance.m_lastValues[25];
CVRInputManager.Instance.finger3StretchedRightIndex = FingerSystem.Instance.m_lastValues[26];
CVRInputManager.Instance.fingerSpreadRightIndex = FingerSystem.Instance.m_lastValues[27];
CVRInputManager.Instance.finger1StretchedRightMiddle = FingerSystem.Instance.m_lastValues[28];
CVRInputManager.Instance.finger2StretchedRightMiddle = FingerSystem.Instance.m_lastValues[29];
CVRInputManager.Instance.finger3StretchedRightMiddle = FingerSystem.Instance.m_lastValues[30];
CVRInputManager.Instance.fingerSpreadRightMiddle = FingerSystem.Instance.m_lastValues[31];
CVRInputManager.Instance.finger1StretchedRightRing = FingerSystem.Instance.m_lastValues[32];
CVRInputManager.Instance.finger2StretchedRightRing = FingerSystem.Instance.m_lastValues[33];
CVRInputManager.Instance.finger3StretchedRightRing = FingerSystem.Instance.m_lastValues[34];
CVRInputManager.Instance.fingerSpreadRightRing = FingerSystem.Instance.m_lastValues[35];
CVRInputManager.Instance.finger1StretchedRightPinky = FingerSystem.Instance.m_lastValues[36];
CVRInputManager.Instance.finger2StretchedRightPinky = FingerSystem.Instance.m_lastValues[37];
CVRInputManager.Instance.finger3StretchedRightPinky = FingerSystem.Instance.m_lastValues[38];
CVRInputManager.Instance.fingerSpreadRightPinky = FingerSystem.Instance.m_lastValues[39];
}
}
void OnVRModeSwitch(bool p_state)
{
try
{
if(Utils.IsInVR())
SetupHandlers();
else
RemoveHandlers();
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
// Settings
void OnSkeletalInputChanged(bool p_value)
{
if(!p_value)
CVRInputManager.Instance.individualFingerTracking = Utils.AreKnucklesInUse();
}
}
}

40
archived/ml_bft/Main.cs Normal file
View file

@ -0,0 +1,40 @@
using System.Collections;
namespace ml_bft
{
public class BetterFingersTracking : MelonLoader.MelonMod
{
InputHandler m_inputHandler = null;
FingerSystem m_fingerSystem = null;
public override void OnInitializeMelon()
{
AssetsHandler.Load();
GameEvents.Init(HarmonyInstance);
}
public override void OnLateInitializeMelon()
{
Settings.Init();
MelonLoader.MelonCoroutines.Start(WaitForInstances());
}
IEnumerator WaitForInstances()
{
while(ABI_RC.Systems.InputManagement.CVRInputManager.Instance == null)
yield return null;
m_inputHandler = new InputHandler();
m_fingerSystem = new FingerSystem();
}
public override void OnDeinitializeMelon()
{
m_inputHandler?.Cleanup();
m_inputHandler = null;
m_fingerSystem?.Cleanup();
m_fingerSystem = null;
}
}
}

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_bft.BetterFingersTracking), "BetterFingersTracking", "1.1.4", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

22
archived/ml_bft/README.md Normal file
View file

@ -0,0 +1,22 @@
# Better Fingers Tracking
Mod that overhauls behaviour of fingers tracking.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `BetterFingersTracking.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - Input & Key-Bindings - Better Fingers Tracking`:
* **Force SteamVR skeletal input:** forced usage of SteamVR skeletal input (works as long as controllers' driver supplies skeletal pose throught OpenVR interfaces); `false` by default
* **Motion range:** fingers tracking motion range/mode/type; `With controller` by default
* **Filter humanoid limits:** Limits fingers rotations to be valid for Unity's Mechanim; `true` by default
* Note: Enabling this option ensures that visual representation of your fingers will be same for you and remote players, but it cancels out additional finger segments rotations that can be better visually in most cases.
* **Show hands model:** shows transparent hands model (mostly as debug option); `false` by default
* **Change fingers direction at bind:** tries to allign avatar's fingers for more accurate poses; `true` by default
# Notes
* Currently supports only SteamVR environment, OpenXR support is planned.
* Fingers tracking quality is highly dependant on avatar's hand state in Unity's T-pose, possible solutions are in search.
* For Oculus Quest controllers (all versions) be sure that skeleton bindings are properly set up in SteamVR controllers bindings.
<kbd>![](.github/img_01.png)</kbd>

View file

@ -0,0 +1,30 @@
using System;
using System.IO;
using System.Reflection;
namespace ml_bft
{
static class ResourcesHandler
{
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
return l_result;
}
}
}

158
archived/ml_bft/Settings.cs Normal file
View file

@ -0,0 +1,158 @@
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_bft
{
static class Settings
{
internal class SettingEvent<T>
{
event Action<T> m_action;
public void AddListener(Action<T> p_listener) => m_action += p_listener;
public void RemoveListener(Action<T> p_listener) => m_action -= p_listener;
public void Invoke(T p_value) => m_action?.Invoke(p_value);
}
public enum MotionRangeType
{
WithController = 0,
WithoutController
}
enum ModSetting
{
SkeletalInput = 0,
MotionRange,
ShowHands,
MechanimFilter
}
public static bool SkeletalInput { get; private set; } = false;
public static MotionRangeType MotionRange { get; private set; } = MotionRangeType.WithController;
public static bool ShowHands { get; private set; } = false;
public static bool MechanimFilter { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
public static readonly SettingEvent<bool> OnSkeletalInputChanged = new SettingEvent<bool>();
public static readonly SettingEvent<MotionRangeType> OnMotionRangeChanged = new SettingEvent<MotionRangeType>();
public static readonly SettingEvent<bool> OnShowHandsChanged = new SettingEvent<bool>();
public static readonly SettingEvent<bool> OnMechanimFilterChanged = new SettingEvent<bool>();
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("BFT", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.SkeletalInput.ToString(), SkeletalInput),
ms_category.CreateEntry(ModSetting.MotionRange.ToString(), (int)MotionRange),
ms_category.CreateEntry(ModSetting.ShowHands.ToString(), ShowHands),
ms_category.CreateEntry(ModSetting.MechanimFilter.ToString(), MechanimFilter)
};
SkeletalInput = (bool)ms_entries[(int)ModSetting.SkeletalInput].BoxedValue;
MotionRange = (MotionRangeType)(int)ms_entries[(int)ModSetting.MotionRange].BoxedValue;
ShowHands = (bool)ms_entries[(int)ModSetting.ShowHands].BoxedValue;
MechanimFilter = (bool)ms_entries[(int)ModSetting.MechanimFilter].BoxedValue;
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
static System.Collections.IEnumerator WaitMainMenuUi()
{
while(ViewManager.Instance == null)
yield return null;
while(ViewManager.Instance.cohtmlView == null)
yield return null;
while(ViewManager.Instance.cohtmlView.Listener == null)
yield return null;
ViewManager.Instance.cohtmlView.Listener.ReadyForBindings += () =>
{
ViewManager.Instance.cohtmlView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.cohtmlView.View.BindCall("OnDropdownUpdate_" + ms_category.Identifier, new Action<string, string>(OnDropdownUpdate));
};
ViewManager.Instance.cohtmlView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.cohtmlView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.cohtmlView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
MelonLoader.MelonCoroutines.Start(UpdateMenuSettings());
};
}
static System.Collections.IEnumerator UpdateMenuSettings()
{
while(!ViewManager.Instance.IsReady || !ViewManager.Instance.IsViewShown)
yield return null;
foreach(var l_entry in ms_entries)
ViewManager.Instance.cohtmlView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
}
static void OnToggleUpdate(string p_name, string p_value)
{
try
{
if(Enum.TryParse(p_name, out ModSetting l_setting) && bool.TryParse(p_value, out bool l_value))
{
switch(l_setting)
{
case ModSetting.SkeletalInput:
{
SkeletalInput = l_value;
OnSkeletalInputChanged.Invoke(SkeletalInput);
}
break;
case ModSetting.ShowHands:
{
ShowHands = l_value;
OnShowHandsChanged.Invoke(ShowHands);
}
break;
case ModSetting.MechanimFilter:
{
MechanimFilter = l_value;
OnMechanimFilterChanged.Invoke(MechanimFilter);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = l_value;
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnDropdownUpdate(string p_name, string p_value)
{
try
{
if(Enum.TryParse(p_name, out ModSetting l_setting) && int.TryParse(p_value, out int l_value))
{
switch(l_setting)
{
case ModSetting.MotionRange:
{
MotionRange = (MotionRangeType)l_value;
OnMotionRangeChanged.Invoke(MotionRange);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = l_value;
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

21
archived/ml_bft/Utils.cs Normal file
View file

@ -0,0 +1,21 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.InputManagement;
using System.Reflection;
using UnityEngine;
namespace ml_bft
{
static class Utils
{
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.Instance | BindingFlags.NonPublic);
public static void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => (ms_view?.GetValue(p_instance) as cohtml.Net.View)?.ExecuteScript(p_script);
public static bool IsInVR() => ((MetaPort.Instance != null) && MetaPort.Instance.isUsingVr);
public static bool AreKnucklesInUse() => ((CVRInputManager.Instance._leftController == ABI_RC.Systems.InputManagement.XR.eXRControllerType.Index) || (CVRInputManager.Instance._rightController == ABI_RC.Systems.InputManagement.XR.eXRControllerType.Index));
}
}

View file

@ -0,0 +1,100 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>BetterFingersTracking</PackageId>
<Authors>SDraw</Authors>
<Company>SDraw</Company>
<Product>BetterFingersTracking</Product>
<Version>1.1.4</Version>
<AssemblyName>BetterFingersTracking</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>embedded</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="resources/mod_menu.js" />
<EmbeddedResource Include="resources/ovr_fingers.asset" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="../js/mods_extension.js" Link="resources/mods_extension.js" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>$(CVRPath)/MelonLoader/net35/0Harmony.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/Assembly-CSharp.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/cohtml.Net.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/Cohtml.Runtime.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>$(CVRPath)/MelonLoader/net35/MelonLoader.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="SteamVR">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/SteamVR.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Unity.XR.Hands">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/Unity.XR.Hands.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="Unity.XR.OpenVR">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/Unity.XR.OpenVR.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="Unity.XR.OpenXR">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/Unity.XR.OpenXR.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.AnimationModule.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AssetBundleModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.AssetBundleModule.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.CoreModule.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.XRModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.XRModule.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;$(CVRPath)/Mods/&quot;" />
</Target>
</Project>

View file

@ -0,0 +1,46 @@
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Better Fingers Tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Force SteamVR skeletal input: </div>
<div class ="option-input">
<div id="SkeletalInput" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Motion range: </div>
<div class ="option-input">
<div id="MotionRange" class ="inp_dropdown no-scroll" data-options="0:With controller,1:Without controller" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Filter humanoid limits: </div>
<div class ="option-input">
<div id="MechanimFilter" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Show hands model: </div>
<div class ="option-input">
<div id="ShowHands" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
`;
document.getElementById('settings-input').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('BFT', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_BFT'));
// Dropdowns
for (let l_dropdown of l_block.querySelectorAll('.inp_dropdown'))
modsExtension.addSetting('BFT', l_dropdown.id, modsExtension.createDropdown(l_dropdown, 'OnDropdownUpdate_BFT'));
}

Binary file not shown.

Binary file not shown.