Merge remote-tracking branch 'origin/master' into experimental

# Conflicts:
#	README.md
#	ml_amt/Main.cs
#	ml_amt/MotionTweaker.cs
#	ml_amt/Properties/AssemblyInfo.cs
#	ml_amt/README.md
#	ml_amt/Utils.cs
#	ml_lme/GestureMatcher.cs
#	ml_lme/LeapTracked.cs
#	ml_lme/Main.cs
#	ml_lme/Properties/AssemblyInfo.cs
#	ml_lme/Utils.cs
This commit is contained in:
SDraw 2023-02-13 12:05:43 +03:00
commit 7d3b76e092
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
43 changed files with 2786 additions and 834 deletions

14
README.md Normal file
View file

@ -0,0 +1,14 @@
Merged set of MelonLoader mods for ChilloutVR.
**State table for game build 2022r170:**
| Full name | Short name | Latest version | Available in [CVRMA](https://github.com/knah/CVRMelonAssistant) | Current Status | Notes |
|-----------|------------|----------------|-----------------------------------------------------------------|----------------|-------|
| Avatar Change Info | ml_aci | 1.0.3 | Retired | Retired | Superseded by `Extended Game Notifications`
| Avatar Motion Tweaker | ml_amt | 1.2.3 | Yes, update review | Working |
| Desktop Head Tracking | ml_dht | 1.1.1 | Yes | Working |
| Desktop Reticle Switch | ml_drs | 1.0.0 | Yes | Working |
| Extended Game Notifications | ml_egn | 1.0.1 | Yes | Working
| Four Point Tracking | ml_fpt | 1.0.9 | Retired | Deprecated | In-game feature since 2022r170 update
| Leap Motion Extension | ml_lme | 1.3.1 | Yes, update review | Working |
| Pickup Arm Movement | ml_pam | 1.0.1 | Retired, update review | Working |
| Server Connection Info | ml_sci | 1.0.2 | Retired | Retired | Superseded by `Extended Game Notifications`

78
ml_amt/AvatarParameter.cs Normal file
View file

@ -0,0 +1,78 @@
using ABI_RC.Core.Player;
namespace ml_amt
{
class AvatarParameter
{
public enum ParameterType
{
Upright,
GroundedRaw,
Moving
}
public enum ParameterSyncType
{
Synced,
Local
}
public readonly ParameterType m_type;
public readonly ParameterSyncType m_sync;
public readonly string m_name;
public readonly int m_hash; // For local only
public AvatarParameter(ParameterType p_type, string p_name, ParameterSyncType p_sync = ParameterSyncType.Synced, int p_hash = 0)
{
m_type = p_type;
m_sync = p_sync;
m_name = p_name;
m_hash = p_hash;
}
public void Update(MotionTweaker p_tweaker)
{
switch(m_type)
{
case ParameterType.Upright:
SetFloat(p_tweaker.GetUpright());
break;
case ParameterType.GroundedRaw:
SetBoolean(p_tweaker.GetGroundedRaw());
break;
case ParameterType.Moving:
SetBoolean(p_tweaker.GetMoving());
break;
}
}
void SetFloat(float p_value)
{
switch(m_sync)
{
case ParameterSyncType.Local:
PlayerSetup.Instance._animator.SetFloat(m_hash, p_value);
break;
case ParameterSyncType.Synced:
PlayerSetup.Instance.animatorManager.SetAnimatorParameterFloat(m_name, p_value);
break;
}
}
void SetBoolean(bool p_value)
{
switch(m_sync)
{
case ParameterSyncType.Local:
PlayerSetup.Instance._animator.SetBool(m_hash, p_value);
break;
case ParameterSyncType.Synced:
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool(m_name, p_value);
break;
}
}
}
}

View file

@ -1,15 +1,29 @@
using ABI_RC.Core.Player;
using ABI.CCK.Components;
using ABI_RC.Core;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using System.Collections;
using System.Reflection;
using UnityEngine;
namespace ml_amt
{
public class AvatarMotionTweaker : MelonLoader.MelonMod
{
static readonly MethodInfo[] ms_fbtDetouredMethods =
{
typeof(PlayerSetup).GetMethod("Update", BindingFlags.NonPublic | BindingFlags.Instance),
typeof(PlayerSetup).GetMethod("FixedUpdate", BindingFlags.NonPublic | BindingFlags.Instance),
typeof(PlayerSetup).GetMethod("UpdatePlayerAvatarMovementData", BindingFlags.NonPublic | BindingFlags.Instance),
typeof(CVRParameterStreamEntry).GetMethod(nameof(CVRParameterStreamEntry.CheckUpdate))
};
static AvatarMotionTweaker ms_instance = null;
MotionTweaker m_localTweaker = null;
static int ms_calibrationCounts = 0;
static bool ms_fbtDetour = false;
public override void OnInitializeMelon()
{
@ -29,25 +43,49 @@ namespace ml_amt
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetMethod(nameof(ABI_RC.Systems.IK.SubSystems.BodySystem.Calibrate)),
typeof(BodySystem).GetMethod(nameof(BodySystem.Calibrate)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
// FBT detour
HarmonyInstance.Patch(
typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetMethod(nameof(ABI_RC.Systems.IK.SubSystems.BodySystem.FBTAvailable)),
typeof(BodySystem).GetMethod(nameof(BodySystem.FBTAvailable)),
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnFBTAvailable_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
null
);
foreach(MethodInfo l_detoured in ms_fbtDetouredMethods)
{
HarmonyInstance.Patch(
l_detoured,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(FBTDetour_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(FBTDetour_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
}
// Alternative collider height
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ReCalibrateAvatar)),
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnReCalibrateAvatar_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
typeof(MovementSystem).GetMethod("UpdateCollider", BindingFlags.NonPublic | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnUpdateCollider_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
null
);
// AAS overriding "fix"
HarmonyInstance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.SetOverrideAnimation), BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnAnimationOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
null
);
HarmonyInstance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.RestoreOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnAnimationOverrideRestore_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
null
);
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
System.Collections.IEnumerator WaitForLocalPlayer()
IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
@ -92,8 +130,6 @@ namespace ml_amt
{
try
{
ms_calibrationCounts = 0;
if(m_localTweaker != null)
m_localTweaker.OnSetupAvatar();
}
@ -117,20 +153,98 @@ namespace ml_amt
}
}
static void OnReCalibrateAvatar_Prefix()
// FBT detection override
static void FBTDetour_Prefix()
{
MotionTweaker.ms_fptActive = false;
ms_calibrationCounts++;
ms_fbtDetour = true;
}
static void FBTDetour_Postfix()
{
ms_fbtDetour = false;
}
static bool OnFBTAvailable_Prefix(ref bool __result)
{
if(MotionTweaker.ms_fptActive || (ms_calibrationCounts == 0))
if(ms_fbtDetour && !BodySystem.isCalibratedAsFullBody)
{
__result = false;
return false;
}
return true;
}
// Alternative collider size
static bool OnUpdateCollider_Prefix(
ref MovementSystem __instance,
bool __0, // updateRadius
CharacterController ___controller,
float ____avatarHeight,
float ____avatarHeightFactor,
float ____minimumColliderRadius,
Vector3 ____colliderCenter
)
{
if(!Settings.CollisionScale)
return true;
try
{
if(___controller != null)
{
float l_scaledHeight = ____avatarHeight * ____avatarHeightFactor;
float l_newRadius = (__0 ? Mathf.Max(____minimumColliderRadius, l_scaledHeight / 6f) : ___controller.radius);
float l_newHeight = Mathf.Max(l_scaledHeight, l_newRadius * 2f);
float l_currentHeight = ___controller.height;
Vector3 l_newCenter = ____colliderCenter;
l_newCenter.y = (l_newHeight + 0.075f) * 0.5f; // Idk where 0.075f has come from
Vector3 l_currentCenter = ___controller.center;
if(__0 || (Mathf.Abs(l_currentHeight - l_newHeight) > (l_currentHeight * 0.05f)) || (Vector3.Distance(l_currentCenter, l_newCenter) > (l_currentHeight * 0.05f)))
{
bool l_active = ___controller.enabled;
if(__0)
___controller.radius = l_newRadius;
___controller.height = l_newHeight;
___controller.center = l_newCenter;
__instance.groundDistance = l_newRadius;
if(__instance.proxyCollider != null)
{
if(__0)
__instance.proxyCollider.radius = l_newRadius;
__instance.proxyCollider.height = l_newHeight;
__instance.proxyCollider.center = new Vector3(0f, l_newCenter.y, 0f);
}
if(__instance.forceObject != null)
__instance.forceObject.transform.localScale = new Vector3(l_newRadius + 0.1f, l_newHeight, l_newRadius + 0.1f);
if(__instance.groundCheck != null)
__instance.groundCheck.localPosition = ____colliderCenter;
___controller.enabled = l_active;
}
}
}
catch(System.Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
return false;
}
static bool OnAnimationOverride_Prefix()
{
return !Settings.OverrideFix;
}
static bool OnAnimationOverrideRestore_Prefix()
{
return !Settings.OverrideFix;
}
}
}

View file

@ -11,32 +11,12 @@ namespace ml_amt
[DisallowMultipleComponent]
class MotionTweaker : MonoBehaviour
{
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
static readonly FieldInfo ms_grounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_groundedRaw = typeof(MovementSystem).GetField("_isGroundedRaw", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_rootVelocity = typeof(IKSolverVR).GetField("rootVelocity", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_hasToes = typeof(IKSolverVR).GetField("hasToes", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly int ms_emoteHash = Animator.StringToHash("Emote");
enum ParameterType
{
Upright,
GroundedRaw,
Moving
}
enum ParameterSyncType
{
Local,
Synced
}
struct AdditionalParameterInfo
{
public ParameterType m_type;
public ParameterSyncType m_sync;
public string m_name;
public int m_hash; // For local only
}
enum PoseState
{
Standing = 0,
@ -44,17 +24,18 @@ namespace ml_amt
Proning
}
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
VRIK m_vrIk = null;
int m_locomotionLayer = 0;
float m_ikWeight = 1f; // Original weight
float m_locomotionWeight = 1f; // Original weight
bool m_plantFeet = false; // Original plant feet
float m_avatarScale = 1f; // Instantiated scale
Vector3 m_locomotionOffset = Vector3.zero; // Original locomotion offset
bool m_bendNormalLeft = false;
bool m_bendNormalRight = false;
Transform m_avatarHips = null;
float m_viewPointHeight = 1f;
bool m_isInVR = false;
public static bool ms_fptActive = false;
bool m_inVR = false;
bool m_avatarReady = false;
bool m_compatibleAvatar = false;
@ -77,25 +58,24 @@ namespace ml_amt
bool m_ikOverrideFly = true;
bool m_ikOverrideJump = true;
bool m_customLocomotionOffset = false;
Vector3 m_locomotionOffset = Vector3.zero;
bool m_detectEmotes = true;
bool m_emoteActive = false;
bool m_followHips = true;
Vector3 m_hipsToPlayer = Vector3.zero;
readonly List<AdditionalParameterInfo> m_parameters = null;
Vector3 m_massCenter = Vector3.zero;
public MotionTweaker()
readonly List<AvatarParameter> m_parameters = null;
internal MotionTweaker()
{
m_parameters = new List<AdditionalParameterInfo>();
m_parameters = new List<AvatarParameter>();
}
void Start()
{
m_isInVR = Utils.IsInVR();
m_inVR = Utils.IsInVR();
Settings.IKOverrideCrouchChange += this.SetIKOverrideCrouch;
Settings.CrouchLimitChange += this.SetCrouchLimit;
@ -107,6 +87,7 @@ namespace ml_amt
Settings.IKOverrideJumpChange += this.SetIKOverrideJump;
Settings.DetectEmotesChange += this.SetDetectEmotes;
Settings.FollowHipsChange += this.SetFollowHips;
Settings.MassCenterChange += this.SetMassCenter;
}
void OnDestroy()
@ -121,6 +102,7 @@ namespace ml_amt
Settings.IKOverrideJumpChange -= this.SetIKOverrideJump;
Settings.DetectEmotesChange -= this.SetDetectEmotes;
Settings.FollowHipsChange -= this.SetFollowHips;
Settings.MassCenterChange -= this.SetMassCenter;
}
void Update()
@ -132,12 +114,12 @@ namespace ml_amt
m_moving = !Mathf.Approximately(MovementSystem.Instance.movementVector.magnitude, 0f);
// Update upright
Matrix4x4 l_hmdMatrix = PlayerSetup.Instance.transform.GetMatrix().inverse * (m_isInVR ? PlayerSetup.Instance.vrHeadTracker.transform.GetMatrix() : PlayerSetup.Instance.desktopCameraRig.transform.GetMatrix());
Matrix4x4 l_hmdMatrix = PlayerSetup.Instance.transform.GetMatrix().inverse * (m_inVR ? PlayerSetup.Instance.vrHeadTracker.transform.GetMatrix() : PlayerSetup.Instance.desktopCameraRig.transform.GetMatrix());
float l_currentHeight = Mathf.Clamp((l_hmdMatrix * ms_pointVector).y, 0f, float.MaxValue);
float l_avatarScale = (m_avatarScale > 0f) ? (PlayerSetup.Instance._avatar.transform.localScale.y / m_avatarScale) : 0f;
float l_avatarViewHeight = Mathf.Clamp(m_viewPointHeight * l_avatarScale, 0f, float.MaxValue);
m_upright = Mathf.Clamp(((l_avatarViewHeight > 0f) ? (l_currentHeight / l_avatarViewHeight) : 0f), 0f, 1f);
PoseState l_poseState = (m_upright <= m_proneLimit) ? PoseState.Proning : ((m_upright <= m_crouchLimit) ? PoseState.Crouching : PoseState.Standing);
m_upright = Mathf.Clamp01((l_avatarViewHeight > 0f) ? (l_currentHeight / l_avatarViewHeight) : 0f);
m_poseState = (m_upright <= Mathf.Min(m_proneLimit, m_crouchLimit)) ? PoseState.Proning : ((m_upright <= Mathf.Max(m_proneLimit, m_crouchLimit)) ? PoseState.Crouching : PoseState.Standing);
if(m_avatarHips != null)
{
@ -145,21 +127,12 @@ namespace ml_amt
m_hipsToPlayer.Set(l_hipsToPoint.x, 0f, l_hipsToPoint.z);
}
if(m_isInVR && (m_vrIk != null) && m_vrIk.enabled)
if(m_inVR && (m_vrIk != null) && m_vrIk.enabled)
{
if(m_poseState != l_poseState)
{
// Weird fix of torso shaking
if(m_ikOverrideCrouch && (l_poseState == PoseState.Standing))
ms_rootVelocity.SetValue(m_vrIk.solver, Vector3.zero);
if(m_ikOverrideProne && !m_ikOverrideCrouch && (l_poseState == PoseState.Crouching))
ms_rootVelocity.SetValue(m_vrIk.solver, Vector3.zero);
}
if(m_adjustedMovement)
{
MovementSystem.Instance.ChangeCrouch(l_poseState == PoseState.Crouching);
MovementSystem.Instance.ChangeProne(l_poseState == PoseState.Proning);
MovementSystem.Instance.ChangeCrouch(m_poseState == PoseState.Crouching);
MovementSystem.Instance.ChangeProne(m_poseState == PoseState.Proning);
if(!m_poseTransitions)
{
@ -170,13 +143,11 @@ namespace ml_amt
if(m_poseTransitions)
{
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", (l_poseState == PoseState.Crouching) && !m_compatibleAvatar && !Utils.IsInFullbody());
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", (l_poseState == PoseState.Proning) && !m_compatibleAvatar && !Utils.IsInFullbody());
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", (m_poseState == PoseState.Crouching) && !m_compatibleAvatar && !BodySystem.isCalibratedAsFullBody);
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", (m_poseState == PoseState.Proning) && !m_compatibleAvatar && !BodySystem.isCalibratedAsFullBody);
}
}
m_poseState = l_poseState;
m_emoteActive = false;
if(m_detectEmotes && (m_locomotionLayer >= 0))
{
@ -186,58 +157,13 @@ namespace ml_amt
if(m_parameters.Count > 0)
{
foreach(AdditionalParameterInfo l_param in m_parameters)
{
switch(l_param.m_type)
{
case ParameterType.Upright:
{
switch(l_param.m_sync)
{
case ParameterSyncType.Local:
PlayerSetup.Instance._animator.SetFloat(l_param.m_hash, m_upright);
break;
case ParameterSyncType.Synced:
PlayerSetup.Instance.animatorManager.SetAnimatorParameterFloat(l_param.m_name, m_upright);
break;
}
}
break;
case ParameterType.GroundedRaw:
{
switch(l_param.m_sync)
{
case ParameterSyncType.Local:
PlayerSetup.Instance._animator.SetBool(l_param.m_hash, m_groundedRaw);
break;
case ParameterSyncType.Synced:
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool(l_param.m_name, m_groundedRaw);
break;
}
}
break;
case ParameterType.Moving:
{
switch(l_param.m_sync)
{
case ParameterSyncType.Local:
PlayerSetup.Instance._animator.SetBool(l_param.m_hash, m_moving);
break;
case ParameterSyncType.Synced:
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool(l_param.m_name, m_moving);
break;
}
}
break;
}
}
foreach(AvatarParameter l_param in m_parameters)
l_param.Update(this);
}
}
}
public void OnAvatarClear()
internal void OnAvatarClear()
{
m_vrIk = null;
m_locomotionLayer = -1;
@ -248,20 +174,20 @@ namespace ml_amt
m_poseState = PoseState.Standing;
m_customCrouchLimit = false;
m_customProneLimit = false;
m_customLocomotionOffset = false;
m_locomotionOffset = Vector3.zero;
m_avatarScale = 1f;
m_locomotionOffset = Vector3.zero;
m_emoteActive = false;
m_moving = false;
m_hipsToPlayer = Vector3.zero;
m_avatarHips = null;
m_viewPointHeight = 1f;
m_massCenter = Vector3.zero;
m_parameters.Clear();
ms_fptActive = false;
}
public void OnSetupAvatar()
internal void OnSetupAvatar()
{
m_inVR = Utils.IsInVR();
m_vrIk = PlayerSetup.Instance._avatar.GetComponent<VRIK>();
m_locomotionLayer = PlayerSetup.Instance._animator.GetLayerIndex("Locomotion/Emotes");
m_avatarHips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
@ -269,51 +195,63 @@ namespace ml_amt
// Parse animator parameters
AnimatorControllerParameter[] l_params = PlayerSetup.Instance._animator.parameters;
ParameterType[] l_enumParams = (ParameterType[])System.Enum.GetValues(typeof(ParameterType));
foreach(var l_param in l_params)
{
foreach(var l_enumParam in l_enumParams)
foreach(AvatarParameter.ParameterType l_enumParam in System.Enum.GetValues(typeof(AvatarParameter.ParameterType)))
{
if(l_param.name.Contains(l_enumParam.ToString()) && (m_parameters.FindIndex(p => p.m_type == l_enumParam) == -1))
{
bool l_local = (l_param.name[0] == '#');
m_parameters.Add(new AdditionalParameterInfo
{
m_type = l_enumParam,
m_sync = (l_local ? ParameterSyncType.Local : ParameterSyncType.Synced),
m_name = l_param.name,
m_hash = (l_local ? l_param.nameHash : 0)
});
m_parameters.Add(new AvatarParameter(
l_enumParam,
l_param.name,
(l_local ? AvatarParameter.ParameterSyncType.Local : AvatarParameter.ParameterSyncType.Synced),
(l_local ? l_param.nameHash : 0)
));
break;
}
}
}
m_compatibleAvatar = m_parameters.Exists(p => p.m_name.Contains("Upright"));
m_compatibleAvatar = m_parameters.Exists(p => p.m_type == AvatarParameter.ParameterType.Upright);
m_avatarScale = Mathf.Abs(PlayerSetup.Instance._avatar.transform.localScale.y);
Transform l_customTransform = PlayerSetup.Instance._avatar.transform.Find("CrouchLimit");
m_customCrouchLimit = (l_customTransform != null);
m_crouchLimit = m_customCrouchLimit ? Mathf.Clamp(l_customTransform.localPosition.y, 0f, 1f) : Settings.CrouchLimit;
m_crouchLimit = m_customCrouchLimit ? Mathf.Clamp01(l_customTransform.localPosition.y) : Settings.CrouchLimit;
l_customTransform = PlayerSetup.Instance._avatar.transform.Find("ProneLimit");
m_customProneLimit = (l_customTransform != null);
m_proneLimit = m_customProneLimit ? Mathf.Clamp(l_customTransform.localPosition.y, 0f, 1f) : Settings.ProneLimit;
l_customTransform = PlayerSetup.Instance._avatar.transform.Find("LocomotionOffset");
m_customLocomotionOffset = (l_customTransform != null);
m_locomotionOffset = m_customLocomotionOffset ? l_customTransform.localPosition : Vector3.zero;
m_proneLimit = m_customProneLimit ? Mathf.Clamp01(l_customTransform.localPosition.y) : Settings.ProneLimit;
// Apply VRIK tweaks
if(m_vrIk != null)
{
if(m_customLocomotionOffset)
m_vrIk.solver.locomotion.offset = m_locomotionOffset;
m_locomotionOffset = m_vrIk.solver.locomotion.offset;
m_massCenter = m_locomotionOffset;
if((bool)ms_hasToes.GetValue(m_vrIk.solver))
{
Transform l_foot = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftFoot);
if(l_foot == null)
l_foot = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightFoot);
Transform l_toe = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftToes);
if(l_toe == null)
l_toe = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightToes);
if((l_foot != null) && (l_toe != null))
{
Vector3 l_footPos = (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_foot.GetMatrix()) * ms_pointVector;
Vector3 l_toePos = (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_toe.GetMatrix()) * ms_pointVector;
m_massCenter = new Vector3(0f, 0f, l_toePos.z - l_footPos.z);
}
}
m_vrIk.solver.locomotion.offset = (Settings.MassCenter ? m_massCenter : m_locomotionOffset);
// Place our pre-solver listener first
m_vrIk.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
m_vrIk.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
}
@ -321,9 +259,9 @@ namespace ml_amt
m_avatarReady = true;
}
public void OnCalibrate()
internal void OnCalibrate()
{
if(m_avatarReady && BodySystem.enableHipTracking && !BodySystem.enableRightFootTracking && !BodySystem.enableLeftFootTracking && !BodySystem.enableLeftKneeTracking && !BodySystem.enableRightKneeTracking)
if(m_avatarReady && BodySystem.isCalibratedAsFullBody && BodySystem.enableHipTracking && !BodySystem.enableRightFootTracking && !BodySystem.enableLeftFootTracking && !BodySystem.enableLeftKneeTracking && !BodySystem.enableRightKneeTracking)
{
BodySystem.isCalibratedAsFullBody = false;
BodySystem.TrackingLeftLegEnabled = false;
@ -332,8 +270,6 @@ namespace ml_amt
if(m_vrIk != null)
m_vrIk.solver.spine.maxRootAngle = 25f; // I need to rotate my legs, ffs!
ms_fptActive = true;
}
}
@ -343,6 +279,9 @@ namespace ml_amt
m_ikWeight = m_vrIk.solver.IKPositionWeight;
m_locomotionWeight = m_vrIk.solver.locomotion.weight;
m_plantFeet = m_vrIk.solver.plantFeet;
m_bendNormalLeft = m_vrIk.solver.leftLeg.useAnimatedBendNormal;
m_bendNormalRight = m_vrIk.solver.rightLeg.useAnimatedBendNormal;
if(m_detectEmotes && m_emoteActive)
m_vrIk.solver.IKPositionWeight = 0f;
@ -355,19 +294,24 @@ namespace ml_amt
if(m_ikOverrideFly && MovementSystem.Instance.flying)
{
m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_legsOverride = true;
}
if(m_ikOverrideJump && !m_grounded && !MovementSystem.Instance.flying)
{
m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_legsOverride = true;
}
bool l_solverActive = !Mathf.Approximately(m_vrIk.solver.IKPositionWeight, 0f);
if(l_legsOverride && l_solverActive && m_followHips && (!m_moving || (m_poseState == PoseState.Proning)) && m_isInVR && !BodySystem.isCalibratedAsFullBody)
if(l_legsOverride && l_solverActive && m_followHips && (!m_moving || (m_poseState == PoseState.Proning)) && m_inVR && !BodySystem.isCalibratedAsFullBody)
{
m_vrIk.solver.plantFeet = false;
ABI_RC.Systems.IK.IKSystem.VrikRootController.enabled = false;
PlayerSetup.Instance._avatar.transform.localPosition = m_hipsToPlayer;
}
@ -377,6 +321,9 @@ namespace ml_amt
{
m_vrIk.solver.IKPositionWeight = m_ikWeight;
m_vrIk.solver.locomotion.weight = m_locomotionWeight;
m_vrIk.solver.plantFeet = m_plantFeet;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = m_bendNormalLeft;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = m_bendNormalRight;
}
public void SetIKOverrideCrouch(bool p_state)
@ -386,7 +333,7 @@ namespace ml_amt
public void SetCrouchLimit(float p_value)
{
if(!m_customCrouchLimit)
m_crouchLimit = Mathf.Clamp(p_value, 0f, 1f);
m_crouchLimit = Mathf.Clamp01(p_value);
}
public void SetIKOverrideProne(bool p_state)
{
@ -395,13 +342,13 @@ namespace ml_amt
public void SetProneLimit(float p_value)
{
if(!m_customProneLimit)
m_proneLimit = Mathf.Clamp(p_value, 0f, 1f);
m_proneLimit = Mathf.Clamp01(p_value);
}
public void SetPoseTransitions(bool p_state)
{
m_poseTransitions = p_state;
if(!m_poseTransitions && m_avatarReady && m_isInVR)
if(!m_poseTransitions && m_avatarReady && m_inVR)
{
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", false);
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", false);
@ -411,7 +358,7 @@ namespace ml_amt
{
m_adjustedMovement = p_state;
if(!m_adjustedMovement && m_avatarReady && m_isInVR)
if(!m_adjustedMovement && m_avatarReady && m_inVR)
{
MovementSystem.Instance.ChangeCrouch(false);
MovementSystem.Instance.ChangeProne(false);
@ -433,5 +380,14 @@ namespace ml_amt
{
m_followHips = p_state;
}
public void SetMassCenter(bool p_state)
{
if(m_vrIk != null)
m_vrIk.solver.locomotion.offset = (Settings.MassCenter ? m_massCenter : m_locomotionOffset);
}
public float GetUpright() => m_upright;
public bool GetGroundedRaw() => m_groundedRaw;
public bool GetMoving() => m_moving;
}
}

View file

@ -1,10 +1,10 @@
using System.Reflection;
[assembly: AssemblyTitle("AvatarMotionTweaker")]
[assembly: AssemblyVersion("1.1.9")]
[assembly: AssemblyFileVersion("1.1.9")]
[assembly: AssemblyVersion("1.2.3")]
[assembly: AssemblyFileVersion("1.2.3")]
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.1.9-ex2", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.2.3", "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)]

View file

@ -18,12 +18,19 @@ Available mod's settings in `Settings - IK - Avatar Motion Tweaker`:
* Note: Can be overrided by avatar. For this avatar has to have child gameobject with name `ProneLimit`, its Y-axis location will be used as limit, should be in range [0.0, 1.0].
* **IK override while flying:** disables legs locomotion/autostep in fly mode; default value - `true`.
* **IK override while jumping:** disables legs locomotion/autostep in jump; default value - `true`.
* **Follow hips on IK override:** adjust avatar position to overcome animation snapping on IK override; default value - `true`.
* **Follow hips on IK override:** adjusts avatar position to overcome animation snapping on IK override; default value - `true`.
* Note: Works best with animations that have root transform position (XZ) based on center of mass.
* Note: Made for four point tracking (head, hands, hips) in mind.
* **Pose transitions:** allows regular avatars animator to transit in crouch/prone states; default value - `true`.
* Note: Avatar is considered as regular if its AAS animator doesn't have `Upright` parameter.
* **Adjusted pose movement speed:** scales movement speed upon crouching/proning; default value - `true`.
* **Detect animations emote tag:** disables avatar's IK entirely if current animator state has `Emote` tag; default value - `true`;
* **Detect animations emote tag:** disables avatar's IK entirely if current animator state has `Emote` tag; default value - `true`.
* Note: Created as example for [propoused game feature](https://feedback.abinteractive.net/p/disabling-vr-ik-for-emotes-via-animator-state-tag-7b80d963-053a-41c0-86ac-e3d53c61c1e2).
* **Adjusted locomotion mass center:** automatically changes IK locomotion center if avatar has toe bones; default value - `true`.
* Note: Compatible with [DesktopVRIK](https://github.com/NotAKidOnSteam/DesktopVRIK) and [FuckToes](https://github.com/NotAKidOnSteam/FuckToes).
* **Alternative avatar collider scale:** applies slightly different approach to avatar collider size change; default value - `true`
* **Prevent Unity animation override:** disables overriding of animations at runtime for avatars with AAS; default value - `false`.
* Note: This options is made for "fix" of [broken animator issues with chairs and combat worlds](https://feedback.abinteractive.net/p/gestures-getting-stuck-locally-upon-entering-vehicles-chairs).
Available additional parameters for AAS animator:
* **`Upright`:** defines linear coefficient between current viewpoint height and avatar's viewpoint height; float, range - [0.0, 1.0].
@ -35,8 +42,5 @@ Available additional parameters for AAS animator:
* **`Moving`:** defines movement state of player
* Note: Can be set as local-only (not synced) if starts with `#` character.
Additional avatars tweaks:
* If avatar has child object with name `LocomotionOffset` its local position will be used for offsetting VRIK locomotion mass center.
Additional mod's behaviour:
* Disables FBT state in 4PT mode (head, hands, hips). Be sure to enable only hips tracking in `Settings - IK` tab.
* Overrides FBT behaviour in 4PT mode (head, hands, hips). Be sure to disable legs and knees tracking in `Settings - IK tab`.

View file

@ -18,7 +18,10 @@ namespace ml_amt
IKOverrideFly,
IKOverrideJump,
DetectEmotes,
FollowHips
FollowHips,
CollisionScale,
MassCenter,
OverrideFix
};
static bool ms_ikOverrideCrouch = true;
@ -31,6 +34,9 @@ namespace ml_amt
static bool ms_ikOverrideJump = true;
static bool ms_detectEmotes = true;
static bool ms_followHips = true;
static bool ms_collisionScale = true;
static bool ms_massCenter = true;
static bool ms_overrideFix = false;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
@ -45,22 +51,30 @@ namespace ml_amt
static public event Action<bool> IKOverrideJumpChange;
static public event Action<bool> DetectEmotesChange;
static public event Action<bool> FollowHipsChange;
static public event Action<bool> CollisionScaleChange;
static public event Action<bool> MassCenterChange;
static public event Action<bool> OverrideFixChange;
public static void Init()
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("AMT");
ms_entries = new List<MelonLoader.MelonPreferences_Entry>();
ms_entries.Add(ms_category.CreateEntry(ModSetting.IKOverrideCrouch.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.CrouchLimit.ToString(), 65));
ms_entries.Add(ms_category.CreateEntry(ModSetting.IKOverrideProne.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.ProneLimit.ToString(), 30));
ms_entries.Add(ms_category.CreateEntry(ModSetting.PoseTransitions.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.AdjustedMovement.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.IKOverrideJump.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.DetectEmotes.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.FollowHips.ToString(), true));
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.IKOverrideCrouch.ToString(), true),
ms_category.CreateEntry(ModSetting.CrouchLimit.ToString(), 65),
ms_category.CreateEntry(ModSetting.IKOverrideProne.ToString(), true),
ms_category.CreateEntry(ModSetting.ProneLimit.ToString(), 30),
ms_category.CreateEntry(ModSetting.PoseTransitions.ToString(), true),
ms_category.CreateEntry(ModSetting.AdjustedMovement.ToString(), true),
ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), true),
ms_category.CreateEntry(ModSetting.IKOverrideJump.ToString(), true),
ms_category.CreateEntry(ModSetting.DetectEmotes.ToString(), true),
ms_category.CreateEntry(ModSetting.FollowHips.ToString(), true),
ms_category.CreateEntry(ModSetting.CollisionScale.ToString(), true),
ms_category.CreateEntry(ModSetting.MassCenter.ToString(), true),
ms_category.CreateEntry(ModSetting.OverrideFix.ToString(), false)
};
Load();
@ -101,6 +115,9 @@ namespace ml_amt
ms_ikOverrideJump = (bool)ms_entries[(int)ModSetting.IKOverrideJump].BoxedValue;
ms_detectEmotes = (bool)ms_entries[(int)ModSetting.DetectEmotes].BoxedValue;
ms_followHips = (bool)ms_entries[(int)ModSetting.FollowHips].BoxedValue;
ms_collisionScale = (bool)ms_entries[(int)ModSetting.CollisionScale].BoxedValue;
ms_massCenter = (bool)ms_entries[(int)ModSetting.MassCenter].BoxedValue;
ms_overrideFix = (bool)ms_entries[(int)ModSetting.OverrideFix].BoxedValue;
}
static void OnSliderUpdate(string p_name, string p_value)
@ -189,6 +206,27 @@ namespace ml_amt
FollowHipsChange?.Invoke(ms_followHips);
}
break;
case ModSetting.CollisionScale:
{
ms_collisionScale = bool.Parse(p_value);
CollisionScaleChange?.Invoke(ms_collisionScale);
}
break;
case ModSetting.MassCenter:
{
ms_massCenter = bool.Parse(p_value);
MassCenterChange?.Invoke(ms_massCenter);
}
break;
case ModSetting.OverrideFix:
{
ms_overrideFix = bool.Parse(p_value);
OverrideFixChange?.Invoke(ms_overrideFix);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
@ -235,5 +273,17 @@ namespace ml_amt
{
get => ms_followHips;
}
public static bool CollisionScale
{
get => ms_collisionScale;
}
public static bool MassCenter
{
get => ms_massCenter;
}
public static bool OverrideFix
{
get => ms_overrideFix;
}
}
}

View file

@ -6,7 +6,6 @@ namespace ml_amt
static class Utils
{
public static bool IsInVR() => ((ABI_RC.Core.Savior.CheckVR.Instance != null) && ABI_RC.Core.Savior.CheckVR.Instance.hasVrDeviceLoaded);
public static bool IsInFullbody() => ((IKSystem.Instance != null) && IKSystem.Instance.BodySystem.FBTAvailable());
// Extensions
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)

View file

@ -68,8 +68,14 @@
<Reference Include="UnityEngine.CoreModule">
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AvatarParameter.cs" />
<Compile Include="MotionTweaker.cs" />
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -249,6 +249,27 @@ function inp_toggle_mod_amt(_obj, _callbackName) {
<div id="DetectEmotes" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Adjusted locomotion mass center: </div>
<div class ="option-input">
<div id="MassCenter" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Alternative avatar collider scale: </div>
<div class ="option-input">
<div id="CollisionScale" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Prevent Unity animation override (chairs, etc.): </div>
<div class ="option-input">
<div id="OverrideFix" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
`;
document.getElementById('settings-ik').appendChild(l_block);

View file

@ -77,7 +77,7 @@ namespace ml_dht
}
}
public void OnEyeControllerUpdate(CVREyeController p_component)
internal void OnEyeControllerUpdate(CVREyeController p_component)
{
if(m_enabled)
{
@ -99,14 +99,14 @@ namespace ml_dht
}
}
public void OnFaceTrackingUpdate(CVRFaceTracking p_component)
internal void OnFaceTrackingUpdate(CVRFaceTracking p_component)
{
if(m_enabled && m_faceOverride)
{
if(m_avatarDescriptor != null)
m_avatarDescriptor.useVisemeLipsync = false;
float l_weight = Mathf.Clamp(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(m_mouthShapes.y)), 0f, 1f) * 100f;
float l_weight = Mathf.Clamp01(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(m_mouthShapes.y))) * 100f;
p_component.BlendShapeValues[(int)LipShape_v2.Jaw_Open] = m_mouthShapes.x * 100f;
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Pout] = ((m_mouthShapes.y > 0f) ? l_weight : 0f);
@ -117,7 +117,7 @@ namespace ml_dht
}
}
public void OnSetupAvatar()
internal void OnSetupAvatar()
{
m_avatarDescriptor = PlayerSetup.Instance._avatar.GetComponent<CVRAvatar>();
m_headBone = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Head);
@ -130,7 +130,7 @@ namespace ml_dht
m_lookIK.solver.OnPostUpdate += this.OnLookIKPostUpdate;
}
public void OnAvatarClear()
internal void OnAvatarClear()
{
m_avatarDescriptor = null;
m_lookIK = null;

View file

@ -17,7 +17,7 @@ namespace ml_dht
Smoothing,
FaceOverride
}
static bool ms_enabled = false;
static bool ms_headTracking = true;
static bool ms_eyeTracking = true;
@ -25,10 +25,10 @@ namespace ml_dht
static bool ms_mirrored = false;
static float ms_smoothing = 0.5f;
static bool ms_faceOverride = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<bool> EnabledChange;
static public event Action<bool> HeadTrackingChange;
static public event Action<bool> EyeTrackingChange;
@ -36,25 +36,27 @@ namespace ml_dht
static public event Action<bool> MirroredChange;
static public event Action<float> SmoothingChange;
static public event Action<bool> FaceOverrideChange;
public static void Init()
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("DHT");
ms_entries = new List<MelonLoader.MelonPreferences_Entry>();
ms_entries.Add(ms_category.CreateEntry(ModSetting.Enabled.ToString(), false));
ms_entries.Add(ms_category.CreateEntry(ModSetting.HeadTracking.ToString(), false));
ms_entries.Add(ms_category.CreateEntry(ModSetting.EyeTracking.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.Blinking.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.Mirrored.ToString(), false));
ms_entries.Add(ms_category.CreateEntry(ModSetting.Smoothing.ToString(), 50));
ms_entries.Add(ms_category.CreateEntry(ModSetting.FaceOverride.ToString(), true));
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.Enabled.ToString(), false),
ms_category.CreateEntry(ModSetting.HeadTracking.ToString(), false),
ms_category.CreateEntry(ModSetting.EyeTracking.ToString(), true),
ms_category.CreateEntry(ModSetting.Blinking.ToString(), true),
ms_category.CreateEntry(ModSetting.Mirrored.ToString(), false),
ms_category.CreateEntry(ModSetting.Smoothing.ToString(), 50),
ms_category.CreateEntry(ModSetting.FaceOverride.ToString(), true)
};
Load();
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
static System.Collections.IEnumerator WaitMainMenuUi()
{
while(ViewManager.Instance == null)
@ -76,18 +78,18 @@ namespace ml_dht
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingDHT", l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void Load()
{
ms_enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
ms_headTracking = (bool)ms_entries[(int)ModSetting.HeadTracking].BoxedValue;
ms_eyeTracking = (bool)ms_entries[(int)ModSetting.EyeTracking].BoxedValue;
ms_blinking = (bool)ms_entries[(int)ModSetting.Blinking].BoxedValue;
ms_headTracking = (bool)ms_entries[(int)ModSetting.HeadTracking].BoxedValue;
ms_eyeTracking = (bool)ms_entries[(int)ModSetting.EyeTracking].BoxedValue;
ms_blinking = (bool)ms_entries[(int)ModSetting.Blinking].BoxedValue;
ms_mirrored = (bool)ms_entries[(int)ModSetting.Mirrored].BoxedValue;
ms_smoothing = ((int)ms_entries[(int)ModSetting.Smoothing].BoxedValue) * 0.01f;
ms_faceOverride = (bool)ms_entries[(int)ModSetting.FaceOverride].BoxedValue;
}
static void OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
@ -105,7 +107,7 @@ namespace ml_dht
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
@ -130,13 +132,15 @@ namespace ml_dht
{
ms_eyeTracking = bool.Parse(p_value);
EyeTrackingChange?.Invoke(ms_eyeTracking);
} break;
}
break;
case ModSetting.Blinking:
{
ms_blinking = bool.Parse(p_value);
BlinkingChange?.Invoke(ms_blinking);
} break;
}
break;
case ModSetting.Mirrored:
{
@ -149,13 +153,14 @@ namespace ml_dht
{
ms_faceOverride = bool.Parse(p_value);
FaceOverrideChange?.Invoke(ms_faceOverride);
} break;
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
public static bool Enabled
{
get => ms_enabled;

View file

@ -88,6 +88,6 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\common\ChilloutVR\Mods\"</PostBuildEvent>
<PostBuildEvent>copy /y "$(TargetPath)" "D:\Games\Steam\steamapps\common\ChilloutVR\Mods\"</PostBuildEvent>
</PropertyGroup>
</Project>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ReferencePath>C:\Games\Steam\common\ChilloutVR\MelonLoader\;C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
<ReferencePath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
</PropertyGroup>
</Project>

143
ml_egn/Main.cs Normal file
View file

@ -0,0 +1,143 @@
using ABI_RC.Core.EventSystem;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.IO;
using ABI_RC.Core.Networking;
using ABI_RC.Core.Util;
using DarkRift.Client;
using System.Reflection;
namespace ml_egn
{
public class ExtendedGameNotifications : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(AssetManagement).GetMethod(nameof(AssetManagement.LoadLocalAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(ExtendedGameNotifications).GetMethod(nameof(OnLocalAvatarLoad), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnProp)),
null,
new HarmonyLib.HarmonyMethod(typeof(ExtendedGameNotifications).GetMethod(nameof(OnPropSpawned), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(NetworkManager).GetMethod("OnGameNetworkConnectionClosed", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(ExtendedGameNotifications).GetMethod(nameof(OnGameNetworkConnectionClosed), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(CVRCamImageSaver).GetMethod(nameof(CVRCamImageSaver.SavePicture), BindingFlags.Public | BindingFlags.Static),
null,
new HarmonyLib.HarmonyMethod(typeof(ExtendedGameNotifications).GetMethod(nameof(OnPictureSave), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteAllProps), BindingFlags.Public | BindingFlags.Static),
null,
new HarmonyLib.HarmonyMethod(typeof(ExtendedGameNotifications).GetMethod(nameof(OnAllPropsDelete), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteMyProps), BindingFlags.Public | BindingFlags.Static),
null,
new HarmonyLib.HarmonyMethod(typeof(ExtendedGameNotifications).GetMethod(nameof(OnOwnPropsDelete), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnLocalAvatarLoad()
{
try
{
if(Utils.IsMenuOpened())
Utils.ShowMenuNotification("Avatar changed", 1f);
else
Utils.ShowHUDNotification("(Synced) Client", "Avatar changed");
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPropSpawned()
{
try
{
if(Utils.IsConnected())
{
if(Utils.IsMenuOpened())
Utils.ShowMenuNotification("Prop spawned", 1f);
else
Utils.ShowHUDNotification("(Synced) Client", "Prop spawned");
}
else
{
if(Utils.IsMenuOpened())
Utils.ShowMenuAlert("Prop Error", "Not connected to live instance");
else
Utils.ShowHUDNotification("(Local) Client", "Unable to spawn prop", "Not connected to live instance");
}
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnGameNetworkConnectionClosed(DisconnectedEventArgs __1)
{
try
{
if((__1 != null) && (!__1.LocalDisconnect))
Utils.ShowHUDNotification("(Local) Client", "Connection lost", (__1.Error != System.Net.Sockets.SocketError.Success) ? ("Reason: " + __1.Error.ToString()) : "", true);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPictureSave()
{
try
{
Utils.ShowHUDNotification("(Local) Client", "Screenshot saved");
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnAllPropsDelete()
{
try
{
if(Utils.IsMenuOpened())
Utils.ShowMenuNotification("Props are removed");
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnOwnPropsDelete()
{
try
{
if(Utils.IsMenuOpened())
Utils.ShowMenuNotification("Own props are removed");
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -0,0 +1,10 @@
using System.Reflection;
[assembly: AssemblyTitle("ExtendedGameNotifications")]
[assembly: AssemblyVersion("1.0.1")]
[assembly: AssemblyFileVersion("1.0.1")]
[assembly: MelonLoader.MelonInfo(typeof(ml_egn.ExtendedGameNotifications), "ExtendedGameNotifications", "1.0.1", "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)]

12
ml_egn/README.md Normal file
View file

@ -0,0 +1,12 @@
# Extended Game Notifications
This mod shows main menu notifications and HUD popups upon:
* Avatar changing
* Prop spawning/deletion
* Server connection loss
* Screenshot saving
Basically, merged previous `Avatar Change Info` and `Server Connection Info` mods in one.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_egn.dll` in `Mods` folder of game

46
ml_egn/Utils.cs Normal file
View file

@ -0,0 +1,46 @@
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Networking;
using ABI_RC.Core.UI;
using DarkRift;
namespace ml_egn
{
static class Utils
{
public static bool IsMenuOpened()
{
return ((ViewManager.Instance != null) ? ViewManager.Instance.isGameMenuOpen() : false);
}
public static void ShowMenuNotification(string p_message, float p_time = 1f)
{
if(ViewManager.Instance != null)
ViewManager.Instance.TriggerPushNotification(p_message, p_time);
}
public static void ShowMenuAlert(string p_title, string p_message)
{
if(ViewManager.Instance != null)
ViewManager.Instance.TriggerAlert(p_title, p_message, -1, true);
}
public static void ShowHUDNotification(string p_title, string p_message, string p_small = "", bool p_immediate = false)
{
if(CohtmlHud.Instance != null)
{
if(p_immediate)
CohtmlHud.Instance.ViewDropTextImmediate(p_title, p_message, p_small);
else
CohtmlHud.Instance.ViewDropText(p_title, p_message, p_small);
}
}
public static bool IsConnected()
{
bool l_result = false;
if((NetworkManager.Instance != null) && (NetworkManager.Instance.GameNetwork != null))
l_result = (NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected);
return l_result;
}
}
}

82
ml_egn/ml_egn.csproj Normal file
View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ml_egn</RootNamespace>
<AssemblyName>ml_egn</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony, Version=2.9.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="DarkRift">
<Private>False</Private>
</Reference>
<Reference Include="DarkRift.Client, Version=2.4.5.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
<Reference Include="MelonLoader, Version=0.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "D:\Games\Steam\steamapps\common\ChilloutVR\Mods\"</PostBuildEvent>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ReferencePath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
</PropertyGroup>
</Project>

View file

@ -4,7 +4,6 @@ namespace ml_lme
{
static class GestureMatcher
{
readonly static Vector2[] ms_fingerLimits =
{
new Vector2(-50f, 0f),
@ -14,100 +13,75 @@ namespace ml_lme
new Vector2(-10f, 25f)
};
public class GesturesData
public class HandData
{
readonly public static int ms_handsCount = 2;
readonly public static int ms_fingersCount = 5;
public bool m_present = false;
public Vector3 m_position = Vector3.zero;
public Quaternion m_rotation = Quaternion.identity;
public Vector3 m_elbowPosition = Vector3.zero;
public readonly float[] m_spreads = null;
public readonly float[] m_bends = null;
public float m_grabStrength = 0f;
public bool[] m_handsPresenses = null;
public Vector3[] m_handsPositons = null;
public Quaternion[] m_handsRotations = null;
public Vector3[] m_elbowsPositions = null;
public float[] m_leftFingersBends = null;
public float[] m_leftFingersSpreads = null;
public float[] m_rightFingersBends = null;
public float[] m_rightFingersSpreads = null;
public GesturesData()
public HandData()
{
m_handsPresenses = new bool[ms_handsCount];
m_handsPositons = new Vector3[ms_handsCount];
m_handsRotations = new Quaternion[ms_handsCount];
m_elbowsPositions = new Vector3[ms_handsCount];
m_leftFingersBends = new float[ms_fingersCount];
m_leftFingersSpreads = new float[ms_fingersCount];
m_rightFingersBends = new float[ms_fingersCount];
m_rightFingersSpreads = new float[ms_fingersCount];
m_spreads = new float[5];
m_bends = new float[5];
}
public void Reset()
{
m_present = false;
for(int i = 0; i < 5; i++)
{
m_bends[i] = 0f;
m_spreads[i] = 0f;
}
m_grabStrength = 0f;
}
}
public static void GetGestures(Leap.Frame p_frame, ref GesturesData p_data)
public class LeapData
{
// Fill as default
for(int i = 0; i < GesturesData.ms_handsCount; i++)
p_data.m_handsPresenses[i] = false;
for(int i = 0; i < GesturesData.ms_fingersCount; i++)
public readonly HandData m_leftHand = null;
public readonly HandData m_rightHand = null;
public LeapData()
{
p_data.m_leftFingersBends[i] = 0f;
p_data.m_leftFingersSpreads[i] = 0f;
p_data.m_rightFingersBends[i] = 0f;
p_data.m_leftFingersSpreads[i] = 0f;
m_leftHand = new HandData();
m_rightHand = new HandData();
}
public void Reset()
{
m_leftHand.Reset();
m_rightHand.Reset();
}
}
public static void GetFrameData(Leap.Frame p_frame, LeapData p_data)
{
p_data.Reset();
// Fill hands data
foreach(Leap.Hand l_hand in p_frame.Hands)
{
int l_sideID = (l_hand.IsLeft ? 0 : 1);
if(!p_data.m_handsPresenses[l_sideID])
{
p_data.m_handsPresenses[l_sideID] = true;
FillHandPosition(l_hand, ref p_data.m_handsPositons[l_sideID]);
FillHandRotation(l_hand, ref p_data.m_handsRotations[l_sideID]);
FillElbowPosition(l_hand, ref p_data.m_elbowsPositions[l_sideID]);
switch(l_sideID)
{
case 0:
{
FillFingerBends(l_hand, ref p_data.m_leftFingersBends);
FilFingerSpreads(l_hand, ref p_data.m_leftFingersSpreads);
}
break;
case 1:
{
FillFingerBends(l_hand, ref p_data.m_rightFingersBends);
FilFingerSpreads(l_hand, ref p_data.m_rightFingersSpreads);
}
break;
}
}
if(l_hand.IsLeft && !p_data.m_leftHand.m_present)
FillHandData(l_hand, p_data.m_leftHand);
if(l_hand.IsRight && !p_data.m_rightHand.m_present)
FillHandData(l_hand, p_data.m_rightHand);
}
}
static void FillHandPosition(Leap.Hand p_hand, ref Vector3 p_pos)
static void FillHandData(Leap.Hand p_hand, HandData p_data)
{
// Unity's IK and FinalIK move hand bones to target, therefore - wrist
p_pos.x = p_hand.WristPosition.x;
p_pos.y = p_hand.WristPosition.y;
p_pos.z = p_hand.WristPosition.z;
}
p_data.m_present = true;
p_data.m_position.Set(p_hand.WristPosition.x, p_hand.WristPosition.y, p_hand.WristPosition.z);
p_data.m_rotation.Set(p_hand.Rotation.x, p_hand.Rotation.y, p_hand.Rotation.z, p_hand.Rotation.w);
p_data.m_elbowPosition.Set(p_hand.Arm.ElbowPosition.x, p_hand.Arm.ElbowPosition.y, p_hand.Arm.ElbowPosition.z);
static void FillHandRotation(Leap.Hand p_hand, ref Quaternion p_rot)
{
p_rot.x = p_hand.Rotation.x;
p_rot.y = p_hand.Rotation.y;
p_rot.z = p_hand.Rotation.z;
p_rot.w = p_hand.Rotation.w;
}
static void FillElbowPosition(Leap.Hand p_hand, ref Vector3 p_pos)
{
p_pos.x = p_hand.Arm.ElbowPosition.x;
p_pos.y = p_hand.Arm.ElbowPosition.y;
p_pos.z = p_hand.Arm.ElbowPosition.z;
}
static void FillFingerBends(Leap.Hand p_hand, ref float[] p_bends)
{
// Bends
foreach(Leap.Finger l_finger in p_hand.Fingers)
{
Quaternion l_prevSegment = Quaternion.identity;
@ -132,12 +106,10 @@ namespace ml_lme
l_angle += l_curAngle;
}
p_bends[(int)l_finger.Type] = Mathf.InverseLerp(0f, (l_finger.Type == Leap.Finger.FingerType.TYPE_THUMB) ? 90f : 180f, l_angle);
p_data.m_bends[(int)l_finger.Type] = Mathf.InverseLerp(0f, (l_finger.Type == Leap.Finger.FingerType.TYPE_THUMB) ? 90f : 180f, l_angle);
}
}
static void FilFingerSpreads(Leap.Hand p_hand, ref float[] p_spreads)
{
// Spreads
foreach(Leap.Finger l_finger in p_hand.Fingers)
{
float l_angle = 0f;
@ -162,13 +134,15 @@ namespace ml_lme
if(l_finger.Type != Leap.Finger.FingerType.TYPE_THUMB)
{
if(l_angle < 0f)
p_spreads[(int)l_finger.Type] = 0.5f * Mathf.InverseLerp(ms_fingerLimits[(int)l_finger.Type].x, 0f, l_angle);
p_data.m_spreads[(int)l_finger.Type] = 0.5f * Mathf.InverseLerp(ms_fingerLimits[(int)l_finger.Type].x, 0f, l_angle);
else
p_spreads[(int)l_finger.Type] = 0.5f + 0.5f * Mathf.InverseLerp(0f, ms_fingerLimits[(int)l_finger.Type].y, l_angle);
p_data.m_spreads[(int)l_finger.Type] = 0.5f + 0.5f * Mathf.InverseLerp(0f, ms_fingerLimits[(int)l_finger.Type].y, l_angle);
}
else
p_spreads[(int)l_finger.Type] = Mathf.InverseLerp(ms_fingerLimits[(int)l_finger.Type].x, ms_fingerLimits[(int)l_finger.Type].y, l_angle);
p_data.m_spreads[(int)l_finger.Type] = Mathf.InverseLerp(ms_fingerLimits[(int)l_finger.Type].x, ms_fingerLimits[(int)l_finger.Type].y, l_angle);
}
p_data.m_grabStrength = (p_data.m_bends[1] + p_data.m_bends[2] + p_data.m_bends[3] + p_data.m_bends[4]) * 0.25f;
}
}
}

286
ml_lme/LeapInput.cs Normal file
View file

@ -0,0 +1,286 @@
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.IK;
using System.Collections;
using System.Reflection;
using UnityEngine;
namespace ml_lme
{
[DisallowMultipleComponent]
class LeapInput : CVRInputModule
{
static readonly FieldInfo ms_indexGestureToggle = typeof(InputModuleSteamVR).GetField("_steamVrIndexGestureToggleValue", BindingFlags.Instance | BindingFlags.NonPublic);
CVRInputManager m_inputManager = null;
InputModuleSteamVR m_steamVrModule = null;
bool m_inVR = false;
bool m_gripToGrab = true;
ControllerRay m_handRayLeft = null;
ControllerRay m_handRayRight = null;
LineRenderer m_lineLeft = null;
LineRenderer m_lineRight = null;
bool m_interactLeft = false;
bool m_interactRight = false;
bool m_gripLeft = false;
bool m_gripRight = false;
public new void Start()
{
base.Start();
m_inputManager = CVRInputManager.Instance; // _inputManager is stripped out, cool beans
m_steamVrModule = m_inputManager.GetComponent<InputModuleSteamVR>();
m_inVR = Utils.IsInVR();
m_handRayLeft = LeapTracking.GetInstance().GetLeftHand().gameObject.AddComponent<ControllerRay>();
m_handRayLeft.hand = true;
m_handRayLeft.generalMask = -1485;
m_handRayLeft.isInteractionRay = true;
m_handRayLeft.triggerGazeEvents = false;
m_handRayLeft.holderRoot = m_handRayLeft.gameObject;
m_handRayRight = LeapTracking.GetInstance().GetRightHand().gameObject.AddComponent<ControllerRay>();
m_handRayRight.hand = false;
m_handRayRight.generalMask = -1485;
m_handRayRight.isInteractionRay = true;
m_handRayRight.triggerGazeEvents = false;
m_handRayRight.holderRoot = m_handRayRight.gameObject;
m_lineLeft = m_handRayLeft.gameObject.AddComponent<LineRenderer>();
m_lineLeft.endWidth = 1f;
m_lineLeft.startWidth = 1f;
m_lineLeft.textureMode = LineTextureMode.Tile;
m_lineLeft.useWorldSpace = false;
m_lineLeft.widthMultiplier = 1f;
m_lineLeft.allowOcclusionWhenDynamic = false;
m_lineLeft.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
m_lineLeft.enabled = false;
m_lineLeft.receiveShadows = false;
m_handRayLeft.lineRenderer = m_lineLeft;
m_lineRight = m_handRayRight.gameObject.AddComponent<LineRenderer>();
m_lineRight.endWidth = 1f;
m_lineRight.startWidth = 1f;
m_lineRight.textureMode = LineTextureMode.Tile;
m_lineRight.useWorldSpace = false;
m_lineRight.widthMultiplier = 1f;
m_lineRight.allowOcclusionWhenDynamic = false;
m_lineRight.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
m_lineRight.enabled = false;
m_lineRight.receiveShadows = false;
m_handRayRight.lineRenderer = m_lineRight;
Settings.EnabledChange += this.OnEnableChange;
Settings.InputChange += this.OnInputChange;
OnEnableChange(Settings.Enabled);
OnInputChange(Settings.Input);
MelonLoader.MelonCoroutines.Start(WaitForSettings());
MelonLoader.MelonCoroutines.Start(WaitForMaterial());
}
IEnumerator WaitForSettings()
{
while(MetaPort.Instance == null)
yield return null;
while(MetaPort.Instance.settings == null)
yield return null;
m_gripToGrab = MetaPort.Instance.settings.GetSettingsBool("ControlUseGripToGrab", true);
MetaPort.Instance.settings.settingBoolChanged.AddListener(this.OnGameSettingBoolChange);
}
IEnumerator WaitForMaterial()
{
while(PlayerSetup.Instance == null)
yield return null;
while(PlayerSetup.Instance.leftRay == null)
yield return null;
while(PlayerSetup.Instance.leftRay.lineRenderer == null)
yield return null;
m_lineLeft.material = PlayerSetup.Instance.leftRay.lineRenderer.material;
m_lineLeft.gameObject.layer = PlayerSetup.Instance.leftRay.gameObject.layer;
m_lineRight.material = PlayerSetup.Instance.leftRay.lineRenderer.material;
m_lineRight.gameObject.layer = PlayerSetup.Instance.leftRay.gameObject.layer;
}
void OnDestroy()
{
Settings.EnabledChange -= this.OnEnableChange;
Settings.InputChange -= this.OnInputChange;
}
void Update()
{
GestureMatcher.LeapData l_data = LeapManager.GetInstance().GetLatestData();
if(Settings.Enabled)
{
if(l_data.m_leftHand.m_present)
SetFingersInput(l_data.m_leftHand, true);
if(l_data.m_rightHand.m_present)
SetFingersInput(l_data.m_rightHand, false);
}
m_handRayLeft.enabled = (l_data.m_leftHand.m_present && (!m_inVR || !Utils.IsLeftHandTracked() || !Settings.FingersOnly));
m_handRayRight.enabled = (l_data.m_rightHand.m_present && (!m_inVR || !Utils.IsRightHandTracked() || !Settings.FingersOnly));
}
public override void UpdateInput()
{
if(Settings.Enabled && Settings.Input)
{
GestureMatcher.LeapData l_data = LeapManager.GetInstance().GetLatestData();
if(l_data.m_leftHand.m_present && (!m_inVR || !Utils.IsLeftHandTracked() || !Settings.FingersOnly))
{
float l_strength = l_data.m_leftHand.m_grabStrength;
float l_interactValue = 0f;
if(m_gripToGrab)
l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(Mathf.Min(Settings.GripThreadhold, Settings.InteractThreadhold), Mathf.Max(Settings.GripThreadhold, Settings.InteractThreadhold), l_strength));
else
l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.InteractThreadhold, l_strength));
m_inputManager.interactLeftValue = Mathf.Max(l_interactValue, m_inputManager.interactLeftValue);
if(m_interactLeft != (l_strength > Settings.InteractThreadhold))
{
m_interactLeft = (l_strength > Settings.InteractThreadhold);
m_inputManager.interactLeftDown |= m_interactLeft;
m_inputManager.interactLeftUp |= !m_interactLeft;
}
float l_gripValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.GripThreadhold, l_strength));
m_inputManager.gripLeftValue = Mathf.Max(l_gripValue, m_inputManager.gripLeftValue);
if(m_gripLeft != (l_strength > Settings.GripThreadhold))
{
m_gripLeft = (l_strength > Settings.GripThreadhold);
m_inputManager.gripLeftDown |= m_gripLeft;
m_inputManager.gripLeftUp |= !m_gripLeft;
}
}
if(l_data.m_rightHand.m_present && (!m_inVR || !Utils.IsRightHandTracked() || !Settings.FingersOnly))
{
float l_strength = l_data.m_rightHand.m_grabStrength;
float l_interactValue = 0f;
if(m_gripToGrab)
l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(Mathf.Min(Settings.GripThreadhold, Settings.InteractThreadhold), Mathf.Max(Settings.GripThreadhold, Settings.InteractThreadhold), l_strength));
else
l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.InteractThreadhold, l_strength));
m_inputManager.interactRightValue = Mathf.Max(l_interactValue, m_inputManager.interactRightValue);
if(m_interactRight != (l_strength > Settings.InteractThreadhold))
{
m_interactRight = (l_strength > Settings.InteractThreadhold);
m_inputManager.interactRightDown |= m_interactRight;
m_inputManager.interactRightUp |= !m_interactRight;
}
float l_gripValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.GripThreadhold, l_strength));
m_inputManager.gripRightValue = Mathf.Max(l_gripValue, m_inputManager.gripRightValue);
if(m_gripRight != (l_strength > Settings.GripThreadhold))
{
m_gripRight = (l_strength > Settings.GripThreadhold);
m_inputManager.gripRightDown |= m_gripRight;
m_inputManager.gripRightUp |= !m_gripRight;
}
}
}
}
// Settings changes
void OnEnableChange(bool p_state)
{
OnInputChange(p_state && Settings.Input);
UpdateFingerTracking();
}
void OnInputChange(bool p_state)
{
((MonoBehaviour)m_handRayLeft).enabled = (p_state && Settings.Enabled);
((MonoBehaviour)m_handRayRight).enabled = (p_state && Settings.Enabled);
m_lineLeft.enabled = (p_state && Settings.Enabled);
m_lineRight.enabled = (p_state && Settings.Enabled);
if(!p_state)
{
m_handRayLeft.DropObject(true);
m_handRayLeft.ClearGrabbedObject();
m_handRayRight.DropObject(true);
m_handRayRight.ClearGrabbedObject();
m_interactLeft = false;
m_interactRight = false;
m_gripLeft = false;
m_gripRight = false;
}
}
// Game events
internal void OnAvatarSetup()
{
m_inVR = Utils.IsInVR();
UpdateFingerTracking();
}
internal void OnRayScale(float p_scale)
{
m_handRayLeft.SetRayScale(p_scale);
m_handRayRight.SetRayScale(p_scale);
}
// Arbitrary
void UpdateFingerTracking()
{
m_inputManager.individualFingerTracking = (Settings.Enabled || (m_inVR && Utils.AreKnucklesInUse() && !(bool)ms_indexGestureToggle.GetValue(m_steamVrModule)));
IKSystem.Instance.FingerSystem.controlActive = m_inputManager.individualFingerTracking;
}
void SetFingersInput(GestureMatcher.HandData p_hand, bool p_left)
{
m_inputManager.individualFingerTracking = true;
IKSystem.Instance.FingerSystem.controlActive = true;
if(p_left)
{
m_inputManager.fingerCurlLeftThumb = p_hand.m_bends[0];
m_inputManager.fingerCurlLeftIndex = p_hand.m_bends[1];
m_inputManager.fingerCurlLeftMiddle = p_hand.m_bends[2];
m_inputManager.fingerCurlLeftRing = p_hand.m_bends[3];
m_inputManager.fingerCurlLeftPinky = p_hand.m_bends[4];
IKSystem.Instance.FingerSystem.leftThumbCurl = p_hand.m_bends[0];
IKSystem.Instance.FingerSystem.leftIndexCurl = p_hand.m_bends[1];
IKSystem.Instance.FingerSystem.leftMiddleCurl = p_hand.m_bends[2];
IKSystem.Instance.FingerSystem.leftRingCurl = p_hand.m_bends[3];
IKSystem.Instance.FingerSystem.leftPinkyCurl = p_hand.m_bends[4];
}
else
{
m_inputManager.fingerCurlRightThumb = p_hand.m_bends[0];
m_inputManager.fingerCurlRightIndex = p_hand.m_bends[1];
m_inputManager.fingerCurlRightMiddle = p_hand.m_bends[2];
m_inputManager.fingerCurlRightRing = p_hand.m_bends[3];
m_inputManager.fingerCurlRightPinky = p_hand.m_bends[4];
IKSystem.Instance.FingerSystem.rightThumbCurl = p_hand.m_bends[0];
IKSystem.Instance.FingerSystem.rightIndexCurl = p_hand.m_bends[1];
IKSystem.Instance.FingerSystem.rightMiddleCurl = p_hand.m_bends[2];
IKSystem.Instance.FingerSystem.rightRingCurl = p_hand.m_bends[3];
IKSystem.Instance.FingerSystem.rightPinkyCurl = p_hand.m_bends[4];
}
}
void OnGameSettingBoolChange(string p_name, bool p_state)
{
if(p_name == "ControlUseGripToGrab")
m_gripToGrab = p_state;
}
}
}

212
ml_lme/LeapManager.cs Normal file
View file

@ -0,0 +1,212 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using System.Collections;
using UnityEngine;
namespace ml_lme
{
[DisallowMultipleComponent]
class LeapManager : MonoBehaviour
{
static LeapManager ms_instance = null;
readonly Leap.Controller m_leapController = null;
readonly GestureMatcher.LeapData m_leapData = null;
LeapTracking m_leapTracking = null;
LeapTracked m_leapTracked = null;
LeapInput m_leapInput = null;
public static LeapManager GetInstance() => ms_instance;
internal LeapManager()
{
m_leapController = new Leap.Controller();
m_leapData = new GestureMatcher.LeapData();
}
~LeapManager()
{
m_leapController.StopConnection();
m_leapController.Dispose();
}
void Start()
{
if(ms_instance == null)
ms_instance = this;
DontDestroyOnLoad(this);
m_leapController.Device += this.OnLeapDeviceInitialized;
m_leapController.DeviceFailure += this.OnLeapDeviceFailure;
m_leapController.DeviceLost += this.OnLeapDeviceLost;
m_leapController.Connect += this.OnLeapServiceConnect;
m_leapController.Disconnect += this.OnLeapServiceDisconnect;
Settings.EnabledChange += this.OnEnableChange;
Settings.TrackingModeChange += this.OnTrackingModeChange;
m_leapTracking = new GameObject("[LeapTrackingRoot]").AddComponent<LeapTracking>();
m_leapTracking.transform.parent = this.transform;
MelonLoader.MelonCoroutines.Start(WaitForInputManager());
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
OnEnableChange(Settings.Enabled);
OnTrackingModeChange(Settings.TrackingMode);
}
void OnDestroy()
{
if(ms_instance == this)
ms_instance = null;
m_leapController.Device -= this.OnLeapDeviceInitialized;
m_leapController.DeviceFailure -= this.OnLeapDeviceFailure;
m_leapController.DeviceLost -= this.OnLeapDeviceLost;
m_leapController.Connect -= this.OnLeapServiceConnect;
m_leapController.Disconnect -= this.OnLeapServiceDisconnect;
Settings.EnabledChange -= this.OnEnableChange;
Settings.TrackingModeChange -= this.OnTrackingModeChange;
}
IEnumerator WaitForInputManager()
{
while(CVRInputManager.Instance == null)
yield return null;
m_leapInput = CVRInputManager.Instance.gameObject.AddComponent<LeapInput>();
}
IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_leapTracked = PlayerSetup.Instance.gameObject.AddComponent<LeapTracked>();
}
void Update()
{
if(Settings.Enabled)
{
m_leapData.Reset();
if(m_leapController.IsConnected)
{
Leap.Frame l_frame = m_leapController.Frame();
GestureMatcher.GetFrameData(l_frame, m_leapData);
}
}
}
public GestureMatcher.LeapData GetLatestData() => m_leapData;
// Device events
void OnLeapDeviceInitialized(object p_sender, Leap.DeviceEventArgs p_args)
{
if(Settings.Enabled)
{
m_leapController.SubscribeToDeviceEvents(p_args.Device);
UpdateDeviceTrackingMode();
}
Utils.ShowHUDNotification("Leap Motion Extension", "Device initialized");
}
void OnLeapDeviceFailure(object p_sender, Leap.DeviceFailureEventArgs p_args)
{
Utils.ShowHUDNotification("Leap Motion Extension", "Device failure", "Code " + p_args.ErrorCode + ": " + p_args.ErrorMessage);
}
void OnLeapDeviceLost(object p_sender, Leap.DeviceEventArgs p_args)
{
m_leapController.UnsubscribeFromDeviceEvents(p_args.Device);
Utils.ShowHUDNotification("Leap Motion Extension", "Device lost");
}
void OnLeapServiceConnect(object p_sender, Leap.ConnectionEventArgs p_args)
{
Utils.ShowHUDNotification("Leap Motion Extension", "Service connected");
}
void OnLeapServiceDisconnect(object p_sender, Leap.ConnectionLostEventArgs p_args)
{
Utils.ShowHUDNotification("Leap Motion Extension", "Service disconnected");
}
// Settings
void OnEnableChange(bool p_state)
{
if(p_state)
{
m_leapController.StartConnection();
UpdateDeviceTrackingMode();
}
else
m_leapController.StopConnection();
}
void OnTrackingModeChange(Settings.LeapTrackingMode p_mode)
{
if(Settings.Enabled)
UpdateDeviceTrackingMode();
}
// Game events
internal void OnAvatarClear()
{
if(m_leapTracking != null)
m_leapTracking.OnAvatarClear();
if(m_leapTracked != null)
m_leapTracked.OnAvatarClear();
}
internal void OnAvatarSetup()
{
if(m_leapTracking != null)
m_leapTracking.OnAvatarSetup();
if(m_leapInput != null)
m_leapInput.OnAvatarSetup();
if(m_leapTracked != null)
m_leapTracked.OnAvatarSetup();
}
internal void OnCalibrate()
{
if(m_leapTracked != null)
m_leapTracked.OnCalibrate();
}
internal void OnRayScale(float p_scale)
{
if(m_leapInput != null)
m_leapInput.OnRayScale(p_scale);
}
internal void OnPlayspaceScale(float p_relation)
{
if(m_leapTracking != null)
m_leapTracking.OnPlayspaceScale(p_relation);
}
// Arbitrary
void UpdateDeviceTrackingMode()
{
m_leapController.ClearPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_SCREENTOP, null);
m_leapController.ClearPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_HMD, null);
switch(Settings.TrackingMode)
{
case Settings.LeapTrackingMode.Screentop:
m_leapController.SetPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_SCREENTOP, null);
break;
case Settings.LeapTrackingMode.HMD:
m_leapController.SetPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_HMD, null);
break;
}
}
}
}

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.IK;
using RootMotion.FinalIK;
using System.Reflection;
@ -11,57 +10,54 @@ namespace ml_lme
class LeapTracked : MonoBehaviour
{
static readonly float[] ms_tposeMuscles = typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetField("TPoseMuscles", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as float[];
static readonly FieldInfo ms_indexGestureToggle = typeof(InputModuleSteamVR).GetField("_steamVrIndexGestureToggleValue", BindingFlags.Instance | BindingFlags.NonPublic);
static readonly Quaternion ms_offsetLeft = Quaternion.Euler(0f, 0f, 270f);
static readonly Quaternion ms_offsetRight = Quaternion.Euler(0f, 0f, 90f);
static readonly Quaternion ms_offsetLeftDesktop = Quaternion.Euler(0f, 90f, 0f);
static readonly Quaternion ms_offsetRightDesktop = Quaternion.Euler(0f, 270f, 0f);
InputModuleSteamVR m_steamVrModule = null;
VRIK m_vrIK = null;
Vector2 m_armsWeights = Vector2.zero;
bool m_isInVR = false;
Vector4 m_armsWeights = Vector2.zero;
bool m_inVR = false;
Transform m_hips = null;
Transform m_origLeftHand = null;
Transform m_origRightHand = null;
Transform m_origLeftElbow = null;
Transform m_origRightElbow = null;
bool m_enabled = true;
bool m_fingersOnly = false;
bool m_trackElbows = true;
ArmIK m_leftIK = null;
ArmIK m_rightIK = null;
ArmIK m_leftArmIK = null;
ArmIK m_rightArmIK = null;
HumanPoseHandler m_poseHandler = null;
HumanPose m_pose;
Transform m_leftHand = null;
Transform m_rightHand = null;
Transform m_leftHandTarget = null;
Transform m_rightHandTarget = null;
Transform m_leftElbow = null;
Transform m_rightElbow = null;
bool m_leftTargetActive = false;
bool m_rightTargetActive = false;
void Start()
{
m_steamVrModule = CVRInputManager.Instance.GetComponent<InputModuleSteamVR>();
m_isInVR = Utils.IsInVR();
m_inVR = Utils.IsInVR();
if(m_leftHand != null)
{
m_leftHandTarget = new GameObject("RotationTarget").transform;
m_leftHandTarget.parent = m_leftHand;
m_leftHandTarget.localPosition = Vector3.zero;
m_leftHandTarget.localRotation = Quaternion.identity;
}
if(m_rightHand != null)
{
m_rightHandTarget = new GameObject("RotationTarget").transform;
m_rightHandTarget.parent = m_rightHand;
m_rightHandTarget.localPosition = Vector3.zero;
m_rightHandTarget.localRotation = Quaternion.identity;
}
m_leftHandTarget = new GameObject("RotationTarget").transform;
m_leftHandTarget.parent = LeapTracking.GetInstance().GetLeftHand();
m_leftHandTarget.localPosition = Vector3.zero;
m_leftHandTarget.localRotation = Quaternion.identity;
m_rightHandTarget = new GameObject("RotationTarget").transform;
m_rightHandTarget.parent = LeapTracking.GetInstance().GetRightHand();
m_rightHandTarget.localPosition = Vector3.zero;
m_rightHandTarget.localRotation = Quaternion.identity;
Settings.EnabledChange += this.SetEnabled;
Settings.FingersOnlyChange += this.SetFingersOnly;
Settings.TrackElbowsChange += this.SetTrackElbows;
SetEnabled(Settings.Enabled);
SetFingersOnly(Settings.FingersOnly);
SetTrackElbows(Settings.TrackElbows);
}
void OnDestroy()
@ -71,17 +67,16 @@ namespace ml_lme
Settings.TrackElbowsChange -= this.SetTrackElbows;
}
public void SetEnabled(bool p_state)
void SetEnabled(bool p_state)
{
m_enabled = p_state;
RefreshFingersTracking();
RefreshArmIK();
if(!m_enabled || m_fingersOnly)
RestoreVRIK();
}
public void SetFingersOnly(bool p_state)
void SetFingersOnly(bool p_state)
{
m_fingersOnly = p_state;
@ -90,199 +85,163 @@ namespace ml_lme
RestoreVRIK();
}
public void SetTrackElbows(bool p_state)
void SetTrackElbows(bool p_state)
{
m_trackElbows = p_state;
if((m_leftIK != null) && (m_rightIK != null))
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
}
if(m_vrIK != null)
{
if(m_leftTargetActive)
m_vrIK.solver.leftArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
if(m_rightTargetActive)
m_vrIK.solver.rightArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
}
RestoreVRIK();
}
public void SetTransforms(Transform p_left, Transform p_right, Transform p_leftElbow, Transform p_rightElbow)
{
m_leftHand = p_left;
m_rightHand = p_right;
m_leftElbow = p_leftElbow;
m_rightElbow = p_rightElbow;
}
public void UpdateTracking(GestureMatcher.GesturesData p_data)
void Update()
{
if(m_enabled)
{
if((m_leftIK != null) && (m_rightIK != null))
{
m_leftIK.solver.IKPositionWeight = Mathf.Lerp(m_leftIK.solver.IKPositionWeight, (p_data.m_handsPresenses[0] && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_leftIK.solver.IKRotationWeight = Mathf.Lerp(m_leftIK.solver.IKRotationWeight, (p_data.m_handsPresenses[0] && !m_fingersOnly) ? 1f : 0f, 0.25f);
if(m_trackElbows)
m_leftIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_leftIK.solver.arm.bendGoalWeight, (p_data.m_handsPresenses[0] && !m_fingersOnly) ? 1f : 0f, 0.25f);
GestureMatcher.LeapData l_data = LeapManager.GetInstance().GetLatestData();
m_rightIK.solver.IKPositionWeight = Mathf.Lerp(m_rightIK.solver.IKPositionWeight, (p_data.m_handsPresenses[1] && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_rightIK.solver.IKRotationWeight = Mathf.Lerp(m_rightIK.solver.IKRotationWeight, (p_data.m_handsPresenses[1] && !m_fingersOnly) ? 1f : 0f, 0.25f);
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftArmIK.solver.IKPositionWeight = Mathf.Lerp(m_leftArmIK.solver.IKPositionWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_leftArmIK.solver.IKRotationWeight = Mathf.Lerp(m_leftArmIK.solver.IKRotationWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
if(m_trackElbows)
m_rightIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_rightIK.solver.arm.bendGoalWeight, (p_data.m_handsPresenses[1] && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_leftArmIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_leftArmIK.solver.arm.bendGoalWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_rightArmIK.solver.IKPositionWeight = Mathf.Lerp(m_rightArmIK.solver.IKPositionWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_rightArmIK.solver.IKRotationWeight = Mathf.Lerp(m_rightArmIK.solver.IKRotationWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
if(m_trackElbows)
m_rightArmIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_rightArmIK.solver.arm.bendGoalWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
}
if((m_vrIK != null) && !m_fingersOnly)
{
if(p_data.m_handsPresenses[0] && !m_leftTargetActive)
if(l_data.m_leftHand.m_present && !m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = m_leftHandTarget;
m_vrIK.solver.leftArm.bendGoal = m_leftElbow;
m_vrIK.solver.leftArm.bendGoal = LeapTracking.GetInstance().GetLeftElbow();
m_vrIK.solver.leftArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftTargetActive = true;
}
if(!p_data.m_handsPresenses[0] && m_leftTargetActive)
if(!l_data.m_leftHand.m_present && m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = IKSystem.Instance.leftHandAnchor;
m_vrIK.solver.leftArm.bendGoal = null;
m_vrIK.solver.leftArm.bendGoalWeight = 0f;
m_vrIK.solver.leftArm.target = m_origLeftHand;
m_vrIK.solver.leftArm.bendGoal = m_origLeftElbow;
m_vrIK.solver.leftArm.bendGoalWeight = ((m_origLeftElbow != null) ? 1f : 0f);
m_leftTargetActive = false;
}
if(p_data.m_handsPresenses[1] && !m_rightTargetActive)
if(l_data.m_rightHand.m_present && !m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_rightHandTarget;
m_vrIK.solver.rightArm.bendGoal = m_rightElbow;
m_vrIK.solver.rightArm.bendGoal = LeapTracking.GetInstance().GetRightElbow();
m_vrIK.solver.rightArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightTargetActive = true;
}
if(!p_data.m_handsPresenses[1] && m_rightTargetActive)
if(!l_data.m_rightHand.m_present && m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = IKSystem.Instance.rightHandAnchor;
m_vrIK.solver.rightArm.bendGoal = null;
m_vrIK.solver.rightArm.bendGoalWeight = 0f;
m_vrIK.solver.rightArm.target = m_origRightHand;
m_vrIK.solver.rightArm.bendGoal = m_origRightElbow;
m_vrIK.solver.rightArm.bendGoalWeight = ((m_origRightElbow != null) ? 1f : 0f);
m_rightTargetActive = false;
}
}
if(p_data.m_handsPresenses[0])
{
CVRInputManager.Instance.individualFingerTracking = true;
CVRInputManager.Instance.fingerCurlLeftThumb = p_data.m_leftFingersBends[0];
CVRInputManager.Instance.fingerCurlLeftIndex = p_data.m_leftFingersBends[1];
CVRInputManager.Instance.fingerCurlLeftMiddle = p_data.m_leftFingersBends[2];
CVRInputManager.Instance.fingerCurlLeftRing = p_data.m_leftFingersBends[3];
CVRInputManager.Instance.fingerCurlLeftPinky = p_data.m_leftFingersBends[4];
IKSystem.Instance.FingerSystem.controlActive = true;
IKSystem.Instance.FingerSystem.leftThumbCurl = p_data.m_leftFingersBends[0];
IKSystem.Instance.FingerSystem.leftIndexCurl = p_data.m_leftFingersBends[1];
IKSystem.Instance.FingerSystem.leftMiddleCurl = p_data.m_leftFingersBends[2];
IKSystem.Instance.FingerSystem.leftRingCurl = p_data.m_leftFingersBends[3];
IKSystem.Instance.FingerSystem.leftPinkyCurl = p_data.m_leftFingersBends[4];
}
if(p_data.m_handsPresenses[1])
{
CVRInputManager.Instance.individualFingerTracking = true;
CVRInputManager.Instance.fingerCurlRightThumb = p_data.m_rightFingersBends[0];
CVRInputManager.Instance.fingerCurlRightIndex = p_data.m_rightFingersBends[1];
CVRInputManager.Instance.fingerCurlRightMiddle = p_data.m_rightFingersBends[2];
CVRInputManager.Instance.fingerCurlRightRing = p_data.m_rightFingersBends[3];
CVRInputManager.Instance.fingerCurlRightPinky = p_data.m_rightFingersBends[4];
IKSystem.Instance.FingerSystem.controlActive = true;
IKSystem.Instance.FingerSystem.rightThumbCurl = p_data.m_rightFingersBends[0];
IKSystem.Instance.FingerSystem.rightIndexCurl = p_data.m_rightFingersBends[1];
IKSystem.Instance.FingerSystem.rightMiddleCurl = p_data.m_rightFingersBends[2];
IKSystem.Instance.FingerSystem.rightRingCurl = p_data.m_rightFingersBends[3];
IKSystem.Instance.FingerSystem.rightPinkyCurl = p_data.m_rightFingersBends[4];
}
}
}
public void UpdateTrackingLate(GestureMatcher.GesturesData p_data)
void LateUpdate()
{
if(m_enabled && !m_isInVR && (m_poseHandler != null))
if(m_enabled && !m_inVR && (m_poseHandler != null))
{
GestureMatcher.LeapData l_data = LeapManager.GetInstance().GetLatestData();
Vector3 l_hipsLocalPos = m_hips.localPosition;
m_poseHandler.GetHumanPose(ref m_pose);
UpdateFingers(p_data);
UpdateFingers(l_data);
m_poseHandler.SetHumanPose(ref m_pose);
m_hips.localPosition = l_hipsLocalPos;
}
}
void UpdateFingers(GestureMatcher.GesturesData p_data)
void UpdateFingers(GestureMatcher.LeapData p_data)
{
if(p_data.m_handsPresenses[0])
if(p_data.m_leftHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb1Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_leftFingersBends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb2Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_leftFingersBends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb3Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_leftFingersBends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumbSpread, Mathf.Lerp(-1.5f, 1.0f, p_data.m_leftFingersSpreads[0])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb1Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb2Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb3Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumbSpread, Mathf.Lerp(-1.5f, 1.0f, p_data.m_leftHand.m_spreads[0])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndexSpread, Mathf.Lerp(1f, -1f, p_data.m_leftFingersSpreads[1])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndexSpread, Mathf.Lerp(1f, -1f, p_data.m_leftHand.m_spreads[1])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddleSpread, Mathf.Lerp(2f, -2f, p_data.m_leftFingersSpreads[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddleSpread, Mathf.Lerp(2f, -2f, p_data.m_leftHand.m_spreads[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRingSpread, Mathf.Lerp(-2f, 2f, p_data.m_leftFingersSpreads[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRingSpread, Mathf.Lerp(-2f, 2f, p_data.m_leftHand.m_spreads[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftFingersBends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittleSpread, Mathf.Lerp(-0.5f, 1f, p_data.m_leftFingersSpreads[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittleSpread, Mathf.Lerp(-0.5f, 1f, p_data.m_leftHand.m_spreads[4]));
}
if(p_data.m_handsPresenses[1])
if(p_data.m_rightHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb1Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_rightFingersBends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb2Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_rightFingersBends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb3Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_rightFingersBends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumbSpread, Mathf.Lerp(-1.5f, 1.0f, p_data.m_rightFingersSpreads[0])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb1Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb2Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb3Stretched, Mathf.Lerp(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumbSpread, Mathf.Lerp(-1.5f, 1.0f, p_data.m_rightHand.m_spreads[0])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndexSpread, Mathf.Lerp(1f, -1f, p_data.m_rightFingersSpreads[1])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndexSpread, Mathf.Lerp(1f, -1f, p_data.m_rightHand.m_spreads[1])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddleSpread, Mathf.Lerp(2f, -2f, p_data.m_rightFingersSpreads[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddleSpread, Mathf.Lerp(2f, -2f, p_data.m_rightHand.m_spreads[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRingSpread, Mathf.Lerp(-2f, 2f, p_data.m_rightFingersSpreads[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRingSpread, Mathf.Lerp(-2f, 2f, p_data.m_rightHand.m_spreads[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightFingersBends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittleSpread, Mathf.Lerp(-0.5f, 1f, p_data.m_rightFingersSpreads[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle1Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle2Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle3Stretched, Mathf.Lerp(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittleSpread, Mathf.Lerp(-0.5f, 1f, p_data.m_rightHand.m_spreads[4]));
}
}
public void OnAvatarClear()
internal void OnAvatarClear()
{
m_vrIK = null;
m_origLeftHand = null;
m_origRightHand = null;
m_origLeftElbow = null;
m_origRightElbow = null;
m_hips = null;
m_armsWeights = Vector2.zero;
m_leftIK = null;
m_rightIK = null;
m_leftArmIK = null;
m_rightArmIK = null;
m_leftTargetActive = false;
m_rightTargetActive = false;
if(!m_isInVR)
if(!m_inVR)
m_poseHandler?.Dispose();
m_poseHandler = null;
@ -292,24 +251,27 @@ namespace ml_lme
m_rightHandTarget.localRotation = Quaternion.identity;
}
public void OnSetupAvatar()
internal void OnAvatarSetup()
{
m_inVR = Utils.IsInVR();
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
RefreshFingersTracking();
if(PlayerSetup.Instance._animator.isHuman)
{
if(!m_isInVR)
m_hips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
if(!m_inVR)
{
// Force desktop avatar into T-Pose
m_poseHandler = new HumanPoseHandler(PlayerSetup.Instance._animator.avatar, PlayerSetup.Instance._avatar.transform);
m_poseHandler.GetHumanPose(ref m_pose);
HumanPose l_tPose = new HumanPose();
l_tPose.bodyPosition = m_pose.bodyPosition;
l_tPose.bodyRotation = m_pose.bodyRotation;
l_tPose.muscles = new float[m_pose.muscles.Length];
HumanPose l_tPose = new HumanPose
{
bodyPosition = m_pose.bodyPosition,
bodyRotation = m_pose.bodyRotation,
muscles = new float[m_pose.muscles.Length]
};
for(int i = 0; i < l_tPose.muscles.Length; i++)
l_tPose.muscles[i] = ms_tposeMuscles[i];
m_poseHandler.SetHumanPose(ref l_tPose);
@ -317,11 +279,11 @@ namespace ml_lme
Transform l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand);
if(l_hand != null)
m_leftHandTarget.localRotation = ((m_vrIK != null) ? ms_offsetLeft : ms_offsetLeftDesktop) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
m_leftHandTarget.localRotation = (m_inVR ? ms_offsetLeft : ms_offsetLeftDesktop) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand);
if(l_hand != null)
m_rightHandTarget.localRotation = ((m_vrIK != null) ? ms_offsetRight : ms_offsetRightDesktop) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
m_rightHandTarget.localRotation = (m_inVR ? ms_offsetRight : ms_offsetRightDesktop) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
if(m_vrIK == null)
{
@ -331,9 +293,9 @@ namespace ml_lme
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Spine);
m_leftIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_leftIK.solver.isLeft = true;
m_leftIK.solver.SetChain(
m_leftArmIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_leftArmIK.solver.isLeft = true;
m_leftArmIK.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftUpperArm),
@ -341,14 +303,14 @@ namespace ml_lme
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand),
PlayerSetup.Instance._animator.transform
);
m_leftIK.solver.arm.target = m_leftHandTarget;
m_leftIK.solver.arm.bendGoal = m_leftElbow;
m_leftIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftIK.enabled = (m_enabled && !m_fingersOnly);
m_leftArmIK.solver.arm.target = m_leftHandTarget;
m_leftArmIK.solver.arm.bendGoal = LeapTracking.GetInstance().GetLeftElbow();
m_leftArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftArmIK.enabled = (m_enabled && !m_fingersOnly);
m_rightIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_rightIK.solver.isLeft = false;
m_rightIK.solver.SetChain(
m_rightArmIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_rightArmIK.solver.isLeft = false;
m_rightArmIK.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightUpperArm),
@ -356,31 +318,51 @@ namespace ml_lme
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand),
PlayerSetup.Instance._animator.transform
);
m_rightIK.solver.arm.target = m_rightHandTarget;
m_rightIK.solver.arm.bendGoal = m_rightElbow;
m_rightIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightIK.enabled = (m_enabled && !m_fingersOnly);
m_rightArmIK.solver.arm.target = m_rightHandTarget;
m_rightArmIK.solver.arm.bendGoal = LeapTracking.GetInstance().GetRightElbow();
m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightArmIK.enabled = (m_enabled && !m_fingersOnly);
m_poseHandler?.SetHumanPose(ref m_pose);
}
else
{
m_origLeftHand = m_vrIK.solver.leftArm.target;
m_origRightHand = m_vrIK.solver.rightArm.target;
m_origLeftElbow = m_vrIK.solver.leftArm.bendGoal;
m_origRightElbow = m_vrIK.solver.rightArm.bendGoal;
m_vrIK.solver.OnPreUpdate += this.OnIKPreUpdate;
m_vrIK.solver.OnPostUpdate += this.OnIKPostUpdate;
}
}
}
internal void OnCalibrate()
{
if(m_vrIK != null)
{
m_origLeftHand = m_vrIK.solver.leftArm.target;
m_origRightHand = m_vrIK.solver.rightArm.target;
m_origLeftElbow = m_vrIK.solver.leftArm.bendGoal;
m_origRightElbow = m_vrIK.solver.rightArm.bendGoal;
}
}
void OnIKPreUpdate()
{
m_armsWeights.Set(m_vrIK.solver.leftArm.positionWeight, m_vrIK.solver.rightArm.positionWeight);
m_armsWeights.Set(
m_vrIK.solver.leftArm.positionWeight,
m_vrIK.solver.leftArm.rotationWeight,
m_vrIK.solver.rightArm.positionWeight,
m_vrIK.solver.rightArm.rotationWeight
);
if(m_leftTargetActive && Mathf.Approximately(m_armsWeights.x, 0f))
if(m_leftTargetActive && (Mathf.Approximately(m_armsWeights.x, 0f) || Mathf.Approximately(m_armsWeights.y, 0f)))
{
m_vrIK.solver.leftArm.positionWeight = 1f;
m_vrIK.solver.leftArm.rotationWeight = 1f;
}
if(m_rightTargetActive && Mathf.Approximately(m_armsWeights.y, 0f))
if(m_rightTargetActive && (Mathf.Approximately(m_armsWeights.z, 0f) || Mathf.Approximately(m_armsWeights.w, 0f)))
{
m_vrIK.solver.rightArm.positionWeight = 1f;
m_vrIK.solver.rightArm.rotationWeight = 1f;
@ -389,10 +371,9 @@ namespace ml_lme
void OnIKPostUpdate()
{
m_vrIK.solver.leftArm.positionWeight = m_armsWeights.x;
m_vrIK.solver.leftArm.rotationWeight = m_armsWeights.x;
m_vrIK.solver.rightArm.positionWeight = m_armsWeights.y;
m_vrIK.solver.rightArm.rotationWeight = m_armsWeights.y;
m_vrIK.solver.leftArm.rotationWeight = m_armsWeights.y;
m_vrIK.solver.rightArm.positionWeight = m_armsWeights.z;
m_vrIK.solver.rightArm.rotationWeight = m_armsWeights.w;
}
void RestoreVRIK()
@ -401,16 +382,16 @@ namespace ml_lme
{
if(m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = IKSystem.Instance.leftHandAnchor;
m_vrIK.solver.leftArm.bendGoal = null;
m_vrIK.solver.leftArm.bendGoalWeight = 0f;
m_vrIK.solver.leftArm.target = m_origLeftHand;
m_vrIK.solver.leftArm.bendGoal = m_origLeftElbow;
m_vrIK.solver.leftArm.bendGoalWeight = ((m_origLeftElbow != null) ? 1f : 0f);
m_leftTargetActive = false;
}
if(m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = IKSystem.Instance.rightHandAnchor;
m_vrIK.solver.rightArm.bendGoal = null;
m_vrIK.solver.rightArm.bendGoalWeight = 0f;
m_vrIK.solver.rightArm.target = m_origRightHand;
m_vrIK.solver.rightArm.bendGoal = m_origRightElbow;
m_vrIK.solver.rightArm.bendGoalWeight = ((m_origRightElbow != null) ? 1f : 0f);
m_rightTargetActive = false;
}
}
@ -418,19 +399,13 @@ namespace ml_lme
void RefreshArmIK()
{
if((m_leftIK != null) && (m_rightIK != null))
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftIK.enabled = (m_enabled && !m_fingersOnly);
m_rightIK.enabled = (m_enabled && !m_fingersOnly);
m_leftArmIK.enabled = (m_enabled && !m_fingersOnly);
m_rightArmIK.enabled = (m_enabled && !m_fingersOnly);
}
}
void RefreshFingersTracking()
{
CVRInputManager.Instance.individualFingerTracking = (m_enabled || (m_isInVR && Utils.AreKnucklesInUse() && !(bool)ms_indexGestureToggle.GetValue(m_steamVrModule)));
IKSystem.Instance.FingerSystem.controlActive = CVRInputManager.Instance.individualFingerTracking;
}
static void UpdatePoseMuscle(ref HumanPose p_pose, int p_index, float p_value)
{
if(p_pose.muscles.Length > p_index)

204
ml_lme/LeapTracking.cs Normal file
View file

@ -0,0 +1,204 @@
using ABI_RC.Core.Player;
using System.Collections;
using UnityEngine;
namespace ml_lme
{
[DisallowMultipleComponent]
class LeapTracking : MonoBehaviour
{
static LeapTracking ms_instance = null;
static Quaternion ms_identityRotation = Quaternion.identity;
bool m_inVR = false;
GameObject m_leapHandLeft = null;
GameObject m_leapHandRight = null;
GameObject m_leapElbowLeft = null;
GameObject m_leapElbowRight = null;
GameObject m_leapControllerModel = null;
float m_scaleRelation = 1f;
public static LeapTracking GetInstance() => ms_instance;
void Start()
{
if(ms_instance == null)
ms_instance = this;
m_inVR = Utils.IsInVR();
m_leapHandLeft = new GameObject("LeapHandLeft");
m_leapHandLeft.transform.parent = this.transform;
m_leapHandLeft.transform.localPosition = Vector3.zero;
m_leapHandLeft.transform.localRotation = Quaternion.identity;
m_leapHandRight = new GameObject("LeapHandRight");
m_leapHandRight.transform.parent = this.transform;
m_leapHandRight.transform.localPosition = Vector3.zero;
m_leapHandRight.transform.localRotation = Quaternion.identity;
m_leapElbowLeft = new GameObject("LeapElbowLeft");
m_leapElbowLeft.transform.parent = this.transform;
m_leapElbowLeft.transform.localPosition = Vector3.zero;
m_leapElbowLeft.transform.localRotation = Quaternion.identity;
m_leapElbowRight = new GameObject("LeapElbowRight");
m_leapElbowRight.transform.parent = this.transform;
m_leapElbowRight.transform.localPosition = Vector3.zero;
m_leapElbowRight.transform.localRotation = Quaternion.identity;
m_leapControllerModel = AssetsHandler.GetAsset("assets/models/leapmotion/leap_motion_1_0.obj");
if(m_leapControllerModel != null)
{
m_leapControllerModel.name = "LeapModel";
m_leapControllerModel.transform.parent = this.transform;
m_leapControllerModel.transform.localPosition = Vector3.zero;
m_leapControllerModel.transform.localRotation = Quaternion.identity;
}
Settings.DesktopOffsetChange += this.OnDesktopOffsetChange;
Settings.ModelVisibilityChange += this.OnModelVisibilityChange;
Settings.TrackingModeChange += this.OnTrackingModeChange;
Settings.RootAngleChange += this.OnRootAngleChange;
Settings.HeadAttachChange += this.OnHeadAttachChange;
Settings.HeadOffsetChange += this.OnHeadOffsetChange;
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
OnModelVisibilityChange(Settings.ModelVisibility);
OnTrackingModeChange(Settings.TrackingMode);
OnRootAngleChange(Settings.RootAngle);
}
IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
OnHeadAttachChange(Settings.HeadAttach);
}
void OnDestroy()
{
if(ms_instance == this)
ms_instance = null;
Settings.DesktopOffsetChange -= this.OnDesktopOffsetChange;
Settings.ModelVisibilityChange -= this.OnModelVisibilityChange;
Settings.TrackingModeChange -= this.OnTrackingModeChange;
Settings.RootAngleChange -= this.OnRootAngleChange;
Settings.HeadAttachChange -= this.OnHeadAttachChange;
Settings.HeadOffsetChange -= this.OnHeadOffsetChange;
}
void Update()
{
if(Settings.Enabled)
{
GestureMatcher.LeapData l_data = LeapManager.GetInstance().GetLatestData();
if(l_data.m_leftHand.m_present)
{
Utils.LeapToUnity(ref l_data.m_leftHand.m_position, ref l_data.m_leftHand.m_rotation, Settings.TrackingMode);
m_leapHandLeft.transform.localPosition = l_data.m_leftHand.m_position;
m_leapHandLeft.transform.localRotation = l_data.m_leftHand.m_rotation;
Utils.LeapToUnity(ref l_data.m_leftHand.m_elbowPosition, ref ms_identityRotation, Settings.TrackingMode);
m_leapElbowLeft.transform.localPosition = l_data.m_leftHand.m_elbowPosition;
}
if(l_data.m_rightHand.m_present)
{
Utils.LeapToUnity(ref l_data.m_rightHand.m_position, ref l_data.m_rightHand.m_rotation, Settings.TrackingMode);
m_leapHandRight.transform.localPosition = l_data.m_rightHand.m_position;
m_leapHandRight.transform.localRotation = l_data.m_rightHand.m_rotation;
Utils.LeapToUnity(ref l_data.m_rightHand.m_elbowPosition, ref ms_identityRotation, Settings.TrackingMode);
m_leapElbowRight.transform.localPosition = l_data.m_rightHand.m_elbowPosition;
}
}
}
public Transform GetLeftHand() => m_leapHandLeft.transform;
public Transform GetRightHand() => m_leapHandRight.transform;
public Transform GetLeftElbow() => m_leapElbowLeft.transform;
public Transform GetRightElbow() => m_leapElbowRight.transform;
// Settings
void OnDesktopOffsetChange(Vector3 p_offset)
{
if(!Settings.HeadAttach)
this.transform.localPosition = p_offset * (!m_inVR ? m_scaleRelation : 1f);
}
void OnModelVisibilityChange(bool p_state)
{
m_leapControllerModel.SetActive(p_state);
}
void OnTrackingModeChange(Settings.LeapTrackingMode p_mode)
{
switch(p_mode)
{
case Settings.LeapTrackingMode.Screentop:
m_leapControllerModel.transform.localRotation = Quaternion.Euler(0f, 0f, 180f);
break;
case Settings.LeapTrackingMode.Desktop:
m_leapControllerModel.transform.localRotation = Quaternion.identity;
break;
case Settings.LeapTrackingMode.HMD:
m_leapControllerModel.transform.localRotation = Quaternion.Euler(270f, 180f, 0f);
break;
}
}
void OnRootAngleChange(Vector3 p_angle)
{
this.transform.localRotation = Quaternion.Euler(p_angle);
}
void OnHeadAttachChange(bool p_state)
{
if(!m_inVR)
{
this.transform.parent = (p_state ? PlayerSetup.Instance.desktopCamera.transform : PlayerSetup.Instance.desktopCameraRig.transform);
this.transform.localPosition = (p_state ? Settings.HeadOffset : Settings.DesktopOffset) * m_scaleRelation;
}
else
{
this.transform.parent = (p_state ? PlayerSetup.Instance.vrCamera.transform : PlayerSetup.Instance.vrCameraRig.transform);
this.transform.localPosition = (p_state ? Settings.HeadOffset : Settings.DesktopOffset);
}
this.transform.localScale = Vector3.one * (!m_inVR ? m_scaleRelation : 1f);
this.transform.localRotation = Quaternion.Euler(Settings.RootAngle);
}
void OnHeadOffsetChange(Vector3 p_offset)
{
if(Settings.HeadAttach)
this.transform.localPosition = p_offset * (!m_inVR ? m_scaleRelation : 1f);
}
// Game events
internal void OnAvatarClear()
{
m_scaleRelation = 1f;
OnHeadAttachChange(Settings.HeadAttach);
}
internal void OnAvatarSetup()
{
m_inVR = Utils.IsInVR();
OnHeadAttachChange(Settings.HeadAttach);
}
internal void OnPlayspaceScale(float p_relation)
{
m_scaleRelation = p_relation;
OnHeadAttachChange(Settings.HeadAttach);
}
}
}

View file

@ -1,5 +1,6 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.UI;
using ABI_RC.Systems.IK.SubSystems;
using System.Collections;
using System.Reflection;
using UnityEngine;
@ -10,16 +11,7 @@ namespace ml_lme
{
static LeapMotionExtension ms_instance = null;
Leap.Controller m_leapController = null;
GestureMatcher.GesturesData m_gesturesData = null;
GameObject m_leapTrackingRoot = null;
GameObject[] m_leapHands = null;
GameObject[] m_leapElbows = null;
GameObject m_leapControllerModel = null;
LeapTracked m_leapTracked = null;
bool m_isInVR = false;
LeapManager m_leapManager = null;
public override void OnInitializeMelon()
{
@ -27,26 +19,8 @@ namespace ml_lme
ms_instance = this;
DependenciesHandler.ExtractDependencies();
Settings.Init();
Settings.EnabledChange += this.OnEnableChange;
Settings.DesktopOffsetChange += this.OnDesktopOffsetChange;
Settings.ModelVisibilityChange += this.OnModelVisibilityChange;
Settings.TrackingModeChange += this.OnTrackingModeChange;
Settings.RootAngleChange += this.OnRootAngleChange;
Settings.HeadAttachChange += this.OnHeadAttachChange;
Settings.HeadOffsetChange += this.OnHeadOffsetChange;
m_leapController = new Leap.Controller();
m_leapController.Device += this.OnLeapDeviceInitialized;
m_leapController.DeviceFailure += this.OnLeapDeviceFailure;
m_leapController.DeviceLost += this.OnLeapDeviceLost;
m_leapController.Connect += this.OnLeapServiceConnect;
m_leapController.Disconnect += this.OnLeapServiceDisconnect;
m_gesturesData = new GestureMatcher.GesturesData();
m_leapHands = new GameObject[GestureMatcher.GesturesData.ms_handsCount];
m_leapElbows = new GameObject[GestureMatcher.GesturesData.ms_handsCount];
AssetsHandler.Load();
// Patches
HarmonyInstance.Patch(
@ -59,280 +33,37 @@ namespace ml_lme
null,
new HarmonyLib.HarmonyMethod(typeof(LeapMotionExtension).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(BodySystem).GetMethod(nameof(BodySystem.Calibrate)),
null,
new HarmonyLib.HarmonyMethod(typeof(LeapMotionExtension).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetControllerRayScale)),
null,
new HarmonyLib.HarmonyMethod(typeof(LeapMotionExtension).GetMethod(nameof(OnRayScale_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("SetPlaySpaceScale", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(LeapMotionExtension).GetMethod(nameof(OnPlayspaceScale_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(CreateTrackingObjects());
}
System.Collections.IEnumerator CreateTrackingObjects()
{
AssetsHandler.Load();
while(PlayerSetup.Instance == null)
yield return null;
while(PlayerSetup.Instance.desktopCameraRig == null)
yield return null;
while(PlayerSetup.Instance.desktopCamera == null)
yield return null;
while(PlayerSetup.Instance.vrCameraRig == null)
yield return null;
while(PlayerSetup.Instance.vrCamera == null)
yield return null;
m_isInVR = Utils.IsInVR();
m_leapTrackingRoot = new GameObject("[LeapRoot]");
for(int i = 0; i < GestureMatcher.GesturesData.ms_handsCount; i++)
{
m_leapHands[i] = new GameObject("LeapHand" + i);
m_leapHands[i].transform.parent = m_leapTrackingRoot.transform;
m_leapHands[i].transform.localPosition = Vector3.zero;
m_leapHands[i].transform.localRotation = Quaternion.identity;
m_leapElbows[i] = new GameObject("LeapElbow" + i);
m_leapElbows[i].transform.parent = m_leapTrackingRoot.transform;
m_leapElbows[i].transform.localPosition = Vector3.zero;
m_leapElbows[i].transform.localRotation = Quaternion.identity;
}
m_leapControllerModel = AssetsHandler.GetAsset("assets/models/leapmotion/leap_motion_1_0.obj");
if(m_leapControllerModel != null)
{
m_leapControllerModel.name = "LeapModel";
m_leapControllerModel.transform.parent = m_leapTrackingRoot.transform;
m_leapControllerModel.transform.localPosition = Vector3.zero;
m_leapControllerModel.transform.localRotation = Quaternion.identity;
}
// Player setup
m_leapTracked = PlayerSetup.Instance.gameObject.AddComponent<LeapTracked>();
m_leapTracked.SetTransforms(m_leapHands[0].transform, m_leapHands[1].transform, m_leapElbows[0].transform, m_leapElbows[1].transform);
m_leapTracked.SetEnabled(Settings.Enabled);
m_leapTracked.SetTrackElbows(Settings.TrackElbows);
m_leapTracked.SetFingersOnly(Settings.FingersOnly);
OnEnableChange(Settings.Enabled);
OnModelVisibilityChange(Settings.ModelVisibility);
OnTrackingModeChange(Settings.TrackingMode);
OnHeadAttachChange(Settings.HeadAttach); // Includes offsets and parenting
MelonLoader.MelonCoroutines.Start(WaitForRootLogic());
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
m_leapController?.StopConnection();
m_leapController?.Dispose();
m_leapController = null;
m_gesturesData = null;
}
public override void OnUpdate()
IEnumerator WaitForRootLogic()
{
if(Settings.Enabled)
{
for(int i = 0; i < GestureMatcher.GesturesData.ms_handsCount; i++)
m_gesturesData.m_handsPresenses[i] = false;
while(ABI_RC.Core.RootLogic.Instance == null)
yield return null;
if((m_leapController != null) && m_leapController.IsConnected)
{
Leap.Frame l_frame = m_leapController.Frame();
if(l_frame != null)
{
GestureMatcher.GetGestures(l_frame, ref m_gesturesData);
for(int i = 0; i < GestureMatcher.GesturesData.ms_handsCount; i++)
{
if((m_leapHands[i] != null) && m_gesturesData.m_handsPresenses[i])
{
Vector3 l_pos = m_gesturesData.m_handsPositons[i];
Quaternion l_rot = m_gesturesData.m_handsRotations[i];
Utils.LeapToUnity(ref l_pos, ref l_rot, Settings.TrackingMode);
m_leapHands[i].transform.localPosition = l_pos;
m_leapHands[i].transform.localRotation = l_rot;
l_pos = m_gesturesData.m_elbowsPositions[i];
Utils.LeapToUnity(ref l_pos, ref l_rot, Settings.TrackingMode);
m_leapElbows[i].transform.localPosition = l_pos;
}
}
}
}
if(m_leapTracked != null)
m_leapTracked.UpdateTracking(m_gesturesData);
}
}
public override void OnLateUpdate()
{
if(Settings.Enabled && !m_isInVR && (m_leapTracked != null))
m_leapTracked.UpdateTrackingLate(m_gesturesData);
}
// Settings changes
void OnEnableChange(bool p_state)
{
if(p_state)
{
m_leapController?.StartConnection();
UpdateDeviceTrackingMode();
}
else
m_leapController?.StopConnection();
}
void OnDesktopOffsetChange(Vector3 p_offset)
{
if((m_leapTrackingRoot != null) && !Settings.HeadAttach)
{
if(!m_isInVR)
m_leapTrackingRoot.transform.localPosition = p_offset * PlayerSetup.Instance.vrCameraRig.transform.localScale.x;
else
m_leapTrackingRoot.transform.localPosition = p_offset;
}
}
void OnModelVisibilityChange(bool p_state)
{
if(m_leapControllerModel != null)
m_leapControllerModel.SetActive(p_state);
}
void OnTrackingModeChange(Settings.LeapTrackingMode p_mode)
{
if(Settings.Enabled)
UpdateDeviceTrackingMode();
if(m_leapControllerModel != null)
{
switch(p_mode)
{
case Settings.LeapTrackingMode.Screentop:
m_leapControllerModel.transform.localRotation = Quaternion.Euler(0f, 0f, 180f);
break;
case Settings.LeapTrackingMode.Desktop:
m_leapControllerModel.transform.localRotation = Quaternion.identity;
break;
case Settings.LeapTrackingMode.HMD:
m_leapControllerModel.transform.localRotation = Quaternion.Euler(270f, 180f, 0f);
break;
}
}
}
void OnRootAngleChange(Vector3 p_angle)
{
if(m_leapTrackingRoot != null)
m_leapTrackingRoot.transform.localRotation = Quaternion.Euler(p_angle);
}
void OnHeadAttachChange(bool p_state)
{
if(m_leapTrackingRoot != null)
{
if(p_state)
{
if(!m_isInVR)
{
m_leapTrackingRoot.transform.parent = PlayerSetup.Instance.desktopCamera.transform;
m_leapTrackingRoot.transform.localPosition = Settings.HeadOffset * PlayerSetup.Instance.vrCameraRig.transform.localScale.x;
m_leapTrackingRoot.transform.localScale = PlayerSetup.Instance.vrCameraRig.transform.localScale;
}
else
{
m_leapTrackingRoot.transform.parent = PlayerSetup.Instance.vrCamera.transform;
m_leapTrackingRoot.transform.localPosition = Settings.HeadOffset;
m_leapTrackingRoot.transform.localScale = Vector3.one;
}
}
else
{
if(!m_isInVR)
{
m_leapTrackingRoot.transform.parent = PlayerSetup.Instance.desktopCameraRig.transform;
m_leapTrackingRoot.transform.localPosition = Settings.DesktopOffset * PlayerSetup.Instance.vrCameraRig.transform.localScale.x;
m_leapTrackingRoot.transform.localScale = PlayerSetup.Instance.vrCameraRig.transform.localScale;
}
else
{
m_leapTrackingRoot.transform.parent = PlayerSetup.Instance.vrCameraRig.transform;
m_leapTrackingRoot.transform.localPosition = Settings.DesktopOffset;
m_leapTrackingRoot.transform.localScale = Vector3.one;
}
}
m_leapTrackingRoot.transform.localRotation = Quaternion.Euler(Settings.RootAngle);
}
}
void OnHeadOffsetChange(Vector3 p_offset)
{
if((m_leapTrackingRoot != null) && Settings.HeadAttach)
{
if(!m_isInVR)
m_leapTrackingRoot.transform.localPosition = p_offset * PlayerSetup.Instance.vrCameraRig.transform.localScale.x;
else
m_leapTrackingRoot.transform.localPosition = p_offset;
}
}
// Internal utility
void UpdateDeviceTrackingMode()
{
m_leapController?.ClearPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_SCREENTOP, null);
m_leapController?.ClearPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_HMD, null);
switch(Settings.TrackingMode)
{
case Settings.LeapTrackingMode.Screentop:
m_leapController?.SetPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_SCREENTOP, null);
break;
case Settings.LeapTrackingMode.HMD:
m_leapController?.SetPolicy(Leap.Controller.PolicyFlag.POLICY_OPTIMIZE_HMD, null);
break;
}
}
// Leap events
void OnLeapDeviceInitialized(object p_sender, Leap.DeviceEventArgs p_args)
{
if(Settings.Enabled)
{
m_leapController?.SubscribeToDeviceEvents(p_args.Device);
UpdateDeviceTrackingMode();
}
if(CohtmlHud.Instance != null)
CohtmlHud.Instance.ViewDropText("Leap Motion Extension", "Device initialized");
}
void OnLeapDeviceFailure(object p_sender, Leap.DeviceFailureEventArgs p_args)
{
if(CohtmlHud.Instance != null)
CohtmlHud.Instance.ViewDropText("Leap Motion Extension", "Device failure, code " + p_args.ErrorCode + ": " + p_args.ErrorMessage);
}
void OnLeapDeviceLost(object p_sender, Leap.DeviceEventArgs p_args)
{
m_leapController?.UnsubscribeFromDeviceEvents(p_args.Device);
if(CohtmlHud.Instance != null)
CohtmlHud.Instance.ViewDropText("Leap Motion Extension", "Device lost");
}
void OnLeapServiceConnect(object p_sender, Leap.ConnectionEventArgs p_args)
{
if(CohtmlHud.Instance != null)
CohtmlHud.Instance.ViewDropText("Leap Motion Extension", "Service connected");
}
void OnLeapServiceDisconnect(object p_sender, Leap.ConnectionLostEventArgs p_args)
{
if(CohtmlHud.Instance != null)
CohtmlHud.Instance.ViewDropText("Leap Motion Extension", "Service disconnected");
m_leapManager = new GameObject("LeapMotionManager").AddComponent<LeapManager>();
}
// Patches
@ -341,8 +72,8 @@ namespace ml_lme
{
try
{
if(m_leapTracked != null)
m_leapTracked.OnAvatarClear();
if(m_leapManager != null)
m_leapManager.OnAvatarClear();
}
catch(System.Exception e)
{
@ -355,10 +86,50 @@ namespace ml_lme
{
try
{
if(m_leapTracked != null)
m_leapTracked.OnSetupAvatar();
if(m_leapManager != null)
m_leapManager.OnAvatarSetup();
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
OnHeadAttachChange(Settings.HeadAttach);
static void OnCalibrate_Postfix() => ms_instance?.OnCalibrate();
void OnCalibrate()
{
try
{
if(m_leapManager != null)
m_leapManager.OnCalibrate();
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnRayScale_Postfix(float __0) => ms_instance?.OnRayScale(__0);
void OnRayScale(float p_scale)
{
try
{
if(m_leapManager != null)
m_leapManager.OnRayScale(p_scale);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPlayspaceScale_Postfix(float ____avatarScaleRelation) => ms_instance?.OnPlayspaceScale(____avatarScaleRelation);
void OnPlayspaceScale(float p_relation)
{
try
{
if(m_leapManager != null)
m_leapManager.OnPlayspaceScale(p_relation);
}
catch(System.Exception e)
{

View file

@ -1,10 +1,10 @@
using System.Reflection;
[assembly: AssemblyTitle("LeapMotionExtension")]
[assembly: AssemblyVersion("1.2.8")]
[assembly: AssemblyFileVersion("1.2.8")]
[assembly: AssemblyVersion("1.3.1")]
[assembly: AssemblyFileVersion("1.3.1")]
[assembly: MelonLoader.MelonInfo(typeof(ml_lme.LeapMotionExtension), "LeapMotionExtension", "1.2.8-ex2", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_lme.LeapMotionExtension), "LeapMotionExtension", "1.3.1", "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)]

View file

@ -1,5 +1,5 @@
# Leap Motion Extension
This mod allows you to use your Leap Motion controller for hands and fingers visual tracking.
This mod allows you to use your Leap Motion controller for hands and fingers tracking.
[![](.github/img_01.png)](https://youtu.be/nak1C8uibgc)
@ -12,12 +12,15 @@ This mod allows you to use your Leap Motion controller for hands and fingers vis
# Usage
## Settings
Available mod's settings in `Settings - Implementation - Leap Motion Tracking`:
* **Enable tracking:** enable hands tracking from Leap Motion data, disabled by default.
* **Tracking mode:** set Leap Motion tracking mode, available values: `Screentop`, `Desktop` (by default), `HMD`.
* **Enable tracking:** enables/disables hands tracking from Leap Motion data, disabled by default.
* **Tracking mode:** sets Leap Motion tracking mode, available values: `Screentop`, `Desktop` (by default), `HMD`.
* **Desktop offset X/Y/Z:** offset position for body attachment, (0, -45, 30) by default.
* **Attach to head:** attach hands transformation to head instead of body, disabled by default.
* **Attach to head:** attaches hands transformation to head instead of body, disabled by default.
* **Head offset X/Y/Z:** offset position for head attachment (`Attach to head` is **`true`**), (0, -30, 15) by default.
* **Offset angle X/Y/X:** rotation around specific axis, useful for neck mounts, 0 by default.
* **Track elbows:** elbows tracking, works best in `Screentop` and `HMD` tracking modes, `true` by default.
* **Fingers tracking only:** apply only fingers tracking, disabled by default.
* **Model visibility:** show Leap Motion controller model, useful for tracking visualizing, disabled by default.
* **Fingers tracking only:** applies only fingers tracking, disabled by default.
* **Model visibility:** shows Leap Motion controller model, useful for tracking visualizing, disabled by default.
* **Interaction input:** enables in-game interactions (props, menu and etc.); `true` by default.
* **Interact gesture threadhold:** activation limit for interaction based on hand gesture; 80 by default.
* **Grip gesture threadhold:** activation limit for grip based on hand gesture; 40 by default.

View file

@ -31,7 +31,10 @@ namespace ml_lme
HeadX,
HeadY,
HeadZ,
TrackElbows
TrackElbows,
Input,
InteractThreadhold,
GripThreadhold
};
static bool ms_enabled = false;
@ -43,6 +46,9 @@ namespace ml_lme
static bool ms_headAttach = false;
static Vector3 ms_headOffset = new Vector3(0f, -0.3f, 0.15f);
static bool ms_trackElbows = true;
static bool ms_input = true;
static float ms_interactThreadhold = 0.8f;
static float ms_gripThreadhold = 0.4f;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
@ -56,27 +62,35 @@ namespace ml_lme
static public event Action<bool> HeadAttachChange;
static public event Action<Vector3> HeadOffsetChange;
static public event Action<bool> TrackElbowsChange;
static public event Action<bool> InputChange;
static public event Action<float> InteractThreadholdChange;
static public event Action<float> GripThreadholdChange;
public static void Init()
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("LME");
ms_entries = new List<MelonLoader.MelonPreferences_Entry>();
ms_entries.Add(ms_category.CreateEntry(ModSetting.Enabled.ToString(), ms_enabled));
ms_entries.Add(ms_category.CreateEntry(ModSetting.DesktopX.ToString(), 0));
ms_entries.Add(ms_category.CreateEntry(ModSetting.DesktopY.ToString(), -45));
ms_entries.Add(ms_category.CreateEntry(ModSetting.DesktopZ.ToString(), 30));
ms_entries.Add(ms_category.CreateEntry(ModSetting.FingersOnly.ToString(), ms_modelVisibility));
ms_entries.Add(ms_category.CreateEntry(ModSetting.Model.ToString(), ms_modelVisibility));
ms_entries.Add(ms_category.CreateEntry(ModSetting.Mode.ToString(), (int)ms_trackingMode));
ms_entries.Add(ms_category.CreateEntry(ModSetting.AngleX.ToString(), 0));
ms_entries.Add(ms_category.CreateEntry(ModSetting.AngleY.ToString(), 0));
ms_entries.Add(ms_category.CreateEntry(ModSetting.AngleZ.ToString(), 0));
ms_entries.Add(ms_category.CreateEntry(ModSetting.Head.ToString(), ms_headAttach));
ms_entries.Add(ms_category.CreateEntry(ModSetting.HeadX.ToString(), 0));
ms_entries.Add(ms_category.CreateEntry(ModSetting.HeadY.ToString(), -30));
ms_entries.Add(ms_category.CreateEntry(ModSetting.HeadZ.ToString(), 15));
ms_entries.Add(ms_category.CreateEntry(ModSetting.TrackElbows.ToString(), true));
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.Enabled.ToString(), ms_enabled),
ms_category.CreateEntry(ModSetting.DesktopX.ToString(), 0),
ms_category.CreateEntry(ModSetting.DesktopY.ToString(), -45),
ms_category.CreateEntry(ModSetting.DesktopZ.ToString(), 30),
ms_category.CreateEntry(ModSetting.FingersOnly.ToString(), ms_modelVisibility),
ms_category.CreateEntry(ModSetting.Model.ToString(), ms_modelVisibility),
ms_category.CreateEntry(ModSetting.Mode.ToString(), (int)ms_trackingMode),
ms_category.CreateEntry(ModSetting.AngleX.ToString(), 0),
ms_category.CreateEntry(ModSetting.AngleY.ToString(), 0),
ms_category.CreateEntry(ModSetting.AngleZ.ToString(), 0),
ms_category.CreateEntry(ModSetting.Head.ToString(), ms_headAttach),
ms_category.CreateEntry(ModSetting.HeadX.ToString(), 0),
ms_category.CreateEntry(ModSetting.HeadY.ToString(), -30),
ms_category.CreateEntry(ModSetting.HeadZ.ToString(), 15),
ms_category.CreateEntry(ModSetting.TrackElbows.ToString(), true),
ms_category.CreateEntry(ModSetting.Input.ToString(), true),
ms_category.CreateEntry(ModSetting.InteractThreadhold.ToString(), 80),
ms_category.CreateEntry(ModSetting.GripThreadhold.ToString(), 40),
};
Load();
@ -129,6 +143,9 @@ namespace ml_lme
(int)ms_entries[(int)ModSetting.HeadZ].BoxedValue
) * 0.01f;
ms_trackElbows = (bool)ms_entries[(int)ModSetting.TrackElbows].BoxedValue;
ms_input = (bool)ms_entries[(int)ModSetting.Input].BoxedValue;
ms_interactThreadhold = (int)ms_entries[(int)ModSetting.InteractThreadhold].BoxedValue * 0.01f;
ms_gripThreadhold = (int)ms_entries[(int)ModSetting.GripThreadhold].BoxedValue * 0.01f;
}
static void OnToggleUpdate(string p_name, string p_value)
@ -171,6 +188,13 @@ namespace ml_lme
TrackElbowsChange?.Invoke(ms_trackElbows);
}
break;
case ModSetting.Input:
{
ms_input = bool.Parse(p_value);
InputChange?.Invoke(ms_input);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
@ -241,6 +265,18 @@ namespace ml_lme
HeadOffsetChange?.Invoke(ms_headOffset);
}
break;
case ModSetting.InteractThreadhold:
{
ms_interactThreadhold = int.Parse(p_value) * 0.01f;
InteractThreadholdChange?.Invoke(ms_interactThreadhold);
}
break;
case ModSetting.GripThreadhold:
{
ms_gripThreadhold = int.Parse(p_value) * 0.01f;
GripThreadholdChange?.Invoke(ms_gripThreadhold);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
@ -301,5 +337,17 @@ namespace ml_lme
{
get => ms_trackElbows;
}
public static bool Input
{
get => ms_input;
}
public static float InteractThreadhold
{
get => ms_interactThreadhold;
}
public static float GripThreadhold
{
get => ms_gripThreadhold;
}
}
}

View file

@ -1,4 +1,5 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.UI;
using System.Linq;
using UnityEngine;
@ -8,16 +9,28 @@ namespace ml_lme
{
static readonly Quaternion ms_hmdRotationFix = new Quaternion(0f, 0.7071068f, 0.7071068f, 0f);
static readonly Quaternion ms_screentopRotationFix = new Quaternion(0f, 0f, -1f, 0f);
public static bool AreKnucklesInUse() => PlayerSetup.Instance._trackerManager.trackerNames.Contains("knuckles");
public static bool IsInVR() => ((ABI_RC.Core.Savior.CheckVR.Instance != null) && ABI_RC.Core.Savior.CheckVR.Instance.hasVrDeviceLoaded);
public static bool AreKnucklesInUse() => PlayerSetup.Instance._trackerManager.trackerNames.Contains("knuckles");
public static bool IsLeftHandTracked() => ((VRTrackerManager.Instance.leftHand != null) && VRTrackerManager.Instance.leftHand.active);
public static bool IsRightHandTracked() => ((VRTrackerManager.Instance.rightHand != null) && VRTrackerManager.Instance.rightHand.active);
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{
return Matrix4x4.TRS(p_pos ? p_transform.position : Vector3.zero, p_rot ? p_transform.rotation : Quaternion.identity, p_scl ? p_transform.lossyScale : Vector3.one);
}
public static void ShowHUDNotification(string p_title, string p_message, string p_small = "", bool p_immediate = false)
{
if(CohtmlHud.Instance != null)
{
if(p_immediate)
CohtmlHud.Instance.ViewDropTextImmediate(p_title, p_message, p_small);
else
CohtmlHud.Instance.ViewDropText(p_title, p_message, p_small);
}
}
public static void LeapToUnity(ref Vector3 p_pos, ref Quaternion p_rot, Settings.LeapTrackingMode p_mode)
{
p_pos *= 0.001f;

View file

@ -84,7 +84,10 @@
<Compile Include="AssetsHandler.cs" />
<Compile Include="DependenciesHandler.cs" />
<Compile Include="GestureMatcher.cs" />
<Compile Include="LeapInput.cs" />
<Compile Include="LeapManager.cs" />
<Compile Include="LeapTracked.cs" />
<Compile Include="LeapTracking.cs" />
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scripts.cs" />

View file

@ -384,6 +384,27 @@ function inp_dropdown_mod_lme(_obj, _callbackName) {
<div id="Model" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Interaction input: </div>
<div class ="option-input">
<div id="Input" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Interact gesture threadhold: </div>
<div class ="option-input">
<div id="InteractThreadhold" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="80"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Grip gesture threadhold: </div>
<div class ="option-input">
<div id="GripThreadhold" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);

View file

@ -3,38 +3,28 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.1738
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_fpt", "ml_fpt\ml_fpt.csproj", "{EC0A8C41-A429-42CD-B8FA-401A802D4BA6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_lme", "ml_lme\ml_lme.csproj", "{83CC74B7-F444-40E1-BD06-67CEC995A919}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_aci", "ml_aci\ml_aci.csproj", "{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_drs", "ml_drs\ml_drs.csproj", "{06CD5155-4459-48C3-8A53-E0B91136351B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_amt", "ml_amt\ml_amt.csproj", "{74E13D02-A506-41A2-A2CF-C8B3D5B1E452}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_sci", "ml_sci\ml_sci.csproj", "{E5481D41-196C-4241-AF26-6595EF1863C1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_dht", "ml_dht\ml_dht.csproj", "{6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_egn", "ml_egn\ml_egn.csproj", "{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_pam", "ml_pam\ml_pam.csproj", "{3B5028DE-8C79-40DF-A1EF-BDB29D366125}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EC0A8C41-A429-42CD-B8FA-401A802D4BA6}.Debug|x64.ActiveCfg = Debug|x64
{EC0A8C41-A429-42CD-B8FA-401A802D4BA6}.Debug|x64.Build.0 = Debug|x64
{EC0A8C41-A429-42CD-B8FA-401A802D4BA6}.Release|x64.ActiveCfg = Release|x64
{EC0A8C41-A429-42CD-B8FA-401A802D4BA6}.Release|x64.Build.0 = Release|x64
{83CC74B7-F444-40E1-BD06-67CEC995A919}.Debug|x64.ActiveCfg = Debug|x64
{83CC74B7-F444-40E1-BD06-67CEC995A919}.Debug|x64.Build.0 = Debug|x64
{83CC74B7-F444-40E1-BD06-67CEC995A919}.Release|x64.ActiveCfg = Release|x64
{83CC74B7-F444-40E1-BD06-67CEC995A919}.Release|x64.Build.0 = Release|x64
{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}.Debug|x64.ActiveCfg = Debug|x64
{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}.Debug|x64.Build.0 = Debug|x64
{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}.Release|x64.ActiveCfg = Release|x64
{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}.Release|x64.Build.0 = Release|x64
{06CD5155-4459-48C3-8A53-E0B91136351B}.Debug|x64.ActiveCfg = Debug|x64
{06CD5155-4459-48C3-8A53-E0B91136351B}.Debug|x64.Build.0 = Debug|x64
{06CD5155-4459-48C3-8A53-E0B91136351B}.Release|x64.ActiveCfg = Release|x64
@ -43,14 +33,18 @@ Global
{74E13D02-A506-41A2-A2CF-C8B3D5B1E452}.Debug|x64.Build.0 = Debug|x64
{74E13D02-A506-41A2-A2CF-C8B3D5B1E452}.Release|x64.ActiveCfg = Release|x64
{74E13D02-A506-41A2-A2CF-C8B3D5B1E452}.Release|x64.Build.0 = Release|x64
{E5481D41-196C-4241-AF26-6595EF1863C1}.Debug|x64.ActiveCfg = Debug|x64
{E5481D41-196C-4241-AF26-6595EF1863C1}.Debug|x64.Build.0 = Debug|x64
{E5481D41-196C-4241-AF26-6595EF1863C1}.Release|x64.ActiveCfg = Release|x64
{E5481D41-196C-4241-AF26-6595EF1863C1}.Release|x64.Build.0 = Release|x64
{6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}.Debug|x64.ActiveCfg = Debug|x64
{6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}.Debug|x64.Build.0 = Debug|x64
{6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}.Release|x64.ActiveCfg = Release|x64
{6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}.Release|x64.Build.0 = Release|x64
{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}.Debug|x64.ActiveCfg = Debug|x64
{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}.Debug|x64.Build.0 = Debug|x64
{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}.Release|x64.ActiveCfg = Release|x64
{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}.Release|x64.Build.0 = Release|x64
{3B5028DE-8C79-40DF-A1EF-BDB29D366125}.Debug|x64.ActiveCfg = Debug|x64
{3B5028DE-8C79-40DF-A1EF-BDB29D366125}.Debug|x64.Build.0 = Debug|x64
{3B5028DE-8C79-40DF-A1EF-BDB29D366125}.Release|x64.ActiveCfg = Release|x64
{3B5028DE-8C79-40DF-A1EF-BDB29D366125}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

284
ml_pam/ArmMover.cs Normal file
View file

@ -0,0 +1,284 @@
using ABI.CCK.Components;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using RootMotion.FinalIK;
using System.Reflection;
using UnityEngine;
namespace ml_pam
{
[DisallowMultipleComponent]
class ArmMover : MonoBehaviour
{
const float c_offsetLimit = 0.5f;
static readonly float[] ms_tposeMuscles = typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetField("TPoseMuscles", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as float[];
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
static readonly Quaternion ms_offsetRight = Quaternion.Euler(0f, 0f, 90f);
static readonly Quaternion ms_offsetRightDesktop = Quaternion.Euler(0f, 270f, 0f);
static readonly Quaternion ms_palmToLeft = Quaternion.Euler(0f, 0f, -90f);
bool m_inVR = false;
VRIK m_vrIK = null;
Vector2 m_armWeight = Vector2.zero;
Transform m_origRightHand = null;
float m_playspaceScale = 1f;
bool m_enabled = true;
ArmIK m_armIK = null;
Transform m_target = null;
Transform m_rotationTarget = null;
CVRPickupObject m_pickup = null;
Matrix4x4 m_offset = Matrix4x4.identity;
bool m_targetActive = false;
void Start()
{
m_inVR = Utils.IsInVR();
m_target = new GameObject("ArmPickupTarget").transform;
m_target.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_target.localPosition = Vector3.zero;
m_target.localRotation = Quaternion.identity;
m_rotationTarget = new GameObject("RotationTarget").transform;
m_rotationTarget.parent = m_target;
m_rotationTarget.localPosition = new Vector3(c_offsetLimit * Settings.GrabOffset, 0f, 0f);
m_rotationTarget.localRotation = Quaternion.identity;
m_enabled = Settings.Enabled;
Settings.EnabledChange += this.SetEnabled;
Settings.GrabOffsetChange += this.SetGrabOffset;
}
void OnDestroy()
{
Settings.EnabledChange -= this.SetEnabled;
Settings.GrabOffsetChange -= this.SetGrabOffset;
}
void Update()
{
if(m_enabled && (m_pickup != null))
{
Matrix4x4 l_result = m_pickup.transform.GetMatrix() * m_offset;
m_target.position = l_result * ms_pointVector;
}
}
void OnIKPreUpdate()
{
m_armWeight.Set(m_vrIK.solver.rightArm.positionWeight, m_vrIK.solver.rightArm.rotationWeight);
if(m_targetActive && (Mathf.Approximately(m_armWeight.x, 0f) || Mathf.Approximately(m_armWeight.y, 0f)))
{
m_vrIK.solver.rightArm.positionWeight = 1f;
m_vrIK.solver.rightArm.rotationWeight = 1f;
}
}
void OnIKPostUpdate()
{
m_vrIK.solver.rightArm.positionWeight = m_armWeight.x;
m_vrIK.solver.rightArm.rotationWeight = m_armWeight.y;
}
// Settings
void SetEnabled(bool p_state)
{
m_enabled = p_state;
RefreshArmIK();
if(m_enabled)
RestorePickup();
else
RestoreVRIK();
}
void SetGrabOffset(float p_value)
{
if(m_rotationTarget != null)
m_rotationTarget.localPosition = new Vector3(c_offsetLimit * m_playspaceScale * p_value, 0f, 0f);
}
// Game events
internal void OnAvatarClear()
{
m_vrIK = null;
m_origRightHand = null;
m_armIK = null;
m_targetActive = false;
}
internal void OnAvatarSetup()
{
// Recheck if user could switch to VR
if(m_inVR != Utils.IsInVR())
{
m_target.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_target.localPosition = Vector3.zero;
m_target.localRotation = Quaternion.identity;
}
m_inVR = Utils.IsInVR();
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
if(PlayerSetup.Instance._animator.isHuman)
{
HumanPose l_currentPose = new HumanPose();
HumanPoseHandler l_poseHandler = null;
if(!m_inVR)
{
l_poseHandler = new HumanPoseHandler(PlayerSetup.Instance._animator.avatar, PlayerSetup.Instance._avatar.transform);
l_poseHandler.GetHumanPose(ref l_currentPose);
HumanPose l_tPose = new HumanPose
{
bodyPosition = l_currentPose.bodyPosition,
bodyRotation = l_currentPose.bodyRotation,
muscles = new float[l_currentPose.muscles.Length]
};
for(int i = 0; i < l_tPose.muscles.Length; i++)
l_tPose.muscles[i] = ms_tposeMuscles[i];
l_poseHandler.SetHumanPose(ref l_tPose);
}
Transform l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand);
if(l_hand != null)
m_rotationTarget.localRotation = (ms_palmToLeft * (m_inVR ? ms_offsetRight : ms_offsetRightDesktop)) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
if(m_vrIK == null)
{
Transform l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.UpperChest);
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Chest);
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Spine);
m_armIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_armIK.solver.isLeft = false;
m_armIK.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightUpperArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightLowerArm),
l_hand,
PlayerSetup.Instance._animator.transform
);
m_armIK.solver.arm.target = m_rotationTarget;
m_armIK.solver.arm.positionWeight = 1f;
m_armIK.solver.arm.rotationWeight = 1f;
m_armIK.solver.IKPositionWeight = 0f;
m_armIK.solver.IKRotationWeight = 0f;
m_armIK.enabled = m_enabled;
}
else
{
m_origRightHand = m_vrIK.solver.rightArm.target;
m_vrIK.solver.OnPreUpdate += this.OnIKPreUpdate;
m_vrIK.solver.OnPostUpdate += this.OnIKPostUpdate;
}
l_poseHandler?.SetHumanPose(ref l_currentPose);
l_poseHandler?.Dispose();
}
if(m_enabled)
RestorePickup();
}
internal void OnPickupGrab(CVRPickupObject p_pickup, ControllerRay p_ray, Vector3 p_hit)
{
if(p_ray == ViewManager.Instance.desktopControllerRay)
{
m_pickup = p_pickup;
// Set offsets
if(m_pickup.gripType == CVRPickupObject.GripType.Origin)
{
if(m_pickup.ikReference != null)
m_offset = (m_pickup.transform.GetMatrix().inverse * m_pickup.ikReference.GetMatrix());
else
{
if(m_pickup.gripOrigin != null)
m_offset = m_pickup.transform.GetMatrix().inverse * m_pickup.gripOrigin.GetMatrix();
}
}
else
m_offset = m_pickup.transform.GetMatrix().inverse * Matrix4x4.Translate(p_hit);
if(m_enabled)
{
if((m_vrIK != null) && !m_targetActive)
{
m_vrIK.solver.rightArm.target = m_rotationTarget;
m_targetActive = true;
}
if(m_armIK != null)
{
m_armIK.solver.IKPositionWeight = 1f;
m_armIK.solver.IKRotationWeight = 1f;
}
}
}
}
internal void OnPickupDrop(CVRPickupObject p_pickup)
{
if(m_pickup == p_pickup)
{
m_pickup = null;
if(m_enabled)
{
RestoreVRIK();
if(m_armIK != null)
{
m_armIK.solver.IKPositionWeight = 0f;
m_armIK.solver.IKRotationWeight = 0f;
}
}
}
}
internal void OnPlayspaceScale(float p_relation)
{
m_playspaceScale = p_relation;
SetGrabOffset(Settings.GrabOffset);
}
// Arbitrary
void RestorePickup()
{
if((m_vrIK != null) && (m_pickup != null))
{
m_vrIK.solver.rightArm.target = m_rotationTarget;
m_targetActive = true;
}
if((m_armIK != null) && (m_pickup != null))
{
m_armIK.solver.IKPositionWeight = 1f;
m_armIK.solver.IKRotationWeight = 1f;
}
}
void RestoreVRIK()
{
if((m_vrIK != null) && m_targetActive)
{
m_vrIK.solver.rightArm.target = m_origRightHand;
m_targetActive = false;
}
}
void RefreshArmIK()
{
if(m_armIK != null)
m_armIK.enabled = m_enabled;
}
}
}

136
ml_pam/Main.cs Normal file
View file

@ -0,0 +1,136 @@
using ABI.CCK.Components;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using System;
using System.Reflection;
using UnityEngine;
namespace ml_pam
{
public class PickupArmMovement : MelonLoader.MelonMod
{
static PickupArmMovement ms_instance = null;
ArmMover m_localMover = null;
public override void OnInitializeMelon()
{
if(ms_instance == null)
ms_instance = this;
Settings.Init();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(PickupArmMovement).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(PickupArmMovement).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(CVRPickupObject).GetMethod(nameof(CVRPickupObject.Grab)),
null,
new HarmonyLib.HarmonyMethod(typeof(PickupArmMovement).GetMethod(nameof(OnCVRPickupObjectGrab_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(CVRPickupObject).GetMethod(nameof(CVRPickupObject.Drop)),
null,
new HarmonyLib.HarmonyMethod(typeof(PickupArmMovement).GetMethod(nameof(OnCVRPickupObjectDrop_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("SetPlaySpaceScale", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(PickupArmMovement).GetMethod(nameof(OnPlayspaceScale_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
System.Collections.IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_localMover = PlayerSetup.Instance.gameObject.AddComponent<ArmMover>();
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
try
{
if(m_localMover != null)
m_localMover.OnAvatarClear();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
void OnSetupAvatar()
{
try
{
if(m_localMover != null)
m_localMover.OnAvatarSetup();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnCVRPickupObjectGrab_Postfix(ref CVRPickupObject __instance, ControllerRay __1, Vector3 __2) => ms_instance?.OnCVRPickupObjectGrab(__instance, __1, __2);
void OnCVRPickupObjectGrab(CVRPickupObject p_pickup, ControllerRay p_ray, Vector3 p_hit)
{
try
{
if(p_pickup.IsGrabbedByMe() && (m_localMover != null))
m_localMover.OnPickupGrab(p_pickup, p_ray, p_hit);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnCVRPickupObjectDrop_Postfix(ref CVRPickupObject __instance) => ms_instance?.OnCVRPickupObjectDrop(__instance);
void OnCVRPickupObjectDrop(CVRPickupObject p_pickup)
{
try
{
if(m_localMover != null)
m_localMover.OnPickupDrop(p_pickup);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPlayspaceScale_Postfix(float ____avatarScaleRelation) => ms_instance?.OnPlayspaceScale(____avatarScaleRelation);
void OnPlayspaceScale(float p_relation)
{
try
{
if(m_localMover != null)
m_localMover.OnPlayspaceScale(p_relation);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -0,0 +1,11 @@
using System.Reflection;
[assembly: AssemblyTitle("PickupArmMovement")]
[assembly: AssemblyVersion("1.0.1")]
[assembly: AssemblyFileVersion("1.0.1")]
[assembly: MelonLoader.MelonInfo(typeof(ml_pam.PickupArmMovement), "PickupArmMovement", "1.0.1", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPriority(1)]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

12
ml_pam/README.md Normal file
View file

@ -0,0 +1,12 @@
# Pickup Arm Movement
This mod adds arm tracking upon holding pickup in desktop mode.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_pam.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - Interactions`:
* **Enable hand movement:** enables/disables arm tracking; default value - `true`.
* **Grab offset:** offset from pickup grab point; defalut value - `25`.

26
ml_pam/Scripts.cs Normal file
View file

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Reflection;
namespace ml_pam
{
static class Scripts
{
public static string GetEmbeddedScript(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}
catch(Exception) { }
return l_result;
}
}
}

113
ml_pam/Settings.cs Normal file
View file

@ -0,0 +1,113 @@
using ABI_RC.Core.InteractionSystem;
using cohtml;
using System;
using System.Collections.Generic;
namespace ml_pam
{
static class Settings
{
public enum ModSetting
{
Enabled = 0,
GrabOffset
}
static bool ms_enabled = true;
static float ms_grabOffset = 0.25f;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<bool> EnabledChange;
static public event Action<float> GrabOffsetChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("PAM");
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.Enabled.ToString(), ms_enabled),
ms_category.CreateEntry(ModSetting.GrabOffset.ToString(), 25),
};
Load();
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
static System.Collections.IEnumerator WaitMainMenuUi()
{
while(ViewManager.Instance == null)
yield return null;
while(ViewManager.Instance.gameMenuView == null)
yield return null;
while(ViewManager.Instance.gameMenuView.Listener == null)
yield return null;
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_PAM_Call_InpToggle", new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_PAM_Call_InpSlider", new Action<string, string>(OnSliderUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingPAM", l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void Load()
{
ms_enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
ms_grabOffset = (int)ms_entries[(int)ModSetting.GrabOffset].BoxedValue * 0.01f;
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.Enabled:
{
ms_enabled = bool.Parse(p_value);
EnabledChange?.Invoke(ms_enabled);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
static void OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.GrabOffset:
{
ms_grabOffset = int.Parse(p_value) * 0.01f;
GrabOffsetChange?.Invoke(ms_grabOffset);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}
public static bool Enabled
{
get => ms_enabled;
}
public static float GrabOffset
{
get => ms_grabOffset;
}
}
}

15
ml_pam/Utils.cs Normal file
View file

@ -0,0 +1,15 @@
using UnityEngine;
namespace ml_pam
{
static class Utils
{
public static bool IsInVR() => ((ABI_RC.Core.Savior.CheckVR.Instance != null) && ABI_RC.Core.Savior.CheckVR.Instance.hasVrDeviceLoaded);
// Extensions
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{
return Matrix4x4.TRS(p_pos ? p_transform.position : Vector3.zero, p_rot ? p_transform.rotation : Quaternion.identity, p_scl ? p_transform.localScale : Vector3.one);
}
}
}

90
ml_pam/ml_pam.csproj Normal file
View file

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{3B5028DE-8C79-40DF-A1EF-BDB29D366125}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ml_pam</RootNamespace>
<AssemblyName>ml_pam</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony, Version=2.9.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
<Reference Include="cohtml.Net">
<Private>False</Private>
</Reference>
<Reference Include="Cohtml.Runtime">
<Private>False</Private>
</Reference>
<Reference Include="MelonLoader, Version=0.5.7.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine.AnimationModule">
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ArmMover.cs" />
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scripts.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Utils.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\menu.js" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "D:\Games\Steam\steamapps\common\ChilloutVR\Mods\"</PostBuildEvent>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ReferencePath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
</PropertyGroup>
</Project>

209
ml_pam/resources/menu.js Normal file
View file

@ -0,0 +1,209 @@
// Add settings
var g_modSettingsPAM = [];
engine.on('updateModSettingPAM', function (_name, _value) {
for (var i = 0; i < g_modSettingsPAM.length; i++) {
if (g_modSettingsPAM[i].name == _name) {
g_modSettingsPAM[i].updateValue(_value);
break;
}
}
});
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_toggle_mod_pam(_obj, _callbackName) {
this.obj = _obj;
this.callbackName = _callbackName;
this.value = _obj.getAttribute('data-current');
this.name = _obj.id;
this.type = _obj.getAttribute('data-type');
var self = this;
this.mouseDown = function (_e) {
self.value = self.value == "True" ? "False" : "True";
self.updateState();
}
this.updateState = function () {
self.obj.classList.remove("checked");
if (self.value == "True") {
self.obj.classList.add("checked");
}
engine.call(self.callbackName, self.name, self.value);
}
_obj.addEventListener('mousedown', this.mouseDown);
this.getValue = function () {
return self.value;
}
this.updateValue = function (value) {
self.value = value;
self.obj.classList.remove("checked");
if (self.value == "True") {
self.obj.classList.add("checked");
}
}
this.updateValue(this.value);
return {
name: this.name,
value: this.getValue,
updateValue: this.updateValue
}
}
function inp_slider_mod_pam(_obj, _callbackName) {
this.obj = _obj;
this.callbackName = _callbackName;
this.minValue = parseFloat(_obj.getAttribute('data-min'));
this.maxValue = parseFloat(_obj.getAttribute('data-max'));
this.percent = 0;
this.value = parseFloat(_obj.getAttribute('data-current'));
this.dragActive = false;
this.name = _obj.id;
this.type = _obj.getAttribute('data-type');
this.stepSize = _obj.getAttribute('data-stepSize') || 0;
this.format = _obj.getAttribute('data-format') || '{value}';
var self = this;
if (this.stepSize != 0)
this.value = Math.round(this.value / this.stepSize) * this.stepSize;
else
this.value = Math.round(this.value);
this.valueLabelBackground = document.createElement('div');
this.valueLabelBackground.className = 'valueLabel background';
this.valueLabelBackground.innerHTML = this.format.replace('{value}', this.value);
this.obj.appendChild(this.valueLabelBackground);
this.valueBar = document.createElement('div');
this.valueBar.className = 'valueBar';
this.valueBar.setAttribute('style', 'width: ' + (((this.value - this.minValue) / (this.maxValue - this.minValue)) * 100) + '%;');
this.obj.appendChild(this.valueBar);
this.valueLabelForeground = document.createElement('div');
this.valueLabelForeground.className = 'valueLabel foreground';
this.valueLabelForeground.innerHTML = this.format.replace('{value}', this.value);
this.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / ((this.value - this.minValue) / (this.maxValue - this.minValue)) * 100) + '%;');
this.valueBar.appendChild(this.valueLabelForeground);
this.mouseDown = function (_e) {
self.dragActive = true;
self.mouseMove(_e, false);
}
this.mouseMove = function (_e, _write) {
if (self.dragActive) {
var rect = _obj.getBoundingClientRect();
var start = rect.left;
var end = rect.right;
self.percent = Math.min(Math.max((_e.clientX - start) / rect.width, 0), 1);
var value = self.percent;
value *= (self.maxValue - self.minValue);
value += self.minValue;
if (self.stepSize != 0) {
value = Math.round(value / self.stepSize);
self.value = value * self.stepSize;
self.percent = (self.value - self.minValue) / (self.maxValue - self.minValue);
}
else
self.value = Math.round(value);
self.valueBar.setAttribute('style', 'width: ' + (self.percent * 100) + '%;');
self.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / self.percent * 100) + '%;');
self.valueLabelBackground.innerHTML = self.valueLabelForeground.innerHTML = self.format.replace('{value}', self.value);
engine.call(self.callbackName, self.name, "" + self.value);
self.displayImperial();
}
}
this.mouseUp = function (_e) {
self.mouseMove(_e, true);
self.dragActive = false;
}
_obj.addEventListener('mousedown', this.mouseDown);
document.addEventListener('mousemove', this.mouseMove);
document.addEventListener('mouseup', this.mouseUp);
this.getValue = function () {
return self.value;
}
this.updateValue = function (value) {
if (self.stepSize != 0)
self.value = Math.round(value * self.stepSize) / self.stepSize;
else
self.value = Math.round(value);
self.percent = (self.value - self.minValue) / (self.maxValue - self.minValue);
self.valueBar.setAttribute('style', 'width: ' + (self.percent * 100) + '%;');
self.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / self.percent * 100) + '%;');
self.valueLabelBackground.innerHTML = self.valueLabelForeground.innerHTML = self.format.replace('{value}', self.value);
self.displayImperial();
}
this.displayImperial = function () {
var displays = document.querySelectorAll('.imperialDisplay');
for (var i = 0; i < displays.length; i++) {
var binding = displays[i].getAttribute('data-binding');
if (binding == self.name) {
var realFeet = ((self.value * 0.393700) / 12);
var feet = Math.floor(realFeet);
var inches = Math.floor((realFeet - feet) * 12);
displays[i].innerHTML = feet + "&apos;" + inches + '&apos;&apos;';
}
}
}
return {
name: this.name,
value: this.getValue,
updateValue: this.updateValue
}
}
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Pickup Arm Mover</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enable hand movement: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Grab offset: </div>
<div class ="option-input">
<div id="GrabOffset" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="25"></div>
</div>
</div>
`;
document.getElementById('settings-interaction').appendChild(l_block);
// Update toggles in new menu block
let l_toggles = l_block.querySelectorAll('.inp_toggle');
for (var i = 0; i < l_toggles.length; i++) {
g_modSettingsPAM[g_modSettingsPAM.length] = new inp_toggle_mod_pam(l_toggles[i], 'MelonMod_PAM_Call_InpToggle');
}
// Update sliders in new menu block
let l_sliders = l_block.querySelectorAll('.inp_slider');
for (var i = 0; i < l_sliders.length; i++) {
g_modSettingsPAM[g_modSettingsPAM.length] = new inp_slider_mod_pam(l_sliders[i], 'MelonMod_PAM_Call_InpSlider');
}
}