Merge branch 'master' into experimental

This commit is contained in:
SDraw 2024-01-26 19:18:36 +03:00
commit c169c7336a
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
155 changed files with 16128 additions and 11723 deletions

View file

@ -1,20 +1,24 @@
Merged set of MelonLoader mods for ChilloutVR.
**Table for game build 2022r171:**
**Table for game build 2023r173:**
| Full name | Short name | Latest version | Available in [CVRMA](https://github.com/knah/CVRMelonAssistant) |
|:---------:|:----------:|:--------------:| :----------------------------------------------------------------|
| [Avatar Motion Tweaker](/ml_amt/README.md) | ml_amt | - | ✔ Yes<br>:warning:Broken |
| [Desktop Head Tracking](/ml_dht/README.md)| ml_dht | - | ✔ Yes<br>:warning:Broken |
| [Desktop Reticle Switch](/ml_drs/README.md)| ml_drs | 1.0.1 [:arrow_down:](../../releases/latest/download/ml_drs.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Extended Game Notifications](/ml_egn/README.md) | ml_egn | 1.0.3 [:arrow_down:](../../releases/latest/download/ml_egn.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Leap Motion Extension](/ml_lme/README.md)| ml_lme | 1.4.0 [:arrow_down:](../../releases/latest/download/ml_lme.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Pickup Arm Movement](/ml_pam/README.md)| ml_pam | 1.0.6 [:arrow_down:](../../releases/latest/download/ml_pam.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Player Movement Copycat](/ml_pmc/README.md)| ml_pmc | 1.0.1 [:arrow_down:](../../releases/latest/download/ml_pmc.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Player Ragdoll Mod](/ml_prm/README.md)| ml_prm | 1.0.6 [:arrow_down:](../../releases/latest/download/ml_prm.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Avatar Motion Tweaker](/ml_amt/README.md) | ml_amt | 1.3.6 [:arrow_down:](../../releases/latest/download/ml_amt.dll)| ✔ Yes<br>:hourglass: Update review |
| [Avatar Synced Look](/ml_asl/README.md) | ml_asl | 1.0.0 [:arrow_down:](../../releases/latest/download/ml_asl.dll)| ✔ Yes |
| [Desktop Head Tracking](/ml_dht/README.md) | ml_dht | 1.2.0 [:arrow_down:](../../releases/latest/download/ml_dht.dll) | ✔ Yes (`Retired` group)<br>:hourglass: Update review |
| [Leap Motion Extension](/ml_lme/README.md)| ml_lme | 1.4.5 [:arrow_down:](../../releases/latest/download/ml_lme.dll)| ✔ Yes |
| [Pickup Arm Movement](/ml_pam/README.md)| ml_pam | 1.0.9 [:arrow_down:](../../releases/latest/download/ml_pam.dll)| ✔ Yes |
| [Player Movement Copycat](/ml_pmc/README.md)| ml_pmc | 1.0.4 [:arrow_down:](../../releases/latest/download/ml_pmc.dll)| ✔ Yes |
| [Player Ragdoll Mod](/ml_prm/README.md) | ml_prm | 1.1.2 [:arrow_down:](../../releases/latest/download/ml_prm.dll)| ✔ Yes<br>:hourglass: Update review |
| [Players Instance Notifier](/ml_pin/README.md) | ml_pin | 1.0.1 [:arrow_down:](../../releases/latest/download/ml_ml_pin.dll)| ✔ Yes<br>:hourglass: Update review |
| [Vive Extended Input](/ml_vei/README.md) | ml_vei | 1.0.0 [:arrow_down:](../../releases/latest/download/ml_vei.dll)| ✔ Yes |
**Archived mods:**
| Full name | Short name | Notes |
|:---------:|:----------:|-------|
| Avatar Change Info | ml_aci | Superseded by `Extended Game Notifications`
| Four Point Tracking | ml_fpt | In-game feature since 2022r170 update
| Avatar Change Info | ml_aci | Superseded by `Extended Game Notifications` |
| Desktop Reticle Switch | ml_drs | Boring functionality |
| Extended Game Notifications | ml_egn | In-game feature sine 2023r172 update |
| Four Point Tracking | ml_fpt | In-game feature since 2022r170 update |
| Game Main Fixes | ml_gmf | In-game feature sine 2023r172 update |
| Server Connection Info | ml_sci | Superseded by `Extended Game Notifications`

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

58
archived/ml_aci/Main.cs Normal file
View file

@ -0,0 +1,58 @@
using ABI_RC.Core.EventSystem;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Networking;
using ABI_RC.Core.Util;
using DarkRift;
using System.Reflection;
namespace ml_aci
{
public class AvatarChangeInfo : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(AssetManagement).GetMethod(nameof(AssetManagement.LoadLocalAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarChangeInfo).GetMethod(nameof(OnLocalAvatarLoad), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnProp)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarChangeInfo).GetMethod(nameof(OnPropSpawned), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnLocalAvatarLoad()
{
try
{
if(ViewManager.Instance != null)
ViewManager.Instance.TriggerPushNotification("Avatar changed", 1f);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPropSpawned()
{
try
{
if(ViewManager.Instance != null)
{
if((NetworkManager.Instance != null) && (NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected))
ViewManager.Instance.TriggerPushNotification("Prop spawned", 1f);
else
ViewManager.Instance.TriggerAlert("Prop Error", "Not connected to live instance", -1, true);
}
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -1,6 +1,10 @@
using System.Reflection;
[assembly: MelonLoader.MelonInfo(typeof(ml_egn.ExtendedGameNotifications), "ExtendedGameNotifications", "1.0.3", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: AssemblyTitle("AvatarChangeInfo")]
[assembly: AssemblyVersion("1.0.3")]
[assembly: AssemblyFileVersion("1.0.3")]
[assembly: MelonLoader.MelonInfo(typeof(ml_aci.AvatarChangeInfo), "AvatarChangeInfo", "1.0.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

@ -0,0 +1,9 @@
# Avatar Change Info
This mod shows simple main menu popup upon local player avatar change and prop spawn.
![](.github/img_01.png)
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_aci.dll` in `Mods` folder of game

View file

@ -0,0 +1,77 @@
<?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_aci</RootNamespace>
<AssemblyName>ml_aci</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="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" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\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>C:\Games\Steam\common\ChilloutVR\MelonLoader\;C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_drs.DesktopReticleSwitch), "DesktopReticleSwitch", "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)]

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_egn.ExtendedGameNotifications), "ExtendedGameNotifications", "1.0.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)]

313
archived/ml_fpt/Main.cs Normal file
View file

@ -0,0 +1,313 @@
using ABI.CCK.Scripts;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace ml_fpt
{
public class FourPointTracking : MelonLoader.MelonMod
{
static FourPointTracking ms_instance = null;
bool m_ready = false;
IndexIK m_indexIK = null;
RootMotion.FinalIK.VRIK m_vrIK = null;
RuntimeAnimatorController m_runtimeAnimator = null;
List<CVRAdvancedSettingsFileProfileValue> m_aasParameters = null;
bool m_calibrationActive = false;
object m_calibrationTask = null;
int m_hipsTrackerIndex = -1;
Transform m_hips = null;
Dictionary<string, Tuple<Vector3, Quaternion>> m_avatarCalibrations = null;
public override void OnInitializeMelon()
{
if(ms_instance == null)
ms_instance = this;
m_avatarCalibrations = new Dictionary<string, Tuple<Vector3, Quaternion>>();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(FourPointTracking).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(FourPointTracking).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForMainMenuView());
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
System.Collections.IEnumerator WaitForMainMenuView()
{
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.RegisterForEvent("MelonMod_FPT_Action_Calibrate", new Action(this.StartCalibration));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
};
}
System.Collections.IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_indexIK = PlayerSetup.Instance.gameObject.GetComponent<IndexIK>();
m_ready = true;
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
m_ready = false;
m_aasParameters?.Clear();
m_aasParameters = null;
m_avatarCalibrations?.Clear();
m_avatarCalibrations = null;
m_hipsTrackerIndex = -1;
if(m_calibrationTask != null)
MelonLoader.MelonCoroutines.Stop(m_calibrationTask);
m_calibrationTask = null;
}
void StartCalibration()
{
if(m_ready && !m_calibrationActive && PlayerSetup.Instance._inVr && !PlayerSetup.Instance.avatarIsLoading && PlayerSetup.Instance._animator.isHuman && !BodySystem.isCalibrating && !BodySystem.isCalibratedAsFullBody)
{
m_hipsTrackerIndex = GetHipsTracker();
if(m_hipsTrackerIndex != -1)
{
m_avatarCalibrations.Remove(MetaPort.Instance.currentAvatarGuid);
m_runtimeAnimator = PlayerSetup.Instance._animator.runtimeAnimatorController;
m_aasParameters = PlayerSetup.Instance.animatorManager.GetAdditionalSettingsCurrent();
PlayerSetup.Instance._animator.runtimeAnimatorController = PlayerSetup.Instance.tPoseAnimatorController;
PlayerSetup.Instance.animatorManager.SetAnimator(PlayerSetup.Instance._animator, PlayerSetup.Instance.tPoseAnimatorController);
m_hips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
m_vrIK = PlayerSetup.Instance._animator.GetComponent<RootMotion.FinalIK.VRIK>();
if(m_vrIK != null)
m_vrIK.solver.OnPreUpdate += this.OverrideIKWeight;
IKSystem.Instance.leftHandModel.SetActive(true);
IKSystem.Instance.rightHandModel.SetActive(true);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(true);
CVR_InteractableManager.enableInteractions = false;
m_calibrationActive = true;
m_calibrationTask = MelonLoader.MelonCoroutines.Start(CalibrationTask());
ViewManager.Instance.ForceUiStatus(false);
ShowHudNotification("Calibration started");
}
else
ShowMenuAlert("No hips tracker detected. Check if tracker has waist role in SteamVR settings.");
}
else
ShowMenuAlert("Calibraton requirements aren't met: be in VR, be not in FBT or avatar calibration, humanoid avatar");
}
System.Collections.IEnumerator CalibrationTask()
{
while(m_calibrationActive)
{
if(m_vrIK != null)
m_vrIK.enabled = false;
m_indexIK.enabled = false;
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(true, m_hips);
if((CVRInputManager.Instance.interactLeftValue > 0.9f) && (CVRInputManager.Instance.interactRightValue > 0.9f))
{
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.position = m_hips.position;
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.rotation = m_hips.rotation;
m_avatarCalibrations.Add(
MetaPort.Instance.currentAvatarGuid,
new Tuple<Vector3, Quaternion>(
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.localPosition,
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.localRotation
)
);
if(m_vrIK != null)
{
m_vrIK.solver.spine.pelvisTarget = PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target;
m_vrIK.solver.spine.pelvisPositionWeight = 1f;
m_vrIK.solver.spine.pelvisRotationWeight = 1f;
m_vrIK.solver.OnPreUpdate -= this.OverrideIKWeight;
m_vrIK.solver.IKPositionWeight = 1f;
m_vrIK.enabled = true;
}
m_indexIK.enabled = true;
PlayerSetup.Instance._animator.runtimeAnimatorController = m_runtimeAnimator;
PlayerSetup.Instance.animatorManager.SetAnimator(PlayerSetup.Instance._animator, m_runtimeAnimator);
if(m_aasParameters != null)
{
foreach(var l_param in m_aasParameters)
{
PlayerSetup.Instance.animatorManager.SetAnimatorParameter(l_param.name, l_param.value);
}
}
IKSystem.Instance.leftHandModel.SetActive(false);
IKSystem.Instance.rightHandModel.SetActive(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(false);
CVR_InteractableManager.enableInteractions = true;
Reset();
ShowHudNotification("Calibration completed");
}
yield return null;
}
m_calibrationTask = null; // Idk if it's safe or not
}
void OverrideIKWeight()
{
if(m_calibrationActive)
{
m_vrIK.solver.IKPositionWeight = 0f;
}
}
void Reset()
{
m_vrIK = null;
m_runtimeAnimator = null;
m_aasParameters = null;
m_calibrationActive = false;
m_calibrationTask = null;
m_hipsTrackerIndex = -1;
m_hips = null;
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
try
{
if(m_calibrationActive)
{
if(m_calibrationTask != null)
MelonLoader.MelonCoroutines.Stop(m_calibrationTask);
m_indexIK.enabled = true;
IKSystem.Instance.leftHandModel.SetActive(false);
IKSystem.Instance.rightHandModel.SetActive(false);
if(m_hipsTrackerIndex != -1)
{
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(false);
}
CVR_InteractableManager.enableInteractions = true;
Reset();
ShowHudNotification("Calibration canceled");
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
void OnSetupAvatar()
{
try
{
if(m_ready && PlayerSetup.Instance._inVr && PlayerSetup.Instance._animator.isHuman && !VRTrackerManager.Instance.CheckFullBody())
{
int l_hipsTracker = GetHipsTracker();
if((l_hipsTracker != -1) && m_avatarCalibrations.TryGetValue(MetaPort.Instance.currentAvatarGuid, out var l_stored))
{
var l_vrIK = PlayerSetup.Instance._animator.GetComponent<RootMotion.FinalIK.VRIK>();
if(l_vrIK != null)
{
l_vrIK.solver.spine.pelvisTarget = PlayerSetup.Instance._trackerManager.trackers[l_hipsTracker].target;
l_vrIK.solver.spine.pelvisPositionWeight = 1f;
l_vrIK.solver.spine.pelvisRotationWeight = 1f;
l_vrIK.solver.spine.pelvisTarget.localPosition = l_stored.Item1;
l_vrIK.solver.spine.pelvisTarget.localRotation = l_stored.Item2;
ShowHudNotification("Applied saved calibration");
}
}
}
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void ShowHudNotification(string p_message)
{
if(CohtmlHud.Instance != null)
CohtmlHud.Instance.ViewDropText("4-Point Tracking", p_message);
}
static void ShowMenuAlert(string p_message)
{
if(ViewManager.Instance != null)
ViewManager.Instance.TriggerAlert("4-Point Tracking", p_message, 0, false);
}
static int GetHipsTracker()
{
int l_result = -1;
for(int i = 0; i < PlayerSetup.Instance._trackerManager.trackerNames.Length; i++)
{
if((PlayerSetup.Instance._trackerManager.trackerNames[i] == "vive_tracker_waist") && PlayerSetup.Instance._trackerManager.trackers[i].active)
{
l_result = i;
break;
}
}
return l_result;
}
}
}

View file

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

18
archived/ml_fpt/README.md Normal file
View file

@ -0,0 +1,18 @@
# Four Point Tracking
This mod adds ability to use 4-point tracking.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_fpt.dll` in `Mods` folder of game
# Usage
* Be sure that your tracker role is set to `Hips` in SteamVR
* Go to `Settings - Implementation - 4-Point Tracking` and press `Calibrate` button
* Adjust your tracker in a similar way as in FBT calibration
* Press trigger on both controllers
# Notes
* Will be deprecated soon
* Calibration is saved per avatar for game session.
* AAS parameters are restored after calibration.

View file

@ -2,7 +2,7 @@
using System.IO;
using System.Reflection;
namespace ml_amt
namespace ml_fpt
{
static class Scripts
{

View file

@ -0,0 +1,88 @@
<?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>{EC0A8C41-A429-42CD-B8FA-401A802D4BA6}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ml_fpt</RootNamespace>
<AssemblyName>ml_fpt</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</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">
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>C:\Games\Steam\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>
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>C:\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.AnimationModule">
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scripts.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\menu.js" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\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>C:\Games\Steam\common\ChilloutVR\MelonLoader\;C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,12 @@
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">4-Point Tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="action-btn button" onclick="engine.trigger('MelonMod_FPT_Action_Calibrate');"><img src="gfx/recalibrate.svg">Calibrate</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);
}

View file

@ -0,0 +1,34 @@
using ABI_RC.Core;
using System;
using System.Reflection;
namespace ml_gmf.Fixes
{
static class AnimationOverrides
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.SetOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimationOverrides).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.RestoreOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimationOverrides).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic))
);
}
static void OnOverride_Prefix(ref CVRAnimatorManager __instance)
{
try
{
if(__instance.animator != null)
__instance.animator.WriteDefaultValues();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}

View file

@ -0,0 +1,48 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using System;
using System.Reflection;
namespace ml_gmf.Fixes
{
static class AvatarOverrides
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(PlayerSetup).GetMethod("SetupAvatarGeneral", BindingFlags.NonPublic | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(AvatarOverrides).GetMethod(nameof(OnSetupAvatarGeneral_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
);
p_instance.Patch(
typeof(PuppetMaster).GetMethod(nameof(PuppetMaster.AvatarInstantiated), BindingFlags.Public | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(AvatarOverrides).GetMethod(nameof(OnPuppetAvatarInstantiated_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnSetupAvatarGeneral_Prefix(CVRAvatar ____avatarDescriptor)
{
try
{
if(____avatarDescriptor.overrides != null)
____avatarDescriptor.overrides = UnityEngine.Object.Instantiate(____avatarDescriptor.overrides);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPuppetAvatarInstantiated_Prefix(ref PuppetMaster __instance)
{
try
{
CVRAvatar l_avatar = __instance.avatarObject.GetComponent<CVRAvatar>();
if((l_avatar != null) && (l_avatar.overrides != null))
l_avatar.overrides = UnityEngine.Object.Instantiate(l_avatar.overrides);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -0,0 +1,31 @@
using ABI_RC.Core.Player;
using System.Collections;
namespace ml_gmf.Fixes
{
static class PostProccesVolumes
{
internal static void Init()
{
MelonLoader.MelonCoroutines.Start(FixVRCameraVolumeTarget());
}
static IEnumerator FixVRCameraVolumeTarget()
{
while(PlayerSetup.Instance == null)
yield return null;
while(PlayerSetup.Instance.vrCamera == null)
yield return null;
UnityEngine.Rendering.PostProcessing.PostProcessLayer l_layer = null;
while(l_layer == null)
{
l_layer = PlayerSetup.Instance.vrCamera.GetComponent<UnityEngine.Rendering.PostProcessing.PostProcessLayer>();
yield return null;
}
l_layer.volumeTrigger = PlayerSetup.Instance.vrCamera.transform;
}
}
}

View file

@ -0,0 +1,60 @@
using ABI_RC.Systems.InputManagement;
using ABI_RC.Systems.InputManagement.XR;
using System;
using System.Reflection;
namespace ml_gmf.Fixes
{
static class ViveControls
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(CVRXRModule).GetMethod("Update_Gestures_Vive", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(ViveControls).GetMethod(nameof(OnViveGesturesUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRXRModule).GetMethod(nameof(CVRXRModule.Reset), BindingFlags.Public | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(ViveControls).GetMethod(nameof(OnCVRXRModuleReset_Prefix), BindingFlags.NonPublic | BindingFlags.Static)),
new HarmonyLib.HarmonyMethod(typeof(ViveControls).GetMethod(nameof(OnCVRXRModuleReset_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnViveGesturesUpdate_Postfix(ref CVRXRModule __instance)
{
try
{
float l_mag = ((!__instance.HasEmoteOverride) ? __instance.Primary2DAxis : __instance.EmoteOverride).magnitude;
if(__instance.ViveDirectionPressed && (l_mag >= CVRInputManager.VrViveGestureDeadZone))
{
if(__instance.Grip > 0.5f)
{
__instance.GestureRaw = -1f;
__instance.Gesture = -1f;
}
else
{
__instance.GestureRaw = __instance.Trigger;
__instance.Gesture = __instance.Trigger;
}
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnCVRXRModuleReset_Prefix(ref CVRXRModule __instance, out bool __state)
{
__state = __instance.ViveDirectionPressed;
}
static void OnCVRXRModuleReset_Postfix(ref CVRXRModule __instance, bool __state)
{
if((__instance.Type == EXRControllerType.Vive) && CVRInputManager._moduleXR.ViveAdvancedControls)
__instance.ViveDirectionPressed = __state;
}
}
}

13
archived/ml_gmf/Main.cs Normal file
View file

@ -0,0 +1,13 @@
namespace ml_gmf
{
public class GameMainFixes : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
Fixes.ViveControls.Init(HarmonyInstance);
Fixes.AvatarOverrides.Init(HarmonyInstance);
Fixes.PostProccesVolumes.Init();
Fixes.AnimationOverrides.Init(HarmonyInstance);
}
}
}

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_gmf.GameMainFixes), "GameMainFixes", "1.0.0", "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)]

17
archived/ml_gmf/README.md Normal file
View file

@ -0,0 +1,17 @@
# Game Main Fixes
This mod fixes some issues that are present in game
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_gmf.dll` in `Mods` folder of game
# Implemented fixes
* Fix of broken `Vive Advanced Controls` game input option
* Additional feature: Disables gestures when moving with Vive controllers
* Fix of post-processing layer volume trigger for VR camera ([feedback post](https://feedback.abinteractive.net/p/2023r171ex1-post-process-volume-effects-are-applied-based-on-playspace-center-instead-of-camera-s-in-vr-mode))
* Fix of shared `AnimatorOverrideController` between same avatars that leads to broken avatar animator
* Fix of animation replacement (chairs, etc.) that leads to broken avatar animator ([feedback post](https://feedback.abinteractive.net/p/gestures-getting-stuck-locally-upon-entering-vehicles-chairs))
# Notes
All these fixes are implemented natively in 2023r172ex4 build.

View file

@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>GameMainFixes</PackageId>
<Version>1.0.0</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>GameMainFixes</Product>
</PropertyGroup>
<ItemGroup>
<None Remove="PlayerRagdollMod.json" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Unity.Postprocessing.Runtime">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Unity.Postprocessing.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

31
archived/ml_sci/Main.cs Normal file
View file

@ -0,0 +1,31 @@
using ABI_RC.Core.UI;
using DarkRift.Client;
using System.Reflection;
namespace ml_sci
{
public class ServerConnectionInfo : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(ABI_RC.Core.Networking.NetworkManager).GetMethod("OnGameNetworkConnectionClosed", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(ServerConnectionInfo).GetMethod(nameof(OnGameNetworkConnectionClosed), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnGameNetworkConnectionClosed(object __0, DisconnectedEventArgs __1)
{
try
{
if((CohtmlHud.Instance != null) && (__1 != null) && (!__1.LocalDisconnect))
CohtmlHud.Instance.ViewDropTextImmediate("(Local) Client", "Connection lost", (__1.Error != System.Net.Sockets.SocketError.Success) ? ("Reason: " + __1.Error.ToString()) : "");
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -1,6 +1,10 @@
using System.Reflection;
[assembly: MelonLoader.MelonInfo(typeof(ml_drs.DesktopReticleSwitch), "DesktopReticleSwitch", "1.0.1", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: AssemblyTitle("ServerConnectionInfo")]
[assembly: AssemblyVersion("1.0.2")]
[assembly: AssemblyFileVersion("1.0.2")]
[assembly: MelonLoader.MelonInfo(typeof(ml_sci.ServerConnectionInfo), "ServerConnectionInfo", "1.0.2", "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

@ -0,0 +1,9 @@
# Server Connection Info
This mod shows HUD notification upon server disconnection.
![](.github/img_01.png)
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_sci.dll` in `Mods` folder of game

View file

@ -0,0 +1,79 @@
<?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>{E5481D41-196C-4241-AF26-6595EF1863C1}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ml_sci</RootNamespace>
<AssemblyName>ml_sci</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.Client, Version=2.4.5.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\DarkRift.Client.dll</HintPath>
<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" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\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>C:\Games\Steam\common\ChilloutVR\MelonLoader\;C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
</PropertyGroup>
</Project>

283
js/mods_extension.js Normal file
View file

@ -0,0 +1,283 @@
if (typeof modsExtension === 'undefined') {
window.modsExtension = []
// UI elements, modified from original `inp` types, because I have no js knowledge to hook stuff
modsExtension.createToggle = function (_obj, _callbackName) {
let uiElement = {};
uiElement.obj = _obj;
uiElement.callbackName = _callbackName;
uiElement.value = _obj.getAttribute('data-current');
uiElement.name = _obj.id;
uiElement.type = _obj.getAttribute('data-type');
var self = uiElement;
uiElement.mouseDown = function (_e) {
self.value = self.value == "True" ? "False" : "True";
self.updateState();
}
uiElement.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', uiElement.mouseDown);
uiElement.getValue = function () {
return self.value;
}
uiElement.updateValue = function (value) {
self.value = value;
self.obj.classList.remove("checked");
if (self.value == "True") {
self.obj.classList.add("checked");
}
}
uiElement.updateValue(uiElement.value);
return {
name: uiElement.name,
value: uiElement.getValue,
updateValue: uiElement.updateValue
}
};
modsExtension.createSlider = function (_obj, _callbackName) {
let uiElement = {};
uiElement.obj = _obj;
uiElement.callbackName = _callbackName;
uiElement.minValue = parseFloat(_obj.getAttribute('data-min'));
uiElement.maxValue = parseFloat(_obj.getAttribute('data-max'));
uiElement.percent = 0;
uiElement.value = parseFloat(_obj.getAttribute('data-current'));
uiElement.dragActive = false;
uiElement.name = _obj.id;
uiElement.type = _obj.getAttribute('data-type');
uiElement.stepSize = _obj.getAttribute('data-stepSize') || 0;
uiElement.format = _obj.getAttribute('data-format') || '{value}';
var self = uiElement;
if (uiElement.stepSize != 0)
uiElement.value = Math.round(uiElement.value / uiElement.stepSize) * uiElement.stepSize;
else
uiElement.value = Math.round(uiElement.value);
uiElement.valueLabelBackground = document.createElement('div');
uiElement.valueLabelBackground.className = 'valueLabel background';
uiElement.valueLabelBackground.innerHTML = uiElement.format.replace('{value}', uiElement.value);
uiElement.obj.appendChild(uiElement.valueLabelBackground);
uiElement.valueBar = document.createElement('div');
uiElement.valueBar.className = 'valueBar';
uiElement.valueBar.setAttribute('style', 'width: ' + (((uiElement.value - uiElement.minValue) / (uiElement.maxValue - uiElement.minValue)) * 100) + '%;');
uiElement.obj.appendChild(uiElement.valueBar);
uiElement.valueLabelForeground = document.createElement('div');
uiElement.valueLabelForeground.className = 'valueLabel foreground';
uiElement.valueLabelForeground.innerHTML = uiElement.format.replace('{value}', uiElement.value);
uiElement.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / ((uiElement.value - uiElement.minValue) / (uiElement.maxValue - uiElement.minValue)) * 100) + '%;');
uiElement.valueBar.appendChild(uiElement.valueLabelForeground);
uiElement.mouseDown = function (_e) {
self.dragActive = true;
self.mouseMove(_e, false);
}
uiElement.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();
}
}
uiElement.mouseUp = function (_e) {
self.mouseMove(_e, true);
self.dragActive = false;
}
_obj.addEventListener('mousedown', uiElement.mouseDown);
document.addEventListener('mousemove', uiElement.mouseMove);
document.addEventListener('mouseup', uiElement.mouseUp);
uiElement.getValue = function () {
return self.value;
}
uiElement.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();
}
uiElement.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: uiElement.name,
value: uiElement.getValue,
updateValue: uiElement.updateValue
}
};
modsExtension.createDropdown = function (_obj, _callbackName) {
let uiElement = {};
uiElement.obj = _obj;
uiElement.callbackName = _callbackName;
uiElement.value = _obj.getAttribute('data-current');
uiElement.options = _obj.getAttribute('data-options').split(',');
uiElement.name = _obj.id;
uiElement.opened = false;
uiElement.keyValue = [];
uiElement.type = _obj.getAttribute('data-type');
uiElement.optionElements = [];
var self = uiElement;
uiElement.SelectValue = function (_e) {
self.value = _e.target.getAttribute('data-key');
self.valueElement.innerHTML = _e.target.getAttribute('data-value');
self.globalClose();
engine.call(self.callbackName, self.name, self.value);
}
uiElement.openClick = function (_e) {
if (self.obj.classList.contains('open')) {
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
} else {
self.obj.classList.add('open');
self.list.setAttribute('style', 'display: block;');
self.opened = true;
window.setTimeout(function () { self.opened = false; }, 10);
}
}
uiElement.globalClose = function (_e) {
if (self.opened) return;
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
}
uiElement.list = document.createElement('div');
uiElement.list.className = 'valueList';
uiElement.updateOptions = function () {
self.list.innerHTML = "";
for (var i = 0; i < self.options.length; i++) {
self.optionElements[i] = document.createElement('div');
self.optionElements[i].className = 'listValue';
var valuePair = Array.isArray(self.options[i]) ? self.options[i] : self.options[i].split(':');
var key = "";
var value = "";
if (valuePair.length == 1) {
key = valuePair[0];
value = valuePair[0];
} else {
key = valuePair[0];
value = valuePair[1];
}
self.keyValue[key] = value;
self.optionElements[i].innerHTML = value;
self.optionElements[i].setAttribute('data-value', value);
self.optionElements[i].setAttribute('data-key', key);
self.list.appendChild(self.optionElements[i]);
self.optionElements[i].addEventListener('mousedown', self.SelectValue);
}
self.valueElement.innerHTML = self.keyValue[self.value];
}
uiElement.valueElement = document.createElement('div');
uiElement.valueElement.className = 'dropdown-value';
uiElement.updateOptions();
uiElement.obj.appendChild(uiElement.valueElement);
uiElement.obj.appendChild(uiElement.list);
uiElement.valueElement.addEventListener('mousedown', uiElement.openClick);
document.addEventListener('mousedown', uiElement.globalClose);
uiElement.getValue = function () {
return self.value;
}
uiElement.updateValue = function (value) {
self.value = value;
self.valueElement.innerHTML = self.keyValue[value];
}
uiElement.setOptions = function (options) {
self.options = options;
}
return {
name: uiElement.name,
value: uiElement.getValue,
updateValue: uiElement.updateValue,
updateOptions: uiElement.updateOptions,
setOptions: uiElement.setOptions
}
};
modsExtension.settings = []
modsExtension.settings.data = []; // [category] -> [entry]
modsExtension.addSetting = function (_category, _entry, _obj) {
if (modsExtension.settings.data[_category] === undefined)
modsExtension.settings.data[_category] = []
modsExtension.settings.data[_category][_entry] = _obj
};
modsExtension.updateSetting = function (_category, _entry, _value) {
if ((modsExtension.settings.data[_category] !== undefined) && (modsExtension.settings.data[_category][_entry] !== undefined))
modsExtension.settings.data[_category][_entry].updateValue(_value);
};
engine.on('updateModSetting', modsExtension.updateSetting);
}

View file

@ -8,7 +8,6 @@ namespace ml_amt
{
public enum ParameterType
{
Upright,
GroundedRaw,
Moving
}
@ -32,7 +31,7 @@ namespace ml_amt
if(l_regex.IsMatch(l_param.name))
{
m_hash = l_param.nameHash;
m_sync = (l_param.name[0] != '#');
m_sync = !l_param.name.StartsWith('#');
m_innerType = l_param.type;
break;
}
@ -43,10 +42,6 @@ namespace ml_amt
{
switch(m_type)
{
case ParameterType.Upright:
SetFloat(p_tweaker.GetUpright());
break;
case ParameterType.GroundedRaw:
SetBoolean(p_tweaker.GetGroundedRaw());
break;

View file

@ -1,71 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace ml_amt.Fixes
{
class AnimatorAnalyzer
{
bool m_enabled = true;
List<AnimatorControllerParameter> m_parameters = null;
public void AnalyzeFrom(Animator p_animator)
{
m_enabled = p_animator.enabled;
m_parameters = p_animator.parameters?.ToList();
if(m_parameters != null)
{
foreach(var l_param in m_parameters)
{
switch(l_param.type)
{
case AnimatorControllerParameterType.Bool:
case AnimatorControllerParameterType.Trigger:
l_param.defaultBool = p_animator.GetBool(l_param.nameHash);
break;
case AnimatorControllerParameterType.Float:
l_param.defaultFloat = p_animator.GetFloat(l_param.nameHash);
break;
case AnimatorControllerParameterType.Int:
l_param.defaultInt = p_animator.GetInteger(l_param.nameHash);
break;
}
}
}
}
public void ApplyTo(Animator p_animator)
{
p_animator.enabled = m_enabled;
if(m_parameters != null)
{
foreach(var l_param in m_parameters)
{
switch(l_param.type)
{
case AnimatorControllerParameterType.Bool:
p_animator.SetBool(l_param.nameHash, l_param.defaultBool);
break;
case AnimatorControllerParameterType.Float:
p_animator.SetFloat(l_param.nameHash, l_param.defaultFloat);
break;
case AnimatorControllerParameterType.Int:
p_animator.SetInteger(l_param.nameHash, l_param.defaultInt);
break;
case AnimatorControllerParameterType.Trigger:
{
if(l_param.defaultBool)
p_animator.SetTrigger(l_param.nameHash);
}
break;
}
}
}
}
public bool IsEnabled() => m_enabled;
}
}

View file

@ -1,60 +0,0 @@
using ABI_RC.Core;
using System;
using System.Reflection;
namespace ml_amt.Fixes
{
static class AnimatorOverrideControllerFix
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
// AAS overriding fix
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.SetOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.RestoreOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
}
// AnimatorOverrideController runtime animation replacement fix
static void OnOverride_Prefix(ref CVRAnimatorManager __instance, out AnimatorAnalyzer __state)
{
__state = new AnimatorAnalyzer();
try
{
if(Settings.OverrideFix && (__instance.animator != null))
{
__state.AnalyzeFrom(__instance.animator);
if(__state.IsEnabled())
__instance.animator.enabled = false;
__instance.animator.WriteDefaultValues();
}
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnOverride_Postfix(ref CVRAnimatorManager __instance, AnimatorAnalyzer __state)
{
try
{
if(Settings.OverrideFix && (__instance.animator != null))
{
__state.ApplyTo(__instance.animator);
if(__state.IsEnabled())
__instance.animator.Update(0f);
}
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}

View file

@ -1,114 +0,0 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.MovementSystem;
using System;
using System.Collections;
using System.Reflection;
using UnityEngine;
namespace ml_amt.Fixes
{
static class MovementJumpFix
{
static FieldInfo ms_avatarHeight = typeof(PlayerSetup).GetField("_avatarHeight", BindingFlags.NonPublic | BindingFlags.Instance);
static float ms_playerHeight = 1f;
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(MovementJumpFix).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRWorld).GetMethod("SetupWorldRules", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(MovementJumpFix).GetMethod(nameof(OnWorldRulesSetup_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(PlayerSetup).GetMethod("SetupIKScaling", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(MovementJumpFix).GetMethod(nameof(OnSetupIKScaling_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
Settings.ScaledJumpChange += OnScaledJumpChange;
MelonLoader.MelonCoroutines.Start(WaitForGameSettings());
}
static IEnumerator WaitForGameSettings()
{
while(MetaPort.Instance == null)
yield return null;
while(MetaPort.Instance.settings == null)
yield return null;
ms_playerHeight = MetaPort.Instance.settings.GetSettingInt("GeneralPlayerHeight") * 0.01f;
MetaPort.Instance.settings.settingIntChanged.AddListener(OnGameSettingIntChange);
}
// Patches
static void OnSetupAvatar_Postfix()
{
try
{
SetScaledJump(Settings.ScaledJump);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnWorldRulesSetup_Postfix()
{
try
{
SetScaledJump(Settings.ScaledJump);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnSetupIKScaling_Postfix()
{
try
{
SetScaledJump(Settings.ScaledJump);
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
// Mod settings
static void OnScaledJumpChange(bool p_state)
{
SetScaledJump(p_state);
}
// Game settings
static void OnGameSettingIntChange(string p_name, int p_value)
{
if(p_name == "GeneralPlayerHeight")
{
ms_playerHeight = p_value * 0.01f;
}
}
// Arbitrary
static void SetScaledJump(bool p_state)
{
if(Utils.IsWorldSafe())
{
if(p_state)
MovementSystem.Instance.jumpHeight = Mathf.Clamp(Utils.GetWorldJumpHeight() * ((float)ms_avatarHeight.GetValue(PlayerSetup.Instance) / ms_playerHeight), float.MinValue, Utils.GetWorldMovementLimit());
else
MovementSystem.Instance.jumpHeight = Utils.GetWorldJumpHeight();
}
}
}
}

View file

@ -1,5 +1,4 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using System;
using System.Collections;
@ -35,12 +34,12 @@ namespace ml_amt
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("SetPlaySpaceScale", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnPlayspaceScale_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
// Fixes
Fixes.AnimatorOverrideControllerFix.Init(HarmonyInstance);
Fixes.MovementJumpFix.Init(HarmonyInstance);
ModSupporter.Init();
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
@ -50,12 +49,6 @@ namespace ml_amt
yield return null;
m_localTweaker = PlayerSetup.Instance.gameObject.AddComponent<MotionTweaker>();
m_localTweaker.SetCrouchLimit(Settings.CrouchLimit);
m_localTweaker.SetProneLimit(Settings.ProneLimit);
m_localTweaker.SetIKOverrideFly(Settings.IKOverrideFly);
m_localTweaker.SetIKOverrideJump(Settings.IKOverrideJump);
m_localTweaker.SetDetectEmotes(Settings.DetectEmotes);
m_localTweaker.SetFollowHips(Settings.FollowHips);
}
public override void OnDeinitializeMelon()
@ -63,6 +56,8 @@ namespace ml_amt
if(ms_instance == this)
ms_instance = null;
if(m_localTweaker != null)
UnityEngine.Object.Destroy(m_localTweaker);
m_localTweaker = null;
}
@ -107,5 +102,19 @@ namespace ml_amt
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnPlayspaceScale_Postfix() => ms_instance?.OnPlayspaceScale();
void OnPlayspaceScale()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnPlayspaceScale();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}

View file

@ -1,49 +0,0 @@
using System.Collections;
using System.Linq;
namespace ml_amt
{
static class ModSupporter
{
static bool ms_ragdollMod = false;
static bool ms_copycatMod = false;
public static void Init()
{
if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PlayerRagdollMod") != null)
MelonLoader.MelonCoroutines.Start(WaitForRagdollInstance());
if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PlayerMovementCopycat") != null)
MelonLoader.MelonCoroutines.Start(WaitForCopycatInstance());
}
// PlayerRagdollMod support
static IEnumerator WaitForRagdollInstance()
{
while(ml_prm.RagdollController.Instance == null)
yield return null;
ms_ragdollMod = true;
}
static bool IsRagdolled() => ml_prm.RagdollController.Instance.IsRagdolled();
// PlayerMovementCopycat support
static IEnumerator WaitForCopycatInstance()
{
while(ml_pmc.PoseCopycat.Instance == null)
yield return null;
ms_copycatMod = true;
}
static bool IsCopycating() => ml_pmc.PoseCopycat.Instance.IsActive();
public static bool SkipHipsOverride()
{
bool l_result = false;
l_result |= (ms_ragdollMod && IsRagdolled());
l_result |= (ms_copycatMod && IsCopycating());
return l_result;
}
}
}

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
@ -29,7 +28,6 @@ namespace ml_amt
int m_locomotionLayer = 0;
float m_avatarScale = 1f;
Vector3 m_locomotionOffset = Vector3.zero; // Original locomotion offset
Transform m_avatarHips = null;
bool m_inVR = false;
bool m_avatarReady = false;
@ -44,11 +42,10 @@ namespace ml_amt
bool m_detectEmotes = true;
bool m_emoteActive = false;
bool m_followHips = true;
Vector3 m_hipsToPlayer = Vector3.zero;
Vector3 m_massCenter = Vector3.zero;
Transform m_ikLimits = null;
readonly List<AvatarParameter> m_parameters = null;
internal MotionTweaker()
@ -61,26 +58,31 @@ namespace ml_amt
{
m_inVR = Utils.IsInVR();
SetCrouchLimit(Settings.CrouchLimit);
SetProneLimit(Settings.ProneLimit);
SetIKOverrideFly(Settings.IKOverrideFly);
SetIKOverrideJump(Settings.IKOverrideJump);
SetDetectEmotes(Settings.DetectEmotes);
Settings.CrouchLimitChange += this.SetCrouchLimit;
Settings.ProneLimitChange += this.SetProneLimit;
Settings.IKOverrideFlyChange += this.SetIKOverrideFly;
Settings.IKOverrideJumpChange += this.SetIKOverrideJump;
Settings.DetectEmotesChange += this.SetDetectEmotes;
Settings.FollowHipsChange += this.SetFollowHips;
Settings.MassCenterChange += this.OnMassCenterChange;
SetCrouchLimit(Settings.CrouchLimit);
SetProneLimit(Settings.ProneLimit);
}
void OnDestroy()
{
m_vrIk = null;
m_ikLimits = null;
m_parameters.Clear();
Settings.CrouchLimitChange -= this.SetCrouchLimit;
Settings.ProneLimitChange -= this.SetProneLimit;
Settings.IKOverrideFlyChange -= this.SetIKOverrideFly;
Settings.IKOverrideJumpChange -= this.SetIKOverrideJump;
Settings.DetectEmotesChange -= this.SetDetectEmotes;
Settings.FollowHipsChange -= this.SetFollowHips;
Settings.MassCenterChange -= this.OnMassCenterChange;
}
@ -92,11 +94,7 @@ namespace ml_amt
m_groundedRaw = MovementSystem.Instance.IsGroundedRaw();
m_moving = !Mathf.Approximately(MovementSystem.Instance.movementVector.magnitude, 0f);
if(m_avatarHips != null)
{
Vector4 l_hipsToPoint = (PlayerSetup.Instance.transform.GetMatrix().inverse * m_avatarHips.GetMatrix()) * ms_pointVector;
m_hipsToPlayer.Set(l_hipsToPoint.x, 0f, l_hipsToPoint.z);
}
UpdateIKLimits();
m_emoteActive = false;
if(m_detectEmotes && (m_locomotionLayer >= 0))
@ -126,10 +124,12 @@ namespace ml_amt
m_emoteActive = false;
m_moving = false;
m_locomotionOverride = false;
m_hipsToPlayer = Vector3.zero;
m_avatarHips = null;
m_massCenter = Vector3.zero;
m_ikLimits = null;
m_parameters.Clear();
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(Settings.CrouchLimit);
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(Settings.ProneLimit);
}
internal void OnSetupAvatar()
@ -137,15 +137,17 @@ namespace ml_amt
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);
m_avatarScale = Mathf.Abs(PlayerSetup.Instance._avatar.transform.localScale.y);
// Parse animator parameters
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Upright, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.GroundedRaw, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Moving, PlayerSetup.Instance.animatorManager));
m_parameters.RemoveAll(p => !p.IsValid());
// Avatar custom IK limits
m_ikLimits = PlayerSetup.Instance._avatar.transform.Find("[IKLimits]");
UpdateIKLimits();
// Apply VRIK tweaks
if(m_vrIk != null)
{
@ -196,9 +198,17 @@ namespace ml_amt
BodySystem.TrackingLeftLegEnabled = false;
BodySystem.TrackingRightLegEnabled = false;
BodySystem.TrackingLocomotionEnabled = true;
IKSystem.Instance.applyOriginalHipRotation = true;
}
}
internal void OnPlayspaceScale()
{
if((m_vrIk != null) && Settings.MassCenter)
m_vrIk.solver.locomotion.offset = m_massCenter * GetRelativeScale();
}
// IK events
void OnIKPreUpdate()
{
@ -238,14 +248,6 @@ namespace ml_amt
}
}
bool l_solverActive = !Mathf.Approximately(m_vrIk.solver.IKPositionWeight, 0f);
if(l_locomotionOverride && l_solverActive && m_followHips && (!m_moving || (PlayerSetup.Instance.avatarUpright <= PlayerSetup.Instance.avatarProneLimit)) && m_inVR && !BodySystem.isCalibratedAsFullBody && !ModSupporter.SkipHipsOverride())
{
m_vrIk.solver.plantFeet = false;
IKSystem.VrikRootController.enabled = false;
PlayerSetup.Instance._avatar.transform.localPosition = m_hipsToPlayer;
}
if(m_locomotionOverride && !l_locomotionOverride)
m_vrIk.solver.Reset();
m_locomotionOverride = l_locomotionOverride;
@ -263,11 +265,13 @@ namespace ml_amt
// Settings
internal void SetCrouchLimit(float p_value)
{
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Max(Mathf.Clamp01(p_value), PlayerSetup.Instance.avatarProneLimit);
if(m_ikLimits == null)
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(p_value);
}
internal void SetProneLimit(float p_value)
{
PlayerSetup.Instance.avatarProneLimit = Mathf.Min(Mathf.Clamp01(p_value), PlayerSetup.Instance.avatarCrouchLimit);
if(m_ikLimits == null)
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(p_value);
}
internal void SetIKOverrideFly(bool p_state)
{
@ -281,10 +285,6 @@ namespace ml_amt
{
m_detectEmotes = p_state;
}
internal void SetFollowHips(bool p_state)
{
m_followHips = p_state;
}
void OnMassCenterChange(bool p_state)
{
if(m_vrIk != null)
@ -297,6 +297,16 @@ namespace ml_amt
return ((m_avatarScale > 0f) ? (PlayerSetup.Instance._avatar.transform.localScale.y / m_avatarScale) : 0f);
}
void UpdateIKLimits()
{
if(m_ikLimits != null)
{
Vector3 l_values = m_ikLimits.localPosition;
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(l_values.x);
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(l_values.y);
}
}
// Parameters access
public float GetUpright() => PlayerSetup.Instance.avatarUpright;
public bool GetGroundedRaw() => m_groundedRaw;

View file

@ -1,7 +1,4 @@
using System.Reflection;
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.2.9", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.3.6", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonOptionalDependencies("ml_prm", "ml_pmc")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -14,18 +14,10 @@ Available mod's settings in `Settings - IK - Avatar Motion Tweaker`:
* **Prone limit:** defines prone limit; default value - `40`.
* **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:** 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 and hips) in mind.
* **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).
#### Fixes/overhauls options
* **Scaled locomotion jump:** scales locomotion jump according to relation between your player settings height and current avatar height (includes avatar scale); default value - `false`.
* Note: Disabled in worlds that don't allow flight.
* **Fix animation overrides (chairs, etc.):** fixes animations overriding for avatars with AAS; default value - `true`.
* Note: This option is made to address [broken animator in chairs and combat worlds issue](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].
@ -38,6 +30,4 @@ Available additional parameters for AAS animator:
Additional mod's behaviour:
* Overrides and fixes IK behaviour in 4PT mode (head, hands and hips).
# NOTE
This is testing update for game build r171, not ready for massive usage yet!
* Avatars can have controlled IK crouch and prone limits. For that create `[IKLimits]` GameObject parented to avatar's root. Its local X and Y positions will be used as crouch and prone limits respectively and can be changed via animations. Values should be in range of [0;1].

View file

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Reflection;
namespace ml_amt
{
static class ResourcesHandler
{
public static string GetEmbeddedResource(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;
}
}
}

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.InteractionSystem;
using cohtml;
using System;
using System.Collections.Generic;
@ -14,10 +13,7 @@ namespace ml_amt
IKOverrideFly,
IKOverrideJump,
DetectEmotes,
FollowHips,
ScaledJump,
MassCenter,
OverrideFix
MassCenter
};
public static float CrouchLimit { get; private set; } = 0.75f;
@ -25,10 +21,7 @@ namespace ml_amt
public static bool IKOverrideFly { get; private set; } = true;
public static bool IKOverrideJump { get; private set; } = true;
public static bool DetectEmotes { get; private set; } = true;
public static bool FollowHips { get; private set; } = true;
public static bool MassCenter { get; private set; } = true;
public static bool ScaledJump { get; private set; } = false;
public static bool OverrideFix { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
@ -38,10 +31,7 @@ namespace ml_amt
static public event Action<bool> IKOverrideFlyChange;
static public event Action<bool> IKOverrideJumpChange;
static public event Action<bool> DetectEmotesChange;
static public event Action<bool> FollowHipsChange;
static public event Action<bool> MassCenterChange;
static public event Action<bool> ScaledJumpChange;
static public event Action<bool> OverrideFixChange;
internal static void Init()
{
@ -54,10 +44,7 @@ namespace ml_amt
ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), IKOverrideFly),
ms_category.CreateEntry(ModSetting.IKOverrideJump.ToString(), IKOverrideJump),
ms_category.CreateEntry(ModSetting.DetectEmotes.ToString(), DetectEmotes),
ms_category.CreateEntry(ModSetting.FollowHips.ToString(), FollowHips),
ms_category.CreateEntry(ModSetting.MassCenter.ToString(), MassCenter),
ms_category.CreateEntry(ModSetting.ScaledJump.ToString(), ScaledJump),
ms_category.CreateEntry(ModSetting.OverrideFix.ToString(), OverrideFix)
ms_category.CreateEntry(ModSetting.MassCenter.ToString(), MassCenter)
};
CrouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f;
@ -65,10 +52,7 @@ namespace ml_amt
IKOverrideFly = (bool)ms_entries[(int)ModSetting.IKOverrideFly].BoxedValue;
IKOverrideJump = (bool)ms_entries[(int)ModSetting.IKOverrideJump].BoxedValue;
DetectEmotes = (bool)ms_entries[(int)ModSetting.DetectEmotes].BoxedValue;
FollowHips = (bool)ms_entries[(int)ModSetting.FollowHips].BoxedValue;
MassCenter = (bool)ms_entries[(int)ModSetting.MassCenter].BoxedValue;
ScaledJump = (bool)ms_entries[(int)ModSetting.ScaledJump].BoxedValue;
OverrideFix = (bool)ms_entries[(int)ModSetting.OverrideFix].BoxedValue;
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
@ -84,14 +68,15 @@ namespace ml_amt
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_AMT_Call_InpSlider", new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_AMT_Call_InpToggle", new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingAMT", l_entry.DisplayName, l_entry.GetValueAsString());
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
@ -147,33 +132,12 @@ namespace ml_amt
}
break;
case ModSetting.FollowHips:
{
FollowHips = bool.Parse(p_value);
FollowHipsChange?.Invoke(FollowHips);
}
break;
case ModSetting.MassCenter:
{
MassCenter = bool.Parse(p_value);
MassCenterChange?.Invoke(MassCenter);
}
break;
case ModSetting.ScaledJump:
{
ScaledJump = bool.Parse(p_value);
ScaledJumpChange?.Invoke(ScaledJump);
}
break;
case ModSetting.OverrideFix:
{
OverrideFix = bool.Parse(p_value);
OverrideFixChange?.Invoke(OverrideFix);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);

View file

@ -12,28 +12,15 @@ namespace ml_amt
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_hasToes = typeof(IKSolverVR).GetField("hasToes", BindingFlags.NonPublic | BindingFlags.Instance);
static MethodInfo ms_getSineKeyframes = typeof(IKSolverVR).GetMethod("GetSineKeyframes", BindingFlags.NonPublic | BindingFlags.Static);
static FieldInfo ms_cohtmlView = typeof(CohtmlControlledViewDisposable).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsInVR() => ((ABI_RC.Core.Savior.CheckVR.Instance != null) && ABI_RC.Core.Savior.CheckVR.Instance.hasVrDeviceLoaded);
public static bool IsGrounded(this MovementSystem p_instance) => (bool)ms_grounded.GetValue(MovementSystem.Instance);
public static bool IsGroundedRaw(this MovementSystem p_instance) => (bool)ms_groundedRaw.GetValue(MovementSystem.Instance);
public static bool HasToes(this IKSolverVR p_instance) => (bool)ms_hasToes.GetValue(p_instance);
public static Keyframe[] GetSineKeyframes(float p_mag)
{
return (Keyframe[])ms_getSineKeyframes.Invoke(null, new object[] { p_mag });
}
public static bool IsWorldSafe() => ((CVRWorld.Instance != null) && CVRWorld.Instance.allowFlying);
public static float GetWorldJumpHeight()
{
float l_result = 1f;
if(CVRWorld.Instance != null)
l_result = CVRWorld.Instance.jumpHeight;
return l_result;
}
public static float GetWorldMovementLimit()
{
float l_result = 1f;
@ -47,7 +34,7 @@ namespace ml_amt
return l_result;
}
public static void ExecuteScript(this CohtmlControlledViewDisposable p_instance, string p_script) => ((cohtml.Net.View)ms_cohtmlView.GetValue(p_instance))?.ExecuteScript(p_script);
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
// Engine extensions
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
@ -6,7 +6,7 @@
<Company>None</Company>
<Product>AvatarMotionTweaker</Product>
<PackageId>AvatarMotionTweaker</PackageId>
<Version>1.2.9</Version>
<Version>1.3.6</Version>
<Platforms>x64</Platforms>
<AssemblyName>ml_amt</AssemblyName>
</PropertyGroup>
@ -16,6 +16,8 @@
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
@ -24,61 +26,67 @@
<ItemGroup>
<None Remove="AvatarMotionTweaker.json" />
<None Remove="resources\menu.js" />
<None Remove="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\menu.js" />
<EmbeddedResource Include="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ml_pmc">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_pmc.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ml_prm">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_prm.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
</ItemGroup>

View file

@ -1,261 +0,0 @@
// Add settings
var g_modSettingsAMT = [];
engine.on('updateModSettingAMT', function (_name, _value) {
for (var i = 0; i < g_modSettingsAMT.length; i++) {
if (g_modSettingsAMT[i].name == _name) {
g_modSettingsAMT[i].updateValue(_value);
break;
}
}
});
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_slider_mod_amt(_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
}
}
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_toggle_mod_amt(_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
}
}
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Avatar Motion Tweaker</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Crouch limit: </div>
<div class ="option-input">
<div id="CrouchLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="75"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Prone limit: </div>
<div class ="option-input">
<div id="ProneLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">IK override while flying: </div>
<div class ="option-input">
<div id="IKOverrideFly" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">IK override while jumping: </div>
<div class ="option-input">
<div id="IKOverrideJump" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Follow hips on IK override: </div>
<div class ="option-input">
<div id="FollowHips" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Detect animations emote tag: </div>
<div class ="option-input">
<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>
<h4><p style="color: #7F7F7F">Avatar independent game fixes/overhauls</p></h4><br>
<div class ="row-wrapper">
<div class ="option-caption">Scaled locomotion jump: </div>
<div class ="option-input">
<div id="ScaledJump" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Fix animator overrides (chairs, etc.): </div>
<div class ="option-input">
<div id="OverrideFix" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-ik').appendChild(l_block);
// Update sliders in new menu block
let l_sliders = l_block.querySelectorAll('.inp_slider');
for (var i = 0; i < l_sliders.length; i++) {
g_modSettingsAMT[g_modSettingsAMT.length] = new inp_slider_mod_amt(l_sliders[i], 'MelonMod_AMT_Call_InpSlider');
}
// Update toggles in new menu block
let l_toggles = l_block.querySelectorAll('.inp_toggle');
for (var i = 0; i < l_toggles.length; i++) {
g_modSettingsAMT[g_modSettingsAMT.length] = new inp_toggle_mod_amt(l_toggles[i], 'MelonMod_AMT_Call_InpToggle');
}
}

View file

@ -0,0 +1,54 @@
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Avatar Motion Tweaker</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Crouch limit: </div>
<div class ="option-input">
<div id="CrouchLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="75"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Prone limit: </div>
<div class ="option-input">
<div id="ProneLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">IK override while flying: </div>
<div class ="option-input">
<div id="IKOverrideFly" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Detect animations emote tag: </div>
<div class ="option-input">
<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>
`;
document.getElementById('settings-ik').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('AMT', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_AMT'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('AMT', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_AMT'));
}

25
ml_asl/Main.cs Normal file
View file

@ -0,0 +1,25 @@
using ABI_RC.Core.Player;
using System.Reflection;
namespace ml_asl
{
public class AvatarSyncedLook : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
Settings.Init();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("UpdatePlayerAvatarMovementData", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarSyncedLook).GetMethod(nameof(OnPlayerAvatarMovementDataUpdate_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnPlayerAvatarMovementDataUpdate_Postfix(ref PlayerSetup __instance, PlayerAvatarMovementData ____playerAvatarMovementData)
{
if(Settings.Enabled && (__instance.eyeMovement != null))
____playerAvatarMovementData.EyeTrackingOverride = true;
}
}
}

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_asl.AvatarSyncedLook), "AvatarSyncedLook", "1.0.0", "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)]

14
ml_asl/README.md Normal file
View file

@ -0,0 +1,14 @@
# Avatar Synced Look
This mod Forces local player's eyes look direction to be synced for remote players.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_asl.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - Interactions - Avatar Synced Look`:
* **Enabled:** sets eyes look direction to be synced or locally generated on remote users side; `true` by default.
# Notes
* Remote users with [EyeMovementFix](https://github.com/kafeijao/Kafe_CVR_Mods/tree/master/EyeMovementFix) installed can't see synced look direction.

View file

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Reflection;
namespace ml_asl
{
static class ResourcesHandler
{
public static string GetEmbeddedResource(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;
}
}
}

75
ml_asl/Settings.cs Normal file
View file

@ -0,0 +1,75 @@
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_asl
{
static class Settings
{
public enum ModSetting
{
Enabled = 0
}
public static bool Enabled { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<bool> EnabledChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("ASL", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled)
};
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
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("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.Enabled:
{
Enabled = bool.Parse(p_value);
EnabledChange?.Invoke(Enabled);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
}
}

12
ml_asl/Utils.cs Normal file
View file

@ -0,0 +1,12 @@
using ABI_RC.Core.UI;
using System.Reflection;
namespace ml_asl
{
static class Utils
{
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
}
}

63
ml_asl/ml_asl.csproj Normal file
View file

@ -0,0 +1,63 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>AvatarSyncedLook</PackageId>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>AvatarSyncedLook</Product>
</PropertyGroup>
<ItemGroup>
<None Remove="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
<EmbeddedResource Include="resources\mod_menu.js" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

View file

@ -0,0 +1,22 @@
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Avatar Synced Look</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enabled: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-interaction').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('ASL', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_ASL'));
}

View file

@ -14,13 +14,10 @@ namespace ml_dht
bool m_enabled = false;
bool m_headTracking = true;
bool m_blinking = true;
bool m_eyeTracking = true;
float m_smoothing = 0.5f;
bool m_mirrored = false;
bool m_faceOverride = true;
CVRAvatar m_avatarDescriptor = null;
Transform m_camera = null;
LookAtIK m_lookIK = null;
Transform m_headBone = null;
@ -28,8 +25,6 @@ namespace ml_dht
Quaternion m_headRotation;
Vector2 m_gazeDirection;
float m_blinkProgress = 0f;
Vector2 m_mouthShapes;
float m_eyebrowsProgress = 0f;
Quaternion m_bindRotation;
Quaternion m_lastHeadRotation;
@ -37,35 +32,29 @@ namespace ml_dht
// Unity events
void Start()
{
SetEnabled(Settings.Enabled);
SetHeadTracking(Settings.HeadTracking);
SetSmoothing(Settings.Smoothing);
Settings.EnabledChange += this.SetEnabled;
Settings.HeadTrackingChange += this.SetHeadTracking;
Settings.EyeTrackingChange += this.SetEyeTracking;
Settings.BlinkingChange += this.SetBlinking;
Settings.SmoothingChange += this.SetSmoothing;
Settings.MirroredChange += this.SetMirrored;
Settings.FaceOverrideChange += this.SetFaceOverride;
}
void OnDestroy()
{
Settings.EnabledChange -= this.SetEnabled;
Settings.HeadTrackingChange -= this.SetHeadTracking;
Settings.EyeTrackingChange -= this.SetEyeTracking;
Settings.BlinkingChange -= this.SetBlinking;
Settings.SmoothingChange -= this.SetSmoothing;
Settings.MirroredChange -= this.SetMirrored;
Settings.FaceOverrideChange -= this.SetFaceOverride;
}
// Tracking updates
public void UpdateTrackingData(ref TrackingData p_data)
{
m_headPosition.Set(p_data.m_headPositionX * (m_mirrored ? -1f : 1f), p_data.m_headPositionY, p_data.m_headPositionZ);
m_headRotation.Set(p_data.m_headRotationX, p_data.m_headRotationY * (m_mirrored ? -1f : 1f), p_data.m_headRotationZ * (m_mirrored ? -1f : 1f), p_data.m_headRotationW);
m_gazeDirection.Set(m_mirrored ? (1f - p_data.m_gazeX) : p_data.m_gazeX, p_data.m_gazeY);
m_headPosition.Set(p_data.m_headPositionX * (Settings.Mirrored ? -1f : 1f), p_data.m_headPositionY, p_data.m_headPositionZ);
m_headRotation.Set(p_data.m_headRotationX, p_data.m_headRotationY * (Settings.Mirrored ? -1f : 1f), p_data.m_headRotationZ * (Settings.Mirrored ? -1f : 1f), p_data.m_headRotationW);
m_gazeDirection.Set(Settings.Mirrored ? (1f - p_data.m_gazeX) : p_data.m_gazeX, p_data.m_gazeY);
m_blinkProgress = p_data.m_blink;
m_mouthShapes.Set(p_data.m_mouthOpen, p_data.m_mouthShape);
m_eyebrowsProgress = p_data.m_brows;
}
void OnLookIKPostUpdate()
@ -80,48 +69,9 @@ namespace ml_dht
}
// Game events
internal void OnEyeControllerUpdate(CVREyeController p_component)
{
if(m_enabled)
{
// Gaze
if(m_eyeTracking)
{
Transform l_camera = PlayerSetup.Instance.GetActiveCamera().transform;
p_component.manualViewTarget = true;
p_component.targetViewPosition = l_camera.position + l_camera.rotation * new Vector3((m_gazeDirection.x - 0.5f) * 2f, (m_gazeDirection.y - 0.5f) * 2f, 1f);
}
// Blink
if(m_blinking)
{
p_component.manualBlinking = true;
p_component.blinkProgress = m_blinkProgress;
}
}
}
internal void OnFaceTrackingUpdate(CVRFaceTracking p_component)
{
if(m_enabled && m_faceOverride)
{
if(m_avatarDescriptor != null)
m_avatarDescriptor.useVisemeLipsync = false;
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);
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Smile_Left] = ((m_mouthShapes.y < 0f) ? l_weight : 0f);
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Smile_Right] = ((m_mouthShapes.y < 0f) ? l_weight : 0f);
p_component.LipSyncWasUpdated = true;
p_component.UpdateLipShapes();
}
}
internal void OnSetupAvatar()
{
m_camera = PlayerSetup.Instance.GetActiveCamera().transform;
m_avatarDescriptor = PlayerSetup.Instance._avatar.GetComponent<CVRAvatar>();
m_headBone = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Head);
m_lookIK = PlayerSetup.Instance._avatar.GetComponent<LookAtIK>();
@ -142,44 +92,53 @@ namespace ml_dht
m_bindRotation = Quaternion.identity;
}
internal void OnEyeControllerUpdate(CVREyeController p_component)
{
if(m_enabled)
{
// Gaze
if(Settings.EyeTracking && (m_camera != null))
{
p_component.manualViewTarget = true;
p_component.targetViewPosition = m_camera.position + m_camera.rotation * new Vector3((m_gazeDirection.x - 0.5f) * 2f, (m_gazeDirection.y - 0.5f) * 2f, 1f);
}
// Blink
if(Settings.Blinking)
{
p_component.manualBlinking = true;
p_component.blinkProgress = m_blinkProgress;
}
}
}
// Settings
internal void SetEnabled(bool p_state)
void SetEnabled(bool p_state)
{
if(m_enabled != p_state)
{
m_enabled = p_state;
if(m_enabled && m_headTracking)
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
TryRestoreHeadRotation();
}
}
internal void SetHeadTracking(bool p_state)
void SetHeadTracking(bool p_state)
{
if(m_headTracking != p_state)
{
m_headTracking = p_state;
TryRestoreHeadRotation();
}
}
void SetSmoothing(float p_value)
{
m_smoothing = 1f - Mathf.Clamp(p_value, 0f, 0.99f);
}
// Arbitrary
void TryRestoreHeadRotation()
{
if(m_enabled && m_headTracking)
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
}
}
internal void SetEyeTracking(bool p_state)
{
m_eyeTracking = p_state;
}
internal void SetBlinking(bool p_state)
{
m_blinking = p_state;
}
internal void SetSmoothing(float p_value)
{
m_smoothing = 1f - Mathf.Clamp(p_value, 0f, 0.99f);
}
internal void SetMirrored(bool p_state)
{
m_mirrored = p_state;
}
internal void SetFaceOverride(bool p_state)
{
m_faceOverride = p_state;
}
}
}

View file

@ -1,5 +1,6 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.FaceTracking;
using System.Reflection;
namespace ml_dht
@ -8,10 +9,7 @@ namespace ml_dht
{
static DesktopHeadTracking ms_instance = null;
MemoryMapReader m_mapReader = null;
byte[] m_buffer = null;
TrackingData m_trackingData;
TrackingModule m_trackingModule = null;
HeadTracked m_localTracked = null;
public override void OnInitializeMelon()
@ -21,10 +19,7 @@ namespace ml_dht
Settings.Init();
m_mapReader = new MemoryMapReader();
m_buffer = new byte[1024];
m_mapReader.Open("head/data");
m_trackingModule = new TrackingModule();
// Patches
HarmonyInstance.Patch(
@ -37,33 +32,27 @@ namespace ml_dht
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForInstances());
}
System.Collections.IEnumerator WaitForInstances()
{
while(MetaPort.Instance == null)
yield return null;
while(PlayerSetup.Instance == null)
yield return null;
m_localTracked = PlayerSetup.Instance.gameObject.AddComponent<HeadTracked>();
FaceTrackingManager.Instance.RegisterModule(m_trackingModule);
// If you think it's a joke to put patch here, go on, try to put it in OnInitializeMelon, you melon :>
HarmonyInstance.Patch(
typeof(CVREyeController).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnEyeControllerUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(CVRFaceTracking).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnFaceTrackingUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForPlayer());
}
System.Collections.IEnumerator WaitForPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_localTracked = PlayerSetup.Instance.gameObject.AddComponent<HeadTracked>();
m_localTracked.SetEnabled(Settings.Enabled);
m_localTracked.SetHeadTracking(Settings.HeadTracking);
m_localTracked.SetEyeTracking(Settings.EyeTracking);
m_localTracked.SetBlinking(Settings.Blinking);
m_localTracked.SetMirrored(Settings.Mirrored);
m_localTracked.SetSmoothing(Settings.Smoothing);
m_localTracked.SetFaceOverride(Settings.FaceOverride);
}
public override void OnDeinitializeMelon()
@ -71,19 +60,17 @@ namespace ml_dht
if(ms_instance == this)
ms_instance = null;
m_mapReader?.Close();
m_mapReader = null;
m_buffer = null;
m_trackingModule = null;
m_localTracked = null;
}
public override void OnUpdate()
{
if(Settings.Enabled && m_mapReader.Read(ref m_buffer))
if(Settings.Enabled && (m_trackingModule != null))
{
m_trackingData = TrackingData.ToObject(m_buffer);
m_trackingModule.Update();
if(m_localTracked != null)
m_localTracked.UpdateTrackingData(ref m_trackingData);
m_localTracked.UpdateTrackingData(ref m_trackingModule.GetLatestTrackingData());
}
}
@ -128,19 +115,5 @@ namespace ml_dht
MelonLoader.MelonLogger.Error(e);
}
}
static void OnFaceTrackingUpdate_Postfix(ref CVRFaceTracking __instance) => ms_instance?.OnFaceTrackingUpdate(__instance);
void OnFaceTrackingUpdate(CVRFaceTracking p_component)
{
try
{
if(p_component.isLocal && (m_localTracked != null))
m_localTracked.OnFaceTrackingUpdate(p_component);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.1.2-ex", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.2.0", "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

@ -7,8 +7,8 @@ Refer to `TrackingData.cs` for reference in case of implementing own software.
# Features
* Head rotation
* Eyes gaze direction
* Basic mouth shapes: open, smile and pout
* Blinking
* Basic mouth shapes
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
@ -19,11 +19,13 @@ Refer to `TrackingData.cs` for reference in case of implementing own software.
Available mod's settings in `Settings - Implementation - Desktop Head Tracking`:
* **Enabled:** enables mod's activity; default value - `false`.
* **Use head tracking:** enables head tracking; default value - `true`.
* **Use eyes tracking:** uses eyes tracking from data; default value - `true`.
* **Use eyes tracking:** enables eyes tracking; default value - `true`.
* **Use face tracking:** enables mouth shapes tracking; default value - `true`.
* **Note:** You need to enable desktop tracking of `Vive Face tracking` in `Settings - Implementation` menu page.
* **Note:** Your avatar should have configured `CVR Face Tracking` component.
* **Use blinking:** uses blinking from data; default value - `true`.
* **Mirrored movement:** mirrors movement and gaze along 0YZ plane; default value - `false`.
* **Movement smoothing:** smoothing factor between new and old movement data; default value - `50`.
* **Override face tracking:** overrides and activates avatar's `VRC Face Tracking` components. List of used shapes: `Jaw_Open`, `Mouth_Pout`, `Mouth_Smile_Left`, `Mouth_Smile_Right`; default value - `true`.
# Known compatible tracking software
* [VSeeFace](https://www.vseeface.icu) with [Tracking Data Parser mod](https://github.com/SDraw/ml_mods_vsf)

View file

@ -4,9 +4,9 @@ using System.Reflection;
namespace ml_dht
{
static class Scripts
static class ResourcesHandler
{
public static string GetEmbeddedScript(string p_name)
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();

View file

@ -11,19 +11,19 @@ namespace ml_dht
Enabled = 0,
HeadTracking,
EyeTracking,
FaceTracking,
Blinking,
Mirrored,
Smoothing,
FaceOverride
}
public static bool Enabled { get; private set; } = false;
public static bool HeadTracking { get; private set; } = true;
public static bool EyeTracking { get; private set; } = true;
public static bool FaceTracking { get; private set; } = true;
public static bool Blinking { get; private set; } = true;
public static bool Mirrored { get; private set; } = false;
public static float Smoothing { get; private set; } = 0.5f;
public static bool FaceOverride { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
@ -31,10 +31,11 @@ namespace ml_dht
static public event Action<bool> EnabledChange;
static public event Action<bool> HeadTrackingChange;
static public event Action<bool> EyeTrackingChange;
static public event Action<bool> FaceTrackingChange;
static public event Action<bool> BlinkingChange;
static public event Action<bool> MirroredChange;
static public event Action<float> SmoothingChange;
static public event Action<bool> FaceOverrideChange;
internal static void Init()
{
@ -45,13 +46,19 @@ namespace ml_dht
ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled),
ms_category.CreateEntry(ModSetting.HeadTracking.ToString(), HeadTracking),
ms_category.CreateEntry(ModSetting.EyeTracking.ToString(), EyeTracking),
ms_category.CreateEntry(ModSetting.FaceTracking.ToString(), FaceTracking),
ms_category.CreateEntry(ModSetting.Blinking.ToString(), Blinking),
ms_category.CreateEntry(ModSetting.Mirrored.ToString(), Mirrored),
ms_category.CreateEntry(ModSetting.Smoothing.ToString(), (int)(Smoothing * 50f)),
ms_category.CreateEntry(ModSetting.FaceOverride.ToString(), FaceOverride)
};
Load();
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
HeadTracking = (bool)ms_entries[(int)ModSetting.HeadTracking].BoxedValue;
EyeTracking = (bool)ms_entries[(int)ModSetting.EyeTracking].BoxedValue;
FaceTracking = (bool)ms_entries[(int)ModSetting.FaceTracking].BoxedValue;
Blinking = (bool)ms_entries[(int)ModSetting.Blinking].BoxedValue;
Mirrored = (bool)ms_entries[(int)ModSetting.Mirrored].BoxedValue;
Smoothing = ((int)ms_entries[(int)ModSetting.Smoothing].BoxedValue) * 0.01f;
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
@ -67,28 +74,18 @@ namespace ml_dht
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_DHT_Call_InpSlider", new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_DHT_Call_InpToggle", new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingDHT", l_entry.DisplayName, l_entry.GetValueAsString());
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void Load()
{
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
HeadTracking = (bool)ms_entries[(int)ModSetting.HeadTracking].BoxedValue;
EyeTracking = (bool)ms_entries[(int)ModSetting.EyeTracking].BoxedValue;
Blinking = (bool)ms_entries[(int)ModSetting.Blinking].BoxedValue;
Mirrored = (bool)ms_entries[(int)ModSetting.Mirrored].BoxedValue;
Smoothing = ((int)ms_entries[(int)ModSetting.Smoothing].BoxedValue) * 0.01f;
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))
@ -134,6 +131,13 @@ namespace ml_dht
}
break;
case ModSetting.FaceTracking:
{
FaceTracking = bool.Parse(p_value);
FaceTrackingChange?.Invoke(FaceTracking);
}
break;
case ModSetting.Blinking:
{
Blinking = bool.Parse(p_value);
@ -147,13 +151,6 @@ namespace ml_dht
MirroredChange?.Invoke(Mirrored);
}
break;
case ModSetting.FaceOverride:
{
FaceOverride = bool.Parse(p_value);
FaceOverrideChange?.Invoke(FaceOverride);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);

70
ml_dht/TrackingModule.cs Normal file
View file

@ -0,0 +1,70 @@
using ABI_RC.Systems.FaceTracking;
using System;
using UnityEngine;
using ViveSR.anipal.Lip;
namespace ml_dht
{
class TrackingModule : ITrackingModule
{
bool m_registered = false;
bool m_activeAsModule = false;
MemoryMapReader m_mapReader = null;
byte[] m_buffer = null;
TrackingData m_trackingData;
LipData_v2 m_lipData;
public TrackingModule()
{
m_lipData = new LipData_v2();
m_lipData.frame = 0;
m_lipData.time = 0;
m_lipData.image = IntPtr.Zero;
m_lipData.prediction_data = new PredictionData_v2();
m_lipData.prediction_data.blend_shape_weight = new float[(int)LipShape_v2.Max];
m_buffer = new byte[1024];
m_mapReader = new MemoryMapReader();
m_mapReader.Open("head/data");
}
~TrackingModule()
{
m_mapReader.Close();
m_mapReader = null;
}
public (bool, bool) Initialize(bool useEye, bool useLip)
{
m_registered = true;
m_activeAsModule = true;
return (false, true);
}
public void Shutdown()
{
m_activeAsModule = false;
}
public bool IsEyeDataAvailable() => false;
public bool IsLipDataAvailable() => true;
internal void Update()
{
if(m_mapReader.Read(ref m_buffer))
{
m_trackingData = TrackingData.ToObject(m_buffer);
float l_weight = Mathf.Clamp01(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(m_trackingData.m_mouthShape)));
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Jaw_Open] = m_trackingData.m_mouthOpen;
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Pout] = ((m_trackingData.m_mouthShape > 0f) ? l_weight : 0f);
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Smile_Left] = ((m_trackingData.m_mouthShape < 0f) ? l_weight : 0f);
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Smile_Right] = ((m_trackingData.m_mouthShape < 0f) ? l_weight : 0f);
if(m_registered && m_activeAsModule && Settings.FaceTracking)
FaceTrackingManager.Instance.SubmitNewFacialData(m_lipData);
}
}
internal ref TrackingData GetLatestTrackingData() => ref m_trackingData;
}
}

View file

@ -6,13 +6,13 @@ namespace ml_dht
{
static class Utils
{
static FieldInfo ms_cohtmlView = typeof(CohtmlControlledViewDisposable).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
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);
}
public static void ExecuteScript(this CohtmlControlledViewDisposable p_viewDisposable, string p_script) => ((cohtml.Net.View)ms_cohtmlView.GetValue(p_viewDisposable))?.ExecuteScript(p_script);
}
}

View file

@ -6,7 +6,7 @@
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>DesktopHeadTracking</Product>
<Version>1.1.2</Version>
<Version>1.2.0</Version>
<Platforms>x64</Platforms>
</PropertyGroup>
@ -18,49 +18,62 @@
<ItemGroup>
<None Remove="DesktopHeadTracking.json" />
<None Remove="resources\menu.js" />
<None Remove="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\menu.js" />
<EmbeddedResource Include="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath>
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath>
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
</ItemGroup>

View file

@ -1,245 +0,0 @@
// Add settings
var g_modSettingsDHT = [];
engine.on('updateModSettingDHT', function (_name, _value) {
for (var i = 0; i < g_modSettingsDHT.length; i++) {
if (g_modSettingsDHT[i].name == _name) {
g_modSettingsDHT[i].updateValue(_value);
break;
}
}
});
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_slider_mod_dht(_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
}
}
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_toggle_mod_dht(_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
}
}
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Desktop Head Tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enabled: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use head tracking: </div>
<div class ="option-input">
<div id="HeadTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use eyes tracking: </div>
<div class ="option-input">
<div id="EyeTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use blinking: </div>
<div class ="option-input">
<div id="Blinking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Mirrored movement: </div>
<div class ="option-input">
<div id="Mirrored" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Movement smoothing: </div>
<div class ="option-input">
<div id="Smoothing" class ="inp_slider no-scroll" data-min="0" data-max="99" data-current="50"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Override face tracking: </div>
<div class ="option-input">
<div id="FaceOverride" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);
// Update sliders in new menu block
let l_sliders = l_block.querySelectorAll('.inp_slider');
for (var i = 0; i < l_sliders.length; i++) {
g_modSettingsDHT[g_modSettingsDHT.length] = new inp_slider_mod_dht(l_sliders[i], 'MelonMod_DHT_Call_InpSlider');
}
// Update toggles in new menu block
let l_toggles = l_block.querySelectorAll('.inp_toggle');
for (var i = 0; i < l_toggles.length; i++) {
g_modSettingsDHT[g_modSettingsDHT.length] = new inp_toggle_mod_dht(l_toggles[i], 'MelonMod_DHT_Call_InpToggle');
}
}

View file

@ -0,0 +1,70 @@
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Desktop Head Tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enabled: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use head tracking: </div>
<div class ="option-input">
<div id="HeadTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use eyes tracking: </div>
<div class ="option-input">
<div id="EyeTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use face tracking: </div>
<div class ="option-input">
<div id="FaceTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use blinking: </div>
<div class ="option-input">
<div id="Blinking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Mirrored movement: </div>
<div class ="option-input">
<div id="Mirrored" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Movement smoothing: </div>
<div class ="option-input">
<div id="Smoothing" class ="inp_slider no-scroll" data-min="0" data-max="99" data-current="50"></div>
</div>
</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('DHT', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_DHT'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('DHT', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_DHT'));
}

View file

@ -13,8 +13,8 @@ namespace ml_lme
"leapmotion_hands.asset"
};
static Dictionary<string, AssetBundle> ms_loadedAssets = new Dictionary<string, AssetBundle>();
static Dictionary<string, GameObject> ms_loadedObjects = new Dictionary<string, GameObject>();
static readonly Dictionary<string, AssetBundle> ms_loadedAssets = new Dictionary<string, AssetBundle>();
static readonly Dictionary<string, GameObject> ms_loadedObjects = new Dictionary<string, GameObject>();
public static void Load()
{

View file

@ -20,7 +20,7 @@ namespace ml_lme
foreach(string l_library in ms_libraries)
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + "." + l_library);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + l_library);
if(!File.Exists(l_library))
{

View file

@ -24,20 +24,11 @@ namespace ml_lme
bool m_gripLeft = false;
bool m_gripRight = false;
~LeapInput()
{
Settings.EnabledChange -= this.OnEnableChange;
Settings.InputChange -= this.OnInputChange;
MetaPort.Instance.settings.settingBoolChanged.RemoveListener(this.OnGameSettingBoolChange);
}
public override void ModuleAdded()
{
base.ModuleAdded();
InputEnabled = Settings.Enabled;
HapticFeedback = false;
base.InputEnabled = Settings.Enabled;
base.HapticFeedback = false;
m_inVR = Utils.IsInVR();
@ -47,13 +38,6 @@ namespace ml_lme
m_handRayLeft.isInteractionRay = true;
m_handRayLeft.triggerGazeEvents = false;
m_handRayLeft.holderRoot = m_handRayLeft.gameObject;
m_handRayRight = LeapTracking.Instance.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_handRayLeft.attachmentDistance = 0f;
m_lineLeft = m_handRayLeft.gameObject.AddComponent<LineRenderer>();
@ -67,6 +51,13 @@ namespace ml_lme
m_lineLeft.enabled = false;
m_lineLeft.receiveShadows = false;
m_handRayLeft.lineRenderer = m_lineLeft;
m_handRayRight = LeapTracking.Instance.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_handRayRight.attachmentDistance = 0f;
m_lineRight = m_handRayRight.gameObject.AddComponent<LineRenderer>();
@ -82,12 +73,14 @@ namespace ml_lme
m_handRayRight.lineRenderer = m_lineRight;
Settings.EnabledChange += this.OnEnableChange;
Settings.InputChange += this.OnInputChange;
Settings.InteractionChange += this.OnInteractionChange;
Settings.GesturesChange += this.OnGesturesChange;
Settings.FingersOnlyChange += this.OnFingersOnlyChange;
OnEnableChange(Settings.Enabled);
OnInputChange(Settings.Input);
OnInteractionChange(Settings.Interaction);
OnGesturesChange(Settings.Gestures);
OnFingersOnlyChange(Settings.FingersOnly);
MelonLoader.MelonCoroutines.Start(WaitForSettings());
MelonLoader.MelonCoroutines.Start(WaitForMaterial());
@ -108,69 +101,173 @@ namespace ml_lme
{
while(PlayerSetup.Instance == null)
yield return null;
while(PlayerSetup.Instance.leftRay == null)
while(PlayerSetup.Instance.vrRayLeft == null)
yield return null;
while(PlayerSetup.Instance.leftRay.lineRenderer == null)
while(PlayerSetup.Instance.vrRayLeft.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;
m_lineLeft.material = PlayerSetup.Instance.vrRayLeft.lineRenderer.material;
m_lineLeft.gameObject.layer = PlayerSetup.Instance.vrRayLeft.gameObject.layer;
m_lineRight.material = PlayerSetup.Instance.vrRayLeft.lineRenderer.material;
m_lineRight.gameObject.layer = PlayerSetup.Instance.vrRayLeft.gameObject.layer;
}
public override void ModuleDestroyed()
{
base.ModuleDestroyed();
if(m_handRayLeft != null)
Object.Destroy(m_handRayLeft);
m_handRayLeft = null;
if(m_handRayRight != null)
Object.Destroy(m_handRayRight);
m_handRayRight = null;
if(m_lineLeft != null)
Object.Destroy(m_lineLeft);
m_lineLeft = null;
if(m_lineRight != null)
Object.Destroy(m_lineRight);
m_lineRight = null;
Settings.EnabledChange -= this.OnEnableChange;
Settings.InteractionChange -= this.OnInteractionChange;
Settings.GesturesChange -= this.OnGesturesChange;
Settings.FingersOnlyChange -= this.OnFingersOnlyChange;
MetaPort.Instance.settings.settingBoolChanged.RemoveListener(this.OnGameSettingBoolChange);
}
public override void UpdateInput()
{
if(InputEnabled)
if(base.InputEnabled)
{
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData();
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if(l_data.m_leftHand.m_present)
{
SetFingersInput(l_data.m_leftHand, true);
m_handVisibleLeft = true;
SetFingersInput(l_data.m_leftHand, true);
if(Settings.Gestures)
{
base._inputManager.gestureLeftRaw = 0f;
// Finger Point & Finger Gun
if((base._inputManager.fingerCurlLeftIndex < 0.2f) && (base._inputManager.fingerCurlLeftMiddle > 0.75f) &&
(base._inputManager.fingerCurlLeftRing > 0.75f) && (base._inputManager.fingerCurlLeftPinky > 0.75f))
{
base._inputManager.gestureLeftRaw = (base._inputManager.fingerCurlLeftThumb >= 0.5f) ? 4f : 3f;
}
// Peace Sign
if((base._inputManager.fingerCurlLeftIndex < 0.2f) && (base._inputManager.fingerCurlLeftMiddle < 0.2f) &&
(base._inputManager.fingerCurlLeftRing > 0.75f) && (base._inputManager.fingerCurlLeftPinky > 0.75f))
{
base._inputManager.gestureLeftRaw = 5f;
}
// Rock and Roll
if((base._inputManager.fingerCurlLeftIndex < 0.2f) && (base._inputManager.fingerCurlLeftMiddle > 0.75f) &&
(base._inputManager.fingerCurlLeftRing > 0.75f) && (base._inputManager.fingerCurlLeftPinky < 0.5f))
{
base._inputManager.gestureLeftRaw = 6f;
}
// Fist & Thumbs Up
if((base._inputManager.fingerCurlLeftIndex > 0.5f) && (base._inputManager.fingerCurlLeftMiddle > 0.5f) &&
(base._inputManager.fingerCurlLeftRing > 0.5f) && (base._inputManager.fingerCurlLeftPinky > 0.5f))
{
base._inputManager.gestureLeftRaw = (base._inputManager.fingerCurlLeftThumb >= 0.5f) ? ((l_data.m_leftHand.m_grabStrength - 0.5f) * 2f) : 2f;
}
// Open Hand
if((base._inputManager.fingerCurlLeftIndex < 0.2f) && (base._inputManager.fingerCurlLeftMiddle < 0.2f) &&
(base._inputManager.fingerCurlLeftRing < 0.2f) && (base._inputManager.fingerCurlLeftPinky < 0.2f))
{
base._inputManager.gestureLeftRaw = -1f;
}
base._inputManager.gestureLeft = base._inputManager.gestureLeftRaw;
}
}
else
{
if(m_handVisibleLeft)
{
ResetFingers(true);
m_handVisibleLeft = false;
if(Settings.Gestures)
ResetGestures(true);
}
m_handVisibleLeft = false;
}
if(l_data.m_rightHand.m_present)
{
SetFingersInput(l_data.m_rightHand, false);
m_handVisibleRight = true;
SetFingersInput(l_data.m_rightHand, false);
if(Settings.Gestures)
{
base._inputManager.gestureRightRaw = 0f;
// Finger Point & Finger Gun
if((base._inputManager.fingerCurlRightIndex < 0.2f) && (base._inputManager.fingerCurlRightMiddle > 0.75f) &&
(base._inputManager.fingerCurlRightRing > 0.75f) && (base._inputManager.fingerCurlRightPinky > 0.75f))
{
base._inputManager.gestureRightRaw = (base._inputManager.fingerCurlRightThumb >= 0.5f) ? 4f : 3f;
}
// Peace Sign
if((base._inputManager.fingerCurlRightIndex < 0.2f) && (base._inputManager.fingerCurlRightMiddle < 0.2f) &&
(base._inputManager.fingerCurlRightRing > 0.75f) && (base._inputManager.fingerCurlRightPinky > 0.75f))
{
base._inputManager.gestureRightRaw = 5f;
}
// Rock and Roll
if((base._inputManager.fingerCurlRightIndex < 0.2f) && (base._inputManager.fingerCurlRightMiddle > 0.75f) &&
(base._inputManager.fingerCurlRightRing > 0.75f) && (base._inputManager.fingerCurlRightPinky < 0.5f))
{
base._inputManager.gestureRightRaw = 6f;
}
// Fist & Thumbs Up
if((base._inputManager.fingerCurlRightIndex > 0.5f) && (base._inputManager.fingerCurlRightMiddle > 0.5f) &&
(base._inputManager.fingerCurlRightRing > 0.5f) && (base._inputManager.fingerCurlRightPinky > 0.5f))
{
base._inputManager.gestureRightRaw = (base._inputManager.fingerCurlRightThumb >= 0.5f) ? ((l_data.m_rightHand.m_grabStrength - 0.5f) * 2f) : 2f;
}
// Open Hand
if((base._inputManager.fingerCurlRightIndex < 0.2f) && (base._inputManager.fingerCurlRightMiddle < 0.2f) &&
(base._inputManager.fingerCurlRightRing < 0.2f) && (base._inputManager.fingerCurlRightPinky < 0.2f))
{
base._inputManager.gestureRightRaw = -1f;
}
base._inputManager.gestureRight = base._inputManager.gestureRightRaw;
}
}
else
{
if(m_handVisibleRight)
{
ResetFingers(false);
m_handVisibleRight = false;
if(Settings.Gestures)
ResetGestures(false);
}
m_handVisibleRight = false;
}
if(!ModSupporter.SkipFingersOverride())
{
if(m_inVR)
{
_inputManager.individualFingerTracking = !CVRInputManager._moduleXR.GestureToggleValue;
_inputManager.individualFingerTracking |= (l_data.m_leftHand.m_present || l_data.m_rightHand.m_present);
}
else
_inputManager.individualFingerTracking = (l_data.m_leftHand.m_present || l_data.m_rightHand.m_present);
IKSystem.Instance.FingerSystem.controlActive = _inputManager.individualFingerTracking;
}
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));
if(!ModSupporter.SkipFingersOverride() && (!m_inVR || !Utils.AreKnucklesInUse()))
SetGameFingersTracking(m_handVisibleRight || m_handVisibleLeft);
base.UpdateInput();
}
@ -178,180 +275,105 @@ namespace ml_lme
public override void Update_Interaction()
{
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData();
if(Settings.Input)
if(Settings.Interaction)
{
if(l_data.m_leftHand.m_present && (!m_inVR || !Utils.IsLeftHandTracked() || !Settings.FingersOnly))
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if(m_handVisibleLeft && (!m_inVR || !Utils.IsLeftHandTracked()) && !Settings.FingersOnly)
{
float l_strength = l_data.m_leftHand.m_grabStrength;
float l_interactValue = 0f;
float l_interactValue;
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));
_inputManager.interactLeftValue = Mathf.Max(l_interactValue, _inputManager.interactLeftValue);
base._inputManager.interactLeftValue = Mathf.Max(l_interactValue, base._inputManager.interactLeftValue);
if(m_interactLeft != (l_strength > Settings.InteractThreadhold))
{
m_interactLeft = (l_strength > Settings.InteractThreadhold);
_inputManager.interactLeftDown |= m_interactLeft;
_inputManager.interactLeftUp |= !m_interactLeft;
base._inputManager.interactLeftDown |= m_interactLeft;
base._inputManager.interactLeftUp |= !m_interactLeft;
}
float l_gripValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.GripThreadhold, l_strength));
_inputManager.gripLeftValue = Mathf.Max(l_gripValue, _inputManager.gripLeftValue);
base._inputManager.gripLeftValue = Mathf.Max(l_gripValue, base._inputManager.gripLeftValue);
if(m_gripLeft != (l_strength > Settings.GripThreadhold))
{
m_gripLeft = (l_strength > Settings.GripThreadhold);
_inputManager.gripLeftDown |= m_gripLeft;
_inputManager.gripLeftUp |= !m_gripLeft;
base._inputManager.gripLeftDown |= m_gripLeft;
base._inputManager.gripLeftUp |= !m_gripLeft;
}
}
if(l_data.m_rightHand.m_present && (!m_inVR || !Utils.IsRightHandTracked() || !Settings.FingersOnly))
if(m_handVisibleRight && (!m_inVR || !Utils.IsRightHandTracked()) && !Settings.FingersOnly)
{
float l_strength = l_data.m_rightHand.m_grabStrength;
float l_interactValue = 0f;
float l_interactValue;
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));
_inputManager.interactRightValue = Mathf.Max(l_interactValue, _inputManager.interactRightValue);
base._inputManager.interactRightValue = Mathf.Max(l_interactValue, base._inputManager.interactRightValue);
if(m_interactRight != (l_strength > Settings.InteractThreadhold))
{
m_interactRight = (l_strength > Settings.InteractThreadhold);
_inputManager.interactRightDown |= m_interactRight;
_inputManager.interactRightUp |= !m_interactRight;
base._inputManager.interactRightDown |= m_interactRight;
base._inputManager.interactRightUp |= !m_interactRight;
}
float l_gripValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.GripThreadhold, l_strength));
_inputManager.gripRightValue = Mathf.Max(l_gripValue, _inputManager.gripRightValue);
base._inputManager.gripRightValue = Mathf.Max(l_gripValue, base._inputManager.gripRightValue);
if(m_gripRight != (l_strength > Settings.GripThreadhold))
{
m_gripRight = (l_strength > Settings.GripThreadhold);
_inputManager.gripRightDown |= m_gripRight;
_inputManager.gripRightUp |= !m_gripRight;
}
base._inputManager.gripRightDown |= m_gripRight;
base._inputManager.gripRightUp |= !m_gripRight;
}
}
if(Settings.Gestures)
{
// Left hand gestures
if(l_data.m_leftHand.m_present)
{
_inputManager.gestureLeftRaw = 0f;
// Finger Point & Finger Gun
if(_inputManager.fingerCurlLeftIndex < 0.2f && _inputManager.fingerCurlLeftMiddle > 0.75f &&
_inputManager.fingerCurlLeftRing > 0.75f && _inputManager.fingerCurlLeftPinky > 0.75f)
{
_inputManager.gestureLeftRaw = _inputManager.fingerCurlLeftThumb >= 0.5f ? 4f : 3f;
}
// Peace Sign
if(_inputManager.fingerCurlLeftIndex < 0.2f && _inputManager.fingerCurlLeftMiddle < 0.2f &&
_inputManager.fingerCurlLeftRing > 0.75f && _inputManager.fingerCurlLeftPinky > 0.75f)
{
_inputManager.gestureLeftRaw = 5f;
}
// Rock and Roll
if(_inputManager.fingerCurlLeftIndex < 0.2f && _inputManager.fingerCurlLeftMiddle > 0.75f &&
_inputManager.fingerCurlLeftRing > 0.75f && _inputManager.fingerCurlLeftPinky < 0.5f)
{
_inputManager.gestureLeftRaw = 6f;
}
// Fist & Thumbs Up
if(_inputManager.fingerCurlLeftIndex > 0.5f && _inputManager.fingerCurlLeftMiddle > 0.5f &&
_inputManager.fingerCurlLeftRing > 0.5f && _inputManager.fingerCurlLeftPinky > 0.5f)
{
_inputManager.gestureLeftRaw = _inputManager.fingerCurlLeftThumb >= 0.5f
? (l_data.m_rightHand.m_grabStrength - 0.5f) * 2f
: 2f;
}
// Open Hand
if(_inputManager.fingerCurlLeftIndex < 0.2f && _inputManager.fingerCurlLeftMiddle < 0.2f &&
_inputManager.fingerCurlLeftRing < 0.2f && _inputManager.fingerCurlLeftPinky < 0.2f)
{
_inputManager.gestureLeftRaw = -1f;
}
_inputManager.gestureLeft = _inputManager.gestureLeftRaw;
}
// Right hand gestures
if(l_data.m_rightHand.m_present)
{
_inputManager.gestureRightRaw = 0f;
// Finger Point & Finger Gun
if(_inputManager.fingerCurlRightIndex < 0.2f && _inputManager.fingerCurlRightMiddle > 0.75f &&
_inputManager.fingerCurlRightRing > 0.75f && _inputManager.fingerCurlRightPinky > 0.75f)
{
_inputManager.gestureRightRaw = _inputManager.fingerCurlRightThumb >= 0.5f ? 4f : 3f;
}
// Peace Sign
if(_inputManager.fingerCurlRightIndex < 0.2f && _inputManager.fingerCurlRightMiddle < 0.2f &&
_inputManager.fingerCurlRightRing > 0.75f && _inputManager.fingerCurlRightPinky > 0.75f)
{
_inputManager.gestureRightRaw = 5f;
}
// Rock and Roll
if(_inputManager.fingerCurlRightIndex < 0.2f && _inputManager.fingerCurlRightMiddle > 0.75f &&
_inputManager.fingerCurlRightRing > 0.75f && _inputManager.fingerCurlRightPinky < 0.5f)
{
_inputManager.gestureRightRaw = 6f;
}
// Fist & Thumbs Up
if(_inputManager.fingerCurlRightIndex > 0.5f && _inputManager.fingerCurlRightMiddle > 0.5f &&
_inputManager.fingerCurlRightRing > 0.5f && _inputManager.fingerCurlRightPinky > 0.5f)
{
_inputManager.gestureRightRaw = _inputManager.fingerCurlRightThumb >= 0.5f
? (l_data.m_rightHand.m_grabStrength - 0.5f) * 2f
: 2f;
}
// Open Hand
if(_inputManager.fingerCurlRightIndex < 0.2f && _inputManager.fingerCurlRightMiddle < 0.2f &&
_inputManager.fingerCurlRightRing < 0.2f && _inputManager.fingerCurlRightPinky < 0.2f)
{
_inputManager.gestureRightRaw = -1f;
}
_inputManager.gestureRight = _inputManager.gestureRightRaw;
}
ToggleHandRay(m_handVisibleLeft && (!m_inVR || !Utils.IsLeftHandTracked()) && !Settings.FingersOnly, true);
ToggleHandRay(m_handVisibleRight && (!m_inVR || !Utils.IsRightHandTracked()) && !Settings.FingersOnly, false);
}
}
// Settings changes
void OnEnableChange(bool p_state)
{
InputEnabled = p_state;
base.InputEnabled = p_state;
OnInputChange(p_state && Settings.Input);
UpdateFingerTracking();
m_handVisibleLeft &= p_state;
m_handVisibleRight &= p_state;
}
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)
{
ResetFingers(true);
ResetFingers(false);
if(Settings.Gestures)
{
ResetGestures(true);
ResetGestures(false);
}
// Reset to default, FreedomFingers can go brrr, player should press funny controller button two times
SetGameFingersTracking(m_inVR && Utils.AreKnucklesInUse() && !CVRInputManager._moduleXR.GestureToggleValue);
}
OnInteractionChange(Settings.Interaction);
}
void OnInteractionChange(bool p_state)
{
bool l_state = (p_state && Settings.Enabled && !Settings.FingersOnly);
ToggleHandRay(l_state, true);
ToggleHandRay(l_state, false);
if(!l_state)
{
m_handRayLeft.DropObject(true);
m_handRayLeft.ClearGrabbedObject();
@ -368,17 +390,21 @@ namespace ml_lme
void OnGesturesChange(bool p_state)
{
_inputManager.gestureLeft = 0f;
_inputManager.gestureLeftRaw = 0f;
_inputManager.gestureRight = 0f;
_inputManager.gestureRightRaw = 0f;
base._inputManager.gestureLeft = 0f;
base._inputManager.gestureLeftRaw = 0f;
base._inputManager.gestureRight = 0f;
base._inputManager.gestureRightRaw = 0f;
}
void OnFingersOnlyChange(bool p_state)
{
OnInteractionChange(Settings.Interaction);
}
// Game events
internal void OnAvatarSetup()
{
m_inVR = Utils.IsInVR();
UpdateFingerTracking();
}
internal void OnRayScale(float p_scale)
@ -388,45 +414,35 @@ namespace ml_lme
}
// Arbitrary
void UpdateFingerTracking()
{
_inputManager.individualFingerTracking = (Settings.Enabled || (m_inVR && Utils.AreKnucklesInUse() && !CVRInputManager._moduleXR.GestureToggleValue));
IKSystem.Instance.FingerSystem.controlActive = _inputManager.individualFingerTracking;
if(!Settings.Enabled)
{
ResetFingers(true);
ResetFingers(false);
}
}
void SetFingersInput(GestureMatcher.HandData p_hand, bool p_left)
void SetFingersInput(LeapParser.HandData p_hand, bool p_left)
{
// Game has spreads in range of [0;1], but mod now operates in range of [-1;1]
// So spreads will be normalized towards game's range
if(p_left)
{
_inputManager.fingerCurlLeftThumb = p_hand.m_bends[0];
_inputManager.fingerCurlLeftIndex = p_hand.m_bends[1];
_inputManager.fingerCurlLeftMiddle = p_hand.m_bends[2];
_inputManager.fingerCurlLeftRing = p_hand.m_bends[3];
_inputManager.fingerCurlLeftPinky = p_hand.m_bends[4];
_inputManager.fingerSpreadLeftThumb = p_hand.m_spreads[0];
_inputManager.fingerSpreadLeftIndex = p_hand.m_spreads[1];
_inputManager.fingerSpreadLeftMiddle = p_hand.m_spreads[2];
_inputManager.fingerSpreadLeftRing = p_hand.m_spreads[3];
_inputManager.fingerSpreadLeftPinky = p_hand.m_spreads[4];
base._inputManager.fingerCurlLeftThumb = p_hand.m_bends[0];
base._inputManager.fingerCurlLeftIndex = p_hand.m_bends[1];
base._inputManager.fingerCurlLeftMiddle = p_hand.m_bends[2];
base._inputManager.fingerCurlLeftRing = p_hand.m_bends[3];
base._inputManager.fingerCurlLeftPinky = p_hand.m_bends[4];
base._inputManager.fingerSpreadLeftThumb = 1f - (p_hand.m_spreads[0] * 0.5f + 0.5f);
base._inputManager.fingerSpreadLeftIndex = 1f - (p_hand.m_spreads[1] * 0.5f + 0.5f);
base._inputManager.fingerSpreadLeftMiddle = 1f - (p_hand.m_spreads[2] * 0.5f + 0.5f);
base._inputManager.fingerSpreadLeftRing = 1f - (p_hand.m_spreads[3] * 0.5f + 0.5f);
base._inputManager.fingerSpreadLeftPinky = 1f - (p_hand.m_spreads[4] * 0.5f + 0.5f);
}
else
{
_inputManager.fingerCurlRightThumb = p_hand.m_bends[0];
_inputManager.fingerCurlRightIndex = p_hand.m_bends[1];
_inputManager.fingerCurlRightMiddle = p_hand.m_bends[2];
_inputManager.fingerCurlRightRing = p_hand.m_bends[3];
_inputManager.fingerCurlRightPinky = p_hand.m_bends[4];
_inputManager.fingerSpreadRightThumb = p_hand.m_spreads[0];
_inputManager.fingerSpreadRightIndex = p_hand.m_spreads[1];
_inputManager.fingerSpreadRightMiddle = p_hand.m_spreads[2];
_inputManager.fingerSpreadRightRing = p_hand.m_spreads[3];
_inputManager.fingerSpreadRightPinky = p_hand.m_spreads[4];
base._inputManager.fingerCurlRightThumb = p_hand.m_bends[0];
base._inputManager.fingerCurlRightIndex = p_hand.m_bends[1];
base._inputManager.fingerCurlRightMiddle = p_hand.m_bends[2];
base._inputManager.fingerCurlRightRing = p_hand.m_bends[3];
base._inputManager.fingerCurlRightPinky = p_hand.m_bends[4];
base._inputManager.fingerSpreadRightThumb = 1f - (p_hand.m_spreads[0] * 0.5f + 0.5f);
base._inputManager.fingerSpreadRightIndex = 1f - (p_hand.m_spreads[1] * 0.5f + 0.5f);
base._inputManager.fingerSpreadRightMiddle = 1f - (p_hand.m_spreads[2] * 0.5f + 0.5f);
base._inputManager.fingerSpreadRightRing = 1f - (p_hand.m_spreads[3] * 0.5f + 0.5f);
base._inputManager.fingerSpreadRightPinky = 1f - (p_hand.m_spreads[4] * 0.5f + 0.5f);
}
}
@ -434,29 +450,29 @@ namespace ml_lme
{
if(p_left)
{
_inputManager.fingerCurlLeftThumb = 0f;
_inputManager.fingerCurlLeftIndex = 0f;
_inputManager.fingerCurlLeftMiddle = 0f;
_inputManager.fingerCurlLeftRing = 0f;
_inputManager.fingerCurlLeftPinky = 0f;
_inputManager.fingerSpreadLeftThumb = 0f;
_inputManager.fingerSpreadLeftIndex = 0f;
_inputManager.fingerSpreadLeftMiddle = 0f;
_inputManager.fingerSpreadLeftRing = 0f;
_inputManager.fingerSpreadLeftPinky = 0f;
base._inputManager.fingerCurlLeftThumb = 0f;
base._inputManager.fingerCurlLeftIndex = 0f;
base._inputManager.fingerCurlLeftMiddle = 0f;
base._inputManager.fingerCurlLeftRing = 0f;
base._inputManager.fingerCurlLeftPinky = 0f;
base._inputManager.fingerSpreadLeftThumb = 0.5f;
base._inputManager.fingerSpreadLeftIndex = 0.5f;
base._inputManager.fingerSpreadLeftMiddle = 0.5f;
base._inputManager.fingerSpreadLeftRing = 0.5f;
base._inputManager.fingerSpreadLeftPinky = 0.5f;
}
else
{
_inputManager.fingerCurlRightThumb = 0f;
_inputManager.fingerCurlRightIndex = 0f;
_inputManager.fingerCurlRightMiddle = 0f;
_inputManager.fingerCurlRightRing = 0f;
_inputManager.fingerCurlRightPinky = 0f;
_inputManager.fingerSpreadRightThumb = 0f;
_inputManager.fingerSpreadRightIndex = 0f;
_inputManager.fingerSpreadRightMiddle = 0f;
_inputManager.fingerSpreadRightRing = 0f;
_inputManager.fingerSpreadRightPinky = 0f;
base._inputManager.fingerCurlRightThumb = 0f;
base._inputManager.fingerCurlRightIndex = 0f;
base._inputManager.fingerCurlRightMiddle = 0f;
base._inputManager.fingerCurlRightRing = 0f;
base._inputManager.fingerCurlRightPinky = 0f;
base._inputManager.fingerSpreadRightThumb = 0.5f;
base._inputManager.fingerSpreadRightIndex = 0.5f;
base._inputManager.fingerSpreadRightMiddle = 0.5f;
base._inputManager.fingerSpreadRightRing = 0.5f;
base._inputManager.fingerSpreadRightPinky = 0.5f;
}
}
@ -464,13 +480,31 @@ namespace ml_lme
{
if(p_left)
{
_inputManager.gestureLeft = 0f;
_inputManager.gestureLeftRaw = 0f;
base._inputManager.gestureLeft = 0f;
base._inputManager.gestureLeftRaw = 0f;
}
else
{
_inputManager.gestureRight = 0f;
_inputManager.gestureRightRaw = 0f;
base._inputManager.gestureRight = 0f;
base._inputManager.gestureRightRaw = 0f;
}
}
void ToggleHandRay(bool p_state, bool p_left)
{
if(p_left)
{
m_handRayLeft.enabled = p_state;
((MonoBehaviour)m_handRayLeft).enabled = p_state;
m_lineLeft.enabled = p_state;
m_lineLeft.forceRenderingOff = !p_state;
}
else
{
m_handRayRight.enabled = p_state;
((MonoBehaviour)m_handRayRight).enabled = p_state;
m_lineRight.enabled = p_state;
m_lineRight.forceRenderingOff = !p_state;
}
}
@ -480,5 +514,11 @@ namespace ml_lme
if(p_name == "ControlUseGripToGrab")
m_gripToGrab = p_state;
}
void SetGameFingersTracking(bool p_state)
{
base._inputManager.individualFingerTracking = p_state;
IKSystem.Instance.FingerSystem.controlActive = base._inputManager.individualFingerTracking;
}
}
}

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.InputManagement;
using System.Collections;
using UnityEngine;
@ -12,7 +11,7 @@ namespace ml_lme
public static LeapManager Instance { get; private set; } = null;
Leap.Controller m_leapController = null;
GestureMatcher.LeapData m_leapData = null;
LeapParser.LeapData m_leapData = null;
LeapTracking m_leapTracking = null;
LeapTracked m_leapTracked = null;
@ -24,7 +23,7 @@ namespace ml_lme
Instance = this;
m_leapController = new Leap.Controller();
m_leapData = new GestureMatcher.LeapData();
m_leapData = new LeapParser.LeapData();
DontDestroyOnLoad(this);
@ -60,6 +59,23 @@ namespace ml_lme
m_leapController.Dispose();
m_leapController = null;
if(m_leapTracking != null)
Object.Destroy(m_leapTracking);
m_leapTracking = null;
if(m_leapTracked != null)
Object.Destroy(m_leapTracked);
m_leapTracked = null;
if(m_leapInput != null)
{
if(CVRInputManager.Instance != null)
CVRInputManager.Instance.DestroyInputModule(m_leapInput);
else
m_leapInput.ModuleDestroyed();
}
m_leapInput = null;
Settings.EnabledChange -= this.OnEnableChange;
Settings.TrackingModeChange -= this.OnTrackingModeChange;
}
@ -90,12 +106,12 @@ namespace ml_lme
if(m_leapController.IsConnected)
{
Leap.Frame l_frame = m_leapController.Frame();
GestureMatcher.GetFrameData(l_frame, m_leapData);
LeapParser.ParseFrame(l_frame, m_leapData);
}
}
}
public GestureMatcher.LeapData GetLatestData() => m_leapData;
public LeapParser.LeapData GetLatestData() => m_leapData;
// Device events
void OnLeapDeviceInitialized(object p_sender, Leap.DeviceEventArgs p_args)
@ -169,12 +185,6 @@ namespace ml_lme
m_leapTracked.OnAvatarSetup();
}
internal void OnCalibrate()
{
if(m_leapTracked != null)
m_leapTracked.OnCalibrate();
}
internal void OnRayScale(float p_scale)
{
m_leapInput?.OnRayScale(p_scale);

View file

@ -2,15 +2,24 @@
namespace ml_lme
{
static class GestureMatcher
static class LeapParser
{
readonly static Vector2[] ms_fingerLimits =
readonly static Vector2[] ms_bendLimits =
{
new Vector2(-50f, 0f),
new Vector2(-20f, 30f),
new Vector2(-15f, 15f),
new Vector2(-10f, 20f),
new Vector2(-10f, 25f)
new Vector2(0f, 90f),
new Vector2(0f, 180f),
new Vector2(0f, 180f),
new Vector2(0f, 180f),
new Vector2(0f, 180f)
};
readonly static Vector2[] ms_spreadLimits =
{
new Vector2(-25f, 25f), // Unity's default limits
new Vector2(-20f, 20f),
new Vector2(-7.5f, 7.5f),
new Vector2(-7.5f, 7.5f),
new Vector2(-20f, 20f)
};
public class HandData
@ -68,7 +77,7 @@ namespace ml_lme
}
}
public static void GetFrameData(Leap.Frame p_frame, LeapData p_data)
public static void ParseFrame(Leap.Frame p_frame, LeapData p_data)
{
p_data.Reset();
@ -93,30 +102,31 @@ namespace ml_lme
// Bends
foreach(Leap.Finger l_finger in p_hand.Fingers)
{
Quaternion l_prevSegment = Quaternion.identity;
Quaternion l_parentRot = Quaternion.identity;
float l_angle = 0f;
foreach(Leap.Bone l_bone in l_finger.bones)
{
p_data.m_fingerPosition[(int)l_finger.Type * 4 + (int)l_bone.Type] = l_bone.PrevJoint;
p_data.m_fingerRotation[(int)l_finger.Type * 4 + (int)l_bone.Type] = l_bone.Rotation;
int l_index = (int)l_finger.Type * 4 + (int)l_bone.Type;
p_data.m_fingerPosition[l_index] = l_bone.PrevJoint;
p_data.m_fingerRotation[l_index] = l_bone.Rotation;
if(l_bone.Type == Leap.Bone.BoneType.TYPE_METACARPAL)
{
l_prevSegment = l_bone.Rotation;
l_parentRot = l_bone.Rotation;
continue;
}
Quaternion l_diff = Quaternion.Inverse(l_prevSegment) * l_bone.Rotation;
l_prevSegment = l_bone.Rotation;
float l_angleDiff = l_diff.eulerAngles.x;
Quaternion l_localRot = Quaternion.Inverse(l_parentRot) * l_bone.Rotation;
float l_angleDiff = l_localRot.eulerAngles.x;
if(l_angleDiff > 180f)
l_angleDiff -= 360f;
l_angle += l_angleDiff;
l_parentRot = l_bone.Rotation;
}
p_data.m_bends[(int)l_finger.Type] = Utils.InverseLerpUnclamped(0f, (l_finger.Type == Leap.Finger.FingerType.TYPE_THUMB) ? 90f : 180f, l_angle);
p_data.m_bends[(int)l_finger.Type] = Utils.InverseLerpUnclamped(ms_bendLimits[(int)l_finger.Type].x, ms_bendLimits[(int)l_finger.Type].y, l_angle);
}
// Spreads
@ -126,24 +136,17 @@ namespace ml_lme
Leap.Bone l_child = l_finger.Bone(Leap.Bone.BoneType.TYPE_PROXIMAL);
Quaternion l_diff = Quaternion.Inverse(l_parent.Rotation) * l_child.Rotation;
// Spread - local Y rotation, but thumb is obnoxious
float l_angle = 360f - l_diff.eulerAngles.y;
// Spread - local Y rotation
float l_angle = l_diff.eulerAngles.y;
if(l_angle > 180f)
l_angle -= 360f;
// Pain
if(p_hand.IsRight)
l_angle *= -1f;
if(l_finger.Type != Leap.Finger.FingerType.TYPE_THUMB)
{
if(l_angle < 0f)
p_data.m_spreads[(int)l_finger.Type] = 0.5f * Utils.InverseLerpUnclamped(ms_fingerLimits[(int)l_finger.Type].x, 0f, l_angle);
else
p_data.m_spreads[(int)l_finger.Type] = 0.5f + 0.5f * Utils.InverseLerpUnclamped(0f, ms_fingerLimits[(int)l_finger.Type].y, l_angle);
}
else
p_data.m_spreads[(int)l_finger.Type] = Utils.InverseLerpUnclamped(ms_fingerLimits[(int)l_finger.Type].x, ms_fingerLimits[(int)l_finger.Type].y, l_angle);
p_data.m_spreads[(int)l_finger.Type] = Utils.InverseLerpUnclamped(ms_spreadLimits[(int)l_finger.Type].x, ms_spreadLimits[(int)l_finger.Type].y, l_angle) * 2f - 1f;
if((l_finger.Type != Leap.Finger.FingerType.TYPE_THUMB) && (p_data.m_bends[(int)l_finger.Type] >= 0.8f))
p_data.m_spreads[(int)l_finger.Type] = Mathf.Lerp(p_data.m_spreads[(int)l_finger.Type], 0f, (p_data.m_bends[(int)l_finger.Type] - 0.8f) * 5f);
}
p_data.m_grabStrength = Mathf.Clamp01((p_data.m_bends[1] + p_data.m_bends[2] + p_data.m_bends[3] + p_data.m_bends[4]) * 0.25f);

View file

@ -9,33 +9,37 @@ namespace ml_lme
[DisallowMultipleComponent]
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 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);
struct IKInfo
{
public Vector4 m_armsWeights;
public Vector2 m_elbowsWeights;
public Transform m_leftHandTarget;
public Transform m_rightHandTarget;
public Transform m_leftElbowTarget;
public Transform m_rightElbowTarget;
}
static readonly float[] ms_tposeMuscles = typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetField("TPoseMuscles", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as float[];
static readonly Quaternion ms_offsetLeft = Quaternion.Euler(0f, 90f, 0f);
static readonly Quaternion ms_offsetRight = Quaternion.Euler(0f, 270f, 0f);
VRIK m_vrIK = null;
Vector4 m_armsWeights = Vector2.zero;
bool m_inVR = false;
VRIK m_vrIK = null;
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;
IKInfo m_vrIKInfo;
ArmIK m_leftArmIK = null;
ArmIK m_rightArmIK = null;
HumanPoseHandler m_poseHandler = null;
HumanPose m_pose;
Transform m_leftHandTarget = null;
Transform m_rightHandTarget = null;
bool m_leftTargetActive = false;
bool m_rightTargetActive = false;
bool m_leftTargetActive = false; // VRIK only
bool m_rightTargetActive = false; // VRIK only
// Unity events
void Start()
@ -63,6 +67,27 @@ namespace ml_lme
void OnDestroy()
{
if(m_leftArmIK != null)
Destroy(m_leftArmIK);
m_leftArmIK = null;
if(m_rightArmIK != null)
Destroy(m_rightArmIK);
m_rightArmIK = null;
if(m_leftHandTarget != null)
Destroy(m_leftHandTarget);
m_leftHandTarget = null;
if(m_rightHandTarget != null)
Destroy(m_rightHandTarget);
m_rightHandTarget = null;
m_poseHandler?.Dispose();
m_poseHandler = null;
m_vrIK = null;
Settings.EnabledChange -= this.OnEnabledChange;
Settings.FingersOnlyChange -= this.OnFingersOnlyChange;
Settings.TrackElbowsChange -= this.OnTrackElbowsChange;
@ -72,7 +97,7 @@ namespace ml_lme
{
if(m_enabled)
{
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData();
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
@ -89,35 +114,8 @@ namespace ml_lme
if((m_vrIK != null) && !m_fingersOnly)
{
if(l_data.m_leftHand.m_present && !m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = m_leftHandTarget;
m_vrIK.solver.leftArm.bendGoal = LeapTracking.Instance.GetLeftElbow();
m_vrIK.solver.leftArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftTargetActive = true;
}
if(!l_data.m_leftHand.m_present && m_leftTargetActive)
{
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(l_data.m_rightHand.m_present && !m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_rightHandTarget;
m_vrIK.solver.rightArm.bendGoal = LeapTracking.Instance.GetRightElbow();
m_vrIK.solver.rightArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightTargetActive = true;
}
if(!l_data.m_rightHand.m_present && m_rightTargetActive)
{
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;
}
m_leftTargetActive = l_data.m_leftHand.m_present;
m_rightTargetActive = l_data.m_rightHand.m_present;
}
}
}
@ -126,7 +124,7 @@ namespace ml_lme
{
if(m_enabled && !m_inVR && (m_poseHandler != null))
{
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData();
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
Vector3 l_hipsLocalPos = m_hips.localPosition;
Quaternion l_hipsLocalRot = m_hips.localRotation;
@ -140,76 +138,11 @@ namespace ml_lme
}
}
// Tracking update
void UpdateFingers(GestureMatcher.LeapData p_data)
{
if(p_data.m_leftHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb1Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb2Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb3Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumbSpread, Mathf.LerpUnclamped(-1.5f, 1.0f, p_data.m_leftHand.m_spreads[0])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndexSpread, Mathf.LerpUnclamped(1f, -1f, p_data.m_leftHand.m_spreads[1])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddleSpread, Mathf.LerpUnclamped(2f, -2f, p_data.m_leftHand.m_spreads[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRingSpread, Mathf.LerpUnclamped(-2f, 2f, p_data.m_leftHand.m_spreads[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittleSpread, Mathf.LerpUnclamped(-0.5f, 1f, p_data.m_leftHand.m_spreads[4]));
}
if(p_data.m_rightHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb1Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb2Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb3Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumbSpread, Mathf.LerpUnclamped(-1.5f, 1.0f, p_data.m_rightHand.m_spreads[0])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndexSpread, Mathf.LerpUnclamped(1f, -1f, p_data.m_rightHand.m_spreads[1])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddleSpread, Mathf.LerpUnclamped(2f, -2f, p_data.m_rightHand.m_spreads[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRingSpread, Mathf.LerpUnclamped(-2f, 2f, p_data.m_rightHand.m_spreads[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittleSpread, Mathf.LerpUnclamped(-0.5f, 1f, p_data.m_rightHand.m_spreads[4]));
}
}
// Game events
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_leftArmIK = null;
m_rightArmIK = null;
m_leftTargetActive = false;
@ -256,11 +189,11 @@ namespace ml_lme
Transform l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand);
if(l_hand != null)
m_leftHandTarget.localRotation = (m_inVR ? ms_offsetLeft : ms_offsetLeftDesktop) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
m_leftHandTarget.localRotation = ms_offsetLeft * (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_inVR ? ms_offsetRight : ms_offsetRightDesktop) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
m_rightHandTarget.localRotation = ms_offsetRight * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
if(m_vrIK == null)
{
@ -304,10 +237,6 @@ namespace ml_lme
}
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;
}
@ -317,44 +246,56 @@ namespace ml_lme
}
}
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;
}
}
// IK updates
// VRIK updates
void OnIKPreUpdate()
{
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) || Mathf.Approximately(m_armsWeights.y, 0f)))
if(m_leftTargetActive)
{
m_vrIKInfo.m_leftHandTarget = m_vrIK.solver.leftArm.target;
m_vrIKInfo.m_armsWeights.x = m_vrIK.solver.leftArm.positionWeight;
m_vrIKInfo.m_armsWeights.y = m_vrIK.solver.leftArm.rotationWeight;
m_vrIKInfo.m_leftElbowTarget = m_vrIK.solver.leftArm.bendGoal;
m_vrIKInfo.m_elbowsWeights.x = m_vrIK.solver.leftArm.bendGoalWeight;
m_vrIK.solver.leftArm.target = m_leftHandTarget;
m_vrIK.solver.leftArm.positionWeight = 1f;
m_vrIK.solver.leftArm.rotationWeight = 1f;
m_vrIK.solver.leftArm.bendGoal = LeapTracking.Instance.GetLeftElbow();
m_vrIK.solver.leftArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
}
if(m_rightTargetActive && (Mathf.Approximately(m_armsWeights.z, 0f) || Mathf.Approximately(m_armsWeights.w, 0f)))
if(m_rightTargetActive)
{
m_vrIKInfo.m_rightHandTarget = m_vrIK.solver.rightArm.target;
m_vrIKInfo.m_armsWeights.z = m_vrIK.solver.rightArm.positionWeight;
m_vrIKInfo.m_armsWeights.w = m_vrIK.solver.rightArm.rotationWeight;
m_vrIKInfo.m_rightElbowTarget = m_vrIK.solver.rightArm.bendGoal;
m_vrIKInfo.m_elbowsWeights.y = m_vrIK.solver.rightArm.bendGoalWeight;
m_vrIK.solver.rightArm.target = m_rightHandTarget;
m_vrIK.solver.rightArm.positionWeight = 1f;
m_vrIK.solver.rightArm.rotationWeight = 1f;
m_vrIK.solver.rightArm.bendGoal = LeapTracking.Instance.GetRightElbow();
m_vrIK.solver.rightArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
}
}
void OnIKPostUpdate()
{
m_vrIK.solver.leftArm.positionWeight = m_armsWeights.x;
m_vrIK.solver.leftArm.rotationWeight = m_armsWeights.y;
m_vrIK.solver.rightArm.positionWeight = m_armsWeights.z;
m_vrIK.solver.rightArm.rotationWeight = m_armsWeights.w;
if(m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = m_vrIKInfo.m_leftHandTarget;
m_vrIK.solver.leftArm.positionWeight = m_vrIKInfo.m_armsWeights.x;
m_vrIK.solver.leftArm.rotationWeight = m_vrIKInfo.m_armsWeights.y;
m_vrIK.solver.leftArm.bendGoal = m_vrIKInfo.m_leftElbowTarget;
m_vrIK.solver.leftArm.bendGoalWeight = m_vrIKInfo.m_elbowsWeights.x;
}
if(m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_vrIKInfo.m_rightHandTarget;
m_vrIK.solver.rightArm.positionWeight = m_vrIKInfo.m_armsWeights.z;
m_vrIK.solver.rightArm.rotationWeight = m_vrIKInfo.m_armsWeights.w;
m_vrIK.solver.rightArm.bendGoal = m_vrIKInfo.m_rightElbowTarget;
m_vrIK.solver.rightArm.bendGoalWeight = m_vrIKInfo.m_elbowsWeights.y;
}
}
// Settings
@ -363,8 +304,7 @@ namespace ml_lme
m_enabled = p_state;
RefreshArmIK();
if(!m_enabled || m_fingersOnly)
RestoreVRIK();
ResetTargetsStates();
}
void OnFingersOnlyChange(bool p_state)
@ -372,8 +312,7 @@ namespace ml_lme
m_fingersOnly = p_state;
RefreshArmIK();
if(!m_enabled || m_fingersOnly)
RestoreVRIK();
ResetTargetsStates();
}
void OnTrackElbowsChange(bool p_state)
@ -386,30 +325,15 @@ namespace ml_lme
m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
}
RestoreVRIK();
ResetTargetsStates();
}
// Arbitrary
void RestoreVRIK()
void ResetTargetsStates()
{
if(m_vrIK != null)
{
if(m_leftTargetActive)
{
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 = m_origRightHand;
m_vrIK.solver.rightArm.bendGoal = m_origRightElbow;
m_vrIK.solver.rightArm.bendGoalWeight = ((m_origRightElbow != null) ? 1f : 0f);
m_rightTargetActive = false;
}
}
}
void RefreshArmIK()
{
@ -420,6 +344,65 @@ namespace ml_lme
}
}
void UpdateFingers(LeapParser.LeapData p_data)
{
if(p_data.m_leftHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb1Stretched, -0.5f - p_data.m_leftHand.m_bends[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb2Stretched, 0.7f - p_data.m_leftHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb3Stretched, 0.7f - p_data.m_leftHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumbSpread, -p_data.m_leftHand.m_spreads[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex1Stretched, 0.5f - p_data.m_leftHand.m_bends[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex2Stretched, 0.7f - p_data.m_leftHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex3Stretched, 0.7f - p_data.m_leftHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndexSpread, p_data.m_leftHand.m_spreads[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle1Stretched, 0.5f - p_data.m_leftHand.m_bends[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle2Stretched, 0.7f - p_data.m_leftHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle3Stretched, 0.7f - p_data.m_leftHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddleSpread, p_data.m_leftHand.m_spreads[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing1Stretched, 0.5f - p_data.m_leftHand.m_bends[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing2Stretched, 0.7f - p_data.m_leftHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing3Stretched, 0.7f - p_data.m_leftHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRingSpread, -p_data.m_leftHand.m_spreads[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle1Stretched, 0.5f - p_data.m_leftHand.m_bends[4]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle2Stretched, 0.7f - p_data.m_leftHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle3Stretched, 0.7f - p_data.m_leftHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittleSpread, -p_data.m_leftHand.m_spreads[4]);
}
if(p_data.m_rightHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb1Stretched, -0.5f - p_data.m_rightHand.m_bends[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb2Stretched, 0.7f - p_data.m_rightHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb3Stretched, 0.7f - p_data.m_rightHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumbSpread, -p_data.m_rightHand.m_spreads[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex1Stretched, 0.5f - p_data.m_rightHand.m_bends[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex2Stretched, 0.7f - p_data.m_rightHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex3Stretched, 0.7f - p_data.m_rightHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndexSpread, p_data.m_rightHand.m_spreads[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle1Stretched, 0.5f - p_data.m_rightHand.m_bends[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle2Stretched, 0.7f - p_data.m_rightHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle3Stretched, 0.7f - p_data.m_rightHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddleSpread, p_data.m_rightHand.m_spreads[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing1Stretched, 0.5f - p_data.m_rightHand.m_bends[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing2Stretched, 0.7f - p_data.m_rightHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing3Stretched, 0.7f - p_data.m_rightHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRingSpread, -p_data.m_rightHand.m_spreads[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle1Stretched, 0.5f - p_data.m_rightHand.m_bends[4]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle2Stretched, 0.7f - p_data.m_rightHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle3Stretched, 0.7f - p_data.m_rightHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittleSpread, -p_data.m_rightHand.m_spreads[4]);
}
}
static void UpdatePoseMuscle(ref HumanPose p_pose, int p_index, float p_value)
{
if(p_pose.muscles.Length > p_index)

View file

@ -102,8 +102,36 @@ namespace ml_lme
if(Instance == this)
Instance = null;
if(m_leapHandLeft != null)
Object.Destroy(m_leapHandLeft);
m_leapHandLeft = null;
if(m_leapHandRight != null)
Object.Destroy(m_leapHandRight);
m_leapHandRight = null;
if(m_leapElbowLeft != null)
Object.Destroy(m_leapElbowLeft);
m_leapElbowLeft = null;
if(m_leapElbowRight != null)
Object.Destroy(m_leapElbowRight);
m_leapElbowRight = null;
if(m_leapControllerModel != null)
Object.Destroy(m_leapControllerModel);
m_leapControllerModel = null;
if(m_visualHands != null)
Object.Destroy(m_visualHands);
m_visualHands = null;
m_visualHandLeft = null;
m_visualHandRight = null;
Settings.DesktopOffsetChange -= this.OnDesktopOffsetChange;
Settings.ModelVisibilityChange -= this.OnModelVisibilityChange;
Settings.VisualHandsChange -= this.OnVisualHandsChange;
Settings.TrackingModeChange -= this.OnTrackingModeChange;
Settings.RootAngleChange -= this.OnRootAngleChange;
Settings.HeadAttachChange -= this.OnHeadAttachChange;
@ -114,7 +142,7 @@ namespace ml_lme
{
if(Settings.Enabled)
{
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData();
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if(l_data.m_leftHand.m_present)
{

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using System.Collections;
using System.Reflection;
using UnityEngine;
@ -33,11 +32,6 @@ 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,
@ -57,6 +51,10 @@ namespace ml_lme
{
if(ms_instance == this)
ms_instance = null;
if(m_leapManager != null)
Object.Destroy(m_leapManager);
m_leapManager = null;
}
IEnumerator WaitForRootLogic()
@ -96,20 +94,6 @@ namespace ml_lme
}
}
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)
{

View file

@ -1,9 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ml_lme
{

View file

@ -1,7 +1,6 @@
using System.Reflection;
[assembly: MelonLoader.MelonInfo(typeof(ml_lme.LeapMotionExtension), "LeapMotionExtension", "1.4.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_lme.LeapMotionExtension), "LeapMotionExtension", "1.4.5", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonOptionalDependencies("ml_pmc")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonLoader.MelonAdditionalCredits("NotAKidOnSteam")]

View file

@ -4,9 +4,9 @@ using System.Reflection;
namespace ml_lme
{
static class Scripts
static class ResourcesHandler
{
public static string GetEmbeddedScript(string p_name)
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.InteractionSystem;
using cohtml;
using System;
using System.Collections.Generic;
using UnityEngine;
@ -32,7 +31,7 @@ namespace ml_lme
HeadY,
HeadZ,
TrackElbows,
Input,
Interaction,
Gestures,
InteractThreadhold,
GripThreadhold,
@ -48,7 +47,7 @@ namespace ml_lme
public static bool HeadAttach { get; private set; } = false;
public static Vector3 HeadOffset { get; private set; } = new Vector3(0f, -0.3f, 0.15f);
public static bool TrackElbows { get; private set; } = true;
public static bool Input { get; private set; } = true;
public static bool Interaction { get; private set; } = true;
public static bool Gestures { get; private set; } = false;
public static float InteractThreadhold { get; private set; } = 0.8f;
public static float GripThreadhold { get; private set; } = 0.4f;
@ -66,7 +65,7 @@ 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<bool> InteractionChange;
static public event Action<bool> GesturesChange;
static public event Action<float> InteractThreadholdChange;
static public event Action<float> GripThreadholdChange;
@ -93,7 +92,7 @@ namespace ml_lme
ms_category.CreateEntry(ModSetting.HeadY.ToString(), (int)(HeadOffset.y * 100f)),
ms_category.CreateEntry(ModSetting.HeadZ.ToString(), (int)(HeadOffset.z * 100f)),
ms_category.CreateEntry(ModSetting.TrackElbows.ToString(), TrackElbows),
ms_category.CreateEntry(ModSetting.Input.ToString(), Input),
ms_category.CreateEntry(ModSetting.Interaction.ToString(), Interaction),
ms_category.CreateEntry(ModSetting.Gestures.ToString(), Gestures),
ms_category.CreateEntry(ModSetting.InteractThreadhold.ToString(), (int)(InteractThreadhold * 100f)),
ms_category.CreateEntry(ModSetting.GripThreadhold.ToString(), (int)(GripThreadhold * 100f)),
@ -116,15 +115,16 @@ namespace ml_lme
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_LME_Call_InpToggle", new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_LME_Call_InpSlider", new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_LME_Call_InpDropdown", new Action<string, string>(OnDropdownUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnDropdownUpdate_" + ms_category.Identifier, new Action<string, string>(OnDropdownUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingLME", l_entry.DisplayName, l_entry.GetValueAsString());
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
@ -151,7 +151,7 @@ namespace ml_lme
(int)ms_entries[(int)ModSetting.HeadZ].BoxedValue
) * 0.01f;
TrackElbows = (bool)ms_entries[(int)ModSetting.TrackElbows].BoxedValue;
Input = (bool)ms_entries[(int)ModSetting.Input].BoxedValue;
Interaction = (bool)ms_entries[(int)ModSetting.Interaction].BoxedValue;
Gestures = (bool)ms_entries[(int)ModSetting.Gestures].BoxedValue;
InteractThreadhold = (int)ms_entries[(int)ModSetting.InteractThreadhold].BoxedValue * 0.01f;
GripThreadhold = (int)ms_entries[(int)ModSetting.GripThreadhold].BoxedValue * 0.01f;
@ -199,10 +199,10 @@ namespace ml_lme
}
break;
case ModSetting.Input:
case ModSetting.Interaction:
{
Input = bool.Parse(p_value);
InputChange?.Invoke(Input);
Interaction = bool.Parse(p_value);
InteractionChange?.Invoke(Interaction);
}
break;

View file

@ -1,8 +1,6 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.InputManagement;
using System.Linq;
using System.Reflection;
using UnityEngine;
@ -10,10 +8,12 @@ namespace ml_lme
{
static class Utils
{
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsInVR() => ((CheckVR.Instance != null) && CheckVR.Instance.hasVrDeviceLoaded);
public static bool AreKnucklesInUse() => ((CVRInputManager.Instance._leftController == ABI_RC.Systems.InputManagement.XR.EXRControllerType.Index) || (CVRInputManager.Instance._rightController == ABI_RC.Systems.InputManagement.XR.EXRControllerType.Index));
public static bool IsLeftHandTracked() => (CVRInputManager.Instance._leftController != ABI_RC.Systems.InputManagement.XR.EXRControllerType.None);
public static bool IsRightHandTracked() => (CVRInputManager.Instance._rightController != ABI_RC.Systems.InputManagement.XR.EXRControllerType.None);
public static bool AreKnucklesInUse() => ((CVRInputManager.Instance._leftController == ABI_RC.Systems.InputManagement.XR.eXRControllerType.Index) || (CVRInputManager.Instance._rightController == ABI_RC.Systems.InputManagement.XR.eXRControllerType.Index));
public static bool IsLeftHandTracked() => (CVRInputManager.Instance._leftController != ABI_RC.Systems.InputManagement.XR.eXRControllerType.None);
public static bool IsRightHandTracked() => (CVRInputManager.Instance._rightController != ABI_RC.Systems.InputManagement.XR.eXRControllerType.None);
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{
@ -31,6 +31,8 @@ namespace ml_lme
}
}
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
public static void Swap<T>(ref T lhs, ref T rhs)
{
T temp = lhs;

View file

@ -4,17 +4,14 @@ namespace ml_lme
{
class VisualHand
{
Transform m_root = null;
Transform m_wrist = null;
Transform[] m_fingers = null;
readonly Transform m_wrist = null;
readonly Transform[] m_fingers = null;
public VisualHand(Transform p_root, bool p_left)
{
m_root = p_root;
if(m_root != null)
if(p_root != null)
{
m_wrist = m_root.Find(p_left ? "LeftHand/Wrist" : "RightHand/Wrist");
m_wrist = p_root.Find(p_left ? "LeftHand/Wrist" : "RightHand/Wrist");
if(m_wrist != null)
{
m_fingers = new Transform[20];
@ -47,7 +44,7 @@ namespace ml_lme
}
}
public void Update(GestureMatcher.HandData p_data)
public void Update(LeapParser.HandData p_data)
{
if(m_wrist != null)
{

View file

@ -4,7 +4,7 @@
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>LeapMotionExtension</PackageId>
<Version>1.4.0</Version>
<Version>1.4.5</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>LeapMotionExtension</Product>
@ -25,15 +25,15 @@
<None Remove="LeapMotionExtension.json" />
<None Remove="resources\leapmotion_controller.asset" />
<None Remove="resources\leapmotion_hands.asset" />
<None Remove="resources\menu.js" />
<None Remove="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\leapmotion_controller.asset" />
<EmbeddedResource Include="resources\leapmotion_hands.asset" />
<EmbeddedResource Include="resources\menu.js" />
<EmbeddedResource Include="resources\mod_menu.js" />
<EmbeddedResource Include="vendor\LeapSDK\lib\x64\LeapC.dll">
<Link>LeapC.dll</Link>
<Link>resources/LeapC.dll</Link>
</EmbeddedResource>
</ItemGroup>
@ -41,50 +41,62 @@
<Reference Include="0Harmony">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="ml_pmc">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_pmc.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.AssetBundleModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AssetBundleModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.XRModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.XRModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
</ItemGroup>
@ -92,6 +104,10 @@
<Folder Include="vendor\LeapCSharp\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>

View file

@ -1,442 +0,0 @@
// Add settings
var g_modSettingsLME = [];
engine.on('updateModSettingLME', function (_name, _value) {
for (var i = 0; i < g_modSettingsLME.length; i++) {
if (g_modSettingsLME[i].name == _name) {
g_modSettingsLME[i].updateValue(_value);
break;
}
}
});
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_toggle_mod_lme(_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_lme(_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
}
}
function inp_dropdown_mod_lme(_obj, _callbackName) {
this.obj = _obj;
this.callbackName = _callbackName;
this.value = _obj.getAttribute('data-current');
this.options = _obj.getAttribute('data-options').split(',');
this.name = _obj.id;
this.opened = false;
this.keyValue = [];
this.type = _obj.getAttribute('data-type');
this.optionElements = [];
var self = this;
this.SelectValue = function (_e) {
self.value = _e.target.getAttribute('data-key');
self.valueElement.innerHTML = _e.target.getAttribute('data-value');
self.globalClose();
engine.call(self.callbackName, self.name, self.value);
}
this.openClick = function (_e) {
if (self.obj.classList.contains('open')) {
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
} else {
self.obj.classList.add('open');
self.list.setAttribute('style', 'display: block;');
self.opened = true;
window.setTimeout(function () { self.opened = false; }, 10);
}
}
this.globalClose = function (_e) {
if (self.opened) return;
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
}
this.list = document.createElement('div');
this.list.className = 'valueList';
this.updateOptions = function () {
self.list.innerHTML = "";
for (var i = 0; i < self.options.length; i++) {
self.optionElements[i] = document.createElement('div');
self.optionElements[i].className = 'listValue';
var valuePair = Array.isArray(self.options[i]) ? self.options[i] : self.options[i].split(':');
var key = "";
var value = "";
if (valuePair.length == 1) {
key = valuePair[0];
value = valuePair[0];
} else {
key = valuePair[0];
value = valuePair[1];
}
self.keyValue[key] = value;
self.optionElements[i].innerHTML = value;
self.optionElements[i].setAttribute('data-value', value);
self.optionElements[i].setAttribute('data-key', key);
self.list.appendChild(self.optionElements[i]);
self.optionElements[i].addEventListener('mousedown', self.SelectValue);
}
self.valueElement.innerHTML = self.keyValue[self.value];
}
this.valueElement = document.createElement('div');
this.valueElement.className = 'dropdown-value';
this.updateOptions();
this.obj.appendChild(this.valueElement);
this.obj.appendChild(this.list);
this.valueElement.addEventListener('mousedown', this.openClick);
document.addEventListener('mousedown', this.globalClose);
this.getValue = function () {
return self.value;
}
this.updateValue = function (value) {
self.value = value;
self.valueElement.innerHTML = self.keyValue[value];
}
this.setOptions = function (options) {
self.options = options;
}
return {
name: this.name,
value: this.getValue,
updateValue: this.updateValue,
updateOptions: this.updateOptions,
setOptions: this.setOptions
}
}
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Leap Motion tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enable tracking: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Tracking mode: </div>
<div class ="option-input">
<div id="Mode" class ="inp_dropdown no-scroll" data-options="0:Screentop,1:Desktop,2:HMD" data-current="1"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset X: </div>
<div class ="option-input">
<div id="DesktopX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Y: </div>
<div class ="option-input">
<div id="DesktopY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-45"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Z: </div>
<div class ="option-input">
<div id="DesktopZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Attach to head: </div>
<div class ="option-input">
<div id="Head" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset X: </div>
<div class ="option-input">
<div id="HeadX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Y: </div>
<div class ="option-input">
<div id="HeadY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Z: </div>
<div class ="option-input">
<div id="HeadZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="15"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle X: </div>
<div class ="option-input">
<div id="AngleX" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Y: </div>
<div class ="option-input">
<div id="AngleY" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Z: </div>
<div class ="option-input">
<div id="AngleZ" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Track elbows: </div>
<div class ="option-input">
<div id="TrackElbows" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Fingers tracking only: </div>
<div class ="option-input">
<div id="FingersOnly" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Model visibility: </div>
<div class ="option-input">
<div id="Model" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Visualize hands: </div>
<div class ="option-input">
<div id="VisualHands" 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">Recognize Gestures: </div>
<div class ="option-input">
<div id="Gestures" 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);
// Update toggles in new menu block
let l_toggles = l_block.querySelectorAll('.inp_toggle');
for (var i = 0; i < l_toggles.length; i++) {
g_modSettingsLME[g_modSettingsLME.length] = new inp_toggle_mod_lme(l_toggles[i], 'MelonMod_LME_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_modSettingsLME[g_modSettingsLME.length] = new inp_slider_mod_lme(l_sliders[i], 'MelonMod_LME_Call_InpSlider');
}
//Update dropdowns in new menu block
let l_dropdowns = l_block.querySelectorAll('.inp_dropdown');
for (var i = 0; i < l_dropdowns.length; i++) {
g_modSettingsLME[g_modSettingsLME.length] = new inp_dropdown_mod_lme(l_dropdowns[i], 'MelonMod_LME_Call_InpDropdown');
}
}

View file

@ -0,0 +1,162 @@
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Leap Motion tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enable tracking: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Tracking mode: </div>
<div class ="option-input">
<div id="Mode" class ="inp_dropdown no-scroll" data-options="0:Screentop,1:Desktop,2:HMD" data-current="1"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset X: </div>
<div class ="option-input">
<div id="DesktopX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Y: </div>
<div class ="option-input">
<div id="DesktopY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-45"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Z: </div>
<div class ="option-input">
<div id="DesktopZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Attach to head: </div>
<div class ="option-input">
<div id="Head" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset X: </div>
<div class ="option-input">
<div id="HeadX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Y: </div>
<div class ="option-input">
<div id="HeadY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Z: </div>
<div class ="option-input">
<div id="HeadZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="15"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle X: </div>
<div class ="option-input">
<div id="AngleX" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Y: </div>
<div class ="option-input">
<div id="AngleY" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Z: </div>
<div class ="option-input">
<div id="AngleZ" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Track elbows: </div>
<div class ="option-input">
<div id="TrackElbows" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Fingers tracking only: </div>
<div class ="option-input">
<div id="FingersOnly" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Model visibility: </div>
<div class ="option-input">
<div id="Model" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Visualize hands: </div>
<div class ="option-input">
<div id="VisualHands" 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="Interaction" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Recognize gestures: </div>
<div class ="option-input">
<div id="Gestures" 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);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('LME', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_LME'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('LME', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_LME'));
// Dropdowns
for (let l_dropdown of l_block.querySelectorAll('.inp_dropdown'))
modsExtension.addSetting('LME', l_dropdown.id, modsExtension.createDropdown(l_dropdown, 'OnDropdownUpdate_LME'));
}

View file

@ -18,6 +18,7 @@ namespace Leap
///
/// @since 1.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public class Config
{
private Connection _connection;
@ -29,12 +30,15 @@ namespace Leap
/// Note that the Controller.Config provides a properly initialized Config object already.
/// @since 3.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public Config(Connection.Key connectionKey)
{
_connection = Connection.GetConnection(connectionKey);
_connection.LeapConfigChange += handleConfigChange;
_connection.LeapConfigResponse += handleConfigResponse;
}
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public Config(int connectionId) : this(new Connection.Key(connectionId)) { }
private void handleConfigChange(object sender, ConfigChangeEventArgs eventArgs)
@ -87,6 +91,7 @@ namespace Leap
///
/// @since 3.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool Get<T>(string key, Action<T> onResult)
{
uint requestId = _connection.GetConfigValue(key);
@ -107,6 +112,7 @@ namespace Leap
///
/// @since 3.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool Set<T>(string key, T value, Action<bool> onResult) where T : IConvertible
{
uint requestId = _connection.SetConfigValue<T>(key, value);
@ -123,6 +129,7 @@ namespace Leap
/// Enumerates the possible data types for configuration values.
/// @since 1.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public enum ValueType
{
TYPE_UNKNOWN = 0,

View file

@ -359,9 +359,6 @@ namespace LeapInternal
StructMarshal<LEAP_CONFIG_CHANGE_EVENT>.PtrToStruct(_msg.eventStructPtr, out config_change_evt);
handleConfigChange(ref config_change_evt);
break;
case eLeapEventType.eLeapEventType_ConfigResponse:
handleConfigResponse(ref _msg);
break;
case eLeapEventType.eLeapEventType_DroppedFrame:
LEAP_DROPPED_FRAME_EVENT dropped_frame_evt;
StructMarshal<LEAP_DROPPED_FRAME_EVENT>.PtrToStruct(_msg.eventStructPtr, out dropped_frame_evt);
@ -749,55 +746,6 @@ namespace LeapInternal
}
}
private void handleConfigResponse(ref LEAP_CONNECTION_MESSAGE configMsg)
{
LEAP_CONFIG_RESPONSE_EVENT config_response_evt;
StructMarshal<LEAP_CONFIG_RESPONSE_EVENT>.PtrToStruct(configMsg.eventStructPtr, out config_response_evt);
string config_key = "";
_configRequests.TryGetValue(config_response_evt.requestId, out config_key);
if (config_key != null)
_configRequests.Remove(config_response_evt.requestId);
Config.ValueType dataType;
object value;
uint requestId = config_response_evt.requestId;
if (config_response_evt.value.type != eLeapValueType.eLeapValueType_String)
{
switch (config_response_evt.value.type)
{
case eLeapValueType.eLeapValueType_Boolean:
dataType = Config.ValueType.TYPE_BOOLEAN;
value = config_response_evt.value.boolValue;
break;
case eLeapValueType.eLeapValueType_Int32:
dataType = Config.ValueType.TYPE_INT32;
value = config_response_evt.value.intValue;
break;
case eLeapValueType.eLeapValueType_Float:
dataType = Config.ValueType.TYPE_FLOAT;
value = config_response_evt.value.floatValue;
break;
default:
dataType = Config.ValueType.TYPE_UNKNOWN;
value = new object();
break;
}
}
else
{
LEAP_CONFIG_RESPONSE_EVENT_WITH_REF_TYPE config_ref_value;
StructMarshal<LEAP_CONFIG_RESPONSE_EVENT_WITH_REF_TYPE>.PtrToStruct(configMsg.eventStructPtr, out config_ref_value);
dataType = Config.ValueType.TYPE_STRING;
value = config_ref_value.value.stringValue;
}
SetConfigResponseEventArgs args = new SetConfigResponseEventArgs(config_key, dataType, value, requestId);
if (LeapConfigResponse != null)
{
LeapConfigResponse.DispatchOnContext(this, EventContext, args);
}
}
private void reportLogMessage(ref LEAP_LOG_EVENT logMsg)
{
if (LeapLogEvent != null)

View file

@ -43,7 +43,6 @@ namespace Leap
bool _disposed = false;
bool _supportsMultipleDevices = true;
string _serverNamespace = "Leap Service";
Config _config;
/// <summary>
/// The SynchronizationContext used for dispatching events.
@ -871,13 +870,12 @@ namespace Leap
///
/// @since 1.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public Config Config
{
get
{
if (_config == null)
_config = new Config(this._connection.ConnectionKey);
return _config;
return null;
}
}

View file

@ -121,14 +121,6 @@ namespace Leap
IsStreaming = true;
else
IsStreaming = false;
if ((status & eLeapDeviceStatus.eLeapDeviceStatus_Smudged) == eLeapDeviceStatus.eLeapDeviceStatus_Smudged)
IsSmudged = true;
else
IsSmudged = false;
if ((status & eLeapDeviceStatus.eLeapDeviceStatus_Robust) == eLeapDeviceStatus.eLeapDeviceStatus_Robust)
IsLightingBad = true;
else
IsLightingBad = false;
if ((status & eLeapDeviceStatus.eLeapDeviceStatus_LowResource) == eLeapDeviceStatus.eLeapDeviceStatus_LowResource)
IsLowResource = true;
else
@ -263,33 +255,61 @@ namespace Leap
return devicePose;
}
//float[] data = new float[16];
//eLeapRS result = LeapC.GetDeviceTransform(Handle, data);
bool deviceTransformAvailable = LeapC.GetDeviceTransformAvailable(Handle);
//if (result != eLeapRS.eLeapRS_Success || data == null)
//{
// devicePose = Pose.identity;
// return devicePose;
//}
if (!deviceTransformAvailable)
{
devicePose = Pose.identity;
poseSet = true;
return Pose.identity;
}
//// Get transform matrix and convert to unity space by inverting Z.
//Matrix4x4 transformMatrix = new Matrix4x4(
// new Vector4(data[0], data[1], data[2], data[3]),
// new Vector4(data[4], data[5], data[6], data[7]),
// new Vector4(data[8], data[9], data[10], data[11]),
// new Vector4(data[12], data[13], data[14], data[15]));
//Matrix4x4 toUnity = Matrix4x4.Scale(new Vector3(1, 1, -1));
//transformMatrix = toUnity * transformMatrix;
float[] data = new float[16];
eLeapRS result = LeapC.GetDeviceTransform(Handle, data);
//// Identity matrix here means we have no device transform, also check validity.
//if (transformMatrix.isIdentity || !transformMatrix.ValidTRS())
//{
// devicePose = Pose.identity;
// return devicePose;
//}
if (result != eLeapRS.eLeapRS_Success || data == null)
{
devicePose = Pose.identity;
poseSet = true;
return Pose.identity;
}
//// Return the valid pose
//devicePose = new Pose(transformMatrix.GetColumn(3), transformMatrix.rotation);
// Using the LEAP->OPENXR device transform matrix
// Unitys matrices are generated as 4 columns:
Matrix4x4 deviceTransform = new Matrix4x4(
new Vector4(data[0], data[1], data[2], data[3]),
new Vector4(data[4], data[5], data[6], data[7]),
new Vector4(data[8], data[9], data[10], data[11]),
new Vector4(data[12], data[13], data[14], data[15]));
// An example of the expected matrix if it were 8cm forward from the head origin
// Unitys matrices are generated as 4 columns:
//Matrix4x4 deviceTransform = new Matrix4x4(
// new Vector4(-0.001f, 0, 0, 0),
// new Vector4(0, 0, -0.001f, 0),
// new Vector4(0, -0.001f, 0, 0),
// new Vector4(0, 0, -0.08f, 1));
if (deviceTransform == Matrix4x4.identity)
{
devicePose = Pose.identity;
poseSet = true;
return Pose.identity;
}
Matrix4x4 openXRToUnity = new Matrix4x4(
new Vector4(1f, 0, 0, 0),
new Vector4(0, 1f, 0, 0),
new Vector4(0, 0, -1f, 0),
new Vector4(0, 0, 0, 1));
deviceTransform = openXRToUnity * deviceTransform;
Vector3 outputPos = deviceTransform.GetPosition();
//Quaternion outputRot = deviceTransform.rotation; // Note: the matrices we receive are not rotatrion matrices. This produces unexpected results
devicePose = new Pose(outputPos, Quaternion.identity);
poseSet = true;
return devicePose;
@ -321,6 +341,7 @@ namespace Leap
/// over the Leap Motion cameras.
/// @since 3.0
/// </summary>
[Obsolete("IsSmudged is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool IsSmudged { get; internal set; }
/// <summary>
@ -335,6 +356,7 @@ namespace Leap
/// isLightingBad() is true.
/// @since 3.0
/// </summary>
[Obsolete("IsLightingBad is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool IsLightingBad { get; internal set; }
/// <summary>

View file

@ -179,6 +179,7 @@ namespace Leap
/// Provides the configuration key, whether the change was successful, and the id of the original change request.
/// @since 3.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public class SetConfigResponseEventArgs : LeapEventArgs
{
public SetConfigResponseEventArgs(string config_key, Config.ValueType dataType, object value, uint requestId) : base(LeapEvent.EVENT_CONFIG_RESPONSE)

View file

@ -24,7 +24,7 @@ namespace Leap
long Now();
bool IsConnected { get; }
Config Config { get; }
DeviceList Devices { get; }
event EventHandler<ConnectionEventArgs> Connect;

View file

@ -1052,6 +1052,9 @@ namespace LeapInternal
[DllImport("LeapC", EntryPoint = "LeapGetDeviceInfo", CharSet = CharSet.Ansi)]
public static extern eLeapRS GetDeviceInfo(IntPtr hDevice, ref LEAP_DEVICE_INFO info);
[DllImport("LeapC", EntryPoint = "LeapDeviceTransformAvailable")]
public static extern bool GetDeviceTransformAvailable(IntPtr hDevice);
[DllImport("LeapC", EntryPoint = "LeapGetDeviceTransform")]
public static extern eLeapRS GetDeviceTransform(IntPtr hDevice, [MarshalAs(UnmanagedType.LPArray, SizeConst = 16)] float[] transform);

View file

@ -41,6 +41,17 @@ namespace Leap
return new Frame().CopyFrom(frame).Transform(transform);
}
/**
* Returns a new frame that is a copy of a frame, with an additional rigid
* transformation applied to it.
*
* @param transform The transformation to be applied to the copied frame.
*/
public static Frame TransformedCopy(this Frame frame, Vector3 position, Quaternion rotation)
{
return new Frame().CopyFrom(frame).Transform(new LeapTransform(position, rotation));
}
/**
* Does an in-place rigid transformation of a Hand.
*
@ -79,6 +90,17 @@ namespace Leap
return new Hand().CopyFrom(hand).Transform(transform);
}
/**
* Returns a new hand that is a copy of a hand, with an additional rigid
* transformation applied to it.
*
* @param transform The transformation to be applied to the copied hand.
*/
public static Hand TransformedCopy(this Hand hand, Vector3 position, Quaternion rotation)
{
return new Hand().CopyFrom(hand).Transform(new LeapTransform(position, rotation));
}
/**
* Does an in-place rigid transformation of a Finger.
*

View file

@ -88,7 +88,6 @@ SRC_DIR=/usr/share/doc/ultraleap-hand-tracking-service/samples
BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=/usr/bin/ultraleap-tracking-samples
cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="${REPOS_INSTALL_ROOT}/leapc_example" `
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}"
@ -96,9 +95,32 @@ cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --config ${BUILD_TYPE}
```
### ARM64 Linux
1. Open the top level directory of the untarred release and select a build directory (eg. ~/ultraleap-tracking-samples/build) to use
2. Set the CMAKE_PREFIX_PATH directory to the absolute location of LeapSDK
3. Configure & Generate CMake with the generator of your choice
4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be :
```bash
SRC_DIR='LeapSDK/samples'
BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=~/ultraleap-tracking-samples/bin
PREFIX_PATH=$(pwd)/LeapSDK
cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="${REPOS_INSTALL_ROOT}/leapc_example"`
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" -DCMAKE_PREFIX_PATH="${PREFIX_PATH}"
cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --config ${BUILD_TYPE}
```
### MacOS
1. Open CMake using /Library/Application Support/Ultraleap/LeapSDK/samples as the source directory
1. Open CMake using /Applications/Ultraleap\ Hand\ Tracking.app/Contents/LeapSDK/samples as the source directory
2. Select a build directory (eg. ~/ultraleap-tracking-samples/build) to use
@ -107,7 +129,7 @@ cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --confi
4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be :
```bash
SRC_DIR='/Library/Application Support/Ultraleap/LeapSDK/samples'
SRC_DIR='/Applications/Ultraleap\ Hand\ Tracking.app/Contents/LeapSDK/samples'
BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=~/ultraleap-tracking-samples/bin

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more